Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filter molecule for templates #339

Merged
merged 14 commits into from
Jun 21, 2024
3 changes: 3 additions & 0 deletions bin/polyply
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ def main(): # pylint: disable=too-many-locals,too-many-statements
help='file with grid-points', default=None)
system_group.add_argument('-mi', dest='maxiter', type=int,
help='max number of trys to grow a molecule', default=800)
system_group.add_argument('-skip_filter', action='store_true', dest='skip_filter',
help='do not group residues by isomophism when making '
'templates but just resname')
system_group.add_argument('-start', dest='start', nargs='+', type=str,
default=[],
help=('Specify which residue to build first. The syntax is '
Expand Down
2 changes: 1 addition & 1 deletion polyply/src/backmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def _place_init_coords(self, meta_molecule):
built_nodes = []
for node in meta_molecule.nodes:
if meta_molecule.nodes[node]["backmap"]:
resname = meta_molecule.nodes[node]["resname"]
resname = meta_molecule.nodes[node]["template"]
cg_coord = meta_molecule.nodes[node]["position"]
resid = meta_molecule.nodes[node]["resid"]
high_res_atoms = meta_molecule.nodes[node]["graph"].nodes
Expand Down
25 changes: 25 additions & 0 deletions polyply/src/check_residue_equivalence.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import networkx as nx
from vermouth.molecule import attributes_match
from collections import defaultdict

def _atoms_match(node1, node2):
return node1["atomname"] == node2["atomname"]

Check warning on line 6 in polyply/src/check_residue_equivalence.py

View check run for this annotation

Codecov / codecov/patch

polyply/src/check_residue_equivalence.py#L6

Added line #L6 was not covered by tests

def check_residue_equivalence(topology):
"""
Expand Down Expand Up @@ -42,3 +46,24 @@
visited_residues[resname] = graph
molnames[resname] = mol_name
resids[resname] = molecule.nodes[node]["resid"]

def group_residues_by_isomorphism(meta_molecule, template_graphs={}):
"""
Collect all unique residue graphs. If the same resname matches
multiple graphs the resname is appended by a number. If required
template_graphs can be given that are used for matching rather
than the first founds residue.
"""
unique_graphs = {}
for graph in template_graphs.values():
graph_hash = nx.algorithms.graph_hashing.weisfeiler_lehman_graph_hash(graph, node_attr='atomname')
unique_graphs[graph_hash] = graph

Check warning on line 60 in polyply/src/check_residue_equivalence.py

View check run for this annotation

Codecov / codecov/patch

polyply/src/check_residue_equivalence.py#L59-L60

Added lines #L59 - L60 were not covered by tests

for node in meta_molecule.nodes:
graph = meta_molecule.nodes[node]["graph"]
graph_hash = nx.algorithms.graph_hashing.weisfeiler_lehman_graph_hash(graph, node_attr='atomname')
if graph_hash not in unique_graphs:
unique_graphs[graph_hash] = graph
meta_molecule.nodes[node]["template"] = graph_hash

return unique_graphs
11 changes: 8 additions & 3 deletions polyply/src/gen_coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def gen_coords(toppath,
grid_spacing=0.2,
grid=None,
maxiter=800,
skip_filter=False,
start=[],
density=None,
box=None,
Expand Down Expand Up @@ -254,11 +255,15 @@ def gen_coords(toppath,
LOGGER.warning(msg, type="warning")

# do a sanity check
LOGGER.info("checking residue integrity", type="step")
check_residue_equivalence(topology)
if skip_filter:
LOGGER.info("checking residue integrity", type="step")
check_residue_equivalence(topology)

# Build polymer structure
LOGGER.info("generating templates", type="step")
GenerateTemplates(topology=topology, max_opt=10).run_system(topology)
GenerateTemplates(topology=topology,
max_opt=10,
skip_filter=skip_filter).run_system(topology)
LOGGER.info("annotating ligands", type="step")
ligand_annotator = AnnotateLigands(topology, ligands)
ligand_annotator.run_system(topology)
Expand Down
56 changes: 35 additions & 21 deletions polyply/src/generate_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@
radius_of_gyration)
from .topology import replace_defined_interaction
from .linalg_functions import dih
from .check_residue_equivalence import group_residues_by_isomorphism
from tqdm import tqdm
"""
Processor generating coordinates for all residues of a meta_molecule
matching those in the meta_molecule.molecule attribute.
"""

LOGGER = StyleAdapter(get_logger(__name__))

def _extract_template_graphs(meta_molecule, template_graphs={}, skip_filter=False):
if skip_filter:
for node in meta_molecule.nodes:
resname = meta_molecule.nodes[node]["resname"]

Check warning on line 36 in polyply/src/generate_templates.py

View check run for this annotation

Codecov / codecov/patch

polyply/src/generate_templates.py#L36

Added line #L36 was not covered by tests
if resname not in template_graphs:
template_graphs[resname] = meta_molecule.nodes[node]["graph"]

Check warning on line 38 in polyply/src/generate_templates.py

View check run for this annotation

Codecov / codecov/patch

polyply/src/generate_templates.py#L38

Added line #L38 was not covered by tests
else:
template_graphs = group_residues_by_isomorphism(meta_molecule, template_graphs)
return template_graphs

def find_atoms(molecule, attr, value):
"""
Find all nodes of a `vermouth.molecule.Molecule` that have the
Expand Down Expand Up @@ -239,7 +251,7 @@
new_interaction = interaction._replace(atoms=new_atoms)
return new_interaction

def extract_block(molecule, resname, defines):
def extract_block(molecule, template_graph, defines):
"""
Given a `vermouth.molecule` and a `resname`
extract the information of a block from the
Expand All @@ -257,8 +269,6 @@
-------
:class:vermouth.molecule.Block
"""
nodes = find_atoms(molecule, "resname", resname)
resid = molecule.nodes[nodes[0]]["resid"]
block = vermouth.molecule.Block()

# select all nodes with the same first resid and
Expand All @@ -267,11 +277,10 @@
# label in the molecule and in the block for
# relabeling the interactions
mapping = {}
for node in nodes:
for node in template_graph.nodes:
attr_dict = molecule.nodes[node]
if attr_dict["resid"] == resid:
block.add_node(attr_dict["atomname"], **attr_dict)
mapping[node] = attr_dict["atomname"]
block.add_node(attr_dict["atomname"], **attr_dict)
mapping[node] = attr_dict["atomname"]

for inter_type in molecule.interactions:
for interaction in molecule.interactions[inter_type]:
Expand All @@ -284,12 +293,6 @@
"virtual_sites2", "virtual_sites3", "virtual_sites4"]:
block.make_edges_from_interaction_type(inter_type)

if not nx.is_connected(block):
msg = ('\n Residue {} with id {} consistes of two disconnected parts. '
'Make sure all atoms/particles in a residue are connected by bonds,'
' constraints or virual-sites.')
raise IOError(msg.format(resname, resid))

return block

class GenerateTemplates(Processor):
Expand All @@ -300,14 +303,15 @@
in the templates attribute. The processor also stores the volume
of each block in the volume attribute.
"""
def __init__(self, topology, max_opt, *args, **kwargs):
def __init__(self, topology, max_opt, skip_filter, *args, **kwargs):
super().__init__(*args, **kwargs)
self.max_opt = max_opt
self.topology = topology
self.volumes = self.topology.volumes
self.templates = {}
self.skip_filter = skip_filter

def gen_templates(self, meta_molecule):
def gen_templates(self, meta_molecule, template_graphs):
"""
Generate blocks for each unique residue by extracting the
block information, placing initial coordinates, and geometry
Expand All @@ -318,17 +322,18 @@
Parameters
----------
meta_molecule: :class:`polyply.src.meta_molecule.MetaMolecule`
template_graphs: dict
a dict of graphs corresponding to the templates to be generated

Updates
---------
self.templates
self.volumes
"""
resnames = set(nx.get_node_attributes(meta_molecule, "resname").values())

for resname in resnames:
for resname, template_graph in tqdm(template_graphs.items()):
if resname not in self.templates:
block = extract_block(meta_molecule.molecule, resname,
block = extract_block(meta_molecule.molecule,
template_graph,
self.topology.defines)

opt_counter = 0
Expand Down Expand Up @@ -366,7 +371,16 @@
and volume attribute.
"""
if hasattr(meta_molecule, "templates"):
self.templates.update(meta_molecule.templates)
self.gen_templates(meta_molecule)
template_graphs = {res: None for res in meta_molecule.templates}
else:
template_graphs = {}
meta_molecule.templates = {}

template_graphs = _extract_template_graphs(meta_molecule,
skip_filter=self.skip_filter,
template_graphs=template_graphs)
self.templates.update(meta_molecule.templates)

self.gen_templates(meta_molecule, template_graphs)
meta_molecule.templates = self.templates
return meta_molecule
3 changes: 2 additions & 1 deletion polyply/src/nonbond_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ def from_topology(cls, molecules, topology, box):
"Make sure all coordiantes are wrapped inside the box.")
raise IOError(msg)

resname = molecule.nodes[node]["resname"]
# try getting the template name; if no template name is given use resname
resname = molecule.nodes[node].get("template", molecule.nodes[node]["resname"])
atom_types.append(resname)
nodes_to_gndx[(mol_count, node)] = idx
idx += 1
Expand Down
6 changes: 3 additions & 3 deletions polyply/tests/test_backmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ def test_backmapping():
meta_molecule = MetaMolecule(graph)
meta_molecule.molecule = molecule

nx.set_node_attributes(meta_molecule, {0: {"resname": "test",
nx.set_node_attributes(meta_molecule, {0: {"resname": "test", "template": "test",
"position": np.array([0, 0, 0]),
"resid": 1, "backmap": True},
1: {"resname": "test",
1: {"resname": "test", "template": "test",
"position": np.array([0, 0, 1.0]),
"resid": 2, "backmap": True},
2: {"resname": "test",
2: {"resname": "test", "template": "test",
"position": np.array([0, 0, 2.0]),
"resid": 3, "backmap": False}})
# test if disordered template works
Expand Down
Loading
Loading