def __init__(self, calc, xc, ibzq_qc, fd, unit_cells, density_cut, ecut, tag, timer): self.calc = calc self.gd = calc.density.gd self.xc = xc self.ibzq_qc = ibzq_qc self.fd = fd self.unit_cells = unit_cells self.density_cut = density_cut self.ecut = ecut self.tag = tag self.timer = timer self.A_x = -(3 / 4.) * (3 / np.pi)**(1 / 3.) self.n_g = calc.get_all_electron_density(gridrefinement=1) self.n_g *= Bohr**3 if xc[-3:] == 'PBE': nf_g = calc.get_all_electron_density(gridrefinement=2) nf_g *= Bohr**3 gdf = self.gd.refine() grad_v = [Gradient(gdf, v, n=1).apply for v in range(3)] gradnf_vg = gdf.empty(3) for v in range(3): grad_v[v](nf_g, gradnf_vg[v]) self.gradn_vg = gradnf_vg[:, ::2, ::2, ::2] qd = KPointDescriptor(self.ibzq_qc) self.pd = PWDescriptor(ecut / Hartree, self.gd, complex, qd)
def create_kpoint_descriptor(self, nspins): par = self.parameters bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kpt_refine = par.experimental.get('kpt_refine') if kpt_refine is None: kd = KPointDescriptor(bzkpts_kc, nspins) self.timer.start('Set symmetry') kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) self.timer.stop('Set symmetry') else: self.timer.start('Set k-point refinement') kd = create_kpoint_descriptor_with_refinement(kpt_refine, bzkpts_kc, nspins, self.atoms, self.symmetry, comm=self.world, timer=self.timer) self.timer.stop('Set k-point refinement') # Update quantities which might have changed, if symmetry # was changed self.symmetry = kd.symmetry self.setups.set_symmetry(kd.symmetry) self.log(kd) return kd
def get_ibz_q_points(self, bz_k_points): # Get all q-points all_qs = [] for k1 in bz_k_points: for k2 in bz_k_points: all_qs.append(k1 - k2) all_qs = np.array(all_qs) # Fold q-points into Brillouin zone all_qs[np.where(all_qs > 0.501)] -= 1. all_qs[np.where(all_qs < -0.499)] += 1. # Make list of non-identical q-points in full BZ bz_qs = [all_qs[0]] for q_a in all_qs: q_in_list = False for q_b in bz_qs: if (abs(q_a[0] - q_b[0]) < 0.01 and abs(q_a[1] - q_b[1]) < 0.01 and abs(q_a[2] - q_b[2]) < 0.01): q_in_list = True break if q_in_list == False: bz_qs.append(q_a) self.bz_q_points = bz_qs # Obtain q-points and weights in the irreducible part of the BZ kpt_descriptor = KPointDescriptor(bz_qs, self.nspins) kpt_descriptor.set_symmetry(self.atoms, self.setups, usesymm=True) ibz_q_points = kpt_descriptor.ibzk_kc q_weights = kpt_descriptor.weight_k return ibz_q_points, q_weights
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
def get_pw_descriptor(q_c, calc, ecut, gammacentered=False): """Get the planewave descriptor of q_c.""" qd = KPointDescriptor([q_c]) pd = PWDescriptor(ecut, calc.wfs.gd, complex, qd, gammacentered=gammacentered) return pd
def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd if self.bd.comm.size > 1: raise ValueError('Band parallelization not supported by hybridk') self.wfs = wfs self.world = wfs.world self.fd = logfile(self.fd, self.world.rank) N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: # XXX ? self.alpha = 6 * vol**(2 / 3.0) / pi**2 if self.ecut is None: self.ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max() * 0.9999 self.bzq_qc = self.kd.get_bz_q_points() qd = KPointDescriptor(self.bzq_qc) q0 = self.kd.where_is_q(np.zeros(3), self.bzq_qc) self.pwd = PWDescriptor(self.ecut, self.gd, complex, kd=qd) G2_qG = self.pwd.G2_qG G2_qG[q0][0] = 117.0 self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] G2_qG[q0][0] = 0.0 self.iG2_qG[q0][0] = 0.0 self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) * self.kd.nbzkpts) for q in range(self.kd.nbzkpts): self.gamma -= np.dot(np.exp(-self.alpha * G2_qG[q]), self.iG2_qG[q]) self.iG2_qG[q0][0] = self.gamma self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups], qd, dtype=complex) self.log('Value of alpha parameter:', self.alpha) self.log('Value of gamma parameter:', self.gamma) self.log('Cutoff energy:', self.ecut, 'Hartree') self.log('%d x %d x %d k-points' % tuple(self.kd.N_c))
def setUp(self): for virtvar in ['boundaries']: assert getattr(self, virtvar) is not None, 'Virtual "%s"!' % virtvar # Basic unit cell information: res, N_c = shapeopt(100, self.G**3, 3, 0.2) #N_c = 4*np.round(np.array(N_c)/4) # makes domain decomposition easier cell_cv = self.h * np.diag(N_c) pbc_c = {'zero' : (False,False,False), \ 'periodic': (True,True,True), \ 'mixed' : (True, False, True)}[self.boundaries] # Create randomized gas-like atomic configuration on interim grid tmpgd = GridDescriptor(N_c, cell_cv, pbc_c) self.atoms = create_random_atoms(tmpgd) # Create setups Z_a = self.atoms.get_atomic_numbers() assert 1 == self.nspins self.setups = Setups(Z_a, p.setups, p.basis, p.lmax, xc) self.natoms = len(self.setups) # Decide how many kpoints to sample from the 1st Brillouin Zone kpts_c = np.ceil( (10 / Bohr) / np.sum(cell_cv**2, axis=1)**0.5).astype(int) kpts_c = tuple(kpts_c * pbc_c + 1 - pbc_c) self.bzk_kc = kpts2ndarray(kpts_c) # Set up k-point descriptor self.kd = KPointDescriptor(self.bzk_kc, self.nspins) self.kd.set_symmetry(self.atoms, self.setups, p.usesymm) # Set the dtype if self.kd.gamma: self.dtype = float else: self.dtype = complex # Create communicators parsize, parsize_bands = self.get_parsizes() assert self.nbands % np.prod(parsize_bands) == 0 domain_comm, kpt_comm, band_comm = distribute_cpus( parsize, parsize_bands, self.nspins, self.kd.nibzkpts) self.kd.set_communicator(kpt_comm) # Set up band descriptor: self.bd = BandDescriptor(self.nbands, band_comm) # Set up grid descriptor: self.gd = GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize) # Set up kpoint/spin descriptor (to be removed): self.kd_old = KPointDescriptorOld(self.nspins, self.kd.nibzkpts, kpt_comm, self.kd.gamma, self.dtype)
def ibz2bz(self, atoms): """Transform wave functions in IBZ to the full BZ.""" assert self.kd.comm.size == 1 # New k-point descriptor for full BZ: kd = KPointDescriptor(self.kd.bzk_kc, nspins=self.nspins) #kd.set_symmetry(atoms, self.setups, enabled=False) kd.set_communicator(serial_comm) self.pt = LFC(self.gd, [setup.pt_j for setup in self.setups], kd, dtype=self.dtype) self.pt.set_positions(atoms.get_scaled_positions()) self.initialize_wave_functions_from_restart_file() weight = 2.0 / kd.nspins / kd.nbzkpts # Build new list of k-points: kpt_u = [] for s in range(self.nspins): for k in range(kd.nbzkpts): # Index of symmetry related point in the IBZ ik = self.kd.bz2ibz_k[k] r, u = self.kd.get_rank_and_index(s, ik) assert r == 0 kpt = self.kpt_u[u] phase_cd = np.exp(2j * np.pi * self.gd.sdisp_cd * kd.bzk_kc[k, :, np.newaxis]) # New k-point: kpt2 = KPoint(weight, s, k, k, phase_cd) kpt2.f_n = kpt.f_n / kpt.weight / kd.nbzkpts * 2 / self.nspins kpt2.eps_n = kpt.eps_n.copy() # Transform wave functions using symmetry operation: Psit_nG = self.gd.collect(kpt.psit_nG) if Psit_nG is not None: Psit_nG = Psit_nG.copy() for Psit_G in Psit_nG: Psit_G[:] = self.kd.transform_wave_function(Psit_G, k) kpt2.psit_nG = self.gd.empty(self.bd.nbands, dtype=self.dtype) self.gd.distribute(Psit_nG, kpt2.psit_nG) # Calculate PAW projections: kpt2.P_ani = self.pt.dict(len(kpt.psit_nG)) self.pt.integrate(kpt2.psit_nG, kpt2.P_ani, k) kpt_u.append(kpt2) self.kd = kd self.kpt_u = kpt_u
def calculate(self, q_c, spin='all', A_x=None): wfs = self.calc.wfs if spin == 'all': spins = range(wfs.nspins) else: assert spin in range(wfs.nspins) spins = [spin] q_c = np.asarray(q_c, dtype=float) qd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, wfs.gd, complex, qd) self.print_chi(pd) if extra_parameters.get('df_dry_run'): print(' Dry run exit', file=self.fd) raise SystemExit nG = pd.ngmax nw = len(self.omega_w) mynG = (nG + self.blockcomm.size - 1) // self.blockcomm.size self.Ga = self.blockcomm.rank * mynG self.Gb = min(self.Ga + mynG, nG) 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 np.allclose(q_c, 0.0): chi0_wxvG = np.zeros((len(self.omega_w), 2, 3, nG), complex) chi0_wvv = np.zeros((len(self.omega_w), 3, 3), complex) self.chi0_vv = np.zeros((3, 3), complex) else: chi0_wxvG = None chi0_wvv = None print('Initializing PAW Corrections', file=self.fd) self.Q_aGii = self.initialize_paw_corrections(pd) # Do all empty bands: m1 = self.nocc1 m2 = self.nbands self._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, self.Q_aGii, m1, m2, spins) return pd, chi0_wGG, chi0_wxvG, chi0_wvv
def setUp(self): UTDomainParallelSetup.setUp(self) for virtvar in ['dtype']: assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar # Create randomized atoms self.atoms = create_random_atoms(self.gd, 5) # also tested: 10xNH3/BDA # XXX DEBUG START if False: from ase import view view(self.atoms*(1+2*self.gd.pbc_c)) # XXX DEBUG END # Do we agree on the atomic positions? pos_ac = self.atoms.get_positions() pos_rac = np.empty((world.size,)+pos_ac.shape, pos_ac.dtype) world.all_gather(pos_ac, pos_rac) if (pos_rac-pos_rac[world.rank,...][np.newaxis,...]).any(): raise RuntimeError('Discrepancy in atomic positions detected.') # Create setups for atoms self.Z_a = self.atoms.get_atomic_numbers() self.setups = Setups(self.Z_a, p.setups, p.basis, p.lmax, xc) # K-point descriptor bzk_kc = np.array([[0, 0, 0]], dtype=float) self.kd = KPointDescriptor(bzk_kc, 1) self.kd.set_symmetry(self.atoms, self.setups) self.kd.set_communicator(self.kpt_comm) # Create gamma-point dummy wavefunctions self.wfs = FDWFS(self.gd, self.bd, self.kd, self.setups, self.block_comm, self.dtype) spos_ac = self.atoms.get_scaled_positions() % 1.0 self.wfs.set_positions(spos_ac) self.pt = self.wfs.pt # XXX shortcut ## Also create pseudo partial waveves #from gpaw.lfc import LFC #self.phit = LFC(self.gd, [setup.phit_j for setup in self.setups], \ # self.kpt_comm, dtype=self.dtype) #self.phit.set_positions(spos_ac) self.r_cG = None self.buf_G = None self.psit_nG = None self.allocate()
def dscf_load_band(filename, paw, molecule=None): """Load and distribute all information for a band from a tar file.""" if not paw.wfs: paw.initialize() world, bd, gd, kd = paw.wfs.world, paw.wfs.bd, paw.wfs.gd, \ KPointDescriptor(paw.wfs.nspins, paw.wfs.nibzkpts, paw.wfs.kpt_comm, \ paw.wfs.gamma, paw.wfs.dtype) if bd.comm.size != 1: raise NotImplementedError('Undefined action for band parallelization.') r = Reader(filename) assert (r.dimension('nspins') == kd.nspins and \ r.dimension('nibzkpts') == kd.nibzkpts), 'Incompatible spin/kpoints.' # Read wave function for every spin/kpoint owned by this rank psit_uG = gd.empty(kd.mynks, kd.dtype) for myu, psit_G in enumerate(psit_uG): u = kd.global_index(myu) s, k = kd.what_is(u) if gd.comm.rank == 0: big_psit_G = np.array(r.get('PseudoWaveFunction', s, k), kd.dtype) else: big_psit_G = None gd.distribute(big_psit_G, psit_G) # Find domain ranks for each atom atoms = paw.get_atoms() spos_ac = atoms.get_scaled_positions() % 1.0 rank_a = gd.get_ranks_from_positions(spos_ac) #my_atom_indices = np.argwhere(rank_a == gd.comm.rank).ravel() #assert np.all(my_atom_indices == paw.wfs.pt.my_atom_indices) assert r.dimension('nproj') == sum([setup.ni for setup in paw.wfs.setups]) if molecule is None: molecule = range(len(atoms)) # Read projections for every spin/kpoint and atom owned by this rank P_uai = [{}] * kd.mynks #[paw.wfs.pt.dict() for myu in range(kd.mynks)] for myu, P_ai in enumerate(P_uai): u = kd.global_index(myu) s, k = kd.what_is(u) P_i = r.get('Projection', s, k) i1 = 0 for a in molecule: setup = paw.wfs.setups[a] i2 = i1 + setup.ni if gd.comm.rank == rank_a[a]: P_ai[a] = np.array(P_i[i1:i2], kd.dtype) i1 = i2 return psit_uG, P_uai
def check(self, i_cG, shift0_c, N_c, q_c, Q_aGii): I0_G = np.ravel_multi_index(i_cG - shift0_c[:, None], N_c, 'wrap') qd1 = KPointDescriptor([q_c]) pd1 = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd1) G_I = np.empty(N_c.prod(), int) G_I[:] = -1 I1_G = pd1.Q_qG[0] G_I[I1_G] = np.arange(len(I0_G)) G_G = G_I[I0_G] assert len(I0_G) == len(I1_G) assert (G_G >= 0).all() for a, Q_Gii in enumerate(self.initialize_paw_corrections(pd1)): e = abs(Q_aGii[a] - Q_Gii[G_G]).max() assert e < 1e-12
def calculate_gamma(self, vol, alpha): if self.molecule: return 0.0 N_c = self.kd.N_c offset_c = (N_c + 1) % 2 * 0.5 / N_c bzq_qc = monkhorst_pack(N_c) + offset_c qd = KPointDescriptor(bzq_qc) pd = PWDescriptor(self.wfs.pd.ecut, self.wfs.gd, kd=qd) gamma = (vol / (2 * pi)**2 * sqrt(pi / alpha) * self.kd.nbzkpts) for G2_G in pd.G2_qG: if G2_G[0] < 1e-7: G2_G = G2_G[1:] gamma -= np.dot(np.exp(-alpha * G2_G), G2_G**-1) return gamma / self.qstride_c.prod()
def dscf_save_band(filename, paw, n): """Extract and save all information for band `n` to a tar file.""" world, bd, gd, kd = paw.wfs.world, paw.wfs.bd, paw.wfs.gd, \ KPointDescriptor(paw.wfs.nspins, paw.wfs.nibzkpts, paw.wfs.kpt_comm, \ paw.wfs.gamma, paw.wfs.dtype) if world.rank == 0: # Minimal amount of information needed: w = Writer(filename) w.dimension('nspins', kd.nspins) w.dimension('nibzkpts', kd.nibzkpts) w.dimension('nproj', sum([setup.ni for setup in paw.wfs.setups])) ng = gd.get_size_of_global_array() w.dimension('ngptsx', ng[0]) w.dimension('ngptsy', ng[1]) w.dimension('ngptsz', ng[2]) # Write projections: if world.rank == 0: w.add('Projection', ('nspins', 'nibzkpts', 'nproj'), dtype=kd.dtype) for s in range(kd.nspins): for k in range(kd.nibzkpts): all_P_ni = paw.wfs.collect_projections(k, s) # gets all bands if world.rank == 0: w.fill(all_P_ni[n]) # Write wave functions: if world.rank == 0: w.add('PseudoWaveFunction', ('nspins', 'nibzkpts', 'ngptsx', 'ngptsy', 'ngptsz'), dtype=kd.dtype) for s in range(kd.nspins): for k in range(kd.nibzkpts): psit_G = paw.wfs.get_wave_function_array(n, k, s) if world.rank == 0: w.fill(psit_G) if world.rank == 0: # Close the file here to ensure that the last wave function is # written to disk: w.close() # We don't want the slaves to start reading before the master has # finished writing: world.barrier()
def setUp(self): UTDomainParallelSetup.setUp(self) for virtvar in ['dtype']: assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar # Set up kpoint descriptor: self.kd = KPointDescriptor(self.nspins, self.nibzkpts, self.kpt_comm, \ self.gamma, self.dtype) # Choose a sufficiently small width of gaussian test functions cell_c = np.sum(self.gd.cell_cv**2, axis=1)**0.5 self.sigma = np.min((0.1+0.4*self.gd.pbc_c)*cell_c) if debug and world.rank == 0: print 'sigma=%8.5f Ang' % (self.sigma*Bohr), 'cell_c:', cell_c*Bohr, 'Ang', 'N_c:', self.gd.N_c self.atoms = create_random_atoms(self.gd, 4, 'H', 4*self.sigma) self.r_vG = None self.wf_uG = None self.laplace0_uG = None self.allocate()
def setUp(self): for virtvar in ['equipartition']: assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar kpts = {'even' : (12,1,2), \ 'prime': (23,1,1)}[self.equipartition] #primes = [i for i in xrange(50,1,-1) if ~np.any(i%np.arange(2,i)==0)] bzk_kc = kpts2ndarray(kpts) assert p.usesymm == None self.nibzkpts = len(bzk_kc) #parsize, parsize_bands = create_parsize_minbands(self.nbands, world.size) parsize, parsize_bands = 1, 1 #XXX assert self.nbands % np.prod(parsize_bands) == 0 domain_comm, kpt_comm, band_comm = distribute_cpus(parsize, parsize_bands, self.nspins, self.nibzkpts) # Set up band descriptor: self.bd = BandDescriptor(self.nbands, band_comm, p.parallel['stridebands']) # Set up grid descriptor: res, ngpts = shapeopt(300, self.G**3, 3, 0.2) cell_c = self.h * np.array(ngpts) pbc_c = (True, False, True) self.gd = GridDescriptor(ngpts, cell_c, pbc_c, domain_comm, parsize) # Create randomized gas-like atomic configuration self.atoms = create_random_atoms(self.gd) # Create setups Z_a = self.atoms.get_atomic_numbers() self.setups = Setups(Z_a, p.setups, p.basis, p.lmax, xc) self.natoms = len(self.setups) # Set up kpoint descriptor: self.kd = KPointDescriptor(bzk_kc, self.nspins) self.kd.set_symmetry(self.atoms, self.setups, p.usesymm) self.kd.set_communicator(kpt_comm)
def calculate_q(self, i, kpt1, kpt2): wfs = self.calc.wfs q_c = wfs.kd.bzk_kc[kpt2.K] - wfs.kd.bzk_kc[kpt1.K] qd = KPointDescriptor([q_c]) pd = PWDescriptor(self.ecut, wfs.gd, wfs.dtype, kd=qd) Q_G = self.get_fft_indices(kpt1.K, kpt2.K, q_c, pd, kpt1.shift_c - kpt2.shift_c) Q_aGii = self.initialize_paw_corrections(pd, soft=True) for n in range(kpt1.n2 - kpt1.n1): ut1cc_R = kpt1.ut_nR[n].conj() C1_aGi = [ np.dot(Q_Gii, P1_ni[n].conj()) for Q_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani) ] n_mG = self.calculate_pair_densities(ut1cc_R, C1_aGi, kpt2, pd, Q_G) e = self.calculate_n(pd, n, n_mG, kpt2) self.exxvv_sin[kpt1.s, i, n] += e
def __init__(self, atoms, kpts, symmetry): """Initialize base class and attributes. Parameters ---------- atoms: Atoms ASE atoms. kpts: tuple or list of tuples Shape of Monkhorst-Pack grid or list of k-points used in the dfpt calculation. symmetry: bool or None Symmetry parameter used in dfpt calculation. """ # Init base class with ``Atoms`` object phonons.Phonons.__init__(atoms) # Create k-point descriptor self.kd = KPointDescriptor(kpts, 1) self.kd.set_symmetry(self.atoms, self.calc.wfs.setups, symmetry) # Overwrite ``N_c`` attribute self.N_c = tuple(self.kd.N_c) # Index of the gamma point -- for the acoustic sum-rule self.gamma_index = None if self.kd.gamma: self.gamma_index = 0 self.dtype == float else: self.dtype == comples for k, k_c in enumerate(self.kd.ibzk_kc): if np.all(k_c == 0.): self.gamma_index = k assert self.gamma_index is not None
def initialize(self, density, hamiltonian, wfs, occupations): self.xc.initialize(density, hamiltonian, wfs, occupations) self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.wfs = wfs self.gd = density.gd self.kd = wfs.kd self.bd = wfs.bd N_c = self.gd.N_c N = self.gd.N_c.prod() vol = self.gd.dv * N if self.alpha is None: # XXX ? self.alpha = 6 * vol**(2 / 3.0) / pi**2 self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) * self.kd.nbzkpts) if self.ecut is None: self.ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max() * 0.9999 assert self.kd.N_c is not None n = self.kd.N_c * 2 - 1 bzk_kc = np.indices(n).transpose((1, 2, 3, 0)) bzk_kc.shape = (-1, 3) bzk_kc -= self.kd.N_c - 1 self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c self.bzq_qc = self.kd.get_bz_q_points() if self.qsym: op_scc = self.kd.symmetry.op_scc self.ibzq_qc = self.kd.get_ibz_q_points(self.bzq_qc, op_scc)[0] self.q_weights = self.kd.q_weights * len(self.bzq_qc) else: self.ibzq_qc = self.bzq_qc self.q_weights = np.ones(len(self.bzq_qc)) self.pwd = PWDescriptor(self.ecut, self.gd, complex) self.G2_qG = self.pwd.g2(self.bzk_kc) n = 0 for k_c, Gpk2_G in zip(self.bzk_kc[:], self.G2_qG): if (k_c > -0.5).all() and (k_c <= 0.5).all(): #XXX??? if k_c.any(): self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G), Gpk2_G**-1) else: self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]), Gpk2_G[1:]**-1) n += 1 assert n == self.kd.N_c.prod() self.pwd = PWDescriptor(self.ecut, self.gd, complex) self.G2_qG = self.pwd.g2(self.ibzq_qc) self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups], KPointDescriptor(self.bzq_qc), dtype=complex) #self.interpolator = density.interpolator self.print_initialization(hamiltonian.xc.name)
def 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 bloch_matrix(self, kpts, qpts, c_kn, u_ql, omega_ql=None, kpts_from=None): """Calculate el-ph coupling in the Bloch basis for the electrons. This function calculates the electron-phonon coupling between the specified Bloch states, i.e.:: ______ mnl / hbar ^ g = /------- < m k + q | e . grad V | n k > kq \/ 2 M w ql q ql In case the ``omega_ql`` keyword argument is not given, the bare matrix element (in units of eV / Ang) without the sqrt prefactor is returned. Parameters ---------- kpts: ndarray or tuple. k-vectors of the Bloch states. When a tuple of integers is given, a Monkhorst-Pack grid with the specified number of k-points along the directions of the reciprocal lattice vectors is generated. qpts: ndarray or tuple. q-vectors of the phonons. c_kn: ndarray Expansion coefficients for the Bloch states. The ordering must be the same as in the ``kpts`` argument. u_ql: ndarray Mass-scaled polarization vectors (in units of 1 / sqrt(amu)) of the phonons. Again, the ordering must be the same as in the corresponding ``qpts`` argument. omega_ql: ndarray Vibrational frequencies in eV. kpts_from: list of ints or int Calculate only the matrix element for the k-vectors specified by their index in the ``kpts`` argument (default: all). In short, phonon frequencies and mode vectors must be given in ase units. """ assert self.g_xNNMM is not None, "Load supercell matrix." assert len(c_kn.shape) == 3 assert len(u_ql.shape) == 4 if omega_ql is not None: assert np.all(u_ql.shape[:2] == omega_ql.shape[:2]) # Translate k-points into 1. BZ (required by ``find_k_plus_q``` member # function of the ```KPointDescriptor``). if isinstance(kpts, np.ndarray): assert kpts.shape[1] == 3, "kpts_kc array must be given" # XXX This does not seem to cause problems! kpts -= kpts.round() # Use the KPointDescriptor to keep track of the k and q-vectors kd_kpts = KPointDescriptor(kpts) kd_qpts = KPointDescriptor(qpts) # Check that number of k- and q-points agree with the number of Bloch # functions and polarization vectors assert kd_kpts.nbzkpts == len(c_kn) assert kd_qpts.nbzkpts == len(u_ql) # Include all k-point per default if kpts_from is None: kpts_kc = kd_kpts.bzk_kc kpts_k = range(kd_kpts.nbzkpts) else: kpts_kc = kd_kpts.bzk_kc[kpts_from] if isinstance(kpts_from, int): kpts_k = list([kpts_from]) else: kpts_k = list(kpts_from) # Supercell matrix (real matrix in Hartree / Bohr) g_xNNMM = self.g_xNNMM # Number of phonon modes and electronic bands nmodes = u_ql.shape[1] nbands = c_kn.shape[1] # Number of atoms displacements and basis functions ndisp = np.prod(u_ql.shape[2:]) assert ndisp == (3 * len(self.indices)) nao = c_kn.shape[2] assert ndisp == g_xNNMM.shape[0] assert nao == g_xNNMM.shape[-1] # Lattice vectors R_cN = self.lattice_vectors() # Number of unit cell in supercell N = np.prod(self.N_c) # Allocate array for couplings g_qklnn = np.zeros( (kd_qpts.nbzkpts, len(kpts_kc), nmodes, nbands, nbands), dtype=complex) self.timer.write_now("Calculating coupling matrix elements") for q, q_c in enumerate(kd_qpts.bzk_kc): # Find indices of k+q for the k-points kplusq_k = kd_kpts.find_k_plus_q(q_c, kpts_k=kpts_k) # Here, ``i`` is counting from 0 and ``k`` is the global index of # the k-point for i, (k, k_c) in enumerate(zip(kpts_k, kpts_kc)): # Check the wave vectors (adapted to the ``KPointDescriptor`` class) kplusq_c = k_c + q_c kplusq_c -= kplusq_c.round() assert np.allclose(kplusq_c, kd_kpts.bzk_kc[kplusq_k[i]] ), \ (i, k, k_c, q_c, kd_kpts.bzk_kc[kplusq_k[i]]) # Allocate array g_xMM = np.zeros((ndisp, nao, nao), dtype=complex) # Multiply phase factors for m in range(N): for n in range(N): Rm_c = R_cN[:, m] Rn_c = R_cN[:, n] phase = np.exp( 2.j * pi * (np.dot(k_c, Rm_c - Rn_c) + np.dot(q_c, Rm_c))) # Sum contributions from different cells g_xMM += g_xNNMM[:, m, n, :, :] * phase # LCAO coefficient for Bloch states ck_nM = c_kn[k] ckplusq_nM = c_kn[kplusq_k[i]] # Mass scaled polarization vectors u_lx = u_ql[q].reshape(nmodes, 3 * len(self.atoms)) g_nxn = np.dot(ckplusq_nM.conj(), np.dot(g_xMM, ck_nM.T)) g_lnn = np.dot(u_lx, g_nxn) # Insert value g_qklnn[q, i] = g_lnn # XXX Temp if np.all(q_c == 0.0): # These should be real print(g_qklnn[q].imag.min(), g_qklnn[q].imag.max()) self.timer.write_now( "Finished calculation of coupling matrix elements") # Return the bare matrix element if frequencies are not given if omega_ql is None: # Convert to eV / Ang g_qklnn *= units.Hartree / units.Bohr else: # Multiply prefactor sqrt(hbar / 2 * M * omega) in units of Bohr amu = units._amu # atomic mass unit me = units._me # electron mass g_qklnn /= np.sqrt(2 * amu / me / units.Hartree * \ omega_ql[:, np.newaxis, :, np.newaxis, np.newaxis]) # Convert to eV g_qklnn *= units.Hartree # Return couplings in eV (or eV / Ang) return g_qklnn
def calculate_exx(self): """Non-selfconsistent calculation.""" self.timer.start('EXX') self.timer.start('Initialization') kd = self.kd wfs = self.wfs if fftw.FFTPlan is fftw.NumpyFFTPlan: self.log('NOT USING FFTW !!') self.log('Spins:', self.wfs.nspins) W = max(1, self.wfs.kd.comm.size // self.wfs.nspins) # Are the k-points distributed? kparallel = (W > 1) # Find number of occupied bands: self.nocc_sk = np.zeros((self.wfs.nspins, kd.nibzkpts), int) for kpt in self.wfs.kpt_u: for n, f in enumerate(kpt.f_n): if abs(f) < self.fcut: self.nocc_sk[kpt.s, kpt.k] = n break else: self.nocc_sk[kpt.s, kpt.k] = self.wfs.bd.nbands self.wfs.kd.comm.sum(self.nocc_sk) noccmin = self.nocc_sk.min() noccmax = self.nocc_sk.max() self.log('Number of occupied bands (min, max): %d, %d' % (noccmin, noccmax)) self.log('Number of valence electrons:', self.wfs.setups.nvalence) if self.bandstructure: self.log('Calculating eigenvalue shifts.') # allocate array for eigenvalue shifts: self.exx_skn = np.zeros( (self.wfs.nspins, kd.nibzkpts, self.wfs.bd.nbands)) if self.bands is None: noccmax = self.wfs.bd.nbands else: noccmax = max(max(self.bands) + 1, noccmax) N_c = self.kd.N_c vol = wfs.gd.dv * wfs.gd.N_c.prod() if self.alpha is None: alpha = 6 * vol**(2 / 3.0) / pi**2 else: alpha = self.alpha if self.gamma_point == 1: if alpha == 0.0: qvol = (2 * np.pi)**3 / vol / N_c.prod() self.gamma = 4 * np.pi * (3 * qvol / (4 * np.pi))**(1 / 3.) / qvol else: self.gamma = self.calculate_gamma(vol, alpha) else: kcell_cv = wfs.gd.cell_cv.copy() kcell_cv[0] *= N_c[0] kcell_cv[1] *= N_c[1] kcell_cv[2] *= N_c[2] self.gamma = madelung(kcell_cv) * vol * N_c.prod() / (4 * np.pi) self.log('Value of alpha parameter: %.3f Bohr^2' % alpha) self.log('Value of gamma parameter: %.3f Bohr^2' % self.gamma) # Construct all possible q=k2-k1 vectors: Nq_c = (N_c - 1) // self.qstride_c i_qc = np.indices(Nq_c * 2 + 1, float).transpose((1, 2, 3, 0)).reshape( (-1, 3)) self.bzq_qc = (i_qc - Nq_c) / N_c * self.qstride_c self.q0 = ((Nq_c * 2 + 1).prod() - 1) // 2 # index of q=(0,0,0) assert not self.bzq_qc[self.q0].any() # Count number of pairs for each q-vector: self.npairs_q = np.zeros(len(self.bzq_qc), int) for s in range(kd.nspins): for k1 in range(kd.nibzkpts): for k2 in range(kd.nibzkpts): for K2, q, n1_n, n2 in self.indices(s, k1, k2): self.npairs_q[q] += len(n1_n) self.npairs0 = self.npairs_q.sum() # total number of pairs self.log('Number of pairs:', self.npairs0) # Distribute q-vectors to Q processors: Q = self.world.size // self.wfs.kd.comm.size myrank = self.world.rank // self.wfs.kd.comm.size rank = 0 N = 0 myq = [] nq = 0 for q, n in enumerate(self.npairs_q): if n > 0: nq += 1 if rank == myrank: myq.append(q) N += n if N >= (rank + 1.0) * self.npairs0 / Q: rank += 1 assert len(myq) > 0, 'Too few q-vectors for too many processes!' self.bzq_qc = self.bzq_qc[myq] try: self.q0 = myq.index(self.q0) except ValueError: self.q0 = None self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) self.log('Distributing %d IBZ k-points over %d process(es).' % (kd.nibzkpts, self.wfs.kd.comm.size)) self.log('Distributing %d q-vectors over %d process(es).' % (nq, Q)) # q-point descriptor for my q-vectors: qd = KPointDescriptor(self.bzq_qc) # Plane-wave descriptor for all wave-functions: self.pd = PWDescriptor(wfs.pd.ecut, wfs.gd, dtype=wfs.pd.dtype, kd=kd) # Plane-wave descriptor pair-densities: self.pd2 = PWDescriptor(self.dens.pd2.ecut, self.dens.gd, dtype=wfs.dtype, kd=qd) self.log('Cutoff energies:') self.log(' Wave functions: %10.3f eV' % (self.pd.ecut * Hartree)) self.log(' Density: %10.3f eV' % (self.pd2.ecut * Hartree)) # Calculate 1/|G+q|^2 with special treatment of |G+q|=0: G2_qG = self.pd2.G2_qG if self.q0 is None: if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] else: self.iG2_qG = [ (1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG ] else: G2_qG[self.q0][0] = 117.0 # avoid division by zero if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] self.iG2_qG[self.q0][0] = self.gamma else: self.iG2_qG = [ (1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG ] self.iG2_qG[self.q0][0] = 1 / (4 * self.omega**2) G2_qG[self.q0][0] = 0.0 # restore correct value # Compensation charges: self.ghat = PWLFC([setup.ghat_l for setup in wfs.setups], self.pd2) self.ghat.set_positions(self.spos_ac) if self.molecule: self.initialize_gaussian() self.log('Value of beta parameter: %.3f 1/Bohr^2' % self.beta) self.timer.stop('Initialization') # Ready ... set ... go: self.t0 = time() self.npairs = 0 self.evv = 0.0 self.evvacdf = 0.0 for s in range(self.wfs.nspins): kpt1_q = [ KPoint(self.wfs, noccmax).initialize(kpt) for kpt in self.wfs.kpt_u if kpt.s == s ] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send and receive ranks: srank = self.wfs.kd.get_rank_and_index(s, (kpt1_q[0].k - 1) % kd.nibzkpts)[0] rrank = self.wfs.kd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % kd.nibzkpts)[0] # Shift k-points kd.nibzkpts - 1 times: for i in range(kd.nibzkpts): if i < kd.nibzkpts - 1: if kparallel: kpt = kpt2_q[-1].next(self.wfs) kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] self.timer.start('Calculate') for kpt1, kpt2 in zip(kpt1_q, kpt2_q): # Loop over all k-points that k2 can be mapped to: for K2, q, n1_n, n2 in self.indices(s, kpt1.k, kpt2.k): self.apply(K2, q, kpt1, kpt2, n1_n, n2) self.timer.stop('Calculate') if i < kd.nibzkpts - 1: self.timer.start('Wait') if kparallel: kpt.wait() kpt2_q[0].wait() self.timer.stop('Wait') kpt2_q.pop(0) kpt2_q.append(kpt) self.evv = self.world.sum(self.evv) self.evvacdf = self.world.sum(self.evvacdf) self.calculate_exx_paw_correction() if self.method == 'standard': self.exx = self.evv + self.devv + self.evc + self.ecc elif self.method == 'acdf': self.exx = self.evvacdf + self.devv + self.evc + self.ecc else: 1 / 0 self.log('Exact exchange energy:') for txt, e in [('core-core', self.ecc), ('valence-core', self.evc), ('valence-valence (pseudo, acdf)', self.evvacdf), ('valence-valence (pseudo, standard)', self.evv), ('valence-valence (correction)', self.devv), ('total (%s)' % self.method, self.exx)]: self.log(' %-36s %14.6f eV' % (txt + ':', e * Hartree)) self.log('Total time: %10.3f seconds' % (time() - self.t0)) self.npairs = self.world.sum(self.npairs) assert self.npairs == self.npairs0 self.timer.stop('EXX') self.timer.write(self.fd)
def initialize(self, atoms=None): """Inexpensive initialization.""" if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() par = self.input_parameters world = par.communicator if world is None: world = mpi.world elif hasattr(world, 'new_communicator'): # Check for whether object has correct type already # # Using isinstance() is complicated because of all the # combinations, serial/parallel/debug... pass else: # world should be a list of ranks: world = mpi.world.new_communicator(np.asarray(world)) self.wfs.world = world if 'txt' in self._changed_keywords: self.set_txt(par.txt) self.verbose = par.verbose natoms = len(atoms) cell_cv = atoms.get_cell() / Bohr pbc_c = atoms.get_pbc() Z_a = atoms.get_atomic_numbers() magmom_av = atoms.get_initial_magnetic_moments() self.check_atoms() # Generate new xc functional only when it is reset by set # XXX sounds like this should use the _changed_keywords dictionary. if self.hamiltonian is None or self.hamiltonian.xc is None: if isinstance(par.xc, str): xc = XC(par.xc) else: xc = par.xc else: xc = self.hamiltonian.xc mode = par.mode if mode == 'fd': mode = FD() elif mode == 'pw': mode = pw.PW() elif mode == 'lcao': mode = LCAO() else: assert hasattr(mode, 'name'), str(mode) if xc.orbital_dependent and mode.name == 'lcao': raise NotImplementedError('LCAO mode does not support ' 'orbital-dependent XC functionals.') if par.realspace is None: realspace = (mode.name != 'pw') else: realspace = par.realspace if mode.name == 'pw': assert not realspace if par.filter is None and mode.name != 'pw': gamma = 1.6 if par.gpts is not None: h = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5 / par.gpts).max() else: h = (par.h or 0.2) / Bohr def filter(rgd, rcut, f_r, l=0): gcut = np.pi / h - 2 / rcut / gamma f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l) else: filter = par.filter setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc, filter, world) if magmom_av.ndim == 1: collinear = True magmom_av, magmom_a = np.zeros((natoms, 3)), magmom_av magmom_av[:, 2] = magmom_a else: collinear = False magnetic = magmom_av.any() spinpol = par.spinpol if par.hund: if natoms != 1: raise ValueError('hund=True arg only valid for single atoms!') spinpol = True magmom_av[0] = (0, 0, setups[0].get_hunds_rule_moment(par.charge)) if spinpol is None: spinpol = magnetic elif magnetic and not spinpol: raise ValueError('Non-zero initial magnetic moment for a ' + 'spin-paired calculation!') if collinear: nspins = 1 + int(spinpol) ncomp = 1 else: nspins = 1 ncomp = 2 if par.usesymm != 'default': warnings.warn('Use "symmetry" keyword instead of ' + '"usesymm" keyword') par.symmetry = usesymm2symmetry(par.usesymm) symm = par.symmetry if symm == 'off': symm = {'point_group': False, 'time_reversal': False} bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kd = KPointDescriptor(bzkpts_kc, nspins, collinear) m_av = magmom_av.round(decimals=3) # round off id_a = zip(setups.id_a, *m_av.T) symmetry = Symmetry(id_a, cell_cv, atoms.pbc, **symm) kd.set_symmetry(atoms, symmetry, comm=world) setups.set_symmetry(symmetry) if par.gpts is not None: N_c = np.array(par.gpts) else: h = par.h if h is not None: h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, kd.symmetry) symmetry.check_grid(N_c) width = par.width if width is None: if pbc_c.any(): width = 0.1 # eV else: width = 0.0 else: assert par.occupations is None if hasattr(self, 'time') or par.dtype == complex: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex nao = setups.nao nvalence = setups.nvalence - par.charge M_v = magmom_av.sum(0) M = np.dot(M_v, M_v)**0.5 nbands = par.nbands orbital_free = any(setup.orbital_free for setup in setups) if orbital_free: nbands = 1 if isinstance(nbands, basestring): if nbands[-1] == '%': basebands = int(nvalence + M + 0.5) // 2 nbands = int((float(nbands[:-1]) / 100) * basebands) else: raise ValueError('Integer Expected: Only use a string ' 'if giving a percentage of occupied bands') if nbands is None: nbands = 0 for setup in setups: nbands_from_atom = setup.get_default_nbands() # Any obscure setup errors? if nbands_from_atom < -(-setup.Nv // 2): raise ValueError('Bad setup: This setup requests %d' ' bands but has %d electrons.' % (nbands_from_atom, setup.Nv)) nbands += nbands_from_atom nbands = min(nao, nbands) elif nbands > nao and mode.name == 'lcao': raise ValueError('Too many bands for LCAO calculation: ' '%d bands and only %d atomic orbitals!' % (nbands, nao)) if nvalence < 0: raise ValueError( 'Charge %f is not possible - not enough valence electrons' % par.charge) if nbands <= 0: nbands = int(nvalence + M + 0.5) // 2 + (-nbands) if nvalence > 2 * nbands and not orbital_free: raise ValueError('Too few bands! Electrons: %f, bands: %d' % (nvalence, nbands)) nbands *= ncomp if par.width is not None: self.text('**NOTE**: please start using ' 'occupations=FermiDirac(width).') if par.fixmom: self.text('**NOTE**: please start using ' 'occupations=FermiDirac(width, fixmagmom=True).') if self.occupations is None: if par.occupations is None: # Create object for occupation numbers: if orbital_free: width = 0.0 # even for PBC self.occupations = occupations.TFOccupations( width, par.fixmom) else: self.occupations = occupations.FermiDirac( width, par.fixmom) else: self.occupations = par.occupations # If occupation numbers are changed, and we have wave functions, # recalculate the occupation numbers if self.wfs is not None and not isinstance(self.wfs, EmptyWaveFunctions): self.occupations.calculate(self.wfs) self.occupations.magmom = M_v[2] cc = par.convergence if mode.name == 'lcao': niter_fixdensity = 0 else: niter_fixdensity = None if self.scf is None: force_crit = cc['forces'] if force_crit is not None: force_crit /= Hartree / Bohr self.scf = SCFLoop(cc['eigenstates'] / Hartree**2 * nvalence, cc['energy'] / Hartree * max(nvalence, 1), cc['density'] * nvalence, par.maxiter, par.fixdensity, niter_fixdensity, force_crit) parsize_kpt = par.parallel['kpt'] parsize_domain = par.parallel['domain'] parsize_bands = par.parallel['band'] if not realspace: pbc_c = np.ones(3, bool) if not self.wfs: if parsize_domain == 'domain only': # XXX this was silly! parsize_domain = world.size parallelization = mpi.Parallelization(world, nspins * kd.nibzkpts) ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) if mode.name == 'pw': if ndomains > 1: raise ValueError('Planewave mode does not support ' 'domain decomposition.') ndomains = 1 parallelization.set(kpt=parsize_kpt, domain=ndomains, band=parsize_bands) comms = parallelization.build_communicators() domain_comm = comms['d'] kpt_comm = comms['k'] band_comm = comms['b'] kptband_comm = comms['D'] domainband_comm = comms['K'] self.comms = comms kd.set_communicator(kpt_comm) parstride_bands = par.parallel['stridebands'] # Unfortunately we need to remember that we adjusted the # number of bands so we can print a warning if it differs # from the number specified by the user. (The number can # be inferred from the input parameters, but it's tricky # because we allow negative numbers) self.nbands_parallelization_adjustment = -nbands % band_comm.size nbands += self.nbands_parallelization_adjustment # I would like to give the following error message, but apparently # there are cases, e.g. gpaw/test/gw_ppa.py, which involve # nbands > nao and are supposed to work that way. #if nbands > nao: # raise ValueError('Number of bands %d adjusted for band ' # 'parallelization %d exceeds number of atomic ' # 'orbitals %d. This problem can be fixed ' # 'by reducing the number of bands a bit.' # % (nbands, band_comm.size, nao)) bd = BandDescriptor(nbands, band_comm, parstride_bands) if (self.density is not None and self.density.gd.comm.size != domain_comm.size): # Domain decomposition has changed, so we need to # reinitialize density and hamiltonian: if par.fixdensity: raise RuntimeError( 'Density reinitialization conflict ' + 'with "fixdensity" - specify domain decomposition.') self.density = None self.hamiltonian = None # Construct grid descriptor for coarse grids for wave functions: gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) # do k-point analysis here? XXX args = (gd, nvalence, setups, bd, dtype, world, kd, kptband_comm, self.timer) if par.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in par.parallel.items(): if (key.startswith('sl_') and key != 'sl_auto' and val is not None): raise ValueError("Cannot use 'sl_auto' together " "with '%s'" % key) max_scalapack_cpus = bd.comm.size * gd.comm.size nprow = max_scalapack_cpus npcol = 1 # Get a sort of reasonable number of columns/rows while npcol < nprow and nprow % 2 == 0: npcol *= 2 nprow //= 2 assert npcol * nprow == max_scalapack_cpus # ScaLAPACK creates trouble if there aren't at least a few # whole blocks; choose block size so there will always be # several blocks. This will crash for small test systems, # but so will ScaLAPACK in any case blocksize = min(-(-nbands // 4), 64) sl_default = (nprow, npcol, blocksize) else: sl_default = par.parallel['sl_default'] if mode.name == 'lcao': # Layouts used for general diagonalizer sl_lcao = par.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, bd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(collinear, lcaoksl, *args) elif mode.name == 'fd' or mode.name == 'pw': # buffer_size keyword only relevant for fdpw buffer_size = par.parallel['buffer_size'] # Layouts used for diagonalizer sl_diagonalize = par.parallel['sl_diagonalize'] if sl_diagonalize is None: sl_diagonalize = sl_default diagksl = get_KohnSham_layouts( sl_diagonalize, 'fd', # XXX # choice of key 'fd' not so nice gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Layouts used for orthonormalizer sl_inverse_cholesky = par.parallel['sl_inverse_cholesky'] if sl_inverse_cholesky is None: sl_inverse_cholesky = sl_default if sl_inverse_cholesky != sl_diagonalize: message = 'sl_inverse_cholesky != sl_diagonalize ' \ 'is not implemented.' raise NotImplementedError(message) orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd', gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Use (at most) all available LCAO for initialization lcaonbands = min(nbands, nao) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = par.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default initksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, lcaobd, domainband_comm, dtype, nao=nao, timer=self.timer) if hasattr(self, 'time'): assert mode.name == 'fd' from gpaw.tddft import TimeDependentWaveFunctions self.wfs = TimeDependentWaveFunctions( par.stencils[0], diagksl, orthoksl, initksl, gd, nvalence, setups, bd, world, kd, kptband_comm, self.timer) elif mode.name == 'fd': self.wfs = mode(par.stencils[0], diagksl, orthoksl, initksl, *args) else: assert mode.name == 'pw' self.wfs = mode(diagksl, orthoksl, initksl, *args) else: self.wfs = mode(self, *args) else: self.wfs.set_setups(setups) if not self.wfs.eigensolver: # Number of bands to converge: nbands_converge = cc['bands'] if nbands_converge == 'all': nbands_converge = nbands elif nbands_converge != 'occupied': assert isinstance(nbands_converge, int) if nbands_converge < 0: nbands_converge += nbands eigensolver = get_eigensolver(par.eigensolver, mode, par.convergence) eigensolver.nbands_converge = nbands_converge # XXX Eigensolver class doesn't define an nbands_converge property if isinstance(xc, SIC): eigensolver.blocksize = 1 self.wfs.set_eigensolver(eigensolver) if self.density is None: gd = self.wfs.gd if par.stencils[1] != 9: # Construct grid descriptor for fine grids for densities # and potentials: finegd = gd.refine() else: # Special case (use only coarse grid): finegd = gd if realspace: self.density = RealSpaceDensity( gd, finegd, nspins, par.charge + setups.core_charge, collinear, par.stencils[1]) else: self.density = pw.ReciprocalSpaceDensity( gd, finegd, nspins, par.charge + setups.core_charge, collinear) self.density.initialize(setups, self.timer, magmom_av, par.hund) self.density.set_mixer(par.mixer) if self.hamiltonian is None: gd, finegd = self.density.gd, self.density.finegd if realspace: self.hamiltonian = RealSpaceHamiltonian( gd, finegd, nspins, setups, self.timer, xc, world, self.wfs.kptband_comm, par.external, collinear, par.poissonsolver, par.stencils[1]) else: self.hamiltonian = pw.ReciprocalSpaceHamiltonian( gd, finegd, self.density.pd2, self.density.pd3, nspins, setups, self.timer, xc, world, self.wfs.kptband_comm, par.external, collinear) xc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) self.text() self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth) self.txt.flush() self.timer.print_info(self) if dry_run: self.dry_run() if realspace and \ self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': self.hamiltonian.poisson.set_density(self.density) self.hamiltonian.poisson.print_messages(self.text) self.txt.flush() self.initialized = True self._changed_keywords.clear()
def __init__(self, calc, nbands=None, Fock=False): self.calc = calc self.Fock = Fock self.K = calc.get_ibz_k_points() # reduced Brillioun zone self.NK = self.K.shape[0] self.wk = calc.get_k_point_weights( ) # weight of reduced Brillioun zone if nbands is None: self.nbands = calc.get_number_of_bands() else: self.nbands = nbands self.nvalence = int(calc.get_number_of_electrons() / 2) self.EK = [ calc.get_eigenvalues(k)[:self.nbands] for k in range(self.NK) ] # bands energy self.EK = np.array(self.EK) / Hartree self.shape = tuple( calc.get_number_of_grid_points()) # shape of real space grid self.density = calc.get_pseudo_density( ) * Bohr**3 # density at zero time # array of u_nk (periodic part of Kohn-Sham orbitals,only reduced Brillion zone) self.ukn = np.zeros(( self.NK, self.nbands, ) + self.shape, dtype=np.complex) for k in range(self.NK): kpt = calc.wfs.kpt_u[k] for n in range(self.nbands): psit_G = kpt.psit_nG[n] psit_R = calc.wfs.pd.ifft(psit_G, kpt.q) self.ukn[k, n] = psit_R self.icell = 2.0 * np.pi * calc.wfs.gd.icell_cv # inverse cell self.cell = calc.wfs.gd.cell_cv # cell self.r = calc.wfs.gd.get_grid_point_coordinates() for i in range(3): self.r[i] -= self.cell[i, i] / 2. self.volume = np.abs(np.linalg.det( calc.wfs.gd.cell_cv)) # volume of cell self.norm = calc.wfs.gd.dv # self.Fermi = calc.get_fermi_level() / Hartree #Fermi level #desriptors at q=gamma for Hartree self.kdH = KPointDescriptor([[0, 0, 0]]) self.pdH = PWDescriptor(ecut=calc.wfs.pd.ecut, gd=calc.wfs.gd, kd=self.kdH, dtype=complex) #desriptors at q=gamma for Fock self.kdF = KPointDescriptor([[0, 0, 0]]) self.pdF = PWDescriptor(ecut=calc.wfs.pd.ecut / 4., gd=calc.wfs.gd, kd=self.kdF, dtype=complex) #Fermi-Dirac temperature self.temperature = calc.occupations.width #calculate pair-density matrices if Fock: self.M = np.zeros((self.nbands, self.nbands, self.NK, self.NK, self.pdF.get_reciprocal_vectors().shape[0]), dtype=np.complex) indexes = [(n, k) for n, k in product(range(self.nbands), range(self.NK))] for i1 in range(len(indexes)): n1, k1 = indexes[i1] for i2 in range(i1, len(indexes)): n2, k2 = indexes[i1] self.M[n1, n2, k1, k2] = self.pdF.fft( self.ukn[k1, n1].conj() * self.ukn[k2, n2]) self.M[n2, n1, k2, k1] = self.M[n1, n2, k1, k2].conj() self.M *= calc.wfs.gd.dv #Fermi-Dirac distribution self.f = 1 / (1 + np.exp((self.EK - self.Fermi) / self.temperature)) self.Hartree_elements = np.zeros( (self.NK, self.nbands, self.NK, self.nbands, self.nbands), dtype=np.complex) self.LDAx_elements = np.zeros( (self.NK, self.nbands, self.NK, self.nbands, self.nbands), dtype=np.complex) self.LDAc_elements = np.zeros( (self.NK, self.nbands, self.NK, self.nbands, self.nbands), dtype=np.complex) G = self.pdH.get_reciprocal_vectors() G2 = np.linalg.norm(G, axis=1)**2 G2[G2 == 0] = np.inf matrix = np.zeros((self.NK, self.nbands, self.nbands), dtype=np.complex) for k in tqdm(range(self.NK)): for n in range(self.nbands): density = 2 * np.abs(self.ukn[k, n])**2 operator = xc.VLDAx(density) self.LDAx_elements[k, n] = operator_matrix_periodic( matrix, operator, self.ukn.conj(), self.ukn) * self.norm operator = xc.VLDAc(density) self.LDAc_elements[k, n] = operator_matrix_periodic( matrix, operator, self.ukn.conj(), self.ukn) * self.norm density = self.pdH.fft(density) operator = 4 * np.pi * self.pdH.ifft(density / G2) self.Hartree_elements[k, n] = operator_matrix_periodic( matrix, operator, self.ukn.conj(), self.ukn) * self.norm self.wavefunction = np.zeros((self.NK, self.nbands, self.nbands), dtype=np.complex) self.Kinetic = np.zeros((self.NK, self.nbands, self.nbands), dtype=np.complex) self.dipole = self.get_dipole_matrix() for k in range(self.NK): self.wavefunction[k] = np.eye(self.nbands) self.Kinetic[k] = np.diag(self.EK[k]) self.VH0 = self.get_Hartree_matrix(self.wavefunction) self.VLDAc0 = self.get_LDA_correlation_matrix(self.wavefunction) self.VLDAx0 = self.get_LDA_exchange_matrix(self.wavefunction) self.Full_BZ = calc.get_bz_k_points() self.IBZ_map = calc.get_bz_to_ibz_map()
def __init__(self, calc, gamma=True, symmetry=False, e_ph=False, communicator=serial_comm): """Inititialize class with a list of atoms. The atoms object must contain a converged ground-state calculation. The set of q-vectors in which the dynamical matrix will be calculated is determined from the ``symmetry`` kwarg. For now, only time-reversal symmetry is used to generate the irrecducible BZ. Add a little note on parallelization strategy here. Parameters ---------- calc: str or Calculator Calculator containing a ground-state calculation. gamma: bool Gamma-point calculation with respect to the q-vector of the dynamical matrix. When ``False``, the Monkhorst-Pack grid from the ground-state calculation is used. symmetry: bool Use symmetries to reduce the q-vectors of the dynamcial matrix (None, False or True). The different options are equivalent to the old style options in a ground-state calculation (see usesymm). e_ph: bool Save the derivative of the effective potential. communicator: Communicator Communicator for parallelization over k-points and real-space domain. """ # XXX assert symmetry in [None, False], "Spatial symmetries not allowed yet" if isinstance(calc, str): self.calc = GPAW(calc, communicator=serial_comm, txt=None) else: self.calc = calc cell_cv = self.calc.atoms.get_cell() setups = self.calc.wfs.setups # XXX - no clue how to get magmom - ignore it for the moment # m_av = magmom_av.round(decimals=3) # round off # id_a = zip(setups.id_a, *m_av.T) id_a = setups.id_a if symmetry is None: self.symmetry = Symmetry(id_a, cell_cv, point_group=False, time_reversal=False) else: self.symmetry = Symmetry(id_a, cell_cv, point_group=False, time_reversal=True) # Make sure localized functions are initialized self.calc.set_positions() # Note that this under some circumstances (e.g. when called twice) # allocates a new array for the P_ani coefficients !! # Store useful objects self.atoms = self.calc.get_atoms() # Get rid of ``calc`` attribute self.atoms.calc = None # Boundary conditions pbc_c = self.calc.atoms.get_pbc() if np.all(pbc_c == False): self.gamma = True self.dtype = float kpts = None # Multigrid Poisson solver poisson_solver = PoissonSolver() else: if gamma: self.gamma = True self.dtype = float kpts = None else: self.gamma = False self.dtype = complex # Get k-points from ground-state calculation kpts = self.calc.input_parameters.kpts # FFT Poisson solver poisson_solver = FFTPoissonSolver(dtype=self.dtype) # K-point descriptor for the q-vectors of the dynamical matrix # Note, no explicit parallelization here. self.kd = KPointDescriptor(kpts, 1) self.kd.set_symmetry(self.atoms, self.symmetry) self.kd.set_communicator(serial_comm) # Number of occupied bands nvalence = self.calc.wfs.nvalence nbands = nvalence // 2 + nvalence % 2 assert nbands <= self.calc.wfs.bd.nbands # Extract other useful objects # Ground-state k-point descriptor - used for the k-points in the # ResponseCalculator # XXX replace communicators when ready to parallelize kd_gs = self.calc.wfs.kd gd = self.calc.density.gd kpt_u = self.calc.wfs.kpt_u setups = self.calc.wfs.setups dtype_gs = self.calc.wfs.dtype # WaveFunctions wfs = WaveFunctions(nbands, kpt_u, setups, kd_gs, gd, dtype=dtype_gs) # Linear response calculator self.response_calc = ResponseCalculator(self.calc, wfs, dtype=self.dtype) # Phonon perturbation self.perturbation = PhononPerturbation(self.calc, self.kd, poisson_solver, dtype=self.dtype) # Dynamical matrix self.dyn = DynamicalMatrix(self.atoms, self.kd, dtype=self.dtype) # Electron-phonon couplings if e_ph: self.e_ph = ElectronPhononCoupling(self.atoms, gd, self.kd, dtype=self.dtype) else: self.e_ph = None # Initialization flag self.initialized = False # Parallel communicator for parallelization over kpts and domain self.comm = communicator
def create_wave_functions(self, mode, realspace, nspins, nbands, nao, nvalence, setups, magmom_a, cell_cv, pbc_c): par = self.parameters bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kd = KPointDescriptor(bzkpts_kc, nspins) self.timer.start('Set symmetry') kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) self.timer.stop('Set symmetry') self.log(kd) parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts) parsize_kpt = self.parallel['kpt'] parsize_domain = self.parallel['domain'] parsize_bands = self.parallel['band'] ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) if mode.name == 'pw': if ndomains is not None and ndomains > 1: raise ValueError('Planewave mode does not support ' 'domain decomposition.') ndomains = 1 parallelization.set(kpt=parsize_kpt, domain=ndomains, band=parsize_bands) comms = parallelization.build_communicators() domain_comm = comms['d'] kpt_comm = comms['k'] band_comm = comms['b'] kptband_comm = comms['D'] domainband_comm = comms['K'] self.comms = comms if par.gpts is not None: if par.h is not None: raise ValueError("""You can't use both "gpts" and "h"!""") N_c = np.array(par.gpts) else: h = par.h if h is not None: h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, kd.symmetry) self.symmetry.check_grid(N_c) kd.set_communicator(kpt_comm) parstride_bands = self.parallel['stridebands'] # Unfortunately we need to remember that we adjusted the # number of bands so we can print a warning if it differs # from the number specified by the user. (The number can # be inferred from the input parameters, but it's tricky # because we allow negative numbers) self.nbands_parallelization_adjustment = -nbands % band_comm.size nbands += self.nbands_parallelization_adjustment bd = BandDescriptor(nbands, band_comm, parstride_bands) # Construct grid descriptor for coarse grids for wave functions: gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) if hasattr(self, 'time') or mode.force_complex_dtype: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex wfs_kwargs = dict(gd=gd, nvalence=nvalence, setups=setups, bd=bd, dtype=dtype, world=self.world, kd=kd, kptband_comm=kptband_comm, timer=self.timer) if self.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in self.parallel.items(): if (key.startswith('sl_') and key != 'sl_auto' and val is not None): raise ValueError("Cannot use 'sl_auto' together " "with '%s'" % key) max_scalapack_cpus = bd.comm.size * gd.comm.size nprow = max_scalapack_cpus npcol = 1 # Get a sort of reasonable number of columns/rows while npcol < nprow and nprow % 2 == 0: npcol *= 2 nprow //= 2 assert npcol * nprow == max_scalapack_cpus # ScaLAPACK creates trouble if there aren't at least a few # whole blocks; choose block size so there will always be # several blocks. This will crash for small test systems, # but so will ScaLAPACK in any case blocksize = min(-(-nbands // 4), 64) sl_default = (nprow, npcol, blocksize) else: sl_default = self.parallel['sl_default'] if mode.name == 'lcao': # Layouts used for general diagonalizer sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, bd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(lcaoksl, **wfs_kwargs) elif mode.name == 'fd' or mode.name == 'pw': # buffer_size keyword only relevant for fdpw buffer_size = self.parallel['buffer_size'] # Layouts used for diagonalizer sl_diagonalize = self.parallel['sl_diagonalize'] if sl_diagonalize is None: sl_diagonalize = sl_default diagksl = get_KohnSham_layouts( sl_diagonalize, 'fd', # XXX # choice of key 'fd' not so nice gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Layouts used for orthonormalizer sl_inverse_cholesky = self.parallel['sl_inverse_cholesky'] if sl_inverse_cholesky is None: sl_inverse_cholesky = sl_default if sl_inverse_cholesky != sl_diagonalize: message = 'sl_inverse_cholesky != sl_diagonalize ' \ 'is not implemented.' raise NotImplementedError(message) orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd', gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Use (at most) all available LCAO for initialization lcaonbands = min(nbands, nao // band_comm.size * band_comm.size) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default initksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, lcaobd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(diagksl, orthoksl, initksl, **wfs_kwargs) else: self.wfs = mode(self, **wfs_kwargs) self.log(self.wfs, '\n')
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 create_kpoint_descriptor(bzkpts_kc, nspins, atoms, symmetry, comm): kd = KPointDescriptor(bzkpts_kc, nspins) # self.timer.start('Set symmetry') kd.set_symmetry(atoms, symmetry, comm=comm) # self.timer.stop('Set symmetry') return kd
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) n_nmvG = pair.get_pair_momentum(pd, kptpair, np.arange(0, nb), np.arange(0, nb)) if ol:
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)