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,953 @@
1
+ """Chemical Reaction Network Theory: complexes, linkage classes and deficiency.
2
+
3
+ This module supplies the structural quantities that Chemical Reaction Network
4
+ Theory (CRNT) is built on, and the two classical theorems that turn them into
5
+ dynamical verdicts.
6
+
7
+ A reaction network is read as a directed graph on *complexes* — the multisets of
8
+ species that appear on either side of a reaction. Writing ``n`` for the number
9
+ of distinct complexes, ``l`` for the number of linkage classes (connected
10
+ components of that graph) and ``s`` for the rank of the stoichiometric matrix,
11
+ the **deficiency** is
12
+
13
+ .. math::
14
+
15
+ \\delta = n - l - s
16
+
17
+ and is always non-negative. It measures how far the network's reaction vectors
18
+ are from being independent given its complex-graph structure.
19
+
20
+ Two theorems are implemented on top of it:
21
+
22
+ - **Deficiency Zero Theorem** (Feinberg / Horn / Jackson). A weakly reversible
23
+ network of deficiency zero has, for *every* choice of positive mass-action
24
+ rate constants, exactly one positive steady state in each positive
25
+ stoichiometric compatibility class, and it is locally asymptotically stable.
26
+ A deficiency-zero network that is *not* weakly reversible admits no positive
27
+ steady state at all, again regardless of rate constants.
28
+ - **Deficiency One Theorem** (Feinberg). If every linkage class has deficiency
29
+ at most one, the linkage-class deficiencies sum to the network deficiency,
30
+ and each linkage class contains exactly one terminal strong linkage class,
31
+ then there is *at most* one positive steady state per positive stoichiometric
32
+ compatibility class — and exactly one when the network is also weakly
33
+ reversible.
34
+
35
+ Both verdicts are structural: they hold for all positive rate constants and need
36
+ no kinetic parameters.
37
+
38
+ All ranks are computed exactly over :class:`~fractions.Fraction`, so an integer
39
+ network never gets a floating-point deficiency.
40
+
41
+ .. rubric:: Example
42
+
43
+ .. code-block:: python
44
+
45
+ from synkit.CRN import SynCRN
46
+ from synkit.CRN.Props import crnt_summary
47
+
48
+ crn = SynCRN.from_reaction_strings(["A+B>>C", "C>>A+B", "C>>D", "D>>C"])
49
+ report = crnt_summary(crn)
50
+ print(report.deficiency, report.is_weakly_reversible)
51
+ print(report)
52
+ """
53
+
54
+ from __future__ import annotations
55
+
56
+ from dataclasses import dataclass, field
57
+ from fractions import Fraction
58
+ from typing import Any, Dict, List, Sequence, Tuple
59
+
60
+ import networkx as nx
61
+ import numpy as np
62
+
63
+ from .helper import _as_graph, _species_and_rule_order
64
+ from .stoich import build_S_minus_plus
65
+
66
+ from .stoich import _reuse_matrices
67
+
68
+ __all__ = [
69
+ "Complex",
70
+ "CRNTSummary",
71
+ "complexes",
72
+ "complex_graph",
73
+ "crnt_summary",
74
+ "deficiency",
75
+ "deficiency_one_verdict",
76
+ "deficiency_zero_verdict",
77
+ "is_deficiency_one_applicable",
78
+ "is_deficiency_zero_applicable",
79
+ "is_reversible",
80
+ "is_weakly_reversible",
81
+ "linkage_class_deficiencies",
82
+ "linkage_classes",
83
+ "strong_linkage_classes",
84
+ "terminal_strong_linkage_classes",
85
+ ]
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Complexes
90
+ # ---------------------------------------------------------------------------
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class Complex:
95
+ """One complex: a multiset of species with non-negative coefficients.
96
+
97
+ :param coefficients:
98
+ Coefficient vector in canonical species order.
99
+ :type coefficients: Tuple[int, ...]
100
+
101
+ :param species_labels:
102
+ Species labels in the same order as ``coefficients``.
103
+ :type species_labels: Tuple[str, ...]
104
+
105
+ .. rubric:: Example
106
+
107
+ .. code-block:: python
108
+
109
+ print(str(Complex((2, 1, 0), ("A", "B", "C"))))
110
+ # '2A + B'
111
+ """
112
+
113
+ coefficients: Tuple[int, ...]
114
+ species_labels: Tuple[str, ...] = field(compare=False, default=())
115
+
116
+ @property
117
+ def is_zero(self) -> bool:
118
+ """Return whether this is the zero complex (an in- or outflow).
119
+
120
+ :return:
121
+ ``True`` when every coefficient is zero.
122
+ :rtype: bool
123
+ """
124
+ return not any(self.coefficients)
125
+
126
+ @property
127
+ def support(self) -> Tuple[str, ...]:
128
+ """Return the labels of the species occurring in this complex.
129
+
130
+ :return:
131
+ Species labels with a nonzero coefficient.
132
+ :rtype: Tuple[str, ...]
133
+ """
134
+ return tuple(
135
+ label
136
+ for label, coeff in zip(self.species_labels, self.coefficients)
137
+ if coeff
138
+ )
139
+
140
+ def to_dict(self) -> Dict[str, int]:
141
+ """Return the complex as a ``species label -> coefficient`` mapping.
142
+
143
+ :return:
144
+ Nonzero coefficients keyed by species label.
145
+ :rtype: Dict[str, int]
146
+ """
147
+ return {
148
+ label: int(coeff)
149
+ for label, coeff in zip(self.species_labels, self.coefficients)
150
+ if coeff
151
+ }
152
+
153
+ def __str__(self) -> str:
154
+ """Return the usual chemical rendering, such as ``2A + B``.
155
+
156
+ :return:
157
+ Human-readable complex.
158
+ :rtype: str
159
+ """
160
+ if self.is_zero:
161
+ return "0"
162
+ parts = []
163
+ for label, coeff in zip(self.species_labels, self.coefficients):
164
+ if not coeff:
165
+ continue
166
+ parts.append(f"{label}" if coeff == 1 else f"{_fmt_coeff(coeff)}{label}")
167
+ return " + ".join(parts)
168
+
169
+
170
+ def _fmt_coeff(coeff: Any) -> str:
171
+ """Render a stoichiometric coefficient without a trailing ``.0``.
172
+
173
+ :param coeff:
174
+ Coefficient value.
175
+ :type coeff: Any
176
+
177
+ :return:
178
+ Compact string form.
179
+ :rtype: str
180
+ """
181
+ value = float(coeff)
182
+ return str(int(value)) if value.is_integer() else str(value)
183
+
184
+
185
+ def _species_labels(crn: Any) -> List[str]:
186
+ """Return display labels for the canonical species order.
187
+
188
+ :param crn:
189
+ CRN-like input.
190
+ :type crn: Any
191
+
192
+ :return:
193
+ One label per species, falling back to the node id.
194
+ :rtype: List[str]
195
+ """
196
+ graph = _as_graph(crn)
197
+ species_order, _, _, _ = _species_and_rule_order(graph)
198
+ labels: List[str] = []
199
+ for node in species_order:
200
+ data = graph.nodes[node]
201
+ label = data.get("label") or data.get("smiles") or node
202
+ labels.append(str(label))
203
+ return labels
204
+
205
+
206
+ def _complex_columns(crn: Any) -> Tuple[List[Complex], List[Tuple[int, int]]]:
207
+ """Extract the distinct complexes and the reaction arrows between them.
208
+
209
+ :param crn:
210
+ CRN-like input.
211
+ :type crn: Any
212
+
213
+ :return:
214
+ Pair ``(complexes, arrows)`` where ``arrows[j]`` is the
215
+ ``(reactant complex index, product complex index)`` of reaction ``j``.
216
+ :rtype: Tuple[List[Complex], List[Tuple[int, int]]]
217
+ """
218
+ _, _, s_minus, s_plus = build_S_minus_plus(crn)
219
+ labels = tuple(_species_labels(crn))
220
+
221
+ order: List[Complex] = []
222
+ index: Dict[Tuple[int, ...], int] = {}
223
+
224
+ def _intern(column: np.ndarray) -> int:
225
+ key = tuple(
226
+ int(v) if float(v).is_integer() else float(v) # type: ignore[misc]
227
+ for v in column
228
+ )
229
+ if key not in index:
230
+ index[key] = len(order)
231
+ order.append(Complex(coefficients=key, species_labels=labels))
232
+ return index[key]
233
+
234
+ arrows: List[Tuple[int, int]] = []
235
+ for j in range(s_minus.shape[1]):
236
+ arrows.append((_intern(s_minus[:, j]), _intern(s_plus[:, j])))
237
+
238
+ return order, arrows
239
+
240
+
241
+ def complexes(crn: Any) -> List[Complex]:
242
+ """Return the distinct complexes of a network, in first-appearance order.
243
+
244
+ The zero complex is included when the network has an inflow or outflow, as
245
+ CRNT requires.
246
+
247
+ :param crn:
248
+ CRN-like input.
249
+ :type crn: Any
250
+
251
+ :return:
252
+ Distinct complexes.
253
+ :rtype: List[Complex]
254
+
255
+ .. rubric:: Example
256
+
257
+ .. code-block:: python
258
+
259
+ crn = SynCRN.from_reaction_strings(["A+B>>C", "C>>A+B"])
260
+ print([str(c) for c in complexes(crn)])
261
+ # ['A + B', 'C']
262
+ """
263
+ order, _ = _complex_columns(crn)
264
+ return order
265
+
266
+
267
+ def complex_graph(crn: Any) -> nx.DiGraph:
268
+ """Return the directed complex graph of a network.
269
+
270
+ Nodes are complex indices carrying a ``complex`` attribute (a
271
+ :class:`Complex`) and a ``label`` attribute (its rendering). One edge is
272
+ added per reaction, from its reactant complex to its product complex.
273
+
274
+ :param crn:
275
+ CRN-like input.
276
+ :type crn: Any
277
+
278
+ :return:
279
+ Complex graph.
280
+ :rtype: networkx.DiGraph
281
+
282
+ .. rubric:: Example
283
+
284
+ .. code-block:: python
285
+
286
+ g = complex_graph(SynCRN.from_reaction_strings(["A>>B", "B>>A"]))
287
+ print(g.number_of_nodes(), g.number_of_edges())
288
+ # 2 2
289
+ """
290
+ order, arrows = _complex_columns(crn)
291
+
292
+ graph = nx.DiGraph()
293
+ for i, cx in enumerate(order):
294
+ graph.add_node(i, complex=cx, label=str(cx))
295
+ for src, dst in arrows:
296
+ if src == dst:
297
+ continue
298
+ graph.add_edge(src, dst)
299
+
300
+ return graph
301
+
302
+
303
+ # ---------------------------------------------------------------------------
304
+ # Exact linear algebra
305
+ # ---------------------------------------------------------------------------
306
+
307
+
308
+ def _exact_rank(rows: Sequence[Sequence[Any]]) -> int:
309
+ """Compute the rank of a matrix exactly, over the rationals.
310
+
311
+ :param rows:
312
+ Matrix rows.
313
+ :type rows: Sequence[Sequence[Any]]
314
+
315
+ :return:
316
+ Exact rank.
317
+ :rtype: int
318
+ """
319
+ work = [
320
+ [Fraction(str(v)) if not isinstance(v, int) else Fraction(v) for v in row]
321
+ for row in rows
322
+ ]
323
+ if not work or not work[0]:
324
+ return 0
325
+
326
+ n_cols = len(work[0])
327
+ rank = 0
328
+ pivot_row = 0
329
+
330
+ for col in range(n_cols):
331
+ pivot = None
332
+ for r in range(pivot_row, len(work)):
333
+ if work[r][col] != 0:
334
+ pivot = r
335
+ break
336
+ if pivot is None:
337
+ continue
338
+
339
+ work[pivot_row], work[pivot] = work[pivot], work[pivot_row]
340
+ pivot_value = work[pivot_row][col]
341
+ for r in range(pivot_row + 1, len(work)):
342
+ factor = work[r][col] / pivot_value
343
+ if factor == 0:
344
+ continue
345
+ work[r] = [a - factor * b for a, b in zip(work[r], work[pivot_row])]
346
+
347
+ rank += 1
348
+ pivot_row += 1
349
+ if pivot_row == len(work):
350
+ break
351
+
352
+ return rank
353
+
354
+
355
+ def _reaction_vectors(crn: Any) -> List[List[Any]]:
356
+ """Return the reaction vectors of a network, one per reaction.
357
+
358
+ :param crn:
359
+ CRN-like input.
360
+ :type crn: Any
361
+
362
+ :return:
363
+ Reaction vectors as rows (reactions x species).
364
+ :rtype: List[List[Any]]
365
+ """
366
+ _, _, s_minus, s_plus = build_S_minus_plus(crn)
367
+ net = s_plus - s_minus
368
+ return [
369
+ [int(v) if float(v).is_integer() else float(v) for v in net[:, j]]
370
+ for j in range(net.shape[1])
371
+ ]
372
+
373
+
374
+ # ---------------------------------------------------------------------------
375
+ # Linkage classes
376
+ # ---------------------------------------------------------------------------
377
+
378
+
379
+ def linkage_classes(crn: Any) -> List[List[int]]:
380
+ """Return the linkage classes as sorted lists of complex indices.
381
+
382
+ A linkage class is a connected component of the complex graph, taken
383
+ without regard to arrow direction.
384
+
385
+ :param crn:
386
+ CRN-like input.
387
+ :type crn: Any
388
+
389
+ :return:
390
+ Linkage classes, each a sorted list of complex indices.
391
+ :rtype: List[List[int]]
392
+
393
+ .. rubric:: Example
394
+
395
+ .. code-block:: python
396
+
397
+ crn = SynCRN.from_reaction_strings(["A>>B", "C>>D"])
398
+ print(linkage_classes(crn))
399
+ # [[0, 1], [2, 3]]
400
+ """
401
+ graph = complex_graph(crn)
402
+ components = [sorted(c) for c in nx.weakly_connected_components(graph)]
403
+ return sorted(components, key=lambda comp: comp[0] if comp else -1)
404
+
405
+
406
+ def strong_linkage_classes(crn: Any) -> List[List[int]]:
407
+ """Return the strong linkage classes as sorted lists of complex indices.
408
+
409
+ A strong linkage class is a strongly connected component of the complex
410
+ graph: a maximal set of complexes each reachable from every other.
411
+
412
+ :param crn:
413
+ CRN-like input.
414
+ :type crn: Any
415
+
416
+ :return:
417
+ Strong linkage classes, each a sorted list of complex indices.
418
+ :rtype: List[List[int]]
419
+ """
420
+ graph = complex_graph(crn)
421
+ components = [sorted(c) for c in nx.strongly_connected_components(graph)]
422
+ return sorted(components, key=lambda comp: comp[0] if comp else -1)
423
+
424
+
425
+ def terminal_strong_linkage_classes(crn: Any) -> List[List[int]]:
426
+ """Return the terminal strong linkage classes.
427
+
428
+ A strong linkage class is *terminal* when no reaction leads out of it: once
429
+ the network's complex graph enters it, it cannot leave.
430
+
431
+ :param crn:
432
+ CRN-like input.
433
+ :type crn: Any
434
+
435
+ :return:
436
+ Terminal strong linkage classes, each a sorted list of complex indices.
437
+ :rtype: List[List[int]]
438
+ """
439
+ graph = complex_graph(crn)
440
+ terminal: List[List[int]] = []
441
+
442
+ for component in nx.strongly_connected_components(graph):
443
+ leaves = any(
444
+ successor not in component
445
+ for node in component
446
+ for successor in graph.successors(node)
447
+ )
448
+ if not leaves:
449
+ terminal.append(sorted(component))
450
+
451
+ return sorted(terminal, key=lambda comp: comp[0] if comp else -1)
452
+
453
+
454
+ def is_weakly_reversible(crn: Any) -> bool:
455
+ """Return whether every linkage class is strongly connected.
456
+
457
+ Weak reversibility means that whenever complex ``y`` can reach complex
458
+ ``y'`` through a sequence of reactions, ``y'`` can reach ``y`` as well. It is
459
+ the hypothesis both classical deficiency theorems turn on.
460
+
461
+ :param crn:
462
+ CRN-like input.
463
+ :type crn: Any
464
+
465
+ :return:
466
+ ``True`` when the network is weakly reversible.
467
+ :rtype: bool
468
+
469
+ .. rubric:: Example
470
+
471
+ .. code-block:: python
472
+
473
+ is_weakly_reversible(SynCRN.from_reaction_strings(["A>>B", "B>>A"]))
474
+ # True
475
+ is_weakly_reversible(SynCRN.from_reaction_strings(["A>>B"]))
476
+ # False
477
+ """
478
+ graph = complex_graph(crn)
479
+ if graph.number_of_nodes() == 0:
480
+ return True
481
+
482
+ strong = {frozenset(c) for c in nx.strongly_connected_components(graph)}
483
+ weak = {frozenset(c) for c in nx.weakly_connected_components(graph)}
484
+ return strong == weak
485
+
486
+
487
+ def is_reversible(crn: Any) -> bool:
488
+ """Return whether every reaction arrow has a matching reverse arrow.
489
+
490
+ This is the strict notion: ``y -> y'`` requires ``y' -> y`` to be present as
491
+ a reaction. Reversibility implies weak reversibility, not the converse.
492
+
493
+ :param crn:
494
+ CRN-like input.
495
+ :type crn: Any
496
+
497
+ :return:
498
+ ``True`` when the network is reversible.
499
+ :rtype: bool
500
+ """
501
+ _, arrows = _complex_columns(crn)
502
+ arrow_set = {(src, dst) for src, dst in arrows if src != dst}
503
+ return all((dst, src) in arrow_set for src, dst in arrow_set)
504
+
505
+
506
+ # ---------------------------------------------------------------------------
507
+ # Deficiency
508
+ # ---------------------------------------------------------------------------
509
+
510
+
511
+ def deficiency(crn: Any) -> int:
512
+ """Compute the deficiency ``delta = n - l - s`` of a network.
513
+
514
+ ``n`` counts distinct complexes, ``l`` counts linkage classes and ``s`` is
515
+ the exact rank of the stoichiometric matrix. The result is always
516
+ non-negative.
517
+
518
+ :param crn:
519
+ CRN-like input.
520
+ :type crn: Any
521
+
522
+ :return:
523
+ Network deficiency.
524
+ :rtype: int
525
+
526
+ .. rubric:: Example
527
+
528
+ .. code-block:: python
529
+
530
+ deficiency(SynCRN.from_reaction_strings(["A>>B", "B>>A"]))
531
+ # 0
532
+ deficiency(SynCRN.from_reaction_strings(["A+B>>C", "C>>A", "C>>B"]))
533
+ # 1
534
+ """
535
+ order, _ = _complex_columns(crn)
536
+ n_complexes = len(order)
537
+ n_linkage = len(linkage_classes(crn))
538
+ rank = _exact_rank(_reaction_vectors(crn))
539
+ return n_complexes - n_linkage - rank
540
+
541
+
542
+ def linkage_class_deficiencies(crn: Any) -> List[int]:
543
+ """Compute the deficiency of each linkage class.
544
+
545
+ For a linkage class with ``n_l`` complexes and reaction-vector rank ``s_l``
546
+ the linkage-class deficiency is ``n_l - 1 - s_l``. Their sum never exceeds
547
+ the network deficiency; equality is one of the Deficiency One Theorem's
548
+ hypotheses.
549
+
550
+ :param crn:
551
+ CRN-like input.
552
+ :type crn: Any
553
+
554
+ :return:
555
+ One deficiency per linkage class, in the order of
556
+ :func:`linkage_classes`.
557
+ :rtype: List[int]
558
+ """
559
+ order, arrows = _complex_columns(crn)
560
+ if not order:
561
+ return []
562
+
563
+ vectors = _reaction_vectors(crn)
564
+ classes = linkage_classes(crn)
565
+ member_of = {node: i for i, comp in enumerate(classes) for node in comp}
566
+
567
+ per_class: List[List[List[Any]]] = [[] for _ in classes]
568
+ for j, (src, _dst) in enumerate(arrows):
569
+ per_class[member_of[src]].append(vectors[j])
570
+
571
+ return [len(comp) - 1 - _exact_rank(rows) for comp, rows in zip(classes, per_class)]
572
+
573
+
574
+ # ---------------------------------------------------------------------------
575
+ # Theorem verdicts
576
+ # ---------------------------------------------------------------------------
577
+
578
+
579
+ def is_deficiency_zero_applicable(crn: Any) -> bool:
580
+ """Return whether the Deficiency Zero Theorem applies to this network.
581
+
582
+ The theorem applies exactly when the deficiency is zero; weak reversibility
583
+ then decides *which* of its two conclusions holds. Use
584
+ :func:`deficiency_zero_verdict` for the conclusion itself.
585
+
586
+ :param crn:
587
+ CRN-like input.
588
+ :type crn: Any
589
+
590
+ :return:
591
+ ``True`` when the network has deficiency zero.
592
+ :rtype: bool
593
+ """
594
+ return deficiency(crn) == 0
595
+
596
+
597
+ def deficiency_zero_verdict(crn: Any) -> Dict[str, Any]:
598
+ """Apply the Deficiency Zero Theorem and report its conclusion.
599
+
600
+ :param crn:
601
+ CRN-like input.
602
+ :type crn: Any
603
+
604
+ :return:
605
+ Mapping with ``applicable``, ``deficiency``, ``weakly_reversible``,
606
+ ``conclusion`` (one of ``"unique_stable_equilibrium"``,
607
+ ``"no_positive_equilibrium"`` or ``"inconclusive"``) and a
608
+ human-readable ``statement``.
609
+ :rtype: Dict[str, Any]
610
+
611
+ .. rubric:: Example
612
+
613
+ .. code-block:: python
614
+
615
+ crn = SynCRN.from_reaction_strings(["A>>B", "B>>A"])
616
+ print(deficiency_zero_verdict(crn)["conclusion"])
617
+ # 'unique_stable_equilibrium'
618
+ """
619
+ delta = deficiency(crn)
620
+ weakly_reversible = is_weakly_reversible(crn)
621
+
622
+ return _deficiency_zero_values(delta, weakly_reversible)
623
+
624
+
625
+ def _deficiency_zero_values(delta, weakly_reversible):
626
+ if delta != 0:
627
+ return {
628
+ "applicable": False,
629
+ "deficiency": delta,
630
+ "weakly_reversible": weakly_reversible,
631
+ "conclusion": "inconclusive",
632
+ "statement": (
633
+ f"Deficiency is {delta}, not zero; the Deficiency Zero Theorem "
634
+ "says nothing about this network."
635
+ ),
636
+ }
637
+
638
+ if weakly_reversible:
639
+ return {
640
+ "applicable": True,
641
+ "deficiency": 0,
642
+ "weakly_reversible": True,
643
+ "conclusion": "unique_stable_equilibrium",
644
+ "statement": (
645
+ "Deficiency zero and weakly reversible: for every choice of "
646
+ "positive mass-action rate constants there is exactly one "
647
+ "positive steady state in each positive stoichiometric "
648
+ "compatibility class, it is locally asymptotically stable, and "
649
+ "there is no nontrivial periodic orbit."
650
+ ),
651
+ }
652
+
653
+ return {
654
+ "applicable": True,
655
+ "deficiency": 0,
656
+ "weakly_reversible": False,
657
+ "conclusion": "no_positive_equilibrium",
658
+ "statement": (
659
+ "Deficiency zero but not weakly reversible: no positive steady "
660
+ "state and no cyclic composition trajectory through a positive "
661
+ "composition exists, for any positive mass-action rate constants."
662
+ ),
663
+ }
664
+
665
+
666
+ def is_deficiency_one_applicable(crn: Any) -> bool:
667
+ """Return whether the Deficiency One Theorem's hypotheses hold.
668
+
669
+ The three hypotheses are: every linkage-class deficiency is at most one,
670
+ the linkage-class deficiencies sum to the network deficiency, and each
671
+ linkage class contains exactly one terminal strong linkage class.
672
+
673
+ :param crn:
674
+ CRN-like input.
675
+ :type crn: Any
676
+
677
+ :return:
678
+ ``True`` when all three hypotheses hold.
679
+ :rtype: bool
680
+ """
681
+ return bool(deficiency_one_verdict(crn)["applicable"])
682
+
683
+
684
+ def deficiency_one_verdict(crn: Any) -> Dict[str, Any]:
685
+ """Apply the Deficiency One Theorem and report its conclusion.
686
+
687
+ :param crn:
688
+ CRN-like input.
689
+ :type crn: Any
690
+
691
+ :return:
692
+ Mapping with ``applicable``, the three hypothesis flags
693
+ (``all_linkage_deficiencies_at_most_one``, ``deficiencies_sum_to_total``,
694
+ ``one_terminal_class_per_linkage_class``), ``deficiency``,
695
+ ``linkage_class_deficiencies``, ``weakly_reversible``, ``conclusion``
696
+ (one of ``"exactly_one_equilibrium"``, ``"at_most_one_equilibrium"`` or
697
+ ``"inconclusive"``) and a human-readable ``statement``.
698
+ :rtype: Dict[str, Any]
699
+
700
+ .. rubric:: Example
701
+
702
+ .. code-block:: python
703
+
704
+ crn = SynCRN.from_reaction_strings(["A+B>>C", "C>>A+B", "C>>A", "A>>C"])
705
+ print(deficiency_one_verdict(crn)["conclusion"])
706
+ """
707
+ delta = deficiency(crn)
708
+ per_class = linkage_class_deficiencies(crn)
709
+ weakly_reversible = is_weakly_reversible(crn)
710
+
711
+ classes = linkage_classes(crn)
712
+ terminal = terminal_strong_linkage_classes(crn)
713
+ terminal_counts = []
714
+ for comp in classes:
715
+ members = set(comp)
716
+ terminal_counts.append(sum(1 for t in terminal if set(t) <= members))
717
+
718
+ return _deficiency_one_values(delta, per_class, weakly_reversible, terminal_counts)
719
+
720
+
721
+ def _deficiency_one_values(delta, per_class, weakly_reversible, terminal_counts):
722
+ hypotheses = {
723
+ "all_linkage_deficiencies_at_most_one": all(d <= 1 for d in per_class),
724
+ "deficiencies_sum_to_total": sum(per_class) == delta,
725
+ "one_terminal_class_per_linkage_class": all(c == 1 for c in terminal_counts),
726
+ }
727
+ applicable = all(hypotheses.values())
728
+
729
+ result: Dict[str, Any] = {
730
+ "applicable": applicable,
731
+ "deficiency": delta,
732
+ "linkage_class_deficiencies": per_class,
733
+ "weakly_reversible": weakly_reversible,
734
+ **hypotheses,
735
+ }
736
+
737
+ if not applicable:
738
+ failed = [name for name, ok in hypotheses.items() if not ok]
739
+ result["conclusion"] = "inconclusive"
740
+ result["statement"] = (
741
+ "The Deficiency One Theorem does not apply; failed hypotheses: "
742
+ + ", ".join(failed)
743
+ + "."
744
+ )
745
+ return result
746
+
747
+ if weakly_reversible:
748
+ result["conclusion"] = "exactly_one_equilibrium"
749
+ result["statement"] = (
750
+ "Deficiency One hypotheses hold and the network is weakly "
751
+ "reversible: for every choice of positive mass-action rate "
752
+ "constants there is exactly one positive steady state in each "
753
+ "positive stoichiometric compatibility class."
754
+ )
755
+ return result
756
+
757
+ result["conclusion"] = "at_most_one_equilibrium"
758
+ result["statement"] = (
759
+ "Deficiency One hypotheses hold: for every choice of positive "
760
+ "mass-action rate constants there is at most one positive steady state "
761
+ "in each positive stoichiometric compatibility class."
762
+ )
763
+ return result
764
+
765
+
766
+ # ---------------------------------------------------------------------------
767
+ # Summary
768
+ # ---------------------------------------------------------------------------
769
+
770
+
771
+ @dataclass
772
+ class CRNTSummary:
773
+ """Structural CRNT report for one network.
774
+
775
+ :param n_species:
776
+ Number of species.
777
+ :type n_species: int
778
+
779
+ :param n_reactions:
780
+ Number of reactions.
781
+ :type n_reactions: int
782
+
783
+ :param n_complexes:
784
+ Number of distinct complexes.
785
+ :type n_complexes: int
786
+
787
+ :param n_linkage_classes:
788
+ Number of linkage classes.
789
+ :type n_linkage_classes: int
790
+
791
+ :param n_terminal_strong_linkage_classes:
792
+ Number of terminal strong linkage classes.
793
+ :type n_terminal_strong_linkage_classes: int
794
+
795
+ :param rank:
796
+ Rank of the stoichiometric matrix.
797
+ :type rank: int
798
+
799
+ :param deficiency:
800
+ Network deficiency ``n - l - s``.
801
+ :type deficiency: int
802
+
803
+ :param linkage_class_deficiencies:
804
+ Deficiency of each linkage class.
805
+ :type linkage_class_deficiencies: List[int]
806
+
807
+ :param is_weakly_reversible:
808
+ Whether every linkage class is strongly connected.
809
+ :type is_weakly_reversible: bool
810
+
811
+ :param is_reversible:
812
+ Whether every reaction arrow has a matching reverse arrow.
813
+ :type is_reversible: bool
814
+
815
+ :param deficiency_zero:
816
+ Verdict from :func:`deficiency_zero_verdict`.
817
+ :type deficiency_zero: Dict[str, Any]
818
+
819
+ :param deficiency_one:
820
+ Verdict from :func:`deficiency_one_verdict`.
821
+ :type deficiency_one: Dict[str, Any]
822
+
823
+ :param complexes:
824
+ Rendered complexes in canonical order.
825
+ :type complexes: List[str]
826
+ """
827
+
828
+ n_species: int
829
+ n_reactions: int
830
+ n_complexes: int
831
+ n_linkage_classes: int
832
+ n_terminal_strong_linkage_classes: int
833
+ rank: int
834
+ deficiency: int
835
+ linkage_class_deficiencies: List[int]
836
+ is_weakly_reversible: bool
837
+ is_reversible: bool
838
+ deficiency_zero: Dict[str, Any] = field(default_factory=dict)
839
+ deficiency_one: Dict[str, Any] = field(default_factory=dict)
840
+ complexes: List[str] = field(default_factory=list)
841
+
842
+ @classmethod
843
+ @_reuse_matrices
844
+ def from_crn(cls, crn: Any) -> "CRNTSummary":
845
+ """Build a CRNT summary for a network.
846
+
847
+ :param crn:
848
+ CRN-like input.
849
+ :type crn: Any
850
+
851
+ :return:
852
+ Populated summary.
853
+ :rtype: CRNTSummary
854
+ """
855
+ order, arrows = _complex_columns(crn)
856
+ labels = _species_labels(crn)
857
+ classes = linkage_classes(crn)
858
+ rank = _exact_rank(_reaction_vectors(crn))
859
+
860
+ return cls(
861
+ n_species=len(labels),
862
+ n_reactions=len(arrows),
863
+ n_complexes=len(order),
864
+ n_linkage_classes=len(classes),
865
+ n_terminal_strong_linkage_classes=len(terminal_strong_linkage_classes(crn)),
866
+ rank=rank,
867
+ deficiency=len(order) - len(classes) - rank,
868
+ linkage_class_deficiencies=linkage_class_deficiencies(crn),
869
+ is_weakly_reversible=is_weakly_reversible(crn),
870
+ is_reversible=is_reversible(crn),
871
+ deficiency_zero=deficiency_zero_verdict(crn),
872
+ deficiency_one=deficiency_one_verdict(crn),
873
+ complexes=[str(c) for c in order],
874
+ )
875
+
876
+ def to_dict(self) -> Dict[str, Any]:
877
+ """Return the summary as a plain dictionary.
878
+
879
+ :return:
880
+ Serializable mapping of every field.
881
+ :rtype: Dict[str, Any]
882
+ """
883
+ return {
884
+ "n_species": self.n_species,
885
+ "n_reactions": self.n_reactions,
886
+ "n_complexes": self.n_complexes,
887
+ "n_linkage_classes": self.n_linkage_classes,
888
+ "n_terminal_strong_linkage_classes": (
889
+ self.n_terminal_strong_linkage_classes
890
+ ),
891
+ "rank": self.rank,
892
+ "deficiency": self.deficiency,
893
+ "linkage_class_deficiencies": list(self.linkage_class_deficiencies),
894
+ "is_weakly_reversible": self.is_weakly_reversible,
895
+ "is_reversible": self.is_reversible,
896
+ "deficiency_zero": dict(self.deficiency_zero),
897
+ "deficiency_one": dict(self.deficiency_one),
898
+ "complexes": list(self.complexes),
899
+ }
900
+
901
+ def __str__(self) -> str:
902
+ """Return a compact multi-line report.
903
+
904
+ :return:
905
+ Human-readable summary.
906
+ :rtype: str
907
+ """
908
+ lines = [
909
+ f"CRNT summary: {self.n_species} species, "
910
+ f"{self.n_reactions} reactions, {self.n_complexes} complexes",
911
+ f" linkage classes n={self.n_linkage_classes} "
912
+ f"(terminal strong: {self.n_terminal_strong_linkage_classes})",
913
+ f" rank s={self.rank}, deficiency delta={self.deficiency}",
914
+ f" weakly reversible: {self.is_weakly_reversible}, "
915
+ f"reversible: {self.is_reversible}",
916
+ ]
917
+ if self.deficiency_zero.get("applicable"):
918
+ lines.append(f" deficiency-zero: {self.deficiency_zero['conclusion']}")
919
+ if self.deficiency_one.get("applicable"):
920
+ lines.append(f" deficiency-one: {self.deficiency_one['conclusion']}")
921
+ return "\n".join(lines)
922
+
923
+
924
+ def crnt_summary(
925
+ crn: Any, *, backend="auto", library_path=None, time_limit=30.0
926
+ ) -> CRNTSummary:
927
+ """Compute the full structural CRNT report for a network.
928
+
929
+ :param crn:
930
+ CRN-like input.
931
+ :type crn: Any
932
+
933
+ :return:
934
+ Populated summary.
935
+ :rtype: CRNTSummary
936
+
937
+ .. rubric:: Example
938
+
939
+ .. code-block:: python
940
+
941
+ crn = SynCRN.from_reaction_strings(["A>>B", "B>>A", "B>>C", "C>>B"])
942
+ print(crnt_summary(crn))
943
+ """
944
+ from ..backend import resolve_backend
945
+
946
+ backend, library_path = resolve_backend(backend, library_path)
947
+ if backend == "cpp":
948
+ from ..native_analysis import crnt_summary_native
949
+
950
+ return crnt_summary_native(
951
+ crn, library_path=library_path, time_limit=time_limit
952
+ )
953
+ return CRNTSummary.from_crn(crn)