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,817 @@
|
|
|
1
|
+
# standardize.py
|
|
2
|
+
"""
|
|
3
|
+
Molecule standardization helpers and a chainable MolStandardizer class.
|
|
4
|
+
|
|
5
|
+
Provides:
|
|
6
|
+
- lightweight helpers: sanitize_and_canonicalize_smiles, fix_radical_rsmi, remove_isotopes, ...
|
|
7
|
+
- MolStandardizer: fluent, chainable standardization API with convenience constructors.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from rdkit import Chem
|
|
16
|
+
from rdkit.Chem import rdmolops
|
|
17
|
+
from rdkit.Chem.SaltRemover import SaltRemover
|
|
18
|
+
|
|
19
|
+
# rdMolStandardize may not be available in all RDKit builds; import defensively.
|
|
20
|
+
try:
|
|
21
|
+
from rdkit.Chem.MolStandardize import rdMolStandardize # type: ignore
|
|
22
|
+
except Exception: # pragma: no cover - allow imports to fail gracefully
|
|
23
|
+
rdMolStandardize = None # type: ignore
|
|
24
|
+
|
|
25
|
+
# Prefer project's logger if available, otherwise fallback to stdlib logging
|
|
26
|
+
try:
|
|
27
|
+
# If running in the synkit environment this will pick up project logger
|
|
28
|
+
from synkit.IO.debug import setup_logging # type: ignore
|
|
29
|
+
|
|
30
|
+
logger = setup_logging()
|
|
31
|
+
except Exception:
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
if not logger.handlers:
|
|
34
|
+
logging.basicConfig(level=logging.WARNING)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# -------------------------
|
|
38
|
+
# Simple functional helpers
|
|
39
|
+
# -------------------------
|
|
40
|
+
def sanitize_and_canonicalize_smiles(smiles: str) -> Optional[str]:
|
|
41
|
+
"""Sanitize and canonicalize a SMILES string.
|
|
42
|
+
|
|
43
|
+
:param smiles: Input SMILES string.
|
|
44
|
+
:type smiles: str
|
|
45
|
+
:return: Canonical SMILES if valid, otherwise ``None``.
|
|
46
|
+
:rtype: Optional[str]
|
|
47
|
+
|
|
48
|
+
.. rubric:: Notes
|
|
49
|
+
|
|
50
|
+
The function attempts to parse and sanitize the SMILES with RDKit. On any
|
|
51
|
+
parsing/sanitization failure it returns ``None`` (best-effort policy).
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
mol = Chem.MolFromSmiles(smiles, sanitize=True)
|
|
55
|
+
if mol is None:
|
|
56
|
+
return None
|
|
57
|
+
Chem.SanitizeMol(mol)
|
|
58
|
+
return Chem.MolToSmiles(mol, canonical=True)
|
|
59
|
+
except Exception:
|
|
60
|
+
logger.debug(
|
|
61
|
+
"sanitize_and_canonicalize_smiles failed for %r", smiles, exc_info=True
|
|
62
|
+
)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def normalize_molecule(mol: Chem.Mol) -> Chem.Mol:
|
|
67
|
+
"""Normalize a molecule using rdMolStandardize.Normalizer when available.
|
|
68
|
+
|
|
69
|
+
:param mol: RDKit Mol object to normalize.
|
|
70
|
+
:type mol: Chem.Mol
|
|
71
|
+
:return: Normalized RDKit Mol object (or original if normalizer missing).
|
|
72
|
+
:rtype: Chem.Mol
|
|
73
|
+
"""
|
|
74
|
+
if rdMolStandardize is None:
|
|
75
|
+
logger.debug("rdMolStandardize not available; normalize skipped.")
|
|
76
|
+
return mol
|
|
77
|
+
try:
|
|
78
|
+
normalizer_cls = getattr(rdMolStandardize, "Normalizer", None)
|
|
79
|
+
if normalizer_cls is None:
|
|
80
|
+
logger.debug("Normalizer not found in rdMolStandardize; normalize skipped.")
|
|
81
|
+
return mol
|
|
82
|
+
return normalizer_cls().normalize(mol)
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
logger.debug("normalize_molecule failed: %s", exc, exc_info=True)
|
|
85
|
+
return mol
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def canonicalize_tautomer(mol: Chem.Mol) -> Chem.Mol:
|
|
89
|
+
"""Canonicalize tautomeric form using rdMolStandardize.TautomerEnumerator if available.
|
|
90
|
+
|
|
91
|
+
:param mol: RDKit Mol object to canonicalize.
|
|
92
|
+
:type mol: Chem.Mol
|
|
93
|
+
:return: Canonicalized tautomer Mol (or original if unavailable).
|
|
94
|
+
:rtype: Chem.Mol
|
|
95
|
+
"""
|
|
96
|
+
if rdMolStandardize is None:
|
|
97
|
+
logger.debug("rdMolStandardize not available; canonicalize_tautomer skipped.")
|
|
98
|
+
return mol
|
|
99
|
+
try:
|
|
100
|
+
taut_enum_cls = getattr(rdMolStandardize, "TautomerEnumerator", None)
|
|
101
|
+
if taut_enum_cls is None:
|
|
102
|
+
logger.debug(
|
|
103
|
+
"TautomerEnumerator not present; canonicalize_tautomer skipped."
|
|
104
|
+
)
|
|
105
|
+
return mol
|
|
106
|
+
return taut_enum_cls().Canonicalize(mol)
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
logger.debug("canonicalize_tautomer failed: %s", exc, exc_info=True)
|
|
109
|
+
return mol
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def salts_remover(mol: Chem.Mol, remover: Optional[SaltRemover] = None) -> Chem.Mol:
|
|
113
|
+
"""Remove salts from a molecule using RDKit's SaltRemover.
|
|
114
|
+
|
|
115
|
+
:param mol: RDKit Mol object to process.
|
|
116
|
+
:type mol: Chem.Mol
|
|
117
|
+
:param remover: Optional SaltRemover instance to use.
|
|
118
|
+
:type remover: Optional[SaltRemover]
|
|
119
|
+
:return: Mol object with salts removed (best-effort).
|
|
120
|
+
:rtype: Chem.Mol
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
_rem = remover if remover is not None else SaltRemover()
|
|
124
|
+
return _rem.StripMol(mol)
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
logger.debug("salts_remover failed: %s", exc, exc_info=True)
|
|
127
|
+
return mol
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def uncharge_molecule(mol: Chem.Mol) -> Chem.Mol:
|
|
131
|
+
"""Neutralize/uncharge a molecule using rdMolStandardize.Uncharger if available.
|
|
132
|
+
|
|
133
|
+
:param mol: RDKit Mol object to neutralize.
|
|
134
|
+
:type mol: Chem.Mol
|
|
135
|
+
:return: Neutralized Mol object (or original if uncharger missing).
|
|
136
|
+
:rtype: Chem.Mol
|
|
137
|
+
"""
|
|
138
|
+
if rdMolStandardize is None:
|
|
139
|
+
logger.debug("rdMolStandardize not available; uncharge skipped.")
|
|
140
|
+
return mol
|
|
141
|
+
try:
|
|
142
|
+
uncharger_cls = getattr(rdMolStandardize, "Uncharger", None)
|
|
143
|
+
if uncharger_cls is None:
|
|
144
|
+
logger.debug("Uncharger not found; uncharge skipped.")
|
|
145
|
+
return mol
|
|
146
|
+
return uncharger_cls().uncharge(mol)
|
|
147
|
+
except Exception as exc:
|
|
148
|
+
logger.debug("uncharge_molecule failed: %s", exc, exc_info=True)
|
|
149
|
+
return mol
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def fragments_remover(mol: Chem.Mol) -> Optional[Chem.Mol]:
|
|
153
|
+
"""Keep only the largest fragment by atom count.
|
|
154
|
+
|
|
155
|
+
:param mol: RDKit Mol object to fragment.
|
|
156
|
+
:type mol: Chem.Mol
|
|
157
|
+
:return: Mol of the largest fragment, or None if input is empty.
|
|
158
|
+
:rtype: Optional[Chem.Mol]
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
frags = Chem.GetMolFrags(mol, asMols=True, sanitizeFrags=True)
|
|
162
|
+
return max(frags, default=None, key=lambda m: m.GetNumAtoms())
|
|
163
|
+
except Exception:
|
|
164
|
+
logger.debug("fragments_remover failed.", exc_info=True)
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def remove_explicit_hydrogens(mol: Chem.Mol) -> Chem.Mol:
|
|
169
|
+
"""Remove explicit hydrogens from the molecule (Chem.RemoveHs wrapper).
|
|
170
|
+
|
|
171
|
+
:param mol: RDKit Mol object to process.
|
|
172
|
+
:type mol: Chem.Mol
|
|
173
|
+
:return: Mol object without explicit hydrogens.
|
|
174
|
+
:rtype: Chem.Mol
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
return Chem.RemoveHs(mol)
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
logger.debug("remove_explicit_hydrogens failed: %s", exc, exc_info=True)
|
|
180
|
+
return mol
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# -------------------------
|
|
184
|
+
# Radical-handling helpers (further split to reduce complexity)
|
|
185
|
+
# -------------------------
|
|
186
|
+
def _zero_out_radicals_on_atoms(mol: Chem.Mol) -> None:
|
|
187
|
+
"""Iterate atoms and zero radical electron counts; also increment explicit H count
|
|
188
|
+
by the number of radical electrons for each atom (best-effort).
|
|
189
|
+
|
|
190
|
+
This mutates the input molecule in-place.
|
|
191
|
+
|
|
192
|
+
:param mol: RDKit Mol to operate on (mutated in place).
|
|
193
|
+
:type mol: Chem.Mol
|
|
194
|
+
:return: None
|
|
195
|
+
:rtype: None
|
|
196
|
+
"""
|
|
197
|
+
for atom in mol.GetAtoms():
|
|
198
|
+
try:
|
|
199
|
+
rad = int(atom.GetNumRadicalElectrons())
|
|
200
|
+
except Exception:
|
|
201
|
+
rad = 0
|
|
202
|
+
if rad > 0:
|
|
203
|
+
try:
|
|
204
|
+
atom.SetNumExplicitHs(atom.GetNumExplicitHs() + rad)
|
|
205
|
+
atom.SetNumRadicalElectrons(0)
|
|
206
|
+
except Exception:
|
|
207
|
+
# ignore per-atom failures
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _add_explicit_hydrogens(mol: Chem.Mol) -> Chem.Mol:
|
|
212
|
+
"""Wrapper around rdmolops.AddHs with defensive fallback to return original mol
|
|
213
|
+
if AddHs fails.
|
|
214
|
+
|
|
215
|
+
:param mol: RDKit Mol to process.
|
|
216
|
+
:type mol: Chem.Mol
|
|
217
|
+
:return: Molecule with explicit hydrogens added, or original on failure.
|
|
218
|
+
:rtype: Chem.Mol
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
return rdmolops.AddHs(mol)
|
|
222
|
+
except Exception:
|
|
223
|
+
return mol
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _maybe_remove_explicit_hydrogens(mol: Chem.Mol, removeH: bool) -> Chem.Mol:
|
|
227
|
+
"""Remove explicit hydrogens if removeH is True, otherwise return the molecule unchanged.
|
|
228
|
+
|
|
229
|
+
:param mol: RDKit Mol to process.
|
|
230
|
+
:type mol: Chem.Mol
|
|
231
|
+
:param removeH: whether to remove explicit hydrogens after adding them.
|
|
232
|
+
:type removeH: bool
|
|
233
|
+
:return: Processed molecule.
|
|
234
|
+
:rtype: Chem.Mol
|
|
235
|
+
"""
|
|
236
|
+
if not removeH:
|
|
237
|
+
return mol
|
|
238
|
+
try:
|
|
239
|
+
return Chem.RemoveHs(mol)
|
|
240
|
+
except Exception:
|
|
241
|
+
return mol
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _replace_radicals_with_hs_in_mol(
|
|
245
|
+
mol: Chem.Mol, removeH: bool = True
|
|
246
|
+
) -> Optional[Chem.Mol]:
|
|
247
|
+
"""High-level helper that replaces radicals with hydrogens.
|
|
248
|
+
|
|
249
|
+
Steps:
|
|
250
|
+
1. zero out radicals (and add explicit H counters)
|
|
251
|
+
2. call AddHs to create explicit H atoms
|
|
252
|
+
3. optionally remove explicit H atoms again
|
|
253
|
+
|
|
254
|
+
:param mol: RDKit Mol to process.
|
|
255
|
+
:type mol: Chem.Mol
|
|
256
|
+
:param removeH: whether to remove explicit hydrogens after addition.
|
|
257
|
+
:type removeH: bool
|
|
258
|
+
:return: Processed RDKit Mol or None on extreme failure.
|
|
259
|
+
:rtype: Optional[Chem.Mol]
|
|
260
|
+
"""
|
|
261
|
+
if mol is None:
|
|
262
|
+
return None
|
|
263
|
+
try:
|
|
264
|
+
_zero_out_radicals_on_atoms(mol)
|
|
265
|
+
mol_with_h = _add_explicit_hydrogens(mol)
|
|
266
|
+
return _maybe_remove_explicit_hydrogens(mol_with_h, removeH)
|
|
267
|
+
except Exception as exc:
|
|
268
|
+
logger.debug("_replace_radicals_with_hs_in_mol failed: %s", exc, exc_info=True)
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def remove_radicals_and_add_hydrogens(
|
|
273
|
+
mol: Chem.Mol, removeH: bool = True
|
|
274
|
+
) -> Optional[Chem.Mol]:
|
|
275
|
+
"""Replace radical electrons by adding hydrogens and optionally remove explicit H.
|
|
276
|
+
|
|
277
|
+
:param mol: RDKit Mol with possible radical atoms.
|
|
278
|
+
:type mol: Chem.Mol
|
|
279
|
+
:param removeH: If True, remove explicit hydrogens after addition.
|
|
280
|
+
:type removeH: bool
|
|
281
|
+
:return: Mol with radicals neutralized (or None on failure).
|
|
282
|
+
:rtype: Optional[Chem.Mol]
|
|
283
|
+
"""
|
|
284
|
+
return _replace_radicals_with_hs_in_mol(mol, removeH)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# -------------------------
|
|
288
|
+
# Reaction SMILES helpers
|
|
289
|
+
# -------------------------
|
|
290
|
+
def _parse_reaction_smiles(rsmi: str) -> Optional[Tuple[str, str]]:
|
|
291
|
+
"""Parse a reaction SMILES of the form 'reactant>>product' and return tuple,
|
|
292
|
+
or None if not parseable.
|
|
293
|
+
|
|
294
|
+
:param rsmi: reaction SMILES string.
|
|
295
|
+
:type rsmi: str
|
|
296
|
+
:return: tuple (reactant_smiles, product_smiles) or None.
|
|
297
|
+
:rtype: Optional[Tuple[str, str]]
|
|
298
|
+
"""
|
|
299
|
+
if ">>" not in rsmi:
|
|
300
|
+
return None
|
|
301
|
+
parts = rsmi.split(">>", 1)
|
|
302
|
+
if len(parts) != 2:
|
|
303
|
+
return None
|
|
304
|
+
return parts[0], parts[1]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _fix_single_side_of_reaction(smiles: str, removeH: bool) -> Optional[Chem.Mol]:
|
|
308
|
+
"""Create a Mol from SMILES (without sanitization), sanitize best-effort,
|
|
309
|
+
then replace radicals and return processed Mol.
|
|
310
|
+
|
|
311
|
+
:param smiles: SMILES for one side of a reaction.
|
|
312
|
+
:type smiles: str
|
|
313
|
+
:param removeH: whether to remove explicit hydrogens after addition.
|
|
314
|
+
:type removeH: bool
|
|
315
|
+
:return: Processed RDKit Mol or None.
|
|
316
|
+
:rtype: Optional[Chem.Mol]
|
|
317
|
+
"""
|
|
318
|
+
m = Chem.MolFromSmiles(smiles, sanitize=False)
|
|
319
|
+
if m is None:
|
|
320
|
+
return None
|
|
321
|
+
try:
|
|
322
|
+
Chem.SanitizeMol(m)
|
|
323
|
+
except Exception:
|
|
324
|
+
pass
|
|
325
|
+
return _replace_radicals_with_hs_in_mol(m, removeH)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def fix_radical_rsmi(rsmi: str, removeH: bool = True) -> str:
|
|
329
|
+
"""Fix radicals in a reaction SMILES by converting them to hydrogens.
|
|
330
|
+
|
|
331
|
+
:param rsmi: Reaction SMILES string (format 'reactant>>product').
|
|
332
|
+
:type rsmi: str
|
|
333
|
+
:param removeH: If True, remove explicit hydrogens after addition.
|
|
334
|
+
:type removeH: bool
|
|
335
|
+
:return: Corrected reaction SMILES with radicals replaced (or original on failure).
|
|
336
|
+
:rtype: str
|
|
337
|
+
"""
|
|
338
|
+
try:
|
|
339
|
+
parsed = _parse_reaction_smiles(rsmi)
|
|
340
|
+
if parsed is None:
|
|
341
|
+
return rsmi
|
|
342
|
+
react_smiles, prod_smiles = parsed
|
|
343
|
+
r_fixed = _fix_single_side_of_reaction(react_smiles, removeH)
|
|
344
|
+
p_fixed = _fix_single_side_of_reaction(prod_smiles, removeH)
|
|
345
|
+
r_out = Chem.MolToSmiles(r_fixed) if r_fixed is not None else react_smiles
|
|
346
|
+
p_out = Chem.MolToSmiles(p_fixed) if p_fixed is not None else prod_smiles
|
|
347
|
+
return f"{r_out}>>{p_out}"
|
|
348
|
+
except Exception:
|
|
349
|
+
logger.debug("fix_radical_rsmi failed for %r", rsmi, exc_info=True)
|
|
350
|
+
return rsmi
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def remove_isotopes(mol: Chem.Mol) -> Chem.Mol:
|
|
354
|
+
"""Clear isotope labels on every atom in the molecule.
|
|
355
|
+
|
|
356
|
+
:param mol: RDKit Mol object to process.
|
|
357
|
+
:type mol: Chem.Mol
|
|
358
|
+
:return: The same RDKit Mol instance with isotopic labels cleared.
|
|
359
|
+
:rtype: Chem.Mol
|
|
360
|
+
"""
|
|
361
|
+
try:
|
|
362
|
+
for atom in mol.GetAtoms():
|
|
363
|
+
try:
|
|
364
|
+
atom.SetIsotope(0)
|
|
365
|
+
except Exception:
|
|
366
|
+
continue
|
|
367
|
+
except Exception as exc:
|
|
368
|
+
logger.debug("remove_isotopes encountered error: %s", exc, exc_info=True)
|
|
369
|
+
return mol
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def clear_stereochemistry(mol: Chem.Mol) -> Chem.Mol:
|
|
373
|
+
"""Remove stereochemical annotations from a molecule.
|
|
374
|
+
|
|
375
|
+
:param mol: RDKit Mol object to process.
|
|
376
|
+
:type mol: Chem.Mol
|
|
377
|
+
:return: Mol object with stereochemistry removed.
|
|
378
|
+
:rtype: Chem.Mol
|
|
379
|
+
"""
|
|
380
|
+
try:
|
|
381
|
+
Chem.RemoveStereochemistry(mol)
|
|
382
|
+
except Exception as exc:
|
|
383
|
+
logger.debug("clear_stereochemistry failed: %s", exc, exc_info=True)
|
|
384
|
+
return mol
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# -------------------------
|
|
388
|
+
# MolStandardizer class
|
|
389
|
+
# -------------------------
|
|
390
|
+
class MolStandardizer:
|
|
391
|
+
"""Chainable molecule standardizer wrapper around RDKit utilities.
|
|
392
|
+
|
|
393
|
+
Use the fluent API to apply a sequence of standardizations and then
|
|
394
|
+
retrieve the resulting molecule via the ``.mol`` property or ``.to_smiles()``.
|
|
395
|
+
|
|
396
|
+
.. rubric:: Example
|
|
397
|
+
|
|
398
|
+
>>> std = MolStandardizer.from_smiles("CC(=O)[O-]").remove_salts().uncharge().mol
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def __init__(self, mol: Chem.Mol, sanitize: bool = True) -> None:
|
|
402
|
+
"""
|
|
403
|
+
Create a MolStandardizer working on a defensive copy of ``mol``.
|
|
404
|
+
|
|
405
|
+
:param mol: RDKit Mol to operate on (a copy is created).
|
|
406
|
+
:type mol: Chem.Mol
|
|
407
|
+
:param sanitize: attempt initial sanitization (default True).
|
|
408
|
+
:type sanitize: bool
|
|
409
|
+
"""
|
|
410
|
+
self._mol: Optional[Chem.Mol] = Chem.Mol(mol) if mol is not None else None
|
|
411
|
+
self._last_error: Optional[Exception] = None
|
|
412
|
+
|
|
413
|
+
if sanitize and self._mol is not None:
|
|
414
|
+
try:
|
|
415
|
+
Chem.SanitizeMol(self._mol)
|
|
416
|
+
except Exception as exc:
|
|
417
|
+
logger.debug("Initial SanitizeMol failed: %s", exc, exc_info=True)
|
|
418
|
+
self._last_error = exc
|
|
419
|
+
|
|
420
|
+
# ----- constructors / convenience -----
|
|
421
|
+
@classmethod
|
|
422
|
+
def from_smiles(cls, smiles: str, sanitize: bool = True) -> "MolStandardizer":
|
|
423
|
+
"""Parse SMILES and return a configured standardizer.
|
|
424
|
+
|
|
425
|
+
:param smiles: SMILES string to parse.
|
|
426
|
+
:type smiles: str
|
|
427
|
+
:param sanitize: attempt sanitization on parse (default True).
|
|
428
|
+
:type sanitize: bool
|
|
429
|
+
:return: MolStandardizer.
|
|
430
|
+
:rtype: MolStandardizer
|
|
431
|
+
:raises ValueError: if SMILES fails to parse.
|
|
432
|
+
"""
|
|
433
|
+
m = Chem.MolFromSmiles(smiles)
|
|
434
|
+
if m is None:
|
|
435
|
+
raise ValueError(f"Failed to parse SMILES: {smiles!r}")
|
|
436
|
+
return cls(m, sanitize=sanitize)
|
|
437
|
+
|
|
438
|
+
@classmethod
|
|
439
|
+
def _maybe_normalize(cls, inst: "MolStandardizer") -> "MolStandardizer":
|
|
440
|
+
"""Normalize if rdMolStandardize available (small wrapper).
|
|
441
|
+
|
|
442
|
+
:param inst: MolStandardizer instance to operate on.
|
|
443
|
+
:type inst: MolStandardizer
|
|
444
|
+
:return: same instance (chainable).
|
|
445
|
+
:rtype: MolStandardizer
|
|
446
|
+
"""
|
|
447
|
+
if rdMolStandardize is None:
|
|
448
|
+
return inst
|
|
449
|
+
try:
|
|
450
|
+
inst.normalize()
|
|
451
|
+
except Exception:
|
|
452
|
+
pass
|
|
453
|
+
return inst
|
|
454
|
+
|
|
455
|
+
@classmethod
|
|
456
|
+
def _maybe_keep_largest_fragment(
|
|
457
|
+
cls, inst: "MolStandardizer", keep: bool
|
|
458
|
+
) -> "MolStandardizer":
|
|
459
|
+
"""Keep largest fragment if requested (small wrapper).
|
|
460
|
+
|
|
461
|
+
:param inst: MolStandardizer instance to operate on.
|
|
462
|
+
:type inst: MolStandardizer
|
|
463
|
+
:param keep: whether to keep the largest fragment.
|
|
464
|
+
:type keep: bool
|
|
465
|
+
:return: same instance (chainable).
|
|
466
|
+
:rtype: MolStandardizer
|
|
467
|
+
"""
|
|
468
|
+
if not keep:
|
|
469
|
+
return inst
|
|
470
|
+
try:
|
|
471
|
+
inst.keep_largest_fragment()
|
|
472
|
+
except Exception:
|
|
473
|
+
pass
|
|
474
|
+
return inst
|
|
475
|
+
|
|
476
|
+
@classmethod
|
|
477
|
+
def _maybe_remove_salts(cls, inst: "MolStandardizer") -> "MolStandardizer":
|
|
478
|
+
"""Remove salts (small wrapper).
|
|
479
|
+
|
|
480
|
+
:param inst: MolStandardizer instance to operate on.
|
|
481
|
+
:type inst: MolStandardizer
|
|
482
|
+
:return: same instance (chainable).
|
|
483
|
+
:rtype: MolStandardizer
|
|
484
|
+
"""
|
|
485
|
+
try:
|
|
486
|
+
inst.remove_salts()
|
|
487
|
+
except Exception:
|
|
488
|
+
pass
|
|
489
|
+
return inst
|
|
490
|
+
|
|
491
|
+
@classmethod
|
|
492
|
+
def _maybe_uncharge(cls, inst: "MolStandardizer") -> "MolStandardizer":
|
|
493
|
+
"""Uncharge if rdMolStandardize available (small wrapper).
|
|
494
|
+
|
|
495
|
+
:param inst: MolStandardizer instance to operate on.
|
|
496
|
+
:type inst: MolStandardizer
|
|
497
|
+
:return: same instance (chainable).
|
|
498
|
+
:rtype: MolStandardizer
|
|
499
|
+
"""
|
|
500
|
+
if rdMolStandardize is None:
|
|
501
|
+
return inst
|
|
502
|
+
try:
|
|
503
|
+
inst.uncharge()
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
return inst
|
|
507
|
+
|
|
508
|
+
@classmethod
|
|
509
|
+
def _maybe_canonicalize_tautomer(cls, inst: "MolStandardizer") -> "MolStandardizer":
|
|
510
|
+
"""Canonicalize tautomer if rdMolStandardize available (small wrapper).
|
|
511
|
+
|
|
512
|
+
:param inst: MolStandardizer instance to operate on.
|
|
513
|
+
:type inst: MolStandardizer
|
|
514
|
+
:return: same instance (chainable).
|
|
515
|
+
:rtype: MolStandardizer
|
|
516
|
+
"""
|
|
517
|
+
if rdMolStandardize is None:
|
|
518
|
+
return inst
|
|
519
|
+
try:
|
|
520
|
+
inst.canonicalize_tautomer()
|
|
521
|
+
except Exception:
|
|
522
|
+
pass
|
|
523
|
+
return inst
|
|
524
|
+
|
|
525
|
+
@classmethod
|
|
526
|
+
def _apply_default_pipeline(
|
|
527
|
+
cls, inst: "MolStandardizer", keep_largest_fragment: bool
|
|
528
|
+
) -> "MolStandardizer":
|
|
529
|
+
"""Apply the default standardization pipeline to an instance.
|
|
530
|
+
|
|
531
|
+
:param inst: MolStandardizer instance to operate on.
|
|
532
|
+
:type inst: MolStandardizer
|
|
533
|
+
:param keep_largest_fragment: whether to keep the largest fragment.
|
|
534
|
+
:type keep_largest_fragment: bool
|
|
535
|
+
:return: same instance after applying pipeline.
|
|
536
|
+
:rtype: MolStandardizer
|
|
537
|
+
"""
|
|
538
|
+
cls._maybe_normalize(inst)
|
|
539
|
+
cls._maybe_keep_largest_fragment(inst, keep_largest_fragment)
|
|
540
|
+
cls._maybe_remove_salts(inst)
|
|
541
|
+
cls._maybe_uncharge(inst)
|
|
542
|
+
cls._maybe_canonicalize_tautomer(inst)
|
|
543
|
+
return inst
|
|
544
|
+
|
|
545
|
+
@classmethod
|
|
546
|
+
def standardize_smiles(
|
|
547
|
+
cls, smiles: str, *, keep_largest_fragment: bool = True
|
|
548
|
+
) -> Optional[str]:
|
|
549
|
+
"""Quick convenience: parse SMILES, apply a sensible default standardization,
|
|
550
|
+
and return canonical SMILES or None on failure.
|
|
551
|
+
|
|
552
|
+
Default pipeline:
|
|
553
|
+
sanitize -> normalize (if available) -> keep largest fragment ->
|
|
554
|
+
remove salts -> uncharge -> canonicalize tautomer (if available)
|
|
555
|
+
|
|
556
|
+
:param smiles: Input SMILES string.
|
|
557
|
+
:type smiles: str
|
|
558
|
+
:param keep_largest_fragment: keep only the largest fragment (default True).
|
|
559
|
+
:type keep_largest_fragment: bool
|
|
560
|
+
:return: Canonical SMILES or None.
|
|
561
|
+
:rtype: Optional[str]
|
|
562
|
+
"""
|
|
563
|
+
try:
|
|
564
|
+
inst = cls.from_smiles(smiles, sanitize=True)
|
|
565
|
+
cls._apply_default_pipeline(
|
|
566
|
+
inst, keep_largest_fragment=keep_largest_fragment
|
|
567
|
+
)
|
|
568
|
+
return inst.to_smiles(canonical=True)
|
|
569
|
+
except Exception:
|
|
570
|
+
logger.debug("standardize_smiles failed for %r", smiles, exc_info=True)
|
|
571
|
+
return None
|
|
572
|
+
|
|
573
|
+
# ----- mutating chainable operations -----
|
|
574
|
+
def normalize(self) -> "MolStandardizer":
|
|
575
|
+
"""Normalize the internal molecule using rdMolStandardize.Normalizer.
|
|
576
|
+
|
|
577
|
+
:return: self (chainable).
|
|
578
|
+
:rtype: MolStandardizer
|
|
579
|
+
"""
|
|
580
|
+
if self._mol is None:
|
|
581
|
+
return self
|
|
582
|
+
if rdMolStandardize is None:
|
|
583
|
+
logger.debug("rdMolStandardize not available; normalize skipped.")
|
|
584
|
+
return self
|
|
585
|
+
try:
|
|
586
|
+
normalizer_cls = getattr(rdMolStandardize, "Normalizer", None)
|
|
587
|
+
if normalizer_cls is None:
|
|
588
|
+
logger.debug("Normalizer not found in rdMolStandardize.")
|
|
589
|
+
return self
|
|
590
|
+
self._mol = normalizer_cls().normalize(self._mol)
|
|
591
|
+
except Exception as exc:
|
|
592
|
+
logger.debug("normalize failed: %s", exc, exc_info=True)
|
|
593
|
+
self._last_error = exc
|
|
594
|
+
return self
|
|
595
|
+
|
|
596
|
+
def canonicalize_tautomer(self) -> "MolStandardizer":
|
|
597
|
+
"""Canonicalize tautomer using rdMolStandardize.TautomerEnumerator.
|
|
598
|
+
|
|
599
|
+
:return: self (chainable).
|
|
600
|
+
:rtype: MolStandardizer
|
|
601
|
+
"""
|
|
602
|
+
if self._mol is None:
|
|
603
|
+
return self
|
|
604
|
+
if rdMolStandardize is None:
|
|
605
|
+
logger.debug(
|
|
606
|
+
"rdMolStandardize not available; canonicalize_tautomer skipped."
|
|
607
|
+
)
|
|
608
|
+
return self
|
|
609
|
+
try:
|
|
610
|
+
taut_enum_cls = getattr(rdMolStandardize, "TautomerEnumerator", None)
|
|
611
|
+
if taut_enum_cls is None:
|
|
612
|
+
logger.debug("TautomerEnumerator not found in rdMolStandardize.")
|
|
613
|
+
return self
|
|
614
|
+
self._mol = taut_enum_cls().Canonicalize(self._mol)
|
|
615
|
+
except Exception as exc:
|
|
616
|
+
logger.debug("canonicalize_tautomer failed: %s", exc, exc_info=True)
|
|
617
|
+
self._last_error = exc
|
|
618
|
+
return self
|
|
619
|
+
|
|
620
|
+
def remove_salts(
|
|
621
|
+
self, salt_remover: Optional[SaltRemover] = None
|
|
622
|
+
) -> "MolStandardizer":
|
|
623
|
+
"""Remove salts using RDKit's SaltRemover.
|
|
624
|
+
|
|
625
|
+
:param salt_remover: Optional SaltRemover instance to use; if None a new one is created.
|
|
626
|
+
:type salt_remover: Optional[SaltRemover]
|
|
627
|
+
:return: self (chainable).
|
|
628
|
+
:rtype: MolStandardizer
|
|
629
|
+
"""
|
|
630
|
+
if self._mol is None:
|
|
631
|
+
return self
|
|
632
|
+
try:
|
|
633
|
+
remover = salt_remover if salt_remover is not None else SaltRemover()
|
|
634
|
+
self._mol = remover.StripMol(self._mol)
|
|
635
|
+
except Exception as exc:
|
|
636
|
+
logger.debug("remove_salts failed: %s", exc, exc_info=True)
|
|
637
|
+
self._last_error = exc
|
|
638
|
+
return self
|
|
639
|
+
|
|
640
|
+
def uncharge(self) -> "MolStandardizer":
|
|
641
|
+
"""Neutralize charges using rdMolStandardize.Uncharger.
|
|
642
|
+
|
|
643
|
+
:return: self (chainable).
|
|
644
|
+
:rtype: MolStandardizer
|
|
645
|
+
"""
|
|
646
|
+
if self._mol is None:
|
|
647
|
+
return self
|
|
648
|
+
if rdMolStandardize is None:
|
|
649
|
+
logger.debug("rdMolStandardize not available; uncharge skipped.")
|
|
650
|
+
return self
|
|
651
|
+
try:
|
|
652
|
+
uncharger_cls = getattr(rdMolStandardize, "Uncharger", None)
|
|
653
|
+
if uncharger_cls is None:
|
|
654
|
+
logger.debug("Uncharger not found in rdMolStandardize.")
|
|
655
|
+
return self
|
|
656
|
+
self._mol = uncharger_cls().uncharge(self._mol)
|
|
657
|
+
except Exception as exc:
|
|
658
|
+
logger.debug("uncharge failed: %s", exc, exc_info=True)
|
|
659
|
+
self._last_error = exc
|
|
660
|
+
return self
|
|
661
|
+
|
|
662
|
+
def keep_largest_fragment(self) -> "MolStandardizer":
|
|
663
|
+
"""Keep only the largest fragment by atom count.
|
|
664
|
+
|
|
665
|
+
:return: self (chainable).
|
|
666
|
+
:rtype: MolStandardizer
|
|
667
|
+
"""
|
|
668
|
+
if self._mol is None:
|
|
669
|
+
return self
|
|
670
|
+
try:
|
|
671
|
+
frags = Chem.GetMolFrags(self._mol, asMols=True, sanitizeFrags=True)
|
|
672
|
+
if frags:
|
|
673
|
+
self._mol = max(frags, key=lambda m: m.GetNumAtoms())
|
|
674
|
+
except Exception as exc:
|
|
675
|
+
logger.debug("keep_largest_fragment failed: %s", exc, exc_info=True)
|
|
676
|
+
self._last_error = exc
|
|
677
|
+
return self
|
|
678
|
+
|
|
679
|
+
def remove_explicit_hs(self) -> "MolStandardizer":
|
|
680
|
+
"""Remove explicit hydrogens (Chem.RemoveHs).
|
|
681
|
+
|
|
682
|
+
:return: self (chainable).
|
|
683
|
+
:rtype: MolStandardizer
|
|
684
|
+
"""
|
|
685
|
+
if self._mol is None:
|
|
686
|
+
return self
|
|
687
|
+
try:
|
|
688
|
+
self._mol = Chem.RemoveHs(self._mol)
|
|
689
|
+
except Exception as exc:
|
|
690
|
+
logger.debug("remove_explicit_hs failed: %s", exc, exc_info=True)
|
|
691
|
+
self._last_error = exc
|
|
692
|
+
return self
|
|
693
|
+
|
|
694
|
+
def add_hs_and_clear_radicals(self, removeH: bool = True) -> "MolStandardizer":
|
|
695
|
+
"""Replace radical electrons with explicit hydrogens and optionally remove them.
|
|
696
|
+
|
|
697
|
+
:param removeH: if True remove explicit hydrogens after addition.
|
|
698
|
+
:type removeH: bool
|
|
699
|
+
:return: self (chainable).
|
|
700
|
+
:rtype: MolStandardizer
|
|
701
|
+
"""
|
|
702
|
+
if self._mol is None:
|
|
703
|
+
return self
|
|
704
|
+
try:
|
|
705
|
+
processed = _replace_radicals_with_hs_in_mol(self._mol, removeH=removeH)
|
|
706
|
+
if processed is not None:
|
|
707
|
+
self._mol = processed
|
|
708
|
+
except Exception as exc:
|
|
709
|
+
logger.debug("add_hs_and_clear_radicals failed: %s", exc, exc_info=True)
|
|
710
|
+
self._last_error = exc
|
|
711
|
+
return self
|
|
712
|
+
|
|
713
|
+
def remove_isotopes(self) -> "MolStandardizer":
|
|
714
|
+
"""Clear isotope labels on all atoms.
|
|
715
|
+
|
|
716
|
+
:return: self (chainable).
|
|
717
|
+
:rtype: MolStandardizer
|
|
718
|
+
"""
|
|
719
|
+
if self._mol is None:
|
|
720
|
+
return self
|
|
721
|
+
try:
|
|
722
|
+
for atom in self._mol.GetAtoms():
|
|
723
|
+
try:
|
|
724
|
+
atom.SetIsotope(0)
|
|
725
|
+
except Exception:
|
|
726
|
+
pass
|
|
727
|
+
except Exception as exc:
|
|
728
|
+
logger.debug("remove_isotopes failed: %s", exc, exc_info=True)
|
|
729
|
+
self._last_error = exc
|
|
730
|
+
return self
|
|
731
|
+
|
|
732
|
+
def clear_stereochemistry(self) -> "MolStandardizer":
|
|
733
|
+
"""Remove stereochemical annotations (Chem.RemoveStereochemistry).
|
|
734
|
+
|
|
735
|
+
:return: self (chainable).
|
|
736
|
+
:rtype: MolStandardizer
|
|
737
|
+
"""
|
|
738
|
+
if self._mol is None:
|
|
739
|
+
return self
|
|
740
|
+
try:
|
|
741
|
+
Chem.RemoveStereochemistry(self._mol)
|
|
742
|
+
except Exception as exc:
|
|
743
|
+
logger.debug("clear_stereochemistry failed: %s", exc, exc_info=True)
|
|
744
|
+
self._last_error = exc
|
|
745
|
+
return self
|
|
746
|
+
|
|
747
|
+
# ----- retrieval / helpers -----
|
|
748
|
+
@property
|
|
749
|
+
def mol(self) -> Optional[Chem.Mol]:
|
|
750
|
+
"""Return the internal RDKit Mol (or None if absent).
|
|
751
|
+
|
|
752
|
+
:return: the internal RDKit Mol or None.
|
|
753
|
+
:rtype: Optional[Chem.Mol]
|
|
754
|
+
"""
|
|
755
|
+
return self._mol
|
|
756
|
+
|
|
757
|
+
def to_smiles(self, canonical: bool = True) -> Optional[str]:
|
|
758
|
+
"""Return a SMILES string for the internal molecule.
|
|
759
|
+
|
|
760
|
+
:param canonical: whether to return a canonical SMILES (default True).
|
|
761
|
+
:type canonical: bool
|
|
762
|
+
:return: SMILES string or None.
|
|
763
|
+
:rtype: Optional[str]
|
|
764
|
+
"""
|
|
765
|
+
if self._mol is None:
|
|
766
|
+
return None
|
|
767
|
+
try:
|
|
768
|
+
return Chem.MolToSmiles(self._mol, canonical=canonical)
|
|
769
|
+
except Exception:
|
|
770
|
+
try:
|
|
771
|
+
Chem.SanitizeMol(self._mol)
|
|
772
|
+
return Chem.MolToSmiles(self._mol, canonical=canonical)
|
|
773
|
+
except Exception:
|
|
774
|
+
logger.debug("to_smiles serialization failed.", exc_info=True)
|
|
775
|
+
return None
|
|
776
|
+
|
|
777
|
+
def summarize_last_error(self) -> Optional[str]:
|
|
778
|
+
"""Return a short string describing the last internal exception, if any.
|
|
779
|
+
|
|
780
|
+
:return: descriptive string for last error or None.
|
|
781
|
+
:rtype: Optional[str]
|
|
782
|
+
"""
|
|
783
|
+
if self._last_error is None:
|
|
784
|
+
return None
|
|
785
|
+
return f"{type(self._last_error).__name__}: {str(self._last_error)}"
|
|
786
|
+
|
|
787
|
+
def __repr__(self) -> str:
|
|
788
|
+
"""Debug representation showing the number of atoms in the internal Mol.
|
|
789
|
+
|
|
790
|
+
:return: repr string.
|
|
791
|
+
:rtype: str
|
|
792
|
+
"""
|
|
793
|
+
n = -1
|
|
794
|
+
try:
|
|
795
|
+
n = self._mol.GetNumAtoms() if self._mol is not None else -1
|
|
796
|
+
except Exception:
|
|
797
|
+
n = -1
|
|
798
|
+
return f"{self.__class__.__name__}(n_atoms={n})"
|
|
799
|
+
|
|
800
|
+
@classmethod
|
|
801
|
+
def help(cls) -> str:
|
|
802
|
+
"""Short machine-readable help describing capabilities.
|
|
803
|
+
|
|
804
|
+
:return: help string.
|
|
805
|
+
:rtype: str
|
|
806
|
+
"""
|
|
807
|
+
return (
|
|
808
|
+
"MolStandardizer.help() -> str\n\n"
|
|
809
|
+
"Fluent methods:\n"
|
|
810
|
+
" .normalize(), .canonicalize_tautomer(), .remove_salts(), .uncharge(),\n"
|
|
811
|
+
" .keep_largest_fragment(), .remove_explicit_hs(), .add_hs_and_clear_radicals(),\n"
|
|
812
|
+
" ' .remove_isotopes(), .clear_stereochemistry()\n\n"
|
|
813
|
+
"Constructors:\n"
|
|
814
|
+
" MolStandardizer.from_smiles(smiles), MolStandardizer(mol)\n\n"
|
|
815
|
+
"Convenience:\n"
|
|
816
|
+
" MolStandardizer.standardize_smiles(smiles) -> Optional[canonical_smiles]\n"
|
|
817
|
+
)
|