Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify to_openmm_simulation docstring and rename to_lammps(file_path) arg to prefix #1042

Merged
merged 9 commits into from
Oct 7, 2024
37 changes: 29 additions & 8 deletions examples/lammps/lammps.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"from openmm.app import PDBFile\n",
"\n",
"from openff.interchange import Interchange\n",
"from openff.interchange.components.mdconfig import MDConfig\n",
"\n",
"# Read a structure from the Toolkit's test suite into a Topology\n",
"pdbfile = PDBFile(get_data_file_path(\"systems/packmol_boxes/propane_methane_butanol_0.2_0.3_0.5.pdb\"))\n",
Expand Down Expand Up @@ -87,14 +86,14 @@
"metadata": {},
"outputs": [],
"source": [
"interchange.to_lammps(\"interchange.lmp\")"
"interchange.to_lammps_datafile(\"interchange.lmp\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we need to write an input file for LAMMPS. Parts of these input files depend on force field parameters, so we should use a sample input file written for our interchange as a starting point. We can generate such a sample file from `MDConfig`:"
"Now we need to write an input file for LAMMPS. Parts of these input files depend on force field parameters, so we should use a sample input file written for our interchange as a starting point. We can generate such a sample file with the `to_lammps_input()` method:"
Comment on lines -97 to +96
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

]
},
{
Expand All @@ -103,17 +102,32 @@
"metadata": {},
"outputs": [],
"source": [
"mdconfig = MDConfig.from_interchange(interchange)\n",
"mdconfig.write_lammps_input(input_file=\"auto_generated.in\", interchange=interchange)\n",
"with open(\"auto_generated.in\") as f:\n",
"interchange.to_lammps_input(\"interchange_pointenergy.in\", data_file=\"interchange.lmp\")\n",
"with open(\"interchange_pointenergy.in\") as f:\n",
" print(f.read())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That sample file will only perform a single point energy calculation; here's a more complete file that includes the above parameters but will run an actual MD simulation. Note that `out.lmp` is changed to `interchange.lmp` to reflect the filename we used earlier:\n",
"Note that the `read_data` line must match the data file name - in this case, `interchange.lmp`. That's why `to_lammps_input` needs the data file name. We could alternatively use the `to_lammps` method to write both files in a consistent way:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"interchange.to_lammps(\"interchange\") # writes interchange.lmp and interchange_pointenergy.in"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That sample file will only perform a single point energy calculation; here's a more complete file that includes the above parameters but will run an actual MD simulation. \n",
"\n",
"<div class=\"alert alert-warning\" style=\"max-width: 700px; margin-left: auto; margin-right: auto;\">\n",
" <b>⚠️ Don't use example input files in production</b><br />\n",
Expand Down Expand Up @@ -208,6 +222,13 @@
"traj.make_molecules_whole()\n",
"nglview.show_mdtraj(traj)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand All @@ -227,7 +248,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.14"
"version": "3.11.0"
}
},
"nbformat": 4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from openff.interchange import Interchange
from openff.interchange._tests import MoleculeWithConformer, needs_lmp
from openff.interchange.components.mdconfig import MDConfig
from openff.interchange.drivers import get_lammps_energies, get_openmm_energies

rng = numpy.random.default_rng(821)
Expand Down Expand Up @@ -105,24 +104,17 @@ def test_unique_lammps_mol_ids(
topology.box_vectors = Quantity([4, 4, 4], "nanometer")

with temporary_cd():
lammps_input_path = Path.cwd() / "temp.in"
lammps_data_path = Path.cwd() / "out.lmp"
lammps_prefix = Path.cwd() / "lammps_test"

interchange = sage_unconstrained.create_interchange(topology)
interchange.to_lammps(lammps_data_path)

mdconfig = MDConfig.from_interchange(interchange)
mdconfig.write_lammps_input(
interchange=interchange,
input_file=lammps_input_path,
)
interchange.to_lammps(lammps_prefix)

# Extract molecule IDs from data file
with lammps.lammps(
cmdargs=["-screen", "none", "-log", "none"],
) as lmp:
lmp.file(
"temp.in",
"lammps_test_pointenergy.in",
)
written_mol_ids = {
mol_id
Expand Down
33 changes: 17 additions & 16 deletions openff/interchange/components/interchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,24 +425,20 @@ def to_gro(self, file_path: Path | str, decimal: int = 3):
gro_file=file_path,
).to_gro(decimal=decimal)

def to_lammps(self, file_path: Path | str):
def to_lammps(self, prefix: str):
"""
Export this ``Interchange`` to LAMMPS data and run input files.

Parameters
----------
file_path
The prefix to use for the LAMMPS data and run input files. If a path
ending in ".lmp" is given, the extension will be dropped to generate
the prefix. For example, both "foo" and "foo.lmp" will produce files
named "foo.lmp" and "foo_pointenergy.in".

"""
# TODO: Rename `file_path` to `prefix` (breaking change)
prefix = str(file_path)
if prefix.endswith(".lmp"):
prefix = prefix[:-4]
prefix
The prefix to use for the LAMMPS data and run input files. For
example, "foo" will produce files named "foo.lmp" and
"foo_pointenergy.in".

"""
prefix = str(prefix)
datafile_path = prefix + ".lmp"
self.to_lammps_datafile(datafile_path)
self.to_lammps_input(
Expand Down Expand Up @@ -562,6 +558,14 @@ def to_openmm_simulation(

Positions are set on the `Simulation` if present on the `Interchange`.

Additional forces, such as a barostat, should be added with the
``additional_forces`` argument to avoid having to re-initialize
the ``Context``. Re-initializing the ``Context`` after adding a
``Force`` is necessary due to `implementation details`_
in OpenMM.

.. _implementation details: https://github.com/openmm/openmm/wiki/Frequently-Asked-Questions#why-does-it-ignore-changes-i-make-to-a-system-or-force

Parameters
----------
integrator : subclass of openmm.Integrator
Expand All @@ -574,7 +578,7 @@ def to_openmm_simulation(
If True, add valence forces that might be overridden by constraints, i.e. call `addBond` or `addAngle`
on a bond or angle that is fully constrained.
additional_forces : Iterable[openmm.Force], default=tuple()
Additional forces to be added to the system, i.e. barostats that are not
Additional forces to be added to the system, e.g. barostats, that are not
added by the force field.
**kwargs
Further keyword parameters are passed on to
Expand All @@ -587,6 +591,7 @@ def to_openmm_simulation(

Examples
--------

Create an OpenMM simulation with a Langevin integrator and a Monte Carlo barostat:

>>> import openmm
Expand All @@ -607,10 +612,6 @@ def to_openmm_simulation(
... additional_forces=[barostat],
... )

Re-initializing the `Context` after adding a `Force` is necessary due to implementation details in OpenMM.
For more, see
https://github.com/openmm/openmm/wiki/Frequently-Asked-Questions#why-does-it-ignore-changes-i-make-to-a-system-or-force

"""
import openmm.app

Expand Down
10 changes: 2 additions & 8 deletions openff/interchange/drivers/lammps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from openff.utilities import MissingOptionalDependencyError, requires_package

from openff.interchange import Interchange
from openff.interchange.components.mdconfig import MDConfig
from openff.interchange.drivers.report import EnergyReport
from openff.interchange.exceptions import LAMMPSNotFoundError, LAMMPSRunError

Expand Down Expand Up @@ -63,20 +62,15 @@ def _get_lammps_energies(
)

with tempfile.TemporaryDirectory():
interchange.to_lammps("out.lmp")
mdconfig = MDConfig.from_interchange(interchange)
mdconfig.write_lammps_input(
interchange=interchange,
input_file="tmp.in",
)
interchange.to_lammps("out")

# By default, LAMMPS spits out logs to the screen, turn it off
# https://matsci.org/t/how-to-remove-or-redirect-python-lammps-stdout/38075/5
# not that this is not sent to STDOUT, so `contextlib.redirect_stdout` won't work
runner = lammps.lammps(cmdargs=["-screen", "none", "-nocite"])

try:
runner.file("tmp.in")
runner.file("out_pointenergy.in")
# LAMMPS does not raise a custom exception :(
except Exception as error:
raise LAMMPSRunError from error
Expand Down
Loading