Bohrium
robot
新建

空间站广场

论文
Notebooks
比赛
课程
Apps
我的主页
我的Notebooks
我的论文库
我的足迹

我的工作空间

任务
节点
文件
数据集
镜像
项目
数据库
公开
Integrators
notebook
AI4S
Deep Learning
python
Molecular Dynamics
notebookAI4SDeep LearningpythonMolecular Dynamics
hmj
Linfeng Zhang
发布于 2023-09-24
推荐镜像 :Third-party software:dmff:0.2.0-notebook
推荐机型 :c32_m64_cpu
赞 2
2
Integrators
1. 导入依赖
2. ODE Integrators
2.1 Euler's method
2.2 verlet method and velocity-verlet method
2.3 Numerical Example
2. Simple SDE Integrators
2.1 Euler-Maruyama Scheme
2.2 Milstein Scheme
3. Langevin Dynamics & Its numerical Integrators
Exercise: Can you try to modify the code above and write down the OABAO integrator? Then, using the code of simulating the one-dimensional harmonic oscillator provided below to test your integrator by finding an invariant/preserved quantity and monitoring whether it is invariant/preserved during the simulation.
4. Overdamped Langevin Dynamics
5. Nose-Hoover Dynamics
6. Numerical Example (1D Harmonic Oscillator)
7. Numerical Example (2D Lennard-Jones Fluid)

Integrators

本文档介绍了关于ordinary differential equations(ODEs) 和stochastic differential equations(SDEs) 常用的integrators。本文档借鉴和照搬了去年的demo list里面的许多内容,这里特别感谢去年的作者们! 本文档主要以英文介绍为主(因为作者不太确定很多英文的名词的中文对应),希望各位能提供宝贵意见。

作者:花勐健以及去年demo list的作者们

审核:

1. 导入依赖

本文档使用的镜像为dmff_0.2.0-notebook。首先我们拉取DMFF最新版本代码,并安装一些必要的依赖。

可以通过import dmff的命令验证dmff已安装成功,在CPU节点上可能会看到一些warning信息,这是正常的。

代码
文本
[1]
! if [ ! -e DMFF ];then git clone https://gitee.com/deepmodeling/DMFF.git;fi
! git config --global --add safe.directory `pwd`/DMFF
! cd DMFF && git checkout devel
! pip install matplotlib
#! pip install setuptools_scm mdtraj
#! cd DMFF && python3 setup.py install
import sys
import os
import shutil
sys.path.insert (0, os.path.join(os.getcwd(),"DMFF"))
sys.path.append (os.path.join(os.getcwd(),"DMFF", "examples", "tutorial_utils"))
if os.path.isdir("data"):
shutil.rmtree("data")
shutil.copytree(os.path.join(os.getcwd(),"DMFF", "examples", "tutorial_utils", "data"), "data")
import dmff
import numpy as np
import matplotlib.pyplot as plt
import jax
import jax.numpy as jnp
from tqdm import tqdm, trange
import openmm.app as app
import openmm.unit as unit
import tutorial_utils as utils
from tutorial_utils import State, BaseIntegrator, XYZWriter, init_state_from_PDB
dmff.settings.update_jax_precision("float")
Already on 'devel'
Your branch is up to date with 'origin/devel'.
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Requirement already satisfied: matplotlib in /opt/mamba/lib/python3.10/site-packages (3.6.2)
Requirement already satisfied: contourpy>=1.0.1 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (1.0.6)
Requirement already satisfied: fonttools>=4.22.0 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (4.38.0)
Requirement already satisfied: cycler>=0.10 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (0.11.0)
Requirement already satisfied: pyparsing>=2.2.1 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (3.0.9)
Requirement already satisfied: numpy>=1.19 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (1.23.4)
Requirement already satisfied: pillow>=6.2.0 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (9.3.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (1.4.4)
Requirement already satisfied: python-dateutil>=2.7 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (2.8.2)
Requirement already satisfied: packaging>=20.0 in /opt/mamba/lib/python3.10/site-packages (from matplotlib) (21.3)
Requirement already satisfied: six>=1.5 in /opt/mamba/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
2023-09-27 21:26:45.061163: W external/org_tensorflow/tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: /usr/lib/x86_64-linux-gnu/libcuda.so.1: file too short; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2023-09-27 21:26:45.061950: W external/org_tensorflow/tensorflow/stream_executor/cuda/cuda_driver.cc:263] failed call to cuInit: UNKNOWN ERROR (303)
WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
代码
文本

2. ODE Integrators

2.1 Euler's method

Many commonly seen deterministic systems are governed by Newton's third law. An object's velocity and position as functions of time can be described by the following ordinary differential equations: where denotes the position of the object, is its time-dependent velocity, and is the acceleration calculated from Newton's third law with a time-dependent force and a mass .

Suppose we have a grid in time , where we denote the mesh width/time step size by . The Euler's method is the simplest way to integrate such ODE systems and the discretization is as follows:

where are the position and velocity at time .

With a Taylor expansion, we can show that the numerical error brought by this way of discretization in time is .

Moreover, it is worth noting that the Euler's method, and more generally, all explicit ODE integrators suffer from the problem of numerical stability. Here's a simple example that demonstrate the issue of numerical stability. Suppose we have an ODE system:

where is a constant. The analytical solution of this ODE is , where is the initial condition. The numerical solution given by the Euler's method is . It follows that . Since , . What if is so large that ? It is obvious that would blow up in this case. The implicit Euler's method would help avoid this issue. Let's take the same ODE as the example. The discretization of the implicit Euler's method is also very simple:

We don't know the right-hand side but we can work out an equivalent expression:

We observe that the numerical stability is no longer a problem for the implicit Euler's method because as long as .

In the block below, we implement the explicit Euler's method for general 1D systems governed by Newton's third law.

代码
文本
[2]
class EulerIntegrator(utils.BaseIntegrator):
def update_state(self, pos_0, vel_0, box_0, mas, ene_0, frc_0, engrad=None):
acc = frc_0 / mas
pos_1 = pos_0 + vel_0 * self.dt
vel_1 = vel_0 + acc * self.dt
ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
return pos_1, vel_1, box_0, mas, ene_1, frc_1

代码
文本

2.2 verlet method and velocity-verlet method

Since the Euler's method is only first-order accurate (i.e. the error ~ ), we often want to use a high-order accuracy method such that we can use a larger time step size without loss of accuracy. There are many methods of such (e.g. Runge-Kutta methods, multistep methods). If the readers want to know more about the families of high-order methods, we recommend this book: https://faculty.washington.edu/rjl/fdmbook/. Here, We introduce the verlet and the velocity-verlet method, which are more relevant to MD problems. Now, we consider our system of ODEs governed by Newton's third law:

For the time step , the Taylor expansion of is

From our ODEs, we know that , , and . Hence,

Similarly, if we expand with Taylor series, we would get

Therefore, adding these two Taylor expansions yields

and this gives us a numerical scheme, which people often call the verlet method,

which is third-order accurate. Note that the velocity-verlet method does not require we update the velocity at every time step and we do not need to keep track of . We can compute directly as long as we have access to and we can evaluate .

As opposed to the verlet method, we also have the velocity-verlet method, which requires us to update the velocity at every step. The scheme is as follows:

The velocity is updated via a central difference scheme with error . So, this error will be become when it comes to the update of the position. Therefore, the position is updated with a local truncation error of .

Both the verlet and the velocity-verlet can achieve better numerical accuracy than the simple explicit and implicit Euler methods. But we should also note here that the time step size for the verlet and the velocity-verlet methods is also restricted by the numerical stability but they both provide much better numerical stability than the explicit Euler's method. So, in practice, they are the methods that people often use.

代码
文本
[3]
class VerletIntegrator(BaseIntegrator):

def __init__(self, timestep=1.0e-3):
self.dt = timestep

def update_state(self, pos_0, pos_1, box_0, mas, ene_1, frc_1, engrad=None):
acc_1 = frc_1/mas
pos_2 = 2*pos_1 - pos_0 + acc_1*(self.dt**2)
ene_2, grd_2 = engrad(pos_2, box_0)
frc_2 = - grd_2
return pos_2,ene_2,frc_2
def update_state_velocity_verlet(self, pos_0, vel_0, box_0, mas, ene_0, frc_0, engrad=None):
acc_0 = frc_0 / mas
pos_1 = pos_0 + vel_0 * self.dt + 0.5 * acc_0 * self.dt ** 2
ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
acc_1 = frc_1 / mas
vel_1 = vel_0 + 0.5 * (acc_0 + acc_1) * self.dt
return pos_1, vel_1, box_0, mas, ene_1, frc_1
def step(self, pos_0, state,engrad = None):
pos = state.getPositions()
vel = state.getVelocities()
box = state.getCellVector()
mas = state.getMasses()
frc = state.getForces()
ener = state.getPotentialEnergy()

pos_2,ene_2,frc_2 = self.update_state(pos_0, pos, box, mas, ener, frc, engrad=engrad)

state_new = State(positions=pos_2,
velocities=vel,
cell=box,
energy=ene_2,
forces=frc_2,
masses=mas)
return state_new
def first_step(self, state, engrad=None):
pos = state.getPositions()
vel = state.getVelocities()
box = state.getCellVector()
mas = state.getMasses()
frc = state.getForces()
ener = state.getPotentialEnergy()

pos_1, vel_1, box_1, mas_1, ene_1, frc_1 = self.update_state_velocity_verlet(
pos, vel, box, mas, ener, frc, engrad=engrad)

state_new = State(positions=pos_1,
velocities=vel_1,
cell=box_1,
energy=ene_1,
forces=frc_1,
masses=mas)
return state_new
class VelocityVerletIntegrator(BaseIntegrator): # Credit to Xinyan Wang
def update_state(self, pos_0, vel_0, box_0, mas, ene_0, frc_0, engrad=None):
acc_0 = frc_0 / mas
pos_1 = pos_0 + vel_0 * self.dt + 0.5 * acc_0 * self.dt ** 2
ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
acc_1 = frc_1 / mas
vel_1 = vel_0 + 0.5 * (acc_0 + acc_1) * self.dt
return pos_1, vel_1, box_0, mas, ene_1, frc_1
代码
文本

2.3 Numerical Example

In the below blocks, we use a simple 1D harmonic oscillator example to illustrate the performance of the methods we introduced above. The energy of the system we are considering is obviously conserved but it is not conserved in the numerical solutions because of discretization errors. Here, we do a convergence study on this. Let our time step sizes be and the potential energy we get from numerical solver integrating till with time step size be . The potential energy only depends on the position , so we use it instead of the total energy to measure the convergence.

Then, the relative error is defined by and a plot of which can indicate the speed of convergence of the numerical solver. We make a plot of such for the three integrators we introduced above for the 1D harmonic oscillator and we see that the verlet method and the velocity-verlet method achieve almost the same accuracy and they are all much better than the forward Euler method.

Here, we should emphasize that the actual error in the numerical solution at the final time (i.e. the global error) is often different in order from the local truncation error, which is what we analyzed from Taylor expansions. In practice, both the verlet and the velocity-verlet methods have a global error of . This difference is due to the fact that we have assumed in our Taylor expansions that and , which only hold when (i.e. initial conditions). The error accumulates in time and this makes these relations no longer hold. This also explains why the verlet method and the velocity-verlet method achieve almost the same accuracy in the end whereas our analysis shows that the verlet method has a local truncation error of approximately in the position whereas the velocity-verlet should have a local truncation error of approximately in the position.

When we perform the analysis, we consider as a function of , but in this example, we have . With this difference, the third-order term in the Taylor expansion is now

代码
文本
[4]
energies_vv = []
time_step_size = 1e-3

omm_pdb, engrad = utils.create1DHarmonicOscillator()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
state.velocities = jax.numpy.array([[-0.79840655, 1.3889997, -0.25326978]])
total_energy = state.getTotalEnergy()

integ = VelocityVerletIntegrator(time_step_size)
steps = int(1/time_step_size)
for nstep in trange(steps):
state = integ.step(state, engrad)
energies_vv.append([state.getTotalEnergy()])
energies_vv = np.array(energies_vv)
plt.plot(energies_vv - total_energy, label="Velocity-Verlet",linewidth = 2)

energies_vv = []
omm_pdb, engrad = utils.create1DHarmonicOscillator()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
state.velocities = jax.numpy.array([[-0.79840655, 1.3889997, -0.25326978]])

integ = EulerIntegrator(time_step_size)
steps = int(1/time_step_size)

for nstep in trange(steps):
state = integ.step(state, engrad)
energies_vv.append([state.getTotalEnergy()])
energies_vv = np.array(energies_vv)
plt.plot(energies_vv - total_energy, label="ForwardEuler",linewidth = 2)

plt.xlabel("Time Steps",fontsize = 15)
plt.ylabel('Total Energy Drift',fontsize = 15)

plt.title("The total energy change in the simulation",fontsize = 15)

plt.legend()
plt.show()
代码
文本
[5]
energies_vv = []
time_grid = (1/8)/(2**(np.arange(5)))

for step_size in range(6):
omm_pdb, engrad = utils.create1DHarmonicOscillator()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
state.velocities = jax.numpy.array([[-0.79840655, 1.3889997, -0.25326978]])
time_step_size = 1/4/(2**step_size)
integ = VelocityVerletIntegrator(time_step_size)
steps = int(1/time_step_size)
for nstep in trange(steps):
state = integ.step(state, engrad)
energies_vv.append([state.getPotentialEnergy()])
energies_vv = np.array(energies_vv)
plt.semilogy(time_grid,np.abs(energies_vv[1:] - energies_vv[:-1]), label="Velocity_Verlet",linestyle = "--", marker='o',linewidth = 2)

energies_v = []

for step_size in range(6):
omm_pdb, engrad = utils.create1DHarmonicOscillator()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
state.velocities = jax.numpy.array([[-0.79840655, 1.3889997, -0.25326978]])
time_step_size = 1/4/(2**step_size)
integ = VerletIntegrator(time_step_size)
steps = int(1/time_step_size)
pos_0 = state.getPositions()
state = integ.first_step(state,engrad)
for nstep in trange(steps-1):
state_0 = state
state = integ.step(pos_0, state, engrad)
pos_0 = state_0.getPositions()
energies_v.append([state.getPotentialEnergy()])
energies_v = np.array(energies_v)
plt.semilogy(time_grid,np.abs(energies_v[1:] - energies_v[:-1]), label="Verlet",linestyle = "--", marker='o',linewidth = 2)

energies_e = []
for step_size in range(6):
omm_pdb, engrad = utils.create1DHarmonicOscillator()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
state.velocities = jax.numpy.array([[-0.79840655, 1.3889997, -0.25326978]])
time_step_size = 1/4/(2**step_size)
integ = EulerIntegrator(time_step_size)
steps = int(1/time_step_size)
for nstep in trange(steps):
state = integ.step(state, engrad)
energies_e.append([state.getPotentialEnergy()])
energies_e = np.array(energies_e)

plt.semilogy(time_grid,np.abs(energies_e[1:] - energies_e[:-1]), label="Euler",linestyle = "--", marker='o',linewidth = 2)
plt.gca().invert_xaxis()
plt.xscale('log', base=2)

plt.xlabel("Time step size",fontsize = 15)
plt.ylabel(r'$|E_{n} - E_{n-1}|$',fontsize = 15)

plt.title("Relative error in the potential energy",fontsize = 15)

plt.legend()
plt.show()
代码
文本

2. Simple SDE Integrators

We will put more emphasis on the following sections on SDE integrators rather than the ODE integrators because we more often deal with SDEs instead of ODEs in MD simulations. Suppose we have an one-dimensional SDE as follows:

where is often called the drift term, is often called the diffusion term, and denotes a standard Brownian motion/Wiener measure.

We often write stochastic processes as the form of SDEs but they are only defined as the short-hand for the integral equation

The integral is defined as the Ito integral. Unlike ODEs, the SDE themselves do not have a rigorous mathematical definition because the Brownian motion is almost surely nowhere differentiable. Therefore, is undefined as a mathematical object.

Now, we are going to introduce several SDE integrators, including the Euler-Maruyama scheme, which is the counterpart of the Euler method we introduced as an ODE integrator. We will also introduce the Milstein scheme, which is less frequently used than the Euler-Maruyama scheme.

2.1 Euler-Maruyama Scheme

The Euler-Maruyama scheme is very simple. Suppose we know at step n (i.e. time ), which we denote by , then

where denotes the time step size and is a sample from the Gaussian distribution with mean and variance . This can be derived from the stochastic Ito-Taylor expansion.

2.2 Milstein Scheme

Using the notation in the previous section, we state the Milstein scheme, which is less stable but more accurate than the Euler-Maruyama scheme, for the time-homogeneous SDEs (i.e. only depend on )as follows:

where denotes the time step size and is a sample from the Gaussian distribution with mean and variance . Notice that the first two terms on the right-hand side coincide with the Euler-Maruyama scheme. Here the last term on the right-hand side is a correction term, which can be derived from the stochastic Ito-Taylor expansion.

代码
文本

3. Langevin Dynamics & Its numerical Integrators

We consider the Hamiltonian system governed by the following equations:

where the bold characters are vector-valued functions. In the above equations, denote the position, momentum, and the potential energy of a particle. denotes the temperature, denotes the Boltzmann constant, and denotes the friction coefficient.

Question: by definition, what is the Hamiltonian of the system?

Answer:

We can rewrite this system of equations as

where we let to simplify the notations.

We label the three terms on the right-hand side as , , and . Suppose we have the following three equations:

From time step to , to the best of our knowledge, these three equations can be solved for one step using the following update:

where is a Gaussian vector with mean and covariance . Now, we have splitted the Langevin equations into three pieces and we know how to solve each one of them.

Since the Langevin equations can be written as

we then know how to solve the Langevin equations with the above solutions to the three splitted equations and there are many numerical schemes we can come up with.

For example, we can first solve A for an half time step, then solve B with obtained from solving A, then solve A again with we get from solving B, and finally solve O with . This method is short-handed as ABAO. Similarly, one can invent many schemes of such, such as BABO, ABOBA, BAOAB. Writing out these schemes (e.g. BABO, ABOBA, BAOAB) will leave as an exercise to the readers.

As a side note, ABOBA and BAOAB are often referred to as the Position-Verlet method and the Velocity-Verlet method and they perserve the variance of position.

代码
文本
[6]
class BAOABLangevinIntegrator(utils.BaseIntegrator): # credit to Xinyan Wang
def __init__(self, temperature=298.15, gamma=5.0, timestep=1.0e-3, removeCMMotion=False):
self.dt = timestep
self.gamma = gamma
self.temperature = temperature
self.removeCMMotion = removeCMMotion
self.vscale = jnp.exp(- self.gamma * self.dt)
kbT = 1.380649 * 6.02214076 * 1e-3 * self.temperature
self.noisescale = jnp.sqrt(kbT * (1. - self.vscale * self.vscale))
def update_state(self, pos_0, vel_0, box_0, mas, ene_0, frc_0, engrad=None):
# B
vel_1 = vel_0 + frc_0 / mas * 0.5 * self.dt
# A
pos_1 = pos_0 + vel_1 * 0.5 * self.dt
# O
vel_1 = self.vscale * vel_1 + self.noisescale / jnp.sqrt(mas) * jnp.array(np.random.normal(size=vel_0.shape))
# A
pos_1 = pos_1 + vel_1 * 0.5 * self.dt
# B
ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
vel_1 = vel_1 + frc_1 / mas * 0.5 * self.dt
if self.removeCMMotion:
vel_1 -= vel_1.mean(axis=0)
return pos_1, vel_1, box_0, mas, ene_1, frc_1
class ABOBALangevinIntegrator(utils.BaseIntegrator):
def __init__(self, temperature=298.15, gamma=5.0, timestep=1.0e-3):
self.dt = timestep
self.gamma = gamma
self.temperature = temperature
self.vscale = jnp.exp(- self.gamma * self.dt)
kbT = 1.380649 * 6.02214076 * 1e-3 * self.temperature
self.noisescale = jnp.sqrt(kbT * (1. - self.vscale * self.vscale))
def update_state(self, pos_0, vel_0, box_0, mas, ene_0, frc_0, engrad=None):
# A
pos_1 = pos_0 + vel_0 * 0.5 * self.dt
# B
ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
vel_1 = vel_0 + frc_1 / mas * 0.5 * self.dt
# O
vel_1 = self.vscale * vel_1 + self.noisescale / jnp.sqrt(mas) * jnp.array(np.random.normal(size=vel_0.shape))
# B
vel_1 = vel_1 + frc_1 / mas * 0.5 * self.dt
# A
pos_1 = pos_1 + vel_1 * 0.5 * self.dt
if self.removeCMMotion:
vel_1 -= vel_1.mean(axis=0)
return pos_1, vel_1, box_0, mas, ene_1, frc_1
代码
文本

Exercise: Can you try to modify the code above and write down the OABAO integrator? Then, using the code of simulating the one-dimensional harmonic oscillator provided below to test your integrator by finding an invariant/preserved quantity and monitoring whether it is invariant/preserved during the simulation.

代码
文本
代码
文本

4. Overdamped Langevin Dynamics

An interesting special case of Langevin dynamics is obtained by considering the large limit, which is often referred to as the overdamped limit. The resulting equation is called the overdamped Langevin dynamics. In the limit of large , the change in damps very quickly and we can assume that the acceleration is negligible, which yields the following system of equations:

From the second equation, we obtain that

We combine it with the first equation and we get

Eliminating and by rescaling the equation yields

This is also sometimes referred to as Brownian dynamics. The overdamped Langevin dynamics have an equilibriunm Gibbs distribution , which is also referred to as the canonical ensemble, and people often integrate the above SDE to sample from . This sampling technique is called Langevin dynamics in the machine learning community and it is widely used to sample from energy-based models and diffusion models. Another less commonly used name for this method is called the stochastic gradient descent algorithm.

代码
文本
[7]
class OverdampedLangevinIntegrator(utils.BaseIntegrator):
def __init__(self, temperature=298.15, timestep=1.0e-3, removeCMMotion=True):
self.dt = timestep
self.temperature = temperature
kbT = 1.380649 * 6.02214076 * 1e-3 * self.temperature
self.noisescale = jnp.sqrt(kbT * 2 * self.dt)
def update_state(self, pos_0, vel_0, box_0, mas, ene_0, frc_0, engrad=None):
vel_0 = jnp.zeros_like(vel_0)
# Euler-Maruyama
pos_1 = pos_0 + frc_0*self.dt + self.noisescale*jnp.array(np.random.normal(size=pos_0.shape))
ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
return pos_1, vel_0, box_0, mas, ene_1, frc_1
代码
文本

5. Nose-Hoover Dynamics

Nose-Hoover dynamics provide an alternative to the Langevin dynamics for sampling from the canonical measure , where denotes the system Hamiltonian, with deterministic paths. For any test function , we want to compute the integral

where denotes the domain and are the coordinates of and . One way to compute this integral in an extended space by introducing new variables in addition to and additional equations of motion to drive them. The combined system is carefully designed to preserve an extended invariant distribution

where the auxiliary density has a simple form (e.g. a multivariate Gaussian). Then, it follows that

With the ergodicity assumption of the coupled system, we can then calculate the integral with respect to the Gibbs distribution by direct averaging along trajectories of the extended system. However, it is likely that the assumption of ergodicity fails and there are some known cases where this deterministic way of computing the integral leads to incorrect results. Still, this class of deterministic methods has been often used in molecular simulation.

Nose-Hoover dynamics is perhaps the simplest one of the class of deterministic methods we summarized above. Let denotes the number of degrees of freedom of the system. The extended system of Nose-Hoover dynamics takes the form

When the temperature of the system, measured as the average kinetic energy per degree of freedom, is higher than the prescribed temperature , the equation for will have a positive right-hand side and it will ensure that increases, and will eventually become positive. This will damp/cool the physical variables through equation for ; eventually the kinetic energy will fall below the target value, and the right-hand side of the last equation will become negative. By fluctuating in this way, typically controls the time-averaged kinetic energy of the system such that it is very close to , which is what the kinetic energy should be at the equilibrium. is a parameter that can be arbitrarily chosen to improve the performance of the dynamics.

This Nose-Hoover Dynamics preserves an extended measure with density of the form

which we will not prove here. To monitor the performance of numerical methods, we also observe that the Nose-Hoover system preserves the quantity

where solves the ODE . In the spirit of the splitting method we present when introducing numerical integrators for the Langevin equation, we here provide a splitting method for solving for the evolution of the Nose-Hoover dynamics. Suppose we want to integrate from time step to with time step size , then the scheme is as follows

代码
文本
[8]
class NoseHooverIntegrator(utils.BaseIntegrator):
def __init__(self, temperature=298.15, mu=5.0, Nd = 1, timestep=1.0e-3,removeCMMotion=False):
self.dt = timestep
self.mu = mu
self.temperature = temperature
self.removeCMMotion= removeCMMotion
self.Nd = 1
self.kbT = 1.380649 * 6.02214076 * 1e-3 * self.temperature
self.xi = -1
def step(self, state, engrad=None):
pos = state.getPositions()
vel = state.getVelocities()
box = state.getCellVector()
mas = state.getMasses()
frc = state.getForces()
ener = state.getPotentialEnergy()

pos_1, vel_1, self.xi, box_1, mas_1, ene_1, frc_1 = self.update_state(
pos, vel, self.xi, box, mas, ener, frc, engrad=engrad)

state_new = State(positions=pos_1,
velocities=vel_1,
cell=box_1,
energy=ene_1,
forces=frc_1,
masses=mas)
return state_new
def update_state(self, pos_0, vel_0, xi_0, box_0, mas, ene_0, frc_0, engrad=None):

pos_1 = pos_0 + vel_0 * 0.5 * self.dt

ene_1, grd_1 = engrad(pos_1, box_0)
frc_1 = - grd_1
vel_1 = vel_0*jnp.exp(-self.dt* xi_0/2) + frc_1 / mas / xi_0 *(1-jnp.exp(-self.dt* xi_0/2))

xi_1 = xi_0 + self.dt/self.mu*(jnp.sum(vel_1**2)*mas -self.Nd*self.kbT)

vel_1 = vel_1*jnp.exp(-self.dt* xi_1/2) + frc_1 / mas / xi_1 *(1-jnp.exp(-self.dt* xi_1/2))

pos_1 = pos_1 + vel_1 * 0.5 * self.dt
if self.removeCMMotion:
vel_1 -= vel_1.mean(axis=0)
return pos_1, vel_1, xi_1, box_0, mas, ene_1, frc_1
代码
文本

6. Numerical Example (1D Harmonic Oscillator)

代码
文本
[9]
def HarmonicOscillatorExample(integrator):
if integrator == "ABOBA":
integ = BAOABLangevinIntegrator()
else:
if integrator == "BAOAB":
integ = BAOABLangevinIntegrator()
if integrator == "Overdamped":
integ = OverdampedLangevinIntegrator()
if integrator == "NoseHoover":
integ = NoseHooverIntegrator()
else:
integ = BAOABLangevinIntegrator()
omm_pdb, engrad = utils.create1DHarmonicOscillator()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
state.velocities.at[1:].set(0)

pos_list = []
pe_list, ke_list, temp_list = [], [], []
for nstep in trange(200000):
state = integ.step(state, engrad)
if nstep % 10 == 0:
pos = state.getPositions()[0,0]
pos_list.append(pos)
pe_list.append(state.getPotentialEnergy())
ke_list.append(state.getKineticEnergy())
temp_list.append(state.getTemperature())
pos_list = np.array(pos_list)
pe_list = np.array(pe_list)
ke_list = np.array(ke_list)
temp_list = np.array(temp_list)

yy, xx, axis = plt.hist(pos_list, bins=31, density=True, label="sample")
xx = (xx[1:] + xx[:-1]) / 2
ee = 50. * xx * xx
pp = np.exp(- ee / 1.380649 / 6.02214076e-3 / 298.15)
pp = pp / pp.sum() / (xx[1] - xx[0])
plt.plot(xx, pp, label="prob.")
plt.xlabel("x coord")
plt.ylabel("energy (kJ/mol)")
plt.legend()
return state
HarmonicOscillatorExample("NoseHoover")
代码
文本
[10]
HarmonicOscillatorExample("ABOBA")
代码
文本
[11]
HarmonicOscillatorExample("Overdamped")
代码
文本

7. Numerical Example (2D Lennard-Jones Fluid)

代码
文本
[ ]
def LennardJonesFluid(integrator):
if integrator == "ABOBA":
integ = BAOABLangevinIntegrator(removeCMMotion=True)
else:
if integrator == "BAOAB":
integ = BAOABLangevinIntegrator(removeCMMotion=True)
if integrator == "Overdamped":
integ = OverdampedLangevinIntegrator()
if integrator == "NoseHoover":
integ = NoseHooverIntegrator(removeCMMotion=True)
else:
integ = BAOABLangevinIntegrator(removeCMMotion=True)
omm_pdb, engrad = utils.createLJFluid()
state = init_state_from_PDB(omm_pdb, engrad=engrad)
trj_writer = XYZWriter("traj_langevin_lj.xyz", omm_pdb.topology)


pe_list, ke_list, temp_list = [], [], []
for nstep in trange(500 * 200):
state = integ.step(state, engrad)
if nstep % 500 == 0:
pe_list.append(state.getPotentialEnergy())
ke_list.append(state.getKineticEnergy())
temp_list.append(state.getTemperature())
trj_writer.write(state)
pe_list = np.array(pe_list)
ke_list = np.array(ke_list)
temp_list = np.array(temp_list)
trj_writer.close()
plt.plot(temp_list)
plt.show()

plt.hist(temp_list,bins=31)
plt.show()
LennardJonesFluid("NoseHoover")
 23%|██▎       | 23045/100000 [27:39<1:28:28, 14.50it/s]IOPub message rate exceeded.
The Jupyter server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--ServerApp.iopub_msg_rate_limit`.

Current values:
ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)
ServerApp.rate_limit_window=3.0 (secs)

 69%|██████▊   | 68571/100000 [1:19:53<35:16, 14.85it/s]  
代码
文本
[ ]
LennardJonesFluid("Overdamped")
代码
文本
[ ]
LennardJonesFluid("ABOBA")
代码
文本
[ ]

代码
文本
notebook
AI4S
Deep Learning
python
Molecular Dynamics
notebookAI4SDeep LearningpythonMolecular Dynamics
已赞2
本文被以下合集收录
MD
xuzhen
更新于 2024-03-15
2 篇0 人关注
推荐阅读
公开
【DMFF开发指南】从头实现Harmonic Bond势函数的Generator
DMFF中文分子动力学#经典分子力学 # 力场
DMFF中文分子动力学#经典分子力学 # 力场
wangxy@dp.tech
发布于 2023-09-23
2 赞8 转存文件3 评论
公开
未命名
qeq_test_residues_2
qeq_test_residues_2
haichao
发布于 2023-11-01
1 转存文件
评论
 ! if [ ! -e DMFF ];t...

Hui_Zhou

09-26 00:05
这块“shutil.rmtree("data")会报无法删除的错误”,推测可能原因是当前目录为根目录,/data是挂载的一个NAS盘,删除盘的操作应该是禁止的

hmj

作者
09-26 04:09
具体原理我其实也不是太明白,您能展开说说么?我有的时候“shutil.rmtree("data")会报错,有的时候不会报错。
评论
 ### 2.2 verlet metho...

Hui_Zhou

09-25 22:40
发现一个拼写错误:段末“third aw”

hmj

作者
09-26 04:08
谢谢提醒,我改一下。
评论
  ### 2.3 Numerical E...

zjgemi

09-26 20:15
Why not consider absolute error wrt exact result?

zjgemi

09-26 20:22
p.s. Exact result of total energy is available for any conservative system, but not for potential energy.

hmj

作者
09-26 22:06
The position-verlet does not update the velocity. So, I did the convergence study in this way. But I also added some code for computing and comparing the absolute error wrt the total energy.
评论