Практикум 3. Хемоинформатика

In [1]:
from warnings import filterwarnings
filterwarnings(action='ignore')
In [2]:
from rdkit import Chem
from rdkit.Chem import AllChem
from rdkit import RDConfig
from rdkit.Chem.Draw import IPythonConsole 
from rdkit.Chem import Draw
import rdkit.Chem.Lipinski as Lipinksy

import numpy as np
from IPython.display import display, Image, HTML

Отрисуем молекулу ибупрофена

In [3]:
ibu = Chem.MolFromSmiles('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O')
AllChem.Compute2DCoords(ibu)
display(ibu)

Посмотрим на показатели, необходимые для оценки молекулы ибупрофена по правилу пяти Липински (RO5).

In [4]:
print('Число доноров водородной связи (должно быть ≤ 5): ', 
      Lipinksy.NumHDonors(ibu))
print('Число акцепторов водородной связи (должно быть ≤ 10): ', 
      Lipinksy.NumHAcceptors(ibu))
print('Молекулярная масса (должна быть < 500 Da): ', 
      Lipinksy.rdMolDescriptors.CalcExactMolWt(ibu)) 
print('Коэффициент распределения октанол-вода, log P (должен быть ≤ 5): ',
      Lipinksy.rdMolDescriptors.CalcCrippenDescriptors(ibu)[0])
Число доноров водородной связи (должно быть ≤ 5):  1
Число акцепторов водородной связи (должно быть ≤ 10):  1
Молекулярная масса (должна быть < 500 Da):  206.130679816
Коэффициент распределения октанол-вода, log P (должен быть ≤ 5):  3.073200000000001

Как мы видим, RO5 выполняется по всем пунктам, что означает доступность ибупрофена при пероральном приёме.

Напишем специальную функцию для проверки RO5:

In [5]:
def test_lipinski_ro5(mol):
    if (Lipinksy.NumHDonors(mol) <= 5) & \
        (Lipinksy.NumHAcceptors(mol) <= 10) & \
        (Lipinksy.rdMolDescriptors.CalcExactMolWt(mol) < 500) &\
        (Lipinksy.rdMolDescriptors.CalcCrippenDescriptors(mol)[0] <= 5):
        return True
    else:
        return False
In [6]:
test_lipinski_ro5(ibu)
Out[6]:
True

Модифицируем ибупрофен так, как если бы он являлся продуктом реакции click-химии.

In [7]:
ibu_mod_smiles = 'N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1'
In [8]:
ibu_mod = Chem.MolFromSmiles(ibu_mod_smiles)
AllChem.Compute2DCoords(ibu_mod)

display(ibu_mod)
In [9]:
Lipinksy.rdMolDescriptors.CalcExactMolWt(ibu_mod)
Out[9]:
273.147726848

Когда мы будем внедрять этот фрагмент вместо азидной группы, мы будем добавлять примерно 273 - 42 = 231 Да. Тогда для прохождения всех критериев RO5 азид-содержащее соединение должно облалать молекулярной массой не более 269 Да.

In [10]:
test_lipinski_ro5(ibu_mod)
Out[10]:
True

Выгрузим из PubChem 10000 находок с азидной группой "N=[N+]=[N-]" в качестве Substructure.

In [11]:
import pandas as pd
In [12]:
df = pd.read_csv('files/pubchem_azide_substructures_1kk.csv', nrows=10000)

Отфильтруем находки с несколькими соединениями и сделаем отсечку по массе.

In [13]:
df = df[~(df['isosmiles'].str.contains('\.'))]
df_candidate = df[df['mw']<=269]
In [14]:
print(f'До: {df.shape[0]} молекул, после фильтрации — {df_candidate.shape[0]}.')
До: 9744 молекул, после фильтрации — 2509.
In [15]:
df_azides = df_candidate[df_candidate['isosmiles'].apply(lambda x: 'N=[N+]=[N-]' in x)]
In [16]:
print(f'Замену азидной группы проведем в {df_azides.shape[0]} структурах.')
Замену азидной группы проведем в 426 структурах.
In [17]:
candidate_smiles = df_azides['isosmiles'].apply(lambda x: x.replace('N=[N+]=[N-]', f'({ibu_mod_smiles})')).values
In [18]:
from tqdm.notebook import tqdm
import multiprocessing as mp
In [19]:
def testing_pipeline(smiles):
    try:
        mol = Chem.MolFromSmiles(smiles)
        AllChem.Compute2DCoords(mol)
        if test_lipinski_ro5(mol):
            return mol
    except:
        return None
In [20]:
pool = mp.Pool(processes=4)
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: C[Si](C)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: (N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:46] SMILES Parse Error: Failed parsing SMILES 'C[Si](C)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: 'C[Si](C)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: CN(C)[Si]((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N(C)C)N(C)C
RDKit ERROR: [02:28:46] SMILES Parse Error: Failed parsing SMILES '(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: '(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
RDKit ERROR: [02:28:46] SMILES Parse Error: Failed parsing SMILES 'CN(C)[Si]((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N(C)C)N(C)C' for input: 'CN(C)[Si]((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N(C)C)N(C)C'
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: C1=CC=C(C=C1)[Si](C2=CC=CC=C2)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:46] SMILES Parse Error: Failed parsing SMILES 'C1=CC=C(C=C1)[Si](C2=CC=CC=C2)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: 'C1=CC=C(C=C1)[Si](C2=CC=CC=C2)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: CCOP(=S)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))OCC
RDKit ERROR: [02:28:46] SMILES Parse Error: Failed parsing SMILES 'CCOP(=S)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))OCC' for input: 'CCOP(=S)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))OCC'
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: [N-]=[N+]=NP((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:46] SMILES Parse Error: Failed parsing SMILES '[N-]=[N+]=NP((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: '[N-]=[N+]=NP((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
RDKit ERROR: [02:28:46] SMILES Parse Error: syntax error while parsing: C[Si](C1=CC=CC=C1)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:47] SMILES Parse Error: Failed parsing SMILES 'C[Si](C1=CC=CC=C1)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: 'C[Si](C1=CC=CC=C1)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
RDKit ERROR: [02:28:47] SMILES Parse Error: syntax error while parsing: C1=CC=C(C=C1)C((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(F)F
RDKit ERROR: [02:28:47] SMILES Parse Error: syntax error while parsing: C1=CC=C2C(=C1)C(=O)C(C(=O)N2)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:47] SMILES Parse Error: Failed parsing SMILES 'C1=CC=C(C=C1)C((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(F)F' for input: 'C1=CC=C(C=C1)C((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(F)F'
RDKit ERROR: [02:28:47] SMILES Parse Error: Failed parsing SMILES 'C1=CC=C2C(=C1)C(=O)C(C(=O)N2)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: 'C1=CC=C2C(=C1)C(=O)C(C(=O)N2)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
RDKit ERROR: [02:28:47] SMILES Parse Error: syntax error while parsing: CCOP(=O)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))OCC
RDKit ERROR: [02:28:47] SMILES Parse Error: Failed parsing SMILES 'CCOP(=O)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))OCC' for input: 'CCOP(=O)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))OCC'
RDKit ERROR: [02:28:47] SMILES Parse Error: syntax error while parsing: C1(=O)C(C(=O)NC(=O)N1)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)
RDKit ERROR: [02:28:47] SMILES Parse Error: Failed parsing SMILES 'C1(=O)C(C(=O)NC(=O)N1)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)' for input: 'C1(=O)C(C(=O)NC(=O)N1)((N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1))(N1N=NC=C(CC(C(=O)O)C2=CC=C(CC(C)C)C=C2)1)'
In [21]:
result = pool.map(testing_pipeline, candidate_smiles)
In [22]:
candidate_molecules = [mol for mol in result if mol]
In [23]:
print(f'Получилось {len(candidate_molecules)} кандидатных молекул.')
Получилось 326 кандидатных молекул.
In [24]:
from random import sample, seed
seed(23)

Отрисуем 9 кандидатов

In [25]:
candidate_sample = sample(candidate_molecules, 9)

Draw.MolsToGridImage(candidate_sample, useSVG=True, molsPerRow=3, subImgSize=(200, 200))
Out[25]:
HN S O O N N N O HO O N N N N O HO N N N N N N O OH H2N NH2 N N N O HO N H O OH H2N N N O N N N O OH NH2 O O N N N O HO OH NH2 N O HN O O N N N O HO HO O O N N N O HO OH O N O HN O NH2 OH N N N O HO

Построим Similiraty Map ибупрофена с пятым соединением.

In [26]:
from rdkit.Chem.Draw import SimilarityMaps
In [27]:
candidate = candidate_sample[4]
In [28]:
fig, maxweight = SimilarityMaps.GetSimilarityMapForFingerprint(ibu, 
                                                               candidate, 
                                                               SimilarityMaps.GetMorganFingerprint)

Получим 3D конформацию нашего кандидата

In [33]:
candidate_3d = Chem.AddHs(candidate)
Chem.AllChem.EmbedMolecule(candidate_3d)
AllChem.MMFFOptimizeMolecule(candidate_3d, maxIters=500, nonBondedThresh=200);
In [34]:
candidate_3d
Out[34]:

Визуализировать с помощью как nglviewer, так и py3Dmol не удалось, так что воспользуемся демонстрацией из PyMOL.

In [35]:
w = Chem.PDBWriter('files/candidate_3d.pdb')
w.write(candidate_3d, confId=0)
In [36]:
import __main__
__main__.pymol_argv = ['pymol', '-x']

import pymol
pymol.finish_launching()

from pymol import cmd
In [37]:
cmd.do('''reinitialize
bg_color white
load files/candidate_3d.pdb, candidate
select candidate
orient
zoom
deselect
mset 1x90
mview store, 1
mview store, 90
turn y, 120
mview store, 30
turn y, 120
mview store, 60
''')
In [39]:
HTML('''
<div align="middle">
<video width="80%" controls>
      <source src="img/candidate.mp4" type="video/mp4">
</video></div>''')
Out[39]: