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,780 @@
|
|
|
1
|
+
"""Stereo propagation and application behavior for SynReactor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from typing import Any, Dict, List, Mapping, Tuple
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
from rdkit import Chem
|
|
10
|
+
|
|
11
|
+
from synkit.Graph.ITS.its_reverter import ITSReverter
|
|
12
|
+
from synkit.IO.graph_to_mol import GraphToMol
|
|
13
|
+
|
|
14
|
+
MappingDict = Dict[Any, Any]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ReactorStereoMixin:
|
|
18
|
+
"""Internal stereo behavior shared by the SynReactor facade."""
|
|
19
|
+
|
|
20
|
+
def _check_stereo_branch_budget(self, branch_count: int) -> None:
|
|
21
|
+
"""Refuse an expansion before it can exceed the configured budget."""
|
|
22
|
+
limit = self.stereo_branch_limit
|
|
23
|
+
requested = self._stereo_branch_count + branch_count
|
|
24
|
+
if limit is not None and requested > limit:
|
|
25
|
+
from .assignment import StereoBranchLimitError
|
|
26
|
+
|
|
27
|
+
raise StereoBranchLimitError(limit, requested)
|
|
28
|
+
|
|
29
|
+
def _matching_host_graph(self) -> nx.Graph:
|
|
30
|
+
"""Return the host graph normalized to the active rule representation."""
|
|
31
|
+
if self._host_for_matching is None:
|
|
32
|
+
host = self.graph.raw
|
|
33
|
+
if getattr(self.rule, "_format", None) == "tuple":
|
|
34
|
+
host = self._implicit_heavy_hydrogens(
|
|
35
|
+
host,
|
|
36
|
+
preserve_mapped_hydrogens=self.preserve_mapped_hydrogens,
|
|
37
|
+
)
|
|
38
|
+
self._host_for_matching = host
|
|
39
|
+
return self._host_for_matching
|
|
40
|
+
|
|
41
|
+
def _noncovariant_propagation_targets(self) -> set[str]:
|
|
42
|
+
"""Return guards whose coupled product stereo cannot be inferred.
|
|
43
|
+
|
|
44
|
+
One same-locus endpoint change can be mirrored directly. A destroyed
|
|
45
|
+
descriptor with no other stereo output is also safe. If other product
|
|
46
|
+
descriptors are formed, however, their correlation to an inverse
|
|
47
|
+
input is not encoded by independent ``StereoChange`` objects; such a
|
|
48
|
+
rule must match its reference geometry or provide a companion rule.
|
|
49
|
+
"""
|
|
50
|
+
from synkit.Graph.Stereo import descriptor_id
|
|
51
|
+
|
|
52
|
+
exact_targets = set()
|
|
53
|
+
for target, guard in self.rule.stereo_guards.items():
|
|
54
|
+
effect = self.rule.stereo_effects.get(target)
|
|
55
|
+
other_outputs = any(
|
|
56
|
+
key != target and change.after is not None
|
|
57
|
+
for key, change in self.rule.stereo_effects.items()
|
|
58
|
+
)
|
|
59
|
+
same_locus_output = (
|
|
60
|
+
effect is not None
|
|
61
|
+
and effect.after is not None
|
|
62
|
+
and type(guard) is type(effect.after)
|
|
63
|
+
and descriptor_id(effect.after) == target
|
|
64
|
+
)
|
|
65
|
+
safe_destruction = (
|
|
66
|
+
effect is not None and effect.after is None and not other_outputs
|
|
67
|
+
)
|
|
68
|
+
if not same_locus_output and not safe_destruction:
|
|
69
|
+
exact_targets.add(target)
|
|
70
|
+
elif other_outputs:
|
|
71
|
+
exact_targets.add(target)
|
|
72
|
+
return exact_targets
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _unknown_stereo_orientation(descriptor: Any) -> Any:
|
|
76
|
+
"""Keep a descriptor locus/reference frame but remove its orientation."""
|
|
77
|
+
return type(descriptor)(
|
|
78
|
+
descriptor.atoms,
|
|
79
|
+
None,
|
|
80
|
+
descriptor.provenance,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _propagated_stereo_effect(
|
|
84
|
+
self,
|
|
85
|
+
change: Any,
|
|
86
|
+
reactant_registry: Mapping[str, Any],
|
|
87
|
+
host: nx.Graph,
|
|
88
|
+
) -> tuple[Any, Any]:
|
|
89
|
+
"""Apply an aligned reaction relation to the matched substrate frame.
|
|
90
|
+
|
|
91
|
+
The rule endpoint is only a reference frame. In chemical ``propagate``
|
|
92
|
+
mode the stored permutation witness acts on the concrete substrate
|
|
93
|
+
configuration after ligand replacement. Missing, unknown, or refused
|
|
94
|
+
alignment evidence never acquires arbitrary template orientation.
|
|
95
|
+
"""
|
|
96
|
+
before, after = change.before, change.after
|
|
97
|
+
if self.stereo_mode != "propagate" or before is None:
|
|
98
|
+
return before, after
|
|
99
|
+
|
|
100
|
+
from synkit.Graph.Stereo import (
|
|
101
|
+
StereoAlignmentError,
|
|
102
|
+
StereoRelationKind,
|
|
103
|
+
descriptor_id,
|
|
104
|
+
normalize_hydrogen_references,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
target = descriptor_id(before)
|
|
108
|
+
candidate = reactant_registry.get(target)
|
|
109
|
+
linked_output = (
|
|
110
|
+
after is not None
|
|
111
|
+
and type(before) is type(after)
|
|
112
|
+
and descriptor_id(after) == target
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if candidate is None:
|
|
116
|
+
unknown_before = self._unknown_stereo_orientation(before)
|
|
117
|
+
unknown_after = (
|
|
118
|
+
self._unknown_stereo_orientation(after) if linked_output else after
|
|
119
|
+
)
|
|
120
|
+
return unknown_before, unknown_after
|
|
121
|
+
|
|
122
|
+
if candidate.parity is None:
|
|
123
|
+
try:
|
|
124
|
+
propagated = (
|
|
125
|
+
change.apply_to(
|
|
126
|
+
candidate,
|
|
127
|
+
semantics=self.stereo_semantics,
|
|
128
|
+
diagnostics=self._stereo_semantic_diagnostics,
|
|
129
|
+
)
|
|
130
|
+
if linked_output
|
|
131
|
+
else after
|
|
132
|
+
)
|
|
133
|
+
except StereoAlignmentError:
|
|
134
|
+
propagated = (
|
|
135
|
+
self._unknown_stereo_orientation(after) if linked_output else after
|
|
136
|
+
)
|
|
137
|
+
return candidate, propagated
|
|
138
|
+
|
|
139
|
+
expected = normalize_hydrogen_references(before, host)
|
|
140
|
+
actual = normalize_hydrogen_references(candidate, host)
|
|
141
|
+
relation = expected.relation_to(actual)
|
|
142
|
+
if relation.kind not in {
|
|
143
|
+
StereoRelationKind.UNRELATED,
|
|
144
|
+
StereoRelationKind.UNSPECIFIED,
|
|
145
|
+
}:
|
|
146
|
+
try:
|
|
147
|
+
return candidate, (
|
|
148
|
+
change.apply_to(
|
|
149
|
+
actual,
|
|
150
|
+
semantics=self.stereo_semantics,
|
|
151
|
+
diagnostics=self._stereo_semantic_diagnostics,
|
|
152
|
+
)
|
|
153
|
+
if linked_output
|
|
154
|
+
else after
|
|
155
|
+
)
|
|
156
|
+
except StereoAlignmentError:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
unknown_after = (
|
|
160
|
+
self._unknown_stereo_orientation(after) if linked_output else after
|
|
161
|
+
)
|
|
162
|
+
return candidate, unknown_after
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def _node_by_atom_map(graph: nx.Graph) -> Dict[int, Any]:
|
|
166
|
+
"""Return product nodes keyed by the application-local atom map."""
|
|
167
|
+
result = {}
|
|
168
|
+
for node, attrs in graph.nodes(data=True):
|
|
169
|
+
atom_map = attrs.get("atom_map") or node
|
|
170
|
+
if isinstance(atom_map, int) and atom_map > 0:
|
|
171
|
+
result[atom_map] = node
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _node_reference(graph: nx.Graph, node: Any) -> int | None:
|
|
176
|
+
"""Return the stable mapped reference for one product node."""
|
|
177
|
+
atom_map = graph.nodes[node].get("atom_map") or node
|
|
178
|
+
return atom_map if isinstance(atom_map, int) and atom_map > 0 else None
|
|
179
|
+
|
|
180
|
+
def _relabeled_stereo_couplings(
|
|
181
|
+
self,
|
|
182
|
+
host: nx.Graph,
|
|
183
|
+
mapping: MappingDict,
|
|
184
|
+
) -> tuple[Any, ...]:
|
|
185
|
+
"""Translate every rule coupling into the selected host mapping."""
|
|
186
|
+
translation: Dict[int, int] = {}
|
|
187
|
+
pattern = self.rule.left.raw
|
|
188
|
+
for pattern_node, host_node in mapping.items():
|
|
189
|
+
pattern_map = pattern.nodes[pattern_node].get("atom_map", pattern_node)
|
|
190
|
+
if isinstance(pattern_map, tuple) and len(pattern_map) == 2:
|
|
191
|
+
pattern_map = pattern_map[0]
|
|
192
|
+
host_map = host.nodes[host_node].get("atom_map") or host_node
|
|
193
|
+
if isinstance(pattern_map, int) and isinstance(host_map, int):
|
|
194
|
+
translation[pattern_map] = host_map
|
|
195
|
+
return tuple(
|
|
196
|
+
self.rule.stereo_couplings[key].relabel(translation)
|
|
197
|
+
for key in sorted(self.rule.stereo_couplings)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def _coupling_planar_from_tetrahedral_input(
|
|
202
|
+
coupling: Any,
|
|
203
|
+
reactant_registry: Mapping[str, Any],
|
|
204
|
+
) -> Any:
|
|
205
|
+
"""Recover the unique correlated alkene frame for an elimination."""
|
|
206
|
+
from synkit.Graph.Stereo import PlanarBondStereo, TetrahedralStereo
|
|
207
|
+
|
|
208
|
+
left_center, right_center = coupling.centers
|
|
209
|
+
left_ligand, right_ligand = coupling.ligands
|
|
210
|
+
left = reactant_registry.get(f"atom:{left_center}")
|
|
211
|
+
right = reactant_registry.get(f"atom:{right_center}")
|
|
212
|
+
if (
|
|
213
|
+
not isinstance(left, TetrahedralStereo)
|
|
214
|
+
or not isinstance(right, TetrahedralStereo)
|
|
215
|
+
or left.parity not in {-1, 1}
|
|
216
|
+
or right.parity not in {-1, 1}
|
|
217
|
+
):
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
remaining = []
|
|
221
|
+
for descriptor, other, ligand in (
|
|
222
|
+
(left, right_center, left_ligand),
|
|
223
|
+
(right, left_center, right_ligand),
|
|
224
|
+
):
|
|
225
|
+
references = [
|
|
226
|
+
reference
|
|
227
|
+
for reference in descriptor.atoms[1:]
|
|
228
|
+
if reference not in {other, ligand}
|
|
229
|
+
]
|
|
230
|
+
if len(references) != 2:
|
|
231
|
+
return None
|
|
232
|
+
remaining.append(tuple(sorted(references, key=repr)))
|
|
233
|
+
|
|
234
|
+
candidates = (
|
|
235
|
+
PlanarBondStereo(
|
|
236
|
+
(
|
|
237
|
+
*remaining[0],
|
|
238
|
+
left_center,
|
|
239
|
+
right_center,
|
|
240
|
+
*remaining[1],
|
|
241
|
+
),
|
|
242
|
+
0,
|
|
243
|
+
"rule-coupling-reverse",
|
|
244
|
+
),
|
|
245
|
+
PlanarBondStereo(
|
|
246
|
+
(
|
|
247
|
+
*remaining[0],
|
|
248
|
+
left_center,
|
|
249
|
+
right_center,
|
|
250
|
+
*reversed(remaining[1]),
|
|
251
|
+
),
|
|
252
|
+
0,
|
|
253
|
+
"rule-coupling-reverse",
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
for candidate in candidates:
|
|
257
|
+
if any(
|
|
258
|
+
pair[0] == left and pair[1] == right
|
|
259
|
+
for pair in coupling.tetrahedral_product_pairs(candidate)
|
|
260
|
+
):
|
|
261
|
+
return candidate
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
def _coupling_mapping_inputs_match(
|
|
265
|
+
self,
|
|
266
|
+
host: nx.Graph,
|
|
267
|
+
mapping: MappingDict,
|
|
268
|
+
*,
|
|
269
|
+
strict: bool,
|
|
270
|
+
) -> bool:
|
|
271
|
+
"""Validate coupling input geometry and strict descriptor presence."""
|
|
272
|
+
from synkit.Graph.Stereo import PlanarBondStereo, descriptor_id
|
|
273
|
+
|
|
274
|
+
registry = dict(host.graph.get("stereo_descriptors", {}))
|
|
275
|
+
by_map = self._node_by_atom_map(host)
|
|
276
|
+
allowed_targets = set()
|
|
277
|
+
for coupling in self._relabeled_stereo_couplings(host, mapping):
|
|
278
|
+
if coupling.kind == "VICINAL_ADDITION":
|
|
279
|
+
descriptor = registry.get(coupling.target)
|
|
280
|
+
left = by_map.get(coupling.centers[0])
|
|
281
|
+
right = by_map.get(coupling.centers[1])
|
|
282
|
+
pi_order = (
|
|
283
|
+
float(host.edges[left, right].get("pi_order", 0.0))
|
|
284
|
+
if left is not None
|
|
285
|
+
and right is not None
|
|
286
|
+
and host.has_edge(left, right)
|
|
287
|
+
else 0.0
|
|
288
|
+
)
|
|
289
|
+
if pi_order < 2.0:
|
|
290
|
+
if (
|
|
291
|
+
not isinstance(descriptor, PlanarBondStereo)
|
|
292
|
+
or descriptor.parity != 0
|
|
293
|
+
):
|
|
294
|
+
return False
|
|
295
|
+
allowed_targets.add(coupling.target)
|
|
296
|
+
else:
|
|
297
|
+
if (
|
|
298
|
+
self._coupling_planar_from_tetrahedral_input(
|
|
299
|
+
coupling,
|
|
300
|
+
registry,
|
|
301
|
+
)
|
|
302
|
+
is None
|
|
303
|
+
):
|
|
304
|
+
return False
|
|
305
|
+
allowed_targets.update(f"atom:{center}" for center in coupling.centers)
|
|
306
|
+
if not strict:
|
|
307
|
+
return True
|
|
308
|
+
|
|
309
|
+
mapped_nodes = set(mapping.values())
|
|
310
|
+
mapped_references = {
|
|
311
|
+
host.nodes[node].get("atom_map") or node for node in mapped_nodes
|
|
312
|
+
}
|
|
313
|
+
scoped = {
|
|
314
|
+
descriptor_id(descriptor)
|
|
315
|
+
for descriptor in registry.values()
|
|
316
|
+
if descriptor.dependencies & mapped_references
|
|
317
|
+
}
|
|
318
|
+
translation = {}
|
|
319
|
+
pattern = self.rule.left.raw
|
|
320
|
+
for pattern_node, host_node in mapping.items():
|
|
321
|
+
pattern_map = pattern.nodes[pattern_node].get("atom_map", pattern_node)
|
|
322
|
+
if isinstance(pattern_map, tuple) and len(pattern_map) == 2:
|
|
323
|
+
pattern_map = pattern_map[0]
|
|
324
|
+
host_map = host.nodes[host_node].get("atom_map", host_node)
|
|
325
|
+
if isinstance(pattern_map, int) and isinstance(host_map, int):
|
|
326
|
+
translation[pattern_map] = host_map
|
|
327
|
+
translated_guards = {
|
|
328
|
+
descriptor_id(descriptor.relabel(translation))
|
|
329
|
+
for descriptor in self.rule.stereo_guards.values()
|
|
330
|
+
}
|
|
331
|
+
return scoped <= allowed_targets | translated_guards
|
|
332
|
+
|
|
333
|
+
def _coupled_planar_product(self, coupling: Any, product: nx.Graph) -> Any:
|
|
334
|
+
"""Construct a formed alkene descriptor from a syn/anti coupling."""
|
|
335
|
+
by_map = self._node_by_atom_map(product)
|
|
336
|
+
left_center, right_center = coupling.centers
|
|
337
|
+
left_ligand, right_ligand = coupling.ligands
|
|
338
|
+
required = {left_center, right_center, left_ligand, right_ligand}
|
|
339
|
+
if not required <= set(by_map):
|
|
340
|
+
return None
|
|
341
|
+
left_node, right_node = by_map[left_center], by_map[right_center]
|
|
342
|
+
if not product.has_edge(left_node, right_node):
|
|
343
|
+
return None
|
|
344
|
+
edge = product.edges[left_node, right_node]
|
|
345
|
+
if float(edge.get("pi_order", 0.0)) < 1.0:
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
references = []
|
|
349
|
+
for center, other, ligand in (
|
|
350
|
+
(left_center, right_center, left_ligand),
|
|
351
|
+
(right_center, left_center, right_ligand),
|
|
352
|
+
):
|
|
353
|
+
center_node = by_map[center]
|
|
354
|
+
excluded = {by_map[other], by_map[ligand]}
|
|
355
|
+
peripheral = [
|
|
356
|
+
self._node_reference(product, neighbor)
|
|
357
|
+
for neighbor in product.neighbors(center_node)
|
|
358
|
+
if neighbor not in excluded
|
|
359
|
+
]
|
|
360
|
+
peripheral = [value for value in peripheral if value is not None]
|
|
361
|
+
if len(peripheral) == 1:
|
|
362
|
+
references.append(peripheral[0])
|
|
363
|
+
elif not peripheral and product.nodes[center_node].get("hcount", 0) == 1:
|
|
364
|
+
references.append(f"@H:{center}")
|
|
365
|
+
else:
|
|
366
|
+
return None
|
|
367
|
+
return coupling.planar_product_descriptor(*references)
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def _potential_tetrahedral_atom_maps(product: nx.Graph) -> set[int] | None:
|
|
371
|
+
"""Return constitutionally stereogenic tetrahedral product centers.
|
|
372
|
+
|
|
373
|
+
Coupling geometry must not create a descriptor when two ligands are
|
|
374
|
+
constitutionally identical, as in ordinary H2 addition to a CH=CH
|
|
375
|
+
alkene. ``None`` is a conservative reconstruction-failure sentinel:
|
|
376
|
+
callers then retain the rule-derived descriptors rather than losing a
|
|
377
|
+
potentially valid stereocenter.
|
|
378
|
+
"""
|
|
379
|
+
probe = product.copy()
|
|
380
|
+
probe.graph["stereo_descriptors"] = {}
|
|
381
|
+
try:
|
|
382
|
+
molecule = GraphToMol().graph_to_mol(probe)
|
|
383
|
+
except Exception:
|
|
384
|
+
return None
|
|
385
|
+
atom_maps = {
|
|
386
|
+
atom.GetIdx(): atom.GetAtomMapNum() for atom in molecule.GetAtoms()
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
atom_maps[stereo.centeredOn]
|
|
390
|
+
for stereo in Chem.FindPotentialStereo(molecule)
|
|
391
|
+
if stereo.type == Chem.StereoType.Atom_Tetrahedral
|
|
392
|
+
and atom_maps.get(stereo.centeredOn, 0) > 0
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
def _apply_stereo_couplings(
|
|
396
|
+
self,
|
|
397
|
+
states: List[Tuple[Dict, Dict, Dict, Dict, Dict]],
|
|
398
|
+
couplings: List[Any],
|
|
399
|
+
reactant_registry: Mapping[str, Any],
|
|
400
|
+
product_graph: nx.Graph,
|
|
401
|
+
) -> List[Tuple[Dict, Dict, Dict, Dict, Dict]]:
|
|
402
|
+
"""Derive coupled endpoint descriptors from chemical rule semantics."""
|
|
403
|
+
if not couplings:
|
|
404
|
+
return states
|
|
405
|
+
|
|
406
|
+
from synkit.Graph.Stereo import PlanarBondStereo, descriptor_id
|
|
407
|
+
from synkit.Graph.Stereo.changes import StereoChange
|
|
408
|
+
|
|
409
|
+
potential_tetrahedral = self._potential_tetrahedral_atom_maps(product_graph)
|
|
410
|
+
for coupling in couplings:
|
|
411
|
+
if coupling.kind == "VICINAL_ELIMINATION":
|
|
412
|
+
planar_product = self._coupling_planar_from_tetrahedral_input(
|
|
413
|
+
coupling,
|
|
414
|
+
reactant_registry,
|
|
415
|
+
)
|
|
416
|
+
if planar_product is None:
|
|
417
|
+
return []
|
|
418
|
+
left_target, right_target = (
|
|
419
|
+
f"atom:{center}" for center in coupling.centers
|
|
420
|
+
)
|
|
421
|
+
next_states = []
|
|
422
|
+
for (
|
|
423
|
+
product_registry,
|
|
424
|
+
relabeled_changes,
|
|
425
|
+
branch_metadata,
|
|
426
|
+
outcome_metadata,
|
|
427
|
+
coupling_branch_metadata,
|
|
428
|
+
) in states:
|
|
429
|
+
registry = dict(product_registry)
|
|
430
|
+
changes = dict(relabeled_changes)
|
|
431
|
+
coupling_branches = dict(coupling_branch_metadata)
|
|
432
|
+
for target in (left_target, right_target):
|
|
433
|
+
descriptor = reactant_registry[target]
|
|
434
|
+
registry.pop(target, None)
|
|
435
|
+
changes[target] = StereoChange(
|
|
436
|
+
"BROKEN",
|
|
437
|
+
descriptor,
|
|
438
|
+
None,
|
|
439
|
+
)
|
|
440
|
+
registry[coupling.target] = planar_product
|
|
441
|
+
changes[coupling.target] = StereoChange(
|
|
442
|
+
"FORMED",
|
|
443
|
+
None,
|
|
444
|
+
planar_product,
|
|
445
|
+
)
|
|
446
|
+
coupling_branches[coupling.target] = {
|
|
447
|
+
"kind": coupling.kind,
|
|
448
|
+
"relation": coupling.relation,
|
|
449
|
+
"paired_input": True,
|
|
450
|
+
"joint_targets": [left_target, right_target],
|
|
451
|
+
}
|
|
452
|
+
next_states.append(
|
|
453
|
+
(
|
|
454
|
+
registry,
|
|
455
|
+
changes,
|
|
456
|
+
dict(branch_metadata),
|
|
457
|
+
dict(outcome_metadata),
|
|
458
|
+
coupling_branches,
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
states = next_states
|
|
462
|
+
self._check_stereo_branch_budget(len(states))
|
|
463
|
+
continue
|
|
464
|
+
planar_product = self._coupled_planar_product(coupling, product_graph)
|
|
465
|
+
planar_reactant = reactant_registry.get(coupling.target)
|
|
466
|
+
next_states = []
|
|
467
|
+
for (
|
|
468
|
+
product_registry,
|
|
469
|
+
relabeled_changes,
|
|
470
|
+
branch_metadata,
|
|
471
|
+
outcome_metadata,
|
|
472
|
+
coupling_branch_metadata,
|
|
473
|
+
) in states:
|
|
474
|
+
if planar_product is not None:
|
|
475
|
+
registry = dict(product_registry)
|
|
476
|
+
changes = dict(relabeled_changes)
|
|
477
|
+
registry[coupling.target] = planar_product
|
|
478
|
+
changes[coupling.target] = StereoChange(
|
|
479
|
+
"FORMED", None, planar_product
|
|
480
|
+
)
|
|
481
|
+
next_states.append(
|
|
482
|
+
(
|
|
483
|
+
registry,
|
|
484
|
+
changes,
|
|
485
|
+
dict(branch_metadata),
|
|
486
|
+
dict(outcome_metadata),
|
|
487
|
+
dict(coupling_branch_metadata),
|
|
488
|
+
)
|
|
489
|
+
)
|
|
490
|
+
continue
|
|
491
|
+
|
|
492
|
+
if not isinstance(planar_reactant, PlanarBondStereo):
|
|
493
|
+
next_states.append(
|
|
494
|
+
(
|
|
495
|
+
product_registry,
|
|
496
|
+
relabeled_changes,
|
|
497
|
+
branch_metadata,
|
|
498
|
+
outcome_metadata,
|
|
499
|
+
coupling_branch_metadata,
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
continue
|
|
503
|
+
|
|
504
|
+
pairs = coupling.tetrahedral_product_pairs(planar_reactant)
|
|
505
|
+
if potential_tetrahedral is not None:
|
|
506
|
+
pairs = tuple(
|
|
507
|
+
tuple(
|
|
508
|
+
descriptor
|
|
509
|
+
for descriptor in pair
|
|
510
|
+
if descriptor.center in potential_tetrahedral
|
|
511
|
+
)
|
|
512
|
+
for pair in pairs
|
|
513
|
+
)
|
|
514
|
+
if not any(pairs):
|
|
515
|
+
registry = dict(product_registry)
|
|
516
|
+
changes = dict(relabeled_changes)
|
|
517
|
+
registry.pop(coupling.target, None)
|
|
518
|
+
changes[coupling.target] = StereoChange(
|
|
519
|
+
"BROKEN", planar_reactant, None
|
|
520
|
+
)
|
|
521
|
+
next_states.append(
|
|
522
|
+
(
|
|
523
|
+
registry,
|
|
524
|
+
changes,
|
|
525
|
+
dict(branch_metadata),
|
|
526
|
+
dict(outcome_metadata),
|
|
527
|
+
dict(coupling_branch_metadata),
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
continue
|
|
531
|
+
|
|
532
|
+
for face_index, pair in enumerate(pairs):
|
|
533
|
+
registry = dict(product_registry)
|
|
534
|
+
changes = dict(relabeled_changes)
|
|
535
|
+
coupling_branches = dict(coupling_branch_metadata)
|
|
536
|
+
registry.pop(coupling.target, None)
|
|
537
|
+
changes[coupling.target] = StereoChange(
|
|
538
|
+
"BROKEN", planar_reactant, None
|
|
539
|
+
)
|
|
540
|
+
for descriptor in pair:
|
|
541
|
+
target = descriptor_id(descriptor)
|
|
542
|
+
registry[target] = descriptor
|
|
543
|
+
changes[target] = StereoChange("FORMED", None, descriptor)
|
|
544
|
+
coupling_branches[coupling.target] = {
|
|
545
|
+
"kind": coupling.kind,
|
|
546
|
+
"relation": coupling.relation,
|
|
547
|
+
"face_branch": face_index,
|
|
548
|
+
"joint_targets": [
|
|
549
|
+
descriptor_id(descriptor) for descriptor in pair
|
|
550
|
+
],
|
|
551
|
+
}
|
|
552
|
+
next_states.append(
|
|
553
|
+
(
|
|
554
|
+
registry,
|
|
555
|
+
changes,
|
|
556
|
+
dict(branch_metadata),
|
|
557
|
+
dict(outcome_metadata),
|
|
558
|
+
coupling_branches,
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
states = next_states
|
|
562
|
+
self._check_stereo_branch_budget(len(states))
|
|
563
|
+
return states
|
|
564
|
+
|
|
565
|
+
def _apply_stereo_rule_metadata(
|
|
566
|
+
self,
|
|
567
|
+
its: nx.Graph,
|
|
568
|
+
host: nx.Graph,
|
|
569
|
+
mapping: MappingDict,
|
|
570
|
+
) -> List[nx.Graph]:
|
|
571
|
+
"""Relabel rule stereo effects and expand declared product branches."""
|
|
572
|
+
if not self.rule.stereo_effects and not self.rule.stereo_couplings:
|
|
573
|
+
reactant_registry = dict(host.graph.get("stereo_descriptors", {}))
|
|
574
|
+
its.graph["stereo_descriptors"] = {
|
|
575
|
+
"reactant": dict(reactant_registry),
|
|
576
|
+
"product": dict(reactant_registry),
|
|
577
|
+
}
|
|
578
|
+
its.graph["stereo_changes"] = {}
|
|
579
|
+
its.graph["stereo_outcomes"] = {}
|
|
580
|
+
its.graph["stereo_couplings"] = {}
|
|
581
|
+
its.graph["stereo_coupling_branch"] = {}
|
|
582
|
+
its.graph["stereo_branch"] = {}
|
|
583
|
+
its.graph["stereo_branch_weight"] = 1.0
|
|
584
|
+
return [its]
|
|
585
|
+
|
|
586
|
+
from synkit.Graph.Stereo import descriptor_id
|
|
587
|
+
from synkit.Graph.Stereo.changes import StereoChange
|
|
588
|
+
|
|
589
|
+
translation: Dict[int, int] = {}
|
|
590
|
+
pattern = self.rule.left.raw
|
|
591
|
+
for pattern_node, host_node in mapping.items():
|
|
592
|
+
pattern_map = pattern.nodes[pattern_node].get("atom_map", pattern_node)
|
|
593
|
+
if isinstance(pattern_map, tuple) and len(pattern_map) == 2:
|
|
594
|
+
pattern_map = pattern_map[0]
|
|
595
|
+
host_map = host.nodes[host_node].get("atom_map") or host_node
|
|
596
|
+
if isinstance(pattern_map, int) and isinstance(host_map, int):
|
|
597
|
+
translation[pattern_map] = host_map
|
|
598
|
+
|
|
599
|
+
reactant_registry = dict(host.graph.get("stereo_descriptors", {}))
|
|
600
|
+
transition_registry = {
|
|
601
|
+
descriptor_id(
|
|
602
|
+
change.transition.relabel(translation)
|
|
603
|
+
): change.transition.relabel(translation)
|
|
604
|
+
for change in self.rule.stereo_effects.values()
|
|
605
|
+
if change.transition is not None
|
|
606
|
+
}
|
|
607
|
+
relabeled_coupling_values = []
|
|
608
|
+
relabeled_couplings = {}
|
|
609
|
+
for coupling_key in sorted(self.rule.stereo_couplings):
|
|
610
|
+
coupling = self.rule.stereo_couplings[coupling_key]
|
|
611
|
+
relabeled = coupling.relabel(translation)
|
|
612
|
+
relabeled_coupling_values.append(relabeled)
|
|
613
|
+
relabeled_couplings[relabeled.target] = relabeled.to_dict()
|
|
614
|
+
states: List[
|
|
615
|
+
Tuple[
|
|
616
|
+
Dict[str, Any],
|
|
617
|
+
Dict[str, StereoChange],
|
|
618
|
+
Dict[str, Dict[str, Any]],
|
|
619
|
+
Dict[str, Dict[str, Any]],
|
|
620
|
+
Dict[str, Dict[str, Any]],
|
|
621
|
+
]
|
|
622
|
+
] = [(dict(reactant_registry), {}, {}, {}, {})]
|
|
623
|
+
|
|
624
|
+
for rule_key in sorted(self.rule.stereo_effects):
|
|
625
|
+
change = self.rule.stereo_effects[rule_key]
|
|
626
|
+
applied_effect = change.relabel(translation)
|
|
627
|
+
before = applied_effect.before
|
|
628
|
+
after = applied_effect.after
|
|
629
|
+
transition = applied_effect.transition
|
|
630
|
+
before, after = self._propagated_stereo_effect(
|
|
631
|
+
applied_effect,
|
|
632
|
+
reactant_registry,
|
|
633
|
+
host,
|
|
634
|
+
)
|
|
635
|
+
outcome = self.rule.stereo_outcomes.get(rule_key)
|
|
636
|
+
alternatives = (
|
|
637
|
+
outcome.alternatives(after)
|
|
638
|
+
if outcome is not None and after is not None
|
|
639
|
+
else (after,)
|
|
640
|
+
)
|
|
641
|
+
branch_weights = (
|
|
642
|
+
tuple(outcome.weights or ()) if outcome is not None else (1.0,)
|
|
643
|
+
)
|
|
644
|
+
next_states = []
|
|
645
|
+
for (
|
|
646
|
+
product_registry,
|
|
647
|
+
relabeled_changes,
|
|
648
|
+
branch_metadata,
|
|
649
|
+
outcome_metadata,
|
|
650
|
+
coupling_branch_metadata,
|
|
651
|
+
) in states:
|
|
652
|
+
for branch_index, (alternative, weight) in enumerate(
|
|
653
|
+
zip(alternatives, branch_weights)
|
|
654
|
+
):
|
|
655
|
+
if float(weight) == 0.0:
|
|
656
|
+
continue
|
|
657
|
+
branch_registry = dict(product_registry)
|
|
658
|
+
branch_changes = dict(relabeled_changes)
|
|
659
|
+
branch_info = dict(branch_metadata)
|
|
660
|
+
branch_outcomes = dict(outcome_metadata)
|
|
661
|
+
if before is not None:
|
|
662
|
+
branch_registry.pop(descriptor_id(before), None)
|
|
663
|
+
if alternative is not None:
|
|
664
|
+
branch_registry[descriptor_id(alternative)] = alternative
|
|
665
|
+
key_descriptor = alternative or before or transition
|
|
666
|
+
if key_descriptor is not None:
|
|
667
|
+
target = descriptor_id(key_descriptor)
|
|
668
|
+
reference_mapping = (
|
|
669
|
+
dict(applied_effect.reference_mapping)
|
|
670
|
+
if applied_effect.alignment.status == "explicit"
|
|
671
|
+
else None
|
|
672
|
+
)
|
|
673
|
+
branch_changes[target] = StereoChange.from_endpoints(
|
|
674
|
+
before,
|
|
675
|
+
alternative,
|
|
676
|
+
transition,
|
|
677
|
+
reference_mapping=reference_mapping,
|
|
678
|
+
)
|
|
679
|
+
if outcome is not None:
|
|
680
|
+
branch_outcomes[target] = outcome.to_dict()
|
|
681
|
+
branch_info[target] = {
|
|
682
|
+
"kind": outcome.kind,
|
|
683
|
+
"branch_index": branch_index,
|
|
684
|
+
"weight": weight,
|
|
685
|
+
}
|
|
686
|
+
next_states.append(
|
|
687
|
+
(
|
|
688
|
+
branch_registry,
|
|
689
|
+
branch_changes,
|
|
690
|
+
branch_info,
|
|
691
|
+
branch_outcomes,
|
|
692
|
+
dict(coupling_branch_metadata),
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
states = next_states
|
|
696
|
+
self._check_stereo_branch_budget(len(states))
|
|
697
|
+
|
|
698
|
+
if relabeled_coupling_values:
|
|
699
|
+
product_graph = ITSReverter(its).to_product_graph()
|
|
700
|
+
states = self._apply_stereo_couplings(
|
|
701
|
+
states,
|
|
702
|
+
relabeled_coupling_values,
|
|
703
|
+
reactant_registry,
|
|
704
|
+
product_graph,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
results = []
|
|
708
|
+
for state_index, (
|
|
709
|
+
product_registry,
|
|
710
|
+
relabeled_changes,
|
|
711
|
+
branch_metadata,
|
|
712
|
+
outcome_metadata,
|
|
713
|
+
coupling_branch_metadata,
|
|
714
|
+
) in enumerate(states):
|
|
715
|
+
# The candidate is no longer needed after metadata application.
|
|
716
|
+
# Reuse it for the final branch and copy only genuine alternatives.
|
|
717
|
+
branch = its if state_index == len(states) - 1 else deepcopy(its)
|
|
718
|
+
branch.graph["stereo_descriptors"] = {
|
|
719
|
+
"reactant": dict(reactant_registry),
|
|
720
|
+
"product": product_registry,
|
|
721
|
+
}
|
|
722
|
+
if transition_registry:
|
|
723
|
+
branch.graph["stereo_descriptors"]["transition"] = dict(
|
|
724
|
+
transition_registry
|
|
725
|
+
)
|
|
726
|
+
branch.graph["stereo_changes"] = relabeled_changes
|
|
727
|
+
branch.graph["stereo_outcomes"] = outcome_metadata
|
|
728
|
+
branch.graph["stereo_couplings"] = relabeled_couplings
|
|
729
|
+
branch.graph["stereo_coupling_branch"] = coupling_branch_metadata
|
|
730
|
+
branch.graph["stereo_branch"] = branch_metadata
|
|
731
|
+
total_weight = 1.0
|
|
732
|
+
for metadata in branch_metadata.values():
|
|
733
|
+
total_weight *= float(metadata["weight"])
|
|
734
|
+
branch.graph["stereo_branch_weight"] = total_weight
|
|
735
|
+
branch.graph["stereo_aggregate_weight"] = total_weight
|
|
736
|
+
branch.graph["stereo_branch_multiplicity"] = 1
|
|
737
|
+
branch.graph["stereo_branch_path"] = tuple(
|
|
738
|
+
(
|
|
739
|
+
target,
|
|
740
|
+
metadata["branch_index"],
|
|
741
|
+
)
|
|
742
|
+
for target, metadata in sorted(branch_metadata.items())
|
|
743
|
+
)
|
|
744
|
+
results.append(branch)
|
|
745
|
+
self._check_stereo_branch_budget(len(results))
|
|
746
|
+
self._stereo_branch_count += len(results)
|
|
747
|
+
return results
|
|
748
|
+
|
|
749
|
+
@staticmethod
|
|
750
|
+
def _validate_product_stereo_registry(its: nx.Graph) -> bool:
|
|
751
|
+
"""Validate product descriptors after the requested H representation."""
|
|
752
|
+
from synkit.Graph.Stereo import descriptor_graph_support_errors
|
|
753
|
+
|
|
754
|
+
layers = its.graph.get("stereo_descriptors", {})
|
|
755
|
+
product_registry = dict(layers.get("product", {}))
|
|
756
|
+
if not product_registry:
|
|
757
|
+
return True
|
|
758
|
+
|
|
759
|
+
changes = its.graph.get("stereo_changes", {})
|
|
760
|
+
product = ITSReverter(its).to_product_graph()
|
|
761
|
+
invalid = {
|
|
762
|
+
key: descriptor_graph_support_errors(
|
|
763
|
+
product,
|
|
764
|
+
descriptor,
|
|
765
|
+
registry_key=key,
|
|
766
|
+
)
|
|
767
|
+
for key, descriptor in product_registry.items()
|
|
768
|
+
}
|
|
769
|
+
invalid = {key: errors for key, errors in invalid.items() if errors}
|
|
770
|
+
if any(key in changes for key in invalid):
|
|
771
|
+
return False
|
|
772
|
+
if invalid:
|
|
773
|
+
layers = dict(layers)
|
|
774
|
+
layers["product"] = {
|
|
775
|
+
key: descriptor
|
|
776
|
+
for key, descriptor in product_registry.items()
|
|
777
|
+
if key not in invalid
|
|
778
|
+
}
|
|
779
|
+
its.graph["stereo_descriptors"] = layers
|
|
780
|
+
return True
|