From 5a525f7daba15a50be37b15a08006c405a608002 Mon Sep 17 00:00:00 2001 From: Co Quach Date: Mon, 30 May 2022 15:33:57 -0500 Subject: [PATCH 01/10] initiate bond graph when creating particle --- mbuild/compound.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mbuild/compound.py b/mbuild/compound.py index 52fa8c4f0..06bb3467b 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -175,7 +175,9 @@ def __init__( self.labels = OrderedDict() self.referrers = set() - self.bond_graph = None + self.bond_graph = BondGraph() + self.bond_graph.add_node(self) + self.port_particle = port_particle self._rigid_id = None @@ -703,7 +705,10 @@ def add( new_child.parent = self if new_child.bond_graph is not None: - if self.root.bond_graph is None: + if ( + len(self.root.bond_graph.nodes()) == 1 + and self.root.bond_graph.nodes()[0] == self + ): self.root.bond_graph = new_child.bond_graph else: self.root.bond_graph.compose(new_child.bond_graph) From 719779e17078b1c249fdf45abe25cfd0d48c5903 Mon Sep 17 00:00:00 2001 From: Co Quach Date: Mon, 6 Jun 2022 10:07:35 -0500 Subject: [PATCH 02/10] udpate check in is_independent --- mbuild/compound.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mbuild/compound.py b/mbuild/compound.py index 06bb3467b..68cc63aa3 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -1336,7 +1336,7 @@ def is_independent(self): if not self.parent: # This is the very top level, and hence have to be independent return True - elif not self.root.bond_graph: + elif not self.root.bond_graph.edges(): # If there is no bond in the top level, then everything is independent return True else: From b0cd870d78e1db43e3422f584d3ea5b071d719df Mon Sep 17 00:00:00 2001 From: Co Quach Date: Mon, 6 Jun 2022 11:11:04 -0500 Subject: [PATCH 03/10] Modify Compound.clone and bond_graph.compose to act on ind node --- mbuild/bond_graph.py | 3 ++- mbuild/compound.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mbuild/bond_graph.py b/mbuild/bond_graph.py index 6fb787765..2dd2edc04 100644 --- a/mbuild/bond_graph.py +++ b/mbuild/bond_graph.py @@ -151,7 +151,8 @@ def compose(self, graph): for node, neighbors in graph._adj.items(): if self.has_node(node): (adj[node].add(neighbor) for neighbor in neighbors) - elif neighbors: + else: + # Add new node even if it has no bond/neighbor adj[node] = neighbors def subgraph(self, nodes): diff --git a/mbuild/compound.py b/mbuild/compound.py index 68cc63aa3..19d310109 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -2803,6 +2803,9 @@ def _clone(self, clone_of=None, root_container=None): def _clone_bonds(self, clone_of=None): """Clone the bond of the source compound to clone compound.""" newone = clone_of[self] + newone.bond_graph = BondGraph() + for particle in self.particles(): + newone.bond_graph.add_node(clone_of[particle]) for c1, c2 in self.bonds(): try: newone.add_bond((clone_of[c1], clone_of[c2])) From 3f481871455f13ed262825a68981eccd4252a0c8 Mon Sep 17 00:00:00 2001 From: Co Quach Date: Mon, 6 Jun 2022 11:59:43 -0500 Subject: [PATCH 04/10] Better way to prune bond graph when adding new particle --- mbuild/bond_graph.py | 1 + mbuild/compound.py | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mbuild/bond_graph.py b/mbuild/bond_graph.py index 2dd2edc04..6d97e0d8d 100644 --- a/mbuild/bond_graph.py +++ b/mbuild/bond_graph.py @@ -66,6 +66,7 @@ def remove_node(self, node): for other_node in self.nodes(): if node in adj[other_node]: self.remove_edge(node, other_node) + del adj[node] def has_node(self, node): """Determine whether the graph contains a node.""" diff --git a/mbuild/compound.py b/mbuild/compound.py index 19d310109..24a923cc8 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -705,13 +705,12 @@ def add( new_child.parent = self if new_child.bond_graph is not None: - if ( - len(self.root.bond_graph.nodes()) == 1 - and self.root.bond_graph.nodes()[0] == self - ): - self.root.bond_graph = new_child.bond_graph - else: - self.root.bond_graph.compose(new_child.bond_graph) + # If anything is added at self level, it is no longer a particle + # search for self in self.root.bond_graph and remove self + if self.root.bond_graph.has_node(self): + self.root.bond_graph.remove_node(self) + # Compose bond_graph of new child + self.root.bond_graph.compose(new_child.bond_graph) new_child.bond_graph = None From fcbd76ed7367fbeb4cca6ad02171f70810071be5 Mon Sep 17 00:00:00 2001 From: Co Quach Date: Mon, 6 Jun 2022 12:45:20 -0500 Subject: [PATCH 05/10] exclude port during bond_graph modification --- mbuild/compound.py | 2 +- mbuild/port.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mbuild/compound.py b/mbuild/compound.py index ee0b992df..542ea6f1c 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -704,7 +704,7 @@ def add( self.children.add(new_child) new_child.parent = self - if new_child.bond_graph is not None: + if new_child.bond_graph is not None and not isinstance(self, Port): # If anything is added at self level, it is no longer a particle # search for self in self.root.bond_graph and remove self if self.root.bond_graph.has_node(self): diff --git a/mbuild/port.py b/mbuild/port.py index 060b69cfa..fcc77d4dd 100644 --- a/mbuild/port.py +++ b/mbuild/port.py @@ -40,8 +40,8 @@ class Port(Compound): def __init__(self, anchor=None, orientation=None, separation=0): super(Port, self).__init__(name="Port", port_particle=True) + self.bond_graph = None self.anchor = anchor - default_direction = np.array([0, 1, 0]) if orientation is None: orientation = [0, 1, 0] From 9a2b0fc37e713ead5a51975966a1c5e6e935e4c6 Mon Sep 17 00:00:00 2001 From: Co Quach Date: Tue, 7 Jun 2022 16:33:41 -0500 Subject: [PATCH 06/10] Modify bond graph behavior regarding removal of nodes and edges Fix tests of affected functions --- mbuild/bond_graph.py | 4 ---- mbuild/compound.py | 2 +- mbuild/tests/test_compound.py | 12 +++++++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mbuild/bond_graph.py b/mbuild/bond_graph.py index 6d97e0d8d..da189eedb 100644 --- a/mbuild/bond_graph.py +++ b/mbuild/bond_graph.py @@ -96,10 +96,6 @@ def remove_edge(self, node1, node2): if self.has_node(node1) and self.has_node(node2): adj[node1].remove(node2) adj[node2].remove(node1) - if not adj[node1]: - del adj[node1] - if not adj[node2]: - del adj[node2] else: raise ValueError( "There is no edge between {} and {}".format(node1, node2) diff --git a/mbuild/compound.py b/mbuild/compound.py index 542ea6f1c..1661f70d8 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -860,7 +860,7 @@ def _remove(self, removed_part): if removed_part.rigid_id is not None: for ancestor in removed_part.ancestors(): ancestor._check_if_contains_rigid_bodies = True - if self.root.bond_graph and self.root.bond_graph.has_node(removed_part): + if self.root.bond_graph.has_node(removed_part): for neighbor in self.root.bond_graph.neighbors(removed_part): self.root.remove_bond((removed_part, neighbor)) self.root.bond_graph.remove_node(removed_part) diff --git a/mbuild/tests/test_compound.py b/mbuild/tests/test_compound.py index 465ea244a..39abf3a15 100644 --- a/mbuild/tests/test_compound.py +++ b/mbuild/tests/test_compound.py @@ -1097,14 +1097,20 @@ def test_bond_graph(self, ch3): ) ch3_nobonds = mb.clone(ch3) - for bond in ch3_nobonds.bonds(): + bonds_list = list(ch3_nobonds.bonds()) + for bond in bonds_list: ch3_nobonds.remove_bond(bond) compound.add(ch3_nobonds) assert compound.n_bonds == 3 - assert not any( + assert all( compound.bond_graph.has_node(particle) for particle in ch3_nobonds.particles() ) + assert not any( + compound.bond_graph.has_edge(bond[0], bond[1]) + for bond in bonds_list + ) + assert compound.bond_graph.has_edge carbons = list(compound.particles_by_name("C")) compound.add_bond((carbons[0], carbons[1])) @@ -1118,7 +1124,7 @@ def test_bond_graph(self, ch3): ) compound.remove_bond((carbons[0], carbons[1])) - assert not any( + assert all( compound.bond_graph.has_node(particle) for particle in ch3_nobonds.particles() ) From 6664c173fce601df0e74ed1e81a9ba63a4b55c8d Mon Sep 17 00:00:00 2001 From: Co Quach Date: Tue, 21 Jun 2022 10:29:48 -0500 Subject: [PATCH 07/10] relay kwargs to the gmso conversions method --- mbuild/conversion.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mbuild/conversion.py b/mbuild/conversion.py index a2179cfc0..4cc7aa869 100644 --- a/mbuild/conversion.py +++ b/mbuild/conversion.py @@ -906,9 +906,11 @@ def from_gmso( # Convert gmso Topology to mbuild Compound if not compound: - return to_mbuild(topology, **kwargs) + return to_mbuild(topology, infer_hierarchy=infer_hierarchy, **kwargs) else: - compound.add(to_mbuild(topology), **kwargs) + compound.add( + to_mbuild(topology, infer_hierarchy=infer_hierarchy), **kwargs + ) return compound @@ -1619,14 +1621,17 @@ def _iterate_children(compound, nodes, edges, names_only=False): return nodes, edges -def to_gmso(compound): +def to_gmso(compound, box=None, parse_label=True, **kwargs): """Create a GMSO Topology from a mBuild Compound. Parameters ---------- compound : mb.Compound The mb.Compound to be converted. - + box : mb.Box, optional, default=None + The mb.Box to be converted, if different that compound.box + parse_label : bool, optional, default=True + Option to parse hierarchy information into sites' labels Returns ------- topology : gmso.Topology @@ -1634,7 +1639,7 @@ def to_gmso(compound): """ from gmso.external.convert_mbuild import from_mbuild - return from_mbuild(compound) + return from_mbuild(compound, box=None, parse_label=parse_label**kwargs) def to_intermol(compound, molecule_types=None): # pragma: no cover From 5d9e7b83c24d0afefb3e078659786a6cc9d2fd6f Mon Sep 17 00:00:00 2001 From: Co Quach Date: Tue, 21 Jun 2022 18:43:03 -0500 Subject: [PATCH 08/10] trimming code and fix typo --- mbuild/compound.py | 7 ++----- mbuild/conversion.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/mbuild/compound.py b/mbuild/compound.py index ee2f6d949..78e9df7bc 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -953,11 +953,8 @@ def direct_bonds(self): "The direct_bonds method can only " "be used on compounds at the bottom of their hierarchy." ) - if not self.root.bond_graph: - return iter(()) - elif self.root.bond_graph.has_node(self): - for i in self.root.bond_graph._adj[self]: - yield i + for i in self.root.bond_graph._adj[self]: + yield i def bonds(self): """Return all bonds in the Compound and sub-Compounds. diff --git a/mbuild/conversion.py b/mbuild/conversion.py index 4cc7aa869..9a9f0e64c 100644 --- a/mbuild/conversion.py +++ b/mbuild/conversion.py @@ -1639,7 +1639,7 @@ def to_gmso(compound, box=None, parse_label=True, **kwargs): """ from gmso.external.convert_mbuild import from_mbuild - return from_mbuild(compound, box=None, parse_label=parse_label**kwargs) + return from_mbuild(compound, box=None, parse_label=parse_label, **kwargs) def to_intermol(compound, molecule_types=None): # pragma: no cover From b5033dc2ee3219bcd324f62e6e362c640d2fcd90 Mon Sep 17 00:00:00 2001 From: Co Quach Date: Fri, 24 Jun 2022 10:44:15 -0500 Subject: [PATCH 09/10] temporarily disable infer_hierarchy for from_gmso, pending gmso release --- mbuild/compound.py | 6 ++++-- mbuild/conversion.py | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/mbuild/compound.py b/mbuild/compound.py index 78e9df7bc..9df3f5ea2 100644 --- a/mbuild/compound.py +++ b/mbuild/compound.py @@ -2431,6 +2431,7 @@ def from_gmso(self, topology, coords_only=False, infer_hierarchy=True): Set preexisting atoms in compound to coordinates given by Topology. infer_hierarchy : bool, optional, default=True If True, infer compound hierarchy from Topology residue, to be implemented. + Pending new GMSO release. Returns ------- @@ -2440,10 +2441,11 @@ def from_gmso(self, topology, coords_only=False, infer_hierarchy=True): topology=topology, compound=self, coords_only=coords_only, - infer_hierarchy=infer_hierarchy, + # infer_hierarchy=infer_hierarchy, + # TO DO: enable this with new release of GMSO ) - def to_gmso(self): + def to_gmso(self, **kwargs): """Create a GMSO Topology from a mBuild Compound. Parameters diff --git a/mbuild/conversion.py b/mbuild/conversion.py index 9a9f0e64c..a8989221e 100644 --- a/mbuild/conversion.py +++ b/mbuild/conversion.py @@ -433,7 +433,8 @@ def load_file( topology=top, compound=compound, coords_only=coords_only, - infer_hierarchy=infer_hierarchy, + # infer_hierarchy=infer_hierarchy, + # TODO: enable this with new release of GMSO ) # Then pybel reader @@ -877,6 +878,7 @@ def from_gmso( Set preexisting atoms in compound to coordinates given by Topology. infer_hierarchy : bool, optional, default=True If True, infer compound hierarchy from Topology residue, to be implemented. + Pending new GMSO release. Returns ------- @@ -906,10 +908,20 @@ def from_gmso( # Convert gmso Topology to mbuild Compound if not compound: - return to_mbuild(topology, infer_hierarchy=infer_hierarchy, **kwargs) + return to_mbuild( + topology, + # infer_hierarchy=infer_hierarchy, + # TODO: enable this with new release of GMSO + **kwargs, + ) else: compound.add( - to_mbuild(topology, infer_hierarchy=infer_hierarchy), **kwargs + to_mbuild( + topology, + # infer_hierarchy=infer_hierarchy), + # TODP: enable this with new release of GMSO + **kwargs, + ) ) return compound From 1da04743fc5d2a31216696a4f0b18b76d616580f Mon Sep 17 00:00:00 2001 From: Co Quach Date: Fri, 24 Jun 2022 14:19:41 -0500 Subject: [PATCH 10/10] reverse changes relate to unreleased features in gmso --- mbuild/conversion.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mbuild/conversion.py b/mbuild/conversion.py index a8989221e..f4862b509 100644 --- a/mbuild/conversion.py +++ b/mbuild/conversion.py @@ -1633,7 +1633,7 @@ def _iterate_children(compound, nodes, edges, names_only=False): return nodes, edges -def to_gmso(compound, box=None, parse_label=True, **kwargs): +def to_gmso(compound, box=None, **kwargs): """Create a GMSO Topology from a mBuild Compound. Parameters @@ -1642,8 +1642,7 @@ def to_gmso(compound, box=None, parse_label=True, **kwargs): The mb.Compound to be converted. box : mb.Box, optional, default=None The mb.Box to be converted, if different that compound.box - parse_label : bool, optional, default=True - Option to parse hierarchy information into sites' labels + Returns ------- topology : gmso.Topology @@ -1651,7 +1650,7 @@ def to_gmso(compound, box=None, parse_label=True, **kwargs): """ from gmso.external.convert_mbuild import from_mbuild - return from_mbuild(compound, box=None, parse_label=parse_label, **kwargs) + return from_mbuild(compound, box=None, **kwargs) def to_intermol(compound, molecule_types=None): # pragma: no cover