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,953 @@
|
|
|
1
|
+
"""Chemical Reaction Network Theory: complexes, linkage classes and deficiency.
|
|
2
|
+
|
|
3
|
+
This module supplies the structural quantities that Chemical Reaction Network
|
|
4
|
+
Theory (CRNT) is built on, and the two classical theorems that turn them into
|
|
5
|
+
dynamical verdicts.
|
|
6
|
+
|
|
7
|
+
A reaction network is read as a directed graph on *complexes* — the multisets of
|
|
8
|
+
species that appear on either side of a reaction. Writing ``n`` for the number
|
|
9
|
+
of distinct complexes, ``l`` for the number of linkage classes (connected
|
|
10
|
+
components of that graph) and ``s`` for the rank of the stoichiometric matrix,
|
|
11
|
+
the **deficiency** is
|
|
12
|
+
|
|
13
|
+
.. math::
|
|
14
|
+
|
|
15
|
+
\\delta = n - l - s
|
|
16
|
+
|
|
17
|
+
and is always non-negative. It measures how far the network's reaction vectors
|
|
18
|
+
are from being independent given its complex-graph structure.
|
|
19
|
+
|
|
20
|
+
Two theorems are implemented on top of it:
|
|
21
|
+
|
|
22
|
+
- **Deficiency Zero Theorem** (Feinberg / Horn / Jackson). A weakly reversible
|
|
23
|
+
network of deficiency zero has, for *every* choice of positive mass-action
|
|
24
|
+
rate constants, exactly one positive steady state in each positive
|
|
25
|
+
stoichiometric compatibility class, and it is locally asymptotically stable.
|
|
26
|
+
A deficiency-zero network that is *not* weakly reversible admits no positive
|
|
27
|
+
steady state at all, again regardless of rate constants.
|
|
28
|
+
- **Deficiency One Theorem** (Feinberg). If every linkage class has deficiency
|
|
29
|
+
at most one, the linkage-class deficiencies sum to the network deficiency,
|
|
30
|
+
and each linkage class contains exactly one terminal strong linkage class,
|
|
31
|
+
then there is *at most* one positive steady state per positive stoichiometric
|
|
32
|
+
compatibility class — and exactly one when the network is also weakly
|
|
33
|
+
reversible.
|
|
34
|
+
|
|
35
|
+
Both verdicts are structural: they hold for all positive rate constants and need
|
|
36
|
+
no kinetic parameters.
|
|
37
|
+
|
|
38
|
+
All ranks are computed exactly over :class:`~fractions.Fraction`, so an integer
|
|
39
|
+
network never gets a floating-point deficiency.
|
|
40
|
+
|
|
41
|
+
.. rubric:: Example
|
|
42
|
+
|
|
43
|
+
.. code-block:: python
|
|
44
|
+
|
|
45
|
+
from synkit.CRN import SynCRN
|
|
46
|
+
from synkit.CRN.Props import crnt_summary
|
|
47
|
+
|
|
48
|
+
crn = SynCRN.from_reaction_strings(["A+B>>C", "C>>A+B", "C>>D", "D>>C"])
|
|
49
|
+
report = crnt_summary(crn)
|
|
50
|
+
print(report.deficiency, report.is_weakly_reversible)
|
|
51
|
+
print(report)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
from dataclasses import dataclass, field
|
|
57
|
+
from fractions import Fraction
|
|
58
|
+
from typing import Any, Dict, List, Sequence, Tuple
|
|
59
|
+
|
|
60
|
+
import networkx as nx
|
|
61
|
+
import numpy as np
|
|
62
|
+
|
|
63
|
+
from .helper import _as_graph, _species_and_rule_order
|
|
64
|
+
from .stoich import build_S_minus_plus
|
|
65
|
+
|
|
66
|
+
from .stoich import _reuse_matrices
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"Complex",
|
|
70
|
+
"CRNTSummary",
|
|
71
|
+
"complexes",
|
|
72
|
+
"complex_graph",
|
|
73
|
+
"crnt_summary",
|
|
74
|
+
"deficiency",
|
|
75
|
+
"deficiency_one_verdict",
|
|
76
|
+
"deficiency_zero_verdict",
|
|
77
|
+
"is_deficiency_one_applicable",
|
|
78
|
+
"is_deficiency_zero_applicable",
|
|
79
|
+
"is_reversible",
|
|
80
|
+
"is_weakly_reversible",
|
|
81
|
+
"linkage_class_deficiencies",
|
|
82
|
+
"linkage_classes",
|
|
83
|
+
"strong_linkage_classes",
|
|
84
|
+
"terminal_strong_linkage_classes",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# Complexes
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class Complex:
|
|
95
|
+
"""One complex: a multiset of species with non-negative coefficients.
|
|
96
|
+
|
|
97
|
+
:param coefficients:
|
|
98
|
+
Coefficient vector in canonical species order.
|
|
99
|
+
:type coefficients: Tuple[int, ...]
|
|
100
|
+
|
|
101
|
+
:param species_labels:
|
|
102
|
+
Species labels in the same order as ``coefficients``.
|
|
103
|
+
:type species_labels: Tuple[str, ...]
|
|
104
|
+
|
|
105
|
+
.. rubric:: Example
|
|
106
|
+
|
|
107
|
+
.. code-block:: python
|
|
108
|
+
|
|
109
|
+
print(str(Complex((2, 1, 0), ("A", "B", "C"))))
|
|
110
|
+
# '2A + B'
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
coefficients: Tuple[int, ...]
|
|
114
|
+
species_labels: Tuple[str, ...] = field(compare=False, default=())
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def is_zero(self) -> bool:
|
|
118
|
+
"""Return whether this is the zero complex (an in- or outflow).
|
|
119
|
+
|
|
120
|
+
:return:
|
|
121
|
+
``True`` when every coefficient is zero.
|
|
122
|
+
:rtype: bool
|
|
123
|
+
"""
|
|
124
|
+
return not any(self.coefficients)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def support(self) -> Tuple[str, ...]:
|
|
128
|
+
"""Return the labels of the species occurring in this complex.
|
|
129
|
+
|
|
130
|
+
:return:
|
|
131
|
+
Species labels with a nonzero coefficient.
|
|
132
|
+
:rtype: Tuple[str, ...]
|
|
133
|
+
"""
|
|
134
|
+
return tuple(
|
|
135
|
+
label
|
|
136
|
+
for label, coeff in zip(self.species_labels, self.coefficients)
|
|
137
|
+
if coeff
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def to_dict(self) -> Dict[str, int]:
|
|
141
|
+
"""Return the complex as a ``species label -> coefficient`` mapping.
|
|
142
|
+
|
|
143
|
+
:return:
|
|
144
|
+
Nonzero coefficients keyed by species label.
|
|
145
|
+
:rtype: Dict[str, int]
|
|
146
|
+
"""
|
|
147
|
+
return {
|
|
148
|
+
label: int(coeff)
|
|
149
|
+
for label, coeff in zip(self.species_labels, self.coefficients)
|
|
150
|
+
if coeff
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def __str__(self) -> str:
|
|
154
|
+
"""Return the usual chemical rendering, such as ``2A + B``.
|
|
155
|
+
|
|
156
|
+
:return:
|
|
157
|
+
Human-readable complex.
|
|
158
|
+
:rtype: str
|
|
159
|
+
"""
|
|
160
|
+
if self.is_zero:
|
|
161
|
+
return "0"
|
|
162
|
+
parts = []
|
|
163
|
+
for label, coeff in zip(self.species_labels, self.coefficients):
|
|
164
|
+
if not coeff:
|
|
165
|
+
continue
|
|
166
|
+
parts.append(f"{label}" if coeff == 1 else f"{_fmt_coeff(coeff)}{label}")
|
|
167
|
+
return " + ".join(parts)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _fmt_coeff(coeff: Any) -> str:
|
|
171
|
+
"""Render a stoichiometric coefficient without a trailing ``.0``.
|
|
172
|
+
|
|
173
|
+
:param coeff:
|
|
174
|
+
Coefficient value.
|
|
175
|
+
:type coeff: Any
|
|
176
|
+
|
|
177
|
+
:return:
|
|
178
|
+
Compact string form.
|
|
179
|
+
:rtype: str
|
|
180
|
+
"""
|
|
181
|
+
value = float(coeff)
|
|
182
|
+
return str(int(value)) if value.is_integer() else str(value)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _species_labels(crn: Any) -> List[str]:
|
|
186
|
+
"""Return display labels for the canonical species order.
|
|
187
|
+
|
|
188
|
+
:param crn:
|
|
189
|
+
CRN-like input.
|
|
190
|
+
:type crn: Any
|
|
191
|
+
|
|
192
|
+
:return:
|
|
193
|
+
One label per species, falling back to the node id.
|
|
194
|
+
:rtype: List[str]
|
|
195
|
+
"""
|
|
196
|
+
graph = _as_graph(crn)
|
|
197
|
+
species_order, _, _, _ = _species_and_rule_order(graph)
|
|
198
|
+
labels: List[str] = []
|
|
199
|
+
for node in species_order:
|
|
200
|
+
data = graph.nodes[node]
|
|
201
|
+
label = data.get("label") or data.get("smiles") or node
|
|
202
|
+
labels.append(str(label))
|
|
203
|
+
return labels
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _complex_columns(crn: Any) -> Tuple[List[Complex], List[Tuple[int, int]]]:
|
|
207
|
+
"""Extract the distinct complexes and the reaction arrows between them.
|
|
208
|
+
|
|
209
|
+
:param crn:
|
|
210
|
+
CRN-like input.
|
|
211
|
+
:type crn: Any
|
|
212
|
+
|
|
213
|
+
:return:
|
|
214
|
+
Pair ``(complexes, arrows)`` where ``arrows[j]`` is the
|
|
215
|
+
``(reactant complex index, product complex index)`` of reaction ``j``.
|
|
216
|
+
:rtype: Tuple[List[Complex], List[Tuple[int, int]]]
|
|
217
|
+
"""
|
|
218
|
+
_, _, s_minus, s_plus = build_S_minus_plus(crn)
|
|
219
|
+
labels = tuple(_species_labels(crn))
|
|
220
|
+
|
|
221
|
+
order: List[Complex] = []
|
|
222
|
+
index: Dict[Tuple[int, ...], int] = {}
|
|
223
|
+
|
|
224
|
+
def _intern(column: np.ndarray) -> int:
|
|
225
|
+
key = tuple(
|
|
226
|
+
int(v) if float(v).is_integer() else float(v) # type: ignore[misc]
|
|
227
|
+
for v in column
|
|
228
|
+
)
|
|
229
|
+
if key not in index:
|
|
230
|
+
index[key] = len(order)
|
|
231
|
+
order.append(Complex(coefficients=key, species_labels=labels))
|
|
232
|
+
return index[key]
|
|
233
|
+
|
|
234
|
+
arrows: List[Tuple[int, int]] = []
|
|
235
|
+
for j in range(s_minus.shape[1]):
|
|
236
|
+
arrows.append((_intern(s_minus[:, j]), _intern(s_plus[:, j])))
|
|
237
|
+
|
|
238
|
+
return order, arrows
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def complexes(crn: Any) -> List[Complex]:
|
|
242
|
+
"""Return the distinct complexes of a network, in first-appearance order.
|
|
243
|
+
|
|
244
|
+
The zero complex is included when the network has an inflow or outflow, as
|
|
245
|
+
CRNT requires.
|
|
246
|
+
|
|
247
|
+
:param crn:
|
|
248
|
+
CRN-like input.
|
|
249
|
+
:type crn: Any
|
|
250
|
+
|
|
251
|
+
:return:
|
|
252
|
+
Distinct complexes.
|
|
253
|
+
:rtype: List[Complex]
|
|
254
|
+
|
|
255
|
+
.. rubric:: Example
|
|
256
|
+
|
|
257
|
+
.. code-block:: python
|
|
258
|
+
|
|
259
|
+
crn = SynCRN.from_reaction_strings(["A+B>>C", "C>>A+B"])
|
|
260
|
+
print([str(c) for c in complexes(crn)])
|
|
261
|
+
# ['A + B', 'C']
|
|
262
|
+
"""
|
|
263
|
+
order, _ = _complex_columns(crn)
|
|
264
|
+
return order
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def complex_graph(crn: Any) -> nx.DiGraph:
|
|
268
|
+
"""Return the directed complex graph of a network.
|
|
269
|
+
|
|
270
|
+
Nodes are complex indices carrying a ``complex`` attribute (a
|
|
271
|
+
:class:`Complex`) and a ``label`` attribute (its rendering). One edge is
|
|
272
|
+
added per reaction, from its reactant complex to its product complex.
|
|
273
|
+
|
|
274
|
+
:param crn:
|
|
275
|
+
CRN-like input.
|
|
276
|
+
:type crn: Any
|
|
277
|
+
|
|
278
|
+
:return:
|
|
279
|
+
Complex graph.
|
|
280
|
+
:rtype: networkx.DiGraph
|
|
281
|
+
|
|
282
|
+
.. rubric:: Example
|
|
283
|
+
|
|
284
|
+
.. code-block:: python
|
|
285
|
+
|
|
286
|
+
g = complex_graph(SynCRN.from_reaction_strings(["A>>B", "B>>A"]))
|
|
287
|
+
print(g.number_of_nodes(), g.number_of_edges())
|
|
288
|
+
# 2 2
|
|
289
|
+
"""
|
|
290
|
+
order, arrows = _complex_columns(crn)
|
|
291
|
+
|
|
292
|
+
graph = nx.DiGraph()
|
|
293
|
+
for i, cx in enumerate(order):
|
|
294
|
+
graph.add_node(i, complex=cx, label=str(cx))
|
|
295
|
+
for src, dst in arrows:
|
|
296
|
+
if src == dst:
|
|
297
|
+
continue
|
|
298
|
+
graph.add_edge(src, dst)
|
|
299
|
+
|
|
300
|
+
return graph
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ---------------------------------------------------------------------------
|
|
304
|
+
# Exact linear algebra
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _exact_rank(rows: Sequence[Sequence[Any]]) -> int:
|
|
309
|
+
"""Compute the rank of a matrix exactly, over the rationals.
|
|
310
|
+
|
|
311
|
+
:param rows:
|
|
312
|
+
Matrix rows.
|
|
313
|
+
:type rows: Sequence[Sequence[Any]]
|
|
314
|
+
|
|
315
|
+
:return:
|
|
316
|
+
Exact rank.
|
|
317
|
+
:rtype: int
|
|
318
|
+
"""
|
|
319
|
+
work = [
|
|
320
|
+
[Fraction(str(v)) if not isinstance(v, int) else Fraction(v) for v in row]
|
|
321
|
+
for row in rows
|
|
322
|
+
]
|
|
323
|
+
if not work or not work[0]:
|
|
324
|
+
return 0
|
|
325
|
+
|
|
326
|
+
n_cols = len(work[0])
|
|
327
|
+
rank = 0
|
|
328
|
+
pivot_row = 0
|
|
329
|
+
|
|
330
|
+
for col in range(n_cols):
|
|
331
|
+
pivot = None
|
|
332
|
+
for r in range(pivot_row, len(work)):
|
|
333
|
+
if work[r][col] != 0:
|
|
334
|
+
pivot = r
|
|
335
|
+
break
|
|
336
|
+
if pivot is None:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
work[pivot_row], work[pivot] = work[pivot], work[pivot_row]
|
|
340
|
+
pivot_value = work[pivot_row][col]
|
|
341
|
+
for r in range(pivot_row + 1, len(work)):
|
|
342
|
+
factor = work[r][col] / pivot_value
|
|
343
|
+
if factor == 0:
|
|
344
|
+
continue
|
|
345
|
+
work[r] = [a - factor * b for a, b in zip(work[r], work[pivot_row])]
|
|
346
|
+
|
|
347
|
+
rank += 1
|
|
348
|
+
pivot_row += 1
|
|
349
|
+
if pivot_row == len(work):
|
|
350
|
+
break
|
|
351
|
+
|
|
352
|
+
return rank
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _reaction_vectors(crn: Any) -> List[List[Any]]:
|
|
356
|
+
"""Return the reaction vectors of a network, one per reaction.
|
|
357
|
+
|
|
358
|
+
:param crn:
|
|
359
|
+
CRN-like input.
|
|
360
|
+
:type crn: Any
|
|
361
|
+
|
|
362
|
+
:return:
|
|
363
|
+
Reaction vectors as rows (reactions x species).
|
|
364
|
+
:rtype: List[List[Any]]
|
|
365
|
+
"""
|
|
366
|
+
_, _, s_minus, s_plus = build_S_minus_plus(crn)
|
|
367
|
+
net = s_plus - s_minus
|
|
368
|
+
return [
|
|
369
|
+
[int(v) if float(v).is_integer() else float(v) for v in net[:, j]]
|
|
370
|
+
for j in range(net.shape[1])
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# ---------------------------------------------------------------------------
|
|
375
|
+
# Linkage classes
|
|
376
|
+
# ---------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def linkage_classes(crn: Any) -> List[List[int]]:
|
|
380
|
+
"""Return the linkage classes as sorted lists of complex indices.
|
|
381
|
+
|
|
382
|
+
A linkage class is a connected component of the complex graph, taken
|
|
383
|
+
without regard to arrow direction.
|
|
384
|
+
|
|
385
|
+
:param crn:
|
|
386
|
+
CRN-like input.
|
|
387
|
+
:type crn: Any
|
|
388
|
+
|
|
389
|
+
:return:
|
|
390
|
+
Linkage classes, each a sorted list of complex indices.
|
|
391
|
+
:rtype: List[List[int]]
|
|
392
|
+
|
|
393
|
+
.. rubric:: Example
|
|
394
|
+
|
|
395
|
+
.. code-block:: python
|
|
396
|
+
|
|
397
|
+
crn = SynCRN.from_reaction_strings(["A>>B", "C>>D"])
|
|
398
|
+
print(linkage_classes(crn))
|
|
399
|
+
# [[0, 1], [2, 3]]
|
|
400
|
+
"""
|
|
401
|
+
graph = complex_graph(crn)
|
|
402
|
+
components = [sorted(c) for c in nx.weakly_connected_components(graph)]
|
|
403
|
+
return sorted(components, key=lambda comp: comp[0] if comp else -1)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def strong_linkage_classes(crn: Any) -> List[List[int]]:
|
|
407
|
+
"""Return the strong linkage classes as sorted lists of complex indices.
|
|
408
|
+
|
|
409
|
+
A strong linkage class is a strongly connected component of the complex
|
|
410
|
+
graph: a maximal set of complexes each reachable from every other.
|
|
411
|
+
|
|
412
|
+
:param crn:
|
|
413
|
+
CRN-like input.
|
|
414
|
+
:type crn: Any
|
|
415
|
+
|
|
416
|
+
:return:
|
|
417
|
+
Strong linkage classes, each a sorted list of complex indices.
|
|
418
|
+
:rtype: List[List[int]]
|
|
419
|
+
"""
|
|
420
|
+
graph = complex_graph(crn)
|
|
421
|
+
components = [sorted(c) for c in nx.strongly_connected_components(graph)]
|
|
422
|
+
return sorted(components, key=lambda comp: comp[0] if comp else -1)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def terminal_strong_linkage_classes(crn: Any) -> List[List[int]]:
|
|
426
|
+
"""Return the terminal strong linkage classes.
|
|
427
|
+
|
|
428
|
+
A strong linkage class is *terminal* when no reaction leads out of it: once
|
|
429
|
+
the network's complex graph enters it, it cannot leave.
|
|
430
|
+
|
|
431
|
+
:param crn:
|
|
432
|
+
CRN-like input.
|
|
433
|
+
:type crn: Any
|
|
434
|
+
|
|
435
|
+
:return:
|
|
436
|
+
Terminal strong linkage classes, each a sorted list of complex indices.
|
|
437
|
+
:rtype: List[List[int]]
|
|
438
|
+
"""
|
|
439
|
+
graph = complex_graph(crn)
|
|
440
|
+
terminal: List[List[int]] = []
|
|
441
|
+
|
|
442
|
+
for component in nx.strongly_connected_components(graph):
|
|
443
|
+
leaves = any(
|
|
444
|
+
successor not in component
|
|
445
|
+
for node in component
|
|
446
|
+
for successor in graph.successors(node)
|
|
447
|
+
)
|
|
448
|
+
if not leaves:
|
|
449
|
+
terminal.append(sorted(component))
|
|
450
|
+
|
|
451
|
+
return sorted(terminal, key=lambda comp: comp[0] if comp else -1)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def is_weakly_reversible(crn: Any) -> bool:
|
|
455
|
+
"""Return whether every linkage class is strongly connected.
|
|
456
|
+
|
|
457
|
+
Weak reversibility means that whenever complex ``y`` can reach complex
|
|
458
|
+
``y'`` through a sequence of reactions, ``y'`` can reach ``y`` as well. It is
|
|
459
|
+
the hypothesis both classical deficiency theorems turn on.
|
|
460
|
+
|
|
461
|
+
:param crn:
|
|
462
|
+
CRN-like input.
|
|
463
|
+
:type crn: Any
|
|
464
|
+
|
|
465
|
+
:return:
|
|
466
|
+
``True`` when the network is weakly reversible.
|
|
467
|
+
:rtype: bool
|
|
468
|
+
|
|
469
|
+
.. rubric:: Example
|
|
470
|
+
|
|
471
|
+
.. code-block:: python
|
|
472
|
+
|
|
473
|
+
is_weakly_reversible(SynCRN.from_reaction_strings(["A>>B", "B>>A"]))
|
|
474
|
+
# True
|
|
475
|
+
is_weakly_reversible(SynCRN.from_reaction_strings(["A>>B"]))
|
|
476
|
+
# False
|
|
477
|
+
"""
|
|
478
|
+
graph = complex_graph(crn)
|
|
479
|
+
if graph.number_of_nodes() == 0:
|
|
480
|
+
return True
|
|
481
|
+
|
|
482
|
+
strong = {frozenset(c) for c in nx.strongly_connected_components(graph)}
|
|
483
|
+
weak = {frozenset(c) for c in nx.weakly_connected_components(graph)}
|
|
484
|
+
return strong == weak
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def is_reversible(crn: Any) -> bool:
|
|
488
|
+
"""Return whether every reaction arrow has a matching reverse arrow.
|
|
489
|
+
|
|
490
|
+
This is the strict notion: ``y -> y'`` requires ``y' -> y`` to be present as
|
|
491
|
+
a reaction. Reversibility implies weak reversibility, not the converse.
|
|
492
|
+
|
|
493
|
+
:param crn:
|
|
494
|
+
CRN-like input.
|
|
495
|
+
:type crn: Any
|
|
496
|
+
|
|
497
|
+
:return:
|
|
498
|
+
``True`` when the network is reversible.
|
|
499
|
+
:rtype: bool
|
|
500
|
+
"""
|
|
501
|
+
_, arrows = _complex_columns(crn)
|
|
502
|
+
arrow_set = {(src, dst) for src, dst in arrows if src != dst}
|
|
503
|
+
return all((dst, src) in arrow_set for src, dst in arrow_set)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# ---------------------------------------------------------------------------
|
|
507
|
+
# Deficiency
|
|
508
|
+
# ---------------------------------------------------------------------------
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def deficiency(crn: Any) -> int:
|
|
512
|
+
"""Compute the deficiency ``delta = n - l - s`` of a network.
|
|
513
|
+
|
|
514
|
+
``n`` counts distinct complexes, ``l`` counts linkage classes and ``s`` is
|
|
515
|
+
the exact rank of the stoichiometric matrix. The result is always
|
|
516
|
+
non-negative.
|
|
517
|
+
|
|
518
|
+
:param crn:
|
|
519
|
+
CRN-like input.
|
|
520
|
+
:type crn: Any
|
|
521
|
+
|
|
522
|
+
:return:
|
|
523
|
+
Network deficiency.
|
|
524
|
+
:rtype: int
|
|
525
|
+
|
|
526
|
+
.. rubric:: Example
|
|
527
|
+
|
|
528
|
+
.. code-block:: python
|
|
529
|
+
|
|
530
|
+
deficiency(SynCRN.from_reaction_strings(["A>>B", "B>>A"]))
|
|
531
|
+
# 0
|
|
532
|
+
deficiency(SynCRN.from_reaction_strings(["A+B>>C", "C>>A", "C>>B"]))
|
|
533
|
+
# 1
|
|
534
|
+
"""
|
|
535
|
+
order, _ = _complex_columns(crn)
|
|
536
|
+
n_complexes = len(order)
|
|
537
|
+
n_linkage = len(linkage_classes(crn))
|
|
538
|
+
rank = _exact_rank(_reaction_vectors(crn))
|
|
539
|
+
return n_complexes - n_linkage - rank
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def linkage_class_deficiencies(crn: Any) -> List[int]:
|
|
543
|
+
"""Compute the deficiency of each linkage class.
|
|
544
|
+
|
|
545
|
+
For a linkage class with ``n_l`` complexes and reaction-vector rank ``s_l``
|
|
546
|
+
the linkage-class deficiency is ``n_l - 1 - s_l``. Their sum never exceeds
|
|
547
|
+
the network deficiency; equality is one of the Deficiency One Theorem's
|
|
548
|
+
hypotheses.
|
|
549
|
+
|
|
550
|
+
:param crn:
|
|
551
|
+
CRN-like input.
|
|
552
|
+
:type crn: Any
|
|
553
|
+
|
|
554
|
+
:return:
|
|
555
|
+
One deficiency per linkage class, in the order of
|
|
556
|
+
:func:`linkage_classes`.
|
|
557
|
+
:rtype: List[int]
|
|
558
|
+
"""
|
|
559
|
+
order, arrows = _complex_columns(crn)
|
|
560
|
+
if not order:
|
|
561
|
+
return []
|
|
562
|
+
|
|
563
|
+
vectors = _reaction_vectors(crn)
|
|
564
|
+
classes = linkage_classes(crn)
|
|
565
|
+
member_of = {node: i for i, comp in enumerate(classes) for node in comp}
|
|
566
|
+
|
|
567
|
+
per_class: List[List[List[Any]]] = [[] for _ in classes]
|
|
568
|
+
for j, (src, _dst) in enumerate(arrows):
|
|
569
|
+
per_class[member_of[src]].append(vectors[j])
|
|
570
|
+
|
|
571
|
+
return [len(comp) - 1 - _exact_rank(rows) for comp, rows in zip(classes, per_class)]
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
# ---------------------------------------------------------------------------
|
|
575
|
+
# Theorem verdicts
|
|
576
|
+
# ---------------------------------------------------------------------------
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def is_deficiency_zero_applicable(crn: Any) -> bool:
|
|
580
|
+
"""Return whether the Deficiency Zero Theorem applies to this network.
|
|
581
|
+
|
|
582
|
+
The theorem applies exactly when the deficiency is zero; weak reversibility
|
|
583
|
+
then decides *which* of its two conclusions holds. Use
|
|
584
|
+
:func:`deficiency_zero_verdict` for the conclusion itself.
|
|
585
|
+
|
|
586
|
+
:param crn:
|
|
587
|
+
CRN-like input.
|
|
588
|
+
:type crn: Any
|
|
589
|
+
|
|
590
|
+
:return:
|
|
591
|
+
``True`` when the network has deficiency zero.
|
|
592
|
+
:rtype: bool
|
|
593
|
+
"""
|
|
594
|
+
return deficiency(crn) == 0
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def deficiency_zero_verdict(crn: Any) -> Dict[str, Any]:
|
|
598
|
+
"""Apply the Deficiency Zero Theorem and report its conclusion.
|
|
599
|
+
|
|
600
|
+
:param crn:
|
|
601
|
+
CRN-like input.
|
|
602
|
+
:type crn: Any
|
|
603
|
+
|
|
604
|
+
:return:
|
|
605
|
+
Mapping with ``applicable``, ``deficiency``, ``weakly_reversible``,
|
|
606
|
+
``conclusion`` (one of ``"unique_stable_equilibrium"``,
|
|
607
|
+
``"no_positive_equilibrium"`` or ``"inconclusive"``) and a
|
|
608
|
+
human-readable ``statement``.
|
|
609
|
+
:rtype: Dict[str, Any]
|
|
610
|
+
|
|
611
|
+
.. rubric:: Example
|
|
612
|
+
|
|
613
|
+
.. code-block:: python
|
|
614
|
+
|
|
615
|
+
crn = SynCRN.from_reaction_strings(["A>>B", "B>>A"])
|
|
616
|
+
print(deficiency_zero_verdict(crn)["conclusion"])
|
|
617
|
+
# 'unique_stable_equilibrium'
|
|
618
|
+
"""
|
|
619
|
+
delta = deficiency(crn)
|
|
620
|
+
weakly_reversible = is_weakly_reversible(crn)
|
|
621
|
+
|
|
622
|
+
return _deficiency_zero_values(delta, weakly_reversible)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _deficiency_zero_values(delta, weakly_reversible):
|
|
626
|
+
if delta != 0:
|
|
627
|
+
return {
|
|
628
|
+
"applicable": False,
|
|
629
|
+
"deficiency": delta,
|
|
630
|
+
"weakly_reversible": weakly_reversible,
|
|
631
|
+
"conclusion": "inconclusive",
|
|
632
|
+
"statement": (
|
|
633
|
+
f"Deficiency is {delta}, not zero; the Deficiency Zero Theorem "
|
|
634
|
+
"says nothing about this network."
|
|
635
|
+
),
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if weakly_reversible:
|
|
639
|
+
return {
|
|
640
|
+
"applicable": True,
|
|
641
|
+
"deficiency": 0,
|
|
642
|
+
"weakly_reversible": True,
|
|
643
|
+
"conclusion": "unique_stable_equilibrium",
|
|
644
|
+
"statement": (
|
|
645
|
+
"Deficiency zero and weakly reversible: for every choice of "
|
|
646
|
+
"positive mass-action rate constants there is exactly one "
|
|
647
|
+
"positive steady state in each positive stoichiometric "
|
|
648
|
+
"compatibility class, it is locally asymptotically stable, and "
|
|
649
|
+
"there is no nontrivial periodic orbit."
|
|
650
|
+
),
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return {
|
|
654
|
+
"applicable": True,
|
|
655
|
+
"deficiency": 0,
|
|
656
|
+
"weakly_reversible": False,
|
|
657
|
+
"conclusion": "no_positive_equilibrium",
|
|
658
|
+
"statement": (
|
|
659
|
+
"Deficiency zero but not weakly reversible: no positive steady "
|
|
660
|
+
"state and no cyclic composition trajectory through a positive "
|
|
661
|
+
"composition exists, for any positive mass-action rate constants."
|
|
662
|
+
),
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def is_deficiency_one_applicable(crn: Any) -> bool:
|
|
667
|
+
"""Return whether the Deficiency One Theorem's hypotheses hold.
|
|
668
|
+
|
|
669
|
+
The three hypotheses are: every linkage-class deficiency is at most one,
|
|
670
|
+
the linkage-class deficiencies sum to the network deficiency, and each
|
|
671
|
+
linkage class contains exactly one terminal strong linkage class.
|
|
672
|
+
|
|
673
|
+
:param crn:
|
|
674
|
+
CRN-like input.
|
|
675
|
+
:type crn: Any
|
|
676
|
+
|
|
677
|
+
:return:
|
|
678
|
+
``True`` when all three hypotheses hold.
|
|
679
|
+
:rtype: bool
|
|
680
|
+
"""
|
|
681
|
+
return bool(deficiency_one_verdict(crn)["applicable"])
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def deficiency_one_verdict(crn: Any) -> Dict[str, Any]:
|
|
685
|
+
"""Apply the Deficiency One Theorem and report its conclusion.
|
|
686
|
+
|
|
687
|
+
:param crn:
|
|
688
|
+
CRN-like input.
|
|
689
|
+
:type crn: Any
|
|
690
|
+
|
|
691
|
+
:return:
|
|
692
|
+
Mapping with ``applicable``, the three hypothesis flags
|
|
693
|
+
(``all_linkage_deficiencies_at_most_one``, ``deficiencies_sum_to_total``,
|
|
694
|
+
``one_terminal_class_per_linkage_class``), ``deficiency``,
|
|
695
|
+
``linkage_class_deficiencies``, ``weakly_reversible``, ``conclusion``
|
|
696
|
+
(one of ``"exactly_one_equilibrium"``, ``"at_most_one_equilibrium"`` or
|
|
697
|
+
``"inconclusive"``) and a human-readable ``statement``.
|
|
698
|
+
:rtype: Dict[str, Any]
|
|
699
|
+
|
|
700
|
+
.. rubric:: Example
|
|
701
|
+
|
|
702
|
+
.. code-block:: python
|
|
703
|
+
|
|
704
|
+
crn = SynCRN.from_reaction_strings(["A+B>>C", "C>>A+B", "C>>A", "A>>C"])
|
|
705
|
+
print(deficiency_one_verdict(crn)["conclusion"])
|
|
706
|
+
"""
|
|
707
|
+
delta = deficiency(crn)
|
|
708
|
+
per_class = linkage_class_deficiencies(crn)
|
|
709
|
+
weakly_reversible = is_weakly_reversible(crn)
|
|
710
|
+
|
|
711
|
+
classes = linkage_classes(crn)
|
|
712
|
+
terminal = terminal_strong_linkage_classes(crn)
|
|
713
|
+
terminal_counts = []
|
|
714
|
+
for comp in classes:
|
|
715
|
+
members = set(comp)
|
|
716
|
+
terminal_counts.append(sum(1 for t in terminal if set(t) <= members))
|
|
717
|
+
|
|
718
|
+
return _deficiency_one_values(delta, per_class, weakly_reversible, terminal_counts)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _deficiency_one_values(delta, per_class, weakly_reversible, terminal_counts):
|
|
722
|
+
hypotheses = {
|
|
723
|
+
"all_linkage_deficiencies_at_most_one": all(d <= 1 for d in per_class),
|
|
724
|
+
"deficiencies_sum_to_total": sum(per_class) == delta,
|
|
725
|
+
"one_terminal_class_per_linkage_class": all(c == 1 for c in terminal_counts),
|
|
726
|
+
}
|
|
727
|
+
applicable = all(hypotheses.values())
|
|
728
|
+
|
|
729
|
+
result: Dict[str, Any] = {
|
|
730
|
+
"applicable": applicable,
|
|
731
|
+
"deficiency": delta,
|
|
732
|
+
"linkage_class_deficiencies": per_class,
|
|
733
|
+
"weakly_reversible": weakly_reversible,
|
|
734
|
+
**hypotheses,
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if not applicable:
|
|
738
|
+
failed = [name for name, ok in hypotheses.items() if not ok]
|
|
739
|
+
result["conclusion"] = "inconclusive"
|
|
740
|
+
result["statement"] = (
|
|
741
|
+
"The Deficiency One Theorem does not apply; failed hypotheses: "
|
|
742
|
+
+ ", ".join(failed)
|
|
743
|
+
+ "."
|
|
744
|
+
)
|
|
745
|
+
return result
|
|
746
|
+
|
|
747
|
+
if weakly_reversible:
|
|
748
|
+
result["conclusion"] = "exactly_one_equilibrium"
|
|
749
|
+
result["statement"] = (
|
|
750
|
+
"Deficiency One hypotheses hold and the network is weakly "
|
|
751
|
+
"reversible: for every choice of positive mass-action rate "
|
|
752
|
+
"constants there is exactly one positive steady state in each "
|
|
753
|
+
"positive stoichiometric compatibility class."
|
|
754
|
+
)
|
|
755
|
+
return result
|
|
756
|
+
|
|
757
|
+
result["conclusion"] = "at_most_one_equilibrium"
|
|
758
|
+
result["statement"] = (
|
|
759
|
+
"Deficiency One hypotheses hold: for every choice of positive "
|
|
760
|
+
"mass-action rate constants there is at most one positive steady state "
|
|
761
|
+
"in each positive stoichiometric compatibility class."
|
|
762
|
+
)
|
|
763
|
+
return result
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
# ---------------------------------------------------------------------------
|
|
767
|
+
# Summary
|
|
768
|
+
# ---------------------------------------------------------------------------
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
@dataclass
|
|
772
|
+
class CRNTSummary:
|
|
773
|
+
"""Structural CRNT report for one network.
|
|
774
|
+
|
|
775
|
+
:param n_species:
|
|
776
|
+
Number of species.
|
|
777
|
+
:type n_species: int
|
|
778
|
+
|
|
779
|
+
:param n_reactions:
|
|
780
|
+
Number of reactions.
|
|
781
|
+
:type n_reactions: int
|
|
782
|
+
|
|
783
|
+
:param n_complexes:
|
|
784
|
+
Number of distinct complexes.
|
|
785
|
+
:type n_complexes: int
|
|
786
|
+
|
|
787
|
+
:param n_linkage_classes:
|
|
788
|
+
Number of linkage classes.
|
|
789
|
+
:type n_linkage_classes: int
|
|
790
|
+
|
|
791
|
+
:param n_terminal_strong_linkage_classes:
|
|
792
|
+
Number of terminal strong linkage classes.
|
|
793
|
+
:type n_terminal_strong_linkage_classes: int
|
|
794
|
+
|
|
795
|
+
:param rank:
|
|
796
|
+
Rank of the stoichiometric matrix.
|
|
797
|
+
:type rank: int
|
|
798
|
+
|
|
799
|
+
:param deficiency:
|
|
800
|
+
Network deficiency ``n - l - s``.
|
|
801
|
+
:type deficiency: int
|
|
802
|
+
|
|
803
|
+
:param linkage_class_deficiencies:
|
|
804
|
+
Deficiency of each linkage class.
|
|
805
|
+
:type linkage_class_deficiencies: List[int]
|
|
806
|
+
|
|
807
|
+
:param is_weakly_reversible:
|
|
808
|
+
Whether every linkage class is strongly connected.
|
|
809
|
+
:type is_weakly_reversible: bool
|
|
810
|
+
|
|
811
|
+
:param is_reversible:
|
|
812
|
+
Whether every reaction arrow has a matching reverse arrow.
|
|
813
|
+
:type is_reversible: bool
|
|
814
|
+
|
|
815
|
+
:param deficiency_zero:
|
|
816
|
+
Verdict from :func:`deficiency_zero_verdict`.
|
|
817
|
+
:type deficiency_zero: Dict[str, Any]
|
|
818
|
+
|
|
819
|
+
:param deficiency_one:
|
|
820
|
+
Verdict from :func:`deficiency_one_verdict`.
|
|
821
|
+
:type deficiency_one: Dict[str, Any]
|
|
822
|
+
|
|
823
|
+
:param complexes:
|
|
824
|
+
Rendered complexes in canonical order.
|
|
825
|
+
:type complexes: List[str]
|
|
826
|
+
"""
|
|
827
|
+
|
|
828
|
+
n_species: int
|
|
829
|
+
n_reactions: int
|
|
830
|
+
n_complexes: int
|
|
831
|
+
n_linkage_classes: int
|
|
832
|
+
n_terminal_strong_linkage_classes: int
|
|
833
|
+
rank: int
|
|
834
|
+
deficiency: int
|
|
835
|
+
linkage_class_deficiencies: List[int]
|
|
836
|
+
is_weakly_reversible: bool
|
|
837
|
+
is_reversible: bool
|
|
838
|
+
deficiency_zero: Dict[str, Any] = field(default_factory=dict)
|
|
839
|
+
deficiency_one: Dict[str, Any] = field(default_factory=dict)
|
|
840
|
+
complexes: List[str] = field(default_factory=list)
|
|
841
|
+
|
|
842
|
+
@classmethod
|
|
843
|
+
@_reuse_matrices
|
|
844
|
+
def from_crn(cls, crn: Any) -> "CRNTSummary":
|
|
845
|
+
"""Build a CRNT summary for a network.
|
|
846
|
+
|
|
847
|
+
:param crn:
|
|
848
|
+
CRN-like input.
|
|
849
|
+
:type crn: Any
|
|
850
|
+
|
|
851
|
+
:return:
|
|
852
|
+
Populated summary.
|
|
853
|
+
:rtype: CRNTSummary
|
|
854
|
+
"""
|
|
855
|
+
order, arrows = _complex_columns(crn)
|
|
856
|
+
labels = _species_labels(crn)
|
|
857
|
+
classes = linkage_classes(crn)
|
|
858
|
+
rank = _exact_rank(_reaction_vectors(crn))
|
|
859
|
+
|
|
860
|
+
return cls(
|
|
861
|
+
n_species=len(labels),
|
|
862
|
+
n_reactions=len(arrows),
|
|
863
|
+
n_complexes=len(order),
|
|
864
|
+
n_linkage_classes=len(classes),
|
|
865
|
+
n_terminal_strong_linkage_classes=len(terminal_strong_linkage_classes(crn)),
|
|
866
|
+
rank=rank,
|
|
867
|
+
deficiency=len(order) - len(classes) - rank,
|
|
868
|
+
linkage_class_deficiencies=linkage_class_deficiencies(crn),
|
|
869
|
+
is_weakly_reversible=is_weakly_reversible(crn),
|
|
870
|
+
is_reversible=is_reversible(crn),
|
|
871
|
+
deficiency_zero=deficiency_zero_verdict(crn),
|
|
872
|
+
deficiency_one=deficiency_one_verdict(crn),
|
|
873
|
+
complexes=[str(c) for c in order],
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
877
|
+
"""Return the summary as a plain dictionary.
|
|
878
|
+
|
|
879
|
+
:return:
|
|
880
|
+
Serializable mapping of every field.
|
|
881
|
+
:rtype: Dict[str, Any]
|
|
882
|
+
"""
|
|
883
|
+
return {
|
|
884
|
+
"n_species": self.n_species,
|
|
885
|
+
"n_reactions": self.n_reactions,
|
|
886
|
+
"n_complexes": self.n_complexes,
|
|
887
|
+
"n_linkage_classes": self.n_linkage_classes,
|
|
888
|
+
"n_terminal_strong_linkage_classes": (
|
|
889
|
+
self.n_terminal_strong_linkage_classes
|
|
890
|
+
),
|
|
891
|
+
"rank": self.rank,
|
|
892
|
+
"deficiency": self.deficiency,
|
|
893
|
+
"linkage_class_deficiencies": list(self.linkage_class_deficiencies),
|
|
894
|
+
"is_weakly_reversible": self.is_weakly_reversible,
|
|
895
|
+
"is_reversible": self.is_reversible,
|
|
896
|
+
"deficiency_zero": dict(self.deficiency_zero),
|
|
897
|
+
"deficiency_one": dict(self.deficiency_one),
|
|
898
|
+
"complexes": list(self.complexes),
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
def __str__(self) -> str:
|
|
902
|
+
"""Return a compact multi-line report.
|
|
903
|
+
|
|
904
|
+
:return:
|
|
905
|
+
Human-readable summary.
|
|
906
|
+
:rtype: str
|
|
907
|
+
"""
|
|
908
|
+
lines = [
|
|
909
|
+
f"CRNT summary: {self.n_species} species, "
|
|
910
|
+
f"{self.n_reactions} reactions, {self.n_complexes} complexes",
|
|
911
|
+
f" linkage classes n={self.n_linkage_classes} "
|
|
912
|
+
f"(terminal strong: {self.n_terminal_strong_linkage_classes})",
|
|
913
|
+
f" rank s={self.rank}, deficiency delta={self.deficiency}",
|
|
914
|
+
f" weakly reversible: {self.is_weakly_reversible}, "
|
|
915
|
+
f"reversible: {self.is_reversible}",
|
|
916
|
+
]
|
|
917
|
+
if self.deficiency_zero.get("applicable"):
|
|
918
|
+
lines.append(f" deficiency-zero: {self.deficiency_zero['conclusion']}")
|
|
919
|
+
if self.deficiency_one.get("applicable"):
|
|
920
|
+
lines.append(f" deficiency-one: {self.deficiency_one['conclusion']}")
|
|
921
|
+
return "\n".join(lines)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def crnt_summary(
|
|
925
|
+
crn: Any, *, backend="auto", library_path=None, time_limit=30.0
|
|
926
|
+
) -> CRNTSummary:
|
|
927
|
+
"""Compute the full structural CRNT report for a network.
|
|
928
|
+
|
|
929
|
+
:param crn:
|
|
930
|
+
CRN-like input.
|
|
931
|
+
:type crn: Any
|
|
932
|
+
|
|
933
|
+
:return:
|
|
934
|
+
Populated summary.
|
|
935
|
+
:rtype: CRNTSummary
|
|
936
|
+
|
|
937
|
+
.. rubric:: Example
|
|
938
|
+
|
|
939
|
+
.. code-block:: python
|
|
940
|
+
|
|
941
|
+
crn = SynCRN.from_reaction_strings(["A>>B", "B>>A", "B>>C", "C>>B"])
|
|
942
|
+
print(crnt_summary(crn))
|
|
943
|
+
"""
|
|
944
|
+
from ..backend import resolve_backend
|
|
945
|
+
|
|
946
|
+
backend, library_path = resolve_backend(backend, library_path)
|
|
947
|
+
if backend == "cpp":
|
|
948
|
+
from ..native_analysis import crnt_summary_native
|
|
949
|
+
|
|
950
|
+
return crnt_summary_native(
|
|
951
|
+
crn, library_path=library_path, time_limit=time_limit
|
|
952
|
+
)
|
|
953
|
+
return CRNTSummary.from_crn(crn)
|