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,899 @@
1
+ """Stereo predicates layered after structural candidate matching."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import Counter
6
+ from collections.abc import Iterator
7
+ from typing import Any, Callable, Hashable, Mapping
8
+
9
+ import networkx as nx
10
+
11
+ from synkit.Graph.Morphism.morphism import GraphMorphism, GraphMorphismError
12
+ from synkit.Graph.Morphism.constraints import WildcardConstraint
13
+ from synkit.Graph.Morphism.stereo_morphism import (
14
+ StereoMorphism,
15
+ StereoMorphismError,
16
+ )
17
+
18
+ from .changes import stereo_registry
19
+ from .descriptors import (
20
+ CumuleneAxisStereo,
21
+ ExtendedCisTransStereo,
22
+ PlanarBondStereo,
23
+ StereoValue,
24
+ TetrahedralStereo,
25
+ descriptor_id,
26
+ parse_virtual_reference,
27
+ virtual_reference,
28
+ )
29
+ from .extended_descriptors import HelicalStereo, PlanarChiralityStereo
30
+ from .identity import mapped_stereo_registries_match
31
+ from .global_stereo import FrameworkStereo
32
+ from .legacy import (
33
+ StereoSemanticComparison,
34
+ StereoSemanticsMode,
35
+ legacy_descriptor_query_matches,
36
+ )
37
+
38
+
39
+ def _atom_map_translation(
40
+ pattern: nx.Graph, host: nx.Graph, mapping: Mapping[Any, Any]
41
+ ) -> dict[int, int]:
42
+ return {
43
+ int(pattern.nodes[p_node].get("atom_map", p_node)): int(
44
+ host.nodes[h_node].get("atom_map") or h_node
45
+ )
46
+ for p_node, h_node in mapping.items()
47
+ }
48
+
49
+
50
+ def _node_by_atom_map(graph: nx.Graph) -> dict[int, Any]:
51
+ values: dict[int, Any] = {}
52
+ for node, attrs in graph.nodes(data=True):
53
+ # Ordinary unmapped molecules use graph node IDs as internal stereo
54
+ # references. Treat atom-map zero as absent, matching
55
+ # ``_atom_map_translation`` above, so explicit H can normalize to the
56
+ # same virtual-H identity as its implicit representation.
57
+ atom_map = attrs.get("atom_map") or node
58
+ if isinstance(atom_map, int):
59
+ values[atom_map] = node
60
+ return values
61
+
62
+
63
+ def _unique_stereo_map_lookup(
64
+ graph: nx.Graph,
65
+ ) -> tuple[dict[int, Any], str | None]:
66
+ by_map: dict[int, Any] = {}
67
+ for node, attrs in graph.nodes(data=True):
68
+ atom_map = attrs.get("atom_map") or node
69
+ if type(atom_map) is not int:
70
+ continue
71
+ if atom_map in by_map:
72
+ return by_map, f"duplicate atom map {atom_map}"
73
+ by_map[atom_map] = node
74
+ return by_map, None
75
+
76
+
77
+ def _path_support_errors(
78
+ graph: nx.Graph,
79
+ path: tuple[int, ...],
80
+ by_map: Mapping[int, Any],
81
+ label: str,
82
+ ) -> tuple[str, ...]:
83
+ """Validate that a descriptor path is present in the graph."""
84
+ absent = tuple(atom for atom in path if atom not in by_map)
85
+ if absent:
86
+ return (
87
+ f"{label} path atoms are absent: " + ", ".join(map(str, sorted(absent))),
88
+ )
89
+ missing_edges = [
90
+ (left, right)
91
+ for left, right in zip(path, path[1:])
92
+ if not graph.has_edge(by_map[left], by_map[right])
93
+ ]
94
+ if not missing_edges:
95
+ return ()
96
+ return (
97
+ f"{label} path edges are absent: "
98
+ + ", ".join(f"{left}-{right}" for left, right in missing_edges),
99
+ )
100
+
101
+
102
+ def _stereo_reference_owners(
103
+ graph: nx.Graph,
104
+ descriptor: StereoValue,
105
+ by_map: Mapping[int, Any],
106
+ ) -> tuple[tuple[tuple[int, tuple[Any, ...]], ...], tuple[str, ...]]:
107
+ if isinstance(descriptor, FrameworkStereo):
108
+ absent = tuple(atom for atom in descriptor.support_atoms if atom not in by_map)
109
+ if absent:
110
+ return (), (
111
+ "framework support atoms are absent: "
112
+ + ", ".join(map(str, sorted(absent))),
113
+ )
114
+ return (
115
+ tuple(
116
+ (frame.center, tuple(frame.references)) for frame in descriptor.frames
117
+ ),
118
+ (),
119
+ )
120
+ if isinstance(
121
+ descriptor,
122
+ (CumuleneAxisStereo, ExtendedCisTransStereo),
123
+ ):
124
+ path = (
125
+ descriptor.axis_path
126
+ if isinstance(descriptor, CumuleneAxisStereo)
127
+ else descriptor.path
128
+ )
129
+ owners = (
130
+ (path[0], tuple(descriptor.terminal_frames[0])),
131
+ (path[-1], tuple(descriptor.terminal_frames[1])),
132
+ )
133
+ return owners, _path_support_errors(graph, path, by_map, "stereo")
134
+ if isinstance(descriptor, HelicalStereo):
135
+ return (), _path_support_errors(
136
+ graph,
137
+ descriptor.path,
138
+ by_map,
139
+ "helical",
140
+ )
141
+ if isinstance(descriptor, PlanarChiralityStereo):
142
+ dependencies = (*descriptor.plane_atoms, descriptor.pilot)
143
+ absent = tuple(atom for atom in dependencies if atom not in by_map)
144
+ return (
145
+ (),
146
+ (
147
+ (
148
+ (
149
+ "planar-chirality support atoms are absent: "
150
+ + ", ".join(map(str, sorted(absent)))
151
+ ),
152
+ )
153
+ if absent
154
+ else ()
155
+ ),
156
+ )
157
+ atom_centered = descriptor.descriptor_class in {
158
+ "tetrahedral",
159
+ "square_planar",
160
+ "trigonal_bipyramidal",
161
+ "octahedral",
162
+ }
163
+ if atom_centered:
164
+ center = descriptor.atoms[0]
165
+ if type(center) is not int or center not in by_map:
166
+ return (), ("atom stereocenter is absent",)
167
+ return ((center, tuple(descriptor.atoms[1:])),), ()
168
+
169
+ left, right = descriptor.atoms[2:4]
170
+ if (
171
+ type(left) is not int
172
+ or type(right) is not int
173
+ or left not in by_map
174
+ or right not in by_map
175
+ ):
176
+ return (), ("central stereo bond atoms are absent",)
177
+ errors = []
178
+ if not graph.has_edge(by_map[left], by_map[right]):
179
+ errors.append("central stereo bond is absent")
180
+ elif (
181
+ descriptor.descriptor_class == "planar_bond"
182
+ and float(graph.edges[by_map[left], by_map[right]].get("pi_order", 0.0)) < 1.0
183
+ ):
184
+ errors.append("planar-bond stereo requires a pi bond")
185
+ owners = (
186
+ (left, tuple(descriptor.atoms[:2])),
187
+ (right, tuple(descriptor.atoms[4:])),
188
+ )
189
+ return owners, tuple(errors)
190
+
191
+
192
+ def _owner_reference_support_errors(
193
+ graph: nx.Graph,
194
+ owner: int,
195
+ references: tuple[Any, ...],
196
+ by_map: Mapping[int, Any],
197
+ ) -> tuple[str, ...]:
198
+ errors: list[str] = []
199
+ virtual_counts: Counter[str] = Counter()
200
+ owner_node = by_map[owner]
201
+ for reference in references:
202
+ if type(reference) is int:
203
+ if reference not in by_map:
204
+ errors.append(f"ligand atom map {reference} is absent")
205
+ elif not graph.has_edge(owner_node, by_map[reference]):
206
+ errors.append(
207
+ f"ligand atom map {reference} is not adjacent to owner {owner}"
208
+ )
209
+ continue
210
+ virtual = parse_virtual_reference(reference)
211
+ if virtual is None or virtual.center != owner:
212
+ errors.append(f"invalid virtual ligand {reference!r}")
213
+ continue
214
+ virtual_counts[virtual.kind] += 1
215
+
216
+ for kind, count in virtual_counts.items():
217
+ field = "hcount" if kind == "H" else "lone_pairs"
218
+ available = graph.nodes[owner_node].get(field, 0)
219
+ if not isinstance(available, (int, float)) or available < count:
220
+ errors.append(
221
+ f"virtual {kind} ligand requires {count} {field} resource at {owner}"
222
+ )
223
+ return tuple(errors)
224
+
225
+
226
+ def descriptor_graph_support_errors(
227
+ graph: nx.Graph,
228
+ descriptor: StereoValue,
229
+ *,
230
+ registry_key: str | None = None,
231
+ ) -> tuple[str, ...]:
232
+ """Return exact topology/resource reasons a descriptor cannot exist."""
233
+ errors: list[str] = []
234
+ by_map, map_error = _unique_stereo_map_lookup(graph)
235
+ if map_error is not None:
236
+ return (map_error,)
237
+
238
+ expected_key = descriptor_id(descriptor)
239
+ if registry_key is not None and registry_key != expected_key:
240
+ errors.append(f"registry key {registry_key} does not match {expected_key}")
241
+
242
+ owners, topology_errors = _stereo_reference_owners(graph, descriptor, by_map)
243
+ errors.extend(topology_errors)
244
+ for owner, references in owners:
245
+ errors.extend(_owner_reference_support_errors(graph, owner, references, by_map))
246
+ return tuple(errors)
247
+
248
+
249
+ def _is_bound_explicit_hydrogen(
250
+ graph: nx.Graph,
251
+ by_map: Mapping[int, Any],
252
+ reference: Any,
253
+ center: Any,
254
+ ) -> bool:
255
+ if not isinstance(reference, int) or not isinstance(center, int):
256
+ return False
257
+ h_node, center_node = by_map.get(reference), by_map.get(center)
258
+ return (
259
+ h_node is not None
260
+ and center_node is not None
261
+ and graph.nodes[h_node].get("element") == "H"
262
+ and graph.has_edge(h_node, center_node)
263
+ )
264
+
265
+
266
+ def normalize_hydrogen_references(
267
+ descriptor: StereoValue,
268
+ graph: nx.Graph,
269
+ ) -> StereoValue:
270
+ """Project explicit bound H references onto SynKit's virtual-H identity.
271
+
272
+ SynKit rules may strip ordinary explicit hydrogens while imported graphs
273
+ retain them. Matching therefore compares both representations through
274
+ ``@H:<center>`` without mutating either stored descriptor.
275
+ """
276
+ by_map = _node_by_atom_map(graph)
277
+ if isinstance(descriptor, TetrahedralStereo):
278
+ center = descriptor.center
279
+ bound_hydrogens = {
280
+ ref
281
+ for ref in descriptor.atoms[1:]
282
+ if _is_bound_explicit_hydrogen(graph, by_map, ref, center)
283
+ }
284
+ refs = tuple(
285
+ (
286
+ virtual_reference("H", center)
287
+ if len(bound_hydrogens) == 1 and ref in bound_hydrogens
288
+ else ref
289
+ )
290
+ for ref in descriptor.atoms[1:]
291
+ )
292
+ return TetrahedralStereo(
293
+ (center, *refs), descriptor.parity, descriptor.provenance
294
+ )
295
+
296
+ if not isinstance(descriptor, PlanarBondStereo):
297
+ return descriptor
298
+
299
+ left, right = descriptor.atoms[2:4]
300
+ left_refs = tuple(
301
+ (
302
+ virtual_reference("H", left)
303
+ if _is_bound_explicit_hydrogen(graph, by_map, ref, left)
304
+ else ref
305
+ )
306
+ for ref in descriptor.atoms[:2]
307
+ )
308
+ right_refs = tuple(
309
+ (
310
+ virtual_reference("H", right)
311
+ if _is_bound_explicit_hydrogen(graph, by_map, ref, right)
312
+ else ref
313
+ )
314
+ for ref in descriptor.atoms[4:]
315
+ )
316
+ return PlanarBondStereo(
317
+ (*left_refs, left, right, *right_refs),
318
+ descriptor.parity,
319
+ descriptor.provenance,
320
+ )
321
+
322
+
323
+ def _orbit_descriptor_query_matches(
324
+ query: StereoValue,
325
+ candidate: StereoValue,
326
+ *,
327
+ unknown_policy: str = "exact",
328
+ ) -> bool:
329
+ """Match one descriptor through orbit identity and information policy."""
330
+ if unknown_policy not in {"exact", "wildcard", "either"}:
331
+ raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
332
+ if type(query) is not type(candidate):
333
+ return False
334
+ if unknown_policy == "either" and query.parity is not None:
335
+ return query.same_configuration(candidate) or query.invert().same_configuration(
336
+ candidate
337
+ )
338
+ if query.parity is not None or unknown_policy == "exact":
339
+ return query.same_configuration(candidate)
340
+ unknown_candidate = type(candidate)(
341
+ candidate.atoms,
342
+ None,
343
+ candidate.provenance,
344
+ )
345
+ return query.same_configuration(unknown_candidate)
346
+
347
+
348
+ def descriptor_query_matches(
349
+ query: StereoValue,
350
+ candidate: StereoValue,
351
+ *,
352
+ unknown_policy: str = "exact",
353
+ semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
354
+ diagnostics: list[StereoSemanticComparison] | None = None,
355
+ ) -> bool:
356
+ """Match one descriptor using orbit, frozen legacy, or dual-run semantics.
357
+
358
+ Compare mode always returns the orbit decision. If a diagnostics sink is
359
+ supplied it also records agreement with the independent Beta-2 oracle;
360
+ legacy is never used as a fallback.
361
+ """
362
+ mode = StereoSemanticsMode(semantics)
363
+ if mode is StereoSemanticsMode.LEGACY:
364
+ return legacy_descriptor_query_matches(
365
+ query,
366
+ candidate,
367
+ unknown_policy=unknown_policy,
368
+ )
369
+ orbit_result = _orbit_descriptor_query_matches(
370
+ query,
371
+ candidate,
372
+ unknown_policy=unknown_policy,
373
+ )
374
+ if mode is StereoSemanticsMode.ORBIT:
375
+ return orbit_result
376
+ legacy_result = legacy_descriptor_query_matches(
377
+ query,
378
+ candidate,
379
+ unknown_policy=unknown_policy,
380
+ )
381
+ if diagnostics is not None:
382
+ diagnostics.append(
383
+ StereoSemanticComparison.create(
384
+ "descriptor_query",
385
+ orbit_result,
386
+ legacy_result,
387
+ )
388
+ )
389
+ return orbit_result
390
+
391
+
392
+ def _default_node_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
393
+ """Compare stable molecular/Lewis node attributes while ignoring atom maps."""
394
+ keys = (
395
+ "element",
396
+ "charge",
397
+ "radical",
398
+ "hcount",
399
+ "lone_pairs",
400
+ "aromatic",
401
+ "valence",
402
+ )
403
+ return all(left.get(key) == right.get(key) for key in keys)
404
+
405
+
406
+ def _default_edge_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
407
+ """Compare stable molecular/Lewis bond attributes.
408
+
409
+ The alternating sigma/pi assignment of an aromatic presentation is not
410
+ identity: independently reconstructed graphs, and different supported
411
+ RDKit releases, may choose opposite Kekule phases. ``order == 1.5`` is
412
+ SynKit's stable aromatic marker, so ignore only that phase-local split.
413
+ """
414
+ left_aromatic = left.get("aromatic") is True or left.get("order") == 1.5
415
+ right_aromatic = right.get("aromatic") is True or right.get("order") == 1.5
416
+ if left_aromatic or right_aromatic:
417
+ return left_aromatic and right_aromatic
418
+ keys = ("order", "sigma_order", "pi_order", "aromatic")
419
+ return all(left.get(key) == right.get(key) for key in keys)
420
+
421
+
422
+ def _mapped_registry_matches(
423
+ left: nx.Graph,
424
+ right: nx.Graph,
425
+ mapping: Mapping[Any, Any],
426
+ *,
427
+ unknown_policy: str,
428
+ ) -> bool:
429
+ return mapped_stereo_registries_match(
430
+ left,
431
+ right,
432
+ mapping,
433
+ unknown_policy=unknown_policy,
434
+ )
435
+
436
+
437
+ def _structural_isomorphisms_iter(
438
+ left: nx.Graph,
439
+ right: nx.Graph,
440
+ *,
441
+ node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
442
+ edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
443
+ ) -> Iterator[dict[Any, Any]]:
444
+ if (
445
+ left.is_multigraph() != right.is_multigraph()
446
+ or left.is_directed() != right.is_directed()
447
+ ):
448
+ return
449
+ if left.is_directed():
450
+ matcher_type = (
451
+ nx.algorithms.isomorphism.MultiDiGraphMatcher
452
+ if left.is_multigraph()
453
+ else nx.algorithms.isomorphism.DiGraphMatcher
454
+ )
455
+ else:
456
+ matcher_type = (
457
+ nx.algorithms.isomorphism.MultiGraphMatcher
458
+ if left.is_multigraph()
459
+ else nx.algorithms.isomorphism.GraphMatcher
460
+ )
461
+ matcher = matcher_type(
462
+ left,
463
+ right,
464
+ node_match=node_match or _default_node_match,
465
+ edge_match=edge_match or _default_edge_match,
466
+ )
467
+ for mapping in matcher.isomorphisms_iter():
468
+ yield dict(mapping)
469
+
470
+
471
+ def _structural_isomorphism_mappings(
472
+ left: nx.Graph,
473
+ right: nx.Graph,
474
+ *,
475
+ node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
476
+ edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
477
+ ) -> tuple[dict[Any, Any], ...]:
478
+ return tuple(
479
+ _structural_isomorphisms_iter(
480
+ left,
481
+ right,
482
+ node_match=node_match,
483
+ edge_match=edge_match,
484
+ )
485
+ )
486
+
487
+
488
+ def _mapping_set_form(
489
+ mappings: tuple[Mapping[Any, Any], ...],
490
+ ) -> tuple[tuple[tuple[str, str], ...], ...]:
491
+ return tuple(
492
+ sorted(
493
+ (
494
+ tuple(
495
+ sorted(
496
+ ((repr(left), repr(right)) for left, right in mapping.items())
497
+ )
498
+ )
499
+ for mapping in mappings
500
+ ),
501
+ key=repr,
502
+ )
503
+ )
504
+
505
+
506
+ def _stereo_morphisms_for_mappings(
507
+ left: nx.Graph,
508
+ right: nx.Graph,
509
+ mappings: tuple[dict[Any, Any], ...],
510
+ unknown_policy: str,
511
+ ) -> tuple[StereoMorphism, ...]:
512
+ results = []
513
+ for mapping in mappings:
514
+ graph_morphism = GraphMorphism(
515
+ "left",
516
+ "right",
517
+ frozenset(left.nodes),
518
+ frozenset(right.nodes),
519
+ mapping,
520
+ )
521
+ try:
522
+ results.append(
523
+ StereoMorphism.from_graphs(
524
+ graph_morphism,
525
+ left,
526
+ right,
527
+ presence_mode="strict",
528
+ information_policy=unknown_policy,
529
+ )
530
+ )
531
+ except StereoMorphismError:
532
+ continue
533
+ return tuple(results)
534
+
535
+
536
+ def stereo_isomorphism_morphisms(
537
+ left: nx.Graph,
538
+ right: nx.Graph,
539
+ *,
540
+ node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
541
+ edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
542
+ unknown_policy: str = "exact",
543
+ ) -> tuple[StereoMorphism, ...]:
544
+ """Return every accepted isomorphism with complete local certificates."""
545
+ if unknown_policy not in {"exact", "wildcard", "either"}:
546
+ raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
547
+ structural = _structural_isomorphism_mappings(
548
+ left,
549
+ right,
550
+ node_match=node_match,
551
+ edge_match=edge_match,
552
+ )
553
+ return _stereo_morphisms_for_mappings(
554
+ left,
555
+ right,
556
+ structural,
557
+ unknown_policy,
558
+ )
559
+
560
+
561
+ def stereo_isomorphism_mappings(
562
+ left: nx.Graph,
563
+ right: nx.Graph,
564
+ *,
565
+ node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
566
+ edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
567
+ unknown_policy: str = "exact",
568
+ semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
569
+ diagnostics: list[StereoSemanticComparison] | None = None,
570
+ ) -> tuple[dict[Any, Any], ...]:
571
+ """Return the complete accepted mapping set under selected semantics."""
572
+ if unknown_policy not in {"exact", "wildcard", "either"}:
573
+ raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
574
+ mode = StereoSemanticsMode(semantics)
575
+ structural = _structural_isomorphism_mappings(
576
+ left,
577
+ right,
578
+ node_match=node_match,
579
+ edge_match=edge_match,
580
+ )
581
+ if mode is StereoSemanticsMode.LEGACY:
582
+ return tuple(
583
+ mapping
584
+ for mapping in structural
585
+ if _mapped_registry_matches(
586
+ left,
587
+ right,
588
+ mapping,
589
+ unknown_policy=unknown_policy,
590
+ )
591
+ )
592
+ orbit = tuple(
593
+ morphism.graph_morphism.mapping
594
+ for morphism in _stereo_morphisms_for_mappings(
595
+ left,
596
+ right,
597
+ structural,
598
+ unknown_policy,
599
+ )
600
+ )
601
+ if mode is StereoSemanticsMode.COMPARE:
602
+ legacy = tuple(
603
+ mapping
604
+ for mapping in structural
605
+ if _mapped_registry_matches(
606
+ left,
607
+ right,
608
+ mapping,
609
+ unknown_policy=unknown_policy,
610
+ )
611
+ )
612
+ if diagnostics is not None:
613
+ diagnostics.append(
614
+ StereoSemanticComparison.create(
615
+ "stereo_isomorphism_mapping_set",
616
+ _mapping_set_form(orbit),
617
+ _mapping_set_form(legacy),
618
+ )
619
+ )
620
+ return orbit
621
+
622
+
623
+ def stereo_isomorphism_mapping(
624
+ left: nx.Graph,
625
+ right: nx.Graph,
626
+ *,
627
+ node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
628
+ edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
629
+ unknown_policy: str = "exact",
630
+ semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
631
+ diagnostics: list[StereoSemanticComparison] | None = None,
632
+ ) -> dict[Any, Any] | None:
633
+ """Return the first accepted mapping; compare mode audits the full set."""
634
+ if unknown_policy not in {"exact", "wildcard", "either"}:
635
+ raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
636
+ mode = StereoSemanticsMode(semantics)
637
+ if mode is StereoSemanticsMode.COMPARE:
638
+ mappings = stereo_isomorphism_mappings(
639
+ left,
640
+ right,
641
+ node_match=node_match,
642
+ edge_match=edge_match,
643
+ unknown_policy=unknown_policy,
644
+ semantics=mode,
645
+ diagnostics=diagnostics,
646
+ )
647
+ return None if not mappings else mappings[0]
648
+ for mapping in _structural_isomorphisms_iter(
649
+ left,
650
+ right,
651
+ node_match=node_match,
652
+ edge_match=edge_match,
653
+ ):
654
+ if mode is StereoSemanticsMode.LEGACY:
655
+ if _mapped_registry_matches(
656
+ left,
657
+ right,
658
+ mapping,
659
+ unknown_policy=unknown_policy,
660
+ ):
661
+ return mapping
662
+ continue
663
+ graph_morphism = GraphMorphism(
664
+ "left",
665
+ "right",
666
+ frozenset(left.nodes),
667
+ frozenset(right.nodes),
668
+ mapping,
669
+ )
670
+ try:
671
+ StereoMorphism.from_graphs(
672
+ graph_morphism,
673
+ left,
674
+ right,
675
+ presence_mode="strict",
676
+ information_policy=unknown_policy,
677
+ )
678
+ except StereoMorphismError:
679
+ continue
680
+ return mapping
681
+ return None
682
+
683
+
684
+ def stereo_isomorphic(
685
+ left: nx.Graph,
686
+ right: nx.Graph,
687
+ *,
688
+ node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
689
+ edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
690
+ unknown_policy: str = "exact",
691
+ semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
692
+ diagnostics: list[StereoSemanticComparison] | None = None,
693
+ ) -> bool:
694
+ """Return whether two graphs are structurally and stereochemically equal."""
695
+ return (
696
+ stereo_isomorphism_mapping(
697
+ left,
698
+ right,
699
+ node_match=node_match,
700
+ edge_match=edge_match,
701
+ unknown_policy=unknown_policy,
702
+ semantics=semantics,
703
+ diagnostics=diagnostics,
704
+ )
705
+ is not None
706
+ )
707
+
708
+
709
+ def candidate_mapping_stereo_morphism(
710
+ pattern: nx.Graph,
711
+ host: nx.Graph,
712
+ mapping: Mapping[Any, Any],
713
+ *,
714
+ mode: str = "require",
715
+ unknown_policy: str = "exact",
716
+ query_policies: Mapping[str, str] | None = None,
717
+ source_id: Hashable = "pattern",
718
+ target_id: Hashable = "host",
719
+ substitutions: Mapping[Hashable, WildcardConstraint] | None = None,
720
+ ) -> StereoMorphism:
721
+ """Return a proof-bearing refinement of one structural candidate mapping."""
722
+ if mode not in {"require", "strict", "ignore", "propagate"}:
723
+ raise ValueError(f"Unsupported stereo matching mode: {mode!r}")
724
+ normalized_substitutions = {}
725
+ for node, constraint in (substitutions or {}).items():
726
+ owner = constraint.owner
727
+ if owner is not None and owner not in pattern:
728
+ owner_nodes = [
729
+ candidate
730
+ for candidate, attrs in pattern.nodes(data=True)
731
+ if owner
732
+ in (
733
+ attrs.get("atom_map"),
734
+ *(
735
+ attrs.get("atom_map")
736
+ if isinstance(attrs.get("atom_map"), (tuple, list))
737
+ else ()
738
+ ),
739
+ )
740
+ ]
741
+ if len(owner_nodes) == 1:
742
+ constraint = constraint.relabel_owner({owner: owner_nodes[0]})
743
+ normalized_substitutions[node] = constraint
744
+ graph_morphism = GraphMorphism(
745
+ source_id,
746
+ target_id,
747
+ frozenset(pattern.nodes),
748
+ frozenset(host.nodes),
749
+ mapping,
750
+ normalized_substitutions,
751
+ )
752
+ return StereoMorphism.from_graphs(
753
+ graph_morphism,
754
+ pattern,
755
+ host,
756
+ presence_mode=mode,
757
+ information_policy=unknown_policy,
758
+ information_policies=query_policies,
759
+ )
760
+
761
+
762
+ def _legacy_candidate_mapping_stereo_matches(
763
+ pattern: nx.Graph,
764
+ host: nx.Graph,
765
+ mapping: Mapping[Any, Any],
766
+ *,
767
+ mode: str = "require",
768
+ unknown_policy: str = "exact",
769
+ query_policies: Mapping[str, str] | None = None,
770
+ ) -> bool:
771
+ if mode in {"ignore", "propagate"}:
772
+ return True
773
+ if mode not in {"require", "strict"}:
774
+ raise ValueError(f"Unsupported stereo matching mode: {mode!r}")
775
+ translation = _atom_map_translation(pattern, host, mapping)
776
+ host_descriptors = tuple(stereo_registry(host).values())
777
+ query_policies = query_policies or {}
778
+ for key, descriptor in stereo_registry(pattern).items():
779
+ mapped = normalize_hydrogen_references(descriptor.relabel(translation), host)
780
+ policy = query_policies.get(key, unknown_policy)
781
+ if not any(
782
+ descriptor_query_matches(
783
+ mapped,
784
+ normalize_hydrogen_references(candidate, host),
785
+ unknown_policy=policy,
786
+ )
787
+ for candidate in host_descriptors
788
+ ):
789
+ return False
790
+ if mode == "strict":
791
+ mapped_dependencies = set(translation.values())
792
+ pattern_count = len(stereo_registry(pattern))
793
+ host_count = sum(
794
+ bool(candidate.dependencies & mapped_dependencies)
795
+ for candidate in host_descriptors
796
+ )
797
+ return pattern_count == host_count
798
+ return True
799
+
800
+
801
+ def candidate_mapping_stereo_matches(
802
+ pattern: nx.Graph,
803
+ host: nx.Graph,
804
+ mapping: Mapping[Any, Any],
805
+ *,
806
+ mode: str = "require",
807
+ unknown_policy: str = "exact",
808
+ query_policies: Mapping[str, str] | None = None,
809
+ substitutions: Mapping[Hashable, WildcardConstraint] | None = None,
810
+ semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
811
+ diagnostics: list[StereoSemanticComparison] | None = None,
812
+ morphism_issues: list[Any] | None = None,
813
+ ) -> bool:
814
+ """Check one candidate mapping and optionally audit frozen behavior."""
815
+ semantics_mode = StereoSemanticsMode(semantics)
816
+ if semantics_mode is StereoSemanticsMode.LEGACY:
817
+ if substitutions:
818
+ try:
819
+ candidate_mapping_stereo_morphism(
820
+ pattern,
821
+ host,
822
+ mapping,
823
+ mode=mode,
824
+ unknown_policy=unknown_policy,
825
+ query_policies=query_policies,
826
+ substitutions=substitutions,
827
+ )
828
+ except StereoMorphismError as error:
829
+ if morphism_issues is not None:
830
+ morphism_issues.extend(error.issues)
831
+ return False
832
+ except GraphMorphismError:
833
+ return False
834
+ return _legacy_candidate_mapping_stereo_matches(
835
+ pattern,
836
+ host,
837
+ mapping,
838
+ mode=mode,
839
+ unknown_policy=unknown_policy,
840
+ query_policies=query_policies,
841
+ )
842
+ try:
843
+ candidate_mapping_stereo_morphism(
844
+ pattern,
845
+ host,
846
+ mapping,
847
+ mode=mode,
848
+ unknown_policy=unknown_policy,
849
+ query_policies=query_policies,
850
+ substitutions=substitutions,
851
+ )
852
+ orbit_result = True
853
+ except StereoMorphismError as error:
854
+ if morphism_issues is not None:
855
+ morphism_issues.extend(error.issues)
856
+ orbit_result = False
857
+ except GraphMorphismError:
858
+ orbit_result = False
859
+ if semantics_mode is StereoSemanticsMode.ORBIT:
860
+ return orbit_result
861
+ legacy_result = _legacy_candidate_mapping_stereo_matches(
862
+ pattern,
863
+ host,
864
+ mapping,
865
+ mode=mode,
866
+ unknown_policy=unknown_policy,
867
+ query_policies=query_policies,
868
+ )
869
+ if diagnostics is not None:
870
+ diagnostics.append(
871
+ StereoSemanticComparison.create(
872
+ "candidate_mapping",
873
+ orbit_result,
874
+ legacy_result,
875
+ )
876
+ )
877
+ return orbit_result
878
+
879
+
880
+ def propagate_unaffected_stereo(
881
+ source: nx.Graph, target: nx.Graph, *, changed_atom_maps: set[int]
882
+ ) -> None:
883
+ """Copy descriptors whose complete dependency set remains unaffected."""
884
+ registry: dict[str, StereoValue] = dict(stereo_registry(target))
885
+ target_maps = {
886
+ int(attrs.get("atom_map", node)) for node, attrs in target.nodes(data=True)
887
+ }
888
+ for descriptor in stereo_registry(source).values():
889
+ if descriptor.dependencies & changed_atom_maps:
890
+ continue
891
+ if (
892
+ descriptor.dependencies <= target_maps
893
+ and not descriptor_graph_support_errors(
894
+ target,
895
+ descriptor,
896
+ )
897
+ ):
898
+ registry[descriptor_id(descriptor)] = descriptor
899
+ target.graph["stereo_descriptors"] = registry