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,652 @@
|
|
|
1
|
+
"""Wildcard handling and fast matching for the RBL engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
|
|
9
|
+
from synkit.Graph.Fusion import graphs_exactly_equivalent
|
|
10
|
+
from synkit.Rule.syn_rule import SynRule
|
|
11
|
+
from synkit.Synthesis.RBL.validation import WildcardRole
|
|
12
|
+
|
|
13
|
+
ITSLike = Any
|
|
14
|
+
Port = tuple[str, Any, Any, tuple[Any, ...]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _typed_leaf(
|
|
18
|
+
graph: nx.Graph,
|
|
19
|
+
node: Any,
|
|
20
|
+
*,
|
|
21
|
+
element_key: str,
|
|
22
|
+
wildcard_values: tuple[Any, Any],
|
|
23
|
+
) -> bool:
|
|
24
|
+
data = graph.nodes[node]
|
|
25
|
+
neighbours = list(graph.neighbors(node))
|
|
26
|
+
return (
|
|
27
|
+
data.get(element_key) in wildcard_values
|
|
28
|
+
and data.get("wildcard_role")
|
|
29
|
+
in {
|
|
30
|
+
WildcardRole.ATTACHMENT_PORT.value,
|
|
31
|
+
WildcardRole.RADICAL_COMPLETION.value,
|
|
32
|
+
}
|
|
33
|
+
and len(neighbours) == 1
|
|
34
|
+
and data.get("owner") == neighbours[0]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _edge_signature(
|
|
39
|
+
graph: nx.Graph,
|
|
40
|
+
left: Any,
|
|
41
|
+
right: Any,
|
|
42
|
+
edge_attrs: Sequence[str],
|
|
43
|
+
) -> tuple[Any, ...]:
|
|
44
|
+
attributes = graph.edges[left, right]
|
|
45
|
+
return tuple(attributes.get(key) for key in edge_attrs)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _collect_ports(
|
|
49
|
+
forward: nx.Graph,
|
|
50
|
+
backward: nx.Graph,
|
|
51
|
+
base: Mapping[Any, Any],
|
|
52
|
+
*,
|
|
53
|
+
element_key: str,
|
|
54
|
+
wildcard_values: tuple[Any, Any],
|
|
55
|
+
edge_attrs: Sequence[str],
|
|
56
|
+
) -> list[Port]:
|
|
57
|
+
"""Collect typed leaves whose owner already belongs to the overlap."""
|
|
58
|
+
ports: list[Port] = []
|
|
59
|
+
for direction, graph, used, owners in (
|
|
60
|
+
("forward", forward, set(base), base),
|
|
61
|
+
(
|
|
62
|
+
"backward",
|
|
63
|
+
backward,
|
|
64
|
+
set(base.values()),
|
|
65
|
+
{right: left for left, right in base.items()},
|
|
66
|
+
),
|
|
67
|
+
):
|
|
68
|
+
for port in sorted(graph.nodes, key=repr):
|
|
69
|
+
if port in used or not _typed_leaf(
|
|
70
|
+
graph,
|
|
71
|
+
port,
|
|
72
|
+
element_key=element_key,
|
|
73
|
+
wildcard_values=wildcard_values,
|
|
74
|
+
):
|
|
75
|
+
continue
|
|
76
|
+
owner = next(iter(graph.neighbors(port)))
|
|
77
|
+
if owner in owners:
|
|
78
|
+
ports.append(
|
|
79
|
+
(
|
|
80
|
+
direction,
|
|
81
|
+
port,
|
|
82
|
+
owner,
|
|
83
|
+
_edge_signature(graph, port, owner, edge_attrs),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
return ports
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _port_extensions(
|
|
90
|
+
port_info: Port,
|
|
91
|
+
current: Mapping[Any, Any],
|
|
92
|
+
forward: nx.Graph,
|
|
93
|
+
backward: nx.Graph,
|
|
94
|
+
*,
|
|
95
|
+
element_key: str,
|
|
96
|
+
wildcard_values: tuple[Any, Any],
|
|
97
|
+
edge_attrs: Sequence[str],
|
|
98
|
+
) -> list[tuple[Any, Any]]:
|
|
99
|
+
"""Return injective left-to-right pairs that can resolve one port."""
|
|
100
|
+
direction, port, owner, signature = port_info
|
|
101
|
+
if direction == "forward":
|
|
102
|
+
source, opposite = backward, current.get(owner)
|
|
103
|
+
used = set(current.values())
|
|
104
|
+
else:
|
|
105
|
+
source = forward
|
|
106
|
+
opposite = {right: left for left, right in current.items()}.get(owner)
|
|
107
|
+
used = set(current)
|
|
108
|
+
if opposite is None:
|
|
109
|
+
return []
|
|
110
|
+
candidates = [
|
|
111
|
+
node
|
|
112
|
+
for node in source.neighbors(opposite)
|
|
113
|
+
if node not in used
|
|
114
|
+
and source.nodes[node].get(element_key) not in wildcard_values
|
|
115
|
+
and _edge_signature(source, opposite, node, edge_attrs) == signature
|
|
116
|
+
]
|
|
117
|
+
ordered = sorted(candidates, key=repr)
|
|
118
|
+
if direction == "forward":
|
|
119
|
+
return [(port, candidate) for candidate in ordered]
|
|
120
|
+
return [(candidate, port) for candidate in ordered]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _port_assignment_edges(
|
|
124
|
+
ports: Sequence[Port],
|
|
125
|
+
base: Mapping[Any, Any],
|
|
126
|
+
forward: nx.Graph,
|
|
127
|
+
backward: nx.Graph,
|
|
128
|
+
*,
|
|
129
|
+
element_key: str,
|
|
130
|
+
wildcard_values: tuple[Any, Any],
|
|
131
|
+
edge_attrs: Sequence[str],
|
|
132
|
+
) -> tuple[tuple[Any, Any], ...]:
|
|
133
|
+
"""Build the exact bipartite compatibility graph for port assignment."""
|
|
134
|
+
edges: set[tuple[Any, Any]] = set()
|
|
135
|
+
for port in ports:
|
|
136
|
+
edges.update(
|
|
137
|
+
_port_extensions(
|
|
138
|
+
port,
|
|
139
|
+
base,
|
|
140
|
+
forward,
|
|
141
|
+
backward,
|
|
142
|
+
element_key=element_key,
|
|
143
|
+
wildcard_values=wildcard_values,
|
|
144
|
+
edge_attrs=edge_attrs,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
return tuple(sorted(edges, key=lambda edge: (repr(edge[0]), repr(edge[1]))))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _maximum_matching_size(
|
|
151
|
+
choices: Mapping[Any, Sequence[Any]],
|
|
152
|
+
remaining_left: Sequence[Any],
|
|
153
|
+
used_right: set[Any],
|
|
154
|
+
) -> int:
|
|
155
|
+
graph = nx.Graph()
|
|
156
|
+
tagged_left = [("left", node) for node in remaining_left]
|
|
157
|
+
graph.add_nodes_from(tagged_left, bipartite=0)
|
|
158
|
+
for left in remaining_left:
|
|
159
|
+
for right in choices.get(left, ()):
|
|
160
|
+
if right not in used_right:
|
|
161
|
+
graph.add_node(("right", right), bipartite=1)
|
|
162
|
+
graph.add_edge(("left", left), ("right", right))
|
|
163
|
+
if not graph.edges:
|
|
164
|
+
return 0
|
|
165
|
+
matching = nx.algorithms.bipartite.maximum_matching(
|
|
166
|
+
graph,
|
|
167
|
+
top_nodes=set(tagged_left),
|
|
168
|
+
)
|
|
169
|
+
return len(matching) // 2
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _enumerate_port_matchings(
|
|
173
|
+
base: Mapping[Any, Any],
|
|
174
|
+
edges: Sequence[tuple[Any, Any]],
|
|
175
|
+
*,
|
|
176
|
+
maximum_only: bool,
|
|
177
|
+
) -> List[Dict[Any, Any]]:
|
|
178
|
+
"""Enumerate exact bipartite matchings, with optimality pruning if asked."""
|
|
179
|
+
choices: dict[Any, list[Any]] = {}
|
|
180
|
+
for left, right in edges:
|
|
181
|
+
choices.setdefault(left, []).append(right)
|
|
182
|
+
left_nodes = tuple(
|
|
183
|
+
sorted(choices, key=lambda node: (len(choices[node]), repr(node)))
|
|
184
|
+
)
|
|
185
|
+
optimum = (
|
|
186
|
+
_maximum_matching_size(choices, left_nodes, set(base.values()))
|
|
187
|
+
if maximum_only
|
|
188
|
+
else None
|
|
189
|
+
)
|
|
190
|
+
completed: List[Dict[Any, Any]] = []
|
|
191
|
+
|
|
192
|
+
def visit(
|
|
193
|
+
index: int,
|
|
194
|
+
current: Dict[Any, Any],
|
|
195
|
+
used_right: set[Any],
|
|
196
|
+
added: int,
|
|
197
|
+
) -> None:
|
|
198
|
+
if index == len(left_nodes):
|
|
199
|
+
if optimum is None or added == optimum:
|
|
200
|
+
completed.append(dict(current))
|
|
201
|
+
return
|
|
202
|
+
if optimum is not None:
|
|
203
|
+
upper = added + _maximum_matching_size(
|
|
204
|
+
choices,
|
|
205
|
+
left_nodes[index:],
|
|
206
|
+
used_right,
|
|
207
|
+
)
|
|
208
|
+
if upper < optimum:
|
|
209
|
+
return
|
|
210
|
+
left = left_nodes[index]
|
|
211
|
+
for right in choices[left]:
|
|
212
|
+
if right in used_right:
|
|
213
|
+
continue
|
|
214
|
+
current[left] = right
|
|
215
|
+
used_right.add(right)
|
|
216
|
+
visit(index + 1, current, used_right, added + 1)
|
|
217
|
+
used_right.remove(right)
|
|
218
|
+
del current[left]
|
|
219
|
+
if optimum is None or added + _maximum_matching_size(
|
|
220
|
+
choices,
|
|
221
|
+
left_nodes[index + 1 :],
|
|
222
|
+
used_right,
|
|
223
|
+
) >= optimum:
|
|
224
|
+
visit(index + 1, current, used_right, added)
|
|
225
|
+
|
|
226
|
+
visit(0, dict(base), set(base.values()), 0)
|
|
227
|
+
return _all_unique_mappings(completed)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _all_unique_mappings(
|
|
231
|
+
completed: Sequence[Dict[Any, Any]],
|
|
232
|
+
) -> List[Dict[Any, Any]]:
|
|
233
|
+
unique: Dict[tuple[tuple[str, str], ...], Dict[Any, Any]] = {}
|
|
234
|
+
for candidate in completed:
|
|
235
|
+
key = tuple(
|
|
236
|
+
sorted((repr(left), repr(right)) for left, right in candidate.items())
|
|
237
|
+
)
|
|
238
|
+
unique.setdefault(key, candidate)
|
|
239
|
+
return sorted(
|
|
240
|
+
unique.values(),
|
|
241
|
+
key=lambda mapping: (
|
|
242
|
+
-len(mapping),
|
|
243
|
+
repr(tuple(sorted(mapping.items(), key=repr))),
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class RBLMatchingMixin:
|
|
249
|
+
def _has_wildcard_nodes(self, G: ITSLike) -> bool:
|
|
250
|
+
"""Check whether an ITS graph contains any wildcard atoms.
|
|
251
|
+
|
|
252
|
+
For now this assumes a NetworkX-style graph with node attributes.
|
|
253
|
+
|
|
254
|
+
:param G: ITS graph to inspect.
|
|
255
|
+
:type G: ITSLike
|
|
256
|
+
:return: ``True`` if any node has ``element_key == wildcard_element``.
|
|
257
|
+
:rtype: bool
|
|
258
|
+
"""
|
|
259
|
+
if not isinstance(G, nx.Graph):
|
|
260
|
+
# For non-graph ITS types we conservatively return True
|
|
261
|
+
# so that the fast path is not applied.
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
wildcard = self.wildcard_element
|
|
265
|
+
scalar_wildcard = wildcard[0] if isinstance(wildcard, tuple) else wildcard
|
|
266
|
+
element_key = self.element_key
|
|
267
|
+
|
|
268
|
+
for _, data in G.nodes(data=True):
|
|
269
|
+
if data.get(element_key) in (wildcard, scalar_wildcard):
|
|
270
|
+
return True
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
def _annotate_wildcard_roles(
|
|
274
|
+
self,
|
|
275
|
+
graph: ITSLike,
|
|
276
|
+
role: WildcardRole,
|
|
277
|
+
) -> ITSLike:
|
|
278
|
+
"""Declare the role of otherwise-untyped wildcards at an RBL boundary."""
|
|
279
|
+
if not isinstance(graph, nx.Graph):
|
|
280
|
+
return graph
|
|
281
|
+
scalar_wildcard = (
|
|
282
|
+
self.wildcard_element[0]
|
|
283
|
+
if isinstance(self.wildcard_element, tuple)
|
|
284
|
+
else self.wildcard_element
|
|
285
|
+
)
|
|
286
|
+
for _, data in graph.nodes(data=True):
|
|
287
|
+
if data.get(self.element_key) in (
|
|
288
|
+
self.wildcard_element,
|
|
289
|
+
scalar_wildcard,
|
|
290
|
+
):
|
|
291
|
+
data.setdefault("wildcard_role", role.value)
|
|
292
|
+
# A degree-one radical completion is an attachment port owned by its
|
|
293
|
+
# sole neighbour. Recording that incidence lets the verified fusion
|
|
294
|
+
# interface prove any later wildcard-to-concrete substitution.
|
|
295
|
+
for node, data in graph.nodes(data=True):
|
|
296
|
+
if data.get(self.element_key) not in (
|
|
297
|
+
self.wildcard_element,
|
|
298
|
+
scalar_wildcard,
|
|
299
|
+
):
|
|
300
|
+
continue
|
|
301
|
+
neighbours = list(graph.neighbors(node))
|
|
302
|
+
if len(neighbours) == 1:
|
|
303
|
+
data.setdefault("owner", neighbours[0])
|
|
304
|
+
return graph
|
|
305
|
+
|
|
306
|
+
def _complete_typed_wildcard_ports(
|
|
307
|
+
self,
|
|
308
|
+
forward: nx.Graph,
|
|
309
|
+
backward: nx.Graph,
|
|
310
|
+
mapping: Mapping[Any, Any],
|
|
311
|
+
*,
|
|
312
|
+
maximum_only: bool = True,
|
|
313
|
+
) -> List[Dict[Any, Any]]:
|
|
314
|
+
"""Maximally extend an MCS overlap through typed leaf ports.
|
|
315
|
+
|
|
316
|
+
MCS compares concrete node labels and therefore cannot identify a
|
|
317
|
+
wildcard leaf with the concrete substituent supplied by the other
|
|
318
|
+
partial graph. For every typed degree-one wildcard whose owner is
|
|
319
|
+
already in the overlap, enumerate injective owner-adjacent concrete
|
|
320
|
+
substitutions with the same edge label. Only extensions with the
|
|
321
|
+
maximum number of resolved ports are returned; the categorical
|
|
322
|
+
interface performs the full constraint audit afterward.
|
|
323
|
+
"""
|
|
324
|
+
scalar_wildcard = (
|
|
325
|
+
self.wildcard_element[0]
|
|
326
|
+
if isinstance(self.wildcard_element, tuple)
|
|
327
|
+
else self.wildcard_element
|
|
328
|
+
)
|
|
329
|
+
wildcard_values = (self.wildcard_element, scalar_wildcard)
|
|
330
|
+
base = dict(mapping)
|
|
331
|
+
ports = _collect_ports(
|
|
332
|
+
forward,
|
|
333
|
+
backward,
|
|
334
|
+
base,
|
|
335
|
+
element_key=self.element_key,
|
|
336
|
+
wildcard_values=wildcard_values,
|
|
337
|
+
edge_attrs=self.edge_attrs,
|
|
338
|
+
)
|
|
339
|
+
if not ports:
|
|
340
|
+
return [base]
|
|
341
|
+
edges = _port_assignment_edges(
|
|
342
|
+
ports,
|
|
343
|
+
base,
|
|
344
|
+
forward,
|
|
345
|
+
backward,
|
|
346
|
+
element_key=self.element_key,
|
|
347
|
+
wildcard_values=wildcard_values,
|
|
348
|
+
edge_attrs=self.edge_attrs,
|
|
349
|
+
)
|
|
350
|
+
return _enumerate_port_matchings(
|
|
351
|
+
base,
|
|
352
|
+
edges,
|
|
353
|
+
maximum_only=maximum_only,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def replace_wildcard_with_H(self, G: nx.Graph) -> nx.Graph:
|
|
357
|
+
"""Replace wildcard atoms in an ITS graph with hydrogen.
|
|
358
|
+
|
|
359
|
+
This updates node-level attributes:
|
|
360
|
+
|
|
361
|
+
* ``node[element_key]``
|
|
362
|
+
* ``typesGH`` (if present, element field only)
|
|
363
|
+
* ``neighbors`` lists (string-based)
|
|
364
|
+
|
|
365
|
+
Edge structure and other attributes are not touched.
|
|
366
|
+
|
|
367
|
+
:param G: ITS graph to modify in-place.
|
|
368
|
+
:type G: nx.Graph
|
|
369
|
+
:return: The same graph instance, for convenience.
|
|
370
|
+
:rtype: nx.Graph
|
|
371
|
+
"""
|
|
372
|
+
wildcard = self.wildcard_element
|
|
373
|
+
scalar_wildcard = wildcard[0] if isinstance(wildcard, tuple) else wildcard
|
|
374
|
+
element_key = self.element_key
|
|
375
|
+
|
|
376
|
+
wildcard_nodes = [
|
|
377
|
+
n
|
|
378
|
+
for n, d in G.nodes(data=True)
|
|
379
|
+
if d.get(element_key) in (wildcard, scalar_wildcard)
|
|
380
|
+
]
|
|
381
|
+
if not wildcard_nodes:
|
|
382
|
+
return G
|
|
383
|
+
|
|
384
|
+
for n in wildcard_nodes:
|
|
385
|
+
data = G.nodes[n]
|
|
386
|
+
data[element_key] = (
|
|
387
|
+
("H", "H") if isinstance(data.get(element_key), tuple) else "H"
|
|
388
|
+
)
|
|
389
|
+
# The role is evidence for the one-time materialization, not a
|
|
390
|
+
# persistent atom property of the resulting hydrogen.
|
|
391
|
+
data.pop("wildcard_role", None)
|
|
392
|
+
|
|
393
|
+
if "typesGH" in data and isinstance(data["typesGH"], tuple):
|
|
394
|
+
gh1, gh2 = data["typesGH"]
|
|
395
|
+
gh1 = ("H",) + tuple(gh1[1:])
|
|
396
|
+
gh2 = ("H",) + tuple(gh2[1:])
|
|
397
|
+
data["typesGH"] = (gh1, gh2)
|
|
398
|
+
|
|
399
|
+
for _, d in G.nodes(data=True):
|
|
400
|
+
if "neighbors" not in d:
|
|
401
|
+
continue
|
|
402
|
+
neighbors = d["neighbors"]
|
|
403
|
+
if isinstance(neighbors, tuple) and len(neighbors) == 2:
|
|
404
|
+
d["neighbors"] = (
|
|
405
|
+
[("H" if x == scalar_wildcard else x) for x in neighbors[0]],
|
|
406
|
+
[("H" if x == scalar_wildcard else x) for x in neighbors[1]],
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
d["neighbors"] = [
|
|
410
|
+
("H" if x == scalar_wildcard else x) for x in neighbors
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
return G
|
|
414
|
+
|
|
415
|
+
# ------------------------------------------------------------------
|
|
416
|
+
# Matcher construction
|
|
417
|
+
# ------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
def _build_matcher(self) -> Any:
|
|
420
|
+
"""Construct a matcher instance using engine configuration.
|
|
421
|
+
|
|
422
|
+
Assumes :attr:`matcher_cls` is API-compatible with :class:`MCSMatcher`
|
|
423
|
+
or :class:`ApproxMCSMatcher`.
|
|
424
|
+
|
|
425
|
+
:return: Matcher instance.
|
|
426
|
+
:rtype: Any
|
|
427
|
+
"""
|
|
428
|
+
node_defaults: List[Any] = []
|
|
429
|
+
for attr in self.node_attrs:
|
|
430
|
+
if attr == "element":
|
|
431
|
+
node_defaults.append(self.wildcard_element)
|
|
432
|
+
elif attr == "aromatic":
|
|
433
|
+
node_defaults.append((False, False))
|
|
434
|
+
elif attr == "charge":
|
|
435
|
+
node_defaults.append((0, 0))
|
|
436
|
+
else:
|
|
437
|
+
node_defaults.append(self.wildcard_element)
|
|
438
|
+
|
|
439
|
+
matcher = self.matcher_cls(
|
|
440
|
+
node_attrs=self.node_attrs,
|
|
441
|
+
node_defaults=node_defaults,
|
|
442
|
+
edge_attrs=self.edge_attrs,
|
|
443
|
+
prune_wc=self.prune_wc,
|
|
444
|
+
prune_automorphisms=self.prune_automorphisms,
|
|
445
|
+
wildcard_element=self.wildcard_element,
|
|
446
|
+
element_key=self.element_key,
|
|
447
|
+
)
|
|
448
|
+
return matcher
|
|
449
|
+
|
|
450
|
+
# ------------------------------------------------------------------
|
|
451
|
+
# Quick-check logic (pre-pipeline early-stop)
|
|
452
|
+
# ------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
def _quick_check(
|
|
455
|
+
self,
|
|
456
|
+
rsmi: str,
|
|
457
|
+
template: Union[str, nx.Graph, SynRule, ITSLike],
|
|
458
|
+
) -> Optional[str]:
|
|
459
|
+
"""Fast pre-check used when early-stop or fast-paths-only logic is active.
|
|
460
|
+
|
|
461
|
+
Logic:
|
|
462
|
+
|
|
463
|
+
1. Canonicalize the input reaction using :attr:`standardize_fn`.
|
|
464
|
+
2. Split into reactants/products (``r``, ``p``).
|
|
465
|
+
3. Prepare the template ITS (mirroring normal preparation).
|
|
466
|
+
4. Run :class:`SynReactor` with ``partial=False``.
|
|
467
|
+
5. For each candidate solution in ``reactor.smarts``, canonicalize it
|
|
468
|
+
and check whether the product side contains the canonicalized
|
|
469
|
+
product ``p``. The first such solution is returned.
|
|
470
|
+
|
|
471
|
+
If a match is found, :meth:`_record_stop` is called with mode
|
|
472
|
+
``"quick_check"`` and the corresponding reason.
|
|
473
|
+
|
|
474
|
+
:param rsmi: Input reaction SMILES.
|
|
475
|
+
:type rsmi: str
|
|
476
|
+
:param template: Template as reaction SMILES, graph, normalized
|
|
477
|
+
:class:`SynRule`, or ITS-like value.
|
|
478
|
+
:type template: str | nx.Graph | SynRule | ITSLike
|
|
479
|
+
:return: Matching solution string or ``None`` if no match is found.
|
|
480
|
+
:rtype: Optional[str]
|
|
481
|
+
"""
|
|
482
|
+
split = self._canonical_split(rsmi)
|
|
483
|
+
if split is None:
|
|
484
|
+
return None
|
|
485
|
+
r_canon, p_canon = split
|
|
486
|
+
|
|
487
|
+
if isinstance(template, SynRule):
|
|
488
|
+
temp_its = template
|
|
489
|
+
elif isinstance(template, nx.Graph):
|
|
490
|
+
temp_its = template
|
|
491
|
+
elif isinstance(template, str):
|
|
492
|
+
temp_its = self._prepare_from_str(template)
|
|
493
|
+
else:
|
|
494
|
+
temp_its = self.standardize_h_fn(template)
|
|
495
|
+
|
|
496
|
+
reactor = self.reactor_cls(
|
|
497
|
+
r_canon,
|
|
498
|
+
temp_its,
|
|
499
|
+
partial=False,
|
|
500
|
+
implicit_temp=self.implicit_temp,
|
|
501
|
+
explicit_h=self.explicit_h,
|
|
502
|
+
automorphism=False,
|
|
503
|
+
invert=False,
|
|
504
|
+
embed_threshold=self.embed_threshold,
|
|
505
|
+
electron_diagnostics=self.electron_diagnostics,
|
|
506
|
+
)
|
|
507
|
+
self._diagnostics["quick_check"].extend(
|
|
508
|
+
getattr(reactor, "diagnostics", []) or []
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
sols: Sequence[str] = getattr(reactor, "smarts", []) or []
|
|
512
|
+
if not sols:
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
canon_sols = [self.standardize_fn(sol) for sol in sols]
|
|
516
|
+
for idx, rxn in enumerate(canon_sols):
|
|
517
|
+
split_sol = self._canonical_split(rxn)
|
|
518
|
+
if split_sol is None:
|
|
519
|
+
continue
|
|
520
|
+
_, prod = split_sol
|
|
521
|
+
if p_canon in prod:
|
|
522
|
+
self.logger.debug("Quick-check succeeded with solution index %d.", idx)
|
|
523
|
+
self._record_stop(
|
|
524
|
+
mode="quick_check",
|
|
525
|
+
reason="quick_check_match",
|
|
526
|
+
metadata={"solution_index": idx, "n_solutions": len(sols)},
|
|
527
|
+
)
|
|
528
|
+
return sols[idx]
|
|
529
|
+
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
# ------------------------------------------------------------------
|
|
533
|
+
# Early-stop pruning on wildcard-free ITS
|
|
534
|
+
# ------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
def _early_stop_on_nonwildcard(
|
|
537
|
+
self,
|
|
538
|
+
fw_its: Sequence[ITSLike],
|
|
539
|
+
bw_its: Sequence[ITSLike],
|
|
540
|
+
*,
|
|
541
|
+
replace_wc: bool,
|
|
542
|
+
stop_first: bool = True,
|
|
543
|
+
) -> bool:
|
|
544
|
+
"""Try an early-stop path based on ITS graphs that contain no wildcard
|
|
545
|
+
atoms, using the same endpoint-preservation proof as every other mode.
|
|
546
|
+
|
|
547
|
+
Rationale
|
|
548
|
+
---------
|
|
549
|
+
For many reactions, some forward or backward ITS graphs are already
|
|
550
|
+
fully resolved (i.e. they do not contain any wildcard atoms).
|
|
551
|
+
In early-stop mode, we can treat such graphs as "good enough" and
|
|
552
|
+
directly post-process them without running expensive MCS/fusion,
|
|
553
|
+
provided that the resulting reaction is consistent with the
|
|
554
|
+
original one on the appropriate side.
|
|
555
|
+
|
|
556
|
+
Strategy
|
|
557
|
+
--------
|
|
558
|
+
1. Collect all forward ITS without wildcard atoms.
|
|
559
|
+
2. Collect all backward ITS without wildcard atoms.
|
|
560
|
+
3. Iterate through these candidates (forward first, then backward),
|
|
561
|
+
and for each:
|
|
562
|
+
a. Post-process via :meth:`_postprocess_single`.
|
|
563
|
+
b. Require chemical validation and component-injective embeddings
|
|
564
|
+
of both original endpoints in the candidate endpoints.
|
|
565
|
+
c. On the first proven candidate, record an early-stop and return.
|
|
566
|
+
4. If no candidate yields a valid fused RSMI, return ``False`` and
|
|
567
|
+
fall back to full fusion (unless fast-path-only mode is active).
|
|
568
|
+
|
|
569
|
+
:param fw_its: Forward ITS graphs.
|
|
570
|
+
:type fw_its: Sequence[ITSLike]
|
|
571
|
+
:param bw_its: Backward ITS graphs.
|
|
572
|
+
:type bw_its: Sequence[ITSLike]
|
|
573
|
+
:param replace_wc: Whether to replace wildcard atoms with H during
|
|
574
|
+
post-processing.
|
|
575
|
+
:type replace_wc: bool
|
|
576
|
+
:param stop_first: Return immediately after the first accepted direct
|
|
577
|
+
candidate. When false, collect every distinct direct candidate
|
|
578
|
+
for a wider search.
|
|
579
|
+
:type stop_first: bool
|
|
580
|
+
:return: ``True`` if a non-wildcard solution was found, ``False``
|
|
581
|
+
otherwise.
|
|
582
|
+
:rtype: bool
|
|
583
|
+
"""
|
|
584
|
+
if (not fw_its and not bw_its) or self._last_reaction is None:
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
candidates: List[tuple[str, ITSLike]] = []
|
|
588
|
+
for g in fw_its:
|
|
589
|
+
if not self._has_wildcard_nodes(g):
|
|
590
|
+
candidates.append(("fw", g))
|
|
591
|
+
for g in bw_its:
|
|
592
|
+
if not self._has_wildcard_nodes(g):
|
|
593
|
+
candidates.append(("bw", g))
|
|
594
|
+
|
|
595
|
+
if not candidates:
|
|
596
|
+
return False
|
|
597
|
+
|
|
598
|
+
rw_adder = self.wildcard_adder_cls()
|
|
599
|
+
fused_graphs: List[ITSLike] = list(self._fused_its)
|
|
600
|
+
fused_rsmis: List[str] = list(self._fused_rsmis)
|
|
601
|
+
found_nonwildcard = False
|
|
602
|
+
|
|
603
|
+
for side, graph in candidates:
|
|
604
|
+
rsmi_final = self._postprocess_single(
|
|
605
|
+
graph,
|
|
606
|
+
replace_wc=replace_wc,
|
|
607
|
+
rw_adder=rw_adder,
|
|
608
|
+
diagnostic_context={"candidate_source": side},
|
|
609
|
+
)
|
|
610
|
+
if rsmi_final is None:
|
|
611
|
+
continue
|
|
612
|
+
|
|
613
|
+
final_graph = self._latest_postprocessed_its
|
|
614
|
+
if not isinstance(final_graph, nx.Graph):
|
|
615
|
+
final_graph = graph
|
|
616
|
+
if any(
|
|
617
|
+
graphs_exactly_equivalent(final_graph, previous)
|
|
618
|
+
for previous in fused_graphs
|
|
619
|
+
):
|
|
620
|
+
continue
|
|
621
|
+
|
|
622
|
+
found_nonwildcard = True
|
|
623
|
+
fused_graphs.append(final_graph)
|
|
624
|
+
fused_rsmis.append(rsmi_final)
|
|
625
|
+
|
|
626
|
+
self._fused_its = fused_graphs
|
|
627
|
+
self._fused_rsmis = fused_rsmis
|
|
628
|
+
self._record_stop(
|
|
629
|
+
mode="early_stop",
|
|
630
|
+
reason="early_stop_nonwildcard_its",
|
|
631
|
+
metadata={
|
|
632
|
+
"source": side,
|
|
633
|
+
"n_fw": len(fw_its),
|
|
634
|
+
"n_bw": len(bw_its),
|
|
635
|
+
"n_candidates": len(candidates),
|
|
636
|
+
"n_fused_rsmis": len(fused_rsmis),
|
|
637
|
+
"early_stop": stop_first,
|
|
638
|
+
},
|
|
639
|
+
)
|
|
640
|
+
self.logger.debug(
|
|
641
|
+
"Early-stop on non-wildcard ITS: source=%s, "
|
|
642
|
+
"n_candidates=%d, fused_rsmis=%d",
|
|
643
|
+
side,
|
|
644
|
+
len(candidates),
|
|
645
|
+
len(fused_rsmis),
|
|
646
|
+
)
|
|
647
|
+
if stop_first:
|
|
648
|
+
return True
|
|
649
|
+
|
|
650
|
+
# All candidates failed verification or post-processing;
|
|
651
|
+
# fall back to full fusion (unless fast-path-only mode is active).
|
|
652
|
+
return found_nonwildcard
|