Quickstart¶
Two ways in. The fast path runs the whole pipeline in six lines. The full walkthrough then builds a TIP3P water box by hand — template, typing, box, export — so you see every boundary you will later automate.
The fast path: SMILES to a typed system¶
import molpy as mp
mol = mp.io.smiles.read_smiles("CCO").to_atomistic() # ethanol from SMILES (heavy atoms)
mol, _ = mp.conformer.Conformer(add_hydrogens=True, seed=42).generate(
mol
) # add hydrogens + 3D coordinates
typifier = mp.ff.typifier.OplsAaTypifier() # carries the OPLS-AA library
typed = typifier.typify(mol) # assign force-field types
ff = typifier.forcefield() # the parameters of the types just assigned
frame = typed.to_frame() # columnar arrays
print(frame["atoms"].n_rows, "typed atoms") # 9 typed atoms
That is the entire MolPy story — parse, embed, typify, convert. Every guide in this manual is a variation on those boundaries. Now do the same thing with full control, one explicit step at a time.
The full walkthrough: a TIP3P water box¶
The rest of this page builds a small TIP3P water box end to end and exports LAMMPS input files:
- A LAMMPS data file (
.data) describing the typed system - A LAMMPS force-field file (
.ff) containing TIP3P coefficients
1. Define a TIP3P water molecule¶
Atomistic is MolPy's chemistry-first container: a molecular graph where atoms
are nodes and bonds are edges. The bundled tip3p.xml is read into the store
units, Å and radians, so the coordinates are in Å too.
water_template = mp.Atomistic(name="water_tip3p")
# TIP3P geometry: r(O-H) = 0.9572 Å, H-O-H = 104.52°
o = water_template.add_atom(atomic_number=8, name="O", mass=15.999, charge=-0.834, x=0.0, y=0.0, z=0.0)
h1 = water_template.add_atom(atomic_number=1, name="H1", mass=1.008, charge=0.417, x=0.9572, y=0.0, z=0.0)
h2 = water_template.add_atom(atomic_number=1, name="H2", mass=1.008, charge=0.417, x=-0.2400, y=0.9266, z=0.0)
water_template.add_bond(o, h1, order=1)
water_template.add_bond(o, h2, order=1)
# perceive angles from the bonds, in place (returns the counts added, not self)
water_template.generate_topology(gen_angle=True, gen_dihedral=False)
print("atoms:", len(water_template.atoms), "bonds:", len(water_template.bonds))
print("angles:", len(water_template.angles))
print("atom names:", [a.get("name") for a in water_template.atoms])
2. Assign TIP3P types¶
Load the bundled tip3p.xml and put its type names on the template.
Note: nothing here has to decide a type. A rigid TIP3P water is three named sites, so the atoms, the bonds and the angle simply carry the force field's type labels; the parameters are looked up by label when the force field is compiled or exported. Reach for a typifier (
mp.ff.typifier.OplsAaTypifier, …) when the types are the unknown.
The reader names each bonded type by joining its endpoint atom types with ::
(the atom-type names contain -). A label is matched to a type name exactly, so
it is spelled the way the force field lists it:
ff = mp.io.read_openmm_forcefield(mp.resources.get_path("forcefield/tip3p.xml"))
print("bond types:", [t.name for t in ff.get_types("bond")])
print("angle types:", [t.name for t in ff.get_types("angle")])
for atom, atom_type in zip(water_template.atoms, ["tip3p-O", "tip3p-H", "tip3p-H"]):
atom["type"] = atom_type
for bond in water_template.bonds:
bond["type"] = "tip3p-O::tip3p-H"
for angle in water_template.angles:
angle["type"] = "tip3p-H::tip3p-O::tip3p-H"
print("atom types:", [a.get("type") for a in water_template.atoms])
print("bond types:", [b.get("type") for b in water_template.bonds])
print("angle types:", [a.get("type") for a in water_template.angles])
3. Instantiate and transform a molecule¶
A template is a reusable Atomistic; an instance is a copy you place in a
larger system. Transforms are deterministic rigid-body operations —
translate(delta), rotate(axis, angle, about=None) (angle in radians) and
scale([sx, sy, sz], about=None) — that move the structure in place and return
it, so they chain:
water_instance = water_template.copy().rotate(
[0.0, 0.0, 1.0], math.pi / 2.0, about=[0.0, 0.0, 0.0]
).translate([5.0, 0.0, 0.0])
print("instance center (Å):", water_instance.center().tolist())
4. Build a water box¶
Place copies on a simple 3D grid inside a cubic periodic box. (This is a deterministic grid, not a packing algorithm — for clash-free packing at target density, see Packing Systems.)
n = 4 # molecules per edge
spacing = 3.2 # Å
water_box = mp.Atomistic(name="water_box_tip3p")
for idx in range(n**3):
ix, iy, iz = idx % n, (idx // n) % n, idx // (n * n)
mol = water_template.copy().rotate([0.0, 0.0, 1.0], 0.1 * idx)
water_box.merge(mol.translate([ix * spacing, iy * spacing, iz * spacing]))
box = mp.Box(mp.core.Cell.lengths([n * spacing] * 3))
print("box lengths (Å):", box.lengths.tolist())
print("box atoms:", len(water_box.atoms), "box bonds:", len(water_box.bonds))
merge copies the type labels with the atoms, bonds and angles, so the box is
typed without a second pass.
5. Convert Atomistic to Frame¶
Frame is the columnar container — named tables plus the simulation box and
metadata. Writers operate on Frame, so this is the boundary where your edited
graph becomes exportable tables.
frame = water_box.to_frame()
frame.box = box # box is a first-class Frame attribute; writers read frame.box
# atom_style full needs a molecule id: one per connected component
frame["atoms"]["mol_id"] = mp.Topology.from_frame(frame).connected_components() + 1
print("atoms rows:", frame["atoms"].n_rows)
print("bonds rows:", frame["bonds"].n_rows)
print("angles rows:", frame["angles"].n_rows)
The LAMMPS data writer refuses a bonded frame without mol_id, so this step is
not optional.
6. Export to LAMMPS files¶
The force-field writer takes the frame as well: every type label of the frame
is looked up in ff, and only the coefficients the system uses are written.
The pair_style line needs a cutoff. A cutoff is a run setting, not a
force-field parameter, so you declare it on the pair styles; molpy never
invents one.
ff.get_style("pair", "lj/cut")["cutoff"] = 10.0
ff.get_style("pair", "coul/cut")["cutoff"] = 10.0
out_dir = Path("quickstart-output")
out_dir.mkdir(parents=True, exist_ok=True)
mp.io.write_lammps_data(out_dir / "water_box_tip3p.data", frame)
mp.io.write_lammps_forcefield(out_dir / "water_box_tip3p.ff", ff, frame)
print("wrote:", out_dir / "water_box_tip3p.data")
print("wrote:", out_dir / "water_box_tip3p.ff")
What you built¶
- A TIP3P water molecule as an editable
Atomisticgraph — derived angles, and type labels that name the bundledtip3p.xmlparameters. - 64 molecules placed deterministically in a periodic box.
- A
Framewith box attached, exported as LAMMPS data + force-field files.
Next: the Example Gallery for more workflows to copy, or the data-model tutorials to understand each object you just used.