def calculate_energy(self, pd, chi0_wGG, cut_G, q_v=None): """Evaluate correlation energy from chi0.""" sqrV_G = get_coulomb_kernel(pd, self.calc.wfs.kd.N_c, q_v=q_v, truncation=self.truncation, wstc=self.wstc)**0.5 if cut_G is not None: sqrV_G = sqrV_G[cut_G] nG = len(sqrV_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy
def get_dielectric_matrix(self, xc='RPA', q_c=[0, 0, 0], direction='x', symmetric=True, calculate_chi=False, q_v=None, add_intraband=True): """Returns the symmetrized dielectric matrix. :: \tilde\epsilon_GG' = v^{-1/2}_G \epsilon_GG' v^{1/2}_G', where:: epsilon_GG' = 1 - v_G * P_GG' and P_GG' is the polarization. :: In RPA: P = chi^0 In TDDFT: P = (1 - chi^0 * f_xc)^{-1} chi^0 in addition to RPA one can use the kernels, ALDA, rALDA, rAPBE, Bootstrap and LRalpha (long-range kerne), where alpha is a user specified parameter (for example xc='LR0.25') The head of the inverse symmetrized dielectric matrix is equal to the head of the inverse dielectric matrix (inverse dielectric function)""" pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c) N_c = self.chi0.calc.wfs.kd.N_c if self.truncation == 'wigner-seitz': self.wstc = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv, N_c) else: self.wstc = None K_G = get_coulomb_kernel(pd, N_c, truncation=self.truncation, wstc=self.wstc, q_v=q_v)**0.5 nG = len(K_G) if pd.kd.gamma: if isinstance(direction, str): d_v = { 'x': [1, 0, 0], 'y': [0, 1, 0], 'z': [0, 0, 1] }[direction] else: d_v = direction d_v = np.asarray(d_v) / np.linalg.norm(d_v) W = slice(self.w1, self.w2) if add_intraband: chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0]) chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1]) chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T) if q_v is not None: print('Restoring q dependence of head and wings of chi0') chi0_wGG[:, 1:, 0] *= np.dot(q_v, d_v) chi0_wGG[:, 0, 1:] *= np.dot(q_v, d_v) chi0_wGG[:, 0, 0] *= np.dot(q_v, d_v)**2 if xc != 'RPA': Kxc_sGG = get_xc_kernel(pd, self.chi0, functional=xc, chi0_wGG=chi0_wGG) if calculate_chi: chi_wGG = [] for chi0_GG in chi0_wGG: if xc == 'RPA': P_GG = chi0_GG else: P_GG = np.dot( np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, Kxc_sGG[0])), chi0_GG) if symmetric: e_GG = np.eye(nG) - P_GG * K_G * K_G[:, np.newaxis] else: K_GG = (K_G**2 * np.ones([nG, nG])).T e_GG = np.eye(nG) - P_GG * K_GG if calculate_chi: K_GG = np.diag(K_G**2) if xc != 'RPA': K_GG += Kxc_sGG[0] chi_wGG.append( np.dot(np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)), chi0_GG)) chi0_GG[:] = e_GG # chi0_wGG is now the dielectric matrix if not calculate_chi: return chi0_wGG else: # chi_wGG is the full density response function.. return pd, chi0_wGG, np.array(chi_wGG)
def get_chi(self, xc='RPA', q_c=[0, 0, 0], direction='x', return_VchiV=True, q_v=None): """ Returns v^1/2 chi v^1/2. The truncated Coulomb interaction is then included as v^-1/2 v_t v^-1/2. This is in order to conform with the head and wings of chi0, which is treated specially for q=0.""" pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c) N_c = self.chi0.calc.wfs.kd.N_c Kbare_G = get_coulomb_kernel(pd, N_c, truncation=None, q_v=q_v) vsqr_G = Kbare_G**0.5 nG = len(vsqr_G) if self.truncation is not None: if self.truncation == 'wigner-seitz': self.wstc = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv, N_c) else: self.wstc = None Ktrunc_G = get_coulomb_kernel(pd, N_c, truncation=self.truncation, wstc=self.wstc, q_v=q_v) K_GG = np.diag(Ktrunc_G / Kbare_G) else: K_GG = np.eye(nG, dtype=complex) if pd.kd.gamma: if isinstance(direction, str): d_v = { 'x': [1, 0, 0], 'y': [0, 1, 0], 'z': [0, 0, 1] }[direction] else: d_v = direction d_v = np.asarray(d_v) / np.linalg.norm(d_v) W = slice(self.w1, self.w2) chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0]) chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1]) chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T) if xc != 'RPA': Kxc_sGG = get_xc_kernel(pd, self.chi0, functional=xc, chi0_wGG=chi0_wGG) K_GG += Kxc_sGG[0] / vsqr_G / vsqr_G[:, np.newaxis] chi_wGG = [] for chi0_GG in chi0_wGG: """v^1/2 chi0 V^1/2""" chi0_GG[:] = chi0_GG * vsqr_G * vsqr_G[:, np.newaxis] chi_GG = np.dot(np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)), chi0_GG) if not return_VchiV: chi0_GG /= vsqr_G * vsqr_G[:, np.newaxis] chi_GG /= vsqr_G * vsqr_G[:, np.newaxis] chi_wGG.append(chi_GG) return pd, chi0_wGG, np.array(chi_wGG)
def calculate_screened_potential(self, ac): """Calculate W_GG(q)""" chi0 = Chi0(self.calc, frequencies=[0.0], eta=0.001, ecut=self.ecut, intraband=False, hilbert=False, nbands=self.nbands, txt='chi0.txt', world=world, ) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs self.Q_qaGii = [] self.W_qGG = [] self.pd_q = [] t0 = time() print('Calculating screened potential', file=self.fd) for iq, q_c in enumerate(self.qd.ibzk_kc): thisqd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, wfs.gd, complex, thisqd) nG = pd.ngmax chi0.Ga = self.blockcomm.rank * nG chi0.Gb = min(chi0.Ga + nG, nG) chi0_wGG = np.zeros((1, nG, nG), complex) if np.allclose(q_c, 0.0): chi0_wxvG = np.zeros((1, 2, 3, nG), complex) chi0_wvv = np.zeros((1, 3, 3), complex) else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, 0, self.nbands, spins='all', extend_head=False) chi0_GG = chi0_wGG[0] # Calculate eps^{-1}_GG if pd.kd.gamma: # Generate fine grid in vicinity of gamma kd = self.calc.wfs.kd N = 4 N_c = np.array([N, N, N]) if self.truncation is not None: # Only average periodic directions if trunction is used N_c[kd.N_c == 1] = 1 qf_qc = monkhorst_pack(N_c) / kd.N_c qf_qc *= 1.0e-6 U_scc = kd.symmetry.op_scc qf_qc = kd.get_ibz_q_points(qf_qc, U_scc)[0] weight_q = kd.q_weights qf_qv = 2 * np.pi * np.dot(qf_qc, pd.gd.icell_cv) a_q = np.sum(np.dot(chi0_wvv[0], qf_qv.T) * qf_qv.T, axis=0) a0_qG = np.dot(qf_qv, chi0_wxvG[0, 0]) a1_qG = np.dot(qf_qv, chi0_wxvG[0, 1]) einv_GG = np.zeros((nG, nG), complex) # W_GG = np.zeros((nG, nG), complex) for iqf in range(len(qf_qv)): chi0_GG[0] = a0_qG[iqf] chi0_GG[:, 0] = a1_qG[iqf] chi0_GG[0, 0] = a_q[iqf] sqrV_G = get_coulomb_kernel(pd, kd.N_c, truncation=self.truncation, wstc=self.wstc, q_v=qf_qv[iqf])**0.5 sqrV_G *= ac**0.5 # Multiply by adiabatic coupling e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] einv_GG += np.linalg.inv(e_GG) * weight_q[iqf] # einv_GG = np.linalg.inv(e_GG) * weight_q[iqf] # W_GG += (einv_GG * sqrV_G * sqrV_G[:, np.newaxis] # * weight_q[iqf]) else: sqrV_G = get_coulomb_kernel(pd, self.kd.N_c, truncation=self.truncation, wstc=self.wstc)**0.5 sqrV_G *= ac**0.5 # Multiply by adiabatic coupling e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] einv_GG = np.linalg.inv(e_GG) # W_GG = einv_GG * sqrV_G * sqrV_G[:, np.newaxis] # Now calculate W_GG if pd.kd.gamma: # Reset bare Coulomb interaction sqrV_G = get_coulomb_kernel(pd, self.kd.N_c, truncation=self.truncation, wstc=self.wstc)**0.5 W_GG = einv_GG * sqrV_G * sqrV_G[:, np.newaxis] if self.integrate_gamma != 0: # Numerical integration of Coulomb interaction at all q-points if self.integrate_gamma == 2: reduced = True else: reduced = False V0, sqrV0 = get_integrated_kernel(pd, self.kd.N_c, truncation=self.truncation, reduced=reduced, N=100) W_GG[0, 0] = einv_GG[0, 0] * V0 W_GG[0, 1:] = einv_GG[0, 1:] * sqrV0 * sqrV_G[1:] W_GG[1:, 0] = einv_GG[1:, 0] * sqrV_G[1:] * sqrV0 elif self.integrate_gamma == 0 and pd.kd.gamma: # Analytical integration at gamma bzvol = (2 * np.pi)**3 / self.vol / self.qd.nbzkpts Rq0 = (3 * bzvol / (4 * np.pi))**(1. / 3.) V0 = 16 * np.pi**2 * Rq0 / bzvol sqrV0 = (4 * np.pi)**(1.5) * Rq0**2 / bzvol / 2 W_GG[0, 0] = einv_GG[0, 0] * V0 W_GG[0, 1:] = einv_GG[0, 1:] * sqrV0 * sqrV_G[1:] W_GG[1:, 0] = einv_GG[1:, 0] * sqrV_G[1:] * sqrV0 else: pass if pd.kd.gamma: e = 1 / einv_GG[0, 0].real print(' RPA dielectric constant is: %3.3f' % e, file=self.fd) self.Q_qaGii.append(chi0.Q_aGii) self.pd_q.append(pd) self.W_qGG.append(W_GG) if iq % (self.qd.nibzkpts // 5 + 1) == 2: dt = time() - t0 tleft = dt * self.qd.nibzkpts / (iq + 1) - dt print(' Finished %s q-points in %s - Estimated %s left' % (iq + 1, timedelta(seconds=round(dt)), timedelta(seconds=round(tleft))), file=self.fd)
def calculate(self, optical=True, ac=1.0): if self.spinors: """Calculate spinors. Here m is index of eigenvalues with SOC and n is the basis of eigenstates withour SOC. Below m is used for unoccupied states and n is used for occupied states so be careful!""" print('Diagonalizing spin-orbit Hamiltonian', file=self.fd) param = self.calc.parameters if not param['symmetry'] == 'off': print('Calculating KS wavefunctions without symmetry ' + 'for spin-orbit', file=self.fd) if not op.isfile('gs_nosym.gpw'): calc_so = GPAW(**param) calc_so.set(symmetry='off', fixdensity=True, txt='gs_nosym.txt') calc_so.atoms = self.calc.atoms calc_so.density = self.calc.density calc_so.get_potential_energy() calc_so.write('gs_nosym.gpw') calc_so = GPAW('gs_nosym.gpw', txt=None, communicator=serial_comm) e_mk, v_knm = get_spinorbit_eigenvalues(calc_so, return_wfs=True, scale=self.scale) del calc_so else: e_mk, v_knm = get_spinorbit_eigenvalues(self.calc, return_wfs=True, scale=self.scale) e_mk /= Hartree # Parallelization stuff nK = self.kd.nbzkpts myKrange, myKsize, mySsize = self.parallelisation_sizes() # Calculate exchange interaction qd0 = KPointDescriptor([self.q_c]) pd0 = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd0) ikq_k = self.kd.find_k_plus_q(self.q_c) v_G = get_coulomb_kernel(pd0, self.kd.N_c, truncation=self.truncation, wstc=self.wstc) if optical: v_G[0] = 0.0 self.pair = PairDensity(self.calc, self.ecut, world=serial_comm, txt='pair.txt') # Calculate direct (screened) interaction and PAW corrections if self.mode == 'RPA': Q_aGii = self.pair.initialize_paw_corrections(pd0) else: self.get_screened_potential(ac=ac) if (self.qd.ibzk_kc - self.q_c < 1.0e-6).all(): iq0 = self.qd.bz2ibz_k[self.kd.where_is_q(self.q_c, self.qd.bzk_kc)] Q_aGii = self.Q_qaGii[iq0] else: Q_aGii = self.pair.initialize_paw_corrections(pd0) # Calculate pair densities, eigenvalues and occupations so = self.spinors + 1 Nv, Nc = so * self.nv, so * self.nc Ns = self.spins rhoex_KsmnG = np.zeros((nK, Ns, Nv, Nc, len(v_G)), complex) # rhoG0_Ksmn = np.zeros((nK, Ns, Nv, Nc), complex) df_Ksmn = np.zeros((nK, Ns, Nv, Nc), float) # -(ev - ec) deps_ksmn = np.zeros((myKsize, Ns, Nv, Nc), float) # -(fv - fc) if np.allclose(self.q_c, 0.0): optical_limit = True else: optical_limit = False get_pair = self.pair.get_kpoint_pair get_rho = self.pair.get_pair_density if self.spinors: # Get all pair densities to allow for SOC mixing # Use twice as many no-SOC states as BSE bands to allow mixing vi_s = [2 * self.val_sn[0, 0] - self.val_sn[0, -1] - 1] vf_s = [2 * self.con_sn[0, -1] - self.con_sn[0, 0] + 2] if vi_s[0] < 0: vi_s[0] = 0 ci_s, cf_s = vi_s, vf_s ni, nf = vi_s[0], vf_s[0] mvi = 2 * self.val_sn[0, 0] mvf = 2 * (self.val_sn[0, -1] + 1) mci = 2 * self.con_sn[0, 0] mcf = 2 * (self.con_sn[0, -1] + 1) else: vi_s, vf_s = self.val_sn[:, 0], self.val_sn[:, -1] + 1 ci_s, cf_s = self.con_sn[:, 0], self.con_sn[:, -1] + 1 for ik, iK in enumerate(myKrange): for s in range(Ns): pair = get_pair(pd0, s, iK, vi_s[s], vf_s[s], ci_s[s], cf_s[s]) m_m = np.arange(vi_s[s], vf_s[s]) n_n = np.arange(ci_s[s], cf_s[s]) if self.gw_skn is not None: iKq = self.calc.wfs.kd.find_k_plus_q(self.q_c, [iK])[0] epsv_m = self.gw_skn[s, iK, :self.nv] epsc_n = self.gw_skn[s, iKq, self.nv:] deps_ksmn[ik] = -(epsv_m[:, np.newaxis] - epsc_n) elif self.spinors: iKq = self.calc.wfs.kd.find_k_plus_q(self.q_c, [iK])[0] epsv_m = e_mk[mvi:mvf, iK] epsc_n = e_mk[mci:mcf, iKq] deps_ksmn[ik, s] = -(epsv_m[:, np.newaxis] - epsc_n) else: deps_ksmn[ik, s] = -pair.get_transition_energies(m_m, n_n) df_mn = pair.get_occupation_differences(self.val_sn[s], self.con_sn[s]) rho_mnG = get_rho(pd0, pair, m_m, n_n, optical_limit=optical_limit, direction=self.direction, Q_aGii=Q_aGii, extend_head=False) if self.spinors: if optical_limit: deps0_mn = -pair.get_transition_energies(m_m, n_n) rho_mnG[:, :, 0] *= deps0_mn df_Ksmn[iK, s, ::2, ::2] = df_mn df_Ksmn[iK, s, ::2, 1::2] = df_mn df_Ksmn[iK, s, 1::2, ::2] = df_mn df_Ksmn[iK, s, 1::2, 1::2] = df_mn vecv0_nm = v_knm[iK][::2][ni:nf, mvi:mvf] vecc0_nm = v_knm[iKq][::2][ni:nf, mci:mcf] rho_0mnG = np.dot(vecv0_nm.T.conj(), np.dot(vecc0_nm.T, rho_mnG)) vecv1_nm = v_knm[iK][1::2][ni:nf, mvi:mvf] vecc1_nm = v_knm[iKq][1::2][ni:nf, mci:mcf] rho_1mnG = np.dot(vecv1_nm.T.conj(), np.dot(vecc1_nm.T, rho_mnG)) rhoex_KsmnG[iK, s] = rho_0mnG + rho_1mnG if optical_limit: rhoex_KsmnG[iK, s, :, :, 0] /= deps_ksmn[ik, s] else: df_Ksmn[iK, s] = pair.get_occupation_differences(m_m, n_n) rhoex_KsmnG[iK, s] = rho_mnG if self.eshift is not None: deps_ksmn[np.where(df_Ksmn[myKrange] > 1.0e-3)] += self.eshift deps_ksmn[np.where(df_Ksmn[myKrange] < -1.0e-3)] -= self.eshift world.sum(df_Ksmn) world.sum(rhoex_KsmnG) self.rhoG0_S = np.reshape(rhoex_KsmnG[:, :, :, :, 0], -1) if hasattr(self, 'H_sS'): return # Calculate Hamiltonian t0 = time() print('Calculating %s matrix elements at q_c = %s' % (self.mode, self.q_c), file=self.fd) H_ksmnKsmn = np.zeros((myKsize, Ns, Nv, Nc, nK, Ns, Nv, Nc), complex) for ik1, iK1 in enumerate(myKrange): for s1 in range(Ns): kptv1 = self.pair.get_k_point(s1, iK1, vi_s[s1], vf_s[s1]) kptc1 = self.pair.get_k_point(s1, ikq_k[iK1], ci_s[s1], cf_s[s1]) rho1_mnG = rhoex_KsmnG[iK1, s1] #rhoG0_Ksmn[iK1, s1] = rho1_mnG[:, :, 0] rho1ccV_mnG = rho1_mnG.conj()[:, :] * v_G for s2 in range(Ns): for Q_c in self.qd.bzk_kc: iK2 = self.kd.find_k_plus_q(Q_c, [kptv1.K])[0] rho2_mnG = rhoex_KsmnG[iK2, s2] rho2_mGn = np.swapaxes(rho2_mnG, 1, 2) H_ksmnKsmn[ik1, s1, :, :, iK2, s2, :, :] += ( np.dot(rho1ccV_mnG, rho2_mGn)) if not self.mode == 'RPA' and s1 == s2: ikq = ikq_k[iK2] kptv2 = self.pair.get_k_point(s1, iK2, vi_s[s1], vf_s[s1]) kptc2 = self.pair.get_k_point(s1, ikq, ci_s[s1], cf_s[s1]) rho3_mmG, iq = self.get_density_matrix(kptv1, kptv2) rho4_nnG, iq = self.get_density_matrix(kptc1, kptc2) if self.spinors: vec0_nm = v_knm[iK1][::2][ni:nf, mvi:mvf] vec1_nm = v_knm[iK1][1::2][ni:nf, mvi:mvf] vec2_nm = v_knm[iK2][::2][ni:nf, mvi:mvf] vec3_nm = v_knm[iK2][1::2][ni:nf, mvi:mvf] rho_0mnG = np.dot(vec0_nm.T.conj(), np.dot(vec2_nm.T, rho3_mmG)) rho_1mnG = np.dot(vec1_nm.T.conj(), np.dot(vec3_nm.T, rho3_mmG)) rho3_mmG = rho_0mnG + rho_1mnG vec0_nm = v_knm[ikq_k[iK1]][::2][ni:nf, mci:mcf] vec1_nm = v_knm[ikq_k[iK1]][1::2][ni:nf,mci:mcf] vec2_nm = v_knm[ikq][::2][ni:nf, mci:mcf] vec3_nm = v_knm[ikq][1::2][ni:nf, mci:mcf] rho_0mnG = np.dot(vec0_nm.T.conj(), np.dot(vec2_nm.T, rho4_nnG)) rho_1mnG = np.dot(vec1_nm.T.conj(), np.dot(vec3_nm.T, rho4_nnG)) rho4_nnG = rho_0mnG + rho_1mnG rho3ccW_mmG = np.dot(rho3_mmG.conj(), self.W_qGG[iq]) W_mmnn = np.dot(rho3ccW_mmG, np.swapaxes(rho4_nnG, 1, 2)) W_mnmn = np.swapaxes(W_mmnn, 1, 2) * Ns * so H_ksmnKsmn[ik1, s1, :, :, iK2, s1] -= 0.5 * W_mnmn if iK1 % (myKsize // 5 + 1) == 0: dt = time() - t0 tleft = dt * myKsize / (iK1 + 1) - dt print(' Finished %s pair orbitals in %s - Estimated %s left' % ((iK1 + 1) * Nv * Nc * Ns * world.size, timedelta(seconds=round(dt)), timedelta(seconds=round(tleft))), file=self.fd) #if self.mode == 'BSE': # del self.Q_qaGii, self.W_qGG, self.pd_q H_ksmnKsmn /= self.vol mySsize = myKsize * Nv * Nc * Ns if myKsize > 0: iS0 = myKrange[0] * Nv * Nc * Ns #world.sum(rhoG0_Ksmn) #self.rhoG0_S = np.reshape(rhoG0_Ksmn, -1) self.df_S = np.reshape(df_Ksmn, -1) if not self.td: self.excludef_S = np.where(np.abs(self.df_S) < 0.001)[0] # multiply by 2 when spin-paired and no SOC self.df_S *= 2.0 / nK / Ns / so self.deps_s = np.reshape(deps_ksmn, -1) H_sS = np.reshape(H_ksmnKsmn, (mySsize, self.nS)) for iS in range(mySsize): # Multiply by occupations and adiabatic coupling H_sS[iS] *= self.df_S[iS0 + iS] * ac # add bare transition energies H_sS[iS, iS0 + iS] += self.deps_s[iS] self.H_sS = H_sS if self.write_h: self.par_save('H_SS.ulm', 'H_SS', self.H_sS)
def get_chi(self, xc='RPA', q_c=[0, 0, 0], spin='all', direction='x', return_VchiV=True, q_v=None, RSrep='gpaw', spinpol_cut=None, density_cut=None, fxc_scaling=None): """ Returns v^1/2 chi v^1/2 for the density response and chi for the spin response. The truncated Coulomb interaction is included as v^-1/2 v_t v^-1/2. This is in order to conform with the head and wings of chi0, which is treated specially for q=0. spin : str or int If 'all' then include all spins. If 0 or 1, only include this specific spin. (not used in transverse reponse functions) RSrep : str real space representation of kernel ('gpaw' or 'grid') spinpol_cut : float cutoff spin polarization below which f_xc is evaluated in unpolarized limit (make sure divergent terms cancel out correctly) density_cut : float cutoff density below which f_xc is set to zero fxc_scaling : list Possible scaling of kernel to hit Goldstone mode. If w=0 is included in the present calculation and fxc_scaling=[True, None], the fxc_scaling to match kappaM_w[0] = 0. will be calculated. If fxc_scaling = [True, float], Kxc will be scaled by float. Default is None, i.e. no scaling """ # XXX generalize to kernel check response = self.chi0.response if response in ['+-', '-+']: assert xc in ('ALDA_x', 'ALDA_X', 'ALDA') pd, chi0_wGG, chi0_wxvG, chi0_wvv = self.calculate_chi0(q_c, spin) if response == 'density': N_c = self.chi0.calc.wfs.kd.N_c Kbare_G = get_coulomb_kernel(pd, N_c, truncation=None, q_v=q_v) vsqr_G = Kbare_G**0.5 nG = len(vsqr_G) if self.truncation is not None: if self.truncation == 'wigner-seitz': self.wstc = WignerSeitzTruncatedCoulomb(pd.gd.cell_cv, N_c) else: self.wstc = None Ktrunc_G = get_coulomb_kernel(pd, N_c, truncation=self.truncation, wstc=self.wstc, q_v=q_v) K_GG = np.diag(Ktrunc_G / Kbare_G) else: K_GG = np.eye(nG, dtype=complex) if pd.kd.gamma: if isinstance(direction, str): d_v = { 'x': [1, 0, 0], 'y': [0, 1, 0], 'z': [0, 0, 1] }[direction] else: d_v = direction d_v = np.asarray(d_v) / np.linalg.norm(d_v) W = slice(self.w1, self.w2) chi0_wGG[:, 0] = np.dot(d_v, chi0_wxvG[W, 0]) chi0_wGG[:, :, 0] = np.dot(d_v, chi0_wxvG[W, 1]) chi0_wGG[:, 0, 0] = np.dot(d_v, np.dot(chi0_wvv[W], d_v).T) if xc != 'RPA': Kxc_GG = get_xc_kernel(pd, self.chi0, functional=xc, chi0_wGG=chi0_wGG, density_cut=density_cut) K_GG += Kxc_GG / vsqr_G / vsqr_G[:, np.newaxis] # Invert Dyson eq. chi_wGG = [] for chi0_GG in chi0_wGG: """v^1/2 chi0 V^1/2""" chi0_GG[:] = chi0_GG * vsqr_G * vsqr_G[:, np.newaxis] chi_GG = np.dot( np.linalg.inv(np.eye(nG) - np.dot(chi0_GG, K_GG)), chi0_GG) if not return_VchiV: chi0_GG /= vsqr_G * vsqr_G[:, np.newaxis] chi_GG /= vsqr_G * vsqr_G[:, np.newaxis] chi_wGG.append(chi_GG) if len(chi_wGG): chi_wGG = np.array(chi_wGG) else: chi_wGG = np.zeros((0, nG, nG), complex) # Spin response else: Kxc_GG = get_xc_kernel(pd, self.chi0, functional=xc, kernel=response[::-1], RSrep=RSrep, chi0_wGG=chi0_wGG, fxc_scaling=fxc_scaling, density_cut=density_cut, spinpol_cut=spinpol_cut) # Invert Dyson equation chi_wGG = [] for chi0_GG in chi0_wGG: chi_GG = np.dot( np.linalg.inv( np.eye(len(chi0_GG)) - np.dot(chi0_GG, Kxc_GG)), chi0_GG) chi_wGG.append(chi_GG) return pd, chi0_wGG, np.array(chi_wGG)