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,611 @@
1
+ from __future__ import annotations
2
+
3
+ """MTG – Mechanistic Transition Graph fusion utility.
4
+
5
+ This module exposes :class:`~MTG`, a helper that merges a chronological
6
+ sequence of **Intermediate Transition State** (ITS) graphs – or their RSMI
7
+ string representations – into a single *product* graph capturing the entire
8
+ bond-order history across the reaction trajectory.
9
+
10
+ The implementation is self-contained except for the external *synkit* helpers
11
+ used for RSMI⇒ITS inter-conversion and canonicalisation.
12
+ """
13
+
14
+ from collections.abc import Iterator
15
+ from typing import Any, Dict, List, Mapping, MutableMapping, Set, Tuple, Union
16
+
17
+ import networkx as nx
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Optional dependencies
21
+ # ---------------------------------------------------------------------------
22
+ try:
23
+ import pandas as pd # type: ignore
24
+ except ImportError: # pragma: no cover – pandas is only required for to_dataframe()
25
+ pd = None # noqa: N816
26
+
27
+ from synkit.Graph.Hyrogen._misc import h_to_explicit
28
+ from synkit.Graph.ITS.normalize_aam import NormalizeAAM
29
+ from synkit.Graph.MTG.mcs_matcher import MCSMatcher
30
+ from synkit.Graph.MTG.utils import (
31
+ normalize_hcount_and_typesGH,
32
+ normalize_order,
33
+ label_mtg_edges,
34
+ compute_standard_order,
35
+ )
36
+ from synkit.Graph.canon_graph import GraphCanonicaliser
37
+ from synkit.IO import ITSFormat, its_to_rsmi, rsmi_to_its
38
+
39
+ NodeID = int
40
+ MissingOrder = Tuple[Set[float], Set[float]]
41
+ GraphMapping = Dict[NodeID, NodeID]
42
+
43
+ _PLACEHOLDER: MissingOrder = (set(), set())
44
+ _PLACEHOLDER_TYPESGH = (set(), set(), set(), set(), set())
45
+ _TUPLE_EDGE_ATTRS = ("order", "kekule_order", "sigma_order", "pi_order")
46
+ _TUPLE_NODE_SCALAR_ATTRS = ("element", "atom_map", "valence_electrons")
47
+ _TUPLE_NODE_TIMELINE_ATTRS = (
48
+ "aromatic",
49
+ "hcount",
50
+ "charge",
51
+ "radical",
52
+ "lone_pairs",
53
+ "present",
54
+ )
55
+
56
+ __all__ = ["MTG"]
57
+
58
+
59
+ class MTG:
60
+ """Fuse a chronological series of ITS graphs into a Mechanistic Transition Graph.
61
+
62
+ :param sequences: A list of ITS-format NetworkX graphs or RSMI strings.
63
+ :param mappings: Optional list of precomputed mappings; computed via MCS if None.
64
+ :param node_label_names: Keys for node-label matching.
65
+ :param canonicaliser: Optional GraphCanonicaliser for snapshot canonicalisation.
66
+ :param its_format: ITS format used when ``sequences`` contains RSMI strings.
67
+ Defaults to ``"tuple"`` for Lewis-labelled graph MTGs. Pass
68
+ ``"typesGH"`` to build legacy MTGs from strings.
69
+ :raises ValueError: On invalid sequence or mapping lengths.
70
+ :raises RuntimeError: On mapping failures.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ sequences: Union[List[nx.Graph], List[str]],
76
+ mappings: List[GraphMapping] | None = None,
77
+ *,
78
+ node_label_names: List[str] | None = None,
79
+ canonicaliser: GraphCanonicaliser | None = None,
80
+ mcs_mol: bool = False,
81
+ mcs: bool = False,
82
+ its_format: ITSFormat = "tuple",
83
+ ) -> None:
84
+ if len(sequences) < 2:
85
+ raise ValueError("Need at least two snapshots.")
86
+
87
+ self._node_label_names = node_label_names or ["element", "charge", "hcount"]
88
+ self._canonicaliser = canonicaliser
89
+ self.mcs_mol = mcs_mol
90
+ self.mcs = mcs
91
+ self.its_format = its_format
92
+
93
+ self._graphs = self._prepare_graph_sequence(sequences)
94
+ self._k = len(self._graphs)
95
+ self._tuple_its = all(self._is_tuple_its(g) for g in self._graphs)
96
+
97
+ self._mappings = (
98
+ mappings if mappings is not None else self._compute_mappings(self._graphs)
99
+ )
100
+ if len(self._mappings) != self._k - 1:
101
+ raise ValueError("Mappings must match snapshot pairs.")
102
+
103
+ self._prod_nodes: Dict[int, Dict[str, Any]]
104
+ self._node_map: Dict[Tuple[int, NodeID], int]
105
+ self._graph: nx.Graph
106
+
107
+ self._build_node_map_and_attributes()
108
+ self._build_edge_history_and_graph()
109
+
110
+ def __repr__(self) -> str:
111
+ return f"<MTG k={self._k} nodes={self._graph.number_of_nodes()} edges={self._graph.number_of_edges()}>"
112
+
113
+ def __len__(self) -> int:
114
+ return self._graph.number_of_nodes()
115
+
116
+ def __iter__(self) -> Iterator[int]:
117
+ return iter(self._graph.nodes)
118
+
119
+ def __getitem__(self, node_id: int) -> Dict[str, Any]:
120
+ return self._graph.nodes[node_id]
121
+
122
+ @staticmethod
123
+ def describe() -> str:
124
+ return (
125
+ "# Usage example\n"
126
+ "mtg = MTG([G0, G1, G2])\n"
127
+ "mg = mtg.get_mtg()\n"
128
+ "rsmi = mtg.get_aam()\n"
129
+ )
130
+
131
+ def get_mtg(self, *, directed: bool = False) -> nx.Graph:
132
+ return self._graph.to_directed() if directed else self._graph
133
+
134
+ def get_its_steps(self, *, directed: bool = False) -> List[nx.Graph]:
135
+ """Reconstruct the ordered list of per-step ITS graphs from the MTG."""
136
+ if not self._tuple_its:
137
+ return [graph.copy() for graph in self._graphs]
138
+ graph = self.get_mtg(directed=directed)
139
+ return [self._tuple_step_its(graph, step) for step in range(self._k)]
140
+
141
+ def get_rsmi_steps(
142
+ self,
143
+ *,
144
+ directed: bool = False,
145
+ explicit_hydrogen: bool = False,
146
+ sanitize: bool = True,
147
+ ) -> List[str]:
148
+ """Serialize reconstructed per-step ITS graphs to reaction SMILES."""
149
+ fmt = "tuple" if self._tuple_its else "typesGH"
150
+ return [
151
+ its_to_rsmi(
152
+ its,
153
+ format=fmt,
154
+ explicit_hydrogen=explicit_hydrogen,
155
+ sanitize=sanitize,
156
+ )
157
+ for its in self.get_its_steps(directed=directed)
158
+ ]
159
+
160
+ def get_compose_its(self, *, directed: bool = False) -> nx.Graph:
161
+ g = self.get_mtg(directed=directed)
162
+ if self._tuple_its:
163
+ g = self._compose_tuple_node_attrs(g)
164
+ g = self._compose_tuple_edge_attrs(g)
165
+ else:
166
+ g = label_mtg_edges(g, inplace=False)
167
+ g = normalize_order(g)
168
+ g = normalize_hcount_and_typesGH(g)
169
+ return compute_standard_order(g)
170
+
171
+ def get_aam(self, *, directed: bool = False, explicit_h: bool = False) -> str:
172
+ g = self.get_compose_its(directed=directed)
173
+ rsmi = its_to_rsmi(g, explicit_hydrogen=True)
174
+ return (
175
+ NormalizeAAM().fit(rsmi, fix_aam_indice=False) if not explicit_h else rsmi
176
+ )
177
+
178
+ def to_dataframe(self):
179
+ if pd is None:
180
+ raise RuntimeError("pandas required for DataFrame export.")
181
+ return pd.DataFrame.from_dict(
182
+ dict(self._graph.nodes(data=True)), orient="index"
183
+ )
184
+
185
+ @staticmethod
186
+ def _merge_attrs(lhs: MutableMapping[str, Any], rhs: Mapping[str, Any]) -> None:
187
+ for k, v in rhs.items():
188
+ if not lhs.get(k) and v is not None:
189
+ lhs[k] = v
190
+
191
+ def _build_node_map_and_attributes(self) -> None:
192
+ if self._tuple_its and self._has_tuple_atom_maps(self._graphs):
193
+ self._build_tuple_node_map_and_attributes()
194
+ return
195
+
196
+ prod, node_map = {}, {}
197
+ last = self._graphs[-1]
198
+ for nid, attrs in last.nodes(data=True):
199
+ prod[nid] = attrs.copy()
200
+ node_map[(self._k - 1, nid)] = nid
201
+ pid_counter = max(prod, default=-1) + 1
202
+
203
+ # merge attributes backwards
204
+ for i in range(self._k - 2, -1, -1):
205
+ G, mp = self._graphs[i], self._mappings[i]
206
+ for nid, attrs in G.nodes(data=True):
207
+ tgt = mp.get(nid)
208
+ if tgt is not None and (i + 1, tgt) in node_map:
209
+ pid = node_map[(i + 1, tgt)]
210
+ self._merge_attrs(prod[pid], attrs)
211
+ else:
212
+ pid = pid_counter
213
+ prod[pid] = attrs.copy()
214
+ pid_counter += 1
215
+ node_map[(i, nid)] = pid
216
+
217
+ # assemble typesGH history per pid
218
+ first_idx: Dict[int, int] = {}
219
+ for (gi, n), p in node_map.items():
220
+ # track the earliest snapshot index where pid appears
221
+ if p in first_idx:
222
+ first_idx[p] = min(first_idx[p], gi)
223
+ else:
224
+ first_idx[p] = gi
225
+
226
+ if self._tuple_its:
227
+ self._simplify_tuple_node_attrs(prod, node_map)
228
+ else:
229
+ for p, attrs in prod.items():
230
+ hist: List[Any] = []
231
+ fi = first_idx[p]
232
+ for i in range(self._k):
233
+ if i < fi:
234
+ hist.append(_PLACEHOLDER_TYPESGH)
235
+ elif i == fi:
236
+ val = (
237
+ self._graphs[i]
238
+ .nodes[
239
+ next(
240
+ n
241
+ for (gi, n), pp in node_map.items()
242
+ if gi == i and pp == p
243
+ )
244
+ ]
245
+ .get(
246
+ "typesGH", (_PLACEHOLDER_TYPESGH, _PLACEHOLDER_TYPESGH)
247
+ )
248
+ )
249
+ hist.append(val)
250
+ else:
251
+ originals = [
252
+ n for (gi, n), pp in node_map.items() if gi == i and pp == p
253
+ ]
254
+ if originals:
255
+ val = (
256
+ self._graphs[i]
257
+ .nodes[originals[0]]
258
+ .get(
259
+ "typesGH",
260
+ (_PLACEHOLDER_TYPESGH, _PLACEHOLDER_TYPESGH),
261
+ )[-1]
262
+ )
263
+ hist.append(val)
264
+ else:
265
+ hist.append(_PLACEHOLDER_TYPESGH)
266
+ attrs["typesGH_history"] = tuple(hist)
267
+ attrs["typesGH"] = attrs["typesGH_history"]
268
+
269
+ self._prod_nodes = prod
270
+ self._node_map = node_map
271
+
272
+ def _build_tuple_node_map_and_attributes(self) -> None:
273
+ prod: Dict[int, Dict[str, Any]] = {}
274
+ node_map: Dict[Tuple[int, NodeID], int] = {}
275
+ pid_counter = 0
276
+
277
+ for gi, graph in enumerate(self._graphs):
278
+ used_in_graph: Set[int] = set()
279
+ for nid, attrs in graph.nodes(data=True):
280
+ pid = self._tuple_node_pid(attrs)
281
+ if pid is None or pid in used_in_graph:
282
+ while pid_counter in prod:
283
+ pid_counter += 1
284
+ pid = pid_counter
285
+ pid_counter += 1
286
+ prod.setdefault(pid, {})
287
+ node_map[(gi, nid)] = pid
288
+ used_in_graph.add(pid)
289
+
290
+ self._simplify_tuple_node_attrs(prod, node_map)
291
+ self._prod_nodes = prod
292
+ self._node_map = node_map
293
+
294
+ def _build_edge_history_and_graph(self) -> None:
295
+ hist: Dict[Tuple[int, int], Dict[str, List[MissingOrder]]] = {}
296
+ for i, G in enumerate(self._graphs):
297
+ for u, v, a in G.edges(data=True):
298
+ pu, pv = self._node_map[(i, u)], self._node_map[(i, v)]
299
+ key = tuple(sorted((pu, pv)))
300
+ attr_hist = hist.setdefault(
301
+ key,
302
+ {name: [_PLACEHOLDER] * self._k for name in _TUPLE_EDGE_ATTRS},
303
+ )
304
+ for name in _TUPLE_EDGE_ATTRS:
305
+ attr_hist[name][i] = a.get(name, _PLACEHOLDER)
306
+ g = nx.Graph()
307
+ g.add_nodes_from(self._prod_nodes.items())
308
+ for (u, v), attr_hist in hist.items():
309
+ attrs: Dict[str, Any] = {"order": tuple(attr_hist["order"])}
310
+ if self._tuple_its:
311
+ attrs = {}
312
+ for name, values in attr_hist.items():
313
+ attrs[name] = self._edge_pair_history_to_timeline(
314
+ tuple(values),
315
+ g.nodes[u].get("present"),
316
+ g.nodes[v].get("present"),
317
+ )
318
+ attrs["steps"] = tuple(
319
+ i
320
+ for i, value in enumerate(attr_hist["order"])
321
+ if self._is_observed_pair(value)
322
+ )
323
+ g.add_edge(u, v, **attrs)
324
+ if g.number_of_nodes() != len(self._prod_nodes):
325
+ raise RuntimeError("Node count mismatch.")
326
+ self._graph = g
327
+
328
+ def _simplify_tuple_node_attrs(
329
+ self,
330
+ prod: Dict[int, Dict[str, Any]],
331
+ node_map: Dict[Tuple[int, NodeID], int],
332
+ ) -> None:
333
+ """
334
+ Replace tuple-ITS node attrs with compact MTG attrs.
335
+
336
+ A path of ``k`` ITS steps has ``k + 1`` mechanism states: the first
337
+ step's left side followed by each step's right side.
338
+ """
339
+ refs_by_pid: Dict[int, Dict[int, NodeID]] = {}
340
+ for (gi, nid), pid in node_map.items():
341
+ refs_by_pid.setdefault(pid, {})[gi] = nid
342
+
343
+ for pid, refs in refs_by_pid.items():
344
+ simplified: Dict[str, Any] = {}
345
+ for key in _TUPLE_NODE_SCALAR_ATTRS:
346
+ timeline = self._node_attr_timeline(refs, key)
347
+ simplified[key] = next(
348
+ (value for value in timeline if value is not None),
349
+ None,
350
+ )
351
+
352
+ for key in _TUPLE_NODE_TIMELINE_ATTRS:
353
+ simplified[key] = self._node_attr_timeline(refs, key)
354
+
355
+ simplified["steps"] = tuple(sorted(refs))
356
+ prod[pid] = simplified
357
+
358
+ def _node_attr_timeline(
359
+ self,
360
+ refs: Dict[int, NodeID],
361
+ key: str,
362
+ ) -> Tuple[Any, ...]:
363
+ timeline: List[Any] = [None] * (self._k + 1)
364
+ for gi in range(self._k):
365
+ nid = refs.get(gi)
366
+ if nid is None:
367
+ continue
368
+ value = self._graphs[gi].nodes[nid].get(key)
369
+ if self._is_pair(value):
370
+ timeline[gi] = value[0]
371
+ timeline[gi + 1] = value[1]
372
+ return tuple(timeline)
373
+
374
+ def _compose_tuple_node_attrs(self, graph: nx.Graph) -> nx.Graph:
375
+ """
376
+ Collapse tuple-ITS node histories to the outermost observed states.
377
+
378
+ The fused MTG node attrs are initially copied from the last ITS step.
379
+ For a composed ITS we instead need the first available left-side value
380
+ and the last available right-side value across the whole trajectory.
381
+ """
382
+ out = graph.copy()
383
+ for _, attrs in out.nodes(data=True):
384
+ for key in _TUPLE_NODE_SCALAR_ATTRS:
385
+ value = attrs.get(key)
386
+ attrs[key] = (value, value)
387
+ for key in _TUPLE_NODE_TIMELINE_ATTRS:
388
+ timeline = attrs.get(key)
389
+ if isinstance(timeline, tuple) and timeline:
390
+ attrs[key] = (timeline[0], timeline[-1])
391
+ return out
392
+
393
+ def _compose_tuple_edge_attrs(self, graph: nx.Graph) -> nx.Graph:
394
+ """Collapse tuple edge timelines to first-state / final-state pairs."""
395
+ out = graph.copy()
396
+ for _, _, attrs in out.edges(data=True):
397
+ for name in _TUPLE_EDGE_ATTRS:
398
+ timeline = attrs.get(name)
399
+ if not isinstance(timeline, tuple):
400
+ continue
401
+ if timeline:
402
+ attrs[name] = (timeline[0], timeline[-1])
403
+ return out
404
+
405
+ def _tuple_step_its(self, graph: nx.Graph, step: int) -> nx.Graph:
406
+ """Extract one paired tuple ITS step from compact tuple-MTG timelines."""
407
+ its = nx.Graph()
408
+ for node, attrs in graph.nodes(data=True):
409
+ node_attrs: Dict[str, Any] = {}
410
+ if step not in attrs.get("steps", ()):
411
+ continue
412
+ present_pair = self._timeline_pair(attrs.get("present"), step)
413
+ if present_pair[0] is None or present_pair[1] is None:
414
+ continue
415
+ for key in _TUPLE_NODE_SCALAR_ATTRS:
416
+ value = attrs.get(key)
417
+ node_attrs[key] = (value, value)
418
+ for key in _TUPLE_NODE_TIMELINE_ATTRS:
419
+ value = self._timeline_pair(attrs.get(key), step)
420
+ if value != (None, None):
421
+ node_attrs[key] = value
422
+ its.add_node(node, **node_attrs)
423
+
424
+ for u, v, attrs in graph.edges(data=True):
425
+ if step not in attrs.get("steps", ()):
426
+ continue
427
+ edge_attrs: Dict[str, Any] = {}
428
+ has_edge = False
429
+ for key in _TUPLE_EDGE_ATTRS:
430
+ value = self._timeline_pair(attrs.get(key), step)
431
+ if value == (None, None):
432
+ continue
433
+ edge_attrs[key] = value
434
+ if (
435
+ key == "order"
436
+ and value[0] is not None
437
+ and value[1] is not None
438
+ and value != (0, 0)
439
+ and value != (0.0, 0.0)
440
+ ):
441
+ has_edge = True
442
+ if has_edge and u in its and v in its:
443
+ its.add_edge(u, v, **edge_attrs)
444
+ return compute_standard_order(its)
445
+
446
+ def _prepare_graph_sequence(
447
+ self, seq: List[nx.Graph] | List[str]
448
+ ) -> List[nx.Graph]:
449
+ out: List[nx.Graph] = []
450
+ for item in seq:
451
+ g = (
452
+ rsmi_to_its(item, core=False, format=self.its_format)
453
+ if isinstance(item, str)
454
+ else item
455
+ )
456
+ if self._canonicaliser:
457
+ g = self._canonicaliser.canonicalise_graph(g).canonical_graph
458
+ if self._is_tuple_its(g):
459
+ out.append(g)
460
+ continue
461
+ g = h_to_explicit(g, its=True)
462
+ out.append(normalize_hcount_and_typesGH(g))
463
+ return out
464
+
465
+ @staticmethod
466
+ def _is_tuple_its(graph: nx.Graph) -> bool:
467
+ """
468
+ Detect paired-attribute ITS graphs produced by the newer tuple format.
469
+
470
+ Tuple ITS nodes carry side-specific attributes directly, such as
471
+ ``element=("C", "C")`` and ``lone_pairs=(0, 0)``. Legacy ITS graphs
472
+ instead keep the paired state primarily in ``typesGH``.
473
+ """
474
+ if graph.number_of_nodes() == 0:
475
+ return False
476
+ _, attrs = next(iter(graph.nodes(data=True)))
477
+ element = attrs.get("element")
478
+ return isinstance(element, tuple) and len(element) == 2
479
+
480
+ @staticmethod
481
+ def _is_pair(value: Any) -> bool:
482
+ return isinstance(value, tuple) and len(value) == 2
483
+
484
+ @classmethod
485
+ def _is_observed_pair(cls, value: Any) -> bool:
486
+ return cls._is_pair(value) and not (
487
+ isinstance(value[0], set) and isinstance(value[1], set)
488
+ )
489
+
490
+ @staticmethod
491
+ def _timeline_pair(timeline: Any, step: int) -> Tuple[Any, Any]:
492
+ if not isinstance(timeline, tuple) or len(timeline) <= step + 1:
493
+ return (None, None)
494
+ return (timeline[step], timeline[step + 1])
495
+
496
+ @classmethod
497
+ def _edge_pair_history_to_timeline(
498
+ cls,
499
+ history: Tuple[Any, ...],
500
+ u_present: Any,
501
+ v_present: Any,
502
+ ) -> Tuple[Any, ...]:
503
+ """
504
+ Convert ITS step-pair history into mechanism-state timeline.
505
+
506
+ Example: ``((2, 1), (1, 2))`` becomes ``(2, 1, 2)``.
507
+ Missing edge states are ``0`` when both endpoint atoms exist and
508
+ ``None`` when an endpoint is absent.
509
+ """
510
+ if not history:
511
+ return ()
512
+
513
+ timeline: List[Any] = [None] * (len(history) + 1)
514
+ for idx, value in enumerate(history):
515
+ if cls._is_pair(value) and not (
516
+ isinstance(value[0], set) and isinstance(value[1], set)
517
+ ):
518
+ timeline[idx] = value[0]
519
+ timeline[idx + 1] = value[1]
520
+ return tuple(
521
+ cls._fill_missing_edge_state(value, idx, u_present, v_present)
522
+ for idx, value in enumerate(timeline)
523
+ )
524
+
525
+ @staticmethod
526
+ def _fill_missing_edge_state(
527
+ value: Any,
528
+ idx: int,
529
+ u_present: Any,
530
+ v_present: Any,
531
+ ) -> Any:
532
+ if value is not None:
533
+ return value
534
+ if (
535
+ isinstance(u_present, tuple)
536
+ and isinstance(v_present, tuple)
537
+ and len(u_present) > idx
538
+ and len(v_present) > idx
539
+ and u_present[idx] is True
540
+ and v_present[idx] is True
541
+ ):
542
+ return 0.0
543
+ return None
544
+
545
+ def _compute_mappings(self, graphs: List[nx.Graph]) -> List[GraphMapping]:
546
+ if self._tuple_its:
547
+ return [
548
+ self._compute_tuple_mapping(graphs[i], graphs[i + 1])
549
+ for i in range(len(graphs) - 1)
550
+ ]
551
+
552
+ mappings: List[GraphMapping] = []
553
+ for i in range(len(graphs) - 1):
554
+ m = MCSMatcher(node_label_names=self._node_label_names)
555
+ m.find_rc_mapping(
556
+ graphs[i], graphs[i + 1], mcs=self.mcs, mcs_mol=self.mcs_mol
557
+ )
558
+ if not m._mappings:
559
+ raise RuntimeError(f"No mapping between {i} and {i+1}")
560
+ mappings.append(m._mappings[0])
561
+ return mappings
562
+
563
+ @classmethod
564
+ def _compute_tuple_mapping(cls, left: nx.Graph, right: nx.Graph) -> GraphMapping:
565
+ left_by_map = cls._nodes_by_atom_map(left)
566
+ right_by_map = cls._nodes_by_atom_map(right)
567
+ common_maps = sorted(set(left_by_map) & set(right_by_map))
568
+ mapping = {left_by_map[amap]: right_by_map[amap] for amap in common_maps}
569
+
570
+ if mapping:
571
+ return mapping
572
+
573
+ common_nodes = sorted(set(left.nodes()) & set(right.nodes()))
574
+ return {node: node for node in common_nodes}
575
+
576
+ @classmethod
577
+ def _has_tuple_atom_maps(cls, graphs: List[nx.Graph]) -> bool:
578
+ return any(
579
+ cls._tuple_node_pid(attrs) is not None
580
+ for graph in graphs
581
+ for _, attrs in graph.nodes(data=True)
582
+ )
583
+
584
+ @staticmethod
585
+ def _tuple_node_pid(attrs: Mapping[str, Any]) -> int | None:
586
+ atom_map = attrs.get("atom_map")
587
+ if isinstance(atom_map, tuple) and len(atom_map) == 2:
588
+ atom_map = atom_map[1] if atom_map[1] not in (None, 0, "") else atom_map[0]
589
+ if atom_map in (None, 0, ""):
590
+ return None
591
+ return int(atom_map)
592
+
593
+ @staticmethod
594
+ def _nodes_by_atom_map(graph: nx.Graph) -> Dict[int, NodeID]:
595
+ by_map: Dict[int, NodeID] = {}
596
+ for node, attrs in graph.nodes(data=True):
597
+ atom_map = MTG._tuple_node_pid(attrs)
598
+ if atom_map is None:
599
+ continue
600
+ if atom_map in by_map:
601
+ continue
602
+ by_map[atom_map] = node
603
+ return by_map
604
+
605
+ @property
606
+ def node_mapping(self) -> Dict[Tuple[int, NodeID], int]:
607
+ return dict(self._node_map)
608
+
609
+ @property
610
+ def k(self) -> int:
611
+ return self._k
@@ -0,0 +1,74 @@
1
+ from typing import Optional, List
2
+ from synkit.Graph.MTG.mtg import MTG
3
+ from synkit.Rule.Apply.rule_matcher import RuleMatcher
4
+ from synkit.Graph.MTG.mcs_matcher import MCSMatcher
5
+ from networkx import Graph
6
+
7
+
8
+ def find_mtg(
9
+ g1: Graph,
10
+ g2: Graph,
11
+ ground_truth: str,
12
+ node_label_names: Optional[List[str]] = None,
13
+ ) -> Optional[MTG]:
14
+ """Attempt to construct a Mapping Transformation Graph (MTG) for two input graphs
15
+ by finding maximum common substructure mappings and validating against a ground truth.
16
+
17
+ :param g1: The first input graph to match.
18
+ :type g1: networkx.Graph
19
+ :param g2: The second input graph to match.
20
+ :type g2: networkx.Graph
21
+ :param ground_truth: A string representation of the expected atom-atom mapping (AAM)
22
+ used to validate candidate mappings.
23
+ :type ground_truth: str
24
+ :param node_label_names: List of node attribute names to use for MCS matching.
25
+ Defaults to ["element", "charge", "hcount"].
26
+ :type node_label_names: list of str, optional
27
+ :return: An MTG instance if a valid mapping satisfying the ground truth is found;
28
+ otherwise, None.
29
+ :rtype: MTG or None
30
+ :raises ValueError: If input graphs are empty or ground_truth is invalid format.
31
+
32
+ .. rubric:: Examples
33
+
34
+ >>> from networkx import Graph
35
+ >>> g1, g2 = Graph(), Graph()
36
+ >>> # populate g1 and g2 with nodes/edges
37
+ >>> mtg = find_mtg(
38
+ ... g1,
39
+ ... g2,
40
+ ... ground_truth="{0:1, 1:0}",
41
+ ... node_label_names=["element", "charge", "hcount"]
42
+ ... )
43
+ >>> if mtg:
44
+ ... print(mtg)
45
+ """
46
+ # Validate inputs
47
+ if not g1 or not g2:
48
+ raise ValueError("Input graphs g1 and g2 must be non-empty.")
49
+ if not isinstance(ground_truth, str) or not ground_truth.strip():
50
+ raise ValueError("Ground truth mapping must be a non-empty string.")
51
+
52
+ # Set default node_label_names if not provided
53
+ if node_label_names is None:
54
+ node_label_names = ["element", "charge", "hcount"]
55
+
56
+ # Initialize maximum common substructure matcher
57
+ mcs = MCSMatcher(node_label_names=node_label_names)
58
+ mcs.find_rc_mapping(g1, g2, mcs=False)
59
+ mappings = mcs._mappings
60
+
61
+ for mapping in mappings:
62
+ # Construct MTG using current mapping
63
+ mtg = MTG([g1, g2], mappings=[mapping])
64
+ aam = mtg.get_aam()
65
+ try:
66
+ # Validate generated AAM against ground truth
67
+ RuleMatcher(ground_truth, aam, explicit_h=False)
68
+ return mtg
69
+ except AssertionError:
70
+ # Mapping did not satisfy ground truth, try next
71
+ continue
72
+
73
+ # No valid mapping found
74
+ return None