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,736 @@
|
|
|
1
|
+
"""Approximate automorphism orbits with 1-WL color refinement.
|
|
2
|
+
|
|
3
|
+
Distinct WL colors imply distinct orbits, while equal colors do not prove
|
|
4
|
+
symmetry. The module also provides component-aware mapping deduplication;
|
|
5
|
+
anchor components remain exempt from orbit-independence pruning.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import (
|
|
12
|
+
Any,
|
|
13
|
+
Dict,
|
|
14
|
+
FrozenSet,
|
|
15
|
+
Hashable,
|
|
16
|
+
Iterable,
|
|
17
|
+
List,
|
|
18
|
+
Mapping,
|
|
19
|
+
Optional,
|
|
20
|
+
Tuple,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
import networkx as nx
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# --------------------------------------------------------------------------- #
|
|
27
|
+
# Small, typed config container (keeps complexity low)
|
|
28
|
+
# --------------------------------------------------------------------------- #
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class _WLConfig:
|
|
31
|
+
"""
|
|
32
|
+
Internal WL configuration.
|
|
33
|
+
|
|
34
|
+
:param node_attrs: Node attribute keys for initial labels.
|
|
35
|
+
:type node_attrs: tuple[str, ...]
|
|
36
|
+
:param edge_attrs: Edge attribute keys for neighbor signatures.
|
|
37
|
+
:type edge_attrs: tuple[str, ...]
|
|
38
|
+
:param max_iter: Maximum refinement iterations.
|
|
39
|
+
:type max_iter: int
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
node_attrs: Tuple[str, ...]
|
|
43
|
+
edge_attrs: Tuple[str, ...]
|
|
44
|
+
max_iter: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# --------------------------------------------------------------------------- #
|
|
48
|
+
# Core estimator
|
|
49
|
+
# --------------------------------------------------------------------------- #
|
|
50
|
+
class AutoEst:
|
|
51
|
+
"""Approximate node automorphism groups (orbits) via 1-WL color refinement.
|
|
52
|
+
|
|
53
|
+
This class performs a Weisfeiler–Lehman (WL-1) style color refinement
|
|
54
|
+
on the input graph to approximate a partition of nodes into
|
|
55
|
+
*automorphism-indistinguishability* classes (often called “WL-orbits”).
|
|
56
|
+
In many practical graphs (especially with chemically meaningful
|
|
57
|
+
node/edge labels), the WL partition coincides with, or closely
|
|
58
|
+
approximates, the true orbit partition and is much cheaper than
|
|
59
|
+
enumerating automorphisms.
|
|
60
|
+
|
|
61
|
+
Besides the basic orbit partition, :class:`AutoEst` provides a
|
|
62
|
+
“components-style” interface analogous to the exact automorphism helper:
|
|
63
|
+
|
|
64
|
+
- :meth:`components` returns connected components of an induced subgraph
|
|
65
|
+
(useful for “anchor components”).
|
|
66
|
+
- :meth:`orbit_components` returns connected components in the
|
|
67
|
+
**orbit-quotient graph** restricted to a node subset, capturing which
|
|
68
|
+
orbits are *coupled* by edges inside the subset.
|
|
69
|
+
- :meth:`deduplicate_host_orbits` prunes mappings by **host-side** WL-orbits.
|
|
70
|
+
- :meth:`deduplicate_pattern_orbits` prunes mappings by **pattern** orbits,
|
|
71
|
+
with optional anchor-aware behavior (no pruning inside anchored nodes).
|
|
72
|
+
|
|
73
|
+
:param graph: Input NetworkX graph. It is not modified in-place.
|
|
74
|
+
:type graph: nx.Graph
|
|
75
|
+
:param node_attrs: Node attribute keys whose values will be included in
|
|
76
|
+
the initial coloring. If ``None``, defaults are used.
|
|
77
|
+
:type node_attrs: list[str] or None
|
|
78
|
+
:param edge_attrs: Edge attribute keys whose values will be incorporated
|
|
79
|
+
into the neighborhood signatures. If ``None``, defaults are used.
|
|
80
|
+
:type edge_attrs: list[str] or None
|
|
81
|
+
:param max_iter: Maximum number of WL refinement iterations.
|
|
82
|
+
:type max_iter: int
|
|
83
|
+
|
|
84
|
+
.. note::
|
|
85
|
+
This is an **approximate** estimator of automorphism orbits:
|
|
86
|
+
two nodes with different final WL colors cannot be in the same orbit,
|
|
87
|
+
but nodes with the same WL color might still be distinguishable by
|
|
88
|
+
higher-order invariants (e.g. higher-dimensional WL, spectral
|
|
89
|
+
invariants, or full automorphism search). In many molecular graphs
|
|
90
|
+
where node/edge labels are informative, this partition is typically
|
|
91
|
+
very close to the true automorphism partition and is often sufficient
|
|
92
|
+
for symmetry-aware pruning.
|
|
93
|
+
|
|
94
|
+
.. note::
|
|
95
|
+
“Anchor components” can invalidate orbit-wise independence:
|
|
96
|
+
when a subset of nodes is treated as anchored, orbits that are
|
|
97
|
+
connected through the anchored subgraph should be considered *coupled*.
|
|
98
|
+
Use :meth:`orbit_components` and anchor-aware
|
|
99
|
+
:meth:`deduplicate_pattern_orbits` to avoid incorrect pruning.
|
|
100
|
+
|
|
101
|
+
.. seealso::
|
|
102
|
+
For discussions relating Weisfeiler–Lehman refinement to
|
|
103
|
+
automorphism indistinguishability and orbit structure, see:
|
|
104
|
+
|
|
105
|
+
* A. Dawar and G. Vagnozzi, *Generalizations of k-dimensional
|
|
106
|
+
Weisfeiler–Leman stabilization*, arXiv preprint (2019/2020).
|
|
107
|
+
|
|
108
|
+
.. rubric:: Example
|
|
109
|
+
|
|
110
|
+
.. code-block:: python
|
|
111
|
+
|
|
112
|
+
import networkx as nx
|
|
113
|
+
from synkit.Graph.automorphism import AutoEst
|
|
114
|
+
|
|
115
|
+
# Simple 4-cycle where all nodes are symmetric under rotation/reflection
|
|
116
|
+
G = nx.cycle_graph(4)
|
|
117
|
+
|
|
118
|
+
est = AutoEst(G, node_attrs=[], edge_attrs=[])
|
|
119
|
+
est = est.fit()
|
|
120
|
+
|
|
121
|
+
print(est.orbits)
|
|
122
|
+
# [frozenset({0, 1, 2, 3})]
|
|
123
|
+
|
|
124
|
+
print(est.n_orbits)
|
|
125
|
+
# 1
|
|
126
|
+
|
|
127
|
+
# "components style": connected components of an induced subgraph
|
|
128
|
+
comps = est.components(nodes=[0, 1])
|
|
129
|
+
print(comps)
|
|
130
|
+
# [frozenset({0, 1})]
|
|
131
|
+
|
|
132
|
+
# orbit-quotient components of an induced subgraph
|
|
133
|
+
oc = est.orbit_components(nodes=[0, 1, 2, 3])
|
|
134
|
+
print(oc)
|
|
135
|
+
# [frozenset({0})] # one orbit-id component in this symmetric case
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
_DEF_NODE_ATTRS: Tuple[str, ...] = ("element", "charge")
|
|
139
|
+
_DEF_EDGE_ATTRS: Tuple[str, ...] = ("order",)
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
graph: nx.Graph,
|
|
144
|
+
node_attrs: Optional[List[str]] = None,
|
|
145
|
+
edge_attrs: Optional[List[str]] = None,
|
|
146
|
+
max_iter: int = 10,
|
|
147
|
+
) -> None:
|
|
148
|
+
self._graph: nx.Graph = graph
|
|
149
|
+
|
|
150
|
+
cfg = _WLConfig(
|
|
151
|
+
node_attrs=(
|
|
152
|
+
tuple(node_attrs) if node_attrs is not None else self._DEF_NODE_ATTRS
|
|
153
|
+
),
|
|
154
|
+
edge_attrs=(
|
|
155
|
+
tuple(edge_attrs) if edge_attrs is not None else self._DEF_EDGE_ATTRS
|
|
156
|
+
),
|
|
157
|
+
max_iter=int(max_iter),
|
|
158
|
+
)
|
|
159
|
+
self._cfg: _WLConfig = cfg
|
|
160
|
+
|
|
161
|
+
self._colors: Dict[Hashable, int] = {}
|
|
162
|
+
self._orbits: List[FrozenSet[Hashable]] = []
|
|
163
|
+
self._orbit_index: Optional[Dict[Hashable, int]] = None
|
|
164
|
+
self._fitted: bool = False
|
|
165
|
+
|
|
166
|
+
# ------------------------------------------------------------------ #
|
|
167
|
+
# Public API: properties
|
|
168
|
+
# ------------------------------------------------------------------ #
|
|
169
|
+
@property
|
|
170
|
+
def graph(self) -> nx.Graph:
|
|
171
|
+
"""Underlying graph.
|
|
172
|
+
|
|
173
|
+
:return: Graph passed to the constructor.
|
|
174
|
+
:rtype: nx.Graph
|
|
175
|
+
"""
|
|
176
|
+
return self._graph
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def node_attrs(self) -> Tuple[str, ...]:
|
|
180
|
+
"""Node attribute keys used in WL initialization.
|
|
181
|
+
|
|
182
|
+
:return: Node-attribute keys.
|
|
183
|
+
:rtype: tuple[str, ...]
|
|
184
|
+
"""
|
|
185
|
+
return self._cfg.node_attrs
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def edge_attrs(self) -> Tuple[str, ...]:
|
|
189
|
+
"""Edge attribute keys used in WL refinement.
|
|
190
|
+
|
|
191
|
+
:return: Edge-attribute keys.
|
|
192
|
+
:rtype: tuple[str, ...]
|
|
193
|
+
"""
|
|
194
|
+
return self._cfg.edge_attrs
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def max_iter(self) -> int:
|
|
198
|
+
"""Maximum number of WL refinement iterations.
|
|
199
|
+
|
|
200
|
+
:return: Maximum refinement iterations.
|
|
201
|
+
:rtype: int
|
|
202
|
+
"""
|
|
203
|
+
return self._cfg.max_iter
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def anchor_component(self) -> FrozenSet[Hashable]:
|
|
207
|
+
"""Largest connected component of the fitted graph.
|
|
208
|
+
|
|
209
|
+
This is a convenience “components-style” accessor. It is commonly used as
|
|
210
|
+
an anchor set for match pruning and symmetry breaking.
|
|
211
|
+
|
|
212
|
+
:return: The node-set of the largest connected component. If multiple
|
|
213
|
+
components share the maximum size, the one with the smallest
|
|
214
|
+
(sorted) node is returned for determinism.
|
|
215
|
+
:rtype: frozenset[hashable]
|
|
216
|
+
|
|
217
|
+
:raises RuntimeError: If :meth:`fit` has not been called.
|
|
218
|
+
"""
|
|
219
|
+
self._ensure_fitted()
|
|
220
|
+
comps = self._components_on_induced(nodes=None)
|
|
221
|
+
if not comps:
|
|
222
|
+
return frozenset()
|
|
223
|
+
|
|
224
|
+
# Deterministic tie-break: size desc, then smallest node
|
|
225
|
+
comps_sorted = sorted(
|
|
226
|
+
comps,
|
|
227
|
+
key=lambda c: (-len(c), min((repr(node) for node in c), default="")),
|
|
228
|
+
)
|
|
229
|
+
return comps_sorted[0]
|
|
230
|
+
|
|
231
|
+
# ------------------------------------------------------------------ #
|
|
232
|
+
# Fitting and results
|
|
233
|
+
# ------------------------------------------------------------------ #
|
|
234
|
+
def fit(self) -> AutoEst:
|
|
235
|
+
"""Run WL-1 refinement and compute approximate orbits.
|
|
236
|
+
|
|
237
|
+
:return: The fitted estimator (``self``).
|
|
238
|
+
:rtype: AutoEst
|
|
239
|
+
"""
|
|
240
|
+
self._initialize_colors()
|
|
241
|
+
self._refine_colors()
|
|
242
|
+
self._build_orbits()
|
|
243
|
+
self._orbit_index = None
|
|
244
|
+
self._fitted = True
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def node_colors(self) -> Dict[Hashable, int]:
|
|
249
|
+
"""Node-to-color mapping after refinement.
|
|
250
|
+
|
|
251
|
+
:return: Mapping node -> WL color id.
|
|
252
|
+
:rtype: dict[hashable, int]
|
|
253
|
+
"""
|
|
254
|
+
self._ensure_fitted()
|
|
255
|
+
return dict(self._colors)
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def orbits(self) -> List[FrozenSet[Hashable]]:
|
|
259
|
+
"""WL-equivalence classes (approximate automorphism orbits).
|
|
260
|
+
|
|
261
|
+
:return: List of frozensets, each representing an orbit.
|
|
262
|
+
:rtype: list[frozenset[hashable]]
|
|
263
|
+
"""
|
|
264
|
+
self._ensure_fitted()
|
|
265
|
+
return list(self._orbits)
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def groups(self) -> List[List[Hashable]]:
|
|
269
|
+
"""Orbits represented as sorted lists.
|
|
270
|
+
|
|
271
|
+
:return: List of sorted node lists.
|
|
272
|
+
:rtype: list[list[hashable]]
|
|
273
|
+
"""
|
|
274
|
+
self._ensure_fitted()
|
|
275
|
+
out: List[List[Hashable]] = []
|
|
276
|
+
for orb in self._orbits:
|
|
277
|
+
out.append(sorted(orb, key=repr))
|
|
278
|
+
out.sort(key=lambda g: (len(g), repr(g[0]) if g else ""))
|
|
279
|
+
return out
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def orbit_index(self) -> Dict[Hashable, int]:
|
|
283
|
+
"""Map each node to its orbit id.
|
|
284
|
+
|
|
285
|
+
:return: Mapping node -> orbit_id.
|
|
286
|
+
:rtype: dict[hashable, int]
|
|
287
|
+
"""
|
|
288
|
+
self._ensure_fitted()
|
|
289
|
+
if self._orbit_index is None:
|
|
290
|
+
self._orbit_index = self._build_orbit_index()
|
|
291
|
+
return dict(self._orbit_index)
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def n_orbits(self) -> int:
|
|
295
|
+
"""Number of approximate orbits.
|
|
296
|
+
|
|
297
|
+
:return: Number of orbits.
|
|
298
|
+
:rtype: int
|
|
299
|
+
"""
|
|
300
|
+
self._ensure_fitted()
|
|
301
|
+
return len(self._orbits)
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def n_groups(self) -> int:
|
|
305
|
+
"""Alias for :attr:`n_orbits`.
|
|
306
|
+
|
|
307
|
+
:return: Number of orbits.
|
|
308
|
+
:rtype: int
|
|
309
|
+
"""
|
|
310
|
+
return self.n_orbits
|
|
311
|
+
|
|
312
|
+
def __len__(self) -> int:
|
|
313
|
+
"""Number of orbits (0 if not fitted).
|
|
314
|
+
|
|
315
|
+
:return: Orbit count or 0.
|
|
316
|
+
:rtype: int
|
|
317
|
+
"""
|
|
318
|
+
return self.n_orbits if self._fitted else 0
|
|
319
|
+
|
|
320
|
+
def __repr__(self) -> str:
|
|
321
|
+
"""Summary representation.
|
|
322
|
+
|
|
323
|
+
:return: Debug-friendly repr string.
|
|
324
|
+
:rtype: str
|
|
325
|
+
"""
|
|
326
|
+
n_nodes = self._graph.number_of_nodes()
|
|
327
|
+
n_orb: object = len(self) if self._fitted else "?"
|
|
328
|
+
return (
|
|
329
|
+
f"<AutoEst | nodes={n_nodes} "
|
|
330
|
+
f"orbits={n_orb} approx='WL-1' max_iter={self._cfg.max_iter}>"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# ------------------------------------------------------------------ #
|
|
334
|
+
# "Components style" (similar spirit to Automorphism)
|
|
335
|
+
# ------------------------------------------------------------------ #
|
|
336
|
+
def components(
|
|
337
|
+
self, nodes: Optional[Iterable[Hashable]] = None
|
|
338
|
+
) -> List[FrozenSet[Hashable]]:
|
|
339
|
+
"""Compute connected components on an induced subgraph.
|
|
340
|
+
|
|
341
|
+
This mirrors the "components" utilities you used around Automorphism.
|
|
342
|
+
|
|
343
|
+
:param nodes: Subset of nodes to induce. If None, uses all nodes.
|
|
344
|
+
:type nodes: iterable[hashable] or None
|
|
345
|
+
|
|
346
|
+
:return: Connected components as frozensets (deterministic order).
|
|
347
|
+
:rtype: list[frozenset[hashable]]
|
|
348
|
+
|
|
349
|
+
:raises RuntimeError: If not fitted.
|
|
350
|
+
"""
|
|
351
|
+
self._ensure_fitted()
|
|
352
|
+
return self._components_on_induced(nodes)
|
|
353
|
+
|
|
354
|
+
def orbit_components(
|
|
355
|
+
self, nodes: Optional[Iterable[Hashable]] = None
|
|
356
|
+
) -> List[FrozenSet[int]]:
|
|
357
|
+
"""Components of the orbit-quotient graph restricted to an induced subgraph.
|
|
358
|
+
|
|
359
|
+
- First restrict to `nodes` (or all nodes).
|
|
360
|
+
- Collapse nodes to their orbit ids.
|
|
361
|
+
- Build orbit-quotient adjacency based on edges between orbits.
|
|
362
|
+
- Return connected components of orbit ids.
|
|
363
|
+
|
|
364
|
+
This is useful when you have an "anchor component" defined as a set of
|
|
365
|
+
pattern nodes and want to treat coupled orbits as a single unit.
|
|
366
|
+
|
|
367
|
+
:param nodes: Subset of nodes. If None, uses all nodes.
|
|
368
|
+
:type nodes: iterable[hashable] or None
|
|
369
|
+
|
|
370
|
+
:return: List of connected components in orbit-id space.
|
|
371
|
+
:rtype: list[frozenset[int]]
|
|
372
|
+
|
|
373
|
+
:raises RuntimeError: If not fitted.
|
|
374
|
+
"""
|
|
375
|
+
self._ensure_fitted()
|
|
376
|
+
keep = self._normalize_nodes(nodes)
|
|
377
|
+
orbit_idx = self.orbit_index
|
|
378
|
+
q = self._orbit_quotient_graph(keep, orbit_idx)
|
|
379
|
+
comps = [frozenset(c) for c in nx.connected_components(q)]
|
|
380
|
+
return sorted(comps, key=lambda c: (len(c), min(map(repr, c))))
|
|
381
|
+
|
|
382
|
+
# ------------------------------------------------------------------ #
|
|
383
|
+
# Internal helpers
|
|
384
|
+
# ------------------------------------------------------------------ #
|
|
385
|
+
def _ensure_fitted(self) -> None:
|
|
386
|
+
"""
|
|
387
|
+
Ensure estimator is fitted.
|
|
388
|
+
|
|
389
|
+
:raises RuntimeError: If not fitted.
|
|
390
|
+
"""
|
|
391
|
+
if not self._fitted:
|
|
392
|
+
raise RuntimeError("Call 'fit()' before accessing results.")
|
|
393
|
+
|
|
394
|
+
def _initialize_colors(self) -> None:
|
|
395
|
+
"""
|
|
396
|
+
Initialize node colors using degree and selected node attributes.
|
|
397
|
+
"""
|
|
398
|
+
palette: Dict[Tuple[Any, ...], int] = {}
|
|
399
|
+
colors: Dict[Hashable, int] = {}
|
|
400
|
+
next_color = 0
|
|
401
|
+
|
|
402
|
+
for node in self._graph.nodes():
|
|
403
|
+
label = self._initial_label(node)
|
|
404
|
+
if label not in palette:
|
|
405
|
+
palette[label] = next_color
|
|
406
|
+
next_color += 1
|
|
407
|
+
colors[node] = palette[label]
|
|
408
|
+
|
|
409
|
+
self._colors = colors
|
|
410
|
+
|
|
411
|
+
def _initial_label(self, node: Hashable) -> Tuple[Any, ...]:
|
|
412
|
+
"""Build initial label.
|
|
413
|
+
|
|
414
|
+
:param node: Node id.
|
|
415
|
+
:type node: hashable
|
|
416
|
+
|
|
417
|
+
:return: Tuple label (degree + attr values).
|
|
418
|
+
:rtype: tuple
|
|
419
|
+
"""
|
|
420
|
+
degree = self._graph.degree(node)
|
|
421
|
+
attrs = self._graph.nodes[node]
|
|
422
|
+
vals = [attrs.get(k) for k in self._cfg.node_attrs]
|
|
423
|
+
return (degree, *vals)
|
|
424
|
+
|
|
425
|
+
def _refine_colors(self) -> None:
|
|
426
|
+
"""
|
|
427
|
+
Iterate WL refinement until convergence or max_iter.
|
|
428
|
+
"""
|
|
429
|
+
for _ in range(self._cfg.max_iter):
|
|
430
|
+
new_colors, changed = self._refine_once()
|
|
431
|
+
self._colors = new_colors
|
|
432
|
+
if not changed:
|
|
433
|
+
break
|
|
434
|
+
|
|
435
|
+
def _refine_once(self) -> Tuple[Dict[Hashable, int], bool]:
|
|
436
|
+
"""Single WL sweep.
|
|
437
|
+
|
|
438
|
+
:return: (new_colors, changed)
|
|
439
|
+
:rtype: tuple[dict[hashable, int], bool]
|
|
440
|
+
"""
|
|
441
|
+
palette: Dict[Tuple[Any, ...], int] = {}
|
|
442
|
+
new_colors: Dict[Hashable, int] = {}
|
|
443
|
+
next_color = 0
|
|
444
|
+
changed = False
|
|
445
|
+
|
|
446
|
+
for node in self._graph.nodes():
|
|
447
|
+
label = self._refined_label(node)
|
|
448
|
+
if label not in palette:
|
|
449
|
+
palette[label] = next_color
|
|
450
|
+
next_color += 1
|
|
451
|
+
c = palette[label]
|
|
452
|
+
new_colors[node] = c
|
|
453
|
+
if c != self._colors.get(node):
|
|
454
|
+
changed = True
|
|
455
|
+
|
|
456
|
+
return new_colors, changed
|
|
457
|
+
|
|
458
|
+
def _refined_label(self, node: Hashable) -> Tuple[Any, ...]:
|
|
459
|
+
"""Combine current color with sorted neighbor signatures.
|
|
460
|
+
|
|
461
|
+
:param node: Node id.
|
|
462
|
+
:type node: hashable
|
|
463
|
+
|
|
464
|
+
:return: Refined label.
|
|
465
|
+
:rtype: tuple
|
|
466
|
+
"""
|
|
467
|
+
base = self._colors[node]
|
|
468
|
+
sigs: List[Tuple[Any, ...]] = []
|
|
469
|
+
|
|
470
|
+
for nbr in self._graph.neighbors(node):
|
|
471
|
+
sigs.append(self._neighbor_signature(node, nbr))
|
|
472
|
+
|
|
473
|
+
sigs.sort()
|
|
474
|
+
return (base, tuple(sigs))
|
|
475
|
+
|
|
476
|
+
def _neighbor_signature(
|
|
477
|
+
self, node: Hashable, neighbor: Hashable
|
|
478
|
+
) -> Tuple[Any, ...]:
|
|
479
|
+
"""Neighbor signature: (neighbor_color, edge_attr_1, ...).
|
|
480
|
+
|
|
481
|
+
:param node: Central node.
|
|
482
|
+
:type node: hashable
|
|
483
|
+
:param neighbor: Neighbor node.
|
|
484
|
+
:type neighbor: hashable
|
|
485
|
+
|
|
486
|
+
:return: Neighbor signature.
|
|
487
|
+
:rtype: tuple
|
|
488
|
+
"""
|
|
489
|
+
edge_data = self._graph.get_edge_data(node, neighbor, default={})
|
|
490
|
+
edge_vals = [edge_data.get(k) for k in self._cfg.edge_attrs]
|
|
491
|
+
return (self._colors[neighbor], *edge_vals)
|
|
492
|
+
|
|
493
|
+
def _build_orbits(self) -> None:
|
|
494
|
+
"""
|
|
495
|
+
Group nodes by final colors.
|
|
496
|
+
"""
|
|
497
|
+
color_to_nodes: Dict[int, List[Hashable]] = {}
|
|
498
|
+
for node, color in self._colors.items():
|
|
499
|
+
color_to_nodes.setdefault(color, []).append(node)
|
|
500
|
+
|
|
501
|
+
orbits = [frozenset(v) for v in color_to_nodes.values()]
|
|
502
|
+
self._orbits = sorted(
|
|
503
|
+
orbits,
|
|
504
|
+
key=lambda orbit: (len(orbit), min(map(repr, orbit))),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def _build_orbit_index(self) -> Dict[Hashable, int]:
|
|
508
|
+
"""Build node -> orbit id mapping.
|
|
509
|
+
|
|
510
|
+
:return: Orbit index.
|
|
511
|
+
:rtype: dict[hashable, int]
|
|
512
|
+
"""
|
|
513
|
+
idx: Dict[Hashable, int] = {}
|
|
514
|
+
for i, orb in enumerate(self._orbits):
|
|
515
|
+
for n in orb:
|
|
516
|
+
idx[n] = i
|
|
517
|
+
return idx
|
|
518
|
+
|
|
519
|
+
def _normalize_nodes(
|
|
520
|
+
self, nodes: Optional[Iterable[Hashable]]
|
|
521
|
+
) -> FrozenSet[Hashable]:
|
|
522
|
+
"""Normalize subset nodes.
|
|
523
|
+
|
|
524
|
+
:param nodes: Subset or None.
|
|
525
|
+
:type nodes: iterable[hashable] or None
|
|
526
|
+
|
|
527
|
+
:return: Frozenset of nodes (validated).
|
|
528
|
+
:rtype: frozenset[hashable]
|
|
529
|
+
"""
|
|
530
|
+
if nodes is None:
|
|
531
|
+
return frozenset(self._graph.nodes())
|
|
532
|
+
keep = frozenset(nodes)
|
|
533
|
+
unknown = [n for n in keep if n not in self._graph]
|
|
534
|
+
if unknown:
|
|
535
|
+
raise ValueError(f"Unknown nodes in subset: {unknown}")
|
|
536
|
+
return keep
|
|
537
|
+
|
|
538
|
+
def _components_on_induced(
|
|
539
|
+
self, nodes: Optional[Iterable[Hashable]]
|
|
540
|
+
) -> List[FrozenSet[Hashable]]:
|
|
541
|
+
"""Connected components on induced subgraph.
|
|
542
|
+
|
|
543
|
+
:param nodes: Subset or None.
|
|
544
|
+
:type nodes: iterable[hashable] or None
|
|
545
|
+
|
|
546
|
+
:return: Components in deterministic order.
|
|
547
|
+
:rtype: list[frozenset[hashable]]
|
|
548
|
+
"""
|
|
549
|
+
keep = self._normalize_nodes(nodes)
|
|
550
|
+
sub = self._graph.subgraph(keep)
|
|
551
|
+
component_nodes = (
|
|
552
|
+
nx.weakly_connected_components(sub)
|
|
553
|
+
if sub.is_directed()
|
|
554
|
+
else nx.connected_components(sub)
|
|
555
|
+
)
|
|
556
|
+
comps = [frozenset(component) for component in component_nodes]
|
|
557
|
+
return sorted(comps, key=lambda c: (len(c), min(map(repr, c))))
|
|
558
|
+
|
|
559
|
+
def _orbit_quotient_graph(
|
|
560
|
+
self,
|
|
561
|
+
keep_nodes: FrozenSet[Hashable],
|
|
562
|
+
orbit_idx: Dict[Hashable, int],
|
|
563
|
+
) -> nx.Graph:
|
|
564
|
+
"""Build orbit quotient graph restricted to keep_nodes.
|
|
565
|
+
|
|
566
|
+
:param keep_nodes: Nodes to keep.
|
|
567
|
+
:type keep_nodes: frozenset[hashable]
|
|
568
|
+
:param orbit_idx: Node->orbit id.
|
|
569
|
+
:type orbit_idx: dict[hashable, int]
|
|
570
|
+
|
|
571
|
+
:return: Quotient graph in orbit-id space.
|
|
572
|
+
:rtype: nx.Graph
|
|
573
|
+
"""
|
|
574
|
+
q = nx.Graph()
|
|
575
|
+
for n in keep_nodes:
|
|
576
|
+
q.add_node(orbit_idx[n])
|
|
577
|
+
|
|
578
|
+
for u, v in self._graph.edges():
|
|
579
|
+
if u not in keep_nodes or v not in keep_nodes:
|
|
580
|
+
continue
|
|
581
|
+
ou = orbit_idx[u]
|
|
582
|
+
ov = orbit_idx[v]
|
|
583
|
+
if ou != ov:
|
|
584
|
+
q.add_edge(ou, ov)
|
|
585
|
+
|
|
586
|
+
return q
|
|
587
|
+
|
|
588
|
+
def _validate_host_nodes_in_mappings(
|
|
589
|
+
self,
|
|
590
|
+
mappings: List[Mapping[Hashable, Hashable]],
|
|
591
|
+
orbit_idx: Dict[Hashable, int],
|
|
592
|
+
) -> None:
|
|
593
|
+
"""
|
|
594
|
+
Validate all host nodes are known.
|
|
595
|
+
|
|
596
|
+
:param mappings: Mappings list.
|
|
597
|
+
:type mappings: list[Mapping[hashable, hashable]]
|
|
598
|
+
:param orbit_idx: Node->orbit id.
|
|
599
|
+
:type orbit_idx: dict[hashable, int]
|
|
600
|
+
|
|
601
|
+
:raises ValueError: If unknown nodes appear.
|
|
602
|
+
"""
|
|
603
|
+
missing: List[Hashable] = []
|
|
604
|
+
for mp in mappings:
|
|
605
|
+
for h in mp.values():
|
|
606
|
+
if h not in orbit_idx:
|
|
607
|
+
missing.append(h)
|
|
608
|
+
if missing:
|
|
609
|
+
raise ValueError(
|
|
610
|
+
"Host nodes in mappings not present in fitted graph: "
|
|
611
|
+
f"{sorted(set(missing), key=repr)}"
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
def _dedup_by_signature(
|
|
615
|
+
self,
|
|
616
|
+
mappings: List[Mapping[Hashable, Hashable]],
|
|
617
|
+
sig_fn: Any,
|
|
618
|
+
) -> List[Mapping[Hashable, Hashable]]:
|
|
619
|
+
"""Deduplicate by computed signature (stable representative selection).
|
|
620
|
+
|
|
621
|
+
:param mappings: List of mappings.
|
|
622
|
+
:type mappings: list[Mapping[hashable, hashable]]
|
|
623
|
+
:param sig_fn: Function mapping mapping->signature.
|
|
624
|
+
:type sig_fn: callable
|
|
625
|
+
|
|
626
|
+
:return: Deduplicated list.
|
|
627
|
+
:rtype: list[Mapping[hashable, hashable]]
|
|
628
|
+
"""
|
|
629
|
+
seen = set()
|
|
630
|
+
out: List[Mapping[Hashable, Hashable]] = []
|
|
631
|
+
for mapping in mappings:
|
|
632
|
+
signature = sig_fn(mapping)
|
|
633
|
+
if signature in seen:
|
|
634
|
+
continue
|
|
635
|
+
seen.add(signature)
|
|
636
|
+
out.append(mapping)
|
|
637
|
+
return out
|
|
638
|
+
|
|
639
|
+
def _sort_orbits(
|
|
640
|
+
self, pattern_orbits: Iterable[FrozenSet[Hashable]]
|
|
641
|
+
) -> Tuple[Tuple[Hashable, ...], ...]:
|
|
642
|
+
"""Deterministically sort orbits.
|
|
643
|
+
|
|
644
|
+
:param pattern_orbits: Orbits.
|
|
645
|
+
:type pattern_orbits: iterable[frozenset[hashable]]
|
|
646
|
+
|
|
647
|
+
:return: Tuple of sorted orbit tuples.
|
|
648
|
+
:rtype: tuple[tuple[hashable, ...], ...]
|
|
649
|
+
"""
|
|
650
|
+
orbs = [tuple(sorted(o, key=repr)) for o in pattern_orbits]
|
|
651
|
+
orbs.sort(key=lambda orbit: (len(orbit), repr(orbit[0]) if orbit else ""))
|
|
652
|
+
return tuple(orbs)
|
|
653
|
+
|
|
654
|
+
def _dedup_pattern_orbits_no_anchor(
|
|
655
|
+
self,
|
|
656
|
+
mappings: List[Mapping[Hashable, Hashable]],
|
|
657
|
+
orbits: Tuple[Tuple[Hashable, ...], ...],
|
|
658
|
+
) -> List[Mapping[Hashable, Hashable]]:
|
|
659
|
+
"""Pattern-orbit dedup without anchor.
|
|
660
|
+
|
|
661
|
+
:param mappings: Mappings.
|
|
662
|
+
:type mappings: list[Mapping[hashable, hashable]]
|
|
663
|
+
:param orbits: Sorted orbit tuples.
|
|
664
|
+
:type orbits: tuple[tuple[hashable, ...], ...]
|
|
665
|
+
|
|
666
|
+
:return: Deduplicated mappings.
|
|
667
|
+
:rtype: list[Mapping[hashable, hashable]]
|
|
668
|
+
"""
|
|
669
|
+
|
|
670
|
+
def _sig(m: Mapping[Hashable, Hashable]) -> Tuple[Tuple[Hashable, ...], ...]:
|
|
671
|
+
return tuple(tuple(sorted((m[p] for p in orb), key=repr)) for orb in orbits)
|
|
672
|
+
|
|
673
|
+
return self._dedup_by_signature(mappings, _sig)
|
|
674
|
+
|
|
675
|
+
def _dedup_pattern_orbits_with_anchor(
|
|
676
|
+
self,
|
|
677
|
+
mappings: List[Mapping[Hashable, Hashable]],
|
|
678
|
+
orbits: Tuple[Tuple[Hashable, ...], ...],
|
|
679
|
+
anchor: FrozenSet[Hashable],
|
|
680
|
+
) -> List[Mapping[Hashable, Hashable]]:
|
|
681
|
+
"""Pattern-orbit dedup with anchor (no pruning inside anchor nodes).
|
|
682
|
+
|
|
683
|
+
:param mappings: Mappings.
|
|
684
|
+
:type mappings: list[Mapping[hashable, hashable]]
|
|
685
|
+
:param orbits: Sorted orbit tuples.
|
|
686
|
+
:type orbits: tuple[tuple[hashable, ...], ...]
|
|
687
|
+
:param anchor: Anchor nodes.
|
|
688
|
+
:type anchor: frozenset[hashable]
|
|
689
|
+
|
|
690
|
+
:return: Deduplicated mappings.
|
|
691
|
+
:rtype: list[Mapping[hashable, hashable]]
|
|
692
|
+
"""
|
|
693
|
+
anchor_nodes = tuple(sorted(anchor, key=repr))
|
|
694
|
+
free_orbits = tuple(orb for orb in orbits if not (set(orb) & anchor))
|
|
695
|
+
|
|
696
|
+
def _sig(m: Mapping[Hashable, Hashable]) -> Tuple[Any, ...]:
|
|
697
|
+
free_part = tuple(
|
|
698
|
+
tuple(sorted((m[p] for p in orb), key=repr)) for orb in free_orbits
|
|
699
|
+
)
|
|
700
|
+
anchor_part = tuple(m[p] for p in anchor_nodes)
|
|
701
|
+
return (free_part, anchor_part)
|
|
702
|
+
|
|
703
|
+
return self._dedup_by_signature(mappings, _sig)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
# --------------------------------------------------------------------------- #
|
|
707
|
+
# Convenience function (kept minimal)
|
|
708
|
+
# --------------------------------------------------------------------------- #
|
|
709
|
+
def estimate_automorphism_groups(
|
|
710
|
+
graph: nx.Graph,
|
|
711
|
+
node_attrs: Optional[Iterable[str]] = None,
|
|
712
|
+
edge_attrs: Optional[Iterable[str]] = None,
|
|
713
|
+
max_iter: int = 10,
|
|
714
|
+
) -> AutoEst:
|
|
715
|
+
"""Convenience function to fit :class:`AutoEst`.
|
|
716
|
+
|
|
717
|
+
:param graph: Input NetworkX graph.
|
|
718
|
+
:type graph: nx.Graph
|
|
719
|
+
:param node_attrs: Node attribute keys to include in WL initialization.
|
|
720
|
+
:type node_attrs: iterable[str] or None
|
|
721
|
+
:param edge_attrs: Edge attribute keys to include in neighbor signatures.
|
|
722
|
+
:type edge_attrs: iterable[str] or None
|
|
723
|
+
:param max_iter: Maximum WL iterations.
|
|
724
|
+
:type max_iter: int
|
|
725
|
+
|
|
726
|
+
:return: Fitted estimator.
|
|
727
|
+
:rtype: AutoEst
|
|
728
|
+
"""
|
|
729
|
+
node_list = list(node_attrs) if node_attrs is not None else None
|
|
730
|
+
edge_list = list(edge_attrs) if edge_attrs is not None else None
|
|
731
|
+
return AutoEst(
|
|
732
|
+
graph=graph,
|
|
733
|
+
node_attrs=node_list,
|
|
734
|
+
edge_attrs=edge_list,
|
|
735
|
+
max_iter=max_iter,
|
|
736
|
+
).fit()
|