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

0. Импорты и макрос

In [80]:
from textwrap import dedent
from fabric import Connection
import keyring
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
import seaborn as sns
from jupyterthemes import jtplot
import shutil, time, os
from IPython.display import Image
from io import StringIO

jtplot.style(figsize=(17, 11), fscale=1.5)

THERMOSTATS = ['an', 'nh', 'vr', 'be', 'sd']
NAMES = ['Andersen', 'Nose-Hoover', 'V-rescale', 'Berendsen', 'Stochastic']

Макрос для исполнения команд на kodomo:

In [3]:
def kodomo_do(command):
    with Connection('kodomo.fbb.msu.ru', user='aidar', 
                    connect_kwargs={'password': keyring.get_password('kodomo', 'aidar')}) as kodomo:
        with kodomo.cd('~/tmp/term8'):
            result = kodomo.run(command, hide='both')
            return result.stdout

1. Файл с координатами

In [20]:
_ = kodomo_do('wget -q http://kodomo.fbb.msu.ru/FBB/year_08/term6/etane.gro -O et.gro')

2. Файл с топологией

Нам была дана заготовка для файла топологии, но ее нужно было подкорректировать. Сначала я добавил недостающие связи, углы и торсионные углы (для последних я также поменял тип на 3). Затем в файле с типами атомов (/usr/share/gromacs/top/oplsaa.ff/atomtypes.atp) я нашел следующие строчки:

opls_135   12.01100  ; alkane CH3
opls_140    1.00800  ; alkane H.
Эти типы я и вставил в наш файл с топологией (и поправил массы углерода). И наконец, я поправил заряды согласно файлу с нековалентными взаимодействиями (/usr/share/gromacs/top/oplsaa.ff/ffnonbonded.itp).
Итоговый файл с топологией

3. Конфигурационные файлы

In [21]:
for therm in THERMOSTATS:
    kodomo_do(f'wget http://kodomo.fbb.msu.ru/FBB/year_08/term6/{therm}.mdp')

4. Входные файлы для mdrun

In [18]:
for therm in THERMOSTATS:
    kodomo_do(f'grompp -f {therm}.mdp -c et.gro -p et.top -o et_{therm}.tpr')

5. Запускаем mdrun

In [19]:
for therm in THERMOSTATS:
    kodomo_do(f'mdrun -deffnm et_{therm} -nt 1')

6. Анализ pdb

1) Переведем траектории в pdb

In [3]:
for therm in THERMOSTATS:
    kodomo_do(f'echo 0 | trjconv -f et_{therm}.trr -s et_{therm}.tpr -o et_{therm}.pdb')

2) Скачаем полученные pdb

In [9]:
for therm in THERMOSTATS:
    with open(f'{therm}.pdb', 'w') as f:
        f.write(kodomo_do(f'cat et_{therm}.pdb'))

3) Посмотрим на них в пимоле

In [7]:
import pymol
pymol.finish_launching(['pymol'])
from pymol import cmd
In [19]:
for therm in THERMOSTATS:
    cmd.reinitialize()
    cmd.set('ray_trace_mode', 3)
    cmd.set('ray_trace_gain', 0)
    cmd.set('antialias', 2)
    cmd.set('sphere_scale', 0.2)
    cmd.set('stick_radius', 0.15)
    
    cmd.load(f'{therm}.pdb')
    cmd.orient()
    cmd.move('z', 3)
    cmd.show('spheres')
    
    dirpath = 'images/'
    if os.path.exists(dirpath):
        shutil.rmtree(dirpath)
        time.sleep(0.1)
    os.mkdir(dirpath)

    cmd.mpng(dirpath, mode=2, width=1920//3, height=1080//3)

    while not os.path.exists(f'{dirpath}0251.png'):
        time.sleep(0.1)

    with imageio.get_writer(f'{therm}.gif', mode='I', fps=30) as writer:
        for x in range(0, 251):
            filename = os.path.join(dirpath, f'{x+1:04}.png')
            image = imageio.imread(filename)
            writer.append_data(image)
In [4]:
for therm, name in zip(THERMOSTATS, NAMES):
    print(f'{name} thermostat:')
    display(Image(f'{therm}.gif', format='png'))
Andersen thermostat:
Nose-Hoover thermostat:
V-rescale thermostat:
Berendsen thermostat:
Stochastic thermostat:

Из траекторий видно, что с термостатом Андерсена молекула почти не двигается, а со стохастическим ее наоборот очень сильно колбасит. Наверно, ни то, ни другое не правильно и правда где-то между ними (V-rescale например).

7. Анализ энергий

1) Вытащим энергии из выдачи mdrun

In [5]:
for therm in THERMOSTATS:
    kodomo_do(f'echo 10 11 | g_energy -f et_{therm}.edr -o et_{therm}_en.xvg -xvg none')

2) Построим графики

In [49]:
for therm, name in zip(THERMOSTATS, NAMES):
    time, pot, kin = np.loadtxt(StringIO(kodomo_do(f'cat et_{therm}_en.xvg'))).T
    time /= 25
    plt.figure()
    sns.lineplot(time, pot + kin)
    sns.lineplot(time, kin, alpha=0.7)
    sns.lineplot(time, pot, alpha=0.7)
    plt.title(f'{name} thermostat')
    plt.ylabel('Energy (kJ/mol)')
    plt.xlabel('Time (ps)')
    plt.legend(['Total energy', 'Kinetic energy', 'Potential energy'])

Как видно из графиков, термостаты Андерсена и Берендсена поддерживают полную энергию молекулы на одном и том же уровне; энергии у них колеблются несильно и достаточно периодично; потенциальная энергия определенно выше кинетической. У скалирования скоростей и стохастического термостата кинетическая и потенциальная энергия колеблются примерно на одном уровне, а у термостата Нуза-Хувера кинетическая энгергия заметно выше. Также у него и разброс энергий самый большой (полная почти дошла до 1400), в то время как у Андерсена она чрезвычайно мала (держится на 10).

8. Распределение длины связи

In [75]:
for therm in THERMOSTATS:
    kodomo_do(f'g_bond -f et_{therm}.trr -s et_{therm}.tpr -d bond_{therm}.xvg -n b.ndx -xvg none')
In [79]:
for therm, name in zip(THERMOSTATS, NAMES):
    time, blens = np.loadtxt(StringIO(kodomo_do(f'cat bond_{therm}.xvg'))).T
    plt.figure()
    sns.distplot(blens)
    plt.xlim(0.1522 - 0.02, 0.1522 + 0.02)
    plt.title(f'{name} thermostat')
    plt.ylabel('Probability density')
    plt.xlabel('Bond length (nm)')
    lb, ub = plt.xlim()
    plt.xticks(np.linspace(lb, ub, 9))
    plt.tick_params(labelleft=False)

Видно, что у всех термостатов KDE близок по форме к нормальному распределению, причем среднее значение угла у всех практически одинаково -- чуть больше 0.1522 (в поле вроде такая длина связи). А вот дисперсия не такая одинаковая: у Берендсена она самая маленькая, а у скалирования скоростей самая большая.

9. Распределение Больцмана

In [86]:
for therm, name in zip(THERMOSTATS, NAMES):
    time, pot, kin = np.loadtxt(StringIO(kodomo_do(f'cat et_{therm}_en.xvg'))).T
    plt.figure()
    sns.distplot(pot + kin)
    plt.title(f'{name} thermostat')
    plt.ylabel('Probability density')
    plt.xlabel('Total energy (kJ/mol)')
    plt.tick_params(labelleft=False)

Из этих графиков видно, что только у Нуза-Хувера и Берендсена распределения не нормальные, а экспоненциальные (как и у Больцмана), однако у Берендсена энергии "начинаются" не с нуля, а с 60, и заканчиваются примерно там же (где-то на 63). Также можно заметить, что термостат Андерсона практически не меняет энергию и держит ее на значительно меньшем уровне (в среднем 9.8 против 60 у остальных). Очевидно, что Нуз-Хувер наиболее реалистичен.

10. Быстродействие

In [97]:
times = np.zeros((len(THERMOSTATS), 10), dtype=np.float32)
for x, therm in enumerate(THERMOSTATS):
    for y in range(10):
        ns_day = kodomo_do(f'mdrun -deffnm et_{therm} -nt 1 2>&1'
                           f'| grep Performance | awk \'{{print $4}}\'')
        times[x, y] = float(ns_day)
In [111]:
sns.barplot(data=list(times))
plt.title('Thermostat performance')
plt.ylabel('Performance (ns/day)')
plt.xlabel('Thermostat')
_ = plt.xticks(range(5), labels=NAMES)

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