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,114 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from rdkit import Chem
|
|
3
|
+
from rdkit.Chem import SanitizeFlags
|
|
4
|
+
from typing import Tuple, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RadWC:
|
|
8
|
+
"""Static utility for appending wildcard dummy atoms ``[*]`` with atom-map
|
|
9
|
+
indices to all radical centers **in the product block** of a reaction SMILES.
|
|
10
|
+
|
|
11
|
+
- Reactant and agent blocks are not modified.
|
|
12
|
+
- Only atoms in the product with unpaired electrons are considered.
|
|
13
|
+
- Each product radical gets a new ``[*:N]`` with a unique map number.
|
|
14
|
+
|
|
15
|
+
.. rubric:: Example
|
|
16
|
+
|
|
17
|
+
>>> rxn = '[CH2:1][OH:2]>>[CH2:1][O:2]'
|
|
18
|
+
>>> RadWC.transform(rxn)
|
|
19
|
+
'[CH2:1][OH:2]>>[CH2:1][O:2]'
|
|
20
|
+
>>> rxn2 = '[CH2:1][OH:2]>>[CH:1].[OH:2]'
|
|
21
|
+
>>> RadWC.transform(rxn2)
|
|
22
|
+
'[CH2:1][OH:2]>>[CH:1]([*:3]).[OH:2]'
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def transform(rxn_smiles: str, start_map: Optional[int] = None) -> str:
|
|
27
|
+
"""Add [*] wildcards (with atom-map index) to every radical in the
|
|
28
|
+
product block of the input reaction SMILES.
|
|
29
|
+
|
|
30
|
+
:param rxn_smiles: Reaction SMILES, 2 or 3 blocks (R>>P or R>A>P).
|
|
31
|
+
:type rxn_smiles: str
|
|
32
|
+
:param start_map: Optional; first atom-map index for wildcards.
|
|
33
|
+
:type start_map: int or None
|
|
34
|
+
:return: Modified reaction SMILES with product wildcards.
|
|
35
|
+
:rtype: str
|
|
36
|
+
:raises ValueError: On parse error or invalid input.
|
|
37
|
+
|
|
38
|
+
.. rubric:: Example
|
|
39
|
+
|
|
40
|
+
>>> RadWC.transform('[CH2:1][OH:2]>>[CH:1].[OH:2]')
|
|
41
|
+
'[CH2:1][OH:2]>>[CH:1]([*:3]).[OH:2]'
|
|
42
|
+
"""
|
|
43
|
+
react_blk, agents_blk, prod_blk = RadWC._split_reaction(rxn_smiles)
|
|
44
|
+
# Determine atom-map to use for wildcards
|
|
45
|
+
existing = [int(n) for n in re.findall(r":(\d+)", rxn_smiles)]
|
|
46
|
+
next_map = (
|
|
47
|
+
start_map if start_map is not None else (max(existing, default=0) + 1)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
prod_frags = prod_blk.split(".") if prod_blk else []
|
|
51
|
+
new_prod_frags = []
|
|
52
|
+
|
|
53
|
+
keep_ops = SanitizeFlags.SANITIZE_ALL & ~SanitizeFlags.SANITIZE_ADJUSTHS
|
|
54
|
+
|
|
55
|
+
for smi in prod_frags:
|
|
56
|
+
if not smi:
|
|
57
|
+
continue
|
|
58
|
+
mol = Chem.MolFromSmiles(smi, sanitize=False)
|
|
59
|
+
if mol is None:
|
|
60
|
+
raise ValueError(f"Cannot parse product SMILES fragment: {smi}")
|
|
61
|
+
Chem.SanitizeMol(mol, sanitizeOps=keep_ops)
|
|
62
|
+
rw = Chem.RWMol(mol)
|
|
63
|
+
atoms = list(rw.GetAtoms())
|
|
64
|
+
changed = False
|
|
65
|
+
for atom in atoms:
|
|
66
|
+
rad = atom.GetNumRadicalElectrons()
|
|
67
|
+
if rad > 0:
|
|
68
|
+
for _ in range(rad):
|
|
69
|
+
dummy = Chem.Atom(0)
|
|
70
|
+
dummy.SetAtomMapNum(next_map)
|
|
71
|
+
dummy.SetNoImplicit(True)
|
|
72
|
+
rw.AddAtom(dummy)
|
|
73
|
+
rw.AddBond(
|
|
74
|
+
atom.GetIdx(), rw.GetNumAtoms() - 1, Chem.BondType.SINGLE
|
|
75
|
+
)
|
|
76
|
+
next_map += 1
|
|
77
|
+
changed = True
|
|
78
|
+
if changed:
|
|
79
|
+
Chem.SanitizeMol(rw.GetMol(), sanitizeOps=keep_ops)
|
|
80
|
+
new_prod_frags.append(
|
|
81
|
+
Chem.MolToSmiles(rw.GetMol(), isomericSmiles=True, allHsExplicit=True)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
prod_str = ".".join(new_prod_frags)
|
|
85
|
+
if agents_blk is None:
|
|
86
|
+
return f"{react_blk}>>{prod_str}"
|
|
87
|
+
return f"{react_blk}>{agents_blk}>{prod_str}"
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def _split_reaction(rxn: str) -> Tuple[str, Optional[str], str]:
|
|
91
|
+
"""Split a reaction SMILES into (reactant, agent or None, product).
|
|
92
|
+
|
|
93
|
+
:param rxn: Reaction SMILES string.
|
|
94
|
+
:type rxn: str
|
|
95
|
+
:return: (reactant, agent, product) tuple (agent may be None).
|
|
96
|
+
:rtype: Tuple[str, Optional[str], str]
|
|
97
|
+
:raises ValueError: If the SMILES does not contain 2 or 3 '>'s.
|
|
98
|
+
"""
|
|
99
|
+
parts = rxn.split(">")
|
|
100
|
+
if len(parts) == 2:
|
|
101
|
+
return parts[0], None, parts[1]
|
|
102
|
+
if len(parts) == 3:
|
|
103
|
+
return parts[0], parts[1], parts[2]
|
|
104
|
+
raise ValueError("Reaction SMILES must contain 2 or 3 '>' symbols")
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def describe():
|
|
108
|
+
"""
|
|
109
|
+
Print a description and minimal example.
|
|
110
|
+
"""
|
|
111
|
+
print(RadWC.__doc__)
|
|
112
|
+
|
|
113
|
+
def __repr__(self):
|
|
114
|
+
return "<RadWC: static radical-wildcard utility for product block only>"
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Set, Tuple, Union
|
|
4
|
+
from collections import Counter
|
|
5
|
+
import logging
|
|
6
|
+
import numbers
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
from networkx.algorithms import isomorphism
|
|
10
|
+
|
|
11
|
+
GraphType = Union[nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
|
12
|
+
|
|
13
|
+
__all__ = ["WCMatcher", "WildcardPatternMatcher"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WCMatcher:
|
|
17
|
+
"""
|
|
18
|
+
Wildcard-aware pattern→host matcher with subgraph regions for wildcard nodes.
|
|
19
|
+
|
|
20
|
+
Semantics
|
|
21
|
+
---------
|
|
22
|
+
• The graph containing nodes with ``attrs[element_key] == wildcard_element``
|
|
23
|
+
is treated as the **pattern**.
|
|
24
|
+
• Wildcard nodes (``element == "*"`` by default) are **removed** from the
|
|
25
|
+
pattern when computing the *core isomorphism*.
|
|
26
|
+
• Only the **core** pattern nodes (non-wildcards) are matched against the
|
|
27
|
+
host using :class:`networkx.algorithms.isomorphism.GraphMatcher`.
|
|
28
|
+
• Wildcard nodes are treated as “don’t care” substituents attached to core
|
|
29
|
+
atoms. Their **exact number and structure are ignored** – we only ensure
|
|
30
|
+
that the core scaffold is present in the host.
|
|
31
|
+
• After a core mapping is found, each wildcard node is associated with a
|
|
32
|
+
**host subgraph region**, obtained as neighbours of its mapped anchor
|
|
33
|
+
nodes in the host.
|
|
34
|
+
|
|
35
|
+
Attribute matching
|
|
36
|
+
------------------
|
|
37
|
+
Node attributes (for keys listed in :data:`node_attrs`):
|
|
38
|
+
|
|
39
|
+
• ``str`` and ``bool``: must match **exactly**.
|
|
40
|
+
• ``int`` and ``float``: pattern value ≤ host value (lower-bound semantics).
|
|
41
|
+
• Special handling for a ``"neighbors"`` attribute if present:
|
|
42
|
+
- Pattern neighbour list may contain wildcard entries (e.g. ``"*"``
|
|
43
|
+
for any element).
|
|
44
|
+
- Concrete neighbour labels in the pattern act as **lower bounds** on
|
|
45
|
+
host counts (multiset inclusion).
|
|
46
|
+
- Wildcard neighbour labels impose no constraint.
|
|
47
|
+
|
|
48
|
+
Edge attributes (for keys listed in :data:`edge_attrs`):
|
|
49
|
+
|
|
50
|
+
• Same typed semantics as node attributes (str/bool exact, numeric ≤).
|
|
51
|
+
|
|
52
|
+
Typical usage
|
|
53
|
+
-------------
|
|
54
|
+
.. code-block:: python
|
|
55
|
+
|
|
56
|
+
import networkx as nx
|
|
57
|
+
from synkit.Graph.Matcher.wc_matcher import WCMatcher
|
|
58
|
+
|
|
59
|
+
# Host: C-C-C chain
|
|
60
|
+
host = nx.path_graph(3)
|
|
61
|
+
nx.set_node_attributes(host, "C", "element")
|
|
62
|
+
|
|
63
|
+
# Pattern: C-C-* (wildcard substituent on the second carbon)
|
|
64
|
+
pattern = nx.path_graph(3)
|
|
65
|
+
nx.set_node_attributes(pattern, "C", "element")
|
|
66
|
+
pattern.nodes[2]["element"] = "*" # wildcard node
|
|
67
|
+
|
|
68
|
+
matcher = WCMatcher(
|
|
69
|
+
pattern,
|
|
70
|
+
host,
|
|
71
|
+
wildcard_element="*",
|
|
72
|
+
element_key="element",
|
|
73
|
+
node_attrs=["element"],
|
|
74
|
+
edge_attrs=[],
|
|
75
|
+
).fit()
|
|
76
|
+
|
|
77
|
+
if matcher.is_match:
|
|
78
|
+
print("Core mapping:", matcher.core_mapping_without_wildcard_regions)
|
|
79
|
+
print("Wildcard regions:", matcher.wildcard_subgraph_mapping)
|
|
80
|
+
|
|
81
|
+
:param G1: First input graph.
|
|
82
|
+
:type G1: GraphType
|
|
83
|
+
:param G2: Second input graph.
|
|
84
|
+
:type G2: GraphType
|
|
85
|
+
:param wildcard_element: Node attribute value that denotes a wildcard
|
|
86
|
+
in the pattern. Defaults to ``"*"``
|
|
87
|
+
:type wildcard_element: str
|
|
88
|
+
:param element_key: Name of the node attribute storing the element /
|
|
89
|
+
atom type. Defaults to ``"element"``.
|
|
90
|
+
:type element_key: str
|
|
91
|
+
:param node_attrs: Node attribute keys to be checked in addition to
|
|
92
|
+
:data:`element_key`. If ``"neighbors"`` is included, neighbour
|
|
93
|
+
lists are compared with lower-bound / wildcard semantics.
|
|
94
|
+
:type node_attrs: Sequence[str] | None
|
|
95
|
+
:param edge_attrs: Edge attribute keys to be checked with typed
|
|
96
|
+
semantics.
|
|
97
|
+
:type edge_attrs: Sequence[str] | None
|
|
98
|
+
:param node_match: Optional additional node predicate, combined with
|
|
99
|
+
the default matcher. Signature:
|
|
100
|
+
``(host_attr: dict, pattern_attr: dict) -> bool``.
|
|
101
|
+
:type node_match: Callable[[Dict[str, Any], Dict[str, Any]], bool] | None
|
|
102
|
+
:param edge_match: Optional additional edge predicate, combined with
|
|
103
|
+
the default matcher. Signature:
|
|
104
|
+
``(host_attr: dict, pattern_attr: dict) -> bool``.
|
|
105
|
+
:type edge_match: Callable[[Dict[str, Any], Dict[str, Any]], bool] | None
|
|
106
|
+
:param logger: Optional logger for diagnostics.
|
|
107
|
+
:type logger: logging.Logger | None
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------ init / basic props
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
G1: GraphType,
|
|
115
|
+
G2: GraphType,
|
|
116
|
+
*,
|
|
117
|
+
wildcard_element: str = "*",
|
|
118
|
+
element_key: str = "element",
|
|
119
|
+
node_attrs: Optional[Sequence[str]] = None,
|
|
120
|
+
edge_attrs: Optional[Sequence[str]] = None,
|
|
121
|
+
node_match: Optional[Callable[[Dict[str, Any], Dict[str, Any]], bool]] = None,
|
|
122
|
+
edge_match: Optional[Callable[[Dict[str, Any], Dict[str, Any]], bool]] = None,
|
|
123
|
+
logger: Optional[logging.Logger] = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
self._logger = logger or logging.getLogger(__name__)
|
|
126
|
+
self._element_key = element_key
|
|
127
|
+
self._wildcard_element = wildcard_element
|
|
128
|
+
self._user_node_match = node_match
|
|
129
|
+
self._user_edge_match = edge_match
|
|
130
|
+
self._node_attrs = tuple(node_attrs or [])
|
|
131
|
+
self._edge_attrs = tuple(edge_attrs or [])
|
|
132
|
+
|
|
133
|
+
self._host: GraphType
|
|
134
|
+
self._pattern: GraphType
|
|
135
|
+
self._pattern_is_G1: bool
|
|
136
|
+
|
|
137
|
+
self._host, self._pattern, self._pattern_is_G1 = self._choose_host_pattern(
|
|
138
|
+
G1, G2
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Core mapping: pattern(non-wc node) -> host node
|
|
142
|
+
self._core_mapping: Dict[Any, Any] = {}
|
|
143
|
+
self._is_match: bool = False
|
|
144
|
+
|
|
145
|
+
def __repr__(self) -> str:
|
|
146
|
+
"""String representation for debugging.
|
|
147
|
+
|
|
148
|
+
:return: Short summary including match status and node counts.
|
|
149
|
+
:rtype: str
|
|
150
|
+
"""
|
|
151
|
+
status = "matched" if self._is_match else "unmatched"
|
|
152
|
+
return (
|
|
153
|
+
f"<WCMatcher status={status} "
|
|
154
|
+
f"n_pattern={self._pattern.number_of_nodes()} "
|
|
155
|
+
f"n_host={self._host.number_of_nodes()}>"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# ------------------------------------------------------------------ public API
|
|
159
|
+
|
|
160
|
+
def fit(self) -> "WCMatcher":
|
|
161
|
+
"""Run the wildcard-aware core isomorphism search.
|
|
162
|
+
|
|
163
|
+
The method computes the **core** subgraph isomorphism (ignoring
|
|
164
|
+
wildcard nodes) and stores the mapping internally. Use
|
|
165
|
+
:attr:`is_match` and :attr:`core_mapping_without_wildcard_regions`
|
|
166
|
+
to inspect the result.
|
|
167
|
+
|
|
168
|
+
:return: Self, to allow fluent chaining.
|
|
169
|
+
:rtype: WCMatcher
|
|
170
|
+
"""
|
|
171
|
+
pattern_core = self._build_pattern_core()
|
|
172
|
+
|
|
173
|
+
# Quick size check – if core is larger than host, no match is possible.
|
|
174
|
+
if (
|
|
175
|
+
pattern_core.number_of_nodes() > self._host.number_of_nodes()
|
|
176
|
+
or pattern_core.number_of_edges() > self._host.number_of_edges()
|
|
177
|
+
):
|
|
178
|
+
self._logger.debug(
|
|
179
|
+
"WCMatcher: pattern core larger than host (no match possible)."
|
|
180
|
+
)
|
|
181
|
+
self._core_mapping = {}
|
|
182
|
+
self._is_match = False
|
|
183
|
+
return self
|
|
184
|
+
|
|
185
|
+
gm = self._make_graph_matcher(pattern_core)
|
|
186
|
+
|
|
187
|
+
for host_to_pattern_core in gm.subgraph_isomorphisms_iter():
|
|
188
|
+
# networkx gives host→pattern mapping; we want pattern→host
|
|
189
|
+
core_to_host = {p: h for h, p in host_to_pattern_core.items()}
|
|
190
|
+
self._core_mapping = core_to_host
|
|
191
|
+
self._is_match = True
|
|
192
|
+
self._logger.debug("WCMatcher: core match found: %s", core_to_host)
|
|
193
|
+
return self
|
|
194
|
+
|
|
195
|
+
self._logger.debug("WCMatcher: no core mapping found.")
|
|
196
|
+
self._core_mapping = {}
|
|
197
|
+
self._is_match = False
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def is_match(self) -> bool:
|
|
202
|
+
"""Whether a wildcard-compatible **core** mapping was found.
|
|
203
|
+
|
|
204
|
+
:return: ``True`` if the core pattern matches a subgraph of the host.
|
|
205
|
+
:rtype: bool
|
|
206
|
+
"""
|
|
207
|
+
return self._is_match
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def pattern_graph(self) -> GraphType:
|
|
211
|
+
"""Graph that was treated as the **pattern** (may contain wildcards).
|
|
212
|
+
|
|
213
|
+
:return: Pattern graph.
|
|
214
|
+
:rtype: GraphType
|
|
215
|
+
"""
|
|
216
|
+
return self._pattern
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def host_graph(self) -> GraphType:
|
|
220
|
+
"""Graph that was treated as the **host**.
|
|
221
|
+
|
|
222
|
+
:return: Host graph.
|
|
223
|
+
:rtype: GraphType
|
|
224
|
+
"""
|
|
225
|
+
return self._host
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def core_mapping_without_wildcard_regions(self) -> Dict[Any, Any]:
|
|
229
|
+
"""Mapping from **non-wildcard pattern nodes → host nodes**.
|
|
230
|
+
|
|
231
|
+
Wildcard nodes in the pattern are ignored in this mapping. This is the
|
|
232
|
+
clean "core" mapping without any enlargement due to wildcard regions.
|
|
233
|
+
|
|
234
|
+
:return: Mapping from pattern-core nodes to host nodes.
|
|
235
|
+
:rtype: dict[Any, Any]
|
|
236
|
+
"""
|
|
237
|
+
return dict(self._core_mapping)
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def wildcard_subgraph_mapping(self) -> Dict[Any, Set[Any]]:
|
|
241
|
+
"""Mapping from each **wildcard pattern node** to a set of host nodes
|
|
242
|
+
forming its wildcard subgraph region.
|
|
243
|
+
|
|
244
|
+
Construction heuristic
|
|
245
|
+
----------------------
|
|
246
|
+
For each wildcard node ``w`` in the pattern:
|
|
247
|
+
|
|
248
|
+
1. Collect its **non-wildcard neighbour(s)** in the pattern.
|
|
249
|
+
2. Map those neighbours to host anchors using the core mapping.
|
|
250
|
+
3. For each anchor ``h`` in the host, add all neighbours of ``h``
|
|
251
|
+
that are **not already used** by any core mapping.
|
|
252
|
+
|
|
253
|
+
.. rubric:: Notes
|
|
254
|
+
|
|
255
|
+
• If there is no core mapping (``is_match == False``), the result is
|
|
256
|
+
an empty dict.
|
|
257
|
+
• If a wildcard's anchors cannot be mapped (e.g. missing in core),
|
|
258
|
+
its region is an empty set.
|
|
259
|
+
|
|
260
|
+
:return: Mapping ``wildcard_pattern_node → set(host_nodes_in_region)``.
|
|
261
|
+
:rtype: dict[Any, set[Any]]
|
|
262
|
+
"""
|
|
263
|
+
if not self._is_match:
|
|
264
|
+
return {}
|
|
265
|
+
|
|
266
|
+
wc_nodes = [
|
|
267
|
+
n
|
|
268
|
+
for n, data in self._pattern.nodes(data=True)
|
|
269
|
+
if data.get(self._element_key) == self._wildcard_element
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
if not wc_nodes:
|
|
273
|
+
return {}
|
|
274
|
+
|
|
275
|
+
core_hosts: Set[Any] = set(self._core_mapping.values())
|
|
276
|
+
regions: Dict[Any, Set[Any]] = {}
|
|
277
|
+
|
|
278
|
+
for w in wc_nodes:
|
|
279
|
+
neigh = list(self._pattern.neighbors(w))
|
|
280
|
+
anchors = [
|
|
281
|
+
p
|
|
282
|
+
for p in neigh
|
|
283
|
+
if self._pattern.nodes[p].get(self._element_key)
|
|
284
|
+
!= self._wildcard_element
|
|
285
|
+
and p in self._core_mapping
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
region: Set[Any] = set()
|
|
289
|
+
for p in anchors:
|
|
290
|
+
h_anchor = self._core_mapping[p]
|
|
291
|
+
for h_nbr in self._host.neighbors(h_anchor):
|
|
292
|
+
if h_nbr not in core_hosts:
|
|
293
|
+
region.add(h_nbr)
|
|
294
|
+
|
|
295
|
+
regions[w] = region
|
|
296
|
+
|
|
297
|
+
return regions
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def help(self) -> str:
|
|
301
|
+
"""Human-readable summary of the matcher behaviour.
|
|
302
|
+
|
|
303
|
+
:return: Description string summarising semantics and attributes.
|
|
304
|
+
:rtype: str
|
|
305
|
+
"""
|
|
306
|
+
return (
|
|
307
|
+
"WCMatcher (WildcardPatternMatcher):\n"
|
|
308
|
+
" • pattern = graph with wildcard nodes (element == wildcard_element)\n"
|
|
309
|
+
" • host = other graph (or larger if both/none have wildcards)\n"
|
|
310
|
+
" • semantics:\n"
|
|
311
|
+
" - core match ignores wildcard nodes\n"
|
|
312
|
+
" - node attrs: str/bool exact, numeric pattern ≤ host\n"
|
|
313
|
+
" - neighbours: concrete labels are lower bounds; '*' is wildcard\n"
|
|
314
|
+
" - wildcard_subgraph_mapping gives host regions per wildcard node"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# ------------------------------------------------------------------ internals: host/pattern selection
|
|
318
|
+
|
|
319
|
+
def _choose_host_pattern(
|
|
320
|
+
self, G1: GraphType, G2: GraphType
|
|
321
|
+
) -> Tuple[GraphType, GraphType, bool]:
|
|
322
|
+
"""Decide which graph is host and which is pattern.
|
|
323
|
+
|
|
324
|
+
Preference
|
|
325
|
+
----------
|
|
326
|
+
1. Graph containing wildcard nodes is the pattern.
|
|
327
|
+
2. If both (or neither) contain wildcards, the **smaller** graph is
|
|
328
|
+
treated as the pattern.
|
|
329
|
+
|
|
330
|
+
:param G1: First input graph.
|
|
331
|
+
:type G1: GraphType
|
|
332
|
+
:param G2: Second input graph.
|
|
333
|
+
:type G2: GraphType
|
|
334
|
+
:return: Tuple ``(host, pattern, pattern_is_G1)``.
|
|
335
|
+
:rtype: tuple[GraphType, GraphType, bool]
|
|
336
|
+
"""
|
|
337
|
+
has_wc1 = self._graph_has_wildcard(G1)
|
|
338
|
+
has_wc2 = self._graph_has_wildcard(G2)
|
|
339
|
+
|
|
340
|
+
if has_wc1 and not has_wc2:
|
|
341
|
+
return G2, G1, True
|
|
342
|
+
if has_wc2 and not has_wc1:
|
|
343
|
+
return G1, G2, False
|
|
344
|
+
|
|
345
|
+
if G1.number_of_nodes() <= G2.number_of_nodes():
|
|
346
|
+
return G2, G1, True
|
|
347
|
+
return G1, G2, False
|
|
348
|
+
|
|
349
|
+
def _graph_has_wildcard(self, G: GraphType) -> bool:
|
|
350
|
+
"""Check whether a graph contains at least one wildcard node.
|
|
351
|
+
|
|
352
|
+
:param G: Graph to inspect.
|
|
353
|
+
:type G: GraphType
|
|
354
|
+
:return: ``True`` if a wildcard node is present.
|
|
355
|
+
:rtype: bool
|
|
356
|
+
"""
|
|
357
|
+
key = self._element_key
|
|
358
|
+
wc = self._wildcard_element
|
|
359
|
+
for _, data in G.nodes(data=True):
|
|
360
|
+
if data.get(key) == wc:
|
|
361
|
+
return True
|
|
362
|
+
return False
|
|
363
|
+
|
|
364
|
+
# ------------------------------------------------------------------ internals: pattern core construction
|
|
365
|
+
|
|
366
|
+
def _build_pattern_core(self) -> GraphType:
|
|
367
|
+
"""Build the core pattern graph (non-wildcard nodes only).
|
|
368
|
+
|
|
369
|
+
Wildcard nodes (with ``element == wildcard_element``) are removed,
|
|
370
|
+
and the induced subgraph is returned.
|
|
371
|
+
|
|
372
|
+
:return: Core pattern graph without wildcard nodes.
|
|
373
|
+
:rtype: GraphType
|
|
374
|
+
"""
|
|
375
|
+
key = self._element_key
|
|
376
|
+
wc = self._wildcard_element
|
|
377
|
+
|
|
378
|
+
core_nodes: Set[Any] = {
|
|
379
|
+
n for n, data in self._pattern.nodes(data=True) if data.get(key) != wc
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return self._pattern.subgraph(core_nodes).copy()
|
|
383
|
+
|
|
384
|
+
# ------------------------------------------------------------------ internals: typed attribute comparison
|
|
385
|
+
|
|
386
|
+
def _typed_leq(self, host_val: Any, pat_val: Any) -> bool:
|
|
387
|
+
"""Typed comparison of attribute values.
|
|
388
|
+
|
|
389
|
+
Rules
|
|
390
|
+
-----
|
|
391
|
+
• If ``pat_val`` is ``None``: no constraint → ``True``.
|
|
392
|
+
• If both values are numeric (int/float): require ``host_val >= pat_val``.
|
|
393
|
+
• If both are ``str`` or both are ``bool``: require equality.
|
|
394
|
+
• Otherwise: fall back to equality.
|
|
395
|
+
|
|
396
|
+
:param host_val: Attribute value from the host graph.
|
|
397
|
+
:type host_val: Any
|
|
398
|
+
:param pat_val: Attribute value from the pattern graph.
|
|
399
|
+
:type pat_val: Any
|
|
400
|
+
:return: ``True`` if ``host_val`` satisfies the pattern constraint.
|
|
401
|
+
:rtype: bool
|
|
402
|
+
"""
|
|
403
|
+
if pat_val is None:
|
|
404
|
+
return True
|
|
405
|
+
|
|
406
|
+
if isinstance(host_val, numbers.Number) and isinstance(pat_val, numbers.Number):
|
|
407
|
+
return host_val >= pat_val
|
|
408
|
+
|
|
409
|
+
if isinstance(host_val, str) and isinstance(pat_val, str):
|
|
410
|
+
return host_val == pat_val
|
|
411
|
+
if isinstance(host_val, bool) and isinstance(pat_val, bool):
|
|
412
|
+
return host_val == pat_val
|
|
413
|
+
|
|
414
|
+
return host_val == pat_val
|
|
415
|
+
|
|
416
|
+
def _neighbors_compatible(
|
|
417
|
+
self,
|
|
418
|
+
host_neigh: Optional[Iterable[Any]],
|
|
419
|
+
pat_neigh: Optional[Iterable[Any]],
|
|
420
|
+
) -> bool:
|
|
421
|
+
"""Compare neighbour "element" lists with wildcard & lower-bound semantics.
|
|
422
|
+
|
|
423
|
+
• Pattern neighbour list may contain wildcard entries
|
|
424
|
+
(``self._wildcard_element``).
|
|
425
|
+
• Concrete labels in the pattern act as **lower bounds** on host counts:
|
|
426
|
+
``count_host[label] >= count_pattern[label]``.
|
|
427
|
+
• Wildcards in the pattern impose no constraint on the host.
|
|
428
|
+
|
|
429
|
+
:param host_neigh: Neighbour labels in the host (e.g. list of element
|
|
430
|
+
symbols).
|
|
431
|
+
:type host_neigh: Iterable[Any] | None
|
|
432
|
+
:param pat_neigh: Neighbour labels in the pattern.
|
|
433
|
+
:type pat_neigh: Iterable[Any] | None
|
|
434
|
+
:return: ``True`` if host neighbours satisfy the pattern constraints.
|
|
435
|
+
:rtype: bool
|
|
436
|
+
"""
|
|
437
|
+
if pat_neigh is None:
|
|
438
|
+
return True
|
|
439
|
+
|
|
440
|
+
wc = self._wildcard_element
|
|
441
|
+
host_neigh = list(host_neigh or [])
|
|
442
|
+
pat_neigh = list(pat_neigh)
|
|
443
|
+
|
|
444
|
+
pat_counts = Counter(lbl for lbl in pat_neigh if lbl != wc)
|
|
445
|
+
host_counts = Counter(host_neigh)
|
|
446
|
+
|
|
447
|
+
for lbl, p_cnt in pat_counts.items():
|
|
448
|
+
if host_counts[lbl] < p_cnt:
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
return True
|
|
452
|
+
|
|
453
|
+
# ------------------------------------------------------------------ internals: attribute-based node / edge checks
|
|
454
|
+
|
|
455
|
+
def _node_attrs_ok(
|
|
456
|
+
self,
|
|
457
|
+
host_attr: Dict[str, Any],
|
|
458
|
+
pat_attr: Dict[str, Any],
|
|
459
|
+
*,
|
|
460
|
+
key: str,
|
|
461
|
+
wc: str,
|
|
462
|
+
) -> bool:
|
|
463
|
+
"""Check node-level attributes including element & neighbours.
|
|
464
|
+
|
|
465
|
+
:param host_attr: Host node attribute dictionary.
|
|
466
|
+
:type host_attr: dict[str, Any]
|
|
467
|
+
:param pat_attr: Pattern node attribute dictionary.
|
|
468
|
+
:type pat_attr: dict[str, Any]
|
|
469
|
+
:param key: Element attribute key (e.g. ``"element"``).
|
|
470
|
+
:type key: str
|
|
471
|
+
:param wc: Wildcard element value.
|
|
472
|
+
:type wc: str
|
|
473
|
+
:return: ``True`` if host node satisfies pattern node constraints.
|
|
474
|
+
:rtype: bool
|
|
475
|
+
"""
|
|
476
|
+
host_el = host_attr.get(key)
|
|
477
|
+
pat_el = pat_attr.get(key)
|
|
478
|
+
if pat_el is not None and pat_el != wc:
|
|
479
|
+
if host_el != pat_el:
|
|
480
|
+
return False
|
|
481
|
+
|
|
482
|
+
for name in self._node_attrs:
|
|
483
|
+
if name == "neighbors":
|
|
484
|
+
if not self._neighbors_compatible(
|
|
485
|
+
host_attr.get(name), pat_attr.get(name)
|
|
486
|
+
):
|
|
487
|
+
return False
|
|
488
|
+
else:
|
|
489
|
+
if not self._typed_leq(host_attr.get(name), pat_attr.get(name)):
|
|
490
|
+
return False
|
|
491
|
+
|
|
492
|
+
return True
|
|
493
|
+
|
|
494
|
+
def _edge_attrs_ok(
|
|
495
|
+
self,
|
|
496
|
+
host_attr: Dict[str, Any],
|
|
497
|
+
pat_attr: Dict[str, Any],
|
|
498
|
+
) -> bool:
|
|
499
|
+
"""Check edge-level attributes using typed semantics.
|
|
500
|
+
|
|
501
|
+
:param host_attr: Host edge attribute dictionary.
|
|
502
|
+
:type host_attr: dict[str, Any]
|
|
503
|
+
:param pat_attr: Pattern edge attribute dictionary.
|
|
504
|
+
:type pat_attr: dict[str, Any]
|
|
505
|
+
:return: ``True`` if host edge satisfies pattern edge constraints.
|
|
506
|
+
:rtype: bool
|
|
507
|
+
"""
|
|
508
|
+
for name in self._edge_attrs:
|
|
509
|
+
if not self._typed_leq(host_attr.get(name), pat_attr.get(name)):
|
|
510
|
+
return False
|
|
511
|
+
return True
|
|
512
|
+
|
|
513
|
+
# ------------------------------------------------------------------ internals: GraphMatcher construction
|
|
514
|
+
|
|
515
|
+
def _make_graph_matcher(self, pattern_core: GraphType) -> Any:
|
|
516
|
+
"""Build a NetworkX GraphMatcher for host vs. pattern_core.
|
|
517
|
+
|
|
518
|
+
Default behaviour
|
|
519
|
+
-----------------
|
|
520
|
+
• Node match:
|
|
521
|
+
- :data:`element_key`: exact, unless pattern element is
|
|
522
|
+
:data:`wildcard_element`.
|
|
523
|
+
- For keys in :attr:`_node_attrs`:
|
|
524
|
+
* If key == ``"neighbors"``: use neighbour lower-bound semantics.
|
|
525
|
+
* Else: typed comparison via :meth:`_typed_leq`.
|
|
526
|
+
- Then apply optional :data:`node_match` predicate if given.
|
|
527
|
+
• Edge match:
|
|
528
|
+
- For keys in :attr:`_edge_attrs`: typed comparison via
|
|
529
|
+
:meth:`_typed_leq`.
|
|
530
|
+
- Then optional :data:`edge_match` predicate if given.
|
|
531
|
+
|
|
532
|
+
:param pattern_core: Induced subgraph of the pattern on non-wildcard
|
|
533
|
+
nodes.
|
|
534
|
+
:type pattern_core: GraphType
|
|
535
|
+
:return: Instance of :class:`GraphMatcher` or
|
|
536
|
+
:class:`MultiGraphMatcher` depending on the host type.
|
|
537
|
+
:rtype: Any
|
|
538
|
+
"""
|
|
539
|
+
key = self._element_key
|
|
540
|
+
wc = self._wildcard_element
|
|
541
|
+
user_node_match = self._user_node_match
|
|
542
|
+
user_edge_match = self._user_edge_match
|
|
543
|
+
|
|
544
|
+
def default_node_match(
|
|
545
|
+
host_attr: Dict[str, Any], pat_attr: Dict[str, Any]
|
|
546
|
+
) -> bool:
|
|
547
|
+
if not self._node_attrs_ok(host_attr, pat_attr, key=key, wc=wc):
|
|
548
|
+
return False
|
|
549
|
+
if user_node_match is not None and not user_node_match(host_attr, pat_attr):
|
|
550
|
+
return False
|
|
551
|
+
return True
|
|
552
|
+
|
|
553
|
+
def default_edge_match(
|
|
554
|
+
host_attr: Dict[str, Any], pat_attr: Dict[str, Any]
|
|
555
|
+
) -> bool:
|
|
556
|
+
if not self._edge_attrs_ok(host_attr, pat_attr):
|
|
557
|
+
return False
|
|
558
|
+
if user_edge_match is not None and not user_edge_match(host_attr, pat_attr):
|
|
559
|
+
return False
|
|
560
|
+
return True
|
|
561
|
+
|
|
562
|
+
if isinstance(self._host, (nx.MultiGraph, nx.MultiDiGraph)):
|
|
563
|
+
matcher_cls = isomorphism.MultiGraphMatcher
|
|
564
|
+
else:
|
|
565
|
+
matcher_cls = isomorphism.GraphMatcher
|
|
566
|
+
|
|
567
|
+
return matcher_cls(
|
|
568
|
+
self._host,
|
|
569
|
+
pattern_core,
|
|
570
|
+
node_match=default_node_match,
|
|
571
|
+
edge_match=default_edge_match,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
# Backwards-compatible alias
|
|
576
|
+
WildcardPatternMatcher = WCMatcher
|