Skip to content

Commit

Permalink
Merge pull request #1046 from openforcefield/add-amber-example
Browse files Browse the repository at this point in the history
Add amber example
  • Loading branch information
mattwthompson authored Aug 30, 2024
2 parents 0b946fd + 7fa5358 commit 2f2b3d1
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ repos:
- --py310-plus
- id: nbqa-ruff
files: ^examples
args:
- --fix
- repo: https://github.com/kynan/nbstripout
rev: 0.7.1
hooks:
Expand Down
1 change: 1 addition & 0 deletions devtools/conda-envs/dev_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies:
- python =3.10
- pip
- versioneer-518
- pip
- numpy
- pydantic =2
# OpenFF stack
Expand Down
14 changes: 10 additions & 4 deletions docs/using/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,18 @@ openmm_box: openmm.unit.Quantity = interchange.box.to_openmm()
An `Interchange` object can be written to Amber parameter/topology, coordinate, and SANDER run input files with [`Interchange.to_prmtop()`], [`Interchange.to_inpcrd()`], and [`Interchange.to_sander_input()`]:

```python
interchange.to_prmtop("out.prmtop")
interchange.to_inpcrd("out.inpcrd")
interchange.to_sander_input("out_pointenergy.in")
interchange.to_prmtop("mysim.prmtop")
interchange.to_inpcrd("mysim.inpcrd")
interchange.to_sander_input("mysim_pointenergy.in")
```

Note that the SANDER input file generated is configured for a single-point energy calculation and must be modified to run other simulations. Interchange cannot currently produce PMEMD input files. Amber does not implement a switching function as [commonly used](https://openforcefield.github.io/standards/standards/smirnoff/#vdw) by SMIRNOFF force fields, so these force fields will produce different results in Amber than in OpenMM or GROMACS.
The [`Interchange.to_amber()`] convenience method produces all three files in one invocation:

```python
interchange.to_amber("mysim") # Produces the same three files
```

Note that the input file generated is configured for a single-point energy calculation with sander and must be modified to run other simulations. Interchange cannot currently produce PMEMD input files. Amber does not implement a switching function as [commonly used](https://openforcefield.github.io/standards/standards/smirnoff/#vdw) by SMIRNOFF force fields, so these force fields will produce different results in Amber than in OpenMM or GROMACS.

<!--
## CHARMM
Expand Down
9 changes: 9 additions & 0 deletions examples/amber/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
amber.in
_tmp_pdb_file.pdb
amber.prmtop
amber.inpcrd
mdout
mdcrd
mdinfo
restrt
trajectory.nc
198 changes: 198 additions & 0 deletions examples/amber/amber.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Simulate an Interchange with Amber\n",
"\n",
"<details>\n",
" <summary><small>▼ Click here for dependency installation instructions</small></summary>\n",
" The simplest way to install dependencies is to use the Interchange examples environment. From the root of the cloned openff-interchange repository:\n",
" \n",
" conda env create --name interchange-examples --file devtools/conda-envs/examples_env.yaml \n",
" conda activate interchange-examples\n",
" pip install -e .\n",
" cd examples/amber\n",
" jupyter notebook amber.ipynb\n",
" \n",
"</details>\n",
"\n",
"In this example, we'll quickly construct an `Interchange` and then run a simulation in Amber. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We need an `Interchange` to get started, so let's put that together quickly. For more explanation on this process, take a look at the [packed_box] and [protein_ligand] examples.\n",
"\n",
"[packed_box]: https://github.com/openforcefield/openff-interchange/tree/main/examples/packed_box\n",
"[protein_ligand]: https://github.com/openforcefield/openff-interchange/tree/main/examples/protein_ligand"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import mdtraj\n",
"import nglview\n",
"import openmm.app\n",
"from openff.toolkit import ForceField, Molecule, Topology\n",
"from openff.toolkit.utils import get_data_file_path\n",
"\n",
"from openff.interchange import Interchange\n",
"\n",
"# Read a structure from the Toolkit's test suite into a Topology\n",
"pdbfile = openmm.app.PDBFile(get_data_file_path(\"systems/packmol_boxes/propane_methane_butanol_0.2_0.3_0.5.pdb\"))\n",
"molecules = [Molecule.from_smiles(smi) for smi in [\"CCC\", \"C\", \"CCCCO\"]]\n",
"off_topology = Topology.from_openmm(pdbfile.topology, unique_molecules=molecules)\n",
"\n",
"# Construct the Interchange with the OpenFF \"Sage\" force field\n",
"interchange = Interchange.from_smirnoff(\n",
" force_field=ForceField(\"openff-2.0.0.offxml\"),\n",
" topology=off_topology,\n",
")\n",
"interchange.positions = pdbfile.positions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tada! A beautiful solvent system:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"interchange.visualize(\"nglview\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"## Run a simulation\n",
"\n",
"We need Amber input files to run our simulation. `Interchange.to_amber` takes a (string) prefix as an argument and wraps three other methods that each write out a file needed for running a simulation in Amber:\n",
"* `mysim.prmtop` stores the chemical topology and physics paramaters\n",
"* `mysim.inpcrd` file stores coordinates\n",
"* `mysim_pointenergy.in` tells `sander` how a single-point energy \"simulation\" should be run"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"interchange.to_amber(\"mysim\")\n",
"\n",
"!ls mysim*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To get a proper simulation with a trajectory, we'll also need an input file to describe the simulation parameters a a few other details, like a thermostat and what information to write to files:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"amber_in = \"\"\"Basic Amber control file\n",
"&cntrl\n",
" imin=0, ! Run molecular dynamics.\n",
" ntx=1, ! Take positions from input and generate velocities\n",
" nstlim=500, ! Number of MD-steps to be performed.\n",
" dt=0.001, ! Time step (ps), use a low 1 ps timestep to be safe\n",
" tempi=300.0, ! Initial temperature for velocity generation\n",
" temp0=300.0, ! Thermostat temperature\n",
" cut=9.0, ! vdW cutoff (Å)\n",
" fswitch=8.0 ! vdW switching function start point (Å)\n",
" igb=0, ! Don't use a Generalized Born model\n",
" ntt=3, gamma_ln=2.0, ! Temperature scaling using Langevin dynamics with the collision frequency in gamma_ln (1/ps)\n",
" ntp=0, ! No pressure scaling\n",
" iwrap=1, ! Wrap trajectory coordinates to stay in box\n",
" ioutfm=1, ! Write out netcdf trajectory\n",
" ntwx=10, ! Frequency to write coordinates\n",
" ntpr=50, ! Frequency to log energy info\n",
" ntc=2, ! Constraints on Hydrogen-involving bonds\n",
"/\n",
"\"\"\"\n",
"with open(\"amber.in\", \"w\") as f:\n",
" f.write(amber_in)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Run the simulation with Sander:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!sander \\\n",
" -O \\\n",
" -i amber.in \\\n",
" -p mysim.prmtop \\\n",
" -c mysim.inpcrd \\\n",
" -x trajectory.nc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And finally we can visualize!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"traj = mdtraj.load(\"trajectory.nc\", top=mdtraj.load_prmtop(\"mysim.prmtop\"))\n",
"nglview.show_mdtraj(traj)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.14"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import subprocess

import numpy
import pytest
from openff.toolkit import Molecule, Quantity, Topology, unit
Expand Down Expand Up @@ -424,6 +426,15 @@ def test_from_openmm_called(self, monkeypatch, simple_interchange):
box_vectors=box,
)

def test_to_amber(self, simple_interchange):
simple_interchange.to_amber(prefix="blargh")

# Just make sure it returns a non-zero error code
subprocess.check_output(
"sander -i blargh_pointenergy.in -c blargh.inpcrd -p blargh.prmtop -o out.mdout -O",
shell=True,
)

def test_from_gromacs_called(self, monkeypatch, simple_interchange):
monkeypatch.setenv("INTERCHANGE_EXPERIMENTAL", "1")

Expand Down
36 changes: 30 additions & 6 deletions openff/interchange/components/interchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,12 +644,6 @@ def to_openmm_simulation(

return simulation

def to_prmtop(self, file_path: Path | str):
"""Export this Interchange to an Amber .prmtop file."""
from openff.interchange.interop.amber import to_prmtop

to_prmtop(self, file_path)

@requires_package("openmm")
def to_pdb(self, file_path: Path | str, include_virtual_sites: bool = False):
"""Export this Interchange to a .pdb file."""
Expand Down Expand Up @@ -687,6 +681,36 @@ def to_crd(self, file_path: Path | str):
"""Export this Interchange to a CHARMM-style .crd file."""
raise UnsupportedExportError

def to_amber(
self,
prefix: str,
):
"""
Export this Interchange object to Amber files.
Parameters
----------
prefix: str
The prefix to use for the Amber parameter/topology, coordinate, and run files, i.e.
"foo" will produce "foo.top", "foo.gro", and "foo_pointenergy.in".
Notes
-----
The run input file is configured for a single-point energy calculation with sander. It is
likely portable to pmemd with little or no work.
"""
self.to_prmtop(f"{prefix}.prmtop")
self.to_inpcrd(f"{prefix}.inpcrd")

self.to_sander_input(f"{prefix}_pointenergy.in")

def to_prmtop(self, file_path: Path | str):
"""Export this Interchange to an Amber .prmtop file."""
from openff.interchange.interop.amber import to_prmtop

to_prmtop(self, file_path)

def to_inpcrd(self, file_path: Path | str):
"""Export this Interchange to an Amber .inpcrd file."""
from openff.interchange.interop.amber import to_inpcrd
Expand Down

0 comments on commit 2f2b3d1

Please sign in to comment.