synkit 1.6.5__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synkit/CRN/Benchmark/__init__.py +53 -0
- synkit/CRN/Benchmark/crosschecks.py +13 -0
- synkit/CRN/Benchmark/kegg.py +12 -0
- synkit/CRN/Benchmark/networks.py +13 -0
- synkit/CRN/Benchmark/scaling.py +13 -0
- synkit/CRN/Benchmark/validate.py +13 -0
- synkit/CRN/Construct/__init__.py +16 -0
- synkit/CRN/Construct/abstract.py +668 -0
- synkit/CRN/Construct/arity.py +36 -0
- synkit/CRN/Construct/builder.py +619 -0
- synkit/CRN/Construct/derivation.py +63 -0
- synkit/CRN/Construct/flattener.py +100 -0
- synkit/CRN/Construct/keys.py +14 -0
- synkit/CRN/Construct/mixtures.py +117 -0
- synkit/CRN/Construct/rdkit_worker.py +69 -0
- synkit/CRN/Construct/smiles.py +99 -0
- synkit/CRN/Construct/state.py +31 -0
- synkit/CRN/Construct/strategy.py +75 -0
- synkit/CRN/Construct/worker.py +42 -0
- synkit/CRN/IO/__init__.py +36 -0
- synkit/CRN/IO/sbml.py +925 -0
- synkit/CRN/Pathway/__init__.py +72 -0
- synkit/CRN/Pathway/_adapter.py +333 -0
- synkit/CRN/Pathway/pathfinder.py +500 -0
- synkit/CRN/Pathway/reachability.py +796 -0
- synkit/CRN/Pathway/realizability.py +1322 -0
- synkit/CRN/Petrinet/__init__.py +93 -0
- synkit/CRN/Petrinet/analyzer.py +486 -0
- synkit/CRN/Petrinet/minimal_semiflows.py +436 -0
- synkit/CRN/Petrinet/net.py +1041 -0
- synkit/CRN/Petrinet/persistence.py +97 -0
- synkit/CRN/Petrinet/semiflows.py +483 -0
- synkit/CRN/Petrinet/siphon_search.py +402 -0
- synkit/CRN/Petrinet/structure.py +286 -0
- synkit/CRN/Props/__init__.py +130 -0
- synkit/CRN/Props/_rank.py +29 -0
- synkit/CRN/Props/deficiency.py +953 -0
- synkit/CRN/Props/dynamics.py +832 -0
- synkit/CRN/Props/helper.py +151 -0
- synkit/CRN/Props/stoich.py +751 -0
- synkit/CRN/Props/thermo.py +584 -0
- synkit/CRN/Query/__init__.py +66 -0
- synkit/CRN/Query/cache.py +41 -0
- synkit/CRN/Query/kegg_api.py +99 -0
- synkit/CRN/Query/kegg_extract.py +714 -0
- synkit/CRN/Query/kegg_impute.py +707 -0
- synkit/CRN/Query/kegg_parse.py +480 -0
- synkit/CRN/Query/to_syncrn.py +290 -0
- synkit/CRN/README.md +234 -0
- synkit/CRN/Structure/__init__.py +12 -0
- synkit/CRN/Structure/_graph_io.py +713 -0
- synkit/CRN/Structure/_matrices.py +88 -0
- synkit/CRN/Structure/_parse.py +544 -0
- synkit/CRN/Structure/reaction.py +237 -0
- synkit/CRN/Structure/rule.py +55 -0
- synkit/CRN/Structure/species.py +46 -0
- synkit/CRN/Structure/syncrn.py +929 -0
- synkit/CRN/Symmetry/__init__.py +20 -0
- synkit/CRN/Symmetry/_common.py +1059 -0
- synkit/CRN/Symmetry/_ir.py +940 -0
- synkit/CRN/Symmetry/automorphism.py +472 -0
- synkit/CRN/Symmetry/canon.py +441 -0
- synkit/CRN/Symmetry/isomorphism.py +412 -0
- synkit/CRN/Symmetry/symmetry.py +61 -0
- synkit/CRN/Symmetry/wl_canon.py +967 -0
- synkit/CRN/Visualize/__init__.py +11 -0
- synkit/CRN/Visualize/labels.py +113 -0
- synkit/CRN/Visualize/layout.py +708 -0
- synkit/CRN/Visualize/palette.py +292 -0
- synkit/CRN/Visualize/validation.py +122 -0
- synkit/CRN/Visualize/vis.py +1152 -0
- synkit/CRN/__init__.py +233 -0
- synkit/CRN/_build.py +50 -0
- synkit/CRN/_native_build.py +175 -0
- synkit/CRN/analysis.py +629 -0
- synkit/CRN/backend.py +74 -0
- synkit/CRN/cpp/CMakeLists.txt +58 -0
- synkit/CRN/cpp/algebra.cpp +46 -0
- synkit/CRN/cpp/closure.cpp +29 -0
- synkit/CRN/cpp/exact.h +55 -0
- synkit/CRN/cpp/pathway.cpp +46 -0
- synkit/CRN/cpp/semiflows.cpp +209 -0
- synkit/CRN/cpp/structural.cpp +240 -0
- synkit/CRN/cpp/symmetry.cpp +45 -0
- synkit/CRN/cpp/write_manifest.cmake +28 -0
- synkit/CRN/kinds.py +137 -0
- synkit/CRN/lib/synkit_crn.dll +0 -0
- synkit/CRN/lib/synkit_crn.json +33 -0
- synkit/CRN/native.py +388 -0
- synkit/CRN/native_analysis.py +459 -0
- synkit/Chem/Cluster/__init__.py +0 -0
- synkit/Chem/Cluster/butina.py +140 -0
- synkit/Chem/Fingerprint/__init__.py +0 -0
- synkit/Chem/Fingerprint/fp_calculator.py +155 -0
- synkit/Chem/Fingerprint/smiles_featurizer.py +258 -0
- synkit/Chem/Fingerprint/transformation_fp.py +139 -0
- synkit/Chem/Mapper/README.rst +43 -0
- synkit/Chem/Mapper/__init__.py +255 -0
- synkit/Chem/Mapper/_compat.py +130 -0
- synkit/Chem/Mapper/adapters/__init__.py +1 -0
- synkit/Chem/Mapper/adapters/prediction.py +126 -0
- synkit/Chem/Mapper/adapters/template.py +412 -0
- synkit/Chem/Mapper/chem/__init__.py +52 -0
- synkit/Chem/Mapper/chem/aam.py +673 -0
- synkit/Chem/Mapper/chem/blind.py +160 -0
- synkit/Chem/Mapper/chem/its.py +309 -0
- synkit/Chem/Mapper/chem/smiles.py +564 -0
- synkit/Chem/Mapper/graph/__init__.py +66 -0
- synkit/Chem/Mapper/graph/automorphism.py +395 -0
- synkit/Chem/Mapper/graph/blockcut.py +98 -0
- synkit/Chem/Mapper/graph/labeled_graph.py +104 -0
- synkit/Chem/Mapper/graph/refine.py +176 -0
- synkit/Chem/Mapper/graph/synkit_adapter.py +110 -0
- synkit/Chem/Mapper/io/__init__.py +13 -0
- synkit/Chem/Mapper/io/idxmapstr.py +82 -0
- synkit/Chem/Mapper/mapper.py +685 -0
- synkit/Chem/Mapper/reaction_search.py +554 -0
- synkit/Chem/Mapper/reporting/__init__.py +1 -0
- synkit/Chem/Mapper/reporting/alternatives.py +451 -0
- synkit/Chem/Mapper/reporting/analysis.py +681 -0
- synkit/Chem/Mapper/reporting/native_analysis.py +485 -0
- synkit/Chem/Mapper/reporting/spectrum.py +642 -0
- synkit/Chem/Mapper/solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/__init__.py +141 -0
- synkit/Chem/Mapper/solvers/exact/assignment/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_certificate.py +113 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_domains.py +162 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_verification.py +58 -0
- synkit/Chem/Mapper/solvers/exact/assignment/incremental_assignment.py +247 -0
- synkit/Chem/Mapper/solvers/exact/certificate.py +128 -0
- synkit/Chem/Mapper/solvers/exact/core.py +183 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/bounds.py +319 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/exhaustive.py +179 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/inputs.py +283 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/records.py +242 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/search.py +1025 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/verify.py +378 -0
- synkit/Chem/Mapper/solvers/exact/edit_support.py +375 -0
- synkit/Chem/Mapper/solvers/exact/hybrid.py +224 -0
- synkit/Chem/Mapper/solvers/exact/hydrogen.py +264 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/branching.py +499 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumerate.py +1050 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumeration_result.py +30 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/kernel.py +256 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/milp.py +194 -0
- synkit/Chem/Mapper/solvers/exact/native/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/native/build.py +154 -0
- synkit/Chem/Mapper/solvers/exact/native/candidates.py +593 -0
- synkit/Chem/Mapper/solvers/exact/native/canonical.py +396 -0
- synkit/Chem/Mapper/solvers/exact/native/disk_frontier.py +415 -0
- synkit/Chem/Mapper/solvers/exact/native/frontier.py +390 -0
- synkit/Chem/Mapper/solvers/exact/native/hydrogen_quotient.py +213 -0
- synkit/Chem/Mapper/solvers/exact/native/its.py +528 -0
- synkit/Chem/Mapper/solvers/exact/native/native_distance.cpp +2803 -0
- synkit/Chem/Mapper/solvers/exact/native/orbit_aggregation.py +328 -0
- synkit/Chem/Mapper/solvers/exact/native/parallel.py +205 -0
- synkit/Chem/Mapper/solvers/exact/native/pmcd.py +203 -0
- synkit/Chem/Mapper/solvers/exact/native/runtime.py +40 -0
- synkit/Chem/Mapper/solvers/exact/native/search.py +294 -0
- synkit/Chem/Mapper/solvers/exact/native/separator.py +224 -0
- synkit/Chem/Mapper/solvers/exact/native_build.py +12 -0
- synkit/Chem/Mapper/solvers/exact/pabs/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/pabs/cost_lattice.py +43 -0
- synkit/Chem/Mapper/solvers/exact/pabs/factor_spectrum.py +110 -0
- synkit/Chem/Mapper/solvers/exact/pabs/multi_shell.py +368 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation.py +679 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_bounds.py +436 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_limits.py +13 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_search.py +856 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_symmetry.py +66 -0
- synkit/Chem/Mapper/solvers/exact/pabs/residual_typed_bonds.py +136 -0
- synkit/Chem/Mapper/solvers/exact/pabs/reward_frontier.py +201 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_bound.py +258 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_spectrum.py +308 -0
- synkit/Chem/Mapper/solvers/exact/pabs/sparse_reward_bound.py +207 -0
- synkit/Chem/Mapper/solvers/exact/pabs/suffix_spectrum.py +299 -0
- synkit/Chem/Mapper/solvers/exact/search.py +55 -0
- synkit/Chem/Mapper/solvers/exact/seeds/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed.py +104 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_fragments.py +148 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_relaxation.py +181 -0
- synkit/Chem/Mapper/solvers/exact/symmetry.py +223 -0
- synkit/Chem/Mapper/solvers/native/__init__.py +4 -0
- synkit/Chem/Mapper/solvers/native/assignment.py +99 -0
- synkit/Chem/Mapper/solvers/native/astar.py +22 -0
- synkit/Chem/Mapper/solvers/native/build.py +385 -0
- synkit/Chem/Mapper/solvers/native/classification.py +641 -0
- synkit/Chem/Mapper/solvers/native/core.py +378 -0
- synkit/Chem/Mapper/solvers/native/cpp/CMakeLists.txt +120 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/map_reaction.cpp +29 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/slap_graph.cpp +63 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/assignment/assignment.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/astar.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/bound.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/pmcd_witness.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/runtime.h +130 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/hydrogen/hydrogen.h +73 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mapper.h +175 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mces/mces.h +9 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/pabs/seed.h +34 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/reaction/reaction.h +30 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/sepdp/sepdp.h +21 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/slap.h +72 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/solvers/solvers.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/classification.h +140 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/cpabs.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/assignment/abb.cpp +302 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/CMakeLists.txt +12 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/README.md +47 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar.cpp +228 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar_graph.cpp +37 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/bound.cpp +212 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/dispatch.cpp +272 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/pmcd_witness.cpp +36 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/hydrogen/hydrogen.cpp +380 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/README.md +57 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/mces.cpp +287 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/bridge.cpp +334 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/chemical_floor.inc +181 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/endpoint_symmetry.inc +104 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/exact_helpers.h +20 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_assignment_certificate.h +165 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_certificate.h +125 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_model.h +365 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_provider.inc +319 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_rounding.inc +170 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_test_hooks.h +342 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/objective_reduction.inc +137 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/overlap_dual.inc +351 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/profile_seed.inc +214 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/seed_heuristics.inc +640 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_embedding.cpp +430 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_root_lp.h +176 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/test_hooks.inc +262 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/reaction/reaction.cpp +611 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/README.md +61 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/sepdp.cpp +253 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/slap.cpp +810 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/formulation.h +138 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/maxsat.cpp +526 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/ortools.cpp +475 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/seed.h +80 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/weighted_cnf.h +256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/README.md +43 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/dfs.cpp +123 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/edit_support.cpp +224 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification.cpp +553 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_pipeline.cpp +124 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_stream.h +13 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/cpabs.cpp +455 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/edge_deck.cpp +743 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naugraph.c +732 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.c +94 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.h +49 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.c +1771 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.h +133 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nautil.c +750 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naututil.h +355 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.c +1256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.h +1544 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.c +1131 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.h +70 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/sorttemplates.c +580 -0
- synkit/Chem/Mapper/solvers/native/hydrogen.py +711 -0
- synkit/Chem/Mapper/solvers/native/implicit.py +506 -0
- synkit/Chem/Mapper/solvers/native/implicit_certificate.py +187 -0
- synkit/Chem/Mapper/solvers/native/implicit_preparation.py +309 -0
- synkit/Chem/Mapper/solvers/native/lib/synkit_mapper.dll +0 -0
- synkit/Chem/Mapper/solvers/native/mces.py +16 -0
- synkit/Chem/Mapper/solvers/native/reaction.py +179 -0
- synkit/Chem/Mapper/solvers/native/reaction_build.py +277 -0
- synkit/Chem/Mapper/solvers/native/reaction_text.py +26 -0
- synkit/Chem/Mapper/solvers/native/runtime.py +242 -0
- synkit/Chem/Mapper/solvers/native/sepdp.py +47 -0
- synkit/Chem/Mapper/solvers/native/slap.py +266 -0
- synkit/Chem/Mapper/solvers/native/solvers.py +67 -0
- synkit/Chem/Mapper/solvers/native/stereo.py +86 -0
- synkit/Chem/Mapper/solvers/native/supporting.py +34 -0
- synkit/Chem/Mapper/solvers/native/symmetry.py +91 -0
- synkit/Chem/Mapper/solvers/slap/__init__.py +29 -0
- synkit/Chem/Mapper/solvers/slap/lap.py +174 -0
- synkit/Chem/Mapper/solvers/slap/sequential.py +696 -0
- synkit/Chem/Mapper/validation/__init__.py +1 -0
- synkit/Chem/Mapper/validation/aam_validator.py +442 -0
- synkit/Chem/Mapper/validation/annotations.py +274 -0
- synkit/Chem/Mapper/validation/bonds.py +124 -0
- synkit/Chem/Mapper/validation/cohorts.py +72 -0
- synkit/Chem/Mapper/validation/identifiability.py +150 -0
- synkit/Chem/Mapper/validation/numerical_scope.py +36 -0
- synkit/Chem/Mapper/validation/orbits.py +137 -0
- synkit/Chem/Molecule/__init__.py +0 -0
- synkit/Chem/Molecule/_chirality_loci.py +165 -0
- synkit/Chem/Molecule/_chirality_types.py +231 -0
- synkit/Chem/Molecule/_helical_loci.py +167 -0
- synkit/Chem/Molecule/_stereo_axis_evidence.py +202 -0
- synkit/Chem/Molecule/_stereo_element_detection.py +193 -0
- synkit/Chem/Molecule/_stereo_orientation_constraints.py +511 -0
- synkit/Chem/Molecule/_tetrahedral_carriers.py +63 -0
- synkit/Chem/Molecule/atom_features.py +379 -0
- synkit/Chem/Molecule/chirality.py +1041 -0
- synkit/Chem/Molecule/cip_assignment.py +569 -0
- synkit/Chem/Molecule/cip_ranking.py +843 -0
- synkit/Chem/Molecule/coordinate_stereo.py +503 -0
- synkit/Chem/Molecule/derived_stereo_naming.py +165 -0
- synkit/Chem/Molecule/descriptors.py +452 -0
- synkit/Chem/Molecule/formula.py +249 -0
- synkit/Chem/Molecule/global_stereo.py +175 -0
- synkit/Chem/Molecule/graph_annotator.py +300 -0
- synkit/Chem/Molecule/standardize.py +817 -0
- synkit/Chem/Molecule/stereo_evidence.py +179 -0
- synkit/Chem/Molecule/stereo_perception.py +1032 -0
- synkit/Chem/Molecule/valence.py +123 -0
- synkit/Chem/Reaction/Mapper/__init__.py +44 -0
- synkit/Chem/Reaction/__init__.py +20 -0
- synkit/Chem/Reaction/aam_validator.py +5 -0
- synkit/Chem/Reaction/balance_check.py +138 -0
- synkit/Chem/Reaction/canon_rsmi.py +249 -0
- synkit/Chem/Reaction/cleaning.py +58 -0
- synkit/Chem/Reaction/deionize.py +199 -0
- synkit/Chem/Reaction/explicit_h_audit.py +232 -0
- synkit/Chem/Reaction/fix_aam.py +64 -0
- synkit/Chem/Reaction/neutralize.py +207 -0
- synkit/Chem/Reaction/radical_wildcard.py +224 -0
- synkit/Chem/Reaction/standardize.py +203 -0
- synkit/Chem/Reaction/tautomerize.py +205 -0
- synkit/Chem/__init__.py +1 -0
- synkit/Chem/utils.py +315 -0
- synkit/Data/__init__.py +0 -0
- synkit/Data/aldol.json.gz +1 -0
- synkit/Data/gen_partial_aam.py +147 -0
- synkit/Data/paracetamol.json.gz +1 -0
- synkit/Graph/Canon/__init__.py +16 -0
- synkit/Graph/Canon/_native/__init__.py +13 -0
- synkit/Graph/Canon/_native/build.py +93 -0
- synkit/Graph/Canon/_native/lib/synkit_canon.dll +0 -0
- synkit/Graph/Canon/_native/prepare.py +49 -0
- synkit/Graph/Canon/_native/runtime.py +140 -0
- synkit/Graph/Canon/_native/search.py +137 -0
- synkit/Graph/Canon/canon_algs.py +228 -0
- synkit/Graph/Canon/canon_graph.py +531 -0
- synkit/Graph/Canon/cpp/CMakeLists.txt +42 -0
- synkit/Graph/Canon/cpp/README.md +123 -0
- synkit/Graph/Canon/cpp/examples/canonical_graph.cpp +16 -0
- synkit/Graph/Canon/cpp/include/synkit_canon/canon.h +85 -0
- synkit/Graph/Canon/cpp/src/canonical.cpp +391 -0
- synkit/Graph/Canon/cpp/src/symmetry.h +83 -0
- synkit/Graph/Canon/cpp/tests/symmetry.cpp +93 -0
- synkit/Graph/Canon/exact.py +1020 -0
- synkit/Graph/Canon/exact_refinement.py +368 -0
- synkit/Graph/Canon/nauty.py +143 -0
- synkit/Graph/Context/__init__.py +0 -0
- synkit/Graph/Context/hier_context.py +244 -0
- synkit/Graph/Context/radius_expand.py +252 -0
- synkit/Graph/FG/__init__.py +19 -0
- synkit/Graph/FG/api.py +39 -0
- synkit/Graph/FG/audit.py +155 -0
- synkit/Graph/FG/catalog.py +919 -0
- synkit/Graph/FG/detector.py +288 -0
- synkit/Graph/FG/model.py +97 -0
- synkit/Graph/FG/recognizers.py +274 -0
- synkit/Graph/FG/ring_system.py +154 -0
- synkit/Graph/Feature/Descriptors/topology.py +857 -0
- synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
- synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +222 -0
- synkit/Graph/Feature/__init__.py +5 -0
- synkit/Graph/Feature/graph_descriptors.py +323 -0
- synkit/Graph/Feature/graph_fps.py +96 -0
- synkit/Graph/Feature/graph_signature.py +239 -0
- synkit/Graph/Feature/hash_fps.py +133 -0
- synkit/Graph/Feature/morgan_fps.py +88 -0
- synkit/Graph/Feature/path_fps.py +81 -0
- synkit/Graph/Feature/wl_hash.py +134 -0
- synkit/Graph/Fusion/__init__.py +85 -0
- synkit/Graph/Fusion/candidate.py +559 -0
- synkit/Graph/Fusion/constructor.py +877 -0
- synkit/Graph/Fusion/identity.py +184 -0
- synkit/Graph/Fusion/interface.py +700 -0
- synkit/Graph/Fusion/search.py +241 -0
- synkit/Graph/Hyrogen/__init__.py +6 -0
- synkit/Graph/Hyrogen/_misc.py +591 -0
- synkit/Graph/Hyrogen/hcomplete.py +995 -0
- synkit/Graph/Hyrogen/hcomplete_legacy.py +368 -0
- synkit/Graph/Hyrogen/hcompletion_algorithms.py +713 -0
- synkit/Graph/Hyrogen/hextend.py +570 -0
- synkit/Graph/Hyrogen/hextend_legacy.py +215 -0
- synkit/Graph/ITS/__init__.py +4 -0
- synkit/Graph/ITS/its_builder.py +120 -0
- synkit/Graph/ITS/its_construction.py +719 -0
- synkit/Graph/ITS/its_decompose.py +572 -0
- synkit/Graph/ITS/its_destruction.py +331 -0
- synkit/Graph/ITS/its_expand.py +1251 -0
- synkit/Graph/ITS/its_relabel.py +187 -0
- synkit/Graph/ITS/its_reverter.py +356 -0
- synkit/Graph/ITS/normalize_aam.py +145 -0
- synkit/Graph/ITS/partial_its.py +238 -0
- synkit/Graph/ITS/rc_extractor.py +656 -0
- synkit/Graph/ITS/stereo.py +74 -0
- synkit/Graph/MTG/__init__.py +107 -0
- synkit/Graph/MTG/gluing.py +451 -0
- synkit/Graph/MTG/history.py +928 -0
- synkit/Graph/MTG/inventory.py +118 -0
- synkit/Graph/MTG/local.py +447 -0
- synkit/Graph/MTG/mcs_matcher.py +246 -0
- synkit/Graph/MTG/mtg.py +611 -0
- synkit/Graph/MTG/mtg_explore.py +74 -0
- synkit/Graph/MTG/observation.py +209 -0
- synkit/Graph/MTG/process.py +577 -0
- synkit/Graph/MTG/queries.py +66 -0
- synkit/Graph/MTG/realization.py +95 -0
- synkit/Graph/MTG/series_parallel.py +107 -0
- synkit/Graph/MTG/utils.py +424 -0
- synkit/Graph/Matcher/__init__.py +10 -0
- synkit/Graph/Matcher/approx_mcs.py +1264 -0
- synkit/Graph/Matcher/auto_est.py +736 -0
- synkit/Graph/Matcher/automorphism.py +314 -0
- synkit/Graph/Matcher/batch_cluster.py +224 -0
- synkit/Graph/Matcher/dedup_matches.py +247 -0
- synkit/Graph/Matcher/graph_cluster.py +191 -0
- synkit/Graph/Matcher/graph_matcher.py +312 -0
- synkit/Graph/Matcher/graph_morphism.py +421 -0
- synkit/Graph/Matcher/mcs_matcher.py +804 -0
- synkit/Graph/Matcher/multi_turbo_iso.py +186 -0
- synkit/Graph/Matcher/orbit.py +284 -0
- synkit/Graph/Matcher/partial_matcher.py +694 -0
- synkit/Graph/Matcher/sing.py +544 -0
- synkit/Graph/Matcher/subgraph_matcher.py +812 -0
- synkit/Graph/Matcher/turbo_iso.py +272 -0
- synkit/Graph/Matcher/wl_sel.py +537 -0
- synkit/Graph/Mech/__init__.py +26 -0
- synkit/Graph/Mech/conversion.py +1466 -0
- synkit/Graph/Mech/conversion_debug.py +237 -0
- synkit/Graph/Mech/electron_accounting.py +200 -0
- synkit/Graph/Mech/lwg_editor.py +406 -0
- synkit/Graph/Mech/lwg_ops.py +313 -0
- synkit/Graph/Morphism/__init__.py +99 -0
- synkit/Graph/Morphism/constraints.py +460 -0
- synkit/Graph/Morphism/llg.py +999 -0
- synkit/Graph/Morphism/morphism.py +349 -0
- synkit/Graph/Morphism/stereo.py +258 -0
- synkit/Graph/Morphism/stereo_morphism.py +383 -0
- synkit/Graph/Morphism/stereo_morphism_helpers.py +957 -0
- synkit/Graph/Morphism/stereo_morphism_types.py +117 -0
- synkit/Graph/Stereo/__init__.py +376 -0
- synkit/Graph/Stereo/_descriptor_core.py +207 -0
- synkit/Graph/Stereo/_descriptor_serialization.py +138 -0
- synkit/Graph/Stereo/canonical.py +648 -0
- synkit/Graph/Stereo/changes.py +964 -0
- synkit/Graph/Stereo/composition.py +391 -0
- synkit/Graph/Stereo/configured.py +946 -0
- synkit/Graph/Stereo/couplings.py +245 -0
- synkit/Graph/Stereo/descriptors.py +1030 -0
- synkit/Graph/Stereo/enumeration.py +597 -0
- synkit/Graph/Stereo/extended_descriptors.py +406 -0
- synkit/Graph/Stereo/global_configured.py +152 -0
- synkit/Graph/Stereo/global_stereo.py +304 -0
- synkit/Graph/Stereo/identity.py +648 -0
- synkit/Graph/Stereo/interchange.py +496 -0
- synkit/Graph/Stereo/legacy.py +460 -0
- synkit/Graph/Stereo/matching.py +899 -0
- synkit/Graph/Stereo/native.py +154 -0
- synkit/Graph/Stereo/orbits.py +668 -0
- synkit/Graph/Stereo/outcomes.py +99 -0
- synkit/Graph/Stereo/pairwise.py +350 -0
- synkit/Graph/Stereo/rdkit_adapter.py +662 -0
- synkit/Graph/Stereo/rdkit_identity.py +118 -0
- synkit/Graph/Stereo/rdkit_resonance.py +140 -0
- synkit/Graph/Stereo/reaction_interchange.py +266 -0
- synkit/Graph/Stereo/semantics.py +296 -0
- synkit/Graph/Stereo/supports.py +257 -0
- synkit/Graph/Stereo/wire.py +339 -0
- synkit/Graph/Wildcard/__init__.py +0 -0
- synkit/Graph/Wildcard/fuse_graph.py +156 -0
- synkit/Graph/Wildcard/graph_wc.py +303 -0
- synkit/Graph/Wildcard/its_merge.py +857 -0
- synkit/Graph/Wildcard/radwc.py +114 -0
- synkit/Graph/Wildcard/wc_matcher.py +576 -0
- synkit/Graph/Wildcard/wildcard.py +225 -0
- synkit/Graph/__init__.py +17 -0
- synkit/Graph/canon_graph.py +14 -0
- synkit/Graph/syn_graph.py +199 -0
- synkit/Graph/utils.py +324 -0
- synkit/IO/__init__.py +4 -0
- synkit/IO/chem_converter.py +1059 -0
- synkit/IO/combinatorial/__init__.py +8 -0
- synkit/IO/combinatorial/gml_to_graph.py +223 -0
- synkit/IO/combinatorial/graph_to_gml.py +280 -0
- synkit/IO/combinatorial/graph_to_smarts.py +186 -0
- synkit/IO/combinatorial/smarts_expander.py +147 -0
- synkit/IO/combinatorial/smarts_generalizer.py +133 -0
- synkit/IO/combinatorial/smarts_to_graph.py +179 -0
- synkit/IO/conversion.py +42 -0
- synkit/IO/data_io.py +269 -0
- synkit/IO/data_process.py +143 -0
- synkit/IO/debug.py +73 -0
- synkit/IO/gml_to_nx.py +150 -0
- synkit/IO/graph_to_mol.py +199 -0
- synkit/IO/mol_to_graph.py +1105 -0
- synkit/IO/mol_to_graph_chemistry.py +545 -0
- synkit/IO/nx_to_gml.py +209 -0
- synkit/IO/smiles_to_id.py +123 -0
- synkit/Mechanism/__init__.py +151 -0
- synkit/Mechanism/adapters.py +136 -0
- synkit/Mechanism/audit.py +323 -0
- synkit/Mechanism/benchmark.py +638 -0
- synkit/Mechanism/compatibility.py +13 -0
- synkit/Mechanism/drawing.py +85 -0
- synkit/Mechanism/electron_models.py +725 -0
- synkit/Mechanism/equivalence.py +521 -0
- synkit/Mechanism/evidence.py +262 -0
- synkit/Mechanism/interchange.py +141 -0
- synkit/Mechanism/model.py +918 -0
- synkit/Mechanism/neighbor_stereo.py +312 -0
- synkit/Mechanism/radical_data.py +740 -0
- synkit/Mechanism/replay.py +986 -0
- synkit/Mechanism/schema.py +264 -0
- synkit/Mechanism/stereo_state.py +416 -0
- synkit/Mechanism/symbols.py +91 -0
- synkit/Rule/Apply/__init__.py +35 -0
- synkit/Rule/Apply/conditions.py +45 -0
- synkit/Rule/Apply/dpo.py +998 -0
- synkit/Rule/Apply/rule_matcher.py +218 -0
- synkit/Rule/Apply/validation.py +354 -0
- synkit/Rule/Compose/__init__.py +106 -0
- synkit/Rule/Compose/_identity.py +43 -0
- synkit/Rule/Compose/composition.py +976 -0
- synkit/Rule/Compose/laws.py +439 -0
- synkit/Rule/Compose/search.py +771 -0
- synkit/Rule/Compose/synrule.py +170 -0
- synkit/Rule/Modify/__init__.py +0 -0
- synkit/Rule/Modify/implict_rule.py +56 -0
- synkit/Rule/Modify/longest_path.py +92 -0
- synkit/Rule/Modify/molecule_rule.py +115 -0
- synkit/Rule/Modify/prune_templates.py +78 -0
- synkit/Rule/Modify/rule_utils.py +194 -0
- synkit/Rule/Modify/strip_rule.py +97 -0
- synkit/Rule/__init__.py +28 -0
- synkit/Rule/generic_stereo.py +884 -0
- synkit/Rule/stereo_identity.py +271 -0
- synkit/Rule/syn_rule.py +1090 -0
- synkit/Synthesis/MSR/__init__.py +0 -0
- synkit/Synthesis/MSR/path_finder.py +239 -0
- synkit/Synthesis/Metrics/__init__.py +0 -0
- synkit/Synthesis/Metrics/_base.py +52 -0
- synkit/Synthesis/Metrics/_plot.py +129 -0
- synkit/Synthesis/Metrics/_ranking.py +179 -0
- synkit/Synthesis/RBL/__init__.py +80 -0
- synkit/Synthesis/RBL/engine.py +1249 -0
- synkit/Synthesis/RBL/matching.py +652 -0
- synkit/Synthesis/RBL/overlap.py +336 -0
- synkit/Synthesis/RBL/pipeline.py +376 -0
- synkit/Synthesis/RBL/policy.py +139 -0
- synkit/Synthesis/RBL/proof.py +370 -0
- synkit/Synthesis/RBL/reaction.py +506 -0
- synkit/Synthesis/RBL/state.py +169 -0
- synkit/Synthesis/RBL/validation.py +1060 -0
- synkit/Synthesis/Reactor/__init__.py +60 -0
- synkit/Synthesis/Reactor/core/__init__.py +1 -0
- synkit/Synthesis/Reactor/core/engine.py +967 -0
- synkit/Synthesis/Reactor/core/product.py +621 -0
- synkit/Synthesis/Reactor/core/rewrite.py +561 -0
- synkit/Synthesis/Reactor/core/strategy.py +44 -0
- synkit/Synthesis/Reactor/matching/__init__.py +1 -0
- synkit/Synthesis/Reactor/matching/mixin.py +920 -0
- synkit/Synthesis/Reactor/matching/policy.py +345 -0
- synkit/Synthesis/Reactor/matching/symmetry.py +309 -0
- synkit/Synthesis/Reactor/output/__init__.py +1 -0
- synkit/Synthesis/Reactor/output/deduplication.py +703 -0
- synkit/Synthesis/Reactor/output/policy.py +16 -0
- synkit/Synthesis/Reactor/output/serialization.py +485 -0
- synkit/Synthesis/Reactor/output/structural.py +893 -0
- synkit/Synthesis/Reactor/stereo/__init__.py +1 -0
- synkit/Synthesis/Reactor/stereo/assignment.py +35 -0
- synkit/Synthesis/Reactor/stereo/mixin.py +780 -0
- synkit/Synthesis/Reactor/variants/__init__.py +1 -0
- synkit/Synthesis/Reactor/variants/imbalanced.py +180 -0
- synkit/Synthesis/Reactor/variants/partial.py +84 -0
- synkit/Synthesis/Reactor/workflow/__init__.py +1 -0
- synkit/Synthesis/Reactor/workflow/batch.py +550 -0
- synkit/Synthesis/Reactor/workflow/benchmark.py +143 -0
- synkit/Synthesis/Reactor/workflow/postprocess.py +267 -0
- synkit/Synthesis/Reactor/workflow/rule_filter.py +210 -0
- synkit/Synthesis/__init__.py +0 -0
- synkit/Synthesis/reactor_utils.py +316 -0
- synkit/Utils/__init__.py +0 -0
- synkit/Utils/utils.py +172 -0
- synkit/Vis/__init__.py +91 -0
- synkit/Vis/crn/__init__.py +40 -0
- synkit/Vis/epd/__init__.py +20 -0
- synkit/Vis/epd/arrows.py +425 -0
- synkit/Vis/epd/chem.py +197 -0
- synkit/Vis/epd/constants.py +83 -0
- synkit/Vis/epd/layout.py +283 -0
- synkit/Vis/epd/mapping.py +51 -0
- synkit/Vis/epd/models.py +136 -0
- synkit/Vis/epd/render.py +543 -0
- synkit/Vis/epd/utils.py +108 -0
- synkit/Vis/epd/visualizer.py +811 -0
- synkit/Vis/graph_visualizer.py +375 -0
- synkit/Vis/its/__init__.py +19 -0
- synkit/Vis/its/drawer.py +615 -0
- synkit/Vis/its_drawer.py +35 -0
- synkit/Vis/molecule/__init__.py +31 -0
- synkit/Vis/molecule/drawer.py +563 -0
- synkit/Vis/molecule/reaction.py +285 -0
- synkit/Vis/molecule_drawer.py +33 -0
- synkit/Vis/mtg/__init__.py +17 -0
- synkit/Vis/mtg/drawer.py +769 -0
- synkit/Vis/mtg_drawer.py +34 -0
- synkit/Vis/pdf_writer.py +146 -0
- synkit/Vis/reaction/__init__.py +19 -0
- synkit/Vis/reaction/rule.py +141 -0
- synkit/Vis/reaction/rxn.py +532 -0
- synkit/Vis/reaction_drawer.py +41 -0
- synkit/Vis/space/__init__.py +17 -0
- synkit/Vis/space/chemical.py +80 -0
- synkit/Vis/space/embedding.py +87 -0
- synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
- synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
- synkit/Vis/vis_synedu/rxn_vis.py +377 -0
- synkit/Vis/vis_synedu/vis.py +501 -0
- synkit/Vis/visual_drawer.py +215 -0
- synkit/Vis/visual_model.py +520 -0
- synkit/__init__.py +3 -0
- synkit/_native.py +91 -0
- synkit/examples.py +50 -0
- synkit/version.py +23 -0
- synkit-1.6.5.dist-info/METADATA +156 -0
- synkit-1.6.5.dist-info/RECORD +636 -0
- synkit-1.6.5.dist-info/WHEEL +4 -0
- synkit-1.6.5.dist-info/licenses/LICENSE +21 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
synkit/Rule/Apply/dpo.py
ADDED
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
"""Typed DPO rule spans and application over finite Lewis-labelled graphs.
|
|
2
|
+
|
|
3
|
+
Rule interfaces use the endpoint schema with explicitly undefined coordinates.
|
|
4
|
+
Contexts erase these coordinates; labels on the two squares are checked by
|
|
5
|
+
coordinate unions and agreement pullbacks. Matches into total hosts are exact.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections import Counter
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from numbers import Integral
|
|
14
|
+
from typing import Any, Hashable, Mapping
|
|
15
|
+
|
|
16
|
+
import networkx as nx
|
|
17
|
+
|
|
18
|
+
from synkit.Graph.Morphism import (
|
|
19
|
+
ISOTOPE_ELECTRON_LLG_SCHEMA,
|
|
20
|
+
LLGError,
|
|
21
|
+
LLGIssueCode,
|
|
22
|
+
LLGMorphism,
|
|
23
|
+
LewisLabelledGraph,
|
|
24
|
+
derive_electron_labeled_graph,
|
|
25
|
+
is_electron_schema,
|
|
26
|
+
llg_from_its,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DPOIssueCode(str, Enum):
|
|
31
|
+
"""Stable refusal codes for rule construction and application."""
|
|
32
|
+
|
|
33
|
+
SCHEMA_MISMATCH = "DPO_SCHEMA_MISMATCH"
|
|
34
|
+
INTERFACE_SCHEMA = "DPO_INTERFACE_SCHEMA"
|
|
35
|
+
INTERFACE_PARTIAL = "DPO_INTERFACE_PARTIAL"
|
|
36
|
+
INTERFACE_NON_INJECTIVE = "DPO_INTERFACE_NON_INJECTIVE"
|
|
37
|
+
INTERFACE_INCIDENCE = "DPO_INTERFACE_INCIDENCE"
|
|
38
|
+
INTERFACE_LABEL = "DPO_INTERFACE_LABEL"
|
|
39
|
+
MATCH_PARTIAL = "DPO_MATCH_PARTIAL"
|
|
40
|
+
IDENTIFICATION = "DPO_IDENTIFICATION_CONDITION"
|
|
41
|
+
MATCH_OUTSIDE_HOST = "DPO_MATCH_OUTSIDE_HOST"
|
|
42
|
+
MATCH_INCIDENCE = "DPO_MATCH_INCIDENCE"
|
|
43
|
+
MATCH_LABEL = "DPO_MATCH_LABEL"
|
|
44
|
+
DANGLING = "DPO_DANGLING_CONDITION"
|
|
45
|
+
RESULT_LABEL = "DPO_RESULT_LABEL_CONFLICT"
|
|
46
|
+
CLOSED_MATERIAL = "DPO_CLOSED_MATERIAL_IMBALANCE"
|
|
47
|
+
CLOSED_ELECTRON = "DPO_CLOSED_ELECTRON_IMBALANCE"
|
|
48
|
+
OPEN_TOKEN_REQUIRED = "DPO_OPEN_TOKEN_REQUIRED"
|
|
49
|
+
OPEN_TOKEN_MISMATCH = "DPO_OPEN_TOKEN_MISMATCH"
|
|
50
|
+
ELECTRON_SCHEMA_REQUIRED = "DPO_ELECTRON_SCHEMA_REQUIRED"
|
|
51
|
+
NONCOMMUTATIVE = "DPO_NONCOMMUTATIVE_SQUARE"
|
|
52
|
+
SOURCE_MUTATED = "DPO_SOURCE_MUTATED"
|
|
53
|
+
APPLICATION_CONDITION = "DPO_APPLICATION_CONDITION"
|
|
54
|
+
CLOSED_ISOTOPE = "DPO_CLOSED_ISOTOPE_IMBALANCE"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class DPOIssue:
|
|
59
|
+
code: DPOIssueCode
|
|
60
|
+
message: str
|
|
61
|
+
context: Mapping[str, Any] = field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
def to_dict(self) -> dict[str, Any]:
|
|
64
|
+
return {
|
|
65
|
+
"code": self.code.value,
|
|
66
|
+
"message": self.message,
|
|
67
|
+
"context": dict(self.context),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DPOError(ValueError):
|
|
72
|
+
"""Raised when a DPO premise or construction fails."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, *issues: DPOIssue):
|
|
75
|
+
self.issues = tuple(issues)
|
|
76
|
+
super().__init__("; ".join(issue.message for issue in issues))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SystemBoundary(str, Enum):
|
|
80
|
+
"""Resource policy attached to a rule."""
|
|
81
|
+
|
|
82
|
+
ABSTRACT = "abstract"
|
|
83
|
+
CLOSED = "closed"
|
|
84
|
+
OPEN = "open"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class EnvironmentToken:
|
|
89
|
+
"""Explicit net resources supplied by an open environment.
|
|
90
|
+
|
|
91
|
+
Deltas use ``right - left`` convention. Positive values enter the graph
|
|
92
|
+
system; negative values leave it.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
name: str
|
|
96
|
+
element_delta: tuple[tuple[str, int], ...] = ()
|
|
97
|
+
electron_delta: float = 0.0
|
|
98
|
+
isotope_delta: tuple[tuple[tuple[str, int], int], ...] = ()
|
|
99
|
+
|
|
100
|
+
def __post_init__(self) -> None:
|
|
101
|
+
if not self.name.strip():
|
|
102
|
+
raise ValueError("An environment token requires a non-empty name.")
|
|
103
|
+
normalized = tuple(
|
|
104
|
+
sorted(
|
|
105
|
+
(str(element), int(count))
|
|
106
|
+
for element, count in dict(self.element_delta).items()
|
|
107
|
+
if int(count) != 0
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
object.__setattr__(self, "element_delta", normalized)
|
|
111
|
+
object.__setattr__(self, "electron_delta", float(self.electron_delta))
|
|
112
|
+
object.__setattr__(
|
|
113
|
+
self, "isotope_delta", _normalize_isotope_delta(self.isotope_delta)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class RuleEmbedding:
|
|
119
|
+
"""An embedding preserving identity and every defined interface coordinate."""
|
|
120
|
+
|
|
121
|
+
interface: LewisLabelledGraph
|
|
122
|
+
endpoint: LewisLabelledGraph
|
|
123
|
+
f: tuple[tuple[Hashable, Hashable], ...]
|
|
124
|
+
|
|
125
|
+
def __post_init__(self) -> None:
|
|
126
|
+
pairs = tuple(self.f.items()) if isinstance(self.f, Mapping) else tuple(self.f)
|
|
127
|
+
object.__setattr__(self, "f", tuple(sorted(pairs, key=repr)))
|
|
128
|
+
if (
|
|
129
|
+
self.interface.schema != self.endpoint.schema
|
|
130
|
+
or not self.interface.partial
|
|
131
|
+
or self.endpoint.partial
|
|
132
|
+
):
|
|
133
|
+
raise DPOError(
|
|
134
|
+
DPOIssue(
|
|
135
|
+
DPOIssueCode.INTERFACE_SCHEMA,
|
|
136
|
+
"The rule interface must be a partial pattern under the endpoint schema.",
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
source_keys = tuple(left for left, _ in pairs)
|
|
140
|
+
target_values = tuple(right for _, right in pairs)
|
|
141
|
+
missing = set(self.interface.node_ids) - set(source_keys)
|
|
142
|
+
outside = set(target_values) - set(self.endpoint.node_ids)
|
|
143
|
+
if (
|
|
144
|
+
len(set(source_keys)) != len(source_keys)
|
|
145
|
+
or missing
|
|
146
|
+
or outside
|
|
147
|
+
or set(source_keys) != set(self.interface.node_ids)
|
|
148
|
+
):
|
|
149
|
+
raise DPOError(
|
|
150
|
+
DPOIssue(
|
|
151
|
+
DPOIssueCode.INTERFACE_PARTIAL,
|
|
152
|
+
"An interface arm is total and lands in its endpoint.",
|
|
153
|
+
{
|
|
154
|
+
"missing": tuple(sorted(map(repr, missing))),
|
|
155
|
+
"outside": tuple(sorted(map(repr, outside))),
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if len(set(target_values)) != len(target_values):
|
|
160
|
+
raise DPOError(
|
|
161
|
+
DPOIssue(
|
|
162
|
+
DPOIssueCode.INTERFACE_NON_INJECTIVE,
|
|
163
|
+
"An interface arm must be injective.",
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
mapping = dict(pairs)
|
|
167
|
+
issues: list[DPOIssue] = []
|
|
168
|
+
for node, image in pairs:
|
|
169
|
+
interface_labels = self.interface.node_labels(node, semantic=True)
|
|
170
|
+
endpoint_labels = self.endpoint.node_labels(image, semantic=True)
|
|
171
|
+
if not set(self.endpoint.schema.node_identity) <= set(
|
|
172
|
+
interface_labels
|
|
173
|
+
) or any(
|
|
174
|
+
endpoint_labels.get(key) != value
|
|
175
|
+
for key, value in interface_labels.items()
|
|
176
|
+
):
|
|
177
|
+
issues.append(
|
|
178
|
+
DPOIssue(
|
|
179
|
+
DPOIssueCode.INTERFACE_LABEL,
|
|
180
|
+
"An interface node omits identity or does not preserve a defined coordinate.",
|
|
181
|
+
{"node": repr(node), "image": repr(image)},
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
for edge in self.interface.edge_keys:
|
|
185
|
+
image = frozenset(mapping[node] for node in edge)
|
|
186
|
+
if image not in self.endpoint.edge_keys:
|
|
187
|
+
issues.append(
|
|
188
|
+
DPOIssue(
|
|
189
|
+
DPOIssueCode.INTERFACE_INCIDENCE,
|
|
190
|
+
"An interface edge image is absent from its endpoint.",
|
|
191
|
+
{"edge": tuple(sorted(map(repr, edge)))},
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
continue
|
|
195
|
+
interface_labels = self.interface.edge_labels(edge, semantic=True)
|
|
196
|
+
endpoint_labels = self.endpoint.edge_labels(image, semantic=True)
|
|
197
|
+
if not set(self.endpoint.schema.edge_identity) <= set(
|
|
198
|
+
interface_labels
|
|
199
|
+
) or any(
|
|
200
|
+
endpoint_labels.get(key) != value
|
|
201
|
+
for key, value in interface_labels.items()
|
|
202
|
+
):
|
|
203
|
+
issues.append(
|
|
204
|
+
DPOIssue(
|
|
205
|
+
DPOIssueCode.INTERFACE_LABEL,
|
|
206
|
+
"An interface edge omits identity or does not preserve a defined coordinate.",
|
|
207
|
+
{"edge": tuple(sorted(map(repr, edge)))},
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
if issues:
|
|
211
|
+
raise DPOError(*issues)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def mapping(self) -> dict[Hashable, Hashable]:
|
|
215
|
+
return dict(self.f)
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def edge_mapping(self) -> dict[frozenset[Hashable], frozenset[Hashable]]:
|
|
219
|
+
mapping = self.mapping
|
|
220
|
+
return {
|
|
221
|
+
edge: frozenset(mapping[node] for node in edge)
|
|
222
|
+
for edge in self.interface.edge_keys
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass(frozen=True)
|
|
227
|
+
class ResourceDelta:
|
|
228
|
+
"""Rule-level material and electron delta using right-minus-left sign."""
|
|
229
|
+
|
|
230
|
+
element_delta: tuple[tuple[str, int], ...]
|
|
231
|
+
electron_delta: float
|
|
232
|
+
isotope_delta: tuple[tuple[tuple[str, int], int], ...] = ()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _normalize_isotope_delta(pairs):
|
|
236
|
+
pairs = tuple(pairs)
|
|
237
|
+
for (element, mass), count in pairs:
|
|
238
|
+
if (
|
|
239
|
+
not str(element)
|
|
240
|
+
or isinstance(mass, bool)
|
|
241
|
+
or not isinstance(mass, Integral)
|
|
242
|
+
or mass < 0
|
|
243
|
+
or isinstance(count, bool)
|
|
244
|
+
or not isinstance(count, Integral)
|
|
245
|
+
):
|
|
246
|
+
raise ValueError(
|
|
247
|
+
"Isotope resource deltas need an element, integral mass and integral count."
|
|
248
|
+
)
|
|
249
|
+
return tuple(
|
|
250
|
+
sorted(
|
|
251
|
+
((str(element), int(mass)), int(count))
|
|
252
|
+
for (element, mass), count in dict(pairs).items()
|
|
253
|
+
if count
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _isotope_inventory(graph):
|
|
259
|
+
if graph.schema != ISOTOPE_ELECTRON_LLG_SCHEMA:
|
|
260
|
+
return Counter()
|
|
261
|
+
values = Counter(
|
|
262
|
+
(
|
|
263
|
+
str(graph.node_labels(node)["element"]),
|
|
264
|
+
int(graph.node_labels(node)["isotope"]),
|
|
265
|
+
)
|
|
266
|
+
for node in graph.node_ids
|
|
267
|
+
)
|
|
268
|
+
values[("H", 0)] += sum(int(graph.node_labels(n)["hcount"]) for n in graph.node_ids)
|
|
269
|
+
return values
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _resource_delta(
|
|
273
|
+
left: LewisLabelledGraph, right: LewisLabelledGraph
|
|
274
|
+
) -> ResourceDelta:
|
|
275
|
+
if not is_electron_schema(left.schema) or not is_electron_schema(right.schema):
|
|
276
|
+
raise DPOError(
|
|
277
|
+
DPOIssue(
|
|
278
|
+
DPOIssueCode.ELECTRON_SCHEMA_REQUIRED,
|
|
279
|
+
"Chemical resource policies require the electron-complete LLG schema.",
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def inventory(graph: LewisLabelledGraph) -> tuple[Counter[str], float]:
|
|
284
|
+
elements: Counter[str] = Counter()
|
|
285
|
+
electrons = 0.0
|
|
286
|
+
for node in graph.node_ids:
|
|
287
|
+
labels = graph.node_labels(node)
|
|
288
|
+
elements[str(labels["element"])] += 1
|
|
289
|
+
hydrogen_count = float(labels["hcount"])
|
|
290
|
+
if not hydrogen_count.is_integer() or hydrogen_count < 0:
|
|
291
|
+
raise DPOError(
|
|
292
|
+
DPOIssue(
|
|
293
|
+
DPOIssueCode.CLOSED_MATERIAL,
|
|
294
|
+
"Unexpanded hydrogen inventory must be a nonnegative integer.",
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
elements["H"] += int(hydrogen_count)
|
|
298
|
+
electrons += (
|
|
299
|
+
float(labels["valence_electrons"])
|
|
300
|
+
+ float(labels["hcount"])
|
|
301
|
+
- float(labels["charge"])
|
|
302
|
+
)
|
|
303
|
+
return elements, electrons
|
|
304
|
+
|
|
305
|
+
left_elements, left_electrons = inventory(left)
|
|
306
|
+
right_elements, right_electrons = inventory(right)
|
|
307
|
+
keys = sorted(set(left_elements) | set(right_elements))
|
|
308
|
+
element_delta = tuple(
|
|
309
|
+
(key, right_elements[key] - left_elements[key])
|
|
310
|
+
for key in keys
|
|
311
|
+
if right_elements[key] != left_elements[key]
|
|
312
|
+
)
|
|
313
|
+
isotope_delta = _isotope_inventory(right)
|
|
314
|
+
isotope_delta.subtract(_isotope_inventory(left))
|
|
315
|
+
return ResourceDelta(
|
|
316
|
+
element_delta,
|
|
317
|
+
right_electrons - left_electrons,
|
|
318
|
+
_normalize_isotope_delta(isotope_delta.items()),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _closed_boundary_issues(delta, environment):
|
|
323
|
+
issues = []
|
|
324
|
+
if delta.isotope_delta:
|
|
325
|
+
issues.append(
|
|
326
|
+
DPOIssue(
|
|
327
|
+
DPOIssueCode.CLOSED_ISOTOPE,
|
|
328
|
+
"A closed rule changes the isotope-resolved material inventory.",
|
|
329
|
+
{"delta": delta.isotope_delta},
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
if delta.element_delta:
|
|
333
|
+
issues.append(
|
|
334
|
+
DPOIssue(
|
|
335
|
+
DPOIssueCode.CLOSED_MATERIAL,
|
|
336
|
+
"A closed rule changes the elemental inventory.",
|
|
337
|
+
{"delta": delta.element_delta},
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
if delta.electron_delta != 0:
|
|
341
|
+
issues.append(
|
|
342
|
+
DPOIssue(
|
|
343
|
+
DPOIssueCode.CLOSED_ELECTRON,
|
|
344
|
+
"A closed rule changes the electron inventory.",
|
|
345
|
+
{"delta": delta.electron_delta},
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
if environment is not None:
|
|
349
|
+
issues.append(
|
|
350
|
+
DPOIssue(
|
|
351
|
+
DPOIssueCode.OPEN_TOKEN_MISMATCH,
|
|
352
|
+
"A closed rule cannot carry an environment token.",
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
return issues
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@dataclass(frozen=True)
|
|
359
|
+
class RuleSpan:
|
|
360
|
+
"""A partially labelled DPO rule ``L <- K -> R``."""
|
|
361
|
+
|
|
362
|
+
left: LewisLabelledGraph
|
|
363
|
+
interface: LewisLabelledGraph
|
|
364
|
+
right: LewisLabelledGraph
|
|
365
|
+
left_arm: RuleEmbedding
|
|
366
|
+
right_arm: RuleEmbedding
|
|
367
|
+
boundary: SystemBoundary = SystemBoundary.ABSTRACT
|
|
368
|
+
environment: EnvironmentToken | None = None
|
|
369
|
+
name: str = "rule"
|
|
370
|
+
condition: Any = None
|
|
371
|
+
|
|
372
|
+
def __post_init__(self) -> None:
|
|
373
|
+
if self.left.partial or self.right.partial:
|
|
374
|
+
raise DPOError(
|
|
375
|
+
DPOIssue(
|
|
376
|
+
DPOIssueCode.INTERFACE_SCHEMA,
|
|
377
|
+
"Rule endpoints must have total semantic labels.",
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
if self.left.schema != self.right.schema:
|
|
381
|
+
raise DPOError(
|
|
382
|
+
DPOIssue(
|
|
383
|
+
DPOIssueCode.SCHEMA_MISMATCH,
|
|
384
|
+
"Rule endpoints must use the same LLG schema.",
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
if (
|
|
388
|
+
self.left_arm.interface != self.interface
|
|
389
|
+
or self.right_arm.interface != self.interface
|
|
390
|
+
or self.left_arm.endpoint != self.left
|
|
391
|
+
or self.right_arm.endpoint != self.right
|
|
392
|
+
):
|
|
393
|
+
raise DPOError(
|
|
394
|
+
DPOIssue(
|
|
395
|
+
DPOIssueCode.INTERFACE_SCHEMA,
|
|
396
|
+
"Rule arms must have the declared interface and endpoints.",
|
|
397
|
+
)
|
|
398
|
+
)
|
|
399
|
+
boundary = SystemBoundary(self.boundary)
|
|
400
|
+
object.__setattr__(self, "boundary", boundary)
|
|
401
|
+
if boundary is SystemBoundary.ABSTRACT:
|
|
402
|
+
if self.environment is not None:
|
|
403
|
+
raise DPOError(
|
|
404
|
+
DPOIssue(
|
|
405
|
+
DPOIssueCode.OPEN_TOKEN_MISMATCH,
|
|
406
|
+
"Abstract rules do not consume chemical environment tokens.",
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
return
|
|
410
|
+
delta = _resource_delta(self.left, self.right)
|
|
411
|
+
if boundary is SystemBoundary.CLOSED:
|
|
412
|
+
issues = _closed_boundary_issues(delta, self.environment)
|
|
413
|
+
if issues:
|
|
414
|
+
raise DPOError(*issues)
|
|
415
|
+
return
|
|
416
|
+
if self.environment is None:
|
|
417
|
+
raise DPOError(
|
|
418
|
+
DPOIssue(
|
|
419
|
+
DPOIssueCode.OPEN_TOKEN_REQUIRED,
|
|
420
|
+
"An open rule requires an explicit environment token.",
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
if (
|
|
424
|
+
self.environment.element_delta != delta.element_delta
|
|
425
|
+
or self.environment.electron_delta != delta.electron_delta
|
|
426
|
+
or self.environment.isotope_delta != delta.isotope_delta
|
|
427
|
+
):
|
|
428
|
+
raise DPOError(
|
|
429
|
+
DPOIssue(
|
|
430
|
+
DPOIssueCode.OPEN_TOKEN_MISMATCH,
|
|
431
|
+
"The environment token does not equal the rule resource delta.",
|
|
432
|
+
{
|
|
433
|
+
"expected_elements": delta.element_delta,
|
|
434
|
+
"observed_elements": self.environment.element_delta,
|
|
435
|
+
"expected_electrons": delta.electron_delta,
|
|
436
|
+
"observed_electrons": self.environment.electron_delta,
|
|
437
|
+
"expected_isotopes": delta.isotope_delta,
|
|
438
|
+
"observed_isotopes": self.environment.isotope_delta,
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def from_mapping(
|
|
445
|
+
cls,
|
|
446
|
+
left: LewisLabelledGraph,
|
|
447
|
+
right: LewisLabelledGraph,
|
|
448
|
+
preserved_nodes: Mapping[Hashable, Hashable],
|
|
449
|
+
*,
|
|
450
|
+
preserved_edges: set[frozenset[Hashable]] | None = None,
|
|
451
|
+
boundary: SystemBoundary | str = SystemBoundary.ABSTRACT,
|
|
452
|
+
environment: EnvironmentToken | None = None,
|
|
453
|
+
name: str = "rule",
|
|
454
|
+
) -> "RuleSpan":
|
|
455
|
+
"""Build an explicit interface from a preserved endpoint mapping.
|
|
456
|
+
|
|
457
|
+
``preserved_edges`` contains left-side edge keys. When omitted, every
|
|
458
|
+
left edge whose mapped right edge exists is structurally preserved.
|
|
459
|
+
"""
|
|
460
|
+
if left.schema != right.schema:
|
|
461
|
+
raise DPOError(
|
|
462
|
+
DPOIssue(
|
|
463
|
+
DPOIssueCode.SCHEMA_MISMATCH,
|
|
464
|
+
"Rule endpoints must use the same LLG schema.",
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
pairs = tuple(sorted(preserved_nodes.items(), key=repr))
|
|
468
|
+
left_nodes = tuple(node for node, _ in pairs)
|
|
469
|
+
if len(set(left_nodes)) != len(left_nodes):
|
|
470
|
+
raise DPOError(
|
|
471
|
+
DPOIssue(
|
|
472
|
+
DPOIssueCode.INTERFACE_PARTIAL,
|
|
473
|
+
"Each preserved left node occurs once.",
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
interface_schema = left.schema
|
|
477
|
+
interface_graph = nx.Graph()
|
|
478
|
+
for interface_node, (left_node, right_node) in enumerate(pairs):
|
|
479
|
+
if left_node not in left.node_ids or right_node not in right.node_ids:
|
|
480
|
+
raise DPOError(
|
|
481
|
+
DPOIssue(
|
|
482
|
+
DPOIssueCode.INTERFACE_PARTIAL,
|
|
483
|
+
"A preserved node must exist in both endpoints.",
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
left_identity = {
|
|
487
|
+
key: left.node_labels(left_node, semantic=True)[key]
|
|
488
|
+
for key in left.schema.node_identity
|
|
489
|
+
}
|
|
490
|
+
right_identity = {
|
|
491
|
+
key: right.node_labels(right_node, semantic=True)[key]
|
|
492
|
+
for key in right.schema.node_identity
|
|
493
|
+
}
|
|
494
|
+
if left_identity != right_identity:
|
|
495
|
+
raise DPOError(
|
|
496
|
+
DPOIssue(
|
|
497
|
+
DPOIssueCode.INTERFACE_LABEL,
|
|
498
|
+
"Preserved nodes disagree on identity labels.",
|
|
499
|
+
{"left": repr(left_node), "right": repr(right_node)},
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
left_values = left.node_labels(left_node, semantic=True)
|
|
503
|
+
right_values = right.node_labels(right_node, semantic=True)
|
|
504
|
+
common = {
|
|
505
|
+
key: value
|
|
506
|
+
for key, value in left_values.items()
|
|
507
|
+
if key in right_values and right_values[key] == value
|
|
508
|
+
}
|
|
509
|
+
interface_graph.add_node(interface_node, **common)
|
|
510
|
+
|
|
511
|
+
left_to_interface = {
|
|
512
|
+
left_node: interface_node
|
|
513
|
+
for interface_node, (left_node, _) in enumerate(pairs)
|
|
514
|
+
}
|
|
515
|
+
right_by_left = dict(pairs)
|
|
516
|
+
candidates = {
|
|
517
|
+
edge
|
|
518
|
+
for edge in left.edge_keys
|
|
519
|
+
if edge <= set(left_to_interface)
|
|
520
|
+
and frozenset(right_by_left[node] for node in edge) in right.edge_keys
|
|
521
|
+
}
|
|
522
|
+
selected = candidates if preserved_edges is None else set(preserved_edges)
|
|
523
|
+
if not selected <= candidates:
|
|
524
|
+
raise DPOError(
|
|
525
|
+
DPOIssue(
|
|
526
|
+
DPOIssueCode.INTERFACE_INCIDENCE,
|
|
527
|
+
"A preserved edge must exist under both endpoint arms.",
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
for edge in selected:
|
|
531
|
+
left_a, left_b = tuple(edge)
|
|
532
|
+
right_edge = frozenset((right_by_left[left_a], right_by_left[left_b]))
|
|
533
|
+
left_identity = {
|
|
534
|
+
key: left.edge_labels(edge, semantic=True)[key]
|
|
535
|
+
for key in left.schema.edge_identity
|
|
536
|
+
}
|
|
537
|
+
right_identity = {
|
|
538
|
+
key: right.edge_labels(right_edge, semantic=True)[key]
|
|
539
|
+
for key in right.schema.edge_identity
|
|
540
|
+
}
|
|
541
|
+
if left_identity != right_identity:
|
|
542
|
+
raise DPOError(
|
|
543
|
+
DPOIssue(
|
|
544
|
+
DPOIssueCode.INTERFACE_LABEL,
|
|
545
|
+
"Preserved edges disagree on identity labels.",
|
|
546
|
+
)
|
|
547
|
+
)
|
|
548
|
+
left_values = left.edge_labels(edge, semantic=True)
|
|
549
|
+
right_values = right.edge_labels(right_edge, semantic=True)
|
|
550
|
+
common = {
|
|
551
|
+
key: value
|
|
552
|
+
for key, value in left_values.items()
|
|
553
|
+
if key in right_values and right_values[key] == value
|
|
554
|
+
}
|
|
555
|
+
interface_graph.add_edge(
|
|
556
|
+
left_to_interface[left_a], left_to_interface[left_b], **common
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
interface = LewisLabelledGraph.from_networkx(
|
|
560
|
+
interface_graph, interface_schema, name=f"{name}:K", partial=True
|
|
561
|
+
)
|
|
562
|
+
left_map = {
|
|
563
|
+
interface_node: left_node
|
|
564
|
+
for interface_node, (left_node, _) in enumerate(pairs)
|
|
565
|
+
}
|
|
566
|
+
right_map = {
|
|
567
|
+
interface_node: right_node
|
|
568
|
+
for interface_node, (_, right_node) in enumerate(pairs)
|
|
569
|
+
}
|
|
570
|
+
return cls(
|
|
571
|
+
left,
|
|
572
|
+
interface,
|
|
573
|
+
right,
|
|
574
|
+
RuleEmbedding(interface, left, left_map),
|
|
575
|
+
RuleEmbedding(interface, right, right_map),
|
|
576
|
+
SystemBoundary(boundary),
|
|
577
|
+
environment,
|
|
578
|
+
name,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def resource_delta(self) -> ResourceDelta | None:
|
|
583
|
+
if self.boundary is SystemBoundary.ABSTRACT:
|
|
584
|
+
return None
|
|
585
|
+
return _resource_delta(self.left, self.right)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@dataclass(frozen=True)
|
|
589
|
+
class DPOReplay:
|
|
590
|
+
valid: bool
|
|
591
|
+
issues: tuple[DPOIssue, ...] = ()
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
@dataclass(frozen=True)
|
|
595
|
+
class DPOCertificate:
|
|
596
|
+
"""Complete carrier-level certificate for the two DPO squares."""
|
|
597
|
+
|
|
598
|
+
rule: RuleSpan
|
|
599
|
+
host: LewisLabelledGraph
|
|
600
|
+
context: LewisLabelledGraph
|
|
601
|
+
result: LewisLabelledGraph
|
|
602
|
+
match: tuple[tuple[Hashable, Hashable], ...]
|
|
603
|
+
interface_to_context: tuple[tuple[Hashable, Hashable], ...]
|
|
604
|
+
context_to_host: tuple[tuple[Hashable, Hashable], ...]
|
|
605
|
+
context_to_result: tuple[tuple[Hashable, Hashable], ...]
|
|
606
|
+
right_to_result: tuple[tuple[Hashable, Hashable], ...]
|
|
607
|
+
deleted_nodes: tuple[Hashable, ...]
|
|
608
|
+
deleted_edges: tuple[frozenset[Hashable], ...]
|
|
609
|
+
added_nodes: tuple[Hashable, ...]
|
|
610
|
+
added_edges: tuple[frozenset[Hashable], ...]
|
|
611
|
+
resource_delta: ResourceDelta | None
|
|
612
|
+
|
|
613
|
+
def replay(self) -> DPOReplay:
|
|
614
|
+
from .validation import validate_certificate
|
|
615
|
+
|
|
616
|
+
issues = validate_certificate(self)
|
|
617
|
+
return DPOReplay(not issues, tuple(issues))
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@dataclass(frozen=True)
|
|
621
|
+
class DPOApplication:
|
|
622
|
+
result: LewisLabelledGraph
|
|
623
|
+
certificate: DPOCertificate
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _translate_match_error(error: LLGError) -> DPOError:
|
|
627
|
+
translated = []
|
|
628
|
+
for issue in error.issues:
|
|
629
|
+
if issue.code is LLGIssueCode.PARTIAL_MAPPING:
|
|
630
|
+
code = DPOIssueCode.MATCH_PARTIAL
|
|
631
|
+
elif issue.code is LLGIssueCode.NON_INJECTIVE:
|
|
632
|
+
code = DPOIssueCode.IDENTIFICATION
|
|
633
|
+
elif issue.code in {LLGIssueCode.OUTSIDE_SOURCE, LLGIssueCode.OUTSIDE_TARGET}:
|
|
634
|
+
code = DPOIssueCode.MATCH_OUTSIDE_HOST
|
|
635
|
+
elif issue.code is LLGIssueCode.MISSING_EDGE:
|
|
636
|
+
code = DPOIssueCode.MATCH_INCIDENCE
|
|
637
|
+
else:
|
|
638
|
+
code = DPOIssueCode.MATCH_LABEL
|
|
639
|
+
translated.append(DPOIssue(code, issue.message, issue.context))
|
|
640
|
+
return DPOError(*translated)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _map_edge(
|
|
644
|
+
mapping: Mapping[Hashable, Hashable],
|
|
645
|
+
edge: frozenset[Hashable] | None,
|
|
646
|
+
) -> frozenset[Hashable] | None:
|
|
647
|
+
if edge is None or any(node not in mapping for node in edge):
|
|
648
|
+
return None
|
|
649
|
+
return frozenset(mapping[node] for node in edge)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _fresh_nodes(existing: set[Hashable], count: int) -> tuple[tuple[str, int], ...]:
|
|
653
|
+
fresh = []
|
|
654
|
+
index = 0
|
|
655
|
+
while len(fresh) < count:
|
|
656
|
+
candidate = ("__dpo_fresh__", index)
|
|
657
|
+
index += 1
|
|
658
|
+
if candidate not in existing:
|
|
659
|
+
fresh.append(candidate)
|
|
660
|
+
existing.add(candidate)
|
|
661
|
+
return tuple(fresh)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
@dataclass(frozen=True)
|
|
665
|
+
class _DeletionPlan:
|
|
666
|
+
nodes: frozenset[Hashable]
|
|
667
|
+
edges: frozenset[frozenset[Hashable]]
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
@dataclass
|
|
671
|
+
class _ResultPlan:
|
|
672
|
+
graph: nx.Graph
|
|
673
|
+
interface_to_context: dict[Hashable, Hashable]
|
|
674
|
+
right_to_result: dict[Hashable, Hashable]
|
|
675
|
+
added_nodes: tuple[Hashable, ...]
|
|
676
|
+
added_edges: set[frozenset[Hashable]]
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _validated_match(
|
|
680
|
+
rule: RuleSpan,
|
|
681
|
+
host: LewisLabelledGraph,
|
|
682
|
+
match: Mapping[Hashable, Hashable],
|
|
683
|
+
) -> LLGMorphism:
|
|
684
|
+
if rule.left.schema != host.schema:
|
|
685
|
+
raise DPOError(
|
|
686
|
+
DPOIssue(
|
|
687
|
+
DPOIssueCode.SCHEMA_MISMATCH,
|
|
688
|
+
"The rule left side and host must use the same schema.",
|
|
689
|
+
)
|
|
690
|
+
)
|
|
691
|
+
try:
|
|
692
|
+
return LLGMorphism(rule.left, host, tuple(match.items()))
|
|
693
|
+
except LLGError as error:
|
|
694
|
+
raise _translate_match_error(error) from error
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _deletion_plan(
|
|
698
|
+
rule: RuleSpan,
|
|
699
|
+
host: LewisLabelledGraph,
|
|
700
|
+
match: Mapping[Hashable, Hashable],
|
|
701
|
+
) -> _DeletionPlan:
|
|
702
|
+
preserved_nodes = set(rule.left_arm.mapping.values())
|
|
703
|
+
deleted_nodes = frozenset(
|
|
704
|
+
match[node] for node in set(rule.left.node_ids) - preserved_nodes
|
|
705
|
+
)
|
|
706
|
+
preserved_edges = set(rule.left_arm.edge_mapping.values())
|
|
707
|
+
deleted_edges = frozenset(
|
|
708
|
+
frozenset(match[node] for node in edge)
|
|
709
|
+
for edge in set(rule.left.edge_keys) - preserved_edges
|
|
710
|
+
)
|
|
711
|
+
matched_edges = {
|
|
712
|
+
frozenset(match[node] for node in edge) for edge in rule.left.edge_keys
|
|
713
|
+
}
|
|
714
|
+
dangling = {
|
|
715
|
+
edge
|
|
716
|
+
for edge in host.edge_keys
|
|
717
|
+
if edge & deleted_nodes and edge not in matched_edges
|
|
718
|
+
}
|
|
719
|
+
if dangling:
|
|
720
|
+
raise DPOError(
|
|
721
|
+
DPOIssue(
|
|
722
|
+
DPOIssueCode.DANGLING,
|
|
723
|
+
"Deleting a matched node would leave an incident host edge dangling.",
|
|
724
|
+
{
|
|
725
|
+
"edges": tuple(
|
|
726
|
+
sorted(
|
|
727
|
+
(tuple(sorted(map(repr, edge))) for edge in dangling),
|
|
728
|
+
key=repr,
|
|
729
|
+
)
|
|
730
|
+
)
|
|
731
|
+
},
|
|
732
|
+
)
|
|
733
|
+
)
|
|
734
|
+
return _DeletionPlan(deleted_nodes, deleted_edges)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _construct_context(
|
|
738
|
+
rule: RuleSpan,
|
|
739
|
+
host: LewisLabelledGraph,
|
|
740
|
+
deletion: _DeletionPlan,
|
|
741
|
+
match: Mapping[Hashable, Hashable],
|
|
742
|
+
) -> LewisLabelledGraph:
|
|
743
|
+
graph = host.to_networkx()
|
|
744
|
+
graph.remove_edges_from(tuple(tuple(edge) for edge in deletion.edges))
|
|
745
|
+
graph.remove_nodes_from(deletion.nodes)
|
|
746
|
+
# Naturality requires erasing precisely the coordinates undefined in K.
|
|
747
|
+
for node in rule.interface.node_ids:
|
|
748
|
+
image = match[rule.left_arm.mapping[node]]
|
|
749
|
+
labels = rule.interface.node_labels(node, semantic=True)
|
|
750
|
+
for key in host.schema.semantic_node_keys:
|
|
751
|
+
if key not in labels:
|
|
752
|
+
graph.nodes[image].pop(key, None)
|
|
753
|
+
for edge in rule.interface.edge_keys:
|
|
754
|
+
image = _map_edge(match, rule.left_arm.edge_mapping[edge])
|
|
755
|
+
left, right = tuple(image)
|
|
756
|
+
labels = rule.interface.edge_labels(edge, semantic=True)
|
|
757
|
+
for key in host.schema.semantic_edge_keys:
|
|
758
|
+
if key not in labels:
|
|
759
|
+
graph.edges[left, right].pop(key, None)
|
|
760
|
+
# Derived chemistry is meaningful on complete host states, not on D.
|
|
761
|
+
for _, labels in graph.nodes(data=True):
|
|
762
|
+
for key in host.schema.node_derived:
|
|
763
|
+
labels.pop(key, None)
|
|
764
|
+
for _, _, labels in graph.edges(data=True):
|
|
765
|
+
for key in host.schema.edge_derived:
|
|
766
|
+
labels.pop(key, None)
|
|
767
|
+
return LewisLabelledGraph.from_networkx(
|
|
768
|
+
graph, host.schema, name=f"{rule.name}:D", partial=True
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def _initialize_result(
|
|
773
|
+
rule: RuleSpan,
|
|
774
|
+
context: LewisLabelledGraph,
|
|
775
|
+
match: Mapping[Hashable, Hashable],
|
|
776
|
+
) -> _ResultPlan:
|
|
777
|
+
graph = context.to_networkx()
|
|
778
|
+
left_arm = rule.left_arm.mapping
|
|
779
|
+
right_arm = rule.right_arm.mapping
|
|
780
|
+
interface_to_context: dict[Hashable, Hashable] = {}
|
|
781
|
+
right_to_result: dict[Hashable, Hashable] = {}
|
|
782
|
+
for interface_node in rule.interface.node_ids:
|
|
783
|
+
host_node = match[left_arm[interface_node]]
|
|
784
|
+
interface_to_context[interface_node] = host_node
|
|
785
|
+
right_to_result[right_arm[interface_node]] = host_node
|
|
786
|
+
|
|
787
|
+
added_right = sorted(set(rule.right.node_ids) - set(right_arm.values()), key=repr)
|
|
788
|
+
fresh = _fresh_nodes(set(graph.nodes), len(added_right))
|
|
789
|
+
for right_node, result_node in zip(added_right, fresh):
|
|
790
|
+
right_to_result[right_node] = result_node
|
|
791
|
+
graph.add_node(result_node, **rule.right.node_labels(right_node))
|
|
792
|
+
return _ResultPlan(
|
|
793
|
+
graph,
|
|
794
|
+
interface_to_context,
|
|
795
|
+
right_to_result,
|
|
796
|
+
tuple(fresh),
|
|
797
|
+
set(),
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def _update_preserved_values(rule: RuleSpan, plan: _ResultPlan) -> None:
|
|
802
|
+
right_arm = rule.right_arm.mapping
|
|
803
|
+
node_keys = rule.right.schema.semantic_node_keys + rule.right.schema.node_derived
|
|
804
|
+
for interface_node in rule.interface.node_ids:
|
|
805
|
+
right_node = right_arm[interface_node]
|
|
806
|
+
result_node = plan.right_to_result[right_node]
|
|
807
|
+
right_labels = rule.right.node_labels(right_node)
|
|
808
|
+
for key in node_keys:
|
|
809
|
+
if key in right_labels:
|
|
810
|
+
plan.graph.nodes[result_node][key] = right_labels[key]
|
|
811
|
+
|
|
812
|
+
edge_keys = rule.right.schema.semantic_edge_keys + rule.right.schema.edge_derived
|
|
813
|
+
right_edges = rule.right_arm.edge_mapping
|
|
814
|
+
for interface_edge in rule.interface.edge_keys:
|
|
815
|
+
right_edge = right_edges[interface_edge]
|
|
816
|
+
result_edge = frozenset(plan.right_to_result[node] for node in right_edge)
|
|
817
|
+
right_labels = rule.right.edge_labels(right_edge)
|
|
818
|
+
left, right = tuple(result_edge)
|
|
819
|
+
for key in edge_keys:
|
|
820
|
+
if key in right_labels:
|
|
821
|
+
plan.graph.edges[left, right][key] = right_labels[key]
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _add_right_edges(rule: RuleSpan, plan: _ResultPlan) -> None:
|
|
825
|
+
preserved = set(rule.right_arm.edge_mapping.values())
|
|
826
|
+
for right_edge in set(rule.right.edge_keys) - preserved:
|
|
827
|
+
result_edge = frozenset(plan.right_to_result[node] for node in right_edge)
|
|
828
|
+
left, right = tuple(result_edge)
|
|
829
|
+
if plan.graph.has_edge(left, right):
|
|
830
|
+
raise DPOError(
|
|
831
|
+
DPOIssue(
|
|
832
|
+
DPOIssueCode.RESULT_LABEL,
|
|
833
|
+
"An added rule edge collides with an unmatched host edge.",
|
|
834
|
+
{"edge": tuple(sorted(map(repr, result_edge)))},
|
|
835
|
+
)
|
|
836
|
+
)
|
|
837
|
+
plan.graph.add_edge(left, right, **rule.right.edge_labels(right_edge))
|
|
838
|
+
plan.added_edges.add(result_edge)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _coherent_result(
|
|
842
|
+
rule: RuleSpan,
|
|
843
|
+
host: LewisLabelledGraph,
|
|
844
|
+
plan: _ResultPlan,
|
|
845
|
+
) -> LewisLabelledGraph:
|
|
846
|
+
graph = plan.graph
|
|
847
|
+
if is_electron_schema(host.schema):
|
|
848
|
+
for _, attrs in graph.nodes(data=True):
|
|
849
|
+
for key in host.schema.node_derived:
|
|
850
|
+
attrs.pop(key, None)
|
|
851
|
+
for _, _, attrs in graph.edges(data=True):
|
|
852
|
+
for key in host.schema.edge_derived:
|
|
853
|
+
attrs.pop(key, None)
|
|
854
|
+
try:
|
|
855
|
+
graph = derive_electron_labeled_graph(graph)
|
|
856
|
+
except LLGError as error:
|
|
857
|
+
raise DPOError(
|
|
858
|
+
DPOIssue(
|
|
859
|
+
DPOIssueCode.RESULT_LABEL,
|
|
860
|
+
"The rewritten electron state is not coherent.",
|
|
861
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
862
|
+
)
|
|
863
|
+
) from error
|
|
864
|
+
try:
|
|
865
|
+
return LewisLabelledGraph.from_networkx(
|
|
866
|
+
graph, host.schema, name=f"{rule.name}:H"
|
|
867
|
+
)
|
|
868
|
+
except LLGError as error:
|
|
869
|
+
raise DPOError(
|
|
870
|
+
DPOIssue(
|
|
871
|
+
DPOIssueCode.RESULT_LABEL,
|
|
872
|
+
"The rewritten graph is outside the LLG object class.",
|
|
873
|
+
{"issues": tuple(issue.to_dict() for issue in error.issues)},
|
|
874
|
+
)
|
|
875
|
+
) from error
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def _assert_unmutated(
|
|
879
|
+
host: LewisLabelledGraph,
|
|
880
|
+
rule: RuleSpan,
|
|
881
|
+
before: tuple[nx.Graph, nx.Graph, nx.Graph],
|
|
882
|
+
) -> None:
|
|
883
|
+
observed = (host.to_networkx(), rule.left.to_networkx(), rule.right.to_networkx())
|
|
884
|
+
if any(
|
|
885
|
+
not nx.utils.graphs_equal(current, original)
|
|
886
|
+
for current, original in zip(observed, before)
|
|
887
|
+
):
|
|
888
|
+
raise DPOError(
|
|
889
|
+
DPOIssue(
|
|
890
|
+
DPOIssueCode.SOURCE_MUTATED,
|
|
891
|
+
"DPO application mutated a source object.",
|
|
892
|
+
)
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def apply_dpo(
|
|
897
|
+
rule: RuleSpan,
|
|
898
|
+
host: LewisLabelledGraph,
|
|
899
|
+
match: Mapping[Hashable, Hashable],
|
|
900
|
+
) -> DPOApplication:
|
|
901
|
+
"""Apply a rule at one explicit match or raise a typed DPO refusal."""
|
|
902
|
+
match_morphism = _validated_match(rule, host, match)
|
|
903
|
+
before = (host.to_networkx(), rule.left.to_networkx(), rule.right.to_networkx())
|
|
904
|
+
match_map = match_morphism.mapping
|
|
905
|
+
if rule.condition is not None:
|
|
906
|
+
rule.condition.validate(host, match_map)
|
|
907
|
+
deletion = _deletion_plan(rule, host, match_map)
|
|
908
|
+
context = _construct_context(rule, host, deletion, match_map)
|
|
909
|
+
plan = _initialize_result(rule, context, match_map)
|
|
910
|
+
_update_preserved_values(rule, plan)
|
|
911
|
+
_add_right_edges(rule, plan)
|
|
912
|
+
result = _coherent_result(rule, host, plan)
|
|
913
|
+
_assert_unmutated(host, rule, before)
|
|
914
|
+
|
|
915
|
+
context_identity = tuple((node, node) for node in context.node_ids)
|
|
916
|
+
certificate = DPOCertificate(
|
|
917
|
+
rule=rule,
|
|
918
|
+
host=host,
|
|
919
|
+
context=context,
|
|
920
|
+
result=result,
|
|
921
|
+
match=tuple(sorted(match_map.items(), key=repr)),
|
|
922
|
+
interface_to_context=tuple(sorted(plan.interface_to_context.items(), key=repr)),
|
|
923
|
+
context_to_host=context_identity,
|
|
924
|
+
context_to_result=context_identity,
|
|
925
|
+
right_to_result=tuple(sorted(plan.right_to_result.items(), key=repr)),
|
|
926
|
+
deleted_nodes=tuple(sorted(deletion.nodes, key=repr)),
|
|
927
|
+
deleted_edges=tuple(sorted(deletion.edges, key=repr)),
|
|
928
|
+
added_nodes=plan.added_nodes,
|
|
929
|
+
added_edges=tuple(sorted(plan.added_edges, key=repr)),
|
|
930
|
+
resource_delta=rule.resource_delta,
|
|
931
|
+
)
|
|
932
|
+
replay = certificate.replay()
|
|
933
|
+
if not replay.valid:
|
|
934
|
+
raise DPOError(*replay.issues)
|
|
935
|
+
return DPOApplication(result, certificate)
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def rule_from_its(
|
|
939
|
+
its: nx.Graph,
|
|
940
|
+
*,
|
|
941
|
+
electron_complete: bool = False,
|
|
942
|
+
boundary: SystemBoundary | str = SystemBoundary.ABSTRACT,
|
|
943
|
+
environment: EnvironmentToken | None = None,
|
|
944
|
+
name: str = "its-rule",
|
|
945
|
+
) -> RuleSpan:
|
|
946
|
+
"""Adapt an ITS through two endpoint LLGs and an explicit carrier map."""
|
|
947
|
+
left = llg_from_its(its, "reactant", electron_complete=electron_complete)
|
|
948
|
+
right = llg_from_its(its, "product", electron_complete=electron_complete)
|
|
949
|
+
shared = set(left.node_ids) & set(right.node_ids)
|
|
950
|
+
return RuleSpan.from_mapping(
|
|
951
|
+
left,
|
|
952
|
+
right,
|
|
953
|
+
{node: node for node in shared},
|
|
954
|
+
boundary=boundary,
|
|
955
|
+
environment=environment,
|
|
956
|
+
name=name,
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def rule_from_synrule(
|
|
961
|
+
rule: Any,
|
|
962
|
+
*,
|
|
963
|
+
electron_complete: bool = False,
|
|
964
|
+
boundary: SystemBoundary | str = SystemBoundary.ABSTRACT,
|
|
965
|
+
environment: EnvironmentToken | None = None,
|
|
966
|
+
) -> RuleSpan:
|
|
967
|
+
"""Adapt ``SynRule`` endpoints without treating paired ITS as a rule span."""
|
|
968
|
+
from synkit.Graph.ITS.its_construction import ITSConstruction
|
|
969
|
+
from synkit.Rule.syn_rule import SynRule
|
|
970
|
+
|
|
971
|
+
if not isinstance(rule, SynRule):
|
|
972
|
+
raise TypeError("rule_from_synrule expects a SynRule instance.")
|
|
973
|
+
its = ITSConstruction.construct(rule.left.raw, rule.right.raw)
|
|
974
|
+
return rule_from_its(
|
|
975
|
+
its,
|
|
976
|
+
electron_complete=electron_complete,
|
|
977
|
+
boundary=boundary,
|
|
978
|
+
environment=environment,
|
|
979
|
+
name=getattr(rule, "_name", "rule"),
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
__all__ = [
|
|
984
|
+
"DPOApplication",
|
|
985
|
+
"DPOCertificate",
|
|
986
|
+
"DPOError",
|
|
987
|
+
"DPOIssue",
|
|
988
|
+
"DPOIssueCode",
|
|
989
|
+
"DPOReplay",
|
|
990
|
+
"EnvironmentToken",
|
|
991
|
+
"ResourceDelta",
|
|
992
|
+
"RuleEmbedding",
|
|
993
|
+
"RuleSpan",
|
|
994
|
+
"SystemBoundary",
|
|
995
|
+
"apply_dpo",
|
|
996
|
+
"rule_from_its",
|
|
997
|
+
"rule_from_synrule",
|
|
998
|
+
]
|