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,771 @@
1
+ """Bounded exhaustive overlap search and exact composite quotienting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import itertools
7
+ import json
8
+ import math
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Any, Hashable, Iterable, Mapping
12
+
13
+ import networkx as nx
14
+
15
+ from synkit.Graph.Morphism import LewisLabelledGraph
16
+ from synkit.Rule.Apply import RuleSpan
17
+
18
+ from .composition import (
19
+ CompositionError,
20
+ CompositionIssue,
21
+ CompositionResult,
22
+ RuleOverlap,
23
+ compose_rules,
24
+ )
25
+
26
+
27
+ class OverlapSearchIssueCode(str, Enum):
28
+ """Stable failures for bounded overlap enumeration and quotienting."""
29
+
30
+ SCHEMA_MISMATCH = "OVERLAP_SEARCH_SCHEMA_MISMATCH"
31
+ STATE_LIMIT = "OVERLAP_SEARCH_STATE_LIMIT"
32
+ OVERLAP_LIMIT = "OVERLAP_SEARCH_OVERLAP_LIMIT"
33
+ NODE_LIMIT = "OVERLAP_SEARCH_NODE_LIMIT"
34
+ MATRIX_LIMIT = "OVERLAP_SEARCH_MATRIX_LIMIT"
35
+ CANONICAL_LIMIT = "OVERLAP_SEARCH_CANONICAL_LIMIT"
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class OverlapSearchIssue:
40
+ code: OverlapSearchIssueCode
41
+ message: str
42
+ context: Mapping[str, Any] = field(default_factory=dict)
43
+
44
+ def to_dict(self) -> dict[str, Any]:
45
+ return {
46
+ "code": self.code.value,
47
+ "message": self.message,
48
+ "context": dict(self.context),
49
+ }
50
+
51
+
52
+ class OverlapSearchError(RuntimeError):
53
+ """Raised instead of returning a silently truncated candidate family."""
54
+
55
+ def __init__(
56
+ self,
57
+ issue: OverlapSearchIssue,
58
+ *,
59
+ partial_overlaps: Iterable[RuleOverlap] = (),
60
+ ) -> None:
61
+ self.issue = issue
62
+ self.partial_overlaps = tuple(partial_overlaps)
63
+ super().__init__(issue.message)
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class OverlapSearchLimits:
68
+ """Explicit finite resource bounds for one search."""
69
+
70
+ max_states: int = 1_000_000
71
+ max_overlaps: int = 100_000
72
+ max_overlap_nodes: int | None = None
73
+ max_component_embeddings: int = 100_000
74
+ max_canonical_permutations: int = 1_000_000
75
+
76
+ def __post_init__(self) -> None:
77
+ positive = {
78
+ "max_states": self.max_states,
79
+ "max_overlaps": self.max_overlaps,
80
+ "max_component_embeddings": self.max_component_embeddings,
81
+ "max_canonical_permutations": self.max_canonical_permutations,
82
+ }
83
+ if any(value <= 0 for value in positive.values()):
84
+ raise ValueError("Search and canonicalization limits must be positive.")
85
+ if self.max_overlap_nodes is not None and self.max_overlap_nodes < 0:
86
+ raise ValueError("max_overlap_nodes cannot be negative.")
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class CompositionWitness:
91
+ """One material overlap and its accepted construction proof."""
92
+
93
+ overlap: RuleOverlap
94
+ overlap_digest: str
95
+ composition: CompositionResult
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class RejectedOverlap:
100
+ """One enumerated overlap whose composition premise failed."""
101
+
102
+ overlap: RuleOverlap
103
+ overlap_digest: str
104
+ issues: tuple[CompositionIssue, ...]
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class CompositionClass:
109
+ """One exact labelled-rule isomorphism class retaining every witness."""
110
+
111
+ canonical_id: str
112
+ representative: RuleSpan
113
+ witnesses: tuple[CompositionWitness, ...]
114
+
115
+
116
+ @dataclass(frozen=True)
117
+ class CompositionSearchResult:
118
+ """A complete bounded search result; construction ambiguity is retained."""
119
+
120
+ overlaps: tuple[RuleOverlap, ...]
121
+ classes: tuple[CompositionClass, ...]
122
+ rejected: tuple[RejectedOverlap, ...]
123
+ match_matrix: "ExtendedMatchMatrix"
124
+ explored_states: int
125
+
126
+ @property
127
+ def raw_overlap_count(self) -> int:
128
+ return len(self.overlaps)
129
+
130
+ @property
131
+ def accepted_count(self) -> int:
132
+ return sum(len(group.witnesses) for group in self.classes)
133
+
134
+ @property
135
+ def exact_class_count(self) -> int:
136
+ return len(self.classes)
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class ExtendedMatchMatrix:
141
+ """Full-component embedding counts plus one explicit empty column."""
142
+
143
+ second_components: tuple[frozenset[Hashable], ...]
144
+ first_components: tuple[frozenset[Hashable], ...]
145
+ counts: tuple[tuple[int, ...], ...]
146
+
147
+ @property
148
+ def empty_column(self) -> int:
149
+ return len(self.first_components)
150
+
151
+
152
+ @dataclass(frozen=True)
153
+ class RuleSpanIsomorphism:
154
+ """Replayable layerwise isomorphism between two complete rule spans."""
155
+
156
+ source: RuleSpan
157
+ target: RuleSpan
158
+ left_map: tuple[tuple[Hashable, Hashable], ...]
159
+ interface_map: tuple[tuple[Hashable, Hashable], ...]
160
+ right_map: tuple[tuple[Hashable, Hashable], ...]
161
+
162
+ def replay(self) -> bool:
163
+ mapping = {
164
+ **{("L", left): ("L", right) for left, right in self.left_map},
165
+ **{("K", left): ("K", right) for left, right in self.interface_map},
166
+ **{("R", left): ("R", right) for left, right in self.right_map},
167
+ }
168
+ return _rule_metadata_key(self.source) == _rule_metadata_key(
169
+ self.target
170
+ ) and _mapping_is_colored_isomorphism(
171
+ _rule_graph(self.source), _rule_graph(self.target), mapping
172
+ )
173
+
174
+
175
+ def _labels(graph: LewisLabelledGraph, node: Hashable) -> tuple[tuple[str, Any], ...]:
176
+ return tuple(sorted(graph.node_labels(node, semantic=True).items()))
177
+
178
+
179
+ def _edge_labels(
180
+ graph: LewisLabelledGraph, edge: frozenset[Hashable]
181
+ ) -> tuple[tuple[str, Any], ...]:
182
+ return tuple(sorted(graph.edge_labels(edge, semantic=True).items()))
183
+
184
+
185
+ def _node_order_key(
186
+ graph: LewisLabelledGraph, node: Hashable
187
+ ) -> tuple[str, int, tuple[str, ...], str]:
188
+ incident = tuple(
189
+ sorted(
190
+ repr(_edge_labels(graph, edge)) for edge in graph.edge_keys if node in edge
191
+ )
192
+ )
193
+ return repr(_labels(graph, node)), len(incident), incident, repr(node)
194
+
195
+
196
+ def _component_order_key(
197
+ graph: LewisLabelledGraph, component: frozenset[Hashable]
198
+ ) -> tuple[Any, ...]:
199
+ edges = tuple(edge for edge in graph.edge_keys if edge <= component)
200
+ return (
201
+ len(component),
202
+ len(edges),
203
+ tuple(sorted(repr(_labels(graph, node)) for node in component)),
204
+ tuple(sorted(repr(_edge_labels(graph, edge)) for edge in edges)),
205
+ tuple(sorted(map(repr, component))),
206
+ )
207
+
208
+
209
+ def _component_embedding_count(
210
+ first: LewisLabelledGraph,
211
+ first_component: frozenset[Hashable],
212
+ second: LewisLabelledGraph,
213
+ second_component: frozenset[Hashable],
214
+ limit: int,
215
+ ) -> int:
216
+ first_graph = first.to_networkx().subgraph(first_component).copy()
217
+ second_graph = second.to_networkx().subgraph(second_component).copy()
218
+ node_keys = first.schema.semantic_node_keys
219
+ edge_keys = first.schema.semantic_edge_keys
220
+ matcher = nx.algorithms.isomorphism.GraphMatcher(
221
+ first_graph,
222
+ second_graph,
223
+ node_match=lambda left, right: all(
224
+ left[key] == right[key] for key in node_keys
225
+ ),
226
+ edge_match=lambda left, right: all(
227
+ left[key] == right[key] for key in edge_keys
228
+ ),
229
+ )
230
+ count = 0
231
+ for _ in matcher.subgraph_monomorphisms_iter():
232
+ count += 1
233
+ if count > limit:
234
+ raise OverlapSearchError(
235
+ OverlapSearchIssue(
236
+ OverlapSearchIssueCode.MATRIX_LIMIT,
237
+ "Extended match-matrix construction exceeded its embedding bound.",
238
+ {"limit": limit},
239
+ )
240
+ )
241
+ return count
242
+
243
+
244
+ def extended_component_match_matrix(
245
+ first_right: LewisLabelledGraph,
246
+ second_left: LewisLabelledGraph,
247
+ *,
248
+ max_embeddings: int = 100_000,
249
+ ) -> ExtendedMatchMatrix:
250
+ """Build the native extended match matrix used to order exhaustive search."""
251
+ if first_right.schema != second_left.schema:
252
+ raise OverlapSearchError(
253
+ OverlapSearchIssue(
254
+ OverlapSearchIssueCode.SCHEMA_MISMATCH,
255
+ "Match-matrix endpoints must use the same LLG schema.",
256
+ )
257
+ )
258
+ first_components = tuple(
259
+ sorted(
260
+ (
261
+ frozenset(part)
262
+ for part in nx.connected_components(first_right.to_networkx())
263
+ ),
264
+ key=lambda part: _component_order_key(first_right, part),
265
+ )
266
+ )
267
+ second_components = tuple(
268
+ sorted(
269
+ (
270
+ frozenset(part)
271
+ for part in nx.connected_components(second_left.to_networkx())
272
+ ),
273
+ key=lambda part: _component_order_key(second_left, part),
274
+ )
275
+ )
276
+ remaining = max_embeddings
277
+ rows = []
278
+ for second_component in second_components:
279
+ row = []
280
+ for first_component in first_components:
281
+ count = _component_embedding_count(
282
+ first_right,
283
+ first_component,
284
+ second_left,
285
+ second_component,
286
+ remaining,
287
+ )
288
+ remaining -= count
289
+ row.append(count)
290
+ rows.append(tuple(row) + (1,))
291
+ counts = tuple(rows)
292
+ return ExtendedMatchMatrix(second_components, first_components, counts)
293
+
294
+
295
+ def _extension_is_compatible(
296
+ source: LewisLabelledGraph,
297
+ target: LewisLabelledGraph,
298
+ mapping: Mapping[Hashable, Hashable],
299
+ source_node: Hashable,
300
+ target_node: Hashable,
301
+ ) -> bool:
302
+ for mapped_source, mapped_target in mapping.items():
303
+ target_edge = frozenset((target_node, mapped_target))
304
+ if target_edge not in target.edge_keys:
305
+ continue
306
+ source_edge = frozenset((source_node, mapped_source))
307
+ # An edge present only in L2 can be supplied by the first rule's frame.
308
+ # Only a shared pair with conflicting values prevents a common world.
309
+ # Dangling and deleted-frame requirements are checked by composition.
310
+ if source_edge in source.edge_keys and (
311
+ _edge_labels(source, source_edge) != _edge_labels(target, target_edge)
312
+ ):
313
+ return False
314
+ return True
315
+
316
+
317
+ @dataclass
318
+ class _EnumerationState:
319
+ explored: int = 0
320
+ overlaps: list[RuleOverlap] = field(default_factory=list)
321
+
322
+
323
+ def enumerate_overlaps(
324
+ first_right: LewisLabelledGraph,
325
+ second_left: LewisLabelledGraph,
326
+ *,
327
+ limits: OverlapSearchLimits | None = None,
328
+ _match_matrix: ExtendedMatchMatrix | None = None,
329
+ ) -> tuple[tuple[RuleOverlap, ...], int]:
330
+ """Enumerate every admitted partial injective overlap within hard bounds."""
331
+ active = limits or OverlapSearchLimits()
332
+ if first_right.schema != second_left.schema:
333
+ raise OverlapSearchError(
334
+ OverlapSearchIssue(
335
+ OverlapSearchIssueCode.SCHEMA_MISMATCH,
336
+ "Overlap endpoints must use the same LLG schema.",
337
+ )
338
+ )
339
+ maximum = min(len(first_right.node_ids), len(second_left.node_ids))
340
+ if active.max_overlap_nodes is not None and active.max_overlap_nodes < maximum:
341
+ raise OverlapSearchError(
342
+ OverlapSearchIssue(
343
+ OverlapSearchIssueCode.NODE_LIMIT,
344
+ "The node bound excludes possible overlap cardinalities.",
345
+ {"bound": active.max_overlap_nodes, "unbounded_maximum": maximum},
346
+ )
347
+ )
348
+ max_nodes = maximum
349
+ matrix = _match_matrix or extended_component_match_matrix(
350
+ first_right,
351
+ second_left,
352
+ max_embeddings=active.max_component_embeddings,
353
+ )
354
+ first_component = {
355
+ node: index
356
+ for index, component in enumerate(matrix.first_components)
357
+ for node in component
358
+ }
359
+ second_component = {
360
+ node: index
361
+ for index, component in enumerate(matrix.second_components)
362
+ for node in component
363
+ }
364
+
365
+ def target_key(source: Hashable, target: Hashable) -> tuple[Any, ...]:
366
+ count = matrix.counts[second_component[target]][first_component[source]]
367
+ return -count, _node_order_key(second_left, target)
368
+
369
+ compatible_targets = {
370
+ node: tuple(
371
+ sorted(
372
+ (
373
+ target
374
+ for target in second_left.node_ids
375
+ if _labels(first_right, node) == _labels(second_left, target)
376
+ ),
377
+ key=lambda target: target_key(node, target),
378
+ )
379
+ )
380
+ for node in first_right.node_ids
381
+ }
382
+ source_nodes = sorted(
383
+ first_right.node_ids,
384
+ key=lambda node: (
385
+ len(compatible_targets[node]),
386
+ _node_order_key(first_right, node),
387
+ ),
388
+ )
389
+ state = _EnumerationState()
390
+
391
+ def visit(
392
+ index: int,
393
+ mapping: dict[Hashable, Hashable],
394
+ used_targets: set[Hashable],
395
+ ) -> None:
396
+ state.explored += 1
397
+ if state.explored > active.max_states:
398
+ raise OverlapSearchError(
399
+ OverlapSearchIssue(
400
+ OverlapSearchIssueCode.STATE_LIMIT,
401
+ "Overlap enumeration exceeded max_states.",
402
+ {"limit": active.max_states},
403
+ ),
404
+ partial_overlaps=state.overlaps,
405
+ )
406
+ if index == len(source_nodes):
407
+ if len(state.overlaps) >= active.max_overlaps:
408
+ raise OverlapSearchError(
409
+ OverlapSearchIssue(
410
+ OverlapSearchIssueCode.OVERLAP_LIMIT,
411
+ "Overlap enumeration exceeded max_overlaps.",
412
+ {"limit": active.max_overlaps},
413
+ ),
414
+ partial_overlaps=state.overlaps,
415
+ )
416
+ state.overlaps.append(
417
+ RuleOverlap.from_mapping(first_right, second_left, mapping)
418
+ )
419
+ return
420
+ source_node = source_nodes[index]
421
+ visit(index + 1, mapping, used_targets)
422
+ if len(mapping) >= max_nodes:
423
+ return
424
+ for target_node in compatible_targets[source_node]:
425
+ if target_node in used_targets or not _extension_is_compatible(
426
+ first_right,
427
+ second_left,
428
+ mapping,
429
+ source_node,
430
+ target_node,
431
+ ):
432
+ continue
433
+ mapping[source_node] = target_node
434
+ used_targets.add(target_node)
435
+ visit(index + 1, mapping, used_targets)
436
+ used_targets.remove(target_node)
437
+ del mapping[source_node]
438
+
439
+ visit(0, {}, set())
440
+ return tuple(state.overlaps), state.explored
441
+
442
+
443
+ def _add_layer(
444
+ target: nx.Graph,
445
+ layer: str,
446
+ graph: LewisLabelledGraph,
447
+ ) -> None:
448
+ for node in graph.node_ids:
449
+ target.add_node((layer, node), color=repr((layer, _labels(graph, node))))
450
+ for edge in graph.edge_keys:
451
+ left, right = tuple(edge)
452
+ target.add_edge(
453
+ (layer, left),
454
+ (layer, right),
455
+ color=repr(("incidence", _edge_labels(graph, edge))),
456
+ )
457
+
458
+
459
+ def _rule_graph(rule: RuleSpan) -> nx.Graph:
460
+ graph = nx.Graph()
461
+ _add_layer(graph, "L", rule.left)
462
+ _add_layer(graph, "K", rule.interface)
463
+ _add_layer(graph, "R", rule.right)
464
+ for node, image in rule.left_arm.mapping.items():
465
+ graph.add_edge(("K", node), ("L", image), color="arm:left")
466
+ for node, image in rule.right_arm.mapping.items():
467
+ graph.add_edge(("K", node), ("R", image), color="arm:right")
468
+ return graph
469
+
470
+
471
+ def _overlap_graph(overlap: RuleOverlap) -> nx.Graph:
472
+ graph = nx.Graph()
473
+ _add_layer(graph, "R1", overlap.first_arm.target)
474
+ _add_layer(graph, "D", overlap.interface)
475
+ _add_layer(graph, "L2", overlap.second_arm.target)
476
+ for node, image in overlap.first_arm.mapping.items():
477
+ graph.add_edge(("D", node), ("R1", image), color="arm:first")
478
+ for node, image in overlap.second_arm.mapping.items():
479
+ graph.add_edge(("D", node), ("L2", image), color="arm:second")
480
+ return graph
481
+
482
+
483
+ def _boundary_key(rule: RuleSpan) -> tuple[Any, ...]:
484
+ environment = rule.environment
485
+ return (
486
+ rule.boundary.value,
487
+ (
488
+ None
489
+ if environment is None
490
+ else (
491
+ environment.name,
492
+ environment.element_delta,
493
+ environment.electron_delta,
494
+ )
495
+ ),
496
+ )
497
+
498
+
499
+ def _rule_metadata_key(rule: RuleSpan) -> tuple[Any, ...]:
500
+ return repr(rule.left.schema), _boundary_key(rule)
501
+
502
+
503
+ def _mapping_is_colored_isomorphism(
504
+ left: nx.Graph,
505
+ right: nx.Graph,
506
+ mapping: Mapping[Hashable, Hashable],
507
+ ) -> bool:
508
+ if set(mapping) != set(left) or set(mapping.values()) != set(right):
509
+ return False
510
+ if len(set(mapping.values())) != len(mapping):
511
+ return False
512
+ for node, image in mapping.items():
513
+ if left.nodes[node].get("color") != right.nodes[image].get("color"):
514
+ return False
515
+ if left.number_of_edges() != right.number_of_edges():
516
+ return False
517
+ for source, target, attrs in left.edges(data=True):
518
+ image = (mapping[source], mapping[target])
519
+ if not right.has_edge(*image):
520
+ return False
521
+ if attrs.get("color") != right.edges[image].get("color"):
522
+ return False
523
+ return True
524
+
525
+
526
+ def find_rule_span_isomorphism(
527
+ left: RuleSpan, right: RuleSpan
528
+ ) -> RuleSpanIsomorphism | None:
529
+ """Return an exact carrier-span witness, or ``None`` when none exists.
530
+
531
+ Sequential application conditions are retained on each rule but are not
532
+ compared here. A span quotient does not prove guarded-rule equivalence.
533
+ """
534
+ if _rule_metadata_key(left) != _rule_metadata_key(right):
535
+ return None
536
+ layers = (
537
+ (left.left, right.left),
538
+ (left.interface, right.interface),
539
+ (left.right, right.right),
540
+ )
541
+ if all(source.node_ids == target.node_ids for source, target in layers):
542
+ identity = RuleSpanIsomorphism(
543
+ left,
544
+ right,
545
+ *(
546
+ tuple((node, node) for node in sorted(source.node_ids, key=repr))
547
+ for source, _ in layers
548
+ ),
549
+ )
550
+ if identity.replay():
551
+ return identity
552
+ matcher = nx.algorithms.isomorphism.GraphMatcher(
553
+ _rule_graph(left),
554
+ _rule_graph(right),
555
+ node_match=nx.algorithms.isomorphism.categorical_node_match("color", None),
556
+ edge_match=nx.algorithms.isomorphism.categorical_edge_match("color", None),
557
+ )
558
+ if not matcher.is_isomorphic():
559
+ return None
560
+ mapping = matcher.mapping
561
+ witness = RuleSpanIsomorphism(
562
+ left,
563
+ right,
564
+ tuple(
565
+ sorted(
566
+ (
567
+ (source[1], target[1])
568
+ for source, target in mapping.items()
569
+ if source[0] == "L"
570
+ ),
571
+ key=repr,
572
+ )
573
+ ),
574
+ tuple(
575
+ sorted(
576
+ (
577
+ (source[1], target[1])
578
+ for source, target in mapping.items()
579
+ if source[0] == "K"
580
+ ),
581
+ key=repr,
582
+ )
583
+ ),
584
+ tuple(
585
+ sorted(
586
+ (
587
+ (source[1], target[1])
588
+ for source, target in mapping.items()
589
+ if source[0] == "R"
590
+ ),
591
+ key=repr,
592
+ )
593
+ ),
594
+ )
595
+ return witness if witness.replay() else None
596
+
597
+
598
+ def rule_spans_isomorphic(left: RuleSpan, right: RuleSpan) -> bool:
599
+ """Decide exact rule-span isomorphism, including arms and resource policy."""
600
+ return find_rule_span_isomorphism(left, right) is not None
601
+
602
+
603
+ def _canonical_code(graph: nx.Graph, permutation_limit: int) -> str:
604
+ partitions: dict[str, list[Hashable]] = {}
605
+ for node, attrs in graph.nodes(data=True):
606
+ partitions.setdefault(str(attrs["color"]), []).append(node)
607
+ colors = sorted(partitions)
608
+ permutations = math.prod(math.factorial(len(partitions[color])) for color in colors)
609
+ if permutations > permutation_limit:
610
+ raise OverlapSearchError(
611
+ OverlapSearchIssue(
612
+ OverlapSearchIssueCode.CANONICAL_LIMIT,
613
+ "Exact canonicalization exceeded its permutation bound.",
614
+ {"required": permutations, "limit": permutation_limit},
615
+ )
616
+ )
617
+ best: str | None = None
618
+ choices = [tuple(itertools.permutations(partitions[color])) for color in colors]
619
+ color_sequence = tuple(
620
+ color for color in colors for _ in range(len(partitions[color]))
621
+ )
622
+ for selection in itertools.product(*choices):
623
+ order = tuple(itertools.chain.from_iterable(selection))
624
+ edges = []
625
+ for index, left in enumerate(order):
626
+ for right in order[index + 1 :]:
627
+ edges.append(
628
+ graph.edges[left, right]["color"]
629
+ if graph.has_edge(left, right)
630
+ else None
631
+ )
632
+ code = json.dumps(
633
+ (color_sequence, edges), separators=(",", ":"), sort_keys=True
634
+ )
635
+ if best is None or code < best:
636
+ best = code
637
+ return best or json.dumps((color_sequence, ()), separators=(",", ":"))
638
+
639
+
640
+ def canonical_rule_identity(rule: RuleSpan, *, permutation_limit: int) -> str:
641
+ """Return an exact, carrier-map-invariant digest for a rule span."""
642
+ payload = json.dumps(
643
+ (
644
+ _rule_metadata_key(rule),
645
+ _canonical_code(_rule_graph(rule), permutation_limit),
646
+ ),
647
+ separators=(",", ":"),
648
+ sort_keys=True,
649
+ )
650
+ return hashlib.sha256(payload.encode()).hexdigest()
651
+
652
+
653
+ def canonical_overlap_digest(overlap: RuleOverlap, *, permutation_limit: int) -> str:
654
+ """Return a carrier-map-invariant digest of both overlap arms."""
655
+ code = _canonical_code(_overlap_graph(overlap), permutation_limit)
656
+ payload = json.dumps(
657
+ (repr(overlap.first_arm.target.schema), code), separators=(",", ":")
658
+ )
659
+ return hashlib.sha256(payload.encode()).hexdigest()
660
+
661
+
662
+ def _rule_bucket(rule: RuleSpan) -> tuple[Any, ...]:
663
+ graph = _rule_graph(rule)
664
+ digest = nx.weisfeiler_lehman_graph_hash(
665
+ graph, node_attr="color", edge_attr="color"
666
+ )
667
+ return _rule_metadata_key(rule), len(graph), graph.number_of_edges(), digest
668
+
669
+
670
+ def quotient_composition_witnesses(
671
+ witnesses: Iterable[CompositionWitness],
672
+ *,
673
+ limits: OverlapSearchLimits | None = None,
674
+ ) -> tuple[CompositionClass, ...]:
675
+ """Quotient accepted witnesses by exact labelled-rule isomorphism.
676
+
677
+ This is public so validation can time exact quotienting separately from
678
+ overlap enumeration and composite construction. It never chooses one
679
+ material witness: every witness remains attached to its exact class.
680
+ """
681
+ active = limits or OverlapSearchLimits()
682
+ buckets: dict[tuple[Any, ...], list[list[CompositionWitness]]] = {}
683
+ for witness in witnesses:
684
+ rule = witness.composition.rule
685
+ groups = buckets.setdefault(_rule_bucket(rule), [])
686
+ for group in groups:
687
+ if rule_spans_isomorphic(rule, group[0].composition.rule):
688
+ group.append(witness)
689
+ break
690
+ else:
691
+ groups.append([witness])
692
+ classes = []
693
+ for groups in buckets.values():
694
+ for group in groups:
695
+ representative = group[0].composition.rule
696
+ canonical_id = canonical_rule_identity(
697
+ representative,
698
+ permutation_limit=active.max_canonical_permutations,
699
+ )
700
+ classes.append(
701
+ CompositionClass(
702
+ canonical_id,
703
+ representative,
704
+ tuple(sorted(group, key=lambda item: item.overlap_digest)),
705
+ )
706
+ )
707
+ return tuple(sorted(classes, key=lambda group: group.canonical_id))
708
+
709
+
710
+ def search_compositions(
711
+ first: RuleSpan,
712
+ second: RuleSpan,
713
+ *,
714
+ limits: OverlapSearchLimits | None = None,
715
+ ) -> CompositionSearchResult:
716
+ """Enumerate, construct, retain, and exactly quotient all bounded overlaps."""
717
+ active = limits or OverlapSearchLimits()
718
+ match_matrix = extended_component_match_matrix(
719
+ first.right,
720
+ second.left,
721
+ max_embeddings=active.max_component_embeddings,
722
+ )
723
+ overlaps, explored = enumerate_overlaps(
724
+ first.right,
725
+ second.left,
726
+ limits=active,
727
+ _match_matrix=match_matrix,
728
+ )
729
+ accepted: list[CompositionWitness] = []
730
+ rejected: list[RejectedOverlap] = []
731
+ for overlap in overlaps:
732
+ digest = canonical_overlap_digest(
733
+ overlap,
734
+ permutation_limit=active.max_canonical_permutations,
735
+ )
736
+ try:
737
+ composition = compose_rules(first, second, overlap)
738
+ except CompositionError as error:
739
+ rejected.append(RejectedOverlap(overlap, digest, error.issues))
740
+ else:
741
+ accepted.append(CompositionWitness(overlap, digest, composition))
742
+ classes = quotient_composition_witnesses(accepted, limits=active)
743
+ return CompositionSearchResult(
744
+ overlaps,
745
+ classes,
746
+ tuple(sorted(rejected, key=lambda item: item.overlap_digest)),
747
+ match_matrix,
748
+ explored,
749
+ )
750
+
751
+
752
+ __all__ = [
753
+ "CompositionClass",
754
+ "CompositionSearchResult",
755
+ "CompositionWitness",
756
+ "ExtendedMatchMatrix",
757
+ "OverlapSearchError",
758
+ "OverlapSearchIssue",
759
+ "OverlapSearchIssueCode",
760
+ "OverlapSearchLimits",
761
+ "RejectedOverlap",
762
+ "RuleSpanIsomorphism",
763
+ "canonical_overlap_digest",
764
+ "canonical_rule_identity",
765
+ "enumerate_overlaps",
766
+ "extended_component_match_matrix",
767
+ "find_rule_span_isomorphism",
768
+ "quotient_composition_witnesses",
769
+ "rule_spans_isomorphic",
770
+ "search_compositions",
771
+ ]