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,648 @@
|
|
|
1
|
+
"""Map-invariant identity forms for graph-level relative stereochemistry.
|
|
2
|
+
|
|
3
|
+
The structural graph remains canonicalized by :class:`GraphCanonicaliser`.
|
|
4
|
+
This module adds a separate stereo form whose atom references are replaced by
|
|
5
|
+
isomorphism-invariant colour-refinement tokens. The tokens are deliberately a
|
|
6
|
+
prefilter: exact stereo-aware graph isomorphism remains the equality authority
|
|
7
|
+
when two stereo-bearing :class:`~synkit.Graph.syn_graph.SynGraph` objects share
|
|
8
|
+
the same combined signature.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections import Counter
|
|
14
|
+
from collections.abc import Callable, Hashable, Mapping
|
|
15
|
+
import hashlib
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import networkx as nx
|
|
19
|
+
|
|
20
|
+
from .descriptors import (
|
|
21
|
+
AtropBondStereo,
|
|
22
|
+
OctahedralStereo,
|
|
23
|
+
PlanarBondStereo,
|
|
24
|
+
Reference,
|
|
25
|
+
SquarePlanarStereo,
|
|
26
|
+
StereoValue,
|
|
27
|
+
TetrahedralStereo,
|
|
28
|
+
TrigonalBipyramidalStereo,
|
|
29
|
+
parse_virtual_reference,
|
|
30
|
+
)
|
|
31
|
+
from .global_stereo import FrameworkStereo
|
|
32
|
+
|
|
33
|
+
StereoToken = Hashable
|
|
34
|
+
StereoReferenceResolver = Callable[[Reference], StereoToken]
|
|
35
|
+
|
|
36
|
+
_STEREO_TYPES = (
|
|
37
|
+
TetrahedralStereo,
|
|
38
|
+
SquarePlanarStereo,
|
|
39
|
+
TrigonalBipyramidalStereo,
|
|
40
|
+
OctahedralStereo,
|
|
41
|
+
PlanarBondStereo,
|
|
42
|
+
AtropBondStereo,
|
|
43
|
+
)
|
|
44
|
+
_ATOM_TYPES = (
|
|
45
|
+
TetrahedralStereo,
|
|
46
|
+
SquarePlanarStereo,
|
|
47
|
+
TrigonalBipyramidalStereo,
|
|
48
|
+
OctahedralStereo,
|
|
49
|
+
)
|
|
50
|
+
_TETRAHEDRAL_PERMUTATIONS = (
|
|
51
|
+
(0, 1, 2, 3, 4),
|
|
52
|
+
(0, 3, 1, 2, 4),
|
|
53
|
+
(0, 2, 3, 1, 4),
|
|
54
|
+
(0, 1, 4, 2, 3),
|
|
55
|
+
(0, 2, 1, 4, 3),
|
|
56
|
+
(0, 4, 2, 1, 3),
|
|
57
|
+
(0, 1, 3, 4, 2),
|
|
58
|
+
(0, 4, 1, 3, 2),
|
|
59
|
+
(0, 3, 4, 1, 2),
|
|
60
|
+
(0, 2, 4, 3, 1),
|
|
61
|
+
(0, 3, 2, 4, 1),
|
|
62
|
+
(0, 4, 3, 2, 1),
|
|
63
|
+
)
|
|
64
|
+
_TETRAHEDRAL_INVERSION = (0, 2, 1, 3, 4)
|
|
65
|
+
_NODE_IDENTITY_ATTRIBUTES = (
|
|
66
|
+
"element",
|
|
67
|
+
"charge",
|
|
68
|
+
"lone_pairs",
|
|
69
|
+
"radical",
|
|
70
|
+
"aromatic",
|
|
71
|
+
"hcount",
|
|
72
|
+
)
|
|
73
|
+
_EDGE_IDENTITY_ATTRIBUTES = ("order", "standard_order")
|
|
74
|
+
_NODE_IDENTITY_DEFAULTS = {
|
|
75
|
+
"element": "",
|
|
76
|
+
"charge": 0,
|
|
77
|
+
"lone_pairs": 0,
|
|
78
|
+
"radical": 0,
|
|
79
|
+
"aromatic": False,
|
|
80
|
+
"hcount": 0,
|
|
81
|
+
}
|
|
82
|
+
_EDGE_IDENTITY_DEFAULTS = {"order": 0, "standard_order": 0}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class StereoIdentityError(ValueError):
|
|
86
|
+
"""Raised when stereo metadata cannot form a map-invariant identity."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _freeze(value: Any) -> Hashable:
|
|
90
|
+
if isinstance(value, Mapping):
|
|
91
|
+
return tuple(sorted((str(key), _freeze(item)) for key, item in value.items()))
|
|
92
|
+
if isinstance(value, (list, tuple)):
|
|
93
|
+
return tuple(_freeze(item) for item in value)
|
|
94
|
+
if isinstance(value, set):
|
|
95
|
+
return tuple(sorted((_freeze(item) for item in value), key=repr))
|
|
96
|
+
try:
|
|
97
|
+
hash(value)
|
|
98
|
+
except TypeError:
|
|
99
|
+
return repr(value)
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _digest(value: Any) -> str:
|
|
104
|
+
return hashlib.sha256(repr(value).encode("utf-8")).hexdigest()[:32]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def stereo_identity_node_match(
|
|
108
|
+
left: Mapping[str, Any],
|
|
109
|
+
right: Mapping[str, Any],
|
|
110
|
+
) -> bool:
|
|
111
|
+
"""Match the stable node attributes used by SynGraph's default identity."""
|
|
112
|
+
return all(
|
|
113
|
+
_freeze(left.get(key, _NODE_IDENTITY_DEFAULTS[key]))
|
|
114
|
+
== _freeze(right.get(key, _NODE_IDENTITY_DEFAULTS[key]))
|
|
115
|
+
for key in _NODE_IDENTITY_ATTRIBUTES
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def stereo_identity_edge_match(
|
|
120
|
+
left: Mapping[str, Any],
|
|
121
|
+
right: Mapping[str, Any],
|
|
122
|
+
) -> bool:
|
|
123
|
+
"""Match the stable edge attributes used by SynGraph's default identity."""
|
|
124
|
+
return all(
|
|
125
|
+
_freeze(left.get(key, _EDGE_IDENTITY_DEFAULTS[key]))
|
|
126
|
+
== _freeze(right.get(key, _EDGE_IDENTITY_DEFAULTS[key]))
|
|
127
|
+
for key in _EDGE_IDENTITY_ATTRIBUTES
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _node_seed(attributes: Mapping[str, Any]) -> tuple[Any, ...]:
|
|
132
|
+
return tuple(
|
|
133
|
+
_freeze(attributes.get(key, _NODE_IDENTITY_DEFAULTS[key]))
|
|
134
|
+
for key in _NODE_IDENTITY_ATTRIBUTES
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _edge_seed(attributes: Mapping[str, Any]) -> tuple[Any, ...]:
|
|
139
|
+
return tuple(
|
|
140
|
+
_freeze(attributes.get(key, _EDGE_IDENTITY_DEFAULTS[key]))
|
|
141
|
+
for key in _EDGE_IDENTITY_ATTRIBUTES
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _adjacent_colour_records(
|
|
146
|
+
graph: nx.Graph,
|
|
147
|
+
node: Hashable,
|
|
148
|
+
colours: Mapping[Hashable, str],
|
|
149
|
+
) -> tuple[Any, ...]:
|
|
150
|
+
records: list[tuple[Any, ...]] = []
|
|
151
|
+
|
|
152
|
+
def append(direction: str, neighbor: Hashable, edge_data: Any) -> None:
|
|
153
|
+
if graph.is_multigraph():
|
|
154
|
+
for data in edge_data.values():
|
|
155
|
+
records.append((direction, _edge_seed(data), colours[neighbor]))
|
|
156
|
+
else:
|
|
157
|
+
records.append((direction, _edge_seed(edge_data), colours[neighbor]))
|
|
158
|
+
|
|
159
|
+
if graph.is_directed():
|
|
160
|
+
for neighbor, edge_data in graph.succ[node].items(): # type: ignore[attr-defined]
|
|
161
|
+
append("out", neighbor, edge_data)
|
|
162
|
+
for neighbor, edge_data in graph.pred[node].items(): # type: ignore[attr-defined]
|
|
163
|
+
append("in", neighbor, edge_data)
|
|
164
|
+
else:
|
|
165
|
+
for neighbor, edge_data in graph.adj[node].items():
|
|
166
|
+
append("edge", neighbor, edge_data)
|
|
167
|
+
return tuple(sorted(records, key=repr))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def structural_node_colours(graph: nx.Graph) -> Mapping[Hashable, str]:
|
|
171
|
+
"""Return deterministic, map-independent 1-WL colours for graph nodes.
|
|
172
|
+
|
|
173
|
+
Colour refinement may collide for non-isomorphic rooted environments, but
|
|
174
|
+
it cannot split isomorphic ones. That one-sided guarantee is exactly what
|
|
175
|
+
a hash prefilter requires; exact isomorphism resolves collisions later.
|
|
176
|
+
"""
|
|
177
|
+
colours = {
|
|
178
|
+
node: _digest(("node", _node_seed(attributes)))
|
|
179
|
+
for node, attributes in graph.nodes(data=True)
|
|
180
|
+
}
|
|
181
|
+
for _iteration in range(max(1, len(graph))):
|
|
182
|
+
colours = {
|
|
183
|
+
node: _digest(
|
|
184
|
+
(
|
|
185
|
+
"refine",
|
|
186
|
+
colours[node],
|
|
187
|
+
_adjacent_colour_records(graph, node, colours),
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
for node in graph
|
|
191
|
+
}
|
|
192
|
+
return colours
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def stereo_registry_layers(
|
|
196
|
+
graph: nx.Graph,
|
|
197
|
+
) -> Mapping[str, Mapping[str, StereoValue]]:
|
|
198
|
+
"""Normalize flat molecule and named ITS stereo registries into layers."""
|
|
199
|
+
raw = graph.graph.get("stereo_descriptors", {})
|
|
200
|
+
if raw is None or raw == {}:
|
|
201
|
+
return {}
|
|
202
|
+
if not isinstance(raw, Mapping):
|
|
203
|
+
raise StereoIdentityError("stereo_descriptors must be a mapping.")
|
|
204
|
+
if all(isinstance(value, _STEREO_TYPES) for value in raw.values()):
|
|
205
|
+
return {"state": raw} # type: ignore[return-value]
|
|
206
|
+
if not all(isinstance(value, Mapping) for value in raw.values()):
|
|
207
|
+
raise StereoIdentityError(
|
|
208
|
+
"stereo_descriptors must be one flat registry or named registries."
|
|
209
|
+
)
|
|
210
|
+
layers: dict[str, Mapping[str, StereoValue]] = {}
|
|
211
|
+
for layer, registry in raw.items():
|
|
212
|
+
if not all(isinstance(value, _STEREO_TYPES) for value in registry.values()):
|
|
213
|
+
raise StereoIdentityError(
|
|
214
|
+
f"Stereo registry layer {layer!r} contains an invalid descriptor."
|
|
215
|
+
)
|
|
216
|
+
if registry:
|
|
217
|
+
layers[str(layer)] = registry
|
|
218
|
+
return layers
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _layer_atom_reference(
|
|
222
|
+
graph: nx.Graph,
|
|
223
|
+
node: Hashable,
|
|
224
|
+
layer: str,
|
|
225
|
+
) -> int | None:
|
|
226
|
+
atom_map = graph.nodes[node].get("atom_map")
|
|
227
|
+
if isinstance(atom_map, tuple) and len(atom_map) == 2:
|
|
228
|
+
side = {"reactant": 0, "product": 1}.get(layer)
|
|
229
|
+
if side is not None and type(atom_map[side]) is int and atom_map[side] > 0:
|
|
230
|
+
return atom_map[side]
|
|
231
|
+
return node if type(node) is int else None
|
|
232
|
+
if type(atom_map) is int and atom_map > 0:
|
|
233
|
+
return atom_map
|
|
234
|
+
return node if type(node) is int else None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _effective_stereo_side(graph: nx.Graph, layer: str) -> str | None:
|
|
238
|
+
if layer in {"reactant", "product"}:
|
|
239
|
+
return layer
|
|
240
|
+
projection = graph.graph.get("stereo_projection")
|
|
241
|
+
return projection if projection in {"reactant", "product"} else None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _reference_nodes(
|
|
245
|
+
graph: nx.Graph,
|
|
246
|
+
layer: str,
|
|
247
|
+
) -> Mapping[int, tuple[Hashable, str]]:
|
|
248
|
+
references: dict[int, tuple[Hashable, str]] = {}
|
|
249
|
+
for node in graph:
|
|
250
|
+
reference = _layer_atom_reference(graph, node, layer)
|
|
251
|
+
if reference is None:
|
|
252
|
+
continue
|
|
253
|
+
previous = references.get(reference)
|
|
254
|
+
target = (node, "atom")
|
|
255
|
+
if previous is not None and previous != target:
|
|
256
|
+
raise StereoIdentityError(
|
|
257
|
+
f"Stereo reference {reference} is ambiguous in layer {layer!r}."
|
|
258
|
+
)
|
|
259
|
+
references[reference] = target
|
|
260
|
+
|
|
261
|
+
side = _effective_stereo_side(graph, layer)
|
|
262
|
+
pair_field = {
|
|
263
|
+
"reactant": "h_pairs_left",
|
|
264
|
+
"product": "h_pairs_right",
|
|
265
|
+
}.get(side, "h_pairs")
|
|
266
|
+
for node, attributes in graph.nodes(data=True):
|
|
267
|
+
pair_maps = attributes.get("h_pair_atom_maps", {})
|
|
268
|
+
pair_ids = attributes.get(pair_field, ())
|
|
269
|
+
if not isinstance(pair_maps, Mapping) or not isinstance(
|
|
270
|
+
pair_ids, (list, tuple, set)
|
|
271
|
+
):
|
|
272
|
+
continue
|
|
273
|
+
for pair_id in pair_ids:
|
|
274
|
+
reference = pair_maps.get(pair_id)
|
|
275
|
+
if type(reference) is not int or reference <= 0:
|
|
276
|
+
continue
|
|
277
|
+
target = (node, "H")
|
|
278
|
+
previous = references.get(reference)
|
|
279
|
+
# Generated ITS graphs can retain a virtual-H bookkeeping map
|
|
280
|
+
# that collides with a real mapped atom introduced by another
|
|
281
|
+
# component. Integer descriptor references denote the real atom;
|
|
282
|
+
# virtual hydrogens have their explicit ``@H:center`` form, so the
|
|
283
|
+
# structural atom is the unambiguous identity authority here.
|
|
284
|
+
if previous is not None and previous[1] == "atom":
|
|
285
|
+
continue
|
|
286
|
+
if previous is not None and previous != target:
|
|
287
|
+
raise StereoIdentityError(
|
|
288
|
+
f"Hydrogen reference {reference} is ambiguous in layer {layer!r}."
|
|
289
|
+
)
|
|
290
|
+
references[reference] = target
|
|
291
|
+
return references
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def descriptor_relative_form(
|
|
295
|
+
descriptor: StereoValue,
|
|
296
|
+
resolve_reference: StereoReferenceResolver,
|
|
297
|
+
) -> tuple[Any, ...]:
|
|
298
|
+
"""Canonicalize one descriptor after replacing map-dependent references."""
|
|
299
|
+
if isinstance(descriptor, FrameworkStereo):
|
|
300
|
+
support = tuple(
|
|
301
|
+
sorted(
|
|
302
|
+
(resolve_reference(atom) for atom in descriptor.support_atoms),
|
|
303
|
+
key=repr,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
frames = []
|
|
307
|
+
for coupled_frame in descriptor.frames:
|
|
308
|
+
center = resolve_reference(coupled_frame.center)
|
|
309
|
+
references = tuple(
|
|
310
|
+
resolve_reference(reference) for reference in coupled_frame.references
|
|
311
|
+
)
|
|
312
|
+
parity = (
|
|
313
|
+
None
|
|
314
|
+
if descriptor.orientation is None
|
|
315
|
+
else coupled_frame.relation * descriptor.orientation
|
|
316
|
+
)
|
|
317
|
+
if parity is None:
|
|
318
|
+
form = (center, tuple(sorted(references, key=repr)), None)
|
|
319
|
+
else:
|
|
320
|
+
frame = (center, *references)
|
|
321
|
+
if parity == -1:
|
|
322
|
+
frame = tuple(frame[index] for index in _TETRAHEDRAL_INVERSION)
|
|
323
|
+
forms = tuple(
|
|
324
|
+
tuple(frame[index] for index in permutation)
|
|
325
|
+
for permutation in _TETRAHEDRAL_PERMUTATIONS
|
|
326
|
+
)
|
|
327
|
+
form = (min(forms, key=repr), 1)
|
|
328
|
+
frames.append(form)
|
|
329
|
+
return (
|
|
330
|
+
descriptor.descriptor_class,
|
|
331
|
+
support,
|
|
332
|
+
tuple(sorted(frames, key=repr)),
|
|
333
|
+
descriptor.orientation is None,
|
|
334
|
+
)
|
|
335
|
+
atoms = tuple(resolve_reference(reference) for reference in descriptor.atoms)
|
|
336
|
+
if descriptor.parity is None:
|
|
337
|
+
if isinstance(descriptor, _ATOM_TYPES):
|
|
338
|
+
return (
|
|
339
|
+
descriptor.descriptor_class,
|
|
340
|
+
None,
|
|
341
|
+
atoms[0],
|
|
342
|
+
tuple(sorted(atoms[1:], key=repr)),
|
|
343
|
+
)
|
|
344
|
+
left = (atoms[2], tuple(sorted(atoms[:2], key=repr)))
|
|
345
|
+
right = (atoms[3], tuple(sorted(atoms[4:], key=repr)))
|
|
346
|
+
return (
|
|
347
|
+
descriptor.descriptor_class,
|
|
348
|
+
None,
|
|
349
|
+
tuple(sorted((left, right), key=repr)),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if isinstance(descriptor, TetrahedralStereo):
|
|
353
|
+
permutations = _TETRAHEDRAL_PERMUTATIONS
|
|
354
|
+
inversion = _TETRAHEDRAL_INVERSION
|
|
355
|
+
else:
|
|
356
|
+
permutations = descriptor._PERMUTATIONS # type: ignore[attr-defined]
|
|
357
|
+
inversion = getattr(descriptor, "_INVERSION", None)
|
|
358
|
+
working = atoms
|
|
359
|
+
parity = descriptor.parity
|
|
360
|
+
if parity == -1:
|
|
361
|
+
if inversion is None:
|
|
362
|
+
raise StereoIdentityError(
|
|
363
|
+
f"{descriptor.descriptor_class} has no defined inversion."
|
|
364
|
+
)
|
|
365
|
+
working = tuple(working[index] for index in inversion)
|
|
366
|
+
parity = 1
|
|
367
|
+
forms = tuple(
|
|
368
|
+
tuple(working[index] for index in permutation) for permutation in permutations
|
|
369
|
+
)
|
|
370
|
+
return (
|
|
371
|
+
descriptor.descriptor_class,
|
|
372
|
+
parity,
|
|
373
|
+
min(forms, key=repr),
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _signature_reference_resolver(
|
|
378
|
+
graph: nx.Graph,
|
|
379
|
+
layer: str,
|
|
380
|
+
colours: Mapping[Hashable, str],
|
|
381
|
+
) -> StereoReferenceResolver:
|
|
382
|
+
by_reference = _reference_nodes(graph, layer)
|
|
383
|
+
|
|
384
|
+
def resolve(reference: Reference) -> StereoToken:
|
|
385
|
+
if type(reference) is int:
|
|
386
|
+
target = by_reference.get(reference)
|
|
387
|
+
if target is None:
|
|
388
|
+
raise StereoIdentityError(
|
|
389
|
+
f"Descriptor reference {reference} is absent from layer {layer!r}."
|
|
390
|
+
)
|
|
391
|
+
node, kind = target
|
|
392
|
+
if kind == "H":
|
|
393
|
+
return ("virtual", "H", colours[node])
|
|
394
|
+
return ("atom", colours[node])
|
|
395
|
+
virtual = parse_virtual_reference(reference)
|
|
396
|
+
if virtual is None:
|
|
397
|
+
raise StereoIdentityError(f"Invalid virtual reference: {reference!r}.")
|
|
398
|
+
owner_target = by_reference.get(virtual.center)
|
|
399
|
+
if owner_target is None or owner_target[1] != "atom":
|
|
400
|
+
raise StereoIdentityError(
|
|
401
|
+
f"Virtual-reference owner {virtual.center} is absent from {layer!r}."
|
|
402
|
+
)
|
|
403
|
+
owner = owner_target[0]
|
|
404
|
+
return ("virtual", virtual.kind, colours[owner])
|
|
405
|
+
|
|
406
|
+
return resolve
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def stereo_identity_form(graph: nx.Graph) -> tuple[Any, ...]:
|
|
410
|
+
"""Return the inspectable, map-invariant stereo registry form."""
|
|
411
|
+
layers = stereo_registry_layers(graph)
|
|
412
|
+
if not layers:
|
|
413
|
+
return ()
|
|
414
|
+
colours = structural_node_colours(graph)
|
|
415
|
+
result = []
|
|
416
|
+
for layer, registry in sorted(layers.items()):
|
|
417
|
+
resolver = _signature_reference_resolver(graph, layer, colours)
|
|
418
|
+
forms = tuple(
|
|
419
|
+
sorted(
|
|
420
|
+
(
|
|
421
|
+
descriptor_relative_form(descriptor, resolver)
|
|
422
|
+
for descriptor in registry.values()
|
|
423
|
+
),
|
|
424
|
+
key=repr,
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
result.append((layer, forms))
|
|
428
|
+
return tuple(result)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def stereo_identity_signature(graph: nx.Graph) -> str | None:
|
|
432
|
+
"""Return a compact digest of :func:`stereo_identity_form`, if present."""
|
|
433
|
+
form = stereo_identity_form(graph)
|
|
434
|
+
return _digest(("stereo", form)) if form else None
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _mapped_reference_resolver(
|
|
438
|
+
graph: nx.Graph,
|
|
439
|
+
layer: str,
|
|
440
|
+
node_mapping: Mapping[Hashable, Hashable] | None = None,
|
|
441
|
+
) -> StereoReferenceResolver:
|
|
442
|
+
by_reference = _reference_nodes(graph, layer)
|
|
443
|
+
|
|
444
|
+
def resolve(reference: Reference) -> StereoToken:
|
|
445
|
+
if type(reference) is int:
|
|
446
|
+
target = by_reference.get(reference)
|
|
447
|
+
if target is None:
|
|
448
|
+
raise StereoIdentityError(
|
|
449
|
+
f"Descriptor reference {reference} is absent from layer {layer!r}."
|
|
450
|
+
)
|
|
451
|
+
node, kind = target
|
|
452
|
+
mapped_node = node_mapping.get(node, node) if node_mapping else node
|
|
453
|
+
if kind == "H":
|
|
454
|
+
return ("virtual", "H", ("node", mapped_node))
|
|
455
|
+
return ("node", mapped_node)
|
|
456
|
+
virtual = parse_virtual_reference(reference)
|
|
457
|
+
if virtual is None:
|
|
458
|
+
raise StereoIdentityError(f"Invalid virtual reference: {reference!r}.")
|
|
459
|
+
owner_target = by_reference.get(virtual.center)
|
|
460
|
+
if owner_target is None or owner_target[1] != "atom":
|
|
461
|
+
raise StereoIdentityError(
|
|
462
|
+
f"Virtual-reference owner {virtual.center} is absent from {layer!r}."
|
|
463
|
+
)
|
|
464
|
+
owner = owner_target[0]
|
|
465
|
+
mapped_owner = node_mapping.get(owner, owner) if node_mapping else owner
|
|
466
|
+
return ("virtual", virtual.kind, ("node", mapped_owner))
|
|
467
|
+
|
|
468
|
+
return resolve
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def mapped_reference_resolver(
|
|
472
|
+
graph: nx.Graph,
|
|
473
|
+
layer: str,
|
|
474
|
+
node_mapping: Mapping[Hashable, Hashable] | None = None,
|
|
475
|
+
) -> StereoReferenceResolver:
|
|
476
|
+
"""Resolve descriptor references to node identities under one mapping.
|
|
477
|
+
|
|
478
|
+
This is the exact counterpart of the colour-based signature resolver. It
|
|
479
|
+
is public so rule and mechanism consumers can compare associated metadata
|
|
480
|
+
under the same structural mapping as the descriptor registry.
|
|
481
|
+
"""
|
|
482
|
+
return _mapped_reference_resolver(graph, layer, node_mapping)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _descriptor_matches_policy(
|
|
486
|
+
query: StereoValue,
|
|
487
|
+
candidate: StereoValue,
|
|
488
|
+
query_resolver: StereoReferenceResolver,
|
|
489
|
+
candidate_resolver: StereoReferenceResolver,
|
|
490
|
+
unknown_policy: str,
|
|
491
|
+
) -> bool:
|
|
492
|
+
"""Match one descriptor under an explicit query-unknown policy."""
|
|
493
|
+
if type(query) is not type(candidate):
|
|
494
|
+
return False
|
|
495
|
+
query_form = descriptor_relative_form(query, query_resolver)
|
|
496
|
+
candidate_form = descriptor_relative_form(candidate, candidate_resolver)
|
|
497
|
+
if unknown_policy == "either" and query.parity is not None:
|
|
498
|
+
inverse_form = descriptor_relative_form(query.invert(), query_resolver)
|
|
499
|
+
return candidate_form in {query_form, inverse_form}
|
|
500
|
+
if query.parity is not None or unknown_policy == "exact":
|
|
501
|
+
return query_form == candidate_form
|
|
502
|
+
unknown_candidate = type(candidate)(
|
|
503
|
+
candidate.atoms,
|
|
504
|
+
None,
|
|
505
|
+
candidate.provenance,
|
|
506
|
+
)
|
|
507
|
+
return query_form == descriptor_relative_form(
|
|
508
|
+
unknown_candidate,
|
|
509
|
+
candidate_resolver,
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _exact_layer_matches(
|
|
514
|
+
queries: list[StereoValue],
|
|
515
|
+
candidates: list[StereoValue],
|
|
516
|
+
query_resolver: StereoReferenceResolver,
|
|
517
|
+
candidate_resolver: StereoReferenceResolver,
|
|
518
|
+
) -> bool:
|
|
519
|
+
"""Compare a registry layer as a multiset of exact relative forms."""
|
|
520
|
+
query_forms = Counter(
|
|
521
|
+
descriptor_relative_form(descriptor, query_resolver) for descriptor in queries
|
|
522
|
+
)
|
|
523
|
+
candidate_forms = Counter(
|
|
524
|
+
descriptor_relative_form(descriptor, candidate_resolver)
|
|
525
|
+
for descriptor in candidates
|
|
526
|
+
)
|
|
527
|
+
return query_forms == candidate_forms
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _query_layer_matches(
|
|
531
|
+
queries: list[StereoValue],
|
|
532
|
+
candidates: list[StereoValue],
|
|
533
|
+
query_resolver: StereoReferenceResolver,
|
|
534
|
+
candidate_resolver: StereoReferenceResolver,
|
|
535
|
+
unknown_policy: str,
|
|
536
|
+
) -> bool:
|
|
537
|
+
"""Find a one-to-one candidate assignment for a query registry layer."""
|
|
538
|
+
remaining = list(candidates)
|
|
539
|
+
for query in queries:
|
|
540
|
+
match_index = next(
|
|
541
|
+
(
|
|
542
|
+
index
|
|
543
|
+
for index, candidate in enumerate(remaining)
|
|
544
|
+
if _descriptor_matches_policy(
|
|
545
|
+
query,
|
|
546
|
+
candidate,
|
|
547
|
+
query_resolver,
|
|
548
|
+
candidate_resolver,
|
|
549
|
+
unknown_policy,
|
|
550
|
+
)
|
|
551
|
+
),
|
|
552
|
+
None,
|
|
553
|
+
)
|
|
554
|
+
if match_index is None:
|
|
555
|
+
return False
|
|
556
|
+
remaining.pop(match_index)
|
|
557
|
+
return True
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def mapped_stereo_registries_match(
|
|
561
|
+
left: nx.Graph,
|
|
562
|
+
right: nx.Graph,
|
|
563
|
+
node_mapping: Mapping[Hashable, Hashable],
|
|
564
|
+
*,
|
|
565
|
+
unknown_policy: str = "exact",
|
|
566
|
+
) -> bool:
|
|
567
|
+
"""Check exact layered stereo identity under one structural node mapping."""
|
|
568
|
+
if unknown_policy not in {"exact", "wildcard", "either"}:
|
|
569
|
+
raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
left_layers = stereo_registry_layers(left)
|
|
573
|
+
right_layers = stereo_registry_layers(right)
|
|
574
|
+
if set(left_layers) != set(right_layers):
|
|
575
|
+
return False
|
|
576
|
+
for layer in left_layers:
|
|
577
|
+
left_resolver = _mapped_reference_resolver(left, layer, node_mapping)
|
|
578
|
+
right_resolver = _mapped_reference_resolver(right, layer)
|
|
579
|
+
queries = list(left_layers[layer].values())
|
|
580
|
+
candidates = list(right_layers[layer].values())
|
|
581
|
+
if len(queries) != len(candidates):
|
|
582
|
+
return False
|
|
583
|
+
if unknown_policy == "exact":
|
|
584
|
+
if not _exact_layer_matches(
|
|
585
|
+
queries,
|
|
586
|
+
candidates,
|
|
587
|
+
left_resolver,
|
|
588
|
+
right_resolver,
|
|
589
|
+
):
|
|
590
|
+
return False
|
|
591
|
+
continue
|
|
592
|
+
if not _query_layer_matches(
|
|
593
|
+
queries,
|
|
594
|
+
candidates,
|
|
595
|
+
left_resolver,
|
|
596
|
+
right_resolver,
|
|
597
|
+
unknown_policy,
|
|
598
|
+
):
|
|
599
|
+
return False
|
|
600
|
+
except StereoIdentityError:
|
|
601
|
+
return False
|
|
602
|
+
return True
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def mapped_stereo_subgraph_registries_match(
|
|
606
|
+
query: nx.Graph,
|
|
607
|
+
candidate: nx.Graph,
|
|
608
|
+
node_mapping: Mapping[Hashable, Hashable],
|
|
609
|
+
*,
|
|
610
|
+
unknown_policy: str = "exact",
|
|
611
|
+
) -> bool:
|
|
612
|
+
"""Check query stereo constraints under a structural subgraph mapping.
|
|
613
|
+
|
|
614
|
+
Unlike :func:`mapped_stereo_registries_match`, candidate-only descriptors
|
|
615
|
+
are permitted. Every query descriptor must match one distinct candidate
|
|
616
|
+
descriptor after its references are transported through ``node_mapping``.
|
|
617
|
+
This is the stereo predicate required by injective graph morphisms where
|
|
618
|
+
the host may contain additional atoms, components, or stereo centers.
|
|
619
|
+
"""
|
|
620
|
+
if unknown_policy not in {"exact", "wildcard", "either"}:
|
|
621
|
+
raise ValueError("unknown_policy must be 'exact', 'wildcard', or 'either'.")
|
|
622
|
+
|
|
623
|
+
try:
|
|
624
|
+
query_layers = stereo_registry_layers(query)
|
|
625
|
+
candidate_layers = stereo_registry_layers(candidate)
|
|
626
|
+
for layer, query_registry in query_layers.items():
|
|
627
|
+
candidate_registry = candidate_layers.get(layer)
|
|
628
|
+
if candidate_registry is None or len(candidate_registry) < len(
|
|
629
|
+
query_registry
|
|
630
|
+
):
|
|
631
|
+
return False
|
|
632
|
+
query_resolver = _mapped_reference_resolver(
|
|
633
|
+
query,
|
|
634
|
+
layer,
|
|
635
|
+
node_mapping,
|
|
636
|
+
)
|
|
637
|
+
candidate_resolver = _mapped_reference_resolver(candidate, layer)
|
|
638
|
+
if not _query_layer_matches(
|
|
639
|
+
list(query_registry.values()),
|
|
640
|
+
list(candidate_registry.values()),
|
|
641
|
+
query_resolver,
|
|
642
|
+
candidate_resolver,
|
|
643
|
+
unknown_policy,
|
|
644
|
+
):
|
|
645
|
+
return False
|
|
646
|
+
except StereoIdentityError:
|
|
647
|
+
return False
|
|
648
|
+
return True
|