1+ import itertools
12from typing import Optional
23
34from openff .toolkit .topology .molecule import Atom , Molecule
1617 GROMACSAngle ,
1718 GROMACSAtom ,
1819 GROMACSBond ,
20+ GROMACSExclusion ,
1921 GROMACSMolecule ,
2022 GROMACSPair ,
23+ GROMACSSettles ,
2124 GROMACSSystem ,
2225 LennardJonesAtomType ,
2326 PeriodicImproperDihedral ,
2427 PeriodicProperDihedral ,
2528 RyckaertBellemansDihedral ,
2629)
27- from openff .interchange .models import TopologyKey
30+ from openff .interchange .models import BondKey , TopologyKey
2831
2932
3033def _convert (interchange : Interchange ) -> GROMACSSystem :
@@ -171,12 +174,12 @@ def _convert(interchange: Interchange) -> GROMACSSystem:
171174 else :
172175 raise NotImplementedError ()
173176
177+ _convert_settles (molecule , unique_molecule , interchange )
174178 _convert_bonds (molecule , unique_molecule , interchange )
175179 _convert_angles (molecule , unique_molecule , interchange )
176180 # pairs
177181 _convert_dihedrals (molecule , unique_molecule , interchange )
178- # settles?
179- # constraints?
182+ # other constraints?
180183
181184 system .molecule_types [unique_molecule .name ] = molecule
182185
@@ -199,6 +202,9 @@ def _convert_bonds(
199202 unique_molecule : "Molecule" ,
200203 interchange : Interchange ,
201204):
205+ if len (molecule .settles ) > 0 :
206+ return
207+
202208 collection = interchange ["Bonds" ]
203209
204210 for bond in unique_molecule .bonds :
@@ -246,6 +252,9 @@ def _convert_angles(
246252 unique_molecule : "Molecule" ,
247253 interchange : Interchange ,
248254):
255+ if len (molecule .settles ) > 0 :
256+ return
257+
249258 collection = interchange ["Angles" ]
250259
251260 for angle in unique_molecule .angles :
@@ -407,3 +416,75 @@ def _convert_dihedrals(
407416 multiplicity = int (params ["periodicity" ]),
408417 ),
409418 )
419+
420+
421+ def _convert_settles (
422+ molecule : GROMACSMolecule ,
423+ unique_molecule : "Molecule" ,
424+ interchange : Interchange ,
425+ ):
426+ if "Constraints" not in interchange .collections :
427+ return
428+
429+ if not unique_molecule .is_isomorphic_with (Molecule .from_smiles ("O" )):
430+ return
431+
432+ if unique_molecule .atom (0 ).atomic_number != 8 :
433+ raise Exception (
434+ "Writing `[ settles ]` assumes water is ordered as OHH. Please raise an issue "
435+ "if you would benefit from this assumption changing." ,
436+ )
437+
438+ topology_atom_indices = [
439+ interchange .topology .atom_index (atom ) for atom in unique_molecule .atoms
440+ ]
441+
442+ constraint_lengths = set ()
443+
444+ for atom_pair in itertools .combinations (topology_atom_indices , 2 ):
445+ key = BondKey (atom_indices = atom_pair )
446+
447+ if key not in interchange ["Constraints" ].key_map :
448+ return
449+
450+ try :
451+ constraint_lengths .add (
452+ interchange ["Bonds" ]
453+ .potentials [interchange ["Bonds" ].key_map [key ]]
454+ .parameters ["length" ],
455+ )
456+ # KeyError (subclass of LookupErrorR) when this BondKey is not found in the bond collection
457+ # LookupError for the Interchange not having a bond collection
458+ # in either case, look to the constraint collection for this distance
459+ except LookupError :
460+ constraint_lengths .add (
461+ interchange ["Constraints" ]
462+ .constraints [interchange ["Constraints" ].key_map [key ]]
463+ .parameters ["distance" ],
464+ )
465+
466+ if len (constraint_lengths ) != 2 :
467+ raise RuntimeError (
468+ "Found three unique constraint lengths in constrained water." ,
469+ )
470+
471+ molecule .settles .append (
472+ GROMACSSettles (
473+ first_atom = 1 , # TODO: documentation unclear on if this is first or oxygen
474+ oxygen_hydrogen_distance = min (constraint_lengths ),
475+ hydrogen_hydrogen_distance = max (constraint_lengths ),
476+ ),
477+ )
478+
479+ molecule .exclusions .append (
480+ GROMACSExclusion (
481+ first_atom = 1 ,
482+ other_atoms = [2 , 3 ],
483+ ),
484+ )
485+ molecule .exclusions .append (
486+ GROMACSExclusion (
487+ first_atom = 2 ,
488+ other_atoms = [3 ],
489+ ),
490+ )
0 commit comments