name = 'si.k%d.g%d.c%d.s%d' % (k, gamma, center, bool(sym)) print(name) if 1: calc = a.calc = GPAW( kpts=kpts, eigensolver='rmm-diis', symmetry={'point_group': sym}, mode='pw', occupations=FermiDirac(width=0.001), txt=name + '.txt') e = a.get_potential_energy() calc.write(name, 'all') calc = GPAW(name, txt=None, communicator=serial_comm) chi = Chi0(calc, frequencies=omega, hilbert=False, ecut=100, txt=name + '.log') pd, chi0_wGG, _, _ = chi.calculate(q_c) if not sym and not center: chi00_w = chi0_wGG[:, 0, 0] elif -1 not in calc.wfs.kd.bz2bz_ks: assert abs(chi0_wGG[:, 0, 0] - chi00_w).max() < 3e-5 if not sym: chi00_wGG = chi0_wGG elif -1 not in calc.wfs.kd.bz2bz_ks: assert abs(chi0_wGG - chi00_wGG).max() < 2e-5 q0_c = [0, 1e-7, 1e-7] q0_v = np.dot(q0_c, a.get_reciprocal_cell() * 2 * np.pi) * Bohr q0 = (q0_v**2).sum()**0.5
def __init__(self, calc, name=None, frequencies=None, domega0=0.1, omega2=10.0, omegamax=None, ecut=50, hilbert=True, nbands=None, eta=0.2, ftol=1e-6, threshold=1, intraband=True, nblocks=1, world=mpi.world, txt=sys.stdout, gate_voltage=None, truncation=None, disable_point_group=False, disable_time_reversal=False, use_more_memory=1, unsymmetrized=True, eshift=None): """Creates a DielectricFunction object. calc: str The groundstate calculation file that the linear response calculation is based on. name: str If defined, save the density-density response function to:: name + '%+d%+d%+d.pckl' % tuple((q_c * kd.N_c).round()) where q_c is the reduced momentum and N_c is the number of kpoints along each direction. frequencies: np.ndarray Specification of frequency grid. If not set the non-linear frequency grid is used. domega0: float Frequency grid spacing for non-linear frequency grid at omega = 0. omega2: float Frequency at which the non-linear frequency grid has doubled the spacing. omegamax: float The upper frequency bound for the non-linear frequency grid. ecut: float Plane-wave cut-off. hilbert: bool Use hilbert transform. nbands: int Number of bands from calc. eta: float Broadening parameter. ftol: float Threshold for including close to equally occupied orbitals, f_ik - f_jk > ftol. threshold: float Threshold for matrix elements in optical response perturbation theory. intraband: bool Include intraband transitions. world: comm mpi communicator. nblocks: int Split matrices in nblocks blocks and distribute them G-vectors or frequencies over processes. txt: str Output file. gate_voltage: float Shift Fermi level of ground state calculation by the specified amount. truncation: str 'wigner-seitz' for Wigner Seitz truncated Coulomb. '2D, 1D or 0d for standard analytical truncation schemes. Non-periodic directions are determined from k-point grid """ self.chi0 = Chi0(calc, frequencies, domega0=domega0, omega2=omega2, omegamax=omegamax, ecut=ecut, hilbert=hilbert, nbands=nbands, eta=eta, ftol=ftol, threshold=threshold, intraband=intraband, world=world, nblocks=nblocks, txt=txt, gate_voltage=gate_voltage, disable_point_group=disable_point_group, disable_time_reversal=disable_time_reversal, use_more_memory=use_more_memory, unsymmetrized=unsymmetrized, eshift=eshift) self.name = name self.omega_w = self.chi0.omega_w nw = len(self.omega_w) world = self.chi0.world self.mynw = (nw + world.size - 1) // world.size self.w1 = min(self.mynw * world.rank, nw) self.w2 = min(self.w1 + self.mynw, nw) self.truncation = truncation
def calculate_screened_potential(self, ac): """Calculate W_GG(q)""" chi0 = Chi0(self.calc, frequencies=[0.0], eta=0.001, ecut=self.ecut, intraband=False, hilbert=False, nbands=self.nbands, txt='chi0.txt', world=world, ) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs self.Q_qaGii = [] self.W_qGG = [] self.pd_q = [] t0 = time() print('Calculating screened potential', file=self.fd) for iq, q_c in enumerate(self.qd.ibzk_kc): thisqd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, wfs.gd, complex, thisqd) nG = pd.ngmax chi0.Ga = self.blockcomm.rank * nG chi0.Gb = min(chi0.Ga + nG, nG) chi0_wGG = np.zeros((1, nG, nG), complex) if np.allclose(q_c, 0.0): chi0_wxvG = np.zeros((1, 2, 3, nG), complex) chi0_wvv = np.zeros((1, 3, 3), complex) else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, 0, self.nbands, spins='all', extend_head=False) chi0_GG = chi0_wGG[0] # Calculate eps^{-1}_GG if pd.kd.gamma: # Generate fine grid in vicinity of gamma kd = self.calc.wfs.kd N = 4 N_c = np.array([N, N, N]) if self.truncation is not None: # Only average periodic directions if trunction is used N_c[kd.N_c == 1] = 1 qf_qc = monkhorst_pack(N_c) / kd.N_c qf_qc *= 1.0e-6 U_scc = kd.symmetry.op_scc qf_qc = kd.get_ibz_q_points(qf_qc, U_scc)[0] weight_q = kd.q_weights qf_qv = 2 * np.pi * np.dot(qf_qc, pd.gd.icell_cv) a_q = np.sum(np.dot(chi0_wvv[0], qf_qv.T) * qf_qv.T, axis=0) a0_qG = np.dot(qf_qv, chi0_wxvG[0, 0]) a1_qG = np.dot(qf_qv, chi0_wxvG[0, 1]) einv_GG = np.zeros((nG, nG), complex) # W_GG = np.zeros((nG, nG), complex) for iqf in range(len(qf_qv)): chi0_GG[0] = a0_qG[iqf] chi0_GG[:, 0] = a1_qG[iqf] chi0_GG[0, 0] = a_q[iqf] sqrV_G = get_coulomb_kernel(pd, kd.N_c, truncation=self.truncation, wstc=self.wstc, q_v=qf_qv[iqf])**0.5 sqrV_G *= ac**0.5 # Multiply by adiabatic coupling e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] einv_GG += np.linalg.inv(e_GG) * weight_q[iqf] # einv_GG = np.linalg.inv(e_GG) * weight_q[iqf] # W_GG += (einv_GG * sqrV_G * sqrV_G[:, np.newaxis] # * weight_q[iqf]) else: sqrV_G = get_coulomb_kernel(pd, self.kd.N_c, truncation=self.truncation, wstc=self.wstc)**0.5 sqrV_G *= ac**0.5 # Multiply by adiabatic coupling e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] einv_GG = np.linalg.inv(e_GG) # W_GG = einv_GG * sqrV_G * sqrV_G[:, np.newaxis] # Now calculate W_GG if pd.kd.gamma: # Reset bare Coulomb interaction sqrV_G = get_coulomb_kernel(pd, self.kd.N_c, truncation=self.truncation, wstc=self.wstc)**0.5 W_GG = einv_GG * sqrV_G * sqrV_G[:, np.newaxis] if self.integrate_gamma != 0: # Numerical integration of Coulomb interaction at all q-points if self.integrate_gamma == 2: reduced = True else: reduced = False V0, sqrV0 = get_integrated_kernel(pd, self.kd.N_c, truncation=self.truncation, reduced=reduced, N=100) W_GG[0, 0] = einv_GG[0, 0] * V0 W_GG[0, 1:] = einv_GG[0, 1:] * sqrV0 * sqrV_G[1:] W_GG[1:, 0] = einv_GG[1:, 0] * sqrV_G[1:] * sqrV0 elif self.integrate_gamma == 0 and pd.kd.gamma: # Analytical integration at gamma bzvol = (2 * np.pi)**3 / self.vol / self.qd.nbzkpts Rq0 = (3 * bzvol / (4 * np.pi))**(1. / 3.) V0 = 16 * np.pi**2 * Rq0 / bzvol sqrV0 = (4 * np.pi)**(1.5) * Rq0**2 / bzvol / 2 W_GG[0, 0] = einv_GG[0, 0] * V0 W_GG[0, 1:] = einv_GG[0, 1:] * sqrV0 * sqrV_G[1:] W_GG[1:, 0] = einv_GG[1:, 0] * sqrV_G[1:] * sqrV0 else: pass if pd.kd.gamma: e = 1 / einv_GG[0, 0].real print(' RPA dielectric constant is: %3.3f' % e, file=self.fd) self.Q_qaGii.append(chi0.Q_aGii) self.pd_q.append(pd) self.W_qGG.append(W_GG) if iq % (self.qd.nibzkpts // 5 + 1) == 2: dt = time() - t0 tleft = dt * self.qd.nibzkpts / (iq + 1) - dt print(' Finished %s q-points in %s - Estimated %s left' % (iq + 1, timedelta(seconds=round(dt)), timedelta(seconds=round(tleft))), file=self.fd)
scaled_positions=TiO2_basis, cell=rutile_cell, pbc=(1, 1, 1)) data_s = [] for symmetry in ['off', {}]: bulk_calc = GPAW(mode=PW(pwcutoff, force_complex_dtype=True), kpts={ 'size': (k, k, k), 'gamma': True }, xc='PBE', occupations=FermiDirac(0.00001), parallel={'band': 1}, symmetry=symmetry) bulk_crystal.set_calculator(bulk_calc) e0_bulk_pbe = bulk_crystal.get_potential_energy() bulk_calc.write('bulk.gpw', mode='all') X = Chi0('bulk.gpw') chi_t = X.calculate([1. / 4, 0, 0])[1:] data_s.append(list(chi_t)) msg = 'Difference in Chi when turning off symmetries!' while len(data_s): data1 = data_s.pop() for data2 in data_s: for dat1, dat2 in zip(data1, data2): if dat1 is not None: equal(np.abs(dat1 - dat2).max(), 0, 1e-5, msg=msg)
print(name) if 1: calc = a.calc = GPAW(kpts=kpts, eigensolver='rmm-diis', symmetry={'point_group': sym}, mode='pw', occupations=FermiDirac(width=0.001), txt=name + '.txt') e = a.get_potential_energy() calc.write(name, 'all') calc = GPAW(name, txt=None, communicator=serial_comm) chi = Chi0(calc, omega, hilbert=False, ecut=100, txt=name + '.log') pd, chi0_wGG, _, _ = chi.calculate(q_c) if not sym and not center: chi00_w = chi0_wGG[:, 0, 0] elif -1 not in calc.wfs.kd.bz2bz_ks: assert abs(chi0_wGG[:, 0, 0] - chi00_w).max() < 3e-5 if not sym: chi00_wGG = chi0_wGG elif -1 not in calc.wfs.kd.bz2bz_ks: assert abs(chi0_wGG - chi00_wGG).max() < 2e-5 q0_c = [0, 1e-7, 1e-7]
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 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)