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,780 @@
1
+ """Stereo propagation and application behavior for SynReactor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from copy import deepcopy
6
+ from typing import Any, Dict, List, Mapping, Tuple
7
+
8
+ import networkx as nx
9
+ from rdkit import Chem
10
+
11
+ from synkit.Graph.ITS.its_reverter import ITSReverter
12
+ from synkit.IO.graph_to_mol import GraphToMol
13
+
14
+ MappingDict = Dict[Any, Any]
15
+
16
+
17
+ class ReactorStereoMixin:
18
+ """Internal stereo behavior shared by the SynReactor facade."""
19
+
20
+ def _check_stereo_branch_budget(self, branch_count: int) -> None:
21
+ """Refuse an expansion before it can exceed the configured budget."""
22
+ limit = self.stereo_branch_limit
23
+ requested = self._stereo_branch_count + branch_count
24
+ if limit is not None and requested > limit:
25
+ from .assignment import StereoBranchLimitError
26
+
27
+ raise StereoBranchLimitError(limit, requested)
28
+
29
+ def _matching_host_graph(self) -> nx.Graph:
30
+ """Return the host graph normalized to the active rule representation."""
31
+ if self._host_for_matching is None:
32
+ host = self.graph.raw
33
+ if getattr(self.rule, "_format", None) == "tuple":
34
+ host = self._implicit_heavy_hydrogens(
35
+ host,
36
+ preserve_mapped_hydrogens=self.preserve_mapped_hydrogens,
37
+ )
38
+ self._host_for_matching = host
39
+ return self._host_for_matching
40
+
41
+ def _noncovariant_propagation_targets(self) -> set[str]:
42
+ """Return guards whose coupled product stereo cannot be inferred.
43
+
44
+ One same-locus endpoint change can be mirrored directly. A destroyed
45
+ descriptor with no other stereo output is also safe. If other product
46
+ descriptors are formed, however, their correlation to an inverse
47
+ input is not encoded by independent ``StereoChange`` objects; such a
48
+ rule must match its reference geometry or provide a companion rule.
49
+ """
50
+ from synkit.Graph.Stereo import descriptor_id
51
+
52
+ exact_targets = set()
53
+ for target, guard in self.rule.stereo_guards.items():
54
+ effect = self.rule.stereo_effects.get(target)
55
+ other_outputs = any(
56
+ key != target and change.after is not None
57
+ for key, change in self.rule.stereo_effects.items()
58
+ )
59
+ same_locus_output = (
60
+ effect is not None
61
+ and effect.after is not None
62
+ and type(guard) is type(effect.after)
63
+ and descriptor_id(effect.after) == target
64
+ )
65
+ safe_destruction = (
66
+ effect is not None and effect.after is None and not other_outputs
67
+ )
68
+ if not same_locus_output and not safe_destruction:
69
+ exact_targets.add(target)
70
+ elif other_outputs:
71
+ exact_targets.add(target)
72
+ return exact_targets
73
+
74
+ @staticmethod
75
+ def _unknown_stereo_orientation(descriptor: Any) -> Any:
76
+ """Keep a descriptor locus/reference frame but remove its orientation."""
77
+ return type(descriptor)(
78
+ descriptor.atoms,
79
+ None,
80
+ descriptor.provenance,
81
+ )
82
+
83
+ def _propagated_stereo_effect(
84
+ self,
85
+ change: Any,
86
+ reactant_registry: Mapping[str, Any],
87
+ host: nx.Graph,
88
+ ) -> tuple[Any, Any]:
89
+ """Apply an aligned reaction relation to the matched substrate frame.
90
+
91
+ The rule endpoint is only a reference frame. In chemical ``propagate``
92
+ mode the stored permutation witness acts on the concrete substrate
93
+ configuration after ligand replacement. Missing, unknown, or refused
94
+ alignment evidence never acquires arbitrary template orientation.
95
+ """
96
+ before, after = change.before, change.after
97
+ if self.stereo_mode != "propagate" or before is None:
98
+ return before, after
99
+
100
+ from synkit.Graph.Stereo import (
101
+ StereoAlignmentError,
102
+ StereoRelationKind,
103
+ descriptor_id,
104
+ normalize_hydrogen_references,
105
+ )
106
+
107
+ target = descriptor_id(before)
108
+ candidate = reactant_registry.get(target)
109
+ linked_output = (
110
+ after is not None
111
+ and type(before) is type(after)
112
+ and descriptor_id(after) == target
113
+ )
114
+
115
+ if candidate is None:
116
+ unknown_before = self._unknown_stereo_orientation(before)
117
+ unknown_after = (
118
+ self._unknown_stereo_orientation(after) if linked_output else after
119
+ )
120
+ return unknown_before, unknown_after
121
+
122
+ if candidate.parity is None:
123
+ try:
124
+ propagated = (
125
+ change.apply_to(
126
+ candidate,
127
+ semantics=self.stereo_semantics,
128
+ diagnostics=self._stereo_semantic_diagnostics,
129
+ )
130
+ if linked_output
131
+ else after
132
+ )
133
+ except StereoAlignmentError:
134
+ propagated = (
135
+ self._unknown_stereo_orientation(after) if linked_output else after
136
+ )
137
+ return candidate, propagated
138
+
139
+ expected = normalize_hydrogen_references(before, host)
140
+ actual = normalize_hydrogen_references(candidate, host)
141
+ relation = expected.relation_to(actual)
142
+ if relation.kind not in {
143
+ StereoRelationKind.UNRELATED,
144
+ StereoRelationKind.UNSPECIFIED,
145
+ }:
146
+ try:
147
+ return candidate, (
148
+ change.apply_to(
149
+ actual,
150
+ semantics=self.stereo_semantics,
151
+ diagnostics=self._stereo_semantic_diagnostics,
152
+ )
153
+ if linked_output
154
+ else after
155
+ )
156
+ except StereoAlignmentError:
157
+ pass
158
+
159
+ unknown_after = (
160
+ self._unknown_stereo_orientation(after) if linked_output else after
161
+ )
162
+ return candidate, unknown_after
163
+
164
+ @staticmethod
165
+ def _node_by_atom_map(graph: nx.Graph) -> Dict[int, Any]:
166
+ """Return product nodes keyed by the application-local atom map."""
167
+ result = {}
168
+ for node, attrs in graph.nodes(data=True):
169
+ atom_map = attrs.get("atom_map") or node
170
+ if isinstance(atom_map, int) and atom_map > 0:
171
+ result[atom_map] = node
172
+ return result
173
+
174
+ @staticmethod
175
+ def _node_reference(graph: nx.Graph, node: Any) -> int | None:
176
+ """Return the stable mapped reference for one product node."""
177
+ atom_map = graph.nodes[node].get("atom_map") or node
178
+ return atom_map if isinstance(atom_map, int) and atom_map > 0 else None
179
+
180
+ def _relabeled_stereo_couplings(
181
+ self,
182
+ host: nx.Graph,
183
+ mapping: MappingDict,
184
+ ) -> tuple[Any, ...]:
185
+ """Translate every rule coupling into the selected host mapping."""
186
+ translation: Dict[int, int] = {}
187
+ pattern = self.rule.left.raw
188
+ for pattern_node, host_node in mapping.items():
189
+ pattern_map = pattern.nodes[pattern_node].get("atom_map", pattern_node)
190
+ if isinstance(pattern_map, tuple) and len(pattern_map) == 2:
191
+ pattern_map = pattern_map[0]
192
+ host_map = host.nodes[host_node].get("atom_map") or host_node
193
+ if isinstance(pattern_map, int) and isinstance(host_map, int):
194
+ translation[pattern_map] = host_map
195
+ return tuple(
196
+ self.rule.stereo_couplings[key].relabel(translation)
197
+ for key in sorted(self.rule.stereo_couplings)
198
+ )
199
+
200
+ @staticmethod
201
+ def _coupling_planar_from_tetrahedral_input(
202
+ coupling: Any,
203
+ reactant_registry: Mapping[str, Any],
204
+ ) -> Any:
205
+ """Recover the unique correlated alkene frame for an elimination."""
206
+ from synkit.Graph.Stereo import PlanarBondStereo, TetrahedralStereo
207
+
208
+ left_center, right_center = coupling.centers
209
+ left_ligand, right_ligand = coupling.ligands
210
+ left = reactant_registry.get(f"atom:{left_center}")
211
+ right = reactant_registry.get(f"atom:{right_center}")
212
+ if (
213
+ not isinstance(left, TetrahedralStereo)
214
+ or not isinstance(right, TetrahedralStereo)
215
+ or left.parity not in {-1, 1}
216
+ or right.parity not in {-1, 1}
217
+ ):
218
+ return None
219
+
220
+ remaining = []
221
+ for descriptor, other, ligand in (
222
+ (left, right_center, left_ligand),
223
+ (right, left_center, right_ligand),
224
+ ):
225
+ references = [
226
+ reference
227
+ for reference in descriptor.atoms[1:]
228
+ if reference not in {other, ligand}
229
+ ]
230
+ if len(references) != 2:
231
+ return None
232
+ remaining.append(tuple(sorted(references, key=repr)))
233
+
234
+ candidates = (
235
+ PlanarBondStereo(
236
+ (
237
+ *remaining[0],
238
+ left_center,
239
+ right_center,
240
+ *remaining[1],
241
+ ),
242
+ 0,
243
+ "rule-coupling-reverse",
244
+ ),
245
+ PlanarBondStereo(
246
+ (
247
+ *remaining[0],
248
+ left_center,
249
+ right_center,
250
+ *reversed(remaining[1]),
251
+ ),
252
+ 0,
253
+ "rule-coupling-reverse",
254
+ ),
255
+ )
256
+ for candidate in candidates:
257
+ if any(
258
+ pair[0] == left and pair[1] == right
259
+ for pair in coupling.tetrahedral_product_pairs(candidate)
260
+ ):
261
+ return candidate
262
+ return None
263
+
264
+ def _coupling_mapping_inputs_match(
265
+ self,
266
+ host: nx.Graph,
267
+ mapping: MappingDict,
268
+ *,
269
+ strict: bool,
270
+ ) -> bool:
271
+ """Validate coupling input geometry and strict descriptor presence."""
272
+ from synkit.Graph.Stereo import PlanarBondStereo, descriptor_id
273
+
274
+ registry = dict(host.graph.get("stereo_descriptors", {}))
275
+ by_map = self._node_by_atom_map(host)
276
+ allowed_targets = set()
277
+ for coupling in self._relabeled_stereo_couplings(host, mapping):
278
+ if coupling.kind == "VICINAL_ADDITION":
279
+ descriptor = registry.get(coupling.target)
280
+ left = by_map.get(coupling.centers[0])
281
+ right = by_map.get(coupling.centers[1])
282
+ pi_order = (
283
+ float(host.edges[left, right].get("pi_order", 0.0))
284
+ if left is not None
285
+ and right is not None
286
+ and host.has_edge(left, right)
287
+ else 0.0
288
+ )
289
+ if pi_order < 2.0:
290
+ if (
291
+ not isinstance(descriptor, PlanarBondStereo)
292
+ or descriptor.parity != 0
293
+ ):
294
+ return False
295
+ allowed_targets.add(coupling.target)
296
+ else:
297
+ if (
298
+ self._coupling_planar_from_tetrahedral_input(
299
+ coupling,
300
+ registry,
301
+ )
302
+ is None
303
+ ):
304
+ return False
305
+ allowed_targets.update(f"atom:{center}" for center in coupling.centers)
306
+ if not strict:
307
+ return True
308
+
309
+ mapped_nodes = set(mapping.values())
310
+ mapped_references = {
311
+ host.nodes[node].get("atom_map") or node for node in mapped_nodes
312
+ }
313
+ scoped = {
314
+ descriptor_id(descriptor)
315
+ for descriptor in registry.values()
316
+ if descriptor.dependencies & mapped_references
317
+ }
318
+ translation = {}
319
+ pattern = self.rule.left.raw
320
+ for pattern_node, host_node in mapping.items():
321
+ pattern_map = pattern.nodes[pattern_node].get("atom_map", pattern_node)
322
+ if isinstance(pattern_map, tuple) and len(pattern_map) == 2:
323
+ pattern_map = pattern_map[0]
324
+ host_map = host.nodes[host_node].get("atom_map", host_node)
325
+ if isinstance(pattern_map, int) and isinstance(host_map, int):
326
+ translation[pattern_map] = host_map
327
+ translated_guards = {
328
+ descriptor_id(descriptor.relabel(translation))
329
+ for descriptor in self.rule.stereo_guards.values()
330
+ }
331
+ return scoped <= allowed_targets | translated_guards
332
+
333
+ def _coupled_planar_product(self, coupling: Any, product: nx.Graph) -> Any:
334
+ """Construct a formed alkene descriptor from a syn/anti coupling."""
335
+ by_map = self._node_by_atom_map(product)
336
+ left_center, right_center = coupling.centers
337
+ left_ligand, right_ligand = coupling.ligands
338
+ required = {left_center, right_center, left_ligand, right_ligand}
339
+ if not required <= set(by_map):
340
+ return None
341
+ left_node, right_node = by_map[left_center], by_map[right_center]
342
+ if not product.has_edge(left_node, right_node):
343
+ return None
344
+ edge = product.edges[left_node, right_node]
345
+ if float(edge.get("pi_order", 0.0)) < 1.0:
346
+ return None
347
+
348
+ references = []
349
+ for center, other, ligand in (
350
+ (left_center, right_center, left_ligand),
351
+ (right_center, left_center, right_ligand),
352
+ ):
353
+ center_node = by_map[center]
354
+ excluded = {by_map[other], by_map[ligand]}
355
+ peripheral = [
356
+ self._node_reference(product, neighbor)
357
+ for neighbor in product.neighbors(center_node)
358
+ if neighbor not in excluded
359
+ ]
360
+ peripheral = [value for value in peripheral if value is not None]
361
+ if len(peripheral) == 1:
362
+ references.append(peripheral[0])
363
+ elif not peripheral and product.nodes[center_node].get("hcount", 0) == 1:
364
+ references.append(f"@H:{center}")
365
+ else:
366
+ return None
367
+ return coupling.planar_product_descriptor(*references)
368
+
369
+ @staticmethod
370
+ def _potential_tetrahedral_atom_maps(product: nx.Graph) -> set[int] | None:
371
+ """Return constitutionally stereogenic tetrahedral product centers.
372
+
373
+ Coupling geometry must not create a descriptor when two ligands are
374
+ constitutionally identical, as in ordinary H2 addition to a CH=CH
375
+ alkene. ``None`` is a conservative reconstruction-failure sentinel:
376
+ callers then retain the rule-derived descriptors rather than losing a
377
+ potentially valid stereocenter.
378
+ """
379
+ probe = product.copy()
380
+ probe.graph["stereo_descriptors"] = {}
381
+ try:
382
+ molecule = GraphToMol().graph_to_mol(probe)
383
+ except Exception:
384
+ return None
385
+ atom_maps = {
386
+ atom.GetIdx(): atom.GetAtomMapNum() for atom in molecule.GetAtoms()
387
+ }
388
+ return {
389
+ atom_maps[stereo.centeredOn]
390
+ for stereo in Chem.FindPotentialStereo(molecule)
391
+ if stereo.type == Chem.StereoType.Atom_Tetrahedral
392
+ and atom_maps.get(stereo.centeredOn, 0) > 0
393
+ }
394
+
395
+ def _apply_stereo_couplings(
396
+ self,
397
+ states: List[Tuple[Dict, Dict, Dict, Dict, Dict]],
398
+ couplings: List[Any],
399
+ reactant_registry: Mapping[str, Any],
400
+ product_graph: nx.Graph,
401
+ ) -> List[Tuple[Dict, Dict, Dict, Dict, Dict]]:
402
+ """Derive coupled endpoint descriptors from chemical rule semantics."""
403
+ if not couplings:
404
+ return states
405
+
406
+ from synkit.Graph.Stereo import PlanarBondStereo, descriptor_id
407
+ from synkit.Graph.Stereo.changes import StereoChange
408
+
409
+ potential_tetrahedral = self._potential_tetrahedral_atom_maps(product_graph)
410
+ for coupling in couplings:
411
+ if coupling.kind == "VICINAL_ELIMINATION":
412
+ planar_product = self._coupling_planar_from_tetrahedral_input(
413
+ coupling,
414
+ reactant_registry,
415
+ )
416
+ if planar_product is None:
417
+ return []
418
+ left_target, right_target = (
419
+ f"atom:{center}" for center in coupling.centers
420
+ )
421
+ next_states = []
422
+ for (
423
+ product_registry,
424
+ relabeled_changes,
425
+ branch_metadata,
426
+ outcome_metadata,
427
+ coupling_branch_metadata,
428
+ ) in states:
429
+ registry = dict(product_registry)
430
+ changes = dict(relabeled_changes)
431
+ coupling_branches = dict(coupling_branch_metadata)
432
+ for target in (left_target, right_target):
433
+ descriptor = reactant_registry[target]
434
+ registry.pop(target, None)
435
+ changes[target] = StereoChange(
436
+ "BROKEN",
437
+ descriptor,
438
+ None,
439
+ )
440
+ registry[coupling.target] = planar_product
441
+ changes[coupling.target] = StereoChange(
442
+ "FORMED",
443
+ None,
444
+ planar_product,
445
+ )
446
+ coupling_branches[coupling.target] = {
447
+ "kind": coupling.kind,
448
+ "relation": coupling.relation,
449
+ "paired_input": True,
450
+ "joint_targets": [left_target, right_target],
451
+ }
452
+ next_states.append(
453
+ (
454
+ registry,
455
+ changes,
456
+ dict(branch_metadata),
457
+ dict(outcome_metadata),
458
+ coupling_branches,
459
+ )
460
+ )
461
+ states = next_states
462
+ self._check_stereo_branch_budget(len(states))
463
+ continue
464
+ planar_product = self._coupled_planar_product(coupling, product_graph)
465
+ planar_reactant = reactant_registry.get(coupling.target)
466
+ next_states = []
467
+ for (
468
+ product_registry,
469
+ relabeled_changes,
470
+ branch_metadata,
471
+ outcome_metadata,
472
+ coupling_branch_metadata,
473
+ ) in states:
474
+ if planar_product is not None:
475
+ registry = dict(product_registry)
476
+ changes = dict(relabeled_changes)
477
+ registry[coupling.target] = planar_product
478
+ changes[coupling.target] = StereoChange(
479
+ "FORMED", None, planar_product
480
+ )
481
+ next_states.append(
482
+ (
483
+ registry,
484
+ changes,
485
+ dict(branch_metadata),
486
+ dict(outcome_metadata),
487
+ dict(coupling_branch_metadata),
488
+ )
489
+ )
490
+ continue
491
+
492
+ if not isinstance(planar_reactant, PlanarBondStereo):
493
+ next_states.append(
494
+ (
495
+ product_registry,
496
+ relabeled_changes,
497
+ branch_metadata,
498
+ outcome_metadata,
499
+ coupling_branch_metadata,
500
+ )
501
+ )
502
+ continue
503
+
504
+ pairs = coupling.tetrahedral_product_pairs(planar_reactant)
505
+ if potential_tetrahedral is not None:
506
+ pairs = tuple(
507
+ tuple(
508
+ descriptor
509
+ for descriptor in pair
510
+ if descriptor.center in potential_tetrahedral
511
+ )
512
+ for pair in pairs
513
+ )
514
+ if not any(pairs):
515
+ registry = dict(product_registry)
516
+ changes = dict(relabeled_changes)
517
+ registry.pop(coupling.target, None)
518
+ changes[coupling.target] = StereoChange(
519
+ "BROKEN", planar_reactant, None
520
+ )
521
+ next_states.append(
522
+ (
523
+ registry,
524
+ changes,
525
+ dict(branch_metadata),
526
+ dict(outcome_metadata),
527
+ dict(coupling_branch_metadata),
528
+ )
529
+ )
530
+ continue
531
+
532
+ for face_index, pair in enumerate(pairs):
533
+ registry = dict(product_registry)
534
+ changes = dict(relabeled_changes)
535
+ coupling_branches = dict(coupling_branch_metadata)
536
+ registry.pop(coupling.target, None)
537
+ changes[coupling.target] = StereoChange(
538
+ "BROKEN", planar_reactant, None
539
+ )
540
+ for descriptor in pair:
541
+ target = descriptor_id(descriptor)
542
+ registry[target] = descriptor
543
+ changes[target] = StereoChange("FORMED", None, descriptor)
544
+ coupling_branches[coupling.target] = {
545
+ "kind": coupling.kind,
546
+ "relation": coupling.relation,
547
+ "face_branch": face_index,
548
+ "joint_targets": [
549
+ descriptor_id(descriptor) for descriptor in pair
550
+ ],
551
+ }
552
+ next_states.append(
553
+ (
554
+ registry,
555
+ changes,
556
+ dict(branch_metadata),
557
+ dict(outcome_metadata),
558
+ coupling_branches,
559
+ )
560
+ )
561
+ states = next_states
562
+ self._check_stereo_branch_budget(len(states))
563
+ return states
564
+
565
+ def _apply_stereo_rule_metadata(
566
+ self,
567
+ its: nx.Graph,
568
+ host: nx.Graph,
569
+ mapping: MappingDict,
570
+ ) -> List[nx.Graph]:
571
+ """Relabel rule stereo effects and expand declared product branches."""
572
+ if not self.rule.stereo_effects and not self.rule.stereo_couplings:
573
+ reactant_registry = dict(host.graph.get("stereo_descriptors", {}))
574
+ its.graph["stereo_descriptors"] = {
575
+ "reactant": dict(reactant_registry),
576
+ "product": dict(reactant_registry),
577
+ }
578
+ its.graph["stereo_changes"] = {}
579
+ its.graph["stereo_outcomes"] = {}
580
+ its.graph["stereo_couplings"] = {}
581
+ its.graph["stereo_coupling_branch"] = {}
582
+ its.graph["stereo_branch"] = {}
583
+ its.graph["stereo_branch_weight"] = 1.0
584
+ return [its]
585
+
586
+ from synkit.Graph.Stereo import descriptor_id
587
+ from synkit.Graph.Stereo.changes import StereoChange
588
+
589
+ translation: Dict[int, int] = {}
590
+ pattern = self.rule.left.raw
591
+ for pattern_node, host_node in mapping.items():
592
+ pattern_map = pattern.nodes[pattern_node].get("atom_map", pattern_node)
593
+ if isinstance(pattern_map, tuple) and len(pattern_map) == 2:
594
+ pattern_map = pattern_map[0]
595
+ host_map = host.nodes[host_node].get("atom_map") or host_node
596
+ if isinstance(pattern_map, int) and isinstance(host_map, int):
597
+ translation[pattern_map] = host_map
598
+
599
+ reactant_registry = dict(host.graph.get("stereo_descriptors", {}))
600
+ transition_registry = {
601
+ descriptor_id(
602
+ change.transition.relabel(translation)
603
+ ): change.transition.relabel(translation)
604
+ for change in self.rule.stereo_effects.values()
605
+ if change.transition is not None
606
+ }
607
+ relabeled_coupling_values = []
608
+ relabeled_couplings = {}
609
+ for coupling_key in sorted(self.rule.stereo_couplings):
610
+ coupling = self.rule.stereo_couplings[coupling_key]
611
+ relabeled = coupling.relabel(translation)
612
+ relabeled_coupling_values.append(relabeled)
613
+ relabeled_couplings[relabeled.target] = relabeled.to_dict()
614
+ states: List[
615
+ Tuple[
616
+ Dict[str, Any],
617
+ Dict[str, StereoChange],
618
+ Dict[str, Dict[str, Any]],
619
+ Dict[str, Dict[str, Any]],
620
+ Dict[str, Dict[str, Any]],
621
+ ]
622
+ ] = [(dict(reactant_registry), {}, {}, {}, {})]
623
+
624
+ for rule_key in sorted(self.rule.stereo_effects):
625
+ change = self.rule.stereo_effects[rule_key]
626
+ applied_effect = change.relabel(translation)
627
+ before = applied_effect.before
628
+ after = applied_effect.after
629
+ transition = applied_effect.transition
630
+ before, after = self._propagated_stereo_effect(
631
+ applied_effect,
632
+ reactant_registry,
633
+ host,
634
+ )
635
+ outcome = self.rule.stereo_outcomes.get(rule_key)
636
+ alternatives = (
637
+ outcome.alternatives(after)
638
+ if outcome is not None and after is not None
639
+ else (after,)
640
+ )
641
+ branch_weights = (
642
+ tuple(outcome.weights or ()) if outcome is not None else (1.0,)
643
+ )
644
+ next_states = []
645
+ for (
646
+ product_registry,
647
+ relabeled_changes,
648
+ branch_metadata,
649
+ outcome_metadata,
650
+ coupling_branch_metadata,
651
+ ) in states:
652
+ for branch_index, (alternative, weight) in enumerate(
653
+ zip(alternatives, branch_weights)
654
+ ):
655
+ if float(weight) == 0.0:
656
+ continue
657
+ branch_registry = dict(product_registry)
658
+ branch_changes = dict(relabeled_changes)
659
+ branch_info = dict(branch_metadata)
660
+ branch_outcomes = dict(outcome_metadata)
661
+ if before is not None:
662
+ branch_registry.pop(descriptor_id(before), None)
663
+ if alternative is not None:
664
+ branch_registry[descriptor_id(alternative)] = alternative
665
+ key_descriptor = alternative or before or transition
666
+ if key_descriptor is not None:
667
+ target = descriptor_id(key_descriptor)
668
+ reference_mapping = (
669
+ dict(applied_effect.reference_mapping)
670
+ if applied_effect.alignment.status == "explicit"
671
+ else None
672
+ )
673
+ branch_changes[target] = StereoChange.from_endpoints(
674
+ before,
675
+ alternative,
676
+ transition,
677
+ reference_mapping=reference_mapping,
678
+ )
679
+ if outcome is not None:
680
+ branch_outcomes[target] = outcome.to_dict()
681
+ branch_info[target] = {
682
+ "kind": outcome.kind,
683
+ "branch_index": branch_index,
684
+ "weight": weight,
685
+ }
686
+ next_states.append(
687
+ (
688
+ branch_registry,
689
+ branch_changes,
690
+ branch_info,
691
+ branch_outcomes,
692
+ dict(coupling_branch_metadata),
693
+ )
694
+ )
695
+ states = next_states
696
+ self._check_stereo_branch_budget(len(states))
697
+
698
+ if relabeled_coupling_values:
699
+ product_graph = ITSReverter(its).to_product_graph()
700
+ states = self._apply_stereo_couplings(
701
+ states,
702
+ relabeled_coupling_values,
703
+ reactant_registry,
704
+ product_graph,
705
+ )
706
+
707
+ results = []
708
+ for state_index, (
709
+ product_registry,
710
+ relabeled_changes,
711
+ branch_metadata,
712
+ outcome_metadata,
713
+ coupling_branch_metadata,
714
+ ) in enumerate(states):
715
+ # The candidate is no longer needed after metadata application.
716
+ # Reuse it for the final branch and copy only genuine alternatives.
717
+ branch = its if state_index == len(states) - 1 else deepcopy(its)
718
+ branch.graph["stereo_descriptors"] = {
719
+ "reactant": dict(reactant_registry),
720
+ "product": product_registry,
721
+ }
722
+ if transition_registry:
723
+ branch.graph["stereo_descriptors"]["transition"] = dict(
724
+ transition_registry
725
+ )
726
+ branch.graph["stereo_changes"] = relabeled_changes
727
+ branch.graph["stereo_outcomes"] = outcome_metadata
728
+ branch.graph["stereo_couplings"] = relabeled_couplings
729
+ branch.graph["stereo_coupling_branch"] = coupling_branch_metadata
730
+ branch.graph["stereo_branch"] = branch_metadata
731
+ total_weight = 1.0
732
+ for metadata in branch_metadata.values():
733
+ total_weight *= float(metadata["weight"])
734
+ branch.graph["stereo_branch_weight"] = total_weight
735
+ branch.graph["stereo_aggregate_weight"] = total_weight
736
+ branch.graph["stereo_branch_multiplicity"] = 1
737
+ branch.graph["stereo_branch_path"] = tuple(
738
+ (
739
+ target,
740
+ metadata["branch_index"],
741
+ )
742
+ for target, metadata in sorted(branch_metadata.items())
743
+ )
744
+ results.append(branch)
745
+ self._check_stereo_branch_budget(len(results))
746
+ self._stereo_branch_count += len(results)
747
+ return results
748
+
749
+ @staticmethod
750
+ def _validate_product_stereo_registry(its: nx.Graph) -> bool:
751
+ """Validate product descriptors after the requested H representation."""
752
+ from synkit.Graph.Stereo import descriptor_graph_support_errors
753
+
754
+ layers = its.graph.get("stereo_descriptors", {})
755
+ product_registry = dict(layers.get("product", {}))
756
+ if not product_registry:
757
+ return True
758
+
759
+ changes = its.graph.get("stereo_changes", {})
760
+ product = ITSReverter(its).to_product_graph()
761
+ invalid = {
762
+ key: descriptor_graph_support_errors(
763
+ product,
764
+ descriptor,
765
+ registry_key=key,
766
+ )
767
+ for key, descriptor in product_registry.items()
768
+ }
769
+ invalid = {key: errors for key, errors in invalid.items() if errors}
770
+ if any(key in changes for key in invalid):
771
+ return False
772
+ if invalid:
773
+ layers = dict(layers)
774
+ layers["product"] = {
775
+ key: descriptor
776
+ for key, descriptor in product_registry.items()
777
+ if key not in invalid
778
+ }
779
+ its.graph["stereo_descriptors"] = layers
780
+ return True