synkit 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synkit/Chem/Fingerprint/__init__.py +0 -0
- synkit/Chem/Fingerprint/fp_calculator.py +122 -0
- synkit/Chem/Fingerprint/smiles_featurizer.py +185 -0
- synkit/Chem/Fingerprint/transformation_fp.py +79 -0
- synkit/Chem/Molecule/__init__.py +0 -0
- synkit/Chem/Molecule/standardize.py +137 -0
- synkit/Chem/Reaction/__init__.py +0 -0
- synkit/Chem/Reaction/balance_check.py +162 -0
- synkit/Chem/Reaction/cleanning.py +59 -0
- synkit/Chem/Reaction/deionize.py +289 -0
- synkit/Chem/Reaction/neutralize.py +256 -0
- synkit/Chem/Reaction/reagent.py +102 -0
- synkit/Chem/Reaction/standardize.py +157 -0
- synkit/Chem/Reaction/tautomerize.py +168 -0
- synkit/Graph/Cluster/__init__.py +0 -0
- synkit/Graph/Cluster/morphism.py +83 -0
- synkit/Graph/Feature/__init__.py +0 -0
- synkit/Graph/Feature/graph_descriptors.py +325 -0
- synkit/Graph/Feature/graph_fps.py +97 -0
- synkit/Graph/Feature/graph_signature.py +236 -0
- synkit/Graph/Feature/hash_fps.py +130 -0
- synkit/Graph/Feature/morgan_fps.py +87 -0
- synkit/Graph/Feature/path_fps.py +82 -0
- synkit/Graph/__init.py +0 -0
- synkit/IO/__init__.py +0 -0
- synkit/IO/chem_converter.py +231 -0
- synkit/IO/data_io.py +277 -0
- synkit/IO/data_process.py +49 -0
- synkit/IO/debug.py +78 -0
- synkit/IO/dg_to_gml.py +124 -0
- synkit/IO/gml_to_nx.py +119 -0
- synkit/IO/graph_to_mol.py +110 -0
- synkit/IO/mol_to_graph.py +282 -0
- synkit/IO/nx_to_gml.py +200 -0
- synkit/IO/parse_rule.py +172 -0
- synkit/IO/smiles_to_id.py +119 -0
- synkit/ITS/_misc.py +280 -0
- synkit/ITS/aam_validator.py +254 -0
- synkit/ITS/its_builder.py +94 -0
- synkit/ITS/its_construction.py +213 -0
- synkit/ITS/normalize_aam.py +183 -0
- synkit/ITS/partial_expand.py +170 -0
- synkit/Reactor/__init__.py +0 -0
- synkit/Reactor/core_engine.py +164 -0
- synkit/Reactor/inference.py +73 -0
- synkit/Reactor/multi_step.py +227 -0
- synkit/Reactor/multi_step_aam.py +82 -0
- synkit/Reactor/reagent.py +95 -0
- synkit/Reactor/rule_apply.py +81 -0
- synkit/Vis/__init__.py +0 -0
- synkit/Vis/chemical_graph_visualizer.py +378 -0
- synkit/Vis/chemical_reaction_visualizer.py +133 -0
- synkit/Vis/chemical_space.py +83 -0
- synkit/Vis/embedding.py +92 -0
- synkit/Vis/graph_visualizer.py +286 -0
- synkit/Vis/pdf_writer.py +143 -0
- synkit/Vis/rsmi_to_fig.py +169 -0
- synkit/__init__.py +0 -0
- synkit/_misc.py +181 -0
- synkit-0.0.1.dist-info/METADATA +148 -0
- synkit-0.0.1.dist-info/RECORD +63 -0
- synkit-0.0.1.dist-info/WHEEL +4 -0
- synkit-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HashFPs:
|
|
7
|
+
def __init__(
|
|
8
|
+
self, graph: nx.Graph, numBits: int = 256, hash_alg: str = "sha256"
|
|
9
|
+
) -> None:
|
|
10
|
+
"""
|
|
11
|
+
Initialize the HashFPs class with a graph and configuration settings.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
- graph (nx.Graph): The graph to be fingerprinted.
|
|
15
|
+
- numBits (int): Number of bits in the output binary hash. Default is 256 bits.
|
|
16
|
+
- hash_alg (str): The hash algorithm to use, such as 'sha256' or 'sha512'.
|
|
17
|
+
|
|
18
|
+
Raises:
|
|
19
|
+
- ValueError: If `numBits` is non-positive or if `hash_alg` is not supported
|
|
20
|
+
by hashlib.
|
|
21
|
+
"""
|
|
22
|
+
self.graph = graph
|
|
23
|
+
self.numBits = numBits
|
|
24
|
+
self.hash_alg = hash_alg
|
|
25
|
+
self.validate_parameters()
|
|
26
|
+
|
|
27
|
+
def validate_parameters(self) -> None:
|
|
28
|
+
"""Validate the initial parameters for errors."""
|
|
29
|
+
if self.numBits <= 0:
|
|
30
|
+
raise ValueError("Number of bits must be positive")
|
|
31
|
+
if not hasattr(hashlib, self.hash_alg):
|
|
32
|
+
raise ValueError(f"Unsupported hash algorithm: {self.hash_alg}")
|
|
33
|
+
|
|
34
|
+
def hash_fps(
|
|
35
|
+
self,
|
|
36
|
+
start_node: Optional[int] = None,
|
|
37
|
+
end_node: Optional[int] = None,
|
|
38
|
+
max_path_length: Optional[int] = None,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Generate a binary hash fingerprint of the graph based on its paths and cycles.
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
- start_node (Optional[int]): The starting node index for path detection.
|
|
45
|
+
- end_node (Optional[int]): The ending node index for path detection.
|
|
46
|
+
- max_path_length (Optional[int]): The maximum length for paths to be considered.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
- str: A binary string representing the truncated hash of the graph's structural
|
|
50
|
+
features.
|
|
51
|
+
"""
|
|
52
|
+
hash_object = self.initialize_hash()
|
|
53
|
+
features = self.extract_features(start_node, end_node, max_path_length)
|
|
54
|
+
full_hash_binary = self.finalize_hash(hash_object, features)
|
|
55
|
+
return full_hash_binary
|
|
56
|
+
|
|
57
|
+
def initialize_hash(self) -> Any:
|
|
58
|
+
"""Initialize and return the hash object based on the specified algorithm."""
|
|
59
|
+
return getattr(hashlib, self.hash_alg)()
|
|
60
|
+
|
|
61
|
+
def extract_features(
|
|
62
|
+
self,
|
|
63
|
+
start_node: Optional[int],
|
|
64
|
+
end_node: Optional[int],
|
|
65
|
+
max_path_length: Optional[int],
|
|
66
|
+
) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Extract features from the graph based on paths and cycles.
|
|
69
|
+
|
|
70
|
+
Parameters:
|
|
71
|
+
- start_node (Optional[int]): The starting node for path detection.
|
|
72
|
+
- end_node (Optional[int]): The ending node for path detection.
|
|
73
|
+
- max_path_length (Optional[int]): Cutoff for path length during detection.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
- str: A string of concatenated feature values.
|
|
77
|
+
"""
|
|
78
|
+
cycles = list(nx.simple_cycles(self.graph))
|
|
79
|
+
paths = []
|
|
80
|
+
if start_node is not None and end_node is not None:
|
|
81
|
+
paths = list(
|
|
82
|
+
nx.all_simple_paths(
|
|
83
|
+
self.graph,
|
|
84
|
+
source=start_node,
|
|
85
|
+
target=end_node,
|
|
86
|
+
cutoff=max_path_length,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
features = [len(c) for c in cycles] + [len(p) for p in paths]
|
|
90
|
+
return "".join(map(str, features))
|
|
91
|
+
|
|
92
|
+
def finalize_hash(self, hash_object: Any, features: str) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Finalize the hash using the features extracted and return the hash as a binary
|
|
95
|
+
string.
|
|
96
|
+
|
|
97
|
+
Parameters:
|
|
98
|
+
- hash_object (Any): The hash object.
|
|
99
|
+
- features (str): Concatenated string of graph features.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
- str: The final binary string of the hash, truncated or extended to `numBits`.
|
|
103
|
+
"""
|
|
104
|
+
hash_object.update(features.encode())
|
|
105
|
+
full_hash_binary = bin(int(hash_object.hexdigest(), 16))[2:]
|
|
106
|
+
if len(full_hash_binary) < self.numBits:
|
|
107
|
+
full_hash_binary += self.iterative_deepening(
|
|
108
|
+
hash_object, self.numBits - len(full_hash_binary)
|
|
109
|
+
)
|
|
110
|
+
return full_hash_binary[: self.numBits]
|
|
111
|
+
|
|
112
|
+
def iterative_deepening(self, hash_object: Any, remaining_bits: int) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Extend hash length using iterative hashing until the desired bit length is
|
|
115
|
+
achieved.
|
|
116
|
+
|
|
117
|
+
Parameters:
|
|
118
|
+
- hash_object (hashlib._Hash): The hash object for iterative deepening.
|
|
119
|
+
- remaining_bits (int): Number of bits needed to reach `numBits`.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
- str: Additional binary data to achieve the desired hash length.
|
|
123
|
+
"""
|
|
124
|
+
additional_data = ""
|
|
125
|
+
while (
|
|
126
|
+
len(additional_data) * 4 < remaining_bits
|
|
127
|
+
): # Each hex digit represents 4 bits
|
|
128
|
+
hash_object.update(additional_data.encode())
|
|
129
|
+
additional_data += hash_object.hexdigest()
|
|
130
|
+
return bin(int(additional_data, 16))[2:][:remaining_bits]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MorganFPs:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
graph: nx.Graph,
|
|
10
|
+
radius: int = 3,
|
|
11
|
+
nBits: int = 1024,
|
|
12
|
+
hash_alg: str = "sha256",
|
|
13
|
+
):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the MorganFPs class to generate fingerprints based on the Morgan
|
|
16
|
+
algorithm, approximating Extended Connectivity Fingerprints (ECFPs).
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
- graph (nx.Graph): The graph to analyze.
|
|
20
|
+
- radius (int): The radius to consider for node neighborhood analysis.
|
|
21
|
+
- nBits (int): Total number of bits in the final fingerprint output.
|
|
22
|
+
- hash_alg (str): Hash algorithm to use for generating hashes of node
|
|
23
|
+
neighborhoods.
|
|
24
|
+
"""
|
|
25
|
+
self.graph = graph
|
|
26
|
+
self.radius = radius
|
|
27
|
+
self.nBits = nBits
|
|
28
|
+
self.hash_alg = hash_alg
|
|
29
|
+
self.hash_function = getattr(hashlib, self.hash_alg)
|
|
30
|
+
|
|
31
|
+
def generate_fingerprint(self) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Generate a binary string fingerprint of the graph based on the local environments
|
|
34
|
+
of nodes. Ensures the output is exactly `nBits` in length using iterative
|
|
35
|
+
deepening if necessary.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
- str: A binary string of length `nBits` representing the fingerprint of the
|
|
39
|
+
graph.
|
|
40
|
+
"""
|
|
41
|
+
fingerprint = ""
|
|
42
|
+
for node in self.graph.nodes():
|
|
43
|
+
neighborhood = nx.single_source_shortest_path_length(
|
|
44
|
+
self.graph, node, cutoff=self.radius
|
|
45
|
+
)
|
|
46
|
+
neighborhood_str = "-".join(
|
|
47
|
+
[
|
|
48
|
+
f"{nbr}-{dist}"
|
|
49
|
+
for nbr, dist in sorted(neighborhood.items())
|
|
50
|
+
if nbr != node
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
hash_obj = self.hash_function(neighborhood_str.encode())
|
|
54
|
+
node_hash = bin(int(hash_obj.hexdigest(), 16))[2:].zfill(
|
|
55
|
+
hash_obj.digest_size * 8
|
|
56
|
+
)
|
|
57
|
+
if len(fingerprint) + len(node_hash) > self.nBits:
|
|
58
|
+
needed_bits = self.nBits - len(fingerprint)
|
|
59
|
+
node_hash = node_hash[:needed_bits]
|
|
60
|
+
fingerprint += node_hash
|
|
61
|
+
if len(fingerprint) == self.nBits:
|
|
62
|
+
return fingerprint
|
|
63
|
+
|
|
64
|
+
if len(fingerprint) < self.nBits:
|
|
65
|
+
fingerprint += self.iterative_deepening(
|
|
66
|
+
hash_obj, self.nBits - len(fingerprint)
|
|
67
|
+
)
|
|
68
|
+
return fingerprint
|
|
69
|
+
|
|
70
|
+
def iterative_deepening(self, hash_object: Any, remaining_bits: int) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Extend the hash length using iterative hashing until the desired bit length is
|
|
73
|
+
achieved.
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
- hash_object (hashlib._Hash): The hash object used for iterative deepening.
|
|
77
|
+
- remaining_bits (int): Number of bits needed to complete the fingerprint to
|
|
78
|
+
`nBits`.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
- str: Additional binary data to achieve the desired hash length.
|
|
82
|
+
"""
|
|
83
|
+
additional_data = ""
|
|
84
|
+
while len(additional_data) * 4 < remaining_bits:
|
|
85
|
+
hash_object.update(additional_data.encode())
|
|
86
|
+
additional_data += hash_object.hexdigest()
|
|
87
|
+
return bin(int(additional_data, 16))[2:][:remaining_bits]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PathFPs:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
graph: nx.Graph,
|
|
10
|
+
max_length: int = 10,
|
|
11
|
+
nBits: int = 1024,
|
|
12
|
+
hash_alg: str = "sha256",
|
|
13
|
+
) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Initialize the PathFPs class to create a binary fingerprint based on paths in a
|
|
16
|
+
graph.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
- graph (nx.Graph): Graph on which to perform analysis.
|
|
20
|
+
- max_length (int): Limit on path lengths considered in the fingerprint.
|
|
21
|
+
- nBits (int): Size of the binary fingerprint in bits.
|
|
22
|
+
- hash_alg (str): Cryptographic hash function used for path hashing.
|
|
23
|
+
- hash_function (Callable): Hash function initialized from hashlib.
|
|
24
|
+
"""
|
|
25
|
+
self.graph = graph
|
|
26
|
+
self.max_length = max_length
|
|
27
|
+
self.nBits = nBits
|
|
28
|
+
self.hash_alg = hash_alg
|
|
29
|
+
self.hash_function = getattr(hashlib, self.hash_alg)
|
|
30
|
+
|
|
31
|
+
def generate_fingerprint(self) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Generate a binary string fingerprint of the graph by hashing paths up to a certain
|
|
34
|
+
length and combining them.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
- str: A binary string of length `nBits` that represents the fingerprint of the
|
|
38
|
+
graph.
|
|
39
|
+
"""
|
|
40
|
+
fingerprint = ""
|
|
41
|
+
for node in self.graph.nodes():
|
|
42
|
+
for target in self.graph.nodes():
|
|
43
|
+
if node != target:
|
|
44
|
+
for path in nx.all_simple_paths(
|
|
45
|
+
self.graph, source=node, target=target, cutoff=self.max_length
|
|
46
|
+
):
|
|
47
|
+
path_str = "-".join(map(str, path))
|
|
48
|
+
hash_obj = self.hash_function(path_str.encode())
|
|
49
|
+
path_hash = bin(int(hash_obj.hexdigest(), 16))[2:].zfill(
|
|
50
|
+
hash_obj.digest_size * 8
|
|
51
|
+
)
|
|
52
|
+
if len(fingerprint) + len(path_hash) > self.nBits:
|
|
53
|
+
needed_bits = self.nBits - len(fingerprint)
|
|
54
|
+
path_hash = path_hash[:needed_bits]
|
|
55
|
+
fingerprint += path_hash
|
|
56
|
+
if len(fingerprint) == self.nBits:
|
|
57
|
+
return fingerprint
|
|
58
|
+
|
|
59
|
+
if len(fingerprint) < self.nBits:
|
|
60
|
+
fingerprint += self.iterative_deepening(
|
|
61
|
+
hash_obj, self.nBits - len(fingerprint)
|
|
62
|
+
)
|
|
63
|
+
return fingerprint
|
|
64
|
+
|
|
65
|
+
def iterative_deepening(self, hash_object: Any, remaining_bits: int) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Extend the hash length using iterative hashing until the desired bit length is
|
|
68
|
+
achieved.
|
|
69
|
+
|
|
70
|
+
Parameters:
|
|
71
|
+
- hash_object (hashlib._Hash): The hash object used for iterative deepening.
|
|
72
|
+
- remaining_bits (int): Number of bits needed to complete the fingerprint
|
|
73
|
+
to `nBits`.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
- str: Additional binary data to achieve the desired hash length.
|
|
77
|
+
"""
|
|
78
|
+
additional_data = ""
|
|
79
|
+
while len(additional_data) * 4 < remaining_bits:
|
|
80
|
+
hash_object.update(additional_data.encode())
|
|
81
|
+
additional_data += hash_object.hexdigest()
|
|
82
|
+
return bin(int(additional_data, 16))[2:][:remaining_bits]
|
synkit/Graph/__init.py
ADDED
|
File without changes
|
synkit/IO/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from rdkit import Chem
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from synkit.IO.debug import setup_logging
|
|
6
|
+
from synkit.IO.mol_to_graph import MolToGraph
|
|
7
|
+
from synkit.IO.graph_to_mol import GraphToMol
|
|
8
|
+
from synkit.ITS.its_construction import ITSConstruction
|
|
9
|
+
from synkit.IO.nx_to_gml import NXToGML
|
|
10
|
+
from synkit.IO.gml_to_nx import GMLToNX
|
|
11
|
+
from synkit.ITS._misc import get_rc, its_decompose
|
|
12
|
+
from synkit._misc import remove_explicit_hydrogen
|
|
13
|
+
|
|
14
|
+
logger = setup_logging()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def smiles_to_graph(
|
|
18
|
+
smiles: str,
|
|
19
|
+
drop_non_aam: bool,
|
|
20
|
+
light_weight: bool,
|
|
21
|
+
sanitize: bool,
|
|
22
|
+
use_index_as_atom_map: bool,
|
|
23
|
+
) -> Optional[nx.Graph]:
|
|
24
|
+
"""
|
|
25
|
+
Helper function to convert SMILES string to a graph using MolToGraph class.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
- smiles (str): SMILES representation of the molecule.
|
|
29
|
+
- drop_non_aam (bool): Whether to drop nodes without atom mapping.
|
|
30
|
+
- light_weight (bool): Whether to create a light-weight graph.
|
|
31
|
+
- sanitize (bool): Whether to sanitize the molecule during conversion.
|
|
32
|
+
- use_index_as_atom_map (bool): Whether to use the index of atoms as atom map numbers
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
- nx.Graph or None: The networkx graph representation of the molecule,
|
|
36
|
+
or None if conversion fails.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# Parse SMILES to a molecule object, without sanitizing initially
|
|
41
|
+
mol = Chem.MolFromSmiles(smiles, sanitize=False)
|
|
42
|
+
if mol is None:
|
|
43
|
+
logger.warning(f"Failed to parse SMILES: {smiles}")
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
# Perform sanitization if requested
|
|
47
|
+
if sanitize:
|
|
48
|
+
try:
|
|
49
|
+
Chem.SanitizeMol(mol)
|
|
50
|
+
except Exception as sanitize_error:
|
|
51
|
+
logger.error(
|
|
52
|
+
f"Sanitization failed for SMILES {smiles}: {sanitize_error}"
|
|
53
|
+
)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
# Convert molecule to graph
|
|
57
|
+
graph_converter = MolToGraph()
|
|
58
|
+
graph = graph_converter.mol_to_graph(
|
|
59
|
+
mol, drop_non_aam, light_weight, use_index_as_atom_map
|
|
60
|
+
)
|
|
61
|
+
if graph is None:
|
|
62
|
+
logger.warning(f"Failed to convert molecule to graph for SMILES: {smiles}")
|
|
63
|
+
return graph
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(
|
|
67
|
+
"Unhandled exception in converting SMILES to graph"
|
|
68
|
+
+ f": {smiles}, Error: {str(e)}"
|
|
69
|
+
)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def rsmi_to_graph(
|
|
74
|
+
rsmi: str,
|
|
75
|
+
drop_non_aam: bool = True,
|
|
76
|
+
light_weight: bool = True,
|
|
77
|
+
sanitize: bool = True,
|
|
78
|
+
use_index_as_atom_map: bool = True,
|
|
79
|
+
) -> Tuple[Optional[nx.Graph], Optional[nx.Graph]]:
|
|
80
|
+
"""
|
|
81
|
+
Converts reactant and product SMILES strings from a reaction SMILES (RSMI) format
|
|
82
|
+
to graph representations.
|
|
83
|
+
|
|
84
|
+
Parameters:
|
|
85
|
+
- rsmi (str): Reaction SMILES string in "reactants>>products" format.
|
|
86
|
+
- drop_non_aam (bool, optional): If True, nodes without atom mapping numbers
|
|
87
|
+
will be dropped.
|
|
88
|
+
- light_weight (bool, optional): If True, creates a light-weight graph.
|
|
89
|
+
- sanitize (bool, optional): If True, sanitizes molecules during conversion.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
- Tuple[Optional[nx.Graph], Optional[nx.Graph]]: A tuple containing t
|
|
93
|
+
he graph representations of the reactants and products.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
reactants_smiles, products_smiles = rsmi.split(">>")
|
|
97
|
+
r_graph = smiles_to_graph(
|
|
98
|
+
reactants_smiles,
|
|
99
|
+
drop_non_aam,
|
|
100
|
+
light_weight,
|
|
101
|
+
sanitize,
|
|
102
|
+
use_index_as_atom_map,
|
|
103
|
+
)
|
|
104
|
+
p_graph = smiles_to_graph(
|
|
105
|
+
products_smiles, drop_non_aam, light_weight, sanitize, use_index_as_atom_map
|
|
106
|
+
)
|
|
107
|
+
return (r_graph, p_graph)
|
|
108
|
+
except ValueError:
|
|
109
|
+
logger.error(f"Invalid RSMI format: {rsmi}")
|
|
110
|
+
return (None, None)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def graph_to_rsmi(
|
|
114
|
+
r: nx.Graph,
|
|
115
|
+
p: nx.Graph,
|
|
116
|
+
its: nx.Graph,
|
|
117
|
+
sanitize: bool = True,
|
|
118
|
+
explicit_hydrogen: bool = False,
|
|
119
|
+
ignore_hcount_inference: bool = False,
|
|
120
|
+
) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Converts graph representations of reactants and products into a
|
|
123
|
+
reaction SMILES string.
|
|
124
|
+
|
|
125
|
+
Parameters:
|
|
126
|
+
- r (nx.Graph): Graph of the reactants.
|
|
127
|
+
- p (nx.Graph): Graph of the products.
|
|
128
|
+
- its (nx.Graph): Intermediate transition state graph, relevant for hydrogen count
|
|
129
|
+
inference.
|
|
130
|
+
- sanitize (bool): Specifies whether the molecule should be sanitized upon conversion.
|
|
131
|
+
- explicit_hydrogen (bool): Controls whether hydrogens are explicitly represented in
|
|
132
|
+
the output.
|
|
133
|
+
- ignore_hcount_inference (bool): If false, hydrogens counts are inferred from
|
|
134
|
+
the ITS graph.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
- str: Reaction SMILES string representing the conversion from reactants to products.
|
|
138
|
+
"""
|
|
139
|
+
# Initialize a GraphToMol converter
|
|
140
|
+
converter = GraphToMol()
|
|
141
|
+
|
|
142
|
+
if not explicit_hydrogen:
|
|
143
|
+
# Decide whether to infer hydrogen count based on the ITS graph
|
|
144
|
+
if ignore_hcount_inference:
|
|
145
|
+
r_mol = converter.graph_to_mol(r, sanitize=sanitize, use_h_count=True)
|
|
146
|
+
p_mol = converter.graph_to_mol(p, sanitize=sanitize, use_h_count=True)
|
|
147
|
+
else:
|
|
148
|
+
rc = get_rc(its)
|
|
149
|
+
r = remove_explicit_hydrogen(r, rc.nodes())
|
|
150
|
+
p = remove_explicit_hydrogen(p, rc.nodes())
|
|
151
|
+
r_mol = converter.graph_to_mol(r, sanitize=sanitize, use_h_count=True)
|
|
152
|
+
p_mol = converter.graph_to_mol(p, sanitize=sanitize, use_h_count=True)
|
|
153
|
+
else:
|
|
154
|
+
r_mol = converter.graph_to_mol(r, sanitize=sanitize)
|
|
155
|
+
p_mol = converter.graph_to_mol(p, sanitize=sanitize)
|
|
156
|
+
|
|
157
|
+
# Convert RDKit Mol objects to SMILES and format them into a reaction SMILES string
|
|
158
|
+
try:
|
|
159
|
+
r_smiles = Chem.MolToSmiles(r_mol)
|
|
160
|
+
p_smiles = Chem.MolToSmiles(p_mol)
|
|
161
|
+
reaction_smiles = f"{r_smiles}>>{p_smiles}"
|
|
162
|
+
except Exception as e:
|
|
163
|
+
# Handle errors gracefully
|
|
164
|
+
reaction_smiles = "Error in generating SMILES: " + str(e)
|
|
165
|
+
|
|
166
|
+
return reaction_smiles
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def smart_to_gml(
|
|
170
|
+
smart: str,
|
|
171
|
+
core: bool = True,
|
|
172
|
+
sanitize: bool = False,
|
|
173
|
+
rule_name: str = "rule",
|
|
174
|
+
reindex: bool = True,
|
|
175
|
+
explicit_hydrogen: bool = False,
|
|
176
|
+
) -> str:
|
|
177
|
+
"""
|
|
178
|
+
Converts a SMARTS string to GML format, optionally focusing on the reaction core.
|
|
179
|
+
|
|
180
|
+
Parameters:
|
|
181
|
+
- smart (str): The SMARTS string representing the reaction.
|
|
182
|
+
- core (bool): Whether to extract and focus on the reaction core. Defaults to True.
|
|
183
|
+
- sanitize (bool): Specifies whether the molecule should be sanitized upon conversion.
|
|
184
|
+
- rule_name (str): The name of the reaction rule. Defaults to "rule".
|
|
185
|
+
- reindex (bool): Whether to reindex the graph nodes. Defaults to True.
|
|
186
|
+
- explicit_hydrogen (bool): Controls whether hydrogens are explicitly represented
|
|
187
|
+
in the output.
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
- str: The GML representation of the reaction.
|
|
192
|
+
"""
|
|
193
|
+
r, p = rsmi_to_graph(smart, sanitize=sanitize)
|
|
194
|
+
its = ITSConstruction.ITSGraph(r, p)
|
|
195
|
+
if core:
|
|
196
|
+
its = get_rc(its)
|
|
197
|
+
r, p = its_decompose(its)
|
|
198
|
+
gml = NXToGML().transform(
|
|
199
|
+
(r, p, its),
|
|
200
|
+
reindex=reindex,
|
|
201
|
+
rule_name=rule_name,
|
|
202
|
+
explicit_hydrogen=explicit_hydrogen,
|
|
203
|
+
)
|
|
204
|
+
return gml
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def gml_to_smart(
|
|
208
|
+
gml: str,
|
|
209
|
+
sanitize: bool = True,
|
|
210
|
+
explicit_hydrogen: bool = False,
|
|
211
|
+
ignore_hcount_inference: bool = False,
|
|
212
|
+
) -> str:
|
|
213
|
+
"""
|
|
214
|
+
Converts a GML string back to a SMARTS string by interpreting the graph structures.
|
|
215
|
+
|
|
216
|
+
Parameters:
|
|
217
|
+
- gml (str): The GML string to convert.
|
|
218
|
+
- sanitize (bool): Specifies whether the molecule should be sanitized upon conversion.
|
|
219
|
+
- explicit_hydrogen (bool): Controls whether hydrogens are explicitly represented
|
|
220
|
+
in the output.
|
|
221
|
+
- ignore_hcount_inference (bool): If false, hydrogens counts are inferred
|
|
222
|
+
from the ITS graph.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
- str: The corresponding SMARTS string.
|
|
226
|
+
"""
|
|
227
|
+
r, p, rc = GMLToNX(gml).transform()
|
|
228
|
+
return (
|
|
229
|
+
graph_to_rsmi(r, p, rc, sanitize, explicit_hydrogen, ignore_hcount_inference),
|
|
230
|
+
rc,
|
|
231
|
+
)
|