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,771 @@
|
|
|
1
|
+
"""Bounded exhaustive overlap search and exact composite quotienting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import itertools
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any, Hashable, Iterable, Mapping
|
|
12
|
+
|
|
13
|
+
import networkx as nx
|
|
14
|
+
|
|
15
|
+
from synkit.Graph.Morphism import LewisLabelledGraph
|
|
16
|
+
from synkit.Rule.Apply import RuleSpan
|
|
17
|
+
|
|
18
|
+
from .composition import (
|
|
19
|
+
CompositionError,
|
|
20
|
+
CompositionIssue,
|
|
21
|
+
CompositionResult,
|
|
22
|
+
RuleOverlap,
|
|
23
|
+
compose_rules,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OverlapSearchIssueCode(str, Enum):
|
|
28
|
+
"""Stable failures for bounded overlap enumeration and quotienting."""
|
|
29
|
+
|
|
30
|
+
SCHEMA_MISMATCH = "OVERLAP_SEARCH_SCHEMA_MISMATCH"
|
|
31
|
+
STATE_LIMIT = "OVERLAP_SEARCH_STATE_LIMIT"
|
|
32
|
+
OVERLAP_LIMIT = "OVERLAP_SEARCH_OVERLAP_LIMIT"
|
|
33
|
+
NODE_LIMIT = "OVERLAP_SEARCH_NODE_LIMIT"
|
|
34
|
+
MATRIX_LIMIT = "OVERLAP_SEARCH_MATRIX_LIMIT"
|
|
35
|
+
CANONICAL_LIMIT = "OVERLAP_SEARCH_CANONICAL_LIMIT"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class OverlapSearchIssue:
|
|
40
|
+
code: OverlapSearchIssueCode
|
|
41
|
+
message: str
|
|
42
|
+
context: Mapping[str, Any] = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
def to_dict(self) -> dict[str, Any]:
|
|
45
|
+
return {
|
|
46
|
+
"code": self.code.value,
|
|
47
|
+
"message": self.message,
|
|
48
|
+
"context": dict(self.context),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OverlapSearchError(RuntimeError):
|
|
53
|
+
"""Raised instead of returning a silently truncated candidate family."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
issue: OverlapSearchIssue,
|
|
58
|
+
*,
|
|
59
|
+
partial_overlaps: Iterable[RuleOverlap] = (),
|
|
60
|
+
) -> None:
|
|
61
|
+
self.issue = issue
|
|
62
|
+
self.partial_overlaps = tuple(partial_overlaps)
|
|
63
|
+
super().__init__(issue.message)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class OverlapSearchLimits:
|
|
68
|
+
"""Explicit finite resource bounds for one search."""
|
|
69
|
+
|
|
70
|
+
max_states: int = 1_000_000
|
|
71
|
+
max_overlaps: int = 100_000
|
|
72
|
+
max_overlap_nodes: int | None = None
|
|
73
|
+
max_component_embeddings: int = 100_000
|
|
74
|
+
max_canonical_permutations: int = 1_000_000
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
positive = {
|
|
78
|
+
"max_states": self.max_states,
|
|
79
|
+
"max_overlaps": self.max_overlaps,
|
|
80
|
+
"max_component_embeddings": self.max_component_embeddings,
|
|
81
|
+
"max_canonical_permutations": self.max_canonical_permutations,
|
|
82
|
+
}
|
|
83
|
+
if any(value <= 0 for value in positive.values()):
|
|
84
|
+
raise ValueError("Search and canonicalization limits must be positive.")
|
|
85
|
+
if self.max_overlap_nodes is not None and self.max_overlap_nodes < 0:
|
|
86
|
+
raise ValueError("max_overlap_nodes cannot be negative.")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class CompositionWitness:
|
|
91
|
+
"""One material overlap and its accepted construction proof."""
|
|
92
|
+
|
|
93
|
+
overlap: RuleOverlap
|
|
94
|
+
overlap_digest: str
|
|
95
|
+
composition: CompositionResult
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class RejectedOverlap:
|
|
100
|
+
"""One enumerated overlap whose composition premise failed."""
|
|
101
|
+
|
|
102
|
+
overlap: RuleOverlap
|
|
103
|
+
overlap_digest: str
|
|
104
|
+
issues: tuple[CompositionIssue, ...]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class CompositionClass:
|
|
109
|
+
"""One exact labelled-rule isomorphism class retaining every witness."""
|
|
110
|
+
|
|
111
|
+
canonical_id: str
|
|
112
|
+
representative: RuleSpan
|
|
113
|
+
witnesses: tuple[CompositionWitness, ...]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class CompositionSearchResult:
|
|
118
|
+
"""A complete bounded search result; construction ambiguity is retained."""
|
|
119
|
+
|
|
120
|
+
overlaps: tuple[RuleOverlap, ...]
|
|
121
|
+
classes: tuple[CompositionClass, ...]
|
|
122
|
+
rejected: tuple[RejectedOverlap, ...]
|
|
123
|
+
match_matrix: "ExtendedMatchMatrix"
|
|
124
|
+
explored_states: int
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def raw_overlap_count(self) -> int:
|
|
128
|
+
return len(self.overlaps)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def accepted_count(self) -> int:
|
|
132
|
+
return sum(len(group.witnesses) for group in self.classes)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def exact_class_count(self) -> int:
|
|
136
|
+
return len(self.classes)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class ExtendedMatchMatrix:
|
|
141
|
+
"""Full-component embedding counts plus one explicit empty column."""
|
|
142
|
+
|
|
143
|
+
second_components: tuple[frozenset[Hashable], ...]
|
|
144
|
+
first_components: tuple[frozenset[Hashable], ...]
|
|
145
|
+
counts: tuple[tuple[int, ...], ...]
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def empty_column(self) -> int:
|
|
149
|
+
return len(self.first_components)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class RuleSpanIsomorphism:
|
|
154
|
+
"""Replayable layerwise isomorphism between two complete rule spans."""
|
|
155
|
+
|
|
156
|
+
source: RuleSpan
|
|
157
|
+
target: RuleSpan
|
|
158
|
+
left_map: tuple[tuple[Hashable, Hashable], ...]
|
|
159
|
+
interface_map: tuple[tuple[Hashable, Hashable], ...]
|
|
160
|
+
right_map: tuple[tuple[Hashable, Hashable], ...]
|
|
161
|
+
|
|
162
|
+
def replay(self) -> bool:
|
|
163
|
+
mapping = {
|
|
164
|
+
**{("L", left): ("L", right) for left, right in self.left_map},
|
|
165
|
+
**{("K", left): ("K", right) for left, right in self.interface_map},
|
|
166
|
+
**{("R", left): ("R", right) for left, right in self.right_map},
|
|
167
|
+
}
|
|
168
|
+
return _rule_metadata_key(self.source) == _rule_metadata_key(
|
|
169
|
+
self.target
|
|
170
|
+
) and _mapping_is_colored_isomorphism(
|
|
171
|
+
_rule_graph(self.source), _rule_graph(self.target), mapping
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _labels(graph: LewisLabelledGraph, node: Hashable) -> tuple[tuple[str, Any], ...]:
|
|
176
|
+
return tuple(sorted(graph.node_labels(node, semantic=True).items()))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _edge_labels(
|
|
180
|
+
graph: LewisLabelledGraph, edge: frozenset[Hashable]
|
|
181
|
+
) -> tuple[tuple[str, Any], ...]:
|
|
182
|
+
return tuple(sorted(graph.edge_labels(edge, semantic=True).items()))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _node_order_key(
|
|
186
|
+
graph: LewisLabelledGraph, node: Hashable
|
|
187
|
+
) -> tuple[str, int, tuple[str, ...], str]:
|
|
188
|
+
incident = tuple(
|
|
189
|
+
sorted(
|
|
190
|
+
repr(_edge_labels(graph, edge)) for edge in graph.edge_keys if node in edge
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
return repr(_labels(graph, node)), len(incident), incident, repr(node)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _component_order_key(
|
|
197
|
+
graph: LewisLabelledGraph, component: frozenset[Hashable]
|
|
198
|
+
) -> tuple[Any, ...]:
|
|
199
|
+
edges = tuple(edge for edge in graph.edge_keys if edge <= component)
|
|
200
|
+
return (
|
|
201
|
+
len(component),
|
|
202
|
+
len(edges),
|
|
203
|
+
tuple(sorted(repr(_labels(graph, node)) for node in component)),
|
|
204
|
+
tuple(sorted(repr(_edge_labels(graph, edge)) for edge in edges)),
|
|
205
|
+
tuple(sorted(map(repr, component))),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _component_embedding_count(
|
|
210
|
+
first: LewisLabelledGraph,
|
|
211
|
+
first_component: frozenset[Hashable],
|
|
212
|
+
second: LewisLabelledGraph,
|
|
213
|
+
second_component: frozenset[Hashable],
|
|
214
|
+
limit: int,
|
|
215
|
+
) -> int:
|
|
216
|
+
first_graph = first.to_networkx().subgraph(first_component).copy()
|
|
217
|
+
second_graph = second.to_networkx().subgraph(second_component).copy()
|
|
218
|
+
node_keys = first.schema.semantic_node_keys
|
|
219
|
+
edge_keys = first.schema.semantic_edge_keys
|
|
220
|
+
matcher = nx.algorithms.isomorphism.GraphMatcher(
|
|
221
|
+
first_graph,
|
|
222
|
+
second_graph,
|
|
223
|
+
node_match=lambda left, right: all(
|
|
224
|
+
left[key] == right[key] for key in node_keys
|
|
225
|
+
),
|
|
226
|
+
edge_match=lambda left, right: all(
|
|
227
|
+
left[key] == right[key] for key in edge_keys
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
count = 0
|
|
231
|
+
for _ in matcher.subgraph_monomorphisms_iter():
|
|
232
|
+
count += 1
|
|
233
|
+
if count > limit:
|
|
234
|
+
raise OverlapSearchError(
|
|
235
|
+
OverlapSearchIssue(
|
|
236
|
+
OverlapSearchIssueCode.MATRIX_LIMIT,
|
|
237
|
+
"Extended match-matrix construction exceeded its embedding bound.",
|
|
238
|
+
{"limit": limit},
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
return count
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def extended_component_match_matrix(
|
|
245
|
+
first_right: LewisLabelledGraph,
|
|
246
|
+
second_left: LewisLabelledGraph,
|
|
247
|
+
*,
|
|
248
|
+
max_embeddings: int = 100_000,
|
|
249
|
+
) -> ExtendedMatchMatrix:
|
|
250
|
+
"""Build the native extended match matrix used to order exhaustive search."""
|
|
251
|
+
if first_right.schema != second_left.schema:
|
|
252
|
+
raise OverlapSearchError(
|
|
253
|
+
OverlapSearchIssue(
|
|
254
|
+
OverlapSearchIssueCode.SCHEMA_MISMATCH,
|
|
255
|
+
"Match-matrix endpoints must use the same LLG schema.",
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
first_components = tuple(
|
|
259
|
+
sorted(
|
|
260
|
+
(
|
|
261
|
+
frozenset(part)
|
|
262
|
+
for part in nx.connected_components(first_right.to_networkx())
|
|
263
|
+
),
|
|
264
|
+
key=lambda part: _component_order_key(first_right, part),
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
second_components = tuple(
|
|
268
|
+
sorted(
|
|
269
|
+
(
|
|
270
|
+
frozenset(part)
|
|
271
|
+
for part in nx.connected_components(second_left.to_networkx())
|
|
272
|
+
),
|
|
273
|
+
key=lambda part: _component_order_key(second_left, part),
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
remaining = max_embeddings
|
|
277
|
+
rows = []
|
|
278
|
+
for second_component in second_components:
|
|
279
|
+
row = []
|
|
280
|
+
for first_component in first_components:
|
|
281
|
+
count = _component_embedding_count(
|
|
282
|
+
first_right,
|
|
283
|
+
first_component,
|
|
284
|
+
second_left,
|
|
285
|
+
second_component,
|
|
286
|
+
remaining,
|
|
287
|
+
)
|
|
288
|
+
remaining -= count
|
|
289
|
+
row.append(count)
|
|
290
|
+
rows.append(tuple(row) + (1,))
|
|
291
|
+
counts = tuple(rows)
|
|
292
|
+
return ExtendedMatchMatrix(second_components, first_components, counts)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _extension_is_compatible(
|
|
296
|
+
source: LewisLabelledGraph,
|
|
297
|
+
target: LewisLabelledGraph,
|
|
298
|
+
mapping: Mapping[Hashable, Hashable],
|
|
299
|
+
source_node: Hashable,
|
|
300
|
+
target_node: Hashable,
|
|
301
|
+
) -> bool:
|
|
302
|
+
for mapped_source, mapped_target in mapping.items():
|
|
303
|
+
target_edge = frozenset((target_node, mapped_target))
|
|
304
|
+
if target_edge not in target.edge_keys:
|
|
305
|
+
continue
|
|
306
|
+
source_edge = frozenset((source_node, mapped_source))
|
|
307
|
+
# An edge present only in L2 can be supplied by the first rule's frame.
|
|
308
|
+
# Only a shared pair with conflicting values prevents a common world.
|
|
309
|
+
# Dangling and deleted-frame requirements are checked by composition.
|
|
310
|
+
if source_edge in source.edge_keys and (
|
|
311
|
+
_edge_labels(source, source_edge) != _edge_labels(target, target_edge)
|
|
312
|
+
):
|
|
313
|
+
return False
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass
|
|
318
|
+
class _EnumerationState:
|
|
319
|
+
explored: int = 0
|
|
320
|
+
overlaps: list[RuleOverlap] = field(default_factory=list)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def enumerate_overlaps(
|
|
324
|
+
first_right: LewisLabelledGraph,
|
|
325
|
+
second_left: LewisLabelledGraph,
|
|
326
|
+
*,
|
|
327
|
+
limits: OverlapSearchLimits | None = None,
|
|
328
|
+
_match_matrix: ExtendedMatchMatrix | None = None,
|
|
329
|
+
) -> tuple[tuple[RuleOverlap, ...], int]:
|
|
330
|
+
"""Enumerate every admitted partial injective overlap within hard bounds."""
|
|
331
|
+
active = limits or OverlapSearchLimits()
|
|
332
|
+
if first_right.schema != second_left.schema:
|
|
333
|
+
raise OverlapSearchError(
|
|
334
|
+
OverlapSearchIssue(
|
|
335
|
+
OverlapSearchIssueCode.SCHEMA_MISMATCH,
|
|
336
|
+
"Overlap endpoints must use the same LLG schema.",
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
maximum = min(len(first_right.node_ids), len(second_left.node_ids))
|
|
340
|
+
if active.max_overlap_nodes is not None and active.max_overlap_nodes < maximum:
|
|
341
|
+
raise OverlapSearchError(
|
|
342
|
+
OverlapSearchIssue(
|
|
343
|
+
OverlapSearchIssueCode.NODE_LIMIT,
|
|
344
|
+
"The node bound excludes possible overlap cardinalities.",
|
|
345
|
+
{"bound": active.max_overlap_nodes, "unbounded_maximum": maximum},
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
max_nodes = maximum
|
|
349
|
+
matrix = _match_matrix or extended_component_match_matrix(
|
|
350
|
+
first_right,
|
|
351
|
+
second_left,
|
|
352
|
+
max_embeddings=active.max_component_embeddings,
|
|
353
|
+
)
|
|
354
|
+
first_component = {
|
|
355
|
+
node: index
|
|
356
|
+
for index, component in enumerate(matrix.first_components)
|
|
357
|
+
for node in component
|
|
358
|
+
}
|
|
359
|
+
second_component = {
|
|
360
|
+
node: index
|
|
361
|
+
for index, component in enumerate(matrix.second_components)
|
|
362
|
+
for node in component
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
def target_key(source: Hashable, target: Hashable) -> tuple[Any, ...]:
|
|
366
|
+
count = matrix.counts[second_component[target]][first_component[source]]
|
|
367
|
+
return -count, _node_order_key(second_left, target)
|
|
368
|
+
|
|
369
|
+
compatible_targets = {
|
|
370
|
+
node: tuple(
|
|
371
|
+
sorted(
|
|
372
|
+
(
|
|
373
|
+
target
|
|
374
|
+
for target in second_left.node_ids
|
|
375
|
+
if _labels(first_right, node) == _labels(second_left, target)
|
|
376
|
+
),
|
|
377
|
+
key=lambda target: target_key(node, target),
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
for node in first_right.node_ids
|
|
381
|
+
}
|
|
382
|
+
source_nodes = sorted(
|
|
383
|
+
first_right.node_ids,
|
|
384
|
+
key=lambda node: (
|
|
385
|
+
len(compatible_targets[node]),
|
|
386
|
+
_node_order_key(first_right, node),
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
state = _EnumerationState()
|
|
390
|
+
|
|
391
|
+
def visit(
|
|
392
|
+
index: int,
|
|
393
|
+
mapping: dict[Hashable, Hashable],
|
|
394
|
+
used_targets: set[Hashable],
|
|
395
|
+
) -> None:
|
|
396
|
+
state.explored += 1
|
|
397
|
+
if state.explored > active.max_states:
|
|
398
|
+
raise OverlapSearchError(
|
|
399
|
+
OverlapSearchIssue(
|
|
400
|
+
OverlapSearchIssueCode.STATE_LIMIT,
|
|
401
|
+
"Overlap enumeration exceeded max_states.",
|
|
402
|
+
{"limit": active.max_states},
|
|
403
|
+
),
|
|
404
|
+
partial_overlaps=state.overlaps,
|
|
405
|
+
)
|
|
406
|
+
if index == len(source_nodes):
|
|
407
|
+
if len(state.overlaps) >= active.max_overlaps:
|
|
408
|
+
raise OverlapSearchError(
|
|
409
|
+
OverlapSearchIssue(
|
|
410
|
+
OverlapSearchIssueCode.OVERLAP_LIMIT,
|
|
411
|
+
"Overlap enumeration exceeded max_overlaps.",
|
|
412
|
+
{"limit": active.max_overlaps},
|
|
413
|
+
),
|
|
414
|
+
partial_overlaps=state.overlaps,
|
|
415
|
+
)
|
|
416
|
+
state.overlaps.append(
|
|
417
|
+
RuleOverlap.from_mapping(first_right, second_left, mapping)
|
|
418
|
+
)
|
|
419
|
+
return
|
|
420
|
+
source_node = source_nodes[index]
|
|
421
|
+
visit(index + 1, mapping, used_targets)
|
|
422
|
+
if len(mapping) >= max_nodes:
|
|
423
|
+
return
|
|
424
|
+
for target_node in compatible_targets[source_node]:
|
|
425
|
+
if target_node in used_targets or not _extension_is_compatible(
|
|
426
|
+
first_right,
|
|
427
|
+
second_left,
|
|
428
|
+
mapping,
|
|
429
|
+
source_node,
|
|
430
|
+
target_node,
|
|
431
|
+
):
|
|
432
|
+
continue
|
|
433
|
+
mapping[source_node] = target_node
|
|
434
|
+
used_targets.add(target_node)
|
|
435
|
+
visit(index + 1, mapping, used_targets)
|
|
436
|
+
used_targets.remove(target_node)
|
|
437
|
+
del mapping[source_node]
|
|
438
|
+
|
|
439
|
+
visit(0, {}, set())
|
|
440
|
+
return tuple(state.overlaps), state.explored
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _add_layer(
|
|
444
|
+
target: nx.Graph,
|
|
445
|
+
layer: str,
|
|
446
|
+
graph: LewisLabelledGraph,
|
|
447
|
+
) -> None:
|
|
448
|
+
for node in graph.node_ids:
|
|
449
|
+
target.add_node((layer, node), color=repr((layer, _labels(graph, node))))
|
|
450
|
+
for edge in graph.edge_keys:
|
|
451
|
+
left, right = tuple(edge)
|
|
452
|
+
target.add_edge(
|
|
453
|
+
(layer, left),
|
|
454
|
+
(layer, right),
|
|
455
|
+
color=repr(("incidence", _edge_labels(graph, edge))),
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _rule_graph(rule: RuleSpan) -> nx.Graph:
|
|
460
|
+
graph = nx.Graph()
|
|
461
|
+
_add_layer(graph, "L", rule.left)
|
|
462
|
+
_add_layer(graph, "K", rule.interface)
|
|
463
|
+
_add_layer(graph, "R", rule.right)
|
|
464
|
+
for node, image in rule.left_arm.mapping.items():
|
|
465
|
+
graph.add_edge(("K", node), ("L", image), color="arm:left")
|
|
466
|
+
for node, image in rule.right_arm.mapping.items():
|
|
467
|
+
graph.add_edge(("K", node), ("R", image), color="arm:right")
|
|
468
|
+
return graph
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _overlap_graph(overlap: RuleOverlap) -> nx.Graph:
|
|
472
|
+
graph = nx.Graph()
|
|
473
|
+
_add_layer(graph, "R1", overlap.first_arm.target)
|
|
474
|
+
_add_layer(graph, "D", overlap.interface)
|
|
475
|
+
_add_layer(graph, "L2", overlap.second_arm.target)
|
|
476
|
+
for node, image in overlap.first_arm.mapping.items():
|
|
477
|
+
graph.add_edge(("D", node), ("R1", image), color="arm:first")
|
|
478
|
+
for node, image in overlap.second_arm.mapping.items():
|
|
479
|
+
graph.add_edge(("D", node), ("L2", image), color="arm:second")
|
|
480
|
+
return graph
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _boundary_key(rule: RuleSpan) -> tuple[Any, ...]:
|
|
484
|
+
environment = rule.environment
|
|
485
|
+
return (
|
|
486
|
+
rule.boundary.value,
|
|
487
|
+
(
|
|
488
|
+
None
|
|
489
|
+
if environment is None
|
|
490
|
+
else (
|
|
491
|
+
environment.name,
|
|
492
|
+
environment.element_delta,
|
|
493
|
+
environment.electron_delta,
|
|
494
|
+
)
|
|
495
|
+
),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _rule_metadata_key(rule: RuleSpan) -> tuple[Any, ...]:
|
|
500
|
+
return repr(rule.left.schema), _boundary_key(rule)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _mapping_is_colored_isomorphism(
|
|
504
|
+
left: nx.Graph,
|
|
505
|
+
right: nx.Graph,
|
|
506
|
+
mapping: Mapping[Hashable, Hashable],
|
|
507
|
+
) -> bool:
|
|
508
|
+
if set(mapping) != set(left) or set(mapping.values()) != set(right):
|
|
509
|
+
return False
|
|
510
|
+
if len(set(mapping.values())) != len(mapping):
|
|
511
|
+
return False
|
|
512
|
+
for node, image in mapping.items():
|
|
513
|
+
if left.nodes[node].get("color") != right.nodes[image].get("color"):
|
|
514
|
+
return False
|
|
515
|
+
if left.number_of_edges() != right.number_of_edges():
|
|
516
|
+
return False
|
|
517
|
+
for source, target, attrs in left.edges(data=True):
|
|
518
|
+
image = (mapping[source], mapping[target])
|
|
519
|
+
if not right.has_edge(*image):
|
|
520
|
+
return False
|
|
521
|
+
if attrs.get("color") != right.edges[image].get("color"):
|
|
522
|
+
return False
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def find_rule_span_isomorphism(
|
|
527
|
+
left: RuleSpan, right: RuleSpan
|
|
528
|
+
) -> RuleSpanIsomorphism | None:
|
|
529
|
+
"""Return an exact carrier-span witness, or ``None`` when none exists.
|
|
530
|
+
|
|
531
|
+
Sequential application conditions are retained on each rule but are not
|
|
532
|
+
compared here. A span quotient does not prove guarded-rule equivalence.
|
|
533
|
+
"""
|
|
534
|
+
if _rule_metadata_key(left) != _rule_metadata_key(right):
|
|
535
|
+
return None
|
|
536
|
+
layers = (
|
|
537
|
+
(left.left, right.left),
|
|
538
|
+
(left.interface, right.interface),
|
|
539
|
+
(left.right, right.right),
|
|
540
|
+
)
|
|
541
|
+
if all(source.node_ids == target.node_ids for source, target in layers):
|
|
542
|
+
identity = RuleSpanIsomorphism(
|
|
543
|
+
left,
|
|
544
|
+
right,
|
|
545
|
+
*(
|
|
546
|
+
tuple((node, node) for node in sorted(source.node_ids, key=repr))
|
|
547
|
+
for source, _ in layers
|
|
548
|
+
),
|
|
549
|
+
)
|
|
550
|
+
if identity.replay():
|
|
551
|
+
return identity
|
|
552
|
+
matcher = nx.algorithms.isomorphism.GraphMatcher(
|
|
553
|
+
_rule_graph(left),
|
|
554
|
+
_rule_graph(right),
|
|
555
|
+
node_match=nx.algorithms.isomorphism.categorical_node_match("color", None),
|
|
556
|
+
edge_match=nx.algorithms.isomorphism.categorical_edge_match("color", None),
|
|
557
|
+
)
|
|
558
|
+
if not matcher.is_isomorphic():
|
|
559
|
+
return None
|
|
560
|
+
mapping = matcher.mapping
|
|
561
|
+
witness = RuleSpanIsomorphism(
|
|
562
|
+
left,
|
|
563
|
+
right,
|
|
564
|
+
tuple(
|
|
565
|
+
sorted(
|
|
566
|
+
(
|
|
567
|
+
(source[1], target[1])
|
|
568
|
+
for source, target in mapping.items()
|
|
569
|
+
if source[0] == "L"
|
|
570
|
+
),
|
|
571
|
+
key=repr,
|
|
572
|
+
)
|
|
573
|
+
),
|
|
574
|
+
tuple(
|
|
575
|
+
sorted(
|
|
576
|
+
(
|
|
577
|
+
(source[1], target[1])
|
|
578
|
+
for source, target in mapping.items()
|
|
579
|
+
if source[0] == "K"
|
|
580
|
+
),
|
|
581
|
+
key=repr,
|
|
582
|
+
)
|
|
583
|
+
),
|
|
584
|
+
tuple(
|
|
585
|
+
sorted(
|
|
586
|
+
(
|
|
587
|
+
(source[1], target[1])
|
|
588
|
+
for source, target in mapping.items()
|
|
589
|
+
if source[0] == "R"
|
|
590
|
+
),
|
|
591
|
+
key=repr,
|
|
592
|
+
)
|
|
593
|
+
),
|
|
594
|
+
)
|
|
595
|
+
return witness if witness.replay() else None
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def rule_spans_isomorphic(left: RuleSpan, right: RuleSpan) -> bool:
|
|
599
|
+
"""Decide exact rule-span isomorphism, including arms and resource policy."""
|
|
600
|
+
return find_rule_span_isomorphism(left, right) is not None
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _canonical_code(graph: nx.Graph, permutation_limit: int) -> str:
|
|
604
|
+
partitions: dict[str, list[Hashable]] = {}
|
|
605
|
+
for node, attrs in graph.nodes(data=True):
|
|
606
|
+
partitions.setdefault(str(attrs["color"]), []).append(node)
|
|
607
|
+
colors = sorted(partitions)
|
|
608
|
+
permutations = math.prod(math.factorial(len(partitions[color])) for color in colors)
|
|
609
|
+
if permutations > permutation_limit:
|
|
610
|
+
raise OverlapSearchError(
|
|
611
|
+
OverlapSearchIssue(
|
|
612
|
+
OverlapSearchIssueCode.CANONICAL_LIMIT,
|
|
613
|
+
"Exact canonicalization exceeded its permutation bound.",
|
|
614
|
+
{"required": permutations, "limit": permutation_limit},
|
|
615
|
+
)
|
|
616
|
+
)
|
|
617
|
+
best: str | None = None
|
|
618
|
+
choices = [tuple(itertools.permutations(partitions[color])) for color in colors]
|
|
619
|
+
color_sequence = tuple(
|
|
620
|
+
color for color in colors for _ in range(len(partitions[color]))
|
|
621
|
+
)
|
|
622
|
+
for selection in itertools.product(*choices):
|
|
623
|
+
order = tuple(itertools.chain.from_iterable(selection))
|
|
624
|
+
edges = []
|
|
625
|
+
for index, left in enumerate(order):
|
|
626
|
+
for right in order[index + 1 :]:
|
|
627
|
+
edges.append(
|
|
628
|
+
graph.edges[left, right]["color"]
|
|
629
|
+
if graph.has_edge(left, right)
|
|
630
|
+
else None
|
|
631
|
+
)
|
|
632
|
+
code = json.dumps(
|
|
633
|
+
(color_sequence, edges), separators=(",", ":"), sort_keys=True
|
|
634
|
+
)
|
|
635
|
+
if best is None or code < best:
|
|
636
|
+
best = code
|
|
637
|
+
return best or json.dumps((color_sequence, ()), separators=(",", ":"))
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def canonical_rule_identity(rule: RuleSpan, *, permutation_limit: int) -> str:
|
|
641
|
+
"""Return an exact, carrier-map-invariant digest for a rule span."""
|
|
642
|
+
payload = json.dumps(
|
|
643
|
+
(
|
|
644
|
+
_rule_metadata_key(rule),
|
|
645
|
+
_canonical_code(_rule_graph(rule), permutation_limit),
|
|
646
|
+
),
|
|
647
|
+
separators=(",", ":"),
|
|
648
|
+
sort_keys=True,
|
|
649
|
+
)
|
|
650
|
+
return hashlib.sha256(payload.encode()).hexdigest()
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def canonical_overlap_digest(overlap: RuleOverlap, *, permutation_limit: int) -> str:
|
|
654
|
+
"""Return a carrier-map-invariant digest of both overlap arms."""
|
|
655
|
+
code = _canonical_code(_overlap_graph(overlap), permutation_limit)
|
|
656
|
+
payload = json.dumps(
|
|
657
|
+
(repr(overlap.first_arm.target.schema), code), separators=(",", ":")
|
|
658
|
+
)
|
|
659
|
+
return hashlib.sha256(payload.encode()).hexdigest()
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _rule_bucket(rule: RuleSpan) -> tuple[Any, ...]:
|
|
663
|
+
graph = _rule_graph(rule)
|
|
664
|
+
digest = nx.weisfeiler_lehman_graph_hash(
|
|
665
|
+
graph, node_attr="color", edge_attr="color"
|
|
666
|
+
)
|
|
667
|
+
return _rule_metadata_key(rule), len(graph), graph.number_of_edges(), digest
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def quotient_composition_witnesses(
|
|
671
|
+
witnesses: Iterable[CompositionWitness],
|
|
672
|
+
*,
|
|
673
|
+
limits: OverlapSearchLimits | None = None,
|
|
674
|
+
) -> tuple[CompositionClass, ...]:
|
|
675
|
+
"""Quotient accepted witnesses by exact labelled-rule isomorphism.
|
|
676
|
+
|
|
677
|
+
This is public so validation can time exact quotienting separately from
|
|
678
|
+
overlap enumeration and composite construction. It never chooses one
|
|
679
|
+
material witness: every witness remains attached to its exact class.
|
|
680
|
+
"""
|
|
681
|
+
active = limits or OverlapSearchLimits()
|
|
682
|
+
buckets: dict[tuple[Any, ...], list[list[CompositionWitness]]] = {}
|
|
683
|
+
for witness in witnesses:
|
|
684
|
+
rule = witness.composition.rule
|
|
685
|
+
groups = buckets.setdefault(_rule_bucket(rule), [])
|
|
686
|
+
for group in groups:
|
|
687
|
+
if rule_spans_isomorphic(rule, group[0].composition.rule):
|
|
688
|
+
group.append(witness)
|
|
689
|
+
break
|
|
690
|
+
else:
|
|
691
|
+
groups.append([witness])
|
|
692
|
+
classes = []
|
|
693
|
+
for groups in buckets.values():
|
|
694
|
+
for group in groups:
|
|
695
|
+
representative = group[0].composition.rule
|
|
696
|
+
canonical_id = canonical_rule_identity(
|
|
697
|
+
representative,
|
|
698
|
+
permutation_limit=active.max_canonical_permutations,
|
|
699
|
+
)
|
|
700
|
+
classes.append(
|
|
701
|
+
CompositionClass(
|
|
702
|
+
canonical_id,
|
|
703
|
+
representative,
|
|
704
|
+
tuple(sorted(group, key=lambda item: item.overlap_digest)),
|
|
705
|
+
)
|
|
706
|
+
)
|
|
707
|
+
return tuple(sorted(classes, key=lambda group: group.canonical_id))
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def search_compositions(
|
|
711
|
+
first: RuleSpan,
|
|
712
|
+
second: RuleSpan,
|
|
713
|
+
*,
|
|
714
|
+
limits: OverlapSearchLimits | None = None,
|
|
715
|
+
) -> CompositionSearchResult:
|
|
716
|
+
"""Enumerate, construct, retain, and exactly quotient all bounded overlaps."""
|
|
717
|
+
active = limits or OverlapSearchLimits()
|
|
718
|
+
match_matrix = extended_component_match_matrix(
|
|
719
|
+
first.right,
|
|
720
|
+
second.left,
|
|
721
|
+
max_embeddings=active.max_component_embeddings,
|
|
722
|
+
)
|
|
723
|
+
overlaps, explored = enumerate_overlaps(
|
|
724
|
+
first.right,
|
|
725
|
+
second.left,
|
|
726
|
+
limits=active,
|
|
727
|
+
_match_matrix=match_matrix,
|
|
728
|
+
)
|
|
729
|
+
accepted: list[CompositionWitness] = []
|
|
730
|
+
rejected: list[RejectedOverlap] = []
|
|
731
|
+
for overlap in overlaps:
|
|
732
|
+
digest = canonical_overlap_digest(
|
|
733
|
+
overlap,
|
|
734
|
+
permutation_limit=active.max_canonical_permutations,
|
|
735
|
+
)
|
|
736
|
+
try:
|
|
737
|
+
composition = compose_rules(first, second, overlap)
|
|
738
|
+
except CompositionError as error:
|
|
739
|
+
rejected.append(RejectedOverlap(overlap, digest, error.issues))
|
|
740
|
+
else:
|
|
741
|
+
accepted.append(CompositionWitness(overlap, digest, composition))
|
|
742
|
+
classes = quotient_composition_witnesses(accepted, limits=active)
|
|
743
|
+
return CompositionSearchResult(
|
|
744
|
+
overlaps,
|
|
745
|
+
classes,
|
|
746
|
+
tuple(sorted(rejected, key=lambda item: item.overlap_digest)),
|
|
747
|
+
match_matrix,
|
|
748
|
+
explored,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
__all__ = [
|
|
753
|
+
"CompositionClass",
|
|
754
|
+
"CompositionSearchResult",
|
|
755
|
+
"CompositionWitness",
|
|
756
|
+
"ExtendedMatchMatrix",
|
|
757
|
+
"OverlapSearchError",
|
|
758
|
+
"OverlapSearchIssue",
|
|
759
|
+
"OverlapSearchIssueCode",
|
|
760
|
+
"OverlapSearchLimits",
|
|
761
|
+
"RejectedOverlap",
|
|
762
|
+
"RuleSpanIsomorphism",
|
|
763
|
+
"canonical_overlap_digest",
|
|
764
|
+
"canonical_rule_identity",
|
|
765
|
+
"enumerate_overlaps",
|
|
766
|
+
"extended_component_match_matrix",
|
|
767
|
+
"find_rule_span_isomorphism",
|
|
768
|
+
"quotient_composition_witnesses",
|
|
769
|
+
"rule_spans_isomorphic",
|
|
770
|
+
"search_compositions",
|
|
771
|
+
]
|