import mod import networkx as nx import time # from equilibrator_assets.generate_compound import create_compounds # each vertex has attribute "label" which stores string label # each edge has attribute "bond" which stores bond order (-, =, etc.) class GraphObj: def __init__(self, graph, verbose=False): self._verbose = verbose mod_lg_class = mod.libpymod.Rule.LeftGraph mod_rg_class = mod.libpymod.Rule.RightGraph mod_g_class = mod.libpymod.Graph if isinstance(graph, mod_lg_class) or isinstance(graph, mod_rg_class) or isinstance(graph, mod_g_class): self._nx_graph = self.mod_to_nx_graph(graph) self._gml_string = self.nx_graph_to_GML_string(self._nx_graph) elif isinstance(graph, nx.Graph): self._nx_graph = graph self._gml_string = self.nx_graph_to_GML_string(self._nx_graph) elif isinstance(graph, str): self._gml_string = graph self._nx_graph = self.GML_to_nx_graph(self._gml_string) else: print(f"ERROR: Graph class cannot identify graph type in constructor: {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_mod(self._nx_graph) # self._equ_compounds = self.equ_compounds() def equ_compounds(self): smiles = [c.mod_graph.smiles for c in self._components] equ_comps = create_compounds(smiles, mol_format="smiles", bypass_chemaxon=True, save_empty_compounds=True) return equ_comps def mod_to_nx_graph(self, modGraph: mod.Graph): g = nx.Graph() for v in modGraph.vertices: g.add_node(int(v.id), label=str(v.stringLabel), modID=int(v.id)) for e in modGraph.edges: g.add_edge(int(e.source.id), int(e.target.id), bond=str(e.bondType)) return g def nx_graph_to_mod(self, nxGraph): try: return mod.graphGMLString(self.nx_graph_to_GML_string(nxGraph)) except mod.libpymod.InputError: # graph is not connected probably if self._verbose: print("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": (_,_,_, mid, _, l, _) = tokens g.add_node(int(mid), label=l[1:-1], modID=int(mid)) elif tokens[0] == "edge": (_, _, _, u, _, v, _, bondOrder, _ ) = tokens g.add_edge(int(u), int(v), bond=bondOrder[1:-1]) 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 mod.graphGMLString(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)