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.
Files changed (63) hide show
  1. synkit/Chem/Fingerprint/__init__.py +0 -0
  2. synkit/Chem/Fingerprint/fp_calculator.py +122 -0
  3. synkit/Chem/Fingerprint/smiles_featurizer.py +185 -0
  4. synkit/Chem/Fingerprint/transformation_fp.py +79 -0
  5. synkit/Chem/Molecule/__init__.py +0 -0
  6. synkit/Chem/Molecule/standardize.py +137 -0
  7. synkit/Chem/Reaction/__init__.py +0 -0
  8. synkit/Chem/Reaction/balance_check.py +162 -0
  9. synkit/Chem/Reaction/cleanning.py +59 -0
  10. synkit/Chem/Reaction/deionize.py +289 -0
  11. synkit/Chem/Reaction/neutralize.py +256 -0
  12. synkit/Chem/Reaction/reagent.py +102 -0
  13. synkit/Chem/Reaction/standardize.py +157 -0
  14. synkit/Chem/Reaction/tautomerize.py +168 -0
  15. synkit/Graph/Cluster/__init__.py +0 -0
  16. synkit/Graph/Cluster/morphism.py +83 -0
  17. synkit/Graph/Feature/__init__.py +0 -0
  18. synkit/Graph/Feature/graph_descriptors.py +325 -0
  19. synkit/Graph/Feature/graph_fps.py +97 -0
  20. synkit/Graph/Feature/graph_signature.py +236 -0
  21. synkit/Graph/Feature/hash_fps.py +130 -0
  22. synkit/Graph/Feature/morgan_fps.py +87 -0
  23. synkit/Graph/Feature/path_fps.py +82 -0
  24. synkit/Graph/__init.py +0 -0
  25. synkit/IO/__init__.py +0 -0
  26. synkit/IO/chem_converter.py +231 -0
  27. synkit/IO/data_io.py +277 -0
  28. synkit/IO/data_process.py +49 -0
  29. synkit/IO/debug.py +78 -0
  30. synkit/IO/dg_to_gml.py +124 -0
  31. synkit/IO/gml_to_nx.py +119 -0
  32. synkit/IO/graph_to_mol.py +110 -0
  33. synkit/IO/mol_to_graph.py +282 -0
  34. synkit/IO/nx_to_gml.py +200 -0
  35. synkit/IO/parse_rule.py +172 -0
  36. synkit/IO/smiles_to_id.py +119 -0
  37. synkit/ITS/_misc.py +280 -0
  38. synkit/ITS/aam_validator.py +254 -0
  39. synkit/ITS/its_builder.py +94 -0
  40. synkit/ITS/its_construction.py +213 -0
  41. synkit/ITS/normalize_aam.py +183 -0
  42. synkit/ITS/partial_expand.py +170 -0
  43. synkit/Reactor/__init__.py +0 -0
  44. synkit/Reactor/core_engine.py +164 -0
  45. synkit/Reactor/inference.py +73 -0
  46. synkit/Reactor/multi_step.py +227 -0
  47. synkit/Reactor/multi_step_aam.py +82 -0
  48. synkit/Reactor/reagent.py +95 -0
  49. synkit/Reactor/rule_apply.py +81 -0
  50. synkit/Vis/__init__.py +0 -0
  51. synkit/Vis/chemical_graph_visualizer.py +378 -0
  52. synkit/Vis/chemical_reaction_visualizer.py +133 -0
  53. synkit/Vis/chemical_space.py +83 -0
  54. synkit/Vis/embedding.py +92 -0
  55. synkit/Vis/graph_visualizer.py +286 -0
  56. synkit/Vis/pdf_writer.py +143 -0
  57. synkit/Vis/rsmi_to_fig.py +169 -0
  58. synkit/__init__.py +0 -0
  59. synkit/_misc.py +181 -0
  60. synkit-0.0.1.dist-info/METADATA +148 -0
  61. synkit-0.0.1.dist-info/RECORD +63 -0
  62. synkit-0.0.1.dist-info/WHEEL +4 -0
  63. 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
+ )