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,957 @@
1
+ """Validation and certificate helpers for stereo-aware graph morphisms."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Hashable, Mapping
6
+
7
+ import networkx as nx
8
+
9
+ from synkit.Graph.Stereo.descriptors import StereoValue, parse_virtual_reference
10
+ from synkit.Graph.Stereo.identity import (
11
+ StereoIdentityError,
12
+ StereoReferenceResolver,
13
+ mapped_reference_resolver,
14
+ stereo_registry_layers,
15
+ )
16
+ from synkit.Graph.Stereo.orbits import (
17
+ StereoConfiguration,
18
+ StereoSpecification,
19
+ )
20
+
21
+ from .constraints import EndpointSide, WildcardConstraint, WildcardRole
22
+ from .morphism import GraphMorphism
23
+ from .stereo_morphism_types import (
24
+ LocalStereoCertificate,
25
+ StereoCertificateStatus,
26
+ StereoInformationPolicy,
27
+ StereoMorphismError,
28
+ StereoMorphismIssue,
29
+ StereoMorphismIssueCode,
30
+ StereoPresenceMode,
31
+ )
32
+
33
+ _ATOM_CENTERED_STEREO = frozenset(
34
+ {"tetrahedral", "square_planar", "trigonal_bipyramidal", "octahedral"}
35
+ )
36
+
37
+
38
+ def _layer_value(value: Any, layer: str) -> Any:
39
+ if isinstance(value, tuple) and len(value) == 2:
40
+ side = {"reactant": 0, "product": 1}.get(layer)
41
+ if side is not None:
42
+ return value[side]
43
+ return value
44
+
45
+
46
+ def _material_node(token: Hashable) -> Hashable | None:
47
+ if isinstance(token, tuple) and len(token) == 2 and token[0] == "node":
48
+ return token[1]
49
+ return None
50
+
51
+
52
+ def _reference_owner(descriptor: StereoValue, reference: Any) -> int | None:
53
+ if descriptor.descriptor_class in _ATOM_CENTERED_STEREO:
54
+ return descriptor.atoms[0] if reference != descriptor.atoms[0] else None
55
+ if reference in descriptor.atoms[:2]:
56
+ return descriptor.atoms[2]
57
+ if reference in descriptor.atoms[4:]:
58
+ return descriptor.atoms[3]
59
+ return None
60
+
61
+
62
+ def _owned_references(descriptor: StereoValue, owner: int) -> tuple[Any, ...]:
63
+ if descriptor.descriptor_class in _ATOM_CENTERED_STEREO:
64
+ return tuple(descriptor.atoms[1:])
65
+ return (
66
+ tuple(descriptor.atoms[:2])
67
+ if owner == descriptor.atoms[2]
68
+ else tuple(descriptor.atoms[4:])
69
+ )
70
+
71
+
72
+ def _node_token(value: Hashable) -> tuple[str, Hashable]:
73
+ return "node", value
74
+
75
+
76
+ def _owner_node(
77
+ owner: Hashable,
78
+ resolver: StereoReferenceResolver,
79
+ graph: nx.Graph,
80
+ ) -> Hashable | None:
81
+ if owner in graph:
82
+ return owner
83
+ try:
84
+ return _material_node(resolver(owner))
85
+ except StereoIdentityError:
86
+ return None
87
+
88
+
89
+ def _endpoint_side(layer: str, graph: nx.Graph | None = None) -> EndpointSide:
90
+ if layer == "reactant":
91
+ return EndpointSide.REACTANT
92
+ if layer == "product":
93
+ return EndpointSide.PRODUCT
94
+ projection = None if graph is None else graph.graph.get("stereo_projection")
95
+ if projection == "reactant":
96
+ return EndpointSide.REACTANT
97
+ if projection == "product":
98
+ return EndpointSide.PRODUCT
99
+ return EndpointSide.ANY
100
+
101
+
102
+ def _target_virtual_kind(
103
+ graph: nx.Graph,
104
+ target_node: Hashable,
105
+ layer: str,
106
+ ) -> str | None:
107
+ element = _layer_value(graph.nodes[target_node].get("element"), layer)
108
+ if element == "H":
109
+ return "H"
110
+ if element in {"LP", "Lp", "lp"}:
111
+ return "LP"
112
+ return None
113
+
114
+
115
+ def _port_concrete_state(
116
+ constraint: WildcardConstraint,
117
+ target_graph: nx.Graph,
118
+ target_node: Hashable,
119
+ target_owner: Hashable,
120
+ layer: str,
121
+ side: EndpointSide,
122
+ ) -> dict[str, Any]:
123
+ attrs = target_graph.nodes[target_node]
124
+ virtual_kind = _target_virtual_kind(target_graph, target_node, layer)
125
+ concrete = {
126
+ "element": _layer_value(attrs.get("element"), layer),
127
+ "charge": _layer_value(attrs.get("charge", 0), layer),
128
+ "radical": _layer_value(attrs.get("radical", 0), layer),
129
+ "owner": constraint.owner,
130
+ "side": side.value,
131
+ "stereo_slot": constraint.stereo_slot,
132
+ "virtual_kind": virtual_kind,
133
+ "mapped_identity": _layer_value(attrs.get("atom_map", target_node), layer),
134
+ "materialization": attrs.get(
135
+ "materialization",
136
+ "virtual" if virtual_kind is not None else "concrete",
137
+ ),
138
+ "capacity": 1,
139
+ "resource_usage": 1,
140
+ }
141
+ if target_graph.has_edge(target_owner, target_node):
142
+ edge = target_graph.edges[target_owner, target_node]
143
+ concrete["bond_order"] = _layer_value(edge.get("order"), layer)
144
+ return concrete
145
+
146
+
147
+ def _descriptor_port_bindings(
148
+ source_graph: nx.Graph,
149
+ source_node: Hashable,
150
+ ) -> list[tuple[str, str, Hashable, int]]:
151
+ """Return ``(layer, descriptor key, owner node, owner-local slot)`` values."""
152
+ bindings: list[tuple[str, str, Hashable, int]] = []
153
+ for layer, registry in sorted(stereo_registry_layers(source_graph).items()):
154
+ resolver = _morphism_reference_resolver(source_graph, layer)
155
+ for key, descriptor in sorted(registry.items(), key=lambda item: str(item[0])):
156
+ configuration = _resolved_configuration(
157
+ descriptor,
158
+ resolver,
159
+ source_graph,
160
+ layer,
161
+ )
162
+ binding = _configuration_port_binding(configuration, source_node)
163
+ if binding is not None:
164
+ owner_node, slot = binding
165
+ bindings.append((layer, str(key), owner_node, slot))
166
+ return bindings
167
+
168
+
169
+ def _port_materialization_issue(
170
+ constraint: WildcardConstraint,
171
+ source_graph: nx.Graph,
172
+ target_graph: nx.Graph,
173
+ target_node: Hashable,
174
+ target_owner: Hashable,
175
+ layer: str,
176
+ context: Mapping[str, Any],
177
+ ) -> StereoMorphismIssue | None:
178
+ concrete = _port_concrete_state(
179
+ constraint,
180
+ target_graph,
181
+ target_node,
182
+ target_owner,
183
+ layer,
184
+ _endpoint_side(layer, source_graph),
185
+ )
186
+ if constraint.virtual_kind is not None and (
187
+ concrete["virtual_kind"] != constraint.virtual_kind
188
+ ):
189
+ return StereoMorphismIssue(
190
+ StereoMorphismIssueCode.PORT_VIRTUAL_KIND_MISMATCH,
191
+ "A stereo ligand port materialized as the wrong virtual kind.",
192
+ context,
193
+ )
194
+ if constraint.capacity < 1 or (
195
+ constraint.resource_budget is not None and constraint.resource_budget < 1
196
+ ):
197
+ return StereoMorphismIssue(
198
+ StereoMorphismIssueCode.PORT_CAPACITY_MISMATCH,
199
+ "A stereo ligand port has no capacity for its mapped ligand.",
200
+ context,
201
+ )
202
+ if not constraint.satisfies(concrete):
203
+ return StereoMorphismIssue(
204
+ StereoMorphismIssueCode.PORT_DOMAIN_MISMATCH,
205
+ "A mapped stereo ligand does not satisfy its typed constraint.",
206
+ context,
207
+ )
208
+ return None
209
+
210
+
211
+ def _validate_stereo_port_bindings(
212
+ graph_morphism: GraphMorphism,
213
+ source_graph: nx.Graph,
214
+ target_graph: nx.Graph,
215
+ ) -> None:
216
+ """Validate typed stereo ports against owner-local ordered frames."""
217
+ issues: list[StereoMorphismIssue] = []
218
+ mapping = graph_morphism.mapping
219
+ for source_node, constraint in graph_morphism.substitutions.items():
220
+ bindings = _descriptor_port_bindings(source_graph, source_node)
221
+ if not bindings:
222
+ if constraint.role is WildcardRole.STEREO_LIGAND_PORT:
223
+ issues.append(
224
+ StereoMorphismIssue(
225
+ StereoMorphismIssueCode.PORT_BINDING_MISSING,
226
+ "A typed stereo ligand port is absent from every source frame.",
227
+ {"source_node": repr(source_node)},
228
+ )
229
+ )
230
+ continue
231
+ if constraint.role is not WildcardRole.STEREO_LIGAND_PORT:
232
+ issues.append(
233
+ StereoMorphismIssue(
234
+ StereoMorphismIssueCode.PORT_ROLE_MISMATCH,
235
+ "A wildcard used as a stereo ligand must declare the stereo-ligand-port role.",
236
+ {
237
+ "source_node": repr(source_node),
238
+ "role": constraint.role.value,
239
+ },
240
+ )
241
+ )
242
+ continue
243
+ if len(bindings) != 1:
244
+ issues.append(
245
+ StereoMorphismIssue(
246
+ StereoMorphismIssueCode.PORT_BINDING_AMBIGUOUS,
247
+ "A stereo ligand port must bind exactly one descriptor slot.",
248
+ {"source_node": repr(source_node), "bindings": len(bindings)},
249
+ )
250
+ )
251
+ continue
252
+ layer, descriptor_key, source_owner, slot = bindings[0]
253
+ source_element = _layer_value(
254
+ source_graph.nodes[source_node].get("element"),
255
+ layer,
256
+ )
257
+ declared_owner = _owner_node(
258
+ constraint.owner, # type: ignore[arg-type]
259
+ _morphism_reference_resolver(source_graph, layer),
260
+ source_graph,
261
+ )
262
+ context = {
263
+ "source_node": repr(source_node),
264
+ "layer": layer,
265
+ "descriptor": descriptor_key,
266
+ "owner": repr(source_owner),
267
+ "slot": slot,
268
+ }
269
+ if source_element != "*":
270
+ issues.append(
271
+ StereoMorphismIssue(
272
+ StereoMorphismIssueCode.PORT_SOURCE_NOT_WILDCARD,
273
+ "A typed stereo ligand port must originate at a wildcard node.",
274
+ context,
275
+ )
276
+ )
277
+ continue
278
+ if declared_owner != source_owner:
279
+ issues.append(
280
+ StereoMorphismIssue(
281
+ StereoMorphismIssueCode.PORT_OWNER_MISMATCH,
282
+ "A stereo ligand port declares the wrong descriptor owner.",
283
+ context,
284
+ )
285
+ )
286
+ continue
287
+ if constraint.stereo_slot != slot:
288
+ issues.append(
289
+ StereoMorphismIssue(
290
+ StereoMorphismIssueCode.PORT_SLOT_MISMATCH,
291
+ "A stereo ligand port declares the wrong owner-local frame slot.",
292
+ context,
293
+ )
294
+ )
295
+ continue
296
+ target_node = mapping[source_node]
297
+ target_owner = mapping[source_owner]
298
+ if not source_graph.has_edge(source_owner, source_node) or (
299
+ not target_graph.has_edge(target_owner, target_node)
300
+ ):
301
+ issues.append(
302
+ StereoMorphismIssue(
303
+ StereoMorphismIssueCode.PORT_INCIDENCE_MISMATCH,
304
+ "A stereo ligand port must remain incident to its mapped owner.",
305
+ context,
306
+ )
307
+ )
308
+ continue
309
+ materialization_issue = _port_materialization_issue(
310
+ constraint,
311
+ source_graph,
312
+ target_graph,
313
+ target_node,
314
+ target_owner,
315
+ layer,
316
+ context,
317
+ )
318
+ if materialization_issue is not None:
319
+ issues.append(materialization_issue)
320
+ if issues:
321
+ raise StereoMorphismError(*issues)
322
+
323
+
324
+ def _configuration_port_binding(
325
+ configuration: StereoConfiguration,
326
+ source_node: Hashable,
327
+ ) -> tuple[Hashable, int] | None:
328
+ token = _node_token(source_node)
329
+ positions = [
330
+ position
331
+ for position, reference in enumerate(configuration.frame)
332
+ if reference == token
333
+ ]
334
+ if len(positions) != 1:
335
+ return None
336
+ position = positions[0]
337
+ if configuration.shape in _ATOM_CENTERED_STEREO:
338
+ if position == 0:
339
+ return None
340
+ owner_token = configuration.frame[0]
341
+ return _material_node(owner_token), position - 1
342
+ if position in {0, 1}:
343
+ return _material_node(configuration.frame[2]), position
344
+ if position in {4, 5}:
345
+ return _material_node(configuration.frame[3]), position - 4
346
+ return None
347
+
348
+
349
+ def _validate_stored_port_bindings(
350
+ graph_morphism: GraphMorphism,
351
+ certificates: tuple[LocalStereoCertificate, ...],
352
+ ) -> None:
353
+ """Replay owner-local port bindings without retaining endpoint graphs."""
354
+ issues: list[StereoMorphismIssue] = []
355
+ for source_node, constraint in graph_morphism.substitutions.items():
356
+ bindings = [
357
+ binding
358
+ for certificate in certificates
359
+ if (
360
+ binding := _configuration_port_binding(
361
+ certificate.source_configuration,
362
+ source_node,
363
+ )
364
+ )
365
+ is not None
366
+ ]
367
+ if not bindings:
368
+ if constraint.role is WildcardRole.STEREO_LIGAND_PORT:
369
+ issues.append(
370
+ StereoMorphismIssue(
371
+ StereoMorphismIssueCode.PORT_BINDING_MISSING,
372
+ "A stored stereo port has no certificate-local frame binding.",
373
+ {"source_node": repr(source_node)},
374
+ )
375
+ )
376
+ continue
377
+ if constraint.role is not WildcardRole.STEREO_LIGAND_PORT:
378
+ issues.append(
379
+ StereoMorphismIssue(
380
+ StereoMorphismIssueCode.PORT_ROLE_MISMATCH,
381
+ "A stored wildcard frame reference has a non-stereo role.",
382
+ {"source_node": repr(source_node)},
383
+ )
384
+ )
385
+ continue
386
+ if len(bindings) != 1:
387
+ issues.append(
388
+ StereoMorphismIssue(
389
+ StereoMorphismIssueCode.PORT_BINDING_AMBIGUOUS,
390
+ "A stored stereo port binds more than one local certificate.",
391
+ {"source_node": repr(source_node)},
392
+ )
393
+ )
394
+ continue
395
+ owner, slot = bindings[0]
396
+ if owner != constraint.owner:
397
+ issues.append(
398
+ StereoMorphismIssue(
399
+ StereoMorphismIssueCode.PORT_OWNER_MISMATCH,
400
+ "A stored stereo port owner disagrees with its local frame.",
401
+ {"source_node": repr(source_node)},
402
+ )
403
+ )
404
+ if slot != constraint.stereo_slot:
405
+ issues.append(
406
+ StereoMorphismIssue(
407
+ StereoMorphismIssueCode.PORT_SLOT_MISMATCH,
408
+ "A stored stereo port slot disagrees with its local frame.",
409
+ {"source_node": repr(source_node)},
410
+ )
411
+ )
412
+ if issues:
413
+ raise StereoMorphismError(*issues)
414
+
415
+
416
+ def _topology_supports_implicit_hydrogen(
417
+ descriptor: StereoValue,
418
+ resolver: StereoReferenceResolver,
419
+ graph: nx.Graph,
420
+ owner: int,
421
+ ) -> bool:
422
+ owner_node = _material_node(resolver(owner))
423
+ if owner_node is None:
424
+ return False
425
+ material_count = 0
426
+ for reference in _owned_references(descriptor, owner):
427
+ if parse_virtual_reference(reference) is not None:
428
+ continue
429
+ try:
430
+ node = _material_node(resolver(reference))
431
+ except StereoIdentityError:
432
+ continue
433
+ if node is None:
434
+ continue
435
+ material_count += 1
436
+ if not graph.has_edge(owner_node, node):
437
+ return False
438
+ return material_count > 0
439
+
440
+
441
+ def _implicit_hydrogen_capacity(
442
+ descriptor: StereoValue,
443
+ resolver: StereoReferenceResolver,
444
+ graph: nx.Graph,
445
+ owner: int,
446
+ owner_node: Hashable,
447
+ layer: str,
448
+ ) -> int:
449
+ hcount = _layer_value(graph.nodes[owner_node].get("hcount", 0), layer)
450
+ if not isinstance(hcount, (int, float)):
451
+ return 0
452
+ neighbors = _layer_value(graph.nodes[owner_node].get("neighbors", ()), layer)
453
+ recorded = (
454
+ sum(element == "H" for element in neighbors)
455
+ if isinstance(neighbors, (list, tuple, set))
456
+ else 0
457
+ )
458
+ material = sum(
459
+ _layer_value(graph.nodes[node].get("element"), layer) == "H"
460
+ for node in nx.all_neighbors(graph, owner_node)
461
+ )
462
+ stale_capacity = max(0, min(int(hcount), recorded - material))
463
+ topology_valid = _topology_supports_implicit_hydrogen(
464
+ descriptor,
465
+ resolver,
466
+ graph,
467
+ owner,
468
+ )
469
+ return int(hcount) if stale_capacity or topology_valid else 0
470
+
471
+
472
+ def _resolve_missing_hydrogen(
473
+ reference: Any,
474
+ error: StereoIdentityError,
475
+ descriptor: StereoValue,
476
+ resolver: StereoReferenceResolver,
477
+ graph: nx.Graph,
478
+ layer: str,
479
+ fallback_counts: dict[int, int],
480
+ ) -> Hashable:
481
+ owner = _reference_owner(descriptor, reference) if type(reference) is int else None
482
+ if owner is None:
483
+ raise error
484
+ owner_token = resolver(owner)
485
+ owner_node = _material_node(owner_token)
486
+ if owner_node is None:
487
+ raise error
488
+ count = fallback_counts.get(owner, 0) + 1
489
+ capacity = _implicit_hydrogen_capacity(
490
+ descriptor,
491
+ resolver,
492
+ graph,
493
+ owner,
494
+ owner_node,
495
+ layer,
496
+ )
497
+ if count > capacity:
498
+ raise error
499
+ fallback_counts[owner] = count
500
+ return "virtual", "H", owner_token
501
+
502
+
503
+ def _project_explicit_hydrogen(
504
+ token: Hashable,
505
+ graph: nx.Graph,
506
+ layer: str,
507
+ implicit_h_owner_tokens: frozenset[Hashable],
508
+ ) -> Hashable:
509
+ node = _material_node(token)
510
+ if node is None or graph.degree(node) != 1:
511
+ return token
512
+ if _layer_value(graph.nodes[node].get("element"), layer) != "H":
513
+ return token
514
+ owner_node = next(nx.all_neighbors(graph, node))
515
+ if ("node", owner_node) in implicit_h_owner_tokens:
516
+ return token
517
+ return "virtual", "H", ("node", owner_node)
518
+
519
+
520
+ def _resolve_configuration_reference(
521
+ reference: Any,
522
+ descriptor: StereoValue,
523
+ resolver: StereoReferenceResolver,
524
+ graph: nx.Graph,
525
+ layer: str,
526
+ implicit_h_owner_tokens: frozenset[Hashable],
527
+ fallback_counts: dict[int, int],
528
+ ) -> Hashable:
529
+ try:
530
+ token = resolver(reference)
531
+ except StereoIdentityError as error:
532
+ return _resolve_missing_hydrogen(
533
+ reference,
534
+ error,
535
+ descriptor,
536
+ resolver,
537
+ graph,
538
+ layer,
539
+ fallback_counts,
540
+ )
541
+ return _project_explicit_hydrogen(
542
+ token,
543
+ graph,
544
+ layer,
545
+ implicit_h_owner_tokens,
546
+ )
547
+
548
+
549
+ def _resolved_configuration(
550
+ descriptor: StereoValue,
551
+ resolver: StereoReferenceResolver,
552
+ graph: nx.Graph,
553
+ layer: str,
554
+ ) -> StereoConfiguration:
555
+ configuration = descriptor.configuration
556
+ implicit_h_owners = {
557
+ virtual.center
558
+ for reference in descriptor.atoms
559
+ if (virtual := parse_virtual_reference(reference)) is not None
560
+ and virtual.kind == "H"
561
+ }
562
+ owner_tokens = frozenset(resolver(owner) for owner in implicit_h_owners)
563
+ fallback_counts: dict[int, int] = {}
564
+ frame = tuple(
565
+ _resolve_configuration_reference(
566
+ reference,
567
+ descriptor,
568
+ resolver,
569
+ graph,
570
+ layer,
571
+ owner_tokens,
572
+ fallback_counts,
573
+ )
574
+ for reference in configuration.frame
575
+ )
576
+ return StereoConfiguration(
577
+ configuration.shape,
578
+ frame,
579
+ configuration.specification,
580
+ )
581
+
582
+
583
+ def _morphism_reference_resolver(
584
+ graph: nx.Graph,
585
+ layer: str,
586
+ ) -> StereoReferenceResolver:
587
+ """Return the exact endpoint-local resolver used by morphism certificates."""
588
+ return mapped_reference_resolver(graph, layer)
589
+
590
+
591
+ def _transport_token(
592
+ token: Hashable,
593
+ node_mapping: Mapping[Hashable, Hashable],
594
+ substitutions: Mapping[Hashable, WildcardConstraint] | None = None,
595
+ ) -> Hashable:
596
+ if isinstance(token, tuple) and len(token) == 2 and token[0] == "node":
597
+ node = token[1]
598
+ if node not in node_mapping:
599
+ raise StereoMorphismError(
600
+ StereoMorphismIssue(
601
+ StereoMorphismIssueCode.INVALID_REFERENCE,
602
+ "A material stereo reference lies outside the morphism source.",
603
+ {"node": repr(node)},
604
+ )
605
+ )
606
+ constraint = (substitutions or {}).get(node)
607
+ if constraint is not None and constraint.virtual_kind is not None:
608
+ owner = constraint.owner
609
+ if owner not in node_mapping:
610
+ raise StereoMorphismError(
611
+ StereoMorphismIssue(
612
+ StereoMorphismIssueCode.PORT_OWNER_MISMATCH,
613
+ "A virtual stereo port owner lies outside the morphism.",
614
+ {"owner": repr(owner)},
615
+ )
616
+ )
617
+ return "virtual", constraint.virtual_kind, ("node", node_mapping[owner])
618
+ return "node", node_mapping[node]
619
+ if isinstance(token, tuple) and len(token) == 3 and token[0] == "virtual":
620
+ return token[0], token[1], _transport_token(token[2], node_mapping)
621
+ raise StereoMorphismError(
622
+ StereoMorphismIssue(
623
+ StereoMorphismIssueCode.INVALID_REFERENCE,
624
+ "A stereo resolver produced an unsupported reference token.",
625
+ {"token": repr(token)},
626
+ )
627
+ )
628
+
629
+
630
+ def _transport_configuration(
631
+ configuration: StereoConfiguration,
632
+ node_mapping: Mapping[Hashable, Hashable],
633
+ substitutions: Mapping[Hashable, WildcardConstraint] | None = None,
634
+ ) -> StereoConfiguration:
635
+ return StereoConfiguration(
636
+ configuration.shape,
637
+ tuple(
638
+ _transport_token(token, node_mapping, substitutions)
639
+ for token in configuration.frame
640
+ ),
641
+ configuration.specification,
642
+ )
643
+
644
+
645
+ def _descriptor_alternative(
646
+ descriptor: StereoValue,
647
+ resolver: StereoReferenceResolver,
648
+ graph: nx.Graph,
649
+ layer: str,
650
+ ) -> StereoConfiguration:
651
+ return _resolved_configuration(descriptor.invert(), resolver, graph, layer)
652
+
653
+
654
+ def _information_accepts(
655
+ descriptor: StereoValue,
656
+ source: StereoConfiguration,
657
+ transported_source: StereoConfiguration,
658
+ target: StereoConfiguration,
659
+ resolver: StereoReferenceResolver,
660
+ source_graph: nx.Graph,
661
+ layer: str,
662
+ node_mapping: Mapping[Hashable, Hashable],
663
+ substitutions: Mapping[Hashable, WildcardConstraint],
664
+ policy: StereoInformationPolicy,
665
+ ) -> bool:
666
+ if policy is StereoInformationPolicy.EXACT:
667
+ return transported_source.same_configuration(target)
668
+ if source.specification is StereoSpecification.UNSPECIFIED:
669
+ projected_target = StereoConfiguration(
670
+ target.shape,
671
+ target.frame,
672
+ StereoSpecification.UNSPECIFIED,
673
+ )
674
+ return transported_source.same_configuration(projected_target)
675
+ if policy is StereoInformationPolicy.WILDCARD:
676
+ return transported_source.same_configuration(target)
677
+ alternative = _transport_configuration(
678
+ _descriptor_alternative(descriptor, resolver, source_graph, layer),
679
+ node_mapping,
680
+ substitutions,
681
+ )
682
+ return transported_source.same_configuration(target) or (
683
+ alternative.same_configuration(target)
684
+ )
685
+
686
+
687
+ def _matched_certificate(
688
+ layer: str,
689
+ source: StereoConfiguration,
690
+ target: StereoConfiguration,
691
+ graph_morphism: GraphMorphism,
692
+ policy: StereoInformationPolicy,
693
+ ) -> LocalStereoCertificate:
694
+ transported = _transport_configuration(
695
+ source,
696
+ graph_morphism.mapping,
697
+ graph_morphism.substitutions,
698
+ )
699
+ relation = transported.relation_to(target)
700
+ if relation.witness is None:
701
+ raise StereoMorphismError(
702
+ StereoMorphismIssue(
703
+ StereoMorphismIssueCode.INVALID_CERTIFICATE,
704
+ "Matched stereo configurations lack a replayable relation witness.",
705
+ {"layer": layer, "shape": source.shape},
706
+ )
707
+ )
708
+ return LocalStereoCertificate(
709
+ layer,
710
+ source,
711
+ target,
712
+ relation,
713
+ relation.witness,
714
+ StereoCertificateStatus.MATCHED,
715
+ policy,
716
+ )
717
+
718
+
719
+ def _frame_profile(
720
+ configuration: StereoConfiguration,
721
+ wildcard_nodes: frozenset[Hashable],
722
+ ) -> tuple[Any, ...]:
723
+ profile = []
724
+ for token in configuration.frame:
725
+ if isinstance(token, tuple) and token[0] == "virtual":
726
+ profile.append(("virtual", token[1]))
727
+ elif isinstance(token, tuple) and token[0] == "node":
728
+ profile.append("wildcard" if token[1] in wildcard_nodes else "material")
729
+ else:
730
+ profile.append("invalid")
731
+ return tuple(profile)
732
+
733
+
734
+ def _assign_certificates(
735
+ options: list[list[tuple[int, LocalStereoCertificate]]],
736
+ source_configurations: list[StereoConfiguration],
737
+ source_policies: list[StereoInformationPolicy],
738
+ layer: str,
739
+ mode: StereoPresenceMode,
740
+ index: int = 0,
741
+ used: frozenset[int] = frozenset(),
742
+ ) -> tuple[tuple[int | None, LocalStereoCertificate], ...] | None:
743
+ """Return one deterministic injective descriptor assignment."""
744
+ if index == len(source_configurations):
745
+ return ()
746
+ for target_index, certificate in options[index]:
747
+ if target_index in used:
748
+ continue
749
+ suffix = _assign_certificates(
750
+ options,
751
+ source_configurations,
752
+ source_policies,
753
+ layer,
754
+ mode,
755
+ index + 1,
756
+ used | {target_index},
757
+ )
758
+ if suffix is not None:
759
+ return ((target_index, certificate), *suffix)
760
+ if mode is not StereoPresenceMode.PROPAGATE:
761
+ return None
762
+ certificate = LocalStereoCertificate(
763
+ layer,
764
+ source_configurations[index],
765
+ None,
766
+ None,
767
+ None,
768
+ StereoCertificateStatus.PROPAGATE,
769
+ source_policies[index],
770
+ )
771
+ suffix = _assign_certificates(
772
+ options,
773
+ source_configurations,
774
+ source_policies,
775
+ layer,
776
+ mode,
777
+ index + 1,
778
+ used,
779
+ )
780
+ return None if suffix is None else ((None, certificate), *suffix)
781
+
782
+
783
+ def _layer_certificates(
784
+ graph_morphism: GraphMorphism,
785
+ source_graph: nx.Graph,
786
+ target_graph: nx.Graph,
787
+ layer: str,
788
+ source_registry: Mapping[str, StereoValue],
789
+ target_registry: Mapping[str, StereoValue],
790
+ mode: StereoPresenceMode,
791
+ default_policy: StereoInformationPolicy,
792
+ information_policies: Mapping[str, StereoInformationPolicy | str],
793
+ ) -> tuple[list[LocalStereoCertificate], frozenset[int]]:
794
+ source_resolver = _morphism_reference_resolver(source_graph, layer)
795
+ target_resolver = _morphism_reference_resolver(target_graph, layer)
796
+ source_items = sorted(source_registry.items(), key=lambda item: str(item[0]))
797
+ target_items = sorted(target_registry.items(), key=lambda item: str(item[0]))
798
+ source_configurations = [
799
+ _resolved_configuration(descriptor, source_resolver, source_graph, layer)
800
+ for _key, descriptor in source_items
801
+ ]
802
+ source_policies = [
803
+ StereoInformationPolicy(information_policies.get(str(key), default_policy))
804
+ for key, _descriptor in source_items
805
+ ]
806
+ if mode is StereoPresenceMode.IGNORE:
807
+ return (
808
+ [
809
+ LocalStereoCertificate(
810
+ layer,
811
+ configuration,
812
+ None,
813
+ None,
814
+ None,
815
+ StereoCertificateStatus.IGNORED,
816
+ source_policy,
817
+ )
818
+ for configuration, source_policy in zip(
819
+ source_configurations,
820
+ source_policies,
821
+ )
822
+ ],
823
+ frozenset(),
824
+ )
825
+ target_configurations = [
826
+ _resolved_configuration(descriptor, target_resolver, target_graph, layer)
827
+ for _key, descriptor in target_items
828
+ ]
829
+ options: list[list[tuple[int, LocalStereoCertificate]]] = []
830
+ for (_source_key, descriptor), source_configuration, source_policy in zip(
831
+ source_items,
832
+ source_configurations,
833
+ source_policies,
834
+ ):
835
+ transported = _transport_configuration(
836
+ source_configuration,
837
+ graph_morphism.mapping,
838
+ graph_morphism.substitutions,
839
+ )
840
+ descriptor_options = []
841
+ for target_index, target_configuration in enumerate(target_configurations):
842
+ if not _information_accepts(
843
+ descriptor,
844
+ source_configuration,
845
+ transported,
846
+ target_configuration,
847
+ source_resolver,
848
+ source_graph,
849
+ layer,
850
+ graph_morphism.mapping,
851
+ graph_morphism.substitutions,
852
+ source_policy,
853
+ ):
854
+ continue
855
+ descriptor_options.append(
856
+ (
857
+ target_index,
858
+ _matched_certificate(
859
+ layer,
860
+ source_configuration,
861
+ target_configuration,
862
+ graph_morphism,
863
+ source_policy,
864
+ ),
865
+ )
866
+ )
867
+ options.append(descriptor_options)
868
+ assignment = _assign_certificates(
869
+ options,
870
+ source_configurations,
871
+ source_policies,
872
+ layer,
873
+ mode,
874
+ )
875
+ if assignment is None:
876
+ raise StereoMorphismError(
877
+ StereoMorphismIssue(
878
+ StereoMorphismIssueCode.MISSING_DESCRIPTOR,
879
+ "A source stereo descriptor has no policy-valid target.",
880
+ {"layer": layer},
881
+ )
882
+ )
883
+ certificates = [certificate for _target, certificate in assignment]
884
+ matched = frozenset(
885
+ target for target, _certificate in assignment if target is not None
886
+ )
887
+ return certificates, matched
888
+
889
+
890
+ def _validate_strict_target_extras(
891
+ graph_morphism: GraphMorphism,
892
+ target_graph: nx.Graph,
893
+ target_layers: Mapping[str, Mapping[str, StereoValue]],
894
+ matched_target_ids: frozenset[tuple[str, int]],
895
+ ) -> None:
896
+ target_image = frozenset(graph_morphism.mapping.values())
897
+ for layer, target_registry in target_layers.items():
898
+ resolver = _morphism_reference_resolver(target_graph, layer)
899
+ target_items = sorted(target_registry.items(), key=lambda item: str(item[0]))
900
+ for target_index, (_key, descriptor) in enumerate(target_items):
901
+ configuration = _resolved_configuration(
902
+ descriptor,
903
+ resolver,
904
+ target_graph,
905
+ layer,
906
+ )
907
+ incident = any(
908
+ isinstance(token, tuple)
909
+ and token[0] == "node"
910
+ and token[1] in target_image
911
+ for token in configuration.frame
912
+ )
913
+ if incident and (layer, target_index) not in matched_target_ids:
914
+ raise StereoMorphismError(
915
+ StereoMorphismIssue(
916
+ StereoMorphismIssueCode.EXTRA_DESCRIPTOR,
917
+ "Strict stereo matching forbids an unmatched target "
918
+ "descriptor incident to the image.",
919
+ {"layer": layer},
920
+ )
921
+ )
922
+
923
+
924
+ def _build_certificates(
925
+ graph_morphism: GraphMorphism,
926
+ source_graph: nx.Graph,
927
+ target_graph: nx.Graph,
928
+ mode: StereoPresenceMode,
929
+ policy: StereoInformationPolicy,
930
+ information_policies: Mapping[str, StereoInformationPolicy | str],
931
+ ) -> tuple[LocalStereoCertificate, ...]:
932
+ source_layers = stereo_registry_layers(source_graph)
933
+ target_layers = stereo_registry_layers(target_graph)
934
+ certificates: list[LocalStereoCertificate] = []
935
+ matched_target_ids: set[tuple[str, int]] = set()
936
+ for layer, source_registry in sorted(source_layers.items()):
937
+ layer_certificates, matched = _layer_certificates(
938
+ graph_morphism,
939
+ source_graph,
940
+ target_graph,
941
+ layer,
942
+ source_registry,
943
+ target_layers.get(layer, {}),
944
+ mode,
945
+ policy,
946
+ information_policies,
947
+ )
948
+ certificates.extend(layer_certificates)
949
+ matched_target_ids.update((layer, target) for target in matched)
950
+ if mode is StereoPresenceMode.STRICT:
951
+ _validate_strict_target_extras(
952
+ graph_morphism,
953
+ target_graph,
954
+ target_layers,
955
+ frozenset(matched_target_ids),
956
+ )
957
+ return tuple(certificates)