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,899 @@
|
|
|
1
|
+
"""Stereo predicates layered after structural candidate matching."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from typing import Any, Callable, Hashable, Mapping
|
|
8
|
+
|
|
9
|
+
import networkx as nx
|
|
10
|
+
|
|
11
|
+
from synkit.Graph.Morphism.morphism import GraphMorphism, GraphMorphismError
|
|
12
|
+
from synkit.Graph.Morphism.constraints import WildcardConstraint
|
|
13
|
+
from synkit.Graph.Morphism.stereo_morphism import (
|
|
14
|
+
StereoMorphism,
|
|
15
|
+
StereoMorphismError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .changes import stereo_registry
|
|
19
|
+
from .descriptors import (
|
|
20
|
+
CumuleneAxisStereo,
|
|
21
|
+
ExtendedCisTransStereo,
|
|
22
|
+
PlanarBondStereo,
|
|
23
|
+
StereoValue,
|
|
24
|
+
TetrahedralStereo,
|
|
25
|
+
descriptor_id,
|
|
26
|
+
parse_virtual_reference,
|
|
27
|
+
virtual_reference,
|
|
28
|
+
)
|
|
29
|
+
from .extended_descriptors import HelicalStereo, PlanarChiralityStereo
|
|
30
|
+
from .identity import mapped_stereo_registries_match
|
|
31
|
+
from .global_stereo import FrameworkStereo
|
|
32
|
+
from .legacy import (
|
|
33
|
+
StereoSemanticComparison,
|
|
34
|
+
StereoSemanticsMode,
|
|
35
|
+
legacy_descriptor_query_matches,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _atom_map_translation(
|
|
40
|
+
pattern: nx.Graph, host: nx.Graph, mapping: Mapping[Any, Any]
|
|
41
|
+
) -> dict[int, int]:
|
|
42
|
+
return {
|
|
43
|
+
int(pattern.nodes[p_node].get("atom_map", p_node)): int(
|
|
44
|
+
host.nodes[h_node].get("atom_map") or h_node
|
|
45
|
+
)
|
|
46
|
+
for p_node, h_node in mapping.items()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _node_by_atom_map(graph: nx.Graph) -> dict[int, Any]:
|
|
51
|
+
values: dict[int, Any] = {}
|
|
52
|
+
for node, attrs in graph.nodes(data=True):
|
|
53
|
+
# Ordinary unmapped molecules use graph node IDs as internal stereo
|
|
54
|
+
# references. Treat atom-map zero as absent, matching
|
|
55
|
+
# ``_atom_map_translation`` above, so explicit H can normalize to the
|
|
56
|
+
# same virtual-H identity as its implicit representation.
|
|
57
|
+
atom_map = attrs.get("atom_map") or node
|
|
58
|
+
if isinstance(atom_map, int):
|
|
59
|
+
values[atom_map] = node
|
|
60
|
+
return values
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _unique_stereo_map_lookup(
|
|
64
|
+
graph: nx.Graph,
|
|
65
|
+
) -> tuple[dict[int, Any], str | None]:
|
|
66
|
+
by_map: dict[int, Any] = {}
|
|
67
|
+
for node, attrs in graph.nodes(data=True):
|
|
68
|
+
atom_map = attrs.get("atom_map") or node
|
|
69
|
+
if type(atom_map) is not int:
|
|
70
|
+
continue
|
|
71
|
+
if atom_map in by_map:
|
|
72
|
+
return by_map, f"duplicate atom map {atom_map}"
|
|
73
|
+
by_map[atom_map] = node
|
|
74
|
+
return by_map, None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _path_support_errors(
|
|
78
|
+
graph: nx.Graph,
|
|
79
|
+
path: tuple[int, ...],
|
|
80
|
+
by_map: Mapping[int, Any],
|
|
81
|
+
label: str,
|
|
82
|
+
) -> tuple[str, ...]:
|
|
83
|
+
"""Validate that a descriptor path is present in the graph."""
|
|
84
|
+
absent = tuple(atom for atom in path if atom not in by_map)
|
|
85
|
+
if absent:
|
|
86
|
+
return (
|
|
87
|
+
f"{label} path atoms are absent: " + ", ".join(map(str, sorted(absent))),
|
|
88
|
+
)
|
|
89
|
+
missing_edges = [
|
|
90
|
+
(left, right)
|
|
91
|
+
for left, right in zip(path, path[1:])
|
|
92
|
+
if not graph.has_edge(by_map[left], by_map[right])
|
|
93
|
+
]
|
|
94
|
+
if not missing_edges:
|
|
95
|
+
return ()
|
|
96
|
+
return (
|
|
97
|
+
f"{label} path edges are absent: "
|
|
98
|
+
+ ", ".join(f"{left}-{right}" for left, right in missing_edges),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _stereo_reference_owners(
|
|
103
|
+
graph: nx.Graph,
|
|
104
|
+
descriptor: StereoValue,
|
|
105
|
+
by_map: Mapping[int, Any],
|
|
106
|
+
) -> tuple[tuple[tuple[int, tuple[Any, ...]], ...], tuple[str, ...]]:
|
|
107
|
+
if isinstance(descriptor, FrameworkStereo):
|
|
108
|
+
absent = tuple(atom for atom in descriptor.support_atoms if atom not in by_map)
|
|
109
|
+
if absent:
|
|
110
|
+
return (), (
|
|
111
|
+
"framework support atoms are absent: "
|
|
112
|
+
+ ", ".join(map(str, sorted(absent))),
|
|
113
|
+
)
|
|
114
|
+
return (
|
|
115
|
+
tuple(
|
|
116
|
+
(frame.center, tuple(frame.references)) for frame in descriptor.frames
|
|
117
|
+
),
|
|
118
|
+
(),
|
|
119
|
+
)
|
|
120
|
+
if isinstance(
|
|
121
|
+
descriptor,
|
|
122
|
+
(CumuleneAxisStereo, ExtendedCisTransStereo),
|
|
123
|
+
):
|
|
124
|
+
path = (
|
|
125
|
+
descriptor.axis_path
|
|
126
|
+
if isinstance(descriptor, CumuleneAxisStereo)
|
|
127
|
+
else descriptor.path
|
|
128
|
+
)
|
|
129
|
+
owners = (
|
|
130
|
+
(path[0], tuple(descriptor.terminal_frames[0])),
|
|
131
|
+
(path[-1], tuple(descriptor.terminal_frames[1])),
|
|
132
|
+
)
|
|
133
|
+
return owners, _path_support_errors(graph, path, by_map, "stereo")
|
|
134
|
+
if isinstance(descriptor, HelicalStereo):
|
|
135
|
+
return (), _path_support_errors(
|
|
136
|
+
graph,
|
|
137
|
+
descriptor.path,
|
|
138
|
+
by_map,
|
|
139
|
+
"helical",
|
|
140
|
+
)
|
|
141
|
+
if isinstance(descriptor, PlanarChiralityStereo):
|
|
142
|
+
dependencies = (*descriptor.plane_atoms, descriptor.pilot)
|
|
143
|
+
absent = tuple(atom for atom in dependencies if atom not in by_map)
|
|
144
|
+
return (
|
|
145
|
+
(),
|
|
146
|
+
(
|
|
147
|
+
(
|
|
148
|
+
(
|
|
149
|
+
"planar-chirality support atoms are absent: "
|
|
150
|
+
+ ", ".join(map(str, sorted(absent)))
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
if absent
|
|
154
|
+
else ()
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
atom_centered = descriptor.descriptor_class in {
|
|
158
|
+
"tetrahedral",
|
|
159
|
+
"square_planar",
|
|
160
|
+
"trigonal_bipyramidal",
|
|
161
|
+
"octahedral",
|
|
162
|
+
}
|
|
163
|
+
if atom_centered:
|
|
164
|
+
center = descriptor.atoms[0]
|
|
165
|
+
if type(center) is not int or center not in by_map:
|
|
166
|
+
return (), ("atom stereocenter is absent",)
|
|
167
|
+
return ((center, tuple(descriptor.atoms[1:])),), ()
|
|
168
|
+
|
|
169
|
+
left, right = descriptor.atoms[2:4]
|
|
170
|
+
if (
|
|
171
|
+
type(left) is not int
|
|
172
|
+
or type(right) is not int
|
|
173
|
+
or left not in by_map
|
|
174
|
+
or right not in by_map
|
|
175
|
+
):
|
|
176
|
+
return (), ("central stereo bond atoms are absent",)
|
|
177
|
+
errors = []
|
|
178
|
+
if not graph.has_edge(by_map[left], by_map[right]):
|
|
179
|
+
errors.append("central stereo bond is absent")
|
|
180
|
+
elif (
|
|
181
|
+
descriptor.descriptor_class == "planar_bond"
|
|
182
|
+
and float(graph.edges[by_map[left], by_map[right]].get("pi_order", 0.0)) < 1.0
|
|
183
|
+
):
|
|
184
|
+
errors.append("planar-bond stereo requires a pi bond")
|
|
185
|
+
owners = (
|
|
186
|
+
(left, tuple(descriptor.atoms[:2])),
|
|
187
|
+
(right, tuple(descriptor.atoms[4:])),
|
|
188
|
+
)
|
|
189
|
+
return owners, tuple(errors)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _owner_reference_support_errors(
|
|
193
|
+
graph: nx.Graph,
|
|
194
|
+
owner: int,
|
|
195
|
+
references: tuple[Any, ...],
|
|
196
|
+
by_map: Mapping[int, Any],
|
|
197
|
+
) -> tuple[str, ...]:
|
|
198
|
+
errors: list[str] = []
|
|
199
|
+
virtual_counts: Counter[str] = Counter()
|
|
200
|
+
owner_node = by_map[owner]
|
|
201
|
+
for reference in references:
|
|
202
|
+
if type(reference) is int:
|
|
203
|
+
if reference not in by_map:
|
|
204
|
+
errors.append(f"ligand atom map {reference} is absent")
|
|
205
|
+
elif not graph.has_edge(owner_node, by_map[reference]):
|
|
206
|
+
errors.append(
|
|
207
|
+
f"ligand atom map {reference} is not adjacent to owner {owner}"
|
|
208
|
+
)
|
|
209
|
+
continue
|
|
210
|
+
virtual = parse_virtual_reference(reference)
|
|
211
|
+
if virtual is None or virtual.center != owner:
|
|
212
|
+
errors.append(f"invalid virtual ligand {reference!r}")
|
|
213
|
+
continue
|
|
214
|
+
virtual_counts[virtual.kind] += 1
|
|
215
|
+
|
|
216
|
+
for kind, count in virtual_counts.items():
|
|
217
|
+
field = "hcount" if kind == "H" else "lone_pairs"
|
|
218
|
+
available = graph.nodes[owner_node].get(field, 0)
|
|
219
|
+
if not isinstance(available, (int, float)) or available < count:
|
|
220
|
+
errors.append(
|
|
221
|
+
f"virtual {kind} ligand requires {count} {field} resource at {owner}"
|
|
222
|
+
)
|
|
223
|
+
return tuple(errors)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def descriptor_graph_support_errors(
|
|
227
|
+
graph: nx.Graph,
|
|
228
|
+
descriptor: StereoValue,
|
|
229
|
+
*,
|
|
230
|
+
registry_key: str | None = None,
|
|
231
|
+
) -> tuple[str, ...]:
|
|
232
|
+
"""Return exact topology/resource reasons a descriptor cannot exist."""
|
|
233
|
+
errors: list[str] = []
|
|
234
|
+
by_map, map_error = _unique_stereo_map_lookup(graph)
|
|
235
|
+
if map_error is not None:
|
|
236
|
+
return (map_error,)
|
|
237
|
+
|
|
238
|
+
expected_key = descriptor_id(descriptor)
|
|
239
|
+
if registry_key is not None and registry_key != expected_key:
|
|
240
|
+
errors.append(f"registry key {registry_key} does not match {expected_key}")
|
|
241
|
+
|
|
242
|
+
owners, topology_errors = _stereo_reference_owners(graph, descriptor, by_map)
|
|
243
|
+
errors.extend(topology_errors)
|
|
244
|
+
for owner, references in owners:
|
|
245
|
+
errors.extend(_owner_reference_support_errors(graph, owner, references, by_map))
|
|
246
|
+
return tuple(errors)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _is_bound_explicit_hydrogen(
|
|
250
|
+
graph: nx.Graph,
|
|
251
|
+
by_map: Mapping[int, Any],
|
|
252
|
+
reference: Any,
|
|
253
|
+
center: Any,
|
|
254
|
+
) -> bool:
|
|
255
|
+
if not isinstance(reference, int) or not isinstance(center, int):
|
|
256
|
+
return False
|
|
257
|
+
h_node, center_node = by_map.get(reference), by_map.get(center)
|
|
258
|
+
return (
|
|
259
|
+
h_node is not None
|
|
260
|
+
and center_node is not None
|
|
261
|
+
and graph.nodes[h_node].get("element") == "H"
|
|
262
|
+
and graph.has_edge(h_node, center_node)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def normalize_hydrogen_references(
|
|
267
|
+
descriptor: StereoValue,
|
|
268
|
+
graph: nx.Graph,
|
|
269
|
+
) -> StereoValue:
|
|
270
|
+
"""Project explicit bound H references onto SynKit's virtual-H identity.
|
|
271
|
+
|
|
272
|
+
SynKit rules may strip ordinary explicit hydrogens while imported graphs
|
|
273
|
+
retain them. Matching therefore compares both representations through
|
|
274
|
+
``@H:<center>`` without mutating either stored descriptor.
|
|
275
|
+
"""
|
|
276
|
+
by_map = _node_by_atom_map(graph)
|
|
277
|
+
if isinstance(descriptor, TetrahedralStereo):
|
|
278
|
+
center = descriptor.center
|
|
279
|
+
bound_hydrogens = {
|
|
280
|
+
ref
|
|
281
|
+
for ref in descriptor.atoms[1:]
|
|
282
|
+
if _is_bound_explicit_hydrogen(graph, by_map, ref, center)
|
|
283
|
+
}
|
|
284
|
+
refs = tuple(
|
|
285
|
+
(
|
|
286
|
+
virtual_reference("H", center)
|
|
287
|
+
if len(bound_hydrogens) == 1 and ref in bound_hydrogens
|
|
288
|
+
else ref
|
|
289
|
+
)
|
|
290
|
+
for ref in descriptor.atoms[1:]
|
|
291
|
+
)
|
|
292
|
+
return TetrahedralStereo(
|
|
293
|
+
(center, *refs), descriptor.parity, descriptor.provenance
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if not isinstance(descriptor, PlanarBondStereo):
|
|
297
|
+
return descriptor
|
|
298
|
+
|
|
299
|
+
left, right = descriptor.atoms[2:4]
|
|
300
|
+
left_refs = tuple(
|
|
301
|
+
(
|
|
302
|
+
virtual_reference("H", left)
|
|
303
|
+
if _is_bound_explicit_hydrogen(graph, by_map, ref, left)
|
|
304
|
+
else ref
|
|
305
|
+
)
|
|
306
|
+
for ref in descriptor.atoms[:2]
|
|
307
|
+
)
|
|
308
|
+
right_refs = tuple(
|
|
309
|
+
(
|
|
310
|
+
virtual_reference("H", right)
|
|
311
|
+
if _is_bound_explicit_hydrogen(graph, by_map, ref, right)
|
|
312
|
+
else ref
|
|
313
|
+
)
|
|
314
|
+
for ref in descriptor.atoms[4:]
|
|
315
|
+
)
|
|
316
|
+
return PlanarBondStereo(
|
|
317
|
+
(*left_refs, left, right, *right_refs),
|
|
318
|
+
descriptor.parity,
|
|
319
|
+
descriptor.provenance,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _orbit_descriptor_query_matches(
|
|
324
|
+
query: StereoValue,
|
|
325
|
+
candidate: StereoValue,
|
|
326
|
+
*,
|
|
327
|
+
unknown_policy: str = "exact",
|
|
328
|
+
) -> bool:
|
|
329
|
+
"""Match one descriptor through orbit identity and information policy."""
|
|
330
|
+
if unknown_policy not in {"exact", "wildcard", "either"}:
|
|
331
|
+
raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
|
|
332
|
+
if type(query) is not type(candidate):
|
|
333
|
+
return False
|
|
334
|
+
if unknown_policy == "either" and query.parity is not None:
|
|
335
|
+
return query.same_configuration(candidate) or query.invert().same_configuration(
|
|
336
|
+
candidate
|
|
337
|
+
)
|
|
338
|
+
if query.parity is not None or unknown_policy == "exact":
|
|
339
|
+
return query.same_configuration(candidate)
|
|
340
|
+
unknown_candidate = type(candidate)(
|
|
341
|
+
candidate.atoms,
|
|
342
|
+
None,
|
|
343
|
+
candidate.provenance,
|
|
344
|
+
)
|
|
345
|
+
return query.same_configuration(unknown_candidate)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def descriptor_query_matches(
|
|
349
|
+
query: StereoValue,
|
|
350
|
+
candidate: StereoValue,
|
|
351
|
+
*,
|
|
352
|
+
unknown_policy: str = "exact",
|
|
353
|
+
semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
|
|
354
|
+
diagnostics: list[StereoSemanticComparison] | None = None,
|
|
355
|
+
) -> bool:
|
|
356
|
+
"""Match one descriptor using orbit, frozen legacy, or dual-run semantics.
|
|
357
|
+
|
|
358
|
+
Compare mode always returns the orbit decision. If a diagnostics sink is
|
|
359
|
+
supplied it also records agreement with the independent Beta-2 oracle;
|
|
360
|
+
legacy is never used as a fallback.
|
|
361
|
+
"""
|
|
362
|
+
mode = StereoSemanticsMode(semantics)
|
|
363
|
+
if mode is StereoSemanticsMode.LEGACY:
|
|
364
|
+
return legacy_descriptor_query_matches(
|
|
365
|
+
query,
|
|
366
|
+
candidate,
|
|
367
|
+
unknown_policy=unknown_policy,
|
|
368
|
+
)
|
|
369
|
+
orbit_result = _orbit_descriptor_query_matches(
|
|
370
|
+
query,
|
|
371
|
+
candidate,
|
|
372
|
+
unknown_policy=unknown_policy,
|
|
373
|
+
)
|
|
374
|
+
if mode is StereoSemanticsMode.ORBIT:
|
|
375
|
+
return orbit_result
|
|
376
|
+
legacy_result = legacy_descriptor_query_matches(
|
|
377
|
+
query,
|
|
378
|
+
candidate,
|
|
379
|
+
unknown_policy=unknown_policy,
|
|
380
|
+
)
|
|
381
|
+
if diagnostics is not None:
|
|
382
|
+
diagnostics.append(
|
|
383
|
+
StereoSemanticComparison.create(
|
|
384
|
+
"descriptor_query",
|
|
385
|
+
orbit_result,
|
|
386
|
+
legacy_result,
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
return orbit_result
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _default_node_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
393
|
+
"""Compare stable molecular/Lewis node attributes while ignoring atom maps."""
|
|
394
|
+
keys = (
|
|
395
|
+
"element",
|
|
396
|
+
"charge",
|
|
397
|
+
"radical",
|
|
398
|
+
"hcount",
|
|
399
|
+
"lone_pairs",
|
|
400
|
+
"aromatic",
|
|
401
|
+
"valence",
|
|
402
|
+
)
|
|
403
|
+
return all(left.get(key) == right.get(key) for key in keys)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _default_edge_match(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool:
|
|
407
|
+
"""Compare stable molecular/Lewis bond attributes.
|
|
408
|
+
|
|
409
|
+
The alternating sigma/pi assignment of an aromatic presentation is not
|
|
410
|
+
identity: independently reconstructed graphs, and different supported
|
|
411
|
+
RDKit releases, may choose opposite Kekule phases. ``order == 1.5`` is
|
|
412
|
+
SynKit's stable aromatic marker, so ignore only that phase-local split.
|
|
413
|
+
"""
|
|
414
|
+
left_aromatic = left.get("aromatic") is True or left.get("order") == 1.5
|
|
415
|
+
right_aromatic = right.get("aromatic") is True or right.get("order") == 1.5
|
|
416
|
+
if left_aromatic or right_aromatic:
|
|
417
|
+
return left_aromatic and right_aromatic
|
|
418
|
+
keys = ("order", "sigma_order", "pi_order", "aromatic")
|
|
419
|
+
return all(left.get(key) == right.get(key) for key in keys)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _mapped_registry_matches(
|
|
423
|
+
left: nx.Graph,
|
|
424
|
+
right: nx.Graph,
|
|
425
|
+
mapping: Mapping[Any, Any],
|
|
426
|
+
*,
|
|
427
|
+
unknown_policy: str,
|
|
428
|
+
) -> bool:
|
|
429
|
+
return mapped_stereo_registries_match(
|
|
430
|
+
left,
|
|
431
|
+
right,
|
|
432
|
+
mapping,
|
|
433
|
+
unknown_policy=unknown_policy,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _structural_isomorphisms_iter(
|
|
438
|
+
left: nx.Graph,
|
|
439
|
+
right: nx.Graph,
|
|
440
|
+
*,
|
|
441
|
+
node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
442
|
+
edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
443
|
+
) -> Iterator[dict[Any, Any]]:
|
|
444
|
+
if (
|
|
445
|
+
left.is_multigraph() != right.is_multigraph()
|
|
446
|
+
or left.is_directed() != right.is_directed()
|
|
447
|
+
):
|
|
448
|
+
return
|
|
449
|
+
if left.is_directed():
|
|
450
|
+
matcher_type = (
|
|
451
|
+
nx.algorithms.isomorphism.MultiDiGraphMatcher
|
|
452
|
+
if left.is_multigraph()
|
|
453
|
+
else nx.algorithms.isomorphism.DiGraphMatcher
|
|
454
|
+
)
|
|
455
|
+
else:
|
|
456
|
+
matcher_type = (
|
|
457
|
+
nx.algorithms.isomorphism.MultiGraphMatcher
|
|
458
|
+
if left.is_multigraph()
|
|
459
|
+
else nx.algorithms.isomorphism.GraphMatcher
|
|
460
|
+
)
|
|
461
|
+
matcher = matcher_type(
|
|
462
|
+
left,
|
|
463
|
+
right,
|
|
464
|
+
node_match=node_match or _default_node_match,
|
|
465
|
+
edge_match=edge_match or _default_edge_match,
|
|
466
|
+
)
|
|
467
|
+
for mapping in matcher.isomorphisms_iter():
|
|
468
|
+
yield dict(mapping)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _structural_isomorphism_mappings(
|
|
472
|
+
left: nx.Graph,
|
|
473
|
+
right: nx.Graph,
|
|
474
|
+
*,
|
|
475
|
+
node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
476
|
+
edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
477
|
+
) -> tuple[dict[Any, Any], ...]:
|
|
478
|
+
return tuple(
|
|
479
|
+
_structural_isomorphisms_iter(
|
|
480
|
+
left,
|
|
481
|
+
right,
|
|
482
|
+
node_match=node_match,
|
|
483
|
+
edge_match=edge_match,
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _mapping_set_form(
|
|
489
|
+
mappings: tuple[Mapping[Any, Any], ...],
|
|
490
|
+
) -> tuple[tuple[tuple[str, str], ...], ...]:
|
|
491
|
+
return tuple(
|
|
492
|
+
sorted(
|
|
493
|
+
(
|
|
494
|
+
tuple(
|
|
495
|
+
sorted(
|
|
496
|
+
((repr(left), repr(right)) for left, right in mapping.items())
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
for mapping in mappings
|
|
500
|
+
),
|
|
501
|
+
key=repr,
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _stereo_morphisms_for_mappings(
|
|
507
|
+
left: nx.Graph,
|
|
508
|
+
right: nx.Graph,
|
|
509
|
+
mappings: tuple[dict[Any, Any], ...],
|
|
510
|
+
unknown_policy: str,
|
|
511
|
+
) -> tuple[StereoMorphism, ...]:
|
|
512
|
+
results = []
|
|
513
|
+
for mapping in mappings:
|
|
514
|
+
graph_morphism = GraphMorphism(
|
|
515
|
+
"left",
|
|
516
|
+
"right",
|
|
517
|
+
frozenset(left.nodes),
|
|
518
|
+
frozenset(right.nodes),
|
|
519
|
+
mapping,
|
|
520
|
+
)
|
|
521
|
+
try:
|
|
522
|
+
results.append(
|
|
523
|
+
StereoMorphism.from_graphs(
|
|
524
|
+
graph_morphism,
|
|
525
|
+
left,
|
|
526
|
+
right,
|
|
527
|
+
presence_mode="strict",
|
|
528
|
+
information_policy=unknown_policy,
|
|
529
|
+
)
|
|
530
|
+
)
|
|
531
|
+
except StereoMorphismError:
|
|
532
|
+
continue
|
|
533
|
+
return tuple(results)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def stereo_isomorphism_morphisms(
|
|
537
|
+
left: nx.Graph,
|
|
538
|
+
right: nx.Graph,
|
|
539
|
+
*,
|
|
540
|
+
node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
541
|
+
edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
542
|
+
unknown_policy: str = "exact",
|
|
543
|
+
) -> tuple[StereoMorphism, ...]:
|
|
544
|
+
"""Return every accepted isomorphism with complete local certificates."""
|
|
545
|
+
if unknown_policy not in {"exact", "wildcard", "either"}:
|
|
546
|
+
raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
|
|
547
|
+
structural = _structural_isomorphism_mappings(
|
|
548
|
+
left,
|
|
549
|
+
right,
|
|
550
|
+
node_match=node_match,
|
|
551
|
+
edge_match=edge_match,
|
|
552
|
+
)
|
|
553
|
+
return _stereo_morphisms_for_mappings(
|
|
554
|
+
left,
|
|
555
|
+
right,
|
|
556
|
+
structural,
|
|
557
|
+
unknown_policy,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def stereo_isomorphism_mappings(
|
|
562
|
+
left: nx.Graph,
|
|
563
|
+
right: nx.Graph,
|
|
564
|
+
*,
|
|
565
|
+
node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
566
|
+
edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
567
|
+
unknown_policy: str = "exact",
|
|
568
|
+
semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
|
|
569
|
+
diagnostics: list[StereoSemanticComparison] | None = None,
|
|
570
|
+
) -> tuple[dict[Any, Any], ...]:
|
|
571
|
+
"""Return the complete accepted mapping set under selected semantics."""
|
|
572
|
+
if unknown_policy not in {"exact", "wildcard", "either"}:
|
|
573
|
+
raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
|
|
574
|
+
mode = StereoSemanticsMode(semantics)
|
|
575
|
+
structural = _structural_isomorphism_mappings(
|
|
576
|
+
left,
|
|
577
|
+
right,
|
|
578
|
+
node_match=node_match,
|
|
579
|
+
edge_match=edge_match,
|
|
580
|
+
)
|
|
581
|
+
if mode is StereoSemanticsMode.LEGACY:
|
|
582
|
+
return tuple(
|
|
583
|
+
mapping
|
|
584
|
+
for mapping in structural
|
|
585
|
+
if _mapped_registry_matches(
|
|
586
|
+
left,
|
|
587
|
+
right,
|
|
588
|
+
mapping,
|
|
589
|
+
unknown_policy=unknown_policy,
|
|
590
|
+
)
|
|
591
|
+
)
|
|
592
|
+
orbit = tuple(
|
|
593
|
+
morphism.graph_morphism.mapping
|
|
594
|
+
for morphism in _stereo_morphisms_for_mappings(
|
|
595
|
+
left,
|
|
596
|
+
right,
|
|
597
|
+
structural,
|
|
598
|
+
unknown_policy,
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
if mode is StereoSemanticsMode.COMPARE:
|
|
602
|
+
legacy = tuple(
|
|
603
|
+
mapping
|
|
604
|
+
for mapping in structural
|
|
605
|
+
if _mapped_registry_matches(
|
|
606
|
+
left,
|
|
607
|
+
right,
|
|
608
|
+
mapping,
|
|
609
|
+
unknown_policy=unknown_policy,
|
|
610
|
+
)
|
|
611
|
+
)
|
|
612
|
+
if diagnostics is not None:
|
|
613
|
+
diagnostics.append(
|
|
614
|
+
StereoSemanticComparison.create(
|
|
615
|
+
"stereo_isomorphism_mapping_set",
|
|
616
|
+
_mapping_set_form(orbit),
|
|
617
|
+
_mapping_set_form(legacy),
|
|
618
|
+
)
|
|
619
|
+
)
|
|
620
|
+
return orbit
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def stereo_isomorphism_mapping(
|
|
624
|
+
left: nx.Graph,
|
|
625
|
+
right: nx.Graph,
|
|
626
|
+
*,
|
|
627
|
+
node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
628
|
+
edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
629
|
+
unknown_policy: str = "exact",
|
|
630
|
+
semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
|
|
631
|
+
diagnostics: list[StereoSemanticComparison] | None = None,
|
|
632
|
+
) -> dict[Any, Any] | None:
|
|
633
|
+
"""Return the first accepted mapping; compare mode audits the full set."""
|
|
634
|
+
if unknown_policy not in {"exact", "wildcard", "either"}:
|
|
635
|
+
raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
|
|
636
|
+
mode = StereoSemanticsMode(semantics)
|
|
637
|
+
if mode is StereoSemanticsMode.COMPARE:
|
|
638
|
+
mappings = stereo_isomorphism_mappings(
|
|
639
|
+
left,
|
|
640
|
+
right,
|
|
641
|
+
node_match=node_match,
|
|
642
|
+
edge_match=edge_match,
|
|
643
|
+
unknown_policy=unknown_policy,
|
|
644
|
+
semantics=mode,
|
|
645
|
+
diagnostics=diagnostics,
|
|
646
|
+
)
|
|
647
|
+
return None if not mappings else mappings[0]
|
|
648
|
+
for mapping in _structural_isomorphisms_iter(
|
|
649
|
+
left,
|
|
650
|
+
right,
|
|
651
|
+
node_match=node_match,
|
|
652
|
+
edge_match=edge_match,
|
|
653
|
+
):
|
|
654
|
+
if mode is StereoSemanticsMode.LEGACY:
|
|
655
|
+
if _mapped_registry_matches(
|
|
656
|
+
left,
|
|
657
|
+
right,
|
|
658
|
+
mapping,
|
|
659
|
+
unknown_policy=unknown_policy,
|
|
660
|
+
):
|
|
661
|
+
return mapping
|
|
662
|
+
continue
|
|
663
|
+
graph_morphism = GraphMorphism(
|
|
664
|
+
"left",
|
|
665
|
+
"right",
|
|
666
|
+
frozenset(left.nodes),
|
|
667
|
+
frozenset(right.nodes),
|
|
668
|
+
mapping,
|
|
669
|
+
)
|
|
670
|
+
try:
|
|
671
|
+
StereoMorphism.from_graphs(
|
|
672
|
+
graph_morphism,
|
|
673
|
+
left,
|
|
674
|
+
right,
|
|
675
|
+
presence_mode="strict",
|
|
676
|
+
information_policy=unknown_policy,
|
|
677
|
+
)
|
|
678
|
+
except StereoMorphismError:
|
|
679
|
+
continue
|
|
680
|
+
return mapping
|
|
681
|
+
return None
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def stereo_isomorphic(
|
|
685
|
+
left: nx.Graph,
|
|
686
|
+
right: nx.Graph,
|
|
687
|
+
*,
|
|
688
|
+
node_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
689
|
+
edge_match: Callable[[Mapping[str, Any], Mapping[str, Any]], bool] | None = None,
|
|
690
|
+
unknown_policy: str = "exact",
|
|
691
|
+
semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
|
|
692
|
+
diagnostics: list[StereoSemanticComparison] | None = None,
|
|
693
|
+
) -> bool:
|
|
694
|
+
"""Return whether two graphs are structurally and stereochemically equal."""
|
|
695
|
+
return (
|
|
696
|
+
stereo_isomorphism_mapping(
|
|
697
|
+
left,
|
|
698
|
+
right,
|
|
699
|
+
node_match=node_match,
|
|
700
|
+
edge_match=edge_match,
|
|
701
|
+
unknown_policy=unknown_policy,
|
|
702
|
+
semantics=semantics,
|
|
703
|
+
diagnostics=diagnostics,
|
|
704
|
+
)
|
|
705
|
+
is not None
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def candidate_mapping_stereo_morphism(
|
|
710
|
+
pattern: nx.Graph,
|
|
711
|
+
host: nx.Graph,
|
|
712
|
+
mapping: Mapping[Any, Any],
|
|
713
|
+
*,
|
|
714
|
+
mode: str = "require",
|
|
715
|
+
unknown_policy: str = "exact",
|
|
716
|
+
query_policies: Mapping[str, str] | None = None,
|
|
717
|
+
source_id: Hashable = "pattern",
|
|
718
|
+
target_id: Hashable = "host",
|
|
719
|
+
substitutions: Mapping[Hashable, WildcardConstraint] | None = None,
|
|
720
|
+
) -> StereoMorphism:
|
|
721
|
+
"""Return a proof-bearing refinement of one structural candidate mapping."""
|
|
722
|
+
if mode not in {"require", "strict", "ignore", "propagate"}:
|
|
723
|
+
raise ValueError(f"Unsupported stereo matching mode: {mode!r}")
|
|
724
|
+
normalized_substitutions = {}
|
|
725
|
+
for node, constraint in (substitutions or {}).items():
|
|
726
|
+
owner = constraint.owner
|
|
727
|
+
if owner is not None and owner not in pattern:
|
|
728
|
+
owner_nodes = [
|
|
729
|
+
candidate
|
|
730
|
+
for candidate, attrs in pattern.nodes(data=True)
|
|
731
|
+
if owner
|
|
732
|
+
in (
|
|
733
|
+
attrs.get("atom_map"),
|
|
734
|
+
*(
|
|
735
|
+
attrs.get("atom_map")
|
|
736
|
+
if isinstance(attrs.get("atom_map"), (tuple, list))
|
|
737
|
+
else ()
|
|
738
|
+
),
|
|
739
|
+
)
|
|
740
|
+
]
|
|
741
|
+
if len(owner_nodes) == 1:
|
|
742
|
+
constraint = constraint.relabel_owner({owner: owner_nodes[0]})
|
|
743
|
+
normalized_substitutions[node] = constraint
|
|
744
|
+
graph_morphism = GraphMorphism(
|
|
745
|
+
source_id,
|
|
746
|
+
target_id,
|
|
747
|
+
frozenset(pattern.nodes),
|
|
748
|
+
frozenset(host.nodes),
|
|
749
|
+
mapping,
|
|
750
|
+
normalized_substitutions,
|
|
751
|
+
)
|
|
752
|
+
return StereoMorphism.from_graphs(
|
|
753
|
+
graph_morphism,
|
|
754
|
+
pattern,
|
|
755
|
+
host,
|
|
756
|
+
presence_mode=mode,
|
|
757
|
+
information_policy=unknown_policy,
|
|
758
|
+
information_policies=query_policies,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _legacy_candidate_mapping_stereo_matches(
|
|
763
|
+
pattern: nx.Graph,
|
|
764
|
+
host: nx.Graph,
|
|
765
|
+
mapping: Mapping[Any, Any],
|
|
766
|
+
*,
|
|
767
|
+
mode: str = "require",
|
|
768
|
+
unknown_policy: str = "exact",
|
|
769
|
+
query_policies: Mapping[str, str] | None = None,
|
|
770
|
+
) -> bool:
|
|
771
|
+
if mode in {"ignore", "propagate"}:
|
|
772
|
+
return True
|
|
773
|
+
if mode not in {"require", "strict"}:
|
|
774
|
+
raise ValueError(f"Unsupported stereo matching mode: {mode!r}")
|
|
775
|
+
translation = _atom_map_translation(pattern, host, mapping)
|
|
776
|
+
host_descriptors = tuple(stereo_registry(host).values())
|
|
777
|
+
query_policies = query_policies or {}
|
|
778
|
+
for key, descriptor in stereo_registry(pattern).items():
|
|
779
|
+
mapped = normalize_hydrogen_references(descriptor.relabel(translation), host)
|
|
780
|
+
policy = query_policies.get(key, unknown_policy)
|
|
781
|
+
if not any(
|
|
782
|
+
descriptor_query_matches(
|
|
783
|
+
mapped,
|
|
784
|
+
normalize_hydrogen_references(candidate, host),
|
|
785
|
+
unknown_policy=policy,
|
|
786
|
+
)
|
|
787
|
+
for candidate in host_descriptors
|
|
788
|
+
):
|
|
789
|
+
return False
|
|
790
|
+
if mode == "strict":
|
|
791
|
+
mapped_dependencies = set(translation.values())
|
|
792
|
+
pattern_count = len(stereo_registry(pattern))
|
|
793
|
+
host_count = sum(
|
|
794
|
+
bool(candidate.dependencies & mapped_dependencies)
|
|
795
|
+
for candidate in host_descriptors
|
|
796
|
+
)
|
|
797
|
+
return pattern_count == host_count
|
|
798
|
+
return True
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def candidate_mapping_stereo_matches(
|
|
802
|
+
pattern: nx.Graph,
|
|
803
|
+
host: nx.Graph,
|
|
804
|
+
mapping: Mapping[Any, Any],
|
|
805
|
+
*,
|
|
806
|
+
mode: str = "require",
|
|
807
|
+
unknown_policy: str = "exact",
|
|
808
|
+
query_policies: Mapping[str, str] | None = None,
|
|
809
|
+
substitutions: Mapping[Hashable, WildcardConstraint] | None = None,
|
|
810
|
+
semantics: StereoSemanticsMode | str = StereoSemanticsMode.ORBIT,
|
|
811
|
+
diagnostics: list[StereoSemanticComparison] | None = None,
|
|
812
|
+
morphism_issues: list[Any] | None = None,
|
|
813
|
+
) -> bool:
|
|
814
|
+
"""Check one candidate mapping and optionally audit frozen behavior."""
|
|
815
|
+
semantics_mode = StereoSemanticsMode(semantics)
|
|
816
|
+
if semantics_mode is StereoSemanticsMode.LEGACY:
|
|
817
|
+
if substitutions:
|
|
818
|
+
try:
|
|
819
|
+
candidate_mapping_stereo_morphism(
|
|
820
|
+
pattern,
|
|
821
|
+
host,
|
|
822
|
+
mapping,
|
|
823
|
+
mode=mode,
|
|
824
|
+
unknown_policy=unknown_policy,
|
|
825
|
+
query_policies=query_policies,
|
|
826
|
+
substitutions=substitutions,
|
|
827
|
+
)
|
|
828
|
+
except StereoMorphismError as error:
|
|
829
|
+
if morphism_issues is not None:
|
|
830
|
+
morphism_issues.extend(error.issues)
|
|
831
|
+
return False
|
|
832
|
+
except GraphMorphismError:
|
|
833
|
+
return False
|
|
834
|
+
return _legacy_candidate_mapping_stereo_matches(
|
|
835
|
+
pattern,
|
|
836
|
+
host,
|
|
837
|
+
mapping,
|
|
838
|
+
mode=mode,
|
|
839
|
+
unknown_policy=unknown_policy,
|
|
840
|
+
query_policies=query_policies,
|
|
841
|
+
)
|
|
842
|
+
try:
|
|
843
|
+
candidate_mapping_stereo_morphism(
|
|
844
|
+
pattern,
|
|
845
|
+
host,
|
|
846
|
+
mapping,
|
|
847
|
+
mode=mode,
|
|
848
|
+
unknown_policy=unknown_policy,
|
|
849
|
+
query_policies=query_policies,
|
|
850
|
+
substitutions=substitutions,
|
|
851
|
+
)
|
|
852
|
+
orbit_result = True
|
|
853
|
+
except StereoMorphismError as error:
|
|
854
|
+
if morphism_issues is not None:
|
|
855
|
+
morphism_issues.extend(error.issues)
|
|
856
|
+
orbit_result = False
|
|
857
|
+
except GraphMorphismError:
|
|
858
|
+
orbit_result = False
|
|
859
|
+
if semantics_mode is StereoSemanticsMode.ORBIT:
|
|
860
|
+
return orbit_result
|
|
861
|
+
legacy_result = _legacy_candidate_mapping_stereo_matches(
|
|
862
|
+
pattern,
|
|
863
|
+
host,
|
|
864
|
+
mapping,
|
|
865
|
+
mode=mode,
|
|
866
|
+
unknown_policy=unknown_policy,
|
|
867
|
+
query_policies=query_policies,
|
|
868
|
+
)
|
|
869
|
+
if diagnostics is not None:
|
|
870
|
+
diagnostics.append(
|
|
871
|
+
StereoSemanticComparison.create(
|
|
872
|
+
"candidate_mapping",
|
|
873
|
+
orbit_result,
|
|
874
|
+
legacy_result,
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
return orbit_result
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def propagate_unaffected_stereo(
|
|
881
|
+
source: nx.Graph, target: nx.Graph, *, changed_atom_maps: set[int]
|
|
882
|
+
) -> None:
|
|
883
|
+
"""Copy descriptors whose complete dependency set remains unaffected."""
|
|
884
|
+
registry: dict[str, StereoValue] = dict(stereo_registry(target))
|
|
885
|
+
target_maps = {
|
|
886
|
+
int(attrs.get("atom_map", node)) for node, attrs in target.nodes(data=True)
|
|
887
|
+
}
|
|
888
|
+
for descriptor in stereo_registry(source).values():
|
|
889
|
+
if descriptor.dependencies & changed_atom_maps:
|
|
890
|
+
continue
|
|
891
|
+
if (
|
|
892
|
+
descriptor.dependencies <= target_maps
|
|
893
|
+
and not descriptor_graph_support_errors(
|
|
894
|
+
target,
|
|
895
|
+
descriptor,
|
|
896
|
+
)
|
|
897
|
+
):
|
|
898
|
+
registry[descriptor_id(descriptor)] = descriptor
|
|
899
|
+
target.graph["stereo_descriptors"] = registry
|