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,345 @@
|
|
|
1
|
+
"""Narrow chemistry policies used during reactor mapping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
from networkx.algorithms.isomorphism import (
|
|
9
|
+
GraphMatcher,
|
|
10
|
+
categorical_edge_match,
|
|
11
|
+
categorical_node_match,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_PRIMITIVE_TYPES = frozenset((str, int, float, bool, type(None)))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _freeze(
|
|
18
|
+
value: Any,
|
|
19
|
+
cache: dict[int, tuple[Any, Any]] | None = None,
|
|
20
|
+
) -> Any:
|
|
21
|
+
if type(value) in _PRIMITIVE_TYPES:
|
|
22
|
+
return value
|
|
23
|
+
cache_key = id(value)
|
|
24
|
+
if cache is not None:
|
|
25
|
+
cached = cache.get(cache_key)
|
|
26
|
+
if cached is not None and cached[0] is value:
|
|
27
|
+
return cached[1]
|
|
28
|
+
if isinstance(value, dict):
|
|
29
|
+
result = tuple(
|
|
30
|
+
sorted((key, _freeze(item, cache)) for key, item in value.items())
|
|
31
|
+
)
|
|
32
|
+
elif isinstance(value, (list, tuple)):
|
|
33
|
+
shallow = tuple(value)
|
|
34
|
+
try:
|
|
35
|
+
hash(shallow)
|
|
36
|
+
except TypeError:
|
|
37
|
+
result = tuple(_freeze(item, cache) for item in value)
|
|
38
|
+
else:
|
|
39
|
+
# Every recursively mutable child makes the shallow tuple
|
|
40
|
+
# unhashable. If hashing succeeds, recursive normalization would
|
|
41
|
+
# preserve each child and produce exactly this tuple.
|
|
42
|
+
result = shallow
|
|
43
|
+
elif isinstance(value, set):
|
|
44
|
+
result = tuple(sorted((_freeze(item, cache) for item in value), key=repr))
|
|
45
|
+
else:
|
|
46
|
+
result = value
|
|
47
|
+
if cache is not None:
|
|
48
|
+
cache[cache_key] = (value, result)
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def contextual_electron_pattern_graph(
|
|
53
|
+
pattern: nx.Graph,
|
|
54
|
+
*,
|
|
55
|
+
reaction_center: nx.Graph,
|
|
56
|
+
template_format: str | None,
|
|
57
|
+
) -> nx.Graph:
|
|
58
|
+
"""Record query attributes made unknown by stripped local context."""
|
|
59
|
+
if template_format != "tuple":
|
|
60
|
+
return pattern
|
|
61
|
+
|
|
62
|
+
decorated = None
|
|
63
|
+
for node, attrs in pattern.nodes(data=True):
|
|
64
|
+
neighbors = attrs.get("neighbors", ())
|
|
65
|
+
explicit_hydrogens = sum(
|
|
66
|
+
pattern.nodes[neighbor].get("element") == "H"
|
|
67
|
+
for neighbor in pattern.neighbors(node)
|
|
68
|
+
)
|
|
69
|
+
hidden_hydrogen = (
|
|
70
|
+
isinstance(neighbors, (list, tuple))
|
|
71
|
+
and neighbors.count("H") > explicit_hydrogens
|
|
72
|
+
)
|
|
73
|
+
rc_attrs = reaction_center.nodes.get(node, {})
|
|
74
|
+
radical = rc_attrs.get("radical")
|
|
75
|
+
radical_changes = (
|
|
76
|
+
isinstance(radical, tuple)
|
|
77
|
+
and len(radical) == 2
|
|
78
|
+
and radical[0] != radical[1]
|
|
79
|
+
)
|
|
80
|
+
if not hidden_hydrogen or radical_changes:
|
|
81
|
+
continue
|
|
82
|
+
if decorated is None:
|
|
83
|
+
decorated = pattern.copy()
|
|
84
|
+
policies = dict(decorated.nodes[node].get("_query_attribute_policies", {}))
|
|
85
|
+
policies["radical"] = "unknown"
|
|
86
|
+
decorated.nodes[node]["_query_attribute_policies"] = policies
|
|
87
|
+
return decorated or pattern
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def has_heavy_cross_component_correlation(
|
|
91
|
+
pattern: nx.Graph,
|
|
92
|
+
reaction_center: nx.Graph,
|
|
93
|
+
) -> bool:
|
|
94
|
+
"""Return whether product bonds jointly couple heavy source components."""
|
|
95
|
+
components = [
|
|
96
|
+
frozenset(component) for component in nx.connected_components(pattern)
|
|
97
|
+
]
|
|
98
|
+
if len(components) < 2:
|
|
99
|
+
return False
|
|
100
|
+
component_index = {
|
|
101
|
+
node: index for index, component in enumerate(components) for node in component
|
|
102
|
+
}
|
|
103
|
+
heavy_components = {
|
|
104
|
+
index
|
|
105
|
+
for index, component in enumerate(components)
|
|
106
|
+
if any(pattern.nodes[node].get("element") != "H" for node in component)
|
|
107
|
+
}
|
|
108
|
+
for left, right, attrs in reaction_center.edges(data=True):
|
|
109
|
+
if left not in component_index or right not in component_index:
|
|
110
|
+
continue
|
|
111
|
+
left_component = component_index[left]
|
|
112
|
+
right_component = component_index[right]
|
|
113
|
+
if (
|
|
114
|
+
left_component == right_component
|
|
115
|
+
or left_component not in heavy_components
|
|
116
|
+
or right_component not in heavy_components
|
|
117
|
+
):
|
|
118
|
+
continue
|
|
119
|
+
order: Any = attrs.get("order")
|
|
120
|
+
if (
|
|
121
|
+
isinstance(order, tuple)
|
|
122
|
+
and len(order) == 2
|
|
123
|
+
and float(order[0]) == 0.0
|
|
124
|
+
and float(order[1]) > 0.0
|
|
125
|
+
):
|
|
126
|
+
return True
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _wl_proves_transition_asymmetric(transition: nx.Graph) -> bool:
|
|
131
|
+
"""Return whether exact color refinement proves a trivial automorphism group.
|
|
132
|
+
|
|
133
|
+
Every attributed automorphism preserves each 1-WL color class. Therefore,
|
|
134
|
+
if refinement makes every vertex color unique, only the identity
|
|
135
|
+
automorphism can remain. A non-discrete partition proves nothing and keeps
|
|
136
|
+
the complete GraphMatcher path.
|
|
137
|
+
"""
|
|
138
|
+
if len(transition) < 2:
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
node_palette: dict[Any, int] = {}
|
|
142
|
+
colors = {}
|
|
143
|
+
for node, attrs in transition.nodes(data=True):
|
|
144
|
+
role = attrs.get("_transition_node_role", ())
|
|
145
|
+
color = node_palette.get(role)
|
|
146
|
+
if color is None:
|
|
147
|
+
color = len(node_palette) + 1
|
|
148
|
+
node_palette[role] = color
|
|
149
|
+
colors[node] = color
|
|
150
|
+
if len(set(colors.values())) == len(transition):
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
edge_palette: dict[Any, int] = {}
|
|
154
|
+
edge_colors = {}
|
|
155
|
+
for left, right, attrs in transition.edges(data=True):
|
|
156
|
+
role = attrs.get("_transition_role", ())
|
|
157
|
+
color = edge_palette.get(role)
|
|
158
|
+
if color is None:
|
|
159
|
+
color = len(edge_palette) + 1
|
|
160
|
+
edge_palette[role] = color
|
|
161
|
+
edge_colors[frozenset((left, right))] = color
|
|
162
|
+
|
|
163
|
+
for _ in range(len(transition)):
|
|
164
|
+
signatures = {
|
|
165
|
+
node: (
|
|
166
|
+
colors[node],
|
|
167
|
+
tuple(
|
|
168
|
+
sorted(
|
|
169
|
+
(
|
|
170
|
+
edge_colors[frozenset((node, neighbor))],
|
|
171
|
+
colors[neighbor],
|
|
172
|
+
)
|
|
173
|
+
for neighbor in transition.neighbors(node)
|
|
174
|
+
)
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
for node in transition
|
|
178
|
+
}
|
|
179
|
+
palette: dict[Any, int] = {}
|
|
180
|
+
refined = {}
|
|
181
|
+
for node, signature in signatures.items():
|
|
182
|
+
color = palette.get(signature)
|
|
183
|
+
if color is None:
|
|
184
|
+
color = len(palette) + 1
|
|
185
|
+
palette[signature] = color
|
|
186
|
+
refined[node] = color
|
|
187
|
+
if len(palette) == len(transition):
|
|
188
|
+
return True
|
|
189
|
+
if all(refined[node] == colors[node] for node in transition):
|
|
190
|
+
return False
|
|
191
|
+
if len(set(refined.values())) == len(set(colors.values())):
|
|
192
|
+
return False
|
|
193
|
+
colors = refined
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def deduplicate_joint_rule_mappings( # noqa: C901
|
|
198
|
+
mappings: list[dict[Any, Any]],
|
|
199
|
+
pattern: nx.Graph,
|
|
200
|
+
reaction_center: nx.Graph,
|
|
201
|
+
*,
|
|
202
|
+
node_attrs: list[str],
|
|
203
|
+
edge_attrs: list[str],
|
|
204
|
+
fixed_nodes: frozenset[Any] = frozenset(),
|
|
205
|
+
max_automorphisms: int = 256,
|
|
206
|
+
) -> list[dict[Any, Any]]:
|
|
207
|
+
"""Quotient mappings only by complete transition-graph automorphisms."""
|
|
208
|
+
if len(mappings) < 2:
|
|
209
|
+
return mappings
|
|
210
|
+
pattern_nodes = frozenset(pattern)
|
|
211
|
+
if any(not pattern_nodes.issubset(mapping) for mapping in mappings):
|
|
212
|
+
# Partial-template mappings are not closed under whole-rule
|
|
213
|
+
# automorphisms, so the group action below is undefined for them.
|
|
214
|
+
return mappings
|
|
215
|
+
|
|
216
|
+
transition = pattern.copy()
|
|
217
|
+
identity_cache: dict[int, tuple[Any, Any]] = {}
|
|
218
|
+
transition_node_keys = (
|
|
219
|
+
"element",
|
|
220
|
+
"aromatic",
|
|
221
|
+
"hcount",
|
|
222
|
+
"charge",
|
|
223
|
+
"radical",
|
|
224
|
+
"neighbors",
|
|
225
|
+
"lone_pairs",
|
|
226
|
+
"valence_electrons",
|
|
227
|
+
"present",
|
|
228
|
+
"charge_model_consistent",
|
|
229
|
+
)
|
|
230
|
+
for node, attrs in transition.nodes(data=True):
|
|
231
|
+
reaction_attrs = reaction_center.nodes.get(node, {})
|
|
232
|
+
attrs["_transition_node_role"] = _freeze(
|
|
233
|
+
(
|
|
234
|
+
"source",
|
|
235
|
+
tuple((key, attrs.get(key)) for key in node_attrs),
|
|
236
|
+
"reaction_center",
|
|
237
|
+
tuple((key, reaction_attrs.get(key)) for key in transition_node_keys),
|
|
238
|
+
),
|
|
239
|
+
identity_cache,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# An attributed automorphism must preserve every node role. If those
|
|
243
|
+
# roles already form a discrete partition, every chemical pattern node
|
|
244
|
+
# is fixed. Any later virtual hydrogen-pair nodes can then permute only
|
|
245
|
+
# among themselves and cannot change a mapping orbit.
|
|
246
|
+
if len(
|
|
247
|
+
{attrs["_transition_node_role"] for _, attrs in transition.nodes(data=True)}
|
|
248
|
+
) == len(transition):
|
|
249
|
+
return mappings
|
|
250
|
+
transition_edge_keys = (
|
|
251
|
+
"order",
|
|
252
|
+
"kekule_order",
|
|
253
|
+
"sigma_order",
|
|
254
|
+
"pi_order",
|
|
255
|
+
"aromatic",
|
|
256
|
+
"standard_order",
|
|
257
|
+
)
|
|
258
|
+
for left, right, attrs in reaction_center.edges(data=True):
|
|
259
|
+
if left not in transition or right not in transition:
|
|
260
|
+
continue
|
|
261
|
+
role = _freeze(
|
|
262
|
+
(
|
|
263
|
+
"reaction_center",
|
|
264
|
+
tuple((key, attrs.get(key)) for key in transition_edge_keys),
|
|
265
|
+
),
|
|
266
|
+
identity_cache,
|
|
267
|
+
)
|
|
268
|
+
if transition.has_edge(left, right):
|
|
269
|
+
transition.edges[left, right]["_transition_role"] = role
|
|
270
|
+
else:
|
|
271
|
+
transition.add_edge(
|
|
272
|
+
left,
|
|
273
|
+
right,
|
|
274
|
+
_transition_role=role,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Explicit-H transfer pair IDs are provenance labels, but their incidence
|
|
278
|
+
# relation is chemical. Encode that relation with anonymous virtual nodes
|
|
279
|
+
# so automorphisms may rename a pair while preserving its donor/recipient
|
|
280
|
+
# connectivity exactly.
|
|
281
|
+
pair_incidence: dict[Any, dict[Any, set[str]]] = {}
|
|
282
|
+
for node, attrs in reaction_center.nodes(data=True):
|
|
283
|
+
for side, key in (("left", "h_pairs_left"), ("right", "h_pairs_right")):
|
|
284
|
+
for pair_id in attrs.get(key, ()):
|
|
285
|
+
pair_incidence.setdefault(pair_id, {}).setdefault(node, set()).add(side)
|
|
286
|
+
for ordinal, (_, incidence) in enumerate(
|
|
287
|
+
sorted(pair_incidence.items(), key=lambda item: repr(item[0]))
|
|
288
|
+
):
|
|
289
|
+
pair_node = ("_synkit_h_pair_role", ordinal)
|
|
290
|
+
transition.add_node(
|
|
291
|
+
pair_node,
|
|
292
|
+
_transition_node_role=("hydrogen_pair",),
|
|
293
|
+
)
|
|
294
|
+
for node, sides in incidence.items():
|
|
295
|
+
if node in transition:
|
|
296
|
+
transition.add_edge(
|
|
297
|
+
pair_node,
|
|
298
|
+
node,
|
|
299
|
+
_transition_role=("hydrogen_pair", tuple(sorted(sides))),
|
|
300
|
+
)
|
|
301
|
+
for _, _, attrs in transition.edges(data=True):
|
|
302
|
+
attrs.setdefault(
|
|
303
|
+
"_transition_role",
|
|
304
|
+
_freeze(
|
|
305
|
+
(
|
|
306
|
+
"source",
|
|
307
|
+
tuple((key, attrs.get(key)) for key in edge_attrs),
|
|
308
|
+
),
|
|
309
|
+
identity_cache,
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if _wl_proves_transition_asymmetric(transition):
|
|
314
|
+
return mappings
|
|
315
|
+
|
|
316
|
+
matcher = GraphMatcher(
|
|
317
|
+
transition,
|
|
318
|
+
transition,
|
|
319
|
+
node_match=categorical_node_match("_transition_node_role", ()),
|
|
320
|
+
edge_match=categorical_edge_match("_transition_role", ()),
|
|
321
|
+
)
|
|
322
|
+
automorphisms = []
|
|
323
|
+
for automorphism in matcher.isomorphisms_iter():
|
|
324
|
+
if any(automorphism[node] != node for node in fixed_nodes):
|
|
325
|
+
continue
|
|
326
|
+
automorphisms.append(automorphism)
|
|
327
|
+
if len(automorphisms) > max_automorphisms:
|
|
328
|
+
return mappings
|
|
329
|
+
|
|
330
|
+
nodes = sorted(pattern.nodes, key=repr)
|
|
331
|
+
seen = set()
|
|
332
|
+
unique = []
|
|
333
|
+
for mapping in mappings:
|
|
334
|
+
signature = min(
|
|
335
|
+
(
|
|
336
|
+
tuple(mapping[automorphism[node]] for node in nodes)
|
|
337
|
+
for automorphism in automorphisms
|
|
338
|
+
),
|
|
339
|
+
key=repr,
|
|
340
|
+
)
|
|
341
|
+
if signature in seen:
|
|
342
|
+
continue
|
|
343
|
+
seen.add(signature)
|
|
344
|
+
unique.append(mapping)
|
|
345
|
+
return unique
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Exact mapping quotients used by the synthesis reactor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import Any, Dict, List, Mapping, Tuple
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
|
|
10
|
+
from synkit.Graph.Matcher.subgraph_matcher import electron_aware_edge_match
|
|
11
|
+
|
|
12
|
+
MappingDict = Dict[Any, Any]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def deduplicate_port_witness_mappings(
|
|
16
|
+
mappings: List[MappingDict],
|
|
17
|
+
structural_pattern: nx.Graph,
|
|
18
|
+
) -> List[MappingDict]:
|
|
19
|
+
"""Project existential stereo-port witnesses from accepted mappings."""
|
|
20
|
+
structural_nodes = frozenset(structural_pattern)
|
|
21
|
+
if len(mappings) < 2 or not any(
|
|
22
|
+
set(mapping) - structural_nodes for mapping in mappings
|
|
23
|
+
):
|
|
24
|
+
return mappings
|
|
25
|
+
seen = set()
|
|
26
|
+
unique = []
|
|
27
|
+
for mapping in mappings:
|
|
28
|
+
signature = tuple(
|
|
29
|
+
sorted(
|
|
30
|
+
((node, mapping[node]) for node in structural_nodes if node in mapping),
|
|
31
|
+
key=repr,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
if signature in seen:
|
|
35
|
+
continue
|
|
36
|
+
seen.add(signature)
|
|
37
|
+
unique.append(mapping)
|
|
38
|
+
return unique
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def deduplicate_exact_pattern_mappings(
|
|
42
|
+
mappings: List[MappingDict],
|
|
43
|
+
pattern: nx.Graph,
|
|
44
|
+
*,
|
|
45
|
+
node_attrs: List[str],
|
|
46
|
+
edge_attrs: List[str],
|
|
47
|
+
max_automorphisms: int = 256,
|
|
48
|
+
) -> List[MappingDict]:
|
|
49
|
+
"""Quotient complete mappings by the exact attributed group action."""
|
|
50
|
+
if len(mappings) < 2 or pattern.is_multigraph():
|
|
51
|
+
return mappings
|
|
52
|
+
pattern_nodes = tuple(sorted(pattern, key=repr))
|
|
53
|
+
if any(not set(pattern_nodes).issubset(mapping) for mapping in mappings):
|
|
54
|
+
return mappings
|
|
55
|
+
|
|
56
|
+
def node_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
57
|
+
return all(
|
|
58
|
+
left.get(attr, 0 if attr in {"hcount", "lone_pairs"} else None)
|
|
59
|
+
== right.get(attr, 0 if attr in {"hcount", "lone_pairs"} else None)
|
|
60
|
+
for attr in node_attrs
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def edge_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
64
|
+
return all(left.get(attr) == right.get(attr) for attr in edge_attrs)
|
|
65
|
+
|
|
66
|
+
matcher_cls = (
|
|
67
|
+
nx.algorithms.isomorphism.DiGraphMatcher
|
|
68
|
+
if pattern.is_directed()
|
|
69
|
+
else nx.algorithms.isomorphism.GraphMatcher
|
|
70
|
+
)
|
|
71
|
+
matcher = matcher_cls(
|
|
72
|
+
pattern,
|
|
73
|
+
pattern,
|
|
74
|
+
node_match=node_match,
|
|
75
|
+
edge_match=edge_match,
|
|
76
|
+
)
|
|
77
|
+
automorphisms = []
|
|
78
|
+
for automorphism in matcher.isomorphisms_iter():
|
|
79
|
+
automorphisms.append(automorphism)
|
|
80
|
+
if len(automorphisms) > max_automorphisms:
|
|
81
|
+
return mappings
|
|
82
|
+
|
|
83
|
+
seen = set()
|
|
84
|
+
unique = []
|
|
85
|
+
for mapping in mappings:
|
|
86
|
+
signature = min(
|
|
87
|
+
(
|
|
88
|
+
tuple(mapping[automorphism[node]] for node in pattern_nodes)
|
|
89
|
+
for automorphism in automorphisms
|
|
90
|
+
),
|
|
91
|
+
key=repr,
|
|
92
|
+
)
|
|
93
|
+
if signature in seen:
|
|
94
|
+
continue
|
|
95
|
+
seen.add(signature)
|
|
96
|
+
unique.append(mapping)
|
|
97
|
+
return unique
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def deduplicate_free_host_component_mappings(
|
|
101
|
+
mappings: List[MappingDict],
|
|
102
|
+
host: nx.Graph,
|
|
103
|
+
*,
|
|
104
|
+
node_attrs: List[str],
|
|
105
|
+
edge_attrs: List[str],
|
|
106
|
+
max_automorphisms: int = 256,
|
|
107
|
+
) -> List[MappingDict]:
|
|
108
|
+
"""Quotient by exact automorphisms outside the largest host component."""
|
|
109
|
+
if len(mappings) < 2 or host.is_multigraph():
|
|
110
|
+
return mappings
|
|
111
|
+
component_nodes = list(
|
|
112
|
+
nx.weakly_connected_components(host)
|
|
113
|
+
if host.is_directed()
|
|
114
|
+
else nx.connected_components(host)
|
|
115
|
+
)
|
|
116
|
+
if len(component_nodes) < 2:
|
|
117
|
+
return mappings
|
|
118
|
+
anchor_index = max(
|
|
119
|
+
range(len(component_nodes)),
|
|
120
|
+
key=lambda index: (len(component_nodes[index]), -index),
|
|
121
|
+
)
|
|
122
|
+
component_index = {
|
|
123
|
+
node: index
|
|
124
|
+
for index, component in enumerate(component_nodes)
|
|
125
|
+
for node in component
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def node_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
129
|
+
return all(
|
|
130
|
+
left.get(attr, 0 if attr in {"hcount", "lone_pairs"} else None)
|
|
131
|
+
== right.get(attr, 0 if attr in {"hcount", "lone_pairs"} else None)
|
|
132
|
+
for attr in node_attrs
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def edge_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
136
|
+
return electron_aware_edge_match(
|
|
137
|
+
left, right, edge_attrs
|
|
138
|
+
) and electron_aware_edge_match(right, left, edge_attrs)
|
|
139
|
+
|
|
140
|
+
automorphisms = {}
|
|
141
|
+
matcher_cls = (
|
|
142
|
+
nx.algorithms.isomorphism.DiGraphMatcher
|
|
143
|
+
if host.is_directed()
|
|
144
|
+
else nx.algorithms.isomorphism.GraphMatcher
|
|
145
|
+
)
|
|
146
|
+
for index, nodes in enumerate(component_nodes):
|
|
147
|
+
if index == anchor_index:
|
|
148
|
+
continue
|
|
149
|
+
component = host.subgraph(nodes)
|
|
150
|
+
group = []
|
|
151
|
+
matcher = matcher_cls(
|
|
152
|
+
component,
|
|
153
|
+
component,
|
|
154
|
+
node_match=node_match,
|
|
155
|
+
edge_match=edge_match,
|
|
156
|
+
)
|
|
157
|
+
for automorphism in matcher.isomorphisms_iter():
|
|
158
|
+
group.append(automorphism)
|
|
159
|
+
if len(group) > max_automorphisms:
|
|
160
|
+
return mappings
|
|
161
|
+
automorphisms[index] = group
|
|
162
|
+
|
|
163
|
+
seen = set()
|
|
164
|
+
unique = []
|
|
165
|
+
for mapping in mappings:
|
|
166
|
+
fixed = tuple(
|
|
167
|
+
sorted(
|
|
168
|
+
(
|
|
169
|
+
(pattern_node, host_node)
|
|
170
|
+
for pattern_node, host_node in mapping.items()
|
|
171
|
+
if component_index[host_node] == anchor_index
|
|
172
|
+
),
|
|
173
|
+
key=repr,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
free = []
|
|
177
|
+
for index, group in automorphisms.items():
|
|
178
|
+
placements = tuple(
|
|
179
|
+
(pattern_node, host_node)
|
|
180
|
+
for pattern_node, host_node in mapping.items()
|
|
181
|
+
if component_index[host_node] == index
|
|
182
|
+
)
|
|
183
|
+
variants = [
|
|
184
|
+
tuple(
|
|
185
|
+
sorted(
|
|
186
|
+
(
|
|
187
|
+
(pattern_node, automorphism[host_node])
|
|
188
|
+
for pattern_node, host_node in placements
|
|
189
|
+
),
|
|
190
|
+
key=repr,
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
for automorphism in group
|
|
194
|
+
]
|
|
195
|
+
free.append(min(variants, key=repr))
|
|
196
|
+
signature = fixed, tuple(free)
|
|
197
|
+
if signature in seen:
|
|
198
|
+
continue
|
|
199
|
+
seen.add(signature)
|
|
200
|
+
unique.append(mapping)
|
|
201
|
+
return unique
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def deduplicate_pure_coupling_mappings( # noqa: C901
|
|
205
|
+
mappings: List[MappingDict],
|
|
206
|
+
pattern: nx.Graph,
|
|
207
|
+
rule: Any,
|
|
208
|
+
*,
|
|
209
|
+
node_attrs: List[str],
|
|
210
|
+
edge_attrs: List[str],
|
|
211
|
+
max_automorphisms: int = 256,
|
|
212
|
+
) -> List[MappingDict]:
|
|
213
|
+
"""Deduplicate equivalent applications of a pure stereo coupling rule."""
|
|
214
|
+
if (
|
|
215
|
+
len(mappings) < 2
|
|
216
|
+
or not rule.stereo_couplings
|
|
217
|
+
or rule.stereo_guards
|
|
218
|
+
or rule.stereo_effects
|
|
219
|
+
or rule.stereo_outcomes
|
|
220
|
+
):
|
|
221
|
+
return mappings
|
|
222
|
+
|
|
223
|
+
pattern_by_map = {}
|
|
224
|
+
for node, attrs in pattern.nodes(data=True):
|
|
225
|
+
atom_map = attrs.get("atom_map", node)
|
|
226
|
+
if isinstance(atom_map, int):
|
|
227
|
+
pattern_by_map[atom_map] = node
|
|
228
|
+
|
|
229
|
+
dependencies = {
|
|
230
|
+
atom_map
|
|
231
|
+
for coupling in rule.stereo_couplings.values()
|
|
232
|
+
for atom_map in coupling.dependencies
|
|
233
|
+
}
|
|
234
|
+
changed_maps = set()
|
|
235
|
+
for left, right, attrs in rule.rc.raw.edges(data=True):
|
|
236
|
+
order = attrs.get("order")
|
|
237
|
+
if not (isinstance(order, tuple) and len(order) == 2 and order[0] != order[1]):
|
|
238
|
+
continue
|
|
239
|
+
for node in (left, right):
|
|
240
|
+
atom_map = rule.rc.raw.nodes[node].get("atom_map", node)
|
|
241
|
+
if isinstance(atom_map, tuple):
|
|
242
|
+
atom_map = atom_map[0]
|
|
243
|
+
if isinstance(atom_map, int):
|
|
244
|
+
changed_maps.add(atom_map)
|
|
245
|
+
if not changed_maps or not changed_maps <= dependencies:
|
|
246
|
+
return mappings
|
|
247
|
+
if not dependencies <= set(pattern_by_map):
|
|
248
|
+
return mappings
|
|
249
|
+
|
|
250
|
+
decorated = pattern.copy()
|
|
251
|
+
coupling_roles: Dict[Any, List[Tuple[str, str]]] = defaultdict(list)
|
|
252
|
+
coupling_nodes = []
|
|
253
|
+
for key, coupling in sorted(rule.stereo_couplings.items()):
|
|
254
|
+
centers = tuple(pattern_by_map[value] for value in coupling.centers)
|
|
255
|
+
ligands = tuple(pattern_by_map[value] for value in coupling.ligands)
|
|
256
|
+
coupling_nodes.append((key, coupling, centers, ligands))
|
|
257
|
+
for node in centers:
|
|
258
|
+
coupling_roles[node].append((key, "center"))
|
|
259
|
+
for node in ligands:
|
|
260
|
+
coupling_roles[node].append((key, "ligand"))
|
|
261
|
+
for node in decorated:
|
|
262
|
+
decorated.nodes[node]["_coupling_role"] = tuple(
|
|
263
|
+
sorted(coupling_roles.get(node, ()))
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
exact_node_attrs = [*node_attrs, "_coupling_role"]
|
|
267
|
+
|
|
268
|
+
def node_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
269
|
+
return all(left.get(attr) == right.get(attr) for attr in exact_node_attrs)
|
|
270
|
+
|
|
271
|
+
def edge_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
272
|
+
return all(left.get(attr) == right.get(attr) for attr in edge_attrs)
|
|
273
|
+
|
|
274
|
+
matcher = nx.algorithms.isomorphism.GraphMatcher(
|
|
275
|
+
decorated,
|
|
276
|
+
decorated,
|
|
277
|
+
node_match=node_match,
|
|
278
|
+
edge_match=edge_match,
|
|
279
|
+
)
|
|
280
|
+
automorphisms = []
|
|
281
|
+
for automorphism in matcher.isomorphisms_iter():
|
|
282
|
+
automorphisms.append(automorphism)
|
|
283
|
+
if len(automorphisms) > max_automorphisms:
|
|
284
|
+
return mappings
|
|
285
|
+
|
|
286
|
+
seen = set()
|
|
287
|
+
unique = []
|
|
288
|
+
for mapping in mappings:
|
|
289
|
+
variants = []
|
|
290
|
+
for automorphism in automorphisms:
|
|
291
|
+
coupling_signature = []
|
|
292
|
+
for key, coupling, centers, ligands in coupling_nodes:
|
|
293
|
+
mapped_locus = tuple(
|
|
294
|
+
(
|
|
295
|
+
mapping[automorphism[center]],
|
|
296
|
+
mapping[automorphism[ligand]],
|
|
297
|
+
)
|
|
298
|
+
for center, ligand in zip(centers, ligands)
|
|
299
|
+
)
|
|
300
|
+
coupling_signature.append(
|
|
301
|
+
(key, coupling.kind, coupling.relation, mapped_locus)
|
|
302
|
+
)
|
|
303
|
+
variants.append(tuple(coupling_signature))
|
|
304
|
+
signature = min(variants, key=repr)
|
|
305
|
+
if signature in seen:
|
|
306
|
+
continue
|
|
307
|
+
seen.add(signature)
|
|
308
|
+
unique.append(mapping)
|
|
309
|
+
return unique
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Product serialization and exact application deduplication."""
|