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,804 @@
|
|
|
1
|
+
"""Common and maximum-common-subgraph matching for molecular graphs.
|
|
2
|
+
|
|
3
|
+
The matcher supports configurable node and edge attributes, optional wildcard
|
|
4
|
+
and automorphism pruning, connected-component matching, and reaction-centre
|
|
5
|
+
side selection. Results are cached and can be returned in either graph
|
|
6
|
+
orientation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import itertools
|
|
12
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple
|
|
13
|
+
|
|
14
|
+
import networkx as nx
|
|
15
|
+
from networkx.algorithms.isomorphism import GraphMatcher, generic_node_match
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from synkit.Graph.ITS import its_decompose # optional
|
|
19
|
+
except ImportError: # pragma: no cover – allow standalone use
|
|
20
|
+
its_decompose = None # type: ignore[assignment]
|
|
21
|
+
|
|
22
|
+
__all__ = ["MCSMatcher"]
|
|
23
|
+
|
|
24
|
+
MappingDict = Dict[int, int]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MCSMatcher:
|
|
28
|
+
"""
|
|
29
|
+
Common / maximum-common subgraph matcher.
|
|
30
|
+
|
|
31
|
+
This class wraps :class:`networkx.algorithms.isomorphism.GraphMatcher`
|
|
32
|
+
to provide higher-level utilities for computing sets of common subgraphs
|
|
33
|
+
between two graphs, with a focus on molecular graphs (atoms/bonds).
|
|
34
|
+
|
|
35
|
+
Node matching is controlled via :func:`generic_node_match` using one or
|
|
36
|
+
more attribute names and default values. Edge matching compares one or
|
|
37
|
+
more scalar edge attributes (e.g. bond order) specified in
|
|
38
|
+
``edge_attrs``.
|
|
39
|
+
|
|
40
|
+
Mappings and orientation
|
|
41
|
+
------------------------
|
|
42
|
+
Internally, mappings are always stored as **pattern → host**, where
|
|
43
|
+
the *pattern* is the smaller of the two (after optional wildcard
|
|
44
|
+
pruning). The helper :py:meth:`get_mappings` can convert these to
|
|
45
|
+
``G1→G2`` or ``G2→G1`` orientation as needed, and the property
|
|
46
|
+
:attr:`mapping_direction` exposes which graph acted as the pattern.
|
|
47
|
+
|
|
48
|
+
Optional wildcard pruning
|
|
49
|
+
-------------------------
|
|
50
|
+
If ``prune_wc`` is ``True``, nodes with
|
|
51
|
+
``attrs[element_key] == wildcard_element`` are removed from both input
|
|
52
|
+
graphs **non-inplace** before the MCS search. Node labels are preserved,
|
|
53
|
+
so the resulting mappings still reference the original node ids.
|
|
54
|
+
|
|
55
|
+
Optional automorphism pruning
|
|
56
|
+
-----------------------------
|
|
57
|
+
If ``prune_automorphisms`` is ``True``, mappings that induce
|
|
58
|
+
the same **host node set** (i.e. same image in the host graph) are
|
|
59
|
+
collapsed. This is especially useful for highly symmetric hosts (rings,
|
|
60
|
+
repeated subunits, etc.) where many mappings are equivalent at the level
|
|
61
|
+
of “which part of the host is covered”.
|
|
62
|
+
|
|
63
|
+
:param node_attrs: Node attribute keys to compare. If ``None``,
|
|
64
|
+
defaults to ``["element"]``.
|
|
65
|
+
:type node_attrs: list[str] | None
|
|
66
|
+
:param node_defaults: Fallback values for each node attribute
|
|
67
|
+
when missing. If ``None``, defaults to a list of ``"*"``
|
|
68
|
+
of the same length as ``node_attrs``.
|
|
69
|
+
:type node_defaults: list[Any] | None
|
|
70
|
+
:param allow_shift: Placeholder for future asymmetric rules. Currently
|
|
71
|
+
unused but kept for API compatibility.
|
|
72
|
+
:type allow_shift: bool
|
|
73
|
+
:param edge_attrs: Edge attribute keys to use for scalar comparison
|
|
74
|
+
(e.g. ``["order"]`` or ``["order", "standard_order"]``). If
|
|
75
|
+
``None``, defaults to ``["order"]``.
|
|
76
|
+
:type edge_attrs: list[str] | None
|
|
77
|
+
:param prune_wc: If ``True``, strip wildcard nodes (see
|
|
78
|
+
``wildcard_element``, ``element_key``) from both
|
|
79
|
+
graphs before searching.
|
|
80
|
+
:type prune_wc: bool
|
|
81
|
+
:param prune_automorphisms: If ``True``, collapse mappings that have
|
|
82
|
+
the same host node set (automorphism pruning).
|
|
83
|
+
:type prune_automorphisms: bool
|
|
84
|
+
:param wildcard_element: Attribute value denoting wildcard nodes
|
|
85
|
+
(typically ``"*"``, used together with ``element_key``).
|
|
86
|
+
:type wildcard_element: Any
|
|
87
|
+
:param element_key: Node attribute key used to detect wildcard nodes
|
|
88
|
+
when ``prune_wc`` is ``True``.
|
|
89
|
+
:type element_key: str
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
# Construction
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
node_attrs: Optional[List[str]] = None,
|
|
98
|
+
node_defaults: Optional[List[Any]] = None,
|
|
99
|
+
allow_shift: bool = True,
|
|
100
|
+
*,
|
|
101
|
+
edge_attrs: Optional[List[str]] = None,
|
|
102
|
+
prune_wc: bool = False,
|
|
103
|
+
prune_automorphisms: bool = False,
|
|
104
|
+
wildcard_element: Any = "*",
|
|
105
|
+
element_key: str = "element",
|
|
106
|
+
) -> None:
|
|
107
|
+
if node_attrs is None:
|
|
108
|
+
node_attrs = ["element"]
|
|
109
|
+
if node_defaults is None:
|
|
110
|
+
node_defaults = ["*"] * len(node_attrs)
|
|
111
|
+
if len(node_defaults) != len(node_attrs):
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"MCSMatcher: node_defaults must have the same length as node_attrs."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self._node_attrs: List[str] = node_attrs
|
|
117
|
+
self._node_defaults: List[Any] = node_defaults
|
|
118
|
+
self._edge_attrs: List[str] = (
|
|
119
|
+
["order"] if edge_attrs is None else list(edge_attrs)
|
|
120
|
+
)
|
|
121
|
+
self.allow_shift: bool = allow_shift
|
|
122
|
+
|
|
123
|
+
self.prune_wc: bool = prune_wc
|
|
124
|
+
self.prune_automorphisms: bool = prune_automorphisms
|
|
125
|
+
self.wildcard_element: Any = wildcard_element
|
|
126
|
+
self.element_key: str = element_key
|
|
127
|
+
|
|
128
|
+
comparators: List[Callable[[Any, Any], bool]] = [
|
|
129
|
+
(lambda x, y: x == y) for _ in node_attrs
|
|
130
|
+
]
|
|
131
|
+
self.node_match: Callable[[Dict[str, Any], Dict[str, Any]], bool] = (
|
|
132
|
+
generic_node_match(
|
|
133
|
+
node_attrs,
|
|
134
|
+
node_defaults,
|
|
135
|
+
comparators,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Internal cache
|
|
140
|
+
self._mappings: List[MappingDict] = [] # pattern -> host
|
|
141
|
+
self._last_size: int = 0
|
|
142
|
+
self._last_pattern_is_G1: Optional[bool] = None # None until a search runs
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
# Internal edge / mapping helpers
|
|
146
|
+
# ------------------------------------------------------------------
|
|
147
|
+
def _edge_match(
|
|
148
|
+
self,
|
|
149
|
+
host_attrs: Dict[str, Any],
|
|
150
|
+
pat_attrs: Dict[str, Any],
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""Compare edge attributes listed in :attr:`_edge_attrs`.
|
|
153
|
+
|
|
154
|
+
For each name in :attr:`_edge_attrs`:
|
|
155
|
+
|
|
156
|
+
* If both values can be cast to ``float``, use numeric equality.
|
|
157
|
+
* Otherwise, fall back to direct ``==`` comparison.
|
|
158
|
+
* Missing values on both sides are ignored.
|
|
159
|
+
|
|
160
|
+
:param host_attrs: Edge attribute dictionary from host graph.
|
|
161
|
+
:type host_attrs: dict[str, Any]
|
|
162
|
+
:param pat_attrs: Edge attribute dictionary from pattern graph.
|
|
163
|
+
:type pat_attrs: dict[str, Any]
|
|
164
|
+
:return: ``True`` if all configured edge attributes match, otherwise
|
|
165
|
+
``False``.
|
|
166
|
+
:rtype: bool
|
|
167
|
+
"""
|
|
168
|
+
for name in self._edge_attrs:
|
|
169
|
+
hv = host_attrs.get(name, None)
|
|
170
|
+
pv = pat_attrs.get(name, None)
|
|
171
|
+
if hv is None and pv is None:
|
|
172
|
+
continue
|
|
173
|
+
try:
|
|
174
|
+
if float(hv) != float(pv):
|
|
175
|
+
return False
|
|
176
|
+
except (TypeError, ValueError):
|
|
177
|
+
if hv != pv:
|
|
178
|
+
return False
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _validate_graph_pair(G1: nx.Graph, G2: nx.Graph) -> None:
|
|
183
|
+
if G1.is_directed() != G2.is_directed():
|
|
184
|
+
raise ValueError("MCSMatcher requires graphs with the same directedness")
|
|
185
|
+
if G1.is_multigraph() or G2.is_multigraph():
|
|
186
|
+
raise NotImplementedError("MCSMatcher does not support multigraphs")
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def _component_nodes(graph: nx.Graph):
|
|
190
|
+
return (
|
|
191
|
+
nx.weakly_connected_components(graph)
|
|
192
|
+
if graph.is_directed()
|
|
193
|
+
else nx.connected_components(graph)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _matcher_class(graph: nx.Graph):
|
|
198
|
+
return (
|
|
199
|
+
nx.algorithms.isomorphism.DiGraphMatcher
|
|
200
|
+
if graph.is_directed()
|
|
201
|
+
else GraphMatcher
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _invert_mapping(gm_mapping: MappingDict) -> MappingDict:
|
|
206
|
+
"""Convert *host→pattern* dict to *pattern→host*.
|
|
207
|
+
|
|
208
|
+
:param gm_mapping: Mapping from host nodes to pattern nodes.
|
|
209
|
+
:type gm_mapping: dict[int, int]
|
|
210
|
+
:return: Mapping from pattern nodes to host nodes.
|
|
211
|
+
:rtype: dict[int, int]
|
|
212
|
+
"""
|
|
213
|
+
return {pat: host for host, pat in gm_mapping.items()}
|
|
214
|
+
|
|
215
|
+
def _prune_graph(self, G: nx.Graph) -> nx.Graph:
|
|
216
|
+
"""Return a version of ``G`` with wildcard nodes removed if needed.
|
|
217
|
+
|
|
218
|
+
This method does **not** mutate the input graph. When
|
|
219
|
+
:attr:`prune_wc` is ``False``, it simply returns ``G`` as-is.
|
|
220
|
+
|
|
221
|
+
:param G: Input graph.
|
|
222
|
+
:type G: nx.Graph
|
|
223
|
+
:return: Possibly pruned graph (wildcard nodes removed).
|
|
224
|
+
:rtype: nx.Graph
|
|
225
|
+
"""
|
|
226
|
+
if not self.prune_wc:
|
|
227
|
+
return G
|
|
228
|
+
|
|
229
|
+
key = self.element_key
|
|
230
|
+
wc = self.wildcard_element
|
|
231
|
+
keep_nodes = [n for n, data in G.nodes(data=True) if data.get(key) != wc]
|
|
232
|
+
return G.subgraph(keep_nodes).copy()
|
|
233
|
+
|
|
234
|
+
def _componentwise_mcs(
|
|
235
|
+
self,
|
|
236
|
+
G1: nx.Graph,
|
|
237
|
+
G2: nx.Graph,
|
|
238
|
+
*,
|
|
239
|
+
mcs: bool,
|
|
240
|
+
) -> MappingDict:
|
|
241
|
+
"""Perform size-sorted, component-wise MCS between ``G1`` and ``G2``.
|
|
242
|
+
|
|
243
|
+
The graphs are decomposed into connected components, which are
|
|
244
|
+
sorted by size (descending). Components are then matched in
|
|
245
|
+
order (largest with largest, etc.) using the internal subgraph-
|
|
246
|
+
search machinery.
|
|
247
|
+
|
|
248
|
+
The combined mapping is always reported as **G1 → G2** in terms
|
|
249
|
+
of the original node ids; components that fail to yield any
|
|
250
|
+
mapping are simply skipped.
|
|
251
|
+
|
|
252
|
+
:param G1: First input graph.
|
|
253
|
+
:type G1: nx.Graph
|
|
254
|
+
:param G2: Second input graph.
|
|
255
|
+
:type G2: nx.Graph
|
|
256
|
+
:param mcs: If ``True``, restrict each component pair to
|
|
257
|
+
maximum-common-subgraph mappings.
|
|
258
|
+
:type mcs: bool
|
|
259
|
+
:return: Combined mapping from nodes of ``G1`` to nodes of
|
|
260
|
+
``G2``.
|
|
261
|
+
:rtype: dict[int, int]
|
|
262
|
+
"""
|
|
263
|
+
comps1 = [G1.subgraph(c).copy() for c in self._component_nodes(G1)]
|
|
264
|
+
comps2 = [G2.subgraph(c).copy() for c in self._component_nodes(G2)]
|
|
265
|
+
|
|
266
|
+
comps1.sort(key=lambda g: g.number_of_nodes(), reverse=True)
|
|
267
|
+
comps2.sort(key=lambda g: g.number_of_nodes(), reverse=True)
|
|
268
|
+
|
|
269
|
+
limit = min(len(comps1), len(comps2))
|
|
270
|
+
combined: MappingDict = {}
|
|
271
|
+
|
|
272
|
+
for idx in range(limit):
|
|
273
|
+
sub1 = comps1[idx]
|
|
274
|
+
sub2 = comps2[idx]
|
|
275
|
+
|
|
276
|
+
pattern, host, pattern_is_G1 = self._prepare_orientation(sub1, sub2)
|
|
277
|
+
local_maps = self._search_subgraphs(pattern, host, mcs=mcs)
|
|
278
|
+
if not local_maps:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
best = local_maps[0]
|
|
282
|
+
if pattern_is_G1:
|
|
283
|
+
# pattern is subgraph of G1 → host is subgraph of G2
|
|
284
|
+
combined.update(best)
|
|
285
|
+
else:
|
|
286
|
+
# pattern is subgraph of G2, host is subgraph of G1 → invert
|
|
287
|
+
combined.update(self._invert_mapping(best))
|
|
288
|
+
|
|
289
|
+
return combined
|
|
290
|
+
|
|
291
|
+
def _bipartite_assign_components(
|
|
292
|
+
self,
|
|
293
|
+
G1: nx.Graph,
|
|
294
|
+
G2: nx.Graph,
|
|
295
|
+
*,
|
|
296
|
+
mcs: bool,
|
|
297
|
+
) -> MappingDict:
|
|
298
|
+
"""Hungarian-optimal component pairing followed by per-pair MCS.
|
|
299
|
+
|
|
300
|
+
Builds an n₁×n₂ score matrix (MCS size for each component pair),
|
|
301
|
+
solves the linear assignment problem to maximise total overlap, then
|
|
302
|
+
runs the actual MCS for the assigned pairs and combines the mappings
|
|
303
|
+
into a single **G1 → G2** dict.
|
|
304
|
+
|
|
305
|
+
Falls back to :py:meth:`_componentwise_mcs` when scipy is not
|
|
306
|
+
available.
|
|
307
|
+
|
|
308
|
+
:param G1: First input graph (after wildcard pruning).
|
|
309
|
+
:type G1: nx.Graph
|
|
310
|
+
:param G2: Second input graph (after wildcard pruning).
|
|
311
|
+
:type G2: nx.Graph
|
|
312
|
+
:param mcs: If ``True``, restrict to maximum-common-subgraph
|
|
313
|
+
mappings within each assigned pair.
|
|
314
|
+
:type mcs: bool
|
|
315
|
+
:return: Combined mapping from nodes of ``G1`` to nodes of ``G2``.
|
|
316
|
+
:rtype: dict[int, int]
|
|
317
|
+
"""
|
|
318
|
+
try:
|
|
319
|
+
import numpy as np
|
|
320
|
+
from scipy.optimize import linear_sum_assignment
|
|
321
|
+
except ImportError: # pragma: no cover
|
|
322
|
+
return self._componentwise_mcs(G1, G2, mcs=mcs)
|
|
323
|
+
|
|
324
|
+
comps1 = [G1.subgraph(c).copy() for c in self._component_nodes(G1)]
|
|
325
|
+
comps2 = [G2.subgraph(c).copy() for c in self._component_nodes(G2)]
|
|
326
|
+
n1, n2 = len(comps1), len(comps2)
|
|
327
|
+
|
|
328
|
+
# Build score matrix and cache maps in one pass to avoid re-running
|
|
329
|
+
score = np.zeros((n1, n2), dtype=float)
|
|
330
|
+
cache: Dict[Tuple[int, int], Tuple[List[MappingDict], bool]] = {}
|
|
331
|
+
for i, sub1 in enumerate(comps1):
|
|
332
|
+
for j, sub2 in enumerate(comps2):
|
|
333
|
+
pattern, host, pat_is_g1 = self._prepare_orientation(sub1, sub2)
|
|
334
|
+
maps = self._search_subgraphs(pattern, host, mcs=True)
|
|
335
|
+
score[i, j] = len(maps[0]) if maps else 0.0
|
|
336
|
+
cache[(i, j)] = (maps, pat_is_g1)
|
|
337
|
+
|
|
338
|
+
rows, cols = linear_sum_assignment(-score)
|
|
339
|
+
combined: MappingDict = {}
|
|
340
|
+
for i, j in zip(rows, cols):
|
|
341
|
+
if score[i, j] == 0.0:
|
|
342
|
+
continue
|
|
343
|
+
maps, pattern_is_g1 = cache[(i, j)]
|
|
344
|
+
if not maps:
|
|
345
|
+
continue
|
|
346
|
+
# Re-run with the caller's mcs flag if it differs from True
|
|
347
|
+
if not mcs:
|
|
348
|
+
sub1, sub2 = comps1[i], comps2[j]
|
|
349
|
+
pattern, host, pattern_is_g1 = self._prepare_orientation(sub1, sub2)
|
|
350
|
+
maps = self._search_subgraphs(pattern, host, mcs=False)
|
|
351
|
+
if not maps:
|
|
352
|
+
continue
|
|
353
|
+
best = maps[0]
|
|
354
|
+
if pattern_is_g1:
|
|
355
|
+
combined.update(best)
|
|
356
|
+
else:
|
|
357
|
+
combined.update(self._invert_mapping(best))
|
|
358
|
+
|
|
359
|
+
return combined
|
|
360
|
+
|
|
361
|
+
# ------------------------------------------------------------------
|
|
362
|
+
# Connected-component (molecule) level matching
|
|
363
|
+
# ------------------------------------------------------------------
|
|
364
|
+
def _find_mcs_mol(self, G1: nx.Graph, G2: nx.Graph) -> MappingDict:
|
|
365
|
+
"""Match connected components of ``G1`` to ``G2`` of the same size.
|
|
366
|
+
|
|
367
|
+
Components are sorted by size (descending) and matched greedily.
|
|
368
|
+
For each component in ``G1``, the method looks for a component in
|
|
369
|
+
``G2`` with the same size and an isomorphic mapping, combining
|
|
370
|
+
component-mappings into a single dictionary.
|
|
371
|
+
|
|
372
|
+
Direction: the returned mapping is **G1 → G2** in this mode.
|
|
373
|
+
|
|
374
|
+
:param G1: First graph (treated as source of components).
|
|
375
|
+
:type G1: nx.Graph
|
|
376
|
+
:param G2: Second graph (target for component mapping).
|
|
377
|
+
:type G2: nx.Graph
|
|
378
|
+
:return: Combined mapping from nodes of ``G1`` to nodes of ``G2``.
|
|
379
|
+
:rtype: dict[int, int]
|
|
380
|
+
"""
|
|
381
|
+
comps1 = sorted(self._component_nodes(G1), key=len, reverse=True)
|
|
382
|
+
comps2 = sorted(self._component_nodes(G2), key=len, reverse=True)
|
|
383
|
+
|
|
384
|
+
used2: Set[frozenset[int]] = set()
|
|
385
|
+
combined: MappingDict = {}
|
|
386
|
+
|
|
387
|
+
for comp1 in comps1:
|
|
388
|
+
size = len(comp1)
|
|
389
|
+
sub1 = G1.subgraph(comp1)
|
|
390
|
+
|
|
391
|
+
for comp2 in comps2:
|
|
392
|
+
if len(comp2) != size:
|
|
393
|
+
continue
|
|
394
|
+
key2 = frozenset(comp2)
|
|
395
|
+
if key2 in used2:
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
sub2 = G2.subgraph(comp2)
|
|
399
|
+
gm = self._matcher_class(sub1)(
|
|
400
|
+
sub1,
|
|
401
|
+
sub2,
|
|
402
|
+
node_match=self.node_match,
|
|
403
|
+
edge_match=self._edge_match,
|
|
404
|
+
)
|
|
405
|
+
if gm.is_isomorphic():
|
|
406
|
+
combined.update(gm.mapping)
|
|
407
|
+
used2.add(key2)
|
|
408
|
+
break
|
|
409
|
+
|
|
410
|
+
return combined
|
|
411
|
+
|
|
412
|
+
# ------------------------------------------------------------------
|
|
413
|
+
# Core subgraph search
|
|
414
|
+
# ------------------------------------------------------------------
|
|
415
|
+
def _prepare_orientation(
|
|
416
|
+
self,
|
|
417
|
+
G1: nx.Graph,
|
|
418
|
+
G2: nx.Graph,
|
|
419
|
+
) -> Tuple[nx.Graph, nx.Graph, bool]:
|
|
420
|
+
"""Ensure the smaller graph is used as pattern for efficiency.
|
|
421
|
+
|
|
422
|
+
:param G1: Original first graph.
|
|
423
|
+
:type G1: nx.Graph
|
|
424
|
+
:param G2: Original second graph.
|
|
425
|
+
:type G2: nx.Graph
|
|
426
|
+
:return: Tuple ``(pattern, host, pattern_is_G1)`` where
|
|
427
|
+
``pattern_is_G1=True`` indicates that the pattern is ``G1``.
|
|
428
|
+
:rtype: tuple[nx.Graph, nx.Graph, bool]
|
|
429
|
+
"""
|
|
430
|
+
if G1.number_of_nodes() <= G2.number_of_nodes():
|
|
431
|
+
return G1, G2, True
|
|
432
|
+
return G2, G1, False
|
|
433
|
+
|
|
434
|
+
def _search_subgraphs(
|
|
435
|
+
self,
|
|
436
|
+
pattern: nx.Graph,
|
|
437
|
+
host: nx.Graph,
|
|
438
|
+
*,
|
|
439
|
+
mcs: bool,
|
|
440
|
+
) -> List[MappingDict]:
|
|
441
|
+
"""Enumerate common subgraphs between ``pattern`` and ``host``.
|
|
442
|
+
|
|
443
|
+
The returned mappings always map **pattern nodes → host nodes**.
|
|
444
|
+
|
|
445
|
+
If :attr:`prune_automorphisms` is ``True``, mappings with the
|
|
446
|
+
same host-node set are collapsed.
|
|
447
|
+
|
|
448
|
+
:param pattern: Graph treated as pattern (smaller or equal).
|
|
449
|
+
:type pattern: nx.Graph
|
|
450
|
+
:param host: Graph treated as host (larger or equal).
|
|
451
|
+
:type host: nx.Graph
|
|
452
|
+
:param mcs: If ``True``, retain only maximum-size mappings.
|
|
453
|
+
:type mcs: bool
|
|
454
|
+
:return: List of mappings from pattern nodes to host nodes.
|
|
455
|
+
:rtype: list[MappingDict]
|
|
456
|
+
"""
|
|
457
|
+
max_k = min(pattern.number_of_nodes(), host.number_of_nodes())
|
|
458
|
+
seen: Set[Tuple[Tuple[int, int], ...]] = set()
|
|
459
|
+
mappings: List[MappingDict] = []
|
|
460
|
+
best_size = 0
|
|
461
|
+
|
|
462
|
+
# For automorphism pruning: track host node sets we have already seen
|
|
463
|
+
host_sets_seen: Set[frozenset[Any]] = set()
|
|
464
|
+
|
|
465
|
+
for k in range(max_k, 0, -1):
|
|
466
|
+
if mcs and best_size and k < best_size:
|
|
467
|
+
break
|
|
468
|
+
|
|
469
|
+
level_found = False
|
|
470
|
+
for nodes in itertools.combinations(pattern.nodes(), k):
|
|
471
|
+
sub_pat = pattern.subgraph(nodes).copy()
|
|
472
|
+
gm = self._matcher_class(host)(
|
|
473
|
+
host,
|
|
474
|
+
sub_pat,
|
|
475
|
+
node_match=self.node_match,
|
|
476
|
+
edge_match=self._edge_match,
|
|
477
|
+
)
|
|
478
|
+
for iso in gm.subgraph_isomorphisms_iter():
|
|
479
|
+
inv = self._invert_mapping(iso) # pattern -> host
|
|
480
|
+
key = tuple(sorted(inv.items(), key=repr))
|
|
481
|
+
if key in seen:
|
|
482
|
+
continue
|
|
483
|
+
seen.add(key)
|
|
484
|
+
|
|
485
|
+
if self.prune_automorphisms:
|
|
486
|
+
host_set = frozenset(inv.values())
|
|
487
|
+
if host_set in host_sets_seen:
|
|
488
|
+
# Same image in host as an earlier mapping -> skip
|
|
489
|
+
continue
|
|
490
|
+
host_sets_seen.add(host_set)
|
|
491
|
+
|
|
492
|
+
mappings.append(inv)
|
|
493
|
+
level_found = True
|
|
494
|
+
|
|
495
|
+
if level_found:
|
|
496
|
+
best_size = k
|
|
497
|
+
if mcs:
|
|
498
|
+
break
|
|
499
|
+
|
|
500
|
+
if mcs and best_size:
|
|
501
|
+
mappings = [m for m in mappings if len(m) == best_size]
|
|
502
|
+
|
|
503
|
+
mappings.sort(
|
|
504
|
+
key=lambda mapping: (
|
|
505
|
+
-len(mapping),
|
|
506
|
+
repr(tuple(sorted(mapping.items(), key=repr))),
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
self._last_size = (
|
|
510
|
+
best_size if best_size else (len(mappings[0]) if mappings else 0)
|
|
511
|
+
)
|
|
512
|
+
return mappings
|
|
513
|
+
|
|
514
|
+
# ------------------------------------------------------------------
|
|
515
|
+
# Public search methods
|
|
516
|
+
# ------------------------------------------------------------------
|
|
517
|
+
def find_common_subgraph(
|
|
518
|
+
self,
|
|
519
|
+
G1: nx.Graph,
|
|
520
|
+
G2: nx.Graph,
|
|
521
|
+
*,
|
|
522
|
+
mcs: bool = False,
|
|
523
|
+
mcs_mol: bool = False,
|
|
524
|
+
) -> "MCSMatcher":
|
|
525
|
+
"""Search for common subgraphs between two graphs.
|
|
526
|
+
|
|
527
|
+
The results are cached in :attr:`mappings` and
|
|
528
|
+
:attr:`last_size`. The method returns ``self`` to enable a
|
|
529
|
+
fluent style.
|
|
530
|
+
|
|
531
|
+
If :attr:`prune_wc` is ``True``, wildcard nodes are stripped
|
|
532
|
+
(non-inplace) from both graphs before the search.
|
|
533
|
+
|
|
534
|
+
:param G1: First input graph.
|
|
535
|
+
:type G1: nx.Graph
|
|
536
|
+
:param G2: Second input graph.
|
|
537
|
+
:type G2: nx.Graph
|
|
538
|
+
:param mcs: If ``True``, restrict to maximum-common-subgraph
|
|
539
|
+
mappings (largest possible node count).
|
|
540
|
+
:type mcs: bool
|
|
541
|
+
:param mcs_mol: If ``True``, perform connected-component
|
|
542
|
+
(molecule-level) matching using :py:meth:`_find_mcs_mol`.
|
|
543
|
+
In this mode, ``mcs`` is ignored.
|
|
544
|
+
:type mcs_mol: bool
|
|
545
|
+
:return: The matcher instance (with internal cache updated).
|
|
546
|
+
:rtype: MCSMatcher
|
|
547
|
+
"""
|
|
548
|
+
self._mappings = []
|
|
549
|
+
self._last_size = 0
|
|
550
|
+
self._last_pattern_is_G1 = None
|
|
551
|
+
|
|
552
|
+
self._validate_graph_pair(G1, G2)
|
|
553
|
+
|
|
554
|
+
G1_use = self._prune_graph(G1)
|
|
555
|
+
G2_use = self._prune_graph(G2)
|
|
556
|
+
|
|
557
|
+
if mcs_mol:
|
|
558
|
+
combined = self._find_mcs_mol(G1_use, G2_use)
|
|
559
|
+
self._mappings = [combined] # G1 -> G2
|
|
560
|
+
self._last_size = len(combined)
|
|
561
|
+
self._last_pattern_is_G1 = True
|
|
562
|
+
return self
|
|
563
|
+
|
|
564
|
+
pattern, host, pattern_is_G1 = self._prepare_orientation(G1_use, G2_use)
|
|
565
|
+
self._last_pattern_is_G1 = pattern_is_G1
|
|
566
|
+
self._mappings = self._search_subgraphs(pattern, host, mcs=mcs)
|
|
567
|
+
|
|
568
|
+
return self
|
|
569
|
+
|
|
570
|
+
def find_rc_mapping(
|
|
571
|
+
self,
|
|
572
|
+
rc1: Any,
|
|
573
|
+
rc2: Any,
|
|
574
|
+
*,
|
|
575
|
+
side: str = "op",
|
|
576
|
+
mcs: bool = True,
|
|
577
|
+
mcs_mol: bool = False,
|
|
578
|
+
component: bool = True,
|
|
579
|
+
) -> "MCSMatcher":
|
|
580
|
+
"""Convenience wrapper for ITS reaction-centre or ITS-like graph
|
|
581
|
+
objects.
|
|
582
|
+
|
|
583
|
+
Depending on ``side``, this either uses
|
|
584
|
+
:func:`synkit.Graph.ITS.its_decompose` to obtain left/right
|
|
585
|
+
graphs or treats the inputs directly as graphs.
|
|
586
|
+
|
|
587
|
+
Side selection
|
|
588
|
+
--------------
|
|
589
|
+
* ``'r'`` → compare right sides: ``r1`` vs ``r2``.
|
|
590
|
+
* ``'l'`` → compare left sides: ``l1`` vs ``l2``.
|
|
591
|
+
* ``'op'`` → compare opposite: ``r1`` vs ``l2``.
|
|
592
|
+
* ``'its'`` → treat ``rc1`` and ``rc2`` directly as graphs
|
|
593
|
+
(no decomposition), useful when the inputs are already ITS
|
|
594
|
+
(or ITS-like) :class:`networkx.Graph` objects.
|
|
595
|
+
|
|
596
|
+
Component-wise mode
|
|
597
|
+
-------------------
|
|
598
|
+
If ``component`` is ``True``, the selected graphs are
|
|
599
|
+
decomposed into connected components, sorted by size
|
|
600
|
+
(descending), and matched pairwise (largest with largest, etc.)
|
|
601
|
+
using a common-/maximum-common-subgraph search for each pair.
|
|
602
|
+
The resulting mappings are combined into a single **G1 → G2**
|
|
603
|
+
mapping in terms of the original node ids. In this mode,
|
|
604
|
+
``mcs_mol`` is ignored.
|
|
605
|
+
|
|
606
|
+
:param rc1: First reaction-centre or ITS-like graph object.
|
|
607
|
+
:type rc1: Any
|
|
608
|
+
:param rc2: Second reaction-centre or ITS-like graph object.
|
|
609
|
+
:type rc2: Any
|
|
610
|
+
:param side: Which ITS sides to compare (``'r'``, ``'l'``,
|
|
611
|
+
``'op'``, or ``'its'``).
|
|
612
|
+
:type side: str
|
|
613
|
+
:param mcs: If ``True``, restrict to maximum-common-subgraph
|
|
614
|
+
mappings (for the whole graph or per-component in
|
|
615
|
+
component-wise mode).
|
|
616
|
+
:type mcs: bool
|
|
617
|
+
:param mcs_mol: If ``True``, use connected-component matching
|
|
618
|
+
via :py:meth:`_find_mcs_mol`. Ignored if
|
|
619
|
+
``component`` is ``True``.
|
|
620
|
+
:type mcs_mol: bool
|
|
621
|
+
:param component: If ``True``, perform size-sorted,
|
|
622
|
+
component-wise MCS between the selected sides and combine
|
|
623
|
+
the per-component mappings into a single mapping.
|
|
624
|
+
:type component: bool
|
|
625
|
+
:return: The matcher instance (with internal cache updated).
|
|
626
|
+
:rtype: MCSMatcher
|
|
627
|
+
:raises ImportError: If :mod:`synkit` ITS utilities are not
|
|
628
|
+
available for ``side`` in ``{'r', 'l', 'op'}``.
|
|
629
|
+
:raises ValueError: If ``side`` is not one of
|
|
630
|
+
``'r'``, ``'l'``, ``'op'``, ``'its'``.
|
|
631
|
+
"""
|
|
632
|
+
# reset cache
|
|
633
|
+
self._mappings = []
|
|
634
|
+
self._last_size = 0
|
|
635
|
+
self._last_pattern_is_G1 = None
|
|
636
|
+
|
|
637
|
+
side_norm = side.lower()
|
|
638
|
+
|
|
639
|
+
if side_norm == "its":
|
|
640
|
+
# Treat rc1 and rc2 directly as graphs
|
|
641
|
+
G1, G2 = rc1, rc2
|
|
642
|
+
else:
|
|
643
|
+
if its_decompose is None:
|
|
644
|
+
raise ImportError(
|
|
645
|
+
"synkit is not available; cannot decompose reaction centres "
|
|
646
|
+
"for side values 'r', 'l' or 'op'."
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
l1, r1 = its_decompose(rc1)
|
|
650
|
+
l2, r2 = its_decompose(rc2)
|
|
651
|
+
|
|
652
|
+
if side_norm == "r":
|
|
653
|
+
G1, G2 = r1, r2
|
|
654
|
+
elif side_norm == "l":
|
|
655
|
+
G1, G2 = l1, l2
|
|
656
|
+
elif side_norm == "op":
|
|
657
|
+
G1, G2 = r1, l2
|
|
658
|
+
else:
|
|
659
|
+
raise ValueError(
|
|
660
|
+
"MCSMatcher.find_rc_mapping: side must be one of "
|
|
661
|
+
"'r', 'l', 'op', 'its', got "
|
|
662
|
+
f"{side!r}."
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if component:
|
|
666
|
+
self._validate_graph_pair(G1, G2)
|
|
667
|
+
G1_use = self._prune_graph(G1)
|
|
668
|
+
G2_use = self._prune_graph(G2)
|
|
669
|
+
combined = self._bipartite_assign_components(G1_use, G2_use, mcs=mcs)
|
|
670
|
+
self._mappings = [combined] # G1 -> G2
|
|
671
|
+
self._last_size = len(combined)
|
|
672
|
+
self._last_pattern_is_G1 = True
|
|
673
|
+
return self
|
|
674
|
+
|
|
675
|
+
return self.find_common_subgraph(G1, G2, mcs=mcs, mcs_mol=mcs_mol)
|
|
676
|
+
|
|
677
|
+
# ------------------------------------------------------------------
|
|
678
|
+
# Accessors / properties
|
|
679
|
+
# ------------------------------------------------------------------
|
|
680
|
+
def get_mappings(self, direction: str = "pattern_to_host") -> List[MappingDict]:
|
|
681
|
+
"""Return a copy of the cached mapping list in the requested orientation.
|
|
682
|
+
|
|
683
|
+
Internal cache is **pattern → host** (where pattern is the smaller
|
|
684
|
+
graph after pruning). This method can convert to original
|
|
685
|
+
``G1→G2`` or ``G2→G1`` orientation based on the last call to
|
|
686
|
+
:py:meth:`find_common_subgraph`.
|
|
687
|
+
|
|
688
|
+
:param direction: Orientation of the returned mappings. One of:
|
|
689
|
+
- ``"pattern_to_host"`` (default): internal orientation.
|
|
690
|
+
- ``"G1_to_G2"``: mapping from first input graph to second.
|
|
691
|
+
- ``"G2_to_G1"``: mapping from second input graph to first.
|
|
692
|
+
:type direction: str
|
|
693
|
+
:return: List of node-mapping dictionaries.
|
|
694
|
+
:rtype: list[dict[int, int]]
|
|
695
|
+
:raises ValueError: If ``direction`` is not supported.
|
|
696
|
+
"""
|
|
697
|
+
if direction == "pattern_to_host" or self._last_pattern_is_G1 is None:
|
|
698
|
+
return [dict(m) for m in self._mappings]
|
|
699
|
+
|
|
700
|
+
if direction not in {"G1_to_G2", "G2_to_G1"}:
|
|
701
|
+
raise ValueError(
|
|
702
|
+
"MCSMatcher.get_mappings: direction must be one of "
|
|
703
|
+
"'pattern_to_host', 'G1_to_G2', 'G2_to_G1'."
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
pattern_is_G1 = self._last_pattern_is_G1
|
|
707
|
+
result: List[MappingDict] = []
|
|
708
|
+
|
|
709
|
+
for m in self._mappings:
|
|
710
|
+
if direction == "G1_to_G2":
|
|
711
|
+
if pattern_is_G1:
|
|
712
|
+
result.append(dict(m))
|
|
713
|
+
else:
|
|
714
|
+
result.append(self._invert_mapping(m))
|
|
715
|
+
else: # direction == "G2_to_G1"
|
|
716
|
+
if pattern_is_G1:
|
|
717
|
+
result.append(self._invert_mapping(m))
|
|
718
|
+
else:
|
|
719
|
+
result.append(dict(m))
|
|
720
|
+
|
|
721
|
+
return result
|
|
722
|
+
|
|
723
|
+
@property
|
|
724
|
+
def mappings(self) -> List[MappingDict]:
|
|
725
|
+
"""Cached node mappings from the most recent search (pattern→host).
|
|
726
|
+
|
|
727
|
+
To obtain G1→G2 or G2→G1 orientation, use
|
|
728
|
+
:py:meth:`get_mappings` with ``direction='G1_to_G2'`` or
|
|
729
|
+
``direction='G2_to_G1'``.
|
|
730
|
+
|
|
731
|
+
:return: List of node-mapping dictionaries (pattern→host).
|
|
732
|
+
:rtype: list[dict[int, int]]
|
|
733
|
+
"""
|
|
734
|
+
return self.get_mappings(direction="pattern_to_host")
|
|
735
|
+
|
|
736
|
+
@property
|
|
737
|
+
def last_size(self) -> int:
|
|
738
|
+
"""Number of nodes in the most recent maximum mapping set.
|
|
739
|
+
|
|
740
|
+
This is the size of the largest mapping found in the last call
|
|
741
|
+
to :py:meth:`find_common_subgraph` (or zero if no mappings
|
|
742
|
+
exist).
|
|
743
|
+
|
|
744
|
+
:return: Size of the largest mapping.
|
|
745
|
+
:rtype: int
|
|
746
|
+
"""
|
|
747
|
+
return self._last_size
|
|
748
|
+
|
|
749
|
+
@property
|
|
750
|
+
def num_mappings(self) -> int:
|
|
751
|
+
"""Number of mappings stored from the most recent search.
|
|
752
|
+
|
|
753
|
+
:return: Count of cached mappings.
|
|
754
|
+
:rtype: int
|
|
755
|
+
"""
|
|
756
|
+
return len(self._mappings)
|
|
757
|
+
|
|
758
|
+
@property
|
|
759
|
+
def mapping_direction(self) -> str:
|
|
760
|
+
"""Human-readable description of internal mapping orientation.
|
|
761
|
+
|
|
762
|
+
:return: ``"G1_to_G2"``, ``"G2_to_G1"``, or ``"unknown"`` if
|
|
763
|
+
no search has been run yet.
|
|
764
|
+
:rtype: str
|
|
765
|
+
"""
|
|
766
|
+
if self._last_pattern_is_G1 is None:
|
|
767
|
+
return "unknown"
|
|
768
|
+
return "G1_to_G2" if self._last_pattern_is_G1 else "G2_to_G1"
|
|
769
|
+
|
|
770
|
+
# ------------------------------------------------------------------
|
|
771
|
+
# Iteration & niceties
|
|
772
|
+
# ------------------------------------------------------------------
|
|
773
|
+
def __iter__(self) -> Iterable[MappingDict]:
|
|
774
|
+
"""Iterate over cached mappings in pattern→host orientation.
|
|
775
|
+
|
|
776
|
+
:return: Iterator over mapping dictionaries.
|
|
777
|
+
:rtype: Iterable[dict[int, int]]
|
|
778
|
+
"""
|
|
779
|
+
return iter(self._mappings)
|
|
780
|
+
|
|
781
|
+
def __repr__(self) -> str:
|
|
782
|
+
"""Short textual representation for debugging.
|
|
783
|
+
|
|
784
|
+
:return: Summary string with key attributes.
|
|
785
|
+
:rtype: str
|
|
786
|
+
"""
|
|
787
|
+
return (
|
|
788
|
+
f"<MCSMatcher mappings={self.num_mappings} "
|
|
789
|
+
f"last_size={self.last_size} "
|
|
790
|
+
f"prune_wc={self.prune_wc} "
|
|
791
|
+
f"prune_automorphisms={self.prune_automorphisms} "
|
|
792
|
+
f"direction={self.mapping_direction}>"
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
__str__ = __repr__
|
|
796
|
+
|
|
797
|
+
@property
|
|
798
|
+
def help(self) -> str:
|
|
799
|
+
"""Return the module-level documentation string.
|
|
800
|
+
|
|
801
|
+
:return: The full module docstring, if available.
|
|
802
|
+
:rtype: str
|
|
803
|
+
"""
|
|
804
|
+
return __doc__ or ""
|