# import mod import molfoundry as mf import networkx as nx import time import re # from equilibrator_assets.generate_compound import create_compounds from prettify import red, warn # each vertex has attribute "label" which stores string label # each edge has attribute "bond" which stores bond order (-, =, etc.) class Graph: def __init__(self, graph, left=False, verbose=False): self._verbose = verbose if isinstance(graph, nx.Graph): self._nx_graph = graph self._gml_string = self.nx_graph_to_GML_string(graph) elif isinstance(graph, str): self._gml_string = graph self._nx_graph = self.GML_to_nx_graph(self._gml_string) elif isinstance(graph, mf.graph.Graph): self._nx_graph = self.mf_to_nx_graph(graph) self._gml_string = self.nx_graph_to_GML_string(self._nx_graph) elif isinstance(graph, mf.rule.Rule): if left: self._nx_graph = self.rule_to_nx_graph(graph, left=True) self._gml_string = self.nx_graph_to_GML_string(self._nx_graph) else: self._nx_graph = self.rule_to_nx_graph(graph, left=False) self._gml_string = self.nx_graph_to_GML_string(self._nx_graph) else: print(red(f"Error: type not found for {type(graph)}")) # up to the caller to check the number of components created ccs = [self._nx_graph.subgraph(c).copy() for c in nx.connected_components(self._nx_graph)] ccs = sorted(ccs, key=len, reverse=True) self._num_components = len(ccs) if self._num_components > 1: self._components = [Graph(c) for c in ccs] else: self._components = [self] if self._num_components == 1: self._mod_graph = self.nx_graph_to_mf(self._nx_graph) def rule_to_nx_graph(self, rule, left): g = nx.Graph() # vertices that don't change for v in rule.vertices: if v.isContext: g.add_node(int(v.id), label=v.leftLabel, modID=int(v.id)) # bonds that don't change for e in rule.edges: if e.isContext: g.add_edge(e.source.id, e.target.id, bond=e.leftLabel) if left: # vertices/edges which are destroyed verts = [v for v in rule.vertices if v.isDeleted] edges = [e for e in rule.edges if e.isDeleted] else: # vertices/edges which are created verts = [v for v in rule.vertices if v.isCreated] edges = [e for e in rule.edges if e.isCreated] # vertices which change label verts.extend([v for v in rule.vertices if v.isChanged]) # bonds which change label edges.extend([e for e in rule.edges if e.isChanged]) for v in verts: vLabel = v.leftLabel if left else v.rightLabel g.add_node(v.id, label=vLabel, modID=v.id) for e in edges: eLabel = e.leftLabel if left else e.rightLabel g.add_edge(e.source.id, e.target.id, bond=eLabel) return g def mf_to_nx_graph(self, mfGraph): g = nx.Graph() for v in mfGraph.vertices: g.add_node(int(v.id), label=str(v.stringLabel), modID=int(v.id)) for e in mfGraph.edges: g.add_edge(int(e.source.id), int(e.target.id), bond=e.stringLabel) return g def nx_graph_to_mf(self, nxGraph): try: return mf.Graph.fromGMLString(self.nx_graph_to_GML_string(nxGraph)) except: # graph is not connected probably if self._verbose: print(warn("Error converting nxGraph to mod graph. Likely graph is not connected. This will not affect rule generation.")) return None def GML_to_nx_graph(self, gml_string): g = nx.Graph() lines = gml_string.split("\n") for line in lines: tokens = line.split() if len(tokens) < 2: continue if tokens[0] == "node": m = re.search(r"label\s+\"(.*)\"", line) l = m.group(1) m = re.search(r"id\s+(\d+)", line) mid = m.group(1) #(_,_,_, mid, _, l, _) = tokens g.add_node(int(mid), label=l, modID=int(mid)) elif tokens[0] == "edge": m = re.search(r"source\s+(\d+)\s+target\s+(\d+)\s+label\s+\"(.*)\"", line) u = m.group(1) v = m.group(2) bondOrder = m.group(3) #(_, _, _, u, _, v, _, bondOrder, _ ) = tokens g.add_edge(int(u), int(v), bond=bondOrder) return g def nx_graph_to_GML_string(self, nxGraph): out = [] out.append("graph [") out.extend([f"\t\tnode [ id {nxGraph.nodes[node]['modID']} label \"{nxGraph.nodes[node]['label']}\" ]" for node in nxGraph.nodes]) out.extend([f"\t\tedge [ source {u} target {v} label \"{nxGraph[u][v]['bond']}\" ]" for (u,v) in nxGraph.edges]) out.append("]") return "\n".join(out) @property def nx_graph(self): return self._nx_graph @property def mod_graph(self): if self.num_components == 1: return self._mod_graph else: return mf.Graph.fromGMLString(self.gml_string) @property def gml_string(self): return self._gml_string @property def gml(self): return self._gml_string @property def edges(self): return self._nx_graph.edges @property def nodes(self): return self._nx_graph.nodes @property def num_components(self): return self._num_components @property def connected_components(self): return self._components def mod_print(self): self._mod_graph.print() def __str__(self) -> str: return self.gml_string def __hash__(self) -> int: return hash(self.gml_string)