技术背景

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

初始化坐标参数

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

  1. # constrain.py
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. np.random.seed(0)
  5. N = 10
  6. crd = np.random.random((N, 3))
  7. plt.figure()
  8. plt.plot(crd[:,0], crd[:,1], 'o', color='black')
  9. plt.savefig('initial.png')

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

坐标的更新

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

  1. # constrain.py
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. np.random.seed(0)
  5. N = 10
  6. crd = np.random.random((N, 3))
  7. dt = 0.1
  8. vel = np.random.random((N, 3))
  9. new_crd = crd + vel * dt
  10. plt.figure()
  11. plt.plot(crd[:,0], crd[:,1], 'o', color='black')
  12. plt.plot(new_crd[:,0], new_crd[:,1], 'o', color='red')
  13. plt.savefig('move.png')

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

定义成键关系

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

  1. # constrain.py
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. np.random.seed(0)
  5. N = 10
  6. crd = np.random.random((N, 3))
  7. dt = 0.1
  8. vel = np.random.random((N, 3))
  9. new_crd = crd + vel * dt
  10. # Add bonds information
  11. bonds = np.array([[0,1],[0,2],[0,4],[2,3],
  12. [2,4],[3,8],[5,8],[4,6],
  13. [6,7],[7,9]])
  14. plt.figure()
  15. plt.plot(crd[:,0], crd[:,1], 'o', color='black')
  16. plt.plot(new_crd[:,0], new_crd[:,1], 'o', color='red')
  17. for bond in bonds:
  18. plt.plot(crd[bond][:,0], crd[bond][:,1], color='green')
  19. plt.plot(new_crd[bond][:, 0], new_crd[bond][:, 1], color='purple')
  20. plt.savefig('move.png')

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

LINCS算法

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



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

  1. # constrain.py
  2. import numpy as np
  3. from jax import numpy as jnp
  4. from jax import grad, jit, vmap
  5. import matplotlib.pyplot as plt
  6. # Initialization
  7. np.random.seed(0)
  8. N = 10
  9. Dimension = 3
  10. crd = np.random.random((N, Dimension))
  11. # Mass diag
  12. M = np.random.random(N)
  13. Mi = np.identity(N) * M
  14. Mii = np.identity(N) * (M ** (-1))
  15. dt = 0.1
  16. vel = np.random.random((N, Dimension))
  17. new_crd = crd + vel * dt
  18. # Add bonds information
  19. bonds = np.array([[0,1],[0,2],[0,4],[2,3],
  20. [2,4],[3,8],[5,8],[4,6],
  21. [6,7],[7,9]])
  22. # Bond length
  23. di = np.linalg.norm(crd[bonds[:,0]] - crd[bonds[:,1]], axis=1)
  24. # Automatic differentiation
  25. def B(new_crd, bond, crd):
  26. return jnp.linalg.norm(new_crd[bond[0]]-new_crd[bond[1]]) -\
  27. jnp.linalg.norm(crd[bond[0]]-crd[bond[1]])
  28. B_grad = grad(B, argnums=(0,))
  29. B_vmap = jit(vmap(B_grad,(None,0,None)))
  30. B_value = B_vmap(new_crd, bonds, crd)[0]
  31. # LINCS
  32. ccrd = new_crd.copy()
  33. tmp0 = jnp.einsum('ij,kjl->kil', Mii, B_value)
  34. tmp1 = jnp.einsum('jil,kil->jk', B_value, tmp0)
  35. tmp2 = np.linalg.inv(tmp1)
  36. tmp3 = jnp.einsum('ijk,jk->i', B_value, new_crd)-di
  37. tmp4 = jnp.einsum('ij,j->i', tmp2, tmp3)
  38. tmp5 = jnp.einsum('ijk,i->jk', B_value, tmp4)
  39. tmp6 = jnp.einsum('ij,jk->ik', Mii, tmp5)
  40. ccrd -= tmp6
  41. # Draw
  42. plt.subplot(211)
  43. plt.plot(crd[:,0], crd[:,1], 'o', color='black')
  44. plt.plot(new_crd[:,0], new_crd[:,1], 'o', color='blue')
  45. plt.plot(ccrd[:,0], ccrd[:,1], 'o', color='red')
  46. for bond in bonds:
  47. plt.plot(crd[bond][:,0], crd[bond][:,1], color='black')
  48. plt.plot(new_crd[bond][:,0], new_crd[bond][:,1], color='blue')
  49. plt.plot(ccrd[bond][:, 0], ccrd[bond][:, 1], color='red')
  50. plt.subplot(212)
  51. di = np.linalg.norm(crd[bonds[:,0]] - crd[bonds[:,1]], axis=1)
  52. diuc = np.linalg.norm(new_crd[bonds[:,0]] - new_crd[bonds[:,1]], axis=1)
  53. dic = np.linalg.norm(ccrd[bonds[:,0]] - ccrd[bonds[:,1]], axis=1)
  54. plt.plot(di, color='black')
  55. plt.plot(diuc, color='blue')
  56. plt.plot(dic, '+', color='red')
  57. plt.savefig('move.png')

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



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

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

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

\[\frac{d^2\textbf r}{dt^2}=\textbf M^{-1}\textbf f
\]

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

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

\[g_i(\textbf r)=|\textbf r_{i1}-\textbf r_{i2}|-d_i=0\ \ \ \ i=1,...,K
\]

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

\[-\textbf M\frac{d^2\textbf r}{dt^2}=\frac{\partial}{\partial\textbf r}(\textbf V-\lambda \cdot g)
\]

其中非势能项可以定位为\(B^{T}\lambda\),其中\(B\)定义为:

\[B_i=\frac{\partial g_i}{\partial r_i}
\]

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

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

\[\textbf r_{n+1}=\textbf r_{n+1}^{unc}-\textbf M^{-1}B_n(B_n\textbf M^{-1}B_n^T)^{-1}(B_n\textbf r_{n+1}^{unc}-\textbf d)
\]

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

注意事项一

\(\textbf r_{n+1}\)是基于\(\textbf r_{n+1}^{unc}\)来进行调整的,但是如果一开始直接使用:

  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

分子动力学模拟之基于自动微分的LINCS约束的更多相关文章

  1. 分子动力学模拟之SETTLE约束算法

    技术背景 在上一篇文章中,我们讨论了在分子动力学里面使用LINCS约束算法及其在具备自动微分能力的Jax框架下的代码实现.约束算法,在分子动力学模拟的过程中时常会使用到,用于固定一些既定的成键关系.例 ...

  2. Gromacs分子动力学模拟流程概述

    Gromacs分子动力学模拟主要可以分为以下几个步骤,不同的体系步骤可能略有不同. 在开始之前,先简单了解一下预平衡: 分子动力学模拟的最终目的是对体系进行抽样,然后计算体系的能量,各种化学键,成分分 ...

  3. MindSpore:自动微分

    MindSpore:自动微分 作为一款「全场景 AI 框架」,MindSpore 是人工智能解决方案的重要组成部分,与 TensorFlow.PyTorch.PaddlePaddle 等流行深度学习框 ...

  4. 附录D——自动微分(Autodiff)

    本文介绍了五种微分方式,最后两种才是自动微分. 前两种方法求出了原函数对应的导函数,后三种方法只是求出了某一点的导数. 假设原函数是$f(x,y) = x^2y + y +2$,需要求其偏导数$\fr ...

  5. pytorch学习-AUTOGRAD: AUTOMATIC DIFFERENTIATION自动微分

    参考:https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autog ...

  6. 自动微分(AD)学习笔记

    1.自动微分(AD) 作者:李济深链接:https://www.zhihu.com/question/48356514/answer/125175491来源:知乎著作权归作者所有.商业转载请联系作者获 ...

  7. <转>如何用C++实现自动微分

    作者:李瞬生转摘链接:https://www.zhihu.com/question/48356514/answer/123290631来源:知乎著作权归作者所有. 实现 AD 有两种方式,函数重载与代 ...

  8. 【转】C# winform 加载网页 模拟键盘输入自动接入访问网络

    [转]C# winform 加载网页 模拟键盘输入自动接入访问网络 声明: 本文原创,首发于博客园 http://www.cnblogs.com/EasyInvoice/p/6070563.html  ...

  9. (转)自动微分(Automatic Differentiation)简介——tensorflow核心原理

    现代深度学习系统中(比如MXNet, TensorFlow等)都用到了一种技术——自动微分.在此之前,机器学习社区中很少发挥这个利器,一般都是用Backpropagation进行梯度求解,然后进行SG ...

随机推荐

  1. Redis 实现了自己的 VM

    Redis的VM(虚拟内存)机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据). Redis提高数据库容量的办法有两种: 1.一种是可以 ...

  2. ROC and AUC

    目录 概 TPR, FPR ROC and AUC 代码 ROC-wiki 概 AUC常常在文章中作为评价一个分类器优劣的指标, 却总是忘记其原由, 索性记上一笔. TPR, FPR 首先理解TP, ...

  3. Android物联网应用程序开发(智慧城市)—— 火焰监控界面开发

    效果: 布局代码: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns: ...

  4. 制作四个选项卡页 Tab,用户可以通过切换不同的 Tab 页查看不同类别的新闻信息,每个 Tab 有对应的内容版块,点击某个选项卡时,显示对应的内容版块,隐藏其他内容版块,并且为了突出当前的选项卡,还

    查看本章节 查看作业目录 需求说明: 制作四个选项卡页 Tab,用户可以通过切换不同的 Tab 页查看不同类别的新闻信息,每个 Tab 有对应的内容版块,点击某个选项卡时,显示对应的内容版块,隐藏其他 ...

  5. Linux配置yum源(离线和在线)

    配置yum源有2种方法,一种是离线yum源,另外一种是在线yum源. 一.离线yum源,基于安装光盘提供的安装仓库. 建立一个属于仓库文件夹 mkdir /media/zidong cd /media ...

  6. mongdb分片

    实验环境 主机              IP                虚拟通道 centos1       192.168.3.10         vmnet8 centos2       ...

  7. 【PowerShell】文件的解压与压缩

    1 New-Item -ItemType File 1.txt -Force #新建文本文件 2 Compress-Archive -Path '1.txt' -DestinationPath '1. ...

  8. F5 BIG-IP 远程代码执行漏洞环境搭建

    最近F5设备里的远程代码执行漏洞可谓是火爆,漏洞评分10分,所以,我也想搭建下环境复现一下该漏洞 漏洞详情 F5 BIG-IP 是美国F5公司一款集成流量管理.DNS.出入站规则.web应用防火墙.w ...

  9. Whitelabel Error Page错误原因

    前言: 今天在做项目中遇到了一个问题,项目启动成功,但是前段访问接口始终访问不成功,页面一直在404,百度了一番无非两种解决方案: 一.解决方案 1.项目是boot项目查看启动类的位置是否放置正确 要 ...

  10. Android官方文档翻译 八 2.1Setting Up the Action Bar

    Setting Up the Action Bar 建立Action Bar This lesson teaches you to 这节课教给你 Support Android 3.0 and Abo ...