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,967 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import warnings
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, List, Mapping, Optional, Tuple, Union
7
+
8
+ import networkx as nx
9
+ from synkit.IO.chem_converter import (
10
+ ITSFormat,
11
+ detect_its_format,
12
+ smiles_to_graph,
13
+ rsmi_to_its,
14
+ graph_to_smi,
15
+ )
16
+ from synkit.IO import setup_logging
17
+ from synkit.Chem.utils import reverse_reaction
18
+
19
+ from synkit.Rule import SynRule
20
+ from synkit.Graph.syn_graph import SynGraph
21
+ from synkit.Graph.canon_graph import GraphCanonicaliser
22
+ from synkit.Graph.Mech.electron_accounting import refresh_electron_fields
23
+ from .strategy import Strategy
24
+ from . import product as _product_state
25
+ from . import rewrite as _graph_rewrite
26
+ from ..matching.mixin import ReactorMatchingMixin
27
+ from ..output import deduplication as _deduplication
28
+ from ..output import serialization as _serialization
29
+ from ..output import structural as _structural_deduplication
30
+ from ..output.policy import (
31
+ RawITSApplicationSerializationWarning,
32
+ )
33
+ from ..stereo.mixin import ReactorStereoMixin
34
+
35
+ # ──────────────────────────────────────────────────────────────────────────────
36
+ # Typing aliases
37
+ # ──────────────────────────────────────────────────────────────────────────────
38
+ NodeId = Any
39
+ EdgeAttr = Mapping[str, Any]
40
+ MappingDict = Dict[NodeId, NodeId]
41
+
42
+ # ──────────────────────────────────────────────────────────────────────────────
43
+ # Logging setup
44
+ # ──────────────────────────────────────────────────────────────────────────────
45
+
46
+ log = setup_logging(task_type="synreactor")
47
+
48
+ # ──────────────────────────────────────────────────────────────────────────────
49
+ # SynReactor core
50
+ # ──────────────────────────────────────────────────────────────────────────────
51
+
52
+
53
+ @dataclass
54
+ class SynReactor(ReactorMatchingMixin, ReactorStereoMixin):
55
+ """A hardened and typed re-write of the original SynReactor, preserving API
56
+ compatibility while offering safer, faster, and cleaner behavior.
57
+
58
+ :param substrate: The input reaction substrate, as a SMILES string,
59
+ a raw NetworkX graph, or a SynGraph.
60
+ :type substrate: Union[str, nx.Graph, SynGraph]
61
+ :param template: Reaction template, provided as SMILES/SMARTS, a raw
62
+ NetworkX graph, or a SynRule.
63
+ :type template: Union[str, nx.Graph, SynRule]
64
+ :param invert: Whether to invert the reaction (predict precursors).
65
+ Defaults to False.
66
+ :type invert: bool
67
+ :param canonicaliser: Optional canonicaliser for intermediate
68
+ graphs. If None, a default GraphCanonicaliser is used.
69
+ :type canonicaliser: Optional[GraphCanonicaliser]
70
+ :param explicit_h: If True, render all hydrogens explicitly in the
71
+ reaction-center SMARTS. Defaults to True.
72
+ :type explicit_h: bool
73
+ :param implicit_temp: If True, treat the input template as
74
+ implicit-H (forces explicit_h=False). Defaults to False.
75
+ :type implicit_temp: bool
76
+ :param strategy: Matching strategy, one of Strategy.ALL, 'comp', or
77
+ 'bt'. Defaults to Strategy.ALL.
78
+ :type strategy: Strategy or str
79
+ :param partial: If True, use a partial matching fallback. Defaults
80
+ to False.
81
+ :type partial: bool
82
+ :param template_format: ITS representation used when ``template`` is a
83
+ reaction string. Defaults to ``"typesGH"`` for compatibility.
84
+ :type template_format: ITSFormat
85
+ :param electron_diagnostics: If True, expose per-result electron-accounting
86
+ diagnostics without changing generated products.
87
+ :type electron_diagnostics: bool
88
+ :param radical_policy: ``"strict"`` requires equal radical counts,
89
+ ``"lower_bound"`` permits extra host radical resource for generalized
90
+ templates, and ``"ignore"`` preserves legacy matching behavior.
91
+ :type radical_policy: str
92
+ :param stereo_mode: Stereo application policy. ``"propagate"`` (default)
93
+ applies relative rule effects to the orientation actually present in
94
+ the substrate. ``"require"`` checks every exact rule guard, while
95
+ ``"strict"`` additionally rejects undeclared host descriptors in the
96
+ mapped dependency scope. ``"ignore"`` preserves fixed-template legacy
97
+ behaviour without filtering structural mappings.
98
+ :type stereo_mode: str
99
+ :param stereo_query_mode: Meaning of an unknown-parity rule descriptor.
100
+ ``"exact"`` matches only unknown host stereo; ``"wildcard"`` matches
101
+ either orientation. Per-descriptor rule policies override this value.
102
+ :type stereo_query_mode: str
103
+ :param stereo_semantics: Reaction stereo execution mode: authoritative
104
+ ``"orbit"`` (default), frozen ``"legacy"``, or orbit-authoritative
105
+ ``"compare"`` with structured diagnostics.
106
+ :type stereo_semantics: str
107
+ :param preserve_mapped_hydrogens: Preserve positive explicit-H atom maps
108
+ through implicit matching. This is intended for mapped mechanism/rule
109
+ agreement; the default keeps legacy implicit-output projection.
110
+ :type preserve_mapped_hydrogens: bool
111
+ :param dedup_its: If True, consolidate equivalent post-rewrite ITS graphs.
112
+ If False, retain deterministic mapping and stereo-branch multiplicity
113
+ while still finalizing electron fields and validating stereo state.
114
+ This policy is independent of mapping-level ``automorphism`` pruning.
115
+ :type dedup_its: bool
116
+ :param product_deduplication: Post-rewrite quotient policy when
117
+ ``dedup_its`` is true. ``"structural"`` keeps the exact internal
118
+ application and ITS quotients. ``"deferred"`` finalizes every product
119
+ state but leaves application/product endpoint identity to downstream
120
+ serialized-output canonicalization; it is restricted to
121
+ ``stereo_mode="ignore"``.
122
+ :type product_deduplication: str
123
+ :param serialization_errors: Raw ITS serialization policy. ``"raise"``
124
+ preserves the compatibility behavior; ``"skip"`` returns all
125
+ serializable results in their original order and reports omitted raw
126
+ application indices.
127
+ :type serialization_errors: str
128
+ :param stereo_assignment_limit: Optional hard cap on admissible injective
129
+ typed stereo-port assignments. Exceeding it raises instead of silently
130
+ truncating the search.
131
+ :type stereo_assignment_limit: Optional[int]
132
+ :param stereo_branch_limit: Optional hard cap on stereo product branches
133
+ across all accepted applications. Exceeding it raises a typed error
134
+ before a partial result can be returned.
135
+ :type stereo_branch_limit: Optional[int]
136
+ :ivar _graph: Cached SynGraph for the substrate.
137
+ :vartype _graph: Optional[SynGraph]
138
+ :ivar _rule: Cached SynRule for the template.
139
+ :vartype _rule: Optional[SynRule]
140
+ :ivar _mappings: Cached list of subgraph-mapping dicts.
141
+ :vartype _mappings: Optional[List[MappingDict]]
142
+ :ivar _its: Cached list of ITS graphs.
143
+ :vartype _its: Optional[List[nx.Graph]]
144
+ :ivar _smarts: Cached list of SMARTS strings.
145
+ :vartype _smarts: Optional[List[str]]
146
+ :ivar _flag_pattern_has_explicit_H: Internal flag indicating
147
+ explicit-H constraints.
148
+ :vartype _flag_pattern_has_explicit_H: bool
149
+ """
150
+
151
+ substrate: Union[str, nx.Graph, SynGraph]
152
+ template: Union[str, nx.Graph, SynRule]
153
+ invert: bool = False
154
+ canonicaliser: GraphCanonicaliser | None = None
155
+ explicit_h: bool = True
156
+ implicit_temp: bool = False
157
+ strategy: Strategy | str = Strategy.ALL
158
+ partial: bool = False
159
+ template_format: ITSFormat = "typesGH"
160
+ electron_diagnostics: bool = False
161
+ radical_policy: str = "strict"
162
+ stereo_mode: str = "propagate"
163
+ stereo_query_mode: str = "exact"
164
+ stereo_semantics: str = "orbit"
165
+ embed_threshold: Optional[int] = None
166
+ embed_pre_filter: bool = False
167
+ automorphism: bool = True
168
+ preserve_mapped_hydrogens: bool = False
169
+ dedup_its: bool = True
170
+ product_deduplication: str = "structural"
171
+ stereo_assignment_limit: int | None = None
172
+ stereo_branch_limit: int | None = None
173
+ serialization_errors: str = "raise"
174
+
175
+ # Private caches – populated on demand -------------------------------
176
+ _graph: SynGraph | None = field(init=False, default=None, repr=False)
177
+ _rule: SynRule | None = field(init=False, default=None, repr=False)
178
+ _mappings: List[MappingDict] | None = field(init=False, default=None, repr=False)
179
+ _its: List[nx.Graph] | None = field(init=False, default=None, repr=False)
180
+ _smarts: List[str] | None = field(init=False, default=None, repr=False)
181
+ _serialization_failure_indices: Tuple[int, ...] = field(
182
+ init=False,
183
+ default=(),
184
+ repr=False,
185
+ )
186
+ _host_for_matching: nx.Graph | None = field(init=False, default=None, repr=False)
187
+ _stereo_semantic_diagnostics: List[Any] = field(
188
+ init=False,
189
+ default_factory=list,
190
+ repr=False,
191
+ )
192
+ _stereo_morphism_issues: List[Any] = field(
193
+ init=False,
194
+ default_factory=list,
195
+ repr=False,
196
+ )
197
+ _flag_pattern_has_explicit_H: bool = field(init=False, default=False, repr=False)
198
+ _stereo_branch_count: int = field(init=False, default=0, repr=False)
199
+
200
+ def __post_init__(self) -> None:
201
+ """Validate and enforce consistency of `explicit_h` and
202
+ `implicit_temp`.
203
+
204
+ :raises ValueError: If `explicit_h` is True while `implicit_temp` is False.
205
+ """
206
+ if self.implicit_temp and self.explicit_h:
207
+ raise ValueError(
208
+ "`explicit_h` cannot be True when `implicit_temp` is False."
209
+ )
210
+ if self.radical_policy not in {"strict", "lower_bound", "ignore"}:
211
+ raise ValueError(
212
+ "radical_policy must be 'strict', 'lower_bound', or 'ignore'."
213
+ )
214
+ if self.stereo_mode not in {"ignore", "require", "propagate", "strict"}:
215
+ raise ValueError(
216
+ "stereo_mode must be 'ignore', 'require', 'propagate', or 'strict'."
217
+ )
218
+ if self.stereo_query_mode not in {"exact", "wildcard"}:
219
+ raise ValueError("stereo_query_mode must be 'exact' or 'wildcard'.")
220
+ if self.stereo_semantics not in {"orbit", "legacy", "compare"}:
221
+ raise ValueError(
222
+ "stereo_semantics must be 'orbit', 'legacy', or 'compare'."
223
+ )
224
+ if not isinstance(self.dedup_its, bool):
225
+ raise TypeError("dedup_its must be a bool.")
226
+ if self.product_deduplication not in {"structural", "deferred"}:
227
+ raise ValueError(
228
+ "product_deduplication must be 'structural' or 'deferred'."
229
+ )
230
+ if self.product_deduplication == "deferred" and self.stereo_mode != "ignore":
231
+ raise ValueError(
232
+ "deferred product deduplication requires stereo_mode='ignore'."
233
+ )
234
+ if self.serialization_errors not in {"raise", "skip"}:
235
+ raise ValueError("serialization_errors must be 'raise' or 'skip'.")
236
+ if self.stereo_assignment_limit is not None and (
237
+ type(self.stereo_assignment_limit) is not int
238
+ or self.stereo_assignment_limit < 1
239
+ ):
240
+ raise ValueError("stereo_assignment_limit must be a positive integer.")
241
+ if self.stereo_branch_limit is not None and (
242
+ type(self.stereo_branch_limit) is not int or self.stereo_branch_limit < 1
243
+ ):
244
+ raise ValueError("stereo_branch_limit must be a positive integer.")
245
+
246
+ # ------------------------------------------------------------------
247
+ # Construction helpers
248
+ # ------------------------------------------------------------------
249
+ @classmethod
250
+ def from_smiles(
251
+ cls,
252
+ smiles: str,
253
+ template: Union[str, nx.Graph, SynRule],
254
+ *,
255
+ invert: bool = False,
256
+ canonicaliser: Optional[GraphCanonicaliser] = None,
257
+ explicit_h: bool = True,
258
+ implicit_temp: bool = False,
259
+ automorphism: bool = False,
260
+ strategy: Strategy | str = Strategy.ALL,
261
+ template_format: ITSFormat = "typesGH",
262
+ electron_diagnostics: bool = False,
263
+ radical_policy: str = "strict",
264
+ stereo_mode: str = "propagate",
265
+ stereo_query_mode: str = "exact",
266
+ stereo_semantics: str = "orbit",
267
+ preserve_mapped_hydrogens: bool = False,
268
+ dedup_its: bool = True,
269
+ product_deduplication: str = "structural",
270
+ serialization_errors: str = "raise",
271
+ stereo_assignment_limit: int | None = None,
272
+ stereo_branch_limit: int | None = None,
273
+ ) -> "SynReactor":
274
+ """Alternate constructor: build a SynReactor directly from SMILES.
275
+
276
+ :param smiles: SMILES string for the substrate.
277
+ :type smiles: str
278
+ :param template: Reaction template (SMILES/SMARTS string, Graph, or SynRule).
279
+ :type template: str or networkx.Graph or SynRule
280
+ :param invert: If True, perform backward prediction (target→precursors).
281
+ Defaults to False (forward prediction).
282
+ :type invert: bool
283
+ :param canonicaliser: Optional GraphCanonicaliser to use for internal graphs.
284
+ :type canonicaliser: GraphCanonicaliser or None
285
+ :param explicit_h: If True, keep explicit hydrogens in the reaction center.
286
+ :type explicit_h: bool
287
+ :param implicit_temp: If True, treat the template as implicit-H (forces explicit_h=False).
288
+ :type implicit_temp: bool
289
+ :param strategy: Matching strategy: ALL, 'comp', or 'bt'. Defaults to ALL.
290
+ :type strategy: Strategy or str
291
+ :param template_format: ITS representation used when ``template`` is a
292
+ reaction string. Defaults to ``"typesGH"``.
293
+ :type template_format: ITSFormat
294
+ :param electron_diagnostics: If True, expose per-result electron
295
+ diagnostics without changing products.
296
+ :type electron_diagnostics: bool
297
+ :param radical_policy: Radical matching policy: ``"strict"``,
298
+ ``"lower_bound"``, or ``"ignore"``.
299
+ :type radical_policy: str
300
+ :param stereo_mode: Stereo application policy: chemical-relative
301
+ ``"propagate"`` (default), fixed-template ``"ignore"``, or the
302
+ stereoisomer-selective ``"require"`` / ``"strict"`` modes.
303
+ :type stereo_mode: str
304
+ :param stereo_query_mode: Unknown-parity query policy, either
305
+ ``"exact"`` or ``"wildcard"``.
306
+ :type stereo_query_mode: str
307
+ :param stereo_semantics: ``"orbit"``, ``"legacy"``, or ``"compare"``
308
+ reaction-stereo execution.
309
+ :type stereo_semantics: str
310
+ :param dedup_its: Consolidate equivalent post-rewrite ITS graphs while
311
+ preserving all correctness finalization and validation steps.
312
+ :type dedup_its: bool
313
+ :param product_deduplication: ``"structural"`` performs exact internal
314
+ application and ITS quotienting. ``"deferred"`` leaves endpoint
315
+ identity to downstream canonicalized serialization and requires
316
+ ``stereo_mode="ignore"``.
317
+ :type product_deduplication: str
318
+ :param serialization_errors: Raw ITS serialization policy, either
319
+ ``"raise"`` (default) or ``"skip"``.
320
+ :type serialization_errors: str
321
+ :param stereo_assignment_limit: Optional hard cap on exhaustive typed
322
+ stereo-port assignments.
323
+ :type stereo_assignment_limit: Optional[int]
324
+ :param stereo_branch_limit: Optional hard cap on generated stereo
325
+ product branches.
326
+ :type stereo_branch_limit: Optional[int]
327
+ :return: A new `SynReactor` instance.
328
+ :rtype: SynReactor
329
+ """
330
+ return cls(
331
+ substrate=smiles,
332
+ template=template,
333
+ invert=invert,
334
+ canonicaliser=canonicaliser,
335
+ explicit_h=explicit_h,
336
+ implicit_temp=implicit_temp,
337
+ strategy=strategy,
338
+ automorphism=automorphism,
339
+ template_format=template_format,
340
+ electron_diagnostics=electron_diagnostics,
341
+ radical_policy=radical_policy,
342
+ stereo_mode=stereo_mode,
343
+ stereo_query_mode=stereo_query_mode,
344
+ stereo_semantics=stereo_semantics,
345
+ preserve_mapped_hydrogens=preserve_mapped_hydrogens,
346
+ dedup_its=dedup_its,
347
+ product_deduplication=product_deduplication,
348
+ serialization_errors=serialization_errors,
349
+ stereo_assignment_limit=stereo_assignment_limit,
350
+ stereo_branch_limit=stereo_branch_limit,
351
+ )
352
+
353
+ # ------------------------------------------------------------------
354
+ # Public read‑only properties (lazily computed) ----------------------
355
+ # ------------------------------------------------------------------
356
+ @property
357
+ def stereo_semantic_diagnostics(self) -> tuple[Any, ...]:
358
+ """Return accumulated orbit/legacy mapping and application audits."""
359
+ return tuple(self._stereo_semantic_diagnostics)
360
+
361
+ @property
362
+ def stereo_morphism_issues(self) -> tuple[Any, ...]:
363
+ """Return structured reasons rejected typed stereo mappings failed."""
364
+ return tuple(self._stereo_morphism_issues)
365
+
366
+ @property
367
+ def serialization_failure_indices(self) -> Tuple[int, ...]:
368
+ """Return raw ITS indices omitted or rejected during serialization."""
369
+ return self._serialization_failure_indices
370
+
371
+ @property
372
+ def graph(self) -> SynGraph: # noqa: D401 – read‑only property
373
+ """Lazily wrap the substrate into a SynGraph.
374
+ :return: The reaction substrate as a `SynGraph`.
375
+ :rtype: SynGraph
376
+ """
377
+ if self._graph is None:
378
+ self._graph = self._wrap_input(self.substrate)
379
+ return self._graph
380
+
381
+ @property
382
+ def rule(self) -> SynRule: # noqa: D401
383
+ """Lazily wrap the template into a SynRule.
384
+ :return: The reaction template as a `SynRule`.
385
+ :rtype: SynRule
386
+ """
387
+ if self._rule is None:
388
+ self._rule = self._wrap_template(self.template)
389
+ return self._rule
390
+
391
+ @property
392
+ def its_list(self) -> List[nx.Graph]:
393
+ """Build ITS graphs for exact rewrite representatives.
394
+ :return: A list of ITS (Internal Transition State) graphs.
395
+ """
396
+ if self._its is None:
397
+ host_raw = self._matching_host_graph()
398
+ rc_raw = self.rule.rc.raw
399
+ pattern_explicit = self.rule.left.raw
400
+ strategy = Strategy.from_string(self.strategy)
401
+ relative_pi_edges = self._relative_pi_rewrite_edges(rc_raw)
402
+ electron_aware = self._is_electron_aware_template(rc_raw)
403
+ stereo_substitutions = self._typed_stereo_wildcard_substitutions(
404
+ pattern_explicit
405
+ )
406
+ certified_generic = bool(
407
+ self.rule.rc.raw.graph.get("generic_stereo_extraction")
408
+ )
409
+ application_mappings = self.mappings
410
+ stereo_sensitive = bool(
411
+ self.rule.stereo_guards
412
+ or self.rule.stereo_effects
413
+ or self.rule.stereo_outcomes
414
+ or self.rule.stereo_couplings
415
+ )
416
+ exact_endpoint_certificate = (
417
+ self.dedup_its
418
+ and self.product_deduplication == "structural"
419
+ and not host_raw.is_directed()
420
+ and not host_raw.is_multigraph()
421
+ )
422
+ if (
423
+ self.dedup_its
424
+ and self.product_deduplication == "structural"
425
+ and not certified_generic
426
+ and not stereo_sensitive
427
+ and not exact_endpoint_certificate
428
+ ):
429
+ mapping_population = len(application_mappings)
430
+ application_mappings = self._deduplicate_rewrite_equivalent_mappings(
431
+ application_mappings,
432
+ host_raw,
433
+ rc_raw,
434
+ )
435
+ log.debug(
436
+ "Rewrite-equivalence quotient: %d -> %d application(s)",
437
+ mapping_population,
438
+ len(application_mappings),
439
+ )
440
+ elif exact_endpoint_certificate and not certified_generic:
441
+ # Complete mapping enumeration followed by an injective
442
+ # canonical certificate is exact and cheaper here than an
443
+ # additional sufficient-but-not-necessary source quotient.
444
+ # The finalized endpoint certificate remains the only merge
445
+ # authority, so no application is discarded heuristically.
446
+ log.debug(
447
+ "Retained %d application(s) for exact endpoint certificates",
448
+ len(application_mappings),
449
+ )
450
+ rewrite_host, rewrite_host_prepared = (
451
+ _graph_rewrite._prepare_rewrite_batch_host(
452
+ host_raw,
453
+ application_mappings,
454
+ rc_raw,
455
+ self._flag_pattern_has_explicit_H,
456
+ electron_aware,
457
+ seed_structural_signatures=(
458
+ self.dedup_its and self.product_deduplication == "structural"
459
+ ),
460
+ )
461
+ )
462
+ self._its = []
463
+ expanded_host_cache: Dict[frozenset[Any], nx.Graph] = {}
464
+ self._stereo_branch_count = 0
465
+ raw_application_index = 0
466
+ for mapping_index, m in enumerate(application_mappings):
467
+ retain_provenance = not self.dedup_its or certified_generic
468
+ mapping_provenance = (
469
+ self._mapping_provenance(m, pattern_explicit, host_raw)
470
+ if retain_provenance
471
+ else ()
472
+ )
473
+ port_assignment = tuple(
474
+ pair
475
+ for pair in mapping_provenance
476
+ if pair[0]
477
+ in {
478
+ pattern_explicit.nodes[node].get("atom_map", node)
479
+ for node in stereo_substitutions
480
+ }
481
+ )
482
+ stereo_proof = (
483
+ self._stereo_application_provenance(
484
+ pattern_explicit,
485
+ host_raw,
486
+ m,
487
+ stereo_substitutions,
488
+ )
489
+ if certified_generic
490
+ else None
491
+ )
492
+ its_batch = _graph_rewrite._glue_graph(
493
+ rewrite_host,
494
+ rc_raw,
495
+ m,
496
+ self._flag_pattern_has_explicit_H,
497
+ pattern_explicit,
498
+ strategy,
499
+ embed_threshold=self.embed_threshold,
500
+ embed_pre_filter=False,
501
+ relative_pi_edges=relative_pi_edges,
502
+ restore_unmatched_explicit_h=not self.explicit_h,
503
+ refresh_electrons=False,
504
+ electron_aware=electron_aware,
505
+ relative_match_resources=(
506
+ frozenset({"radical"})
507
+ if self.radical_policy == "lower_bound"
508
+ else frozenset()
509
+ ),
510
+ anchor_explicit_embedding=self.dedup_its,
511
+ host_prepared=rewrite_host_prepared,
512
+ expanded_host_cache=expanded_host_cache,
513
+ consume_prepared_host=(
514
+ rewrite_host_prepared
515
+ and mapping_index == len(application_mappings) - 1
516
+ ),
517
+ )
518
+ stereo_batch: List[nx.Graph] = []
519
+ for rewrite_index, candidate in enumerate(its_batch):
520
+ branches = self._apply_stereo_rule_metadata(candidate, host_raw, m)
521
+ if retain_provenance:
522
+ for stereo_branch_index, branch in enumerate(branches):
523
+ provenance = {
524
+ "application_index": raw_application_index,
525
+ "mapping_index": mapping_index,
526
+ "rewrite_index": rewrite_index,
527
+ "stereo_branch_index": stereo_branch_index,
528
+ "mapping": mapping_provenance,
529
+ "port_assignment": port_assignment,
530
+ }
531
+ if stereo_proof is not None:
532
+ provenance["stereo_morphism"] = stereo_proof
533
+ branch.graph["application_provenance"] = provenance
534
+ if certified_generic:
535
+ branch.graph["application_orbit"] = {
536
+ "multiplicity": 1,
537
+ "applications": [provenance],
538
+ }
539
+ raw_application_index += 1
540
+ stereo_batch.extend(branches)
541
+ self._its.extend(stereo_batch)
542
+ if self.explicit_h:
543
+ self._its = [self._explicit_h(g) for g in self._its]
544
+ self._its = [
545
+ its for its in self._its if self._validate_product_stereo_registry(its)
546
+ ]
547
+ if self.dedup_its:
548
+ if self.product_deduplication == "structural":
549
+ self._its = self._deduplicate_coupling_face_products(self._its)
550
+ self._its = self._deduplicate_structural_its(self._its)
551
+ else:
552
+ self._its = self._finalize_product_electron_fields(self._its)
553
+ else:
554
+ self._its = self._finalize_product_electron_fields(self._its)
555
+ log.debug("Built %d ITS graph(s)", len(self._its))
556
+ return self._its
557
+
558
+ @property
559
+ def smarts_list(self) -> List[str]:
560
+ """Serialise each ITS graph to a reaction-SMARTS string.
561
+ :return: A list of SMARTS strings (inverted if `invert=True`).
562
+ """
563
+ if self._smarts is None:
564
+ serializer_parameters = inspect.signature(self._to_smarts).parameters
565
+ cache_supported = {
566
+ "reactant_smiles",
567
+ "preserved_hydrogen_maps",
568
+ }.issubset(serializer_parameters)
569
+ reactant_cache: Dict[Tuple[int, ...], str] = {}
570
+ serialized = []
571
+ for graph in self.its_list:
572
+ if (
573
+ not cache_supported
574
+ or self._flag_pattern_has_explicit_H
575
+ or not graph.graph.get("electron_aware_rewrite", False)
576
+ ):
577
+ serialized.append(self._to_smarts(graph))
578
+ continue
579
+ preserved = tuple(self._tuple_preserved_hydrogen_maps(graph))
580
+ cached_reactant = reactant_cache.get(preserved)
581
+ value = self._to_smarts(
582
+ graph,
583
+ reactant_smiles=cached_reactant,
584
+ preserved_hydrogen_maps=preserved,
585
+ )
586
+ serialized.append(value)
587
+ if value is not None and cached_reactant is None:
588
+ reactant_cache[preserved] = value.partition(">>")[0]
589
+ failed = tuple(
590
+ index for index, value in enumerate(serialized) if value is None
591
+ )
592
+ self._serialization_failure_indices = failed if not self.dedup_its else ()
593
+ if not self.dedup_its and failed:
594
+ if self.serialization_errors == "raise":
595
+ raise ValueError(
596
+ "Could not serialize raw ITS application(s): "
597
+ + ", ".join(map(str, failed))
598
+ )
599
+ warnings.warn(
600
+ RawITSApplicationSerializationWarning(failed),
601
+ stacklevel=2,
602
+ )
603
+ smarts = [value for value in serialized if value]
604
+ if self.invert:
605
+ smarts = [reverse_reaction(rsmi) for rsmi in smarts]
606
+ if self.dedup_its:
607
+ smarts = list(dict.fromkeys(smarts))
608
+ self._smarts = smarts
609
+ return self._smarts
610
+
611
+ @property
612
+ def diagnostics(self) -> List[Dict[str, Any]]:
613
+ """Return optional electron-accounting diagnostics for built ITS graphs."""
614
+ if not self.electron_diagnostics:
615
+ return []
616
+
617
+ reports: List[Dict[str, Any]] = []
618
+ for index, its in enumerate(self.its_list):
619
+ if its.graph.get("electron_aware_rewrite", False):
620
+ mismatches = {}
621
+ for node, attrs in its.nodes(data=True):
622
+ charge_mismatch = attrs.get("charge_mismatch")
623
+ mismatch = (
624
+ charge_mismatch[1]
625
+ if isinstance(charge_mismatch, tuple)
626
+ and len(charge_mismatch) == 2
627
+ else charge_mismatch
628
+ )
629
+ if mismatch:
630
+ template_charge = attrs.get("template_charge")
631
+ recomputed_charge = attrs.get("recomputed_charge")
632
+ mismatches[node] = {
633
+ "charge": (
634
+ template_charge[1]
635
+ if isinstance(template_charge, tuple)
636
+ and len(template_charge) == 2
637
+ else template_charge
638
+ ),
639
+ "recomputed_charge": (
640
+ recomputed_charge[1]
641
+ if isinstance(recomputed_charge, tuple)
642
+ and len(recomputed_charge) == 2
643
+ else recomputed_charge
644
+ ),
645
+ }
646
+ else:
647
+ product = self._product_graph_for_diagnostics(its)
648
+ refreshed = refresh_electron_fields(product)
649
+ mismatches = {
650
+ node: {
651
+ "charge": attrs.get("charge"),
652
+ "recomputed_charge": attrs.get("recomputed_charge"),
653
+ }
654
+ for node, attrs in refreshed.nodes(data=True)
655
+ if attrs.get("charge_mismatch")
656
+ }
657
+ reports.append(
658
+ {
659
+ "index": index,
660
+ "electron_aware_rewrite": bool(
661
+ its.graph.get("electron_aware_rewrite", False)
662
+ ),
663
+ "mismatch_count": len(mismatches),
664
+ "mismatches": mismatches,
665
+ }
666
+ )
667
+ return reports
668
+
669
+ # Backward‑compat aliases (original attribute names) ----------------
670
+ smarts = property(lambda self: self.smarts_list)
671
+ its = property(lambda self: self.its_list)
672
+ _mappings_prop = property(lambda self: self.mappings, doc="Alias for compatibility")
673
+
674
+ # Convenience re‑exports -------------------------------------------
675
+ mapping_count = property(lambda self: len(self.mappings), doc="Number of mappings")
676
+ smiles_list = property(lambda self: [s.split(">>")[-1] for s in self.smarts_list])
677
+ substrate_smiles = property(lambda self: graph_to_smi(self.graph.raw))
678
+
679
+ # ------------------------------------------------------------------
680
+ # String‑likes ------------------------------------------------------
681
+ # ------------------------------------------------------------------
682
+ def __str__(self) -> str: # pragma: no cover
683
+ return (
684
+ f"<SynReactor atoms={self.graph.raw.number_of_nodes()} "
685
+ f"mappings={self.mapping_count}>"
686
+ )
687
+
688
+ __repr__ = __str__
689
+
690
+ # ------------------------------------------------------------------
691
+ # Public helper -----------------------------------------------------
692
+ # ------------------------------------------------------------------
693
+ def help(
694
+ self, print_results=False
695
+ ) -> None: # pragma: no cover – human‑oriented output
696
+ print("SynReactor")
697
+ print(" Substrate :", self.substrate_smiles)
698
+ print(" Template :", self.rule)
699
+ print(" Implicit Template :", self.implicit_temp)
700
+ print(" Invert rule :", self.invert)
701
+ print(" Strategy :", Strategy.from_string(self.strategy).value)
702
+ print(" Predictions :", self.mapping_count)
703
+ if print_results:
704
+ for i, s in enumerate(self.smarts_list, 1):
705
+ print(f" SMARTS[{i:02d}] : {s}")
706
+ else:
707
+ print(f" First result : {self.smarts_list[0]}")
708
+
709
+ # ==================================================================
710
+ # Private – wrapping / canonicalising
711
+ # ==================================================================
712
+ def _wrap_input(self, obj: Union[str, nx.Graph, SynGraph]) -> SynGraph:
713
+ if isinstance(obj, SynGraph):
714
+ return obj
715
+ if isinstance(obj, nx.Graph):
716
+ return SynGraph(obj, self.canonicaliser or GraphCanonicaliser())
717
+ if isinstance(obj, str):
718
+ graph = smiles_to_graph(
719
+ obj, use_index_as_atom_map=False, drop_non_aam=False
720
+ )
721
+ return SynGraph(graph, self.canonicaliser or GraphCanonicaliser())
722
+ raise TypeError(f"Unsupported substrate type: {type(obj)}")
723
+
724
+ def _wrap_template(self, tpl: Union[str, nx.Graph, SynRule]) -> SynRule:
725
+ # Return early when incoming SynRule matches desired orientation
726
+ if not self.invert and isinstance(tpl, SynRule):
727
+ return tpl
728
+
729
+ # Convert to raw graph ------------------------------------------------
730
+ if isinstance(tpl, SynRule):
731
+ graph = tpl.rc.raw # raw reaction‑core graph
732
+ elif isinstance(tpl, nx.Graph):
733
+ graph = tpl
734
+ elif isinstance(tpl, str):
735
+ graph = rsmi_to_its(tpl, format=self.template_format)
736
+ else: # pragma: no cover
737
+ raise TypeError(f"Unsupported template type: {type(tpl)}")
738
+ # graph = normalize_h_pair_graph(graph)
739
+
740
+ format = detect_its_format(graph)
741
+
742
+ # Invert if asked -----------------------------------------------------
743
+ if self.invert:
744
+ if isinstance(tpl, SynRule):
745
+ return tpl.reversed(
746
+ balance_its=self.implicit_temp,
747
+ semantics=self.stereo_semantics,
748
+ diagnostics=self._stereo_semantic_diagnostics,
749
+ )
750
+ rich_reverse = isinstance(tpl, nx.Graph) and any(
751
+ key in graph.graph
752
+ for key in (
753
+ "stereo_outcomes",
754
+ "stereo_couplings",
755
+ "stereo_query_policies",
756
+ "stereo_reverse_outcomes",
757
+ )
758
+ )
759
+ audited_reverse = self.stereo_semantics != "orbit" and bool(
760
+ graph.graph.get("stereo_changes")
761
+ )
762
+ if rich_reverse or audited_reverse:
763
+ return SynRule(
764
+ graph,
765
+ canonicaliser=self.canonicaliser or GraphCanonicaliser(),
766
+ format=format,
767
+ ).reversed(
768
+ balance_its=self.implicit_temp,
769
+ semantics=self.stereo_semantics,
770
+ diagnostics=self._stereo_semantic_diagnostics,
771
+ )
772
+ if self.implicit_temp:
773
+ graph = self._invert_template(
774
+ graph,
775
+ balance_its=True,
776
+ format=format,
777
+ )
778
+ return SynRule(
779
+ graph,
780
+ canonicaliser=self.canonicaliser or GraphCanonicaliser(),
781
+ format=format,
782
+ )
783
+ else:
784
+ graph = self._invert_template(
785
+ graph,
786
+ balance_its=False,
787
+ format=format,
788
+ )
789
+ return SynRule(
790
+ graph,
791
+ canonicaliser=self.canonicaliser or GraphCanonicaliser(),
792
+ format=format,
793
+ )
794
+ else:
795
+ if self.implicit_temp:
796
+ return SynRule(
797
+ graph,
798
+ canonicaliser=self.canonicaliser or GraphCanonicaliser(),
799
+ format=format,
800
+ )
801
+ return SynRule(
802
+ graph,
803
+ canonicaliser=self.canonicaliser or GraphCanonicaliser(),
804
+ format=format,
805
+ )
806
+
807
+ @staticmethod
808
+ def _implicit_heavy_hydrogens(
809
+ graph: nx.Graph,
810
+ *,
811
+ preserve_mapped_hydrogens: bool = False,
812
+ ) -> nx.Graph:
813
+ return _graph_rewrite._implicit_heavy_hydrogens(
814
+ graph,
815
+ preserve_mapped_hydrogens=preserve_mapped_hydrogens,
816
+ )
817
+
818
+ @staticmethod
819
+ def _invert_template(
820
+ tpl: nx.Graph,
821
+ balance_its: bool = True,
822
+ format: ITSFormat | None = None,
823
+ ) -> nx.Graph:
824
+ return _graph_rewrite._invert_template(tpl, balance_its, format)
825
+
826
+ @staticmethod
827
+ def _node_glue(
828
+ host_n: Dict[str, Any],
829
+ pat_n: Dict[str, Any],
830
+ key: str = "typesGH",
831
+ ) -> None:
832
+ _graph_rewrite._node_glue(host_n, pat_n, key)
833
+
834
+ @staticmethod
835
+ def _get_explicit_map(
836
+ host: nx.Graph,
837
+ mapping: MappingDict,
838
+ pattern_explicit: nx.Graph | None = None,
839
+ strategy: Strategy = Strategy.ALL,
840
+ embed_threshold: float = None,
841
+ embed_pre_filter: bool = False,
842
+ ):
843
+ return _graph_rewrite._get_explicit_map(
844
+ host,
845
+ mapping,
846
+ pattern_explicit,
847
+ strategy,
848
+ embed_threshold,
849
+ embed_pre_filter,
850
+ )
851
+
852
+ @staticmethod
853
+ def _mapping_atom_map_alignment(
854
+ pattern: nx.Graph,
855
+ host: nx.Graph,
856
+ mapping: MappingDict,
857
+ ) -> int:
858
+ return _graph_rewrite._mapping_atom_map_alignment(pattern, host, mapping)
859
+
860
+ @staticmethod
861
+ def _restore_unmatched_pattern_hydrogens(
862
+ graph: nx.Graph,
863
+ mapping: MappingDict,
864
+ ) -> None:
865
+ _graph_rewrite._restore_unmatched_pattern_hydrogens(graph, mapping)
866
+
867
+ @staticmethod
868
+ def _glue_graph(
869
+ host: nx.Graph,
870
+ rc: nx.Graph,
871
+ mapping: MappingDict,
872
+ pattern_has_explicit_H: bool = False,
873
+ pattern_explicit: nx.Graph | None = None,
874
+ strategy: Strategy = Strategy.ALL,
875
+ embed_threshold: float = None,
876
+ embed_pre_filter: bool = False,
877
+ relative_pi_edges: set[frozenset[Any]] | None = None,
878
+ restore_unmatched_explicit_h: bool = True,
879
+ refresh_electrons: bool = True,
880
+ electron_aware: bool | None = None,
881
+ relative_match_resources: frozenset[str] = frozenset(),
882
+ anchor_explicit_embedding: bool = True,
883
+ ) -> List[nx.Graph]:
884
+ return _graph_rewrite._glue_graph(
885
+ host,
886
+ rc,
887
+ mapping,
888
+ pattern_has_explicit_H,
889
+ pattern_explicit,
890
+ strategy,
891
+ embed_threshold,
892
+ embed_pre_filter,
893
+ relative_pi_edges,
894
+ restore_unmatched_explicit_h,
895
+ refresh_electrons,
896
+ electron_aware,
897
+ relative_match_resources,
898
+ anchor_explicit_embedding,
899
+ )
900
+
901
+ _is_electron_aware_template = staticmethod(
902
+ _graph_rewrite._is_electron_aware_template
903
+ )
904
+ _merge_application_orbits = staticmethod(_deduplication._merge_application_orbits)
905
+ _chemical_rewrite_role = staticmethod(_deduplication._chemical_rewrite_role)
906
+ _prepare_its_for_structural_cluster = staticmethod(
907
+ _structural_deduplication._prepare_its_for_structural_cluster
908
+ )
909
+ _cluster_structural_its = staticmethod(
910
+ _structural_deduplication._cluster_structural_its
911
+ )
912
+ _finalize_product_electron_fields = staticmethod(
913
+ _deduplication._finalize_product_electron_fields
914
+ )
915
+ _deduplicate_structural_its = staticmethod(
916
+ _deduplication._deduplicate_structural_its
917
+ )
918
+ _deduplicate_coupling_face_products = staticmethod(
919
+ _deduplication._deduplicate_coupling_face_products
920
+ )
921
+ _deduplicate_rewrite_equivalent_mappings = staticmethod(
922
+ _deduplication._deduplicate_rewrite_equivalent_mappings
923
+ )
924
+ _components_are_equivalent = staticmethod(_deduplication._components_are_equivalent)
925
+ _pair_electron_aware_node_attrs = staticmethod(
926
+ _product_state._pair_electron_aware_node_attrs
927
+ )
928
+ _ensure_host_atom_maps = staticmethod(_product_state._ensure_host_atom_maps)
929
+ _refresh_product_electron_fields = staticmethod(
930
+ _product_state._refresh_product_electron_fields
931
+ )
932
+ _refresh_product_electron_fields_direct = staticmethod(
933
+ _product_state._refresh_product_electron_fields_direct
934
+ )
935
+ _product_kekule_phase_is_dirty = staticmethod(
936
+ _product_state._product_kekule_phase_is_dirty
937
+ )
938
+ _prepared_electron_product_graph = staticmethod(
939
+ _product_state._prepared_electron_product_graph
940
+ )
941
+ _electron_product_charge = staticmethod(_product_state._electron_product_charge)
942
+ _reperceive_product_kekule_phase = staticmethod(
943
+ _product_state._reperceive_product_kekule_phase
944
+ )
945
+ _product_graph_for_diagnostics = staticmethod(
946
+ _product_state._product_graph_for_diagnostics
947
+ )
948
+ _explicit_h = staticmethod(_serialization._explicit_h)
949
+ _explicit_h_tuple = staticmethod(_serialization._explicit_h_tuple)
950
+ _ensure_tuple_atom_maps = staticmethod(_serialization._ensure_tuple_atom_maps)
951
+ _tuple_preserved_hydrogen_maps = staticmethod(
952
+ _serialization._tuple_preserved_hydrogen_maps
953
+ )
954
+ _tuple_endpoint_graphs = staticmethod(_serialization._tuple_endpoint_graphs)
955
+
956
+ @staticmethod
957
+ def _to_smarts(
958
+ its: nx.Graph,
959
+ *,
960
+ reactant_smiles: Optional[str] = None,
961
+ preserved_hydrogen_maps: Optional[Tuple[int, ...]] = None,
962
+ ) -> str:
963
+ return _serialization._to_smarts(
964
+ its,
965
+ reactant_smiles=reactant_smiles,
966
+ preserved_hydrogen_maps=preserved_hydrogen_maps,
967
+ )