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
synkit/CRN/analysis.py
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
"""Scenario-based biochemical audits and independently replayed pathways.
|
|
2
|
+
|
|
3
|
+
Expensive enumeration is deliberately separate from the routine audit. Instances
|
|
4
|
+
own a detached snapshot; construct a new instance after editing a network.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
from dataclasses import asdict, dataclass, field
|
|
11
|
+
from itertools import product
|
|
12
|
+
from math import prod
|
|
13
|
+
from time import monotonic
|
|
14
|
+
from typing import Any, Mapping
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from scipy.optimize import linprog
|
|
18
|
+
|
|
19
|
+
from .Structure import SynCRN
|
|
20
|
+
from .Props.stoich import _exact_null_space
|
|
21
|
+
from .Props.deficiency import crnt_summary
|
|
22
|
+
from .Pathway import PathwayRealizability
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class AnalysisResult:
|
|
27
|
+
value: Any
|
|
28
|
+
status: str = "complete"
|
|
29
|
+
complete: bool = True
|
|
30
|
+
method: str = "structural"
|
|
31
|
+
assumptions: dict = field(default_factory=dict)
|
|
32
|
+
certificate: Any = None
|
|
33
|
+
termination_reason: str = "complete"
|
|
34
|
+
|
|
35
|
+
def to_dict(self):
|
|
36
|
+
return asdict(self)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class Scenario:
|
|
41
|
+
"""Species selectors accept unique ids, source ids or labels.
|
|
42
|
+
|
|
43
|
+
Chemostats have unlimited supply in discrete execution. Currency deletion
|
|
44
|
+
removes incidences and changes the model. Neither happens implicitly.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
chemostats: tuple[str, ...] = ()
|
|
48
|
+
drop_species: tuple[str, ...] = ()
|
|
49
|
+
initial_marking: Mapping[str, int] = field(default_factory=dict)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_model(cls, crn, *, initial_marking=None):
|
|
53
|
+
"""Use declared SBML boundary species; never infer token counts from concentrations."""
|
|
54
|
+
return cls(
|
|
55
|
+
chemostats=tuple(
|
|
56
|
+
sid
|
|
57
|
+
for sid, sp in crn.species.items()
|
|
58
|
+
if str(sp.source_attrs.get("boundaryCondition", "")).lower() == "true"
|
|
59
|
+
),
|
|
60
|
+
initial_marking={} if initial_marking is None else dict(initial_marking),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CRNAnalysis:
|
|
65
|
+
"""Prepared, detached model and explicit biochemical scenario."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, crn: Any, scenario: Scenario | None = None):
|
|
68
|
+
self.scenario = deepcopy(scenario or Scenario())
|
|
69
|
+
self._original = deepcopy(
|
|
70
|
+
crn if isinstance(crn, SynCRN) else SynCRN.from_digraph(crn)
|
|
71
|
+
)
|
|
72
|
+
self._crn = deepcopy(self._original)
|
|
73
|
+
dropped = {self._resolve(x) for x in self.scenario.drop_species}
|
|
74
|
+
self.chemostats = {self._resolve(x) for x in self.scenario.chemostats}
|
|
75
|
+
if dropped & self.chemostats:
|
|
76
|
+
raise ValueError("A species cannot be both removed and chemostatted")
|
|
77
|
+
for reaction in self._crn.reactions.values():
|
|
78
|
+
for side in (reaction.lhs, reaction.rhs):
|
|
79
|
+
for sid in dropped:
|
|
80
|
+
side.counts.pop(sid, None)
|
|
81
|
+
for sid in dropped:
|
|
82
|
+
del self._crn.species[sid]
|
|
83
|
+
self._matrices = self._crn.to_stoichiometric_matrices()
|
|
84
|
+
self.species_order = tuple(self._matrices["species_order"])
|
|
85
|
+
self.reaction_order = tuple(self._matrices["reaction_order"])
|
|
86
|
+
self._structure = None
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def model(self):
|
|
90
|
+
return deepcopy(self._crn)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def matrices(self):
|
|
94
|
+
return deepcopy(self._matrices)
|
|
95
|
+
|
|
96
|
+
def _resolve(self, token, *, reaction=False):
|
|
97
|
+
records = self._crn.reactions if reaction else self._crn.species
|
|
98
|
+
if token in records:
|
|
99
|
+
return token
|
|
100
|
+
matches = [
|
|
101
|
+
key
|
|
102
|
+
for key, record in records.items()
|
|
103
|
+
if str(token) in {str(record.label), str(record.source_node_id)}
|
|
104
|
+
]
|
|
105
|
+
if len(matches) != 1:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Unknown or ambiguous {'reaction' if reaction else 'species'}: {token!r}"
|
|
108
|
+
)
|
|
109
|
+
return matches[0]
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _counts(values):
|
|
113
|
+
if any(
|
|
114
|
+
isinstance(v, bool) or not isinstance(v, (int, np.integer)) or v < 0
|
|
115
|
+
for v in values.values()
|
|
116
|
+
):
|
|
117
|
+
raise ValueError(
|
|
118
|
+
"Discrete inventories and flows require nonnegative integers"
|
|
119
|
+
)
|
|
120
|
+
return {k: int(v) for k, v in values.items() if v}
|
|
121
|
+
|
|
122
|
+
def _species_compositions(self):
|
|
123
|
+
from rdkit import Chem
|
|
124
|
+
|
|
125
|
+
compositions = {}
|
|
126
|
+
for sid, species in self._crn.species.items():
|
|
127
|
+
formula = species.source_attrs.get("chemicalFormula")
|
|
128
|
+
charge = species.source_attrs.get("charge")
|
|
129
|
+
if formula and charge is not None:
|
|
130
|
+
import re
|
|
131
|
+
|
|
132
|
+
parts = re.findall(r"([A-Z][a-z]?)([0-9]*)", formula)
|
|
133
|
+
if "".join(e + n for e, n in parts) == formula:
|
|
134
|
+
counts = {}
|
|
135
|
+
for element, number in parts:
|
|
136
|
+
counts[element] = counts.get(element, 0) + int(number or 1)
|
|
137
|
+
counts["charge"] = float(charge)
|
|
138
|
+
compositions[sid] = counts
|
|
139
|
+
mol = Chem.MolFromSmiles(species.smiles) if species.smiles else None
|
|
140
|
+
if mol is not None:
|
|
141
|
+
counts = {}
|
|
142
|
+
for atom in Chem.AddHs(mol).GetAtoms():
|
|
143
|
+
counts[atom.GetSymbol()] = counts.get(atom.GetSymbol(), 0) + 1
|
|
144
|
+
counts["charge"] = sum(a.GetFormalCharge() for a in mol.GetAtoms())
|
|
145
|
+
compositions[sid] = counts
|
|
146
|
+
return compositions
|
|
147
|
+
|
|
148
|
+
def audit(self):
|
|
149
|
+
"""Cheap model diagnostics; chemical balance is unknown without structures."""
|
|
150
|
+
signatures, duplicates, empty, unbalanced, unknown = {}, [], [], [], []
|
|
151
|
+
compositions = self._species_compositions()
|
|
152
|
+
involved = set()
|
|
153
|
+
for rid, reaction in self._crn.reactions.items():
|
|
154
|
+
sig = (
|
|
155
|
+
tuple(sorted(reaction.lhs.items())),
|
|
156
|
+
tuple(sorted(reaction.rhs.items())),
|
|
157
|
+
)
|
|
158
|
+
if sig in signatures:
|
|
159
|
+
duplicates.append((signatures[sig], rid))
|
|
160
|
+
signatures[sig] = rid
|
|
161
|
+
if not reaction.lhs or not reaction.rhs:
|
|
162
|
+
empty.append(rid)
|
|
163
|
+
participants = set(dict(reaction.lhs.items())) | set(
|
|
164
|
+
dict(reaction.rhs.items())
|
|
165
|
+
)
|
|
166
|
+
involved |= participants
|
|
167
|
+
if not participants <= compositions.keys():
|
|
168
|
+
unknown.append(rid)
|
|
169
|
+
continue
|
|
170
|
+
balance = {}
|
|
171
|
+
for sign, side in ((-1, reaction.lhs), (1, reaction.rhs)):
|
|
172
|
+
for sid, coeff in side.items():
|
|
173
|
+
for element, count in compositions[sid].items():
|
|
174
|
+
balance[element] = (
|
|
175
|
+
balance.get(element, 0) + sign * coeff * count
|
|
176
|
+
)
|
|
177
|
+
if any(balance.values()):
|
|
178
|
+
unbalanced.append(
|
|
179
|
+
{
|
|
180
|
+
"reaction": rid,
|
|
181
|
+
"imbalance": {k: v for k, v in balance.items() if v},
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
return AnalysisResult(
|
|
185
|
+
dict(
|
|
186
|
+
n_species=len(self.species_order),
|
|
187
|
+
n_reactions=len(self.reaction_order),
|
|
188
|
+
duplicate_reactions=duplicates,
|
|
189
|
+
open_reactions=empty,
|
|
190
|
+
isolated_species=sorted(set(self.species_order) - involved),
|
|
191
|
+
unbalanced_reactions=unbalanced,
|
|
192
|
+
balance_unknown=unknown,
|
|
193
|
+
),
|
|
194
|
+
assumptions=self._assumptions(),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def _assumptions(self):
|
|
198
|
+
return {
|
|
199
|
+
"chemostats": sorted(self.chemostats),
|
|
200
|
+
"drop_species": list(self.scenario.drop_species),
|
|
201
|
+
"inventory_semantics": "integer tokens; concentrations are not converted",
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
def conservation(self):
|
|
205
|
+
s = self._matrices["S"]
|
|
206
|
+
order = tuple(sid for sid in self.species_order if sid not in self.chemostats)
|
|
207
|
+
s = s[
|
|
208
|
+
[
|
|
209
|
+
i
|
|
210
|
+
for i, sid in enumerate(self.species_order)
|
|
211
|
+
if sid not in self.chemostats
|
|
212
|
+
],
|
|
213
|
+
:,
|
|
214
|
+
]
|
|
215
|
+
laws = _exact_null_space(s.T.tolist(), len(order))
|
|
216
|
+
return AnalysisResult(
|
|
217
|
+
{
|
|
218
|
+
"species_order": order,
|
|
219
|
+
"basis": laws,
|
|
220
|
+
"dimension": len(laws),
|
|
221
|
+
"rank": len(order) - len(laws),
|
|
222
|
+
},
|
|
223
|
+
method="rational elimination",
|
|
224
|
+
assumptions=self._assumptions(),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def structure(self):
|
|
228
|
+
if self._structure is None:
|
|
229
|
+
self._structure = crnt_summary(self._crn).to_dict()
|
|
230
|
+
value = deepcopy(self._structure)
|
|
231
|
+
value["scope"] = "reaction equations including external species"
|
|
232
|
+
if self.chemostats:
|
|
233
|
+
value["deficiency_zero"] = {
|
|
234
|
+
"applicable": None,
|
|
235
|
+
"reason": "Chemostatted dynamics require a separately reduced model",
|
|
236
|
+
}
|
|
237
|
+
value["deficiency_one"] = {
|
|
238
|
+
"applicable": None,
|
|
239
|
+
"reason": "Chemostatted dynamics require a separately reduced model",
|
|
240
|
+
}
|
|
241
|
+
return AnalysisResult(value, assumptions=self._assumptions())
|
|
242
|
+
|
|
243
|
+
def accessibility(self, seeds=()):
|
|
244
|
+
available = {self._resolve(s) for s in seeds} | self.chemostats
|
|
245
|
+
fired, witnesses = set(), {}
|
|
246
|
+
changed = True
|
|
247
|
+
while changed:
|
|
248
|
+
changed = False
|
|
249
|
+
for rid, reaction in self._crn.reactions.items():
|
|
250
|
+
if rid not in fired and set(dict(reaction.lhs.items())) <= available:
|
|
251
|
+
fired.add(rid)
|
|
252
|
+
for sid, _ in reaction.rhs.items():
|
|
253
|
+
if sid not in available:
|
|
254
|
+
witnesses[sid] = rid
|
|
255
|
+
available.add(sid)
|
|
256
|
+
changed = True
|
|
257
|
+
return AnalysisResult(
|
|
258
|
+
{
|
|
259
|
+
"reachable_species": sorted(available),
|
|
260
|
+
"enabled_reactions": sorted(fired),
|
|
261
|
+
"unreachable_species": sorted(set(self.species_order) - available),
|
|
262
|
+
},
|
|
263
|
+
method="qualitative closure",
|
|
264
|
+
certificate=witnesses,
|
|
265
|
+
assumptions=self._assumptions(),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def flux_feasibility(self, *, bounds=None, required=None, time_limit=30.0):
|
|
269
|
+
"""Steady-state feasibility on internal species, not discrete execution.
|
|
270
|
+
|
|
271
|
+
Bounds default to nonnegative directed fluxes. Require a reaction to
|
|
272
|
+
exclude the zero solution. External supply must be declared explicitly.
|
|
273
|
+
"""
|
|
274
|
+
if time_limit <= 0:
|
|
275
|
+
raise ValueError("time_limit must be positive")
|
|
276
|
+
bounds = (
|
|
277
|
+
{}
|
|
278
|
+
if bounds is None
|
|
279
|
+
else {self._resolve(k, reaction=True): v for k, v in bounds.items()}
|
|
280
|
+
)
|
|
281
|
+
required = (
|
|
282
|
+
{}
|
|
283
|
+
if required is None
|
|
284
|
+
else {self._resolve(k, reaction=True): v for k, v in required.items()}
|
|
285
|
+
)
|
|
286
|
+
limits = []
|
|
287
|
+
for rid in self.reaction_order:
|
|
288
|
+
lo, hi = bounds.get(rid, (0, None))
|
|
289
|
+
lo = max(0 if lo is None else lo, required.get(rid, 0))
|
|
290
|
+
if hi is not None and hi < lo:
|
|
291
|
+
return AnalysisResult(
|
|
292
|
+
False,
|
|
293
|
+
certificate={"conflicting_bounds": rid},
|
|
294
|
+
assumptions=self._assumptions(),
|
|
295
|
+
)
|
|
296
|
+
limits.append((lo, hi))
|
|
297
|
+
if not limits:
|
|
298
|
+
return AnalysisResult(True, certificate={}, assumptions=self._assumptions())
|
|
299
|
+
internal = [
|
|
300
|
+
i for i, sid in enumerate(self.species_order) if sid not in self.chemostats
|
|
301
|
+
]
|
|
302
|
+
s = self._matrices["S"][internal, :]
|
|
303
|
+
answer = linprog(
|
|
304
|
+
np.zeros(len(limits)),
|
|
305
|
+
A_eq=s,
|
|
306
|
+
b_eq=np.zeros(len(internal)),
|
|
307
|
+
bounds=limits,
|
|
308
|
+
method="highs",
|
|
309
|
+
options={"time_limit": time_limit},
|
|
310
|
+
)
|
|
311
|
+
if answer.status == 2:
|
|
312
|
+
return AnalysisResult(
|
|
313
|
+
False, method="HiGHS numerical LP", assumptions=self._assumptions()
|
|
314
|
+
)
|
|
315
|
+
valid = answer.success and np.max(np.abs(s @ answer.x), initial=0) <= 1e-7
|
|
316
|
+
if valid:
|
|
317
|
+
valid = all(
|
|
318
|
+
x >= (lo * (1 - 1e-7) if lo > 0 else lo - 1e-7)
|
|
319
|
+
and (hi is None or x <= hi + 1e-7)
|
|
320
|
+
for x, (lo, hi) in zip(answer.x, limits)
|
|
321
|
+
)
|
|
322
|
+
return AnalysisResult(
|
|
323
|
+
True if valid else None,
|
|
324
|
+
"complete" if valid else "inconclusive",
|
|
325
|
+
bool(valid),
|
|
326
|
+
"HiGHS numerical LP",
|
|
327
|
+
self._assumptions(),
|
|
328
|
+
dict(zip(self.reaction_order, map(float, answer.x))) if valid else None,
|
|
329
|
+
"complete" if valid else str(answer.message),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def blocked_reactions(self, *, bounds=None, time_limit=30.0, threshold=1e-6):
|
|
333
|
+
"""Classify ability to carry at least threshold forward steady-state flux."""
|
|
334
|
+
if threshold <= 0:
|
|
335
|
+
raise ValueError("threshold must be positive")
|
|
336
|
+
deadline = monotonic() + time_limit
|
|
337
|
+
blocked, active, unknown = [], [], []
|
|
338
|
+
for rid in self.reaction_order:
|
|
339
|
+
remaining = deadline - monotonic()
|
|
340
|
+
result = (
|
|
341
|
+
self.flux_feasibility(
|
|
342
|
+
bounds=bounds, required={rid: threshold}, time_limit=remaining
|
|
343
|
+
)
|
|
344
|
+
if remaining > 0
|
|
345
|
+
else AnalysisResult(None)
|
|
346
|
+
)
|
|
347
|
+
(
|
|
348
|
+
blocked
|
|
349
|
+
if result.value is False
|
|
350
|
+
else active if result.value is True else unknown
|
|
351
|
+
).append(rid)
|
|
352
|
+
return AnalysisResult(
|
|
353
|
+
{"blocked": blocked, "active": active, "unknown": unknown},
|
|
354
|
+
"inconclusive" if unknown else "complete",
|
|
355
|
+
not unknown,
|
|
356
|
+
"threshold LP feasibility",
|
|
357
|
+
{**self._assumptions(), "threshold": threshold},
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def verify_pathway(
|
|
361
|
+
self, flow, initial_marking=None, *, max_states=100000, max_depth=10000
|
|
362
|
+
):
|
|
363
|
+
flow = {
|
|
364
|
+
self._resolve(k, reaction=True): v for k, v in self._counts(flow).items()
|
|
365
|
+
}
|
|
366
|
+
marking = (
|
|
367
|
+
self.scenario.initial_marking
|
|
368
|
+
if initial_marking is None
|
|
369
|
+
else initial_marking
|
|
370
|
+
)
|
|
371
|
+
marking = {self._resolve(k): v for k, v in self._counts(marking).items()}
|
|
372
|
+
model = deepcopy(self._crn)
|
|
373
|
+
for reaction in model.reactions.values():
|
|
374
|
+
for side in (reaction.lhs, reaction.rhs):
|
|
375
|
+
for sid in self.chemostats:
|
|
376
|
+
side.counts.pop(sid, None)
|
|
377
|
+
checker = PathwayRealizability().load_syncrn_and_flow(
|
|
378
|
+
model, flow=flow, initial_marking=marking, species="id", reaction="id"
|
|
379
|
+
)
|
|
380
|
+
checker.build_petri_net_from_flow()
|
|
381
|
+
outcome = checker.realizability_result(
|
|
382
|
+
max_states=max_states, max_depth=max_depth
|
|
383
|
+
).to_dict()
|
|
384
|
+
inventory = dict(marking)
|
|
385
|
+
trajectory = [dict(inventory)]
|
|
386
|
+
counts = {}
|
|
387
|
+
for rid in outcome.get("certificate") or []:
|
|
388
|
+
reaction = model.reactions[rid]
|
|
389
|
+
if any(inventory.get(s, 0) < n for s, n in reaction.lhs.items()):
|
|
390
|
+
raise RuntimeError("Returned certificate is not enabled")
|
|
391
|
+
for sign, side in ((-1, reaction.lhs), (1, reaction.rhs)):
|
|
392
|
+
for sid, n in side.items():
|
|
393
|
+
inventory[sid] = inventory.get(sid, 0) + sign * n
|
|
394
|
+
counts[rid] = counts.get(rid, 0) + 1
|
|
395
|
+
trajectory.append(dict(inventory))
|
|
396
|
+
if outcome["status"] == "realizable" and counts != flow:
|
|
397
|
+
raise RuntimeError("Certificate does not match the requested flow")
|
|
398
|
+
outcome.update(
|
|
399
|
+
trajectory=trajectory,
|
|
400
|
+
replay_verified=outcome["status"] == "realizable",
|
|
401
|
+
assumptions=self._assumptions(),
|
|
402
|
+
)
|
|
403
|
+
return outcome
|
|
404
|
+
|
|
405
|
+
def pathways(
|
|
406
|
+
self,
|
|
407
|
+
seeds,
|
|
408
|
+
targets,
|
|
409
|
+
*,
|
|
410
|
+
initial_marking=None,
|
|
411
|
+
max_depth=20,
|
|
412
|
+
max_paths=10,
|
|
413
|
+
max_states=100000,
|
|
414
|
+
):
|
|
415
|
+
"""Suggest qualitative routes and verify each against finite inventory.
|
|
416
|
+
|
|
417
|
+
Candidate search is bounded and qualitative; absent candidates never
|
|
418
|
+
constitute a proof of discrete unreachability or route completeness.
|
|
419
|
+
"""
|
|
420
|
+
from .Pathway import PathwayFinder
|
|
421
|
+
|
|
422
|
+
source = {self._resolve(s) for s in seeds} | self.chemostats
|
|
423
|
+
target = {self._resolve(s) for s in targets}
|
|
424
|
+
finder = PathwayFinder().load_syncrn(self._crn, species="id", reaction="id")
|
|
425
|
+
candidates = finder.find_paths_set(
|
|
426
|
+
source_species=source,
|
|
427
|
+
target_species=target,
|
|
428
|
+
max_depth=max_depth,
|
|
429
|
+
max_paths=max_paths,
|
|
430
|
+
stop_after_first=False,
|
|
431
|
+
)
|
|
432
|
+
rows = []
|
|
433
|
+
for candidate in candidates:
|
|
434
|
+
outcome = self.verify_pathway(
|
|
435
|
+
candidate.flow,
|
|
436
|
+
initial_marking,
|
|
437
|
+
max_states=max_states,
|
|
438
|
+
max_depth=max_depth,
|
|
439
|
+
)
|
|
440
|
+
rows.append(
|
|
441
|
+
{
|
|
442
|
+
"flow": candidate.flow,
|
|
443
|
+
"qualitative_sequence": candidate.reactions,
|
|
444
|
+
"execution": outcome,
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
return AnalysisResult(
|
|
448
|
+
rows,
|
|
449
|
+
"inconclusive",
|
|
450
|
+
False,
|
|
451
|
+
"bounded qualitative candidates plus discrete replay",
|
|
452
|
+
self._assumptions(),
|
|
453
|
+
termination_reason="candidate_search_not_exhaustive",
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
def to_cobra(self, *, bounds, exchange_bounds=None):
|
|
457
|
+
"""Optional COBRApy bridge with caller-specified flux bounds.
|
|
458
|
+
|
|
459
|
+
Every reaction and chemostat exchange must have explicit bounds; no
|
|
460
|
+
physiological capacities or exchange directions are inferred.
|
|
461
|
+
"""
|
|
462
|
+
try:
|
|
463
|
+
import cobra
|
|
464
|
+
except ImportError as exc:
|
|
465
|
+
raise ImportError(
|
|
466
|
+
"Install cobra separately to use the optional COBRApy bridge"
|
|
467
|
+
) from exc
|
|
468
|
+
bounds = {self._resolve(k, reaction=True): value for k, value in bounds.items()}
|
|
469
|
+
if set(bounds) != set(self.reaction_order):
|
|
470
|
+
raise ValueError("Explicit bounds are required for every directed reaction")
|
|
471
|
+
exchanges = (
|
|
472
|
+
{}
|
|
473
|
+
if exchange_bounds is None
|
|
474
|
+
else {self._resolve(k): value for k, value in exchange_bounds.items()}
|
|
475
|
+
)
|
|
476
|
+
if set(exchanges) != self.chemostats:
|
|
477
|
+
raise ValueError(
|
|
478
|
+
"Explicit exchange bounds are required for every chemostat"
|
|
479
|
+
)
|
|
480
|
+
model = cobra.Model("synkit_crn")
|
|
481
|
+
metabolites = {}
|
|
482
|
+
for sid, sp in self._crn.species.items():
|
|
483
|
+
metabolites[sid] = cobra.Metabolite(
|
|
484
|
+
sid,
|
|
485
|
+
name=sp.label,
|
|
486
|
+
compartment=sp.source_attrs.get("compartment", "cell"),
|
|
487
|
+
)
|
|
488
|
+
model.add_metabolites(list(metabolites.values()))
|
|
489
|
+
for rid, record in self._crn.reactions.items():
|
|
490
|
+
reaction = cobra.Reaction(rid, name=record.label or rid)
|
|
491
|
+
reaction.bounds = bounds[rid]
|
|
492
|
+
counts = {}
|
|
493
|
+
for sign, side in ((-1, record.lhs), (1, record.rhs)):
|
|
494
|
+
for sid, coefficient in side.items():
|
|
495
|
+
counts[metabolites[sid]] = (
|
|
496
|
+
counts.get(metabolites[sid], 0) + sign * coefficient
|
|
497
|
+
)
|
|
498
|
+
reaction.add_metabolites(counts)
|
|
499
|
+
model.add_reactions([reaction])
|
|
500
|
+
for sid, limits in exchanges.items():
|
|
501
|
+
identifier = "EX_" + sid
|
|
502
|
+
if identifier in model.reactions:
|
|
503
|
+
raise ValueError("Generated exchange id conflicts with a reaction")
|
|
504
|
+
reaction = cobra.Reaction(identifier)
|
|
505
|
+
reaction.bounds = limits
|
|
506
|
+
reaction.add_metabolites({metabolites[sid]: -1})
|
|
507
|
+
model.add_reactions([reaction])
|
|
508
|
+
return model
|
|
509
|
+
|
|
510
|
+
def minimum_supplements(
|
|
511
|
+
self,
|
|
512
|
+
flow,
|
|
513
|
+
*,
|
|
514
|
+
allowed,
|
|
515
|
+
initial_marking=None,
|
|
516
|
+
costs=None,
|
|
517
|
+
max_added=4,
|
|
518
|
+
max_candidates=10000,
|
|
519
|
+
max_states=100000,
|
|
520
|
+
max_depth=10000,
|
|
521
|
+
):
|
|
522
|
+
"""Minimum weighted added tokens within a bounded, explicit search domain.
|
|
523
|
+
|
|
524
|
+
`allowed` lists species; each may receive 0..max_added tokens. A result
|
|
525
|
+
is optimal only within that box, and only if every cheaper candidate
|
|
526
|
+
was conclusively rejected. No global minimality is implied.
|
|
527
|
+
"""
|
|
528
|
+
if max_added < 0 or max_candidates < 1:
|
|
529
|
+
raise ValueError("Invalid supplementation search limits")
|
|
530
|
+
ids = sorted({self._resolve(s) for s in allowed})
|
|
531
|
+
costs = {} if costs is None else {self._resolve(s): c for s, c in costs.items()}
|
|
532
|
+
if any(not np.isfinite(c) or c <= 0 for c in costs.values()):
|
|
533
|
+
raise ValueError("Supplement costs must be finite and positive")
|
|
534
|
+
domain = prod([max_added + 1] * len(ids))
|
|
535
|
+
if domain > max_candidates:
|
|
536
|
+
return AnalysisResult(
|
|
537
|
+
None,
|
|
538
|
+
"inconclusive",
|
|
539
|
+
False,
|
|
540
|
+
"bounded exhaustive supplementation",
|
|
541
|
+
termination_reason="candidate_limit",
|
|
542
|
+
)
|
|
543
|
+
base = (
|
|
544
|
+
self.scenario.initial_marking
|
|
545
|
+
if initial_marking is None
|
|
546
|
+
else initial_marking
|
|
547
|
+
)
|
|
548
|
+
base = {self._resolve(s): n for s, n in self._counts(base).items()}
|
|
549
|
+
candidates = sorted(
|
|
550
|
+
product(range(max_added + 1), repeat=len(ids)),
|
|
551
|
+
key=lambda values: (
|
|
552
|
+
sum(costs.get(s, 1) * n for s, n in zip(ids, values)),
|
|
553
|
+
values,
|
|
554
|
+
),
|
|
555
|
+
)
|
|
556
|
+
uncertain = []
|
|
557
|
+
for values in candidates:
|
|
558
|
+
score = sum(costs.get(s, 1) * n for s, n in zip(ids, values))
|
|
559
|
+
added = {s: n for s, n in zip(ids, values) if n}
|
|
560
|
+
marking = dict(base)
|
|
561
|
+
for s, n in added.items():
|
|
562
|
+
marking[s] = marking.get(s, 0) + n
|
|
563
|
+
outcome = self.verify_pathway(
|
|
564
|
+
flow, marking, max_states=max_states, max_depth=max_depth
|
|
565
|
+
)
|
|
566
|
+
if outcome["status"] == "realizable":
|
|
567
|
+
optimal = not any(c < score for c in uncertain)
|
|
568
|
+
return AnalysisResult(
|
|
569
|
+
{
|
|
570
|
+
"supplements": added,
|
|
571
|
+
"cost": score,
|
|
572
|
+
"optimal_within_bounds": optimal,
|
|
573
|
+
"max_added_per_species": max_added,
|
|
574
|
+
},
|
|
575
|
+
"complete" if optimal else "inconclusive",
|
|
576
|
+
optimal,
|
|
577
|
+
"bounded exhaustive supplementation",
|
|
578
|
+
self._assumptions(),
|
|
579
|
+
outcome,
|
|
580
|
+
"optimum" if optimal else "unresolved_cheaper_candidates",
|
|
581
|
+
)
|
|
582
|
+
if outcome["status"] == "inconclusive":
|
|
583
|
+
uncertain.append(score)
|
|
584
|
+
return AnalysisResult(
|
|
585
|
+
None,
|
|
586
|
+
"inconclusive" if uncertain else "complete",
|
|
587
|
+
not uncertain,
|
|
588
|
+
"bounded exhaustive supplementation",
|
|
589
|
+
self._assumptions(),
|
|
590
|
+
termination_reason=(
|
|
591
|
+
"search_limit" if uncertain else "no_solution_within_bounds"
|
|
592
|
+
),
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def compare(self, other):
|
|
596
|
+
"""Compare scenarios sharing the same stable species/reaction identities."""
|
|
597
|
+
if not isinstance(other, CRNAnalysis):
|
|
598
|
+
other = CRNAnalysis(other)
|
|
599
|
+
species = sorted(set(self.species_order) | set(other.species_order))
|
|
600
|
+
reactions = sorted(set(self.reaction_order) | set(other.reaction_order))
|
|
601
|
+
|
|
602
|
+
def aligned(analysis, name):
|
|
603
|
+
a = np.zeros((len(species), len(reactions)))
|
|
604
|
+
si, ri = {s: i for i, s in enumerate(species)}, {
|
|
605
|
+
r: i for i, r in enumerate(reactions)
|
|
606
|
+
}
|
|
607
|
+
for i, s in enumerate(analysis.species_order):
|
|
608
|
+
for j, r in enumerate(analysis.reaction_order):
|
|
609
|
+
a[si[s], ri[r]] = analysis._matrices[name][i, j]
|
|
610
|
+
return a
|
|
611
|
+
|
|
612
|
+
return AnalysisResult(
|
|
613
|
+
{
|
|
614
|
+
"same_net_matrix": bool(
|
|
615
|
+
np.array_equal(aligned(self, "S"), aligned(other, "S"))
|
|
616
|
+
),
|
|
617
|
+
"same_reactants": bool(
|
|
618
|
+
np.array_equal(aligned(self, "S_minus"), aligned(other, "S_minus"))
|
|
619
|
+
),
|
|
620
|
+
"same_products": bool(
|
|
621
|
+
np.array_equal(aligned(self, "S_plus"), aligned(other, "S_plus"))
|
|
622
|
+
),
|
|
623
|
+
"before": self.structure().value,
|
|
624
|
+
"after": other.structure().value,
|
|
625
|
+
"conservation_before": self.conservation().value,
|
|
626
|
+
"conservation_after": other.conservation().value,
|
|
627
|
+
},
|
|
628
|
+
assumptions={"before": self._assumptions(), "after": other._assumptions()},
|
|
629
|
+
)
|
synkit/CRN/backend.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Explicit, task-local native backend selection; never compiles on import."""
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from contextvars import ContextVar
|
|
5
|
+
|
|
6
|
+
_LIBRARY = ContextVar("synkit_crn_native_library", default=None)
|
|
7
|
+
_OPERATIONS = ContextVar("synkit_crn_native_operations", default=None)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def record_operation(name):
|
|
11
|
+
operations = _OPERATIONS.get()
|
|
12
|
+
if operations is not None:
|
|
13
|
+
operations[name] = operations.get(name, 0) + 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def backend_metadata():
|
|
17
|
+
from .native import _library_manifest
|
|
18
|
+
|
|
19
|
+
library = _LIBRARY.get()
|
|
20
|
+
metadata = {
|
|
21
|
+
"production_backend": "cpp" if library is not None else "python",
|
|
22
|
+
"library_path": str(library) if library is not None else None,
|
|
23
|
+
"native_operations": dict(_OPERATIONS.get() or {}),
|
|
24
|
+
}
|
|
25
|
+
if library is not None:
|
|
26
|
+
manifest = _library_manifest(library)
|
|
27
|
+
metadata.update(
|
|
28
|
+
library_sha256=manifest["library_sha256"],
|
|
29
|
+
build_sha256=manifest["build_sha256"],
|
|
30
|
+
capabilities=manifest.get("capabilities", []),
|
|
31
|
+
)
|
|
32
|
+
return metadata
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def resolve_backend(backend="auto", library_path=None):
|
|
36
|
+
if backend not in {"auto", "python", "cpp"}:
|
|
37
|
+
raise ValueError("backend must be auto, python or cpp")
|
|
38
|
+
library_path = library_path if library_path is not None else _LIBRARY.get()
|
|
39
|
+
if backend == "python":
|
|
40
|
+
return "python", None
|
|
41
|
+
if backend == "cpp" and library_path is None:
|
|
42
|
+
from .native import bundled_native_library
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
library_path = bundled_native_library()
|
|
46
|
+
except FileNotFoundError as exc:
|
|
47
|
+
raise ValueError(str(exc)) from exc
|
|
48
|
+
return ("cpp", library_path) if library_path is not None else ("python", None)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@contextmanager
|
|
52
|
+
def native_backend(library_path=None):
|
|
53
|
+
"""Use a verified native library throughout a Python analysis workflow."""
|
|
54
|
+
from .native import _load_library
|
|
55
|
+
|
|
56
|
+
_, library_path = resolve_backend("cpp", library_path)
|
|
57
|
+
_load_library(library_path)
|
|
58
|
+
token = _LIBRARY.set(library_path)
|
|
59
|
+
operations_token = _OPERATIONS.set({})
|
|
60
|
+
try:
|
|
61
|
+
yield
|
|
62
|
+
finally:
|
|
63
|
+
_LIBRARY.reset(token)
|
|
64
|
+
_OPERATIONS.reset(operations_token)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@contextmanager
|
|
68
|
+
def python_backend():
|
|
69
|
+
"""Temporarily select the independent Python reference implementation."""
|
|
70
|
+
token = _LIBRARY.set(None)
|
|
71
|
+
try:
|
|
72
|
+
yield
|
|
73
|
+
finally:
|
|
74
|
+
_LIBRARY.reset(token)
|