Skip to content

Commit

Permalink
Merge pull request #250 from marrink-lab/fix/239
Browse files Browse the repository at this point in the history
make sure self-links are applied
  • Loading branch information
fgrunewald authored Sep 10, 2022
2 parents dffac68 + 3acd0dc commit fd55c4e
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 16 deletions.
59 changes: 46 additions & 13 deletions polyply/src/apply_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,29 @@ def __init__(self, *args, debug=False, **kwargs):
self.debug = debug
super().__init__(*args, **kwargs)
self.applied_links = defaultdict(dict)
self.nodes_to_remove = []

def _update_interactions_dict(self, interactions_dict, molecule, citations, mapping=None):
"""
Helper function for adding links to the applied links dictionary.
If mapping is given the interaction atoms are mapped to the molecule
atoms using mapping. Otherwise interactions are assumed to be written
in terms of molecule nodes.
"""
for inter_type, interactions in interactions_dict.items():
for interaction in interactions:
# it is not guaranteed that interaction.atoms is a tuple
# the key is the atoms involved in the interaction and the version type so
# that multiple versions are kept and not overwritten
if mapping:
new_interaction = _build_link_interaction_from(molecule,
interaction,
mapping)
else:
new_interaction = interaction

interaction_key = (*new_interaction.atoms, new_interaction.meta.get("version", 1))
self.applied_links[inter_type][interaction_key] = (new_interaction, citations)

def apply_link_between_residues(self, meta_molecule, link, link_to_resid):
"""
Expand Down Expand Up @@ -384,18 +407,18 @@ def apply_link_between_residues(self, meta_molecule, link, link_to_resid):
# if all atoms have a match the link applies and we first
# replace any attributes from the link node section
for node in link.nodes:
molecule.nodes[link_to_mol[node]].update(link.nodes[node].get('replace', {}))
# if the atomname is set to null we schedule the node to be removed
if link.nodes[node].get('replace', {}).get('atomname', False) is None:
self.nodes_to_remove.append(link_to_mol[node])
else:
molecule.nodes[link_to_mol[node]].update(link.nodes[node].get('replace', {}))

# based on the match we build the link interaction
for inter_type in link.interactions:
for interaction in link.interactions[inter_type]:
new_interaction = _build_link_interaction_from(molecule, interaction, link_to_mol)
# it is not guaranteed that interaction.atoms is a tuple
# the key is the atoms involved in the interaction and the version type so
# that multiple versions are kept and not overwritten
interaction_key = tuple(new_interaction.atoms) +\
tuple([new_interaction.meta.get("version",1)])
self.applied_links[inter_type][interaction_key] = (new_interaction, link.citations)
self._update_interactions_dict(link.interactions,
molecule,
link.citations,
mapping=link_to_mol)

# now we already add the edges of this link
# links can overwrite each other but the edges must be the same
# this is safer than using the make_edge method because it accounts
Expand Down Expand Up @@ -423,6 +446,11 @@ def run_molecule(self, meta_molecule):
"""
molecule = meta_molecule.molecule
force_field = meta_molecule.force_field
# we need to update the temporary interactions dict
self._update_interactions_dict(molecule.interactions, molecule, molecule.citations, mapping=None)
# now we can clear the molecule dict
molecule.interactions = defaultdict(list)

resnames = set(nx.get_node_attributes(molecule, "resname").values())
for link in tqdm(force_field.links):
link_resnames = _get_link_resnames(link)
Expand Down Expand Up @@ -453,10 +481,15 @@ def run_molecule(self, meta_molecule):
except MatchError as error:
LOGGER.debug(str(error), type='step')

# take care to remove nodes if there are any scheduled for removal
# we do this here becuase that's more efficent
molecule.remove_nodes_from(self.nodes_to_remove)
# now we add all interactions but not the ones that contain the removed
# nodes
for inter_type in self.applied_links:
for interaction, citation in self.applied_links[inter_type].values():
meta_molecule.molecule.interactions[inter_type].append(interaction)
if citation:
for atoms, (interaction, citation) in self.applied_links[inter_type].items():
if not any(atom in self.nodes_to_remove for atom in atoms):
meta_molecule.molecule.interactions[inter_type].append(interaction)
meta_molecule.molecule.citations.update(citation)

for link in force_field.links:
Expand Down
39 changes: 37 additions & 2 deletions polyply/tests/test_apply_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,35 @@ def test_match_link_and_residue_atoms_fail(example_meta_molecule,
[[(1, 4, 1)], [(6, 9, 1)], [(1, 4, 5, 6, 1)]],
[[(0, 0)], [(1, 0)], [(2, 0)]]
),
# 8 - simple check single residue but both nodes are the same
([[(0, {'name': 'BB'}),
(1, {'name': 'BB1'}),
(2, {'name': 'SC1'})]],
[{0: 0, 1: 0, 2:0}],
['angles'],
[[vermouth.molecule.Interaction(atoms=(0, 1, 2),
parameters=['1', '124', '500'],
meta={})]],
[[]],
[[]],
[[(0, 1, 2, 1)]],
[[(0, 0)]]
),
# 9 - test node get scheduled for removal
([[(0, {'name': 'BB'}),
(1, {'name': 'BB1'}),
(2, {'name': 'SC1'}),
(3, {'name': 'SC2', 'replace': {'atomname': None}})]],
[{0: 0, 1: 0, 2:0, 3:0}],
['angles'],
[[vermouth.molecule.Interaction(atoms=(0, 1, 2),
parameters=['1', '124', '500'],
meta={})]],
[[]],
[[]],
[[(0, 1, 2, 1)]],
[[(0, 0)]]
),
))
def test_apply_link_to_residue(example_meta_molecule,
link_defs,
Expand All @@ -352,8 +381,14 @@ def test_apply_link_to_residue(example_meta_molecule,
link.patterns = patterns
link.make_edges_from_interaction_type(inter_type)
processor.apply_link_between_residues(example_meta_molecule,
link,
link_to_resid)
link,
link_to_resid)

for node in processor.nodes_to_remove:
node_name = example_meta_molecule.molecule.nodes[node]['name']
for node in link.nodes:
if link.nodes[node]['name'] == node_name:
assert link.nodes[node]['replace']['atomname'] is None

for nodes, inter_idxs, inter_type in zip(expected_nodes, expected_inters, inter_types):
for inter_nodes, inter_idx in zip(nodes, inter_idxs):
Expand Down
36 changes: 36 additions & 0 deletions polyply/tests/test_data/gen_params/input/removal.ff
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
; Copyright 2020 University of Groningen
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.

[ moleculetype ]
; name nexcl.
PEO 1
;
[ atoms ]
1 SN1a 1 PEO EO 1 0.000 45
2 SN2a 1 PEO EP 1 0.000 45
[ bonds ]
1 2 1 0.44 7000

[ link ]
resname "PEO"
[ bonds ]
; back bone bonds
EP +EO 1 0.37 7000

[ link ]
resname "PEO"
[ atoms ]
EP {"replace": {"atomname": null}}
[ non-edges]
EP +EO
20 changes: 20 additions & 0 deletions polyply/tests/test_data/gen_params/output/removal.itp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; /Users/fabian/ProgramDev/dev_env/bin/pytest test_gen_params.py

; Please cite the following papers:

[ moleculetype ]
test 1

[ atoms ]
1 SN1a 1 PEO EO 1 0.0 45.0
2 SN2a 1 PEO EP 1 0.0 45.0
3 SN1a 2 PEO EO 2 0.0 45.0
4 SN2a 2 PEO EP 2 0.0 45.0
5 SN1a 3 PEO EO 3 0.0 45.0

[ bonds ]
1 2 1 0.44 7000
3 4 1 0.44 7000
2 3 1 0.37 7000
4 5 1 0.37 7000

20 changes: 20 additions & 0 deletions polyply/tests/test_data/gen_params/ref/removal.itp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; /Users/fabian/ProgramDev/dev_env/bin/polyply gen_params -f ../input/test_removal.ff -seq PEO:3 -o removal.itp -name test -vv

; Please cite the following papers:

[ moleculetype ]
testref 1

[ atoms ]
1 SN1a 1 PEO EO 1 0.0 45.0
2 SN2a 1 PEO EP 1 0.0 45.0
3 SN1a 2 PEO EO 2 0.0 45.0
4 SN2a 2 PEO EP 2 0.0 45.0
5 SN1a 3 PEO EO 3 0.0 45.0

[ bonds ]
1 2 1 0.44 7000
3 4 1 0.44 7000
2 3 1 0.37 7000
4 5 1 0.37 7000

8 changes: 7 additions & 1 deletion polyply/tests/test_gen_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ class TestGenParams():
"-seqf", TEST_DATA + "/gen_params/input/test_edge_attr.json",
"-name", "test",
"-o", TEST_DATA + "/gen_params/output/test_edge_attr_out.itp"],
TEST_DATA + "/gen_params/ref/test_edge_attr_ref.itp")
TEST_DATA + "/gen_params/ref/test_edge_attr_ref.itp"),
# check if nodes can be removed
(["-f", TEST_DATA+"/gen_params/input/removal.ff",
"-seq", "PEO:3",
"-name", "test",
"-o", TEST_DATA + "/gen_params/output/removal.itp"],
TEST_DATA + "/gen_params/ref/removal.itp")
))
def test_gen_params(args_in, ref_file):
parser = argparse.ArgumentParser(
Expand Down

0 comments on commit fd55c4e

Please sign in to comment.