Source code for pyscf.cc.rccsd

#!/usr/bin/env python
# Copyright 2014-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.
#
# Author: Timothy Berkelbach <tim.berkelbach@gmail.com>
#

'''
Restricted CCSD implementation which supports both real and complex integrals.
The 4-index integrals are saved on disk entirely (without using any symmetry).
This code is slower than the pyscf.cc.ccsd implementation.

Note MO integrals are treated in chemist's notation

Ref: Hirata et al., J. Chem. Phys. 120, 2581 (2004)
'''


import numpy as np

from pyscf import lib
from pyscf import ao2mo
from pyscf.lib import logger
from pyscf.cc import ccsd
from pyscf.cc import rintermediates as imd
from pyscf.mp import mp2
from pyscf import __config__

BLKMIN = getattr(__config__, 'cc_ccsd_blkmin', 4)
MEMORYMIN = getattr(__config__, 'cc_ccsd_memorymin', 2000)

[docs] def update_amps(cc, t1, t2, eris): # Ref: Hirata et al., J. Chem. Phys. 120, 2581 (2004) Eqs.(35)-(36) assert (isinstance(eris, ccsd._ChemistsERIs)) nocc, nvir = t1.shape fock = eris.fock mo_e_o = eris.mo_energy[:nocc] mo_e_v = eris.mo_energy[nocc:] + cc.level_shift fov = fock[:nocc,nocc:].copy() foo = fock[:nocc,:nocc].copy() fvv = fock[nocc:,nocc:].copy() Foo = imd.cc_Foo(t1,t2,eris) Fvv = imd.cc_Fvv(t1,t2,eris) Fov = imd.cc_Fov(t1,t2,eris) # Move energy terms to the other side Foo[np.diag_indices(nocc)] -= mo_e_o Fvv[np.diag_indices(nvir)] -= mo_e_v # T1 equation t1new =-2*np.einsum('kc,ka,ic->ia', fov, t1, t1) t1new += np.einsum('ac,ic->ia', Fvv, t1) t1new += -np.einsum('ki,ka->ia', Foo, t1) t1new += 2*np.einsum('kc,kica->ia', Fov, t2) t1new += -np.einsum('kc,ikca->ia', Fov, t2) t1new += np.einsum('kc,ic,ka->ia', Fov, t1, t1) t1new += fov.conj() t1new += 2*np.einsum('kcai,kc->ia', eris.ovvo, t1) t1new += -np.einsum('kiac,kc->ia', eris.oovv, t1) eris_ovvv = np.asarray(eris.get_ovvv()) t1new += 2*lib.einsum('kdac,ikcd->ia', eris_ovvv, t2) t1new += -lib.einsum('kcad,ikcd->ia', eris_ovvv, t2) t1new += 2*lib.einsum('kdac,kd,ic->ia', eris_ovvv, t1, t1) t1new += -lib.einsum('kcad,kd,ic->ia', eris_ovvv, t1, t1) eris_ovoo = np.asarray(eris.ovoo, order='C') t1new +=-2*lib.einsum('lcki,klac->ia', eris_ovoo, t2) t1new += lib.einsum('kcli,klac->ia', eris_ovoo, t2) t1new +=-2*lib.einsum('lcki,lc,ka->ia', eris_ovoo, t1, t1) t1new += lib.einsum('kcli,lc,ka->ia', eris_ovoo, t1, t1) # T2 equation tmp2 = lib.einsum('kibc,ka->abic', eris.oovv, -t1) tmp2 += np.asarray(eris_ovvv).conj().transpose(1,3,0,2) tmp = lib.einsum('abic,jc->ijab', tmp2, t1) t2new = tmp + tmp.transpose(1,0,3,2) tmp2 = lib.einsum('kcai,jc->akij', eris.ovvo, t1) tmp2 += eris_ovoo.transpose(1,3,0,2).conj() tmp = lib.einsum('akij,kb->ijab', tmp2, t1) t2new -= tmp + tmp.transpose(1,0,3,2) t2new += np.asarray(eris.ovov).conj().transpose(0,2,1,3) if cc.cc2: Woooo2 = np.asarray(eris.oooo).transpose(0,2,1,3).copy() Woooo2 += lib.einsum('lcki,jc->klij', eris_ovoo, t1) Woooo2 += lib.einsum('kclj,ic->klij', eris_ovoo, t1) Woooo2 += lib.einsum('kcld,ic,jd->klij', eris.ovov, t1, t1) t2new += lib.einsum('klij,ka,lb->ijab', Woooo2, t1, t1) Wvvvv = lib.einsum('kcbd,ka->abcd', eris_ovvv, -t1) Wvvvv = Wvvvv + Wvvvv.transpose(1,0,3,2) Wvvvv += np.asarray(eris.vvvv).transpose(0,2,1,3) t2new += lib.einsum('abcd,ic,jd->ijab', Wvvvv, t1, t1) Lvv2 = fvv - np.einsum('kc,ka->ac', fov, t1) Lvv2 -= np.diag(np.diag(fvv)) tmp = lib.einsum('ac,ijcb->ijab', Lvv2, t2) t2new += (tmp + tmp.transpose(1,0,3,2)) Loo2 = foo + np.einsum('kc,ic->ki', fov, t1) Loo2 -= np.diag(np.diag(foo)) 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[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) Wvvvv = imd.cc_Wvvvv(t1, t2, eris) tau = t2 + np.einsum('ia,jb->ijab', t1, t1) t2new += lib.einsum('klij,klab->ijab', Woooo, tau) t2new += lib.einsum('abcd,ijcd->ijab', Wvvvv, tau) tmp = lib.einsum('ac,ijcb->ijab', Lvv, t2) t2new += (tmp + tmp.transpose(1,0,3,2)) tmp = lib.einsum('ki,kjab->ijab', Loo, t2) t2new -= (tmp + tmp.transpose(1,0,3,2)) tmp = 2*lib.einsum('akic,kjcb->ijab', Wvoov, t2) tmp -= lib.einsum('akci,kjcb->ijab', Wvovo, t2) t2new += (tmp + tmp.transpose(1,0,3,2)) tmp = lib.einsum('akic,kjbc->ijab', Wvoov, t2) t2new -= (tmp + tmp.transpose(1,0,3,2)) tmp = lib.einsum('bkci,kjac->ijab', Wvovo, t2) t2new -= (tmp + tmp.transpose(1,0,3,2)) eia = mo_e_o[:,None] - mo_e_v eijab = lib.direct_sum('ia,jb->ijab',eia,eia) t1new /= eia t2new /= eijab return t1new, t2new
[docs] def energy(cc, t1=None, t2=None, eris=None): '''RCCSD correlation energy''' if t1 is None: t1 = cc.t1 if t2 is None: t2 = cc.t2 if eris is None: eris = cc.ao2mo() nocc, nvir = t1.shape fock = eris.fock e = 2*np.einsum('ia,ia', fock[:nocc,nocc:], t1) tau = np.einsum('ia,jb->ijab',t1,t1) tau += t2 eris_ovov = np.asarray(eris.ovov) e += 2*np.einsum('ijab,iajb', tau, eris_ovov) e += -np.einsum('ijab,ibja', tau, eris_ovov) if abs(e.imag) > 1e-4: logger.warn(cc, 'Non-zero imaginary part found in RCCSD energy %s', e) return e.real
[docs] class RCCSD(ccsd.CCSD): '''restricted CCSD with IP-EOM, EA-EOM, EE-EOM, and SF-EOM capabilities Ground-state CCSD is performed in optimized ccsd.CCSD and EOM is performed here. '''
[docs] def kernel(self, t1=None, t2=None, eris=None, mbpt2=False): return self.ccsd(t1, t2, eris, mbpt2)
[docs] def ccsd(self, t1=None, t2=None, eris=None, mbpt2=False): '''Ground-state CCSD. Kwargs: mbpt2 : bool Use one-shot MBPT2 approximation to CCSD. ''' if mbpt2: pt = mp2.MP2(self._scf, self.frozen, self.mo_coeff, self.mo_occ) self.e_corr, self.t2 = pt.kernel(eris=eris) nocc, nvir = self.t2.shape[1:3] self.t1 = np.zeros((nocc,nvir)) return self.e_corr, self.t1, self.t2 if eris is None: eris = self.ao2mo(self.mo_coeff) return ccsd.CCSDBase.ccsd(self, t1, t2, eris)
[docs] def ao2mo(self, mo_coeff=None): nmo = self.nmo nao = self.mo_coeff.shape[0] nmo_pair = nmo * (nmo+1) // 2 nao_pair = nao * (nao+1) // 2 mem_incore = (max(nao_pair**2, nmo**4) + nmo_pair**2) * 8/1e6 mem_now = lib.current_memory()[0] if (self._scf._eri is not None and (mem_incore+mem_now < self.max_memory) or self.mol.incore_anyway): return _make_eris_incore(self, mo_coeff) elif getattr(self._scf, 'with_df', None): logger.warn(self, 'CCSD detected DF being used in the HF object. ' 'MO integrals are computed based on the DF 3-index tensors.\n' 'It\'s recommended to use dfccsd.CCSD for the ' 'DF-CCSD calculations') raise NotImplementedError #return _make_df_eris_outcore(self, mo_coeff) else: return _make_eris_outcore(self, mo_coeff)
energy = energy update_amps = update_amps
[docs] def solve_lambda(self, t1=None, t2=None, l1=None, l2=None, eris=None): from pyscf.cc import rccsd_lambda if t1 is None: t1 = self.t1 if t2 is None: t2 = self.t2 if eris is None: eris = self.ao2mo(self.mo_coeff) self.converged_lambda, self.l1, self.l2 = \ rccsd_lambda.kernel(self, eris, t1, t2, l1, l2, max_cycle=self.max_cycle, tol=self.conv_tol_normt, verbose=self.verbose) return self.l1, self.l2
class _ChemistsERIs(ccsd._ChemistsERIs): def get_ovvv(self, *slices): '''To access a subblock of ovvv tensor''' if slices: return self.ovvv[slices] else: return self.ovvv def _make_eris_incore(mycc, mo_coeff=None, ao2mofn=None): cput0 = (logger.process_clock(), logger.perf_counter()) eris = _ChemistsERIs() eris._common_init_(mycc, mo_coeff) nocc = eris.nocc nmo = eris.fock.shape[0] if callable(ao2mofn): eri1 = ao2mofn(eris.mo_coeff).reshape([nmo]*4) else: eri1 = ao2mo.incore.full(mycc._scf._eri, eris.mo_coeff) eri1 = ao2mo.restore(1, eri1, nmo) eris.oooo = eri1[:nocc,:nocc,:nocc,:nocc].copy() eris.ovoo = eri1[:nocc,nocc:,:nocc,:nocc].copy() eris.ovov = eri1[:nocc,nocc:,:nocc,nocc:].copy() eris.oovv = eri1[:nocc,:nocc,nocc:,nocc:].copy() eris.ovvo = eri1[:nocc,nocc:,nocc:,:nocc].copy() eris.ovvv = eri1[:nocc,nocc:,nocc:,nocc:].copy() eris.vvvv = eri1[nocc:,nocc:,nocc:,nocc:].copy() logger.timer(mycc, 'CCSD integral transformation', *cput0) return eris def _make_eris_outcore(mycc, mo_coeff=None): from pyscf.scf.hf import RHF assert isinstance(mycc._scf, RHF) cput0 = (logger.process_clock(), logger.perf_counter()) log = logger.Logger(mycc.stdout, mycc.verbose) eris = _ChemistsERIs() eris._common_init_(mycc, mo_coeff) mol = mycc.mol mo_coeff = eris.mo_coeff nocc = eris.nocc nao, nmo = mo_coeff.shape nvir = nmo - nocc eris.feri1 = lib.H5TmpFile() eris.oooo = eris.feri1.create_dataset('oooo', (nocc,nocc,nocc,nocc), 'f8') eris.ovoo = eris.feri1.create_dataset('ovoo', (nocc,nvir,nocc,nocc), 'f8', chunks=(nocc,1,nocc,nocc)) eris.ovov = eris.feri1.create_dataset('ovov', (nocc,nvir,nocc,nvir), 'f8', chunks=(nocc,1,nocc,nvir)) eris.ovvo = eris.feri1.create_dataset('ovvo', (nocc,nvir,nvir,nocc), 'f8', chunks=(nocc,1,nvir,nocc)) eris.ovvv = eris.feri1.create_dataset('ovvv', (nocc,nvir,nvir,nvir), 'f8') eris.oovv = eris.feri1.create_dataset('oovv', (nocc,nocc,nvir,nvir), 'f8', chunks=(nocc,nocc,1,nvir)) eris.vvvv = eris.feri1.create_dataset('vvvv', (nvir,nvir,nvir,nvir), 'f8') max_memory = max(MEMORYMIN, mycc.max_memory-lib.current_memory()[0]) ftmp = lib.H5TmpFile() ao2mo.full(mol, mo_coeff, ftmp, max_memory=max_memory, verbose=log) eri = ftmp['eri_mo'] nocc_pair = nocc*(nocc+1)//2 tril2sq = lib.square_mat_in_trilu_indices(nmo) oo = eri[:nocc_pair] eris.oooo[:] = ao2mo.restore(1, oo[:,:nocc_pair], nocc) oovv = lib.take_2d(oo, tril2sq[:nocc,:nocc].ravel(), tril2sq[nocc:,nocc:].ravel()) eris.oovv[:] = oovv.reshape(nocc,nocc,nvir,nvir) oo = oovv = None tril2sq = lib.square_mat_in_trilu_indices(nmo) blksize = min(nvir, max(BLKMIN, int(max_memory*1e6/8/nmo**3/2))) for p0, p1 in lib.prange(0, nvir, blksize): q0, q1 = p0+nocc, p1+nocc off0 = q0*(q0+1)//2 off1 = q1*(q1+1)//2 buf = lib.unpack_tril(eri[off0:off1]) tmp = buf[ tril2sq[q0:q1,:nocc] - off0 ] eris.ovoo[:,p0:p1] = tmp[:,:,:nocc,:nocc].transpose(1,0,2,3) eris.ovvo[:,p0:p1] = tmp[:,:,nocc:,:nocc].transpose(1,0,2,3) eris.ovov[:,p0:p1] = tmp[:,:,:nocc,nocc:].transpose(1,0,2,3) eris.ovvv[:,p0:p1] = tmp[:,:,nocc:,nocc:].transpose(1,0,2,3) tmp = buf[ tril2sq[q0:q1,nocc:q1] - off0 ] eris.vvvv[p0:p1,:p1] = tmp[:,:,nocc:,nocc:] if p0 > 0: eris.vvvv[:p0,p0:p1] = tmp[:,:p0,nocc:,nocc:].transpose(1,0,2,3) buf = tmp = None log.timer('CCSD integral transformation', *cput0) return eris if __name__ == '__main__': from pyscf import scf from pyscf import gto from pyscf.cc import gccsd mol = gto.Mole() mol.atom = [ [8 , (0. , 0. , 0.)], [1 , (0. , -0.757 , 0.587)], [1 , (0. , 0.757 , 0.587)]] mol.basis = 'cc-pvdz' #mol.basis = '3-21G' mol.verbose = 0 mol.spin = 0 mol.build() mf = scf.RHF(mol).run(conv_tol=1e-14) mycc = RCCSD(mf) mycc.max_memory = 0 eris = mycc.ao2mo() emp2, t1, t2 = mycc.init_amps(eris) print(lib.finger(t2) - 0.044540097905897198) np.random.seed(1) t1 = np.random.random(t1.shape)*.1 t2 = np.random.random(t2.shape)*.1 t2 = t2 + t2.transpose(1,0,3,2) t1, t2 = update_amps(mycc, t1, t2, eris) print(lib.finger(t1) - 0.25118555558133576) print(lib.finger(t2) - 0.02352137419932243) ecc, t1, t2 = mycc.kernel() print(ecc - -0.21334326214236796) e, v = mycc.ipccsd(nroots=3) print(e[0] - 0.43356041409195489) print(e[1] - 0.51876598058509493) print(e[2] - 0.6782879569941862 ) e, v = mycc.eeccsd(nroots=4) print(e[0] - 0.2757159395886167) print(e[1] - 0.2757159395886167) print(e[2] - 0.2757159395886167) print(e[3] - 0.3005716731825082) mol = gto.Mole() mol.verbose = 0 mol.atom = [ [8 , (0. , 0. , 0.)], [1 , (0. , -0.757 , 0.587)], [1 , (0. , 0.757 , 0.587)]] mol.basis = '631g' mol.build() mf = scf.RHF(mol) mf.conv_tol = 1e-16 mf.scf() mo_coeff = mf.mo_coeff + np.sin(mf.mo_coeff) * .01j nao = mo_coeff.shape[0] eri = ao2mo.restore(1, mf._eri, nao) eri0 = lib.einsum('pqrs,pi,qj,rk,sl->ijkl', eri, mo_coeff.conj(), mo_coeff, mo_coeff.conj(), mo_coeff) nocc, nvir = 5, nao-5 eris = _ChemistsERIs(mol) eris.oooo = eri0[:nocc,:nocc,:nocc,:nocc].copy() eris.ovoo = eri0[:nocc,nocc:,:nocc,:nocc].copy() eris.oovv = eri0[:nocc,:nocc,nocc:,nocc:].copy() eris.ovvo = eri0[:nocc,nocc:,nocc:,:nocc].copy() eris.ovov = eri0[:nocc,nocc:,:nocc,nocc:].copy() eris.ovvv = eri0[:nocc,nocc:,nocc:,nocc:].copy() eris.vvvv = eri0[nocc:,nocc:,nocc:,nocc:].copy() eris.fock = np.diag(mf.mo_energy) eris.mo_energy = mf.mo_energy np.random.seed(1) t1 = np.random.random((nocc,nvir)) + np.random.random((nocc,nvir))*.1j - .5 t2 = np.random.random((nocc,nocc,nvir,nvir)) - .5 t2 = t2 + np.sin(t2) * .1j t2 = t2 + t2.transpose(1,0,3,2) mycc = RCCSD(mf) t1new_ref, t2new_ref = update_amps(mycc, t1, t2, eris) orbspin = np.zeros(nao*2, dtype=int) orbspin[1::2] = 1 eri1 = np.zeros([nao*2]*4, dtype=np.complex128) eri1[0::2,0::2,0::2,0::2] = eri1[0::2,0::2,1::2,1::2] = eri0 eri1[1::2,1::2,0::2,0::2] = eri1[1::2,1::2,1::2,1::2] = eri0 eri1 = eri1.transpose(0,2,1,3) - eri1.transpose(0,2,3,1) erig = gccsd._PhysicistsERIs(mol) nocc *= 2 nvir *= 2 erig.oooo = eri1[:nocc,:nocc,:nocc,:nocc].copy() erig.ooov = eri1[:nocc,:nocc,:nocc,nocc:].copy() erig.ovov = eri1[:nocc,nocc:,:nocc,nocc:].copy() erig.ovvo = eri1[:nocc,nocc:,nocc:,:nocc].copy() erig.oovv = eri1[:nocc,:nocc,nocc:,nocc:].copy() erig.ovvv = eri1[:nocc,nocc:,nocc:,nocc:].copy() erig.vvvv = eri1[nocc:,nocc:,nocc:,nocc:].copy() mo_e = np.array([mf.mo_energy]*2) erig.fock = np.diag(mo_e.T.ravel()) myccg = gccsd.GCCSD(scf.addons.convert_to_ghf(mf)) t1, t2 = myccg.amplitudes_from_ccsd(t1, t2) t1new, t2new = gccsd.update_amps(myccg, t1, t2, erig) print(abs(t1new[0::2,0::2]-t1new_ref).max()) t2aa = t2new[0::2,0::2,0::2,0::2] t2ab = t2new[0::2,1::2,0::2,1::2] print(abs(t2ab-t2new_ref).max()) print(abs(t2ab-t2ab.transpose(1,0,2,3) - t2aa).max())