百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

分子动力学模拟之基于自动微分的LINCS约束

zhezhongyun 2025-05-26 20:18 32 浏览

目录

  • 技术背景
  • 初始化坐标参数
  • 坐标的更新
  • 定义成键关系
  • LINCS算法
  • LINCS算法原理以及代码实现思路
    • 注意事项一
    • 注意事项二
    • 注意事项三
    • 注意事项四
    • 注意事项五
    • 总结
  • 总结概要
  • 版权声明
  • 参考链接

技术背景

在分子动力学模拟的过程中,考虑到运动过程实际上是遵守牛顿第二定律的。而牛顿第二定律告诉我们,粒子的动力学过程仅跟受到的力场有关系,但是在模拟的过程中,有一些参量我们是不希望他们被更新或者改变的,比如稳定的OH键的键长就是一个不需要高频更新的参量。这时就需要在一次不加约束的更新迭代之后(如Velocity-Verlet算法等),再施加一次约束算法,重新调整更新的坐标,使得规定的键长不会产生较大幅度的变更。

初始化坐标参数

为了实现LINCS这一算法,我们先初始化一组随机的坐标用于测试,比如我们测试一个10原子的体系:

# constrain.py
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
N = 10
crd = np.random.random((N, 3))

plt.figure()
plt.plot(crd[:,0], crd[:,1], 'o', color='black')
plt.savefig('initial.png')

初始化的体系效果如下,这是一个仅观测x-y平面的投影的结果(因为二维的投影在可视化上方便一些):

坐标的更新

参考牛顿定律,我们也用随机的方法产生一组初始速度,用于定义原子体系下一步的运动,再定义一个时间步长,我们就可以获取到下一步的体系坐标:

# constrain.py
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
N = 10
crd = np.random.random((N, 3))
dt = 0.1
vel = np.random.random((N, 3))
new_crd = crd + vel * dt

plt.figure()
plt.plot(crd[:,0], crd[:,1], 'o', color='black')
plt.plot(new_crd[:,0], new_crd[:,1], 'o', color='red')
plt.savefig('move.png')

把旧的坐标和更新之后的坐标放到一起的可视化效果如下:

定义成键关系

因为LINCS约束是施加在键长这一相对参数上的,因此我们首先需要在测试的体系中定义一套成键的关系:

# constrain.py
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
N = 10
crd = np.random.random((N, 3))
dt = 0.1
vel = np.random.random((N, 3))
new_crd = crd + vel * dt

# Add bonds information
bonds = np.array([[0,1],[0,2],[0,4],[2,3],
                  [2,4],[3,8],[5,8],[4,6],
                  [6,7],[7,9]])

plt.figure()
plt.plot(crd[:,0], crd[:,1], 'o', color='black')
plt.plot(new_crd[:,0], new_crd[:,1], 'o', color='red')
for bond in bonds:
    plt.plot(crd[bond][:,0], crd[bond][:,1], color='green')
    plt.plot(new_crd[bond][:, 0], new_crd[bond][:, 1], color='purple')
plt.savefig('move.png')

然后我们把成键关系也在可视化的结果中展现出来,得到这样一张图:

LINCS算法

接下来我们就讲到本文最核心的LINCS算法,其大致流程可以分为如下图(图片来自于参考链接1与LINCS原始文章)所示的3个步骤:


大致描述就是:先按照无约束的条件进行更新,这一点事实上我们在上一个章节中通过速度来更新坐标已经实现了这一操作。然后将更新后的成键在旧的成键上进行投影。最后对新的成键执行一个变换,即可得到保持原有键长的新的体系坐标。我们先看下相关的代码实现和结果,感兴趣的童鞋可以再往后阅读代码实现的思路和原理。

# constrain.py
import numpy as np
from jax import numpy as jnp
from jax import grad, jit, vmap
import matplotlib.pyplot as plt

# Initialization
np.random.seed(0)
N = 10
Dimension = 3
crd = np.random.random((N, Dimension))
# Mass diag
M = np.random.random(N)
Mi = np.identity(N) * M
Mii = np.identity(N) * (M ** (-1))
dt = 0.1
vel = np.random.random((N, Dimension))
new_crd = crd + vel * dt

# Add bonds information
bonds = np.array([[0,1],[0,2],[0,4],[2,3],
                  [2,4],[3,8],[5,8],[4,6],
                  [6,7],[7,9]])
# Bond length
di = np.linalg.norm(crd[bonds[:,0]] - crd[bonds[:,1]], axis=1)

# Automatic differentiation
def B(new_crd, bond, crd):
    return jnp.linalg.norm(new_crd[bond[0]]-new_crd[bond[1]]) -\
           jnp.linalg.norm(crd[bond[0]]-crd[bond[1]])
B_grad = grad(B, argnums=(0,))
B_vmap = jit(vmap(B_grad,(None,0,None)))
B_value = B_vmap(new_crd, bonds, crd)[0]

# LINCS
ccrd = new_crd.copy()
tmp0 = jnp.einsum('ij,kjl->kil', Mii, B_value)
tmp1 = jnp.einsum('jil,kil->jk', B_value, tmp0)
tmp2 = np.linalg.inv(tmp1)
tmp3 = jnp.einsum('ijk,jk->i', B_value, new_crd)-di
tmp4 = jnp.einsum('ij,j->i', tmp2, tmp3)
tmp5 = jnp.einsum('ijk,i->jk', B_value, tmp4)
tmp6 = jnp.einsum('ij,jk->ik', Mii, tmp5)
ccrd -= tmp6

# Draw
plt.subplot(211)
plt.plot(crd[:,0], crd[:,1], 'o', color='black')
plt.plot(new_crd[:,0], new_crd[:,1], 'o', color='blue')
plt.plot(ccrd[:,0], ccrd[:,1], 'o', color='red')
for bond in bonds:
    plt.plot(crd[bond][:,0], crd[bond][:,1], color='black')
    plt.plot(new_crd[bond][:,0], new_crd[bond][:,1], color='blue')
    plt.plot(ccrd[bond][:, 0], ccrd[bond][:, 1], color='red')

plt.subplot(212)
di = np.linalg.norm(crd[bonds[:,0]] - crd[bonds[:,1]], axis=1)
diuc = np.linalg.norm(new_crd[bonds[:,0]] - new_crd[bonds[:,1]], axis=1)
dic = np.linalg.norm(ccrd[bonds[:,0]] - ccrd[bonds[:,1]], axis=1)
plt.plot(di, color='black')
plt.plot(diuc, color='blue')
plt.plot(dic, '+', color='red')
plt.savefig('move.png')

执行输出的结果如下图所示:


在这个结果中我们可以看到第二个图中红色的十字就是施加LINCS约束之后的结果,很显然的距离原始的键长更近。需要额外提醒的是,第一张图中的成键实际上是三维的成键,所以视觉上的大小差异不是真是的键长大小差异,具体差异数值还是以第二张图中展示的为准。

LINCS算法原理以及代码实现思路

首先我们提到了分子的动力学模拟过程还是遵守牛顿第二定律,也就是:

d2rdt2=M-1f

其中rr是一个N×3的三维坐标体系,这里NN是体系的原子数,M是一个N×N的对角矩阵,每一个对角元代表一个原子的质量。事实上在计算过程中更加经常用到的是M的逆矩阵,又由于M是一个对角矩阵,因此M-1实际上就是每个对角元为对应原子质量的倒数这样的一个对角矩阵。f是跟r维度相同的体系作用力。

LINCS约束的方程可以表述为K个方程:

gi(r)=|ri1-ri2|-di=0 i=1,...,K

其中K的大小在这里代表了成键的对数,简单理解就是保证每一对更新后的键的键长的大小与正常的键长大小保持一致,比方说固定了一个OH基中O和H的相对距离。施加该约束的过程可以表述为拉格朗日乘子法:

-Md2rdt2=r(V-λ·g)

其中非势能项可以定位为BTλ,其中B定义为:

Bi=giri

由于这个形式涉及到了微分,不过由于自动微分这项技术的诞生,使得我们不需要自己再去手动的计算这个微分项,只需要把gi的形式给定,就可以在Jax中非常方便的计算其导数,并且有别于数值微分,自动微分兼具了高性能与高精度。而另外一点是向量化的操作,在Numba和Jax中分别支持了CPU上和GPU上的向量化操作,我们只需要写一条计算的方法,就可以把这个计算公式扩展到对更高维的数据进行处理,在Jax中这一功能接口为vmap。举个例子说,我们只需要写好计算BiBi的过程,就可以直接用vmap推广到求整个的BB。思路大体上就是如此,具体的过程可以参考上一章节中的源代码。

需要注意的是,这是一个0项,即一阶导数dgdtdgdt和二阶导数d2gdt2都是0的项,再结合leap-frog坐标更新算法,可以得到最终的坐标更新表达式(具体的推导过程还是建议看下原始文章,很多平台比如Gromacs也是使用了最终的这个表达式来进行计算或者优化)为:

rn+1=runcn+1-M-1Bn(BnM-1BTn)-1(Bnruncn+1-d)

我们从这个公式来分析下代码实现的流程,以及Python的实现过程中有可能遇到的一些坑。

注意事项一

rn+1是基于runcn+1来进行调整的,但是如果一开始直接使用:

r=r_unc

来初始化的话,会导致r_unc被覆盖,要知道r_unc还是会被频繁调用的,所以我们初始化的时候最好加上一个copy的操作。

注意事项二

矩阵乘法是从右往左来计算的,而Python中默认的矩阵乘法是从左往右的,因此最好不要直接使用Python中的乘号来直接计算多个矩阵的乘法,替代方案是手写numpy的multiply或者dot等函数配置参数。

注意事项三

在原始的论文中很多地方用到了求转置矩阵的操作,而面对高维矩阵的时候一定要指明操作所对应的轴,在本文的代码实现中,我们是使用了爱因斯坦求和的操作,这个操作在numpy和jax中都有接口支持。

注意事项四

在原始的论文中,为了避免对矩阵进行求逆,使用了一些展开和截断的近似计算的技术。但是对于体系规模不大的场景,其实直接使用numpy或者jax中的求逆函数,速度也不会很慢,本文旨在算法的实现,这里就直接使用了jax的求逆函数。

注意事项五

在jax中的一些函数返回的结果是一个tuple的形式,这是使用vmap和jit技术经常会遇到的情况,虽然并不是很难处理,只需要在得到的结果上取一个0的index即可,但是在实际计算的过程中还是需要注意。

总结

具体的代码实现,都在上一个章节中完整的展示了出来,这一章节只是介绍了LINCS算法的形式以及实现LINCS算法的一些思路,更加详细的推导,还是建议看下原始论文。

总结概要

本文通过完整的案例及其算法实现的过程,介绍了LINCS(Linear Constraint Solver)这一分子动力学模拟过程常用的约束算法。得益于Jax这一框架的便用性及其对numpy的强大支持、对GPU计算的优化、还有自动微分与向量化运算等技术的实现,使得我们实现LINCS这一算法变的不再困难。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/lincs.html

作者ID:DechinPhy

更多原著文章请参考:https://www.cnblogs.com/dechinphy/

打赏专用链接:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

腾讯云专栏同步:https://cloud.tencent.com/developer/column/91958

参考链接

  1. http://jerkwin.github.io/GMX/GMXman-3/#362-lincs

相关推荐

perl基础——循环控制_principle循环

在编程中,我们往往需要进行不同情况的判断,选择,重复操作。这些时候我们需要对简单语句来添加循环控制变量或者命令。if/unless我们需要在满足特定条件下再执行的语句,可以通过if/unle...

CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅

CHAPTER1TheThreePresentsofD'ArtagnantheElderCHAPTER2TheAntechamber...

CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士

CHAPTER3TheAudienceCHAPTER5TheKing'SMusketeersandtheCardinal'SGuard...

CHAPTER 3 The Audience 第三章 接见

CHAPTER3TheAudienceCHAPTER3TheAudience第三章接见M.DeTrévillewasatt...

别搞印象流!数据说明谁才是外线防守第一人!

来源:Reddit译者:@assholeeric编辑:伯伦WhoarethebestperimeterdefendersintheNBA?Here'sagraphofStea...

V-Day commemorations prove anti-China claims hollow

People'sLiberationArmyhonorguardstakepartinthemilitaryparademarkingthe80thanniversary...

EasyPoi使用_easypoi api

EasyPoi的主要特点:1.设计精巧,使用简单2.接口丰富,扩展简单3.默认值多,writelessdomore4.springmvc支持,web导出可以简单明了使用1.easypoi...

关于Oracle数据库12c 新特性总结_oracle数据库12514

概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...

【开发者成长】JAVA 线上故障排查完整套路!

线上故障主要会包括CPU、磁盘、内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。同时例如jstack、jmap等工具也是不囿于一个方面的问题...

使用 Python 向多个地址发送电子邮件

在本文中,我们将演示如何使用Python编程语言向使用不同电子邮件地址的不同收件人发送电子邮件。具体来说,我们将向许多不同的人发送电子邮件。使用Python向多个地址发送电子邮件Python...

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

linux常用系统命令_linux操作系统常用命令

系统信息arch显示机器的处理器架构dmidecode-q显示硬件系统部件-(SMBIOS/DMI)hdparm-i/dev/hda罗列一个磁盘的架构特性hdparm-tT/dev/s...

小白入门必知必会-PostgreSQL-15.2源码编译安装

一PostgreSQL编译安装1.1下载源码包在PostgreSQL官方主页https://www.postgresql.org/ftp/source/下载区选择所需格式的源码包下载。cd/we...

Linux操作系统之常用命令_linux系统常用命令详解

Linux操作系统一、常用命令1.系统(1)系统信息arch显示机器的处理器架构uname-m显示机器的处理器架构uname-r显示正在使用的内核版本dmidecode-q显示硬件系...

linux网络命名空间简介_linux 网络相关命令

此篇会以例子的方式介绍下linux网络命名空间。此例中会创建两个networknamespace:nsa、nsb,一个网桥bridge0,nsa、nsb中添加网络设备veth,网络设备间...