def energy_mp2_aux(mo, se, both_sides=False): ''' Calculates the two-body contribution to the electronic energy using the MOs and the auxiliary representation of the self-energy according the the MP2 form of the Galitskii-Migdal formula. Parameters ---------- mo : (n) ndarray MO energies se : Aux auxiliary representation of self-energy both_sides : bool, optional if True, calculate both halves of the functional and return the mean, default False Returns ------- e2b : float two-body contribution to electronic energy ''' if isinstance(se, (tuple, list)): n = len(se) if util.iter_depth(mo) == 2: return sum([ energy_mp2_aux(mo[i], se[i], both_sides=both_sides) for i in range(n) ]) / n else: return sum([ energy_mp2_aux(mo, se[i], both_sides=both_sides) for i in range(n) ]) / n nphys = se.nphys occ = mo < se.chempot vir = mo >= se.chempot vxk = se.v_vir[occ] dxk = 1.0 / util.outer_sum([mo[occ], -se.e_vir]) e2b = util.einsum('xk,xk,xk->', vxk, vxk.conj(), dxk) if both_sides: vxk = se.v_occ[vir] dxk = -1.0 / util.outer_sum([mo[vir], -se.e_occ]) e2b += util.einsum('xk,xk,xk->', vxk, vxk.conj(), dxk) e2b *= 0.5 return np.ravel(e2b.real)[0]
def iterate_qp(self): # Should e_qp become e_gf or is that only for evGW? e_qp = self.e_qp e_qp_0 = e_qp.copy() e = self.gf.e v = self.gf.v for niter in range(1, self.options['qp_maxiter'] + 1): denom = 1.0 / util.outer_sum(e_qp, -e) se = v * v * denom z = se * denom se = np.sum(se, axis=1) z = np.sum(z, axis=1) z = 1.0 / (1.0 + z) e_qp_prev = e_qp.copy() e_qp = e_qp_0 + z.real * se error = np.max(np.absolute(e_qp.real - e_qp_prev.real)) if error < self.options['qp_etol']: break self.e_qp = e_qp return e_qp
def solve_casida(self): #TODO: this step is n^6 and inefficient in memory, rethink e_ia = util.outer_sum([-self.gf.e_occ, self.gf.e_vir]) co = self.gf.v[:self.nphys, self.gf.e < self.chempot] cv = self.gf.v[:self.nphys, self.gf.e >= self.chempot] iajb = util.ao2mo(self.eri, co, cv, co, cv).reshape((e_ia.size, ) * 2) apb = np.diag(e_ia.flatten()) + 4.0 * iajb amb = np.diag(np.sqrt(e_ia.flatten())) h_rpa = util.dots((amb, apb, amb)) e_rpa, v_rpa = util.eigh(h_rpa) e_rpa = np.sqrt(e_rpa) xpy = util.einsum('ij,jk,k->ik', amb, v_rpa, 1.0 / np.sqrt(e_rpa)) xpy *= np.sqrt(2.0) self.rpa = (e_rpa, v_rpa, xpy) return self.rpa
def build_part(gf_occ, gf_vir, eri, sym_in='s2'): ''' Builds the truncated occupied (or virtual) self-energy. Parameters ---------- gf_occ : Aux Occupied (or virtual) Green's function gf_vir : Aux Virtual (or occupied) Green's function eri : ndarray Cholesky-decomposed DF ERI tensor sym_in : str, optional Symmetry of `eri`, default 's2' Returns ------- se : Aux Occupied (or virtual) truncated self-energy ''' syms = dict(sym_in=sym_in, sym_out='s1') nphys = gf_occ.nphys nocc = gf_occ.naux nvir = gf_vir.naux ixq = OptRAGF2.ao2mo(eri, gf_occ.v, np.eye(nphys), **syms).T qja = OptRAGF2.ao2mo(eri, gf_occ.v, gf_vir.v, **syms) vv = np.zeros((nphys, nphys), dtype=types.float64) vev = np.zeros((nphys, nphys), dtype=types.float64) if libagf2._libagf2 is not None: vv, vev = libagf2.build_part_loop_rhf(ixq, qja, gf_occ, gf_vir, 0, nocc, vv=vv, vev=vev) else: buf1 = np.zeros((nphys, nocc * nvir), dtype=types.float64) buf2 = np.zeros((nocc * nphys, nvir), dtype=types.float64) for i in range(nocc): xja = np.dot(ixq[i * nphys:(i + 1) * nphys], qja, out=buf1) xia = np.dot(ixq, qja[:, i * nvir:(i + 1) * nvir], out=buf2) xia = util.reshape_internal(xia, (nocc, nphys, nvir), (0, 1), (nphys, nocc * nvir)) eja = util.outer_sum([gf_occ.e[i] + gf_occ.e, -gf_vir.e]) eja = eja.ravel() vv = util.dgemm(xja, xja.T, alpha=2, beta=1, c=vv) vv = util.dgemm(xja, xia.T, alpha=-1, beta=1, c=vv) vev = util.dgemm(xja * eja[None], xja.T, alpha=2, beta=1, c=vev) vev = util.dgemm(xja * eja[None], xia.T, alpha=-1, beta=1, c=vev) b = np.linalg.cholesky(vv).T b_inv = np.linalg.inv(b) m = np.dot(np.dot(b_inv.T, vev), b_inv) e, c = util.eigh(m) c = np.dot(b.T, c[:nphys]) se = gf_occ.new(e, c) return se
def build_ump2_part_se_direct(eo, ev, xija, grid, chempot=0.0, ordering='feynman'): ''' Builds a set of auxiliaries representing all (i,j,a) or (a,b,i) diagrams for an unrestricted reference. Poles are summed straight into the self-energy and returns an `ndarray` instead of `Aux`. Parameters ---------- eo : (o) ndarray occupied (virtual) energies ev : (v) ndarray virtual (occupied) energies xija : (n,o,o,v) two-electron integrals indexed as physical, occupied, occupied, virtual (physical, virtual, virtual, occupied) grid : (k) ImFqGrid, ImFqQuad or ReFqGrid grid chempot : tuple of float, optional chemical potential for alpha, beta (beta, alpha) spin ordering : str ordering of the poles {'feynman', 'advanced', 'retarded'} (default 'feynman') Returns ------- se : (k,n,n) ndarray frequency-dependent self-energy ''' if not _is_tuple(chempot): chempot = (chempot, chempot) #TODO write in C if grid.axis == 'imag': if ordering == 'feynman': get_s = lambda x: np.sign(x) elif ordering == 'advanced': get_s = lambda x: np.ones(x.shape, dtype=types.int64) elif ordering == 'retarded': get_s = lambda x: -np.ones(x.shape, dtype=types.int64) else: get_s = lambda x: 0.0 w = grid.prefac * grid.values nphys, nocca, _, nvira = xija[0].shape se = np.zeros((grid.shape[0], nphys, nphys), dtype=types.complex128) eova = util.outer_sum([eo[0], -ev[0]]).flatten() - chempot[0] eovb = util.outer_sum([eo[1], -ev[1]]).flatten() - chempot[0] for i in range(nocca): ei_a = eo[0][i] + eova ei_b = eo[0][i] + eovb vi_a = xija[0][:, i].reshape((nphys, -1)) vip_a = xija[0][:, :, i].reshape((nphys, -1)) vi_b = xija[1][:, i].reshape((nphys, -1)) di_a = 1.0 / util.outer_sum([w, -ei_a + get_s(ei_a) * grid.eta * 1.0j]) di_b = 1.0 / util.outer_sum([w, -ei_b + get_s(ei_b) * grid.eta * 1.0j]) se += util.einsum('wk,xk,yk->wxy', di_a, vi_a, (vi_a - vip_a).conj()) se += util.einsum('wk,xk,yk->wxy', di_b, vi_b, (vi_b).conj()) return se
def run(self): t_amp = np.zeros((self.nvir, self.nvir, self.nocc, self.nocc), dtype=types.float64) o = slice(None, self.nocc) v = slice(self.nocc, None) opdm_corr = np.zeros((self.nso,)*2, dtype=types.float64) opdm_ref = opdm_corr.copy() opdm_ref[o,o] = np.eye(self.nocc) tpdm_corr = np.zeros((self.nso,)*4, dtype=types.float64) x = opdm_corr.copy() eija = util.outer_sum([-self.e[v], -self.e[v], self.e[o], self.e[o]]) eija = 1.0 / eija if self.diis: diis = util.DIIS(self.diis_space) for niter in range(1, self.maxiter+1): f = self.h1e_mo + util.einsum('piqi->pq', self.eri_mo[:,o,:,o]) fp = f.copy() np.fill_diagonal(fp, 0.0) e = f.diagonal() t1 = self.eri_mo[v,v,o,o] t2 = util.einsum('ac,cbij->abij', fp[v,v], t_amp) t3 = util.einsum('ki,abkj->abij', fp[o,o], t_amp) t_amp = t1.copy() t_amp += t2 - t2.transpose(1,0,2,3) t_amp -= t3 - t3.transpose(0,1,3,2) t_amp *= eija if niter > 1: if self.diis: t_amp = diis.update(t_amp) if self.damping > 0.0: damping = self.damping t_amp = (1.0 - damping) * t_amp + damping * self._t_prev if self.damping > 0.0: self._t_prev = t_amp.copy() opdm_corr[v,v] = util.einsum('ijac,bcij->ba', t_amp.T, t_amp) * 0.5 opdm_corr[o,o] = util.einsum('jkab,abik->ji', t_amp.T, t_amp) * -0.5 opdm = opdm_corr + opdm_ref tpdm_corr[v,v,o,o] = t_amp tpdm_corr[o,o,v,v] = t_amp.T tpdm2 = util.einsum('rp,sq->rspq', opdm_corr, opdm_ref) tpdm3 = util.einsum('rp,sq->rspq', opdm_ref, opdm_ref) tpdm = tpdm_corr.copy() tpdm += tpdm2 - tpdm2.transpose(1,0,2,3) tpdm -= tpdm2.transpose(0,1,3,2) - tpdm2.transpose(1,0,3,2) tpdm += tpdm3 - tpdm3.transpose(1,0,2,3) fnr = util.einsum('pr,rq->pq', self.h1e_mo, opdm) fnr += util.einsum('prst,stqr->pq', self.eri_mo, tpdm) * 0.5 x[v,o] = ((fnr - fnr.T)[v,o]) / util.outer_sum([-self.e[v], self.e[o]]) u = expm(x - x.T) c = np.dot(self.c, u) self.c = c self.h1e_mo = util.ao2mo(self.h1e_ao, c, c) self.eri_mo = util.ao2mo(self.eri_ao, c, c, c, c) e_prev = self.e_tot self.e_1body = util.einsum('pq,qp->', self.h1e_mo, opdm) self.e_1body += self.hf.e_nuc self.e_2body = 0.25 * util.einsum('pqrs,rspq->', self.eri_mo, tpdm) if abs(self.e_tot - e_prev) < self.etol: break return self
def _build_part(s=slice(None)): vv = np.zeros((nphys, nphys), dtype=types.float64) vev = np.zeros((nphys, nphys), dtype=types.float64) if libagf2._libagf2 is not None: vv, vev = libagf2.build_part_loop_uhf(ixq[s], qja[s], gf_occ[s], gf_vir[s], 0, nocc[s][0], vv=vv, vev=vev) else: nocca, noccb = nocc[s] nvira, nvirb = nvir[s] ixq_a, ixq_b = ixq[s] qja_a, qja_b = qja[s] gf_occ_a, gf_occ_b = gf_occ[s] gf_vir_a, gf_vir_b = gf_vir[s] buf1 = np.zeros((nphys, nocca * nvira), dtype=types.float64) buf2 = np.zeros((nocca * nphys, nvira), dtype=types.float64) buf3 = np.zeros((nphys, noccb * nvirb), dtype=types.float64) for i in range(nocca): xja_aa = np.dot(ixq_a[i * nphys:(i + 1) * nphys], qja_a, out=buf1) xia_aa = np.dot(ixq_a, qja_a[:, i * nvira:(i + 1) * nvira], out=buf2) xia_aa = util.reshape_internal(xia_aa, (nocca, nphys, nvira), (0, 1), (nphys, nocca * nvira)) xja_ab = np.dot(ixq_a[i * nphys:(i + 1) * nphys], qja_b, out=buf3) eja_aa = util.outer_sum( [gf_occ_a.e[i] + gf_occ_a.e, -gf_vir_a.e]).flatten() eja_ab = util.outer_sum( [gf_occ_a.e[i] + gf_occ_b.e, -gf_vir_b.e]).flatten() vv = util.dgemm(xja_aa, xja_aa.T, alpha=1, beta=1, c=vv) vv = util.dgemm(xja_aa, xia_aa.T, alpha=-1, beta=1, c=vv) vv = util.dgemm(xja_ab, xja_ab.T, alpha=1, beta=1, c=vv) vev = util.dgemm(xja_aa * eja_aa[None], xja_aa.T, alpha=1, beta=1, c=vev) vev = util.dgemm(xja_aa * eja_aa[None], xia_aa.T, alpha=-1, beta=1, c=vev) vev = util.dgemm(xja_ab * eja_ab[None], xja_ab.T, alpha=1, beta=1, c=vev) b = np.linalg.cholesky(vv).T b_inv = np.linalg.inv(b) m = np.dot(np.dot(b_inv.T, vev), b_inv) e, c = util.eigh(m) c = np.dot(b.T, c[:nphys]) se = gf_occ[s][0].new(e, c) return se
def build_rmp2_part_se_direct(eo, ev, xija, grid, chempot=0.0, ordering='feynman'): ''' Builds a set of auxiliaries representing all (i,j,a) or (a,b,i) diagrams for a restricted reference. Poles are summed straight into the self-energy and returns an `ndarray` instead of `Aux`. Parameters ---------- eo : (o) ndarray occupied (virtual) energies ev : (v) ndarray virtual (occupied) energies xija : (n,o,o,v) two-electron integrals indexed as physical, occupied, occupied, virtual (physical, virtual, virtual, occupied) grid : (k) ImFqGrid, ImFqQuad or ReFqGrid grid chempot : float, optional chemical potential ordering : str ordering of the poles {'feynman', 'advanced', 'retarded'} (default 'feynman') Returns ------- se : (k,n,n) ndarray frequency-dependent self-energy ''' #TODO write in C if grid.axis == 'imag': if ordering == 'feynman': get_s = lambda x: np.sign(x) elif ordering == 'advanced': get_s = lambda x: np.ones(x.shape, dtype=types.int64) elif ordering == 'retarded': get_s = lambda x: -np.ones(x.shape, dtype=types.int64) else: get_s = lambda x: 0.0 w = grid.prefac * grid.values nphys, nocc, _, nvir = xija.shape se = np.zeros((grid.shape[0], nphys, nphys), dtype=types.complex128) eov = util.outer_sum([eo, -ev]).flatten() for i in range(nocc): ei = eo[i] + eov - chempot vi = xija[:, i].reshape((nphys, -1)) vip = xija[:, :, i].reshape((nphys, -1)) di = 1.0 / util.outer_sum([w, -ei + get_s(ei) * grid.eta * 1.0j]) se += util.einsum('wk,xk,yk->wxy', di, vi, (2 * vi - vip).conj()) return se
def build_dfump2_part_se_direct(eo, ev, ixq, qja, grid, chempot=0.0, ordering='feynman'): ''' Builds a set of auxiliaries representing all (i,j,a) or (a,b,i) diagrams for an unrestricted reference. Poles are summed straight into the self-energy and returns an `ndarray` instead of `Aux`. Parameters ---------- eo : (o) ndarray occupied (virtual) energies ev : (v) ndarray virtual (occupied) energies ixq : 1-tuple or 2-tuple of (n,o,o,v) density-fitted two-electron integrals index as occupied, physical, auxiliary (virtual, physical, auxiliary) for alpha, beta (beta, alpha) spin. Only alpha (beta) is required. qja : 2-tuple of (n,o,o,v) density-fitted two-electron integrals index as auxiliary, occupied, virtual (auxiliary, virtual, occupied) for alpha, beta (beta, alpha) spin. grid : (k) ImFqGrid, ImFqQuad or ReFqGrid grid chempot : tuple of float, optional chemical potential for alpha, beta (beta, alpha) spin ordering : str ordering of the poles {'feynman', 'advanced', 'retarded'} (default 'feynman') Returns ------- se : (k,n,n) ndarray frequency-dependent self-energy ''' if not _is_tuple(chempot): chempot = (chempot, chempot) #TODO write in C if grid.axis == 'imag': if ordering == 'feynman': get_s = lambda x : np.sign(x) elif ordering == 'advanced': get_s = lambda x : np.ones(x.shape, dtype=types.int64) elif ordering == 'retarded': get_s = lambda x : -np.ones(x.shape, dtype=types.int64) else: get_s = lambda x : 0.0 w = grid.prefac * grid.values nphys = ixq[0].shape[1] ndf, nocca, nvira = qja[0].shape _, noccb, nvirb = qja[1].shape se = np.zeros((grid.shape[0], nphys, nphys), dtype=types.complex128) ixq = (ixq[0].reshape((nocca*nphys, ndf)),) qja = (qja[0].reshape((ndf, nocca*nvira)), qja[1].reshape((ndf, noccb*nvirb))) eova = util.outer_sum([eo[0], -ev[0]]).flatten() - chempot[0] eovb = util.outer_sum([eo[1], -ev[1]]).flatten() - chempot[0] for i in range(nocca): ei_a = eo[0][i] + eova ei_b = eo[0][i] + eovb xq_a = ixq[0][i*nphys:(i+1)*nphys] vi_a = np.dot(xq_a.conj(), qja[0]).reshape((nphys, -1)) vi_b = np.dot(xq_a.conj(), qja[1]).reshape((nphys, -1)) vip_a = np.dot(ixq[0].conj(), qja[0][:,i*nvira:(i+1)*nvira]) vip_a = util.reshape_internal(vip_a, (nocca, nphys, nvira), (0,1), (nphys, nocca*nvira)) di_a = 1.0 / util.outer_sum([w, -ei_a + get_s(ei_a) * grid.eta * 1.0j]) di_b = 1.0 / util.outer_sum([w, -ei_b + get_s(ei_b) * grid.eta * 1.0j]) se += util.einsum('wk,xk,yk->wxy', di_a, vi_a, (vi_a - vip_a).conj()) se += util.einsum('wk,xk,yk->wxy', di_b, vi_b, vi_b.conj()) return se
def build_dfrmp2_part_se_direct(eo, ev, ixq, qja, grid, chempot=0.0, ordering='feynman'): ''' Builds a set of auxiliaries representing all (i,j,a) or (a,b,i) diagrams for a restricted reference. Poles are summed straight into the self-energy and returns an `ndarray` instead of `Aux`. Parameters ---------- eo : (o) ndarray occupied (virtual) energies ev : (v) ndarray virtual (occupied) energies ixq : (o,n,q) ndarray density-fitted two-electron integrals indexed as occupied, physical, auxiliary (physical, virtual, auxiliary) qja : (q,o,v) ndarray density-fitted two-electron integrals indexed as auxiliary, occupied, virtual (auxiliary, virtual, occupied) grid : (k) ImFqGrid, ImFqQuad or ReFqGrid grid chempot : float, optional chemical potential ordering : str ordering of the poles {'feynman', 'advanced', 'retarded'} (default 'feynman') Returns ------- se : (k,n,n) ndarray frequency-dependent self-energy ''' if grid.axis == 'imag': if ordering == 'feynman': get_s = lambda x: np.sign(x) elif ordering == 'advanced': get_s = lambda x: np.ones(x.shape, dtype=types.int64) elif ordering == 'retarded': get_s = lambda x: -np.ones(x.shape, dtype=types.int64) else: get_s = lambda x: 0.0 w = grid.prefac * grid.values nphys = ixq.shape[1] ndf, nocc, nvir = qja.shape npoles = nocc * nocc * nvir se = np.zeros((grid.shape[0], nphys, nphys), dtype=types.complex128) ixq = ixq.reshape((nocc * nphys, ndf)) qja = qja.reshape((ndf, nocc * nvir)) eov = util.outer_sum([eo, -ev]).flatten() for i in range(nocc): ei = eo[i] + eov - chempot vi = np.dot(ixq[i * nphys:(i + 1) * nphys].conj(), qja).reshape( (nphys, -1)) vip = np.dot(ixq.conj(), qja[:, i * nvir:(i + 1) * nvir]) vip = util.reshape_internal(vip, (nocc, nphys, -1), (0, 1), (nphys, -1)) di = 1.0 / util.outer_sum([w, -ei + get_s(ei) * grid.eta * 1.0j]) se += util.einsum('wk,xk,yk->wxy', di, vi, (2 * vi - vip).conj()) return se