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,1105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
import networkx as nx
|
|
7
|
+
from rdkit import Chem
|
|
8
|
+
from rdkit.Chem import AllChem
|
|
9
|
+
|
|
10
|
+
from synkit.IO.debug import setup_logging
|
|
11
|
+
from synkit.Chem.Molecule.descriptors import (
|
|
12
|
+
compute_gasteiger_inplace,
|
|
13
|
+
PerMolDescriptors,
|
|
14
|
+
)
|
|
15
|
+
from synkit.Chem.Molecule.atom_features import AtomFeatureExtractor
|
|
16
|
+
from synkit.Chem.Molecule.graph_annotator import GraphAnnotator
|
|
17
|
+
from synkit.IO.mol_to_graph_chemistry import MolToGraphChemistryMixin
|
|
18
|
+
|
|
19
|
+
logger = setup_logging()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MolToGraph(MolToGraphChemistryMixin):
|
|
23
|
+
"""Convert an RDKit molecule into a NetworkX molecular graph.
|
|
24
|
+
|
|
25
|
+
The converter preserves the public API while adding corrected lone-pair
|
|
26
|
+
bookkeeping for aromatic heteroatoms, especially pyrrolic / ``[nH]``-like
|
|
27
|
+
aromatic nitrogen. RDKit aromatic bonds have order ``1.5``; for aromatic
|
|
28
|
+
lone-pair donor heteroatoms, this class counts aromatic bonds as sigma bonds
|
|
29
|
+
during lone-pair estimation.
|
|
30
|
+
|
|
31
|
+
Important node fields are ``estimated_lone_pairs``, ``lone_pairs``
|
|
32
|
+
backward-compatible alias, ``available_lone_pairs``, ``available_lp``,
|
|
33
|
+
``bond_order_sum``, ``lp_bond_order_sum``, ``valence_electrons``, and
|
|
34
|
+
``oxidation_state``.
|
|
35
|
+
|
|
36
|
+
:param node_attrs: Optional whitelist of node attributes to keep.
|
|
37
|
+
:type node_attrs: Optional[List[str]]
|
|
38
|
+
:param edge_attrs: Optional whitelist of edge attributes to keep.
|
|
39
|
+
:type edge_attrs: Optional[List[str]]
|
|
40
|
+
:param attr_profile: Atom feature profile, either ``"minimal"`` or
|
|
41
|
+
``"full"``.
|
|
42
|
+
:type attr_profile: str
|
|
43
|
+
:param with_topology: If ``True``, run :class:`GraphAnnotator` on the graph.
|
|
44
|
+
:type with_topology: bool
|
|
45
|
+
:param include_stereo_descriptors: If ``False``, omit the graph-level
|
|
46
|
+
relative-stereochemistry registry. Defaults to ``True``.
|
|
47
|
+
:type include_stereo_descriptors: bool
|
|
48
|
+
:raises ValueError: If ``attr_profile`` is unsupported.
|
|
49
|
+
|
|
50
|
+
.. code-block:: python
|
|
51
|
+
|
|
52
|
+
from rdkit import Chem
|
|
53
|
+
from synkit.IO.mol_to_graph import MolToGraph
|
|
54
|
+
|
|
55
|
+
mol = Chem.MolFromSmiles("c1cc[nH]c1")
|
|
56
|
+
graph = MolToGraph(attr_profile="minimal").transform(mol)
|
|
57
|
+
|
|
58
|
+
for node, data in graph.nodes(data=True):
|
|
59
|
+
print(node, data["element"], data["lone_pairs"], data["available_lp"])
|
|
60
|
+
|
|
61
|
+
.. code-block:: python
|
|
62
|
+
|
|
63
|
+
mol = Chem.MolFromSmiles("[CH3:1][CH2:2][Br:3]")
|
|
64
|
+
graph = MolToGraph(
|
|
65
|
+
node_attrs=["element", "atom_map", "charge", "lone_pairs"],
|
|
66
|
+
edge_attrs=["order", "kekule_order"],
|
|
67
|
+
).transform(mol, use_index_as_atom_map=True)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
SUPPORTED_PROFILES = ("minimal", "full")
|
|
71
|
+
AUGMENTED_NODE_ATTRS = frozenset(
|
|
72
|
+
{
|
|
73
|
+
"oxidation_state",
|
|
74
|
+
"available_lp",
|
|
75
|
+
"lone_pairs",
|
|
76
|
+
"valence_electrons",
|
|
77
|
+
"stereo_descriptor",
|
|
78
|
+
"cip_label",
|
|
79
|
+
"chiral_tag",
|
|
80
|
+
"bond_order_sum",
|
|
81
|
+
"lp_bond_order_sum",
|
|
82
|
+
"estimated_lone_pairs",
|
|
83
|
+
"available_lone_pairs",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
DIRECT_NODE_ATTRS = frozenset(
|
|
87
|
+
{
|
|
88
|
+
"element",
|
|
89
|
+
"isotope",
|
|
90
|
+
"aromatic",
|
|
91
|
+
"hcount",
|
|
92
|
+
"charge",
|
|
93
|
+
"radical",
|
|
94
|
+
"neighbors",
|
|
95
|
+
"atom_map",
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
KEKULE_EDGE_ATTRS = frozenset(
|
|
99
|
+
{"kekule_order", "sigma_order", "pi_order", "kekule_bond_type"}
|
|
100
|
+
)
|
|
101
|
+
DIRECT_EDGE_ATTRS = frozenset({"order", "aromatic"})
|
|
102
|
+
|
|
103
|
+
# Pauling electronegativities used for oxidation-state bookkeeping.
|
|
104
|
+
# Missing elements are skipped instead of guessed.
|
|
105
|
+
PAULING_EN: Dict[str, float] = {
|
|
106
|
+
"H": 2.20,
|
|
107
|
+
"B": 2.04,
|
|
108
|
+
"C": 2.55,
|
|
109
|
+
"N": 3.04,
|
|
110
|
+
"O": 3.44,
|
|
111
|
+
"F": 3.98,
|
|
112
|
+
"P": 2.19,
|
|
113
|
+
"S": 2.58,
|
|
114
|
+
"Cl": 3.16,
|
|
115
|
+
"Br": 2.96,
|
|
116
|
+
"I": 2.66,
|
|
117
|
+
"Se": 2.55,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
node_attrs: Optional[List[str]] = None,
|
|
123
|
+
edge_attrs: Optional[List[str]] = None,
|
|
124
|
+
*,
|
|
125
|
+
attr_profile: str = "minimal",
|
|
126
|
+
with_topology: bool = False,
|
|
127
|
+
include_stereo_descriptors: bool = True,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Initialize the converter.
|
|
130
|
+
|
|
131
|
+
:param node_attrs: Optional node-attribute whitelist.
|
|
132
|
+
:type node_attrs: Optional[List[str]]
|
|
133
|
+
:param edge_attrs: Optional edge-attribute whitelist.
|
|
134
|
+
:type edge_attrs: Optional[List[str]]
|
|
135
|
+
:param attr_profile: Feature profile, ``"minimal"`` or ``"full"``.
|
|
136
|
+
:type attr_profile: str
|
|
137
|
+
:param with_topology: Whether to add topology annotations.
|
|
138
|
+
:type with_topology: bool
|
|
139
|
+
:param include_stereo_descriptors: Whether to construct the graph-level
|
|
140
|
+
relative-stereochemistry registry.
|
|
141
|
+
:type include_stereo_descriptors: bool
|
|
142
|
+
:raises ValueError: If ``attr_profile`` is unsupported.
|
|
143
|
+
"""
|
|
144
|
+
if attr_profile not in self.SUPPORTED_PROFILES:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"Unsupported attr_profile: {attr_profile!r}. "
|
|
147
|
+
f"Supported: {self.SUPPORTED_PROFILES}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self.node_attrs: Optional[List[str]] = (
|
|
151
|
+
None if node_attrs is None else list(node_attrs)
|
|
152
|
+
)
|
|
153
|
+
self.edge_attrs: Optional[List[str]] = (
|
|
154
|
+
None if edge_attrs is None else list(edge_attrs)
|
|
155
|
+
)
|
|
156
|
+
self.attr_profile: str = attr_profile
|
|
157
|
+
self.with_topology: bool = bool(with_topology)
|
|
158
|
+
self.include_stereo_descriptors: bool = bool(include_stereo_descriptors)
|
|
159
|
+
|
|
160
|
+
self._graph: Optional[nx.Graph] = None
|
|
161
|
+
self._last_mol: Optional[Chem.Mol] = None
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
# Public conversion API
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def transform(
|
|
168
|
+
self,
|
|
169
|
+
mol: Chem.Mol,
|
|
170
|
+
drop_non_aam: bool = False,
|
|
171
|
+
use_index_as_atom_map: bool = False,
|
|
172
|
+
) -> nx.Graph:
|
|
173
|
+
"""Build a NetworkX graph from an RDKit molecule.
|
|
174
|
+
|
|
175
|
+
:param mol: RDKit molecule.
|
|
176
|
+
:type mol: Chem.Mol
|
|
177
|
+
:param drop_non_aam: If ``True``, exclude atoms with atom-map ``0``.
|
|
178
|
+
:type drop_non_aam: bool
|
|
179
|
+
:param use_index_as_atom_map: If ``True``, use non-zero atom-map numbers
|
|
180
|
+
as node identifiers; otherwise use ``atom index + 1``.
|
|
181
|
+
:type use_index_as_atom_map: bool
|
|
182
|
+
:return: Molecular graph with atom and bond attributes.
|
|
183
|
+
:rtype: nx.Graph
|
|
184
|
+
:raises ValueError: If ``drop_non_aam=True`` but
|
|
185
|
+
``use_index_as_atom_map=False``.
|
|
186
|
+
|
|
187
|
+
.. code-block:: python
|
|
188
|
+
|
|
189
|
+
mol = Chem.MolFromSmiles("[CH3:1][CH2:2][Br:3]")
|
|
190
|
+
graph = MolToGraph().transform(
|
|
191
|
+
mol,
|
|
192
|
+
drop_non_aam=True,
|
|
193
|
+
use_index_as_atom_map=True,
|
|
194
|
+
)
|
|
195
|
+
"""
|
|
196
|
+
if drop_non_aam and not use_index_as_atom_map:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
"drop_non_aam and use_index_as_atom_map must both be True "
|
|
199
|
+
"to drop unmapped atoms."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
self._last_mol = mol
|
|
203
|
+
|
|
204
|
+
needs_partial_charge = (
|
|
205
|
+
self.node_attrs is None or "partial_charge" in self.node_attrs
|
|
206
|
+
)
|
|
207
|
+
if needs_partial_charge:
|
|
208
|
+
try:
|
|
209
|
+
compute_gasteiger_inplace(mol)
|
|
210
|
+
except Exception:
|
|
211
|
+
logger.debug("Gasteiger computation failed (best-effort). Continuing.")
|
|
212
|
+
|
|
213
|
+
needs_oxidation_state = (
|
|
214
|
+
self.node_attrs is None or "oxidation_state" in self.node_attrs
|
|
215
|
+
)
|
|
216
|
+
needs_kekule = (
|
|
217
|
+
self.edge_attrs is None
|
|
218
|
+
or bool(self.KEKULE_EDGE_ATTRS.intersection(self.edge_attrs))
|
|
219
|
+
or needs_oxidation_state
|
|
220
|
+
)
|
|
221
|
+
kek_mol: Optional[Chem.Mol] = (
|
|
222
|
+
self._make_kekule_copy(mol) if needs_kekule else None
|
|
223
|
+
)
|
|
224
|
+
oxidation_states = (
|
|
225
|
+
self.estimate_oxidation_states(mol, kek_mol=kek_mol)
|
|
226
|
+
if needs_oxidation_state
|
|
227
|
+
else {}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
per: Optional[PerMolDescriptors] = None
|
|
231
|
+
if self.attr_profile == "full":
|
|
232
|
+
try:
|
|
233
|
+
per = PerMolDescriptors.compute(mol)
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
logger.debug("PerMolDescriptors.compute failed: %s", exc)
|
|
236
|
+
per = None
|
|
237
|
+
|
|
238
|
+
extractor = AtomFeatureExtractor(mol, per=per, profile=self.attr_profile)
|
|
239
|
+
needs_augmentation = self.node_attrs is None or bool(
|
|
240
|
+
self.AUGMENTED_NODE_ATTRS.intersection(self.node_attrs)
|
|
241
|
+
)
|
|
242
|
+
requested_node_attrs = frozenset(self.node_attrs or ())
|
|
243
|
+
use_direct_node_attrs = self.node_attrs is not None and (
|
|
244
|
+
requested_node_attrs.issubset(self.DIRECT_NODE_ATTRS)
|
|
245
|
+
)
|
|
246
|
+
requested_edge_attrs = frozenset(self.edge_attrs or ())
|
|
247
|
+
use_direct_edge_attrs = self.edge_attrs is not None and (
|
|
248
|
+
requested_edge_attrs.issubset(self.DIRECT_EDGE_ATTRS)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
graph = nx.Graph()
|
|
252
|
+
index_to_id: Dict[int, int] = {}
|
|
253
|
+
|
|
254
|
+
for atom in mol.GetAtoms():
|
|
255
|
+
atom_map = self._safe_atom_map(atom)
|
|
256
|
+
atom_id = (
|
|
257
|
+
atom_map
|
|
258
|
+
if (use_index_as_atom_map and atom_map != 0)
|
|
259
|
+
else atom.GetIdx() + 1
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if drop_non_aam and atom_map == 0:
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
if use_direct_node_attrs:
|
|
266
|
+
props = self._gather_direct_atom_properties(atom, requested_node_attrs)
|
|
267
|
+
else:
|
|
268
|
+
try:
|
|
269
|
+
props = extractor.build_dict(atom)
|
|
270
|
+
if needs_augmentation:
|
|
271
|
+
props = self._augment_atom_properties(
|
|
272
|
+
atom,
|
|
273
|
+
props,
|
|
274
|
+
oxidation_state=oxidation_states.get(atom.GetIdx()),
|
|
275
|
+
profile=self.attr_profile,
|
|
276
|
+
)
|
|
277
|
+
except Exception as exc:
|
|
278
|
+
logger.debug(
|
|
279
|
+
"AtomFeatureExtractor failed for atom %s: %s",
|
|
280
|
+
atom.GetIdx(),
|
|
281
|
+
exc,
|
|
282
|
+
)
|
|
283
|
+
props = self._gather_atom_properties(
|
|
284
|
+
atom,
|
|
285
|
+
oxidation_state=oxidation_states.get(atom.GetIdx()),
|
|
286
|
+
profile=self.attr_profile,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if self.node_attrs is not None:
|
|
290
|
+
props = {k: v for k, v in props.items() if k in self.node_attrs}
|
|
291
|
+
|
|
292
|
+
graph.add_node(atom_id, **props)
|
|
293
|
+
index_to_id[atom.GetIdx()] = atom_id
|
|
294
|
+
|
|
295
|
+
for bond in mol.GetBonds():
|
|
296
|
+
begin_idx = bond.GetBeginAtomIdx()
|
|
297
|
+
end_idx = bond.GetEndAtomIdx()
|
|
298
|
+
|
|
299
|
+
begin = index_to_id.get(begin_idx)
|
|
300
|
+
end = index_to_id.get(end_idx)
|
|
301
|
+
if begin is None or end is None:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
kek_bond: Optional[Chem.Bond] = None
|
|
305
|
+
if kek_mol is not None:
|
|
306
|
+
try:
|
|
307
|
+
kek_bond = kek_mol.GetBondWithIdx(bond.GetIdx())
|
|
308
|
+
except Exception:
|
|
309
|
+
kek_bond = None
|
|
310
|
+
|
|
311
|
+
if use_direct_edge_attrs:
|
|
312
|
+
bprops = self._gather_direct_bond_properties(bond, requested_edge_attrs)
|
|
313
|
+
else:
|
|
314
|
+
try:
|
|
315
|
+
bprops = self._gather_bond_properties(bond, kek_bond=kek_bond)
|
|
316
|
+
except Exception as exc:
|
|
317
|
+
logger.debug(
|
|
318
|
+
"Bond property collection failed for bond %s: %s",
|
|
319
|
+
bond.GetIdx(),
|
|
320
|
+
exc,
|
|
321
|
+
)
|
|
322
|
+
original_order = (
|
|
323
|
+
bond.GetBondTypeAsDouble()
|
|
324
|
+
if hasattr(bond, "GetBondTypeAsDouble")
|
|
325
|
+
else 1.0
|
|
326
|
+
)
|
|
327
|
+
bprops = {
|
|
328
|
+
"order": original_order,
|
|
329
|
+
"bond_type": (
|
|
330
|
+
str(bond.GetBondType())
|
|
331
|
+
if hasattr(bond, "GetBondType")
|
|
332
|
+
else "UNKNOWN"
|
|
333
|
+
),
|
|
334
|
+
"kekule_order": (
|
|
335
|
+
kek_bond.GetBondTypeAsDouble()
|
|
336
|
+
if kek_bond is not None
|
|
337
|
+
and hasattr(kek_bond, "GetBondTypeAsDouble")
|
|
338
|
+
else original_order
|
|
339
|
+
),
|
|
340
|
+
"kekule_bond_type": (
|
|
341
|
+
str(kek_bond.GetBondType())
|
|
342
|
+
if kek_bond is not None and hasattr(kek_bond, "GetBondType")
|
|
343
|
+
else (
|
|
344
|
+
str(bond.GetBondType())
|
|
345
|
+
if hasattr(bond, "GetBondType")
|
|
346
|
+
else "UNKNOWN"
|
|
347
|
+
)
|
|
348
|
+
),
|
|
349
|
+
"aromatic": (
|
|
350
|
+
bond.GetIsAromatic()
|
|
351
|
+
if hasattr(bond, "GetIsAromatic")
|
|
352
|
+
else False
|
|
353
|
+
),
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if self.edge_attrs is not None:
|
|
357
|
+
bprops = {k: v for k, v in bprops.items() if k in self.edge_attrs}
|
|
358
|
+
|
|
359
|
+
graph.add_edge(begin, end, **bprops)
|
|
360
|
+
|
|
361
|
+
# Relative stereo is registry-based because descriptors depend on
|
|
362
|
+
# multiple atoms. For ordinary unmapped reactor substrates, graph node
|
|
363
|
+
# IDs (RDKit index + 1) provide stable internal references; external
|
|
364
|
+
# atom maps remain reserved for mapped rules and serialized reactions.
|
|
365
|
+
descriptors = {}
|
|
366
|
+
if self.include_stereo_descriptors:
|
|
367
|
+
try:
|
|
368
|
+
from synkit.Graph.Stereo import descriptors_from_rdkit
|
|
369
|
+
|
|
370
|
+
atom_maps = [int(atom.GetAtomMapNum()) for atom in mol.GetAtoms()]
|
|
371
|
+
mapped = all(value > 0 for value in atom_maps) and len(
|
|
372
|
+
set(atom_maps)
|
|
373
|
+
) == len(atom_maps)
|
|
374
|
+
descriptors = descriptors_from_rdkit(mol, require_atom_maps=mapped)
|
|
375
|
+
except ImportError:
|
|
376
|
+
descriptors = {}
|
|
377
|
+
graph.graph["stereo_descriptors"] = descriptors
|
|
378
|
+
node_by_map = {
|
|
379
|
+
int(attrs.get("atom_map", 0)): node
|
|
380
|
+
for node, attrs in graph.nodes(data=True)
|
|
381
|
+
if int(attrs.get("atom_map", 0) or 0) > 0
|
|
382
|
+
}
|
|
383
|
+
for descriptor_id, descriptor in descriptors.items():
|
|
384
|
+
if descriptor_id.startswith("atom:"):
|
|
385
|
+
node = node_by_map.get(int(descriptor.center))
|
|
386
|
+
if node is not None:
|
|
387
|
+
graph.nodes[node].setdefault("stereo_descriptor_ids", []).append(
|
|
388
|
+
descriptor_id
|
|
389
|
+
)
|
|
390
|
+
else:
|
|
391
|
+
left, right = (int(value) for value in descriptor.atoms[2:4])
|
|
392
|
+
if (
|
|
393
|
+
left in node_by_map
|
|
394
|
+
and right in node_by_map
|
|
395
|
+
and graph.has_edge(node_by_map[left], node_by_map[right])
|
|
396
|
+
):
|
|
397
|
+
graph.edges[node_by_map[left], node_by_map[right]].setdefault(
|
|
398
|
+
"stereo_descriptor_ids", []
|
|
399
|
+
).append(descriptor_id)
|
|
400
|
+
|
|
401
|
+
if self.with_topology:
|
|
402
|
+
try:
|
|
403
|
+
GraphAnnotator(graph, in_place=True).annotate()
|
|
404
|
+
except Exception as exc:
|
|
405
|
+
logger.debug("GraphAnnotator failed: %s", exc)
|
|
406
|
+
|
|
407
|
+
return graph
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def _gather_direct_atom_properties(
|
|
411
|
+
atom: Chem.Atom,
|
|
412
|
+
attributes: frozenset[str],
|
|
413
|
+
) -> Dict[str, Any]:
|
|
414
|
+
"""Read a requested subset of inexpensive RDKit atom properties."""
|
|
415
|
+
props: Dict[str, Any] = {}
|
|
416
|
+
if "element" in attributes:
|
|
417
|
+
props["element"] = atom.GetSymbol()
|
|
418
|
+
if "isotope" in attributes:
|
|
419
|
+
props["isotope"] = atom.GetIsotope()
|
|
420
|
+
if "aromatic" in attributes:
|
|
421
|
+
props["aromatic"] = atom.GetIsAromatic()
|
|
422
|
+
if "hcount" in attributes:
|
|
423
|
+
props["hcount"] = atom.GetTotalNumHs()
|
|
424
|
+
if "charge" in attributes:
|
|
425
|
+
props["charge"] = atom.GetFormalCharge()
|
|
426
|
+
if "radical" in attributes:
|
|
427
|
+
props["radical"] = atom.GetNumRadicalElectrons()
|
|
428
|
+
if "neighbors" in attributes:
|
|
429
|
+
props["neighbors"] = sorted(
|
|
430
|
+
neighbor.GetSymbol() for neighbor in atom.GetNeighbors()
|
|
431
|
+
)
|
|
432
|
+
if "atom_map" in attributes:
|
|
433
|
+
props["atom_map"] = atom.GetAtomMapNum()
|
|
434
|
+
return props
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _gather_direct_bond_properties(
|
|
438
|
+
bond: Chem.Bond,
|
|
439
|
+
attributes: frozenset[str],
|
|
440
|
+
) -> Dict[str, Any]:
|
|
441
|
+
"""Read requested bond attributes without building discarded fields."""
|
|
442
|
+
props: Dict[str, Any] = {}
|
|
443
|
+
if "order" in attributes:
|
|
444
|
+
props["order"] = bond.GetBondTypeAsDouble()
|
|
445
|
+
if "aromatic" in attributes:
|
|
446
|
+
props["aromatic"] = bond.GetIsAromatic()
|
|
447
|
+
return props
|
|
448
|
+
|
|
449
|
+
def transform_store(
|
|
450
|
+
self,
|
|
451
|
+
mol: Chem.Mol,
|
|
452
|
+
drop_non_aam: bool = False,
|
|
453
|
+
use_index_as_atom_map: bool = False,
|
|
454
|
+
) -> "MolToGraph":
|
|
455
|
+
"""Build, store, and return ``self``.
|
|
456
|
+
|
|
457
|
+
:param mol: RDKit molecule.
|
|
458
|
+
:type mol: Chem.Mol
|
|
459
|
+
:param drop_non_aam: If ``True``, exclude atoms with atom-map ``0``.
|
|
460
|
+
:type drop_non_aam: bool
|
|
461
|
+
:param use_index_as_atom_map: If ``True``, use atom maps as node IDs.
|
|
462
|
+
:type use_index_as_atom_map: bool
|
|
463
|
+
:return: Current converter instance.
|
|
464
|
+
:rtype: MolToGraph
|
|
465
|
+
"""
|
|
466
|
+
self._graph = self.transform(
|
|
467
|
+
mol,
|
|
468
|
+
drop_non_aam=drop_non_aam,
|
|
469
|
+
use_index_as_atom_map=use_index_as_atom_map,
|
|
470
|
+
)
|
|
471
|
+
return self
|
|
472
|
+
|
|
473
|
+
@property
|
|
474
|
+
def graph(self) -> nx.Graph:
|
|
475
|
+
"""Return the graph produced by :meth:`transform_store`.
|
|
476
|
+
|
|
477
|
+
:return: Stored molecular graph.
|
|
478
|
+
:rtype: nx.Graph
|
|
479
|
+
:raises RuntimeError: If no graph has been stored yet.
|
|
480
|
+
"""
|
|
481
|
+
if self._graph is None:
|
|
482
|
+
raise RuntimeError(
|
|
483
|
+
"No graph produced yet. Call `transform_store(mol)` first."
|
|
484
|
+
)
|
|
485
|
+
return self._graph
|
|
486
|
+
|
|
487
|
+
def __repr__(self) -> str:
|
|
488
|
+
"""Return a compact representation.
|
|
489
|
+
|
|
490
|
+
:return: Developer-facing representation string.
|
|
491
|
+
:rtype: str
|
|
492
|
+
"""
|
|
493
|
+
try:
|
|
494
|
+
n = self._graph.number_of_nodes() if self._graph is not None else 0
|
|
495
|
+
except Exception:
|
|
496
|
+
n = -1
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
f"{self.__class__.__name__}(profile={self.attr_profile!r}, "
|
|
500
|
+
f"with_topology={self.with_topology}, "
|
|
501
|
+
f"include_stereo_descriptors={self.include_stereo_descriptors}, "
|
|
502
|
+
f"node_attrs={self.node_attrs!r}, "
|
|
503
|
+
f"edge_attrs={self.edge_attrs!r}, last_nodes={n})"
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
@classmethod
|
|
507
|
+
def help(cls) -> str:
|
|
508
|
+
"""Return a short usage string.
|
|
509
|
+
|
|
510
|
+
:return: Usage summary.
|
|
511
|
+
:rtype: str
|
|
512
|
+
"""
|
|
513
|
+
return (
|
|
514
|
+
"MolToGraph.help() -> str\n\n"
|
|
515
|
+
"Create with MolToGraph(node_attrs=[...], edge_attrs=[...], "
|
|
516
|
+
"attr_profile='minimal'|'full', with_topology=False).\n"
|
|
517
|
+
"Use `.transform(mol)` to get an nx.Graph, or `.transform_store(mol)` "
|
|
518
|
+
"to build and store the graph on the instance."
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
@classmethod
|
|
522
|
+
def _augment_atom_properties(
|
|
523
|
+
cls,
|
|
524
|
+
atom: Chem.Atom,
|
|
525
|
+
props: Dict[str, Any],
|
|
526
|
+
oxidation_state: Optional[float] = None,
|
|
527
|
+
*,
|
|
528
|
+
profile: str = "full",
|
|
529
|
+
) -> Dict[str, Any]:
|
|
530
|
+
"""Add electron-bookkeeping fields to existing atom attributes.
|
|
531
|
+
|
|
532
|
+
For both profiles sets ``oxidation_state``, ``radical``,
|
|
533
|
+
``available_lp``, and the backward-compatible ``lone_pairs`` alias.
|
|
534
|
+
The ``"full"`` profile additionally sets ``bond_order_sum``,
|
|
535
|
+
``lp_bond_order_sum``, ``valence_electrons``,
|
|
536
|
+
``estimated_lone_pairs``, and ``available_lone_pairs``.
|
|
537
|
+
|
|
538
|
+
:param atom: RDKit atom.
|
|
539
|
+
:type atom: Chem.Atom
|
|
540
|
+
:param props: Existing atom attributes from
|
|
541
|
+
:class:`~synkit.Chem.Molecule.atom_features.AtomFeatureExtractor`.
|
|
542
|
+
:type props: Dict[str, Any]
|
|
543
|
+
:param oxidation_state: Pre-computed oxidation state, or ``None``.
|
|
544
|
+
:type oxidation_state: Optional[float]
|
|
545
|
+
:param profile: Feature profile — ``"minimal"`` or ``"full"``.
|
|
546
|
+
:type profile: str
|
|
547
|
+
:return: Augmented atom attributes dict.
|
|
548
|
+
:rtype: Dict[str, Any]
|
|
549
|
+
"""
|
|
550
|
+
new_props = dict(props)
|
|
551
|
+
|
|
552
|
+
estimated_lone_pairs = cls.estimate_lone_pairs(atom)
|
|
553
|
+
available_lone_pairs = cls.estimate_available_lone_pairs(atom)
|
|
554
|
+
|
|
555
|
+
new_props["oxidation_state"] = (
|
|
556
|
+
None if oxidation_state is None else round(float(oxidation_state), 3)
|
|
557
|
+
)
|
|
558
|
+
new_props["radical"] = int(atom.GetNumRadicalElectrons())
|
|
559
|
+
new_props["available_lp"] = available_lone_pairs > 0
|
|
560
|
+
# Backward-compatible field used by SynEltra.
|
|
561
|
+
new_props["lone_pairs"] = estimated_lone_pairs
|
|
562
|
+
new_props["valence_electrons"] = cls._safe_valence_electrons(atom)
|
|
563
|
+
# v2 keeps relative stereo identity separate from derived display
|
|
564
|
+
# labels. Sprint 5 fills ``stereo_descriptor`` with an ordered,
|
|
565
|
+
# map-based descriptor; keeping the field now avoids schema churn.
|
|
566
|
+
new_props["stereo_descriptor"] = None
|
|
567
|
+
new_props["cip_label"] = (
|
|
568
|
+
atom.GetProp("_CIPCode") if atom.HasProp("_CIPCode") else None
|
|
569
|
+
)
|
|
570
|
+
new_props["chiral_tag"] = cls.get_chiral_tag(atom)
|
|
571
|
+
|
|
572
|
+
if profile == "full":
|
|
573
|
+
new_props["bond_order_sum"] = round(cls._safe_bond_order_sum(atom), 3)
|
|
574
|
+
new_props["lp_bond_order_sum"] = round(
|
|
575
|
+
cls._bond_order_sum_for_lone_pairs(atom), 3
|
|
576
|
+
)
|
|
577
|
+
new_props["estimated_lone_pairs"] = estimated_lone_pairs
|
|
578
|
+
new_props["available_lone_pairs"] = available_lone_pairs
|
|
579
|
+
|
|
580
|
+
return new_props
|
|
581
|
+
|
|
582
|
+
@staticmethod
|
|
583
|
+
def _gather_atom_properties(
|
|
584
|
+
atom: Chem.Atom,
|
|
585
|
+
oxidation_state: Optional[float] = None,
|
|
586
|
+
*,
|
|
587
|
+
profile: str = "full",
|
|
588
|
+
) -> Dict[str, Any]:
|
|
589
|
+
"""Collect fallback atom-level node attributes.
|
|
590
|
+
|
|
591
|
+
Minimal profile keys: ``element``, ``aromatic``, ``hcount``,
|
|
592
|
+
``charge``, ``radical``, ``isomer``, ``partial_charge``,
|
|
593
|
+
``hybridization``, ``in_ring``, ``neighbors``, ``atom_map``,
|
|
594
|
+
``oxidation_state``, ``available_lp``, ``lone_pairs``,
|
|
595
|
+
``valence_electrons``.
|
|
596
|
+
|
|
597
|
+
Full profile additionally includes ``bond_order_sum``,
|
|
598
|
+
``lp_bond_order_sum``, ``estimated_lone_pairs``,
|
|
599
|
+
``available_lone_pairs``.
|
|
600
|
+
|
|
601
|
+
:param atom: RDKit atom.
|
|
602
|
+
:type atom: Chem.Atom
|
|
603
|
+
:param oxidation_state: Pre-computed oxidation state, or ``None``.
|
|
604
|
+
:type oxidation_state: Optional[float]
|
|
605
|
+
:param profile: Feature profile — ``"minimal"`` or ``"full"``.
|
|
606
|
+
:type profile: str
|
|
607
|
+
:return: Node attribute dict.
|
|
608
|
+
:rtype: Dict[str, Any]
|
|
609
|
+
"""
|
|
610
|
+
try:
|
|
611
|
+
gcharge = (
|
|
612
|
+
round(float(atom.GetProp("_GasteigerCharge")), 3)
|
|
613
|
+
if atom.HasProp("_GasteigerCharge")
|
|
614
|
+
else 0.0
|
|
615
|
+
)
|
|
616
|
+
except Exception:
|
|
617
|
+
gcharge = 0.0
|
|
618
|
+
|
|
619
|
+
try:
|
|
620
|
+
neighbors = sorted(nb.GetSymbol() for nb in atom.GetNeighbors())
|
|
621
|
+
except Exception:
|
|
622
|
+
neighbors = []
|
|
623
|
+
|
|
624
|
+
atom_map = MolToGraph._safe_atom_map(atom)
|
|
625
|
+
estimated_lone_pairs = MolToGraph.estimate_lone_pairs(atom)
|
|
626
|
+
available_lone_pairs = MolToGraph.estimate_available_lone_pairs(atom)
|
|
627
|
+
|
|
628
|
+
props: Dict[str, Any] = {
|
|
629
|
+
"element": atom.GetSymbol(),
|
|
630
|
+
"isotope": atom.GetIsotope(),
|
|
631
|
+
"aromatic": atom.GetIsAromatic(),
|
|
632
|
+
"hcount": atom.GetTotalNumHs(),
|
|
633
|
+
"charge": atom.GetFormalCharge(),
|
|
634
|
+
"radical": atom.GetNumRadicalElectrons(),
|
|
635
|
+
"stereo_descriptor": None,
|
|
636
|
+
"cip_label": atom.GetProp("_CIPCode") if atom.HasProp("_CIPCode") else None,
|
|
637
|
+
"chiral_tag": MolToGraph.get_chiral_tag(atom),
|
|
638
|
+
# Compatibility display field only; do not use as a graph-stereo
|
|
639
|
+
# identity because it historically mapped RDKit CW/CCW to R/S.
|
|
640
|
+
"isomer": MolToGraph.get_stereochemistry(atom),
|
|
641
|
+
"partial_charge": gcharge,
|
|
642
|
+
"hybridization": str(atom.GetHybridization()),
|
|
643
|
+
"in_ring": atom.IsInRing(),
|
|
644
|
+
"neighbors": neighbors,
|
|
645
|
+
"atom_map": atom_map,
|
|
646
|
+
"oxidation_state": (
|
|
647
|
+
None if oxidation_state is None else round(float(oxidation_state), 3)
|
|
648
|
+
),
|
|
649
|
+
"available_lp": available_lone_pairs > 0,
|
|
650
|
+
"lone_pairs": estimated_lone_pairs,
|
|
651
|
+
"valence_electrons": MolToGraph._safe_valence_electrons(atom),
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if profile == "full":
|
|
655
|
+
props["bond_order_sum"] = round(MolToGraph._safe_bond_order_sum(atom), 3)
|
|
656
|
+
props["lp_bond_order_sum"] = round(
|
|
657
|
+
MolToGraph._bond_order_sum_for_lone_pairs(atom), 3
|
|
658
|
+
)
|
|
659
|
+
props["estimated_lone_pairs"] = estimated_lone_pairs
|
|
660
|
+
props["available_lone_pairs"] = available_lone_pairs
|
|
661
|
+
|
|
662
|
+
return props
|
|
663
|
+
|
|
664
|
+
@staticmethod
|
|
665
|
+
def _gather_bond_properties(
|
|
666
|
+
bond: Chem.Bond,
|
|
667
|
+
kek_bond: Optional[Chem.Bond] = None,
|
|
668
|
+
) -> Dict[str, Any]:
|
|
669
|
+
"""Collect bond-level edge attributes.
|
|
670
|
+
|
|
671
|
+
:param bond: Original RDKit bond.
|
|
672
|
+
:type bond: Chem.Bond
|
|
673
|
+
:param kek_bond: Matching bond from a kekulized copy.
|
|
674
|
+
:type kek_bond: Optional[Chem.Bond]
|
|
675
|
+
:return: Edge attributes.
|
|
676
|
+
:rtype: Dict[str, Any]
|
|
677
|
+
"""
|
|
678
|
+
try:
|
|
679
|
+
order = bond.GetBondTypeAsDouble()
|
|
680
|
+
except Exception:
|
|
681
|
+
order = 1.0
|
|
682
|
+
|
|
683
|
+
try:
|
|
684
|
+
bond_type = str(bond.GetBondType())
|
|
685
|
+
except Exception:
|
|
686
|
+
bond_type = "UNKNOWN"
|
|
687
|
+
|
|
688
|
+
try:
|
|
689
|
+
ez = MolToGraph.get_bond_stereochemistry(bond)
|
|
690
|
+
except Exception:
|
|
691
|
+
ez = "N"
|
|
692
|
+
|
|
693
|
+
try:
|
|
694
|
+
conjugated = bond.GetIsConjugated()
|
|
695
|
+
except Exception:
|
|
696
|
+
conjugated = False
|
|
697
|
+
|
|
698
|
+
try:
|
|
699
|
+
in_ring = bond.IsInRing()
|
|
700
|
+
except Exception:
|
|
701
|
+
in_ring = False
|
|
702
|
+
|
|
703
|
+
try:
|
|
704
|
+
aromatic = bond.GetIsAromatic()
|
|
705
|
+
except Exception:
|
|
706
|
+
aromatic = False
|
|
707
|
+
|
|
708
|
+
try:
|
|
709
|
+
kekule_order = (
|
|
710
|
+
kek_bond.GetBondTypeAsDouble() if kek_bond is not None else order
|
|
711
|
+
)
|
|
712
|
+
except Exception:
|
|
713
|
+
kekule_order = order
|
|
714
|
+
|
|
715
|
+
try:
|
|
716
|
+
kekule_bond_type = (
|
|
717
|
+
str(kek_bond.GetBondType()) if kek_bond is not None else bond_type
|
|
718
|
+
)
|
|
719
|
+
except Exception:
|
|
720
|
+
kekule_bond_type = bond_type
|
|
721
|
+
|
|
722
|
+
sigma_order, pi_order = MolToGraph._split_sigma_pi_order(kekule_order)
|
|
723
|
+
|
|
724
|
+
return {
|
|
725
|
+
"order": order,
|
|
726
|
+
"bond_type": bond_type,
|
|
727
|
+
"aromatic": aromatic,
|
|
728
|
+
"kekule_order": kekule_order,
|
|
729
|
+
"sigma_order": sigma_order,
|
|
730
|
+
"pi_order": pi_order,
|
|
731
|
+
"kekule_bond_type": kekule_bond_type,
|
|
732
|
+
"ez_isomer": ez,
|
|
733
|
+
"stereo_descriptor": None,
|
|
734
|
+
"ez_label": ez,
|
|
735
|
+
"conjugated": conjugated,
|
|
736
|
+
"in_ring": in_ring,
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
@staticmethod
|
|
740
|
+
def _split_sigma_pi_order(kekule_order: float) -> tuple[float, float]:
|
|
741
|
+
"""Split a Kekule bond order into sigma and pi contributions."""
|
|
742
|
+
order = max(0.0, float(kekule_order))
|
|
743
|
+
if order <= 0:
|
|
744
|
+
return 0.0, 0.0
|
|
745
|
+
return 1.0, max(0.0, order - 1.0)
|
|
746
|
+
|
|
747
|
+
# ------------------------------------------------------------------
|
|
748
|
+
# Stereochemistry helpers
|
|
749
|
+
# ------------------------------------------------------------------
|
|
750
|
+
|
|
751
|
+
@staticmethod
|
|
752
|
+
def get_stereochemistry(atom: Chem.Atom) -> str:
|
|
753
|
+
"""Return the legacy ``R``/``S`` display projection of a chiral tag.
|
|
754
|
+
|
|
755
|
+
This compatibility helper is **not** a CIP assignment and must not be
|
|
756
|
+
used as stereochemical identity. New code should use
|
|
757
|
+
:meth:`get_chiral_tag` until a v2 relative descriptor is available.
|
|
758
|
+
|
|
759
|
+
:param atom: RDKit atom.
|
|
760
|
+
:type atom: Chem.Atom
|
|
761
|
+
:return: Simple atom stereochemistry label.
|
|
762
|
+
:rtype: str
|
|
763
|
+
"""
|
|
764
|
+
chiral_tag = atom.GetChiralTag()
|
|
765
|
+
if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CCW:
|
|
766
|
+
return "S"
|
|
767
|
+
if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CW:
|
|
768
|
+
return "R"
|
|
769
|
+
return "N"
|
|
770
|
+
|
|
771
|
+
@staticmethod
|
|
772
|
+
def get_chiral_tag(atom: Chem.Atom) -> str:
|
|
773
|
+
"""Return RDKit's atom-order-relative tetrahedral tag.
|
|
774
|
+
|
|
775
|
+
``CW`` and ``CCW`` encode parity relative to atom ordering, not an
|
|
776
|
+
absolute CIP label. ``NONE`` covers atoms without a tetrahedral tag.
|
|
777
|
+
"""
|
|
778
|
+
chiral_tag = atom.GetChiralTag()
|
|
779
|
+
if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CCW:
|
|
780
|
+
return "CCW"
|
|
781
|
+
if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CW:
|
|
782
|
+
return "CW"
|
|
783
|
+
return "NONE"
|
|
784
|
+
|
|
785
|
+
@staticmethod
|
|
786
|
+
def get_bond_stereochemistry(bond: Chem.Bond) -> str:
|
|
787
|
+
"""Return ``E``, ``Z``, or ``N`` for double-bond stereochemistry.
|
|
788
|
+
|
|
789
|
+
:param bond: RDKit bond.
|
|
790
|
+
:type bond: Chem.Bond
|
|
791
|
+
:return: Simple bond stereochemistry label.
|
|
792
|
+
:rtype: str
|
|
793
|
+
"""
|
|
794
|
+
if bond.GetBondType() != Chem.BondType.DOUBLE:
|
|
795
|
+
return "N"
|
|
796
|
+
|
|
797
|
+
stereo = bond.GetStereo()
|
|
798
|
+
if stereo == Chem.BondStereo.STEREOE:
|
|
799
|
+
return "E"
|
|
800
|
+
if stereo == Chem.BondStereo.STEREOZ:
|
|
801
|
+
return "Z"
|
|
802
|
+
return "N"
|
|
803
|
+
|
|
804
|
+
# ------------------------------------------------------------------
|
|
805
|
+
# Mapping and legacy API
|
|
806
|
+
# ------------------------------------------------------------------
|
|
807
|
+
|
|
808
|
+
@staticmethod
|
|
809
|
+
def has_atom_mapping(mol: Chem.Mol) -> bool:
|
|
810
|
+
"""Return whether any atom has a non-zero atom-map number.
|
|
811
|
+
|
|
812
|
+
:param mol: RDKit molecule.
|
|
813
|
+
:type mol: Chem.Mol
|
|
814
|
+
:return: ``True`` if mapped.
|
|
815
|
+
:rtype: bool
|
|
816
|
+
"""
|
|
817
|
+
return any(atom.GetAtomMapNum() != 0 for atom in mol.GetAtoms())
|
|
818
|
+
|
|
819
|
+
@staticmethod
|
|
820
|
+
def random_atom_mapping(mol: Chem.Mol) -> Chem.Mol:
|
|
821
|
+
"""Assign random atom-map numbers from ``1`` to ``n`` in-place.
|
|
822
|
+
|
|
823
|
+
:param mol: RDKit molecule to mutate.
|
|
824
|
+
:type mol: Chem.Mol
|
|
825
|
+
:return: Same molecule with assigned atom-map numbers.
|
|
826
|
+
:rtype: Chem.Mol
|
|
827
|
+
"""
|
|
828
|
+
indices = list(range(1, mol.GetNumAtoms() + 1))
|
|
829
|
+
random.shuffle(indices)
|
|
830
|
+
for atom, idx in zip(mol.GetAtoms(), indices):
|
|
831
|
+
atom.SetAtomMapNum(idx)
|
|
832
|
+
return mol
|
|
833
|
+
|
|
834
|
+
@classmethod
|
|
835
|
+
def mol_to_graph(
|
|
836
|
+
cls,
|
|
837
|
+
mol: Chem.Mol,
|
|
838
|
+
drop_non_aam: bool = False,
|
|
839
|
+
light_weight: bool = False,
|
|
840
|
+
use_index_as_atom_map: bool = False,
|
|
841
|
+
) -> nx.Graph:
|
|
842
|
+
"""Backward-compatible graph converter.
|
|
843
|
+
|
|
844
|
+
New code should usually prefer :meth:`transform`.
|
|
845
|
+
|
|
846
|
+
:param mol: RDKit molecule.
|
|
847
|
+
:type mol: Chem.Mol
|
|
848
|
+
:param drop_non_aam: If ``True``, remove atoms with atom-map ``0``.
|
|
849
|
+
:type drop_non_aam: bool
|
|
850
|
+
:param light_weight: If ``True``, use reduced attributes.
|
|
851
|
+
:type light_weight: bool
|
|
852
|
+
:param use_index_as_atom_map: If ``True``, use atom maps as node IDs.
|
|
853
|
+
:type use_index_as_atom_map: bool
|
|
854
|
+
:return: Molecular graph.
|
|
855
|
+
:rtype: nx.Graph
|
|
856
|
+
:raises ValueError: If ``drop_non_aam=True`` but
|
|
857
|
+
``use_index_as_atom_map=False``.
|
|
858
|
+
|
|
859
|
+
.. code-block:: python
|
|
860
|
+
|
|
861
|
+
mol = Chem.MolFromSmiles("[CH3:1][CH2:2][Br:3]")
|
|
862
|
+
graph = MolToGraph.mol_to_graph(
|
|
863
|
+
mol,
|
|
864
|
+
drop_non_aam=True,
|
|
865
|
+
light_weight=True,
|
|
866
|
+
use_index_as_atom_map=True,
|
|
867
|
+
)
|
|
868
|
+
"""
|
|
869
|
+
if drop_non_aam and not use_index_as_atom_map:
|
|
870
|
+
raise ValueError(
|
|
871
|
+
"drop_non_aam and use_index_as_atom_map must be both False or both True."
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
if light_weight:
|
|
875
|
+
return cls._create_light_weight_graph(
|
|
876
|
+
mol,
|
|
877
|
+
drop_non_aam=drop_non_aam,
|
|
878
|
+
use_index_as_atom_map=use_index_as_atom_map,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
return cls._create_detailed_graph(
|
|
882
|
+
mol,
|
|
883
|
+
drop_non_aam=drop_non_aam,
|
|
884
|
+
use_index_as_atom_map=use_index_as_atom_map,
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
@classmethod
|
|
888
|
+
def _create_light_weight_graph(
|
|
889
|
+
cls,
|
|
890
|
+
mol: Chem.Mol,
|
|
891
|
+
drop_non_aam: bool = False,
|
|
892
|
+
use_index_as_atom_map: bool = False,
|
|
893
|
+
) -> nx.Graph:
|
|
894
|
+
"""Create a lightweight graph with corrected lone-pair fields.
|
|
895
|
+
|
|
896
|
+
Node attributes: ``element``, ``aromatic``, ``hcount``, ``charge``,
|
|
897
|
+
``radical``, ``neighbors``, ``atom_map``, ``oxidation_state``,
|
|
898
|
+
``available_lp``, ``lone_pairs``, ``valence_electrons``.
|
|
899
|
+
Edge attributes: ``order``, ``bond_type``, ``aromatic``,
|
|
900
|
+
``kekule_order``, ``sigma_order``, ``pi_order``,
|
|
901
|
+
``kekule_bond_type``.
|
|
902
|
+
|
|
903
|
+
:param mol: RDKit molecule.
|
|
904
|
+
:type mol: Chem.Mol
|
|
905
|
+
:param drop_non_aam: If ``True``, remove atoms with atom-map ``0``.
|
|
906
|
+
:type drop_non_aam: bool
|
|
907
|
+
:param use_index_as_atom_map: If ``True``, use atom-map numbers as node
|
|
908
|
+
IDs for mapped atoms; unmapped atoms fall back to
|
|
909
|
+
``atom.GetIdx() + 1``.
|
|
910
|
+
:type use_index_as_atom_map: bool
|
|
911
|
+
:return: Lightweight molecular graph.
|
|
912
|
+
:rtype: nx.Graph
|
|
913
|
+
"""
|
|
914
|
+
graph = nx.Graph()
|
|
915
|
+
kek_mol: Optional[Chem.Mol] = cls._make_kekule_copy(mol)
|
|
916
|
+
oxidation_states = cls.estimate_oxidation_states(mol, kek_mol=kek_mol)
|
|
917
|
+
|
|
918
|
+
for atom in mol.GetAtoms():
|
|
919
|
+
atom_map = cls._safe_atom_map(atom)
|
|
920
|
+
atom_id = (
|
|
921
|
+
atom_map
|
|
922
|
+
if (use_index_as_atom_map and atom_map != 0)
|
|
923
|
+
else atom.GetIdx() + 1
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
if drop_non_aam and atom_map == 0:
|
|
927
|
+
continue
|
|
928
|
+
|
|
929
|
+
try:
|
|
930
|
+
neighbors = sorted(nb.GetSymbol() for nb in atom.GetNeighbors())
|
|
931
|
+
except Exception:
|
|
932
|
+
neighbors = []
|
|
933
|
+
|
|
934
|
+
estimated_lone_pairs = cls.estimate_lone_pairs(atom)
|
|
935
|
+
available_lone_pairs = cls.estimate_available_lone_pairs(atom)
|
|
936
|
+
|
|
937
|
+
graph.add_node(
|
|
938
|
+
atom_id,
|
|
939
|
+
element=atom.GetSymbol(),
|
|
940
|
+
aromatic=atom.GetIsAromatic(),
|
|
941
|
+
hcount=atom.GetTotalNumHs(),
|
|
942
|
+
charge=atom.GetFormalCharge(),
|
|
943
|
+
radical=atom.GetNumRadicalElectrons(),
|
|
944
|
+
neighbors=neighbors,
|
|
945
|
+
atom_map=atom_map,
|
|
946
|
+
oxidation_state=round(
|
|
947
|
+
float(oxidation_states.get(atom.GetIdx(), 0.0)), 3
|
|
948
|
+
),
|
|
949
|
+
available_lp=available_lone_pairs > 0,
|
|
950
|
+
lone_pairs=estimated_lone_pairs,
|
|
951
|
+
valence_electrons=cls._safe_valence_electrons(atom),
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
for bond in mol.GetBonds():
|
|
955
|
+
begin_atom = bond.GetBeginAtom()
|
|
956
|
+
end_atom = bond.GetEndAtom()
|
|
957
|
+
|
|
958
|
+
begin_map = cls._safe_atom_map(begin_atom)
|
|
959
|
+
end_map = cls._safe_atom_map(end_atom)
|
|
960
|
+
|
|
961
|
+
if drop_non_aam and (begin_map == 0 or end_map == 0):
|
|
962
|
+
continue
|
|
963
|
+
|
|
964
|
+
begin_id = (
|
|
965
|
+
begin_map
|
|
966
|
+
if (use_index_as_atom_map and begin_map != 0)
|
|
967
|
+
else begin_atom.GetIdx() + 1
|
|
968
|
+
)
|
|
969
|
+
end_id = (
|
|
970
|
+
end_map
|
|
971
|
+
if (use_index_as_atom_map and end_map != 0)
|
|
972
|
+
else end_atom.GetIdx() + 1
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
kek_bond: Optional[Chem.Bond] = None
|
|
976
|
+
if kek_mol is not None:
|
|
977
|
+
try:
|
|
978
|
+
kek_bond = kek_mol.GetBondWithIdx(bond.GetIdx())
|
|
979
|
+
except Exception:
|
|
980
|
+
kek_bond = None
|
|
981
|
+
|
|
982
|
+
try:
|
|
983
|
+
order = bond.GetBondTypeAsDouble()
|
|
984
|
+
except Exception:
|
|
985
|
+
order = 1.0
|
|
986
|
+
|
|
987
|
+
try:
|
|
988
|
+
aromatic = bond.GetIsAromatic()
|
|
989
|
+
except Exception:
|
|
990
|
+
aromatic = False
|
|
991
|
+
|
|
992
|
+
try:
|
|
993
|
+
bond_type = str(bond.GetBondType())
|
|
994
|
+
except Exception:
|
|
995
|
+
bond_type = "UNKNOWN"
|
|
996
|
+
|
|
997
|
+
try:
|
|
998
|
+
kekule_order = (
|
|
999
|
+
kek_bond.GetBondTypeAsDouble() if kek_bond is not None else order
|
|
1000
|
+
)
|
|
1001
|
+
except Exception:
|
|
1002
|
+
kekule_order = order
|
|
1003
|
+
|
|
1004
|
+
try:
|
|
1005
|
+
kekule_bond_type = (
|
|
1006
|
+
str(kek_bond.GetBondType()) if kek_bond is not None else bond_type
|
|
1007
|
+
)
|
|
1008
|
+
except Exception:
|
|
1009
|
+
kekule_bond_type = bond_type
|
|
1010
|
+
|
|
1011
|
+
graph.add_edge(
|
|
1012
|
+
begin_id,
|
|
1013
|
+
end_id,
|
|
1014
|
+
order=order,
|
|
1015
|
+
bond_type=bond_type,
|
|
1016
|
+
aromatic=aromatic,
|
|
1017
|
+
kekule_order=kekule_order,
|
|
1018
|
+
kekule_bond_type=kekule_bond_type,
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
return graph
|
|
1022
|
+
|
|
1023
|
+
@classmethod
|
|
1024
|
+
def _create_detailed_graph(
|
|
1025
|
+
cls,
|
|
1026
|
+
mol: Chem.Mol,
|
|
1027
|
+
drop_non_aam: bool = False,
|
|
1028
|
+
use_index_as_atom_map: bool = False,
|
|
1029
|
+
) -> nx.Graph:
|
|
1030
|
+
"""Create a detailed graph with fallback atom and bond attributes.
|
|
1031
|
+
|
|
1032
|
+
:param mol: RDKit molecule.
|
|
1033
|
+
:type mol: Chem.Mol
|
|
1034
|
+
:param drop_non_aam: If ``True``, remove unmapped atoms.
|
|
1035
|
+
:type drop_non_aam: bool
|
|
1036
|
+
:param use_index_as_atom_map: If ``True``, use atom maps as node IDs.
|
|
1037
|
+
:type use_index_as_atom_map: bool
|
|
1038
|
+
:return: Detailed molecular graph.
|
|
1039
|
+
:rtype: nx.Graph
|
|
1040
|
+
"""
|
|
1041
|
+
try:
|
|
1042
|
+
compute_gasteiger_inplace(mol)
|
|
1043
|
+
except Exception:
|
|
1044
|
+
logger.debug("Gasteiger compute failed inside _create_detailed_graph.")
|
|
1045
|
+
|
|
1046
|
+
graph = nx.Graph()
|
|
1047
|
+
idx_map: Dict[int, int] = {}
|
|
1048
|
+
kek_mol: Optional[Chem.Mol] = cls._make_kekule_copy(mol)
|
|
1049
|
+
oxidation_states = cls.estimate_oxidation_states(mol, kek_mol=kek_mol)
|
|
1050
|
+
|
|
1051
|
+
for atom in mol.GetAtoms():
|
|
1052
|
+
atom_map = cls._safe_atom_map(atom)
|
|
1053
|
+
atom_id = (
|
|
1054
|
+
atom_map
|
|
1055
|
+
if (use_index_as_atom_map and atom_map != 0)
|
|
1056
|
+
else atom.GetIdx() + 1
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
if drop_non_aam and atom_map == 0:
|
|
1060
|
+
continue
|
|
1061
|
+
|
|
1062
|
+
graph.add_node(
|
|
1063
|
+
atom_id,
|
|
1064
|
+
**cls._gather_atom_properties(
|
|
1065
|
+
atom,
|
|
1066
|
+
oxidation_state=oxidation_states.get(atom.GetIdx()),
|
|
1067
|
+
),
|
|
1068
|
+
)
|
|
1069
|
+
idx_map[atom.GetIdx()] = atom_id
|
|
1070
|
+
|
|
1071
|
+
for bond in mol.GetBonds():
|
|
1072
|
+
begin = idx_map.get(bond.GetBeginAtomIdx())
|
|
1073
|
+
end = idx_map.get(bond.GetEndAtomIdx())
|
|
1074
|
+
|
|
1075
|
+
if begin is None or end is None:
|
|
1076
|
+
continue
|
|
1077
|
+
|
|
1078
|
+
kek_bond: Optional[Chem.Bond] = None
|
|
1079
|
+
if kek_mol is not None:
|
|
1080
|
+
try:
|
|
1081
|
+
kek_bond = kek_mol.GetBondWithIdx(bond.GetIdx())
|
|
1082
|
+
except Exception:
|
|
1083
|
+
kek_bond = None
|
|
1084
|
+
|
|
1085
|
+
graph.add_edge(
|
|
1086
|
+
begin,
|
|
1087
|
+
end,
|
|
1088
|
+
**cls._gather_bond_properties(bond, kek_bond=kek_bond),
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
return graph
|
|
1092
|
+
|
|
1093
|
+
@staticmethod
|
|
1094
|
+
def add_partial_charges(mol: Chem.Mol) -> None:
|
|
1095
|
+
"""Compute Gasteiger partial charges in-place.
|
|
1096
|
+
|
|
1097
|
+
:param mol: RDKit molecule to modify.
|
|
1098
|
+
:type mol: Chem.Mol
|
|
1099
|
+
:return: ``None``.
|
|
1100
|
+
:rtype: None
|
|
1101
|
+
"""
|
|
1102
|
+
try:
|
|
1103
|
+
AllChem.ComputeGasteigerCharges(mol)
|
|
1104
|
+
except Exception as exc:
|
|
1105
|
+
logger.error("Error computing Gasteiger charges: %s", exc)
|