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,561 @@
|
|
|
1
|
+
"""Graph preparation and ITS gluing helpers for SynReactor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Tuple
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
|
|
9
|
+
from synkit.Graph import add_wildcard_subgraph_for_unmapped
|
|
10
|
+
from synkit.Graph.Hyrogen._misc import h_to_explicit
|
|
11
|
+
from synkit.Graph.ITS.its_construction import ITSConstruction
|
|
12
|
+
from synkit.Graph.ITS.its_decompose import its_decompose
|
|
13
|
+
from synkit.Graph.ITS.its_reverter import ITSReverter
|
|
14
|
+
from synkit.Graph.Matcher.subgraph_matcher import SubgraphSearchEngine
|
|
15
|
+
from synkit.IO.chem_converter import ITSFormat, detect_its_format
|
|
16
|
+
from . import product as _product_state
|
|
17
|
+
from .strategy import Strategy
|
|
18
|
+
|
|
19
|
+
MappingDict = Dict[Any, Any]
|
|
20
|
+
_EMBEDDING_ANCHOR = "_synkit_explicit_h_embedding_anchor"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _implicit_heavy_hydrogens(
|
|
24
|
+
graph: nx.Graph,
|
|
25
|
+
*,
|
|
26
|
+
preserve_mapped_hydrogens: bool = False,
|
|
27
|
+
) -> nx.Graph:
|
|
28
|
+
"""Convert ordinary heavy-atom-bound explicit H nodes into hcount."""
|
|
29
|
+
removable = []
|
|
30
|
+
for node, attrs in graph.nodes(data=True):
|
|
31
|
+
if attrs.get("element") != "H":
|
|
32
|
+
continue
|
|
33
|
+
neighbors = list(graph.neighbors(node))
|
|
34
|
+
heavy_neighbors = [
|
|
35
|
+
nbr for nbr in neighbors if graph.nodes[nbr].get("element") != "H"
|
|
36
|
+
]
|
|
37
|
+
if heavy_neighbors and len(heavy_neighbors) == len(neighbors):
|
|
38
|
+
removable.append((node, heavy_neighbors))
|
|
39
|
+
|
|
40
|
+
# Parsed heavy-atom graphs normally contain no removable explicit H.
|
|
41
|
+
# Returning the immutable matching input is then exact and avoids a full
|
|
42
|
+
# graph copy; the copy-on-write branch below preserves the old contract
|
|
43
|
+
# whenever normalization actually changes state.
|
|
44
|
+
if not removable:
|
|
45
|
+
return graph
|
|
46
|
+
|
|
47
|
+
normalized = graph.copy()
|
|
48
|
+
|
|
49
|
+
for h, heavy_neighbors in removable:
|
|
50
|
+
if not normalized.has_node(h):
|
|
51
|
+
continue
|
|
52
|
+
for heavy in heavy_neighbors:
|
|
53
|
+
atom_map = normalized.nodes[h].get("atom_map")
|
|
54
|
+
if preserve_mapped_hydrogens and isinstance(atom_map, int) and atom_map > 0:
|
|
55
|
+
stored = list(normalized.nodes[heavy].get("_implicit_h_atom_maps", ()))
|
|
56
|
+
stored.append(atom_map)
|
|
57
|
+
normalized.nodes[heavy]["_implicit_h_atom_maps"] = tuple(stored)
|
|
58
|
+
normalized.nodes[heavy]["hcount"] = (
|
|
59
|
+
normalized.nodes[heavy].get("hcount", 0) + 1
|
|
60
|
+
)
|
|
61
|
+
normalized.remove_node(h)
|
|
62
|
+
return normalized
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _invert_template(
|
|
66
|
+
tpl: nx.Graph,
|
|
67
|
+
balance_its: bool = True,
|
|
68
|
+
format: ITSFormat | None = None,
|
|
69
|
+
) -> nx.Graph:
|
|
70
|
+
resolved_format = format or detect_its_format(tpl)
|
|
71
|
+
if resolved_format == "tuple":
|
|
72
|
+
reverter = ITSReverter(tpl)
|
|
73
|
+
left, right = reverter.to_reactant_graph(), reverter.to_product_graph()
|
|
74
|
+
return ITSConstruction().construct(
|
|
75
|
+
right,
|
|
76
|
+
left,
|
|
77
|
+
balance_its=balance_its,
|
|
78
|
+
)
|
|
79
|
+
left, right = its_decompose(tpl)
|
|
80
|
+
return ITSConstruction().ITSGraph(right, left, balance_its=balance_its)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ==================================================================
|
|
84
|
+
# Aux – glue, explicit‑H, SMARTS
|
|
85
|
+
# ==================================================================
|
|
86
|
+
def _node_glue(
|
|
87
|
+
host_n: Dict[str, Any], pat_n: Dict[str, Any], key: str = "typesGH"
|
|
88
|
+
) -> None:
|
|
89
|
+
host_r, host_p = host_n[key]
|
|
90
|
+
pat_r, pat_p = pat_n[key]
|
|
91
|
+
delta = pat_r[2] - pat_p[2]
|
|
92
|
+
if pat_r[0] == "*":
|
|
93
|
+
new_r = host_r
|
|
94
|
+
else:
|
|
95
|
+
new_r = host_r[:2] + (host_r[2],) + host_r[3:]
|
|
96
|
+
if pat_p[0] == "*":
|
|
97
|
+
new_p = host_p[:2] + (host_r[2] - delta,) + (host_p[3],) + host_p[4:]
|
|
98
|
+
else:
|
|
99
|
+
new_p = host_p[:2] + (host_r[2] - delta,) + (pat_p[3],) + host_p[4:]
|
|
100
|
+
# if pat_r[0] == '*':
|
|
101
|
+
# host_r[0] = '*'
|
|
102
|
+
# if pat_p[0] == '*':
|
|
103
|
+
# host_p[0] = '*'
|
|
104
|
+
host_n[key] = (new_r, new_p)
|
|
105
|
+
|
|
106
|
+
for key in ("h_pairs", "h_pairs_left", "h_pairs_right", "h_pair_atom_maps"):
|
|
107
|
+
if key in pat_n:
|
|
108
|
+
host_n[key] = pat_n[key]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_explicit_map(
|
|
112
|
+
host: nx.Graph,
|
|
113
|
+
mapping: MappingDict,
|
|
114
|
+
pattern_explicit: nx.Graph | None = None,
|
|
115
|
+
strategy: Strategy = Strategy.ALL,
|
|
116
|
+
embed_threshold: float = None,
|
|
117
|
+
embed_pre_filter: bool = False,
|
|
118
|
+
anchor_embedding: bool = True,
|
|
119
|
+
expanded_host_cache: Dict[frozenset[Any], nx.Graph] | None = None,
|
|
120
|
+
):
|
|
121
|
+
"""Expand implicit H only within, and consistently with, one embedding.
|
|
122
|
+
|
|
123
|
+
In deduplicated mode, ``mapping`` was already accepted against the
|
|
124
|
+
implicit-H pattern, so the explicit-H search needs only extensions of
|
|
125
|
+
that embedding. Unique temporary anchor colours enforce this condition
|
|
126
|
+
inside VF2 and avoid repeating every heavy-atom permutation. Raw mode can
|
|
127
|
+
disable anchoring to retain its historical exhaustive multiplicity.
|
|
128
|
+
"""
|
|
129
|
+
expand_nodes = [v for _, v in mapping.items()]
|
|
130
|
+
original_nodes = set(host)
|
|
131
|
+
cache_key = frozenset(expand_nodes)
|
|
132
|
+
cached_host = (
|
|
133
|
+
expanded_host_cache.get(cache_key) if expanded_host_cache is not None else None
|
|
134
|
+
)
|
|
135
|
+
if cached_host is None:
|
|
136
|
+
host_explicit = h_to_explicit(host, expand_nodes)
|
|
137
|
+
for node in set(host_explicit) - original_nodes:
|
|
138
|
+
if not host_explicit.nodes[node].get("_restored_mapped_h"):
|
|
139
|
+
host_explicit.nodes[node]["_pattern_expanded_h"] = True
|
|
140
|
+
if expanded_host_cache is not None:
|
|
141
|
+
expanded_host_cache[cache_key] = host_explicit.copy()
|
|
142
|
+
else:
|
|
143
|
+
host_explicit = cached_host.copy()
|
|
144
|
+
anchored_pattern = (pattern_explicit or nx.Graph()).copy()
|
|
145
|
+
if anchor_embedding:
|
|
146
|
+
for anchor, (pattern_node, host_node) in enumerate(
|
|
147
|
+
sorted(mapping.items(), key=lambda item: repr(item[0])),
|
|
148
|
+
start=1,
|
|
149
|
+
):
|
|
150
|
+
if pattern_node not in anchored_pattern or host_node not in host_explicit:
|
|
151
|
+
continue
|
|
152
|
+
anchored_pattern.nodes[pattern_node][_EMBEDDING_ANCHOR] = anchor
|
|
153
|
+
host_explicit.nodes[host_node][_EMBEDDING_ANCHOR] = anchor
|
|
154
|
+
|
|
155
|
+
search_host = host_explicit
|
|
156
|
+
missing_pattern_nodes = set(anchored_pattern) - set(mapping)
|
|
157
|
+
local_h_extension = bool(anchor_embedding) and all(
|
|
158
|
+
anchored_pattern.nodes[node].get("element") == "H"
|
|
159
|
+
and all(neighbor in mapping for neighbor in anchored_pattern.neighbors(node))
|
|
160
|
+
and any(neighbor in mapping for neighbor in anchored_pattern.neighbors(node))
|
|
161
|
+
for node in missing_pattern_nodes
|
|
162
|
+
)
|
|
163
|
+
if local_h_extension:
|
|
164
|
+
# Heavy nodes are pointwise anchored. Every omitted pattern H came
|
|
165
|
+
# from implicitization and is adjacent only to those anchors, so no
|
|
166
|
+
# node outside their closed H-neighbourhood can extend this mapping.
|
|
167
|
+
# Restricting VF2 to that induced subgraph preserves the exact
|
|
168
|
+
# extension set while avoiding traversal of unrelated host context.
|
|
169
|
+
local_nodes = set(mapping.values())
|
|
170
|
+
for host_node in tuple(local_nodes):
|
|
171
|
+
local_nodes.update(
|
|
172
|
+
neighbor
|
|
173
|
+
for neighbor in host_explicit.neighbors(host_node)
|
|
174
|
+
if host_explicit.nodes[neighbor].get("element") == "H"
|
|
175
|
+
)
|
|
176
|
+
search_host = host_explicit.subgraph(local_nodes).copy()
|
|
177
|
+
|
|
178
|
+
mappings = SubgraphSearchEngine.find_subgraph_mappings(
|
|
179
|
+
host=search_host,
|
|
180
|
+
pattern=anchored_pattern,
|
|
181
|
+
node_attrs=["element", "charge", _EMBEDDING_ANCHOR],
|
|
182
|
+
edge_attrs=["order"],
|
|
183
|
+
strategy=strategy,
|
|
184
|
+
threshold=embed_threshold,
|
|
185
|
+
pre_filter=embed_pre_filter,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Atom maps are not chemical matching constraints, but when applying a
|
|
189
|
+
# reviewed mapped rule back to its mapped host they are the deterministic
|
|
190
|
+
# tie-breaker between symmetry-equivalent explicit hydrogens.
|
|
191
|
+
mappings.sort(
|
|
192
|
+
key=lambda candidate: _mapping_atom_map_alignment(
|
|
193
|
+
anchored_pattern,
|
|
194
|
+
host_explicit,
|
|
195
|
+
candidate,
|
|
196
|
+
),
|
|
197
|
+
reverse=True,
|
|
198
|
+
)
|
|
199
|
+
for _, attrs in host_explicit.nodes(data=True):
|
|
200
|
+
attrs.pop(_EMBEDDING_ANCHOR, None)
|
|
201
|
+
return mappings, host_explicit
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _mapping_atom_map_alignment(
|
|
205
|
+
pattern: nx.Graph,
|
|
206
|
+
host: nx.Graph,
|
|
207
|
+
mapping: MappingDict,
|
|
208
|
+
) -> int:
|
|
209
|
+
"""Count positive reactant-side AAM identities in one candidate map."""
|
|
210
|
+
|
|
211
|
+
def reactant_map(value: Any) -> Any:
|
|
212
|
+
if isinstance(value, tuple) and len(value) == 2:
|
|
213
|
+
return value[0]
|
|
214
|
+
return value
|
|
215
|
+
|
|
216
|
+
score = 0
|
|
217
|
+
for pattern_node, host_node in mapping.items():
|
|
218
|
+
pattern_map = reactant_map(pattern.nodes[pattern_node].get("atom_map"))
|
|
219
|
+
host_map = reactant_map(host.nodes[host_node].get("atom_map"))
|
|
220
|
+
if isinstance(pattern_map, int) and pattern_map > 0:
|
|
221
|
+
score += pattern_map == host_map
|
|
222
|
+
return score
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _restore_unmatched_pattern_hydrogens(
|
|
226
|
+
graph: nx.Graph,
|
|
227
|
+
mapping: MappingDict,
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Fold unmatched temporary H expansions back into heavy-atom hcount."""
|
|
230
|
+
matched_nodes = set(mapping.values())
|
|
231
|
+
removable = [
|
|
232
|
+
node
|
|
233
|
+
for node, attrs in graph.nodes(data=True)
|
|
234
|
+
if attrs.get("_pattern_expanded_h") and node not in matched_nodes
|
|
235
|
+
]
|
|
236
|
+
for hydrogen in removable:
|
|
237
|
+
neighbors = list(graph.neighbors(hydrogen))
|
|
238
|
+
if len(neighbors) != 1:
|
|
239
|
+
continue
|
|
240
|
+
heavy = neighbors[0]
|
|
241
|
+
graph.nodes[heavy]["hcount"] = graph.nodes[heavy].get("hcount", 0) + 1
|
|
242
|
+
types = graph.nodes[heavy].get("typesGH")
|
|
243
|
+
if (
|
|
244
|
+
isinstance(types, tuple)
|
|
245
|
+
and len(types) == 2
|
|
246
|
+
and isinstance(types[0], tuple)
|
|
247
|
+
and len(types[0]) >= 3
|
|
248
|
+
):
|
|
249
|
+
graph.nodes[heavy]["typesGH"] = (
|
|
250
|
+
types[0][:2] + (types[0][2] + 1,) + types[0][3:],
|
|
251
|
+
types[1],
|
|
252
|
+
)
|
|
253
|
+
graph.remove_node(hydrogen)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _prepare_rewrite_host(host: nx.Graph, *, electron_aware: bool) -> nx.Graph:
|
|
257
|
+
"""Materialize mapping-invariant host state once for a rewrite batch."""
|
|
258
|
+
prepared = host.copy()
|
|
259
|
+
for _, data in prepared.nodes(data=True):
|
|
260
|
+
tpl = (
|
|
261
|
+
data.get("element", "*"),
|
|
262
|
+
data.get("aromatic", False),
|
|
263
|
+
data.get("hcount", 0),
|
|
264
|
+
data.get("charge", 0),
|
|
265
|
+
data.get("neighbors", []),
|
|
266
|
+
)
|
|
267
|
+
data.setdefault("typesGH", (tpl, tpl))
|
|
268
|
+
if electron_aware:
|
|
269
|
+
_product_state._ensure_host_atom_maps(prepared)
|
|
270
|
+
for _, _, data in prepared.edges(data=True):
|
|
271
|
+
order = data.get("order", 1.0)
|
|
272
|
+
data["order"] = (order, order)
|
|
273
|
+
if electron_aware:
|
|
274
|
+
sigma = data.get("sigma_order", 1.0 if order else 0.0)
|
|
275
|
+
pi = data.get("pi_order", max(0.0, float(order) - 1.0))
|
|
276
|
+
data["sigma_order"] = (sigma, sigma)
|
|
277
|
+
data["pi_order"] = (pi, pi)
|
|
278
|
+
data.setdefault("standard_order", 0.0)
|
|
279
|
+
return prepared
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _prepare_rewrite_batch_host(
|
|
283
|
+
host: nx.Graph,
|
|
284
|
+
mappings: List[MappingDict],
|
|
285
|
+
reaction_center: nx.Graph,
|
|
286
|
+
pattern_has_explicit_h: bool,
|
|
287
|
+
electron_aware: bool,
|
|
288
|
+
*,
|
|
289
|
+
seed_structural_signatures: bool = False,
|
|
290
|
+
) -> Tuple[nx.Graph, bool]:
|
|
291
|
+
"""Return a reusable prepared host when every application is total."""
|
|
292
|
+
if pattern_has_explicit_h or any(
|
|
293
|
+
not set(reaction_center).issubset(mapping) for mapping in mappings
|
|
294
|
+
):
|
|
295
|
+
return host, False
|
|
296
|
+
prepared = _prepare_rewrite_host(host, electron_aware=electron_aware)
|
|
297
|
+
if seed_structural_signatures and len(mappings) >= 2:
|
|
298
|
+
# Exact labels of unchanged context are mapping-invariant. NetworkX
|
|
299
|
+
# copies their immutable values into every application, so materialize
|
|
300
|
+
# them once and invalidate only the local write/electron support below.
|
|
301
|
+
from ..output import structural as _structural
|
|
302
|
+
|
|
303
|
+
prepared.graph["electron_aware_rewrite"] = electron_aware
|
|
304
|
+
prepared.graph[_structural._EXACT_NODE_PALETTE] = {}
|
|
305
|
+
prepared.graph[_structural._EXACT_EDGE_PALETTE] = {}
|
|
306
|
+
prepared.graph[_structural._EXACT_IDENTITY_CACHE] = {}
|
|
307
|
+
_structural._attach_exact_structural_signatures(prepared)
|
|
308
|
+
prepared.graph["_structural_signatures_seeded"] = True
|
|
309
|
+
return prepared, True
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _glue_graph(
|
|
313
|
+
host: nx.Graph,
|
|
314
|
+
rc: nx.Graph,
|
|
315
|
+
mapping: MappingDict,
|
|
316
|
+
pattern_has_explicit_H: bool = False,
|
|
317
|
+
pattern_explicit: nx.Graph | None = None,
|
|
318
|
+
strategy: Strategy = Strategy.ALL,
|
|
319
|
+
embed_threshold: float = None,
|
|
320
|
+
embed_pre_filter: bool = False,
|
|
321
|
+
relative_pi_edges: set[frozenset[Any]] | None = None,
|
|
322
|
+
restore_unmatched_explicit_h: bool = True,
|
|
323
|
+
refresh_electrons: bool = True,
|
|
324
|
+
electron_aware: bool | None = None,
|
|
325
|
+
relative_match_resources: frozenset[str] = frozenset(),
|
|
326
|
+
anchor_explicit_embedding: bool = True,
|
|
327
|
+
host_prepared: bool = False,
|
|
328
|
+
expanded_host_cache: Dict[frozenset[Any], nx.Graph] | None = None,
|
|
329
|
+
consume_prepared_host: bool = False,
|
|
330
|
+
) -> List[nx.Graph]:
|
|
331
|
+
list_its: List[nx.Graph] = []
|
|
332
|
+
# NetworkX copies node/edge attribute dictionaries. Rewrite values
|
|
333
|
+
# are replaced rather than mutated in place, so recursively copying
|
|
334
|
+
# every tuple and stereo descriptor only adds mapping-sized overhead.
|
|
335
|
+
# A prepared batch host is a private working copy. Its last application
|
|
336
|
+
# can safely take ownership instead of cloning the full molecular graph
|
|
337
|
+
# once more; earlier applications have already received independent
|
|
338
|
+
# copies. Explicit-H expansion may branch and therefore keeps the
|
|
339
|
+
# conservative copy path.
|
|
340
|
+
host_g = (
|
|
341
|
+
host
|
|
342
|
+
if consume_prepared_host and host_prepared and not pattern_has_explicit_H
|
|
343
|
+
else host.copy()
|
|
344
|
+
)
|
|
345
|
+
if electron_aware is None:
|
|
346
|
+
electron_aware = _is_electron_aware_template(rc)
|
|
347
|
+
|
|
348
|
+
def _default_tg(a: Dict[str, Any]) -> Tuple[Tuple[Any, ...], Tuple[Any, ...]]:
|
|
349
|
+
tpl = (
|
|
350
|
+
a.get("element", "*"),
|
|
351
|
+
a.get("aromatic", False),
|
|
352
|
+
a.get("hcount", 0),
|
|
353
|
+
a.get("charge", 0),
|
|
354
|
+
a.get("neighbors", []),
|
|
355
|
+
)
|
|
356
|
+
return tpl, tpl
|
|
357
|
+
|
|
358
|
+
if not host_prepared:
|
|
359
|
+
for _, data in host_g.nodes(data=True):
|
|
360
|
+
data.setdefault("typesGH", _default_tg(data))
|
|
361
|
+
if electron_aware:
|
|
362
|
+
_product_state._ensure_host_atom_maps(host_g)
|
|
363
|
+
|
|
364
|
+
if pattern_has_explicit_H:
|
|
365
|
+
mappings, host_g = _get_explicit_map(
|
|
366
|
+
host_g,
|
|
367
|
+
mapping,
|
|
368
|
+
pattern_explicit,
|
|
369
|
+
strategy,
|
|
370
|
+
embed_threshold,
|
|
371
|
+
embed_pre_filter,
|
|
372
|
+
anchor_explicit_embedding,
|
|
373
|
+
expanded_host_cache,
|
|
374
|
+
)
|
|
375
|
+
if electron_aware:
|
|
376
|
+
_product_state._ensure_host_atom_maps(host_g)
|
|
377
|
+
else:
|
|
378
|
+
mappings = [mapping]
|
|
379
|
+
|
|
380
|
+
relative_pi_centers = {
|
|
381
|
+
node for edge in (relative_pi_edges or set()) for node in edge
|
|
382
|
+
}
|
|
383
|
+
relative_node_resources = (
|
|
384
|
+
frozenset(rc.graph.get("relative_node_resources", ()))
|
|
385
|
+
| relative_match_resources
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Iterate over remappings --------------------------------------
|
|
389
|
+
reuse_prepared_host = len(mappings) == 1
|
|
390
|
+
for m in mappings:
|
|
391
|
+
|
|
392
|
+
its = host_g if reuse_prepared_host else host_g.copy()
|
|
393
|
+
if its.graph.get("_structural_signatures_seeded", False):
|
|
394
|
+
# Graph.copy() deliberately shares immutable label values. Dirty
|
|
395
|
+
# supports, in contrast, must be private to this application.
|
|
396
|
+
its.graph["_structural_exact_dirty_nodes"] = set()
|
|
397
|
+
its.graph["_structural_exact_dirty_edges"] = set()
|
|
398
|
+
structural_dirty_nodes = its.graph.get("_structural_exact_dirty_nodes")
|
|
399
|
+
structural_dirty_edges = its.graph.get("_structural_exact_dirty_edges")
|
|
400
|
+
if pattern_has_explicit_H and restore_unmatched_explicit_h:
|
|
401
|
+
_restore_unmatched_pattern_hydrogens(its, m)
|
|
402
|
+
# Materialize wildcard nodes for partial mappings.
|
|
403
|
+
if len(m.keys()) < rc.number_of_nodes():
|
|
404
|
+
its, m = add_wildcard_subgraph_for_unmapped(
|
|
405
|
+
its,
|
|
406
|
+
rc,
|
|
407
|
+
m,
|
|
408
|
+
inplace=True,
|
|
409
|
+
tuple_mode=electron_aware,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if not host_prepared:
|
|
413
|
+
for _, _, data in its.edges(data=True):
|
|
414
|
+
o = data.get("order", 1.0)
|
|
415
|
+
data["order"] = (o, o)
|
|
416
|
+
if electron_aware:
|
|
417
|
+
sigma = data.get("sigma_order", 1.0 if o else 0.0)
|
|
418
|
+
pi = data.get("pi_order", max(0.0, float(o) - 1.0))
|
|
419
|
+
data["sigma_order"] = (sigma, sigma)
|
|
420
|
+
data["pi_order"] = (pi, pi)
|
|
421
|
+
data.setdefault("standard_order", 0.0)
|
|
422
|
+
|
|
423
|
+
for _, data in rc.nodes(data=True):
|
|
424
|
+
data.setdefault("typesGH", _default_tg(data))
|
|
425
|
+
|
|
426
|
+
# merge nodes -------------------------------------------
|
|
427
|
+
for rc_n, host_n in m.items():
|
|
428
|
+
if its.has_node(host_n):
|
|
429
|
+
its.nodes[host_n].pop("_structural_exact_node_sig", None)
|
|
430
|
+
if isinstance(structural_dirty_nodes, set):
|
|
431
|
+
structural_dirty_nodes.add(host_n)
|
|
432
|
+
_node_glue(its.nodes[host_n], rc.nodes[rc_n])
|
|
433
|
+
if electron_aware:
|
|
434
|
+
rc_element = rc.nodes[rc_n].get("element")
|
|
435
|
+
wildcard_context = (
|
|
436
|
+
isinstance(rc_element, tuple)
|
|
437
|
+
and len(rc_element) == 2
|
|
438
|
+
and rc_element[0] == rc_element[1] == "*"
|
|
439
|
+
)
|
|
440
|
+
_product_state._pair_electron_aware_node_attrs(
|
|
441
|
+
its.nodes[host_n],
|
|
442
|
+
rc.nodes[rc_n],
|
|
443
|
+
preserve_unchanged_state=(
|
|
444
|
+
rc_n in relative_pi_centers or wildcard_context
|
|
445
|
+
),
|
|
446
|
+
relative_resources=relative_node_resources,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# merge edges (additive order) ---------------------------
|
|
450
|
+
for u, v, rc_attr in rc.edges(data=True):
|
|
451
|
+
hu, hv = m.get(u), m.get(v)
|
|
452
|
+
if hu is None or hv is None:
|
|
453
|
+
continue
|
|
454
|
+
if not its.has_edge(hu, hv):
|
|
455
|
+
its.add_edge(hu, hv, **dict(rc_attr))
|
|
456
|
+
if isinstance(structural_dirty_edges, set):
|
|
457
|
+
structural_dirty_edges.add((hu, hv))
|
|
458
|
+
else:
|
|
459
|
+
host_attr = its[hu][hv]
|
|
460
|
+
host_attr.pop("_structural_exact_edge_sig", None)
|
|
461
|
+
if isinstance(structural_dirty_edges, set):
|
|
462
|
+
structural_dirty_edges.add((hu, hv))
|
|
463
|
+
rc_order = rc_attr.get("order", (0, 0))
|
|
464
|
+
if relative_pi_edges and frozenset((u, v)) in relative_pi_edges:
|
|
465
|
+
# Apply the rule delta to the matched host edge. Thus
|
|
466
|
+
# C=C -> C-C naturally becomes C#C -> C=C on an
|
|
467
|
+
# alkyne, while both still remove exactly one pi bond.
|
|
468
|
+
for key in (
|
|
469
|
+
"order",
|
|
470
|
+
"kekule_order",
|
|
471
|
+
"sigma_order",
|
|
472
|
+
"pi_order",
|
|
473
|
+
):
|
|
474
|
+
host_value = host_attr.get(key)
|
|
475
|
+
rule_value = rc_attr.get(key)
|
|
476
|
+
if not (
|
|
477
|
+
isinstance(host_value, tuple)
|
|
478
|
+
and len(host_value) == 2
|
|
479
|
+
and isinstance(rule_value, tuple)
|
|
480
|
+
and len(rule_value) == 2
|
|
481
|
+
):
|
|
482
|
+
continue
|
|
483
|
+
delta = float(rule_value[0]) - float(rule_value[1])
|
|
484
|
+
product_value = float(host_value[0]) - delta
|
|
485
|
+
if product_value.is_integer():
|
|
486
|
+
product_value = int(product_value)
|
|
487
|
+
host_attr[key] = (host_value[0], product_value)
|
|
488
|
+
host_attr["standard_order"] = rc_attr.get("standard_order", 0.0)
|
|
489
|
+
elif rc_order[0] == 0: # additive only on product side
|
|
490
|
+
ho = host_attr["order"]
|
|
491
|
+
host_attr["order"] = (ho[0], round(ho[1] + rc_order[1]))
|
|
492
|
+
if electron_aware:
|
|
493
|
+
host_sigma = host_attr.get("sigma_order", (0.0, 0.0))
|
|
494
|
+
host_pi = host_attr.get("pi_order", (0.0, 0.0))
|
|
495
|
+
rc_sigma = rc_attr.get("sigma_order", (0.0, 0.0))
|
|
496
|
+
rc_pi = rc_attr.get("pi_order", (0.0, 0.0))
|
|
497
|
+
host_attr["sigma_order"] = (
|
|
498
|
+
host_sigma[0],
|
|
499
|
+
host_sigma[1] + rc_sigma[1],
|
|
500
|
+
)
|
|
501
|
+
host_attr["pi_order"] = (
|
|
502
|
+
host_pi[0],
|
|
503
|
+
host_pi[1] + rc_pi[1],
|
|
504
|
+
)
|
|
505
|
+
host_attr["standard_order"] += rc_attr.get("standard_order", 0.0)
|
|
506
|
+
else:
|
|
507
|
+
# Preserve the host reactant phase; independently parsed
|
|
508
|
+
# aromatic graphs may use an equivalent Kekule assignment.
|
|
509
|
+
for key, rule_value in rc_attr.items():
|
|
510
|
+
if isinstance(rule_value, tuple) and len(rule_value) == 2:
|
|
511
|
+
host_value = host_attr.get(key)
|
|
512
|
+
host_left = (
|
|
513
|
+
host_value[0]
|
|
514
|
+
if isinstance(host_value, tuple)
|
|
515
|
+
and len(host_value) == 2
|
|
516
|
+
else host_value
|
|
517
|
+
)
|
|
518
|
+
host_attr[key] = (host_left, rule_value[1])
|
|
519
|
+
else:
|
|
520
|
+
host_attr[key] = rule_value
|
|
521
|
+
its.graph["electron_aware_rewrite"] = electron_aware
|
|
522
|
+
if electron_aware:
|
|
523
|
+
# Retain the exact one-local support of the electron functional:
|
|
524
|
+
# mapped write nodes plus boundary neighbors whose incident edge
|
|
525
|
+
# state actually changes. Consumers that receive an ITS without
|
|
526
|
+
# this proof hint retain the conservative whole-graph path.
|
|
527
|
+
its.graph["_product_refresh_nodes"] = (
|
|
528
|
+
_product_state._electron_refresh_support(
|
|
529
|
+
its,
|
|
530
|
+
m.values(),
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
its.graph["_product_electron_fields_current"] = False
|
|
534
|
+
its.graph["_product_kekule_phase_dirty"] = (
|
|
535
|
+
_product_state._product_kekule_phase_is_dirty(its)
|
|
536
|
+
)
|
|
537
|
+
if refresh_electrons:
|
|
538
|
+
_product_state._refresh_product_electron_fields(its)
|
|
539
|
+
list_its.append(its)
|
|
540
|
+
return list_its
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _is_electron_aware_template(rc: nx.Graph) -> bool:
|
|
544
|
+
"""Return whether an RC carries paired Lewis-state rewrite data.
|
|
545
|
+
|
|
546
|
+
Explicit hydrogen transfer edges may be folded into ``hcount`` by
|
|
547
|
+
:class:`SynRule`. Radical or lone-pair changes on the remaining nodes
|
|
548
|
+
still require the electron-aware tuple rewrite path in that case.
|
|
549
|
+
"""
|
|
550
|
+
edge_state = any(
|
|
551
|
+
"sigma_order" in data and "pi_order" in data
|
|
552
|
+
for _, _, data in rc.edges(data=True)
|
|
553
|
+
)
|
|
554
|
+
node_state = any(
|
|
555
|
+
isinstance(data.get(key), tuple)
|
|
556
|
+
and len(data[key]) == 2
|
|
557
|
+
and data[key][0] != data[key][1]
|
|
558
|
+
for _, data in rc.nodes(data=True)
|
|
559
|
+
for key in ("radical", "lone_pairs", "valence_electrons")
|
|
560
|
+
)
|
|
561
|
+
return edge_state or node_state
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Strategy(str, Enum):
|
|
6
|
+
"""Strategy for sub-graph matching/application:
|
|
7
|
+
|
|
8
|
+
- ALL: classic VF2 on the whole graph
|
|
9
|
+
- COMPONENT: component-aware only (no cross-CC backtracking)
|
|
10
|
+
- BACKTRACK: component-aware with backtracking across CCs
|
|
11
|
+
- PARTIAL: partial matching (mcs)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
ALL = "all"
|
|
15
|
+
COMPONENT = "comp"
|
|
16
|
+
BACKTRACK = "bt"
|
|
17
|
+
PARTIAL = "partial"
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_string(cls, value: Union[str, "Strategy"]) -> "Strategy":
|
|
21
|
+
"""Convert a string or Strategy to a Strategy enum.
|
|
22
|
+
|
|
23
|
+
:param value: The strategy to parse.
|
|
24
|
+
:type value: str or Strategy
|
|
25
|
+
|
|
26
|
+
:return: Parsed Strategy.
|
|
27
|
+
:rtype: Strategy
|
|
28
|
+
|
|
29
|
+
:raises ValueError: If the input is not a valid Strategy.
|
|
30
|
+
"""
|
|
31
|
+
if isinstance(value, cls):
|
|
32
|
+
return value
|
|
33
|
+
try:
|
|
34
|
+
return cls(value.lower()) # type: ignore[arg-type]
|
|
35
|
+
except ValueError as e:
|
|
36
|
+
raise ValueError(f"Unknown strategy: {value!r}") from e
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
"""Return the strategy’s canonical code (e.g. 'all', 'comp', 'bt')."""
|
|
40
|
+
return self.value
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
"""Return the enum-style representation, e.g. 'Strategy.ALL'."""
|
|
44
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reaction-rule matching policies and symmetry reduction."""
|