запускаем 'ipython notebook' и 'pymol -R' из рабочей директории.
Форматирование задаётся Markdown: http://daringfireball.net/projects/markdown/basics

Работа проводится с помощью putty и обработки pymol. Команды для bash выделены табуляцией.

Изучение работы методов контроля температуры в GROMACS


In [1]:
#Для работы с Pymol: прокси и загрузка картинок:
from xmlrpclib import ServerProxy
from IPython.display import Image
import time

# launch pymol
import __main__
#__main__.pymol_argv = [ 'pymol', '-x' ]

__main__.pymol_argv = [ 'pymol', '-cp' ]  # to disable GUI
import pymol
from pymol import cmd
pymol.finish_launching()

запускаю ipython c Windows, в виду того, что порты на кодомо закрыты, это не предосудительно. У меня нет Bash, потому я просто копирую строки из скрипта ниже в putty, и они выполняются по мере готовности. Тем не менее, тэг "%%bash" я поставлю.

Изучим реализацию контроля температуры в молекулярной динамике на примере GROMACS. Объект исследования – это одна молекула этана.

Сначала подготовим файл координат и файл топологии. На основе .gro файла с 38 молекулами этана создадим индекс-файл, в котором будет группа из одной молекулы этана.

файл box_38.gro взят с http://kodomo.fbb.msu.ru/FBB/year_08/term6/box_38.gro.

 wget http://kodomo.fbb.msu.ru/FBB/year_08/term6/box_38.gro
In [1]:
%%bash
make_ndx -f box_38.gro -o 1.ndx
# Then type `r1` to choose the first residue
# Then type `q` to save and quit
Couldn't find program: u'bash'

Теперь создадим .gro файл с 1 молекулой и зададим ячейку.

При запуске ediconf нужно выбрать номер группы - выбираем одну молекулу - (3).

Затем зададим ячейку и расположим молекулу по центру ячейки.

In [4]:
%%bash
editconf -f box_38.gro -o et1.gro -n 1.ndx
# print 3
editconf -f et1.gro -o et.gro -d 2 -c
Couldn't find program: u'bash'

Построим файл топологии et.top для этана. При этом необходимо, в частности, подобрать типы атомов: /usr/share/gromacs/top/oplsaa.ff/atomtypes.atp

opls_135 12.01100 ; alkane CH3

opls_140 1.00800 ; alkane H.

In [5]:
ethane = """#include "/usr/share/gromacs/top/oplsaa.ff/forcefield.itp"

[ moleculetype ]
; Name            nrexcl
et            3

[ atoms ]
;   nr  type        resnr  residue  atom    cgnr charge      mass
    1   opls_135    1      ETH      C1      1    -0.189      12.01
    2   opls_135    1      ETH      C2      2    -0.155      12.01
    3   opls_140    1      ETH      H1      3     0.0059     1.008
    4   opls_140    1      ETH      H2      4     0.0059     1.008
    5   opls_140    1      ETH      H3      5     0.0059     1.008
    6   opls_140    1      ETH      H4      6     0.0056     1.008
    7   opls_140    1      ETH      H5      7     0.0056     1.008
    8   opls_140    1      ETH      H6      8     0.0056     1.008
    
[ bonds ]
;  ai    aj funct  b0       kb
     1   2   1  
     1   4   1  
     1   5   1  
     2   6   1
     2   7   1  
     2   8   1 
[ angles ]
;  ai    aj    ak funct  phi0   kphi
;around c1
    3     1     4     1  
    4     1     5     1  
    3     1     5     1  
    2     1     3     1  
    2     1     4     1  
    2     1     5     1  
;around c2
    1     2     6     1   
    6     2     8     1   
    6     2     7     1   
    7     2     8     1   
    1     2     7     1  
    1     2     8     1  
[ dihedrals ]
;  ai    aj    ak    al funct  
    3    1     2     6      1  
    3    1     2     7      1 
    3    1     2     8      1  
    4    1     2     6      1  
    4    1     2     7      1  
    4    1     2     8      1  
    5    1     2     6      1 
    5    1     2     7      1  
    5    1     2     8      1  
[ pairs ]
;  ai    aj funct
   3  6
   3  7
   3  8
   4  6
   4  7
   4  8
   5  6
   5  7
   5  8

[ System ]
; any text here
first one
[ molecules ]
;Name count
 et    1"""

with open('et.top', 'w') as w:
    w.write(ethane)

так получили файл et.top (на рабочем компьютере), который был перекинут в рабочую директорию (putty) и использован sed:

sed -e 's/\r$//' et.top > et_sed.top 

но и так тестовый запуск (после скачивания http://kodomo.fbb.msu.ru/FBB/year_08/term6/be.mdp)

grompp -f be.mdp -c et.gro -p et.top -o et_test.tpr

или

grompp -f be.mdp -c et.gro -p et_sed.top -o et_test.tpr

не приводил к работе программы, которая ругалась на строки 44-52 файла et.top.

Лишь благодаря коллективному разуму (спасибо одному из немногих оставшихся однокурсникОВ) выход был найден с помощью замены параметра func с 1 на 3 в соответствующих строках ([ dihedrals ]).

Используем 5 файлов с разными параметрами контроля температуры:
  • be.mdp - метод Берендсена для контроля температуры.

  • vr.mdp - метод "Velocity rescale" для контроля температуры.

  • nh.mdp - метод Нуза-Хувера для контроля температуры.

  • an.mdp - метод Андерсена для контроля температуры.

  • sd.mdp - метод стохастической молекулярной динамики.

In [7]:
%%bash
for i in $(echo be vr nh an sd); do
    grompp -f $i.mdp -c et.gro -p et_sed.top -o et_$i.tpr
    mdrun -deffnm et_$i -v -nt 1;
done
# sometimes you have to use ctrl+c
Couldn't find program: u'bash'

анализ результатов

Сперва посмотрим на pdb-файлы молекул.

In [7]:
%%bash
for i in $(echo be vr nh an sd); do
    trjconv -f et_$i.trr -s et_$i.tpr -o et_$i.pdb;
done
Couldn't find program: u'bash'

In [2]:
from IPython.display import Image

defaultImage = 'tmp\mypng1.png' 
def prepareImage(width=300, height=300, sleep=2, filename=defaultImage):
    ## To save the rendered image
    cmd.ray(width, height)
    cmd.png('tmp\mypng1.png')
    time.sleep(sleep)

def showPDBoneState(name):
    cmd.do('''
    reinit
    bg white
    set antialias, 2
    load ''' + name + '.pdb' + '''
    split_states ''' + name + '''
    hide everything, all
    select main, ''' + name + '_0001' + '''
    show spheres, main
    set sphere_scale, 0.2
    show sticks, main
    set stick_radius, 0.12
    zoom main
    ''')
In [26]:
#showPDBoneState('et_be')
#showPDBoneState('et_vr')
showPDBoneState('et_nh')
#showPDBoneState('et_an')
#showPDBoneState('et_sd')

prepareImage(); Image(defaultImage)
Out[26]:

Интересно наблюдать, как меняется набор состояний, проигрывая "запись" states в pymol для разных методов. Получилось, что число состояний в разных методах разное. Занятно, что в методе Нуза-Хувера изменяется лишь длина одной связи СН, а в остальных методах молекула скачет по ячейке, меняет углы и длины связей.

То есть можно ждать некой "замороженности" молекулы в методе Нуза-Хувера.

In [34]:
%%bash
for i in $(echo be vr nh an sd); do
    g_energy -f et_$i.edr -o et_en_$i.xvg;
done

Так удалось посчитать потенциальную и кинетическую энергию, выбирая, значения 10 и 11 при выборе.

дальнейший анализ данных столкнулся с техническими трудностями (три блока команд далее) обойденными переходом на запуск ipython c kodomo.

In [13]:
%matplotlib inline
import matplotlib.pyplot as plt
import math
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-13-7659b6eaacf9> in <module>()
----> 1 get_ipython().magic(u'matplotlib inline')
      2 import matplotlib.pyplot as plt
      3 import math

C:\Python27\lib\site-packages\IPython\core\interactiveshell.pyc in magic(self, arg_s)
   2203         magic_name, _, magic_arg_s = arg_s.partition(' ')
   2204         magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
-> 2205         return self.run_line_magic(magic_name, magic_arg_s)
   2206 
   2207     #-------------------------------------------------------------------------

C:\Python27\lib\site-packages\IPython\core\interactiveshell.pyc in run_line_magic(self, magic_name, line)
   2124                 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
   2125             with self.builtin_trap:
-> 2126                 result = fn(*args,**kwargs)
   2127             return result
   2128 

C:\Python27\lib\site-packages\IPython\core\magics\pylab.pyc in matplotlib(self, line)

C:\Python27\lib\site-packages\IPython\core\magic.pyc in <lambda>(f, *a, **k)
    191     # but it's overkill for just that one bit of state.
    192     def magic_deco(arg):
--> 193         call = lambda f, *a, **k: f(*a, **k)
    194 
    195         if callable(arg):

C:\Python27\lib\site-packages\IPython\core\magics\pylab.pyc in matplotlib(self, line)
     78         """
     79         args = magic_arguments.parse_argstring(self.matplotlib, line)
---> 80         gui, backend = self.shell.enable_matplotlib(args.gui)
     81         self._show_matplotlib_backend(args.gui, backend)
     82 

C:\Python27\lib\site-packages\IPython\core\interactiveshell.pyc in enable_matplotlib(self, gui)
   2929         """
   2930         from IPython.core import pylabtools as pt
-> 2931         gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
   2932 
   2933         if gui != 'inline':

C:\Python27\lib\site-packages\IPython\core\pylabtools.pyc in find_gui_and_backend(gui, gui_select)
    252     """
    253 
--> 254     import matplotlib
    255 
    256     if gui and gui != 'auto':

ImportError: No module named matplotlib
In [2]:
%lsmagic
Out[2]:
Available line magics:
%alias  %alias_magic  %autocall  %automagic  %autosave  %bookmark  %cd  %clear  %cls  %colors  %config  %connect_info  %copy  %ddir  %debug  %dhist  %dirs  %doctest_mode  %echo  %ed  %edit  %env  %gui  %hist  %history  %install_default_config  %install_ext  %install_profiles  %killbgscripts  %ldir  %less  %load  %load_ext  %loadpy  %logoff  %logon  %logstart  %logstate  %logstop  %ls  %lsmagic  %macro  %magic  %matplotlib  %mkdir  %more  %notebook  %page  %pastebin  %pdb  %pdef  %pdoc  %pfile  %pinfo  %pinfo2  %popd  %pprint  %precision  %profile  %prun  %psearch  %psource  %pushd  %pwd  %pycat  %pylab  %qtconsole  %quickref  %recall  %rehashx  %reload_ext  %ren  %rep  %rerun  %reset  %reset_selective  %rmdir  %run  %save  %sc  %store  %sx  %system  %tb  %time  %timeit  %unalias  %unload_ext  %who  %who_ls  %whos  %xdel  %xmode

Available cell magics:
%%!  %%HTML  %%SVG  %%bash  %%capture  %%cmd  %%debug  %%file  %%html  %%javascript  %%latex  %%perl  %%powershell  %%prun  %%pypy  %%python  %%python2  %%python3  %%ruby  %%script  %%sh  %%svg  %%sx  %%system  %%time  %%timeit  %%writefile

Automagic is ON, % prefix IS NOT needed for line magics.
In [9]:
%%bash

Если честно, уже порядком надоело, что не работает нормально magic. Я нашел похожее обсуждение на stackoverflow, установил Anaconda и обновил полностью ipython и т.д., прописал пути в PATH - безрезультатно. По какому принципу оказалось, что %lsmagic работает, а %matplotlib нет?..
###В итоге разобраться с этой проблемой дома у меня не получилось, поэтому я разобрался, как запускать ipython notebook на kodomo.
для себя прописываю на будущее ключевые команды:
перед запуском plink необходимо написать
set PATH=C:Files (x86)-0.63-RU-14;%PATH%
теперь мы можем использовать path из любой папки, например из рабочей директории, и далее следуем по инструкциям с http://kodomo.cmm.msu.ru/~golovin/ipynb/ipymol-kodomo.html

к сожалению запуск pymol с параметром -x сильно тормозит, но с параметром -cp все проходит гладко.

In [3]:
%matplotlib inline
import matplotlib.pyplot as plt
import math

УРА! все получилось! Я смогу использовать mathplotlib c kodomo и не заморачиваться с капризным Xserver!

In [103]:
%matplotlib inline
import matplotlib.pyplot as plt
import math

def parseXVG(filename):    #будем обращаться к файлам по очереди, назовем каждый "filename" внутри этой функции
    with open(filename, 'r') as f:  # открываем на чтение созданный ранее файл
        lines = filter(lambda x: not (x.startswith('#') or x.startswith('@')), f.readlines())   
        # с помощью лямбда-функции (чтобы не засорять код) фильтруем строки, пропуская те из них, которые начинаются с '#' или '@' все, что
        # останется и есть интересующая нас информация.
        [t, V, T] = zip(*[map(float, line.split()) for line in lines])  #здесь мы обработаем все строки из lines, разбив их  
        # на значимые числа и упаковав все в список: [время (пс), потенциальная энергия (кДж/моль), кинетическая энергия (кДж/моль)].
        return [t, V, T]

def liftF(f, xs, ys):
    return f(map(f, [xs, ys]))

def log10vec(vector):
    return map(lambda x: math.log(x, 10), vector)

def showEnergy(filename, descr="", lpos="lower right"):
    [x, y, z] = parseXVG(filename)
    
    f = plt.figure(1)
    f.text(0.5, 1, descr, 
           horizontalalignment='center', verticalalignment='top')
    
    plt.subplot(141)
    plt.title("Potential energy")
    plt.scatter(x, y, c='b', alpha=0.7)
    plt.xlabel("Time, ps")
    plt.ylabel("Energy, kJ/mol")
    
    plt.subplot(142)
    plt.title("Kinetic energy")
    plt.scatter(x, z, c='r', alpha=0.7)
    plt.legend(loc=lpos)
    plt.xlabel("Time, ps")
    
    plt.subplot(143)
    plt.title("log10 potential energy")
    plt.scatter(x, log10vec(y), c='c', alpha=0.7)
    plt.legend(loc=lpos)
    plt.xlabel("Time, ps")
    plt.ylabel("log10 energy, kJ/mol")
    
    plt.subplot(144)
    plt.title("log10 kinetic energy")
    plt.scatter(x, log10vec(z), c='m', alpha=0.7)
    plt.legend(loc=lpos)
    plt.xlabel("Time, ps")
    
def showOneEnergy(listOfCoords, descr="", log=False, sparse=False, energy_h=25, energy_l=-.1): #получает список из списка времени(расположить по х) и энергий (по у).
    labels = ('be', 'vr', 'nh', 'an', 'sd')
    colours = ('r', 'g', 'b', 'y', 'c')
    f = plt.figure(1)
    f.text(0.5, 1, descr, 
       horizontalalignment='center', verticalalignment='top')
    
    for i, pairedList in enumerate(listOfCoords):      #работаем с списком из x (время) и y (энергия)
        (x, y) = pairedList
        plt.subplot(231+(i if i<4 else 5) if sparse else 151+i)
        plt.title(labels[i])
        plt.ylim(energy_l, energy_h)   # тут пришлось покопаться и выбрать границы энергий для изображения всех графиков.
        #Ясно, что оптимальным образом показать все не получится, поэтому ниже генерим целую серию рисунков
        if log:
            plt.scatter(x, log10vec(y), c=colours[i], alpha=0.7)
            if i==0: plt.ylabel("log10 Energy")
        else:
            plt.scatter(x, y, c=colours[i], alpha=0.7)
            if i==0: plt.ylabel("Energy, kJ/mol")
        plt.xlabel("Time, ps")
    plt.show()


filenames = ('et_en_be.xvg', 'et_en_vr.xvg', 'et_en_nh.xvg', 'et_en_an.xvg', 'et_en_sd.xvg')
coords = map(parseXVG, filenames)
getKinetic   = lambda k: (k[0], k[2])   #обращается к списку и вытаскивает из него список с только первым и третьим элементами
getPotential = lambda k: (k[0], k[1])   #аналогично, но берет 1-й и 2-й элементы списка

Изобразим кинетические энергии, полученные разными методами. К сожалению, детальные различия показать не легко, потому придется генерировать серию графиков, соответствующих разным уровням энергии (все они в масштабе от нуля до i, где i от большего к меньшему: 5e17, 5e12, 5e10, 5e8, 5e6).

In [102]:
from matplotlib import pylab
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getKinetic, coords), descr="Kinetic energy, kJ/mol", energy_h=.5*1e18, energy_l=-.1*1e17)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getKinetic, coords), descr="Kinetic energy, kJ/mol", energy_h=.5*1e13, energy_l=-.1*1e12)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getKinetic, coords), descr="Kinetic energy, kJ/mol", energy_h=.5*1e11, energy_l=-.1*1e10)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getKinetic, coords), descr="Kinetic energy, kJ/mol", energy_h=.5*1e9, energy_l=-.1*1e8)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getKinetic, coords), descr="Kinetic energy, kJ/mol", energy_h=.5*1e7, energy_l=-.1*1e6)

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

In [105]:
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getKinetic, coords), descr="Kinetic energy, kJ/mol", log=True)

аналогичные графики построим для потенциальной энергии, после чего сделаем выводы.

In [106]:
from matplotlib import pylab
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getPotential, coords), descr="Potential energy, kJ/mol", energy_h=.7*1e7, energy_l=-.1*1e6)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getPotential, coords), descr="Potential energy, kJ/mol", energy_h=.5*1e4, energy_l=-.1*1e3)
In [107]:
pylab.rcParams['figure.figsize'] = (13.0, 5.0)
showOneEnergy(map(getPotential, coords), descr="Potential energy, kJ/mol", log=True, energy_h=8, energy_l=2)

Как же распределяется потенциальная энергия, какие значения чаще всего принимает?

In [141]:
def distPlotxxx(coords, type="scatter", cc_l=0, cc_h=3.6):
    f = plt.figure(1)
    f.text(0.5, 1, "Potential energy", 
       horizontalalignment='center', verticalalignment='top')

    for i, pairedList in enumerate(coords):
        (x, y) = pairedList
        plt.subplot(151+i)
        plt.title(labels[i])
        if type=="hist":
            plt.hist(y, color=colours[i], alpha=0.7)
        else:
            plt.scatter(x, y, color=colours[i], alpha=0.7)
        plt.xlabel("Energy, kj/mol")
        if i==0: plt.ylabel("Number of counts")
        plt.ylim(cc_l, cc_h)
In [142]:
distCoords = map(parseXVG2d, filenames)
distPlotxxx(map(getPotential, coords), "hist", cc_l=0, cc_h=1000)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)

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

Ранее было сделано предсказание о "замороженности" молекулы этана при использованном методе Нуза-Хувера.

Теперь мы видим, что в данном случае кинетическая энергия практически постоянно равна приблизительно 1е8. На всякий случай проверил по файлу .xml, действительно, нигде ничего не перепутано, кинетическая энергия почти всегда более 100 млн кДж/моль. Насколько можно судить по графикам, это больше, чем медианное значение для других методов, но в отличие от Нуза-Хувера, значения кинетических энергий в них гораздо более разнообразны. Что касается потенциальной энергии молекулы в методе Нуза-Хувера, то она самая низкая среди рассмотренных методов и падает в самом начале измерений, после чего практически постоянна.

Вторым по аномальности можно считать метод Андерсена, где потенциальные энергии менее разнообразны, чем в трех оставшихся методах, а кинетическая энергия как бы скачет с уровня, аналогичного Нузу-Хуверу на более высокие уровни в три скачка, редко отклоняясь от принятого после перескока значения энергии.

К сожалению метод Берендсена считался очень недолго, после чего прекращал счет, потому мы имеем на порядок меньшее количество точек, но по полученным данным можно сказать, что в данном методе кинетические энергии молекулы в среднем меньше, чем в методе Velocity rescale и меньше, чем в методе стохастической молекулярной динамики.

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

Теперь рассмотрим распределение длины связи С-С за время моделирования. Сначала создадим индекс файл с одной связью.

In [12]:
%%bash
echo -e "[ b ]\n1 2" > b.ndx

запустим утилиту по анализу связей g_bond.

In [12]:
%%bash
for i in $(echo be vr nh an sd); do
    g_bond -f et_$i.trr -s et_$i.tpr -o bond_$i.xvg -d distance_$i.xvg -n b.ndx &> /dev/null;
done

запустим утилиту по анализу связей g_bond.

In [129]:
labels = ('be', 'vr', 'nh', 'an', 'sd')
filenames = map(lambda x: 'distance_%s.xvg' % x, labels)
colours = ('r', 'g', 'b', 'y', 'c')


def parseXVG2d(filename):  # все аналогично прежней функции, только входной файл содержит не время и два типа энергии, а расстояние С-С.
    with open(filename, 'r') as f:
        lines = filter(lambda x: not (x.startswith('#') or x.startswith('@')), f.readlines())
        [t, d] = zip(*[map(float, line.split()) for line in lines])
        return [t, d]      # [time (ps), distance (nm)]

def distPlot(coords, type="scatter", cc_l=0, cc_h=3.6):
    f = plt.figure(1)
    f.text(0.5, 1, "Distance", 
       horizontalalignment='center', verticalalignment='top')

    for i, pairedList in enumerate(coords):
        (x, y) = pairedList
        plt.subplot(151+i)
        plt.title(labels[i])
        if type=="hist":
            plt.hist(y, color=colours[i], alpha=0.7)
        else:
            plt.scatter(x, y, color=colours[i], alpha=0.7)
        plt.xlabel("Distance, nm")
        if i==0: plt.ylabel("Number of counts")
        plt.ylim(cc_l, cc_h)
In [130]:
distCoords = map(parseXVG2d, filenames)
distPlot(distCoords, "hist", cc_l=0, cc_h=250)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)

напоследок сменим масштаб оси, чтобы лучше изучить распределение.

In [131]:
distCoords = map(parseXVG2d, filenames)
distPlot(distCoords, "hist", cc_l=0, cc_h=35)
pylab.rcParams['figure.figsize'] = (13.0, 5.0)

Что тут можно сказать. Странно, что связь СС совсем не колебалась в методе nh, странно, что длина связи в методе be изменяется раз в секунду.

Учитывая форму распределения Больцмана и распределения энергий в молекулах, посчитанных разными методами, очень трудно выбрать между методом vr и sd, но по совокупности признаков, как например, бОльшее разнообразие энергий и при этом бОльшая схожесть с распределением Больцмана, я бы предпочел указать метод стохастической молекулярной динамики, как способный наиболее реалистично поддерживать температуру в системе.

в финале конвертируем файл ipython в html:

ipython nbconvert --to html pract4.ipynb

In [145]:
%%bash
ipython nbconvert --to html pract7.ipynb