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,239 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import networkx as nx
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GraphSignature:
|
|
6
|
+
"""Provides methods to generate canonical signatures for graph edges (with
|
|
7
|
+
flexible 'order' and 'state' attributes, and node degrees/neighbor
|
|
8
|
+
information), various spectral invariants, adjacency matrix, and complete
|
|
9
|
+
graphs.
|
|
10
|
+
|
|
11
|
+
Aims for high uniqueness without relying solely on isomorphism
|
|
12
|
+
checks.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, graph: nx.Graph):
|
|
16
|
+
"""Initializes the GraphSignature class with a specified graph.
|
|
17
|
+
|
|
18
|
+
:param graph: A NetworkX graph instance.
|
|
19
|
+
:type graph: nx.Graph
|
|
20
|
+
"""
|
|
21
|
+
self.graph = graph
|
|
22
|
+
self._validate_graph()
|
|
23
|
+
|
|
24
|
+
def _validate_graph(self):
|
|
25
|
+
"""Validates that all nodes have the required attributes ('element' and
|
|
26
|
+
'charge'), and all edges have the required 'order' attribute as int,
|
|
27
|
+
float, or tuple of two floats, and optionally the 'state' attribute.
|
|
28
|
+
|
|
29
|
+
:raises ValueError: If required node or edge attributes are missing or
|
|
30
|
+
have invalid types.
|
|
31
|
+
"""
|
|
32
|
+
for node, data in self.graph.nodes(data=True):
|
|
33
|
+
if "element" not in data:
|
|
34
|
+
raise ValueError(f"Node {node} is missing the 'element' attribute.")
|
|
35
|
+
if "charge" not in data:
|
|
36
|
+
raise ValueError(f"Node {node} is missing the 'charge' attribute.")
|
|
37
|
+
|
|
38
|
+
for u, v, data in self.graph.edges(data=True):
|
|
39
|
+
if "order" not in data:
|
|
40
|
+
raise ValueError(f"Edge ({u}, {v}) is missing the 'order' attribute.")
|
|
41
|
+
order = data["order"]
|
|
42
|
+
if isinstance(order, tuple):
|
|
43
|
+
if len(order) != 2 or not all(
|
|
44
|
+
isinstance(o, (int, float)) for o in order
|
|
45
|
+
):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Edge ({u}, {v}) has an invalid 'order'. It must be a tuple of two ints/floats."
|
|
48
|
+
)
|
|
49
|
+
elif not isinstance(order, (int, float)):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Edge ({u}, {v}) has an invalid 'order'. It must be an int, float, or a tuple of two ints/floats."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Optional: Validate 'state' attribute if present
|
|
55
|
+
state = data.get("state", "steady") # Default to 'steady' if missing
|
|
56
|
+
if state not in {"break", "form", "steady"}:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Edge ({u}, {v}) has an invalid 'state'. It must be 'break', 'form', or 'steady'."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def create_edge_signature(
|
|
62
|
+
self, include_neighbors: bool = False, max_hop: int = 2
|
|
63
|
+
) -> str:
|
|
64
|
+
"""Generates a canonical edge signature by formatting each edge with
|
|
65
|
+
sorted node elements (including charge), node degrees, bond order, bond
|
|
66
|
+
state, and optionally including neighbor information and topological
|
|
67
|
+
context.
|
|
68
|
+
|
|
69
|
+
:param include_neighbors: Whether to include neighbors' details in the edge signature.
|
|
70
|
+
:type include_neighbors: bool
|
|
71
|
+
:param max_hop: Maximum number of hops to include for neighbor-level structural information.
|
|
72
|
+
:type max_hop: int
|
|
73
|
+
|
|
74
|
+
:return: A concatenated and sorted string of edge representations.
|
|
75
|
+
:rtype: str
|
|
76
|
+
"""
|
|
77
|
+
edge_signature_parts = []
|
|
78
|
+
|
|
79
|
+
for u, v, data in self.graph.edges(data=True):
|
|
80
|
+
# Retrieve bond order (default to (1.0, 1.0) if missing)
|
|
81
|
+
order = data.get("order", (1.0, 1.0))
|
|
82
|
+
|
|
83
|
+
# Format order as a tuple (default or actual value)
|
|
84
|
+
if isinstance(order, tuple):
|
|
85
|
+
order_str = f"{{{order[0]:.1f},{order[1]:.1f}}}"
|
|
86
|
+
else:
|
|
87
|
+
order_str = f"{float(order):.1f}"
|
|
88
|
+
|
|
89
|
+
# Get node elements and charges for both nodes
|
|
90
|
+
node1_element = self.graph.nodes[u].get(
|
|
91
|
+
"element", "X"
|
|
92
|
+
) # Default to 'X' if missing
|
|
93
|
+
node1_charge = self.graph.nodes[u].get(
|
|
94
|
+
"charge", 0
|
|
95
|
+
) # Default to 0 if missing
|
|
96
|
+
node2_element = self.graph.nodes[v].get("element", "X")
|
|
97
|
+
node2_charge = self.graph.nodes[v].get("charge", 0)
|
|
98
|
+
|
|
99
|
+
# Construct node representation with element and charge
|
|
100
|
+
node1 = f"{node1_element}{node1_charge}"
|
|
101
|
+
node2 = f"{node2_element}{node2_charge}"
|
|
102
|
+
|
|
103
|
+
# Optionally include neighbors in the signature
|
|
104
|
+
if include_neighbors:
|
|
105
|
+
neighbors_u = sorted(
|
|
106
|
+
[
|
|
107
|
+
f"{self.graph.nodes[neighbor].get('element', 'X')}{self.graph.nodes[neighbor].get('charge', 0)}"
|
|
108
|
+
+ f"d{self.graph.degree(neighbor)}"
|
|
109
|
+
for neighbor in self.graph.neighbors(u)
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
neighbors_v = sorted(
|
|
113
|
+
[
|
|
114
|
+
f"{self.graph.nodes[neighbor].get('element', 'X')}{self.graph.nodes[neighbor].get('charge', 0)}"
|
|
115
|
+
+ f"d{self.graph.degree(neighbor)}"
|
|
116
|
+
for neighbor in self.graph.neighbors(v)
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Represent neighbors within square brackets
|
|
121
|
+
node1_neighbors = "".join(neighbors_u)
|
|
122
|
+
node2_neighbors = "".join(neighbors_v)
|
|
123
|
+
node1 = f"{node1}[{node1_neighbors}]"
|
|
124
|
+
node2 = f"{node2}[{node2_neighbors}]"
|
|
125
|
+
|
|
126
|
+
# Include k-hop neighborhood information
|
|
127
|
+
if max_hop > 1:
|
|
128
|
+
node1_neighbors_khop = self._get_khop_neighbors(u, max_hop)
|
|
129
|
+
node2_neighbors_khop = self._get_khop_neighbors(v, max_hop)
|
|
130
|
+
node1 += f"[{node1_neighbors_khop}]"
|
|
131
|
+
node2 += f"[{node2_neighbors_khop}]"
|
|
132
|
+
|
|
133
|
+
# Sort nodes to ensure consistency in edge signature (avoid direction dependency)
|
|
134
|
+
node1, node2 = sorted([node1, node2])
|
|
135
|
+
|
|
136
|
+
# Format the edge signature and append it
|
|
137
|
+
edge_part = f"{node1}{order_str}{node2}"
|
|
138
|
+
edge_signature_parts.append(edge_part)
|
|
139
|
+
|
|
140
|
+
# Sort all edge signatures to ensure consistency in the final representation
|
|
141
|
+
return "/".join(sorted(edge_signature_parts))
|
|
142
|
+
|
|
143
|
+
def _get_khop_neighbors(self, node, max_hop):
|
|
144
|
+
"""Retrieves the k-hop neighborhood information for a given node.
|
|
145
|
+
|
|
146
|
+
:param node: The node for which to get neighborhood information.
|
|
147
|
+
:type node: int
|
|
148
|
+
:param max_hop: Maximum number of hops for neighborhood exploration.
|
|
149
|
+
:type max_hop: int
|
|
150
|
+
|
|
151
|
+
:return: A concatenated string representing the k-hop neighborhood information.
|
|
152
|
+
:rtype: str
|
|
153
|
+
"""
|
|
154
|
+
k_hop_neighbors = []
|
|
155
|
+
current_hop_neighbors = [node]
|
|
156
|
+
for _ in range(max_hop):
|
|
157
|
+
next_hop_neighbors = []
|
|
158
|
+
for n in current_hop_neighbors:
|
|
159
|
+
next_hop_neighbors.extend(list(self.graph.neighbors(n)))
|
|
160
|
+
# Filter out already seen nodes to avoid loops
|
|
161
|
+
next_hop_neighbors = set(next_hop_neighbors) - set(k_hop_neighbors)
|
|
162
|
+
k_hop_neighbors.extend(next_hop_neighbors)
|
|
163
|
+
current_hop_neighbors = next_hop_neighbors
|
|
164
|
+
|
|
165
|
+
# Return sorted k-hop neighborhood info
|
|
166
|
+
return "".join(
|
|
167
|
+
sorted(
|
|
168
|
+
[
|
|
169
|
+
f"{self.graph.nodes[neighbor].get('element', 'X')}{self.graph.nodes[neighbor].get('charge', 0)}"
|
|
170
|
+
for neighbor in k_hop_neighbors
|
|
171
|
+
]
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def create_wl_hash(self, iterations: int = 3) -> str:
|
|
176
|
+
"""Generates a Weisfeiler-Lehman (WL) hash for the graph to capture its
|
|
177
|
+
structural features.
|
|
178
|
+
|
|
179
|
+
:param iterations: Number of WL iterations to perform.
|
|
180
|
+
:type iterations: int
|
|
181
|
+
|
|
182
|
+
:return: A hexadecimal hash representing the WL feature.
|
|
183
|
+
:rtype: str
|
|
184
|
+
"""
|
|
185
|
+
# Initialize labels with both 'element' and 'charge'
|
|
186
|
+
labels = {
|
|
187
|
+
node: f"{data['element']}{data.get('charge', 0)}"
|
|
188
|
+
for node, data in self.graph.nodes(data=True)
|
|
189
|
+
}
|
|
190
|
+
for _ in range(iterations):
|
|
191
|
+
new_labels = {}
|
|
192
|
+
for node in self.graph.nodes():
|
|
193
|
+
# Gather sorted labels of neighbors
|
|
194
|
+
neighbor_labels = sorted(
|
|
195
|
+
labels[neighbor] for neighbor in self.graph.neighbors(node)
|
|
196
|
+
)
|
|
197
|
+
# Concatenate current label with neighbor labels
|
|
198
|
+
concatenated = labels[node] + "".join(neighbor_labels)
|
|
199
|
+
# Hash the concatenated string to obtain a new label
|
|
200
|
+
new_label = hashlib.sha256(concatenated.encode()).hexdigest()
|
|
201
|
+
new_labels[node] = new_label
|
|
202
|
+
labels = new_labels
|
|
203
|
+
# Aggregate all node labels into a sorted string and hash it
|
|
204
|
+
sorted_labels = sorted(labels.values())
|
|
205
|
+
aggregated = "".join(sorted_labels)
|
|
206
|
+
graph_hash = hashlib.sha256(aggregated.encode()).hexdigest()
|
|
207
|
+
return graph_hash
|
|
208
|
+
|
|
209
|
+
def create_graph_signature(
|
|
210
|
+
self,
|
|
211
|
+
include_wl_hash: bool = True,
|
|
212
|
+
include_neighbors: bool = True,
|
|
213
|
+
max_hop: int = 1,
|
|
214
|
+
) -> str:
|
|
215
|
+
"""Combines edge, various spectral invariants, and WL hash into a
|
|
216
|
+
single comprehensive graph signature.
|
|
217
|
+
|
|
218
|
+
:param include_wl_hash: Whether to include the Weisfeiler-Lehman hash.
|
|
219
|
+
:type include_wl_hash: bool
|
|
220
|
+
:param include_neighbors: Whether to include neighbor information in edge signatures.
|
|
221
|
+
:type include_neighbors: bool
|
|
222
|
+
:param max_hop: Maximum neighborhood depth used by edge signatures.
|
|
223
|
+
:type max_hop: int
|
|
224
|
+
|
|
225
|
+
:return: A concatenated string representing the complete graph signature.
|
|
226
|
+
:rtype: str
|
|
227
|
+
"""
|
|
228
|
+
signatures = []
|
|
229
|
+
|
|
230
|
+
if include_wl_hash:
|
|
231
|
+
wl_signature = self.create_wl_hash()
|
|
232
|
+
signatures.append(f"{wl_signature}")
|
|
233
|
+
|
|
234
|
+
edge_signature = self.create_edge_signature(
|
|
235
|
+
include_neighbors=include_neighbors, max_hop=max_hop
|
|
236
|
+
)
|
|
237
|
+
signatures.append(f"{edge_signature}")
|
|
238
|
+
|
|
239
|
+
return "|".join(signatures)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HashFPs:
|
|
7
|
+
def __init__(
|
|
8
|
+
self, graph: nx.Graph, numBits: int = 256, hash_alg: str = "sha256"
|
|
9
|
+
) -> None:
|
|
10
|
+
"""Initialize the HashFPs class with a graph and configuration
|
|
11
|
+
settings.
|
|
12
|
+
|
|
13
|
+
:param graph: The graph to be fingerprinted.
|
|
14
|
+
:type graph: nx.Graph
|
|
15
|
+
:param numBits: Number of bits in the output binary hash. Default is 256 bits.
|
|
16
|
+
:type numBits: int
|
|
17
|
+
:param hash_alg: The hash algorithm to use, such as 'sha256' or 'sha512'.
|
|
18
|
+
:type hash_alg: str
|
|
19
|
+
|
|
20
|
+
:raises ValueError: If `numBits` is non-positive or if `hash_alg` is not supported by hashlib.
|
|
21
|
+
"""
|
|
22
|
+
self.graph = graph
|
|
23
|
+
self.numBits = numBits
|
|
24
|
+
self.hash_alg = hash_alg
|
|
25
|
+
self.validate_parameters()
|
|
26
|
+
|
|
27
|
+
def validate_parameters(self) -> None:
|
|
28
|
+
"""Validate the initial parameters for errors."""
|
|
29
|
+
if self.numBits <= 0:
|
|
30
|
+
raise ValueError("Number of bits must be positive")
|
|
31
|
+
if not hasattr(hashlib, self.hash_alg):
|
|
32
|
+
raise ValueError(f"Unsupported hash algorithm: {self.hash_alg}")
|
|
33
|
+
|
|
34
|
+
def hash_fps(
|
|
35
|
+
self,
|
|
36
|
+
start_node: Optional[int] = None,
|
|
37
|
+
end_node: Optional[int] = None,
|
|
38
|
+
max_path_length: Optional[int] = None,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Generate a binary hash fingerprint of the graph based on its paths
|
|
41
|
+
and cycles.
|
|
42
|
+
|
|
43
|
+
:param start_node: The starting node index for path detection.
|
|
44
|
+
:type start_node: Optional[int]
|
|
45
|
+
:param end_node: The ending node index for path detection.
|
|
46
|
+
:type end_node: Optional[int]
|
|
47
|
+
:param max_path_length: The maximum length for paths to be considered.
|
|
48
|
+
:type max_path_length: Optional[int]
|
|
49
|
+
|
|
50
|
+
:return: A binary string representing the truncated hash of the graph's structural features.
|
|
51
|
+
:rtype: str
|
|
52
|
+
"""
|
|
53
|
+
hash_object = self.initialize_hash()
|
|
54
|
+
features = self.extract_features(start_node, end_node, max_path_length)
|
|
55
|
+
full_hash_binary = self.finalize_hash(hash_object, features)
|
|
56
|
+
return full_hash_binary
|
|
57
|
+
|
|
58
|
+
def initialize_hash(self) -> Any:
|
|
59
|
+
"""Initialize and return the hash object based on the specified
|
|
60
|
+
algorithm."""
|
|
61
|
+
return getattr(hashlib, self.hash_alg)()
|
|
62
|
+
|
|
63
|
+
def extract_features(
|
|
64
|
+
self,
|
|
65
|
+
start_node: Optional[int],
|
|
66
|
+
end_node: Optional[int],
|
|
67
|
+
max_path_length: Optional[int],
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Extract features from the graph based on paths and cycles.
|
|
70
|
+
|
|
71
|
+
:param start_node: The starting node for path detection.
|
|
72
|
+
:type start_node: Optional[int]
|
|
73
|
+
:param end_node: The ending node for path detection.
|
|
74
|
+
:type end_node: Optional[int]
|
|
75
|
+
:param max_path_length: Cutoff for path length during detection.
|
|
76
|
+
:type max_path_length: Optional[int]
|
|
77
|
+
|
|
78
|
+
:return: A string of concatenated feature values.
|
|
79
|
+
:rtype: str
|
|
80
|
+
"""
|
|
81
|
+
cycles = list(nx.simple_cycles(self.graph))
|
|
82
|
+
paths = []
|
|
83
|
+
if start_node is not None and end_node is not None:
|
|
84
|
+
paths = list(
|
|
85
|
+
nx.all_simple_paths(
|
|
86
|
+
self.graph,
|
|
87
|
+
source=start_node,
|
|
88
|
+
target=end_node,
|
|
89
|
+
cutoff=max_path_length,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
features = [len(c) for c in cycles] + [len(p) for p in paths]
|
|
93
|
+
return "".join(map(str, features))
|
|
94
|
+
|
|
95
|
+
def finalize_hash(self, hash_object: Any, features: str) -> str:
|
|
96
|
+
"""Finalize the hash using the features extracted and return the hash
|
|
97
|
+
as a binary string.
|
|
98
|
+
|
|
99
|
+
:param hash_object: The hash object.
|
|
100
|
+
:type hash_object: Any
|
|
101
|
+
:param features: Concatenated string of graph features.
|
|
102
|
+
:type features: str
|
|
103
|
+
|
|
104
|
+
:return: The final binary string of the hash, truncated or extended to `numBits`.
|
|
105
|
+
:rtype: str
|
|
106
|
+
"""
|
|
107
|
+
hash_object.update(features.encode())
|
|
108
|
+
full_hash_binary = bin(int(hash_object.hexdigest(), 16))[2:]
|
|
109
|
+
if len(full_hash_binary) < self.numBits:
|
|
110
|
+
full_hash_binary += self.iterative_deepening(
|
|
111
|
+
hash_object, self.numBits - len(full_hash_binary)
|
|
112
|
+
)
|
|
113
|
+
return full_hash_binary[: self.numBits]
|
|
114
|
+
|
|
115
|
+
def iterative_deepening(self, hash_object: Any, remaining_bits: int) -> str:
|
|
116
|
+
"""Extend hash length using iterative hashing until the desired bit
|
|
117
|
+
length is achieved.
|
|
118
|
+
|
|
119
|
+
:param hash_object: The hash object for iterative deepening.
|
|
120
|
+
:type hash_object: hashlib._Hash
|
|
121
|
+
:param remaining_bits: Number of bits needed to reach `numBits`.
|
|
122
|
+
:type remaining_bits: int
|
|
123
|
+
|
|
124
|
+
:return: Additional binary data to achieve the desired hash length.
|
|
125
|
+
:rtype: str
|
|
126
|
+
"""
|
|
127
|
+
additional_data = ""
|
|
128
|
+
while (
|
|
129
|
+
len(additional_data) * 4 < remaining_bits
|
|
130
|
+
): # Each hex digit represents 4 bits
|
|
131
|
+
hash_object.update(additional_data.encode())
|
|
132
|
+
additional_data += hash_object.hexdigest()
|
|
133
|
+
return bin(int(additional_data, 16))[2:][:remaining_bits]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MorganFPs:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
graph: nx.Graph,
|
|
10
|
+
radius: int = 3,
|
|
11
|
+
nBits: int = 1024,
|
|
12
|
+
hash_alg: str = "sha256",
|
|
13
|
+
):
|
|
14
|
+
"""Initialize the MorganFPs class to generate fingerprints based on the
|
|
15
|
+
Morgan algorithm, approximating Extended Connectivity Fingerprints
|
|
16
|
+
(ECFPs).
|
|
17
|
+
|
|
18
|
+
:param graph: The graph to analyze.
|
|
19
|
+
:type graph: nx.Graph
|
|
20
|
+
:param radius: The radius to consider for node neighborhood analysis.
|
|
21
|
+
:type radius: int
|
|
22
|
+
:param nBits: Total number of bits in the final fingerprint output.
|
|
23
|
+
:type nBits: int
|
|
24
|
+
:param hash_alg: Hash algorithm to use for generating hashes of node
|
|
25
|
+
neighborhoods.
|
|
26
|
+
:type hash_alg: str
|
|
27
|
+
"""
|
|
28
|
+
self.graph = graph
|
|
29
|
+
self.radius = radius
|
|
30
|
+
self.nBits = nBits
|
|
31
|
+
self.hash_alg = hash_alg
|
|
32
|
+
self.hash_function = getattr(hashlib, self.hash_alg)
|
|
33
|
+
|
|
34
|
+
def generate_fingerprint(self) -> str:
|
|
35
|
+
"""Generate a binary string fingerprint of the graph based on the local
|
|
36
|
+
environments of nodes. Ensures the output is exactly `nBits` in length
|
|
37
|
+
using iterative deepening if necessary.
|
|
38
|
+
|
|
39
|
+
:return: A binary string of length `nBits` representing the fingerprint of the graph.
|
|
40
|
+
:rtype: str
|
|
41
|
+
"""
|
|
42
|
+
fingerprint = ""
|
|
43
|
+
for node in self.graph.nodes():
|
|
44
|
+
neighborhood = nx.single_source_shortest_path_length(
|
|
45
|
+
self.graph, node, cutoff=self.radius
|
|
46
|
+
)
|
|
47
|
+
neighborhood_str = "-".join(
|
|
48
|
+
[
|
|
49
|
+
f"{nbr}-{dist}"
|
|
50
|
+
for nbr, dist in sorted(neighborhood.items())
|
|
51
|
+
if nbr != node
|
|
52
|
+
]
|
|
53
|
+
)
|
|
54
|
+
hash_obj = self.hash_function(neighborhood_str.encode())
|
|
55
|
+
node_hash = bin(int(hash_obj.hexdigest(), 16))[2:].zfill(
|
|
56
|
+
hash_obj.digest_size * 8
|
|
57
|
+
)
|
|
58
|
+
if len(fingerprint) + len(node_hash) > self.nBits:
|
|
59
|
+
needed_bits = self.nBits - len(fingerprint)
|
|
60
|
+
node_hash = node_hash[:needed_bits]
|
|
61
|
+
fingerprint += node_hash
|
|
62
|
+
if len(fingerprint) == self.nBits:
|
|
63
|
+
return fingerprint
|
|
64
|
+
|
|
65
|
+
if len(fingerprint) < self.nBits:
|
|
66
|
+
fingerprint += self.iterative_deepening(
|
|
67
|
+
hash_obj, self.nBits - len(fingerprint)
|
|
68
|
+
)
|
|
69
|
+
return fingerprint
|
|
70
|
+
|
|
71
|
+
def iterative_deepening(self, hash_object: Any, remaining_bits: int) -> str:
|
|
72
|
+
"""Extend the hash length using iterative hashing until the desired bit
|
|
73
|
+
length is achieved.
|
|
74
|
+
|
|
75
|
+
:param hash_object: The hash object used for iterative deepening.
|
|
76
|
+
:type hash_object: hashlib._Hash
|
|
77
|
+
:param remaining_bits: Number of bits needed to complete the fingerprint to
|
|
78
|
+
`nBits`.
|
|
79
|
+
:type remaining_bits: int
|
|
80
|
+
|
|
81
|
+
:return: Additional binary data to achieve the desired hash length.
|
|
82
|
+
:rtype: str
|
|
83
|
+
"""
|
|
84
|
+
additional_data = ""
|
|
85
|
+
while len(additional_data) * 4 < remaining_bits:
|
|
86
|
+
hash_object.update(additional_data.encode())
|
|
87
|
+
additional_data += hash_object.hexdigest()
|
|
88
|
+
return bin(int(additional_data, 16))[2:][:remaining_bits]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PathFPs:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
graph: nx.Graph,
|
|
10
|
+
max_length: int = 10,
|
|
11
|
+
nBits: int = 1024,
|
|
12
|
+
hash_alg: str = "sha256",
|
|
13
|
+
) -> None:
|
|
14
|
+
"""Initialize the PathFPs class to create a binary fingerprint based on
|
|
15
|
+
paths in a graph.
|
|
16
|
+
|
|
17
|
+
:param graph: Graph on which to perform analysis.
|
|
18
|
+
:type graph: nx.Graph
|
|
19
|
+
:param max_length: Limit on path lengths considered in the fingerprint.
|
|
20
|
+
:type max_length: int
|
|
21
|
+
:param nBits: Size of the binary fingerprint in bits.
|
|
22
|
+
:type nBits: int
|
|
23
|
+
:param hash_alg: Cryptographic hash function used for path hashing.
|
|
24
|
+
:type hash_alg: str
|
|
25
|
+
"""
|
|
26
|
+
self.graph = graph
|
|
27
|
+
self.max_length = max_length
|
|
28
|
+
self.nBits = nBits
|
|
29
|
+
self.hash_alg = hash_alg
|
|
30
|
+
self.hash_function = getattr(hashlib, self.hash_alg)
|
|
31
|
+
|
|
32
|
+
def generate_fingerprint(self) -> str:
|
|
33
|
+
"""Generate a binary string fingerprint of the graph by hashing paths
|
|
34
|
+
up to a certain length and combining them.
|
|
35
|
+
|
|
36
|
+
:return: A binary string of length `nBits` that represents the fingerprint of the graph.
|
|
37
|
+
:rtype: str
|
|
38
|
+
"""
|
|
39
|
+
fingerprint = ""
|
|
40
|
+
for node in self.graph.nodes():
|
|
41
|
+
for target in self.graph.nodes():
|
|
42
|
+
if node != target:
|
|
43
|
+
for path in nx.all_simple_paths(
|
|
44
|
+
self.graph, source=node, target=target, cutoff=self.max_length
|
|
45
|
+
):
|
|
46
|
+
path_str = "-".join(map(str, path))
|
|
47
|
+
hash_obj = self.hash_function(path_str.encode())
|
|
48
|
+
path_hash = bin(int(hash_obj.hexdigest(), 16))[2:].zfill(
|
|
49
|
+
hash_obj.digest_size * 8
|
|
50
|
+
)
|
|
51
|
+
if len(fingerprint) + len(path_hash) > self.nBits:
|
|
52
|
+
needed_bits = self.nBits - len(fingerprint)
|
|
53
|
+
path_hash = path_hash[:needed_bits]
|
|
54
|
+
fingerprint += path_hash
|
|
55
|
+
if len(fingerprint) == self.nBits:
|
|
56
|
+
return fingerprint
|
|
57
|
+
|
|
58
|
+
if len(fingerprint) < self.nBits:
|
|
59
|
+
fingerprint += self.iterative_deepening(
|
|
60
|
+
hash_obj, self.nBits - len(fingerprint)
|
|
61
|
+
)
|
|
62
|
+
return fingerprint
|
|
63
|
+
|
|
64
|
+
def iterative_deepening(self, hash_object: Any, remaining_bits: int) -> str:
|
|
65
|
+
"""Extend the hash length using iterative hashing until the desired bit
|
|
66
|
+
length is achieved.
|
|
67
|
+
|
|
68
|
+
:param hash_object: The hash object used for iterative deepening.
|
|
69
|
+
:type hash_object: hashlib._Hash
|
|
70
|
+
:param remaining_bits: Number of bits needed to complete the fingerprint
|
|
71
|
+
to `nBits`.
|
|
72
|
+
:type remaining_bits: int
|
|
73
|
+
|
|
74
|
+
:return: Additional binary data to achieve the desired hash length.
|
|
75
|
+
:rtype: str
|
|
76
|
+
"""
|
|
77
|
+
additional_data = ""
|
|
78
|
+
while len(additional_data) * 4 < remaining_bits:
|
|
79
|
+
hash_object.update(additional_data.encode())
|
|
80
|
+
additional_data += hash_object.hexdigest()
|
|
81
|
+
return bin(int(additional_data, 16))[2:][:remaining_bits]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import networkx as nx
|
|
3
|
+
from typing import List, Dict, Union, Tuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WLHash:
|
|
7
|
+
"""A class that implements the Weisfeiler-Lehman graph hashing algorithm,
|
|
8
|
+
supporting multiple node/edge attributes for hashing.
|
|
9
|
+
|
|
10
|
+
:ivar node: A single attribute name or a list of attribute names for nodes used in hashing.
|
|
11
|
+
:ivar edge: A single attribute name or a list of attribute names for edges used in hashing.
|
|
12
|
+
:ivar iterations: Number of iterations for the Weisfeiler-Lehman algorithm.
|
|
13
|
+
:ivar digest_size: Length of the hash to be generated.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
node: Union[str, List[str]] = ["element", "charge"],
|
|
19
|
+
edge: Union[str, List[str]] = "order",
|
|
20
|
+
iterations: int = 5,
|
|
21
|
+
digest_size: int = 16,
|
|
22
|
+
):
|
|
23
|
+
"""Initializes the WLHash class with configuration for hashing.
|
|
24
|
+
|
|
25
|
+
:param node: A node attribute name or list of node attribute names.
|
|
26
|
+
:param edge: An edge attribute name or list of edge attribute names.
|
|
27
|
+
:param iterations: The number of WL iterations (default 5).
|
|
28
|
+
:param digest_size: The length of the generated hash (default 16).
|
|
29
|
+
"""
|
|
30
|
+
self.node = node
|
|
31
|
+
self.edge = edge
|
|
32
|
+
self.iterations = iterations
|
|
33
|
+
self.digest_size = digest_size
|
|
34
|
+
|
|
35
|
+
def _prepare_graph(
|
|
36
|
+
self, graph: nx.Graph
|
|
37
|
+
) -> Tuple[nx.Graph, Union[str, None], Union[str, None]]:
|
|
38
|
+
"""Prepare a deep copy of the graph with combined/missing node and edge
|
|
39
|
+
attributes.
|
|
40
|
+
|
|
41
|
+
Returns (H, node_attr_name, edge_attr_name).
|
|
42
|
+
"""
|
|
43
|
+
# Deep-copy to avoid mutating original graph
|
|
44
|
+
H = copy.deepcopy(graph)
|
|
45
|
+
|
|
46
|
+
# --- NODE ATTRIBUTE HANDLING ---
|
|
47
|
+
if isinstance(self.node, (list, tuple)) and len(self.node) > 1:
|
|
48
|
+
combined_node_attr = "_wl_hash_node_attr"
|
|
49
|
+
for n, data in H.nodes(data=True):
|
|
50
|
+
# Combine each attribute's string value (default empty)
|
|
51
|
+
vals = [str(data.get(attr, "")) for attr in self.node]
|
|
52
|
+
data[combined_node_attr] = "|".join(vals)
|
|
53
|
+
node_attr_name = combined_node_attr
|
|
54
|
+
else:
|
|
55
|
+
node_attr_name = (
|
|
56
|
+
self.node
|
|
57
|
+
if isinstance(self.node, str)
|
|
58
|
+
else (self.node[0] if self.node else None)
|
|
59
|
+
)
|
|
60
|
+
# Ensure missing attributes default to empty string
|
|
61
|
+
if node_attr_name:
|
|
62
|
+
for _, data in H.nodes(data=True):
|
|
63
|
+
data.setdefault(node_attr_name, "")
|
|
64
|
+
|
|
65
|
+
# --- EDGE ATTRIBUTE HANDLING ---
|
|
66
|
+
if isinstance(self.edge, (list, tuple)) and len(self.edge) > 1:
|
|
67
|
+
combined_edge_attr = "_wl_hash_edge_attr"
|
|
68
|
+
for u, v, data in H.edges(data=True):
|
|
69
|
+
vals = [str(data.get(attr, "")) for attr in self.edge]
|
|
70
|
+
data[combined_edge_attr] = "|".join(vals)
|
|
71
|
+
edge_attr_name = combined_edge_attr
|
|
72
|
+
else:
|
|
73
|
+
edge_attr_name = (
|
|
74
|
+
self.edge
|
|
75
|
+
if isinstance(self.edge, str)
|
|
76
|
+
else (self.edge[0] if self.edge else None)
|
|
77
|
+
)
|
|
78
|
+
if edge_attr_name:
|
|
79
|
+
for _, _, data in H.edges(data=True):
|
|
80
|
+
data.setdefault(edge_attr_name, "")
|
|
81
|
+
|
|
82
|
+
return H, node_attr_name, edge_attr_name
|
|
83
|
+
|
|
84
|
+
def weisfeiler_lehman_graph_hash(self, graph: nx.Graph) -> str:
|
|
85
|
+
"""Computes the WL graph hash for the entire graph."""
|
|
86
|
+
G, node_attr, edge_attr = self._prepare_graph(graph)
|
|
87
|
+
return nx.weisfeiler_lehman_graph_hash(
|
|
88
|
+
G,
|
|
89
|
+
node_attr=node_attr,
|
|
90
|
+
edge_attr=edge_attr,
|
|
91
|
+
iterations=self.iterations,
|
|
92
|
+
digest_size=self.digest_size,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def weisfeiler_lehman_subgraph_hashes(
|
|
96
|
+
self, graph: nx.Graph
|
|
97
|
+
) -> Dict[Union[int, str], List[str]]:
|
|
98
|
+
"""Computes the WL subgraph hashes for each node in the graph."""
|
|
99
|
+
G, node_attr, edge_attr = self._prepare_graph(graph)
|
|
100
|
+
return nx.weisfeiler_lehman_subgraph_hashes(
|
|
101
|
+
G,
|
|
102
|
+
node_attr=node_attr,
|
|
103
|
+
edge_attr=edge_attr,
|
|
104
|
+
iterations=self.iterations,
|
|
105
|
+
digest_size=self.digest_size,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def process_data(
|
|
109
|
+
self,
|
|
110
|
+
data: List[Dict[str, Union[str, nx.Graph]]],
|
|
111
|
+
graph_key: str = "ITS",
|
|
112
|
+
subgraph: bool = False,
|
|
113
|
+
) -> List[Dict[str, Union[str, None]]]:
|
|
114
|
+
"""Applies WL hashing (or subgraph hashing) to a list of data entries.
|
|
115
|
+
|
|
116
|
+
Each entry must contain a graph under 'graph_key'.
|
|
117
|
+
"""
|
|
118
|
+
for entry in data:
|
|
119
|
+
if graph_key in entry and isinstance(entry[graph_key], nx.Graph):
|
|
120
|
+
graph = entry[graph_key]
|
|
121
|
+
try:
|
|
122
|
+
if subgraph:
|
|
123
|
+
entry["WL"] = self.weisfeiler_lehman_subgraph_hashes(graph)
|
|
124
|
+
else:
|
|
125
|
+
entry["WL"] = self.weisfeiler_lehman_graph_hash(graph)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"Error processing graph {entry.get('name', 'Unnamed')}: {e}")
|
|
128
|
+
entry["WL"] = None
|
|
129
|
+
else:
|
|
130
|
+
print(
|
|
131
|
+
f"Missing or invalid '{graph_key}' for graph in data: {entry.get('name', 'Unnamed')}"
|
|
132
|
+
)
|
|
133
|
+
entry["WL"] = None
|
|
134
|
+
return data
|