Ejemplo n.º 1
0
    def get_chi(self, xc='RPA', q_c=[0, 0, 0], direction='x'):
        """ Returns v^1/2 chi V^1/2"""
        pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c)
        G_G = pd.G2_qG[0]**0.5
        nG = len(G_G)

        if pd.kd.gamma:
            G_G[0] = 1.0
            if isinstance(direction, str):
                d_v = {
                    'x': [1, 0, 0],
                    'y': [0, 1, 0],
                    'z': [0, 0, 1]
                }[direction]
            else:
                d_v = direction
            d_v = np.asarray(d_v) / np.linalg.norm(d_v)
            W = slice(self.w1, self.w2)
            chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0])
            chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1])
            chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T)

        G_G /= (4 * pi)**0.5

        if self.truncation == 'wigner-seitz':
            kernel = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv,
                                                 self.chi0.calc.wfs.kd.N_c)
            K_G = kernel.get_potential(pd)
            K_G *= G_G**2
            if pd.kd.gamma:
                K_G[0] = 0.0
        elif self.truncation == '2D':
            K_G = truncated_coulomb(pd)
            K_G *= G_G**2
        else:
            K_G = np.ones(nG)

        K_GG = np.zeros((nG, nG), dtype=complex)
        for i in range(nG):
            K_GG[i, i] = K_G[i]

        if xc != 'RPA':
            R_av = self.chi0.calc.atoms.positions / Bohr
            nt_sG = self.chi0.calc.density.nt_sG
            K_GG += calculate_Kxc(pd,
                                  nt_sG,
                                  R_av,
                                  self.chi0.calc.wfs.setups,
                                  self.chi0.calc.density.D_asp,
                                  functional=xc) * G_G * G_G[:, np.newaxis]

        chi_wGG = []
        for chi0_GG in chi0_wGG:
            chi0_GG[:] = chi0_GG / G_G / G_G[:, np.newaxis]
            chi_wGG.append(
                np.dot(np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)),
                       chi0_GG))
        return chi0_wGG, np.array(chi_wGG)
Ejemplo n.º 2
0
    def get_dielectric_matrix(self,
                              xc='RPA',
                              q_c=[0, 0, 0],
                              direction='x',
                              symmetric=True,
                              calculate_chi=False,
                              q_v=None,
                              add_intraband=True):
        """Returns the symmetrized dielectric matrix.

        ::

            \tilde\epsilon_GG' = v^{-1/2}_G \epsilon_GG' v^{1/2}_G',

        where::

            epsilon_GG' = 1 - v_G * P_GG' and P_GG'

        is the polarization.

        ::

            In RPA:   P = chi^0
            In TDDFT: P = (1 - chi^0 * f_xc)^{-1} chi^0

        in addition to RPA one can use the kernels, ALDA, rALDA, rAPBE,
        Bootstrap and LRalpha (long-range kerne), where alpha is a user
        specified parameter (for example xc='LR0.25')

        The head of the inverse symmetrized dielectric matrix is equal
        to the head of the inverse dielectric matrix (inverse dielectric
        function)"""

        pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c)

        N_c = self.chi0.calc.wfs.kd.N_c
        if self.truncation == 'wigner-seitz':
            self.wstc = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv, N_c)
        else:
            self.wstc = None
        K_G = get_coulomb_kernel(pd,
                                 N_c,
                                 truncation=self.truncation,
                                 wstc=self.wstc,
                                 q_v=q_v)**0.5
        nG = len(K_G)

        if pd.kd.gamma:
            if isinstance(direction, str):
                d_v = {
                    'x': [1, 0, 0],
                    'y': [0, 1, 0],
                    'z': [0, 0, 1]
                }[direction]
            else:
                d_v = direction

            d_v = np.asarray(d_v) / np.linalg.norm(d_v)
            W = slice(self.w1, self.w2)
            if add_intraband:
                chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0])
                chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1])
                chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T)
            if q_v is not None:
                print('Restoring q dependence of head and wings of chi0')
                chi0_wGG[:, 1:, 0] *= np.dot(q_v, d_v)
                chi0_wGG[:, 0, 1:] *= np.dot(q_v, d_v)
                chi0_wGG[:, 0, 0] *= np.dot(q_v, d_v)**2

        if xc != 'RPA':
            Kxc_sGG = get_xc_kernel(pd,
                                    self.chi0,
                                    functional=xc,
                                    chi0_wGG=chi0_wGG)

        if calculate_chi:
            chi_wGG = []

        for chi0_GG in chi0_wGG:
            if xc == 'RPA':
                P_GG = chi0_GG
            else:
                P_GG = np.dot(
                    np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, Kxc_sGG[0])),
                    chi0_GG)
            if symmetric:
                e_GG = np.eye(nG) - P_GG * K_G * K_G[:, np.newaxis]
            else:
                K_GG = (K_G**2 * np.ones([nG, nG])).T
                e_GG = np.eye(nG) - P_GG * K_GG
            if calculate_chi:
                K_GG = np.diag(K_G**2)
                if xc != 'RPA':
                    K_GG += Kxc_sGG[0]
                chi_wGG.append(
                    np.dot(np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)),
                           chi0_GG))
            chi0_GG[:] = e_GG

        # chi0_wGG is now the dielectric matrix
        if not calculate_chi:
            return chi0_wGG
        else:
            # chi_wGG is the full density response function..
            return pd, chi0_wGG, np.array(chi_wGG)
Ejemplo n.º 3
0
    def get_chi(self,
                xc='RPA',
                q_c=[0, 0, 0],
                direction='x',
                return_VchiV=True,
                q_v=None):
        """ Returns v^1/2 chi v^1/2. The truncated Coulomb interaction is
        then included as v^-1/2 v_t v^-1/2. This is in order to conform with
        the head and wings of chi0, which is treated specially for q=0."""

        pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c)
        N_c = self.chi0.calc.wfs.kd.N_c
        Kbare_G = get_coulomb_kernel(pd, N_c, truncation=None, q_v=q_v)
        vsqr_G = Kbare_G**0.5
        nG = len(vsqr_G)

        if self.truncation is not None:
            if self.truncation == 'wigner-seitz':
                self.wstc = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv, N_c)
            else:
                self.wstc = None
            Ktrunc_G = get_coulomb_kernel(pd,
                                          N_c,
                                          truncation=self.truncation,
                                          wstc=self.wstc,
                                          q_v=q_v)
            K_GG = np.diag(Ktrunc_G / Kbare_G)
        else:
            K_GG = np.eye(nG, dtype=complex)

        if pd.kd.gamma:
            if isinstance(direction, str):
                d_v = {
                    'x': [1, 0, 0],
                    'y': [0, 1, 0],
                    'z': [0, 0, 1]
                }[direction]
            else:
                d_v = direction
            d_v = np.asarray(d_v) / np.linalg.norm(d_v)
            W = slice(self.w1, self.w2)
            chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0])
            chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1])
            chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T)

        if xc != 'RPA':
            Kxc_sGG = get_xc_kernel(pd,
                                    self.chi0,
                                    functional=xc,
                                    chi0_wGG=chi0_wGG)
            K_GG += Kxc_sGG[0] / vsqr_G / vsqr_G[:, np.newaxis]

        chi_wGG = []
        for chi0_GG in chi0_wGG:
            """v^1/2 chi0 V^1/2"""
            chi0_GG[:] = chi0_GG * vsqr_G * vsqr_G[:, np.newaxis]
            chi_GG = np.dot(np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)),
                            chi0_GG)
            if not return_VchiV:
                chi0_GG /= vsqr_G * vsqr_G[:, np.newaxis]
                chi_GG /= vsqr_G * vsqr_G[:, np.newaxis]
            chi_wGG.append(chi_GG)

        return pd, chi0_wGG, np.array(chi_wGG)
Ejemplo n.º 4
0
    def __init__(self,
                 calc,
                 xc=None,
                 kpts=None,
                 bands=None,
                 ecut=None,
                 omega=None,
                 world=mpi.world,
                 txt=sys.stdout,
                 timer=None):

        PairDensity.__init__(self,
                             calc,
                             ecut,
                             world=world,
                             txt=txt,
                             timer=timer)

        if xc is None or xc == 'EXX':
            self.exx_fraction = 1.0
            xc = XC(XCNull())
        elif xc == 'PBE0':
            self.exx_fraction = 0.25
            xc = XC('HYB_GGA_XC_PBEH')
        elif xc == 'HSE03':
            omega = 0.106
            self.exx_fraction = 0.25
            xc = XC('HYB_GGA_XC_HSE03')
        elif xc == 'HSE06':
            omega = 0.11
            self.exx_fraction = 0.25
            xc = XC('HYB_GGA_XC_HSE06')
        elif xc == 'B3LYP':
            self.exx_fraction = 0.2
            xc = XC('HYB_GGA_XC_B3LYP')

        self.xc = xc
        self.omega = omega
        self.exc = np.nan  # density dependent part of xc-energy

        self.kpts = select_kpts(kpts, self.calc)

        if bands is None:
            # Do all occupied bands:
            bands = [0, self.nocc2]

        prnt('Calculating exact exchange contributions for band index',
             '%d-%d' % (bands[0], bands[1] - 1),
             file=self.fd)
        prnt('for IBZ k-points with indices:',
             ', '.join(str(i) for i in self.kpts),
             file=self.fd)

        self.bands = bands

        if self.ecut is None:
            self.ecut = self.calc.wfs.pd.ecut
        prnt('Plane-wave cutoff: %.3f eV' % (self.ecut * Hartree),
             file=self.fd)

        shape = (self.calc.wfs.nspins, len(self.kpts), bands[1] - bands[0])
        self.exxvv_sin = np.zeros(shape)  # valence-valence exchange energies
        self.exxvc_sin = np.zeros(shape)  # valence-core exchange energies
        self.f_sin = np.empty(shape)  # occupation numbers

        # The total EXX energy will not be calculated if we are only
        # interested in a few eigenvalues for a few k-points
        self.exx = np.nan  # total EXX energy
        self.exxvv = np.nan  # valence-valence
        self.exxvc = np.nan  # valence-core
        self.exxcc = 0.0  # core-core

        self.mysKn1n2 = None  # my (s, K, n1, n2) indices
        self.distribute_k_points_and_bands(0, self.nocc2)

        # All occupied states:
        self.mykpts = [
            self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2
        ]

        prnt('Using Wigner-Seitz truncated coulomb interaction.', file=self.fd)
        self.wstc = WignerSeitzTruncatedCoulomb(self.calc.wfs.gd.cell_cv,
                                                self.calc.wfs.kd.N_c, self.fd)
        self.iG_qG = {}  # cache

        # PAW matrices:
        self.V_asii = []  # valence-valence correction
        self.C_aii = []  # valence-core correction
        self.initialize_paw_exx_corrections()
Ejemplo n.º 5
0
    def __init__(self,
                 calc=None,
                 spinors=False,
                 ecut=10.,
                 scale=1.0,
                 nbands=None,
                 valence_bands=None,
                 conduction_bands=None,
                 eshift=None,
                 gw_skn=None,
                 truncation=None,
                 integrate_gamma=1,
                 txt=sys.stdout,
                 mode='BSE',
                 wfile=None,
                 write_h=False,
                 write_v=False):

        """Creates the BSE object

        calc: str or calculator object
            The string should refer to the .gpw file contaning KS orbitals
        ecut: float
            Plane wave cutoff energy (eV)
        nbands: int
            Number of bands used for the screened interaction
        valence_bands: list
            Valence bands used in the BSE Hamiltonian
        conduction_bands: list
            Conduction bands used in the BSE Hamiltonian
        eshift: float
            Scissors operator opening the gap (eV)
        gw_skn: list / array
            List or array defining the gw quasiparticle energies used in
            the BSE Hamiltonian. Should match spin, k-points and
            valence/conduction bands
        truncation: str
            Coulomb truncation scheme. Can be either wigner-seitz,
            2D, 1D, or 0D
        integrate_gamma: int
            Method to integrate the Coulomb interaction. 1 is a numerical
            integration at all q-points with G=[0,0,0] - this breaks the
            symmetry slightly. 0 is analytical integration at q=[0,0,0] only -
            this conserves the symmetry. integrate_gamma=2 is the same as 1,
            but the average is only carried out in the non-periodic directions.
        txt: str
            txt output
        mode: str
            Theory level used. can be RPA TDHF or BSE. Only BSE is screened.
        wfile: str
            File for saving screened interaction and some other stuff
            needed later
        write_h: bool
            If True, write the BSE Hamiltonian to H_SS.ulm.
        write_v: bool
            If True, write eigenvalues and eigenstates to v_TS.ulm
        """

        # Calculator
        if isinstance(calc, str):
            calc = GPAW(calc, txt=None, communicator=serial_comm)
        self.calc = calc
        self.spinors = spinors
        self.scale = scale

        assert mode in ['RPA', 'TDHF', 'BSE']
        # assert calc.wfs.kd.nbzkpts % world.size == 0

        # txt file
        if world.rank != 0:
            txt = devnull
        elif isinstance(txt, str):
            txt = open(txt, 'w', 1)
        self.fd = txt

        self.ecut = ecut / Hartree
        self.nbands = nbands
        self.mode = mode
        self.truncation = truncation
        if integrate_gamma == 0 and truncation is not None:
            print('***WARNING*** Analytical Coulomb integration is ' +
                  'not expected to work with Coulomb truncation. ' +
                  'Use integrate_gamma=1', file=self.fd)
        self.integrate_gamma = integrate_gamma
        self.wfile = wfile
        self.write_h = write_h
        self.write_v = write_v

        # Find q-vectors and weights in the IBZ:
        self.kd = calc.wfs.kd
        if -1 in self.kd.bz2bz_ks:
            print('***WARNING*** Symmetries may not be right ' +
                  'Use gamma-centered grid to be sure', file=self.fd)
        offset_c = 0.5 * ((self.kd.N_c + 1) % 2) / self.kd.N_c
        bzq_qc = monkhorst_pack(self.kd.N_c) + offset_c
        self.qd = KPointDescriptor(bzq_qc)
        self.qd.set_symmetry(self.calc.atoms, self.kd.symmetry)
        self.vol = abs(np.linalg.det(calc.wfs.gd.cell_cv))

        # bands
        self.spins = self.calc.wfs.nspins
        if self.spins == 2:
            if self.spinors:
                self.spinors = False
                print('***WARNING*** Presently the spinor version' +
                      'does not work for spin-polarized calculations.' +
                      'Performing scalar calculation', file=self.fd)
            assert len(valence_bands[0]) == len(valence_bands[1])
            assert len(conduction_bands[0]) == len(conduction_bands[1])
        if valence_bands is None:
            nv = self.calc.wfs.setups.nvalence
            valence_bands = [[nv // 2 - 1]]
            if self.spins == 2:
                valence_bands *= 2
        if conduction_bands is None:
            conduction_bands = [[valence_bands[-1] + 1]]
            if self.spins == 2:
                conduction_bands *= 2

        self.val_sn = np.array(valence_bands)
        if len(np.shape(self.val_sn)) == 1:
            self.val_sn = np.array([self.val_sn])
        self.con_sn = np.array(conduction_bands)
        if len(np.shape(self.con_sn)) == 1:
            self.con_sn = np.array([self.con_sn])

        self.td = True
        for n in self.val_sn[0]:
            if n in self.con_sn[0]:
                self.td = False
        if len(self.val_sn) == 2:
            for n in self.val_sn[1]:
                if n in self.con_sn[1]:
                    self.td = False

        self.nv = len(self.val_sn[0])
        self.nc = len(self.con_sn[0])
        if eshift is not None:
            eshift /= Hartree
        if gw_skn is not None:
            assert self.nv + self.nc == len(gw_skn[0, 0])
            assert self.kd.nibzkpts == len(gw_skn[0])
            gw_skn = gw_skn[:, self.kd.bz2ibz_k]
            # assert self.kd.nbzkpts == len(gw_skn[0])
            gw_skn /= Hartree
        self.gw_skn = gw_skn
        self.eshift = eshift

        # Number of pair orbitals
        self.nS = self.kd.nbzkpts * self.nv * self.nc * self.spins
        self.nS *= (self.spinors + 1)**2

        # Wigner-Seitz stuff
        if self.truncation == 'wigner-seitz':
            self.wstc = WignerSeitzTruncatedCoulomb(self.calc.wfs.gd.cell_cv,
                                                    self.kd.N_c, self.fd)
        else:
            self.wstc = None

        self.print_initialization(self.td, self.eshift, self.gw_skn)
Ejemplo n.º 6
0
    def get_chi(self,
                xc='RPA',
                q_c=[0, 0, 0],
                spin='all',
                direction='x',
                return_VchiV=True,
                q_v=None,
                RSrep='gpaw',
                spinpol_cut=None,
                density_cut=None,
                fxc_scaling=None):
        """ Returns v^1/2 chi v^1/2 for the density response and chi for the
        spin response. The truncated Coulomb interaction is included as
        v^-1/2 v_t v^-1/2. This is in order to conform with
        the head and wings of chi0, which is treated specially for q=0.
        
        spin : str or int
            If 'all' then include all spins.
            If 0 or 1, only include this specific spin.
            (not used in transverse reponse functions)
        RSrep : str
            real space representation of kernel ('gpaw' or 'grid')
        spinpol_cut : float
            cutoff spin polarization below which f_xc is evaluated in
            unpolarized limit (make sure divergent terms cancel out correctly)
        density_cut : float
            cutoff density below which f_xc is set to zero
        fxc_scaling : list
            Possible scaling of kernel to hit Goldstone mode.
            If w=0 is included in the present calculation and
            fxc_scaling=[True, None], the fxc_scaling to match
            kappaM_w[0] = 0. will be calculated. If
            fxc_scaling = [True, float], Kxc will be scaled by float.
            Default is None, i.e. no scaling
        """

        # XXX generalize to kernel check
        response = self.chi0.response
        if response in ['+-', '-+']:
            assert xc in ('ALDA_x', 'ALDA_X', 'ALDA')

        pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c, spin)

        if response == 'density':
            N_c = self.chi0.calc.wfs.kd.N_c

            Kbare_G = get_coulomb_kernel(pd, N_c, truncation=None, q_v=q_v)
            vsqr_G = Kbare_G**0.5
            nG = len(vsqr_G)

            if self.truncation is not None:
                if self.truncation == 'wigner-seitz':
                    self.wstc = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv, N_c)
                else:
                    self.wstc = None
                Ktrunc_G = get_coulomb_kernel(pd,
                                              N_c,
                                              truncation=self.truncation,
                                              wstc=self.wstc,
                                              q_v=q_v)
                K_GG = np.diag(Ktrunc_G / Kbare_G)
            else:
                K_GG = np.eye(nG, dtype=complex)

            if pd.kd.gamma:
                if isinstance(direction, str):
                    d_v = {
                        'x': [1, 0, 0],
                        'y': [0, 1, 0],
                        'z': [0, 0, 1]
                    }[direction]
                else:
                    d_v = direction
                d_v = np.asarray(d_v) / np.linalg.norm(d_v)
                W = slice(self.w1, self.w2)
                chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0])
                chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1])
                chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T)

            if xc != 'RPA':
                Kxc_GG = get_xc_kernel(pd,
                                       self.chi0,
                                       functional=xc,
                                       chi0_wGG=chi0_wGG,
                                       density_cut=density_cut)
                K_GG += Kxc_GG / vsqr_G / vsqr_G[:, np.newaxis]

            # Invert Dyson eq.
            chi_wGG = []
            for chi0_GG in chi0_wGG:
                """v^1/2 chi0 V^1/2"""
                chi0_GG[:] = chi0_GG * vsqr_G * vsqr_G[:, np.newaxis]
                chi_GG = np.dot(
                    np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)), chi0_GG)
                if not return_VchiV:
                    chi0_GG /= vsqr_G * vsqr_G[:, np.newaxis]
                    chi_GG /= vsqr_G * vsqr_G[:, np.newaxis]
                chi_wGG.append(chi_GG)

            if len(chi_wGG):
                chi_wGG = np.array(chi_wGG)
            else:
                chi_wGG = np.zeros((0, nG, nG), complex)

        # Spin response
        else:
            Kxc_GG = get_xc_kernel(pd,
                                   self.chi0,
                                   functional=xc,
                                   kernel=response[::-1],
                                   RSrep=RSrep,
                                   chi0_wGG=chi0_wGG,
                                   fxc_scaling=fxc_scaling,
                                   density_cut=density_cut,
                                   spinpol_cut=spinpol_cut)

            # Invert Dyson equation
            chi_wGG = []
            for chi0_GG in chi0_wGG:
                chi_GG = np.dot(
                    np.linalg.inv(
                        np.eye(len(chi0_GG)) - np.dot(chi0_GG, Kxc_GG)),
                    chi0_GG)

                chi_wGG.append(chi_GG)

        return pd, chi0_wGG, np.array(chi_wGG)
Ejemplo n.º 7
0
    def calculate(self, ecut, nbands=None, spin=False):
        """Calculate RPA correlation energy for one or several cutoffs.

        ecut: float or list of floats
            Plane-wave cutoff(s).
        nbands: int
            Number of bands (defaults to number of plane-waves).
        spin: bool
            Separate spin in response function.
            (Only needed for beyond RPA methods that inherit this function).
        """

        p = functools.partial(print, file=self.fd)

        if isinstance(ecut, (float, int)):
            ecut = ecut * (1 + 0.5 * np.arange(6))**(-2 / 3)
        self.ecut_i = np.asarray(np.sort(ecut)) / Hartree
        ecutmax = max(self.ecut_i)

        if nbands is None:
            p('Response function bands : Equal to number of plane waves')
        else:
            p('Response function bands : %s' % nbands)
        p('Plane wave cutoffs (eV) :', end='')
        for e in self.ecut_i:
            p(' {0:.3f}'.format(e * Hartree), end='')
        p()
        if self.truncation is not None:
            p('Using %s Coulomb truncation' % self.truncation)
        p()
            
        if self.filename and os.path.isfile(self.filename):
            self.read()
            self.world.barrier()

        chi0 = Chi0(self.calc, 1j * Hartree * self.omega_w, eta=0.0,
                    intraband=False, hilbert=False,
                    txt='chi0.txt', timer=self.timer, world=self.world,
                    nblocks=self.nblocks)

        self.blockcomm = chi0.blockcomm
        
        wfs = self.calc.wfs

        if self.truncation == 'wigner-seitz':
            self.wstc = WignerSeitzTruncatedCoulomb(wfs.gd.cell_cv,
                                                    wfs.kd.N_c, self.fd)
        else:
            self.wstc = None

        nq = len(self.energy_qi)
        nw = len(self.omega_w)
        nGmax = max(count_reciprocal_vectors(ecutmax, wfs.gd, q_c)
                    for q_c in self.ibzq_qc[nq:])
        mynGmax = (nGmax + self.nblocks - 1) // self.nblocks
        
        nx = (1 + spin) * nw * mynGmax * nGmax
        A1_x = np.empty(nx, complex)
        if self.nblocks > 1:
            A2_x = np.empty(nx, complex)
        else:
            A2_x = None
        
        self.timer.start('RPA')
        
        for q_c in self.ibzq_qc[nq:]:
            if np.allclose(q_c, 0.0) and self.skip_gamma:
                self.energy_qi.append(len(self.ecut_i) * [0.0])
                self.write()
                p('Not calculating E_c(q) at Gamma')
                p()
                continue

            thisqd = KPointDescriptor([q_c])
            pd = PWDescriptor(ecutmax, wfs.gd, complex, thisqd)
            nG = pd.ngmax
            mynG = (nG + self.nblocks - 1) // self.nblocks
            chi0.Ga = self.blockcomm.rank * mynG
            chi0.Gb = min(chi0.Ga + mynG, nG)
            
            shape = (1 + spin, nw, chi0.Gb - chi0.Ga, nG)
            chi0_swGG = A1_x[:np.prod(shape)].reshape(shape)
            chi0_swGG[:] = 0.0
            
            if np.allclose(q_c, 0.0):
                chi0_swxvG = np.zeros((1 + spin, nw, 2, 3, nG), complex)
                chi0_swvv = np.zeros((1 + spin, nw, 3, 3), complex)
            else:
                chi0_swxvG = None
                chi0_swvv = None

            # First not completely filled band:
            m1 = chi0.nocc1
            p('# %s  -  %s' % (len(self.energy_qi), ctime().split()[-2]))
            p('q = [%1.3f %1.3f %1.3f]' % tuple(q_c))

            energy_i = []
            for ecut in self.ecut_i:
                if ecut == ecutmax:
                    # Nothing to cut away:
                    cut_G = None
                    m2 = nbands or nG
                else:
                    cut_G = np.arange(nG)[pd.G2_qG[0] <= 2 * ecut]
                    m2 = len(cut_G)

                p('E_cut = %d eV / Bands = %d:' % (ecut * Hartree, m2))
                self.fd.flush()

                energy = self.calculate_q(chi0, pd,
                                          chi0_swGG, chi0_swxvG, chi0_swvv,
                                          m1, m2, cut_G, A2_x)

                energy_i.append(energy)
                m1 = m2

                a = 1 / chi0.kncomm.size
                if ecut < ecutmax and a != 1.0:
                    # Chi0 will be summed again over chicomm, so we divide
                    # by its size:
                    chi0_swGG *= a
                    if chi0_swxvG is not None:
                        chi0_swxvG *= a
                        chi0_swvv *= a

            self.energy_qi.append(energy_i)
            self.write()
            p()

        e_i = np.dot(self.weight_q, np.array(self.energy_qi))
        p('==========================================================')
        p()
        p('Total correlation energy:')
        for e_cut, e in zip(self.ecut_i, e_i):
            p('%6.0f:   %6.4f eV' % (e_cut * Hartree, e * Hartree))
        p()

        self.energy_qi = []  # important if another calculation is performed

        if len(e_i) > 1:
            self.extrapolate(e_i)

        p('Calculation completed at: ', ctime())
        p()

        self.timer.stop('RPA')
        self.timer.write(self.fd)
        self.fd.flush()
        
        return e_i * Hartree
Ejemplo n.º 8
0
    def get_dielectric_matrix(self,
                              xc='RPA',
                              q_c=[0, 0, 0],
                              direction='x',
                              symmetric=True,
                              calculate_chi=False):
        """Returns the symmetrized dielectric matrix.
        
        ::
        
            \tilde\epsilon_GG' = v^{-1/2}_G \epsilon_GG' v^{1/2}_G',
            
        where::
            
            epsilon_GG' = 1 - v_G * P_GG' and P_GG'
            
        is the polarization.
        
        ::
            
            In RPA:   P = chi^0
            In TDDFT: P = (1 - chi^0 * f_xc)^{-1} chi^0
        
        The head of the inverse symmetrized dielectric matrix is equal
        to the head of the inverse dielectric matrix (inverse dielectric
        function)
        """
        pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c)
        G_G = pd.G2_qG[0]**0.5
        nG = len(G_G)

        if pd.kd.gamma:
            G_G[0] = 1.0
            if isinstance(direction, str):
                d_v = {
                    'x': [1, 0, 0],
                    'y': [0, 1, 0],
                    'z': [0, 0, 1]
                }[direction]
            else:
                d_v = direction

            d_v = np.asarray(d_v) / np.linalg.norm(d_v)
            W = slice(self.w1, self.w2)
            chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0])
            chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1])
            chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T)

        if self.truncation == 'wigner-seitz':
            kernel = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv,
                                                 self.chi0.calc.wfs.kd.N_c)
            K_G = kernel.get_potential(pd)**0.5
            if pd.kd.gamma:
                K_G[0] = 0.0
        elif self.truncation == '2D':
            K_G = truncated_coulomb(pd)
            if pd.kd.gamma:
                K_G[0] = 0.0
        else:
            K_G = (4 * pi)**0.5 / G_G

        if xc != 'RPA':
            R_av = self.chi0.calc.atoms.positions / Bohr
            nt_sG = self.chi0.calc.density.nt_sG
            Kxc_sGG = calculate_Kxc(pd,
                                    nt_sG,
                                    R_av,
                                    self.chi0.calc.wfs.setups,
                                    self.chi0.calc.density.D_asp,
                                    functional=xc)

        if calculate_chi:
            chi_wGG = []

        for chi0_GG in chi0_wGG:
            if xc == 'RPA':
                P_GG = chi0_GG
            else:
                P_GG = np.dot(
                    np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, Kxc_sGG[0])),
                    chi0_GG)
            if symmetric:
                e_GG = np.eye(nG) - P_GG * K_G * K_G[:, np.newaxis]
            else:
                K_GG = (K_G**2 * np.ones([nG, nG])).T
                e_GG = np.eye(nG) - P_GG * K_GG
            if calculate_chi:
                K_GG = np.diag(K_G**2)
                if xc != 'RPA':
                    K_GG += Kxc_sGG[0]
                chi_wGG.append(
                    np.dot(np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)),
                           chi0_GG))
            chi0_GG[:] = e_GG

        # chi0_wGG is now the dielectric matrix
        if not calculate_chi:
            return chi0_wGG
        else:
            return pd, chi0_wGG, chi_wGG
Ejemplo n.º 9
0
    def calculate_screened_potential(self):
        """Calculates the screened potential for each q-point in the 1st BZ.
        Since many q-points are related by symmetry, the actual calculation is
        only done for q-points in the IBZ and the rest are obtained by symmetry
        transformations. Results are returned as a generator to that it is not
        necessary to store a huge matrix for each q-point in the memory."""
        # The decorator $timer('W') doesn't work for generators, do we will
        # have to manually start and stop the timer here:
        self.timer.start('W')
        print('Calculating screened Coulomb potential', file=self.fd)
        if self.wstc:
            print('Using Wigner-Seitz truncated Coloumb potential',
                  file=self.fd)
            
        if self.ppa:
            print('Using Godby-Needs plasmon-pole approximation:',
                  file=self.fd)
            print('    Fitting energy: i*E0, E0 = %.3f Hartee' % self.E0,
                  file=self.fd)

            # use small imaginary frequency to avoid dividing by zero:
            frequencies = [1e-10j, 1j * self.E0 * Hartree]
            
            parameters = {'eta': 0,
                          'hilbert': False,
                          'timeordered': False,
                          'frequencies': frequencies}
        else:
            print('Using full frequency integration:', file=self.fd)
            print('  domega0: {0:g}'.format(self.domega0 * Hartree),
                  file=self.fd)
            print('  omega2: {0:g}'.format(self.omega2 * Hartree),
                  file=self.fd)

            parameters = {'eta': self.eta * Hartree,
                          'hilbert': True,
                          'timeordered': True,
                          'domega0': self.domega0 * Hartree,
                          'omega2': self.omega2 * Hartree}
        
        chi0 = Chi0(self.calc,
                    nbands=self.nbands,
                    ecut=self.ecut * Hartree,
                    intraband=False,
                    real_space_derivatives=False,
                    txt=self.filename + '.w.txt',
                    timer=self.timer,
                    keep_occupied_states=True,
                    nblocks=self.blockcomm.size,
                    no_optical_limit=self.wstc,
                    **parameters)

        if self.wstc:
            wstc = WignerSeitzTruncatedCoulomb(
                self.calc.wfs.gd.cell_cv,
                self.calc.wfs.kd.N_c,
                chi0.fd)
        else:
            wstc = None
        
        self.omega_w = chi0.omega_w
        self.omegamax = chi0.omegamax
        
        htp = HilbertTransform(self.omega_w, self.eta, gw=True)
        htm = HilbertTransform(self.omega_w, -self.eta, gw=True)

        # Find maximum size of chi-0 matrices:
        gd = self.calc.wfs.gd
        nGmax = max(count_reciprocal_vectors(self.ecut, gd, q_c)
                    for q_c in self.qd.ibzk_kc)
        nw = len(self.omega_w)
        
        size = self.blockcomm.size
        mynGmax = (nGmax + size - 1) // size
        mynw = (nw + size - 1) // size
        
        # Allocate memory in the beginning and use for all q:
        A1_x = np.empty(nw * mynGmax * nGmax, complex)
        A2_x = np.empty(max(mynw * nGmax, nw * mynGmax) * nGmax, complex)
        
        # Need to pause the timer in between iterations
        self.timer.stop('W')
        for iq, q_c in enumerate(self.qd.ibzk_kc):
            self.timer.start('W')
            if self.savew:
                wfilename = self.filename + '.w.q%d.pckl' % iq
                fd = opencew(wfilename)
            if self.savew and fd is None:
                # Read screened potential from file
                with open(wfilename) as fd:
                    pd, W = pickle.load(fd)
            else:
                # First time calculation
                pd, W = self.calculate_w(chi0, q_c, htp, htm, wstc, A1_x, A2_x)
                if self.savew:
                    pickle.dump((pd, W), fd, pickle.HIGHEST_PROTOCOL)

            self.timer.stop('W')
            # Loop over all k-points in the BZ and find those that are related
            # to the current IBZ k-point by symmetry
            Q1 = self.qd.ibz2bz_k[iq]
            done = set()
            for s, Q2 in enumerate(self.qd.bz2bz_ks[Q1]):
                if Q2 >= 0 and Q2 not in done:
                    s = self.qd.sym_k[Q2]
                    self.s = s
                    self.U_cc = self.qd.symmetry.op_scc[s]
                    time_reversal = self.qd.time_reversal_k[Q2]
                    self.sign = 1 - 2 * time_reversal
                    Q_c = self.qd.bzk_kc[Q2]
                    d_c = self.sign * np.dot(self.U_cc, q_c) - Q_c
                    assert np.allclose(d_c.round(), d_c)
                    yield pd, W, Q_c
                    done.add(Q2)