Engine¶
MD / simulation engine abstractions for LAMMPS, GROMACS, OpenMM and CP2K.
Every engine has run. LAMMPS, GROMACS and OpenMM also have one input
writer each, with one signature: generate_inputs(frame, forcefield,
output_dir, *, ...) returns the written paths by name, "input" always the
runnable input run takes. CP2K runs a CP2K input you write; it generates
no inputs.
Quick reference¶
| Symbol | Summary | Preferred for |
|---|---|---|
LammpsEngine |
generate_inputs(frame, ff, out) → input (the input script), data, settings, init; minimize / md relax a frame through the same deck |
LAMMPS decks and relaxations |
GromacsEngine |
generate_inputs(frame, ff, out) → input (em.mdp), gro, the whole top (write_top_system), nvt (nvt.mdp); run grompp's and mdrun's an .mdp |
GROMACS input sets |
OpenmmEngine |
generate_inputs(frame, ff, out, simulation=…) → input (the Python script), pdb, forcefield (XML with residue templates) |
Running OpenMM simulations |
OpenmmSimulationConfig |
OpenMM run configuration, validated on construction | Configuring an OpenMM run |
Cp2kEngine |
Runs a CP2K input you write (no generate_inputs) |
Running CP2K simulations |
Script |
An immutable (name, text, path) value; what run takes |
Writing or loading an input script |
Key behavior¶
- An engine's executable, environment (
env+env_manager),launcher,env_varsandtimeoutare its[engine.<name>]settings in molpy's configuration, falling back to[engine](molpy.config); the constructor takesconfig,workdirandcheck_executable; they are resolved by the sharedmolpy.wrapper.Toolcore run(scripts, *, workdir, capture_output, check)writes the scripts (the first is the input) and runs the engine; it keeps no state and does not change the scripts.check=Trueis the default. GROMACS'srunalso takesstructure=, the coordinatesgrompp -creads:<prefix>.groby default, the previous stage's output ("em.gro") for a later stage- Every failure is a
molpy.wrapper.ToolErrorcarryingtool("engine.lammps"),returncodeandtail— the program's stderr or, when nothing was captured, its log (log.lammps,cp2k.out, GROMACS's<stem>.log) - Every subprocess is logged to
molpy.engine.<name>(lammps,gromacs,openmm,cp2k) as mollog records carrying thecommand,cwd,returncodeandelapsed_s(GROMACS addsstep:grompp/mdrun);generate_inputslogsinputs writtenwith the files - The decks write the force field's own nonbonded settings (each pair
style's
cutoff;coul/long/pmelong-range,coul/cutcut off); an explicit argument overrides them - A frame without a periodic box is a non-periodic system in every engine:
LAMMPS shrink-wraps it, OpenMM uses
NoCutoff; a method that needs a periodic cell (Ewald / PME / PPPM,CutoffPeriodic, NPT) is refused for it, and GROMACS, periodic only, refuses it
Related¶
- Guide: Simulation Engines
- Guide: Configure and Log a Run
- API: Config
- Guide: File I/O (the data and force-field files the script reads)
Full API¶
Base¶
Engine ¶
Bases: Tool, ABC
Abstract base class for computational chemistry engines.
Concrete subclasses name their configuration table in :attr:tool
("lammps" reads [engine.lammps], falling back to [engine]) and
implement :meth:_execute and :meth:_get_default_extension. The
:class:~molpy.wrapper.Tool core resolves the settings, environment,
executable, launcher, environment variables, time limit and logger; this
class writes the scripts into the run directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
Config | None
|
molpy's configuration (:func: |
None
|
workdir
|
str | Path | None
|
Default working directory. |
None
|
check_executable
|
bool
|
Verify the executable is available at construction
time (system |
True
|
Raises:
| Type | Description |
|---|---|
ToolError
|
If check_executable is |
ValueError
|
If the configured environment is incomplete or its manager unsupported. |
Example
from molpy.config import load_config from molpy.engine import LammpsEngine, Script
script = Script.from_text("input", "units real\natom_style full\n") config = load_config({"engine": {"lammps": {"executable": "lmp"}}}) engine = LammpsEngine(config=config, check_executable=False) result = engine.run(script, workdir="./calc") print(result.returncode) 0
run ¶
Write scripts into the run directory and execute the engine.
Accepts a :class:~molpy.engine.Script, a raw string (named
input), a :class:~pathlib.Path to a script file, or a list of
scripts and paths. The first script is the engine's input; the rest
are written beside it. A script with a path is written under its file
name, any other as <name><extension>. Neither the scripts nor the
engine are changed: every call names what it runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scripts
|
Script | str | Path | Sequence[Script | Path]
|
The script(s) to run; the first is the input. |
required |
workdir
|
str | Path | None
|
Working directory for this run; |
None
|
capture_output
|
bool
|
Capture stdout/stderr. |
False
|
check
|
bool
|
Raise :class: |
True
|
Returns:
| Type | Description |
|---|---|
CompletedProcess[str]
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If scripts is an empty list. |
ToolError
|
The engine could not start, timed out, or (with check) exited non-zero. |
CP2K¶
Cp2kEngine ¶
Bases: Engine
CP2K quantum chemistry / molecular dynamics engine.
Runs CP2K input scripts. The executable is engine.cp2k.executable:
cp2k.psmp (MPI + OpenMP build) by default, or e.g. cp2k.popt
(MPI only). Settings: [engine.cp2k]; logger: molpy.engine.cp2k.
A minimal CP2K input must contain at least &GLOBAL, &FORCE_EVAL,
and &MOTION (or &ENERGY) sections.
Example
from molpy.engine import Script from molpy.engine import Cp2kEngine
inp = ( ... "&GLOBAL\n" ... " PROJECT water\n" ... " RUN_TYPE ENERGY\n" ... "&END GLOBAL\n" ... "&FORCE_EVAL\n" ... " METHOD Quickstep\n" ... "&END FORCE_EVAL\n" ... ) script = Script.from_text("input", inp) engine = Cp2kEngine(check_executable=False) result = engine.run(script, workdir="./calc") print(result.returncode) 0
LAMMPS¶
LammpsEngine ¶
Bases: Engine
LAMMPS molecular dynamics engine.
Runs LAMMPS input scripts. The engine binary is typically named lmp,
lmp_serial, or lmp_mpi depending on the build; with no
engine.lammps.executable configured, the first of those found in the
engine's environment is used, so LammpsEngine() works out of the box
on a typical install. Settings: [engine.lammps]; logger:
molpy.engine.lammps.
Example
from molpy.config import load_config from molpy.engine import LammpsEngine, Script
script = Script.from_text("input", "units real\natom_style full\nrun 0\n") engine = LammpsEngine(check_executable=False) result = engine.run(script, workdir="./calc") print(result.returncode) 0
MPI execution, as a run override::
config = load_config(
{"engine": {"lammps": {"launcher": ["mpirun", "-np", "16"]}}}
)
result = LammpsEngine(config=config).run(script, workdir="./calc")
generate_inputs ¶
generate_inputs(
frame,
forcefield,
output_dir,
*,
prefix="system",
atom_style=None,
units="real",
pair_style=None,
body=None,
)
Write a complete LAMMPS deck for frame under forcefield.
Files written (given prefix="system"):
system.data— the structure (molrs's LAMMPS data writer), in atom_style and units.system.in.settings— molrs's LAMMPS force-field include: the*_styleline of every category the frame uses (built-in or a registered force-field IR style;hybridwhen a category spans several styles), its coefficients,special_bondsandpair_modify. It is included afterread_data, where LAMMPS rejectsunits, sounitsis the init's.system.in.init—unitsandatom_style(the data file's own),boundary,neighborand, when pair_style is given, thatpair_styleline (the include then leaves its own out).system.in— the input script: init,read_data, settings,kspace_style pppm 1.0e-4when the force field's Coulomb style is long-range (coul/long/pme) and its pair style is in force, then body (a starter minimise + NVT run by default).
The nonbonded settings are the force field's own: its pair style, cutoffs and mixing are written as it states them, and only an explicit pair_style replaces them.
A frame with a periodic box gets boundary p p p and binned
neighbour lists. A frame without one (Atomistic.to_frame()
carries no box) is a non-periodic system and gets a non-periodic
deck: molrs's data writer puts the atoms inside the bounds of their
coordinates widened by 1 length unit on every side, boundary s s
s shrink-wraps that box to the atoms, and neighbor 2.0 nsq
searches all pairs, since binning needs a box wider than the pair
cutoff. A long-range Coulomb style, whose PPPM solver needs a
periodic cell, is refused for it (the policy every engine follows:
:mod:molpy.engine).
molpy names no style itself: a style molrs can write is emitted and one it cannot is refused by molrs, by name, before any file is written.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Frame
|
Typed structure ( |
required |
forcefield
|
ForceField
|
The force field the settings are written from. |
required |
output_dir
|
str | Path
|
Directory for the deck (created if absent). |
required |
prefix
|
str
|
Stem of the four file names. |
'system'
|
atom_style
|
str | None
|
LAMMPS |
None
|
units
|
str
|
LAMMPS |
'real'
|
pair_style
|
str | None
|
A |
None
|
body
|
str | None
|
Commands after the settings include; |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Path]
|
|
dict[str, Path]
|
paths. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If units is not one molrs writes, or the frame has no periodic box and the force field's Coulomb style is long-range; before any file is written. |
md ¶
md(
frame,
ff,
*,
ensemble="nve",
steps=1000,
temperature=300.0,
timestep=1.0,
seed=12345,
limit=0.1,
pair_style=None,
atom_style=None,
units="real",
workdir=None,
capture_output=False,
)
Run short MD on frame under ff and return a new frame.
A thin sibling of :meth:minimize for settling a packed box. Note that
a freshly packed box carries residual clashes; run :meth:minimize
first, or use ensemble="nve/limit", to avoid a blow-up under plain
nve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Frame
|
Input structure: periodic when it carries a periodic box
( |
required |
ff
|
ForceField
|
Typified force field. |
required |
ensemble
|
str
|
One of |
'nve'
|
steps
|
int
|
Number of MD steps. |
1000
|
temperature
|
float
|
Initial / target temperature (K). |
300.0
|
timestep
|
float
|
Timestep in units time (fs for |
1.0
|
seed
|
int
|
RNG seed for the initial velocity distribution. |
12345
|
limit
|
float
|
Per-step displacement cap (Å) for |
0.1
|
pair_style
|
str | None
|
A |
None
|
atom_style
|
str | None
|
LAMMPS |
None
|
units
|
str
|
LAMMPS |
'real'
|
workdir
|
str | Path | None
|
Working directory; temporary when |
None
|
capture_output
|
bool
|
Capture LAMMPS stdout/stderr. |
False
|
Returns:
| Type | Description |
|---|---|
Frame
|
A new :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If ensemble is unknown, or as :meth: |
minimize ¶
minimize(
frame,
ff,
*,
etol=0.0001,
ftol=1e-06,
max_iter=1000,
max_eval=10000,
pair_style=None,
atom_style=None,
units="real",
workdir=None,
capture_output=False,
)
Energy-minimise frame under force field ff and return a new frame.
Writes a LAMMPS data file and coefficient settings from frame / ff,
runs minimize, then splices the relaxed coordinates back onto a copy
of frame (topology, types, and box preserved). frame is not mutated.
Typical use is removing residual overlaps after packing::
eng = LammpsEngine()
relaxed = eng.minimize(pack_result.frame, ff)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Frame
|
Input structure: periodic when it carries a periodic box
( |
required |
ff
|
ForceField
|
Typified force field providing pair/bond/angle/... coefficients. |
required |
etol
|
float
|
Energy stopping tolerance (unitless). |
0.0001
|
ftol
|
float
|
Force stopping tolerance (force units). |
1e-06
|
max_iter
|
int
|
Maximum minimiser iterations. |
1000
|
max_eval
|
int
|
Maximum force/energy evaluations. |
10000
|
pair_style
|
str | None
|
A LAMMPS |
None
|
atom_style
|
str | None
|
LAMMPS |
None
|
units
|
str
|
LAMMPS |
'real'
|
workdir
|
str | Path | None
|
Directory for input/output files; a temporary directory is
created when |
None
|
capture_output
|
bool
|
Capture LAMMPS stdout/stderr. |
False
|
Returns:
| Type | Description |
|---|---|
Frame
|
A new :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
As :meth: |
ToolError
|
If LAMMPS fails or writes no output structure; the
error carries the tail of |
GROMACS¶
GromacsEngine ¶
Bases: Engine
GROMACS molecular dynamics engine.
The executable is the gmx driver, engine.gromacs.executable
(gmx by default; gmx_mpi, …). An input script is an .mdp
file; :meth:generate_inputs writes the structure and topology it is run
against.
Example::
engine = GromacsEngine(check_executable=False)
paths = engine.generate_inputs(frame, ff, "./md")
engine.run(Script.from_path(paths["input"]), workdir="./md")
# NVT from the minimised structure EM wrote:
engine.run(Script.from_path(paths["nvt"]), workdir="./md", structure="em.gro")
Initialise the GROMACS engine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Stem of the |
'system'
|
config
|
Config | None
|
molpy's configuration; |
None
|
workdir
|
str | Path | None
|
Default working directory. |
None
|
check_executable
|
bool
|
Verify the executable is available. |
True
|
generate_inputs ¶
generate_inputs(
frame,
forcefield,
output_dir,
*,
temperature=300.0,
coulombtype=None,
rcoulomb=None,
rvdw=None,
)
Write a ready-to-run GROMACS input set.
Files written (with the engine's prefix, "system" by default):
system.gro (initial structure), system.top (the whole
topology: the force field's directives, one [ moleculetype ] per
molecule with each row's parameters, [ system ] and
[ molecules ] — what grompp -p reads), em.mdp
(steepest-descent minimisation) and nvt.mdp (V-rescale NVT at
temperature K).
The .mdp nonbonded settings are the force field's own:
coulombtype is PME for a long-range Coulomb style
(coul/long/pme) and Cut-off for a truncated one, rcoulomb
is its Coulomb style's cutoff and rvdw its other pair styles'
(each defaulting to the other when only one is stated), and
rlist the larger. Only what the force field leaves unstated falls
back to PME and 1.2 nm; the keyword arguments override.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Frame
|
The typed structure ( |
required |
forcefield
|
ForceField
|
The force field the frame's types name. |
required |
output_dir
|
str | Path
|
Directory for the files (created if absent). |
required |
temperature
|
float
|
Reference and initial-velocity temperature (K). |
300.0
|
coulombtype
|
str | None
|
The |
None
|
rcoulomb
|
float | None
|
Coulomb cutoff (nm); |
None
|
rvdw
|
float | None
|
Van der Waals cutoff (nm); |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Path]
|
|
dict[str, Path]
|
|
dict[str, Path]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the frame has no periodic box, before any file is written: GROMACS's Verlet cutoff scheme (its only one since 2020) simulates periodic systems only, so a vacuum system needs a box larger than the molecule plus twice the cutoff. |
run ¶
grompp then mdrun the .mdp scripts starts with.
As :meth:Engine.run <molpy.engine.Engine.run>, plus the structure
the stage starts from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scripts
|
Script | str | Path | Sequence[Script | Path]
|
The |
required |
workdir
|
str | Path | None
|
Working directory for this run (see
:meth: |
None
|
structure
|
str | Path | None
|
The coordinates |
None
|
capture_output
|
bool
|
Capture stdout/stderr. |
False
|
check
|
bool
|
Raise :class: |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If scripts is an empty list. |
ToolError
|
A step could not start, timed out, or (with check) exited non-zero. |
Script¶
Script
dataclass
¶
An input script: a name, its text and the file it came from.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Logical name; an engine writes an unpathed script to
|
text |
str
|
The full text, written as is. |
path |
Path | None
|
The file it was read from, or |
from_path
classmethod
¶
The script in the file at path, named after its stem.
The text is the file's, unchanged.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the file does not exist. |
from_text
classmethod
¶
A script from a string literal.
The block is dedented and ends in exactly one newline, so an indented triple-quoted string in code becomes a flush-left file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Logical name of the script. |
required |
text
|
str
|
The script text. |
required |
path
|
str | Path | None
|
The file name an engine writes it under. |
None
|
save ¶
Write the text to path (default: :attr:path); return where.
Parent directories are created. The script itself is unchanged.
Raises:
| Type | Description |
|---|---|
ValueError
|
If no path is given and the script has none. |
OpenMM¶
OpenmmEngine ¶
Bases: Engine
OpenMM molecular dynamics engine.
Generates a complete set of OpenMM input files from MolPy
:class:~molpy.Frame and :class:~molpy.ff.forcefield.ForceField
objects. OpenMM itself is not required for input generation; it is
only needed to run the generated script.
The generated Python script can be executed directly::
python simulate.py
or via the engine::
result = engine.run(paths["input"], workdir="./output")
which runs engine.openmm.executable (the Python interpreter, python
by default) in the configured environment. Settings:
[engine.openmm]; logger: molpy.engine.openmm.
Example
from molpy.engine import OpenmmEngine, OpenmmSimulationConfig
simulation = OpenmmSimulationConfig(ensemble="NVT", n_steps=10_000) engine = OpenmmEngine(check_executable=False) paths = engine.generate_inputs(frame, ff, "./output", simulation=simulation)
paths["input"] (simulate.py), paths["pdb"], paths["forcefield"]¶
generate_inputs ¶
generate_inputs(
frame,
forcefield,
output_dir,
*,
simulation=None,
pdb_filename="system.pdb",
ff_filename="forcefield.xml",
script_filename="simulate.py",
)
Generate PDB, XML force field, and Python simulation script.
Does not require OpenMM to be installed. The PDB is molrs's
:func:~molpy.io.write_pdb; the force-field XML is
:func:~molpy.io.write_openmm_system: the force field plus a
<Residue> template for every residue of that PDB (its atom names,
types, charges and bonds), what ForceField.createSystem matches
the PDB topology against.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Frame
|
Typed :class: |
required |
forcefield
|
ForceField
|
A :class: |
required |
output_dir
|
PathLike
|
Directory where files are written (created if absent). |
required |
simulation
|
OpenmmSimulationConfig | None
|
Simulation parameters ( |
None
|
pdb_filename
|
str
|
Name for the PDB coordinate file. |
'system.pdb'
|
ff_filename
|
str
|
Name for the XML force field file. |
'forcefield.xml'
|
script_filename
|
str
|
Name for the Python simulation script. |
'simulate.py'
|
Returns:
| Type | Description |
|---|---|
dict[str, Path]
|
|
dict[str, Path]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the frame has no periodic box and simulation asks
for a periodic method ( |
OpenmmSimulationConfig
dataclass
¶
OpenmmSimulationConfig(
ensemble="NVT",
temperature=300.0,
pressure=1.0,
timestep_fs=2.0,
n_steps=500000,
nonbonded_method=None,
nonbonded_cutoff_nm=None,
constraints="HBonds",
friction_per_ps=1.0,
dcd_reporter_interval=1000,
state_reporter_interval=1000,
checkpoint_interval=10000,
output_dcd="trajectory.dcd",
output_log="simulation.log",
output_chk="checkpoint.chk",
output_pdb="minimized.pdb",
minimize_tolerance=10.0,
minimize_max_iterations=1000,
barostat_frequency=25,
platform="CUDA",
)
Configuration for an OpenMM simulation.
All fields have physical units encoded in their names. The object
round-trips through JSON via :meth:to_json / :meth:from_json.
Attributes:
| Name | Type | Description |
|---|---|---|
ensemble |
Ensemble
|
Simulation ensemble (case-sensitive). |
temperature |
float
|
Temperature in Kelvin (the thermostat's, or NVE's initial velocities'). |
pressure |
float
|
Pressure in bar (NPT only; a periodic frame only). |
timestep_fs |
float
|
Integration timestep in femtoseconds. |
n_steps |
int
|
Number of MD steps. |
nonbonded_method |
NonbondedMethod | None
|
Nonbonded treatment, by its |
nonbonded_cutoff_nm |
float | None
|
Nonbonded cutoff radius in nanometres. |
constraints |
Constraints
|
Constraint scheme — |
friction_per_ps |
float
|
Langevin friction coefficient in ps⁻¹ (NVT, NPT). |
dcd_reporter_interval |
int
|
Steps between DCD trajectory frames. |
state_reporter_interval |
int
|
Steps between log lines. |
checkpoint_interval |
int
|
Steps between checkpoint saves. |
output_dcd |
str
|
Filename for the DCD trajectory. |
output_log |
str
|
Filename for the state-data log. |
output_chk |
str
|
Filename for checkpoint files. |
output_pdb |
str
|
Filename for the minimised coordinates ( |
minimize_tolerance |
float
|
Energy minimisation tolerance in kJ mol⁻¹ nm⁻¹. |
minimize_max_iterations |
int
|
Maximum minimisation iterations. |
barostat_frequency |
int
|
MC barostat move frequency in steps (NPT only). |
platform |
str
|
OpenMM platform name ( |
Raises:
| Type | Description |
|---|---|
ValueError
|
If ensemble, nonbonded_method or constraints is not
one of the values listed above (also from :meth: |
from_dict
classmethod
¶
Construct a config from a plain dictionary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
Dict as returned by :meth: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
New |
'OpenmmSimulationConfig'
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a choice field holds an unknown value. |
from_json
classmethod
¶
Load a configuration from a JSON file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike
|
Path to a JSON file previously written by :meth: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
New |
'OpenmmSimulationConfig'
|
class: |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If path does not exist. |
ValueError
|
If a choice field holds an unknown value. |
to_dict ¶
Return a JSON-serialisable dictionary of all fields.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Plain dict with field names as keys and Python scalars as values. |
to_json ¶
Write the configuration to a JSON file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike
|
Destination file path (created or overwritten). |
required |
Raises:
| Type | Description |
|---|---|
OSError
|
If the file cannot be written. |