Source code for vayesta.ewf.ufragment

import numpy as np

import pyscf
import pyscf.cc

import vayesta.core.ao2mo
from vayesta.core.util import dot, einsum, log_method, with_doc
from vayesta.core.qemb import UFragment as BaseFragment
from vayesta.ewf.fragment import Fragment as RFragment


[docs]class Fragment(RFragment, BaseFragment):
[docs] def set_cas(self, iaos=None, c_occ=None, c_vir=None, minao="auto", dmet_threshold=None): """Set complete active space for tailored CCSD and active-space CC methods.""" if iaos is not None: raise NotImplementedError("Unrestricted IAO-based CAS not implemented yet.") self.opts.c_cas_occ = c_occ self.opts.c_cas_vir = c_vir return c_occ, c_vir
[docs] def get_fragment_energy(self, c1, c2, hamil=None, fock=None, axis1="fragment", c2ba_order="ba"): """Calculate fragment correlation energy contribution from projected C1, C2. Parameters ---------- c1: (n(occ-CO), n(vir-CO)) array Fragment projected C1-amplitudes. c2: (n(occ-CO), n(occ-CO), n(vir-CO), n(vir-CO)) array Fragment projected C2-amplitudes. hamil : ClusterHamiltonian object. Object representing cluster hamiltonian, possibly including cached ERIs. fock: (n(AO), n(AO)) array, optional Fock matrix in AO representation. If None, self.base.get_fock_for_energy() is used. Default: None. Returns ------- e_singles: float Fragment correlation energy contribution from single excitations. e_doubles: float Fragment correlation energy contribution from double excitations. e_corr: float Total fragment correlation energy contribution. """ if axis1 == "fragment": pxa, pxb = self.get_overlap("proj|cluster-occ") oaf, obf = np.s_[: pxa.shape[0]], np.s_[: pxb.shape[0]] # Doubles energy # TODO: loop to reduce memory? if hamil is None: hamil = self.hamil gaa = hamil.get_eris_bare(block="ovov") gab = hamil.get_eris_bare(block="ovOV") gbb = hamil.get_eris_bare(block="OVOV") nocc = gab.shape[0], gab.shape[2] nvir = gab.shape[1], gab.shape[3] self.log.debugv("nocc= %d, %d nvir= %d, %d", *nocc, *nvir) oa, ob = np.s_[:nocc[0]], np.s_[:nocc[1]] va, vb = np.s_[:nvir[0]], np.s_[:nvir[1]] if axis1 == "fragment": assert len(c2) == 4 caa, cab, cba, cbb = c2 # Remove padding caa = caa[oaf, oa, va, va] cab = cab[oaf, ob, va, vb] cba = cba[obf, oa, vb, va] cbb = cbb[obf, ob, vb, vb] if c2ba_order == "ab": cba = cba.transpose(1, 0, 3, 2) e_doubles = ( einsum("xi,xjab,iajb", pxa, caa, gaa) / 4 - einsum("xi,xjab,ibja", pxa, caa, gaa) / 4 + einsum("xi,xjab,iajb", pxb, cbb, gbb) / 4 - einsum("xi,xjab,ibja", pxb, cbb, gbb) / 4 + einsum("xi,xjab,iajb", pxa, cab, gab) / 2 + einsum("xi,xjab,jbia", pxb, cba, gab) / 2 ) else: assert len(c2) == 3 caa, cab, cbb = c2 # Remove padding caa = caa[oa, oa, va, va] cab = cab[oa, ob, va, vb] cbb = cbb[ob, ob, vb, vb] e_doubles = ( einsum("ijab,iajb", caa, gaa) / 4 - einsum("ijab,ibja", caa, gaa) / 4 + einsum("ijab,iajb", cbb, gbb) / 4 - einsum("ijab,ibja", cbb, gbb) / 4 + einsum("ijab,iajb", cab, gab) ) # --- Singles energy (zero for HF-reference) if c1 is not None: # if hasattr(eris, 'fock'): # fa = eris.fock[0][oa,va] # fb = eris.fock[1][ob,vb] # else: # fock = self.base.get_fock() # fa = dot(self.c_active_occ[0].T, fock[0], self.c_active_vir[0]) # fb = dot(self.c_active_occ[1].T, fock[1], self.c_active_vir[1]) if fock is None: fock = self.base.get_fock_for_energy() fova = dot(self.cluster.c_active_occ[0].T, fock[0], self.cluster.c_active_vir[0]) fovb = dot(self.cluster.c_active_occ[1].T, fock[1], self.cluster.c_active_vir[1]) assert len(c1) == 2 ca, cb = c1 # Remove padding ca = ca[oaf, va] cb = cb[obf, vb] e_singles = 0 if fova.shape[0] != 0 and fova.shape[1] != 0: if axis1 == "fragment": e_singles += einsum("ia,xi,xa->", fova, pxa, ca) else: e_singles += np.sum(fova * ca) if fovb.shape[0] != 0 and fovb.shape[1] != 0: if axis1 == "fragment": e_singles += einsum("ia,xi,xa->", fovb, pxb, cb) else: e_singles += np.sum(fovb * cb) else: e_singles = 0 e_singles = self.sym_factor * e_singles e_doubles = self.sym_factor * e_doubles e_corr = e_singles + e_doubles return e_singles, e_doubles, e_corr
@with_doc(RFragment._get_projected_gamma1_intermediates) def _get_projected_gamma1_intermediates(self, t_as_lambda=None, sym_t2=True): raise NotImplementedError @with_doc(RFragment._get_projected_gamma2_intermediates) def _get_projected_gamma2_intermediates(self, t_as_lambda=None, sym_t2=True): t1, t2, l1, l2, t1x, t2x, l1x, l2x = self._ccsd_amplitudes_for_dm(t_as_lambda=t_as_lambda, sym_t2=sym_t2) # Only incore for UCCSD: # d2 = pyscf.cc.uccsd_rdm._gamma2_intermediates(None, t1, t2, l1x, l2x) d2ovov, *d2 = pyscf.cc.uccsd_rdm._gamma2_intermediates(None, t1, t2, l1x, l2x) # Correction of unprojected terms (which do not involve L1/L2): # dovov: dtau = (t2x[0] - t2[0] + einsum("ia,jb->ijab", t1x[0] - t1[0], 2 * t1[0])) / 4 d2ovov[0][:] += dtau.transpose(0, 2, 1, 3) d2ovov[0][:] -= dtau.transpose(0, 3, 1, 2) # dovOV (symmetrize between t1x[0] and t1x[1]; t2x[1] should already be symmetrized): dtau = ( (t2x[1] - t2[1]) + einsum("ia,jb->ijab", t1x[0] - t1[0], t1[1] / 2) + einsum("ia,jb->ijab", t1[0] / 2, t1x[1] - t1[1]) ) / 2 d2ovov[1][:] += dtau.transpose(0, 2, 1, 3) # dOVOV: dtau = (t2x[2] - t2[2] + einsum("ia,jb->ijab", t1x[1] - t1[1], 2 * t1[1])) / 4 d2ovov[3][:] += dtau.transpose(0, 2, 1, 3) d2ovov[3][:] -= dtau.transpose(0, 3, 1, 2) d2 = (d2ovov, *d2) return d2
[docs] def make_fragment_dm2cumulant(self, t_as_lambda=None, sym_t2=True, approx_cumulant=True, full_shape=True): if int(approx_cumulant) != 1: raise NotImplementedError if self.solver == "MP2": t2xaa, t2xab, t2xbb = self.results.pwf.restore(sym=sym_t2).as_ccsd().t2 dovov = t2xaa.transpose(0, 2, 1, 3) dovOV = t2xab.transpose(0, 2, 1, 3) dOVOV = t2xbb.transpose(0, 2, 1, 3) if not full_shape: return (dovov, dovOV, dOVOV) nocca, nvira, noccb, nvirb = dovOV.shape norba = nocca + nvira norbb = noccb + nvirb oa, va = np.s_[:nocca], np.s_[nocca:] ob, vb = np.s_[:noccb], np.s_[noccb:] dm2aa = np.zeros(4 * [norba]) dm2ab = np.zeros(2 * [norba] + 2 * [norbb]) dm2bb = np.zeros(4 * [norbb]) dm2aa[oa, va, oa, va] = dovov dm2aa[va, oa, va, oa] = dovov.transpose(1, 0, 3, 2) dm2ab[oa, va, ob, vb] = dovOV dm2ab[va, oa, vb, ob] = dovOV.transpose(1, 0, 3, 2) dm2bb[ob, vb, ob, vb] = dOVOV dm2bb[vb, ob, vb, ob] = dOVOV.transpose(1, 0, 3, 2) return (dm2aa, dm2ab, dm2bb) cc = d1 = None d2 = self._get_projected_gamma2_intermediates(t_as_lambda=t_as_lambda, sym_t2=sym_t2) dm2 = pyscf.cc.uccsd_rdm._make_rdm2(cc, d1, d2, with_dm1=False, with_frozen=False) return dm2
[docs] @log_method() def make_fragment_dm2cumulant_energy(self, hamil=None, t_as_lambda=None, sym_t2=True, approx_cumulant=True): if hamil is None: hamil = self.hamil if self.solver == "MP2": dm2 = self.make_fragment_dm2cumulant( t_as_lambda=t_as_lambda, sym_t2=sym_t2, approx_cumulant=approx_cumulant, full_shape=False ) dm2aa, dm2ab, dm2bb = dm2 gaa = hamil.get_eris_bare(block="ovov") gab = hamil.get_eris_bare(block="ovOV") gbb = hamil.get_eris_bare(block="OVOV") return ( 2.0 * ( einsum("ijkl,ijkl->", gaa, dm2aa) + einsum("ijkl,ijkl->", gab, dm2ab) * 2 + einsum("ijkl,ijkl->", gbb, dm2bb) ) / 2 ) elif approx_cumulant: # Working hypothesis: this branch will effectively always uses `approx_cumulant=True`. eris = hamil.get_dummy_eri_object(force_bare=True, with_vext=False) d2 = self._get_projected_gamma2_intermediates(t_as_lambda=t_as_lambda, sym_t2=sym_t2) return vayesta.core.ao2mo.helper.contract_dm2intermeds_eris_uhf(d2, eris) / 2 else: dm2 = self.make_fragment_dm2cumulant( t_as_lambda=t_as_lambda, sym_t2=sym_t2, approx_cumulant=approx_cumulant, full_shape=True ) dm2aa, dm2ab, dm2bb = dm2 gaa, gab, gbb = hamil.get_eris_bare() return ( einsum("ijkl,ijkl->", gaa, dm2aa) + einsum("ijkl,ijkl->", gab, dm2ab) * 2 + einsum("ijkl,ijkl->", gbb, dm2bb) ) / 2