©️ Copyright 2023 @ Authors
作者:ChenYuxiang LiDenan(AI4S1O1-材料小队学习志愿者) 📨
日期:2024-6-16
共享协议:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
快速开始:点击上方的 开始连接 按钮,选择 bohrium-notebook:2023-03-26 镜像及 c2_m4_cpu 节点配置即可开始运行。
樊哲勇《分子动力学模拟》python实例 | 2.使用近邻列表的分子动力学模拟程序
樊哲勇老师最近出版的书籍《分子动力学模拟》中,使用C++语言进行分子模拟程序的实现。未了更好地理解分子动力学模拟,AI4S1O1-材料小队学习志愿者决定针对樊老师书籍中的内容,进行python语言的程序实现以及注释说明。
本节内容对应樊老师书中的第三章
本章目标
- 了解如何在 三斜盒子 内进行坐标系变换、施加周期性边界条件和最小镜像约定
- 学习 近邻列表 的构造与更新
- 用python实现上述内容
一、三斜盒子
1.1 定义
在simpleMD中,我们只考虑了正交盒子,正交盒子只有、和三个边长的自由度,三个边长是相互垂直正交的,沿方向,沿方向,沿方向,因此没有角度上的自由度。本节我们将引入三斜盒子(triclinic box)。下图中左边就是正交盒子,右边是三斜盒子
由上图可知,盒子的三条有向线段可以表示为矢量,记为、和。、和、是一组标准正交基,而模拟盒子的三条边、和可以表示为:
上述的系数可以组合成一个矩阵,记为盒子矩阵:
上一章讨论的正交盒子可以看成是一种特殊的三斜盒子:
三斜盒子的体积等于上述矩阵行列式的绝对值:
为方便讨论,我们记盒子的矩阵的逆为。
对于三斜盒子,三个面的面积,和为:
则垂直于平面的厚度、和为:
1.2 盒子里的原子坐标
对于一个盒子内的原子,其坐标可以表示为:
表示原子在盒子内的相对位置,因为原子只能在盒子内部或其表面,故定义域为,称为分数坐标。上式可以表示为如下矩阵形式:
上式可用简写为:
将盒子矩阵的逆作用在上述等式两边,可得:
通过上述2个等式,我们便可以很方便的将盒子内的原子坐标在分数坐标和笛卡尔坐标之间相互转化。
1.3 周期性边界条件
在得到原子的分数坐标后, 可很方便的施加周期性边界条件:
- 将原坐标转换为分数坐标。
- 对于分数坐标中任意一个分量:若,则;若,则。
- 将分数坐标转换为笛卡尔坐标
1.4 最小镜像约定
如果有粒子(坐标)和粒子(坐标),在三斜盒子内计算两个粒子的相对坐标时,也可以很方便的施加最小镜像约定:
- 计算粒子和粒子的相对坐标:。
- 用盒子逆矩阵可以将变为分数坐标,变换关系为:
- 实施最小镜像约定:对分数坐标中任意一个坐标分量,若,则;若,则。
- 将变换后的分数坐标转换为普通的笛卡尔坐标:
1.5 三斜盒子的代码实现
大家可以思考如何用python3实现以下功能:
- 计算三斜晶胞的体积,边长
- 分数坐标和笛卡尔坐标相互转化
- 施加最小镜像约定和周期性边界条件
下面给出了一个参考代码,需要注意的是:代码中的盒子矩阵是上述的转置
volume: 161.31810738968053 l_a: 5.443702372939453, l_b: 5.443702372939453, l_c: 5.443702372939453 frac_coords: [[ 7.50000000e-01 7.50000000e-01 2.50000000e-01] [-4.45929298e-33 5.00000000e-01 5.00000000e-01] [ 7.50000000e-01 2.50000000e-01 7.50000000e-01] [ 0.00000000e+00 0.00000000e+00 0.00000000e+00] [ 2.50000000e-01 7.50000000e-01 7.50000000e-01] [ 5.00000000e-01 5.00000000e-01 2.45444455e-33] [ 2.50000000e-01 2.50000000e-01 2.50000000e-01] [ 5.00000000e-01 0.00000000e+00 5.00000000e-01]] cart_coords: [[4.08277678e+00 4.08277678e+00 1.36092559e+00] [4.50000000e-16 2.72185119e+00 2.72185119e+00] [4.08277678e+00 1.36092559e+00 4.08277678e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00] [1.36092559e+00 4.08277678e+00 4.08277678e+00] [2.72185119e+00 2.72185119e+00 3.00000000e-16] [1.36092559e+00 1.36092559e+00 1.36092559e+00] [2.72185119e+00 0.00000000e+00 2.72185119e+00]] origin coords: [[4.08277678e+00 4.08277678e+00 1.36092559e+00] [4.50000000e-16 2.72185119e+00 2.72185119e+00] [4.08277678e+00 1.36092559e+00 4.08277678e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00] [1.36092559e+00 4.08277678e+00 4.08277678e+00] [2.72185119e+00 2.72185119e+00 3.00000000e-16] [1.36092559e+00 1.36092559e+00 1.36092559e+00] [2.72185119e+00 0.00000000e+00 2.72185119e+00]] new coords: [[4.08277678e+00 4.08277678e+00 1.36092559e+00] [4.50000000e-16 2.72185119e+00 2.72185119e+00] [4.08277678e+00 1.36092559e+00 4.08277678e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00] [1.36092559e+00 4.08277678e+00 4.08277678e+00] [2.72185119e+00 2.72185119e+00 3.00000000e-16] [1.36092559e+00 1.36092559e+00 1.36092559e+00] [2.72185119e+00 0.00000000e+00 2.72185119e+00]] origin rij: [ 4.08277678 1.36092559 -1.36092559] new rij: [-1.36092559 1.36092559 -1.36092559]
二、近邻列表的构建和维护
2.1 为什么要使用近邻列表
在上一章中,我们使用LJ势计算了体系的能量和每个原子的受力,并根据原子受力更新了原子的位置和速度。对于一个有个原子的体系,求能量和力时需要遍历原子对,其计算量与成正比。当更新原子的位置和速度时,需要遍历每个原子,其计算量与成正比。因此可以写出总计算量,其中、和为常数。当很大时,占主导地位,该程序的计算复杂度为,具有平方标度。当粒子数很大时,计算量也会变得很大。
解决上述问题的一个方式是构建近邻列表(neighbor list),在其中提前储存好每一个原子周围的近邻原子,在需要计算时直接使用。近邻列表中有几个关键参数:
- 近邻列表的截断半径:在构建近邻列表时,我们只会考虑距离在以内的原子为近邻原子,应该大于势函数的截断半径。越大,近邻原子数量越多,但内存消耗会更大。
- 缓冲半径:一般而言,刚构建好的近邻列表在若干步内都是安全的,不需要每一步都更新。若较小,则每次构建近邻列表所需时间较小,但更新近邻列表的频率较高。一般而言是不错的选择。
2.2 何时更新近邻列表
原子的近邻不是一成不变的,因此构建好的近邻列表需要每隔一段时间更新一下。更新的时机,既可以按照某种固定频率,也可以根据原子坐标变化来判断。下面介绍一种决定何时更新近邻列表的算法:
- 定义一套额外的参考坐标,每个原子的坐标都初始化为0.
- 在更新原子位置时,对每个原子计算其位移矢量:
- 若,则更新近邻列表,同时更新
这样,当原子移动了一个较大距离后,程序就会自动更新近邻列表。下面是该算法的一个python实现:
Need update!
2.3 平方标度算法构建近邻列表
近邻列表的平方标度构建方法较为简单:
对于一个原子,遍历该原子和所有原子间的距离,若其间距小于,则将对应原子记为的近邻。
该算法仍具有的复杂度,但和原来相比,该算法不用在每一步都遍历所有原子对,只需要间隔一段时间更新近邻列表即可,从而节省时间。
下面给出了该算法的一个python实现:
2.4 线性标度算法构建近邻列表
在MD模拟中最早提出线性标度算法的可能是Quentrec和Brot。该算法的主要思想有以下两点:
- 整个模拟盒子被划分为一系列小胞,每个小胞的任何厚度均不小于近邻列表的截断半径。
- 对于任何一个原子,只需要在27个小胞(一个是该原子所在的小胞,另外26个是与该胞挨着的)中寻找近邻原子。(下图展示了二维空间中的情形,对于任意原子,只需要在该原子本身所在的1个小胞+8个近邻小胞,共9个小胞内寻找近邻原子即可)
这样,我们可以先计算原子在哪个小胞内。然后遍历每个原子,并只在对应的小胞内寻找近邻,避免了遍历所有的原子对,因此提高了效率。
在实现该算法时,首先我们需要确定将整个模拟盒子划分成多少个小胞,可以根据1.1节中的公式来计算盒子在每个方向上的厚度、和 ,将厚度除以近邻列表的截断半径,并向下取整,即可得到每个方向上的小胞个数:
下面给出了该算法的一个python实现:
[4 4 4 4 4 4 4 4]
三、用ASE或pymatgen快速构建近邻列表(拓展阅读)
Python语言的一大优势是具有丰富的第三方库,可以很方便的帮助我们快速实现某些功能。在材料模拟领域,较常用的库有ASE(全称:Atomic Simulation Environment, 官网:https://wiki.fysik.dtu.dk/ase/) 和Pymatgen(全称:Python Materials Genomics,官网:https://pymatgen.org/) 。这里展示如何基于ASE或Pymatgen快速构建近邻列表。这里只展示基本用法,高阶用法可以前往官网阅读手册。
3.1 用ASE构建近邻列表
[5 7 1 3]
3.2 用Pymatgen构建近邻列表
[5 7 1 3]
四、课后作业
通过本节的学习,你应该已经掌握了三斜盒子、近邻列表的相关基础知识,并能通过Python实现相关功能。课后希望各位同学能运用本节所学知识,使用python实现linearMD程序,并完成相应测试。C++版的代码可以参考樊老师的github仓库:https://github.com/brucefan1983/Molecular-Dynamics-Simulation ,Python版的参考代码详见本notebook的数据集。