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,928 @@
|
|
|
1
|
+
"""Occurrence-aware MTG histories with lossless process reconstruction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any, Hashable, Iterable, Mapping
|
|
9
|
+
|
|
10
|
+
import networkx as nx
|
|
11
|
+
|
|
12
|
+
from synkit.Graph.Morphism import (
|
|
13
|
+
LLGError,
|
|
14
|
+
LabelSchema,
|
|
15
|
+
LewisLabelledGraph,
|
|
16
|
+
derive_electron_labeled_graph,
|
|
17
|
+
is_electron_schema,
|
|
18
|
+
)
|
|
19
|
+
from synkit.Rule.Apply import DPOError, RuleSpan
|
|
20
|
+
|
|
21
|
+
from .process import (
|
|
22
|
+
ChoiceWitness,
|
|
23
|
+
IndependenceWitness,
|
|
24
|
+
MaterialOccurrence,
|
|
25
|
+
OccurrenceProcess,
|
|
26
|
+
OccurrenceProcessFamily,
|
|
27
|
+
ProcessAlternative,
|
|
28
|
+
ProcessError,
|
|
29
|
+
RuleOccurrence,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
Carrier = tuple[str, Hashable]
|
|
33
|
+
EdgeCarrier = tuple[str, frozenset[Hashable]]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class HistoryIssueCode(str, Enum):
|
|
37
|
+
"""Stable failures for occurrence-aware history derivation and replay."""
|
|
38
|
+
|
|
39
|
+
EMPTY = "HISTORY_EMPTY"
|
|
40
|
+
SCHEMA_MISMATCH = "HISTORY_SCHEMA_MISMATCH"
|
|
41
|
+
FLOW_LOOKUP = "HISTORY_FLOW_LOOKUP"
|
|
42
|
+
LINEAGE_CONFLICT = "HISTORY_LINEAGE_CONFLICT"
|
|
43
|
+
INVALID_EXTENSION = "HISTORY_INVALID_EXTENSION"
|
|
44
|
+
INVALID_CONFIGURATION = "HISTORY_INVALID_CONFIGURATION"
|
|
45
|
+
INVALID_STATE = "HISTORY_INVALID_STATE"
|
|
46
|
+
INACTIVE_INPUT = "HISTORY_INACTIVE_INPUT"
|
|
47
|
+
ACTIVE_OUTPUT = "HISTORY_ACTIVE_OUTPUT"
|
|
48
|
+
OUTER_RULE = "HISTORY_OUTER_RULE"
|
|
49
|
+
ROUNDTRIP = "HISTORY_ROUNDTRIP"
|
|
50
|
+
ALTERNATIVE_MISMATCH = "HISTORY_ALTERNATIVE_MISMATCH"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class HistoryIssue:
|
|
55
|
+
code: HistoryIssueCode
|
|
56
|
+
message: str
|
|
57
|
+
context: Mapping[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class HistoryError(ValueError):
|
|
61
|
+
"""Raised when a process cannot yield a lossless history object."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, *issues: HistoryIssue):
|
|
64
|
+
self.issues = tuple(issues)
|
|
65
|
+
super().__init__("; ".join(issue.message for issue in issues))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class CarrierState:
|
|
70
|
+
"""One lineage state at a process cut; absence is explicit."""
|
|
71
|
+
|
|
72
|
+
present: bool
|
|
73
|
+
material_id: str | None = None
|
|
74
|
+
carrier: Hashable | frozenset[Hashable] | None = None
|
|
75
|
+
labels: tuple[tuple[str, Any], ...] = ()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
ABSENT = CarrierState(False)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class NodeLineage:
|
|
83
|
+
lineage_id: str
|
|
84
|
+
members: tuple[Carrier, ...]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class EdgeLineage:
|
|
89
|
+
lineage_id: str
|
|
90
|
+
endpoints: frozenset[str]
|
|
91
|
+
members: tuple[EdgeCarrier, ...]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class ConfigurationCut:
|
|
96
|
+
"""One order ideal with decoded state and supplied carrier correspondence.
|
|
97
|
+
|
|
98
|
+
``world`` uses persistent node-lineage IDs. Node and edge states retain the
|
|
99
|
+
currently active material/carrier, including explicit absence. No schedule
|
|
100
|
+
is selected and no linear extension is enumerated.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
process_id: str
|
|
104
|
+
executed_events: frozenset[str]
|
|
105
|
+
active_material_ids: frozenset[str]
|
|
106
|
+
world: LewisLabelledGraph
|
|
107
|
+
node_states: tuple[tuple[NodeLineage, CarrierState], ...]
|
|
108
|
+
edge_states: tuple[tuple[EdgeLineage, CarrierState], ...]
|
|
109
|
+
|
|
110
|
+
def lineage_graph(self, *, include_absent: bool = True) -> nx.MultiGraph:
|
|
111
|
+
"""Return lineage observations, optionally including absent carriers.
|
|
112
|
+
|
|
113
|
+
:param include_absent: Retain absent historical node and edge lineages.
|
|
114
|
+
:return: Fresh graph with one ``state`` observation per retained lineage.
|
|
115
|
+
"""
|
|
116
|
+
graph = nx.MultiGraph()
|
|
117
|
+
graph.graph.update(
|
|
118
|
+
process_id=self.process_id,
|
|
119
|
+
executed_events=self.executed_events,
|
|
120
|
+
active_material_ids=self.active_material_ids,
|
|
121
|
+
projection="configuration",
|
|
122
|
+
)
|
|
123
|
+
for lineage, state in self.node_states:
|
|
124
|
+
if state.present or include_absent:
|
|
125
|
+
graph.add_node(lineage.lineage_id, members=lineage.members, state=state)
|
|
126
|
+
for lineage, state in self.edge_states:
|
|
127
|
+
if state.present or include_absent:
|
|
128
|
+
left, right = tuple(lineage.endpoints)
|
|
129
|
+
graph.add_edge(
|
|
130
|
+
left,
|
|
131
|
+
right,
|
|
132
|
+
key=lineage.lineage_id,
|
|
133
|
+
lineage_id=lineage.lineage_id,
|
|
134
|
+
members=lineage.members,
|
|
135
|
+
state=state,
|
|
136
|
+
)
|
|
137
|
+
return graph
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class HistoryReplay:
|
|
142
|
+
valid: bool
|
|
143
|
+
issues: tuple[HistoryIssue, ...] = ()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class _UnionFind:
|
|
147
|
+
def __init__(self, values: Iterable[Hashable]) -> None:
|
|
148
|
+
self.parent = {value: value for value in values}
|
|
149
|
+
|
|
150
|
+
def find(self, value: Hashable) -> Hashable:
|
|
151
|
+
parent = self.parent[value]
|
|
152
|
+
if parent != value:
|
|
153
|
+
self.parent[value] = self.find(parent)
|
|
154
|
+
return self.parent[value]
|
|
155
|
+
|
|
156
|
+
def union(self, left: Hashable, right: Hashable) -> None:
|
|
157
|
+
left_root, right_root = self.find(left), self.find(right)
|
|
158
|
+
if left_root == right_root:
|
|
159
|
+
return
|
|
160
|
+
if repr(left_root) <= repr(right_root):
|
|
161
|
+
self.parent[right_root] = left_root
|
|
162
|
+
else:
|
|
163
|
+
self.parent[left_root] = right_root
|
|
164
|
+
|
|
165
|
+
def groups(self) -> tuple[tuple[Hashable, ...], ...]:
|
|
166
|
+
grouped: dict[Hashable, list[Hashable]] = {}
|
|
167
|
+
for value in self.parent:
|
|
168
|
+
grouped.setdefault(self.find(value), []).append(value)
|
|
169
|
+
return tuple(
|
|
170
|
+
sorted(
|
|
171
|
+
(tuple(sorted(group, key=repr)) for group in grouped.values()),
|
|
172
|
+
key=repr,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _binding_carrier(bindings: tuple[Any, ...], endpoint_node: Hashable) -> Carrier:
|
|
178
|
+
for binding in bindings:
|
|
179
|
+
mapping = dict(binding.mapping)
|
|
180
|
+
if endpoint_node in mapping:
|
|
181
|
+
return binding.material_id, mapping[endpoint_node]
|
|
182
|
+
raise HistoryError(
|
|
183
|
+
HistoryIssue(
|
|
184
|
+
HistoryIssueCode.FLOW_LOOKUP,
|
|
185
|
+
"No material binding covers a rule endpoint carrier.",
|
|
186
|
+
{"node": repr(endpoint_node)},
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _binding_edge(
|
|
192
|
+
bindings: tuple[Any, ...], endpoint_edge: frozenset[Hashable]
|
|
193
|
+
) -> EdgeCarrier:
|
|
194
|
+
carriers = tuple(_binding_carrier(bindings, node) for node in endpoint_edge)
|
|
195
|
+
material_ids = {material for material, _ in carriers}
|
|
196
|
+
if len(material_ids) != 1:
|
|
197
|
+
raise HistoryError(
|
|
198
|
+
HistoryIssue(
|
|
199
|
+
HistoryIssueCode.FLOW_LOOKUP,
|
|
200
|
+
"One endpoint edge must belong to one material component.",
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
return next(iter(material_ids)), frozenset(node for _, node in carriers)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _lineages(
|
|
207
|
+
materials: tuple[MaterialOccurrence, ...], events: tuple[RuleOccurrence, ...]
|
|
208
|
+
) -> tuple[tuple[NodeLineage, ...], tuple[EdgeLineage, ...]]:
|
|
209
|
+
node_values = tuple(
|
|
210
|
+
(material.occurrence_id, node)
|
|
211
|
+
for material in materials
|
|
212
|
+
for node in material.value.node_ids
|
|
213
|
+
)
|
|
214
|
+
edge_values = tuple(
|
|
215
|
+
(material.occurrence_id, edge)
|
|
216
|
+
for material in materials
|
|
217
|
+
for edge in material.value.edge_keys
|
|
218
|
+
)
|
|
219
|
+
node_union = _UnionFind(node_values)
|
|
220
|
+
edge_union = _UnionFind(edge_values)
|
|
221
|
+
for event in events:
|
|
222
|
+
for interface in event.rule.interface.node_ids:
|
|
223
|
+
left_node = event.rule.left_arm.mapping[interface]
|
|
224
|
+
right_node = event.rule.right_arm.mapping[interface]
|
|
225
|
+
node_union.union(
|
|
226
|
+
_binding_carrier(event.inputs, left_node),
|
|
227
|
+
_binding_carrier(event.outputs, right_node),
|
|
228
|
+
)
|
|
229
|
+
for interface_edge in event.rule.interface.edge_keys:
|
|
230
|
+
left_edge = event.rule.left_arm.edge_mapping[interface_edge]
|
|
231
|
+
right_edge = event.rule.right_arm.edge_mapping[interface_edge]
|
|
232
|
+
edge_union.union(
|
|
233
|
+
_binding_edge(event.inputs, left_edge),
|
|
234
|
+
_binding_edge(event.outputs, right_edge),
|
|
235
|
+
)
|
|
236
|
+
node_lineages = tuple(
|
|
237
|
+
NodeLineage(f"node:{index}", group) # type: ignore[arg-type]
|
|
238
|
+
for index, group in enumerate(node_union.groups())
|
|
239
|
+
)
|
|
240
|
+
node_by_carrier = {
|
|
241
|
+
carrier: lineage.lineage_id
|
|
242
|
+
for lineage in node_lineages
|
|
243
|
+
for carrier in lineage.members
|
|
244
|
+
}
|
|
245
|
+
edge_lineages = []
|
|
246
|
+
material_by_id = {material.occurrence_id: material for material in materials}
|
|
247
|
+
for index, group in enumerate(edge_union.groups()):
|
|
248
|
+
endpoints: frozenset[str] | None = None
|
|
249
|
+
for material_id, edge in group:
|
|
250
|
+
candidate = frozenset(node_by_carrier[(material_id, node)] for node in edge)
|
|
251
|
+
if endpoints is None:
|
|
252
|
+
endpoints = candidate
|
|
253
|
+
elif endpoints != candidate:
|
|
254
|
+
raise HistoryError(
|
|
255
|
+
HistoryIssue(
|
|
256
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
257
|
+
"A preserved edge lineage changes node lineage endpoints.",
|
|
258
|
+
{"material": material_id},
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
if edge not in material_by_id[material_id].value.edge_keys:
|
|
262
|
+
raise HistoryError(
|
|
263
|
+
HistoryIssue(
|
|
264
|
+
HistoryIssueCode.FLOW_LOOKUP,
|
|
265
|
+
"An edge lineage names an absent material edge.",
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
edge_lineages.append(
|
|
269
|
+
EdgeLineage(
|
|
270
|
+
f"edge:{index}",
|
|
271
|
+
endpoints or frozenset(),
|
|
272
|
+
group, # type: ignore[arg-type]
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
return node_lineages, tuple(edge_lineages)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass(frozen=True)
|
|
279
|
+
class OccurrenceMTG:
|
|
280
|
+
"""A lossless event/material history plus compact carrier lineages."""
|
|
281
|
+
|
|
282
|
+
process_id: str
|
|
283
|
+
materials: tuple[MaterialOccurrence, ...]
|
|
284
|
+
events: tuple[RuleOccurrence, ...]
|
|
285
|
+
independence: tuple[IndependenceWitness, ...]
|
|
286
|
+
causal_pairs: tuple[tuple[str, str], ...]
|
|
287
|
+
node_lineages: tuple[NodeLineage, ...]
|
|
288
|
+
edge_lineages: tuple[EdgeLineage, ...]
|
|
289
|
+
empty_schema: LabelSchema | None = None
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def empty(
|
|
293
|
+
cls, schema: LabelSchema, *, process_id: str = "empty"
|
|
294
|
+
) -> "OccurrenceMTG":
|
|
295
|
+
"""Return the empty history, typed by its graph schema.
|
|
296
|
+
|
|
297
|
+
:param schema: Schema for the empty boundary worlds.
|
|
298
|
+
:param process_id: Identity of this empty occurrence history.
|
|
299
|
+
:return: Typed monoidal unit with no events or material copies.
|
|
300
|
+
"""
|
|
301
|
+
return cls(process_id, (), (), (), (), (), (), schema)
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def schema(self) -> LabelSchema:
|
|
305
|
+
"""Return the common material schema or the declared empty schema."""
|
|
306
|
+
if self.materials:
|
|
307
|
+
return self.materials[0].value.schema
|
|
308
|
+
if self.empty_schema is not None:
|
|
309
|
+
return self.empty_schema
|
|
310
|
+
raise HistoryError(
|
|
311
|
+
HistoryIssue(HistoryIssueCode.EMPTY, "An empty history needs a schema.")
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
@classmethod
|
|
315
|
+
def from_process(
|
|
316
|
+
cls, process: OccurrenceProcess, *, empty_schema: LabelSchema | None = None
|
|
317
|
+
) -> "OccurrenceMTG":
|
|
318
|
+
if not process.materials:
|
|
319
|
+
if not process.events and empty_schema is not None:
|
|
320
|
+
return cls.empty(empty_schema, process_id=process.process_id)
|
|
321
|
+
raise HistoryError(
|
|
322
|
+
HistoryIssue(
|
|
323
|
+
HistoryIssueCode.EMPTY,
|
|
324
|
+
"An occurrence MTG requires at least one material occurrence.",
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
schemas = {material.value.schema for material in process.materials}
|
|
328
|
+
if len(schemas) != 1:
|
|
329
|
+
raise HistoryError(
|
|
330
|
+
HistoryIssue(
|
|
331
|
+
HistoryIssueCode.SCHEMA_MISMATCH,
|
|
332
|
+
"All material occurrences in one MTG need one LLG schema.",
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
nodes, edges = _lineages(process.materials, process.events)
|
|
336
|
+
result = cls(
|
|
337
|
+
process.process_id,
|
|
338
|
+
process.materials,
|
|
339
|
+
process.events,
|
|
340
|
+
process.independence,
|
|
341
|
+
process.causal_pairs,
|
|
342
|
+
nodes,
|
|
343
|
+
edges,
|
|
344
|
+
)
|
|
345
|
+
replay = result.replay()
|
|
346
|
+
if not replay.valid:
|
|
347
|
+
raise HistoryError(*replay.issues)
|
|
348
|
+
return result
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def material_by_id(self) -> dict[str, MaterialOccurrence]:
|
|
352
|
+
return {material.occurrence_id: material for material in self.materials}
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def event_by_id(self) -> dict[str, RuleOccurrence]:
|
|
356
|
+
return {event.occurrence_id: event for event in self.events}
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def node_lineage_by_carrier(self) -> dict[Carrier, str]:
|
|
360
|
+
return {
|
|
361
|
+
carrier: lineage.lineage_id
|
|
362
|
+
for lineage in self.node_lineages
|
|
363
|
+
for carrier in lineage.members
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
def to_process(self) -> OccurrenceProcess:
|
|
367
|
+
"""Reconstruct the complete process, including occurrence bindings."""
|
|
368
|
+
return OccurrenceProcess(
|
|
369
|
+
self.process_id, self.materials, self.events, self.independence
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def replay(self) -> HistoryReplay:
|
|
373
|
+
issues = []
|
|
374
|
+
schemas_valid = (
|
|
375
|
+
len({m.value.schema for m in self.materials}) == 1
|
|
376
|
+
and self.empty_schema is None
|
|
377
|
+
if self.materials
|
|
378
|
+
else self.empty_schema is not None and not self.events
|
|
379
|
+
)
|
|
380
|
+
if not schemas_valid:
|
|
381
|
+
return HistoryReplay(
|
|
382
|
+
False,
|
|
383
|
+
(
|
|
384
|
+
HistoryIssue(
|
|
385
|
+
HistoryIssueCode.SCHEMA_MISMATCH,
|
|
386
|
+
"A history requires materials under one schema.",
|
|
387
|
+
),
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
state_issues = self._material_state_issues()
|
|
391
|
+
if state_issues:
|
|
392
|
+
return HistoryReplay(False, state_issues)
|
|
393
|
+
try:
|
|
394
|
+
process = self.to_process()
|
|
395
|
+
except ProcessError as error:
|
|
396
|
+
issues.append(
|
|
397
|
+
HistoryIssue(
|
|
398
|
+
HistoryIssueCode.ROUNDTRIP,
|
|
399
|
+
"The retained occurrence process no longer validates.",
|
|
400
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
return HistoryReplay(False, tuple(issues))
|
|
404
|
+
observed_nodes = Counter(
|
|
405
|
+
carrier for lineage in self.node_lineages for carrier in lineage.members
|
|
406
|
+
)
|
|
407
|
+
expected_nodes = Counter(
|
|
408
|
+
(material.occurrence_id, node)
|
|
409
|
+
for material in self.materials
|
|
410
|
+
for node in material.value.node_ids
|
|
411
|
+
)
|
|
412
|
+
observed_edges = Counter(
|
|
413
|
+
carrier for lineage in self.edge_lineages for carrier in lineage.members
|
|
414
|
+
)
|
|
415
|
+
expected_edges = Counter(
|
|
416
|
+
(material.occurrence_id, edge)
|
|
417
|
+
for material in self.materials
|
|
418
|
+
for edge in material.value.edge_keys
|
|
419
|
+
)
|
|
420
|
+
if observed_nodes != expected_nodes or observed_edges != expected_edges:
|
|
421
|
+
issues.append(
|
|
422
|
+
HistoryIssue(
|
|
423
|
+
HistoryIssueCode.ROUNDTRIP,
|
|
424
|
+
"Carrier lineages do not cover every material carrier exactly.",
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
if process.causal_pairs != self.causal_pairs:
|
|
428
|
+
issues.append(
|
|
429
|
+
HistoryIssue(
|
|
430
|
+
HistoryIssueCode.ROUNDTRIP,
|
|
431
|
+
"Causal dependencies changed during reconstruction.",
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
if observed_nodes == expected_nodes and observed_edges == expected_edges:
|
|
435
|
+
issues.extend(self._lineage_issues())
|
|
436
|
+
return HistoryReplay(not issues, tuple(issues))
|
|
437
|
+
|
|
438
|
+
def _material_state_issues(self) -> tuple[HistoryIssue, ...]:
|
|
439
|
+
if not is_electron_schema(self.schema):
|
|
440
|
+
return ()
|
|
441
|
+
issues = []
|
|
442
|
+
for material in self.materials:
|
|
443
|
+
try:
|
|
444
|
+
derive_electron_labeled_graph(material.value.to_networkx())
|
|
445
|
+
except LLGError as error:
|
|
446
|
+
issues.append(
|
|
447
|
+
HistoryIssue(
|
|
448
|
+
HistoryIssueCode.INVALID_STATE,
|
|
449
|
+
"A material's derived electron state disagrees with its authoritative coordinates.",
|
|
450
|
+
{
|
|
451
|
+
"material": material.occurrence_id,
|
|
452
|
+
"issues": tuple(issue.to_dict() for issue in error.issues),
|
|
453
|
+
},
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
return tuple(issues)
|
|
457
|
+
|
|
458
|
+
def _lineage_issues(self) -> tuple[HistoryIssue, ...]:
|
|
459
|
+
expected_nodes, expected_edges = _lineages(self.materials, self.events)
|
|
460
|
+
expected_groups = {frozenset(n.members): n.lineage_id for n in expected_nodes}
|
|
461
|
+
node_groups = {frozenset(n.members) for n in self.node_lineages}
|
|
462
|
+
edge_groups = {frozenset(e.members) for e in self.edge_lineages}
|
|
463
|
+
if (
|
|
464
|
+
node_groups != set(expected_groups)
|
|
465
|
+
or len(node_groups) != len(self.node_lineages)
|
|
466
|
+
or len({n.lineage_id for n in self.node_lineages})
|
|
467
|
+
!= len(self.node_lineages)
|
|
468
|
+
or edge_groups != {frozenset(e.members) for e in expected_edges}
|
|
469
|
+
or len(edge_groups) != len(self.edge_lineages)
|
|
470
|
+
or len({e.lineage_id for e in self.edge_lineages})
|
|
471
|
+
!= len(self.edge_lineages)
|
|
472
|
+
):
|
|
473
|
+
return (
|
|
474
|
+
HistoryIssue(
|
|
475
|
+
HistoryIssueCode.ROUNDTRIP,
|
|
476
|
+
"Lineage partitions or identifiers disagree with supplied preservation maps.",
|
|
477
|
+
),
|
|
478
|
+
)
|
|
479
|
+
transported = {
|
|
480
|
+
n.lineage_id: expected_groups[frozenset(n.members)]
|
|
481
|
+
for n in self.node_lineages
|
|
482
|
+
}
|
|
483
|
+
endpoints = {frozenset(e.members): e.endpoints for e in expected_edges}
|
|
484
|
+
for edge in self.edge_lineages:
|
|
485
|
+
if (
|
|
486
|
+
not edge.endpoints <= set(transported)
|
|
487
|
+
or frozenset(transported[node] for node in edge.endpoints)
|
|
488
|
+
!= endpoints[frozenset(edge.members)]
|
|
489
|
+
):
|
|
490
|
+
return (
|
|
491
|
+
HistoryIssue(
|
|
492
|
+
HistoryIssueCode.ROUNDTRIP,
|
|
493
|
+
"An edge lineage does not have its preserved carrier endpoints.",
|
|
494
|
+
{"lineage": edge.lineage_id},
|
|
495
|
+
),
|
|
496
|
+
)
|
|
497
|
+
return ()
|
|
498
|
+
|
|
499
|
+
def configuration_cut(self, executed_events: Iterable[str]) -> ConfigurationCut:
|
|
500
|
+
"""Decode a downward-closed event set without choosing a schedule.
|
|
501
|
+
|
|
502
|
+
:param executed_events: Set of completed event occurrence IDs.
|
|
503
|
+
:return: Decoded cut, active materials, and complete lineage observations.
|
|
504
|
+
:raises HistoryError: If the history is invalid or the set is not an ideal.
|
|
505
|
+
"""
|
|
506
|
+
replay = self.replay()
|
|
507
|
+
if not replay.valid:
|
|
508
|
+
raise HistoryError(*replay.issues)
|
|
509
|
+
executed = frozenset(executed_events)
|
|
510
|
+
unknown = executed - set(self.event_by_id)
|
|
511
|
+
missing = {
|
|
512
|
+
first
|
|
513
|
+
for first, second in self.causal_pairs
|
|
514
|
+
if second in executed and first not in executed
|
|
515
|
+
}
|
|
516
|
+
if unknown or missing:
|
|
517
|
+
raise HistoryError(
|
|
518
|
+
HistoryIssue(
|
|
519
|
+
HistoryIssueCode.INVALID_CONFIGURATION,
|
|
520
|
+
"A configuration must be a downward-closed set of known event IDs.",
|
|
521
|
+
{
|
|
522
|
+
"unknown": tuple(sorted(unknown, key=repr)),
|
|
523
|
+
"missing_predecessors": tuple(sorted(missing)),
|
|
524
|
+
},
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
produced = {
|
|
528
|
+
b.material_id: e.occurrence_id for e in self.events for b in e.outputs
|
|
529
|
+
}
|
|
530
|
+
consumed = {
|
|
531
|
+
b.material_id: e.occurrence_id for e in self.events for b in e.inputs
|
|
532
|
+
}
|
|
533
|
+
active = frozenset(
|
|
534
|
+
m.occurrence_id
|
|
535
|
+
for m in self.materials
|
|
536
|
+
if (
|
|
537
|
+
m.occurrence_id not in produced or produced[m.occurrence_id] in executed
|
|
538
|
+
)
|
|
539
|
+
and (
|
|
540
|
+
m.occurrence_id not in consumed
|
|
541
|
+
or consumed[m.occurrence_id] not in executed
|
|
542
|
+
)
|
|
543
|
+
)
|
|
544
|
+
nodes = tuple((n, self._node_state(n, active)) for n in self.node_lineages)
|
|
545
|
+
edges = tuple((e, self._edge_state(e, active)) for e in self.edge_lineages)
|
|
546
|
+
world = self._decode_cut(nodes, edges)
|
|
547
|
+
return ConfigurationCut(self.process_id, executed, active, world, nodes, edges)
|
|
548
|
+
|
|
549
|
+
def _decode_cut(
|
|
550
|
+
self,
|
|
551
|
+
nodes: tuple[tuple[NodeLineage, CarrierState], ...],
|
|
552
|
+
edges: tuple[tuple[EdgeLineage, CarrierState], ...],
|
|
553
|
+
) -> LewisLabelledGraph:
|
|
554
|
+
graph = nx.Graph()
|
|
555
|
+
materials = self.material_by_id
|
|
556
|
+
for lineage, state in nodes:
|
|
557
|
+
if state.present:
|
|
558
|
+
graph.add_node(
|
|
559
|
+
lineage.lineage_id,
|
|
560
|
+
**materials[state.material_id].value.node_labels(state.carrier),
|
|
561
|
+
)
|
|
562
|
+
for lineage, state in edges:
|
|
563
|
+
if not state.present:
|
|
564
|
+
continue
|
|
565
|
+
endpoints = tuple(lineage.endpoints)
|
|
566
|
+
if len(endpoints) != 2 or not set(endpoints) <= set(graph):
|
|
567
|
+
raise HistoryError(
|
|
568
|
+
HistoryIssue(
|
|
569
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
570
|
+
"An active edge lacks distinct active endpoints.",
|
|
571
|
+
)
|
|
572
|
+
)
|
|
573
|
+
if graph.has_edge(*endpoints):
|
|
574
|
+
raise HistoryError(
|
|
575
|
+
HistoryIssue(
|
|
576
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
577
|
+
"Two active edge lineages occupy one simple bond.",
|
|
578
|
+
)
|
|
579
|
+
)
|
|
580
|
+
graph.add_edge(
|
|
581
|
+
*endpoints,
|
|
582
|
+
**materials[state.material_id].value.edge_labels(state.carrier),
|
|
583
|
+
)
|
|
584
|
+
return LewisLabelledGraph.from_networkx(
|
|
585
|
+
(
|
|
586
|
+
derive_electron_labeled_graph(graph)
|
|
587
|
+
if is_electron_schema(self.schema)
|
|
588
|
+
else graph
|
|
589
|
+
),
|
|
590
|
+
self.schema,
|
|
591
|
+
name=f"{self.process_id}:configuration",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
def _flow_roles(self) -> tuple[set[str], set[str]]:
|
|
595
|
+
produced = {
|
|
596
|
+
binding.material_id for event in self.events for binding in event.outputs
|
|
597
|
+
}
|
|
598
|
+
consumed = {
|
|
599
|
+
binding.material_id for event in self.events for binding in event.inputs
|
|
600
|
+
}
|
|
601
|
+
return produced, consumed
|
|
602
|
+
|
|
603
|
+
@property
|
|
604
|
+
def initial_material_ids(self) -> frozenset[str]:
|
|
605
|
+
produced, _ = self._flow_roles()
|
|
606
|
+
return frozenset(set(self.material_by_id) - produced)
|
|
607
|
+
|
|
608
|
+
@property
|
|
609
|
+
def final_material_ids(self) -> frozenset[str]:
|
|
610
|
+
_, consumed = self._flow_roles()
|
|
611
|
+
return frozenset(set(self.material_by_id) - consumed)
|
|
612
|
+
|
|
613
|
+
def _active_cuts(self, extension: Iterable[str]) -> tuple[frozenset[str], ...]:
|
|
614
|
+
process = self.to_process()
|
|
615
|
+
order = tuple(extension)
|
|
616
|
+
if not process.is_linear_extension(order):
|
|
617
|
+
raise HistoryError(
|
|
618
|
+
HistoryIssue(
|
|
619
|
+
HistoryIssueCode.INVALID_EXTENSION,
|
|
620
|
+
"A history timeline requires a valid process linear extension.",
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
active = set(self.initial_material_ids)
|
|
624
|
+
cuts = [frozenset(active)]
|
|
625
|
+
for event_id in order:
|
|
626
|
+
event = self.event_by_id[event_id]
|
|
627
|
+
inputs = {binding.material_id for binding in event.inputs}
|
|
628
|
+
outputs = {binding.material_id for binding in event.outputs}
|
|
629
|
+
if not inputs <= active:
|
|
630
|
+
raise HistoryError(
|
|
631
|
+
HistoryIssue(
|
|
632
|
+
HistoryIssueCode.INACTIVE_INPUT,
|
|
633
|
+
"An event consumes material absent at its process cut.",
|
|
634
|
+
{
|
|
635
|
+
"event": event_id,
|
|
636
|
+
"materials": tuple(sorted(inputs - active)),
|
|
637
|
+
},
|
|
638
|
+
)
|
|
639
|
+
)
|
|
640
|
+
if outputs & active:
|
|
641
|
+
raise HistoryError(
|
|
642
|
+
HistoryIssue(
|
|
643
|
+
HistoryIssueCode.ACTIVE_OUTPUT,
|
|
644
|
+
"An event produces an already active material occurrence.",
|
|
645
|
+
{"event": event_id},
|
|
646
|
+
)
|
|
647
|
+
)
|
|
648
|
+
active.difference_update(inputs)
|
|
649
|
+
active.update(outputs)
|
|
650
|
+
cuts.append(frozenset(active))
|
|
651
|
+
return tuple(cuts)
|
|
652
|
+
|
|
653
|
+
def _node_state(self, lineage: NodeLineage, active: frozenset[str]) -> CarrierState:
|
|
654
|
+
members = tuple(member for member in lineage.members if member[0] in active)
|
|
655
|
+
if not members:
|
|
656
|
+
return ABSENT
|
|
657
|
+
if len(members) != 1:
|
|
658
|
+
raise HistoryError(
|
|
659
|
+
HistoryIssue(
|
|
660
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
661
|
+
"A process cut contains two active copies of one node lineage.",
|
|
662
|
+
{"lineage": lineage.lineage_id},
|
|
663
|
+
)
|
|
664
|
+
)
|
|
665
|
+
material_id, node = members[0]
|
|
666
|
+
labels = tuple(
|
|
667
|
+
sorted(
|
|
668
|
+
self.material_by_id[material_id]
|
|
669
|
+
.value.node_labels(node, semantic=True)
|
|
670
|
+
.items()
|
|
671
|
+
)
|
|
672
|
+
)
|
|
673
|
+
return CarrierState(True, material_id, node, labels)
|
|
674
|
+
|
|
675
|
+
def _edge_state(self, lineage: EdgeLineage, active: frozenset[str]) -> CarrierState:
|
|
676
|
+
members = tuple(member for member in lineage.members if member[0] in active)
|
|
677
|
+
if not members:
|
|
678
|
+
return ABSENT
|
|
679
|
+
if len(members) != 1:
|
|
680
|
+
raise HistoryError(
|
|
681
|
+
HistoryIssue(
|
|
682
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
683
|
+
"A process cut contains two active copies of one edge lineage.",
|
|
684
|
+
{"lineage": lineage.lineage_id},
|
|
685
|
+
)
|
|
686
|
+
)
|
|
687
|
+
material_id, edge = members[0]
|
|
688
|
+
labels = tuple(
|
|
689
|
+
sorted(
|
|
690
|
+
self.material_by_id[material_id]
|
|
691
|
+
.value.edge_labels(edge, semantic=True)
|
|
692
|
+
.items()
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
return CarrierState(True, material_id, edge, labels)
|
|
696
|
+
|
|
697
|
+
def full_history(self, extension: Iterable[str]) -> nx.MultiGraph:
|
|
698
|
+
"""Project all node/edge lineages over one certified linear extension."""
|
|
699
|
+
replay = self.replay()
|
|
700
|
+
if not replay.valid:
|
|
701
|
+
raise HistoryError(*replay.issues)
|
|
702
|
+
order = tuple(extension)
|
|
703
|
+
cuts = self._active_cuts(order)
|
|
704
|
+
graph = nx.MultiGraph()
|
|
705
|
+
graph.graph.update(
|
|
706
|
+
process_id=self.process_id,
|
|
707
|
+
event_order=order,
|
|
708
|
+
causal_pairs=self.to_process().causal_pairs,
|
|
709
|
+
cut_count=len(cuts),
|
|
710
|
+
)
|
|
711
|
+
for lineage in self.node_lineages:
|
|
712
|
+
graph.add_node(
|
|
713
|
+
lineage.lineage_id,
|
|
714
|
+
members=lineage.members,
|
|
715
|
+
history=tuple(self._node_state(lineage, cut) for cut in cuts),
|
|
716
|
+
)
|
|
717
|
+
for lineage in self.edge_lineages:
|
|
718
|
+
if len(lineage.endpoints) != 2:
|
|
719
|
+
raise HistoryError(
|
|
720
|
+
HistoryIssue(
|
|
721
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
722
|
+
"A simple edge lineage must have two node endpoints.",
|
|
723
|
+
)
|
|
724
|
+
)
|
|
725
|
+
left, right = tuple(lineage.endpoints)
|
|
726
|
+
graph.add_edge(
|
|
727
|
+
left,
|
|
728
|
+
right,
|
|
729
|
+
key=lineage.lineage_id,
|
|
730
|
+
lineage_id=lineage.lineage_id,
|
|
731
|
+
members=lineage.members,
|
|
732
|
+
history=tuple(self._edge_state(lineage, cut) for cut in cuts),
|
|
733
|
+
)
|
|
734
|
+
return graph
|
|
735
|
+
|
|
736
|
+
def _aggregate(self, material_ids: frozenset[str], name: str) -> LewisLabelledGraph:
|
|
737
|
+
schema = self.schema
|
|
738
|
+
graph = nx.Graph()
|
|
739
|
+
for material_id in sorted(material_ids):
|
|
740
|
+
material = self.material_by_id[material_id]
|
|
741
|
+
for node in material.value.node_ids:
|
|
742
|
+
graph.add_node((material_id, node), **material.value.node_labels(node))
|
|
743
|
+
for edge in material.value.edge_keys:
|
|
744
|
+
left, right = tuple(edge)
|
|
745
|
+
graph.add_edge(
|
|
746
|
+
(material_id, left),
|
|
747
|
+
(material_id, right),
|
|
748
|
+
**material.value.edge_labels(edge),
|
|
749
|
+
)
|
|
750
|
+
return LewisLabelledGraph.from_networkx(graph, schema, name=name)
|
|
751
|
+
|
|
752
|
+
def outer_rule(self) -> RuleSpan:
|
|
753
|
+
"""Return the initial-to-final transformation induced by carrier lineages."""
|
|
754
|
+
replay = self.replay()
|
|
755
|
+
if not replay.valid:
|
|
756
|
+
raise HistoryError(*replay.issues)
|
|
757
|
+
left = self._aggregate(self.initial_material_ids, f"{self.process_id}:initial")
|
|
758
|
+
right = self._aggregate(self.final_material_ids, f"{self.process_id}:final")
|
|
759
|
+
preserved = {}
|
|
760
|
+
for lineage in self.node_lineages:
|
|
761
|
+
initial = tuple(
|
|
762
|
+
member
|
|
763
|
+
for member in lineage.members
|
|
764
|
+
if member[0] in self.initial_material_ids
|
|
765
|
+
)
|
|
766
|
+
final = tuple(
|
|
767
|
+
member
|
|
768
|
+
for member in lineage.members
|
|
769
|
+
if member[0] in self.final_material_ids
|
|
770
|
+
)
|
|
771
|
+
if len(initial) == len(final) == 1:
|
|
772
|
+
preserved[initial[0]] = final[0]
|
|
773
|
+
elif len(initial) > 1 or len(final) > 1:
|
|
774
|
+
raise HistoryError(
|
|
775
|
+
HistoryIssue(
|
|
776
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
777
|
+
"Outer projection has multiple boundary copies in one lineage.",
|
|
778
|
+
)
|
|
779
|
+
)
|
|
780
|
+
preserved_edges = set()
|
|
781
|
+
for lineage in self.edge_lineages:
|
|
782
|
+
initial = tuple(
|
|
783
|
+
member
|
|
784
|
+
for member in lineage.members
|
|
785
|
+
if member[0] in self.initial_material_ids
|
|
786
|
+
)
|
|
787
|
+
final = tuple(
|
|
788
|
+
member
|
|
789
|
+
for member in lineage.members
|
|
790
|
+
if member[0] in self.final_material_ids
|
|
791
|
+
)
|
|
792
|
+
if len(initial) == len(final) == 1:
|
|
793
|
+
material_id, edge = initial[0]
|
|
794
|
+
preserved_edges.add(frozenset((material_id, node) for node in edge))
|
|
795
|
+
elif len(initial) > 1 or len(final) > 1:
|
|
796
|
+
raise HistoryError(
|
|
797
|
+
HistoryIssue(
|
|
798
|
+
HistoryIssueCode.LINEAGE_CONFLICT,
|
|
799
|
+
"Outer projection has multiple boundary edges in one lineage.",
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
try:
|
|
803
|
+
return RuleSpan.from_mapping(
|
|
804
|
+
left,
|
|
805
|
+
right,
|
|
806
|
+
preserved,
|
|
807
|
+
preserved_edges=preserved_edges,
|
|
808
|
+
name=f"{self.process_id}:outer",
|
|
809
|
+
)
|
|
810
|
+
except DPOError as error:
|
|
811
|
+
raise HistoryError(
|
|
812
|
+
HistoryIssue(
|
|
813
|
+
HistoryIssueCode.OUTER_RULE,
|
|
814
|
+
"The history boundary does not form an admitted outer rule.",
|
|
815
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
816
|
+
)
|
|
817
|
+
) from error
|
|
818
|
+
|
|
819
|
+
def composed_change_graph(self) -> nx.MultiGraph:
|
|
820
|
+
"""Project only initial/final lineage states, analogous to a composed ITS."""
|
|
821
|
+
initial = self.configuration_cut(())
|
|
822
|
+
final = self.configuration_cut(self.event_by_id)
|
|
823
|
+
final_nodes = {n.lineage_id: state for n, state in final.node_states}
|
|
824
|
+
final_edges = {e.lineage_id: state for e, state in final.edge_states}
|
|
825
|
+
result = nx.MultiGraph()
|
|
826
|
+
result.graph.update(process_id=self.process_id, projection="composed")
|
|
827
|
+
for lineage, state in initial.node_states:
|
|
828
|
+
result.add_node(
|
|
829
|
+
lineage.lineage_id, left=state, right=final_nodes[lineage.lineage_id]
|
|
830
|
+
)
|
|
831
|
+
for lineage, state in initial.edge_states:
|
|
832
|
+
final_state = final_edges[lineage.lineage_id]
|
|
833
|
+
if state.present or final_state.present:
|
|
834
|
+
left, right = tuple(lineage.endpoints)
|
|
835
|
+
result.add_edge(
|
|
836
|
+
left,
|
|
837
|
+
right,
|
|
838
|
+
key=lineage.lineage_id,
|
|
839
|
+
left=state,
|
|
840
|
+
right=final_state,
|
|
841
|
+
)
|
|
842
|
+
return result
|
|
843
|
+
|
|
844
|
+
def minimal_changed_core(self, extension: Iterable[str]) -> nx.MultiGraph:
|
|
845
|
+
"""Retain every carrier whose full history changes, including transients."""
|
|
846
|
+
full = self.full_history(extension)
|
|
847
|
+
changed_edges = []
|
|
848
|
+
changed_nodes = set()
|
|
849
|
+
for left, right, key, attrs in full.edges(keys=True, data=True):
|
|
850
|
+
signatures = {(state.present, state.labels) for state in attrs["history"]}
|
|
851
|
+
if len(signatures) > 1:
|
|
852
|
+
changed_edges.append((left, right, key, attrs))
|
|
853
|
+
changed_nodes.update((left, right))
|
|
854
|
+
for node, attrs in full.nodes(data=True):
|
|
855
|
+
signatures = {(state.present, state.labels) for state in attrs["history"]}
|
|
856
|
+
if len(signatures) > 1:
|
|
857
|
+
changed_nodes.add(node)
|
|
858
|
+
result = nx.MultiGraph()
|
|
859
|
+
result.graph.update(
|
|
860
|
+
process_id=self.process_id, projection="minimal-changed-core"
|
|
861
|
+
)
|
|
862
|
+
for node in sorted(changed_nodes):
|
|
863
|
+
result.add_node(node, **full.nodes[node])
|
|
864
|
+
for left, right, key, attrs in changed_edges:
|
|
865
|
+
result.add_edge(left, right, key=key, **attrs)
|
|
866
|
+
return result
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
@dataclass(frozen=True)
|
|
870
|
+
class OccurrenceMTGAlternative:
|
|
871
|
+
alternative_id: str
|
|
872
|
+
history: OccurrenceMTG
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
@dataclass(frozen=True)
|
|
876
|
+
class OccurrenceMTGFamily:
|
|
877
|
+
"""One MTG per process alternative; no material-flow quotient is applied."""
|
|
878
|
+
|
|
879
|
+
alternatives: tuple[OccurrenceMTGAlternative, ...]
|
|
880
|
+
choices: tuple[ChoiceWitness, ...]
|
|
881
|
+
|
|
882
|
+
@classmethod
|
|
883
|
+
def from_process_family(
|
|
884
|
+
cls, family: OccurrenceProcessFamily
|
|
885
|
+
) -> "OccurrenceMTGFamily":
|
|
886
|
+
alternatives = tuple(
|
|
887
|
+
OccurrenceMTGAlternative(
|
|
888
|
+
alternative.alternative_id,
|
|
889
|
+
OccurrenceMTG.from_process(alternative.process),
|
|
890
|
+
)
|
|
891
|
+
for alternative in family.alternatives
|
|
892
|
+
)
|
|
893
|
+
result = cls(alternatives, family.choices)
|
|
894
|
+
if {item.alternative_id for item in alternatives} != {
|
|
895
|
+
item.alternative_id for item in family.alternatives
|
|
896
|
+
}:
|
|
897
|
+
raise HistoryError(
|
|
898
|
+
HistoryIssue(
|
|
899
|
+
HistoryIssueCode.ALTERNATIVE_MISMATCH,
|
|
900
|
+
"MTG derivation changed the process alternative family.",
|
|
901
|
+
)
|
|
902
|
+
)
|
|
903
|
+
return result
|
|
904
|
+
|
|
905
|
+
def to_process_family(self) -> OccurrenceProcessFamily:
|
|
906
|
+
return OccurrenceProcessFamily(
|
|
907
|
+
tuple(
|
|
908
|
+
ProcessAlternative(item.alternative_id, item.history.to_process())
|
|
909
|
+
for item in self.alternatives
|
|
910
|
+
),
|
|
911
|
+
self.choices,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
__all__ = [
|
|
916
|
+
"ABSENT",
|
|
917
|
+
"CarrierState",
|
|
918
|
+
"ConfigurationCut",
|
|
919
|
+
"EdgeLineage",
|
|
920
|
+
"HistoryError",
|
|
921
|
+
"HistoryIssue",
|
|
922
|
+
"HistoryIssueCode",
|
|
923
|
+
"HistoryReplay",
|
|
924
|
+
"NodeLineage",
|
|
925
|
+
"OccurrenceMTG",
|
|
926
|
+
"OccurrenceMTGAlternative",
|
|
927
|
+
"OccurrenceMTGFamily",
|
|
928
|
+
]
|