import numpy as np
import pymol
from pymol import cmd, stored
from IPython.display import Image, Video
import time
pymol.finish_launching(['pymol', '-xQ'])
Скрипты, предложенные в качестве упражнения (с комментариями):
cmd.do('''
fetch 1cll, async=0
as lines, name C+O+N+CA
zoom resi 4+5
mset 1 x1000
mview store
''')
# загружаем структуру с PDB ID 1CLL; async=0 нужен, чтобы следующие команды не выполнялись до загрузки, т. к. обычно она идет в фоне
# изображаем в виде линий остов (здесь атомы выбраны по именам; можно было просто backbone)
# переведем камеру на остатки 4 и 5
# создадим 1000 кадров анимации из состояния 1
# делаем текущий кадр ключевым, сохраняя в него вид и состояние
stored.res = []
cmd.iterate('1cll and name CA and resi 4-', 'stored.res.append(resi)') # итерируем по C-alpha атомам и добавляем номера остатков в список
cmd.bg_color('white')
colors = np.linspace(1, 0, len(stored.res)) # генерируем массив чисел от 0 до 1 с длиной, равной кол-ву остатков
for k, resi in enumerate(stored.res):
cmd.set_color('color_{}'.format(k), [colors[k], 0.5, 0.75]) # для каждого остатка задаем цвет, в котором красный канал берется из массива
cmd.set('cartoon_color', 'color_{}'.format(k) , 'resi {}'.format(resi)) # и красим cartoon-представление остатка в этот цвет
cmd.show_as('cartoon', 'all') # показываем cartoon
cmd.mclear()
cmd.mset('1 x{}'.format(len(stored.res) * 10)) # пусть число кадров в анимации будет равно 10x длина белка
cmd.mview('store')
for k, i in enumerate(stored.res):
cmd.frame(10 * (k + 1)) # переключаемся на каждый 10-й кадр анимации
cmd.zoom('name CA and resi {}'.format(int(i)), buffer=5) # переводим камеру на соответствующий остаток
cmd.mview('store') # сохраняем вид в текущий кадр
# получилась анимация, показывающая по очереди остатки, при этом переходы плавные, т. к. оставшиеся кадры интерполируются между ключевыми
cmd.movie.produce('iter_res.mp4', quality=75)
Video('iter_res.mp4')
Этот инструмент, конечно, намного удобнее использовать интерактивно, но можно и из скрипта.
cmd.reinitialize()
cmd.bg_color('white')
cmd.fetch('1lmp', type='pdb')
cmd.show_as('cartoon', 'all')
cmd.show_as('sticks', 'chain B')
cmd.util.cbag('chain B')
cmd.spectrum('resi', 'rainbow', 'polymer.protein and name ca')
cmd.set_view((
-0.067935139, -0.896828830, 0.437129945,
-0.028282769, 0.439697266, 0.897701979,
-0.997288942, 0.048622441, -0.055236682,
0.000000000, 0.000000000, -160.223159790,
16.166221619, 47.597465515, 19.186870575,
132.890777588, 187.555541992, -20.000000000))
cmd.ipython_image(ray=1)
Попробуем с помощью sculpting немного размотать глобулу белка с N-конца и вытащить лиганд из сайта связывания:
cmd.wizard('sculpting')
cmd.refresh_wizard()
cmd.get_wizard().set_radius(20)
cmd.select('pk1', '/1lmp//A/LYS`1/CA')
cmd.get_wizard().do_pick(0) # выберем C-alpha атом N-концевого остатка белка центральным и начнем sculpting
cmd.alter_state(1, '/1lmp//A/LYS`1', 'x+=20') # оттянем остаток куда-то в сторону
cmd.alter_state(1, '/1lmp//A/LYS`1', 'z+=-20')
time.sleep(5) # подождем, пока релаксируется
cmd.get_wizard().free_all()
cmd.select('pk1', '/1lmp//B/NAG`3/C3') # то же самое для лиганда
cmd.get_wizard().do_pick(0)
cmd.alter_state(1, '/1lmp//B/NAG`3', 'y+=20')
cmd.alter_state(1, '/1lmp//B/NAG`3', 'z+=10')
time.sleep(5)
cmd.set_wizard()
cmd.spectrum('resi', 'rainbow', 'polymer.protein and name ca')
cmd.set_view((
-0.067935139, -0.896828830, 0.437129945,
-0.028282769, 0.439697266, 0.897701979,
-0.997288942, 0.048622441, -0.055236682,
0.000000000, 0.000000000, -160.223159790,
16.166221619, 47.597465515, 19.186870575,
132.890777588, 187.555541992, -20.000000000))
cmd.ipython_image(ray=1)
cmd.reinitialize()
cmd.fetch('1lmp')
cmd.wizard('mutagenesis')
cmd.refresh_wizard()
cmd.get_wizard().do_select('/1lmp//A/ASP`52')
cmd.get_wizard().set_mode('ILE')
cmd.get_wizard().apply()
cmd.save('1lmp_mut.pdb')
Asp52 образует водородную связь с лигандом, поэтому его замена на алифатический изолейцин может нарушить связывание
cmd.do('''
rei
bg_color white
set cartoon_transparency, 0.3
set specular, 0
set stick_radius, 0.35
set dash_radius, 0.15
fetch 1lmp, async=0
load 1lmp_mut.pdb
set cartoon_color, grey90, 1lmp
set cartoon_color, lightblue, 1lmp_mut
as cartoon, all
as licorice, chain B
set cartoon_fancy_helices, 1
set cartoon_fancy_sheets, 1
set valence, 0
color green, ///B
util.cnc ///B
show licorice, ////52
set cartoon_side_chain_helper, 1
color wheat, /1lmp//A/52
color slate, /1lmp_mut//A/52
util.cnc ///A/52
distance h_bond, /1lmp//A/ASP`52/OD2, /1lmp//B/NDG`1/O1
hide labels, h_bond
color cyan, h_bond
set movie_auto_interpolate, 0
set matrix_mode, 1
set movie_panel, 1
set scene_buttons, 1
set cache_frames, 1
config_mouse three_button_motions, 1
mset 1x250
set_view (\
-0.070946813, 0.454174250, 0.888083220,\
0.997249424, 0.013156364, 0.072939575,\
0.021443166, 0.890815139, -0.453858197,\
0.000000000, 0.000000000, -198.787551880,\
14.313907623, 75.994682312, 20.765768051,\
144.848373413, 252.726791382, -20.000000000 )
frame 1
translate [50,0,0], object=1lmp_mut
mview store
mview store, object=1lmp
mview store, object=1lmp_mut
frame 20
mview store
mview store, object=1lmp
mview store, object=1lmp_mut
frame 100
center 1lmp
translate [-50,0,0], object=1lmp_mut
mview store
mview store, object=1lmp
mview store, object=1lmp_mut
frame 200
set_view (\
-0.865439832, 0.280594200, 0.415061891,\
0.150427088, -0.644699991, 0.749487519,\
0.477893174, 0.711077034, 0.515738726,\
-0.000037510, -0.000080432, -26.044509888,\
20.041763306, 57.592155457, 26.446636200,\
-11.748870850, 63.795650482, -20.000000000 )
mview store
mview store, object=1lmp
mview store, object=1lmp_mut
frame 250
mview store
mview store, object=1lmp
mview store, object=1lmp_mut
mview reinterpolate, object=1lmp
mview reinterpolate, object=1lmp_mut
mview reinterpolate
''')
cmd.movie.produce('mutation.mp4', quality=75)
Video('mutation.mp4')
cmd.do('''
rei
fetch 1lmp, async=0
fetch cid_2762604, tamra, async=0
remove tamra and (id 2 or id 54)
fuse tamra and id 29, /1lmp//A/SER`37/OG
delete tamra
zoom /1lmp//A/SER`37/OG
set_dihedral /1lmp//A/SER`37/CA, /1lmp//A/SER`37/CB, /1lmp//A/SER`37/OG, /1lmp///UNK`0/C26, 180
set_dihedral /1lmp//A/SER`37/CB, /1lmp//A/SER`37/OG, /1lmp///UNK`0/C26, /1lmp///UNK`0/C17, 180
unpick
''')
cmd.do('''
bg_color white
set ray_shadow, 0
set cartoon_transparency, 0.3
set specular, 0
set stick_radius, 0.35
set cartoon_fancy_helices, 1
set cartoon_fancy_sheets, 1
set valence, 0
as cartoon, all
show licorice, /1lmp//A/SER`37 or /1lmp///UNK`0
set cartoon_side_chain_helper, 1
hide everything, hydrogens
set cartoon_color, grey90
set cartoon_transparency, 0.3
set_view (\
-0.554764450, 0.831972420, 0.007011981,\
0.402615875, 0.261071891, 0.877349138,\
0.728102148, 0.489544928, -0.479802132,\
-0.000068754, -0.000335315, -84.520309448,\
25.399314880, 51.911579132, 11.563386917,\
-79.771675110, 248.810974121, -20.000000000 )
''')
cmd.ipython_image(ray=1)
cmd.reinitialize()
for _ in range(100):
cmd.editor.attach_amino_acid('pk1', 'ala')
phi = -57
psi = -47
# получим номера остатков и установим значения торсионов вручную:
stored.resid = []
cmd.iterate('name CA', 'stored.resid.append(int(resi))')
for i in stored.resid[1:]:
cmd.set_dihedral(f'////{i - 1}/C', f'////{i}/N', f'////{i}/CA', f'////{i}/C', phi)
for i in stored.resid[:-1]:
cmd.set_dihedral(f'////{i}/N', f'////{i}/CA', f'////{i}/C', f'////{i + 1}/N', psi)
cmd.unpick()
def vis_preset():
cmd.do('''
bg_color white
set ray_shadow, 0
set stick_radius, 0.35
set valence, 0
as licorice, all
hide everything, hydrogens
orient
''')
vis_preset()
cmd.move('z', 200)
cmd.ipython_image(ray=1)
Вместо того, чтобы вручную задавать значения торсионных углов, можно сразу указать нужную вторичную структуру:
cmd.reinitialize()
for _ in range(100):
cmd.editor.attach_amino_acid('pk1', 'ala', ss=1)
cmd.unpick()
vis_preset()
cmd.move('z', 200)
cmd.ipython_image(ray=1)
Существует также готовая реализация (команда fab):
cmd.reinitialize()
cmd.fab('A' * 100, ss=1)
vis_preset()
cmd.move('z', 200)
cmd.ipython_image(ray=1)
Можно снова воспользоваться готовым решением (все-таки встроенный функционал PyMOL расширился со времен составления заданий практикума):
cmd.reinitialize()
cmd.fnab('ACGT' * 25, form='B')
vis_preset()
cmd.move('z', 500)
cmd.ipython_image(ray=1)
Собственно, все
А теперь реализуем добавление новых пар нуклеотидов самостоятельно, как и предлагалось в задании:
# сохраним структуры пар
for base in ['A', 'C', 'G', 'T']:
cmd.reinitialize()
cmd.fnab(base, form='B')
cmd.save(f'base_pair_{base}.pdb')
cmd.reinitialize()
cmd.fnab('AA', name='template', form='B') # создадим ApA-динуклеотид
cmd.load('base_pair_A.pdb')
cmd.pair_fit('/base_pair_A///1/', '/template///2/') # фиттируем сохраненную пару нуклеотидов на следующую из динуклеотида
matrix = cmd.get_object_matrix('base_pair_A') # получаем матрицу преобразования
matrix = np.matrix(np.array(matrix)).reshape((4, 4)) # делаем numpy.matrix, чтобы удобно было умножать и возводить в степень
matrix
matrix([[ 8.09016943e-01, 5.87785304e-01, -1.32823558e-07, -2.16755382e-01], [-5.87785304e-01, 8.09016943e-01, -1.71687145e-08, -3.56234938e-01], [ 9.73649961e-08, 9.19615175e-08, 1.00000000e+00, -3.37460977e+00], [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
def build_dna(seq, matrix=matrix):
seq = seq.upper()
cmd.create('dna', None)
for i, base in enumerate(seq):
name = f'new_{i}'
cmd.load(f'base_pair_{base}.pdb', name)
cmd.alter(f'/{name}//A//', f'resi={i + 1}')
cmd.alter(f'/{name}//B//', f'resi={-i - 1}')
cmd.transform_selection(name, (matrix ** i).flatten().tolist()[0]) # применяем матрицу преобразования, возведенную в степень номера основания
cmd.create('dna', f'dna or {name}')
cmd.bond(f"/dna//A/`{i}/O3'", f"/dna//A/`{i + 1}/P")
cmd.bond(f"/dna//B/`\\{-i}/P", f"/dna//B/`\\{-i - 1}/O3'")
cmd.delete('new_*')
cmd.util.cbag('dna')
cmd.reinitialize()
build_dna('ACGT' * 25)
vis_preset()
cmd.move('z', 500)
cmd.ipython_image(ray=1)