def calculate_exx_paw_correction(self): self.timer.start('PAW correction') self.devv = 0.0 self.evc = 0.0 self.ecc = 0.0 deg = 2 // self.wfs.nspins # spin degeneracy for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.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] self.devv -= D_ii[i1, i2] * A / deg self.evc -= np.dot(D_p, setup.X_p) self.ecc += setup.ExxC if not self.bandstructure: self.timer.stop('PAW correction') return Q = self.world.size // self.wfs.kd.comm.size self.exx_skn *= Q for kpt in self.wfs.kpt_u: for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.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] -= \ (A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) self.exx_skn[kpt.s, kpt.k] -= \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.wfs.nspins self.world.sum(self.exx_skn) self.exx_skn *= self.hybrid / Q self.timer.stop('PAW correction')
def initialize_paw_exx_corrections(self): for a, atomdata in enumerate(self.calc.wfs.setups): V_sii = [] for D_p in self.calc.density.D_asp[a]: D_ii = unpack2(D_p) V_ii = pawexxvv(atomdata, D_ii) V_sii.append(V_ii) C_ii = unpack(atomdata.X_p) self.V_asii.append(V_sii) self.C_aii.append(C_ii) self.exxcc += atomdata.ExxC
def symmetrize_atomic_density_matrices(self, D_asp): if len(self.kd.symmetry.op_scc) == 0: return a_sa = self.kd.symmetry.a_sa D_asp.redistribute(self.atom_partition.as_serial()) for s in range(self.nspins): D_aii = [unpack2(D_asp[a][s]) for a in range(len(D_asp))] for a, D_ii in enumerate(D_aii): setup = self.setups[a] D_asp[a][s] = pack(setup.symmetrize(a, D_aii, a_sa)) D_asp.redistribute(self.atom_partition)
def initialize_paw_exx_corrections(self): for a, atomdata in enumerate(self.calc.wfs.setups): V_sii = [] for D_p in self.calc.density.D_asp[a]: D_ii = unpack2(D_p) V_ii = pawexxvv(atomdata, D_ii) V_sii.append(V_ii) if atomdata.X_p is None: C_ii = D_ii * 0.0 else: C_ii = unpack(atomdata.X_p) self.V_asii.append(V_sii) self.C_aii.append(C_ii) self.exxcc += atomdata.ExxC or 0.0
def symmetrize_atomic_density_matrices(self, D_asp): if len(self.kd.symmetry.op_scc) > 1: all_D_asp = [] for a, setup in enumerate(self.setups): D_sp = D_asp.get(a) if D_sp is None: ni = setup.ni D_sp = np.empty((self.ns, ni * (ni + 1) // 2)) self.gd.comm.broadcast(D_sp, self.rank_a[a]) all_D_asp.append(D_sp) for s in range(self.nspins): D_aii = [unpack2(D_sp[s]) for D_sp in all_D_asp] for a, D_sp in D_asp.items(): setup = self.setups[a] D_sp[s] = pack(setup.symmetrize(a, D_aii, self.kd.symmetry.a_sa))
def symmetrize_atomic_density_matrices(self, D_asp): if len(self.kd.symmetry.op_scc) > 1: all_D_asp = [] for a, setup in enumerate(self.setups): D_sp = D_asp.get(a) if D_sp is None: ni = setup.ni D_sp = np.empty((self.ns, ni * (ni + 1) // 2)) self.gd.comm.broadcast(D_sp, self.rank_a[a]) all_D_asp.append(D_sp) for s in range(self.nspins): D_aii = [unpack2(D_sp[s]) for D_sp in all_D_asp] for a, D_sp in D_asp.items(): setup = self.setups[a] D_sp[s] = pack( setup.symmetrize(a, D_aii, self.kd.symmetry.a_sa))
def apply_oper(self, obj, sym, cart_rot, atom_deperm): from gpaw.utilities import pack, unpack2 a_a = self.symmetry.a_sa[sym] assert ( a_a == atom_deperm).all(), "mismatched oper order or something?" # permute the 'a' axis (atoms in the dict) obj = super().apply_oper(obj, sym, cart_rot, atom_deperm) # and now the 'p' axis dH_asp = obj for a in range(len(dH_asp)): R_ii = self.symmetry.R_asii[a][sym] for s in range(self.nspins): dH_p = dH_asp[a][s] dH_ii = unpack2(dH_p) tmp_ii = R_ii @ dH_ii @ R_ii.T tmp_p = pack(tmp_ii) dH_asp[a][s][...] = tmp_p return dH_asp
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 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 atomic_val_val(self, paw, H_nn, u=0): deg = 2 / self.nspins kpt = paw.wfs.kpt_u[u] for a, P_ni in kpt.P_ani.items(): # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii setup = paw.wfs.setups[a] D_p = paw.density.D_asp[a][kpt.s] H_p = np.zeros_like(D_p) 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) H_p[p12] -= 2 / deg * A / ((i1 != i2) + 1) H_nn += np.dot(P_ni, np.inner(unpack(H_p), P_ni.conj()))
def paw_corrections(self, gridrefinement=2): Fn_wsg, gd = self.interpolate_pseudo_density(gridrefinement) # Splines splines = {} phi_aj = [] phit_aj = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j = splines[id] else: # Load splines: phi_j, phit_j = self.setups[a].get_partial_waves()[:2] splines[id] = (phi_j, phit_j) phi_aj.append(phi_j) phit_aj.append(phit_j) # Create localized functions from splines phi = BasisFunctions(gd, phi_aj, dtype=float) phit = BasisFunctions(gd, phit_aj, dtype=float) # phi = BasisFunctions(gd, phi_aj, dtype=complex) # phit = BasisFunctions(gd, phit_aj, dtype=complex) spos_ac = self.atoms.get_scaled_positions() phi.set_positions(spos_ac) phit.set_positions(spos_ac) tmp_g = gd.empty(dtype=float) rho_MM = np.zeros((phi.Mmax, phi.Mmax), dtype=self.dtype) rho2_MM = np.zeros_like(rho_MM) for w in range(self.nw): for s in range(self.nspins): rho_MM[:] = 0 M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni FD_wsp = self.FD_awsp.get(a) if FD_wsp is None: FD_p = np.empty((ni * (ni + 1) // 2), dtype=self.dtype) else: FD_p = FD_wsp[w][s] if gd.comm.size > 1: gd.comm.broadcast(FD_p, self.rank_a[a]) D_ij = unpack2(FD_p) # unpack does complex conjugation that we don't want so # remove conjugation D_ij = np.triu(D_ij, 1) + np.conj(np.tril(D_ij)) # if FD_wsp is None: # FD_wsp = np.empty((self.nw, self.nspins, # ni * (ni + 1) // 2), # dtype=self.dtype) # if gd.comm.size > 1: # gd.comm.broadcast(FD_wsp, self.rank_a[a]) # D_ij = unpack2(FD_wsp[w][s]) # D_ij = np.triu(D_ij, 1) + np.conj(np.tril(D_ij)) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = D_ij M1 = M2 # Add real part of AE corrections tmp_g[:] = 0 rho2_MM[:] = rho_MM.real # TODO: use ae_valence_density_correction phi.construct_density(rho2_MM, tmp_g, q=-1) phit.construct_density(-rho2_MM, tmp_g, q=-1) # phi.lfc.ae_valence_density_correction(rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) # phit.lfc.ae_valence_density_correction(-rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) Fn_wsg[w][s] += tmp_g # Add imag part of AE corrections tmp_g[:] = 0 rho2_MM[:] = rho_MM.imag # TODO: use ae_valence_density_correction phi.construct_density(rho2_MM, tmp_g, q=-1) phit.construct_density(-rho2_MM, tmp_g, q=-1) # phi.lfc.ae_valence_density_correction(rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) # phit.lfc.ae_valence_density_correction(-rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) Fn_wsg[w][s] += 1.0j * tmp_g return Fn_wsg, gd
def get_M(self, modes, log=None, q=0): """Calculate el-ph coupling matrix for given modes(s). XXX: kwarg "q=0" is an ugly hack for k-points. There shuold be loops over q! Note that modes must be given as a dictionary with mode frequencies in eV and corresponding mode vectors in units of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit. In short frequencies and mode vectors must be given in ase units. :: d d ~ < w | -- v | w' > = < w | -- v | w'> dP dP _ \ ~a d . ~a + ) < w | p > -- /_\H < p | w' > /_ i dP ij j a,ij _ \ d ~a . ~a + ) < w | -- p > /_\H < p | w' > /_ dP i ij j a,ij _ \ ~a . d ~a + ) < w | p > /_\H < -- p | w' > /_ i ij dP j a,ij """ if log is None: timer = nulltimer elif log == '-': timer = StepTimer(name='EPCM') else: timer = StepTimer(name='EPCM', out=open(log, 'w')) modes1 = modes.copy() #convert to atomic units amu = 1.6605402e-27 # atomic unit mass [Kg] me = 9.1093897e-31 # electron mass [Kg] modes = {} for k in modes1.keys(): modes[k / Hartree] = modes1[k] / np.sqrt(amu / me) dvt_Gx, ddH_aspx = self.get_gradient() from gpaw import restart atoms, calc = restart('eq.gpw', txt=None) spos_ac = atoms.get_scaled_positions() if calc.wfs.S_qMM is None: calc.initialize(atoms) calc.initialize_positions(atoms) wfs = calc.wfs nao = wfs.setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX M_lii = {} timer.write_now('Starting gradient of pseudo part') for f, mode in modes.items(): mo = [] M_ii = np.zeros((nao, nao), dtype) for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() dvtdP_G = np.dot(dvt_Gx, mode) bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q) tri2full(M_ii, 'L') M_lii[f] = M_ii timer.write_now('Finished gradient of pseudo part') P_aqMi = calc.wfs.P_aqMi # Add the term # _ # \ ~a d . ~a # ) < w | p > -- /_\H < p | w' > # /_ i dP ij j # a,ij Ma_lii = {} for f, mode in modes.items(): Ma_lii[f] = np.zeros_like(M_lii.values()[0]) timer.write_now('Starting gradient of dH^a part') for f, mode in modes.items(): mo = [] for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() for a, ddH_spx in ddH_aspx.items(): ddHdP_sp = np.dot(ddH_spx, mode) ddHdP_ii = unpack2(ddHdP_sp[spin]) Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T) timer.write_now('Finished gradient of dH^a part') timer.write_now('Starting gradient of projectors part') spos_ac = self.atoms.get_scaled_positions() % 1.0 dP_aMix = self.get_dP_aMix(spos_ac, wfs, q, timer) timer.write_now('Finished gradient of projectors part') dH_asp = pickle.load(open('v.eq.pckl'))[1] Mb_lii = {} for f, mode in modes.items(): Mb_lii[f] = np.zeros_like(M_lii.values()[0]) for f, mode in modes.items(): for a, dP_Mix in dP_aMix.items(): dPdP_Mi = np.dot(dP_Mix, mode[a]) dH_ii = unpack2(dH_asp[a][spin]) dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T) Mb_lii[f] -= dPdP_MM + dPdP_MM.T # XXX The minus sign here is quite subtle. # It is related to how the derivative of projector # functions in GPAW is calculated. # More thorough explanations, anyone...? # Units of M_lii are Hartree/(Bohr * sqrt(m_e)) for mode in M_lii.keys(): M_lii[mode] += Ma_lii[mode] + Mb_lii[mode] # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f) # has units Bohr * sqrt(me) M_lii_1 = M_lii.copy() M_lii = {} for f in M_lii_1.keys(): M_lii[f * Hartree] = M_lii_1[f] * Hartree / np.sqrt(2 * f) return M_lii
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.ns) self.vHt_g = self.finegd.zeros() self.vt_sG = self.gd.empty(self.ns) self.poisson.initialize() self.timer.stop('Initialize Hamiltonian') Ekin, Epot, Ebar, Eext, Exc, W_aL = \ self.update_pseudo_potential(density) self.timer.start('Atomic') self.dH_asp = None # XXXX dH_asp = {} for a, D_sp in density.D_asp.items(): W_L = W_aL[a] setup = self.setups[a] D_p = D_sp[:self.nspins].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 is not None: vext = self.vext.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) dH_asp[a] = dH_sp = np.zeros_like(D_sp) if setup.HubU is not None: assert self.collinear nspins = len(D_sp) l_j = setup.l_j l = setup.Hubl scale = setup.Hubs 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, dH_asp[a]): [N_mm, V] = self.aoom(unpack2(D_p), a, l, scale) 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[:self.nspins] += dH_p if self.ref_dH_asp: dH_sp += self.ref_dH_asp[a] # We are not yet done with dH_sp; still need XC correction below Ddist_asp = self.dh_distributor.distribute(density.D_asp) dHdist_asp = {} Exca = 0.0 self.timer.start('XC Correction') for a, D_sp in Ddist_asp.items(): setup = self.setups[a] dH_sp = np.zeros_like(D_sp) Exca += self.xc.calculate_paw_correction(setup, D_sp, dH_sp, a=a) # XXX Exc are added on the "wrong" distribution; sum only works # when gd.comm and distribution comm are the same dHdist_asp[a] = dH_sp self.timer.stop('XC Correction') dHdist_asp = self.dh_distributor.collect(dHdist_asp) # Exca has contributions from all cores so modify it so it is # parallel in the same way as the other energies. Exca = self.world.sum(Exca) if self.gd.comm.rank == 0: Exc += Exca assert len(dHdist_asp) == len(self.atom_partition.my_indices) for a, D_sp in density.D_asp.items(): dH_sp = dH_asp[a] dH_sp += dHdist_asp[a] Ekin -= (D_sp * dH_sp).sum() # NCXXX self.dH_asp = dH_asp 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) # Make sure that all CPUs have the same energies self.world.broadcast(energies, 0) 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 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 get_M(self, modes, log=None, q=0): """Calculate el-ph coupling matrix for given modes(s). XXX: kwarg "q=0" is an ugly hack for k-points. There shuold be loops over q! Note that modes must be given as a dictionary with mode frequencies in eV and corresponding mode vectors in units of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit. In short frequencies and mode vectors must be given in ase units. :: d d ~ < w | -- v | w' > = < w | -- v | w'> dP dP _ \ ~a d . ~a + ) < w | p > -- /_\H < p | w' > /_ i dP ij j a,ij _ \ d ~a . ~a + ) < w | -- p > /_\H < p | w' > /_ dP i ij j a,ij _ \ ~a . d ~a + ) < w | p > /_\H < -- p | w' > /_ i ij dP j a,ij """ if log is None: timer = nulltimer elif log == '-': timer = StepTimer(name='EPCM') else: timer = StepTimer(name='EPCM', out=open(log, 'w')) modes1 = modes.copy() #convert to atomic units amu = 1.6605402e-27 # atomic unit mass [Kg] me = 9.1093897e-31 # electron mass [Kg] modes = {} for k in modes1.keys(): modes[k / Hartree] = modes1[k] / np.sqrt(amu / me) dvt_Gx, ddH_aspx = self.get_gradient() from gpaw import restart atoms, calc = restart('eq.gpw', txt=None) if calc.wfs.S_qMM is None: calc.initialize(atoms) calc.initialize_positions(atoms) wfs = calc.wfs nao = wfs.setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX M_lii = {} timer.write_now('Starting gradient of pseudo part') for f, mode in modes.items(): mo = [] M_ii = np.zeros((nao, nao), dtype) for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() dvtdP_G = np.dot(dvt_Gx, mode) bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q) tri2full(M_ii, 'L') M_lii[f] = M_ii timer.write_now('Finished gradient of pseudo part') P_aqMi = calc.wfs.P_aqMi # Add the term # _ # \ ~a d . ~a # ) < w | p > -- /_\H < p | w' > # /_ i dP ij j # a,ij Ma_lii = {} for f, mode in modes.items(): Ma_lii[f] = np.zeros_like(M_lii.values()[0]) timer.write_now('Starting gradient of dH^a part') for f, mode in modes.items(): mo = [] for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() for a, ddH_spx in ddH_aspx.items(): ddHdP_sp = np.dot(ddH_spx, mode) ddHdP_ii = unpack2(ddHdP_sp[spin]) Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T) timer.write_now('Finished gradient of dH^a part') timer.write_now('Starting gradient of projectors part') dP_aMix = self.get_dP_aMix(calc.spos_ac, wfs, q, timer) timer.write_now('Finished gradient of projectors part') dH_asp = pickle.load(open('v.eq.pckl', 'rb'))[1] Mb_lii = {} for f, mode in modes.items(): Mb_lii[f] = np.zeros_like(M_lii.values()[0]) for f, mode in modes.items(): for a, dP_Mix in dP_aMix.items(): dPdP_Mi = np.dot(dP_Mix, mode[a]) dH_ii = unpack2(dH_asp[a][spin]) dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T) Mb_lii[f] -= dPdP_MM + dPdP_MM.T # XXX The minus sign here is quite subtle. # It is related to how the derivative of projector # functions in GPAW is calculated. # More thorough explanations, anyone...? # Units of M_lii are Hartree/(Bohr * sqrt(m_e)) for mode in M_lii.keys(): M_lii[mode] += Ma_lii[mode] + Mb_lii[mode] # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f) # has units Bohr * sqrt(me) M_lii_1 = M_lii.copy() M_lii = {} for f in M_lii_1.keys(): M_lii[f * Hartree] = M_lii_1[f] * Hartree / np.sqrt(2 * f) return M_lii
def with_ae_corrections(self, finegrid=False): """Get pair density including the AE corrections""" nij_g = self.get(finegrid) # Generate the density matrix D_ap = {} # D_aii = {} for a, P_ni in self.P_ani.items(): Pi_i = P_ni[self.i] Pj_i = P_ni[self.j] D_ii = np.outer(Pi_i.conj(), Pj_i) # Note: D_ii is not symmetric but the products of partial waves are # so that we can pack D_ap[a] = pack(D_ii) # D_aii[a] = D_ii # Load partial waves if needed if ((finegrid and (not hasattr(self, 'phi'))) or ((not finegrid) and (not hasattr(self, 'Phi')))): # Splines splines = {} phi_aj = [] phit_aj = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j = splines[id] else: # Load splines: phi_j, phit_j = self.setups[a].get_partial_waves()[:2] splines[id] = (phi_j, phit_j) phi_aj.append(phi_j) phit_aj.append(phit_j) # Store partial waves as class variables if finegrid: gd = self.density.finegd self.__class__.phi = BasisFunctions(gd, phi_aj) self.__class__.phit = BasisFunctions(gd, phit_aj) self.__class__.phi.set_positions(self.spos_ac) self.__class__.phit.set_positions(self.spos_ac) else: gd = self.density.gd self.__class__.Phi = BasisFunctions(gd, phi_aj) self.__class__.Phit = BasisFunctions(gd, phit_aj) self.__class__.Phi.set_positions(self.spos_ac) self.__class__.Phit.set_positions(self.spos_ac) # Add AE corrections if finegrid: phi = self.phi phit = self.phit gd = self.density.finegd else: phi = self.Phi phit = self.Phit gd = self.density.gd rho_MM = np.zeros((phi.Mmax, phi.Mmax)) M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni D_p = D_ap.get(a) if D_p is None: D_p = np.empty((ni * (ni + 1) // 2)) if gd.comm.size > 1: gd.comm.broadcast(D_p, self.wfs.rank_a[a]) D_ii = unpack2(D_p) # D_ii = D_aii.get(a) # if D_ii is None: # D_ii = np.empty((ni, ni)) # if gd.comm.size > 1: # gd.comm.broadcast(D_ii, self.wfs.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = D_ii M1 = M2 # construct_density assumes symmetric rho_MM and # takes only the upper half of it phi.construct_density(rho_MM, nij_g, q=-1) phit.construct_density(-rho_MM, nij_g, q=-1) # TODO: use ae_valence_density_correction # phi.lfc.ae_valence_density_correction( # rho_MM, nij_g, np.zeros(len(phi.M_W), np.intc), np.zeros(self.na)) # phit.lfc.ae_valence_density_correction( # -rho_MM, nij_g, np.zeros(len(phit.M_W), np.intc), np.zeros(self.na)) return nij_g
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 with_ae_corrections(self, finegrid=False): """Get pair density including the AE corrections""" nij_g = self.get(finegrid) # Generate the density matrix D_ap = {} # D_aii = {} for a, P_ni in self.P_ani.items(): Pi_i = P_ni[self.i] Pj_i = P_ni[self.j] D_ii = np.outer(Pi_i.conj(), Pj_i) # Note: D_ii is not symmetric but the products of partial waves are # so that we can pack D_ap[a] = pack(D_ii) # D_aii[a] = D_ii # Load partial waves if needed if ((finegrid and (not hasattr(self, 'phi'))) or ((not finegrid) and (not hasattr(self, 'Phi')))): # Splines splines = {} phi_aj = [] phit_aj = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j = splines[id] else: # Load splines: phi_j, phit_j = self.setups[a].get_partial_waves()[:2] splines[id] = (phi_j, phit_j) phi_aj.append(phi_j) phit_aj.append(phit_j) # Store partial waves as class variables if finegrid: gd = self.density.finegd self.__class__.phi = BasisFunctions(gd, phi_aj) self.__class__.phit = BasisFunctions(gd, phit_aj) self.__class__.phi.set_positions(self.spos_ac) self.__class__.phit.set_positions(self.spos_ac) else: gd = self.density.gd self.__class__.Phi = BasisFunctions(gd, phi_aj) self.__class__.Phit = BasisFunctions(gd, phit_aj) self.__class__.Phi.set_positions(self.spos_ac) self.__class__.Phit.set_positions(self.spos_ac) # Add AE corrections if finegrid: phi = self.phi phit = self.phit gd = self.density.finegd else: phi = self.Phi phit = self.Phit gd = self.density.gd rho_MM = np.zeros((phi.Mmax, phi.Mmax)) M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni D_p = D_ap.get(a) if D_p is None: D_p = np.empty((ni * (ni + 1) // 2)) if gd.comm.size > 1: gd.comm.broadcast(D_p, self.wfs.partition.rank_a[a]) D_ii = unpack2(D_p) # D_ii = D_aii.get(a) # if D_ii is None: # D_ii = np.empty((ni, ni)) # if gd.comm.size > 1: # gd.comm.broadcast(D_ii, self.wfs.atom_partition.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = D_ii M1 = M2 # construct_density assumes symmetric rho_MM and # takes only the upper half of it phi.construct_density(rho_MM, nij_g, q=-1) phit.construct_density(-rho_MM, nij_g, q=-1) # TODO: use ae_valence_density_correction # phi.lfc.ae_valence_density_correction( # rho_MM, nij_g, np.zeros(len(phi.M_W), np.intc), np.zeros(self.na)) # phit.lfc.ae_valence_density_correction( # -rho_MM, nij_g, np.zeros(len(phit.M_W), np.intc), np.zeros(self.na)) return nij_g
def calculate_supercell_matrix(self, dump=0, name=None, filter=None, include_pseudo=True, atoms=None): """Calculate matrix elements of the el-ph coupling in the LCAO basis. This function calculates the matrix elements between LCAOs and local atomic gradients of the effective potential. The matrix elements are calculated for the supercell used to obtain finite-difference approximations to the derivatives of the effective potential wrt to atomic displacements. Parameters ---------- dump: int Dump supercell matrix to pickle file (default: 0). 0: Supercell matrix not saved 1: Supercell matrix saved in a single pickle file. 2: Dump matrix for different gradients in separate files. Useful for large systems where the total array gets too large for a single pickle file. name: string User specified name of the generated pickle file(s). If not provided, the string in the ``name`` attribute is used. filter: str Fourier filter atomic gradients of the effective potential. The specified components (``normal`` or ``umklapp``) are removed (default: None). include_pseudo: bool Include the contribution from the psedupotential in the atomic gradients. If ``False``, only the gradient of the effective potential is included (default: True). atoms: Atoms object Calculate supercell for an ``Atoms`` object different from the one provided in the ``__init__`` method (WARNING, NOT working!). """ assert self.calc_lcao is not None, "Set LCAO calculator" # Supercell atoms if atoms is None: atoms_N = self.atoms * self.N_c else: atoms_N = atoms # Initialize calculator if required and extract useful quantities calc = self.calc_lcao if not hasattr(calc.wfs, 'S_qMM'): calc.initialize(atoms_N) calc.initialize_positions(atoms_N) self.set_basis_info() basis = calc.input_parameters['basis'] # Extract useful objects from the calculator wfs = calc.wfs gd = calc.wfs.gd kd = calc.wfs.kd kpt_u = wfs.kpt_u setups = wfs.setups nao = setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX # If gamma calculation, overlap with neighboring cell cannot be removed if kd.gamma: print "WARNING: Gamma-point calculation." else: # Bloch to real-space converter tb = TightBinding(atoms_N, calc) self.timer.write_now("Calculating supercell matrix") self.timer.write_now("Calculating real-space gradients") # Calculate finite-difference gradients (in Hartree / Bohr) V1t_xG, dH1_xasp = self.calculate_gradient() self.timer.write_now("Finished real-space gradients") # Fourier filter the atomic gradients of the effective potential if filter is not None: self.timer.write_now("Fourier filtering gradients") V1_xG = V1t_xG.copy() self.fourier_filter(V1t_xG, components=filter) self.timer.write_now("Finished Fourier filtering") # For the contribution from the derivative of the projectors dP_aqvMi = self.calculate_dP_aqvMi(wfs) # Equilibrium atomic Hamiltonian matrix (projector coefficients) dH_asp = pickle.load(open(self.name + '.eq.pckl'))[1] # Check that the grid is the same as in the calculator assert np.all(V1t_xG.shape[-3:] == (gd.N_c + gd.pbc_c - 1)), \ "Mismatch in grids." # Calculate < i k | grad H | j k >, i.e. matrix elements in Bloch basis # List for supercell matrices; g_xNNMM = [] self.timer.write_now("Calculating gradient of PAW Hamiltonian") # Do each cartesian component separately for i, a in enumerate(self.indices): for v in range(3): # Corresponding array index x = 3 * i + v V1t_G = V1t_xG[x] self.timer.write_now("%s-gradient of atom %u" % (['x','y','z'][v], a)) # Array for different k-point components g_qMM = np.zeros((len(kpt_u), nao, nao), dtype) # 1) Gradient of effective potential self.timer.write_now("Starting gradient of effective potential") for kpt in kpt_u: # Matrix elements geff_MM = np.zeros((nao, nao), dtype) bfs.calculate_potential_matrix(V1t_G, geff_MM, q=kpt.q) tri2full(geff_MM, 'L') # Insert in array g_qMM[kpt.q] += geff_MM self.timer.write_now("Finished gradient of effective potential") if include_pseudo: self.timer.write_now("Starting gradient of pseudo part") # 2) Gradient of non-local part (projectors) self.timer.write_now("Starting gradient of dH^a") P_aqMi = calc.wfs.P_aqMi # 2a) dH^a part has contributions from all other atoms for kpt in kpt_u: # Matrix elements gp_MM = np.zeros((nao, nao), dtype) dH1_asp = dH1_xasp[x] for a_, dH1_sp in dH1_asp.items(): dH1_ii = unpack2(dH1_sp[spin]) gp_MM += np.dot(P_aqMi[a_][kpt.q], np.dot(dH1_ii, P_aqMi[a_][kpt.q].T.conjugate())) g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of dH^a") self.timer.write_now("Starting gradient of projectors") # 2b) dP^a part has only contributions from the same atoms dP_qvMi = dP_aqvMi[a] dH_ii = unpack2(dH_asp[a][spin]) for kpt in kpt_u: #XXX Sort out the sign here; conclusion -> sign = +1 ! P1HP_MM = +1 * np.dot(dP_qvMi[kpt.q][v], np.dot(dH_ii, P_aqMi[a][kpt.q].T.conjugate())) # Matrix elements gp_MM = P1HP_MM + P1HP_MM.T.conjugate() g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of projectors") self.timer.write_now("Finished gradient of pseudo part") # Extract R_c=(0, 0, 0) block by Fourier transforming if kd.gamma or kd.N_c is None: g_MM = g_qMM[0] else: # Convert to array g_MM = tb.bloch_to_real_space(g_qMM, R_c=(0, 0, 0))[0] # Reshape to global unit cell indices N = np.prod(self.N_c) # Number of basis function in the primitive cell assert (nao % N) == 0, "Alarm ...!" nao_cell = nao / N g_NMNM = g_MM.reshape((N, nao_cell, N, nao_cell)) g_NNMM = g_NMNM.swapaxes(1, 2).copy() self.timer.write_now("Finished supercell matrix") if dump != 2: g_xNNMM.append(g_NNMM) else: if name is not None: fname = '%s.supercell_matrix_x_%2.2u.%s.pckl' % (name, x, basis) else: fname = self.name + \ '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis) if kd.comm.rank == 0: fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['niAO_a'] pickle.dump((g_NNMM, M_a, nao_a), fd, 2) fd.close() self.timer.write_now("Finished gradient of PAW Hamiltonian") if dump != 2: # Collect gradients in one array self.g_xNNMM = np.array(g_xNNMM) # Dump to pickle file using binary mode together with basis info if dump and kd.comm.rank == 0: if name is not None: fname = '%s.supercell_matrix.%s.pckl' % (name, basis) else: fname = self.name + '.supercell_matrix.%s.pckl' % basis fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['nao_a'] pickle.dump((self.g_xNNMM, M_a, nao_a), fd, 2) fd.close()
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.ncomp**2) self.vHt_g = self.finegd.zeros() self.vt_sG = self.gd.empty(self.nspins * self.ncomp**2) self.poisson.initialize() self.timer.stop('Initialize Hamiltonian') Ekin, Epot, Ebar, Eext, Exc, W_aL = \ self.update_pseudo_potential(density) self.timer.start('Atomic') 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[:self.nspins].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 is not None: vext = self.vext.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 += self.xc.calculate_paw_correction(setup, D_sp, dH_sp, a=a) self.timer.stop('XC Correction') if setup.HubU is not None: assert self.collinear nspins = len(D_sp) l_j = setup.l_j l = setup.Hubl scale = setup.Hubs 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, scale) 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[:self.nspins] += dH_p Ekin -= (D_sp * dH_sp).sum() # NCXXX 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) # Make sure that all CPUs have the same energies self.world.broadcast(energies, 0) 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 update_corrections(self, dens, W_aL): self.timer.start('Atomic') self.dH_asp = None # XXXX e_kinetic = 0.0 e_coulomb = 0.0 e_zero = 0.0 e_external = 0.0 e_xc = 0.0 D_asp = self.atomdist.to_work(dens.D_asp) dH_asp = self.setups.empty_atomic_matrix(self.nspins, D_asp.partition) for a, D_sp in 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)) e_kinetic += np.dot(setup.K_p, D_p) + setup.Kc e_zero += setup.MB + np.dot(setup.MB_p, D_p) e_coulomb += setup.M + np.dot( D_p, (setup.M_p + np.dot(setup.M_pp, D_p))) dH_asp[a] = dH_sp = np.zeros_like(D_sp) if setup.HubU is not None: nspins = len(D_sp) l_j = setup.l_j l = setup.Hubl scale = setup.Hubs 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, dH_asp[a]): [N_mm, V] = self.aoom(unpack2(D_p), a, l, scale) 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) e_xc += Eorb if nspins == 1: # add contribution of other spin manyfold e_xc += 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 if self.ref_dH_asp: dH_sp += self.ref_dH_asp[a] self.timer.start('XC Correction') for a, D_sp in D_asp.items(): e_xc += self.xc.calculate_paw_correction(self.setups[a], D_sp, dH_asp[a], a=a) self.timer.stop('XC Correction') for a, D_sp in D_asp.items(): e_kinetic -= (D_sp * dH_asp[a]).sum() # NCXXX self.dH_asp = self.atomdist.from_work(dH_asp) self.timer.stop('Atomic') # Make corrections due to non-local xc: self.Enlxc = 0.0 # XXXxcfunc.get_non_local_energy() e_kinetic += self.xc.get_kinetic_energy_correction() / self.world.size return np.array([e_kinetic, e_coulomb, e_zero, e_external, e_xc])
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 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 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 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.ns) self.vHt_g = self.finegd.zeros() self.vt_sG = self.gd.empty(self.ns) self.poisson.initialize() self.timer.stop("Initialize Hamiltonian") Ekin, Epot, Ebar, Eext, Exc, W_aL = self.update_pseudo_potential(density) self.timer.start("Atomic") self.dH_asp = None # XXXX dH_asp = {} for a, D_sp in density.D_asp.items(): W_L = W_aL[a] setup = self.setups[a] D_p = D_sp[: self.nspins].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 is not None: vext = self.vext.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) dH_asp[a] = dH_sp = np.zeros_like(D_sp) if setup.HubU is not None: assert self.collinear nspins = len(D_sp) l_j = setup.l_j l = setup.Hubl scale = setup.Hubs 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, dH_asp[a]): [N_mm, V] = self.aoom(unpack2(D_p), a, l, scale) N_mm = N_mm / 2 * nspins Eorb = setup.HubU / 2.0 * (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[: self.nspins] += dH_p if self.ref_dH_asp: dH_sp += self.ref_dH_asp[a] # We are not yet done with dH_sp; still need XC correction below Ddist_asp = self.dh_distributor.distribute(density.D_asp) dHdist_asp = {} Exca = 0.0 self.timer.start("XC Correction") for a, D_sp in Ddist_asp.items(): setup = self.setups[a] dH_sp = np.zeros_like(D_sp) Exca += self.xc.calculate_paw_correction(setup, D_sp, dH_sp, a=a) # XXX Exc are added on the "wrong" distribution; sum only works # when gd.comm and distribution comm are the same dHdist_asp[a] = dH_sp self.timer.stop("XC Correction") dHdist_asp = self.dh_distributor.collect(dHdist_asp) # Exca has contributions from all cores so modify it so it is # parallel in the same way as the other energies. Exca = self.world.sum(Exca) if self.gd.comm.rank == 0: Exc += Exca assert len(dHdist_asp) == len(self.atom_partition.my_indices) for a, D_sp in density.D_asp.items(): dH_sp = dH_asp[a] dH_sp += dHdist_asp[a] Ekin -= (D_sp * dH_sp).sum() # NCXXX self.dH_asp = dH_asp 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) # Make sure that all CPUs have the same energies self.world.broadcast(energies, 0) 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 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
def calculate_supercell_matrix(self, dump=0, name=None, filter=None, include_pseudo=True, atoms=None): """Calculate matrix elements of the el-ph coupling in the LCAO basis. This function calculates the matrix elements between LCAOs and local atomic gradients of the effective potential. The matrix elements are calculated for the supercell used to obtain finite-difference approximations to the derivatives of the effective potential wrt to atomic displacements. Parameters ---------- dump: int Dump supercell matrix to pickle file (default: 0). 0: Supercell matrix not saved 1: Supercell matrix saved in a single pickle file. 2: Dump matrix for different gradients in separate files. Useful for large systems where the total array gets too large for a single pickle file. name: string User specified name of the generated pickle file(s). If not provided, the string in the ``name`` attribute is used. filter: str Fourier filter atomic gradients of the effective potential. The specified components (``normal`` or ``umklapp``) are removed (default: None). include_pseudo: bool Include the contribution from the psedupotential in the atomic gradients. If ``False``, only the gradient of the effective potential is included (default: True). atoms: Atoms object Calculate supercell for an ``Atoms`` object different from the one provided in the ``__init__`` method (WARNING, NOT working!). """ assert self.calc_lcao is not None, "Set LCAO calculator" # Supercell atoms if atoms is None: atoms_N = self.atoms * self.N_c else: atoms_N = atoms # Initialize calculator if required and extract useful quantities calc = self.calc_lcao if not hasattr(calc.wfs, 'S_qMM'): calc.initialize(atoms_N) calc.initialize_positions(atoms_N) self.set_basis_info() basis = calc.input_parameters['basis'] # Extract useful objects from the calculator wfs = calc.wfs gd = calc.wfs.gd kd = calc.wfs.kd kpt_u = wfs.kpt_u setups = wfs.setups nao = setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX # If gamma calculation, overlap with neighboring cell cannot be removed if kd.gamma: print("WARNING: Gamma-point calculation.") else: # Bloch to real-space converter tb = TightBinding(atoms_N, calc) self.timer.write_now("Calculating supercell matrix") self.timer.write_now("Calculating real-space gradients") # Calculate finite-difference gradients (in Hartree / Bohr) V1t_xG, dH1_xasp = self.calculate_gradient() self.timer.write_now("Finished real-space gradients") # Fourier filter the atomic gradients of the effective potential if filter is not None: self.timer.write_now("Fourier filtering gradients") V1_xG = V1t_xG.copy() self.fourier_filter(V1t_xG, components=filter) self.timer.write_now("Finished Fourier filtering") # For the contribution from the derivative of the projectors dP_aqvMi = self.calculate_dP_aqvMi(wfs) # Equilibrium atomic Hamiltonian matrix (projector coefficients) dH_asp = pickle.load(open(self.name + '.eq.pckl'))[1] # Check that the grid is the same as in the calculator assert np.all(V1t_xG.shape[-3:] == (gd.N_c + gd.pbc_c - 1)), \ "Mismatch in grids." # Calculate < i k | grad H | j k >, i.e. matrix elements in Bloch basis # List for supercell matrices; g_xNNMM = [] self.timer.write_now("Calculating gradient of PAW Hamiltonian") # Do each cartesian component separately for i, a in enumerate(self.indices): for v in range(3): # Corresponding array index x = 3 * i + v V1t_G = V1t_xG[x] self.timer.write_now("%s-gradient of atom %u" % (['x', 'y', 'z'][v], a)) # Array for different k-point components g_qMM = np.zeros((len(kpt_u), nao, nao), dtype) # 1) Gradient of effective potential self.timer.write_now( "Starting gradient of effective potential") for kpt in kpt_u: # Matrix elements geff_MM = np.zeros((nao, nao), dtype) bfs.calculate_potential_matrix(V1t_G, geff_MM, q=kpt.q) tri2full(geff_MM, 'L') # Insert in array g_qMM[kpt.q] += geff_MM self.timer.write_now( "Finished gradient of effective potential") if include_pseudo: self.timer.write_now("Starting gradient of pseudo part") # 2) Gradient of non-local part (projectors) self.timer.write_now("Starting gradient of dH^a") P_aqMi = calc.wfs.P_aqMi # 2a) dH^a part has contributions from all other atoms for kpt in kpt_u: # Matrix elements gp_MM = np.zeros((nao, nao), dtype) dH1_asp = dH1_xasp[x] for a_, dH1_sp in dH1_asp.items(): dH1_ii = unpack2(dH1_sp[spin]) gp_MM += np.dot( P_aqMi[a_][kpt.q], np.dot(dH1_ii, P_aqMi[a_][kpt.q].T.conjugate())) g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of dH^a") self.timer.write_now("Starting gradient of projectors") # 2b) dP^a part has only contributions from the same atoms dP_qvMi = dP_aqvMi[a] dH_ii = unpack2(dH_asp[a][spin]) for kpt in kpt_u: #XXX Sort out the sign here; conclusion -> sign = +1 ! P1HP_MM = +1 * np.dot( dP_qvMi[kpt.q][v], np.dot(dH_ii, P_aqMi[a][kpt.q].T.conjugate())) # Matrix elements gp_MM = P1HP_MM + P1HP_MM.T.conjugate() g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of projectors") self.timer.write_now("Finished gradient of pseudo part") # Extract R_c=(0, 0, 0) block by Fourier transforming if kd.gamma or kd.N_c is None: g_MM = g_qMM[0] else: # Convert to array g_MM = tb.bloch_to_real_space(g_qMM, R_c=(0, 0, 0))[0] # Reshape to global unit cell indices N = np.prod(self.N_c) # Number of basis function in the primitive cell assert (nao % N) == 0, "Alarm ...!" nao_cell = nao / N g_NMNM = g_MM.reshape((N, nao_cell, N, nao_cell)) g_NNMM = g_NMNM.swapaxes(1, 2).copy() self.timer.write_now("Finished supercell matrix") if dump != 2: g_xNNMM.append(g_NNMM) else: if name is not None: fname = '%s.supercell_matrix_x_%2.2u.%s.pckl' % ( name, x, basis) else: fname = self.name + \ '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis) if kd.comm.rank == 0: fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['nao_a'] pickle.dump((g_NNMM, M_a, nao_a), fd, 2) fd.close() self.timer.write_now("Finished gradient of PAW Hamiltonian") if dump != 2: # Collect gradients in one array self.g_xNNMM = np.array(g_xNNMM) # Dump to pickle file using binary mode together with basis info if dump and kd.comm.rank == 0: if name is not None: fname = '%s.supercell_matrix.%s.pckl' % (name, basis) else: fname = self.name + '.supercell_matrix.%s.pckl' % basis fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['nao_a'] pickle.dump((self.g_xNNMM, M_a, nao_a), fd, 2) fd.close()
def get_all_electron_density(self, atoms=None, gridrefinement=2, spos_ac=None, skip_core=False): """Return real all-electron density array. Usage: Either get_all_electron_density(atoms) or get_all_electron_density(spos_ac=spos_ac) skip_core=True theoretically returns the all-electron valence density (use with care; will not in general integrate to valence) """ if spos_ac is None: spos_ac = atoms.get_scaled_positions() % 1.0 # Refinement of coarse grid, for representation of the AE-density # XXXXXXXXXXXX think about distribution depending on gridrefinement! if gridrefinement == 1: gd = self.redistributor.aux_gd n_sg = self.nt_sG.copy() # This will get the density with the same distribution # as finegd: n_sg = self.redistributor.distribute(n_sg) elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate_pseudo_density() 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) # XXX grids! # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate_pseudo_density() 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) 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(spos_ac))) a_W = np.empty(len(phi.M_W), np.intc) W = 0 for a in phi.atom_indices: nw = len(phi.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw x_W = phi.create_displacement_arrays()[0] D_asp = self.D_asp # XXX really? 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 = 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) / self.nspins - sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) if not skip_core: I_a[a] -= setup.Nc / self.nspins if gd.comm.size > 1: gd.comm.broadcast(D_sp, D_asp.partition.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = unpack2(D_sp[s]) M1 = M2 assert np.all(n_sg[s].shape == phi.gd.n_c) phi.lfc.ae_valence_density_correction(rho_MM, n_sg[s], a_W, I_a, x_W) phit.lfc.ae_valence_density_correction(-rho_MM, n_sg[s], a_W, I_a, x_W) a_W = np.empty(len(nc.M_W), np.intc) 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): if not skip_core: 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 if not skip_core: 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 paw_corrections(self, gridrefinement=2): Fn_wsg, gd = self.interpolate_pseudo_density(gridrefinement) # Splines splines = {} phi_aj = [] phit_aj = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j = splines[id] else: # Load splines: phi_j, phit_j = self.setups[a].get_partial_waves()[:2] splines[id] = (phi_j, phit_j) phi_aj.append(phi_j) phit_aj.append(phit_j) # Create localized functions from splines phi = BasisFunctions(gd, phi_aj, dtype=float) phit = BasisFunctions(gd, phit_aj, dtype=float) # phi = BasisFunctions(gd, phi_aj, dtype=complex) # phit = BasisFunctions(gd, phit_aj, dtype=complex) spos_ac = self.atoms.get_scaled_positions() phi.set_positions(spos_ac) phit.set_positions(spos_ac) tmp_g = gd.empty(dtype=float) rho_MM = np.zeros((phi.Mmax, phi.Mmax), dtype=self.dtype) rho2_MM = np.zeros_like(rho_MM) for w in range(self.nw): for s in range(self.nspins): rho_MM[:] = 0 M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni FD_wsp = self.FD_awsp.get(a) if FD_wsp is None: FD_p = np.empty((ni * (ni + 1) // 2), dtype=self.dtype) else: FD_p = FD_wsp[w][s] if gd.comm.size > 1: gd.comm.broadcast(FD_p, self.rank_a[a]) D_ij = unpack2(FD_p) # unpack does complex conjugation that we don't want so # remove conjugation D_ij = np.triu(D_ij, 1) + np.conj(np.tril(D_ij)) # if FD_wsp is None: # FD_wsp = np.empty((self.nw, self.nspins, # ni * (ni + 1) // 2), # dtype=self.dtype) # if gd.comm.size > 1: # gd.comm.broadcast(FD_wsp, self.rank_a[a]) # D_ij = unpack2(FD_wsp[w][s]) # D_ij = np.triu(D_ij, 1) + np.conj(np.tril(D_ij)) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = D_ij M1 = M2 # Add real part of AE corrections tmp_g[:] = 0 rho2_MM[:] = rho_MM.real # TODO: use ae_valence_density_correction phi.construct_density(rho2_MM, tmp_g, q=-1) phit.construct_density(-rho2_MM, tmp_g, q=-1) # phi.lfc.ae_valence_density_correction(rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) # phit.lfc.ae_valence_density_correction(-rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) Fn_wsg[w][s] += tmp_g # Add imag part of AE corrections tmp_g[:] = 0 rho2_MM[:] = rho_MM.imag # TODO: use ae_valence_density_correction phi.construct_density(rho2_MM, tmp_g, q=-1) phit.construct_density(-rho2_MM, tmp_g, q=-1) # phi.lfc.ae_valence_density_correction(rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) # phit.lfc.ae_valence_density_correction(-rho2_MM, tmp_g, # np.zeros(len(phi.M_W), # np.intc), # np.zeros(self.na)) Fn_wsg[w][s] += 1.0j * tmp_g return Fn_wsg, gd