Хемоинформатика

Молекулярное моделирование биополимеров, 2017. Факультет биоинженерии и биоинформатики, МГУ им. М. В. Ломоносова

In [1]:
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 numpy as np
from IPython.display import display,Image
import rdkit.Chem.Lipinski as Lipinski

Небольшая функция для удобства

In [2]:
def chemdraw(smiles):
    mol = Chem.MolFromSmiles(smiles)
    AllChem.Compute2DCoords(mol)
    display(mol)

Нарисуем ибупрофен

In [3]:
chemdraw('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O')

Можно предложить множество позиций для введения алкильного радикала для осуществления затем комбинаторного синтеза с помощью клик-химии. Ниже я привел три примера.

In [4]:
chemdraw('CC(C)C(C#C)C1=CC=C(C=C1)C(C)C(=O)O')
In [5]:
chemdraw('CC(C)CC1=CC=C(C=C1)C(C(C#C))C(=O)O')
In [6]:
chemdraw('CC(C)CC1=CC=C(C(C#C)=C1)C(C)C(=O)O')

Теперь нарисуем второго участника реакции — какое-либо вещество с азидом N=[N+]=[N-]

In [7]:
chemdraw('C1C(COC1=O)CN=[N+]=[N-]')

Перестроим SMILES модификаций ибупрофена, чтобы мимикрировать результат клик-реакции. Атом азота, к которому присоединяется радикал при азиде, должен стоять первым для удобства дальнейшей манипуляции.

In [8]:
template_1 = 'N1N=NC(=C1)C(C(C)(C))C1=CC=C(C=C1)C(C)C(=O)O'
chemdraw(template_1)
In [9]:
template_2 = 'N1N=NC(=C1)CC(C(=O)O)C1=CC=C(C=C1)CC(C)C'
chemdraw(template_2)
In [10]:
template_3 = 'N1N=NC(=C1)C1=CC(CC(C)C)=CC=C1(C(C)C(=O)O)'
chemdraw(template_3)

Сделав так, мы можем "осуществить" клие-реакцию простой функцией:

In [11]:
def click(ibu,azide):
    return azide.replace('N=[N+]=[N-]',ibu)

Протестируем на уже знакомом нам азиде

In [12]:
test = click(template_1, 'C1C(COC1=O)CN=[N+]=[N-]')
chemdraw(test)

Путем структурного поиска в PubChem были найдены вещества, содержащие азид, и их SMILES сохранены в файл. Отберем только небольшие азид-содержащие вещества:

In [13]:
smiles = []
with open('pubchem.txt','r') as pchem:
    for line in pchem:
        tokens = line.split()
        smile = tokens[1]
        if len(smile) < 30 and '.' not in smile: # we exclude '.': '.' means 2+ separated ions/molecules
            smiles.append(smile)

Сколько веществ осталось в выборке?

In [14]:
len(smiles)
Out[14]:
8821

Проведем клик-реакцию для всех азидов против трех алкил-содержащих ибупрофенов и отсеим только те производные, что отвечают правилу 5-ти Липински (я использовал его расширенную формулировку, включающую еще число способных вращаться связей и индекс рефракции. Есть и еще более расширенные варианты)

In [22]:
hits = np.zeros((3*len(smiles),8), dtype=np.float16)
    
allgood = []
templates = (template_1, template_2, template_3)

good_k = 0
for smile in smiles:
    if 'N=[N+]=[N-]' in smile:
        for i in range(3):
            template = templates[i]
            hit_smile = click(template,smile)
            
            try:
                hit = Chem.MolFromSmiles(hit_smile)
                hit_hbdon = Lipinski.NumHDonors(hit)
                hit_hbacc = Lipinski.NumHAcceptors(hit)
                hit_mw    = Lipinski.rdMolDescriptors.CalcExactMolWt(hit)
                hit_nrotb = Lipinski.NumRotatableBonds(hit)
                hit_cripp = Lipinski.rdMolDescriptors.CalcCrippenDescriptors(hit)
                hit_logp  = hit_cripp[0]
                hit_refr  = hit_cripp[1]
                
                # Lipinsky rule of 5 (extended with rotatable bonds and refraction coefficient)
                
                if hit_hbdon <= 5 and hit_hbacc <= 10 and hit_mw <= 500 and hit_nrotb <= 10 and hit_logp <= 5 and (abs(hit_refr - 85) <= 45):
                    allgood.append(hit)
                    hits[good_k,:] = [good_k,i,hit_hbdon,
                                      hit_hbacc,hit_mw,hit_nrotb,
                                      hit_logp,hit_refr]
                    good_k += 1
                
            except:
                continue
RDKit ERROR: [02:11:52] Explicit valence for atom # 1 Cl, 2, is greater than permitted
RDKit ERROR: [02:11:52] Explicit valence for atom # 1 Cl, 2, is greater than permitted
RDKit ERROR: [02:11:52] Explicit valence for atom # 1 Cl, 2, is greater than permitted
In [23]:
hits = hits[:good_k,:]
In [24]:
len(allgood) == len(hits)
Out[24]:
True

Возьмем только лучшие 50 по параметру logP

In [27]:
top50idx = []
for c,hit in enumerate(sorted(hits, key = lambda x: x[6])):
    if c < 50:
        print('%d\tlogP=%.3f' % (c,hit[6]))
        idx = int(hit[0])
        top50idx.append(idx)
        mol = allgood[idx]
        AllChem.Compute2DCoords(mol)
        display(mol)        
0	logP=-0.547
1	logP=-0.317
2	logP=-0.181
3	logP=-0.103
4	logP=-0.035
5	logP=0.050
6	logP=0.088
7	logP=0.107
8	logP=0.128
9	logP=0.128
10	logP=0.207
11	logP=0.226
12	logP=0.302
13	logP=0.328
14	logP=0.329
15	logP=0.332
16	logP=0.388
17	logP=0.407
18	logP=0.409
19	logP=0.454
20	logP=0.474
21	logP=0.489
22	logP=0.495
23	logP=0.520
24	logP=0.532
25	logP=0.552
26	logP=0.560
27	logP=0.561
28	logP=0.572
29	logP=0.573
30	logP=0.573
31	logP=0.577
32	logP=0.583
33	logP=0.593
34	logP=0.602
35	logP=0.602
36	logP=0.603
37	logP=0.612
38	logP=0.629
39	logP=0.637
40	logP=0.651
41	logP=0.659
42	logP=0.669
43	logP=0.669
44	logP=0.669
45	logP=0.670
46	logP=0.681
47	logP=0.695
48	logP=0.700
49	logP=0.705

Попадаются дубликаты, например, 0, 2, 3. Видимо, это следствие избыточности исходной выборки из PubChem (либо хиральность, не отражаемая в 2D). Любопытно, что для этих веществ вычисленные значения logP различны.

In [41]:
def to3d(m):
    m3d=Chem.AddHs(m)
    Chem.AllChem.EmbedMolecule(m3d)
    AllChem.MMFFOptimizeMolecule(m3d,maxIters=500,nonBondedThresh=200)
    return m3d
In [32]:
top20mols = [allgood[x] for x in top50idx[:20]]
In [36]:
Chem.Draw.MolsToGridImage(top20mols, molsPerRow=4, subImgSize=(200,150))
Out[36]:
In [42]:
top20_3d = list(map(to3d,top20mols))
In [39]:
import nglview
import ipywidgets

Некоторые варианты в 3D

In [43]:
nglview.show_rdkit(top20_3d[0])
In [44]:
nglview.show_rdkit(top20_3d[7])
In [45]:
nglview.show_rdkit(top20_3d[17])