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)
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)
class RPACorrelation: def __init__(self, calc, xc='RPA', filename=None, skip_gamma=False, qsym=True, nlambda=None, nfrequencies=16, frequency_max=800.0, frequency_scale=2.0, frequencies=None, weights=None, world=mpi.world, nblocks=1, wstc=False, txt=sys.stdout): if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=mpi.serial_comm) self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt self.timer = Timer() if frequencies is None: frequencies, weights = get_gauss_legendre_points(nfrequencies, frequency_max, frequency_scale) user_spec = False else: assert weights is not None user_spec = True self.omega_w = frequencies / Hartree self.weight_w = weights / Hartree if nblocks > 1: assert len(self.omega_w) % nblocks == 0 assert wstc self.wstc = wstc self.nblocks = nblocks self.world = world self.skip_gamma = skip_gamma self.ibzq_qc = None self.weight_q = None self.initialize_q_points(qsym) # Energies for all q-vetors and cutoff energies: self.energy_qi = [] self.filename = filename self.print_initialization(xc, frequency_scale, nlambda, user_spec) def initialize_q_points(self, qsym): kd = self.calc.wfs.kd self.bzq_qc = kd.get_bz_q_points(first=True) if not qsym: self.ibzq_qc = self.bzq_qc self.weight_q = np.ones(len(self.bzq_qc)) / len(self.bzq_qc) else: U_scc = kd.symmetry.op_scc self.ibzq_qc = kd.get_ibz_q_points(self.bzq_qc, U_scc)[0] self.weight_q = kd.q_weights def read(self): lines = open(self.filename).readlines()[1:] n = 0 self.energy_qi = [] nq = len(lines) // len(self.ecut_i) for q_c in self.ibzq_qc[:nq]: self.energy_qi.append([]) for ecut in self.ecut_i: q1, q2, q3, ec, energy = [float(x) for x in lines[n].split()] self.energy_qi[-1].append(energy / Hartree) n += 1 if (abs(q_c - (q1, q2, q3)).max() > 1e-4 or abs(int(ecut * Hartree) - ec) > 0): self.energy_qi = [] return print('Read %d q-points from file: %s' % (nq, self.filename), file=self.fd) print(file=self.fd) def write(self): if self.world.rank == 0 and self.filename: fd = open(self.filename, 'w') print('#%9s %10s %10s %8s %12s' % ('q1', 'q2', 'q3', 'E_cut', 'E_c(q)'), file=fd) for energy_i, q_c in zip(self.energy_qi, self.ibzq_qc): for energy, ecut in zip(energy_i, self.ecut_i): print('%10.4f %10.4f %10.4f %8d %r' % (tuple(q_c) + (ecut * Hartree, energy * Hartree)), file=fd) 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 funtion. (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() 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=self.fd, timer=self.timer, world=self.world, no_optical_limit=self.wstc, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.wstc: with self.timer('WSTC-init'): p('Using Wigner-Seitz truncated Coulomb potential.') self.wstc = WignerSeitzTruncatedCoulomb( wfs.gd.cell_cv, wfs.kd.N_c, self.fd) 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 self.wstc or np.allclose(q_c, 0.0): # Wings (x=0,1) and head (G=0) for optical limit and three # directions (v=0,1,2): 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 Q_aGii = chi0.initialize_paw_corrections(pd) # 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, Q_aGii, 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 @timer('chi0(q)') def calculate_q(self, chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, Q_aGii, m1, m2, cut_G, A2_x): chi0_wGG = chi0_swGG[0] if chi0_swxvG is not None: chi0_wxvG = chi0_swxvG[0] chi0_wvv = chi0_swvv[0] else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, Q_aGii, m1, m2, [0, 1]) print('E_c(q) = ', end='', file=self.fd) chi0_wGG = chi0.redistribute(chi0_wGG, A2_x) if not pd.kd.gamma or self.wstc: e = self.calculate_energy(pd, chi0_wGG, cut_G) print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() else: e = 0.0 for v in range(3): chi0_wGG[:, 0] = chi0_wxvG[:, 0, v] chi0_wGG[:, :, 0] = chi0_wxvG[:, 1, v] chi0_wGG[:, 0, 0] = chi0_wvv[:, v, v] ev = self.calculate_energy(pd, chi0_wGG, cut_G) e += ev print('%.3f' % (ev * Hartree), end='', file=self.fd) if v < 2: print('/', end='', file=self.fd) else: print(' eV', file=self.fd) self.fd.flush() e /= 3 return e @timer('Energy') def calculate_energy(self, pd, chi0_wGG, cut_G): """Evaluate correlation energy from chi0.""" if self.wstc: invG_G = (self.wstc.get_potential(pd) / (4 * pi))**0.5 else: G_G = pd.G2_qG[0]**0.5 # |G+q| if pd.kd.gamma: G_G[0] = 1.0 invG_G = 1.0 / G_G if cut_G is not None: invG_G = invG_G[cut_G] nG = len(invG_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = (np.eye(nG) - 4 * np.pi * chi0_GG * invG_G * invG_G[:, np.newaxis]) e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy def extrapolate(self, e_i): print('Extrapolated energies:', file=self.fd) ex_i = [] for i in range(len(e_i) - 1): e1, e2 = e_i[i:i + 2] x1, x2 = self.ecut_i[i:i + 2]**-1.5 ex = (e1 * x2 - e2 * x1) / (x2 - x1) ex_i.append(ex) print(' %4.0f -%4.0f: %5.3f eV' % (self.ecut_i[i] * Hartree, self.ecut_i[i + 1] * Hartree, ex * Hartree), file=self.fd) print(file=self.fd) self.fd.flush() return e_i * Hartree def print_initialization(self, xc, frequency_scale, nlambda, user_spec): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('Non-self-consistent %s correlation energy' % xc) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', len(self.calc.wfs.kd.bzk_kc)) p('Number of irreducible k-points :', len(self.calc.wfs.kd.ibzk_kc)) p('Number of q-points :', len(self.bzq_qc)) p('Number of irreducible q-points :', len(self.ibzq_qc)) p() for q, weight in zip(self.ibzq_qc, self.weight_q): p(' q: [%1.4f %1.4f %1.4f] - weight: %1.3f' % (q[0], q[1], q[2], weight)) p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() if nlambda is None: p('Analytical coupling constant integration') else: p('Numerical coupling constant integration using', nlambda, 'Gauss-Legendre points') p() p('Frequencies') if not user_spec: p(' Gauss-Legendre integration with %s frequency points' % len(self.omega_w)) p(' Transformed from [0,oo] to [0,1] using e^[-aw^(1/B)]') p(' Highest frequency point at %5.1f eV and B=%1.1f' % (self.omega_w[-1] * Hartree, frequency_scale)) else: p(' User specified frequency integration with', len(self.omega_w), 'frequency points') p() p('Parallelization') p(' Total number of CPUs : % s' % self.world.size) p(' G-vector decomposition : % s' % self.nblocks) p(' K-point/band decomposition : % s' % (self.world.size // self.nblocks)) p()
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 funtion. (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() 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=self.fd, timer=self.timer, world=self.world, no_optical_limit=self.wstc, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.wstc: with self.timer('WSTC-init'): p('Using Wigner-Seitz truncated Coulomb potential.') self.wstc = WignerSeitzTruncatedCoulomb( wfs.gd.cell_cv, wfs.kd.N_c, self.fd) 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 self.wstc or np.allclose(q_c, 0.0): # Wings (x=0,1) and head (G=0) for optical limit and three # directions (v=0,1,2): 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 Q_aGii = chi0.initialize_paw_corrections(pd) # 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, Q_aGii, 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
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
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()
class EXX(PairDensity): 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() def calculate(self): kd = self.calc.wfs.kd nspins = self.calc.wfs.nspins for s in range(nspins): for i, k1 in enumerate(self.kpts): K1 = kd.ibz2bz_k[k1] kpt1 = self.get_k_point(s, K1, *self.bands) self.f_sin[s, i] = kpt1.f_n for kpt2 in self.mykpts: if kpt2.s == s: self.calculate_q(i, kpt1, kpt2) self.calculate_paw_exx_corrections(i, kpt1) self.world.sum(self.exxvv_sin) # Calculate total energy if we have everything needed: if (len(self.kpts) == kd.nibzkpts and self.bands[0] == 0 and self.bands[1] >= self.nocc2): exxvv_i = (self.exxvv_sin * self.f_sin).sum(axis=2).sum(axis=0) exxvc_i = 2 * (self.exxvc_sin * self.f_sin).sum(axis=2).sum(axis=0) self.exxvv = np.dot(kd.weight_k[self.kpts], exxvv_i) / nspins self.exxvc = np.dot(kd.weight_k[self.kpts], exxvc_i) / nspins self.exx = self.exxvv + self.exxvc + self.exxcc prnt('Exact exchange energy:', file=self.fd) for kind, exx in [('valence-valence', self.exxvv), ('valence-core', self.exxvc), ('core-core', self.exxcc), ('total', self.exx)]: prnt('%16s%11.3f eV' % (kind + ':', exx * Hartree), file=self.fd) self.exc = self.calculate_hybrid_correction() exx_sin = self.exxvv_sin + self.exxvc_sin prnt('EXX eigenvalue contributions in eV:', file=self.fd) prnt(np.array_str(exx_sin * Hartree, precision=3), file=self.fd) def get_exx_energy(self): return self.exx * Hartree def get_total_energy(self): ham = self.calc.hamiltonian return (self.exx * self.exx_fraction + self.exc + ham.Etot - ham.Exc) * Hartree def get_eigenvalue_contributions(self): if self.reader is not None: self.calc.wfs.read_projections(self.reader) b1, b2 = self.bands e_sin = vxc(self.calc, self.xc)[:, self.kpts, b1:b2] / Hartree e_sin += (self.exxvv_sin + self.exxvc_sin) * self.exx_fraction return e_sin * Hartree def calculate_q(self, i, kpt1, kpt2): wfs = self.calc.wfs q_c = wfs.kd.bzk_kc[kpt2.K] - wfs.kd.bzk_kc[kpt1.K] qd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, wfs.gd, wfs.dtype, kd=qd) Q_G = self.get_fft_indices(kpt1.K, kpt2.K, q_c, pd, kpt1.shift_c - kpt2.shift_c) Q_aGii = self.initialize_paw_corrections(pd, soft=True) for n in range(kpt1.n2 - kpt1.n1): ut1cc_R = kpt1.ut_nR[n].conj() C1_aGi = [np.dot(Q_Gii, P1_ni[n].conj()) for Q_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani)] n_mG = self.calculate_pair_densities(ut1cc_R, C1_aGi, kpt2, pd, Q_G) e = self.calculate_n(pd, n, n_mG, kpt2) self.exxvv_sin[kpt1.s, i, n] += e def calculate_n(self, pd, n, n_mG, kpt2): iG_G = self.get_coulomb_kernel(pd) x = 4 * pi / self.calc.wfs.kd.nbzkpts / pd.gd.dv**2 e = 0.0 for f, n_G in zip(kpt2.f_n, n_mG): x_G = n_G * iG_G e -= x * f * pd.integrate(x_G, x_G).real return e def get_coulomb_kernel(self, pd): if self.omega is not None: G2_G = pd.G2_qG[0] iG_G = np.empty_like(G2_G) if pd.kd.gamma: iG_G[0] = 1 / (2 * self.omega) else: iG_G[0] = ((1 - np.exp(-G2_G[0] / (4 * self.omega**2))) / G2_G[0])**0.5 iG_G[1:] = ((1 - np.exp(-G2_G[1:] / (4 * self.omega**2))) / G2_G[1:])**0.5 return iG_G key = tuple((pd.kd.bzk_kc[0] * self.calc.wfs.kd.N_c).round()) iG_G = self.iG_qG.get(key) if iG_G is None: v_G = self.wstc.get_potential(pd) iG_G = (v_G / (4 * pi))**0.5 self.iG_qG[key] = iG_G return iG_G def initialize_paw_exx_corrections(self): for a, atomdata in enumerate(self.calc.wfs.setups): V_sii = [] for D_p in self.calc.density.D_asp[a]: D_ii = unpack2(D_p) V_ii = pawexxvv(atomdata, D_ii) V_sii.append(V_ii) C_ii = unpack(atomdata.X_p) self.V_asii.append(V_sii) self.C_aii.append(C_ii) self.exxcc += atomdata.ExxC def calculate_paw_exx_corrections(self, i, kpt): x = self.calc.wfs.nspins / self.world.size s = kpt.s for V_sii, C_ii, P_ni in zip(self.V_asii, self.C_aii, kpt.P_ani): V_ii = V_sii[s] v_n = (np.dot(P_ni, V_ii) * P_ni.conj()).sum(axis=1).real c_n = (np.dot(P_ni, C_ii) * P_ni.conj()).sum(axis=1).real self.exxvv_sin[s, i] -= v_n * x self.exxvc_sin[s, i] -= c_n def calculate_hybrid_correction(self): dens = self.calc.density if dens.nt_sg is None: dens.interpolate_pseudo_density() exc = self.xc.calculate(dens.finegd, dens.nt_sg) for a, D_sp in dens.D_asp.items(): atomdata = dens.setups[a] exc += self.xc.calculate_paw_correction(atomdata, D_sp) return exc
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)
class EXX(PairDensity): 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() def calculate(self): kd = self.calc.wfs.kd nspins = self.calc.wfs.nspins for s in range(nspins): for i, k1 in enumerate(self.kpts): K1 = kd.ibz2bz_k[k1] kpt1 = self.get_k_point(s, K1, *self.bands) self.f_sin[s, i] = kpt1.f_n for kpt2 in self.mykpts: if kpt2.s == s: self.calculate_q(i, kpt1, kpt2) self.calculate_paw_exx_corrections(i, kpt1) self.world.sum(self.exxvv_sin) # Calculate total energy if we have everything needed: if (len(self.kpts) == kd.nibzkpts and self.bands[0] == 0 and self.bands[1] >= self.nocc2): exxvv_i = (self.exxvv_sin * self.f_sin).sum(axis=2).sum(axis=0) exxvc_i = 2 * (self.exxvc_sin * self.f_sin).sum(axis=2).sum(axis=0) self.exxvv = np.dot(kd.weight_k[self.kpts], exxvv_i) / nspins self.exxvc = np.dot(kd.weight_k[self.kpts], exxvc_i) / nspins self.exx = self.exxvv + self.exxvc + self.exxcc prnt('Exact exchange energy:', file=self.fd) for kind, exx in [('valence-valence', self.exxvv), ('valence-core', self.exxvc), ('core-core', self.exxcc), ('total', self.exx)]: prnt('%16s%11.3f eV' % (kind + ':', exx * Hartree), file=self.fd) self.exc = self.calculate_hybrid_correction() exx_sin = self.exxvv_sin + self.exxvc_sin prnt('EXX eigenvalue contributions in eV:', file=self.fd) prnt(np.array_str(exx_sin * Hartree, precision=3), file=self.fd) def get_exx_energy(self): return self.exx * Hartree def get_total_energy(self): ham = self.calc.hamiltonian return (self.exx * self.exx_fraction + self.exc + ham.Etot - ham.Exc) * Hartree def get_eigenvalue_contributions(self): if self.reader is not None: self.calc.wfs.read_projections(self.reader) b1, b2 = self.bands e_sin = vxc(self.calc, self.xc)[:, self.kpts, b1:b2] / Hartree e_sin += (self.exxvv_sin + self.exxvc_sin) * self.exx_fraction return e_sin * Hartree def calculate_q(self, i, kpt1, kpt2): wfs = self.calc.wfs q_c = wfs.kd.bzk_kc[kpt2.K] - wfs.kd.bzk_kc[kpt1.K] qd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, wfs.gd, wfs.dtype, kd=qd) Q_G = self.get_fft_indices(kpt1.K, kpt2.K, q_c, pd, kpt1.shift_c - kpt2.shift_c) Q_aGii = self.initialize_paw_corrections(pd, soft=True) for n in range(kpt1.n2 - kpt1.n1): ut1cc_R = kpt1.ut_nR[n].conj() C1_aGi = [ np.dot(Q_Gii, P1_ni[n].conj()) for Q_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani) ] n_mG = self.calculate_pair_densities(ut1cc_R, C1_aGi, kpt2, pd, Q_G) e = self.calculate_n(pd, n, n_mG, kpt2) self.exxvv_sin[kpt1.s, i, n] += e def calculate_n(self, pd, n, n_mG, kpt2): iG_G = self.get_coulomb_kernel(pd) x = 4 * pi / self.calc.wfs.kd.nbzkpts / pd.gd.dv**2 e = 0.0 for f, n_G in zip(kpt2.f_n, n_mG): x_G = n_G * iG_G e -= x * f * pd.integrate(x_G, x_G).real return e def get_coulomb_kernel(self, pd): if self.omega is not None: G2_G = pd.G2_qG[0] iG_G = np.empty_like(G2_G) if pd.kd.gamma: iG_G[0] = 1 / (2 * self.omega) else: iG_G[0] = ((1 - np.exp(-G2_G[0] / (4 * self.omega**2))) / G2_G[0])**0.5 iG_G[1:] = ((1 - np.exp(-G2_G[1:] / (4 * self.omega**2))) / G2_G[1:])**0.5 return iG_G key = tuple((pd.kd.bzk_kc[0] * self.calc.wfs.kd.N_c).round()) iG_G = self.iG_qG.get(key) if iG_G is None: v_G = self.wstc.get_potential(pd) iG_G = (v_G / (4 * pi))**0.5 self.iG_qG[key] = iG_G return iG_G def initialize_paw_exx_corrections(self): for a, atomdata in enumerate(self.calc.wfs.setups): V_sii = [] for D_p in self.calc.density.D_asp[a]: D_ii = unpack2(D_p) V_ii = pawexxvv(atomdata, D_ii) V_sii.append(V_ii) C_ii = unpack(atomdata.X_p) self.V_asii.append(V_sii) self.C_aii.append(C_ii) self.exxcc += atomdata.ExxC def calculate_paw_exx_corrections(self, i, kpt): x = self.calc.wfs.nspins / self.world.size s = kpt.s for V_sii, C_ii, P_ni in zip(self.V_asii, self.C_aii, kpt.P_ani): V_ii = V_sii[s] v_n = (np.dot(P_ni, V_ii) * P_ni.conj()).sum(axis=1).real c_n = (np.dot(P_ni, C_ii) * P_ni.conj()).sum(axis=1).real self.exxvv_sin[s, i] -= v_n * x self.exxvc_sin[s, i] -= c_n def calculate_hybrid_correction(self): dens = self.calc.density if dens.nt_sg is None: dens.interpolate_pseudo_density() exc = self.xc.calculate(dens.finegd, dens.nt_sg) for a, D_sp in dens.D_asp.items(): atomdata = dens.setups[a] exc += self.xc.calculate_paw_correction(atomdata, D_sp) return exc
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)
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)
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
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
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)
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()
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)
class RPACorrelation: def __init__(self, calc, xc='RPA', filename=None, skip_gamma=False, qsym=True, nlambda=None, nfrequencies=16, frequency_max=800.0, frequency_scale=2.0, frequencies=None, weights=None, world=mpi.world, nblocks=1, wstc=False, txt=sys.stdout): if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=mpi.serial_comm) self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt self.timer = Timer() if frequencies is None: frequencies, weights = get_gauss_legendre_points( nfrequencies, frequency_max, frequency_scale) user_spec = False else: assert weights is not None user_spec = True self.omega_w = frequencies / Hartree self.weight_w = weights / Hartree if nblocks > 1: assert len(self.omega_w) % nblocks == 0 assert wstc self.wstc = wstc self.nblocks = nblocks self.world = world self.skip_gamma = skip_gamma self.ibzq_qc = None self.weight_q = None self.initialize_q_points(qsym) # Energies for all q-vetors and cutoff energies: self.energy_qi = [] self.filename = filename self.print_initialization(xc, frequency_scale, nlambda, user_spec) def initialize_q_points(self, qsym): kd = self.calc.wfs.kd self.bzq_qc = kd.get_bz_q_points(first=True) if not qsym: self.ibzq_qc = self.bzq_qc self.weight_q = np.ones(len(self.bzq_qc)) / len(self.bzq_qc) else: U_scc = kd.symmetry.op_scc self.ibzq_qc = kd.get_ibz_q_points(self.bzq_qc, U_scc)[0] self.weight_q = kd.q_weights def read(self): lines = open(self.filename).readlines()[1:] n = 0 self.energy_qi = [] nq = len(lines) // len(self.ecut_i) for q_c in self.ibzq_qc[:nq]: self.energy_qi.append([]) for ecut in self.ecut_i: q1, q2, q3, ec, energy = [float(x) for x in lines[n].split()] self.energy_qi[-1].append(energy / Hartree) n += 1 if (abs(q_c - (q1, q2, q3)).max() > 1e-4 or abs(int(ecut * Hartree) - ec) > 0): self.energy_qi = [] return print('Read %d q-points from file: %s' % (nq, self.filename), file=self.fd) print(file=self.fd) def write(self): if self.world.rank == 0 and self.filename: fd = open(self.filename, 'w') print('#%9s %10s %10s %8s %12s' % ('q1', 'q2', 'q3', 'E_cut', 'E_c(q)'), file=fd) for energy_i, q_c in zip(self.energy_qi, self.ibzq_qc): for energy, ecut in zip(energy_i, self.ecut_i): print('%10.4f %10.4f %10.4f %8d %r' % (tuple(q_c) + (ecut * Hartree, energy * Hartree)), file=fd) 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 funtion. (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() 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=self.fd, timer=self.timer, world=self.world, no_optical_limit=self.wstc, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.wstc: with self.timer('WSTC-init'): p('Using Wigner-Seitz truncated Coulomb potential.') self.wstc = WignerSeitzTruncatedCoulomb( wfs.gd.cell_cv, wfs.kd.N_c, self.fd) 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 self.wstc or np.allclose(q_c, 0.0): # Wings (x=0,1) and head (G=0) for optical limit and three # directions (v=0,1,2): 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 Q_aGii = chi0.initialize_paw_corrections(pd) # 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, Q_aGii, 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 @timer('chi0(q)') def calculate_q(self, chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, Q_aGii, m1, m2, cut_G, A2_x): chi0_wGG = chi0_swGG[0] if chi0_swxvG is not None: chi0_wxvG = chi0_swxvG[0] chi0_wvv = chi0_swvv[0] else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, Q_aGii, m1, m2, [0, 1]) print('E_c(q) = ', end='', file=self.fd) chi0_wGG = chi0.redistribute(chi0_wGG, A2_x) if not pd.kd.gamma or self.wstc: e = self.calculate_energy(pd, chi0_wGG, cut_G) print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() else: e = 0.0 for v in range(3): chi0_wGG[:, 0] = chi0_wxvG[:, 0, v] chi0_wGG[:, :, 0] = chi0_wxvG[:, 1, v] chi0_wGG[:, 0, 0] = chi0_wvv[:, v, v] ev = self.calculate_energy(pd, chi0_wGG, cut_G) e += ev print('%.3f' % (ev * Hartree), end='', file=self.fd) if v < 2: print('/', end='', file=self.fd) else: print(' eV', file=self.fd) self.fd.flush() e /= 3 return e @timer('Energy') def calculate_energy(self, pd, chi0_wGG, cut_G): """Evaluate correlation energy from chi0.""" if self.wstc: invG_G = (self.wstc.get_potential(pd) / (4 * pi))**0.5 else: G_G = pd.G2_qG[0]**0.5 # |G+q| if pd.kd.gamma: G_G[0] = 1.0 invG_G = 1.0 / G_G if cut_G is not None: invG_G = invG_G[cut_G] nG = len(invG_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = (np.eye(nG) - 4 * np.pi * chi0_GG * invG_G * invG_G[:, np.newaxis]) e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy def extrapolate(self, e_i): print('Extrapolated energies:', file=self.fd) ex_i = [] for i in range(len(e_i) - 1): e1, e2 = e_i[i:i + 2] x1, x2 = self.ecut_i[i:i + 2]**-1.5 ex = (e1 * x2 - e2 * x1) / (x2 - x1) ex_i.append(ex) print(' %4.0f -%4.0f: %5.3f eV' % (self.ecut_i[i] * Hartree, self.ecut_i[i + 1] * Hartree, ex * Hartree), file=self.fd) print(file=self.fd) self.fd.flush() return e_i * Hartree def print_initialization(self, xc, frequency_scale, nlambda, user_spec): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('Non-self-consistent %s correlation energy' % xc) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', len(self.calc.wfs.kd.bzk_kc)) p('Number of irreducible k-points :', len(self.calc.wfs.kd.ibzk_kc)) p('Number of q-points :', len(self.bzq_qc)) p('Number of irreducible q-points :', len(self.ibzq_qc)) p() for q, weight in zip(self.ibzq_qc, self.weight_q): p(' q: [%1.4f %1.4f %1.4f] - weight: %1.3f' % (q[0], q[1], q[2], weight)) p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() if nlambda is None: p('Analytical coupling constant integration') else: p('Numerical coupling constant integration using', nlambda, 'Gauss-Legendre points') p() p('Frequencies') if not user_spec: p(' Gauss-Legendre integration with %s frequency points' % len(self.omega_w)) p(' Transformed from [0,oo] to [0,1] using e^[-aw^(1/B)]') p(' Highest frequency point at %5.1f eV and B=%1.1f' % (self.omega_w[-1] * Hartree, frequency_scale)) else: p(' User specified frequency integration with', len(self.omega_w), 'frequency points') p() p('Parallelization') p(' Total number of CPUs : % s' % self.world.size) p(' G-vector decomposition : % s' % self.nblocks) p(' K-point/band decomposition : % s' % (self.world.size // self.nblocks)) p()