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,893 @@
|
|
|
1
|
+
"""Structural ITS signatures, certificates, and clustering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter, defaultdict
|
|
6
|
+
from typing import Any, Dict, List, Tuple
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
|
|
10
|
+
from synkit.Graph.Matcher.graph_cluster import GraphCluster
|
|
11
|
+
from ..core import product as _product_state
|
|
12
|
+
from .deduplication import (
|
|
13
|
+
_PRIMITIVE_IDENTITY_TYPES,
|
|
14
|
+
_freeze_typed_identity,
|
|
15
|
+
_merge_application_orbits,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
ITS_STRUCTURAL_NODE_ATTRS = [
|
|
19
|
+
"element",
|
|
20
|
+
"aromatic",
|
|
21
|
+
"hcount",
|
|
22
|
+
"charge",
|
|
23
|
+
"radical",
|
|
24
|
+
"lone_pairs",
|
|
25
|
+
"valence_electrons",
|
|
26
|
+
"present",
|
|
27
|
+
"_legacy_typesgh_sig",
|
|
28
|
+
]
|
|
29
|
+
ITS_STRUCTURAL_EDGE_ATTRS = ["order", "kekule_order", "sigma_order", "pi_order"]
|
|
30
|
+
_EXACT_NODE_SIG = "_structural_exact_node_sig"
|
|
31
|
+
_REFINED_NODE_COLOUR = "_structural_refined_node_colour"
|
|
32
|
+
_EXACT_EDGE_SIG = "_structural_exact_edge_sig"
|
|
33
|
+
_REFINED_EDGE_COLOUR = "_structural_refined_edge_colour"
|
|
34
|
+
_EXACT_NODE_PALETTE = "_structural_exact_node_palette"
|
|
35
|
+
_EXACT_EDGE_PALETTE = "_structural_exact_edge_palette"
|
|
36
|
+
_EXACT_IDENTITY_CACHE = "_structural_exact_identity_cache"
|
|
37
|
+
_EXACT_DIRTY_NODES = "_structural_exact_dirty_nodes"
|
|
38
|
+
_EXACT_DIRTY_EDGES = "_structural_exact_dirty_edges"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _attach_exact_structural_signatures( # noqa: C901
|
|
42
|
+
graph: nx.Graph,
|
|
43
|
+
identity_cache: Dict[int, Tuple[Any, Any]] | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Attach the normalized exact labels used by structural isomorphism."""
|
|
46
|
+
node_palette = graph.graph.get(_EXACT_NODE_PALETTE)
|
|
47
|
+
edge_palette = graph.graph.get(_EXACT_EDGE_PALETTE)
|
|
48
|
+
if identity_cache is None:
|
|
49
|
+
identity_cache = graph.graph.get(_EXACT_IDENTITY_CACHE)
|
|
50
|
+
|
|
51
|
+
dirty_nodes = graph.graph.get(_EXACT_DIRTY_NODES)
|
|
52
|
+
dirty_edges = graph.graph.get(_EXACT_DIRTY_EDGES)
|
|
53
|
+
incrementally_seeded = (
|
|
54
|
+
graph.graph.get("_structural_signatures_seeded", False)
|
|
55
|
+
and isinstance(dirty_nodes, set)
|
|
56
|
+
and isinstance(dirty_edges, set)
|
|
57
|
+
)
|
|
58
|
+
if incrementally_seeded:
|
|
59
|
+
# The prepared host already carries exact labels. Every subsequent
|
|
60
|
+
# mutation records its support, so evaluating the same label function
|
|
61
|
+
# on that support is equivalent to rescanning the complete graph.
|
|
62
|
+
node_records = (
|
|
63
|
+
(node, graph.nodes[node]) for node in dirty_nodes if node in graph
|
|
64
|
+
)
|
|
65
|
+
edge_records = (
|
|
66
|
+
(left, right, graph.edges[left, right])
|
|
67
|
+
for edge in dirty_edges
|
|
68
|
+
for left, right in (edge,)
|
|
69
|
+
if graph.has_edge(left, right)
|
|
70
|
+
)
|
|
71
|
+
adjacency = graph._adj
|
|
72
|
+
aromatic_nodes = {
|
|
73
|
+
node
|
|
74
|
+
for node in dirty_nodes
|
|
75
|
+
if node in adjacency
|
|
76
|
+
and any(
|
|
77
|
+
attrs.get("order") == (1.5, 1.5) for attrs in adjacency[node].values()
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
else:
|
|
81
|
+
node_records = graph.nodes(data=True)
|
|
82
|
+
edge_records = graph.edges(data=True)
|
|
83
|
+
aromatic_nodes = {
|
|
84
|
+
node
|
|
85
|
+
for u, v, attrs in graph.edges(data=True)
|
|
86
|
+
if attrs.get("order") == (1.5, 1.5)
|
|
87
|
+
for node in (u, v)
|
|
88
|
+
}
|
|
89
|
+
electron_aware = bool(graph.graph.get("electron_aware_rewrite", False))
|
|
90
|
+
for node, attrs in node_records:
|
|
91
|
+
if _EXACT_NODE_SIG in attrs:
|
|
92
|
+
continue
|
|
93
|
+
values = []
|
|
94
|
+
for name in ITS_STRUCTURAL_NODE_ATTRS:
|
|
95
|
+
if name == "_legacy_typesgh_sig":
|
|
96
|
+
value = () if electron_aware else attrs.get("typesGH", ())
|
|
97
|
+
elif name == "charge" and node in aromatic_nodes:
|
|
98
|
+
template_charge = attrs.get("template_charge")
|
|
99
|
+
value = (
|
|
100
|
+
template_charge
|
|
101
|
+
if isinstance(template_charge, tuple) and len(template_charge) == 2
|
|
102
|
+
else attrs.get(name)
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
value = attrs.get(name)
|
|
106
|
+
value_type = type(value)
|
|
107
|
+
values.append(
|
|
108
|
+
(value_type, value)
|
|
109
|
+
if value_type in _PRIMITIVE_IDENTITY_TYPES
|
|
110
|
+
else _freeze_typed_identity(value, identity_cache)
|
|
111
|
+
)
|
|
112
|
+
signature = tuple(values)
|
|
113
|
+
if node_palette is None:
|
|
114
|
+
attrs[_EXACT_NODE_SIG] = signature
|
|
115
|
+
else:
|
|
116
|
+
colour = node_palette.get(signature)
|
|
117
|
+
if colour is None:
|
|
118
|
+
colour = len(node_palette) + 1
|
|
119
|
+
node_palette[signature] = colour
|
|
120
|
+
attrs[_EXACT_NODE_SIG] = colour
|
|
121
|
+
for _, _, attrs in edge_records:
|
|
122
|
+
if _EXACT_EDGE_SIG in attrs:
|
|
123
|
+
continue
|
|
124
|
+
edge_values = []
|
|
125
|
+
aromatic_unchanged = attrs.get("order") == (1.5, 1.5)
|
|
126
|
+
for name in ITS_STRUCTURAL_EDGE_ATTRS:
|
|
127
|
+
value = attrs.get(name)
|
|
128
|
+
if aromatic_unchanged and name in {
|
|
129
|
+
"kekule_order",
|
|
130
|
+
"sigma_order",
|
|
131
|
+
"pi_order",
|
|
132
|
+
}:
|
|
133
|
+
value = "aromatic_phase"
|
|
134
|
+
value_type = type(value)
|
|
135
|
+
edge_values.append(
|
|
136
|
+
(value_type, value)
|
|
137
|
+
if value_type in _PRIMITIVE_IDENTITY_TYPES
|
|
138
|
+
else _freeze_typed_identity(value, identity_cache)
|
|
139
|
+
)
|
|
140
|
+
signature = tuple(edge_values)
|
|
141
|
+
if edge_palette is None:
|
|
142
|
+
attrs[_EXACT_EDGE_SIG] = signature
|
|
143
|
+
else:
|
|
144
|
+
colour = edge_palette.get(signature)
|
|
145
|
+
if colour is None:
|
|
146
|
+
colour = len(edge_palette) + 1
|
|
147
|
+
edge_palette[signature] = colour
|
|
148
|
+
attrs[_EXACT_EDGE_SIG] = colour
|
|
149
|
+
if incrementally_seeded:
|
|
150
|
+
dirty_nodes.clear()
|
|
151
|
+
dirty_edges.clear()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _prepare_its_for_structural_cluster(
|
|
155
|
+
its: nx.Graph,
|
|
156
|
+
*,
|
|
157
|
+
refresh_electrons: bool = True,
|
|
158
|
+
hash_iterations: int = 5,
|
|
159
|
+
) -> nx.Graph:
|
|
160
|
+
"""Attach invariant signatures that accelerate exact ITS clustering."""
|
|
161
|
+
prepared = its.copy()
|
|
162
|
+
if (
|
|
163
|
+
refresh_electrons
|
|
164
|
+
and prepared.graph.get("electron_aware_rewrite", False)
|
|
165
|
+
and not prepared.graph.get("_product_electron_fields_current", False)
|
|
166
|
+
):
|
|
167
|
+
_product_state._refresh_product_electron_fields(prepared)
|
|
168
|
+
_attach_exact_structural_signatures(prepared)
|
|
169
|
+
# Retain the established diagnostic labels on copied prepared graphs.
|
|
170
|
+
for _, attrs in prepared.nodes(data=True):
|
|
171
|
+
attrs["_its_node_sig"] = attrs[_EXACT_NODE_SIG]
|
|
172
|
+
for _, _, attrs in prepared.edges(data=True):
|
|
173
|
+
attrs["_its_edge_sig"] = attrs[_EXACT_EDGE_SIG]
|
|
174
|
+
node_hashes = nx.weisfeiler_lehman_subgraph_hashes(
|
|
175
|
+
prepared,
|
|
176
|
+
node_attr=_EXACT_NODE_SIG,
|
|
177
|
+
edge_attr=_EXACT_EDGE_SIG,
|
|
178
|
+
iterations=hash_iterations,
|
|
179
|
+
digest_size=16,
|
|
180
|
+
)
|
|
181
|
+
for node, hashes in node_hashes.items():
|
|
182
|
+
prepared.nodes[node][_REFINED_NODE_COLOUR] = hashes[-1] if hashes else ""
|
|
183
|
+
return prepared
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _refine_structural_colours_in_place( # noqa: C901
|
|
187
|
+
graphs: List[nx.Graph],
|
|
188
|
+
*,
|
|
189
|
+
iterations: int,
|
|
190
|
+
node_signature_attr: str = _EXACT_NODE_SIG,
|
|
191
|
+
edge_signature_attr: str = _EXACT_EDGE_SIG,
|
|
192
|
+
node_colour_attr: str = _REFINED_NODE_COLOUR,
|
|
193
|
+
edge_colour_attr: str = _REFINED_EDGE_COLOUR,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Run joint exact 1-WL refinement using interned integer colours.
|
|
196
|
+
|
|
197
|
+
Integer interning avoids cryptographic hashing. The refinement is used
|
|
198
|
+
only as a necessary isomorphism invariant; exact component comparison is
|
|
199
|
+
still the sole authority for merging a collision bucket.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def assign(signatures: List[Tuple[nx.Graph, Any, Any]]) -> int:
|
|
203
|
+
palette: Dict[Any, int] = {}
|
|
204
|
+
for graph, node, signature in signatures:
|
|
205
|
+
colour = palette.get(signature)
|
|
206
|
+
if colour is None:
|
|
207
|
+
colour = len(palette) + 1
|
|
208
|
+
palette[signature] = colour
|
|
209
|
+
graph.nodes[node][node_colour_attr] = colour
|
|
210
|
+
return len(palette)
|
|
211
|
+
|
|
212
|
+
labels_are_interned_integers = all(
|
|
213
|
+
type(attrs[node_signature_attr]) is int
|
|
214
|
+
for graph in graphs
|
|
215
|
+
for _, attrs in graph.nodes(data=True)
|
|
216
|
+
) and all(
|
|
217
|
+
type(attrs[edge_signature_attr]) is int
|
|
218
|
+
for graph in graphs
|
|
219
|
+
for _, _, attrs in graph.edges(data=True)
|
|
220
|
+
)
|
|
221
|
+
if labels_are_interned_integers:
|
|
222
|
+
node_colours = set()
|
|
223
|
+
for graph in graphs:
|
|
224
|
+
for _, attrs in graph.nodes(data=True):
|
|
225
|
+
colour = attrs[node_signature_attr]
|
|
226
|
+
attrs[node_colour_attr] = colour
|
|
227
|
+
node_colours.add(colour)
|
|
228
|
+
for _, _, attrs in graph.edges(data=True):
|
|
229
|
+
attrs[edge_colour_attr] = attrs[edge_signature_attr]
|
|
230
|
+
colour_count = len(node_colours)
|
|
231
|
+
else:
|
|
232
|
+
edge_palette: Dict[Any, int] = {}
|
|
233
|
+
for graph in graphs:
|
|
234
|
+
for _, _, attrs in graph.edges(data=True):
|
|
235
|
+
signature = attrs[edge_signature_attr]
|
|
236
|
+
colour = edge_palette.get(signature)
|
|
237
|
+
if colour is None:
|
|
238
|
+
colour = len(edge_palette) + 1
|
|
239
|
+
edge_palette[signature] = colour
|
|
240
|
+
attrs[edge_colour_attr] = colour
|
|
241
|
+
|
|
242
|
+
colour_count = assign(
|
|
243
|
+
[
|
|
244
|
+
(graph, node, attrs[node_signature_attr])
|
|
245
|
+
for graph in graphs
|
|
246
|
+
for node, attrs in graph.nodes(data=True)
|
|
247
|
+
]
|
|
248
|
+
)
|
|
249
|
+
for _ in range(iterations):
|
|
250
|
+
signatures = []
|
|
251
|
+
for graph in graphs:
|
|
252
|
+
for node in graph:
|
|
253
|
+
neighbourhood = tuple(
|
|
254
|
+
sorted(
|
|
255
|
+
(
|
|
256
|
+
graph.edges[node, neighbor][edge_colour_attr],
|
|
257
|
+
graph.nodes[neighbor][node_colour_attr],
|
|
258
|
+
)
|
|
259
|
+
for neighbor in graph.neighbors(node)
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
signatures.append(
|
|
263
|
+
(
|
|
264
|
+
graph,
|
|
265
|
+
node,
|
|
266
|
+
(
|
|
267
|
+
graph.nodes[node][node_colour_attr],
|
|
268
|
+
neighbourhood,
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
refined_count = assign(signatures)
|
|
273
|
+
if refined_count == colour_count:
|
|
274
|
+
break
|
|
275
|
+
colour_count = refined_count
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _remove_structural_signatures(graphs: List[nx.Graph]) -> None:
|
|
279
|
+
"""Remove temporary exact/refinement labels from returned ITS graphs."""
|
|
280
|
+
for graph in graphs:
|
|
281
|
+
graph.graph.pop(_EXACT_NODE_PALETTE, None)
|
|
282
|
+
graph.graph.pop(_EXACT_EDGE_PALETTE, None)
|
|
283
|
+
graph.graph.pop(_EXACT_IDENTITY_CACHE, None)
|
|
284
|
+
graph.graph.pop(_EXACT_DIRTY_NODES, None)
|
|
285
|
+
graph.graph.pop(_EXACT_DIRTY_EDGES, None)
|
|
286
|
+
graph.graph.pop("_structural_signatures_seeded", None)
|
|
287
|
+
for _, attrs in graph.nodes(data=True):
|
|
288
|
+
attrs.pop(_EXACT_NODE_SIG, None)
|
|
289
|
+
attrs.pop(_REFINED_NODE_COLOUR, None)
|
|
290
|
+
for _, _, attrs in graph.edges(data=True):
|
|
291
|
+
attrs.pop(_EXACT_EDGE_SIG, None)
|
|
292
|
+
attrs.pop(_REFINED_EDGE_COLOUR, None)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _exact_inventory_key(graph: nx.Graph) -> Any:
|
|
296
|
+
"""Return a cheap necessary invariant for attributed isomorphism.
|
|
297
|
+
|
|
298
|
+
Equal attributed graphs necessarily have equal multisets of exact node
|
|
299
|
+
and edge labels. This key is therefore allowed to reject a comparison,
|
|
300
|
+
but never to authorize a merge.
|
|
301
|
+
"""
|
|
302
|
+
return (
|
|
303
|
+
graph.is_directed(),
|
|
304
|
+
graph.is_multigraph(),
|
|
305
|
+
graph.number_of_nodes(),
|
|
306
|
+
graph.number_of_edges(),
|
|
307
|
+
frozenset(
|
|
308
|
+
Counter(
|
|
309
|
+
attrs[_EXACT_NODE_SIG] for _, attrs in graph.nodes(data=True)
|
|
310
|
+
).items()
|
|
311
|
+
),
|
|
312
|
+
frozenset(
|
|
313
|
+
Counter(
|
|
314
|
+
attrs[_EXACT_EDGE_SIG] for _, _, attrs in graph.edges(data=True)
|
|
315
|
+
).items()
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _active_neighbourhood_invariant(
|
|
321
|
+
graph: nx.Graph,
|
|
322
|
+
*,
|
|
323
|
+
radius: int = 2,
|
|
324
|
+
) -> Any:
|
|
325
|
+
"""Return an exact local invariant rooted at endpoint-paired vertices.
|
|
326
|
+
|
|
327
|
+
Electron-aware rewrites store ``present`` as an endpoint pair precisely on
|
|
328
|
+
their mapped vertices. Container type and value are part of
|
|
329
|
+
``_EXACT_NODE_SIG``, so every attributed isomorphism preserves this root
|
|
330
|
+
set. It consequently preserves graph distance from that set and every
|
|
331
|
+
labelled neighbourhood recorded below. Unequal results prove
|
|
332
|
+
non-isomorphism; equality is never used to merge.
|
|
333
|
+
|
|
334
|
+
This sparse refinement visits only the reaction locus and its immediate
|
|
335
|
+
context. Graphs without such intrinsic roots fall back to one full exact
|
|
336
|
+
1-WL round in the caller.
|
|
337
|
+
"""
|
|
338
|
+
nodes = graph._node
|
|
339
|
+
adjacency = graph._adj
|
|
340
|
+
roots = {
|
|
341
|
+
node
|
|
342
|
+
for node, attrs in nodes.items()
|
|
343
|
+
if isinstance(attrs.get("present"), tuple) and len(attrs["present"]) == 2
|
|
344
|
+
}
|
|
345
|
+
if not roots:
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
records = []
|
|
349
|
+
frontier = roots
|
|
350
|
+
seen = set(roots)
|
|
351
|
+
for depth in range(radius):
|
|
352
|
+
next_frontier = set()
|
|
353
|
+
for node in frontier:
|
|
354
|
+
node_signature = nodes[node][_EXACT_NODE_SIG]
|
|
355
|
+
neighbourhood = []
|
|
356
|
+
for neighbour, edge_attrs in adjacency[node].items():
|
|
357
|
+
next_frontier.add(neighbour)
|
|
358
|
+
neighbourhood.append(
|
|
359
|
+
(
|
|
360
|
+
edge_attrs[_EXACT_EDGE_SIG],
|
|
361
|
+
nodes[neighbour][_EXACT_NODE_SIG],
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
records.append(
|
|
365
|
+
(
|
|
366
|
+
depth,
|
|
367
|
+
node_signature,
|
|
368
|
+
tuple(sorted(neighbourhood)),
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
frontier = next_frontier - seen
|
|
372
|
+
seen.update(next_frontier)
|
|
373
|
+
if not frontier:
|
|
374
|
+
break
|
|
375
|
+
return len(roots), tuple(sorted(records))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _canonical_attributed_graph_certificate(
|
|
379
|
+
graph: nx.Graph,
|
|
380
|
+
*,
|
|
381
|
+
node_attribute: str,
|
|
382
|
+
edge_attribute: str,
|
|
383
|
+
node_palette: Dict[Any, int],
|
|
384
|
+
edge_palette: Dict[Any, int],
|
|
385
|
+
topology_cache: Dict[Any, Any] | None = None,
|
|
386
|
+
) -> str | None:
|
|
387
|
+
"""Return an injective canonical encoding of a simple attributed graph.
|
|
388
|
+
|
|
389
|
+
Original vertices become isotope-labelled dummy atoms. Every labelled
|
|
390
|
+
edge is subdivided by an isotope-labelled helium atom. The two atom types
|
|
391
|
+
make this transformation injective: decoding contracts every helium atom
|
|
392
|
+
and recovers the original vertex- and edge-labelled graph. Consequently,
|
|
393
|
+
equal SMILES certificates are sufficient proof of attributed isomorphism,
|
|
394
|
+
rather than a probabilistic hash match.
|
|
395
|
+
|
|
396
|
+
RDKit supplies compiled canonical traversal. Unsupported graph kinds or
|
|
397
|
+
an exhausted isotope label space return ``None`` and retain the exact
|
|
398
|
+
NetworkX tree/VF2 path.
|
|
399
|
+
"""
|
|
400
|
+
if graph.is_directed() or graph.is_multigraph() or nx.number_of_selfloops(graph):
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
from rdkit import Chem
|
|
404
|
+
|
|
405
|
+
node_order = tuple(graph)
|
|
406
|
+
edge_order = tuple(graph.edges())
|
|
407
|
+
topology_key = (node_order, edge_order)
|
|
408
|
+
skeleton = topology_cache.get(topology_key) if topology_cache is not None else None
|
|
409
|
+
if skeleton is None:
|
|
410
|
+
builder = Chem.RWMol()
|
|
411
|
+
node_index = {node: builder.AddAtom(Chem.Atom(0)) for node in node_order}
|
|
412
|
+
for left, right in edge_order:
|
|
413
|
+
edge_node = builder.AddAtom(Chem.Atom(2))
|
|
414
|
+
builder.AddBond(node_index[left], edge_node, Chem.BondType.SINGLE)
|
|
415
|
+
builder.AddBond(edge_node, node_index[right], Chem.BondType.SINGLE)
|
|
416
|
+
skeleton = builder.GetMol()
|
|
417
|
+
if topology_cache is not None:
|
|
418
|
+
topology_cache[topology_key] = skeleton
|
|
419
|
+
|
|
420
|
+
mol = Chem.Mol(skeleton)
|
|
421
|
+
for atom_index, node in enumerate(node_order):
|
|
422
|
+
attrs = graph.nodes[node]
|
|
423
|
+
label = attrs[node_attribute]
|
|
424
|
+
colour = node_palette.get(label)
|
|
425
|
+
if colour is None:
|
|
426
|
+
colour = len(node_palette) + 1
|
|
427
|
+
if colour > 65535:
|
|
428
|
+
return None
|
|
429
|
+
node_palette[label] = colour
|
|
430
|
+
mol.GetAtomWithIdx(atom_index).SetIsotope(colour)
|
|
431
|
+
|
|
432
|
+
edge_offset = len(node_order)
|
|
433
|
+
for edge_index, (left, right) in enumerate(edge_order):
|
|
434
|
+
attrs = graph.edges[left, right]
|
|
435
|
+
label = attrs[edge_attribute]
|
|
436
|
+
colour = edge_palette.get(label)
|
|
437
|
+
if colour is None:
|
|
438
|
+
colour = len(edge_palette) + 1
|
|
439
|
+
if colour > 65535:
|
|
440
|
+
return None
|
|
441
|
+
edge_palette[label] = colour
|
|
442
|
+
mol.GetAtomWithIdx(edge_offset + edge_index).SetIsotope(colour)
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
return Chem.MolToSmiles(
|
|
446
|
+
mol,
|
|
447
|
+
canonical=True,
|
|
448
|
+
allBondsExplicit=True,
|
|
449
|
+
allHsExplicit=True,
|
|
450
|
+
)
|
|
451
|
+
except Exception:
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _order_prepared_its_for_vf2(prepared: nx.Graph) -> nx.Graph:
|
|
456
|
+
"""Put rare refined environments first before an exact VF2 comparison."""
|
|
457
|
+
# VF2 selects pattern nodes in insertion order. Put rare refined
|
|
458
|
+
# environments first so a small reaction locus anchors the match
|
|
459
|
+
# before traversal enters a large symmetric scaffold.
|
|
460
|
+
frequencies = Counter(
|
|
461
|
+
attrs[_REFINED_NODE_COLOUR] for _, attrs in prepared.nodes(data=True)
|
|
462
|
+
)
|
|
463
|
+
node_order = sorted(
|
|
464
|
+
prepared.nodes,
|
|
465
|
+
key=lambda node: (
|
|
466
|
+
frequencies[prepared.nodes[node][_REFINED_NODE_COLOUR]],
|
|
467
|
+
prepared.nodes[node][_REFINED_NODE_COLOUR],
|
|
468
|
+
repr(node),
|
|
469
|
+
),
|
|
470
|
+
)
|
|
471
|
+
ordered = prepared.__class__()
|
|
472
|
+
ordered.graph.update(prepared.graph)
|
|
473
|
+
ordered.add_nodes_from((node, prepared.nodes[node].copy()) for node in node_order)
|
|
474
|
+
if prepared.is_multigraph():
|
|
475
|
+
ordered.add_edges_from(
|
|
476
|
+
(u, v, key, attrs.copy())
|
|
477
|
+
for u, v, key, attrs in prepared.edges(keys=True, data=True)
|
|
478
|
+
)
|
|
479
|
+
else:
|
|
480
|
+
ordered.add_edges_from(
|
|
481
|
+
(u, v, attrs.copy()) for u, v, attrs in prepared.edges(data=True)
|
|
482
|
+
)
|
|
483
|
+
return ordered
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _attributed_tree_code(component: nx.Graph) -> Any | None:
|
|
487
|
+
"""Return a complete canonical code for an undirected attributed tree."""
|
|
488
|
+
if (
|
|
489
|
+
component.is_directed()
|
|
490
|
+
or component.is_multigraph()
|
|
491
|
+
or component.number_of_edges() + 1 != len(component)
|
|
492
|
+
):
|
|
493
|
+
return None
|
|
494
|
+
neighbours = {node: set(component.neighbors(node)) for node in component}
|
|
495
|
+
remaining = set(component)
|
|
496
|
+
leaves = {node for node in remaining if len(neighbours[node]) <= 1}
|
|
497
|
+
while len(remaining) > 2:
|
|
498
|
+
remaining.difference_update(leaves)
|
|
499
|
+
next_leaves = set()
|
|
500
|
+
for leaf in leaves:
|
|
501
|
+
for neighbor in neighbours[leaf]:
|
|
502
|
+
neighbours[neighbor].discard(leaf)
|
|
503
|
+
if neighbor in remaining and len(neighbours[neighbor]) <= 1:
|
|
504
|
+
next_leaves.add(neighbor)
|
|
505
|
+
leaves = next_leaves
|
|
506
|
+
|
|
507
|
+
def rooted_code(node: Any, parent: Any | None) -> Any:
|
|
508
|
+
children = [
|
|
509
|
+
(
|
|
510
|
+
component.edges[node, child][_EXACT_EDGE_SIG],
|
|
511
|
+
rooted_code(child, node),
|
|
512
|
+
)
|
|
513
|
+
for child in component.neighbors(node)
|
|
514
|
+
if child != parent
|
|
515
|
+
]
|
|
516
|
+
node_role = (
|
|
517
|
+
component.nodes[node][_EXACT_NODE_SIG],
|
|
518
|
+
component.nodes[node][_REFINED_NODE_COLOUR],
|
|
519
|
+
)
|
|
520
|
+
return node_role, tuple(sorted(children, key=repr))
|
|
521
|
+
|
|
522
|
+
return min((rooted_code(center, None) for center in remaining), key=repr)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _component_inventory( # noqa: C901
|
|
526
|
+
graphs: List[nx.Graph],
|
|
527
|
+
cluster: GraphCluster,
|
|
528
|
+
) -> List[Tuple[int, ...]]:
|
|
529
|
+
"""Classify disconnected non-stereo ITS components exactly."""
|
|
530
|
+
tree_classes: Dict[Any, int] = {}
|
|
531
|
+
representatives: Dict[Any, List[List[Any]]] = defaultdict(list)
|
|
532
|
+
multigraph_representatives: Dict[Any, List[Tuple[int, nx.Graph]]] = defaultdict(
|
|
533
|
+
list
|
|
534
|
+
)
|
|
535
|
+
literal_classes: Dict[Any, int] = {}
|
|
536
|
+
inventories: List[Tuple[int, ...]] = []
|
|
537
|
+
next_class = 0
|
|
538
|
+
for graph in graphs:
|
|
539
|
+
components = (
|
|
540
|
+
nx.weakly_connected_components(graph)
|
|
541
|
+
if graph.is_directed()
|
|
542
|
+
else nx.connected_components(graph)
|
|
543
|
+
)
|
|
544
|
+
classes = []
|
|
545
|
+
for nodes in components:
|
|
546
|
+
component = graph.subgraph(nodes)
|
|
547
|
+
node_records = tuple(
|
|
548
|
+
sorted(
|
|
549
|
+
[
|
|
550
|
+
(
|
|
551
|
+
node,
|
|
552
|
+
(
|
|
553
|
+
attrs[_EXACT_NODE_SIG],
|
|
554
|
+
attrs[_REFINED_NODE_COLOUR],
|
|
555
|
+
),
|
|
556
|
+
)
|
|
557
|
+
for node, attrs in component.nodes(data=True)
|
|
558
|
+
],
|
|
559
|
+
key=lambda record: repr(record[0]),
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
edge_records = tuple(
|
|
563
|
+
sorted(
|
|
564
|
+
[
|
|
565
|
+
(
|
|
566
|
+
tuple(sorted((left, right), key=repr)),
|
|
567
|
+
attrs[_EXACT_EDGE_SIG],
|
|
568
|
+
)
|
|
569
|
+
for left, right, attrs in component.edges(data=True)
|
|
570
|
+
],
|
|
571
|
+
key=repr,
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
literal_signature = (node_records, edge_records)
|
|
575
|
+
component_class = literal_classes.get(literal_signature)
|
|
576
|
+
if component_class is not None:
|
|
577
|
+
classes.append(component_class)
|
|
578
|
+
continue
|
|
579
|
+
tree_key = _attributed_tree_code(component)
|
|
580
|
+
if tree_key is not None:
|
|
581
|
+
component_class = tree_classes.get(tree_key)
|
|
582
|
+
if component_class is None:
|
|
583
|
+
component_class = next_class
|
|
584
|
+
next_class += 1
|
|
585
|
+
tree_classes[tree_key] = component_class
|
|
586
|
+
elif component.is_multigraph():
|
|
587
|
+
fingerprint = (
|
|
588
|
+
component.number_of_nodes(),
|
|
589
|
+
component.number_of_edges(),
|
|
590
|
+
tuple(
|
|
591
|
+
sorted(
|
|
592
|
+
attrs[_REFINED_NODE_COLOUR]
|
|
593
|
+
for _, attrs in component.nodes(data=True)
|
|
594
|
+
)
|
|
595
|
+
),
|
|
596
|
+
)
|
|
597
|
+
for class_id, representative in multigraph_representatives[fingerprint]:
|
|
598
|
+
if nx.is_isomorphic(
|
|
599
|
+
component,
|
|
600
|
+
representative,
|
|
601
|
+
node_match=cluster.nodeMatch,
|
|
602
|
+
edge_match=cluster.edgeMatch,
|
|
603
|
+
):
|
|
604
|
+
component_class = class_id
|
|
605
|
+
break
|
|
606
|
+
if component_class is None:
|
|
607
|
+
component_class = next_class
|
|
608
|
+
next_class += 1
|
|
609
|
+
multigraph_representatives[fingerprint].append(
|
|
610
|
+
(component_class, component)
|
|
611
|
+
)
|
|
612
|
+
else:
|
|
613
|
+
fingerprint = (
|
|
614
|
+
component.number_of_nodes(),
|
|
615
|
+
component.number_of_edges(),
|
|
616
|
+
tuple(
|
|
617
|
+
sorted(
|
|
618
|
+
attrs[_REFINED_NODE_COLOUR]
|
|
619
|
+
for _, attrs in component.nodes(data=True)
|
|
620
|
+
)
|
|
621
|
+
),
|
|
622
|
+
tuple(
|
|
623
|
+
sorted(
|
|
624
|
+
(
|
|
625
|
+
attrs[_EXACT_EDGE_SIG]
|
|
626
|
+
for _, _, attrs in component.edges(data=True)
|
|
627
|
+
),
|
|
628
|
+
key=repr,
|
|
629
|
+
)
|
|
630
|
+
),
|
|
631
|
+
)
|
|
632
|
+
bucket = representatives[fingerprint]
|
|
633
|
+
if not bucket:
|
|
634
|
+
component_class = next_class
|
|
635
|
+
next_class += 1
|
|
636
|
+
bucket.append([component_class, component])
|
|
637
|
+
else:
|
|
638
|
+
for record in bucket:
|
|
639
|
+
if nx.is_isomorphic(
|
|
640
|
+
component,
|
|
641
|
+
record[1],
|
|
642
|
+
node_match=cluster.nodeMatch,
|
|
643
|
+
edge_match=cluster.edgeMatch,
|
|
644
|
+
):
|
|
645
|
+
component_class = record[0]
|
|
646
|
+
break
|
|
647
|
+
if component_class is None:
|
|
648
|
+
component_class = next_class
|
|
649
|
+
next_class += 1
|
|
650
|
+
bucket.append([component_class, component])
|
|
651
|
+
literal_classes[literal_signature] = component_class
|
|
652
|
+
classes.append(component_class)
|
|
653
|
+
inventories.append(tuple(sorted(classes)))
|
|
654
|
+
return inventories
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def _cluster_prepared_structural_its(
|
|
658
|
+
its_graphs: List[nx.Graph],
|
|
659
|
+
prepared_records: List[Tuple[int, nx.Graph]],
|
|
660
|
+
) -> List[nx.Graph]:
|
|
661
|
+
"""Classify prepared ITS graphs, with exact tests deciding every merge."""
|
|
662
|
+
from synkit.Graph.Stereo import stereo_identity_signature
|
|
663
|
+
|
|
664
|
+
buckets: Dict[Any, List[Tuple[int, nx.Graph]]] = defaultdict(list)
|
|
665
|
+
for index, prepared in prepared_records:
|
|
666
|
+
signature = (
|
|
667
|
+
prepared.is_directed(),
|
|
668
|
+
prepared.is_multigraph(),
|
|
669
|
+
prepared.number_of_nodes(),
|
|
670
|
+
prepared.number_of_edges(),
|
|
671
|
+
tuple(
|
|
672
|
+
sorted(
|
|
673
|
+
attrs[_REFINED_NODE_COLOUR]
|
|
674
|
+
for _, attrs in prepared.nodes(data=True)
|
|
675
|
+
)
|
|
676
|
+
),
|
|
677
|
+
tuple(
|
|
678
|
+
sorted(
|
|
679
|
+
(
|
|
680
|
+
attrs[_EXACT_EDGE_SIG]
|
|
681
|
+
for _, _, attrs in prepared.edges(data=True)
|
|
682
|
+
),
|
|
683
|
+
key=repr,
|
|
684
|
+
)
|
|
685
|
+
),
|
|
686
|
+
)
|
|
687
|
+
stereo_signature = stereo_identity_signature(prepared)
|
|
688
|
+
buckets[(signature, stereo_signature)].append((index, prepared))
|
|
689
|
+
|
|
690
|
+
cluster = GraphCluster(
|
|
691
|
+
node_label_names=[_EXACT_NODE_SIG, _REFINED_NODE_COLOUR],
|
|
692
|
+
node_label_default=[(), ""],
|
|
693
|
+
edge_attribute=_EXACT_EDGE_SIG,
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
representative_indices: List[int] = []
|
|
697
|
+
for (_, stereo_signature), bucket in buckets.items():
|
|
698
|
+
if len(bucket) == 1:
|
|
699
|
+
representative_indices.append(bucket[0][0])
|
|
700
|
+
continue
|
|
701
|
+
prepared = [_order_prepared_its_for_vf2(prepared) for _, prepared in bucket]
|
|
702
|
+
if stereo_signature is None:
|
|
703
|
+
by_inventory: Dict[Tuple[int, ...], set[int]] = defaultdict(set)
|
|
704
|
+
for index, inventory in enumerate(_component_inventory(prepared, cluster)):
|
|
705
|
+
by_inventory[inventory].add(index)
|
|
706
|
+
classes = list(by_inventory.values())
|
|
707
|
+
else:
|
|
708
|
+
classes, _ = cluster.iterative_cluster(
|
|
709
|
+
prepared,
|
|
710
|
+
nodeMatch=cluster.nodeMatch,
|
|
711
|
+
edgeMatch=cluster.edgeMatch,
|
|
712
|
+
)
|
|
713
|
+
for cls in classes:
|
|
714
|
+
member_indices = [bucket[index][0] for index in sorted(cls)]
|
|
715
|
+
representative_index = member_indices[0]
|
|
716
|
+
representative_indices.append(representative_index)
|
|
717
|
+
_merge_application_orbits(
|
|
718
|
+
its_graphs[representative_index],
|
|
719
|
+
[its_graphs[index] for index in member_indices],
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
representative_indices.sort()
|
|
723
|
+
return [its_graphs[index] for index in representative_indices]
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def _cluster_structural_its( # noqa: C901
|
|
727
|
+
its_graphs: List[nx.Graph],
|
|
728
|
+
*,
|
|
729
|
+
refresh_electrons: bool,
|
|
730
|
+
hash_iterations: int = 5,
|
|
731
|
+
) -> List[nx.Graph]:
|
|
732
|
+
"""Run one exact structural/stereo clustering pass.
|
|
733
|
+
|
|
734
|
+
Finalized simple graphs first use an exact radius-two invariant around the
|
|
735
|
+
intrinsically endpoint-paired vertices to prove that unequal buckets
|
|
736
|
+
cannot be isomorphic. Graphs without such roots retain one exact joint
|
|
737
|
+
1-WL round. Singleton buckets need no canonical work. Collisions are merged
|
|
738
|
+
only by the injective attributed-graph certificate, with complete tree/VF2
|
|
739
|
+
comparison as the unsupported-graph fallback.
|
|
740
|
+
"""
|
|
741
|
+
if len(its_graphs) < 2:
|
|
742
|
+
if any(
|
|
743
|
+
_EXACT_NODE_SIG in attrs
|
|
744
|
+
for graph in its_graphs
|
|
745
|
+
for _, attrs in graph.nodes(data=True)
|
|
746
|
+
):
|
|
747
|
+
_remove_structural_signatures(its_graphs)
|
|
748
|
+
return its_graphs
|
|
749
|
+
|
|
750
|
+
use_integer_refinement = not refresh_electrons and all(
|
|
751
|
+
not graph.is_directed() and not graph.is_multigraph() for graph in its_graphs
|
|
752
|
+
)
|
|
753
|
+
if not use_integer_refinement:
|
|
754
|
+
prepared_records = [
|
|
755
|
+
(
|
|
756
|
+
index,
|
|
757
|
+
_prepare_its_for_structural_cluster(
|
|
758
|
+
its,
|
|
759
|
+
refresh_electrons=refresh_electrons,
|
|
760
|
+
hash_iterations=hash_iterations,
|
|
761
|
+
),
|
|
762
|
+
)
|
|
763
|
+
for index, its in enumerate(its_graphs)
|
|
764
|
+
]
|
|
765
|
+
return _cluster_prepared_structural_its(its_graphs, prepared_records)
|
|
766
|
+
|
|
767
|
+
seeded_node_palette = its_graphs[0].graph.get(_EXACT_NODE_PALETTE)
|
|
768
|
+
seeded_edge_palette = its_graphs[0].graph.get(_EXACT_EDGE_PALETTE)
|
|
769
|
+
seeded_identity_cache = its_graphs[0].graph.get(_EXACT_IDENTITY_CACHE)
|
|
770
|
+
palettes_are_shared = (
|
|
771
|
+
isinstance(seeded_node_palette, dict)
|
|
772
|
+
and isinstance(seeded_edge_palette, dict)
|
|
773
|
+
and isinstance(seeded_identity_cache, dict)
|
|
774
|
+
and all(
|
|
775
|
+
graph.graph.get(_EXACT_NODE_PALETTE) is seeded_node_palette
|
|
776
|
+
and graph.graph.get(_EXACT_EDGE_PALETTE) is seeded_edge_palette
|
|
777
|
+
and graph.graph.get(_EXACT_IDENTITY_CACHE) is seeded_identity_cache
|
|
778
|
+
for graph in its_graphs
|
|
779
|
+
)
|
|
780
|
+
)
|
|
781
|
+
if palettes_are_shared:
|
|
782
|
+
node_label_palette = seeded_node_palette
|
|
783
|
+
edge_label_palette = seeded_edge_palette
|
|
784
|
+
identity_cache = seeded_identity_cache
|
|
785
|
+
else:
|
|
786
|
+
node_label_palette: Dict[Any, int] = {}
|
|
787
|
+
edge_label_palette: Dict[Any, int] = {}
|
|
788
|
+
identity_cache: Dict[int, Tuple[Any, Any]] = {}
|
|
789
|
+
# Existing labels without the same palette are not mutually
|
|
790
|
+
# comparable. Recompute them once in the new quotient-local palette.
|
|
791
|
+
_remove_structural_signatures(its_graphs)
|
|
792
|
+
for graph in its_graphs:
|
|
793
|
+
graph.graph[_EXACT_NODE_PALETTE] = node_label_palette
|
|
794
|
+
graph.graph[_EXACT_EDGE_PALETTE] = edge_label_palette
|
|
795
|
+
graph.graph[_EXACT_IDENTITY_CACHE] = identity_cache
|
|
796
|
+
for graph in its_graphs:
|
|
797
|
+
_attach_exact_structural_signatures(graph, identity_cache)
|
|
798
|
+
try:
|
|
799
|
+
from synkit.Graph.Stereo import stereo_identity_signature
|
|
800
|
+
|
|
801
|
+
# The endpoint-paired rewrite locus is intrinsic to the exact node
|
|
802
|
+
# labels. Its radius-two attributed neighbourhood separates most
|
|
803
|
+
# placements without traversing the complete molecular graph. If any
|
|
804
|
+
# candidate lacks that intrinsic root, retain the full exact 1-WL
|
|
805
|
+
# invariant. Neither path is allowed to authorize a merge.
|
|
806
|
+
local_invariants = [
|
|
807
|
+
_active_neighbourhood_invariant(graph) for graph in its_graphs
|
|
808
|
+
]
|
|
809
|
+
use_local_invariant = all(
|
|
810
|
+
invariant is not None for invariant in local_invariants
|
|
811
|
+
)
|
|
812
|
+
if not use_local_invariant:
|
|
813
|
+
_refine_structural_colours_in_place(its_graphs, iterations=1)
|
|
814
|
+
coarse_buckets: Dict[Any, List[Tuple[int, nx.Graph]]] = defaultdict(list)
|
|
815
|
+
for index, (graph, local_invariant) in enumerate(
|
|
816
|
+
zip(its_graphs, local_invariants)
|
|
817
|
+
):
|
|
818
|
+
structural_invariant = (
|
|
819
|
+
local_invariant
|
|
820
|
+
if use_local_invariant
|
|
821
|
+
else (
|
|
822
|
+
graph.number_of_edges(),
|
|
823
|
+
tuple(
|
|
824
|
+
sorted(
|
|
825
|
+
attrs[_REFINED_NODE_COLOUR]
|
|
826
|
+
for _, attrs in graph.nodes(data=True)
|
|
827
|
+
)
|
|
828
|
+
),
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
coarse_buckets[
|
|
832
|
+
(
|
|
833
|
+
structural_invariant,
|
|
834
|
+
stereo_identity_signature(graph),
|
|
835
|
+
)
|
|
836
|
+
].append((index, graph))
|
|
837
|
+
|
|
838
|
+
certificate_node_palette: Dict[Any, int] = {}
|
|
839
|
+
certificate_edge_palette: Dict[Any, int] = {}
|
|
840
|
+
certificate_topology_cache: Dict[Any, Any] = {}
|
|
841
|
+
representative_indices: List[int] = []
|
|
842
|
+
fallback_records: List[Tuple[int, nx.Graph]] = []
|
|
843
|
+
for bucket in coarse_buckets.values():
|
|
844
|
+
if len(bucket) == 1:
|
|
845
|
+
representative_indices.append(bucket[0][0])
|
|
846
|
+
continue
|
|
847
|
+
certificate_buckets: Dict[str, List[int]] = defaultdict(list)
|
|
848
|
+
supported = True
|
|
849
|
+
for index, graph in bucket:
|
|
850
|
+
certificate = _canonical_attributed_graph_certificate(
|
|
851
|
+
graph,
|
|
852
|
+
node_attribute=_EXACT_NODE_SIG,
|
|
853
|
+
edge_attribute=_EXACT_EDGE_SIG,
|
|
854
|
+
node_palette=certificate_node_palette,
|
|
855
|
+
edge_palette=certificate_edge_palette,
|
|
856
|
+
topology_cache=certificate_topology_cache,
|
|
857
|
+
)
|
|
858
|
+
if certificate is None:
|
|
859
|
+
supported = False
|
|
860
|
+
break
|
|
861
|
+
certificate_buckets[certificate].append(index)
|
|
862
|
+
if not supported:
|
|
863
|
+
fallback_records.extend(bucket)
|
|
864
|
+
continue
|
|
865
|
+
for members in certificate_buckets.values():
|
|
866
|
+
representative_index = members[0]
|
|
867
|
+
representative_indices.append(representative_index)
|
|
868
|
+
_merge_application_orbits(
|
|
869
|
+
its_graphs[representative_index],
|
|
870
|
+
[its_graphs[index] for index in members],
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
if fallback_records:
|
|
874
|
+
if use_local_invariant:
|
|
875
|
+
# Complete fallback routines consume refined colours. Compute
|
|
876
|
+
# them only for the rare graph kinds that the compiled
|
|
877
|
+
# certificate cannot represent.
|
|
878
|
+
_refine_structural_colours_in_place(
|
|
879
|
+
[graph for _, graph in fallback_records],
|
|
880
|
+
iterations=hash_iterations,
|
|
881
|
+
)
|
|
882
|
+
fallback_representatives = _cluster_prepared_structural_its(
|
|
883
|
+
its_graphs,
|
|
884
|
+
fallback_records,
|
|
885
|
+
)
|
|
886
|
+
representative_indices.extend(
|
|
887
|
+
its_graphs.index(graph) for graph in fallback_representatives
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
representative_indices.sort()
|
|
891
|
+
return [its_graphs[index] for index in representative_indices]
|
|
892
|
+
finally:
|
|
893
|
+
_remove_structural_signatures(its_graphs)
|