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,736 @@
1
+ """Approximate automorphism orbits with 1-WL color refinement.
2
+
3
+ Distinct WL colors imply distinct orbits, while equal colors do not prove
4
+ symmetry. The module also provides component-aware mapping deduplication;
5
+ anchor components remain exempt from orbit-independence pruning.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import (
12
+ Any,
13
+ Dict,
14
+ FrozenSet,
15
+ Hashable,
16
+ Iterable,
17
+ List,
18
+ Mapping,
19
+ Optional,
20
+ Tuple,
21
+ )
22
+
23
+ import networkx as nx
24
+
25
+
26
+ # --------------------------------------------------------------------------- #
27
+ # Small, typed config container (keeps complexity low)
28
+ # --------------------------------------------------------------------------- #
29
+ @dataclass(frozen=True)
30
+ class _WLConfig:
31
+ """
32
+ Internal WL configuration.
33
+
34
+ :param node_attrs: Node attribute keys for initial labels.
35
+ :type node_attrs: tuple[str, ...]
36
+ :param edge_attrs: Edge attribute keys for neighbor signatures.
37
+ :type edge_attrs: tuple[str, ...]
38
+ :param max_iter: Maximum refinement iterations.
39
+ :type max_iter: int
40
+ """
41
+
42
+ node_attrs: Tuple[str, ...]
43
+ edge_attrs: Tuple[str, ...]
44
+ max_iter: int
45
+
46
+
47
+ # --------------------------------------------------------------------------- #
48
+ # Core estimator
49
+ # --------------------------------------------------------------------------- #
50
+ class AutoEst:
51
+ """Approximate node automorphism groups (orbits) via 1-WL color refinement.
52
+
53
+ This class performs a Weisfeiler–Lehman (WL-1) style color refinement
54
+ on the input graph to approximate a partition of nodes into
55
+ *automorphism-indistinguishability* classes (often called “WL-orbits”).
56
+ In many practical graphs (especially with chemically meaningful
57
+ node/edge labels), the WL partition coincides with, or closely
58
+ approximates, the true orbit partition and is much cheaper than
59
+ enumerating automorphisms.
60
+
61
+ Besides the basic orbit partition, :class:`AutoEst` provides a
62
+ “components-style” interface analogous to the exact automorphism helper:
63
+
64
+ - :meth:`components` returns connected components of an induced subgraph
65
+ (useful for “anchor components”).
66
+ - :meth:`orbit_components` returns connected components in the
67
+ **orbit-quotient graph** restricted to a node subset, capturing which
68
+ orbits are *coupled* by edges inside the subset.
69
+ - :meth:`deduplicate_host_orbits` prunes mappings by **host-side** WL-orbits.
70
+ - :meth:`deduplicate_pattern_orbits` prunes mappings by **pattern** orbits,
71
+ with optional anchor-aware behavior (no pruning inside anchored nodes).
72
+
73
+ :param graph: Input NetworkX graph. It is not modified in-place.
74
+ :type graph: nx.Graph
75
+ :param node_attrs: Node attribute keys whose values will be included in
76
+ the initial coloring. If ``None``, defaults are used.
77
+ :type node_attrs: list[str] or None
78
+ :param edge_attrs: Edge attribute keys whose values will be incorporated
79
+ into the neighborhood signatures. If ``None``, defaults are used.
80
+ :type edge_attrs: list[str] or None
81
+ :param max_iter: Maximum number of WL refinement iterations.
82
+ :type max_iter: int
83
+
84
+ .. note::
85
+ This is an **approximate** estimator of automorphism orbits:
86
+ two nodes with different final WL colors cannot be in the same orbit,
87
+ but nodes with the same WL color might still be distinguishable by
88
+ higher-order invariants (e.g. higher-dimensional WL, spectral
89
+ invariants, or full automorphism search). In many molecular graphs
90
+ where node/edge labels are informative, this partition is typically
91
+ very close to the true automorphism partition and is often sufficient
92
+ for symmetry-aware pruning.
93
+
94
+ .. note::
95
+ “Anchor components” can invalidate orbit-wise independence:
96
+ when a subset of nodes is treated as anchored, orbits that are
97
+ connected through the anchored subgraph should be considered *coupled*.
98
+ Use :meth:`orbit_components` and anchor-aware
99
+ :meth:`deduplicate_pattern_orbits` to avoid incorrect pruning.
100
+
101
+ .. seealso::
102
+ For discussions relating Weisfeiler–Lehman refinement to
103
+ automorphism indistinguishability and orbit structure, see:
104
+
105
+ * A. Dawar and G. Vagnozzi, *Generalizations of k-dimensional
106
+ Weisfeiler–Leman stabilization*, arXiv preprint (2019/2020).
107
+
108
+ .. rubric:: Example
109
+
110
+ .. code-block:: python
111
+
112
+ import networkx as nx
113
+ from synkit.Graph.automorphism import AutoEst
114
+
115
+ # Simple 4-cycle where all nodes are symmetric under rotation/reflection
116
+ G = nx.cycle_graph(4)
117
+
118
+ est = AutoEst(G, node_attrs=[], edge_attrs=[])
119
+ est = est.fit()
120
+
121
+ print(est.orbits)
122
+ # [frozenset({0, 1, 2, 3})]
123
+
124
+ print(est.n_orbits)
125
+ # 1
126
+
127
+ # "components style": connected components of an induced subgraph
128
+ comps = est.components(nodes=[0, 1])
129
+ print(comps)
130
+ # [frozenset({0, 1})]
131
+
132
+ # orbit-quotient components of an induced subgraph
133
+ oc = est.orbit_components(nodes=[0, 1, 2, 3])
134
+ print(oc)
135
+ # [frozenset({0})] # one orbit-id component in this symmetric case
136
+ """
137
+
138
+ _DEF_NODE_ATTRS: Tuple[str, ...] = ("element", "charge")
139
+ _DEF_EDGE_ATTRS: Tuple[str, ...] = ("order",)
140
+
141
+ def __init__(
142
+ self,
143
+ graph: nx.Graph,
144
+ node_attrs: Optional[List[str]] = None,
145
+ edge_attrs: Optional[List[str]] = None,
146
+ max_iter: int = 10,
147
+ ) -> None:
148
+ self._graph: nx.Graph = graph
149
+
150
+ cfg = _WLConfig(
151
+ node_attrs=(
152
+ tuple(node_attrs) if node_attrs is not None else self._DEF_NODE_ATTRS
153
+ ),
154
+ edge_attrs=(
155
+ tuple(edge_attrs) if edge_attrs is not None else self._DEF_EDGE_ATTRS
156
+ ),
157
+ max_iter=int(max_iter),
158
+ )
159
+ self._cfg: _WLConfig = cfg
160
+
161
+ self._colors: Dict[Hashable, int] = {}
162
+ self._orbits: List[FrozenSet[Hashable]] = []
163
+ self._orbit_index: Optional[Dict[Hashable, int]] = None
164
+ self._fitted: bool = False
165
+
166
+ # ------------------------------------------------------------------ #
167
+ # Public API: properties
168
+ # ------------------------------------------------------------------ #
169
+ @property
170
+ def graph(self) -> nx.Graph:
171
+ """Underlying graph.
172
+
173
+ :return: Graph passed to the constructor.
174
+ :rtype: nx.Graph
175
+ """
176
+ return self._graph
177
+
178
+ @property
179
+ def node_attrs(self) -> Tuple[str, ...]:
180
+ """Node attribute keys used in WL initialization.
181
+
182
+ :return: Node-attribute keys.
183
+ :rtype: tuple[str, ...]
184
+ """
185
+ return self._cfg.node_attrs
186
+
187
+ @property
188
+ def edge_attrs(self) -> Tuple[str, ...]:
189
+ """Edge attribute keys used in WL refinement.
190
+
191
+ :return: Edge-attribute keys.
192
+ :rtype: tuple[str, ...]
193
+ """
194
+ return self._cfg.edge_attrs
195
+
196
+ @property
197
+ def max_iter(self) -> int:
198
+ """Maximum number of WL refinement iterations.
199
+
200
+ :return: Maximum refinement iterations.
201
+ :rtype: int
202
+ """
203
+ return self._cfg.max_iter
204
+
205
+ @property
206
+ def anchor_component(self) -> FrozenSet[Hashable]:
207
+ """Largest connected component of the fitted graph.
208
+
209
+ This is a convenience “components-style” accessor. It is commonly used as
210
+ an anchor set for match pruning and symmetry breaking.
211
+
212
+ :return: The node-set of the largest connected component. If multiple
213
+ components share the maximum size, the one with the smallest
214
+ (sorted) node is returned for determinism.
215
+ :rtype: frozenset[hashable]
216
+
217
+ :raises RuntimeError: If :meth:`fit` has not been called.
218
+ """
219
+ self._ensure_fitted()
220
+ comps = self._components_on_induced(nodes=None)
221
+ if not comps:
222
+ return frozenset()
223
+
224
+ # Deterministic tie-break: size desc, then smallest node
225
+ comps_sorted = sorted(
226
+ comps,
227
+ key=lambda c: (-len(c), min((repr(node) for node in c), default="")),
228
+ )
229
+ return comps_sorted[0]
230
+
231
+ # ------------------------------------------------------------------ #
232
+ # Fitting and results
233
+ # ------------------------------------------------------------------ #
234
+ def fit(self) -> AutoEst:
235
+ """Run WL-1 refinement and compute approximate orbits.
236
+
237
+ :return: The fitted estimator (``self``).
238
+ :rtype: AutoEst
239
+ """
240
+ self._initialize_colors()
241
+ self._refine_colors()
242
+ self._build_orbits()
243
+ self._orbit_index = None
244
+ self._fitted = True
245
+ return self
246
+
247
+ @property
248
+ def node_colors(self) -> Dict[Hashable, int]:
249
+ """Node-to-color mapping after refinement.
250
+
251
+ :return: Mapping node -> WL color id.
252
+ :rtype: dict[hashable, int]
253
+ """
254
+ self._ensure_fitted()
255
+ return dict(self._colors)
256
+
257
+ @property
258
+ def orbits(self) -> List[FrozenSet[Hashable]]:
259
+ """WL-equivalence classes (approximate automorphism orbits).
260
+
261
+ :return: List of frozensets, each representing an orbit.
262
+ :rtype: list[frozenset[hashable]]
263
+ """
264
+ self._ensure_fitted()
265
+ return list(self._orbits)
266
+
267
+ @property
268
+ def groups(self) -> List[List[Hashable]]:
269
+ """Orbits represented as sorted lists.
270
+
271
+ :return: List of sorted node lists.
272
+ :rtype: list[list[hashable]]
273
+ """
274
+ self._ensure_fitted()
275
+ out: List[List[Hashable]] = []
276
+ for orb in self._orbits:
277
+ out.append(sorted(orb, key=repr))
278
+ out.sort(key=lambda g: (len(g), repr(g[0]) if g else ""))
279
+ return out
280
+
281
+ @property
282
+ def orbit_index(self) -> Dict[Hashable, int]:
283
+ """Map each node to its orbit id.
284
+
285
+ :return: Mapping node -> orbit_id.
286
+ :rtype: dict[hashable, int]
287
+ """
288
+ self._ensure_fitted()
289
+ if self._orbit_index is None:
290
+ self._orbit_index = self._build_orbit_index()
291
+ return dict(self._orbit_index)
292
+
293
+ @property
294
+ def n_orbits(self) -> int:
295
+ """Number of approximate orbits.
296
+
297
+ :return: Number of orbits.
298
+ :rtype: int
299
+ """
300
+ self._ensure_fitted()
301
+ return len(self._orbits)
302
+
303
+ @property
304
+ def n_groups(self) -> int:
305
+ """Alias for :attr:`n_orbits`.
306
+
307
+ :return: Number of orbits.
308
+ :rtype: int
309
+ """
310
+ return self.n_orbits
311
+
312
+ def __len__(self) -> int:
313
+ """Number of orbits (0 if not fitted).
314
+
315
+ :return: Orbit count or 0.
316
+ :rtype: int
317
+ """
318
+ return self.n_orbits if self._fitted else 0
319
+
320
+ def __repr__(self) -> str:
321
+ """Summary representation.
322
+
323
+ :return: Debug-friendly repr string.
324
+ :rtype: str
325
+ """
326
+ n_nodes = self._graph.number_of_nodes()
327
+ n_orb: object = len(self) if self._fitted else "?"
328
+ return (
329
+ f"<AutoEst | nodes={n_nodes} "
330
+ f"orbits={n_orb} approx='WL-1' max_iter={self._cfg.max_iter}>"
331
+ )
332
+
333
+ # ------------------------------------------------------------------ #
334
+ # "Components style" (similar spirit to Automorphism)
335
+ # ------------------------------------------------------------------ #
336
+ def components(
337
+ self, nodes: Optional[Iterable[Hashable]] = None
338
+ ) -> List[FrozenSet[Hashable]]:
339
+ """Compute connected components on an induced subgraph.
340
+
341
+ This mirrors the "components" utilities you used around Automorphism.
342
+
343
+ :param nodes: Subset of nodes to induce. If None, uses all nodes.
344
+ :type nodes: iterable[hashable] or None
345
+
346
+ :return: Connected components as frozensets (deterministic order).
347
+ :rtype: list[frozenset[hashable]]
348
+
349
+ :raises RuntimeError: If not fitted.
350
+ """
351
+ self._ensure_fitted()
352
+ return self._components_on_induced(nodes)
353
+
354
+ def orbit_components(
355
+ self, nodes: Optional[Iterable[Hashable]] = None
356
+ ) -> List[FrozenSet[int]]:
357
+ """Components of the orbit-quotient graph restricted to an induced subgraph.
358
+
359
+ - First restrict to `nodes` (or all nodes).
360
+ - Collapse nodes to their orbit ids.
361
+ - Build orbit-quotient adjacency based on edges between orbits.
362
+ - Return connected components of orbit ids.
363
+
364
+ This is useful when you have an "anchor component" defined as a set of
365
+ pattern nodes and want to treat coupled orbits as a single unit.
366
+
367
+ :param nodes: Subset of nodes. If None, uses all nodes.
368
+ :type nodes: iterable[hashable] or None
369
+
370
+ :return: List of connected components in orbit-id space.
371
+ :rtype: list[frozenset[int]]
372
+
373
+ :raises RuntimeError: If not fitted.
374
+ """
375
+ self._ensure_fitted()
376
+ keep = self._normalize_nodes(nodes)
377
+ orbit_idx = self.orbit_index
378
+ q = self._orbit_quotient_graph(keep, orbit_idx)
379
+ comps = [frozenset(c) for c in nx.connected_components(q)]
380
+ return sorted(comps, key=lambda c: (len(c), min(map(repr, c))))
381
+
382
+ # ------------------------------------------------------------------ #
383
+ # Internal helpers
384
+ # ------------------------------------------------------------------ #
385
+ def _ensure_fitted(self) -> None:
386
+ """
387
+ Ensure estimator is fitted.
388
+
389
+ :raises RuntimeError: If not fitted.
390
+ """
391
+ if not self._fitted:
392
+ raise RuntimeError("Call 'fit()' before accessing results.")
393
+
394
+ def _initialize_colors(self) -> None:
395
+ """
396
+ Initialize node colors using degree and selected node attributes.
397
+ """
398
+ palette: Dict[Tuple[Any, ...], int] = {}
399
+ colors: Dict[Hashable, int] = {}
400
+ next_color = 0
401
+
402
+ for node in self._graph.nodes():
403
+ label = self._initial_label(node)
404
+ if label not in palette:
405
+ palette[label] = next_color
406
+ next_color += 1
407
+ colors[node] = palette[label]
408
+
409
+ self._colors = colors
410
+
411
+ def _initial_label(self, node: Hashable) -> Tuple[Any, ...]:
412
+ """Build initial label.
413
+
414
+ :param node: Node id.
415
+ :type node: hashable
416
+
417
+ :return: Tuple label (degree + attr values).
418
+ :rtype: tuple
419
+ """
420
+ degree = self._graph.degree(node)
421
+ attrs = self._graph.nodes[node]
422
+ vals = [attrs.get(k) for k in self._cfg.node_attrs]
423
+ return (degree, *vals)
424
+
425
+ def _refine_colors(self) -> None:
426
+ """
427
+ Iterate WL refinement until convergence or max_iter.
428
+ """
429
+ for _ in range(self._cfg.max_iter):
430
+ new_colors, changed = self._refine_once()
431
+ self._colors = new_colors
432
+ if not changed:
433
+ break
434
+
435
+ def _refine_once(self) -> Tuple[Dict[Hashable, int], bool]:
436
+ """Single WL sweep.
437
+
438
+ :return: (new_colors, changed)
439
+ :rtype: tuple[dict[hashable, int], bool]
440
+ """
441
+ palette: Dict[Tuple[Any, ...], int] = {}
442
+ new_colors: Dict[Hashable, int] = {}
443
+ next_color = 0
444
+ changed = False
445
+
446
+ for node in self._graph.nodes():
447
+ label = self._refined_label(node)
448
+ if label not in palette:
449
+ palette[label] = next_color
450
+ next_color += 1
451
+ c = palette[label]
452
+ new_colors[node] = c
453
+ if c != self._colors.get(node):
454
+ changed = True
455
+
456
+ return new_colors, changed
457
+
458
+ def _refined_label(self, node: Hashable) -> Tuple[Any, ...]:
459
+ """Combine current color with sorted neighbor signatures.
460
+
461
+ :param node: Node id.
462
+ :type node: hashable
463
+
464
+ :return: Refined label.
465
+ :rtype: tuple
466
+ """
467
+ base = self._colors[node]
468
+ sigs: List[Tuple[Any, ...]] = []
469
+
470
+ for nbr in self._graph.neighbors(node):
471
+ sigs.append(self._neighbor_signature(node, nbr))
472
+
473
+ sigs.sort()
474
+ return (base, tuple(sigs))
475
+
476
+ def _neighbor_signature(
477
+ self, node: Hashable, neighbor: Hashable
478
+ ) -> Tuple[Any, ...]:
479
+ """Neighbor signature: (neighbor_color, edge_attr_1, ...).
480
+
481
+ :param node: Central node.
482
+ :type node: hashable
483
+ :param neighbor: Neighbor node.
484
+ :type neighbor: hashable
485
+
486
+ :return: Neighbor signature.
487
+ :rtype: tuple
488
+ """
489
+ edge_data = self._graph.get_edge_data(node, neighbor, default={})
490
+ edge_vals = [edge_data.get(k) for k in self._cfg.edge_attrs]
491
+ return (self._colors[neighbor], *edge_vals)
492
+
493
+ def _build_orbits(self) -> None:
494
+ """
495
+ Group nodes by final colors.
496
+ """
497
+ color_to_nodes: Dict[int, List[Hashable]] = {}
498
+ for node, color in self._colors.items():
499
+ color_to_nodes.setdefault(color, []).append(node)
500
+
501
+ orbits = [frozenset(v) for v in color_to_nodes.values()]
502
+ self._orbits = sorted(
503
+ orbits,
504
+ key=lambda orbit: (len(orbit), min(map(repr, orbit))),
505
+ )
506
+
507
+ def _build_orbit_index(self) -> Dict[Hashable, int]:
508
+ """Build node -> orbit id mapping.
509
+
510
+ :return: Orbit index.
511
+ :rtype: dict[hashable, int]
512
+ """
513
+ idx: Dict[Hashable, int] = {}
514
+ for i, orb in enumerate(self._orbits):
515
+ for n in orb:
516
+ idx[n] = i
517
+ return idx
518
+
519
+ def _normalize_nodes(
520
+ self, nodes: Optional[Iterable[Hashable]]
521
+ ) -> FrozenSet[Hashable]:
522
+ """Normalize subset nodes.
523
+
524
+ :param nodes: Subset or None.
525
+ :type nodes: iterable[hashable] or None
526
+
527
+ :return: Frozenset of nodes (validated).
528
+ :rtype: frozenset[hashable]
529
+ """
530
+ if nodes is None:
531
+ return frozenset(self._graph.nodes())
532
+ keep = frozenset(nodes)
533
+ unknown = [n for n in keep if n not in self._graph]
534
+ if unknown:
535
+ raise ValueError(f"Unknown nodes in subset: {unknown}")
536
+ return keep
537
+
538
+ def _components_on_induced(
539
+ self, nodes: Optional[Iterable[Hashable]]
540
+ ) -> List[FrozenSet[Hashable]]:
541
+ """Connected components on induced subgraph.
542
+
543
+ :param nodes: Subset or None.
544
+ :type nodes: iterable[hashable] or None
545
+
546
+ :return: Components in deterministic order.
547
+ :rtype: list[frozenset[hashable]]
548
+ """
549
+ keep = self._normalize_nodes(nodes)
550
+ sub = self._graph.subgraph(keep)
551
+ component_nodes = (
552
+ nx.weakly_connected_components(sub)
553
+ if sub.is_directed()
554
+ else nx.connected_components(sub)
555
+ )
556
+ comps = [frozenset(component) for component in component_nodes]
557
+ return sorted(comps, key=lambda c: (len(c), min(map(repr, c))))
558
+
559
+ def _orbit_quotient_graph(
560
+ self,
561
+ keep_nodes: FrozenSet[Hashable],
562
+ orbit_idx: Dict[Hashable, int],
563
+ ) -> nx.Graph:
564
+ """Build orbit quotient graph restricted to keep_nodes.
565
+
566
+ :param keep_nodes: Nodes to keep.
567
+ :type keep_nodes: frozenset[hashable]
568
+ :param orbit_idx: Node->orbit id.
569
+ :type orbit_idx: dict[hashable, int]
570
+
571
+ :return: Quotient graph in orbit-id space.
572
+ :rtype: nx.Graph
573
+ """
574
+ q = nx.Graph()
575
+ for n in keep_nodes:
576
+ q.add_node(orbit_idx[n])
577
+
578
+ for u, v in self._graph.edges():
579
+ if u not in keep_nodes or v not in keep_nodes:
580
+ continue
581
+ ou = orbit_idx[u]
582
+ ov = orbit_idx[v]
583
+ if ou != ov:
584
+ q.add_edge(ou, ov)
585
+
586
+ return q
587
+
588
+ def _validate_host_nodes_in_mappings(
589
+ self,
590
+ mappings: List[Mapping[Hashable, Hashable]],
591
+ orbit_idx: Dict[Hashable, int],
592
+ ) -> None:
593
+ """
594
+ Validate all host nodes are known.
595
+
596
+ :param mappings: Mappings list.
597
+ :type mappings: list[Mapping[hashable, hashable]]
598
+ :param orbit_idx: Node->orbit id.
599
+ :type orbit_idx: dict[hashable, int]
600
+
601
+ :raises ValueError: If unknown nodes appear.
602
+ """
603
+ missing: List[Hashable] = []
604
+ for mp in mappings:
605
+ for h in mp.values():
606
+ if h not in orbit_idx:
607
+ missing.append(h)
608
+ if missing:
609
+ raise ValueError(
610
+ "Host nodes in mappings not present in fitted graph: "
611
+ f"{sorted(set(missing), key=repr)}"
612
+ )
613
+
614
+ def _dedup_by_signature(
615
+ self,
616
+ mappings: List[Mapping[Hashable, Hashable]],
617
+ sig_fn: Any,
618
+ ) -> List[Mapping[Hashable, Hashable]]:
619
+ """Deduplicate by computed signature (stable representative selection).
620
+
621
+ :param mappings: List of mappings.
622
+ :type mappings: list[Mapping[hashable, hashable]]
623
+ :param sig_fn: Function mapping mapping->signature.
624
+ :type sig_fn: callable
625
+
626
+ :return: Deduplicated list.
627
+ :rtype: list[Mapping[hashable, hashable]]
628
+ """
629
+ seen = set()
630
+ out: List[Mapping[Hashable, Hashable]] = []
631
+ for mapping in mappings:
632
+ signature = sig_fn(mapping)
633
+ if signature in seen:
634
+ continue
635
+ seen.add(signature)
636
+ out.append(mapping)
637
+ return out
638
+
639
+ def _sort_orbits(
640
+ self, pattern_orbits: Iterable[FrozenSet[Hashable]]
641
+ ) -> Tuple[Tuple[Hashable, ...], ...]:
642
+ """Deterministically sort orbits.
643
+
644
+ :param pattern_orbits: Orbits.
645
+ :type pattern_orbits: iterable[frozenset[hashable]]
646
+
647
+ :return: Tuple of sorted orbit tuples.
648
+ :rtype: tuple[tuple[hashable, ...], ...]
649
+ """
650
+ orbs = [tuple(sorted(o, key=repr)) for o in pattern_orbits]
651
+ orbs.sort(key=lambda orbit: (len(orbit), repr(orbit[0]) if orbit else ""))
652
+ return tuple(orbs)
653
+
654
+ def _dedup_pattern_orbits_no_anchor(
655
+ self,
656
+ mappings: List[Mapping[Hashable, Hashable]],
657
+ orbits: Tuple[Tuple[Hashable, ...], ...],
658
+ ) -> List[Mapping[Hashable, Hashable]]:
659
+ """Pattern-orbit dedup without anchor.
660
+
661
+ :param mappings: Mappings.
662
+ :type mappings: list[Mapping[hashable, hashable]]
663
+ :param orbits: Sorted orbit tuples.
664
+ :type orbits: tuple[tuple[hashable, ...], ...]
665
+
666
+ :return: Deduplicated mappings.
667
+ :rtype: list[Mapping[hashable, hashable]]
668
+ """
669
+
670
+ def _sig(m: Mapping[Hashable, Hashable]) -> Tuple[Tuple[Hashable, ...], ...]:
671
+ return tuple(tuple(sorted((m[p] for p in orb), key=repr)) for orb in orbits)
672
+
673
+ return self._dedup_by_signature(mappings, _sig)
674
+
675
+ def _dedup_pattern_orbits_with_anchor(
676
+ self,
677
+ mappings: List[Mapping[Hashable, Hashable]],
678
+ orbits: Tuple[Tuple[Hashable, ...], ...],
679
+ anchor: FrozenSet[Hashable],
680
+ ) -> List[Mapping[Hashable, Hashable]]:
681
+ """Pattern-orbit dedup with anchor (no pruning inside anchor nodes).
682
+
683
+ :param mappings: Mappings.
684
+ :type mappings: list[Mapping[hashable, hashable]]
685
+ :param orbits: Sorted orbit tuples.
686
+ :type orbits: tuple[tuple[hashable, ...], ...]
687
+ :param anchor: Anchor nodes.
688
+ :type anchor: frozenset[hashable]
689
+
690
+ :return: Deduplicated mappings.
691
+ :rtype: list[Mapping[hashable, hashable]]
692
+ """
693
+ anchor_nodes = tuple(sorted(anchor, key=repr))
694
+ free_orbits = tuple(orb for orb in orbits if not (set(orb) & anchor))
695
+
696
+ def _sig(m: Mapping[Hashable, Hashable]) -> Tuple[Any, ...]:
697
+ free_part = tuple(
698
+ tuple(sorted((m[p] for p in orb), key=repr)) for orb in free_orbits
699
+ )
700
+ anchor_part = tuple(m[p] for p in anchor_nodes)
701
+ return (free_part, anchor_part)
702
+
703
+ return self._dedup_by_signature(mappings, _sig)
704
+
705
+
706
+ # --------------------------------------------------------------------------- #
707
+ # Convenience function (kept minimal)
708
+ # --------------------------------------------------------------------------- #
709
+ def estimate_automorphism_groups(
710
+ graph: nx.Graph,
711
+ node_attrs: Optional[Iterable[str]] = None,
712
+ edge_attrs: Optional[Iterable[str]] = None,
713
+ max_iter: int = 10,
714
+ ) -> AutoEst:
715
+ """Convenience function to fit :class:`AutoEst`.
716
+
717
+ :param graph: Input NetworkX graph.
718
+ :type graph: nx.Graph
719
+ :param node_attrs: Node attribute keys to include in WL initialization.
720
+ :type node_attrs: iterable[str] or None
721
+ :param edge_attrs: Edge attribute keys to include in neighbor signatures.
722
+ :type edge_attrs: iterable[str] or None
723
+ :param max_iter: Maximum WL iterations.
724
+ :type max_iter: int
725
+
726
+ :return: Fitted estimator.
727
+ :rtype: AutoEst
728
+ """
729
+ node_list = list(node_attrs) if node_attrs is not None else None
730
+ edge_list = list(edge_attrs) if edge_attrs is not None else None
731
+ return AutoEst(
732
+ graph=graph,
733
+ node_attrs=node_list,
734
+ edge_attrs=edge_list,
735
+ max_iter=max_iter,
736
+ ).fit()