Skip to content

Commit

Permalink
Adding medicinal chemistry calculators and associated tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
mcnaughtonadm committed Sep 18, 2024
1 parent 5736c28 commit be69fc2
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/adme_py/adme.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from adme_py.druglikeness import calculate_all_druglikeness
from adme_py.input_handlers import mol_from_identifier
from adme_py.lipophilicity import calculate_all_lipophilicity
from adme_py.medicinal import calculate_all_medicinal
from adme_py.pharmacokinetics import calculate_all_pharmacokinetics
from adme_py.physiochemical import calculate_all_physiochemical
from adme_py.solubility import calculate_all_solubility
Expand All @@ -29,6 +30,7 @@ def calculate(self):
"lipophilicity": calculate_all_lipophilicity(self.mol),
"pharmacokinetics": calculate_all_pharmacokinetics(self.mol),
"druglikeness": calculate_all_druglikeness(self.mol),
"medicinal": calculate_all_medicinal(self.mol),
}

return properties
6 changes: 3 additions & 3 deletions src/adme_py/druglikeness.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ def calculate_ghose(mol: Chem.Mol) -> Union[str, dict]:

number_of_atoms = calculate_number_of_atoms(mol)
if number_of_atoms < 20 or number_of_atoms > 70:
violation[
"num_atoms"
] = f"Number of atoms: {number_of_atoms} is outside the acceptable range (20-70)"
violation["num_atoms"] = (
f"Number of atoms: {number_of_atoms} is outside the acceptable range (20-70)"
)

if violation:
return violation
Expand Down
156 changes: 156 additions & 0 deletions src/adme_py/medicinal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""Script containing tools to calculate Medicinal Chemistry properties."""

import os
import sys
from typing import Union

from rdkit import Chem
from rdkit.Chem import RDConfig
from rdkit.Chem.FilterCatalog import FilterCatalog, FilterCatalogParams

from adme_py.lipophilicity import calculate_logp_crippen
from adme_py.physiochemical import calculate_molecular_weight, calculate_number_rotatable_bonds

sys.path.append(os.path.join(RDConfig.RDContribDir, "SA_Score"))
import sascorer


def calculate_all_medicinal(mol: Chem.Mol) -> dict[str, Union[bool, str, dict[str, str], float]]:
"""Calculate all properties in the medicinal chemistry script.
Parameters
----------
mol : Chem.Mol
The input rdkit Mol object
Returns
-------
properties : dict[str, Union[str,dict[str,str]]]
Dictionary of properties calculated
"""
pains: bool = calculate_pains(mol)
brenk: bool = calculate_brenk(mol)
zinc: bool = calculate_zinc(mol)
leadlikeness: Union[str, dict[str, str]] = calculate_leadlikeness(mol)
synthetic_accessibility: float = calculate_synthetic_accessiblity(mol)

properties: dict[str, Union[bool, str, dict[str, str], float]] = {
"pains": pains,
"brenk": brenk,
"zinc": zinc,
"leadlikeness": leadlikeness,
"synthetic_accessibility": synthetic_accessibility,
}

return properties


def calculate_pains(mol: Chem.Mol) -> bool:
"""Calculate whether the molecule triggers the PAINS filter.
Parameters
----------
mol : Chem.Mol
The input rdkit Mol object
Returns
-------
bool
Whether the molecule triggers the PAINS filter.
"""
params_pains = FilterCatalogParams()
params_pains.AddCatalog(FilterCatalogParams.FilterCatalogs.PAINS_A)
params_pains.AddCatalog(FilterCatalogParams.FilterCatalogs.PAINS_B)
params_pains.AddCatalog(FilterCatalogParams.FilterCatalogs.PAINS_C)

catalog_pains = FilterCatalog(params_pains)
return catalog_pains.HasMatch(mol)


def calculate_brenk(mol: Chem.Mol) -> bool:
"""Calculate whether the molecule triggers the Brenk filter.
Parameters
----------
mol : Chem.Mol
The input rdkit Mol object
Returns
-------
bool
Whether the molecule triggers the Brenk filter.
"""
params_brenk = FilterCatalogParams()
params_brenk.AddCatalog(FilterCatalogParams.FilterCatalogs.BRENK)

catalog_brenk = FilterCatalog(params_brenk)
return catalog_brenk.HasMatch(mol)


def calculate_zinc(mol: Chem.Mol) -> bool:
"""Calculate whether the molecule triggers the Zinc filter.
Parameters
----------
mol : Chem.Mol
The input rdkit Mol object
Returns
-------
bool
Whether the molecule triggers the ZINC filter.
"""
params_zinc = FilterCatalogParams()
params_zinc.AddCatalog(FilterCatalogParams.FilterCatalogs.ZINC)

catalog_zinc = FilterCatalog(params_zinc)
return catalog_zinc.HasMatch(mol)


def calculate_leadlikeness(mol: Chem.Mol) -> Union[str, dict[str, str]]:
"""Calculate if the molecule exhibits leadlikeness.
Parameters
----------
mol : Chem.Mol
The input rdkit Mol object
Returns
-------
Union[str,dict[str,str]]
The violations to the leadlikeness filter
"""
violation = {}

logp = calculate_logp_crippen(mol)
if logp > 3.5:
violation["LogP"] = f"LogP: {logp} > 3.5"

molecular_weight = calculate_molecular_weight(mol)
if molecular_weight < 250 or molecular_weight > 350:
violation["MW"] = f"MW: {molecular_weight} is outside the acceptable range (250-350)"

number_rotatable_bonds = calculate_number_rotatable_bonds(mol)
if number_rotatable_bonds > 7:
violation["num_rot_bonds"] = f"Number of Rotatable Bonds: {number_rotatable_bonds} > 7"

if violation:
return violation
else:
return "Yes"


def calculate_synthetic_accessiblity(mol: Chem.Mol) -> float:
"""Calculate the synthetic accessibility of the molecule.
Parameters
----------
mol : Chem.Mol
The input rdkit Mol object
Returns
-------
float
The Synthetic Accessibility score of the molecule
"""
return sascorer.calculateScore(mol)
31 changes: 31 additions & 0 deletions tests/test_adme.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,34 @@ def test_druglikeness_veber(test_adme):
expected_value = "Pass"

assert calculated_veber == expected_value


def test_medicinal_pains(test_adme):
"""Test the medicinal PAINS filter."""
assert not test_adme.properties["medicinal"]["pains"]


def test_medicinal_brenk(test_adme):
"""Test the medicinal Brenk filter."""
assert not test_adme.properties["medicinal"]["brenk"]


def test_medicinal_zinc(test_adme):
"""Test the medicinal Zinc filter."""
assert not test_adme.properties["medicinal"]["zinc"]


def test_medicinal_leadlikeness(test_adme):
"""Test the medicinal leadlikeness filter."""
result = test_adme.properties["medicinal"]["leadlikeness"]
expected_result = {"MW": "MW: 92.14099999999999 is outside the acceptable range (250-350)"}

assert expected_result == result


def test_medicinal_synthetic_accessibility(test_adme):
"""Test the medicinal synthetic accesssibility calculator."""
result = test_adme.properties["medicinal"]["synthetic_accessibility"]
expected_result = 1.0

assert expected_result == result
72 changes: 72 additions & 0 deletions tests/test_medicinal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Tests for methods in medicinal.py file."""

from adme_py.medicinal import (
calculate_brenk,
calculate_leadlikeness,
calculate_pains,
calculate_synthetic_accessiblity,
calculate_zinc,
)
from rdkit import Chem


def test_calculate_pains_pass(rdkit_mol):
"""Test a passing case of calculate_pains()."""
assert not calculate_pains(rdkit_mol)


def test_calculate_pains_fail():
"""Test a failing case of calculate_pains()."""
pains_mol = Chem.MolFromSmiles("CC(=O)NCC1CN(C(=O)O1)C2=CC(=C(C=C2)N3CCOCC3)F")
assert calculate_pains(pains_mol)


def test_calculate_brenk_pass(rdkit_mol):
"""Test a passing case of calculate_brenk()."""
assert not calculate_brenk(rdkit_mol)


def test_calculate_brenk_fail():
"""Test a failing case of calculate_brenk()."""
brenk_mol = Chem.MolFromSmiles("OCOCC(COc1ccc(c(c1C(=O)Oc1ccc(cc1N)N1CCNCC1)N)OC)O")
assert calculate_brenk(brenk_mol)


def test_calculate_zinc_pass(rdkit_mol):
"""Test a passing case of calculate_zinc()."""
assert not calculate_zinc(rdkit_mol)


def test_calculate_zinc_fail():
"""Test a failing case of calculate_zinc()."""
zinc_mol = Chem.MolFromSmiles(
"CC1=C2C(C(=O)C3(C(CC4C(C3C(C(C2(C)C)(CC1OC(=O)C(C(C5=CC=CC=C5)NC(=O)C6=CC=CC=C6)O)O)OC(=O)C7=CC=CC=C7)(CO4)OC(=O)C)O)C)OC(=O)C"
)
assert calculate_zinc(zinc_mol)


def test_calculate_leadlikeness_pass(rdkit_mol):
"""Test a passing case of calculate_leadlikeness()."""
mol = Chem.MolFromSmiles("COc1ccc2c(c1)nc([nH]2)S(=O)Cc1ncc(c(c1C)OC)C")
result = calculate_leadlikeness(mol)
expected_result = "Yes"
assert result == expected_result


def test_calculate_leadlikeness_fail(rdkit_mol):
"""Test a failing case of calculate_leadlikeness()."""
result = calculate_leadlikeness(rdkit_mol)
expected_violations = {
"MW": "MW: 92.14099999999999 is outside the acceptable range (250-350)",
}

assert isinstance(result, dict)
assert result == expected_violations


def test_calculate_synthetic_accessibility(rdkit_mol):
"""Test the result of calculate_synthetic_accessibility()."""
result = calculate_synthetic_accessiblity(rdkit_mol)
expected_result = 1.0

assert result == expected_result

0 comments on commit be69fc2

Please sign in to comment.