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,976 @@
|
|
|
1
|
+
"""Binary DPO rule composition along one explicit certified overlap."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any, Hashable, Mapping
|
|
9
|
+
|
|
10
|
+
import networkx as nx
|
|
11
|
+
|
|
12
|
+
from synkit.Graph.Morphism import (
|
|
13
|
+
ELECTRON_LLG_SCHEMA,
|
|
14
|
+
LLGError,
|
|
15
|
+
LLGMorphism,
|
|
16
|
+
LewisLabelledGraph,
|
|
17
|
+
derive_electron_labeled_graph,
|
|
18
|
+
is_electron_schema,
|
|
19
|
+
)
|
|
20
|
+
from synkit.Rule.Apply import (
|
|
21
|
+
DPOApplication,
|
|
22
|
+
DPOError,
|
|
23
|
+
EnvironmentToken,
|
|
24
|
+
RuleEmbedding,
|
|
25
|
+
RuleSpan,
|
|
26
|
+
SystemBoundary,
|
|
27
|
+
apply_dpo,
|
|
28
|
+
)
|
|
29
|
+
from synkit.Rule.Apply.conditions import SequentialCondition
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CompositionIssueCode(str, Enum):
|
|
33
|
+
"""Stable failures for explicit binary rule composition."""
|
|
34
|
+
|
|
35
|
+
SCHEMA_MISMATCH = "COMPOSITION_SCHEMA_MISMATCH"
|
|
36
|
+
OVERLAP_PARTIAL = "COMPOSITION_OVERLAP_PARTIAL"
|
|
37
|
+
OVERLAP_NON_INJECTIVE = "COMPOSITION_OVERLAP_NON_INJECTIVE"
|
|
38
|
+
OVERLAP_NODE_LABEL = "COMPOSITION_OVERLAP_NODE_LABEL"
|
|
39
|
+
OVERLAP_EDGE_LABEL = "COMPOSITION_OVERLAP_EDGE_LABEL"
|
|
40
|
+
OVERLAP_EDGE_CLOSURE = "COMPOSITION_OVERLAP_EDGE_CLOSURE"
|
|
41
|
+
PULLBACK_COMPLEMENT = "COMPOSITION_PULLBACK_COMPLEMENT"
|
|
42
|
+
PARALLEL_EDGE_CONFLICT = "COMPOSITION_PARALLEL_EDGE_CONFLICT"
|
|
43
|
+
FIRST_APPLICATION = "COMPOSITION_FIRST_APPLICATION"
|
|
44
|
+
SECOND_APPLICATION = "COMPOSITION_SECOND_APPLICATION"
|
|
45
|
+
RESOURCE_MISMATCH = "COMPOSITION_RESOURCE_MISMATCH"
|
|
46
|
+
OUTER_REPLAY = "COMPOSITION_OUTER_REPLAY"
|
|
47
|
+
NONCOMMUTATIVE = "COMPOSITION_NONCOMMUTATIVE"
|
|
48
|
+
INCOMPLETE_PROVENANCE = "COMPOSITION_INCOMPLETE_PROVENANCE"
|
|
49
|
+
SOURCE_MUTATED = "COMPOSITION_SOURCE_MUTATED"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class CompositionIssue:
|
|
54
|
+
code: CompositionIssueCode
|
|
55
|
+
message: str
|
|
56
|
+
context: Mapping[str, Any] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"code": self.code.value,
|
|
61
|
+
"message": self.message,
|
|
62
|
+
"context": dict(self.context),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CompositionError(ValueError):
|
|
67
|
+
"""Raised when the declared overlap has no admitted composite."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, *issues: CompositionIssue):
|
|
70
|
+
self.issues = tuple(issues)
|
|
71
|
+
super().__init__("; ".join(issue.message for issue in issues))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class RuleOverlap:
|
|
76
|
+
"""An explicit overlap span ``R1 <- D -> L2``."""
|
|
77
|
+
|
|
78
|
+
interface: LewisLabelledGraph
|
|
79
|
+
first_arm: LLGMorphism
|
|
80
|
+
second_arm: LLGMorphism
|
|
81
|
+
|
|
82
|
+
def __post_init__(self) -> None:
|
|
83
|
+
if (
|
|
84
|
+
self.first_arm.source != self.interface
|
|
85
|
+
or self.second_arm.source != self.interface
|
|
86
|
+
):
|
|
87
|
+
raise CompositionError(
|
|
88
|
+
CompositionIssue(
|
|
89
|
+
CompositionIssueCode.OVERLAP_PARTIAL,
|
|
90
|
+
"Both overlap arms must start at the declared interface.",
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
if self.first_arm.target.schema != self.second_arm.target.schema:
|
|
94
|
+
raise CompositionError(
|
|
95
|
+
CompositionIssue(
|
|
96
|
+
CompositionIssueCode.SCHEMA_MISMATCH,
|
|
97
|
+
"Overlap endpoints must use the same LLG schema.",
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def from_mapping(
|
|
103
|
+
cls,
|
|
104
|
+
first_right: LewisLabelledGraph,
|
|
105
|
+
second_left: LewisLabelledGraph,
|
|
106
|
+
mapping: Mapping[Hashable, Hashable],
|
|
107
|
+
*,
|
|
108
|
+
overlap_edges: set[frozenset[Hashable]] | None = None,
|
|
109
|
+
name: str = "overlap",
|
|
110
|
+
) -> "RuleOverlap":
|
|
111
|
+
"""Create an overlap; edge keys, when supplied, belong to ``R1``."""
|
|
112
|
+
if first_right.schema != second_left.schema:
|
|
113
|
+
raise CompositionError(
|
|
114
|
+
CompositionIssue(
|
|
115
|
+
CompositionIssueCode.SCHEMA_MISMATCH,
|
|
116
|
+
"Overlap endpoints must use the same LLG schema.",
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
pairs = tuple(sorted(mapping.items(), key=repr))
|
|
120
|
+
first_nodes = tuple(left for left, _ in pairs)
|
|
121
|
+
second_nodes = tuple(right for _, right in pairs)
|
|
122
|
+
if len(set(first_nodes)) != len(first_nodes) or (
|
|
123
|
+
set(first_nodes) - set(first_right.node_ids)
|
|
124
|
+
):
|
|
125
|
+
raise CompositionError(
|
|
126
|
+
CompositionIssue(
|
|
127
|
+
CompositionIssueCode.OVERLAP_PARTIAL,
|
|
128
|
+
"Every overlap source node must occur once in R1.",
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
if len(set(second_nodes)) != len(second_nodes):
|
|
132
|
+
raise CompositionError(
|
|
133
|
+
CompositionIssue(
|
|
134
|
+
CompositionIssueCode.OVERLAP_NON_INJECTIVE,
|
|
135
|
+
"The overlap mapping must be injective.",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
if set(second_nodes) - set(second_left.node_ids):
|
|
139
|
+
raise CompositionError(
|
|
140
|
+
CompositionIssue(
|
|
141
|
+
CompositionIssueCode.OVERLAP_PARTIAL,
|
|
142
|
+
"Every overlap image must lie in L2.",
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
interface_nx = nx.Graph()
|
|
147
|
+
first_to_interface: dict[Hashable, int] = {}
|
|
148
|
+
for interface_node, (first_node, second_node) in enumerate(pairs):
|
|
149
|
+
first_labels = first_right.node_labels(first_node, semantic=True)
|
|
150
|
+
second_labels = second_left.node_labels(second_node, semantic=True)
|
|
151
|
+
if first_labels != second_labels:
|
|
152
|
+
raise CompositionError(
|
|
153
|
+
CompositionIssue(
|
|
154
|
+
CompositionIssueCode.OVERLAP_NODE_LABEL,
|
|
155
|
+
"Mapped overlap nodes disagree on semantic labels.",
|
|
156
|
+
{"first": repr(first_node), "second": repr(second_node)},
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
interface_nx.add_node(interface_node, **first_right.node_labels(first_node))
|
|
160
|
+
first_to_interface[first_node] = interface_node
|
|
161
|
+
|
|
162
|
+
candidates = {
|
|
163
|
+
edge
|
|
164
|
+
for edge in first_right.edge_keys
|
|
165
|
+
if edge <= set(first_nodes)
|
|
166
|
+
and frozenset(mapping[node] for node in edge) in second_left.edge_keys
|
|
167
|
+
}
|
|
168
|
+
selected = candidates if overlap_edges is None else set(overlap_edges)
|
|
169
|
+
if not selected <= candidates:
|
|
170
|
+
raise CompositionError(
|
|
171
|
+
CompositionIssue(
|
|
172
|
+
CompositionIssueCode.OVERLAP_EDGE_CLOSURE,
|
|
173
|
+
"Each declared overlap edge must exist under both arms.",
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
for first_edge in selected:
|
|
177
|
+
second_edge = frozenset(mapping[node] for node in first_edge)
|
|
178
|
+
if first_right.edge_labels(
|
|
179
|
+
first_edge, semantic=True
|
|
180
|
+
) != second_left.edge_labels(second_edge, semantic=True):
|
|
181
|
+
raise CompositionError(
|
|
182
|
+
CompositionIssue(
|
|
183
|
+
CompositionIssueCode.OVERLAP_EDGE_LABEL,
|
|
184
|
+
"Mapped overlap edges disagree on semantic labels.",
|
|
185
|
+
{"edge": tuple(sorted(map(repr, first_edge)))},
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
left, right = tuple(first_edge)
|
|
189
|
+
interface_nx.add_edge(
|
|
190
|
+
first_to_interface[left],
|
|
191
|
+
first_to_interface[right],
|
|
192
|
+
**first_right.edge_labels(first_edge),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
interface = LewisLabelledGraph.from_networkx(
|
|
196
|
+
interface_nx, first_right.schema, name=name
|
|
197
|
+
)
|
|
198
|
+
first_map = {
|
|
199
|
+
interface_node: first_node
|
|
200
|
+
for interface_node, (first_node, _) in enumerate(pairs)
|
|
201
|
+
}
|
|
202
|
+
second_map = {
|
|
203
|
+
interface_node: second_node
|
|
204
|
+
for interface_node, (_, second_node) in enumerate(pairs)
|
|
205
|
+
}
|
|
206
|
+
try:
|
|
207
|
+
return cls(
|
|
208
|
+
interface,
|
|
209
|
+
LLGMorphism(interface, first_right, first_map),
|
|
210
|
+
LLGMorphism(interface, second_left, second_map),
|
|
211
|
+
)
|
|
212
|
+
except LLGError as error:
|
|
213
|
+
raise CompositionError(
|
|
214
|
+
CompositionIssue(
|
|
215
|
+
CompositionIssueCode.OVERLAP_EDGE_LABEL,
|
|
216
|
+
"The overlap arms are not strict LLG morphisms.",
|
|
217
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
218
|
+
)
|
|
219
|
+
) from error
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True)
|
|
223
|
+
class ProvenanceRef:
|
|
224
|
+
rule: int
|
|
225
|
+
side: str
|
|
226
|
+
item: Hashable
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True)
|
|
230
|
+
class CompositionProvenance:
|
|
231
|
+
"""Source-rule and overlap origins for every composite carrier item."""
|
|
232
|
+
|
|
233
|
+
node_sources: tuple[tuple[str, Hashable, tuple[ProvenanceRef, ...]], ...]
|
|
234
|
+
edge_sources: tuple[tuple[str, frozenset[Hashable], tuple[ProvenanceRef, ...]], ...]
|
|
235
|
+
overlap_nodes: tuple[tuple[Hashable, Hashable, Hashable], ...]
|
|
236
|
+
overlap_edges: tuple[
|
|
237
|
+
tuple[frozenset[Hashable], frozenset[Hashable], frozenset[Hashable]], ...
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dataclass(frozen=True)
|
|
242
|
+
class CompositionReplay:
|
|
243
|
+
valid: bool
|
|
244
|
+
issues: tuple[CompositionIssue, ...] = ()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass(frozen=True)
|
|
248
|
+
class CompositionCertificate:
|
|
249
|
+
"""Replayable proof for one explicit-overlap composition."""
|
|
250
|
+
|
|
251
|
+
first: RuleSpan
|
|
252
|
+
second: RuleSpan
|
|
253
|
+
overlap: RuleOverlap
|
|
254
|
+
minimal_host: LewisLabelledGraph
|
|
255
|
+
first_application: DPOApplication
|
|
256
|
+
second_application: DPOApplication
|
|
257
|
+
composite: RuleSpan
|
|
258
|
+
outer_application: DPOApplication
|
|
259
|
+
second_match: tuple[tuple[Hashable, Hashable], ...]
|
|
260
|
+
provenance: CompositionProvenance
|
|
261
|
+
|
|
262
|
+
def replay(self) -> CompositionReplay:
|
|
263
|
+
issues = _application_link_issues(self)
|
|
264
|
+
if not self.first_application.certificate.replay().valid:
|
|
265
|
+
issues.append(
|
|
266
|
+
CompositionIssue(
|
|
267
|
+
CompositionIssueCode.NONCOMMUTATIVE,
|
|
268
|
+
"The first DPO certificate no longer replays.",
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
if not self.second_application.certificate.replay().valid:
|
|
272
|
+
issues.append(
|
|
273
|
+
CompositionIssue(
|
|
274
|
+
CompositionIssueCode.NONCOMMUTATIVE,
|
|
275
|
+
"The second DPO certificate no longer replays.",
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
if not self.outer_application.certificate.replay().valid:
|
|
279
|
+
issues.append(
|
|
280
|
+
CompositionIssue(
|
|
281
|
+
CompositionIssueCode.OUTER_REPLAY,
|
|
282
|
+
"The outer DPO certificate no longer replays.",
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
if not self.second_application.result.is_isomorphic(
|
|
286
|
+
self.outer_application.result
|
|
287
|
+
):
|
|
288
|
+
issues.append(
|
|
289
|
+
CompositionIssue(
|
|
290
|
+
CompositionIssueCode.OUTER_REPLAY,
|
|
291
|
+
"Sequential and composed applications disagree.",
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
left_node_keys = {
|
|
295
|
+
(side, node)
|
|
296
|
+
for side, graph in (
|
|
297
|
+
("left", self.composite.left),
|
|
298
|
+
("right", self.composite.right),
|
|
299
|
+
)
|
|
300
|
+
for node in graph.node_ids
|
|
301
|
+
}
|
|
302
|
+
observed_nodes = {
|
|
303
|
+
(side, node) for side, node, refs in self.provenance.node_sources if refs
|
|
304
|
+
}
|
|
305
|
+
left_edge_keys = {
|
|
306
|
+
(side, edge)
|
|
307
|
+
for side, graph in (
|
|
308
|
+
("left", self.composite.left),
|
|
309
|
+
("right", self.composite.right),
|
|
310
|
+
)
|
|
311
|
+
for edge in graph.edge_keys
|
|
312
|
+
}
|
|
313
|
+
observed_edges = {
|
|
314
|
+
(side, edge) for side, edge, refs in self.provenance.edge_sources if refs
|
|
315
|
+
}
|
|
316
|
+
if left_node_keys != observed_nodes or left_edge_keys != observed_edges:
|
|
317
|
+
issues.append(
|
|
318
|
+
CompositionIssue(
|
|
319
|
+
CompositionIssueCode.INCOMPLETE_PROVENANCE,
|
|
320
|
+
"Composite carrier provenance is incomplete.",
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
return CompositionReplay(not issues, tuple(issues))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@dataclass(frozen=True)
|
|
327
|
+
class CompositionResult:
|
|
328
|
+
rule: RuleSpan
|
|
329
|
+
certificate: CompositionCertificate
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _application_link_issues(c: CompositionCertificate) -> list[CompositionIssue]:
|
|
333
|
+
"""Tie retained proofs to the actual applications and dependency witness."""
|
|
334
|
+
first, second, outer = (
|
|
335
|
+
c.first_application,
|
|
336
|
+
c.second_application,
|
|
337
|
+
c.outer_application,
|
|
338
|
+
)
|
|
339
|
+
checks = (
|
|
340
|
+
first.certificate.rule == c.first,
|
|
341
|
+
first.certificate.host == c.minimal_host,
|
|
342
|
+
second.certificate.rule == c.second,
|
|
343
|
+
second.certificate.host == first.result,
|
|
344
|
+
outer.certificate.rule == c.composite,
|
|
345
|
+
outer.certificate.host == c.minimal_host,
|
|
346
|
+
c.composite.left == c.minimal_host,
|
|
347
|
+
c.composite.right == second.result,
|
|
348
|
+
dict(second.certificate.match) == dict(c.second_match),
|
|
349
|
+
c.overlap.first_arm.target == c.first.right,
|
|
350
|
+
c.overlap.second_arm.target == c.second.left,
|
|
351
|
+
)
|
|
352
|
+
expected_condition = SequentialCondition(
|
|
353
|
+
c.first,
|
|
354
|
+
c.second,
|
|
355
|
+
first.certificate.match,
|
|
356
|
+
first.certificate.right_to_result,
|
|
357
|
+
c.second_match,
|
|
358
|
+
)
|
|
359
|
+
right_result = dict(first.certificate.right_to_result)
|
|
360
|
+
second_match = dict(c.second_match)
|
|
361
|
+
overlap_consistent = all(
|
|
362
|
+
right_result.get(c.overlap.first_arm.mapping[n])
|
|
363
|
+
== second_match.get(c.overlap.second_arm.mapping[n])
|
|
364
|
+
for n in c.overlap.interface.node_ids
|
|
365
|
+
)
|
|
366
|
+
if (
|
|
367
|
+
not all(checks)
|
|
368
|
+
or not overlap_consistent
|
|
369
|
+
or c.composite.condition != expected_condition
|
|
370
|
+
or any(a.result != a.certificate.result for a in (first, second, outer))
|
|
371
|
+
):
|
|
372
|
+
return [
|
|
373
|
+
CompositionIssue(
|
|
374
|
+
CompositionIssueCode.NONCOMMUTATIVE,
|
|
375
|
+
"Application objects, sequential condition, and retained proofs disagree.",
|
|
376
|
+
)
|
|
377
|
+
]
|
|
378
|
+
return []
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@dataclass
|
|
382
|
+
class _HostPlan:
|
|
383
|
+
graph: nx.Graph
|
|
384
|
+
second_left_to_initial: dict[Hashable, Hashable | None]
|
|
385
|
+
node_refs: dict[Hashable, set[ProvenanceRef]]
|
|
386
|
+
edge_refs: dict[frozenset[Hashable], set[ProvenanceRef]]
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _fresh_context_nodes(existing: set[Hashable], count: int) -> tuple[Hashable, ...]:
|
|
390
|
+
result = []
|
|
391
|
+
index = 0
|
|
392
|
+
while len(result) < count:
|
|
393
|
+
candidate = ("__composition_context__", index)
|
|
394
|
+
index += 1
|
|
395
|
+
if candidate not in existing:
|
|
396
|
+
result.append(candidate)
|
|
397
|
+
existing.add(candidate)
|
|
398
|
+
return tuple(result)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _overlap_edge_closed(overlap: RuleOverlap) -> None:
|
|
402
|
+
first = overlap.first_arm.target
|
|
403
|
+
second = overlap.second_arm.target
|
|
404
|
+
first_map = overlap.first_arm.mapping
|
|
405
|
+
second_map = overlap.second_arm.mapping
|
|
406
|
+
inverse_second = {node: source for source, node in second_map.items()}
|
|
407
|
+
interface_edges = set(overlap.interface.edge_keys)
|
|
408
|
+
for second_edge in second.edge_keys:
|
|
409
|
+
if not second_edge <= set(inverse_second):
|
|
410
|
+
continue
|
|
411
|
+
interface_edge = frozenset(inverse_second[node] for node in second_edge)
|
|
412
|
+
first_edge = frozenset(first_map[node] for node in interface_edge)
|
|
413
|
+
if first_edge in first.edge_keys and interface_edge not in interface_edges:
|
|
414
|
+
raise CompositionError(
|
|
415
|
+
CompositionIssue(
|
|
416
|
+
CompositionIssueCode.OVERLAP_EDGE_CLOSURE,
|
|
417
|
+
"The declared overlap omits an edge shared by both endpoints.",
|
|
418
|
+
{"edge": tuple(sorted(map(repr, second_edge)))},
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _initial_node_plan(
|
|
424
|
+
first: RuleSpan,
|
|
425
|
+
second: RuleSpan,
|
|
426
|
+
overlap: RuleOverlap,
|
|
427
|
+
graph: nx.Graph,
|
|
428
|
+
) -> tuple[
|
|
429
|
+
dict[Hashable, Hashable | None],
|
|
430
|
+
dict[Hashable, set[ProvenanceRef]],
|
|
431
|
+
dict[frozenset[Hashable], set[ProvenanceRef]],
|
|
432
|
+
dict[Hashable, Hashable],
|
|
433
|
+
]:
|
|
434
|
+
node_refs = {node: {ProvenanceRef(1, "left", node)} for node in first.left.node_ids}
|
|
435
|
+
edge_refs = {
|
|
436
|
+
edge: {ProvenanceRef(1, "left", edge)} for edge in first.left.edge_keys
|
|
437
|
+
}
|
|
438
|
+
first_right_inverse = {
|
|
439
|
+
node: interface for interface, node in first.right_arm.mapping.items()
|
|
440
|
+
}
|
|
441
|
+
inverse_second = {
|
|
442
|
+
node: interface for interface, node in overlap.second_arm.mapping.items()
|
|
443
|
+
}
|
|
444
|
+
second_to_initial: dict[Hashable, Hashable | None] = {}
|
|
445
|
+
for second_node, overlap_node in inverse_second.items():
|
|
446
|
+
first_right_node = overlap.first_arm.mapping[overlap_node]
|
|
447
|
+
first_interface = first_right_inverse.get(first_right_node)
|
|
448
|
+
initial_node = (
|
|
449
|
+
first.left_arm.mapping[first_interface]
|
|
450
|
+
if first_interface is not None
|
|
451
|
+
else None
|
|
452
|
+
)
|
|
453
|
+
second_to_initial[second_node] = initial_node
|
|
454
|
+
if initial_node is not None:
|
|
455
|
+
node_refs[initial_node].add(ProvenanceRef(2, "left", second_node))
|
|
456
|
+
|
|
457
|
+
external = sorted(set(second.left.node_ids) - set(inverse_second), key=repr)
|
|
458
|
+
fresh = _fresh_context_nodes(set(graph.nodes), len(external))
|
|
459
|
+
for second_node, initial_node in zip(external, fresh):
|
|
460
|
+
second_to_initial[second_node] = initial_node
|
|
461
|
+
graph.add_node(initial_node, **second.left.node_labels(second_node))
|
|
462
|
+
node_refs[initial_node] = {ProvenanceRef(2, "left", second_node)}
|
|
463
|
+
return second_to_initial, node_refs, edge_refs, inverse_second
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _add_external_second_edge(
|
|
467
|
+
second: RuleSpan,
|
|
468
|
+
graph: nx.Graph,
|
|
469
|
+
second_to_initial: Mapping[Hashable, Hashable | None],
|
|
470
|
+
edge_refs: dict[frozenset[Hashable], set[ProvenanceRef]],
|
|
471
|
+
second_edge: frozenset[Hashable],
|
|
472
|
+
) -> None:
|
|
473
|
+
initial_endpoints = tuple(second_to_initial[node] for node in second_edge)
|
|
474
|
+
if any(node is None for node in initial_endpoints):
|
|
475
|
+
raise CompositionError(
|
|
476
|
+
CompositionIssue(
|
|
477
|
+
CompositionIssueCode.PULLBACK_COMPLEMENT,
|
|
478
|
+
"L2 context is incident to an R1-created overlap node.",
|
|
479
|
+
{"edge": tuple(sorted(map(repr, second_edge)))},
|
|
480
|
+
)
|
|
481
|
+
)
|
|
482
|
+
initial_edge = frozenset(initial_endpoints)
|
|
483
|
+
if len(initial_edge) != 2:
|
|
484
|
+
raise CompositionError(
|
|
485
|
+
CompositionIssue(
|
|
486
|
+
CompositionIssueCode.PARALLEL_EDGE_CONFLICT,
|
|
487
|
+
"The minimal host would contain a collapsed loop.",
|
|
488
|
+
{"edge": tuple(sorted(map(repr, second_edge)))},
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
left, right = tuple(initial_edge)
|
|
492
|
+
if graph.has_edge(left, right):
|
|
493
|
+
raise CompositionError(
|
|
494
|
+
CompositionIssue(
|
|
495
|
+
CompositionIssueCode.PARALLEL_EDGE_CONFLICT,
|
|
496
|
+
"The minimal host would collapse two non-interface edges.",
|
|
497
|
+
{"edge": tuple(sorted(map(repr, initial_edge)))},
|
|
498
|
+
)
|
|
499
|
+
)
|
|
500
|
+
graph.add_edge(left, right, **second.left.edge_labels(second_edge))
|
|
501
|
+
edge_refs[initial_edge] = {ProvenanceRef(2, "left", second_edge)}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _add_second_context_edges(
|
|
505
|
+
second: RuleSpan,
|
|
506
|
+
overlap: RuleOverlap,
|
|
507
|
+
graph: nx.Graph,
|
|
508
|
+
second_to_initial: Mapping[Hashable, Hashable | None],
|
|
509
|
+
inverse_second: Mapping[Hashable, Hashable],
|
|
510
|
+
edge_refs: dict[frozenset[Hashable], set[ProvenanceRef]],
|
|
511
|
+
) -> None:
|
|
512
|
+
interface_edges = set(overlap.interface.edge_keys)
|
|
513
|
+
for second_edge in second.left.edge_keys:
|
|
514
|
+
if not second_edge <= set(inverse_second):
|
|
515
|
+
_add_external_second_edge(
|
|
516
|
+
second, graph, second_to_initial, edge_refs, second_edge
|
|
517
|
+
)
|
|
518
|
+
continue
|
|
519
|
+
interface_edge = frozenset(inverse_second[node] for node in second_edge)
|
|
520
|
+
if interface_edge not in interface_edges:
|
|
521
|
+
_add_external_second_edge(
|
|
522
|
+
second, graph, second_to_initial, edge_refs, second_edge
|
|
523
|
+
)
|
|
524
|
+
continue
|
|
525
|
+
initial_endpoints = tuple(second_to_initial[node] for node in second_edge)
|
|
526
|
+
if all(node is not None for node in initial_endpoints):
|
|
527
|
+
initial_edge = frozenset(initial_endpoints)
|
|
528
|
+
if initial_edge in edge_refs:
|
|
529
|
+
edge_refs[initial_edge].add(ProvenanceRef(2, "left", second_edge))
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _derive_initial_electron_state(graph: nx.Graph) -> nx.Graph:
|
|
533
|
+
for _, attrs in graph.nodes(data=True):
|
|
534
|
+
for key in ELECTRON_LLG_SCHEMA.node_derived:
|
|
535
|
+
attrs.pop(key, None)
|
|
536
|
+
for _, _, attrs in graph.edges(data=True):
|
|
537
|
+
for key in ELECTRON_LLG_SCHEMA.edge_derived:
|
|
538
|
+
attrs.pop(key, None)
|
|
539
|
+
try:
|
|
540
|
+
return derive_electron_labeled_graph(graph)
|
|
541
|
+
except LLGError as error:
|
|
542
|
+
raise CompositionError(
|
|
543
|
+
CompositionIssue(
|
|
544
|
+
CompositionIssueCode.PULLBACK_COMPLEMENT,
|
|
545
|
+
"The minimal electron host is not coherent.",
|
|
546
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
547
|
+
)
|
|
548
|
+
) from error
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _minimal_host_plan(
|
|
552
|
+
first: RuleSpan,
|
|
553
|
+
second: RuleSpan,
|
|
554
|
+
overlap: RuleOverlap,
|
|
555
|
+
) -> _HostPlan:
|
|
556
|
+
if (
|
|
557
|
+
overlap.first_arm.target != first.right
|
|
558
|
+
or overlap.second_arm.target != second.left
|
|
559
|
+
):
|
|
560
|
+
raise CompositionError(
|
|
561
|
+
CompositionIssue(
|
|
562
|
+
CompositionIssueCode.OVERLAP_PARTIAL,
|
|
563
|
+
"The overlap endpoints must be exactly R1 and L2.",
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
_overlap_edge_closed(overlap)
|
|
567
|
+
graph = first.left.to_networkx()
|
|
568
|
+
second_to_initial, node_refs, edge_refs, inverse_second = _initial_node_plan(
|
|
569
|
+
first, second, overlap, graph
|
|
570
|
+
)
|
|
571
|
+
_add_second_context_edges(
|
|
572
|
+
second,
|
|
573
|
+
overlap,
|
|
574
|
+
graph,
|
|
575
|
+
second_to_initial,
|
|
576
|
+
inverse_second,
|
|
577
|
+
edge_refs,
|
|
578
|
+
)
|
|
579
|
+
if is_electron_schema(first.left.schema):
|
|
580
|
+
graph = _derive_initial_electron_state(graph)
|
|
581
|
+
return _HostPlan(graph, second_to_initial, node_refs, edge_refs)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _first_match(first: RuleSpan, host: LewisLabelledGraph) -> dict[Hashable, Hashable]:
|
|
585
|
+
return {node: node for node in first.left.node_ids}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _second_match(
|
|
589
|
+
second: RuleSpan,
|
|
590
|
+
overlap: RuleOverlap,
|
|
591
|
+
plan: _HostPlan,
|
|
592
|
+
first_application: DPOApplication,
|
|
593
|
+
) -> dict[Hashable, Hashable]:
|
|
594
|
+
first_right_result = dict(first_application.certificate.right_to_result)
|
|
595
|
+
overlap_first = overlap.first_arm.mapping
|
|
596
|
+
overlap_second_inverse = {
|
|
597
|
+
node: interface for interface, node in overlap.second_arm.mapping.items()
|
|
598
|
+
}
|
|
599
|
+
result: dict[Hashable, Hashable] = {}
|
|
600
|
+
for second_node in second.left.node_ids:
|
|
601
|
+
if second_node in overlap_second_inverse:
|
|
602
|
+
interface = overlap_second_inverse[second_node]
|
|
603
|
+
result[second_node] = first_right_result[overlap_first[interface]]
|
|
604
|
+
else:
|
|
605
|
+
initial = plan.second_left_to_initial[second_node]
|
|
606
|
+
if initial is None or initial not in first_application.result.node_ids:
|
|
607
|
+
raise CompositionError(
|
|
608
|
+
CompositionIssue(
|
|
609
|
+
CompositionIssueCode.PULLBACK_COMPLEMENT,
|
|
610
|
+
"An L2 context item did not survive the first application.",
|
|
611
|
+
{"node": repr(second_node)},
|
|
612
|
+
)
|
|
613
|
+
)
|
|
614
|
+
result[second_node] = initial
|
|
615
|
+
return result
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _apply_or_translate(
|
|
619
|
+
rule: RuleSpan,
|
|
620
|
+
host: LewisLabelledGraph,
|
|
621
|
+
match: Mapping[Hashable, Hashable],
|
|
622
|
+
code: CompositionIssueCode,
|
|
623
|
+
) -> DPOApplication:
|
|
624
|
+
try:
|
|
625
|
+
return apply_dpo(rule, host, match)
|
|
626
|
+
except DPOError as error:
|
|
627
|
+
raise CompositionError(
|
|
628
|
+
CompositionIssue(
|
|
629
|
+
code,
|
|
630
|
+
"A component DPO application is not admissible.",
|
|
631
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
632
|
+
)
|
|
633
|
+
) from error
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _combine_boundary(
|
|
637
|
+
first: RuleSpan, second: RuleSpan
|
|
638
|
+
) -> tuple[SystemBoundary, EnvironmentToken | None]:
|
|
639
|
+
if SystemBoundary.ABSTRACT in {first.boundary, second.boundary}:
|
|
640
|
+
return SystemBoundary.ABSTRACT, None
|
|
641
|
+
if (
|
|
642
|
+
first.boundary is SystemBoundary.CLOSED
|
|
643
|
+
and second.boundary is SystemBoundary.CLOSED
|
|
644
|
+
):
|
|
645
|
+
return SystemBoundary.CLOSED, None
|
|
646
|
+
deltas = (first.resource_delta, second.resource_delta)
|
|
647
|
+
elements: Counter[str] = Counter()
|
|
648
|
+
isotopes = Counter()
|
|
649
|
+
electrons = 0.0
|
|
650
|
+
names = []
|
|
651
|
+
for rule, delta in zip((first, second), deltas):
|
|
652
|
+
if delta is None:
|
|
653
|
+
continue
|
|
654
|
+
elements.update(dict(delta.element_delta))
|
|
655
|
+
isotopes.update(dict(delta.isotope_delta))
|
|
656
|
+
electrons += delta.electron_delta
|
|
657
|
+
if rule.environment is not None:
|
|
658
|
+
names.append(rule.environment.name)
|
|
659
|
+
token = EnvironmentToken(
|
|
660
|
+
" + ".join(names) or "composed environment",
|
|
661
|
+
tuple((element, count) for element, count in elements.items() if count),
|
|
662
|
+
electrons,
|
|
663
|
+
tuple((isotope, count) for isotope, count in isotopes.items() if count),
|
|
664
|
+
)
|
|
665
|
+
return SystemBoundary.OPEN, token
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _outer_interface_from_contexts(
|
|
669
|
+
rule: RuleSpan,
|
|
670
|
+
first_application: DPOApplication,
|
|
671
|
+
second_application: DPOApplication,
|
|
672
|
+
) -> RuleSpan:
|
|
673
|
+
"""Preserve only coordinates that survive both realized natural contexts."""
|
|
674
|
+
contexts = (
|
|
675
|
+
first_application.certificate.context,
|
|
676
|
+
second_application.certificate.context,
|
|
677
|
+
)
|
|
678
|
+
graph = rule.interface.to_networkx()
|
|
679
|
+
for node, carrier in rule.left_arm.mapping.items():
|
|
680
|
+
labels = [context.node_labels(carrier, semantic=True) for context in contexts]
|
|
681
|
+
for key in rule.left.schema.semantic_node_keys:
|
|
682
|
+
if (
|
|
683
|
+
key not in labels[0]
|
|
684
|
+
or key not in labels[1]
|
|
685
|
+
or labels[0][key] != labels[1][key]
|
|
686
|
+
):
|
|
687
|
+
graph.nodes[node].pop(key, None)
|
|
688
|
+
for edge, carrier in rule.left_arm.edge_mapping.items():
|
|
689
|
+
labels = [context.edge_labels(carrier, semantic=True) for context in contexts]
|
|
690
|
+
for key in rule.left.schema.semantic_edge_keys:
|
|
691
|
+
if (
|
|
692
|
+
key not in labels[0]
|
|
693
|
+
or key not in labels[1]
|
|
694
|
+
or labels[0][key] != labels[1][key]
|
|
695
|
+
):
|
|
696
|
+
graph.edges[tuple(edge)].pop(key, None)
|
|
697
|
+
interface = LewisLabelledGraph.from_networkx(
|
|
698
|
+
graph, rule.interface.schema, name=rule.interface.name, partial=True
|
|
699
|
+
)
|
|
700
|
+
return replace(
|
|
701
|
+
rule,
|
|
702
|
+
interface=interface,
|
|
703
|
+
left_arm=RuleEmbedding(interface, rule.left, rule.left_arm.f),
|
|
704
|
+
right_arm=RuleEmbedding(interface, rule.right, rule.right_arm.f),
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _outer_rule(
|
|
709
|
+
first: RuleSpan,
|
|
710
|
+
second: RuleSpan,
|
|
711
|
+
host: LewisLabelledGraph,
|
|
712
|
+
first_application: DPOApplication,
|
|
713
|
+
second_application: DPOApplication,
|
|
714
|
+
) -> RuleSpan:
|
|
715
|
+
removed_first = set(first_application.certificate.deleted_nodes)
|
|
716
|
+
removed_second = set(second_application.certificate.deleted_nodes)
|
|
717
|
+
preserved = {
|
|
718
|
+
node: node
|
|
719
|
+
for node in host.node_ids
|
|
720
|
+
if node not in removed_first
|
|
721
|
+
and node not in removed_second
|
|
722
|
+
and node in second_application.result.node_ids
|
|
723
|
+
}
|
|
724
|
+
removed_edges = set(first_application.certificate.deleted_edges) | set(
|
|
725
|
+
second_application.certificate.deleted_edges
|
|
726
|
+
)
|
|
727
|
+
preserved_edges = {
|
|
728
|
+
edge
|
|
729
|
+
for edge in host.edge_keys
|
|
730
|
+
if edge <= set(preserved)
|
|
731
|
+
and edge not in removed_edges
|
|
732
|
+
and edge in second_application.result.edge_keys
|
|
733
|
+
}
|
|
734
|
+
boundary, environment = _combine_boundary(first, second)
|
|
735
|
+
try:
|
|
736
|
+
rule = RuleSpan.from_mapping(
|
|
737
|
+
host,
|
|
738
|
+
second_application.result,
|
|
739
|
+
preserved,
|
|
740
|
+
preserved_edges=preserved_edges,
|
|
741
|
+
boundary=boundary,
|
|
742
|
+
environment=environment,
|
|
743
|
+
name=f"{first.name};{second.name}",
|
|
744
|
+
)
|
|
745
|
+
return _outer_interface_from_contexts(
|
|
746
|
+
rule, first_application, second_application
|
|
747
|
+
)
|
|
748
|
+
except DPOError as error:
|
|
749
|
+
raise CompositionError(
|
|
750
|
+
CompositionIssue(
|
|
751
|
+
CompositionIssueCode.RESOURCE_MISMATCH,
|
|
752
|
+
"The outer rule violates the composed resource contract.",
|
|
753
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
754
|
+
)
|
|
755
|
+
) from error
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def _mapped_edge(
|
|
759
|
+
mapping: Mapping[Hashable, Hashable], edge: frozenset[Hashable]
|
|
760
|
+
) -> frozenset[Hashable]:
|
|
761
|
+
return frozenset(mapping[node] for node in edge)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def _provenance(
|
|
765
|
+
first: RuleSpan,
|
|
766
|
+
second: RuleSpan,
|
|
767
|
+
overlap: RuleOverlap,
|
|
768
|
+
host: LewisLabelledGraph,
|
|
769
|
+
plan: _HostPlan,
|
|
770
|
+
first_application: DPOApplication,
|
|
771
|
+
second_application: DPOApplication,
|
|
772
|
+
) -> CompositionProvenance:
|
|
773
|
+
final = second_application.result
|
|
774
|
+
left_nodes = {node: set(refs) for node, refs in plan.node_refs.items()}
|
|
775
|
+
left_edges = {edge: set(refs) for edge, refs in plan.edge_refs.items()}
|
|
776
|
+
removed_nodes = set(first_application.certificate.deleted_nodes) | set(
|
|
777
|
+
second_application.certificate.deleted_nodes
|
|
778
|
+
)
|
|
779
|
+
removed_edges = set(first_application.certificate.deleted_edges) | set(
|
|
780
|
+
second_application.certificate.deleted_edges
|
|
781
|
+
)
|
|
782
|
+
final_nodes: dict[Hashable, set[ProvenanceRef]] = {
|
|
783
|
+
node: set() if node in removed_nodes else set(left_nodes.get(node, ()))
|
|
784
|
+
for node in final.node_ids
|
|
785
|
+
}
|
|
786
|
+
final_edges: dict[frozenset[Hashable], set[ProvenanceRef]] = {
|
|
787
|
+
edge: set() if edge in removed_edges else set(left_edges.get(edge, ()))
|
|
788
|
+
for edge in final.edge_keys
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
first_right = dict(first_application.certificate.right_to_result)
|
|
792
|
+
removed_second = set(second_application.certificate.deleted_nodes)
|
|
793
|
+
for node in first.right.node_ids:
|
|
794
|
+
result_node = first_right[node]
|
|
795
|
+
if result_node in final.node_ids and result_node not in removed_second:
|
|
796
|
+
final_nodes[result_node].add(ProvenanceRef(1, "right", node))
|
|
797
|
+
for edge in first.right.edge_keys:
|
|
798
|
+
result_edge = _mapped_edge(first_right, edge)
|
|
799
|
+
if (
|
|
800
|
+
result_edge in final.edge_keys
|
|
801
|
+
and result_edge not in second_application.certificate.deleted_edges
|
|
802
|
+
):
|
|
803
|
+
final_edges[result_edge].add(ProvenanceRef(1, "right", edge))
|
|
804
|
+
|
|
805
|
+
second_right = dict(second_application.certificate.right_to_result)
|
|
806
|
+
for node in second.right.node_ids:
|
|
807
|
+
final_nodes[second_right[node]].add(ProvenanceRef(2, "right", node))
|
|
808
|
+
for edge in second.right.edge_keys:
|
|
809
|
+
result_edge = _mapped_edge(second_right, edge)
|
|
810
|
+
final_edges[result_edge].add(ProvenanceRef(2, "right", edge))
|
|
811
|
+
|
|
812
|
+
node_sources = tuple(
|
|
813
|
+
sorted(
|
|
814
|
+
[
|
|
815
|
+
(side, node, tuple(sorted(refs, key=repr)))
|
|
816
|
+
for side, values in (("left", left_nodes), ("right", final_nodes))
|
|
817
|
+
for node, refs in values.items()
|
|
818
|
+
],
|
|
819
|
+
key=repr,
|
|
820
|
+
)
|
|
821
|
+
)
|
|
822
|
+
edge_sources = tuple(
|
|
823
|
+
sorted(
|
|
824
|
+
[
|
|
825
|
+
(side, edge, tuple(sorted(refs, key=repr)))
|
|
826
|
+
for side, values in (("left", left_edges), ("right", final_edges))
|
|
827
|
+
for edge, refs in values.items()
|
|
828
|
+
],
|
|
829
|
+
key=repr,
|
|
830
|
+
)
|
|
831
|
+
)
|
|
832
|
+
overlap_nodes = tuple(
|
|
833
|
+
sorted(
|
|
834
|
+
(
|
|
835
|
+
(
|
|
836
|
+
node,
|
|
837
|
+
overlap.first_arm.mapping[node],
|
|
838
|
+
overlap.second_arm.mapping[node],
|
|
839
|
+
)
|
|
840
|
+
for node in overlap.interface.node_ids
|
|
841
|
+
),
|
|
842
|
+
key=repr,
|
|
843
|
+
)
|
|
844
|
+
)
|
|
845
|
+
overlap_edges = tuple(
|
|
846
|
+
sorted(
|
|
847
|
+
(
|
|
848
|
+
(
|
|
849
|
+
edge,
|
|
850
|
+
_mapped_edge(overlap.first_arm.mapping, edge),
|
|
851
|
+
_mapped_edge(overlap.second_arm.mapping, edge),
|
|
852
|
+
)
|
|
853
|
+
for edge in overlap.interface.edge_keys
|
|
854
|
+
),
|
|
855
|
+
key=repr,
|
|
856
|
+
)
|
|
857
|
+
)
|
|
858
|
+
return CompositionProvenance(
|
|
859
|
+
node_sources, edge_sources, overlap_nodes, overlap_edges
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def compose_rules(
|
|
864
|
+
first: RuleSpan,
|
|
865
|
+
second: RuleSpan,
|
|
866
|
+
overlap: RuleOverlap,
|
|
867
|
+
) -> CompositionResult:
|
|
868
|
+
"""Compose exactly two rules along exactly one caller-supplied overlap."""
|
|
869
|
+
if first.right.schema != second.left.schema:
|
|
870
|
+
raise CompositionError(
|
|
871
|
+
CompositionIssue(
|
|
872
|
+
CompositionIssueCode.SCHEMA_MISMATCH,
|
|
873
|
+
"Consecutive rule endpoints must use one LLG schema.",
|
|
874
|
+
)
|
|
875
|
+
)
|
|
876
|
+
sources_before = (
|
|
877
|
+
first.left.to_networkx(),
|
|
878
|
+
first.right.to_networkx(),
|
|
879
|
+
second.left.to_networkx(),
|
|
880
|
+
second.right.to_networkx(),
|
|
881
|
+
)
|
|
882
|
+
plan = _minimal_host_plan(first, second, overlap)
|
|
883
|
+
try:
|
|
884
|
+
host = LewisLabelledGraph.from_networkx(
|
|
885
|
+
plan.graph, first.left.schema, name=f"{first.name};{second.name}:G"
|
|
886
|
+
)
|
|
887
|
+
except LLGError as error:
|
|
888
|
+
raise CompositionError(
|
|
889
|
+
CompositionIssue(
|
|
890
|
+
CompositionIssueCode.PULLBACK_COMPLEMENT,
|
|
891
|
+
"The minimal initial context is not an LLG object.",
|
|
892
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
893
|
+
)
|
|
894
|
+
) from error
|
|
895
|
+
first_application = _apply_or_translate(
|
|
896
|
+
first, host, _first_match(first, host), CompositionIssueCode.FIRST_APPLICATION
|
|
897
|
+
)
|
|
898
|
+
second_match = _second_match(second, overlap, plan, first_application)
|
|
899
|
+
second_application = _apply_or_translate(
|
|
900
|
+
second,
|
|
901
|
+
first_application.result,
|
|
902
|
+
second_match,
|
|
903
|
+
CompositionIssueCode.SECOND_APPLICATION,
|
|
904
|
+
)
|
|
905
|
+
composite = _outer_rule(first, second, host, first_application, second_application)
|
|
906
|
+
composite = replace(
|
|
907
|
+
composite,
|
|
908
|
+
condition=SequentialCondition(
|
|
909
|
+
first,
|
|
910
|
+
second,
|
|
911
|
+
first_application.certificate.match,
|
|
912
|
+
first_application.certificate.right_to_result,
|
|
913
|
+
tuple(sorted(second_match.items(), key=repr)),
|
|
914
|
+
),
|
|
915
|
+
)
|
|
916
|
+
outer_application = _apply_or_translate(
|
|
917
|
+
composite,
|
|
918
|
+
host,
|
|
919
|
+
{node: node for node in host.node_ids},
|
|
920
|
+
CompositionIssueCode.OUTER_REPLAY,
|
|
921
|
+
)
|
|
922
|
+
provenance = _provenance(
|
|
923
|
+
first,
|
|
924
|
+
second,
|
|
925
|
+
overlap,
|
|
926
|
+
host,
|
|
927
|
+
plan,
|
|
928
|
+
first_application,
|
|
929
|
+
second_application,
|
|
930
|
+
)
|
|
931
|
+
certificate = CompositionCertificate(
|
|
932
|
+
first,
|
|
933
|
+
second,
|
|
934
|
+
overlap,
|
|
935
|
+
host,
|
|
936
|
+
first_application,
|
|
937
|
+
second_application,
|
|
938
|
+
composite,
|
|
939
|
+
outer_application,
|
|
940
|
+
tuple(sorted(second_match.items(), key=repr)),
|
|
941
|
+
provenance,
|
|
942
|
+
)
|
|
943
|
+
replay = certificate.replay()
|
|
944
|
+
if not replay.valid:
|
|
945
|
+
raise CompositionError(*replay.issues)
|
|
946
|
+
sources_after = (
|
|
947
|
+
first.left.to_networkx(),
|
|
948
|
+
first.right.to_networkx(),
|
|
949
|
+
second.left.to_networkx(),
|
|
950
|
+
second.right.to_networkx(),
|
|
951
|
+
)
|
|
952
|
+
if any(
|
|
953
|
+
not nx.utils.graphs_equal(before, after)
|
|
954
|
+
for before, after in zip(sources_before, sources_after)
|
|
955
|
+
):
|
|
956
|
+
raise CompositionError(
|
|
957
|
+
CompositionIssue(
|
|
958
|
+
CompositionIssueCode.SOURCE_MUTATED,
|
|
959
|
+
"Composition mutated a source rule.",
|
|
960
|
+
)
|
|
961
|
+
)
|
|
962
|
+
return CompositionResult(composite, certificate)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
__all__ = [
|
|
966
|
+
"CompositionCertificate",
|
|
967
|
+
"CompositionError",
|
|
968
|
+
"CompositionIssue",
|
|
969
|
+
"CompositionIssueCode",
|
|
970
|
+
"CompositionProvenance",
|
|
971
|
+
"CompositionReplay",
|
|
972
|
+
"CompositionResult",
|
|
973
|
+
"ProvenanceRef",
|
|
974
|
+
"RuleOverlap",
|
|
975
|
+
"compose_rules",
|
|
976
|
+
]
|