def add_potential_correction(self, v_R, alpha): dens = self.calc.density dens.D_asp.redistribute(dens.atom_partition.as_serial()) dens.Q_aL.redistribute(dens.atom_partition.as_serial()) dv_a1 = [] for a, D_sp in dens.D_asp.items(): setup = dens.setups[a] c = setup.xc_correction rgd = c.rgd ghat_g = gauss(rgd, 1 / setup.rcgauss**2) Z_g = gauss(rgd, alpha) * setup.Z D_q = np.dot(D_sp.sum(0), c.B_pqL[:, :, 0]) dn_g = np.dot(D_q, (c.n_qg - c.nt_qg)) * sqrt(4 * pi) dn_g += 4 * pi * (c.nc_g - c.nct_g) dn_g -= Z_g dn_g -= dens.Q_aL[a][0] * ghat_g * sqrt(4 * pi) dv_g = rgd.poisson(dn_g) / sqrt(4 * pi) dv_g[1:] /= rgd.r_g[1:] dv_g[0] = dv_g[1] dv_g[-1] = 0.0 dv_a1.append([rgd.spline(dv_g, points=POINTS)]) dens.D_asp.redistribute(dens.atom_partition) dens.Q_aL.redistribute(dens.atom_partition) if dv_a1: dv = LFC(self.gd, dv_a1) dv.set_positions(self.calc.spos_ac) dv.add(v_R) dens.gd.comm.broadcast(v_R, 0)
class HybridXC(HybridXCBase): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, gygi=False, alpha=None, skip_gamma=False, ecut=None, etotflag=False, acdf=False, coredensity=True, logfilename='-', bands=None, core_valence=True): """Mix standard functionals with exact exchange. bands: list or None List of bands to calculate energy for. Default is None meaning do all bands. """ self.alpha = alpha self.skip_gamma = skip_gamma self.gygi = gygi self.exx = 0.0 self.etotflag = etotflag self.ecut = ecut self.fd = logfilename self.write_timing_information = True self.bands = bands self.acdf = acdf # adiabatic-connection dissipation fluctuation for RPA correlation energy self.coredensity = coredensity self.core_valence = core_valence if self.acdf: self.exxacdf = 0.0 self.etotflag = True print('etotflag is True') HybridXCBase.__init__(self, name, hybrid, xc) def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) self.fd.flush() def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): addcoredensity = self.coredensity # XXX overwrites input return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd if self.bd.comm.size > 1: raise ValueError('Band parallelization not supported by hybridk') self.wfs = wfs self.world = wfs.world self.fd = logfile(self.fd, self.world.rank) N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: # XXX ? self.alpha = 6 * vol**(2 / 3.0) / pi**2 if self.ecut is None: self.ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max() * 0.9999 self.bzq_qc = self.kd.get_bz_q_points() qd = KPointDescriptor(self.bzq_qc) q0 = self.kd.where_is_q(np.zeros(3), self.bzq_qc) self.pwd = PWDescriptor(self.ecut, self.gd, complex, kd=qd) G2_qG = self.pwd.G2_qG G2_qG[q0][0] = 117.0 self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] G2_qG[q0][0] = 0.0 self.iG2_qG[q0][0] = 0.0 self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) * self.kd.nbzkpts) for q in range(self.kd.nbzkpts): self.gamma -= np.dot(np.exp(-self.alpha * G2_qG[q]), self.iG2_qG[q]) self.iG2_qG[q0][0] = self.gamma self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups], qd, dtype=complex) self.log('Value of alpha parameter:', self.alpha) self.log('Value of gamma parameter:', self.gamma) self.log('Cutoff energy:', self.ecut, 'Hartree') self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx def calculate_exx(self): """Non-selfconsistent calculation.""" kd = self.kd K = kd.nibzkpts W = self.world.size // self.nspins parallel = (W > 1) self.log("%d CPU's used for %d IBZ k-points" % (W, K)) self.log('Spins:', self.nspins) if self.etotflag and not self.gygi: self.nbandstmp = 0 for s in range(self.nspins): kpt1_k = [KPoint(kd, kpt) for kpt in self.kpt_u if kpt.s == s] for kpt1 in kpt1_k: for n1 in range(self.bd.nbands): f_n = kpt1.f_n[n1] if np.abs(f_n) < 1e-10: self.nbandstmp = max(self.nbandstmp, n1) break else: self.nbandstmp = self.bd.nbands tmp = np.zeros(kd.comm.size, dtype=int) kd.comm.all_gather(np.array([self.nbandstmp]), tmp) self.nbands = tmp.max() else: self.nbands = self.bd.nbands B = self.nbands self.log('Number of bands calculated:', B) self.log('Number of valence electrons:', self.setups.nvalence) E = B - self.setups.nvalence / 2.0 # empty bands self.npairs = (K * kd.nbzkpts - 0.5 * K**2) * (B**2 - E**2) self.log('Approximate number of pairs:', self.npairs) if not self.etotflag: self.exx_skn = np.zeros((self.nspins, K, B)) self.debug_skn = np.zeros((self.nspins, K, B)) for s in range(self.nspins): kpt1_q = [KPoint(kd, kpt) for kpt in self.kpt_u if kpt.s == s] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send rank: srank = kd.get_rank_and_index(s, (kpt1_q[0].k - 1) % K)[0] # Receive rank: rrank = kd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % K)[0] # Shift k-points K - 1 times: for i in range(K): if i < K - 1: if parallel: kpt = kpt2_q[-1].next() kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] for kpt1, kpt2 in zip(kpt1_q, kpt2_q): for k, ik in enumerate(kd.bz2ibz_k): if ik == kpt2.k: self.apply(kpt1, kpt2, k) if i < K - 1: if parallel: kpt.wait() kpt2_q[0].wait() kpt2_q.pop(0) kpt2_q.append(kpt) if self.etotflag: if self.acdf: self.exxacdf = self.world.sum(self.exxacdf[0]) self.exx = self.exxacdf else: self.exx = self.world.sum(self.exx) self.exx += self.calculate_exx_paw_correction() else: for kpt in self.kpt_u: for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) P_ni = kpt.P_ani[a] for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.exx_skn[kpt.s, kpt.k] -= \ (self.hybrid * A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) if self.core_valence: if setup.X_p is not None: self.exx_skn[kpt.s, kpt.k] -= self.hybrid * \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.nspins self.world.sum(self.exx_skn) self.exx = 0.0 for kpt in self.kpt_u: self.exx += 0.5 * np.dot(kpt.f_n, self.exx_skn[kpt.s, kpt.k]) self.exx = self.world.sum(self.exx) for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] if self.coredensity: self.exx += self.hybrid * setup.ExxC if self.core_valence: self.exx -= self.hybrid * 0.5 * np.dot( D_sp.sum(0), setup.X_p) self.world.sum(self.debug_skn) assert (self.debug_skn == self.kd.nbzkpts * B).all() def apply(self, kpt1, kpt2, k): k1_c = self.kd.ibzk_kc[kpt1.k] k20_c = self.kd.ibzk_kc[kpt2.k] k2_c = self.kd.bzk_kc[k] q_c = k2_c - k1_c N_c = self.gd.N_c q = self.kd.where_is_q(q_c, self.bzq_qc) q_c = self.bzq_qc[q] eik1r_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, k1_c / N_c).T) eik2r_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, k20_c / N_c).T) eiqr_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, q_c / N_c).T) same = abs(k1_c - k2_c).max() < 1e-9 iG2_G = self.iG2_qG[q] N = N_c.prod() vol = self.gd.dv * N nspins = self.nspins fcut = 1e-10 is_ibz2 = abs(k2_c - self.kd.ibzk_kc[kpt2.k]).max() < 1e-9 for n1 in range(self.nbands): f1 = kpt1.f_n[n1] e1 = kpt1.eps_n[n1] for n2 in range(self.nbands): if same: assert is_ibz2 if n2 > n1: continue elif is_ibz2: if kpt1.k > kpt2.k: if n2 > n1: continue else: if n2 >= n1: continue f2 = kpt2.f_n[n2] e2 = kpt2.eps_n[n2] x = 1.0 if same and n1 == n2: x = 0.5 if not self.etotflag: self.debug_skn[kpt1.s, kpt1.k, n1] += x if is_ibz2: self.debug_skn[kpt2.s, kpt2.k, n2] += x if self.etotflag and not self.gygi: if abs(f1) < fcut or abs(f2) < fcut: continue else: if abs(f1) < fcut and abs(f2) < fcut: continue if self.bands is not None: if not (n1 in self.bands or is_ibz2 and n2 in self.bands): continue if self.skip_gamma and same: continue t0 = time() nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, q, k, eik1r_R, eik2r_R, eiqr_R, is_ibz2) nt_G = self.pwd.fft(nt_R, q) / N vt_G = nt_G.copy() vt_G *= -pi * vol * iG2_G e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid * x if self.etotflag: if self.acdf: if self.gygi and same: self.exxacdf += f2 * e * kpt1.weight else: self.exxacdf += 0.5 * ( f1 * (1 - np.sign(e2 - e1)) * e + f2 * (1 - np.sign(e1 - e2)) * e) * kpt1.weight else: self.exx += f2 * e * kpt1.weight[ 0] * f1 * self.kd.nbzkpts * nspins / 2 else: self.exx_skn[kpt1.s, kpt1.k, n1] += 2 * f2 * e if is_ibz2: if self.etotflag: if self.acdf: if self.gygi and same: self.exxacdf += f1 * e * kpt2.weight else: self.exxacdf += 0.5 * ( f1 * (1 - np.sign(e2 - e1)) * e + f2 * (1 - np.sign(e1 - e2)) * e) * kpt2.weight else: self.exx += f1 * e * kpt2.weight[ 0] * f2 * self.kd.nbzkpts * nspins / 2 else: self.exx_skn[kpt2.s, kpt2.k, n2] += 2 * f1 * e if self.write_timing_information: t = time() - t0 self.log('Time for first pair-density:', t, 'seconds') self.log('Estimated total time', t * self.npairs / self.world.size, 'seconds') self.write_timing_information = False def calculate_exx_paw_correction(self): exx = 0 deg = 2 // self.nspins # spin degeneracy for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] exx -= self.hybrid / deg * D_ii[i1, i2] * A if self.core_valence: if setup.X_p is not None: exx -= self.hybrid * np.dot(D_p, setup.X_p) if self.coredensity: exx += self.hybrid * setup.ExxC return exx def calculate_pair_density(self, n1, n2, kpt1, kpt2, q, k, eik1r_R, eik2r_R, eiqr_R, ibz2): if isinstance(self.wfs, PWWaveFunctions): psit1_R = self.wfs.pd.ifft(kpt1.psit_nG[n1]) * eik1r_R psit2_R = self.wfs.pd.ifft(kpt2.psit_nG[n2]) * eik2r_R else: psit1_R = kpt1.psit_nG[n1] psit2_R = kpt2.psit_nG[n2] if ibz2: psit2_R = psit2_R else: psit2_R = np.asarray(self.kd.transform_wave_function(psit2_R, k), complex) nt_R = psit1_R.conj() * psit2_R s = self.kd.sym_k[k] time_reversal = self.kd.time_reversal_k[k] k2_c = self.kd.ibzk_kc[kpt2.k] Q_aL = {} for a, P1_ni in kpt1.P_ani.items(): P1_i = P1_ni[n1] b = self.kd.symmetry.a_sa[s, a] S_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s]) - self.spos_ac[b]) assert abs(S_c.round() - S_c).max() < 1e-13 x = np.exp(2j * pi * np.dot(k2_c, S_c)) P2_i = np.dot(self.setups[a].R_sii[s], kpt2.P_ani[b][n2]) * x if time_reversal: P2_i = P2_i.conj() if ibz2: P2_i = kpt2.P_ani[a][n2] D_ii = np.outer(P1_i.conj(), P2_i) D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) self.ghat.add(nt_R, Q_aL, q) return nt_R / eiqr_R
class HybridXC(XCFunctional): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, finegrid=False, alpha=None, skip_gamma=False, gygi=False, acdf=True, qsym=True, txt=None, ecut=None): """Mix standard functionals with exact exchange. name: str Name of hybrid functional. hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. finegrid: boolean Use fine grid for energy functional evaluations? """ if name == 'EXX': assert hybrid is None and xc is None hybrid = 1.0 xc = XC(XCNull()) elif name == 'PBE0': assert hybrid is None and xc is None hybrid = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif name == 'B3LYP': assert hybrid is None and xc is None hybrid = 0.2 xc = XC('HYB_GGA_XC_B3LYP') if isinstance(xc, str): xc = XC(xc) self.hybrid = hybrid self.xc = xc self.type = xc.type self.alpha = alpha self.qsym = qsym self.skip_gamma = skip_gamma self.gygi = gygi self.acdf = acdf self.exx = None self.ecut = ecut if txt is None: if rank == 0: #self.txt = devnull self.txt = sys.stdout else: sys.stdout = devnull self.txt = devnull else: assert type(txt) is str from ase.parallel import paropen self.txt = paropen(txt, 'w') XCFunctional.__init__(self, name) def get_setup_name(self): return 'PBE' def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.wfs = wfs self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd N_c = self.gd.N_c N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: # XXX ? self.alpha = 6 * vol**(2 / 3.0) / pi**2 self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) * self.kd.nbzkpts) if self.ecut is None: self.ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max() * 0.9999 assert self.kd.N_c is not None n = self.kd.N_c * 2 - 1 bzk_kc = np.indices(n).transpose((1, 2, 3, 0)) bzk_kc.shape = (-1, 3) bzk_kc -= self.kd.N_c - 1 self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c self.bzq_qc = self.kd.get_bz_q_points() if self.qsym: op_scc = self.kd.symmetry.op_scc self.ibzq_qc = self.kd.get_ibz_q_points(self.bzq_qc, op_scc)[0] self.q_weights = self.kd.q_weights * len(self.bzq_qc) else: self.ibzq_qc = self.bzq_qc self.q_weights = np.ones(len(self.bzq_qc)) self.pwd = PWDescriptor(self.ecut, self.gd, complex) self.G2_qG = self.pwd.g2(self.bzk_kc) n = 0 for k_c, Gpk2_G in zip(self.bzk_kc[:], self.G2_qG): if (k_c > -0.5).all() and (k_c <= 0.5).all(): #XXX??? if k_c.any(): self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G), Gpk2_G**-1) else: self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]), Gpk2_G[1:]**-1) n += 1 assert n == self.kd.N_c.prod() self.pwd = PWDescriptor(self.ecut, self.gd, complex) self.G2_qG = self.pwd.g2(self.ibzq_qc) self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups], KPointDescriptor(self.bzq_qc), dtype=complex) #self.interpolator = density.interpolator self.print_initialization(hamiltonian.xc.name) def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx def calculate_exx(self): """Non-selfconsistent calculation.""" kd = self.kd K = len(kd.bzk_kc) W = world.size // self.nspins parallel = (W > 1) self.exx = 0.0 self.exx_kq = np.zeros((K, len(self.ibzq_qc)), float) for s in range(self.nspins): ibz_kpts = [KPoint(kd, kpt) for kpt in self.kpt_u if kpt.s == s] for ik, kpt in enumerate(kd.bzk_kc): print('K %s %s ...' % (ik, kpt), file=self.txt) for iq, q in enumerate(self.ibzq_qc): kpq = kd.find_k_plus_q(q, kpts_k=[ik]) self.apply(ibz_kpts[kd.bz2ibz_k[ik]], ibz_kpts[kd.bz2ibz_k[kpq[0]]], ik, kpq[0], iq) self.exx = world.sum(self.exx) self.exx += self.calculate_exx_paw_correction() exx_q = np.sum(self.exx_kq, 0) print(file=self.txt) print('------------------------------------------------------', file=self.txt) print(file=self.txt) print('Contributions: q w E_q (eV)', file=self.txt) for q in range(len(exx_q)): print('[%1.3f %1.3f %1.3f] %1.3f %s' % \ (self.ibzq_qc[q][0], self.ibzq_qc[q][1], self.ibzq_qc[q][2], self.q_weights[q]/len(self.bzq_qc), exx_q[q]/self.q_weights[q]*len(self.bzq_qc)*Ha), file=self.txt) print('E_EXX = %s eV' % (self.exx * Ha), file=self.txt) print(file=self.txt) print('Calculation completed at: ', ctime(), file=self.txt) print(file=self.txt) print('------------------------------------------------------', file=self.txt) print(file=self.txt) def apply(self, kpt1, kpt2, ik1, ik2, iq): k1_c = self.kd.bzk_kc[ik1] k2_c = self.kd.bzk_kc[ik2] q = self.ibzq_qc[iq] if self.qsym: for i, q in enumerate(self.bzq_qc): if abs(q - self.ibzq_qc[iq]).max() < 1e-9: bzq_index = i break else: bzq_index = iq N_c = self.gd.N_c eikr_R = np.exp(-2j * pi * np.dot(np.indices(N_c).T, q / N_c).T) Gamma = abs(q).max() < 1e-9 if Gamma and self.skip_gamma: return Gpk2_G = self.G2_qG[iq] if Gamma: Gpk2_G = Gpk2_G.copy() Gpk2_G[0] = 1.0 / self.gamma N = N_c.prod() vol = self.gd.dv * N nspins = self.nspins fcut = 1e-10 for n1, psit1_R in enumerate(kpt1.psit_nG): f1 = kpt1.f_n[n1] for n2, psit2_R in enumerate(kpt2.psit_nG): if self.acdf: if self.gygi and Gamma: #print n2, kpt2.f_n[n2]/kpt2.weight f2 = (self.q_weights[iq] * kpt2.weight) else: f2 = (self.q_weights[iq] * kpt2.weight * (1 - np.sign(kpt2.eps_n[n2] - kpt1.eps_n[n1]))) else: f2 = kpt2.f_n[n2] * self.q_weights[iq] if abs(f1) < fcut or abs(f2) < fcut: continue nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, ik1, ik2, bzq_index) nt_G = self.pwd.fft(nt_R * eikr_R) / N vt_G = nt_G.copy() vt_G *= -pi * vol / Gpk2_G e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid self.exx += f1 * f2 * e self.exx_kq[ik1, iq] += f1 * f2 * e def calculate_pair_density(self, n1, n2, kpt1, kpt2, ik1, ik2, bzq_index): psit1_G = self.kd.transform_wave_function(kpt1.psit_nG[n1], ik1) psit2_G = self.kd.transform_wave_function(kpt2.psit_nG[n2], ik2) nt_G = psit1_G.conj() * psit2_G s1 = self.kd.sym_k[ik1] s2 = self.kd.sym_k[ik2] t1 = self.kd.time_reversal_k[ik1] t2 = self.kd.time_reversal_k[ik2] k1_c = self.kd.ibzk_kc[kpt1.k] k2_c = self.kd.ibzk_kc[kpt2.k] Q_aL = {} for a in kpt1.P_ani.keys(): b1 = self.kd.symmetry.a_sa[s1, a] b2 = self.kd.symmetry.a_sa[s2, a] S1_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s1]) - self.spos_ac[b1]) S2_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s2]) - self.spos_ac[b2]) assert abs(S1_c.round() - S1_c).max() < 1e-13 assert abs(S2_c.round() - S2_c).max() < 1e-13 x1 = np.exp(2j * pi * np.dot(k1_c, S1_c)) x2 = np.exp(2j * pi * np.dot(k2_c, S2_c)) P1_i = np.dot(self.setups[a].R_sii[s1], kpt1.P_ani[b1][n1]) * x1 P2_i = np.dot(self.setups[a].R_sii[s2], kpt2.P_ani[b2][n2]) * x2 if t1: P1_i = P1_i.conj() if t2: P2_i = P2_i.conj() D_ii = np.outer(P1_i.conj(), P2_i) D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) self.ghat.add(nt_G, Q_aL, bzq_index) return nt_G def calculate_exx_paw_correction(self): exx = 0 deg = 2 // self.nspins # spin degeneracy for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) exx -= self.hybrid / deg * D_ii[i1, i2] * A if setup.X_p is not None: exx -= self.hybrid * np.dot(D_p, setup.X_p) exx += self.hybrid * setup.ExxC return exx def print_initialization(self, xc): print('------------------------------------------------------', file=self.txt) print('Non-self-consistent HF correlation energy', file=self.txt) print('------------------------------------------------------', file=self.txt) print('Started at: ', ctime(), file=self.txt) print(file=self.txt) print('Ground state XC functional : %s' % xc, file=self.txt) print('Valence electrons : %s' % self.setups.nvalence, file=self.txt) print('Number of Spins : %s' % self.nspins, file=self.txt) print('Plane wave cutoff energy : %4.1f eV' % (self.ecut * Ha), file=self.txt) print('Gamma q-point excluded : %s' % self.skip_gamma, file=self.txt) if not self.skip_gamma: print('Alpha parameter : %s' % self.alpha, file=self.txt) print('Gamma parameter : %3.3f' % self.gamma, file=self.txt) print('ACDF method : %s' % self.acdf, file=self.txt) print('Number of k-points : %s' % len(self.kd.bzk_kc), file=self.txt) print('Number of Irreducible k-points : %s' % len(self.kd.ibzk_kc), file=self.txt) print('Number of q-points : %s' % len(self.bzq_qc), file=self.txt) if not self.qsym: print('q-point symmetry : %s' % self.qsym, file=self.txt) else: print('Number of Irreducible q-points : %s' % len(self.ibzq_qc), file=self.txt) print(file=self.txt) for q, weight in zip(self.ibzq_qc, self.q_weights): print('q: [%1.3f %1.3f %1.3f] - weight: %1.3f' % \ (q[0],q[1],q[2], weight/len(self.bzq_qc)), file=self.txt) print(file=self.txt) print('------------------------------------------------------', file=self.txt) print('------------------------------------------------------', file=self.txt) print(file=self.txt) print('Looping over k-points in the full Brillouin zone', file=self.txt) print(file=self.txt)
class RealSpaceHamiltonian(Hamiltonian): def __init__( self, gd, finegd, nspins, setups, timer, xc, world, kptband_comm, vext=None, collinear=True, psolver=None, stencil=3, ): Hamiltonian.__init__(self, gd, finegd, nspins, setups, timer, xc, world, kptband_comm, vext, collinear) # Solver for the Poisson equation: if psolver is None: psolver = PoissonSolver(nn=3, relax="J") self.poisson = psolver self.poisson.set_grid_descriptor(finegd) # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.gd, stencil) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.vbar_g = None def summary(self, fd): Hamiltonian.summary(self, fd) degree = self.restrictor.nn * 2 - 1 name = ["linear", "cubic", "quintic", "heptic"][degree // 2] fd.write("Interpolation: tri-%s " % name + "(%d. degree polynomial)\n" % degree) fd.write("Poisson solver: %s\n" % self.poisson.get_description()) def set_positions(self, spos_ac, rank_a): Hamiltonian.set_positions(self, spos_ac, rank_a) if self.vbar_g is None: self.vbar_g = self.finegd.empty() self.vbar_g[:] = 0.0 self.vbar.add(self.vbar_g) def update_pseudo_potential(self, density): self.timer.start("vbar") Ebar = self.finegd.integrate(self.vbar_g, density.nt_g, global_integral=False) vt_g = self.vt_sg[0] vt_g[:] = self.vbar_g self.timer.stop("vbar") Eext = 0.0 if self.vext is not None: assert self.collinear vt_g += self.vext.get_potential(self.finegd) Eext = self.finegd.integrate(vt_g, density.nt_g, global_integral=False) - Ebar self.vt_sg[1 : self.nspins] = vt_g self.vt_sg[self.nspins :] = 0.0 self.timer.start("XC 3D grid") Exc = self.xc.calculate(self.finegd, density.nt_sg, self.vt_sg) Exc /= self.gd.comm.size self.timer.stop("XC 3D grid") self.timer.start("Poisson") # npoisson is the number of iterations: self.npoisson = self.poisson.solve(self.vHt_g, density.rhot_g, charge=-density.charge) self.timer.stop("Poisson") self.timer.start("Hartree integrate/restrict") Epot = 0.5 * self.finegd.integrate(self.vHt_g, density.rhot_g, global_integral=False) Ekin = 0.0 s = 0 for s, (vt_g, vt_G, nt_G) in enumerate(zip(self.vt_sg, self.vt_sG, density.nt_sG)): if s < self.nspins: vt_g += self.vHt_g self.restrict(vt_g, vt_G) if self.ref_vt_sG is not None: vt_G += self.ref_vt_sG[s] if s < self.nspins: Ekin -= self.gd.integrate(vt_G, nt_G - density.nct_G, global_integral=False) else: Ekin -= self.gd.integrate(vt_G, nt_G, global_integral=False) s += 1 self.timer.stop("Hartree integrate/restrict") # Calculate atomic hamiltonians: W_aL = {} for a in density.D_asp: W_aL[a] = np.empty((self.setups[a].lmax + 1) ** 2) density.ghat.integrate(self.vHt_g, W_aL) return Ekin, Epot, Ebar, Eext, Exc, W_aL def calculate_forces2(self, dens, ghat_aLv, nct_av, vbar_av): if self.nspins == 2: vt_G = self.vt_sG.mean(0) else: vt_G = self.vt_sG[0] dens.ghat.derivative(self.vHt_g, ghat_aLv) dens.nct.derivative(vt_G, nct_av) self.vbar.derivative(dens.nt_g, vbar_av)
class Hamiltonian: """Hamiltonian object. Attributes: =============== ===================================================== ``xc`` ``XC3DGrid`` object. ``poisson`` ``PoissonSolver``. ``gd`` Grid descriptor for coarse grids. ``finegd`` Grid descriptor for fine grids. ``restrict`` Function for restricting the effective potential. =============== ===================================================== Soft and smooth pseudo functions on uniform 3D grids: ========== ========================================= ``vHt_g`` Hartree potential on the fine grid. ``vt_sG`` Effective potential on the coarse grid. ``vt_sg`` Effective potential on the fine grid. ========== ========================================= Energy contributions and forces: =========== ========================================== Description =========== ========================================== ``Ekin`` Kinetic energy. ``Epot`` Potential energy. ``Etot`` Total energy. ``Exc`` Exchange-Correlation energy. ``Eext`` Energy of external potential ``Eref`` Reference energy for all-electron atoms. ``S`` Entropy. ``Ebar`` Should be close to zero! =========== ========================================== """ def __init__(self, gd, finegd, nspins, setups, stencil, timer, xc, psolver, vext_g): """Create the Hamiltonian.""" self.gd = gd self.finegd = finegd self.nspins = nspins self.setups = setups self.timer = timer self.xc = xc # Solver for the Poisson equation: if psolver is None: psolver = PoissonSolver(nn=3, relax='J') self.poisson = psolver self.poisson.set_grid_descriptor(finegd) self.dH_asp = None # The external potential self.vext_g = vext_g self.vt_sG = None self.vHt_g = None self.vt_sg = None self.vbar_g = None self.rank_a = None # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.gd, stencil, allocate=False) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.Ekin0 = None self.Ekin = None self.Epot = None self.Ebar = None self.Eext = None self.Exc = None self.Etot = None self.S = None self.allocated = False def allocate(self): # TODO We should move most of the gd.empty() calls here assert not self.allocated self.restrictor.allocate() self.allocated = True def set_positions(self, spos_ac, rank_a=None): self.spos_ac = spos_ac if not self.allocated: self.allocate() self.vbar.set_positions(spos_ac) if self.vbar_g is None: self.vbar_g = self.finegd.empty() self.vbar_g[:] = 0.0 self.vbar.add(self.vbar_g) self.xc.set_positions(spos_ac) # If both old and new atomic ranks are present, start a blank dict if # it previously didn't exist but it will needed for the new atoms. if (self.rank_a is not None and rank_a is not None and self.dH_asp is None and (rank_a == self.gd.comm.rank).any()): self.dH_asp = {} if self.rank_a is not None and self.dH_asp is not None: self.timer.start('Redistribute') requests = [] flags = (self.rank_a != rank_a) my_incoming_atom_indices = np.argwhere(np.bitwise_and(flags, \ rank_a == self.gd.comm.rank)).ravel() my_outgoing_atom_indices = np.argwhere(np.bitwise_and(flags, \ self.rank_a == self.gd.comm.rank)).ravel() for a in my_incoming_atom_indices: # Get matrix from old domain: ni = self.setups[a].ni dH_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) requests.append(self.gd.comm.receive(dH_sp, self.rank_a[a], tag=a, block=False)) assert a not in self.dH_asp self.dH_asp[a] = dH_sp for a in my_outgoing_atom_indices: # Send matrix to new domain: dH_sp = self.dH_asp.pop(a) requests.append(self.gd.comm.send(dH_sp, rank_a[a], tag=a, block=False)) self.gd.comm.waitall(requests) self.timer.stop('Redistribute') self.rank_a = rank_a def aoom(self, DM, a, l, scale=1): """Atomic Orbital Occupation Matrix. Determine the Atomic Orbital Occupation Matrix (aoom) for a given l-quantum number. This operation, takes the density matrix (DM), which for example is given by unpack2(D_asq[i][spin]), and corrects for the overlap between the selected orbitals (l) upon which the the density is expanded (ex <p|p*>,<p|p>,<p*|p*> ). Returned is only the "corrected" part of the density matrix, which represents the orbital occupation matrix for l=2 this is a 5x5 matrix. """ S=self.setups[a] l_j = S.l_j n_j = S.n_j lq = S.lq nl = np.where(np.equal(l_j, l))[0] V = np.zeros(np.shape(DM)) if len(nl) == 2: aa = (nl[0])*len(l_j)-((nl[0]-1)*(nl[0])/2) bb = (nl[1])*len(l_j)-((nl[1]-1)*(nl[1])/2) ab = aa+nl[1]-nl[0] if(scale==0 or scale=='False' or scale =='false'): lq_a = lq[aa] lq_ab = lq[ab] lq_b = lq[bb] else: lq_a = 1 lq_ab = lq[ab]/lq[aa] lq_b = lq[bb]/lq[aa] # and the correct entrances in the DM nn = (2*np.array(l_j)+1)[0:nl[0]].sum() mm = (2*np.array(l_j)+1)[0:nl[1]].sum() # finally correct and add the four submatrices of NC_DM A = DM[nn:nn+2*l+1,nn:nn+2*l+1]*(lq_a) B = DM[nn:nn+2*l+1,mm:mm+2*l+1]*(lq_ab) C = DM[mm:mm+2*l+1,nn:nn+2*l+1]*(lq_ab) D = DM[mm:mm+2*l+1,mm:mm+2*l+1]*(lq_b) V[nn:nn+2*l+1,nn:nn+2*l+1]=+(lq_a) V[nn:nn+2*l+1,mm:mm+2*l+1]=+(lq_ab) V[mm:mm+2*l+1,nn:nn+2*l+1]=+(lq_ab) V[mm:mm+2*l+1,mm:mm+2*l+1]=+(lq_b) return A+B+C+D, V else: nn =(2*np.array(l_j)+1)[0:nl[0]].sum() A=DM[nn:nn+2*l+1,nn:nn+2*l+1]*lq[-1] V[nn:nn+2*l+1,nn:nn+2*l+1]=+lq[-1] return A,V def update(self, density): """Calculate effective potential. The XC-potential and the Hartree potential are evaluated on the fine grid, and the sum is then restricted to the coarse grid.""" self.timer.start('Hamiltonian') if self.vt_sg is None: self.timer.start('Initialize Hamiltonian') self.vt_sg = self.finegd.empty(self.nspins) self.vHt_g = self.finegd.zeros() self.vt_sG = self.gd.empty(self.nspins) self.poisson.initialize() self.timer.stop('Initialize Hamiltonian') self.timer.start('vbar') Ebar = self.finegd.integrate(self.vbar_g, density.nt_g, global_integral=False) vt_g = self.vt_sg[0] vt_g[:] = self.vbar_g self.timer.stop('vbar') Eext = 0.0 if self.vext_g is not None: vt_g += self.vext_g.get_potential(self.finegd) Eext = self.finegd.integrate(vt_g, density.nt_g, global_integral=False) - Ebar if self.nspins == 2: self.vt_sg[1] = vt_g self.timer.start('XC 3D grid') Exc = self.xc.calculate(self.finegd, density.nt_sg, self.vt_sg) Exc /= self.gd.comm.size self.timer.stop('XC 3D grid') self.timer.start('Poisson') # npoisson is the number of iterations: self.npoisson = self.poisson.solve(self.vHt_g, density.rhot_g, charge=-density.charge) self.timer.stop('Poisson') self.timer.start('Hartree integrate/restrict') Epot = 0.5 * self.finegd.integrate(self.vHt_g, density.rhot_g, global_integral=False) Ekin = 0.0 for vt_g, vt_G, nt_G in zip(self.vt_sg, self.vt_sG, density.nt_sG): vt_g += self.vHt_g self.restrict(vt_g, vt_G) Ekin -= self.gd.integrate(vt_G, nt_G - density.nct_G, global_integral=False) self.timer.stop('Hartree integrate/restrict') # Calculate atomic hamiltonians: self.timer.start('Atomic') W_aL = {} for a in density.D_asp: W_aL[a] = np.empty((self.setups[a].lmax + 1)**2) density.ghat.integrate(self.vHt_g, W_aL) self.dH_asp = {} for a, D_sp in density.D_asp.items(): W_L = W_aL[a] setup = self.setups[a] D_p = D_sp.sum(0) dH_p = (setup.K_p + setup.M_p + setup.MB_p + 2.0 * np.dot(setup.M_pp, D_p) + np.dot(setup.Delta_pL, W_L)) Ekin += np.dot(setup.K_p, D_p) + setup.Kc Ebar += setup.MB + np.dot(setup.MB_p, D_p) Epot += setup.M + np.dot(D_p, (setup.M_p + np.dot(setup.M_pp, D_p))) if self.vext_g is not None: vext = self.vext_g.get_taylor(spos_c=self.spos_ac[a, :]) # Tailor expansion to the zeroth order Eext += vext[0][0] * (sqrt(4 * pi) * density.Q_aL[a][0] + setup.Z) dH_p += vext[0][0] * sqrt(4 * pi) * setup.Delta_pL[:, 0] if len(vext) > 1: # Tailor expansion to the first order Eext += sqrt(4 * pi / 3) * np.dot(vext[1], density.Q_aL[a][1:4]) # there must be a better way XXXX Delta_p1 = np.array([setup.Delta_pL[:, 1], setup.Delta_pL[:, 2], setup.Delta_pL[:, 3]]) dH_p += sqrt(4 * pi / 3) * np.dot(vext[1], Delta_p1) self.dH_asp[a] = dH_sp = np.zeros_like(D_sp) self.timer.start('XC Correction') Exc += setup.xc_correction.calculate(self.xc, D_sp, dH_sp, a) self.timer.stop('XC Correction') if setup.HubU is not None: nspins = len(D_sp) l_j = setup.l_j l = setup.Hubl nl = np.where(np.equal(l_j,l))[0] nn = (2*np.array(l_j)+1)[0:nl[0]].sum() for D_p, H_p in zip(D_sp, self.dH_asp[a]): [N_mm,V] =self.aoom(unpack2(D_p),a,l) N_mm = N_mm / 2 * nspins Eorb = setup.HubU / 2. * (N_mm - np.dot(N_mm,N_mm)).trace() Vorb = setup.HubU * (0.5 * np.eye(2*l+1) - N_mm) Exc += Eorb if nspins == 1: # add contribution of other spin manyfold Exc += Eorb if len(nl)==2: mm = (2*np.array(l_j)+1)[0:nl[1]].sum() V[nn:nn+2*l+1,nn:nn+2*l+1] *= Vorb V[mm:mm+2*l+1,nn:nn+2*l+1] *= Vorb V[nn:nn+2*l+1,mm:mm+2*l+1] *= Vorb V[mm:mm+2*l+1,mm:mm+2*l+1] *= Vorb else: V[nn:nn+2*l+1,nn:nn+2*l+1] *= Vorb Htemp = unpack(H_p) Htemp += V H_p[:] = pack2(Htemp) dH_sp += dH_p Ekin -= (D_sp * dH_sp).sum() self.timer.stop('Atomic') # Make corrections due to non-local xc: #xcfunc = self.xc.xcfunc self.Enlxc = 0.0#XXXxcfunc.get_non_local_energy() Ekin += self.xc.get_kinetic_energy_correction() / self.gd.comm.size energies = np.array([Ekin, Epot, Ebar, Eext, Exc]) self.timer.start('Communicate energies') self.gd.comm.sum(energies) self.timer.stop('Communicate energies') (self.Ekin0, self.Epot, self.Ebar, self.Eext, self.Exc) = energies #self.Exc += self.Enlxc #self.Ekin0 += self.Enlkin self.timer.stop('Hamiltonian') def get_energy(self, occupations): self.Ekin = self.Ekin0 + occupations.e_band self.S = occupations.e_entropy # Total free energy: self.Etot = (self.Ekin + self.Epot + self.Eext + self.Ebar + self.Exc - self.S) return self.Etot def apply_local_potential(self, psit_nG, Htpsit_nG, s): """Apply the Hamiltonian operator to a set of vectors. XXX Parameter description is deprecated! Parameters: a_nG: ndarray Set of vectors to which the overlap operator is applied. b_nG: ndarray, output Resulting H times a_nG vectors. kpt: KPoint object k-point object defined in kpoint.py. calculate_projections: bool When True, the integrals of projector times vectors P_ni = <p_i | a_nG> are calculated. When False, existing P_uni are used local_part_only: bool When True, the non-local atomic parts of the Hamiltonian are not applied and calculate_projections is ignored. """ vt_G = self.vt_sG[s] if psit_nG.ndim == 3: Htpsit_nG += psit_nG * vt_G else: for psit_G, Htpsit_G in zip(psit_nG, Htpsit_nG): Htpsit_G += psit_G * vt_G def apply(self, a_xG, b_xG, wfs, kpt, calculate_P_ani=True): """Apply the Hamiltonian operator to a set of vectors. Parameters: a_nG: ndarray Set of vectors to which the overlap operator is applied. b_nG: ndarray, output Resulting S times a_nG vectors. wfs: WaveFunctions Wave-function object defined in wavefunctions.py kpt: KPoint object k-point object defined in kpoint.py. calculate_P_ani: bool When True, the integrals of projector times vectors P_ni = <p_i | a_nG> are calculated. When False, existing P_ani are used """ wfs.kin.apply(a_xG, b_xG, kpt.phase_cd) self.apply_local_potential(a_xG, b_xG, kpt.s) shape = a_xG.shape[:-3] P_axi = wfs.pt.dict(shape) if calculate_P_ani: #TODO calculate_P_ani=False is experimental wfs.pt.integrate(a_xG, P_axi, kpt.q) else: for a, P_ni in kpt.P_ani.items(): P_axi[a][:] = P_ni for a, P_xi in P_axi.items(): dH_ii = unpack(self.dH_asp[a][kpt.s]) P_axi[a] = np.dot(P_xi, dH_ii) wfs.pt.add(b_xG, P_axi, kpt.q) def get_xc_difference(self, xc, density): """Calculate non-selfconsistent XC-energy difference.""" if density.nt_sg is None: density.interpolate() nt_sg = density.nt_sg if hasattr(xc, 'hybrid'): xc.calculate_exx() Exc = xc.calculate(density.finegd, nt_sg) / self.gd.comm.size for a, D_sp in density.D_asp.items(): setup = self.setups[a] Exc += setup.xc_correction.calculate(xc, D_sp) Exc = self.gd.comm.sum(Exc) return Exc - self.Exc def get_vxc(self, density, wfs): """Calculate matrix elements of the xc-potential.""" dtype = wfs.dtype nbands = wfs.nbands nu = len(wfs.kpt_u) if density.nt_sg is None: density.interpolate() # Allocate space for result matrix Vxc_unn = np.empty((nu, nbands, nbands), dtype=dtype) # Get pseudo xc potential on the coarse grid Vxct_sG = self.gd.empty(self.nspins) Vxct_sg = self.finegd.zeros(self.nspins) if nspins == 1: self.xc.get_energy_and_potential(density.nt_sg[0], Vxct_sg[0]) else: self.xc.get_energy_and_potential(density.nt_sg[0], Vxct_sg[0], density.nt_sg[1], Vxct_sg[1]) for Vxct_G, Vxct_g in zip(Vxct_sG, Vxct_sg): self.restrict(Vxct_g, Vxct_G) del Vxct_sg # Get atomic corrections to the xc potential Vxc_asp = {} for a, D_sp in density.D_asp.items(): Vxc_asp[a] = np.zeros_like(D_sp) self.setups[a].xc_correction.calculate_energy_and_derivatives( D_sp, Vxc_asp[a]) # Project potential onto the eigenstates for kpt, Vxc_nn in xip(wfs.kpt_u, Vxc_unn): s, q = kpt.s, kpt.q psit_nG = kpt.psit_nG # Project pseudo part r2k(.5 * self.gd.dv, psit_nG, Vxct_sG[s] * psit_nG, 0.0, Vxc_nn) tri2full(Vxc_nn, 'L') self.gd.comm.sum(Vxc_nn) # Add atomic corrections # H_ij = \int dr phi_i(r) Ĥ phi_j^*(r) # P_ni = \int dr psi_n(r) pt_i^*(r) # Vxc_nm = \int dr phi_n(r) vxc(r) phi_m^*(r) # + sum_ij P_ni H_ij P_mj^* for a, P_ni in kpt.P_ani.items(): Vxc_ii = unpack(Vxc_asp[a][s]) Vxc_nn += np.dot(P_ni, np.inner(H_ii, P_ni).conj()) return Vxc_unn def estimate_memory(self, mem): nbytes = self.gd.bytecount() nfinebytes = self.finegd.bytecount() arrays = mem.subnode('Arrays', 0) arrays.subnode('vHt_g', nfinebytes) arrays.subnode('vt_sG', self.nspins * nbytes) arrays.subnode('vt_sg', self.nspins * nfinebytes) self.restrictor.estimate_memory(mem.subnode('Restrictor')) self.xc.estimate_memory(mem.subnode('XC')) self.poisson.estimate_memory(mem.subnode('Poisson')) self.vbar.estimate_memory(mem.subnode('vbar'))
class Density: """Density object. Attributes: =============== ===================================================== ``gd`` Grid descriptor for coarse grids. ``finegd`` Grid descriptor for fine grids. ``interpolate`` Function for interpolating the electron density. ``mixer`` ``DensityMixer`` object. =============== ===================================================== Soft and smooth pseudo functions on uniform 3D grids: ========== ========================================= ``nt_sG`` Electron density on the coarse grid. ``nt_sg`` Electron density on the fine grid. ``nt_g`` Electron density on the fine grid. ``rhot_g`` Charge density on the fine grid. ``nct_G`` Core electron-density on the coarse grid. ========== ========================================= """ def __init__(self, gd, finegd, nspins, charge): """Create the Density object.""" self.gd = gd self.finegd = finegd self.nspins = nspins self.charge = float(charge) self.charge_eps = 1e-7 self.D_asp = None self.Q_aL = None self.nct_G = None self.nt_sG = None self.rhot_g = None self.nt_sg = None self.nt_g = None self.rank_a = None self.mixer = BaseMixer() self.timer = nulltimer self.allocated = False def initialize(self, setups, stencil, timer, magmom_a, hund): self.timer = timer self.setups = setups self.hund = hund self.magmom_a = magmom_a # Interpolation function for the density: self.interpolator = Transformer(self.gd, self.finegd, stencil, allocate=False) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) if self.allocated: self.allocated = False self.allocate() def allocate(self): assert not self.allocated self.interpolator.allocate() self.allocated = True def reset(self): # TODO: reset other parameters? self.nt_sG = None def set_positions(self, spos_ac, rank_a=None): if not self.allocated: self.allocate() self.nct.set_positions(spos_ac) self.ghat.set_positions(spos_ac) self.mixer.reset() self.nct_G = self.gd.zeros() self.nct.add(self.nct_G, 1.0 / self.nspins) #self.nt_sG = None self.nt_sg = None self.nt_g = None self.rhot_g = None self.Q_aL = None # If both old and new atomic ranks are present, start a blank dict if # it previously didn't exist but it will needed for the new atoms. if (self.rank_a is not None and rank_a is not None and self.D_asp is None and (rank_a == self.gd.comm.rank).any()): self.D_asp = {} if self.rank_a is not None and self.D_asp is not None: self.timer.start('Redistribute') requests = [] flags = (self.rank_a != rank_a) my_incoming_atom_indices = np.argwhere(np.bitwise_and(flags, \ rank_a == self.gd.comm.rank)).ravel() my_outgoing_atom_indices = np.argwhere(np.bitwise_and(flags, \ self.rank_a == self.gd.comm.rank)).ravel() for a in my_incoming_atom_indices: # Get matrix from old domain: ni = self.setups[a].ni D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) requests.append(self.gd.comm.receive(D_sp, self.rank_a[a], tag=a, block=False)) assert a not in self.D_asp self.D_asp[a] = D_sp for a in my_outgoing_atom_indices: # Send matrix to new domain: D_sp = self.D_asp.pop(a) requests.append(self.gd.comm.send(D_sp, rank_a[a], tag=a, block=False)) self.gd.comm.waitall(requests) self.timer.stop('Redistribute') self.rank_a = rank_a def calculate_pseudo_density(self, wfs): """Calculate nt_sG from scratch. nt_sG will be equal to nct_G plus the contribution from wfs.add_to_density(). """ wfs.calculate_density_contribution(self.nt_sG) self.nt_sG += self.nct_G def update(self, wfs): self.timer.start('Density') self.timer.start('Pseudo density') self.calculate_pseudo_density(wfs) self.timer.stop('Pseudo density') self.timer.start('Atomic density matrices') wfs.calculate_atomic_density_matrices(self.D_asp) self.timer.stop('Atomic density matrices') self.timer.start('Multipole moments') comp_charge = self.calculate_multipole_moments() self.timer.stop('Multipole moments') if isinstance(wfs, LCAOWaveFunctions): self.timer.start('Normalize') self.normalize(comp_charge) self.timer.stop('Normalize') self.timer.start('Mix') self.mix(comp_charge) self.timer.stop('Mix') self.timer.stop('Density') def normalize(self, comp_charge=None): """Normalize pseudo density.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() pseudo_charge = self.gd.integrate(self.nt_sG).sum() if pseudo_charge + self.charge + comp_charge != 0: if pseudo_charge != 0: x = -(self.charge + comp_charge) / pseudo_charge self.nt_sG *= x else: # Use homogeneous background: self.nt_sG[:] = (self.charge + comp_charge) * self.gd.dv def calculate_pseudo_charge(self, comp_charge): self.nt_g = self.nt_sg.sum(axis=0) self.rhot_g = self.nt_g.copy() self.ghat.add(self.rhot_g, self.Q_aL) if debug: charge = self.finegd.integrate(self.rhot_g) + self.charge if abs(charge) > self.charge_eps: raise RuntimeError('Charge not conserved: excess=%.9f' % charge) def mix(self, comp_charge): if not self.mixer.mix_rho: self.mixer.mix(self) comp_charge = None self.interpolate(comp_charge) self.calculate_pseudo_charge(comp_charge) if self.mixer.mix_rho: self.mixer.mix(self) def interpolate(self, comp_charge=None): """Interpolate pseudo density to fine grid.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() if self.nt_sg is None: self.nt_sg = self.finegd.empty(self.nspins) for s in range(self.nspins): self.interpolator.apply(self.nt_sG[s], self.nt_sg[s]) # With periodic boundary conditions, the interpolation will # conserve the number of electrons. if not self.gd.pbc_c.all(): # With zero-boundary conditions in one or more directions, # this is not the case. pseudo_charge = -(self.charge + comp_charge) if abs(pseudo_charge) > 1.0e-14: x = pseudo_charge / self.finegd.integrate(self.nt_sg).sum() self.nt_sg *= x def calculate_multipole_moments(self): """Calculate multipole moments of compensation charges. Returns the total compensation charge in units of electron charge, so the number will be negative because of the dominating contribution from the nuclear charge.""" comp_charge = 0.0 self.Q_aL = {} for a, D_sp in self.D_asp.items(): Q_L = self.Q_aL[a] = np.dot(D_sp.sum(0), self.setups[a].Delta_pL) Q_L[0] += self.setups[a].Delta0 comp_charge += Q_L[0] return self.gd.comm.sum(comp_charge) * sqrt(4 * pi) def initialize_from_atomic_densities(self, basis_functions): """Initialize D_asp, nt_sG and Q_aL from atomic densities. nt_sG is initialized from atomic orbitals, and will be constructed with the specified magnetic moments and obeying Hund's rules if ``hund`` is true.""" # XXX does this work with blacs? What should be distributed? # Apparently this doesn't use blacs at all, so it's serial # with respect to the blacs distribution. That means it works # but is not particularly efficient (not that this is a time # consuming step) f_sM = np.empty((self.nspins, basis_functions.Mmax)) self.D_asp = {} f_asi = {} for a in basis_functions.atom_indices: c = self.charge / len(self.setups) # distribute on all atoms f_si = self.setups[a].calculate_initial_occupation_numbers( self.magmom_a[a], self.hund, charge=c, nspins=self.nspins) if a in basis_functions.my_atom_indices: self.D_asp[a] = self.setups[a].initialize_density_matrix(f_si) f_asi[a] = f_si self.nt_sG = self.gd.zeros(self.nspins) basis_functions.add_to_density(self.nt_sG, f_asi) self.nt_sG += self.nct_G self.calculate_normalized_charges_and_mix() def initialize_from_wavefunctions(self, wfs): """Initialize D_asp, nt_sG and Q_aL from wave functions.""" self.nt_sG = self.gd.empty(self.nspins) self.calculate_pseudo_density(wfs) self.D_asp = {} my_atom_indices = np.argwhere(wfs.rank_a == self.gd.comm.rank).ravel() for a in my_atom_indices: ni = self.setups[a].ni self.D_asp[a] = np.empty((self.nspins, ni * (ni + 1) // 2)) wfs.calculate_atomic_density_matrices(self.D_asp) self.calculate_normalized_charges_and_mix() def initialize_directly_from_arrays(self, nt_sG, D_asp): """Set D_asp and nt_sG directly.""" self.nt_sG = nt_sG self.D_asp = D_asp #self.calculate_normalized_charges_and_mix() # No calculate multipole moments? Tests will fail because of # improperly initialized mixer def calculate_normalized_charges_and_mix(self): comp_charge = self.calculate_multipole_moments() self.normalize(comp_charge) self.mix(comp_charge) def set_mixer(self, mixer): if mixer is not None: if self.nspins == 1 and isinstance(mixer, MixerSum): raise RuntimeError('Cannot use MixerSum with nspins==1') self.mixer = mixer else: if self.gd.pbc_c.any(): beta = 0.1 weight = 50.0 else: beta = 0.25 weight = 1.0 if self.nspins == 2: self.mixer = MixerSum(beta=beta, weight=weight) else: self.mixer = Mixer(beta=beta, weight=weight) self.mixer.initialize(self) def estimate_magnetic_moments(self): magmom_a = np.zeros_like(self.magmom_a) if self.nspins == 2: for a, D_sp in self.D_asp.items(): magmom_a[a] = np.dot(D_sp[0] - D_sp[1], self.setups[a].N0_p) self.gd.comm.sum(magmom_a) return magmom_a def get_correction(self, a, spin): """Integrated atomic density correction. Get the integrated correction to the pseuso density relative to the all-electron density. """ setup = self.setups[a] return sqrt(4 * pi) * ( np.dot(self.D_asp[a][spin], setup.Delta_pL[:, 0]) + setup.Delta0 / self.nspins) def get_density_array(self): XXX # XXX why not replace with get_spin_density and get_total_density? """Return pseudo-density array.""" if self.nspins == 2: return self.nt_sG else: return self.nt_sG[0] def get_all_electron_density(self, atoms, gridrefinement=2): """Return real all-electron density array.""" # Refinement of coarse grid, for representation of the AE-density if gridrefinement == 1: gd = self.gd n_sg = self.nt_sG.copy() elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = LFC(gd, phi_aj) phit = LFC(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) spos_ac = atoms.get_scaled_positions() % 1.0 phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) all_D_asp = [] for a, setup in enumerate(self.setups): D_sp = self.D_asp.get(a) if D_sp is None: ni = setup.ni D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) if gd.comm.size > 1: gd.comm.broadcast(D_sp, self.rank_a[a]) all_D_asp.append(D_sp) for s in range(self.nspins): I_a = np.zeros(len(atoms)) nc.add1(n_sg[s], 1.0 / self.nspins, I_a) nct.add1(n_sg[s], -1.0 / self.nspins, I_a) phi.add2(n_sg[s], all_D_asp, s, 1.0, I_a) phit.add2(n_sg[s], all_D_asp, s, -1.0, I_a) for a, D_sp in self.D_asp.items(): setup = self.setups[a] I_a[a] -= ((setup.Nc - setup.Nct) / self.nspins + sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd def new_get_all_electron_density(self, atoms, gridrefinement=2): """Return real all-electron density array.""" # Refinement of coarse grid, for representation of the AE-density if gridrefinement == 1: gd = self.gd n_sg = self.nt_sG.copy() elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = BasisFunctions(gd, phi_aj) phit = BasisFunctions(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) spos_ac = atoms.get_scaled_positions() % 1.0 phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) I_sa = np.zeros((self.nspins, len(atoms))) a_W = np.empty(len(phi.M_W), np.int32) W = 0 for a in phi.atom_indices: nw = len(phi.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw rho_MM = np.zeros((phi.Mmax, phi.Mmax)) for s, I_a in enumerate(I_sa): M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni D_sp = self.D_asp.get(a) if D_sp is None: D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) else: I_a[a] = ((setup.Nct - setup.Nc) / self.nspins - sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) if gd.comm.size > 1: gd.comm.broadcast(D_sp, self.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = unpack2(D_sp[s]) M1 = M2 phi.lfc.ae_valence_density_correction(rho_MM, n_sg[s], a_W, I_a) phit.lfc.ae_valence_density_correction(-rho_MM, n_sg[s], a_W, I_a) a_W = np.empty(len(nc.M_W), np.int32) W = 0 for a in nc.atom_indices: nw = len(nc.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw scale = 1.0 / self.nspins for s, I_a in enumerate(I_sa): nc.lfc.ae_core_density_correction(scale, n_sg[s], a_W, I_a) nct.lfc.ae_core_density_correction(-scale, n_sg[s], a_W, I_a) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd if extra_parameters.get('usenewlfc', True): get_all_electron_density = new_get_all_electron_density def estimate_memory(self, mem): nspins = self.nspins nbytes = self.gd.bytecount() nfinebytes = self.finegd.bytecount() arrays = mem.subnode('Arrays') for name, size in [('nt_sG', nbytes * nspins), ('nt_sg', nfinebytes * nspins), ('nt_g', nfinebytes), ('rhot_g', nfinebytes), ('nct_G', nbytes)]: arrays.subnode(name, size) lfs = mem.subnode('Localized functions') for name, obj in [('nct', self.nct), ('ghat', self.ghat)]: obj.estimate_memory(lfs.subnode(name)) self.mixer.estimate_memory(mem.subnode('Mixer'), self.gd) # TODO # The implementation of interpolator memory use is not very # accurate; 20 MiB vs 13 MiB estimated in one example, probably # worse for parallel calculations. self.interpolator.estimate_memory(mem.subnode('Interpolator')) def get_spin_contamination(self, atoms, majority_spin=0): """Calculate the spin contamination. Spin contamination is defined as the integral over the spin density difference, where it is negative (i.e. the minority spin density is larger than the majority spin density. """ if majority_spin == 0: smaj = 0 smin = 1 else: smaj = 1 smin = 0 nt_sg, gd = self.get_all_electron_density(atoms) dt_sg = nt_sg[smin] - nt_sg[smaj] dt_sg = np.where(dt_sg > 0, dt_sg, 0.0) return gd.integrate(dt_sg)
class HybridXC(XCFunctional): orbital_dependent = True def __init__( self, name, hybrid=None, xc=None, finegrid=False, alpha=None, skip_gamma=False, gygi=False, acdf=True, qsym=True, txt=None, ecut=None, ): """Mix standard functionals with exact exchange. name: str Name of hybrid functional. hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. finegrid: boolean Use fine grid for energy functional evaluations? """ if name == "EXX": assert hybrid is None and xc is None hybrid = 1.0 xc = XC(XCNull()) elif name == "PBE0": assert hybrid is None and xc is None hybrid = 0.25 xc = XC("HYB_GGA_XC_PBEH") elif name == "B3LYP": assert hybrid is None and xc is None hybrid = 0.2 xc = XC("HYB_GGA_XC_B3LYP") if isinstance(xc, str): xc = XC(xc) self.hybrid = hybrid self.xc = xc self.type = xc.type self.alpha = alpha self.qsym = qsym self.skip_gamma = skip_gamma self.gygi = gygi self.acdf = acdf self.exx = None self.ecut = ecut if txt is None: if rank == 0: # self.txt = devnull self.txt = sys.stdout else: sys.stdout = devnull self.txt = devnull else: assert type(txt) is str from ase.parallel import paropen self.txt = paropen(txt, "w") XCFunctional.__init__(self, name) def get_setup_name(self): return "PBE" def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.wfs = wfs self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd N_c = self.gd.N_c N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: # XXX ? self.alpha = 6 * vol ** (2 / 3.0) / pi ** 2 self.gamma = vol / (2 * pi) ** 2 * sqrt(pi / self.alpha) * self.kd.nbzkpts if self.ecut is None: self.ecut = 0.5 * pi ** 2 / (self.gd.h_cv ** 2).sum(1).max() * 0.9999 assert self.kd.N_c is not None n = self.kd.N_c * 2 - 1 bzk_kc = np.indices(n).transpose((1, 2, 3, 0)) bzk_kc.shape = (-1, 3) bzk_kc -= self.kd.N_c - 1 self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c self.bzq_qc = self.kd.get_bz_q_points() if self.qsym: op_scc = self.kd.symmetry.op_scc self.ibzq_qc = self.kd.get_ibz_q_points(self.bzq_qc, op_scc)[0] self.q_weights = self.kd.q_weights * len(self.bzq_qc) else: self.ibzq_qc = self.bzq_qc self.q_weights = np.ones(len(self.bzq_qc)) self.pwd = PWDescriptor(self.ecut, self.gd, complex) self.G2_qG = self.pwd.g2(self.bzk_kc) n = 0 for k_c, Gpk2_G in zip(self.bzk_kc[:], self.G2_qG): if (k_c > -0.5).all() and (k_c <= 0.5).all(): # XXX??? if k_c.any(): self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G), Gpk2_G ** -1) else: self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]), Gpk2_G[1:] ** -1) n += 1 assert n == self.kd.N_c.prod() self.pwd = PWDescriptor(self.ecut, self.gd, complex) self.G2_qG = self.pwd.g2(self.ibzq_qc) self.ghat = LFC( self.gd, [setup.ghat_l for setup in density.setups], KPointDescriptor(self.bzq_qc), dtype=complex ) # self.interpolator = density.interpolator self.print_initialization(hamiltonian.xc.name) def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx def calculate_exx(self): """Non-selfconsistent calculation.""" kd = self.kd K = len(kd.bzk_kc) W = world.size // self.nspins parallel = W > 1 self.exx = 0.0 self.exx_kq = np.zeros((K, len(self.ibzq_qc)), float) for s in range(self.nspins): ibz_kpts = [KPoint(kd, kpt) for kpt in self.kpt_u if kpt.s == s] for ik, kpt in enumerate(kd.bzk_kc): print >>self.txt, "K %s %s ..." % (ik, kpt) for iq, q in enumerate(self.ibzq_qc): kpq = kd.find_k_plus_q(q, kpts_k=[ik]) self.apply(ibz_kpts[kd.bz2ibz_k[ik]], ibz_kpts[kd.bz2ibz_k[kpq[0]]], ik, kpq[0], iq) self.exx = world.sum(self.exx) self.exx += self.calculate_exx_paw_correction() exx_q = np.sum(self.exx_kq, 0) print >>self.txt print >>self.txt, "------------------------------------------------------" print >>self.txt print >>self.txt, "Contributions: q w E_q (eV)" for q in range(len(exx_q)): print >>self.txt, "[%1.3f %1.3f %1.3f] %1.3f %s" % ( self.ibzq_qc[q][0], self.ibzq_qc[q][1], self.ibzq_qc[q][2], self.q_weights[q] / len(self.bzq_qc), exx_q[q] / self.q_weights[q] * len(self.bzq_qc) * Ha, ) print >>self.txt, "E_EXX = %s eV" % (self.exx * Ha) print >>self.txt print >>self.txt, "Calculation completed at: ", ctime() print >>self.txt print >>self.txt, "------------------------------------------------------" print >>self.txt def apply(self, kpt1, kpt2, ik1, ik2, iq): k1_c = self.kd.bzk_kc[ik1] k2_c = self.kd.bzk_kc[ik2] q = self.ibzq_qc[iq] if self.qsym: for i, q in enumerate(self.bzq_qc): if abs(q - self.ibzq_qc[iq]).max() < 1e-9: bzq_index = i break else: bzq_index = iq N_c = self.gd.N_c eikr_R = np.exp(-2j * pi * np.dot(np.indices(N_c).T, q / N_c).T) Gamma = abs(q).max() < 1e-9 if Gamma and self.skip_gamma: return Gpk2_G = self.G2_qG[iq] if Gamma: Gpk2_G = Gpk2_G.copy() Gpk2_G[0] = 1.0 / self.gamma N = N_c.prod() vol = self.gd.dv * N nspins = self.nspins fcut = 1e-10 for n1, psit1_R in enumerate(kpt1.psit_nG): f1 = kpt1.f_n[n1] for n2, psit2_R in enumerate(kpt2.psit_nG): if self.acdf: if self.gygi and Gamma: # print n2, kpt2.f_n[n2]/kpt2.weight f2 = self.q_weights[iq] * kpt2.weight else: f2 = self.q_weights[iq] * kpt2.weight * (1 - np.sign(kpt2.eps_n[n2] - kpt1.eps_n[n1])) else: f2 = kpt2.f_n[n2] * self.q_weights[iq] if abs(f1) < fcut or abs(f2) < fcut: continue nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, ik1, ik2, bzq_index) nt_G = self.pwd.fft(nt_R * eikr_R) / N vt_G = nt_G.copy() vt_G *= -pi * vol / Gpk2_G e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid self.exx += f1 * f2 * e self.exx_kq[ik1, iq] += f1 * f2 * e def calculate_pair_density(self, n1, n2, kpt1, kpt2, ik1, ik2, bzq_index): psit1_G = self.kd.transform_wave_function(kpt1.psit_nG[n1], ik1) psit2_G = self.kd.transform_wave_function(kpt2.psit_nG[n2], ik2) nt_G = psit1_G.conj() * psit2_G s1 = self.kd.sym_k[ik1] s2 = self.kd.sym_k[ik2] t1 = self.kd.time_reversal_k[ik1] t2 = self.kd.time_reversal_k[ik2] k1_c = self.kd.ibzk_kc[kpt1.k] k2_c = self.kd.ibzk_kc[kpt2.k] Q_aL = {} for a in kpt1.P_ani.keys(): b1 = self.kd.symmetry.a_sa[s1, a] b2 = self.kd.symmetry.a_sa[s2, a] S1_c = np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s1]) - self.spos_ac[b1] S2_c = np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s2]) - self.spos_ac[b2] assert abs(S1_c.round() - S1_c).max() < 1e-13 assert abs(S2_c.round() - S2_c).max() < 1e-13 x1 = np.exp(2j * pi * np.dot(k1_c, S1_c)) x2 = np.exp(2j * pi * np.dot(k2_c, S2_c)) P1_i = np.dot(self.setups[a].R_sii[s1], kpt1.P_ani[b1][n1]) * x1 P2_i = np.dot(self.setups[a].R_sii[s2], kpt2.P_ani[b2][n2]) * x2 if t1: P1_i = P1_i.conj() if t2: P2_i = P2_i.conj() D_ii = np.outer(P1_i.conj(), P2_i) D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) self.ghat.add(nt_G, Q_aL, bzq_index) return nt_G def calculate_exx_paw_correction(self): exx = 0 deg = 2 // self.nspins # spin degeneracy for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) exx -= self.hybrid / deg * D_ii[i1, i2] * A if setup.X_p is not None: exx -= self.hybrid * np.dot(D_p, setup.X_p) exx += self.hybrid * setup.ExxC return exx def print_initialization(self, xc): print >>self.txt, "------------------------------------------------------" print >>self.txt, "Non-self-consistent HF correlation energy" print >>self.txt, "------------------------------------------------------" print >>self.txt, "Started at: ", ctime() print >>self.txt print >>self.txt, "Ground state XC functional : %s" % xc print >>self.txt, "Valence electrons : %s" % self.setups.nvalence print >>self.txt, "Number of Spins : %s" % self.nspins print >>self.txt, "Plane wave cutoff energy : %4.1f eV" % (self.ecut * Ha) print >>self.txt, "Gamma q-point excluded : %s" % self.skip_gamma if not self.skip_gamma: print >>self.txt, "Alpha parameter : %s" % self.alpha print >>self.txt, "Gamma parameter : %3.3f" % self.gamma print >>self.txt, "ACDF method : %s" % self.acdf print >>self.txt, "Number of k-points : %s" % len(self.kd.bzk_kc) print >>self.txt, "Number of Irreducible k-points : %s" % len(self.kd.ibzk_kc) print >>self.txt, "Number of q-points : %s" % len(self.bzq_qc) if not self.qsym: print >>self.txt, "q-point symmetry : %s" % self.qsym else: print >>self.txt, "Number of Irreducible q-points : %s" % len(self.ibzq_qc) print >>self.txt for q, weight in zip(self.ibzq_qc, self.q_weights): print >>self.txt, "q: [%1.3f %1.3f %1.3f] - weight: %1.3f" % (q[0], q[1], q[2], weight / len(self.bzq_qc)) print >>self.txt print >>self.txt, "------------------------------------------------------" print >>self.txt, "------------------------------------------------------" print >>self.txt print >>self.txt, "Looping over k-points in the full Brillouin zone" print >>self.txt
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? """ self.finegrid = finegrid self.unocc = unocc HybridXCBase.__init__(self, name, hybrid, xc) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kpt_comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) if self.finegrid: self.poissonsolver = hamiltonian.poisson self.ghat = density.ghat self.interpolator = density.interpolator self.restrictor = hamiltonian.restrictor else: self.poissonsolver = PoissonSolver(eps=1e-11) self.poissonsolver.set_grid_descriptor(density.gd) self.poissonsolver.initialize() self.ghat = LFC(density.gd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.gd = density.gd self.finegd = self.ghat.gd def set_positions(self, spos_ac): if not self.finegrid: self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() nocc = int(kpt.f_n.sum()) // (3 - self.nspins) if self.unocc: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density(n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 iter = self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G else: Htpsit_nG[n2] += f1 * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii # Apply the atomic corrections to the energy and the Hamiltonian matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize(np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] -= 2 * hybrid / deg * A / ((i1 != i2) + 1) ekin += 2 * hybrid / deg * D_ii[i1, i2] * A exx -= hybrid / deg * D_ii[i1, i2] * A # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return if self.gd.comm.rank > 0: H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize(np.inner(P_ni[:nbands], kpt.vxx_ani[a])) self.gd.comm.sum(H_nn) H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
class HybridXC(XCFunctional): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, finegrid=False, alpha=None): """Mix standard functionals with exact exchange. name: str Name of hybrid functional. hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. finegrid: boolean Use fine grid for energy functional evaluations? """ if name == 'EXX': assert hybrid is None and xc is None hybrid = 1.0 xc = XC(XCNull()) elif name == 'PBE0': assert hybrid is None and xc is None hybrid = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif name == 'B3LYP': assert hybrid is None and xc is None hybrid = 0.2 xc = XC('HYB_GGA_XC_B3LYP') if isinstance(xc, str): xc = XC(xc) self.hybrid = hybrid self.xc = xc self.type = xc.type self.alpha = alpha self.exx = 0.0 XCFunctional.__init__(self, name) def get_setup_name(self): return 'PBE' def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd N_c = self.gd.N_c N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: self.alpha = 6 * vol**(2 / 3.0) / pi**2 self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) * self.kd.nbzkpts) ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max() if self.kd.N_c is None: self.bzk_kc = np.zeros((1, 3)) dfghdfgh else: n = self.kd.N_c * 2 - 1 bzk_kc = np.indices(n).transpose((1, 2, 3, 0)) bzk_kc.shape = (-1, 3) bzk_kc -= self.kd.N_c - 1 self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c self.pwd = PWDescriptor(ecut, self.gd, self.bzk_kc) n = 0 for k_c, Gpk2_G in zip(self.bzk_kc[:], self.pwd.G2_qG): if (k_c > -0.5).all() and (k_c <= 0.5).all(): #XXX??? if k_c.any(): self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G), Gpk2_G**-1) else: self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]), Gpk2_G[1:]**-1) n += 1 assert n == self.kd.N_c.prod() self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups], dtype=complex) self.ghat.set_k_points(self.bzk_kc) self.fullkd = KPointDescriptor(self.kd.bzk_kc, nspins=1) class S: id_a = [] def set_symmetry(self, s): pass self.fullkd.set_symmetry(Atoms(pbc=True), S(), False) self.fullkd.set_communicator(world) self.pt = LFC(self.gd, [setup.pt_j for setup in density.setups], dtype=complex) self.pt.set_k_points(self.fullkd.ibzk_kc) self.interpolator = density.interpolator def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) self.pt.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx def calculate_exx(self): """Non-selfconsistent calculation.""" kd = self.kd K = self.fullkd.nibzkpts assert self.nspins == 1 Q = K // world.size assert Q * world.size == K parallel = (world.size > self.nspins) self.exx = 0.0 self.exx_skn = np.zeros((self.nspins, K, self.bd.nbands)) kpt_u = [] for k in range(world.rank * Q, (world.rank + 1) * Q): k_c = self.fullkd.ibzk_kc[k] for k1, k1_c in enumerate(kd.bzk_kc): if abs(k1_c - k_c).max() < 1e-10: break # Index of symmetry related point in the irreducible BZ ik = kd.kibz_k[k1] kpt = self.kpt_u[ik] # KPoint from ground-state calculation phase_cd = np.exp(2j * pi * self.gd.sdisp_cd * k_c[:, np.newaxis]) kpt2 = KPoint0(kpt.weight, kpt.s, k, None, phase_cd) kpt2.psit_nG = np.empty_like(kpt.psit_nG) kpt2.f_n = kpt.f_n / kpt.weight / K * 2 for n, psit_G in enumerate(kpt2.psit_nG): psit_G[:] = kd.transform_wave_function(kpt.psit_nG[n], k1) kpt2.P_ani = self.pt.dict(len(kpt.psit_nG)) self.pt.integrate(kpt2.psit_nG, kpt2.P_ani, k) kpt_u.append(kpt2) for s in range(self.nspins): kpt1_q = [KPoint(self.fullkd, kpt) for kpt in kpt_u if kpt.s == s] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send rank: srank = self.fullkd.get_rank_and_index(s, (kpt1_q[0].k - 1) % K)[0] # Receive rank: rrank = self.fullkd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % K)[0] # Shift k-points K // 2 times: for i in range(K // 2 + 1): if i < K // 2: if parallel: kpt = kpt2_q[-1].next() kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] for kpt1, kpt2 in zip(kpt1_q, kpt2_q): if 2 * i == K: self.apply(kpt1, kpt2, invert=(kpt1.k > kpt2.k)) else: self.apply(kpt1, kpt2) self.apply(kpt1, kpt2, invert=True) if i < K // 2: if parallel: kpt.wait() kpt2_q[0].wait() kpt2_q.pop(0) kpt2_q.append(kpt) self.exx = world.sum(self.exx) world.sum(self.exx_skn) self.exx += self.calculate_paw_correction() def apply(self, kpt1, kpt2, invert=False): #print world.rank,kpt1.k,kpt2.k,invert k1_c = self.fullkd.ibzk_kc[kpt1.k] k2_c = self.fullkd.ibzk_kc[kpt2.k] if invert: k2_c = -k2_c k12_c = k1_c - k2_c N_c = self.gd.N_c eikr_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, k12_c / N_c).T) for q, k_c in enumerate(self.bzk_kc): if abs(k_c + k12_c).max() < 1e-9: q0 = q break for q, k_c in enumerate(self.bzk_kc): if abs(k_c - k12_c).max() < 1e-9: q00 = q break Gpk2_G = self.pwd.G2_qG[q0] if Gpk2_G[0] == 0: Gpk2_G = Gpk2_G.copy() Gpk2_G[0] = 1.0 / self.gamma N = N_c.prod() vol = self.gd.dv * N nspins = self.nspins same = (kpt1.k == kpt2.k) for n1, psit1_R in enumerate(kpt1.psit_nG): f1 = kpt1.f_n[n1] for n2, psit2_R in enumerate(kpt2.psit_nG): if same and n2 > n1: continue f2 = kpt2.f_n[n2] nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, q0, invert) nt_G = self.pwd.fft(nt_R * eikr_R) / N vt_G = nt_G.copy() vt_G *= -pi * vol / Gpk2_G e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid if same and n1 == n2: e /= 2 self.exx += e * f1 * f2 self.ekin -= 2 * e * f1 * f2 self.exx_skn[kpt1.s, kpt1.k, n1] += f2 * e self.exx_skn[kpt2.s, kpt2.k, n2] += f1 * e calculate_potential = not True if calculate_potential: vt_R = self.pwd.ifft(vt_G).conj() * eikr_R * N / vol if kpt1 is kpt2 and not invert and n1 == n2: kpt1.vt_nG[n1] = 0.5 * f1 * vt_R if invert: kpt1.Htpsit_nG[n1] += \ f2 * nspins * psit2_R.conj() * vt_R else: kpt1.Htpsit_nG[n1] += f2 * nspins * psit2_R * vt_R if kpt1 is not kpt2: if invert: kpt2.Htpsit_nG[n2] += (f1 * nspins * psit1_R.conj() * vt_R) else: kpt2.Htpsit_nG[n2] += (f1 * nspins * psit1_R * vt_R.conj()) def calculate_paw_correction(self): exx = 0 deg = 2 // self.nspins # spin degeneracy for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) exx -= self.hybrid / deg * D_ii[i1, i2] * A if setup.X_p is not None: exx -= self.hybrid * np.dot(D_p, setup.X_p) exx += self.hybrid * setup.ExxC return exx def calculate_pair_density(self, n1, n2, kpt1, kpt2, q, invert): if invert: nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2].conj() else: nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2] Q_aL = {} for a, P1_ni in kpt1.P_ani.items(): P1_i = P1_ni[n1] P2_i = kpt2.P_ani[a][n2] if invert: D_ii = np.outer(P1_i.conj(), P2_i.conj()) else: D_ii = np.outer(P1_i.conj(), P2_i) D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) self.ghat.add(nt_G, Q_aL, q) return nt_G
class QNA(GGA): def __init__(self, atoms, parameters, qna_setup_name='PBE', alpha=2.0, override_atoms=None, stencil=2): # override_atoms is only used to test the partial derivatives # of xc-functional kernel = QNAKernel(self) GGA.__init__(self, kernel, stencil=stencil) self.atoms = atoms self.parameters = parameters self.qna_setup_name = qna_setup_name self.alpha = alpha self.override_atoms = override_atoms self.orbital_dependent = False def todict(self): dct = dict(type='qna-gga', name='QNA', setup_name=self.qna_setup_name, parameters=self.parameters, alpha=self.alpha, orbital_dependent=False) return dct def set_grid_descriptor(self, gd): GGA.set_grid_descriptor(self, gd) self.dedmu_g = gd.zeros() self.dedbeta_g = gd.zeros() # Create gaussian LFC l_lim = 1.0e-30 rcut = 12 points = 200 r_i = np.linspace(0, rcut, points + 1) rcgauss = 1.2 g_g = (2 / rcgauss**3 / np.pi * np.exp(-((r_i / rcgauss)**2)**self.alpha)) # Values too close to zero can cause numerical problems especially with # forces (some parts of the mu and beta field can become negative) g_g[np.where(g_g < l_lim)] = l_lim spline = Spline(l=0, rmax=rcut, f_g=g_g) spline_j = [[spline]] * len(self.atoms) self.Pa = LFC(gd, spline_j) def set_positions(self, spos_ac, atom_partition=None): self.Pa.set_positions(spos_ac) def calculate_spatial_parameters(self, atoms): mu_g = self.gd.zeros() beta_g = self.gd.zeros() denominator = self.gd.zeros() mu_a = {} beta_a = {} eye_a = {} for atom in atoms: mu, beta = self.parameters[atom.symbol] mu_a[atom.index] = np.array([mu]) beta_a[atom.index] = np.array([beta]) eye_a[atom.index] = np.array(1.0) self.Pa.add(mu_g, mu_a) self.Pa.add(beta_g, beta_a) self.Pa.add(denominator, eye_a) mu_g /= denominator beta_g /= denominator return mu_g, beta_g def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): self.current_atom = a return GGA.calculate_paw_correction(self, setup, D_sp, dEdD_sp, addcoredensity, a) def get_setup_name(self): return self.qna_setup_name def get_description(self): return 'QNA Parameters: ' + str(self.parameters) def add_forces(self, F_av): mu_g = self.gd.zeros() beta_g = self.gd.zeros() denominator = self.gd.zeros() mu_a = {} beta_a = {} eye_a = {} for atom in self.atoms: mu, beta = self.parameters[atom.symbol] mu_a[atom.index] = np.array([mu]) beta_a[atom.index] = np.array([beta]) eye_a[atom.index] = np.array(1.0) self.Pa.add(mu_g, mu_a) self.Pa.add(beta_g, beta_a) self.Pa.add(denominator, eye_a) mu_g /= denominator beta_g /= denominator # mu part1 = -self.dedmu_g / denominator part2 = -part1 * mu_g c_axiv = self.Pa.dict(derivative=True) self.Pa.derivative(part1, c_axiv) for atom in self.atoms: F_av[atom.index] -= c_axiv[atom.index][0][:] * mu_a[atom.index][0] c_axiv = self.Pa.dict(derivative=True) self.Pa.derivative(part2, c_axiv) for atom in self.atoms: F_av[atom.index] -= c_axiv[atom.index][0][:] # beta part1 = -self.dedbeta_g / denominator part2 = -part1 * beta_g c_axiv = self.Pa.dict(derivative=True) self.Pa.derivative(part1, c_axiv) for atom in self.atoms: F_av[atom.index] -= c_axiv[atom.index][0] * beta_a[atom.index][0] c_axiv = self.Pa.dict(derivative=True) self.Pa.derivative(part2, c_axiv) for atom in self.atoms: F_av[atom.index] -= c_axiv[atom.index][0][:]
class HybridXC(XCFunctional): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, finegrid=False, alpha=None): """Mix standard functionals with exact exchange. name: str Name of hybrid functional. hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. finegrid: boolean Use fine grid for energy functional evaluations? """ if name == 'EXX': assert hybrid is None and xc is None hybrid = 1.0 xc = XC(XCNull()) elif name == 'PBE0': assert hybrid is None and xc is None hybrid = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif name == 'B3LYP': assert hybrid is None and xc is None hybrid = 0.2 xc = XC('HYB_GGA_XC_B3LYP') if isinstance(xc, str): xc = XC(xc) self.hybrid = hybrid self.xc = xc self.type = xc.type self.alpha = alpha self.exx = 0.0 XCFunctional.__init__(self, name) def get_setup_name(self): return 'PBE' def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd N_c = self.gd.N_c N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: self.alpha = 6 * vol**(2 / 3.0) / pi**2 self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) * self.kd.nbzkpts) ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max() if self.kd.N_c is None: self.bzk_kc = np.zeros((1, 3)) dfghdfgh else: n = self.kd.N_c * 2 - 1 bzk_kc = np.indices(n).transpose((1, 2, 3, 0)) bzk_kc.shape = (-1, 3) bzk_kc -= self.kd.N_c - 1 self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c self.pwd = PWDescriptor(ecut, self.gd, self.bzk_kc) n = 0 for k_c, Gpk2_G in zip(self.bzk_kc[:], self.pwd.G2_qG): if (k_c > -0.5).all() and (k_c <= 0.5).all(): #XXX??? if k_c.any(): self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G), Gpk2_G**-1) else: self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]), Gpk2_G[1:]**-1) n += 1 assert n == self.kd.N_c.prod() self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups], dtype=complex ) self.ghat.set_k_points(self.bzk_kc) self.fullkd = KPointDescriptor(self.kd.bzk_kc, nspins=1) class S: id_a = [] def set_symmetry(self, s): pass self.fullkd.set_symmetry(Atoms(pbc=True), S(), False) self.fullkd.set_communicator(world) self.pt = LFC(self.gd, [setup.pt_j for setup in density.setups], dtype=complex) self.pt.set_k_points(self.fullkd.ibzk_kc) self.interpolator = density.interpolator def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) self.pt.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx def calculate_exx(self): """Non-selfconsistent calculation.""" kd = self.kd K = self.fullkd.nibzkpts assert self.nspins == 1 Q = K // world.size assert Q * world.size == K parallel = (world.size > self.nspins) self.exx = 0.0 self.exx_skn = np.zeros((self.nspins, K, self.bd.nbands)) kpt_u = [] for k in range(world.rank * Q, (world.rank + 1) * Q): k_c = self.fullkd.ibzk_kc[k] for k1, k1_c in enumerate(kd.bzk_kc): if abs(k1_c - k_c).max() < 1e-10: break # Index of symmetry related point in the irreducible BZ ik = kd.kibz_k[k1] kpt = self.kpt_u[ik] # KPoint from ground-state calculation phase_cd = np.exp(2j * pi * self.gd.sdisp_cd * k_c[:, np.newaxis]) kpt2 = KPoint0(kpt.weight, kpt.s, k, None, phase_cd) kpt2.psit_nG = np.empty_like(kpt.psit_nG) kpt2.f_n = kpt.f_n / kpt.weight / K * 2 for n, psit_G in enumerate(kpt2.psit_nG): psit_G[:] = kd.transform_wave_function(kpt.psit_nG[n], k1) kpt2.P_ani = self.pt.dict(len(kpt.psit_nG)) self.pt.integrate(kpt2.psit_nG, kpt2.P_ani, k) kpt_u.append(kpt2) for s in range(self.nspins): kpt1_q = [KPoint(self.fullkd, kpt) for kpt in kpt_u if kpt.s == s] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send rank: srank = self.fullkd.get_rank_and_index(s, (kpt1_q[0].k - 1) % K)[0] # Receive rank: rrank = self.fullkd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % K)[0] # Shift k-points K // 2 times: for i in range(K // 2 + 1): if i < K // 2: if parallel: kpt = kpt2_q[-1].next() kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] for kpt1, kpt2 in zip(kpt1_q, kpt2_q): if 2 * i == K: self.apply(kpt1, kpt2, invert=(kpt1.k > kpt2.k)) else: self.apply(kpt1, kpt2) self.apply(kpt1, kpt2, invert=True) if i < K // 2: if parallel: kpt.wait() kpt2_q[0].wait() kpt2_q.pop(0) kpt2_q.append(kpt) self.exx = world.sum(self.exx) world.sum(self.exx_skn) self.exx += self.calculate_paw_correction() def apply(self, kpt1, kpt2, invert=False): #print world.rank,kpt1.k,kpt2.k,invert k1_c = self.fullkd.ibzk_kc[kpt1.k] k2_c = self.fullkd.ibzk_kc[kpt2.k] if invert: k2_c = -k2_c k12_c = k1_c - k2_c N_c = self.gd.N_c eikr_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, k12_c / N_c).T) for q, k_c in enumerate(self.bzk_kc): if abs(k_c + k12_c).max() < 1e-9: q0 = q break for q, k_c in enumerate(self.bzk_kc): if abs(k_c - k12_c).max() < 1e-9: q00 = q break Gpk2_G = self.pwd.G2_qG[q0] if Gpk2_G[0] == 0: Gpk2_G = Gpk2_G.copy() Gpk2_G[0] = 1.0 / self.gamma N = N_c.prod() vol = self.gd.dv * N nspins = self.nspins same = (kpt1.k == kpt2.k) for n1, psit1_R in enumerate(kpt1.psit_nG): f1 = kpt1.f_n[n1] for n2, psit2_R in enumerate(kpt2.psit_nG): if same and n2 > n1: continue f2 = kpt2.f_n[n2] nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, q0, invert) nt_G = self.pwd.fft(nt_R * eikr_R) / N vt_G = nt_G.copy() vt_G *= -pi * vol / Gpk2_G e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid if same and n1 == n2: e /= 2 self.exx += e * f1 * f2 self.ekin -= 2 * e * f1 * f2 self.exx_skn[kpt1.s, kpt1.k, n1] += f2 * e self.exx_skn[kpt2.s, kpt2.k, n2] += f1 * e calculate_potential = not True if calculate_potential: vt_R = self.pwd.ifft(vt_G).conj() * eikr_R * N / vol if kpt1 is kpt2 and not invert and n1 == n2: kpt1.vt_nG[n1] = 0.5 * f1 * vt_R if invert: kpt1.Htpsit_nG[n1] += \ f2 * nspins * psit2_R.conj() * vt_R else: kpt1.Htpsit_nG[n1] += f2 * nspins * psit2_R * vt_R if kpt1 is not kpt2: if invert: kpt2.Htpsit_nG[n2] += (f1 * nspins * psit1_R.conj() * vt_R) else: kpt2.Htpsit_nG[n2] += (f1 * nspins * psit1_R * vt_R.conj()) def calculate_paw_correction(self): exx = 0 deg = 2 // self.nspins # spin degeneracy for a, D_sp in self.density.D_asp.items(): setup = self.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) exx -= self.hybrid / deg * D_ii[i1, i2] * A if setup.X_p is not None: exx -= self.hybrid * np.dot(D_p, setup.X_p) exx += self.hybrid * setup.ExxC return exx def calculate_pair_density(self, n1, n2, kpt1, kpt2, q, invert): if invert: nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2].conj() else: nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2] Q_aL = {} for a, P1_ni in kpt1.P_ani.items(): P1_i = P1_ni[n1] P2_i = kpt2.P_ani[a][n2] if invert: D_ii = np.outer(P1_i.conj(), P2_i.conj()) else: D_ii = np.outer(P1_i.conj(), P2_i) D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) self.ghat.add(nt_G, Q_aL, q) return nt_G
class RealSpaceHamiltonian(Hamiltonian): def __init__(self, gd, finegd, nspins, setups, timer, xc, world, kptband_comm, vext=None, collinear=True, psolver=None, stencil=3): Hamiltonian.__init__(self, gd, finegd, nspins, setups, timer, xc, world, kptband_comm, vext, collinear) # Solver for the Poisson equation: if psolver is None: psolver = PoissonSolver(nn=3, relax='J') self.poisson = psolver self.poisson.set_grid_descriptor(finegd) # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.gd, stencil) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.vbar_g = None def summary(self, fd): Hamiltonian.summary(self, fd) degree = self.restrictor.nn * 2 - 1 name = ['linear', 'cubic', 'quintic', 'heptic'][degree // 2] fd.write('Interpolation: tri-%s ' % name + '(%d. degree polynomial)\n' % degree) fd.write('Poisson solver: %s\n' % self.poisson.get_description()) def set_positions(self, spos_ac, rank_a): Hamiltonian.set_positions(self, spos_ac, rank_a) if self.vbar_g is None: self.vbar_g = self.finegd.empty() self.vbar_g[:] = 0.0 self.vbar.add(self.vbar_g) def update_pseudo_potential(self, density): self.timer.start('vbar') Ebar = self.finegd.integrate(self.vbar_g, density.nt_g, global_integral=False) vt_g = self.vt_sg[0] vt_g[:] = self.vbar_g self.timer.stop('vbar') Eext = 0.0 if self.vext is not None: assert self.collinear vt_g += self.vext.get_potential(self.finegd) Eext = self.finegd.integrate( vt_g, density.nt_g, global_integral=False) - Ebar self.vt_sg[1:self.nspins] = vt_g self.vt_sg[self.nspins:] = 0.0 self.timer.start('XC 3D grid') Exc = self.xc.calculate(self.finegd, density.nt_sg, self.vt_sg) Exc /= self.gd.comm.size self.timer.stop('XC 3D grid') self.timer.start('Poisson') # npoisson is the number of iterations: self.npoisson = self.poisson.solve(self.vHt_g, density.rhot_g, charge=-density.charge) self.timer.stop('Poisson') self.timer.start('Hartree integrate/restrict') Epot = 0.5 * self.finegd.integrate( self.vHt_g, density.rhot_g, global_integral=False) Ekin = 0.0 s = 0 for s, (vt_g, vt_G, nt_G) in enumerate(zip(self.vt_sg, self.vt_sG, density.nt_sG)): if s < self.nspins: vt_g += self.vHt_g self.restrict(vt_g, vt_G) if self.ref_vt_sG is not None: vt_G += self.ref_vt_sG[s] if s < self.nspins: Ekin -= self.gd.integrate(vt_G, nt_G - density.nct_G, global_integral=False) else: Ekin -= self.gd.integrate(vt_G, nt_G, global_integral=False) s += 1 self.timer.stop('Hartree integrate/restrict') # Calculate atomic hamiltonians: W_aL = {} for a in density.D_asp: W_aL[a] = np.empty((self.setups[a].lmax + 1)**2) density.ghat.integrate(self.vHt_g, W_aL) return Ekin, Epot, Ebar, Eext, Exc, W_aL def calculate_forces2(self, dens, ghat_aLv, nct_av, vbar_av): if self.nspins == 2: vt_G = self.vt_sG.mean(0) else: vt_G = self.vt_sG[0] dens.ghat.derivative(self.vHt_g, ghat_aLv) dens.nct.derivative(vt_G, nct_av) self.vbar.derivative(dens.nt_g, vbar_av)
class PS2AE: """Transform PS to AE wave functions. Interpolates PS wave functions to a fine grid and adds PAW corrections in order to obtain true AE wave functions. """ def __init__(self, calc, h=0.05, n=2): """Create transformation object. calc: GPAW calculator object The calcalator that has the wave functions. h: float Desired grid-spacing in Angstrom. n: int Force number of points to be a mulitiple of n. """ self.calc = calc gd = calc.wfs.gd gd1 = GridDescriptor(gd.N_c, gd.cell_cv, comm=serial_comm) # Descriptor for the final grid: N_c = h2gpts(h / Bohr, gd.cell_cv) N_c = np.array([get_efficient_fft_size(N, n) for N in N_c]) gd2 = self.gd = GridDescriptor(N_c, gd.cell_cv, comm=serial_comm) self.interpolator = Interpolator(gd1, gd2, self.calc.wfs.dtype) self.dphi = None # PAW correction (will be initialized when needed) def _initialize_corrections(self): if self.dphi is not None: return splines = {} dphi_aj = [] for setup in self.calc.wfs.setups: dphi_j = splines.get(setup) if dphi_j is None: rcut = max(setup.rcut_j) * 1.1 gcut = setup.rgd.ceil(rcut) dphi_j = [] for l, phi_g, phit_g in zip(setup.l_j, setup.data.phi_jg, setup.data.phit_jg): dphi_g = (phi_g - phit_g)[:gcut] dphi_j.append(setup.rgd.spline(dphi_g, rcut, l, points=200)) dphi_aj.append(dphi_j) self.dphi = LFC(self.gd, dphi_aj, kd=self.calc.wfs.kd.copy(), dtype=self.calc.wfs.dtype) self.dphi.set_positions(self.calc.spos_ac) def get_wave_function(self, n, k=0, s=0, ae=True): """Interpolate wave function. n: int Band index. k: int K-point index. s: int Spin index. ae: bool Add PAW correction to get an all-electron wave function. """ psi_r = self.calc.get_pseudo_wave_function(n, k, s, pad=True, periodic=True) psi_R = self.interpolator.interpolate(psi_r * Bohr**1.5) if ae: self._initialize_corrections() wfs = self.calc.wfs P_nI = wfs.collect_projections(k, s) if wfs.world.rank == 0: P_ai = {} I1 = 0 for a, setup in enumerate(wfs.setups): I2 = I1 + setup.ni P_ai[a] = P_nI[n, I1:I2] I1 = I2 self.dphi.add(psi_R, P_ai, k) wfs.world.broadcast(psi_R, 0) return psi_R * Bohr**-1.5 def get_electrostatic_potential(self, ae=True, rcgauss=0.02): """Interpolate electrostatic potential. Return value in eV. ae: bool Add PAW correction to get the all-electron potential. rcgauss: float Width of gaussian (in Angstrom) used to represent the nuclear charge. """ gd = self.calc.hamiltonian.finegd v_r = self.calc.get_electrostatic_potential() / Ha gd1 = GridDescriptor(gd.N_c, gd.cell_cv, comm=serial_comm) interpolator = Interpolator(gd1, self.gd) v_R = interpolator.interpolate(v_r) if ae: alpha = 1 / (rcgauss / Bohr)**2 self.add_potential_correction(v_R, alpha) return v_R * Ha def add_potential_correction(self, v_R, alpha): dens = self.calc.density dens.D_asp.redistribute(dens.atom_partition.as_serial()) dens.Q_aL.redistribute(dens.atom_partition.as_serial()) dv_a1 = [] for a, D_sp in dens.D_asp.items(): setup = dens.setups[a] c = setup.xc_correction rgd = c.rgd ghat_g = gauss(rgd, 1 / setup.rcgauss**2) Z_g = gauss(rgd, alpha) * setup.Z D_q = np.dot(D_sp.sum(0), c.B_pqL[:, :, 0]) dn_g = np.dot(D_q, (c.n_qg - c.nt_qg)) * sqrt(4 * pi) dn_g += 4 * pi * (c.nc_g - c.nct_g) dn_g -= Z_g dn_g -= dens.Q_aL[a][0] * ghat_g * sqrt(4 * pi) dv_g = rgd.poisson(dn_g) / sqrt(4 * pi) dv_g[1:] /= rgd.r_g[1:] dv_g[0] = dv_g[1] dv_g[-1] = 0.0 dv_a1.append([rgd.spline(dv_g, points=POINTS)]) dens.D_asp.redistribute(dens.atom_partition) dens.Q_aL.redistribute(dens.atom_partition) if dv_a1: dv = LFC(self.gd, dv_a1) dv.set_positions(self.calc.spos_ac) dv.add(v_R) dens.gd.comm.broadcast(v_R, 0)
class DensityCollector(Observer): def __init__(self, filename, lcao, ranges_str='full'): Observer.__init__(self) self.lcao = lcao self.filename = filename self.ranges = None print("ranges-str", ranges_str) self.ranges_str = ranges_str def update(self): if self.ranges is None: # First time self.ranges = [] self.nbands = self.lcao.wfs.bd.nbands start = 0 if self.ranges_str != 'full': for rng in self.ranges_str.split(','): print("rng", rng) rng = eval(rng) self.ranges.append(range(start, rng)) start = rng self.ranges.append(range(start, self.nbands)) print(self.ranges) self.ghat = LFC( self.lcao.wfs.gd, [setup.ghat_l for setup in self.lcao.density.setups], integral=sqrt(4 * pi), forces=False) self.ghat.set_positions(self.lcao.wfs.spos_ac) # Clear files for rid, rng in enumerate(self.ranges): f = open(self.filename + '.' + str(rid) + '.density', 'w') print("# Density file", file=f) N_c = self.lcao.wfs.gd.N_c print(N_c[0], N_c[1], N_c[2], file=f) print( "# This header is 10 lines long, then double precision binary data starts.", file=f) for i in range(7): print("#", file=f) f.close() #self.lcao.timer.start('Dump density') for rid, rng in enumerate(self.ranges): assert len(self.lcao.wfs.kpt_u) == 1 f_un = [self.lcao.wfs.kpt_u[0].f_n.copy()] for n in range(self.lcao.wfs.bd.nbands): band_rank, myn = self.lcao.wfs.bd.who_has(n) if self.lcao.wfs.bd.rank == band_rank: if n not in rng: f_un[0][myn] = 0.0 n_sG = self.lcao.wfs.gd.zeros(1) self.lcao.wfs.add_to_density_from_k_point_with_occupation( n_sG, self.lcao.wfs.kpt_u[0], f_un[0]) self.lcao.wfs.kptband_comm.sum(n_sG) D_asp = {} for a in self.lcao.density.D_asp: ni = self.lcao.density.setups[a].ni D_asp[a] = np.zeros((1, ni * (ni + 1) // 2)) self.lcao.wfs.calculate_atomic_density_matrices_with_occupation( D_asp, f_un) Q_aL = {} for a, D_sp in D_asp.items(): Q_aL[a] = np.dot(D_sp.sum(0), self.lcao.density.setups[a].Delta_pL) self.ghat.add(n_sG, Q_aL) n_sg = self.lcao.wfs.gd.collect(n_sG, broadcast=False) if world.rank == 0: f = open(self.filename + '.' + str(rid) + '.density', 'a+') #n_sg.astype(np.float32).tofile(f) #print "max", np.max(n_sg), np.min(n_sg) n_sg.tofile(f) f.close() s = n_sg.shape f = open(self.filename + '.info', 'w') print(s[0], s[1], s[2], file=f) f.close()
class DensityCollector(Observer): def __init__(self, filename, lcao, ranges_str="full"): Observer.__init__(self) self.lcao = lcao self.filename = filename self.ranges = None print("ranges-str", ranges_str) self.ranges_str = ranges_str def update(self): if self.ranges is None: # First time self.ranges = [] self.nbands = self.lcao.wfs.bd.nbands start = 0 if self.ranges_str != "full": for rng in self.ranges_str.split(","): print("rng", rng) rng = eval(rng) self.ranges.append(range(start, rng)) start = rng self.ranges.append(range(start, self.nbands)) print(self.ranges) self.ghat = LFC( self.lcao.wfs.gd, [setup.ghat_l for setup in self.lcao.density.setups], integral=sqrt(4 * pi), forces=False, ) spos_ac = self.lcao.atoms.get_scaled_positions() % 1.0 self.ghat.set_positions(spos_ac) # Clear files for rid, rng in enumerate(self.ranges): f = open(self.filename + "." + str(rid) + ".density", "w") print("# Density file", file=f) N_c = self.lcao.wfs.gd.N_c print(N_c[0], N_c[1], N_c[2], file=f) print("# This header is 10 lines long, then double precision binary data starts.", file=f) for i in range(7): print("#", file=f) f.close() # self.lcao.timer.start('Dump density') for rid, rng in enumerate(self.ranges): assert len(self.lcao.wfs.kpt_u) == 1 f_un = [self.lcao.wfs.kpt_u[0].f_n.copy()] for n in range(self.lcao.wfs.bd.nbands): band_rank, myn = self.lcao.wfs.bd.who_has(n) if self.lcao.wfs.bd.rank == band_rank: if not n in rng: f_un[0][myn] = 0.0 n_sG = self.lcao.wfs.gd.zeros(1) self.lcao.wfs.add_to_density_from_k_point_with_occupation(n_sG, self.lcao.wfs.kpt_u[0], f_un[0]) self.lcao.wfs.kptband_comm.sum(n_sG) D_asp = {} for a in self.lcao.density.D_asp: ni = self.lcao.density.setups[a].ni D_asp[a] = np.zeros((1, ni * (ni + 1) // 2)) self.lcao.wfs.calculate_atomic_density_matrices_with_occupation(D_asp, f_un) Q_aL = {} for a, D_sp in D_asp.items(): Q_aL[a] = np.dot(D_sp.sum(0), self.lcao.density.setups[a].Delta_pL) self.ghat.add(n_sG, Q_aL) n_sg = self.lcao.wfs.gd.collect(n_sG, broadcast=False) if world.rank == 0: f = open(self.filename + "." + str(rid) + ".density", "a+") # n_sg.astype(np.float32).tofile(f) # print "max", np.max(n_sg), np.min(n_sg) n_sg.tofile(f) f.close() s = n_sg.shape f = open(self.filename + ".info", "w") print(s[0], s[1], s[2], file=f) f.close()
class MGGA(GGA): orbital_dependent = True def __init__(self, kernel, nn=1): """Meta GGA functional. nn: int Number of neighbor grid points to use for FD stencil for wave function gradient. """ self.nn = nn GGA.__init__(self, kernel) def set_grid_descriptor(self, gd): GGA.set_grid_descriptor(self, gd) def get_setup_name(self): return "PBE" def initialize(self, density, hamiltonian, wfs, occupations): self.wfs = wfs self.tauct = LFC(wfs.gd, [[setup.tauct] for setup in wfs.setups], forces=True, cut=True) self.tauct_G = None self.dedtaut_sG = None self.restrict = hamiltonian.restrictor.apply self.interpolate = density.interpolator.apply self.taugrad_v = [Gradient(wfs.gd, v, n=self.nn, dtype=wfs.dtype, allocate=True).apply for v in range(3)] def set_positions(self, spos_ac): self.tauct.set_positions(spos_ac) if self.tauct_G is None: self.tauct_G = self.wfs.gd.empty() self.tauct_G[:] = 0.0 self.tauct.add(self.tauct_G) def calculate_gga(self, e_g, nt_sg, v_sg, sigma_xg, dedsigma_xg): taut_sG = self.wfs.calculate_kinetic_energy_density(self.tauct, self.taugrad_v) taut_sg = np.empty_like(nt_sg) for taut_G, taut_g in zip(taut_sG, taut_sg): taut_G += 1.0 / self.wfs.nspins * self.tauct_G self.interpolate(taut_G, taut_g) dedtaut_sg = np.empty_like(nt_sg) self.kernel.calculate(e_g, nt_sg, v_sg, sigma_xg, dedsigma_xg, taut_sg, dedtaut_sg) self.dedtaut_sG = self.wfs.gd.empty(self.wfs.nspins) self.ekin = 0.0 for s in range(self.wfs.nspins): self.restrict(dedtaut_sg[s], self.dedtaut_sG[s]) self.ekin -= self.wfs.gd.integrate(self.dedtaut_sG[s] * (taut_sG[s] - self.tauct_G / self.wfs.nspins)) def apply_orbital_dependent_hamiltonian(self, kpt, psit_xG, Htpsit_xG, dH_asp): a_G = self.wfs.gd.empty(dtype=psit_xG.dtype) for psit_G, Htpsit_G in zip(psit_xG, Htpsit_xG): for v in range(3): self.taugrad_v[v](psit_G, a_G, kpt.phase_cd) self.taugrad_v[v](self.dedtaut_sG[kpt.s] * a_G, a_G, kpt.phase_cd) axpy(-0.5, a_G, Htpsit_G) def add_forces(self, F_av): dF_av = self.tauct.dict(derivative=True) self.tauct.derivative(self.dedtaut_sG.sum(0), dF_av) for a, dF_v in dF_av.items(): F_av[a] += dF_v[0] def estimate_memory(self, mem): bytecount = self.wfs.gd.bytecount() mem.subnode("MGGA arrays", (1 + self.wfs.nspins) * bytecount) def initialize_kinetic(self, xccorr): nii = xccorr.nii nn = len(xccorr.rnablaY_nLv) ng = len(xccorr.phi_jg[0]) tau_npg = np.zeros((nn, nii, ng)) taut_npg = np.zeros((nn, nii, ng)) self.create_kinetic(xccorr, nn, xccorr.phi_jg, tau_npg) self.create_kinetic(xccorr, nn, xccorr.phit_jg, taut_npg) return tau_npg, taut_npg def create_kinetic(self, x, ny, phi_jg, tau_ypg): """Short title here. kinetic expression is:: __ __ tau_s = 1/2 Sum_{i1,i2} D(s,i1,i2) \/phi_i1 . \/phi_i2 +tauc_s here the orbital dependent part is calculated:: __ __ \/phi_i1 . \/phi_i2 = __ __ \/YL1.\/YL2 phi_j1 phi_j2 +YL1 YL2 dphi_j1 dphi_j2 ------ ------ dr dr __ __ \/YL1.\/YL2 [y] = Sum_c A[L1,c,y] A[L2,c,y] / r**2 """ nj = len(phi_jg) ni = len(x.jlL) nii = ni * (ni + 1) // 2 dphidr_jg = np.zeros(np.shape(phi_jg)) for j in range(nj): phi_g = phi_jg[j] x.rgd.derivative(phi_g, dphidr_jg[j]) # Second term: for y in range(ny): i1 = 0 p = 0 Y_L = x.Y_nL[y] for j1, l1, L1 in x.jlL: for j2, l2, L2 in x.jlL[i1:]: c = Y_L[L1] * Y_L[L2] temp = c * dphidr_jg[j1] * dphidr_jg[j2] tau_ypg[y, p, :] += temp p += 1 i1 += 1 ##first term for y in range(ny): i1 = 0 p = 0 rnablaY_Lv = x.rnablaY_nLv[y, : x.Lmax] Ax_L = rnablaY_Lv[:, 0] Ay_L = rnablaY_Lv[:, 1] Az_L = rnablaY_Lv[:, 2] for j1, l1, L1 in x.jlL: for j2, l2, L2 in x.jlL[i1:]: temp = Ax_L[L1] * Ax_L[L2] + Ay_L[L1] * Ay_L[L2] + Az_L[L1] * Az_L[L2] temp *= phi_jg[j1] * phi_jg[j2] temp[1:] /= x.rgd.r_g[1:] ** 2 temp[0] = temp[1] tau_ypg[y, p, :] += temp p += 1 i1 += 1 tau_ypg *= 0.5 return
class RealSpaceDensity(Density): def __init__(self, gd, finegd, nspins, charge, collinear=True, stencil=3): Density.__init__(self, gd, finegd, nspins, charge, collinear) self.stencil = stencil def initialize(self, setups, timer, magmom_av, hund): Density.initialize(self, setups, timer, magmom_av, hund) # Interpolation function for the density: self.interpolator = Transformer(self.gd, self.finegd, self.stencil) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) def set_positions(self, spos_ac, rank_a=None): Density.set_positions(self, spos_ac, rank_a) self.nct_G = self.gd.zeros() self.nct.add(self.nct_G, 1.0 / self.nspins) def interpolate_pseudo_density(self, comp_charge=None): """Interpolate pseudo density to fine grid.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() self.nt_sg = self.interpolate(self.nt_sG, self.nt_sg) # With periodic boundary conditions, the interpolation will # conserve the number of electrons. if not self.gd.pbc_c.all(): # With zero-boundary conditions in one or more directions, # this is not the case. pseudo_charge = -(self.charge + comp_charge) if abs(pseudo_charge) > 1.0e-14: x = (pseudo_charge / self.finegd.integrate(self.nt_sg[:self.nspins]).sum()) self.nt_sg *= x def interpolate(self, in_xR, out_xR=None): """Interpolate array(s).""" # ndim will be 3 in finite-difference mode and 1 when working # with the AtomPAW class (spherical atoms and 1d grids) ndim = self.gd.ndim if out_xR is None: out_xR = self.finegd.empty(in_xR.shape[:-ndim]) a_xR = in_xR.reshape((-1,) + in_xR.shape[-ndim:]) b_xR = out_xR.reshape((-1,) + out_xR.shape[-ndim:]) for in_R, out_R in zip(a_xR, b_xR): self.interpolator.apply(in_R, out_R) return out_xR def calculate_pseudo_charge(self): self.nt_g = self.nt_sg[:self.nspins].sum(axis=0) self.rhot_g = self.nt_g.copy() self.ghat.add(self.rhot_g, self.Q_aL) if debug: charge = self.finegd.integrate(self.rhot_g) + self.charge if abs(charge) > self.charge_eps: raise RuntimeError('Charge not conserved: excess=%.9f' % charge) def get_pseudo_core_kinetic_energy_density_lfc(self): return LFC(self.gd, [[setup.tauct] for setup in self.setups], forces=True, cut=True) def calculate_dipole_moment(self): return self.finegd.calculate_dipole_moment(self.rhot_g)
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False, omega=None, excitation=None, excited=0, stencil=2): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? omega: float RSF mixing parameter excitation: string: Apply operator for improved virtual orbitals to unocc states? Possible modes: singlet: excitations to singlets triplet: excitations to triplets average: average between singlets and tripletts see f.e. http://dx.doi.org/10.1021/acs.jctc.8b00238 excited: number Band to excite from - counted from H**O downwards """ self.finegrid = finegrid self.unocc = unocc self.excitation = excitation self.excited = excited HybridXCBase.__init__(self, name, hybrid=hybrid, xc=xc, omega=omega, stencil=stencil) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) self.gd = density.gd self.redistributor = density.redistributor use_charge_center = hamiltonian.poisson.use_charge_center # XXX How do we construct a copy of the Poisson solver of the # Hamiltonian? We don't know what class it is, etc., but gd # may differ. # XXX One might consider using a charged centered compensation # charge for the PoissonSolver in the case of EXX as standard self.poissonsolver = PoissonSolver('fd', eps=1e-11, use_charge_center=use_charge_center) # self.poissonsolver = hamiltonian.poisson if self.finegrid: self.finegd = self.gd.refine() # XXX Taking restrictor from Hamiltonian will not work in PW mode, # will it? I think this supports only real-space mode. # self.restrictor = hamiltonian.restrictor self.restrictor = Transformer(self.finegd, self.gd, 3) self.interpolator = Transformer(self.gd, self.finegd, 3) else: self.finegd = self.gd self.ghat = LFC(self.finegd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.poissonsolver.set_grid_descriptor(self.finegd) if self.rsf == 'Yukawa': omega2 = self.omega**2 self.screened_poissonsolver = HelmholtzSolver( k2=-omega2, eps=1e-11, nn=3, use_charge_center=use_charge_center) self.screened_poissonsolver.set_grid_descriptor(self.finegd) def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Note that the quantities passed are on the # density/Hamiltonian grids! # They may be distributed differently from own quantities. self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups is_cam = self.is_cam vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() if self.rsf == 'Yukawa': y_vt_g = self.finegd.empty() # if self.gd is not self.finegd: # y_vt_G = self.gd.empty() nocc = int(ceil(kpt.f_n.sum())) // (3 - self.nspins) if self.excitation is not None: ex_band = nocc - self.excited - 1 if self.excitation == 'singlet': ex_weight = -1 elif self.excitation == 'triplet': ex_weight = +1 else: ex_weight = 0 if self.unocc or self.excitation is not None: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # XXXX nbands can be different numbers on different cpus! # That means some will execute the loop and others not. # And deadlocks with augment-grids. # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg if n1 != n2 and f1 == 0 and f1 == f2: continue # Don't work on double unocc. bands # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density( n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 # XXXXX This will go wrong because we are solving the # Poisson equation on the distribution of gd, not finegd # Or maybe it's fixed now self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.rsf == 'Yukawa': y_vt_g[:] = 0.0 self.screened_poissonsolver.solve(y_vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) if is_cam: # Cam like correction y_vt_g *= self.cam_beta else: y_vt_g *= hybrid vt_g -= y_vt_g if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G if self.excitation is not None and n1 == ex_band: Htpsit_nG[nocc:] += f1 * vt_G * psit_nG[nocc:] else: if self.excitation is None or n1 != ex_band \ or n2 < nocc: Htpsit_nG[n2] += f1 * vt_G * psit1_G else: Htpsit_nG[n2] += f1 * ex_weight * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: if self.excitation is None or n1 != ex_band or \ n2 < nocc: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: v_ni[n2] += f1 * ex_weight * \ np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii if self.excitation is not None and n1 == ex_band: for nuoc in range(nocc, nbands): v_ni[nuoc] += f1 * \ np.dot(v_ii, P_ni[nuoc]) def calculate_vv(ni, D_ii, M_pp, weight, addme=False): """Calculate the local corrections depending on Mpp.""" dexx = 0 dekin = 0 if not addme: addsign = -2.0 else: addsign = 2.0 for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] += addsign * weight / \ deg * A / ((i1 != i2) + 1) dekin += 2 * weight / deg * D_ii[i1, i2] * A dexx -= weight / deg * D_ii[i1, i2] * A return (dexx, dekin) # Apply the atomic corrections to the energy and the Hamiltonian # matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii (dexx, dekin) = calculate_vv(ni, D_ii, setup.M_pp, hybrid) ekin += dekin exx += dexx if self.rsf is not None: Mg_pp = setup.calculate_yukawa_interaction(self.omega) if is_cam: (dexx, dekin) = calculate_vv(ni, D_ii, Mg_pp, self.cam_beta, addme=True) else: (dexx, dekin) = calculate_vv(ni, D_ii, Mg_pp, hybrid, addme=True) ekin -= dekin exx -= dexx # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) if self.rsf == 'Yukawa' and setup.X_pg is not None: if is_cam: thybrid = self.cam_beta # 0th order else: thybrid = hybrid exx += thybrid * np.dot(D_p, setup.X_pg) if Htpsit_nG is not None: dH_p += thybrid * setup.X_pg ekin -= thybrid * np.dot(D_p, setup.X_pg) elif self.rsf == 'Yukawa' and setup.X_pg is None: thybrid = exp(-3.62e-2 * self.omega) # educated guess if is_cam: thybrid *= self.cam_beta else: thybrid *= hybrid exx += thybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p += thybrid * setup.X_p ekin -= thybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: if self.rsf is None or is_cam: if is_cam: exx += self.cam_alpha * setup.ExxC else: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return # if self.gd.comm.rank > 0: # H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a])) # self.gd.comm.sum(H_nn) if not self.unocc or self.excitation is not None: H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return if self.unocc or self.excitation is not None: nocc = len(kpt.vt_nG) else: nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return U_nn = U_nn.T.copy() nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? """ self.finegrid = finegrid self.unocc = unocc HybridXCBase.__init__(self, name, hybrid, xc) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) if self.finegrid: self.poissonsolver = hamiltonian.poisson self.ghat = density.ghat self.interpolator = density.interpolator self.restrictor = hamiltonian.restrictor else: self.poissonsolver = PoissonSolver(eps=1e-11) self.poissonsolver.set_grid_descriptor(density.gd) self.poissonsolver.initialize() self.ghat = LFC(density.gd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.gd = density.gd self.finegd = self.ghat.gd def set_positions(self, spos_ac): if not self.finegrid: self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() nocc = int(kpt.f_n.sum()) // (3 - self.nspins) if self.unocc: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density( n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 iter = self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G else: Htpsit_nG[n2] += f1 * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii # Apply the atomic corrections to the energy and the Hamiltonian matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] -= 2 * hybrid / deg * A / ((i1 != i2) + 1) ekin += 2 * hybrid / deg * D_ii[i1, i2] * A exx -= hybrid / deg * D_ii[i1, i2] * A # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return if self.gd.comm.rank > 0: H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a])) self.gd.comm.sum(H_nn) H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
class RealSpaceDensity(Density): def __init__(self, gd, finegd, nspins, charge, redistributor, stencil=3, background_charge=None): Density.__init__(self, gd, finegd, nspins, charge, redistributor, background_charge=background_charge) self.stencil = stencil def initialize(self, setups, timer, magmom_a, hund): Density.initialize(self, setups, timer, magmom_a, hund) # Interpolation function for the density: self.interpolator = Transformer(self.redistributor.aux_gd, self.finegd, self.stencil) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) def set_positions(self, spos_ac, rank_a=None): Density.set_positions(self, spos_ac, rank_a) self.nct_G = self.gd.zeros() self.nct.add(self.nct_G, 1.0 / self.nspins) def interpolate_pseudo_density(self, comp_charge=None): """Interpolate pseudo density to fine grid.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() self.nt_sg = self.distribute_and_interpolate(self.nt_sG, self.nt_sg) # With periodic boundary conditions, the interpolation will # conserve the number of electrons. if not self.gd.pbc_c.all(): # With zero-boundary conditions in one or more directions, # this is not the case. pseudo_charge = (self.background_charge.charge - self.charge - comp_charge) if abs(pseudo_charge) > 1.0e-14: x = (pseudo_charge / self.finegd.integrate(self.nt_sg).sum()) self.nt_sg *= x def interpolate(self, in_xR, out_xR=None): """Interpolate array(s).""" # ndim will be 3 in finite-difference mode and 1 when working # with the AtomPAW class (spherical atoms and 1d grids) ndim = self.gd.ndim if out_xR is None: out_xR = self.finegd.empty(in_xR.shape[:-ndim]) a_xR = in_xR.reshape((-1, ) + in_xR.shape[-ndim:]) b_xR = out_xR.reshape((-1, ) + out_xR.shape[-ndim:]) for in_R, out_R in zip(a_xR, b_xR): self.interpolator.apply(in_R, out_R) return out_xR def distribute_and_interpolate(self, in_xR, out_xR=None): in_xR = self.redistributor.distribute(in_xR) return self.interpolate(in_xR, out_xR) def calculate_pseudo_charge(self): self.nt_g = self.nt_sg.sum(axis=0) self.rhot_g = self.nt_g.copy() self.ghat.add(self.rhot_g, self.Q_aL) self.background_charge.add_charge_to(self.rhot_g) if debug: charge = self.finegd.integrate(self.rhot_g) + self.charge if abs(charge) > self.charge_eps: raise RuntimeError('Charge not conserved: excess=%.9f' % charge) def get_pseudo_core_kinetic_energy_density_lfc(self): return LFC(self.gd, [[setup.tauct] for setup in self.setups], forces=True, cut=True) def calculate_dipole_moment(self): return self.finegd.calculate_dipole_moment(self.rhot_g)
class RealSpaceHamiltonian(Hamiltonian): def __init__(self, gd, finegd, nspins, setups, timer, xc, world, vext=None, psolver=None, stencil=3, redistributor=None): Hamiltonian.__init__(self, gd, finegd, nspins, setups, timer, xc, world, vext=vext, redistributor=redistributor) # Solver for the Poisson equation: if psolver is None: psolver = {} if isinstance(psolver, dict): psolver = create_poisson_solver(**psolver) self.poisson = psolver self.poisson.set_grid_descriptor(self.finegd) # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.redistributor.aux_gd, stencil) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.vbar_g = None def restrict_and_collect(self, a_xg, b_xg=None, phases=None): if self.redistributor.enabled: tmp_xg = self.restrictor.apply(a_xg, output=None, phases=phases) b_xg = self.redistributor.collect(tmp_xg, b_xg) else: b_xg = self.restrictor.apply(a_xg, output=b_xg, phases=phases) return b_xg def __str__(self): s = Hamiltonian.__str__(self) degree = self.restrictor.nn * 2 - 1 name = ['linear', 'cubic', 'quintic', 'heptic'][degree // 2] s += (' Interpolation: tri-%s ' % name + '(%d. degree polynomial)\n' % degree) s += ' Poisson solver: %s' % self.poisson.get_description() return s def set_positions(self, spos_ac, rank_a): Hamiltonian.set_positions(self, spos_ac, rank_a) if self.vbar_g is None: self.vbar_g = self.finegd.empty() self.vbar_g[:] = 0.0 self.vbar.add(self.vbar_g) def update_pseudo_potential(self, dens): self.timer.start('vbar') e_zero = self.finegd.integrate(self.vbar_g, dens.nt_g, global_integral=False) vt_g = self.vt_sg[0] vt_g[:] = self.vbar_g self.timer.stop('vbar') e_external = 0.0 if self.vext is not None: vext_g = self.vext.get_potential(self.finegd) vt_g += vext_g e_external = self.finegd.integrate(vext_g, dens.rhot_g, global_integral=False) if self.nspins == 2: self.vt_sg[1] = vt_g self.timer.start('XC 3D grid') e_xc = self.xc.calculate(self.finegd, dens.nt_sg, self.vt_sg) e_xc /= self.finegd.comm.size self.timer.stop('XC 3D grid') self.timer.start('Poisson') # npoisson is the number of iterations: self.npoisson = self.poisson.solve(self.vHt_g, dens.rhot_g, charge=-dens.charge) self.timer.stop('Poisson') self.timer.start('Hartree integrate/restrict') e_coulomb = 0.5 * self.finegd.integrate( self.vHt_g, dens.rhot_g, global_integral=False) for vt_g in self.vt_sg: vt_g += self.vHt_g self.timer.stop('Hartree integrate/restrict') return np.array([e_coulomb, e_zero, e_external, e_xc]) def calculate_kinetic_energy(self, density): # XXX new timer item for kinetic energy? self.timer.start('Hartree integrate/restrict') self.restrict_and_collect(self.vt_sg, self.vt_sG) e_kinetic = 0.0 s = 0 for vt_G, nt_G in zip(self.vt_sG, density.nt_sG): if self.ref_vt_sG is not None: vt_G += self.ref_vt_sG[s] if s < self.nspins: e_kinetic -= self.gd.integrate(vt_G, nt_G - density.nct_G, global_integral=False) else: e_kinetic -= self.gd.integrate(vt_G, nt_G, global_integral=False) s += 1 self.timer.stop('Hartree integrate/restrict') return e_kinetic def calculate_atomic_hamiltonians(self, dens): def getshape(a): return sum(2 * l + 1 for l, _ in enumerate(self.setups[a].ghat_l)), W_aL = ArrayDict(self.atomdist.aux_partition, getshape, float) if self.vext: vext_g = self.vext.get_potential(self.finegd) dens.ghat.integrate(self.vHt_g + vext_g, W_aL) else: dens.ghat.integrate(self.vHt_g, W_aL) return self.atomdist.to_work(self.atomdist.from_aux(W_aL)) def calculate_forces2(self, dens, ghat_aLv, nct_av, vbar_av): if self.nspins == 2: vt_G = self.vt_sG.mean(0) else: vt_G = self.vt_sG[0] self.vbar.derivative(dens.nt_g, vbar_av) if self.vext: vext_g = self.vext.get_potential(self.finegd) dens.ghat.derivative(self.vHt_g + vext_g, ghat_aLv) else: dens.ghat.derivative(self.vHt_g, ghat_aLv) dens.nct.derivative(vt_G, nct_av) def get_electrostatic_potential(self, dens): self.update(dens) v_g = self.finegd.collect(self.vHt_g, broadcast=True) v_g = self.finegd.zero_pad(v_g) if hasattr(self.poisson, 'correction'): assert self.poisson.c == 2 v_g[:, :, 0] = self.poisson.correction return v_g