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.
- synkit/CRN/Benchmark/__init__.py +53 -0
- synkit/CRN/Benchmark/crosschecks.py +13 -0
- synkit/CRN/Benchmark/kegg.py +12 -0
- synkit/CRN/Benchmark/networks.py +13 -0
- synkit/CRN/Benchmark/scaling.py +13 -0
- synkit/CRN/Benchmark/validate.py +13 -0
- synkit/CRN/Construct/__init__.py +16 -0
- synkit/CRN/Construct/abstract.py +668 -0
- synkit/CRN/Construct/arity.py +36 -0
- synkit/CRN/Construct/builder.py +619 -0
- synkit/CRN/Construct/derivation.py +63 -0
- synkit/CRN/Construct/flattener.py +100 -0
- synkit/CRN/Construct/keys.py +14 -0
- synkit/CRN/Construct/mixtures.py +117 -0
- synkit/CRN/Construct/rdkit_worker.py +69 -0
- synkit/CRN/Construct/smiles.py +99 -0
- synkit/CRN/Construct/state.py +31 -0
- synkit/CRN/Construct/strategy.py +75 -0
- synkit/CRN/Construct/worker.py +42 -0
- synkit/CRN/IO/__init__.py +36 -0
- synkit/CRN/IO/sbml.py +925 -0
- synkit/CRN/Pathway/__init__.py +72 -0
- synkit/CRN/Pathway/_adapter.py +333 -0
- synkit/CRN/Pathway/pathfinder.py +500 -0
- synkit/CRN/Pathway/reachability.py +796 -0
- synkit/CRN/Pathway/realizability.py +1322 -0
- synkit/CRN/Petrinet/__init__.py +93 -0
- synkit/CRN/Petrinet/analyzer.py +486 -0
- synkit/CRN/Petrinet/minimal_semiflows.py +436 -0
- synkit/CRN/Petrinet/net.py +1041 -0
- synkit/CRN/Petrinet/persistence.py +97 -0
- synkit/CRN/Petrinet/semiflows.py +483 -0
- synkit/CRN/Petrinet/siphon_search.py +402 -0
- synkit/CRN/Petrinet/structure.py +286 -0
- synkit/CRN/Props/__init__.py +130 -0
- synkit/CRN/Props/_rank.py +29 -0
- synkit/CRN/Props/deficiency.py +953 -0
- synkit/CRN/Props/dynamics.py +832 -0
- synkit/CRN/Props/helper.py +151 -0
- synkit/CRN/Props/stoich.py +751 -0
- synkit/CRN/Props/thermo.py +584 -0
- synkit/CRN/Query/__init__.py +66 -0
- synkit/CRN/Query/cache.py +41 -0
- synkit/CRN/Query/kegg_api.py +99 -0
- synkit/CRN/Query/kegg_extract.py +714 -0
- synkit/CRN/Query/kegg_impute.py +707 -0
- synkit/CRN/Query/kegg_parse.py +480 -0
- synkit/CRN/Query/to_syncrn.py +290 -0
- synkit/CRN/README.md +234 -0
- synkit/CRN/Structure/__init__.py +12 -0
- synkit/CRN/Structure/_graph_io.py +713 -0
- synkit/CRN/Structure/_matrices.py +88 -0
- synkit/CRN/Structure/_parse.py +544 -0
- synkit/CRN/Structure/reaction.py +237 -0
- synkit/CRN/Structure/rule.py +55 -0
- synkit/CRN/Structure/species.py +46 -0
- synkit/CRN/Structure/syncrn.py +929 -0
- synkit/CRN/Symmetry/__init__.py +20 -0
- synkit/CRN/Symmetry/_common.py +1059 -0
- synkit/CRN/Symmetry/_ir.py +940 -0
- synkit/CRN/Symmetry/automorphism.py +472 -0
- synkit/CRN/Symmetry/canon.py +441 -0
- synkit/CRN/Symmetry/isomorphism.py +412 -0
- synkit/CRN/Symmetry/symmetry.py +61 -0
- synkit/CRN/Symmetry/wl_canon.py +967 -0
- synkit/CRN/Visualize/__init__.py +11 -0
- synkit/CRN/Visualize/labels.py +113 -0
- synkit/CRN/Visualize/layout.py +708 -0
- synkit/CRN/Visualize/palette.py +292 -0
- synkit/CRN/Visualize/validation.py +122 -0
- synkit/CRN/Visualize/vis.py +1152 -0
- synkit/CRN/__init__.py +233 -0
- synkit/CRN/_build.py +50 -0
- synkit/CRN/_native_build.py +175 -0
- synkit/CRN/analysis.py +629 -0
- synkit/CRN/backend.py +74 -0
- synkit/CRN/cpp/CMakeLists.txt +58 -0
- synkit/CRN/cpp/algebra.cpp +46 -0
- synkit/CRN/cpp/closure.cpp +29 -0
- synkit/CRN/cpp/exact.h +55 -0
- synkit/CRN/cpp/pathway.cpp +46 -0
- synkit/CRN/cpp/semiflows.cpp +209 -0
- synkit/CRN/cpp/structural.cpp +240 -0
- synkit/CRN/cpp/symmetry.cpp +45 -0
- synkit/CRN/cpp/write_manifest.cmake +28 -0
- synkit/CRN/kinds.py +137 -0
- synkit/CRN/lib/synkit_crn.dll +0 -0
- synkit/CRN/lib/synkit_crn.json +33 -0
- synkit/CRN/native.py +388 -0
- synkit/CRN/native_analysis.py +459 -0
- synkit/Chem/Cluster/__init__.py +0 -0
- synkit/Chem/Cluster/butina.py +140 -0
- synkit/Chem/Fingerprint/__init__.py +0 -0
- synkit/Chem/Fingerprint/fp_calculator.py +155 -0
- synkit/Chem/Fingerprint/smiles_featurizer.py +258 -0
- synkit/Chem/Fingerprint/transformation_fp.py +139 -0
- synkit/Chem/Mapper/README.rst +43 -0
- synkit/Chem/Mapper/__init__.py +255 -0
- synkit/Chem/Mapper/_compat.py +130 -0
- synkit/Chem/Mapper/adapters/__init__.py +1 -0
- synkit/Chem/Mapper/adapters/prediction.py +126 -0
- synkit/Chem/Mapper/adapters/template.py +412 -0
- synkit/Chem/Mapper/chem/__init__.py +52 -0
- synkit/Chem/Mapper/chem/aam.py +673 -0
- synkit/Chem/Mapper/chem/blind.py +160 -0
- synkit/Chem/Mapper/chem/its.py +309 -0
- synkit/Chem/Mapper/chem/smiles.py +564 -0
- synkit/Chem/Mapper/graph/__init__.py +66 -0
- synkit/Chem/Mapper/graph/automorphism.py +395 -0
- synkit/Chem/Mapper/graph/blockcut.py +98 -0
- synkit/Chem/Mapper/graph/labeled_graph.py +104 -0
- synkit/Chem/Mapper/graph/refine.py +176 -0
- synkit/Chem/Mapper/graph/synkit_adapter.py +110 -0
- synkit/Chem/Mapper/io/__init__.py +13 -0
- synkit/Chem/Mapper/io/idxmapstr.py +82 -0
- synkit/Chem/Mapper/mapper.py +685 -0
- synkit/Chem/Mapper/reaction_search.py +554 -0
- synkit/Chem/Mapper/reporting/__init__.py +1 -0
- synkit/Chem/Mapper/reporting/alternatives.py +451 -0
- synkit/Chem/Mapper/reporting/analysis.py +681 -0
- synkit/Chem/Mapper/reporting/native_analysis.py +485 -0
- synkit/Chem/Mapper/reporting/spectrum.py +642 -0
- synkit/Chem/Mapper/solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/__init__.py +141 -0
- synkit/Chem/Mapper/solvers/exact/assignment/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_certificate.py +113 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_domains.py +162 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_verification.py +58 -0
- synkit/Chem/Mapper/solvers/exact/assignment/incremental_assignment.py +247 -0
- synkit/Chem/Mapper/solvers/exact/certificate.py +128 -0
- synkit/Chem/Mapper/solvers/exact/core.py +183 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/bounds.py +319 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/exhaustive.py +179 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/inputs.py +283 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/records.py +242 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/search.py +1025 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/verify.py +378 -0
- synkit/Chem/Mapper/solvers/exact/edit_support.py +375 -0
- synkit/Chem/Mapper/solvers/exact/hybrid.py +224 -0
- synkit/Chem/Mapper/solvers/exact/hydrogen.py +264 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/branching.py +499 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumerate.py +1050 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumeration_result.py +30 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/kernel.py +256 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/milp.py +194 -0
- synkit/Chem/Mapper/solvers/exact/native/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/native/build.py +154 -0
- synkit/Chem/Mapper/solvers/exact/native/candidates.py +593 -0
- synkit/Chem/Mapper/solvers/exact/native/canonical.py +396 -0
- synkit/Chem/Mapper/solvers/exact/native/disk_frontier.py +415 -0
- synkit/Chem/Mapper/solvers/exact/native/frontier.py +390 -0
- synkit/Chem/Mapper/solvers/exact/native/hydrogen_quotient.py +213 -0
- synkit/Chem/Mapper/solvers/exact/native/its.py +528 -0
- synkit/Chem/Mapper/solvers/exact/native/native_distance.cpp +2803 -0
- synkit/Chem/Mapper/solvers/exact/native/orbit_aggregation.py +328 -0
- synkit/Chem/Mapper/solvers/exact/native/parallel.py +205 -0
- synkit/Chem/Mapper/solvers/exact/native/pmcd.py +203 -0
- synkit/Chem/Mapper/solvers/exact/native/runtime.py +40 -0
- synkit/Chem/Mapper/solvers/exact/native/search.py +294 -0
- synkit/Chem/Mapper/solvers/exact/native/separator.py +224 -0
- synkit/Chem/Mapper/solvers/exact/native_build.py +12 -0
- synkit/Chem/Mapper/solvers/exact/pabs/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/pabs/cost_lattice.py +43 -0
- synkit/Chem/Mapper/solvers/exact/pabs/factor_spectrum.py +110 -0
- synkit/Chem/Mapper/solvers/exact/pabs/multi_shell.py +368 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation.py +679 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_bounds.py +436 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_limits.py +13 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_search.py +856 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_symmetry.py +66 -0
- synkit/Chem/Mapper/solvers/exact/pabs/residual_typed_bonds.py +136 -0
- synkit/Chem/Mapper/solvers/exact/pabs/reward_frontier.py +201 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_bound.py +258 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_spectrum.py +308 -0
- synkit/Chem/Mapper/solvers/exact/pabs/sparse_reward_bound.py +207 -0
- synkit/Chem/Mapper/solvers/exact/pabs/suffix_spectrum.py +299 -0
- synkit/Chem/Mapper/solvers/exact/search.py +55 -0
- synkit/Chem/Mapper/solvers/exact/seeds/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed.py +104 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_fragments.py +148 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_relaxation.py +181 -0
- synkit/Chem/Mapper/solvers/exact/symmetry.py +223 -0
- synkit/Chem/Mapper/solvers/native/__init__.py +4 -0
- synkit/Chem/Mapper/solvers/native/assignment.py +99 -0
- synkit/Chem/Mapper/solvers/native/astar.py +22 -0
- synkit/Chem/Mapper/solvers/native/build.py +385 -0
- synkit/Chem/Mapper/solvers/native/classification.py +641 -0
- synkit/Chem/Mapper/solvers/native/core.py +378 -0
- synkit/Chem/Mapper/solvers/native/cpp/CMakeLists.txt +120 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/map_reaction.cpp +29 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/slap_graph.cpp +63 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/assignment/assignment.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/astar.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/bound.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/pmcd_witness.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/runtime.h +130 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/hydrogen/hydrogen.h +73 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mapper.h +175 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mces/mces.h +9 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/pabs/seed.h +34 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/reaction/reaction.h +30 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/sepdp/sepdp.h +21 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/slap.h +72 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/solvers/solvers.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/classification.h +140 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/cpabs.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/assignment/abb.cpp +302 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/CMakeLists.txt +12 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/README.md +47 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar.cpp +228 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar_graph.cpp +37 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/bound.cpp +212 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/dispatch.cpp +272 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/pmcd_witness.cpp +36 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/hydrogen/hydrogen.cpp +380 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/README.md +57 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/mces.cpp +287 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/bridge.cpp +334 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/chemical_floor.inc +181 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/endpoint_symmetry.inc +104 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/exact_helpers.h +20 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_assignment_certificate.h +165 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_certificate.h +125 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_model.h +365 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_provider.inc +319 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_rounding.inc +170 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_test_hooks.h +342 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/objective_reduction.inc +137 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/overlap_dual.inc +351 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/profile_seed.inc +214 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/seed_heuristics.inc +640 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_embedding.cpp +430 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_root_lp.h +176 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/test_hooks.inc +262 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/reaction/reaction.cpp +611 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/README.md +61 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/sepdp.cpp +253 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/slap.cpp +810 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/formulation.h +138 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/maxsat.cpp +526 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/ortools.cpp +475 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/seed.h +80 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/weighted_cnf.h +256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/README.md +43 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/dfs.cpp +123 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/edit_support.cpp +224 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification.cpp +553 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_pipeline.cpp +124 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_stream.h +13 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/cpabs.cpp +455 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/edge_deck.cpp +743 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naugraph.c +732 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.c +94 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.h +49 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.c +1771 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.h +133 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nautil.c +750 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naututil.h +355 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.c +1256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.h +1544 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.c +1131 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.h +70 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/sorttemplates.c +580 -0
- synkit/Chem/Mapper/solvers/native/hydrogen.py +711 -0
- synkit/Chem/Mapper/solvers/native/implicit.py +506 -0
- synkit/Chem/Mapper/solvers/native/implicit_certificate.py +187 -0
- synkit/Chem/Mapper/solvers/native/implicit_preparation.py +309 -0
- synkit/Chem/Mapper/solvers/native/lib/synkit_mapper.dll +0 -0
- synkit/Chem/Mapper/solvers/native/mces.py +16 -0
- synkit/Chem/Mapper/solvers/native/reaction.py +179 -0
- synkit/Chem/Mapper/solvers/native/reaction_build.py +277 -0
- synkit/Chem/Mapper/solvers/native/reaction_text.py +26 -0
- synkit/Chem/Mapper/solvers/native/runtime.py +242 -0
- synkit/Chem/Mapper/solvers/native/sepdp.py +47 -0
- synkit/Chem/Mapper/solvers/native/slap.py +266 -0
- synkit/Chem/Mapper/solvers/native/solvers.py +67 -0
- synkit/Chem/Mapper/solvers/native/stereo.py +86 -0
- synkit/Chem/Mapper/solvers/native/supporting.py +34 -0
- synkit/Chem/Mapper/solvers/native/symmetry.py +91 -0
- synkit/Chem/Mapper/solvers/slap/__init__.py +29 -0
- synkit/Chem/Mapper/solvers/slap/lap.py +174 -0
- synkit/Chem/Mapper/solvers/slap/sequential.py +696 -0
- synkit/Chem/Mapper/validation/__init__.py +1 -0
- synkit/Chem/Mapper/validation/aam_validator.py +442 -0
- synkit/Chem/Mapper/validation/annotations.py +274 -0
- synkit/Chem/Mapper/validation/bonds.py +124 -0
- synkit/Chem/Mapper/validation/cohorts.py +72 -0
- synkit/Chem/Mapper/validation/identifiability.py +150 -0
- synkit/Chem/Mapper/validation/numerical_scope.py +36 -0
- synkit/Chem/Mapper/validation/orbits.py +137 -0
- synkit/Chem/Molecule/__init__.py +0 -0
- synkit/Chem/Molecule/_chirality_loci.py +165 -0
- synkit/Chem/Molecule/_chirality_types.py +231 -0
- synkit/Chem/Molecule/_helical_loci.py +167 -0
- synkit/Chem/Molecule/_stereo_axis_evidence.py +202 -0
- synkit/Chem/Molecule/_stereo_element_detection.py +193 -0
- synkit/Chem/Molecule/_stereo_orientation_constraints.py +511 -0
- synkit/Chem/Molecule/_tetrahedral_carriers.py +63 -0
- synkit/Chem/Molecule/atom_features.py +379 -0
- synkit/Chem/Molecule/chirality.py +1041 -0
- synkit/Chem/Molecule/cip_assignment.py +569 -0
- synkit/Chem/Molecule/cip_ranking.py +843 -0
- synkit/Chem/Molecule/coordinate_stereo.py +503 -0
- synkit/Chem/Molecule/derived_stereo_naming.py +165 -0
- synkit/Chem/Molecule/descriptors.py +452 -0
- synkit/Chem/Molecule/formula.py +249 -0
- synkit/Chem/Molecule/global_stereo.py +175 -0
- synkit/Chem/Molecule/graph_annotator.py +300 -0
- synkit/Chem/Molecule/standardize.py +817 -0
- synkit/Chem/Molecule/stereo_evidence.py +179 -0
- synkit/Chem/Molecule/stereo_perception.py +1032 -0
- synkit/Chem/Molecule/valence.py +123 -0
- synkit/Chem/Reaction/Mapper/__init__.py +44 -0
- synkit/Chem/Reaction/__init__.py +20 -0
- synkit/Chem/Reaction/aam_validator.py +5 -0
- synkit/Chem/Reaction/balance_check.py +138 -0
- synkit/Chem/Reaction/canon_rsmi.py +249 -0
- synkit/Chem/Reaction/cleaning.py +58 -0
- synkit/Chem/Reaction/deionize.py +199 -0
- synkit/Chem/Reaction/explicit_h_audit.py +232 -0
- synkit/Chem/Reaction/fix_aam.py +64 -0
- synkit/Chem/Reaction/neutralize.py +207 -0
- synkit/Chem/Reaction/radical_wildcard.py +224 -0
- synkit/Chem/Reaction/standardize.py +203 -0
- synkit/Chem/Reaction/tautomerize.py +205 -0
- synkit/Chem/__init__.py +1 -0
- synkit/Chem/utils.py +315 -0
- synkit/Data/__init__.py +0 -0
- synkit/Data/aldol.json.gz +1 -0
- synkit/Data/gen_partial_aam.py +147 -0
- synkit/Data/paracetamol.json.gz +1 -0
- synkit/Graph/Canon/__init__.py +16 -0
- synkit/Graph/Canon/_native/__init__.py +13 -0
- synkit/Graph/Canon/_native/build.py +93 -0
- synkit/Graph/Canon/_native/lib/synkit_canon.dll +0 -0
- synkit/Graph/Canon/_native/prepare.py +49 -0
- synkit/Graph/Canon/_native/runtime.py +140 -0
- synkit/Graph/Canon/_native/search.py +137 -0
- synkit/Graph/Canon/canon_algs.py +228 -0
- synkit/Graph/Canon/canon_graph.py +531 -0
- synkit/Graph/Canon/cpp/CMakeLists.txt +42 -0
- synkit/Graph/Canon/cpp/README.md +123 -0
- synkit/Graph/Canon/cpp/examples/canonical_graph.cpp +16 -0
- synkit/Graph/Canon/cpp/include/synkit_canon/canon.h +85 -0
- synkit/Graph/Canon/cpp/src/canonical.cpp +391 -0
- synkit/Graph/Canon/cpp/src/symmetry.h +83 -0
- synkit/Graph/Canon/cpp/tests/symmetry.cpp +93 -0
- synkit/Graph/Canon/exact.py +1020 -0
- synkit/Graph/Canon/exact_refinement.py +368 -0
- synkit/Graph/Canon/nauty.py +143 -0
- synkit/Graph/Context/__init__.py +0 -0
- synkit/Graph/Context/hier_context.py +244 -0
- synkit/Graph/Context/radius_expand.py +252 -0
- synkit/Graph/FG/__init__.py +19 -0
- synkit/Graph/FG/api.py +39 -0
- synkit/Graph/FG/audit.py +155 -0
- synkit/Graph/FG/catalog.py +919 -0
- synkit/Graph/FG/detector.py +288 -0
- synkit/Graph/FG/model.py +97 -0
- synkit/Graph/FG/recognizers.py +274 -0
- synkit/Graph/FG/ring_system.py +154 -0
- synkit/Graph/Feature/Descriptors/topology.py +857 -0
- synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
- synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +222 -0
- synkit/Graph/Feature/__init__.py +5 -0
- synkit/Graph/Feature/graph_descriptors.py +323 -0
- synkit/Graph/Feature/graph_fps.py +96 -0
- synkit/Graph/Feature/graph_signature.py +239 -0
- synkit/Graph/Feature/hash_fps.py +133 -0
- synkit/Graph/Feature/morgan_fps.py +88 -0
- synkit/Graph/Feature/path_fps.py +81 -0
- synkit/Graph/Feature/wl_hash.py +134 -0
- synkit/Graph/Fusion/__init__.py +85 -0
- synkit/Graph/Fusion/candidate.py +559 -0
- synkit/Graph/Fusion/constructor.py +877 -0
- synkit/Graph/Fusion/identity.py +184 -0
- synkit/Graph/Fusion/interface.py +700 -0
- synkit/Graph/Fusion/search.py +241 -0
- synkit/Graph/Hyrogen/__init__.py +6 -0
- synkit/Graph/Hyrogen/_misc.py +591 -0
- synkit/Graph/Hyrogen/hcomplete.py +995 -0
- synkit/Graph/Hyrogen/hcomplete_legacy.py +368 -0
- synkit/Graph/Hyrogen/hcompletion_algorithms.py +713 -0
- synkit/Graph/Hyrogen/hextend.py +570 -0
- synkit/Graph/Hyrogen/hextend_legacy.py +215 -0
- synkit/Graph/ITS/__init__.py +4 -0
- synkit/Graph/ITS/its_builder.py +120 -0
- synkit/Graph/ITS/its_construction.py +719 -0
- synkit/Graph/ITS/its_decompose.py +572 -0
- synkit/Graph/ITS/its_destruction.py +331 -0
- synkit/Graph/ITS/its_expand.py +1251 -0
- synkit/Graph/ITS/its_relabel.py +187 -0
- synkit/Graph/ITS/its_reverter.py +356 -0
- synkit/Graph/ITS/normalize_aam.py +145 -0
- synkit/Graph/ITS/partial_its.py +238 -0
- synkit/Graph/ITS/rc_extractor.py +656 -0
- synkit/Graph/ITS/stereo.py +74 -0
- synkit/Graph/MTG/__init__.py +107 -0
- synkit/Graph/MTG/gluing.py +451 -0
- synkit/Graph/MTG/history.py +928 -0
- synkit/Graph/MTG/inventory.py +118 -0
- synkit/Graph/MTG/local.py +447 -0
- synkit/Graph/MTG/mcs_matcher.py +246 -0
- synkit/Graph/MTG/mtg.py +611 -0
- synkit/Graph/MTG/mtg_explore.py +74 -0
- synkit/Graph/MTG/observation.py +209 -0
- synkit/Graph/MTG/process.py +577 -0
- synkit/Graph/MTG/queries.py +66 -0
- synkit/Graph/MTG/realization.py +95 -0
- synkit/Graph/MTG/series_parallel.py +107 -0
- synkit/Graph/MTG/utils.py +424 -0
- synkit/Graph/Matcher/__init__.py +10 -0
- synkit/Graph/Matcher/approx_mcs.py +1264 -0
- synkit/Graph/Matcher/auto_est.py +736 -0
- synkit/Graph/Matcher/automorphism.py +314 -0
- synkit/Graph/Matcher/batch_cluster.py +224 -0
- synkit/Graph/Matcher/dedup_matches.py +247 -0
- synkit/Graph/Matcher/graph_cluster.py +191 -0
- synkit/Graph/Matcher/graph_matcher.py +312 -0
- synkit/Graph/Matcher/graph_morphism.py +421 -0
- synkit/Graph/Matcher/mcs_matcher.py +804 -0
- synkit/Graph/Matcher/multi_turbo_iso.py +186 -0
- synkit/Graph/Matcher/orbit.py +284 -0
- synkit/Graph/Matcher/partial_matcher.py +694 -0
- synkit/Graph/Matcher/sing.py +544 -0
- synkit/Graph/Matcher/subgraph_matcher.py +812 -0
- synkit/Graph/Matcher/turbo_iso.py +272 -0
- synkit/Graph/Matcher/wl_sel.py +537 -0
- synkit/Graph/Mech/__init__.py +26 -0
- synkit/Graph/Mech/conversion.py +1466 -0
- synkit/Graph/Mech/conversion_debug.py +237 -0
- synkit/Graph/Mech/electron_accounting.py +200 -0
- synkit/Graph/Mech/lwg_editor.py +406 -0
- synkit/Graph/Mech/lwg_ops.py +313 -0
- synkit/Graph/Morphism/__init__.py +99 -0
- synkit/Graph/Morphism/constraints.py +460 -0
- synkit/Graph/Morphism/llg.py +999 -0
- synkit/Graph/Morphism/morphism.py +349 -0
- synkit/Graph/Morphism/stereo.py +258 -0
- synkit/Graph/Morphism/stereo_morphism.py +383 -0
- synkit/Graph/Morphism/stereo_morphism_helpers.py +957 -0
- synkit/Graph/Morphism/stereo_morphism_types.py +117 -0
- synkit/Graph/Stereo/__init__.py +376 -0
- synkit/Graph/Stereo/_descriptor_core.py +207 -0
- synkit/Graph/Stereo/_descriptor_serialization.py +138 -0
- synkit/Graph/Stereo/canonical.py +648 -0
- synkit/Graph/Stereo/changes.py +964 -0
- synkit/Graph/Stereo/composition.py +391 -0
- synkit/Graph/Stereo/configured.py +946 -0
- synkit/Graph/Stereo/couplings.py +245 -0
- synkit/Graph/Stereo/descriptors.py +1030 -0
- synkit/Graph/Stereo/enumeration.py +597 -0
- synkit/Graph/Stereo/extended_descriptors.py +406 -0
- synkit/Graph/Stereo/global_configured.py +152 -0
- synkit/Graph/Stereo/global_stereo.py +304 -0
- synkit/Graph/Stereo/identity.py +648 -0
- synkit/Graph/Stereo/interchange.py +496 -0
- synkit/Graph/Stereo/legacy.py +460 -0
- synkit/Graph/Stereo/matching.py +899 -0
- synkit/Graph/Stereo/native.py +154 -0
- synkit/Graph/Stereo/orbits.py +668 -0
- synkit/Graph/Stereo/outcomes.py +99 -0
- synkit/Graph/Stereo/pairwise.py +350 -0
- synkit/Graph/Stereo/rdkit_adapter.py +662 -0
- synkit/Graph/Stereo/rdkit_identity.py +118 -0
- synkit/Graph/Stereo/rdkit_resonance.py +140 -0
- synkit/Graph/Stereo/reaction_interchange.py +266 -0
- synkit/Graph/Stereo/semantics.py +296 -0
- synkit/Graph/Stereo/supports.py +257 -0
- synkit/Graph/Stereo/wire.py +339 -0
- synkit/Graph/Wildcard/__init__.py +0 -0
- synkit/Graph/Wildcard/fuse_graph.py +156 -0
- synkit/Graph/Wildcard/graph_wc.py +303 -0
- synkit/Graph/Wildcard/its_merge.py +857 -0
- synkit/Graph/Wildcard/radwc.py +114 -0
- synkit/Graph/Wildcard/wc_matcher.py +576 -0
- synkit/Graph/Wildcard/wildcard.py +225 -0
- synkit/Graph/__init__.py +17 -0
- synkit/Graph/canon_graph.py +14 -0
- synkit/Graph/syn_graph.py +199 -0
- synkit/Graph/utils.py +324 -0
- synkit/IO/__init__.py +4 -0
- synkit/IO/chem_converter.py +1059 -0
- synkit/IO/combinatorial/__init__.py +8 -0
- synkit/IO/combinatorial/gml_to_graph.py +223 -0
- synkit/IO/combinatorial/graph_to_gml.py +280 -0
- synkit/IO/combinatorial/graph_to_smarts.py +186 -0
- synkit/IO/combinatorial/smarts_expander.py +147 -0
- synkit/IO/combinatorial/smarts_generalizer.py +133 -0
- synkit/IO/combinatorial/smarts_to_graph.py +179 -0
- synkit/IO/conversion.py +42 -0
- synkit/IO/data_io.py +269 -0
- synkit/IO/data_process.py +143 -0
- synkit/IO/debug.py +73 -0
- synkit/IO/gml_to_nx.py +150 -0
- synkit/IO/graph_to_mol.py +199 -0
- synkit/IO/mol_to_graph.py +1105 -0
- synkit/IO/mol_to_graph_chemistry.py +545 -0
- synkit/IO/nx_to_gml.py +209 -0
- synkit/IO/smiles_to_id.py +123 -0
- synkit/Mechanism/__init__.py +151 -0
- synkit/Mechanism/adapters.py +136 -0
- synkit/Mechanism/audit.py +323 -0
- synkit/Mechanism/benchmark.py +638 -0
- synkit/Mechanism/compatibility.py +13 -0
- synkit/Mechanism/drawing.py +85 -0
- synkit/Mechanism/electron_models.py +725 -0
- synkit/Mechanism/equivalence.py +521 -0
- synkit/Mechanism/evidence.py +262 -0
- synkit/Mechanism/interchange.py +141 -0
- synkit/Mechanism/model.py +918 -0
- synkit/Mechanism/neighbor_stereo.py +312 -0
- synkit/Mechanism/radical_data.py +740 -0
- synkit/Mechanism/replay.py +986 -0
- synkit/Mechanism/schema.py +264 -0
- synkit/Mechanism/stereo_state.py +416 -0
- synkit/Mechanism/symbols.py +91 -0
- synkit/Rule/Apply/__init__.py +35 -0
- synkit/Rule/Apply/conditions.py +45 -0
- synkit/Rule/Apply/dpo.py +998 -0
- synkit/Rule/Apply/rule_matcher.py +218 -0
- synkit/Rule/Apply/validation.py +354 -0
- synkit/Rule/Compose/__init__.py +106 -0
- synkit/Rule/Compose/_identity.py +43 -0
- synkit/Rule/Compose/composition.py +976 -0
- synkit/Rule/Compose/laws.py +439 -0
- synkit/Rule/Compose/search.py +771 -0
- synkit/Rule/Compose/synrule.py +170 -0
- synkit/Rule/Modify/__init__.py +0 -0
- synkit/Rule/Modify/implict_rule.py +56 -0
- synkit/Rule/Modify/longest_path.py +92 -0
- synkit/Rule/Modify/molecule_rule.py +115 -0
- synkit/Rule/Modify/prune_templates.py +78 -0
- synkit/Rule/Modify/rule_utils.py +194 -0
- synkit/Rule/Modify/strip_rule.py +97 -0
- synkit/Rule/__init__.py +28 -0
- synkit/Rule/generic_stereo.py +884 -0
- synkit/Rule/stereo_identity.py +271 -0
- synkit/Rule/syn_rule.py +1090 -0
- synkit/Synthesis/MSR/__init__.py +0 -0
- synkit/Synthesis/MSR/path_finder.py +239 -0
- synkit/Synthesis/Metrics/__init__.py +0 -0
- synkit/Synthesis/Metrics/_base.py +52 -0
- synkit/Synthesis/Metrics/_plot.py +129 -0
- synkit/Synthesis/Metrics/_ranking.py +179 -0
- synkit/Synthesis/RBL/__init__.py +80 -0
- synkit/Synthesis/RBL/engine.py +1249 -0
- synkit/Synthesis/RBL/matching.py +652 -0
- synkit/Synthesis/RBL/overlap.py +336 -0
- synkit/Synthesis/RBL/pipeline.py +376 -0
- synkit/Synthesis/RBL/policy.py +139 -0
- synkit/Synthesis/RBL/proof.py +370 -0
- synkit/Synthesis/RBL/reaction.py +506 -0
- synkit/Synthesis/RBL/state.py +169 -0
- synkit/Synthesis/RBL/validation.py +1060 -0
- synkit/Synthesis/Reactor/__init__.py +60 -0
- synkit/Synthesis/Reactor/core/__init__.py +1 -0
- synkit/Synthesis/Reactor/core/engine.py +967 -0
- synkit/Synthesis/Reactor/core/product.py +621 -0
- synkit/Synthesis/Reactor/core/rewrite.py +561 -0
- synkit/Synthesis/Reactor/core/strategy.py +44 -0
- synkit/Synthesis/Reactor/matching/__init__.py +1 -0
- synkit/Synthesis/Reactor/matching/mixin.py +920 -0
- synkit/Synthesis/Reactor/matching/policy.py +345 -0
- synkit/Synthesis/Reactor/matching/symmetry.py +309 -0
- synkit/Synthesis/Reactor/output/__init__.py +1 -0
- synkit/Synthesis/Reactor/output/deduplication.py +703 -0
- synkit/Synthesis/Reactor/output/policy.py +16 -0
- synkit/Synthesis/Reactor/output/serialization.py +485 -0
- synkit/Synthesis/Reactor/output/structural.py +893 -0
- synkit/Synthesis/Reactor/stereo/__init__.py +1 -0
- synkit/Synthesis/Reactor/stereo/assignment.py +35 -0
- synkit/Synthesis/Reactor/stereo/mixin.py +780 -0
- synkit/Synthesis/Reactor/variants/__init__.py +1 -0
- synkit/Synthesis/Reactor/variants/imbalanced.py +180 -0
- synkit/Synthesis/Reactor/variants/partial.py +84 -0
- synkit/Synthesis/Reactor/workflow/__init__.py +1 -0
- synkit/Synthesis/Reactor/workflow/batch.py +550 -0
- synkit/Synthesis/Reactor/workflow/benchmark.py +143 -0
- synkit/Synthesis/Reactor/workflow/postprocess.py +267 -0
- synkit/Synthesis/Reactor/workflow/rule_filter.py +210 -0
- synkit/Synthesis/__init__.py +0 -0
- synkit/Synthesis/reactor_utils.py +316 -0
- synkit/Utils/__init__.py +0 -0
- synkit/Utils/utils.py +172 -0
- synkit/Vis/__init__.py +91 -0
- synkit/Vis/crn/__init__.py +40 -0
- synkit/Vis/epd/__init__.py +20 -0
- synkit/Vis/epd/arrows.py +425 -0
- synkit/Vis/epd/chem.py +197 -0
- synkit/Vis/epd/constants.py +83 -0
- synkit/Vis/epd/layout.py +283 -0
- synkit/Vis/epd/mapping.py +51 -0
- synkit/Vis/epd/models.py +136 -0
- synkit/Vis/epd/render.py +543 -0
- synkit/Vis/epd/utils.py +108 -0
- synkit/Vis/epd/visualizer.py +811 -0
- synkit/Vis/graph_visualizer.py +375 -0
- synkit/Vis/its/__init__.py +19 -0
- synkit/Vis/its/drawer.py +615 -0
- synkit/Vis/its_drawer.py +35 -0
- synkit/Vis/molecule/__init__.py +31 -0
- synkit/Vis/molecule/drawer.py +563 -0
- synkit/Vis/molecule/reaction.py +285 -0
- synkit/Vis/molecule_drawer.py +33 -0
- synkit/Vis/mtg/__init__.py +17 -0
- synkit/Vis/mtg/drawer.py +769 -0
- synkit/Vis/mtg_drawer.py +34 -0
- synkit/Vis/pdf_writer.py +146 -0
- synkit/Vis/reaction/__init__.py +19 -0
- synkit/Vis/reaction/rule.py +141 -0
- synkit/Vis/reaction/rxn.py +532 -0
- synkit/Vis/reaction_drawer.py +41 -0
- synkit/Vis/space/__init__.py +17 -0
- synkit/Vis/space/chemical.py +80 -0
- synkit/Vis/space/embedding.py +87 -0
- synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
- synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
- synkit/Vis/vis_synedu/rxn_vis.py +377 -0
- synkit/Vis/vis_synedu/vis.py +501 -0
- synkit/Vis/visual_drawer.py +215 -0
- synkit/Vis/visual_model.py +520 -0
- synkit/__init__.py +3 -0
- synkit/_native.py +91 -0
- synkit/examples.py +50 -0
- synkit/version.py +23 -0
- synkit-1.6.5.dist-info/METADATA +156 -0
- synkit-1.6.5.dist-info/RECORD +636 -0
- synkit-1.6.5.dist-info/WHEEL +4 -0
- synkit-1.6.5.dist-info/licenses/LICENSE +21 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- 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
|