In [7]:
from pathlib import Path

OUTDIR = Path("2_ssh")
OUTDIR.mkdir(exist_ok=True)
OUTDIR
Out[7]:
PosixPath('2_ssh')
In [2]:
from rdkit import Chem
from rdkit.Chem import AllChem, Draw
from rdkit.Chem.Draw import IPythonConsole, SimilarityMaps, rdMolDraw2D
from rdkit.Chem import Lipinski, rdMolDescriptors, Crippen
from IPython.display import display, Image, SVG
import pandas as pd
import pubchempy as pcp
import nglview as nv

Ибупрофен¶

Сначала возьмём SMILES ибупрофена, создадим молекулу в RDKit и отрисуем её 2D-структуру.
После этого я посчитаю параметры, связанные с правилом пяти Липински.

In [3]:
ibu_smi = 'CC(C)CC1=CC=C(C=C1)C(C)C(=O)O'
ibu = Chem.MolFromSmiles(ibu_smi)
AllChem.Compute2DCoords(ibu)
display(ibu)
No description has been provided for this image
In [4]:
print('H-donors :', Lipinski.NumHDonors(ibu))

# создаём объект молекулы из SMILES
print('H-acceptors :', Lipinski.NumHAcceptors(ibu))
print('Exact MW :', round(rdMolDescriptors.CalcExactMolWt(ibu), 2))
print('LogP :', round(Crippen.MolLogP(ibu), 2))
H-donors : 1
H-acceptors : 1
Exact MW : 206.13
LogP : 3.07

Поиск азидов в PubChem¶

Теперь нужно найти в PubChem молекулы, содержащие азидную группу.
Для простоты использую два варианта записи этой группы.

In [6]:
raw_smiles = []

# ищем по двум вариантам записи азидной группы
for query in ['N=[N+]=[N-]', 'NN#N']:
    try:
        hits = pcp.get_properties(
            'SMILES',
            query,
            namespace='smiles',
            searchtype='substructure',
            listkey_count=5000
        )
        raw_smiles.extend([x['SMILES'] for x in hits if 'SMILES' in x])
        print(query, '->', len(hits))
    except Exception as e:
        print(query, '-> ошибка:', e)
N=[N+]=[N-] -> 5000
NN#N -> 5000
In [7]:
hits[0]
Out[7]:
{'CID': 33557, 'SMILES': '[N-]=[N+]=[N-].[Na+]'}

Очень большие молекулы нам не нужны, оставлю только относительно короткие SMILES. Плюс уберу соли, т.д.

In [8]:
smiles = sorted({s for s in raw_smiles if len(s) < 60 and '.' not in s})
print('После простой фильтрации:', len(smiles))
smiles[:10]
После простой фильтрации: 2236
Out[8]:
['B(C1=C(C=CC(=C1)CCCN=[N+]=[N-])OCC(=C)C)(O)O',
 'B(C1=CC2=NN(N=C2C(=C1)F)C)(O)O',
 'B(C1=CC=C(C=C1)N2C(=C(N=N2)C(=O)NC3=CC(=NC=C3)C)C4CC4)(O)O',
 'B(O)(ON(C1=CC(=CC=C1)[N+](=O)[O-])[N+]#N)OF',
 'B(O)(ON(C1=CC=C(C=C1)F)[N+]#N)OF',
 'B([O-])(ON(C1=CC(=CC=C1)[N+](=O)[O-])[N+]#N)OF',
 'B([O-])(ON(C1=CC=C(C=C1)F)[N+]#N)OF',
 'B1(C2=C(CO1)C=C(C=C2)NN3C=C(C=N3)C(=O)N)O',
 'B1(OC(C(O1)(C)C)(C)C)C2=CC3=C(C=C2OC)N=NN3C',
 'B1(OC(C(O1)(C)C)(C)C)C2=CN(N=N2)C3CN(C3)C(=O)OC(C)(C)C']

Эмуляция click-продукта¶

Для клик-химии нужен терминальный алкин, поэтому обычный ибупрофен я модифицировала, добавив к нему пропаргильный фрагмент. В качестве простого click-совместимого производного использован пропаргиловый эфир ибупрофена.

Далее я не моделирую реакцию полностью, а эмулирую продукт: азидная группа в найденных молекулах заменяется на один фиксированный триазольный фрагмент, содержащий ибупрофен.

In [12]:
# обычный ибупрофен
ibu_smi = 'CC(C)CC1=CC=C(C=C1)C(C)C(=O)O'
ibu = Chem.MolFromSmiles(ibu_smi)

# модифицированный ибупрофен с терминальным алкином
ibu_click_smi = 'CC(C)CC1=CC=C(C=C1)C(C)C(=O)OCC#C'
ibu_click = Chem.MolFromSmiles(ibu_click_smi)

for mol in [ibu, ibu_click]:
    AllChem.Compute2DCoords(mol)

display(Draw.MolsToGridImage(
    [ibu, ibu_click],
    molsPerRow=2,
    subImgSize=(300, 220),
    legends=['Ibuprofen', 'Modified ibuprofen']
))
No description has been provided for this image
In [16]:
# сюда вместо азида будет "подключаться" исходный радикал R
template = 'n1cc(nn1)COC(=O)C(C)c2ccc(CC(C)C)cc2'

Уточнение: для эмуляции click chemistry ибупрофен был модифицирован введением терминального алкина (пропаргиловый эфир ибупрофена). Далее азидная группа в найденных молекулах заменялась на вручную заданный фрагмент, соответствующий одному из возможных триазольных продуктов 'циклоприсоединения' с этим производным ибупрофена.

Построение новых аналогов¶

Из найденных структур я оставляю только те, где азидная группа записана как N=[N+]=[N-], затем заменяю её на триазольный фрагмент с ибупрофеном. После этого создаю новые молекулы RDKit и отбираю только те, которые проходят правило пяти Липински.

In [17]:
def lipinski_ok(mol):
    return (
        Lipinski.NumHDonors(mol) <= 5 and
        Lipinski.NumHAcceptors(mol) <= 10 and
        rdMolDescriptors.CalcExactMolWt(mol) <= 500 and
        Crippen.MolLogP(mol) <= 5
    )
In [18]:
selected = []
rows = []

for smi in smiles[:1500]:
    if 'N=[N+]=[N-]' not in smi:
        continue

    # заменяем только первое вхождение азида
    newsmi = smi.replace('N=[N+]=[N-]', template, 1)

    try:
        newmol = Chem.MolFromSmiles(newsmi)
        if newmol is None:
            continue

        # Oставляем только структуры, проходящие правило пяти
        if not lipinski_ok(newmol):
            continue

        AllChem.Compute2DCoords(newmol)

        selected.append(newmol)
        rows.append({
            'original_azide_smiles': smi,
            'analogue_smiles': Chem.MolToSmiles(newmol),
            'HBD': Lipinski.NumHDonors(newmol),
            'HBA': Lipinski.NumHAcceptors(newmol),
            'ExactMolWt': round(rdMolDescriptors.CalcExactMolWt(newmol), 2),
            'LogP': round(Crippen.MolLogP(newmol), 2)
        })

    except:
        pass

print('Прошло правило 5:', len(selected))
Прошло правило 5: 65
In [34]:
df = pd.DataFrame(rows).drop_duplicates(subset='analogue_smiles').reset_index(drop=True)
selected = [Chem.MolFromSmiles(s) for s in df['analogue_smiles']]

for mol in selected:
    AllChem.Compute2DCoords(mol)

df.head(10)
Out[34]:
original_azide_smiles analogue_smiles HBD HBA ExactMolWt LogP
0 B(C1=C(C=CC(=C1)CCCN=[N+]=[N-])OCC(=C)C)(O)O C=C(C)COc1ccc(CCCn2cc(C(Cc3ccc(C(C)C)cc3)C(=O)... 3 7 491.26 3.08
1 B1(OC(C(O1)(C)C)(C)C)[C@@H](CCCN=[N+]=[N-])Cl CC(C)c1ccc(CC(C(=O)O)c2cn(CCC[C@@H](Cl)B3OC(C)... 1 6 475.24 4.83
2 B1=CC2=C(O1)C=C(C=C2)N=[N+]=[N-] CC(C)c1ccc(CC(C(=O)O)c2cn(-c3ccc4cboc4c3)nn2)cc1 1 5 373.16 3.89
3 C#CC1=CC(=CC(=C1)CBr)N=[N+]=[N-] C#Cc1cc(CBr)cc(-n2cc(C(Cc3ccc(C(C)C)cc3)C(=O)O... 1 4 451.09 4.68
4 C(C(CN=[N+]=[N-])([N+](=O)[O-])Br)N=[N+]=[N-] CC(C)c1ccc(CC(C(=O)O)c2cn(CC(Br)(CN=[N+]=[N-])... 1 7 465.08 3.49
5 C(C1[C@H](C([C@H]([C@@H](O1)O)N=[N+]=[N-])O)O)O CC(C)c1ccc(CC(C(=O)O)c2cn([C@@H]3C(O)[C@H](O)C... 5 9 421.18 -0.22
6 C(C1[C@H](C([C@H]([C@@H](O1)O)O)O)O)N=[N+]=[N-] CC(C)c1ccc(CC(C(=O)O)c2cn(CC3O[C@@H](O)[C@H](O... 5 9 421.18 -0.39
7 C(CC(=O)CCC(=O)CCC(=O)CCCN=[N+]=[N-])CN CC(C)c1ccc(CC(C(=O)O)c2cn(CCCC(=O)CCC(=O)CCC(=... 2 8 498.28 3.60
8 C(CC(=O)O)CC(=O)OCCCN=[N+]=[N-] CC(C)c1ccc(CC(C(=O)O)c2cn(CCCOC(=O)CCCC(=O)O)n... 2 7 431.21 3.00
9 C(CC(=O)O)CC(CN=[N+]=[N-])(F)F CC(C)c1ccc(CC(C(=O)O)c2cn(CC(F)(F)CCCC(=O)O)nn... 2 5 409.18 3.70
In [35]:
df.to_csv(OUTDIR / 'selected_analogs.csv', index=False)
In [52]:
display(Draw.MolsToGridImage(
    selected[1:5],
    molsPerRow=2,
    subImgSize=(500, 500),
    legends=[f'#{i+1}' for i in range(min(4, len(selected)))]
))
No description has been provided for this image

Для одного из найденных аналогов построим similarity map.
Она помогает наглядно увидеть, какие части молекулы сильнее влияют на выбранное свойство или сходство.

In [50]:
from rdkit.Chem import Draw
from rdkit.Chem.Draw import SimilarityMaps
from IPython.display import Image

refmol = ibu
somemol = selected[4] if len(selected) >= 5 else selected[-1]

drawer = Draw.MolDraw2DCairo(700, 500)

draw2d, maxweight = SimilarityMaps.GetSimilarityMapForFingerprint(
    refmol,
    somemol,
    SimilarityMaps.GetMorganFingerprint,
    draw2d=drawer
)

drawer.FinishDrawing()

with open(OUTDIR / 'similarity_map.png', 'wb') as f:
    f.write(drawer.GetDrawingText())

Image(filename=str(OUTDIR / 'similarity_map.png'))
[16:39:48] DEPRECATION WARNING: please use MorganGenerator
[16:39:48] DEPRECATION WARNING: please use MorganGenerator
Out[50]:
No description has been provided for this image

На карте видно, что основной вклад в сходство аналога с ибупрофеном вносит левый фрагмент молекулы, содержащий ароматическое кольцо, изобутильный заместитель и карбоксильную группу. Эти области выделены зелёным цветом. Присоединённый справа триазольный и азидсодержащий фрагмент окрашен в розово-фиолетовые тона, что указывает на его меньший вклад в сходство с ибупрофеном и на то, что именно он сильнее всего отличает новый аналог от исходной молекулы.

Теперь выведем 3D-модель:

In [45]:
m2d = selected[4] if len(selected) >= 5 else selected[-1]

m3d = Chem.AddHs(m2d)
AllChem.EmbedMolecule(m3d, randomSeed=1)
AllChem.MMFFOptimizeMolecule(m3d, maxIters=500, nonBondedThresh=200)
Out[45]:
0
In [46]:
nv.show_rdkit(m3d)
NGLWidget()
In [47]:
w = Chem.SDWriter(str(OUTDIR / 'analogue_3d.sdf'))
w.write(m3d)
w.close()

Вывод¶

С помощью RDKit и PubChem были найдены азидсодержащие молекулы. Для эмуляции продукта click chemistry был выбран модифицированный ибупрофен с алкиновым фрагментом, после чего азидная группа в найденных структурах заменялась на триазольный фрагмент с ибупрофеном. Полученные аналоги были превращены в объекты RDKit и отфильтрованы по правилу пяти Липински. Для одного из отобранных соединений были построены similarity map и 3D-конформация.