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,832 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, List, Optional, Tuple
5
+
6
+ import networkx as nx
7
+ import numpy as np
8
+ from networkx.algorithms import bipartite as nx_bipartite
9
+
10
+ try:
11
+ import sympy as sp # type: ignore
12
+
13
+ _SYMPY_AVAILABLE = True
14
+ except Exception:
15
+ sp = None # type: ignore
16
+ _SYMPY_AVAILABLE = False
17
+
18
+ from .stoich import build_S, build_S_minus_plus
19
+
20
+ __all__ = [
21
+ "StructuralSingularitySummary",
22
+ "symbolic_reactivity_matrix",
23
+ "symbolic_jacobian",
24
+ "jacobian_sparsity",
25
+ "jacobian_sign_pattern",
26
+ "species_influence_graph",
27
+ "structural_singularity_summary",
28
+ ]
29
+
30
+
31
+ def _require_sympy() -> None:
32
+ """Ensure that SymPy is available before using symbolic CRN dynamics helpers.
33
+
34
+ This helper centralizes the optional dependency check used by symbolic
35
+ routines such as symbolic Jacobian construction and exact determinant
36
+ evaluation.
37
+
38
+ :raises ImportError:
39
+ If :mod:`sympy` is not installed or could not be imported.
40
+
41
+ .. rubric:: Example
42
+
43
+ .. code-block:: python
44
+
45
+ try:
46
+ _require_sympy()
47
+ except ImportError as exc:
48
+ print(exc)
49
+ """
50
+ if not _SYMPY_AVAILABLE or sp is None:
51
+ raise ImportError(
52
+ "sympy is required for symbolic CRN dynamics helpers. "
53
+ "Please install it, e.g. `pip install sympy`."
54
+ )
55
+
56
+
57
+ def _safe_symbol_token(value: Any) -> str:
58
+ """Convert an arbitrary object into a SymPy-safe symbol token.
59
+
60
+ Non-alphanumeric characters are replaced by underscores. If the resulting
61
+ token is empty, ``"x"`` is used. If the token starts with a digit, a
62
+ leading underscore is prepended.
63
+
64
+ :param value:
65
+ Arbitrary object to convert into a symbol-safe token.
66
+ :type value:
67
+ Any
68
+
69
+ :return:
70
+ Sanitized token suitable for building SymPy symbol names.
71
+ :rtype:
72
+ str
73
+
74
+ .. rubric:: Example
75
+
76
+ .. code-block:: python
77
+
78
+ _safe_symbol_token("A")
79
+ _safe_symbol_token("species-1")
80
+ _safe_symbol_token("3-node")
81
+
82
+ # possible outputs:
83
+ # "A"
84
+ # "species_1"
85
+ # "_3_node"
86
+ """
87
+ text = str(value)
88
+ token = "".join(ch if ch.isalnum() else "_" for ch in text)
89
+ if not token:
90
+ token = "x"
91
+ if token[0].isdigit():
92
+ token = f"_{token}"
93
+ return token
94
+
95
+
96
+ def _sympy_matrix_from_numpy(A: np.ndarray) -> "sp.Matrix":
97
+ """Convert a numeric NumPy array into a SymPy matrix.
98
+
99
+ Each entry is passed through :func:`sympy.nsimplify` so that integer and
100
+ rational values are preserved when possible.
101
+
102
+ :param A:
103
+ Numeric array to convert.
104
+ :type A:
105
+ numpy.ndarray
106
+
107
+ :return:
108
+ SymPy matrix with simplified symbolic entries.
109
+ :rtype:
110
+ sp.Matrix
111
+
112
+ :raises ImportError:
113
+ If :mod:`sympy` is not available.
114
+
115
+ .. rubric:: Example
116
+
117
+ .. code-block:: python
118
+
119
+ import numpy as np
120
+
121
+ A = np.array([[1.0, -1.0], [0.0, 2.0]])
122
+ M = _sympy_matrix_from_numpy(A)
123
+ print(M)
124
+ """
125
+ _require_sympy()
126
+ return sp.Matrix([[sp.nsimplify(x) for x in row] for row in np.asarray(A)])
127
+
128
+
129
+ def _structural_sign_pattern(
130
+ S: np.ndarray,
131
+ S_minus: np.ndarray,
132
+ *,
133
+ tol: float = 1e-12,
134
+ ) -> np.ndarray:
135
+ """Compute the structural sign pattern of the symbolic Jacobian.
136
+
137
+ For a symbolic Jacobian of the form ``G = S R``, the sign of entry
138
+ ``G[i, k]`` depends on the stoichiometric effects in row ``i`` and on
139
+ whether species ``k`` participates as a reactant in the corresponding
140
+ rule columns. This routine derives the sign pattern directly from the
141
+ stoichiometric matrices without constructing the full symbolic Jacobian.
142
+
143
+ Returned entries are one of:
144
+
145
+ - ``"0"``
146
+ - ``"+"``
147
+ - ``"-"``
148
+ - ``"mixed"``
149
+
150
+ :param S:
151
+ Net stoichiometric matrix with shape ``(n_species, n_rules)``.
152
+ :type S:
153
+ numpy.ndarray
154
+ :param S_minus:
155
+ Reactant stoichiometric matrix with shape ``(n_species, n_rules)``.
156
+ :type S_minus:
157
+ numpy.ndarray
158
+ :param tol:
159
+ Numerical tolerance used to test structural positivity or negativity.
160
+ :type tol:
161
+ float
162
+
163
+ :return:
164
+ Matrix of sign labels with shape ``(n_species, n_species)``.
165
+ :rtype:
166
+ numpy.ndarray
167
+
168
+ .. rubric:: Example
169
+
170
+ .. code-block:: python
171
+
172
+ import numpy as np
173
+
174
+ S = np.array([
175
+ [-1, 0],
176
+ [ 1, -1],
177
+ [ 0, 1],
178
+ ], dtype=float)
179
+
180
+ S_minus = np.array([
181
+ [1, 0],
182
+ [0, 1],
183
+ [0, 0],
184
+ ], dtype=float)
185
+
186
+ P = _structural_sign_pattern(S, S_minus)
187
+ print(P)
188
+ """
189
+ n_species, n_rules = S.shape
190
+ out = np.full((n_species, n_species), "0", dtype=object)
191
+
192
+ for i in range(n_species): # affected species / row of G
193
+ for k in range(n_species): # source species / column of G
194
+ has_pos = False
195
+ has_neg = False
196
+
197
+ for j in range(n_rules):
198
+ if float(S_minus[k, j]) > tol:
199
+ coeff = float(S[i, j])
200
+ if coeff > tol:
201
+ has_pos = True
202
+ elif coeff < -tol:
203
+ has_neg = True
204
+
205
+ if has_pos and has_neg:
206
+ out[i, k] = "mixed"
207
+ break
208
+ else:
209
+ if has_pos and has_neg:
210
+ out[i, k] = "mixed"
211
+ elif has_pos:
212
+ out[i, k] = "+"
213
+ elif has_neg:
214
+ out[i, k] = "-"
215
+ else:
216
+ out[i, k] = "0"
217
+
218
+ return out
219
+
220
+
221
+ def _jacobian_pattern_bipartite(A: np.ndarray) -> Tuple[nx.Graph, List[str], List[str]]:
222
+ """Build the row/column bipartite graph for a Jacobian sparsity pattern.
223
+
224
+ The bipartite graph is used for structural-rank diagnostics via maximum
225
+ matching. Row nodes correspond to Jacobian rows and column nodes correspond
226
+ to Jacobian columns. An edge is added when the corresponding entry in the
227
+ sparsity matrix is nonzero.
228
+
229
+ :param A:
230
+ Boolean or truthy/falsy Jacobian sparsity matrix.
231
+ :type A:
232
+ numpy.ndarray
233
+
234
+ :return:
235
+ Tuple containing:
236
+
237
+ - bipartite graph,
238
+ - ordered row-node names,
239
+ - ordered column-node names.
240
+ :rtype:
241
+ Tuple[nx.Graph, List[str], List[str]]
242
+
243
+ .. rubric:: Example
244
+
245
+ .. code-block:: python
246
+
247
+ import numpy as np
248
+
249
+ A = np.array([
250
+ [True, False],
251
+ [True, True],
252
+ ], dtype=bool)
253
+
254
+ B, row_nodes, col_nodes = _jacobian_pattern_bipartite(A)
255
+ print(row_nodes)
256
+ print(col_nodes)
257
+ print(B.number_of_edges())
258
+ """
259
+ n_rows, n_cols = A.shape
260
+ B = nx.Graph()
261
+
262
+ row_nodes = [f"row:{i}" for i in range(n_rows)]
263
+ col_nodes = [f"col:{j}" for j in range(n_cols)]
264
+
265
+ B.add_nodes_from(row_nodes, bipartite=0)
266
+ B.add_nodes_from(col_nodes, bipartite=1)
267
+
268
+ for i in range(n_rows):
269
+ for j in range(n_cols):
270
+ if bool(A[i, j]):
271
+ B.add_edge(row_nodes[i], col_nodes[j])
272
+
273
+ return B, row_nodes, col_nodes
274
+
275
+
276
+ @dataclass
277
+ class StructuralSingularitySummary:
278
+ """Summary of structural singularity diagnostics for a symbolic Jacobian.
279
+
280
+ The diagnostics are species-level because the Jacobian is a
281
+ species-by-species object, even though the underlying SynCRN graph is
282
+ bipartite with species nodes and rule nodes.
283
+
284
+ :param n_species:
285
+ Number of species, i.e. Jacobian dimension.
286
+ :type n_species:
287
+ int
288
+ :param structural_rank:
289
+ Structural rank inferred from the Jacobian sparsity pattern.
290
+ :type structural_rank:
291
+ int
292
+ :param has_perfect_matching:
293
+ Whether the bipartite sparsity graph admits a perfect matching.
294
+ :type has_perfect_matching:
295
+ bool
296
+ :param pattern_singular:
297
+ Whether the Jacobian is singular at the structural-pattern level.
298
+ :type pattern_singular:
299
+ bool
300
+ :param determinant_checked:
301
+ Whether an exact symbolic determinant was computed.
302
+ :type determinant_checked:
303
+ bool
304
+ :param determinant_expr:
305
+ Exact symbolic determinant expression, if evaluated.
306
+ :type determinant_expr:
307
+ Optional[Any]
308
+ :param determinant_is_zero:
309
+ Whether the exact symbolic determinant simplified to zero.
310
+ :type determinant_is_zero:
311
+ Optional[bool]
312
+
313
+ .. rubric:: Example
314
+
315
+ .. code-block:: python
316
+
317
+ summary = StructuralSingularitySummary(
318
+ n_species=3,
319
+ structural_rank=2,
320
+ has_perfect_matching=False,
321
+ pattern_singular=True,
322
+ determinant_checked=False,
323
+ )
324
+
325
+ print(summary.classification)
326
+ print(summary.to_dict())
327
+ """
328
+
329
+ n_species: int
330
+ structural_rank: int
331
+ has_perfect_matching: bool
332
+ pattern_singular: bool
333
+ determinant_checked: bool
334
+ determinant_expr: Optional[Any] = None
335
+ determinant_is_zero: Optional[bool] = None
336
+
337
+ @property
338
+ def classification(self) -> str:
339
+ """Return a concise structural-singularity classification label.
340
+
341
+ Possible values include pattern-level singularity, exact symbolic
342
+ singularity, exact symbolic nonsingularity, or the case where only
343
+ the sparsity-pattern analysis was performed.
344
+
345
+ :return:
346
+ Classification string summarizing the diagnostic outcome.
347
+ :rtype:
348
+ str
349
+
350
+ .. rubric:: Example
351
+
352
+ .. code-block:: python
353
+
354
+ summary = StructuralSingularitySummary(
355
+ n_species=4,
356
+ structural_rank=4,
357
+ has_perfect_matching=True,
358
+ pattern_singular=False,
359
+ determinant_checked=True,
360
+ determinant_is_zero=False,
361
+ )
362
+
363
+ print(summary.classification)
364
+ """
365
+ if self.pattern_singular:
366
+ return "singular_by_pattern"
367
+ if self.determinant_is_zero is True:
368
+ return "singular_by_exact_determinant"
369
+ if self.determinant_is_zero is False:
370
+ return "structurally_nonsingular"
371
+ return "pattern_nonsingular_exact_unchecked"
372
+
373
+ def to_dict(self) -> Dict[str, Any]:
374
+ """Convert the summary to a plain dictionary.
375
+
376
+ Symbolic determinant expressions are stringified so the result is easier
377
+ to serialize or log.
378
+
379
+ :return:
380
+ Plain dictionary representation of the summary.
381
+ :rtype:
382
+ Dict[str, Any]
383
+
384
+ .. rubric:: Example
385
+
386
+ .. code-block:: python
387
+
388
+ d = summary.to_dict()
389
+ print(d["classification"])
390
+ """
391
+ return {
392
+ "n_species": self.n_species,
393
+ "structural_rank": self.structural_rank,
394
+ "has_perfect_matching": self.has_perfect_matching,
395
+ "pattern_singular": self.pattern_singular,
396
+ "determinant_checked": self.determinant_checked,
397
+ "determinant_expr": (
398
+ None if self.determinant_expr is None else str(self.determinant_expr)
399
+ ),
400
+ "determinant_is_zero": self.determinant_is_zero,
401
+ "classification": self.classification,
402
+ }
403
+
404
+ def __str__(self) -> str:
405
+ """Return a readable multiline summary.
406
+
407
+ :return:
408
+ Human-readable diagnostic summary.
409
+ :rtype:
410
+ str
411
+
412
+ .. rubric:: Example
413
+
414
+ .. code-block:: python
415
+
416
+ print(summary)
417
+ """
418
+ lines = [
419
+ "StructuralSingularitySummary(",
420
+ f" n_species = {self.n_species}",
421
+ f" structural_rank = {self.structural_rank}",
422
+ f" has_perfect_matching = {self.has_perfect_matching}",
423
+ f" pattern_singular = {self.pattern_singular}",
424
+ f" determinant_checked = {self.determinant_checked}",
425
+ f" determinant_is_zero = {self.determinant_is_zero}",
426
+ f" classification = {self.classification}",
427
+ ")",
428
+ ]
429
+ return "\n".join(lines)
430
+
431
+
432
+ def symbolic_reactivity_matrix(
433
+ crn: Any,
434
+ *,
435
+ symbol_prefix: str = "rprime",
436
+ tol: float = 1e-12,
437
+ ) -> Tuple[List[Any], List[Any], "sp.Matrix"]:
438
+ """Build the symbolic reactivity matrix ``R``.
439
+
440
+ For a SynCRN with ``n_species`` species nodes and ``n_rules`` rule nodes,
441
+ the matrix ``R`` has shape ``(n_rules, n_species)``. Entry ``R[j, i]`` is
442
+ a positive symbolic variable if species ``i`` is a reactant of rule ``j``,
443
+ and zero otherwise.
444
+
445
+ This matrix captures the structural dependence of reaction rates on species
446
+ concentrations without assuming a specific kinetic law beyond positive
447
+ reactant sensitivity.
448
+
449
+ :param crn:
450
+ SynCRN graph or SynCRN-like object in the species/rule representation.
451
+ :type crn:
452
+ Any
453
+ :param symbol_prefix:
454
+ Prefix used when naming symbolic reactivity variables.
455
+ :type symbol_prefix:
456
+ str
457
+ :param tol:
458
+ Tolerance used to decide whether a reactant stoichiometric entry is
459
+ structurally positive.
460
+ :type tol:
461
+ float
462
+
463
+ :return:
464
+ Tuple containing:
465
+
466
+ - species node order,
467
+ - rule node order,
468
+ - symbolic reactivity matrix ``R``.
469
+ :rtype:
470
+ Tuple[List[Any], List[Any], sp.Matrix]
471
+
472
+ :raises ImportError:
473
+ If :mod:`sympy` is not available.
474
+
475
+ .. rubric:: Example
476
+
477
+ .. code-block:: python
478
+
479
+ species_order, rule_order, R = symbolic_reactivity_matrix(crn)
480
+ print(species_order)
481
+ print(rule_order)
482
+ print(R)
483
+ """
484
+ _require_sympy()
485
+
486
+ species_order, rule_order, S_minus, _S_plus = build_S_minus_plus(crn)
487
+ n_species = len(species_order)
488
+ n_rules = len(rule_order)
489
+
490
+ rows = []
491
+ for j in range(n_rules):
492
+ r_tok = _safe_symbol_token(rule_order[j])
493
+ row = []
494
+ for i in range(n_species):
495
+ s_tok = _safe_symbol_token(species_order[i])
496
+ if float(S_minus[i, j]) > tol:
497
+ row.append(sp.Symbol(f"{symbol_prefix}_{r_tok}_{s_tok}", positive=True))
498
+ else:
499
+ row.append(sp.Integer(0))
500
+ rows.append(row)
501
+
502
+ R = sp.Matrix(rows)
503
+ return species_order, rule_order, R
504
+
505
+
506
+ def symbolic_jacobian(
507
+ crn: Any,
508
+ *,
509
+ symbol_prefix: str = "rprime",
510
+ tol: float = 1e-12,
511
+ ) -> Tuple[List[Any], List[Any], "sp.Matrix"]:
512
+ """Build the symbolic Jacobian ``G = S R``.
513
+
514
+ Here ``S`` is the species-by-rule stoichiometric matrix and ``R`` is the
515
+ rule-by-species symbolic reactivity matrix induced by reactant incidence.
516
+ The resulting Jacobian is a species-by-species symbolic matrix describing
517
+ local structural influence in the CRN dynamics.
518
+
519
+ :param crn:
520
+ SynCRN graph or SynCRN-like object in the species/rule representation.
521
+ :type crn:
522
+ Any
523
+ :param symbol_prefix:
524
+ Prefix used for symbolic reactivity variables.
525
+ :type symbol_prefix:
526
+ str
527
+ :param tol:
528
+ Tolerance forwarded to :func:`symbolic_reactivity_matrix`.
529
+ :type tol:
530
+ float
531
+
532
+ :return:
533
+ Tuple containing:
534
+
535
+ - species node order,
536
+ - rule node order,
537
+ - symbolic Jacobian ``G``.
538
+ :rtype:
539
+ Tuple[List[Any], List[Any], sp.Matrix]
540
+
541
+ :raises ImportError:
542
+ If :mod:`sympy` is not available.
543
+
544
+ .. rubric:: Example
545
+
546
+ .. code-block:: python
547
+
548
+ species_order, rule_order, G = symbolic_jacobian(crn)
549
+ print(G.shape)
550
+ print(G)
551
+ """
552
+ _require_sympy()
553
+
554
+ species_order, rule_order, S = build_S(crn)
555
+ _sp_order, _rule_order, R = symbolic_reactivity_matrix(
556
+ crn,
557
+ symbol_prefix=symbol_prefix,
558
+ tol=tol,
559
+ )
560
+
561
+ S_sym = _sympy_matrix_from_numpy(S)
562
+ G = S_sym * R
563
+ return species_order, rule_order, G
564
+
565
+
566
+ def jacobian_sparsity(
567
+ crn: Any,
568
+ *,
569
+ tol: float = 1e-12,
570
+ ) -> Tuple[List[Any], np.ndarray]:
571
+ """Return the boolean sparsity pattern of the symbolic Jacobian.
572
+
573
+ Entry ``A[i, k]`` is ``True`` if species ``k`` can structurally influence
574
+ species ``i`` through at least one rule under the local linearized
575
+ dynamics.
576
+
577
+ :param crn:
578
+ SynCRN graph or SynCRN-like object in the species/rule representation.
579
+ :type crn:
580
+ Any
581
+ :param tol:
582
+ Tolerance used for structural zero testing.
583
+ :type tol:
584
+ float
585
+
586
+ :return:
587
+ Tuple containing:
588
+
589
+ - species node order,
590
+ - boolean Jacobian sparsity matrix.
591
+ :rtype:
592
+ Tuple[List[Any], numpy.ndarray]
593
+
594
+ .. rubric:: Example
595
+
596
+ .. code-block:: python
597
+
598
+ species_order, A = jacobian_sparsity(crn)
599
+ print(species_order)
600
+ print(A.astype(int))
601
+ """
602
+ species_order, _rule_order, S_minus, S_plus = build_S_minus_plus(crn)
603
+ S = S_plus - S_minus
604
+ sign_pat = _structural_sign_pattern(S, S_minus, tol=tol)
605
+ A = sign_pat != "0"
606
+ return species_order, A
607
+
608
+
609
+ def jacobian_sign_pattern(
610
+ crn: Any,
611
+ *,
612
+ tol: float = 1e-12,
613
+ ) -> Tuple[List[Any], np.ndarray]:
614
+ """Compute the structural sign pattern of the symbolic Jacobian.
615
+
616
+ Returned entries are one of:
617
+
618
+ - ``"0"``
619
+ - ``"+"``
620
+ - ``"-"``
621
+ - ``"mixed"``
622
+
623
+ The value ``"mixed"`` means that multiple structurally valid paths exist
624
+ with conflicting positive and negative net effects.
625
+
626
+ :param crn:
627
+ SynCRN graph or SynCRN-like object in the species/rule representation.
628
+ :type crn:
629
+ Any
630
+ :param tol:
631
+ Tolerance used for structural zero testing.
632
+ :type tol:
633
+ float
634
+
635
+ :return:
636
+ Tuple containing:
637
+
638
+ - species node order,
639
+ - Jacobian sign-pattern matrix.
640
+ :rtype:
641
+ Tuple[List[Any], numpy.ndarray]
642
+
643
+ .. rubric:: Example
644
+
645
+ .. code-block:: python
646
+
647
+ species_order, P = jacobian_sign_pattern(crn)
648
+ print(species_order)
649
+ print(P)
650
+ """
651
+ species_order, _rule_order, S_minus, S_plus = build_S_minus_plus(crn)
652
+ S = S_plus - S_minus
653
+ P = _structural_sign_pattern(S, S_minus, tol=tol)
654
+ return species_order, P
655
+
656
+
657
+ def species_influence_graph(
658
+ crn: Any,
659
+ *,
660
+ tol: float = 1e-12,
661
+ use_labels: bool = False,
662
+ ) -> nx.DiGraph:
663
+ """Build the species influence graph induced by the symbolic Jacobian.
664
+
665
+ Nodes represent species. A directed edge ``u -> v`` is added when species
666
+ ``u`` can structurally influence species ``v`` in the local linearized
667
+ dynamics.
668
+
669
+ Edge attribute ``sign`` is one of:
670
+
671
+ - ``"+"``
672
+ - ``"-"``
673
+ - ``"mixed"``
674
+
675
+ Additional edge attributes record the original source and target species
676
+ node identifiers.
677
+
678
+ :param crn:
679
+ SynCRN graph or SynCRN-like object in the species/rule representation.
680
+ :type crn:
681
+ Any
682
+ :param tol:
683
+ Tolerance used for structural zero testing.
684
+ :type tol:
685
+ float
686
+ :param use_labels:
687
+ If ``True`` and ``crn`` is a NetworkX graph, use node labels when
688
+ available; otherwise use original node identifiers.
689
+ :type use_labels:
690
+ bool
691
+
692
+ :return:
693
+ Directed species influence graph.
694
+ :rtype:
695
+ nx.DiGraph
696
+
697
+ .. rubric:: Example
698
+
699
+ .. code-block:: python
700
+
701
+ G_inf = species_influence_graph(crn, use_labels=True)
702
+
703
+ print(G_inf.nodes(data=True))
704
+ print(G_inf.edges(data=True))
705
+ """
706
+ species_order, P = jacobian_sign_pattern(crn, tol=tol)
707
+
708
+ label_map: Dict[Any, Any] = {}
709
+ if use_labels and isinstance(
710
+ crn, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
711
+ ):
712
+ for s in species_order:
713
+ label_map[s] = crn.nodes[s].get("label", s)
714
+ else:
715
+ for s in species_order:
716
+ label_map[s] = s
717
+
718
+ G_inf = nx.DiGraph()
719
+ for s in species_order:
720
+ G_inf.add_node(label_map[s], source_node=s)
721
+
722
+ n_species = len(species_order)
723
+ for i in range(n_species): # target row
724
+ for k in range(n_species): # source column
725
+ sign = str(P[i, k])
726
+ if sign != "0":
727
+ G_inf.add_edge(
728
+ label_map[species_order[k]],
729
+ label_map[species_order[i]],
730
+ sign=sign,
731
+ source_species=species_order[k],
732
+ target_species=species_order[i],
733
+ )
734
+
735
+ return G_inf
736
+
737
+
738
+ def structural_singularity_summary(
739
+ crn: Any,
740
+ *,
741
+ tol: float = 1e-12,
742
+ max_exact_size: int = 7,
743
+ symbol_prefix: str = "rprime",
744
+ ) -> StructuralSingularitySummary:
745
+ """Diagnose structural singularity of the symbolic Jacobian.
746
+
747
+ This routine performs two levels of analysis:
748
+
749
+ 1. structural-rank and perfect-matching diagnostics on the Jacobian
750
+ sparsity pattern, and
751
+ 2. optional exact symbolic determinant evaluation for sufficiently small
752
+ systems.
753
+
754
+ Pattern-level singularity indicates that the Jacobian is singular for all
755
+ admissible parameter values consistent with the sparsity structure. If the
756
+ pattern is not singular, an exact determinant test may still detect
757
+ symbolic cancellation and prove singularity for small systems.
758
+
759
+ :param crn:
760
+ SynCRN graph or SynCRN-like object in the species/rule representation.
761
+ :type crn:
762
+ Any
763
+ :param tol:
764
+ Tolerance used for structural zero testing.
765
+ :type tol:
766
+ float
767
+ :param max_exact_size:
768
+ Maximum number of species for which the exact symbolic determinant is
769
+ computed.
770
+ :type max_exact_size:
771
+ int
772
+ :param symbol_prefix:
773
+ Prefix used for symbolic reactivity variables.
774
+ :type symbol_prefix:
775
+ str
776
+
777
+ :return:
778
+ Structured summary of Jacobian structural-singularity diagnostics.
779
+ :rtype:
780
+ StructuralSingularitySummary
781
+
782
+ :raises ImportError:
783
+ If exact symbolic determinant evaluation is requested but
784
+ :mod:`sympy` is not available.
785
+
786
+ .. rubric:: Example
787
+
788
+ .. code-block:: python
789
+
790
+ summary = structural_singularity_summary(
791
+ crn,
792
+ max_exact_size=6,
793
+ symbol_prefix="rprime",
794
+ )
795
+
796
+ print(summary)
797
+ print(summary.to_dict())
798
+ print(summary.classification)
799
+ """
800
+ species_order, A = jacobian_sparsity(crn, tol=tol)
801
+ n_species = len(species_order)
802
+
803
+ B, row_nodes, _col_nodes = _jacobian_pattern_bipartite(A)
804
+ matching = nx_bipartite.maximum_matching(B, top_nodes=set(row_nodes))
805
+ structural_rank = sum(1 for u in row_nodes if u in matching)
806
+ has_perfect_matching = structural_rank == n_species
807
+ pattern_singular = structural_rank < n_species
808
+
809
+ determinant_checked = False
810
+ determinant_expr: Optional[Any] = None
811
+ determinant_is_zero: Optional[bool] = None
812
+
813
+ if n_species <= max_exact_size:
814
+ _require_sympy()
815
+ determinant_checked = True
816
+ _sp_order, _rule_order, G = symbolic_jacobian(
817
+ crn,
818
+ symbol_prefix=symbol_prefix,
819
+ tol=tol,
820
+ )
821
+ determinant_expr = sp.simplify(G.det())
822
+ determinant_is_zero = bool(determinant_expr == 0)
823
+
824
+ return StructuralSingularitySummary(
825
+ n_species=n_species,
826
+ structural_rank=structural_rank,
827
+ has_perfect_matching=has_perfect_matching,
828
+ pattern_singular=pattern_singular,
829
+ determinant_checked=determinant_checked,
830
+ determinant_expr=determinant_expr,
831
+ determinant_is_zero=determinant_is_zero,
832
+ )