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,804 @@
1
+ """Common and maximum-common-subgraph matching for molecular graphs.
2
+
3
+ The matcher supports configurable node and edge attributes, optional wildcard
4
+ and automorphism pruning, connected-component matching, and reaction-centre
5
+ side selection. Results are cached and can be returned in either graph
6
+ orientation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import itertools
12
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple
13
+
14
+ import networkx as nx
15
+ from networkx.algorithms.isomorphism import GraphMatcher, generic_node_match
16
+
17
+ try:
18
+ from synkit.Graph.ITS import its_decompose # optional
19
+ except ImportError: # pragma: no cover – allow standalone use
20
+ its_decompose = None # type: ignore[assignment]
21
+
22
+ __all__ = ["MCSMatcher"]
23
+
24
+ MappingDict = Dict[int, int]
25
+
26
+
27
+ class MCSMatcher:
28
+ """
29
+ Common / maximum-common subgraph matcher.
30
+
31
+ This class wraps :class:`networkx.algorithms.isomorphism.GraphMatcher`
32
+ to provide higher-level utilities for computing sets of common subgraphs
33
+ between two graphs, with a focus on molecular graphs (atoms/bonds).
34
+
35
+ Node matching is controlled via :func:`generic_node_match` using one or
36
+ more attribute names and default values. Edge matching compares one or
37
+ more scalar edge attributes (e.g. bond order) specified in
38
+ ``edge_attrs``.
39
+
40
+ Mappings and orientation
41
+ ------------------------
42
+ Internally, mappings are always stored as **pattern → host**, where
43
+ the *pattern* is the smaller of the two (after optional wildcard
44
+ pruning). The helper :py:meth:`get_mappings` can convert these to
45
+ ``G1→G2`` or ``G2→G1`` orientation as needed, and the property
46
+ :attr:`mapping_direction` exposes which graph acted as the pattern.
47
+
48
+ Optional wildcard pruning
49
+ -------------------------
50
+ If ``prune_wc`` is ``True``, nodes with
51
+ ``attrs[element_key] == wildcard_element`` are removed from both input
52
+ graphs **non-inplace** before the MCS search. Node labels are preserved,
53
+ so the resulting mappings still reference the original node ids.
54
+
55
+ Optional automorphism pruning
56
+ -----------------------------
57
+ If ``prune_automorphisms`` is ``True``, mappings that induce
58
+ the same **host node set** (i.e. same image in the host graph) are
59
+ collapsed. This is especially useful for highly symmetric hosts (rings,
60
+ repeated subunits, etc.) where many mappings are equivalent at the level
61
+ of “which part of the host is covered”.
62
+
63
+ :param node_attrs: Node attribute keys to compare. If ``None``,
64
+ defaults to ``["element"]``.
65
+ :type node_attrs: list[str] | None
66
+ :param node_defaults: Fallback values for each node attribute
67
+ when missing. If ``None``, defaults to a list of ``"*"``
68
+ of the same length as ``node_attrs``.
69
+ :type node_defaults: list[Any] | None
70
+ :param allow_shift: Placeholder for future asymmetric rules. Currently
71
+ unused but kept for API compatibility.
72
+ :type allow_shift: bool
73
+ :param edge_attrs: Edge attribute keys to use for scalar comparison
74
+ (e.g. ``["order"]`` or ``["order", "standard_order"]``). If
75
+ ``None``, defaults to ``["order"]``.
76
+ :type edge_attrs: list[str] | None
77
+ :param prune_wc: If ``True``, strip wildcard nodes (see
78
+ ``wildcard_element``, ``element_key``) from both
79
+ graphs before searching.
80
+ :type prune_wc: bool
81
+ :param prune_automorphisms: If ``True``, collapse mappings that have
82
+ the same host node set (automorphism pruning).
83
+ :type prune_automorphisms: bool
84
+ :param wildcard_element: Attribute value denoting wildcard nodes
85
+ (typically ``"*"``, used together with ``element_key``).
86
+ :type wildcard_element: Any
87
+ :param element_key: Node attribute key used to detect wildcard nodes
88
+ when ``prune_wc`` is ``True``.
89
+ :type element_key: str
90
+ """
91
+
92
+ # ------------------------------------------------------------------
93
+ # Construction
94
+ # ------------------------------------------------------------------
95
+ def __init__(
96
+ self,
97
+ node_attrs: Optional[List[str]] = None,
98
+ node_defaults: Optional[List[Any]] = None,
99
+ allow_shift: bool = True,
100
+ *,
101
+ edge_attrs: Optional[List[str]] = None,
102
+ prune_wc: bool = False,
103
+ prune_automorphisms: bool = False,
104
+ wildcard_element: Any = "*",
105
+ element_key: str = "element",
106
+ ) -> None:
107
+ if node_attrs is None:
108
+ node_attrs = ["element"]
109
+ if node_defaults is None:
110
+ node_defaults = ["*"] * len(node_attrs)
111
+ if len(node_defaults) != len(node_attrs):
112
+ raise ValueError(
113
+ "MCSMatcher: node_defaults must have the same length as node_attrs."
114
+ )
115
+
116
+ self._node_attrs: List[str] = node_attrs
117
+ self._node_defaults: List[Any] = node_defaults
118
+ self._edge_attrs: List[str] = (
119
+ ["order"] if edge_attrs is None else list(edge_attrs)
120
+ )
121
+ self.allow_shift: bool = allow_shift
122
+
123
+ self.prune_wc: bool = prune_wc
124
+ self.prune_automorphisms: bool = prune_automorphisms
125
+ self.wildcard_element: Any = wildcard_element
126
+ self.element_key: str = element_key
127
+
128
+ comparators: List[Callable[[Any, Any], bool]] = [
129
+ (lambda x, y: x == y) for _ in node_attrs
130
+ ]
131
+ self.node_match: Callable[[Dict[str, Any], Dict[str, Any]], bool] = (
132
+ generic_node_match(
133
+ node_attrs,
134
+ node_defaults,
135
+ comparators,
136
+ )
137
+ )
138
+
139
+ # Internal cache
140
+ self._mappings: List[MappingDict] = [] # pattern -> host
141
+ self._last_size: int = 0
142
+ self._last_pattern_is_G1: Optional[bool] = None # None until a search runs
143
+
144
+ # ------------------------------------------------------------------
145
+ # Internal edge / mapping helpers
146
+ # ------------------------------------------------------------------
147
+ def _edge_match(
148
+ self,
149
+ host_attrs: Dict[str, Any],
150
+ pat_attrs: Dict[str, Any],
151
+ ) -> bool:
152
+ """Compare edge attributes listed in :attr:`_edge_attrs`.
153
+
154
+ For each name in :attr:`_edge_attrs`:
155
+
156
+ * If both values can be cast to ``float``, use numeric equality.
157
+ * Otherwise, fall back to direct ``==`` comparison.
158
+ * Missing values on both sides are ignored.
159
+
160
+ :param host_attrs: Edge attribute dictionary from host graph.
161
+ :type host_attrs: dict[str, Any]
162
+ :param pat_attrs: Edge attribute dictionary from pattern graph.
163
+ :type pat_attrs: dict[str, Any]
164
+ :return: ``True`` if all configured edge attributes match, otherwise
165
+ ``False``.
166
+ :rtype: bool
167
+ """
168
+ for name in self._edge_attrs:
169
+ hv = host_attrs.get(name, None)
170
+ pv = pat_attrs.get(name, None)
171
+ if hv is None and pv is None:
172
+ continue
173
+ try:
174
+ if float(hv) != float(pv):
175
+ return False
176
+ except (TypeError, ValueError):
177
+ if hv != pv:
178
+ return False
179
+ return True
180
+
181
+ @staticmethod
182
+ def _validate_graph_pair(G1: nx.Graph, G2: nx.Graph) -> None:
183
+ if G1.is_directed() != G2.is_directed():
184
+ raise ValueError("MCSMatcher requires graphs with the same directedness")
185
+ if G1.is_multigraph() or G2.is_multigraph():
186
+ raise NotImplementedError("MCSMatcher does not support multigraphs")
187
+
188
+ @staticmethod
189
+ def _component_nodes(graph: nx.Graph):
190
+ return (
191
+ nx.weakly_connected_components(graph)
192
+ if graph.is_directed()
193
+ else nx.connected_components(graph)
194
+ )
195
+
196
+ @staticmethod
197
+ def _matcher_class(graph: nx.Graph):
198
+ return (
199
+ nx.algorithms.isomorphism.DiGraphMatcher
200
+ if graph.is_directed()
201
+ else GraphMatcher
202
+ )
203
+
204
+ @staticmethod
205
+ def _invert_mapping(gm_mapping: MappingDict) -> MappingDict:
206
+ """Convert *host→pattern* dict to *pattern→host*.
207
+
208
+ :param gm_mapping: Mapping from host nodes to pattern nodes.
209
+ :type gm_mapping: dict[int, int]
210
+ :return: Mapping from pattern nodes to host nodes.
211
+ :rtype: dict[int, int]
212
+ """
213
+ return {pat: host for host, pat in gm_mapping.items()}
214
+
215
+ def _prune_graph(self, G: nx.Graph) -> nx.Graph:
216
+ """Return a version of ``G`` with wildcard nodes removed if needed.
217
+
218
+ This method does **not** mutate the input graph. When
219
+ :attr:`prune_wc` is ``False``, it simply returns ``G`` as-is.
220
+
221
+ :param G: Input graph.
222
+ :type G: nx.Graph
223
+ :return: Possibly pruned graph (wildcard nodes removed).
224
+ :rtype: nx.Graph
225
+ """
226
+ if not self.prune_wc:
227
+ return G
228
+
229
+ key = self.element_key
230
+ wc = self.wildcard_element
231
+ keep_nodes = [n for n, data in G.nodes(data=True) if data.get(key) != wc]
232
+ return G.subgraph(keep_nodes).copy()
233
+
234
+ def _componentwise_mcs(
235
+ self,
236
+ G1: nx.Graph,
237
+ G2: nx.Graph,
238
+ *,
239
+ mcs: bool,
240
+ ) -> MappingDict:
241
+ """Perform size-sorted, component-wise MCS between ``G1`` and ``G2``.
242
+
243
+ The graphs are decomposed into connected components, which are
244
+ sorted by size (descending). Components are then matched in
245
+ order (largest with largest, etc.) using the internal subgraph-
246
+ search machinery.
247
+
248
+ The combined mapping is always reported as **G1 → G2** in terms
249
+ of the original node ids; components that fail to yield any
250
+ mapping are simply skipped.
251
+
252
+ :param G1: First input graph.
253
+ :type G1: nx.Graph
254
+ :param G2: Second input graph.
255
+ :type G2: nx.Graph
256
+ :param mcs: If ``True``, restrict each component pair to
257
+ maximum-common-subgraph mappings.
258
+ :type mcs: bool
259
+ :return: Combined mapping from nodes of ``G1`` to nodes of
260
+ ``G2``.
261
+ :rtype: dict[int, int]
262
+ """
263
+ comps1 = [G1.subgraph(c).copy() for c in self._component_nodes(G1)]
264
+ comps2 = [G2.subgraph(c).copy() for c in self._component_nodes(G2)]
265
+
266
+ comps1.sort(key=lambda g: g.number_of_nodes(), reverse=True)
267
+ comps2.sort(key=lambda g: g.number_of_nodes(), reverse=True)
268
+
269
+ limit = min(len(comps1), len(comps2))
270
+ combined: MappingDict = {}
271
+
272
+ for idx in range(limit):
273
+ sub1 = comps1[idx]
274
+ sub2 = comps2[idx]
275
+
276
+ pattern, host, pattern_is_G1 = self._prepare_orientation(sub1, sub2)
277
+ local_maps = self._search_subgraphs(pattern, host, mcs=mcs)
278
+ if not local_maps:
279
+ continue
280
+
281
+ best = local_maps[0]
282
+ if pattern_is_G1:
283
+ # pattern is subgraph of G1 → host is subgraph of G2
284
+ combined.update(best)
285
+ else:
286
+ # pattern is subgraph of G2, host is subgraph of G1 → invert
287
+ combined.update(self._invert_mapping(best))
288
+
289
+ return combined
290
+
291
+ def _bipartite_assign_components(
292
+ self,
293
+ G1: nx.Graph,
294
+ G2: nx.Graph,
295
+ *,
296
+ mcs: bool,
297
+ ) -> MappingDict:
298
+ """Hungarian-optimal component pairing followed by per-pair MCS.
299
+
300
+ Builds an n₁×n₂ score matrix (MCS size for each component pair),
301
+ solves the linear assignment problem to maximise total overlap, then
302
+ runs the actual MCS for the assigned pairs and combines the mappings
303
+ into a single **G1 → G2** dict.
304
+
305
+ Falls back to :py:meth:`_componentwise_mcs` when scipy is not
306
+ available.
307
+
308
+ :param G1: First input graph (after wildcard pruning).
309
+ :type G1: nx.Graph
310
+ :param G2: Second input graph (after wildcard pruning).
311
+ :type G2: nx.Graph
312
+ :param mcs: If ``True``, restrict to maximum-common-subgraph
313
+ mappings within each assigned pair.
314
+ :type mcs: bool
315
+ :return: Combined mapping from nodes of ``G1`` to nodes of ``G2``.
316
+ :rtype: dict[int, int]
317
+ """
318
+ try:
319
+ import numpy as np
320
+ from scipy.optimize import linear_sum_assignment
321
+ except ImportError: # pragma: no cover
322
+ return self._componentwise_mcs(G1, G2, mcs=mcs)
323
+
324
+ comps1 = [G1.subgraph(c).copy() for c in self._component_nodes(G1)]
325
+ comps2 = [G2.subgraph(c).copy() for c in self._component_nodes(G2)]
326
+ n1, n2 = len(comps1), len(comps2)
327
+
328
+ # Build score matrix and cache maps in one pass to avoid re-running
329
+ score = np.zeros((n1, n2), dtype=float)
330
+ cache: Dict[Tuple[int, int], Tuple[List[MappingDict], bool]] = {}
331
+ for i, sub1 in enumerate(comps1):
332
+ for j, sub2 in enumerate(comps2):
333
+ pattern, host, pat_is_g1 = self._prepare_orientation(sub1, sub2)
334
+ maps = self._search_subgraphs(pattern, host, mcs=True)
335
+ score[i, j] = len(maps[0]) if maps else 0.0
336
+ cache[(i, j)] = (maps, pat_is_g1)
337
+
338
+ rows, cols = linear_sum_assignment(-score)
339
+ combined: MappingDict = {}
340
+ for i, j in zip(rows, cols):
341
+ if score[i, j] == 0.0:
342
+ continue
343
+ maps, pattern_is_g1 = cache[(i, j)]
344
+ if not maps:
345
+ continue
346
+ # Re-run with the caller's mcs flag if it differs from True
347
+ if not mcs:
348
+ sub1, sub2 = comps1[i], comps2[j]
349
+ pattern, host, pattern_is_g1 = self._prepare_orientation(sub1, sub2)
350
+ maps = self._search_subgraphs(pattern, host, mcs=False)
351
+ if not maps:
352
+ continue
353
+ best = maps[0]
354
+ if pattern_is_g1:
355
+ combined.update(best)
356
+ else:
357
+ combined.update(self._invert_mapping(best))
358
+
359
+ return combined
360
+
361
+ # ------------------------------------------------------------------
362
+ # Connected-component (molecule) level matching
363
+ # ------------------------------------------------------------------
364
+ def _find_mcs_mol(self, G1: nx.Graph, G2: nx.Graph) -> MappingDict:
365
+ """Match connected components of ``G1`` to ``G2`` of the same size.
366
+
367
+ Components are sorted by size (descending) and matched greedily.
368
+ For each component in ``G1``, the method looks for a component in
369
+ ``G2`` with the same size and an isomorphic mapping, combining
370
+ component-mappings into a single dictionary.
371
+
372
+ Direction: the returned mapping is **G1 → G2** in this mode.
373
+
374
+ :param G1: First graph (treated as source of components).
375
+ :type G1: nx.Graph
376
+ :param G2: Second graph (target for component mapping).
377
+ :type G2: nx.Graph
378
+ :return: Combined mapping from nodes of ``G1`` to nodes of ``G2``.
379
+ :rtype: dict[int, int]
380
+ """
381
+ comps1 = sorted(self._component_nodes(G1), key=len, reverse=True)
382
+ comps2 = sorted(self._component_nodes(G2), key=len, reverse=True)
383
+
384
+ used2: Set[frozenset[int]] = set()
385
+ combined: MappingDict = {}
386
+
387
+ for comp1 in comps1:
388
+ size = len(comp1)
389
+ sub1 = G1.subgraph(comp1)
390
+
391
+ for comp2 in comps2:
392
+ if len(comp2) != size:
393
+ continue
394
+ key2 = frozenset(comp2)
395
+ if key2 in used2:
396
+ continue
397
+
398
+ sub2 = G2.subgraph(comp2)
399
+ gm = self._matcher_class(sub1)(
400
+ sub1,
401
+ sub2,
402
+ node_match=self.node_match,
403
+ edge_match=self._edge_match,
404
+ )
405
+ if gm.is_isomorphic():
406
+ combined.update(gm.mapping)
407
+ used2.add(key2)
408
+ break
409
+
410
+ return combined
411
+
412
+ # ------------------------------------------------------------------
413
+ # Core subgraph search
414
+ # ------------------------------------------------------------------
415
+ def _prepare_orientation(
416
+ self,
417
+ G1: nx.Graph,
418
+ G2: nx.Graph,
419
+ ) -> Tuple[nx.Graph, nx.Graph, bool]:
420
+ """Ensure the smaller graph is used as pattern for efficiency.
421
+
422
+ :param G1: Original first graph.
423
+ :type G1: nx.Graph
424
+ :param G2: Original second graph.
425
+ :type G2: nx.Graph
426
+ :return: Tuple ``(pattern, host, pattern_is_G1)`` where
427
+ ``pattern_is_G1=True`` indicates that the pattern is ``G1``.
428
+ :rtype: tuple[nx.Graph, nx.Graph, bool]
429
+ """
430
+ if G1.number_of_nodes() <= G2.number_of_nodes():
431
+ return G1, G2, True
432
+ return G2, G1, False
433
+
434
+ def _search_subgraphs(
435
+ self,
436
+ pattern: nx.Graph,
437
+ host: nx.Graph,
438
+ *,
439
+ mcs: bool,
440
+ ) -> List[MappingDict]:
441
+ """Enumerate common subgraphs between ``pattern`` and ``host``.
442
+
443
+ The returned mappings always map **pattern nodes → host nodes**.
444
+
445
+ If :attr:`prune_automorphisms` is ``True``, mappings with the
446
+ same host-node set are collapsed.
447
+
448
+ :param pattern: Graph treated as pattern (smaller or equal).
449
+ :type pattern: nx.Graph
450
+ :param host: Graph treated as host (larger or equal).
451
+ :type host: nx.Graph
452
+ :param mcs: If ``True``, retain only maximum-size mappings.
453
+ :type mcs: bool
454
+ :return: List of mappings from pattern nodes to host nodes.
455
+ :rtype: list[MappingDict]
456
+ """
457
+ max_k = min(pattern.number_of_nodes(), host.number_of_nodes())
458
+ seen: Set[Tuple[Tuple[int, int], ...]] = set()
459
+ mappings: List[MappingDict] = []
460
+ best_size = 0
461
+
462
+ # For automorphism pruning: track host node sets we have already seen
463
+ host_sets_seen: Set[frozenset[Any]] = set()
464
+
465
+ for k in range(max_k, 0, -1):
466
+ if mcs and best_size and k < best_size:
467
+ break
468
+
469
+ level_found = False
470
+ for nodes in itertools.combinations(pattern.nodes(), k):
471
+ sub_pat = pattern.subgraph(nodes).copy()
472
+ gm = self._matcher_class(host)(
473
+ host,
474
+ sub_pat,
475
+ node_match=self.node_match,
476
+ edge_match=self._edge_match,
477
+ )
478
+ for iso in gm.subgraph_isomorphisms_iter():
479
+ inv = self._invert_mapping(iso) # pattern -> host
480
+ key = tuple(sorted(inv.items(), key=repr))
481
+ if key in seen:
482
+ continue
483
+ seen.add(key)
484
+
485
+ if self.prune_automorphisms:
486
+ host_set = frozenset(inv.values())
487
+ if host_set in host_sets_seen:
488
+ # Same image in host as an earlier mapping -> skip
489
+ continue
490
+ host_sets_seen.add(host_set)
491
+
492
+ mappings.append(inv)
493
+ level_found = True
494
+
495
+ if level_found:
496
+ best_size = k
497
+ if mcs:
498
+ break
499
+
500
+ if mcs and best_size:
501
+ mappings = [m for m in mappings if len(m) == best_size]
502
+
503
+ mappings.sort(
504
+ key=lambda mapping: (
505
+ -len(mapping),
506
+ repr(tuple(sorted(mapping.items(), key=repr))),
507
+ )
508
+ )
509
+ self._last_size = (
510
+ best_size if best_size else (len(mappings[0]) if mappings else 0)
511
+ )
512
+ return mappings
513
+
514
+ # ------------------------------------------------------------------
515
+ # Public search methods
516
+ # ------------------------------------------------------------------
517
+ def find_common_subgraph(
518
+ self,
519
+ G1: nx.Graph,
520
+ G2: nx.Graph,
521
+ *,
522
+ mcs: bool = False,
523
+ mcs_mol: bool = False,
524
+ ) -> "MCSMatcher":
525
+ """Search for common subgraphs between two graphs.
526
+
527
+ The results are cached in :attr:`mappings` and
528
+ :attr:`last_size`. The method returns ``self`` to enable a
529
+ fluent style.
530
+
531
+ If :attr:`prune_wc` is ``True``, wildcard nodes are stripped
532
+ (non-inplace) from both graphs before the search.
533
+
534
+ :param G1: First input graph.
535
+ :type G1: nx.Graph
536
+ :param G2: Second input graph.
537
+ :type G2: nx.Graph
538
+ :param mcs: If ``True``, restrict to maximum-common-subgraph
539
+ mappings (largest possible node count).
540
+ :type mcs: bool
541
+ :param mcs_mol: If ``True``, perform connected-component
542
+ (molecule-level) matching using :py:meth:`_find_mcs_mol`.
543
+ In this mode, ``mcs`` is ignored.
544
+ :type mcs_mol: bool
545
+ :return: The matcher instance (with internal cache updated).
546
+ :rtype: MCSMatcher
547
+ """
548
+ self._mappings = []
549
+ self._last_size = 0
550
+ self._last_pattern_is_G1 = None
551
+
552
+ self._validate_graph_pair(G1, G2)
553
+
554
+ G1_use = self._prune_graph(G1)
555
+ G2_use = self._prune_graph(G2)
556
+
557
+ if mcs_mol:
558
+ combined = self._find_mcs_mol(G1_use, G2_use)
559
+ self._mappings = [combined] # G1 -> G2
560
+ self._last_size = len(combined)
561
+ self._last_pattern_is_G1 = True
562
+ return self
563
+
564
+ pattern, host, pattern_is_G1 = self._prepare_orientation(G1_use, G2_use)
565
+ self._last_pattern_is_G1 = pattern_is_G1
566
+ self._mappings = self._search_subgraphs(pattern, host, mcs=mcs)
567
+
568
+ return self
569
+
570
+ def find_rc_mapping(
571
+ self,
572
+ rc1: Any,
573
+ rc2: Any,
574
+ *,
575
+ side: str = "op",
576
+ mcs: bool = True,
577
+ mcs_mol: bool = False,
578
+ component: bool = True,
579
+ ) -> "MCSMatcher":
580
+ """Convenience wrapper for ITS reaction-centre or ITS-like graph
581
+ objects.
582
+
583
+ Depending on ``side``, this either uses
584
+ :func:`synkit.Graph.ITS.its_decompose` to obtain left/right
585
+ graphs or treats the inputs directly as graphs.
586
+
587
+ Side selection
588
+ --------------
589
+ * ``'r'`` → compare right sides: ``r1`` vs ``r2``.
590
+ * ``'l'`` → compare left sides: ``l1`` vs ``l2``.
591
+ * ``'op'`` → compare opposite: ``r1`` vs ``l2``.
592
+ * ``'its'`` → treat ``rc1`` and ``rc2`` directly as graphs
593
+ (no decomposition), useful when the inputs are already ITS
594
+ (or ITS-like) :class:`networkx.Graph` objects.
595
+
596
+ Component-wise mode
597
+ -------------------
598
+ If ``component`` is ``True``, the selected graphs are
599
+ decomposed into connected components, sorted by size
600
+ (descending), and matched pairwise (largest with largest, etc.)
601
+ using a common-/maximum-common-subgraph search for each pair.
602
+ The resulting mappings are combined into a single **G1 → G2**
603
+ mapping in terms of the original node ids. In this mode,
604
+ ``mcs_mol`` is ignored.
605
+
606
+ :param rc1: First reaction-centre or ITS-like graph object.
607
+ :type rc1: Any
608
+ :param rc2: Second reaction-centre or ITS-like graph object.
609
+ :type rc2: Any
610
+ :param side: Which ITS sides to compare (``'r'``, ``'l'``,
611
+ ``'op'``, or ``'its'``).
612
+ :type side: str
613
+ :param mcs: If ``True``, restrict to maximum-common-subgraph
614
+ mappings (for the whole graph or per-component in
615
+ component-wise mode).
616
+ :type mcs: bool
617
+ :param mcs_mol: If ``True``, use connected-component matching
618
+ via :py:meth:`_find_mcs_mol`. Ignored if
619
+ ``component`` is ``True``.
620
+ :type mcs_mol: bool
621
+ :param component: If ``True``, perform size-sorted,
622
+ component-wise MCS between the selected sides and combine
623
+ the per-component mappings into a single mapping.
624
+ :type component: bool
625
+ :return: The matcher instance (with internal cache updated).
626
+ :rtype: MCSMatcher
627
+ :raises ImportError: If :mod:`synkit` ITS utilities are not
628
+ available for ``side`` in ``{'r', 'l', 'op'}``.
629
+ :raises ValueError: If ``side`` is not one of
630
+ ``'r'``, ``'l'``, ``'op'``, ``'its'``.
631
+ """
632
+ # reset cache
633
+ self._mappings = []
634
+ self._last_size = 0
635
+ self._last_pattern_is_G1 = None
636
+
637
+ side_norm = side.lower()
638
+
639
+ if side_norm == "its":
640
+ # Treat rc1 and rc2 directly as graphs
641
+ G1, G2 = rc1, rc2
642
+ else:
643
+ if its_decompose is None:
644
+ raise ImportError(
645
+ "synkit is not available; cannot decompose reaction centres "
646
+ "for side values 'r', 'l' or 'op'."
647
+ )
648
+
649
+ l1, r1 = its_decompose(rc1)
650
+ l2, r2 = its_decompose(rc2)
651
+
652
+ if side_norm == "r":
653
+ G1, G2 = r1, r2
654
+ elif side_norm == "l":
655
+ G1, G2 = l1, l2
656
+ elif side_norm == "op":
657
+ G1, G2 = r1, l2
658
+ else:
659
+ raise ValueError(
660
+ "MCSMatcher.find_rc_mapping: side must be one of "
661
+ "'r', 'l', 'op', 'its', got "
662
+ f"{side!r}."
663
+ )
664
+
665
+ if component:
666
+ self._validate_graph_pair(G1, G2)
667
+ G1_use = self._prune_graph(G1)
668
+ G2_use = self._prune_graph(G2)
669
+ combined = self._bipartite_assign_components(G1_use, G2_use, mcs=mcs)
670
+ self._mappings = [combined] # G1 -> G2
671
+ self._last_size = len(combined)
672
+ self._last_pattern_is_G1 = True
673
+ return self
674
+
675
+ return self.find_common_subgraph(G1, G2, mcs=mcs, mcs_mol=mcs_mol)
676
+
677
+ # ------------------------------------------------------------------
678
+ # Accessors / properties
679
+ # ------------------------------------------------------------------
680
+ def get_mappings(self, direction: str = "pattern_to_host") -> List[MappingDict]:
681
+ """Return a copy of the cached mapping list in the requested orientation.
682
+
683
+ Internal cache is **pattern → host** (where pattern is the smaller
684
+ graph after pruning). This method can convert to original
685
+ ``G1→G2`` or ``G2→G1`` orientation based on the last call to
686
+ :py:meth:`find_common_subgraph`.
687
+
688
+ :param direction: Orientation of the returned mappings. One of:
689
+ - ``"pattern_to_host"`` (default): internal orientation.
690
+ - ``"G1_to_G2"``: mapping from first input graph to second.
691
+ - ``"G2_to_G1"``: mapping from second input graph to first.
692
+ :type direction: str
693
+ :return: List of node-mapping dictionaries.
694
+ :rtype: list[dict[int, int]]
695
+ :raises ValueError: If ``direction`` is not supported.
696
+ """
697
+ if direction == "pattern_to_host" or self._last_pattern_is_G1 is None:
698
+ return [dict(m) for m in self._mappings]
699
+
700
+ if direction not in {"G1_to_G2", "G2_to_G1"}:
701
+ raise ValueError(
702
+ "MCSMatcher.get_mappings: direction must be one of "
703
+ "'pattern_to_host', 'G1_to_G2', 'G2_to_G1'."
704
+ )
705
+
706
+ pattern_is_G1 = self._last_pattern_is_G1
707
+ result: List[MappingDict] = []
708
+
709
+ for m in self._mappings:
710
+ if direction == "G1_to_G2":
711
+ if pattern_is_G1:
712
+ result.append(dict(m))
713
+ else:
714
+ result.append(self._invert_mapping(m))
715
+ else: # direction == "G2_to_G1"
716
+ if pattern_is_G1:
717
+ result.append(self._invert_mapping(m))
718
+ else:
719
+ result.append(dict(m))
720
+
721
+ return result
722
+
723
+ @property
724
+ def mappings(self) -> List[MappingDict]:
725
+ """Cached node mappings from the most recent search (pattern→host).
726
+
727
+ To obtain G1→G2 or G2→G1 orientation, use
728
+ :py:meth:`get_mappings` with ``direction='G1_to_G2'`` or
729
+ ``direction='G2_to_G1'``.
730
+
731
+ :return: List of node-mapping dictionaries (pattern→host).
732
+ :rtype: list[dict[int, int]]
733
+ """
734
+ return self.get_mappings(direction="pattern_to_host")
735
+
736
+ @property
737
+ def last_size(self) -> int:
738
+ """Number of nodes in the most recent maximum mapping set.
739
+
740
+ This is the size of the largest mapping found in the last call
741
+ to :py:meth:`find_common_subgraph` (or zero if no mappings
742
+ exist).
743
+
744
+ :return: Size of the largest mapping.
745
+ :rtype: int
746
+ """
747
+ return self._last_size
748
+
749
+ @property
750
+ def num_mappings(self) -> int:
751
+ """Number of mappings stored from the most recent search.
752
+
753
+ :return: Count of cached mappings.
754
+ :rtype: int
755
+ """
756
+ return len(self._mappings)
757
+
758
+ @property
759
+ def mapping_direction(self) -> str:
760
+ """Human-readable description of internal mapping orientation.
761
+
762
+ :return: ``"G1_to_G2"``, ``"G2_to_G1"``, or ``"unknown"`` if
763
+ no search has been run yet.
764
+ :rtype: str
765
+ """
766
+ if self._last_pattern_is_G1 is None:
767
+ return "unknown"
768
+ return "G1_to_G2" if self._last_pattern_is_G1 else "G2_to_G1"
769
+
770
+ # ------------------------------------------------------------------
771
+ # Iteration & niceties
772
+ # ------------------------------------------------------------------
773
+ def __iter__(self) -> Iterable[MappingDict]:
774
+ """Iterate over cached mappings in pattern→host orientation.
775
+
776
+ :return: Iterator over mapping dictionaries.
777
+ :rtype: Iterable[dict[int, int]]
778
+ """
779
+ return iter(self._mappings)
780
+
781
+ def __repr__(self) -> str:
782
+ """Short textual representation for debugging.
783
+
784
+ :return: Summary string with key attributes.
785
+ :rtype: str
786
+ """
787
+ return (
788
+ f"<MCSMatcher mappings={self.num_mappings} "
789
+ f"last_size={self.last_size} "
790
+ f"prune_wc={self.prune_wc} "
791
+ f"prune_automorphisms={self.prune_automorphisms} "
792
+ f"direction={self.mapping_direction}>"
793
+ )
794
+
795
+ __str__ = __repr__
796
+
797
+ @property
798
+ def help(self) -> str:
799
+ """Return the module-level documentation string.
800
+
801
+ :return: The full module docstring, if available.
802
+ :rtype: str
803
+ """
804
+ return __doc__ or ""