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,452 @@
|
|
|
1
|
+
# synkit/Chem/Molecule/descriptors.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import (
|
|
6
|
+
List,
|
|
7
|
+
Tuple,
|
|
8
|
+
Optional,
|
|
9
|
+
Protocol,
|
|
10
|
+
runtime_checkable,
|
|
11
|
+
Dict,
|
|
12
|
+
Any,
|
|
13
|
+
Sequence,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from rdkit import Chem
|
|
17
|
+
from rdkit.Chem import AllChem, rdMolDescriptors
|
|
18
|
+
from rdkit import RDLogger
|
|
19
|
+
|
|
20
|
+
# Keep RDKit quiet in helper scope (best effort)
|
|
21
|
+
try:
|
|
22
|
+
RDLogger.DisableLog("rdApp.*")
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class _MolLike(Protocol):
|
|
29
|
+
"""
|
|
30
|
+
Minimal protocol for RDKit-like molecules used by this module.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def GetNumAtoms(self) -> int: ...
|
|
34
|
+
def GetAtoms(self): ...
|
|
35
|
+
def GetAtomWithIdx(self, idx: int): ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# -----------------------------
|
|
39
|
+
# Internal small helpers
|
|
40
|
+
# -----------------------------
|
|
41
|
+
def compute_gasteiger_inplace(mol: Chem.Mol | Any) -> None:
|
|
42
|
+
"""Compatibility helper: compute Gasteiger charges in-place (best-effort).
|
|
43
|
+
Reintroduced for backward compatibility with code that imports this from
|
|
44
|
+
synkit.Chem.Molecule.descriptors.
|
|
45
|
+
|
|
46
|
+
:param mol: RDKit Mol (or mol-like object) to annotate. Mutates in place.
|
|
47
|
+
:return: None
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
AllChem.ComputeGasteigerCharges(mol)
|
|
51
|
+
except Exception:
|
|
52
|
+
# best-effort: swallow RDKit quirks and continue
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _make_copy_and_sanitize(mol: Chem.Mol | _MolLike, sanitize: bool) -> Chem.Mol:
|
|
57
|
+
"""Make a defensive copy of ``mol`` and optionally sanitize it.
|
|
58
|
+
|
|
59
|
+
:param mol: input molecule-like object
|
|
60
|
+
:param sanitize: whether to call ``Chem.SanitizeMol`` on the copy
|
|
61
|
+
:return: copied (and possibly sanitized) RDKit Mol
|
|
62
|
+
"""
|
|
63
|
+
m = Chem.Mol(mol)
|
|
64
|
+
if sanitize:
|
|
65
|
+
try:
|
|
66
|
+
Chem.SanitizeMol(m)
|
|
67
|
+
except Exception:
|
|
68
|
+
# best-effort: keep working with the copy even if sanitization fails
|
|
69
|
+
pass
|
|
70
|
+
return m
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _safe_get_gasteiger_value(atom: Any) -> float:
|
|
74
|
+
"""Extract a Gasteiger charge value from an RDKit atom, tolerant to API variants.
|
|
75
|
+
|
|
76
|
+
:param atom: RDKit atom-like object.
|
|
77
|
+
:return: float value (0.0 on failure).
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
if atom.HasProp("_GasteigerCharge"):
|
|
81
|
+
try:
|
|
82
|
+
return float(atom.GetProp("_GasteigerCharge"))
|
|
83
|
+
except Exception:
|
|
84
|
+
try:
|
|
85
|
+
return float(atom.GetDoubleProp("_GasteigerCharge"))
|
|
86
|
+
except Exception:
|
|
87
|
+
return 0.0
|
|
88
|
+
except Exception:
|
|
89
|
+
return 0.0
|
|
90
|
+
return 0.0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _compute_gasteiger_list(m: Chem.Mol, n_atoms: int) -> List[float]:
|
|
94
|
+
"""
|
|
95
|
+
Compute per-atom Gasteiger charges for molecule ``m``.
|
|
96
|
+
|
|
97
|
+
Best-effort: returns a list of length ``n_atoms`` filled with floats.
|
|
98
|
+
"""
|
|
99
|
+
gasteiger: List[float] = [0.0] * n_atoms
|
|
100
|
+
try:
|
|
101
|
+
AllChem.ComputeGasteigerCharges(m)
|
|
102
|
+
for a in m.GetAtoms():
|
|
103
|
+
try:
|
|
104
|
+
idx = a.GetIdx()
|
|
105
|
+
except Exception:
|
|
106
|
+
continue
|
|
107
|
+
gasteiger[idx] = _safe_get_gasteiger_value(a)
|
|
108
|
+
except Exception:
|
|
109
|
+
# On any failure leave zeros
|
|
110
|
+
pass
|
|
111
|
+
return gasteiger
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _compute_estate_list(m: Chem.Mol, n_atoms: int) -> List[float]:
|
|
115
|
+
"""
|
|
116
|
+
Compute per-atom EState indices (best-effort). Returns zeros if unavailable.
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
estate_vals = list(
|
|
120
|
+
__import__("rdkit.Chem.EState", fromlist=["EState"]).EState.EStateIndices(m)
|
|
121
|
+
)
|
|
122
|
+
if len(estate_vals) != n_atoms:
|
|
123
|
+
estate_vals = [0.0] * n_atoms
|
|
124
|
+
except Exception:
|
|
125
|
+
estate_vals = [0.0] * n_atoms
|
|
126
|
+
return estate_vals
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _calc_crippen_contribs(m: Chem.Mol) -> Optional[List[Tuple[float, float]]]:
|
|
130
|
+
"""
|
|
131
|
+
Version-tolerant wrapper for RDKit's Crippen contribution routine.
|
|
132
|
+
|
|
133
|
+
Kept small and used by _compute_crippen_lists.
|
|
134
|
+
"""
|
|
135
|
+
fn = getattr(rdMolDescriptors, "CalcCrippenContribs", None)
|
|
136
|
+
if fn is None:
|
|
137
|
+
fn = getattr(rdMolDescriptors, "_CalcCrippenContribs", None)
|
|
138
|
+
if fn is None:
|
|
139
|
+
return None
|
|
140
|
+
try:
|
|
141
|
+
return list(fn(m))
|
|
142
|
+
except Exception:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _compute_crippen_lists(
|
|
147
|
+
m: Chem.Mol, n_atoms: int
|
|
148
|
+
) -> Tuple[List[float], List[float]]:
|
|
149
|
+
"""
|
|
150
|
+
Compute per-atom Crippen (logP, MR) lists. Returns two lists of length n_atoms.
|
|
151
|
+
"""
|
|
152
|
+
cr_logp: List[float] = [0.0] * n_atoms
|
|
153
|
+
cr_mr: List[float] = [0.0] * n_atoms
|
|
154
|
+
cr = _calc_crippen_contribs(m)
|
|
155
|
+
if cr is not None and len(cr) == n_atoms:
|
|
156
|
+
try:
|
|
157
|
+
cr_logp = [float(x[0]) for x in cr]
|
|
158
|
+
cr_mr = [float(x[1]) for x in cr]
|
|
159
|
+
except Exception:
|
|
160
|
+
cr_logp = [0.0] * n_atoms
|
|
161
|
+
cr_mr = [0.0] * n_atoms
|
|
162
|
+
return cr_logp, cr_mr
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _normalize_vector(vec: Sequence[float], method: Optional[str]) -> List[float]:
|
|
166
|
+
"""
|
|
167
|
+
Normalize a numeric vector in-place style (returns a new list).
|
|
168
|
+
|
|
169
|
+
Supported methods: None, "zscore", "minmax".
|
|
170
|
+
If method is None or vector length <= 1, returns a float copy of vec.
|
|
171
|
+
"""
|
|
172
|
+
n = len(vec)
|
|
173
|
+
if n == 0 or method is None:
|
|
174
|
+
return [float(x) for x in vec]
|
|
175
|
+
|
|
176
|
+
vals = [float(x) for x in vec]
|
|
177
|
+
if method == "zscore":
|
|
178
|
+
mean = sum(vals) / n
|
|
179
|
+
var = sum((x - mean) ** 2 for x in vals) / n
|
|
180
|
+
std = var**0.5
|
|
181
|
+
if std == 0.0:
|
|
182
|
+
return [0.0 for _ in vals]
|
|
183
|
+
return [(x - mean) / std for x in vals]
|
|
184
|
+
elif method == "minmax":
|
|
185
|
+
lo = min(vals)
|
|
186
|
+
hi = max(vals)
|
|
187
|
+
if hi == lo:
|
|
188
|
+
return [0.0 for _ in vals]
|
|
189
|
+
return [(x - lo) / (hi - lo) for x in vals]
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError(f"Unsupported normalization method: {method!r}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _apply_normalization_to_all(
|
|
195
|
+
vectors: Dict[str, List[float]], method: Optional[str]
|
|
196
|
+
) -> Dict[str, List[float]]:
|
|
197
|
+
"""
|
|
198
|
+
Apply normalization ``method`` to all vectors in the dict and return a new dict.
|
|
199
|
+
"""
|
|
200
|
+
if method is None:
|
|
201
|
+
# ensure float conversion
|
|
202
|
+
return {k: [float(x) for x in v] for k, v in vectors.items()}
|
|
203
|
+
normalized: Dict[str, List[float]] = {}
|
|
204
|
+
for k, v in vectors.items():
|
|
205
|
+
normalized[k] = _normalize_vector(v, method)
|
|
206
|
+
return normalized
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# -----------------------------
|
|
210
|
+
# Public dataclass + builder
|
|
211
|
+
# -----------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class PerMolDescriptors:
|
|
216
|
+
"""
|
|
217
|
+
Immutable container for per-atom descriptor lists.
|
|
218
|
+
|
|
219
|
+
:param gasteiger: per-atom Gasteiger charges
|
|
220
|
+
:param estate: per-atom EState indices
|
|
221
|
+
:param crippen_logp: per-atom Crippen logP contributions
|
|
222
|
+
:param crippen_mr: per-atom Crippen MR contributions
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
gasteiger: List[float]
|
|
226
|
+
estate: List[float]
|
|
227
|
+
crippen_logp: List[float]
|
|
228
|
+
crippen_mr: List[float]
|
|
229
|
+
|
|
230
|
+
# ----------------- Convenience constructors ---------------------------
|
|
231
|
+
@classmethod
|
|
232
|
+
def compute(
|
|
233
|
+
cls,
|
|
234
|
+
mol: Chem.Mol | _MolLike,
|
|
235
|
+
sanitize: bool = True,
|
|
236
|
+
normalize: Optional[str] = None,
|
|
237
|
+
) -> "PerMolDescriptors":
|
|
238
|
+
"""Best-effort compute per-atom descriptors for the given molecule.
|
|
239
|
+
|
|
240
|
+
This function delegates work to small helpers for clarity and easier
|
|
241
|
+
testing. Behavior is identical to the previous implementation.
|
|
242
|
+
|
|
243
|
+
:param mol: RDKit molecule (or duck-typed equivalent).
|
|
244
|
+
:param sanitize: try to sanitize the copied molecule (default True).
|
|
245
|
+
:param normalize: normalization method: None (default), "zscore", or "minmax".
|
|
246
|
+
:return: PerMolDescriptors instance.
|
|
247
|
+
"""
|
|
248
|
+
# Defensive copy and optional sanitization
|
|
249
|
+
m = _make_copy_and_sanitize(mol, sanitize)
|
|
250
|
+
n_atoms = m.GetNumAtoms() if hasattr(m, "GetNumAtoms") else 0
|
|
251
|
+
|
|
252
|
+
# Compute components (small focused helpers)
|
|
253
|
+
gasteiger = _compute_gasteiger_list(m, n_atoms)
|
|
254
|
+
estate_vals = _compute_estate_list(m, n_atoms)
|
|
255
|
+
cr_logp, cr_mr = _compute_crippen_lists(m, n_atoms)
|
|
256
|
+
|
|
257
|
+
# Apply normalization if requested
|
|
258
|
+
vectors = {
|
|
259
|
+
"gasteiger": gasteiger,
|
|
260
|
+
"estate": estate_vals,
|
|
261
|
+
"crippen_logp": cr_logp,
|
|
262
|
+
"crippen_mr": cr_mr,
|
|
263
|
+
}
|
|
264
|
+
vectors = _apply_normalization_to_all(vectors, normalize)
|
|
265
|
+
|
|
266
|
+
return cls(
|
|
267
|
+
gasteiger=vectors["gasteiger"],
|
|
268
|
+
estate=vectors["estate"],
|
|
269
|
+
crippen_logp=vectors["crippen_logp"],
|
|
270
|
+
crippen_mr=vectors["crippen_mr"],
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def from_smiles(
|
|
275
|
+
cls, smiles: str, sanitize: bool = True, normalize: Optional[str] = None
|
|
276
|
+
) -> "PerMolDescriptors":
|
|
277
|
+
"""Parse SMILES and compute descriptors.
|
|
278
|
+
|
|
279
|
+
:param smiles: SMILES string to parse.
|
|
280
|
+
:param sanitize: try to sanitize the parsed molecule (default True).
|
|
281
|
+
:param normalize: optional normalization ("zscore" | "minmax" | None).
|
|
282
|
+
:return: PerMolDescriptors instance.
|
|
283
|
+
:raises ValueError: if SMILES fails to parse.
|
|
284
|
+
"""
|
|
285
|
+
m = Chem.MolFromSmiles(smiles)
|
|
286
|
+
if m is None:
|
|
287
|
+
raise ValueError(f"Failed to parse SMILES: {smiles!r}")
|
|
288
|
+
return cls.compute(m, sanitize=sanitize, normalize=normalize)
|
|
289
|
+
|
|
290
|
+
# ----------------- Small helpers / serialization ---------------------
|
|
291
|
+
def __repr__(self) -> str:
|
|
292
|
+
return (
|
|
293
|
+
f"{self.__class__.__name__}(num_atoms={self.num_atoms}, "
|
|
294
|
+
f"has_gasteiger={any(v != 0.0 for v in self.gasteiger)})"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def num_atoms(self) -> int:
|
|
299
|
+
"""Infer atom count from stored lists (prefers gasteiger length).
|
|
300
|
+
|
|
301
|
+
:return: inferred atom count.
|
|
302
|
+
"""
|
|
303
|
+
return len(self.gasteiger)
|
|
304
|
+
|
|
305
|
+
def to_dict(self) -> Dict[str, List[float]]:
|
|
306
|
+
"""Convert to a plain dictionary.
|
|
307
|
+
|
|
308
|
+
:return: dict with the per-atom lists.
|
|
309
|
+
"""
|
|
310
|
+
return {
|
|
311
|
+
"gasteiger": list(self.gasteiger),
|
|
312
|
+
"estate": list(self.estate),
|
|
313
|
+
"crippen_logp": list(self.crippen_logp),
|
|
314
|
+
"crippen_mr": list(self.crippen_mr),
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class PerMolDescriptorsBuilder:
|
|
319
|
+
"""Fluent builder for :class:`PerMolDescriptors`.
|
|
320
|
+
|
|
321
|
+
.. rubric:: Example
|
|
322
|
+
|
|
323
|
+
.. code-block:: python
|
|
324
|
+
|
|
325
|
+
desc = (
|
|
326
|
+
PerMolDescriptorsBuilder(mol)
|
|
327
|
+
.compute_gasteiger()
|
|
328
|
+
.compute_estate()
|
|
329
|
+
.compute_crippen()
|
|
330
|
+
.normalize("zscore")
|
|
331
|
+
.build()
|
|
332
|
+
.descriptor
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
The builder's chainable methods return ``self``; call ``.build()`` then
|
|
336
|
+
access the final PerMolDescriptors via the ``.descriptor`` property.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(self, mol: Chem.Mol | _MolLike, sanitize: bool = True):
|
|
340
|
+
"""Create a builder for the given molecule.
|
|
341
|
+
|
|
342
|
+
:param mol: RDKit Mol or equivalent.
|
|
343
|
+
:param sanitize: try to sanitize the internal copy (default True).
|
|
344
|
+
"""
|
|
345
|
+
self._mol = Chem.Mol(mol)
|
|
346
|
+
self._sanitize = sanitize
|
|
347
|
+
if sanitize:
|
|
348
|
+
try:
|
|
349
|
+
Chem.SanitizeMol(self._mol)
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
n_atoms = self._mol.GetNumAtoms() if hasattr(self._mol, "GetNumAtoms") else 0
|
|
354
|
+
self._n_atoms = n_atoms
|
|
355
|
+
|
|
356
|
+
# Internal working vectors (None until computed)
|
|
357
|
+
self._gasteiger: Optional[List[float]] = None
|
|
358
|
+
self._estate: Optional[List[float]] = None
|
|
359
|
+
self._crippen_logp: Optional[List[float]] = None
|
|
360
|
+
self._crippen_mr: Optional[List[float]] = None
|
|
361
|
+
self._normalized_method: Optional[str] = None
|
|
362
|
+
self._built: Optional[PerMolDescriptors] = None
|
|
363
|
+
|
|
364
|
+
# ---------- Chainable compute methods --------------------------------
|
|
365
|
+
def compute_gasteiger(self) -> "PerMolDescriptorsBuilder":
|
|
366
|
+
"""Compute Gasteiger charges (best-effort) and store internally.
|
|
367
|
+
|
|
368
|
+
:return: self (chainable).
|
|
369
|
+
"""
|
|
370
|
+
self._gasteiger = _compute_gasteiger_list(self._mol, self._n_atoms)
|
|
371
|
+
self._built = None
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
def compute_estate(self) -> "PerMolDescriptorsBuilder":
|
|
375
|
+
"""Compute EState indices (best-effort).
|
|
376
|
+
|
|
377
|
+
:return: self (chainable).
|
|
378
|
+
"""
|
|
379
|
+
self._estate = _compute_estate_list(self._mol, self._n_atoms)
|
|
380
|
+
self._built = None
|
|
381
|
+
return self
|
|
382
|
+
|
|
383
|
+
def compute_crippen(self) -> "PerMolDescriptorsBuilder":
|
|
384
|
+
"""Compute Crippen per-atom contributions (best-effort).
|
|
385
|
+
|
|
386
|
+
:return: self (chainable).
|
|
387
|
+
"""
|
|
388
|
+
cr_logp, cr_mr = _compute_crippen_lists(self._mol, self._n_atoms)
|
|
389
|
+
self._crippen_logp = cr_logp
|
|
390
|
+
self._crippen_mr = cr_mr
|
|
391
|
+
self._built = None
|
|
392
|
+
return self
|
|
393
|
+
|
|
394
|
+
# ---------- Normalization --------------------------------------------
|
|
395
|
+
def normalize(self, method: Optional[str]) -> "PerMolDescriptorsBuilder":
|
|
396
|
+
"""Normalize any computed vectors using ``method`` ("zscore" | "minmax" | None).
|
|
397
|
+
|
|
398
|
+
:param method: normalization method or None to skip.
|
|
399
|
+
:return: self (chainable).
|
|
400
|
+
"""
|
|
401
|
+
if method is None:
|
|
402
|
+
self._normalized_method = None
|
|
403
|
+
return self
|
|
404
|
+
|
|
405
|
+
if method not in ("zscore", "minmax"):
|
|
406
|
+
raise ValueError(f"Unsupported normalization method: {method!r}")
|
|
407
|
+
|
|
408
|
+
if self._gasteiger is not None:
|
|
409
|
+
self._gasteiger = _normalize_vector(self._gasteiger, method)
|
|
410
|
+
if self._estate is not None:
|
|
411
|
+
self._estate = _normalize_vector(self._estate, method)
|
|
412
|
+
if self._crippen_logp is not None:
|
|
413
|
+
self._crippen_logp = _normalize_vector(self._crippen_logp, method)
|
|
414
|
+
if self._crippen_mr is not None:
|
|
415
|
+
self._crippen_mr = _normalize_vector(self._crippen_mr, method)
|
|
416
|
+
|
|
417
|
+
self._normalized_method = method
|
|
418
|
+
self._built = None
|
|
419
|
+
return self
|
|
420
|
+
|
|
421
|
+
# ---------- Finalize / retrieve -------------------------------------
|
|
422
|
+
def build(self) -> "PerMolDescriptorsBuilder":
|
|
423
|
+
"""Finalize internal state and prepare the immutable PerMolDescriptors.
|
|
424
|
+
|
|
425
|
+
The method stores the result internally and returns ``self``. Use the
|
|
426
|
+
``.descriptor`` property to access the final object.
|
|
427
|
+
|
|
428
|
+
:return: self
|
|
429
|
+
"""
|
|
430
|
+
# If a vector wasn't computed, fill with zeros of appropriate length
|
|
431
|
+
n = self._n_atoms
|
|
432
|
+
g = self._gasteiger if self._gasteiger is not None else [0.0] * n
|
|
433
|
+
e = self._estate if self._estate is not None else [0.0] * n
|
|
434
|
+
crp = self._crippen_logp if self._crippen_logp is not None else [0.0] * n
|
|
435
|
+
crm = self._crippen_mr if self._crippen_mr is not None else [0.0] * n
|
|
436
|
+
|
|
437
|
+
self._built = PerMolDescriptors(
|
|
438
|
+
gasteiger=g, estate=e, crippen_logp=crp, crippen_mr=crm
|
|
439
|
+
)
|
|
440
|
+
return self
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def descriptor(self) -> PerMolDescriptors:
|
|
444
|
+
"""Retrieve the built PerMolDescriptors. If not built yet, ``build()`` is
|
|
445
|
+
called implicitly.
|
|
446
|
+
|
|
447
|
+
:return: PerMolDescriptors
|
|
448
|
+
"""
|
|
449
|
+
if self._built is None:
|
|
450
|
+
self.build()
|
|
451
|
+
assert self._built is not None
|
|
452
|
+
return self._built
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
from typing import Dict, List, Optional, Union, Any
|
|
2
|
+
import re
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from joblib import Parallel, delayed
|
|
7
|
+
from rdkit import Chem
|
|
8
|
+
from rdkit.Chem import Descriptors, rdMolDescriptors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Formula:
|
|
12
|
+
"""
|
|
13
|
+
Decompose SMILES into element counts and generate Hill-order formulas / molecular weights using RDKit.
|
|
14
|
+
|
|
15
|
+
Main APIs:
|
|
16
|
+
- :meth:`decompose`: element counts as a dict (e.g., {'C': 6, 'H': 6})
|
|
17
|
+
- :meth:`hill_formula`: Hill-order formula string
|
|
18
|
+
- :meth:`mol_weight`: RDKit molecular weight (sums '.' fragments)
|
|
19
|
+
- :meth:`process_list`: batch over a list of SMILES
|
|
20
|
+
- :meth:`process_list_dict`: batch over a list of dicts or a pandas DataFrame
|
|
21
|
+
|
|
22
|
+
Hill notation rules implemented:
|
|
23
|
+
- If carbon ('C') present: list 'C', then 'H', then other elements alphabetically.
|
|
24
|
+
- If no carbon: list all elements alphabetically.
|
|
25
|
+
- Counts of 1 are omitted (CH3, not C1H3).
|
|
26
|
+
|
|
27
|
+
:param n_jobs: Number of parallel jobs for batch processing via joblib. Use 1 to disable parallelism.
|
|
28
|
+
:param verbose: Verbosity level passed to joblib.Parallel.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
_element_pattern = re.compile(r"([A-Z][a-z]*)(\d*)")
|
|
32
|
+
|
|
33
|
+
def __init__(self, n_jobs: int = 1, verbose: int = 0) -> None:
|
|
34
|
+
self.n_jobs = int(n_jobs)
|
|
35
|
+
self.verbose = int(verbose)
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return f"Formula(n_jobs={self.n_jobs}, verbose={self.verbose})"
|
|
39
|
+
|
|
40
|
+
# ---------------------------- Core utilities ---------------------------- #
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _parse_formula_string(formula: str) -> Dict[str, int]:
|
|
44
|
+
"""
|
|
45
|
+
Parse a molecular formula string into a dict of element counts.
|
|
46
|
+
|
|
47
|
+
:param formula: Molecular formula string (e.g., "C6H12O6", "CH4", "H2O").
|
|
48
|
+
:return: Dict mapping element symbol to integer count.
|
|
49
|
+
"""
|
|
50
|
+
parts = Formula._element_pattern.findall(formula)
|
|
51
|
+
return {elem: (int(n) if n else 1) for elem, n in parts}
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _sum_counts(counts_list: List[Dict[str, int]]) -> Dict[str, int]:
|
|
55
|
+
"""
|
|
56
|
+
Sum a list of element-count dictionaries.
|
|
57
|
+
|
|
58
|
+
:param counts_list: List of dicts of element counts.
|
|
59
|
+
:return: Single dict with summed counts.
|
|
60
|
+
"""
|
|
61
|
+
out: Dict[str, int] = {}
|
|
62
|
+
for d in counts_list:
|
|
63
|
+
for k, v in d.items():
|
|
64
|
+
out[k] = out.get(k, 0) + int(v)
|
|
65
|
+
return out
|
|
66
|
+
|
|
67
|
+
# ----------------------------- Single item ------------------------------ #
|
|
68
|
+
|
|
69
|
+
def decompose(self, smiles: str) -> Dict[str, int]:
|
|
70
|
+
"""
|
|
71
|
+
Decompose a SMILES string into element counts using RDKit's CalcMolFormula.
|
|
72
|
+
Disconnected fragments separated by '.' are handled by summing counts.
|
|
73
|
+
|
|
74
|
+
:param smiles: SMILES string (may contain '.' for multiple fragments).
|
|
75
|
+
:return: Dict of element counts (empty dict if invalid/empty).
|
|
76
|
+
"""
|
|
77
|
+
if not isinstance(smiles, str) or not smiles.strip():
|
|
78
|
+
return {}
|
|
79
|
+
|
|
80
|
+
per_frag_counts: List[Dict[str, int]] = []
|
|
81
|
+
for frag in smiles.split("."):
|
|
82
|
+
mol = Chem.MolFromSmiles(frag)
|
|
83
|
+
if mol is None:
|
|
84
|
+
# Ignore invalid fragments
|
|
85
|
+
continue
|
|
86
|
+
fstr = rdMolDescriptors.CalcMolFormula(mol)
|
|
87
|
+
per_frag_counts.append(self._parse_formula_string(fstr))
|
|
88
|
+
|
|
89
|
+
return self._sum_counts(per_frag_counts)
|
|
90
|
+
|
|
91
|
+
def hill_formula(self, smiles: str) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Convert a SMILES to a Hill-order formula string.
|
|
94
|
+
|
|
95
|
+
Rules:
|
|
96
|
+
- If 'C' present: C then H then other elements alphabetical.
|
|
97
|
+
- If no 'C': all elements alphabetical.
|
|
98
|
+
- Counts of 1 are omitted.
|
|
99
|
+
|
|
100
|
+
:param smiles: SMILES string.
|
|
101
|
+
:return: Hill-order formula string; empty string for invalid/empty SMILES.
|
|
102
|
+
"""
|
|
103
|
+
counts = self.decompose(smiles)
|
|
104
|
+
if not counts:
|
|
105
|
+
return ""
|
|
106
|
+
|
|
107
|
+
def fmt(elem: str, n: int) -> str:
|
|
108
|
+
return f"{elem}{n if n != 1 else ''}"
|
|
109
|
+
|
|
110
|
+
if "C" in counts:
|
|
111
|
+
parts: List[str] = []
|
|
112
|
+
parts.append(fmt("C", counts.get("C", 0)))
|
|
113
|
+
if counts.get("H", 0) > 0:
|
|
114
|
+
parts.append(fmt("H", counts["H"]))
|
|
115
|
+
others = sorted(k for k in counts.keys() if k not in ("C", "H"))
|
|
116
|
+
parts.extend(fmt(k, counts[k]) for k in others if counts[k] > 0)
|
|
117
|
+
# Defensive: drop any accidental zeros
|
|
118
|
+
parts = [p for p in parts if not p.endswith("0")]
|
|
119
|
+
return "".join(parts)
|
|
120
|
+
|
|
121
|
+
# No carbon present
|
|
122
|
+
parts = [fmt(k, counts[k]) for k in sorted(counts.keys()) if counts[k] > 0]
|
|
123
|
+
return "".join(parts)
|
|
124
|
+
|
|
125
|
+
def mol_weight(self, smiles: str) -> Optional[float]:
|
|
126
|
+
"""
|
|
127
|
+
Compute molecular weight using RDKit (sum over '.' fragments).
|
|
128
|
+
|
|
129
|
+
:param smiles: SMILES string (may contain '.' fragments).
|
|
130
|
+
:return: Molecular weight as float, or None if invalid/empty.
|
|
131
|
+
"""
|
|
132
|
+
if not isinstance(smiles, str) or not smiles.strip():
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
total = 0.0
|
|
136
|
+
valid = False
|
|
137
|
+
for frag in smiles.split("."):
|
|
138
|
+
mol = Chem.MolFromSmiles(frag)
|
|
139
|
+
if mol is None:
|
|
140
|
+
continue
|
|
141
|
+
total += Descriptors.MolWt(mol)
|
|
142
|
+
valid = True
|
|
143
|
+
|
|
144
|
+
return total if valid else None
|
|
145
|
+
|
|
146
|
+
# ----------------------------- Batch APIs -------------------------------- #
|
|
147
|
+
|
|
148
|
+
def process_list(
|
|
149
|
+
self,
|
|
150
|
+
smiles_list: List[str],
|
|
151
|
+
what: str = "hill",
|
|
152
|
+
) -> List[Union[str, Dict[str, int], float, None]]:
|
|
153
|
+
"""
|
|
154
|
+
Batch process a list of SMILES.
|
|
155
|
+
|
|
156
|
+
:param smiles_list: List of SMILES strings.
|
|
157
|
+
:param what: One of {'hill', 'decompose', 'molwt'}.
|
|
158
|
+
:return: List of results corresponding to 'what'.
|
|
159
|
+
- 'hill' -> List[str]
|
|
160
|
+
- 'decompose' -> List[Dict[str,int]]
|
|
161
|
+
- 'molwt' -> List[Optional[float]]
|
|
162
|
+
:raises ValueError: If 'what' is unsupported.
|
|
163
|
+
"""
|
|
164
|
+
dispatch = {
|
|
165
|
+
"hill": self.hill_formula,
|
|
166
|
+
"decompose": self.decompose,
|
|
167
|
+
"molwt": self.mol_weight,
|
|
168
|
+
}
|
|
169
|
+
if what not in dispatch:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
"Unsupported 'what'. Choose from {'hill', 'decompose', 'molwt'}."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
fn = dispatch[what]
|
|
175
|
+
if self.n_jobs == 1:
|
|
176
|
+
return [fn(s) for s in smiles_list]
|
|
177
|
+
return Parallel(n_jobs=self.n_jobs, verbose=self.verbose)(
|
|
178
|
+
delayed(fn)(s) for s in smiles_list
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def process_list_dict(
|
|
182
|
+
self,
|
|
183
|
+
records: Union[List[Dict[str, Any]], pd.DataFrame],
|
|
184
|
+
smiles_key: str = "smiles",
|
|
185
|
+
out_key: str = "hill",
|
|
186
|
+
what: str = "hill",
|
|
187
|
+
copy: bool = True,
|
|
188
|
+
) -> List[Dict[str, Any]]:
|
|
189
|
+
"""
|
|
190
|
+
Batch process a list of dictionaries (or a pandas DataFrame) containing SMILES,
|
|
191
|
+
and return a list of dictionaries with the computed output appended.
|
|
192
|
+
|
|
193
|
+
- If a pandas DataFrame is provided, it is converted to a list of dicts (records).
|
|
194
|
+
- Input dicts are deep-copied by default to avoid in-place mutation.
|
|
195
|
+
|
|
196
|
+
:param records: List[dict] or pandas DataFrame. Each record must contain `smiles_key`.
|
|
197
|
+
:param smiles_key: Key in each record holding the SMILES string.
|
|
198
|
+
:param out_key: Output key to store the computed value, e.g. 'hill', 'decompose', 'molwt'.
|
|
199
|
+
:param what: One of {'hill', 'decompose', 'molwt'} specifying the computation.
|
|
200
|
+
:param copy: If True, deep-copy each record before adding output.
|
|
201
|
+
:return: List of dicts with an added `out_key`.
|
|
202
|
+
:raises KeyError: If a record is missing `smiles_key`.
|
|
203
|
+
:raises ValueError: If 'what' is unsupported.
|
|
204
|
+
"""
|
|
205
|
+
if isinstance(records, pd.DataFrame):
|
|
206
|
+
records = records.to_dict(orient="records")
|
|
207
|
+
|
|
208
|
+
if not isinstance(records, list):
|
|
209
|
+
raise ValueError("`records` must be a list of dicts or a pandas DataFrame.")
|
|
210
|
+
|
|
211
|
+
dispatch = {
|
|
212
|
+
"hill": self.hill_formula,
|
|
213
|
+
"decompose": self.decompose,
|
|
214
|
+
"molwt": self.mol_weight,
|
|
215
|
+
}
|
|
216
|
+
if what not in dispatch:
|
|
217
|
+
raise ValueError(
|
|
218
|
+
"Unsupported 'what'. Choose from {'hill', 'decompose', 'molwt'}."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
fn = dispatch[what]
|
|
222
|
+
|
|
223
|
+
# Extract SMILES list with index mapping
|
|
224
|
+
smiles_seq: List[str] = []
|
|
225
|
+
for i, rec in enumerate(records):
|
|
226
|
+
if not isinstance(rec, dict):
|
|
227
|
+
raise ValueError(f"Record at index {i} is not a dict.")
|
|
228
|
+
if smiles_key not in rec:
|
|
229
|
+
raise KeyError(
|
|
230
|
+
f"Record at index {i} missing required key '{smiles_key}'."
|
|
231
|
+
)
|
|
232
|
+
smiles_seq.append(rec[smiles_key])
|
|
233
|
+
|
|
234
|
+
# Compute results
|
|
235
|
+
if self.n_jobs == 1:
|
|
236
|
+
outputs = [fn(s) for s in smiles_seq]
|
|
237
|
+
else:
|
|
238
|
+
outputs = Parallel(n_jobs=self.n_jobs, verbose=self.verbose)(
|
|
239
|
+
delayed(fn)(s) for s in smiles_seq
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Attach outputs
|
|
243
|
+
out_records: List[Dict[str, Any]] = []
|
|
244
|
+
for rec, val in zip(records, outputs):
|
|
245
|
+
r = deepcopy(rec) if copy else rec
|
|
246
|
+
r[out_key] = val
|
|
247
|
+
out_records.append(r)
|
|
248
|
+
|
|
249
|
+
return out_records
|