Bohrium
robot
新建

空间站广场

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

我的工作空间

任务
节点
文件
数据集
镜像
项目
数据库
公开
周期性体系中静电相互作用的计算方法: Ewald Summation和Partical Mesh Ewald
中文
#经典分子力学 # 力场
LaTex
从算法原理到代码实现
科普
力场
中文#经典分子力学 # 力场LaTex从算法原理到代码实现科普力场
liangyy
发布于 2023-09-22
推荐镜像 :ABACUS:3.3.2-user-guide
推荐机型 :c2_m4_cpu
赞 7
1
2
前言
Background
Ewald Summation
Partical Mesh Ewald
PME code 实现
附录
倒易空间
Fast Fourier Transform

前言

一、本文重科普轻推导,主要是介绍Ewald Summation 与 Partical Mesh Ewald为什么会 出现,它们做了什么以及为什么这么做。

二、book内容是对以下两篇文章的简述,欢迎大家阅读。

  1. Z. Hu, J. Chem. Theory Comput. 2014, 10, 5254–5264.
  2. Essmann, U.; Perera, L.; Berkowitz, M. L.; Darden, T.; Lee, H.; Pedersen, L. G. J. Chem. Phys. 1995, 103, 8577−8593.

三、有疑问可联系zhaoyihao@protonmail.com

若读者只对分子模拟有基本了解,那本文可以让你对Ewald Summation方法本身以及发展脉络有一个清晰的理解,若读者推导过Ewald Summmation方法并写过相应的code,那么恭喜你将是本文最大的受益者,深刻理解Ewald Summation背后的物理意义是比数学推导更加困难的事情

代码
文本

Background

静电相互作用势为: 对于N个带电粒子的体系而言,体系的静电相互作用能为: 受算力的限制我们无法模拟一个宏观体系,因此我们通常会施加周期性边界条件,使得能从微观体系中获得和宏观体系一般的性质。 此时对于周期性体系而言,其静电相互作用能为: E^{\mathrm{ele}} = \dfrac{1}{2}\sum_{\mathbf{n}} \sum_{i}^{N} \sum_{j}^{N} !' q_iq_j u^{\mathrm{ele}}(\mathbf{r}_{ij}+\mathbf{n}\mathbf{L}) \tag{1-3} 其中 , 为盒子边长, ,“”代表当 。 显然我们不能真的对所有的格矢 进行求和,需要像其它作用势那样加截断距离。然而静电势是衰减,在一维空间及以上时其从0积分至 时是发散的, 不能直接加截断。 因此出现Ewald Summation方法。

代码
文本

Ewald Summation

1921年Paul Peter Ewald提出, 当静电势 分解为两部分:

代码
文本

erf.png

代码
文本

是调控两部分占比的系数。由图1我们可以看到两部分中 部分衰减迅速, 部分衰减缓慢。分解后对 于衰减迅速的 部分就可以像其它作用势一般加截断在实空间进行求和,对于 部分做特殊处理。

当作用势被分解后,相应地静电相互作用能也被分解为多部分:

E^{R} = \dfrac{1}{2}\sum_{\mathbf{n}} \sum_{i}^{N} \sum_{j}^{N} !'q_iq_j \dfrac{\mathrm{erfc}(\alpha|\mathbf{r}{ij}+\mathbf{n}\mathbf{L}|)}{|\mathbf{r}{ij}+\mathbf{a}|} \tag{2-3}

两项作用能分别对应分解出的两部分作用势,需注意在 中没有去掉 项,即自相互作用项,因此需要额外减去

对于衰减缓慢的 部分所作的特殊处理是,不再将其在实空间对格矢 进行求和, 而是变换到 Fourier空间(也称倒易空间)对波矢进行求和。

代码
文本

Kspace.png

代码
文本

这一步是通过Poisson's summation formula完成的:

其中 。由于 处在分母位置上,因此将 项单列为 称为无穷边界项,该项是条件收敛的且与体系的介电响应等性质息息相关(1921年Ewald提出Ewald Summation方法时并没有注意到这一项,直到1981年人们才认识到这一项的重要性,无穷边界项是Ewald Summation插图的最后一角,人们至今为止依旧没能完全理解这一项)。

此时,对于 项也是通过在倒空间加截断求和的(如图2中的虚线)。

项变换到倒空间对倒格矢进行求和的必要性是: 尽管我们依旧无法对无限的格矢求和, 但是对有限的倒格矢求和还是使得我们能够抓住两个相距无穷远的带电粒子之间的correlation。 而这是直接对静电势 在实空间加截断无法做到的,因为只有截断距离是有限的,那么有限+1尺度上的correlation还是无法抓住的。

那么自然而然地就存在一个问题,为什么对 在倒易空间中对波矢加截断不会切断实空间中两个相距无穷远的电荷之间的关联? 对比笔者认为最易于理解的角度是点阵的角度出发:由于我们模拟的体系是具有周期性边界条件可以将整个体系视作晶格,当把周期性体系抽象为点阵后,eq(1-3)中的对无穷的格矢求和其实就是对无限的点阵点求和,同样也等价于对无限的晶面族求和,而倒易点阵的点阵点与正点阵中晶面族一一对应(见附录),对倒易点阵点加截断即为只对有限的晶面族求和, 而任一晶面族中都不仅包括包含离正点阵原点近的正点阵点也包括相距无穷远的正点阵点。 这就是为什么Ewald Summation能够抓住相距无穷远的电荷之间的关联。

代码
文本

Partical Mesh Ewald

从eq(2-3)与eq(2-8)中我们不难发现,不论是实空间能量 项还是倒空间能量 项,它们的复杂度均为 。 对于实空间中的 项我们可以使用neighbour list方法来降低复杂度。而对于倒空间的 项则需要通过Partical Mesh Ewald方法来降低复杂度 至

Partical Mesh Ewald的核心是Fast Fourier Transform (FFT), 而为了能够FFT需要先离散插值。 为保持与参考文献的一致性,将eq(2-8)中做 替换:

其中:

u是带电粒子的分数坐标, 例

首先将 离散到的网格上:

其中为b样条系数,可由 Cox-de Boor formula递归得到:

需要说明的是样条插值的系数由线性方程组的解得到,但会出现不满秩的情况(出现基函数为0)。 对于复指数的样条插值有特殊的解决方法称为欧拉指数样条,在阶数为偶数时:

b_{\alpha}(m_{\alpha}) = \exp(i2\pi(n-1)\frac{m_{\alpha}}{K_{\alpha}})\big{[} \sum_{k=0}^{n-2}M_n(k+1)\exp(i2\pi \frac{m_{\alpha}}{K_1}k)\big{]}^{-1} \tag{3-6}

eq(3-3)中被Fouier变换的函数 的表达式如下, 其物理意义是将电荷离散到网格的格点上(如图3所示), 的存在是保持电荷离散时格点在同一个周期内。

代码
文本

mesh.png

代码
文本

此时将离散后的 带入到 中:

其中 。此时对于 使用Cooley-Tukey FFT算法已经降低复杂度至 了,但通常会更进一步简化,即通过 以及卷积的 Fourier 变换等于 Fourier 的乘积得到最终表达式: 其中:

代码
文本

PME code 实现

示例代码力求可读性,不具备工作效率。

求解倒易空间的能量项时,所需的的物理量如下:

  1. N ;粒子的总数目;
  2. : 粒子坐标的集合;
  3. : 粒子电荷的集合;
  4. : 三个维度上离散格点的数目(三者必须均为2的整数次幂,Cooley-Tukey FFT算法结构决定);
  5. : B样条阶数,(必须为偶数,且小于等于 ,由离散电荷时不超过一个周期决定)
代码
文本
  • 第一步:计算欧拉指数样条系数
  1. 通过Cox-de Boor formula eq(3-4)和初始条件eq(3-5)递归计算B样条系数 (递归结构如图4所示);
  2. 由eq(3-6)计算 ;
  3. Mn.png
代码
文本
  • 第二步计算实空间中电荷网格
  1. 通过Cox-de Boor formula eq(3-4)和初始条件eq(3-5)递归计算B样条系数 (递归结构如图5所示);
  2. 由eq(3-7)离散电荷到网格上得到

Mn.png

代码
文本
[ ]
// STEP 1: calculate Euler exponential spline B(m1,m2,m3)

std::vector<std::vector<std::vector<double>>>
B(K1, std::vector<std::vector<double>>
(K2, std::vector<double>
(K3,0)));

calc_Euler_spl_coeffs(n, K1, K2, K3, B);

---------------------------------------------------------------------------------

void calc_Euler_spl_coeffs(int n,
int K1,
int K2,
int K3,
std::vector<std::vector<std::vector<double>>>& B){

/*
n : Order of B-splines int
B : Coefficients in Euler exponential spline Size : (K1,K2,K3)
*/


//-----------------------------------------------
// calculate B-spline coeffs Mn(i) i = 1, 2, ..., n-1 see eq(3-4) and eq(3-5)

std::vector<double> Mn(n);
std::vector<double> Mnold(n, 0.e0);

Mnold[1] = 1.e0;

if ( n == 2 ){
for ( int u = 0; u < n; ++u ) { Mn[u] = Mnold[u]; }
}


for ( int nn = 3; nn <= n; ++nn ){

for ( int u = 1; u < n; ++u ){
Mn[u] = double(u)/(double(nn)-1.e0)*Mnold[u]
+ double(nn-u)/double(nn-1.e0)*Mnold[u-1];
}
Mn[0] = 0;

for ( int u = 0; u < n; ++u ){
Mnold[u] = Mn[u];
}

} // loop for nn

//--------------------------------------
// calculate b_1, b_2, b_3 see eq(3-6)

std::vector<std::complex<double>> b1(K1), b2(K2), b3(K3);
double expnum;
std::complex<double> ccc;

for ( int m1 = 0; m1 < K1; ++m1 ){
ccc = {0.e0,0.e0};

for ( int k = 0; k < n-1; ++k ){

expnum = 2.e0*M_PI*double(m1)*double(k)/double(K1);
ccc = ccc + Mn[k+1]*std::complex<double>{cos(expnum),sin(expnum)};
}

expnum = 2.e0*M_PI*double(n-1)*double(m1)/double(K1);
b1[m1] = std::complex<double>{cos(expnum), sin(expnum)}/ccc;
}


for ( int m2 = 0; m2 < K2; ++m2 ){
ccc = {0.e0,0.e0};

for ( int k = 0; k < n-1; ++k ){

expnum = 2.e0*M_PI*double(m2)*double(k)/double(K2);
ccc = ccc + Mn[k+1]*std::complex<double>{cos(expnum),sin(expnum)};
}

expnum = 2.e0*M_PI*double(n-1)*double(m2)/double(K2);
b2[m2] = std::complex<double>{cos(expnum), sin(expnum)}/ccc;
}


for ( int m3 = 0; m3 < K3; ++m3 ){
ccc = {0.e0,0.e0};

for ( int k = 0; k < n-1; ++k ){

expnum = 2.e0*M_PI*double(m3)*double(k)/double(K3);
ccc = ccc + Mn[k+1]*std::complex<double>{cos(expnum),sin(expnum)};
}

expnum = 2.e0*M_PI*double(n-1)*double(m3)/double(K3);
b3[m3] = std::complex<double>{cos(expnum), sin(expnum)}/ccc;
}

//------------------------------------------------------------
// calculate B(m1,m2, m3)

std::complex<double> B3, B2;

for ( int m3 = 0; m3 < K3; ++m3 ){
B3 = b3[m3]*conj(b3[m3]);

for ( int m2 = 0; m2 < K2; ++m2 ){
B2 = B3*b2[m2]*conj(b2[m2]);

for ( int m1 = 0; m1 < K1; ++m1 ){

B[m1][m2][m3] = ( B2*b1[m1]*conj(b1[m1]) ).real();
} } }

return;
}

代码
文本
[ ]
// STPE 2: calculate charge grid Q(k1,k2,k3)

std::vector<std::vector<std::vector<double>>>
Q(K1, std::vector<std::vector<double>>
(K2, std::vector<double>
(K3,0)));

calc_charge_grid(N, Lx, Ly, Lz, rX, rY, rZ, q, n, K1, K2, K3, Q);

---------------------------------------------------------------------------------------------

double round(double& x){ return (x>0.0)? floor(x+0.5):ceil(x-0.5); }

void calc_charge_grid(int N,
double Lx,
double Ly,
double Lz,
std::vector<double> rX,
std::vector<double> rY,
std::vector<double> rZ,
std::vector<double> q,
int n,
int K1,
int K2,
int K3,
std::vector<std::vector<std::vector<double>>>& Q){

/*

N : number of particles
Lx, Ly, lz : The side length of the Box
rX, rY, rZ : 3D coordinates of particles size : N
q : particle charge size : N
n : Order of B-splines
K1, K2, K3 : Number of 3D discrete grid points
Q : charge grid size : Q(K1, K2, K3)

*/

//--------------------------------------------
// fractional coordinates uX, uY, uZ


std::vector<double> uX(N), uY(N), uZ(N);

for ( int i = 0; i < N; ++i ){

double uu;
uu = rX[i] - round(rX[i]/Lx)*Lx; uX[i] = double(K1)*(uu/Lx+0.5e0);
uu = rY[i] - round(rY[i]/Ly)*Ly; uY[i] = double(K2)*(uu/Ly+0.5e0);
uu = rZ[i] - round(rZ[i]/Lz)*Lz; uZ[i] = double(K3)*(uu/Lz+0.5e0);
}

//--------------------------------------------
// calculate B-spline coeffs Mn(x+k) x = u(i) - int(u(i)) and k = 0, ... , n see eq(3-4) and eq(3-5)

std::vector<std::vector<double>> Mn1(N, std::vector<double>(n)),
Mn2(N, std::vector<double>(n)),
Mn3(N, std::vector<double>(n)),
Mn1old(N, std::vector<double>(n, 0.e0)),
Mn2old(N, std::vector<double>(n, 0.e0)),
Mn3old(N, std::vector<double>(n, 0.e0));

for ( int i = 0; i < N; ++i ){

Mn1old[i][0] = uX[i] - int(uX[i]);
Mn2old[i][0] = uY[i] - int(uY[i]);
Mn3old[i][0] = uZ[i] - int(uZ[i]);
Mn1old[i][1] = 1.e0 - uX[i] + int(uX[i]);
Mn2old[i][1] = 1.e0 - uY[i] + int(uY[i]);
Mn3old[i][1] = 1.e0 - uZ[i] + int(uZ[i]);
}


if ( n == 2 ){
for ( int u = 0; u < n; ++u ) {
for ( int i = 0; i < N; ++i ){

Mn1[i][u] = Mn1old[i][u];
Mn2[i][u] = Mn2old[i][u];
Mn3[i][u] = Mn3old[i][u];
} } }

double x1, x2, x3;

for ( int nn = 3; nn <= n; ++nn ){

for ( int i = 0; i < N; ++i ){
x1 = uX[i] - int(uX[i]);
x2 = uY[i] - int(uY[i]);
x3 = uZ[i] - int(uZ[i]);

for ( int u = 1; u < n; ++u ){

Mn1[i][u] = ( (x1+double(u))*Mn1old[i][u]
+ (double(nn)-(x1+double(u)))*Mn1old[i][u-1] )/double(nn-1);
Mn2[i][u] = ( (x2+double(u))*Mn2old[i][u]
+ (double(nn)-(x2+double(u)))*Mn2old[i][u-1] )/double(nn-1);
Mn3[i][u] = ( (x3+double(u))*Mn3old[i][u]
+ (double(nn)-(x3+double(u)))*Mn3old[i][u-1] )/double(nn-1);
}

Mn1[i][0] = x1/double(nn-1)*Mn1old[i][0];
Mn2[i][0] = x2/double(nn-1)*Mn2old[i][0];
Mn3[i][0] = x3/double(nn-1)*Mn3old[i][0];

for ( int u = 0; u < n; ++u ){

Mn1old[i][u] = Mn1[i][u];
Mn2old[i][u] = Mn2[i][u];
Mn3old[i][u] = Mn3[i][u];
}
} // loop for i
} // loop for nn

std::cout << Mn1[0][0] << std::endl;
//-------------------------------------------------------------------
// calculate charge grid Q(k1,k2,k3) see eq(3-7)

for ( int i = 0; i < N; ++i ){
int k1, k2, k3;
int iu1=int(uX[i]), iu2 = int(uY[i]), iu3 = int(uZ[i]);

for ( int j = 0; j < n; ++j ){
k3 = iu3-j; if( k3 >= K3 ){ k3 = 0; }
if( k3 < 0 ){ k3 = k3 + K3; }

for ( int k = 0; k < n; ++k){
k2 = iu2-k; if( k2 >= K2 ){ k2 = 0; }
if( k2 < 0 ){ k2 = k2 + K2; }

for ( int l = 0; l < n; ++l ){
k1 = iu1-l; if( k1 >= K1 ){ k1 = 0; }
if( k1 < 0 ){ k1 = k1 + K1; }

Q[k1][k2][k3] = Q[k1][k2][k3]
+ q[i]*Mn1[i][l]*Mn2[i][k]*Mn3[i][j];
} } }
}// loop for i


return;
}


代码
文本
  • 第三步对电荷网格做FFT变换

代码
文本
[ ]
// STEP 3: calculate F[Q](-m1,-m2,-m3) == FQ_naga

std::vector<std::vector<std::vector<std::complex<double>>>>
FQ_naga(Kmax1, std::vector<std::vector<std::complex<double>>>
(Kmax2, std::vector<std::complex<double>>
(Kmax3)));

bool is_negative = true;

FFT(Lx, Ly, Lz, K1, K2, K3, Q, FQ_naga, is_negative);

代码
文本
  • 第四步计算 coefficient , 三者乘积CBF
  1. 注意倒易空间具有截断.
代码
文本
[ ]
// STEP 4: calculate [exp(-pi^2*m^2/alpha^2)/m^2]*B(m1,m2,m3)*F[Q](-m1,-m2,-m3)

std::vector<std::vector<std::vector<std::complex<double>>>>
CBF(Kmax1, std::vector<std::vector<std::complex<double>>>
(Kmax2, std::vector<std::complex<double>>
(Kmax3)));

// reciprocal space truncation distance
double rcpcut, rcpcut2;
rcpcut =std::min( 0.5e0*{double(K1)/Lx, 0.5e0*double(K2)/Ly,0.5e0* double(K3)*Lz} );
rcpcut = 1.0005e0*rcpcut;
rcpcut2 = rcpcut*rcpcut;

double nega_pisq = -pow(M_PI,2.e0);
double alphasq = alpha*alpha;

double mX, mY, mZ, msq;

for ( int m3 = 0; m3 < K3; ++m3 ){
mm3 = m3; if ( m3 > K3/2 ){ mm3 = m3-K3; };
mZ = double(mm3)/Lz;

for ( int m2 = 0; m2 < K2; ++m2 ){
mm2 = m2; if ( m2 > K2/2 ){ mm2 = m2-K2; };
mY = double(mm2)/Ly;

for ( int m1 = 0; m1 < K1; ++m1 ){
mm1 = m1; if ( m1 > K1/2 ){ mm1 = m1-K1; };
mX = double(mm1)/Lz;

msq = mX*mX + mY*mY + mZ*mZ;

if ( msq > 1.e-6 && msq <= rcpcut2 ){

CBF[m1][m2][m3] = B[m1][m2][m3]*exp(nega_pisq*msq/alphasq)/msq*FQ_naga[m1][m2][m3];

}
else{

CBF[m1][m2][m3] = {0.e0, 0.e0};

} // if
} } }

代码
文本
  • 第五步计算第四步三者乘积CBF的Fouier变换

代码
文本
[ ]
// STEP 5; calculate F[CBF](k1,k2,k3)

std::vector<std::vector<std::vector<std::complex<double>>>>
theta_star_Q(Kmax1, std::vector<std::vector<std::complex<double>>>
(Kmax2, std::vector<std::complex<double>>
(Kmax3)));

is_negative = false;
FFT(Lx, Ly, Lz, K1, K2, K3, CBF, theta_star_Q, is_negative);

代码
文本
  • 第六步计算倒易空间能量项
  1. 计算的乘积;
  2. 由eq(3-9)计算乘积得到的数组之和,乘上系数与被reduce的得到
代码
文本
[ ]
// STEP 6; calculate E

// r4pie = 1/(4*pi*eps_0)
std::complex<double> Ecplx={0.e0,0.e0};

for ( int m1 = 0; m1 < K1; ++m1 ){
for ( int m2 = 0; m2 < K2; ++m2 ){
for ( int m3 = 0; m3 < K3; ++m3 ){
Ecplx = Ecplx + Q[m1][m2][m3]*theta_star_Q[m1][m2][m3];
} } }

double Ecoeffi = 1.e0/(2.e0*M_PI*Lx*Ly*Lz)*r4pie;
E = Ecplx.real()*Ecoeffi;

代码
文本

附录

代码
文本

倒易空间

  • 点阵

当在分子模拟中使用周期性边界条件时可视为晶体, 而晶体则可以抽象为点阵。额外需要说明的是由于分子模拟中Box里的粒子通常并不是直观有序的, 因此粒子并不需要一定处在晶格点上, 但粒子的排列必须满足晶格对称性(对于直观有序的如NaCl晶体,粒子可以不在晶格点上)。

  • 密勒指数

密勒指数就是取晶面族在坐标轴上的截距,截距的单位就是基向量大小,取倒数。 dianzhen.png

如图6所示,该族晶面在轴上的截距是,在轴上的截距是,同时与轴平行截距为, 因此是(230)晶面族。正点阵中晶面族的数目是无穷的。

  • 倒基矢

倒格基矢 的方向分别为(100)、(010)、(001)三个晶面族的法向量方向,大小是三个晶面族的间距的倒数乘以 。下面我们以 为例来推导倒基矢与正基矢的关系,其它方向上的倒基矢推导同理。

就是(100)晶面族的间距,它等与在该族晶面法向量上的投影,而叉乘就是该族晶面的法向量。因此就等于乘上单位法向量: 由上述表达式我们可得: 因此我们可以看到一些教材上是这样写的:

  • 倒易点阵

倒易点阵的一个格矢是和正点阵中的一族晶面相对应的, 又因为一个格点对应一个格矢, 因此可以说倒易点阵中一个点阵点对应正点阵中的一族晶面。

代码
文本

Fast Fourier Transform

按笔者的理解, FFT的核心就是利用复指数的特性eq(B1)再加上分而治之的思想(图7)。

FFT.png

根据分而治之的思想中可以获得两点知识, 一是说明为什么要离散为2的整数次幂, 二是FFT的复杂度中的 是以2为底, 这同样也是分而治之的层数。 受篇幅所限不再对FFT做过多介绍。

代码
文本
中文
#经典分子力学 # 力场
LaTex
从算法原理到代码实现
科普
力场
中文#经典分子力学 # 力场LaTex从算法原理到代码实现科普力场
已赞7
本文被以下合集收录
测试合集文章列表100篇
xingyanshi@dp.tech很长的名字xingyanshi@dp.tech很长的名字很长的
更新于 2024-08-04
104 篇2 人关注
理论
凝聚态平方
更新于 2024-03-17
12 篇0 人关注
推荐阅读
公开
周期性体系中静电相互作用的计算方法: Ewald Summation和Partical Mesh Ewald副本
CyrusZHOU
发布于 2024-03-16
公开
快速开始ABACUS | 计算铑(Rh)元素单质的晶体结构
中文notebookpythonABACUS分子动力学Tutorial
中文notebookpythonABACUS分子动力学Tutorial
donglikun@dp.tech
更新于 2024-07-16
评论
 * **第六步计算倒易空间能量项 $E^...

Linfeng Zhang

09-26 02:03
code不能跑?以及,是不是可以有些对\alpha 大小带来的影响之类的讨论?
评论