synkit 1.6.5__py3-none-win_amd64.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 (636) hide show
  1. synkit/CRN/Benchmark/__init__.py +53 -0
  2. synkit/CRN/Benchmark/crosschecks.py +13 -0
  3. synkit/CRN/Benchmark/kegg.py +12 -0
  4. synkit/CRN/Benchmark/networks.py +13 -0
  5. synkit/CRN/Benchmark/scaling.py +13 -0
  6. synkit/CRN/Benchmark/validate.py +13 -0
  7. synkit/CRN/Construct/__init__.py +16 -0
  8. synkit/CRN/Construct/abstract.py +668 -0
  9. synkit/CRN/Construct/arity.py +36 -0
  10. synkit/CRN/Construct/builder.py +619 -0
  11. synkit/CRN/Construct/derivation.py +63 -0
  12. synkit/CRN/Construct/flattener.py +100 -0
  13. synkit/CRN/Construct/keys.py +14 -0
  14. synkit/CRN/Construct/mixtures.py +117 -0
  15. synkit/CRN/Construct/rdkit_worker.py +69 -0
  16. synkit/CRN/Construct/smiles.py +99 -0
  17. synkit/CRN/Construct/state.py +31 -0
  18. synkit/CRN/Construct/strategy.py +75 -0
  19. synkit/CRN/Construct/worker.py +42 -0
  20. synkit/CRN/IO/__init__.py +36 -0
  21. synkit/CRN/IO/sbml.py +925 -0
  22. synkit/CRN/Pathway/__init__.py +72 -0
  23. synkit/CRN/Pathway/_adapter.py +333 -0
  24. synkit/CRN/Pathway/pathfinder.py +500 -0
  25. synkit/CRN/Pathway/reachability.py +796 -0
  26. synkit/CRN/Pathway/realizability.py +1322 -0
  27. synkit/CRN/Petrinet/__init__.py +93 -0
  28. synkit/CRN/Petrinet/analyzer.py +486 -0
  29. synkit/CRN/Petrinet/minimal_semiflows.py +436 -0
  30. synkit/CRN/Petrinet/net.py +1041 -0
  31. synkit/CRN/Petrinet/persistence.py +97 -0
  32. synkit/CRN/Petrinet/semiflows.py +483 -0
  33. synkit/CRN/Petrinet/siphon_search.py +402 -0
  34. synkit/CRN/Petrinet/structure.py +286 -0
  35. synkit/CRN/Props/__init__.py +130 -0
  36. synkit/CRN/Props/_rank.py +29 -0
  37. synkit/CRN/Props/deficiency.py +953 -0
  38. synkit/CRN/Props/dynamics.py +832 -0
  39. synkit/CRN/Props/helper.py +151 -0
  40. synkit/CRN/Props/stoich.py +751 -0
  41. synkit/CRN/Props/thermo.py +584 -0
  42. synkit/CRN/Query/__init__.py +66 -0
  43. synkit/CRN/Query/cache.py +41 -0
  44. synkit/CRN/Query/kegg_api.py +99 -0
  45. synkit/CRN/Query/kegg_extract.py +714 -0
  46. synkit/CRN/Query/kegg_impute.py +707 -0
  47. synkit/CRN/Query/kegg_parse.py +480 -0
  48. synkit/CRN/Query/to_syncrn.py +290 -0
  49. synkit/CRN/README.md +234 -0
  50. synkit/CRN/Structure/__init__.py +12 -0
  51. synkit/CRN/Structure/_graph_io.py +713 -0
  52. synkit/CRN/Structure/_matrices.py +88 -0
  53. synkit/CRN/Structure/_parse.py +544 -0
  54. synkit/CRN/Structure/reaction.py +237 -0
  55. synkit/CRN/Structure/rule.py +55 -0
  56. synkit/CRN/Structure/species.py +46 -0
  57. synkit/CRN/Structure/syncrn.py +929 -0
  58. synkit/CRN/Symmetry/__init__.py +20 -0
  59. synkit/CRN/Symmetry/_common.py +1059 -0
  60. synkit/CRN/Symmetry/_ir.py +940 -0
  61. synkit/CRN/Symmetry/automorphism.py +472 -0
  62. synkit/CRN/Symmetry/canon.py +441 -0
  63. synkit/CRN/Symmetry/isomorphism.py +412 -0
  64. synkit/CRN/Symmetry/symmetry.py +61 -0
  65. synkit/CRN/Symmetry/wl_canon.py +967 -0
  66. synkit/CRN/Visualize/__init__.py +11 -0
  67. synkit/CRN/Visualize/labels.py +113 -0
  68. synkit/CRN/Visualize/layout.py +708 -0
  69. synkit/CRN/Visualize/palette.py +292 -0
  70. synkit/CRN/Visualize/validation.py +122 -0
  71. synkit/CRN/Visualize/vis.py +1152 -0
  72. synkit/CRN/__init__.py +233 -0
  73. synkit/CRN/_build.py +50 -0
  74. synkit/CRN/_native_build.py +175 -0
  75. synkit/CRN/analysis.py +629 -0
  76. synkit/CRN/backend.py +74 -0
  77. synkit/CRN/cpp/CMakeLists.txt +58 -0
  78. synkit/CRN/cpp/algebra.cpp +46 -0
  79. synkit/CRN/cpp/closure.cpp +29 -0
  80. synkit/CRN/cpp/exact.h +55 -0
  81. synkit/CRN/cpp/pathway.cpp +46 -0
  82. synkit/CRN/cpp/semiflows.cpp +209 -0
  83. synkit/CRN/cpp/structural.cpp +240 -0
  84. synkit/CRN/cpp/symmetry.cpp +45 -0
  85. synkit/CRN/cpp/write_manifest.cmake +28 -0
  86. synkit/CRN/kinds.py +137 -0
  87. synkit/CRN/lib/synkit_crn.dll +0 -0
  88. synkit/CRN/lib/synkit_crn.json +33 -0
  89. synkit/CRN/native.py +388 -0
  90. synkit/CRN/native_analysis.py +459 -0
  91. synkit/Chem/Cluster/__init__.py +0 -0
  92. synkit/Chem/Cluster/butina.py +140 -0
  93. synkit/Chem/Fingerprint/__init__.py +0 -0
  94. synkit/Chem/Fingerprint/fp_calculator.py +155 -0
  95. synkit/Chem/Fingerprint/smiles_featurizer.py +258 -0
  96. synkit/Chem/Fingerprint/transformation_fp.py +139 -0
  97. synkit/Chem/Mapper/README.rst +43 -0
  98. synkit/Chem/Mapper/__init__.py +255 -0
  99. synkit/Chem/Mapper/_compat.py +130 -0
  100. synkit/Chem/Mapper/adapters/__init__.py +1 -0
  101. synkit/Chem/Mapper/adapters/prediction.py +126 -0
  102. synkit/Chem/Mapper/adapters/template.py +412 -0
  103. synkit/Chem/Mapper/chem/__init__.py +52 -0
  104. synkit/Chem/Mapper/chem/aam.py +673 -0
  105. synkit/Chem/Mapper/chem/blind.py +160 -0
  106. synkit/Chem/Mapper/chem/its.py +309 -0
  107. synkit/Chem/Mapper/chem/smiles.py +564 -0
  108. synkit/Chem/Mapper/graph/__init__.py +66 -0
  109. synkit/Chem/Mapper/graph/automorphism.py +395 -0
  110. synkit/Chem/Mapper/graph/blockcut.py +98 -0
  111. synkit/Chem/Mapper/graph/labeled_graph.py +104 -0
  112. synkit/Chem/Mapper/graph/refine.py +176 -0
  113. synkit/Chem/Mapper/graph/synkit_adapter.py +110 -0
  114. synkit/Chem/Mapper/io/__init__.py +13 -0
  115. synkit/Chem/Mapper/io/idxmapstr.py +82 -0
  116. synkit/Chem/Mapper/mapper.py +685 -0
  117. synkit/Chem/Mapper/reaction_search.py +554 -0
  118. synkit/Chem/Mapper/reporting/__init__.py +1 -0
  119. synkit/Chem/Mapper/reporting/alternatives.py +451 -0
  120. synkit/Chem/Mapper/reporting/analysis.py +681 -0
  121. synkit/Chem/Mapper/reporting/native_analysis.py +485 -0
  122. synkit/Chem/Mapper/reporting/spectrum.py +642 -0
  123. synkit/Chem/Mapper/solvers/__init__.py +1 -0
  124. synkit/Chem/Mapper/solvers/exact/__init__.py +141 -0
  125. synkit/Chem/Mapper/solvers/exact/assignment/__init__.py +1 -0
  126. synkit/Chem/Mapper/solvers/exact/assignment/assignment_certificate.py +113 -0
  127. synkit/Chem/Mapper/solvers/exact/assignment/assignment_domains.py +162 -0
  128. synkit/Chem/Mapper/solvers/exact/assignment/assignment_verification.py +58 -0
  129. synkit/Chem/Mapper/solvers/exact/assignment/incremental_assignment.py +247 -0
  130. synkit/Chem/Mapper/solvers/exact/certificate.py +128 -0
  131. synkit/Chem/Mapper/solvers/exact/core.py +183 -0
  132. synkit/Chem/Mapper/solvers/exact/distance_search/__init__.py +1 -0
  133. synkit/Chem/Mapper/solvers/exact/distance_search/bounds.py +319 -0
  134. synkit/Chem/Mapper/solvers/exact/distance_search/exhaustive.py +179 -0
  135. synkit/Chem/Mapper/solvers/exact/distance_search/inputs.py +283 -0
  136. synkit/Chem/Mapper/solvers/exact/distance_search/records.py +242 -0
  137. synkit/Chem/Mapper/solvers/exact/distance_search/search.py +1025 -0
  138. synkit/Chem/Mapper/solvers/exact/distance_search/verify.py +378 -0
  139. synkit/Chem/Mapper/solvers/exact/edit_support.py +375 -0
  140. synkit/Chem/Mapper/solvers/exact/hybrid.py +224 -0
  141. synkit/Chem/Mapper/solvers/exact/hydrogen.py +264 -0
  142. synkit/Chem/Mapper/solvers/exact/kernel_solvers/__init__.py +1 -0
  143. synkit/Chem/Mapper/solvers/exact/kernel_solvers/branching.py +499 -0
  144. synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumerate.py +1050 -0
  145. synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumeration_result.py +30 -0
  146. synkit/Chem/Mapper/solvers/exact/kernel_solvers/kernel.py +256 -0
  147. synkit/Chem/Mapper/solvers/exact/kernel_solvers/milp.py +194 -0
  148. synkit/Chem/Mapper/solvers/exact/native/__init__.py +1 -0
  149. synkit/Chem/Mapper/solvers/exact/native/build.py +154 -0
  150. synkit/Chem/Mapper/solvers/exact/native/candidates.py +593 -0
  151. synkit/Chem/Mapper/solvers/exact/native/canonical.py +396 -0
  152. synkit/Chem/Mapper/solvers/exact/native/disk_frontier.py +415 -0
  153. synkit/Chem/Mapper/solvers/exact/native/frontier.py +390 -0
  154. synkit/Chem/Mapper/solvers/exact/native/hydrogen_quotient.py +213 -0
  155. synkit/Chem/Mapper/solvers/exact/native/its.py +528 -0
  156. synkit/Chem/Mapper/solvers/exact/native/native_distance.cpp +2803 -0
  157. synkit/Chem/Mapper/solvers/exact/native/orbit_aggregation.py +328 -0
  158. synkit/Chem/Mapper/solvers/exact/native/parallel.py +205 -0
  159. synkit/Chem/Mapper/solvers/exact/native/pmcd.py +203 -0
  160. synkit/Chem/Mapper/solvers/exact/native/runtime.py +40 -0
  161. synkit/Chem/Mapper/solvers/exact/native/search.py +294 -0
  162. synkit/Chem/Mapper/solvers/exact/native/separator.py +224 -0
  163. synkit/Chem/Mapper/solvers/exact/native_build.py +12 -0
  164. synkit/Chem/Mapper/solvers/exact/pabs/__init__.py +1 -0
  165. synkit/Chem/Mapper/solvers/exact/pabs/cost_lattice.py +43 -0
  166. synkit/Chem/Mapper/solvers/exact/pabs/factor_spectrum.py +110 -0
  167. synkit/Chem/Mapper/solvers/exact/pabs/multi_shell.py +368 -0
  168. synkit/Chem/Mapper/solvers/exact/pabs/propagation.py +679 -0
  169. synkit/Chem/Mapper/solvers/exact/pabs/propagation_bounds.py +436 -0
  170. synkit/Chem/Mapper/solvers/exact/pabs/propagation_limits.py +13 -0
  171. synkit/Chem/Mapper/solvers/exact/pabs/propagation_search.py +856 -0
  172. synkit/Chem/Mapper/solvers/exact/pabs/propagation_symmetry.py +66 -0
  173. synkit/Chem/Mapper/solvers/exact/pabs/residual_typed_bonds.py +136 -0
  174. synkit/Chem/Mapper/solvers/exact/pabs/reward_frontier.py +201 -0
  175. synkit/Chem/Mapper/solvers/exact/pabs/separator_bound.py +258 -0
  176. synkit/Chem/Mapper/solvers/exact/pabs/separator_spectrum.py +308 -0
  177. synkit/Chem/Mapper/solvers/exact/pabs/sparse_reward_bound.py +207 -0
  178. synkit/Chem/Mapper/solvers/exact/pabs/suffix_spectrum.py +299 -0
  179. synkit/Chem/Mapper/solvers/exact/search.py +55 -0
  180. synkit/Chem/Mapper/solvers/exact/seeds/__init__.py +1 -0
  181. synkit/Chem/Mapper/solvers/exact/seeds/seed.py +104 -0
  182. synkit/Chem/Mapper/solvers/exact/seeds/seed_fragments.py +148 -0
  183. synkit/Chem/Mapper/solvers/exact/seeds/seed_relaxation.py +181 -0
  184. synkit/Chem/Mapper/solvers/exact/symmetry.py +223 -0
  185. synkit/Chem/Mapper/solvers/native/__init__.py +4 -0
  186. synkit/Chem/Mapper/solvers/native/assignment.py +99 -0
  187. synkit/Chem/Mapper/solvers/native/astar.py +22 -0
  188. synkit/Chem/Mapper/solvers/native/build.py +385 -0
  189. synkit/Chem/Mapper/solvers/native/classification.py +641 -0
  190. synkit/Chem/Mapper/solvers/native/core.py +378 -0
  191. synkit/Chem/Mapper/solvers/native/cpp/CMakeLists.txt +120 -0
  192. synkit/Chem/Mapper/solvers/native/cpp/examples/map_reaction.cpp +29 -0
  193. synkit/Chem/Mapper/solvers/native/cpp/examples/slap_graph.cpp +63 -0
  194. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/assignment/assignment.h +18 -0
  195. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/astar.h +18 -0
  196. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/bound.h +33 -0
  197. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/pmcd_witness.h +16 -0
  198. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/runtime.h +130 -0
  199. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/hydrogen/hydrogen.h +73 -0
  200. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mapper.h +175 -0
  201. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mces/mces.h +9 -0
  202. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/pabs/seed.h +34 -0
  203. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/reaction/reaction.h +30 -0
  204. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/sepdp/sepdp.h +21 -0
  205. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/slap.h +72 -0
  206. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/solvers/solvers.h +16 -0
  207. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/classification.h +140 -0
  208. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/cpabs.h +33 -0
  209. synkit/Chem/Mapper/solvers/native/cpp/src/assignment/abb.cpp +302 -0
  210. synkit/Chem/Mapper/solvers/native/cpp/src/astar/CMakeLists.txt +12 -0
  211. synkit/Chem/Mapper/solvers/native/cpp/src/astar/README.md +47 -0
  212. synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar.cpp +228 -0
  213. synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar_graph.cpp +37 -0
  214. synkit/Chem/Mapper/solvers/native/cpp/src/astar/bound.cpp +212 -0
  215. synkit/Chem/Mapper/solvers/native/cpp/src/core/dispatch.cpp +272 -0
  216. synkit/Chem/Mapper/solvers/native/cpp/src/core/pmcd_witness.cpp +36 -0
  217. synkit/Chem/Mapper/solvers/native/cpp/src/hydrogen/hydrogen.cpp +380 -0
  218. synkit/Chem/Mapper/solvers/native/cpp/src/mces/README.md +57 -0
  219. synkit/Chem/Mapper/solvers/native/cpp/src/mces/mces.cpp +287 -0
  220. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/bridge.cpp +334 -0
  221. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/chemical_floor.inc +181 -0
  222. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/endpoint_symmetry.inc +104 -0
  223. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/exact_helpers.h +20 -0
  224. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_assignment_certificate.h +165 -0
  225. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_certificate.h +125 -0
  226. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_model.h +365 -0
  227. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_provider.inc +319 -0
  228. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_rounding.inc +170 -0
  229. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_test_hooks.h +342 -0
  230. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/objective_reduction.inc +137 -0
  231. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/overlap_dual.inc +351 -0
  232. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/profile_seed.inc +214 -0
  233. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/seed_heuristics.inc +640 -0
  234. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_embedding.cpp +430 -0
  235. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_root_lp.h +176 -0
  236. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/test_hooks.inc +262 -0
  237. synkit/Chem/Mapper/solvers/native/cpp/src/reaction/reaction.cpp +611 -0
  238. synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/README.md +61 -0
  239. synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/sepdp.cpp +253 -0
  240. synkit/Chem/Mapper/solvers/native/cpp/src/slap.cpp +810 -0
  241. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/formulation.h +138 -0
  242. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/maxsat.cpp +526 -0
  243. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/ortools.cpp +475 -0
  244. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/seed.h +80 -0
  245. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/weighted_cnf.h +256 -0
  246. synkit/Chem/Mapper/solvers/native/cpp/src/supporting/README.md +43 -0
  247. synkit/Chem/Mapper/solvers/native/cpp/src/supporting/dfs.cpp +123 -0
  248. synkit/Chem/Mapper/solvers/native/cpp/src/supporting/edit_support.cpp +224 -0
  249. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification.cpp +553 -0
  250. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_pipeline.cpp +124 -0
  251. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_stream.h +13 -0
  252. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/cpabs.cpp +455 -0
  253. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/edge_deck.cpp +743 -0
  254. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
  255. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
  256. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naugraph.c +732 -0
  257. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.c +94 -0
  258. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.h +49 -0
  259. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.c +1771 -0
  260. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.h +133 -0
  261. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nautil.c +750 -0
  262. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naututil.h +355 -0
  263. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.c +1256 -0
  264. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.h +1544 -0
  265. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.c +1131 -0
  266. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.h +70 -0
  267. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/sorttemplates.c +580 -0
  268. synkit/Chem/Mapper/solvers/native/hydrogen.py +711 -0
  269. synkit/Chem/Mapper/solvers/native/implicit.py +506 -0
  270. synkit/Chem/Mapper/solvers/native/implicit_certificate.py +187 -0
  271. synkit/Chem/Mapper/solvers/native/implicit_preparation.py +309 -0
  272. synkit/Chem/Mapper/solvers/native/lib/synkit_mapper.dll +0 -0
  273. synkit/Chem/Mapper/solvers/native/mces.py +16 -0
  274. synkit/Chem/Mapper/solvers/native/reaction.py +179 -0
  275. synkit/Chem/Mapper/solvers/native/reaction_build.py +277 -0
  276. synkit/Chem/Mapper/solvers/native/reaction_text.py +26 -0
  277. synkit/Chem/Mapper/solvers/native/runtime.py +242 -0
  278. synkit/Chem/Mapper/solvers/native/sepdp.py +47 -0
  279. synkit/Chem/Mapper/solvers/native/slap.py +266 -0
  280. synkit/Chem/Mapper/solvers/native/solvers.py +67 -0
  281. synkit/Chem/Mapper/solvers/native/stereo.py +86 -0
  282. synkit/Chem/Mapper/solvers/native/supporting.py +34 -0
  283. synkit/Chem/Mapper/solvers/native/symmetry.py +91 -0
  284. synkit/Chem/Mapper/solvers/slap/__init__.py +29 -0
  285. synkit/Chem/Mapper/solvers/slap/lap.py +174 -0
  286. synkit/Chem/Mapper/solvers/slap/sequential.py +696 -0
  287. synkit/Chem/Mapper/validation/__init__.py +1 -0
  288. synkit/Chem/Mapper/validation/aam_validator.py +442 -0
  289. synkit/Chem/Mapper/validation/annotations.py +274 -0
  290. synkit/Chem/Mapper/validation/bonds.py +124 -0
  291. synkit/Chem/Mapper/validation/cohorts.py +72 -0
  292. synkit/Chem/Mapper/validation/identifiability.py +150 -0
  293. synkit/Chem/Mapper/validation/numerical_scope.py +36 -0
  294. synkit/Chem/Mapper/validation/orbits.py +137 -0
  295. synkit/Chem/Molecule/__init__.py +0 -0
  296. synkit/Chem/Molecule/_chirality_loci.py +165 -0
  297. synkit/Chem/Molecule/_chirality_types.py +231 -0
  298. synkit/Chem/Molecule/_helical_loci.py +167 -0
  299. synkit/Chem/Molecule/_stereo_axis_evidence.py +202 -0
  300. synkit/Chem/Molecule/_stereo_element_detection.py +193 -0
  301. synkit/Chem/Molecule/_stereo_orientation_constraints.py +511 -0
  302. synkit/Chem/Molecule/_tetrahedral_carriers.py +63 -0
  303. synkit/Chem/Molecule/atom_features.py +379 -0
  304. synkit/Chem/Molecule/chirality.py +1041 -0
  305. synkit/Chem/Molecule/cip_assignment.py +569 -0
  306. synkit/Chem/Molecule/cip_ranking.py +843 -0
  307. synkit/Chem/Molecule/coordinate_stereo.py +503 -0
  308. synkit/Chem/Molecule/derived_stereo_naming.py +165 -0
  309. synkit/Chem/Molecule/descriptors.py +452 -0
  310. synkit/Chem/Molecule/formula.py +249 -0
  311. synkit/Chem/Molecule/global_stereo.py +175 -0
  312. synkit/Chem/Molecule/graph_annotator.py +300 -0
  313. synkit/Chem/Molecule/standardize.py +817 -0
  314. synkit/Chem/Molecule/stereo_evidence.py +179 -0
  315. synkit/Chem/Molecule/stereo_perception.py +1032 -0
  316. synkit/Chem/Molecule/valence.py +123 -0
  317. synkit/Chem/Reaction/Mapper/__init__.py +44 -0
  318. synkit/Chem/Reaction/__init__.py +20 -0
  319. synkit/Chem/Reaction/aam_validator.py +5 -0
  320. synkit/Chem/Reaction/balance_check.py +138 -0
  321. synkit/Chem/Reaction/canon_rsmi.py +249 -0
  322. synkit/Chem/Reaction/cleaning.py +58 -0
  323. synkit/Chem/Reaction/deionize.py +199 -0
  324. synkit/Chem/Reaction/explicit_h_audit.py +232 -0
  325. synkit/Chem/Reaction/fix_aam.py +64 -0
  326. synkit/Chem/Reaction/neutralize.py +207 -0
  327. synkit/Chem/Reaction/radical_wildcard.py +224 -0
  328. synkit/Chem/Reaction/standardize.py +203 -0
  329. synkit/Chem/Reaction/tautomerize.py +205 -0
  330. synkit/Chem/__init__.py +1 -0
  331. synkit/Chem/utils.py +315 -0
  332. synkit/Data/__init__.py +0 -0
  333. synkit/Data/aldol.json.gz +1 -0
  334. synkit/Data/gen_partial_aam.py +147 -0
  335. synkit/Data/paracetamol.json.gz +1 -0
  336. synkit/Graph/Canon/__init__.py +16 -0
  337. synkit/Graph/Canon/_native/__init__.py +13 -0
  338. synkit/Graph/Canon/_native/build.py +93 -0
  339. synkit/Graph/Canon/_native/lib/synkit_canon.dll +0 -0
  340. synkit/Graph/Canon/_native/prepare.py +49 -0
  341. synkit/Graph/Canon/_native/runtime.py +140 -0
  342. synkit/Graph/Canon/_native/search.py +137 -0
  343. synkit/Graph/Canon/canon_algs.py +228 -0
  344. synkit/Graph/Canon/canon_graph.py +531 -0
  345. synkit/Graph/Canon/cpp/CMakeLists.txt +42 -0
  346. synkit/Graph/Canon/cpp/README.md +123 -0
  347. synkit/Graph/Canon/cpp/examples/canonical_graph.cpp +16 -0
  348. synkit/Graph/Canon/cpp/include/synkit_canon/canon.h +85 -0
  349. synkit/Graph/Canon/cpp/src/canonical.cpp +391 -0
  350. synkit/Graph/Canon/cpp/src/symmetry.h +83 -0
  351. synkit/Graph/Canon/cpp/tests/symmetry.cpp +93 -0
  352. synkit/Graph/Canon/exact.py +1020 -0
  353. synkit/Graph/Canon/exact_refinement.py +368 -0
  354. synkit/Graph/Canon/nauty.py +143 -0
  355. synkit/Graph/Context/__init__.py +0 -0
  356. synkit/Graph/Context/hier_context.py +244 -0
  357. synkit/Graph/Context/radius_expand.py +252 -0
  358. synkit/Graph/FG/__init__.py +19 -0
  359. synkit/Graph/FG/api.py +39 -0
  360. synkit/Graph/FG/audit.py +155 -0
  361. synkit/Graph/FG/catalog.py +919 -0
  362. synkit/Graph/FG/detector.py +288 -0
  363. synkit/Graph/FG/model.py +97 -0
  364. synkit/Graph/FG/recognizers.py +274 -0
  365. synkit/Graph/FG/ring_system.py +154 -0
  366. synkit/Graph/Feature/Descriptors/topology.py +857 -0
  367. synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
  368. synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +222 -0
  369. synkit/Graph/Feature/__init__.py +5 -0
  370. synkit/Graph/Feature/graph_descriptors.py +323 -0
  371. synkit/Graph/Feature/graph_fps.py +96 -0
  372. synkit/Graph/Feature/graph_signature.py +239 -0
  373. synkit/Graph/Feature/hash_fps.py +133 -0
  374. synkit/Graph/Feature/morgan_fps.py +88 -0
  375. synkit/Graph/Feature/path_fps.py +81 -0
  376. synkit/Graph/Feature/wl_hash.py +134 -0
  377. synkit/Graph/Fusion/__init__.py +85 -0
  378. synkit/Graph/Fusion/candidate.py +559 -0
  379. synkit/Graph/Fusion/constructor.py +877 -0
  380. synkit/Graph/Fusion/identity.py +184 -0
  381. synkit/Graph/Fusion/interface.py +700 -0
  382. synkit/Graph/Fusion/search.py +241 -0
  383. synkit/Graph/Hyrogen/__init__.py +6 -0
  384. synkit/Graph/Hyrogen/_misc.py +591 -0
  385. synkit/Graph/Hyrogen/hcomplete.py +995 -0
  386. synkit/Graph/Hyrogen/hcomplete_legacy.py +368 -0
  387. synkit/Graph/Hyrogen/hcompletion_algorithms.py +713 -0
  388. synkit/Graph/Hyrogen/hextend.py +570 -0
  389. synkit/Graph/Hyrogen/hextend_legacy.py +215 -0
  390. synkit/Graph/ITS/__init__.py +4 -0
  391. synkit/Graph/ITS/its_builder.py +120 -0
  392. synkit/Graph/ITS/its_construction.py +719 -0
  393. synkit/Graph/ITS/its_decompose.py +572 -0
  394. synkit/Graph/ITS/its_destruction.py +331 -0
  395. synkit/Graph/ITS/its_expand.py +1251 -0
  396. synkit/Graph/ITS/its_relabel.py +187 -0
  397. synkit/Graph/ITS/its_reverter.py +356 -0
  398. synkit/Graph/ITS/normalize_aam.py +145 -0
  399. synkit/Graph/ITS/partial_its.py +238 -0
  400. synkit/Graph/ITS/rc_extractor.py +656 -0
  401. synkit/Graph/ITS/stereo.py +74 -0
  402. synkit/Graph/MTG/__init__.py +107 -0
  403. synkit/Graph/MTG/gluing.py +451 -0
  404. synkit/Graph/MTG/history.py +928 -0
  405. synkit/Graph/MTG/inventory.py +118 -0
  406. synkit/Graph/MTG/local.py +447 -0
  407. synkit/Graph/MTG/mcs_matcher.py +246 -0
  408. synkit/Graph/MTG/mtg.py +611 -0
  409. synkit/Graph/MTG/mtg_explore.py +74 -0
  410. synkit/Graph/MTG/observation.py +209 -0
  411. synkit/Graph/MTG/process.py +577 -0
  412. synkit/Graph/MTG/queries.py +66 -0
  413. synkit/Graph/MTG/realization.py +95 -0
  414. synkit/Graph/MTG/series_parallel.py +107 -0
  415. synkit/Graph/MTG/utils.py +424 -0
  416. synkit/Graph/Matcher/__init__.py +10 -0
  417. synkit/Graph/Matcher/approx_mcs.py +1264 -0
  418. synkit/Graph/Matcher/auto_est.py +736 -0
  419. synkit/Graph/Matcher/automorphism.py +314 -0
  420. synkit/Graph/Matcher/batch_cluster.py +224 -0
  421. synkit/Graph/Matcher/dedup_matches.py +247 -0
  422. synkit/Graph/Matcher/graph_cluster.py +191 -0
  423. synkit/Graph/Matcher/graph_matcher.py +312 -0
  424. synkit/Graph/Matcher/graph_morphism.py +421 -0
  425. synkit/Graph/Matcher/mcs_matcher.py +804 -0
  426. synkit/Graph/Matcher/multi_turbo_iso.py +186 -0
  427. synkit/Graph/Matcher/orbit.py +284 -0
  428. synkit/Graph/Matcher/partial_matcher.py +694 -0
  429. synkit/Graph/Matcher/sing.py +544 -0
  430. synkit/Graph/Matcher/subgraph_matcher.py +812 -0
  431. synkit/Graph/Matcher/turbo_iso.py +272 -0
  432. synkit/Graph/Matcher/wl_sel.py +537 -0
  433. synkit/Graph/Mech/__init__.py +26 -0
  434. synkit/Graph/Mech/conversion.py +1466 -0
  435. synkit/Graph/Mech/conversion_debug.py +237 -0
  436. synkit/Graph/Mech/electron_accounting.py +200 -0
  437. synkit/Graph/Mech/lwg_editor.py +406 -0
  438. synkit/Graph/Mech/lwg_ops.py +313 -0
  439. synkit/Graph/Morphism/__init__.py +99 -0
  440. synkit/Graph/Morphism/constraints.py +460 -0
  441. synkit/Graph/Morphism/llg.py +999 -0
  442. synkit/Graph/Morphism/morphism.py +349 -0
  443. synkit/Graph/Morphism/stereo.py +258 -0
  444. synkit/Graph/Morphism/stereo_morphism.py +383 -0
  445. synkit/Graph/Morphism/stereo_morphism_helpers.py +957 -0
  446. synkit/Graph/Morphism/stereo_morphism_types.py +117 -0
  447. synkit/Graph/Stereo/__init__.py +376 -0
  448. synkit/Graph/Stereo/_descriptor_core.py +207 -0
  449. synkit/Graph/Stereo/_descriptor_serialization.py +138 -0
  450. synkit/Graph/Stereo/canonical.py +648 -0
  451. synkit/Graph/Stereo/changes.py +964 -0
  452. synkit/Graph/Stereo/composition.py +391 -0
  453. synkit/Graph/Stereo/configured.py +946 -0
  454. synkit/Graph/Stereo/couplings.py +245 -0
  455. synkit/Graph/Stereo/descriptors.py +1030 -0
  456. synkit/Graph/Stereo/enumeration.py +597 -0
  457. synkit/Graph/Stereo/extended_descriptors.py +406 -0
  458. synkit/Graph/Stereo/global_configured.py +152 -0
  459. synkit/Graph/Stereo/global_stereo.py +304 -0
  460. synkit/Graph/Stereo/identity.py +648 -0
  461. synkit/Graph/Stereo/interchange.py +496 -0
  462. synkit/Graph/Stereo/legacy.py +460 -0
  463. synkit/Graph/Stereo/matching.py +899 -0
  464. synkit/Graph/Stereo/native.py +154 -0
  465. synkit/Graph/Stereo/orbits.py +668 -0
  466. synkit/Graph/Stereo/outcomes.py +99 -0
  467. synkit/Graph/Stereo/pairwise.py +350 -0
  468. synkit/Graph/Stereo/rdkit_adapter.py +662 -0
  469. synkit/Graph/Stereo/rdkit_identity.py +118 -0
  470. synkit/Graph/Stereo/rdkit_resonance.py +140 -0
  471. synkit/Graph/Stereo/reaction_interchange.py +266 -0
  472. synkit/Graph/Stereo/semantics.py +296 -0
  473. synkit/Graph/Stereo/supports.py +257 -0
  474. synkit/Graph/Stereo/wire.py +339 -0
  475. synkit/Graph/Wildcard/__init__.py +0 -0
  476. synkit/Graph/Wildcard/fuse_graph.py +156 -0
  477. synkit/Graph/Wildcard/graph_wc.py +303 -0
  478. synkit/Graph/Wildcard/its_merge.py +857 -0
  479. synkit/Graph/Wildcard/radwc.py +114 -0
  480. synkit/Graph/Wildcard/wc_matcher.py +576 -0
  481. synkit/Graph/Wildcard/wildcard.py +225 -0
  482. synkit/Graph/__init__.py +17 -0
  483. synkit/Graph/canon_graph.py +14 -0
  484. synkit/Graph/syn_graph.py +199 -0
  485. synkit/Graph/utils.py +324 -0
  486. synkit/IO/__init__.py +4 -0
  487. synkit/IO/chem_converter.py +1059 -0
  488. synkit/IO/combinatorial/__init__.py +8 -0
  489. synkit/IO/combinatorial/gml_to_graph.py +223 -0
  490. synkit/IO/combinatorial/graph_to_gml.py +280 -0
  491. synkit/IO/combinatorial/graph_to_smarts.py +186 -0
  492. synkit/IO/combinatorial/smarts_expander.py +147 -0
  493. synkit/IO/combinatorial/smarts_generalizer.py +133 -0
  494. synkit/IO/combinatorial/smarts_to_graph.py +179 -0
  495. synkit/IO/conversion.py +42 -0
  496. synkit/IO/data_io.py +269 -0
  497. synkit/IO/data_process.py +143 -0
  498. synkit/IO/debug.py +73 -0
  499. synkit/IO/gml_to_nx.py +150 -0
  500. synkit/IO/graph_to_mol.py +199 -0
  501. synkit/IO/mol_to_graph.py +1105 -0
  502. synkit/IO/mol_to_graph_chemistry.py +545 -0
  503. synkit/IO/nx_to_gml.py +209 -0
  504. synkit/IO/smiles_to_id.py +123 -0
  505. synkit/Mechanism/__init__.py +151 -0
  506. synkit/Mechanism/adapters.py +136 -0
  507. synkit/Mechanism/audit.py +323 -0
  508. synkit/Mechanism/benchmark.py +638 -0
  509. synkit/Mechanism/compatibility.py +13 -0
  510. synkit/Mechanism/drawing.py +85 -0
  511. synkit/Mechanism/electron_models.py +725 -0
  512. synkit/Mechanism/equivalence.py +521 -0
  513. synkit/Mechanism/evidence.py +262 -0
  514. synkit/Mechanism/interchange.py +141 -0
  515. synkit/Mechanism/model.py +918 -0
  516. synkit/Mechanism/neighbor_stereo.py +312 -0
  517. synkit/Mechanism/radical_data.py +740 -0
  518. synkit/Mechanism/replay.py +986 -0
  519. synkit/Mechanism/schema.py +264 -0
  520. synkit/Mechanism/stereo_state.py +416 -0
  521. synkit/Mechanism/symbols.py +91 -0
  522. synkit/Rule/Apply/__init__.py +35 -0
  523. synkit/Rule/Apply/conditions.py +45 -0
  524. synkit/Rule/Apply/dpo.py +998 -0
  525. synkit/Rule/Apply/rule_matcher.py +218 -0
  526. synkit/Rule/Apply/validation.py +354 -0
  527. synkit/Rule/Compose/__init__.py +106 -0
  528. synkit/Rule/Compose/_identity.py +43 -0
  529. synkit/Rule/Compose/composition.py +976 -0
  530. synkit/Rule/Compose/laws.py +439 -0
  531. synkit/Rule/Compose/search.py +771 -0
  532. synkit/Rule/Compose/synrule.py +170 -0
  533. synkit/Rule/Modify/__init__.py +0 -0
  534. synkit/Rule/Modify/implict_rule.py +56 -0
  535. synkit/Rule/Modify/longest_path.py +92 -0
  536. synkit/Rule/Modify/molecule_rule.py +115 -0
  537. synkit/Rule/Modify/prune_templates.py +78 -0
  538. synkit/Rule/Modify/rule_utils.py +194 -0
  539. synkit/Rule/Modify/strip_rule.py +97 -0
  540. synkit/Rule/__init__.py +28 -0
  541. synkit/Rule/generic_stereo.py +884 -0
  542. synkit/Rule/stereo_identity.py +271 -0
  543. synkit/Rule/syn_rule.py +1090 -0
  544. synkit/Synthesis/MSR/__init__.py +0 -0
  545. synkit/Synthesis/MSR/path_finder.py +239 -0
  546. synkit/Synthesis/Metrics/__init__.py +0 -0
  547. synkit/Synthesis/Metrics/_base.py +52 -0
  548. synkit/Synthesis/Metrics/_plot.py +129 -0
  549. synkit/Synthesis/Metrics/_ranking.py +179 -0
  550. synkit/Synthesis/RBL/__init__.py +80 -0
  551. synkit/Synthesis/RBL/engine.py +1249 -0
  552. synkit/Synthesis/RBL/matching.py +652 -0
  553. synkit/Synthesis/RBL/overlap.py +336 -0
  554. synkit/Synthesis/RBL/pipeline.py +376 -0
  555. synkit/Synthesis/RBL/policy.py +139 -0
  556. synkit/Synthesis/RBL/proof.py +370 -0
  557. synkit/Synthesis/RBL/reaction.py +506 -0
  558. synkit/Synthesis/RBL/state.py +169 -0
  559. synkit/Synthesis/RBL/validation.py +1060 -0
  560. synkit/Synthesis/Reactor/__init__.py +60 -0
  561. synkit/Synthesis/Reactor/core/__init__.py +1 -0
  562. synkit/Synthesis/Reactor/core/engine.py +967 -0
  563. synkit/Synthesis/Reactor/core/product.py +621 -0
  564. synkit/Synthesis/Reactor/core/rewrite.py +561 -0
  565. synkit/Synthesis/Reactor/core/strategy.py +44 -0
  566. synkit/Synthesis/Reactor/matching/__init__.py +1 -0
  567. synkit/Synthesis/Reactor/matching/mixin.py +920 -0
  568. synkit/Synthesis/Reactor/matching/policy.py +345 -0
  569. synkit/Synthesis/Reactor/matching/symmetry.py +309 -0
  570. synkit/Synthesis/Reactor/output/__init__.py +1 -0
  571. synkit/Synthesis/Reactor/output/deduplication.py +703 -0
  572. synkit/Synthesis/Reactor/output/policy.py +16 -0
  573. synkit/Synthesis/Reactor/output/serialization.py +485 -0
  574. synkit/Synthesis/Reactor/output/structural.py +893 -0
  575. synkit/Synthesis/Reactor/stereo/__init__.py +1 -0
  576. synkit/Synthesis/Reactor/stereo/assignment.py +35 -0
  577. synkit/Synthesis/Reactor/stereo/mixin.py +780 -0
  578. synkit/Synthesis/Reactor/variants/__init__.py +1 -0
  579. synkit/Synthesis/Reactor/variants/imbalanced.py +180 -0
  580. synkit/Synthesis/Reactor/variants/partial.py +84 -0
  581. synkit/Synthesis/Reactor/workflow/__init__.py +1 -0
  582. synkit/Synthesis/Reactor/workflow/batch.py +550 -0
  583. synkit/Synthesis/Reactor/workflow/benchmark.py +143 -0
  584. synkit/Synthesis/Reactor/workflow/postprocess.py +267 -0
  585. synkit/Synthesis/Reactor/workflow/rule_filter.py +210 -0
  586. synkit/Synthesis/__init__.py +0 -0
  587. synkit/Synthesis/reactor_utils.py +316 -0
  588. synkit/Utils/__init__.py +0 -0
  589. synkit/Utils/utils.py +172 -0
  590. synkit/Vis/__init__.py +91 -0
  591. synkit/Vis/crn/__init__.py +40 -0
  592. synkit/Vis/epd/__init__.py +20 -0
  593. synkit/Vis/epd/arrows.py +425 -0
  594. synkit/Vis/epd/chem.py +197 -0
  595. synkit/Vis/epd/constants.py +83 -0
  596. synkit/Vis/epd/layout.py +283 -0
  597. synkit/Vis/epd/mapping.py +51 -0
  598. synkit/Vis/epd/models.py +136 -0
  599. synkit/Vis/epd/render.py +543 -0
  600. synkit/Vis/epd/utils.py +108 -0
  601. synkit/Vis/epd/visualizer.py +811 -0
  602. synkit/Vis/graph_visualizer.py +375 -0
  603. synkit/Vis/its/__init__.py +19 -0
  604. synkit/Vis/its/drawer.py +615 -0
  605. synkit/Vis/its_drawer.py +35 -0
  606. synkit/Vis/molecule/__init__.py +31 -0
  607. synkit/Vis/molecule/drawer.py +563 -0
  608. synkit/Vis/molecule/reaction.py +285 -0
  609. synkit/Vis/molecule_drawer.py +33 -0
  610. synkit/Vis/mtg/__init__.py +17 -0
  611. synkit/Vis/mtg/drawer.py +769 -0
  612. synkit/Vis/mtg_drawer.py +34 -0
  613. synkit/Vis/pdf_writer.py +146 -0
  614. synkit/Vis/reaction/__init__.py +19 -0
  615. synkit/Vis/reaction/rule.py +141 -0
  616. synkit/Vis/reaction/rxn.py +532 -0
  617. synkit/Vis/reaction_drawer.py +41 -0
  618. synkit/Vis/space/__init__.py +17 -0
  619. synkit/Vis/space/chemical.py +80 -0
  620. synkit/Vis/space/embedding.py +87 -0
  621. synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
  622. synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
  623. synkit/Vis/vis_synedu/rxn_vis.py +377 -0
  624. synkit/Vis/vis_synedu/vis.py +501 -0
  625. synkit/Vis/visual_drawer.py +215 -0
  626. synkit/Vis/visual_model.py +520 -0
  627. synkit/__init__.py +3 -0
  628. synkit/_native.py +91 -0
  629. synkit/examples.py +50 -0
  630. synkit/version.py +23 -0
  631. synkit-1.6.5.dist-info/METADATA +156 -0
  632. synkit-1.6.5.dist-info/RECORD +636 -0
  633. synkit-1.6.5.dist-info/WHEEL +4 -0
  634. synkit-1.6.5.dist-info/licenses/LICENSE +21 -0
  635. synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
  636. synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
@@ -0,0 +1,817 @@
1
+ # standardize.py
2
+ """
3
+ Molecule standardization helpers and a chainable MolStandardizer class.
4
+
5
+ Provides:
6
+ - lightweight helpers: sanitize_and_canonicalize_smiles, fix_radical_rsmi, remove_isotopes, ...
7
+ - MolStandardizer: fluent, chainable standardization API with convenience constructors.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from typing import Optional, Tuple
14
+
15
+ from rdkit import Chem
16
+ from rdkit.Chem import rdmolops
17
+ from rdkit.Chem.SaltRemover import SaltRemover
18
+
19
+ # rdMolStandardize may not be available in all RDKit builds; import defensively.
20
+ try:
21
+ from rdkit.Chem.MolStandardize import rdMolStandardize # type: ignore
22
+ except Exception: # pragma: no cover - allow imports to fail gracefully
23
+ rdMolStandardize = None # type: ignore
24
+
25
+ # Prefer project's logger if available, otherwise fallback to stdlib logging
26
+ try:
27
+ # If running in the synkit environment this will pick up project logger
28
+ from synkit.IO.debug import setup_logging # type: ignore
29
+
30
+ logger = setup_logging()
31
+ except Exception:
32
+ logger = logging.getLogger(__name__)
33
+ if not logger.handlers:
34
+ logging.basicConfig(level=logging.WARNING)
35
+
36
+
37
+ # -------------------------
38
+ # Simple functional helpers
39
+ # -------------------------
40
+ def sanitize_and_canonicalize_smiles(smiles: str) -> Optional[str]:
41
+ """Sanitize and canonicalize a SMILES string.
42
+
43
+ :param smiles: Input SMILES string.
44
+ :type smiles: str
45
+ :return: Canonical SMILES if valid, otherwise ``None``.
46
+ :rtype: Optional[str]
47
+
48
+ .. rubric:: Notes
49
+
50
+ The function attempts to parse and sanitize the SMILES with RDKit. On any
51
+ parsing/sanitization failure it returns ``None`` (best-effort policy).
52
+ """
53
+ try:
54
+ mol = Chem.MolFromSmiles(smiles, sanitize=True)
55
+ if mol is None:
56
+ return None
57
+ Chem.SanitizeMol(mol)
58
+ return Chem.MolToSmiles(mol, canonical=True)
59
+ except Exception:
60
+ logger.debug(
61
+ "sanitize_and_canonicalize_smiles failed for %r", smiles, exc_info=True
62
+ )
63
+ return None
64
+
65
+
66
+ def normalize_molecule(mol: Chem.Mol) -> Chem.Mol:
67
+ """Normalize a molecule using rdMolStandardize.Normalizer when available.
68
+
69
+ :param mol: RDKit Mol object to normalize.
70
+ :type mol: Chem.Mol
71
+ :return: Normalized RDKit Mol object (or original if normalizer missing).
72
+ :rtype: Chem.Mol
73
+ """
74
+ if rdMolStandardize is None:
75
+ logger.debug("rdMolStandardize not available; normalize skipped.")
76
+ return mol
77
+ try:
78
+ normalizer_cls = getattr(rdMolStandardize, "Normalizer", None)
79
+ if normalizer_cls is None:
80
+ logger.debug("Normalizer not found in rdMolStandardize; normalize skipped.")
81
+ return mol
82
+ return normalizer_cls().normalize(mol)
83
+ except Exception as exc:
84
+ logger.debug("normalize_molecule failed: %s", exc, exc_info=True)
85
+ return mol
86
+
87
+
88
+ def canonicalize_tautomer(mol: Chem.Mol) -> Chem.Mol:
89
+ """Canonicalize tautomeric form using rdMolStandardize.TautomerEnumerator if available.
90
+
91
+ :param mol: RDKit Mol object to canonicalize.
92
+ :type mol: Chem.Mol
93
+ :return: Canonicalized tautomer Mol (or original if unavailable).
94
+ :rtype: Chem.Mol
95
+ """
96
+ if rdMolStandardize is None:
97
+ logger.debug("rdMolStandardize not available; canonicalize_tautomer skipped.")
98
+ return mol
99
+ try:
100
+ taut_enum_cls = getattr(rdMolStandardize, "TautomerEnumerator", None)
101
+ if taut_enum_cls is None:
102
+ logger.debug(
103
+ "TautomerEnumerator not present; canonicalize_tautomer skipped."
104
+ )
105
+ return mol
106
+ return taut_enum_cls().Canonicalize(mol)
107
+ except Exception as exc:
108
+ logger.debug("canonicalize_tautomer failed: %s", exc, exc_info=True)
109
+ return mol
110
+
111
+
112
+ def salts_remover(mol: Chem.Mol, remover: Optional[SaltRemover] = None) -> Chem.Mol:
113
+ """Remove salts from a molecule using RDKit's SaltRemover.
114
+
115
+ :param mol: RDKit Mol object to process.
116
+ :type mol: Chem.Mol
117
+ :param remover: Optional SaltRemover instance to use.
118
+ :type remover: Optional[SaltRemover]
119
+ :return: Mol object with salts removed (best-effort).
120
+ :rtype: Chem.Mol
121
+ """
122
+ try:
123
+ _rem = remover if remover is not None else SaltRemover()
124
+ return _rem.StripMol(mol)
125
+ except Exception as exc:
126
+ logger.debug("salts_remover failed: %s", exc, exc_info=True)
127
+ return mol
128
+
129
+
130
+ def uncharge_molecule(mol: Chem.Mol) -> Chem.Mol:
131
+ """Neutralize/uncharge a molecule using rdMolStandardize.Uncharger if available.
132
+
133
+ :param mol: RDKit Mol object to neutralize.
134
+ :type mol: Chem.Mol
135
+ :return: Neutralized Mol object (or original if uncharger missing).
136
+ :rtype: Chem.Mol
137
+ """
138
+ if rdMolStandardize is None:
139
+ logger.debug("rdMolStandardize not available; uncharge skipped.")
140
+ return mol
141
+ try:
142
+ uncharger_cls = getattr(rdMolStandardize, "Uncharger", None)
143
+ if uncharger_cls is None:
144
+ logger.debug("Uncharger not found; uncharge skipped.")
145
+ return mol
146
+ return uncharger_cls().uncharge(mol)
147
+ except Exception as exc:
148
+ logger.debug("uncharge_molecule failed: %s", exc, exc_info=True)
149
+ return mol
150
+
151
+
152
+ def fragments_remover(mol: Chem.Mol) -> Optional[Chem.Mol]:
153
+ """Keep only the largest fragment by atom count.
154
+
155
+ :param mol: RDKit Mol object to fragment.
156
+ :type mol: Chem.Mol
157
+ :return: Mol of the largest fragment, or None if input is empty.
158
+ :rtype: Optional[Chem.Mol]
159
+ """
160
+ try:
161
+ frags = Chem.GetMolFrags(mol, asMols=True, sanitizeFrags=True)
162
+ return max(frags, default=None, key=lambda m: m.GetNumAtoms())
163
+ except Exception:
164
+ logger.debug("fragments_remover failed.", exc_info=True)
165
+ return None
166
+
167
+
168
+ def remove_explicit_hydrogens(mol: Chem.Mol) -> Chem.Mol:
169
+ """Remove explicit hydrogens from the molecule (Chem.RemoveHs wrapper).
170
+
171
+ :param mol: RDKit Mol object to process.
172
+ :type mol: Chem.Mol
173
+ :return: Mol object without explicit hydrogens.
174
+ :rtype: Chem.Mol
175
+ """
176
+ try:
177
+ return Chem.RemoveHs(mol)
178
+ except Exception as exc:
179
+ logger.debug("remove_explicit_hydrogens failed: %s", exc, exc_info=True)
180
+ return mol
181
+
182
+
183
+ # -------------------------
184
+ # Radical-handling helpers (further split to reduce complexity)
185
+ # -------------------------
186
+ def _zero_out_radicals_on_atoms(mol: Chem.Mol) -> None:
187
+ """Iterate atoms and zero radical electron counts; also increment explicit H count
188
+ by the number of radical electrons for each atom (best-effort).
189
+
190
+ This mutates the input molecule in-place.
191
+
192
+ :param mol: RDKit Mol to operate on (mutated in place).
193
+ :type mol: Chem.Mol
194
+ :return: None
195
+ :rtype: None
196
+ """
197
+ for atom in mol.GetAtoms():
198
+ try:
199
+ rad = int(atom.GetNumRadicalElectrons())
200
+ except Exception:
201
+ rad = 0
202
+ if rad > 0:
203
+ try:
204
+ atom.SetNumExplicitHs(atom.GetNumExplicitHs() + rad)
205
+ atom.SetNumRadicalElectrons(0)
206
+ except Exception:
207
+ # ignore per-atom failures
208
+ continue
209
+
210
+
211
+ def _add_explicit_hydrogens(mol: Chem.Mol) -> Chem.Mol:
212
+ """Wrapper around rdmolops.AddHs with defensive fallback to return original mol
213
+ if AddHs fails.
214
+
215
+ :param mol: RDKit Mol to process.
216
+ :type mol: Chem.Mol
217
+ :return: Molecule with explicit hydrogens added, or original on failure.
218
+ :rtype: Chem.Mol
219
+ """
220
+ try:
221
+ return rdmolops.AddHs(mol)
222
+ except Exception:
223
+ return mol
224
+
225
+
226
+ def _maybe_remove_explicit_hydrogens(mol: Chem.Mol, removeH: bool) -> Chem.Mol:
227
+ """Remove explicit hydrogens if removeH is True, otherwise return the molecule unchanged.
228
+
229
+ :param mol: RDKit Mol to process.
230
+ :type mol: Chem.Mol
231
+ :param removeH: whether to remove explicit hydrogens after adding them.
232
+ :type removeH: bool
233
+ :return: Processed molecule.
234
+ :rtype: Chem.Mol
235
+ """
236
+ if not removeH:
237
+ return mol
238
+ try:
239
+ return Chem.RemoveHs(mol)
240
+ except Exception:
241
+ return mol
242
+
243
+
244
+ def _replace_radicals_with_hs_in_mol(
245
+ mol: Chem.Mol, removeH: bool = True
246
+ ) -> Optional[Chem.Mol]:
247
+ """High-level helper that replaces radicals with hydrogens.
248
+
249
+ Steps:
250
+ 1. zero out radicals (and add explicit H counters)
251
+ 2. call AddHs to create explicit H atoms
252
+ 3. optionally remove explicit H atoms again
253
+
254
+ :param mol: RDKit Mol to process.
255
+ :type mol: Chem.Mol
256
+ :param removeH: whether to remove explicit hydrogens after addition.
257
+ :type removeH: bool
258
+ :return: Processed RDKit Mol or None on extreme failure.
259
+ :rtype: Optional[Chem.Mol]
260
+ """
261
+ if mol is None:
262
+ return None
263
+ try:
264
+ _zero_out_radicals_on_atoms(mol)
265
+ mol_with_h = _add_explicit_hydrogens(mol)
266
+ return _maybe_remove_explicit_hydrogens(mol_with_h, removeH)
267
+ except Exception as exc:
268
+ logger.debug("_replace_radicals_with_hs_in_mol failed: %s", exc, exc_info=True)
269
+ return None
270
+
271
+
272
+ def remove_radicals_and_add_hydrogens(
273
+ mol: Chem.Mol, removeH: bool = True
274
+ ) -> Optional[Chem.Mol]:
275
+ """Replace radical electrons by adding hydrogens and optionally remove explicit H.
276
+
277
+ :param mol: RDKit Mol with possible radical atoms.
278
+ :type mol: Chem.Mol
279
+ :param removeH: If True, remove explicit hydrogens after addition.
280
+ :type removeH: bool
281
+ :return: Mol with radicals neutralized (or None on failure).
282
+ :rtype: Optional[Chem.Mol]
283
+ """
284
+ return _replace_radicals_with_hs_in_mol(mol, removeH)
285
+
286
+
287
+ # -------------------------
288
+ # Reaction SMILES helpers
289
+ # -------------------------
290
+ def _parse_reaction_smiles(rsmi: str) -> Optional[Tuple[str, str]]:
291
+ """Parse a reaction SMILES of the form 'reactant>>product' and return tuple,
292
+ or None if not parseable.
293
+
294
+ :param rsmi: reaction SMILES string.
295
+ :type rsmi: str
296
+ :return: tuple (reactant_smiles, product_smiles) or None.
297
+ :rtype: Optional[Tuple[str, str]]
298
+ """
299
+ if ">>" not in rsmi:
300
+ return None
301
+ parts = rsmi.split(">>", 1)
302
+ if len(parts) != 2:
303
+ return None
304
+ return parts[0], parts[1]
305
+
306
+
307
+ def _fix_single_side_of_reaction(smiles: str, removeH: bool) -> Optional[Chem.Mol]:
308
+ """Create a Mol from SMILES (without sanitization), sanitize best-effort,
309
+ then replace radicals and return processed Mol.
310
+
311
+ :param smiles: SMILES for one side of a reaction.
312
+ :type smiles: str
313
+ :param removeH: whether to remove explicit hydrogens after addition.
314
+ :type removeH: bool
315
+ :return: Processed RDKit Mol or None.
316
+ :rtype: Optional[Chem.Mol]
317
+ """
318
+ m = Chem.MolFromSmiles(smiles, sanitize=False)
319
+ if m is None:
320
+ return None
321
+ try:
322
+ Chem.SanitizeMol(m)
323
+ except Exception:
324
+ pass
325
+ return _replace_radicals_with_hs_in_mol(m, removeH)
326
+
327
+
328
+ def fix_radical_rsmi(rsmi: str, removeH: bool = True) -> str:
329
+ """Fix radicals in a reaction SMILES by converting them to hydrogens.
330
+
331
+ :param rsmi: Reaction SMILES string (format 'reactant>>product').
332
+ :type rsmi: str
333
+ :param removeH: If True, remove explicit hydrogens after addition.
334
+ :type removeH: bool
335
+ :return: Corrected reaction SMILES with radicals replaced (or original on failure).
336
+ :rtype: str
337
+ """
338
+ try:
339
+ parsed = _parse_reaction_smiles(rsmi)
340
+ if parsed is None:
341
+ return rsmi
342
+ react_smiles, prod_smiles = parsed
343
+ r_fixed = _fix_single_side_of_reaction(react_smiles, removeH)
344
+ p_fixed = _fix_single_side_of_reaction(prod_smiles, removeH)
345
+ r_out = Chem.MolToSmiles(r_fixed) if r_fixed is not None else react_smiles
346
+ p_out = Chem.MolToSmiles(p_fixed) if p_fixed is not None else prod_smiles
347
+ return f"{r_out}>>{p_out}"
348
+ except Exception:
349
+ logger.debug("fix_radical_rsmi failed for %r", rsmi, exc_info=True)
350
+ return rsmi
351
+
352
+
353
+ def remove_isotopes(mol: Chem.Mol) -> Chem.Mol:
354
+ """Clear isotope labels on every atom in the molecule.
355
+
356
+ :param mol: RDKit Mol object to process.
357
+ :type mol: Chem.Mol
358
+ :return: The same RDKit Mol instance with isotopic labels cleared.
359
+ :rtype: Chem.Mol
360
+ """
361
+ try:
362
+ for atom in mol.GetAtoms():
363
+ try:
364
+ atom.SetIsotope(0)
365
+ except Exception:
366
+ continue
367
+ except Exception as exc:
368
+ logger.debug("remove_isotopes encountered error: %s", exc, exc_info=True)
369
+ return mol
370
+
371
+
372
+ def clear_stereochemistry(mol: Chem.Mol) -> Chem.Mol:
373
+ """Remove stereochemical annotations from a molecule.
374
+
375
+ :param mol: RDKit Mol object to process.
376
+ :type mol: Chem.Mol
377
+ :return: Mol object with stereochemistry removed.
378
+ :rtype: Chem.Mol
379
+ """
380
+ try:
381
+ Chem.RemoveStereochemistry(mol)
382
+ except Exception as exc:
383
+ logger.debug("clear_stereochemistry failed: %s", exc, exc_info=True)
384
+ return mol
385
+
386
+
387
+ # -------------------------
388
+ # MolStandardizer class
389
+ # -------------------------
390
+ class MolStandardizer:
391
+ """Chainable molecule standardizer wrapper around RDKit utilities.
392
+
393
+ Use the fluent API to apply a sequence of standardizations and then
394
+ retrieve the resulting molecule via the ``.mol`` property or ``.to_smiles()``.
395
+
396
+ .. rubric:: Example
397
+
398
+ >>> std = MolStandardizer.from_smiles("CC(=O)[O-]").remove_salts().uncharge().mol
399
+ """
400
+
401
+ def __init__(self, mol: Chem.Mol, sanitize: bool = True) -> None:
402
+ """
403
+ Create a MolStandardizer working on a defensive copy of ``mol``.
404
+
405
+ :param mol: RDKit Mol to operate on (a copy is created).
406
+ :type mol: Chem.Mol
407
+ :param sanitize: attempt initial sanitization (default True).
408
+ :type sanitize: bool
409
+ """
410
+ self._mol: Optional[Chem.Mol] = Chem.Mol(mol) if mol is not None else None
411
+ self._last_error: Optional[Exception] = None
412
+
413
+ if sanitize and self._mol is not None:
414
+ try:
415
+ Chem.SanitizeMol(self._mol)
416
+ except Exception as exc:
417
+ logger.debug("Initial SanitizeMol failed: %s", exc, exc_info=True)
418
+ self._last_error = exc
419
+
420
+ # ----- constructors / convenience -----
421
+ @classmethod
422
+ def from_smiles(cls, smiles: str, sanitize: bool = True) -> "MolStandardizer":
423
+ """Parse SMILES and return a configured standardizer.
424
+
425
+ :param smiles: SMILES string to parse.
426
+ :type smiles: str
427
+ :param sanitize: attempt sanitization on parse (default True).
428
+ :type sanitize: bool
429
+ :return: MolStandardizer.
430
+ :rtype: MolStandardizer
431
+ :raises ValueError: if SMILES fails to parse.
432
+ """
433
+ m = Chem.MolFromSmiles(smiles)
434
+ if m is None:
435
+ raise ValueError(f"Failed to parse SMILES: {smiles!r}")
436
+ return cls(m, sanitize=sanitize)
437
+
438
+ @classmethod
439
+ def _maybe_normalize(cls, inst: "MolStandardizer") -> "MolStandardizer":
440
+ """Normalize if rdMolStandardize available (small wrapper).
441
+
442
+ :param inst: MolStandardizer instance to operate on.
443
+ :type inst: MolStandardizer
444
+ :return: same instance (chainable).
445
+ :rtype: MolStandardizer
446
+ """
447
+ if rdMolStandardize is None:
448
+ return inst
449
+ try:
450
+ inst.normalize()
451
+ except Exception:
452
+ pass
453
+ return inst
454
+
455
+ @classmethod
456
+ def _maybe_keep_largest_fragment(
457
+ cls, inst: "MolStandardizer", keep: bool
458
+ ) -> "MolStandardizer":
459
+ """Keep largest fragment if requested (small wrapper).
460
+
461
+ :param inst: MolStandardizer instance to operate on.
462
+ :type inst: MolStandardizer
463
+ :param keep: whether to keep the largest fragment.
464
+ :type keep: bool
465
+ :return: same instance (chainable).
466
+ :rtype: MolStandardizer
467
+ """
468
+ if not keep:
469
+ return inst
470
+ try:
471
+ inst.keep_largest_fragment()
472
+ except Exception:
473
+ pass
474
+ return inst
475
+
476
+ @classmethod
477
+ def _maybe_remove_salts(cls, inst: "MolStandardizer") -> "MolStandardizer":
478
+ """Remove salts (small wrapper).
479
+
480
+ :param inst: MolStandardizer instance to operate on.
481
+ :type inst: MolStandardizer
482
+ :return: same instance (chainable).
483
+ :rtype: MolStandardizer
484
+ """
485
+ try:
486
+ inst.remove_salts()
487
+ except Exception:
488
+ pass
489
+ return inst
490
+
491
+ @classmethod
492
+ def _maybe_uncharge(cls, inst: "MolStandardizer") -> "MolStandardizer":
493
+ """Uncharge if rdMolStandardize available (small wrapper).
494
+
495
+ :param inst: MolStandardizer instance to operate on.
496
+ :type inst: MolStandardizer
497
+ :return: same instance (chainable).
498
+ :rtype: MolStandardizer
499
+ """
500
+ if rdMolStandardize is None:
501
+ return inst
502
+ try:
503
+ inst.uncharge()
504
+ except Exception:
505
+ pass
506
+ return inst
507
+
508
+ @classmethod
509
+ def _maybe_canonicalize_tautomer(cls, inst: "MolStandardizer") -> "MolStandardizer":
510
+ """Canonicalize tautomer if rdMolStandardize available (small wrapper).
511
+
512
+ :param inst: MolStandardizer instance to operate on.
513
+ :type inst: MolStandardizer
514
+ :return: same instance (chainable).
515
+ :rtype: MolStandardizer
516
+ """
517
+ if rdMolStandardize is None:
518
+ return inst
519
+ try:
520
+ inst.canonicalize_tautomer()
521
+ except Exception:
522
+ pass
523
+ return inst
524
+
525
+ @classmethod
526
+ def _apply_default_pipeline(
527
+ cls, inst: "MolStandardizer", keep_largest_fragment: bool
528
+ ) -> "MolStandardizer":
529
+ """Apply the default standardization pipeline to an instance.
530
+
531
+ :param inst: MolStandardizer instance to operate on.
532
+ :type inst: MolStandardizer
533
+ :param keep_largest_fragment: whether to keep the largest fragment.
534
+ :type keep_largest_fragment: bool
535
+ :return: same instance after applying pipeline.
536
+ :rtype: MolStandardizer
537
+ """
538
+ cls._maybe_normalize(inst)
539
+ cls._maybe_keep_largest_fragment(inst, keep_largest_fragment)
540
+ cls._maybe_remove_salts(inst)
541
+ cls._maybe_uncharge(inst)
542
+ cls._maybe_canonicalize_tautomer(inst)
543
+ return inst
544
+
545
+ @classmethod
546
+ def standardize_smiles(
547
+ cls, smiles: str, *, keep_largest_fragment: bool = True
548
+ ) -> Optional[str]:
549
+ """Quick convenience: parse SMILES, apply a sensible default standardization,
550
+ and return canonical SMILES or None on failure.
551
+
552
+ Default pipeline:
553
+ sanitize -> normalize (if available) -> keep largest fragment ->
554
+ remove salts -> uncharge -> canonicalize tautomer (if available)
555
+
556
+ :param smiles: Input SMILES string.
557
+ :type smiles: str
558
+ :param keep_largest_fragment: keep only the largest fragment (default True).
559
+ :type keep_largest_fragment: bool
560
+ :return: Canonical SMILES or None.
561
+ :rtype: Optional[str]
562
+ """
563
+ try:
564
+ inst = cls.from_smiles(smiles, sanitize=True)
565
+ cls._apply_default_pipeline(
566
+ inst, keep_largest_fragment=keep_largest_fragment
567
+ )
568
+ return inst.to_smiles(canonical=True)
569
+ except Exception:
570
+ logger.debug("standardize_smiles failed for %r", smiles, exc_info=True)
571
+ return None
572
+
573
+ # ----- mutating chainable operations -----
574
+ def normalize(self) -> "MolStandardizer":
575
+ """Normalize the internal molecule using rdMolStandardize.Normalizer.
576
+
577
+ :return: self (chainable).
578
+ :rtype: MolStandardizer
579
+ """
580
+ if self._mol is None:
581
+ return self
582
+ if rdMolStandardize is None:
583
+ logger.debug("rdMolStandardize not available; normalize skipped.")
584
+ return self
585
+ try:
586
+ normalizer_cls = getattr(rdMolStandardize, "Normalizer", None)
587
+ if normalizer_cls is None:
588
+ logger.debug("Normalizer not found in rdMolStandardize.")
589
+ return self
590
+ self._mol = normalizer_cls().normalize(self._mol)
591
+ except Exception as exc:
592
+ logger.debug("normalize failed: %s", exc, exc_info=True)
593
+ self._last_error = exc
594
+ return self
595
+
596
+ def canonicalize_tautomer(self) -> "MolStandardizer":
597
+ """Canonicalize tautomer using rdMolStandardize.TautomerEnumerator.
598
+
599
+ :return: self (chainable).
600
+ :rtype: MolStandardizer
601
+ """
602
+ if self._mol is None:
603
+ return self
604
+ if rdMolStandardize is None:
605
+ logger.debug(
606
+ "rdMolStandardize not available; canonicalize_tautomer skipped."
607
+ )
608
+ return self
609
+ try:
610
+ taut_enum_cls = getattr(rdMolStandardize, "TautomerEnumerator", None)
611
+ if taut_enum_cls is None:
612
+ logger.debug("TautomerEnumerator not found in rdMolStandardize.")
613
+ return self
614
+ self._mol = taut_enum_cls().Canonicalize(self._mol)
615
+ except Exception as exc:
616
+ logger.debug("canonicalize_tautomer failed: %s", exc, exc_info=True)
617
+ self._last_error = exc
618
+ return self
619
+
620
+ def remove_salts(
621
+ self, salt_remover: Optional[SaltRemover] = None
622
+ ) -> "MolStandardizer":
623
+ """Remove salts using RDKit's SaltRemover.
624
+
625
+ :param salt_remover: Optional SaltRemover instance to use; if None a new one is created.
626
+ :type salt_remover: Optional[SaltRemover]
627
+ :return: self (chainable).
628
+ :rtype: MolStandardizer
629
+ """
630
+ if self._mol is None:
631
+ return self
632
+ try:
633
+ remover = salt_remover if salt_remover is not None else SaltRemover()
634
+ self._mol = remover.StripMol(self._mol)
635
+ except Exception as exc:
636
+ logger.debug("remove_salts failed: %s", exc, exc_info=True)
637
+ self._last_error = exc
638
+ return self
639
+
640
+ def uncharge(self) -> "MolStandardizer":
641
+ """Neutralize charges using rdMolStandardize.Uncharger.
642
+
643
+ :return: self (chainable).
644
+ :rtype: MolStandardizer
645
+ """
646
+ if self._mol is None:
647
+ return self
648
+ if rdMolStandardize is None:
649
+ logger.debug("rdMolStandardize not available; uncharge skipped.")
650
+ return self
651
+ try:
652
+ uncharger_cls = getattr(rdMolStandardize, "Uncharger", None)
653
+ if uncharger_cls is None:
654
+ logger.debug("Uncharger not found in rdMolStandardize.")
655
+ return self
656
+ self._mol = uncharger_cls().uncharge(self._mol)
657
+ except Exception as exc:
658
+ logger.debug("uncharge failed: %s", exc, exc_info=True)
659
+ self._last_error = exc
660
+ return self
661
+
662
+ def keep_largest_fragment(self) -> "MolStandardizer":
663
+ """Keep only the largest fragment by atom count.
664
+
665
+ :return: self (chainable).
666
+ :rtype: MolStandardizer
667
+ """
668
+ if self._mol is None:
669
+ return self
670
+ try:
671
+ frags = Chem.GetMolFrags(self._mol, asMols=True, sanitizeFrags=True)
672
+ if frags:
673
+ self._mol = max(frags, key=lambda m: m.GetNumAtoms())
674
+ except Exception as exc:
675
+ logger.debug("keep_largest_fragment failed: %s", exc, exc_info=True)
676
+ self._last_error = exc
677
+ return self
678
+
679
+ def remove_explicit_hs(self) -> "MolStandardizer":
680
+ """Remove explicit hydrogens (Chem.RemoveHs).
681
+
682
+ :return: self (chainable).
683
+ :rtype: MolStandardizer
684
+ """
685
+ if self._mol is None:
686
+ return self
687
+ try:
688
+ self._mol = Chem.RemoveHs(self._mol)
689
+ except Exception as exc:
690
+ logger.debug("remove_explicit_hs failed: %s", exc, exc_info=True)
691
+ self._last_error = exc
692
+ return self
693
+
694
+ def add_hs_and_clear_radicals(self, removeH: bool = True) -> "MolStandardizer":
695
+ """Replace radical electrons with explicit hydrogens and optionally remove them.
696
+
697
+ :param removeH: if True remove explicit hydrogens after addition.
698
+ :type removeH: bool
699
+ :return: self (chainable).
700
+ :rtype: MolStandardizer
701
+ """
702
+ if self._mol is None:
703
+ return self
704
+ try:
705
+ processed = _replace_radicals_with_hs_in_mol(self._mol, removeH=removeH)
706
+ if processed is not None:
707
+ self._mol = processed
708
+ except Exception as exc:
709
+ logger.debug("add_hs_and_clear_radicals failed: %s", exc, exc_info=True)
710
+ self._last_error = exc
711
+ return self
712
+
713
+ def remove_isotopes(self) -> "MolStandardizer":
714
+ """Clear isotope labels on all atoms.
715
+
716
+ :return: self (chainable).
717
+ :rtype: MolStandardizer
718
+ """
719
+ if self._mol is None:
720
+ return self
721
+ try:
722
+ for atom in self._mol.GetAtoms():
723
+ try:
724
+ atom.SetIsotope(0)
725
+ except Exception:
726
+ pass
727
+ except Exception as exc:
728
+ logger.debug("remove_isotopes failed: %s", exc, exc_info=True)
729
+ self._last_error = exc
730
+ return self
731
+
732
+ def clear_stereochemistry(self) -> "MolStandardizer":
733
+ """Remove stereochemical annotations (Chem.RemoveStereochemistry).
734
+
735
+ :return: self (chainable).
736
+ :rtype: MolStandardizer
737
+ """
738
+ if self._mol is None:
739
+ return self
740
+ try:
741
+ Chem.RemoveStereochemistry(self._mol)
742
+ except Exception as exc:
743
+ logger.debug("clear_stereochemistry failed: %s", exc, exc_info=True)
744
+ self._last_error = exc
745
+ return self
746
+
747
+ # ----- retrieval / helpers -----
748
+ @property
749
+ def mol(self) -> Optional[Chem.Mol]:
750
+ """Return the internal RDKit Mol (or None if absent).
751
+
752
+ :return: the internal RDKit Mol or None.
753
+ :rtype: Optional[Chem.Mol]
754
+ """
755
+ return self._mol
756
+
757
+ def to_smiles(self, canonical: bool = True) -> Optional[str]:
758
+ """Return a SMILES string for the internal molecule.
759
+
760
+ :param canonical: whether to return a canonical SMILES (default True).
761
+ :type canonical: bool
762
+ :return: SMILES string or None.
763
+ :rtype: Optional[str]
764
+ """
765
+ if self._mol is None:
766
+ return None
767
+ try:
768
+ return Chem.MolToSmiles(self._mol, canonical=canonical)
769
+ except Exception:
770
+ try:
771
+ Chem.SanitizeMol(self._mol)
772
+ return Chem.MolToSmiles(self._mol, canonical=canonical)
773
+ except Exception:
774
+ logger.debug("to_smiles serialization failed.", exc_info=True)
775
+ return None
776
+
777
+ def summarize_last_error(self) -> Optional[str]:
778
+ """Return a short string describing the last internal exception, if any.
779
+
780
+ :return: descriptive string for last error or None.
781
+ :rtype: Optional[str]
782
+ """
783
+ if self._last_error is None:
784
+ return None
785
+ return f"{type(self._last_error).__name__}: {str(self._last_error)}"
786
+
787
+ def __repr__(self) -> str:
788
+ """Debug representation showing the number of atoms in the internal Mol.
789
+
790
+ :return: repr string.
791
+ :rtype: str
792
+ """
793
+ n = -1
794
+ try:
795
+ n = self._mol.GetNumAtoms() if self._mol is not None else -1
796
+ except Exception:
797
+ n = -1
798
+ return f"{self.__class__.__name__}(n_atoms={n})"
799
+
800
+ @classmethod
801
+ def help(cls) -> str:
802
+ """Short machine-readable help describing capabilities.
803
+
804
+ :return: help string.
805
+ :rtype: str
806
+ """
807
+ return (
808
+ "MolStandardizer.help() -> str\n\n"
809
+ "Fluent methods:\n"
810
+ " .normalize(), .canonicalize_tautomer(), .remove_salts(), .uncharge(),\n"
811
+ " .keep_largest_fragment(), .remove_explicit_hs(), .add_hs_and_clear_radicals(),\n"
812
+ " ' .remove_isotopes(), .clear_stereochemistry()\n\n"
813
+ "Constructors:\n"
814
+ " MolStandardizer.from_smiles(smiles), MolStandardizer(mol)\n\n"
815
+ "Convenience:\n"
816
+ " MolStandardizer.standardize_smiles(smiles) -> Optional[canonical_smiles]\n"
817
+ )