分子动力学模拟之基于自动微分的LINCS约束
itomcoil 2025-01-10 14:19 30 浏览
目录
- 技术背景
- 初始化坐标参数
- 坐标的更新
- 定义成键关系
- 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=?gi?ri
由于这个形式涉及到了微分,不过由于自动微分这项技术的诞生,使得我们不需要自己再去手动的计算这个微分项,只需要把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
参考链接
- http://jerkwin.github.io/GMX/GMXman-3/#362-lincs
- 上一篇:Python 3.5.0b1发布
- 下一篇:C,Java和Python之间的性能比较
相关推荐
- python创建文件夹,轻松搞定,喝咖啡去了
-
最近经常在录视频课程,一个课程下面往往有许多小课,需要分多个文件夹来放视频、PPT和案例,这下可好了,一个一个手工创建,手酸了都做不完。别急,来段PYTHON代码,轻松搞定,喝咖啡去了!import...
- 如何编写第一个Python程序_pycharm写第一个python程序
-
一、第一个python程序[掌握]python:python解释器,将python代码解释成计算机认识的语言pycharm:IDE(集成开发环境),写代码的一个软件,集成了写代码,...
- Python文件怎么打包为exe程序?_python3.8打包成exe文件
-
PyInstaller是一个Python应用程序打包工具,它可以将Python程序打包为单个独立可执行文件。要使用PyInstaller打包Python程序,需要在命令行中使用py...
- 官方的Python环境_python环境版本
-
Python是一种解释型编程开发语言,根据Python语法编写出来的程序,需要经过Python解释器来进行执行。打开Python官网(https://www.python.org),找到下载页面,选择...
- [编程基础] Python配置文件读取库ConfigParser总结
-
PythonConfigParser教程显示了如何使用ConfigParser在Python中使用配置文件。文章目录1介绍1.1PythonConfigParser读取文件1.2Python...
- Python打包exe软件,用这个库真的很容易
-
初学Python的人会觉得开发一个exe软件非常复杂,其实不然,从.py到.exe文件的过程很简单。你甚至可以在一天之内用Python开发一个能正常运行的exe软件,因为Python有专门exe打包库...
- 2025 PyInstaller 打包说明(中文指南),python 打包成exe 都在这里
-
点赞标记,明天就能用上这几个技巧!linux运维、shell、python、网络爬虫、数据采集等定定做,请私信。。。PyInstaller打包说明(中文指南)下面按准备→基本使用→常用...
- Python自动化办公应用学习笔记40—文件路径2
-
4.特殊路径操作用户主目录·获取当前用户的主目录路径非常常用:frompathlibimportPathhome_dir=Path.home()#返回当前用户主目录的Path对象...
- Python内置tempfile模块: 生成临时文件和目录详解
-
1.引言在Python开发中,临时文件和目录的创建和管理是一个常见的需求。Python提供了内置模块tempfile,用于生成临时文件和目录。本文将详细介绍tempfile模块的使用方法、原理及相关...
- python代码实现读取文件并生成韦恩图
-
00、背景今天战略解码,有同学用韦恩图展示各个产品线的占比,效果不错。韦恩图(Venndiagram),是在集合论数学分支中,在不太严格的意义下用以表示集合的一种图解。它们用于展示在不同的事物群组之...
- Python技术解放双手,一键搞定海量文件重命名,一周工作量秒搞定
-
摘要:想象一下,周五傍晚,办公室的同事们纷纷准备享受周末,而你,面对着堆积如山的文件,需要将它们的文件名从美国日期格式改为欧洲日期格式,这似乎注定了你将与加班为伍。但别担心,Python自动化办公来...
- Python路径操作的一些基础方法_python路径文件
-
带你走进@机器人时代Discover点击上面蓝色文字,关注我们Python自动化操作文件避开不了路径操作方法,今天我们来学习一下路径操作的一些基础。Pathlib库模块提供的路径操作包括路径的...
- Python爬取下载m3u8加密视频,原来这么简单
-
1.前言爬取视频的时候发现,现在的视频都是经过加密(m3u8),不再是mp4或者avi链接直接在网页显示,都是经过加密形成ts文件分段进行播放。今天就教大家如果通过python爬取下载m3u8加密视频...
- 探秘 shutil:Python 高级文件操作的得力助手
-
在Python的标准库中,shutil模块犹如一位技艺精湛的工匠,为我们处理文件和目录提供了一系列高级操作功能。无论是文件的复制、移动、删除,还是归档与解压缩,shutil都能以简洁高效的方式完成...
- 怎么把 Python + Flet 开发的程序,打包为 exe ?这个方法很简单!
-
前面用Python+Flet开发的“我的计算器v3”,怎么打包为exe文件呢?这样才能分发给他人,直接“双击”运行使用啊!今天我给大家分享一个简单的、可用的,把Flet开发的程序打包为...
- 一周热门
- 最近发表
-
- python创建文件夹,轻松搞定,喝咖啡去了
- 如何编写第一个Python程序_pycharm写第一个python程序
- Python文件怎么打包为exe程序?_python3.8打包成exe文件
- 官方的Python环境_python环境版本
- [编程基础] Python配置文件读取库ConfigParser总结
- Python打包exe软件,用这个库真的很容易
- 2025 PyInstaller 打包说明(中文指南),python 打包成exe 都在这里
- Python自动化办公应用学习笔记40—文件路径2
- Python内置tempfile模块: 生成临时文件和目录详解
- python代码实现读取文件并生成韦恩图
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)