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,751 @@
1
+ from __future__ import annotations
2
+
3
+ from contextvars import ContextVar
4
+ from dataclasses import dataclass, field
5
+ from fractions import Fraction
6
+ from functools import wraps
7
+ from math import gcd
8
+ from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
9
+
10
+ import numpy as np
11
+
12
+ try:
13
+ from scipy.linalg import null_space as scipy_null_space # type: ignore
14
+
15
+ _SCIPY_AVAILABLE = True
16
+ except Exception:
17
+ scipy_null_space = None # type: ignore
18
+ _SCIPY_AVAILABLE = False
19
+
20
+ from .helper import (
21
+ _as_graph,
22
+ _edge_coeff,
23
+ _is_rule_node,
24
+ _is_species_node,
25
+ _normalize_role,
26
+ _species_and_rule_order,
27
+ )
28
+
29
+ __all__ = [
30
+ "build_S_minus_plus",
31
+ "build_S",
32
+ "stoichiometric_matrix",
33
+ "stoichiometric_rank",
34
+ "left_nullspace",
35
+ "right_nullspace",
36
+ "left_right_kernels",
37
+ "integer_conservation_laws",
38
+ "conserved_moieties",
39
+ "StoichSummary",
40
+ "summary",
41
+ ]
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # S⁻, S⁺ and S = S⁺ − S⁻
45
+ # ---------------------------------------------------------------------------
46
+
47
+
48
+ def _resolve_species_rule_incidence(
49
+ G: Any,
50
+ u: Any,
51
+ v: Any,
52
+ ) -> Optional[Tuple[Any, Any]]:
53
+ """Resolve an edge endpoint pair into a ``(species_node, rule_node)`` pair.
54
+
55
+ This helper inspects node metadata and accepts either orientation
56
+ ``species -> rule`` or ``rule -> species``. If the endpoints do not form
57
+ a valid species-rule incidence, ``None`` is returned.
58
+
59
+ :param G: Graph containing the edge endpoints.
60
+ :type G: Any
61
+ :param u: First endpoint.
62
+ :type u: Any
63
+ :param v: Second endpoint.
64
+ :type v: Any
65
+ :return:
66
+ A pair ``(species_node, rule_node)`` when the endpoints define a valid
67
+ species-rule incidence, otherwise ``None``.
68
+ :rtype: Optional[Tuple[Any, Any]]
69
+ """
70
+ u_data = G.nodes[u]
71
+ v_data = G.nodes[v]
72
+
73
+ if _is_species_node(u_data) and _is_rule_node(v_data):
74
+ return u, v
75
+ if _is_rule_node(u_data) and _is_species_node(v_data):
76
+ return v, u
77
+ return None
78
+
79
+
80
+ def _accumulate_stoich_entry(
81
+ S_minus: np.ndarray,
82
+ S_plus: np.ndarray,
83
+ i: int,
84
+ j: int,
85
+ role: Optional[str],
86
+ coeff: float,
87
+ ) -> None:
88
+ """Accumulate one stoichiometric coefficient into ``S^-`` or ``S^+``.
89
+
90
+ :param S_minus: Reactant stoichiometric matrix.
91
+ :type S_minus: np.ndarray
92
+ :param S_plus: Product stoichiometric matrix.
93
+ :type S_plus: np.ndarray
94
+ :param i: Species row index.
95
+ :type i: int
96
+ :param j: Rule column index.
97
+ :type j: int
98
+ :param role:
99
+ Normalized edge role. Expected values are ``"reactant"``,
100
+ ``"product"``, or ``None``.
101
+ :type role: Optional[str]
102
+ :param coeff: Stoichiometric coefficient to add.
103
+ :type coeff: float
104
+ :return: ``None``. The matrices are modified in place.
105
+ :rtype: None
106
+ """
107
+ if role == "reactant":
108
+ S_minus[i, j] += coeff
109
+ elif role == "product":
110
+ S_plus[i, j] += coeff
111
+
112
+
113
+ def _iter_graph_edges_with_data(G: Any) -> Iterable[Tuple[Any, Any, Dict[str, Any]]]:
114
+ """Yield graph edges as ``(u, v, data)`` triples for simple and multigraphs.
115
+
116
+ For multigraphs, parallel edges are yielded individually while discarding
117
+ the internal edge key.
118
+
119
+ :param G: NetworkX graph-like object.
120
+ :type G: Any
121
+ :return:
122
+ Iterable of edge triples ``(u, v, data)`` where ``data`` is the edge
123
+ attribute mapping.
124
+ :rtype: Iterable[Tuple[Any, Any, Dict[str, Any]]]
125
+ """
126
+ if G.is_multigraph():
127
+ for u, v, _k, data in G.edges(data=True, keys=True):
128
+ yield u, v, data
129
+ else:
130
+ for u, v, data in G.edges(data=True):
131
+ yield u, v, data
132
+
133
+
134
+ def build_S_minus_plus(
135
+ crn: Any,
136
+ ) -> Tuple[List[Any], List[Any], np.ndarray, np.ndarray]:
137
+ """Build the reactant matrix ``S^-`` and product matrix ``S^+`` from a SynCRN.
138
+
139
+ The input is interpreted as a bipartite species-rule incidence graph. Rows
140
+ correspond to species nodes and columns correspond to rule nodes.
141
+
142
+ Graph conventions
143
+ -----------------
144
+ Nodes:
145
+ - species nodes have ``kind="species"``
146
+ - rule nodes have ``kind="rule"``
147
+
148
+ Edges:
149
+ - ``role``: ``"reactant"`` or ``"product"``
150
+ - ``stoich``: stoichiometric coefficient, default ``1.0``
151
+
152
+ Orientation
153
+ -----------
154
+ The graph may be directed or undirected. The edge ``role`` attribute is
155
+ treated as the source of truth, and endpoint order is not assumed to encode
156
+ reactant/product direction.
157
+
158
+ :param crn:
159
+ A NetworkX bipartite graph or a SynCRN-like object containing one.
160
+ :type crn: Any
161
+ :return:
162
+ A 4-tuple ``(species_order, rule_order, S_minus, S_plus)`` where
163
+ ``species_order`` defines row order, ``rule_order`` defines column
164
+ order, ``S_minus`` contains reactant stoichiometries, and ``S_plus``
165
+ contains product stoichiometries.
166
+ :rtype: Tuple[List[Any], List[Any], np.ndarray, np.ndarray]
167
+ """
168
+ cache = _MATRIX_CONTEXT.get()
169
+ if cache is not None and id(crn) in cache:
170
+ return cache[id(crn)][1]
171
+ G = _as_graph(crn)
172
+
173
+ species_order, rule_order, species_index, rule_index = _species_and_rule_order(G)
174
+
175
+ n_species = len(species_order)
176
+ n_rules = len(rule_order)
177
+
178
+ S_minus = np.zeros((n_species, n_rules), dtype=float)
179
+ S_plus = np.zeros((n_species, n_rules), dtype=float)
180
+
181
+ infer_roles = G.is_directed() and not any(
182
+ data.get("role") is not None for _, _, data in _iter_graph_edges_with_data(G)
183
+ )
184
+ for u, v, data in _iter_graph_edges_with_data(G):
185
+ role = _normalize_role(data.get("role"))
186
+ if role is None and data.get("role") is not None:
187
+ continue
188
+ if role is None and infer_roles:
189
+ role = "reactant" if _is_species_node(G.nodes[u]) else "product"
190
+ if role is None:
191
+ continue
192
+
193
+ incidence = _resolve_species_rule_incidence(G, u, v)
194
+ if incidence is None:
195
+ continue
196
+
197
+ s_node, r_node = incidence
198
+ i = species_index[s_node]
199
+ j = rule_index[r_node]
200
+ coeff = _edge_coeff(data)
201
+
202
+ _accumulate_stoich_entry(S_minus, S_plus, i, j, role, coeff)
203
+
204
+ result = (species_order, rule_order, S_minus, S_plus)
205
+ if cache is not None:
206
+ cache[id(crn)] = (crn, result)
207
+ return result
208
+
209
+
210
+ def build_S(crn: Any) -> Tuple[List[Any], List[Any], np.ndarray]:
211
+ """Build the stoichiometric matrix ``S = S^+ - S^-``.
212
+
213
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
214
+ :type crn: Any
215
+ :return:
216
+ A 3-tuple ``(species_order, rule_order, S)`` where ``S`` is the species
217
+ x rule stoichiometric matrix.
218
+ :rtype: Tuple[List[Any], List[Any], np.ndarray]
219
+ """
220
+ species_order, rule_order, S_minus, S_plus = build_S_minus_plus(crn)
221
+ S = S_plus - S_minus
222
+ return species_order, rule_order, S
223
+
224
+
225
+ def stoichiometric_matrix(crn: Any) -> np.ndarray:
226
+ """Return the species x rule stoichiometric matrix ``S``.
227
+
228
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
229
+ :type crn: Any
230
+ :return: Stoichiometric matrix with species as rows and rule nodes as columns.
231
+ :rtype: np.ndarray
232
+ """
233
+ _, _, S = build_S(crn)
234
+ return S
235
+
236
+
237
+ def stoichiometric_rank(
238
+ crn: Any, *, tol: float = 1e-10, backend="auto", library_path=None
239
+ ) -> int:
240
+ """Compute the numerical rank of the stoichiometric matrix.
241
+
242
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
243
+ :type crn: Any
244
+ :param tol: Numerical tolerance passed to ``numpy.linalg.matrix_rank``.
245
+ :type tol: float
246
+ :return: Rank of the stoichiometric matrix.
247
+ :rtype: int
248
+ """
249
+ from ..backend import resolve_backend
250
+
251
+ backend, library_path = resolve_backend(backend, library_path)
252
+ if backend == "cpp":
253
+ from ..native_analysis import matrix_analysis
254
+
255
+ return matrix_analysis(
256
+ stoichiometric_matrix(crn), library_path=library_path, kernels="none"
257
+ )["rank"]
258
+ S = stoichiometric_matrix(crn)
259
+ return int(np.linalg.matrix_rank(S, tol=tol))
260
+
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # Nullspaces: left and right kernels
264
+ # ---------------------------------------------------------------------------
265
+
266
+
267
+ def _svd_null_space(A: np.ndarray, rtol: float = 1e-12) -> np.ndarray:
268
+ """Compute a null-space basis using SVD.
269
+
270
+ This is used as a fallback when SciPy is unavailable.
271
+
272
+ :param A: Input matrix.
273
+ :type A: np.ndarray
274
+ :param rtol: Relative singular-value threshold for rank detection.
275
+ :type rtol: float
276
+ :return:
277
+ Matrix whose columns form a basis of the null space of ``A``.
278
+ :rtype: np.ndarray
279
+ """
280
+ _u, s, vh = np.linalg.svd(A, full_matrices=True)
281
+ if s.size == 0:
282
+ return np.eye(A.shape[1])
283
+
284
+ tol = rtol * s[0]
285
+ rank = int((s > tol).sum())
286
+ ns = vh[rank:].T
287
+
288
+ if ns.size == 0:
289
+ return np.zeros((A.shape[1], 0))
290
+
291
+ return ns
292
+
293
+
294
+ def _null_space(A: np.ndarray, rtol: float = 1e-12) -> np.ndarray:
295
+ """Compute a null-space basis using SciPy when available, otherwise SVD.
296
+
297
+ :param A: Input matrix.
298
+ :type A: np.ndarray
299
+ :param rtol: Relative threshold used to determine the numerical null space.
300
+ :type rtol: float
301
+ :return:
302
+ Matrix whose columns form a basis of the null space of ``A``.
303
+ :rtype: np.ndarray
304
+ """
305
+ if _SCIPY_AVAILABLE and scipy_null_space is not None:
306
+ return scipy_null_space(A, rcond=rtol)
307
+ return _svd_null_space(A, rtol=rtol)
308
+
309
+
310
+ def left_nullspace(crn: Any, *, rtol: float = 1e-12) -> np.ndarray:
311
+ """Compute a basis for the left null space ``ker(S^T)``.
312
+
313
+ In CRN language, these directions correspond to conservation-law vectors
314
+ over species.
315
+
316
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
317
+ :type crn: Any
318
+ :param rtol: Relative tolerance used in null-space computation.
319
+ :type rtol: float
320
+ :return:
321
+ Matrix whose columns form a basis of ``ker(S^T)``.
322
+ :rtype: np.ndarray
323
+ """
324
+ S = stoichiometric_matrix(crn)
325
+ return _null_space(S.T, rtol=rtol)
326
+
327
+
328
+ def right_nullspace(crn: Any, *, rtol: float = 1e-12) -> np.ndarray:
329
+ """Compute a basis for the right null space ``ker(S)``.
330
+
331
+ In CRN or Petri-net language, these directions correspond to rule-flux
332
+ modes or T-semiflows.
333
+
334
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
335
+ :type crn: Any
336
+ :param rtol: Relative tolerance used in null-space computation.
337
+ :type rtol: float
338
+ :return:
339
+ Matrix whose columns form a basis of ``ker(S)``.
340
+ :rtype: np.ndarray
341
+ """
342
+ S = stoichiometric_matrix(crn)
343
+ return _null_space(S, rtol=rtol)
344
+
345
+
346
+ def left_right_kernels(
347
+ crn: Any,
348
+ *,
349
+ rtol: float = 1e-12,
350
+ ) -> Tuple[np.ndarray, np.ndarray]:
351
+ """Compute both left and right kernels of the stoichiometric matrix.
352
+
353
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
354
+ :type crn: Any
355
+ :param rtol: Relative tolerance used in null-space computation.
356
+ :type rtol: float
357
+ :return:
358
+ Pair ``(left_basis, right_basis)`` where ``left_basis`` spans
359
+ ``ker(S^T)`` and ``right_basis`` spans ``ker(S)``.
360
+ :rtype: Tuple[np.ndarray, np.ndarray]
361
+ """
362
+ left_basis = left_nullspace(crn, rtol=rtol)
363
+ right_basis = right_nullspace(crn, rtol=rtol)
364
+ return left_basis, right_basis
365
+
366
+
367
+ # ---------------------------------------------------------------------------
368
+ # Integer scaling helpers
369
+ # ---------------------------------------------------------------------------
370
+
371
+
372
+ def _lcm(a: int, b: int) -> int:
373
+ """Compute the least common multiple of two integers.
374
+
375
+ :param a: First integer.
376
+ :type a: int
377
+ :param b: Second integer.
378
+ :type b: int
379
+ :return: Least common multiple of ``a`` and ``b``.
380
+ :rtype: int
381
+ """
382
+ return abs(a // gcd(a, b) * b) if a and b else abs(a or b)
383
+
384
+
385
+ def _vector_to_minimal_integer(vec: np.ndarray, *, tol: float = 1e-12) -> List[int]:
386
+ """Legacy approximate conversion; production invariants use exact elimination.
387
+
388
+ Retained for callers of the historical private helper. There is no rounded
389
+ integer fallback: coefficients are reconstructed as bounded rationals.
390
+ """
391
+ values = np.asarray(vec, dtype=float).reshape(-1)
392
+ if not np.all(np.isfinite(values)):
393
+ raise ValueError("Vector entries must be finite")
394
+ scale = np.max(np.abs(values), initial=0)
395
+ if scale <= tol:
396
+ return [0] * len(values)
397
+ fractions = [
398
+ (
399
+ Fraction(0)
400
+ if abs(v / scale) <= tol
401
+ else Fraction(float(v / scale)).limit_denominator(10**6)
402
+ )
403
+ for v in values
404
+ ]
405
+ result = _primitive_integer_vector(fractions)
406
+ sign = next((v for v in fractions if v), Fraction(0))
407
+ return [-v for v in result] if sign < 0 else result
408
+
409
+
410
+ def _primitive_integer_vector(vec: Sequence[Fraction]) -> List[int]:
411
+ """Scale an exact rational vector to a primitive integer vector.
412
+
413
+ The vector is multiplied by the least common multiple of its denominators,
414
+ divided by the greatest common divisor of the result, and sign-normalized so
415
+ that the first nonzero entry is positive.
416
+
417
+ :param vec: Exact rational vector.
418
+ :type vec: Sequence[Fraction]
419
+ :return: Primitive integer vector.
420
+ :rtype: List[int]
421
+ """
422
+ den_lcm = 1
423
+ for value in vec:
424
+ den_lcm = _lcm(den_lcm, value.denominator)
425
+
426
+ ints = [int(value * den_lcm) for value in vec]
427
+
428
+ divisor = 0
429
+ for value in ints:
430
+ divisor = gcd(divisor, abs(value))
431
+ if divisor > 1:
432
+ ints = [value // divisor for value in ints]
433
+
434
+ for value in ints:
435
+ if value != 0:
436
+ if value < 0:
437
+ ints = [-x for x in ints]
438
+ break
439
+
440
+ return ints
441
+
442
+
443
+ def _exact_null_space(rows: Sequence[Sequence[Any]], n_cols: int) -> List[List[int]]:
444
+ """Compute an exact integer basis of the null space of a rational matrix.
445
+
446
+ Row-reduces over :class:`~fractions.Fraction`, so the result is exact: every
447
+ returned vector ``v`` satisfies ``M v = 0`` identically rather than to
448
+ within a tolerance.
449
+
450
+ :param rows: Matrix rows.
451
+ :type rows: Sequence[Sequence[Any]]
452
+ :param n_cols: Number of columns.
453
+ :type n_cols: int
454
+ :return: Primitive integer basis vectors of the null space.
455
+ :rtype: List[List[int]]
456
+ """
457
+ if n_cols == 0:
458
+ return []
459
+
460
+ mat: List[List[Fraction]] = [
461
+ [
462
+ Fraction(str(value)) if not isinstance(value, int) else Fraction(value)
463
+ for value in row
464
+ ]
465
+ for row in rows
466
+ ]
467
+
468
+ pivot_cols: List[int] = []
469
+ pivot_row = 0
470
+ for col in range(n_cols):
471
+ if pivot_row >= len(mat):
472
+ break
473
+ candidate = next(
474
+ (r for r in range(pivot_row, len(mat)) if mat[r][col] != 0), None
475
+ )
476
+ if candidate is None:
477
+ continue
478
+
479
+ mat[pivot_row], mat[candidate] = mat[candidate], mat[pivot_row]
480
+ pivot_value = mat[pivot_row][col]
481
+ mat[pivot_row] = [value / pivot_value for value in mat[pivot_row]]
482
+
483
+ for r in range(len(mat)):
484
+ if r != pivot_row and mat[r][col] != 0:
485
+ factor = mat[r][col]
486
+ mat[r] = [a - factor * b for a, b in zip(mat[r], mat[pivot_row])]
487
+
488
+ pivot_cols.append(col)
489
+ pivot_row += 1
490
+
491
+ free_cols = [c for c in range(n_cols) if c not in pivot_cols]
492
+
493
+ basis: List[List[int]] = []
494
+ for free_col in free_cols:
495
+ vector = [Fraction(0)] * n_cols
496
+ vector[free_col] = Fraction(1)
497
+ for row_index, pivot_col in enumerate(pivot_cols):
498
+ vector[pivot_col] = -mat[row_index][free_col]
499
+ basis.append(_primitive_integer_vector(vector))
500
+
501
+ return basis
502
+
503
+
504
+ def integer_conservation_laws(
505
+ crn: Any, *, rtol: float = 1e-12, backend="auto", library_path=None, time_limit=30.0
506
+ ) -> List[List[int]]:
507
+ """Return an exact integer basis for ``ker(S^T)``, the conservation laws.
508
+
509
+ A conservation law is a vector ``y`` over species with ``y^T S = 0``: the
510
+ quantity ``y . c`` is constant along every trajectory, whatever the kinetics.
511
+ Basis vectors are exact primitive integers obtained by rational row
512
+ reduction, so ``y^T S`` is identically zero rather than zero to within a
513
+ tolerance.
514
+
515
+ Basis vectors may contain negative entries. For the non-negative,
516
+ inclusion-minimal laws — the *conserved moieties*, which are the ones with a
517
+ chemical reading such as "total enzyme" or "total phosphate" — use
518
+ :func:`conserved_moieties`.
519
+
520
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
521
+ :type crn: Any
522
+ :param rtol:
523
+ Retained for backward compatibility and ignored; the computation is
524
+ exact and needs no tolerance.
525
+ :type rtol: float
526
+ :return:
527
+ Exact integer basis of the left kernel, one law per row.
528
+ :rtype: List[List[int]]
529
+
530
+ .. rubric:: Example
531
+
532
+ .. code-block:: python
533
+
534
+ crn = SynCRN.from_reaction_strings(["A>>B", "B>>A"])
535
+ print(integer_conservation_laws(crn))
536
+ # [[1, 1]]
537
+ """
538
+ from ..backend import resolve_backend
539
+
540
+ backend, library_path = resolve_backend(backend, library_path)
541
+ if backend == "cpp":
542
+ from ..native_analysis import matrix_analysis
543
+
544
+ return matrix_analysis(
545
+ stoichiometric_matrix(crn),
546
+ library_path=library_path,
547
+ kernels="left",
548
+ time_limit=time_limit,
549
+ )["left"]
550
+ S = stoichiometric_matrix(crn)
551
+ if S.size == 0:
552
+ return []
553
+
554
+ n_species, _ = S.shape
555
+ rows = [
556
+ [int(v) if float(v).is_integer() else float(v) for v in S[:, j]]
557
+ for j in range(S.shape[1])
558
+ ]
559
+ return _exact_null_space(rows, n_species)
560
+
561
+
562
+ def conserved_moieties(
563
+ crn: Any, *, backend="auto", library_path=None, **native_options
564
+ ) -> List[List[int]]:
565
+ """Return the minimal non-negative integer conservation laws.
566
+
567
+ These are the P-semiflows of the network's Petri-net view: vectors ``y >= 0``
568
+ with ``y^T S = 0`` and inclusion-minimal support. Each one reads as a
569
+ conserved pool — total enzyme, total phosphate, a carbon backbone — which is
570
+ what makes them the useful form in a metabolic or signalling context.
571
+
572
+ Unlike :func:`integer_conservation_laws`, this is not a basis: a network can
573
+ have more minimal moieties than the dimension of its left kernel, and a
574
+ network with only sign-mixed conservation laws has none at all.
575
+
576
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
577
+ :type crn: Any
578
+ :return:
579
+ Minimal non-negative integer conservation laws, one per row, in the
580
+ canonical species order.
581
+ :rtype: List[List[int]]
582
+
583
+ .. rubric:: Example
584
+
585
+ .. code-block:: python
586
+
587
+ crn = SynCRN.from_reaction_strings(["E+S>>ES", "ES>>E+S", "ES>>E+P"])
588
+ print(conserved_moieties(crn))
589
+ """
590
+ from ..Petrinet.minimal_semiflows import minimal_semiflows
591
+
592
+ S = stoichiometric_matrix(crn)
593
+ if S.size == 0:
594
+ return []
595
+ return minimal_semiflows(
596
+ S, kind="p", backend=backend, library_path=library_path, **native_options
597
+ )
598
+
599
+
600
+ # ---------------------------------------------------------------------------
601
+ # Lightweight structural summary
602
+ # ---------------------------------------------------------------------------
603
+
604
+
605
+ @dataclass
606
+ class StoichSummary:
607
+ """
608
+ Lightweight stoichiometric summary of a SynCRN.
609
+
610
+ The field name ``n_reactions`` is retained for backward compatibility even
611
+ though the current SynCRN representation uses rule nodes as process
612
+ columns.
613
+
614
+ :param n_species: Number of species rows in the stoichiometric matrix.
615
+ :type n_species: int
616
+ :param n_reactions:
617
+ Number of process columns in the stoichiometric matrix. In the current
618
+ representation this corresponds to the number of rule nodes.
619
+ :type n_reactions: int
620
+ :param rank: Numerical rank of the stoichiometric matrix.
621
+ :type rank: int
622
+ """
623
+
624
+ n_species: int
625
+ n_reactions: int
626
+ rank: int
627
+
628
+ dim_left_kernel: int = field(init=False)
629
+ dim_right_kernel: int = field(init=False)
630
+
631
+ def __post_init__(self) -> None:
632
+ """
633
+ Validate dimensions and derive kernel dimensions.
634
+
635
+ :raises ValueError:
636
+ If dimensions are negative or if ``rank`` exceeds
637
+ ``min(n_species, n_reactions)``.
638
+ """
639
+ if self.n_species < 0 or self.n_reactions < 0:
640
+ raise ValueError("n_species and n_reactions must be non-negative.")
641
+ if self.rank < 0:
642
+ raise ValueError("rank must be non-negative.")
643
+ if self.rank > min(self.n_species, self.n_reactions):
644
+ raise ValueError(
645
+ f"rank={self.rank} cannot exceed min(n_species, n_reactions) = "
646
+ f"{min(self.n_species, self.n_reactions)}"
647
+ )
648
+
649
+ self.dim_left_kernel = max(self.n_species - self.rank, 0)
650
+ self.dim_right_kernel = max(self.n_reactions - self.rank, 0)
651
+
652
+ @property
653
+ def is_full_rank(self) -> bool:
654
+ """Whether the stoichiometric matrix has full rank.
655
+
656
+ :return:
657
+ ``True`` when ``rank == min(n_species, n_reactions)``,
658
+ otherwise ``False``.
659
+ :rtype: bool
660
+ """
661
+ return self.rank == min(self.n_species, self.n_reactions)
662
+
663
+ @property
664
+ def is_underdetermined(self) -> bool:
665
+ """Whether the right kernel is non-trivial.
666
+
667
+ Equivalently, this checks whether ``rank < n_reactions``.
668
+
669
+ :return: ``True`` if ``dim_right_kernel > 0``, else ``False``.
670
+ :rtype: bool
671
+ """
672
+ return self.rank < self.n_reactions
673
+
674
+ def to_dict(self) -> Dict[str, Any]:
675
+ """Convert the summary into a plain dictionary.
676
+
677
+ :return: Dictionary representation of the summary.
678
+ :rtype: Dict[str, Any]
679
+ """
680
+ return {
681
+ "n_species": self.n_species,
682
+ "n_reactions": self.n_reactions,
683
+ "rank": self.rank,
684
+ "dim_left_kernel": self.dim_left_kernel,
685
+ "dim_right_kernel": self.dim_right_kernel,
686
+ }
687
+
688
+ @classmethod
689
+ def from_crn(cls, crn: Any) -> "StoichSummary":
690
+ """Construct a summary directly from a CRN object or graph.
691
+
692
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
693
+ :type crn: Any
694
+ :return: Stoichiometric summary derived from the CRN.
695
+ :rtype: StoichSummary
696
+ """
697
+ S = stoichiometric_matrix(crn)
698
+ n_species, n_reactions = S.shape
699
+ # ``matrix_rank`` raises on a zero-size array, which a network with no
700
+ # species or no reactions legitimately produces.
701
+ rank = 0 if S.size == 0 else int(np.linalg.matrix_rank(S))
702
+ return cls(
703
+ n_species=n_species,
704
+ n_reactions=n_reactions,
705
+ rank=rank,
706
+ )
707
+
708
+ def __str__(self) -> str:
709
+ """Return a human-readable multi-line representation.
710
+
711
+ :return: Formatted summary string.
712
+ :rtype: str
713
+ """
714
+ lines = [
715
+ "StoichSummary(",
716
+ f" n_species = {self.n_species}",
717
+ f" n_reactions = {self.n_reactions}",
718
+ f" rank = {self.rank}",
719
+ f" dim_left_kernel = {self.dim_left_kernel}",
720
+ f" dim_right_kernel = {self.dim_right_kernel}",
721
+ ")",
722
+ ]
723
+ return "\n".join(lines)
724
+
725
+
726
+ def summary(crn: Any) -> StoichSummary:
727
+ """Compute a quick stoichiometric summary.
728
+
729
+ :param crn: A NetworkX bipartite graph or a SynCRN-like object containing one.
730
+ :type crn: Any
731
+ :return: Lightweight stoichiometric summary of the CRN.
732
+ :rtype: StoichSummary
733
+ """
734
+ return StoichSummary.from_crn(crn)
735
+
736
+
737
+ # This cache exists only during one summary calculation; mutable models are never
738
+ # cached across calls or shared between threads/tasks.
739
+ _MATRIX_CONTEXT = ContextVar("crn_matrix_context", default=None)
740
+
741
+
742
+ def _reuse_matrices(function):
743
+ @wraps(function)
744
+ def wrapped(*args, **kwargs):
745
+ token = _MATRIX_CONTEXT.set({})
746
+ try:
747
+ return function(*args, **kwargs)
748
+ finally:
749
+ _MATRIX_CONTEXT.reset(token)
750
+
751
+ return wrapped