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,1251 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass
4
+
5
+ import networkx as nx
6
+ from rdkit import Chem
7
+
8
+ from synkit.IO.chem_converter import rsmi_to_graph, graph_to_rsmi, smiles_to_graph
9
+ from synkit.Graph.ITS.its_decompose import get_rc, its_decompose
10
+ from synkit.Graph.ITS.its_construction import ITSConstruction
11
+ from synkit.Graph.ITS.its_builder import ITSBuilder
12
+ from synkit.Chem.Reaction.standardize import Standardize
13
+ from synkit.Graph.ITS.its_relabel import ITSRelabel
14
+
15
+ std = Standardize()
16
+
17
+ CONSTITUTIONAL_RC_NODE_ATTRS = (
18
+ "element",
19
+ "aromatic",
20
+ "hcount",
21
+ "charge",
22
+ "neighbors",
23
+ "atom_map",
24
+ )
25
+ CONSTITUTIONAL_SIDE_NODE_ATTRS = (
26
+ *CONSTITUTIONAL_RC_NODE_ATTRS,
27
+ "radical",
28
+ )
29
+ CONSTITUTIONAL_RC_EDGE_ATTRS = ("order",)
30
+ CONSTITUTIONAL_SIDE_EDGE_ATTRS = ("order",)
31
+
32
+
33
+ class ITSExpansionError(ValueError):
34
+ """Raised when partial-AAM ITS reconstruction cannot produce a safe RSMI."""
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ITSExpansionResult:
39
+ """Structured evidence for one successful partial-AAM expansion."""
40
+
41
+ rsmi: str
42
+ preferred_side: str
43
+ selected_side: str
44
+ fallback_used: bool
45
+ constitution_checked: bool
46
+ constitution_guard_passed: bool | None
47
+ unmapped_explicit_hydrogens_folded: bool = False
48
+ folded_unmapped_explicit_hydrogen_count: int = 0
49
+ stereochemistry_ignored_for_expansion: bool = False
50
+ explicit_hydrogen_serialization: bool = False
51
+ radical_state_preserved: bool = False
52
+ fallback_reason: str | None = None
53
+
54
+ def to_dict(self) -> dict:
55
+ """Return JSON-serializable expansion evidence."""
56
+ return asdict(self)
57
+
58
+
59
+ class ITSExpand:
60
+ """Partially expand a reaction SMILES (RSMI) by reconstructing intermediate
61
+ transition states (ITS) and applying transformation rules based on the
62
+ reaction center graph.
63
+
64
+ This class identifies the reaction center from an RSMI, builds and
65
+ reconstructs the ITS graph, decomposes it back into reactants and products,
66
+ and standardizes atom mappings to produce a fully mapped AAM RSMI.
67
+
68
+ The optional ``preserve_older_map`` mode keeps existing atom-map numbers
69
+ from the input RSMI by reindexing the side graph before ITS reconstruction.
70
+
71
+ .. rubric:: Notes
72
+
73
+ ``preserve_older_map=True`` is intended for the ITS expansion path only.
74
+ It should not be combined with ``relabel=True``, because ``ITSRelabel``
75
+ globally renumbers atom maps.
76
+
77
+ :cvar std: Standardize instance for reaction SMILES standardization.
78
+ :type std: Standardize
79
+ """
80
+
81
+ def __init__(self) -> None:
82
+ """Initialize ITSExpand.
83
+
84
+ No instance-specific attributes are required.
85
+ """
86
+ pass
87
+
88
+ @staticmethod
89
+ def _split_rsmi(rsmi: str) -> tuple[str, str]:
90
+ """Split a reaction SMILES into reactant and product sides.
91
+
92
+ :param rsmi: Reaction SMILES string in ``reactant>>product`` format.
93
+ :type rsmi: str
94
+ :return: Reactant-side SMILES and product-side SMILES.
95
+ :rtype: tuple[str, str]
96
+ :raises ValueError: If the input is not a valid two-sided RSMI.
97
+ """
98
+ try:
99
+ react_smi, prod_smi = rsmi.split(">>")
100
+ except ValueError as e:
101
+ raise ValueError("Input RSMI must be 'reactant>>product'") from e
102
+
103
+ if not react_smi or not prod_smi:
104
+ raise ValueError("Input RSMI must contain both reactant and product sides")
105
+
106
+ return react_smi, prod_smi
107
+
108
+ @staticmethod
109
+ def _atom_map(data: dict) -> int:
110
+ """Safely extract an atom-map number from node attributes.
111
+
112
+ :param data: Node attribute dictionary.
113
+ :type data: dict
114
+ :return: Atom-map number. Returns ``0`` if absent or falsy.
115
+ :rtype: int
116
+ """
117
+ return int(data.get("atom_map", 0) or 0)
118
+
119
+ @staticmethod
120
+ def _nonzero_atom_maps(graph) -> list[int]:
121
+ """Collect all nonzero atom-map numbers from a graph.
122
+
123
+ :param graph: Molecular graph.
124
+ :type graph: networkx.Graph
125
+ :return: List of nonzero atom-map numbers.
126
+ :rtype: list[int]
127
+ """
128
+ return [
129
+ ITSExpand._atom_map(data)
130
+ for _, data in graph.nodes(data=True)
131
+ if ITSExpand._atom_map(data) != 0
132
+ ]
133
+
134
+ @staticmethod
135
+ def _validate_unique_atom_maps(atom_maps: list[int]) -> None:
136
+ """Validate that all nonzero atom-map numbers are unique.
137
+
138
+ :param atom_maps: Nonzero atom-map numbers.
139
+ :type atom_maps: list[int]
140
+ :raises ValueError: If duplicate nonzero atom-map numbers are found.
141
+ """
142
+ if len(atom_maps) != len(set(atom_maps)):
143
+ raise ValueError(
144
+ "Duplicate nonzero atom_map values found in side graph. "
145
+ "Cannot safely reindex graph by atom_map."
146
+ )
147
+
148
+ @staticmethod
149
+ def _validate_atom_maps_within_range(
150
+ atom_maps: list[int],
151
+ n_nodes: int,
152
+ ) -> None:
153
+ """Validate that atom-map numbers can be used as contiguous node IDs.
154
+
155
+ In the side graph, we want final node IDs to remain exactly ``1..N``.
156
+ Therefore, a mapped atom can only be moved to its atom-map number if
157
+ that number is within ``1..N``.
158
+
159
+ :param atom_maps: Nonzero atom-map numbers.
160
+ :type atom_maps: list[int]
161
+ :param n_nodes: Number of nodes in the side graph.
162
+ :type n_nodes: int
163
+ :raises ValueError: If any atom-map number is outside ``1..N``.
164
+ """
165
+ bad_targets = [
166
+ atom_map for atom_map in atom_maps if atom_map < 1 or atom_map > n_nodes
167
+ ]
168
+
169
+ if bad_targets:
170
+ raise ValueError(
171
+ "Cannot keep side graph node ids contiguous from 1..N while "
172
+ f"also using atom_map as node id. The following atom maps are "
173
+ f"outside 1..{n_nodes}: {bad_targets}"
174
+ )
175
+
176
+ @staticmethod
177
+ def _validate_positive_atom_maps(atom_maps: list[int]) -> None:
178
+ """Validate that atom-map numbers are valid positive node IDs."""
179
+ bad_targets = [atom_map for atom_map in atom_maps if atom_map < 1]
180
+
181
+ if bad_targets:
182
+ raise ValueError(
183
+ "Cannot use non-positive atom_map values as node ids. "
184
+ f"Invalid atom maps: {bad_targets}"
185
+ )
186
+
187
+ @staticmethod
188
+ def _assign_mapped_nodes(graph) -> tuple[dict, set[int]]:
189
+ """Assign mapped atoms to node IDs equal to their atom-map numbers.
190
+
191
+ For example, if a node has ``atom_map=20``, the returned mapping assigns
192
+ that old node to new node ID ``20``.
193
+
194
+ :param graph: Molecular side graph.
195
+ :type graph: networkx.Graph
196
+ :return: A partial old-node to new-node mapping and the used node IDs.
197
+ :rtype: tuple[dict, set[int]]
198
+ """
199
+ mapping = {}
200
+ used_ids = set()
201
+
202
+ for node, data in graph.nodes(data=True):
203
+ atom_map = ITSExpand._atom_map(data)
204
+
205
+ if atom_map == 0:
206
+ continue
207
+
208
+ mapping[node] = atom_map
209
+ used_ids.add(atom_map)
210
+
211
+ return mapping, used_ids
212
+
213
+ @staticmethod
214
+ def _assign_unmapped_nodes(
215
+ graph,
216
+ mapping: dict,
217
+ used_ids: set[int],
218
+ ) -> dict:
219
+ """Assign unmapped atoms while preserving contiguous node IDs.
220
+
221
+ Unmapped atoms keep their original node ID when possible. If an
222
+ unmapped atom's node ID conflicts with a mapped atom's target ID, it is
223
+ moved into one of the remaining free IDs inside ``1..N``.
224
+
225
+ :param graph: Molecular side graph.
226
+ :type graph: networkx.Graph
227
+ :param mapping: Existing mapping from mapped atoms.
228
+ :type mapping: dict
229
+ :param used_ids: Node IDs already occupied by mapped atoms.
230
+ :type used_ids: set[int]
231
+ :return: Complete old-node to new-node mapping.
232
+ :rtype: dict
233
+ """
234
+ n_nodes = graph.number_of_nodes()
235
+ free_ids = set(range(1, n_nodes + 1)) - used_ids
236
+ pending_unmapped = []
237
+
238
+ for node, data in graph.nodes(data=True):
239
+ atom_map = ITSExpand._atom_map(data)
240
+
241
+ if atom_map != 0:
242
+ continue
243
+
244
+ if isinstance(node, int) and node in free_ids:
245
+ mapping[node] = node
246
+ free_ids.remove(node)
247
+ else:
248
+ pending_unmapped.append(node)
249
+
250
+ for old_node, new_node in zip(pending_unmapped, sorted(free_ids)):
251
+ mapping[old_node] = new_node
252
+
253
+ return mapping
254
+
255
+ @staticmethod
256
+ def _assign_unmapped_nodes_sparse(
257
+ graph,
258
+ mapping: dict,
259
+ used_ids: set[int],
260
+ ) -> dict:
261
+ """Assign unmapped atoms without changing preserved sparse map IDs."""
262
+ next_candidate = 1
263
+
264
+ for node, data in graph.nodes(data=True):
265
+ if ITSExpand._atom_map(data) != 0:
266
+ continue
267
+
268
+ if isinstance(node, int) and node > 0 and node not in used_ids:
269
+ mapping[node] = node
270
+ used_ids.add(node)
271
+ next_candidate = max(next_candidate, node + 1)
272
+ continue
273
+
274
+ while next_candidate in used_ids:
275
+ next_candidate += 1
276
+ mapping[node] = next_candidate
277
+ used_ids.add(next_candidate)
278
+ next_candidate += 1
279
+
280
+ return mapping
281
+
282
+ @staticmethod
283
+ def _validate_contiguous_mapping(mapping: dict, n_nodes: int) -> None:
284
+ """Validate that a mapping produces exactly node IDs ``1..N``.
285
+
286
+ :param mapping: Old-node to new-node mapping.
287
+ :type mapping: dict
288
+ :param n_nodes: Number of nodes in the graph.
289
+ :type n_nodes: int
290
+ :raises RuntimeError: If the mapped node IDs are not exactly ``1..N``.
291
+ """
292
+ expected_ids = set(range(1, n_nodes + 1))
293
+ actual_ids = set(mapping.values())
294
+
295
+ if actual_ids != expected_ids:
296
+ missing = sorted(expected_ids - actual_ids)
297
+ extra = sorted(actual_ids - expected_ids)
298
+ raise RuntimeError(
299
+ f"Reindexing failed. Missing node ids: {missing}; "
300
+ f"extra node ids: {extra}"
301
+ )
302
+
303
+ @staticmethod
304
+ def _validate_complete_unique_mapping(mapping: dict, n_nodes: int) -> None:
305
+ """Validate that every source node maps to one unique target node."""
306
+ if len(mapping) != n_nodes:
307
+ raise RuntimeError(
308
+ f"Reindexing failed. Expected {n_nodes} mapped nodes; "
309
+ f"got {len(mapping)}."
310
+ )
311
+
312
+ if len(set(mapping.values())) != n_nodes:
313
+ raise RuntimeError(
314
+ "Reindexing failed. Multiple source nodes map to the same "
315
+ "target node id."
316
+ )
317
+
318
+ @staticmethod
319
+ def _build_side_graph_reindex_mapping(
320
+ graph,
321
+ contiguous: bool = True,
322
+ ) -> dict:
323
+ """Build an old-node to new-node mapping for a side graph.
324
+
325
+ The mapping satisfies two conditions:
326
+
327
+ 1. Every atom with ``atom_map != 0`` is assigned to node ID
328
+ ``atom_map``.
329
+ 2. The final node IDs are exactly contiguous from ``1..N`` when
330
+ ``contiguous=True``. With ``contiguous=False``, existing positive,
331
+ sparse atom-map IDs are retained and unmapped atoms receive unused
332
+ positive IDs.
333
+
334
+ :param graph: Molecular side graph.
335
+ :type graph: networkx.Graph
336
+ :return: Old-node to new-node mapping.
337
+ :rtype: dict
338
+ :raises ValueError: If atom-map values are duplicated or incompatible
339
+ with contiguous node IDs.
340
+ """
341
+ n_nodes = graph.number_of_nodes()
342
+ atom_maps = ITSExpand._nonzero_atom_maps(graph)
343
+
344
+ ITSExpand._validate_unique_atom_maps(atom_maps)
345
+ if contiguous:
346
+ ITSExpand._validate_atom_maps_within_range(atom_maps, n_nodes)
347
+ else:
348
+ ITSExpand._validate_positive_atom_maps(atom_maps)
349
+
350
+ mapping, used_ids = ITSExpand._assign_mapped_nodes(graph)
351
+ if contiguous:
352
+ mapping = ITSExpand._assign_unmapped_nodes(graph, mapping, used_ids)
353
+ ITSExpand._validate_contiguous_mapping(mapping, n_nodes)
354
+ else:
355
+ mapping = ITSExpand._assign_unmapped_nodes_sparse(
356
+ graph,
357
+ mapping,
358
+ used_ids,
359
+ )
360
+ ITSExpand._validate_complete_unique_mapping(mapping, n_nodes)
361
+
362
+ return mapping
363
+
364
+ @staticmethod
365
+ def _copy_nodes_with_mapping(graph, new_graph, mapping: dict) -> None:
366
+ """Copy graph nodes into a new graph using a node mapping.
367
+
368
+ :param graph: Source graph.
369
+ :type graph: networkx.Graph
370
+ :param new_graph: Destination graph.
371
+ :type new_graph: networkx.Graph
372
+ :param mapping: Old-node to new-node mapping.
373
+ :type mapping: dict
374
+ """
375
+ for old_node, new_node in mapping.items():
376
+ attrs = dict(graph.nodes[old_node])
377
+ new_graph.add_node(new_node, **attrs)
378
+
379
+ @staticmethod
380
+ def _copy_edges_with_mapping(graph, new_graph, mapping: dict) -> None:
381
+ """Copy graph edges into a new graph using a node mapping.
382
+
383
+ Supports both simple graphs and multigraphs.
384
+
385
+ :param graph: Source graph.
386
+ :type graph: networkx.Graph
387
+ :param new_graph: Destination graph.
388
+ :type new_graph: networkx.Graph
389
+ :param mapping: Old-node to new-node mapping.
390
+ :type mapping: dict
391
+ """
392
+ if graph.is_multigraph():
393
+ for u, v, key, attrs in graph.edges(keys=True, data=True):
394
+ new_graph.add_edge(
395
+ mapping[u],
396
+ mapping[v],
397
+ key=key,
398
+ **dict(attrs),
399
+ )
400
+ return
401
+
402
+ for u, v, attrs in graph.edges(data=True):
403
+ new_graph.add_edge(
404
+ mapping[u],
405
+ mapping[v],
406
+ **dict(attrs),
407
+ )
408
+
409
+ @staticmethod
410
+ def _rebuild_graph_with_mapping(graph, mapping: dict):
411
+ """Rebuild a graph with remapped node IDs.
412
+
413
+ This avoids in-place relabeling collisions, for example when node ``27``
414
+ must become node ``20`` while old node ``20`` must move elsewhere.
415
+
416
+ :param graph: Source graph.
417
+ :type graph: networkx.Graph
418
+ :param mapping: Old-node to new-node mapping.
419
+ :type mapping: dict
420
+ :return: Rebuilt graph with remapped node IDs.
421
+ :rtype: networkx.Graph
422
+ """
423
+ new_graph = graph.__class__()
424
+ new_graph.graph.update(graph.graph)
425
+
426
+ ITSExpand._copy_nodes_with_mapping(graph, new_graph, mapping)
427
+ ITSExpand._copy_edges_with_mapping(graph, new_graph, mapping)
428
+
429
+ return new_graph
430
+
431
+ @staticmethod
432
+ def reindex_side_graph_by_atom_map(graph, contiguous: bool = True):
433
+ """Reindex a side graph so mapped atoms use ``atom_map`` as node ID.
434
+
435
+ By default, the returned graph keeps node IDs contiguous from ``1..N``.
436
+ Set ``contiguous=False`` to preserve valid sparse map IDs such as
437
+ ``:10`` and ``:21``; this is needed by the EF-SMIRKS expansion path.
438
+
439
+ This is useful because the reaction-center graph produced by
440
+ ``ITSConstruction().ITSGraph(...)`` uses atom-map numbers as node IDs,
441
+ whereas the side graph produced by ``smiles_to_graph(...)`` may use
442
+ RDKit-style atom indices as node IDs.
443
+
444
+ .. rubric:: Example
445
+
446
+ Before reindexing:
447
+
448
+ .. code-block:: text
449
+
450
+ Node 20: atom_map = 0
451
+ Node 27: atom_map = 20
452
+
453
+ After reindexing:
454
+
455
+ .. code-block:: text
456
+
457
+ Node 20: atom_map = 20
458
+ Node 27: atom_map = 0
459
+
460
+ or another unmapped atom may be moved into the freed node position.
461
+
462
+ :param graph: Molecular side graph.
463
+ :type graph: networkx.Graph
464
+ :param contiguous: Whether the returned node IDs must be ``1..N``.
465
+ :type contiguous: bool
466
+ :return: Reindexed side graph.
467
+ :rtype: networkx.Graph
468
+ :raises ValueError: If atom-map numbers cannot be safely used as node
469
+ IDs while preserving ``1..N`` indexing.
470
+ """
471
+ mapping = ITSExpand._build_side_graph_reindex_mapping(graph, contiguous)
472
+ return ITSExpand._rebuild_graph_with_mapping(graph, mapping)
473
+
474
+ @staticmethod
475
+ def _canonical_unmapped_constitution(smiles: str) -> str:
476
+ """Return an atom-map- and stereo-independent constitutional key.
477
+
478
+ Isotopes, formal charges, explicit hydrogens, bond orders, and molecular
479
+ fragments remain part of the key. Only atom-map labels and
480
+ stereochemical annotations are removed.
481
+ """
482
+ mol = Chem.MolFromSmiles(smiles)
483
+ if mol is None:
484
+ raise ITSExpansionError(
485
+ f"Cannot parse endpoint while checking constitution: {smiles!r}"
486
+ )
487
+
488
+ for atom in mol.GetAtoms():
489
+ atom.SetAtomMapNum(0)
490
+ Chem.RemoveStereochemistry(mol)
491
+ return Chem.MolToSmiles(mol, canonical=True, isomericSmiles=True)
492
+
493
+ @classmethod
494
+ def endpoint_constitutions_match(
495
+ cls, source_rsmi: str, candidate_rsmi: str
496
+ ) -> bool:
497
+ """Check that reactant and product constitutions are independently equal.
498
+
499
+ This is a fail-closed guard for partial-AAM completion, not an atom-map
500
+ accuracy metric. It prevents a fallback reconstruction from silently
501
+ deleting fragments or changing either reaction endpoint.
502
+ """
503
+ source_react, source_prod = cls._split_rsmi(source_rsmi)
504
+ candidate_react, candidate_prod = cls._split_rsmi(candidate_rsmi)
505
+ return cls._canonical_unmapped_constitution(
506
+ source_react
507
+ ) == cls._canonical_unmapped_constitution(
508
+ candidate_react
509
+ ) and cls._canonical_unmapped_constitution(
510
+ source_prod
511
+ ) == cls._canonical_unmapped_constitution(
512
+ candidate_prod
513
+ )
514
+
515
+ @staticmethod
516
+ def _fold_unmapped_explicit_hydrogens_in_side_with_count(
517
+ smiles: str,
518
+ ) -> tuple[str, int]:
519
+ """Fold removable unmapped H atoms into their heavy-atom H count.
520
+
521
+ Mapped hydrogen atoms, isotopic hydrogens, and non-removable standalone
522
+ hydrogen fragments remain explicit. This prevents the partial-AAM
523
+ reaction-center path from deleting an unmapped explicit H without
524
+ transferring its hydrogen count to the attached atom.
525
+ """
526
+ parser = Chem.SmilesParserParams()
527
+ parser.removeHs = False
528
+ mol = Chem.MolFromSmiles(smiles, parser)
529
+ if mol is None:
530
+ raise ITSExpansionError(
531
+ f"Cannot parse endpoint while folding unmapped H: {smiles!r}"
532
+ )
533
+
534
+ explicit_unmapped_before = sum(
535
+ atom.GetAtomicNum() == 1 and atom.GetAtomMapNum() == 0
536
+ for atom in mol.GetAtoms()
537
+ )
538
+
539
+ parameters = Chem.RemoveHsParameters()
540
+ parameters.removeMapped = False
541
+ parameters.updateExplicitCount = True
542
+ try:
543
+ folded = Chem.RemoveHs(mol, parameters, sanitize=True)
544
+ except Exception as exc:
545
+ raise ITSExpansionError(
546
+ f"Cannot safely fold unmapped explicit H: {smiles!r}; {exc}"
547
+ ) from exc
548
+ explicit_unmapped_after = sum(
549
+ atom.GetAtomicNum() == 1 and atom.GetAtomMapNum() == 0
550
+ for atom in folded.GetAtoms()
551
+ )
552
+ return Chem.MolToSmiles(
553
+ folded,
554
+ canonical=False,
555
+ isomericSmiles=True,
556
+ ), int(explicit_unmapped_before - explicit_unmapped_after)
557
+
558
+ @classmethod
559
+ def _fold_unmapped_explicit_hydrogens_in_side(cls, smiles: str) -> str:
560
+ """Fold removable unmapped H atoms and return the normalized side."""
561
+ folded, _count = cls._fold_unmapped_explicit_hydrogens_in_side_with_count(
562
+ smiles
563
+ )
564
+ return folded
565
+
566
+ @classmethod
567
+ def fold_unmapped_explicit_hydrogens(cls, rsmi: str) -> str:
568
+ """Normalize removable unmapped H on both reaction endpoints."""
569
+ reactant, product = cls._split_rsmi(rsmi)
570
+ return (
571
+ f"{cls._fold_unmapped_explicit_hydrogens_in_side(reactant)}>>"
572
+ f"{cls._fold_unmapped_explicit_hydrogens_in_side(product)}"
573
+ )
574
+
575
+ @classmethod
576
+ def _fold_unmapped_explicit_hydrogens_with_count(cls, rsmi: str) -> tuple[str, int]:
577
+ """Normalize both endpoints and count the explicit H atoms folded."""
578
+ reactant, product = cls._split_rsmi(rsmi)
579
+ folded_reactant, reactant_count = (
580
+ cls._fold_unmapped_explicit_hydrogens_in_side_with_count(reactant)
581
+ )
582
+ folded_product, product_count = (
583
+ cls._fold_unmapped_explicit_hydrogens_in_side_with_count(product)
584
+ )
585
+ return (
586
+ f"{folded_reactant}>>{folded_product}",
587
+ reactant_count + product_count,
588
+ )
589
+
590
+ @staticmethod
591
+ def _side_has_stereochemistry(smiles: str) -> bool:
592
+ """Return whether an endpoint contains atom or bond stereochemistry."""
593
+ parser = Chem.SmilesParserParams()
594
+ parser.removeHs = False
595
+ mol = Chem.MolFromSmiles(smiles, parser)
596
+ if mol is None:
597
+ raise ITSExpansionError(
598
+ f"Cannot parse endpoint while inspecting stereochemistry: {smiles!r}"
599
+ )
600
+ return any(
601
+ atom.GetChiralTag() != Chem.ChiralType.CHI_UNSPECIFIED
602
+ for atom in mol.GetAtoms()
603
+ ) or any(
604
+ bond.GetStereo() != Chem.BondStereo.STEREONONE
605
+ or bond.GetBondDir() != Chem.BondDir.NONE
606
+ for bond in mol.GetBonds()
607
+ )
608
+
609
+ @staticmethod
610
+ def _remove_stereochemistry_in_side(smiles: str) -> str:
611
+ """Remove atom/bond stereo while retaining explicit mapped H atoms."""
612
+ parser = Chem.SmilesParserParams()
613
+ parser.removeHs = False
614
+ mol = Chem.MolFromSmiles(smiles, parser)
615
+ if mol is None:
616
+ raise ITSExpansionError(
617
+ f"Cannot parse endpoint while removing stereochemistry: {smiles!r}"
618
+ )
619
+ Chem.RemoveStereochemistry(mol)
620
+ return Chem.MolToSmiles(mol, canonical=False, isomericSmiles=False)
621
+
622
+ @classmethod
623
+ def remove_stereochemistry(cls, rsmi: str) -> str:
624
+ """Remove endpoint stereo without folding explicit mapped H atoms."""
625
+ reactant, product = cls._split_rsmi(rsmi)
626
+ return (
627
+ f"{cls._remove_stereochemistry_in_side(reactant)}>>"
628
+ f"{cls._remove_stereochemistry_in_side(product)}"
629
+ )
630
+
631
+ @classmethod
632
+ def _rsmi_has_stereochemistry(cls, rsmi: str) -> bool:
633
+ """Return whether either reaction endpoint carries stereo metadata."""
634
+ if not any(marker in rsmi for marker in ("@", "/", "\\")):
635
+ return False
636
+ return any(
637
+ cls._side_has_stereochemistry(side) for side in cls._split_rsmi(rsmi)
638
+ )
639
+
640
+ @staticmethod
641
+ def _build_expansion_reaction_center(
642
+ rsmi: str,
643
+ *,
644
+ constitutional_only: bool,
645
+ ) -> nx.Graph:
646
+ """Convert partial endpoints and construct their ITS reaction centre."""
647
+ react_graph, prod_graph = rsmi_to_graph(
648
+ rsmi,
649
+ node_attrs=(CONSTITUTIONAL_RC_NODE_ATTRS if constitutional_only else None),
650
+ edge_attrs=(CONSTITUTIONAL_RC_EDGE_ATTRS if constitutional_only else None),
651
+ include_stereo_descriptors=not constitutional_only,
652
+ )
653
+ if react_graph is None or prod_graph is None:
654
+ raise ITSExpansionError("reaction graph conversion returned None")
655
+ rc_graph = ITSConstruction().ITSGraph(react_graph, prod_graph)
656
+ if rc_graph is None:
657
+ raise ITSExpansionError("ITS reaction-center construction returned None")
658
+ return rc_graph
659
+
660
+ @staticmethod
661
+ def _convert_expansion_side(
662
+ smiles: str,
663
+ *,
664
+ constitutional_only: bool,
665
+ preserve_radical_state: bool,
666
+ ) -> nx.Graph:
667
+ """Convert one complete endpoint with only expansion-required fields."""
668
+ graph = smiles_to_graph(
669
+ smiles,
670
+ sanitize=True,
671
+ drop_non_aam=False,
672
+ use_index_as_atom_map=False,
673
+ node_attrs=(
674
+ (
675
+ CONSTITUTIONAL_SIDE_NODE_ATTRS
676
+ if preserve_radical_state
677
+ else CONSTITUTIONAL_RC_NODE_ATTRS
678
+ )
679
+ if constitutional_only
680
+ else None
681
+ ),
682
+ edge_attrs=(
683
+ CONSTITUTIONAL_SIDE_EDGE_ATTRS if constitutional_only else None
684
+ ),
685
+ include_stereo_descriptors=not constitutional_only,
686
+ )
687
+ if graph is None:
688
+ raise ITSExpansionError("full endpoint conversion returned None")
689
+ return graph
690
+
691
+ @classmethod
692
+ def _expansion_side_graphs(
693
+ cls,
694
+ react_smi: str,
695
+ prod_smi: str,
696
+ *,
697
+ use_G: bool,
698
+ preserve_radical_state: bool,
699
+ constitutional_only: bool,
700
+ ) -> tuple[nx.Graph, nx.Graph | None, nx.Graph | None]:
701
+ """Return selected side and optional radical-reference endpoints."""
702
+ selected_smiles = react_smi if use_G else prod_smi
703
+ side_graph = cls._convert_expansion_side(
704
+ selected_smiles,
705
+ constitutional_only=constitutional_only,
706
+ preserve_radical_state=preserve_radical_state,
707
+ )
708
+ full_react_graph = side_graph if use_G else None
709
+ full_prod_graph = side_graph if not use_G else None
710
+ if preserve_radical_state:
711
+ other_graph = cls._convert_expansion_side(
712
+ prod_smi if use_G else react_smi,
713
+ constitutional_only=constitutional_only,
714
+ preserve_radical_state=True,
715
+ )
716
+ if use_G:
717
+ full_prod_graph = other_graph
718
+ else:
719
+ full_react_graph = other_graph
720
+ return side_graph, full_react_graph, full_prod_graph
721
+
722
+ @staticmethod
723
+ def _reacting_hydrogen_output_maps(
724
+ rc_graph: nx.Graph,
725
+ side_graph: nx.Graph,
726
+ *,
727
+ explicit_hydrogen: bool,
728
+ ) -> list[int]:
729
+ """Translate reacting H labels to post-builder atom-map identifiers."""
730
+ if explicit_hydrogen:
731
+ return []
732
+ reacting_hydrogen_maps = {
733
+ data["atom_map"]
734
+ for _, data in get_rc(rc_graph).nodes(data=True)
735
+ if data.get("element") == "H"
736
+ }
737
+ return [
738
+ node
739
+ for node, data in side_graph.nodes(data=True)
740
+ if data.get("atom_map") in reacting_hydrogen_maps
741
+ ]
742
+
743
+ @staticmethod
744
+ def _serialize_expansion(
745
+ react_graph: nx.Graph,
746
+ prod_graph: nx.Graph,
747
+ its_graph: nx.Graph,
748
+ *,
749
+ explicit_hydrogen: bool,
750
+ preserve_hydrogen_maps: list[int],
751
+ standardize_output: bool,
752
+ ) -> str:
753
+ """Serialize a reconstructed ITS and optionally standardize its RSMI."""
754
+ expanded_rsmi = graph_to_rsmi(
755
+ react_graph,
756
+ prod_graph,
757
+ its_graph,
758
+ sanitize=True,
759
+ explicit_hydrogen=explicit_hydrogen,
760
+ preserve_hydrogen_maps=preserve_hydrogen_maps,
761
+ )
762
+ if not isinstance(expanded_rsmi, str) or not expanded_rsmi:
763
+ raise ITSExpansionError("graph-to-RSMI serialization returned no result")
764
+ if not standardize_output:
765
+ return expanded_rsmi
766
+ standardized = std.fit(
767
+ expanded_rsmi,
768
+ remove_aam=False,
769
+ remove_invalid=False,
770
+ )
771
+ if not standardized:
772
+ raise ITSExpansionError(
773
+ "expanded RSMI contains an invalid fragment; no fragments were dropped"
774
+ )
775
+ return standardized
776
+
777
+ @classmethod
778
+ def _expand_aam_once(
779
+ cls,
780
+ rsmi: str,
781
+ *,
782
+ use_G: bool,
783
+ preserve_older_map: bool,
784
+ explicit_hydrogen: bool,
785
+ preserve_radical_state: bool,
786
+ constitutional_only: bool,
787
+ standardize_output: bool,
788
+ ) -> str:
789
+ """Run one side-specific ITS expansion and fail on invalid output."""
790
+ side_name = "reactant" if use_G else "product"
791
+ try:
792
+ react_smi, prod_smi = cls._split_rsmi(rsmi)
793
+ rc_graph = cls._build_expansion_reaction_center(
794
+ rsmi,
795
+ constitutional_only=constitutional_only,
796
+ )
797
+ side_graph, full_react_graph, full_prod_graph = cls._expansion_side_graphs(
798
+ react_smi,
799
+ prod_smi,
800
+ use_G=use_G,
801
+ preserve_radical_state=preserve_radical_state,
802
+ constitutional_only=constitutional_only,
803
+ )
804
+
805
+ if preserve_older_map:
806
+ side_graph = cls.reindex_side_graph_by_atom_map(
807
+ side_graph,
808
+ contiguous=False,
809
+ )
810
+
811
+ preserve_hydrogen_maps = cls._reacting_hydrogen_output_maps(
812
+ rc_graph,
813
+ side_graph,
814
+ explicit_hydrogen=explicit_hydrogen,
815
+ )
816
+
817
+ its_graph = ITSBuilder().ITSGraph(side_graph, rc_graph)
818
+ if its_graph is None:
819
+ raise ITSExpansionError("ITS reconstruction returned None")
820
+
821
+ new_react, new_prod = its_decompose(its_graph)
822
+ if preserve_radical_state:
823
+ assert full_react_graph is not None
824
+ assert full_prod_graph is not None
825
+ cls._transfer_endpoint_radicals(
826
+ full_react_graph,
827
+ new_react,
828
+ preserve_atom_maps=preserve_older_map,
829
+ )
830
+ cls._transfer_endpoint_radicals(
831
+ full_prod_graph,
832
+ new_prod,
833
+ preserve_atom_maps=preserve_older_map,
834
+ )
835
+ return cls._serialize_expansion(
836
+ new_react,
837
+ new_prod,
838
+ its_graph,
839
+ explicit_hydrogen=explicit_hydrogen,
840
+ preserve_hydrogen_maps=preserve_hydrogen_maps,
841
+ standardize_output=standardize_output,
842
+ )
843
+ except ITSExpansionError as exc:
844
+ raise ITSExpansionError(
845
+ f"{side_name}-side ITS expansion failed: {exc}"
846
+ ) from exc
847
+ except Exception as exc:
848
+ raise ITSExpansionError(
849
+ f"{side_name}-side ITS expansion failed: {exc}"
850
+ ) from exc
851
+
852
+ @staticmethod
853
+ def _mapped_radical_assignments(source_graph, target_graph):
854
+ """Return unique anchor-local radical assignments when compatible."""
855
+ radical_sources = [
856
+ (node, data)
857
+ for node, data in source_graph.nodes(data=True)
858
+ if int(data.get("radical", 0) or 0) > 0
859
+ ]
860
+ target_by_map = {
861
+ int(data.get("atom_map", 0) or 0): node
862
+ for node, data in target_graph.nodes(data=True)
863
+ if int(data.get("atom_map", 0) or 0) > 0
864
+ }
865
+ assignments = []
866
+ assigned_targets = set()
867
+
868
+ def compatible(source_data, target_data) -> bool:
869
+ return all(
870
+ source_data.get(name) == target_data.get(name)
871
+ for name in ("element", "aromatic", "hcount", "charge")
872
+ )
873
+
874
+ def mapped_neighbour_signature(graph, node):
875
+ signature = []
876
+ for neighbour in graph.neighbors(node):
877
+ neighbour_data = graph.nodes[neighbour]
878
+ atom_map = int(neighbour_data.get("atom_map", 0) or 0)
879
+ if atom_map <= 0:
880
+ continue
881
+ signature.append(
882
+ (
883
+ atom_map,
884
+ neighbour_data.get("element"),
885
+ neighbour_data.get("charge"),
886
+ graph.edges[node, neighbour].get("order", 1.0),
887
+ )
888
+ )
889
+ return tuple(sorted(signature))
890
+
891
+ for source_node, source_data in radical_sources:
892
+ source_map = int(source_data.get("atom_map", 0) or 0)
893
+ if source_map > 0:
894
+ target_node = target_by_map.get(source_map)
895
+ if target_node is None or not compatible(
896
+ source_data, target_graph.nodes[target_node]
897
+ ):
898
+ return None
899
+ else:
900
+ source_signature = mapped_neighbour_signature(source_graph, source_node)
901
+ if not source_signature:
902
+ return None
903
+ candidates = [
904
+ node
905
+ for node, target_data in target_graph.nodes(data=True)
906
+ if node not in assigned_targets
907
+ and compatible(source_data, target_data)
908
+ and source_graph.degree[source_node] == target_graph.degree[node]
909
+ and mapped_neighbour_signature(target_graph, node)
910
+ == source_signature
911
+ ]
912
+ if len(candidates) != 1:
913
+ return None
914
+ target_node = candidates[0]
915
+ assigned_targets.add(target_node)
916
+ assignments.append((target_node, int(source_data.get("radical", 0) or 0)))
917
+ return assignments
918
+
919
+ @staticmethod
920
+ def _transfer_endpoint_radicals(
921
+ source_graph,
922
+ target_graph,
923
+ *,
924
+ preserve_atom_maps: bool = True,
925
+ ) -> None:
926
+ """Transfer radical counts through exact endpoint identity.
927
+
928
+ Existing atom maps constrain the correspondence only when the caller
929
+ requested anchor preservation. In normal, unanchored expansion the
930
+ reconstructed endpoint has deliberately received fresh map numbers,
931
+ so requiring equality to the source labels would make a valid graph
932
+ isomorphism impossible.
933
+ """
934
+
935
+ radical_sources = [
936
+ (node, data)
937
+ for node, data in source_graph.nodes(data=True)
938
+ if int(data.get("radical", 0) or 0) > 0
939
+ ]
940
+ if not radical_sources:
941
+ nx.set_node_attributes(target_graph, 0, "radical")
942
+ return
943
+
944
+ if preserve_atom_maps:
945
+ direct_assignments = ITSExpand._mapped_radical_assignments(
946
+ source_graph, target_graph
947
+ )
948
+ if direct_assignments is not None:
949
+ nx.set_node_attributes(target_graph, 0, "radical")
950
+ for target_node, radical_count in direct_assignments:
951
+ target_graph.nodes[target_node]["radical"] = radical_count
952
+ return
953
+
954
+ def node_match(source: dict, target: dict) -> bool:
955
+ if any(
956
+ source.get(name) != target.get(name)
957
+ for name in ("element", "aromatic", "hcount", "charge")
958
+ ):
959
+ return False
960
+ if not preserve_atom_maps:
961
+ return True
962
+ required_map = int(source.get("atom_map", 0) or 0)
963
+ target_map = int(target.get("atom_map", 0) or 0)
964
+ return required_map == 0 or required_map == target_map
965
+
966
+ def edge_match(source: dict, target: dict) -> bool:
967
+ return source.get("order", 1.0) == target.get("order", 1.0)
968
+
969
+ matcher = nx.algorithms.isomorphism.GraphMatcher(
970
+ source_graph,
971
+ target_graph,
972
+ node_match=node_match,
973
+ edge_match=edge_match,
974
+ )
975
+ try:
976
+ mapping = next(matcher.isomorphisms_iter())
977
+ except StopIteration as exc:
978
+ raise ITSExpansionError(
979
+ "Cannot align a reconstructed endpoint with its supplied radical state"
980
+ ) from exc
981
+
982
+ for source_node, target_node in mapping.items():
983
+ target_graph.nodes[target_node]["radical"] = int(
984
+ source_graph.nodes[source_node].get("radical", 0) or 0
985
+ )
986
+
987
+ @classmethod
988
+ def expand_aam_with_its_report(
989
+ cls,
990
+ rsmi: str,
991
+ relabel: bool = False,
992
+ use_G: bool = True,
993
+ preserve_older_map: bool = False,
994
+ fallback_to_other_side: bool = False,
995
+ require_constitution_preservation: bool = False,
996
+ fold_unmapped_explicit_hydrogens: bool = False,
997
+ ignore_stereochemistry: bool = False,
998
+ explicit_hydrogen: bool = False,
999
+ preserve_radical_state: bool = False,
1000
+ constitutional_only: bool = False,
1001
+ standardize_output: bool = True,
1002
+ ) -> ITSExpansionResult:
1003
+ """Expand a partial reaction SMILES to a full AAM RSMI using ITS
1004
+ reconstruction.
1005
+
1006
+ :param rsmi: Reaction SMILES string in the format
1007
+ ``reactant>>product``.
1008
+ :type rsmi: str
1009
+ :param relabel: If True, directly apply ``ITSRelabel().fit(rsmi)``.
1010
+ This globally renumbers atom maps.
1011
+ :type relabel: bool
1012
+ :param use_G: If True, expand using the reactant side. If False,
1013
+ expand using the product side.
1014
+ :type use_G: bool
1015
+ :param preserve_older_map: If True, preserve existing nonzero atom-map
1016
+ numbers by reindexing the side graph before ITS reconstruction.
1017
+ This keeps old maps such as ``:20`` attached to the same atom.
1018
+ This option is incompatible with ``relabel=True``.
1019
+ :type preserve_older_map: bool
1020
+ :param fallback_to_other_side: Retry from the opposite reaction side if
1021
+ the preferred side cannot be expanded. Defaults to False.
1022
+ :type fallback_to_other_side: bool
1023
+ :param require_constitution_preservation: Require the expanded RSMI to
1024
+ preserve the reactant and product constitutions independently.
1025
+ This guard ignores atom maps and stereochemistry but retains
1026
+ isotopes, charges, hydrogens, fragments, and bond orders.
1027
+ :type require_constitution_preservation: bool
1028
+ :param fold_unmapped_explicit_hydrogens: Fold removable unmapped
1029
+ explicit H atoms into heavy-atom hydrogen counts before partial
1030
+ reaction-center construction, while retaining mapped H atoms.
1031
+ :type fold_unmapped_explicit_hydrogens: bool
1032
+ :param ignore_stereochemistry: Remove atom and bond stereo from the
1033
+ working RSMI before ITS construction. This is useful when stereo is
1034
+ explicitly outside the expansion task and malformed directional
1035
+ annotations prevent graph conversion. The constitution guard still
1036
+ compares both original endpoints independently.
1037
+ :type ignore_stereochemistry: bool
1038
+ :param explicit_hydrogen: Preserve explicit hydrogen graph nodes during
1039
+ graph-to-RSMI serialization. This does not materialize hydrogen
1040
+ atoms already represented only by ``hcount``.
1041
+ :type explicit_hydrogen: bool
1042
+ :param preserve_radical_state: Transfer supplied endpoint radical
1043
+ counts through exact endpoint identity instead of asking RDKit to
1044
+ infer them solely from valence during serialization. Existing maps
1045
+ constrain identity only when ``preserve_older_map=True``.
1046
+ :type preserve_radical_state: bool
1047
+ :param constitutional_only: Build only attributes required for
1048
+ constitutional partial-AAM completion and omit graph-level stereo
1049
+ descriptors. Defaults to False.
1050
+ :type constitutional_only: bool
1051
+ :param standardize_output: Reparse and standardize the already
1052
+ sanitized graph serialization. Disable only for the minimal RSMI
1053
+ adapter, whose validation is performed separately.
1054
+ :type standardize_output: bool
1055
+ :return: Fully mapped RSMI and structured expansion evidence.
1056
+ :rtype: ITSExpansionResult
1057
+ :raises ValueError: If input RSMI format is invalid, if incompatible
1058
+ options are used, or if side-graph reindexing is unsafe.
1059
+
1060
+ .. rubric:: Examples
1061
+
1062
+ >>> expander = ITSExpand()
1063
+ >>> expander.expand_aam_with_its(
1064
+ ... "CC[CH2:3][Cl:1].[N:2]>>CC[CH2:3][N:2].[Cl:1]",
1065
+ ... preserve_older_map=True,
1066
+ ... )
1067
+ '[CH3:1][CH2:2][CH2:3][Cl:4].[N:5]>>[CH3:1][CH2:2][CH2:3][N:5].[Cl:4]'
1068
+ """
1069
+ if relabel and preserve_older_map:
1070
+ raise ValueError(
1071
+ "preserve_older_map=True cannot be combined with relabel=True. "
1072
+ "ITSRelabel globally renumbers atom maps. Use relabel=False "
1073
+ "with preserve_older_map=True."
1074
+ )
1075
+
1076
+ if relabel:
1077
+ expanded = ITSRelabel().fit(rsmi)
1078
+ if (
1079
+ require_constitution_preservation
1080
+ and not cls.endpoint_constitutions_match(rsmi, expanded)
1081
+ ):
1082
+ raise ITSExpansionError(
1083
+ "Relabelled RSMI does not preserve both endpoint constitutions."
1084
+ )
1085
+ return ITSExpansionResult(
1086
+ expanded,
1087
+ preferred_side="relabel",
1088
+ selected_side="relabel",
1089
+ fallback_used=False,
1090
+ constitution_checked=require_constitution_preservation,
1091
+ constitution_guard_passed=(
1092
+ True if require_constitution_preservation else None
1093
+ ),
1094
+ explicit_hydrogen_serialization=explicit_hydrogen,
1095
+ radical_state_preserved=preserve_radical_state,
1096
+ )
1097
+
1098
+ cls._split_rsmi(rsmi)
1099
+ source_rsmi = rsmi
1100
+ if fold_unmapped_explicit_hydrogens:
1101
+ working_rsmi, folded_hydrogen_count = (
1102
+ cls._fold_unmapped_explicit_hydrogens_with_count(rsmi)
1103
+ )
1104
+ else:
1105
+ working_rsmi = rsmi
1106
+ folded_hydrogen_count = 0
1107
+ stereochemistry_ignored = bool(
1108
+ ignore_stereochemistry and cls._rsmi_has_stereochemistry(working_rsmi)
1109
+ )
1110
+ if stereochemistry_ignored:
1111
+ working_rsmi = cls.remove_stereochemistry(working_rsmi)
1112
+ preferred_name = "reactant" if use_G else "product"
1113
+ fallback_name = "product" if use_G else "reactant"
1114
+
1115
+ def expand_and_guard(side: bool) -> str:
1116
+ expanded = cls._expand_aam_once(
1117
+ working_rsmi,
1118
+ use_G=side,
1119
+ preserve_older_map=preserve_older_map,
1120
+ explicit_hydrogen=explicit_hydrogen,
1121
+ preserve_radical_state=preserve_radical_state,
1122
+ constitutional_only=constitutional_only,
1123
+ standardize_output=standardize_output,
1124
+ )
1125
+ if (
1126
+ require_constitution_preservation
1127
+ and not cls.endpoint_constitutions_match(source_rsmi, expanded)
1128
+ ):
1129
+ side_name = "reactant" if side else "product"
1130
+ raise ITSExpansionError(
1131
+ f"{side_name}-side expansion changed an endpoint constitution"
1132
+ )
1133
+ return expanded
1134
+
1135
+ try:
1136
+ expanded = expand_and_guard(use_G)
1137
+ return ITSExpansionResult(
1138
+ expanded,
1139
+ preferred_side=preferred_name,
1140
+ selected_side=preferred_name,
1141
+ fallback_used=False,
1142
+ constitution_checked=require_constitution_preservation,
1143
+ constitution_guard_passed=(
1144
+ True if require_constitution_preservation else None
1145
+ ),
1146
+ unmapped_explicit_hydrogens_folded=(folded_hydrogen_count > 0),
1147
+ folded_unmapped_explicit_hydrogen_count=folded_hydrogen_count,
1148
+ stereochemistry_ignored_for_expansion=stereochemistry_ignored,
1149
+ explicit_hydrogen_serialization=explicit_hydrogen,
1150
+ radical_state_preserved=preserve_radical_state,
1151
+ )
1152
+ except ITSExpansionError as preferred_error:
1153
+ if not fallback_to_other_side:
1154
+ raise
1155
+
1156
+ try:
1157
+ expanded = expand_and_guard(not use_G)
1158
+ return ITSExpansionResult(
1159
+ expanded,
1160
+ preferred_side=preferred_name,
1161
+ selected_side=fallback_name,
1162
+ fallback_used=True,
1163
+ constitution_checked=require_constitution_preservation,
1164
+ constitution_guard_passed=(
1165
+ True if require_constitution_preservation else None
1166
+ ),
1167
+ unmapped_explicit_hydrogens_folded=(folded_hydrogen_count > 0),
1168
+ folded_unmapped_explicit_hydrogen_count=folded_hydrogen_count,
1169
+ stereochemistry_ignored_for_expansion=stereochemistry_ignored,
1170
+ explicit_hydrogen_serialization=explicit_hydrogen,
1171
+ radical_state_preserved=preserve_radical_state,
1172
+ fallback_reason=str(preferred_error),
1173
+ )
1174
+ except ITSExpansionError as fallback_error:
1175
+ raise ITSExpansionError(
1176
+ f"Both ITS expansion directions failed. {preferred_name}: "
1177
+ f"{preferred_error}; {fallback_name}: {fallback_error}"
1178
+ ) from fallback_error
1179
+
1180
+ @classmethod
1181
+ def expand_rsmi(
1182
+ cls,
1183
+ rsmi: str,
1184
+ *,
1185
+ use_G: bool = True,
1186
+ preserve_radical_state: bool = False,
1187
+ ) -> str:
1188
+ """Complete partial AAM through the minimal RSMI reconstruction path.
1189
+
1190
+ This adapter parses the mapped reaction centre and one complete
1191
+ endpoint, performs ITS glue/decomposition, and serializes the result.
1192
+ It deliberately assigns fresh maps and excludes optional hydrogen,
1193
+ stereo, fallback, and post-expansion constitution guards. Radical
1194
+ attributes can be transported as part of reconstruction without
1195
+ enabling those guards.
1196
+
1197
+ :param rsmi: Partially mapped reaction SMILES.
1198
+ :type rsmi: str
1199
+ :param use_G: Reconstruct from reactants when True, products otherwise.
1200
+ :type use_G: bool
1201
+ :param preserve_radical_state: Transport endpoint radical counts as a
1202
+ required graph attribute. This does not enable validation guards.
1203
+ :type preserve_radical_state: bool
1204
+ :return: Completely mapped reaction SMILES.
1205
+ :rtype: str
1206
+ """
1207
+ return cls.expand_aam_with_its(
1208
+ rsmi,
1209
+ use_G=use_G,
1210
+ preserve_older_map=False,
1211
+ preserve_radical_state=preserve_radical_state,
1212
+ constitutional_only=True,
1213
+ standardize_output=False,
1214
+ )
1215
+
1216
+ @classmethod
1217
+ def expand_aam_with_its(
1218
+ cls,
1219
+ rsmi: str,
1220
+ relabel: bool = False,
1221
+ use_G: bool = True,
1222
+ preserve_older_map: bool = False,
1223
+ fallback_to_other_side: bool = False,
1224
+ require_constitution_preservation: bool = False,
1225
+ fold_unmapped_explicit_hydrogens: bool = False,
1226
+ ignore_stereochemistry: bool = False,
1227
+ explicit_hydrogen: bool = False,
1228
+ preserve_radical_state: bool = False,
1229
+ constitutional_only: bool = False,
1230
+ standardize_output: bool = True,
1231
+ ) -> str:
1232
+ """Expand partial AAM and return the completed reaction SMILES.
1233
+
1234
+ Use :meth:`expand_aam_with_its_report` when selected-side and guard
1235
+ evidence are also required. This method preserves the historical
1236
+ string-returning API.
1237
+ """
1238
+ return cls.expand_aam_with_its_report(
1239
+ rsmi,
1240
+ relabel=relabel,
1241
+ use_G=use_G,
1242
+ preserve_older_map=preserve_older_map,
1243
+ fallback_to_other_side=fallback_to_other_side,
1244
+ require_constitution_preservation=require_constitution_preservation,
1245
+ fold_unmapped_explicit_hydrogens=fold_unmapped_explicit_hydrogens,
1246
+ ignore_stereochemistry=ignore_stereochemistry,
1247
+ explicit_hydrogen=explicit_hydrogen,
1248
+ preserve_radical_state=preserve_radical_state,
1249
+ constitutional_only=constitutional_only,
1250
+ standardize_output=standardize_output,
1251
+ ).rsmi