diff --git a/pyscf/cc/ccsd.py b/pyscf/cc/ccsd.py index f080a62ee9..9ece2a40a7 100644 --- a/pyscf/cc/ccsd.py +++ b/pyscf/cc/ccsd.py @@ -183,14 +183,33 @@ def load_ovvo(p0, p1): fov[:,p0:p1] -= numpy.einsum('kc,akic->ia', t1, eris_voov) tau = numpy.einsum('ia,jb->ijab', t1[:,p0:p1]*.5, t1) - tau += t2[:,:,p0:p1] + if mycc.dcsd: + # This adds mosaic terms "quadratic" in t2. They are multiplied + # by 0.5 in DCSD. + tau += 0.5*t2[:,:,p0:p1] + tau_t2 = 0.5*t2.copy()[:,:,p0:p1] + theta_t2 = tau_t2.copy().transpose(1,0,2,3) * 2 + theta_t2 -= tau_t2 + fvv_t2 = -lib.einsum('cjia,cjib->ab', theta_t2.transpose(2,1,0,3), eris_voov) + foo_t2 = lib.einsum('aikb,kjab->ij', eris_voov, theta_t2) + tau_t2 = theta_t2 = None + else: + tau += t2[:,:,p0:p1] theta = tau.transpose(1,0,2,3) * 2 theta -= tau fvv -= lib.einsum('cjia,cjib->ab', theta.transpose(2,1,0,3), eris_voov) foo += lib.einsum('aikb,kjab->ij', eris_voov, theta) tau = theta = None - tau = t2[:,:,p0:p1] + numpy.einsum('ia,jb->ijab', t1[:,p0:p1], t1) + if mycc.dcsd: + # The t2 is removed since it eventually is part of a ladder term + # "quadratic" in t2, not used in DCSD. The other terms the missing + # t2 otherwise is part of have to be added later. + # The t2 part is split of woooo and is now in woooo_t2. + tau = numpy.einsum('ia,jb->ijab', t1[:,p0:p1], t1) + woooo_t2 = lib.einsum('ijab,aklb->ijkl', t2[:,:,p0:p1], eris_voov) + else: + tau = t2[:,:,p0:p1] + numpy.einsum('ia,jb->ijab', t1[:,p0:p1], t1) woooo += lib.einsum('ijab,aklb->ijkl', tau, eris_voov) tau = None @@ -198,8 +217,11 @@ def update_wVooV(q0, q1, tau): wVooV[:] += lib.einsum('bkic,jkca->bija', eris_voov[:,:,:,q0:q1], tau) with lib.call_in_background(update_wVooV, sync=not mycc.async_io) as update_wVooV: for q0, q1 in lib.prange(0, nvir, blksize): - tau = t2[:,:,q0:q1] * .5 - tau += numpy.einsum('ia,jb->ijab', t1[:,q0:q1], t1) + tau = numpy.einsum('ia,jb->ijab', t1[:,q0:q1], t1) + if not mycc.dcsd: + # This adds xring and some more "exchange-like" ring terms + # "quadratic" in t2. They are not used in DCSD. + tau += t2[:,:,q0:q1] * .5 #:wVooV += lib.einsum('bkic,jkca->bija', eris_voov[:,:,:,q0:q1], tau) update_wVooV(q0, q1, tau) tau = update_wVooV = None @@ -217,20 +239,33 @@ def update_t2(q0, q1, tmp): tmp = None wVOov += eris_voov - eris_VOov = -.5 * eris_voov.transpose(0,2,1,3) - eris_VOov += eris_voov - eris_voov = None - def update_wVOov(q0, q1, tau): - wVOov[:,:,:,q0:q1] += .5 * lib.einsum('aikc,kcjb->aijb', eris_VOov, tau) + eris_VOov = -.5 * eris_voov.copy().transpose(0,2,1,3) + if not mycc.dcsd: + eris_VOov += eris_voov + eris_voov = None + def update_wVOov(q0, q1, tau): + wVOov[:,:,:,q0:q1] += .5 * lib.einsum('aikc,kcjb->aijb', eris_VOov, tau) + else: + def update_wVOov(q0, q1, tau, tau_not2): + wVOov[:,:,:,q0:q1] += .5 * lib.einsum('aikc,kcjb->aijb', eris_voov, tau) + wVOov[:,:,:,q0:q1] += .5 * lib.einsum('aikc,kcjb->aijb', eris_VOov, tau_not2) with lib.call_in_background(update_wVOov, sync=not mycc.async_io) as update_wVOov: for q0, q1 in lib.prange(0, nvir, blksize): tau = t2[:,:,q0:q1].transpose(1,3,0,2) * 2 tau -= t2[:,:,q0:q1].transpose(0,3,1,2) tau -= numpy.einsum('ia,jb->ibja', t1[:,q0:q1]*2, t1) - #:wVOov[:,:,:,q0:q1] += .5 * lib.einsum('aikc,kcjb->aijb', eris_VOov, tau) - update_wVOov(q0, q1, tau) - tau = None + if mycc.dcsd: + tau_not2 = -numpy.einsum('ia,jb->ibja', t1[:,q0:q1]*2, t1) + update_wVOov(q0, q1, tau, tau_not2) + tau = tau_not2 = None + else: + #:wVOov[:,:,:,q0:q1] += .5 * lib.einsum('aikc,kcjb->aijb', eris_VOov, tau) + update_wVOov(q0, q1, tau) + tau = None + if mycc.dcsd: + eris_voov = None def update_t2(q0, q1, theta): + # This adds ring terms "quadratic" in t2. They are used by DCSD. t2new[:,:,q0:q1] += lib.einsum('kica,ckjb->ijab', theta, wVOov) with lib.call_in_background(update_t2, sync=not mycc.async_io) as update_t2: for q0, q1 in lib.prange(0, nvir, blksize): @@ -249,6 +284,12 @@ def update_t2(q0, q1, theta): t1new -= lib.einsum('jbki,kjba->ia', eris.ovoo[:,p0:p1], theta) tau = numpy.einsum('ia,jb->ijab', t1[:,p0:p1], t1) + if mycc.dcsd: + # For DCSD, woooo_t2 contains the t2 term in woooo (which was split + # off woooo in DCSD). This t2 term, now in woooo_t2, should not + # be multiplied with another t2 containing term, as ladder terms are + # not present in DCSD. + t2new[:,:,p0:p1] += .5 * lib.einsum('ijkl,klab->ijab', woooo_t2, tau) tau += t2[:,:,p0:p1] t2new[:,:,p0:p1] += .5 * lib.einsum('ijkl,klab->ijab', woooo, tau) theta = tau = None @@ -259,6 +300,9 @@ def update_t2(q0, q1, theta): t2new -= lib.einsum('ki,kjab->ijab', ft_ij, t2) eia = mo_e_o[:,None] - mo_e_v + if mycc.dcsd: + fvv += fvv_t2 + foo += foo_t2 t1new += numpy.einsum('ib,ab->ia', t1, fvv) t1new -= numpy.einsum('ja,ji->ia', t1, foo) t1new /= eia @@ -981,6 +1025,10 @@ def ecc(self): def e_tot(self): return self.e_hf + self.e_corr + @property + def dcsd(self): + return self.__class__.__name__[-4:] == "DCSD" + @property def nocc(self): return self.get_nocc() diff --git a/pyscf/cc/dcsd.py b/pyscf/cc/dcsd.py new file mode 100644 index 0000000000..027cb5d0e5 --- /dev/null +++ b/pyscf/cc/dcsd.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# Copyright 2014-2020 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +Distinguishable (restricted, more optimised) coupled cluster singles doubles +''' + +from pyscf.cc import ccsd + +class DCSD(ccsd.CCSD): + def dcsd(self, t1=None, t2=None, eris=None): + return self.ccsd(t1, t2, eris) diff --git a/pyscf/cc/rccsd.py b/pyscf/cc/rccsd.py index f7ed87f980..e7fdef8346 100644 --- a/pyscf/cc/rccsd.py +++ b/pyscf/cc/rccsd.py @@ -110,18 +110,26 @@ def update_amps(cc, t1, t2, eris): tmp = lib.einsum('ki,kjab->ijab', Loo2, t2) t2new -= (tmp + tmp.transpose(1,0,3,2)) else: - Loo = imd.Loo(t1, t2, eris) - Lvv = imd.Lvv(t1, t2, eris) + Loo = imd.Loo(t1, t2, eris, cc.dcsd) + Lvv = imd.Lvv(t1, t2, eris, cc.dcsd) Loo[np.diag_indices(nocc)] -= mo_e_o Lvv[np.diag_indices(nvir)] -= mo_e_v - Woooo = imd.cc_Woooo(t1, t2, eris) - Wvoov = imd.cc_Wvoov(t1, t2, eris) - Wvovo = imd.cc_Wvovo(t1, t2, eris) + Woooo = imd.cc_Woooo(t1, t2, eris, cc.dcsd) + if cc.dcsd: + Woooo_witht2 = imd.cc_Woooo(t1, t2, eris, False) + Wvoov = imd.cc_Wvoov(t1, t2, eris, cc.dcsd) + Wvovo = imd.cc_Wvovo(t1, t2, eris, cc.dcsd) Wvvvv = imd.cc_Wvvvv(t1, t2, eris) - tau = t2 + np.einsum('ia,jb->ijab', t1, t1) - t2new += lib.einsum('klij,klab->ijab', Woooo, tau) + if not cc.dcsd: + tau = t2 + np.einsum('ia,jb->ijab', t1, t1) + t2new += lib.einsum('klij,klab->ijab', Woooo, tau) + else: + t2new += lib.einsum('klij,klab->ijab', Woooo, t2) + tau = np.einsum('ia,jb->ijab', t1, t1) + t2new += lib.einsum('klij,klab->ijab', Woooo_witht2, tau) + tau += t2 t2new += lib.einsum('abcd,ijcd->ijab', Wvvvv, tau) tmp = lib.einsum('ac,ijcb->ijab', Lvv, t2) t2new += (tmp + tmp.transpose(1,0,3,2)) @@ -177,6 +185,8 @@ def ccsd(self, t1=None, t2=None, eris=None, mbpt2=False): mbpt2 : bool Use one-shot MBPT2 approximation to CCSD. ''' + if mbpt2 and self.dcsd: + raise RuntimeError('MBPT2 and DCSD are mutually exclusive approximations.') if mbpt2: pt = mp2.MP2(self._scf, self.frozen, self.mo_coeff, self.mo_occ) self.e_corr, self.t2 = pt.kernel(eris=eris) diff --git a/pyscf/cc/rccsd_slow.py b/pyscf/cc/rccsd_slow.py index 5821212ab1..c2697ee34f 100644 --- a/pyscf/cc/rccsd_slow.py +++ b/pyscf/cc/rccsd_slow.py @@ -94,27 +94,35 @@ def update_amps(cc, t1, t2, eris): tmp = einsum('ki,kjab->ijab', Loo2, t2) t2new -= (tmp + tmp.transpose(1,0,3,2)) else: - Loo = imd.Loo(t1, t2, eris) - Lvv = imd.Lvv(t1, t2, eris) + Loo = imd.Loo(t1, t2, eris, cc.dcsd) + Lvv = imd.Lvv(t1, t2, eris, cc.dcsd) Loo -= np.diag(np.diag(foo)) Lvv -= np.diag(np.diag(fvv)) - Woooo = imd.cc_Woooo(t1, t2, eris) - Wvoov = imd.cc_Wvoov(t1, t2, eris) - Wvovo = imd.cc_Wvovo(t1, t2, eris) + Woooo = imd.cc_Woooo(t1, t2, eris, cc.dcsd) + if cc.dcsd: + Woooo_witht2 = imd.cc_Woooo(t1, t2, eris, False) + Wvoov = imd.cc_Wvoov(t1, t2, eris, cc.dcsd) + Wvovo = imd.cc_Wvovo(t1, t2, eris, cc.dcsd) Wvvvv = imd.cc_Wvvvv(t1, t2, eris) - tau = t2 + einsum('ia,jb->ijab', t1, t1) - t2new += einsum('klij,klab->ijab', Woooo, tau) + if not cc.dcsd: + tau = t2 + np.einsum('ia,jb->ijab', t1, t1) + t2new += lib.einsum('klij,klab->ijab', Woooo, tau) # contains t2 t2 ladder, removed for dcsd + else: + t2new += lib.einsum('klij,klab->ijab', Woooo, t2) # contains t2 t2 ladder, removed for dcsd + tau = np.einsum('ia,jb->ijab', t1, t1) + t2new += lib.einsum('klij,klab->ijab', Woooo_witht2, tau) + tau += t2 t2new += einsum('abcd,ijcd->ijab', Wvvvv, tau) - tmp = einsum('ac,ijcb->ijab', Lvv, t2) + tmp = einsum('ac,ijcb->ijab', Lvv, t2) # contains t2 t2 mosaic, multiplied by 0.5 t2new += (tmp + tmp.transpose(1,0,3,2)) - tmp = einsum('ki,kjab->ijab', Loo, t2) + tmp = einsum('ki,kjab->ijab', Loo, t2) # contains t2 t2 mosaic, multiplied by 0.5 t2new -= (tmp + tmp.transpose(1,0,3,2)) - tmp = 2*einsum('akic,kjcb->ijab', Wvoov, t2) - tmp -= einsum('akci,kjcb->ijab', Wvovo, t2) + tmp = 2*einsum('akic,kjcb->ijab', Wvoov, t2) # contains t2 t2 ring, removed partly + tmp -= einsum('akci,kjcb->ijab', Wvovo, t2) # contains t2 t2 ring, t_il^da t_kj^cb, removed for dcsd t2new += (tmp + tmp.transpose(1,0,3,2)) - tmp = einsum('akic,kjbc->ijab', Wvoov, t2) + tmp = einsum('akic,kjbc->ijab', Wvoov, t2) # contains t2 t2 ring, removed partly t2new -= (tmp + tmp.transpose(1,0,3,2)) - tmp = einsum('bkci,kjac->ijab', Wvovo, t2) + tmp = einsum('bkci,kjac->ijab', Wvovo, t2) # contains t2 t2 crossring, removed for dcsd t2new -= (tmp + tmp.transpose(1,0,3,2)) tmp2 = einsum('kibc,ka->abic', eris.oovv, -t1) @@ -184,6 +192,10 @@ def ccsd(self, t1=None, t2=None, eris=None, mbpt2=False, cc2=False): ''' if mbpt2 and cc2: raise RuntimeError('MBPT2 and CC2 are mutually exclusive approximations to the CCSD ground state.') + if mbpt2 and self.dcsd: + raise RuntimeError('MBPT2 and DCSD are mutually exclusive approximations.') + if cc2 and self.dcsd: + raise RuntimeError('MBPT2 and DCSD are mutually exclusive approximations.') if eris is None: eris = self.ao2mo(self.mo_coeff) self.e_hf = self.get_e_hf() self.eris = eris @@ -195,6 +207,9 @@ def ccsd(self, t1=None, t2=None, eris=None, mbpt2=False, cc2=False): if cc2: cctyp = 'CC2' self.cc2 = True + elif self.dcsd: + cctyp = 'DCSD' + self.cc2 = False else: cctyp = 'CCSD' self.cc2 = False diff --git a/pyscf/cc/rdcsd.py b/pyscf/cc/rdcsd.py new file mode 100644 index 0000000000..67974479e6 --- /dev/null +++ b/pyscf/cc/rdcsd.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# Copyright 2014-2020 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +Distinguishable restricted coupled cluster singles doubles +''' + +from pyscf.cc import rccsd + +class RDCSD(rccsd.RCCSD): + def dcsd(self, t1=None, t2=None, eris=None): + return self.ccsd(t1=t1, t2=t2, eris=eris) diff --git a/pyscf/cc/rintermediates.py b/pyscf/cc/rintermediates.py index ebdb4d7460..d6136f2935 100644 --- a/pyscf/cc/rintermediates.py +++ b/pyscf/cc/rintermediates.py @@ -27,23 +27,27 @@ ### Eqs. (37)-(39) "kappa" -def cc_Foo(t1, t2, eris): +def cc_Foo(t1, t2, eris, dcsd=False): nocc, nvir = t1.shape foo = eris.fock[:nocc,:nocc] eris_ovov = np.asarray(eris.ovov) Fki = 2*lib.einsum('kcld,ilcd->ki', eris_ovov, t2) Fki -= lib.einsum('kdlc,ilcd->ki', eris_ovov, t2) + if dcsd: + Fki *= 0.5 Fki += 2*lib.einsum('kcld,ic,ld->ki', eris_ovov, t1, t1) Fki -= lib.einsum('kdlc,ic,ld->ki', eris_ovov, t1, t1) Fki += foo return Fki -def cc_Fvv(t1, t2, eris): +def cc_Fvv(t1, t2, eris, dcsd=False): nocc, nvir = t1.shape fvv = eris.fock[nocc:,nocc:] eris_ovov = np.asarray(eris.ovov) Fac =-2*lib.einsum('kcld,klad->ac', eris_ovov, t2) Fac += lib.einsum('kdlc,klad->ac', eris_ovov, t2) + if dcsd: + Fac *= 0.5 Fac -= 2*lib.einsum('kcld,ka,ld->ac', eris_ovov, t1, t1) Fac += lib.einsum('kdlc,ka,ld->ac', eris_ovov, t1, t1) Fac += fvv @@ -60,19 +64,19 @@ def cc_Fov(t1, t2, eris): ### Eqs. (40)-(41) "lambda" -def Loo(t1, t2, eris): +def Loo(t1, t2, eris, dcsd=False): nocc, nvir = t1.shape fov = eris.fock[:nocc,nocc:] - Lki = cc_Foo(t1, t2, eris) + np.einsum('kc,ic->ki',fov, t1) + Lki = cc_Foo(t1, t2, eris, dcsd) + np.einsum('kc,ic->ki',fov, t1) eris_ovoo = np.asarray(eris.ovoo) Lki += 2*np.einsum('lcki,lc->ki', eris_ovoo, t1) Lki -= np.einsum('kcli,lc->ki', eris_ovoo, t1) return Lki -def Lvv(t1, t2, eris): +def Lvv(t1, t2, eris, dcsd=False): nocc, nvir = t1.shape fov = eris.fock[:nocc,nocc:] - Lac = cc_Fvv(t1, t2, eris) - np.einsum('kc,ka->ac',fov, t1) + Lac = cc_Fvv(t1, t2, eris, dcsd) - np.einsum('kc,ka->ac',fov, t1) eris_ovvv = np.asarray(eris.get_ovvv()) Lac += 2*np.einsum('kdac,kd->ac', eris_ovvv, t1) Lac -= np.einsum('kcad,kd->ac', eris_ovvv, t1) @@ -80,12 +84,13 @@ def Lvv(t1, t2, eris): ### Eqs. (42)-(45) "chi" -def cc_Woooo(t1, t2, eris): +def cc_Woooo(t1, t2, eris, dcsd=False): eris_ovoo = np.asarray(eris.ovoo) Wklij = lib.einsum('lcki,jc->klij', eris_ovoo, t1) Wklij += lib.einsum('kclj,ic->klij', eris_ovoo, t1) eris_ovov = np.asarray(eris.ovov) - Wklij += lib.einsum('kcld,ijcd->klij', eris_ovov, t2) + if not dcsd: + Wklij += lib.einsum('kcld,ijcd->klij', eris_ovov, t2) Wklij += lib.einsum('kcld,ic,jd->klij', eris_ovov, t1, t1) Wklij += np.asarray(eris.oooo).transpose(0,2,1,3) return Wklij @@ -98,7 +103,7 @@ def cc_Wvvvv(t1, t2, eris): Wabcd += np.asarray(_get_vvvv(eris)).transpose(0,2,1,3) return Wabcd -def cc_Wvoov(t1, t2, eris): +def cc_Wvoov(t1, t2, eris, dcsd=False): eris_ovvv = np.asarray(eris.get_ovvv()) eris_ovoo = np.asarray(eris.ovoo) Wakic = lib.einsum('kcad,id->akic', eris_ovvv, t1) @@ -106,19 +111,21 @@ def cc_Wvoov(t1, t2, eris): Wakic += np.asarray(eris.ovvo).transpose(2,0,3,1) eris_ovov = np.asarray(eris.ovov) Wakic -= 0.5*lib.einsum('ldkc,ilda->akic', eris_ovov, t2) - Wakic -= 0.5*lib.einsum('lckd,ilad->akic', eris_ovov, t2) + if not dcsd: + Wakic -= 0.5*lib.einsum('lckd,ilad->akic', eris_ovov, t2) Wakic -= lib.einsum('ldkc,id,la->akic', eris_ovov, t1, t1) Wakic += lib.einsum('ldkc,ilad->akic', eris_ovov, t2) return Wakic -def cc_Wvovo(t1, t2, eris): +def cc_Wvovo(t1, t2, eris, dcsd=False): eris_ovvv = np.asarray(eris.get_ovvv()) eris_ovoo = np.asarray(eris.ovoo) Wakci = lib.einsum('kdac,id->akci', eris_ovvv, t1) Wakci -= lib.einsum('lcki,la->akci', eris_ovoo, t1) Wakci += np.asarray(eris.oovv).transpose(2,0,3,1) eris_ovov = np.asarray(eris.ovov) - Wakci -= 0.5*lib.einsum('lckd,ilda->akci', eris_ovov, t2) + if not dcsd: + Wakci -= 0.5*lib.einsum('lckd,ilda->akci', eris_ovov, t2) Wakci -= lib.einsum('lckd,id,la->akci', eris_ovov, t1, t1) return Wakci diff --git a/pyscf/cc/test/test_rccsd.py b/pyscf/cc/test/test_rccsd.py index 6c1ebe3037..3736c7e5e6 100644 --- a/pyscf/cc/test/test_rccsd.py +++ b/pyscf/cc/test/test_rccsd.py @@ -27,8 +27,8 @@ from pyscf import cc from pyscf import ao2mo from pyscf import mp -from pyscf.cc import ccsd -from pyscf.cc import rccsd +from pyscf.cc import ccsd, dcsd +from pyscf.cc import rccsd, rdcsd def setUpModule(): global mol, mf, eris, mycc @@ -59,6 +59,18 @@ def tearDownModule(): class KnownValues(unittest.TestCase): + def test_dcsd(self): + mf = scf.RHF(mol).run() + mycc = dcsd.DCSD(mf) + mycc.kernel() + self.assertAlmostEqual(mycc.e_tot, -76.12243069638626, 7) + + def test_rdcsd(self): + mf = scf.RHF(mol).run() + mycc = rdcsd.RDCSD(mf) + mycc.kernel() + self.assertAlmostEqual(mycc.e_tot, -76.12243069638626, 7) + def test_roccsd(self): mf = scf.ROHF(mol).run() mycc = cc.RCCSD(mf).run() diff --git a/pyscf/cc/test/test_uccsd.py b/pyscf/cc/test/test_uccsd.py index 52a26c2c45..4c3e5d49f3 100644 --- a/pyscf/cc/test/test_uccsd.py +++ b/pyscf/cc/test/test_uccsd.py @@ -24,7 +24,7 @@ from pyscf import mp from pyscf import cc from pyscf import ao2mo -from pyscf.cc import uccsd +from pyscf.cc import uccsd, udcsd from pyscf.cc import gccsd from pyscf.cc import addons from pyscf.cc import uccsd_rdm @@ -70,6 +70,12 @@ def tearDownModule(): class KnownValues(unittest.TestCase): + def test_udcsd(self): + mf = scf.UHF(mol).run() + mycc = udcsd.UDCSD(mf) + mycc.kernel() + self.assertAlmostEqual(mycc.e_tot, -76.12243069638626, 7) + def test_with_df_s0(self): mf = scf.UHF(mol).density_fit(auxbasis='weigend').run() mycc = cc.UCCSD(mf).run() diff --git a/pyscf/cc/uccsd.py b/pyscf/cc/uccsd.py index 115ffec35d..d3cd9535cd 100644 --- a/pyscf/cc/uccsd.py +++ b/pyscf/cc/uccsd.py @@ -60,6 +60,8 @@ def update_amps(cc, t1, t2, eris): #:u2bb += lib.einsum('ijef,aebf->ijab', taubb, eris_VVVV) * .5 #:u2ab += lib.einsum('iJeF,aeBF->iJaB', tauab, eris_vvVV) tauaa, tauab, taubb = make_tau(t2, t1, t1) + if cc.dcsd: + tauaa_not2, tauab_not2, taubb_not2 = make_tau(t2, t1, t1, fac_t2=0) u2aa, u2ab, u2bb = cc._add_vvvv(None, (tauaa,tauab,taubb), eris) u2aa *= .5 u2bb *= .5 @@ -139,8 +141,17 @@ def update_amps(cc, t1, t2, eris): Woooo = lib.einsum('je,nemi->mnij', t1a, eris_ovoo) Woooo = Woooo - Woooo.transpose(0,1,3,2) Woooo += np.asarray(eris.oooo).transpose(0,2,1,3) - Woooo += lib.einsum('ijef,menf->mnij', tauaa, eris_ovov) * .5 - u2aa += lib.einsum('mnab,mnij->ijab', tauaa, Woooo*.5) + if cc.dcsd: + # Includes t2 t2 ladder terms. Removed for DCSD. + Woooo += lib.einsum('ijef,menf->mnij', tauaa_not2, eris_ovov) * .5 + u2aa += lib.einsum('mnab,mnij->ijab', tauaa, Woooo*.5) + Woooo = lib.einsum('ijef,menf->mnij', t2[0], eris_ovov) * .5 + u2aa += lib.einsum('mnab,mnij->ijab', tauaa_not2, Woooo*.5) + tauaa_not2 = None + else: + # Includes t2 t2 ladder terms. Removed for DCSD. + Woooo += lib.einsum('ijef,menf->mnij', tauaa, eris_ovov) * .5 + u2aa += lib.einsum('mnab,mnij->ijab', tauaa, Woooo*.5) Woooo = tauaa = None ovoo = eris_ovoo - eris_ovoo.transpose(2,1,0,3) Fooa += np.einsum('ne,nemi->mi', t1a, ovoo) @@ -148,14 +159,24 @@ def update_amps(cc, t1, t2, eris): wovvo += lib.einsum('nb,nemj->mbej', t1a, ovoo) ovoo = eris_ovoo = None - tilaa = make_tau_aa(t2[0], t1a, t1a, fac=0.5) + fac_t2 = 0.5 if cc.dcsd else 1 + tilaa = make_tau_aa(t2[0], t1a, t1a, fac=0.5, fac_t2=fac_t2) ovov = eris_ovov - eris_ovov.transpose(0,3,2,1) - Fvva -= .5 * lib.einsum('mnaf,menf->ae', tilaa, ovov) - Fooa += .5 * lib.einsum('inef,menf->mi', tilaa, ovov) + Fvva -= .5 * lib.einsum('mnaf,menf->ae', tilaa, ovov) # Mosaic t2 t2 terms coming + Fooa += .5 * lib.einsum('inef,menf->mi', tilaa, ovov) # Mosaic t2 t2 terms coming + if cc.dcsd: + # Removed 0.5 t2 contribution in Fvva/Fooa, now saved in Fvva_t2/Fooa_t2. + Fvva_t2 = -0.25 * lib.einsum('mnaf,menf->ae', t2[0], ovov) # Mosaic t2 t2 terms coming + Fooa_t2 = .25 * lib.einsum('inef,menf->mi', t2[0], ovov) # Mosaic t2 t2 terms coming + Fova = np.einsum('nf,menf->me',t1a, ovov) u2aa += ovov.conj().transpose(0,2,1,3) * .5 - wovvo -= 0.5*lib.einsum('jnfb,menf->mbej', t2aa, ovov) - woVvO += 0.5*lib.einsum('nJfB,menf->mBeJ', t2ab, ovov) + if cc.dcsd: + wovvo -= 0.5*lib.einsum('jnfb,menf->mbej', t2aa, eris_ovov) # Part of ring terms coming + woVvO += 0.5*lib.einsum('nJfB,menf->mBeJ', t2ab, eris_ovov) # Part of ring terms coming + else: + wovvo -= 0.5*lib.einsum('jnfb,menf->mbej', t2aa, ovov) # Part of ring and "exchange like" ring terms coming + woVvO += 0.5*lib.einsum('nJfB,menf->mBeJ', t2ab, ovov) # Part of ring and "exchange like" ring terms coming tmpaa = lib.einsum('jf,menf->mnej', t1a, ovov) wovvo -= lib.einsum('nb,mnej->mbej', t1a, tmpaa) eris_ovov = ovov = tmpaa = tilaa = None @@ -165,8 +186,16 @@ def update_amps(cc, t1, t2, eris): WOOOO = lib.einsum('je,nemi->mnij', t1b, eris_OVOO) WOOOO = WOOOO - WOOOO.transpose(0,1,3,2) WOOOO += np.asarray(eris.OOOO).transpose(0,2,1,3) - WOOOO += lib.einsum('ijef,menf->mnij', taubb, eris_OVOV) * .5 - u2bb += lib.einsum('mnab,mnij->ijab', taubb, WOOOO*.5) + if cc.dcsd: + # Includes t2 t2 ladder terms. Removed for DCSD. + WOOOO += lib.einsum('ijef,menf->mnij', taubb_not2, eris_OVOV) * .5 + u2bb += lib.einsum('mnab,mnij->ijab', taubb, WOOOO*.5) + WOOOO = lib.einsum('ijef,menf->mnij', t2[2], eris_OVOV) * .5 + u2bb += lib.einsum('mnab,mnij->ijab', taubb_not2, WOOOO*.5) + taubb_not2 = None + else: + WOOOO += lib.einsum('ijef,menf->mnij', taubb, eris_OVOV) * .5 + u2bb += lib.einsum('mnab,mnij->ijab', taubb, WOOOO*.5) WOOOO = taubb = None OVOO = eris_OVOO - eris_OVOO.transpose(2,1,0,3) Foob += np.einsum('ne,nemi->mi', t1b, OVOO) @@ -174,14 +203,24 @@ def update_amps(cc, t1, t2, eris): wOVVO += lib.einsum('nb,nemj->mbej', t1b, OVOO) OVOO = eris_OVOO = None - tilbb = make_tau_aa(t2[2], t1b, t1b, fac=0.5) + fac_t2 = 0.5 if cc.dcsd else 1 + tilbb = make_tau_aa(t2[2], t1b, t1b, fac=0.5, fac_t2=fac_t2) OVOV = eris_OVOV - eris_OVOV.transpose(0,3,2,1) - Fvvb -= .5 * lib.einsum('MNAF,MENF->AE', tilbb, OVOV) - Foob += .5 * lib.einsum('inef,menf->mi', tilbb, OVOV) + Fvvb -= .5 * lib.einsum('MNAF,MENF->AE', tilbb, OVOV) # Mosaic t2 t2 terms coming + Foob += .5 * lib.einsum('inef,menf->mi', tilbb, OVOV) # Mosaic t2 t2 terms coming + if cc.dcsd: + # Removed 0.5 t2 contribution in Fvvb/Foob, now saved in Fvvb_t2/Foob_t2. + Fvvb_t2 = -0.25 * lib.einsum('MNAF,MENF->AE', t2[2], OVOV) # Mosaic t2 t2 terms coming + Foob_t2 = .25 * lib.einsum('inef,menf->mi', t2[2], OVOV) # Mosaic t2 t2 terms coming + Fovb = np.einsum('nf,menf->me',t1b, OVOV) u2bb += OVOV.conj().transpose(0,2,1,3) * .5 - wOVVO -= 0.5*lib.einsum('jnfb,menf->mbej', t2bb, OVOV) - wOvVo += 0.5*lib.einsum('jNbF,MENF->MbEj', t2ab, OVOV) + if cc.dcsd: + wOVVO -= 0.5*lib.einsum('jnfb,menf->mbej', t2bb, eris_OVOV) # Ring t2 t2 ring terms coming. + wOvVo += 0.5*lib.einsum('jNbF,MENF->MbEj', t2ab, eris_OVOV) # Ring t2 t2 ring terms coming. + else: + wOVVO -= 0.5*lib.einsum('jnfb,menf->mbej', t2bb, OVOV) # Ring and "ex-like" ring t2 t2 ring terms coming. + wOvVo += 0.5*lib.einsum('jNbF,MENF->MbEj', t2ab, OVOV) # Ring and "ex-like" ring t2 t2 ring terms coming. tmpbb = lib.einsum('jf,menf->mnej', t1b, OVOV) wOVVO -= lib.einsum('nb,mnej->mbej', t1b, tmpbb) eris_OVOV = OVOV = tmpbb = tilbb = None @@ -202,24 +241,41 @@ def update_amps(cc, t1, t2, eris): eris_OVoo = eris_ovOO = None eris_ovOV = np.asarray(eris.ovOV) - WoOoO += lib.einsum('iJeF,meNF->mNiJ', tauab, eris_ovOV) - u2ab += lib.einsum('mNaB,mNiJ->iJaB', tauab, WoOoO) + if cc.dcsd: + # Includes t2 t2 ladder terms. Removed for DCSD. + WoOoO += lib.einsum('iJeF,meNF->mNiJ', tauab_not2, eris_ovOV) + u2ab += lib.einsum('mNaB,mNiJ->iJaB', tauab, WoOoO) + WoOoO = lib.einsum('iJeF,meNF->mNiJ', t2[1], eris_ovOV) + u2ab += lib.einsum('mNaB,mNiJ->iJaB', tauab_not2, WoOoO) + tauab_not2 = None + else: + WoOoO += lib.einsum('iJeF,meNF->mNiJ', tauab, eris_ovOV) + u2ab += lib.einsum('mNaB,mNiJ->iJaB', tauab, WoOoO) WoOoO = None - tilab = make_tau_ab(t2[1], t1 , t1 , fac=0.5) - Fvva -= lib.einsum('mNaF,meNF->ae', tilab, eris_ovOV) - Fvvb -= lib.einsum('nMfA,nfME->AE', tilab, eris_ovOV) - Fooa += lib.einsum('iNeF,meNF->mi', tilab, eris_ovOV) - Foob += lib.einsum('nIfE,nfME->MI', tilab, eris_ovOV) + fac_t2 = 0.5 if cc.dcsd else 1 + tilab = make_tau_ab(t2[1], t1 , t1 , fac=0.5, fac_t2=fac_t2) + Fvva -= lib.einsum('mNaF,meNF->ae', tilab, eris_ovOV) # Mosaic t2 t2 terms coming + Fvvb -= lib.einsum('nMfA,nfME->AE', tilab, eris_ovOV) # Mosaic t2 t2 terms coming + Fooa += lib.einsum('iNeF,meNF->mi', tilab, eris_ovOV) # Mosaic t2 t2 terms coming + Foob += lib.einsum('nIfE,nfME->MI', tilab, eris_ovOV) # Mosaic t2 t2 terms coming + if cc.dcsd: + # Removed 0.5 t2 contribution in Fvva/b, Fooa/b, now saved in Fvva/b_t2, Fooa/b_t2. + Fvva_t2 -= 0.5*lib.einsum('mNaF,meNF->ae', t2[1], eris_ovOV) # Mosaic t2 t2 terms coming + Fvvb_t2 -= 0.5*lib.einsum('nMfA,nfME->AE', t2[1], eris_ovOV) # Mosaic t2 t2 terms coming + Fooa_t2 += 0.5*lib.einsum('iNeF,meNF->mi', t2[1], eris_ovOV) # Mosaic t2 t2 terms coming + Foob_t2 += 0.5*lib.einsum('nIfE,nfME->MI', t2[1], eris_ovOV) # Mosaic t2 t2 terms coming + Fova += np.einsum('NF,meNF->me',t1b, eris_ovOV) Fovb += np.einsum('nf,nfME->ME',t1a, eris_ovOV) u2ab += eris_ovOV.conj().transpose(0,2,1,3) - wovvo += 0.5*lib.einsum('jNbF,meNF->mbej', t2ab, eris_ovOV) - wOVVO += 0.5*lib.einsum('nJfB,nfME->MBEJ', t2ab, eris_ovOV) - wOvVo -= 0.5*lib.einsum('jnfb,nfME->MbEj', t2aa, eris_ovOV) - woVvO -= 0.5*lib.einsum('JNFB,meNF->mBeJ', t2bb, eris_ovOV) - woVVo += 0.5*lib.einsum('jNfB,mfNE->mBEj', t2ab, eris_ovOV) - wOvvO += 0.5*lib.einsum('nJbF,neMF->MbeJ', t2ab, eris_ovOV) + wovvo += 0.5*lib.einsum('jNbF,meNF->mbej', t2ab, eris_ovOV) # Ring t2 t2 term coming + wOVVO += 0.5*lib.einsum('nJfB,nfME->MBEJ', t2ab, eris_ovOV) # Ring t2 t2 term coming + wOvVo -= 0.5*lib.einsum('jnfb,nfME->MbEj', t2aa, eris_ovOV) # Ring t2 t2 term coming + woVvO -= 0.5*lib.einsum('JNFB,meNF->mBeJ', t2bb, eris_ovOV) # Ring t2 t2 term coming + if not cc.dcsd: + woVVo += 0.5*lib.einsum('jNfB,mfNE->mBEj', t2ab, eris_ovOV) # Exchange ring t2 t2 term coming + wOvvO += 0.5*lib.einsum('nJbF,neMF->MbeJ', t2ab, eris_ovOV) # Exchange ring t2 t2 term coming tmpabab = lib.einsum('JF,meNF->mNeJ', t1b, eris_ovOV) tmpbaba = lib.einsum('jf,nfME->MnEj', t1a, eris_ovOV) woVvO -= lib.einsum('NB,mNeJ->mBeJ', t1b, tmpabab) @@ -233,11 +289,17 @@ def update_amps(cc, t1, t2, eris): u1a += fova.conj() u1a += np.einsum('ie,ae->ia', t1a, Fvva) u1a -= np.einsum('ma,mi->ia', t1a, Fooa) + if cc.dcsd: + u1a += np.einsum('ie,ae->ia', t1a, Fvva_t2) + u1a -= np.einsum('ma,mi->ia', t1a, Fooa_t2) u1a -= np.einsum('imea,me->ia', t2aa, Fova) u1a += np.einsum('iMaE,ME->ia', t2ab, Fovb) u1b += fovb.conj() u1b += np.einsum('ie,ae->ia',t1b,Fvvb) u1b -= np.einsum('ma,mi->ia',t1b,Foob) + if cc.dcsd: + u1b += np.einsum('ie,ae->ia',t1b,Fvvb_t2) + u1b -= np.einsum('ma,mi->ia',t1b,Foob_t2) u1b -= np.einsum('imea,me->ia', t2bb, Fovb) u1b += np.einsum('mIeA,me->IA', t2ab, Fova) @@ -571,7 +633,6 @@ def init_amps(self, eris=None): t1a = fova.conj() / eia_a t1b = fovb.conj() / eia_b - eris_ovov = np.asarray(eris.ovov) eris_OVOV = np.asarray(eris.OVOV) eris_ovOV = np.asarray(eris.ovOV) @@ -1188,29 +1249,29 @@ def _make_eris_outcore(mycc, mo_coeff=None): return eris -def make_tau(t2, t1, r1, fac=1, out=None): +def make_tau(t2, t1, r1, fac=1, out=None, fac_t2=1): t1a, t1b = t1 r1a, r1b = r1 - tau1aa = make_tau_aa(t2[0], t1a, r1a, fac, out) - tau1bb = make_tau_aa(t2[2], t1b, r1b, fac, out) - tau1ab = make_tau_ab(t2[1], t1, r1, fac, out) + tau1aa = make_tau_aa(t2[0], t1a, r1a, fac, out, fac_t2=fac_t2) + tau1bb = make_tau_aa(t2[2], t1b, r1b, fac, out, fac_t2=fac_t2) + tau1ab = make_tau_ab(t2[1], t1, r1, fac, out, fac_t2=fac_t2) return tau1aa, tau1ab, tau1bb -def make_tau_aa(t2aa, t1a, r1a, fac=1, out=None): +def make_tau_aa(t2aa, t1a, r1a, fac=1, out=None, fac_t2=1): tau1aa = np.einsum('ia,jb->ijab', t1a, r1a) tau1aa-= np.einsum('ia,jb->jiab', t1a, r1a) tau1aa = tau1aa - tau1aa.transpose(0,1,3,2) tau1aa *= fac * .5 - tau1aa += t2aa + tau1aa += fac_t2*t2aa return tau1aa -def make_tau_ab(t2ab, t1, r1, fac=1, out=None): +def make_tau_ab(t2ab, t1, r1, fac=1, out=None, fac_t2=1): t1a, t1b = t1 r1a, r1b = r1 tau1ab = np.einsum('ia,jb->ijab', t1a, r1b) tau1ab+= np.einsum('ia,jb->ijab', r1a, t1b) tau1ab *= fac * .5 - tau1ab += t2ab + tau1ab += fac_t2*t2ab return tau1ab def _flops(nocc, nmo): diff --git a/pyscf/cc/udcsd.py b/pyscf/cc/udcsd.py new file mode 100644 index 0000000000..9b35f9ccd4 --- /dev/null +++ b/pyscf/cc/udcsd.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# Copyright 2014-2020 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +Distinguishable unrestricted coupled cluster singles doubles +''' + +from pyscf.cc import uccsd + +class UDCSD(uccsd.UCCSD): + def dcsd(self, t1=None, t2=None, eris=None): + return self.ccsd(t1=t1, t2=t2, eris=eris) diff --git a/pyscf/pbc/cc/__init__.py b/pyscf/pbc/cc/__init__.py index 684b7c77b0..baa6172d9a 100644 --- a/pyscf/pbc/cc/__init__.py +++ b/pyscf/pbc/cc/__init__.py @@ -14,8 +14,9 @@ # limitations under the License. from pyscf.pbc import scf -from pyscf.pbc.cc import ccsd +from pyscf.pbc.cc import ccsd, dcsd from pyscf.pbc.cc import kccsd_rhf as krccsd +from pyscf.pbc.cc import kdcsd_rhf from pyscf.pbc.cc import kccsd_uhf as kuccsd from pyscf.pbc.cc import kccsd as kgccsd from pyscf.pbc.cc import eom_kccsd_rhf @@ -37,6 +38,17 @@ def GCCSD(mf, frozen=None, mo_coeff=None, mo_occ=None): mf = scf.addons.convert_to_ghf(mf) return ccsd.GCCSD(mf, frozen, mo_coeff, mo_occ) +def RDCSD(mf, frozen=None, mo_coeff=None, mo_occ=None): + mf = scf.addons.convert_to_rhf(mf) + return dcsd.RDCSD(mf, frozen, mo_coeff, mo_occ) + +DCSD = RDCSD + +def UDCSD(mf, frozen=None, mo_coeff=None, mo_occ=None): + mf = scf.addons.convert_to_uhf(mf) + return dcsd.UDCSD(mf, frozen, mo_coeff, mo_occ) + + def KGCCSD(mf, frozen=None, mo_coeff=None, mo_occ=None): from pyscf.pbc.cc import kccsd mf = scf.addons.convert_to_ghf(mf) @@ -55,3 +67,11 @@ def KUCCSD(mf, frozen=None, mo_coeff=None, mo_occ=None): if not isinstance(mf, scf.kuhf.KUHF): mf = scf.addons.convert_to_uhf(mf) return kccsd_uhf.UCCSD(mf, frozen, mo_coeff, mo_occ) + +def KRDCSD(mf, frozen=None, mo_coeff=None, mo_occ=None): + from pyscf.pbc.cc import kdcsd_rhf + if not isinstance(mf, scf.khf.KRHF): + mf = scf.addons.convert_to_rhf(mf) + return kdcsd_rhf.RDCSD(mf, frozen, mo_coeff, mo_occ) + +KDCSD = KRDCSD diff --git a/pyscf/pbc/cc/dcsd.py b/pyscf/pbc/cc/dcsd.py new file mode 100644 index 0000000000..e385e69b4a --- /dev/null +++ b/pyscf/pbc/cc/dcsd.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# Copyright 2014-2020 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pyscf.pbc.cc import ccsd as pbcccsd + +class RDCSD(pbcccsd.RCCSD): + def dcsd(self, t1=None, t2=None, eris=None): + return self.ccsd(t1=t1, t2=t2, eris=eris) + +class UDCSD(pbcccsd.UCCSD): + def dcsd(self, t1=None, t2=None, eris=None): + return self.ccsd(t1=t1, t2=t2, eris=eris) + diff --git a/pyscf/pbc/cc/kccsd_rhf.py b/pyscf/pbc/cc/kccsd_rhf.py index d1cd02557e..4be6823e5b 100644 --- a/pyscf/pbc/cc/kccsd_rhf.py +++ b/pyscf/pbc/cc/kccsd_rhf.py @@ -72,8 +72,8 @@ def update_amps(cc, t1, t2, eris): Foo = imdk.cc_Foo(t1, t2, eris, kconserv) Fvv = imdk.cc_Fvv(t1, t2, eris, kconserv) Fov = imdk.cc_Fov(t1, t2, eris, kconserv) - Loo = imdk.Loo(t1, t2, eris, kconserv) - Lvv = imdk.Lvv(t1, t2, eris, kconserv) + Loo = imdk.Loo(t1, t2, eris, kconserv, dcsd=cc.dcsd) + Lvv = imdk.Lvv(t1, t2, eris, kconserv, dcsd=cc.dcsd) # Move energy terms to the other side for k in range(nkpts): @@ -131,11 +131,21 @@ def update_amps(cc, t1, t2, eris): mem_now = lib.current_memory()[0] if (nocc ** 4 * nkpts ** 3) * 16 / 1e6 + mem_now < cc.max_memory * .9: - Woooo = imdk.cc_Woooo(t1, t2, eris, kconserv) + if cc.dcsd: + Woooo_not2 = imdk.cc_Woooo(t1, t2, eris, kconserv, not2=True) + Woooo = imdk.cc_Woooo(t1, t2, eris, kconserv) + else: + Woooo = imdk.cc_Woooo(t1, t2, eris, kconserv) else: fimd = lib.H5TmpFile() - Woooo = fimd.create_dataset('oooo', (nkpts, nkpts, nkpts, nocc, nocc, nocc, nocc), t1.dtype.char) - Woooo = imdk.cc_Woooo(t1, t2, eris, kconserv, Woooo) + if cc.dcsd: + Woooo_not2 = fimd.create_dataset('oooonot2', (nkpts, nkpts, nkpts, nocc, nocc, nocc, nocc), t1.dtype.char) + Woooo_not2 = imdk.cc_Woooo(t1, t2, eris, kconserv, Woooo, not2=True) + Woooo = fimd.create_dataset('oooo', (nkpts, nkpts, nkpts, nocc, nocc, nocc, nocc), t1.dtype.char) + Woooo = imdk.cc_Woooo(t1, t2, eris, kconserv, Woooo) + else: + Woooo = fimd.create_dataset('oooo', (nkpts, nkpts, nkpts, nocc, nocc, nocc, nocc), t1.dtype.char) + Woooo = imdk.cc_Woooo(t1, t2, eris, kconserv, Woooo) for ki, kj, ka in kpts_helper.loop_kkk(nkpts): # Chemist's notation for momentum conserving t2(ki,kj,ka,kb) @@ -144,12 +154,21 @@ def update_amps(cc, t1, t2, eris): t2new_tmp = np.zeros((nocc, nocc, nvir, nvir), dtype=t2.dtype) for kl in range(nkpts): kk = kconserv[kj, kl, ki] - tau_term = t2[kk, kl, ka].copy() - if kl == kb and kk == ka: - tau_term += einsum('ic,jd->ijcd', t1[ka], t1[kb]) - t2new_tmp += 0.5 * einsum('klij,klab->ijab', Woooo[kk, kl, ki], tau_term) + if cc.dcsd: + t2new_tmp += 0.5 * einsum('klij,klab->ijab', Woooo_not2[kk, kl, ki], t2[kk, kl, ka]) + tau_term = np.zeros_like(t2[kk,kl,ka]) + if kl == kb and kk ==ka: + tau_term += einsum('ic,jd->ijcd', t1[ka], t1[kb]) + t2new_tmp += 0.5 * einsum('klij,klab->ijab', Woooo[kk, kl, ki], tau_term) + else: + tau_term = t2[kk, kl, ka].copy() + if kl == kb and kk == ka: + tau_term += einsum('ic,jd->ijcd', t1[ka], t1[kb]) + t2new_tmp += 0.5 * einsum('klij,klab->ijab', Woooo[kk, kl, ki], tau_term) # t2 t2 ladder terms included. t2new[ki, kj, ka] += t2new_tmp t2new[kj, ki, kb] += t2new_tmp.transpose(1, 0, 3, 2) + if cc.dcsd: + Woooo_not2 = None Woooo = None fimd = None time1 = log.timer_debug1('t2 oooo', *time1) @@ -161,8 +180,8 @@ def update_amps(cc, t1, t2, eris): for ki, kj, ka in kpts_helper.loop_kkk(nkpts): kb = kconserv[ki, ka, kj] - t2new_tmp = einsum('ac,ijcb->ijab', Lvv[ka], t2[ki, kj, ka]) - t2new_tmp += einsum('ki,kjab->ijab', -Loo[ki], t2[ki, kj, ka]) + t2new_tmp = einsum('ac,ijcb->ijab', Lvv[ka], t2[ki, kj, ka]) # t2 t2 mosaic terms + t2new_tmp += einsum('ki,kjab->ijab', -Loo[ki], t2[ki, kj, ka]) # t2 t2 mosaic terms kc = kconserv[ka, ki, kb] tmp2 = np.asarray(eris.vovv[kc, ki, kb]).transpose(3, 2, 1, 0).conj() \ @@ -178,15 +197,16 @@ def update_amps(cc, t1, t2, eris): mem_now = lib.current_memory()[0] if (nocc ** 2 * nvir ** 2 * nkpts ** 3) * 16 / 1e6 * 2 + mem_now < cc.max_memory * .9: - Wvoov = imdk.cc_Wvoov(t1, t2, eris, kconserv) - Wvovo = imdk.cc_Wvovo(t1, t2, eris, kconserv) + Wvoov = imdk.cc_Wvoov(t1, t2, eris, kconserv, dcsd=cc.dcsd) + Wvovo = imdk.cc_Wvovo(t1, t2, eris, kconserv, not2=cc.dcsd) else: fimd = lib.H5TmpFile() Wvoov = fimd.create_dataset('voov', (nkpts, nkpts, nkpts, nvir, nocc, nocc, nvir), t1.dtype.char) Wvovo = fimd.create_dataset('vovo', (nkpts, nkpts, nkpts, nvir, nocc, nvir, nocc), t1.dtype.char) - Wvoov = imdk.cc_Wvoov(t1, t2, eris, kconserv, Wvoov) - Wvovo = imdk.cc_Wvovo(t1, t2, eris, kconserv, Wvovo) + Wvoov = imdk.cc_Wvoov(t1, t2, eris, kconserv, Wvoov, dcsd=cc.dcsd) + Wvovo = imdk.cc_Wvovo(t1, t2, eris, kconserv, Wvovo, not2=cc.dcsd) + # t2 t2 (x)ring terms in here: for ki, kj, ka in kpts_helper.loop_kkk(nkpts): kb = kconserv[ki, ka, kj] t2new_tmp = np.zeros((nocc, nocc, nvir, nvir), dtype=t2.dtype) diff --git a/pyscf/pbc/cc/kdcsd_rhf.py b/pyscf/pbc/cc/kdcsd_rhf.py new file mode 100644 index 0000000000..bf464f6f14 --- /dev/null +++ b/pyscf/pbc/cc/kdcsd_rhf.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# Copyright 2017-2021 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pyscf.pbc.cc as pbccc + +class RDCSD(pbccc.kccsd_rhf.RCCSD): + def dcsd(self, t1=None, t2=None, eris=None): + return self.ccsd(t1=t1, t2=t2, eris=eris) + +KRDCSD = RDCSD + diff --git a/pyscf/pbc/cc/kintermediates_rhf.py b/pyscf/pbc/cc/kintermediates_rhf.py index 99eed1c89a..e08b4432f5 100644 --- a/pyscf/pbc/cc/kintermediates_rhf.py +++ b/pyscf/pbc/cc/kintermediates_rhf.py @@ -35,7 +35,7 @@ ### Eqs. (37)-(39) "kappa" -def cc_Foo(t1,t2,eris,kconserv): +def cc_Foo(t1,t2,eris,kconserv,dcsd=False): nkpts, nocc, nvir = t1.shape Fki = np.empty((nkpts,nocc,nocc),dtype=t2.dtype) for ki in range(nkpts): @@ -45,14 +45,17 @@ def cc_Foo(t1,t2,eris,kconserv): for kc in range(nkpts): kd = kconserv[kk,kc,kl] Soovv = 2*eris.oovv[kk,kl,kc] - eris.oovv[kk,kl,kd].transpose(0,1,3,2) - Fki[ki] += einsum('klcd,ilcd->ki',Soovv,t2[ki,kl,kc]) + if dcsd: + Fki[ki] += 0.5*einsum('klcd,ilcd->ki',Soovv,t2[ki,kl,kc]) + else: + Fki[ki] += einsum('klcd,ilcd->ki',Soovv,t2[ki,kl,kc]) #if ki == kc: kd = kconserv[kk,ki,kl] Soovv = 2*eris.oovv[kk,kl,ki] - eris.oovv[kk,kl,kd].transpose(0,1,3,2) Fki[ki] += einsum('klcd,ic,ld->ki',Soovv,t1[ki],t1[kl]) return Fki -def cc_Fvv(t1,t2,eris,kconserv): +def cc_Fvv(t1,t2,eris,kconserv,dcsd=False): nkpts, nocc, nvir = t1.shape Fac = np.empty((nkpts,nvir,nvir),dtype=t2.dtype) for ka in range(nkpts): @@ -62,7 +65,10 @@ def cc_Fvv(t1,t2,eris,kconserv): for kk in range(nkpts): kd = kconserv[kk,kc,kl] Soovv = 2*eris.oovv[kk,kl,kc] - eris.oovv[kk,kl,kd].transpose(0,1,3,2) - Fac[ka] += -einsum('klcd,klad->ac',Soovv,t2[kk,kl,ka]) + if dcsd: + Fac[ka] += -0.5*einsum('klcd,klad->ac',Soovv,t2[kk,kl,ka]) + else: + Fac[ka] += -einsum('klcd,klad->ac',Soovv,t2[kk,kl,ka]) #if kk == ka kd = kconserv[ka,kc,kl] Soovv = 2*eris.oovv[ka,kl,kc] - eris.oovv[ka,kl,kd].transpose(0,1,3,2) @@ -81,10 +87,10 @@ def cc_Fov(t1,t2,eris,kconserv): ### Eqs. (40)-(41) "lambda" -def Loo(t1,t2,eris,kconserv): +def Loo(t1,t2,eris,kconserv,dcsd=False): nkpts, nocc, nvir = t1.shape fov = eris.fock[:,:nocc,nocc:] - Lki = cc_Foo(t1,t2,eris,kconserv) + Lki = cc_Foo(t1,t2,eris,kconserv,dcsd=dcsd) for ki in range(nkpts): Lki[ki] += einsum('kc,ic->ki',fov[ki],t1[ki]) for kl in range(nkpts): @@ -92,10 +98,10 @@ def Loo(t1,t2,eris,kconserv): Lki[ki] += -einsum('lkic,lc->ki',eris.ooov[kl,ki,ki],t1[kl]) return Lki -def Lvv(t1,t2,eris,kconserv): +def Lvv(t1,t2,eris,kconserv,dcsd=False): nkpts, nocc, nvir = t1.shape fov = eris.fock[:,:nocc,nocc:] - Lac = cc_Fvv(t1,t2,eris,kconserv) + Lac = cc_Fvv(t1,t2,eris,kconserv,dcsd=dcsd) for ka in range(nkpts): Lac[ka] += -einsum('kc,ka->ac',fov[ka],t1[ka]) for kk in range(nkpts): @@ -105,7 +111,7 @@ def Lvv(t1,t2,eris,kconserv): ### Eqs. (42)-(45) "chi" -def cc_Woooo(t1, t2, eris, kconserv, out=None): +def cc_Woooo(t1, t2, eris, kconserv, out=None, not2=False): nkpts, nocc, nvir = t1.shape Wklij = _new(eris.oooo.shape, t1.dtype, out) @@ -123,7 +129,10 @@ def cc_Woooo(t1, t2, eris, kconserv, out=None): # Wklij[kk,kl,ki] += einsum('klcd,ijcd->klij',eris.oovv[kk,kl,kc],t2[ki,kj,kc]) #Wklij[kk,kl,ki] += einsum('klcd,ic,jd->klij',eris.oovv[kk,kl,ki],t1[ki],t1[kj]) vvoo = eris.oovv[kk,kl].transpose(0,3,4,1,2).reshape(nkpts*nvir,nvir,nocc,nocc) - t2t = t2[ki,kj].copy().transpose(0,3,4,1,2) + if not2: + t2t = np.zeros_like(t2[ki,kj].transpose(0,3,4,1,2)) + else: + t2t = t2[ki,kj].copy().transpose(0,3,4,1,2) #for kc in range(nkpts): # kd = kconserv[ki,kc,kj] # if kc == ki and kj == kd: @@ -162,7 +171,7 @@ def cc_Wvvvv(t1, t2, eris, kconserv, out=None): return Wabcd -def cc_Wvoov(t1, t2, eris, kconserv, out=None): +def cc_Wvoov(t1, t2, eris, kconserv, out=None, dcsd=False): Wakic = _new(eris.voov.shape, t1.dtype, out) nkpts, nocc, nvir = t1.shape for ka in range(nkpts): @@ -188,13 +197,16 @@ def cc_Wvoov(t1, t2, eris, kconserv, out=None): oovv_tmp = np.array(eris.oovv[kk,:,kc]) voov_i[ki] -= 0.5*einsum('xklcd,xliad->akic',oovv_tmp,tau) - Soovv_tmp = 2*oovv_tmp - eris.oovv[:,kk,kc].transpose(0,2,1,3,4) + if dcsd: + Soovv_tmp = 2*oovv_tmp + else: + Soovv_tmp = 2*oovv_tmp - eris.oovv[:,kk,kc].transpose(0,2,1,3,4) voov_i[ki] += 0.5*einsum('xklcd,xilad->akic',Soovv_tmp,t2[ki,:,ka]) Wakic[ka,kk,:] = voov_i[:] return Wakic -def cc_Wvovo(t1, t2, eris, kconserv, out=None): +def cc_Wvovo(t1, t2, eris, kconserv, out=None, not2=False): nkpts, nocc, nvir = t1.shape Wakci = _new((nkpts,nkpts,nkpts,nvir,nocc,nvir,nocc), t1.dtype, out) @@ -212,7 +224,10 @@ def cc_Wvovo(t1, t2, eris, kconserv, out=None): # Wakci[ka,kk,kc] -= 0.5*einsum('lkcd,ilda->akci',eris.oovv[kl,kk,kc],t2[ki,kl,kd]) #Wakci[ka,kk,kc] -= einsum('lkcd,id,la->akci',eris.oovv[ka,kk,kc],t1[ki],t1[ka]) oovvf = eris.oovv[:,kk,kc].reshape(nkpts*nocc,nocc,nvir,nvir) - t2f = t2[:,ki,ka].copy() #This is a tau like term + if not2: + t2f = np.zeros_like(t2[:,ki,ka]) + else: + t2f = t2[:,ki,ka].copy() #This is a tau like term #for kl in range(nkpts): # kd = kconserv[kl,kc,kk] # if ki == kd and kl == ka: diff --git a/pyscf/pbc/cc/test/test_dcsd.py b/pyscf/pbc/cc/test/test_dcsd.py new file mode 100644 index 0000000000..e5cabf24e4 --- /dev/null +++ b/pyscf/pbc/cc/test/test_dcsd.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python +# Copyright 2014-2020 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Authors: James D. McClain +# Timothy Berkelbach +# Verena Neufeld + +import unittest +import numpy as np + +from pyscf.pbc import gto as pbcgto +from pyscf.pbc import scf as pbcscf +from pyscf.pbc import dft as pbcdft + +import pyscf.pbc.cc as pbcc +import pyscf.pbc.tools.make_test_cell as make_test_cell +from pyscf.pbc.lib import kpts_helper + + +def setUpModule(): + global cell, rand_kmf + cell = pbcgto.Cell() + cell.atom = ''' + He 0.000000000000 0.000000000000 0.000000000000 + He 1.685068664391 1.685068664391 1.685068664391 + ''' + cell.basis = [[0, (1., 1.)], [0, (.5, 1.)]] + cell.a = ''' + 0.000000000, 3.370137329, 3.370137329 + 3.370137329, 0.000000000, 3.370137329 + 3.370137329, 3.370137329, 0.000000000''' + cell.unit = 'B' + #cell.verbose = 7 + cell.output = '/dev/null' + cell.mesh = [15] * 3 + cell.build() + + rand_kmf = make_rand_kmf() + +def tearDownModule(): + global cell, rand_kmf + cell.stdout.close() + del cell, rand_kmf + + +def make_rand_kmf(): + # Not perfect way to generate a random mf. + # CSC = 1 is not satisfied and the fock matrix is neither + # diagonal nor sorted. + np.random.seed(2) + nkpts = 3 + kmf = pbcscf.KRHF(cell, kpts=cell.make_kpts([1, 1, nkpts])) + kmf.exxdiv = None + nmo = cell.nao_nr() + kmf.mo_occ = np.zeros((nkpts, nmo)) + kmf.mo_occ[:, :2] = 2 + kmf.mo_energy = np.arange(nmo) + np.random.random((nkpts, nmo)) * .3 + kmf.mo_energy[kmf.mo_occ == 0] += 2 + kmf.mo_coeff = (np.random.random((nkpts, nmo, nmo)) + + np.random.random((nkpts, nmo, nmo)) * 1j - .5 - .5j) + ## Round to make this insensitive to small changes between PySCF versions + #mat_veff = kmf.get_veff().round(4) + #mat_hcore = kmf.get_hcore().round(4) + #kmf.get_veff = lambda *x: mat_veff + #kmf.get_hcore = lambda *x: mat_hcore + return kmf + + +def run_kcell(cell, n, nk): + ############################################# + # Do a k-point calculation # + ############################################# + abs_kpts = cell.make_kpts(nk, wrap_around=True) + #cell.verbose = 7 + + # HF + kmf = pbcscf.KRHF(cell, abs_kpts, exxdiv=None) + kmf.conv_tol = 1e-14 + ekpt = kmf.scf() + + # DCSD + cc = pbcc.kdcsd_rhf.RDCSD(kmf) + cc.conv_tol = 1e-8 + ecc, t1, t2 = cc.kernel() + return ekpt, ecc + + +class KnownValues(unittest.TestCase): + def test_311_n1_high_cost(self): + L = 7.0 + n = 9 + cell = make_test_cell.test_cell_n1(L,[n]*3) + nk = (3, 1, 1) + hf_311 = -0.92687629918229486 + cc_311 = -0.04336682332846571 + escf, ecc = run_kcell(cell,n,nk) + self.assertAlmostEqual(escf,hf_311, 8) + self.assertAlmostEqual(ecc, cc_311, 6) + + def test_single_kpt(self): + cell = pbcgto.Cell() + cell.atom = ''' + H 0 0 0 + H 1 0 0 + H 0 1 0 + H 0 1 1 + ''' + cell.a = np.eye(3)*2 + cell.basis = [[0, [1.2, 1]], [1, [1.0, 1]]] + cell.verbose = 0 + cell.build() + + kpts = cell.get_abs_kpts([.5,.5,.5]).reshape(1,3) + mf = pbcscf.KRHF(cell, kpts=kpts).run(conv_tol=1e-9) + kcc = pbcc.kdcsd_rhf.RDCSD(mf) + kcc.level_shift = .05 + e0 = kcc.kernel()[0] + + mf = pbcscf.RHF(cell, kpt=kpts[0]).run(conv_tol=1e-9) + mycc = pbcc.RDCSD(mf) + e1 = mycc.kernel()[0] + self.assertAlmostEqual(e0, e1, 5) + + def test_frozen_n3(self): + mesh = 5 + cell = make_test_cell.test_cell_n3([mesh]*3) + nk = (1, 1, 2) + ehf_bench = -8.348616843863795 + ecc_bench = -0.038375755632854835 + + abs_kpts = cell.make_kpts(nk, with_gamma_point=True) + + # RHF calculation + kmf = pbcscf.KRHF(cell, abs_kpts, exxdiv=None) + kmf.conv_tol = 1e-9 + ehf = kmf.scf() + + # KRCCSD calculation, equivalent to running supercell + # calculation with frozen=[0,1,2] (if done with larger mesh) + cc = pbcc.kdcsd_rhf.RDCSD(kmf, frozen=[[0],[0,1]]) + cc.diis_start_cycle = 1 + ecc, t1, t2 = cc.kernel() + self.assertAlmostEqual(ehf, ehf_bench, 8) + self.assertAlmostEqual(ecc, ecc_bench, 6) + + def test_dcsd_non_hf(self): + '''Tests dcsd for non-Hartree-Fock references + using supercell vs k-point calculation.''' + n = 14 + cell = make_test_cell.test_cell_n3([n]*3) + + nk = [2, 1, 1] + kpts = cell.make_kpts(nk) + kpts -= kpts[0] + kks = pbcdft.KRKS(cell, kpts=kpts) + ekks = kks.kernel() + + khf = pbcscf.KRHF(cell) + khf.__dict__.update(kks.__dict__) + + mycc = pbcc.KRDCSD(khf) + eris = mycc.ao2mo() + ekcc, t1, t2 = mycc.kernel(eris=eris) + + # Run supercell + from pyscf.pbc.tools.pbc import super_cell + supcell = super_cell(cell, nk) + rks = pbcdft.RKS(supcell) + erks = rks.kernel() + + rhf = pbcscf.RHF(supcell) + rhf.__dict__.update(rks.__dict__) + + mycc = pbcc.RDCSD(rhf) + eris = mycc.ao2mo() + ercc, t1, t2 = mycc.kernel(eris=eris) + self.assertAlmostEqual(ercc/np.prod(nk), -0.16235728620565748, 6) + self.assertAlmostEqual(ercc/np.prod(nk), ekcc, 6) + + + def test_h4_fcc_k2(self): + '''Metallic hydrogen fcc lattice. Checks versus a corresponding + supercell calculation. + + NOTE: different versions of the davidson may converge to a different + solution for the k-point IP/EA eom. If you're getting the wrong + root, check to see if it's contained in the supercell set of + eigenvalues.''' + cell = pbcgto.Cell() + cell.atom = [['H', (0.000000000, 0.000000000, 0.000000000)], + ['H', (0.000000000, 0.500000000, 0.250000000)], + ['H', (0.500000000, 0.500000000, 0.500000000)], + ['H', (0.500000000, 0.000000000, 0.750000000)]] + cell.unit = 'Bohr' + cell.a = [[1.,0.,0.],[0.,1.,0],[0,0,2.2]] + cell.verbose = 7 + cell.spin = 0 + cell.charge = 0 + cell.basis = [[0, [1.0, 1]],] + cell.pseudo = 'gth-pade' + cell.output = '/dev/null' + #cell.max_memory = 1000 + for i in range(len(cell.atom)): + cell.atom[i][1] = tuple(np.dot(np.array(cell.atom[i][1]),np.array(cell.a))) + cell.build() + + nmp = [2, 1, 1] + + kmf = pbcscf.KRHF(cell) + kmf.kpts = cell.make_kpts(nmp, scaled_center=[0.0,0.0,0.0]) + e = kmf.kernel() + + mycc = pbcc.KDCSD(kmf) + ekccsd, _, _ = mycc.kernel() + self.assertAlmostEqual(ekccsd, -0.06351536244501263, 6) + + # Start of supercell calculations + from pyscf.pbc.tools.pbc import super_cell + supcell = super_cell(cell, nmp) + supcell.build() + mf = pbcscf.KRHF(supcell) + e = mf.kernel() + + myscc = pbcc.KDCSD(mf) + eccsd, _, _ = myscc.kernel() + eccsd /= np.prod(nmp) + self.assertAlmostEqual(eccsd, -0.06351536244501263, 6) + +if __name__ == '__main__': + unittest.main()