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,681 @@
1
+ """Reference-blinded exact-shell analysis and reaction-center statistics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import math
8
+ import time
9
+ import zlib
10
+ from collections import Counter
11
+ from dataclasses import dataclass
12
+ from numbers import Integral, Real
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+
17
+ from ..solvers.exact.hybrid import enumerate_hybrid_distance_mappings
18
+ from ..solvers.slap.lap import _adjacency_and_elements, chemical_distance
19
+ from ..solvers.slap.lap import recover_mapping
20
+ from ..solvers.slap.sequential import GraphMatcher
21
+ from .spectrum import (
22
+ ExactStructureSpectrum,
23
+ ExactStructureSpectrumAccumulator,
24
+ )
25
+
26
+ ShellTargetMode = Literal["reference_cd", "minimal"]
27
+
28
+
29
+ def _canonical_json(value: object) -> bytes:
30
+ return json.dumps(
31
+ value,
32
+ allow_nan=False,
33
+ ensure_ascii=True,
34
+ separators=(",", ":"),
35
+ sort_keys=True,
36
+ ).encode("ascii")
37
+
38
+
39
+ def _validate_optional_positive_integer(value, name):
40
+ if value is not None and (
41
+ isinstance(value, bool) or not isinstance(value, Integral) or value < 1
42
+ ):
43
+ raise ValueError(f"{name} must be a positive integer or None")
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class GlobalShellConfig:
48
+ """Resource and exact-search policy for one blinded global shell."""
49
+
50
+ binary: bool = False
51
+ time_limit_seconds: float | None = 300.0
52
+ max_bijections: int | None = None
53
+ max_mappings: int | None = 100_000
54
+ tolerance: float = 1e-9
55
+ symmetry_pruning: bool = True
56
+ max_symmetry_automorphisms: int = 256
57
+ symmetry_timeout_seconds: float = 0.25
58
+ symmetry_max_search_nodes: int = 10_000
59
+ symmetry_node_properties: tuple[str, ...] = ("hcounts", "charges")
60
+ reaction_center_properties: tuple[str, ...] = ("hcounts", "charges")
61
+ backend: str = "auto"
62
+ max_edit_support_pairs: int | None = 50_000
63
+ use_slap_seed: bool = True
64
+ structure_analysis: bool = True
65
+ template_radius: int = 1
66
+ structure_timeout_seconds: float = 0.25
67
+ structure_max_search_nodes: int = 100_000
68
+ structure_native_library_path: str | None = None
69
+
70
+ def __post_init__(self):
71
+ if not isinstance(self.binary, bool):
72
+ raise TypeError("binary must be boolean")
73
+ if self.time_limit_seconds is not None and (
74
+ isinstance(self.time_limit_seconds, bool)
75
+ or not isinstance(self.time_limit_seconds, Real)
76
+ or not math.isfinite(float(self.time_limit_seconds))
77
+ or self.time_limit_seconds < 0
78
+ ):
79
+ raise ValueError("time_limit_seconds must be finite and non-negative")
80
+ _validate_optional_positive_integer(self.max_bijections, "max_bijections")
81
+ _validate_optional_positive_integer(self.max_mappings, "max_mappings")
82
+ if self.tolerance < 0 or not math.isfinite(self.tolerance):
83
+ raise ValueError("tolerance must be finite and non-negative")
84
+ if not isinstance(self.symmetry_pruning, bool):
85
+ raise TypeError("symmetry_pruning must be boolean")
86
+ if not isinstance(self.structure_analysis, bool):
87
+ raise TypeError("structure_analysis must be boolean")
88
+ if not isinstance(self.use_slap_seed, bool):
89
+ raise TypeError("use_slap_seed must be boolean")
90
+ if self.backend not in {"auto", "assignment", "edit_support"}:
91
+ raise ValueError("backend must be 'auto', 'assignment', or 'edit_support'")
92
+ _validate_optional_positive_integer(
93
+ self.max_edit_support_pairs,
94
+ "max_edit_support_pairs",
95
+ )
96
+ if (
97
+ isinstance(self.template_radius, bool)
98
+ or not isinstance(self.template_radius, Integral)
99
+ or self.template_radius < 0
100
+ ):
101
+ raise ValueError("template_radius must be a non-negative integer")
102
+ _validate_optional_positive_integer(
103
+ self.max_symmetry_automorphisms, "max_symmetry_automorphisms"
104
+ )
105
+ _validate_optional_positive_integer(
106
+ self.symmetry_max_search_nodes, "symmetry_max_search_nodes"
107
+ )
108
+ if (
109
+ isinstance(self.symmetry_timeout_seconds, bool)
110
+ or not isinstance(self.symmetry_timeout_seconds, Real)
111
+ or not math.isfinite(float(self.symmetry_timeout_seconds))
112
+ or self.symmetry_timeout_seconds < 0
113
+ ):
114
+ raise ValueError("symmetry_timeout_seconds must be finite and non-negative")
115
+ if (
116
+ isinstance(self.structure_timeout_seconds, bool)
117
+ or not isinstance(self.structure_timeout_seconds, Real)
118
+ or not math.isfinite(float(self.structure_timeout_seconds))
119
+ or self.structure_timeout_seconds < 0
120
+ ):
121
+ raise ValueError(
122
+ "structure_timeout_seconds must be finite and non-negative"
123
+ )
124
+ _validate_optional_positive_integer(
125
+ self.structure_max_search_nodes,
126
+ "structure_max_search_nodes",
127
+ )
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class ReactionCenterSpectrum:
132
+ """Exact reaction-centre statistics over observed shell representatives."""
133
+
134
+ representative_count: int
135
+ frequency_denominator: int
136
+ frequency_scope: str
137
+ bond_union: tuple[tuple[int, int], ...]
138
+ bond_intersection: tuple[tuple[int, int], ...]
139
+ bond_change_counts: tuple[tuple[int, int, int], ...]
140
+ atom_union: tuple[int, ...]
141
+ atom_intersection: tuple[int, ...]
142
+ atom_change_counts: tuple[tuple[int, int], ...]
143
+ unary_properties: tuple[str, ...]
144
+ representative_stream_sha256: str
145
+
146
+ def as_dict(self) -> dict[str, object]:
147
+ return {
148
+ "representative_count": self.representative_count,
149
+ "frequency_denominator": self.frequency_denominator,
150
+ "frequency_scope": self.frequency_scope,
151
+ "bond_union": [list(pair) for pair in self.bond_union],
152
+ "bond_intersection": [list(pair) for pair in self.bond_intersection],
153
+ "bond_change_counts": [list(item) for item in self.bond_change_counts],
154
+ "atom_union": list(self.atom_union),
155
+ "atom_intersection": list(self.atom_intersection),
156
+ "atom_change_counts": [list(item) for item in self.atom_change_counts],
157
+ "unary_properties": list(self.unary_properties),
158
+ "representative_stream_sha256": self.representative_stream_sha256,
159
+ }
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class GlobalShellAnalysisResult:
164
+ """JSON-ready result of blind search followed by reference reveal."""
165
+
166
+ target_mode: ShellTargetMode
167
+ target: str | float
168
+ binary: bool
169
+ status: str
170
+ complete: bool
171
+ truncation_reason: str | None
172
+ scope: str
173
+ minimum_cost: float | None
174
+ reference_cd: float
175
+ reference_gap_from_minimum: float | None
176
+ reference_mapping_observed: bool
177
+ reference_class_observed: bool
178
+ shell_complete_and_reference_class_observed: bool
179
+ reference_is_global_minimum_proven: bool
180
+ total_bijections: int
181
+ representative_solution_count: int
182
+ mapping_hartley_entropy_nats: float | None
183
+ labeled_solution_count: int | None
184
+ symmetry_group_order: int | None
185
+ symmetry_quotient_complete: bool
186
+ visited_nodes: int
187
+ visited_leaves: int
188
+ distance_pruned_branches: int
189
+ lower_bound_pruned_branches: int
190
+ upper_bound_pruned_branches: int
191
+ symmetry_pruned_branches: int
192
+ elapsed_seconds: float
193
+ backend: str
194
+ backend_statistics: dict[str, object] | None
195
+ reaction_center: ReactionCenterSpectrum
196
+ structure: ExactStructureSpectrum
197
+ schema_version: int = 4
198
+ kind: str = "synister_reference_blinded_global_shell"
199
+
200
+ def as_dict(self, *, copy_sequences=True) -> dict[str, object]:
201
+ payload = {
202
+ field: getattr(self, field)
203
+ for field in self.__dataclass_fields__
204
+ if field not in {"reaction_center", "structure"}
205
+ }
206
+ payload["total_bijections"] = str(self.total_bijections)
207
+ payload["labeled_solution_count"] = (
208
+ None
209
+ if self.labeled_solution_count is None
210
+ else str(self.labeled_solution_count)
211
+ )
212
+ payload["symmetry_group_order"] = (
213
+ None
214
+ if self.symmetry_group_order is None
215
+ else str(self.symmetry_group_order)
216
+ )
217
+ payload["reaction_center"] = self.reaction_center.as_dict()
218
+ payload["structure"] = self.structure.as_dict(copy_sequences=copy_sequences)
219
+ return payload
220
+
221
+
222
+ def _property_vectors(lgp, names):
223
+ vectors = {}
224
+ atom_count = len(lgp[0].labels)
225
+ for name in names:
226
+ reactant = lgp[0].props.get(name)
227
+ product = lgp[1].props.get(name)
228
+ if (
229
+ reactant is not None
230
+ and product is not None
231
+ and len(reactant) == atom_count
232
+ and len(product) == atom_count
233
+ ):
234
+ vectors[str(name)] = (tuple(reactant), tuple(product))
235
+ return vectors
236
+
237
+
238
+ def _mapping_sha256(mapping) -> bytes:
239
+ return hashlib.sha256(_canonical_json([int(value) for value in mapping])).digest()
240
+
241
+
242
+ def _mapping_key(mapping) -> bytes:
243
+ """Injective packed witness; no digest assumption enters membership."""
244
+ if len(mapping) <= 256:
245
+ return b"\x00" + bytes(mapping)
246
+ return b"\x01" + np.asarray(mapping, dtype="<u8").tobytes()
247
+
248
+
249
+ def _transport_key(product, properties, mapping) -> bytes:
250
+ images = np.asarray(mapping, dtype=int)
251
+ payload = (
252
+ len(images).to_bytes(8, "little")
253
+ + np.asarray(product[images[:, None], images[None, :]], dtype="<f8").tobytes()
254
+ + _canonical_json({
255
+ name: [values[image] for image in mapping]
256
+ for name, (_, values) in properties.items()
257
+ })
258
+ )
259
+ # Lossless compression bounds dense zero-heavy witnesses without replacing
260
+ # equality with a fingerprint. The same compressor is used on both sides.
261
+ return zlib.compress(payload)
262
+
263
+
264
+ def _transport_sha256(product, properties, mapping) -> bytes:
265
+ images = np.asarray(mapping, dtype=int)
266
+ transported = product[images[:, None], images[None, :]]
267
+ # This fingerprint is internal to reference-class membership. Preserve
268
+ # floating-point values directly instead of serializing a dense JSON matrix.
269
+ digest = hashlib.sha256(b"synister-transport-f64-v1")
270
+ digest.update(len(images).to_bytes(8, "little"))
271
+ digest.update(np.asarray(transported, dtype="<f8").tobytes(order="C"))
272
+ digest.update(
273
+ _canonical_json(
274
+ {
275
+ name: [product_values[image] for image in mapping]
276
+ for name, (_, product_values) in properties.items()
277
+ }
278
+ )
279
+ )
280
+ return digest.digest()
281
+
282
+
283
+ class _BlindShellObserver:
284
+ def __init__(
285
+ self,
286
+ reactant,
287
+ product,
288
+ elements,
289
+ properties,
290
+ config,
291
+ ):
292
+ self.reactant = reactant
293
+ self.product = product
294
+ self.properties = properties
295
+ self.tolerance = config.tolerance
296
+ self.count = 0
297
+ self.bond_counts = Counter()
298
+ self.atom_counts = Counter()
299
+ self.mapping_hashes = set()
300
+ self.transport_hashes = set()
301
+ self.stream_digest = hashlib.sha256()
302
+ self.structure = ExactStructureSpectrumAccumulator(
303
+ reactant,
304
+ product,
305
+ elements,
306
+ properties,
307
+ enabled=config.structure_analysis,
308
+ template_radius=config.template_radius,
309
+ tolerance=config.tolerance,
310
+ timeout_seconds=config.structure_timeout_seconds,
311
+ max_search_nodes=config.structure_max_search_nodes,
312
+ native_library_path=config.structure_native_library_path,
313
+ )
314
+
315
+ def observe(self, mapping, cost):
316
+ normalized = tuple(int(image) for image in mapping)
317
+ self.count += 1
318
+ self.mapping_hashes.add(_mapping_key(normalized))
319
+ self.transport_hashes.add(
320
+ _transport_key(self.product, self.properties, normalized)
321
+ )
322
+ payload = _canonical_json([list(normalized), float(cost)])
323
+ self.stream_digest.update(len(payload).to_bytes(8, "little"))
324
+ self.stream_digest.update(payload)
325
+ self.structure.observe(normalized)
326
+ images = np.asarray(normalized, dtype=int)
327
+ transported = self.product[images[:, None], images[None, :]]
328
+ for left in range(self.reactant.shape[0]):
329
+ atom_changed = any(
330
+ reactant_values[left] != product_values[normalized[left]]
331
+ for reactant_values, product_values in self.properties.values()
332
+ )
333
+ if atom_changed:
334
+ self.atom_counts[left] += 1
335
+ changed = np.triu(
336
+ ~np.isclose(self.reactant, transported, atol=self.tolerance, rtol=0), 1
337
+ )
338
+ for left, right in zip(*np.nonzero(changed)):
339
+ self.bond_counts[(int(left), int(right))] += 1
340
+
341
+ def spectrum(self, frequency_scope):
342
+ bonds = tuple(sorted(self.bond_counts))
343
+ atoms = tuple(sorted(self.atom_counts))
344
+ return ReactionCenterSpectrum(
345
+ representative_count=self.count,
346
+ frequency_denominator=self.count,
347
+ frequency_scope=frequency_scope,
348
+ bond_union=bonds,
349
+ bond_intersection=tuple(
350
+ pair for pair in bonds if self.bond_counts[pair] == self.count
351
+ ),
352
+ bond_change_counts=tuple(
353
+ (left, right, self.bond_counts[(left, right)]) for left, right in bonds
354
+ ),
355
+ atom_union=atoms,
356
+ atom_intersection=tuple(
357
+ atom for atom in atoms if self.atom_counts[atom] == self.count
358
+ ),
359
+ atom_change_counts=tuple((atom, self.atom_counts[atom]) for atom in atoms),
360
+ unary_properties=tuple(self.properties),
361
+ representative_stream_sha256=self.stream_digest.hexdigest(),
362
+ )
363
+
364
+
365
+ def _reference_free_slap_seed(lgp, binary, *, repair=False): # noqa: C901
366
+ started = time.perf_counter()
367
+ try:
368
+ matcher = GraphMatcher(
369
+ binary=binary,
370
+ max_lap_fingerprints=1_000,
371
+ cache_label_blocks=True,
372
+ deterministic_labels=True,
373
+ )
374
+ matcher.get_maps(lgp)
375
+ if not matcher.results:
376
+ raise RuntimeError("SLAP produced no complete candidate")
377
+ from ..solvers.exact.kernel_solvers.enumerate import complete_mapping
378
+
379
+ mapping = complete_mapping(
380
+ lgp,
381
+ recover_mapping(matcher.results[0]["lgp"]),
382
+ binary=binary,
383
+ )
384
+ if sorted(mapping) != list(range(len(mapping))):
385
+ raise RuntimeError("SLAP candidate is not a complete permutation")
386
+ initial_cost = chemical_distance(lgp, mapping, binary=binary)
387
+ cost = initial_cost
388
+ repair_statistics = {
389
+ "attempted": False,
390
+ "improved": False,
391
+ "elapsed_seconds": 0.0,
392
+ }
393
+ if repair and cost > 0:
394
+ repair_started = time.perf_counter()
395
+ repair_statistics["attempted"] = True
396
+ try:
397
+ from ..solvers.exact.kernel_solvers.enumerate import (
398
+ _greedy_local_swap_descent,
399
+ _local_swap_pool,
400
+ _mismatch_scores,
401
+ )
402
+
403
+ pool, reactant, product = _local_swap_pool(lgp, mapping, binary, 48, 14)
404
+ if len(pool) > 48:
405
+ scores = _mismatch_scores(reactant, product, mapping)
406
+ pool = set(
407
+ sorted(pool, key=lambda atom: (-scores[atom], atom))[:48]
408
+ )
409
+ candidates = _greedy_local_swap_descent(
410
+ lgp, mapping, pool, reactant, product, binary, 8
411
+ )
412
+ candidate = [int(image) for image in candidates[-1][0]]
413
+ # Recompute the full objective; a heuristic delta is not proof.
414
+ candidate_cost = chemical_distance(lgp, candidate, binary=binary)
415
+ if candidate_cost < cost:
416
+ mapping, cost = candidate, candidate_cost
417
+ repair_statistics["improved"] = True
418
+ from ..solvers.exact.seeds.seed import improve_seed_mapping
419
+ from ..solvers.slap.lap import _adjacency_and_elements
420
+
421
+ _, elements = _adjacency_and_elements(lgp[0], binary)
422
+ _, product_elements = _adjacency_and_elements(lgp[1], binary)
423
+ candidate, broad_statistics = improve_seed_mapping(
424
+ reactant, product, elements, product_elements, mapping
425
+ )
426
+ candidate_cost = chemical_distance(lgp, candidate, binary=binary)
427
+ if candidate_cost < cost:
428
+ mapping, cost = candidate, candidate_cost
429
+ repair_statistics["improved"] = True
430
+ repair_statistics["broad_descent"] = broad_statistics
431
+ from ..solvers.exact.seeds.seed_relaxation import refine_seed_mapping
432
+
433
+ candidate, relaxed_statistics = refine_seed_mapping(
434
+ reactant, product, elements, product_elements, mapping
435
+ )
436
+ candidate_cost = chemical_distance(lgp, candidate, binary=binary)
437
+ if candidate_cost < cost:
438
+ candidate, _ = improve_seed_mapping(
439
+ reactant, product, elements, product_elements, candidate
440
+ )
441
+ candidate_cost = chemical_distance(lgp, candidate, binary=binary)
442
+ if candidate_cost < cost:
443
+ mapping, cost = candidate, candidate_cost
444
+ repair_statistics["improved"] = True
445
+ repair_statistics["relaxation"] = relaxed_statistics
446
+ if cost > relaxed_statistics.get("profile_lower_bound", cost) + 1e-9:
447
+ from ..solvers.exact.seeds.seed_fragments import improve_fragment_seed
448
+
449
+ candidate, fragment_statistics = improve_fragment_seed(
450
+ reactant, product, elements, product_elements, mapping
451
+ )
452
+ candidate_cost = chemical_distance(lgp, candidate, binary=binary)
453
+ if candidate_cost < cost:
454
+ mapping, cost = candidate, candidate_cost
455
+ repair_statistics["improved"] = True
456
+ repair_statistics["fragments"] = fragment_statistics
457
+ if cost > relaxed_statistics.get("profile_lower_bound", cost) + 1e-9:
458
+ # A second, deterministic atom order explores different
459
+ # fragment embeddings. Transport the feasible seed back
460
+ # and verify its full objective before accepting it.
461
+ reverse = np.arange(len(elements) - 1, -1, -1)
462
+ alternative, alternate_statistics = improve_fragment_seed(
463
+ reactant[np.ix_(reverse, reverse)], product,
464
+ [elements[i] for i in reverse], product_elements,
465
+ [mapping[i] for i in reverse],
466
+ )
467
+ candidate = list(reversed(alternative))
468
+ candidate_cost = chemical_distance(lgp, candidate, binary=binary)
469
+ if candidate_cost < cost:
470
+ mapping, cost = candidate, candidate_cost
471
+ repair_statistics["improved"] = True
472
+ repair_statistics["fragment_reverse_retry"] = alternate_statistics
473
+ except Exception as error:
474
+ # Optional repair cannot invalidate an already feasible seed.
475
+ repair_statistics["error_type"] = type(error).__name__
476
+ repair_statistics["error"] = str(error)
477
+ repair_statistics["elapsed_seconds"] = time.perf_counter() - repair_started
478
+ return mapping, {
479
+ "method": "reference_free_slap",
480
+ "available": True,
481
+ "elapsed_seconds": time.perf_counter() - started,
482
+ "cost": cost,
483
+ "initial_cost": initial_cost,
484
+ "repair": repair_statistics,
485
+ }
486
+ except Exception as error:
487
+ return None, {
488
+ "method": "reference_free_slap",
489
+ "available": False,
490
+ "elapsed_seconds": time.perf_counter() - started,
491
+ "error_type": type(error).__name__,
492
+ "error": str(error),
493
+ }
494
+
495
+
496
+ def _run_blind_search(lgp, target, config, observer, symmetry_properties):
497
+ """Run the global search without accepting any reference-map argument."""
498
+ if config.use_slap_seed:
499
+ initial_mapping, seed_statistics = _reference_free_slap_seed(
500
+ lgp,
501
+ config.binary,
502
+ repair=True,
503
+ )
504
+ else:
505
+ initial_mapping = None
506
+ seed_statistics = {
507
+ "method": "disabled",
508
+ "available": False,
509
+ "elapsed_seconds": 0.0,
510
+ }
511
+ result = enumerate_hybrid_distance_mappings(
512
+ lgp,
513
+ CD=target,
514
+ binary=config.binary,
515
+ backend=config.backend,
516
+ max_edit_support_pairs=config.max_edit_support_pairs,
517
+ max_bijections=config.max_bijections,
518
+ tolerance=config.tolerance,
519
+ time_limit_seconds=config.time_limit_seconds,
520
+ certify=False,
521
+ symmetry_pruning=config.symmetry_pruning,
522
+ max_symmetry_automorphisms=config.max_symmetry_automorphisms,
523
+ symmetry_timeout_seconds=config.symmetry_timeout_seconds,
524
+ symmetry_max_search_nodes=config.symmetry_max_search_nodes,
525
+ expand_symmetry=False,
526
+ initial_mapping=initial_mapping,
527
+ max_mappings=config.max_mappings,
528
+ assignment_lower_bound=True,
529
+ assignment_upper_bound=True,
530
+ atom_profile_pruning=True,
531
+ seed_center_order=True,
532
+ symmetry_node_properties=symmetry_properties,
533
+ fixed_mapping=None,
534
+ collect_mappings=False,
535
+ mapping_callback=observer.observe,
536
+ compute_minimum_cost=target == "minimal",
537
+ )
538
+ statistics = dict(result.backend_statistics or {})
539
+ statistics["seed"] = seed_statistics
540
+ statistics["structure_cache"] = observer.structure._code_cache.statistics()
541
+ result.backend_statistics = statistics
542
+ return result
543
+
544
+
545
+ def analyze_reference_blinded_global_shell(
546
+ lgp,
547
+ reference_mapping,
548
+ *,
549
+ target_mode: ShellTargetMode,
550
+ config: GlobalShellConfig | None = None,
551
+ ) -> GlobalShellAnalysisResult:
552
+ """Search globally, then reveal a held-out reference for evaluation.
553
+
554
+ ``target_mode='reference_cd'`` permits the scalar distance of the held-out
555
+ mapping to define the shell. The mapping itself is never passed to the
556
+ search. ``target_mode='minimal'`` hides both the reference mapping and its
557
+ distance until the global minimum and optimizer shell have been explored.
558
+ """
559
+ if target_mode not in {"reference_cd", "minimal"}:
560
+ raise ValueError("target_mode must be 'reference_cd' or 'minimal'")
561
+ config = GlobalShellConfig() if config is None else config
562
+ if not isinstance(config, GlobalShellConfig):
563
+ raise TypeError("config must be GlobalShellConfig or None")
564
+
565
+ reactant, reactant_elements = _adjacency_and_elements(lgp[0], config.binary)
566
+ product, product_elements = _adjacency_and_elements(lgp[1], config.binary)
567
+ reference = tuple(int(image) for image in reference_mapping)
568
+ if len(reference) != len(reactant_elements) or sorted(reference) != list(
569
+ range(len(product_elements))
570
+ ):
571
+ raise ValueError("reference_mapping must be a complete permutation")
572
+ if any(
573
+ reactant_elements[atom] != product_elements[image]
574
+ for atom, image in enumerate(reference)
575
+ ):
576
+ raise ValueError("reference_mapping must preserve atom types")
577
+
578
+ reference_cd = chemical_distance(lgp, reference, binary=config.binary)
579
+ target = reference_cd if target_mode == "reference_cd" else "minimal"
580
+ property_names = tuple(
581
+ dict.fromkeys(
582
+ (*config.reaction_center_properties, *config.symmetry_node_properties)
583
+ )
584
+ )
585
+ available_properties = _property_vectors(lgp, property_names)
586
+ properties = {
587
+ name: available_properties[name]
588
+ for name in config.reaction_center_properties
589
+ if name in available_properties
590
+ }
591
+ # Every unary attribute used by reaction-centre or structural analysis must
592
+ # also refine product automorphisms. A bond-graph automorphism that changes
593
+ # such an attribute preserves CD but need not preserve the downstream ITS.
594
+ symmetry_properties = tuple(available_properties)
595
+ observer = _BlindShellObserver(
596
+ reactant,
597
+ product,
598
+ reactant_elements,
599
+ properties,
600
+ config,
601
+ )
602
+ result = _run_blind_search(lgp, target, config, observer, symmetry_properties)
603
+ if observer.count != result.selected_mapping_count:
604
+ raise RuntimeError("streamed mapping count disagrees with search result")
605
+
606
+ mapping_observed = _mapping_key(reference) in observer.mapping_hashes
607
+ class_observed = (
608
+ _transport_key(product, properties, reference) in observer.transport_hashes
609
+ )
610
+ minimum = result.minimum_cost
611
+ gap = None if minimum is None else reference_cd - float(minimum)
612
+ is_minimal = bool(
613
+ result.complete
614
+ and minimum is not None
615
+ and math.isclose(
616
+ reference_cd,
617
+ float(minimum),
618
+ abs_tol=config.tolerance,
619
+ rel_tol=0.0,
620
+ )
621
+ and class_observed
622
+ )
623
+ frequency_scope = (
624
+ "verified_product_subgroup_orbit_representatives"
625
+ if config.symmetry_pruning
626
+ else "labeled_mappings"
627
+ )
628
+ return GlobalShellAnalysisResult(
629
+ target_mode=target_mode,
630
+ target=result.target,
631
+ binary=config.binary,
632
+ status=result.status,
633
+ complete=result.complete,
634
+ truncation_reason=result.truncation_reason,
635
+ scope=result.scope,
636
+ minimum_cost=result.minimum_cost,
637
+ reference_cd=reference_cd,
638
+ reference_gap_from_minimum=gap,
639
+ reference_mapping_observed=mapping_observed,
640
+ reference_class_observed=class_observed,
641
+ shell_complete_and_reference_class_observed=(
642
+ result.complete and class_observed
643
+ ),
644
+ reference_is_global_minimum_proven=is_minimal,
645
+ total_bijections=result.total_bijections,
646
+ representative_solution_count=result.selected_mapping_count,
647
+ mapping_hartley_entropy_nats=(
648
+ math.log(result.selected_mapping_count)
649
+ if result.complete
650
+ and result.symmetry_quotient_complete
651
+ and result.selected_mapping_count
652
+ else None
653
+ ),
654
+ labeled_solution_count=result.selected_labeled_mapping_count,
655
+ symmetry_group_order=result.symmetry_group_order,
656
+ symmetry_quotient_complete=result.symmetry_quotient_complete,
657
+ visited_nodes=result.visited_nodes,
658
+ visited_leaves=result.visited_leaves,
659
+ distance_pruned_branches=result.pruned_branches,
660
+ lower_bound_pruned_branches=result.lower_bound_pruned_branches,
661
+ upper_bound_pruned_branches=result.upper_bound_pruned_branches,
662
+ symmetry_pruned_branches=result.symmetry_pruned_branches,
663
+ elapsed_seconds=result.elapsed_seconds,
664
+ backend=result.backend,
665
+ backend_statistics=result.backend_statistics,
666
+ reaction_center=observer.spectrum(frequency_scope),
667
+ structure=observer.structure.finalize(
668
+ shell_complete=result.complete,
669
+ reference_mapping=reference,
670
+ class_count_scope=frequency_scope,
671
+ ),
672
+ )
673
+
674
+
675
+ __all__ = [
676
+ "GlobalShellAnalysisResult",
677
+ "GlobalShellConfig",
678
+ "ReactionCenterSpectrum",
679
+ "ShellTargetMode",
680
+ "analyze_reference_blinded_global_shell",
681
+ ]