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,506 @@
1
+ """Exact heavy-atom classes scored by minimum implicit-H Lewis completion.
2
+
3
+ This additive interface never expands hydrogen or enumerates H identities. A
4
+ numeric shell selects heavy maps whose *minimum* H-completion score equals CD;
5
+ it is distinct from projecting an arbitrary full-H exact shell onto heavy atoms.
6
+ Literal H atoms remain in rendered endpoints, with no identity map numbers.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import math
13
+ import re
14
+ import time
15
+ from collections import Counter
16
+ from dataclasses import dataclass
17
+ from numbers import Integral, Real
18
+
19
+ import numpy as np
20
+ from rdkit import Chem
21
+
22
+ from synkit.IO.mol_to_graph import MolToGraph
23
+
24
+ from ..exact.distance_search.records import (
25
+ DistanceEnumerationResult,
26
+ normalize_distance_target,
27
+ )
28
+ from ...reaction_search import ReactionMapping, ReactionMappingResult
29
+ from ..slap.lap import _adjacency_and_elements
30
+ from .classification import NativeITSBatchClassifier
31
+ from .core import enumerate_core_mappings
32
+ from .implicit_preparation import (
33
+ UnsupportedImplicitHydrogen,
34
+ prepare_implicit_problem,
35
+ )
36
+ from .reaction_text import split_reaction_fields
37
+ from .runtime import resolve_library_path
38
+ from .stereo import assign_stereo, stereo_smiles
39
+
40
+ SCOPE = "heavyclasses_minimum_H_completion_score"
41
+ _PROPERTIES = ("charge", "radical", "hcount", "stereo", "dummy")
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ImplicitReactionMapping(ReactionMapping):
46
+ """One heavy ITS class, with its actual native equality certificate."""
47
+
48
+ canonical_key_base64: str
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class PreparedImplicitReaction:
53
+ """Heavy graphs, original molecules and a fixed doubled-distance offset.
54
+
55
+ Graph order is real heavy atoms, typed heavy dummies, electron anchor, H
56
+ count anchor. ``heavy_indices`` addresses the unexpanded endpoint molecules.
57
+ Anchor vertices participate in scoring and certificates, never in SMILES.
58
+ """
59
+
60
+ mols: tuple
61
+ graphs: tuple
62
+ heavy_indices: tuple[tuple[int, ...], tuple[int, ...]]
63
+ offset2: int
64
+ metadata: dict
65
+ edge_stereo: tuple
66
+ matrices2: tuple
67
+ agents: str
68
+ reaction: str
69
+
70
+ def classifier(self, library_path, *, identifier_mode="legacy"):
71
+ return make_implicit_classifier(
72
+ self, library_path=library_path, identifier_mode=identifier_mode
73
+ )
74
+
75
+
76
+ def prepare_implicit_reaction(reaction, *, balance="dummy"):
77
+ """Prepare the unexpanded model, discarding all input identity map tags.
78
+
79
+ Unsupported H chemistry raises ``UnsupportedImplicitHydrogen``; there is no
80
+ hidden explicit-H fallback. Charge, radicals, total H counts, map-independent
81
+ CIP labels and heavy bond E/Z remain exact class attributes.
82
+ """
83
+ if not isinstance(reaction, str):
84
+ raise TypeError("reaction must be reaction SMILES")
85
+ parts = split_reaction_fields(
86
+ reaction,
87
+ error_message="Expected reactants>agents>products or reactants>>products",
88
+ )
89
+ if not parts[0] or not parts[2]:
90
+ raise ValueError("Expected reactants>agents>products or reactants>>products")
91
+ params = Chem.SmilesParserParams()
92
+ params.removeHs = False
93
+ cleaned = []
94
+ for side in (parts[0], parts[2]):
95
+ mol = Chem.MolFromSmiles(side, params)
96
+ if mol is None:
97
+ raise ValueError("Could not parse reaction endpoint")
98
+ for atom in mol.GetAtoms():
99
+ atom.SetAtomMapNum(0)
100
+ # Remove only the parsed atom-map field in-place. A SMILES writer can
101
+ # reorder branches even with canonical=False; retaining the input
102
+ # traversal keeps reference heavy indices aligned with this model.
103
+ cleaned.append(re.sub(r":\d+(?=\])", "", side))
104
+ mapless = f"{cleaned[0]}>{parts[1]}>{cleaned[1]}"
105
+ graphs, offset2, metadata = prepare_implicit_problem(mapless, balance=balance)
106
+ mols = tuple(Chem.MolFromSmiles(side, params) for side in cleaned)
107
+ heavy_indices = tuple(
108
+ tuple(metadata[f"heavy_original_indices_{side}"]) for side in ("r", "p")
109
+ )
110
+ n = len(graphs[0].labels)
111
+ stereo_matrices = []
112
+ for mol, graph, indices in zip(mols, graphs, heavy_indices):
113
+ assign_stereo(mol)
114
+ atoms = [mol.GetAtomWithIdx(i) for i in indices]
115
+ pad = n - len(atoms)
116
+ graph.props.update(
117
+ charge=[a.GetFormalCharge() for a in atoms] + [0] * pad,
118
+ radical=[a.GetNumRadicalElectrons() for a in atoms] + [0] * pad,
119
+ hcount=[MolToGraph._total_h_count(a) for a in atoms] + [0] * pad,
120
+ stereo=[
121
+ a.GetProp("_CIPCode") if a.HasProp("_CIPCode") else "" for a in atoms
122
+ ]
123
+ + [""] * pad,
124
+ dummy=[False] * len(atoms) + [True] * pad,
125
+ _stereo_anchor=[0] * n,
126
+ )
127
+ positions = {original: i for i, original in enumerate(indices)}
128
+ edges = np.full((n, n), "", dtype=object)
129
+ for bond in mol.GetBonds():
130
+ left, right = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
131
+ if left not in positions or right not in positions:
132
+ continue
133
+ i, j = positions[left], positions[right]
134
+ edges[i, j] = edges[j, i] = str(bond.GetStereo())
135
+ if bond.GetStereo() != Chem.BondStereo.STEREONONE:
136
+ graph.props["_stereo_anchor"][i] = i + 1
137
+ graph.props["_stereo_anchor"][j] = j + 1
138
+ stereo_matrices.append(edges)
139
+ colors = {"": 0}
140
+ for graph, edges in zip(graphs, stereo_matrices):
141
+ graph.props["_native_edge_colors"] = np.asarray(
142
+ [[colors.setdefault(value, len(colors)) for value in row] for row in edges],
143
+ dtype=np.int32,
144
+ )
145
+ matrices2 = tuple(
146
+ np.rint(_adjacency_and_elements(g, False)[0] * 2).astype(np.int64)
147
+ for g in graphs
148
+ )
149
+ return PreparedImplicitReaction(
150
+ mols,
151
+ graphs,
152
+ heavy_indices,
153
+ offset2,
154
+ {
155
+ **metadata,
156
+ "scope": SCOPE,
157
+ "class_scope": SCOPE,
158
+ "stereo_assignment": "modern_CIP_v1; supplied_geometry_preserved",
159
+ },
160
+ tuple(stereo_matrices),
161
+ matrices2,
162
+ parts[1],
163
+ mapless,
164
+ )
165
+
166
+
167
+ def make_implicit_classifier(prepared, *, library_path, identifier_mode="legacy"):
168
+ """Construct the exact heavy ITS palette used by returned certificates."""
169
+ a, elements = _adjacency_and_elements(prepared.graphs[0], False)
170
+ b, _ = _adjacency_and_elements(prepared.graphs[1], False)
171
+ return NativeITSBatchClassifier(
172
+ a,
173
+ b,
174
+ elements,
175
+ {name: tuple(g.props[name] for g in prepared.graphs) for name in _PROPERTIES},
176
+ library_path=library_path,
177
+ edge_properties={"stereo": prepared.edge_stereo},
178
+ identifier_mode=identifier_mode,
179
+ )
180
+
181
+
182
+ def score_implicit_mapping2(prepared, mapping, *, include_offset=True):
183
+ """Independently rescore a complete typed map, returning doubled CD."""
184
+ n = len(prepared.graphs[0].labels)
185
+ mapping = tuple(mapping)
186
+ if (
187
+ len(mapping) != n
188
+ or any(isinstance(j, bool) or not isinstance(j, Integral) for j in mapping)
189
+ or sorted(mapping) != list(range(n))
190
+ ):
191
+ raise ValueError("Expected a complete integer bijection")
192
+ if any(
193
+ prepared.graphs[0].labels[i] != prepared.graphs[1].labels[j]
194
+ for i, j in enumerate(mapping)
195
+ ):
196
+ raise ValueError("Mapping violates atom types")
197
+ a, b = prepared.matrices2
198
+ cost2 = int(np.abs(a - b[np.ix_(mapping, mapping)]).sum() // 2)
199
+ return cost2 + (prepared.offset2 if include_offset else 0)
200
+
201
+
202
+ def _render_reactant(prepared, *, canonical_output=True):
203
+ mol = Chem.Mol(prepared.mols[0])
204
+ for i, original in enumerate(prepared.heavy_indices[0]):
205
+ mol.GetAtomWithIdx(original).SetAtomMapNum(i + 1)
206
+ return stereo_smiles(mol, canonical=canonical_output)
207
+
208
+
209
+ def _render(prepared, mapping, *, canonical_output=True, reactant_text=None):
210
+ product = Chem.Mol(prepared.mols[1])
211
+ left, right = prepared.heavy_indices
212
+ inverse = {j: i for i, j in enumerate(mapping)}
213
+ for j, original in enumerate(right):
214
+ product.GetAtomWithIdx(original).SetAtomMapNum(inverse[j] + 1)
215
+ reactant_only = tuple(i + 1 for i in range(len(left)) if mapping[i] >= len(right))
216
+ product_only = tuple(
217
+ inverse[j] + 1 for j in range(len(right)) if inverse[j] >= len(left)
218
+ )
219
+ if reactant_text is None:
220
+ reactant_text = _render_reactant(prepared, canonical_output=canonical_output)
221
+ product_text = stereo_smiles(product, canonical=canonical_output)
222
+ return (
223
+ f"{reactant_text}>{prepared.agents}>{product_text}",
224
+ reactant_only,
225
+ product_only,
226
+ )
227
+
228
+
229
+ def _empty_search(target, backend, reason=None, *, complete=False, unsupported=False):
230
+ return DistanceEnumerationResult(
231
+ target=target,
232
+ cost=None,
233
+ minimum_cost=None,
234
+ mappings=[],
235
+ distances=[],
236
+ total_bijections=0,
237
+ maximum_cost_upper_bound=0,
238
+ visited_leaves=0,
239
+ pruned_branches=0,
240
+ elapsed_seconds=0,
241
+ status="no_solutions"
242
+ if complete
243
+ else "unsupported"
244
+ if unsupported
245
+ else "timeout",
246
+ complete=complete,
247
+ truncation_reason=reason,
248
+ backend=f"{backend}_cpp",
249
+ backend_statistics={
250
+ "minimum_proven": 0,
251
+ "minimum2": -1,
252
+ "incumbent2": -1,
253
+ "lower_bound2": -1,
254
+ "search_complete": int(complete),
255
+ "classification_complete": int(complete),
256
+ "candidate_count": 0,
257
+ "class_count": 0,
258
+ "backend_executed": False,
259
+ },
260
+ )
261
+
262
+
263
+ # Preparation, native search and class rendering consume one shared deadline.
264
+ def map_reaction_implicit( # noqa: C901
265
+ reaction,
266
+ *,
267
+ library_path=None,
268
+ backend="pabs",
269
+ CD="minimal",
270
+ CD_mode="exact",
271
+ balance="dummy",
272
+ time_limit_seconds=None,
273
+ max_mappings=10000,
274
+ max_states=None,
275
+ max_classes=None,
276
+ max_frontier=None,
277
+ memory_limit_bytes=None,
278
+ classification_max_search_nodes=100000,
279
+ symmetry=True,
280
+ proof_only=False,
281
+ cancel_token=None,
282
+ identifier_mode="legacy",
283
+ canonical_output=True,
284
+ ):
285
+ """Enumerate exact heavy classes with minimum implicit-H Lewis scores.
286
+
287
+ Preparation, native search, classification and rendering share one deadline.
288
+ ``CD='minimal'`` proves the minimum and enumerates its heavy classes unless
289
+ ``proof_only=True``. Numeric exact/at-most queries do no extra minimum solve.
290
+ Unsupported H models return an explicit unsupported result, with no H lift.
291
+ ``canonical_key_base64`` is the actual native packed equality key; class IDs
292
+ are presentation identifiers and are never used for deduplication.
293
+ """
294
+ started = time.perf_counter()
295
+ if not isinstance(canonical_output, bool):
296
+ raise TypeError("canonical_output must be boolean")
297
+ if identifier_mode not in {"legacy", "packed"}:
298
+ raise ValueError("identifier_mode must be 'legacy' or 'packed'")
299
+ target = normalize_distance_target(CD)
300
+ if CD_mode not in {"exact", "at_most"} or (
301
+ CD_mode == "at_most" and target == "minimal"
302
+ ):
303
+ raise ValueError("at_most requires numeric CD; otherwise use exact")
304
+ if not isinstance(proof_only, bool) or not isinstance(symmetry, bool):
305
+ raise TypeError("proof_only and symmetry must be boolean")
306
+ if proof_only and target != "minimal":
307
+ raise ValueError("proof_only requires minimal CD")
308
+ if time_limit_seconds is not None and (
309
+ isinstance(time_limit_seconds, bool)
310
+ or not isinstance(time_limit_seconds, Real)
311
+ or not math.isfinite(time_limit_seconds)
312
+ or time_limit_seconds < 0
313
+ ):
314
+ raise ValueError("time_limit_seconds must be finite and nonnegative")
315
+ if classification_max_search_nodes is not None and (
316
+ isinstance(classification_max_search_nodes, bool)
317
+ or not isinstance(classification_max_search_nodes, Integral)
318
+ or classification_max_search_nodes < 1
319
+ ):
320
+ raise ValueError("classification_max_search_nodes must be positive or None")
321
+ library_path = resolve_library_path(library_path)
322
+ deadline = None if time_limit_seconds is None else started + time_limit_seconds
323
+
324
+ def remaining():
325
+ return None if deadline is None else max(0, deadline - time.perf_counter())
326
+
327
+ records, registry = {}, {}
328
+ reactant_text = None
329
+ rendering_complete = True
330
+ preparation_reason = None
331
+ prepared = None
332
+ try:
333
+ prepared = prepare_implicit_reaction(reaction, balance=balance)
334
+ except UnsupportedImplicitHydrogen as error:
335
+ preparation_reason = f"implicit_H_unsupported:{error.reason}"
336
+ if prepared is None:
337
+ search = _empty_search(target, backend, preparation_reason, unsupported=True)
338
+ else:
339
+ reduced_target = (
340
+ target if target == "minimal" else target - prepared.offset2 / 2
341
+ )
342
+ if reduced_target != "minimal" and reduced_target < 0:
343
+ search = _empty_search(target, backend, complete=True)
344
+ elif remaining() == 0:
345
+ search = _empty_search(target, backend, "time_limit")
346
+ else:
347
+ classifier = None
348
+ if not proof_only:
349
+ classifier = make_implicit_classifier(
350
+ prepared, library_path=library_path, identifier_mode=identifier_mode
351
+ )
352
+ classifier.frontend_max_canonical_nodes = (
353
+ classification_max_search_nodes
354
+ )
355
+
356
+ def receive(mapping, cost):
357
+ nonlocal rendering_complete, reactant_text
358
+ record = registry.pop(tuple(mapping), None)
359
+ if record is None:
360
+ raise RuntimeError(
361
+ "Native callback lacks its exact class certificate"
362
+ )
363
+ if (
364
+ score_implicit_mapping2(prepared, mapping, include_offset=False)
365
+ != record.cost2
366
+ or abs(cost * 2 - record.cost2) > 1e-9
367
+ ):
368
+ raise RuntimeError(
369
+ "Native implicit mapping failed independent rescoring"
370
+ )
371
+ if record.key.parts in records:
372
+ return
373
+ if deadline is not None and time.perf_counter() >= deadline:
374
+ rendering_complete = False
375
+ return
376
+ if reactant_text is None:
377
+ reactant_text = _render_reactant(
378
+ prepared, canonical_output=canonical_output
379
+ )
380
+ rendered, left_only, right_only = _render(
381
+ prepared,
382
+ mapping,
383
+ canonical_output=canonical_output,
384
+ reactant_text=reactant_text,
385
+ )
386
+ records[record.key.parts] = ImplicitReactionMapping(
387
+ rendered,
388
+ tuple(mapping),
389
+ record.identifier,
390
+ (record.cost2 + prepared.offset2) / 2,
391
+ None,
392
+ left_only,
393
+ right_only,
394
+ base64.b64encode(record.key.parts).decode("ascii"),
395
+ )
396
+
397
+ search = enumerate_core_mappings(
398
+ prepared.graphs,
399
+ library_path=library_path,
400
+ backend=backend,
401
+ CD=reduced_target,
402
+ CD_mode=CD_mode,
403
+ symmetry=symmetry,
404
+ node_properties=(*_PROPERTIES, "_stereo_anchor"),
405
+ time_limit_seconds=remaining(),
406
+ max_states=max_states,
407
+ max_mappings=max_mappings,
408
+ max_classes=max_classes,
409
+ max_frontier=max_frontier,
410
+ memory_limit_bytes=memory_limit_bytes,
411
+ collect_mappings=False,
412
+ mapping_callback=None if proof_only else receive,
413
+ proof_only=proof_only,
414
+ cancel_token=cancel_token,
415
+ _native_classifier=classifier,
416
+ _native_class_registry=None if proof_only else registry,
417
+ )
418
+ if not search.backend_statistics.get("backend_executed", False):
419
+ search.total_bijections = math.prod(
420
+ math.factorial(count)
421
+ for count in Counter(prepared.graphs[0].labels).values()
422
+ )
423
+ search.maximum_cost_upper_bound = (
424
+ sum(int(matrix.sum()) for matrix in prepared.matrices2) / 4
425
+ )
426
+ search.target = target
427
+ for field in ("cost", "minimum_cost", "maximum_cost_upper_bound"):
428
+ value = getattr(search, field)
429
+ if value is not None:
430
+ setattr(search, field, value + prepared.offset2 / 2)
431
+ details = search.backend_statistics
432
+ details.update(
433
+ scope=SCOPE,
434
+ class_scope=SCOPE,
435
+ representation="implicit_H_heavy_graph_with_two_score_anchors",
436
+ hydrogen_nodes_added=0,
437
+ hydrogen_vertices_searched=0,
438
+ hydrogen_nodes_enumerated=0,
439
+ hydrogen_enumeration_requested=False,
440
+ H_identity_scope="marginalized; literal H rendered without map tags",
441
+ enumeration_requested=not proof_only,
442
+ CD_mode=CD_mode,
443
+ class_equality="exact_native_packed_key",
444
+ identifier_mode=identifier_mode,
445
+ canonical_output=canonical_output,
446
+ delivered_heavy_classes=len(records),
447
+ rendering_complete=rendering_complete,
448
+ preparation_unsupported_reason=preparation_reason,
449
+ )
450
+ if prepared is not None:
451
+ details["implicit_preparation"] = prepared.metadata
452
+ details["offset2"] = prepared.offset2
453
+ for field in ("minimum2", "incumbent2", "lower_bound2"):
454
+ if field in details:
455
+ details[f"reduced_{field}"] = details[field]
456
+ if details[field] >= 0:
457
+ details[field] += prepared.offset2
458
+ classification_complete = (
459
+ bool(details.get("classification_complete")) and rendering_complete
460
+ )
461
+ failure = preparation_reason
462
+ if not proof_only and not classification_complete and failure is None:
463
+ failure = "classification:" + (
464
+ details.get("classification_reason")
465
+ or ("rendering_time_limit" if not rendering_complete else None)
466
+ or search.truncation_reason
467
+ or "incomplete"
468
+ )
469
+ count = search.selected_labeled_mapping_count
470
+ return ReactionMappingResult(
471
+ reactions=tuple(
472
+ sorted(records.values(), key=lambda item: item.canonical_key_base64)
473
+ ),
474
+ complete=search.complete and classification_complete and not proof_only,
475
+ search_complete=search.complete,
476
+ classification_complete=classification_complete,
477
+ hydrogen_complete=True,
478
+ minimum_cost=search.minimum_cost,
479
+ incomplete_reason=failure or search.truncation_reason,
480
+ backend=search.backend,
481
+ hydrogen="implicit_minimum_completion",
482
+ objective=SCOPE,
483
+ balance=balance,
484
+ agents=prepared.agents if prepared else split_reaction_fields(reaction)[1],
485
+ labeled_mapping_count=count,
486
+ labeled_mapping_count_scope=(
487
+ "not_computed_symmetry_pruned_search"
488
+ if count is None
489
+ else "padded_heavy_mapping"
490
+ ),
491
+ minimum_cost_scope=SCOPE,
492
+ elapsed_seconds=time.perf_counter() - started,
493
+ search_result=search,
494
+ )
495
+
496
+
497
+ __all__ = [
498
+ "SCOPE",
499
+ "ImplicitReactionMapping",
500
+ "PreparedImplicitReaction",
501
+ "UnsupportedImplicitHydrogen",
502
+ "make_implicit_classifier",
503
+ "map_reaction_implicit",
504
+ "prepare_implicit_reaction",
505
+ "score_implicit_mapping2",
506
+ ]
@@ -0,0 +1,187 @@
1
+ """One exact native certificate for a fixed implicit-H heavy mapping."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ctypes as ct
6
+ import json
7
+ import math
8
+ import time
9
+ from dataclasses import replace
10
+ from numbers import Real
11
+ from pathlib import Path
12
+
13
+ import numpy as np
14
+
15
+ from synkit.Graph.Canon.exact import _encode_color
16
+
17
+ from ...reporting.spectrum import _typed
18
+ from .classification import (
19
+ _INT,
20
+ _LONG,
21
+ NativeClassificationResult,
22
+ _cap,
23
+ _classification_result,
24
+ _load,
25
+ _Problem,
26
+ _records_callback,
27
+ _Statistics,
28
+ )
29
+ from .implicit import _PROPERTIES, score_implicit_mapping2
30
+
31
+
32
+ def classify_implicit_mapping(
33
+ prepared,
34
+ mapping,
35
+ *,
36
+ library_path,
37
+ identifier_mode="packed",
38
+ time_limit_seconds=None,
39
+ max_canonical_nodes=None,
40
+ cancel_token=None,
41
+ ):
42
+ """Canonicalize only the joint states used by this complete typed mapping.
43
+
44
+ The native canonicalizer and exact certificate bytes are the same as batch
45
+ classification. This prepares O(n + edges) joint color tokens instead of
46
+ every possible atom-state pairing. Dense ABI arrays remain O(n squared).
47
+ No reusable restricted palette or search domains are exposed to callers.
48
+ Preparation and canonicalization share the supplied deadline.
49
+ """
50
+ started = time.perf_counter()
51
+ if identifier_mode not in {"legacy", "packed"}:
52
+ raise ValueError("identifier_mode must be 'legacy' or 'packed'")
53
+ if time_limit_seconds is not None and (
54
+ isinstance(time_limit_seconds, bool)
55
+ or not isinstance(time_limit_seconds, Real)
56
+ or not math.isfinite(time_limit_seconds)
57
+ or time_limit_seconds < 0
58
+ ):
59
+ raise ValueError("time_limit_seconds must be finite and nonnegative")
60
+ node_cap = _cap(max_canonical_nodes, "max_canonical_nodes")
61
+ mapping = tuple(mapping)
62
+ # This independently checks integral IDs, bijectivity and all hard types.
63
+ cost2 = score_implicit_mapping2(prepared, mapping, include_offset=False)
64
+ a, b = prepared.graphs
65
+ n = len(mapping)
66
+ library = _load(str(Path(library_path).resolve()))
67
+ capacity = min(
68
+ 512,
69
+ library.synkit_mapper_classification_max_vertices()
70
+ if hasattr(library, "synkit_mapper_classification_max_vertices")
71
+ else 256,
72
+ )
73
+ if not 1 <= n <= capacity:
74
+ raise ValueError(f"Native ITS library supports 1..{capacity} vertices")
75
+ if identifier_mode == "packed" and not hasattr(
76
+ library, "synkit_mapper_classify_batch_ex"
77
+ ):
78
+ raise RuntimeError("Native library does not support packed identifiers")
79
+ elements = a.props.get("atomic numbers", a.labels)
80
+ nodes = [
81
+ _encode_color(
82
+ (
83
+ _typed(elements[i]),
84
+ tuple(
85
+ (name, _typed(a.props[name][i]), _typed(b.props[name][j]))
86
+ for name in _PROPERTIES
87
+ ),
88
+ (),
89
+ )
90
+ )
91
+ for i, j in enumerate(mapping)
92
+ ]
93
+ inverse = {j: i for i, j in enumerate(mapping)}
94
+ pairs = {
95
+ (i, j)
96
+ for i, row in a.graph.items()
97
+ for j, weight in row.items()
98
+ if i < j and weight
99
+ }
100
+ pairs.update(
101
+ tuple(sorted((inverse[i], inverse[j])))
102
+ for i, row in b.graph.items()
103
+ for j, weight in row.items()
104
+ if i < j and weight
105
+ )
106
+ edges = {
107
+ (i, k): _encode_color(
108
+ (
109
+ float(a.graph.get(i, {}).get(k, 0)),
110
+ float(b.graph.get(mapping[i], {}).get(mapping[k], 0)),
111
+ prepared.edge_stereo[0][i, k],
112
+ prepared.edge_stereo[1][mapping[i], mapping[k]],
113
+ )
114
+ )
115
+ for i, k in pairs
116
+ }
117
+ tokens = sorted(set(nodes) | set(edges.values()))
118
+ if len(tokens) >= 65536:
119
+ raise ValueError("Native ITS exact palette exceeds uint16")
120
+ ids = {token: i for i, token in enumerate(tokens)}
121
+ node_colors = np.ascontiguousarray(
122
+ np.repeat(
123
+ np.asarray([ids[token] for token in nodes], dtype=np.int32)[:, None],
124
+ n,
125
+ axis=1,
126
+ )
127
+ )
128
+ edge_matrix = np.zeros((n, n), dtype=np.int32)
129
+ for (i, j), token in edges.items():
130
+ edge_matrix[i, j] = edge_matrix[j, i] = ids[token] + 1
131
+ zero = np.zeros_like(edge_matrix)
132
+ edge_colors = np.arange(len(tokens) + 1, dtype=np.int32)
133
+ palette = (ct.c_char_p * len(tokens))(
134
+ *(json.dumps(token, separators=(",", ":")).encode("ascii") for token in tokens)
135
+ )
136
+ problem = _Problem(
137
+ n,
138
+ node_colors.ctypes.data_as(_INT),
139
+ edge_matrix.ctypes.data_as(_INT),
140
+ zero.ctypes.data_as(_INT),
141
+ len(tokens) + 1,
142
+ 1,
143
+ edge_colors.ctypes.data_as(_INT),
144
+ len(tokens),
145
+ palette,
146
+ )
147
+ remaining = (
148
+ None
149
+ if time_limit_seconds is None
150
+ else max(0.0, time_limit_seconds - (time.perf_counter() - started))
151
+ )
152
+ if remaining == 0:
153
+ return NativeClassificationResult(
154
+ (), 0, False, "time_limit", 0, time.perf_counter() - started
155
+ )
156
+ identity = np.arange(n, dtype=np.int32)
157
+ costs = np.asarray([cost2], dtype=np.int64)
158
+ classes, errors = [], []
159
+ receive = _records_callback(n, classes, errors, None)
160
+ statistics = _Statistics()
161
+ function = (
162
+ library.synkit_mapper_classify_batch_ex
163
+ if identifier_mode == "packed"
164
+ else library.synkit_mapper_classify_batch_with_cancel
165
+ )
166
+ status = function(
167
+ ct.byref(problem),
168
+ 1,
169
+ identity.ctypes.data_as(_INT),
170
+ costs.ctypes.data_as(_LONG),
171
+ -1 if remaining is None else remaining,
172
+ node_cap,
173
+ -1,
174
+ None if cancel_token is None else cancel_token.pointer,
175
+ receive,
176
+ None,
177
+ ct.byref(statistics),
178
+ *((0, 1) if identifier_mode == "packed" else ()),
179
+ )
180
+ if errors:
181
+ raise errors[0]
182
+ if status:
183
+ raise RuntimeError(library.synkit_mapper_classification_error().decode())
184
+ return replace(
185
+ _classification_result(classes, statistics, time.perf_counter() - started),
186
+ classes=tuple(replace(item, representative=mapping) for item in classes),
187
+ )