-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:OpenFreeEnergy/gufe into shared-obj…
…ect-v2
- Loading branch information
Showing
14 changed files
with
504 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# This code is part of gufe and is licensed under the MIT license. | ||
# For details, see https://github.com/OpenFreeEnergy/gufe | ||
from __future__ import annotations | ||
|
||
import json | ||
import networkx as nx | ||
from typing import FrozenSet, Iterable, Optional | ||
|
||
from gufe import SmallMoleculeComponent | ||
from .mapping import LigandAtomMapping | ||
from .tokenization import GufeTokenizable | ||
|
||
|
||
class LigandNetwork(GufeTokenizable): | ||
"""A directed graph connecting many ligands according to their atom mapping | ||
Parameters | ||
---------- | ||
edges : Iterable[LigandAtomMapping] | ||
edges for this network | ||
nodes : Iterable[SmallMoleculeComponent] | ||
nodes for this network | ||
""" | ||
def __init__( | ||
self, | ||
edges: Iterable[LigandAtomMapping], | ||
nodes: Optional[Iterable[SmallMoleculeComponent]] = None | ||
): | ||
if nodes is None: | ||
nodes = [] | ||
|
||
self._edges = frozenset(edges) | ||
edge_nodes = set.union(*[{edge.componentA, edge.componentB} for edge in edges]) | ||
self._nodes = frozenset(edge_nodes) | frozenset(nodes) | ||
self._graph = None | ||
|
||
@classmethod | ||
def _defaults(cls): | ||
return {} | ||
|
||
def _to_dict(self) -> dict: | ||
return {'graphml': self.to_graphml()} | ||
|
||
@classmethod | ||
def _from_dict(cls, dct: dict): | ||
return cls.from_graphml(dct['graphml']) | ||
|
||
@property | ||
def graph(self) -> nx.Graph: | ||
"""NetworkX graph for this network""" | ||
if self._graph is None: | ||
graph = nx.MultiDiGraph() | ||
# set iterator order depends on PYTHONHASHSEED, sorting ensures | ||
# reproducibility | ||
for node in sorted(self._nodes): | ||
graph.add_node(node) | ||
for edge in sorted(self._edges): | ||
graph.add_edge(edge.componentA, edge.componentB, object=edge, | ||
**edge.annotations) | ||
|
||
self._graph = nx.freeze(graph) | ||
|
||
return self._graph | ||
|
||
@property | ||
def edges(self) -> FrozenSet[LigandAtomMapping]: | ||
"""A read-only view of the edges of the Network""" | ||
return self._edges | ||
|
||
@property | ||
def nodes(self) -> FrozenSet[SmallMoleculeComponent]: | ||
"""A read-only view of the nodes of the Network""" | ||
return self._nodes | ||
|
||
def _serializable_graph(self) -> nx.Graph: | ||
""" | ||
Create NetworkX graph with serializable attribute representations. | ||
This enables us to use easily use different serialization | ||
approaches. | ||
""" | ||
# sorting ensures that we always preserve order in files, so two | ||
# identical networks will show no changes if you diff their | ||
# serialized versions | ||
sorted_nodes = sorted(self.nodes, key=lambda m: (m.smiles, m.name)) | ||
mol_to_label = {mol: f"mol{num}" | ||
for num, mol in enumerate(sorted_nodes)} | ||
|
||
edge_data = sorted([ | ||
( | ||
mol_to_label[edge.componentA], | ||
mol_to_label[edge.componentB], | ||
json.dumps(list(edge.componentA_to_componentB.items())) | ||
) | ||
for edge in self.edges | ||
]) | ||
|
||
# from here, we just build the graph | ||
serializable_graph = nx.MultiDiGraph() | ||
for mol, label in mol_to_label.items(): | ||
serializable_graph.add_node(label, | ||
moldict=json.dumps(mol.to_dict(), | ||
sort_keys=True)) | ||
|
||
for molA, molB, mapping in edge_data: | ||
serializable_graph.add_edge(molA, molB, mapping=mapping) | ||
|
||
return serializable_graph | ||
|
||
@classmethod | ||
def _from_serializable_graph(cls, graph: nx.Graph): | ||
"""Create network from NetworkX graph with serializable attributes. | ||
This is the inverse of ``_serializable_graph``. | ||
""" | ||
label_to_mol = {node: SmallMoleculeComponent.from_dict(json.loads(d)) | ||
for node, d in graph.nodes(data='moldict')} | ||
|
||
edges = [ | ||
LigandAtomMapping(componentA=label_to_mol[node1], | ||
componentB=label_to_mol[node2], | ||
componentA_to_componentB=dict(json.loads(mapping))) | ||
for node1, node2, mapping in graph.edges(data='mapping') | ||
] | ||
|
||
return cls(edges=edges, nodes=label_to_mol.values()) | ||
|
||
def to_graphml(self) -> str: | ||
"""Return the GraphML string representing this ``Network``. | ||
This is the primary serialization mechanism for this class. | ||
Returns | ||
------- | ||
str : | ||
string representing this network in GraphML format | ||
""" | ||
return "\n".join(nx.generate_graphml(self._serializable_graph())) | ||
|
||
@classmethod | ||
def from_graphml(cls, graphml_str: str): | ||
"""Create ``Network`` from GraphML string. | ||
This is the primary deserialization mechanism for this class. | ||
Parameters | ||
---------- | ||
graphml_str : str | ||
GraphML string representation of a :class:`.Network` | ||
Returns | ||
------- | ||
:class:`.Network`: | ||
new network from the GraphML | ||
""" | ||
return cls._from_serializable_graph(nx.parse_graphml(graphml_str)) | ||
|
||
def enlarge_graph(self, *, edges=None, nodes=None) -> LigandNetwork: | ||
""" | ||
Create a new network with the given edges and nodes added | ||
Parameters | ||
---------- | ||
edges : Iterable[:class:`.LigandAtomMapping`] | ||
edges to append to this network | ||
nodes : Iterable[:class:`.SmallMoleculeComponent`] | ||
nodes to append to this network | ||
Returns | ||
------- | ||
:class:`.Network : | ||
a new network adding the given edges and nodes to this network | ||
""" | ||
if edges is None: | ||
edges = set([]) | ||
|
||
if nodes is None: | ||
nodes = set([]) | ||
|
||
return LigandNetwork(self.edges | set(edges), self.nodes | set(nodes)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"> | ||
<key id="d1" for="edge" attr.name="mapping" attr.type="string" /> | ||
<key id="d0" for="node" attr.name="moldict" attr.type="string" /> | ||
<graph edgedefault="directed"> | ||
<node id="mol0"> | ||
<data key="d0">{"__module__": "gufe.components.smallmoleculecomponent", "__qualname__": "SmallMoleculeComponent", "atoms": [[6, 0, 0, false, 0, 0, {}], [6, 0, 0, false, 0, 0, {}]], "bonds": [[0, 1, 1, 0, {}]], "conformer": ["\u0093NUMPY\u0001\u0000v\u0000{'descr': '<f8', 'fortran_order': False, 'shape': (2, 3), } \n\u0000\u0000\u0000\u0000\u0000\u0000\u00e8\u00bf\u0000\u0000\u0000\u0000\u0000\u0000\u0090<\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00e8?\u0000\u0000\u0000\u0000\u0000\u0000\u0090\u00bc\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", {}], "molprops": {"ofe-name": ""}}</data> | ||
</node> | ||
<node id="mol1"> | ||
<data key="d0">{"__module__": "gufe.components.smallmoleculecomponent", "__qualname__": "SmallMoleculeComponent", "atoms": [[6, 0, 0, false, 0, 0, {}], [6, 0, 0, false, 0, 0, {}], [8, 0, 0, false, 0, 0, {}]], "bonds": [[0, 1, 1, 0, {}], [1, 2, 1, 0, {}]], "conformer": ["\u0093NUMPY\u0001\u0000v\u0000{'descr': '<f8', 'fortran_order': False, 'shape': (3, 3), } \n\u00809B.\u00dc\u00c8\u00f4\u00bf\u00f5\u00ff\u00ff\u00ff\u00ff\u00ff\u00cf\u00bf\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u00e0?\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00809B.\u00dc\u00c8\u00f4?\u0006\u0000\u0000\u0000\u0000\u0000\u00d0\u00bf\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", {}], "molprops": {"ofe-name": ""}}</data> | ||
</node> | ||
<node id="mol2"> | ||
<data key="d0">{"__module__": "gufe.components.smallmoleculecomponent", "__qualname__": "SmallMoleculeComponent", "atoms": [[6, 0, 0, false, 0, 0, {}], [8, 0, 0, false, 0, 0, {}]], "bonds": [[0, 1, 1, 0, {}]], "conformer": ["\u0093NUMPY\u0001\u0000v\u0000{'descr': '<f8', 'fortran_order': False, 'shape': (2, 3), } \n\u0000\u0000\u0000\u0000\u0000\u0000\u00e8\u00bf\u0000\u0000\u0000\u0000\u0000\u0000\u0090<\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00e8?\u0000\u0000\u0000\u0000\u0000\u0000\u0090\u00bc\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", {}], "molprops": {"ofe-name": ""}}</data> | ||
</node> | ||
<edge source="mol0" target="mol2" id="0"> | ||
<data key="d1">[[0, 0]]</data> | ||
</edge> | ||
<edge source="mol1" target="mol0" id="0"> | ||
<data key="d1">[[0, 0], [1, 1]]</data> | ||
</edge> | ||
<edge source="mol1" target="mol2" id="0"> | ||
<data key="d1">[[0, 0], [2, 1]]</data> | ||
</edge> | ||
</graph> | ||
</graphml> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#!/usr/bin/env python | ||
# creates ligand_network.graphml | ||
|
||
|
||
from rdkit import Chem | ||
from rdkit.Chem import AllChem | ||
from gufe import SmallMoleculeComponent, LigandNetwork, LigandAtomMapping | ||
|
||
|
||
def mol_from_smiles(smiles: str) -> Chem.Mol: | ||
m = Chem.MolFromSmiles(smiles) | ||
AllChem.Compute2DCoords(m) | ||
|
||
return m | ||
|
||
|
||
# network_template.graphml | ||
mol1 = SmallMoleculeComponent(mol_from_smiles("CCO")) | ||
mol2 = SmallMoleculeComponent(mol_from_smiles("CC")) | ||
mol3 = SmallMoleculeComponent(mol_from_smiles("CO")) | ||
|
||
edge12 = LigandAtomMapping(mol1, mol2, {0: 0, 1: 1}) | ||
edge23 = LigandAtomMapping(mol2, mol3, {0: 0}) | ||
edge13 = LigandAtomMapping(mol1, mol3, {0: 0, 2: 1}) | ||
|
||
network = LigandNetwork([edge12, edge23, edge13]) | ||
|
||
with open("ligand_network.graphml", "w") as fn: | ||
fn.write(network.to_graphml()) |
File renamed without changes.
Oops, something went wrong.