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/IO/sbml.py
ADDED
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
"""SBML import and export for :class:`~synkit.CRN.Structure.syncrn.SynCRN`.
|
|
2
|
+
|
|
3
|
+
SBML (Systems Biology Markup Language) is the interchange format of the
|
|
4
|
+
existing CRN ecosystem — ``crnpy``, CRNT4SBML, CoNtRol, COPASI and the BioModels
|
|
5
|
+
repository all read or write it. This module is the adapter, so a network built
|
|
6
|
+
by rule expansion here can be analysed there and vice versa.
|
|
7
|
+
|
|
8
|
+
The reader and writer are self-contained: they use only
|
|
9
|
+
:mod:`xml.etree.ElementTree` and target **SBML Level 3 Version 2 core**, which
|
|
10
|
+
is all a reaction network needs (compartments, species, reactions with
|
|
11
|
+
stoichiometry). No kinetic law is written, because a ``SynCRN`` carries none;
|
|
12
|
+
structural analysis does not need one. Files that *do* carry kinetic laws are
|
|
13
|
+
read fine — the laws are simply ignored.
|
|
14
|
+
|
|
15
|
+
Conversion policies:
|
|
16
|
+
|
|
17
|
+
- **Reversible reactions.** SBML marks a reaction ``reversible="true"``; a
|
|
18
|
+
``SynCRN`` reaction is always directed. On import, a reversible reaction is
|
|
19
|
+
split into a forward and a backward reaction by default (``expand_reversible``),
|
|
20
|
+
which is what Chemical Reaction Network Theory expects.
|
|
21
|
+
- **Identifiers.** SBML ids must match ``[A-Za-z_][A-Za-z0-9_]*``. Species
|
|
22
|
+
labels are chemistry — SMILES strings, KEGG ids — and usually do not. The
|
|
23
|
+
writer therefore uses the internal ``SynCRN`` ids (``s_1``, ``r_1``) as SBML
|
|
24
|
+
ids and keeps the chemistry in the ``name`` attribute and a SynKit
|
|
25
|
+
annotation, so nothing is lost on a round-trip.
|
|
26
|
+
|
|
27
|
+
.. rubric:: Example
|
|
28
|
+
|
|
29
|
+
.. code-block:: python
|
|
30
|
+
|
|
31
|
+
from synkit.CRN import SynCRN
|
|
32
|
+
from synkit.CRN.IO import crn_from_sbml, crn_to_sbml
|
|
33
|
+
|
|
34
|
+
crn = SynCRN.from_reaction_strings(["2A>>B", "B>>2A"])
|
|
35
|
+
xml = crn_to_sbml(crn)
|
|
36
|
+
back = crn_from_sbml(xml)
|
|
37
|
+
print(back.to_equations(species="label", include_id=False))
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import re
|
|
43
|
+
import warnings
|
|
44
|
+
import xml.etree.ElementTree as ET
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
47
|
+
|
|
48
|
+
import networkx as nx
|
|
49
|
+
|
|
50
|
+
from ..Structure.syncrn import SynCRN
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"SBML_NS",
|
|
54
|
+
"SYNKIT_NS",
|
|
55
|
+
"crn_from_sbml",
|
|
56
|
+
"crn_to_sbml",
|
|
57
|
+
"read_sbml",
|
|
58
|
+
"write_sbml",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
#: Namespace of the SBML Level 3 Version 2 core specification.
|
|
62
|
+
SBML_NS = "http://www.sbml.org/sbml/level3/version2/core"
|
|
63
|
+
|
|
64
|
+
#: Namespace used for SynKit-specific annotations (SMILES, source ids).
|
|
65
|
+
SYNKIT_NS = "https://github.com/TieuLongPhan/SynKit"
|
|
66
|
+
|
|
67
|
+
_SID_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
68
|
+
_SID_INVALID_RE = re.compile(r"[^A-Za-z0-9_]")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Helpers
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _localname(tag: str) -> str:
|
|
77
|
+
"""Strip the XML namespace from a tag.
|
|
78
|
+
|
|
79
|
+
SBML files in the wild use several namespace URIs across levels and
|
|
80
|
+
versions; matching on the local name keeps the reader version-tolerant.
|
|
81
|
+
|
|
82
|
+
:param tag:
|
|
83
|
+
Fully qualified tag such as ``"{ns}species"``.
|
|
84
|
+
:type tag: str
|
|
85
|
+
|
|
86
|
+
:return:
|
|
87
|
+
Local tag name.
|
|
88
|
+
:rtype: str
|
|
89
|
+
"""
|
|
90
|
+
return tag.rsplit("}", 1)[-1]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _find_children(element: ET.Element, name: str) -> List[ET.Element]:
|
|
94
|
+
"""Find the direct children with a given local name.
|
|
95
|
+
|
|
96
|
+
Direct children only: an ``<annotation>`` block may legitimately contain
|
|
97
|
+
elements whose local name collides with an SBML one, and those must not be
|
|
98
|
+
mistaken for model content.
|
|
99
|
+
|
|
100
|
+
:param element:
|
|
101
|
+
Parent element.
|
|
102
|
+
:type element: xml.etree.ElementTree.Element
|
|
103
|
+
|
|
104
|
+
:param name:
|
|
105
|
+
Local tag name to match.
|
|
106
|
+
:type name: str
|
|
107
|
+
|
|
108
|
+
:return:
|
|
109
|
+
Matching children in document order.
|
|
110
|
+
:rtype: List[xml.etree.ElementTree.Element]
|
|
111
|
+
"""
|
|
112
|
+
return [child for child in element if _localname(child.tag) == name]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _find_descendants(element: ET.Element, name: str) -> List[ET.Element]:
|
|
116
|
+
"""Find all descendants with a given local name.
|
|
117
|
+
|
|
118
|
+
:param element:
|
|
119
|
+
Element to search below.
|
|
120
|
+
:type element: xml.etree.ElementTree.Element
|
|
121
|
+
|
|
122
|
+
:param name:
|
|
123
|
+
Local tag name to match.
|
|
124
|
+
:type name: str
|
|
125
|
+
|
|
126
|
+
:return:
|
|
127
|
+
Matching descendants in document order.
|
|
128
|
+
:rtype: List[xml.etree.ElementTree.Element]
|
|
129
|
+
"""
|
|
130
|
+
return [child for child in element.iter() if _localname(child.tag) == name]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _find_child(element: ET.Element, name: str) -> Optional[ET.Element]:
|
|
134
|
+
"""Find the first direct child with a given local name.
|
|
135
|
+
|
|
136
|
+
:param element:
|
|
137
|
+
Parent element.
|
|
138
|
+
:type element: xml.etree.ElementTree.Element
|
|
139
|
+
|
|
140
|
+
:param name:
|
|
141
|
+
Local tag name to match.
|
|
142
|
+
:type name: str
|
|
143
|
+
|
|
144
|
+
:return:
|
|
145
|
+
The child, or ``None`` when absent.
|
|
146
|
+
:rtype: Optional[xml.etree.ElementTree.Element]
|
|
147
|
+
"""
|
|
148
|
+
for child in element:
|
|
149
|
+
if _localname(child.tag) == name:
|
|
150
|
+
return child
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _as_sid(value: Any, *, fallback: str) -> str:
|
|
155
|
+
"""Coerce a value into a valid SBML ``SId``.
|
|
156
|
+
|
|
157
|
+
:param value:
|
|
158
|
+
Candidate identifier.
|
|
159
|
+
:type value: Any
|
|
160
|
+
|
|
161
|
+
:param fallback:
|
|
162
|
+
Identifier used when ``value`` cannot be sanitized into a valid SId.
|
|
163
|
+
:type fallback: str
|
|
164
|
+
|
|
165
|
+
:return:
|
|
166
|
+
Valid SBML identifier.
|
|
167
|
+
:rtype: str
|
|
168
|
+
"""
|
|
169
|
+
text = str(value)
|
|
170
|
+
if _SID_RE.match(text):
|
|
171
|
+
return text
|
|
172
|
+
|
|
173
|
+
cleaned = _SID_INVALID_RE.sub("_", text)
|
|
174
|
+
if cleaned and not cleaned[0].isalpha() and cleaned[0] != "_":
|
|
175
|
+
cleaned = f"_{cleaned}"
|
|
176
|
+
return cleaned if _SID_RE.match(cleaned) else fallback
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _format_stoich(value: Any) -> str:
|
|
180
|
+
"""Render a stoichiometric coefficient for an SBML attribute.
|
|
181
|
+
|
|
182
|
+
:param value:
|
|
183
|
+
Coefficient value.
|
|
184
|
+
:type value: Any
|
|
185
|
+
|
|
186
|
+
:return:
|
|
187
|
+
String form without a redundant ``.0`` suffix.
|
|
188
|
+
:rtype: str
|
|
189
|
+
"""
|
|
190
|
+
number = float(value)
|
|
191
|
+
return str(int(number)) if number.is_integer() else repr(number)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _parse_stoich(value: Optional[str]) -> float:
|
|
195
|
+
"""Parse an SBML stoichiometry attribute, defaulting to 1.
|
|
196
|
+
|
|
197
|
+
:param value:
|
|
198
|
+
Raw attribute text, possibly ``None``.
|
|
199
|
+
:type value: Optional[str]
|
|
200
|
+
|
|
201
|
+
:return:
|
|
202
|
+
Stoichiometric coefficient.
|
|
203
|
+
:rtype: float
|
|
204
|
+
"""
|
|
205
|
+
if value is None or value == "":
|
|
206
|
+
return 1.0
|
|
207
|
+
try:
|
|
208
|
+
return float(value)
|
|
209
|
+
except ValueError:
|
|
210
|
+
return 1.0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _is_true(value: Optional[str]) -> bool:
|
|
214
|
+
"""Interpret an SBML boolean attribute.
|
|
215
|
+
|
|
216
|
+
:param value:
|
|
217
|
+
Raw attribute text, possibly ``None``.
|
|
218
|
+
:type value: Optional[str]
|
|
219
|
+
|
|
220
|
+
:return:
|
|
221
|
+
``True`` when the attribute spells a true value.
|
|
222
|
+
:rtype: bool
|
|
223
|
+
"""
|
|
224
|
+
return str(value).strip().lower() in {"true", "1"}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
# Export
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _species_element(
|
|
233
|
+
parent: ET.Element,
|
|
234
|
+
*,
|
|
235
|
+
sid: str,
|
|
236
|
+
species: Any,
|
|
237
|
+
compartment_id: str,
|
|
238
|
+
) -> None:
|
|
239
|
+
"""Append one ``<species>`` element for a SynCRN species record.
|
|
240
|
+
|
|
241
|
+
:param parent:
|
|
242
|
+
The ``<listOfSpecies>`` element.
|
|
243
|
+
:type parent: xml.etree.ElementTree.Element
|
|
244
|
+
|
|
245
|
+
:param sid:
|
|
246
|
+
SBML species id.
|
|
247
|
+
:type sid: str
|
|
248
|
+
|
|
249
|
+
:param species:
|
|
250
|
+
SynCRN species record.
|
|
251
|
+
:type species: Any
|
|
252
|
+
|
|
253
|
+
:param compartment_id:
|
|
254
|
+
Id of the compartment the species belongs to.
|
|
255
|
+
:type compartment_id: str
|
|
256
|
+
|
|
257
|
+
:return:
|
|
258
|
+
``None``. The element is appended in place.
|
|
259
|
+
:rtype: None
|
|
260
|
+
"""
|
|
261
|
+
element = ET.SubElement(
|
|
262
|
+
parent,
|
|
263
|
+
"species",
|
|
264
|
+
{
|
|
265
|
+
"id": sid,
|
|
266
|
+
"compartment": compartment_id,
|
|
267
|
+
"hasOnlySubstanceUnits": "true",
|
|
268
|
+
"boundaryCondition": "false",
|
|
269
|
+
"constant": "false",
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
for key in (
|
|
273
|
+
"compartment",
|
|
274
|
+
"boundaryCondition",
|
|
275
|
+
"constant",
|
|
276
|
+
"initialAmount",
|
|
277
|
+
"initialConcentration",
|
|
278
|
+
"substanceUnits",
|
|
279
|
+
"hasOnlySubstanceUnits",
|
|
280
|
+
):
|
|
281
|
+
if key in species.source_attrs:
|
|
282
|
+
element.set(key, str(species.source_attrs[key]))
|
|
283
|
+
if species.label is not None:
|
|
284
|
+
element.set("name", str(species.label))
|
|
285
|
+
|
|
286
|
+
extra = {
|
|
287
|
+
key: value
|
|
288
|
+
for key, value in (
|
|
289
|
+
("smiles", species.smiles),
|
|
290
|
+
("sourceNodeId", species.source_node_id),
|
|
291
|
+
("syncrnId", species.id),
|
|
292
|
+
)
|
|
293
|
+
if value is not None
|
|
294
|
+
}
|
|
295
|
+
if extra:
|
|
296
|
+
annotation = ET.SubElement(element, "annotation")
|
|
297
|
+
ET.SubElement(
|
|
298
|
+
annotation,
|
|
299
|
+
f"{{{SYNKIT_NS}}}speciesInfo",
|
|
300
|
+
{key: str(value) for key, value in extra.items()},
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _reaction_element(
|
|
305
|
+
parent: ET.Element,
|
|
306
|
+
*,
|
|
307
|
+
rid: str,
|
|
308
|
+
reaction: Any,
|
|
309
|
+
species_sids: Dict[str, str],
|
|
310
|
+
) -> None:
|
|
311
|
+
"""Append one ``<reaction>`` element for a SynCRN reaction record.
|
|
312
|
+
|
|
313
|
+
:param parent:
|
|
314
|
+
The ``<listOfReactions>`` element.
|
|
315
|
+
:type parent: xml.etree.ElementTree.Element
|
|
316
|
+
|
|
317
|
+
:param rid:
|
|
318
|
+
SBML reaction id.
|
|
319
|
+
:type rid: str
|
|
320
|
+
|
|
321
|
+
:param reaction:
|
|
322
|
+
SynCRN reaction record.
|
|
323
|
+
:type reaction: Any
|
|
324
|
+
|
|
325
|
+
:param species_sids:
|
|
326
|
+
Mapping from internal species id to SBML species id.
|
|
327
|
+
:type species_sids: Dict[str, str]
|
|
328
|
+
|
|
329
|
+
:return:
|
|
330
|
+
``None``. The element is appended in place.
|
|
331
|
+
:rtype: None
|
|
332
|
+
"""
|
|
333
|
+
element = ET.SubElement(
|
|
334
|
+
parent,
|
|
335
|
+
"reaction",
|
|
336
|
+
{"id": rid, "reversible": "false"},
|
|
337
|
+
)
|
|
338
|
+
if reaction.label is not None:
|
|
339
|
+
element.set("name", str(reaction.label))
|
|
340
|
+
|
|
341
|
+
for side, container, tag in (
|
|
342
|
+
(reaction.lhs, "listOfReactants", "speciesReference"),
|
|
343
|
+
(reaction.rhs, "listOfProducts", "speciesReference"),
|
|
344
|
+
):
|
|
345
|
+
items = list(side.items())
|
|
346
|
+
if not items:
|
|
347
|
+
continue
|
|
348
|
+
listing = ET.SubElement(element, container)
|
|
349
|
+
for internal_sid, coeff in items:
|
|
350
|
+
ET.SubElement(
|
|
351
|
+
listing,
|
|
352
|
+
tag,
|
|
353
|
+
{
|
|
354
|
+
"species": species_sids[internal_sid],
|
|
355
|
+
"stoichiometry": _format_stoich(coeff),
|
|
356
|
+
"constant": "true",
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
extra = {
|
|
361
|
+
key: value
|
|
362
|
+
for key, value in (
|
|
363
|
+
("syncrnId", reaction.id),
|
|
364
|
+
("sourceNodeId", reaction.source_node_id),
|
|
365
|
+
("sourceKind", reaction.source_kind),
|
|
366
|
+
("ruleId", reaction.rule_id),
|
|
367
|
+
("ruleRepr", reaction.rule_repr),
|
|
368
|
+
)
|
|
369
|
+
if value is not None
|
|
370
|
+
}
|
|
371
|
+
if extra:
|
|
372
|
+
annotation = ET.SubElement(element, "annotation")
|
|
373
|
+
ET.SubElement(
|
|
374
|
+
annotation,
|
|
375
|
+
f"{{{SYNKIT_NS}}}reactionInfo",
|
|
376
|
+
{key: str(value) for key, value in extra.items()},
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def crn_to_sbml(
|
|
381
|
+
crn: SynCRN,
|
|
382
|
+
*,
|
|
383
|
+
model_id: str = "synkit_crn",
|
|
384
|
+
model_name: Optional[str] = None,
|
|
385
|
+
compartment_id: str = "cell",
|
|
386
|
+
pretty: bool = True,
|
|
387
|
+
) -> str:
|
|
388
|
+
"""Serialize a :class:`SynCRN` to an SBML Level 3 Version 2 document.
|
|
389
|
+
|
|
390
|
+
Species and reactions keep their internal ids as SBML ids and their labels
|
|
391
|
+
as SBML names; SMILES and provenance travel in a SynKit annotation, so
|
|
392
|
+
:func:`crn_from_sbml` reproduces the network exactly. No kinetic law is
|
|
393
|
+
emitted — a ``SynCRN`` carries none, and structural analysis needs none.
|
|
394
|
+
|
|
395
|
+
:param crn:
|
|
396
|
+
Network to serialize.
|
|
397
|
+
:type crn: SynCRN
|
|
398
|
+
|
|
399
|
+
:param model_id:
|
|
400
|
+
SBML model identifier.
|
|
401
|
+
:type model_id: str
|
|
402
|
+
|
|
403
|
+
:param model_name:
|
|
404
|
+
Optional human-readable model name.
|
|
405
|
+
:type model_name: Optional[str]
|
|
406
|
+
|
|
407
|
+
:param compartment_id:
|
|
408
|
+
Identifier of the single compartment every species is placed in.
|
|
409
|
+
:type compartment_id: str
|
|
410
|
+
|
|
411
|
+
:param pretty:
|
|
412
|
+
Whether to indent the output for readability.
|
|
413
|
+
:type pretty: bool
|
|
414
|
+
|
|
415
|
+
:return:
|
|
416
|
+
SBML document as a UTF-8 string.
|
|
417
|
+
:rtype: str
|
|
418
|
+
|
|
419
|
+
:raises TypeError:
|
|
420
|
+
If ``crn`` is not a :class:`SynCRN`.
|
|
421
|
+
|
|
422
|
+
.. rubric:: Example
|
|
423
|
+
|
|
424
|
+
.. code-block:: python
|
|
425
|
+
|
|
426
|
+
xml = crn_to_sbml(SynCRN.from_reaction_strings(["2A>>B"]))
|
|
427
|
+
print("<reaction" in xml)
|
|
428
|
+
# True
|
|
429
|
+
"""
|
|
430
|
+
if not isinstance(crn, SynCRN):
|
|
431
|
+
raise TypeError(f"crn must be a SynCRN, got {type(crn).__name__}")
|
|
432
|
+
|
|
433
|
+
root = ET.Element(
|
|
434
|
+
f"{{{SBML_NS}}}sbml",
|
|
435
|
+
{"level": "3", "version": "2"},
|
|
436
|
+
)
|
|
437
|
+
model_attrs = {"id": _as_sid(model_id, fallback="synkit_crn")}
|
|
438
|
+
if model_name is not None:
|
|
439
|
+
model_attrs["name"] = model_name
|
|
440
|
+
model = ET.SubElement(root, "model", model_attrs)
|
|
441
|
+
|
|
442
|
+
compartment_sid = _as_sid(compartment_id, fallback="cell")
|
|
443
|
+
compartments = ET.SubElement(model, "listOfCompartments")
|
|
444
|
+
ET.SubElement(
|
|
445
|
+
compartments,
|
|
446
|
+
"compartment",
|
|
447
|
+
{"id": compartment_sid, "spatialDimensions": "3", "constant": "true"},
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
for name in sorted(
|
|
451
|
+
{
|
|
452
|
+
sp.source_attrs.get("compartment", compartment_sid)
|
|
453
|
+
for sp in crn.species.values()
|
|
454
|
+
}
|
|
455
|
+
- {compartment_sid}
|
|
456
|
+
):
|
|
457
|
+
ET.SubElement(compartments, "compartment", {"id": name, "constant": "true"})
|
|
458
|
+
species_sids: Dict[str, str] = {}
|
|
459
|
+
if crn.species:
|
|
460
|
+
listing = ET.SubElement(model, "listOfSpecies")
|
|
461
|
+
for i, (internal_sid, species) in enumerate(crn.species.items(), start=1):
|
|
462
|
+
sid = _as_sid(internal_sid, fallback=f"s_{i}")
|
|
463
|
+
species_sids[internal_sid] = sid
|
|
464
|
+
_species_element(
|
|
465
|
+
listing,
|
|
466
|
+
sid=sid,
|
|
467
|
+
species=species,
|
|
468
|
+
compartment_id=compartment_sid,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if crn.reactions:
|
|
472
|
+
listing = ET.SubElement(model, "listOfReactions")
|
|
473
|
+
for j, (internal_rid, reaction) in enumerate(crn.reactions.items(), start=1):
|
|
474
|
+
_reaction_element(
|
|
475
|
+
listing,
|
|
476
|
+
rid=_as_sid(internal_rid, fallback=f"r_{j}"),
|
|
477
|
+
reaction=reaction,
|
|
478
|
+
species_sids=species_sids,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
if pretty:
|
|
482
|
+
ET.indent(root, space=" ")
|
|
483
|
+
|
|
484
|
+
ET.register_namespace("", SBML_NS)
|
|
485
|
+
ET.register_namespace("synkit", SYNKIT_NS)
|
|
486
|
+
body = ET.tostring(root, encoding="unicode")
|
|
487
|
+
return f'<?xml version="1.0" encoding="UTF-8"?>\n{body}\n'
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def write_sbml(crn: SynCRN, path: Union[str, Path], **kwargs: Any) -> Path:
|
|
491
|
+
"""Write a :class:`SynCRN` to an SBML file.
|
|
492
|
+
|
|
493
|
+
:param crn:
|
|
494
|
+
Network to serialize.
|
|
495
|
+
:type crn: SynCRN
|
|
496
|
+
|
|
497
|
+
:param path:
|
|
498
|
+
Destination file path.
|
|
499
|
+
:type path: Union[str, Path]
|
|
500
|
+
|
|
501
|
+
:param kwargs:
|
|
502
|
+
Extra keyword arguments forwarded to :func:`crn_to_sbml`.
|
|
503
|
+
:type kwargs: Any
|
|
504
|
+
|
|
505
|
+
:return:
|
|
506
|
+
The path written to.
|
|
507
|
+
:rtype: pathlib.Path
|
|
508
|
+
|
|
509
|
+
.. rubric:: Example
|
|
510
|
+
|
|
511
|
+
.. code-block:: python
|
|
512
|
+
|
|
513
|
+
write_sbml(crn, "network.xml")
|
|
514
|
+
"""
|
|
515
|
+
destination = Path(path)
|
|
516
|
+
destination.write_text(crn_to_sbml(crn, **kwargs), encoding="utf-8")
|
|
517
|
+
return destination
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# ---------------------------------------------------------------------------
|
|
521
|
+
# Import
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _annotation_values(element: ET.Element, name: str) -> Dict[str, str]:
|
|
526
|
+
"""Read the attributes of a SynKit annotation child, if present.
|
|
527
|
+
|
|
528
|
+
:param element:
|
|
529
|
+
Species or reaction element.
|
|
530
|
+
:type element: xml.etree.ElementTree.Element
|
|
531
|
+
|
|
532
|
+
:param name:
|
|
533
|
+
Local name of the SynKit annotation element.
|
|
534
|
+
:type name: str
|
|
535
|
+
|
|
536
|
+
:return:
|
|
537
|
+
Annotation attributes, empty when the annotation is absent.
|
|
538
|
+
:rtype: Dict[str, str]
|
|
539
|
+
"""
|
|
540
|
+
annotation = _find_child(element, "annotation")
|
|
541
|
+
if annotation is None:
|
|
542
|
+
return {}
|
|
543
|
+
for child in annotation.iter():
|
|
544
|
+
if _localname(child.tag) == name:
|
|
545
|
+
return dict(child.attrib)
|
|
546
|
+
return {}
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _read_species(
|
|
550
|
+
model: ET.Element,
|
|
551
|
+
*,
|
|
552
|
+
drop_boundary_species: bool,
|
|
553
|
+
) -> Tuple[Dict[str, Dict[str, Any]], set]:
|
|
554
|
+
"""Collect species definitions from an SBML model element.
|
|
555
|
+
|
|
556
|
+
:param model:
|
|
557
|
+
The ``<model>`` element.
|
|
558
|
+
:type model: xml.etree.ElementTree.Element
|
|
559
|
+
|
|
560
|
+
:param drop_boundary_species:
|
|
561
|
+
Whether species with ``boundaryCondition="true"`` should be excluded.
|
|
562
|
+
:type drop_boundary_species: bool
|
|
563
|
+
|
|
564
|
+
:return:
|
|
565
|
+
Pair ``(species_attrs, dropped_ids)``.
|
|
566
|
+
:rtype: Tuple[Dict[str, Dict[str, Any]], set]
|
|
567
|
+
"""
|
|
568
|
+
listing = _find_child(model, "listOfSpecies")
|
|
569
|
+
species: Dict[str, Dict[str, Any]] = {}
|
|
570
|
+
dropped: set = set()
|
|
571
|
+
|
|
572
|
+
for element in [] if listing is None else _find_children(listing, "species"):
|
|
573
|
+
sid = element.get("id")
|
|
574
|
+
if sid is None:
|
|
575
|
+
continue
|
|
576
|
+
if drop_boundary_species and _is_true(element.get("boundaryCondition")):
|
|
577
|
+
dropped.add(sid)
|
|
578
|
+
continue
|
|
579
|
+
|
|
580
|
+
annotation = _annotation_values(element, "speciesInfo")
|
|
581
|
+
species[sid] = {
|
|
582
|
+
"kind": "species",
|
|
583
|
+
"label": element.get("name") or sid,
|
|
584
|
+
"smiles": annotation.get("smiles"),
|
|
585
|
+
"sbml_id": sid,
|
|
586
|
+
**{
|
|
587
|
+
_localname(key): value
|
|
588
|
+
for key, value in element.attrib.items()
|
|
589
|
+
if _localname(key) in {"chemicalFormula", "charge"}
|
|
590
|
+
},
|
|
591
|
+
**{
|
|
592
|
+
key: element.get(key)
|
|
593
|
+
for key in (
|
|
594
|
+
"compartment",
|
|
595
|
+
"boundaryCondition",
|
|
596
|
+
"constant",
|
|
597
|
+
"initialAmount",
|
|
598
|
+
"initialConcentration",
|
|
599
|
+
"substanceUnits",
|
|
600
|
+
"hasOnlySubstanceUnits",
|
|
601
|
+
)
|
|
602
|
+
if element.get(key) is not None
|
|
603
|
+
},
|
|
604
|
+
"source_node_id": annotation.get("sourceNodeId", sid),
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return species, dropped
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _side_references(
|
|
611
|
+
reaction: ET.Element,
|
|
612
|
+
container: str,
|
|
613
|
+
) -> List[Tuple[str, float]]:
|
|
614
|
+
"""Collect ``(species id, stoichiometry)`` pairs from one reaction side.
|
|
615
|
+
|
|
616
|
+
Repeated references to the same species are summed, as SBML allows.
|
|
617
|
+
|
|
618
|
+
:param reaction:
|
|
619
|
+
The ``<reaction>`` element.
|
|
620
|
+
:type reaction: xml.etree.ElementTree.Element
|
|
621
|
+
|
|
622
|
+
:param container:
|
|
623
|
+
Either ``"listOfReactants"`` or ``"listOfProducts"``.
|
|
624
|
+
:type container: str
|
|
625
|
+
|
|
626
|
+
:return:
|
|
627
|
+
Species references with their coefficients.
|
|
628
|
+
:rtype: List[Tuple[str, float]]
|
|
629
|
+
"""
|
|
630
|
+
listing = _find_child(reaction, container)
|
|
631
|
+
if listing is None:
|
|
632
|
+
return []
|
|
633
|
+
|
|
634
|
+
totals: Dict[str, float] = {}
|
|
635
|
+
for reference in _find_children(listing, "speciesReference"):
|
|
636
|
+
target = reference.get("species")
|
|
637
|
+
if target is None:
|
|
638
|
+
continue
|
|
639
|
+
totals[target] = totals.get(target, 0.0) + _parse_stoich(
|
|
640
|
+
reference.get("stoichiometry")
|
|
641
|
+
)
|
|
642
|
+
return list(totals.items())
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _add_reaction_node(
|
|
646
|
+
graph: nx.DiGraph,
|
|
647
|
+
*,
|
|
648
|
+
node_id: str,
|
|
649
|
+
label: str,
|
|
650
|
+
reactants: List[Tuple[str, float]],
|
|
651
|
+
products: List[Tuple[str, float]],
|
|
652
|
+
known_species: Dict[str, Dict[str, Any]],
|
|
653
|
+
extra: Dict[str, Any],
|
|
654
|
+
) -> None:
|
|
655
|
+
"""Add one reaction node and its incidence edges to the working graph.
|
|
656
|
+
|
|
657
|
+
:param graph:
|
|
658
|
+
Bipartite graph under construction.
|
|
659
|
+
:type graph: networkx.DiGraph
|
|
660
|
+
|
|
661
|
+
:param node_id:
|
|
662
|
+
Node id for the reaction.
|
|
663
|
+
:type node_id: str
|
|
664
|
+
|
|
665
|
+
:param label:
|
|
666
|
+
Reaction label.
|
|
667
|
+
:type label: str
|
|
668
|
+
|
|
669
|
+
:param reactants:
|
|
670
|
+
Reactant references.
|
|
671
|
+
:type reactants: List[Tuple[str, float]]
|
|
672
|
+
|
|
673
|
+
:param products:
|
|
674
|
+
Product references.
|
|
675
|
+
:type products: List[Tuple[str, float]]
|
|
676
|
+
|
|
677
|
+
:param known_species:
|
|
678
|
+
Species definitions keyed by SBML id; references outside this mapping
|
|
679
|
+
are skipped.
|
|
680
|
+
:type known_species: Dict[str, Dict[str, Any]]
|
|
681
|
+
|
|
682
|
+
:param extra:
|
|
683
|
+
Additional node attributes.
|
|
684
|
+
:type extra: Dict[str, Any]
|
|
685
|
+
|
|
686
|
+
:return:
|
|
687
|
+
``None``. The graph is modified in place.
|
|
688
|
+
:rtype: None
|
|
689
|
+
"""
|
|
690
|
+
graph.add_node(node_id, kind="reaction", label=label, **extra)
|
|
691
|
+
|
|
692
|
+
for sid, coeff in reactants:
|
|
693
|
+
if sid in known_species:
|
|
694
|
+
graph.add_edge(sid, node_id, role="reactant", stoich=_normalize(coeff))
|
|
695
|
+
for sid, coeff in products:
|
|
696
|
+
if sid in known_species:
|
|
697
|
+
graph.add_edge(node_id, sid, role="product", stoich=_normalize(coeff))
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _normalize(coeff: float) -> Union[int, float]:
|
|
701
|
+
"""Return an integer coefficient when the value is integral.
|
|
702
|
+
|
|
703
|
+
:param coeff:
|
|
704
|
+
Coefficient value.
|
|
705
|
+
:type coeff: float
|
|
706
|
+
|
|
707
|
+
:return:
|
|
708
|
+
Integer or float coefficient.
|
|
709
|
+
:rtype: Union[int, float]
|
|
710
|
+
"""
|
|
711
|
+
return int(coeff) if float(coeff).is_integer() else coeff
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def _warn_no_reactions(model: ET.Element) -> None:
|
|
715
|
+
"""Warn that an SBML model declares no reactions, and say why if possible.
|
|
716
|
+
|
|
717
|
+
An empty network is a valid but almost always unintended result. The usual
|
|
718
|
+
cause is a model that is not a reaction network at all: SBML-``qual``
|
|
719
|
+
logical models carry ``<listOfTransitions>`` over ``<listOfQualitativeSpecies>``
|
|
720
|
+
and have no ``<listOfReactions>``, and returning ``0`` species silently
|
|
721
|
+
looks like a parse failure to the caller.
|
|
722
|
+
|
|
723
|
+
:param model:
|
|
724
|
+
The ``<model>`` element.
|
|
725
|
+
:type model: xml.etree.ElementTree.Element
|
|
726
|
+
|
|
727
|
+
:return:
|
|
728
|
+
``None``. A :class:`UserWarning` is issued.
|
|
729
|
+
:rtype: None
|
|
730
|
+
"""
|
|
731
|
+
hints = []
|
|
732
|
+
if _find_child(model, "listOfQualitativeSpecies") is not None or (
|
|
733
|
+
_find_child(model, "listOfTransitions") is not None
|
|
734
|
+
):
|
|
735
|
+
hints.append(
|
|
736
|
+
"the model uses the SBML qual package (qualitative species and "
|
|
737
|
+
"transitions), which describes a logical model rather than a "
|
|
738
|
+
"reaction network"
|
|
739
|
+
)
|
|
740
|
+
if _find_child(model, "listOfSpecies") is not None:
|
|
741
|
+
hints.append("species are declared but no reactions connect them")
|
|
742
|
+
|
|
743
|
+
detail = f" ({'; '.join(hints)})" if hints else ""
|
|
744
|
+
warnings.warn(
|
|
745
|
+
f"SBML model {model.get('id')!r} declares no reactions{detail}; "
|
|
746
|
+
"the resulting network is empty.",
|
|
747
|
+
UserWarning,
|
|
748
|
+
stacklevel=3,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def crn_from_sbml(
|
|
753
|
+
source: Union[str, Path, ET.Element],
|
|
754
|
+
*,
|
|
755
|
+
expand_reversible: bool = True,
|
|
756
|
+
drop_boundary_species: bool = False,
|
|
757
|
+
strict: bool = False,
|
|
758
|
+
**syncrn_kwargs: Any,
|
|
759
|
+
) -> SynCRN:
|
|
760
|
+
"""Build a :class:`SynCRN` from an SBML document.
|
|
761
|
+
|
|
762
|
+
The reader matches elements by local name, so SBML Level 2 and Level 3 files
|
|
763
|
+
both parse. Kinetic laws, units, parameters, events and rules are ignored:
|
|
764
|
+
they carry no structural information.
|
|
765
|
+
|
|
766
|
+
:param source:
|
|
767
|
+
SBML document text, a path to an SBML file, or a parsed XML element.
|
|
768
|
+
:type source: Union[str, pathlib.Path, xml.etree.ElementTree.Element]
|
|
769
|
+
|
|
770
|
+
:param expand_reversible:
|
|
771
|
+
Whether a reaction marked ``reversible="true"`` is split into a forward
|
|
772
|
+
and a backward reaction. Leave enabled for CRNT analysis.
|
|
773
|
+
:type expand_reversible: bool
|
|
774
|
+
|
|
775
|
+
:param drop_boundary_species:
|
|
776
|
+
Whether species with ``boundaryCondition="true"`` are excluded together
|
|
777
|
+
with their incidences. Useful when a model uses boundary species as
|
|
778
|
+
constant sources or sinks.
|
|
779
|
+
:type drop_boundary_species: bool
|
|
780
|
+
|
|
781
|
+
:param strict:
|
|
782
|
+
Whether malformed network structure should raise; forwarded to
|
|
783
|
+
:meth:`SynCRN.from_digraph`. Defaults to ``False`` because published
|
|
784
|
+
SBML models routinely contain reactions with an empty side.
|
|
785
|
+
:type strict: bool
|
|
786
|
+
|
|
787
|
+
:param syncrn_kwargs:
|
|
788
|
+
Extra keyword arguments forwarded to :meth:`SynCRN.from_digraph`, such
|
|
789
|
+
as ``id_style``.
|
|
790
|
+
:type syncrn_kwargs: Any
|
|
791
|
+
|
|
792
|
+
:return:
|
|
793
|
+
Canonical network object.
|
|
794
|
+
:rtype: SynCRN
|
|
795
|
+
|
|
796
|
+
:raises ValueError:
|
|
797
|
+
If the document contains no ``<model>`` element.
|
|
798
|
+
|
|
799
|
+
.. rubric:: Example
|
|
800
|
+
|
|
801
|
+
.. code-block:: python
|
|
802
|
+
|
|
803
|
+
crn = crn_from_sbml("model.xml")
|
|
804
|
+
print(crn.n_species, crn.n_reactions)
|
|
805
|
+
"""
|
|
806
|
+
root = _resolve_root(source)
|
|
807
|
+
model = root if _localname(root.tag) == "model" else _find_child(root, "model")
|
|
808
|
+
if model is None:
|
|
809
|
+
found = _find_descendants(root, "model")
|
|
810
|
+
model = found[0] if found else None
|
|
811
|
+
if model is None:
|
|
812
|
+
raise ValueError("SBML document contains no <model> element")
|
|
813
|
+
|
|
814
|
+
species, _dropped = _read_species(
|
|
815
|
+
model, drop_boundary_species=drop_boundary_species
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
graph = nx.DiGraph()
|
|
819
|
+
graph.graph["sbml_model_id"] = model.get("id")
|
|
820
|
+
if model.get("name"):
|
|
821
|
+
graph.graph["sbml_model_name"] = model.get("name")
|
|
822
|
+
|
|
823
|
+
for sid, attrs in species.items():
|
|
824
|
+
graph.add_node(sid, **attrs)
|
|
825
|
+
|
|
826
|
+
listing = _find_child(model, "listOfReactions")
|
|
827
|
+
if listing is None or not _find_children(listing, "reaction"):
|
|
828
|
+
_warn_no_reactions(model)
|
|
829
|
+
|
|
830
|
+
for index, element in enumerate(
|
|
831
|
+
[] if listing is None else _find_children(listing, "reaction"), start=1
|
|
832
|
+
):
|
|
833
|
+
rid = element.get("id") or f"reaction_{index}"
|
|
834
|
+
label = element.get("name") or rid
|
|
835
|
+
reactants = _side_references(element, "listOfReactants")
|
|
836
|
+
products = _side_references(element, "listOfProducts")
|
|
837
|
+
reversible = _is_true(element.get("reversible"))
|
|
838
|
+
annotation = _annotation_values(element, "reactionInfo")
|
|
839
|
+
|
|
840
|
+
extra: Dict[str, Any] = {
|
|
841
|
+
"sbml_id": rid,
|
|
842
|
+
"sbml_reversible": reversible,
|
|
843
|
+
"source_node_id": annotation.get("sourceNodeId", rid),
|
|
844
|
+
}
|
|
845
|
+
if annotation.get("sourceKind"):
|
|
846
|
+
extra["source_kind"] = annotation["sourceKind"]
|
|
847
|
+
if annotation.get("ruleRepr"):
|
|
848
|
+
extra["rule_repr"] = annotation["ruleRepr"]
|
|
849
|
+
|
|
850
|
+
_add_reaction_node(
|
|
851
|
+
graph,
|
|
852
|
+
node_id=rid,
|
|
853
|
+
label=label,
|
|
854
|
+
reactants=reactants,
|
|
855
|
+
products=products,
|
|
856
|
+
known_species=species,
|
|
857
|
+
extra=extra,
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
if reversible and expand_reversible:
|
|
861
|
+
_add_reaction_node(
|
|
862
|
+
graph,
|
|
863
|
+
node_id=f"{rid}_rev",
|
|
864
|
+
label=f"{label}_rev",
|
|
865
|
+
reactants=products,
|
|
866
|
+
products=reactants,
|
|
867
|
+
known_species=species,
|
|
868
|
+
extra={**extra, "sbml_reverse_of": rid},
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
return SynCRN.from_digraph(graph, strict=strict, **syncrn_kwargs)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def _resolve_root(source: Union[str, Path, ET.Element]) -> ET.Element:
|
|
875
|
+
"""Resolve the input into a parsed XML root element.
|
|
876
|
+
|
|
877
|
+
A string is treated as a file path when it names an existing file and as
|
|
878
|
+
document text otherwise.
|
|
879
|
+
|
|
880
|
+
:param source:
|
|
881
|
+
SBML document text, a path, or an already parsed element.
|
|
882
|
+
:type source: Union[str, pathlib.Path, xml.etree.ElementTree.Element]
|
|
883
|
+
|
|
884
|
+
:return:
|
|
885
|
+
Root element.
|
|
886
|
+
:rtype: xml.etree.ElementTree.Element
|
|
887
|
+
|
|
888
|
+
:raises FileNotFoundError:
|
|
889
|
+
If a :class:`~pathlib.Path` is given that does not exist.
|
|
890
|
+
"""
|
|
891
|
+
if isinstance(source, ET.Element):
|
|
892
|
+
return source
|
|
893
|
+
|
|
894
|
+
if isinstance(source, Path):
|
|
895
|
+
return ET.parse(source).getroot()
|
|
896
|
+
|
|
897
|
+
text = str(source)
|
|
898
|
+
stripped = text.lstrip()
|
|
899
|
+
if stripped.startswith("<"):
|
|
900
|
+
return ET.fromstring(text)
|
|
901
|
+
return ET.parse(Path(text)).getroot()
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def read_sbml(path: Union[str, Path], **kwargs: Any) -> SynCRN:
|
|
905
|
+
"""Read a :class:`SynCRN` from an SBML file.
|
|
906
|
+
|
|
907
|
+
:param path:
|
|
908
|
+
Path to the SBML file.
|
|
909
|
+
:type path: Union[str, pathlib.Path]
|
|
910
|
+
|
|
911
|
+
:param kwargs:
|
|
912
|
+
Extra keyword arguments forwarded to :func:`crn_from_sbml`.
|
|
913
|
+
:type kwargs: Any
|
|
914
|
+
|
|
915
|
+
:return:
|
|
916
|
+
Canonical network object.
|
|
917
|
+
:rtype: SynCRN
|
|
918
|
+
|
|
919
|
+
.. rubric:: Example
|
|
920
|
+
|
|
921
|
+
.. code-block:: python
|
|
922
|
+
|
|
923
|
+
crn = read_sbml("BIOMD0000000001.xml")
|
|
924
|
+
"""
|
|
925
|
+
return crn_from_sbml(Path(path), **kwargs)
|