Skip to content

Commit 14ae8da

Browse files
authored
Modify bond graph to include independent particle (#1036)
* initiate bond graph when creating particle * udpate check in is_independent * Modify Compound.clone and bond_graph.compose to act on ind node * Better way to prune bond graph when adding new particle * exclude port during bond_graph modification * Modify bond graph behavior regarding removal of nodes and edges Fix tests of affected functions * relay kwargs to the gmso conversions method * trimming code and fix typo * temporarily disable infer_hierarchy for from_gmso, pending gmso release * reverse changes relate to unreleased features in gmso
1 parent a7ff062 commit 14ae8da

File tree

5 files changed

+55
-29
lines changed

5 files changed

+55
-29
lines changed

mbuild/bond_graph.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def remove_node(self, node):
6666
for other_node in self.nodes():
6767
if node in adj[other_node]:
6868
self.remove_edge(node, other_node)
69+
del adj[node]
6970

7071
def has_node(self, node):
7172
"""Determine whether the graph contains a node."""
@@ -95,10 +96,6 @@ def remove_edge(self, node1, node2):
9596
if self.has_node(node1) and self.has_node(node2):
9697
adj[node1].remove(node2)
9798
adj[node2].remove(node1)
98-
if not adj[node1]:
99-
del adj[node1]
100-
if not adj[node2]:
101-
del adj[node2]
10299
else:
103100
raise ValueError(
104101
"There is no edge between {} and {}".format(node1, node2)
@@ -151,7 +148,8 @@ def compose(self, graph):
151148
for node, neighbors in graph._adj.items():
152149
if self.has_node(node):
153150
(adj[node].add(neighbor) for neighbor in neighbors)
154-
elif neighbors:
151+
else:
152+
# Add new node even if it has no bond/neighbor
155153
adj[node] = neighbors
156154

157155
def subgraph(self, nodes):

mbuild/compound.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ def __init__(
175175
self.labels = OrderedDict()
176176
self.referrers = set()
177177

178-
self.bond_graph = None
178+
self.bond_graph = BondGraph()
179+
self.bond_graph.add_node(self)
180+
179181
self.port_particle = port_particle
180182

181183
self._rigid_id = None
@@ -702,11 +704,13 @@ def add(
702704
self.children.add(new_child)
703705
new_child.parent = self
704706

705-
if new_child.bond_graph is not None:
706-
if self.root.bond_graph is None:
707-
self.root.bond_graph = new_child.bond_graph
708-
else:
709-
self.root.bond_graph.compose(new_child.bond_graph)
707+
if new_child.bond_graph is not None and not isinstance(self, Port):
708+
# If anything is added at self level, it is no longer a particle
709+
# search for self in self.root.bond_graph and remove self
710+
if self.root.bond_graph.has_node(self):
711+
self.root.bond_graph.remove_node(self)
712+
# Compose bond_graph of new child
713+
self.root.bond_graph.compose(new_child.bond_graph)
710714

711715
new_child.bond_graph = None
712716

@@ -856,7 +860,7 @@ def _remove(self, removed_part):
856860
if removed_part.rigid_id is not None:
857861
for ancestor in removed_part.ancestors():
858862
ancestor._check_if_contains_rigid_bodies = True
859-
if self.root.bond_graph and self.root.bond_graph.has_node(removed_part):
863+
if self.root.bond_graph.has_node(removed_part):
860864
for neighbor in self.root.bond_graph.neighbors(removed_part):
861865
self.root.remove_bond((removed_part, neighbor))
862866
self.root.bond_graph.remove_node(removed_part)
@@ -949,11 +953,8 @@ def direct_bonds(self):
949953
"The direct_bonds method can only "
950954
"be used on compounds at the bottom of their hierarchy."
951955
)
952-
if not self.root.bond_graph:
953-
return iter(())
954-
elif self.root.bond_graph.has_node(self):
955-
for i in self.root.bond_graph._adj[self]:
956-
yield i
956+
for i in self.root.bond_graph._adj[self]:
957+
yield i
957958

958959
def bonds(self):
959960
"""Return all bonds in the Compound and sub-Compounds.
@@ -1381,7 +1382,7 @@ def is_independent(self):
13811382
if not self.parent:
13821383
# This is the very top level, and hence have to be independent
13831384
return True
1384-
elif not self.root.bond_graph:
1385+
elif not self.root.bond_graph.edges():
13851386
# If there is no bond in the top level, then everything is independent
13861387
return True
13871388
else:
@@ -2430,6 +2431,7 @@ def from_gmso(self, topology, coords_only=False, infer_hierarchy=True):
24302431
Set preexisting atoms in compound to coordinates given by Topology.
24312432
infer_hierarchy : bool, optional, default=True
24322433
If True, infer compound hierarchy from Topology residue, to be implemented.
2434+
Pending new GMSO release.
24332435
24342436
Returns
24352437
-------
@@ -2439,10 +2441,11 @@ def from_gmso(self, topology, coords_only=False, infer_hierarchy=True):
24392441
topology=topology,
24402442
compound=self,
24412443
coords_only=coords_only,
2442-
infer_hierarchy=infer_hierarchy,
2444+
# infer_hierarchy=infer_hierarchy,
2445+
# TO DO: enable this with new release of GMSO
24432446
)
24442447

2445-
def to_gmso(self):
2448+
def to_gmso(self, **kwargs):
24462449
"""Create a GMSO Topology from a mBuild Compound.
24472450
24482451
Parameters
@@ -2848,6 +2851,9 @@ def _clone(self, clone_of=None, root_container=None):
28482851
def _clone_bonds(self, clone_of=None):
28492852
"""Clone the bond of the source compound to clone compound."""
28502853
newone = clone_of[self]
2854+
newone.bond_graph = BondGraph()
2855+
for particle in self.particles():
2856+
newone.bond_graph.add_node(clone_of[particle])
28512857
for c1, c2 in self.bonds():
28522858
try:
28532859
newone.add_bond((clone_of[c1], clone_of[c2]))

mbuild/conversion.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ def load_file(
433433
topology=top,
434434
compound=compound,
435435
coords_only=coords_only,
436-
infer_hierarchy=infer_hierarchy,
436+
# infer_hierarchy=infer_hierarchy,
437+
# TODO: enable this with new release of GMSO
437438
)
438439

439440
# Then pybel reader
@@ -877,6 +878,7 @@ def from_gmso(
877878
Set preexisting atoms in compound to coordinates given by Topology.
878879
infer_hierarchy : bool, optional, default=True
879880
If True, infer compound hierarchy from Topology residue, to be implemented.
881+
Pending new GMSO release.
880882
881883
Returns
882884
-------
@@ -906,9 +908,21 @@ def from_gmso(
906908

907909
# Convert gmso Topology to mbuild Compound
908910
if not compound:
909-
return to_mbuild(topology, **kwargs)
911+
return to_mbuild(
912+
topology,
913+
# infer_hierarchy=infer_hierarchy,
914+
# TODO: enable this with new release of GMSO
915+
**kwargs,
916+
)
910917
else:
911-
compound.add(to_mbuild(topology), **kwargs)
918+
compound.add(
919+
to_mbuild(
920+
topology,
921+
# infer_hierarchy=infer_hierarchy),
922+
# TODP: enable this with new release of GMSO
923+
**kwargs,
924+
)
925+
)
912926
return compound
913927

914928

@@ -1619,13 +1633,15 @@ def _iterate_children(compound, nodes, edges, names_only=False):
16191633
return nodes, edges
16201634

16211635

1622-
def to_gmso(compound):
1636+
def to_gmso(compound, box=None, **kwargs):
16231637
"""Create a GMSO Topology from a mBuild Compound.
16241638
16251639
Parameters
16261640
----------
16271641
compound : mb.Compound
16281642
The mb.Compound to be converted.
1643+
box : mb.Box, optional, default=None
1644+
The mb.Box to be converted, if different that compound.box
16291645
16301646
Returns
16311647
-------
@@ -1634,7 +1650,7 @@ def to_gmso(compound):
16341650
"""
16351651
from gmso.external.convert_mbuild import from_mbuild
16361652

1637-
return from_mbuild(compound)
1653+
return from_mbuild(compound, box=None, **kwargs)
16381654

16391655

16401656
def to_intermol(compound, molecule_types=None): # pragma: no cover

mbuild/port.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class Port(Compound):
4040

4141
def __init__(self, anchor=None, orientation=None, separation=0):
4242
super(Port, self).__init__(name="Port", port_particle=True)
43+
self.bond_graph = None
4344
self.anchor = anchor
44-
4545
default_direction = np.array([0, 1, 0])
4646
if orientation is None:
4747
orientation = [0, 1, 0]

mbuild/tests/test_compound.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,14 +1134,20 @@ def test_bond_graph(self, ch3):
11341134
)
11351135

11361136
ch3_nobonds = mb.clone(ch3)
1137-
for bond in ch3_nobonds.bonds():
1137+
bonds_list = list(ch3_nobonds.bonds())
1138+
for bond in bonds_list:
11381139
ch3_nobonds.remove_bond(bond)
11391140
compound.add(ch3_nobonds)
11401141
assert compound.n_bonds == 3
1141-
assert not any(
1142+
assert all(
11421143
compound.bond_graph.has_node(particle)
11431144
for particle in ch3_nobonds.particles()
11441145
)
1146+
assert not any(
1147+
compound.bond_graph.has_edge(bond[0], bond[1])
1148+
for bond in bonds_list
1149+
)
1150+
assert compound.bond_graph.has_edge
11451151

11461152
carbons = list(compound.particles_by_name("C"))
11471153
compound.add_bond((carbons[0], carbons[1]))
@@ -1155,7 +1161,7 @@ def test_bond_graph(self, ch3):
11551161
)
11561162

11571163
compound.remove_bond((carbons[0], carbons[1]))
1158-
assert not any(
1164+
assert all(
11591165
compound.bond_graph.has_node(particle)
11601166
for particle in ch3_nobonds.particles()
11611167
)

0 commit comments

Comments
 (0)