5. 简单实现 CEPA 方法#

创建时间:2021-09-04

本文档会简单地实现闭壳层 CEPA(\(n\)) 方法。

CEPA(\(n\)) 不太完整地说,是基于 CCSD 与 CISD 的近似。一般认为这类方法是严格的 CCSD 近似,对 MP2 甚至 MP3 有所提升;但依据推导方式不同,可以是 CISD 的近似,也可以是 CISD 的补充。CEPA(\(n\)) 方法的相对完善的综述可以参考 [1]

import numpy as np
from pyscf import gto, scf, ci, lib
from pyscf.cc import ccsd
from opt_einsum import contract as einsum

np.set_printoptions(6, suppress=True, linewidth=120)

5.1. CISD 能量#

5.1.1. 准备工作#

我们以键长 0.96 Angstrom、键角 104.5° 的水分子作为研究对象。基组是 cc-pVDZ。

mol = gto.Mole(atom="O; H 1 0.96; H 1 0.96 2 104.5", basis="cc-pVDZ", verbose=0).build()
mf_scf = scf.RHF(mol).run()
mf_ci = ci.CISD(mf_scf).run()

我们首先考察 CISD 能量的计算。回顾到 CISD 计算方程组是

\[ \begin{align*} (\hat H - E_\textsf{HF} - E_\mathrm{c}^\textsf{CISD}) | \Psi \rangle = 0 \end{align*} \]

其中,我们规定 \(|\Psi\rangle\) 是半归一化 (Intermediate Normalized) 的波函数:

\[ |\Psi\rangle = |\Phi_0\rangle + \sum_{ia} t_i^a |\Phi_i^a\rangle + \sum_{ijab} t_{ij}^{ab} |\Phi_{ij}^{ab}\rangle \]

\(|\Phi_0\rangle\) 是 HF 基态波函数,\(|\Phi_i^a\rangle\) 是单激发波函数、\(|\Phi_{ij}^{ab}\rangle\) 是双激发波函数。需要强调的是,由于是空间轨道,因此这些波函数并不反映物理实在,特别是我们无法考察自旋算符 \(\hat S{}^2\) 的本征态。它们只是用来计算能量的方便的记号。

若要针对一次激发、二次激发分别给出 CISD 方程,那么

\[\begin{split} \begin{align*} \langle \Phi_i^a | \hat H - E_\textsf{HF} - E_\mathrm{c}^\textsf{CISD} | \Psi \rangle &= 0 \\ \langle \Phi_{ij}^{ab} | \hat H - E_\textsf{HF} - E_\mathrm{c}^\textsf{CISD} | \Psi \rangle &= 0 \\ \end{align*} \end{split}\]

5.1.2. 激发系数#

通过 PySCF 的 ci attribute,进而通过 cisdvec_to_amplitudes 函数可以得到 \(|\Psi\rangle\) 的激发系数,但该激发系数并不是归一化的。我们手动将其进行半归一化,得到系数 t1 \(t_i^a\)t2 \(t_{ij}^{ab}\)

civec = mf_ci.ci / mf_ci.ci[0]
_, t1, t2 = mf_ci.cisdvec_to_amplitudes(civec)
t1.shape, t2.shape
((5, 19), (5, 5, 19, 19))

5.1.3. 能量表达式验证#

首先我们要给出电子积分。在有足够内存的情况下,可以用 ccsd_make_eris_incore 实现:

eris = ccsd._make_eris_incore(mf_ci, mf_scf.mo_coeff)

我们通篇文档不关心涉及 \(\hat H\) 的计算过程。我们暂时只需要其中的

\[ g_{ij}^{ab} = \langle ij | ab \rangle = (ia|jb) = \iint \phi_i(\boldsymbol{r}_1) \phi_a(\boldsymbol{r}_1) \frac{1}{|\boldsymbol{r}_1 - \boldsymbol{r}_2|} \phi_j(\boldsymbol{r}_2) \phi_b(\boldsymbol{r}_2) \, \mathrm{d} \boldsymbol{r}_1 \, \mathrm{d} \boldsymbol{r}_2 \]

该张量可以通过 eris.ovov 调出,维度是 \((i, a, j, b)\)

eris.ovov.shape
(5, 19, 5, 19)

对于半归一化方法,不论是 MP2, CEPA(\(n\)), CI(S)D 或 CC(S)D,下式在闭壳层下总是成立的:

\[ E_\mathrm{c} = \sum_{ijab} (2 t_{ij}^{ab} - t_{ij}^{ba}) g_{ij}^{ab} \]
2 * einsum("iajb, ijab ->", eris.ovov, t2) - einsum("iajb, ijba ->", eris.ovov, t2)
-0.2053384394211093

我们再次回顾 PySCF 计算得到的 CISD 相关能是下述非常接近的结果:

mf_ci.e_corr
-0.20533844297533488

5.2. CEPA(\(n\)) 计算#

5.2.1. CEPA(\(n\)) 原理#

CEPA(\(n\)) 方程组是

\[\begin{split} \begin{align*} \langle \Phi_{i}^{a} | \hat H - E_\textsf{HF} - B_{i} | \Psi \rangle &= 0 \\ \langle \Phi_{ij}^{ab} | \hat H - E_\textsf{HF} - A_{ij} | \Psi \rangle &= 0 \end{align*} \end{split}\]

其中,

\[\begin{split} \begin{align*} A_{ij} = \left\{ \begin{matrix} 0 & \textsf{CEPA(0)} \\ e_{ij} & \textsf{CEPA(2)} \\ \frac{1}{2} \sum_k (e_{ik} + e_{kj}) & \textsf{CEPA(1)} \\ \sum_k (e_{ik} + e_{kj}) - e_{ij} & \textsf{CEPA(3)} \\ E_\mathrm{c}^\textsf{CISD} = \sum_{kl} e_{kl} & \textsf{CISD} \end{matrix} \right. \end{align*} \end{split}\]
\[\begin{split} \begin{align*} B_{i} = \left\{ \begin{matrix} 0 & \textsf{CEPA(0)} \\ \textsf{NaN} & \textsf{CEPA(2)} \\ \sum_k e_{ik} & \textsf{CEPA(1)} \\ 2 \sum_k e_{ik} - e_{ii} & \textsf{CEPA(3)} \\ E_\mathrm{c}^\textsf{CISD} = \sum_{kl} e_{kl} & \textsf{CISD} \end{matrix} \right. \end{align*} \end{split}\]

对电子能定义为

\[ e_{ij} = \sum_{ab} (2 t_{ij}^{ab} - t_{ij}^{ba}) g_{ij}^{ab} \]
def pair_energy(eris_ovov, t2):
    return 2 * einsum("iajb, ijab -> ij", eris_ovov, t2) - einsum("iajb, ijba -> ij", eris_ovov, t2)

CEPA(2) 由于不确定其 \(B_i\) 的定义,因此这里不作实现。当然,CEPA(3) 的定义也可能存在疑问。

def cepa_shift(cepa_n, t1, t2, e_ij):
    e_i, e_j = e_ij.sum(axis=1), e_ij.sum(axis=0)
    A_ij, B_i = np.zeros_like(e_ij), np.zeros_like(e_i)
    if cepa_n == 0:
        pass
    elif cepa_n == 1:
        A_ij = 0.5 * (e_i[:, None] + e_j[None, :])
        B_i = e_i
    elif cepa_n == 3:
        A_ij = e_i[:, None] + e_j[None, :] - e_ij
        B_i = - e_ij.diagonal() + 2 * e_i
    else:
        raise ValueError("cepa_n value error for " + cepa_n)
    return A_ij, B_i

5.2.2. 迭代法求取 CISD 系数#

我们首先考虑二次激发系数 \(t_{ij}^{ab}\) 的求取。

\[ \langle \Phi_{ij}^{ab} | \hat H - E_\textsf{HF} | \Psi \rangle = A_{ij} \langle \Phi_{ij}^{ab} | \Psi \rangle = A_{ij} t_{ij}^{ab} \]

如果我们记等式左为 \(\mathscr{v}_{ij}^{ab}\),那么该式整理为

\[ \mathscr{v}_{ij}^{ab} = A_{ij} t_{ij}^{ab} \quad \Rightarrow \quad t_{ij}^{ab} = \frac{\mathscr{v}_{ij}^{ab}}{A_{ij}} \]

但需要注意,这不是一个好的激发系数 \(t_{ij}^{ab}\) 的更新策略。这里指出,由于 \(\langle \Phi_{ij}^{ab} | \hat H - E_\textsf{HF} | \Phi_0 \rangle = -D_{ij}^{ab}\)\(\mathscr{v}_{ij}^{ab}\) 的重要贡献项,因此不妨将上式写为

\[ \mathscr{v}_{ij}^{ab} - D_{ij}^{ab} t_{ij}^{ab} + D_{ij}^{ab} t_{ij}^{ab} = A_{ij} t_{ij}^{ab} \quad \Rightarrow \quad t_{ij}^{ab} = t_{ij}^{ab} + \frac{\mathscr{v}_{ij}^{ab} - A_{ij} t_{ij}^{ab}}{D_{ij}^{ab}} \]

上式是我们实际会使用到的激发系数 \(t_{ij}^{ab}\) 的更新策略。

在迭代计算激发系数时,我们需要给定初始激发系数。一般来说使用 MP2 激发系数即可:

\[ \tilde t_i^a = 0, \quad \tilde t_{ij}^{ab} = g_{ij}^{ab} / D_{ij}^{ab} \]

其中,d1 \(D_i^a = \varepsilon_i - \varepsilon_a\)d2 \(D_{ij}^{ab} = \varepsilon_i + \varepsilon_j - \varepsilon_a - \varepsilon_b\)

def corr_cepa(cepa_n):
    # Prepare D_i^a, D_ij^ab
    d0, d1, d2 = mf_ci.cisdvec_to_amplitudes(mf_ci.make_diagonal(eris))
    d1 = d0 - d1; d2 = d0 - d2
    # Prepare initial t_i^a, t_ij^ab
    t0, t1, t2 = 1, np.zeros_like(d1), eris.ovov.swapaxes(1, 2) / d2
    civec = mf_ci.amplitudes_to_cisdvec(t0, t1, t2)
    # Iteration
    for it in range(100):
        t1_old, t2_old = t1, t2
        _, v1, v2 = mf_ci.cisdvec_to_amplitudes(mf_ci.contract(civec, eris))
        e_ij = pair_energy(eris.ovov, t2)
        A_ij, B_i = cepa_shift(cepa_n, t1, t2, e_ij)
        t1 = t1_old + (v1 - B_i[:, None]           * t1_old) / d1
        t2 = t2_old + (v2 - A_ij[:, :, None, None] * t2_old) / d2
        e_corr = pair_energy(eris.ovov, t2).sum()
        civec = mf_ci.amplitudes_to_cisdvec(t0, t1, t2)
        if np.linalg.norm(t1 - t1_old) + np.linalg.norm(t2 - t2_old) < 1e-5:
            print("Total Iterations: ", it)
            print("Correlation Energy: ", e_corr)
            break
corr_cepa(0)
Total Iterations:  26
Correlation Energy:  -0.2167752177602909
corr_cepa(1)
Total Iterations:  38
Correlation Energy:  -0.21352333911480398
corr_cepa(3)
Total Iterations:  53
Correlation Energy:  -0.21129784897010107

5.3. 参考文献#