Запускаем PyMOL.

In [1]:
# pymol launching
import time
import __main__

__main__.pymol_argv = [ 'pymol', '-x' ]

### Если вывод в графическое окно тормозит или не нужен, то:
##__main__.pymol_argv = [ 'pymol', '-cp' ]

import pymol
pymol.finish_launching()
 
from pymol import cmd
from IPython.display import Image
In [2]:
# Borrowed from http://kodomo.cmm.msu.su/~sapsan/v2/terms/term8/PyMolPractice1.html.
defaultImage = './pymolimg.png'
def prepareImage(width=600, height=600, sleep=2, filename=defaultImage):
    ## To save the rendered image
    cmd.ray(width, height)
    cmd.png(filename)
    time.sleep(sleep)

Чистим рабочее пространство: мало ли что туда затесалось.
Загружаем интересующую структуру из PDB: 1lmp.

In [3]:
cmd.reinitialize()
cmd.fetch("1LMP", async=0)
prepareImage()
Image(defaultImage)
Out[3]:

Как нам выбрать лиганды? Мы откуда-нибудь знаем, что нам доступны следующие селекторы: hetatom, или het, возвращающий все атомы, не принадлежащие белкам, и solvent, или sol., возвращающий все атомы в молекулах воды. Вода нам не нужна, и её можно с чистой совестью удалить; все оставшиеся небелковые атомы будут лигандами. Их мы для удобства вынесем в отдельный объект.

In [4]:
cmd.remove("solvent")
# cmd.select("ligands", "het")
cmd.extract("ligands", "het")
cmd.show_as("sticks", "ligands")
prepareImage()
Image(defaultImage)
Out[4]:

Fun fact: if you make a selection of X, then extract X, then manually delete the selection, you can't refer to the extracted object! It's fun. And a parable not to use GUI.

Кхм.
Теперь выделим связи лиганда с белком. Для этого у нас есть команда distance, с аргументами: именем, объектами, между атомами которых смотрятся расстояния, максимальным интересным расстоянием и режимом.
Максимальным расстоянием мы выберем 3.2 (по традиции, 3.2 есть граница между связями водородными и прочими); режимом — режим 2: это должны быть приблизительно водородные связи.
Технически, distance <...> mode=2 может ошибаться и строить не только водородные связи, и конечное решение о том, что является водородной связью, остаётся на пользователе. Как мы могли бы проверить адекватность?
Если быть честными, то никак: квалификации, чтобы на глаз отличить истинную водородную связь от ложной, у нас нет.
Можно вручную добавить водороды; в таком случае связи буду нарисованы не между донором и акцептором, а между водородом и акцептором, набор связей принципиально не поменяется.

In [5]:
cmd.h_add("all")
cmd.distance("hbonds", "1LMP", "ligands", 3.2, 2)
prepareImage()
Image(defaultImage)
Out[5]:

Как сломать взаимодействие белка с лигандом? Двумя путями: отломать несколько водородных связей (2+), или вставить что-нибудь в пространство, которое занимает лиганд.
Пристальным взглядом смотрим на места контакта; замечаем, что ASP`52, будь он длиннее, вдавался бы в лиганд и не давал бы ему занимать подобающее место. Мутируем ASP в TYR.

In [6]:
cmd.wizard("mutagenesis")
cmd.do("refresh_wizard")
cmd.get_wizard().set_mode("TYR")
cmd.get_wizard().do_select("52/")
cmd.get_wizard().apply()
cmd.wizard(None)

Снимаем кино. В кино происходит совмещение двух белков — оригинального и мутированного — и фокус на мутированном остатке.

In [7]:
# Something below breaks hbonds in little pieces, so they're disabled.
cmd.disable("hbonds")
cmd.do('''

set matrix_mode, 1
set movie_auto_store, 1
set movie_auto_interpolate, 1
delete 1LMP_orig
fetch 1LMP, 1LMP_orig, async=0
translate [0, 0, 35], 1LMP_orig
rotate y, 40
rotate x, 25
orient

select tyr, resi 52 and 1LMP
select asp, resi 52 and 1LMP_orig

as sticks, tyr or asp
as sticks, ligand
color purple, n. C* in ligand
color cyan, n. CA+C+N+C* and 1LMP_orig

mset 1 x400

frame 1
mview store
mview store, object=1lmp
mview store, object=1lmp_orig

frame 200
center tyr
origin tyr
zoom tyr
super 1lmp_orig, 1lmp

mview store
mview store, object=1lmp
mview store, object=1lmp_orig

frame 400
orient ligand or tyr
zoom ligand or tyr
mview store
mview store, object=1lmp
mview store, object=1lmp_orig

frame 1
mplay
''')

Что такое сложноэфирная связь? Это связь R1-COOH + R2-OH → R1-COO-R2 + H2O.
Как присоединить флуор-метку:

  • Взять 3д-структуру метки, загрузить её в pymol;
  • Пристальным взглядом посмотреть на метку, понять, способна ли она входить в связь как кислота или как спирт. (Хинт: эта — как кислота, 2 возможными местами);
  • Пристальным взглядом посмотреть на аминокислоты, понять, какие из них способны входить в связь комплементарно к метке (Хинт: как спирт может входить только серин);
    • Смотрим на доступные серины в поисках такого, у которого OH-группа (aka атом OG) торчит наружу. Выбираем #37.
  • С помощью fuse склеить ключевые атомы. Вручную удалять HOH нет нужды: pymol неким магическим образом сделает это сам.
  • Подобрать торсионный угол связи, при котором метка и белок друг с другом не пересекаются. (fuse оставляет выделенной свежеобразованную связь, которую можно вращать с помощью torsion.)
In [8]:
cmd.reinitialize()
cmd.fetch("1LMP", async=0)
cmd.load("6-TAMRA.sdf")
cmd.remove("solvent")
# O-atoms in 6-TAMRA are not easily differentiated.
# Thankfully, one of them has a hydrogen attached.
cmd.select("O1-tamra", "n. O in 6-TAMRA within 2 of n. H in 6-TAMRA")
cmd.select("O1-serine", "SER`37/CB")
cmd.fuse("O1-tamra", "O1-serine")
cmd.torsion(-75)
cmd.select("sele", "1LMP///UNK/ or 1LMP///`36-38/")
cmd.orient("sele")
cmd.show_as("sticks", "sele")
# cmd.rotate()
prepareImage()
Image(defaultImage)
Out[8]:

Как сделать ∞-альфа-спираль:

  • Есть специальная функция, прикрепляющая аминокислоту к C- или N-атому.
  • А у неё есть аргумент, указывающий, какую вторичную структуру новая аминокислота должна образовывать! Удобно-то как.

Поэтому:

  • Подгружаем первую аминокислоту;
  • Выделяем в ней C-атом;
  • В цикле присоединяем новую аминокислоту к атому (выборке) pk1: команда присоединения каждый раз выделяет C-атом в присоединённой аминокислоте.
In [9]:
cmd.reinitialize()
cmd.fragment("lys")
cmd.edit("lys/C")
for i in range(1, 25):
    cmd.editor.attach_amino_acid("pk1", "lys", ss=1)
cmd.orient()
cmd.select("bbone", "n. CA+C+N in lys")
cmd.disable("bbone")
cmd.color("cyan","bbone")
cmd.rotate("z", -60)
cmd.show_as("sticks", "bbone")
prepareImage()
Image(defaultImage)
Out[9]:

Как сделать ∞-DNA:

  • Взять две (одинаковые) пары (ну, откуда-нибудь, из pdb, вестимо);
  • Распилить их, совместить первую со второй, получить матрицу перехода к следующей паре;
  • А дальше можно создавать копии пар, применять к ним матрицу в i-й степени и так получать ∞-DNA.

Что мы узнали в процессе:

  • Найти пару соседних оснований, которые хорошо совместятся совсем не так просто, как кажется!
    • Для A-ДНК неплохо подходят некоторые пары из 440d;
    • Для B-ДНК упорными усилиями удаётся подобрать две пары из характерно названного 1bna: A/`5/ - B/`20/ и A/`6/ - B/`19/.
    • Следует не забывать о цепях! Соседние CG и GC могут совместиться на ура, но при совмещении меняются местами цепи, и матрица перехода 1→2, применённая к 2, даёт не 2→3, а 2→1.
    • Очень полезное упражнение. Развеивает иллюзию регулярности ДНК.
  • Связи можно добавлять только в пределах одного объекта.
    • Что, вообще говоря, логично.
    • Однако при склейке нуклеотидной пары и её копии атомы в них не особенно различимы.
    • Нужно как-нибудь вручную их дифференцировать; например, прописать им различные resi.
In [14]:
cmd.reinitialize()
cmd.fetch("1bna", async=0)

cmd.hide("lines")

cmd.remove("solvent")

# 7-18 and 6-19 are good, but invert chains
# 5-20 and 6-19 are slightly worse, but keep the chains
cmd.extract("bp1", "A/`5/ or B/`20/")
cmd.extract("bp2_", "A/`6/ or B/`19/")

cmd.orient()
cmd.delete("1bna")

cmd.create("matrix", "bp1")
cmd.align("matrix", "bp2_")
cmd.delete("bp2_")

n = 30
cmd.alter("bp1/A///", "resi=1")
cmd.alter("bp1/B///", "resi={}".format(n))

# This works like so:
# Loop begins with a matrix pair, linked chain of length n and one unlinked
# pair. We apply matrix to the pair, make a copy, return pair to the
# original position, set resi of the new basepair as appropriate.
# Then we fuse and link the old pair with the chain, leaving a linked chain
# of length n+1 and an unlinked pair at the end of the loop. 
# Loop starts with a chain of legth zero, and only links basepairs starting 
# from 2nd. After the end, the extraneous basepair is deleted.
for i in range(1, n+1):
    cmd.matrix_copy("matrix", "bp{}".format(i))
    cmd.create("bp{}".format(i+1), "bp{}".format(i))
    cmd.matrix_reset("bp{}".format(i))

    cmd.alter("bp{}/A///".format(i+1), "resi={}".format(i+1))
    cmd.alter("bp{}/B///".format(i+1), "resi={}".format(n-i))
    
    if i <= 1: continue
    
    cmd.fuse("bp{}".format(i), "bp1", mode=3)
    cmd.delete("bp{}".format(i))
    cmd.bond("bp1/A//`{}/P".format(i), 
             "bp1/A//`{}/O3'".format(i-1))
    cmd.bond("bp1/B//`{}/P".format(n-i+2), 
             "bp1/B//`{}/O3'".format(n-i+1))

cmd.delete("bp{}".format(n+1))
cmd.delete("matrix")

cmd.orient()
cmd.rotate("z", -60)
cmd.show_as("sticks")

prepareImage()
Image(defaultImage)
Out[14]: