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,714 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from dataclasses import dataclass
6
+ from typing import Any, Optional, Mapping
7
+
8
+ try:
9
+ from rxnmapper import RXNMapper as _RXNMapper
10
+ except Exception:
11
+ _RXNMapper = None
12
+
13
+ from .kegg_api import KEGGClient
14
+ from .kegg_parse import (
15
+ equation_to_text,
16
+ get_compound_ids_from_equations,
17
+ get_compound_ids_from_text,
18
+ molblock_to_smiles,
19
+ normalize_module_id,
20
+ orient_equation_to_module,
21
+ parse_equation,
22
+ parse_kegg_field_blocks,
23
+ parse_module_reaction_directions,
24
+ reaction_smiles_from_equation,
25
+ )
26
+
27
+ _RID_PATTERN = re.compile(r"R\d{5}")
28
+
29
+
30
+ ReactionEquationMap = dict[str, Optional[str]]
31
+ CompoundTable = dict[str, dict[str, Any]]
32
+ ReactionSmilesMap = dict[str, str]
33
+ MissingByReaction = dict[str, dict[str, list[str]]]
34
+ JSONDict = dict[str, Any]
35
+
36
+
37
+ @dataclass(slots=True)
38
+ class KEGGExtractor:
39
+ """High-level extractor for KEGG pathway and module reaction data.
40
+
41
+ This class orchestrates KEGG entry retrieval, module membership parsing,
42
+ reaction equation collection, compound-table construction, reaction SMILES
43
+ assembly, and optional atom mapping.
44
+
45
+ :param client:
46
+ Optional KEGG REST client. When omitted, a default :class:`KEGGClient`
47
+ instance is created during :meth:`__post_init__`.
48
+ :type client: Optional[KEGGClient]
49
+ :param mapper_cls:
50
+ Optional atom-mapper class used by :meth:`atom_map_reactions`. The
51
+ class must be instantiable without arguments and must provide
52
+ ``get_attention_guided_atom_maps``.
53
+ :type mapper_cls: Optional[type[Any]]
54
+
55
+ .. rubric:: Example
56
+
57
+ .. code-block:: python
58
+
59
+ extractor = KEGGExtractor()
60
+ data = extractor.build_module_json(
61
+ "M00001",
62
+ with_compounds=True,
63
+ with_atom_maps=False,
64
+ )
65
+ """
66
+
67
+ client: Optional[KEGGClient] = None
68
+ mapper_cls: Optional[type[Any]] = _RXNMapper
69
+
70
+ def __post_init__(self) -> None:
71
+ if self.client is None:
72
+ self.client = KEGGClient()
73
+
74
+ @staticmethod
75
+ def save_json(data: Mapping[str, Any], save_as: Optional[str]) -> None:
76
+ """Save JSON data to disk when an output path is provided.
77
+
78
+ :param data:
79
+ JSON-serializable data to write.
80
+ :type data: Mapping[str, Any]
81
+ :param save_as:
82
+ Optional output path.
83
+ :type save_as: Optional[str]
84
+
85
+ :return:
86
+ ``None``.
87
+ :rtype: None
88
+
89
+ .. rubric:: Example
90
+
91
+ .. code-block:: python
92
+
93
+ KEGGExtractor._save_json({"x": 1}, "out.json")
94
+ """
95
+ if save_as:
96
+ with open(save_as, "w", encoding="utf-8") as handle:
97
+ json.dump(data, handle, ensure_ascii=False, indent=2)
98
+
99
+ def get_modules_from_pathway(self, pathway_id: str) -> list[str]:
100
+ """Extract module IDs from a KEGG pathway entry.
101
+
102
+ :param pathway_id:
103
+ KEGG pathway identifier such as ``"hsa00010"``.
104
+ :type pathway_id: str
105
+
106
+ :return:
107
+ Canonical KEGG module identifiers such as ``["M00001",
108
+ "M00002"]``.
109
+ :rtype: list[str]
110
+
111
+ .. rubric:: Example
112
+
113
+ .. code-block:: python
114
+
115
+ modules = extractor.get_modules_from_pathway("hsa00010")
116
+ """
117
+ text = self.client.get_text(f"get/{pathway_id}")
118
+ payloads = parse_kegg_field_blocks(text, "MODULE")
119
+
120
+ modules: list[str] = []
121
+ for payload in payloads:
122
+ for token in payload.split():
123
+ normalized = normalize_module_id(token)
124
+ if normalized is not None:
125
+ modules.append(normalized)
126
+
127
+ return modules
128
+
129
+ def get_reaction_ids_from_module(self, module_id: str) -> list[str]:
130
+ """Collect KEGG reaction IDs from a module entry, preserving module order when
131
+ directional REACTION lines can be parsed.
132
+
133
+ :param module_id:
134
+ KEGG module identifier such as ``"M00001"``.
135
+ :type module_id: str
136
+
137
+ :return:
138
+ Sorted unique KEGG reaction identifiers.
139
+ :rtype: list[str]
140
+
141
+ .. rubric:: Example
142
+
143
+ .. code-block:: python
144
+
145
+ reaction_ids = extractor.get_reaction_ids_from_module("M00001")
146
+ """
147
+ text = self.client.get_text(f"get/{module_id}")
148
+ directions = parse_module_reaction_directions(text)
149
+ if directions:
150
+ return list(directions.keys())
151
+
152
+ payloads = parse_kegg_field_blocks(text, "REACTION")
153
+ reaction_ids: set[str] = set()
154
+ for payload in payloads:
155
+ reaction_ids.update(_RID_PATTERN.findall(payload))
156
+
157
+ return sorted(reaction_ids)
158
+
159
+ def get_equation_for_reaction(self, reaction_id: str) -> Optional[str]:
160
+ """Fetch the KEGG equation string for a reaction.
161
+
162
+ :param reaction_id:
163
+ KEGG reaction identifier such as ``"R00200"``.
164
+ :type reaction_id: str
165
+
166
+ :return:
167
+ Equation string when present, otherwise ``None``.
168
+ :rtype: Optional[str]
169
+
170
+ .. rubric:: Example
171
+
172
+ .. code-block:: python
173
+
174
+ equation = extractor.get_equation_for_reaction("R00200")
175
+ """
176
+ text = self.client.get_text(f"get/rn:{reaction_id}")
177
+ payloads = parse_kegg_field_blocks(text, "EQUATION")
178
+ return payloads[0].strip() if payloads else None
179
+
180
+ def get_module_equations(self, module_id: str) -> ReactionEquationMap:
181
+ """Build a reaction-to-equation mapping for a KEGG module.
182
+
183
+ :param module_id:
184
+ KEGG module identifier.
185
+ :type module_id: str
186
+
187
+ :return:
188
+ Mapping from reaction identifier to equation string.
189
+ :rtype: dict[str, Optional[str]]
190
+
191
+ .. rubric:: Example
192
+
193
+ .. code-block:: python
194
+
195
+ equations = extractor.get_module_equations("M00001")
196
+ """
197
+ module_text = self.client.get_text(f"get/{module_id}")
198
+ directions = parse_module_reaction_directions(module_text)
199
+
200
+ reaction_ids = (
201
+ list(directions.keys())
202
+ if directions
203
+ else self.get_reaction_ids_from_module(module_id)
204
+ )
205
+
206
+ equations_by_rid: ReactionEquationMap = {}
207
+
208
+ for reaction_id in reaction_ids:
209
+ equation = self.get_equation_for_reaction(reaction_id)
210
+ if equation is None:
211
+ equations_by_rid[reaction_id] = None
212
+ continue
213
+
214
+ if reaction_id not in directions:
215
+ equations_by_rid[reaction_id] = equation
216
+ continue
217
+
218
+ left_ids, right_ids, module_arrow = directions[reaction_id]
219
+ parsed = parse_equation(equation)
220
+ oriented = orient_equation_to_module(parsed, left_ids, right_ids)
221
+ equations_by_rid[reaction_id] = equation_to_text(
222
+ oriented, arrow=module_arrow
223
+ )
224
+
225
+ return equations_by_rid
226
+
227
+ def get_pathway_equations(
228
+ self,
229
+ pathway_id: str,
230
+ ) -> dict[str, ReactionEquationMap]:
231
+ """Build nested module/reaction equation mappings for a pathway.
232
+
233
+ :param pathway_id:
234
+ KEGG pathway identifier.
235
+ :type pathway_id: str
236
+
237
+ :return:
238
+ Mapping of the form ``{module_id: {reaction_id: equation}}``.
239
+ :rtype: dict[str, dict[str, Optional[str]]]
240
+
241
+ .. rubric:: Example
242
+
243
+ .. code-block:: python
244
+
245
+ nested = extractor.get_pathway_equations("hsa00010")
246
+ """
247
+ modules = self.get_modules_from_pathway(pathway_id)
248
+ return {
249
+ module_id: self.get_module_equations(module_id) for module_id in modules
250
+ }
251
+
252
+ def get_compound_name(self, compound_id: str) -> Optional[str]:
253
+ """Retrieve the primary KEGG compound name.
254
+
255
+ When multiple synonyms are present in the ``NAME`` field, only the
256
+ first entry is returned.
257
+
258
+ :param compound_id:
259
+ KEGG compound identifier such as ``"C00001"``.
260
+ :type compound_id: str
261
+
262
+ :return:
263
+ Primary compound name if available, otherwise ``None``.
264
+ :rtype: Optional[str]
265
+
266
+ .. rubric:: Example
267
+
268
+ .. code-block:: python
269
+
270
+ name = extractor.get_compound_name("C00001")
271
+ """
272
+ text = self.client.get_text(f"get/cpd:{compound_id}")
273
+ payloads = parse_kegg_field_blocks(text, "NAME")
274
+ if not payloads:
275
+ return None
276
+
277
+ first_payload = payloads[0].strip()
278
+ return first_payload.split(";")[0].strip()
279
+
280
+ def get_compound_molblock(self, compound_id: str) -> Optional[str]:
281
+ """Retrieve the KEGG MOL block for a compound.
282
+
283
+ :param compound_id:
284
+ KEGG compound identifier.
285
+ :type compound_id: str
286
+
287
+ :return:
288
+ MOL block text when available, otherwise ``None``.
289
+ :rtype: Optional[str]
290
+
291
+ .. rubric:: Example
292
+
293
+ .. code-block:: python
294
+
295
+ molblock = extractor.get_compound_molblock("C00001")
296
+ """
297
+ return self.client.get_optional_text(f"get/cpd:{compound_id}/mol")
298
+
299
+ def build_compound_table(
300
+ self,
301
+ compound_ids: list[str],
302
+ ) -> CompoundTable:
303
+ """Build a compound table for a list of KEGG compound identifiers.
304
+
305
+ Each returned record includes the KEGG compound identifier, the primary
306
+ compound name, the optional MOL block, and a canonical SMILES string
307
+ derived from the MOL block when RDKit parsing succeeds.
308
+
309
+ :param compound_ids:
310
+ KEGG compound identifiers.
311
+ :type compound_ids: list[str]
312
+
313
+ :return:
314
+ Compound table of the form
315
+ ``{cid: {"id", "name", "smiles", "molblock"}}``.
316
+ :rtype: dict[str, dict[str, Any]]
317
+
318
+ .. rubric:: Example
319
+
320
+ .. code-block:: python
321
+
322
+ compounds = extractor.build_compound_table(["C00001", "C00002"])
323
+ """
324
+ compounds: CompoundTable = {}
325
+
326
+ for compound_id in compound_ids:
327
+ name = self.get_compound_name(compound_id)
328
+ molblock = self.get_compound_molblock(compound_id)
329
+ smiles = molblock_to_smiles(molblock)
330
+
331
+ compounds[compound_id] = {
332
+ "id": compound_id,
333
+ "name": name,
334
+ "smiles": smiles,
335
+ "molblock": molblock,
336
+ }
337
+
338
+ return compounds
339
+
340
+ def build_reaction_smiles_dict(
341
+ self,
342
+ parsed_by_rid: Mapping[str, Any],
343
+ compounds_by_cid: Mapping[str, Mapping[str, Any]],
344
+ ) -> tuple[ReactionSmilesMap, MissingByReaction]:
345
+ """Build reaction SMILES strings for parsed KEGG equations.
346
+
347
+ :param parsed_by_rid:
348
+ Parsed equation objects keyed by reaction identifier.
349
+ :type parsed_by_rid: Mapping[str, Any]
350
+ :param compounds_by_cid:
351
+ Compound table keyed by KEGG compound identifier.
352
+ :type compounds_by_cid: Mapping[str, Mapping[str, Any]]
353
+
354
+ :return:
355
+ Tuple ``(reaction_smiles_by_id, missing_by_id)``.
356
+ :rtype: tuple[dict[str, str], dict[str, dict[str, list[str]]]]
357
+
358
+ .. rubric:: Example
359
+
360
+ .. code-block:: python
361
+
362
+ rsmi_by_rid, missing = extractor.build_reaction_smiles_dict(
363
+ parsed_by_rid,
364
+ compounds_by_cid,
365
+ )
366
+ """
367
+ reaction_smiles: ReactionSmilesMap = {}
368
+ missing_by_rid: MissingByReaction = {}
369
+
370
+ for reaction_id, parsed_equation in parsed_by_rid.items():
371
+ rsmi, missing = reaction_smiles_from_equation(
372
+ parsed_equation,
373
+ compounds_by_cid,
374
+ )
375
+ reaction_smiles[reaction_id] = rsmi
376
+ missing_by_rid[reaction_id] = missing
377
+
378
+ return reaction_smiles, missing_by_rid
379
+
380
+ def atom_map_reactions(
381
+ self,
382
+ reaction_smiles_by_id: Mapping[str, str],
383
+ ) -> dict[str, Optional[str]]:
384
+ """Atom-map reaction SMILES using RXNMapper.
385
+
386
+ :param reaction_smiles_by_id:
387
+ Mapping ``{reaction_id: reaction_smiles}``.
388
+ :type reaction_smiles_by_id: Mapping[str, str]
389
+
390
+ :return:
391
+ Mapping ``{reaction_id: mapped_reaction_smiles_or_none}``.
392
+ :rtype: dict[str, Optional[str]]
393
+
394
+ .. rubric:: Example
395
+
396
+ .. code-block:: python
397
+
398
+ mapped = extractor.atom_map_reactions({"R00001": "CCO>>CC=O"})
399
+ """
400
+ mapper = self.mapper_cls() if self.mapper_cls is not None else None
401
+ mapped_by_id: dict[str, Optional[str]] = {}
402
+
403
+ for reaction_id, reaction_smiles in reaction_smiles_by_id.items():
404
+ if (
405
+ ">>" not in reaction_smiles
406
+ or reaction_smiles.startswith(">>")
407
+ or reaction_smiles.endswith(">>")
408
+ ):
409
+ mapped_by_id[reaction_id] = None
410
+ continue
411
+
412
+ try:
413
+ result = mapper.get_attention_guided_atom_maps([reaction_smiles])[0]
414
+ mapped_by_id[reaction_id] = result.get("mapped_rxn")
415
+ except Exception:
416
+ mapped_by_id[reaction_id] = None
417
+
418
+ return mapped_by_id
419
+
420
+ def build_missing_compound_report(
421
+ self,
422
+ equations_by_rid: ReactionEquationMap,
423
+ compounds_by_cid: Mapping[str, Mapping[str, Any]],
424
+ ) -> JSONDict:
425
+ """Build a report for compounds lacking SMILES.
426
+
427
+ :param equations_by_rid:
428
+ Reaction equations keyed by reaction identifier.
429
+ :type equations_by_rid: dict[str, Optional[str]]
430
+ :param compounds_by_cid:
431
+ Compound records keyed by KEGG compound identifier.
432
+ :type compounds_by_cid: Mapping[str, Mapping[str, Any]]
433
+
434
+ :return:
435
+ Report containing missing compounds and per-reaction provenance.
436
+ :rtype: dict[str, Any]
437
+
438
+ .. rubric:: Example
439
+
440
+ .. code-block:: python
441
+
442
+ report = extractor.build_missing_compound_report(
443
+ equations_by_rid,
444
+ compounds_by_cid,
445
+ )
446
+ """
447
+ cid_to_rids: dict[str, set[str]] = {}
448
+
449
+ for reaction_id, equation in equations_by_rid.items():
450
+ if not equation:
451
+ continue
452
+
453
+ for compound_id in get_compound_ids_from_text(equation):
454
+ cid_to_rids.setdefault(compound_id, set()).add(reaction_id)
455
+
456
+ missing_compounds: list[dict[str, Any]] = []
457
+ missing_ids: set[str] = set()
458
+ involving_reactions: set[str] = set()
459
+
460
+ for compound_id, record in compounds_by_cid.items():
461
+ if record.get("smiles") is None:
462
+ reaction_ids = sorted(cid_to_rids.get(compound_id, set()))
463
+ missing_compounds.append(
464
+ {
465
+ "id": compound_id,
466
+ "name": record.get("name"),
467
+ "reactions": reaction_ids,
468
+ }
469
+ )
470
+ missing_ids.add(compound_id)
471
+ involving_reactions.update(reaction_ids)
472
+
473
+ missing_compounds.sort(key=lambda record: record["id"])
474
+
475
+ return {
476
+ "missing_compounds": missing_compounds,
477
+ "missing_compound_ids": sorted(missing_ids),
478
+ "reactions_involving_missing": sorted(involving_reactions),
479
+ }
480
+
481
+ def build_kegg_json(
482
+ self,
483
+ equations_by_rid: ReactionEquationMap,
484
+ *,
485
+ smiles_by_rid: Optional[Mapping[str, str]] = None,
486
+ rules_by_rid: Optional[Mapping[str, Optional[str]]] = None,
487
+ molecules_by_cid: Optional[Mapping[str, Mapping[str, Any]]] = None,
488
+ ) -> JSONDict:
489
+ """Build a compact KEGG JSON block with reactions and molecules.
490
+
491
+ :param equations_by_rid:
492
+ Reaction equations keyed by reaction identifier.
493
+ :type equations_by_rid: dict[str, Optional[str]]
494
+ :param smiles_by_rid:
495
+ Optional reaction SMILES keyed by reaction identifier.
496
+ :type smiles_by_rid: Optional[Mapping[str, str]]
497
+ :param rules_by_rid:
498
+ Optional atom-mapped reaction strings keyed by reaction identifier.
499
+ :type rules_by_rid: Optional[Mapping[str, Optional[str]]]
500
+ :param molecules_by_cid:
501
+ Optional molecule table keyed by compound identifier.
502
+ :type molecules_by_cid: Optional[Mapping[str, Mapping[str, Any]]]
503
+
504
+ :return:
505
+ Dictionary with ``"reactions"`` and ``"molecules"`` entries.
506
+ :rtype: dict[str, Any]
507
+
508
+ .. rubric:: Example
509
+
510
+ .. code-block:: python
511
+
512
+ data = extractor.build_kegg_json(equations_by_rid)
513
+ """
514
+ smiles_by_rid = dict(smiles_by_rid or {})
515
+ rules_by_rid = dict(rules_by_rid or {})
516
+ molecules_by_cid = dict(molecules_by_cid or {})
517
+
518
+ all_compound_ids: set[str] = set()
519
+ reactions: list[dict[str, Any]] = []
520
+
521
+ for reaction_id in sorted(equations_by_rid.keys()):
522
+ equation = equations_by_rid[reaction_id]
523
+ if not equation:
524
+ continue
525
+
526
+ all_compound_ids.update(get_compound_ids_from_text(equation))
527
+ reactions.append(
528
+ {
529
+ "id": reaction_id,
530
+ "reaction": equation,
531
+ "rule": rules_by_rid.get(reaction_id),
532
+ "smiles": smiles_by_rid.get(reaction_id),
533
+ }
534
+ )
535
+
536
+ molecules: list[dict[str, Any]] = []
537
+ for compound_id in sorted(all_compound_ids):
538
+ record = molecules_by_cid.get(
539
+ compound_id,
540
+ {"id": compound_id, "name": None, "smiles": None},
541
+ )
542
+ molecules.append(
543
+ {
544
+ "id": compound_id,
545
+ "name": record.get("name"),
546
+ "smiles": record.get("smiles"),
547
+ }
548
+ )
549
+
550
+ return {"reactions": reactions, "molecules": molecules}
551
+
552
+ def build_module_json(
553
+ self,
554
+ module_id: str,
555
+ *,
556
+ with_compounds: bool = True,
557
+ with_atom_maps: bool = True,
558
+ save_as: Optional[str] = None,
559
+ ) -> JSONDict:
560
+ """Build a JSON block for a KEGG module.
561
+
562
+ :param module_id:
563
+ KEGG module ID.
564
+ :type module_id: str
565
+ :param with_compounds:
566
+ Whether to resolve compound names, MOL blocks, and SMILES strings.
567
+ :type with_compounds: bool
568
+ :param with_atom_maps:
569
+ Whether to compute atom-mapped reactions.
570
+ :type with_atom_maps: bool
571
+ :param save_as:
572
+ Optional output path for writing the JSON block to disk.
573
+ :type save_as: Optional[str]
574
+
575
+ :return:
576
+ Module JSON dictionary.
577
+ :rtype: dict[str, Any]
578
+
579
+ .. rubric:: Example
580
+
581
+ .. code-block:: python
582
+
583
+ data = extractor.build_module_json(
584
+ "M00001",
585
+ with_compounds=True,
586
+ with_atom_maps=False,
587
+ )
588
+ """
589
+ equations_by_rid = self.get_module_equations(module_id)
590
+ compound_ids, parsed_by_rid = get_compound_ids_from_equations(equations_by_rid)
591
+
592
+ compounds_by_cid = (
593
+ self.build_compound_table(compound_ids) if with_compounds else {}
594
+ )
595
+
596
+ reaction_smiles_by_rid, _ = (
597
+ self.build_reaction_smiles_dict(parsed_by_rid, compounds_by_cid)
598
+ if with_compounds
599
+ else ({}, {})
600
+ )
601
+
602
+ rules_by_rid = (
603
+ self.atom_map_reactions(reaction_smiles_by_rid)
604
+ if (with_compounds and with_atom_maps)
605
+ else {}
606
+ )
607
+
608
+ data: JSONDict = {"module_id": module_id}
609
+ data.update(
610
+ self.build_kegg_json(
611
+ equations_by_rid,
612
+ smiles_by_rid=reaction_smiles_by_rid,
613
+ rules_by_rid=rules_by_rid,
614
+ molecules_by_cid={
615
+ cid: {
616
+ "id": cid,
617
+ "name": compounds_by_cid[cid]["name"],
618
+ "smiles": compounds_by_cid[cid]["smiles"],
619
+ }
620
+ for cid in compounds_by_cid
621
+ },
622
+ )
623
+ )
624
+
625
+ if with_compounds:
626
+ data["missing"] = self.build_missing_compound_report(
627
+ equations_by_rid,
628
+ compounds_by_cid,
629
+ )
630
+ else:
631
+ data["missing"] = {
632
+ "missing_compounds": [],
633
+ "missing_compound_ids": [],
634
+ "reactions_involving_missing": [],
635
+ }
636
+
637
+ self.save_json(data, save_as)
638
+
639
+ return data
640
+
641
+ def build_pathway_json(
642
+ self,
643
+ pathway_id: str,
644
+ *,
645
+ with_compounds: bool = True,
646
+ with_atom_maps: bool = True,
647
+ save_as: Optional[str] = None,
648
+ ) -> JSONDict:
649
+ """Build a JSON block for a KEGG pathway, organized by module.
650
+
651
+ :param pathway_id:
652
+ KEGG pathway ID.
653
+ :type pathway_id: str
654
+ :param with_compounds:
655
+ Whether to resolve compound records.
656
+ :type with_compounds: bool
657
+ :param with_atom_maps:
658
+ Whether to compute atom-mapped reactions.
659
+ :type with_atom_maps: bool
660
+ :param save_as:
661
+ Optional output path for writing the JSON block to disk.
662
+ :type save_as: Optional[str]
663
+
664
+ :return:
665
+ Pathway JSON dictionary.
666
+ :rtype: dict[str, Any]
667
+
668
+ .. rubric:: Example
669
+
670
+ .. code-block:: python
671
+
672
+ data = extractor.build_pathway_json(
673
+ "hsa00010",
674
+ with_compounds=True,
675
+ with_atom_maps=False,
676
+ )
677
+ """
678
+ modules = self.get_modules_from_pathway(pathway_id)
679
+ by_module: dict[str, Any] = {}
680
+
681
+ aggregate_missing_ids: set[str] = set()
682
+ aggregate_reaction_ids: set[str] = set()
683
+
684
+ for module_id in modules:
685
+ module_block = self.build_module_json(
686
+ module_id,
687
+ with_compounds=with_compounds,
688
+ with_atom_maps=with_atom_maps,
689
+ )
690
+ by_module[module_id] = module_block
691
+
692
+ if with_compounds and "missing" in module_block:
693
+ aggregate_missing_ids.update(
694
+ module_block["missing"].get("missing_compound_ids", [])
695
+ )
696
+ aggregate_reaction_ids.update(
697
+ module_block["missing"].get("reactions_involving_missing", [])
698
+ )
699
+
700
+ data: JSONDict = {
701
+ "pathway_id": pathway_id,
702
+ "modules": modules,
703
+ "by_module": by_module,
704
+ }
705
+
706
+ if with_compounds:
707
+ data["missing"] = {
708
+ "missing_compound_ids": sorted(aggregate_missing_ids),
709
+ "reactions_involving_missing": sorted(aggregate_reaction_ids),
710
+ }
711
+
712
+ self.save_json(data, save_as)
713
+
714
+ return data