What's New in 0.15¶
MolPy 0.15 pairs with molrs 0.15 (molcrafts-molrs>=0.15.0,<0.16). Most of
what changed for a molpy user comes from that release: the graph, table and
force-field types, the notation parsers, the native typifiers and the file
readers and writers are molrs's own, re-exported on mp by identity. molpy
0.15 also finishes moving its own copies of those layers out, so every name now
has exactly one home.
This page lists what you will notice. If you are upgrading a 0.14 script, read Upgrading from 0.14 at the end.
Highlights¶
Record files: *.mrec¶
A *.mrec store is a Zarr-v3 directory that holds a scientific record — one
snapshot, a topology, or a whole trajectory, with ragged frames and step
labels. 0.15 brings it to the molrec contract:
- One door per section:
mp.io.write_mrec/read_mrec(a frame),write_mrec_system/read_mrec_system(a topology),write_mrec_trajectory/read_mrec_trajectory(a whole trajectory), andmp.io.mrec.TrajectoryWriter/TrajectoryReaderto append or read one frame at a time.mp.io.mrec_sections(path)says what a store holds. - A force field travels in the record.
write_mrec(path, frame, forcefield=ff)(orwrite_mrec_forcefield) stores it as its ownforcefieldsection with its units declared and never converted;read_mrec_forcefield(path)returns that section, orNone, andmp.ForceField.from_section(section)turns it back into a force field. - Frame meta is stored typed: an
i32staysi32, a vector stays a vector, NaN stays NaN. - Declared precision. A float column can declare an absolute tolerance;
the writer rounds to it and compresses the column, which takes coordinates
from 24 to about 8 bytes per atom per frame at
1e-3Å. Without a declaration, floats are stored exactly. meta.molrec_versionis checked only when it is present, so a store written by another molrec producer without it opens.
import molpy as mp
frame = mp.Frame(
blocks={
"atoms": {
"type": ["OW", "HW", "HW"],
"x": [0.0, 0.9572, -0.24],
"y": [0.0, 0.0, 0.927],
"z": [0.0, 0.0, 0.0],
}
}
)
frame["atoms"].set_precision("x", 1e-3) # keep x to a thousandth of an Å
ff = mp.ForceField(name="water", units="real")
atom_style = ff.def_style("atom", "full")
ow = atom_style.def_type("OW", mass=15.999, charge=-0.834)
hw = atom_style.def_type("HW", mass=1.008, charge=0.417)
mp.io.write_mrec("water.mrec", frame, forcefield=ff)
print(sorted(mp.io.mrec_sections("water.mrec"))) # ['forcefield', 'frame', 'meta']
ff_back = mp.ForceField.from_section(mp.io.read_mrec_forcefield("water.mrec"))
print(ff_back.units) # real
Typed frame meta¶
frame.meta keeps insertion order and knows each value's dtype:
frame.meta.dtype(key) reports it, frame.meta.typed() hands every value out
as an mp.MetaValue. What comes back is frozen — a JSON object is a read-only
mp.MetaDocument and an array a tuple — so a nested edit is copy, edit,
store:
frame.meta["run"] = {"step": 0, "ensemble": "nvt"}
run = frame.meta["run"].copy() # a plain dict
run["step"] = 3
frame.meta["run"] = run
print(frame.meta.dtype("run"), frame.meta["run"]["step"]) # json 3
See Block and Frame.
One vocabulary for columns and blocks¶
The frame schema follows the molrec conventions (Naming Conventions):
- New canonical atom columns: forces
fxfyfz,formal_charge(integer),name,chain,altloc,icode,occupancyandb_factor. The PDB and GRO readers and writers use them (chain_id→chain,resname→res_name,atom_name→name). - New relation blocks:
constraints,drudes,virtual_sitesand, on a coarse-grained frame,members. A coarse-grained frame is nowatoms+bonds(+members), notbeads+cgbonds. - Floats are
float64only (afloat32array is widened on insert), identifier and index columns areuint64, and image flags areint32.
Force fields: built by name, compiled by PotentialCompiler¶
- A style is defined with
ff.def_style(category, name, params)and a type withstyle.def_type(name, *endpoints, **params), where the endpoints are the atom-type handles it connects. There are no per-style classes to import. mp.PotentialCompiler(ff).compile(frame)binds a force field to a typed frame and returns thePotentialsthat evaluate energies and forces, and thatmp.LBFGSminimizes.- A typifier's
typify(mol)returns a typed copy;forcefield()holds only the types it assigned,library()the whole table. - The LAMMPS writers take the frame:
write_lammps_forcefield(path, ff, frame)writes exactly the coefficients the frame's type labels use. A pair cutoff is a run setting you declare on the pair style; molpy never invents one. ForceField.mergecarries special bonds, style parameters and units, and raises on a conflicting definition instead of keeping the first.
ff.def_style("bond", "harmonic").def_type("OW-HW", ow, hw, k=450.0, r0=0.9572)
typed = mp.Frame(
blocks={
"atoms": {"x": [0.0, 1.0], "y": [0.0, 0.0], "z": [0.0, 0.0], "type": ["OW", "HW"]},
"bonds": {"atomi": [0], "atomj": [1], "type": ["OW-HW"]},
}
)
pots = mp.PotentialCompiler(ff).compile(typed)
print(round(pots.calc_energy(typed), 4)) # ½·k·(1.0 − 0.9572)² = 0.4122
See Force Field.
Harmonic impropers: K = k on LAMMPS I/O¶
The harmonic improper is E = k(χ − χ₀)², LAMMPS's own form, so its constant
is LAMMPS's K as written. molrs 0.14 halved it when writing a LAMMPS file and
doubled it when reading one. In 0.15 an improper read from a LAMMPS file is
evaluated at half the 0.14 energy (the energy LAMMPS gives it), and a force
field whose impropers you built with the kernel's k writes a K twice the
0.14 value. Bond and angle constants keep the ½k convention, and the GROMACS
reader and writer are unchanged.
Energies that change¶
- The OPLS-AA tables are regenerated from GROMACS
oplsaa.ff(2026.3): classes are the GROMACS bond types, and mixing is geometric (it was arithmetic). The LAMMPS include says so withpair_modify mix geometric. - Force fields read with
read_xml_forcefield/read_lammps_forcefieldhonour the mixing rule they declare. - GROMACS function types 2 and 3 were swapped; 3 is now Ryckaert–Bellemans.
Assembly from CGsmiles¶
A polymer is written as two CGsmiles strings — the units with their bonding
descriptors as ports, and the topology as a site graph — and built by
mp.Assembler with mp.GrowthPlacer. The same assembler backmaps a
coarse-grained model onto all-atom units with mp.SubgraphMatcher,
mp.Coarsener, mp.SitePlacer and mp.AxisOrienter. See
Assembly and
Polymer Topologies.
Typing¶
mp.typifier.AtdTypifier(parameter_set="gaff2")evaluates antechamber's atom-type tables natively (atom types only).AntechamberTypifiertypes one complete molecule through antechamber, parmchk2 and tleap;TLeapTypifierfinishes a chain assembled from typed monomers. See AmberTools Integration.
Smaller things¶
- Rigid-body transforms are chainable methods on the graph:
mol.translate(d),mol.rotate(axis, angle, about=None)andmol.scale([sx, sy, sz], about=None)move it in place and return it. - Every analysis is on
mp.compute, including the diffusion routes (EinsteinDiffusion,GreenKuboDiffusion,VACF,Plateau) and their result types. mp.Topology.from_frame(frame).connected_components()numbers the molecules of a frame, for example to fill themol_idcolumn LAMMPS needs.
Upgrading from 0.14¶
pip install -U molcrafts-molpy pulls molrs 0.15. A molrs on another minor line
fails at import molpy, naming both versions.
Everything the molrs migration guide lists
for 0.14 → 0.15 applies to the molrs names molpy re-exports (mp.Frame,
mp.Block, mp.ForceField, the mp.io readers and writers, …). The changes
most scripts hit are:
| 0.14 | 0.15 |
|---|---|
ff.def_bondstyle("harmonic").def_type("c", "h", k=..., r0=...) |
ff.def_style("bond", "harmonic").def_type("c-h", c, h, k=..., r0=...), with c, h the AtomType handles |
ff.to_potentials(frame) |
mp.PotentialCompiler(ff).compile(frame) |
write_lammps_forcefield(path, ff, precision, …, frame=None) |
write_lammps_forcefield(path, ff, frame, *, precision=…) — the frame is required and selects the coefficients |
frame.meta["run"]["step"] = 3 |
copy the document, edit it, store it back |
block.view(key), block.to_dict(), Block.from_dict(d) |
block[key], {k: block[k] for k in block}, mp.Block(d) |
angle.itom … angle.ltom |
angle.endpoints (a bond keeps itom / jtom) |
box.matrix |
box.h |
read_trr, read_xtc (lists), write_trr, write_xtc |
read_trr_trajectory, read_xtc_trajectory (lazy), write_trr_trajectory, write_xtc_trajectory |
mol.move(d), mol.align(...) |
mol.translate(d); compose rotate / translate |
molpy-side breaking changes¶
There are no deprecation shims: an old spelling raises AttributeError,
ImportError or TypeError, so a script that still runs is not silently on an
old path.
One public path per name.
- Names are reached from the molpy root:
mp.Box,mp.Trajectory,mp.UnitSystem,mp.Script,mp.ElementSelector, …molpy.coreno longer re-exports them. - The root no longer carries the analyses, the typifiers or per-style classes:
use
mp.compute.RDF,mp.typifier.OPLSAATypifier, andff.def_style(category, name)instead ofBondHarmonicStyle,PairLJCutCoulLongStyleand the rest. Entity,Link,Entities,GraphViews,Parameters,AtomisticForcefieldandFRAME_SCHEMA_VERSIONare gone.ConformerReportandConformerStageReportare on the root (mp.ConformerReport).Region,BoxRegion,SphereRegionandCubemoved frommp.builderto the root.
Removed subpackages.
| 0.14 | 0.15 |
|---|---|
molpy.parser |
mp.io.read_smiles, mp.SmilesIR, mp.SmartsPattern, mp.CGSmilesIR |
molpy.potential |
mp.PotentialCompiler → mp.Potentials |
molpy.optimize |
mp.LBFGS, mp.OptReport |
molpy.io.forcefield, molpy.io.trajectory, molpy.io.log and the reader / writer classes (PDBReader, LammpsDataWriter, …) |
the mp.io.read_* / mp.io.write_* functions |
mp.io.write_lammps_system(dir, frame, ff) |
mp.io.write_lammps_data + mp.io.write_lammps_forcefield |
molpy.parser.moltemplate, molpy.cli and the molpy command |
removed, no replacement |
molpy.io.emit: EMITTERS, emit, register, OpenMMEmitter, XMLEmitter |
molpy.io.emit.emitters (.emit, .names, .register) |
Building. The 0.14 polymer stack is replaced by site-graph assembly:
PolymerBuilder, GraphAssembler, MonomerLibrary, ResiduePlacer, Placer,
SiteMap, Replicas, the reaction-selector family (TopologySelector,
ProximitySelector, RandomSelector, …), AssemblyFinalizer and
AmberPolymerBuilder are gone. Write the units and the topology in CGsmiles and
build with mp.Assembler; for GAFF chains, type the monomers with
AntechamberTypifier and finish the chain with TLeapTypifier. Crosslinked
networks and gels (joining sites by proximity) are not available in 0.15.
Typing.
AmberToolsTypifier→AntechamberTypifier+TLeapTypifier;MMFFTypifier→MMFF94Typifier.ClpTypifier,SmartsTypifier,LocalTypifier,ForceFieldParams,TypeScopeandUnboundedPatternSetare removed.typifyreturns a typed copy instead of editing its input.- The bundled
oplsaa.xmlis gone:mp.typifier.OPLSAATypifier()carries the table, and.library()returns all of it.
Analysis.
IonicConductivity,DielectricSusceptibility,DebyeSpectrumFitand their result classes are removed: compose a raw compute with a fit and your own SI prefactor (PMSD, Dielectric). The composed route takes the frame spacing in femtoseconds, whereIonicConductivitytook picoseconds.mp.compute.NeighborList→mp.NeighborList;Pca→Pca2.
Other. molpy.core.ops.extract_coords(frame) → frame.coords.ravel().