def screened_interaction_kernel(self, iq): """Calcuate W_GG(w) for a given q. if static: return W_GG(w=0) is not static: return W_GG(q,w) - Vc_GG """ q = self.ibzq_qc[iq] w = self.w_w.copy() * Hartree optical_limit = False if np.abs(q).sum() < 1e-8: q = np.array([1e-12, 0, 0]) # arbitrary q, not really need to be calculated optical_limit = True hilbert_trans = True if self.ppa or self.static: hilbert_trans = False df = DF(calc=self.calc, q=q.copy(), w=w, nbands=self.nbands, eshift=None, optical_limit=optical_limit, hilbert_trans=hilbert_trans, xc='RPA', time_ordered=True, rpad=self.rpad, vcut=self.vcut, G_plus_q=True, eta=self.eta * Hartree, ecut=self.ecut.copy() * Hartree, txt='df.out', comm=self.dfcomm, kcommsize=self.kcommsize) df.initialize() df.e_skn = self.e_skn.copy() df.calculate() dfinv_wGG = df.get_inverse_dielectric_matrix(xc='RPA') assert df.ecut[0] == self.ecut[0] if not self.static and not self.ppa: assert df.eta == self.eta assert df.Nw == self.Nw assert df.dw == self.dw # calculate Coulomb kernel and use truncation in 2D delta_GG = np.eye(df.npw) Vq_G = np.diag( calculate_Kc(q, df.Gvec_Gc, self.acell_cv, self.bcell_cv, self.pbc, integrate_gamma=True, N_k=self.kd.N_c, vcut=self.vcut))**0.5 if (self.vcut == '2D' and df.optical_limit) or self.numint: for iG in range(len(df.Gvec_Gc)): if df.Gvec_Gc[iG, 0] == 0 and df.Gvec_Gc[iG, 1] == 0: v_q, v0_q = calculate_Kc_q(self.acell_cv, self.bcell_cv, self.pbc, self.kd.N_c, vcut=self.vcut, q_qc=np.array([q]), Gvec_c=df.Gvec_Gc[iG]) Vq_G[iG] = v_q[0]**0.5 self.Kc_GG = np.outer(Vq_G, Vq_G) if self.ppa: dfinv1_GG = dfinv_wGG[0] - delta_GG dfinv2_GG = dfinv_wGG[1] - delta_GG self.wt_GG = self.E0 * np.sqrt(dfinv2_GG / (dfinv1_GG - dfinv2_GG)) self.R_GG = -self.wt_GG / 2 * dfinv1_GG del dfinv_wGG dfinv_wGG = np.array([1j * pi * self.R_GG + delta_GG]) if self.static: assert len(dfinv_wGG) == 1 W_GG = dfinv_wGG[0] * self.Kc_GG return df, W_GG else: Nw = np.shape(dfinv_wGG)[0] W_wGG = np.zeros_like(dfinv_wGG) for iw in range(Nw): dfinv_wGG[iw] -= delta_GG W_wGG[iw] = dfinv_wGG[iw] * self.Kc_GG return df, W_wGG
def initialize(self): self.printtxt('----------------------------------------') self.printtxt('Bethe-Salpeter Equation calculation') self.printtxt('----------------------------------------') self.printtxt('Started at: %s' % ctime()) self.printtxt('') BASECHI.initialize(self) assert self.nspins == 1 calc = self.calc self.kd = kd = calc.wfs.kd # frequency points init self.dw = self.w_w[1] - self.w_w[0] assert ((self.w_w[1:] - self.w_w[:-1] - self.dw) < 1e-10).all() # make sure its linear w grid assert self.w_w.max() == self.w_w[-1] self.dw /= Hartree self.w_w /= Hartree self.wmax = self.w_w[-1] self.Nw = int(self.wmax / self.dw) + 1 # band init if self.nc is None: nv = self.nvalence / 2 - 1 self.nv = np.array([nv, nv+1]) # conduction band start / end self.nc = np.array([nv+1, nv+2]) # valence band start / end self.printtxt('') self.printtxt('Number of electrons : %d' % (self.nvalence)) self.printtxt('Valence band included : (band %d to band %d)' %(self.nv[0], self.nv[1]-1)) self.printtxt('Conduction band included : (band %d to band %d)' %(self.nc[0], self.nc[1]-1)) if self.eshift is not None: self.printtxt('Scissors operator : %2.3f eV' % self.eshift) self.printtxt('') # find the pair index and initialized pair energy (e_i - e_j) and occupation(f_i-f_j) self.e_S = {} focc_s = {} self.Sindex_S3 = {} iS = 0 kq_k = self.kq_k for k1 in range(self.kd.nbzkpts): ibzkpt1 = kd.bz2ibz_k[k1] ibzkpt2 = kd.bz2ibz_k[kq_k[k1]] for n1 in range(self.nv[0], self.nv[1]): for m1 in range(self.nc[0], self.nc[1]): focc = self.f_skn[0][ibzkpt1,n1] - self.f_skn[0][ibzkpt2,m1] if self.coupling: # Dont use Tamm-Dancoff Approx. check_ftol = np.abs(focc) > self.ftol else: check_ftol = focc > self.ftol if check_ftol: if self.gw_skn is None: self.e_S[iS] = self.e_skn[0][ibzkpt2,m1] - self.e_skn[0][ibzkpt1,n1] else: self.e_S[iS] = self.gw_skn[0][ibzkpt2,m1] - self.gw_skn[0][ibzkpt1,n1] focc_s[iS] = focc self.Sindex_S3[iS] = (k1, n1, m1) iS += 1 self.nS = iS self.focc_S = np.zeros(self.nS) for iS in range(self.nS): self.focc_S[iS] = focc_s[iS] # q points init self.bzq_qc = kd.get_bz_q_points() if not self.qsymm: self.ibzq_qc = self.bzq_qc else: (self.ibzq_qc, self.ibzq_q, self.iop_q, self.timerev_q, self.diff_qc) = kd.get_ibz_q_points(self.bzq_qc, calc.wfs.symmetry.op_scc) if np.abs(self.bzq_qc - kd.bzk_kc).sum() < 1e-8: assert np.abs(self.ibzq_qc - kd.ibzk_kc).sum() < 1e-8 self.nibzq = len(self.ibzq_qc) # Parallel initialization # kcomm and wScomm is only to be used when wavefunctions are distributed in parallel. self.comm = self.Scomm = world self.kcomm = world self.wScomm = serial_comm self.nS, self.nS_local, self.nS_start, self.nS_end = parallel_partition( self.nS, world.rank, world.size, reshape=False) self.print_bse() if calc.input_parameters['mode'] == 'lcao': calc.initialize_positions() # Coulomb interaction at q=0 for Hartree coupling ### 2D z direction only !!!!!!!!!!!!!!!!!!!!!! if self.integrate_coulomb is None: self.integrate_coulomb = [] if self.vcut is None: pass elif self.vcut == '2D': for iG in range(len(self.Gvec_Gc)): if self.Gvec_Gc[iG, 0] == 0 and self.Gvec_Gc[iG, 1] == 0: self.integrate_coulomb.append(iG) else: raise NotImplementedError elif type(self.integrate_coulomb) is int: self.integrate_coulomb = range(self.integrate_coulomb) elif self.integrate_coulomb == 'all': self.integrate_coulomb = range(len(self.Gvec_Gc)) elif type(self.integrate_coulomb) is list: pass else: raise 'Invalid option for integrate_coulomb' self.printtxt('') self.printtxt('Calculating bare Coulomb kernel') if not len(self.integrate_coulomb) == 0: self.printtxt('Integrating Coulomb kernel at %s reciprocal lattice vector(s)' % len(self.integrate_coulomb)) # Coulomb interaction at problematic G's for exchange coupling if len(self.integrate_coulomb) != 0: self.vint_Gq = [] for iG in self.integrate_coulomb: v_q, v0_q = calculate_Kc_q(self.acell_cv, self.bcell_cv, self.pbc, self.kd.N_c, vcut=self.vcut, Gvec_c=self.Gvec_Gc[iG], q_qc=self.ibzq_qc.copy()) self.vint_Gq.append(v_q) if self.print_coulomb: self.printtxt('') self.printtxt('Average kernel relative to bare kernel - \int v(q)dq / v(q0): ') self.printtxt(' G: % s' % self.Gvec_Gc[iG]) for iq in range(len(v_q)): q_s = ' q = [%1.2f, %1.2f, %1.2f]: ' % (self.ibzq_qc[iq,0], self.ibzq_qc[iq,1], self.ibzq_qc[iq,2]) v_rel = v_q[iq] / v0_q[iq] self.printtxt(q_s + '%1.3f' % v_rel) self.printtxt('') self.V_qGG = self.full_bare_interaction() return
def screened_interaction_kernel(self, iq): """Calcuate W_GG(w) for a given q. if static: return W_GG(w=0) is not static: return W_GG(q,w) - Vc_GG """ q = self.ibzq_qc[iq] w = self.w_w.copy()*Hartree optical_limit = False if np.abs(q).sum() < 1e-8: q = np.array([1e-12, 0, 0]) # arbitrary q, not really need to be calculated optical_limit = True hilbert_trans = True if self.ppa or self.static: hilbert_trans = False df = DF(calc=self.calc, q=q.copy(), w=w, nbands=self.nbands, eshift=None, optical_limit=optical_limit, hilbert_trans=hilbert_trans, xc='RPA', time_ordered=True, rpad=self.rpad, vcut=self.vcut, G_plus_q=True, eta=self.eta*Hartree, ecut=self.ecut.copy()*Hartree, txt='df.out', comm=self.dfcomm, kcommsize=self.kcommsize) df.initialize() df.e_skn = self.e_skn.copy() df.calculate() dfinv_wGG = df.get_inverse_dielectric_matrix(xc='RPA') assert df.ecut[0] == self.ecut[0] if not self.static and not self.ppa: assert df.eta == self.eta assert df.Nw == self.Nw assert df.dw == self.dw # calculate Coulomb kernel and use truncation in 2D delta_GG = np.eye(df.npw) Vq_G = np.diag(calculate_Kc(q, df.Gvec_Gc, self.acell_cv, self.bcell_cv, self.pbc, integrate_gamma=True, N_k=self.kd.N_c, vcut=self.vcut))**0.5 if (self.vcut == '2D' and df.optical_limit) or self.numint: for iG in range(len(df.Gvec_Gc)): if df.Gvec_Gc[iG, 0] == 0 and df.Gvec_Gc[iG, 1] == 0: v_q, v0_q = calculate_Kc_q(self.acell_cv, self.bcell_cv, self.pbc, self.kd.N_c, vcut=self.vcut, q_qc=np.array([q]), Gvec_c=df.Gvec_Gc[iG]) Vq_G[iG] = v_q[0]**0.5 self.Kc_GG = np.outer(Vq_G, Vq_G) if self.ppa: dfinv1_GG = dfinv_wGG[0] - delta_GG dfinv2_GG = dfinv_wGG[1] - delta_GG self.wt_GG = self.E0 * np.sqrt(dfinv2_GG / (dfinv1_GG - dfinv2_GG)) self.R_GG = - self.wt_GG / 2 * dfinv1_GG del dfinv_wGG dfinv_wGG = np.array([1j*pi*self.R_GG + delta_GG]) if self.static: assert len(dfinv_wGG) == 1 W_GG = dfinv_wGG[0] * self.Kc_GG return df, W_GG else: Nw = np.shape(dfinv_wGG)[0] W_wGG = np.zeros_like(dfinv_wGG) for iw in range(Nw): dfinv_wGG[iw] -= delta_GG W_wGG[iw] = dfinv_wGG[iw] * self.Kc_GG return df, W_wGG
def initialize(self): self.printtxt('----------------------------------------') self.printtxt('Bethe-Salpeter Equation calculation') self.printtxt('----------------------------------------') self.printtxt('Started at: %s' % ctime()) self.printtxt('') BASECHI.initialize(self) assert self.nspins == 1 calc = self.calc self.kd = kd = calc.wfs.kd # frequency points init self.dw = self.w_w[1] - self.w_w[0] assert ((self.w_w[1:] - self.w_w[:-1] - self.dw) < 1e-10).all() # make sure its linear w grid assert self.w_w.max() == self.w_w[-1] self.dw /= Hartree self.w_w /= Hartree self.wmax = self.w_w[-1] self.Nw = int(self.wmax / self.dw) + 1 # band init if self.nc is None: nv = self.nvalence / 2 - 1 self.nv = np.array([nv, nv + 1]) # conduction band start / end self.nc = np.array([nv + 1, nv + 2]) # valence band start / end self.printtxt('') self.printtxt('Number of electrons : %d' % (self.nvalence)) self.printtxt('Valence band included : (band %d to band %d)' % (self.nv[0], self.nv[1] - 1)) self.printtxt('Conduction band included : (band %d to band %d)' % (self.nc[0], self.nc[1] - 1)) if self.eshift is not None: self.printtxt('Scissors operator : %2.3f eV' % self.eshift) self.printtxt('') # find the pair index and initialized pair energy (e_i - e_j) and occupation(f_i-f_j) self.e_S = {} focc_s = {} self.Sindex_S3 = {} iS = 0 kq_k = self.kq_k for k1 in range(self.kd.nbzkpts): ibzkpt1 = kd.bz2ibz_k[k1] ibzkpt2 = kd.bz2ibz_k[kq_k[k1]] for n1 in range(self.nv[0], self.nv[1]): for m1 in range(self.nc[0], self.nc[1]): focc = self.f_skn[0][ibzkpt1, n1] - self.f_skn[0][ibzkpt2, m1] if self.coupling: # Dont use Tamm-Dancoff Approx. check_ftol = np.abs(focc) > self.ftol else: check_ftol = focc > self.ftol if check_ftol: if self.gw_skn is None: self.e_S[iS] = self.e_skn[0][ ibzkpt2, m1] - self.e_skn[0][ibzkpt1, n1] else: self.e_S[iS] = self.gw_skn[0][ ibzkpt2, m1] - self.gw_skn[0][ibzkpt1, n1] focc_s[iS] = focc self.Sindex_S3[iS] = (k1, n1, m1) iS += 1 self.nS = iS self.focc_S = np.zeros(self.nS) for iS in range(self.nS): self.focc_S[iS] = focc_s[iS] # q points init self.bzq_qc = kd.get_bz_q_points() if not self.qsymm: self.ibzq_qc = self.bzq_qc else: (self.ibzq_qc, self.ibzq_q, self.iop_q, self.timerev_q, self.diff_qc) = kd.get_ibz_q_points(self.bzq_qc, calc.wfs.kd.symmetry.op_scc) if np.abs(self.bzq_qc - kd.bzk_kc).sum() < 1e-8: assert np.abs(self.ibzq_qc - kd.ibzk_kc).sum() < 1e-8 self.nibzq = len(self.ibzq_qc) # Parallel initialization # kcomm and wScomm is only to be used when wavefunctions are distributed in parallel. self.comm = self.Scomm = world self.kcomm = world self.wScomm = serial_comm self.nS, self.nS_local, self.nS_start, self.nS_end = parallel_partition( self.nS, world.rank, world.size, reshape=False) self.print_bse() if calc.input_parameters['mode'] == 'lcao': calc.initialize_positions() # Coulomb interaction at q=0 for Hartree coupling ### 2D z direction only !!!!!!!!!!!!!!!!!!!!!! if self.integrate_coulomb is None: self.integrate_coulomb = [] if self.vcut is None: pass elif self.vcut == '2D': for iG in range(len(self.Gvec_Gc)): if self.Gvec_Gc[iG, 0] == 0 and self.Gvec_Gc[iG, 1] == 0: self.integrate_coulomb.append(iG) else: raise NotImplementedError elif type(self.integrate_coulomb) is int: self.integrate_coulomb = range(self.integrate_coulomb) elif self.integrate_coulomb == 'all': self.integrate_coulomb = range(len(self.Gvec_Gc)) elif type(self.integrate_coulomb) is list: pass else: raise 'Invalid option for integrate_coulomb' self.printtxt('') self.printtxt('Calculating bare Coulomb kernel') if not len(self.integrate_coulomb) == 0: self.printtxt( 'Integrating Coulomb kernel at %s reciprocal lattice vector(s)' % len(self.integrate_coulomb)) # Coulomb interaction at problematic G's for exchange coupling if len(self.integrate_coulomb) != 0: self.vint_Gq = [] for iG in self.integrate_coulomb: v_q, v0_q = calculate_Kc_q(self.acell_cv, self.bcell_cv, self.pbc, self.kd.N_c, vcut=self.vcut, Gvec_c=self.Gvec_Gc[iG], q_qc=self.ibzq_qc.copy()) self.vint_Gq.append(v_q) if self.print_coulomb: self.printtxt('') self.printtxt( 'Average kernel relative to bare kernel - \int v(q)dq / v(q0): ' ) self.printtxt(' G: % s' % self.Gvec_Gc[iG]) for iq in range(len(v_q)): q_s = ' q = [%1.2f, %1.2f, %1.2f]: ' % ( self.ibzq_qc[iq, 0], self.ibzq_qc[iq, 1], self.ibzq_qc[iq, 2]) v_rel = v_q[iq] / v0_q[iq] self.printtxt(q_s + '%1.3f' % v_rel) self.printtxt('') self.V_qGG = self.full_bare_interaction()