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,1041 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from dataclasses import dataclass, field
5
+ from typing import (
6
+ Any,
7
+ Dict,
8
+ Hashable,
9
+ Iterable,
10
+ List,
11
+ Mapping,
12
+ MutableMapping,
13
+ Optional,
14
+ Set,
15
+ Tuple,
16
+ )
17
+
18
+ import networkx as nx
19
+
20
+ from ..kinds import REACTION_KINDS, SPECIES_KINDS
21
+
22
+ Place = str
23
+ TransitionId = str
24
+ Marking = Mapping[Place, int]
25
+ Multiset = Mapping[str, int]
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class SynCRNIncidence:
30
+ """Canonical species--reaction incidence view extracted from a SynCRN-like object.
31
+
32
+ This dataclass stores a normalized incidence representation that can be
33
+ constructed either from a SynCRN-style object exposing ``species`` and
34
+ ``reactions`` mappings, or from a bipartite :class:`networkx.DiGraph`
35
+ returned by ``SynCRN.to_digraph()``.
36
+
37
+ The canonical representation separates:
38
+
39
+ - species order
40
+ - reaction order
41
+ - species and reaction labels
42
+ - pre-incidence stoichiometry
43
+ - post-incidence stoichiometry
44
+ - source-node provenance metadata
45
+
46
+ :param species_order:
47
+ Ordered list of canonical species identifiers.
48
+ :type species_order: List[str]
49
+ :param reaction_order:
50
+ Ordered list of canonical reaction identifiers.
51
+ :type reaction_order: List[str]
52
+ :param species_labels:
53
+ Mapping from species identifier to display label.
54
+ :type species_labels: Dict[str, str]
55
+ :param reaction_labels:
56
+ Mapping from reaction identifier to display label.
57
+ :type reaction_labels: Dict[str, str]
58
+ :param pre:
59
+ Mapping ``reaction_id -> {species_id: stoichiometric_coefficient}``
60
+ for reactants / input arcs.
61
+ :type pre: Dict[str, Dict[str, int]]
62
+ :param post:
63
+ Mapping ``reaction_id -> {species_id: stoichiometric_coefficient}``
64
+ for products / output arcs.
65
+ :type post: Dict[str, Dict[str, int]]
66
+ :param species_source_node_ids:
67
+ Mapping from canonical species identifier to original source node id.
68
+ :type species_source_node_ids: Dict[str, Hashable]
69
+ :param reaction_source_node_ids:
70
+ Mapping from canonical reaction identifier to original source node id.
71
+ :type reaction_source_node_ids: Dict[str, Hashable]
72
+ :param graph_attrs:
73
+ Graph-level metadata copied from the source object or source graph.
74
+ :type graph_attrs: Dict[str, Any]
75
+ :param metadata:
76
+ Additional extraction metadata.
77
+ :type metadata: Dict[str, Any]
78
+
79
+ .. rubric:: Example
80
+
81
+ .. code-block:: python
82
+
83
+ incidence = SynCRNIncidence(
84
+ species_order=["A", "B", "C"],
85
+ reaction_order=["r1"],
86
+ species_labels={"A": "A", "B": "B", "C": "C"},
87
+ reaction_labels={"r1": "A + B -> C"},
88
+ pre={"r1": {"A": 1, "B": 1}},
89
+ post={"r1": {"C": 1}},
90
+ )
91
+ """
92
+
93
+ species_order: List[str]
94
+ reaction_order: List[str]
95
+ species_labels: Dict[str, str]
96
+ reaction_labels: Dict[str, str]
97
+ pre: Dict[str, Dict[str, int]]
98
+ post: Dict[str, Dict[str, int]]
99
+ species_source_node_ids: Dict[str, Hashable] = field(default_factory=dict)
100
+ reaction_source_node_ids: Dict[str, Hashable] = field(default_factory=dict)
101
+ graph_attrs: Dict[str, Any] = field(default_factory=dict)
102
+ metadata: Dict[str, Any] = field(default_factory=dict)
103
+
104
+
105
+ @dataclass
106
+ class Transition:
107
+ """Petri-net transition with stoichiometric input/output multisets.
108
+
109
+ A transition corresponds to one reaction node in the source SynCRN-like
110
+ representation.
111
+
112
+ :param tid:
113
+ Canonical transition identifier.
114
+ :type tid: TransitionId
115
+ :param pre:
116
+ Input multiset mapping ``place -> coefficient``.
117
+ :type pre: Dict[Place, int]
118
+ :param post:
119
+ Output multiset mapping ``place -> coefficient``.
120
+ :type post: Dict[Place, int]
121
+ :param label:
122
+ Optional display label.
123
+ :type label: Optional[str]
124
+ :param source_reaction_id:
125
+ Optional source reaction identifier from the original CRN object.
126
+ :type source_reaction_id: Optional[str]
127
+ :param metadata:
128
+ Optional transition metadata dictionary.
129
+ :type metadata: Dict[str, Any]
130
+
131
+ .. rubric:: Example
132
+
133
+ .. code-block:: python
134
+
135
+ t = Transition(
136
+ tid="r1",
137
+ pre={"A": 1, "B": 2},
138
+ post={"C": 1},
139
+ label="A + 2B -> C",
140
+ )
141
+ """
142
+
143
+ tid: TransitionId
144
+ pre: Dict[Place, int]
145
+ post: Dict[Place, int]
146
+ label: Optional[str] = None
147
+ source_reaction_id: Optional[str] = None
148
+ metadata: Dict[str, Any] = field(default_factory=dict)
149
+
150
+ def __repr__(self) -> str: # pragma: no cover - simple repr
151
+ """Return a compact debug representation.
152
+
153
+ :return:
154
+ Readable transition representation.
155
+ :rtype: str
156
+ """
157
+ extra = f", label={self.label!r}" if self.label is not None else ""
158
+ return f"Transition({self.tid!r}, pre={self.pre}, post={self.post}{extra})"
159
+
160
+
161
+ def _safe_int(x: Any, default: int = 0) -> int:
162
+ """Convert a value to ``int`` with fallback.
163
+
164
+ :param x:
165
+ Value to convert.
166
+ :type x: Any
167
+ :param default:
168
+ Fallback value used if conversion fails.
169
+ :type default: int
170
+ :return:
171
+ Converted integer value.
172
+ :rtype: int
173
+ """
174
+ try:
175
+ return int(x)
176
+ except Exception:
177
+ return int(default)
178
+
179
+
180
+ def _coerce_stoich(x: Any) -> int:
181
+ """Validate and normalize a stoichiometric coefficient.
182
+
183
+ The value is converted to ``int`` and must be strictly positive.
184
+
185
+ :param x:
186
+ Candidate stoichiometric coefficient.
187
+ :type x: Any
188
+ :return:
189
+ Positive stoichiometric coefficient.
190
+ :rtype: int
191
+ :raises ValueError:
192
+ If the resulting value is not strictly positive.
193
+
194
+ .. rubric:: Example
195
+
196
+ .. code-block:: python
197
+
198
+ _coerce_stoich(2) # 2
199
+ _coerce_stoich("3") # 3
200
+ """
201
+ val = _safe_int(x, default=1)
202
+ if val <= 0:
203
+ raise ValueError(f"Stoichiometric coefficient must be positive, got {x!r}")
204
+ return val
205
+
206
+
207
+ def _naturalish_key(x: Any) -> Tuple[int, str, str]:
208
+ """Build a stable sorting key for mixed identifiers.
209
+
210
+ Numeric-looking strings are sorted numerically first, while all other
211
+ objects are sorted by type name and representation.
212
+
213
+ :param x:
214
+ Object used as an identifier.
215
+ :type x: Any
216
+ :return:
217
+ Tuple suitable for deterministic ordering.
218
+ :rtype: Tuple[int, str, str]
219
+ """
220
+ sx = str(x)
221
+ if sx.isdigit():
222
+ return (0, f"{int(sx):020d}", sx)
223
+ return (1, type(x).__name__, repr(x))
224
+
225
+
226
+ def _graph_node_kind(attrs: Mapping[str, Any]) -> str:
227
+ """Normalize the ``kind`` attribute of a graph node.
228
+
229
+ :param attrs:
230
+ Node attribute mapping.
231
+ :type attrs: Mapping[str, Any]
232
+ :return:
233
+ Lowercased and stripped node kind.
234
+ :rtype: str
235
+ """
236
+ return str(attrs.get("kind", "")).strip().lower()
237
+
238
+
239
+ def _edge_side_from_graph(
240
+ u: Hashable,
241
+ v: Hashable,
242
+ eattrs: Mapping[str, Any],
243
+ reaction_node: Hashable,
244
+ ) -> str:
245
+ """Infer whether an incidence edge belongs to the left or right side.
246
+
247
+ The function first checks the explicit edge attribute ``role``:
248
+
249
+ - ``"reactant"`` -> ``"lhs"``
250
+ - ``"product"`` -> ``"rhs"``
251
+
252
+ If no explicit role is present, direction relative to the reaction node
253
+ is used as a fallback.
254
+
255
+ :param u:
256
+ Source node of the directed edge.
257
+ :type u: Hashable
258
+ :param v:
259
+ Target node of the directed edge.
260
+ :type v: Hashable
261
+ :param eattrs:
262
+ Edge attribute mapping.
263
+ :type eattrs: Mapping[str, Any]
264
+ :param reaction_node:
265
+ Node corresponding to the reaction / transition.
266
+ :type reaction_node: Hashable
267
+ :return:
268
+ Either ``"lhs"`` or ``"rhs"``.
269
+ :rtype: str
270
+ """
271
+ role = eattrs.get("role")
272
+ if role == "reactant":
273
+ return "lhs"
274
+ if role == "product":
275
+ return "rhs"
276
+ return "lhs" if v == reaction_node else "rhs"
277
+
278
+
279
+ def _extract_from_syncrn_object(crn: Any) -> SynCRNIncidence:
280
+ """Extract canonical incidence information from a SynCRN-like object.
281
+
282
+ The input object is expected to expose:
283
+
284
+ - ``species``: mapping of species id -> species-like object
285
+ - ``reactions``: mapping of reaction id -> reaction-like object
286
+
287
+ Species-like objects may expose ``label`` and ``source_node_id``.
288
+ Reaction-like objects may expose ``label``, ``source_node_id``, ``lhs``,
289
+ and ``rhs``.
290
+
291
+ :param crn:
292
+ SynCRN-like object.
293
+ :type crn: Any
294
+ :return:
295
+ Canonical incidence representation.
296
+ :rtype: SynCRNIncidence
297
+
298
+ .. rubric:: Example
299
+
300
+ .. code-block:: python
301
+
302
+ incidence = _extract_from_syncrn_object(crn)
303
+ print(incidence.pre)
304
+ print(incidence.post)
305
+ """
306
+ species_order = [str(x) for x in getattr(crn, "species", {}).keys()]
307
+ reaction_order = [str(x) for x in getattr(crn, "reactions", {}).keys()]
308
+
309
+ species_labels: Dict[str, str] = {}
310
+ reaction_labels: Dict[str, str] = {}
311
+ species_source_node_ids: Dict[str, Hashable] = {}
312
+ reaction_source_node_ids: Dict[str, Hashable] = {}
313
+ pre: Dict[str, Dict[str, int]] = {}
314
+ post: Dict[str, Dict[str, int]] = {}
315
+
316
+ for sid, sp in getattr(crn, "species", {}).items():
317
+ sid_s = str(sid)
318
+ species_labels[sid_s] = str(getattr(sp, "label", sid_s))
319
+ species_source_node_ids[sid_s] = getattr(sp, "source_node_id", sid)
320
+
321
+ for rid, rxn in getattr(crn, "reactions", {}).items():
322
+ rid_s = str(rid)
323
+ reaction_labels[rid_s] = str(getattr(rxn, "label", rid_s))
324
+ reaction_source_node_ids[rid_s] = getattr(rxn, "source_node_id", rid)
325
+ pre[rid_s] = {
326
+ str(sid): int(coeff) for sid, coeff in getattr(rxn, "lhs", {}).items()
327
+ }
328
+ post[rid_s] = {
329
+ str(sid): int(coeff) for sid, coeff in getattr(rxn, "rhs", {}).items()
330
+ }
331
+
332
+ return SynCRNIncidence(
333
+ species_order=species_order,
334
+ reaction_order=reaction_order,
335
+ species_labels=species_labels,
336
+ reaction_labels=reaction_labels,
337
+ pre=pre,
338
+ post=post,
339
+ species_source_node_ids=species_source_node_ids,
340
+ reaction_source_node_ids=reaction_source_node_ids,
341
+ graph_attrs=dict(getattr(crn, "graph_attrs", {}) or {}),
342
+ metadata=dict(getattr(crn, "metadata", {}) or {}),
343
+ )
344
+
345
+
346
+ def _partition_syncrn_nodes(
347
+ crn: nx.DiGraph,
348
+ ) -> Tuple[List[Hashable], List[Hashable]]:
349
+ """Partition graph nodes into species nodes and reaction nodes.
350
+
351
+ Nodes with ``kind == "species"`` are treated as species nodes.
352
+ Nodes with ``kind in {"reaction", "rule"}`` are treated as reaction nodes.
353
+
354
+ :param crn:
355
+ SynCRN bipartite digraph.
356
+ :type crn: nx.DiGraph
357
+ :return:
358
+ Pair ``(species_nodes, reaction_nodes)``.
359
+ :rtype: Tuple[List[Hashable], List[Hashable]]
360
+ """
361
+ species_nodes: List[Hashable] = []
362
+ reaction_nodes: List[Hashable] = []
363
+
364
+ for node, attrs in crn.nodes(data=True):
365
+ kind = _graph_node_kind(attrs)
366
+ if kind in SPECIES_KINDS:
367
+ species_nodes.append(node)
368
+ elif kind in REACTION_KINDS:
369
+ reaction_nodes.append(node)
370
+
371
+ species_nodes.sort(key=lambda n: _naturalish_key(crn.nodes[n].get("syncrn_id", n)))
372
+ reaction_nodes.sort(key=lambda n: _naturalish_key(crn.nodes[n].get("syncrn_id", n)))
373
+ return species_nodes, reaction_nodes
374
+
375
+
376
+ def _build_species_index(
377
+ crn: nx.DiGraph,
378
+ species_nodes: Iterable[Hashable],
379
+ ) -> Tuple[List[str], Dict[str, str], Dict[str, Hashable], Dict[Hashable, str]]:
380
+ """Build canonical species metadata from graph nodes.
381
+
382
+ :param crn:
383
+ Source graph.
384
+ :type crn: nx.DiGraph
385
+ :param species_nodes:
386
+ Iterable of species node ids.
387
+ :type species_nodes: Iterable[Hashable]
388
+ :return:
389
+ Tuple containing species order, labels, source node ids, and reverse lookup.
390
+ :rtype: Tuple[List[str], Dict[str, str], Dict[str, Hashable], Dict[Hashable, str]]
391
+ """
392
+ species_order: List[str] = []
393
+ species_labels: Dict[str, str] = {}
394
+ species_source_node_ids: Dict[str, Hashable] = {}
395
+ species_node_to_id: Dict[Hashable, str] = {}
396
+
397
+ for node in species_nodes:
398
+ attrs = dict(crn.nodes[node])
399
+ sid = str(attrs.get("syncrn_id", node))
400
+ species_order.append(sid)
401
+ species_labels[sid] = str(attrs.get("label", sid))
402
+ species_source_node_ids[sid] = attrs.get("source_node_id", node)
403
+ species_node_to_id[node] = sid
404
+
405
+ return (
406
+ species_order,
407
+ species_labels,
408
+ species_source_node_ids,
409
+ species_node_to_id,
410
+ )
411
+
412
+
413
+ def _build_reaction_index(
414
+ crn: nx.DiGraph,
415
+ reaction_nodes: Iterable[Hashable],
416
+ ) -> Tuple[List[str], Dict[str, str], Dict[str, Hashable], Dict[Hashable, str]]:
417
+ """Build canonical reaction metadata from graph nodes.
418
+
419
+ :param crn:
420
+ Source graph.
421
+ :type crn: nx.DiGraph
422
+ :param reaction_nodes:
423
+ Iterable of reaction node ids.
424
+ :type reaction_nodes: Iterable[Hashable]
425
+ :return:
426
+ Tuple containing reaction order, labels, source node ids, and reverse lookup.
427
+ :rtype: Tuple[List[str], Dict[str, str], Dict[str, Hashable], Dict[Hashable, str]]
428
+ """
429
+ reaction_order: List[str] = []
430
+ reaction_labels: Dict[str, str] = {}
431
+ reaction_source_node_ids: Dict[str, Hashable] = {}
432
+ reaction_node_to_id: Dict[Hashable, str] = {}
433
+
434
+ for node in reaction_nodes:
435
+ attrs = dict(crn.nodes[node])
436
+ rid = str(attrs.get("syncrn_id", node))
437
+ reaction_order.append(rid)
438
+ reaction_labels[rid] = str(attrs.get("label", rid))
439
+ reaction_source_node_ids[rid] = attrs.get("source_node_id", node)
440
+ reaction_node_to_id[node] = rid
441
+
442
+ return (
443
+ reaction_order,
444
+ reaction_labels,
445
+ reaction_source_node_ids,
446
+ reaction_node_to_id,
447
+ )
448
+
449
+
450
+ def _accumulate_incidence_from_edges(
451
+ crn: nx.DiGraph,
452
+ reaction_node: Hashable,
453
+ species_node_to_id: Mapping[Hashable, str],
454
+ ) -> Tuple[Dict[str, int], Dict[str, int]]:
455
+ """Accumulate input and output stoichiometry for one reaction node.
456
+
457
+ Both incoming and outgoing graph edges are inspected. The edge role is
458
+ inferred via :func:`_edge_side_from_graph`.
459
+
460
+ :param crn:
461
+ Source graph.
462
+ :type crn: nx.DiGraph
463
+ :param reaction_node:
464
+ Reaction node whose local incidence is extracted.
465
+ :type reaction_node: Hashable
466
+ :param species_node_to_id:
467
+ Mapping from species graph node to canonical species id.
468
+ :type species_node_to_id: Mapping[Hashable, str]
469
+ :return:
470
+ Pair ``(pre, post)`` for the reaction.
471
+ :rtype: Tuple[Dict[str, int], Dict[str, int]]
472
+ """
473
+ lhs_counter: Counter[str] = Counter()
474
+ rhs_counter: Counter[str] = Counter()
475
+
476
+ for u, v, eattrs in crn.in_edges(reaction_node, data=True):
477
+ if u not in species_node_to_id:
478
+ continue
479
+ sid = species_node_to_id[u]
480
+ side = _edge_side_from_graph(u, v, eattrs, reaction_node)
481
+ stoich = _coerce_stoich(eattrs.get("stoich", 1))
482
+ if side == "lhs":
483
+ lhs_counter[sid] += stoich
484
+ else:
485
+ rhs_counter[sid] += stoich
486
+
487
+ for u, v, eattrs in crn.out_edges(reaction_node, data=True):
488
+ if v not in species_node_to_id:
489
+ continue
490
+ sid = species_node_to_id[v]
491
+ side = _edge_side_from_graph(u, v, eattrs, reaction_node)
492
+ stoich = _coerce_stoich(eattrs.get("stoich", 1))
493
+ if side == "lhs":
494
+ lhs_counter[sid] += stoich
495
+ else:
496
+ rhs_counter[sid] += stoich
497
+
498
+ return dict(lhs_counter), dict(rhs_counter)
499
+
500
+
501
+ def _build_pre_post_from_graph(
502
+ crn: nx.DiGraph,
503
+ reaction_nodes: Iterable[Hashable],
504
+ reaction_node_to_id: Mapping[Hashable, str],
505
+ species_node_to_id: Mapping[Hashable, str],
506
+ ) -> Tuple[Dict[str, Dict[str, int]], Dict[str, Dict[str, int]]]:
507
+ """Build reaction-wise pre and post incidence maps from a graph.
508
+
509
+ :param crn:
510
+ Source graph.
511
+ :type crn: nx.DiGraph
512
+ :param reaction_nodes:
513
+ Iterable of reaction graph nodes.
514
+ :type reaction_nodes: Iterable[Hashable]
515
+ :param reaction_node_to_id:
516
+ Mapping from reaction graph node to canonical reaction id.
517
+ :type reaction_node_to_id: Mapping[Hashable, str]
518
+ :param species_node_to_id:
519
+ Mapping from species graph node to canonical species id.
520
+ :type species_node_to_id: Mapping[Hashable, str]
521
+ :return:
522
+ Pair ``(pre, post)`` keyed by reaction id.
523
+ :rtype: Tuple[Dict[str, Dict[str, int]], Dict[str, Dict[str, int]]]
524
+ """
525
+ pre: Dict[str, Dict[str, int]] = {}
526
+ post: Dict[str, Dict[str, int]] = {}
527
+
528
+ for rnode in reaction_nodes:
529
+ rid = reaction_node_to_id[rnode]
530
+ pre[rid], post[rid] = _accumulate_incidence_from_edges(
531
+ crn=crn,
532
+ reaction_node=rnode,
533
+ species_node_to_id=species_node_to_id,
534
+ )
535
+
536
+ return pre, post
537
+
538
+
539
+ def _extract_from_syncrn_digraph(crn: nx.DiGraph) -> SynCRNIncidence:
540
+ """Extract canonical incidence information from a SynCRN bipartite digraph.
541
+
542
+ The graph is expected to use node attribute ``kind`` with values:
543
+
544
+ - ``"species"`` for species nodes
545
+ - ``"reaction"`` or ``"rule"`` for reaction nodes
546
+
547
+ Optional node attributes include:
548
+
549
+ - ``syncrn_id``
550
+ - ``label``
551
+ - ``source_node_id``
552
+
553
+ Optional edge attributes include:
554
+
555
+ - ``stoich``
556
+ - ``role`` with values ``"reactant"`` or ``"product"``
557
+
558
+ :param crn:
559
+ SynCRN bipartite directed graph.
560
+ :type crn: nx.DiGraph
561
+ :return:
562
+ Canonical incidence representation extracted from the graph.
563
+ :rtype: SynCRNIncidence
564
+ :raises TypeError:
565
+ If ``crn`` is not a :class:`networkx.DiGraph`.
566
+
567
+ .. rubric:: Example
568
+
569
+ .. code-block:: python
570
+
571
+ g = syn.to_digraph()
572
+ incidence = _extract_from_syncrn_digraph(g)
573
+ print(incidence.species_order)
574
+ print(incidence.reaction_order)
575
+ """
576
+ if not isinstance(crn, nx.DiGraph):
577
+ raise TypeError(f"Expected nx.DiGraph, got {type(crn).__name__}")
578
+
579
+ species_nodes, reaction_nodes = _partition_syncrn_nodes(crn)
580
+
581
+ (
582
+ species_order,
583
+ species_labels,
584
+ species_source_node_ids,
585
+ species_node_to_id,
586
+ ) = _build_species_index(crn, species_nodes)
587
+
588
+ (
589
+ reaction_order,
590
+ reaction_labels,
591
+ reaction_source_node_ids,
592
+ reaction_node_to_id,
593
+ ) = _build_reaction_index(crn, reaction_nodes)
594
+
595
+ pre, post = _build_pre_post_from_graph(
596
+ crn=crn,
597
+ reaction_nodes=reaction_nodes,
598
+ reaction_node_to_id=reaction_node_to_id,
599
+ species_node_to_id=species_node_to_id,
600
+ )
601
+
602
+ return SynCRNIncidence(
603
+ species_order=species_order,
604
+ reaction_order=reaction_order,
605
+ species_labels=species_labels,
606
+ reaction_labels=reaction_labels,
607
+ pre=pre,
608
+ post=post,
609
+ species_source_node_ids=species_source_node_ids,
610
+ reaction_source_node_ids=reaction_source_node_ids,
611
+ graph_attrs=dict(crn.graph),
612
+ metadata={"source_graph_type": type(crn).__name__},
613
+ )
614
+
615
+
616
+ def extract_syncrn_incidence(crn: Any) -> SynCRNIncidence:
617
+ """Extract a canonical incidence view from a SynCRN-like object or digraph.
618
+
619
+ The function accepts three input styles:
620
+
621
+ - a :class:`networkx.DiGraph` in SynCRN bipartite format
622
+ - a SynCRN-like object exposing ``species`` and ``reactions``
623
+ - an object exposing ``to_digraph()`` that returns such a graph
624
+
625
+ :param crn:
626
+ SynCRN-like object or bipartite digraph.
627
+ :type crn: Any
628
+ :return:
629
+ Canonical incidence representation.
630
+ :rtype: SynCRNIncidence
631
+ :raises TypeError:
632
+ If the input cannot be interpreted as a supported SynCRN source.
633
+
634
+ .. rubric:: Example
635
+
636
+ .. code-block:: python
637
+
638
+ incidence = extract_syncrn_incidence(crn)
639
+ print(incidence.pre)
640
+ print(incidence.post)
641
+ """
642
+ if isinstance(crn, nx.DiGraph):
643
+ return _extract_from_syncrn_digraph(crn)
644
+
645
+ if hasattr(crn, "species") and hasattr(crn, "reactions"):
646
+ return _extract_from_syncrn_object(crn)
647
+
648
+ if hasattr(crn, "to_digraph"):
649
+ g = crn.to_digraph()
650
+ if isinstance(g, nx.DiGraph):
651
+ return _extract_from_syncrn_digraph(g)
652
+
653
+ raise TypeError(
654
+ "crn must be a SynCRN-like object with species/reactions or an nx.DiGraph "
655
+ "in SynCRN bipartite format"
656
+ )
657
+
658
+
659
+ class PetriNet:
660
+ """Minimal Petri net container with marking semantics and SynCRN metadata.
661
+
662
+ Places correspond to species and transitions correspond to reactions.
663
+ The class stores a lightweight Petri-net representation with utilities
664
+ for adding places and transitions, checking enabledness, firing
665
+ transitions, and converting between mapping-based and tuple-based
666
+ markings.
667
+
668
+ :ivar places: Set of place identifiers.
669
+ :ivar transitions: Mapping from transition id to :class:`Transition`.
670
+ :ivar place_labels: Optional human-readable labels for places.
671
+ :ivar transition_labels: Optional human-readable labels for transitions.
672
+ :ivar place_source_node_ids: Provenance mapping for places.
673
+ :ivar transition_source_node_ids: Provenance mapping for transitions.
674
+ :ivar graph_attrs: Graph-level metadata copied from the source.
675
+ :ivar metadata: Additional metadata.
676
+
677
+
678
+ .. rubric:: Example
679
+
680
+ .. code-block:: python
681
+
682
+ net = PetriNet()
683
+ net.add_place("A")
684
+ net.add_place("B")
685
+ net.add_transition("r1", pre={"A": 1}, post={"B": 1})
686
+
687
+ m0 = {"A": 1}
688
+ assert net.enabled(m0, "r1")
689
+ m1 = net.fire(m0, "r1")
690
+ print(m1) # {'A': 0, 'B': 1}
691
+ """
692
+
693
+ def __init__(self) -> None:
694
+ """Initialize an empty Petri net.
695
+
696
+ :return:
697
+ None
698
+ :rtype: None
699
+ """
700
+ self.places: Set[Place] = set()
701
+ self.transitions: Dict[TransitionId, Transition] = {}
702
+ self._place_index: Dict[Place, int] = {}
703
+ self._transition_index: Dict[TransitionId, int] = {}
704
+ self.place_labels: Dict[Place, str] = {}
705
+ self.transition_labels: Dict[TransitionId, str] = {}
706
+ self.place_source_node_ids: Dict[Place, Hashable] = {}
707
+ self.transition_source_node_ids: Dict[TransitionId, Hashable] = {}
708
+ self.graph_attrs: Dict[str, Any] = {}
709
+ self.metadata: Dict[str, Any] = {}
710
+
711
+ @classmethod
712
+ def from_syncrn(cls, crn: Any) -> "PetriNet":
713
+ """Build a Petri-net view directly from SynCRN incidence data.
714
+
715
+ :param crn:
716
+ SynCRN-like object or SynCRN bipartite digraph.
717
+ :type crn: Any
718
+ :return:
719
+ Petri net constructed from the canonical incidence view.
720
+ :rtype: PetriNet
721
+
722
+ .. rubric:: Example
723
+
724
+ .. code-block:: python
725
+
726
+ net = PetriNet.from_syncrn(crn)
727
+ print(net.place_order)
728
+ print(net.transition_order)
729
+ """
730
+ incidence = extract_syncrn_incidence(crn)
731
+ net = cls()
732
+ net.graph_attrs.update(incidence.graph_attrs)
733
+ net.metadata.update(incidence.metadata)
734
+ net.metadata["source"] = "syncrn"
735
+
736
+ for sid in incidence.species_order:
737
+ net.add_place(
738
+ sid,
739
+ label=incidence.species_labels.get(sid, sid),
740
+ source_node_id=incidence.species_source_node_ids.get(sid),
741
+ )
742
+
743
+ for rid in incidence.reaction_order:
744
+ net.add_transition(
745
+ rid,
746
+ pre=incidence.pre.get(rid, {}),
747
+ post=incidence.post.get(rid, {}),
748
+ label=incidence.reaction_labels.get(rid, rid),
749
+ source_reaction_id=rid,
750
+ )
751
+ if rid in incidence.reaction_source_node_ids:
752
+ net.transition_source_node_ids[rid] = (
753
+ incidence.reaction_source_node_ids[rid]
754
+ )
755
+
756
+ return net
757
+
758
+ @property
759
+ def place_order(self) -> List[Place]:
760
+ """Return places in insertion order.
761
+
762
+ :return:
763
+ Ordered place identifiers.
764
+ :rtype: List[Place]
765
+ """
766
+ return list(self._place_index.keys())
767
+
768
+ @property
769
+ def transition_order(self) -> List[TransitionId]:
770
+ """Return transitions in insertion order.
771
+
772
+ :return:
773
+ Ordered transition identifiers.
774
+ :rtype: List[TransitionId]
775
+ """
776
+ return list(self._transition_index.keys())
777
+
778
+ def add_place(
779
+ self,
780
+ p: Place,
781
+ *,
782
+ label: Optional[str] = None,
783
+ source_node_id: Optional[Hashable] = None,
784
+ ) -> None:
785
+ """Add a place to the Petri net.
786
+
787
+ If the place already exists, only optional metadata is updated.
788
+
789
+ :param p:
790
+ Place identifier.
791
+ :type p: Place
792
+ :param label:
793
+ Optional display label.
794
+ :type label: Optional[str]
795
+ :param source_node_id:
796
+ Optional provenance node id from the source CRN.
797
+ :type source_node_id: Optional[Hashable]
798
+ :return:
799
+ None
800
+ :rtype: None
801
+ """
802
+ if p not in self.places:
803
+ self.places.add(p)
804
+ self._place_index[p] = len(self._place_index)
805
+ if label is not None:
806
+ self.place_labels[p] = str(label)
807
+ if source_node_id is not None:
808
+ self.place_source_node_ids[p] = source_node_id
809
+
810
+ def add_transition(
811
+ self,
812
+ tid: TransitionId,
813
+ pre: Mapping[Place, int],
814
+ post: Mapping[Place, int],
815
+ *,
816
+ label: Optional[str] = None,
817
+ source_reaction_id: Optional[str] = None,
818
+ metadata: Optional[MutableMapping[str, Any]] = None,
819
+ ) -> None:
820
+ """Add or replace a transition in the Petri net.
821
+
822
+ Zero or invalid non-positive weights are filtered out before storage.
823
+ Any places referenced by ``pre`` or ``post`` are created automatically.
824
+
825
+ :param tid:
826
+ Transition identifier.
827
+ :type tid: TransitionId
828
+ :param pre:
829
+ Input arc multiset mapping ``place -> coefficient``.
830
+ :type pre: Mapping[Place, int]
831
+ :param post:
832
+ Output arc multiset mapping ``place -> coefficient``.
833
+ :type post: Mapping[Place, int]
834
+ :param label:
835
+ Optional display label.
836
+ :type label: Optional[str]
837
+ :param source_reaction_id:
838
+ Optional source reaction identifier.
839
+ :type source_reaction_id: Optional[str]
840
+ :param metadata:
841
+ Optional metadata attached to the transition.
842
+ :type metadata: Optional[MutableMapping[str, Any]]
843
+ :return:
844
+ None
845
+ :rtype: None
846
+
847
+ .. rubric:: Example
848
+
849
+ .. code-block:: python
850
+
851
+ net.add_transition(
852
+ "r1",
853
+ pre={"A": 2, "B": 1},
854
+ post={"C": 1},
855
+ label="2A + B -> C",
856
+ )
857
+ """
858
+ clean_pre = {
859
+ str(p): _coerce_stoich(w) for p, w in pre.items() if _safe_int(w, 0) > 0
860
+ }
861
+ clean_post = {
862
+ str(p): _coerce_stoich(w) for p, w in post.items() if _safe_int(w, 0) > 0
863
+ }
864
+
865
+ for p in set(clean_pre) | set(clean_post):
866
+ self.add_place(p)
867
+
868
+ tid = str(tid)
869
+ if tid not in self._transition_index:
870
+ self._transition_index[tid] = len(self._transition_index)
871
+
872
+ self.transitions[tid] = Transition(
873
+ tid=tid,
874
+ pre=clean_pre,
875
+ post=clean_post,
876
+ label=None if label is None else str(label),
877
+ source_reaction_id=(
878
+ None if source_reaction_id is None else str(source_reaction_id)
879
+ ),
880
+ metadata=dict(metadata or {}),
881
+ )
882
+ if label is not None:
883
+ self.transition_labels[tid] = str(label)
884
+
885
+ def enabled(self, marking: Marking, tid: TransitionId) -> bool:
886
+ """Test whether a transition is enabled under a marking.
887
+
888
+ A transition is enabled if every required input place has at least the
889
+ corresponding token count.
890
+
891
+ :param marking:
892
+ Current marking as ``place -> token_count``.
893
+ :type marking: Marking
894
+ :param tid:
895
+ Transition identifier.
896
+ :type tid: TransitionId
897
+ :return:
898
+ ``True`` if the transition is enabled, else ``False``.
899
+ :rtype: bool
900
+ """
901
+ t = self.transitions[tid]
902
+ return all(int(marking.get(p, 0)) >= w for p, w in t.pre.items())
903
+
904
+ def fire(self, marking: Marking, tid: TransitionId) -> Dict[Place, int]:
905
+ """Fire a transition and return the successor marking.
906
+
907
+ This function does not itself check enabledness. If the transition is
908
+ not enabled, negative token counts may appear in the result.
909
+
910
+ :param marking:
911
+ Current marking.
912
+ :type marking: Marking
913
+ :param tid:
914
+ Transition identifier to fire.
915
+ :type tid: TransitionId
916
+ :return:
917
+ Successor marking after consuming ``pre`` and producing ``post``.
918
+ :rtype: Dict[Place, int]
919
+
920
+ .. rubric:: Example
921
+
922
+ .. code-block:: python
923
+
924
+ m0 = {"A": 2, "B": 1}
925
+ m1 = net.fire(m0, "r1")
926
+ """
927
+ t = self.transitions[tid]
928
+ nxt = {str(p): int(v) for p, v in marking.items()}
929
+ for p, w in t.pre.items():
930
+ nxt[p] = nxt.get(p, 0) - w
931
+ for p, w in t.post.items():
932
+ nxt[p] = nxt.get(p, 0) + w
933
+ return nxt
934
+
935
+ def marking_to_tuple(self, m: Marking) -> Tuple[int, ...]:
936
+ """Convert a mapping-based marking into a tuple in place order.
937
+
938
+ :param m:
939
+ Marking mapping.
940
+ :type m: Marking
941
+ :return:
942
+ Tuple of token counts aligned to :attr:`place_order`.
943
+ :rtype: Tuple[int, ...]
944
+ """
945
+ arr = [0] * len(self._place_index)
946
+ for p, idx in self._place_index.items():
947
+ arr[idx] = int(m.get(p, 0))
948
+ return tuple(arr)
949
+
950
+ def tuple_to_marking(self, values: Iterable[int]) -> Dict[Place, int]:
951
+ """Convert a tuple-like token vector into a sparse marking mapping.
952
+
953
+ Zero entries are omitted from the returned dictionary.
954
+
955
+ :param values:
956
+ Iterable of token counts aligned to :attr:`place_order`.
957
+ :type values: Iterable[int]
958
+ :return:
959
+ Sparse marking mapping.
960
+ :rtype: Dict[Place, int]
961
+ """
962
+ vals = list(values)
963
+ return {
964
+ p: int(vals[idx])
965
+ for p, idx in self._place_index.items()
966
+ if idx < len(vals) and int(vals[idx]) != 0
967
+ }
968
+
969
+ def place_name(self, p: Place) -> str:
970
+ """Return the display label of a place if available.
971
+
972
+ :param p:
973
+ Place identifier.
974
+ :type p: Place
975
+ :return:
976
+ Display label or the identifier itself.
977
+ :rtype: str
978
+ """
979
+ return self.place_labels.get(p, p)
980
+
981
+ def transition_name(self, tid: TransitionId) -> str:
982
+ """Return the display label of a transition if available.
983
+
984
+ :param tid:
985
+ Transition identifier.
986
+ :type tid: TransitionId
987
+ :return:
988
+ Display label or the identifier itself.
989
+ :rtype: str
990
+ """
991
+ return self.transition_labels.get(tid, tid)
992
+
993
+ def to_pre_post(self) -> Dict[str, Any]:
994
+ """Export the Petri net as place-indexed pre/post adjacency maps.
995
+
996
+ The returned structure is often convenient for reachability, firing,
997
+ or incidence-based downstream algorithms.
998
+
999
+ :return:
1000
+ Dictionary containing places, transitions, labels, pre/post maps,
1001
+ graph attributes, and metadata.
1002
+ :rtype: Dict[str, Any]
1003
+
1004
+ .. rubric:: Example
1005
+
1006
+ .. code-block:: python
1007
+
1008
+ data = net.to_pre_post()
1009
+ print(data["pre"])
1010
+ print(data["post"])
1011
+ """
1012
+ pre = {p: {} for p in self.place_order}
1013
+ post = {p: {} for p in self.place_order}
1014
+ for tid in self.transition_order:
1015
+ t = self.transitions[tid]
1016
+ for p, w in t.pre.items():
1017
+ pre.setdefault(p, {})[tid] = w
1018
+ for p, w in t.post.items():
1019
+ post.setdefault(p, {})[tid] = w
1020
+ return {
1021
+ "places": self.place_order,
1022
+ "transitions": self.transition_order,
1023
+ "place_labels": dict(self.place_labels),
1024
+ "transition_labels": dict(self.transition_labels),
1025
+ "pre": pre,
1026
+ "post": post,
1027
+ "graph_attrs": dict(self.graph_attrs),
1028
+ "metadata": dict(self.metadata),
1029
+ }
1030
+
1031
+ def __repr__(self) -> str: # pragma: no cover - simple repr
1032
+ """Return a compact debug representation.
1033
+
1034
+ :return:
1035
+ Readable Petri net summary.
1036
+ :rtype: str
1037
+ """
1038
+ return (
1039
+ f"PetriNet(n_places={len(self.places)}, "
1040
+ f"n_transitions={len(self.transitions)})"
1041
+ )