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,137 @@
|
|
|
1
|
+
"""Exact bond-label orbits via point-stabilizer transversals.
|
|
2
|
+
|
|
3
|
+
Only the action on vertices supporting the label is needed. For a base
|
|
4
|
+
b_1,...,b_k of those vertices let H_j fix its first j points. One witnessed
|
|
5
|
+
transversal T_j for H_{j-1}/H_j gives G = T_1 ... T_k H_k. Since H_k fixes the
|
|
6
|
+
label, its orbit is obtained by applying T_k,...,T_1 and deduplicating images.
|
|
7
|
+
Thus irrelevant spectator permutations need never be listed. This uses
|
|
8
|
+
standard group decomposition, not a new graph-isomorphism algorithm.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from time import monotonic
|
|
13
|
+
|
|
14
|
+
import networkx as nx
|
|
15
|
+
|
|
16
|
+
from .bonds import ExactBondEvaluator, IncompleteSymmetry, ScoreWitness, _graph, bond_f1, transport_bonds
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SupportOrbitEvaluator(ExactBondEvaluator):
|
|
20
|
+
"""Exact quotient scorer without explicit enumeration of the entire group.
|
|
21
|
+
|
|
22
|
+
Every transporter is a full attributed-graph automorphism. Failure to
|
|
23
|
+
complete any orbit query raises; no partial group is reported as exact.
|
|
24
|
+
Deadlines are cooperative around isomorphism calls and need an external
|
|
25
|
+
process deadline for a hard wall-clock guarantee.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, reactant, *, max_label_images=100000, time_limit_seconds=30):
|
|
29
|
+
if (not isinstance(max_label_images, int) or max_label_images < 1
|
|
30
|
+
or not math.isfinite(time_limit_seconds) or time_limit_seconds <= 0):
|
|
31
|
+
raise ValueError("Require positive finite time and label-image limits")
|
|
32
|
+
self.n = len(reactant.atomic_numbers)
|
|
33
|
+
self.graph = _graph(reactant)
|
|
34
|
+
self.identity = tuple(range(self.n))
|
|
35
|
+
self.deadline = monotonic() + time_limit_seconds
|
|
36
|
+
self.max_label_images = max_label_images
|
|
37
|
+
self._orbits = {}
|
|
38
|
+
self._transversals = {}
|
|
39
|
+
self.isomorphism_calls = 0
|
|
40
|
+
self.colors = self._refine_colors()
|
|
41
|
+
|
|
42
|
+
def _check(self):
|
|
43
|
+
if monotonic() > self.deadline:
|
|
44
|
+
raise IncompleteSymmetry("Label-orbit deadline exceeded")
|
|
45
|
+
|
|
46
|
+
def _refine_colors(self):
|
|
47
|
+
def number(signatures):
|
|
48
|
+
numbers = {value: i for i, value in enumerate(sorted(set(signatures)))}
|
|
49
|
+
return tuple(numbers[x] for x in signatures)
|
|
50
|
+
colors = number([self.graph.nodes[i]["color"] for i in range(self.n)])
|
|
51
|
+
for _ in range(self.n):
|
|
52
|
+
self._check()
|
|
53
|
+
refined = number([(colors[i], tuple(sorted(
|
|
54
|
+
(self.graph.edges[i, j]["order"], colors[j]) for j in self.graph[i])))
|
|
55
|
+
for i in range(self.n)])
|
|
56
|
+
if len(set(refined)) == len(set(colors)):
|
|
57
|
+
return refined
|
|
58
|
+
colors = refined
|
|
59
|
+
return colors
|
|
60
|
+
|
|
61
|
+
def _extension(self, fixed, source, target):
|
|
62
|
+
self._check()
|
|
63
|
+
if source == target:
|
|
64
|
+
return self.identity
|
|
65
|
+
left, right = self.graph.copy(), self.graph.copy()
|
|
66
|
+
for i in range(self.n):
|
|
67
|
+
left.nodes[i]["constraint"] = (self.colors[i], -1)
|
|
68
|
+
right.nodes[i]["constraint"] = (self.colors[i], -1)
|
|
69
|
+
for i in fixed:
|
|
70
|
+
left.nodes[i]["constraint"] = right.nodes[i]["constraint"] = (self.colors[i], i)
|
|
71
|
+
left.nodes[source]["constraint"] = (self.colors[source], source)
|
|
72
|
+
right.nodes[target]["constraint"] = (self.colors[target], source)
|
|
73
|
+
matcher = nx.algorithms.isomorphism.GraphMatcher(
|
|
74
|
+
left, right,
|
|
75
|
+
node_match=nx.algorithms.isomorphism.categorical_node_match("constraint", None),
|
|
76
|
+
edge_match=nx.algorithms.isomorphism.categorical_edge_match("order", None),
|
|
77
|
+
)
|
|
78
|
+
self.isomorphism_calls += 1
|
|
79
|
+
mapping = next(matcher.isomorphisms_iter(), None)
|
|
80
|
+
self._check()
|
|
81
|
+
if mapping is None:
|
|
82
|
+
return None
|
|
83
|
+
return tuple(mapping[i] for i in range(self.n))
|
|
84
|
+
|
|
85
|
+
def _transversal(self, fixed, source):
|
|
86
|
+
key = (fixed, source)
|
|
87
|
+
if key not in self._transversals:
|
|
88
|
+
values = []
|
|
89
|
+
for target in range(self.n):
|
|
90
|
+
if target in fixed or self.colors[target] != self.colors[source]:
|
|
91
|
+
continue
|
|
92
|
+
witness = self._extension(fixed, source, target)
|
|
93
|
+
if witness is not None:
|
|
94
|
+
values.append(witness)
|
|
95
|
+
self._transversals[key] = tuple(values)
|
|
96
|
+
return self._transversals[key]
|
|
97
|
+
|
|
98
|
+
def orbit(self, label):
|
|
99
|
+
"""Return each distinct image with a verified full-graph transporter."""
|
|
100
|
+
self._check()
|
|
101
|
+
label = self._validate(label)
|
|
102
|
+
if label not in self._orbits:
|
|
103
|
+
base = self._support(label)
|
|
104
|
+
levels = [self._transversal(base[:j], source) for j, source in enumerate(base)]
|
|
105
|
+
images = {label: self.identity}
|
|
106
|
+
for transversal in reversed(levels):
|
|
107
|
+
next_images = {}
|
|
108
|
+
for t in transversal:
|
|
109
|
+
for current, witness in images.items():
|
|
110
|
+
self._check()
|
|
111
|
+
image = self._transport(current, t)
|
|
112
|
+
if image not in next_images:
|
|
113
|
+
next_images[image] = tuple(t[witness[i]] for i in range(self.n))
|
|
114
|
+
if len(next_images) > self.max_label_images:
|
|
115
|
+
raise IncompleteSymmetry("Label-image cap exceeded")
|
|
116
|
+
images = next_images
|
|
117
|
+
self._orbits[label] = images
|
|
118
|
+
return self._orbits[label]
|
|
119
|
+
|
|
120
|
+
def _support(self, label):
|
|
121
|
+
return tuple(sorted({i for pair in label for i in pair}))
|
|
122
|
+
|
|
123
|
+
def _transport(self, label, permutation):
|
|
124
|
+
return transport_bonds(label, permutation)
|
|
125
|
+
|
|
126
|
+
def score(self, prediction, label):
|
|
127
|
+
prediction = self._validate(prediction)
|
|
128
|
+
best = None
|
|
129
|
+
for image, witness in self.orbit(label).items():
|
|
130
|
+
self._check()
|
|
131
|
+
score = bond_f1(prediction, image)
|
|
132
|
+
if best is None or score > best.score:
|
|
133
|
+
best = ScoreWitness(score, witness)
|
|
134
|
+
return best
|
|
135
|
+
|
|
136
|
+
def orbit_key(self, label):
|
|
137
|
+
return min(tuple(sorted(image)) for image in self.orbit(label))
|
|
File without changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Topology-only detection of potential molecular stereo axes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import networkx as nx
|
|
6
|
+
from rdkit import Chem
|
|
7
|
+
|
|
8
|
+
from synkit.Graph.Stereo.descriptors import Reference, virtual_reference
|
|
9
|
+
from synkit.Graph.Stereo.supports import AxisStereoSupport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _cumulene_end_references(
|
|
13
|
+
atom: Chem.Atom,
|
|
14
|
+
axis_neighbor: int,
|
|
15
|
+
) -> tuple[Reference, Reference] | None:
|
|
16
|
+
"""Return zero-based terminal references for one cumulene end."""
|
|
17
|
+
center = atom.GetIdx()
|
|
18
|
+
references: list[Reference] = [
|
|
19
|
+
neighbor.GetIdx()
|
|
20
|
+
for neighbor in atom.GetNeighbors()
|
|
21
|
+
if neighbor.GetIdx() != axis_neighbor
|
|
22
|
+
]
|
|
23
|
+
hidden_hydrogens = int(atom.GetNumExplicitHs()) + int(atom.GetNumImplicitHs())
|
|
24
|
+
references.extend(virtual_reference("H", center) for _ in range(hidden_hydrogens))
|
|
25
|
+
if len(references) == 1 and atom.GetAtomicNum() == 15:
|
|
26
|
+
references.append(virtual_reference("LP", center))
|
|
27
|
+
if len(references) != 2 or references[0] == references[1]:
|
|
28
|
+
return None
|
|
29
|
+
return references[0], references[1]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _potential_cumulene_supports(
|
|
33
|
+
molecule: Chem.Mol,
|
|
34
|
+
) -> tuple[AxisStereoSupport, ...]:
|
|
35
|
+
"""Detect even-cumulene axes without assigning their orientation."""
|
|
36
|
+
double_graph = nx.Graph()
|
|
37
|
+
double_graph.add_edges_from(
|
|
38
|
+
(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx())
|
|
39
|
+
for bond in molecule.GetBonds()
|
|
40
|
+
if bond.GetBondType() == Chem.BondType.DOUBLE
|
|
41
|
+
)
|
|
42
|
+
supports = []
|
|
43
|
+
for component in nx.connected_components(double_graph):
|
|
44
|
+
axis = double_graph.subgraph(component)
|
|
45
|
+
edge_count = axis.number_of_edges()
|
|
46
|
+
if edge_count < 2 or edge_count % 2 != 0:
|
|
47
|
+
continue
|
|
48
|
+
if edge_count != axis.number_of_nodes() - 1:
|
|
49
|
+
continue
|
|
50
|
+
degrees = dict(axis.degree())
|
|
51
|
+
if any(degree > 2 for degree in degrees.values()):
|
|
52
|
+
continue
|
|
53
|
+
ends = sorted(node for node, degree in degrees.items() if degree == 1)
|
|
54
|
+
if len(ends) != 2:
|
|
55
|
+
continue
|
|
56
|
+
path = nx.shortest_path(axis, ends[0], ends[1])
|
|
57
|
+
if any(molecule.GetAtomWithIdx(node).GetDegree() != 2 for node in path[1:-1]):
|
|
58
|
+
continue
|
|
59
|
+
left_refs = _cumulene_end_references(
|
|
60
|
+
molecule.GetAtomWithIdx(path[0]),
|
|
61
|
+
path[1],
|
|
62
|
+
)
|
|
63
|
+
right_refs = _cumulene_end_references(
|
|
64
|
+
molecule.GetAtomWithIdx(path[-1]),
|
|
65
|
+
path[-2],
|
|
66
|
+
)
|
|
67
|
+
if left_refs is None or right_refs is None:
|
|
68
|
+
continue
|
|
69
|
+
supports.append(AxisStereoSupport(tuple(path), (left_refs, right_refs)))
|
|
70
|
+
return tuple(supports)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _potential_biaryl_supports(
|
|
74
|
+
molecule: Chem.Mol,
|
|
75
|
+
*,
|
|
76
|
+
include_extended_ring_axes: bool,
|
|
77
|
+
) -> tuple[AxisStereoSupport, ...]:
|
|
78
|
+
"""Detect ring-associated bond axes without orientation or barrier claims."""
|
|
79
|
+
supports = []
|
|
80
|
+
for bond in molecule.GetBonds():
|
|
81
|
+
if bond.GetBondType() not in {
|
|
82
|
+
Chem.BondType.SINGLE,
|
|
83
|
+
Chem.BondType.AROMATIC,
|
|
84
|
+
}:
|
|
85
|
+
continue
|
|
86
|
+
left_atom = bond.GetBeginAtom()
|
|
87
|
+
right_atom = bond.GetEndAtom()
|
|
88
|
+
hybridizations = {
|
|
89
|
+
left_atom.GetHybridization(),
|
|
90
|
+
right_atom.GetHybridization(),
|
|
91
|
+
}
|
|
92
|
+
containing_ring_sizes = tuple(
|
|
93
|
+
len(ring)
|
|
94
|
+
for ring in molecule.GetRingInfo().BondRings()
|
|
95
|
+
if bond.GetIdx() in ring
|
|
96
|
+
)
|
|
97
|
+
aromatic_ring_axis = (
|
|
98
|
+
bond.GetIsAromatic()
|
|
99
|
+
and left_atom.GetIsAromatic()
|
|
100
|
+
and right_atom.GetIsAromatic()
|
|
101
|
+
and any(size > 6 for size in containing_ring_sizes)
|
|
102
|
+
)
|
|
103
|
+
linked_ring_axis = (
|
|
104
|
+
not bond.GetIsAromatic()
|
|
105
|
+
and (left_atom.IsInRing() or right_atom.IsInRing())
|
|
106
|
+
and Chem.HybridizationType.SP2 in hybridizations
|
|
107
|
+
and hybridizations
|
|
108
|
+
<= {
|
|
109
|
+
Chem.HybridizationType.SP2,
|
|
110
|
+
Chem.HybridizationType.SP3,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
standard_biaryl_axis = (
|
|
114
|
+
not bond.GetIsAromatic()
|
|
115
|
+
and left_atom.GetIsAromatic()
|
|
116
|
+
and right_atom.GetIsAromatic()
|
|
117
|
+
)
|
|
118
|
+
if not standard_biaryl_axis and not (
|
|
119
|
+
include_extended_ring_axes and (aromatic_ring_axis or linked_ring_axis)
|
|
120
|
+
):
|
|
121
|
+
continue
|
|
122
|
+
left, right = left_atom.GetIdx(), right_atom.GetIdx()
|
|
123
|
+
left_refs = tuple(
|
|
124
|
+
neighbor.GetIdx()
|
|
125
|
+
for neighbor in left_atom.GetNeighbors()
|
|
126
|
+
if neighbor.GetIdx() != right_atom.GetIdx()
|
|
127
|
+
)
|
|
128
|
+
right_refs = tuple(
|
|
129
|
+
neighbor.GetIdx()
|
|
130
|
+
for neighbor in right_atom.GetNeighbors()
|
|
131
|
+
if neighbor.GetIdx() != left_atom.GetIdx()
|
|
132
|
+
)
|
|
133
|
+
if len(left_refs) != 2 or len(right_refs) != 2:
|
|
134
|
+
continue
|
|
135
|
+
supports.append(
|
|
136
|
+
AxisStereoSupport(
|
|
137
|
+
(left, right),
|
|
138
|
+
(left_refs, right_refs),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return tuple(supports)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def detect_potential_axis_supports(
|
|
145
|
+
molecule: Chem.Mol,
|
|
146
|
+
*,
|
|
147
|
+
include_extended_ring_axes: bool = False,
|
|
148
|
+
) -> tuple[tuple[str, AxisStereoSupport], ...]:
|
|
149
|
+
"""Return typed topology supports for potential molecular stereo axes."""
|
|
150
|
+
if molecule is None:
|
|
151
|
+
raise ValueError("Potential stereo-locus detection requires a molecule.")
|
|
152
|
+
working = Chem.Mol(molecule)
|
|
153
|
+
return (
|
|
154
|
+
*(
|
|
155
|
+
("cumulene_axis", support)
|
|
156
|
+
for support in _potential_cumulene_supports(working)
|
|
157
|
+
),
|
|
158
|
+
*(
|
|
159
|
+
("atrop_axis", support)
|
|
160
|
+
for support in _potential_biaryl_supports(
|
|
161
|
+
working,
|
|
162
|
+
include_extended_ring_axes=include_extended_ring_axes,
|
|
163
|
+
)
|
|
164
|
+
),
|
|
165
|
+
)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Data models and topology-colour helpers for molecular chirality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
from synkit.Graph.Stereo.descriptors import Reference
|
|
10
|
+
from synkit.Graph.Stereo.supports import AxisStereoSupport
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MolecularChirality(str, Enum):
|
|
14
|
+
"""Global relationship between a molecule and its mirror image."""
|
|
15
|
+
|
|
16
|
+
ACHIRAL = "Achiral"
|
|
17
|
+
CHIRAL = "Chiral"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MolecularChiralityOutcome(str, Enum):
|
|
21
|
+
"""Configuration-aware conclusion for possibly underspecified input."""
|
|
22
|
+
|
|
23
|
+
NECESSARILY_ACHIRAL = "necessarily_achiral"
|
|
24
|
+
NECESSARILY_CHIRAL = "necessarily_chiral"
|
|
25
|
+
CONFIGURATION_DEPENDENT = "configuration_dependent"
|
|
26
|
+
UNSUPPORTED_OR_INCOMPLETE = "unsupported_or_incomplete"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PotentialStereoLocusType(str, Enum):
|
|
30
|
+
"""Topology-supported stereo locus whose configuration is not supplied."""
|
|
31
|
+
|
|
32
|
+
CUMULENE_AXIS = "cumulene_axis"
|
|
33
|
+
ATROP_AXIS = "atrop_axis"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StereoOrientationState(str, Enum):
|
|
37
|
+
"""Information state of a potential stereo locus."""
|
|
38
|
+
|
|
39
|
+
UNSPECIFIED = "unspecified"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StereoStabilityStatus(str, Enum):
|
|
43
|
+
"""Configurational-stability evidence attached to a potential locus."""
|
|
44
|
+
|
|
45
|
+
UNASSESSED = "unassessed"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class UnspecifiedMolecularStereoError(ValueError):
|
|
49
|
+
"""Raised when strict binary classification receives unresolved stereo."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, loci: tuple[str, ...]) -> None:
|
|
52
|
+
self.loci = loci
|
|
53
|
+
joined = ", ".join(loci)
|
|
54
|
+
super().__init__(f"Molecular stereochemistry is underspecified at: {joined}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class MolecularChiralityResult:
|
|
59
|
+
"""Evidence returned by whole-molecule mirror classification."""
|
|
60
|
+
|
|
61
|
+
classification: MolecularChirality
|
|
62
|
+
mirror_isomorphism: tuple[tuple[int, int], ...] | None
|
|
63
|
+
descriptor_count: int
|
|
64
|
+
completed_tetrahedral_centers: tuple[int, ...]
|
|
65
|
+
# Compatibility fields retained for readers of the exploratory report.
|
|
66
|
+
# Sound classification never populates them from 2D connectivity alone.
|
|
67
|
+
completed_extended_tetrahedral_axes: tuple[tuple[int, int], ...] = ()
|
|
68
|
+
completed_biaryl_atrop_axes: tuple[tuple[int, int], ...] = ()
|
|
69
|
+
identity_profile: str = "element-isotope-hydrogen-connectivity"
|
|
70
|
+
decision_method: str = "exact_mirror_isomorphism"
|
|
71
|
+
input_stereo_status: str = "specified"
|
|
72
|
+
unspecified_stereo_loci: tuple[str, ...] = ()
|
|
73
|
+
potential_stereo_loci: tuple["PotentialStereoLocus", ...] = ()
|
|
74
|
+
configured_extended_descriptor_count: int = 0
|
|
75
|
+
stereo_evidence_source: str | None = None
|
|
76
|
+
extended_stability_status: str | None = None
|
|
77
|
+
population_fraction: float | None = None
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def is_chiral(self) -> bool:
|
|
81
|
+
"""Return ``True`` when no orientation-preserving mirror map exists."""
|
|
82
|
+
return self.classification is MolecularChirality.CHIRAL
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True, init=False)
|
|
86
|
+
class PotentialStereoLocus:
|
|
87
|
+
"""A typed candidate locus without an invented configuration.
|
|
88
|
+
|
|
89
|
+
Atom indices and material terminal references are zero-based RDKit atom
|
|
90
|
+
indices. Virtual hydrogen references use ``@H:<owner-index>``. Detection
|
|
91
|
+
from 2D connectivity proves only that the topology can support the locus;
|
|
92
|
+
it supplies neither handedness nor configurational-stability evidence.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
locus_type: PotentialStereoLocusType
|
|
96
|
+
support: AxisStereoSupport
|
|
97
|
+
orientation_state: StereoOrientationState = StereoOrientationState.UNSPECIFIED
|
|
98
|
+
evidence_provenance: str = "two_dimensional_connectivity"
|
|
99
|
+
stability_status: StereoStabilityStatus = StereoStabilityStatus.UNASSESSED
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
locus_type: PotentialStereoLocusType,
|
|
104
|
+
atom_indices: tuple[int, ...] | None = None,
|
|
105
|
+
terminal_references: tuple[tuple[Reference, ...], ...] | None = None,
|
|
106
|
+
orientation_state: StereoOrientationState = StereoOrientationState.UNSPECIFIED,
|
|
107
|
+
evidence_provenance: str = "two_dimensional_connectivity",
|
|
108
|
+
stability_status: StereoStabilityStatus = StereoStabilityStatus.UNASSESSED,
|
|
109
|
+
*,
|
|
110
|
+
support: AxisStereoSupport | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Build from typed support or the compatible legacy field pair."""
|
|
113
|
+
if support is None:
|
|
114
|
+
if atom_indices is None or terminal_references is None:
|
|
115
|
+
raise TypeError(
|
|
116
|
+
"Potential stereo loci require axis support or both legacy "
|
|
117
|
+
"atom_indices and terminal_references."
|
|
118
|
+
)
|
|
119
|
+
support = AxisStereoSupport(
|
|
120
|
+
tuple(atom_indices),
|
|
121
|
+
tuple(tuple(frame) for frame in terminal_references), # type: ignore[arg-type]
|
|
122
|
+
)
|
|
123
|
+
elif atom_indices is not None or terminal_references is not None:
|
|
124
|
+
raise TypeError("Supply typed support or legacy support fields, not both.")
|
|
125
|
+
object.__setattr__(self, "locus_type", PotentialStereoLocusType(locus_type))
|
|
126
|
+
object.__setattr__(self, "support", support)
|
|
127
|
+
object.__setattr__(
|
|
128
|
+
self, "orientation_state", StereoOrientationState(orientation_state)
|
|
129
|
+
)
|
|
130
|
+
object.__setattr__(self, "evidence_provenance", evidence_provenance)
|
|
131
|
+
object.__setattr__(
|
|
132
|
+
self, "stability_status", StereoStabilityStatus(stability_status)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def atom_indices(self) -> tuple[int, ...]:
|
|
137
|
+
"""Return the compatible axis-path view."""
|
|
138
|
+
return self.support.path
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def terminal_references(self) -> tuple[tuple[Reference, Reference], ...]:
|
|
142
|
+
"""Return the compatible terminal-frame view."""
|
|
143
|
+
return self.support.terminal_frames
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def identifier(self) -> str:
|
|
147
|
+
"""Return a deterministic diagnostic identifier."""
|
|
148
|
+
support = "-".join(str(index) for index in self.atom_indices)
|
|
149
|
+
return f"{self.locus_type.value}:{support}"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class MolecularChiralityAssessment:
|
|
154
|
+
"""Configuration-aware result over every enumerated stereo completion."""
|
|
155
|
+
|
|
156
|
+
outcome: MolecularChiralityOutcome
|
|
157
|
+
observed_classifications: tuple[MolecularChirality, ...]
|
|
158
|
+
input_stereo_status: str
|
|
159
|
+
unspecified_stereo_loci: tuple[str, ...]
|
|
160
|
+
unsupported_stereo_loci: tuple[str, ...]
|
|
161
|
+
theoretical_isomer_upper_bound: int
|
|
162
|
+
evaluated_isomer_count: int
|
|
163
|
+
enumeration_complete: bool
|
|
164
|
+
max_isomers: int
|
|
165
|
+
representative_isomers: tuple[tuple[str, MolecularChirality], ...] = ()
|
|
166
|
+
configured_alternative_count: int = 0
|
|
167
|
+
configured_population_status: str | None = None
|
|
168
|
+
decision_method: str = "stereo_completion_enumeration"
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def is_definitive(self) -> bool:
|
|
172
|
+
"""Return whether the outcome is proven despite possible truncation."""
|
|
173
|
+
return self.outcome is not MolecularChiralityOutcome.UNSUPPORTED_OR_INCOMPLETE
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def molecular_node_match(
|
|
177
|
+
left: Mapping[str, Any],
|
|
178
|
+
right: Mapping[str, Any],
|
|
179
|
+
) -> bool:
|
|
180
|
+
"""Match StereoMolGraph's explicit-H topology without expanding H atoms."""
|
|
181
|
+
return (
|
|
182
|
+
left.get("element") == right.get("element")
|
|
183
|
+
and int(left.get("isotope", 0)) == int(right.get("isotope", 0))
|
|
184
|
+
and int(left.get("hcount", 0)) == int(right.get("hcount", 0))
|
|
185
|
+
and left.get("_molecular_colour") == right.get("_molecular_colour")
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def connectivity_edge_match(
|
|
190
|
+
_left: Mapping[str, Any],
|
|
191
|
+
_right: Mapping[str, Any],
|
|
192
|
+
) -> bool:
|
|
193
|
+
"""Use connectivity, not one selected Lewis/resonance bond assignment."""
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _intern_colours(signatures: Mapping[int, Any]) -> dict[int, int]:
|
|
198
|
+
"""Intern comparable signatures into deterministic compact integers."""
|
|
199
|
+
palette = {
|
|
200
|
+
signature: index
|
|
201
|
+
for index, signature in enumerate(sorted(set(signatures.values()), key=repr))
|
|
202
|
+
}
|
|
203
|
+
return {node: palette[signature] for node, signature in signatures.items()}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def molecular_node_colours(graph: Any) -> dict[int, int]:
|
|
207
|
+
"""Return map-independent 1-WL molecular identity colours."""
|
|
208
|
+
colours = _intern_colours(
|
|
209
|
+
{
|
|
210
|
+
node: (
|
|
211
|
+
attributes.get("element"),
|
|
212
|
+
int(attributes.get("isotope", 0)),
|
|
213
|
+
int(attributes.get("hcount", 0)),
|
|
214
|
+
)
|
|
215
|
+
for node, attributes in graph.nodes(data=True)
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
for _iteration in range(max(1, len(graph))):
|
|
219
|
+
refined = _intern_colours(
|
|
220
|
+
{
|
|
221
|
+
node: (
|
|
222
|
+
colours[node],
|
|
223
|
+
tuple(sorted(colours[neighbor] for neighbor in graph[node])),
|
|
224
|
+
)
|
|
225
|
+
for node in graph
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
if refined == colours:
|
|
229
|
+
break
|
|
230
|
+
colours = refined
|
|
231
|
+
return colours
|