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
synkit/CRN/IO/sbml.py ADDED
@@ -0,0 +1,925 @@
1
+ """SBML import and export for :class:`~synkit.CRN.Structure.syncrn.SynCRN`.
2
+
3
+ SBML (Systems Biology Markup Language) is the interchange format of the
4
+ existing CRN ecosystem — ``crnpy``, CRNT4SBML, CoNtRol, COPASI and the BioModels
5
+ repository all read or write it. This module is the adapter, so a network built
6
+ by rule expansion here can be analysed there and vice versa.
7
+
8
+ The reader and writer are self-contained: they use only
9
+ :mod:`xml.etree.ElementTree` and target **SBML Level 3 Version 2 core**, which
10
+ is all a reaction network needs (compartments, species, reactions with
11
+ stoichiometry). No kinetic law is written, because a ``SynCRN`` carries none;
12
+ structural analysis does not need one. Files that *do* carry kinetic laws are
13
+ read fine — the laws are simply ignored.
14
+
15
+ Conversion policies:
16
+
17
+ - **Reversible reactions.** SBML marks a reaction ``reversible="true"``; a
18
+ ``SynCRN`` reaction is always directed. On import, a reversible reaction is
19
+ split into a forward and a backward reaction by default (``expand_reversible``),
20
+ which is what Chemical Reaction Network Theory expects.
21
+ - **Identifiers.** SBML ids must match ``[A-Za-z_][A-Za-z0-9_]*``. Species
22
+ labels are chemistry — SMILES strings, KEGG ids — and usually do not. The
23
+ writer therefore uses the internal ``SynCRN`` ids (``s_1``, ``r_1``) as SBML
24
+ ids and keeps the chemistry in the ``name`` attribute and a SynKit
25
+ annotation, so nothing is lost on a round-trip.
26
+
27
+ .. rubric:: Example
28
+
29
+ .. code-block:: python
30
+
31
+ from synkit.CRN import SynCRN
32
+ from synkit.CRN.IO import crn_from_sbml, crn_to_sbml
33
+
34
+ crn = SynCRN.from_reaction_strings(["2A>>B", "B>>2A"])
35
+ xml = crn_to_sbml(crn)
36
+ back = crn_from_sbml(xml)
37
+ print(back.to_equations(species="label", include_id=False))
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ import re
43
+ import warnings
44
+ import xml.etree.ElementTree as ET
45
+ from pathlib import Path
46
+ from typing import Any, Dict, List, Optional, Tuple, Union
47
+
48
+ import networkx as nx
49
+
50
+ from ..Structure.syncrn import SynCRN
51
+
52
+ __all__ = [
53
+ "SBML_NS",
54
+ "SYNKIT_NS",
55
+ "crn_from_sbml",
56
+ "crn_to_sbml",
57
+ "read_sbml",
58
+ "write_sbml",
59
+ ]
60
+
61
+ #: Namespace of the SBML Level 3 Version 2 core specification.
62
+ SBML_NS = "http://www.sbml.org/sbml/level3/version2/core"
63
+
64
+ #: Namespace used for SynKit-specific annotations (SMILES, source ids).
65
+ SYNKIT_NS = "https://github.com/TieuLongPhan/SynKit"
66
+
67
+ _SID_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
68
+ _SID_INVALID_RE = re.compile(r"[^A-Za-z0-9_]")
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Helpers
73
+ # ---------------------------------------------------------------------------
74
+
75
+
76
+ def _localname(tag: str) -> str:
77
+ """Strip the XML namespace from a tag.
78
+
79
+ SBML files in the wild use several namespace URIs across levels and
80
+ versions; matching on the local name keeps the reader version-tolerant.
81
+
82
+ :param tag:
83
+ Fully qualified tag such as ``"{ns}species"``.
84
+ :type tag: str
85
+
86
+ :return:
87
+ Local tag name.
88
+ :rtype: str
89
+ """
90
+ return tag.rsplit("}", 1)[-1]
91
+
92
+
93
+ def _find_children(element: ET.Element, name: str) -> List[ET.Element]:
94
+ """Find the direct children with a given local name.
95
+
96
+ Direct children only: an ``<annotation>`` block may legitimately contain
97
+ elements whose local name collides with an SBML one, and those must not be
98
+ mistaken for model content.
99
+
100
+ :param element:
101
+ Parent element.
102
+ :type element: xml.etree.ElementTree.Element
103
+
104
+ :param name:
105
+ Local tag name to match.
106
+ :type name: str
107
+
108
+ :return:
109
+ Matching children in document order.
110
+ :rtype: List[xml.etree.ElementTree.Element]
111
+ """
112
+ return [child for child in element if _localname(child.tag) == name]
113
+
114
+
115
+ def _find_descendants(element: ET.Element, name: str) -> List[ET.Element]:
116
+ """Find all descendants with a given local name.
117
+
118
+ :param element:
119
+ Element to search below.
120
+ :type element: xml.etree.ElementTree.Element
121
+
122
+ :param name:
123
+ Local tag name to match.
124
+ :type name: str
125
+
126
+ :return:
127
+ Matching descendants in document order.
128
+ :rtype: List[xml.etree.ElementTree.Element]
129
+ """
130
+ return [child for child in element.iter() if _localname(child.tag) == name]
131
+
132
+
133
+ def _find_child(element: ET.Element, name: str) -> Optional[ET.Element]:
134
+ """Find the first direct child with a given local name.
135
+
136
+ :param element:
137
+ Parent element.
138
+ :type element: xml.etree.ElementTree.Element
139
+
140
+ :param name:
141
+ Local tag name to match.
142
+ :type name: str
143
+
144
+ :return:
145
+ The child, or ``None`` when absent.
146
+ :rtype: Optional[xml.etree.ElementTree.Element]
147
+ """
148
+ for child in element:
149
+ if _localname(child.tag) == name:
150
+ return child
151
+ return None
152
+
153
+
154
+ def _as_sid(value: Any, *, fallback: str) -> str:
155
+ """Coerce a value into a valid SBML ``SId``.
156
+
157
+ :param value:
158
+ Candidate identifier.
159
+ :type value: Any
160
+
161
+ :param fallback:
162
+ Identifier used when ``value`` cannot be sanitized into a valid SId.
163
+ :type fallback: str
164
+
165
+ :return:
166
+ Valid SBML identifier.
167
+ :rtype: str
168
+ """
169
+ text = str(value)
170
+ if _SID_RE.match(text):
171
+ return text
172
+
173
+ cleaned = _SID_INVALID_RE.sub("_", text)
174
+ if cleaned and not cleaned[0].isalpha() and cleaned[0] != "_":
175
+ cleaned = f"_{cleaned}"
176
+ return cleaned if _SID_RE.match(cleaned) else fallback
177
+
178
+
179
+ def _format_stoich(value: Any) -> str:
180
+ """Render a stoichiometric coefficient for an SBML attribute.
181
+
182
+ :param value:
183
+ Coefficient value.
184
+ :type value: Any
185
+
186
+ :return:
187
+ String form without a redundant ``.0`` suffix.
188
+ :rtype: str
189
+ """
190
+ number = float(value)
191
+ return str(int(number)) if number.is_integer() else repr(number)
192
+
193
+
194
+ def _parse_stoich(value: Optional[str]) -> float:
195
+ """Parse an SBML stoichiometry attribute, defaulting to 1.
196
+
197
+ :param value:
198
+ Raw attribute text, possibly ``None``.
199
+ :type value: Optional[str]
200
+
201
+ :return:
202
+ Stoichiometric coefficient.
203
+ :rtype: float
204
+ """
205
+ if value is None or value == "":
206
+ return 1.0
207
+ try:
208
+ return float(value)
209
+ except ValueError:
210
+ return 1.0
211
+
212
+
213
+ def _is_true(value: Optional[str]) -> bool:
214
+ """Interpret an SBML boolean attribute.
215
+
216
+ :param value:
217
+ Raw attribute text, possibly ``None``.
218
+ :type value: Optional[str]
219
+
220
+ :return:
221
+ ``True`` when the attribute spells a true value.
222
+ :rtype: bool
223
+ """
224
+ return str(value).strip().lower() in {"true", "1"}
225
+
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # Export
229
+ # ---------------------------------------------------------------------------
230
+
231
+
232
+ def _species_element(
233
+ parent: ET.Element,
234
+ *,
235
+ sid: str,
236
+ species: Any,
237
+ compartment_id: str,
238
+ ) -> None:
239
+ """Append one ``<species>`` element for a SynCRN species record.
240
+
241
+ :param parent:
242
+ The ``<listOfSpecies>`` element.
243
+ :type parent: xml.etree.ElementTree.Element
244
+
245
+ :param sid:
246
+ SBML species id.
247
+ :type sid: str
248
+
249
+ :param species:
250
+ SynCRN species record.
251
+ :type species: Any
252
+
253
+ :param compartment_id:
254
+ Id of the compartment the species belongs to.
255
+ :type compartment_id: str
256
+
257
+ :return:
258
+ ``None``. The element is appended in place.
259
+ :rtype: None
260
+ """
261
+ element = ET.SubElement(
262
+ parent,
263
+ "species",
264
+ {
265
+ "id": sid,
266
+ "compartment": compartment_id,
267
+ "hasOnlySubstanceUnits": "true",
268
+ "boundaryCondition": "false",
269
+ "constant": "false",
270
+ },
271
+ )
272
+ for key in (
273
+ "compartment",
274
+ "boundaryCondition",
275
+ "constant",
276
+ "initialAmount",
277
+ "initialConcentration",
278
+ "substanceUnits",
279
+ "hasOnlySubstanceUnits",
280
+ ):
281
+ if key in species.source_attrs:
282
+ element.set(key, str(species.source_attrs[key]))
283
+ if species.label is not None:
284
+ element.set("name", str(species.label))
285
+
286
+ extra = {
287
+ key: value
288
+ for key, value in (
289
+ ("smiles", species.smiles),
290
+ ("sourceNodeId", species.source_node_id),
291
+ ("syncrnId", species.id),
292
+ )
293
+ if value is not None
294
+ }
295
+ if extra:
296
+ annotation = ET.SubElement(element, "annotation")
297
+ ET.SubElement(
298
+ annotation,
299
+ f"{{{SYNKIT_NS}}}speciesInfo",
300
+ {key: str(value) for key, value in extra.items()},
301
+ )
302
+
303
+
304
+ def _reaction_element(
305
+ parent: ET.Element,
306
+ *,
307
+ rid: str,
308
+ reaction: Any,
309
+ species_sids: Dict[str, str],
310
+ ) -> None:
311
+ """Append one ``<reaction>`` element for a SynCRN reaction record.
312
+
313
+ :param parent:
314
+ The ``<listOfReactions>`` element.
315
+ :type parent: xml.etree.ElementTree.Element
316
+
317
+ :param rid:
318
+ SBML reaction id.
319
+ :type rid: str
320
+
321
+ :param reaction:
322
+ SynCRN reaction record.
323
+ :type reaction: Any
324
+
325
+ :param species_sids:
326
+ Mapping from internal species id to SBML species id.
327
+ :type species_sids: Dict[str, str]
328
+
329
+ :return:
330
+ ``None``. The element is appended in place.
331
+ :rtype: None
332
+ """
333
+ element = ET.SubElement(
334
+ parent,
335
+ "reaction",
336
+ {"id": rid, "reversible": "false"},
337
+ )
338
+ if reaction.label is not None:
339
+ element.set("name", str(reaction.label))
340
+
341
+ for side, container, tag in (
342
+ (reaction.lhs, "listOfReactants", "speciesReference"),
343
+ (reaction.rhs, "listOfProducts", "speciesReference"),
344
+ ):
345
+ items = list(side.items())
346
+ if not items:
347
+ continue
348
+ listing = ET.SubElement(element, container)
349
+ for internal_sid, coeff in items:
350
+ ET.SubElement(
351
+ listing,
352
+ tag,
353
+ {
354
+ "species": species_sids[internal_sid],
355
+ "stoichiometry": _format_stoich(coeff),
356
+ "constant": "true",
357
+ },
358
+ )
359
+
360
+ extra = {
361
+ key: value
362
+ for key, value in (
363
+ ("syncrnId", reaction.id),
364
+ ("sourceNodeId", reaction.source_node_id),
365
+ ("sourceKind", reaction.source_kind),
366
+ ("ruleId", reaction.rule_id),
367
+ ("ruleRepr", reaction.rule_repr),
368
+ )
369
+ if value is not None
370
+ }
371
+ if extra:
372
+ annotation = ET.SubElement(element, "annotation")
373
+ ET.SubElement(
374
+ annotation,
375
+ f"{{{SYNKIT_NS}}}reactionInfo",
376
+ {key: str(value) for key, value in extra.items()},
377
+ )
378
+
379
+
380
+ def crn_to_sbml(
381
+ crn: SynCRN,
382
+ *,
383
+ model_id: str = "synkit_crn",
384
+ model_name: Optional[str] = None,
385
+ compartment_id: str = "cell",
386
+ pretty: bool = True,
387
+ ) -> str:
388
+ """Serialize a :class:`SynCRN` to an SBML Level 3 Version 2 document.
389
+
390
+ Species and reactions keep their internal ids as SBML ids and their labels
391
+ as SBML names; SMILES and provenance travel in a SynKit annotation, so
392
+ :func:`crn_from_sbml` reproduces the network exactly. No kinetic law is
393
+ emitted — a ``SynCRN`` carries none, and structural analysis needs none.
394
+
395
+ :param crn:
396
+ Network to serialize.
397
+ :type crn: SynCRN
398
+
399
+ :param model_id:
400
+ SBML model identifier.
401
+ :type model_id: str
402
+
403
+ :param model_name:
404
+ Optional human-readable model name.
405
+ :type model_name: Optional[str]
406
+
407
+ :param compartment_id:
408
+ Identifier of the single compartment every species is placed in.
409
+ :type compartment_id: str
410
+
411
+ :param pretty:
412
+ Whether to indent the output for readability.
413
+ :type pretty: bool
414
+
415
+ :return:
416
+ SBML document as a UTF-8 string.
417
+ :rtype: str
418
+
419
+ :raises TypeError:
420
+ If ``crn`` is not a :class:`SynCRN`.
421
+
422
+ .. rubric:: Example
423
+
424
+ .. code-block:: python
425
+
426
+ xml = crn_to_sbml(SynCRN.from_reaction_strings(["2A>>B"]))
427
+ print("<reaction" in xml)
428
+ # True
429
+ """
430
+ if not isinstance(crn, SynCRN):
431
+ raise TypeError(f"crn must be a SynCRN, got {type(crn).__name__}")
432
+
433
+ root = ET.Element(
434
+ f"{{{SBML_NS}}}sbml",
435
+ {"level": "3", "version": "2"},
436
+ )
437
+ model_attrs = {"id": _as_sid(model_id, fallback="synkit_crn")}
438
+ if model_name is not None:
439
+ model_attrs["name"] = model_name
440
+ model = ET.SubElement(root, "model", model_attrs)
441
+
442
+ compartment_sid = _as_sid(compartment_id, fallback="cell")
443
+ compartments = ET.SubElement(model, "listOfCompartments")
444
+ ET.SubElement(
445
+ compartments,
446
+ "compartment",
447
+ {"id": compartment_sid, "spatialDimensions": "3", "constant": "true"},
448
+ )
449
+
450
+ for name in sorted(
451
+ {
452
+ sp.source_attrs.get("compartment", compartment_sid)
453
+ for sp in crn.species.values()
454
+ }
455
+ - {compartment_sid}
456
+ ):
457
+ ET.SubElement(compartments, "compartment", {"id": name, "constant": "true"})
458
+ species_sids: Dict[str, str] = {}
459
+ if crn.species:
460
+ listing = ET.SubElement(model, "listOfSpecies")
461
+ for i, (internal_sid, species) in enumerate(crn.species.items(), start=1):
462
+ sid = _as_sid(internal_sid, fallback=f"s_{i}")
463
+ species_sids[internal_sid] = sid
464
+ _species_element(
465
+ listing,
466
+ sid=sid,
467
+ species=species,
468
+ compartment_id=compartment_sid,
469
+ )
470
+
471
+ if crn.reactions:
472
+ listing = ET.SubElement(model, "listOfReactions")
473
+ for j, (internal_rid, reaction) in enumerate(crn.reactions.items(), start=1):
474
+ _reaction_element(
475
+ listing,
476
+ rid=_as_sid(internal_rid, fallback=f"r_{j}"),
477
+ reaction=reaction,
478
+ species_sids=species_sids,
479
+ )
480
+
481
+ if pretty:
482
+ ET.indent(root, space=" ")
483
+
484
+ ET.register_namespace("", SBML_NS)
485
+ ET.register_namespace("synkit", SYNKIT_NS)
486
+ body = ET.tostring(root, encoding="unicode")
487
+ return f'<?xml version="1.0" encoding="UTF-8"?>\n{body}\n'
488
+
489
+
490
+ def write_sbml(crn: SynCRN, path: Union[str, Path], **kwargs: Any) -> Path:
491
+ """Write a :class:`SynCRN` to an SBML file.
492
+
493
+ :param crn:
494
+ Network to serialize.
495
+ :type crn: SynCRN
496
+
497
+ :param path:
498
+ Destination file path.
499
+ :type path: Union[str, Path]
500
+
501
+ :param kwargs:
502
+ Extra keyword arguments forwarded to :func:`crn_to_sbml`.
503
+ :type kwargs: Any
504
+
505
+ :return:
506
+ The path written to.
507
+ :rtype: pathlib.Path
508
+
509
+ .. rubric:: Example
510
+
511
+ .. code-block:: python
512
+
513
+ write_sbml(crn, "network.xml")
514
+ """
515
+ destination = Path(path)
516
+ destination.write_text(crn_to_sbml(crn, **kwargs), encoding="utf-8")
517
+ return destination
518
+
519
+
520
+ # ---------------------------------------------------------------------------
521
+ # Import
522
+ # ---------------------------------------------------------------------------
523
+
524
+
525
+ def _annotation_values(element: ET.Element, name: str) -> Dict[str, str]:
526
+ """Read the attributes of a SynKit annotation child, if present.
527
+
528
+ :param element:
529
+ Species or reaction element.
530
+ :type element: xml.etree.ElementTree.Element
531
+
532
+ :param name:
533
+ Local name of the SynKit annotation element.
534
+ :type name: str
535
+
536
+ :return:
537
+ Annotation attributes, empty when the annotation is absent.
538
+ :rtype: Dict[str, str]
539
+ """
540
+ annotation = _find_child(element, "annotation")
541
+ if annotation is None:
542
+ return {}
543
+ for child in annotation.iter():
544
+ if _localname(child.tag) == name:
545
+ return dict(child.attrib)
546
+ return {}
547
+
548
+
549
+ def _read_species(
550
+ model: ET.Element,
551
+ *,
552
+ drop_boundary_species: bool,
553
+ ) -> Tuple[Dict[str, Dict[str, Any]], set]:
554
+ """Collect species definitions from an SBML model element.
555
+
556
+ :param model:
557
+ The ``<model>`` element.
558
+ :type model: xml.etree.ElementTree.Element
559
+
560
+ :param drop_boundary_species:
561
+ Whether species with ``boundaryCondition="true"`` should be excluded.
562
+ :type drop_boundary_species: bool
563
+
564
+ :return:
565
+ Pair ``(species_attrs, dropped_ids)``.
566
+ :rtype: Tuple[Dict[str, Dict[str, Any]], set]
567
+ """
568
+ listing = _find_child(model, "listOfSpecies")
569
+ species: Dict[str, Dict[str, Any]] = {}
570
+ dropped: set = set()
571
+
572
+ for element in [] if listing is None else _find_children(listing, "species"):
573
+ sid = element.get("id")
574
+ if sid is None:
575
+ continue
576
+ if drop_boundary_species and _is_true(element.get("boundaryCondition")):
577
+ dropped.add(sid)
578
+ continue
579
+
580
+ annotation = _annotation_values(element, "speciesInfo")
581
+ species[sid] = {
582
+ "kind": "species",
583
+ "label": element.get("name") or sid,
584
+ "smiles": annotation.get("smiles"),
585
+ "sbml_id": sid,
586
+ **{
587
+ _localname(key): value
588
+ for key, value in element.attrib.items()
589
+ if _localname(key) in {"chemicalFormula", "charge"}
590
+ },
591
+ **{
592
+ key: element.get(key)
593
+ for key in (
594
+ "compartment",
595
+ "boundaryCondition",
596
+ "constant",
597
+ "initialAmount",
598
+ "initialConcentration",
599
+ "substanceUnits",
600
+ "hasOnlySubstanceUnits",
601
+ )
602
+ if element.get(key) is not None
603
+ },
604
+ "source_node_id": annotation.get("sourceNodeId", sid),
605
+ }
606
+
607
+ return species, dropped
608
+
609
+
610
+ def _side_references(
611
+ reaction: ET.Element,
612
+ container: str,
613
+ ) -> List[Tuple[str, float]]:
614
+ """Collect ``(species id, stoichiometry)`` pairs from one reaction side.
615
+
616
+ Repeated references to the same species are summed, as SBML allows.
617
+
618
+ :param reaction:
619
+ The ``<reaction>`` element.
620
+ :type reaction: xml.etree.ElementTree.Element
621
+
622
+ :param container:
623
+ Either ``"listOfReactants"`` or ``"listOfProducts"``.
624
+ :type container: str
625
+
626
+ :return:
627
+ Species references with their coefficients.
628
+ :rtype: List[Tuple[str, float]]
629
+ """
630
+ listing = _find_child(reaction, container)
631
+ if listing is None:
632
+ return []
633
+
634
+ totals: Dict[str, float] = {}
635
+ for reference in _find_children(listing, "speciesReference"):
636
+ target = reference.get("species")
637
+ if target is None:
638
+ continue
639
+ totals[target] = totals.get(target, 0.0) + _parse_stoich(
640
+ reference.get("stoichiometry")
641
+ )
642
+ return list(totals.items())
643
+
644
+
645
+ def _add_reaction_node(
646
+ graph: nx.DiGraph,
647
+ *,
648
+ node_id: str,
649
+ label: str,
650
+ reactants: List[Tuple[str, float]],
651
+ products: List[Tuple[str, float]],
652
+ known_species: Dict[str, Dict[str, Any]],
653
+ extra: Dict[str, Any],
654
+ ) -> None:
655
+ """Add one reaction node and its incidence edges to the working graph.
656
+
657
+ :param graph:
658
+ Bipartite graph under construction.
659
+ :type graph: networkx.DiGraph
660
+
661
+ :param node_id:
662
+ Node id for the reaction.
663
+ :type node_id: str
664
+
665
+ :param label:
666
+ Reaction label.
667
+ :type label: str
668
+
669
+ :param reactants:
670
+ Reactant references.
671
+ :type reactants: List[Tuple[str, float]]
672
+
673
+ :param products:
674
+ Product references.
675
+ :type products: List[Tuple[str, float]]
676
+
677
+ :param known_species:
678
+ Species definitions keyed by SBML id; references outside this mapping
679
+ are skipped.
680
+ :type known_species: Dict[str, Dict[str, Any]]
681
+
682
+ :param extra:
683
+ Additional node attributes.
684
+ :type extra: Dict[str, Any]
685
+
686
+ :return:
687
+ ``None``. The graph is modified in place.
688
+ :rtype: None
689
+ """
690
+ graph.add_node(node_id, kind="reaction", label=label, **extra)
691
+
692
+ for sid, coeff in reactants:
693
+ if sid in known_species:
694
+ graph.add_edge(sid, node_id, role="reactant", stoich=_normalize(coeff))
695
+ for sid, coeff in products:
696
+ if sid in known_species:
697
+ graph.add_edge(node_id, sid, role="product", stoich=_normalize(coeff))
698
+
699
+
700
+ def _normalize(coeff: float) -> Union[int, float]:
701
+ """Return an integer coefficient when the value is integral.
702
+
703
+ :param coeff:
704
+ Coefficient value.
705
+ :type coeff: float
706
+
707
+ :return:
708
+ Integer or float coefficient.
709
+ :rtype: Union[int, float]
710
+ """
711
+ return int(coeff) if float(coeff).is_integer() else coeff
712
+
713
+
714
+ def _warn_no_reactions(model: ET.Element) -> None:
715
+ """Warn that an SBML model declares no reactions, and say why if possible.
716
+
717
+ An empty network is a valid but almost always unintended result. The usual
718
+ cause is a model that is not a reaction network at all: SBML-``qual``
719
+ logical models carry ``<listOfTransitions>`` over ``<listOfQualitativeSpecies>``
720
+ and have no ``<listOfReactions>``, and returning ``0`` species silently
721
+ looks like a parse failure to the caller.
722
+
723
+ :param model:
724
+ The ``<model>`` element.
725
+ :type model: xml.etree.ElementTree.Element
726
+
727
+ :return:
728
+ ``None``. A :class:`UserWarning` is issued.
729
+ :rtype: None
730
+ """
731
+ hints = []
732
+ if _find_child(model, "listOfQualitativeSpecies") is not None or (
733
+ _find_child(model, "listOfTransitions") is not None
734
+ ):
735
+ hints.append(
736
+ "the model uses the SBML qual package (qualitative species and "
737
+ "transitions), which describes a logical model rather than a "
738
+ "reaction network"
739
+ )
740
+ if _find_child(model, "listOfSpecies") is not None:
741
+ hints.append("species are declared but no reactions connect them")
742
+
743
+ detail = f" ({'; '.join(hints)})" if hints else ""
744
+ warnings.warn(
745
+ f"SBML model {model.get('id')!r} declares no reactions{detail}; "
746
+ "the resulting network is empty.",
747
+ UserWarning,
748
+ stacklevel=3,
749
+ )
750
+
751
+
752
+ def crn_from_sbml(
753
+ source: Union[str, Path, ET.Element],
754
+ *,
755
+ expand_reversible: bool = True,
756
+ drop_boundary_species: bool = False,
757
+ strict: bool = False,
758
+ **syncrn_kwargs: Any,
759
+ ) -> SynCRN:
760
+ """Build a :class:`SynCRN` from an SBML document.
761
+
762
+ The reader matches elements by local name, so SBML Level 2 and Level 3 files
763
+ both parse. Kinetic laws, units, parameters, events and rules are ignored:
764
+ they carry no structural information.
765
+
766
+ :param source:
767
+ SBML document text, a path to an SBML file, or a parsed XML element.
768
+ :type source: Union[str, pathlib.Path, xml.etree.ElementTree.Element]
769
+
770
+ :param expand_reversible:
771
+ Whether a reaction marked ``reversible="true"`` is split into a forward
772
+ and a backward reaction. Leave enabled for CRNT analysis.
773
+ :type expand_reversible: bool
774
+
775
+ :param drop_boundary_species:
776
+ Whether species with ``boundaryCondition="true"`` are excluded together
777
+ with their incidences. Useful when a model uses boundary species as
778
+ constant sources or sinks.
779
+ :type drop_boundary_species: bool
780
+
781
+ :param strict:
782
+ Whether malformed network structure should raise; forwarded to
783
+ :meth:`SynCRN.from_digraph`. Defaults to ``False`` because published
784
+ SBML models routinely contain reactions with an empty side.
785
+ :type strict: bool
786
+
787
+ :param syncrn_kwargs:
788
+ Extra keyword arguments forwarded to :meth:`SynCRN.from_digraph`, such
789
+ as ``id_style``.
790
+ :type syncrn_kwargs: Any
791
+
792
+ :return:
793
+ Canonical network object.
794
+ :rtype: SynCRN
795
+
796
+ :raises ValueError:
797
+ If the document contains no ``<model>`` element.
798
+
799
+ .. rubric:: Example
800
+
801
+ .. code-block:: python
802
+
803
+ crn = crn_from_sbml("model.xml")
804
+ print(crn.n_species, crn.n_reactions)
805
+ """
806
+ root = _resolve_root(source)
807
+ model = root if _localname(root.tag) == "model" else _find_child(root, "model")
808
+ if model is None:
809
+ found = _find_descendants(root, "model")
810
+ model = found[0] if found else None
811
+ if model is None:
812
+ raise ValueError("SBML document contains no <model> element")
813
+
814
+ species, _dropped = _read_species(
815
+ model, drop_boundary_species=drop_boundary_species
816
+ )
817
+
818
+ graph = nx.DiGraph()
819
+ graph.graph["sbml_model_id"] = model.get("id")
820
+ if model.get("name"):
821
+ graph.graph["sbml_model_name"] = model.get("name")
822
+
823
+ for sid, attrs in species.items():
824
+ graph.add_node(sid, **attrs)
825
+
826
+ listing = _find_child(model, "listOfReactions")
827
+ if listing is None or not _find_children(listing, "reaction"):
828
+ _warn_no_reactions(model)
829
+
830
+ for index, element in enumerate(
831
+ [] if listing is None else _find_children(listing, "reaction"), start=1
832
+ ):
833
+ rid = element.get("id") or f"reaction_{index}"
834
+ label = element.get("name") or rid
835
+ reactants = _side_references(element, "listOfReactants")
836
+ products = _side_references(element, "listOfProducts")
837
+ reversible = _is_true(element.get("reversible"))
838
+ annotation = _annotation_values(element, "reactionInfo")
839
+
840
+ extra: Dict[str, Any] = {
841
+ "sbml_id": rid,
842
+ "sbml_reversible": reversible,
843
+ "source_node_id": annotation.get("sourceNodeId", rid),
844
+ }
845
+ if annotation.get("sourceKind"):
846
+ extra["source_kind"] = annotation["sourceKind"]
847
+ if annotation.get("ruleRepr"):
848
+ extra["rule_repr"] = annotation["ruleRepr"]
849
+
850
+ _add_reaction_node(
851
+ graph,
852
+ node_id=rid,
853
+ label=label,
854
+ reactants=reactants,
855
+ products=products,
856
+ known_species=species,
857
+ extra=extra,
858
+ )
859
+
860
+ if reversible and expand_reversible:
861
+ _add_reaction_node(
862
+ graph,
863
+ node_id=f"{rid}_rev",
864
+ label=f"{label}_rev",
865
+ reactants=products,
866
+ products=reactants,
867
+ known_species=species,
868
+ extra={**extra, "sbml_reverse_of": rid},
869
+ )
870
+
871
+ return SynCRN.from_digraph(graph, strict=strict, **syncrn_kwargs)
872
+
873
+
874
+ def _resolve_root(source: Union[str, Path, ET.Element]) -> ET.Element:
875
+ """Resolve the input into a parsed XML root element.
876
+
877
+ A string is treated as a file path when it names an existing file and as
878
+ document text otherwise.
879
+
880
+ :param source:
881
+ SBML document text, a path, or an already parsed element.
882
+ :type source: Union[str, pathlib.Path, xml.etree.ElementTree.Element]
883
+
884
+ :return:
885
+ Root element.
886
+ :rtype: xml.etree.ElementTree.Element
887
+
888
+ :raises FileNotFoundError:
889
+ If a :class:`~pathlib.Path` is given that does not exist.
890
+ """
891
+ if isinstance(source, ET.Element):
892
+ return source
893
+
894
+ if isinstance(source, Path):
895
+ return ET.parse(source).getroot()
896
+
897
+ text = str(source)
898
+ stripped = text.lstrip()
899
+ if stripped.startswith("<"):
900
+ return ET.fromstring(text)
901
+ return ET.parse(Path(text)).getroot()
902
+
903
+
904
+ def read_sbml(path: Union[str, Path], **kwargs: Any) -> SynCRN:
905
+ """Read a :class:`SynCRN` from an SBML file.
906
+
907
+ :param path:
908
+ Path to the SBML file.
909
+ :type path: Union[str, pathlib.Path]
910
+
911
+ :param kwargs:
912
+ Extra keyword arguments forwarded to :func:`crn_from_sbml`.
913
+ :type kwargs: Any
914
+
915
+ :return:
916
+ Canonical network object.
917
+ :rtype: SynCRN
918
+
919
+ .. rubric:: Example
920
+
921
+ .. code-block:: python
922
+
923
+ crn = read_sbml("BIOMD0000000001.xml")
924
+ """
925
+ return crn_from_sbml(Path(path), **kwargs)