Skip to content

Commit e6b0c35

Browse files
ehhartmannEric Hartmann
and
Eric Hartmann
authored
multipledihedrals for impropers (#387)
* change topology object * fix top.to_dict() error for impropers * black * fix coordinates part --------- Co-authored-by: Eric Hartmann <hartmaec@rh05659.villa-bosch.de>
1 parent f61e655 commit e6b0c35

File tree

5 files changed

+102
-44
lines changed

5 files changed

+102
-44
lines changed

src/kimmdy/coordinates.py

+53-18
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Dihedral,
2020
DihedralType,
2121
ProperDihedralId,
22+
ImproperDihedralId,
2223
MultipleDihedrals,
2324
Interaction,
2425
InteractionType,
@@ -157,8 +158,12 @@ def merge_dihedrals(
157158
dihedral_key: tuple[str, str, str, str],
158159
dihedral_a: Optional[Dihedral],
159160
dihedral_b: Optional[Dihedral],
160-
dihedral_types_a: dict[ProperDihedralId, DihedralType],
161-
dihedral_types_b: dict[ProperDihedralId, DihedralType],
161+
dihedral_types_a: Union[
162+
dict[ProperDihedralId, DihedralType], dict[ImproperDihedralId, DihedralType]
163+
],
164+
dihedral_types_b: Union[
165+
dict[ProperDihedralId, DihedralType], dict[ImproperDihedralId, DihedralType]
166+
],
162167
molA: MoleculeType,
163168
molB: MoleculeType,
164169
funct: str,
@@ -447,28 +452,58 @@ def merge_top_moleculetypes_slow_growth(
447452
)
448453

449454
# improper dihedrals
450-
# all impropers in amber99SB ffbonded.itp have a periodicity of 2
451-
# but not the ones defined in aminoacids.rtp. For now, I am assuming
452-
# a periodicity of 2 in this section
455+
# TODO: duplicate of proper dihedrals, could refactor
453456

454457
keys = set(molA.improper_dihedrals.keys()) | set(molB.improper_dihedrals.keys())
455458
for key in keys:
456-
interactionA = molA.improper_dihedrals.get(key)
457-
interactionB = molB.improper_dihedrals.get(key)
459+
multiple_dihedralsA = molA.improper_dihedrals.get(key)
460+
multiple_dihedralsB = molB.improper_dihedrals.get(key)
458461

459-
if interactionA != interactionB:
460-
molB.improper_dihedrals[key] = merge_dihedrals(
461-
key,
462-
interactionA,
463-
interactionB,
464-
ff.improper_dihedraltypes,
465-
ff.improper_dihedraltypes,
466-
molA,
467-
molB,
468-
"4",
469-
"2",
462+
if multiple_dihedralsA != multiple_dihedralsB:
463+
multiple_dihedralsA = get_explicit_MultipleDihedrals(
464+
key, molA, multiple_dihedralsA, ff
465+
)
466+
multiple_dihedralsB = get_explicit_MultipleDihedrals(
467+
key, molB, multiple_dihedralsB, ff
468+
)
469+
keysA = (
470+
set(multiple_dihedralsA.dihedrals.keys())
471+
if multiple_dihedralsA
472+
else set()
473+
)
474+
keysB = (
475+
set(multiple_dihedralsB.dihedrals.keys())
476+
if multiple_dihedralsB
477+
else set()
470478
)
471479

480+
molB.improper_dihedrals[key] = MultipleDihedrals(*key, "9", {})
481+
periodicities = keysA | keysB
482+
for periodicity in periodicities:
483+
assert isinstance(periodicity, str)
484+
interactionA = (
485+
multiple_dihedralsA.dihedrals.get(periodicity)
486+
if multiple_dihedralsA
487+
else None
488+
)
489+
interactionB = (
490+
multiple_dihedralsB.dihedrals.get(periodicity)
491+
if multiple_dihedralsB
492+
else None
493+
)
494+
495+
molB.improper_dihedrals[key].dihedrals[periodicity] = merge_dihedrals(
496+
key,
497+
interactionA,
498+
interactionB,
499+
ff.improper_dihedraltypes,
500+
ff.improper_dihedraltypes,
501+
molA,
502+
molB,
503+
"9",
504+
periodicity,
505+
)
506+
472507
# amber fix for breaking/binding atom types without LJ potential
473508
# breakpoint()
474509

src/kimmdy/topology/atomic.py

+16-10
Original file line numberDiff line numberDiff line change
@@ -559,17 +559,18 @@ def from_top_line(cls, l: list[str]):
559559

560560
@dataclass()
561561
class ResidueImproperSpec:
562-
"""Information about one imroper dihedral in a residue
562+
"""Information about one improper dihedral in a residue
563563
564-
; atom1 atom2 atom3 atom4 q0 cq
564+
; atom1 atom2 atom3 atom4 c0(q0) c1(cp) c2(mult)
565565
"""
566566

567567
atom1: str
568568
atom2: str
569569
atom3: str
570570
atom4: str
571-
q0: Optional[str]
572-
cq: Optional[str]
571+
c0: Optional[str]
572+
c1: Optional[str]
573+
c2: Optional[str]
573574

574575
@classmethod
575576
def from_top_line(cls, l: list[str]):
@@ -578,23 +579,26 @@ def from_top_line(cls, l: list[str]):
578579
atom2=l[1],
579580
atom3=l[2],
580581
atom4=l[3],
581-
q0=field_or_none(l, 4),
582-
cq=field_or_none(l, 5),
582+
c0=field_or_none(l, 4),
583+
c1=field_or_none(l, 5),
584+
c2=field_or_none(l, 6),
583585
)
584586

585587

586588
@dataclass()
587589
class ResidueProperSpec:
588-
"""Information about one imroper dihedral in a residue
590+
"""Information about one proper dihedral in a residue
589591
590-
; atom1 atom2 atom3 atom4 q0 cq
592+
; atom1 atom2 atom3 atom4 c0(q0) c1(cq) c2
591593
"""
592594

593595
atom1: str
594596
atom2: str
595597
atom3: str
596598
atom4: str
597-
q0: Optional[str]
599+
c0: Optional[str]
600+
c1: Optional[str]
601+
c2: Optional[str]
598602

599603
@classmethod
600604
def from_top_line(cls, l: list[str]):
@@ -603,7 +607,9 @@ def from_top_line(cls, l: list[str]):
603607
atom2=l[1],
604608
atom3=l[2],
605609
atom4=l[3],
606-
q0=field_or_none(l, 4),
610+
c0=field_or_none(l, 4),
611+
c1=field_or_none(l, 5),
612+
c2=field_or_none(l, 6),
607613
)
608614

609615

src/kimmdy/topology/topology.py

+29-15
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __init__(
7272
self.pairs: dict[tuple[str, str], Pair] = {}
7373
self.angles: dict[tuple[str, str, str], Angle] = {}
7474
self.proper_dihedrals: dict[tuple[str, str, str, str], MultipleDihedrals] = {}
75-
self.improper_dihedrals: dict[tuple[str, str, str, str], Dihedral] = {}
75+
self.improper_dihedrals: dict[tuple[str, str, str, str], MultipleDihedrals] = {}
7676
self.position_restraints: dict[str, PositionRestraint] = {}
7777
self.dihedral_restraints: dict[tuple[str, str, str, str], DihedralRestraint] = (
7878
{}
@@ -183,7 +183,11 @@ def _parse_dihedrals(self):
183183
dihedral = Dihedral.from_top_line(l)
184184
key = (dihedral.ai, dihedral.aj, dihedral.ak, dihedral.al)
185185
if dihedral.funct == "4":
186-
self.improper_dihedrals[key] = dihedral
186+
if self.improper_dihedrals.get(key) is None:
187+
self.improper_dihedrals[key] = MultipleDihedrals(
188+
*key, dihedral.funct, dihedrals={}
189+
)
190+
self.improper_dihedrals[key].dihedrals[dihedral.periodicity] = dihedral
187191
else:
188192
if self.proper_dihedrals.get(key) is None:
189193
self.proper_dihedrals[key] = MultipleDihedrals(
@@ -263,7 +267,11 @@ def _update_atomics_dict(self):
263267
attributes_to_list(x)
264268
for dihedrals in self.proper_dihedrals.values()
265269
for x in dihedrals.dihedrals.values()
266-
] + [attributes_to_list(x) for x in self.improper_dihedrals.values()]
270+
] + [
271+
attributes_to_list(x)
272+
for dihedrals in self.improper_dihedrals.values()
273+
for x in dihedrals.dihedrals.values()
274+
]
267275
self.atomics["position_restraints"] = [
268276
attributes_to_list(x) for x in self.position_restraints.values()
269277
]
@@ -464,13 +472,18 @@ def _regenerate_topology_from_bound_to(self, ff):
464472
for atom in self.atoms.values():
465473
impropers = self._get_atom_improper_dihedrals(atom.nr, ff)
466474
for key, improper in impropers:
467-
self.improper_dihedrals[key] = Dihedral(
468-
improper.atom1,
469-
improper.atom2,
470-
improper.atom3,
471-
improper.atom4,
475+
self.improper_dihedrals[key] = MultipleDihedrals(
476+
*key,
472477
"4",
473-
improper.cq,
478+
dihedrals={
479+
"": Dihedral(
480+
*key,
481+
"4",
482+
c0=improper.c0,
483+
c1=improper.c1,
484+
periodicity=improper.c2,
485+
)
486+
},
474487
)
475488

476489
def reindex_atomnrs(self) -> dict[str, str]:
@@ -1256,11 +1269,12 @@ def bind_bond(
12561269
atompair_nrs[0], self.ff
12571270
) + reactive_moleculetype._get_atom_improper_dihedrals(atompair_nrs[1], self.ff)
12581271
for key, value in dihedral_k_v:
1272+
if value.c2 is None:
1273+
value.c2 = ""
12591274
if reactive_moleculetype.improper_dihedrals.get(key) is None:
1260-
# TODO: fix this
1261-
c2 = ""
1262-
if value.q0 is not None:
1263-
c2 = "1"
1264-
reactive_moleculetype.improper_dihedrals[key] = Dihedral(
1265-
key[0], key[1], key[2], key[3], "4", value.q0, value.cq, c2
1275+
reactive_moleculetype.improper_dihedrals[key] = MultipleDihedrals(
1276+
*key, "4", dihedrals={}
12661277
)
1278+
reactive_moleculetype.improper_dihedrals[key].dihedrals[value.c2] = (
1279+
Dihedral(*key, "4", c0=value.c0, c1=value.c1, periodicity=value.c2)
1280+
)

tests/test_coordinates.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ def test_merge_prm_top(arranged_tmp_path):
145145
assert top_merge.bonds[("26", "27")].funct == "3"
146146
assert top_merge.angles[("17", "19", "20")].c3 is not None
147147
assert top_merge.proper_dihedrals[("15", "17", "19", "24")].dihedrals["3"].c5 == "3"
148-
assert top_merge.improper_dihedrals[("17", "20", "19", "24")].c5 == "2"
148+
assert (
149+
top_merge.improper_dihedrals[("17", "20", "19", "24")].dihedrals["2"].c5 == "2"
150+
)
149151
# assert one dihedral merge improper/proper
150152

151153

tests/test_integration.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def test_integration_valid_input_files(arranged_tmp_path):
4747
def test_grompp_with_kimmdy_topology(arranged_tmp_path):
4848
raw_top = read_top(Path("minimal.top"))
4949
top = Topology(raw_top)
50+
top_dict = top.to_dict()
5051
write_top(top.to_dict(), Path("output.top"))
5152
assert sp.run(
5253
[

0 commit comments

Comments
 (0)