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,123 @@
1
+ # Native sparse graph canonicalization
2
+
3
+ This is an opt-in C++17 prepared-graph core. It uses a versioned C ABI and
4
+ ctypes, following the Mapper build and ownership pattern without depending on
5
+ the Mapper library, RDKit headers, Python headers, nauty, or solver SDKs.
6
+ The existing Python exact canonicalizer and persisted stereo `/2` keys are
7
+ unchanged. This prototype is not yet bundled in platform wheels.
8
+
9
+ ```sh
10
+ cmake -S synkit/Graph/Canon/cpp -B /tmp/canon-build \
11
+ -DCMAKE_BUILD_TYPE=Release -DSYNKIT_CANON_BUILD_EXAMPLES=ON
12
+ cmake --build /tmp/canon-build --config Release --parallel 2
13
+ /tmp/canon-build/canonical_graph
14
+ ```
15
+
16
+ The example resides under `Release/` on Windows. A developer can also call
17
+ the explicit, content-addressed CMake builder:
18
+
19
+ ```python
20
+ from synkit.Graph.Canon._native import build_native_canon
21
+ from synkit.Graph.Stereo.native import classify_native_stereograph_mirror
22
+
23
+ library = build_native_canon("/tmp/canon-libraries")
24
+ result = classify_native_stereograph_mirror(
25
+ graph, configured_registry, library_path=library,
26
+ timeout_seconds=5, max_search_nodes=100_000,
27
+ max_workspace_bytes=64 * 1024 * 1024,
28
+ max_output_bytes=8 * 1024 * 1024,
29
+ )
30
+ if result.is_definitive:
31
+ print(result.status, result.atom_mirror_isomorphism)
32
+ else:
33
+ print(result.reason)
34
+ ```
35
+
36
+ The stereo adapter accepts an already prepared attributed constitution and
37
+ source-declared registry. It uses the existing full configuration-orbit
38
+ expansion and all eleven configured families. It does not infer missing
39
+ carriers, select a framework orientation, normalize resonance, or replace
40
+ strict completeness checking in the existing chemical assessment API.
41
+
42
+ ## Exactness and convention
43
+
44
+ Initial color cells are ordered by exact palette rank. Refinement repeatedly
45
+ splits each cell by sorted neighbor-cell sequences, using lexicographic
46
+ signature order. Branching selects the first smallest nonsingleton cell,
47
+ individualizes its members, and retains the minimum full sparse leaf record.
48
+ Branch order is not semantic. Verified graph automorphisms stabilizing the
49
+ current ordered partition and exact twin transpositions may remove equivalent
50
+ branches. A terminal partition of twin cells needs only one leaf because
51
+ every within-cell permutation preserves the entire colored graph.
52
+
53
+ Exact open/closed-neighborhood keys cache false/true twin blocks. Their
54
+ symmetric factors contribute orbits implicitly, without enumerating factorial
55
+ groups. Setup is deferred until refinement leaves an unresolved cell. Direct
56
+ generator orbits are tried first. If they leave an unexplored
57
+ branch, bounded Schreier calculations can recover compositions fixing the
58
+ individualized path; resulting generators are also checked against the current
59
+ ordered partition. A discovery work/generator cutoff retains the direct verified
60
+ subgroup, so it weakens pruning without invalidating a complete key. This
61
+ adapts Mapper's stabilizer and implicit-factor approach, motivated by SURGE's
62
+ generation methods; it does not add a canonical-parent assignment enumerator.
63
+
64
+ Optional `synkit_canon_pruning_statistics` diagnostics report twin factors,
65
+ retained generators, skipped branches and stabilizer attempts/fallbacks. The
66
+ Python adapter also accepts earlier ABI-1 libraries lacking this additive
67
+ accessor. Independent C++ group-oracle checks can be enabled with
68
+ `-DSYNKIT_CANON_BUILD_TESTS=ON` and run with `ctest --test-dir BUILD -C Release`.
69
+
70
+ The sparse leaf record contains every vertex color and every undirected edge,
71
+ so equal records lift to an actual color-preserving graph isomorphism. All
72
+ automorphisms used for general orbit pruning are replayed against the input
73
+ colors and edges. Generators are internal pruning aids; the API does not claim
74
+ to return generators, full groups, or group orders.
75
+
76
+ `synkit.native-sparse-canonical-graph/1` is a **new canonical convention**.
77
+ It does not promise byte equality with the reference JSON or Mapper ITS
78
+ certificates. Native stereo keys have the separate namespace
79
+ `synkit.native-sparse-stereograph/1` and include the declared prepared identity
80
+ profile. Comparison methods reject cross-convention and cross-profile uses.
81
+ Within one convention, equality compares full bytes, never digests alone.
82
+
83
+ Graph keys contain the ASCII schema, a zero delimiter, a little-endian uint64
84
+ palette byte length, the exact JSON typed palette, then the native graph
85
+ record. The latter is `SG01`, little-endian uint32 vertex/edge counts, vertex
86
+ color ranks in canonical order, and lexicographically sorted uint32 endpoint
87
+ pairs. Stereo keys prepend their schema and a length-delimited UTF-8 profile.
88
+ Isolated vertices and exact palette tokens are retained. This format uses
89
+ linear space in graph incidence and palette payload, rather than storing
90
+ quadratically many nonedges.
91
+
92
+ ## Scope, resource limits and ownership
93
+
94
+ - The graph ABI accepts simple undirected, loop-free, vertex-colored CSR.
95
+ Explicit edge-color selectors, directed graphs and multigraphs are refused.
96
+ Encode typed edge attributes as incidence vertices first. The stereo
97
+ expansion already preserves its selected bond attributes that way.
98
+ - Every result is complete or carries a stop reason without certificate bytes
99
+ or canonical order. Comparing incomplete results raises instead of deciding
100
+ graph inequality. Both mirror legs must complete before a binary verdict.
101
+ - Python preparation/expansion uses cooperative deadline checks; it is not a
102
+ hard wall-time sandbox. The native loop checks cancellation/deadlines during
103
+ validation, refinement, branching and output. Node and depth limits are
104
+ explicit; the implementation currently accepts depth limits up to 1024.
105
+ - `max_workspace_bytes` caps allocations routed through the core's tracked
106
+ C++ memory resource, including canonical output buffers. It does not bound
107
+ stack storage, Python allocations, allocator overhead or process RSS.
108
+ `max_output_bytes` counts full key bytes and the returned integer order.
109
+ Mirror output/node/workspace caps apply per leg; its deadline is shared.
110
+ - C requests borrow their arrays until return. Opaque results are freed by
111
+ `synkit_canon_free`; Python copies outputs before freeing them. Exceptions
112
+ never cross the C ABI. Cancellation tokens cannot be closed while borrowed.
113
+ - No build runs at import, no hidden backend fallback occurs, and no original
114
+ certificate namespace is reassigned. Build manifests retain source hashes,
115
+ compiler/environment information and the compiled library hash.
116
+
117
+ The first scientific pilot is in
118
+ `Experiment/Stereo/Canonicalization/native_pilot.py`. It compares complete
119
+ assignment partitions with the independent oracle and reference engine,
120
+ checks mirror decisions and relabeling, and keeps all correctness work outside
121
+ production timers. Its timings change language, convention, serialization and
122
+ symmetry-output workload together; they are development measurements, not an
123
+ isolated C++ speedup or a chemical benchmark.
@@ -0,0 +1,16 @@
1
+ #include "synkit_canon/canon.h"
2
+ #include <iostream>
3
+ int main() {
4
+ const int64_t offsets[] = {0, 1, 3, 4};
5
+ const int32_t neighbors[] = {1, 0, 2, 1}, colors[] = {0, 0, 0};
6
+ SynkitCanonRequest request{1, sizeof(SynkitCanonRequest), 3, 4, offsets,
7
+ neighbors, colors, 2.0, 10000, 1048576, 1048576, 100, nullptr};
8
+ SynkitCanonResult *result = nullptr;
9
+ if (synkit_canon_search(&request, &result) != 0 || !result ||
10
+ !synkit_canon_statistics(result)->complete) return 1;
11
+ int64_t size = 0;
12
+ const auto *bytes = synkit_canon_bytes(result, &size);
13
+ if (!bytes || size != 40) { synkit_canon_free(result); return 2; }
14
+ std::cout << "Complete canonical path certificate: " << size << " bytes\n";
15
+ synkit_canon_free(result);
16
+ }
@@ -0,0 +1,85 @@
1
+ #ifndef SYNKIT_CANON_H
2
+ #define SYNKIT_CANON_H
3
+
4
+ #include <stdint.h>
5
+ #if defined(_WIN32)
6
+ #if defined(SYNKIT_CANON_BUILD)
7
+ #define SYNKIT_CANON_API __declspec(dllexport)
8
+ #else
9
+ #define SYNKIT_CANON_API __declspec(dllimport)
10
+ #endif
11
+ #else
12
+ #define SYNKIT_CANON_API __attribute__((visibility("default")))
13
+ #endif
14
+ #ifdef __cplusplus
15
+ extern "C" {
16
+ #endif
17
+
18
+ /* ABI 1: simple undirected, loop-free, vertex-colored CSR graph. Rows must
19
+ * be sorted and symmetric. Buffers are borrowed for the duration of the call.
20
+ * Limits are positive or -1; seconds also accepts zero (immediate deadline).
21
+ * workspace_bytes bounds tracked native heap allocations, not process RSS.
22
+ * No automorphism group enumeration is requested or returned by this ABI.
23
+ */
24
+ typedef struct SynkitCanonRequest {
25
+ int32_t abi_version;
26
+ int32_t struct_size;
27
+ int64_t n;
28
+ int64_t arcs;
29
+ const int64_t *offsets; /* n+1 entries */
30
+ const int32_t *neighbors; /* arcs entries */
31
+ const int32_t *colors; /* n entries; nonnegative exact palette ranks */
32
+ double seconds;
33
+ int64_t max_nodes;
34
+ int64_t workspace_bytes;
35
+ int64_t output_bytes; /* packed graph bytes plus canonical order */
36
+ int32_t max_depth;
37
+ void *cancel_token;
38
+ } SynkitCanonRequest;
39
+
40
+ typedef struct SynkitCanonStatistics {
41
+ int32_t complete;
42
+ /* 0 complete, 1 deadline, 2 nodes, 3 workspace, 4 output, 5 cancel, 6 depth */
43
+ int32_t reason;
44
+ int64_t nodes;
45
+ int64_t leaves;
46
+ int64_t refinement_rounds;
47
+ int64_t peak_workspace_bytes;
48
+ double elapsed_seconds;
49
+ double validation_seconds;
50
+ double search_seconds;
51
+ double serialization_seconds;
52
+ } SynkitCanonStatistics;
53
+
54
+ /* Additive diagnostics; retained subgroup data, never a full group order. */
55
+ typedef struct SynkitCanonPruningStatistics {
56
+ int64_t implicit_twin_factors;
57
+ int64_t twin_vertices;
58
+ int64_t automorphism_generators;
59
+ int64_t orbit_rebuilds;
60
+ int64_t symmetry_pruned_branches;
61
+ int64_t stabilizer_calls;
62
+ int64_t stabilizer_fallbacks;
63
+ } SynkitCanonPruningStatistics;
64
+
65
+ typedef struct SynkitCanonResult SynkitCanonResult;
66
+ SYNKIT_CANON_API int synkit_canon_abi(void);
67
+ SYNKIT_CANON_API const char *synkit_canon_error(void);
68
+ SYNKIT_CANON_API void *synkit_canon_cancel_create(void);
69
+ SYNKIT_CANON_API void synkit_canon_cancel_request(void *);
70
+ SYNKIT_CANON_API void synkit_canon_cancel_free(void *);
71
+ /* Returns 0 for complete or explicitly stopped work, -1 for malformed input
72
+ * or runtime error. On -1, *result is NULL. Always free a non-NULL result.
73
+ * Incomplete results expose neither a certificate nor an order.
74
+ */
75
+ SYNKIT_CANON_API int synkit_canon_search(const SynkitCanonRequest *, SynkitCanonResult **result);
76
+ SYNKIT_CANON_API const SynkitCanonStatistics *synkit_canon_statistics(const SynkitCanonResult *);
77
+ SYNKIT_CANON_API const SynkitCanonPruningStatistics *synkit_canon_pruning_statistics(const SynkitCanonResult *);
78
+ SYNKIT_CANON_API const uint8_t *synkit_canon_bytes(const SynkitCanonResult *, int64_t *size);
79
+ SYNKIT_CANON_API const int32_t *synkit_canon_order(const SynkitCanonResult *, int64_t *size);
80
+ SYNKIT_CANON_API void synkit_canon_free(SynkitCanonResult *);
81
+
82
+ #ifdef __cplusplus
83
+ }
84
+ #endif
85
+ #endif
@@ -0,0 +1,391 @@
1
+ #include "synkit_canon/canon.h"
2
+ #include "symmetry.h"
3
+ #include <algorithm>
4
+ #include <atomic>
5
+ #include <chrono>
6
+ #include <cmath>
7
+ #include <limits>
8
+ #include <map>
9
+ #include <memory>
10
+ #include <memory_resource>
11
+ #include <stdexcept>
12
+ #include <string>
13
+ #include <vector>
14
+
15
+ namespace {
16
+ using Clock = std::chrono::steady_clock;
17
+ using Vec = std::pmr::vector<int32_t>;
18
+ using Partition = std::pmr::vector<Vec>;
19
+ struct Stop { int reason; };
20
+ struct Cancel { std::atomic<bool> requested{false}; };
21
+ thread_local std::string error;
22
+
23
+ class Workspace : public std::pmr::memory_resource {
24
+ int64_t cap_, used_ = 0;
25
+ void *do_allocate(size_t bytes, size_t alignment) override {
26
+ if (bytes > static_cast<size_t>(std::numeric_limits<int64_t>::max() - used_) ||
27
+ (cap_ >= 0 && bytes > static_cast<size_t>(cap_ - used_))) throw Stop{3};
28
+ void *p = std::pmr::new_delete_resource()->allocate(bytes, alignment);
29
+ used_ += static_cast<int64_t>(bytes);
30
+ peak = std::max(peak, used_);
31
+ return p;
32
+ }
33
+ void do_deallocate(void *p, size_t bytes, size_t alignment) override {
34
+ used_ -= static_cast<int64_t>(bytes);
35
+ std::pmr::new_delete_resource()->deallocate(p, bytes, alignment);
36
+ }
37
+ bool do_is_equal(const std::pmr::memory_resource &other) const noexcept override {
38
+ return this == &other;
39
+ }
40
+ public:
41
+ int64_t peak = 0;
42
+ explicit Workspace(int64_t cap) : cap_(cap) {}
43
+ };
44
+ } // namespace
45
+
46
+ struct SynkitCanonResult {
47
+ Workspace workspace;
48
+ std::pmr::vector<uint8_t> bytes;
49
+ Vec order;
50
+ SynkitCanonStatistics statistics{};
51
+ SynkitCanonPruningStatistics pruning{};
52
+ explicit SynkitCanonResult(int64_t cap) : workspace(cap), bytes(&workspace), order(&workspace) {}
53
+ };
54
+
55
+ namespace {
56
+ class Search {
57
+ const SynkitCanonRequest &r;
58
+ SynkitCanonResult &out;
59
+ std::pmr::memory_resource *mem;
60
+ Clock::time_point start;
61
+ Vec best_key;
62
+ Partition generators;
63
+ Vec twin_class, path;
64
+ int n;
65
+
66
+ void check() const {
67
+ if (r.cancel_token && static_cast<Cancel *>(r.cancel_token)->requested.load(std::memory_order_relaxed))
68
+ throw Stop{5};
69
+ if (r.seconds >= 0 && std::chrono::duration<double>(Clock::now() - start).count() >= r.seconds)
70
+ throw Stop{1};
71
+ }
72
+ bool edge(int a, int b) const {
73
+ return std::binary_search(r.neighbors + r.offsets[a], r.neighbors + r.offsets[a + 1], b);
74
+ }
75
+ static int root(Vec &parent, int v) {
76
+ while (parent[v] != v) { parent[v] = parent[parent[v]]; v = parent[v]; }
77
+ return v;
78
+ }
79
+ static void unite(Vec &parent, int a, int b) {
80
+ a = root(parent, a); b = root(parent, b);
81
+ if (a != b) parent[b] = a;
82
+ }
83
+ void prepare_twins() {
84
+ twin_class.resize(n);
85
+ for (int v = 0; v < n; ++v) twin_class[v] = v;
86
+ std::pmr::map<Vec, int> open(mem), closed(mem);
87
+ for (int v = 0; v < n; ++v) {
88
+ check();
89
+ Vec neighbors(mem);
90
+ neighbors.push_back(r.colors[v]);
91
+ for (int64_t k = r.offsets[v]; k < r.offsets[v + 1]; ++k)
92
+ neighbors.push_back(r.neighbors[k]);
93
+ auto same_open = open.emplace(neighbors, v);
94
+ if (!same_open.second) unite(twin_class, v, same_open.first->second);
95
+ // Equal open neighborhoods characterize false twins; equal closed
96
+ // neighborhoods characterize true twins. Colors are part of both keys.
97
+ neighbors.insert(std::lower_bound(neighbors.begin() + 1, neighbors.end(), v), v);
98
+ auto same_closed = closed.emplace(std::move(neighbors), v);
99
+ if (!same_closed.second) unite(twin_class, v, same_closed.first->second);
100
+ }
101
+ Vec sizes(n, 0, mem);
102
+ for (int v = 0; v < n; ++v) { twin_class[v] = root(twin_class, v); ++sizes[twin_class[v]]; }
103
+ for (int size : sizes) if (size > 1) {
104
+ ++out.pruning.implicit_twin_factors;
105
+ out.pruning.twin_vertices += size;
106
+ }
107
+ }
108
+ Partition refine(Partition current) {
109
+ while (true) {
110
+ check();
111
+ ++out.statistics.refinement_rounds;
112
+ Vec cells(n, 0, mem);
113
+ for (size_t c = 0; c < current.size(); ++c)
114
+ for (int v : current[c]) cells[v] = static_cast<int>(c);
115
+ Partition next(mem);
116
+ for (const auto &cell : current) {
117
+ std::pmr::map<Vec, Vec> buckets(mem);
118
+ for (int v : cell) {
119
+ check();
120
+ Vec signature(mem);
121
+ for (int64_t i = r.offsets[v]; i < r.offsets[v + 1]; ++i)
122
+ signature.push_back(cells[r.neighbors[i]]);
123
+ std::sort(signature.begin(), signature.end());
124
+ auto entry = buckets.try_emplace(std::move(signature));
125
+ entry.first->second.push_back(v);
126
+ }
127
+ for (auto &entry : buckets) next.push_back(std::move(entry.second));
128
+ }
129
+ if (next.size() == current.size()) return next;
130
+ current = std::move(next);
131
+ }
132
+ }
133
+ Vec key(const Vec &order) {
134
+ Vec positions(n, 0, mem), value(mem);
135
+ for (int i = 0; i < n; ++i) positions[order[i]] = i;
136
+ value.reserve(static_cast<size_t>(n) + static_cast<size_t>(r.arcs));
137
+ for (int v : order) value.push_back(r.colors[v]);
138
+ for (int i = 0; i < n; ++i) {
139
+ check();
140
+ Vec row(mem);
141
+ for (int64_t k = r.offsets[order[i]]; k < r.offsets[order[i] + 1]; ++k)
142
+ if (positions[r.neighbors[k]] > i) row.push_back(positions[r.neighbors[k]]);
143
+ std::sort(row.begin(), row.end());
144
+ for (int j : row) { value.push_back(i); value.push_back(j); }
145
+ }
146
+ return value;
147
+ }
148
+ void leaf(const Partition &p) {
149
+ ++out.statistics.leaves;
150
+ Vec order(mem);
151
+ for (const auto &cell : p) order.insert(order.end(), cell.begin(), cell.end());
152
+ Vec candidate = key(order);
153
+ if (out.order.empty() || candidate < best_key) {
154
+ best_key = std::move(candidate);
155
+ out.order = order;
156
+ } else if (candidate == best_key && generators.size() < 256) {
157
+ Vec map(n, 0, mem);
158
+ for (int i = 0; i < n; ++i) map[out.order[i]] = order[i];
159
+ // A full equal colored-graph record implies a permutation automorphism.
160
+ // Verify the projected generator before using it to prune another branch.
161
+ for (int v = 0; v < n; ++v) {
162
+ check();
163
+ if (r.colors[v] != r.colors[map[v]]) throw std::logic_error("Invalid color automorphism");
164
+ for (int64_t k = r.offsets[v]; k < r.offsets[v + 1]; ++k)
165
+ if (!edge(map[v], map[r.neighbors[k]])) throw std::logic_error("Invalid edge automorphism");
166
+ }
167
+ bool identity = true;
168
+ for (int v = 0; v < n; ++v) identity = identity && map[v] == v;
169
+ if (!identity && std::find(generators.begin(), generators.end(), map) == generators.end()) {
170
+ generators.push_back(std::move(map));
171
+ out.pruning.automorphism_generators = static_cast<int64_t>(generators.size());
172
+ }
173
+ }
174
+ }
175
+ bool stabilizes(const Vec &g, const Vec &cells) const {
176
+ for (int v = 0; v < n; ++v) if (cells[v] != cells[g[v]]) return false;
177
+ return true;
178
+ }
179
+ Partition filtered_generators(const Vec &cells) {
180
+ Partition result(mem);
181
+ for (const auto &g : generators) {
182
+ check();
183
+ if (stabilizes(g, cells)) result.push_back(g);
184
+ }
185
+ return result;
186
+ }
187
+ Partition stable_generators(const Vec &cells) {
188
+ // Filtering generators individually can miss stabilizing compositions.
189
+ // Retain this safe subgroup as a fallback for bounded Schreier discovery.
190
+ Partition fallback = filtered_generators(cells);
191
+ if (path.empty() || fallback.size() == generators.size()) return fallback;
192
+ ++out.pruning.stabilizer_calls;
193
+ synkit_canon::StabilizerWork work;
194
+ Partition group(generators, mem);
195
+ for (int point : path) {
196
+ check();
197
+ Partition next(mem);
198
+ if (!synkit_canon::point_stabilizer(group, point, n, next, work, [&]() { check(); })) {
199
+ ++out.pruning.stabilizer_fallbacks;
200
+ return fallback;
201
+ }
202
+ group = std::move(next);
203
+ if (group.empty()) break;
204
+ }
205
+ Partition usable(mem);
206
+ for (auto &g : group) if (stabilizes(g, cells)) usable.push_back(std::move(g));
207
+ return usable;
208
+ }
209
+ Vec orbits(const Partition &p, const Vec &cells) {
210
+ ++out.pruning.orbit_rebuilds;
211
+ Vec parent(n, 0, mem), owner(n, -1, mem), first(n, -1, mem);
212
+ for (int v = 0; v < n; ++v) parent[v] = v;
213
+ // Handle whole symmetric twin factors without generating their factorial
214
+ // groups. Only same-cell transpositions preserve this search partition.
215
+ for (size_t c = 0; c < p.size(); ++c) for (int v : p[c]) {
216
+ const int block = twin_class[v];
217
+ if (owner[block] != static_cast<int>(c)) { owner[block] = static_cast<int>(c); first[block] = v; }
218
+ else unite(parent, first[block], v);
219
+ }
220
+ for (const auto &g : filtered_generators(cells)) {
221
+ check();
222
+ for (int v = 0; v < n; ++v) unite(parent, v, g[v]);
223
+ }
224
+ for (int v = 0; v < n; ++v) parent[v] = root(parent, v);
225
+ return parent;
226
+ }
227
+ void visit(Partition p, int depth) {
228
+ check();
229
+ if (r.max_nodes >= 0 && out.statistics.nodes >= r.max_nodes) throw Stop{2};
230
+ if (depth > r.max_depth) throw Stop{6};
231
+ ++out.statistics.nodes;
232
+ p = refine(std::move(p));
233
+ size_t target = p.size();
234
+ for (size_t i = 0; i < p.size(); ++i) if (p[i].size() > 1) {
235
+ if (target == p.size() || p[i].size() < p[target].size()) target = i;
236
+ }
237
+ // A discrete root has no nontrivial automorphisms and needs no twin or
238
+ // subgroup setup. This is common for fully distinguished stereo inputs.
239
+ if (target == p.size()) { leaf(p); return; }
240
+ if (twin_class.empty()) prepare_twins();
241
+ bool all_twins = true;
242
+ for (size_t i = 0; i < p.size(); ++i) if (p[i].size() > 1) {
243
+ for (int v : p[i]) { check(); if (twin_class[p[i][0]] != twin_class[v]) all_twins = false; }
244
+ }
245
+ // Within a twin cell every ordering gives the same graph; no branching is needed.
246
+ if (all_twins) { leaf(p); return; }
247
+ Vec explored(mem), cells(n, 0, mem);
248
+ for (size_t i = 0; i < p.size(); ++i)
249
+ for (int v : p[i]) cells[v] = static_cast<int>(i);
250
+ Vec orbit(mem);
251
+ size_t generation = std::numeric_limits<size_t>::max();
252
+ bool strengthened = false;
253
+ for (int v : p[target]) {
254
+ check();
255
+ if (generation != generators.size()) {
256
+ orbit = orbits(p, cells); generation = generators.size(); strengthened = false;
257
+ }
258
+ auto already_explored = [&]() {
259
+ for (int previous : explored) if (orbit[v] == orbit[previous]) return true;
260
+ return false;
261
+ };
262
+ bool seen = already_explored();
263
+ // First try inexpensive twin/direct-generator orbits. Deriving a point
264
+ // stabilizer cannot skip the first branch or improve an already skipped
265
+ // branch, so perform it only on demand, once per generator generation.
266
+ if (!seen && !explored.empty() && !strengthened) {
267
+ for (const auto &g : stable_generators(cells)) {
268
+ check();
269
+ for (int w = 0; w < n; ++w) unite(orbit, w, g[w]);
270
+ }
271
+ for (int w = 0; w < n; ++w) orbit[w] = root(orbit, w);
272
+ strengthened = true;
273
+ seen = already_explored();
274
+ }
275
+ if (seen) { ++out.pruning.symmetry_pruned_branches; continue; }
276
+ Partition child(mem);
277
+ for (size_t i = 0; i < p.size(); ++i) {
278
+ if (i != target) child.push_back(p[i]);
279
+ else {
280
+ child.emplace_back(); child.back().push_back(v);
281
+ child.emplace_back();
282
+ for (int w : p[i]) if (w != v) child.back().push_back(w);
283
+ }
284
+ }
285
+ path.push_back(v);
286
+ visit(std::move(child), depth + 1);
287
+ path.pop_back();
288
+ explored.push_back(v);
289
+ }
290
+ }
291
+ void append32(uint32_t value) {
292
+ for (int i = 0; i < 4; ++i) out.bytes.push_back(static_cast<uint8_t>(value >> (8 * i)));
293
+ }
294
+ public:
295
+ Search(const SynkitCanonRequest &request, SynkitCanonResult &result, Clock::time_point began)
296
+ : r(request), out(result), mem(&out.workspace), start(began), best_key(mem), generators(mem),
297
+ twin_class(mem), path(mem),
298
+ n(static_cast<int>(request.n)) {}
299
+ void run() {
300
+ check();
301
+ if (r.offsets[0] != 0 || r.offsets[n] != r.arcs) throw std::invalid_argument("Invalid CSR endpoints");
302
+ for (int v = 0; v < n; ++v) {
303
+ check();
304
+ if (r.colors[v] < 0 || r.offsets[v] < 0 || r.offsets[v] > r.offsets[v + 1] || r.offsets[v + 1] > r.arcs)
305
+ throw std::invalid_argument("Invalid CSR row or color");
306
+ }
307
+ for (int v = 0; v < n; ++v) for (int64_t k = r.offsets[v]; k < r.offsets[v + 1]; ++k) {
308
+ check();
309
+ int w = r.neighbors[k];
310
+ if (w < 0 || w >= n || w == v || (k > r.offsets[v] && r.neighbors[k - 1] >= w) || !edge(w, v))
311
+ throw std::invalid_argument("CSR must be simple, sorted, loop-free and symmetric");
312
+ }
313
+ const uint64_t output = 12ULL + 8ULL * static_cast<uint64_t>(n) + 4ULL * static_cast<uint64_t>(r.arcs);
314
+ if (r.output_bytes >= 0 && output > static_cast<uint64_t>(r.output_bytes)) throw Stop{4};
315
+ const auto search_start = Clock::now();
316
+ out.statistics.validation_seconds = std::chrono::duration<double>(search_start - start).count();
317
+ std::pmr::map<int32_t, Vec> buckets(mem);
318
+ for (int i = 0; i < n; ++i) { check(); buckets[r.colors[i]].push_back(i); }
319
+ Partition initial(mem);
320
+ for (auto &entry : buckets) initial.push_back(std::move(entry.second));
321
+ visit(std::move(initial), 0);
322
+ const auto serialization_start = Clock::now();
323
+ out.statistics.search_seconds = std::chrono::duration<double>(serialization_start - search_start).count();
324
+ // SG01, vertex count, undirected edge count, colors, sorted endpoint pairs.
325
+ out.bytes.reserve(static_cast<size_t>(output - 4ULL * n));
326
+ append32(0x31304753U); append32(static_cast<uint32_t>(n)); append32(static_cast<uint32_t>(r.arcs / 2));
327
+ for (size_t i = 0; i < best_key.size(); ++i) {
328
+ if ((i & 1023) == 0) check();
329
+ append32(static_cast<uint32_t>(best_key[i]));
330
+ }
331
+ check();
332
+ out.statistics.serialization_seconds = std::chrono::duration<double>(Clock::now() - serialization_start).count();
333
+ out.statistics.complete = 1;
334
+ }
335
+ };
336
+
337
+ void validate(const SynkitCanonRequest &r) {
338
+ if (r.abi_version != 1 || r.struct_size != sizeof(SynkitCanonRequest) ||
339
+ r.n < 0 || r.n > INT32_MAX || r.arcs < 0 || r.arcs > UINT32_MAX || (r.arcs & 1) ||
340
+ !r.offsets || (r.n && !r.colors) || (r.arcs && !r.neighbors) ||
341
+ !std::isfinite(r.seconds) || (r.seconds < 0 && r.seconds != -1) ||
342
+ r.max_depth < 0 || r.max_depth > 1024)
343
+ throw std::invalid_argument("Invalid canonical request ABI, graph dimensions or deadline");
344
+ for (auto cap : {r.max_nodes, r.workspace_bytes, r.output_bytes})
345
+ if (cap == 0 || cap < -1) throw std::invalid_argument("Limits must be positive or -1");
346
+ }
347
+ } // namespace
348
+
349
+ extern "C" int synkit_canon_abi() { return 1; }
350
+ extern "C" const char *synkit_canon_error() { return error.c_str(); }
351
+ extern "C" void *synkit_canon_cancel_create() {
352
+ try { return new Cancel; } catch (...) { return nullptr; }
353
+ }
354
+ extern "C" void synkit_canon_cancel_request(void *p) {
355
+ if (p) static_cast<Cancel *>(p)->requested.store(true, std::memory_order_relaxed);
356
+ }
357
+ extern "C" void synkit_canon_cancel_free(void *p) { delete static_cast<Cancel *>(p); }
358
+ extern "C" int synkit_canon_search(const SynkitCanonRequest *r, SynkitCanonResult **result) {
359
+ if (!result) return -1;
360
+ *result = nullptr;
361
+ error.clear();
362
+ try {
363
+ const auto start = Clock::now();
364
+ if (!r) throw std::invalid_argument("Missing request");
365
+ validate(*r);
366
+ auto out = std::make_unique<SynkitCanonResult>(r->workspace_bytes);
367
+ try { Search(*r, *out, start).run(); }
368
+ catch (const Stop &stop) { out->statistics.reason = stop.reason; out->bytes.clear(); out->order.clear(); }
369
+ catch (const std::bad_alloc &) { out->statistics.reason = 3; out->bytes.clear(); out->order.clear(); }
370
+ out->statistics.peak_workspace_bytes = out->workspace.peak;
371
+ out->statistics.elapsed_seconds = std::chrono::duration<double>(Clock::now() - start).count();
372
+ *result = out.release();
373
+ return 0;
374
+ } catch (const std::exception &exception) { error = exception.what(); return -1; }
375
+ catch (...) { error = "Unknown native canonicalization error"; return -1; }
376
+ }
377
+ extern "C" const SynkitCanonStatistics *synkit_canon_statistics(const SynkitCanonResult *r) {
378
+ return r ? &r->statistics : nullptr;
379
+ }
380
+ extern "C" const SynkitCanonPruningStatistics *synkit_canon_pruning_statistics(const SynkitCanonResult *r) {
381
+ return r ? &r->pruning : nullptr;
382
+ }
383
+ extern "C" const uint8_t *synkit_canon_bytes(const SynkitCanonResult *r, int64_t *size) {
384
+ if (size) *size = r && r->statistics.complete ? static_cast<int64_t>(r->bytes.size()) : 0;
385
+ return r && r->statistics.complete ? r->bytes.data() : nullptr;
386
+ }
387
+ extern "C" const int32_t *synkit_canon_order(const SynkitCanonResult *r, int64_t *size) {
388
+ if (size) *size = r && r->statistics.complete ? static_cast<int64_t>(r->order.size()) : 0;
389
+ return r && r->statistics.complete ? r->order.data() : nullptr;
390
+ }
391
+ extern "C" void synkit_canon_free(SynkitCanonResult *r) { delete r; }