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,274 @@
1
+ """Secondary label metrics, structural classes and shared annotation policies.
2
+
3
+ Inputs must retain complete joint labels, not merely one representative per
4
+ changed-bond set. Reference annotations are revealed only to this evaluation
5
+ stage; they are never used to generate predictions or candidate sets.
6
+ """
7
+
8
+ import json
9
+ from numbers import Integral
10
+
11
+ from .bonds import IncompleteSymmetry, ScoreWitness, bond_f1
12
+ from .identifiability import extract_label
13
+ from .orbits import SupportOrbitEvaluator
14
+ from ..solvers.slap.lap import _adjacency_and_elements
15
+ from ..reporting.spectrum import exact_its_and_template_codes
16
+
17
+
18
+ def features(label, kind):
19
+ if kind == "bond":
20
+ return frozenset(("bond", i, j) for i, j in label.changed_bonds)
21
+ if kind == "atom":
22
+ return frozenset(("atom", i) for i in label.centre_atoms)
23
+ if kind == "typed":
24
+ return frozenset(("bond", *edit) for edit in label.typed_bond_edits)
25
+ if kind == "joint":
26
+ return features(label, "typed") | frozenset(
27
+ ("unary", *edit) for edit in label.unary_changes
28
+ )
29
+ raise ValueError("Unknown label kind")
30
+
31
+
32
+ class FeatureOrbitEvaluator(SupportOrbitEvaluator):
33
+ """The same exact support action on typed bonds, unary edits or atom sets."""
34
+
35
+ def __init__(self, reactant, *, exact_match=False, **limits):
36
+ super().__init__(reactant, **limits)
37
+ self.exact_match = exact_match
38
+
39
+ def _validate(self, label):
40
+ label = frozenset(tuple(item) for item in label)
41
+ for item in label:
42
+ if not item or item[0] not in {"atom", "bond", "unary"}:
43
+ raise ValueError("Unknown feature token")
44
+ if item[0] == "atom" and len(item) == 2:
45
+ indices = item[1:2]
46
+ elif item[0] == "bond" and len(item) in (3, 5):
47
+ indices = item[1:3]
48
+ if not item[1] < item[2]:
49
+ raise ValueError("Bond coordinates must be ordered")
50
+ if len(item) == 5 and (
51
+ any(not isinstance(x, Integral) for x in item[3:])
52
+ or item[3] == item[4]
53
+ ):
54
+ raise ValueError(
55
+ "Typed edits need distinct integer before/after orders"
56
+ )
57
+ elif item[0] == "unary" and len(item) == 5:
58
+ indices = item[1:2]
59
+ if item[2] not in {"charges", "hcounts"} or any(
60
+ not isinstance(x, Integral) for x in item[3:]
61
+ ):
62
+ raise ValueError("Unsupported unary edit")
63
+ else:
64
+ raise ValueError("Invalid feature shape")
65
+ if any(not isinstance(i, Integral) or not 0 <= i < self.n for i in indices):
66
+ raise ValueError("Out-of-range feature coordinate")
67
+ return label
68
+
69
+ def _support(self, label):
70
+ return tuple(
71
+ sorted({i for x in label for i in (x[1:3] if x[0] == "bond" else x[1:2])})
72
+ )
73
+
74
+ def _transport(self, label, permutation):
75
+ result = []
76
+ for item in label:
77
+ if item[0] == "bond":
78
+ i, j = sorted((permutation[item[1]], permutation[item[2]]))
79
+ result.append(("bond", i, j, *item[3:]))
80
+ else:
81
+ result.append((item[0], permutation[item[1]], *item[2:]))
82
+ return frozenset(result)
83
+
84
+ def score(self, prediction, label):
85
+ prediction = self._validate(prediction)
86
+ best = None
87
+ for image, witness in self.orbit(label).items():
88
+ self._check()
89
+ value = (
90
+ int(prediction == image)
91
+ if self.exact_match
92
+ else bond_f1(prediction, image)
93
+ )
94
+ if best is None or value > best.score:
95
+ best = ScoreWitness(value, witness)
96
+ return best
97
+
98
+
99
+ def _witness(value):
100
+ return {
101
+ "difference": str(value.difference),
102
+ "label": sorted(value.label),
103
+ "a_score": str(value.a.score),
104
+ "b_score": str(value.b.score),
105
+ "a_transporter": value.a.transporter,
106
+ "b_transporter": value.b.transporter,
107
+ }
108
+
109
+
110
+ def analyze_annotations( # noqa: C901
111
+ reactant,
112
+ product,
113
+ candidate_mappings,
114
+ prediction_a,
115
+ prediction_b,
116
+ *,
117
+ joint_labels_complete,
118
+ minimum,
119
+ reference_mapping=None,
120
+ metric_seconds=10,
121
+ canonical_seconds=0.25,
122
+ template_radius=1,
123
+ ):
124
+ """Evaluate complete joint witnesses with independently reported stages.
125
+
126
+ Canonical full ITS codes choose a deterministic class convention. Policies
127
+ nearest to A/B use primary bond F1 with that canonical code as tie-break;
128
+ every method is evaluated against the resulting *shared* label. These
129
+ conventions are diagnostics, not chemical ground truth.
130
+ """
131
+ if not joint_labels_complete:
132
+ raise ValueError("Secondary evaluation requires complete joint labels")
133
+ mappings = [tuple(m) for m in candidate_mappings]
134
+ if not mappings:
135
+ raise ValueError("A nonempty candidate set is required")
136
+ labels = [extract_label(reactant, product, m) for m in mappings]
137
+ if any(x.weighted_distance != minimum for x in labels):
138
+ raise ValueError(
139
+ "Candidate objective does not match the declared weighted minimum"
140
+ )
141
+ pa = extract_label(reactant, product, prediction_a)
142
+ pb = extract_label(reactant, product, prediction_b)
143
+ reference = (
144
+ None
145
+ if reference_mapping is None
146
+ else extract_label(reactant, product, reference_mapping)
147
+ )
148
+ output = {"status": "evaluated", "metrics": {}, "reference": {}, "policies": {}}
149
+ if reference is not None:
150
+ output["reference"] = {
151
+ "weighted_distance": str(reference.weighted_distance),
152
+ "mapping_in_minimum": reference.weighted_distance == minimum,
153
+ }
154
+ primary_scores, primary_keys = None, None
155
+ for name, kind, exact in (
156
+ ("bond_f1", "bond", False),
157
+ ("atom_f1", "atom", False),
158
+ ("typed_f1", "typed", False),
159
+ ("bond_exact", "bond", True),
160
+ ("joint_exact", "joint", True),
161
+ ):
162
+ try:
163
+ scorer = FeatureOrbitEvaluator(
164
+ reactant, exact_match=exact, time_limit_seconds=metric_seconds
165
+ )
166
+ candidates = [features(x, kind) for x in labels]
167
+ a, b = features(pa, kind), features(pb, kind)
168
+ lo, hi = scorer.paired_envelope(a, b, candidates, labels_complete=True)
169
+ keys = [scorer.orbit_key(x) for x in candidates]
170
+ values = [
171
+ (scorer.score(a, x).score, scorer.score(b, x).score) for x in candidates
172
+ ]
173
+ record = {
174
+ "status": "complete",
175
+ "lower": _witness(lo),
176
+ "upper": _witness(hi),
177
+ "width": str(hi.difference - lo.difference),
178
+ "fixed_labels": len(set(candidates)),
179
+ "label_orbits": len(set(keys)),
180
+ }
181
+ output["metrics"][name] = record
182
+ if name == "bond_f1":
183
+ primary_scores, primary_keys = values, keys
184
+ if reference is not None:
185
+ try:
186
+ ref = features(reference, kind)
187
+ membership = scorer.orbit_key(ref) in set(keys)
188
+ ref_scores = {
189
+ "a": str(scorer.score(a, ref).score),
190
+ "b": str(scorer.score(b, ref).score),
191
+ }
192
+ record.update(
193
+ reference_status="complete",
194
+ reference_orbit_in_minimum=membership,
195
+ reference_scores=ref_scores,
196
+ )
197
+ except IncompleteSymmetry as exc:
198
+ record.update(
199
+ reference_status="unresolved", reference_reason=str(exc)
200
+ )
201
+ except IncompleteSymmetry as exc:
202
+ output["metrics"][name] = {"status": "unresolved", "reason": str(exc)}
203
+ rmatrix, elements = _adjacency_and_elements(reactant.graph(), False)
204
+ pmatrix, _ = _adjacency_and_elements(product.graph(), False)
205
+ properties = {
206
+ name: (getattr(reactant, name), getattr(product, name))
207
+ for name in ("charges", "hcounts")
208
+ }
209
+ codes = []
210
+ for mapping in mappings:
211
+ full, template, reason = exact_its_and_template_codes(
212
+ rmatrix,
213
+ pmatrix,
214
+ elements,
215
+ properties,
216
+ mapping,
217
+ template_radius=template_radius,
218
+ timeout_seconds=canonical_seconds,
219
+ )
220
+ if reason is not None:
221
+ output["structure"] = {"status": "unresolved", "reason": reason}
222
+ break
223
+ codes.append(
224
+ (
225
+ json.dumps(full, separators=(",", ":")),
226
+ json.dumps(template, separators=(",", ":")),
227
+ )
228
+ )
229
+ else:
230
+ output["structure"] = {
231
+ "status": "complete",
232
+ "its_classes": len({x[0] for x in codes}),
233
+ "template_classes": len({x[1] for x in codes}),
234
+ "template_radius": template_radius,
235
+ "codes": [
236
+ {"mapping": m, "its_code": code[0], "template_code": code[1]}
237
+ for m, code in zip(mappings, codes)
238
+ ],
239
+ }
240
+ joint = output["metrics"]["joint_exact"]
241
+ if (
242
+ joint["status"] == "complete"
243
+ and joint["label_orbits"] != output["structure"]["its_classes"]
244
+ ):
245
+ raise ValueError("Full ITS and joint-label orbit counts disagree")
246
+ if primary_scores is not None:
247
+ code_labels = {}
248
+ for code, key in zip(codes, primary_keys):
249
+ if code[0] in code_labels and code_labels[code[0]] != key:
250
+ raise ValueError(
251
+ "One ITS class induced multiple primary bond orbits"
252
+ )
253
+ code_labels[code[0]] = key
254
+ choices = {
255
+ "canonical_its": min(range(len(codes)), key=lambda i: codes[i][0]),
256
+ "nearest_a": min(
257
+ range(len(codes)),
258
+ key=lambda i: (-primary_scores[i][0], codes[i][0]),
259
+ ),
260
+ "nearest_b": min(
261
+ range(len(codes)),
262
+ key=lambda i: (-primary_scores[i][1], codes[i][0]),
263
+ ),
264
+ }
265
+ for name, index in choices.items():
266
+ a, b = primary_scores[index]
267
+ output["policies"][name] = {
268
+ "mapping": mappings[index],
269
+ "its_code": codes[index][0],
270
+ "a_score": str(a),
271
+ "b_score": str(b),
272
+ "difference": str(a - b),
273
+ }
274
+ return output
@@ -0,0 +1,124 @@
1
+ """Exact, shared-label changed-bond evaluation on fully resolved inputs.
2
+
3
+ The exhaustive full-reactant symmetry route is a bounded development oracle.
4
+ It raises on incomplete symmetry rather than silently reporting exact scores.
5
+ Production callers must also establish minimum and label-enumeration closure.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from fractions import Fraction
10
+ from time import monotonic
11
+
12
+ import networkx as nx
13
+
14
+ from .identifiability import Endpoint
15
+
16
+
17
+ class IncompleteSymmetry(RuntimeError):
18
+ """The full attributed automorphism group was not established."""
19
+
20
+
21
+ def _graph(endpoint):
22
+ graph = nx.Graph()
23
+ for i, z in enumerate(endpoint.atomic_numbers):
24
+ graph.add_node(i, color=(z, endpoint.charges[i], endpoint.hcounts[i]))
25
+ for i, j, order in endpoint.bonds:
26
+ graph.add_edge(i, j, order=order)
27
+ return graph
28
+
29
+
30
+ def full_automorphisms(endpoint: Endpoint, *, max_automorphisms=100000,
31
+ time_limit_seconds=30):
32
+ """Enumerate all symmetries, preserving elements, charges, H and orders.
33
+
34
+ The time check is cooperative between matcher yields; a campaign runner
35
+ still needs an external process deadline. Component roles are not supported
36
+ by this contract and must not be claimed as preserved attributes.
37
+ """
38
+ if max_automorphisms < 1 or time_limit_seconds <= 0:
39
+ raise ValueError("Symmetry limits must be positive")
40
+ graph = _graph(endpoint)
41
+ matcher = nx.algorithms.isomorphism.GraphMatcher(
42
+ graph, graph,
43
+ node_match=nx.algorithms.isomorphism.categorical_node_match("color", None),
44
+ edge_match=nx.algorithms.isomorphism.categorical_edge_match("order", None),
45
+ )
46
+ start = monotonic()
47
+ permutations = []
48
+ for mapping in matcher.isomorphisms_iter():
49
+ if monotonic() - start > time_limit_seconds:
50
+ raise IncompleteSymmetry("Automorphism deadline exceeded")
51
+ if len(permutations) >= max_automorphisms:
52
+ raise IncompleteSymmetry("Automorphism output cap exceeded")
53
+ permutations.append(tuple(mapping[i] for i in range(len(graph))))
54
+ if monotonic() - start > time_limit_seconds:
55
+ raise IncompleteSymmetry("Automorphism deadline exceeded")
56
+ return tuple(sorted(permutations))
57
+
58
+
59
+ def bond_f1(prediction, label):
60
+ prediction, label = frozenset(prediction), frozenset(label)
61
+ denominator = len(prediction) + len(label)
62
+ return Fraction(2 * len(prediction & label), denominator) if denominator else Fraction(1)
63
+
64
+
65
+ def transport_bonds(label, permutation):
66
+ return frozenset(tuple(sorted((permutation[i], permutation[j]))) for i, j in label)
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class ScoreWitness:
71
+ score: Fraction
72
+ transporter: tuple[int, ...]
73
+
74
+
75
+ class ExactBondEvaluator:
76
+ """Scorer bound to one endpoint and its completely enumerated symmetry."""
77
+
78
+ def __init__(self, reactant: Endpoint, **symmetry_limits):
79
+ self.n = len(reactant.atomic_numbers)
80
+ self.automorphisms = full_automorphisms(reactant, **symmetry_limits)
81
+
82
+ def _validate(self, bonds):
83
+ bonds = frozenset(bonds)
84
+ if any(len(pair) != 2 or not 0 <= pair[0] < pair[1] < self.n for pair in bonds):
85
+ raise ValueError("Bond labels require canonical in-range unordered pairs")
86
+ return bonds
87
+
88
+ def score(self, prediction, label):
89
+ prediction, label = self._validate(prediction), self._validate(label)
90
+ best = None
91
+ for permutation in self.automorphisms:
92
+ score = bond_f1(prediction, transport_bonds(label, permutation))
93
+ if best is None or score > best.score:
94
+ best = ScoreWitness(score, permutation)
95
+ return best
96
+
97
+ def orbit_key(self, label):
98
+ label = self._validate(label)
99
+ return min(tuple(sorted(transport_bonds(label, p))) for p in self.automorphisms)
100
+
101
+ def paired_envelope(self, prediction_a, prediction_b, labels, *, labels_complete):
102
+ """Return attained min/max differences with shared candidate witnesses.
103
+
104
+ ``labels_complete`` is an explicit caller obligation, not a proof
105
+ generated by the scorer. An incomplete set is refused by this API.
106
+ """
107
+ if not labels_complete:
108
+ raise ValueError("Exact envelopes require complete admitted labels")
109
+ values = []
110
+ for label in labels:
111
+ label = self._validate(label)
112
+ a, b = self.score(prediction_a, label), self.score(prediction_b, label)
113
+ values.append(PairedWitness(a.score - b.score, label, a, b))
114
+ if not values:
115
+ raise ValueError("At least one admitted label is required")
116
+ return min(values, key=lambda x: x.difference), max(values, key=lambda x: x.difference)
117
+
118
+
119
+ @dataclass(frozen=True)
120
+ class PairedWitness:
121
+ difference: Fraction
122
+ label: frozenset[tuple[int, int]]
123
+ a: ScoreWitness
124
+ b: ScoreWitness
@@ -0,0 +1,72 @@
1
+ """Separate intrinsic label sensitivity from unresolved computation.
2
+
3
+ Each case's paired F1 difference is in [-1,1]. Weights and scores are exact
4
+ rationals; these are identification bounds, not sampling confidence intervals.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from fractions import Fraction
9
+
10
+
11
+ def rational(value):
12
+ if isinstance(value, bool) or not isinstance(value, (int, str, Fraction)):
13
+ raise TypeError("Use an integer, Fraction or rational string, not a float")
14
+ return Fraction(value)
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class CohortBounds:
19
+ unresolved_weight: Fraction
20
+ outer_lower: Fraction
21
+ outer_upper: Fraction
22
+ width_lower: Fraction
23
+ width_upper: Fraction
24
+ conditional_lower: Fraction | None
25
+ conditional_upper: Fraction | None
26
+ lower_witness_assignment_upper: Fraction
27
+ upper_witness_assignment_lower: Fraction
28
+ robust_a: bool
29
+ robust_b: bool
30
+ guaranteed_substantive_reversal: bool
31
+ exact: bool
32
+
33
+
34
+ def paired_cohort_bounds(intervals, *, weights=None, margin=Fraction(1, 50)):
35
+ """Bound a fixed common-valid cohort; ``None`` means unresolved.
36
+
37
+ Resolved intervals must be attained shared-label extrema. For a global
38
+ coupled annotation policy these sums need not be attainable; the function
39
+ assumes casewise label choices are independent. Invalid predictions must
40
+ be handled before calling, by an explicit cohort or operational policy.
41
+ """
42
+ intervals = list(intervals)
43
+ if not intervals:
44
+ raise ValueError("A nonempty comparison cohort is required")
45
+ weights = ([Fraction(1, len(intervals))] * len(intervals) if weights is None
46
+ else [rational(x) for x in weights])
47
+ if len(weights) != len(intervals) or any(w < 0 for w in weights) or sum(weights) != 1:
48
+ raise ValueError("Weights must be nonnegative, aligned and sum to one")
49
+ margin = rational(margin)
50
+ if not 0 <= margin <= 1:
51
+ raise ValueError("Margin must lie in [0,1]")
52
+ lower, upper, unknown = Fraction(), Fraction(), Fraction()
53
+ for interval, weight in zip(intervals, weights):
54
+ if interval is None:
55
+ unknown += weight
56
+ else:
57
+ lo, hi = map(rational, interval)
58
+ if not -1 <= lo <= hi <= 1:
59
+ raise ValueError("Invalid paired score interval")
60
+ lower += weight * lo
61
+ upper += weight * hi
62
+ resolved_weight = 1-unknown
63
+ outer_lo, outer_hi = lower-unknown, upper+unknown
64
+ return CohortBounds(
65
+ unknown, outer_lo, outer_hi, upper-lower, upper-lower+2*unknown,
66
+ lower/resolved_weight if resolved_weight else None,
67
+ upper/resolved_weight if resolved_weight else None,
68
+ lower+unknown, upper-unknown,
69
+ outer_lo > margin, outer_hi < -margin,
70
+ lower+unknown < -margin and upper-unknown > margin,
71
+ unknown == 0,
72
+ )
@@ -0,0 +1,150 @@
1
+ """Strict heavy-atom endpoints and joint reaction labels for evaluation.
2
+
3
+ This study interface deliberately does not balance, repair, or discard atoms.
4
+ Stereochemistry is outside its connectivity-only contract; isotope inputs and
5
+ hydrogens that cannot be folded into heavy atoms are rejected.
6
+ """
7
+
8
+ from collections import Counter
9
+ from dataclasses import dataclass
10
+ from fractions import Fraction
11
+ from itertools import combinations
12
+ from numbers import Integral
13
+
14
+ from rdkit import Chem
15
+
16
+ from ..graph.labeled_graph import LabeledGraph
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Endpoint:
21
+ atomic_numbers: tuple[int, ...]
22
+ charges: tuple[int, ...]
23
+ hcounts: tuple[int, ...]
24
+ # Bond orders are represented as exact twice-order integers.
25
+ bonds: tuple[tuple[int, int, int], ...]
26
+
27
+ def __post_init__(self):
28
+ n = len(self.atomic_numbers)
29
+ if len(self.charges) != n or len(self.hcounts) != n:
30
+ raise ValueError("Endpoint property lengths differ")
31
+ if not n or any(z <= 1 for z in self.atomic_numbers):
32
+ raise ValueError("A nonempty heavy-atom inventory is required")
33
+ if any(not isinstance(x, Integral) for values in
34
+ (self.atomic_numbers, self.charges, self.hcounts) for x in values):
35
+ raise ValueError("Atom properties must be integers")
36
+ if any(h < 0 for h in self.hcounts):
37
+ raise ValueError("Hydrogen counts must be nonnegative")
38
+ pairs = set()
39
+ for i, j, order in self.bonds:
40
+ if not all(isinstance(x, Integral) for x in (i, j, order)):
41
+ raise ValueError("Bond records must use integers")
42
+ if not 0 <= i < j < n or order not in (2, 3, 4, 6):
43
+ raise ValueError("Unsupported bond coordinate or order")
44
+ if (i, j) in pairs:
45
+ raise ValueError("Duplicate bond")
46
+ pairs.add((i, j))
47
+
48
+ def graph(self):
49
+ adjacency = {i: {} for i in range(len(self.atomic_numbers))}
50
+ for i, j, order in self.bonds:
51
+ adjacency[i][j] = adjacency[j][i] = order / 2
52
+ result = LabeledGraph(adjacency, self.atomic_numbers)
53
+ for name, values in (("atomic numbers", self.atomic_numbers),
54
+ ("charges", self.charges), ("hcounts", self.hcounts)):
55
+ result.set_prop(name, list(values))
56
+ return result
57
+
58
+
59
+ def parse_endpoint(smiles: str) -> Endpoint:
60
+ """Parse every component, discard map labels, fold ordinary explicit H.
61
+
62
+ Coordinates follow RDKit's retained heavy-atom order, not atom-map numbers.
63
+ The caller must save the exact input string with exported labels.
64
+ """
65
+ params = Chem.SmilesParserParams()
66
+ params.removeHs = False
67
+ mol = Chem.MolFromSmiles(smiles, params)
68
+ if mol is None:
69
+ raise ValueError("Invalid endpoint SMILES")
70
+ for atom in mol.GetAtoms():
71
+ if atom.GetIsotope():
72
+ raise ValueError("Isotope-specific endpoints are unsupported")
73
+ if atom.GetNumRadicalElectrons():
74
+ raise ValueError("Radical endpoints are unsupported")
75
+ atom.SetAtomMapNum(0)
76
+ mol = Chem.RemoveHs(mol)
77
+ bonds = []
78
+ for bond in mol.GetBonds():
79
+ order = 2 * bond.GetBondTypeAsDouble()
80
+ if order not in (2, 3, 4, 6):
81
+ raise ValueError("Unsupported bond type")
82
+ i, j = sorted((bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()))
83
+ bonds.append((i, j, int(order)))
84
+ return Endpoint(
85
+ tuple(a.GetAtomicNum() for a in mol.GetAtoms()),
86
+ tuple(a.GetFormalCharge() for a in mol.GetAtoms()),
87
+ tuple(a.GetTotalNumHs() for a in mol.GetAtoms()),
88
+ tuple(sorted(bonds)),
89
+ )
90
+
91
+
92
+ def parse_reaction(reaction: str) -> tuple[Endpoint, Endpoint]:
93
+ """Accept balanced ``reactants>>products`` without a hidden agent policy."""
94
+ fields = reaction.split(">")
95
+ if len(fields) != 3 or fields[1]:
96
+ raise ValueError("Expected reactants>>products with no agent field")
97
+ reactant, product = map(parse_endpoint, (fields[0], fields[2]))
98
+ if Counter(reactant.atomic_numbers) != Counter(product.atomic_numbers):
99
+ raise ValueError("Heavy-atom inventories are not element balanced")
100
+ return reactant, product
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class JointLabel:
105
+ typed_bond_edits: tuple[tuple[int, int, int, int], ...]
106
+ unary_changes: tuple[tuple[int, str, int, int], ...]
107
+
108
+ @property
109
+ def changed_bonds(self):
110
+ return frozenset((i, j) for i, j, _, _ in self.typed_bond_edits)
111
+
112
+ @property
113
+ def centre_atoms(self):
114
+ return frozenset(
115
+ [i for pair in self.changed_bonds for i in pair]
116
+ + [i for i, _, _, _ in self.unary_changes]
117
+ )
118
+
119
+ @property
120
+ def weighted_distance(self):
121
+ return Fraction(sum(abs(after - before)
122
+ for _, _, before, after in self.typed_bond_edits), 2)
123
+
124
+
125
+ def extract_label(reactant: Endpoint, product: Endpoint, mapping) -> JointLabel:
126
+ """Derive one *joint* label from a complete element-compatible bijection."""
127
+ mapping = tuple(mapping)
128
+ n = len(reactant.atomic_numbers)
129
+ if (len(product.atomic_numbers) != n or len(mapping) != n
130
+ or any(not isinstance(p, Integral) for p in mapping)
131
+ or set(mapping) != set(range(n))):
132
+ raise ValueError("Mapping must be a complete bijection")
133
+ if any(z != product.atomic_numbers[mapping[i]]
134
+ for i, z in enumerate(reactant.atomic_numbers)):
135
+ raise ValueError("Mapping changes an element")
136
+ rb = {(i, j): order for i, j, order in reactant.bonds}
137
+ pb = {(i, j): order for i, j, order in product.bonds}
138
+ edits = []
139
+ for i, j in combinations(range(n), 2):
140
+ before = rb.get((i, j), 0)
141
+ after = pb.get(tuple(sorted((mapping[i], mapping[j]))), 0)
142
+ if before != after:
143
+ edits.append((i, j, before, after))
144
+ unary = []
145
+ for name in ("charges", "hcounts"):
146
+ before_values, after_values = getattr(reactant, name), getattr(product, name)
147
+ for i, p in enumerate(mapping):
148
+ if before_values[i] != after_values[p]:
149
+ unary.append((i, name, before_values[i], after_values[p]))
150
+ return JointLabel(tuple(edits), tuple(sorted(unary)))
@@ -0,0 +1,36 @@
1
+ """Declared finite chemical-lattice domain for the identifiability study.
2
+
3
+ This guard is not a repair of the generic floating-point solver API. The study
4
+ uses undirected loop-free endpoints with doubled orders in {2,3,4,6}, at most
5
+ 512 atoms, and exact integer independent witness rescoring. The current Python
6
+ assignment route verifies bounded lattice LAP answers using integer primal--dual
7
+ certificates; its operation-level argument is in the assignment-proof supplement.
8
+ This input guard alone does not certify another execution route or an archived source
9
+ version, and does not broaden the generic floating-point API guarantee.
10
+ """
11
+
12
+ MAX_STUDY_ATOMS = 512
13
+
14
+
15
+ def validate_study_domain(reactant, product):
16
+ from collections import Counter
17
+
18
+ n = len(reactant.atomic_numbers)
19
+ if not 1 <= n <= MAX_STUDY_ATOMS or len(product.atomic_numbers) != n:
20
+ raise ValueError("Study arithmetic domain requires 1–512 balanced heavy atoms")
21
+ if Counter(reactant.atomic_numbers) != Counter(product.atomic_numbers):
22
+ raise ValueError("Study endpoints must be element balanced")
23
+ for endpoint in (reactant, product):
24
+ endpoint.__post_init__()
25
+ # 4*value must fit exactly in binary64. This deliberately loose polynomial
26
+ # envelope is far above the O(n^2) matrix/DFS/profile quantities actually
27
+ # used; it also leaves headroom for LAP potential/shortest-path updates.
28
+ scaled_working_envelope = 4 * 64 * n**4
29
+ if scaled_working_envelope >= 2**53:
30
+ raise ValueError("Study arithmetic envelope exceeds binary64 integer range")
31
+ return {"domain": "bounded-half-integer-chemical-v1", "heavy_atoms": n,
32
+ "max_heavy_atoms": MAX_STUDY_ATOMS,
33
+ "doubled_orders": [2, 3, 4, 6],
34
+ "scaled_working_envelope": scaled_working_envelope,
35
+ "binary64_exact_integer_limit": 2**53,
36
+ "generic_float_api_certified": False}