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,856 @@
1
+ """Integer depth-first graph-assignment search with propagation and LAP bounds.
2
+
3
+ The objective is undirected bond disagreement on the half-integer lattice.
4
+ Bounds and search costs are represented in quarter units. Only a proved
5
+ minimum is passed to the enumeration phase, so callbacks never see incumbents.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from collections import Counter
12
+ from operator import itemgetter
13
+
14
+ import numpy as np
15
+
16
+ from ..assignment.assignment_domains import AssignmentDomains, bits
17
+ from ..distance_search.bounds import atom_profile_costs, reaction_center_order
18
+ from ..assignment.incremental_assignment import (
19
+ solve_assignment,
20
+ solve_partitioned_assignment,
21
+ cycle_edge_lower_bounds,
22
+ )
23
+ from .propagation_limits import PropagationDeadline, check_deadline
24
+ from .residual_typed_bonds import ResidualTypedBonds
25
+ from .cost_lattice import CostLattice
26
+ from .separator_spectrum import SeparatorCostSpectrum
27
+ from .suffix_spectrum import SuffixCostSpectrum
28
+ from .reward_frontier import RewardFrontierSpectrum
29
+ from .propagation_bounds import PropagationBounds
30
+
31
+
32
+ class PropagatedSearch(PropagationBounds):
33
+ """Own a reversible search state shared by proof and enumeration passes."""
34
+
35
+ def __init__(
36
+ self,
37
+ a,
38
+ b,
39
+ labels,
40
+ product_labels,
41
+ seed,
42
+ fixed,
43
+ group,
44
+ reactant_group,
45
+ expand,
46
+ config,
47
+ deadline,
48
+ ):
49
+ self.a, self.b = a, b
50
+ self.cost_lattice = CostLattice.from_matrices(a, b)
51
+ self.neighbors = a != 0
52
+ self.labels, self.product_labels = labels, product_labels
53
+ self.n, self.seed, self.fixed = len(a), list(seed), dict(fixed)
54
+ self.branch_rank = (
55
+ self._pagerank(a, deadline) if config.branch_order == "pagerank" else None
56
+ )
57
+ self.impact_rank = (
58
+ self._impact_scores(a, b, labels, product_labels, deadline)
59
+ if config.branch_order == "impact"
60
+ else None
61
+ )
62
+ self.group = group
63
+ self.reactant_group = reactant_group
64
+ self.reactant_symmetry = len(reactant_group) > 1
65
+ self.config, self.deadline = config, deadline
66
+ check_deadline(deadline)
67
+ self.expand = expand
68
+ self.emitted_maps = set()
69
+ self.statistics = Counter()
70
+ self.statistics.update(
71
+ assignment_calls=0,
72
+ inherited_edges=0,
73
+ augmentations=0,
74
+ hall_pruned=0,
75
+ domain_edges_removed=0,
76
+ conditioned_calls=0,
77
+ conditioned_skipped=0,
78
+ symmetry_pruned=0,
79
+ reactant_symmetry_pruned=0,
80
+ reactant_symmetry_group_order=len(reactant_group),
81
+ visited_nodes=0,
82
+ visited_leaves=0,
83
+ lower_bound_pruned=0,
84
+ upper_bound_pruned=0,
85
+ forced_assignments=0,
86
+ transport_bound_calls=0,
87
+ seed_swap_candidates=0,
88
+ seed_swaps=0,
89
+ seed_improvement_quarters=0,
90
+ seed_improvement_seconds=0.0,
91
+ star_bound_calls=0,
92
+ star_inner_assignments=0,
93
+ star_infeasible_anchors=0,
94
+ star_improved_entries=0,
95
+ star_bound_pruned=0,
96
+ star_bound_seconds=0.0,
97
+ pairwise_bound_calls=0,
98
+ pairwise_bound_anchors=0,
99
+ pairwise_bound_pruned_edges=0,
100
+ pairwise_bound_seconds=0.0,
101
+ lattice_shell_rejections=0,
102
+ lattice_bound_roundings=0,
103
+ separator_bound_calls=0,
104
+ separator_bound_pruned=0,
105
+ separator_bound_skipped=0,
106
+ separator_bound_seconds=0.0,
107
+ separator_spectrum_calls=0,
108
+ separator_spectrum_skipped=0,
109
+ separator_spectrum_mappings=0,
110
+ separator_spectrum_orbits_pruned=0,
111
+ separator_spectrum_states=0,
112
+ separator_spectrum_seconds=0.0,
113
+ factor_spectrum_calls=0,
114
+ factor_spectrum_pruned=0,
115
+ factor_spectrum_skipped=0,
116
+ factor_spectrum_seconds=0.0,
117
+ suffix_spectrum_calls=0,
118
+ suffix_spectrum_skipped=0,
119
+ suffix_spectrum_mappings=0,
120
+ suffix_spectrum_orbits_pruned=0,
121
+ suffix_spectrum_orbit_branches_pruned=0,
122
+ suffix_spectrum_states=0,
123
+ suffix_spectrum_seconds=0.0,
124
+ suffix_spectrum_prepare_seconds=0.0,
125
+ suffix_spectrum_reconstruction_seconds=0.0,
126
+ suffix_spectrum_reward_frontier_calls=0,
127
+ suffix_spectrum_max_frontier=0,
128
+ )
129
+ self.star_seconds_used = 0.0
130
+ self.pairwise_seconds_used = 0.0
131
+ if config.seed_local_search and config.seed_improvement_seconds:
132
+ self.seed = self.improve_seed(self.seed, config.seed_improvement_seconds)
133
+ floats = atom_profile_costs(a / 4, b / 4, labels, product_labels)
134
+ self.profile = np.rint(np.where(np.isfinite(floats), floats * 4, 0)).astype(
135
+ np.int64
136
+ )
137
+ check_deadline(deadline)
138
+ order = reaction_center_order(a / 4, b / 4, labels, Counter(labels), seed)
139
+ self.rank = {row: i for i, row in enumerate(order)}
140
+ self.base_masks = [
141
+ sum(1 << j for j, element in enumerate(product_labels) if element == label)
142
+ for label in labels
143
+ ]
144
+ self.root_rows = tuple(i for i in range(self.n) if i not in fixed)
145
+ self.root_columns = tuple(
146
+ j for j in range(self.n) if j not in set(fixed.values())
147
+ )
148
+ allowed = np.asarray(
149
+ [
150
+ [labels[i] == product_labels[j] for j in self.root_columns]
151
+ for i in self.root_rows
152
+ ],
153
+ dtype=bool,
154
+ ).reshape(len(self.root_rows), len(self.root_columns))
155
+ self.root_assignment = self.assignment(
156
+ self.profile[np.ix_(self.root_rows, self.root_columns)],
157
+ allowed,
158
+ self.root_rows,
159
+ self.root_columns,
160
+ None,
161
+ )
162
+ self.fixed_profile = sum(int(self.profile[i, j]) for i, j in fixed.items())
163
+ self.root_profile_lower = self.fixed_profile + self.root_assignment.lower_bound
164
+ self.root_lower = self.round_cost_bound(self.root_profile_lower)
165
+ self.root_u = dict(
166
+ zip(self.root_rows, self.root_assignment.certificate.row_potentials)
167
+ )
168
+ self.root_v = dict(
169
+ zip(self.root_columns, self.root_assignment.certificate.column_potentials)
170
+ )
171
+ self.incumbent = self.cost(self.seed)
172
+ self.witness = list(self.seed)
173
+ self.root_forced = None
174
+ if self.config.cycle_cost_bounds and self.incumbent > self.root_lower:
175
+ self.root_forced = np.zeros((self.n, self.n), dtype=np.int64)
176
+ self.root_forced[np.ix_(self.root_rows, self.root_columns)] = (
177
+ self.fixed_profile
178
+ + cycle_edge_lower_bounds(
179
+ self.profile[np.ix_(self.root_rows, self.root_columns)],
180
+ allowed,
181
+ self.root_assignment,
182
+ deadline=self.deadline,
183
+ )
184
+ )
185
+ self.statistics["cycle_bound_calls"] += 1
186
+
187
+ def round_cost_bound(self, lower):
188
+ """Round a valid lower bound to the necessary global cost lattice."""
189
+ if not self.config.cost_lattice_pruning or not self.cost_lattice.modulus:
190
+ return lower
191
+ rounded = self.cost_lattice.first_at_least(lower)
192
+ self.statistics["lattice_bound_roundings"] += int(rounded != lower)
193
+ return rounded
194
+
195
+ def cost(self, mapping):
196
+ """Rescore a complete mapping in exact quarter units."""
197
+ delta = np.abs(self.a - self.b[np.ix_(mapping, mapping)])
198
+ return int(delta.sum() // 2)
199
+
200
+ def improve_seed(self, mapping, seconds):
201
+ """Try exact same-type swaps under a small incumbent-only time slice."""
202
+ started = time.perf_counter()
203
+ stop = started + seconds
204
+ if self.deadline is not None:
205
+ stop = min(stop, self.deadline)
206
+ current = list(mapping)
207
+ current_cost = self.cost(current)
208
+ fixed_rows = set(self.fixed)
209
+ n = self.n
210
+ evaluated = 0
211
+ changed = True
212
+ while changed and evaluated < 512 and time.perf_counter() < stop:
213
+ changed = False
214
+ for i in range(n):
215
+ if i in fixed_rows:
216
+ continue
217
+ for j in range(i + 1, n):
218
+ if (
219
+ j in fixed_rows
220
+ or self.labels[i] != self.labels[j]
221
+ or evaluated >= 512
222
+ or time.perf_counter() >= stop
223
+ ):
224
+ continue
225
+ evaluated += 1
226
+ keep = np.fromiter(
227
+ (k for k in range(n) if k != i and k != j), dtype=int
228
+ )
229
+ images = np.asarray(current, dtype=int)[keep]
230
+ old = int(
231
+ np.abs(self.a[i, keep] - self.b[current[i], images]).sum()
232
+ + np.abs(self.a[j, keep] - self.b[current[j], images]).sum()
233
+ )
234
+ new = int(
235
+ np.abs(self.a[i, keep] - self.b[current[j], images]).sum()
236
+ + np.abs(self.a[j, keep] - self.b[current[i], images]).sum()
237
+ )
238
+ self.statistics["seed_swap_candidates"] += 1
239
+ if new < old:
240
+ current[i], current[j] = current[j], current[i]
241
+ current_cost -= old - new
242
+ self.statistics["seed_swaps"] += 1
243
+ changed = True
244
+ if evaluated >= 512 or time.perf_counter() >= stop:
245
+ break
246
+ rescored = self.cost(current)
247
+ if rescored != current_cost:
248
+ raise ArithmeticError(
249
+ "Incremental seed-swap cost disagrees with literal score"
250
+ )
251
+ self.statistics["seed_improvement_quarters"] = self.cost(mapping) - rescored
252
+ self.statistics["seed_improvement_seconds"] = time.perf_counter() - started
253
+ return current
254
+
255
+ @staticmethod
256
+ def _pagerank(adjacency, deadline=None):
257
+ """Return a bounded unweighted PageRank tie-break score per reactant."""
258
+ transition = (adjacency != 0).astype(float)
259
+ degrees = transition.sum(axis=1)
260
+ transition = np.divide(
261
+ transition,
262
+ degrees[:, None],
263
+ out=np.zeros_like(transition),
264
+ where=degrees[:, None] != 0,
265
+ )
266
+ score = np.full(len(adjacency), 1.0 / len(adjacency))
267
+ for _ in range(100):
268
+ check_deadline(deadline)
269
+ dangling = score[degrees == 0].sum() / len(adjacency)
270
+ updated = 0.85 * (transition.T @ score + dangling) + 0.15 / len(adjacency)
271
+ if np.max(np.abs(updated - score)) < 1e-12:
272
+ return updated
273
+ score = updated
274
+ return score
275
+
276
+ @staticmethod
277
+ def _impact_scores(a, b, labels, product_labels, deadline=None):
278
+ """Upper-bound the bond-disagreement mass incident to each reactant."""
279
+ max_product_abs = {}
280
+ for left, left_label in enumerate(product_labels):
281
+ check_deadline(deadline)
282
+ for right, right_label in enumerate(product_labels):
283
+ if left != right:
284
+ key = (left_label, right_label)
285
+ max_product_abs[key] = max(
286
+ max_product_abs.get(key, 0), abs(int(b[left, right]))
287
+ )
288
+ scores = np.zeros(len(a), dtype=np.int64)
289
+ for i in range(len(a)):
290
+ check_deadline(deadline)
291
+ for k in range(len(a)):
292
+ if i != k:
293
+ product_cap = max_product_abs.get((labels[i], labels[k]), 0)
294
+ # |a-b| <= |a|+|b| for signed and nonnegative weights.
295
+ scores[i] += abs(int(a[i, k])) + product_cap
296
+ return scores
297
+
298
+ def expired(self):
299
+ """Record an honest wall-time stop, including preprocessing time."""
300
+ if self.deadline is not None and time.perf_counter() >= self.deadline:
301
+ self.stop_reason = "time_limit"
302
+ return self.stop_reason is not None
303
+
304
+ def assignment(self, costs, allowed, rows, columns, parent, deadline=None):
305
+ """Solve and independently verify an exact residual assignment."""
306
+ self.statistics["assignment_calls"] += 1
307
+ inherited = parent if self.config.incremental_assignments else None
308
+ deadline = self.deadline if deadline is None else deadline
309
+ if self.config.block_assignments:
310
+ state = solve_partitioned_assignment(
311
+ costs,
312
+ allowed,
313
+ rows,
314
+ columns,
315
+ [self.labels[i] for i in rows],
316
+ [self.product_labels[j] for j in columns],
317
+ inherited,
318
+ deadline=deadline,
319
+ )
320
+ else:
321
+ state = solve_assignment(
322
+ costs, allowed, rows, columns, inherited, deadline=deadline
323
+ )
324
+ if state is not None:
325
+ self.statistics["inherited_edges"] += state.inherited_edges
326
+ self.statistics["augmentations"] += state.augmentations
327
+ return state
328
+
329
+ def budget(self):
330
+ """Proof rejects ties; selected shells retain every tolerance tie."""
331
+ return self.incumbent - 1 if self.proof else self.upper
332
+
333
+ def reset(self, *, proof, lower=0, upper=0, emit=None, max_mappings=None):
334
+ self.proof, self.lower, self.upper = proof, lower, upper
335
+ self.emit, self.max_mappings = emit, max_mappings
336
+ self.stop_reason, self.proven_by_root = None, False
337
+ self.selected = 0
338
+ self.mapping = [-1] * self.n
339
+ self.available = (1 << self.n) - 1
340
+ self.cross = np.zeros((self.n, self.n), dtype=np.int64)
341
+ self.domains = AssignmentDomains(self.base_masks)
342
+ self.typed_bonds = ResidualTypedBonds(
343
+ self.a,
344
+ self.b,
345
+ self.labels,
346
+ self.product_labels,
347
+ self.root_rows,
348
+ self.root_columns,
349
+ )
350
+ assigned = sorted(self.fixed)
351
+ self.assigned_neighbors = np.count_nonzero(self.neighbors[:, assigned], axis=1)
352
+ images = [self.fixed[i] for i in assigned]
353
+ committed = int(
354
+ np.abs(
355
+ self.a[np.ix_(assigned, assigned)] - self.b[np.ix_(images, images)]
356
+ ).sum()
357
+ // 2
358
+ )
359
+ for row, image in self.fixed.items():
360
+ self.mapping[row] = image
361
+ self.available &= ~(1 << image)
362
+ self.cross += np.abs(self.a[:, row, None] - self.b[:, image][None, :])
363
+ for row in self.root_rows:
364
+ self.domains.intersect(row, self.available)
365
+ if self.budget() == self.root_profile_lower:
366
+ for row, image in self.fixed.items():
367
+ self.tighten_zero_rows(row, image, self.root_rows)
368
+ return committed
369
+
370
+ def prove(self):
371
+ """Prove the exact optimum without exposing intermediate witnesses."""
372
+ committed = self.reset(proof=True)
373
+ if self.expired():
374
+ return False
375
+ if self.root_lower >= self.incumbent:
376
+ self.statistics["root_attainment_proofs"] += 1
377
+ return True
378
+ try:
379
+ self.visit(
380
+ self.root_rows,
381
+ committed,
382
+ self.fixed_profile,
383
+ self.group,
384
+ self.root_assignment,
385
+ 0,
386
+ )
387
+ except PropagationDeadline:
388
+ self.stop_reason = "time_limit"
389
+ return self.stop_reason is None
390
+
391
+ def enumerate(self, lower, upper, emit, max_mappings):
392
+ """Enumerate the selected lattice shell with unique subgroup expansion."""
393
+ committed = self.reset(
394
+ proof=False, lower=lower, upper=upper, emit=emit, max_mappings=max_mappings
395
+ )
396
+ self.emitted_maps.clear()
397
+ if self.config.cost_lattice_pruning and not self.cost_lattice.intersects(
398
+ lower, upper
399
+ ):
400
+ self.statistics["lattice_shell_rejections"] += 1
401
+ return True
402
+ try:
403
+ self.visit(
404
+ self.root_rows,
405
+ committed,
406
+ self.fixed_profile,
407
+ self.group,
408
+ self.root_assignment,
409
+ 0,
410
+ )
411
+ except PropagationDeadline:
412
+ self.stop_reason = "time_limit"
413
+ return self.stop_reason is None
414
+
415
+ def restrict(self, rows, columns, allowed):
416
+ """Apply a sound edge mask through the reversible trail."""
417
+ removed = self.domains.restrict_matrix(rows, columns, allowed)
418
+ self.statistics["domain_edges_removed"] += removed
419
+
420
+ def propagate(self, rows, *, matching=None, full=None):
421
+ check_deadline(self.deadline)
422
+ ok, removed = self.domains.propagate(
423
+ rows,
424
+ self.available,
425
+ full=self.config.domain_propagation if full is None else full,
426
+ matching=matching,
427
+ cache=self.config.cache_propagation,
428
+ )
429
+ self.statistics["domain_edges_removed"] += removed
430
+ if not ok:
431
+ self.statistics["hall_pruned"] += 1
432
+ return ok
433
+
434
+ @staticmethod
435
+ def matching_witness(state, rows, columns):
436
+ """Project a certified parent matching onto a residual subproblem."""
437
+ if state is None:
438
+ return None
439
+ certificate = state.certificate
440
+ previous = {
441
+ row: state.columns[certificate.permutation[index]]
442
+ for index, row in enumerate(state.rows)
443
+ }
444
+ column_position = {image: index for index, image in enumerate(columns)}
445
+ try:
446
+ witness = tuple(column_position[previous[row]] for row in rows)
447
+ except KeyError:
448
+ return None
449
+ if len(set(witness)) != len(columns) or len(witness) != len(rows):
450
+ return None
451
+ return witness
452
+
453
+ def tighten_zero_rows(self, row, image, remaining):
454
+ """On an attained profile face, zero-disagreement rows preserve edges."""
455
+ assigned_zero = self.profile[row, image] == 0
456
+ indices = np.asarray(remaining, dtype=int)
457
+ allowed = self.a[indices, row, None] == self.b[None, :, image]
458
+ if not assigned_zero:
459
+ allowed |= self.profile[indices] != 0
460
+ self.domains.restrict_matrix(remaining, tuple(range(self.n)), allowed)
461
+
462
+ def select_row(self, rows):
463
+ """Choose a constrained atom while keeping all admissible candidates."""
464
+ branch_rank = self.branch_rank
465
+ impact_rank = self.impact_rank
466
+ if self.reactant_symmetry:
467
+ assigned = tuple(i for i, image in enumerate(self.mapping) if image >= 0)
468
+ stabilizer = tuple(
469
+ g for g in self.reactant_group if all(g[i] == i for i in assigned)
470
+ )
471
+ remaining = set(rows)
472
+ representatives = []
473
+ while remaining:
474
+ representative = min(remaining, key=lambda i: self.rank[i])
475
+ orbit = {g[representative] for g in stabilizer} & remaining
476
+ if not orbit:
477
+ orbit = {representative}
478
+ representatives.append(representative)
479
+ remaining.difference_update(orbit)
480
+ self.statistics["reactant_symmetry_pruned"] += len(orbit) - 1
481
+ rows = tuple(representatives)
482
+ if self.config.branch_order == "contention":
483
+ return min(
484
+ rows,
485
+ key=lambda i: (
486
+ self.domains.masks[i].bit_count(),
487
+ -int(self.assigned_neighbors[i]),
488
+ -sum(
489
+ (self.domains.masks[i] & self.domains.masks[k]).bit_count()
490
+ for k in rows
491
+ if k != i
492
+ ),
493
+ self.rank[i],
494
+ ),
495
+ )
496
+ return min(
497
+ rows,
498
+ key=lambda i: (
499
+ self.domains.masks[i].bit_count(),
500
+ -int(self.assigned_neighbors[i]),
501
+ -float(branch_rank[i]) if branch_rank is not None else 0.0,
502
+ -int(impact_rank[i]) if impact_rank is not None else 0,
503
+ self.rank[i],
504
+ ),
505
+ )
506
+
507
+ def accept(self, committed): # noqa: C901
508
+ if self.proof:
509
+ if committed < self.incumbent:
510
+ self.incumbent, self.witness = committed, list(self.mapping)
511
+ self.proven_by_root = self.incumbent == self.root_lower
512
+ return
513
+ if not self.lower <= committed <= self.upper:
514
+ return
515
+ if not self.reactant_symmetry:
516
+ # Product action on full bijections is free: g o f = h o f
517
+ # implies g = h because f is surjective. The verified subgroup
518
+ # contains distinct permutations, so no per-leaf dedup is needed.
519
+ cost = committed / 4
520
+ if not self.expand or len(self.group) == 1:
521
+ if self.expired():
522
+ return
523
+ if self.max_mappings is not None and self.selected >= self.max_mappings:
524
+ self.stop_reason = "mapping_limit"
525
+ return
526
+ self.selected += 1
527
+ self.emit(tuple(self.mapping), cost)
528
+ return
529
+ # A nontrivial group needs at least two images, so itemgetter
530
+ # always returns a tuple. Construct it once for this full map.
531
+ permute = itemgetter(*self.mapping)
532
+ for permutation in self.group:
533
+ if self.expired():
534
+ return
535
+ if self.max_mappings is not None and self.selected >= self.max_mappings:
536
+ self.stop_reason = "mapping_limit"
537
+ return
538
+ self.selected += 1
539
+ self.emit(permute(permutation), cost)
540
+ return
541
+ product_expansion = self.group if self.expand else (tuple(range(self.n)),)
542
+ reactant_expansion = (
543
+ self.reactant_group if self.expand else (tuple(range(self.n)),)
544
+ )
545
+ emitted = set()
546
+ for source_permutation in reactant_expansion:
547
+ inverse_source = [0] * self.n
548
+ for i, j in enumerate(source_permutation):
549
+ inverse_source[j] = i
550
+ source_mapped = [self.mapping[inverse_source[i]] for i in range(self.n)]
551
+ for product_permutation in product_expansion:
552
+ if self.expired():
553
+ return
554
+ mapping = tuple(product_permutation[j] for j in source_mapped)
555
+ if mapping in emitted or (
556
+ self.reactant_symmetry and mapping in self.emitted_maps
557
+ ):
558
+ continue
559
+ emitted.add(mapping)
560
+ if self.reactant_symmetry:
561
+ self.emitted_maps.add(mapping)
562
+ if self.max_mappings is not None and self.selected >= self.max_mappings:
563
+ # Stop only when another valid map exists. Exactly hitting
564
+ # the cap on the final map permits honest completion.
565
+ self.stop_reason = "mapping_limit"
566
+ return
567
+ self.selected += 1
568
+ self.emit(mapping, committed / 4)
569
+
570
+ def reconstruct_suffix(self, spectrum, rows, committed, stabilizers):
571
+ """Emit exact suffix mappings and record time even on interruption."""
572
+ started = time.perf_counter()
573
+ try:
574
+ for suffix, residual_cost in spectrum.mappings_between(
575
+ self.lower - committed,
576
+ self.upper - committed,
577
+ symmetry_group=(
578
+ stabilizers if self.config.suffix_spectrum_orbit_pruning else ()
579
+ ),
580
+ ):
581
+ if self.stop_reason or self.expired():
582
+ break
583
+ if (
584
+ not self.config.suffix_spectrum_orbit_pruning
585
+ and len(stabilizers) > 1
586
+ ):
587
+ orbit_minimum = min(
588
+ tuple(group[image] for image in suffix) for group in stabilizers
589
+ )
590
+ if suffix != orbit_minimum:
591
+ self.statistics["suffix_spectrum_orbits_pruned"] += 1
592
+ continue
593
+ try:
594
+ for row, image in zip(rows, suffix):
595
+ self.mapping[row] = image
596
+ self.statistics["visited_leaves"] += 1
597
+ self.statistics["suffix_spectrum_mappings"] += 1
598
+ self.accept(committed + residual_cost)
599
+ finally:
600
+ for row in rows:
601
+ self.mapping[row] = -1
602
+ finally:
603
+ if self.config.suffix_spectrum_orbit_pruning:
604
+ self.statistics[
605
+ "suffix_spectrum_orbit_branches_pruned"
606
+ ] += spectrum.orbits_pruned
607
+ elapsed = time.perf_counter() - started
608
+ self.statistics["suffix_spectrum_reconstruction_seconds"] += elapsed
609
+ self.statistics["suffix_spectrum_seconds"] += elapsed
610
+
611
+ def visit(self, rows, committed, profile_sum, stabilizers, parent, depth): # noqa: C901
612
+ """Explore one reversible partial-bijection state."""
613
+ self.statistics["visited_nodes"] += 1
614
+ if self.expired() or self.proven_by_root:
615
+ return
616
+ if committed > self.budget():
617
+ self.statistics["lower_bound_pruned"] += 1
618
+ return
619
+ if not rows:
620
+ self.statistics["visited_leaves"] += 1
621
+ self.accept(committed)
622
+ return
623
+ token = self.domains.checkpoint()
624
+ forced = []
625
+ try:
626
+ if not self.propagate(rows, full=False):
627
+ return
628
+ if self.config.batch_forced_assignments:
629
+ rows, committed, profile_sum, stabilizers, feasible = self.batch_forced(
630
+ rows, committed, profile_sum, stabilizers, forced
631
+ )
632
+ if not feasible or self.stop_reason or committed > self.budget():
633
+ return
634
+ if not rows:
635
+ self.statistics["visited_leaves"] += 1
636
+ self.accept(committed)
637
+ return
638
+ columns = tuple(bits(self.available))
639
+ cheap = self.cheap_bounds(rows, columns, committed, profile_sum)
640
+ witness = self.matching_witness(parent, rows, columns)
641
+ if cheap is None or not self.propagate(rows, matching=witness):
642
+ return
643
+ ok, state = self.conditioned(rows, columns, committed, cheap, parent, depth)
644
+ if not ok or self.expired():
645
+ return
646
+ if (
647
+ self.config.suffix_spectrum
648
+ and not self.proof
649
+ and len(rows) <= self.config.suffix_spectrum_residual_limit
650
+ and self.statistics["suffix_spectrum_calls"]
651
+ < self.config.suffix_spectrum_max_calls
652
+ and not self.reactant_symmetry
653
+ and self.upper - committed <= self.config.suffix_spectrum_max_cost
654
+ ):
655
+ allowed = self.domains.matrix(rows, columns)
656
+ spectrum_type = SuffixCostSpectrum
657
+ spectrum_options = {}
658
+ if self.config.suffix_spectrum_representation == "reward_frontier":
659
+ spectrum_type = RewardFrontierSpectrum
660
+ spectrum_options["max_reward"] = (
661
+ self.config.suffix_spectrum_max_reward
662
+ )
663
+ self.statistics["suffix_spectrum_reward_frontier_calls"] += 1
664
+ started = time.perf_counter()
665
+ spectrum = spectrum_type(
666
+ self.a,
667
+ self.b,
668
+ rows,
669
+ columns,
670
+ self.cross[np.ix_(rows, columns)],
671
+ allowed,
672
+ deadline=self.deadline,
673
+ max_cost=self.upper - committed,
674
+ max_states=self.config.suffix_spectrum_max_states,
675
+ max_seconds=self.config.suffix_spectrum_max_seconds_per_call,
676
+ **spectrum_options,
677
+ )
678
+ self.statistics["suffix_spectrum_max_frontier"] = max(
679
+ self.statistics["suffix_spectrum_max_frontier"],
680
+ getattr(spectrum, "max_frontier", 0),
681
+ )
682
+ self.statistics["suffix_spectrum_calls"] += 1
683
+ try:
684
+ prepared = spectrum.prepare()
685
+ finally:
686
+ elapsed = time.perf_counter() - started
687
+ self.statistics["suffix_spectrum_prepare_seconds"] += elapsed
688
+ self.statistics["suffix_spectrum_seconds"] += elapsed
689
+ self.statistics["suffix_spectrum_states"] += spectrum.states
690
+ if prepared:
691
+ self.reconstruct_suffix(spectrum, rows, committed, stabilizers)
692
+ return
693
+ self.statistics["suffix_spectrum_skipped"] += 1
694
+ if (
695
+ self.config.separator_spectrum
696
+ and not self.proof
697
+ and len(rows) <= self.config.separator_residual_limit
698
+ and self.statistics["separator_spectrum_calls"]
699
+ < self.config.separator_spectrum_max_calls
700
+ and not self.reactant_symmetry
701
+ ):
702
+ allowed = self.domains.matrix(rows, columns)
703
+ spectrum = SeparatorCostSpectrum(
704
+ self.a,
705
+ self.b,
706
+ rows,
707
+ columns,
708
+ self.cross[np.ix_(rows, columns)],
709
+ allowed,
710
+ deadline=self.deadline,
711
+ max_separator_size=self.config.separator_max_size,
712
+ max_states=self.config.separator_max_states,
713
+ max_cost=self.upper - committed,
714
+ )
715
+ if spectrum.has_separator():
716
+ self.statistics["separator_spectrum_calls"] += 1
717
+ started = time.perf_counter()
718
+ if spectrum.prepare():
719
+ self.statistics["separator_spectrum_states"] += spectrum.states
720
+ for suffix, residual_cost in spectrum.mappings_between(
721
+ self.lower - committed, self.upper - committed
722
+ ):
723
+ if self.stop_reason or self.expired():
724
+ break
725
+ if len(stabilizers) > 1:
726
+ orbit_minimum = min(
727
+ tuple(group[image] for image in suffix)
728
+ for group in stabilizers
729
+ )
730
+ if suffix != orbit_minimum:
731
+ self.statistics[
732
+ "separator_spectrum_orbits_pruned"
733
+ ] += 1
734
+ continue
735
+ for row, image in zip(rows, suffix):
736
+ self.mapping[row] = image
737
+ self.statistics["visited_leaves"] += 1
738
+ self.statistics["separator_spectrum_mappings"] += 1
739
+ self.accept(committed + residual_cost)
740
+ for row in rows:
741
+ self.mapping[row] = -1
742
+ self.statistics["separator_spectrum_seconds"] += (
743
+ time.perf_counter() - started
744
+ )
745
+ return
746
+ self.statistics["separator_spectrum_skipped"] += 1
747
+ self.statistics["separator_spectrum_states"] += spectrum.states
748
+ self.statistics["separator_spectrum_seconds"] += (
749
+ time.perf_counter() - started
750
+ )
751
+ row = self.select_row(rows)
752
+ remaining = tuple(i for i in rows if i != row)
753
+ candidates = sorted(
754
+ bits(self.domains.masks[row] & self.available),
755
+ key=lambda j: (j != self.seed[row], int(self.cross[row, j]), j),
756
+ )
757
+ seen = set()
758
+ for image in candidates:
759
+ if self.stop_reason or self.proven_by_root:
760
+ break
761
+ if image in seen:
762
+ self.statistics["symmetry_pruned"] += 1
763
+ continue
764
+ seen.update(g[image] for g in stabilizers)
765
+ self.child(
766
+ row,
767
+ image,
768
+ remaining,
769
+ committed,
770
+ profile_sum,
771
+ stabilizers,
772
+ state,
773
+ depth,
774
+ )
775
+ finally:
776
+ for row, image, delta, bond_token in reversed(forced):
777
+ self.typed_bonds.restore(bond_token)
778
+ self.cross -= delta
779
+ self.available |= 1 << image
780
+ self.mapping[row] = -1
781
+ self.assigned_neighbors -= self.neighbors[:, row]
782
+ self.domains.restore(token)
783
+
784
+ def batch_forced(self, rows, committed, profile_sum, stabilizers, undo):
785
+ """Commit forced singleton rows iteratively before another full bound.
786
+
787
+ The caller owns the outer domain checkpoint. Assigned images and every
788
+ numeric cache are changed with the same reversible operations as child().
789
+ """
790
+ remaining = tuple(rows)
791
+ while remaining:
792
+ check_deadline(self.deadline)
793
+ forced_rows = [
794
+ row for row in remaining if self.domains.masks[row].bit_count() == 1
795
+ ]
796
+ if not forced_rows:
797
+ break
798
+ row = forced_rows[0]
799
+ image = next(bits(self.domains.masks[row]))
800
+ if not (self.available & (1 << image)):
801
+ self.statistics["hall_pruned"] += 1
802
+ return remaining, committed, profile_sum, stabilizers, False
803
+ next_rows = tuple(i for i in remaining if i != row)
804
+ increment = int(self.cross[row, image])
805
+ self.mapping[row] = image
806
+ self.assigned_neighbors += self.neighbors[:, row]
807
+ self.available &= ~(1 << image)
808
+ delta = np.abs(self.a[:, row, None] - self.b[image][None, :])
809
+ self.cross += delta
810
+ bond_token = self.typed_bonds.remove(
811
+ row, image, next_rows, tuple(bits(self.available))
812
+ )
813
+ undo.append((row, image, delta, bond_token))
814
+ if self.budget() == self.root_profile_lower:
815
+ self.tighten_zero_rows(row, image, next_rows)
816
+ stabilizers = tuple(g for g in stabilizers if g[image] == image)
817
+ committed += increment
818
+ profile_sum += int(self.profile[row, image])
819
+ self.statistics["forced_assignments"] += 1
820
+ remaining = next_rows
821
+ if not self.propagate(remaining, full=False):
822
+ return remaining, committed, profile_sum, stabilizers, False
823
+ return remaining, committed, profile_sum, stabilizers, True
824
+
825
+ def child(
826
+ self, row, image, remaining, committed, profile_sum, stabilizers, parent, depth
827
+ ):
828
+ token = self.domains.checkpoint()
829
+ increment = int(self.cross[row, image])
830
+ self.mapping[row] = image
831
+ self.assigned_neighbors += self.neighbors[:, row]
832
+ self.available &= ~(1 << image)
833
+ delta = np.abs(self.a[:, row, None] - self.b[:, image][None, :])
834
+ self.cross += delta
835
+ bond_token = self.typed_bonds.remove(
836
+ row, image, remaining, tuple(bits(self.available))
837
+ )
838
+ try:
839
+ if self.budget() == self.root_profile_lower:
840
+ self.tighten_zero_rows(row, image, remaining)
841
+ group = tuple(g for g in stabilizers if g[image] == image)
842
+ self.visit(
843
+ remaining,
844
+ committed + increment,
845
+ profile_sum + int(self.profile[row, image]),
846
+ group,
847
+ parent,
848
+ depth + 1,
849
+ )
850
+ finally:
851
+ self.typed_bonds.restore(bond_token)
852
+ self.cross -= delta
853
+ self.available |= 1 << image
854
+ self.mapping[row] = -1
855
+ self.assigned_neighbors -= self.neighbors[:, row]
856
+ self.domains.restore(token)