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,531 @@
|
|
|
1
|
+
"""
|
|
2
|
+
canonicalize_graph.py
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
A **pure‑Python / pure‑NetworkX** library that assigns *canonical*,
|
|
6
|
+
deterministic identifiers to graphs **without any external toolchain**.
|
|
7
|
+
It can be dropped into cheminformatics, bio‑networks, knowledge graphs,
|
|
8
|
+
or any workflow that needs stable de‑duplication of isomorphic graphs.
|
|
9
|
+
|
|
10
|
+
Why canonicalise?
|
|
11
|
+
-----------------
|
|
12
|
+
* **De‑duplication** – hash the canonical form, then compare hashes
|
|
13
|
+
instead of running an expensive isomorphism test for every pair.
|
|
14
|
+
* **Index keys** – store the 32‑hex digest in a database and use it as a
|
|
15
|
+
primary key for sub‑structure search or provenance tracking.
|
|
16
|
+
* **Version control‑friendly** – serialise nodes/edges in a predictable,
|
|
17
|
+
line‑ordered way so diffs stay minimal.
|
|
18
|
+
|
|
19
|
+
Two back‑ends
|
|
20
|
+
~~~~~~~~~~~~~
|
|
21
|
+
``backend="generic"`` (default)
|
|
22
|
+
*Sort‑and‑hash* strategy identical to earlier releases. Fastest on
|
|
23
|
+
graphs where node/edge attributes already break most automorphisms.
|
|
24
|
+
``backend="wl"``
|
|
25
|
+
Weisfeiler–Lehman colour‑refinement adds **structure awareness**
|
|
26
|
+
without leaving pure Python. Slightly slower but collapses many more
|
|
27
|
+
isomorphic graphs to the same label.
|
|
28
|
+
|
|
29
|
+
Quick start
|
|
30
|
+
-----------
|
|
31
|
+
>>> import networkx as nx
|
|
32
|
+
>>> from canonicalize_graph import GraphCanonicaliser
|
|
33
|
+
>>>
|
|
34
|
+
>>> G = nx.Graph()
|
|
35
|
+
>>> G.add_node(1, element="C"); G.add_node(2, element="O")
|
|
36
|
+
>>> G.add_edge(1, 2, order=1)
|
|
37
|
+
>>>
|
|
38
|
+
>>> cg = GraphCanonicaliser(backend="wl").canonicalise_graph(G)
|
|
39
|
+
>>> cg.canonical_hash
|
|
40
|
+
'0df9e34a7c3cd9b35c0ba5f5cbe7598e'
|
|
41
|
+
>>> cg.canonical_graph.nodes(data=True)
|
|
42
|
+
[(1, {'element': 'C'}), (2, {'element': 'O'})]
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import hashlib
|
|
48
|
+
import inspect
|
|
49
|
+
from typing import (
|
|
50
|
+
Any,
|
|
51
|
+
Callable,
|
|
52
|
+
Dict,
|
|
53
|
+
Hashable,
|
|
54
|
+
Iterable,
|
|
55
|
+
List,
|
|
56
|
+
Literal,
|
|
57
|
+
Tuple,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
from synkit.IO.chem_converter import gml_to_its, its_to_gml
|
|
61
|
+
import networkx as nx
|
|
62
|
+
from networkx.algorithms.graph_hashing import (
|
|
63
|
+
weisfeiler_lehman_subgraph_hashes as _wl_hashes,
|
|
64
|
+
)
|
|
65
|
+
from .canon_algs import canon_morgan
|
|
66
|
+
from .nauty import NautyCanonicalizer
|
|
67
|
+
|
|
68
|
+
__all__: list[str] = ["CanonicalGraph", "GraphCanonicaliser", "CanonicalRule"]
|
|
69
|
+
|
|
70
|
+
###############################################################################
|
|
71
|
+
# Type aliases & helpers ######################################################
|
|
72
|
+
###############################################################################
|
|
73
|
+
|
|
74
|
+
NodeId = Hashable # a node label accepted by NetworkX
|
|
75
|
+
NodeData = Dict[str, Any]
|
|
76
|
+
EdgeData = Dict[str, Any]
|
|
77
|
+
Digest = str
|
|
78
|
+
|
|
79
|
+
T_NodeSortKey = Callable[[NodeId, NodeData], Tuple[Any, ...]]
|
|
80
|
+
T_EdgeSortKey = Callable[[NodeId, NodeId, EdgeData], Tuple[Any, ...]]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _default_node_key(node_id: NodeId, data: NodeData) -> Tuple[Any, ...]:
|
|
84
|
+
"""Fallback sort‑key if the user supplies none."""
|
|
85
|
+
return (
|
|
86
|
+
data.get("element", ""),
|
|
87
|
+
data.get("charge", 0),
|
|
88
|
+
data.get("lone_pairs", 0),
|
|
89
|
+
data.get("radical", 0),
|
|
90
|
+
data.get("aromatic", False),
|
|
91
|
+
# data.get("atom_map", 0),
|
|
92
|
+
data.get("hcount", 0),
|
|
93
|
+
# data.get("neighbors", []),
|
|
94
|
+
# tuple(data.get("typesGH", ())),
|
|
95
|
+
# node_id, # final tie‑breaker
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _default_edge_key(u: NodeId, v: NodeId, data: EdgeData) -> Tuple[Any, ...]:
|
|
100
|
+
"""Fallback edge sort‑key (makes undirected edges order‑invariant)."""
|
|
101
|
+
return (tuple(sorted((u, v))), data.get("order", 0), data.get("standard_order", 0))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _digest(text: str) -> Digest:
|
|
105
|
+
"""First 32 hex chars of SHA‑256 – short *but* collision‑safe for up to
|
|
106
|
+
2¹²⁸ graphs."""
|
|
107
|
+
return hashlib.sha256(text.encode()).hexdigest()[:32]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
###############################################################################
|
|
111
|
+
# Public API ##################################################################
|
|
112
|
+
###############################################################################
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class GraphCanonicaliser:
|
|
116
|
+
"""Factory that turns arbitrary ``networkx.Graph`` objects into their
|
|
117
|
+
*canonical* twin plus a **stable 32‑hex digest**.
|
|
118
|
+
|
|
119
|
+
:param backend: ``"generic"`` or ``"wl"`` (structure‑aware Weisfeiler–Lehman).
|
|
120
|
+
:param wl_iterations: Depth of WL refinement (ignored for ``generic``). Three iterations
|
|
121
|
+
distinguish nearly all real‑world chemical graphs; increase for
|
|
122
|
+
very large or highly regular topologies.
|
|
123
|
+
:param node_sort_key: Custom deterministic orderings. They *must* treat their arguments
|
|
124
|
+
as *read‑only* and return plain tuples for total ordering.
|
|
125
|
+
:param edge_sort_key: Custom deterministic orderings. They *must* treat their arguments
|
|
126
|
+
as *read‑only* and return plain tuples for total ordering.
|
|
127
|
+
|
|
128
|
+
.. rubric:: Notes
|
|
129
|
+
|
|
130
|
+
*All* returned graphs are of the *same class* as the input
|
|
131
|
+
(``nx.Graph``, ``nx.DiGraph`` …), so multigraphs and digraphs are
|
|
132
|
+
preserved.
|
|
133
|
+
|
|
134
|
+
.. rubric:: Examples
|
|
135
|
+
|
|
136
|
+
>>> canon = GraphCanonicaliser(backend="generic")
|
|
137
|
+
>>> sig = canon.canonical_signature(G)
|
|
138
|
+
>>> cg = canon.canonicalise_graph(G)
|
|
139
|
+
>>> cg.canonical_graph # a relabelled copy, nodes 1…N
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------------------------ #
|
|
143
|
+
# Construction #
|
|
144
|
+
# ------------------------------------------------------------------ #
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
*,
|
|
148
|
+
backend: Literal["generic", "wl", "morgan", "nauty"] = "generic",
|
|
149
|
+
wl_iterations: int = 3,
|
|
150
|
+
morgan_radius: int = 3,
|
|
151
|
+
node_attrs: List[str] = [
|
|
152
|
+
"element",
|
|
153
|
+
"aromatic",
|
|
154
|
+
"charge",
|
|
155
|
+
"lone_pairs",
|
|
156
|
+
"radical",
|
|
157
|
+
"hcount",
|
|
158
|
+
],
|
|
159
|
+
node_sort_key: T_NodeSortKey = _default_node_key,
|
|
160
|
+
edge_sort_key: T_EdgeSortKey = _default_edge_key,
|
|
161
|
+
) -> None:
|
|
162
|
+
if backend not in {"generic", "wl", "morgan", "nauty"}:
|
|
163
|
+
raise ValueError("backend must be 'generic' or 'wl' or 'morgan' ")
|
|
164
|
+
self.backend: Literal["generic", "wl", "morgan"] = backend
|
|
165
|
+
self._wl_k: int = wl_iterations
|
|
166
|
+
self._mg_k: int = morgan_radius
|
|
167
|
+
self._node_key: T_NodeSortKey = node_sort_key
|
|
168
|
+
self._edge_key: T_EdgeSortKey = edge_sort_key
|
|
169
|
+
self._wl_node_attrs: Tuple[str, ...] = tuple(node_attrs)
|
|
170
|
+
self._mg_node_attrs: List[str] = node_attrs
|
|
171
|
+
if self.backend == "nauty":
|
|
172
|
+
self.nauty = NautyCanonicalizer(node_attrs=node_attrs, edge_attrs=["order"])
|
|
173
|
+
|
|
174
|
+
# ------------------------------------------------------------------ #
|
|
175
|
+
# High‑level helpers #
|
|
176
|
+
# ------------------------------------------------------------------ #
|
|
177
|
+
def canonicalise_graph(self, graph: nx.Graph) -> "CanonicalGraph":
|
|
178
|
+
"""Return a :class:`CanonicalGraph` wrapper around *graph*.
|
|
179
|
+
|
|
180
|
+
The wrapper exposes:
|
|
181
|
+
|
|
182
|
+
* :attr:`~CanonicalGraph.canonical_graph` – relabelled 1…N
|
|
183
|
+
* :attr:`~CanonicalGraph.canonical_hash` – 32‑char digest
|
|
184
|
+
"""
|
|
185
|
+
return CanonicalGraph(graph, self)
|
|
186
|
+
|
|
187
|
+
def canonicalise_graphs(
|
|
188
|
+
self,
|
|
189
|
+
graphs: Iterable[nx.Graph],
|
|
190
|
+
) -> Tuple["CanonicalGraph", ...]:
|
|
191
|
+
"""Bulk helper that returns *all* wrappers **sorted by hash**.
|
|
192
|
+
|
|
193
|
+
Useful when you want fast set comparison but need the canonical
|
|
194
|
+
graphs as well:
|
|
195
|
+
|
|
196
|
+
>>> wrappers = canon.canonicalise_graphs([G1, G2, G3])
|
|
197
|
+
>>> unique = {w.canonical_hash for w in wrappers}
|
|
198
|
+
"""
|
|
199
|
+
return tuple(
|
|
200
|
+
sorted(
|
|
201
|
+
(self.canonicalise_graph(g) for g in graphs),
|
|
202
|
+
key=lambda x: x.canonical_hash,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# ------------------------------------------------------------------ #
|
|
207
|
+
# Digest / core methods #
|
|
208
|
+
# ------------------------------------------------------------------ #
|
|
209
|
+
def canonical_signature(self, graph: nx.Graph) -> Digest:
|
|
210
|
+
"""Return the *hash of the canonical form* of *graph*.
|
|
211
|
+
|
|
212
|
+
Equal digests ⇒ graphs are guaranteed isomorphic **under the
|
|
213
|
+
chosen back‑end and keys**.
|
|
214
|
+
"""
|
|
215
|
+
return _digest(self._serialise(self._make_canonical_graph(graph)))
|
|
216
|
+
|
|
217
|
+
# Alias kept for backward‑compatibility
|
|
218
|
+
graph_canonical_hash: Callable[[nx.Graph], Digest] = canonical_signature
|
|
219
|
+
|
|
220
|
+
# ------------------------------------------------------------------ #
|
|
221
|
+
# Internal – make canonical graph #
|
|
222
|
+
# ------------------------------------------------------------------ #
|
|
223
|
+
def _make_canonical_graph(self, g: nx.Graph) -> nx.Graph:
|
|
224
|
+
"""Dispatcher that calls the appropriate back‑end."""
|
|
225
|
+
if self.backend == "generic":
|
|
226
|
+
return self._canon_generic(g)
|
|
227
|
+
elif self.backend == "wl":
|
|
228
|
+
return self._canon_wl(g)
|
|
229
|
+
elif self.backend == "nauty":
|
|
230
|
+
return self._canon_nauty(g)
|
|
231
|
+
else:
|
|
232
|
+
return canon_morgan(
|
|
233
|
+
g, morgan_radius=self._mg_k, node_attributes=self._mg_node_attrs
|
|
234
|
+
)
|
|
235
|
+
# return (
|
|
236
|
+
# self._canon_generic(g) if self.backend == "generic" else self._canon_wl(g)
|
|
237
|
+
# )
|
|
238
|
+
|
|
239
|
+
def _canon_nauty(self, g: nx.Graph) -> nx.Graph:
|
|
240
|
+
return self.nauty.canonical_form(g)
|
|
241
|
+
|
|
242
|
+
def _canon_generic(self, g: nx.Graph) -> nx.Graph:
|
|
243
|
+
"""Pure attribute-sort strategy with ``O(V log V + E log E)`` cost."""
|
|
244
|
+
nodes_sorted = sorted(g.nodes(data=True), key=lambda x: self._node_key(*x))
|
|
245
|
+
mapping: Dict[NodeId, int] = {
|
|
246
|
+
old: i + 1 for i, (old, _) in enumerate(nodes_sorted)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
G2 = type(g)()
|
|
250
|
+
for old, data in nodes_sorted:
|
|
251
|
+
G2.add_node(mapping[old], **data)
|
|
252
|
+
|
|
253
|
+
edges_sorted = sorted(g.edges(data=True), key=lambda x: self._edge_key(*x))
|
|
254
|
+
for u, v, data in edges_sorted:
|
|
255
|
+
G2.add_edge(mapping[u], mapping[v], **data)
|
|
256
|
+
|
|
257
|
+
return G2
|
|
258
|
+
|
|
259
|
+
def _canon_wl(self, g: nx.Graph) -> nx.Graph:
|
|
260
|
+
"""Weisfeiler–Lehman colour-refinement back-end (pure Python).
|
|
261
|
+
|
|
262
|
+
Seeds each node’s initial colour by the tuple of attributes
|
|
263
|
+
in `self._wl_node_attrs` (e.g. ["element","charge","hcount"]),
|
|
264
|
+
then runs k iterations of WL and orders nodes by
|
|
265
|
+
(final_colour, degree, original_id).
|
|
266
|
+
|
|
267
|
+
Returns a new graph of the same type as `g`, with nodes relabelled
|
|
268
|
+
1…N in that order and all original node & edge attributes preserved.
|
|
269
|
+
"""
|
|
270
|
+
# 1) Prepare a working copy if we need composite node labels
|
|
271
|
+
if self._wl_node_attrs:
|
|
272
|
+
g2 = g.copy()
|
|
273
|
+
for n, data in g2.nodes(data=True):
|
|
274
|
+
# build the initial label tuple
|
|
275
|
+
data["_wl_init"] = tuple(
|
|
276
|
+
data.get(attr, "") for attr in self._wl_node_attrs
|
|
277
|
+
)
|
|
278
|
+
node_attr = "_wl_init"
|
|
279
|
+
else:
|
|
280
|
+
g2 = g
|
|
281
|
+
node_attr = "element"
|
|
282
|
+
|
|
283
|
+
# 2) Run WL hashing
|
|
284
|
+
wl_hash = _wl_hashes(
|
|
285
|
+
g2,
|
|
286
|
+
node_attr=node_attr,
|
|
287
|
+
edge_attr="order",
|
|
288
|
+
iterations=self._wl_k,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# 3) Extract final hash (last iteration) as the node colour
|
|
292
|
+
colour: Dict[NodeId, str] = {n: hashes[-1] for n, hashes in wl_hash.items()}
|
|
293
|
+
|
|
294
|
+
# 4) Determine node ordering
|
|
295
|
+
order: List[NodeId] = sorted(g, key=lambda n: (colour[n], g.degree[n], n))
|
|
296
|
+
mapping: Dict[NodeId, int] = {old: i + 1 for i, old in enumerate(order)}
|
|
297
|
+
|
|
298
|
+
# 5) Build the new graph
|
|
299
|
+
G2 = type(g)()
|
|
300
|
+
# copy any graph‐level attributes
|
|
301
|
+
if hasattr(g, "graph"):
|
|
302
|
+
G2.graph.update(g.graph)
|
|
303
|
+
|
|
304
|
+
# add nodes in canonical order
|
|
305
|
+
for old in order:
|
|
306
|
+
G2.add_node(mapping[old], **g.nodes[old])
|
|
307
|
+
|
|
308
|
+
# add edges, sorted for consistency
|
|
309
|
+
for u, v, e_attrs in sorted(
|
|
310
|
+
g.edges(data=True),
|
|
311
|
+
key=lambda e: tuple(sorted((mapping[e[0]], mapping[e[1]]))),
|
|
312
|
+
):
|
|
313
|
+
G2.add_edge(mapping[u], mapping[v], **e_attrs)
|
|
314
|
+
|
|
315
|
+
return G2
|
|
316
|
+
|
|
317
|
+
# ------------------------------------------------------------------ #
|
|
318
|
+
# Internal – serialisation #
|
|
319
|
+
# ------------------------------------------------------------------ #
|
|
320
|
+
def _serialise(self, g: nx.Graph) -> str:
|
|
321
|
+
"""Stable plain‑text representation → fed into SHA‑256."""
|
|
322
|
+
nodes = sorted(g.nodes(data=True), key=lambda x: self._node_key(*x))
|
|
323
|
+
edges = sorted(g.edges(data=True), key=lambda x: self._edge_key(*x))
|
|
324
|
+
|
|
325
|
+
node_str = ";".join(f"{n}:{self._node_key(n, d)}" for n, d in nodes)
|
|
326
|
+
edge_str = ";".join(f"{(u, v)}:{self._edge_key(u, v, d)}" for u, v, d in edges)
|
|
327
|
+
return f"N[{node_str}]|E[{edge_str}]"
|
|
328
|
+
|
|
329
|
+
# ------------------------------------------------------------------ #
|
|
330
|
+
# Misc utilities #
|
|
331
|
+
# ------------------------------------------------------------------ #
|
|
332
|
+
def help(self) -> None: # pragma: no cover
|
|
333
|
+
"""Pretty‑print the public methods with their signatures."""
|
|
334
|
+
print(inspect.getdoc(self))
|
|
335
|
+
for meth in (
|
|
336
|
+
"canonical_signature",
|
|
337
|
+
"make_canonical_graph",
|
|
338
|
+
"canonicalise_graph",
|
|
339
|
+
):
|
|
340
|
+
print(f" • {meth}{inspect.signature(getattr(self, meth))}")
|
|
341
|
+
|
|
342
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
343
|
+
return (
|
|
344
|
+
f"<GraphCanonicaliser backend={self.backend!r} "
|
|
345
|
+
f"node_key={self._node_key.__name__} "
|
|
346
|
+
f"edge_key={self._edge_key.__name__}>"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# keep public attribute for backwards compatibility
|
|
350
|
+
make_canonical_graph = _make_canonical_graph
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# =============================================================================
|
|
354
|
+
# Value wrapper (unchanged surface – richer docs)
|
|
355
|
+
# =============================================================================
|
|
356
|
+
class CanonicalGraph:
|
|
357
|
+
"""*Value object* tying together:
|
|
358
|
+
|
|
359
|
+
* the **original** NetworkX graph (mutable, user‑supplied);
|
|
360
|
+
* its **canonical twin** (immutable copy, nodes relabelled 1…N);
|
|
361
|
+
* a 32‑char **SHA‑256 digest**.
|
|
362
|
+
|
|
363
|
+
Instances compare & hash **by digest only** – perfect for set/dict
|
|
364
|
+
membership while still carrying the underlying graphs.
|
|
365
|
+
|
|
366
|
+
Mutating :attr:`original_graph` invalidates :attr:`canonical_hash`;
|
|
367
|
+
repeat canonicalisation after structural changes.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
def __init__(self, g: nx.Graph, canon: GraphCanonicaliser) -> None:
|
|
371
|
+
self._original: nx.Graph = g
|
|
372
|
+
self._canonical_graph: nx.Graph = canon._make_canonical_graph(g)
|
|
373
|
+
self._canonical_hash: Digest = canon.canonical_signature(self._canonical_graph)
|
|
374
|
+
|
|
375
|
+
# ------------------------------------------------------------------ #
|
|
376
|
+
# Dunder sugar #
|
|
377
|
+
# ------------------------------------------------------------------ #
|
|
378
|
+
def __eq__(self, other: object) -> bool:
|
|
379
|
+
return (
|
|
380
|
+
isinstance(other, CanonicalGraph)
|
|
381
|
+
and self.canonical_hash == other.canonical_hash
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def __hash__(self) -> int:
|
|
385
|
+
return hash(self.canonical_hash)
|
|
386
|
+
|
|
387
|
+
def __str__(self) -> str: # pragma: no cover
|
|
388
|
+
return (
|
|
389
|
+
f"<CanonicalGraph |V|={self._canonical_graph.number_of_nodes()} "
|
|
390
|
+
f"|E|={self._canonical_graph.number_of_edges()} "
|
|
391
|
+
f"hash={self.canonical_hash[:8]}>"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
__repr__ = __str__
|
|
395
|
+
|
|
396
|
+
# ------------------------------------------------------------------ #
|
|
397
|
+
# Public read‑only views #
|
|
398
|
+
# ------------------------------------------------------------------ #
|
|
399
|
+
@property
|
|
400
|
+
def original_graph(self) -> nx.Graph:
|
|
401
|
+
"""Direct mutable reference to the caller-provided graph."""
|
|
402
|
+
return self._original
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def canonical_graph(self) -> nx.Graph:
|
|
406
|
+
"""Immutable relabelled copy with nodes numbered from 1 through V."""
|
|
407
|
+
return self._canonical_graph
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def canonical_hash(self) -> Digest:
|
|
411
|
+
"""32‑hex‑char SHA‑256 digest (*lower‑case*, deterministic)."""
|
|
412
|
+
return self._canonical_hash
|
|
413
|
+
|
|
414
|
+
# ------------------------------------------------------------------ #
|
|
415
|
+
# Convenience debug helper #
|
|
416
|
+
# ------------------------------------------------------------------ #
|
|
417
|
+
def help(self) -> None: # pragma: no cover
|
|
418
|
+
"""Print both graphs’ node/edge tables – handy for debugging."""
|
|
419
|
+
print("Original graph:")
|
|
420
|
+
for n, d in self._original.nodes(data=True):
|
|
421
|
+
print(f" {n}: {d}")
|
|
422
|
+
print("\nCanonical graph:")
|
|
423
|
+
for n, d in self._canonical_graph.nodes(data=True):
|
|
424
|
+
print(f" {n}: {d}")
|
|
425
|
+
print("\nEdges:")
|
|
426
|
+
for u, v, d in self._canonical_graph.edges(data=True):
|
|
427
|
+
print(f" ({u},{v}): {d}")
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class CanonicalRule:
|
|
431
|
+
"""Value object that wraps a graph transformation rule in GML string form,
|
|
432
|
+
providing a canonicalised GML output and a stable 32-character SHA-256
|
|
433
|
+
hash.
|
|
434
|
+
|
|
435
|
+
Internally, the GML rule is parsed into a NetworkX graph via `gml_to_its`,
|
|
436
|
+
canonicalised using a `GraphCanonicaliser`, and re-serialized back to GML
|
|
437
|
+
with `its_to_gml`.
|
|
438
|
+
|
|
439
|
+
Equality and hashing are based solely on the canonical hash, so
|
|
440
|
+
isomorphic rules (under the chosen backend) compare equal.
|
|
441
|
+
|
|
442
|
+
:ivar original_rule: The raw GML string supplied by the user.
|
|
443
|
+
:vartype original_rule: str
|
|
444
|
+
:ivar original_graph: The NetworkX graph parsed from `original_rule`.
|
|
445
|
+
:vartype original_graph: nx.Graph
|
|
446
|
+
:ivar canonical_graph: The relabelled canonical graph (nodes renumbered 1…N).
|
|
447
|
+
:vartype canonical_graph: nx.Graph
|
|
448
|
+
:ivar canonical_rule: The canonical graph re-serialized to a GML string.
|
|
449
|
+
:vartype canonical_rule: str
|
|
450
|
+
:ivar canonical_hash: 32-hex-character SHA-256 digest of the canonical graph.
|
|
451
|
+
:vartype canonical_hash: Digest
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
def __init__(
|
|
455
|
+
self,
|
|
456
|
+
rule: str,
|
|
457
|
+
canon: GraphCanonicaliser = GraphCanonicaliser(),
|
|
458
|
+
) -> None:
|
|
459
|
+
"""Instantiate a CanonicalRule.
|
|
460
|
+
|
|
461
|
+
:param rule: GML string of the transformation rule.
|
|
462
|
+
:type rule: str
|
|
463
|
+
:param canon: Initialized canonicaliser (generic or WL backend).
|
|
464
|
+
:type canon: GraphCanonicaliser
|
|
465
|
+
"""
|
|
466
|
+
# Store raw inputs
|
|
467
|
+
self._original_rule: str = rule
|
|
468
|
+
# Parse into NetworkX
|
|
469
|
+
self._original_graph: nx.Graph = gml_to_its(rule)
|
|
470
|
+
# Canonicalise graph
|
|
471
|
+
self._canonical_graph: nx.Graph = canon._make_canonical_graph(
|
|
472
|
+
self._original_graph
|
|
473
|
+
)
|
|
474
|
+
# Serialize back to GML
|
|
475
|
+
self._canonical_rule: str = its_to_gml(self._canonical_graph)
|
|
476
|
+
# Compute hash
|
|
477
|
+
self._canonical_hash: Digest = canon.canonical_signature(self._canonical_graph)
|
|
478
|
+
|
|
479
|
+
def __eq__(self, other: object) -> bool:
|
|
480
|
+
return (
|
|
481
|
+
isinstance(other, CanonicalRule)
|
|
482
|
+
and self.canonical_hash == other.canonical_hash
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def __hash__(self) -> int:
|
|
486
|
+
return hash(self._canonical_hash)
|
|
487
|
+
|
|
488
|
+
def __str__(self) -> str:
|
|
489
|
+
n = self._canonical_graph.number_of_nodes()
|
|
490
|
+
m = self._canonical_graph.number_of_edges()
|
|
491
|
+
return f"<CanonicalRule |V|={n} |E|={m} hash={self._canonical_hash[:8]}>"
|
|
492
|
+
|
|
493
|
+
__repr__ = __str__
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def original_rule(self) -> str:
|
|
497
|
+
"""Original GML rule string."""
|
|
498
|
+
return self._original_rule
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def original_graph(self) -> nx.Graph:
|
|
502
|
+
"""Parsed NetworkX graph from `original_rule`."""
|
|
503
|
+
return self._original_graph
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def canonical_graph(self) -> nx.Graph:
|
|
507
|
+
"""Relabelled canonical NetworkX graph (nodes 1…N)."""
|
|
508
|
+
return self._canonical_graph
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def canonical_rule(self) -> str:
|
|
512
|
+
"""GML string of the canonical graph."""
|
|
513
|
+
return self._canonical_rule
|
|
514
|
+
|
|
515
|
+
@property
|
|
516
|
+
def canonical_hash(self) -> Digest:
|
|
517
|
+
"""32-hex-character SHA-256 digest of the canonical graph."""
|
|
518
|
+
return self._canonical_hash
|
|
519
|
+
|
|
520
|
+
def help(self) -> None:
|
|
521
|
+
"""Print original and canonical rule texts and underlying graphs."""
|
|
522
|
+
print("Original GML rule:")
|
|
523
|
+
print(self._original_rule)
|
|
524
|
+
print("\nCanonical GML rule:")
|
|
525
|
+
print(self._canonical_rule)
|
|
526
|
+
print("\nCanonical graph nodes and data:")
|
|
527
|
+
for n, d in self._canonical_graph.nodes(data=True):
|
|
528
|
+
print(f" {n}: {d}")
|
|
529
|
+
print("\nCanonical graph edges and data:")
|
|
530
|
+
for u, v, d in self._canonical_graph.edges(data=True):
|
|
531
|
+
print(f" ({u},{v}): {d}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.16)
|
|
2
|
+
project(synkit_canon VERSION 1.6.5 LANGUAGES CXX)
|
|
3
|
+
add_library(synkit_canon SHARED src/canonical.cpp)
|
|
4
|
+
target_compile_definitions(synkit_canon PRIVATE SYNKIT_CANON_BUILD)
|
|
5
|
+
target_compile_features(synkit_canon PUBLIC cxx_std_17)
|
|
6
|
+
target_include_directories(synkit_canon PUBLIC
|
|
7
|
+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
8
|
+
$<INSTALL_INTERFACE:include>)
|
|
9
|
+
set_target_properties(synkit_canon PROPERTIES CXX_EXTENSIONS OFF)
|
|
10
|
+
if(MSVC)
|
|
11
|
+
target_compile_options(synkit_canon PRIVATE /W4)
|
|
12
|
+
else()
|
|
13
|
+
target_compile_options(synkit_canon PRIVATE -Wall -Wextra -Wpedantic)
|
|
14
|
+
endif()
|
|
15
|
+
option(SYNKIT_CANON_BUILD_EXAMPLES "Build standalone graph example" OFF)
|
|
16
|
+
if(SYNKIT_CANON_BUILD_EXAMPLES)
|
|
17
|
+
add_executable(canonical_graph examples/canonical_graph.cpp)
|
|
18
|
+
target_link_libraries(canonical_graph PRIVATE synkit_canon)
|
|
19
|
+
endif()
|
|
20
|
+
option(SYNKIT_CANON_BUILD_TESTS "Build independent native symmetry checks" OFF)
|
|
21
|
+
if(SYNKIT_CANON_BUILD_TESTS)
|
|
22
|
+
enable_testing()
|
|
23
|
+
add_executable(canonical_symmetry_tests tests/symmetry.cpp)
|
|
24
|
+
target_compile_features(canonical_symmetry_tests PRIVATE cxx_std_17)
|
|
25
|
+
target_include_directories(canonical_symmetry_tests PRIVATE src)
|
|
26
|
+
add_test(NAME canonical_symmetry COMMAND canonical_symmetry_tests)
|
|
27
|
+
endif()
|
|
28
|
+
include(GNUInstallDirs)
|
|
29
|
+
option(SYNKIT_CANON_WHEEL "Install the runtime library inside the Python wheel" OFF)
|
|
30
|
+
if(SYNKIT_CANON_WHEEL)
|
|
31
|
+
if(MSVC)
|
|
32
|
+
set_property(TARGET synkit_canon PROPERTY MSVC_RUNTIME_LIBRARY
|
|
33
|
+
"MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
|
34
|
+
endif()
|
|
35
|
+
install(TARGETS synkit_canon
|
|
36
|
+
LIBRARY DESTINATION synkit/Graph/Canon/_native/lib
|
|
37
|
+
RUNTIME DESTINATION synkit/Graph/Canon/_native/lib)
|
|
38
|
+
else()
|
|
39
|
+
install(TARGETS synkit_canon LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
40
|
+
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|
41
|
+
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
|
42
|
+
endif()
|