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,1025 @@
1
+ """Complete atom-compatible enumeration at an exact chemical distance."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import time
7
+ from numbers import Real
8
+
9
+ import numpy as np
10
+
11
+ from ....graph.automorphism import bounded_automorphism_permutations
12
+ from .bounds import (
13
+ ResidualBondMass as _ResidualBondMass,
14
+ atom_profile_costs as _atom_profile_costs,
15
+ assignment_edge_lower_bounds as _assignment_edge_lower_bounds,
16
+ blocked_assignment_extreme as _blocked_assignment_extreme,
17
+ conditioned_profile_assignment_lower_bound as _conditioned_profile_bound,
18
+ element_blocks as _element_blocks,
19
+ reaction_center_order as _reaction_center_order,
20
+ )
21
+ from .inputs import (
22
+ prepare_distance_problem, validate_distance_options, normalize_initial_mapping,
23
+ initial_distance_domains, supports_strict_improvement,
24
+ )
25
+ from .records import (
26
+ CertificateVerificationError,
27
+ DistanceEnumerationCertificate,
28
+ DistanceEnumerationResult,
29
+ DistanceTarget,
30
+ EnumerationStatus,
31
+ ExactEnumerationLimitError,
32
+ _make_certificate,
33
+ _mappings_sha256,
34
+ normalize_distance_target,
35
+ )
36
+ from .records import _certificate_sha256 as _certificate_sha256 # noqa: F401
37
+ from .verify import verify_distance_enumeration_certificate
38
+ from ..symmetry import (
39
+ _MAX_STABILIZER_GENERATORS,
40
+ _MAX_SCHREIER_WORK,
41
+ largest_cyclic_subgroup,
42
+ orbital_candidate_witnesses,
43
+ permutation_group_order,
44
+ point_stabilizer_generators_checked,
45
+ )
46
+
47
+
48
+ _MAX_CROSS_COST_CACHE_BYTES = 32 * 1024**2
49
+
50
+
51
+ def _mapping_cost(reactant, product, mapping) -> float:
52
+ images = np.asarray(mapping, dtype=int)
53
+ mapped_product = product[images[:, None], images[None, :]]
54
+ return 0.5 * float(np.abs(reactant - mapped_product).sum())
55
+
56
+
57
+ def enumerate_distance_mappings( # noqa: C901
58
+ lgp,
59
+ *,
60
+ CD: str | Real = "minimal",
61
+ binary: bool = True,
62
+ max_bijections: int | None = 1_000_000,
63
+ tolerance: float = 1e-9,
64
+ time_limit_seconds: float | None = None,
65
+ certify: bool = False,
66
+ symmetry_pruning: bool = False,
67
+ max_symmetry_automorphisms: int = 256,
68
+ symmetry_timeout_seconds: float = 0.25,
69
+ symmetry_max_search_nodes: int = 10_000,
70
+ expand_symmetry: bool = False,
71
+ initial_mapping=None,
72
+ max_mappings: int | None = None,
73
+ assignment_lower_bound: bool = True,
74
+ assignment_upper_bound: bool = True,
75
+ atom_profile_pruning: bool = True,
76
+ seed_center_order: bool = True,
77
+ symmetry_node_properties=(),
78
+ fixed_mapping=None,
79
+ collect_mappings: bool = True,
80
+ mapping_callback=None,
81
+ compute_minimum_cost: bool = True,
82
+ _optimization_only: bool = False,
83
+ ) -> DistanceEnumerationResult:
84
+ """Enumerate the complete atom-compatible space at an exact CD.
85
+
86
+ ``CD='minimal'`` returns every globally minimal labeled mapping. A numeric
87
+ ``CD`` returns every labeled mapping whose chemical distance equals that
88
+ value within ``tolerance``. The pre-search factorial cap makes failure
89
+ explicit instead of returning a silently incomplete enumeration. With
90
+ ``symmetry_pruning=True``, verified product-graph automorphisms remove
91
+ non-lex-leading mappings and their witnesses become part of the replayable
92
+ tree cover. Automorphism discovery is bounded, so multiple representatives
93
+ of a full product-automorphism orbit may remain. ``collect_mappings=False``
94
+ keeps only the exact selected count and optionally streams each final
95
+ mapping to ``mapping_callback``. Streaming a minimal shell uses a separate
96
+ optimization pass so provisional incumbents are never emitted.
97
+ ``compute_minimum_cost=False`` skips the otherwise separate minimization
98
+ pass for a numeric shell; this is useful when only that exact shell is
99
+ required and leaves ``result.minimum_cost`` unset.
100
+ Numeric uncertified searches also remove atom pairs whose element-blocked
101
+ incident-bond profiles alone exceed the target.
102
+ ``fixed_mapping`` may pin an injective, atom-compatible subset of reactant
103
+ atoms to product atoms. The returned shell is complete within that
104
+ conditioned subspace, and product symmetry is restricted to the pointwise
105
+ stabilizer of the fixed product images.
106
+ ``expand_symmetry=True`` restricts pruning to one fully enumerated cyclic
107
+ verified subgroup and streams every group image of each representative;
108
+ this recovers the complete labeled shell exactly once.
109
+ """
110
+ target = normalize_distance_target(CD)
111
+ started = time.perf_counter()
112
+ validate_distance_options(
113
+ max_bijections=max_bijections,
114
+ tolerance=tolerance,
115
+ time_limit_seconds=time_limit_seconds,
116
+ symmetry_pruning=symmetry_pruning,
117
+ expand_symmetry=expand_symmetry,
118
+ certify=certify,
119
+ max_symmetry_automorphisms=max_symmetry_automorphisms,
120
+ symmetry_timeout_seconds=symmetry_timeout_seconds,
121
+ symmetry_max_search_nodes=symmetry_max_search_nodes,
122
+ max_mappings=max_mappings,
123
+ assignment_lower_bound=assignment_lower_bound,
124
+ assignment_upper_bound=assignment_upper_bound,
125
+ atom_profile_pruning=atom_profile_pruning,
126
+ seed_center_order=seed_center_order,
127
+ symmetry_node_properties=symmetry_node_properties,
128
+ collect_mappings=collect_mappings,
129
+ mapping_callback=mapping_callback,
130
+ compute_minimum_cost=compute_minimum_cost,
131
+ _optimization_only=_optimization_only,
132
+ )
133
+ symmetry_node_properties = tuple(str(name) for name in symmetry_node_properties)
134
+ (
135
+ reactant,
136
+ product,
137
+ reactant_elements,
138
+ product_elements,
139
+ reactant_counts,
140
+ product_by_element,
141
+ atom_count,
142
+ fixed,
143
+ fixed_images,
144
+ total_bijections,
145
+ maximum_cost_upper_bound,
146
+ input_sha256,
147
+ ) = prepare_distance_problem(lgp, binary, fixed_mapping, certify, expand_symmetry)
148
+
149
+ if target != "minimal" and target > maximum_cost_upper_bound + tolerance:
150
+ certificate = None
151
+ if certify:
152
+ certificate = _make_certificate(
153
+ schema_version=1,
154
+ kind="maximum_cost_upper_bound",
155
+ input_sha256=input_sha256,
156
+ target=target,
157
+ binary=bool(binary),
158
+ tolerance=float(tolerance),
159
+ reactant_order=(),
160
+ terminal_prefixes=(),
161
+ frontier_prefixes=(),
162
+ total_bijections=total_bijections,
163
+ maximum_cost_upper_bound=maximum_cost_upper_bound,
164
+ selected_mapping_count=0,
165
+ selected_mappings_sha256=_mappings_sha256([]),
166
+ cost=None,
167
+ pruning_limit=None,
168
+ status="no_solutions",
169
+ )
170
+ return DistanceEnumerationResult(
171
+ target=target,
172
+ cost=None,
173
+ minimum_cost=None,
174
+ mappings=[],
175
+ distances=[],
176
+ total_bijections=total_bijections,
177
+ maximum_cost_upper_bound=maximum_cost_upper_bound,
178
+ visited_leaves=0,
179
+ pruned_branches=1,
180
+ elapsed_seconds=time.perf_counter() - started,
181
+ status="no_solutions",
182
+ complete=True,
183
+ certificate=certificate,
184
+ )
185
+ if max_bijections is not None and total_bijections > max_bijections:
186
+ raise ExactEnumerationLimitError(
187
+ f"{total_bijections:,} atom-compatible bijections exceed "
188
+ f"max_bijections={max_bijections:,}"
189
+ )
190
+
191
+ proven_minimum_cost = None
192
+ fixed_minimum_cost = None
193
+ optimization = None
194
+ optimization_seconds = 0.0
195
+ needs_optimization_pass = (target != "minimal" and compute_minimum_cost) or (
196
+ target == "minimal" and (not collect_mappings or mapping_callback is not None)
197
+ )
198
+ if needs_optimization_pass and not _optimization_only:
199
+ optimization_started = time.perf_counter()
200
+ optimization = enumerate_distance_mappings(
201
+ lgp,
202
+ CD="minimal",
203
+ binary=binary,
204
+ max_bijections=max_bijections,
205
+ tolerance=tolerance,
206
+ time_limit_seconds=time_limit_seconds,
207
+ certify=False,
208
+ symmetry_pruning=symmetry_pruning,
209
+ max_symmetry_automorphisms=max_symmetry_automorphisms,
210
+ symmetry_timeout_seconds=symmetry_timeout_seconds,
211
+ symmetry_max_search_nodes=symmetry_max_search_nodes,
212
+ expand_symmetry=expand_symmetry,
213
+ initial_mapping=initial_mapping,
214
+ max_mappings=None,
215
+ assignment_lower_bound=assignment_lower_bound,
216
+ assignment_upper_bound=assignment_upper_bound,
217
+ atom_profile_pruning=atom_profile_pruning,
218
+ seed_center_order=seed_center_order,
219
+ symmetry_node_properties=symmetry_node_properties,
220
+ fixed_mapping=fixed,
221
+ collect_mappings=True,
222
+ mapping_callback=None,
223
+ compute_minimum_cost=True,
224
+ _optimization_only=True,
225
+ )
226
+ optimization_seconds = time.perf_counter() - optimization_started
227
+ if optimization.complete:
228
+ proven_minimum_cost = optimization.minimum_cost
229
+ if target == "minimal":
230
+ fixed_minimum_cost = proven_minimum_cost
231
+ if optimization.mappings:
232
+ initial_mapping = optimization.mappings[0]
233
+ elif target == "minimal":
234
+ # Streaming must never expose provisional incumbents. If the
235
+ # prerequisite optimization did not prove the minimum, return its
236
+ # explicit incomplete result without starting the callback pass.
237
+ return optimization
238
+
239
+ preferred_mapping = normalize_initial_mapping(
240
+ initial_mapping, atom_count, reactant_elements, product_elements, fixed
241
+ )
242
+ reactant_order = _reaction_center_order(
243
+ reactant,
244
+ product,
245
+ reactant_elements,
246
+ reactant_counts,
247
+ preferred_mapping if seed_center_order else None,
248
+ )
249
+ reactant_order = [atom for atom in reactant_order if atom not in fixed]
250
+ profile_pair_costs = None
251
+ if atom_profile_pruning and not certify:
252
+ profile_pair_costs = _atom_profile_costs(
253
+ reactant, product, reactant_elements, product_elements
254
+ )
255
+ profile_edge_bounds = profile_pair_costs
256
+ profile_domain_limit = float(target) if target != "minimal" else fixed_minimum_cost
257
+ if profile_domain_limit is None and preferred_mapping is not None:
258
+ profile_domain_limit = _mapping_cost(reactant, product, preferred_mapping)
259
+ if profile_pair_costs is not None and profile_domain_limit is not None:
260
+ finite_profiles = profile_pair_costs[np.isfinite(profile_pair_costs)]
261
+ # Quarter-unit weights keep alternating-path arithmetic exact here.
262
+ # General floats retain the existing tolerance-aware individual bound.
263
+ if (
264
+ maximum_cost_upper_bound < 2**40
265
+ and np.equal(finite_profiles * 4, np.rint(finite_profiles * 4)).all()
266
+ ):
267
+ profile_edge_bounds = _assignment_edge_lower_bounds(
268
+ profile_pair_costs, reactant_elements, product_elements
269
+ )
270
+ domains, profile_domain_pruned = initial_distance_domains(
271
+ reactant_order, reactant_elements, product_by_element, fixed_images,
272
+ profile_edge_bounds, profile_domain_limit, tolerance,
273
+ )
274
+ # At an attained global row-profile bound every row attains its own bound.
275
+ # Zero-cost rows must then match exactly. Restrict this propagation to exact
276
+ # quarter-unit costs with tolerance smaller than the objective quantum.
277
+ tight_profile_rows = bool(
278
+ profile_pair_costs is not None
279
+ and profile_domain_limit is not None
280
+ and maximum_cost_upper_bound < 2**40
281
+ and tolerance < 0.25
282
+ and np.equal(reactant * 2, np.rint(reactant * 2)).all()
283
+ and np.equal(product * 2, np.rint(product * 2)).all()
284
+ and _blocked_assignment_extreme(
285
+ profile_pair_costs,
286
+ range(atom_count),
287
+ range(atom_count),
288
+ reactant_elements,
289
+ product_elements,
290
+ )
291
+ == profile_domain_limit
292
+ )
293
+
294
+ dynamic_row_order = profile_pair_costs is not None and not certify
295
+
296
+ mapping_scope = (
297
+ "fixed_assignment_subspace"
298
+ if fixed
299
+ else "complete_atom_compatible_assignment_space"
300
+ )
301
+ symmetry_permutations = (tuple(range(atom_count)),)
302
+ symmetry_search_complete = True
303
+ symmetry_group_order = 1
304
+ symmetry_quotient_complete = True
305
+ if symmetry_pruning:
306
+ symmetry_permutations, symmetry_search_complete = (
307
+ bounded_automorphism_permutations(
308
+ lgp[1],
309
+ binary,
310
+ limit=int(max_symmetry_automorphisms),
311
+ timeout_seconds=float(symmetry_timeout_seconds),
312
+ max_search_nodes=int(symmetry_max_search_nodes),
313
+ node_properties=symmetry_node_properties,
314
+ )
315
+ )
316
+ root_generators = tuple(symmetry_permutations[1:])
317
+ for image in sorted(fixed_images):
318
+ root_generators, stabilizer_complete = point_stabilizer_generators_checked(
319
+ root_generators, image
320
+ )
321
+ symmetry_quotient_complete &= stabilizer_complete
322
+ if expand_symmetry:
323
+ symmetry_permutations = largest_cyclic_subgroup(
324
+ symmetry_permutations,
325
+ max_order=max_symmetry_automorphisms,
326
+ )
327
+ root_generators = tuple(symmetry_permutations[1:])
328
+ symmetry_group_order = len(symmetry_permutations)
329
+ else:
330
+ mapping_scope = (
331
+ "verified_product_automorphism_lex_leaders_within_"
332
+ "fixed_assignment_subspace"
333
+ if fixed
334
+ else "verified_product_automorphism_lex_leaders"
335
+ )
336
+ symmetry_group_order = permutation_group_order(root_generators)
337
+ if symmetry_group_order is None:
338
+ symmetry_quotient_complete = False
339
+ else:
340
+ root_generators = ()
341
+ mapping = [-1] * atom_count
342
+ used_products = [False] * atom_count
343
+ for atom, image in fixed.items():
344
+ mapping[atom] = image
345
+ used_products[image] = True
346
+ mappings: list[list[int]] = []
347
+ distances: list[float] = []
348
+ selected_mapping_count = 0
349
+ if preferred_mapping is None:
350
+ preferred_mapping = [-1] * atom_count
351
+ for atom, image in fixed.items():
352
+ preferred_mapping[atom] = image
353
+ reactant_blocks = _element_blocks(reactant_order, reactant_elements)
354
+ for element, atoms in reactant_blocks.items():
355
+ available_images = [
356
+ image
357
+ for image in product_by_element[element]
358
+ if image not in fixed_images
359
+ ]
360
+ for atom, image in zip(atoms, available_images):
361
+ preferred_mapping[atom] = image
362
+ best_cost = (
363
+ fixed_minimum_cost
364
+ if fixed_minimum_cost is not None
365
+ else (
366
+ _mapping_cost(reactant, product, preferred_mapping)
367
+ if target == "minimal"
368
+ else math.inf
369
+ )
370
+ )
371
+ if _optimization_only and target == "minimal":
372
+ mappings = [list(preferred_mapping)]
373
+ distances = [float(best_cost)]
374
+ selected_mapping_count = 1
375
+ profile_compatible = None
376
+ tight_profile_pruned = 0
377
+ if tight_profile_rows:
378
+ profile_compatible = np.zeros((atom_count, atom_count), dtype=bool)
379
+ for atom, images in domains.items():
380
+ profile_compatible[atom, list(images)] = True
381
+
382
+ def tighten_profiles(atom, image):
383
+ allowed = (profile_pair_costs != 0) | (
384
+ reactant[:, atom, None] == product[:, image][None, :]
385
+ )
386
+ if profile_pair_costs[atom, image] == 0:
387
+ allowed &= reactant[atom, :, None] == product[image, :][None, :]
388
+ # Each entry is removed at most once on the live DFS path, so retained
389
+ # flat indices use O(n^2) space rather than one dense mask per level.
390
+ removed = np.flatnonzero(profile_compatible & ~allowed)
391
+ profile_compatible.flat[removed] = False
392
+ return removed
393
+
394
+ if tight_profile_rows:
395
+ for atom, image in fixed.items():
396
+ tighten_profiles(atom, image)
397
+ cross_costs = np.zeros((atom_count, atom_count), dtype=float)
398
+ for atom, image in fixed.items():
399
+ delta = 0.5 * np.abs(reactant[:, atom, None] - product[:, image][None, :])
400
+ delta += 0.5 * np.abs(reactant[atom, :, None] - product[image, :][None, :])
401
+ cross_costs += delta
402
+ remaining_product_atoms = [
403
+ image for image in range(atom_count) if image not in fixed_images
404
+ ]
405
+ residual_bond_mass = _ResidualBondMass(
406
+ reactant,
407
+ product,
408
+ reactant_order,
409
+ remaining_product_atoms,
410
+ )
411
+ # A dynamic row order needs reversible reactant mass, rather than suffix
412
+ # sums tied to the original order. Reuse the matrix-side removal tracker.
413
+ reactant_bond_mass = (
414
+ _ResidualBondMass(reactant, reactant, reactant_order, reactant_order)
415
+ if dynamic_row_order
416
+ else None
417
+ )
418
+ initial_order_rank = {
419
+ atom: position for position, atom in enumerate(reactant_order)
420
+ }
421
+ reactant_neighbors = (reactant != 0) | (reactant.T != 0)
422
+ assigned_neighbors = np.zeros(atom_count, dtype=int)
423
+ for atom in fixed:
424
+ assigned_neighbors += reactant_neighbors[:, atom]
425
+ fixed_atoms = sorted(fixed)
426
+ fixed_product_atoms = [fixed[atom] for atom in fixed_atoms]
427
+ initial_committed_cost = (
428
+ 0.5
429
+ * float(
430
+ np.abs(
431
+ reactant[np.ix_(fixed_atoms, fixed_atoms)]
432
+ - product[np.ix_(fixed_product_atoms, fixed_product_atoms)]
433
+ ).sum()
434
+ )
435
+ if fixed_atoms
436
+ else 0.0
437
+ )
438
+ search_count = len(reactant_order)
439
+ visited_leaves = 0
440
+ pruned_branches = 0
441
+ deadline = (
442
+ None if time_limit_seconds is None else started + float(time_limit_seconds)
443
+ )
444
+ timed_out = False
445
+ truncation_reason = None
446
+ terminal_prefixes: list[tuple[int, ...]] = []
447
+ frontier_prefixes: list[tuple[int, ...]] = []
448
+ symmetry_prefixes: list[tuple[tuple[int, ...], tuple[int, ...]]] = []
449
+ lower_bound_prefixes: list[tuple[int, ...]] = []
450
+ upper_bound_prefixes: list[tuple[int, ...]] = []
451
+ visited_nodes = 0
452
+ symmetry_pruned_branches = 0
453
+ lower_bound_pruned_branches = 0
454
+ upper_bound_pruned_branches = 0
455
+ stopped_with_unexplored_work = False
456
+ # Half-integer bond weights give exact quarter-unit DFS costs and bounds
457
+ # in this conservative range. Only the proof pass can discard ties.
458
+ # Arbitrary floats retain tolerance-aware pruning; the shell stays inclusive.
459
+ strict_improvement = supports_strict_improvement(
460
+ _optimization_only, target, certify, maximum_cost_upper_bound, reactant, product
461
+ )
462
+
463
+ def current_prefix(depth: int) -> tuple[int, ...]:
464
+ return tuple(mapping[atom] for atom in reactant_order[:depth])
465
+
466
+ profile_bound_calls = 0
467
+ profile_bound_pruned = 0
468
+ conditioned_bound_calls = 0
469
+ conditioned_bound_pruned = 0
470
+
471
+ def remaining_assignment_interval(
472
+ depth: int, committed_cost: float, profile_cost: float
473
+ ):
474
+ nonlocal profile_bound_calls, profile_bound_pruned
475
+ nonlocal conditioned_bound_calls, conditioned_bound_pruned
476
+ remaining_atoms = reactant_order[depth:]
477
+ branch_costs = None
478
+ if not remaining_atoms:
479
+ return 0.0, 0.0, None
480
+ remaining_products = [
481
+ product_atom
482
+ for product_atom in range(atom_count)
483
+ if not used_products[product_atom]
484
+ ]
485
+ cross_lower = _blocked_assignment_extreme(
486
+ cross_costs,
487
+ remaining_atoms,
488
+ remaining_products,
489
+ reactant_elements,
490
+ product_elements,
491
+ )
492
+ if reactant_bond_mass is None:
493
+ internal_lower, internal_upper = residual_bond_mass.interval(depth)
494
+ else:
495
+ internal_lower = 0.5 * abs(
496
+ reactant_bond_mass.product_sum - residual_bond_mass.product_sum
497
+ )
498
+ internal_upper = 0.5 * (
499
+ reactant_bond_mass.product_abs_sum + residual_bond_mass.product_abs_sum
500
+ )
501
+ lower = cross_lower + internal_lower
502
+ if tight_profile_rows:
503
+ # The attained root bound remains valid for every prefix. Edge
504
+ # filtering and zero-row propagation already constrain this face;
505
+ # avoid repeatedly recomputing its uninformative profile relaxations.
506
+ lower = max(lower, profile_domain_limit - committed_cost)
507
+ limit = float(best_cost if target == "minimal" else target)
508
+ base_pruned = committed_cost + lower > limit + tolerance or (
509
+ strict_improvement and committed_cost + lower >= limit
510
+ )
511
+ if (
512
+ profile_pair_costs is not None
513
+ and not base_pruned
514
+ and not tight_profile_rows
515
+ ):
516
+ # The prefix's half-row profile sum plus a remaining LAP bounds
517
+ # total CD. Combine by max, avoiding overlapping edge costs.
518
+ profile_bound_calls += 1
519
+ profile_lower = profile_cost + _blocked_assignment_extreme(
520
+ profile_pair_costs,
521
+ remaining_atoms,
522
+ remaining_products,
523
+ reactant_elements,
524
+ product_elements,
525
+ )
526
+ lower = max(lower, profile_lower - committed_cost)
527
+ if profile_lower > limit + tolerance or (
528
+ strict_improvement and profile_lower >= limit
529
+ ):
530
+ profile_bound_pruned += 1
531
+ # Condition the residual profiles only after cheaper bounds survive.
532
+ # Limit the cubic profile work to modest remaining subproblems.
533
+ already_pruned = committed_cost + lower > limit + tolerance or (
534
+ strict_improvement and committed_cost + lower >= limit
535
+ )
536
+ if (
537
+ profile_pair_costs is not None
538
+ and not tight_profile_rows
539
+ and not already_pruned
540
+ and depth > 0
541
+ and len(remaining_atoms) <= 256
542
+ ):
543
+ conditioned_bound_calls += 1
544
+ conditioned_lower, branch_costs = _conditioned_profile_bound(
545
+ reactant,
546
+ product,
547
+ cross_costs,
548
+ remaining_atoms,
549
+ remaining_products,
550
+ reactant_elements,
551
+ product_elements,
552
+ return_costs=True,
553
+ )
554
+ lower = max(lower, conditioned_lower)
555
+ if committed_cost + lower > limit + tolerance or (
556
+ strict_improvement and committed_cost + lower >= limit
557
+ ):
558
+ conditioned_bound_pruned += 1
559
+ if target == "minimal" or not assignment_upper_bound:
560
+ return lower, None, branch_costs
561
+ cross_upper = _blocked_assignment_extreme(
562
+ cross_costs,
563
+ remaining_atoms,
564
+ remaining_products,
565
+ reactant_elements,
566
+ product_elements,
567
+ maximize=True,
568
+ )
569
+ return lower, cross_upper + internal_upper, branch_costs
570
+
571
+ cached_delta_bytes = 0
572
+ peak_cached_delta_bytes = 0
573
+ stabilizer_calls = 0
574
+
575
+ def cross_cost_delta(atom: int, image: int):
576
+ """Build a prefix-cost delta; retain it only within the cache budget."""
577
+ delta = 0.5 * np.abs(reactant[:, atom, None] - product[:, image][None, :])
578
+ delta += 0.5 * np.abs(reactant[atom, :, None] - product[image, :][None, :])
579
+ return delta
580
+
581
+ def visit(
582
+ depth: int,
583
+ committed_cost: float,
584
+ stabilizer_generators=(),
585
+ pending_point=None,
586
+ profile_cost=0.0,
587
+ ) -> None:
588
+ nonlocal best_cost, visited_leaves, pruned_branches, mappings, distances
589
+ nonlocal selected_mapping_count
590
+ nonlocal timed_out, truncation_reason, visited_nodes
591
+ nonlocal symmetry_pruned_branches, lower_bound_pruned_branches
592
+ nonlocal upper_bound_pruned_branches, stopped_with_unexplored_work
593
+ nonlocal symmetry_quotient_complete, stabilizer_calls
594
+ nonlocal cached_delta_bytes, peak_cached_delta_bytes
595
+ nonlocal tight_profile_pruned
596
+
597
+ visited_nodes += 1
598
+
599
+ if timed_out:
600
+ stopped_with_unexplored_work = True
601
+ if certify:
602
+ frontier_prefixes.append(current_prefix(depth))
603
+ return
604
+ if deadline is not None and time.perf_counter() >= deadline:
605
+ timed_out = True
606
+ truncation_reason = "time_limit"
607
+ if certify:
608
+ frontier_prefixes.append(current_prefix(depth))
609
+ return
610
+
611
+ limit = best_cost if target == "minimal" else target
612
+ if committed_cost > float(limit) + tolerance or (
613
+ strict_improvement and committed_cost >= float(limit)
614
+ ):
615
+ pruned_branches += 1
616
+ if certify:
617
+ terminal_prefixes.append(current_prefix(depth))
618
+ return
619
+ branch_costs = None
620
+ if assignment_lower_bound and depth < search_count:
621
+ lower_bound, upper_bound, branch_costs = remaining_assignment_interval(
622
+ depth, committed_cost, profile_cost
623
+ )
624
+ if committed_cost + lower_bound > float(limit) + tolerance or (
625
+ strict_improvement and committed_cost + lower_bound >= float(limit)
626
+ ):
627
+ lower_bound_pruned_branches += 1
628
+ if certify:
629
+ lower_bound_prefixes.append(current_prefix(depth))
630
+ return
631
+ if (
632
+ upper_bound is not None
633
+ and target != "minimal"
634
+ and committed_cost + upper_bound < float(target) - tolerance
635
+ ):
636
+ upper_bound_pruned_branches += 1
637
+ if certify:
638
+ upper_bound_prefixes.append(current_prefix(depth))
639
+ return
640
+ if depth == search_count:
641
+ visited_leaves += 1
642
+ if certify:
643
+ terminal_prefixes.append(current_prefix(depth))
644
+ cost = float(committed_cost)
645
+ if cost < best_cost:
646
+ best_cost = cost
647
+ if target == "minimal":
648
+ if _optimization_only:
649
+ mappings = [list(mapping)]
650
+ distances = [cost]
651
+ else:
652
+ retained = [
653
+ (candidate, distance)
654
+ for candidate, distance in zip(mappings, distances)
655
+ if distance <= best_cost + tolerance
656
+ ]
657
+ mappings = [candidate for candidate, _ in retained]
658
+ distances = [distance for _, distance in retained]
659
+ selected_mapping_count = len(mappings)
660
+ selected = (
661
+ cost <= best_cost + tolerance
662
+ if target == "minimal"
663
+ else math.isclose(cost, target, abs_tol=tolerance, rel_tol=0.0)
664
+ )
665
+ if selected and not _optimization_only:
666
+ expanded = (
667
+ (
668
+ [permutation[image] for image in mapping]
669
+ for permutation in symmetry_permutations
670
+ )
671
+ if expand_symmetry
672
+ else (list(mapping) for _ in range(1))
673
+ )
674
+ for expansion_index, selected_mapping in enumerate(expanded):
675
+ selected_mapping_count += 1
676
+ if collect_mappings:
677
+ mappings.append(selected_mapping)
678
+ distances.append(cost)
679
+ if mapping_callback is not None:
680
+ mapping_callback(selected_mapping, cost)
681
+ if (
682
+ max_mappings is not None
683
+ and selected_mapping_count >= max_mappings
684
+ ):
685
+ timed_out = True
686
+ truncation_reason = "mapping_limit"
687
+ if (
688
+ expand_symmetry
689
+ and expansion_index + 1 < len(symmetry_permutations)
690
+ ):
691
+ stopped_with_unexplored_work = True
692
+ break
693
+ return
694
+
695
+ # Bounds and leaves need no child stabilizer. Construct it only for
696
+ # a surviving internal node, where candidate orbit pruning uses it.
697
+ if symmetry_pruning and pending_point is not None:
698
+ # These generators come from the verified, deduplicated root set
699
+ # or a completed stabilizer construction. Preserve the same tuple
700
+ # when the subgroup already fixes this point, within helper budgets.
701
+ if not (
702
+ len(stabilizer_generators)
703
+ <= min(_MAX_STABILIZER_GENERATORS, _MAX_SCHREIER_WORK)
704
+ and all(
705
+ g[pending_point] == pending_point for g in stabilizer_generators
706
+ )
707
+ ):
708
+ stabilizer_calls += 1
709
+ stabilizer_generators, stabilizer_complete = (
710
+ point_stabilizer_generators_checked(
711
+ stabilizer_generators, pending_point
712
+ )
713
+ )
714
+ symmetry_quotient_complete &= stabilizer_complete
715
+
716
+ selected_position = depth
717
+ branch_allowed = None
718
+ if dynamic_row_order:
719
+ remaining_rows = reactant_order[depth:]
720
+ remaining_images = np.flatnonzero(~np.asarray(used_products))
721
+ if tight_profile_rows:
722
+ counts = (
723
+ profile_compatible[remaining_rows] & ~np.asarray(used_products)
724
+ ).sum(axis=1)
725
+ elif branch_costs is not None:
726
+ finite_costs = branch_costs[np.isfinite(branch_costs)]
727
+ if (
728
+ maximum_cost_upper_bound < 2**40
729
+ and np.equal(finite_costs * 4, np.rint(finite_costs * 4)).all()
730
+ ):
731
+ forced_bounds = committed_cost + _assignment_edge_lower_bounds(
732
+ branch_costs,
733
+ [reactant_elements[atom] for atom in remaining_rows],
734
+ [product_elements[image] for image in remaining_images],
735
+ )
736
+ else:
737
+ row_minima = branch_costs.min(axis=1)
738
+ forced_bounds = (
739
+ committed_cost + branch_costs
740
+ + (row_minima.sum() - row_minima)[:, None]
741
+ )
742
+ branch_allowed = forced_bounds <= float(limit) + tolerance
743
+ counts = branch_allowed.sum(axis=1)
744
+ else:
745
+ counts = np.asarray([
746
+ sum(
747
+ not used_products[image]
748
+ and committed_cost + cross_costs[atom, image]
749
+ <= float(limit) + tolerance
750
+ for image in domains[atom]
751
+ )
752
+ for atom in remaining_rows
753
+ ])
754
+ selected_position = depth + min(
755
+ range(len(remaining_rows)),
756
+ key=lambda position: (
757
+ int(counts[position]),
758
+ -int(assigned_neighbors[remaining_rows[position]]),
759
+ initial_order_rank[remaining_rows[position]],
760
+ ),
761
+ )
762
+ reactant_order[depth], reactant_order[selected_position] = (
763
+ reactant_order[selected_position],
764
+ reactant_order[depth],
765
+ )
766
+ reactant_atom = reactant_order[depth]
767
+ allowed_images = (
768
+ set(remaining_images[branch_allowed[selected_position - depth]])
769
+ if branch_allowed is not None else None
770
+ )
771
+ candidate_images = sorted(
772
+ (
773
+ image
774
+ for image in domains[reactant_atom]
775
+ if not used_products[image]
776
+ and (allowed_images is None or image in allowed_images)
777
+ and (
778
+ profile_compatible is None
779
+ or profile_compatible[reactant_atom, image]
780
+ )
781
+ ),
782
+ key=lambda product_atom: (
783
+ product_atom != preferred_mapping[reactant_atom],
784
+ cross_costs[reactant_atom, product_atom],
785
+ product_atom,
786
+ ),
787
+ )
788
+ symmetry_witnesses = (
789
+ orbital_candidate_witnesses(candidate_images, stabilizer_generators)
790
+ if symmetry_pruning
791
+ else {image: None for image in candidate_images}
792
+ )
793
+ for product_atom in candidate_images:
794
+ # Certificate searches still need to record frontier prefixes.
795
+ if timed_out and truncation_reason == "time_limit" and not certify:
796
+ stopped_with_unexplored_work = True
797
+ break
798
+ symmetry_witness = symmetry_witnesses[product_atom]
799
+ if symmetry_witness is not None:
800
+ symmetry_pruned_branches += 1
801
+ if certify:
802
+ prefix = current_prefix(depth) + (product_atom,)
803
+ symmetry_prefixes.append((prefix, symmetry_witness))
804
+ continue
805
+ incremental = float(cross_costs[reactant_atom, product_atom])
806
+ mapping[reactant_atom] = product_atom
807
+ used_products[product_atom] = True
808
+ removed_profiles = None
809
+ if tight_profile_rows:
810
+ removed_profiles = tighten_profiles(reactant_atom, product_atom)
811
+ remaining_rows = reactant_order[depth + 1 :]
812
+ available = profile_compatible[remaining_rows] & ~np.asarray(
813
+ used_products
814
+ )
815
+ if not available.any(axis=1).all():
816
+ tight_profile_pruned += 1
817
+ profile_compatible.flat[removed_profiles] = True
818
+ used_products[product_atom] = False
819
+ mapping[reactant_atom] = -1
820
+ continue
821
+ removed_bond_mass = residual_bond_mass.remove(product_atom)
822
+ removed_reactant_mass = None
823
+ if dynamic_row_order:
824
+ removed_reactant_mass = reactant_bond_mass.remove(reactant_atom)
825
+ assigned_neighbors[:] += reactant_neighbors[:, reactant_atom]
826
+ delta = cross_cost_delta(reactant_atom, product_atom)
827
+ cross_costs[:] += delta
828
+ retained = cached_delta_bytes + delta.nbytes <= _MAX_CROSS_COST_CACHE_BYTES
829
+ if retained:
830
+ cached_delta_bytes += delta.nbytes
831
+ peak_cached_delta_bytes = max(
832
+ peak_cached_delta_bytes, cached_delta_bytes
833
+ )
834
+ else:
835
+ delta = None
836
+ visit(
837
+ depth + 1,
838
+ committed_cost + incremental,
839
+ stabilizer_generators,
840
+ product_atom,
841
+ profile_cost
842
+ + (
843
+ float(profile_pair_costs[reactant_atom, product_atom])
844
+ if profile_pair_costs is not None
845
+ else 0.0
846
+ ),
847
+ )
848
+ if retained:
849
+ cached_delta_bytes -= delta.nbytes
850
+ else:
851
+ delta = cross_cost_delta(reactant_atom, product_atom)
852
+ cross_costs[:] -= delta
853
+ del delta
854
+ residual_bond_mass.restore(product_atom, removed_bond_mass)
855
+ if removed_reactant_mass is not None:
856
+ reactant_bond_mass.restore(reactant_atom, removed_reactant_mass)
857
+ assigned_neighbors[:] -= reactant_neighbors[:, reactant_atom]
858
+ if removed_profiles is not None:
859
+ profile_compatible.flat[removed_profiles] = True
860
+ used_products[product_atom] = False
861
+ mapping[reactant_atom] = -1
862
+ if dynamic_row_order:
863
+ reactant_order[depth], reactant_order[selected_position] = (
864
+ reactant_order[selected_position],
865
+ reactant_order[depth],
866
+ )
867
+
868
+ traversal_started = time.perf_counter()
869
+ initial_profile_cost = (
870
+ sum(float(profile_pair_costs[atom, image]) for atom, image in fixed.items())
871
+ if profile_pair_costs is not None
872
+ else 0.0
873
+ )
874
+ visit(0, initial_committed_cost, root_generators, profile_cost=initial_profile_cost)
875
+ traversal_seconds = time.perf_counter() - traversal_started
876
+ if (
877
+ timed_out
878
+ and truncation_reason == "mapping_limit"
879
+ and not stopped_with_unexplored_work
880
+ ):
881
+ timed_out = False
882
+ truncation_reason = None
883
+ elapsed_seconds = time.perf_counter() - started
884
+ if timed_out and target == "minimal" and fixed_minimum_cost is None:
885
+ # Never expose a best-so-far mapping as a proven exact minimum.
886
+ mappings = []
887
+ distances = []
888
+ selected_mapping_count = 0
889
+ minimum_cost = None
890
+ selected_cost = None
891
+ else:
892
+ minimum_cost = (
893
+ proven_minimum_cost
894
+ if target != "minimal"
895
+ else None
896
+ if math.isinf(best_cost)
897
+ else float(best_cost)
898
+ )
899
+ selected_cost = (
900
+ minimum_cost
901
+ if target == "minimal"
902
+ else (float(target) if selected_mapping_count else None)
903
+ )
904
+ complete = not timed_out
905
+ selected_labeled_mapping_count = selected_mapping_count
906
+ if symmetry_pruning and not expand_symmetry:
907
+ selected_labeled_mapping_count = (
908
+ selected_mapping_count * symmetry_group_order
909
+ if symmetry_group_order is not None and symmetry_quotient_complete
910
+ else None
911
+ )
912
+ status: EnumerationStatus = (
913
+ "timeout"
914
+ if timed_out
915
+ else "complete"
916
+ if selected_mapping_count
917
+ else "no_solutions"
918
+ )
919
+ certificate = None
920
+ if certify:
921
+ certificate = _make_certificate(
922
+ schema_version=(
923
+ 4
924
+ if upper_bound_prefixes
925
+ else 3
926
+ if assignment_lower_bound
927
+ else 2
928
+ if symmetry_pruning
929
+ else 1
930
+ ),
931
+ kind="prefix_cover",
932
+ input_sha256=input_sha256,
933
+ target=target,
934
+ binary=bool(binary),
935
+ tolerance=float(tolerance),
936
+ reactant_order=tuple(reactant_order),
937
+ terminal_prefixes=tuple(terminal_prefixes),
938
+ frontier_prefixes=tuple(frontier_prefixes),
939
+ total_bijections=total_bijections,
940
+ maximum_cost_upper_bound=maximum_cost_upper_bound,
941
+ selected_mapping_count=selected_mapping_count,
942
+ selected_mappings_sha256=_mappings_sha256(mappings),
943
+ cost=selected_cost,
944
+ pruning_limit=(
945
+ None
946
+ if target == "minimal" and math.isinf(best_cost)
947
+ else float(best_cost)
948
+ if target == "minimal"
949
+ else float(target)
950
+ ),
951
+ status=status,
952
+ mapping_scope=mapping_scope,
953
+ symmetry_prefixes=tuple(symmetry_prefixes),
954
+ lower_bound_prefixes=tuple(lower_bound_prefixes),
955
+ upper_bound_prefixes=tuple(upper_bound_prefixes),
956
+ )
957
+ return DistanceEnumerationResult(
958
+ target=target,
959
+ cost=selected_cost,
960
+ minimum_cost=minimum_cost,
961
+ mappings=mappings,
962
+ distances=distances,
963
+ total_bijections=total_bijections,
964
+ maximum_cost_upper_bound=maximum_cost_upper_bound,
965
+ visited_leaves=visited_leaves,
966
+ pruned_branches=pruned_branches,
967
+ elapsed_seconds=elapsed_seconds,
968
+ status=status,
969
+ complete=complete,
970
+ truncation_reason=truncation_reason,
971
+ scope=mapping_scope,
972
+ certificate=certificate,
973
+ visited_nodes=visited_nodes,
974
+ symmetry_pruned_branches=symmetry_pruned_branches,
975
+ symmetry_automorphism_count=len(symmetry_permutations),
976
+ symmetry_search_complete=symmetry_search_complete,
977
+ lower_bound_pruned_branches=lower_bound_pruned_branches,
978
+ upper_bound_pruned_branches=upper_bound_pruned_branches,
979
+ selected_mapping_count=selected_mapping_count,
980
+ symmetry_group_order=symmetry_group_order,
981
+ symmetry_quotient_complete=symmetry_quotient_complete,
982
+ selected_labeled_mapping_count=selected_labeled_mapping_count,
983
+ backend_statistics={
984
+ "search": {
985
+ "phase": "minimum_proof" if _optimization_only else "shell_enumeration",
986
+ "strict_improvement": strict_improvement,
987
+ "stabilizer_calls": stabilizer_calls,
988
+ "profile_bound_calls": profile_bound_calls,
989
+ "profile_domain_pruned": profile_domain_pruned,
990
+ "tight_profile_rows": tight_profile_rows,
991
+ "dynamic_profile_order": dynamic_row_order,
992
+ "tight_profile_pruned": tight_profile_pruned,
993
+ "profile_bound_pruned": profile_bound_pruned,
994
+ "conditioned_bound_calls": conditioned_bound_calls,
995
+ "conditioned_bound_pruned": conditioned_bound_pruned,
996
+ "peak_cached_delta_bytes": peak_cached_delta_bytes,
997
+ "incumbent_cost": (
998
+ float(best_cost)
999
+ if target == "minimal" and math.isfinite(best_cost)
1000
+ else None
1001
+ ),
1002
+ "preprocessing_seconds": traversal_started
1003
+ - started
1004
+ - optimization_seconds,
1005
+ "traversal_seconds": traversal_seconds,
1006
+ "minimum_proof_seconds": optimization_seconds,
1007
+ "minimum_proof_nodes": optimization.visited_nodes
1008
+ if optimization is not None
1009
+ else None,
1010
+ }
1011
+ },
1012
+ )
1013
+
1014
+
1015
+ __all__ = [
1016
+ "CertificateVerificationError",
1017
+ "DistanceEnumerationCertificate",
1018
+ "DistanceEnumerationResult",
1019
+ "DistanceTarget",
1020
+ "EnumerationStatus",
1021
+ "ExactEnumerationLimitError",
1022
+ "enumerate_distance_mappings",
1023
+ "normalize_distance_target",
1024
+ "verify_distance_enumeration_certificate",
1025
+ ]