Chemistry

Chemical graph manipulation performed before modelling: standardization, glycan handling, protecting-group removal, core extraction and graph conversion. Every component accepts Iterable[rdkit.Chem.Mol] and follows the scikit-learn transformer protocol.

Standardization

The first step of any QSAR workflow. Two records of the same compound that differ only in salt form, protonation or tautomer are the same compound, and a model that sees them as different is learning the registration system rather than the chemistry.

>>> from rdkit import Chem
>>> from qsarkit.chemistry import MolecularStandardizer
>>> standardizer = MolecularStandardizer()
>>> mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)[O-].[Na+]")
>>> Chem.MolToSmiles(standardizer.transform([mol])[0])
'CC(=O)Oc1ccccc1C(=O)O'

Failures keep their position, so a parallel array of activities stays aligned:

>>> [m is None for m in standardizer.transform([mol, None])]
[False, True]

Glycans

Natural-product datasets are full of glycosides. The sugar usually carries no activity of its own but dominates the fingerprint, so two glycosides of unrelated aglycones score as more similar to each other than either does to its own aglycone.

>>> from qsarkit.chemistry import GlycanDetector, GlycanRemover
>>> q3g = named_mols["quercetin_3_glucoside"]
>>> GlycanDetector().detect(q3g)["num_sugar_residues"]
1
>>> Chem.MolToSmiles(GlycanRemover().remove(q3g)["aglycone"])
'O=c1cc(-c2ccc(O)c(O)c2)oc2cc(O)cc(O)c12'

Detection is not just “a ring with an oxygen in it” — the exocyclic hydroxylation pattern is what separates a real sugar from a look-alike:

>>> detector = GlycanDetector()
>>> detector.detect(Chem.MolFromSmiles("C1CCOCC1"))["num_sugar_residues"]
0

GlycanDescriptors turns that into features. It returns a DataFrame rather than an array, because one column is a string:

>>> from qsarkit.chemistry import GlycanDescriptors
>>> frame = GlycanDescriptors().transform([q3g, named_mols["aspirin"]])
>>> frame["sugar_count"].tolist()
[1, 0]

Protecting groups and cores

>>> from qsarkit.chemistry import CoreExtractor, FragmentRemover
>>> boc = Chem.MolFromSmiles("CC(C)(C)OC(=O)NCc1ccccc1")
>>> Chem.MolToSmiles(FragmentRemover().transform([boc])[0])
'NCc1ccccc1'

A protecting group is a synthesis artefact, not a pharmacophore. Leaving it on makes every intermediate look like a distinct chemotype and lets a model key on the tag.

>>> extractor = CoreExtractor()
>>> Chem.MolToSmiles(extractor.bemis_murcko(demo_mols[8]))
'c1ccccc1'
>>> pair = [demo_mols[8], demo_mols[9]]
>>> Chem.MolToSmarts(extractor.mcs(pair))
'[#6]-[#6](=[#8])-[#7]-[#6]1:[#6]:[#6]:[#6]:[#6]:[#6]:1'

Graphs

>>> from qsarkit.chemistry import MolecularGraph
>>> graph = MolecularGraph()
>>> G = graph.to_networkx(Chem.MolFromSmiles("CCO"))
>>> G.number_of_nodes(), G.number_of_edges()
(3, 2)
>>> d = graph.descriptors(Chem.MolFromSmiles("c1ccccc1"))
>>> d["num_rings"], d["diameter"], round(d["wiener_index"], 1)
(1, 3, 27.0)

API

Chemical structure curation performed before modeling.

Standardization is the step that makes a QSAR dataset comparable at all: without it the same compound appears as several distinct structures and duplicate detection, splitting and modeling all quietly go wrong. The remaining components support that work – glycan handling for natural-product datasets, protecting-group removal, scaffold extraction and graph descriptors.

All components accept Iterable[rdkit.Chem.Mol].

Examples

>>> from rdkit import Chem
>>> from qsarkit.chemistry import MolecularStandardizer
>>> mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)[O-].[Na+]")
>>> Chem.MolToSmiles(MolecularStandardizer().transform([mol])[0])
'CC(=O)Oc1ccccc1C(=O)O'

References

  • Fourches, D., Muratov, E. & Tropsha, A. (2010). “Trust, But Verify: On the Importance of Chemical Structure Curation in Cheminformatics and QSAR Modeling Research.” J. Chem. Inf. Model., 50(7), 1189-1204. https://doi.org/10.1021/ci100176x

  • Bemis, G. W. & Murcko, M. A. (1996). “The Properties of Known Drugs. 1. Molecular Frameworks.” J. Med. Chem., 39(15), 2887-2893. https://doi.org/10.1021/jm9602928

  • RDKit: Open-source cheminformatics. https://www.rdkit.org

class qsarkit.chemistry.MolecularStandardizer(remove_salts=True, neutralize=True, normalize_tautomers=True, handle_stereochemistry='retain', normalize_hydrogens=True, on_error='none')[source]

Bases: MoleculeToMoleculeTransformer

Standardize a batch of molecules into a canonical, model-ready form.

Applies, in order: sanitization, salt/solvent removal (keep the largest organic fragment), charge neutralization, tautomer normalization, stereochemistry handling, and explicit-hydrogen normalization. This mirrors the “structure normalization” stage expected before any downstream QSAR/curation step (OECD QSAR guidance recommends normalized, unambiguous structures prior to model building).

Parameters:
  • remove_salts (bool) – Keep only the largest organic fragment (strips counter-ions, solvates, hydrates).

  • neutralize (bool) – Neutralize charges where a neutral tautomer/protomer exists (e.g. carboxylates, ammoniums), leaving permanent charges (e.g. quaternary ammonium) untouched.

  • normalize_tautomers (bool) – Canonicalize to the RDKit-preferred tautomer using the Sybyl/MolVS-derived tautomer scoring rules.

  • handle_stereochemistry (str) – One of "retain" (keep stereo as parsed, but reassign stereocenters from the 2D/3D structure), or "remove" (strip all stereochemistry, useful when comparing 2D scaffolds).

  • normalize_hydrogens (bool) – Strip explicit hydrogens except where required for correct valence/stereo perception (RDKit’s implicit-H convention).

  • on_error (str) – "none" -> failed molecules become None in the output list (positional alignment preserved); "raise" -> raise InvalidMoleculeError on the first failure.

Examples

Sodium acetylsalicylate loses its counter-ion and its charge:

>>> from rdkit import Chem
>>> from qsarkit.chemistry import MolecularStandardizer
>>> standardizer = MolecularStandardizer()
>>> mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)[O-].[Na+]")
>>> Chem.MolToSmiles(standardizer.transform([mol])[0])
'CC(=O)Oc1ccccc1C(=O)O'

Failures do not shift the batch. An unparseable record becomes None in place, so a parallel array of activities stays aligned:

>>> out = standardizer.transform([mol, None])
>>> [m is None for m in out]
[False, True]

handle_stereochemistry="remove" strips stereocentres, which is what you want when comparing 2D scaffolds rather than modelling enantiomer-specific activity:

>>> flat = MolecularStandardizer(handle_stereochemistry="remove")
>>> Chem.MolToSmiles(flat.transform([Chem.MolFromSmiles("C[C@H](N)C(=O)O")])[0])
'CC(N)C(=O)O'

References

set_fit_request(*, mols='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the fit method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to fit.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

mols (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for mols parameter in fit.

Returns:

self – The updated object.

Return type:

object

set_transform_request(*, mols='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the transform method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to transform.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

mols (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for mols parameter in transform.

Returns:

self – The updated object.

Return type:

object

class qsarkit.chemistry.GlycanDetector(min_exocyclic_oxygens=2)[source]

Bases: object

Detect carbohydrate (sugar) ring systems in a molecule.

Uses the circular-sugar detection strategy of the Sugar Removal Utility (SRU): a candidate ring is a 5- or 6-membered ring with exactly one ring oxygen and only sp3 carbons otherwise, and is accepted as a sugar only if it is decorated with enough exocyclic oxygens (hydroxyls / glycosidic ethers / an exocyclic CH2OH) to match the hydroxylation pattern of a real aldose/ketose ring - which excludes plain carbocycles (cyclohexane) and simple ethers (tetrahydropyran, tetrahydrofuran).

Parameters:

min_exocyclic_oxygens (int) – Exocyclic oxygens a candidate ring must carry to be called a sugar. Lowering it admits deoxy sugars along with false positives; raising it rejects rhamnose and other deoxy sugars.

Examples

Quercetin 3-O-glucoside carries one pyranose ring, contributing 39% of the molecular weight:

>>> from rdkit import Chem
>>> from qsarkit.chemistry import GlycanDetector
>>> q3g = Chem.MolFromSmiles(
...     "OC[C@H]1O[C@@H](Oc2c(-c3ccc(O)c(O)c3)oc3cc(O)cc(O)c3c2=O)"
...     "[C@H](O)[C@@H](O)[C@@H]1O")
>>> result = GlycanDetector().detect(q3g)
>>> result["num_sugar_residues"]
1
>>> round(result["glycan_mw_fraction"], 3)
0.386

The exocyclic-oxygen requirement is what separates a sugar from a look-alike ring. Tetrahydropyran has the right ring but no hydroxylation, and is correctly rejected:

>>> GlycanDetector().detect(Chem.MolFromSmiles("C1CCOCC1"))["num_sugar_residues"]
0
>>> GlycanDetector().detect(Chem.MolFromSmiles("CCO"))["num_sugar_residues"]
0

References

find_glycans(mol)[source]

Return one GlycanMatch per detected sugar ring.

Return type:

List[GlycanMatch]

detect(mol)[source]

Summarize glycan content of a molecule.

Returns:

num_sugar_residues: number of detected sugar rings. glycan_atoms: sorted list of atom indices belonging to glycan rings/substituents. glycan_mw_fraction: fraction of the molecule’s molecular weight contributed by glycan atoms (including attached H’s).

Return type:

dict

transform(mols)[source]

Run detect() over an Iterable[Mol].

Return type:

List[Optional[dict]]

class qsarkit.chemistry.GlycanRemover(detector=None)[source]

Bases: object

Remove carbohydrate (sugar) moieties, keeping the aglycone core.

Detects sugar rings with GlycanDetector, cleaves the glycosidic (single) bonds linking sugar atoms to the rest of the molecule, and returns the largest non-sugar fragment as the “aglycone” - e.g. quercetin-3-glucoside -> quercetin.

Parameters:

detector (Optional[GlycanDetector]) – Detector used to locate sugar rings. A default instance is created if not supplied.

Examples

>>> from rdkit import Chem
>>> from qsarkit.chemistry import GlycanRemover
>>> q3g = Chem.MolFromSmiles(
...     "OC[C@H]1O[C@@H](Oc2c(-c3ccc(O)c(O)c3)oc3cc(O)cc(O)c3c2=O)"
...     "[C@H](O)[C@@H](O)[C@@H]1O")
>>> result = GlycanRemover().remove(q3g)
>>> Chem.MolToSmiles(result["aglycone"])
'O=c1cc(-c2ccc(O)c(O)c2)oc2cc(O)cc(O)c12'

The result keeps the input alongside what was cut away, so a curation step can record why a structure changed:

>>> sorted(result)
['aglycone', 'original', 'removed_fragments']

Deglycosylation matters for QSAR because the sugar usually carries no activity of its own but dominates the fingerprint, so two glycosides of unrelated aglycones look more similar to each other than either does to its own aglycone. A molecule with no sugar passes through unchanged:

>>> aspirin = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")
>>> Chem.MolToSmiles(GlycanRemover().remove(aspirin)["aglycone"])
'CC(=O)Oc1ccccc1C(=O)O'

References

remove(mol)[source]

Remove glycan moieties from a single molecule.

Returns:

original: the input Mol. aglycone: the largest non-sugar fragment, or None if the whole molecule was consumed by sugar rings. removed_fragments: list of removed sugar (and any other minor) fragments as Mol objects.

Return type:

dict

transform(mols)[source]

Run remove() over an Iterable[Mol].

Return type:

List[Optional[dict]]

class qsarkit.chemistry.GlycanDescriptors(detector=None)[source]

Bases: MoleculeTransformer

Compute glycan-content descriptors for a batch of molecules.

The three columns produced are:

sugar_count

Number of detected sugar rings (see GlycanDetector).

glycan_fraction

Fraction of molecular weight contributed by glycan atoms.

glycosylation_pattern

Comma-separated summary such as "6-ring:O-glycoside x2" describing ring size and linkage type of each detected sugar.

Parameters:

detector (Optional[GlycanDetector])

Notes

Unlike the transformers in qsarkit.representation, this one returns a pandas.DataFrame rather than a NumPy array, because glycosylation_pattern is a string. Forcing it into an array would give the whole block object dtype and lose the numeric columns’ types.

Examples

>>> from rdkit import Chem
>>> from qsarkit.chemistry import GlycanDescriptors
>>> q3g = Chem.MolFromSmiles(
...     "OC[C@H]1O[C@@H](Oc2c(-c3ccc(O)c(O)c3)oc3cc(O)cc(O)c3c2=O)"
...     "[C@H](O)[C@@H](O)[C@@H]1O")
>>> df = GlycanDescriptors().transform([q3g, Chem.MolFromSmiles("CCO")])
>>> list(df.columns)
['sugar_count', 'glycan_fraction', 'glycosylation_pattern']
>>> df["sugar_count"].tolist()
[1, 0]
>>> df["glycosylation_pattern"][0]
'6-ring:terminal'

References

set_fit_request(*, mols='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the fit method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to fit.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

mols (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for mols parameter in fit.

Returns:

self – The updated object.

Return type:

object

set_transform_request(*, mols='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the transform method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to transform.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

mols (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for mols parameter in transform.

Returns:

self – The updated object.

Return type:

object

class qsarkit.chemistry.FragmentRemover(groups=None, max_iterations=5)[source]

Bases: MoleculeToMoleculeTransformer

Strip protecting groups, synthesis linkers, tags and click-handles.

Matches each pattern in groups as a substituent attached to the core structure via a single bond, deletes the matched atoms, and caps the resulting open valence with an implicit hydrogen - conceptually the reverse of a protection reaction.

Parameters:
  • groups (Optional[Dict[str, str]]) – Mapping of group name -> SMARTS pattern. Defaults to DEFAULT_GROUPS (Boc, Cbz, Fmoc, acetyl, TBS, trityl, benzyl ether, PEG linkers, biotin tag, azide/alkyne click handles).

  • max_iterations (int) – Repeat removal up to this many times per molecule, since removing one group can expose another (e.g. a doubly-Boc-protected amine).

Examples

Boc-protected benzylamine loses its carbamate:

>>> from rdkit import Chem
>>> from qsarkit.chemistry import FragmentRemover
>>> boc = Chem.MolFromSmiles("CC(C)(C)OC(=O)NCc1ccccc1")
>>> Chem.MolToSmiles(FragmentRemover().transform([boc])[0])
'NCc1ccccc1'

This belongs in curation because a protecting group is a synthesis artefact, not a pharmacophore: leaving it on makes every intermediate in a series look like a distinct chemotype and lets a model key on the tag instead of the chemistry.

Restricting groups narrows what is stripped – here Boc is not in the set, so the molecule is returned unchanged:

>>> only_acetyl = FragmentRemover(groups={"acetyl": "[CX3](=O)[CH3]"})
>>> Chem.MolToSmiles(only_acetyl.transform([boc])[0])
'CC(C)(C)OC(=O)NCc1ccccc1'

References

set_fit_request(*, mols='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the fit method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to fit.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

mols (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for mols parameter in fit.

Returns:

self – The updated object.

Return type:

object

set_transform_request(*, mols='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the transform method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to transform.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

mols (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for mols parameter in transform.

Returns:

self – The updated object.

Return type:

object

class qsarkit.chemistry.CoreExtractor[source]

Bases: object

Extract a representative structural core from one or more molecules.

Three definitions of “core”, answering different questions:

  • bemis_murcko() gives ring systems plus linkers (the classic scaffold definition), or the fully generic skeleton with element and bond-order information stripped.

  • mcs() gives the maximum common substructure across a set of molecules, which is the right notion for a congeneric series.

  • medchem_core() gives a medicinal-chemistry-oriented core: the Bemis-Murcko scaffold with terminal exocyclic double bonds trimmed back to rings, which tends to match how chemists describe a series’ “core” more closely than the strict Murcko definition.

Examples

>>> from rdkit import Chem
>>> from qsarkit.chemistry import CoreExtractor
>>> extractor = CoreExtractor()
>>> paracetamol_like = Chem.MolFromSmiles("CC(=O)Nc1ccc(Cl)cc1")
>>> Chem.MolToSmiles(extractor.bemis_murcko(paracetamol_like))
'c1ccccc1'

generic=True discards element identity and bond order, so pyridine and benzene analogues collapse onto one skeleton – the right granularity for asking “how many ring systems are in this library”, the wrong one for asking “which chemotype is this”:

>>> Chem.MolToSmiles(extractor.bemis_murcko(paracetamol_like, generic=True))
'C1CCCCC1'

mcs() works across a series rather than on one molecule, and returns the shared substructure as a query mol:

>>> pair = [Chem.MolFromSmiles(s) for s in
...         ("CC(=O)Nc1ccc(Cl)cc1", "CC(=O)Nc1ccc(Br)cc1")]
>>> Chem.MolToSmarts(extractor.mcs(pair))
'[#6]-[#6](=[#8])-[#7]-[#6]1:[#6]:[#6]:[#6]:[#6]:[#6]:1'

References

bemis_murcko(mol, generic=False)[source]
Return type:

Any

mcs(mols, **kwargs)[source]
Return type:

Optional[Any]

medchem_core(mol)[source]
Return type:

Any

transform(mols, method='bemis_murcko', **kwargs)[source]

Apply a per-molecule extraction method over an Iterable[Mol].

Return type:

List[Optional[Any]]

class qsarkit.chemistry.MolecularGraph[source]

Bases: object

Convert RDKit molecules to NetworkX graphs and compute graph descriptors.

Atoms become nodes carrying symbol, atomic_num, formal_charge, is_aromatic, in_ring, degree and hybridization; bonds become edges carrying bond_type, is_aromatic, in_ring and order. This is the substrate for the topological indices below, and for any analysis that is easier to express with NetworkX than with RDKit’s own graph API.

Examples

>>> from rdkit import Chem
>>> from qsarkit.chemistry import MolecularGraph
>>> graph = MolecularGraph()
>>> G = graph.to_networkx(Chem.MolFromSmiles("CCO"))
>>> G.number_of_nodes(), G.number_of_edges()
(3, 2)
>>> G.nodes[2]["symbol"], G.nodes[2]["hybridization"]
('O', 'SP3')

Hydrogens are implicit, so a node’s heavy-atom degree and its num_hs are reported separately:

>>> G.nodes[0]["degree"], G.nodes[0]["num_hs"]
(1, 3)

descriptors() returns the classic topological indices for a single molecule:

>>> d = graph.descriptors(Chem.MolFromSmiles("c1ccccc1"))
>>> d["num_rings"], d["cyclomatic_number"], d["diameter"]
(1, 1, 3)
>>> round(d["wiener_index"], 1)
27.0

References

to_networkx(mol)[source]

Build a networkx.Graph from an RDKit Mol.

Return type:

Graph

descriptors(mol)[source]

Compute topological / graph-theoretic descriptors for one molecule.

Return type:

Dict[str, float]

transform(mols)[source]

Compute descriptors() for every molecule in an Iterable[Mol].

Return type:

List[Optional[Dict[str, float]]]

References

  • Fourches, D., Muratov, E. & Tropsha, A. (2010). “Trust, But Verify.” J. Chem. Inf. Model., 50(7), 1189-1204. doi:10.1021/ci100176x

  • Fischer, J. et al. (2020). “The Sugar Removal Utility.” Molecules, 25(8), 1988. doi:10.3390/molecules25081988

  • Bemis, G. W. & Murcko, M. A. (1996). “The Properties of Known Drugs. 1. Molecular Frameworks.” J. Med. Chem., 39(15), 2887-2893. doi:10.1021/jm9602928