Skip to content

Commit 5f103ed

Browse files
committed
Several changes to support using charged anions in classical MD workflows
1 parent 5f0bc75 commit 5f103ed

File tree

5 files changed

+77
-18
lines changed

5 files changed

+77
-18
lines changed

src/atomate2/openmm/jobs/base.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939

4040

4141
try:
42-
# so we can load OpenMM Interchange created by openmmml
43-
import openmmml
42+
# so we can load OpenMM Interchange created with nnpops
43+
import nnpops
44+
import openmmtorch
4445
except ImportError:
45-
openmmml = None
46+
nnpops = None
47+
openmmtorch = None
4648

4749
try:
4850
from openff.interchange import Interchange
@@ -54,7 +56,7 @@ class Interchange: # type: ignore[no-redef]
5456
def model_validate(self, _: str) -> None:
5557
"""Parse raw is the first method called on the Interchange object."""
5658
raise ImportError(
57-
"openff-interchange must be installed for OpenMM makers to"
59+
"openff-i nterchange must be installed for OpenMM makers to"
5860
"to support OpenFF Interchange objects."
5961
)
6062

src/atomate2/openmm/jobs/generate.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import copy
66
import io
77
import re
8-
import warnings
98
import xml.etree.ElementTree as ET
109
from pathlib import Path
1110
from xml.etree.ElementTree import tostring
@@ -20,7 +19,7 @@
2019
from openmm.app.pdbfile import PDBFile
2120
from openmm.unit import kelvin, picoseconds
2221
from pymatgen.core import Element
23-
from pymatgen.io.openff import get_atom_map
22+
from pymatgen.io.openff import coerce_formal_charges, get_atom_map
2423

2524
from atomate2.openff.utils import create_mol_spec, merge_specs_by_name_and_smiles
2625
from atomate2.openmm.jobs.base import openmm_job
@@ -85,12 +84,8 @@ def increment_types(self, increment: str) -> None:
8584
if type_stub in key:
8685
element.attrib[key] += increment
8786

88-
def to_openff_molecule(self) -> tk.Molecule:
87+
def to_openff_molecule(self, template_molecule: tk.Molecule = None) -> tk.Molecule:
8988
"""Convert the XMLMoleculeFF to an openff_toolkit Molecule."""
90-
if sum(self.partial_charges) > 1e-3:
91-
# TODO: update message
92-
warnings.warn("Formal charges not considered.", stacklevel=1)
93-
9489
p_table = {e.symbol: e.number for e in Element}
9590
openff_mol = tk.Molecule()
9691
for atom in self.tree.getroot().findall(".//Residues/Residue/Atom"):
@@ -108,6 +103,9 @@ def to_openff_molecule(self) -> tk.Molecule:
108103

109104
openff_mol.partial_charges = self.partial_charges * unit.elementary_charge
110105

106+
if template_molecule:
107+
openff_mol = coerce_formal_charges(openff_mol, template_molecule)
108+
111109
return openff_mol
112110

113111
@property
@@ -253,7 +251,7 @@ def generate_openmm_interchange(
253251

254252
for mol_spec, xml_mol in zip(mol_specs, xml_mols, strict=True):
255253
openff_mol = tk.Molecule.from_json(mol_spec.openff_mol)
256-
xml_openff_mol = xml_mol.to_openff_molecule()
254+
xml_openff_mol = xml_mol.to_openff_molecule(template_molecule=openff_mol)
257255
is_isomorphic, _atom_map = get_atom_map(openff_mol, xml_openff_mol)
258256
if not is_isomorphic:
259257
raise ValueError(

tests/openmm_md/flows/test_core.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_hdf5_writing(interchange, run_job):
6363
import MDAnalysis
6464
from packaging.version import Version
6565

66-
if Version(MDAnalysis.__version__) < Version("2.8.0"):
66+
if Version(MDAnalysis.__version__) < Version("2.8.0.dev0"):
6767
return
6868

6969
anneal_maker = OpenMMFlowMaker.anneal_flow(
@@ -90,6 +90,17 @@ def test_hdf5_writing(interchange, run_job):
9090
"trajectory.h5md",
9191
}
9292

93+
with io.StringIO(interchange.topology) as s:
94+
pdb = PDBFile(s)
95+
openmm_topology = pdb.getTopology()
96+
97+
traj_file = Path(task_doc.calcs_reversed[0].output.dir_name) / "trajectory3.h5md"
98+
Universe(
99+
openmm_topology,
100+
str(traj_file),
101+
format="h5md",
102+
)
103+
93104

94105
def test_collect_outputs(interchange, run_job):
95106
# Create an instance of ProductionMaker with custom parameters

tests/openmm_md/jobs/test_generate.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,18 @@ def test_xml_molecule_from_file(openmm_data):
5252

5353

5454
def test_to_openff_molecule(openmm_data):
55-
xml_mol = XMLMoleculeFF.from_file(openmm_data / "opls_xml_files" / "CO.xml")
56-
57-
mol = xml_mol.to_openff_molecule()
58-
assert len(mol.atoms) == 6
59-
assert len(mol.bonds) == 5
55+
co_xml = XMLMoleculeFF.from_file(openmm_data / "opls_xml_files" / "CO.xml")
56+
57+
co = co_xml.to_openff_molecule()
58+
assert len(co.atoms) == 6
59+
assert len(co.bonds) == 5
60+
61+
clo4_template = tk.Molecule.from_smiles("[O-]Cl(=O)(=O)=O")
62+
clo4_xml = XMLMoleculeFF.from_file(openmm_data / "opls_xml_files" / "ClO4.xml")
63+
clo4 = clo4_xml.to_openff_molecule(clo4_template)
64+
assert len(clo4.atoms) == 5
65+
assert len(clo4.bonds) == 4
66+
assert clo4.to_smiles()
6067

6168

6269
def test_assign_partial_charges_w_mol(openmm_data):
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<ForceField>
2+
<AtomTypes>
3+
<Type name="Clc" class="Clc" element="C" mass="35.45" />
4+
<Type name="Oc" class="Oc" element="O" mass="15.99" />
5+
</AtomTypes>
6+
<Residues>
7+
<Residue name="PF6">
8+
<Atom name="Cl1" type="Clc" />
9+
<Atom name="O2" type="Oc" />
10+
<Atom name="O3" type="Oc" />
11+
<Atom name="O4" type="Oc" />
12+
<Atom name="O5" type="Oc" />
13+
<Bond from="0" to="1" />
14+
<Bond from="0" to="2" />
15+
<Bond from="0" to="3" />
16+
<Bond from="0" to="4" />
17+
</Residue>
18+
</Residues>
19+
<HarmonicBondForce>
20+
<Bond class1="Clc" class2="Oc" length="0.1506" k="633959.68" />
21+
<Bond class1="Clc" class2="Oc" length="0.1506" k="633959.68" />
22+
<Bond class1="Clc" class2="Oc" length="0.1506" k="633959.68" />
23+
<Bond class1="Clc" class2="Oc" length="0.1506" k="633959.68" />
24+
</HarmonicBondForce>
25+
<HarmonicAngleForce>
26+
<Angle class1="Oc" class2="Clc" class3="Oc" angle="1.911135530933791" k="1739.707" />
27+
<Angle class1="Oc" class2="Clc" class3="Oc" angle="1.911135530933791" k="1739.707" />
28+
<Angle class1="Oc" class2="Clc" class3="Oc" angle="1.911135530933791" k="1739.707" />
29+
<Angle class1="Oc" class2="Clc" class3="Oc" angle="1.911135530933791" k="1739.707" />
30+
<Angle class1="Oc" class2="Clc" class3="Oc" angle="1.911135530933791" k="1739.707" />
31+
<Angle class1="Oc" class2="Clc" class3="Oc" angle="1.911135530933791" k="1739.707" />
32+
</HarmonicAngleForce>
33+
<PeriodicTorsionForce />
34+
<NonbondedForce coulomb14scale="0.5" lj14scale="0.5">
35+
<Atom type="Clc" charge="1.176" sigma="0.350000" epsilon="0.492830" />
36+
<Atom type="Oc" charge="-0.544" sigma="0.290000" epsilon="0.878640" />
37+
<Atom type="Oc" charge="-0.544" sigma="0.290000" epsilon="0.878640" />
38+
<Atom type="Oc" charge="-0.544" sigma="0.290000" epsilon="0.878640" />
39+
<Atom type="Oc" charge="-0.544" sigma="0.290000" epsilon="0.878640" />
40+
</NonbondedForce>
41+
</ForceField>

0 commit comments

Comments
 (0)