예제 #1
0
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]
예제 #2
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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
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
예제 #6
0
    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
예제 #7
0
        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
예제 #8
0
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
예제 #9
0
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
예제 #10
0
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