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

Improved performance of bond look up #1084

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
192504a
basic routines to get bonds from connected components; avoids overcou…
Jan 29, 2023
54087a6
Merge branch 'mosdef-hub:main' into fastbonds
chrisiacovella Jan 29, 2023
bff95b6
Merge branch 'fastbonds' of https://github.com/chrisiacovella/mbuild …
Jan 31, 2023
f8b9140
faster routines for accessing bonds
Jan 31, 2023
6f79726
Merge branch 'main' into fastbonds
chrisiacovella Jan 31, 2023
940b893
updating tests
Jan 31, 2023
c3a25d8
updating tests; need to use _bonds() to ensure order
Jan 31, 2023
033a952
amended logic to avoid edge cases
Feb 1, 2023
4966687
Merge branch 'main' into fastbonds
daico007 Feb 1, 2023
49320bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 1, 2023
7283f22
trying to fix a failing test; error type exception has changed
Feb 1, 2023
1f7acb7
Merge branch 'fastbonds' of https://github.com/chrisiacovella/mbuild …
Feb 1, 2023
53c9b99
minor formatting
Feb 2, 2023
385e782
instead of writing a temp file, directly set/get obmol data
Jan 20, 2023
f649663
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 20, 2023
4236b60
removing lines of code I commented out related to reading and writing…
Jan 20, 2023
f2c8857
inference of element form name never actually set the element field, …
Jan 22, 2023
7c9544c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 20, 2023
306128c
change test for non element testing
Jan 23, 2023
d0c5e1c
kwargs was not passed from compound.to_gmso, to conversion.gmso
Jan 26, 2023
e662623
basic routines to get bonds from connected components; avoids overcou…
Jan 29, 2023
1ced074
faster routines for accessing bonds
Jan 31, 2023
9fd4e11
Reorder bond, angle, and dihedral in write_lammpsdata (#1071)
jaclark5 Jan 30, 2023
a584b31
updating tests
Jan 31, 2023
56d7d7e
updating tests; need to use _bonds() to ensure order
Jan 31, 2023
8e99976
amended logic to avoid edge cases
Feb 1, 2023
7f62baf
trying to fix a failing test; error type exception has changed
Feb 1, 2023
10bb6b6
[pre-commit.ci] pre-commit autoupdate (#1083)
pre-commit-ci[bot] Feb 1, 2023
1a815d2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 1, 2023
a1ff85b
minor formatting
Feb 2, 2023
46960bc
minor formatting
Feb 2, 2023
e2633ac
updated and merged branch to master
Feb 2, 2023
1296b1d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
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
75 changes: 74 additions & 1 deletion mbuild/compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import ele
import numpy as np
from boltons.setutils import IndexedSet
from ele.element import Element, element_from_name, element_from_symbol
from ele.exceptions import ElementError

Expand Down Expand Up @@ -972,7 +973,7 @@ def direct_bonds(self):
for i in self.root.bond_graph._adj[self]:
yield i

def bonds(self):
def _bonds(self):
"""Return all bonds in the Compound and sub-Compounds.

Yields
Expand All @@ -995,6 +996,78 @@ def bonds(self):
else:
return iter(())

def bonds(self, use_connected=True):
"""Return all bonds in the Compound and sub-Compounds. By default,
this uses the connected components to improve performance when possible.

Yields
------
tuple of mb.Compound
The next bond in the Compound

Parameters
----------
use_connected : bool, optional, default=True
Uses the connected_components information in the bond_graph to
retrieve bonded information for sub-Compounds. In most cases,
this will be substantially faster.

See Also
--------
bond_graph.edges_iter : Iterates over all edges in a BondGraph
Compound.n_bonds : Returns the total number of bonds in the Compound and sub-Compounds
Compound._bonds : Return all bonds in the Compound and sub-Compounds
"""

if use_connected:
if self.root.bond_graph:
connected_subgraph = self.root.bond_graph.connected_components()
bonds = []
molecule_tags = {}

Check notice

Code scanning / CodeQL

Unused local variable

Variable molecule_tags is not used.
for molecule in connected_subgraph:
# if molecule contains a single particle it will not contain any bonds and can be skipped
if len(molecule) > 1:
# if the Compound of interest is not an ancestor of molecule, move to the next molecule
ancestors = [ann for ann in molecule[0].ancestors()]
if self in ancestors:
if len(ancestors) > 1:
# this will set ancestors[0] to the lowest level Compound that
# contains all the connected components in molecule
ancestors = IndexedSet(molecule[0].ancestors())
for particle in molecule[1:]:
ancestors = ancestors.intersection(
IndexedSet(particle.ancestors())
)
# if the compound we are interested in is the lowest level ancestor or root, find the bonds
if ancestors[0] == self or self == self.root:
for b in self.root.bond_graph.subgraph(
molecule
).edges_iter():
bonds.append(b)
# if the Compound of interest is not at a lower level than the lowest level container (i.e. ancestors[0])
# then ancestors[0] is a sub-Compound of the Compound of interest
elif self not in [
part for part in ancestors[0].successors()
]:
for b in self.root.bond_graph.subgraph(
molecule
).edges_iter():
bonds.append(b)

# If we did not add any bonds to the list that does not mean there are no bonds in the Compound of interest.
# It may mean that we are looking at a Compound with bonds to other Compounds
# (e.g., a CH3 in ethane; ethane would be the lowest level connected component stored in ancestors[0])
# As such, we will just return the original bond routine to retrieve the relevant info
if len(bonds) == 0:
return self._bonds()
else:
return iter(bonds)
else:
return self._bonds()

else:
return self._bonds()

@property
def n_direct_bonds(self):
"""Return the number of bonds a particle is directly involved in.
Expand Down
28 changes: 27 additions & 1 deletion mbuild/tests/test_compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,32 @@ def test_direct_bonds_cloning(self, ethane):
for p1, p2 in zip(ethane.particles(), ethane_clone.particles()):
assert p1.n_direct_bonds == p2.n_direct_bonds

def test_bond_speedup(self, ethane):
# does the efficient code give the same bonds as the slower code?
ethane_clone = mb.clone(ethane)
assert sum([1 for _ in ethane_clone.bonds()]) == sum(
[1 for _ in ethane_clone._bonds()]
)

# with added hierarchy, do we still get the same answers
ethane_clone2 = mb.clone(ethane)
system = mb.Compound()
system.add(ethane_clone)
system.add(ethane_clone2)

assert sum([1 for _ in system.bonds()]) == sum(
[1 for _ in system._bonds()]
)
assert sum([1 for _ in system.bonds()]) == sum(
[1 for _ in system.bonds(use_connected=False)]
)

# check we can traverse the hierarchy and get the correct number of bonds
ethane_clone3 = mb.clone(ethane)
assert sum([1 for _ in ethane_clone3.bonds()]) == sum(
[1 for _ in ethane_clone2.bonds()]
)

def test_load_protein(self):
# Testing the loading function with complicated protein,
# The protein file is taken from RCSB protein data bank
Expand Down Expand Up @@ -2154,6 +2180,6 @@ def test_xyz_setter(self):
def test_ordered_bonds(self):
ethane = mb.load("CC", smiles=True)
ethane2 = mb.load("CC", smiles=True)
for bond2, bond in zip(ethane2.bonds(), ethane.bonds()):
for bond2, bond in zip(ethane2._bonds(), ethane._bonds()):
assert bond2[0].name == bond[0].name
assert all(bond2[0].pos == bond[0].pos)