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,1249 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Union
|
|
4
|
+
import logging
|
|
5
|
+
from time import perf_counter
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
|
|
9
|
+
from synkit.Chem.utils import remove_explicit_H_from_rsmi
|
|
10
|
+
from synkit.Graph.Hyrogen._misc import standardize_hydrogen, h_to_implicit
|
|
11
|
+
from synkit.IO import its_to_rsmi, rsmi_to_its
|
|
12
|
+
from synkit.Chem.Reaction.radical_wildcard import RadicalWildcardAdder
|
|
13
|
+
from synkit.Synthesis.Reactor import SynReactor
|
|
14
|
+
from synkit.Graph.Wildcard.its_merge import fuse_its_graphs
|
|
15
|
+
from synkit.Graph.Fusion import (
|
|
16
|
+
DEFAULT_INTERFACE_EDGE_KEYS,
|
|
17
|
+
DEFAULT_INTERFACE_NODE_KEYS,
|
|
18
|
+
FusionCandidate,
|
|
19
|
+
FusionConstructionError,
|
|
20
|
+
FusionInterface,
|
|
21
|
+
FusionInterfaceError,
|
|
22
|
+
construct_pushout,
|
|
23
|
+
fusion_candidate_from_construction,
|
|
24
|
+
fusion_candidates_exactly_equivalent,
|
|
25
|
+
graph_identity_digest,
|
|
26
|
+
graphs_exactly_equivalent,
|
|
27
|
+
)
|
|
28
|
+
from synkit.Graph.Matcher.mcs_matcher import MCSMatcher
|
|
29
|
+
from synkit.Graph.Matcher.wl_sel import WLSel
|
|
30
|
+
|
|
31
|
+
# from synkit.Graph.Matcher.approx_mcs import ApproxMCSMatcher
|
|
32
|
+
from synkit.Chem.Reaction.standardize import Standardize
|
|
33
|
+
from synkit.Synthesis.RBL.validation import (
|
|
34
|
+
FusionIssueCode,
|
|
35
|
+
FusionValidation,
|
|
36
|
+
certify_fusion_postprocessing,
|
|
37
|
+
validate_fusion_rsmi,
|
|
38
|
+
validate_rbl_candidate,
|
|
39
|
+
validate_strict_rbl_candidate,
|
|
40
|
+
validate_wildcard_mapping_roles,
|
|
41
|
+
)
|
|
42
|
+
from synkit.Synthesis.RBL.policy import (
|
|
43
|
+
AcceptanceTask,
|
|
44
|
+
OverlapScope,
|
|
45
|
+
ProofLevel,
|
|
46
|
+
RBLSearchPolicy,
|
|
47
|
+
SearchScope,
|
|
48
|
+
TerminationPolicy,
|
|
49
|
+
)
|
|
50
|
+
from synkit.Synthesis.RBL.overlap import (
|
|
51
|
+
TypedOverlapLimits,
|
|
52
|
+
enumerate_typed_overlaps,
|
|
53
|
+
)
|
|
54
|
+
from synkit.Synthesis.RBL.proof import RBLReplayCertificate
|
|
55
|
+
from synkit.Synthesis.RBL.matching import RBLMatchingMixin
|
|
56
|
+
from synkit.Synthesis.RBL.pipeline import RBLPipelineMixin
|
|
57
|
+
from synkit.Synthesis.RBL.reaction import RBLReactionMixin
|
|
58
|
+
from synkit.Synthesis.RBL.state import RBLStateMixin
|
|
59
|
+
|
|
60
|
+
ITSLike = Any
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RBLEngine(
|
|
64
|
+
RBLStateMixin, RBLPipelineMixin, RBLReactionMixin, RBLMatchingMixin
|
|
65
|
+
):
|
|
66
|
+
"""Radical-based linking (RBL) engine for bidirectional template
|
|
67
|
+
application and ITS-graph fusion using wildcard-based subgraph
|
|
68
|
+
matching.
|
|
69
|
+
|
|
70
|
+
Overview
|
|
71
|
+
--------
|
|
72
|
+
The RBL engine turns a reaction template (RSMI or ITS graph) into a
|
|
73
|
+
set of fused reaction graphs that link forward and backward template
|
|
74
|
+
applications through a wildcard-aware core. The workflow is:
|
|
75
|
+
|
|
76
|
+
1. **Template preparation**:
|
|
77
|
+
Convert a template (RSMI or ITS graph) into a standardized ITS
|
|
78
|
+
representation with normalized hydrogen handling.
|
|
79
|
+
|
|
80
|
+
2. **Forward / backward application**:
|
|
81
|
+
Use :class:`SynReactor` to apply the template to a substrate
|
|
82
|
+
(reactants or products) in forward or inverted mode, convert to
|
|
83
|
+
RSMI, decorate with radical wildcards, and convert back to ITS.
|
|
84
|
+
|
|
85
|
+
3. **Wildcard-based fusion**:
|
|
86
|
+
For each forward/backward ITS pair, run a matcher
|
|
87
|
+
(:class:`MCSMatcher` or :class:`ApproxMCSMatcher`) to detect a
|
|
88
|
+
core overlap (ignoring wildcard regions) and fuse the graphs via
|
|
89
|
+
:func:`fuse_its_graphs`. The fused ITS graphs are then converted
|
|
90
|
+
back to post-processed RSMI strings.
|
|
91
|
+
|
|
92
|
+
Matching back-ends: exact vs. approximate
|
|
93
|
+
-----------------------------------------
|
|
94
|
+
The engine delegates ITS matching to :attr:`matcher_cls`, which is
|
|
95
|
+
assumed to be API-compatible with :class:`MCSMatcher`:
|
|
96
|
+
|
|
97
|
+
* :class:`MCSMatcher` (default)
|
|
98
|
+
- Exhaustive maximum-common-subgraph search based on
|
|
99
|
+
:class:`networkx.algorithms.isomorphism.GraphMatcher`.
|
|
100
|
+
- Respects ``prune_wc`` and
|
|
101
|
+
``prune_automorphisms``.
|
|
102
|
+
- Produces exact MCS mappings but can be expensive on large or
|
|
103
|
+
highly symmetric graphs.
|
|
104
|
+
|
|
105
|
+
* :class:`ApproxMCSMatcher`
|
|
106
|
+
- Heuristic / greedy approximate MCS search.
|
|
107
|
+
- Uses seed selection and local greedy growth instead of
|
|
108
|
+
exhaustive enumeration.
|
|
109
|
+
- Much faster on large graphs but only approximate – mappings
|
|
110
|
+
are usually close to optimal in practice but not guaranteed
|
|
111
|
+
to be globally maximal.
|
|
112
|
+
|
|
113
|
+
Any custom matcher can be plugged in as long as it implements the
|
|
114
|
+
:class:`MCSMatcher` public API:
|
|
115
|
+
|
|
116
|
+
* ``__init__(node_attrs, node_defaults, edge_attrs, prune_wc, ...)``
|
|
117
|
+
* :py:meth:`find_rc_mapping`
|
|
118
|
+
* :py:meth:`get_mappings`
|
|
119
|
+
|
|
120
|
+
Early-stop semantics
|
|
121
|
+
--------------------
|
|
122
|
+
The engine exposes two orthogonal control flags:
|
|
123
|
+
``early_stop`` and ``fast_paths_only``.
|
|
124
|
+
|
|
125
|
+
* If :attr:`early_stop` is ``True``:
|
|
126
|
+
|
|
127
|
+
* A cheap **quick-check** is attempted first via
|
|
128
|
+
:meth:`_quick_check`.
|
|
129
|
+
|
|
130
|
+
* If that fails, the engine looks for **ITS graphs without any
|
|
131
|
+
wildcard atoms** in the forward and backward sets and
|
|
132
|
+
post-processes them directly via
|
|
133
|
+
:meth:`_early_stop_on_nonwildcard`, without any MCS/fusion.
|
|
134
|
+
|
|
135
|
+
For each such candidate, a canonical reactant/product check is
|
|
136
|
+
performed to ensure consistency with the original reaction:
|
|
137
|
+
|
|
138
|
+
* forward candidates must preserve the original *main* product
|
|
139
|
+
component;
|
|
140
|
+
* backward candidates must preserve the original *main*
|
|
141
|
+
reactant component.
|
|
142
|
+
|
|
143
|
+
* Only if both these cheap paths fail, fusion and
|
|
144
|
+
post-processing are run in a **streaming loop**:
|
|
145
|
+
mappings are fused and post-processed one by one, and the
|
|
146
|
+
pipeline stops after the **first successful fused RSMI**.
|
|
147
|
+
|
|
148
|
+
* If :attr:`early_stop` is ``False``, the same loop runs without
|
|
149
|
+
early exit, collecting all fused ITS and fused RSMIs.
|
|
150
|
+
|
|
151
|
+
Fast-track and fast-path-only modes
|
|
152
|
+
-----------------------------------
|
|
153
|
+
``mode="fast_track"`` runs only the two cheap paths and never enters MCS
|
|
154
|
+
or fusion. ``mode="fast_fusion"`` first tries those paths and then searches
|
|
155
|
+
a bounded, WL-ranked prefix of fusion pairs. WL scores affect order only;
|
|
156
|
+
they are not treated as a semantic filter. The default bound is eight pairs
|
|
157
|
+
and the result always reports ``complete=False`` when fusion is used.
|
|
158
|
+
|
|
159
|
+
* If :attr:`fast_paths_only` is ``True`` (or
|
|
160
|
+
:meth:`process` is called with ``fast_paths_only=True``):
|
|
161
|
+
|
|
162
|
+
* The engine **never** enters the expensive MCS/fusion stage
|
|
163
|
+
(:meth:`_fuse_and_postprocess` is skipped).
|
|
164
|
+
|
|
165
|
+
* It only attempts:
|
|
166
|
+
|
|
167
|
+
1. :meth:`_quick_check`
|
|
168
|
+
2. :meth:`_early_stop_on_nonwildcard`
|
|
169
|
+
|
|
170
|
+
* If neither path yields a solution, the engine returns with
|
|
171
|
+
empty :attr:`fused_its` / :attr:`fused_rsmis` and
|
|
172
|
+
``result['mode'] == "fast_paths_only"`` and
|
|
173
|
+
``result['reason'] == "fast_paths_no_solution"``.
|
|
174
|
+
|
|
175
|
+
* The flag :attr:`early_stop` is **ignored** for the fusion
|
|
176
|
+
stage in this mode, but still controls behaviour when
|
|
177
|
+
``fast_paths_only=False``.
|
|
178
|
+
|
|
179
|
+
Reactor / hydrogen control
|
|
180
|
+
--------------------------
|
|
181
|
+
The underlying :class:`SynReactor` is configured via three flags
|
|
182
|
+
that are exposed on the engine:
|
|
183
|
+
|
|
184
|
+
* :attr:`implicit_temp` – forwarded to ``SynReactor(..., implicit_temp=...)``.
|
|
185
|
+
* :attr:`explicit_h` – forwarded to ``SynReactor(..., explicit_h=...)``.
|
|
186
|
+
* :attr:`embed_threshold` – forwarded to ``SynReactor(..., embed_threshold=...)``.
|
|
187
|
+
|
|
188
|
+
This gives fine-grained external control over how templates are
|
|
189
|
+
embedded and how hydrogens are handled during the reaction stage.
|
|
190
|
+
``mode="verified"`` deliberately overrides these two flags with
|
|
191
|
+
``implicit_temp=False`` and ``explicit_h=True`` so mapped hydrogen
|
|
192
|
+
identities cannot be erased from a proof-safe rule application.
|
|
193
|
+
|
|
194
|
+
:param wildcard_element: Value used for wildcard atom elements.
|
|
195
|
+
:type wildcard_element: Any
|
|
196
|
+
:param element_key: Node attribute containing the element symbol.
|
|
197
|
+
:type element_key: str
|
|
198
|
+
:param node_attrs: Node attributes used for candidate matching.
|
|
199
|
+
:type node_attrs: Sequence[str] | None
|
|
200
|
+
:param edge_attrs: Edge attributes used for candidate matching.
|
|
201
|
+
:type edge_attrs: Sequence[str] | None
|
|
202
|
+
:param prune_wc: Remove wildcard nodes before matching when supported.
|
|
203
|
+
:type prune_wc: bool
|
|
204
|
+
:param prune_automorphisms: Collapse automorphism-equivalent mappings.
|
|
205
|
+
``None`` selects the profile default; verified search always disables
|
|
206
|
+
this uncertified quotient.
|
|
207
|
+
:type prune_automorphisms: bool | None
|
|
208
|
+
:param mcs_side: Reaction-center side passed to the MCS matcher.
|
|
209
|
+
:type mcs_side: str
|
|
210
|
+
:param early_stop: Stop after the first valid result.
|
|
211
|
+
:type early_stop: bool
|
|
212
|
+
:param fast_paths_only: Restrict processing to quick-check and
|
|
213
|
+
non-wildcard paths.
|
|
214
|
+
:type fast_paths_only: bool
|
|
215
|
+
:param mode: Compatibility search profile name.
|
|
216
|
+
:type mode: str | None
|
|
217
|
+
:param search_policy: Explicit search scope and termination policy;
|
|
218
|
+
mutually exclusive with ``mode``.
|
|
219
|
+
:type search_policy: RBLSearchPolicy | None
|
|
220
|
+
:param max_pairs: Maximum number of ranked forward/backward pairs.
|
|
221
|
+
:type max_pairs: int | None
|
|
222
|
+
:param max_mappings_per_pair: Maximum mappings evaluated for each pair;
|
|
223
|
+
zero means unbounded and ``None`` selects
|
|
224
|
+
the active profile's default.
|
|
225
|
+
:type max_mappings_per_pair: int | None
|
|
226
|
+
:param overlap_max_states: Maximum incremental states per typed-overlap
|
|
227
|
+
enumeration.
|
|
228
|
+
:type overlap_max_states: int
|
|
229
|
+
:param overlap_max_mappings: Maximum emitted typed overlaps per graph pair.
|
|
230
|
+
:type overlap_max_mappings: int
|
|
231
|
+
:param overlap_timeout_seconds: Optional internal typed-overlap wall time.
|
|
232
|
+
:type overlap_timeout_seconds: float | None
|
|
233
|
+
:param component_matching: Enable component-assignment matching.
|
|
234
|
+
:type component_matching: bool | None
|
|
235
|
+
:param fusion_backend: Fusion materialization backend.
|
|
236
|
+
:type fusion_backend: str | None
|
|
237
|
+
:param implicit_temp: Treat the reactor template as implicit-H.
|
|
238
|
+
:type implicit_temp: bool
|
|
239
|
+
:param explicit_h: Retain explicit hydrogens during reactor application.
|
|
240
|
+
:type explicit_h: bool
|
|
241
|
+
:param electron_diagnostics: Collect electron-accounting diagnostics.
|
|
242
|
+
:type electron_diagnostics: bool
|
|
243
|
+
:param preserve_original_sides: Original reaction sides that accepted
|
|
244
|
+
candidates must preserve.
|
|
245
|
+
:type preserve_original_sides: Sequence[str]
|
|
246
|
+
:param conservation_boundary: Strict-mode ``"closed"`` or ``"open"``
|
|
247
|
+
material boundary.
|
|
248
|
+
:type conservation_boundary: str
|
|
249
|
+
:param environment_delta: Exact declared material/charge delta for an open
|
|
250
|
+
boundary.
|
|
251
|
+
:type environment_delta: Mapping[str, int] | None
|
|
252
|
+
:param embed_threshold: Reactor embedding limit.
|
|
253
|
+
:type embed_threshold: int
|
|
254
|
+
:param reactor_cls: Reactor implementation.
|
|
255
|
+
:type reactor_cls: type
|
|
256
|
+
:param wildcard_adder_cls: Radical-wildcard decorator implementation.
|
|
257
|
+
:type wildcard_adder_cls: type
|
|
258
|
+
:param matcher_cls: ITS matcher implementation.
|
|
259
|
+
:type matcher_cls: type
|
|
260
|
+
:param fuse_fn: Legacy ITS-fusion function.
|
|
261
|
+
:type fuse_fn: Callable[[ITSLike, ITSLike, Dict[Any, Any]], ITSLike]
|
|
262
|
+
:param remove_explicit_H_fn: Reaction explicit-hydrogen remover.
|
|
263
|
+
:type remove_explicit_H_fn: Callable[[str], str]
|
|
264
|
+
:param rsmi_to_its_fn: Reaction-to-ITS converter.
|
|
265
|
+
:type rsmi_to_its_fn: Callable[..., ITSLike]
|
|
266
|
+
:param its_to_rsmi_fn: ITS-to-reaction converter.
|
|
267
|
+
:type its_to_rsmi_fn: Callable[[ITSLike], str]
|
|
268
|
+
:param h_to_implicit_fn: Explicit-to-implicit hydrogen converter.
|
|
269
|
+
:type h_to_implicit_fn: Callable[[ITSLike], ITSLike]
|
|
270
|
+
:param standardize_h_fn: ITS hydrogen standardizer.
|
|
271
|
+
:type standardize_h_fn: Callable[[ITSLike], ITSLike]
|
|
272
|
+
:param standardize_fn: Reaction canonicalizer used for quick checks.
|
|
273
|
+
:type standardize_fn: Callable[[str], str] | None
|
|
274
|
+
:param logger: Diagnostic logger.
|
|
275
|
+
:type logger: logging.Logger | None
|
|
276
|
+
|
|
277
|
+
.. rubric:: Examples
|
|
278
|
+
|
|
279
|
+
Exact MCS back-end
|
|
280
|
+
~~~~~~~~~~~~~~~~~~
|
|
281
|
+
Use the default :class:`MCSMatcher` for exact MCS fusion:
|
|
282
|
+
|
|
283
|
+
.. code-block:: python
|
|
284
|
+
|
|
285
|
+
from synkit.Synthesis.RBL.engine import RBLEngine
|
|
286
|
+
|
|
287
|
+
rxn = "CCO.CBr>>CCOBr"
|
|
288
|
+
template = "CBr>>C[*]" # toy example
|
|
289
|
+
|
|
290
|
+
engine = RBLEngine(
|
|
291
|
+
early_stop=True,
|
|
292
|
+
fast_paths_only=False,
|
|
293
|
+
implicit_temp=True,
|
|
294
|
+
explicit_h=False,
|
|
295
|
+
embed_threshold=5000,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
engine = engine.process(rxn, template)
|
|
299
|
+
print(engine.result["mode"])
|
|
300
|
+
print(engine.fused_rsmis)
|
|
301
|
+
|
|
302
|
+
Approximate MCS back-end
|
|
303
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
304
|
+
Swap in :class:`ApproxMCSMatcher` to accelerate matching on large
|
|
305
|
+
graphs while retaining the same RBL API:
|
|
306
|
+
|
|
307
|
+
.. code-block:: python
|
|
308
|
+
|
|
309
|
+
from synkit.Graph.Matcher.approx_mcs import ApproxMCSMatcher
|
|
310
|
+
from synkit.Synthesis.RBL.engine import RBLEngine
|
|
311
|
+
|
|
312
|
+
rxn = "CC1=CC=CC=C1.OBr>>CC1=CC=CC=C1OBr"
|
|
313
|
+
template = "OBr>>O[*]"
|
|
314
|
+
|
|
315
|
+
engine = RBLEngine(
|
|
316
|
+
matcher_cls=ApproxMCSMatcher, # use heuristic MCS
|
|
317
|
+
early_stop=False, # collect all fused hits
|
|
318
|
+
fast_paths_only=False,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
engine = engine.process(rxn, template)
|
|
322
|
+
for fused in engine.fused_rsmis:
|
|
323
|
+
print(fused)
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
def __init__(
|
|
327
|
+
self,
|
|
328
|
+
*,
|
|
329
|
+
wildcard_element: Any = ("*", "*"),
|
|
330
|
+
element_key: str = "element",
|
|
331
|
+
node_attrs: Optional[Sequence[str]] = None,
|
|
332
|
+
edge_attrs: Optional[Sequence[str]] = None,
|
|
333
|
+
prune_wc: bool = True,
|
|
334
|
+
prune_automorphisms: bool | None = None,
|
|
335
|
+
mcs_side: str = "l",
|
|
336
|
+
early_stop: bool = True,
|
|
337
|
+
fast_paths_only: bool = False,
|
|
338
|
+
mode: str | None = None,
|
|
339
|
+
search_policy: RBLSearchPolicy | None = None,
|
|
340
|
+
max_pairs: int | None = None,
|
|
341
|
+
max_mappings_per_pair: int | None = None,
|
|
342
|
+
overlap_max_states: int = 250_000,
|
|
343
|
+
overlap_max_mappings: int = 50_000,
|
|
344
|
+
overlap_timeout_seconds: float | None = None,
|
|
345
|
+
component_matching: bool | None = None,
|
|
346
|
+
fusion_backend: str | None = None,
|
|
347
|
+
implicit_temp: bool = True,
|
|
348
|
+
explicit_h: bool = False,
|
|
349
|
+
electron_diagnostics: bool = False,
|
|
350
|
+
preserve_original_sides: Sequence[str] = ("products",),
|
|
351
|
+
conservation_boundary: str = "closed",
|
|
352
|
+
environment_delta: Mapping[str, int] | None = None,
|
|
353
|
+
embed_threshold: int = 10_000,
|
|
354
|
+
reactor_cls: type = SynReactor,
|
|
355
|
+
wildcard_adder_cls: type = RadicalWildcardAdder,
|
|
356
|
+
matcher_cls: type = MCSMatcher,
|
|
357
|
+
fuse_fn: Callable[[ITSLike, ITSLike, Dict[Any, Any]], ITSLike] = (
|
|
358
|
+
fuse_its_graphs
|
|
359
|
+
),
|
|
360
|
+
remove_explicit_H_fn: Callable[[str], str] = remove_explicit_H_from_rsmi,
|
|
361
|
+
rsmi_to_its_fn: Callable[..., ITSLike] = rsmi_to_its,
|
|
362
|
+
its_to_rsmi_fn: Callable[[ITSLike], str] = its_to_rsmi,
|
|
363
|
+
h_to_implicit_fn: Callable[[ITSLike], ITSLike] = h_to_implicit,
|
|
364
|
+
standardize_h_fn: Callable[[ITSLike], ITSLike] = standardize_hydrogen,
|
|
365
|
+
standardize_fn: Optional[Callable[[str], str]] = Standardize().fit,
|
|
366
|
+
logger: Optional[logging.Logger] = None,
|
|
367
|
+
) -> None:
|
|
368
|
+
# Core config
|
|
369
|
+
self.wildcard_element: Any = wildcard_element
|
|
370
|
+
self.element_key: str = element_key
|
|
371
|
+
self.node_attrs: List[str] = (
|
|
372
|
+
list(node_attrs)
|
|
373
|
+
if node_attrs is not None
|
|
374
|
+
else ["element", "aromatic", "charge"]
|
|
375
|
+
)
|
|
376
|
+
self.edge_attrs: List[str] = (
|
|
377
|
+
list(edge_attrs) if edge_attrs is not None else ["order"]
|
|
378
|
+
)
|
|
379
|
+
# Candidate discovery may intentionally use a coarse configurable
|
|
380
|
+
# label set. Proof construction may not: its interface uses the fixed
|
|
381
|
+
# isotope/Lewis contract independently of matcher tuning, while the
|
|
382
|
+
# completed-graph proof handles hydrogen presentation equivalence.
|
|
383
|
+
self.interface_node_attrs = list(DEFAULT_INTERFACE_NODE_KEYS)
|
|
384
|
+
self.interface_edge_attrs = list(DEFAULT_INTERFACE_EDGE_KEYS)
|
|
385
|
+
if mode is not None and search_policy is not None:
|
|
386
|
+
raise ValueError("Specify either mode or search_policy, not both.")
|
|
387
|
+
self.mode = mode
|
|
388
|
+
if search_policy is not None:
|
|
389
|
+
if not isinstance(search_policy, RBLSearchPolicy):
|
|
390
|
+
raise TypeError("search_policy must be an RBLSearchPolicy.")
|
|
391
|
+
self.search_policy = search_policy
|
|
392
|
+
elif mode is not None:
|
|
393
|
+
self.search_policy = RBLSearchPolicy.from_mode(mode)
|
|
394
|
+
elif fast_paths_only:
|
|
395
|
+
self.search_policy = RBLSearchPolicy(
|
|
396
|
+
SearchScope.FAST_PATHS_ONLY,
|
|
397
|
+
TerminationPolicy.FIRST_VALID,
|
|
398
|
+
)
|
|
399
|
+
elif early_stop:
|
|
400
|
+
self.search_policy = RBLSearchPolicy.from_mode("early_stop")
|
|
401
|
+
else:
|
|
402
|
+
self.search_policy = RBLSearchPolicy.from_mode("full")
|
|
403
|
+
verified_mode = self.search_policy.proof_level is ProofLevel.REPLAYABLE
|
|
404
|
+
if prune_automorphisms is None:
|
|
405
|
+
# Orbit pruning is an optional heuristic until the matcher emits a
|
|
406
|
+
# checkable quotient certificate. The verified profile therefore
|
|
407
|
+
# uses the literal mapping universe; compatibility profiles retain
|
|
408
|
+
# their historical bounded behavior and report incompleteness.
|
|
409
|
+
prune_automorphisms = not verified_mode
|
|
410
|
+
if verified_mode:
|
|
411
|
+
prune_automorphisms = False
|
|
412
|
+
# Proof-safe rule application keeps mapped hydrogen as an explicit
|
|
413
|
+
# graph vertex. Folding it into an implicit template destroys the
|
|
414
|
+
# identity needed to certify proton-transfer rules.
|
|
415
|
+
implicit_temp = False
|
|
416
|
+
explicit_h = True
|
|
417
|
+
self.prune_wc = prune_wc
|
|
418
|
+
self.prune_automorphisms = prune_automorphisms
|
|
419
|
+
self.mcs_side = mcs_side
|
|
420
|
+
self.verified_mode = verified_mode
|
|
421
|
+
self.early_stop = (
|
|
422
|
+
self.search_policy.termination is TerminationPolicy.FIRST_VALID
|
|
423
|
+
)
|
|
424
|
+
self.fast_paths_only = self.search_policy.scope is SearchScope.FAST_PATHS_ONLY
|
|
425
|
+
if max_pairs is None and self.search_policy.scope is SearchScope.BOUNDED_FUSION:
|
|
426
|
+
max_pairs = 8
|
|
427
|
+
if max_pairs is not None and int(max_pairs) <= 0:
|
|
428
|
+
raise ValueError("max_pairs must be positive or None.")
|
|
429
|
+
self.max_pairs: int | None = None if max_pairs is None else int(max_pairs)
|
|
430
|
+
mapping_limit = (
|
|
431
|
+
0 if verified_mode else 1
|
|
432
|
+
) if max_mappings_per_pair is None else int(max_mappings_per_pair)
|
|
433
|
+
if mapping_limit < 0:
|
|
434
|
+
raise ValueError("max_mappings_per_pair must be non-negative.")
|
|
435
|
+
self.max_mappings_per_pair = mapping_limit
|
|
436
|
+
self.overlap_limits = TypedOverlapLimits(
|
|
437
|
+
max_states=int(overlap_max_states),
|
|
438
|
+
max_overlaps=int(overlap_max_mappings),
|
|
439
|
+
timeout_seconds=overlap_timeout_seconds,
|
|
440
|
+
)
|
|
441
|
+
if component_matching is None:
|
|
442
|
+
component_matching = not (
|
|
443
|
+
not self.prune_automorphisms and self.max_mappings_per_pair == 0
|
|
444
|
+
)
|
|
445
|
+
if verified_mode:
|
|
446
|
+
component_matching = False
|
|
447
|
+
self.component_matching = bool(component_matching)
|
|
448
|
+
if fusion_backend is None:
|
|
449
|
+
fusion_backend = (
|
|
450
|
+
"categorical_pushout"
|
|
451
|
+
if verified_mode
|
|
452
|
+
or self.search_policy.scope is SearchScope.BOUNDED_FUSION
|
|
453
|
+
else "legacy_its_merge"
|
|
454
|
+
)
|
|
455
|
+
if fusion_backend not in {"categorical_pushout", "legacy_its_merge"}:
|
|
456
|
+
raise ValueError(
|
|
457
|
+
"fusion_backend must be 'categorical_pushout' or " "'legacy_its_merge'."
|
|
458
|
+
)
|
|
459
|
+
self.fusion_backend = fusion_backend
|
|
460
|
+
|
|
461
|
+
# Reactor behaviour flags
|
|
462
|
+
self.implicit_temp: bool = bool(implicit_temp)
|
|
463
|
+
self.explicit_h: bool = bool(explicit_h)
|
|
464
|
+
self.electron_diagnostics: bool = bool(electron_diagnostics)
|
|
465
|
+
unknown_preservation_sides = set(preserve_original_sides) - {
|
|
466
|
+
"reactants",
|
|
467
|
+
"products",
|
|
468
|
+
}
|
|
469
|
+
if unknown_preservation_sides:
|
|
470
|
+
raise ValueError(
|
|
471
|
+
"Unknown preserve_original_sides values: "
|
|
472
|
+
f"{sorted(unknown_preservation_sides)!r}."
|
|
473
|
+
)
|
|
474
|
+
self.preserve_original_sides = tuple(dict.fromkeys(preserve_original_sides))
|
|
475
|
+
if conservation_boundary not in {"closed", "open"}:
|
|
476
|
+
raise ValueError("conservation_boundary must be 'closed' or 'open'.")
|
|
477
|
+
self.conservation_boundary = conservation_boundary
|
|
478
|
+
self.environment_delta = dict(environment_delta or {})
|
|
479
|
+
self.embed_threshold: int = int(embed_threshold)
|
|
480
|
+
|
|
481
|
+
# Dependencies (DI)
|
|
482
|
+
self.reactor_cls = reactor_cls
|
|
483
|
+
self.wildcard_adder_cls = wildcard_adder_cls
|
|
484
|
+
self.matcher_cls = matcher_cls
|
|
485
|
+
self.fuse_fn = fuse_fn
|
|
486
|
+
self.remove_explicit_H_fn = remove_explicit_H_fn
|
|
487
|
+
self.rsmi_to_its_fn = rsmi_to_its_fn
|
|
488
|
+
self.its_to_rsmi_fn = its_to_rsmi_fn
|
|
489
|
+
self.h_to_implicit_fn = h_to_implicit_fn
|
|
490
|
+
self.standardize_h_fn = standardize_h_fn
|
|
491
|
+
self.standardize_fn: Callable[[str], str] = (
|
|
492
|
+
standardize_fn if standardize_fn is not None else self._identity_standardize
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# Logging
|
|
496
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
497
|
+
|
|
498
|
+
# Internal state (fluent-style access via properties)
|
|
499
|
+
self._template_raw: Optional[Union[str, nx.Graph, ITSLike]] = None
|
|
500
|
+
self._template_its: Optional[ITSLike] = None
|
|
501
|
+
|
|
502
|
+
self._last_reaction: Optional[str] = None
|
|
503
|
+
self._last_reactants: Optional[str] = None
|
|
504
|
+
self._last_products: Optional[str] = None
|
|
505
|
+
|
|
506
|
+
self._forward_its: List[ITSLike] = []
|
|
507
|
+
self._backward_its: List[ITSLike] = []
|
|
508
|
+
self._fused_its: List[ITSLike] = []
|
|
509
|
+
self._fused_rsmis: List[str] = []
|
|
510
|
+
self._fusion_candidates: List[FusionCandidate] = []
|
|
511
|
+
self._rbl_proofs: List[RBLReplayCertificate] = []
|
|
512
|
+
self._rbl_proof_digests: set[str] = set()
|
|
513
|
+
self._fusion_search_stats: Dict[str, Any] = {}
|
|
514
|
+
self._latest_postprocessed_its: Optional[ITSLike] = None
|
|
515
|
+
self._latest_output_validation: Optional[FusionValidation] = None
|
|
516
|
+
self._diagnostics: Dict[str, List[Dict[str, Any]]] = {
|
|
517
|
+
"forward": [],
|
|
518
|
+
"backward": [],
|
|
519
|
+
"quick_check": [],
|
|
520
|
+
"fusion": [],
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# Result / termination bookkeeping
|
|
524
|
+
self._last_stop_mode: str = "not_run"
|
|
525
|
+
self._last_stop_reason: str = "not_run"
|
|
526
|
+
self._last_stop_metadata: Dict[str, Any] = {}
|
|
527
|
+
self._active_search_policy = self.search_policy
|
|
528
|
+
|
|
529
|
+
def _fuse_and_postprocess(
|
|
530
|
+
self,
|
|
531
|
+
fw_its: Sequence[ITSLike],
|
|
532
|
+
bw_its: Sequence[ITSLike],
|
|
533
|
+
*,
|
|
534
|
+
replace_wc: bool,
|
|
535
|
+
early_stop: bool,
|
|
536
|
+
max_pairs: int | None = None,
|
|
537
|
+
initial_graphs: Sequence[ITSLike] = (),
|
|
538
|
+
initial_rsmis: Sequence[str] = (),
|
|
539
|
+
) -> None:
|
|
540
|
+
"""
|
|
541
|
+
Core fusion + post-processing loop.
|
|
542
|
+
|
|
543
|
+
This is where performance matters. Instead of trying all
|
|
544
|
+
``(fw, bw)`` pairs naively, we first apply a WL-based selector
|
|
545
|
+
(:class:`WLSel`) to rank candidate forward–backward ITS pairs by
|
|
546
|
+
structural similarity. We then process pairs in this order:
|
|
547
|
+
|
|
548
|
+
1. Use the configured matcher (:class:`MCSMatcher` or
|
|
549
|
+
:class:`ApproxMCSMatcher`) to obtain mappings.
|
|
550
|
+
2. Take at most :attr:`max_mappings_per_pair` mappings.
|
|
551
|
+
3. Construct the audited pushout, then use either that graph or the
|
|
552
|
+
configured legacy compatibility fusion backend.
|
|
553
|
+
4. Immediately post-process the fused graph via
|
|
554
|
+
:meth:`_postprocess_single`.
|
|
555
|
+
|
|
556
|
+
If ``early_stop`` is ``True``, the method returns as soon as a
|
|
557
|
+
successful fused RSMI is obtained. Otherwise, it explores all
|
|
558
|
+
WL-selected pairs/mappings and collects all fused ITS and fused
|
|
559
|
+
RSMIs.
|
|
560
|
+
|
|
561
|
+
On return, :attr:`_fused_its`, :attr:`_fused_rsmis` and the
|
|
562
|
+
result bookkeeping attributes are updated.
|
|
563
|
+
|
|
564
|
+
:param fw_its: Forward ITS graphs.
|
|
565
|
+
:type fw_its: Sequence[ITSLike]
|
|
566
|
+
:param bw_its: Backward ITS graphs.
|
|
567
|
+
:type bw_its: Sequence[ITSLike]
|
|
568
|
+
:param replace_wc: Whether to replace wildcard atoms with H.
|
|
569
|
+
:type replace_wc: bool
|
|
570
|
+
:param early_stop: Whether to stop after the first valid fused RSMI.
|
|
571
|
+
:type early_stop: bool
|
|
572
|
+
:param max_pairs: Optional hard prefix bound after deterministic pair
|
|
573
|
+
ranking. ``None`` explores every pair.
|
|
574
|
+
:type max_pairs: int or None
|
|
575
|
+
:param initial_graphs: Already accepted direct-path candidates that a
|
|
576
|
+
wider search must retain.
|
|
577
|
+
:type initial_graphs: Sequence[ITSLike]
|
|
578
|
+
:param initial_rsmis: Serializations aligned with ``initial_graphs``.
|
|
579
|
+
:type initial_rsmis: Sequence[str]
|
|
580
|
+
"""
|
|
581
|
+
if len(initial_graphs) != len(initial_rsmis):
|
|
582
|
+
raise ValueError("initial_graphs and initial_rsmis must be aligned.")
|
|
583
|
+
fused_graphs: List[ITSLike] = list(initial_graphs)
|
|
584
|
+
fused_rsmis: List[str] = list(initial_rsmis)
|
|
585
|
+
rw_adder = self.wildcard_adder_cls()
|
|
586
|
+
|
|
587
|
+
# --- WL-based candidate selection instead of naive nested loops ---
|
|
588
|
+
# The selector is an ordering heuristic only. A similarity threshold
|
|
589
|
+
# is not a sound admissibility predicate for graph fusion.
|
|
590
|
+
pair_order_started = perf_counter()
|
|
591
|
+
selector = WLSel(
|
|
592
|
+
fw_its,
|
|
593
|
+
bw_its,
|
|
594
|
+
element_key=self.element_key,
|
|
595
|
+
node_attrs=self.node_attrs,
|
|
596
|
+
edge_attrs=self.edge_attrs,
|
|
597
|
+
min_score=0.0,
|
|
598
|
+
)
|
|
599
|
+
selector.build_signatures().score_pairs(top_k=max_pairs)
|
|
600
|
+
pairs = selector.pair_indices
|
|
601
|
+
pair_order_seconds = perf_counter() - pair_order_started
|
|
602
|
+
pair_candidate_count = selector.pair_candidate_count
|
|
603
|
+
n_pairs_truncated = pair_candidate_count - len(pairs)
|
|
604
|
+
|
|
605
|
+
n_pairs = 0
|
|
606
|
+
n_mappings_total = 0
|
|
607
|
+
n_mappings_rejected = 0
|
|
608
|
+
n_candidates_deduplicated = 0
|
|
609
|
+
n_mappings_truncated = 0
|
|
610
|
+
n_postprocess_rejections = 0
|
|
611
|
+
overlap_certificates: List[Dict[str, Any]] = []
|
|
612
|
+
overlap_search_incomplete = 0
|
|
613
|
+
n_operational_failures = 0
|
|
614
|
+
overlap_seconds = 0.0
|
|
615
|
+
construction_seconds = 0.0
|
|
616
|
+
postprocess_seconds = 0.0
|
|
617
|
+
candidate_buckets: Dict[str, List[FusionCandidate]] = {}
|
|
618
|
+
accepted_graph_buckets: Dict[str, List[nx.Graph]] = {}
|
|
619
|
+
for graph in initial_graphs:
|
|
620
|
+
if isinstance(graph, nx.Graph):
|
|
621
|
+
accepted_graph_buckets.setdefault(
|
|
622
|
+
graph_identity_digest(graph),
|
|
623
|
+
[],
|
|
624
|
+
).append(graph)
|
|
625
|
+
|
|
626
|
+
def incomplete_reasons(*, first_valid: bool) -> List[str]:
|
|
627
|
+
reasons = []
|
|
628
|
+
if (
|
|
629
|
+
self._active_search_policy.overlap_scope
|
|
630
|
+
is OverlapScope.MAXIMUM_COMMON_SUBGRAPHS
|
|
631
|
+
):
|
|
632
|
+
reasons.append("maximum_common_subgraphs_only")
|
|
633
|
+
if first_valid:
|
|
634
|
+
reasons.append("first_valid_termination")
|
|
635
|
+
if n_pairs_truncated:
|
|
636
|
+
reasons.append("pair_limit")
|
|
637
|
+
if n_mappings_truncated:
|
|
638
|
+
reasons.append("mapping_limit")
|
|
639
|
+
if self.prune_automorphisms:
|
|
640
|
+
reasons.append("uncertified_automorphism_pruning")
|
|
641
|
+
if self.component_matching:
|
|
642
|
+
reasons.append("single_component_assignment")
|
|
643
|
+
if (
|
|
644
|
+
self._active_search_policy.overlap_scope
|
|
645
|
+
is OverlapScope.MAXIMUM_COMMON_SUBGRAPHS
|
|
646
|
+
and self.matcher_cls is not MCSMatcher
|
|
647
|
+
):
|
|
648
|
+
reasons.append("non_exact_matcher")
|
|
649
|
+
if overlap_search_incomplete:
|
|
650
|
+
reasons.append("typed_overlap_limit")
|
|
651
|
+
if n_operational_failures:
|
|
652
|
+
reasons.append("operational_failure")
|
|
653
|
+
return reasons
|
|
654
|
+
|
|
655
|
+
for i_fw, i_bw in pairs:
|
|
656
|
+
fw = fw_its[i_fw]
|
|
657
|
+
bw = bw_its[i_bw]
|
|
658
|
+
n_pairs += 1
|
|
659
|
+
|
|
660
|
+
overlap_started = perf_counter()
|
|
661
|
+
if self._active_search_policy.overlap_scope is OverlapScope.ALL_TYPED:
|
|
662
|
+
overlap_result = enumerate_typed_overlaps(
|
|
663
|
+
fw,
|
|
664
|
+
bw,
|
|
665
|
+
node_keys=self.interface_node_attrs,
|
|
666
|
+
edge_keys=self.interface_edge_attrs,
|
|
667
|
+
element_key=self.element_key,
|
|
668
|
+
wildcard_element=self.wildcard_element,
|
|
669
|
+
limits=self.overlap_limits,
|
|
670
|
+
)
|
|
671
|
+
certificate_payload = overlap_result.certificate.to_dict()
|
|
672
|
+
certificate_payload["pair_index"] = [i_fw, i_bw]
|
|
673
|
+
overlap_certificates.append(certificate_payload)
|
|
674
|
+
if not overlap_result.certificate.complete:
|
|
675
|
+
overlap_search_incomplete += 1
|
|
676
|
+
mappings = [dict(mapping) for mapping in overlap_result.mappings]
|
|
677
|
+
else:
|
|
678
|
+
matcher = self._build_matcher()
|
|
679
|
+
matcher.find_rc_mapping(
|
|
680
|
+
fw,
|
|
681
|
+
bw,
|
|
682
|
+
mcs=True,
|
|
683
|
+
mcs_mol=False,
|
|
684
|
+
component=self.component_matching,
|
|
685
|
+
side=self.mcs_side,
|
|
686
|
+
)
|
|
687
|
+
mappings = matcher.get_mappings(direction="G1_to_G2") or []
|
|
688
|
+
overlap_seconds += perf_counter() - overlap_started
|
|
689
|
+
if not mappings:
|
|
690
|
+
continue
|
|
691
|
+
|
|
692
|
+
if self.verified_mode:
|
|
693
|
+
completed_mappings: List[Dict[Any, Any]] = []
|
|
694
|
+
for mapping in mappings:
|
|
695
|
+
completed_mappings.extend(
|
|
696
|
+
self._complete_typed_wildcard_ports(
|
|
697
|
+
fw,
|
|
698
|
+
bw,
|
|
699
|
+
mapping,
|
|
700
|
+
maximum_only=(
|
|
701
|
+
self._active_search_policy.overlap_scope
|
|
702
|
+
is not OverlapScope.ALL_TYPED
|
|
703
|
+
),
|
|
704
|
+
)
|
|
705
|
+
)
|
|
706
|
+
mappings = completed_mappings
|
|
707
|
+
|
|
708
|
+
mapping_count = len(mappings)
|
|
709
|
+
if self.max_mappings_per_pair > 0:
|
|
710
|
+
mappings = mappings[: self.max_mappings_per_pair]
|
|
711
|
+
n_mappings_truncated += mapping_count - len(mappings)
|
|
712
|
+
|
|
713
|
+
for i_map, mapping in enumerate(mappings):
|
|
714
|
+
n_mappings_total += 1
|
|
715
|
+
role_validation = validate_wildcard_mapping_roles(
|
|
716
|
+
fw,
|
|
717
|
+
bw,
|
|
718
|
+
mapping,
|
|
719
|
+
element_key=self.element_key,
|
|
720
|
+
wildcard_element=self.wildcard_element,
|
|
721
|
+
)
|
|
722
|
+
if not role_validation.valid:
|
|
723
|
+
n_mappings_rejected += 1
|
|
724
|
+
payload = role_validation.to_dict()
|
|
725
|
+
payload.update(
|
|
726
|
+
{
|
|
727
|
+
"source": "wildcard_mapping",
|
|
728
|
+
"pair_index": (i_fw, i_bw),
|
|
729
|
+
"mapping_index": i_map,
|
|
730
|
+
}
|
|
731
|
+
)
|
|
732
|
+
self._diagnostics["fusion"].append(payload)
|
|
733
|
+
continue
|
|
734
|
+
diagnostic_context = {
|
|
735
|
+
"pair_index": (i_fw, i_bw),
|
|
736
|
+
"mapping_index": i_map,
|
|
737
|
+
}
|
|
738
|
+
construction_started = perf_counter()
|
|
739
|
+
try:
|
|
740
|
+
interface = FusionInterface.from_mapping(
|
|
741
|
+
fw,
|
|
742
|
+
bw,
|
|
743
|
+
mapping,
|
|
744
|
+
node_keys=self.interface_node_attrs,
|
|
745
|
+
edge_keys=self.interface_edge_attrs,
|
|
746
|
+
element_key=self.element_key,
|
|
747
|
+
wildcard_element=self.wildcard_element,
|
|
748
|
+
)
|
|
749
|
+
proof_construction = construct_pushout(
|
|
750
|
+
fw,
|
|
751
|
+
bw,
|
|
752
|
+
interface,
|
|
753
|
+
node_keys=self.interface_node_attrs,
|
|
754
|
+
edge_keys=self.interface_edge_attrs,
|
|
755
|
+
element_key=self.element_key,
|
|
756
|
+
wildcard_element=self.wildcard_element,
|
|
757
|
+
)
|
|
758
|
+
except FusionInterfaceError as exc:
|
|
759
|
+
n_mappings_rejected += 1
|
|
760
|
+
self._record_fusion_failure(
|
|
761
|
+
FusionIssueCode.INTERFACE_INVALID,
|
|
762
|
+
"The proposed Sprint 15 fusion interface is incompatible.",
|
|
763
|
+
source="verified_interface",
|
|
764
|
+
context={
|
|
765
|
+
**diagnostic_context,
|
|
766
|
+
"issues": [issue.to_dict() for issue in exc.issues],
|
|
767
|
+
},
|
|
768
|
+
)
|
|
769
|
+
continue
|
|
770
|
+
except FusionConstructionError as exc:
|
|
771
|
+
n_mappings_rejected += 1
|
|
772
|
+
self._record_fusion_failure(
|
|
773
|
+
FusionIssueCode.CONSTRUCTION_INVALID,
|
|
774
|
+
"The Sprint 15 pushout audit rejected the mapping.",
|
|
775
|
+
source="verified_construction",
|
|
776
|
+
context={
|
|
777
|
+
**diagnostic_context,
|
|
778
|
+
"issues": [issue.to_dict() for issue in exc.issues],
|
|
779
|
+
},
|
|
780
|
+
)
|
|
781
|
+
continue
|
|
782
|
+
finally:
|
|
783
|
+
construction_seconds += perf_counter() - construction_started
|
|
784
|
+
if self.fusion_backend == "categorical_pushout":
|
|
785
|
+
fused_graph = proof_construction.graph.copy()
|
|
786
|
+
else:
|
|
787
|
+
# Compatibility backend. Its output is accepted only as
|
|
788
|
+
# a plain result unless it later proves exactly equivalent
|
|
789
|
+
# to the audited pushout graph.
|
|
790
|
+
try:
|
|
791
|
+
try:
|
|
792
|
+
fused_graph = self.fuse_fn(
|
|
793
|
+
fw,
|
|
794
|
+
bw,
|
|
795
|
+
mapping,
|
|
796
|
+
remove_wildcards=True,
|
|
797
|
+
)
|
|
798
|
+
except TypeError:
|
|
799
|
+
fused_graph = self.fuse_fn( # type: ignore[arg-type]
|
|
800
|
+
fw,
|
|
801
|
+
bw,
|
|
802
|
+
mapping,
|
|
803
|
+
)
|
|
804
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
805
|
+
n_mappings_rejected += 1
|
|
806
|
+
n_operational_failures += 1
|
|
807
|
+
self._record_fusion_failure(
|
|
808
|
+
FusionIssueCode.OPERATION_FAILED,
|
|
809
|
+
"Legacy ITS graph fusion failed for the mapping.",
|
|
810
|
+
source="construction",
|
|
811
|
+
context={
|
|
812
|
+
**diagnostic_context,
|
|
813
|
+
"error": type(exc).__name__,
|
|
814
|
+
},
|
|
815
|
+
)
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
if self.verified_mode:
|
|
819
|
+
fused_graph = self._normalize_duplicate_atom_maps(fused_graph)
|
|
820
|
+
|
|
821
|
+
# The base implementation populates these caches, while a
|
|
822
|
+
# subclass override safely falls back to reparsing/revalidating.
|
|
823
|
+
# Clear them here so an override can never inherit evidence
|
|
824
|
+
# from the preceding mapping.
|
|
825
|
+
self._latest_postprocessed_its = None
|
|
826
|
+
self._latest_output_validation = None
|
|
827
|
+
diagnostics_before = len(self._diagnostics["fusion"])
|
|
828
|
+
postprocess_started = perf_counter()
|
|
829
|
+
rsmi_final = self._postprocess_single(
|
|
830
|
+
fused_graph,
|
|
831
|
+
replace_wc=replace_wc,
|
|
832
|
+
rw_adder=rw_adder,
|
|
833
|
+
diagnostic_context=diagnostic_context,
|
|
834
|
+
)
|
|
835
|
+
postprocess_seconds += perf_counter() - postprocess_started
|
|
836
|
+
if rsmi_final is None:
|
|
837
|
+
n_mappings_rejected += 1
|
|
838
|
+
n_postprocess_rejections += 1
|
|
839
|
+
new_diagnostics = self._diagnostics["fusion"][
|
|
840
|
+
diagnostics_before:
|
|
841
|
+
]
|
|
842
|
+
if any(
|
|
843
|
+
issue.get("code")
|
|
844
|
+
in {
|
|
845
|
+
FusionIssueCode.OPERATION_FAILED.value,
|
|
846
|
+
}
|
|
847
|
+
for report in new_diagnostics
|
|
848
|
+
for issue in report.get("issues", ())
|
|
849
|
+
):
|
|
850
|
+
n_operational_failures += 1
|
|
851
|
+
continue
|
|
852
|
+
final_graph = self._latest_postprocessed_its
|
|
853
|
+
if not isinstance(final_graph, nx.Graph):
|
|
854
|
+
final_graph = self._safe_rsmi_to_its(rsmi_final)
|
|
855
|
+
if not isinstance(final_graph, nx.Graph):
|
|
856
|
+
n_mappings_rejected += 1
|
|
857
|
+
n_operational_failures += 1
|
|
858
|
+
self._record_fusion_failure(
|
|
859
|
+
FusionIssueCode.PROOF_FAILED,
|
|
860
|
+
"The accepted serialization could not be restored as an ITS graph.",
|
|
861
|
+
source="verified_proof",
|
|
862
|
+
context=diagnostic_context,
|
|
863
|
+
)
|
|
864
|
+
continue
|
|
865
|
+
postprocess_proof = certify_fusion_postprocessing(
|
|
866
|
+
proof_construction.graph,
|
|
867
|
+
final_graph,
|
|
868
|
+
materialize_hydrogen=replace_wc,
|
|
869
|
+
element_key=self.element_key,
|
|
870
|
+
wildcard_element=self.wildcard_element,
|
|
871
|
+
)
|
|
872
|
+
proof_equivalent = postprocess_proof.valid
|
|
873
|
+
if not proof_equivalent:
|
|
874
|
+
self._record_fusion_failure(
|
|
875
|
+
FusionIssueCode.PROOF_FAILED,
|
|
876
|
+
"Post-processing changed the graph certified by the pushout "
|
|
877
|
+
"outside the audited normalization contract.",
|
|
878
|
+
source="verified_proof",
|
|
879
|
+
context={
|
|
880
|
+
**diagnostic_context,
|
|
881
|
+
"issues": [
|
|
882
|
+
issue.to_dict() for issue in postprocess_proof.issues
|
|
883
|
+
],
|
|
884
|
+
},
|
|
885
|
+
)
|
|
886
|
+
if self.verified_mode:
|
|
887
|
+
n_mappings_rejected += 1
|
|
888
|
+
continue
|
|
889
|
+
proof_validation = self._latest_output_validation
|
|
890
|
+
if proof_validation is None:
|
|
891
|
+
if (
|
|
892
|
+
self._last_reaction is not None
|
|
893
|
+
and self._active_search_policy.acceptance_task
|
|
894
|
+
is AcceptanceTask.STRICT_RECONSTRUCTION
|
|
895
|
+
):
|
|
896
|
+
proof_validation = validate_strict_rbl_candidate(
|
|
897
|
+
self._last_reaction,
|
|
898
|
+
rsmi_final,
|
|
899
|
+
allow_wildcards=not replace_wc,
|
|
900
|
+
boundary=self.conservation_boundary,
|
|
901
|
+
environment_delta=self.environment_delta,
|
|
902
|
+
)
|
|
903
|
+
elif self._last_reaction is not None:
|
|
904
|
+
proof_validation = validate_rbl_candidate(
|
|
905
|
+
self._last_reaction,
|
|
906
|
+
rsmi_final,
|
|
907
|
+
allow_wildcards=not replace_wc,
|
|
908
|
+
preserve_sides=self.preserve_original_sides,
|
|
909
|
+
)
|
|
910
|
+
else:
|
|
911
|
+
proof_validation = validate_fusion_rsmi(
|
|
912
|
+
rsmi_final,
|
|
913
|
+
allow_wildcards=not replace_wc,
|
|
914
|
+
)
|
|
915
|
+
final_digest = graph_identity_digest(final_graph)
|
|
916
|
+
identity_bucket = accepted_graph_buckets.setdefault(
|
|
917
|
+
final_digest,
|
|
918
|
+
[],
|
|
919
|
+
)
|
|
920
|
+
duplicate_outcome = any(
|
|
921
|
+
graphs_exactly_equivalent(final_graph, previous)
|
|
922
|
+
for previous in identity_bucket
|
|
923
|
+
)
|
|
924
|
+
if duplicate_outcome:
|
|
925
|
+
n_candidates_deduplicated += 1
|
|
926
|
+
if proof_equivalent:
|
|
927
|
+
validation_payload = proof_validation.to_dict()
|
|
928
|
+
validation_payload["evidence"] = {
|
|
929
|
+
**validation_payload.get("evidence", {}),
|
|
930
|
+
**postprocess_proof.evidence,
|
|
931
|
+
}
|
|
932
|
+
replay_certificate = None
|
|
933
|
+
if (
|
|
934
|
+
self._active_search_policy.proof_level
|
|
935
|
+
is ProofLevel.REPLAYABLE
|
|
936
|
+
):
|
|
937
|
+
try:
|
|
938
|
+
replay_certificate = RBLReplayCertificate.create(
|
|
939
|
+
original_rsmi=self._last_reaction or rsmi_final,
|
|
940
|
+
forward=fw,
|
|
941
|
+
backward=bw,
|
|
942
|
+
mapping=mapping,
|
|
943
|
+
pushout=proof_construction.graph,
|
|
944
|
+
final_graph=final_graph,
|
|
945
|
+
final_rsmi=rsmi_final,
|
|
946
|
+
materialize_hydrogen=replace_wc,
|
|
947
|
+
acceptance_task=(
|
|
948
|
+
self._active_search_policy.acceptance_task.value
|
|
949
|
+
),
|
|
950
|
+
preserve_sides=self.preserve_original_sides,
|
|
951
|
+
conservation_boundary=self.conservation_boundary,
|
|
952
|
+
environment_delta=self.environment_delta,
|
|
953
|
+
wildcard_element=self.wildcard_element,
|
|
954
|
+
node_keys=tuple(self.interface_node_attrs),
|
|
955
|
+
edge_keys=tuple(self.interface_edge_attrs),
|
|
956
|
+
)
|
|
957
|
+
except (TypeError, ValueError) as error:
|
|
958
|
+
n_operational_failures += 1
|
|
959
|
+
self._record_fusion_failure(
|
|
960
|
+
FusionIssueCode.PROOF_FAILED,
|
|
961
|
+
"The end-to-end RBL certificate did not replay.",
|
|
962
|
+
source="rbl_proof",
|
|
963
|
+
context={
|
|
964
|
+
**diagnostic_context,
|
|
965
|
+
"error": str(error),
|
|
966
|
+
},
|
|
967
|
+
)
|
|
968
|
+
n_mappings_rejected += 1
|
|
969
|
+
continue
|
|
970
|
+
candidate = fusion_candidate_from_construction(
|
|
971
|
+
proof_construction,
|
|
972
|
+
rsmi=rsmi_final,
|
|
973
|
+
validation=(validation_payload,),
|
|
974
|
+
graph=final_graph,
|
|
975
|
+
)
|
|
976
|
+
bucket = candidate_buckets.setdefault(
|
|
977
|
+
candidate.canonical_signature,
|
|
978
|
+
[],
|
|
979
|
+
)
|
|
980
|
+
if not any(
|
|
981
|
+
fusion_candidates_exactly_equivalent(candidate, previous)
|
|
982
|
+
for previous in bucket
|
|
983
|
+
):
|
|
984
|
+
self._fusion_candidates.append(candidate)
|
|
985
|
+
bucket.append(candidate)
|
|
986
|
+
if replay_certificate is not None:
|
|
987
|
+
replay_payload = replay_certificate.to_dict()
|
|
988
|
+
replay_digest = replay_payload["document_digest"]
|
|
989
|
+
if replay_digest not in self._rbl_proof_digests:
|
|
990
|
+
self._rbl_proof_digests.add(replay_digest)
|
|
991
|
+
self._rbl_proofs.append(replay_certificate)
|
|
992
|
+
if duplicate_outcome:
|
|
993
|
+
continue
|
|
994
|
+
identity_bucket.append(final_graph)
|
|
995
|
+
fused_graphs.append(final_graph)
|
|
996
|
+
fused_rsmis.append(rsmi_final)
|
|
997
|
+
if early_stop:
|
|
998
|
+
self.logger.debug(
|
|
999
|
+
"Early-stop: first successful fused RSMI "
|
|
1000
|
+
"at pair=(%d,%d), mapping_index=%d",
|
|
1001
|
+
i_fw,
|
|
1002
|
+
i_bw,
|
|
1003
|
+
i_map,
|
|
1004
|
+
)
|
|
1005
|
+
self._fused_its = fused_graphs
|
|
1006
|
+
self._fused_rsmis = fused_rsmis
|
|
1007
|
+
self._fusion_search_stats = {
|
|
1008
|
+
"complete": False,
|
|
1009
|
+
"complete_within_mapping_scope": False,
|
|
1010
|
+
"termination": "first_valid",
|
|
1011
|
+
"pairs_explored": n_pairs,
|
|
1012
|
+
"mappings_explored": n_mappings_total,
|
|
1013
|
+
"mappings_rejected": n_mappings_rejected,
|
|
1014
|
+
"operational_failures": n_operational_failures,
|
|
1015
|
+
"postprocess_rejections": n_postprocess_rejections,
|
|
1016
|
+
"candidates_valid": len(fused_rsmis),
|
|
1017
|
+
"direct_candidates_valid": len(initial_rsmis),
|
|
1018
|
+
"fusion_candidates_valid": (
|
|
1019
|
+
len(fused_rsmis) - len(initial_rsmis)
|
|
1020
|
+
),
|
|
1021
|
+
"proof_candidates": len(self._fusion_candidates),
|
|
1022
|
+
"candidates_deduplicated": n_candidates_deduplicated,
|
|
1023
|
+
"mappings_truncated": n_mappings_truncated,
|
|
1024
|
+
"pair_candidates": pair_candidate_count,
|
|
1025
|
+
"pairs_truncated": n_pairs_truncated,
|
|
1026
|
+
"component_matching": self.component_matching,
|
|
1027
|
+
"mapping_scope": self._active_search_policy.overlap_scope.value,
|
|
1028
|
+
"pair_ordering": "wl_no_cutoff",
|
|
1029
|
+
"fusion_backend": self.fusion_backend,
|
|
1030
|
+
"interface_completion": (
|
|
1031
|
+
"all_typed_leaf_port_assignments"
|
|
1032
|
+
if self._active_search_policy.overlap_scope
|
|
1033
|
+
is OverlapScope.ALL_TYPED
|
|
1034
|
+
else (
|
|
1035
|
+
"maximum_typed_leaf_ports"
|
|
1036
|
+
if self.verified_mode
|
|
1037
|
+
else "none"
|
|
1038
|
+
)
|
|
1039
|
+
),
|
|
1040
|
+
"overlap_scope": (
|
|
1041
|
+
self._active_search_policy.overlap_scope.value
|
|
1042
|
+
),
|
|
1043
|
+
"globally_complete_over_all_overlaps": False,
|
|
1044
|
+
"incomplete_reasons": incomplete_reasons(first_valid=True),
|
|
1045
|
+
"overlap_certificates": overlap_certificates,
|
|
1046
|
+
"timings_seconds": {
|
|
1047
|
+
"pair_ordering": pair_order_seconds,
|
|
1048
|
+
"overlap_enumeration": overlap_seconds,
|
|
1049
|
+
"construction": construction_seconds,
|
|
1050
|
+
"postprocessing": postprocess_seconds,
|
|
1051
|
+
},
|
|
1052
|
+
}
|
|
1053
|
+
self._record_stop(
|
|
1054
|
+
mode="early_stop",
|
|
1055
|
+
reason="early_stop_first_valid",
|
|
1056
|
+
metadata={
|
|
1057
|
+
"n_fw": len(fw_its),
|
|
1058
|
+
"n_bw": len(bw_its),
|
|
1059
|
+
"n_pairs": n_pairs,
|
|
1060
|
+
"n_mappings_total": n_mappings_total,
|
|
1061
|
+
"n_fused": len(fused_graphs),
|
|
1062
|
+
"n_fused_rsmis": len(fused_rsmis),
|
|
1063
|
+
"early_stop": early_stop,
|
|
1064
|
+
"pair_index": (i_fw, i_bw),
|
|
1065
|
+
"mapping_index": i_map,
|
|
1066
|
+
},
|
|
1067
|
+
)
|
|
1068
|
+
return
|
|
1069
|
+
|
|
1070
|
+
# No early-stop taken: finalise results
|
|
1071
|
+
ranked_outputs = sorted(
|
|
1072
|
+
zip(fused_graphs, fused_rsmis, strict=True),
|
|
1073
|
+
key=lambda record: record[1],
|
|
1074
|
+
)
|
|
1075
|
+
self._fused_its = [record[0] for record in ranked_outputs]
|
|
1076
|
+
self._fused_rsmis = [record[1] for record in ranked_outputs]
|
|
1077
|
+
self._fusion_candidates.sort(
|
|
1078
|
+
key=lambda candidate: (
|
|
1079
|
+
candidate.score.ranking_key if candidate.score is not None else (),
|
|
1080
|
+
candidate.canonical_signature,
|
|
1081
|
+
candidate.proof_digest,
|
|
1082
|
+
)
|
|
1083
|
+
)
|
|
1084
|
+
all_typed_scope = (
|
|
1085
|
+
self._active_search_policy.overlap_scope is OverlapScope.ALL_TYPED
|
|
1086
|
+
)
|
|
1087
|
+
mapping_scope_complete = (
|
|
1088
|
+
n_pairs_truncated == 0
|
|
1089
|
+
and n_mappings_truncated == 0
|
|
1090
|
+
and not self.prune_automorphisms
|
|
1091
|
+
and not self.component_matching
|
|
1092
|
+
and overlap_search_incomplete == 0
|
|
1093
|
+
and n_operational_failures == 0
|
|
1094
|
+
and (all_typed_scope or self.matcher_cls is MCSMatcher)
|
|
1095
|
+
)
|
|
1096
|
+
global_overlap_complete = mapping_scope_complete and all_typed_scope
|
|
1097
|
+
self._fusion_search_stats = {
|
|
1098
|
+
"complete": global_overlap_complete,
|
|
1099
|
+
"complete_within_mapping_scope": mapping_scope_complete,
|
|
1100
|
+
"termination": (
|
|
1101
|
+
"mapping_scope_exhausted" if mapping_scope_complete else "bounded"
|
|
1102
|
+
),
|
|
1103
|
+
"pairs_explored": n_pairs,
|
|
1104
|
+
"mappings_explored": n_mappings_total,
|
|
1105
|
+
"mappings_rejected": n_mappings_rejected,
|
|
1106
|
+
"operational_failures": n_operational_failures,
|
|
1107
|
+
"postprocess_rejections": n_postprocess_rejections,
|
|
1108
|
+
"candidates_valid": len(self._fused_rsmis),
|
|
1109
|
+
"direct_candidates_valid": len(initial_rsmis),
|
|
1110
|
+
"fusion_candidates_valid": len(self._fused_rsmis) - len(initial_rsmis),
|
|
1111
|
+
"proof_candidates": len(self._fusion_candidates),
|
|
1112
|
+
"candidates_deduplicated": n_candidates_deduplicated,
|
|
1113
|
+
"mappings_truncated": n_mappings_truncated,
|
|
1114
|
+
"automorphism_pruned": self.prune_automorphisms,
|
|
1115
|
+
"pair_candidates": pair_candidate_count,
|
|
1116
|
+
"pairs_truncated": n_pairs_truncated,
|
|
1117
|
+
"component_matching": self.component_matching,
|
|
1118
|
+
"mapping_scope": self._active_search_policy.overlap_scope.value,
|
|
1119
|
+
"pair_ordering": "wl_no_cutoff",
|
|
1120
|
+
"fusion_backend": self.fusion_backend,
|
|
1121
|
+
"interface_completion": (
|
|
1122
|
+
"all_typed_leaf_port_assignments"
|
|
1123
|
+
if all_typed_scope
|
|
1124
|
+
else ("maximum_typed_leaf_ports" if self.verified_mode else "none")
|
|
1125
|
+
),
|
|
1126
|
+
"overlap_scope": self._active_search_policy.overlap_scope.value,
|
|
1127
|
+
"globally_complete_over_all_overlaps": global_overlap_complete,
|
|
1128
|
+
"incomplete_reasons": incomplete_reasons(first_valid=False),
|
|
1129
|
+
"overlap_certificates": overlap_certificates,
|
|
1130
|
+
"timings_seconds": {
|
|
1131
|
+
"pair_ordering": pair_order_seconds,
|
|
1132
|
+
"overlap_enumeration": overlap_seconds,
|
|
1133
|
+
"construction": construction_seconds,
|
|
1134
|
+
"postprocessing": postprocess_seconds,
|
|
1135
|
+
},
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if not fused_graphs:
|
|
1139
|
+
self._record_stop(
|
|
1140
|
+
mode="full_pipeline",
|
|
1141
|
+
reason="no_fused_its",
|
|
1142
|
+
metadata={
|
|
1143
|
+
"n_fw": len(fw_its),
|
|
1144
|
+
"n_bw": len(bw_its),
|
|
1145
|
+
"n_pairs": n_pairs,
|
|
1146
|
+
"n_mappings_total": n_mappings_total,
|
|
1147
|
+
"early_stop": early_stop,
|
|
1148
|
+
},
|
|
1149
|
+
)
|
|
1150
|
+
elif not fused_rsmis:
|
|
1151
|
+
self._record_stop(
|
|
1152
|
+
mode="full_pipeline",
|
|
1153
|
+
reason="postprocessing_failed",
|
|
1154
|
+
metadata={
|
|
1155
|
+
"n_fw": len(fw_its),
|
|
1156
|
+
"n_bw": len(bw_its),
|
|
1157
|
+
"n_pairs": n_pairs,
|
|
1158
|
+
"n_mappings_total": n_mappings_total,
|
|
1159
|
+
"n_fused": len(fused_graphs),
|
|
1160
|
+
"n_fused_rsmis": 0,
|
|
1161
|
+
"early_stop": early_stop,
|
|
1162
|
+
},
|
|
1163
|
+
)
|
|
1164
|
+
else:
|
|
1165
|
+
self._record_stop(
|
|
1166
|
+
mode="full_pipeline",
|
|
1167
|
+
reason="fused_its_completed",
|
|
1168
|
+
metadata={
|
|
1169
|
+
"n_fw": len(fw_its),
|
|
1170
|
+
"n_bw": len(bw_its),
|
|
1171
|
+
"n_pairs": n_pairs,
|
|
1172
|
+
"n_mappings_total": n_mappings_total,
|
|
1173
|
+
"n_fused": len(fused_graphs),
|
|
1174
|
+
"n_fused_rsmis": len(fused_rsmis),
|
|
1175
|
+
"early_stop": early_stop,
|
|
1176
|
+
},
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
# ------------------------------------------------------------------
|
|
1180
|
+
# Post-processing helper
|
|
1181
|
+
# ------------------------------------------------------------------
|
|
1182
|
+
|
|
1183
|
+
# ------------------------------------------------------------------
|
|
1184
|
+
# Convenience / diagnostics
|
|
1185
|
+
# ------------------------------------------------------------------
|
|
1186
|
+
|
|
1187
|
+
def help(self) -> str:
|
|
1188
|
+
"""Return a short textual description of the current engine state.
|
|
1189
|
+
|
|
1190
|
+
Useful for quick inspection in interactive sessions.
|
|
1191
|
+
|
|
1192
|
+
:return: Multi-line human-readable summary string.
|
|
1193
|
+
:rtype: str
|
|
1194
|
+
"""
|
|
1195
|
+
template_ready = self._template_its is not None
|
|
1196
|
+
return (
|
|
1197
|
+
"RBLEngine configuration\n"
|
|
1198
|
+
f" wildcard_element : {self.wildcard_element!r}\n"
|
|
1199
|
+
f" element_key : {self.element_key!r}\n"
|
|
1200
|
+
f" node_attrs : {self.node_attrs!r}\n"
|
|
1201
|
+
f" edge_attrs : {self.edge_attrs!r}\n"
|
|
1202
|
+
f" prune_wc : {self.prune_wc}\n"
|
|
1203
|
+
f" prune_autos : {self.prune_automorphisms}\n"
|
|
1204
|
+
f" mcs_side : {self.mcs_side!r}\n"
|
|
1205
|
+
f" early_stop : {self.early_stop}\n"
|
|
1206
|
+
f" fast_paths_only : {self.fast_paths_only}\n"
|
|
1207
|
+
f" search_policy : {self.search_policy.to_dict()!r}\n"
|
|
1208
|
+
f" max_maps/pair : {self.max_mappings_per_pair}\n"
|
|
1209
|
+
f" max_pairs : {self.max_pairs!r}\n"
|
|
1210
|
+
f" component_matching : {self.component_matching}\n"
|
|
1211
|
+
f" fusion_backend : {self.fusion_backend!r}\n"
|
|
1212
|
+
f" implicit_temp : {self.implicit_temp}\n"
|
|
1213
|
+
f" explicit_h : {self.explicit_h}\n"
|
|
1214
|
+
f" preserve_sides : {self.preserve_original_sides!r}\n"
|
|
1215
|
+
f" embed_threshold : {self.embed_threshold}\n"
|
|
1216
|
+
f" reactor_cls : {self.reactor_cls.__name__}\n"
|
|
1217
|
+
f" matcher_cls : {self.matcher_cls.__name__}\n"
|
|
1218
|
+
f" template_ready : {template_ready}\n"
|
|
1219
|
+
f" last_reaction : {self._last_reaction!r}\n"
|
|
1220
|
+
f" #fw_its : {len(self._forward_its)}\n"
|
|
1221
|
+
f" #bw_its : {len(self._backward_its)}\n"
|
|
1222
|
+
f" #fused_its : {len(self._fused_its)}\n"
|
|
1223
|
+
f" #fused_rsmis : {len(self._fused_rsmis)}\n"
|
|
1224
|
+
f" result_mode : {self._last_stop_mode!r}\n"
|
|
1225
|
+
f" result_reason : {self._last_stop_reason!r}"
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
def __repr__(self) -> str:
|
|
1229
|
+
"""Return a concise summary representation of the engine.
|
|
1230
|
+
|
|
1231
|
+
:return: One-line representation string.
|
|
1232
|
+
:rtype: str
|
|
1233
|
+
"""
|
|
1234
|
+
return (
|
|
1235
|
+
f"<RBLEngine wildcard_element={self.wildcard_element!r} "
|
|
1236
|
+
f"node_attrs={self.node_attrs!r} edge_attrs={self.edge_attrs!r} "
|
|
1237
|
+
f"prune_wc={self.prune_wc} "
|
|
1238
|
+
f"prune_automorphisms={self.prune_automorphisms} "
|
|
1239
|
+
f"mcs_side={self.mcs_side!r} "
|
|
1240
|
+
f"early_stop={self.early_stop} "
|
|
1241
|
+
f"fast_paths_only={self.fast_paths_only} "
|
|
1242
|
+
f"search_policy={self.search_policy.to_dict()!r} "
|
|
1243
|
+
f"max_mappings_per_pair={self.max_mappings_per_pair} "
|
|
1244
|
+
f"implicit_temp={self.implicit_temp} "
|
|
1245
|
+
f"explicit_h={self.explicit_h} "
|
|
1246
|
+
f"preserve_original_sides={self.preserve_original_sides!r} "
|
|
1247
|
+
f"embed_threshold={self.embed_threshold} "
|
|
1248
|
+
f"reactor_cls={self.reactor_cls.__name__}>"
|
|
1249
|
+
)
|