class CoulombNEW: def __init__(self, gd, setups, spos_ac, fft=False): assert gd.comm.size == 1 self.rhot1_G = gd.empty() self.rhot2_G = gd.empty() self.pot_G = gd.empty() self.dv = gd.dv if fft: self.poisson = FFTPoissonSolver() else: self.poisson = PoissonSolver(name='fd', nn=3) self.poisson.set_grid_descriptor(gd) self.setups = setups # Set coarse ghat self.Ghat = LFC(gd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi)) self.Ghat.set_positions(spos_ac) def calculate(self, nt1_G, nt2_G, P1_ap, P2_ap): I = 0.0 self.rhot1_G[:] = nt1_G self.rhot2_G[:] = nt2_G Q1_aL = {} Q2_aL = {} for a, P1_p in P1_ap.items(): P2_p = P2_ap[a] setup = self.setups[a] # Add atomic corrections to integral I += 2 * np.dot(P1_p, np.dot(setup.M_pp, P2_p)) # Add compensation charges to pseudo densities Q1_aL[a] = np.dot(P1_p, setup.Delta_pL) Q2_aL[a] = np.dot(P2_p, setup.Delta_pL) self.Ghat.add(self.rhot1_G, Q1_aL) self.Ghat.add(self.rhot2_G, Q2_aL) # Add coulomb energy of compensated pseudo densities to integral self.poisson.solve(self.pot_G, self.rhot2_G, charge=None, eps=1e-12, zero_initial_phi=True) I += np.vdot(self.rhot1_G, self.pot_G) * self.dv return I * Hartree
class CoulombNEW: def __init__(self, gd, setups, spos_ac, fft=False): assert gd.comm.size == 1 self.rhot1_G = gd.empty() self.rhot2_G = gd.empty() self.pot_G = gd.empty() self.dv = gd.dv if fft: self.poisson = FFTPoissonSolver() else: self.poisson = PoissonSolver(nn=3) self.poisson.set_grid_descriptor(gd) self.poisson.initialize() self.setups = setups # Set coarse ghat self.Ghat = LFC(gd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi)) self.Ghat.set_positions(spos_ac) def calculate(self, nt1_G, nt2_G, P1_ap, P2_ap): I = 0.0 self.rhot1_G[:] = nt1_G self.rhot2_G[:] = nt2_G Q1_aL = {} Q2_aL = {} for a, P1_p in P1_ap.items(): P2_p = P2_ap[a] setup = self.setups[a] # Add atomic corrections to integral I += 2 * np.dot(P1_p, np.dot(setup.M_pp, P2_p)) # Add compensation charges to pseudo densities Q1_aL[a] = np.dot(P1_p, setup.Delta_pL) Q2_aL[a] = np.dot(P2_p, setup.Delta_pL) self.Ghat.add(self.rhot1_G, Q1_aL) self.Ghat.add(self.rhot2_G, Q2_aL) # Add coulomb energy of compensated pseudo densities to integral self.poisson.solve(self.pot_G, self.rhot2_G, charge=None, eps=1e-12, zero_initial_phi=True) I += np.vdot(self.rhot1_G, self.pot_G) * self.dv return I * Hartree
def f(n, p): N = 2 * n gd = GridDescriptor((N, N, N), (L, L, L)) a = gd.zeros() print(a.shape) #p = PoissonSolver(nn=1, relax=relax) p.set_grid_descriptor(gd) cut = N / 2.0 * 0.9 s = Spline(l=0, rmax=cut, f_g=np.array([1, 0.5, 0.0])) c = LFC(gd, [[s], [s]]) c.set_positions([(0, 0, 0), (0.5, 0.5, 0.5)]) c.add(a) I0 = gd.integrate(a) a -= I0 / L**3 b = gd.zeros() p.solve(b, a, charge=0, eps=1e-20) return gd.collect(b, broadcast=1)
def f(n, p): N = 2 * n gd = GridDescriptor((N, N, N), (L, L, L)) a = gd.zeros() print(a.shape) #p = PoissonSolver(nn=1, relax=relax) p.set_grid_descriptor(gd) p.initialize() cut = N / 2.0 * 0.9 s = Spline(l=0, rmax=cut, f_g=np.array([1, 0.5, 0.0])) c = LFC(gd, [[s], [s]]) c.set_positions([(0, 0, 0), (0.5, 0.5, 0.5)]) c.add(a) I0 = gd.integrate(a) a -= gd.integrate(a) / L**3 I = gd.integrate(a) b = gd.zeros() p.solve(b, a, charge=0)#, eps=1e-20) return gd.collect(b, broadcast=1)
class FDWaveFunctions(FDPWWaveFunctions): mode = 'fd' def __init__(self, stencil, diagksl, orthoksl, initksl, gd, nvalence, setups, bd, dtype, world, kd, kptband_comm, timer=None): FDPWWaveFunctions.__init__(self, diagksl, orthoksl, initksl, gd, nvalence, setups, bd, dtype, world, kd, kptband_comm, timer) # Kinetic energy operator: self.kin = Laplace(self.gd, -0.5, stencil, self.dtype) self.matrixoperator = MatrixOperator(self.orthoksl) self.taugrad_v = None # initialized by MGGA functional def empty(self, n=(), global_array=False, realspace=False, q=-1): return self.gd.empty(n, self.dtype, global_array) def integrate(self, a_xg, b_yg=None, global_integral=True): return self.gd.integrate(a_xg, b_yg, global_integral) def bytes_per_wave_function(self): return self.gd.bytecount(self.dtype) def set_setups(self, setups): self.pt = LFC(self.gd, [setup.pt_j for setup in setups], self.kd, dtype=self.dtype, forces=True) FDPWWaveFunctions.set_setups(self, setups) def set_positions(self, spos_ac): FDPWWaveFunctions.set_positions(self, spos_ac) def summary(self, fd): fd.write('Wave functions: Uniform real-space grid\n') fd.write('Kinetic energy operator: %s\n' % self.kin.description) def make_preconditioner(self, block=1): return Preconditioner(self.gd, self.kin, self.dtype, block) def apply_pseudo_hamiltonian(self, kpt, hamiltonian, psit_xG, Htpsit_xG): self.timer.start('Apply hamiltonian') self.kin.apply(psit_xG, Htpsit_xG, kpt.phase_cd) hamiltonian.apply_local_potential(psit_xG, Htpsit_xG, kpt.s) self.timer.stop('Apply hamiltonian') def add_orbital_density(self, nt_G, kpt, n): if self.dtype == float: axpy(1.0, kpt.psit_nG[n]**2, nt_G) else: axpy(1.0, kpt.psit_nG[n].real**2, nt_G) axpy(1.0, kpt.psit_nG[n].imag**2, nt_G) def add_to_density_from_k_point_with_occupation(self, nt_sG, kpt, f_n): # Used in calculation of response part of GLLB-potential nt_G = nt_sG[kpt.s] if self.dtype == float: for f, psit_G in zip(f_n, kpt.psit_nG): axpy(f, psit_G**2, nt_G) else: for f, psit_G in zip(f_n, kpt.psit_nG): axpy(f, psit_G.real**2, nt_G) axpy(f, psit_G.imag**2, nt_G) # Hack used in delta-scf calculations: if hasattr(kpt, 'c_on'): assert self.bd.comm.size == 1 d_nn = np.zeros((self.bd.mynbands, self.bd.mynbands), dtype=complex) for ne, c_n in zip(kpt.ne_o, kpt.c_on): d_nn += ne * np.outer(c_n.conj(), c_n) for d_n, psi0_G in zip(d_nn, kpt.psit_nG): for d, psi_G in zip(d_n, kpt.psit_nG): if abs(d) > 1.e-12: nt_G += (psi0_G.conj() * d * psi_G).real def calculate_kinetic_energy_density(self): if self.taugrad_v is None: self.taugrad_v = [ Gradient(self.gd, v, n=3, dtype=self.dtype).apply for v in range(3) ] assert not hasattr(self.kpt_u[0], 'c_on') if self.kpt_u[0].psit_nG is None: raise RuntimeError('No wavefunctions yet') if isinstance(self.kpt_u[0].psit_nG, FileReference): # XXX initialize raise RuntimeError('Wavefunctions have not been initialized.') taut_sG = self.gd.zeros(self.nspins) dpsit_G = self.gd.empty(dtype=self.dtype) for kpt in self.kpt_u: for f, psit_G in zip(kpt.f_n, kpt.psit_nG): for v in range(3): self.taugrad_v[v](psit_G, dpsit_G, kpt.phase_cd) axpy(0.5 * f, abs(dpsit_G)**2, taut_sG[kpt.s]) self.kd.comm.sum(taut_sG) self.band_comm.sum(taut_sG) return taut_sG def apply_mgga_orbital_dependent_hamiltonian(self, kpt, psit_xG, Htpsit_xG, dH_asp, dedtaut_G): a_G = self.gd.empty(dtype=psit_xG.dtype) for psit_G, Htpsit_G in zip(psit_xG, Htpsit_xG): for v in range(3): self.taugrad_v[v](psit_G, a_G, kpt.phase_cd) self.taugrad_v[v](dedtaut_G * a_G, a_G, kpt.phase_cd) axpy(-0.5, a_G, Htpsit_G) 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 write(self, writer, write_wave_functions=False): writer['Mode'] = 'fd' if not write_wave_functions: return writer.add( 'PseudoWaveFunctions', ('nspins', 'nibzkpts', 'nbands', 'ngptsx', 'ngptsy', 'ngptsz'), dtype=self.dtype) if hasattr(writer, 'hdf5'): parallel = (self.world.size > 1) for kpt in self.kpt_u: indices = [kpt.s, kpt.k] indices.append(self.bd.get_slice()) indices += self.gd.get_slice() writer.fill(kpt.psit_nG, parallel=parallel, *indices) else: for s in range(self.nspins): for k in range(self.kd.nibzkpts): for n in range(self.bd.nbands): psit_G = self.get_wave_function_array(n, k, s) writer.fill(psit_G, s, k, n) def read(self, reader, hdf5): if ((not hdf5 and self.bd.comm.size == 1) or (hdf5 and self.world.size == 1)): # We may not be able to keep all the wave # functions in memory - so psit_nG will be a special type of # array that is really just a reference to a file: for kpt in self.kpt_u: kpt.psit_nG = reader.get_reference('PseudoWaveFunctions', (kpt.s, kpt.k)) else: for kpt in self.kpt_u: kpt.psit_nG = self.empty(self.bd.mynbands) if hdf5: indices = [kpt.s, kpt.k] indices.append(self.bd.get_slice()) indices += self.gd.get_slice() reader.get('PseudoWaveFunctions', out=kpt.psit_nG, parallel=(self.world.size > 1), *indices) else: # Read band by band to save memory for myn, psit_G in enumerate(kpt.psit_nG): n = self.bd.global_index(myn) if self.gd.comm.rank == 0: big_psit_G = np.array( reader.get('PseudoWaveFunctions', kpt.s, kpt.k, n), self.dtype) else: big_psit_G = None self.gd.distribute(big_psit_G, psit_G) def initialize_from_lcao_coefficients(self, basis_functions, mynbands): for kpt in self.kpt_u: kpt.psit_nG = self.gd.zeros(self.bd.mynbands, self.dtype) basis_functions.lcao_to_grid(kpt.C_nM, kpt.psit_nG[:mynbands], kpt.q) kpt.C_nM = None if use_mic: kpt.psit_nG_mic = stream.bind(kpt.psit_nG) stream.sync() def random_wave_functions(self, nao): """Generate random wave functions.""" gpts = self.gd.N_c[0] * self.gd.N_c[1] * self.gd.N_c[2] if self.bd.nbands < gpts / 64: gd1 = self.gd.coarsen() gd2 = gd1.coarsen() psit_G1 = gd1.empty(dtype=self.dtype) psit_G2 = gd2.empty(dtype=self.dtype) interpolate2 = Transformer(gd2, gd1, 1, self.dtype).apply interpolate1 = Transformer(gd1, self.gd, 1, self.dtype).apply shape = tuple(gd2.n_c) scale = np.sqrt(12 / abs(np.linalg.det(gd2.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G2[:] = (np.random.random(shape) - 0.5) * scale else: psit_G2.real = (np.random.random(shape) - 0.5) * scale psit_G2.imag = (np.random.random(shape) - 0.5) * scale interpolate2(psit_G2, psit_G1, kpt.phase_cd) interpolate1(psit_G1, psit_G, kpt.phase_cd) np.random.set_state(old_state) elif gpts / 64 <= self.bd.nbands < gpts / 8: gd1 = self.gd.coarsen() psit_G1 = gd1.empty(dtype=self.dtype) interpolate1 = Transformer(gd1, self.gd, 1, self.dtype).apply shape = tuple(gd1.n_c) scale = np.sqrt(12 / abs(np.linalg.det(gd1.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G1[:] = (np.random.random(shape) - 0.5) * scale else: psit_G1.real = (np.random.random(shape) - 0.5) * scale psit_G1.imag = (np.random.random(shape) - 0.5) * scale interpolate1(psit_G1, psit_G, kpt.phase_cd) np.random.set_state(old_state) else: shape = tuple(self.gd.n_c) scale = np.sqrt(12 / abs(np.linalg.det(self.gd.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G[:] = (np.random.random(shape) - 0.5) * scale else: psit_G.real = (np.random.random(shape) - 0.5) * scale psit_G.imag = (np.random.random(shape) - 0.5) * scale np.random.set_state(old_state) def estimate_memory(self, mem): FDPWWaveFunctions.estimate_memory(self, mem)
class ELF: """ELF object for calculating the electronic localization function. Arguments: =============== ===================================================== ``paw`` Instance of ``GPAW`` class. ``ncut`` Density cutoff below which the ELF is zero. =============== ===================================================== """ def __init__(self, paw=None, ncut=1e-6): """Create the ELF object.""" self.gd = paw.wfs.gd self.paw = paw self.finegd = paw.density.finegd self.nspins = paw.density.nspins self.density = paw.density self.ncut = ncut self.spinpol = (self.nspins == 2) self.initialize(paw) def initialize(self, paw): if not paw.initialized: raise RuntimeError('PAW instance is not initialized') paw.converge_wave_functions() self.tauct = LFC(self.gd, [[setup.tauct] for setup in self.density.setups], forces=True, cut=True) self.tauct.set_positions(paw.spos_ac) self.taut_sg = None self.nt_grad2_sG = self.gd.empty(self.nspins) self.nt_grad2_sg = None def interpolate(self): self.density.interpolate_pseudo_density() if self.taut_sg is None: self.taut_sg = self.finegd.empty(self.nspins) self.nt_grad2_sg = self.finegd.empty(self.nspins) ddr_v = [Gradient(self.finegd, v, n=3).apply for v in range(3)] self.nt_grad2_sg[:] = 0.0 d_g = self.finegd.empty() # Transfer the densities from the coarse to the fine grid for s in range(self.nspins): self.density.interpolator.apply(self.taut_sG[s], self.taut_sg[s]) #self.density.interpolator.apply(self.nt_grad2_sG[s], # self.nt_grad2_sg[s]) for v in range(3): ddr_v[v](self.density.nt_sg[s], d_g) self.nt_grad2_sg[s] += d_g**2.0 def update(self): self.taut_sG = self.paw.wfs.calculate_kinetic_energy_density() # Add the pseudo core kinetic array for taut_G in self.taut_sG: self.tauct.add(taut_G, 1.0 / self.paw.wfs.nspins) # For periodic boundary conditions if self.paw.wfs.kd.symmetry is not None: self.paw.wfs.kd.symmetry.symmetrize(self.taut_sG[0], self.paw.wfs.gd) self.nt_grad2_sG[:] = 0.0 d_G = self.gd.empty() for s in range(self.nspins): for v in range(3): self.paw.wfs.taugrad_v[v](self.density.nt_sG[s], d_G) self.nt_grad2_sG[s] += d_G**2.0 # TODO are nct from setups usable for nt_grad2_sG ? def get_electronic_localization_function(self, gridrefinement=1, pad=True, broadcast=True): # Returns dimensionless electronic localization function if gridrefinement == 1: elf_G = _elf(self.density.nt_sG, self.nt_grad2_sG, self.taut_sG, self.ncut, self.spinpol) elf_G = self.gd.collect(elf_G, broadcast=broadcast) if pad: elf_G = self.gd.zero_pad(elf_G) return elf_G elif gridrefinement == 2: if self.nt_grad2_sg is None: self.interpolate() elf_g = _elf(self.density.nt_sg, self.nt_grad2_sg, self.taut_sg, self.ncut, self.spinpol) elf_g = self.finegd.collect(elf_g, broadcast=broadcast) if pad: elf_g = self.finegd.zero_pad(elf_g) return elf_g else: raise NotImplementedError('Arbitrary refinement not implemented')
from gpaw.spherical_harmonics import Y from gpaw.response.math_func import two_phi_planewave_integrals # Initialize s, p, d (9 in total) wave and put them on grid rc = 2.0 a = 2.5 * rc n = 64 lmax = 2 b = 8.0 m = (lmax + 1)**2 gd = GridDescriptor([n, n, n], [a, a, a]) r = np.linspace(0, rc, 200) g = np.exp(-(r / rc * b)**2) splines = [Spline(l=l, rmax=rc, f_g=g) for l in range(lmax + 1)] c = LFC(gd, [splines]) c.set_positions([(0.5, 0.5, 0.5)]) psi = gd.zeros(m) d0 = c.dict(m) if 0 in d0: d0[0] = np.identity(m) c.add(psi, d0) # Calculate on 3d-grid < phi_i | e**(-ik.r) | phi_j > R_a = np.array([a / 2, a / 2, a / 2]) rr = gd.get_grid_point_coordinates() for dim in range(3): rr[dim] -= R_a[dim] k_G = np.array([[11., 0.2, 0.1], [10., 0., 10.]]) nkpt = k_G.shape[0]
def get_projections(self, locfun, spin=0): """Project wave functions onto localized functions Determine the projections of the Kohn-Sham eigenstates onto specified localized functions of the format:: locfun = [[spos_c, l, sigma], [...]] spos_c can be an atom index, or a scaled position vector. l is the angular momentum, and sigma is the (half-) width of the radial gaussian. Return format is:: f_kni = <psi_kn | f_i> where psi_kn are the wave functions, and f_i are the specified localized functions. As a special case, locfun can be the string 'projectors', in which case the bound state projectors are used as localized functions. """ wfs = self.wfs if locfun == 'projectors': f_kin = [] for kpt in wfs.kpt_u: if kpt.s == spin: f_in = [] for a, P_ni in kpt.P_ani.items(): i = 0 setup = wfs.setups[a] for l, n in zip(setup.l_j, setup.n_j): if n >= 0: for j in range(i, i + 2 * l + 1): f_in.append(P_ni[:, j]) i += 2 * l + 1 f_kin.append(f_in) f_kni = np.array(f_kin).transpose(0, 2, 1) return f_kni.conj() from gpaw.lfc import LocalizedFunctionsCollection as LFC from gpaw.spline import Spline from gpaw.utilities import _fact nkpts = len(wfs.ibzk_kc) nbf = np.sum([2 * l + 1 for pos, l, a in locfun]) f_kni = np.zeros((nkpts, wfs.nbands, nbf), wfs.dtype) spos_ac = self.atoms.get_scaled_positions() % 1.0 spos_xc = [] splines_x = [] for spos_c, l, sigma in locfun: if isinstance(spos_c, int): spos_c = spos_ac[spos_c] spos_xc.append(spos_c) alpha = .5 * Bohr**2 / sigma**2 r = np.linspace(0, 6. * sigma, 500) f_g = (_fact[l] * (4 * alpha)**(l + 3 / 2.) * np.exp(-alpha * r**2) / (np.sqrt(4 * np.pi) * _fact[2 * l + 1])) splines_x.append([Spline(l, rmax=r[-1], f_g=f_g, points=61)]) lf = LFC(wfs.gd, splines_x, wfs.kpt_comm, dtype=wfs.dtype) if not wfs.gamma: lf.set_k_points(wfs.ibzk_qc) lf.set_positions(spos_xc) k = 0 f_ani = lf.dict(wfs.nbands) for kpt in wfs.kpt_u: if kpt.s != spin: continue lf.integrate(kpt.psit_nG[:], f_ani, kpt.q) i1 = 0 for x, f_ni in f_ani.items(): i2 = i1 + f_ni.shape[1] f_kni[k, :, i1:i2] = f_ni i1 = i2 k += 1 return f_kni.conj()
class FDWaveFunctions(FDPWWaveFunctions): mode = 'fd' def __init__(self, stencil, parallel, initksl, gd, nvalence, setups, bd, dtype, world, kd, kptband_comm, timer, reuse_wfs_method=None, collinear=True): FDPWWaveFunctions.__init__(self, parallel, initksl, reuse_wfs_method=reuse_wfs_method, collinear=collinear, gd=gd, nvalence=nvalence, setups=setups, bd=bd, dtype=dtype, world=world, kd=kd, kptband_comm=kptband_comm, timer=timer) # Kinetic energy operator: self.kin = Laplace(self.gd, -0.5, stencil, self.dtype) self.taugrad_v = None # initialized by MGGA functional def empty(self, n=(), global_array=False, realspace=False, q=-1): return self.gd.empty(n, self.dtype, global_array) def integrate(self, a_xg, b_yg=None, global_integral=True): return self.gd.integrate(a_xg, b_yg, global_integral) def bytes_per_wave_function(self): return self.gd.bytecount(self.dtype) def set_setups(self, setups): self.pt = LFC(self.gd, [setup.pt_j for setup in setups], self.kd, dtype=self.dtype, forces=True) FDPWWaveFunctions.set_setups(self, setups) def set_positions(self, spos_ac, atom_partition=None): FDPWWaveFunctions.set_positions(self, spos_ac, atom_partition) def __str__(self): s = 'Wave functions: Uniform real-space grid\n' s += ' Kinetic energy operator: %s\n' % self.kin.description return s + FDPWWaveFunctions.__str__(self) def make_preconditioner(self, block=1): return Preconditioner(self.gd, self.kin, self.dtype, block) def apply_pseudo_hamiltonian(self, kpt, ham, psit_xG, Htpsit_xG): self.timer.start('Apply hamiltonian') self.kin.apply(psit_xG, Htpsit_xG, kpt.phase_cd) ham.apply_local_potential(psit_xG, Htpsit_xG, kpt.s) ham.xc.apply_orbital_dependent_hamiltonian(kpt, psit_xG, Htpsit_xG, ham.dH_asp) self.timer.stop('Apply hamiltonian') def get_pseudo_partial_waves(self): phit_aj = [ setup.get_partial_waves_for_atomic_orbitals() for setup in self.setups ] return LFC(self.gd, phit_aj, kd=self.kd, cut=True, dtype=self.dtype) def add_to_density_from_k_point_with_occupation(self, nt_sG, kpt, f_n): # Used in calculation of response part of GLLB-potential nt_G = nt_sG[kpt.s] for f, psit_G in zip(f_n, kpt.psit_nG): # Same as nt_G += f * abs(psit_G)**2, but much faster: _gpaw.add_to_density(f, psit_G, nt_G) # Hack used in delta-scf calculations: if hasattr(kpt, 'c_on'): assert self.bd.comm.size == 1 d_nn = np.zeros((self.bd.mynbands, self.bd.mynbands), dtype=complex) for ne, c_n in zip(kpt.ne_o, kpt.c_on): d_nn += ne * np.outer(c_n.conj(), c_n) for d_n, psi0_G in zip(d_nn, kpt.psit_nG): for d, psi_G in zip(d_n, kpt.psit_nG): if abs(d) > 1.e-12: nt_G += (psi0_G.conj() * d * psi_G).real def calculate_kinetic_energy_density(self): if self.taugrad_v is None: self.taugrad_v = [ Gradient(self.gd, v, n=3, dtype=self.dtype).apply for v in range(3) ] assert not hasattr(self.kpt_u[0], 'c_on') if not isinstance(self.kpt_u[0].psit_nG, np.ndarray): return None taut_sG = self.gd.zeros(self.nspins) dpsit_G = self.gd.empty(dtype=self.dtype) for kpt in self.kpt_u: for f, psit_G in zip(kpt.f_n, kpt.psit_nG): for v in range(3): self.taugrad_v[v](psit_G, dpsit_G, kpt.phase_cd) axpy(0.5 * f, abs(dpsit_G)**2, taut_sG[kpt.s]) self.kptband_comm.sum(taut_sG) return taut_sG def apply_mgga_orbital_dependent_hamiltonian(self, kpt, psit_xG, Htpsit_xG, dH_asp, dedtaut_G): a_G = self.gd.empty(dtype=psit_xG.dtype) for psit_G, Htpsit_G in zip(psit_xG, Htpsit_xG): for v in range(3): self.taugrad_v[v](psit_G, a_G, kpt.phase_cd) self.taugrad_v[v](dedtaut_G * a_G, a_G, kpt.phase_cd) axpy(-0.5, a_G, Htpsit_G) 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_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.mykpts[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 = UniformGridWaveFunctions(self.bd.nbands, self.gd, self.dtype, kpt=k, dist=(self.bd.comm, self.bd.comm.size), spin=kpt.s, collinear=True) self.gd.distribute(Psit_nG, kpt2.psit_nG) # Calculate PAW projections: nproj_a = [setup.ni for setup in self.setups] kpt2.projections = Projections(self.bd.nbands, nproj_a, kpt.projections.atom_partition, self.bd.comm, collinear=True, spin=s, dtype=self.dtype) kpt2.psit.matrix_elements(self.pt, out=kpt2.projections) kpt_u.append(kpt2) self.kd = kd self.mykpts = kpt_u def _get_wave_function_array(self, u, n, realspace=True, periodic=False): assert realspace kpt = self.kpt_u[u] psit_G = kpt.psit_nG[n] if periodic and self.dtype == complex: k_c = self.kd.ibzk_kc[kpt.k] return self.gd.plane_wave(-k_c) * psit_G return psit_G def write(self, writer, write_wave_functions=False): FDPWWaveFunctions.write(self, writer) if not write_wave_functions: return writer.add_array('values', (self.nspins, self.kd.nibzkpts, self.bd.nbands) + tuple(self.gd.get_size_of_global_array()), self.dtype) for s in range(self.nspins): for k in range(self.kd.nibzkpts): for n in range(self.bd.nbands): psit_G = self.get_wave_function_array(n, k, s) writer.fill(psit_G * Bohr**-1.5) def read(self, reader): FDPWWaveFunctions.read(self, reader) if 'values' not in reader.wave_functions: return c = reader.bohr**1.5 if reader.version < 0: c = 1 # old gpw file for kpt in self.kpt_u: # We may not be able to keep all the wave # functions in memory - so psit_nG will be a special type of # array that is really just a reference to a file: psit_nG = reader.wave_functions.proxy('values', kpt.s, kpt.k) psit_nG.scale = c kpt.psit = UniformGridWaveFunctions(self.bd.nbands, self.gd, self.dtype, psit_nG, kpt=kpt.q, dist=(self.bd.comm, self.bd.comm.size), spin=kpt.s, collinear=True) if self.world.size > 1: # Read to memory: for kpt in self.kpt_u: kpt.psit.read_from_file() def initialize_from_lcao_coefficients(self, basis_functions): for kpt in self.mykpts: kpt.psit = UniformGridWaveFunctions(self.bd.nbands, self.gd, self.dtype, kpt=kpt.q, dist=(self.bd.comm, self.bd.comm.size, 1), spin=kpt.s, collinear=True) kpt.psit_nG[:] = 0.0 mynbands = len(kpt.C_nM) basis_functions.lcao_to_grid(kpt.C_nM, kpt.psit_nG[:mynbands], kpt.q) kpt.C_nM = None def random_wave_functions(self, nao): """Generate random wave functions.""" gpts = self.gd.N_c[0] * self.gd.N_c[1] * self.gd.N_c[2] if self.bd.nbands < gpts / 64: gd1 = self.gd.coarsen() gd2 = gd1.coarsen() psit_G1 = gd1.empty(dtype=self.dtype) psit_G2 = gd2.empty(dtype=self.dtype) interpolate2 = Transformer(gd2, gd1, 1, self.dtype).apply interpolate1 = Transformer(gd1, self.gd, 1, self.dtype).apply shape = tuple(gd2.n_c) scale = np.sqrt(12 / abs(np.linalg.det(gd2.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G2[:] = (np.random.random(shape) - 0.5) * scale else: psit_G2.real = (np.random.random(shape) - 0.5) * scale psit_G2.imag = (np.random.random(shape) - 0.5) * scale interpolate2(psit_G2, psit_G1, kpt.phase_cd) interpolate1(psit_G1, psit_G, kpt.phase_cd) np.random.set_state(old_state) elif gpts / 64 <= self.bd.nbands < gpts / 8: gd1 = self.gd.coarsen() psit_G1 = gd1.empty(dtype=self.dtype) interpolate1 = Transformer(gd1, self.gd, 1, self.dtype).apply shape = tuple(gd1.n_c) scale = np.sqrt(12 / abs(np.linalg.det(gd1.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G1[:] = (np.random.random(shape) - 0.5) * scale else: psit_G1.real = (np.random.random(shape) - 0.5) * scale psit_G1.imag = (np.random.random(shape) - 0.5) * scale interpolate1(psit_G1, psit_G, kpt.phase_cd) np.random.set_state(old_state) else: shape = tuple(self.gd.n_c) scale = np.sqrt(12 / abs(np.linalg.det(self.gd.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G[:] = (np.random.random(shape) - 0.5) * scale else: psit_G.real = (np.random.random(shape) - 0.5) * scale psit_G.imag = (np.random.random(shape) - 0.5) * scale np.random.set_state(old_state) def estimate_memory(self, mem): FDPWWaveFunctions.estimate_memory(self, mem)
from gpaw.test import equal from gpaw.grid_descriptor import GridDescriptor from gpaw.spline import Spline import gpaw.mpi as mpi from gpaw.lfc import LocalizedFunctionsCollection as LFC s = Spline(0, 1.0, [1.0, 0.5, 0.0]) n = 40 a = 8.0 gd = GridDescriptor((n, n, n), (a, a, a), comm=mpi.serial_comm) c = LFC(gd, [[s], [s], [s]]) c.set_positions([(0.5, 0.5, 0.25 + 0.25 * i) for i in [0, 1, 2]]) b = gd.zeros() c.add(b) x = gd.integrate(b) gd = GridDescriptor((n, n, n), (a, a, a), comm=mpi.serial_comm) c = LFC(gd, [[s], [s], [s]]) c.set_positions([(0.5, 0.5, 0.25 + 0.25 * i) for i in [0, 1, 2]]) b = gd.zeros() c.add(b) y = gd.integrate(b) equal(x, y, 1e-13)
from gpaw.atom.radialgd import EquidistantRadialGridDescriptor from gpaw.spline import Spline from gpaw.setup import Setup rc = 2.0 a = 2.5 * rc n = 64 lmax = 2 b = 8.0 m = (lmax + 1)**2 gd = GridDescriptor([n, n, n], [a, a, a]) r = np.linspace(0, rc, 200) g = np.exp(-(r / rc * b)**2) splines = [Spline(l=l, rmax=rc, f_g=g) for l in range(lmax + 1)] c = LFC(gd, [splines]) c.set_positions([(0, 0, 0)]) psi = gd.zeros(m) d0 = c.dict(m) if 0 in d0: d0[0] = np.identity(m) c.add(psi, d0) d1 = c.dict(m, derivative=True) c.derivative(psi, d1) class TestSetup(Setup): l_j = range(lmax + 1) nj = lmax + 1 ni = m def __init__(self): pass rgd = EquidistantRadialGridDescriptor(r[1], len(r)) g = [np.exp(-(r / rc * b)**2) * r**l for l in range(lmax + 1)]
class WaveFunctions: """Class for wave-function related stuff (e.g. projectors).""" def __init__(self, nbands, kpt_u, setups, kd, gd, dtype=float): """Store and initialize required attributes. Parameters ---------- nbands: int Number of occupied bands. kpt_u: list of KPoints List of KPoint instances from a ground-state calculation (i.e. the attribute ``calc.wfs.kpt_u``). setups: Setups LocalizedFunctionsCollection setups. kd: KPointDescriptor K-point and symmetry related stuff. gd: GridDescriptor Descriptor for the coarse grid. dtype: dtype This is the ``dtype`` for the wave-function derivatives (same as the ``dtype`` for the ground-state wave-functions). """ self.dtype = dtype # K-point related attributes self.kd = kd # Number of occupied bands self.nbands = nbands # Projectors self.pt = LFC(gd, [setup.pt_j for setup in setups], kd, dtype=self.dtype) # Store grid self.gd = gd # Unfold the irreducible BZ to the full BZ # List of KPointContainers for the k-points in the full BZ self.kpt_u = [] # No symmetries or only time-reversal symmetry used assert kd.symmetry.point_group == False if kd.symmetry.time_reversal == False: # For now, time-reversal symmetry not allowed assert len(kpt_u) == kd.nbzkpts for k in range(kd.nbzkpts): kpt_ = kpt_u[k] psit_nG = gd.empty(nbands, dtype=self.dtype) for n, psit_G in enumerate(psit_nG): psit_G[:] = kpt_.psit_nG[n] # psit_0 = psit_G[0, 0, 0] # psit_G *= psit_0.conj() / (abs(psit_0)) # Strip off KPoint attributes and store in the KPointContainer # Note, only the occupied GS wave-functions are retained here ! kpt = KPointContainer(weight=kpt_.weight, k=kpt_.k, s=kpt_.s, phase_cd=kpt_.phase_cd, eps_n=kpt_.eps_n[:nbands], psit_nG=psit_nG, psit1_nG=None, P_ani=None, dP_aniv=None) # q=kpt.q, # f_n=kpt.f_n[:nbands]) self.kpt_u.append(kpt) else: assert len(kpt_u) == kd.nibzkpts for k, k_c in enumerate(kd.bzk_kc): # Index of symmetry related point in the irreducible BZ ik = kd.bz2ibz_k[k] # Index of point group operation s = kd.sym_k[k] # Time-reversal symmetry used time_reversal = kd.time_reversal_k[k] # Coordinates of symmetry related point in the irreducible BZ ik_c = kd.ibzk_kc[ik] # Point group operation op_cc = kd.symmetry.op_scc[s] # KPoint from ground-state calculation kpt_ = kpt_u[ik] weight = 1. / kd.nbzkpts * (2 - kpt_.s) phase_cd = np.exp(2j * pi * gd.sdisp_cd * k_c[:, np.newaxis]) psit_nG = gd.empty(nbands, dtype=self.dtype) for n, psit_G in enumerate(psit_nG): #XXX Seems to corrupt my memory somehow ??? psit_G[:] = kd.symmetry.symmetrize_wavefunction( kpt_.psit_nG[n], ik_c, k_c, op_cc, time_reversal) # Choose gauge # psit_0 = psit_G[0, 0, 0] # psit_G *= psit_0.conj() / (abs(psit_0)) kpt = KPointContainer(weight=weight, k=k, s=kpt_.s, phase_cd=phase_cd, eps_n=kpt_.eps_n[:nbands], psit_nG=psit_nG, psit1_nG=None, P_ani=None, dP_aniv=None) self.kpt_u.append(kpt) def initialize(self, spos_ac): """Initialize projectors according to the ``gamma`` attribute.""" # Set positions on LFC's self.pt.set_positions(spos_ac) # Calculate projector coefficients for the GS wave-functions self.calculate_projector_coef() def reset(self): """Make fresh arrays for wave-function derivatives.""" for kpt in self.kpt_u: kpt.psit1_nG = self.gd.zeros(n=self.nbands, dtype=self.dtype) def calculate_projector_coef(self): """Coefficients for the derivative of the non-local part of the PP. Parameters ---------- k: int Index of the k-point of the Bloch state on which the non-local potential operates on. The calculated coefficients are the following (except for an overall sign of -1; see ``derivative`` member function of class ``LFC``): 1. Coefficients from the projector functions:: / a P_ani = | dG p (G) Psi (G) , / i n 2. Coefficients from the derivative of the projector functions:: / a dP_aniv = | dG dp (G) Psi (G) , / iv n where:: a d a dp (G) = --- Phi (G) . iv a i dR """ n = self.nbands for kpt in self.kpt_u: # K-point index and wave-functions k = kpt.k psit_nG = kpt.psit_nG # Integration dicts P_ani = self.pt.dict(shape=n) dP_aniv = self.pt.dict(shape=n, derivative=True) # 1) Integrate with projectors self.pt.integrate(psit_nG, P_ani, q=k) kpt.P_ani = P_ani # 2) Integrate with derivative of projectors self.pt.derivative(psit_nG, dP_aniv, q=k) kpt.dP_aniv = dP_aniv
class BASECHI: """This class is to store the basic common stuff for chi and bse.""" def __init__(self, calc=None, nbands=None, w=None, q=None, eshift=None, ecut=10., density_cut=None, G_plus_q=False, eta=0.2, rpad=None, ftol=1e-5, txt=None, optical_limit=False): if rpad is None: rpad = np.ones(3, int) self.txtname = txt self.output_init() if isinstance(calc, str): # Always use serial_communicator when a filename is given. self.calc = GPAW(calc, communicator=serial_comm, txt=None) else: # To be optimized so that the communicator is loaded automatically # according to kcommsize. # # so temporarily it is used like this : # kcommsize = int (should <= world.size) # r0 = rank % kcommsize # ranks = np.arange(r0, r0+size, kcommsize) # calc = GPAW(filename.gpw, communicator=ranks, txt=None) self.calc = calc if self.calc is not None: self.pwmode = isinstance(self.calc.wfs, pw.PWWaveFunctions) else: self.pwmode = False if self.pwmode: assert self.calc.wfs.world.size == 1 self.nbands = nbands self.q_c = q # chi.py modifies the input array w by dividing by Hartree. # This will change the user-supplied arrays in-place unless # we create a copy. So now we create a copy. *Grumble* # # To make matters worse, w is allowed to be None (why not take # care of that *before*?? This should really be cleaned up. if isinstance(w, np.ndarray): w = w.copy() self.w_w = w self.eta = eta self.ftol = ftol if isinstance(ecut, int) or isinstance(ecut, float): self.ecut = np.ones(3) * ecut else: assert len(ecut) == 3 self.ecut = np.array(ecut, dtype=float) self.density_cut = density_cut self.G_plus_q = G_plus_q self.rpad = rpad self.optical_limit = optical_limit if self.optical_limit: self.qopt = 1e-5 self.eshift = eshift def initialize(self): self.eta /= Hartree self.ecut /= Hartree calc = self.calc self.nspins = self.calc.wfs.nspins # kpoint init self.kd = kd = calc.wfs.kd self.nikpt = kd.nibzkpts self.ftol /= kd.nbzkpts # cell init self.acell_cv = calc.wfs.gd.cell_cv self.acell_cv, self.bcell_cv, self.vol, self.BZvol = \ get_primitive_cell(self.acell_cv,rpad=self.rpad) # grid init gd = calc.wfs.gd.new_descriptor(comm=serial_comm) self.pbc = gd.pbc_c self.gd = gd self.nG0 = np.prod(gd.N_c) # Number of grid points and volume including zero padding self.nGrpad = gd.N_c * self.rpad self.nG0rpad = np.prod(self.nGrpad) self.d_c = [Gradient(gd, i, n=4, dtype=complex).apply for i in range(3)] # obtain eigenvalues, occupations nibzkpt = kd.nibzkpts kweight_k = kd.weight_k self.eFermi = self.calc.occupations.get_fermi_level() try: self.e_skn self.printtxt('Use eigenvalues from user.') except: self.printtxt('Use eigenvalues from the calculator.') self.e_skn = {} self.f_skn = {} for ispin in range(self.nspins): self.e_skn[ispin] = np.array([calc.get_eigenvalues(kpt=k, spin=ispin) for k in range(nibzkpt)]) / Hartree self.f_skn[ispin] = np.array([calc.get_occupation_numbers(kpt=k, spin=ispin) / kweight_k[k] for k in range(nibzkpt)]) / kd.nbzkpts #self.printtxt('Eigenvalues(k=0) are:') #print >> self.txt, self.e_skn[0][0] * Hartree self.enoshift_skn = {} for ispin in range(self.nspins): self.enoshift_skn[ispin] = self.e_skn[ispin].copy() if self.eshift is not None: self.add_discontinuity(self.eshift) self.printtxt('Shift unoccupied bands by %f eV' % (self.eshift)) # k + q init if self.q_c is not None: self.qq_v = np.dot(self.q_c, self.bcell_cv) # summation over c if self.optical_limit: kq_k = np.arange(kd.nbzkpts) self.expqr_g = 1. else: r_vg = gd.get_grid_point_coordinates() # (3, nG) qr_g = gemmdot(self.qq_v, r_vg, beta=0.0) self.expqr_g = np.exp(-1j * qr_g) del r_vg, qr_g kq_k = kd.find_k_plus_q(self.q_c) self.kq_k = kq_k # Plane wave init if self.G_plus_q: self.npw, self.Gvec_Gc, self.Gindex_G = set_Gvectors(self.acell_cv, self.bcell_cv, self.gd.N_c, self.ecut, q=self.q_c) else: self.npw, self.Gvec_Gc, self.Gindex_G = set_Gvectors(self.acell_cv, self.bcell_cv, self.gd.N_c, self.ecut) # band init if self.nbands is None: self.nbands = calc.wfs.bd.nbands self.nvalence = calc.wfs.nvalence # Projectors init setups = calc.wfs.setups self.spos_ac = calc.atoms.get_scaled_positions() if self.pwmode: self.pt = PWLFC([setup.pt_j for setup in setups], self.calc.wfs.pd) self.pt.set_positions(self.spos_ac) else: self.pt = LFC(gd, [setup.pt_j for setup in setups], KPointDescriptor(self.kd.bzk_kc), dtype=complex, forces=True) self.pt.set_positions(self.spos_ac) # Printing calculation information self.print_stuff() return def output_init(self): if self.txtname is None: if rank == 0: self.txt = sys.stdout else: sys.stdout = devnull self.txt = devnull elif self.txtname == devnull: self.txt = devnull else: assert type(self.txtname) is str from ase.parallel import paropen self.txt = paropen(self.txtname,'w') def printtxt(self, text): print(text, file=self.txt) def print_stuff(self): printtxt = self.printtxt printtxt('') printtxt('Parameters used:') printtxt('') printtxt('Unit cell (a.u.):') printtxt(self.acell_cv) printtxt('Volume of cell (a.u.**3) : %f' % self.vol) printtxt('Reciprocal cell (1/a.u.)') printtxt(self.bcell_cv) printtxt('BZ volume (1/a.u.**3) : %f' % self.BZvol) printtxt('Number of G-vectors / Grid : %d %s' % (self.nG0, tuple(self.gd.N_c))) printtxt('') printtxt('Coulomb interaction cutoff : %s' % self.vcut) printtxt('') printtxt('Number of bands : %d' % self.nbands) printtxt('Number of kpoints : %d' % self.kd.nbzkpts) if self.ecut[0] == self.ecut[1] and self.ecut[0] == self.ecut[2]: printtxt('Planewave ecut (eV) : %4.1f' % (self.ecut[0] * Hartree)) else: printtxt('Planewave ecut (eV) : (%f, %f, %f)' % tuple(self.ecut * Hartree)) printtxt('Number of planewave used : %d' % self.npw) printtxt('Broadening (eta) : %f' % (self.eta * Hartree)) printtxt('') if self.q_c is not None: if self.optical_limit: printtxt('Optical limit calculation ! (q=1e-5)') else: printtxt('q in reduced coordinate : (%f %f %f)' % tuple(self.q_c)) printtxt('q in cartesian coordinate (1/A): (%f %f %f)' % tuple(self.qq_v / Bohr)) printtxt('|q| (1/A) : %f' % np.linalg.norm(self.qq_v / Bohr)) def timing(self, i, t0, n_local, txt): if i == 0: dt = time() - t0 self.totaltime = dt * n_local self.printtxt(' Finished %s 0 in %s, estimate %s left.' % (txt, timedelta(seconds=round(dt)), timedelta(seconds=round(self.totaltime)))) if rank == 0 and n_local // 5 > 0: if i > 0 and i % (n_local // 5) == 0: dt = time() - t0 self.printtxt(' Finished %s %d in %s, estimate %s left.' % (txt, i, timedelta(seconds=round(dt)), timedelta(seconds=round(self.totaltime - dt)))) def get_phi_aGp(self, q_c=None, parallel=True, alldir=False): if q_c is None: q_c = self.q_c qq_v = self.qq_v optical_limit = self.optical_limit else: optical_limit = False if np.abs(q_c).sum() < 1e-8: q_c = np.array([0.0001, 0, 0]) optical_limit = True qq_v = np.dot(q_c, self.bcell_cv) setups = self.calc.wfs.setups spos_ac = self.calc.atoms.get_scaled_positions() kk_Gv = gemmdot(q_c + self.Gvec_Gc, self.bcell_cv.copy(), beta=0.0) phi_aGp = {} phiG0_avp = {} if parallel: from gpaw.response.parallel import parallel_partition npw, npw_local, Gstart, Gend = parallel_partition( self.npw, self.comm.rank, self.comm.size, reshape=False) else: Gstart = 0 Gend = self.npw for a, id in enumerate(setups.id_a): phi_aGp[a] = two_phi_planewave_integrals(kk_Gv, setups[a], Gstart, Gend) for iG in range(Gstart, Gend): phi_aGp[a][iG] *= np.exp(-1j * 2. * pi * np.dot(q_c + self.Gvec_Gc[iG], spos_ac[a]) ) if parallel: self.comm.sum(phi_aGp[a]) # For optical limit, G == 0 part should change if optical_limit: for a, id in enumerate(setups.id_a): nabla_iiv = setups[a].nabla_iiv phi_aGp[a][0] = -1j * (np.dot(nabla_iiv, qq_v)).ravel() phiG0_avp[a] = np.zeros((3, len(phi_aGp[a][0])), complex) for dir in range(3): # 3 dimension q2_c = np.diag((1,1,1))[dir] * self.qopt qq2_v = np.dot(q2_c, self.bcell_cv) # summation over c phiG0_avp[a][dir] = -1j * (np.dot(nabla_iiv, qq2_v)).ravel() if alldir: return phi_aGp, phiG0_avp else: return phi_aGp def get_wavefunction(self, ibzk, n, check_focc=True, spin=0): if (self.calc.wfs.world.size == 1 or self.calc.wfs.gd.comm.size != 1 or self.calc.input_parameters['mode'] == 'lcao'): if not check_focc: return else: psit_G = self.calc.wfs.get_wave_function_array(n, ibzk, spin) if self.calc.wfs.world.size == 1: return np.complex128(psit_G) if self.calc.wfs.world.rank != 0: psit_G = self.calc.wfs.gd.empty(dtype=self.calc.wfs.dtype, global_array=True) self.calc.wfs.world.broadcast(psit_G, 0) return np.complex128(psit_G) else: # support ground state calculation with kpoint and band parallelization # but domain decomposition must = 1 kpt_rank, u = self.calc.wfs.kd.get_rank_and_index(0, ibzk) bzkpt_rank = self.kcomm.rank band_rank, myn = self.calc.wfs.bd.who_has(n) assert self.calc.wfs.gd.comm.size == 1 world_rank = (kpt_rank * self.calc.wfs.band_comm.size + band_rank) # in the following, kpt_rank is assigned to world_rank klist = np.array([world_rank, u, bzkpt_rank, myn]) klist_kcomm = np.zeros((self.kcomm.size, 4), dtype=int) self.kcomm.all_gather(klist, klist_kcomm) check_focc_global = np.zeros(self.kcomm.size, dtype=bool) self.kcomm.all_gather(np.array([check_focc]), check_focc_global) psit_G = self.calc.wfs.gd.empty(dtype=self.calc.wfs.dtype) for i in range(self.kcomm.size): if check_focc_global[i]: kpt_rank, u, bzkpt_rank, nlocal = klist_kcomm[i] if kpt_rank == bzkpt_rank: if rank == kpt_rank: psit_G = self.calc.wfs.kpt_u[u].psit_nG[nlocal] else: if rank == kpt_rank: world.send(self.calc.wfs.kpt_u[u].psit_nG[nlocal], bzkpt_rank, 1300+bzkpt_rank) if rank == bzkpt_rank: psit_G = self.calc.wfs.gd.empty(dtype=self.calc.wfs.dtype) world.receive(psit_G, kpt_rank, 1300+bzkpt_rank) self.wScomm.broadcast(psit_G, 0) return psit_G def add_discontinuity(self, shift): for ispin in range(self.nspins): for k in range(self.kd.nibzkpts): for i in range(self.e_skn[0].shape[1]): if self.e_skn[ispin][k,i] > self.eFermi: self.e_skn[ispin][k,i] += shift / Hartree def density_matrix(self, n, m, k, kq=None, spin1=0, spin2=0, phi_aGp=None, Gspace=True): gd = self.gd kd = self.kd optical_limit = False if kq is None: kq = self.kq_k[k] expqr_g = self.expqr_g q_v = self.qq_v optical_limit = self.optical_limit q_c = self.q_c else: q_c = kd.bzk_kc[kq] - kd.bzk_kc[k] q_c[np.where(q_c>0.501)] -= 1 q_c[np.where(q_c<-0.499)] += 1 if (np.abs(q_c) < self.ftol).all(): optical_limit = True q_c = self.q_c q_v = np.dot(q_c, self.bcell_cv) r_vg = gd.get_grid_point_coordinates() # (3, nG) qr_g = gemmdot(q_v, r_vg, beta=0.0) expqr_g = np.exp(-1j * qr_g) if optical_limit: expqr_g = 1 ibzkpt1 = kd.bz2ibz_k[k] ibzkpt2 = kd.bz2ibz_k[kq] psitold_g = self.get_wavefunction(ibzkpt1, n, True, spin=spin1) psit1_g = kd.transform_wave_function(psitold_g, k) psitold_g = self.get_wavefunction(ibzkpt2, m, True, spin=spin2) psit2_g = kd.transform_wave_function(psitold_g, kq) if Gspace is False: return psit1_g.conj() * psit2_g * expqr_g else: tmp_g = psit1_g.conj()* psit2_g * expqr_g # zero padding is included through the FFT rho_g = np.fft.fftn(tmp_g, s=self.nGrpad) * self.vol / self.nG0rpad # Here, planewave cutoff is applied rho_G = rho_g.ravel()[self.Gindex_G] if optical_limit: dpsit_g = gd.empty(dtype=complex) tmp = np.zeros((3), dtype=complex) phase_cd = np.exp(2j * pi * gd.sdisp_cd * kd.bzk_kc[kq, :, np.newaxis]) for ix in range(3): self.d_c[ix](psit2_g, dpsit_g, phase_cd) tmp[ix] = gd.integrate(psit1_g.conj() * dpsit_g) rho_G[0] = -1j * np.dot(q_v, tmp) calc = self.calc pt = self.pt if not self.pwmode: if calc.wfs.world.size > 1 or kd.nbzkpts == 1: P1_ai = pt.dict() pt.integrate(psit1_g, P1_ai, k) P2_ai = pt.dict() pt.integrate(psit2_g, P2_ai, kq) else: P1_ai = self.get_P_ai(k, n, spin1) P2_ai = self.get_P_ai(kq, m, spin2) else: # first calculate P_ai at ibzkpt, then rotate to k u = self.kd.get_rank_and_index(spin1, ibzkpt1)[1] Ptmp_ai = pt.dict() kpt = calc.wfs.kpt_u[u] pt.integrate(kpt.psit_nG[n], Ptmp_ai, ibzkpt1) P1_ai = self.get_P_ai(k, n, spin1, Ptmp_ai) u = self.kd.get_rank_and_index(spin2, ibzkpt2)[1] Ptmp_ai = pt.dict() kpt = calc.wfs.kpt_u[u] pt.integrate(kpt.psit_nG[m], Ptmp_ai, ibzkpt2) P2_ai = self.get_P_ai(kq, m, spin2, Ptmp_ai) if phi_aGp is None: try: if not self.mode == 'RPA': if optical_limit: iq = kd.where_is_q(np.zeros(3), self.bzq_qc) else: iq = kd.where_is_q(q_c, self.bzq_qc) assert np.abs(self.bzq_qc[iq] - q_c).sum() < 1e-8 phi_aGp = self.load_phi_aGp(self.reader, iq) #phi_qaGp[iq] except AttributeError: phi_aGp = self.phi_aGp for a, id in enumerate(self.calc.wfs.setups.id_a): P_p = np.outer(P1_ai[a].conj(), P2_ai[a]).ravel() phi_Gp = np.ascontiguousarray(phi_aGp[a], complex) gemv(1.0, phi_Gp, P_p, 1.0, rho_G) if optical_limit: if n==m: rho_G[0] = 1. elif np.abs(self.e_skn[spin2][ibzkpt2, m] - self.e_skn[spin1][ibzkpt1, n]) < 1e-5: rho_G[0] = 0. else: rho_G[0] /= (self.enoshift_skn[spin2][ibzkpt2, m] - self.enoshift_skn[spin1][ibzkpt1, n]) return rho_G def get_P_ai(self, k, n, spin=0, Ptmp_ai=None): calc = self.calc kd = self.calc.wfs.kd spos_ac = self.spos_ac ibzkpt = kd.bz2ibz_k[k] u = ibzkpt + kd.nibzkpts * spin kpt = calc.wfs.kpt_u[u] s = kd.sym_k[k] time_reversal = kd.time_reversal_k[k] P_ai = {} for a, id in enumerate(calc.wfs.setups.id_a): b = kd.symmetry.a_sa[s, a] S_c = (np.dot(spos_ac[a], kd.symmetry.op_scc[s]) - kd.symmetry.ft_sc[s] - spos_ac[b]) #print abs(S_c.round() - S_c).max() #print 'S_c', abs(S_c).max() assert abs(S_c.round() - S_c).max() < 1e-8 ############## k_c = kd.ibzk_kc[kpt.k] x = np.exp(2j * pi * np.dot(k_c, S_c)) if Ptmp_ai is None: P_i = np.dot(calc.wfs.setups[a].R_sii[s], kpt.P_ani[b][n]) * x else: P_i = np.dot(calc.wfs.setups[a].R_sii[s], Ptmp_ai[b]) * x if time_reversal: P_i = P_i.conj() P_ai[a] = P_i return P_ai
gd = GridDescriptor(calc.wfs.gd.N_c, calc.atoms.cell / Bohr, pbc_c=True, comm=serial_comm) kd = calc.wfs.kd setups = calc.wfs.setups bzk_kc = calc.wfs.kd.bzk_kc spos_ac = calc.atoms.get_scaled_positions() nbands = calc.get_number_of_bands() nspins = calc.wfs.nspins df = DF(calc) df.spos_ac = spos_ac if mode == 'fd': pt = LFC(gd, [setup.pt_j for setup in setups], KPointDescriptor(bzk_kc), dtype=calc.wfs.dtype) pt.set_positions(spos_ac) for spin in range(nspins): for k in range(len(bzk_kc)): ibzk = k # since no symmetry u = kd.get_rank_and_index(spin, ibzk)[1] kpt = calc.wfs.kpt_u[u] for n in range(nbands): P_ai = pt.dict() psit_G = calc.wfs.get_wave_function_array(n, ibzk, spin) pt.integrate(psit_G, P_ai, ibzk) for a in range(len(P_ai)): assert np.abs( P_ai[a] -
from gpaw.spherical_harmonics import Y from gpaw.response.math_func import two_phi_planewave_integrals # Initialize s, p, d (9 in total) wave and put them on grid rc = 2.0 a = 2.5 * rc n = 64 lmax = 2 b = 8.0 m = (lmax + 1)**2 gd = GridDescriptor([n, n, n], [a, a, a]) r = np.linspace(0, rc, 200) g = np.exp(-(r / rc * b)**2) splines = [Spline(l=l, rmax=rc, f_g=g) for l in range(lmax + 1)] c = LFC(gd, [splines]) c.set_positions([(0.5, 0.5, 0.5)]) psi = gd.zeros(m) d0 = c.dict(m) if 0 in d0: d0[0] = np.identity(m) c.add(psi, d0) # Calculate on 3d-grid < phi_i | e**(-ik.r) | phi_j > R_a = np.array([a/2,a/2,a/2]) rr = gd.get_grid_point_coordinates() for dim in range(3): rr[dim] -= R_a[dim] k_G = np.array([[11.,0.2,0.1],[10., 0., 10.]]) nkpt = k_G.shape[0]
class BASECHI: """This class is to store the basic common stuff for chi and bse.""" def __init__(self, calc=None, nbands=None, w=None, q=None, eshift=None, ecut=10., density_cut=None, G_plus_q=False, eta=0.2, rpad=None, ftol=1e-5, txt=None, optical_limit=False): if rpad is None: rpad = np.ones(3, int) self.txtname = txt self.output_init() if isinstance(calc, str): # Always use serial_communicator when a filename is given. self.calc = GPAW(calc, communicator=serial_comm, txt=None) else: # To be optimized so that the communicator is loaded automatically # according to kcommsize. # # so temporarily it is used like this : # kcommsize = int (should <= world.size) # r0 = rank % kcommsize # ranks = np.arange(r0, r0+size, kcommsize) # calc = GPAW(filename.gpw, communicator=ranks, txt=None) self.calc = calc if self.calc is not None: self.pwmode = isinstance(self.calc.wfs, pw.PWWaveFunctions) else: self.pwmode = False if self.pwmode: assert self.calc.wfs.world.size == 1 self.nbands = nbands self.q_c = q # chi.py modifies the input array w by dividing by Hartree. # This will change the user-supplied arrays in-place unless # we create a copy. So now we create a copy. *Grumble* # # To make matters worse, w is allowed to be None (why not take # care of that *before*?? This should really be cleaned up. if isinstance(w, np.ndarray): w = w.copy() self.w_w = w self.eta = eta self.ftol = ftol if isinstance(ecut, int) or isinstance(ecut, float): self.ecut = np.ones(3) * ecut else: assert len(ecut) == 3 self.ecut = np.array(ecut, dtype=float) self.density_cut = density_cut self.G_plus_q = G_plus_q self.rpad = rpad self.optical_limit = optical_limit if self.optical_limit: self.qopt = 1e-5 self.eshift = eshift def initialize(self): self.eta /= Hartree self.ecut /= Hartree calc = self.calc self.nspins = self.calc.wfs.nspins # kpoint init self.kd = kd = calc.wfs.kd self.nikpt = kd.nibzkpts self.ftol /= kd.nbzkpts # cell init self.acell_cv = calc.wfs.gd.cell_cv self.acell_cv, self.bcell_cv, self.vol, self.BZvol = \ get_primitive_cell(self.acell_cv,rpad=self.rpad) # grid init gd = calc.wfs.gd.new_descriptor(comm=serial_comm) self.pbc = gd.pbc_c self.gd = gd self.nG0 = np.prod(gd.N_c) # Number of grid points and volume including zero padding self.nGrpad = gd.N_c * self.rpad self.nG0rpad = np.prod(self.nGrpad) self.d_c = [ Gradient(gd, i, n=4, dtype=complex).apply for i in range(3) ] # obtain eigenvalues, occupations nibzkpt = kd.nibzkpts kweight_k = kd.weight_k self.eFermi = self.calc.occupations.get_fermi_level() try: self.e_skn self.printtxt('Use eigenvalues from user.') except: self.printtxt('Use eigenvalues from the calculator.') self.e_skn = {} self.f_skn = {} for ispin in range(self.nspins): self.e_skn[ispin] = np.array([ calc.get_eigenvalues(kpt=k, spin=ispin) for k in range(nibzkpt) ]) / Hartree self.f_skn[ispin] = np.array([ calc.get_occupation_numbers(kpt=k, spin=ispin) / kweight_k[k] for k in range(nibzkpt) ]) / kd.nbzkpts #self.printtxt('Eigenvalues(k=0) are:') #print >> self.txt, self.e_skn[0][0] * Hartree self.enoshift_skn = {} for ispin in range(self.nspins): self.enoshift_skn[ispin] = self.e_skn[ispin].copy() if self.eshift is not None: self.add_discontinuity(self.eshift) self.printtxt('Shift unoccupied bands by %f eV' % (self.eshift)) # k + q init if self.q_c is not None: self.qq_v = np.dot(self.q_c, self.bcell_cv) # summation over c if self.optical_limit: kq_k = np.arange(kd.nbzkpts) self.expqr_g = 1. else: r_vg = gd.get_grid_point_coordinates() # (3, nG) qr_g = gemmdot(self.qq_v, r_vg, beta=0.0) self.expqr_g = np.exp(-1j * qr_g) del r_vg, qr_g kq_k = kd.find_k_plus_q(self.q_c) self.kq_k = kq_k # Plane wave init if self.G_plus_q: self.npw, self.Gvec_Gc, self.Gindex_G = set_Gvectors(self.acell_cv, self.bcell_cv, self.gd.N_c, self.ecut, q=self.q_c) else: self.npw, self.Gvec_Gc, self.Gindex_G = set_Gvectors( self.acell_cv, self.bcell_cv, self.gd.N_c, self.ecut) # band init if self.nbands is None: self.nbands = calc.wfs.bd.nbands self.nvalence = calc.wfs.nvalence # Projectors init setups = calc.wfs.setups self.spos_ac = calc.atoms.get_scaled_positions() if self.pwmode: self.pt = PWLFC([setup.pt_j for setup in setups], self.calc.wfs.pd) self.pt.set_positions(self.spos_ac) else: self.pt = LFC(gd, [setup.pt_j for setup in setups], KPointDescriptor(self.kd.bzk_kc), dtype=complex, forces=True) self.pt.set_positions(self.spos_ac) # Printing calculation information self.print_stuff() return def output_init(self): if self.txtname is None: if rank == 0: self.txt = sys.stdout else: sys.stdout = devnull self.txt = devnull elif self.txtname == devnull: self.txt = devnull else: assert type(self.txtname) is str from ase.parallel import paropen self.txt = paropen(self.txtname, 'w') def printtxt(self, text): print(text, file=self.txt) def print_stuff(self): printtxt = self.printtxt printtxt('') printtxt('Parameters used:') printtxt('') printtxt('Unit cell (a.u.):') printtxt(self.acell_cv) printtxt('Volume of cell (a.u.**3) : %f' % self.vol) printtxt('Reciprocal cell (1/a.u.)') printtxt(self.bcell_cv) printtxt('BZ volume (1/a.u.**3) : %f' % self.BZvol) printtxt('Number of G-vectors / Grid : %d %s' % (self.nG0, tuple(self.gd.N_c))) printtxt('') printtxt('Coulomb interaction cutoff : %s' % self.vcut) printtxt('') printtxt('Number of bands : %d' % self.nbands) printtxt('Number of kpoints : %d' % self.kd.nbzkpts) if self.ecut[0] == self.ecut[1] and self.ecut[0] == self.ecut[2]: printtxt('Planewave ecut (eV) : %4.1f' % (self.ecut[0] * Hartree)) else: printtxt('Planewave ecut (eV) : (%f, %f, %f)' % tuple(self.ecut * Hartree)) printtxt('Number of planewave used : %d' % self.npw) printtxt('Broadening (eta) : %f' % (self.eta * Hartree)) printtxt('') if self.q_c is not None: if self.optical_limit: printtxt('Optical limit calculation ! (q=1e-5)') else: printtxt('q in reduced coordinate : (%f %f %f)' % tuple(self.q_c)) printtxt('q in cartesian coordinate (1/A): (%f %f %f)' % tuple(self.qq_v / Bohr)) printtxt('|q| (1/A) : %f' % np.linalg.norm(self.qq_v / Bohr)) def timing(self, i, t0, n_local, txt): if i == 0: dt = time() - t0 self.totaltime = dt * n_local self.printtxt(' Finished %s 0 in %s, estimate %s left.' % (txt, timedelta(seconds=round(dt)), timedelta(seconds=round(self.totaltime)))) if rank == 0 and n_local // 5 > 0: if i > 0 and i % (n_local // 5) == 0: dt = time() - t0 self.printtxt(' Finished %s %d in %s, estimate %s left.' % (txt, i, timedelta(seconds=round(dt)), timedelta(seconds=round(self.totaltime - dt)))) def get_phi_aGp(self, q_c=None, parallel=True, alldir=False): if q_c is None: q_c = self.q_c qq_v = self.qq_v optical_limit = self.optical_limit else: optical_limit = False if np.abs(q_c).sum() < 1e-8: q_c = np.array([0.0001, 0, 0]) optical_limit = True qq_v = np.dot(q_c, self.bcell_cv) setups = self.calc.wfs.setups spos_ac = self.calc.atoms.get_scaled_positions() kk_Gv = gemmdot(q_c + self.Gvec_Gc, self.bcell_cv.copy(), beta=0.0) phi_aGp = {} phiG0_avp = {} if parallel: from gpaw.response.parallel import parallel_partition npw, npw_local, Gstart, Gend = parallel_partition(self.npw, self.comm.rank, self.comm.size, reshape=False) else: Gstart = 0 Gend = self.npw for a, id in enumerate(setups.id_a): phi_aGp[a] = two_phi_planewave_integrals(kk_Gv, setups[a], Gstart, Gend) for iG in range(Gstart, Gend): phi_aGp[a][iG] *= np.exp( -1j * 2. * pi * np.dot(q_c + self.Gvec_Gc[iG], spos_ac[a])) if parallel: self.comm.sum(phi_aGp[a]) # For optical limit, G == 0 part should change if optical_limit: for a, id in enumerate(setups.id_a): nabla_iiv = setups[a].nabla_iiv phi_aGp[a][0] = -1j * (np.dot(nabla_iiv, qq_v)).ravel() phiG0_avp[a] = np.zeros((3, len(phi_aGp[a][0])), complex) for dir in range(3): # 3 dimension q2_c = np.diag((1, 1, 1))[dir] * self.qopt qq2_v = np.dot(q2_c, self.bcell_cv) # summation over c phiG0_avp[a][dir] = -1j * (np.dot(nabla_iiv, qq2_v)).ravel() if alldir: return phi_aGp, phiG0_avp else: return phi_aGp def get_wavefunction(self, ibzk, n, check_focc=True, spin=0): if (self.calc.wfs.world.size == 1 or self.calc.wfs.gd.comm.size != 1 or self.calc.input_parameters['mode'] == 'lcao'): if not check_focc: return else: psit_G = self.calc.wfs.get_wave_function_array(n, ibzk, spin) if self.calc.wfs.world.size == 1: return np.complex128(psit_G) if self.calc.wfs.world.rank != 0: psit_G = self.calc.wfs.gd.empty(dtype=self.calc.wfs.dtype, global_array=True) self.calc.wfs.world.broadcast(psit_G, 0) return np.complex128(psit_G) else: # support ground state calculation with kpoint and band parallelization # but domain decomposition must = 1 kpt_rank, u = self.calc.wfs.kd.get_rank_and_index(0, ibzk) bzkpt_rank = self.kcomm.rank band_rank, myn = self.calc.wfs.bd.who_has(n) assert self.calc.wfs.gd.comm.size == 1 world_rank = (kpt_rank * self.calc.wfs.band_comm.size + band_rank) # in the following, kpt_rank is assigned to world_rank klist = np.array([world_rank, u, bzkpt_rank, myn]) klist_kcomm = np.zeros((self.kcomm.size, 4), dtype=int) self.kcomm.all_gather(klist, klist_kcomm) check_focc_global = np.zeros(self.kcomm.size, dtype=bool) self.kcomm.all_gather(np.array([check_focc]), check_focc_global) psit_G = self.calc.wfs.gd.empty(dtype=self.calc.wfs.dtype) for i in range(self.kcomm.size): if check_focc_global[i]: kpt_rank, u, bzkpt_rank, nlocal = klist_kcomm[i] if kpt_rank == bzkpt_rank: if rank == kpt_rank: psit_G = self.calc.wfs.kpt_u[u].psit_nG[nlocal] else: if rank == kpt_rank: world.send(self.calc.wfs.kpt_u[u].psit_nG[nlocal], bzkpt_rank, 1300 + bzkpt_rank) if rank == bzkpt_rank: psit_G = self.calc.wfs.gd.empty( dtype=self.calc.wfs.dtype) world.receive(psit_G, kpt_rank, 1300 + bzkpt_rank) self.wScomm.broadcast(psit_G, 0) return psit_G def add_discontinuity(self, shift): for ispin in range(self.nspins): for k in range(self.kd.nibzkpts): for i in range(self.e_skn[0].shape[1]): if self.e_skn[ispin][k, i] > self.eFermi: self.e_skn[ispin][k, i] += shift / Hartree def density_matrix(self, n, m, k, kq=None, spin1=0, spin2=0, phi_aGp=None, Gspace=True): gd = self.gd kd = self.kd optical_limit = False if kq is None: kq = self.kq_k[k] expqr_g = self.expqr_g q_v = self.qq_v optical_limit = self.optical_limit q_c = self.q_c else: q_c = kd.bzk_kc[kq] - kd.bzk_kc[k] q_c[np.where(q_c > 0.501)] -= 1 q_c[np.where(q_c < -0.499)] += 1 if (np.abs(q_c) < self.ftol).all(): optical_limit = True q_c = self.q_c q_v = np.dot(q_c, self.bcell_cv) r_vg = gd.get_grid_point_coordinates() # (3, nG) qr_g = gemmdot(q_v, r_vg, beta=0.0) expqr_g = np.exp(-1j * qr_g) if optical_limit: expqr_g = 1 ibzkpt1 = kd.bz2ibz_k[k] ibzkpt2 = kd.bz2ibz_k[kq] psitold_g = self.get_wavefunction(ibzkpt1, n, True, spin=spin1) psit1_g = kd.transform_wave_function(psitold_g, k) psitold_g = self.get_wavefunction(ibzkpt2, m, True, spin=spin2) psit2_g = kd.transform_wave_function(psitold_g, kq) if Gspace is False: return psit1_g.conj() * psit2_g * expqr_g else: tmp_g = psit1_g.conj() * psit2_g * expqr_g # zero padding is included through the FFT rho_g = np.fft.fftn(tmp_g, s=self.nGrpad) * self.vol / self.nG0rpad # Here, planewave cutoff is applied rho_G = rho_g.ravel()[self.Gindex_G] if optical_limit: dpsit_g = gd.empty(dtype=complex) tmp = np.zeros((3), dtype=complex) phase_cd = np.exp(2j * pi * gd.sdisp_cd * kd.bzk_kc[kq, :, np.newaxis]) for ix in range(3): self.d_c[ix](psit2_g, dpsit_g, phase_cd) tmp[ix] = gd.integrate(psit1_g.conj() * dpsit_g) rho_G[0] = -1j * np.dot(q_v, tmp) calc = self.calc pt = self.pt if not self.pwmode: if calc.wfs.world.size > 1 or kd.nbzkpts == 1: P1_ai = pt.dict() pt.integrate(psit1_g, P1_ai, k) P2_ai = pt.dict() pt.integrate(psit2_g, P2_ai, kq) else: P1_ai = self.get_P_ai(k, n, spin1) P2_ai = self.get_P_ai(kq, m, spin2) else: # first calculate P_ai at ibzkpt, then rotate to k u = self.kd.get_rank_and_index(spin1, ibzkpt1)[1] Ptmp_ai = pt.dict() kpt = calc.wfs.kpt_u[u] pt.integrate(kpt.psit_nG[n], Ptmp_ai, ibzkpt1) P1_ai = self.get_P_ai(k, n, spin1, Ptmp_ai) u = self.kd.get_rank_and_index(spin2, ibzkpt2)[1] Ptmp_ai = pt.dict() kpt = calc.wfs.kpt_u[u] pt.integrate(kpt.psit_nG[m], Ptmp_ai, ibzkpt2) P2_ai = self.get_P_ai(kq, m, spin2, Ptmp_ai) if phi_aGp is None: try: if not self.mode == 'RPA': if optical_limit: iq = kd.where_is_q(np.zeros(3), self.bzq_qc) else: iq = kd.where_is_q(q_c, self.bzq_qc) assert np.abs(self.bzq_qc[iq] - q_c).sum() < 1e-8 phi_aGp = self.load_phi_aGp(self.reader, iq) #phi_qaGp[iq] except AttributeError: phi_aGp = self.phi_aGp for a, id in enumerate(self.calc.wfs.setups.id_a): P_p = np.outer(P1_ai[a].conj(), P2_ai[a]).ravel() phi_Gp = np.ascontiguousarray(phi_aGp[a], complex) gemv(1.0, phi_Gp, P_p, 1.0, rho_G) if optical_limit: if n == m: rho_G[0] = 1. elif np.abs(self.e_skn[spin2][ibzkpt2, m] - self.e_skn[spin1][ibzkpt1, n]) < 1e-5: rho_G[0] = 0. else: rho_G[0] /= (self.enoshift_skn[spin2][ibzkpt2, m] - self.enoshift_skn[spin1][ibzkpt1, n]) return rho_G def get_P_ai(self, k, n, spin=0, Ptmp_ai=None): calc = self.calc kd = self.calc.wfs.kd spos_ac = self.spos_ac ibzkpt = kd.bz2ibz_k[k] u = ibzkpt + kd.nibzkpts * spin kpt = calc.wfs.kpt_u[u] s = kd.sym_k[k] time_reversal = kd.time_reversal_k[k] P_ai = {} for a, id in enumerate(calc.wfs.setups.id_a): b = kd.symmetry.a_sa[s, a] S_c = (np.dot(spos_ac[a], kd.symmetry.op_scc[s]) - kd.symmetry.ft_sc[s] - spos_ac[b]) #print abs(S_c.round() - S_c).max() #print 'S_c', abs(S_c).max() assert abs(S_c.round() - S_c).max() < 1e-8 ############## k_c = kd.ibzk_kc[kpt.k] x = np.exp(2j * pi * np.dot(k_c, S_c)) if Ptmp_ai is None: P_i = np.dot(calc.wfs.setups[a].R_sii[s], kpt.P_ani[b][n]) * x else: P_i = np.dot(calc.wfs.setups[a].R_sii[s], Ptmp_ai[b]) * x if time_reversal: P_i = P_i.conj() P_ai[a] = P_i return P_ai
class ELF: """ELF object for calculating the electronic localization function. Arguments: =============== ===================================================== ``paw`` Instance of ``GPAW`` class. ``ncut`` Density cutoff below which the ELF is zero. =============== ===================================================== """ def __init__(self, paw=None, ncut=1e-6): """Create the ELF object.""" self.gd = paw.wfs.gd self.paw = paw self.finegd = paw.density.finegd self.nspins = paw.density.nspins self.density = paw.density self.ncut = ncut self.spinpol = (self.nspins == 2) self.initialize(paw) def initialize(self, paw): if not paw.initialized: raise RuntimeError('PAW instance is not initialized') paw.converge_wave_functions() self.tauct = LFC(self.gd, [[setup.tauct] for setup in self.density.setups], forces=True, cut=True) spos_ac = paw.atoms.get_scaled_positions() % 1.0 self.tauct.set_positions(spos_ac) self.taut_sg = None self.nt_grad2_sG = self.gd.empty(self.nspins) self.nt_grad2_sg = None def interpolate(self): self.density.interpolate_pseudo_density() if self.taut_sg is None: self.taut_sg = self.finegd.empty(self.nspins) self.nt_grad2_sg = self.finegd.empty(self.nspins) ddr_v = [Gradient(self.finegd, v, n=3).apply for v in range(3)] self.nt_grad2_sg[:] = 0.0 d_g = self.finegd.empty() # Transfer the densities from the coarse to the fine grid for s in range(self.nspins): self.density.interpolator.apply(self.taut_sG[s], self.taut_sg[s]) #self.density.interpolator.apply(self.nt_grad2_sG[s], # self.nt_grad2_sg[s]) for v in range(3): ddr_v[v](self.density.nt_sg[s], d_g) self.nt_grad2_sg[s] += d_g**2.0 def update(self): self.taut_sG = self.paw.wfs.calculate_kinetic_energy_density() # Add the pseudo core kinetic array for taut_G in self.taut_sG: self.tauct.add(taut_G, 1.0 / self.paw.wfs.nspins) # For periodic boundary conditions if self.paw.wfs.symmetry is not None: self.paw.wfs.symmetry.symmetrize(self.taut_sG[0], self.paw.wfs.gd) self.nt_grad2_sG[:] = 0.0 d_G = self.gd.empty() for s in range(self.nspins): for v in range(3): self.paw.wfs.taugrad_v[v](self.density.nt_sG[s], d_G) self.nt_grad2_sG[s] += d_G**2.0 #TODO are nct from setups usable for nt_grad2_sG ? def get_electronic_localization_function(self, gridrefinement=1, pad=True, broadcast=True): # Returns dimensionless electronic localization function if gridrefinement == 1: elf_G = _elf(self.density.nt_sG, self.nt_grad2_sG, self.taut_sG, self.ncut, self.spinpol) elf_G = self.gd.collect(elf_G, broadcast) if pad: elf_G = self.gd.zero_pad(elf_G) return elf_G elif gridrefinement == 2: if self.nt_grad2_sg is None: self.interpolate() elf_g = _elf(self.density.nt_sg, self.nt_grad2_sg, self.taut_sg, self.ncut, self.spinpol) elf_g = self.finegd.collect(elf_g, broadcast) if pad: elf_g = self.finegd.zero_pad(elf_g) return elf_g else: raise NotImplementedError('Arbitrary refinement not implemented')
class WaveFunctions: """Class for wave-function related stuff (e.g. projectors).""" def __init__(self, nbands, kpt_u, setups, kd, gd, dtype=float): """Store and initialize required attributes. Parameters ---------- nbands: int Number of occupied bands. kpt_u: list of KPoints List of KPoint instances from a ground-state calculation (i.e. the attribute ``calc.wfs.kpt_u``). setups: Setups LocalizedFunctionsCollection setups. kd: KPointDescriptor K-point and symmetry related stuff. gd: GridDescriptor Descriptor for the coarse grid. dtype: dtype This is the ``dtype`` for the wave-function derivatives (same as the ``dtype`` for the ground-state wave-functions). """ self.dtype = dtype # K-point related attributes self.kd = kd # Number of occupied bands self.nbands = nbands # Projectors self.pt = LFC(gd, [setup.pt_j for setup in setups], dtype=self.dtype) # Store grid self.gd = gd # Unfold the irreducible BZ to the full BZ # List of KPointContainers for the k-points in the full BZ self.kpt_u = [] # No symmetries or only time-reversal symmetry used if kd.symmetry is None: # For now, time-reversal symmetry not allowed assert len(kpt_u) == kd.nbzkpts for k in range(kd.nbzkpts): kpt_ = kpt_u[k] psit_nG = gd.empty(nbands, dtype=self.dtype) for n, psit_G in enumerate(psit_nG): psit_G[:] = kpt_.psit_nG[n] # psit_0 = psit_G[0, 0, 0] # psit_G *= psit_0.conj() / (abs(psit_0)) # Strip off KPoint attributes and store in the KPointContainer # Note, only the occupied GS wave-functions are retained here ! kpt = KPointContainer(weight=kpt_.weight, k=kpt_.k, s=kpt_.s, phase_cd=kpt_.phase_cd, eps_n=kpt_.eps_n[:nbands], psit_nG=psit_nG, psit1_nG=None, P_ani=None, dP_aniv=None) # q=kpt.q, # f_n=kpt.f_n[:nbands]) self.kpt_u.append(kpt) else: assert len(kpt_u) == kd.nibzkpts for k, k_c in enumerate(kd.bzk_kc): # Index of symmetry related point in the irreducible BZ ik = kd.kibz_k[k] # Index of point group operation s = kd.sym_k[k] # Time-reversal symmetry used time_reversal = kd.time_reversal_k[k] # Coordinates of symmetry related point in the irreducible BZ ik_c = kd.ibzk_kc[ik] # Point group operation op_cc = kd.symmetry.op_scc[s] # KPoint from ground-state calculation kpt_ = kpt_u[ik] weight = 1. / kd.nbzkpts * (2 - kpt_.s) phase_cd = np.exp(2j * pi * gd.sdisp_cd * k_c[:, np.newaxis]) psit_nG = gd.empty(nbands, dtype=self.dtype) for n, psit_G in enumerate(psit_nG): #XXX Seems to corrupt my memory somehow ??? psit_G[:] = kd.symmetry.symmetrize_wavefunction( kpt_.psit_nG[n], ik_c, k_c, op_cc, time_reversal) # Choose gauge # psit_0 = psit_G[0, 0, 0] # psit_G *= psit_0.conj() / (abs(psit_0)) kpt = KPointContainer(weight=weight, k=k, s=kpt_.s, phase_cd=phase_cd, eps_n=kpt_.eps_n[:nbands], psit_nG=psit_nG, psit1_nG=None, P_ani=None, dP_aniv=None) self.kpt_u.append(kpt) def initialize(self, spos_ac): """Initialize projectors according to the ``gamma`` attribute.""" # Set positions on LFC's self.pt.set_positions(spos_ac) if not self.kd.gamma: # Set k-vectors and update self.pt.set_k_points(self.kd.ibzk_kc) self.pt._update(spos_ac) # Calculate projector coefficients for the GS wave-functions self.calculate_projector_coef() def reset(self): """Make fresh arrays for wave-function derivatives.""" for kpt in self.kpt_u: kpt.psit1_nG = self.gd.zeros(n=self.nbands, dtype=self.dtype) def calculate_projector_coef(self): """Coefficients for the derivative of the non-local part of the PP. Parameters ---------- k: int Index of the k-point of the Bloch state on which the non-local potential operates on. The calculated coefficients are the following (except for an overall sign of -1; see ``derivative`` member function of class ``LFC``): 1. Coefficients from the projector functions:: / a P_ani = | dG p (G) Psi (G) , / i n 2. Coefficients from the derivative of the projector functions:: / a dP_aniv = | dG dp (G) Psi (G) , / iv n where:: a d a dp (G) = --- Phi (G) . iv a i dR """ n = self.nbands for kpt in self.kpt_u: # K-point index and wave-functions k = kpt.k psit_nG = kpt.psit_nG # Integration dicts P_ani = self.pt.dict(shape=n) dP_aniv = self.pt.dict(shape=n, derivative=True) # 1) Integrate with projectors self.pt.integrate(psit_nG, P_ani, q=k) kpt.P_ani = P_ani # 2) Integrate with derivative of projectors self.pt.derivative(psit_nG, dP_aniv, q=k) kpt.dP_aniv = dP_aniv
from __future__ import print_function import numpy as np from gpaw.lfc import LocalizedFunctionsCollection as LFC from gpaw.grid_descriptor import GridDescriptor from gpaw.spline import Spline gd = GridDescriptor([20, 16, 16], [(4, 2, 0), (0, 4, 0), (0, 0, 4)]) spos_ac = np.array([[0.252, 0.15, 0.35], [0.503, 0.5, 0.5]]) s = Spline(l=0, rmax=2.0, f_g=np.array([1, 0.9, 0.1, 0.0])) spline_aj = [[s], [s]] c = LFC(gd, spline_aj) c.set_positions(spos_ac) c_ai = c.dict(zero=True) if 1 in c_ai: c_ai[1][0] = 2.0 psi = gd.zeros() c.add(psi, c_ai) d_avv = dict([(a, np.zeros((3, 3))) for a in c.my_atom_indices]) c.second_derivative(psi, d_avv) if 0 in d_avv: print(d_avv[0]) eps = 0.000001 d_aiv = c.dict(derivative=True) pos_av = np.dot(spos_ac, gd.cell_cv) for v in range(3): pos_av[0, v] += eps c.set_positions(np.dot(pos_av, gd.icell_cv.T)) c.derivative(psi, d_aiv) if 0 in d_aiv:
import numpy as np from gpaw.lfc import LocalizedFunctionsCollection as LFC from gpaw.grid_descriptor import GridDescriptor from gpaw.spline import Spline a = 4.0 gd = GridDescriptor(N_c=[16, 20, 20], cell_cv=[a, a + 1, a + 2], pbc_c=(0, 1, 1)) spos_ac = np.array([[0.25, 0.15, 0.35], [0.5, 0.5, 0.5]]) kpts_kc = None s = Spline(l=0, rmax=2.0, f_g=np.array([1, 0.9, 0.1, 0.0])) p = Spline(l=1, rmax=2.0, f_g=np.array([1, 0.9, 0.1, 0.0])) spline_aj = [[s], [s, p]] c = LFC(gd, spline_aj, cut=True, forces=True) c.set_positions(spos_ac) C_ani = c.dict(3, zero=True) if 1 in C_ani: C_ani[1][:, 1:] = np.eye(3) psi = gd.zeros(3) c.add(psi, C_ani) c.integrate(psi, C_ani) if 1 in C_ani: d = C_ani[1][:, 1:].diagonal() assert d.ptp() < 4e-6 C_ani[1][:, 1:] -= np.diag(d) assert abs(C_ani[1]).max() < 5e-17 d_aniv = c.dict(3, derivative=True) c.derivative(psi, d_aniv) if 1 in d_aniv: for v in range(3): assert abs(d_aniv[1][v - 1, 0, v] + 0.2144) < 5e-5 d_aniv[1][v - 1, 0, v] = 0
def initialize(self): self.eta /= Hartree self.ecut /= Hartree calc = self.calc # kpoint init self.kd = kd = calc.wfs.kd self.bzk_kc = kd.bzk_kc self.ibzk_kc = kd.ibzk_kc self.nkpt = kd.nbzkpts self.ftol /= self.nkpt # band init if self.nbands is None: self.nbands = calc.wfs.nbands self.nvalence = calc.wfs.nvalence # cell init self.acell_cv = calc.atoms.cell / Bohr self.bcell_cv, self.vol, self.BZvol = get_primitive_cell(self.acell_cv) # grid init self.nG = calc.get_number_of_grid_points() self.nG0 = self.nG[0] * self.nG[1] * self.nG[2] gd = GridDescriptor(self.nG, calc.wfs.gd.cell_cv, pbc_c=True, comm=serial_comm) self.gd = gd self.h_cv = gd.h_cv # obtain eigenvalues, occupations nibzkpt = kd.nibzkpts kweight_k = kd.weight_k try: self.e_kn except: self.printtxt('Use eigenvalues from the calculator.') self.e_kn = np.array( [calc.get_eigenvalues(kpt=k) for k in range(nibzkpt)]) / Hartree self.printtxt('Eigenvalues(k=0) are:') print >> self.txt, self.e_kn[0] * Hartree self.f_kn = np.array([ calc.get_occupation_numbers(kpt=k) / kweight_k[k] for k in range(nibzkpt) ]) / self.nkpt # k + q init assert self.q_c is not None self.qq_v = np.dot(self.q_c, self.bcell_cv) # summation over c if self.optical_limit: kq_k = np.arange(self.nkpt) self.expqr_g = 1. else: r_vg = gd.get_grid_point_coordinates() # (3, nG) qr_g = gemmdot(self.qq_v, r_vg, beta=0.0) self.expqr_g = np.exp(-1j * qr_g) del r_vg, qr_g kq_k = kd.find_k_plus_q(self.q_c) self.kq_k = kq_k # Plane wave init self.npw, self.Gvec_Gc, self.Gindex_G = set_Gvectors( self.acell_cv, self.bcell_cv, self.nG, self.ecut) # Projectors init setups = calc.wfs.setups pt = LFC(gd, [setup.pt_j for setup in setups], dtype=calc.wfs.dtype, forces=True) spos_ac = calc.atoms.get_scaled_positions() pt.set_k_points(self.bzk_kc) pt.set_positions(spos_ac) self.pt = pt # Printing calculation information self.print_stuff() return
class PhononPerturbation(Perturbation): """Implementation of a phonon perturbation. This class implements the change in the effective potential due to a displacement of an atom ``a`` in direction ``v`` with wave-vector ``q``. The action of the perturbing potential on a state vector is implemented in the ``apply`` member function. """ def __init__(self, calc, kd, poisson_solver, dtype=float, **kwargs): """Store useful objects, e.g. lfc's for the various atomic functions. Depending on whether the system is periodic or finite, Poisson's equation is solved with FFT or multigrid techniques, respectively. Parameters ---------- calc: Calculator Ground-state calculation. kd: KPointDescriptor Descriptor for the q-vectors of the dynamical matrix. """ self.kd = kd self.dtype = dtype self.poisson = poisson_solver # Gamma wrt q-vector if self.kd.gamma: self.phase_cd = None else: assert self.kd.mynks == len(self.kd.ibzk_qc) self.phase_qcd = [] sdisp_cd = calc.wfs.gd.sdisp_cd for q in range(self.kd.mynks): phase_cd = np.exp(2j * np.pi * \ sdisp_cd * self.kd.ibzk_qc[q, :, np.newaxis]) self.phase_qcd.append(phase_cd) # Store grid-descriptors self.gd = calc.density.gd self.finegd = calc.density.finegd # Steal setups for the lfc's setups = calc.wfs.setups # Store projector coefficients self.dH_asp = calc.hamiltonian.dH_asp.copy() # Localized functions: # core corections self.nct = LFC(self.gd, [[setup.nct] for setup in setups], integral=[setup.Nct for setup in setups], dtype=self.dtype) # compensation charges #XXX what is the consequence of numerical errors in the integral ?? self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], dtype=self.dtype) ## self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], ## integral=sqrt(4 * pi), dtype=self.dtype) # vbar potential self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], dtype=self.dtype) # Expansion coefficients for the compensation charges self.Q_aL = calc.density.Q_aL.copy() # Grid transformer -- convert array from fine to coarse grid self.restrictor = Transformer(self.finegd, self.gd, nn=3, dtype=self.dtype, allocate=False) # Atom, cartesian coordinate and q-vector of the perturbation self.a = None self.v = None # Local q-vector index of the perturbation if self.kd.gamma: self.q = -1 else: self.q = None def initialize(self, spos_ac): """Prepare the various attributes for a calculation.""" # Set positions on LFC's self.nct.set_positions(spos_ac) self.ghat.set_positions(spos_ac) self.vbar.set_positions(spos_ac) if not self.kd.gamma: # Set q-vectors and update self.ghat.set_k_points(self.kd.ibzk_qc) self.ghat._update(spos_ac) # Set q-vectors and update self.vbar.set_k_points(self.kd.ibzk_qc) self.vbar._update(spos_ac) # Phase factor exp(iq.r) needed to obtian the periodic part of lfcs coor_vg = self.finegd.get_grid_point_coordinates() cell_cv = self.finegd.cell_cv # Convert to scaled coordinates scoor_cg = np.dot(la.inv(cell_cv), coor_vg.swapaxes(0, -2)) scoor_cg = scoor_cg.swapaxes(1,-2) # Phase factor phase_qg = np.exp(2j * pi * np.dot(self.kd.ibzk_qc, scoor_cg.swapaxes(0,-2))) self.phase_qg = phase_qg.swapaxes(1, -2) #XXX To be removed from this class !! # Setup the Poisson solver -- to be used on the fine grid self.poisson.set_grid_descriptor(self.finegd) self.poisson.initialize() # Grid transformer self.restrictor.allocate() def set_q(self, q): """Set the index of the q-vector of the perturbation.""" assert not self.kd.gamma, "Gamma-point calculation" self.q = q # Update phases and Poisson solver self.phase_cd = self.phase_qcd[q] self.poisson.set_q(self.kd.ibzk_qc[q]) # Invalidate calculated quantities # - local part of perturbing potential self.v1_G = None def set_av(self, a, v): """Set atom and cartesian component of the perturbation. Parameters ---------- a: int Index of the atom. v: int Cartesian component (0, 1 or 2) of the atomic displacement. """ assert self.q is not None self.a = a self.v = v # Update derivative of local potential self.calculate_local_potential() def get_phase_cd(self): """Overwrite base class member function.""" return self.phase_cd def has_q(self): """Overwrite base class member function.""" return (not self.kd.gamma) def get_q(self): """Return q-vector.""" assert not self.kd.gamma, "Gamma-point calculation." return self.kd.ibzk_qc[self.q] def solve_poisson(self, phi_g, rho_g): """Solve Poisson's equation for a Bloch-type charge distribution. More to come here ... Parameters ---------- phi_g: GridDescriptor Grid for the solution of Poissons's equation. rho_g: GridDescriptor Grid with the charge distribution. """ #assert phi_g.shape == rho_g.shape == self.phase_qg.shape[-3:], \ # ("Arrays have incompatible shapes.") assert self.q is not None, ("q-vector not set") # Gamma point calculation wrt the q-vector -> rho_g periodic if self.kd.gamma: #XXX NOTICE: solve_neutral self.poisson.solve_neutral(phi_g, rho_g) else: # Divide out the phase factor to get the periodic part rhot_g = rho_g/self.phase_qg[self.q] # Solve Poisson's equation for the periodic part of the potential #XXX NOTICE: solve_neutral self.poisson.solve_neutral(phi_g, rhot_g) # Return to Bloch form phi_g *= self.phase_qg[self.q] def calculate_local_potential(self): """Derivate of the local potential wrt an atomic displacements. The local part of the PAW potential has contributions from the compensation charges (``ghat``) and a spherical symmetric atomic potential (``vbar``). """ assert self.a is not None assert self.v is not None assert self.q is not None a = self.a v = self.v # Expansion coefficients for the ghat functions Q_aL = self.ghat.dict(zero=True) # Remember sign convention for add_derivative method # And be sure not to change the dtype of the arrays by assigning values # to array elements. Q_aL[a][:] = -1 * self.Q_aL[a] # Grid for derivative of compensation charges ghat1_g = self.finegd.zeros(dtype=self.dtype) self.ghat.add_derivative(a, v, ghat1_g, c_axi=Q_aL, q=self.q) # Solve Poisson's eq. for the potential from the periodic part of the # compensation charge derivative v1_g = self.finegd.zeros(dtype=self.dtype) self.solve_poisson(v1_g, ghat1_g) # Store potential from the compensation charge self.vghat1_g = v1_g.copy() # Add derivative of vbar - sign convention in add_derivative method c_ai = self.vbar.dict(zero=True) c_ai[a][0] = -1. self.vbar.add_derivative(a, v, v1_g, c_axi=c_ai, q=self.q) # Store potential for the evaluation of the energy derivative self.v1_g = v1_g.copy() # Transfer to coarse grid v1_G = self.gd.zeros(dtype=self.dtype) self.restrictor.apply(v1_g, v1_G, phases=self.phase_cd) self.v1_G = v1_G def apply(self, psi_nG, y_nG, wfs, k, kplusq): """Apply perturbation to unperturbed wave-functions. Parameters ---------- psi_nG: ndarray Set of grid vectors to which the perturbation is applied. y_nG: ndarray Output vectors. wfs: WaveFunctions Instance of class ``WaveFunctions``. k: int Index of the k-point for the vectors. kplusq: int Index of the k+q vector. """ assert self.a is not None assert self.v is not None assert self.q is not None assert psi_nG.ndim in (3, 4) assert tuple(self.gd.n_c) == psi_nG.shape[-3:] if psi_nG.ndim == 3: y_nG += self.v1_G * psi_nG else: y_nG += self.v1_G[np.newaxis, :] * psi_nG self.apply_nonlocal_potential(psi_nG, y_nG, wfs, k, kplusq) def apply_nonlocal_potential(self, psi_nG, y_nG, wfs, k, kplusq): """Derivate of the non-local PAW potential wrt an atomic displacement. Parameters ---------- k: int Index of the k-point being operated on. kplusq: int Index of the k+q vector. """ assert self.a is not None assert self.v is not None assert psi_nG.ndim in (3, 4) assert tuple(self.gd.n_c) == psi_nG.shape[-3:] if psi_nG.ndim == 3: n = 1 else: n = psi_nG.shape[0] a = self.a v = self.v P_ani = wfs.kpt_u[k].P_ani dP_aniv = wfs.kpt_u[k].dP_aniv pt = wfs.pt # < p_a^i | Psi_nk > P_ni = P_ani[a] # < dp_av^i | Psi_nk > - remember the sign convention of the derivative dP_ni = -1 * dP_aniv[a][...,v] # Expansion coefficients for the projectors on atom a dH_ii = unpack(self.dH_asp[a][0]) # The derivative of the non-local PAW potential has two contributions # 1) Sum over projectors c_ni = np.dot(dP_ni, dH_ii) c_ani = pt.dict(shape=n, zero=True) c_ani[a] = c_ni # k+q !! pt.add(y_nG, c_ani, q=kplusq) # 2) Sum over derivatives of the projectors dc_ni = np.dot(P_ni, dH_ii) dc_ani = pt.dict(shape=n, zero=True) # Take care of sign of derivative in the coefficients dc_ani[a] = -1 * dc_ni # k+q !! pt.add_derivative(a, v, y_nG, dc_ani, q=kplusq)
from gpaw.grid_descriptor import GridDescriptor, RadialGridDescriptor from gpaw.spline import Spline from gpaw.setup import Setup rc = 2.0 a = 2.5 * rc n = 64 lmax = 2 b = 8.0 m = (lmax + 1)**2 gd = GridDescriptor([n, n, n], [a, a, a]) r = np.linspace(0, rc, 200) g = np.exp(-(r / rc * b)**2) splines = [Spline(l=l, rmax=rc, f_g=g) for l in range(lmax + 1)] c = LFC(gd, [splines]) c.set_positions([(0, 0, 0)]) psi = gd.zeros(m) d0 = c.dict(m) if 0 in d0: d0[0] = np.identity(m) c.add(psi, d0) d1 = c.dict(m, derivative=True) c.derivative(psi, d1) class TestSetup(Setup): l_j = range(lmax + 1) nj = lmax + 1 ni = m def __init__(self):
class FDWaveFunctions(FDPWWaveFunctions): def __init__(self, stencil, diagksl, orthoksl, initksl, gd, nvalence, setups, bd, dtype, world, kd, timer=None): FDPWWaveFunctions.__init__(self, diagksl, orthoksl, initksl, gd, nvalence, setups, bd, dtype, world, kd, timer) # Kinetic energy operator: self.kin = Laplace(self.gd, -0.5, stencil, self.dtype) self.matrixoperator = MatrixOperator(self.orthoksl) self.taugrad_v = None # initialized by MGGA functional def empty(self, n=(), global_array=False, realspace=False, q=-1): return self.gd.empty(n, self.dtype, global_array) def integrate(self, a_xg, b_yg=None, global_integral=True): return self.gd.integrate(a_xg, b_yg, global_integral) def bytes_per_wave_function(self): return self.gd.bytecount(self.dtype) def set_setups(self, setups): self.pt = LFC(self.gd, [setup.pt_j for setup in setups], self.kd, dtype=self.dtype, forces=True) FDPWWaveFunctions.set_setups(self, setups) def set_positions(self, spos_ac): FDPWWaveFunctions.set_positions(self, spos_ac) def summary(self, fd): fd.write('Wave functions: Uniform real-space grid\n') fd.write('Kinetic energy operator: %s\n' % self.kin.description) def make_preconditioner(self, block=1): return Preconditioner(self.gd, self.kin, self.dtype, block) def apply_pseudo_hamiltonian(self, kpt, hamiltonian, psit_xG, Htpsit_xG): self.timer.start('Apply hamiltonian') self.kin.apply(psit_xG, Htpsit_xG, kpt.phase_cd) hamiltonian.apply_local_potential(psit_xG, Htpsit_xG, kpt.s) self.timer.stop('Apply hamiltonian') def add_orbital_density(self, nt_G, kpt, n): if self.dtype == float: axpy(1.0, kpt.psit_nG[n]**2, nt_G) else: axpy(1.0, kpt.psit_nG[n].real**2, nt_G) axpy(1.0, kpt.psit_nG[n].imag**2, nt_G) def add_to_density_from_k_point_with_occupation(self, nt_sG, kpt, f_n): # Used in calculation of response part of GLLB-potential nt_G = nt_sG[kpt.s] if self.dtype == float: for f, psit_G in zip(f_n, kpt.psit_nG): axpy(f, psit_G**2, nt_G) else: for f, psit_G in zip(f_n, kpt.psit_nG): axpy(f, psit_G.real**2, nt_G) axpy(f, psit_G.imag**2, nt_G) # Hack used in delta-scf calculations: if hasattr(kpt, 'c_on'): assert self.bd.comm.size == 1 d_nn = np.zeros((self.bd.mynbands, self.bd.mynbands), dtype=complex) for ne, c_n in zip(kpt.ne_o, kpt.c_on): d_nn += ne * np.outer(c_n.conj(), c_n) for d_n, psi0_G in zip(d_nn, kpt.psit_nG): for d, psi_G in zip(d_n, kpt.psit_nG): if abs(d) > 1.e-12: nt_G += (psi0_G.conj() * d * psi_G).real def calculate_kinetic_energy_density(self): if self.taugrad_v is None: self.taugrad_v = [ Gradient(self.gd, v, n=3, dtype=self.dtype).apply for v in range(3)] assert not hasattr(self.kpt_u[0], 'c_on') if self.kpt_u[0].psit_nG is None: raise RuntimeError('No wavefunctions yet') if isinstance(self.kpt_u[0].psit_nG, FileReference): # XXX initialize raise RuntimeError('Wavefunctions have not been initialized.') taut_sG = self.gd.zeros(self.nspins) dpsit_G = self.gd.empty(dtype=self.dtype) for kpt in self.kpt_u: for f, psit_G in zip(kpt.f_n, kpt.psit_nG): for v in range(3): self.taugrad_v[v](psit_G, dpsit_G, kpt.phase_cd) axpy(0.5 * f, abs(dpsit_G)**2, taut_sG[kpt.s]) self.kpt_comm.sum(taut_sG) self.band_comm.sum(taut_sG) return taut_sG def apply_mgga_orbital_dependent_hamiltonian(self, kpt, psit_xG, Htpsit_xG, dH_asp, dedtaut_G): a_G = self.gd.empty(dtype=psit_xG.dtype) for psit_G, Htpsit_G in zip(psit_xG, Htpsit_xG): for v in range(3): self.taugrad_v[v](psit_G, a_G, kpt.phase_cd) self.taugrad_v[v](dedtaut_G * a_G, a_G, kpt.phase_cd) axpy(-0.5, a_G, Htpsit_G) 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, usesymm=None) 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 write(self, writer, write_wave_functions=False): writer['Mode'] = 'fd' if not write_wave_functions: return writer.add('PseudoWaveFunctions', ('nspins', 'nibzkpts', 'nbands', 'ngptsx', 'ngptsy', 'ngptsz'), dtype=self.dtype) if hasattr(writer, 'hdf5'): parallel = (self.world.size > 1) for kpt in self.kpt_u: indices = [kpt.s, kpt.k] indices.append(self.bd.get_slice()) indices += self.gd.get_slice() writer.fill(kpt.psit_nG, parallel=parallel, *indices) else: for s in range(self.nspins): for k in range(self.nibzkpts): for n in range(self.bd.nbands): psit_G = self.get_wave_function_array(n, k, s) writer.fill(psit_G, s, k, n) def read(self, reader, hdf5): if ((not hdf5 and self.bd.comm.size == 1) or (hdf5 and self.world.size == 1)): # We may not be able to keep all the wave # functions in memory - so psit_nG will be a special type of # array that is really just a reference to a file: for kpt in self.kpt_u: kpt.psit_nG = reader.get_reference('PseudoWaveFunctions', (kpt.s, kpt.k)) else: for kpt in self.kpt_u: kpt.psit_nG = self.empty(self.bd.mynbands) if hdf5: indices = [kpt.s, kpt.k] indices.append(self.bd.get_slice()) indices += self.gd.get_slice() reader.get('PseudoWaveFunctions', out=kpt.psit_nG, parallel=(self.world.size > 1), *indices) else: # Read band by band to save memory for myn, psit_G in enumerate(kpt.psit_nG): n = self.bd.global_index(myn) if self.gd.comm.rank == 0: big_psit_G = np.array( reader.get('PseudoWaveFunctions', kpt.s, kpt.k, n), self.dtype) else: big_psit_G = None self.gd.distribute(big_psit_G, psit_G) def initialize_from_lcao_coefficients(self, basis_functions, mynbands): for kpt in self.kpt_u: kpt.psit_nG = self.gd.zeros(self.bd.mynbands, self.dtype) basis_functions.lcao_to_grid(kpt.C_nM, kpt.psit_nG[:mynbands], kpt.q) kpt.C_nM = None def random_wave_functions(self, nao): """Generate random wave functions.""" gpts = self.gd.N_c[0] * self.gd.N_c[1] * self.gd.N_c[2] if self.bd.nbands < gpts / 64: gd1 = self.gd.coarsen() gd2 = gd1.coarsen() psit_G1 = gd1.empty(dtype=self.dtype) psit_G2 = gd2.empty(dtype=self.dtype) interpolate2 = Transformer(gd2, gd1, 1, self.dtype).apply interpolate1 = Transformer(gd1, self.gd, 1, self.dtype).apply shape = tuple(gd2.n_c) scale = np.sqrt(12 / abs(np.linalg.det(gd2.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G2[:] = (np.random.random(shape) - 0.5) * scale else: psit_G2.real = (np.random.random(shape) - 0.5) * scale psit_G2.imag = (np.random.random(shape) - 0.5) * scale interpolate2(psit_G2, psit_G1, kpt.phase_cd) interpolate1(psit_G1, psit_G, kpt.phase_cd) np.random.set_state(old_state) elif gpts / 64 <= self.bd.nbands < gpts / 8: gd1 = self.gd.coarsen() psit_G1 = gd1.empty(dtype=self.dtype) interpolate1 = Transformer(gd1, self.gd, 1, self.dtype).apply shape = tuple(gd1.n_c) scale = np.sqrt(12 / abs(np.linalg.det(gd1.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G1[:] = (np.random.random(shape) - 0.5) * scale else: psit_G1.real = (np.random.random(shape) - 0.5) * scale psit_G1.imag = (np.random.random(shape) - 0.5) * scale interpolate1(psit_G1, psit_G, kpt.phase_cd) np.random.set_state(old_state) else: shape = tuple(self.gd.n_c) scale = np.sqrt(12 / abs(np.linalg.det(self.gd.cell_cv))) old_state = np.random.get_state() np.random.seed(4 + self.world.rank) for kpt in self.kpt_u: for psit_G in kpt.psit_nG[nao:]: if self.dtype == float: psit_G[:] = (np.random.random(shape) - 0.5) * scale else: psit_G.real = (np.random.random(shape) - 0.5) * scale psit_G.imag = (np.random.random(shape) - 0.5) * scale np.random.set_state(old_state) def estimate_memory(self, mem): FDPWWaveFunctions.estimate_memory(self, mem)
def initialize(self): self.eta /= Hartree self.ecut /= Hartree calc = self.calc # kpoint init self.kd = kd = calc.wfs.kd self.bzk_kc = kd.bzk_kc self.ibzk_kc = kd.ibzk_kc self.nkpt = kd.nbzkpts self.ftol /= self.nkpt # band init if self.nbands is None: self.nbands = calc.wfs.nbands self.nvalence = calc.wfs.nvalence # cell init self.acell_cv = calc.atoms.cell / Bohr self.bcell_cv, self.vol, self.BZvol = get_primitive_cell(self.acell_cv) # grid init self.nG = calc.get_number_of_grid_points() self.nG0 = self.nG[0] * self.nG[1] * self.nG[2] gd = GridDescriptor(self.nG, calc.wfs.gd.cell_cv, pbc_c=True, comm=serial_comm) self.gd = gd self.h_cv = gd.h_cv # obtain eigenvalues, occupations nibzkpt = kd.nibzkpts kweight_k = kd.weight_k try: self.e_kn except: self.printtxt('Use eigenvalues from the calculator.') self.e_kn = np.array([calc.get_eigenvalues(kpt=k) for k in range(nibzkpt)]) / Hartree self.printtxt('Eigenvalues(k=0) are:') print >> self.txt, self.e_kn[0] * Hartree self.f_kn = np.array([calc.get_occupation_numbers(kpt=k) / kweight_k[k] for k in range(nibzkpt)]) / self.nkpt # k + q init assert self.q_c is not None self.qq_v = np.dot(self.q_c, self.bcell_cv) # summation over c if self.optical_limit: kq_k = np.arange(self.nkpt) self.expqr_g = 1. else: r_vg = gd.get_grid_point_coordinates() # (3, nG) qr_g = gemmdot(self.qq_v, r_vg, beta=0.0) self.expqr_g = np.exp(-1j * qr_g) del r_vg, qr_g kq_k = kd.find_k_plus_q(self.q_c) self.kq_k = kq_k # Plane wave init self.npw, self.Gvec_Gc, self.Gindex_G = set_Gvectors(self.acell_cv, self.bcell_cv, self.nG, self.ecut) # Projectors init setups = calc.wfs.setups pt = LFC(gd, [setup.pt_j for setup in setups], dtype=calc.wfs.dtype, forces=True) spos_ac = calc.atoms.get_scaled_positions() pt.set_k_points(self.bzk_kc) pt.set_positions(spos_ac) self.pt = pt # Printing calculation information self.print_stuff() return