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,1322 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict, deque
4
+ from dataclasses import dataclass, field
5
+ from enum import Enum
6
+ from typing import Dict, Iterable, List, Mapping, Optional, Set, Tuple
7
+
8
+ import itertools
9
+ import json
10
+
11
+ import networkx as nx
12
+
13
+ from ..Petrinet import Multiset, PetriNet, Place, TransitionId
14
+ from ._adapter import tokenize_syncrn_incidence
15
+
16
+
17
+ @dataclass
18
+ class RealizabilityConfig:
19
+ """Configuration for bounded realizability search.
20
+
21
+ :param max_states:
22
+ Maximum number of BFS states explored during exact realizability
23
+ search.
24
+ :type max_states: int
25
+ :param max_depth:
26
+ Maximum firing-sequence depth explored during exact realizability
27
+ search.
28
+ :type max_depth: int
29
+
30
+ .. rubric:: Example
31
+
32
+ .. code-block:: python
33
+
34
+ cfg = RealizabilityConfig(
35
+ max_states=200_000,
36
+ max_depth=5_000,
37
+ )
38
+ """
39
+
40
+ max_states: int = 100_000
41
+ max_depth: int = 10_000
42
+
43
+
44
+ class RealizabilityStatus(str, Enum):
45
+ """Outcome of a bounded exact realizability search.
46
+
47
+ ``UNREALIZABLE`` is reserved for an exhaustively searched finite state
48
+ space. Reaching a state or depth budget produces ``INCONCLUSIVE`` instead,
49
+ so a resource limit cannot be mistaken for a mathematical negative.
50
+ """
51
+
52
+ REALIZABLE = "realizable"
53
+ UNREALIZABLE = "unrealizable"
54
+ INCONCLUSIVE = "inconclusive"
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class RealizabilityResult:
59
+ """Typed result of a realizability search.
60
+
61
+ :param status:
62
+ Certified search outcome.
63
+ :type status: RealizabilityStatus
64
+ :param certificate:
65
+ Realizing firing sequence when ``status`` is ``REALIZABLE``.
66
+ :type certificate: Optional[List[TransitionId]]
67
+ :param explored_states:
68
+ Number of Petri-net markings removed from the BFS queue.
69
+ :type explored_states: int
70
+ :param max_depth_reached:
71
+ Greatest firing-sequence depth among explored markings.
72
+ :type max_depth_reached: int
73
+ :param exhaustive:
74
+ Whether the reachable finite state space was exhausted.
75
+ :type exhaustive: bool
76
+ :param termination_reason:
77
+ One of ``certificate``, ``exhausted``, ``state_limit`` or
78
+ ``depth_limit``.
79
+ :type termination_reason: str
80
+ """
81
+
82
+ status: RealizabilityStatus
83
+ certificate: Optional[List[TransitionId]]
84
+ explored_states: int
85
+ max_depth_reached: int
86
+ exhaustive: bool
87
+ termination_reason: str
88
+
89
+ @property
90
+ def realizable(self) -> Optional[bool]:
91
+ """Return a tri-state Boolean view of the outcome.
92
+
93
+ :return:
94
+ ``True`` for a certificate, ``False`` for exhaustive failure and
95
+ ``None`` for a resource-limited search.
96
+ :rtype: Optional[bool]
97
+ """
98
+ if self.status is RealizabilityStatus.REALIZABLE:
99
+ return True
100
+ if self.status is RealizabilityStatus.UNREALIZABLE:
101
+ return False
102
+ return None
103
+
104
+ def to_dict(self) -> Dict[str, object]:
105
+ """Return a JSON-serializable representation.
106
+
107
+ :return:
108
+ Search outcome and audit metadata.
109
+ :rtype: Dict[str, object]
110
+ """
111
+ return {
112
+ "status": self.status.value,
113
+ "realizable": self.realizable,
114
+ "certificate": self.certificate,
115
+ "explored_states": self.explored_states,
116
+ "max_depth_reached": self.max_depth_reached,
117
+ "exhaustive": self.exhaustive,
118
+ "termination_reason": self.termination_reason,
119
+ }
120
+
121
+
122
+ class RealizabilitySearchLimit(RuntimeError):
123
+ """Raised when the compatibility API reaches a search budget.
124
+
125
+ :param result:
126
+ Inconclusive detailed search result.
127
+ :type result: RealizabilityResult
128
+ """
129
+
130
+ def __init__(self, result: RealizabilityResult) -> None:
131
+ self.result = result
132
+ super().__init__(
133
+ "Realizability search is inconclusive: "
134
+ f"{result.termination_reason} after {result.explored_states} states"
135
+ )
136
+
137
+
138
+ @dataclass
139
+ class RealizabilitySummary:
140
+ """Small serializable summary of the active realizability instance.
141
+
142
+ :param n_species:
143
+ Number of species currently loaded in the instance.
144
+ :type n_species: int
145
+ :param n_reactions:
146
+ Number of reactions currently loaded in the instance.
147
+ :type n_reactions: int
148
+ :param active_flow:
149
+ Active reaction flow restricted to reactions with positive requested
150
+ firing count.
151
+ :type active_flow: Dict[str, int]
152
+ :param initial_marking:
153
+ Initial marking supplied by the user before Petri-net augmentation.
154
+ :type initial_marking: Dict[str, int]
155
+ :param goal_exact:
156
+ Exact target marking constraints currently imposed on the auxiliary
157
+ Petri-net places.
158
+ :type goal_exact: Dict[str, int]
159
+ :param goal_atleast:
160
+ Lower-bound target marking constraints currently imposed on the
161
+ auxiliary Petri-net places.
162
+ :type goal_atleast: Dict[str, int]
163
+
164
+ .. rubric:: Example
165
+
166
+ .. code-block:: python
167
+
168
+ summary = pr.summary()
169
+ print(summary.n_species)
170
+ print(summary.active_flow)
171
+ """
172
+
173
+ n_species: int
174
+ n_reactions: int
175
+ active_flow: Dict[str, int] = field(default_factory=dict)
176
+ initial_marking: Dict[str, int] = field(default_factory=dict)
177
+ goal_exact: Dict[str, int] = field(default_factory=dict)
178
+ goal_atleast: Dict[str, int] = field(default_factory=dict)
179
+
180
+
181
+ class PathwayRealizability:
182
+ """Exact flow-realizability utilities for SynCRN-like inputs.
183
+
184
+ A pathway flow is realizable if there exists an ordering of reaction
185
+ firings such that:
186
+
187
+ - each selected reaction is fired exactly the requested number of times,
188
+ - the execution starts from the supplied initial marking,
189
+ - every firing respects stoichiometric token consumption and production.
190
+
191
+ Unspecified reactions default to flow 0.
192
+
193
+ The class supports:
194
+
195
+ - loading directly from tokenized hypergraph-style inputs,
196
+ - loading from a SynCRN-like object,
197
+ - exact bounded BFS realizability testing,
198
+ - a sufficient acyclicity test via a König-style construction,
199
+ - scaled realizability,
200
+ - borrow realizability.
201
+
202
+ :param config:
203
+ Optional configuration for bounded realizability search. If ``None``,
204
+ a default :class:`RealizabilityConfig` is used.
205
+ :type config: Optional[RealizabilityConfig]
206
+
207
+ .. rubric:: Example
208
+
209
+ .. code-block:: python
210
+
211
+ pr = PathwayRealizability().load_syncrn_and_flow(
212
+ syn,
213
+ flow={"12": 1, "13": 1},
214
+ species="label",
215
+ reaction="id",
216
+ )
217
+ pr.build_petri_net_from_flow()
218
+ ok, cert = pr.is_realizable()
219
+ print(ok, cert)
220
+ """
221
+
222
+ def __init__(self, config: Optional[RealizabilityConfig] = None) -> None:
223
+ """Initialize an empty pathway realizability instance.
224
+
225
+ :param config:
226
+ Optional configuration for bounded realizability search.
227
+ :type config: Optional[RealizabilityConfig]
228
+ :return:
229
+ None.
230
+ :rtype: None
231
+ """
232
+ self.vertices: Set[str] = set()
233
+ self.edges: Dict[str, Tuple[Multiset, Multiset]] = {}
234
+ self.flow: Dict[str, int] = {}
235
+ self.species_token_to_id: Dict[str, str] = {}
236
+ self.reaction_token_to_id: Dict[str, str] = {}
237
+ self.species_id_to_token: Dict[str, str] = {}
238
+ self.reaction_id_to_token: Dict[str, str] = {}
239
+ self._provided_initial_marking: Dict[str, int] = {}
240
+ self._petri: Optional[PetriNet] = None
241
+ self._initial_marking: Optional[Dict[Place, int]] = None
242
+ self._goal_exact: Dict[Place, int] = {}
243
+ self._goal_atleast: Dict[Place, int] = {}
244
+ self._certificate: Optional[List[TransitionId]] = None
245
+ self._last_result: Optional[RealizabilityResult] = None
246
+ self._config = config or RealizabilityConfig()
247
+
248
+ # ------------------------------------------------------------------
249
+ # loading
250
+ # ------------------------------------------------------------------
251
+
252
+ @staticmethod
253
+ def _clean_multiset(data: Optional[Mapping[str, int]]) -> Dict[str, int]:
254
+ """Normalize a multiset mapping by dropping zero entries.
255
+
256
+ Negative multiplicities are rejected.
257
+
258
+ :param data:
259
+ Input multiset or ``None``.
260
+ :type data: Optional[Mapping[str, int]]
261
+ :return:
262
+ Cleaned multiset containing only strictly positive entries.
263
+ :rtype: Dict[str, int]
264
+ :raises ValueError:
265
+ If a negative multiplicity is encountered.
266
+ """
267
+ if data is None:
268
+ return {}
269
+ out: Dict[str, int] = {}
270
+ for k, v in data.items():
271
+ n = int(v)
272
+ if n < 0:
273
+ raise ValueError(f"Negative multiplicity not allowed: {k!r} -> {n}")
274
+ if n > 0:
275
+ out[str(k)] = n
276
+ return out
277
+
278
+ def load_hypergraph_and_flow(
279
+ self,
280
+ vertices: Iterable[str],
281
+ edges: Mapping[str, Tuple[Mapping[str, int], Mapping[str, int]]],
282
+ flow: Mapping[str, int],
283
+ *,
284
+ initial_marking: Optional[Mapping[str, int]] = None,
285
+ ) -> "PathwayRealizability":
286
+ """Load tokenized hypergraph data and a requested pathway flow.
287
+
288
+ The ``edges`` mapping is interpreted as
289
+ ``reaction_id -> (tail_multiset, head_multiset)``. Reactions not
290
+ present in ``flow`` default to 0. Negative flow values are excluded
291
+ here and validated again later during Petri-net construction.
292
+
293
+ :param vertices:
294
+ Iterable of species tokens.
295
+ :type vertices: Iterable[str]
296
+ :param edges:
297
+ Mapping from reaction token to ``(tail, head)`` multisets.
298
+ :type edges: Mapping[str, Tuple[Mapping[str, int], Mapping[str, int]]]
299
+ :param flow:
300
+ Requested reaction firing counts.
301
+ :type flow: Mapping[str, int]
302
+ :param initial_marking:
303
+ Optional initial species marking.
304
+ :type initial_marking: Optional[Mapping[str, int]]
305
+ :return:
306
+ The current instance.
307
+ :rtype: PathwayRealizability
308
+
309
+ .. rubric:: Example
310
+
311
+ .. code-block:: python
312
+
313
+ pr = PathwayRealizability().load_hypergraph_and_flow(
314
+ vertices=["A", "B", "C"],
315
+ edges={
316
+ "r1": ({"A": 1}, {"B": 1}),
317
+ "r2": ({"B": 1}, {"C": 1}),
318
+ },
319
+ flow={"r1": 1, "r2": 1},
320
+ initial_marking={"A": 1},
321
+ )
322
+ """
323
+ self.vertices = {str(v) for v in vertices}
324
+ self.edges = {
325
+ str(eid): (
326
+ {str(s): int(v) for s, v in tail.items() if int(v) > 0},
327
+ {str(s): int(v) for s, v in head.items() if int(v) > 0},
328
+ )
329
+ for eid, (tail, head) in edges.items()
330
+ }
331
+ self.flow = {
332
+ str(eid): int(flow.get(eid, 0))
333
+ for eid in self.edges
334
+ if int(flow.get(eid, 0)) >= 0
335
+ }
336
+ self.species_token_to_id = {v: v for v in self.vertices}
337
+ self.reaction_token_to_id = {eid: eid for eid in self.edges}
338
+ self.species_id_to_token = {v: v for v in self.vertices}
339
+ self.reaction_id_to_token = {eid: eid for eid in self.edges}
340
+ self._provided_initial_marking = self._clean_multiset(initial_marking)
341
+ self._petri = None
342
+ self._initial_marking = None
343
+ self._goal_exact = {}
344
+ self._goal_atleast = {}
345
+ self._certificate = None
346
+ return self
347
+
348
+ def _resolve_flow_tokens(
349
+ self,
350
+ incidence_reaction_order: List[str],
351
+ reaction_token_to_id: Mapping[str, str],
352
+ reaction_labels: Mapping[str, str],
353
+ reaction_source_node_ids: Mapping[str, object],
354
+ provided: Mapping[str, int],
355
+ ) -> Dict[str, int]:
356
+ """Resolve user-supplied flow keys onto reaction tokens.
357
+
358
+ Keys may match reaction token, internal id, label, or source node id,
359
+ tried in that order; the first match wins. Missing reactions default
360
+ to 0. The order matters only when a network mixes naming conventions —
361
+ with the default prefixed id policy (``r_1``, ``s_1``) reaction ids
362
+ cannot collide with species ids.
363
+
364
+ :param incidence_reaction_order:
365
+ Canonical reaction-id order from the incidence view.
366
+ :type incidence_reaction_order: List[str]
367
+ :param reaction_token_to_id:
368
+ Mapping from reaction token to internal reaction id.
369
+ :type reaction_token_to_id: Mapping[str, str]
370
+ :param reaction_labels:
371
+ Mapping from reaction id to human-readable label.
372
+ :type reaction_labels: Mapping[str, str]
373
+ :param reaction_source_node_ids:
374
+ Mapping from reaction id to source node id.
375
+ :type reaction_source_node_ids: Mapping[str, object]
376
+ :param provided:
377
+ User-provided flow mapping.
378
+ :type provided: Mapping[str, int]
379
+ :return:
380
+ Flow map keyed by reaction token.
381
+ :rtype: Dict[str, int]
382
+ """
383
+ flow_map: Dict[str, int] = {}
384
+ id_to_token = {rid: tok for tok, rid in reaction_token_to_id.items()}
385
+ for rid in incidence_reaction_order:
386
+ rtoken = id_to_token[rid]
387
+ label = reaction_labels.get(rid, rid)
388
+ source = str(reaction_source_node_ids.get(rid, rid))
389
+ if rtoken in provided:
390
+ flow_map[rtoken] = int(provided[rtoken])
391
+ elif rid in provided:
392
+ flow_map[rtoken] = int(provided[rid])
393
+ elif label in provided:
394
+ flow_map[rtoken] = int(provided[label])
395
+ elif source in provided:
396
+ flow_map[rtoken] = int(provided[source])
397
+ else:
398
+ flow_map[rtoken] = 0
399
+ return flow_map
400
+
401
+ def _resolve_species_marking_tokens(
402
+ self,
403
+ incidence_species_order: List[str],
404
+ species_token_to_id: Mapping[str, str],
405
+ species_labels: Mapping[str, str],
406
+ species_source_node_ids: Mapping[str, object],
407
+ provided: Mapping[str, int],
408
+ ) -> Dict[str, int]:
409
+ """Resolve user-supplied initial marking keys onto species tokens.
410
+
411
+ Keys may match species token, internal id, label, or source node id.
412
+ Missing species default to 0 and are dropped from the returned mapping.
413
+
414
+ :param incidence_species_order:
415
+ Canonical species-id order from the incidence view.
416
+ :type incidence_species_order: List[str]
417
+ :param species_token_to_id:
418
+ Mapping from species token to internal species id.
419
+ :type species_token_to_id: Mapping[str, str]
420
+ :param species_labels:
421
+ Mapping from species id to human-readable label.
422
+ :type species_labels: Mapping[str, str]
423
+ :param species_source_node_ids:
424
+ Mapping from species id to source node id.
425
+ :type species_source_node_ids: Mapping[str, object]
426
+ :param provided:
427
+ User-provided initial marking.
428
+ :type provided: Mapping[str, int]
429
+ :return:
430
+ Initial marking keyed by species token.
431
+ :rtype: Dict[str, int]
432
+ """
433
+ marking_map: Dict[str, int] = {}
434
+ id_to_token = {sid: tok for tok, sid in species_token_to_id.items()}
435
+ for sid in incidence_species_order:
436
+ stok = id_to_token[sid]
437
+ label = species_labels.get(sid, sid)
438
+ source = str(species_source_node_ids.get(sid, sid))
439
+ if stok in provided:
440
+ marking_map[stok] = int(provided[stok])
441
+ elif sid in provided:
442
+ marking_map[stok] = int(provided[sid])
443
+ elif label in provided:
444
+ marking_map[stok] = int(provided[label])
445
+ elif source in provided:
446
+ marking_map[stok] = int(provided[source])
447
+ else:
448
+ marking_map[stok] = 0
449
+ return {k: v for k, v in marking_map.items() if int(v) > 0}
450
+
451
+ def load_syncrn_and_flow(
452
+ self,
453
+ crn: object,
454
+ flow: Optional[Mapping[str, int]] = None,
455
+ *,
456
+ initial_marking: Optional[Mapping[str, int]] = None,
457
+ species: str = "label",
458
+ reaction: str = "id",
459
+ ) -> "PathwayRealizability":
460
+ """Load a SynCRN-like object together with a requested pathway flow.
461
+
462
+ The SynCRN object is tokenized via :func:`tokenize_syncrn_incidence`.
463
+ User-supplied flow and marking keys are resolved flexibly against token,
464
+ internal id, label, and source node id.
465
+
466
+ :param crn:
467
+ SynCRN-like object.
468
+ :type crn: object
469
+ :param flow:
470
+ Requested reaction firing counts.
471
+ :type flow: Optional[Mapping[str, int]]
472
+ :param initial_marking:
473
+ Optional initial species marking.
474
+ :type initial_marking: Optional[Mapping[str, int]]
475
+ :param species:
476
+ Species tokenization mode.
477
+ :type species: str
478
+ :param reaction:
479
+ Reaction tokenization mode.
480
+ :type reaction: str
481
+ :return:
482
+ The current instance.
483
+ :rtype: PathwayRealizability
484
+
485
+ .. rubric:: Example
486
+
487
+ .. code-block:: python
488
+
489
+ pr = PathwayRealizability().load_syncrn_and_flow(
490
+ syn,
491
+ flow={"12": 1, "13": 1},
492
+ initial_marking={"A": 2},
493
+ species="label",
494
+ reaction="id",
495
+ )
496
+ """
497
+ vertices, edges, species_token_to_id, reaction_token_to_id, incidence = (
498
+ tokenize_syncrn_incidence(crn, species=species, reaction=reaction)
499
+ )
500
+
501
+ provided_flow = (
502
+ {} if flow is None else {str(k): int(v) for k, v in flow.items()}
503
+ )
504
+ flow_map = self._resolve_flow_tokens(
505
+ incidence.reaction_order,
506
+ reaction_token_to_id,
507
+ incidence.reaction_labels,
508
+ incidence.reaction_source_node_ids,
509
+ provided_flow,
510
+ )
511
+
512
+ provided_marking = (
513
+ {}
514
+ if initial_marking is None
515
+ else {str(k): int(v) for k, v in initial_marking.items()}
516
+ )
517
+ marking_map = self._resolve_species_marking_tokens(
518
+ incidence.species_order,
519
+ species_token_to_id,
520
+ incidence.species_labels,
521
+ incidence.species_source_node_ids,
522
+ provided_marking,
523
+ )
524
+
525
+ self.load_hypergraph_and_flow(
526
+ vertices,
527
+ edges,
528
+ flow_map,
529
+ initial_marking=marking_map,
530
+ )
531
+ self.species_token_to_id = species_token_to_id
532
+ self.reaction_token_to_id = reaction_token_to_id
533
+ self.species_id_to_token = {
534
+ sid: tok for tok, sid in species_token_to_id.items()
535
+ }
536
+ self.reaction_id_to_token = {
537
+ rid: tok for tok, rid in reaction_token_to_id.items()
538
+ }
539
+ return self
540
+
541
+ # ------------------------------------------------------------------
542
+ # Petri net construction
543
+ # ------------------------------------------------------------------
544
+
545
+ def set_initial_marking(self, marking: Mapping[str, int]) -> "PathwayRealizability":
546
+ """Replace the user-provided initial marking.
547
+
548
+ This resets any previously built Petri-net instance, goals, and cached
549
+ firing certificate.
550
+
551
+ :param marking:
552
+ New initial marking.
553
+ :type marking: Mapping[str, int]
554
+ :return:
555
+ The current instance.
556
+ :rtype: PathwayRealizability
557
+ """
558
+ self._provided_initial_marking = self._clean_multiset(marking)
559
+ self._petri = None
560
+ self._initial_marking = None
561
+ self._goal_exact = {}
562
+ self._goal_atleast = {}
563
+ self._certificate = None
564
+ return self
565
+
566
+ def build_petri_net_from_flow(self) -> "PathwayRealizability":
567
+ """Build an augmented Petri net encoding the requested pathway flow.
568
+
569
+ For every active reaction ``eid`` with requested flow ``f > 0``, two
570
+ auxiliary places are created:
571
+
572
+ - ``__ext__{eid}``: supply place initialized with ``f`` tokens,
573
+ - ``__target__{eid}``: target place expected to accumulate ``f`` tokens.
574
+
575
+ The reaction transition consumes one token from the supply place on each
576
+ firing and produces one token in the target place on each firing. This
577
+ enforces exact realization of the requested number of firings.
578
+
579
+ :return:
580
+ The current instance.
581
+ :rtype: PathwayRealizability
582
+ :raises RuntimeError:
583
+ If no pathway data has been loaded.
584
+ :raises ValueError:
585
+ If a negative flow value is encountered.
586
+
587
+ .. rubric:: Example
588
+
589
+ .. code-block:: python
590
+
591
+ pr = PathwayRealizability().load_syncrn_and_flow(
592
+ syn,
593
+ flow={"12": 1, "13": 1},
594
+ )
595
+ pr.build_petri_net_from_flow()
596
+ print(pr.initial_marking)
597
+ print(pr.goal_exact)
598
+ """
599
+ if not self.edges:
600
+ raise RuntimeError(
601
+ "No pathway data loaded; call load_syncrn_and_flow() or load_hypergraph_and_flow()."
602
+ )
603
+
604
+ net = PetriNet()
605
+ m0: Dict[Place, int] = defaultdict(int)
606
+ goal_exact: Dict[Place, int] = {}
607
+
608
+ for v in sorted(self.vertices):
609
+ net.add_place(v, label=v, source_node_id=v)
610
+ m0[v] = int(self._provided_initial_marking.get(v, 0))
611
+
612
+ for eid in sorted(self.edges):
613
+ fval = int(self.flow.get(eid, 0))
614
+ if fval < 0:
615
+ raise ValueError(f"Flow must be non-negative, got {eid!r} -> {fval}")
616
+ if fval == 0:
617
+ continue
618
+
619
+ tail, head = self.edges[eid]
620
+ pre = {v: int(w) for v, w in tail.items() if int(w) > 0}
621
+ post = {v: int(w) for v, w in head.items() if int(w) > 0}
622
+
623
+ ext = f"__ext__{eid}"
624
+ tgt = f"__target__{eid}"
625
+ net.add_place(ext, label=ext)
626
+ net.add_place(tgt, label=tgt)
627
+
628
+ pre_with_supply = dict(pre)
629
+ pre_with_supply[ext] = 1
630
+ post_with_target = dict(post)
631
+ post_with_target[tgt] = 1
632
+
633
+ net.add_transition(
634
+ eid,
635
+ pre_with_supply,
636
+ post_with_target,
637
+ label=eid,
638
+ source_reaction_id=self.reaction_token_to_id.get(eid, eid),
639
+ )
640
+
641
+ m0[ext] = fval
642
+ goal_exact[ext] = 0
643
+ goal_exact[tgt] = fval
644
+ m0[tgt] = 0
645
+
646
+ self._petri = net
647
+ self._initial_marking = dict(m0)
648
+ self._goal_exact = goal_exact
649
+ self._goal_atleast = {}
650
+ self._certificate = None
651
+ return self
652
+
653
+ # ------------------------------------------------------------------
654
+ # properties
655
+ # ------------------------------------------------------------------
656
+
657
+ @property
658
+ def petri(self) -> PetriNet:
659
+ """Return the active Petri net.
660
+
661
+ :return:
662
+ Augmented Petri net for realizability checking.
663
+ :rtype: PetriNet
664
+ :raises RuntimeError:
665
+ If the Petri net has not yet been built.
666
+ """
667
+ if self._petri is None:
668
+ raise RuntimeError("Petri net not built; call build_petri_net_from_flow().")
669
+ return self._petri
670
+
671
+ @property
672
+ def initial_marking(self) -> Dict[Place, int]:
673
+ """Return the active initial marking of the augmented Petri net.
674
+
675
+ :return:
676
+ Initial marking.
677
+ :rtype: Dict[Place, int]
678
+ :raises RuntimeError:
679
+ If the initial marking has not yet been initialized.
680
+ """
681
+ if self._initial_marking is None:
682
+ raise RuntimeError("Initial marking missing; build Petri net first.")
683
+ return self._initial_marking
684
+
685
+ @property
686
+ def goal_exact(self) -> Dict[Place, int]:
687
+ """Return exact target marking constraints.
688
+
689
+ :return:
690
+ Exact goal marking constraints.
691
+ :rtype: Dict[Place, int]
692
+ """
693
+ return dict(self._goal_exact)
694
+
695
+ @property
696
+ def goal_atleast(self) -> Dict[Place, int]:
697
+ """Return lower-bound target marking constraints.
698
+
699
+ :return:
700
+ Lower-bound goal marking constraints.
701
+ :rtype: Dict[Place, int]
702
+ """
703
+ return dict(self._goal_atleast)
704
+
705
+ @property
706
+ def certificate(self) -> Optional[List[TransitionId]]:
707
+ """Return the most recently found firing certificate.
708
+
709
+ :return:
710
+ Realizing firing sequence, or ``None`` if none is cached.
711
+ :rtype: Optional[List[TransitionId]]
712
+ """
713
+ return self._certificate
714
+
715
+ @property
716
+ def last_result(self) -> Optional[RealizabilityResult]:
717
+ """Return the most recent detailed realizability result.
718
+
719
+ :return:
720
+ Detailed result, or ``None`` before a search has run.
721
+ :rtype: Optional[RealizabilityResult]
722
+ """
723
+ return self._last_result
724
+
725
+ def summary(self) -> RealizabilitySummary:
726
+ """Return a compact summary of the current realizability instance.
727
+
728
+ :return:
729
+ Serializable instance summary.
730
+ :rtype: RealizabilitySummary
731
+
732
+ .. rubric:: Example
733
+
734
+ .. code-block:: python
735
+
736
+ info = pr.summary()
737
+ print(info.active_flow)
738
+ """
739
+ return RealizabilitySummary(
740
+ n_species=len(self.vertices),
741
+ n_reactions=len(self.edges),
742
+ active_flow={k: v for k, v in self.flow.items() if int(v) > 0},
743
+ initial_marking=dict(self._provided_initial_marking),
744
+ goal_exact=dict(self._goal_exact),
745
+ goal_atleast=dict(self._goal_atleast),
746
+ )
747
+
748
+ # ------------------------------------------------------------------
749
+ # goal helpers
750
+ # ------------------------------------------------------------------
751
+
752
+ def _goal_reached(self, marking: Mapping[str, int]) -> bool:
753
+ """Test whether a marking satisfies the active goal constraints.
754
+
755
+ Exact constraints in ``_goal_exact`` must match exactly, while lower
756
+ bounds in ``_goal_atleast`` must be met or exceeded.
757
+
758
+ :param marking:
759
+ Candidate marking.
760
+ :type marking: Mapping[str, int]
761
+ :return:
762
+ ``True`` if the marking satisfies all active goals.
763
+ :rtype: bool
764
+ """
765
+ for p, val in self._goal_exact.items():
766
+ if int(marking.get(p, 0)) != int(val):
767
+ return False
768
+ for p, val in self._goal_atleast.items():
769
+ if int(marking.get(p, 0)) < int(val):
770
+ return False
771
+ return True
772
+
773
+ # ------------------------------------------------------------------
774
+ # bounded BFS
775
+ # ------------------------------------------------------------------
776
+
777
+ def realizability_result(
778
+ self,
779
+ max_states: Optional[int] = None,
780
+ max_depth: Optional[int] = None,
781
+ *,
782
+ backend="auto",
783
+ library_path=None,
784
+ time_limit=30.0,
785
+ ) -> RealizabilityResult:
786
+ """Return a typed result from bounded breadth-first search.
787
+
788
+ The search explores reachable Petri-net markings while recording firing
789
+ sequences. A found sequence is a replayable certificate. A negative is
790
+ returned only after exhaustive search; reaching a budget returns an
791
+ explicit ``INCONCLUSIVE`` outcome.
792
+
793
+ :param max_states:
794
+ Optional override for the maximum number of explored states.
795
+ :type max_states: Optional[int]
796
+ :param max_depth:
797
+ Optional override for the maximum explored firing depth.
798
+ :type max_depth: Optional[int]
799
+ :return:
800
+ Typed, auditable search result.
801
+ :rtype: RealizabilityResult
802
+
803
+ :raises ValueError:
804
+ If a search budget is outside its valid range.
805
+
806
+ .. rubric:: Example
807
+
808
+ .. code-block:: python
809
+
810
+ result = pr.realizability_result(max_states=50000, max_depth=2000)
811
+ print(result.status)
812
+ print(result.certificate)
813
+ """
814
+ from ..backend import resolve_backend
815
+
816
+ backend, library_path = resolve_backend(backend, library_path)
817
+ if backend == "cpp":
818
+ from ..native_analysis import petri_reachability
819
+
820
+ payload = petri_reachability(
821
+ self.petri,
822
+ self.initial_marking,
823
+ self.goal_exact,
824
+ self.goal_atleast,
825
+ library_path=library_path,
826
+ max_states=(
827
+ self._config.max_states if max_states is None else max_states
828
+ ),
829
+ max_depth=self._config.max_depth if max_depth is None else max_depth,
830
+ time_limit=time_limit,
831
+ )
832
+ result = RealizabilityResult(
833
+ status=RealizabilityStatus(payload["status"]),
834
+ **{k: v for k, v in payload.items() if k != "status"},
835
+ )
836
+ self._last_result = result
837
+ self._certificate = result.certificate
838
+ return result
839
+ net = self.petri
840
+ m0 = dict(self.initial_marking)
841
+
842
+ max_states = max_states if max_states is not None else self._config.max_states
843
+ max_depth = max_depth if max_depth is not None else self._config.max_depth
844
+ if max_states < 1:
845
+ raise ValueError("max_states must be at least 1")
846
+ if max_depth < 0:
847
+ raise ValueError("max_depth must be non-negative")
848
+
849
+ if self._goal_reached(m0):
850
+ self._certificate = []
851
+ result = RealizabilityResult(
852
+ status=RealizabilityStatus.REALIZABLE,
853
+ certificate=[],
854
+ explored_states=0,
855
+ max_depth_reached=0,
856
+ exhaustive=False,
857
+ termination_reason="certificate",
858
+ )
859
+ self._last_result = result
860
+ return result
861
+
862
+ start = net.marking_to_tuple(m0)
863
+ q: deque[Tuple[Tuple[int, ...], List[TransitionId]]] = deque([(start, [])])
864
+ visited = {start}
865
+ states = 0
866
+ deepest = 0
867
+ depth_limited = False
868
+
869
+ while q:
870
+ if states >= max_states:
871
+ result = RealizabilityResult(
872
+ status=RealizabilityStatus.INCONCLUSIVE,
873
+ certificate=None,
874
+ explored_states=states,
875
+ max_depth_reached=deepest,
876
+ exhaustive=False,
877
+ termination_reason="state_limit",
878
+ )
879
+ self._certificate = None
880
+ self._last_result = result
881
+ return result
882
+
883
+ mtuple, seq = q.popleft()
884
+ states += 1
885
+ deepest = max(deepest, len(seq))
886
+
887
+ marking = net.tuple_to_marking(mtuple)
888
+ enabled = [tid for tid in net.transition_order if net.enabled(marking, tid)]
889
+ if len(seq) >= max_depth:
890
+ if enabled:
891
+ depth_limited = True
892
+ continue
893
+
894
+ for tid in enabled:
895
+ new_mark = net.fire(marking, tid)
896
+ if self._goal_reached(new_mark):
897
+ seq2 = seq + [tid]
898
+ self._certificate = seq2
899
+ result = RealizabilityResult(
900
+ status=RealizabilityStatus.REALIZABLE,
901
+ certificate=seq2,
902
+ explored_states=states,
903
+ max_depth_reached=max(deepest, len(seq2)),
904
+ exhaustive=False,
905
+ termination_reason="certificate",
906
+ )
907
+ self._last_result = result
908
+ return result
909
+ new_tuple = net.marking_to_tuple(new_mark)
910
+ if new_tuple not in visited:
911
+ visited.add(new_tuple)
912
+ q.append((new_tuple, seq + [tid]))
913
+
914
+ self._certificate = None
915
+ if depth_limited:
916
+ status = RealizabilityStatus.INCONCLUSIVE
917
+ reason = "depth_limit"
918
+ exhaustive = False
919
+ else:
920
+ status = RealizabilityStatus.UNREALIZABLE
921
+ reason = "exhausted"
922
+ exhaustive = True
923
+ result = RealizabilityResult(
924
+ status=status,
925
+ certificate=None,
926
+ explored_states=states,
927
+ max_depth_reached=deepest,
928
+ exhaustive=exhaustive,
929
+ termination_reason=reason,
930
+ )
931
+ self._last_result = result
932
+ return result
933
+
934
+ def is_realizable(
935
+ self,
936
+ max_states: Optional[int] = None,
937
+ max_depth: Optional[int] = None,
938
+ ) -> Tuple[bool, Optional[List[TransitionId]]]:
939
+ """Return the legacy Boolean/certificate pair without silent cutoffs.
940
+
941
+ This compatibility method delegates to :meth:`realizability_result`.
942
+ It raises when the detailed result is inconclusive, preventing a state
943
+ or depth budget from being reported as non-realizability.
944
+
945
+ :param max_states:
946
+ Optional override for the maximum number of explored states.
947
+ :type max_states: Optional[int]
948
+ :param max_depth:
949
+ Optional override for the maximum explored firing depth.
950
+ :type max_depth: Optional[int]
951
+ :return:
952
+ Pair ``(is_realizable, certificate)`` for conclusive outcomes.
953
+ :rtype: Tuple[bool, Optional[List[TransitionId]]]
954
+ :raises RealizabilitySearchLimit:
955
+ If the bounded search is inconclusive.
956
+ """
957
+ result = self.realizability_result(
958
+ max_states=max_states,
959
+ max_depth=max_depth,
960
+ )
961
+ if result.status is RealizabilityStatus.INCONCLUSIVE:
962
+ raise RealizabilitySearchLimit(result)
963
+ return result.status is RealizabilityStatus.REALIZABLE, result.certificate
964
+
965
+ # ------------------------------------------------------------------
966
+ # König sufficient test
967
+ # ------------------------------------------------------------------
968
+
969
+ def is_realizable_via_konig(self) -> bool:
970
+ """Apply a König-style sufficient acyclicity test.
971
+
972
+ A directed bipartite dependency graph is built using:
973
+
974
+ - species vertices ``("v", species)``
975
+ - active-reaction vertices ``("e", reaction)``
976
+
977
+ with edges:
978
+
979
+ - species -> reaction for reactant incidence,
980
+ - reaction -> species for product incidence.
981
+
982
+ If this dependency graph is acyclic, the active flow is certified
983
+ realizable by this sufficient test.
984
+
985
+ :return:
986
+ ``True`` if the sufficient acyclicity condition holds, else
987
+ ``False``.
988
+ :rtype: bool
989
+
990
+ .. rubric:: Example
991
+
992
+ .. code-block:: python
993
+
994
+ if pr.is_realizable_via_konig():
995
+ print("Guaranteed realizable by sufficient test")
996
+ """
997
+ active_edges = {
998
+ eid: self.edges[eid] for eid, fval in self.flow.items() if int(fval) > 0
999
+ }
1000
+ vertices: Set[str] = set()
1001
+ for tail, head in active_edges.values():
1002
+ vertices.update(tail.keys())
1003
+ vertices.update(head.keys())
1004
+
1005
+ g = nx.DiGraph()
1006
+ for v in vertices:
1007
+ g.add_node(("v", v))
1008
+ for eid in active_edges:
1009
+ g.add_node(("e", eid))
1010
+ for eid, (tail, head) in active_edges.items():
1011
+ for v in tail:
1012
+ g.add_edge(("v", v), ("e", eid))
1013
+ for v in head:
1014
+ g.add_edge(("e", eid), ("v", v))
1015
+ return nx.is_directed_acyclic_graph(g)
1016
+
1017
+ # ------------------------------------------------------------------
1018
+ # scaled / borrow realizability
1019
+ # ------------------------------------------------------------------
1020
+
1021
+ def is_scaled_realizable(self, k_max: int = 4) -> Tuple[bool, Optional[int]]:
1022
+ """Test whether a scaled version of the requested flow is realizable.
1023
+
1024
+ For each integer ``k`` from 1 to ``k_max``, the requested flow is scaled
1025
+ to ``k * flow`` and tested for exact realizability.
1026
+
1027
+ :param k_max:
1028
+ Maximum scale factor to try.
1029
+ :type k_max: int
1030
+ :return:
1031
+ Pair ``(success, k)`` where ``k`` is the first successful scale
1032
+ factor, or ``None`` if no tested scale succeeds.
1033
+ :rtype: Tuple[bool, Optional[int]]
1034
+
1035
+ .. rubric:: Example
1036
+
1037
+ .. code-block:: python
1038
+
1039
+ ok, k = pr.is_scaled_realizable(k_max=5)
1040
+ print(ok, k)
1041
+ """
1042
+ saved_flow = dict(self.flow)
1043
+ for k in range(1, k_max + 1):
1044
+ self.flow = {eid: k * int(v) for eid, v in saved_flow.items()}
1045
+ self.build_petri_net_from_flow()
1046
+ ok, _ = self.is_realizable()
1047
+ if ok:
1048
+ self.flow = saved_flow
1049
+ self.build_petri_net_from_flow()
1050
+ return True, k
1051
+ self.flow = saved_flow
1052
+ self.build_petri_net_from_flow()
1053
+ return False, None
1054
+
1055
+ def is_borrow_realizable(
1056
+ self,
1057
+ max_borrow_each: int = 2,
1058
+ ) -> Tuple[bool, Optional[Mapping[str, int]]]:
1059
+ """Test realizability when temporary borrowed initial tokens are allowed.
1060
+
1061
+ Every combination of per-species borrowed tokens from 0 up to
1062
+ ``max_borrow_each`` is tried. For a candidate borrow multiset, the
1063
+ borrowed tokens are added to the initial marking and then imposed again
1064
+ as lower-bound final goals in ``goal_atleast`` so that the borrowed
1065
+ material must be returned.
1066
+
1067
+ :param max_borrow_each:
1068
+ Maximum borrowed amount tested independently for each species.
1069
+ :type max_borrow_each: int
1070
+ :return:
1071
+ Pair ``(success, borrow)`` where ``borrow`` is the first successful
1072
+ borrow multiset, or ``None`` if no tested borrow succeeds.
1073
+ :rtype: Tuple[bool, Optional[Mapping[str, int]]]
1074
+
1075
+ .. rubric:: Example
1076
+
1077
+ .. code-block:: python
1078
+
1079
+ ok, borrow = pr.is_borrow_realizable(max_borrow_each=1)
1080
+ print(ok, borrow)
1081
+ """
1082
+ saved_initial = dict(self._provided_initial_marking)
1083
+ species = sorted(self.vertices)
1084
+
1085
+ for comb in itertools.product(range(max_borrow_each + 1), repeat=len(species)):
1086
+ borrow = {s: comb[i] for i, s in enumerate(species) if comb[i] > 0}
1087
+ self._provided_initial_marking = dict(saved_initial)
1088
+ for s, val in borrow.items():
1089
+ self._provided_initial_marking[s] = (
1090
+ self._provided_initial_marking.get(s, 0) + val
1091
+ )
1092
+ self.build_petri_net_from_flow()
1093
+ self._goal_atleast = dict(borrow)
1094
+ ok, _ = self.is_realizable()
1095
+ if ok:
1096
+ self._provided_initial_marking = saved_initial
1097
+ self.build_petri_net_from_flow()
1098
+ return True, borrow
1099
+
1100
+ self._provided_initial_marking = saved_initial
1101
+ self.build_petri_net_from_flow()
1102
+ return False, None
1103
+
1104
+ # ------------------------------------------------------------------
1105
+ # export
1106
+ # ------------------------------------------------------------------
1107
+
1108
+ def export_pnml(self, fn: str) -> "PathwayRealizability":
1109
+ """Export the current augmented Petri net to a JSON-based PNML-like file.
1110
+
1111
+ The exported structure contains places, transitions, initial marking,
1112
+ and active goal constraints. Despite the method name, the file written
1113
+ here is JSON rather than XML PNML.
1114
+
1115
+ :param fn:
1116
+ Output filename.
1117
+ :type fn: str
1118
+ :return:
1119
+ The current instance.
1120
+ :rtype: PathwayRealizability
1121
+
1122
+ .. rubric:: Example
1123
+
1124
+ .. code-block:: python
1125
+
1126
+ pr.export_pnml("pathway_realizability.json")
1127
+ """
1128
+ self.build_petri_net_from_flow()
1129
+ net = self.petri
1130
+ data = {
1131
+ "places": net.place_order,
1132
+ "transitions": {
1133
+ tid: {"pre": t.pre, "post": t.post}
1134
+ for tid, t in net.transitions.items()
1135
+ },
1136
+ "initial_marking": self.initial_marking,
1137
+ "goal_exact": self._goal_exact,
1138
+ "goal_atleast": self._goal_atleast,
1139
+ }
1140
+ with open(fn, "w", encoding="utf-8") as fh:
1141
+ json.dump(data, fh, indent=2)
1142
+ return self
1143
+
1144
+ def __repr__(self) -> str: # pragma: no cover - simple repr
1145
+ """Return a compact representation.
1146
+
1147
+ :return:
1148
+ Readable summary string.
1149
+ :rtype: str
1150
+ """
1151
+ return (
1152
+ f"<PathwayRealizability species={len(self.vertices)} "
1153
+ f"reactions={len(self.edges)}>"
1154
+ )
1155
+
1156
+
1157
+ def syncrn_to_pr_inputs(
1158
+ crn: object,
1159
+ flow: Optional[Mapping[str, int]] = None,
1160
+ *,
1161
+ initial_marking: Optional[Mapping[str, int]] = None,
1162
+ species: str = "label",
1163
+ reaction: str = "id",
1164
+ ) -> Tuple[
1165
+ List[str],
1166
+ Dict[str, Tuple[Dict[str, int], Dict[str, int]]],
1167
+ Dict[str, int],
1168
+ Dict[str, int],
1169
+ ]:
1170
+ """Convert a SynCRN-like object into tokenized pathway-realizability inputs.
1171
+
1172
+ The result contains species tokens, reaction-tokenized hyperedges, a flow
1173
+ map keyed by reaction token, and an initial marking keyed by species token.
1174
+
1175
+ :param crn: SynCRN-like object.
1176
+ :type crn: object
1177
+ :param flow: Optional requested reaction firing counts.
1178
+ :type flow: Optional[Mapping[str, int]]
1179
+ :param initial_marking: Optional initial marking.
1180
+ :type initial_marking: Optional[Mapping[str, int]]
1181
+ :param species: Species tokenization mode.
1182
+ :type species: str
1183
+ :param reaction: Reaction tokenization mode.
1184
+ :type reaction: str
1185
+ :return: Tuple ``(vertices, edges, flow_map, marking_map)``.
1186
+ :rtype: Tuple[List[str], Dict[str, Tuple[Dict[str, int], Dict[str, int]]],
1187
+ Dict[str, int], Dict[str, int]]
1188
+
1189
+ .. rubric:: Example
1190
+
1191
+ .. code-block:: python
1192
+
1193
+ vertices, edges, flow_map, marking_map = syncrn_to_pr_inputs(
1194
+ syn,
1195
+ flow={"12": 1},
1196
+ initial_marking={"A": 2},
1197
+ species="label",
1198
+ reaction="id",
1199
+ )
1200
+ """
1201
+ vertices, edges, species_token_to_id, reaction_token_to_id, incidence = (
1202
+ tokenize_syncrn_incidence(
1203
+ crn,
1204
+ species=species,
1205
+ reaction=reaction,
1206
+ )
1207
+ )
1208
+
1209
+ dummy = PathwayRealizability()
1210
+ provided_flow = {} if flow is None else {str(k): int(v) for k, v in flow.items()}
1211
+ flow_map = dummy._resolve_flow_tokens(
1212
+ incidence.reaction_order,
1213
+ reaction_token_to_id,
1214
+ incidence.reaction_labels,
1215
+ incidence.reaction_source_node_ids,
1216
+ provided_flow,
1217
+ )
1218
+ provided_marking = (
1219
+ {}
1220
+ if initial_marking is None
1221
+ else {str(k): int(v) for k, v in initial_marking.items()}
1222
+ )
1223
+ marking_map = dummy._resolve_species_marking_tokens(
1224
+ incidence.species_order,
1225
+ species_token_to_id,
1226
+ incidence.species_labels,
1227
+ incidence.species_source_node_ids,
1228
+ provided_marking,
1229
+ )
1230
+ return vertices, edges, flow_map, marking_map
1231
+
1232
+
1233
+ def run_realizability_from_syncrn(
1234
+ crn: object,
1235
+ flow: Optional[Mapping[str, int]] = None,
1236
+ *,
1237
+ initial_marking: Optional[Mapping[str, int]] = None,
1238
+ species: str = "label",
1239
+ reaction: str = "id",
1240
+ verbose: bool = True,
1241
+ ) -> Tuple[PathwayRealizability, Dict[str, object]]:
1242
+ """Run König-style and exact BFS realizability tests on a SynCRN-like object.
1243
+
1244
+ This convenience wrapper:
1245
+
1246
+ 1. loads the SynCRN object and requested flow,
1247
+ 2. builds the augmented Petri net,
1248
+ 3. applies the sufficient König-style acyclicity test,
1249
+ 4. runs exact bounded BFS realizability search,
1250
+ 5. optionally prints a human-readable summary.
1251
+
1252
+ :param crn:
1253
+ SynCRN-like object.
1254
+ :type crn: object
1255
+ :param flow:
1256
+ Optional requested reaction firing counts.
1257
+ :type flow: Optional[Mapping[str, int]]
1258
+ :param initial_marking:
1259
+ Optional initial marking.
1260
+ :type initial_marking: Optional[Mapping[str, int]]
1261
+ :param species:
1262
+ Species tokenization mode.
1263
+ :type species: str
1264
+ :param reaction:
1265
+ Reaction tokenization mode.
1266
+ :type reaction: str
1267
+ :param verbose:
1268
+ Whether to print a textual summary.
1269
+ :type verbose: bool
1270
+ :return:
1271
+ Pair ``(pathway_realizability_instance, info_dict)``.
1272
+ :rtype: Tuple[PathwayRealizability, Dict[str, object]]
1273
+
1274
+ .. rubric:: Example
1275
+
1276
+ .. code-block:: python
1277
+
1278
+ pr, info = run_realizability_from_syncrn(
1279
+ syn,
1280
+ flow={"12": 1, "13": 1},
1281
+ initial_marking={"A": 2},
1282
+ verbose=True,
1283
+ )
1284
+ print(info["konig"])
1285
+ print(info["bfs"])
1286
+ print(info["certificate"])
1287
+ """
1288
+ pr = PathwayRealizability().load_syncrn_and_flow(
1289
+ crn,
1290
+ flow=flow,
1291
+ initial_marking=initial_marking,
1292
+ species=species,
1293
+ reaction=reaction,
1294
+ )
1295
+ pr.build_petri_net_from_flow()
1296
+
1297
+ konig_ok = pr.is_realizable_via_konig()
1298
+ bfs_result = pr.realizability_result()
1299
+ bfs_ok = bfs_result.realizable
1300
+ cert = bfs_result.certificate
1301
+
1302
+ if verbose:
1303
+ print("Active flow:")
1304
+ for eid, val in sorted(pr.flow.items()):
1305
+ if int(val) > 0:
1306
+ print(f" {eid}: {val}")
1307
+ print("Initial marking:", dict(sorted(pr._provided_initial_marking.items())))
1308
+ print("König sufficient test:", konig_ok)
1309
+ print("BFS status:", bfs_result.status.value)
1310
+ print("Firing certificate:", cert)
1311
+
1312
+ info: Dict[str, object] = {
1313
+ "konig": konig_ok,
1314
+ "bfs": bfs_ok,
1315
+ "bfs_result": bfs_result,
1316
+ "bfs_status": bfs_result.status.value,
1317
+ "bfs_exhaustive": bfs_result.exhaustive,
1318
+ "bfs_explored_states": bfs_result.explored_states,
1319
+ "certificate": cert,
1320
+ "summary": pr.summary(),
1321
+ }
1322
+ return pr, info