Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RI-RHF:极简演示

  • 程序:ri_rhf_slow.rs (gitee, github)
  • 实现内容:RI-RHF (restricted resolution-of-identity Hartree-Fock)
  • 不关注程序效率
  • 该程序总共约 50 行

在本节中,我们经常将密度矩阵 看作 长度的 1-D 向量,而非 的 2-D 矩阵。类似地,4c-2e 双电子积分经常看作是 的 2-D 矩阵,而非 的 4-D 张量。

目录

1. RI-RHF 回顾

相比于普通的 RHF,RI-RHF 方法不需要计算庞大的 4c-2e 双电子积分,而只需要计算较小的 3c-2e 双电子积分。同时,通过张量分解,其 Fock 矩阵的计算量明显比 RHF 方法小很多。

以 J 积分 为例,RI-V 近似下的 RI-RHF 的理解思路可以大致用下图表示:

RI-RHFJ 积分图示

1.1 4c-2e 双电子积分分解

RI-V 近似除了普通的分子轨道基组外,还需要设置辅助基组。辅助基数量越大、精度越高但计算消耗也越多。一般来说,辅助基组的数量 大约是 大小。从矩阵分解的角度来看,如果将 4c-2e 双电子积分看作 的矩阵,那么辅助基的大小 数量级上应该要接近 4c-2e 双电子积分的秩 (rank)。

3c-2e 双电子积分与 2c-2e 双电子积分定义如下:

随后对 2c-2e 双电子积分作 Cholesky 分解,分解得到的 是下三角矩阵:

考虑到 4c-2e 双电子积分在 RI-V 下的近似表达式为

定义 Cholesky 分解量 为 (在我们当前的项目中,需要注意其指标顺序与 3c-2e 双电子积分 不同)

那么 4c-2e 双电子积分的近似表达式也可以写为

1.2 J 积分

J 积分的计算近似为

如果用矩阵-向量乘法来表述,则是

1.3 K 积分

在 RI 近似下,K 积分的计算应代入分子轨道系数以提升计算效率。在闭壳层下,占据轨道总是双占据的。

当我们定义半转换 (half-transformed) 的

我们可以将 进一步写为

我们再定义记号 ,这两个张量完全等价。但当作矩阵看待时, 大小,而 大小。定义 的方便之处在于,K 积分可以用简单的矩阵乘法形式表达出来 (上标 half 是指该张量是经过半转换的):

2. 程序

2.1 4c-2e 双电子积分分解

在获得电子积分后,我们的主要目标是得到 。总共需要作两步计算:Cholesky 分解与矩阵求解:

在程序中的实现如下:

#![allow(unused)]
fn main() {
let int2c2e_l = rt::linalg::cholesky((int2c2e.view(), Lower));
let cderi = rt::linalg::solve_triangular((int2c2e_l.view(), int3c2e.reshape([nao * nao, naux]).t(), Lower));
let cderi = cderi.into_shape([naux, nao, nao]).into_contig(RowMajor);
}
  • 关于 Cholesky 分解,代码相信已经很直观了;
  • 关于矩阵求解,需要注意到这里的 是下三角矩阵;通过三角矩阵求解函数 solve_triangular 会比通常的矩阵求解函数 solve_general 或者求逆 inv 函数,性能上要快许多。
  • 最后,我们对 作维度修改 (从 的 2-D 矩阵修改到 的 3-D 张量),并从效率角度出发要求该张量需要是 row-major 的 (需要让辅助基指标 是最不连续的维度,这在 K 积分计算中是比较关键的)。

2.2 J 积分

J 积分的过程非常简单,它就是两次矩阵-向量乘法。但需要注意乘法的顺序,否则效率会非常糟糕:

#![allow(unused)]
fn main() {
let cderi_flat = cderi.reshape([naux, nao * nao]);
let dm_flat = dm.reshape([nao * nao]);
(cderi_flat.t() % (&cderi_flat % dm_flat)).into_shape([nao, nao])
}

上面的代码花了一些功夫在维度更改上。

2.3 K 积分

K 积分的计算是两次矩阵乘法:

#![allow(unused)]
fn main() {
let occ_coeff = mo_coeff.i((.., ..nocc));
let scr = (occ_coeff.t() % cderi.reshape([naux * nao, nao]).t()).into_shape([nocc, naux, nao]);
let scr_flat = scr.reshape([naux * nocc, nao]);
2.0_f64 * (scr_flat.t() % &scr_flat)
}

上述程序的

  • 第 1 行是从分子轨道系数中取出占据轨道的部分
  • 第 2 行是半转换得到
  • 第 3 行是重塑维度得到
  • 第 4 行是进行 的实际计算。

这里的程序实现与先前 RI-RHF 回顾中用到的公式还有些许差别。我们需要作一些额外的展开。

第 2 行中,我们实际执行的矩阵乘法是

该矩阵乘法对 重塑维度,并对参与乘法的两个矩阵都作转置,才能得到目标结果。这么做非常费劲,其目的是将半转换后指标 联系在一起,方便下面得到矩阵 时可以矩阵乘法一步到位。

由于 中,指标 是并在一起的;因此半转换得到的是 还是 并不重要,只要 联系在一起即可。