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,946 @@
1
+ """Exact canonicalization for the complete configured stereo catalogue."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable, Mapping, Sequence
6
+ from dataclasses import dataclass, replace
7
+ import hashlib
8
+ from typing import Any, Hashable
9
+
10
+ import networkx as nx
11
+
12
+ from synkit.Graph.Canon.exact import ExactColoredGraphCanonicalizer
13
+
14
+ from .canonical import (
15
+ AtomVertex,
16
+ AttributeSelector,
17
+ BondVertex,
18
+ CanonicalStereographResult,
19
+ StereoLocusVertex,
20
+ StereoPortVertex,
21
+ StereoSlotVertex,
22
+ StereoTupleVertex,
23
+ STEREOGRAPH_SCHEMA,
24
+ StereographMirrorResult,
25
+ StereographMirrorStatus,
26
+ VirtualResourceVertex,
27
+ _DEFAULT_ATOM_COLOR_KEYS,
28
+ _DEFAULT_BOND_COLOR_KEYS,
29
+ _Expansion,
30
+ _add_coloured_node,
31
+ _add_configuration_orbit,
32
+ _expand_base_graph,
33
+ _material_port_resource,
34
+ _project_witnesses,
35
+ _rdkit_graph_and_registry,
36
+ )
37
+ from .descriptors import (
38
+ AtropBondStereo,
39
+ CumuleneAxisStereo,
40
+ ExtendedCisTransStereo,
41
+ OctahedralStereo,
42
+ PlanarBondStereo,
43
+ Reference,
44
+ SquarePlanarStereo,
45
+ StereoValue,
46
+ TetrahedralStereo,
47
+ TrigonalBipyramidalStereo,
48
+ parse_virtual_reference,
49
+ )
50
+ from .extended_descriptors import HelicalStereo, PlanarChiralityStereo
51
+ from .global_configured import add_framework as _add_framework
52
+ from .global_stereo import FrameworkStereo
53
+ from .orbits import StereoSpecification
54
+ from .rdkit_resonance import (
55
+ MirrorIdentityProfile,
56
+ _is_terminal_phosphate_resonance_locus,
57
+ _resonance_graphs,
58
+ )
59
+
60
+ # Compatibility alias for callers that adopted the former configured-prefixed
61
+ # API. SynKit now exposes one canonical stereograph model.
62
+ CONFIGURED_STEREOGRAPH_SCHEMA = STEREOGRAPH_SCHEMA
63
+
64
+ AtomDescriptor = (
65
+ TetrahedralStereo
66
+ | SquarePlanarStereo
67
+ | TrigonalBipyramidalStereo
68
+ | OctahedralStereo
69
+ )
70
+ BondDescriptor = PlanarBondStereo | AtropBondStereo
71
+ ConfiguredDescriptor = StereoValue
72
+ CONFIGURED_DESCRIPTOR_TYPES = (
73
+ TetrahedralStereo,
74
+ SquarePlanarStereo,
75
+ TrigonalBipyramidalStereo,
76
+ OctahedralStereo,
77
+ PlanarBondStereo,
78
+ AtropBondStereo,
79
+ CumuleneAxisStereo,
80
+ ExtendedCisTransStereo,
81
+ HelicalStereo,
82
+ PlanarChiralityStereo,
83
+ FrameworkStereo,
84
+ )
85
+
86
+ _ACS_TOPOLOGY_ATOM_COLOR = ("element", "isotope", "hcount")
87
+
88
+
89
+ def _connectivity_bond_color(_attributes: Mapping[str, Any]) -> str:
90
+ """Collapse bond placement for the explicit ACS topology profile."""
91
+ return "bond"
92
+
93
+
94
+ def _mirror_profile_options(
95
+ identity_profile: MirrorIdentityProfile,
96
+ ) -> dict[str, AttributeSelector]:
97
+ if identity_profile == "acs_topology":
98
+ return {
99
+ "atom_color": _ACS_TOPOLOGY_ATOM_COLOR,
100
+ "bond_color": _connectivity_bond_color,
101
+ }
102
+ if identity_profile in {"chemical", "lewis_state"}:
103
+ return {}
104
+ raise ValueError(
105
+ "Mirror identity profile must be 'chemical', 'lewis_state', "
106
+ "or 'acs_topology'."
107
+ )
108
+
109
+
110
+ def _record_mirror_profile(
111
+ result: StereographMirrorResult,
112
+ identity_profile: MirrorIdentityProfile,
113
+ ) -> StereographMirrorResult:
114
+ if identity_profile != "chemical" and result.original is not None:
115
+ from .rdkit_identity import profile_certificate
116
+
117
+ original = profile_certificate(result.original, identity_profile)
118
+ mirror = profile_certificate(result.mirror, identity_profile)
119
+ result = replace(
120
+ result, original=original, mirror=mirror,
121
+ original_identity=original, mirror_identity=mirror,
122
+ )
123
+ return replace(
124
+ result,
125
+ method=f"{result.method}:{identity_profile}",
126
+ identity_profile=identity_profile,
127
+ )
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class _Prepared:
132
+ expansion: _Expansion
133
+ base: nx.Graph
134
+ atoms: Mapping[Hashable, AtomVertex]
135
+ bonds: Mapping[tuple[Hashable, Hashable], BondVertex]
136
+
137
+
138
+ def _prepare(
139
+ base_graph: nx.Graph,
140
+ atom_color: AttributeSelector,
141
+ bond_color: AttributeSelector,
142
+ ) -> _Prepared:
143
+ base = base_graph.copy()
144
+ expansion = _expand_base_graph(
145
+ base,
146
+ atom_color=atom_color,
147
+ bond_color=bond_color,
148
+ )
149
+ atoms = {
150
+ node.reference: node for node in expansion.graph if isinstance(node, AtomVertex)
151
+ }
152
+ bonds: dict[tuple[Hashable, Hashable], BondVertex] = {}
153
+ for node in expansion.graph:
154
+ if not isinstance(node, BondVertex):
155
+ continue
156
+ left, right = expansion.bond_endpoints[node.index]
157
+ bonds[(left, right)] = node
158
+ bonds[(right, left)] = node
159
+ return _Prepared(expansion, base, atoms, bonds)
160
+
161
+
162
+ def _port(
163
+ prepared: _Prepared,
164
+ locus: StereoLocusVertex,
165
+ locus_index: int,
166
+ port_position: int,
167
+ owner: Hashable,
168
+ reference: Reference,
169
+ geometry: str,
170
+ ) -> StereoPortVertex:
171
+ port = StereoPortVertex(locus_index, port_position)
172
+ _add_coloured_node(
173
+ prepared.expansion.graph,
174
+ port,
175
+ ("stereo_port", geometry),
176
+ )
177
+ prepared.expansion.graph.add_edge(locus, port)
178
+ virtual = parse_virtual_reference(reference)
179
+ if virtual is None:
180
+ resource: Hashable = _material_port_resource(
181
+ prepared.base,
182
+ prepared.bonds,
183
+ owner,
184
+ reference,
185
+ geometry=geometry,
186
+ )
187
+ else:
188
+ if virtual.center != owner:
189
+ raise ValueError(
190
+ f"Virtual ligand {reference!r} does not belong to {owner!r}."
191
+ )
192
+ resource = VirtualResourceVertex(
193
+ locus_index,
194
+ port_position,
195
+ virtual.kind,
196
+ )
197
+ _add_coloured_node(
198
+ prepared.expansion.graph,
199
+ resource,
200
+ ("virtual_resource", virtual.kind),
201
+ )
202
+ prepared.expansion.graph.add_edge(prepared.atoms[owner], resource)
203
+ prepared.expansion.graph.add_edge(port, resource)
204
+ return port
205
+
206
+
207
+ def _locus(
208
+ prepared: _Prepared,
209
+ index: int,
210
+ geometry: str,
211
+ ) -> StereoLocusVertex:
212
+ locus = StereoLocusVertex(index)
213
+ _add_coloured_node(
214
+ prepared.expansion.graph,
215
+ locus,
216
+ ("stereo_locus", geometry),
217
+ )
218
+ return locus
219
+
220
+
221
+ def _add_atom_descriptor(
222
+ prepared: _Prepared,
223
+ descriptor: AtomDescriptor,
224
+ index: int,
225
+ ) -> None:
226
+ if descriptor.specification is not StereoSpecification.FIXED:
227
+ raise ValueError(f"Configured {descriptor.descriptor_class} must be fixed.")
228
+ center = descriptor.center
229
+ if center not in prepared.atoms:
230
+ raise ValueError(f"Stereo center {center!r} is absent.")
231
+ locus = _locus(prepared, index, descriptor.descriptor_class)
232
+ prepared.expansion.graph.add_edge(locus, prepared.atoms[center])
233
+ targets: dict[Reference, Hashable] = {center: prepared.atoms[center]}
234
+ for position, reference in enumerate(descriptor.atoms[1:]):
235
+ targets[reference] = _port(
236
+ prepared,
237
+ locus,
238
+ index,
239
+ position,
240
+ center,
241
+ reference,
242
+ descriptor.descriptor_class,
243
+ )
244
+ _add_configuration_orbit(
245
+ prepared.expansion.graph,
246
+ locus,
247
+ descriptor, # type: ignore[arg-type]
248
+ index,
249
+ targets,
250
+ tuple(range(1, len(descriptor.atoms))),
251
+ )
252
+
253
+
254
+ def _add_bond_descriptor(
255
+ prepared: _Prepared,
256
+ descriptor: BondDescriptor,
257
+ index: int,
258
+ ) -> None:
259
+ if descriptor.specification is not StereoSpecification.FIXED:
260
+ raise ValueError(f"Configured {descriptor.descriptor_class} must be fixed.")
261
+ left, right = descriptor.atoms[2:4]
262
+ if (left, right) not in prepared.bonds:
263
+ raise ValueError(f"Stereo bond {left!r}-{right!r} is absent.")
264
+ locus = _locus(prepared, index, descriptor.descriptor_class)
265
+ prepared.expansion.graph.add_edge(locus, prepared.bonds[(left, right)])
266
+ targets: dict[Reference, Hashable] = {
267
+ left: prepared.atoms[left],
268
+ right: prepared.atoms[right],
269
+ }
270
+ owners = (left, left, right, right)
271
+ for port_position, (frame_position, owner) in enumerate(zip((0, 1, 4, 5), owners)):
272
+ reference = descriptor.atoms[frame_position]
273
+ targets[reference] = _port(
274
+ prepared,
275
+ locus,
276
+ index,
277
+ port_position,
278
+ owner,
279
+ reference,
280
+ descriptor.descriptor_class,
281
+ )
282
+ _add_configuration_orbit(
283
+ prepared.expansion.graph,
284
+ locus,
285
+ descriptor,
286
+ index,
287
+ targets,
288
+ tuple(range(6)),
289
+ )
290
+
291
+
292
+ def _validate_path(
293
+ prepared: _Prepared,
294
+ path: Sequence[int],
295
+ *,
296
+ cyclic: bool = False,
297
+ ) -> tuple[BondVertex, ...]:
298
+ if any(atom not in prepared.atoms for atom in path):
299
+ raise ValueError("Configured stereo path contains an absent atom.")
300
+ pairs = list(zip(path, path[1:]))
301
+ if cyclic:
302
+ pairs.append((path[-1], path[0]))
303
+ try:
304
+ return tuple(prepared.bonds[pair] for pair in pairs)
305
+ except KeyError as exc:
306
+ raise ValueError("Configured stereo path is not continuous.") from exc
307
+
308
+
309
+ def _add_cumulene_path(
310
+ prepared: _Prepared,
311
+ descriptor: CumuleneAxisStereo | ExtendedCisTransStereo,
312
+ index: int,
313
+ ) -> None:
314
+ if descriptor.specification is not StereoSpecification.FIXED:
315
+ raise ValueError("Configured cumulene path must be fixed.")
316
+ path = (
317
+ descriptor.axis_path
318
+ if isinstance(descriptor, CumuleneAxisStereo)
319
+ else descriptor.path
320
+ )
321
+ path_bonds = _validate_path(prepared, path)
322
+ locus = _locus(prepared, index, descriptor.descriptor_class)
323
+ for bond in path_bonds:
324
+ prepared.expansion.graph.add_edge(locus, bond)
325
+ left, right = path[0], path[-1]
326
+ targets: dict[Reference, Hashable] = {
327
+ left: prepared.atoms[left],
328
+ right: prepared.atoms[right],
329
+ }
330
+ references = (*descriptor.terminal_frames[0], *descriptor.terminal_frames[1])
331
+ for position, (owner, reference) in enumerate(
332
+ zip((left, left, right, right), references)
333
+ ):
334
+ targets[reference] = _port(
335
+ prepared,
336
+ locus,
337
+ index,
338
+ position,
339
+ owner,
340
+ reference,
341
+ descriptor.descriptor_class,
342
+ )
343
+ _add_configuration_orbit(
344
+ prepared.expansion.graph,
345
+ locus,
346
+ descriptor, # type: ignore[arg-type]
347
+ index,
348
+ targets,
349
+ tuple(range(6)),
350
+ )
351
+
352
+
353
+ def _add_frames(
354
+ graph: nx.Graph,
355
+ locus: StereoLocusVertex,
356
+ index: int,
357
+ geometry: str,
358
+ frames: Sequence[Sequence[int]],
359
+ atoms: Mapping[Hashable, AtomVertex],
360
+ ) -> None:
361
+ for tuple_index, frame in enumerate(frames):
362
+ relation = StereoTupleVertex(index, tuple_index)
363
+ _add_coloured_node(graph, relation, ("stereo_tuple", geometry))
364
+ graph.add_edge(locus, relation)
365
+ for position, reference in enumerate(frame):
366
+ slot = StereoSlotVertex(index, tuple_index, position)
367
+ _add_coloured_node(
368
+ graph,
369
+ slot,
370
+ ("stereo_slot", geometry, position),
371
+ )
372
+ graph.add_edge(relation, slot)
373
+ graph.add_edge(slot, atoms[reference])
374
+
375
+
376
+ def _path_variants(
377
+ path: tuple[int, ...],
378
+ cyclic: bool,
379
+ ) -> tuple[tuple[int, ...], ...]:
380
+ if not cyclic:
381
+ return path, tuple(reversed(path))
382
+ reverse = tuple(reversed(path))
383
+ return tuple(
384
+ sequence[offset:] + sequence[:offset]
385
+ for sequence in (path, reverse)
386
+ for offset in range(len(path))
387
+ )
388
+
389
+
390
+ def _add_helical(
391
+ prepared: _Prepared,
392
+ descriptor: HelicalStereo,
393
+ index: int,
394
+ ) -> None:
395
+ if descriptor.specification is not StereoSpecification.FIXED:
396
+ raise ValueError("Configured helical path must be fixed.")
397
+ _validate_path(prepared, descriptor.path, cyclic=descriptor.cyclic)
398
+ locus = StereoLocusVertex(index)
399
+ _add_coloured_node(
400
+ prepared.expansion.graph,
401
+ locus,
402
+ (
403
+ "stereo_locus",
404
+ descriptor.descriptor_class,
405
+ descriptor.parity,
406
+ descriptor.cyclic,
407
+ descriptor.coupling_id,
408
+ ),
409
+ )
410
+ _add_frames(
411
+ prepared.expansion.graph,
412
+ locus,
413
+ index,
414
+ descriptor.descriptor_class,
415
+ _path_variants(descriptor.path, descriptor.cyclic),
416
+ prepared.atoms,
417
+ )
418
+
419
+
420
+ def _add_planar_chirality(
421
+ prepared: _Prepared,
422
+ descriptor: PlanarChiralityStereo,
423
+ index: int,
424
+ ) -> None:
425
+ if descriptor.specification is not StereoSpecification.FIXED:
426
+ raise ValueError("Configured planar chirality must be fixed.")
427
+ _validate_path(prepared, descriptor.plane_atoms, cyclic=True)
428
+ if descriptor.pilot not in prepared.atoms:
429
+ raise ValueError("Planar-chirality pilot atom is absent.")
430
+ locus = _locus(prepared, index, descriptor.descriptor_class)
431
+ plane = (
432
+ descriptor.plane_atoms
433
+ if descriptor.parity == 1
434
+ else tuple(reversed(descriptor.plane_atoms))
435
+ )
436
+ frames = tuple(
437
+ (descriptor.pilot, *(plane[offset:] + plane[:offset]))
438
+ for offset in range(len(plane))
439
+ )
440
+ _add_frames(
441
+ prepared.expansion.graph,
442
+ locus,
443
+ index,
444
+ descriptor.descriptor_class,
445
+ frames,
446
+ prepared.atoms,
447
+ )
448
+
449
+
450
+ def _locus_key(descriptor: ConfiguredDescriptor) -> tuple[str, Hashable]:
451
+ if isinstance(descriptor, FrameworkStereo):
452
+ return "global", descriptor.support_atoms
453
+ if isinstance(
454
+ descriptor,
455
+ (
456
+ TetrahedralStereo,
457
+ SquarePlanarStereo,
458
+ TrigonalBipyramidalStereo,
459
+ OctahedralStereo,
460
+ ),
461
+ ):
462
+ return "atom", descriptor.center
463
+ if isinstance(descriptor, (PlanarBondStereo, AtropBondStereo)):
464
+ return "bond", frozenset(descriptor.atoms[2:4])
465
+ if isinstance(descriptor, (CumuleneAxisStereo, ExtendedCisTransStereo)):
466
+ path = (
467
+ descriptor.axis_path
468
+ if isinstance(descriptor, CumuleneAxisStereo)
469
+ else descriptor.path
470
+ )
471
+ return descriptor.descriptor_class, min(
472
+ path,
473
+ tuple(reversed(path)),
474
+ )
475
+ if isinstance(descriptor, HelicalStereo):
476
+ return "path", (descriptor.canonical_path, descriptor.coupling_id)
477
+ return "plane", (descriptor.canonical_plane, descriptor.pilot)
478
+
479
+
480
+ def _expanded(
481
+ base_graph: nx.Graph,
482
+ descriptors: tuple[ConfiguredDescriptor, ...],
483
+ *,
484
+ atom_color: AttributeSelector,
485
+ bond_color: AttributeSelector,
486
+ ) -> _Expansion:
487
+ prepared = _prepare(base_graph, atom_color, bond_color)
488
+ seen: set[tuple[str, Hashable]] = set()
489
+ global_supports: list[frozenset[int]] = []
490
+ for index, descriptor in enumerate(descriptors):
491
+ key = _locus_key(descriptor)
492
+ if key in seen:
493
+ raise ValueError(f"Duplicate configured stereo locus at {key[1]!r}.")
494
+ seen.add(key)
495
+ if isinstance(descriptor, FrameworkStereo):
496
+ if any(descriptor.support_atoms & support for support in global_supports):
497
+ raise ValueError("Overlapping framework stereo supports are invalid.")
498
+ global_supports.append(descriptor.support_atoms)
499
+ _add_framework(prepared, descriptor, index)
500
+ elif isinstance(
501
+ descriptor,
502
+ (
503
+ TetrahedralStereo,
504
+ SquarePlanarStereo,
505
+ TrigonalBipyramidalStereo,
506
+ OctahedralStereo,
507
+ ),
508
+ ):
509
+ _add_atom_descriptor(prepared, descriptor, index)
510
+ elif isinstance(descriptor, (PlanarBondStereo, AtropBondStereo)):
511
+ _add_bond_descriptor(prepared, descriptor, index)
512
+ elif isinstance(
513
+ descriptor,
514
+ (CumuleneAxisStereo, ExtendedCisTransStereo),
515
+ ):
516
+ _add_cumulene_path(prepared, descriptor, index)
517
+ elif isinstance(descriptor, HelicalStereo):
518
+ _add_helical(prepared, descriptor, index)
519
+ else:
520
+ _add_planar_chirality(prepared, descriptor, index)
521
+ return prepared.expansion
522
+
523
+
524
+ def expand_configured_stereograph(
525
+ base_graph: nx.Graph,
526
+ descriptors: Iterable[ConfiguredDescriptor],
527
+ *,
528
+ atom_color: AttributeSelector = _DEFAULT_ATOM_COLOR_KEYS,
529
+ bond_color: AttributeSelector = _DEFAULT_BOND_COLOR_KEYS,
530
+ ) -> nx.Graph:
531
+ return _expanded(
532
+ base_graph,
533
+ tuple(descriptors),
534
+ atom_color=atom_color,
535
+ bond_color=bond_color,
536
+ ).graph
537
+
538
+
539
+ def canonicalize_configured_stereograph(
540
+ base_graph: nx.Graph,
541
+ descriptors: Iterable[ConfiguredDescriptor],
542
+ *,
543
+ atom_color: AttributeSelector = _DEFAULT_ATOM_COLOR_KEYS,
544
+ bond_color: AttributeSelector = _DEFAULT_BOND_COLOR_KEYS,
545
+ enumerate_automorphism_group: bool = True,
546
+ ) -> CanonicalStereographResult:
547
+ expansion = _expanded(
548
+ base_graph,
549
+ tuple(descriptors),
550
+ atom_color=atom_color,
551
+ bond_color=bond_color,
552
+ )
553
+ auxiliary = ExactColoredGraphCanonicalizer(
554
+ expansion.graph,
555
+ node_color="color",
556
+ edge_color=None,
557
+ prune_automorphisms=True,
558
+ enumerate_automorphism_group=enumerate_automorphism_group,
559
+ ).canonicalize()
560
+ atom_order = tuple(
561
+ node.reference
562
+ for node in auxiliary.canonical_order
563
+ if isinstance(node, AtomVertex)
564
+ )
565
+ bond_order = tuple(
566
+ expansion.bond_endpoints[node.index]
567
+ for node in auxiliary.canonical_order
568
+ if isinstance(node, BondVertex)
569
+ )
570
+ locus_order = tuple(
571
+ node.index
572
+ for node in auxiliary.canonical_order
573
+ if isinstance(node, StereoLocusVertex)
574
+ )
575
+ port_order = tuple(
576
+ node for node in auxiliary.canonical_order if isinstance(node, StereoPortVertex)
577
+ )
578
+ code = f"{STEREOGRAPH_SCHEMA}\n{auxiliary.canonical_code}"
579
+ return CanonicalStereographResult(
580
+ schema=STEREOGRAPH_SCHEMA,
581
+ canonical_code=code,
582
+ canonical_digest=hashlib.sha256(code.encode("utf-8")).hexdigest(),
583
+ atom_order=atom_order,
584
+ bond_order=bond_order,
585
+ locus_order=locus_order,
586
+ port_order=port_order,
587
+ atom_automorphisms=_project_witnesses(auxiliary, AtomVertex),
588
+ locus_automorphisms=_project_witnesses(
589
+ auxiliary,
590
+ StereoLocusVertex,
591
+ ),
592
+ port_automorphisms=_project_witnesses(auxiliary, StereoPortVertex),
593
+ auxiliary=auxiliary,
594
+ )
595
+
596
+
597
+ def canonicalize_configured_registry(
598
+ base_graph: nx.Graph,
599
+ registry: Mapping[str, StereoValue],
600
+ **options: Any,
601
+ ) -> CanonicalStereographResult:
602
+ unsupported = tuple(
603
+ descriptor.descriptor_class
604
+ for descriptor in registry.values()
605
+ if not isinstance(descriptor, CONFIGURED_DESCRIPTOR_TYPES)
606
+ )
607
+ if unsupported:
608
+ raise TypeError(
609
+ "Configured canonicalization cannot omit families: "
610
+ + ", ".join(sorted(set(unsupported)))
611
+ )
612
+ return canonicalize_configured_stereograph(
613
+ base_graph,
614
+ registry.values(), # type: ignore[arg-type]
615
+ **options,
616
+ )
617
+
618
+
619
+ def mirror_configured_descriptor(
620
+ descriptor: ConfiguredDescriptor,
621
+ ) -> ConfiguredDescriptor:
622
+ if isinstance(descriptor, FrameworkStereo):
623
+ return descriptor.invert()
624
+ if isinstance(descriptor, TetrahedralStereo):
625
+ return descriptor.opposite()
626
+ if isinstance(
627
+ descriptor,
628
+ (
629
+ SquarePlanarStereo,
630
+ PlanarBondStereo,
631
+ ExtendedCisTransStereo,
632
+ ),
633
+ ):
634
+ return descriptor
635
+ if isinstance(
636
+ descriptor,
637
+ (
638
+ TrigonalBipyramidalStereo,
639
+ OctahedralStereo,
640
+ AtropBondStereo,
641
+ CumuleneAxisStereo,
642
+ HelicalStereo,
643
+ PlanarChiralityStereo,
644
+ ),
645
+ ):
646
+ return descriptor.invert()
647
+ raise TypeError(f"Unsupported configured mirror family: {type(descriptor)!r}.")
648
+
649
+
650
+ def _configured_mirror_boundary(
651
+ registry: Mapping[str, StereoValue],
652
+ *,
653
+ incomplete_loci: Iterable[str] = (),
654
+ unsupported_loci: Iterable[str] = (),
655
+ ) -> StereographMirrorResult | None:
656
+ """Return a fail-closed boundary result, or ``None`` when definitive."""
657
+ unsupported_entries = tuple(
658
+ (key, value.descriptor_class)
659
+ for key, value in registry.items()
660
+ if not isinstance(value, CONFIGURED_DESCRIPTOR_TYPES)
661
+ )
662
+ declared_unsupported = tuple(unsupported_loci)
663
+ if unsupported_entries or declared_unsupported:
664
+ return StereographMirrorResult(
665
+ StereographMirrorStatus.UNSUPPORTED,
666
+ len(registry),
667
+ unsupported_loci=tuple(
668
+ sorted(
669
+ {
670
+ *declared_unsupported,
671
+ *(key for key, _family in unsupported_entries),
672
+ }
673
+ )
674
+ ),
675
+ unsupported_families=tuple(
676
+ sorted({family for _key, family in unsupported_entries})
677
+ ),
678
+ method="exact_canonical_stereograph_mirror_comparison",
679
+ )
680
+ unknown = tuple(
681
+ key
682
+ for key, descriptor in registry.items()
683
+ if descriptor.specification is StereoSpecification.UNSPECIFIED
684
+ )
685
+ missing = tuple(sorted({*incomplete_loci, *unknown}))
686
+ if missing:
687
+ return StereographMirrorResult(
688
+ StereographMirrorStatus.INCOMPLETE,
689
+ len(registry),
690
+ incomplete_loci=missing,
691
+ method="exact_canonical_stereograph_mirror_comparison",
692
+ )
693
+ return None
694
+
695
+
696
+ def classify_configured_stereograph_mirror(
697
+ base_graph: nx.Graph,
698
+ registry: Mapping[str, StereoValue],
699
+ *,
700
+ incomplete_loci: Iterable[str] = (),
701
+ unsupported_loci: Iterable[str] = (),
702
+ atom_color: AttributeSelector = _DEFAULT_ATOM_COLOR_KEYS,
703
+ bond_color: AttributeSelector = _DEFAULT_BOND_COLOR_KEYS,
704
+ ) -> StereographMirrorResult:
705
+ boundary = _configured_mirror_boundary(
706
+ registry,
707
+ incomplete_loci=incomplete_loci,
708
+ unsupported_loci=unsupported_loci,
709
+ )
710
+ if boundary is not None:
711
+ return boundary
712
+ original = canonicalize_configured_registry(
713
+ base_graph,
714
+ registry,
715
+ atom_color=atom_color,
716
+ bond_color=bond_color,
717
+ )
718
+ mirrored = {
719
+ key: mirror_configured_descriptor(value) # type: ignore[arg-type]
720
+ for key, value in registry.items()
721
+ }
722
+ mirror = canonicalize_configured_registry(
723
+ base_graph,
724
+ mirrored,
725
+ atom_color=atom_color,
726
+ bond_color=bond_color,
727
+ )
728
+ achiral = original.same_stereograph(mirror)
729
+ return StereographMirrorResult(
730
+ (
731
+ StereographMirrorStatus.ACHIRAL
732
+ if achiral
733
+ else StereographMirrorStatus.CHIRAL
734
+ ),
735
+ len(registry),
736
+ original,
737
+ mirror,
738
+ tuple(mirrored.values()),
739
+ (tuple(zip(original.atom_order, mirror.atom_order)) if achiral else None),
740
+ method="exact_canonical_stereograph_mirror_comparison",
741
+ )
742
+
743
+
744
+ def _classify_resonance_family_mirror(
745
+ molecule: Any,
746
+ registry: Mapping[str, StereoValue],
747
+ *,
748
+ incomplete_loci: Iterable[str] = (),
749
+ unsupported_loci: Iterable[str] = (),
750
+ ) -> StereographMirrorResult:
751
+ """Compare the complete Lewis-resonance family with its mirror exactly."""
752
+ boundary = _configured_mirror_boundary(
753
+ registry,
754
+ incomplete_loci=incomplete_loci,
755
+ unsupported_loci=unsupported_loci,
756
+ )
757
+ if boundary is not None:
758
+ return boundary
759
+
760
+ mirrored = {
761
+ key: mirror_configured_descriptor(value) # type: ignore[arg-type]
762
+ for key, value in registry.items()
763
+ }
764
+ originals = []
765
+ mirrors = []
766
+ for graph in _resonance_graphs(molecule):
767
+ originals.append(canonicalize_configured_registry(graph, registry))
768
+ mirrors.append(canonicalize_configured_registry(graph, mirrored))
769
+ from .rdkit_identity import ResonanceFamilyCanonicalResult, profile_certificate
770
+
771
+ original_members = {
772
+ result.canonical_code: profile_certificate(result, "lewis_state") for result in originals
773
+ }
774
+ mirror_members = {
775
+ result.canonical_code: profile_certificate(result, "lewis_state") for result in mirrors
776
+ }
777
+ original_family = ResonanceFamilyCanonicalResult(
778
+ tuple(original_members[key] for key in sorted(original_members))
779
+ )
780
+ mirror_family = ResonanceFamilyCanonicalResult(
781
+ tuple(mirror_members[key] for key in sorted(mirror_members))
782
+ )
783
+ originals.sort(key=lambda result: result.canonical_code)
784
+ mirrors.sort(key=lambda result: result.canonical_code)
785
+ mirror_by_code = {result.canonical_code: result for result in mirrors}
786
+ matched = next(
787
+ (
788
+ (original, mirror_by_code[original.canonical_code])
789
+ for original in originals
790
+ if original.canonical_code in mirror_by_code
791
+ ),
792
+ None,
793
+ )
794
+ achiral = original_family.same_stereograph(mirror_family)
795
+ original, mirror = matched if achiral else (originals[0], mirrors[0])
796
+ return StereographMirrorResult(
797
+ (
798
+ StereographMirrorStatus.ACHIRAL
799
+ if achiral
800
+ else StereographMirrorStatus.CHIRAL
801
+ ),
802
+ len(registry),
803
+ original,
804
+ mirror,
805
+ tuple(mirrored.values()),
806
+ (tuple(zip(original.atom_order, mirror.atom_order)) if achiral else None),
807
+ method="exact_resonance_family_stereograph_mirror_comparison",
808
+ original_identity=original_family,
809
+ mirror_identity=mirror_family,
810
+ )
811
+
812
+
813
+ def canonicalize_rdkit_configured_stereograph(
814
+ molecule: Any,
815
+ *,
816
+ identity_profile: MirrorIdentityProfile = "lewis_state",
817
+ ):
818
+ """Canonicalize source-declared stereo with a persisted identity profile.
819
+
820
+ The existing default retains the supplied Lewis drawing. The chemical
821
+ profile instead returns a resonance-family result with member-specific
822
+ witnesses. Unspecified descriptors are not completed by this operation.
823
+ """
824
+ from .rdkit_identity import canonicalize_rdkit_identity
825
+
826
+ return canonicalize_rdkit_identity(molecule, identity_profile=identity_profile)
827
+
828
+
829
+ def _classify_rdkit_identity_profile(
830
+ molecule: Any,
831
+ graph: nx.Graph,
832
+ registry: Mapping[str, StereoValue],
833
+ identity_profile: MirrorIdentityProfile,
834
+ *,
835
+ incomplete_loci: Iterable[str] = (),
836
+ unsupported_loci: Iterable[str] = (),
837
+ ) -> StereographMirrorResult:
838
+ if identity_profile == "chemical":
839
+ result = _classify_resonance_family_mirror(
840
+ molecule,
841
+ registry,
842
+ incomplete_loci=incomplete_loci,
843
+ unsupported_loci=unsupported_loci,
844
+ )
845
+ else:
846
+ result = classify_configured_stereograph_mirror(
847
+ graph,
848
+ registry,
849
+ incomplete_loci=incomplete_loci,
850
+ unsupported_loci=unsupported_loci,
851
+ **_mirror_profile_options(identity_profile),
852
+ )
853
+ return _record_mirror_profile(result, identity_profile)
854
+
855
+
856
+ def classify_rdkit_configured_stereograph_mirror(
857
+ molecule: Any,
858
+ *,
859
+ require_complete: bool = True,
860
+ identity_profile: MirrorIdentityProfile = "chemical",
861
+ ) -> StereographMirrorResult:
862
+ """Classify an RDKit molecule with the exact stereograph mirror test.
863
+
864
+ The ``chemical`` default compares the complete enumerated resonance family:
865
+ genuine bond-order differences remain distinct, while alternative
866
+ resonance/Kekule drawings do not create false chirality.
867
+ ``lewis_state`` audits the precise supplied charge/bond-order drawing.
868
+ ``acs_topology`` reproduces the external benchmark's connectivity-only
869
+ identity convention and must not be reported as literal Lewis-structure
870
+ chirality.
871
+
872
+ Perception is used only to identify supported loci whose configuration is
873
+ absent. It does not supply a configuration or a handedness label.
874
+ Set ``require_complete=False`` to classify exactly the source-declared
875
+ configured stereograph while leaving every undeclared locus unconstrained.
876
+ Enhanced stereo groups remain outside the single-stereoisomer contract.
877
+ A binary verdict requires every required locus to be resolved. Mirror
878
+ inequality of a partial registry is not a proof about its completions:
879
+ adding a carrier can restore a symmetry exchanging equivalent carriers.
880
+ """
881
+ from rdkit import Chem
882
+
883
+ from synkit.Chem.Molecule.stereo_perception import (
884
+ StereoConfigurationState,
885
+ detect_potential_stereo_elements,
886
+ )
887
+
888
+ _mirror_profile_options(identity_profile)
889
+ graph, registry = _rdkit_graph_and_registry(molecule)
890
+ enhanced_groups = tuple(
891
+ f"enhanced_stereo_group:{index}:{group.GetGroupType()}"
892
+ for index, group in enumerate(molecule.GetStereoGroups())
893
+ )
894
+ if not require_complete:
895
+ return _classify_rdkit_identity_profile(
896
+ molecule,
897
+ graph,
898
+ registry,
899
+ identity_profile,
900
+ unsupported_loci=enhanced_groups,
901
+ )
902
+ configured_tetrahedral_centers = tuple(
903
+ atom.GetIdx()
904
+ for atom in molecule.GetAtoms()
905
+ if atom.GetChiralTag()
906
+ in {
907
+ Chem.ChiralType.CHI_TETRAHEDRAL_CW,
908
+ Chem.ChiralType.CHI_TETRAHEDRAL_CCW,
909
+ }
910
+ )
911
+ incomplete = tuple(
912
+ element.identifier
913
+ for element in detect_potential_stereo_elements(
914
+ molecule,
915
+ excluded_tetrahedral_centers=configured_tetrahedral_centers,
916
+ )
917
+ if (
918
+ element.configuration_state is StereoConfigurationState.UNSPECIFIED
919
+ and not _is_terminal_phosphate_resonance_locus(
920
+ molecule, element, identity_profile=identity_profile
921
+ )
922
+ )
923
+ )
924
+ return _classify_rdkit_identity_profile(
925
+ molecule,
926
+ graph,
927
+ registry,
928
+ identity_profile,
929
+ incomplete_loci=incomplete,
930
+ unsupported_loci=enhanced_groups,
931
+ )
932
+
933
+
934
+ __all__ = [
935
+ "CONFIGURED_DESCRIPTOR_TYPES",
936
+ "CONFIGURED_STEREOGRAPH_SCHEMA",
937
+ "ConfiguredDescriptor",
938
+ "MirrorIdentityProfile",
939
+ "canonicalize_configured_registry",
940
+ "canonicalize_configured_stereograph",
941
+ "canonicalize_rdkit_configured_stereograph",
942
+ "classify_configured_stereograph_mirror",
943
+ "classify_rdkit_configured_stereograph_mirror",
944
+ "expand_configured_stereograph",
945
+ "mirror_configured_descriptor",
946
+ ]