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,940 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from dataclasses import dataclass
5
+ from time import perf_counter
6
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
7
+
8
+ import networkx as nx
9
+
10
+ from synkit.Graph.Canon.exact import (
11
+ CanonicalSearchIncomplete,
12
+ ExactCanonicalResult,
13
+ ExactColoredGraphCanonicalizer,
14
+ IncompleteCanonicalResult,
15
+ )
16
+
17
+ from ._common import (
18
+ AutomorphismResult,
19
+ CanonicalResult,
20
+ SymmetryConfig,
21
+ edge_token,
22
+ graph_key_from_order,
23
+ node_token,
24
+ orbits_from_mappings,
25
+ should_stop,
26
+ )
27
+ from .wl_canon import WLCanonicalizer
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class IRInternalResult:
32
+ """
33
+ Internal exact-search result produced by :class:`IRCanonicalEngine`.
34
+
35
+ :param canonical_order:
36
+ Canonical node ordering selected by the exact individualize-refine search.
37
+ :type canonical_order: List[Any]
38
+
39
+ :param canonical_key:
40
+ Canonical graph key associated with ``canonical_order``.
41
+ :type canonical_key: Tuple[Any, ...]
42
+
43
+ :param automorphism_count:
44
+ Number of automorphisms discovered for the best canonical form. When the
45
+ search is truncated, this may be a lower bound or capped count depending
46
+ on the caller configuration.
47
+ :type automorphism_count: int
48
+
49
+ :param sample_permutations:
50
+ Sample node orders equivalent to the canonical form.
51
+ :type sample_permutations: List[List[Any]]
52
+
53
+ :param sample_mappings:
54
+ Sample automorphism mappings represented as ``source_node -> image_node``.
55
+ :type sample_mappings: List[Dict[Any, Any]]
56
+
57
+ :param orbits:
58
+ Orbit partition induced by the sampled automorphism mappings.
59
+ :type orbits: List[set[Any]]
60
+
61
+ :param elapsed_seconds:
62
+ Wall-clock runtime in seconds for the executed search.
63
+ :type elapsed_seconds: float
64
+
65
+ :param stopped_early:
66
+ Whether the search stopped early because of timeout, count limit, or
67
+ ``stop_after_two``.
68
+ :type stopped_early: bool
69
+ """
70
+
71
+ canonical_order: List[Any]
72
+ canonical_key: Tuple[Any, ...]
73
+ automorphism_count: int
74
+ sample_permutations: List[List[Any]]
75
+ sample_mappings: List[Dict[Any, Any]]
76
+ orbits: List[set[Any]]
77
+ elapsed_seconds: float
78
+ stopped_early: bool
79
+
80
+
81
+ class IRCanonicalEngine:
82
+ """Exact individualize-refine engine for canonical labeling and automorphism search.
83
+
84
+ This engine starts from a Weisfeiler-Lehman (WL) coloring, then applies an
85
+ exact individualize-refine search on ambiguous cells. It is shared by both
86
+ canonicalization and automorphism queries so that an exact completed result
87
+ can be cached and reused.
88
+
89
+ :param source:
90
+ Input CRN-like object or graph accepted by :class:`WLCanonicalizer`.
91
+ :type source: Any
92
+
93
+ :param include_rule:
94
+ Whether rule/reaction node semantics should be encoded into node tokens.
95
+ :type include_rule: bool
96
+
97
+ :param include_stoich:
98
+ Whether stoichiometric edge/node information should be included in the
99
+ canonicalization tokens.
100
+ :type include_stoich: bool
101
+
102
+ :param wl_iters:
103
+ Maximum number of WL refinement iterations used to initialize the exact
104
+ search.
105
+ :type wl_iters: int
106
+
107
+ :param wl_digest_size:
108
+ Digest size used by the WL canonicalizer for hashed color refinement.
109
+ :type wl_digest_size: int
110
+
111
+ :param config:
112
+ Symmetry configuration controlling which semantic attributes are included
113
+ in node and edge tokens.
114
+ :type config: Optional[SymmetryConfig]
115
+
116
+ .. rubric:: Notes
117
+
118
+ Exact automorphism counting is generally harder than obtaining a single
119
+ canonical order. Therefore, this class caches completed exact runs so later
120
+ canonicalization and automorphism queries can reuse the same result instead
121
+ of rerunning the search.
122
+
123
+ .. rubric:: Examples
124
+
125
+ .. code-block:: python
126
+
127
+ from synkit.CRN.Sym._ir import IRCanonicalEngine
128
+
129
+ engine = IRCanonicalEngine(
130
+ source=crn,
131
+ include_rule=True,
132
+ include_stoich=True,
133
+ )
134
+
135
+ canon = engine.canonical_result()
136
+ print(canon.canonical_order)
137
+ print(canon.canonical_key)
138
+
139
+ auto = engine.automorphism_result(max_count=10)
140
+ print(auto.automorphism_count)
141
+ print(auto.orbits)
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ source: Any,
147
+ *,
148
+ include_rule: bool = True,
149
+ include_stoich: bool = True,
150
+ wl_iters: int = 20,
151
+ wl_digest_size: int = 16,
152
+ config: Optional[SymmetryConfig] = None,
153
+ ) -> None:
154
+ """
155
+ Initialize the exact IR engine.
156
+
157
+ :param source:
158
+ Input CRN-like object or graph.
159
+ :type source: Any
160
+
161
+ :param include_rule:
162
+ Whether rule/reaction semantics are included.
163
+ :type include_rule: bool
164
+
165
+ :param include_stoich:
166
+ Whether stoichiometric information is encoded into the graph tokens.
167
+ :type include_stoich: bool
168
+
169
+ :param wl_iters:
170
+ Number of WL refinement iterations.
171
+ :type wl_iters: int
172
+
173
+ :param wl_digest_size:
174
+ Digest size used internally by WL hashing.
175
+ :type wl_digest_size: int
176
+
177
+ :param config:
178
+ Optional semantic symmetry configuration. If ``None``, semantic mode
179
+ is used.
180
+ :type config: Optional[SymmetryConfig]
181
+ """
182
+ self.config = config or SymmetryConfig.semantic()
183
+ self.wl = WLCanonicalizer(
184
+ source,
185
+ include_rule=include_rule,
186
+ include_stoich=include_stoich,
187
+ n_iter=wl_iters,
188
+ digest_size=wl_digest_size,
189
+ config=self.config,
190
+ )
191
+ self._refine_cache: Dict[Tuple[Tuple[Any, ...], ...], List[List[Any]]] = {}
192
+ self._sig_cache: Dict[
193
+ Tuple[Tuple[Tuple[Any, ...], ...], Any], Tuple[Any, ...]
194
+ ] = {}
195
+ self._node_tok_cache: Dict[Any, Tuple[Any, ...]] = {}
196
+ self._deg_cache: Dict[Any, Tuple[int, int]] = {}
197
+ self._pred_cache: Dict[Any, List[Tuple[Any, Tuple[Any, ...]]]] = {}
198
+ self._succ_cache: Dict[Any, List[Tuple[Any, Tuple[Any, ...]]]] = {}
199
+ self._act_cache: Dict[Any, int] = {}
200
+ self._exact_full_cache: Optional[IRInternalResult] = None
201
+ self._prepare_static_caches()
202
+
203
+ @property
204
+ def G(self) -> nx.DiGraph:
205
+ """Return the internal directed graph used by the engine.
206
+
207
+ :return:
208
+ Directed graph produced by :class:`WLCanonicalizer`.
209
+ :rtype: nx.DiGraph
210
+ """
211
+ return self.wl.G
212
+
213
+ @property
214
+ def graph_type(self) -> str:
215
+ """Return the graph type label reported by the WL canonicalizer.
216
+
217
+ :return:
218
+ Graph type identifier.
219
+ :rtype: str
220
+ """
221
+ return self.wl.graph_type
222
+
223
+ def _prepare_static_caches(self) -> None:
224
+ """Precompute static per-node caches used during refinement.
225
+
226
+ This stores node tokens, degree pairs, predecessor signatures, successor
227
+ signatures, and a simple activity score for branch ordering.
228
+
229
+ :return:
230
+ ``None``.
231
+ :rtype: None
232
+ """
233
+ G = self.G
234
+ for v in G.nodes():
235
+ self._node_tok_cache[v] = node_token(G.nodes[v], self.config)
236
+ self._deg_cache[v] = (G.in_degree(v), G.out_degree(v))
237
+ self._pred_cache[v] = [
238
+ (u, edge_token(G[u][v], self.config)) for u in G.predecessors(v)
239
+ ]
240
+ self._succ_cache[v] = [
241
+ (u, edge_token(G[v][u], self.config)) for u in G.successors(v)
242
+ ]
243
+ self._act_cache[v] = len(self._pred_cache[v]) + len(self._succ_cache[v])
244
+
245
+ @staticmethod
246
+ def _part_key(part: Sequence[Sequence[Any]]) -> Tuple[Tuple[Any, ...], ...]:
247
+ """Convert a partition into a hashable cache key.
248
+
249
+ :param part:
250
+ Partition represented as a sequence of cells.
251
+ :type part: Sequence[Sequence[Any]]
252
+
253
+ :return:
254
+ Immutable tuple-of-tuples representation of the partition.
255
+ :rtype: Tuple[Tuple[Any, ...], ...]
256
+ """
257
+ return tuple(tuple(cell) for cell in part)
258
+
259
+ def _cell_signature(self, v: Any, part: Sequence[Sequence[Any]]) -> Tuple[Any, ...]:
260
+ """Compute the refinement signature of a node with respect to a partition.
261
+
262
+ The signature combines node token, degree, and per-cell incident edge
263
+ token multisets. This is the core feature used to split cells during
264
+ refinement.
265
+
266
+ :param v:
267
+ Node whose signature is requested.
268
+ :type v: Any
269
+
270
+ :param part:
271
+ Current partition.
272
+ :type part: Sequence[Sequence[Any]]
273
+
274
+ :return:
275
+ Signature tuple used for exact refinement.
276
+ :rtype: Tuple[Any, ...]
277
+ """
278
+ pkey = self._part_key(part)
279
+ ck = (pkey, v)
280
+ if ck in self._sig_cache:
281
+ return self._sig_cache[ck]
282
+
283
+ tok = self._node_tok_cache[v]
284
+ deg = self._deg_cache[v]
285
+
286
+ pred_map = defaultdict(list)
287
+ succ_map = defaultdict(list)
288
+ for u, etok in self._pred_cache[v]:
289
+ pred_map[u].append(etok)
290
+ for u, etok in self._succ_cache[v]:
291
+ succ_map[u].append(etok)
292
+
293
+ per_cell: List[Any] = []
294
+ for cell in part:
295
+ cell_set = set(cell)
296
+ in_items: List[Tuple[Any, ...]] = []
297
+ out_items: List[Tuple[Any, ...]] = []
298
+ for u in cell_set:
299
+ if u in pred_map:
300
+ in_items.extend(pred_map[u])
301
+ if u in succ_map:
302
+ out_items.extend(succ_map[u])
303
+ in_items.sort(key=str)
304
+ out_items.sort(key=str)
305
+ if self.config.use_edge_direction:
306
+ per_cell.append((tuple(in_items), tuple(out_items)))
307
+ else:
308
+ mixed = in_items + out_items
309
+ mixed.sort(key=str)
310
+ per_cell.append(tuple(mixed))
311
+
312
+ sig = (tok, deg, tuple(per_cell))
313
+ self._sig_cache[ck] = sig
314
+ return sig
315
+
316
+ def _refine(self, part: Sequence[Sequence[Any]]) -> List[List[Any]]:
317
+ """Refine a partition until it becomes stable.
318
+
319
+ Cells are repeatedly split using :meth:`_cell_signature` until no further
320
+ refinement is possible.
321
+
322
+ :param part:
323
+ Input partition.
324
+ :type part: Sequence[Sequence[Any]]
325
+
326
+ :return:
327
+ Stable refined partition.
328
+ :rtype: List[List[Any]]
329
+ """
330
+ pkey = self._part_key(part)
331
+ if pkey in self._refine_cache:
332
+ return [list(cell) for cell in self._refine_cache[pkey]]
333
+
334
+ cur = [sorted(cell, key=str) for cell in part]
335
+ while True:
336
+ changed = False
337
+ new_part: List[List[Any]] = []
338
+ cur_key = self._part_key(cur)
339
+ for cell in cur:
340
+ if len(cell) <= 1:
341
+ new_part.append(list(cell))
342
+ continue
343
+ buckets: Dict[Tuple[Any, ...], List[Any]] = defaultdict(list)
344
+ for v in cell:
345
+ sig = self._cell_signature(v, cur)
346
+ buckets[sig].append(v)
347
+ if len(buckets) == 1:
348
+ new_part.append(list(cell))
349
+ else:
350
+ changed = True
351
+ for _, block in sorted(
352
+ buckets.items(),
353
+ key=lambda kv: (str(kv[0]), tuple(map(str, kv[1]))),
354
+ ):
355
+ new_part.append(sorted(block, key=str))
356
+ cur = new_part
357
+ if not changed:
358
+ result = [list(cell) for cell in cur]
359
+ self._refine_cache[cur_key] = [list(cell) for cell in result]
360
+ self._refine_cache[pkey] = [list(cell) for cell in result]
361
+ return result
362
+
363
+ def _initial_partition(self) -> List[List[Any]]:
364
+ """Build the initial partition from WL colors and node tokens.
365
+
366
+ :return:
367
+ Refined initial partition.
368
+ :rtype: List[List[Any]]
369
+ """
370
+ buckets: Dict[Tuple[Any, ...], List[Any]] = defaultdict(list)
371
+ for v in self.G.nodes():
372
+ buckets[(self.wl.color_of(v), self._node_tok_cache[v])].append(v)
373
+ part = [
374
+ sorted(vs, key=str)
375
+ for _, vs in sorted(
376
+ buckets.items(), key=lambda kv: (str(kv[0]), tuple(map(str, kv[1])))
377
+ )
378
+ ]
379
+ return self._refine(part)
380
+
381
+ def _target_cell(self, part: Sequence[Sequence[Any]]) -> List[Any]:
382
+ """Choose the next ambiguous cell to individualize.
383
+
384
+ Preference is given to smaller cells, then cells with higher total
385
+ activity, then token and string-based tie breaking.
386
+
387
+ :param part:
388
+ Current partition.
389
+ :type part: Sequence[Sequence[Any]]
390
+
391
+ :return:
392
+ Selected ambiguous cell.
393
+ :rtype: List[Any]
394
+ """
395
+ ambiguous = [list(cell) for cell in part if len(cell) > 1]
396
+ return min(
397
+ ambiguous,
398
+ key=lambda cell: (
399
+ len(cell),
400
+ -sum(self._act_cache[v] for v in cell),
401
+ tuple(self._node_tok_cache[v] for v in cell),
402
+ tuple(map(str, cell)),
403
+ ),
404
+ )
405
+
406
+ def _candidate_order_key(
407
+ self, v: Any, part: Sequence[Sequence[Any]]
408
+ ) -> Tuple[Any, ...]:
409
+ """Build a sorting key for candidates within the chosen target cell.
410
+
411
+ :param v:
412
+ Candidate node.
413
+ :type v: Any
414
+
415
+ :param part:
416
+ Current partition.
417
+ :type part: Sequence[Sequence[Any]]
418
+
419
+ :return:
420
+ Candidate ordering key.
421
+ :rtype: Tuple[Any, ...]
422
+ """
423
+ return (
424
+ self._cell_signature(v, part),
425
+ -self._act_cache[v],
426
+ str(v),
427
+ )
428
+
429
+ @staticmethod
430
+ def _slice_result(
431
+ res: IRInternalResult, max_count: Optional[int], *, force_stopped: bool = False
432
+ ) -> IRInternalResult:
433
+ """Return a truncated copy of an exact result.
434
+
435
+ :param res:
436
+ Full internal result.
437
+ :type res: IRInternalResult
438
+
439
+ :param max_count:
440
+ Maximum number of permutations/mappings to retain. If ``None``, the
441
+ original result is returned unchanged.
442
+ :type max_count: Optional[int]
443
+
444
+ :param force_stopped:
445
+ Whether the sliced result should be marked as stopped early even if
446
+ the original full result completed.
447
+ :type force_stopped: bool
448
+
449
+ :return:
450
+ Possibly truncated internal result.
451
+ :rtype: IRInternalResult
452
+ """
453
+ if max_count is None:
454
+ return res
455
+ perms = [list(p) for p in res.sample_permutations[:max_count]]
456
+ maps = [dict(m) for m in res.sample_mappings[:max_count]]
457
+ stopped = (
458
+ force_stopped or (res.automorphism_count > max_count) or res.stopped_early
459
+ )
460
+ orbits = (
461
+ res.orbits
462
+ if not stopped
463
+ else orbits_from_mappings(
464
+ res.canonical_order,
465
+ maps if maps else [{v: v for v in res.canonical_order}],
466
+ )
467
+ )
468
+ return IRInternalResult(
469
+ canonical_order=list(res.canonical_order),
470
+ canonical_key=res.canonical_key,
471
+ automorphism_count=min(res.automorphism_count, max_count),
472
+ sample_permutations=perms,
473
+ sample_mappings=maps,
474
+ orbits=orbits,
475
+ elapsed_seconds=res.elapsed_seconds,
476
+ stopped_early=stopped,
477
+ )
478
+
479
+ def _reuse_cached_run(
480
+ self,
481
+ *,
482
+ max_count: Optional[int],
483
+ timeout_sec: Optional[float],
484
+ stop_after_two: bool,
485
+ ) -> Optional[IRInternalResult]:
486
+ """Reuse a previously completed exact result when valid.
487
+
488
+ :param max_count:
489
+ Requested maximum number of stored automorphisms.
490
+ :type max_count: Optional[int]
491
+
492
+ :param timeout_sec:
493
+ Optional timeout for the current request.
494
+ :type timeout_sec: Optional[float]
495
+
496
+ :param stop_after_two:
497
+ Whether the caller only needs to distinguish uniqueness from
498
+ non-uniqueness.
499
+ :type stop_after_two: bool
500
+
501
+ :return:
502
+ Cached result if reusable, otherwise ``None``.
503
+ :rtype: Optional[IRInternalResult]
504
+ """
505
+ if timeout_sec is not None or self._exact_full_cache is None:
506
+ return None
507
+ if stop_after_two:
508
+ return self._slice_result(self._exact_full_cache, 2)
509
+ return self._slice_result(self._exact_full_cache, max_count)
510
+
511
+ def _initialize_run_state(
512
+ self,
513
+ ) -> Tuple[float, List[List[Any]], List[Any], Tuple[Any, ...]]:
514
+ """Prepare the initial state for an exact run.
515
+
516
+ :return:
517
+ Tuple containing start time, initial refined partition, WL order, and
518
+ the canonical key of the WL order.
519
+ :rtype: Tuple[float, List[List[Any]], List[Any], Tuple[Any, ...]]
520
+ """
521
+ start = perf_counter()
522
+ init = self._initial_partition()
523
+ wl_order = self.wl.canonical_order()
524
+ wl_key = graph_key_from_order(self.G, wl_order, self.config)
525
+ return start, init, list(wl_order), wl_key
526
+
527
+ def _build_child_partition(
528
+ self, part: Sequence[Sequence[Any]], target: Sequence[Any], chosen: Any
529
+ ) -> List[List[Any]]:
530
+ """Create a child partition by individualizing one node from a target cell.
531
+
532
+ :param part:
533
+ Current partition.
534
+ :type part: Sequence[Sequence[Any]]
535
+
536
+ :param target:
537
+ Target ambiguous cell to split.
538
+ :type target: Sequence[Any]
539
+
540
+ :param chosen:
541
+ Node to isolate into its own singleton cell.
542
+ :type chosen: Any
543
+
544
+ :return:
545
+ Child partition after individualization.
546
+ :rtype: List[List[Any]]
547
+ """
548
+ target_set = set(target)
549
+ rem = [u for u in target if u != chosen]
550
+ child: List[List[Any]] = []
551
+ for cell in part:
552
+ if set(cell) == target_set and len(cell) == len(target):
553
+ child.append([chosen])
554
+ if rem:
555
+ child.append(rem)
556
+ else:
557
+ child.append(list(cell))
558
+ return child
559
+
560
+ def _finalize_leaf(
561
+ self,
562
+ part: Sequence[Sequence[Any]],
563
+ *,
564
+ best_key: Optional[Tuple[Any, ...]],
565
+ best_order: Optional[List[Any]],
566
+ sample_perms: List[List[Any]],
567
+ count: int,
568
+ max_count: Optional[int],
569
+ stop_after_two: bool,
570
+ ) -> Tuple[
571
+ Optional[Tuple[Any, ...]], Optional[List[Any]], List[List[Any]], int, bool
572
+ ]:
573
+ """Process a fully individualized partition.
574
+
575
+ :param part:
576
+ Fully discrete partition.
577
+ :type part: Sequence[Sequence[Any]]
578
+
579
+ :param best_key:
580
+ Current best canonical key.
581
+ :type best_key: Optional[Tuple[Any, ...]]
582
+
583
+ :param best_order:
584
+ Current best canonical order.
585
+ :type best_order: Optional[List[Any]]
586
+
587
+ :param sample_perms:
588
+ Collected sample permutations for the current best key.
589
+ :type sample_perms: List[List[Any]]
590
+
591
+ :param count:
592
+ Current automorphism count for the best key.
593
+ :type count: int
594
+
595
+ :param max_count:
596
+ Maximum number of stored permutations.
597
+ :type max_count: Optional[int]
598
+
599
+ :param stop_after_two:
600
+ Whether to stop once two equivalent canonical orders are found.
601
+ :type stop_after_two: bool
602
+
603
+ :return:
604
+ Updated ``(best_key, best_order, sample_perms, count, should_stop_now)``.
605
+ :rtype: Tuple[Optional[Tuple[Any, ...]], Optional[List[Any]], List[List[Any]], int, bool]
606
+ """
607
+ order = [cell[0] for cell in part]
608
+ key = graph_key_from_order(self.G, order, self.config)
609
+
610
+ if best_key is None or key < best_key:
611
+ return key, order, [list(order)], 1, False
612
+
613
+ if key == best_key:
614
+ count += 1
615
+ if max_count is None or len(sample_perms) < max_count:
616
+ sample_perms.append(list(order))
617
+ return (
618
+ best_key,
619
+ best_order,
620
+ sample_perms,
621
+ count,
622
+ (stop_after_two and count >= 2),
623
+ )
624
+
625
+ return best_key, best_order, sample_perms, count, False
626
+
627
+ def _assemble_result(
628
+ self,
629
+ *,
630
+ start: float,
631
+ best_key: Tuple[Any, ...],
632
+ best_order: List[Any],
633
+ sample_perms: List[List[Any]],
634
+ count: int,
635
+ stopped: bool,
636
+ ) -> IRInternalResult:
637
+ """Assemble the final :class:`IRInternalResult`.
638
+
639
+ :param start:
640
+ Start time returned by :func:`perf_counter`.
641
+ :type start: float
642
+
643
+ :param best_key:
644
+ Best canonical key found.
645
+ :type best_key: Tuple[Any, ...]
646
+
647
+ :param best_order:
648
+ Best canonical order found.
649
+ :type best_order: List[Any]
650
+
651
+ :param sample_perms:
652
+ Sample canonical-equivalent permutations.
653
+ :type sample_perms: List[List[Any]]
654
+
655
+ :param count:
656
+ Automorphism count accumulated for the best key.
657
+ :type count: int
658
+
659
+ :param stopped:
660
+ Whether the search stopped early.
661
+ :type stopped: bool
662
+
663
+ :return:
664
+ Internal result object.
665
+ :rtype: IRInternalResult
666
+ """
667
+ sample_mappings = [
668
+ {best_order[i]: p[i] for i in range(len(best_order))} for p in sample_perms
669
+ ]
670
+ orbits = orbits_from_mappings(
671
+ best_order,
672
+ sample_mappings if sample_mappings else [{v: v for v in best_order}],
673
+ )
674
+ return IRInternalResult(
675
+ canonical_order=best_order,
676
+ canonical_key=best_key,
677
+ automorphism_count=count,
678
+ sample_permutations=sample_perms,
679
+ sample_mappings=sample_mappings,
680
+ orbits=orbits,
681
+ elapsed_seconds=perf_counter() - start,
682
+ stopped_early=stopped,
683
+ )
684
+
685
+ def run(
686
+ self,
687
+ *,
688
+ max_count: Optional[int] = None,
689
+ timeout_sec: Optional[float] = None,
690
+ stop_after_two: bool = False,
691
+ ) -> IRInternalResult:
692
+ """Run exact individualize-refine search.
693
+
694
+ This method computes a canonical order exactly and, when requested,
695
+ counts automorphisms of the best canonical form.
696
+
697
+ :param max_count:
698
+ Optional cap on the number of stored sample permutations and mappings.
699
+ This does not necessarily limit the search unless enforced by
700
+ :func:`should_stop`.
701
+ :type max_count: Optional[int]
702
+
703
+ :param timeout_sec:
704
+ Optional wall-clock timeout in seconds.
705
+ :type timeout_sec: Optional[float]
706
+
707
+ :param stop_after_two:
708
+ If ``True``, stop as soon as two canonical-equivalent leaves are found.
709
+ This is useful for fast uniqueness testing.
710
+ :type stop_after_two: bool
711
+
712
+ :return:
713
+ Exact-search internal result.
714
+ :rtype: IRInternalResult
715
+ """
716
+ if max_count is not None or timeout_sec is not None or stop_after_two:
717
+ return self._run_bounded_compatibility(
718
+ max_count=max_count,
719
+ timeout_sec=timeout_sec,
720
+ stop_after_two=stop_after_two,
721
+ )
722
+ cached = self._reuse_cached_run(
723
+ max_count=max_count,
724
+ timeout_sec=timeout_sec,
725
+ stop_after_two=stop_after_two,
726
+ )
727
+ if cached is not None:
728
+ return cached
729
+
730
+ native = ExactColoredGraphCanonicalizer(
731
+ self.G,
732
+ node_color=lambda attrs: node_token(attrs, self.config),
733
+ edge_color=lambda attrs: edge_token(attrs, self.config),
734
+ ).search(timeout_seconds=timeout_sec)
735
+ if isinstance(native, IncompleteCanonicalResult):
736
+ raise CanonicalSearchIncomplete(
737
+ "CRN canonicalization did not complete: " f"{native.reason}."
738
+ )
739
+ if not isinstance(native, ExactCanonicalResult):
740
+ raise RuntimeError("Native exact canonicalizer returned no result.")
741
+
742
+ full_mappings = [witness.as_dict() for witness in native.automorphisms]
743
+ cap = 2 if stop_after_two else max_count
744
+ stopped = cap is not None and len(full_mappings) > cap
745
+ sample_mappings = full_mappings if cap is None else full_mappings[:cap]
746
+ best_order = list(native.canonical_order)
747
+ sample_perms = [
748
+ [mapping[node] for node in best_order] for mapping in sample_mappings
749
+ ]
750
+ orbits = orbits_from_mappings(
751
+ best_order,
752
+ (
753
+ sample_mappings
754
+ if sample_mappings
755
+ else [{node: node for node in best_order}]
756
+ ),
757
+ )
758
+ result = IRInternalResult(
759
+ canonical_order=best_order,
760
+ canonical_key=graph_key_from_order(
761
+ self.G,
762
+ best_order,
763
+ self.config,
764
+ ),
765
+ automorphism_count=len(sample_mappings),
766
+ sample_permutations=sample_perms,
767
+ sample_mappings=sample_mappings,
768
+ orbits=orbits,
769
+ elapsed_seconds=native.statistics.elapsed_seconds,
770
+ stopped_early=stopped,
771
+ )
772
+ if timeout_sec is None:
773
+ full_result = IRInternalResult(
774
+ canonical_order=best_order,
775
+ canonical_key=result.canonical_key,
776
+ automorphism_count=len(full_mappings),
777
+ sample_permutations=[
778
+ [mapping[node] for node in best_order] for mapping in full_mappings
779
+ ],
780
+ sample_mappings=full_mappings,
781
+ orbits=[set(cell) for cell in native.orbits],
782
+ elapsed_seconds=native.statistics.elapsed_seconds,
783
+ stopped_early=False,
784
+ )
785
+ self._exact_full_cache = full_result
786
+ return result
787
+
788
+ def _run_bounded_compatibility(
789
+ self,
790
+ *,
791
+ max_count: Optional[int],
792
+ timeout_sec: Optional[float],
793
+ stop_after_two: bool,
794
+ ) -> IRInternalResult:
795
+ """Run the historical bounded diagnostic IR traversal.
796
+
797
+ This path preserves CRN automorphism-count and timeout diagnostics.
798
+ ``stopped_early`` is authoritative: callers may inspect the partial
799
+ evidence, but :meth:`canonical_result` refuses to promote it.
800
+ """
801
+ cached = self._reuse_cached_run(
802
+ max_count=max_count,
803
+ timeout_sec=timeout_sec,
804
+ stop_after_two=stop_after_two,
805
+ )
806
+ if cached is not None:
807
+ return cached
808
+
809
+ start, init, wl_order, wl_key = self._initialize_run_state()
810
+ best_key: Optional[Tuple[Any, ...]] = wl_key
811
+ best_order: Optional[List[Any]] = list(wl_order)
812
+ sample_perms: List[List[Any]] = []
813
+ count = 0
814
+ stopped = False
815
+
816
+ def dfs(part: List[List[Any]]) -> None:
817
+ nonlocal best_key, best_order, sample_perms, count, stopped
818
+ if stopped:
819
+ return
820
+ if should_stop(
821
+ start,
822
+ timeout_sec,
823
+ count=count,
824
+ max_count=max_count,
825
+ ):
826
+ stopped = True
827
+ return
828
+
829
+ refined = self._refine(part)
830
+ ambiguous = [cell for cell in refined if len(cell) > 1]
831
+ if not ambiguous:
832
+ (
833
+ best_key,
834
+ best_order,
835
+ sample_perms,
836
+ count,
837
+ stop_now,
838
+ ) = self._finalize_leaf(
839
+ refined,
840
+ best_key=best_key,
841
+ best_order=best_order,
842
+ sample_perms=sample_perms,
843
+ count=count,
844
+ max_count=max_count,
845
+ stop_after_two=stop_after_two,
846
+ )
847
+ if stop_now:
848
+ stopped = True
849
+ return
850
+
851
+ target = self._target_cell(refined)
852
+ ordered_candidates = sorted(
853
+ target,
854
+ key=lambda value: self._candidate_order_key(
855
+ value,
856
+ refined,
857
+ ),
858
+ )
859
+ for value in ordered_candidates:
860
+ child = self._build_child_partition(
861
+ refined,
862
+ target,
863
+ value,
864
+ )
865
+ dfs(child)
866
+ if stopped:
867
+ return
868
+
869
+ dfs(init)
870
+ assert best_key is not None and best_order is not None
871
+ return self._assemble_result(
872
+ start=start,
873
+ best_key=best_key,
874
+ best_order=best_order,
875
+ sample_perms=sample_perms,
876
+ count=count,
877
+ stopped=stopped,
878
+ )
879
+
880
+ def canonical_result(
881
+ self, *, timeout_sec: Optional[float] = None
882
+ ) -> CanonicalResult:
883
+ """Compute the exact canonicalization result.
884
+
885
+ :param timeout_sec:
886
+ Optional timeout in seconds.
887
+ :type timeout_sec: Optional[float]
888
+
889
+ :return:
890
+ Public canonicalization result.
891
+ :rtype: CanonicalResult
892
+ """
893
+ res = self.run(timeout_sec=timeout_sec)
894
+ if res.stopped_early:
895
+ raise CanonicalSearchIncomplete(
896
+ "CRN canonical identity requires a complete search."
897
+ )
898
+ return CanonicalResult(
899
+ canonical_order=res.canonical_order,
900
+ canonical_key=res.canonical_key,
901
+ exact=True,
902
+ elapsed_seconds=res.elapsed_seconds,
903
+ )
904
+
905
+ def automorphism_result(
906
+ self,
907
+ *,
908
+ max_count: Optional[int] = None,
909
+ timeout_sec: Optional[float] = None,
910
+ stop_after_two: bool = False,
911
+ ) -> AutomorphismResult:
912
+ """Compute the automorphism result.
913
+
914
+ :param max_count:
915
+ Optional cap on the number of stored sample mappings.
916
+ :type max_count: Optional[int]
917
+
918
+ :param timeout_sec:
919
+ Optional timeout in seconds.
920
+ :type timeout_sec: Optional[float]
921
+
922
+ :param stop_after_two:
923
+ Whether to stop once two automorphisms are found.
924
+ :type stop_after_two: bool
925
+
926
+ :return:
927
+ Public automorphism result.
928
+ :rtype: AutomorphismResult
929
+ """
930
+ res = self.run(
931
+ max_count=max_count, timeout_sec=timeout_sec, stop_after_two=stop_after_two
932
+ )
933
+ return AutomorphismResult(
934
+ graph_type=self.graph_type,
935
+ automorphism_count=res.automorphism_count,
936
+ sample_mappings=res.sample_mappings,
937
+ orbits=res.orbits,
938
+ elapsed_seconds=res.elapsed_seconds,
939
+ stopped_early=res.stopped_early,
940
+ )