Задание 3

Предварительные ласки.

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
RDKit WARNING: [15:49:57] Enabling RDKit 2019.09.3 jupyter extensions
In [19]:
from tqdm import tqdm, trange
In [3]:
import rdkit.Chem.Lipinski as Lipinksy
In [11]:
def lipinsky(smiles):
    return (Lipinksy.NumHDonors(smiles),
            Lipinksy.NumHAcceptors(smiles),
            Lipinksy.rdMolDescriptors.CalcExactMolWt(smiles),
            Lipinksy.rdMolDescriptors.CalcCrippenDescriptors(smiles)[0])


def is_lipinsky(smiles, ideal=(5, 10, 500, 5)):
    return all(i < j for i, j in zip(lipinsky(smiles), ideal))

Создадим молекулу ибупрофена из SMILES и нарисуем её.

In [5]:
ibu=Chem.MolFromSmiles('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O')
AllChem.Compute2DCoords(ibu)
display(ibu)
In [12]:
is_lipinsky(ibu)
Out[12]:
True

Ибупрофен удовлетворяет правилу 5 Липински.

Модификация ибупрофена для клик-химии

Популярная реакция в клик-химии это азид-алкиновое циклоприсоединение.

In [26]:
display(Image('https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/CuAAC.svg/768px-CuAAC.svg.png'))

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

In [30]:
display(Image('https://upload.wikimedia.org/wikipedia/commons/f/fd/Alkynespreparation-3.png'))

Следовательно, нам надо ввести галоген в молекулу ибупрофена. С помощью реакции Гелля-Фольгарда-Зелинского можно галогенировать Cα-атом карбоновых кислот.

In [31]:
display(Image('https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/HVZReaction.png/600px-HVZReaction.png'))

Стоит отметить, что Cα-атом в ибупрофене хирален. Однако в медицине используется рацемат, поэтому нам не надо будет думать о стереспецифичности синтезов.

Посмотрим на хлор-производное ибупрофена.

In [33]:
cl_ibu=Chem.MolFromSmiles('CC(C)CC1=CC=C(C=C1)C(Cl)(C)C(=O)O')
AllChem.Compute2DCoords(cl_ibu)
display(cl_ibu)

Затем на алкиновое производное.

In [34]:
al_ibu=Chem.MolFromSmiles('CC(C)CC1=CC=C(C=C1)C(C#C)(C)C(=O)O')
AllChem.Compute2DCoords(al_ibu)
display(al_ibu)

И на клик-производное.

In [35]:
al_ibu=Chem.MolFromSmiles('CC(C)CC1=CC=C(C=C1)C(C1=CN=NN1)(C)C(=O)O')
AllChem.Compute2DCoords(al_ibu)
display(al_ibu)

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

In [36]:
template = Chem.MolFromSmiles('N1N=NC(=C1)C(C)(C(=O)O)C1=CC=C(C=C1)CC(C)C')
AllChem.Compute2DCoords(template)
display(template)

Азиды в PubChem

Теперь найдём все соединения, содержащие азид, в PubChem.

In [14]:
import pubchempy as pcp
In [22]:
compounds = []
per_page = 10**5
for smiles in tqdm(["N=N=N", "NN#N", "N=[N+]=[N-]"]):
    for i in trange(200):
        try:
            a = pcp.get_properties(properties="CanonicalSMILES", 
                                   identifier=smiles, namespace="smiles", 
                                   searchtype="substructure",
                                   RingsNotEmbedded=False,
                                   listkey_count=per_page,
                                   listkey_start=i * per_page)
        except Exception as e:
            tqdm.write(e)
        tqdm.write("Retrieved page {} of {} search".format(i+1, smiles))
        compounds.extend(a)



  0%|                                                                                            | 0/3 [00:00<?, ?it/s]




                                                                                                                       



                                                                                                           

                                                                                                                 


                                                                                                              
                                                                                                                    




                                                                                                        
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
~\Miniconda3\envs\pymol_env\lib\site-packages\PubChemPy-1.0.3-py3.6.egg\pubchempy.py in request(identifier, namespace, domain, operation, output, searchtype, **kwargs)
     80         log.debug('Request data: %s', postdata)
---> 81         response = urlopen(apiurl, postdata)
     82         return response

~\Miniconda3\envs\pymol_env\lib\urllib\request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    221         opener = _opener
--> 222     return opener.open(url, data, timeout)
    223 

~\Miniconda3\envs\pymol_env\lib\urllib\request.py in open(self, fullurl, data, timeout)
    530             meth = getattr(processor, meth_name)
--> 531             response = meth(req, response)
    532 

~\Miniconda3\envs\pymol_env\lib\urllib\request.py in http_response(self, request, response)
    640             response = self.parent.error(
--> 641                 'http', request, response, code, msg, hdrs)
    642 

~\Miniconda3\envs\pymol_env\lib\urllib\request.py in error(self, proto, *args)
    568             args = (dict, 'default', 'http_error_default') + orig_args
--> 569             return self._call_chain(*args)
    570 

~\Miniconda3\envs\pymol_env\lib\urllib\request.py in _call_chain(self, chain, kind, meth_name, *args)
    502             func = getattr(handler, meth_name)
--> 503             result = func(*args)
    504             if result is not None:

~\Miniconda3\envs\pymol_env\lib\urllib\request.py in http_error_default(self, req, fp, code, msg, hdrs)
    648     def http_error_default(self, req, fp, code, msg, hdrs):
--> 649         raise HTTPError(req.full_url, code, msg, hdrs, fp)
    650 

HTTPError: HTTP Error 400: PUGREST.BadRequest

During handling of the above exception, another exception occurred:

BadRequestError                           Traceback (most recent call last)
<ipython-input-22-d11ed918e1de> in <module>
     10                                    listkey_count=per_page,
---> 11                                    listkey_start=i * per_page)
     12         except Exception as e:

~\Miniconda3\envs\pymol_env\lib\site-packages\PubChemPy-1.0.3-py3.6.egg\pubchempy.py in get_properties(properties, identifier, namespace, searchtype, as_dataframe, **kwargs)
    212     properties = 'property/%s' % properties
--> 213     results = get_json(identifier, namespace, 'compound', properties, searchtype=searchtype, **kwargs)
    214     results = results['PropertyTable']['Properties'] if results else []

~\Miniconda3\envs\pymol_env\lib\site-packages\PubChemPy-1.0.3-py3.6.egg\pubchempy.py in get_json(identifier, namespace, domain, operation, searchtype, **kwargs)
    108     try:
--> 109         return json.loads(get(identifier, namespace, domain, operation, 'JSON', searchtype, **kwargs).decode())
    110     except NotFoundError as e:

~\Miniconda3\envs\pymol_env\lib\site-packages\PubChemPy-1.0.3-py3.6.egg\pubchempy.py in get(identifier, namespace, domain, operation, output, searchtype, **kwargs)
     96                 time.sleep(2)
---> 97                 response = request(identifier, namespace, domain, operation, 'JSON', **kwargs).read()
     98                 status = json.loads(response.decode())

~\Miniconda3\envs\pymol_env\lib\site-packages\PubChemPy-1.0.3-py3.6.egg\pubchempy.py in request(identifier, namespace, domain, operation, output, searchtype, **kwargs)
     83     except HTTPError as e:
---> 84         raise PubChemHTTPError(e)
     85 

~\Miniconda3\envs\pymol_env\lib\site-packages\PubChemPy-1.0.3-py3.6.egg\pubchempy.py in __init__(self, e)
   1088         if self.code == 400:
-> 1089             raise BadRequestError(self.msg)
   1090         elif self.code == 404:

BadRequestError: 'PUGREST.BadRequest'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
<ipython-input-22-d11ed918e1de> in <module>
     11                                    listkey_start=i * per_page)
     12         except Exception as e:
---> 13             tqdm.write(e)
     14         tqdm.write("Retrieved page {} of {} search".format(i+1, smiles))
     15         compounds.extend(a)

~\Miniconda3\envs\pymol_env\lib\site-packages\tqdm\std.py in write(cls, s, file, end, nolock)
    575         with cls.external_write_mode(file=file, nolock=nolock):
    576             # Write the message
--> 577             fp.write(s)
    578             fp.write(end)
    579 

~\Miniconda3\envs\pymol_env\lib\site-packages\ipykernel\iostream.py in write(self, string)
    396             # Make sure that we're handling unicode
    397             if not isinstance(string, unicode_type):
--> 398                 string = string.decode(self.encoding, 'replace')
    399 
    400             is_child = (not self._is_master_process())

AttributeError: 'BadRequestError' object has no attribute 'decode'

Всё очень плохо с API, поэтому будем искать ручками. PubChem CID для азида 33558.

Скачали из PubChem (substructure search, download as SMILES).

In [123]:
with open('azide_search.txt', 'r') as infile:
    compounds = [smiles
                 for smiles in map(lambda i: i.strip().split('\t')[1], infile)
                 if len(smiles) < 30 and len(smiles) > 1 and '.' not in smiles]
In [124]:
len(compounds)
Out[124]:
37479

Теперь заменим азиды на производное ибупрофена.

Азид может быть терминальным и нетерминальным, в некоторых SMILES Это учитывается.

In [125]:
azides = [Chem.MolFromSmiles('N=[N+]=[N-]'),
          Chem.MolFromSmiles('N=[N+]=[NH]')]
In [126]:
template
Out[126]:
In [127]:
broken_count = 0
unlinked_count = 0
modified_compounds = set()
for compound in compounds:
    mol = Chem.MolFromSmiles(compound)
    if mol is None:  # some SMILES are broken
        broken_count += 1
        continue
    modified = False
    for azide in azides:
        if mol.HasSubstructMatch(azide):
            mol = Chem.rdmolops.ReplaceSubstructs(mol=mol,
                                                  query=azide,
                                                  replacement=template,
                                                  replaceAll=True)[0]
            modified = True
        else:
            continue
    if modified:
        modified_compound = Chem.MolToSmiles(mol)
    if '.' not in modified_compound:  # some molecules break up upon modification
        modified_compounds.add(modified_compound)
    else:
        unlinked_count += 1
In [128]:
print(broken_count, unlinked_count, len(modified_compounds))
18 95 3517

Осталось только 10% молекул от исходных азидов. Видимо, как-то плохо искали. Теперь мы отфильтруем полученные молекулы по Липински.

In [129]:
final_compounds = [mol
                   for mol in map(lambda i: Chem.MolFromSmiles(i), modified_compounds)
                   if mol is not None and is_lipinsky(mol)]
In [130]:
len(final_compounds)
Out[130]:
0

Потеряли все молекулы. Видимо, плохо искали в PubChem. Возьмём файл от однокурсников.

In [133]:
with open('azids.csv', 'r') as infile:
    compounds = [smiles
                 for smiles in map(lambda i: i.strip().split(',')[2], infile)
                 if len(smiles) < 30 and len(smiles) > 1 and '.' not in smiles]
In [146]:
len(compounds)
Out[146]:
15442

Молекул в 2 раза меньше, чем было в первый раз.

In [155]:
broken_count = 0
unlinked_count = 0
modified_compounds = set()
for compound in compounds:
    mol = Chem.MolFromSmiles(compound)
    if mol is None:  # some SMILES are broken
        broken_count += 1
        continue
    modified = False
    for azide in azides:
        if mol.HasSubstructMatch(azide):
            mol = Chem.rdmolops.ReplaceSubstructs(mol=mol,
                                                  query=azide,
                                                  replacement=template,
                                                  replaceAll=True)[0]
            modified = True
        else:
            continue
    if modified:
        modified_compound = Chem.MolToSmiles(mol)
    if '.' not in modified_compound:  # some molecules break up upon modification
        modified_compounds.add(modified_compound)
    else:
        unlinked_count += 1
In [156]:
print(broken_count, unlinked_count, len(modified_compounds))
18 130 11933
In [157]:
final_compounds = [mol
                   for mol in map(lambda i: Chem.MolFromSmiles(i), modified_compounds)
                   if mol is not None and is_lipinsky(mol)]
In [158]:
len(final_compounds)
Out[158]:
1
In [159]:
display(Draw.MolsToGridImage(final_compounds, maxMols=9, molsPerRow=3, subImgSize=(200, 200)))

Всё очень плохо. Видимо, слишком умный способ ломает SMILES и ничего не получается. Попробуем по-простому.

In [160]:
final_compounds = []
for compound in compounds:
    if "N=[N+]=[N-]" in compound:
        compound = compound.replace("N=[N+]=[N-]", 'N1N=NC(=C1)C(C)(C(=O)O)C1=CC=C(C=C1)CC(C)C')
    else:
        continue
    try:
        mol = Chem.MolFromSmiles(compound)
        if is_lipinsky(mol):
            final_compounds.append(mol)
    except:
        pass
In [161]:
len(final_compounds)
Out[161]:
13744

Зато почти все сохранились после модификации.

In [164]:
display(Draw.MolsToGridImage(final_compounds, maxMols=9, molsPerRow=3, subImgSize=(400, 400)))

Построим карты схожестей производных с ибупрофеном.

In [165]:
from rdkit.Chem.Draw import SimilarityMaps
In [174]:
similarity_data = [SimilarityMaps.GetSimilarityMapForFingerprint(ibu, mol, SimilarityMaps.GetMorganFingerprint)
                   for mol in final_compounds[:5]]
In [175]:
list(zip(*similarity_data))[1]
Out[175]:
(0.1776773950686994,
 0.17400606168657512,
 0.18149253731343284,
 0.1875,
 0.17956656346749222)

Веса схожести небольшие, кажется. Странно, что часть исходной молекулы ибупрофена покрашена розовым, а не зелёным, мол, не очень похожа на исходную молекулу.

3D-изображение молекул

In [176]:
m3d = Chem.AddHs(final_compounds[0])
Chem.AllChem.EmbedMolecule(m3d)
AllChem.MMFFOptimizeMolecule(m3d, maxIters=500, nonBondedThresh=200)
Out[176]:
0
In [177]:
display(m3d)

Жизнь потрепала молекулу.

In [180]:
import nglview as nv
In [181]:
nv.show_rdkit(m3d)

Прикольно получилось. А главное, удалось завести nglview в jupyter lab. Но в html оно не работает((

In [195]:
view = nv.show_rdkit(m3d)
In [196]:
view
In [197]:
view.render_image()
In [198]:
view._display_image()
Out[198]: