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

Atomtype parameterization methods for parameterizing a topology in GMSO. #644

Merged
merged 46 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8e69d6e
Atomtype parameterization methods for parameterizing a topology in GMSO.
CalCraven Apr 14, 2022
9074909
Move module imports for apply into atomtyping to prevent circular imp…
CalCraven Apr 14, 2022
80c1850
Add a quick fix which will do atomtyping if no residue flag is passed
CalCraven Apr 25, 2022
6376da7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 25, 2022
7d0a790
Merge branch 'master' of https://github.com/mosdef-hub/gmso into deve…
daico007 Apr 26, 2022
99c0793
correctly update sites when adding subtop
daico007 Apr 26, 2022
790aacc
Merge branch 'develop' into develop-gmsoff-parameterize
daico007 Apr 28, 2022
2d7a770
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Apr 28, 2022
e6da3f7
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Apr 29, 2022
40f6639
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Apr 29, 2022
e1f3d4e
Changes to doc strings for clarity. Add a subtop_label variable to ge…
CalCraven May 2, 2022
1503578
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 2, 2022
b13bf94
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina May 6, 2022
7be5f57
More modular architecture
umesh-timalsina May 6, 2022
7366452
WIP- Add testing, minor refactoring of the APIs.
umesh-timalsina May 6, 2022
cc46491
WIP- Better error handling
umesh-timalsina May 9, 2022
c58aa41
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina May 25, 2022
0f374b2
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina May 31, 2022
f5633e7
Misc Changes
umesh-timalsina Jun 1, 2022
d7a90b0
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Jun 1, 2022
72137ee
WIP- Skip opls for now, full TrappE tests
umesh-timalsina Jun 1, 2022
263ef78
Avoid accidental overwriting of typemap when using isomorphism
umesh-timalsina Jun 3, 2022
c7ea346
WIP- Remove unused import
umesh-timalsina Jun 3, 2022
d267d2f
Use enumerate for atom index while converting to TopologyGraph
umesh-timalsina Jun 3, 2022
9aab528
Fix argument order
umesh-timalsina Jun 3, 2022
51b1081
WIP- Add test for subtopology parameterization
umesh-timalsina Jun 3, 2022
06df2ba
Make opls/trappe global fixtures, Add tests for isomorphism
umesh-timalsina Jun 6, 2022
d7b08d6
Further testing isomorphism
umesh-timalsina Jun 6, 2022
031e703
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Jun 7, 2022
4a523e6
REVERT - skip OPLS tests
umesh-timalsina Jun 7, 2022
403a36c
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Jun 7, 2022
593c15a
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Jun 7, 2022
341ecc5
Copy scaling factors and combining rules after parametrization
umesh-timalsina Jun 7, 2022
fe352fd
Proper OPLS tests
umesh-timalsina Jun 7, 2022
b3e045f
WIP- Refactor the test module
umesh-timalsina Jun 7, 2022
4834b5f
WIP- Remove unused import
umesh-timalsina Jun 7, 2022
0a4c780
WIP- Add test for parameterization with impropers
umesh-timalsina Jun 9, 2022
5633c52
Merge remote-tracking branch 'upstream/develop' into develop-gmsoff-p…
umesh-timalsina Jun 9, 2022
3711172
WIP- Additional impropers test; Separate module for testing impropers
umesh-timalsina Jun 15, 2022
956977c
Minor refacotors; additional edge cases coverage/tests
umesh-timalsina Jun 15, 2022
f0fd1e5
Docstring minor fix
umesh-timalsina Jun 15, 2022
1e866d7
Remove rel_to_module as is obsolete in forcefield_utilities
umesh-timalsina Jun 15, 2022
da3a23e
Change trappe_ua to trappe-ua for correct loading
umesh-timalsina Jun 15, 2022
b018a6c
fix typo, add note about specific use case
daico007 Jun 15, 2022
4c62c5c
Merge branch 'develop-gmsoff-parameterize' of https://github.com/CalC…
daico007 Jun 15, 2022
318c5ad
pip install forcefield-utilites until new release
daico007 Jun 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gmso/core/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ def add_subtopology(self, subtop, update=True):
"""
self._subtops.add(subtop)
subtop.parent = self
self._sites.union(subtop.sites)
self._sites = self._sites.union(subtop.sites)
if update:
self.update_topology()

Expand Down
2 changes: 2 additions & 0 deletions gmso/parameterization/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""GMSO functions that generate parameterized Topologies."""
from .parameterize import apply
106 changes: 106 additions & 0 deletions gmso/parameterization/foyer_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Utilities for atomtyping a gmso topology with foyer."""
from collections import namedtuple

from foyer.atomtyper import AtomTypingRulesProvider, find_atomtypes
from foyer.topology_graph import TopologyGraph

from gmso.core.atom import Atom
from gmso.parameterization.subtopology_utils import subtop_bonds


def get_topology_graph(gmso_topology, atomdata_populator=None):
"""Return a TopologyGraph with relevant attributes from an GMSO topology.

Parameters
----------
gmso_topology: gmso.Topology-like
The GMSO Topology

atomdata_populator: callable, default=None
A function that will be called with the following arguments `gmso_topology` as well as `atom` to pass extra
arguments to the foyer.topology_graph.AtomData object

Notes
-----
The gmso topology here is a duck type.
daico007 marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
foyer.topology_graph.TopologyGraph
A light networkx representation of the topology
"""
top_graph = TopologyGraph()
for atom in gmso_topology.sites:
if isinstance(atom, Atom):
kwargs = (
atomdata_populator(gmso_topology, atom)
if atomdata_populator
else {}
)
if atom.name.startswith("_"):
top_graph.add_atom(
name=atom.name,
index=gmso_topology.get_index(atom),
atomic_number=None,
element=atom.name,
**kwargs,
)

else:
top_graph.add_atom(
name=atom.name,
index=gmso_topology.get_index(atom),
atomic_number=atom.element.atomic_number,
element=atom.element.symbol,
**kwargs,
)

for top_bond in gmso_topology.bonds:
atoms_indices = [
gmso_topology.get_index(atom)
for atom in top_bond.connection_members
]
top_graph.add_bond(atoms_indices[0], atoms_indices[1])

return top_graph


def get_topology_graph_from_subtop(subtopology):
"""Get an equivalent topology graph for a sub-topology."""
subtop_named_tuple = namedtuple("subtopology", ("sites", "bonds"))
return get_topology_graph(
subtop_named_tuple(subtopology.sites, subtop_bonds(subtopology))
)


def get_atomtyping_rules_provider(gmso_ff):
"""Return a foyer AtomTypingRulesProvider from a GMSO forcefield.

Parameters
----------
gmso_ff: gmso.core.forcefield.Forcefield
The GMSO forcefield object to extract the rules from

Returns
-------
AtomTypingRulesProvider
The foyer.atomtyper.AtomTypingRulesProvider object used to parse atomtype definitions.
Typically, SMARTS is the ruleset of choice. See https://github.com/mosdef-hub/foyer/issues/63
for curently supported features in Foyer.
"""
atom_type_defs = {}
atom_type_overrides = {}
for atom_type_name, atom_type in gmso_ff.atom_types.items():
if atom_type.definition:
atom_type_defs[atom_type_name] = atom_type.definition
if atom_type.overrides:
atom_type_overrides[atom_type_name] = atom_type.overrides

return AtomTypingRulesProvider(
atom_type_defs, atom_type_overrides, gmso_ff.non_element_types
)


def typemap_dict(topology_graph, atomtyping_rules_provider, max_iter=10):
"""Return a dictionary of typemap, by finding atomtypes in foyer."""
return find_atomtypes(topology_graph, atomtyping_rules_provider, max_iter)
63 changes: 63 additions & 0 deletions gmso/parameterization/isomorph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""TopologyGraph Functions that identify molecules from isomorphism."""
from collections import deque

import networkx as nx


def top_node_match(n1, n2):
"""Match two nodes in the topology graph based on their elements."""
return n1["atom_data"].element == n2["atom_data"].element


def partition_isomorphic_topology_graphs(graph):
"""Return a collection of isomorphic sets of the subgraphs of the Topology Graph.

Parameters
----------
graph: foyer.topology_graph.TopologyGraph
The networkx subclassed TopologyGraph with data identifying the nodes
and edges that make up a topology atom and bonds structure

Returns
-------
isomorphic_elements: dict
The keys are unique disconnected graphs, and the values are all
identical subgraphs in the graph

Notes
-----
See https://github.com/networkx/networkx/blob/main/networkx/algorithms/isomorphism/isomorphvf2.py
from the networkx documentation about identifying isomorphic components
"""
graph_queue = deque(
graph.subgraph(c) for c in nx.connected_components(graph)
)

graph_of_interest = graph_queue.popleft()
isomorphic_elements = {
graph_of_interest: [],
}

count = 0
first_mismatch = None

while graph_queue:
if graph_queue[0] == first_mismatch:
count = 0
graph_of_interest = graph_queue.popleft()
isomorphic_elements[graph_of_interest] = []
if graph_queue:
graph = graph_queue.popleft()
matcher = nx.algorithms.isomorphism.GraphMatcher(
graph, graph_of_interest, node_match=top_node_match
)
if matcher.is_isomorphic():
isomorphic_elements[graph_of_interest].append(
(graph, matcher.mapping)
)
else:
if count == 0:
first_mismatch = graph
graph_queue.append(graph)
count += 1
return isomorphic_elements
72 changes: 72 additions & 0 deletions gmso/parameterization/parameterize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Functions used to atomtype a gmso.Topology."""
from gmso.parameterization.topology_parameterizer import (
TopologyParameterizationConfig,
TopologyParameterizer,
)

__all__ = ["apply"]


def apply(
top,
forcefields,
identify_connections=False,
identify_connected_components=True,
use_residue_info=False,
assert_bond_params=True,
assert_angle_params=True,
assert_dihedral_params=True,
assert_improper_params=False,
):
"""Set Topology parameter types from GMSO Forcefields.

Parameters
----------
top: gmso.core.topology.Topology, required
The GMSO topology on which to apply forcefields
forcefields: dict, required
The keys are labels that match the subtopology label or site residue_name, and the
values are gmso Forcefield objects that gets applied to the specified molecule

identify_connections: bool, optional, default=False
If true, add connections identified using networkx graph matching to match
the topology's bonding graph to smaller sub-graphs that correspond to an angle,
dihedral, improper etc

identify_connected_components: bool, optional, default=True
A flag to determine whether or not to search the topology for repeated disconnected
structures, otherwise known as molecules and type each molecule only once.
use_residue_info: bool, optional, default=False
A flag to determine whether or not to look at site.residue_name to look parameterize
each molecule only once. Will only be used if identify_connected_components=False
assert_bond_params : bool, optional, default=True
If True, an error is raised if parameters are not found for all system
bonds.
assert_angle_params : bool, optional, default=True
If True, an error is raised if parameters are not found for all system
angles.
assert_dihedral_params : bool, optional, default=True
If True, an error is raised if parameters are not found for all system
proper dihedrals.
assert_improper_params : bool, optional, default=False
If True, an error is raised if parameters are not found for all system
improper dihedrals.
"""
config = TopologyParameterizationConfig.parse_obj(
dict(
identify_connections=identify_connections,
identify_connected_components=identify_connected_components,
use_residue_info=use_residue_info,
assert_bond_params=assert_bond_params,
assert_angle_params=assert_angle_params,
assert_dihedral_params=assert_dihedral_params,
assert_improper_params=assert_improper_params,
)
)
parameterizer = TopologyParameterizer(
topology=top, forcefields=forcefields, config=config
)

parameterizer.run_parameterization()

return parameterizer.topology
50 changes: 50 additions & 0 deletions gmso/parameterization/subtopology_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Utilities for application of a particular forcefield to a subtopology."""


def _members_in_subtop(connection, subtop):
"""Check if all the members in a connection belong to a subtopology."""
return all(site in subtop.sites for site in connection.connection_members)


def _subtop_connections(subtop, attr):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot, a much-needed function. Awesome.

"""Return all the connections belonging to a subtopology."""
return filter(
lambda conn: _members_in_subtop(conn, subtop),
getattr(subtop._parent, attr),
)


def subtop_bonds(subtop):
"""Given a subtopology, return its bonds."""
return _subtop_connections(subtop, "bonds")


def subtop_angles(subtop):
"""Given a subtopology, return its angles."""
return _subtop_connections(subtop, "angles")


def subtop_dihedrals(subtop):
"""Given a subtopology, return its dihedrals."""
return _subtop_connections(subtop, "dihedrals")


def subtop_impropers(subtop):
"""Given a subtopology, return its impropers."""
return _subtop_connections(subtop, "impropers")


def assert_no_boundary_bonds(subtop):
"""Given a subtopology, assert that no bonds exist between its sites and external sites."""
for bond in subtop._parent.bonds:
site_pairs = bond.connection_members
assertion_msg = "Site {} is in the subtopology {}, but its bonded partner {} is not."

if site_pairs[0] in subtop.sites:
assert site_pairs[1] in subtop.sites, assertion_msg.format(
site_pairs[0].name, subtop.name, site_pairs[1].name
)
elif site_pairs[1] in subtop.sites:
assert site_pairs[0] in subtop.sites, assertion_msg.format(
site_pairs[1].name, subtop.name, site_pairs[0].name
)
Loading