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,544 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import networkx as nx
|
|
6
|
+
from networkx.algorithms.isomorphism import GraphMatcher
|
|
7
|
+
|
|
8
|
+
from synkit.Graph.Matcher.turbo_iso import _freeze_label_value, _MISSING_LABEL
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SING:
|
|
12
|
+
"""Subgraph search In Non-homogeneous Graphs (SING).
|
|
13
|
+
|
|
14
|
+
A lightweight Python implementation of the path-based
|
|
15
|
+
*filter-and-refine* strategy introduced by Di Natale et al.
|
|
16
|
+
(SING: Subgraph search In Non-homogeneous Graphs, BMC Bioinformatics,
|
|
17
|
+
2010) for subgraph search in large, possibly heterogeneous graphs.
|
|
18
|
+
|
|
19
|
+
The index is built once over a single *data graph* and can then be
|
|
20
|
+
queried with multiple *pattern graphs* via :meth:`search`.
|
|
21
|
+
|
|
22
|
+
.. rubric:: Notes
|
|
23
|
+
|
|
24
|
+
- This implementation focuses on the **path-feature** variant of SING,
|
|
25
|
+
where features are simple paths (with optional node/edge labels)
|
|
26
|
+
up to a maximum length.
|
|
27
|
+
- Multi-graphs are not supported.
|
|
28
|
+
- If the underlying data graph is modified after construction, call
|
|
29
|
+
:meth:`reindex` to rebuild the feature index.
|
|
30
|
+
|
|
31
|
+
.. rubric:: Example
|
|
32
|
+
|
|
33
|
+
A minimal example on an undirected, unlabeled graph:
|
|
34
|
+
|
|
35
|
+
.. code-block:: python
|
|
36
|
+
|
|
37
|
+
import networkx as nx
|
|
38
|
+
from sing import SING
|
|
39
|
+
|
|
40
|
+
# Data graph: 0-1-2-3
|
|
41
|
+
G = nx.path_graph(4)
|
|
42
|
+
|
|
43
|
+
# Query: 0-1-2
|
|
44
|
+
Q = nx.path_graph(3)
|
|
45
|
+
|
|
46
|
+
index = SING(G, max_path_length=2, node_att=[], edge_att=None)
|
|
47
|
+
matches = index.search(Q)
|
|
48
|
+
|
|
49
|
+
# matches contains four embeddings:
|
|
50
|
+
# {0: 0, 1: 1, 2: 2}
|
|
51
|
+
# {0: 1, 1: 2, 2: 3}
|
|
52
|
+
# {0: 2, 1: 1, 2: 0}
|
|
53
|
+
# {0: 3, 1: 2, 2: 1}
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
# Construction & Indexing
|
|
58
|
+
# ------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
graph: nx.Graph,
|
|
63
|
+
max_path_length: int = 3,
|
|
64
|
+
node_att: Union[str, List[str], None] = None,
|
|
65
|
+
edge_att: Union[str, List[str], None] = "order",
|
|
66
|
+
) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Initialize a SING index over a data graph.
|
|
69
|
+
|
|
70
|
+
:param graph: The data graph (directed or undirected; multi-graphs
|
|
71
|
+
are not supported).
|
|
72
|
+
:type graph: nx.Graph
|
|
73
|
+
:param max_path_length: Maximum number of edges considered when
|
|
74
|
+
enumerating path features (``>= 0``). A value of ``0`` keeps
|
|
75
|
+
only single-node features.
|
|
76
|
+
:type max_path_length: int, optional
|
|
77
|
+
:param node_att: Node attribute name(s) whose values are concatenated
|
|
78
|
+
to form the node label used in path features. If ``None``,
|
|
79
|
+
defaults to ``["element", "charge"]``, which is convenient for
|
|
80
|
+
chemical graphs.
|
|
81
|
+
:type node_att: str | list[str] | None, optional
|
|
82
|
+
:param edge_att: Edge attribute name(s) to include in path features.
|
|
83
|
+
If ``None``, edge attributes are ignored. Defaults to ``"order"``,
|
|
84
|
+
matching common chemical-graph conventions.
|
|
85
|
+
:type edge_att: str | list[str] | None, optional
|
|
86
|
+
"""
|
|
87
|
+
if graph.is_multigraph():
|
|
88
|
+
raise ValueError("SING does not support multigraphs")
|
|
89
|
+
if max_path_length < 0:
|
|
90
|
+
raise ValueError("max_path_length must be non-negative")
|
|
91
|
+
self.graph: nx.Graph = graph
|
|
92
|
+
self.max_path_length: int = int(max_path_length)
|
|
93
|
+
|
|
94
|
+
# Normalise attribute selections --------------------------------
|
|
95
|
+
if node_att is None:
|
|
96
|
+
node_att = ["element", "charge"]
|
|
97
|
+
self.node_att: List[str] = (
|
|
98
|
+
[node_att] if isinstance(node_att, str) else list(node_att)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if edge_att is None:
|
|
102
|
+
self.edge_att: List[str] = []
|
|
103
|
+
else:
|
|
104
|
+
self.edge_att = [edge_att] if isinstance(edge_att, str) else list(edge_att)
|
|
105
|
+
|
|
106
|
+
# Inverted index: feature signature -> set[data-node]
|
|
107
|
+
self.feature_index: Dict[Tuple[Any, ...], Set[Any]] = {}
|
|
108
|
+
# Per-vertex feature sets
|
|
109
|
+
self.vertex_features: Dict[Any, Set[Tuple[Any, ...]]] = {}
|
|
110
|
+
|
|
111
|
+
# Cached signatures for the data graph (for efficiency)
|
|
112
|
+
self._node_sig_data: Dict[Any, Tuple[Any, ...]] = {}
|
|
113
|
+
self._edge_sig_data: Dict[tuple[Any, Any], Tuple[Any, ...]] = {}
|
|
114
|
+
|
|
115
|
+
# Build caches + index once up-front
|
|
116
|
+
self._init_data_signatures()
|
|
117
|
+
self._build_index()
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Internal helpers: signatures & indexing
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def _node_signature(self, v: Any, G: nx.Graph) -> Tuple[Any, ...]:
|
|
124
|
+
"""Return a string signature for node ``v`` in graph ``G`` based on
|
|
125
|
+
:attr:`node_att`.
|
|
126
|
+
|
|
127
|
+
:param v: Node identifier.
|
|
128
|
+
:type v: Any
|
|
129
|
+
:param G: Graph containing the node.
|
|
130
|
+
:type G: nx.Graph
|
|
131
|
+
:return: Concatenated attribute values (``"|"``-separated) or
|
|
132
|
+
an empty string if :attr:`node_att` is empty.
|
|
133
|
+
:rtype: str
|
|
134
|
+
"""
|
|
135
|
+
if not self.node_att:
|
|
136
|
+
return ()
|
|
137
|
+
return tuple(
|
|
138
|
+
_freeze_label_value(G.nodes[v].get(attr, _MISSING_LABEL))
|
|
139
|
+
for attr in self.node_att
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def _edge_signature(self, u: Any, v: Any, G: nx.Graph) -> Tuple[Any, ...]:
|
|
143
|
+
"""Return a string signature for edge ``(u, v)`` in graph ``G`` based on
|
|
144
|
+
:attr:`edge_att`.
|
|
145
|
+
|
|
146
|
+
If no edge attributes were requested, returns an empty string.
|
|
147
|
+
|
|
148
|
+
:param u: Source node identifier.
|
|
149
|
+
:type u: Any
|
|
150
|
+
:param v: Target node identifier.
|
|
151
|
+
:type v: Any
|
|
152
|
+
:param G: Graph containing the edge.
|
|
153
|
+
:type G: nx.Graph
|
|
154
|
+
:return: Concatenated attribute values (``"|"``-separated),
|
|
155
|
+
or an empty string when :attr:`edge_att` is empty.
|
|
156
|
+
:rtype: str
|
|
157
|
+
"""
|
|
158
|
+
if not self.edge_att:
|
|
159
|
+
return ()
|
|
160
|
+
return tuple(
|
|
161
|
+
_freeze_label_value(G[u][v].get(attr, _MISSING_LABEL))
|
|
162
|
+
for attr in self.edge_att
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _init_data_signatures(self) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Precompute node and edge signatures for the data graph.
|
|
168
|
+
|
|
169
|
+
This avoids repeatedly looking up attributes and constructing
|
|
170
|
+
strings during index building and search.
|
|
171
|
+
"""
|
|
172
|
+
G = self.graph
|
|
173
|
+
self._node_sig_data = {v: self._node_signature(v, G) for v in G.nodes}
|
|
174
|
+
self._edge_sig_data = {}
|
|
175
|
+
|
|
176
|
+
if self.edge_att:
|
|
177
|
+
# For undirected graphs, cache both (u, v) and (v, u)
|
|
178
|
+
if G.is_directed():
|
|
179
|
+
for u, v in G.edges:
|
|
180
|
+
self._edge_sig_data[(u, v)] = self._edge_signature(u, v, G)
|
|
181
|
+
else:
|
|
182
|
+
for u, v in G.edges:
|
|
183
|
+
sig = self._edge_signature(u, v, G)
|
|
184
|
+
self._edge_sig_data[(u, v)] = sig
|
|
185
|
+
self._edge_sig_data[(v, u)] = sig
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# Feature extraction (paths)
|
|
189
|
+
# ------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def _extract_path_features(
|
|
192
|
+
self, node: Any, G: nx.Graph, is_query: bool = False
|
|
193
|
+
) -> Set[Tuple[Any, ...]]:
|
|
194
|
+
"""Enumerate all simple paths starting at ``node`` up to
|
|
195
|
+
:attr:`max_path_length` edges (inclusive), represented as label
|
|
196
|
+
sequences.
|
|
197
|
+
|
|
198
|
+
Works for both data and query graphs.
|
|
199
|
+
|
|
200
|
+
:param node: Starting node in ``G``.
|
|
201
|
+
:type node: Any
|
|
202
|
+
:param G: Graph in which to enumerate paths (data or query graph).
|
|
203
|
+
:type G: nx.Graph
|
|
204
|
+
:param is_query: Flag indicating whether ``G`` is the query graph.
|
|
205
|
+
Currently unused but kept for future extensions (e.g.,
|
|
206
|
+
query-specific feature tweaks).
|
|
207
|
+
:type is_query: bool, optional
|
|
208
|
+
:return: Set of string-encoded path features.
|
|
209
|
+
:rtype: set[str]
|
|
210
|
+
"""
|
|
211
|
+
features: Set[Tuple[Any, ...]] = set()
|
|
212
|
+
max_len = self.max_path_length
|
|
213
|
+
|
|
214
|
+
# Use cached signatures when possible (data graph)
|
|
215
|
+
if G is self.graph:
|
|
216
|
+
node_sig_cache = self._node_sig_data
|
|
217
|
+
edge_sig_cache = self._edge_sig_data
|
|
218
|
+
|
|
219
|
+
def get_node_sig(x: Any) -> Tuple[Any, ...]:
|
|
220
|
+
return node_sig_cache[x]
|
|
221
|
+
|
|
222
|
+
def get_edge_sig(a: Any, b: Any) -> Tuple[Any, ...]:
|
|
223
|
+
return edge_sig_cache.get((a, b), ())
|
|
224
|
+
|
|
225
|
+
else:
|
|
226
|
+
|
|
227
|
+
def get_node_sig(x: Any) -> Tuple[Any, ...]:
|
|
228
|
+
return self._node_signature(x, G)
|
|
229
|
+
|
|
230
|
+
def get_edge_sig(a: Any, b: Any) -> Tuple[Any, ...]:
|
|
231
|
+
return self._edge_signature(a, b, G)
|
|
232
|
+
|
|
233
|
+
def dfs(
|
|
234
|
+
current: Any,
|
|
235
|
+
depth: int,
|
|
236
|
+
visited: Set[Any],
|
|
237
|
+
path_parts: List[Tuple[Any, ...]],
|
|
238
|
+
):
|
|
239
|
+
# Record current path (including the starting node at depth 0)
|
|
240
|
+
features.add(tuple(path_parts))
|
|
241
|
+
if depth == max_len:
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
for nbr in G.neighbors(current):
|
|
245
|
+
if nbr in visited:
|
|
246
|
+
continue
|
|
247
|
+
edge_sig = get_edge_sig(current, nbr)
|
|
248
|
+
|
|
249
|
+
# Extend path in-place (append & pop for efficiency)
|
|
250
|
+
if edge_sig:
|
|
251
|
+
path_parts.append(edge_sig)
|
|
252
|
+
path_parts.append(get_node_sig(nbr))
|
|
253
|
+
|
|
254
|
+
visited.add(nbr)
|
|
255
|
+
dfs(nbr, depth + 1, visited, path_parts)
|
|
256
|
+
visited.remove(nbr)
|
|
257
|
+
|
|
258
|
+
# Backtrack
|
|
259
|
+
path_parts.pop()
|
|
260
|
+
if edge_sig:
|
|
261
|
+
path_parts.pop()
|
|
262
|
+
|
|
263
|
+
start_sig = get_node_sig(node)
|
|
264
|
+
dfs(node, 0, {node}, [start_sig])
|
|
265
|
+
return features
|
|
266
|
+
|
|
267
|
+
# ------------------------------------------------------------------
|
|
268
|
+
# Index construction
|
|
269
|
+
# ------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
def _build_index(self) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Build the feature index over the current data graph.
|
|
274
|
+
|
|
275
|
+
Called automatically during initialization and by :meth:`reindex`.
|
|
276
|
+
"""
|
|
277
|
+
self.feature_index.clear()
|
|
278
|
+
self.vertex_features.clear()
|
|
279
|
+
|
|
280
|
+
for v in self.graph.nodes:
|
|
281
|
+
feats = self._extract_path_features(v, self.graph)
|
|
282
|
+
self.vertex_features[v] = feats
|
|
283
|
+
for f in feats:
|
|
284
|
+
self.feature_index.setdefault(f, set()).add(v)
|
|
285
|
+
|
|
286
|
+
# ------------------------------------------------------------------
|
|
287
|
+
# Public API: reindexing & candidate generation
|
|
288
|
+
# ------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
def reindex(self, graph: Optional[nx.Graph] = None) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Rebuild the index, optionally replacing the underlying data graph.
|
|
293
|
+
|
|
294
|
+
:param graph: New data graph. If ``None``, the existing
|
|
295
|
+
:attr:`graph` is re-indexed.
|
|
296
|
+
:type graph: nx.Graph | None, optional
|
|
297
|
+
"""
|
|
298
|
+
if graph is not None:
|
|
299
|
+
if graph.is_multigraph():
|
|
300
|
+
raise ValueError("SING does not support multigraphs")
|
|
301
|
+
self.graph = graph
|
|
302
|
+
self._init_data_signatures()
|
|
303
|
+
self._build_index()
|
|
304
|
+
|
|
305
|
+
def _candidate_vertices(self, query_graph: nx.Graph) -> Dict[Any, Set[Any]]:
|
|
306
|
+
"""Return per-query-vertex candidate sets using posting-list
|
|
307
|
+
intersections.
|
|
308
|
+
|
|
309
|
+
:param query_graph: Query (pattern) graph.
|
|
310
|
+
:type query_graph: nx.Graph
|
|
311
|
+
:return: Mapping from query-node -> set of candidate data-nodes.
|
|
312
|
+
:rtype: dict[Any, set[Any]]
|
|
313
|
+
"""
|
|
314
|
+
cand: Dict[Any, Set[Any]] = {}
|
|
315
|
+
for qv in query_graph.nodes:
|
|
316
|
+
q_feats = self._extract_path_features(qv, query_graph, is_query=True)
|
|
317
|
+
if not q_feats:
|
|
318
|
+
# Fallback: no features → all data vertices are candidates
|
|
319
|
+
cand[qv] = set(self.graph.nodes)
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
# Initialise with posting list of *one* feature, then intersect.
|
|
323
|
+
iterator = iter(q_feats)
|
|
324
|
+
first_f = next(iterator)
|
|
325
|
+
cset = set(self.feature_index.get(first_f, []))
|
|
326
|
+
for f in iterator:
|
|
327
|
+
cset &= self.feature_index.get(f, set())
|
|
328
|
+
if not cset:
|
|
329
|
+
break # early quit
|
|
330
|
+
cand[qv] = cset
|
|
331
|
+
return cand
|
|
332
|
+
|
|
333
|
+
# ------------------------------------------------------------------
|
|
334
|
+
# Deduplication by query automorphisms (optional)
|
|
335
|
+
# ------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
def _deduplicate_by_query_automorphisms(
|
|
338
|
+
self, mappings: List[Dict[Any, Any]], query_graph: nx.Graph
|
|
339
|
+
) -> List[Dict[Any, Any]]:
|
|
340
|
+
"""Deduplicate embeddings up to automorphisms of the query graph.
|
|
341
|
+
|
|
342
|
+
Two mappings ``M`` and ``M'`` are considered equivalent if there exists
|
|
343
|
+
an automorphism :math:`\\sigma` of the query such that:
|
|
344
|
+
|
|
345
|
+
.. math::
|
|
346
|
+
|
|
347
|
+
M' = M \\circ \\sigma
|
|
348
|
+
|
|
349
|
+
That is, they differ only by a symmetry of the *pattern*.
|
|
350
|
+
|
|
351
|
+
:param mappings: List of node mappings (query-node -> data-node).
|
|
352
|
+
:type mappings: list[dict[Any, Any]]
|
|
353
|
+
:param query_graph: The query graph whose automorphisms are used for
|
|
354
|
+
deduplication.
|
|
355
|
+
:type query_graph: nx.Graph
|
|
356
|
+
:return: Reduced list with one representative per equivalence class.
|
|
357
|
+
:rtype: list[dict[Any, Any]]
|
|
358
|
+
"""
|
|
359
|
+
if not mappings:
|
|
360
|
+
return []
|
|
361
|
+
|
|
362
|
+
matcher_cls = (
|
|
363
|
+
nx.algorithms.isomorphism.DiGraphMatcher
|
|
364
|
+
if query_graph.is_directed()
|
|
365
|
+
else GraphMatcher
|
|
366
|
+
)
|
|
367
|
+
gm = matcher_cls(query_graph, query_graph)
|
|
368
|
+
autos = list(gm.isomorphisms_iter())
|
|
369
|
+
# If only identity, nothing to dedup
|
|
370
|
+
if len(autos) <= 1:
|
|
371
|
+
return mappings
|
|
372
|
+
|
|
373
|
+
# Fix a deterministic ordering of query nodes
|
|
374
|
+
q_nodes = list(query_graph.nodes())
|
|
375
|
+
|
|
376
|
+
seen_signatures: Set[tuple] = set()
|
|
377
|
+
unique: List[Dict[Any, Any]] = []
|
|
378
|
+
|
|
379
|
+
for m in mappings:
|
|
380
|
+
variants: List[tuple] = []
|
|
381
|
+
for sigma in autos:
|
|
382
|
+
# M' = M ∘ sigma, so M'(p) = M(sigma[p])
|
|
383
|
+
try:
|
|
384
|
+
variant = tuple(m[sigma[p]] for p in q_nodes)
|
|
385
|
+
except KeyError:
|
|
386
|
+
# Should not happen for full mappings, but be defensive
|
|
387
|
+
continue
|
|
388
|
+
variants.append(variant)
|
|
389
|
+
|
|
390
|
+
if not variants:
|
|
391
|
+
# Fallback: direct tuple if something went wrong above
|
|
392
|
+
signature = tuple(m[p] for p in q_nodes)
|
|
393
|
+
else:
|
|
394
|
+
signature = min(variants)
|
|
395
|
+
|
|
396
|
+
if signature not in seen_signatures:
|
|
397
|
+
seen_signatures.add(signature)
|
|
398
|
+
unique.append(m)
|
|
399
|
+
|
|
400
|
+
return unique
|
|
401
|
+
|
|
402
|
+
# ------------------------------------------------------------------
|
|
403
|
+
# Refinement: backtracking with candidate sets
|
|
404
|
+
# ------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
def search(
|
|
407
|
+
self,
|
|
408
|
+
query_graph: nx.Graph,
|
|
409
|
+
prune: bool = False,
|
|
410
|
+
dedup_autos: bool = False,
|
|
411
|
+
) -> Union[List[Dict[Any, Any]], bool]:
|
|
412
|
+
"""Find subgraph isomorphisms from ``query_graph`` into the data graph.
|
|
413
|
+
|
|
414
|
+
This method performs a path-feature-based **filter** to obtain
|
|
415
|
+
candidate vertices, followed by a VF2-style **refinement** via
|
|
416
|
+
backtracking with neighbourhood and label consistency checks.
|
|
417
|
+
|
|
418
|
+
:param query_graph: Pattern graph to match against :attr:`graph`.
|
|
419
|
+
:type query_graph: nx.Graph
|
|
420
|
+
:param prune: If ``True``, stop after finding the first mapping
|
|
421
|
+
and return a boolean indicating existence of at least one
|
|
422
|
+
embedding. If ``False`` (default), return the full list of
|
|
423
|
+
mappings.
|
|
424
|
+
:type prune: bool, optional
|
|
425
|
+
:param dedup_autos: If ``True``, collapse symmetric embeddings that
|
|
426
|
+
differ only by automorphisms of the query graph, returning one
|
|
427
|
+
representative per equivalence class. Has no effect when
|
|
428
|
+
``prune=True``.
|
|
429
|
+
:type dedup_autos: bool, optional
|
|
430
|
+
:return: Either ``True``/``False`` (when ``prune=True``) or a list
|
|
431
|
+
of injective node mappings ``[{q_node: data_node, ...}, ...]``.
|
|
432
|
+
:rtype: list[dict[Any, Any]] | bool
|
|
433
|
+
|
|
434
|
+
.. rubric:: Example
|
|
435
|
+
|
|
436
|
+
.. code-block:: python
|
|
437
|
+
|
|
438
|
+
import networkx as nx
|
|
439
|
+
from sing import SING
|
|
440
|
+
|
|
441
|
+
G = nx.cycle_graph(4)
|
|
442
|
+
Q = nx.path_graph(3)
|
|
443
|
+
|
|
444
|
+
index = SING(G, max_path_length=2, node_att=[], edge_att=None)
|
|
445
|
+
all_mappings = index.search(Q) # all embeddings
|
|
446
|
+
unique_mappings = index.search(Q, dedup_autos=True) # collapse symmetries
|
|
447
|
+
"""
|
|
448
|
+
if query_graph.is_multigraph():
|
|
449
|
+
raise ValueError("SING does not support multigraph queries")
|
|
450
|
+
cand = self._candidate_vertices(query_graph)
|
|
451
|
+
mapping: Dict[Any, Any] = {}
|
|
452
|
+
used: Set[Any] = set()
|
|
453
|
+
results: List[Dict[Any, Any]] = []
|
|
454
|
+
|
|
455
|
+
# Order query vertices by fewest candidates (fail-fast heuristic)
|
|
456
|
+
order = sorted(query_graph.nodes, key=lambda n: len(cand[n]))
|
|
457
|
+
|
|
458
|
+
def backtrack(i: int) -> bool:
|
|
459
|
+
if i == len(order):
|
|
460
|
+
results.append(mapping.copy())
|
|
461
|
+
return prune # signal to stop if pruning
|
|
462
|
+
|
|
463
|
+
qv = order[i]
|
|
464
|
+
for dv in cand[qv]:
|
|
465
|
+
if dv in used:
|
|
466
|
+
continue
|
|
467
|
+
|
|
468
|
+
# Neighbourhood + edge-label consistency
|
|
469
|
+
valid = True
|
|
470
|
+
if query_graph.has_edge(qv, qv):
|
|
471
|
+
if not self.graph.has_edge(dv, dv) or (
|
|
472
|
+
self.edge_att
|
|
473
|
+
and self._edge_signature(qv, qv, query_graph)
|
|
474
|
+
!= self._edge_signature(dv, dv, self.graph)
|
|
475
|
+
):
|
|
476
|
+
valid = False
|
|
477
|
+
for nbr, dn in mapping.items():
|
|
478
|
+
if query_graph.has_edge(qv, nbr):
|
|
479
|
+
if not self.graph.has_edge(dv, dn) or (
|
|
480
|
+
self.edge_att
|
|
481
|
+
and self._edge_signature(qv, nbr, query_graph)
|
|
482
|
+
!= self._edge_signature(dv, dn, self.graph)
|
|
483
|
+
):
|
|
484
|
+
valid = False
|
|
485
|
+
break
|
|
486
|
+
if query_graph.is_directed() and query_graph.has_edge(nbr, qv):
|
|
487
|
+
if not self.graph.has_edge(dn, dv) or (
|
|
488
|
+
self.edge_att
|
|
489
|
+
and self._edge_signature(nbr, qv, query_graph)
|
|
490
|
+
!= self._edge_signature(dn, dv, self.graph)
|
|
491
|
+
):
|
|
492
|
+
valid = False
|
|
493
|
+
break
|
|
494
|
+
if not valid:
|
|
495
|
+
continue
|
|
496
|
+
|
|
497
|
+
# Node-label consistency
|
|
498
|
+
if self.node_att and self._node_signature(
|
|
499
|
+
qv, query_graph
|
|
500
|
+
) != self._node_signature(dv, self.graph):
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
mapping[qv] = dv
|
|
504
|
+
used.add(dv)
|
|
505
|
+
if backtrack(i + 1):
|
|
506
|
+
return True
|
|
507
|
+
used.remove(dv)
|
|
508
|
+
del mapping[qv]
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
backtrack(0)
|
|
512
|
+
|
|
513
|
+
if prune:
|
|
514
|
+
return len(results) > 0
|
|
515
|
+
|
|
516
|
+
if dedup_autos:
|
|
517
|
+
results = self._deduplicate_by_query_automorphisms(results, query_graph)
|
|
518
|
+
|
|
519
|
+
return results
|
|
520
|
+
|
|
521
|
+
# ------------------------------------------------------------------
|
|
522
|
+
# Dunder methods
|
|
523
|
+
# ------------------------------------------------------------------
|
|
524
|
+
|
|
525
|
+
def __len__(self) -> int:
|
|
526
|
+
"""Return the number of vertices in the data graph.
|
|
527
|
+
|
|
528
|
+
:return: Number of data vertices.
|
|
529
|
+
:rtype: int
|
|
530
|
+
"""
|
|
531
|
+
return self.graph.number_of_nodes()
|
|
532
|
+
|
|
533
|
+
def __repr__(self) -> str:
|
|
534
|
+
"""Return a concise string representation of the index.
|
|
535
|
+
|
|
536
|
+
:return: Summary string including graph size and configuration.
|
|
537
|
+
:rtype: str
|
|
538
|
+
"""
|
|
539
|
+
return (
|
|
540
|
+
f"<SING | |V|={self.graph.number_of_nodes()} "
|
|
541
|
+
f"|E|={self.graph.number_of_edges()} "
|
|
542
|
+
f"max_path_length={self.max_path_length} "
|
|
543
|
+
f"node_att={self.node_att} edge_att={self.edge_att}>"
|
|
544
|
+
)
|