def __init__(self, calc, omega_w, ecut=50, hilbert=False, timeordered=False, eta=0.2, ftol=1e-6, real_space_derivatives=False, world=mpi.world, txt=sys.stdout): PairDensity.__init__(self, calc, ecut, ftol, real_space_derivatives, world, txt) eta /= Hartree self.omega_w = omega_w = np.asarray(omega_w) / Hartree self.hilbert = hilbert self.timeordered = bool(timeordered) self.eta = eta if eta == 0.0: assert not hilbert assert not timeordered assert not omega_w.real.any() self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(self.nocc2) self.mykpts = [self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2] wfs = self.calc.wfs self.prefactor = 2 / self.vol / wfs.kd.nbzkpts / wfs.nspins
def __init__(self, calc, frequencies=None, domega0=0.1, omega2=10.0, omegamax=None, ecut=50, hilbert=True, nbands=None, timeordered=False, eta=0.2, ftol=1e-6, threshold=1, real_space_derivatives=False, intraband=True, world=mpi.world, txt=sys.stdout, timer=None, nblocks=1, no_optical_limit=False, keep_occupied_states=False, gate_voltage=None, disable_point_group=False, disable_time_reversal=False, use_more_memory=0, unsymmetrized=True, eshift=None): PairDensity.__init__(self, calc, ecut, ftol, threshold, real_space_derivatives, world, txt, timer, nblocks=nblocks, gate_voltage=gate_voltage, eshift=eshift) self.eta = eta / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree self.omegamax = None if omegamax is None else omegamax / Hartree self.nbands = nbands or self.calc.wfs.bd.nbands self.keep_occupied_states = keep_occupied_states self.intraband = intraband self.no_optical_limit = no_optical_limit self.disable_point_group = disable_point_group self.disable_time_reversal = disable_time_reversal self.use_more_memory = use_more_memory self.unsymmetrized = unsymmetrized omax = self.find_maximum_frequency() if frequencies is None: if self.omegamax is None: self.omegamax = omax print('Using nonlinear frequency grid from 0 to %.3f eV' % (self.omegamax * Hartree), file=self.fd) self.omega_w = frequency_grid(self.domega0, self.omega2, self.omegamax) else: self.omega_w = np.asarray(frequencies) / Hartree assert not hilbert self.hilbert = hilbert self.timeordered = bool(timeordered) if self.eta == 0.0: assert not hilbert assert not timeordered assert not self.omega_w.real.any() # Occupied states: wfs = self.calc.wfs self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(0, self.nocc2, kpts=range(wfs.kd.nibzkpts)) self.mykpts = None self.prefactor = 2 / self.vol / wfs.kd.nbzkpts / wfs.nspins self.chi0_vv = None # strength of intraband peak
def __init__(self, calc, frequencies=None, domega0=0.1, omega2=10.0, omegamax=None, ecut=50, hilbert=True, nbands=None, timeordered=False, eta=0.2, ftol=1e-6, threshold=1, real_space_derivatives=False, intraband=True, world=mpi.world, txt=sys.stdout, timer=None, nblocks=1, no_optical_limit=False, keep_occupied_states=False, gate_voltage=None): PairDensity.__init__(self, calc, ecut, ftol, threshold, real_space_derivatives, world, txt, timer, nblocks=nblocks, gate_voltage=gate_voltage) self.eta = eta / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree self.omegamax = None if omegamax is None else omegamax / Hartree self.nbands = nbands or self.calc.wfs.bd.nbands self.keep_occupied_states = keep_occupied_states self.intraband = intraband self.no_optical_limit = no_optical_limit omax = self.find_maximum_frequency() if frequencies is None: if self.omegamax is None: self.omegamax = omax print('Using nonlinear frequency grid from 0 to %.3f eV' % (self.omegamax * Hartree), file=self.fd) self.omega_w = frequency_grid(self.domega0, self.omega2, self.omegamax) else: self.omega_w = np.asarray(frequencies) / Hartree assert not hilbert self.hilbert = hilbert self.timeordered = bool(timeordered) if self.eta == 0.0: assert not hilbert assert not timeordered assert not self.omega_w.real.any() # Occupied states: self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(0, self.nocc2) self.mykpts = None wfs = self.calc.wfs self.prefactor = 2 / self.vol / wfs.kd.nbzkpts / wfs.nspins self.chi0_vv = None # strength of intraband peak
def update_optical_limit(self, n, m, kpt1, kpt2, deps_m, df_m, n_mG, chi0_wxvG, chi0_wvv): n0_mv = PairDensity.update_optical_limit(self, n, m, kpt1, kpt2, deps_m, df_m, n_mG) if self.hilbert: self.update_optical_limit_hilbert(n0_mv, deps_m, df_m, n_mG, chi0_wxvG, chi0_wvv) return if self.timeordered: # avoid getting a zero from np.sign(): deps1_m = deps_m + 1j * self.eta * np.sign(deps_m + 1e-20) deps2_m = deps1_m else: deps1_m = deps_m + 1j * self.eta deps2_m = deps_m - 1j * self.eta for w, omega in enumerate(self.omega_w): x_m = self.prefactor * df_m * (1 / (omega + deps1_m) - 1 / (omega - deps2_m)) chi0_wvv[w] += np.dot(x_m * n0_mv.T, n0_mv.conj()) chi0_wxvG[w, 0, :, 1:] += np.dot(x_m * n0_mv.T, n_mG[:, 1:].conj()) chi0_wxvG[w, 1, :, 1:] += np.dot(x_m * n0_mv.T.conj(), n_mG[:, 1:])
def get_bz_transitions(filename, q_c, bzk_kc, response='density', spins='all', ecut=50, txt=sys.stdout): """ Get transitions in the Brillouin zone from kpoints bzk_kv contributing to the linear response at wave vector q_c. """ pair = PairDensity(filename, ecut=ecut, response=response, txt=txt) pd = get_pw_descriptor(q_c, pair.calc, pair.ecut) bzk_kv = np.dot(bzk_kc, pd.gd.icell_cv) * 2 * np.pi if spins == 'all': spins = range(pair.calc.wfs.nspins) else: for spin in spins: assert spin in range(pair.calc.wfs.nspins) domain_dl = (bzk_kv, spins) domainsize_d = [len(domain_l) for domain_l in domain_dl] nterms = np.prod(domainsize_d) domainarg_td = [] for t in range(nterms): unravelled_d = np.unravel_index(t, domainsize_d) arg = [] for domain_l, index in zip(domain_dl, unravelled_d): arg.append(domain_l[index]) domainarg_td.append(tuple(arg)) return pair, pd, domainarg_td
def update_intraband(self, kpt, chi0_wvv): """Check whether there are any partly occupied bands.""" width = self.calc.occupations.width if width == 0.0: return assert isinstance(self.calc.occupations, FermiDirac) dfde_m = -1.0 / width * (kpt.f_n - kpt.f_n ** 2.0) partocc_m = np.abs(dfde_m) > 1e-5 if not partocc_m.any(): return # Break bands into degenerate chunks deginds_cm = [] # indexing c as chunk number for m in range(kpt.n2 - kpt.n1): inds_m = np.nonzero(np.abs(kpt.eps_n[m] - kpt.eps_n) < 1e-5)[0] if m == np.min(inds_m) and partocc_m[m]: deginds_cm.append((inds_m)) # Sum over the chunks of degenerate bands for inds_m in deginds_cm: deg = len(inds_m) vel_mmv = -1j * PairDensity.update_intraband(self, inds_m, kpt) vel_mv = np.zeros((deg, 3), dtype=complex) for iv in range(3): w, v = np.linalg.eig(vel_mmv[..., iv]) vel_mv[:, iv] = w for m in range(deg): velm_v = vel_mv[m] x_vv = -self.prefactor * dfde_m[inds_m[m]] * np.outer(velm_v.conj(), velm_v) self.chi0_vv += x_vv
def update_optical_limit(self, n, kpt1, kpt2, deps_m, df_m, n_mG, chi0_wxvG): n0_mv = PairDensity.update_optical_limit(self, n, kpt1, kpt2, deps_m, df_m, n_mG) for w, omega in enumerate(self.omega_w): x_m = (self.prefactor * df_m * (1.0 / (omega + deps_m + 1j * self.eta) - 1.0 / (omega - deps_m + 1j * self.eta))) chi0_wxvG[w, :, :, 0] += np.dot(x_m, n0_mv * n0_mv.conj()) chi0_wxvG[w, 0, :, 1:] += np.dot(x_m * n0_mv.T, n_mG[:, 1:].conj()) chi0_wxvG[w, 1, :, 1:] += np.dot(x_m * n0_mv.T.conj(), n_mG[:, 1:])
def update_intraband(self, kpt): """Check whether there are any partly occupied bands.""" width = self.calc.occupations.width if width == 0.0: return assert isinstance(self.calc.occupations, FermiDirac) dfde_m = -1. / width * (kpt.f_n - kpt.f_n**2.0) partocc_m = np.abs(dfde_m) > 1e-5 if not partocc_m.any(): return # Break bands into degenerate chunks deginds_cm = [] # indexing c as chunk number for m in range(kpt.nb - kpt.na): inds_m = np.nonzero(np.abs(kpt.eps_n[m] - kpt.eps_n) < 1e-5)[0] if m == np.min(inds_m) and partocc_m[m]: deginds_cm.append((inds_m)) # Sum over the chunks of degenerate bands for inds_m in deginds_cm: deg = len(inds_m) vel_mmv = -1j * PairDensity.update_intraband(self, inds_m, kpt) vel_mv = np.zeros((deg, 3), dtype=complex) for iv in range(3): w, v = np.linalg.eig(vel_mmv[..., iv]) vel_mv[:, iv] = w for m in range(deg): velm_v = vel_mv[m] x_vv = (-self.prefactor * dfde_m[inds_m[m]] * np.outer(velm_v.conj(), velm_v)) self.chi0_vv += x_vv
def __init__(self, calc, filename='gw', kpts=None, bands=None, nbands=None, ppa=False, wstc=False, ecut=150.0, eta=0.1, E0=1.0 * Hartree, domega0=0.025, omega2=10.0, world=mpi.world): PairDensity.__init__(self, calc, ecut, world=world, txt=filename + '.txt') self.filename = filename ecut /= Hartree self.ppa = ppa self.wstc = wstc self.eta = eta / Hartree self.E0 = E0 / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree print(' ___ _ _ _ ', file=self.fd) print(' | || | | |', file=self.fd) print(' | | || | | |', file=self.fd) print(' |__ ||_____|', file=self.fd) print(' |___| ', file=self.fd) print(file=self.fd) self.kpts = select_kpts(kpts, self.calc) if bands is None: bands = [0, self.nocc2] self.bands = bands b1, b2 = bands self.shape = shape = (self.calc.wfs.nspins, len(self.kpts), b2 - b1) self.eps_sin = np.empty(shape) # KS-eigenvalues self.f_sin = np.empty(shape) # occupation numbers self.sigma_sin = np.zeros(shape) # self-energies self.dsigma_sin = np.zeros(shape) # derivatives of self-energies self.vxc_sin = None # KS XC-contributions self.exx_sin = None # exact exchange contributions self.Z_sin = None # renormalization factors if nbands is None: nbands = int(self.vol * ecut**1.5 * 2**0.5 / 3 / pi**2) self.nbands = nbands kd = self.calc.wfs.kd self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(b1, b2, kd.ibz2bz_k[self.kpts]) # Find q-vectors and weights in the IBZ: assert -1 not in kd.bz2bz_ks offset_c = 0.5 * ((kd.N_c + 1) % 2) / kd.N_c bzq_qc = monkhorst_pack(kd.N_c) + offset_c self.qd = KPointDescriptor(bzq_qc) self.qd.set_symmetry(self.calc.atoms, self.calc.wfs.setups, usesymm=self.calc.input_parameters.usesymm, N_c=self.calc.wfs.gd.N_c) assert self.calc.wfs.nspins == 1
G = (kpts[ik1] + 1.0 * j * direction[i, :] / nk_size) - kpts[ik2] if j >= 0: G = (kpts[ik1] + 1.0 * (j + 1) * direction[i, :] / nk_size) - kpts[ik2] bG_c = kpts[ik2] - kpts[ik1] + G bG_v = np.dot(bG_c, icell_cv) u2_nG = u_knG[ik2] * np.exp( -1j * np.inner(r_g.T, bG_v).T) S[ik1, i, j, :, :] = get_overlap( calc, bands, np.reshape(u1_nG, (len(u1_nG), Ng)), np.reshape(u2_nG, (len(u2_nG), Ng)), P_kani[ik1], P_kani[ik2], dO_aii, bG_v)[:nb, :nb] np.save(output_directory + case + ".gpaw.S.npy", S) pair = PairDensity(calc=calc) momentum = np.zeros((nkpts, nb, nb, 3), dtype=np.complex) for i in range(nkpts): #k = b1*calc.wfs.kd.bzk_kc[i][0] + b2*calc.wfs.kd.bzk_kc[i][1] +b3*calc.wfs.kd.bzk_kc[i][2] q_c = [0.0, 0.0, 0.0] qd = KPointDescriptor([q_c]) pd = PWDescriptor(pair.ecut, calc.wfs.gd, complex, qd) kptpair = pair.get_kpoint_pair(pd, s=0, K=i, n1=0, n2=nb, m1=0, m2=nb) ol = np.allclose(q_c, 0.0) n_nmvG = pair.get_pair_momentum(pd, kptpair, np.arange(0, nb),
def __init__(self, calc, xc=None, kpts=None, bands=None, ecut=None, omega=None, world=mpi.world, txt=sys.stdout, timer=None): PairDensity.__init__(self, calc, ecut, world=world, txt=txt, timer=timer) if xc is None or xc == 'EXX': self.exx_fraction = 1.0 xc = XC(XCNull()) elif xc == 'PBE0': self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif xc == 'HSE03': omega = 0.106 self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_HSE03') elif xc == 'HSE06': omega = 0.11 self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_HSE06') elif xc == 'B3LYP': self.exx_fraction = 0.2 xc = XC('HYB_GGA_XC_B3LYP') self.xc = xc self.omega = omega self.exc = np.nan # density dependent part of xc-energy self.kpts = select_kpts(kpts, self.calc) if bands is None: # Do all occupied bands: bands = [0, self.nocc2] prnt('Calculating exact exchange contributions for band index', '%d-%d' % (bands[0], bands[1] - 1), file=self.fd) prnt('for IBZ k-points with indices:', ', '.join(str(i) for i in self.kpts), file=self.fd) self.bands = bands if self.ecut is None: self.ecut = self.calc.wfs.pd.ecut prnt('Plane-wave cutoff: %.3f eV' % (self.ecut * Hartree), file=self.fd) shape = (self.calc.wfs.nspins, len(self.kpts), bands[1] - bands[0]) self.exxvv_sin = np.zeros(shape) # valence-valence exchange energies self.exxvc_sin = np.zeros(shape) # valence-core exchange energies self.f_sin = np.empty(shape) # occupation numbers # The total EXX energy will not be calculated if we are only # interested in a few eigenvalues for a few k-points self.exx = np.nan # total EXX energy self.exxvv = np.nan # valence-valence self.exxvc = np.nan # valence-core self.exxcc = 0.0 # core-core self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(0, self.nocc2) # All occupied states: self.mykpts = [self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2] prnt('Using Wigner-Seitz truncated coulomb interaction.', file=self.fd) self.wstc = WignerSeitzTruncatedCoulomb(self.calc.wfs.gd.cell_cv, self.calc.wfs.kd.N_c, self.fd) self.iG_qG = {} # cache # PAW matrices: self.V_asii = [] # valence-valence correction self.C_aii = [] # valence-core correction self.initialize_paw_exx_corrections()
def __init__(self, calc, xc=None, kpts=None, bands=None, ecut=None, omega=None, world=mpi.world, txt=sys.stdout, timer=None): PairDensity.__init__(self, calc, ecut, world=world, txt=txt, timer=timer) if xc is None or xc == 'EXX': self.exx_fraction = 1.0 xc = XC(XCNull()) elif xc == 'PBE0': self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif xc == 'HSE03': omega = 0.106 self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_HSE03') elif xc == 'HSE06': omega = 0.11 self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_HSE06') elif xc == 'B3LYP': self.exx_fraction = 0.2 xc = XC('HYB_GGA_XC_B3LYP') self.xc = xc self.omega = omega self.exc = np.nan # density dependent part of xc-energy self.kpts = select_kpts(kpts, self.calc) if bands is None: # Do all occupied bands: bands = [0, self.nocc2] prnt('Calculating exact exchange contributions for band index', '%d-%d' % (bands[0], bands[1] - 1), file=self.fd) prnt('for IBZ k-points with indices:', ', '.join(str(i) for i in self.kpts), file=self.fd) self.bands = bands if self.ecut is None: self.ecut = self.calc.wfs.pd.ecut prnt('Plane-wave cutoff: %.3f eV' % (self.ecut * Hartree), file=self.fd) shape = (self.calc.wfs.nspins, len(self.kpts), bands[1] - bands[0]) self.exxvv_sin = np.zeros(shape) # valence-valence exchange energies self.exxvc_sin = np.zeros(shape) # valence-core exchange energies self.f_sin = np.empty(shape) # occupation numbers # The total EXX energy will not be calculated if we are only # interested in a few eigenvalues for a few k-points self.exx = np.nan # total EXX energy self.exxvv = np.nan # valence-valence self.exxvc = np.nan # valence-core self.exxcc = 0.0 # core-core self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(0, self.nocc2) # All occupied states: self.mykpts = [ self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2 ] prnt('Using Wigner-Seitz truncated coulomb interaction.', file=self.fd) self.wstc = WignerSeitzTruncatedCoulomb(self.calc.wfs.gd.cell_cv, self.calc.wfs.kd.N_c, self.fd) self.iG_qG = {} # cache # PAW matrices: self.V_asii = [] # valence-valence correction self.C_aii = [] # valence-core correction self.initialize_paw_exx_corrections()
def __init__(self, calc, xc=None, kpts=None, bands=None, ecut=150.0, alpha=0.0, skip_gamma=False, world=mpi.world, txt=sys.stdout): alpha /= Bohr**2 PairDensity.__init__(self, calc, ecut, world=world, txt=txt) ecut /= Hartree if xc is None: self.exx_fraction = 1.0 xc = XC(XCNull()) if xc == 'PBE0': self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif xc == 'B3LYP': self.exx_fraction = 0.2 xc = XC('HYB_GGA_XC_B3LYP') self.xc = xc self.exc = np.nan # density dependent part of xc-energy if kpts is None: # Do all k-points in the IBZ: kpts = range(self.calc.wfs.kd.nibzkpts) if bands is None: # Do all occupied bands: bands = [0, self.nocc2] prnt('Calculating exact exchange contributions for band index', '%d-%d' % (bands[0], bands[1] - 1), file=self.fd) prnt('for IBZ k-points with indices:', ', '.join(str(i) for i in kpts), file=self.fd) self.kpts = kpts self.bands = bands shape = (self.calc.wfs.nspins, len(kpts), bands[1] - bands[0]) self.exxvv_sin = np.zeros(shape) # valence-valence exchange energies self.exxvc_sin = np.zeros(shape) # valence-core exchange energies self.f_sin = np.empty(shape) # occupation numbers # The total EXX energy will not be calculated if we are only # interested in a few eigenvalues for a few k-points self.exx = np.nan # total EXX energy self.exxvv = np.nan # valence-valence self.exxvc = np.nan # valence-core self.exxcc = 0.0 # core-core self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(self.nocc2) # All occupied states: self.mykpts = [self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2] # Compensation charge used for molecular calculations only: self.beta = None # e^(-beta*r^2) self.ngauss_G = None # density self.vgauss_G = None # potential self.G0 = None # effective value for |G+q| when |G+q|=0 self.skip_gamma = skip_gamma if not self.calc.atoms.pbc.any(): # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (self.calc.wfs.gd.icell_cv**2).sum(1).max() prnt('Gaussian for electrostatic decoupling: e^(-beta*r^2),', 'beta=%.3f 1/Ang^2' % (self.beta / Bohr**2), file=self.fd) elif skip_gamma: prnt('Skip |G+q|=0 term', file=self.fd) else: # Volume per q-point: dvq = (2 * pi)**3 / self.vol / self.calc.wfs.kd.nbzkpts qcut = (dvq / (4 * pi / 3))**(1 / 3) if alpha == 0.0: self.G0 = (4 * pi * qcut / dvq)**-0.5 else: self.G0 = (2 * pi**1.5 * erf(alpha**0.5 * qcut) / alpha**0.5 / dvq)**-0.5 prnt('G+q=0 term: Integrate e^(-alpha*q^2)/q^2 for', 'q<%.3f 1/Ang and alpha=%.3f Ang^2' % (qcut / Bohr, alpha * Bohr**2), file=self.fd) # PAW matrices: self.V_asii = [] # valence-valence correction self.C_aii = [] # valence-core correction self.initialize_paw_exx_corrections()
from gpaw.response.pair import PairDensity from gpaw.response.math_func import two_phi_nabla_planewave_integrals np.set_printoptions(precision=1) nb = 6 a = Atoms('H', cell=(3 * np.eye(3)), pbc=True) calc = GPAW(mode=PW(600), kpts=[[0, 0, 0], [0.25, 0, 0]]) a.calc = calc a.get_potential_energy() calc.diagonalize_full_hamiltonian(nbands=nb, expert=True) calc.write('a.gpw', 'all') pair = PairDensity('a.gpw', ecut=100) # Check continuity eq. for q_c in [[0, 0, 0], [1. / 4, 0, 0]]: ol = np.allclose(q_c, 0.0) qd = KPointDescriptor([q_c]) pd = PWDescriptor(pair.ecut, calc.wfs.gd, complex, qd) kptpair = pair.get_kpoint_pair(pd, s=0, K=0, n1=0, n2=nb, m1=0, m2=nb) deps_nm = kptpair.get_transition_energies(np.arange(0, nb), np.arange(0, nb)) n_nmG, n_nmv, _ = pair.get_pair_density(pd, kptpair, np.arange(0, nb), np.arange(0, nb), optical_limit=ol)
class BSE: def __init__(self, calc=None, spinors=False, ecut=10., scale=1.0, nbands=None, valence_bands=None, conduction_bands=None, eshift=None, gw_skn=None, truncation=None, integrate_gamma=1, txt=sys.stdout, mode='BSE', wfile=None, write_h=False, write_v=False): """Creates the BSE object calc: str or calculator object The string should refer to the .gpw file contaning KS orbitals ecut: float Plane wave cutoff energy (eV) nbands: int Number of bands used for the screened interaction valence_bands: list Valence bands used in the BSE Hamiltonian conduction_bands: list Conduction bands used in the BSE Hamiltonian eshift: float Scissors operator opening the gap (eV) gw_skn: list / array List or array defining the gw quasiparticle energies used in the BSE Hamiltonian. Should match spin, k-points and valence/conduction bands truncation: str Coulomb truncation scheme. Can be either wigner-seitz, 2D, 1D, or 0D integrate_gamma: int Method to integrate the Coulomb interaction. 1 is a numerical integration at all q-points with G=[0,0,0] - this breaks the symmetry slightly. 0 is analytical integration at q=[0,0,0] only - this conserves the symmetry. integrate_gamma=2 is the same as 1, but the average is only carried out in the non-periodic directions. txt: str txt output mode: str Theory level used. can be RPA TDHF or BSE. Only BSE is screened. wfile: str File for saving screened interaction and some other stuff needed later write_h: bool If True, write the BSE Hamiltonian to H_SS.ulm. write_v: bool If True, write eigenvalues and eigenstates to v_TS.ulm """ # Calculator if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=serial_comm) self.calc = calc self.spinors = spinors self.scale = scale assert mode in ['RPA', 'TDHF', 'BSE'] # assert calc.wfs.kd.nbzkpts % world.size == 0 # txt file if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w', 1) self.fd = txt self.ecut = ecut / Hartree self.nbands = nbands self.mode = mode self.truncation = truncation if integrate_gamma == 0 and truncation is not None: print('***WARNING*** Analytical Coulomb integration is ' + 'not expected to work with Coulomb truncation. ' + 'Use integrate_gamma=1', file=self.fd) self.integrate_gamma = integrate_gamma self.wfile = wfile self.write_h = write_h self.write_v = write_v # Find q-vectors and weights in the IBZ: self.kd = calc.wfs.kd if -1 in self.kd.bz2bz_ks: print('***WARNING*** Symmetries may not be right ' + 'Use gamma-centered grid to be sure', file=self.fd) offset_c = 0.5 * ((self.kd.N_c + 1) % 2) / self.kd.N_c bzq_qc = monkhorst_pack(self.kd.N_c) + offset_c self.qd = KPointDescriptor(bzq_qc) self.qd.set_symmetry(self.calc.atoms, self.kd.symmetry) self.vol = abs(np.linalg.det(calc.wfs.gd.cell_cv)) # bands self.spins = self.calc.wfs.nspins if self.spins == 2: if self.spinors: self.spinors = False print('***WARNING*** Presently the spinor version' + 'does not work for spin-polarized calculations.' + 'Performing scalar calculation', file=self.fd) assert len(valence_bands[0]) == len(valence_bands[1]) assert len(conduction_bands[0]) == len(conduction_bands[1]) if valence_bands is None: nv = self.calc.wfs.setups.nvalence valence_bands = [[nv // 2 - 1]] if self.spins == 2: valence_bands *= 2 if conduction_bands is None: conduction_bands = [[valence_bands[-1] + 1]] if self.spins == 2: conduction_bands *= 2 self.val_sn = np.array(valence_bands) if len(np.shape(self.val_sn)) == 1: self.val_sn = np.array([self.val_sn]) self.con_sn = np.array(conduction_bands) if len(np.shape(self.con_sn)) == 1: self.con_sn = np.array([self.con_sn]) self.td = True for n in self.val_sn[0]: if n in self.con_sn[0]: self.td = False if len(self.val_sn) == 2: for n in self.val_sn[1]: if n in self.con_sn[1]: self.td = False self.nv = len(self.val_sn[0]) self.nc = len(self.con_sn[0]) if eshift is not None: eshift /= Hartree if gw_skn is not None: assert self.nv + self.nc == len(gw_skn[0, 0]) assert self.kd.nibzkpts == len(gw_skn[0]) gw_skn = gw_skn[:, self.kd.bz2ibz_k] # assert self.kd.nbzkpts == len(gw_skn[0]) gw_skn /= Hartree self.gw_skn = gw_skn self.eshift = eshift # Number of pair orbitals self.nS = self.kd.nbzkpts * self.nv * self.nc * self.spins self.nS *= (self.spinors + 1)**2 # Wigner-Seitz stuff if self.truncation == 'wigner-seitz': self.wstc = WignerSeitzTruncatedCoulomb(self.calc.wfs.gd.cell_cv, self.kd.N_c, self.fd) else: self.wstc = None self.print_initialization(self.td, self.eshift, self.gw_skn) 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_density_matrix(self, kpt1, kpt2): Q_c = self.kd.bzk_kc[kpt2.K] - self.kd.bzk_kc[kpt1.K] iQ = self.qd.where_is_q(Q_c, self.qd.bzk_kc) iq = self.qd.bz2ibz_k[iQ] q_c = self.qd.ibzk_kc[iq] # Find symmetry that transforms Q_c into q_c sym = self.qd.sym_k[iQ] U_cc = self.qd.symmetry.op_scc[sym] time_reversal = self.qd.time_reversal_k[iQ] sign = 1 - 2 * time_reversal d_c = sign * np.dot(U_cc, q_c) - Q_c assert np.allclose(d_c.round(), d_c) pd = self.pd_q[iq] N_c = pd.gd.N_c i_cG = sign * np.dot(U_cc, np.unravel_index(pd.Q_qG[0], N_c)) shift0_c = Q_c - sign * np.dot(U_cc, q_c) assert np.allclose(shift0_c.round(), shift0_c) shift0_c = shift0_c.round().astype(int) shift_c = kpt1.shift_c - kpt2.shift_c - shift0_c I_G = np.ravel_multi_index(i_cG + shift_c[:, None], N_c, 'wrap') G_Gv = pd.get_reciprocal_vectors() pos_ac = self.calc.spos_ac pos_av = np.dot(pos_ac, pd.gd.cell_cv) M_vv = np.dot(pd.gd.cell_cv.T, np.dot(U_cc.T, np.linalg.inv(pd.gd.cell_cv).T)) Q_aGii = [] for a, Q_Gii in enumerate(self.Q_qaGii[iq]): x_G = np.exp(1j * np.dot(G_Gv, (pos_av[a] - np.dot(M_vv, pos_av[a])))) U_ii = self.calc.wfs.setups[a].R_sii[sym] Q_Gii = np.dot(np.dot(U_ii, Q_Gii * x_G[:, None, None]), U_ii.T).transpose(1, 0, 2) if sign == -1: Q_Gii = Q_Gii.conj() Q_aGii.append(Q_Gii) rho_mnG = np.zeros((len(kpt1.eps_n), len(kpt2.eps_n), len(G_Gv)), complex) for m in range(len(rho_mnG)): C1_aGi = [np.dot(Qa_Gii, P1_ni[m].conj()) for Qa_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani)] ut1cc_R = kpt1.ut_nR[m].conj() rho_mnG[m] = self.pair.calculate_pair_densities(ut1cc_R, C1_aGi, kpt2, pd, I_G) return rho_mnG, iq def get_screened_potential(self, ac=1.0): if hasattr(self, 'W_qGG'): return if self.wfile is not None: # Read screened potential from file try: data = np.load(self.wfile + '.npz') self.Q_qaGii = data['Q'] self.W_qGG = data['W'] self.pd_q = data['pd'] print('Reading screened potential from % s' % self.wfile, file=self.fd) except: self.calculate_screened_potential(ac) print('Saving screened potential to % s' % self.wfile, file=self.fd) if world.rank == 0: np.savez(self.wfile, Q=self.Q_qaGii, pd=self.pd_q, W=self.W_qGG) else: self.calculate_screened_potential(ac) 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 diagonalize(self): print('Diagonalizing Hamiltonian', file=self.fd) """The t and T represent local and global eigenstates indices respectively """ # Non-Hermitian matrix can only use linalg.eig if not self.td: print(' Using numpy.linalg.eig...', file=self.fd) print(' Eliminated %s pair orbitals' % len(self.excludef_S), file=self.fd) self.H_SS = self.collect_A_SS(self.H_sS) self.w_T = np.zeros(self.nS - len(self.excludef_S), complex) if world.rank == 0: self.H_SS = np.delete(self.H_SS, self.excludef_S, axis=0) self.H_SS = np.delete(self.H_SS, self.excludef_S, axis=1) self.w_T, self.v_ST = np.linalg.eig(self.H_SS) world.broadcast(self.w_T, 0) self.df_S = np.delete(self.df_S, self.excludef_S) self.rhoG0_S = np.delete(self.rhoG0_S, self.excludef_S) # Here the eigenvectors are returned as complex conjugated rows else: if world.size == 1: print(' Using lapack...', file=self.fd) from gpaw.utilities.lapack import diagonalize self.w_T = np.zeros(self.nS) diagonalize(self.H_sS, self.w_T) self.v_St = self.H_sS.conj().T else: print(' Using scalapack...', file=self.fd) nS = self.nS ns = -(-self.kd.nbzkpts // world.size) * (self.nv * self.nc * self.spins * (self.spinors + 1)**2) grid = BlacsGrid(world, world.size, 1) desc = grid.new_descriptor(nS, nS, ns, nS) desc2 = grid.new_descriptor(nS, nS, 2, 2) H_tmp = desc2.zeros(dtype=complex) r = Redistributor(world, desc, desc2) r.redistribute(self.H_sS, H_tmp) self.w_T = np.empty(nS) v_tmp = desc2.empty(dtype=complex) desc2.diagonalize_dc(H_tmp, v_tmp, self.w_T) r = Redistributor(grid.comm, desc2, desc) self.v_St = desc.zeros(dtype=complex) r.redistribute(v_tmp, self.v_St) self.v_St = self.v_St.conj().T if self.write_v and self.td: # Cannot use par_save without td self.par_save('v_TS.ulm', 'v_TS', self.v_St.T) return def get_bse_matrix(self, q_c=[0.0, 0.0, 0.0], direction=0, ac=1.0, readfile=None, optical=True, write_eig=None): """Calculate and diagonalize BSE matrix""" self.q_c = q_c self.direction = direction if readfile is None: self.calculate(optical=optical, ac=ac) if hasattr(self, 'w_T'): return self.diagonalize() elif readfile == 'H_SS': print('Reading Hamiltonian from file', file=self.fd) self.par_load('H_SS.ulm', 'H_SS') self.diagonalize() elif readfile == 'v_TS': print('Reading eigenstates from file', file=self.fd) self.par_load('v_TS.ulm', 'v_TS') else: raise ValueError('%s array not recognized' % readfile) # TODO: Move write_eig here return def get_vchi(self, w_w=None, eta=0.1, q_c=[0.0, 0.0, 0.0], direction=0, ac=1.0, readfile=None, optical=True, write_eig=None): """Returns v * \chi where v is the bare Coulomb interaction""" self.get_bse_matrix(q_c=q_c, direction=direction, ac=ac, readfile=readfile, optical=optical, write_eig=write_eig) w_T = self.w_T rhoG0_S = self.rhoG0_S df_S = self.df_S print('Calculating response function at %s frequency points' % len(w_w), file=self.fd) vchi_w = np.zeros(len(w_w), dtype=complex) if not self.td: C_T = np.zeros(self.nS - len(self.excludef_S), complex) if world.rank == 0: A_T = np.dot(rhoG0_S, self.v_ST) B_T = np.dot(rhoG0_S * df_S, self.v_ST) tmp = np.dot(self.v_ST.conj().T, self.v_ST) overlap_tt = np.linalg.inv(tmp) C_T = np.dot(B_T.conj(), overlap_tt.T) * A_T world.broadcast(C_T, 0) else: A_t = np.dot(rhoG0_S, self.v_St) B_t = np.dot(rhoG0_S * df_S, self.v_St) if world.size == 1: C_T = B_t.conj() * A_t else: Nv = self.nv * (self.spinors + 1) Nc = self.nc * (self.spinors + 1) Ns = self.spins nS = self.nS ns = -(-self.kd.nbzkpts // world.size) * Nv * Nc * Ns grid = BlacsGrid(world, world.size, 1) desc = grid.new_descriptor(nS, 1, ns, 1) C_t = desc.empty(dtype=complex) C_t[:, 0] = B_t.conj() * A_t C_T = desc.collect_on_master(C_t)[:, 0] if world.rank != 0: C_T = np.empty(nS, dtype=complex) world.broadcast(C_T, 0) eta /= Hartree for iw, w in enumerate(w_w / Hartree): tmp_T = 1. / (w - w_T + 1j * eta) vchi_w[iw] += np.dot(tmp_T, C_T) vchi_w *= 4 * np.pi / self.vol if not np.allclose(self.q_c, 0.0): cell_cv = self.calc.wfs.gd.cell_cv B_cv = 2 * np.pi * np.linalg.inv(cell_cv).T q_v = np.dot(q_c, B_cv) vchi_w /= np.dot(q_v, q_v) """Check f-sum rule.""" nv = self.calc.wfs.setups.nvalence dw_w = (w_w[1:] - w_w[:-1]) / Hartree wchi_w = (w_w[1:] * vchi_w[1:] + w_w[:-1] * vchi_w[:-1]) / Hartree / 2 N = -np.dot(dw_w, wchi_w.imag) * self.vol / (2 * np.pi**2) print(file=self.fd) print('Checking f-sum rule:', file=self.fd) print(' Valence = %s, N = %f' % (nv, N), file=self.fd) print(file=self.fd) if write_eig is not None: if world.rank == 0: f = open(write_eig, 'w') print('# %s eigenvalues in eV' % self.mode, file=f) for iw, w in enumerate(self.w_T * Hartree): print('%8d %12.6f %12.16f' % (iw, w.real, C_T[iw].real), file=f) f.close() return vchi_w * ac def get_dielectric_function(self, w_w=None, eta=0.1, q_c=[0.0, 0.0, 0.0], direction=0, filename='df_bse.csv', readfile=None, write_eig='eig.dat'): """Returns and writes real and imaginary part of the dielectric function. w_w: list of frequencies (eV) Dielectric function is calculated at these frequencies eta: float Lorentzian broadening of the spectrum (eV) q_c: list of three floats Wavevector in reduced units on which the response is calculated direction: int if q_c = [0, 0, 0] this gives the direction in cartesian coordinates - 0=x, 1=y, 2=z filename: str data file on which frequencies, real and imaginary part of dielectric function is written readfile: str If H_SS is given, the method will load the BSE Hamiltonian from H_SS.ulm. If v_TS is given, the method will load the eigenstates from v_TS.ulm write_eig: str File on which the BSE eigenvalues are written """ epsilon_w = -self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction, readfile=readfile, optical=True, write_eig=write_eig) epsilon_w += 1.0 if world.rank == 0 and filename is not None: f = open(filename, 'w') for iw, w in enumerate(w_w): print('%.9f, %.9f, %.9f' % (w, epsilon_w[iw].real, epsilon_w[iw].imag), file=f) f.close() world.barrier() print('Calculation completed at:', ctime(), file=self.fd) print(file=self.fd) return w_w, epsilon_w def get_eels_spectrum(self, w_w=None, eta=0.1, q_c=[0.0, 0.0, 0.0], direction=0, filename='df_bse.csv', readfile=None, write_eig='eig.dat'): """Returns and writes real and imaginary part of the dielectric function. w_w: list of frequencies (eV) Dielectric function is calculated at these frequencies eta: float Lorentzian broadening of the spectrum (eV) q_c: list of three floats Wavevector in reduced units on which the response is calculated direction: int if q_c = [0, 0, 0] this gives the direction in cartesian coordinates - 0=x, 1=y, 2=z filename: str data file on which frequencies, real and imaginary part of dielectric function is written readfile: str If H_SS is given, the method will load the BSE Hamiltonian from H_SS.ulm. If v_TS is given, the method will load the eigenstates from v_TS.ulm write_eig: str File on which the BSE eigenvalues are written """ eels_w = -self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction, readfile=readfile, optical=False, write_eig=write_eig).imag if world.rank == 0 and filename is not None: f = open(filename, 'w') for iw, w in enumerate(w_w): print('%.9f, %.9f' % (w, eels_w[iw]), file=f) f.close() world.barrier() print('Calculation completed at:', ctime(), file=self.fd) print(file=self.fd) return w_w, eels_w def get_polarizability(self, w_w=None, eta=0.1, q_c=[0.0, 0.0, 0.0], direction=0, filename='pol_bse.csv', readfile=None, pbc=None, write_eig='eig.dat'): """Calculate the polarizability alpha. In 3D the imaginary part of the polarizability is related to the dielectric function by Im(eps_M) = 4 pi * Im(alpha). In systems with reduced dimensionality the converged value of alpha is independent of the cell volume. This is not the case for eps_M, which is ill defined. A truncated Coulomb kernel will always give eps_M = 1.0, whereas the polarizability maintains its structure. pbs should be a list of booleans giving the periodic directions. By default, generate a file 'pol_bse.csv'. The three colomns are: frequency (eV), Real(alpha), Imag(alpha). The dimension of alpha is \AA to the power of non-periodic directions. """ cell_cv = self.calc.wfs.gd.cell_cv if not pbc: pbc_c = self.calc.atoms.pbc else: pbc_c = np.array(pbc) if pbc_c.all(): V = 1.0 else: V = np.abs(np.linalg.det(cell_cv[~pbc_c][:, ~pbc_c])) if self.truncation is None: optical = True else: optical = False vchi_w = self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction, readfile=readfile, optical=optical, write_eig=write_eig) alpha_w = -V * vchi_w / (4 * np.pi) alpha_w *= Bohr**(sum(~pbc_c)) if world.rank == 0 and filename is not None: fd = open(filename, 'w') for iw, w in enumerate(w_w): print('%.9f, %.9f, %.9f' % (w, alpha_w[iw].real, alpha_w[iw].imag), file=fd) fd.close() print('Calculation completed at:', ctime(), file=self.fd) print(file=self.fd) return w_w, alpha_w def get_2d_absorption(self, w_w=None, eta=0.1, q_c=[0.0, 0.0, 0.0], direction=0, filename='abs_bse.csv', readfile=None, pbc=None, write_eig='eig.dat'): """Calculate the dimensionless absorption for 2d materials. It is essentially related to the 2D polarizability \alpha_2d as ABS = 4 * np.pi * \omega * \alpha_2d / c where c is the velocity of light """ from ase.units import alpha c = 1.0 / alpha cell_cv = self.calc.wfs.gd.cell_cv if not pbc: pbc_c = self.calc.atoms.pbc else: pbc_c = np.array(pbc) assert np.sum(pbc_c) == 2 V = np.abs(np.linalg.det(cell_cv[~pbc_c][:, ~pbc_c])) vchi_w = self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction, readfile=readfile, optical=True, write_eig=write_eig) abs_w = -V * vchi_w.imag * w_w / Hartree / c if world.rank == 0 and filename is not None: fd = open(filename, 'w') for iw, w in enumerate(w_w): print('%.9f, %.9f' % (w, abs_w[iw]), file=fd) fd.close() print('Calculation completed at:', ctime(), file=self.fd) print(file=self.fd) return w_w, abs_w def par_save(self, filename, name, A_sS): import ase.io.ulm as ulm if world.size == 1: A_XS = A_sS else: A_XS = self.collect_A_SS(A_sS) if world.rank == 0: w = ulm.open(filename, 'w') if name == 'v_TS': w.write(w_T=self.w_T) # w.write(nS=self.nS) w.write(rhoG0_S=self.rhoG0_S) w.write(df_S=self.df_S) w.write(A_XS=A_XS) w.close() world.barrier() def par_load(self, filename, name): import ase.io.ulm as ulm if world.rank == 0: r = ulm.open(filename, 'r') if name == 'v_TS': self.w_T = r.w_T self.rhoG0_S = r.rhoG0_S self.df_S = r.df_S A_XS = r.A_XS r.close() else: if name == 'v_TS': self.w_T = np.zeros((self.nS), dtype=float) self.rhoG0_S = np.zeros((self.nS), dtype=complex) self.df_S = np.zeros((self.nS), dtype=float) A_XS = None world.broadcast(self.rhoG0_S, 0) world.broadcast(self.df_S, 0) if name == 'H_SS': self.H_sS = self.distribute_A_SS(A_XS) if name == 'v_TS': world.broadcast(self.w_T, 0) self.v_St = self.distribute_A_SS(A_XS, transpose=True) def collect_A_SS(self, A_sS): if world.rank == 0: A_SS = np.zeros((self.nS, self.nS), dtype=complex) A_SS[:len(A_sS)] = A_sS Ntot = len(A_sS) for rank in range(1, world.size): nkr, nk, ns = self.parallelisation_sizes(rank) buf = np.empty((ns, self.nS), dtype=complex) world.receive(buf, rank, tag=123) A_SS[Ntot:Ntot + ns] = buf Ntot += ns else: world.send(A_sS, 0, tag=123) world.barrier() if world.rank == 0: return A_SS def distribute_A_SS(self, A_SS, transpose=False): if world.rank == 0: for rank in range(0, world.size): nkr, nk, ns = self.parallelisation_sizes(rank) if rank == 0: A_sS = A_SS[0:ns] Ntot = ns else: world.send(A_SS[Ntot:Ntot + ns], rank, tag=123) Ntot += ns else: nkr, nk, ns = self.parallelisation_sizes() A_sS = np.empty((ns, self.nS), dtype=complex) world.receive(A_sS, 0, tag=123) world.barrier() if transpose: A_sS = A_sS.T return A_sS def parallelisation_sizes(self, rank=None): if rank is None: rank = world.rank nK = self.kd.nbzkpts myKsize = -(-nK // world.size) myKrange = range(rank * myKsize, min((rank + 1) * myKsize, nK)) myKsize = len(myKrange) mySsize = myKsize * self.nv * self.nc * self.spins mySsize *= (1 + self.spinors)**2 return myKrange, myKsize, mySsize def get_bse_wf(self): pass # asd = 1.0 def print_initialization(self, td, eshift, gw_skn): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('%s Hamiltonian' % self.mode) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Spinor calculations :', self.spinors) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', self.kd.nbzkpts) p('Number of irreducible k-points :', self.kd.nibzkpts) p('Number of q-points :', self.qd.nbzkpts) p('Number of irreducible q-points :', self.qd.nibzkpts) p() for q in self.qd.ibzk_kc: p(' q: [%1.4f %1.4f %1.4f]' % (q[0], q[1], q[2])) p() if gw_skn is not None: p('User specified BSE bands') p('Response PW cutoff :', self.ecut * Hartree, 'eV') p('Screening bands included :', self.nbands) if len(self.val_sn) == 1: p('Valence bands :', self.val_sn[0]) p('Conduction bands :', self.con_sn[0]) else: p('Valence bands :', self.val_sn[0],self.val_sn[1]) p('Conduction bands :', self.con_sn[0],self.con_sn[1]) if eshift is not None: p('Scissors operator :', eshift * Hartree, 'eV') p('Tamm-Dancoff approximation :', td) p('Number of pair orbitals :', self.nS) p() p('Truncation of Coulomb kernel :', self.truncation) if self.integrate_gamma == 0: p('Coulomb integration scheme :', 'Analytical - gamma only') elif self.integrate_gamma == 1: p('Coulomb integration scheme :', 'Numerical - all q-points') else: pass p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() p('Parallelization - Total number of CPUs : % s' % world.size) p(' Screened potential') p(' K-point/band decomposition : % s' % world.size) p(' Hamiltonian') p(' Pair orbital decomposition : % s' % world.size) p()
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 __init__(self, calc, gwfile, filename=None, kpts=[0], bands=None, structure=None, d=None, layer=0, dW_qw=None, qqeh=None, wqeh=None, txt=sys.stdout, world=mpi.world, domega0=0.025, omega2=10.0, eta=0.1, include_q0=True, metal=False): """ Class for calculating quasiparticle energies of van der Waals heterostructures using the GW approximation for the self-energy. The quasiparticle energy correction due to increased screening from surrounding layers is obtained from the QEH model. Parameters: calc: str or PAW object GPAW calculator object or filename of saved calculator object. gwfile: str name of gw results file from the monolayer calculation filename: str filename for gwqeh output kpts: list List of indices of sthe IBZ k-points to calculate the quasi particle energies for. Set to [0] by default since the QP correction is generally the same for all k. bands: tuple Range of band indices, like (n1, n2+1), to calculate the quasi particle energies for. Note that the second band index is not included. Should be the same as used for the GW calculation. structure: list of str Heterostructure set up. Each entry should consist of number of layers + chemical formula. For example: ['3H-MoS2', graphene', '10H-WS2'] gives 3 layers of H-MoS2, 1 layer of graphene and 10 layers of H-WS2. The name of the layers should correspond to building block files: "<name>-chi.pckl" in the local repository. d: array of floats Interlayer distances for neighboring layers in Ang. Length of array = number of layers - 1 layer: int index of layer to calculate QP correction for. dW_qw: 2D array of floats dimension q X w Change in screened interaction. Should be set to None to calculate dW directly from buildingblocks. qqeh: array of floats q-grid used for dW_qw (only needed if dW is given by hand). wqeh: array of floats w-grid used for dW_qw. So far this have to be the same as for the GWQEH calculation. (only needed if dW is given by hand). domega0: float Minimum frequency step (in eV) used in the generation of the non- linear frequency grid. omega2: float Control parameter for the non-linear frequency grid, equal to the frequency where the grid spacing has doubled in size. eta: float Broadening parameter. include_q0: bool include q=0 in W or not. if True an integral arround q=0 is performed, if False the q=0 contribution is set to zero. metal: bool If True, the point at q=0 is omitted when averaging the screened potential close to q=0. """ self.gwfile = gwfile self.inputcalc = calc # Set low ecut in order to use PairDensity object since only # G=0 is needed. self.ecut = 1. PairDensity.__init__(self, calc, ecut=self.ecut, world=world, txt=filename + '.txt') self.filename = filename self.ecut /= Hartree self.eta = eta / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree self.kpts = list(select_kpts(kpts, self.calc)) if bands is None: bands = [0, self.nocc2] self.bands = bands b1, b2 = bands self.shape = shape = (self.calc.wfs.nspins, len(self.kpts), b2 - b1) self.eps_sin = np.empty(shape) # KS-eigenvalues self.f_sin = np.empty(shape) # occupation numbers self.sigma_sin = np.zeros(shape) # self-energies self.dsigma_sin = np.zeros(shape) # derivatives of self-energies self.Z_sin = None # renormalization factors self.qp_sin = None self.Qp_sin = None self.ecutnb = 150 / Hartree vol = abs(np.linalg.det(self.calc.wfs.gd.cell_cv)) self.vol = vol self.nbands = min(self.calc.get_number_of_bands(), int(vol * (self.ecutnb)**1.5 * 2**0.5 / 3 / pi**2)) self.nspins = self.calc.wfs.nspins kd = self.calc.wfs.kd self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(b1, b2, kd.ibz2bz_k[self.kpts]) # Find q-vectors and weights in the IBZ: assert -1 not in kd.bz2bz_ks offset_c = 0.5 * ((kd.N_c + 1) % 2) / kd.N_c bzq_qc = monkhorst_pack(kd.N_c) + offset_c self.qd = KPointDescriptor(bzq_qc) self.qd.set_symmetry(self.calc.atoms, kd.symmetry) # frequency grid omax = self.find_maximum_frequency() self.omega_w = frequency_grid(self.domega0, self.omega2, omax) self.nw = len(self.omega_w) self.wsize = 2 * self.nw # Calculate screened potential of Heterostructure if dW_qw is None: try: self.qqeh, self.wqeh, dW_qw = pickle.load( open(filename + '_dW_qw.pckl', 'rb')) except: dW_qw = self.calculate_W_QEH(structure, d, layer) else: self.qqeh = qqeh self.wqeh = None # wqeh self.dW_qw = self.get_W_on_grid(dW_qw, include_q0=include_q0, metal=metal) assert self.nw == self.dW_qw.shape[1], \ ('Frequency grids doesnt match!') self.htp = HilbertTransform(self.omega_w, self.eta, gw=True) self.htm = HilbertTransform(self.omega_w, -self.eta, gw=True) self.complete = False self.nq = 0 if self.load_state_file(): if self.complete: print('Self-energy loaded from file', file=self.fd)
class Chi0: """Class for calculating non-interacting response functions.""" def __init__(self, calc, response='density', frequencies=None, domega0=0.1, omega2=10.0, omegamax=None, ecut=50, gammacentered=False, hilbert=True, nbands=None, timeordered=False, eta=0.2, ftol=1e-6, threshold=1, real_space_derivatives=False, intraband=True, world=mpi.world, txt='-', timer=None, nblocks=1, gate_voltage=None, disable_point_group=False, disable_time_reversal=False, disable_non_symmorphic=True, scissor=None, integrationmode=None, pbc=None, rate=0.0, eshift=0.0): """Construct Chi0 object. Parameters ---------- calc : str The groundstate calculation file that the linear response calculation is based on. response : str Type of response function. Currently collinear, scalar options 'density', '+-' and '-+' are implemented. frequencies : ndarray or None Array of frequencies to evaluate the response function at. If None, frequencies are determined using the frequency_grid function in gpaw.response.chi0. domega0, omega2, omegamax : float Input parameters for frequency_grid. ecut : float Energy cutoff. gammacentered : bool Center the grid of plane waves around the gamma point or q-vector hilbert : bool Switch for hilbert transform. If True, the full density response is determined from a hilbert transform of its spectral function. This is typically much faster, but does not work for imaginary frequencies. nbands : int Maximum band index to include. timeordered : bool Switch for calculating the time ordered density response function. In this case the hilbert transform cannot be used. eta : float Artificial broadening of spectra. ftol : float Threshold determining whether a band is completely filled (f > 1 - ftol) or completely empty (f < ftol). threshold : float Numerical threshold for the optical limit k dot p perturbation theory expansion (used in gpaw/response/pair.py). real_space_derivatives : bool Switch for calculating nabla matrix elements (in the optical limit) using a real space finite difference approximation. intraband : bool Switch for including the intraband contribution to the density response function. world : MPI comm instance MPI communicator. txt : str Output file. timer : gpaw.utilities.timing.timer instance nblocks : int Divide the response function into nblocks. Useful when the response function is large. gate_voltage : float Shift the fermi level by gate_voltage [Hartree]. disable_point_group : bool Do not use the point group symmetry operators. disable_time_reversal : bool Do not use time reversal symmetry. disable_non_symmorphic : bool Do no use non symmorphic symmetry operators. scissor : tuple ([bands], shift [eV]) Use scissor operator on bands. integrationmode : str Integrator for the kpoint integration. If == 'tetrahedron integration' then the kpoint integral is performed using the linear tetrahedron method. pbc : list Periodic directions of the system. Defaults to [True, True, True]. eshift : float Shift unoccupied bands Attributes ---------- pair : gpaw.response.pair.PairDensity instance Class for calculating matrix elements of pairs of wavefunctions. """ self.response = response self.timer = timer or Timer() self.pair = PairDensity(calc, ecut, self.response, ftol, threshold, real_space_derivatives, world, txt, self.timer, nblocks=nblocks, gate_voltage=gate_voltage) self.disable_point_group = disable_point_group self.disable_time_reversal = disable_time_reversal self.disable_non_symmorphic = disable_non_symmorphic self.integrationmode = integrationmode self.eshift = eshift / Hartree calc = self.pair.calc self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt self.vol = abs(np.linalg.det(calc.wfs.gd.cell_cv)) self.world = world if nblocks == 1: self.blockcomm = self.world.new_communicator([world.rank]) self.kncomm = world else: assert world.size % nblocks == 0, world.size rank1 = world.rank // nblocks * nblocks rank2 = rank1 + nblocks self.blockcomm = self.world.new_communicator(range(rank1, rank2)) ranks = range(world.rank % nblocks, world.size, nblocks) self.kncomm = self.world.new_communicator(ranks) self.nblocks = nblocks if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt if ecut is not None: ecut /= Hartree self.ecut = ecut self.gammacentered = gammacentered self.eta = eta / Hartree if rate == 'eta': self.rate = self.eta else: self.rate = rate / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree self.omegamax = None if omegamax is None else omegamax / Hartree self.nbands = nbands or self.calc.wfs.bd.nbands self.include_intraband = intraband omax = self.find_maximum_frequency() if frequencies is None: if self.omegamax is None: self.omegamax = omax print('Using nonlinear frequency grid from 0 to %.3f eV' % (self.omegamax * Hartree), file=self.fd) self.wd = FrequencyDescriptor(self.domega0, self.omega2, self.omegamax) else: self.wd = ArrayDescriptor(np.asarray(frequencies) / Hartree) assert not hilbert self.omega_w = self.wd.get_data() self.hilbert = hilbert self.timeordered = bool(timeordered) if self.eta == 0.0: assert not hilbert assert not timeordered assert not self.omega_w.real.any() self.nocc1 = self.pair.nocc1 # number of completely filled bands self.nocc2 = self.pair.nocc2 # number of non-empty bands self.Q_aGii = None if pbc is not None: self.pbc = np.array(pbc) else: self.pbc = np.array([True, True, True]) if self.pbc is not None and (~self.pbc).any(): assert np.sum((~self.pbc).astype(int)) == 1, \ print('Only one non-periodic direction supported atm.') print('Nonperiodic BC\'s: ', (~self.pbc), file=self.fd) if integrationmode is not None: print('Using integration method: ' + self.integrationmode, file=self.fd) else: print('Using integration method: PointIntegrator', file=self.fd) def find_maximum_frequency(self): """Determine the maximum electron-hole pair transition energy.""" self.epsmin = 10000.0 self.epsmax = -10000.0 for kpt in self.calc.wfs.kpt_u: self.epsmin = min(self.epsmin, kpt.eps_n[0]) self.epsmax = max(self.epsmax, kpt.eps_n[self.nbands - 1]) print('Minimum eigenvalue: %10.3f eV' % (self.epsmin * Hartree), file=self.fd) print('Maximum eigenvalue: %10.3f eV' % (self.epsmax * Hartree), file=self.fd) return self.epsmax - self.epsmin def calculate(self, q_c, spin='all', A_x=None): """Calculate response function. Parameters ---------- q_c : list or ndarray Momentum vector. 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) A_x : ndarray Output array. If None, the output array is created. Returns ------- pd : Planewave descriptor Planewave descriptor for q_c. chi0_wGG : ndarray The response function. chi0_wxvG : ndarray or None (Only in optical limit) Wings of the density response function. chi0_wvv : ndarray or None (Only in optical limit) Head of the density response function. """ wfs = self.calc.wfs if self.response == 'density': if spin == 'all': spins = range(wfs.nspins) else: assert spin in range(wfs.nspins) spins = [spin] else: if self.response == '+-': spins = [0] elif self.response == '-+': spins = [1] else: raise ValueError('Invalid response %s' % self.response) q_c = np.asarray(q_c, dtype=float) optical_limit = np.allclose(q_c, 0.0) and self.response == 'density' pd = self.get_PWDescriptor(q_c, self.gammacentered) self.print_chi(pd) if extra_parameters.get('df_dry_run'): print(' Dry run exit', file=self.fd) raise SystemExit nG = pd.ngmax + 2 * optical_limit nw = len(self.omega_w) mynG = (nG + self.blockcomm.size - 1) // self.blockcomm.size self.Ga = min(self.blockcomm.rank * mynG, nG) self.Gb = min(self.Ga + mynG, nG) # if self.blockcomm.rank == 0: # assert self.Gb - self.Ga >= 3 # assert mynG * (self.blockcomm.size - 1) < nG if A_x is not None: nx = nw * (self.Gb - self.Ga) * nG chi0_wGG = A_x[:nx].reshape((nw, self.Gb - self.Ga, nG)) chi0_wGG[:] = 0.0 else: chi0_wGG = np.zeros((nw, self.Gb - self.Ga, nG), complex) if optical_limit: chi0_wxvG = np.zeros((len(self.omega_w), 2, 3, nG), complex) chi0_wvv = np.zeros((len(self.omega_w), 3, 3), complex) self.plasmafreq_vv = np.zeros((3, 3), complex) else: chi0_wxvG = None chi0_wvv = None self.plasmafreq_vv = None if self.response == 'density': # Do all empty bands: m1 = self.nocc1 else: # Do all bands m1 = 0 m2 = self.nbands pd, chi0_wGG, chi0_wxvG, chi0_wvv = self._calculate( pd, chi0_wGG, chi0_wxvG, chi0_wvv, m1, m2, spins) return pd, chi0_wGG, chi0_wxvG, chi0_wvv @timer('Calculate CHI_0') def _calculate(self, pd, chi0_wGG, chi0_wxvG, chi0_wvv, m1, m2, spins, extend_head=True): """In-place calculation of the response function. Parameters ---------- q_c : list or ndarray Momentum vector.. pd : Planewave descriptor Planewave descriptor for q_c. chi0_wGG : ndarray The response function. chi0_wxvG : ndarray or None Wings of the density response function. chi0_wvv : ndarray or None Head of the density response function. m1 : int Lower band cutoff for band summation m2 : int Upper band cutoff for band summation spins : str or list(ints) If 'all' then include all spins. If [0] or [1], only include this specific spin (and flip it, if calculating the transverse magnetic response). extend_head : bool If True: Extend the wings and head of chi in the optical limit to take into account the non-analytic nature of chi. Effectively means that chi has dimension (nw, nG + 2, nG + 2) in the optical limit. This simplifies the code and should only be switched off for parts of the code that do not support this feature i.e., GW RPA total energy and RALDA. """ # Parse spins wfs = self.calc.wfs if spins == 'all': spins = range(wfs.nspins) elif spins == 'pm': spins = [0] elif spins == 'mp': spins = [1] else: for spin in spins: assert spin in range(wfs.nspins) # Are we calculating the optical limit. optical_limit = np.allclose(pd.kd.bzk_kc[0], 0.0) and \ self.response == 'density' # Reset PAW correction in case momentum has change self.Q_aGii = self.pair.initialize_paw_corrections(pd) A_wxx = chi0_wGG # Change notation # Initialize integrator. The integrator class is a general class # for brillouin zone integration that can integrate user defined # functions over user defined domains and sum over bands. if self.integrationmode is None or \ self.integrationmode == 'point integration': integrator = PointIntegrator(self.pair.calc.wfs.gd.cell_cv, response=self.response, comm=self.world, timer=self.timer, txt=self.fd, eshift=self.eshift, nblocks=self.nblocks) intnoblock = PointIntegrator(self.pair.calc.wfs.gd.cell_cv, response=self.response, comm=self.world, timer=self.timer, eshift=self.eshift, txt=self.fd) elif self.integrationmode == 'tetrahedron integration': integrator = TetrahedronIntegrator(self.pair.calc.wfs.gd.cell_cv, response=self.response, comm=self.world, timer=self.timer, eshift=self.eshift, txt=self.fd, nblocks=self.nblocks) intnoblock = TetrahedronIntegrator(self.pair.calc.wfs.gd.cell_cv, response=self.response, comm=self.world, timer=self.timer, eshift=self.eshift, txt=self.fd) else: print('Integration mode ' + self.integrationmode + ' not implemented.', file=self.fd) raise NotImplementedError # The integration domain is determined by the following function # that reduces the integration domain to the irreducible zone # of the little group of q. bzk_kv, PWSA = self.get_kpoints(pd, integrationmode=self.integrationmode) domain = (bzk_kv, spins) if self.integrationmode == 'tetrahedron integration': # If there are non-periodic directions it is possible that the # integration domain is not compatible with the symmetry operations # which essentially means that too large domains will be # integrated. We normalize by vol(BZ) / vol(domain) to make # sure that to fix this. domainvol = convex_hull_volume(bzk_kv) * PWSA.how_many_symmetries() bzvol = (2 * np.pi)**3 / self.vol factor = bzvol / domainvol else: factor = 1 prefactor = (2 * factor * PWSA.how_many_symmetries() / (wfs.nspins * (2 * np.pi)**3)) # Remember prefactor if self.integrationmode is None: if self.calc.wfs.kd.refine_info is not None: nbzkpts = self.calc.wfs.kd.refine_info.mhnbzkpts else: nbzkpts = self.calc.wfs.kd.nbzkpts prefactor *= len(bzk_kv) / nbzkpts # The functions that are integrated are defined in the bottom # of this file and take a number of constant keyword arguments # which the integrator class accepts through the use of the # kwargs keyword. kd = self.calc.wfs.kd mat_kwargs = { 'kd': kd, 'pd': pd, 'n1': 0, 'm1': m1, 'm2': m2, 'symmetry': PWSA, 'integrationmode': self.integrationmode } eig_kwargs = {'kd': kd, 'm1': m1, 'm2': m2, 'n1': 0, 'pd': pd} if self.response == 'density': mat_kwargs['n2'] = self.nocc2 eig_kwargs['n2'] = self.nocc2 else: mat_kwargs['n2'] = self.nbands eig_kwargs['n2'] = self.nbands if not extend_head: mat_kwargs['extend_head'] = False # Determine what "kind" of integral to make. extraargs = {} # Initialize extra arguments to integration method. if self.eta == 0: # If eta is 0 then we must be working with imaginary frequencies. # In this case chi is hermitian and it is therefore possible to # reduce the computational costs by a only computing half of the # response function. kind = 'hermitian response function' elif self.hilbert: # The spectral function integrator assumes that the form of the # integrand is a function (a matrix element) multiplied by # a delta function and should return a function of at user defined # x's (frequencies). Thus the integrand is tuple of two functions # and takes an additional argument (x). kind = 'spectral function' else: # Otherwise, we can make no simplifying assumptions of the # form of the response function and we simply perform a brute # force calculation of the response function. kind = 'response function' extraargs['eta'] = self.eta extraargs['timeordered'] = self.timeordered if optical_limit and not extend_head: wings = True else: wings = False A_wxx /= prefactor if wings: chi0_wxvG /= prefactor chi0_wvv /= prefactor # Integrate response function print('Integrating response function.', file=self.fd) integrator.integrate( kind=kind, # Kind of integral domain=domain, # Integration domain integrand=( self.get_matrix_element, # Integrand self.get_eigenvalues), # Integrand x=self.wd, # Frequency Descriptor kwargs=(mat_kwargs, eig_kwargs), # Arguments for integrand functions out_wxx=A_wxx, # Output array **extraargs) # extraargs: Extra arguments to integration method if wings: mat_kwargs['extend_head'] = True mat_kwargs['block'] = False if self.eta == 0: extraargs['eta'] = self.eta # This is horrible but we need to update the wings manually # in order to make them work with ralda, RPA and GW. This entire # section can be deleted in the future if the ralda and RPA code is # made compatible with the head and wing extension that other parts # of the code is using. chi0_wxvx = np.zeros( np.array(chi0_wxvG.shape) + [0, 0, 0, 2], complex) # Notice the wxv"x" for head extend intnoblock.integrate( kind=kind + ' wings', # kind'o int. domain=domain, # Integration domain integrand=( self.get_matrix_element, # Intgrnd self.get_eigenvalues), # Integrand x=self.wd, # Frequency Descriptor kwargs=(mat_kwargs, eig_kwargs), # Arguments for integrand functions out_wxx=chi0_wxvx, # Output array **extraargs) if self.hilbert: # The integrator only returns the spectral function and a Hilbert # transform is performed to return the real part of the density # response function. with self.timer('Hilbert transform'): omega_w = self.wd.get_data() # Get frequencies # Make Hilbert transform ht = HilbertTransform(np.array(omega_w), self.eta, timeordered=self.timeordered) ht(A_wxx) if wings: ht(chi0_wxvx) # In the optical limit additional work must be performed # for the intraband response. # Only compute the intraband response if there are partially # unoccupied bands and only if the user has not disabled its # calculation using the include_intraband keyword. if optical_limit and self.nocc1 != self.nocc2: # The intraband response is essentially just the calculation # of the free space Drude plasma frequency. The calculation is # similarly to the interband transitions documented above. mat_kwargs = { 'kd': kd, 'symmetry': PWSA, 'n1': self.nocc1, 'n2': self.nocc2, 'pd': pd } # Integrand arguments eig_kwargs = { 'kd': kd, 'n1': self.nocc1, 'n2': self.nocc2, 'pd': pd } # Integrand arguments domain = (bzk_kv, spins) # Integration domain fermi_level = self.pair.fermi_level # Fermi level # Not so elegant solution but it works plasmafreq_wvv = np.zeros((1, 3, 3), complex) # Output array print('Integrating intraband density response.', file=self.fd) # Depending on which integration method is used we # have to pass different arguments extraargs = {} if self.integrationmode is None: # Calculate intraband transitions at finite fermi smearing extraargs['intraband'] = True # Calculate intraband elif self.integrationmode == 'tetrahedron integration': # Calculate intraband transitions at T=0 extraargs['x'] = ArrayDescriptor([-fermi_level]) intnoblock.integrate( kind='spectral function', # Kind of integral domain=domain, # Integration domain # Integrands integrand=(self.get_intraband_response, self.get_intraband_eigenvalue), # Integrand arguments kwargs=(mat_kwargs, eig_kwargs), out_wxx=plasmafreq_wvv, # Output array **extraargs) # Extra args for int. method # Again, not so pretty but that's how it is plasmafreq_vv = plasmafreq_wvv[0].copy() if self.include_intraband: if extend_head: va = min(self.Ga, 3) vb = min(self.Gb, 3) A_wxx[:, :vb - va, :3] += ( plasmafreq_vv[va:vb] / (self.omega_w[:, np.newaxis, np.newaxis] + 1e-10 + self.rate * 1j)**2) elif self.blockcomm.rank == 0: A_wxx[:, 0, 0] += (plasmafreq_vv[2, 2] / (self.omega_w + 1e-10 + self.rate * 1j)**2) # Save the plasmafrequency try: self.plasmafreq_vv += 4 * np.pi * plasmafreq_vv * prefactor except AttributeError: self.plasmafreq_vv = 4 * np.pi * plasmafreq_vv * prefactor PWSA.symmetrize_wvv(self.plasmafreq_vv[np.newaxis]) print('Plasma frequency:', file=self.fd) print((self.plasmafreq_vv**0.5 * Hartree).round(2), file=self.fd) # The response function is integrated only over the IBZ. The # chi calculated above must therefore be extended to include the # response from the full BZ. This extension can be performed as a # simple post processing of the response function that makes # sure that the response function fulfills the symmetries of the little # group of q. Due to the specific details of the implementation the chi # calculated above is normalized by the number of symmetries (as seen # below) and then symmetrized. A_wxx *= prefactor tmpA_wxx = self.redistribute(A_wxx) if extend_head: PWSA.symmetrize_wxx(tmpA_wxx, optical_limit=optical_limit) else: PWSA.symmetrize_wGG(tmpA_wxx) if wings: chi0_wxvG += chi0_wxvx[..., 2:] chi0_wvv += chi0_wxvx[:, 0, :3, :3] PWSA.symmetrize_wxvG(chi0_wxvG) PWSA.symmetrize_wvv(chi0_wvv) self.redistribute(tmpA_wxx, A_wxx) # If point summation was used then the normalization of the # response function is not right and we have to make up for this # fact. if wings: chi0_wxvG *= prefactor chi0_wvv *= prefactor # In the optical limit, we have extended the wings and the head to # account for their nonanalytic behaviour which means that the size of # the chi0_wGG matrix is nw * (nG + 2)**2. Below we extract these # parameters. if optical_limit and extend_head: # The wings are extracted chi0_wxvG[:, 1, :, self.Ga:self.Gb] = np.transpose(A_wxx[..., 0:3], (0, 2, 1)) va = min(self.Ga, 3) vb = min(self.Gb, 3) # print(self.world.rank, va, vb, chi0_wxvG[:, 0, va:vb].shape, # A_wxx[:, va:vb].shape, A_wxx.shape) chi0_wxvG[:, 0, va:vb] = A_wxx[:, :vb - va] # Add contributions on different ranks self.blockcomm.sum(chi0_wxvG) chi0_wvv[:] = chi0_wxvG[:, 0, :3, :3] chi0_wxvG = chi0_wxvG[..., 2:] # Jesus, this is complicated # The head is extracted # if self.blockcomm.rank == 0: # chi0_wvv[:] = A_wxx[:, :3, :3] # self.blockcomm.broadcast(chi0_wvv, 0) # It is easiest to redistribute over freqs to pick body tmpA_wxx = self.redistribute(A_wxx) chi0_wGG = tmpA_wxx[:, 2:, 2:] chi0_wGG = self.redistribute(chi0_wGG) elif optical_limit: # Since chi_wGG is nonanalytic in the head # and wings we have to take care that # these are handled correctly. Note that # it is important that the wings are overwritten first. chi0_wGG[:, :, 0] = chi0_wxvG[:, 1, 2, self.Ga:self.Gb] if self.blockcomm.rank == 0: chi0_wGG[:, 0, :] = chi0_wxvG[:, 0, 2, :] chi0_wGG[:, 0, 0] = chi0_wvv[:, 2, 2] else: chi0_wGG = A_wxx return pd, chi0_wGG, chi0_wxvG, chi0_wvv def get_PWDescriptor(self, q_c, gammacentered=False): """Get the planewave descriptor of q_c.""" qd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd, gammacentered=gammacentered) return pd @timer('Get kpoints') def get_kpoints(self, pd, integrationmode=None): """Get the integration domain.""" # Use symmetries PWSA = PWSymmetryAnalyzer PWSA = PWSA(self.calc.wfs.kd, pd, timer=self.timer, txt=self.fd, disable_point_group=self.disable_point_group, disable_time_reversal=self.disable_time_reversal, disable_non_symmorphic=self.disable_non_symmorphic) if integrationmode is None: K_gK = PWSA.group_kpoints() bzk_kc = np.array( [self.calc.wfs.kd.bzk_kc[K_K[0]] for K_K in K_gK]) elif integrationmode == 'tetrahedron integration': bzk_kc = PWSA.get_reduced_kd(pbc_c=self.pbc).bzk_kc if (~self.pbc).any(): bzk_kc = np.append(bzk_kc, bzk_kc + (~self.pbc).astype(int), axis=0) bzk_kv = np.dot(bzk_kc, pd.gd.icell_cv) * 2 * np.pi return bzk_kv, PWSA @timer('Get matrix element') def get_matrix_element(self, k_v, s, n1=None, n2=None, m1=None, m2=None, pd=None, kd=None, symmetry=None, integrationmode=None, extend_head=True, block=True): """A function that returns pair-densities. A pair density is defined as:: <snk| e^(-i (q + G) r) |s'mk+q>, where s and s' are spins, n and m are band indices, k is the kpoint and q is the momentum transfer. For dielectric reponse s'=s, for the transverse magnetic reponse s' is flipped with respect to s. Parameters ---------- k_v : ndarray Kpoint coordinate in cartesian coordinates. s : int Spin index. n1 : int Lower occupied band index. n2 : int Upper occupied band index. m1 : int Lower unoccupied band index. m2 : int Upper unoccupied band index. pd : PlanewaveDescriptor instance kd : KpointDescriptor instance Calculator kpoint descriptor. symmetry: gpaw.response.pair.PWSymmetryAnalyzer instance PWSA object for handling symmetries of the kpoints. integrationmode : str The integration mode employed. extend_head: Bool Extend the head to include non-analytic behaviour Return ------ n_nmG : ndarray Pair densities. """ k_c = np.dot(pd.gd.cell_cv, k_v) / (2 * np.pi) q_c = pd.kd.bzk_kc[0] optical_limit = np.allclose(q_c, 0.0) and self.response == 'density' extrapolate_q = False if self.calc.wfs.kd.refine_info is not None: K1 = self.pair.find_kpoint(k_c) label = kd.refine_info.label_k[K1] if label == 'zero': return None elif (kd.refine_info.almostoptical and label == 'mh'): if not hasattr(self, 'pd0'): self.pd0 = self.get_PWDescriptor([ 0, ] * 3) pd = self.pd0 extrapolate_q = True nG = pd.ngmax weight = np.sqrt( symmetry.get_kpoint_weight(k_c) / symmetry.how_many_symmetries()) if self.Q_aGii is None: self.Q_aGii = self.pair.initialize_paw_corrections(pd) kptpair = self.pair.get_kpoint_pair(pd, s, k_c, n1, n2, m1, m2, block=block) m_m = np.arange(m1, m2) n_n = np.arange(n1, n2) n_nmG = self.pair.get_pair_density(pd, kptpair, n_n, m_m, Q_aGii=self.Q_aGii, block=block) if integrationmode is None: n_nmG *= weight df_nm = kptpair.get_occupation_differences(n_n, m_m) if not self.response == 'density': df_nm = np.abs(df_nm) df_nm[df_nm <= 1e-20] = 0.0 n_nmG *= df_nm[..., np.newaxis]**0.5 if extrapolate_q: q_v = np.dot(q_c, pd.gd.icell_cv) * (2 * np.pi) nq_nm = np.dot(n_nmG[:, :, :3], q_v) n_nmG = n_nmG[:, :, 2:] n_nmG[:, :, 0] = nq_nm if not extend_head and optical_limit: n_nmG = np.copy(n_nmG[:, :, 2:]) optical_limit = False if extend_head and optical_limit: return n_nmG.reshape(-1, nG + 2 * optical_limit) else: return n_nmG.reshape(-1, nG) @timer('Get eigenvalues') def get_eigenvalues(self, k_v, s, n1=None, n2=None, m1=None, m2=None, kd=None, pd=None, wfs=None, filter=False): """A function that can return the eigenvalues. A simple function describing the integrand of the response function which gives an output that is compatible with the gpaw k-point integration routines.""" if wfs is None: wfs = self.calc.wfs kd = wfs.kd k_c = np.dot(pd.gd.cell_cv, k_v) / (2 * np.pi) q_c = pd.kd.bzk_kc[0] K1 = self.pair.find_kpoint(k_c) K2 = self.pair.find_kpoint(k_c + q_c) ik1 = kd.bz2ibz_k[K1] ik2 = kd.bz2ibz_k[K2] kpt1 = wfs.kpt_u[s * wfs.kd.nibzkpts + ik1] if self.response in ['+-', '-+']: s2 = 1 - s else: s2 = s kpt2 = wfs.kpt_u[s2 * wfs.kd.nibzkpts + ik2] deps_nm = np.subtract(kpt1.eps_n[n1:n2][:, np.newaxis], kpt2.eps_n[m1:m2]) if filter: fermi_level = self.pair.fermi_level deps_nm[kpt1.eps_n[n1:n2] > fermi_level, :] = np.nan deps_nm[:, kpt2.eps_n[m1:m2] < fermi_level] = np.nan return deps_nm.reshape(-1) def get_intraband_response(self, k_v, s, n1=None, n2=None, kd=None, symmetry=None, pd=None, integrationmode=None): k_c = np.dot(pd.gd.cell_cv, k_v) / (2 * np.pi) kpt1 = self.pair.get_k_point(s, k_c, n1, n2) n_n = range(n1, n2) vel_nv = self.pair.intraband_pair_density(kpt1, n_n) if self.integrationmode is None: f_n = kpt1.f_n width = self.calc.occupations.width if width > 1e-15: dfde_n = -1. / width * (f_n - f_n**2.0) else: dfde_n = np.zeros_like(f_n) vel_nv *= np.sqrt(-dfde_n[:, np.newaxis]) weight = np.sqrt( symmetry.get_kpoint_weight(k_c) / symmetry.how_many_symmetries()) vel_nv *= weight return vel_nv @timer('Intraband eigenvalue') def get_intraband_eigenvalue(self, k_v, s, n1=None, n2=None, kd=None, pd=None): """A function that can return the eigenvalues. A simple function describing the integrand of the response function which gives an output that is compatible with the gpaw k-point integration routines.""" wfs = self.calc.wfs kd = wfs.kd k_c = np.dot(pd.gd.cell_cv, k_v) / (2 * np.pi) K1 = self.pair.find_kpoint(k_c) ik = kd.bz2ibz_k[K1] kpt1 = wfs.kpt_u[s * wfs.kd.nibzkpts + ik] return kpt1.eps_n[n1:n2] @timer('redist') def redistribute(self, in_wGG, out_x=None): """Redistribute array. Switch between two kinds of parallel distributions: 1) parallel over G-vectors (second dimension of in_wGG) 2) parallel over frequency (first dimension of in_wGG) Returns new array using the memory in the 1-d array out_x. """ comm = self.blockcomm if comm.size == 1: return in_wGG nw = len(self.omega_w) nG = in_wGG.shape[2] mynw = (nw + comm.size - 1) // comm.size mynG = (nG + comm.size - 1) // comm.size bg1 = BlacsGrid(comm, comm.size, 1) bg2 = BlacsGrid(comm, 1, comm.size) md1 = BlacsDescriptor(bg1, nw, nG**2, mynw, nG**2) md2 = BlacsDescriptor(bg2, nw, nG**2, nw, mynG * nG) if len(in_wGG) == nw: mdin = md2 mdout = md1 else: mdin = md1 mdout = md2 r = Redistributor(comm, mdin, mdout) outshape = (mdout.shape[0], mdout.shape[1] // nG, nG) if out_x is None: out_wGG = np.empty(outshape, complex) else: out_wGG = out_x[:np.product(outshape)].reshape(outshape) r.redistribute(in_wGG.reshape(mdin.shape), out_wGG.reshape(mdout.shape)) return out_wGG @timer('dist freq') def distribute_frequencies(self, chi0_wGG): """Distribute frequencies to all cores.""" world = self.world comm = self.blockcomm if world.size == 1: return chi0_wGG nw = len(self.omega_w) nG = chi0_wGG.shape[2] mynw = (nw + world.size - 1) // world.size mynG = (nG + comm.size - 1) // comm.size wa = min(world.rank * mynw, nw) wb = min(wa + mynw, nw) if self.blockcomm.size == 1: return chi0_wGG[wa:wb].copy() if self.kncomm.rank == 0: bg1 = BlacsGrid(comm, 1, comm.size) in_wGG = chi0_wGG.reshape((nw, -1)) else: bg1 = DryRunBlacsGrid(mpi.serial_comm, 1, 1) in_wGG = np.zeros((0, 0), complex) md1 = BlacsDescriptor(bg1, nw, nG**2, nw, mynG * nG) bg2 = BlacsGrid(world, world.size, 1) md2 = BlacsDescriptor(bg2, nw, nG**2, mynw, nG**2) r = Redistributor(world, md1, md2) shape = (wb - wa, nG, nG) out_wGG = np.empty(shape, complex) r.redistribute(in_wGG, out_wGG.reshape((wb - wa, nG**2))) return out_wGG def print_chi(self, pd): calc = self.calc gd = calc.wfs.gd if extra_parameters.get('df_dry_run'): from gpaw.mpi import DryRunCommunicator size = extra_parameters['df_dry_run'] world = DryRunCommunicator(size) else: world = self.world q_c = pd.kd.bzk_kc[0] nw = len(self.omega_w) ecut = self.ecut * Hartree ns = calc.wfs.nspins nbands = self.nbands nk = calc.wfs.kd.nbzkpts nik = calc.wfs.kd.nibzkpts ngmax = pd.ngmax eta = self.eta * Hartree wsize = world.size knsize = self.kncomm.size nocc = self.nocc1 npocc = self.nocc2 ngridpoints = gd.N_c[0] * gd.N_c[1] * gd.N_c[2] nstat = (ns * npocc + world.size - 1) // world.size occsize = nstat * ngridpoints * 16. / 1024**2 bsize = self.blockcomm.size chisize = nw * pd.ngmax**2 * 16. / 1024**2 / bsize p = partial(print, file=self.fd) p('%s' % ctime()) p('Called response.chi0.calculate with') p(' q_c: [%f, %f, %f]' % (q_c[0], q_c[1], q_c[2])) p(' Number of frequency points: %d' % nw) p(' Planewave cutoff: %f' % ecut) p(' Number of spins: %d' % ns) p(' Number of bands: %d' % nbands) p(' Number of kpoints: %d' % nk) p(' Number of irredicible kpoints: %d' % nik) p(' Number of planewaves: %d' % ngmax) p(' Broadening (eta): %f' % eta) p(' world.size: %d' % wsize) p(' kncomm.size: %d' % knsize) p(' blockcomm.size: %d' % bsize) p(' Number of completely occupied states: %d' % nocc) p(' Number of partially occupied states: %d' % npocc) p() p(' Memory estimate of potentially large arrays:') p(' chi0_wGG: %f M / cpu' % chisize) p(' Occupied states: %f M / cpu' % occsize) p(' Memory usage before allocation: %f M / cpu' % (maxrss() / 1024**2)) p()
def read_gpaw(input_gpw, nb_max=None, spin_factor=2, output_file='out.hdf5', calculate_overlap=True, calculate_momentum=True): """ Read .gpw file and create a hdf5 as output. Partially adapted from gpaw.wannier90.write_overlaps. input_gpw: location of .gpw input file nb_max: maximum band spin_factor: electrons per band (only default value supported!) """ nn = 1 au = AtomicUnits() atoms, calc = restart(input_gpw, txt=None) # Generate full Brillouin zone if necessary if calc.parameters['symmetry'] != 'off': calc.set(symmetry='off') calc.get_potential_energy() calc.write(input_gpw.replace('.gpw', '_FBZ.gpw'), mode='all') calc.get_potential_energy() #Optional parameters: nb = calc.parameters['convergence']['bands'] if nb_max is not None: nb = min(nb, nb_max) nk_size = calc.parameters['kpts']['size'] nk = calc.wfs.kd.nbzkpts kpts = calc.get_bz_k_points() wfs = calc.wfs nvalence = calc.setups.nvalence // spin_factor lattice = atoms.get_cell() / au.AA reciprocal_lattice = (2 * np.pi * au.AA) * atoms.get_reciprocal_cell() bands = range(nb) direction = np.eye(3, dtype=np.intp) klist1d = np.zeros((nk, 3)) klist3d = np.zeros((nk_size), dtype=np.intp) energy = np.zeros((nk, nb)) for i in range(nk): klist1d[i, :] = calc.wfs.kd.bzk_kc[i] for i in range(nk): ix, iy, iz = [int(q) for q in np.rint(nk_size * kpts[i, :])] klist3d[ix, iy, iz] = i nn_table = nearest_neighbor_table(klist3d, nn) energy_fermi = calc.get_fermi_level() for i in range(nk): energy[i, :] = (calc.get_eigenvalues(kpt=i, spin=0) - energy_fermi)[:nb] / au.eV if calculate_overlap: overlap = np.zeros((nk, 3, 2 * nn, nb, nb), dtype=np.complex) icell_cv = (2 * np.pi) * np.linalg.inv(calc.wfs.gd.cell_cv).T r_grid = calc.wfs.gd.get_grid_point_coordinates() n_grid = np.prod(np.shape(r_grid)[1:]) * (False + 1) u_kng = [] for i in range(nk): u_kng.append( np.array([wfs.get_wave_function_array(n, i, 0) for n in bands])) d0_aii = [] for i in calc.wfs.kpt_u[0].p_ani.keys(): d0_ii = calc.wfs.setups[i].d0_ii d0_aii.append(d0_ii) p_kani = [] for i in range(nk): p_kani.append(calc.wfs.kpt_u[0 * nk + i].p_ani) for ik1 in range(nk): u1_ng = u_kng[ik1] for i in range(3): for j in range(-nn, nn): ik2 = nn_table[ik1, i, j] if j < 0: g_vector = (kpts[ik1] + 1.0 * j * direction[i, :] / nk_size) - kpts[ik2] if j >= 0: g_vector = ( kpts[ik1] + 1.0 * (j + 1) * direction[i, :] / nk_size) - kpts[ik2] bg_c = kpts[ik2] - kpts[ik1] + g_vector bg_v = np.dot(bg_c, icell_cv) u2_ng = u_kng[ik2] * np.exp( -1j * np.inner(r_grid.T, bg_v).T) overlap[ik1, i, j, :, :] = get_overlap( calc, bands, np.reshape(u1_ng, (len(u1_ng), n_grid)), np.reshape(u2_ng, (len(u2_ng), n_grid)), p_kani[ik1], p_kani[ik2], d0_aii, bg_v)[:nb, :nb] if calculate_momentum: pair = PairDensity(calc=calc) momentum = np.zeros((nk, nb, nb, 3), dtype=np.complex) delta_q_vector = [0.0, 0.0, 0.0] delta_q_descriptor = KPointDescriptor([delta_q_vector]) plane_wave_descriptor = PWDescriptor(pair.ecut, calc.wfs.gd, complex, delta_q_descriptor) for i in range(nk): kpoint_pair = pair.get_kpoint_pair(plane_wave_descriptor, s=0, K=i, n1=0, n2=nb, m1=0, m2=nb) kpoint_momentum = pair.get_pair_momentum(plane_wave_descriptor, kpoint_pair, np.arange(0, nb), np.arange(0, nb)) momentum[i, :, :, :] = kpoint_momentum[..., 0] hdf5 = h5py.File(output_file, 'w') hdf5.create_dataset("energy", data=energy) hdf5.create_dataset("klist1d", data=klist1d) hdf5.create_dataset("klist3d", data=klist3d) hdf5.create_dataset("lattice_vectors", data=lattice.T) hdf5.create_dataset("reciprocal_vectors", data=reciprocal_lattice.T) hdf5.create_dataset("valence_bands", data=nvalence) hdf5.create_dataset("size", data=nk_size) hdf5.create_dataset("spin_factor", data=spin_factor) if calculate_momentum: hdf5.create_dataset("momentum", data=momentum) if calculate_overlap: hdf5.create_dataset("overlap", data=overlap) hdf5.create_dataset("neighbour_table", data=nn_table)
def __init__(self, calc, response='density', frequencies=None, domega0=0.1, omega2=10.0, omegamax=None, ecut=50, gammacentered=False, hilbert=True, nbands=None, timeordered=False, eta=0.2, ftol=1e-6, threshold=1, real_space_derivatives=False, intraband=True, world=mpi.world, txt='-', timer=None, nblocks=1, gate_voltage=None, disable_point_group=False, disable_time_reversal=False, disable_non_symmorphic=True, scissor=None, integrationmode=None, pbc=None, rate=0.0, eshift=0.0): """Construct Chi0 object. Parameters ---------- calc : str The groundstate calculation file that the linear response calculation is based on. response : str Type of response function. Currently collinear, scalar options 'density', '+-' and '-+' are implemented. frequencies : ndarray or None Array of frequencies to evaluate the response function at. If None, frequencies are determined using the frequency_grid function in gpaw.response.chi0. domega0, omega2, omegamax : float Input parameters for frequency_grid. ecut : float Energy cutoff. gammacentered : bool Center the grid of plane waves around the gamma point or q-vector hilbert : bool Switch for hilbert transform. If True, the full density response is determined from a hilbert transform of its spectral function. This is typically much faster, but does not work for imaginary frequencies. nbands : int Maximum band index to include. timeordered : bool Switch for calculating the time ordered density response function. In this case the hilbert transform cannot be used. eta : float Artificial broadening of spectra. ftol : float Threshold determining whether a band is completely filled (f > 1 - ftol) or completely empty (f < ftol). threshold : float Numerical threshold for the optical limit k dot p perturbation theory expansion (used in gpaw/response/pair.py). real_space_derivatives : bool Switch for calculating nabla matrix elements (in the optical limit) using a real space finite difference approximation. intraband : bool Switch for including the intraband contribution to the density response function. world : MPI comm instance MPI communicator. txt : str Output file. timer : gpaw.utilities.timing.timer instance nblocks : int Divide the response function into nblocks. Useful when the response function is large. gate_voltage : float Shift the fermi level by gate_voltage [Hartree]. disable_point_group : bool Do not use the point group symmetry operators. disable_time_reversal : bool Do not use time reversal symmetry. disable_non_symmorphic : bool Do no use non symmorphic symmetry operators. scissor : tuple ([bands], shift [eV]) Use scissor operator on bands. integrationmode : str Integrator for the kpoint integration. If == 'tetrahedron integration' then the kpoint integral is performed using the linear tetrahedron method. pbc : list Periodic directions of the system. Defaults to [True, True, True]. eshift : float Shift unoccupied bands Attributes ---------- pair : gpaw.response.pair.PairDensity instance Class for calculating matrix elements of pairs of wavefunctions. """ self.response = response self.timer = timer or Timer() self.pair = PairDensity(calc, ecut, self.response, ftol, threshold, real_space_derivatives, world, txt, self.timer, nblocks=nblocks, gate_voltage=gate_voltage) self.disable_point_group = disable_point_group self.disable_time_reversal = disable_time_reversal self.disable_non_symmorphic = disable_non_symmorphic self.integrationmode = integrationmode self.eshift = eshift / Hartree calc = self.pair.calc self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt self.vol = abs(np.linalg.det(calc.wfs.gd.cell_cv)) self.world = world if nblocks == 1: self.blockcomm = self.world.new_communicator([world.rank]) self.kncomm = world else: assert world.size % nblocks == 0, world.size rank1 = world.rank // nblocks * nblocks rank2 = rank1 + nblocks self.blockcomm = self.world.new_communicator(range(rank1, rank2)) ranks = range(world.rank % nblocks, world.size, nblocks) self.kncomm = self.world.new_communicator(ranks) self.nblocks = nblocks if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt if ecut is not None: ecut /= Hartree self.ecut = ecut self.gammacentered = gammacentered self.eta = eta / Hartree if rate == 'eta': self.rate = self.eta else: self.rate = rate / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree self.omegamax = None if omegamax is None else omegamax / Hartree self.nbands = nbands or self.calc.wfs.bd.nbands self.include_intraband = intraband omax = self.find_maximum_frequency() if frequencies is None: if self.omegamax is None: self.omegamax = omax print('Using nonlinear frequency grid from 0 to %.3f eV' % (self.omegamax * Hartree), file=self.fd) self.wd = FrequencyDescriptor(self.domega0, self.omega2, self.omegamax) else: self.wd = ArrayDescriptor(np.asarray(frequencies) / Hartree) assert not hilbert self.omega_w = self.wd.get_data() self.hilbert = hilbert self.timeordered = bool(timeordered) if self.eta == 0.0: assert not hilbert assert not timeordered assert not self.omega_w.real.any() self.nocc1 = self.pair.nocc1 # number of completely filled bands self.nocc2 = self.pair.nocc2 # number of non-empty bands self.Q_aGii = None if pbc is not None: self.pbc = np.array(pbc) else: self.pbc = np.array([True, True, True]) if self.pbc is not None and (~self.pbc).any(): assert np.sum((~self.pbc).astype(int)) == 1, \ print('Only one non-periodic direction supported atm.') print('Nonperiodic BC\'s: ', (~self.pbc), file=self.fd) if integrationmode is not None: print('Using integration method: ' + self.integrationmode, file=self.fd) else: print('Using integration method: PointIntegrator', file=self.fd)
from gpaw.response.pair import PairDensity from gpaw.response.math_func import two_phi_nabla_planewave_integrals np.set_printoptions(precision=1) nb = 6 a = Atoms('H', cell=(3 * np.eye(3)), pbc=True) calc = GPAW(mode=PW(600), kpts=[[0, 0, 0], [0.25, 0, 0]]) a.calc = calc a.get_potential_energy() calc.diagonalize_full_hamiltonian(nbands=nb, expert=True) calc.write('a.gpw', 'all') pair = PairDensity('a.gpw', ecut=100) # Check continuity eq. for q_c in [[0, 0, 0], [1. / 4, 0, 0]]: ol = np.allclose(q_c, 0.0) qd = KPointDescriptor([q_c]) pd = PWDescriptor(pair.ecut, calc.wfs.gd, complex, qd) kptpair = pair.get_kpoint_pair(pd, 0, [0, 0, 0], 0, nb, 0, nb) deps_nm = kptpair.get_transition_energies(np.arange(0, nb), np.arange(0, nb)) n_nmG = pair.get_pair_density(pd, kptpair, np.arange(0, nb), np.arange(0, nb), optical_limit=ol)
def __init__(self, calc, filename='gw', kpts=None, bands=None, nbands=None, ppa=False, wstc=False, ecut=150.0, eta=0.1, E0=1.0 * Hartree, domega0=0.025, omega2=10.0, nblocks=1, savew=False, world=mpi.world): if world.rank != 0: txt = devnull else: txt = open(filename + '.txt', 'w') p = functools.partial(print, file=txt) p(' ___ _ _ _ ') p(' | || | | |') p(' | | || | | |') p(' |__ ||_____|') p(' |___|') p() PairDensity.__init__(self, calc, ecut, world=world, nblocks=nblocks, txt=txt) self.filename = filename self.savew = savew ecut /= Hartree self.ppa = ppa self.wstc = wstc self.eta = eta / Hartree self.E0 = E0 / Hartree self.domega0 = domega0 / Hartree self.omega2 = omega2 / Hartree self.kpts = select_kpts(kpts, self.calc) if bands is None: bands = [0, self.nocc2] self.bands = bands b1, b2 = bands self.shape = shape = (self.calc.wfs.nspins, len(self.kpts), b2 - b1) self.eps_sin = np.empty(shape) # KS-eigenvalues self.f_sin = np.empty(shape) # occupation numbers self.sigma_sin = np.zeros(shape) # self-energies self.dsigma_sin = np.zeros(shape) # derivatives of self-energies self.vxc_sin = None # KS XC-contributions self.exx_sin = None # exact exchange contributions self.Z_sin = None # renormalization factors if nbands is None: nbands = int(self.vol * ecut**1.5 * 2**0.5 / 3 / pi**2) self.nbands = nbands p() p('Quasi particle states:') if kpts is None: p('All k-points in IBZ') else: kptstxt = ', '.join(['{0:d}'.format(k) for k in self.kpts]) p('k-points (IBZ indices): [' + kptstxt + ']') p('Band range: ({0:d}, {1:d})'.format(b1, b2)) p() p('Computational parameters:') p('Plane wave cut-off: {0:g} eV'.format(self.ecut * Hartree)) p('Number of bands: {0:d}'.format(self.nbands)) p('Broadening: {0:g} eV'.format(self.eta * Hartree)) kd = self.calc.wfs.kd self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(b1, b2, kd.ibz2bz_k[self.kpts]) # Find q-vectors and weights in the IBZ: assert -1 not in kd.bz2bz_ks offset_c = 0.5 * ((kd.N_c + 1) % 2) / kd.N_c bzq_qc = monkhorst_pack(kd.N_c) + offset_c self.qd = KPointDescriptor(bzq_qc) self.qd.set_symmetry(self.calc.atoms, kd.symmetry) assert self.calc.wfs.nspins == 1