def initialize(self, paw, allocate=True): self.allocated = False assert hasattr(paw, 'time') and hasattr(paw, 'niter'), 'Use TDDFT!' self.time = paw.time self.niter = paw.niter self.world = paw.wfs.world self.gd = paw.density.gd self.finegd = paw.density.finegd self.nspins = paw.density.nspins self.stencil = paw.input_parameters.stencils[ 1] # i.e. tar['InterpolationStencil'] self.interpolator = paw.density.interpolator self.cinterpolator = Transformer(self.gd, self.finegd, self.stencil, \ dtype=self.dtype) self.phase_cd = np.ones((3, 2), dtype=complex) self.Ant_sG = paw.density.nt_sG.copy() # TODO in allocate instead? # Attach to PAW-type object paw.attach(self, self.interval, density=paw.density) if allocate: self.allocate()
def interpolate_pseudo_density(self, gridrefinement=2): gd = self.gd Fnt_wsg = self.Fnt_wsG.copy() # Find m for # gridrefinement = 2**m m1 = np.log(gridrefinement) / np.log(2.) m = int(np.round(m1)) # Check if m is really integer if np.absolute(m - m1) < 1e-8: for i in range(m): gd2 = gd.refine() # Interpolate interpolator = Transformer(gd, gd2, self.stencil, dtype=self.dtype) Fnt2_wsg = gd2.empty((self.nw, self.nspins), dtype=self.dtype) for w in range(self.nw): for s in range(self.nspins): interpolator.apply(Fnt_wsg[w][s], Fnt2_wsg[w][s], np.ones((3, 2), dtype=complex)) gd = gd2 Fnt_wsg = Fnt2_wsg else: raise NotImplementedError return Fnt_wsg, gd
def go(comm, ngpts, repeat, narrays, out, prec): N_c = np.array((ngpts, ngpts, ngpts)) a = 10.0 gd = GridDescriptor(N_c, (a, a, a), comm=comm)) gdcoarse = gd.coarsen() gdfine = gd.refine() kin1 = Laplace(gd, -0.5, 1).apply laplace = Laplace(gd, -0.5, 2) kin2 = laplace.apply restrict = Transformer(gd, gdcoarse, 1).apply interpolate = Transformer(gd, gdfine, 1).apply precondition = Preconditioner(gd, laplace, np_float) a1 = gd.empty(narrays) a1[:] = 1.0 a2 = gd.empty(narrays) c = gdcoarse.empty(narrays) f = gdfine.empty(narrays) T = [0, 0, 0, 0, 0] for i in range(repeat): comm.barrier() kin1(a1, a2) comm.barrier() t0a = time() kin1(a1, a2) t0b = time() comm.barrier() t1a = time() kin2(a1, a2) t1b = time() comm.barrier() t2a = time() for A, C in zip(a1,c): restrict(A, C) t2b = time() comm.barrier() t3a = time() for A, F in zip(a1,f): interpolate(A, F) t3b = time() comm.barrier() if prec: t4a = time() for A in a1: precondition(A, None, None, None) t4b = time() comm.barrier() T[0] += t0b - t0a T[1] += t1b - t1a T[2] += t2b - t2a T[3] += t3b - t3a if prec: T[4] += t4b - t4a if mpi.rank == 0: out.write(' %2d %2d %2d' % tuple(gd.parsize_c)) out.write(' %12.6f %12.6f %12.6f %12.6f %12.6f\n' % tuple([t / repeat / narrays for t in T])) out.flush()
def set_grid_descriptor(self, gd): self.gd = gd self.gds = [gd] self.dv = gd.dv gd = self.gd self.B = None self.interpolators = [] self.restrictors = [] self.operators = [] level = 0 self.presmooths = [2] self.postsmooths = [1] self.weights = [2. / 3.] while level < 8: try: gd2 = gd.coarsen() except ValueError: break self.gds.append(gd2) self.interpolators.append(Transformer(gd2, gd)) self.restrictors.append(Transformer(gd, gd2)) self.presmooths.append(4) self.postsmooths.append(4) self.weights.append(1.0) level += 1 gd = gd2 self.levels = level
def set_grid_descriptor(self, gd): # Should probably be renamed initialize self.gd = gd scale = -0.25 / pi if self.nn == 'M': if not gd.orthogonal: raise RuntimeError('Cannot use Mehrstellen stencil with ' 'non orthogonal cell.') self.operators = [LaplaceA(gd, -scale)] self.B = LaplaceB(gd) else: self.operators = [Laplace(gd, scale, self.nn)] self.B = None self.interpolators = [] self.restrictors = [] level = 0 self.presmooths = [2] self.postsmooths = [1] # Weights for the relaxation, # only used if 'J' (Jacobi) is chosen as method self.weights = [2.0 / 3.0] while level < 8: try: gd2 = gd.coarsen() except ValueError: break self.operators.append(Laplace(gd2, scale, 1)) self.interpolators.append(Transformer(gd2, gd)) self.restrictors.append(Transformer(gd, gd2)) self.presmooths.append(4) self.postsmooths.append(4) self.weights.append(1.0) level += 1 gd = gd2 self.levels = level if self.operators[-1].gd.N_c.max() > 36: # Try to warn exactly once no matter how one uses the solver. if gd.comm.parent is None: warn = (gd.comm.rank == 0) else: warn = (gd.comm.parent.rank == 0) if warn: warntxt = '\n'.join([POISSON_GRID_WARNING, '', self.get_description()]) else: warntxt = ('Poisson warning from domain rank %d' % self.gd.comm.rank) # Warn from all ranks to avoid deadlocks. warnings.warn(warntxt, stacklevel=2)
def get_combined_data(self, qmdata=None, cldata=None, spacing=None): if qmdata is None: qmdata = self.density.rhot_g if cldata is None: cldata = self.classical_material.charge_density if spacing is None: spacing = self.cl.gd.h_cv[0, 0] spacing_au = spacing / Bohr # from Angstroms to a.u. # Collect data from different processes cln = self.cl.gd.collect(cldata) qmn = self.qm.gd.collect(qmdata) clgd = GridDescriptor(self.cl.gd.N_c, self.cl.cell, False, serial_comm, None) if world.rank == 0: cln *= self.classical_material.sign # refine classical part while clgd.h_cv[0, 0] > spacing_au * 1.50: # 45: cln = Transformer(clgd, clgd.refine()).apply(cln) clgd = clgd.refine() # refine quantum part qmgd = GridDescriptor(self.qm.gd.N_c, self.qm.cell, False, serial_comm, None) while qmgd.h_cv[0, 0] < clgd.h_cv[0, 0] * 0.95: qmn = Transformer(qmgd, qmgd.coarsen()).apply(qmn) qmgd = qmgd.coarsen() assert np.all(qmgd.h_cv == clgd.h_cv ), " Spacings %.8f (qm) and %.8f (cl) Angstroms" % ( qmgd.h_cv[0][0] * Bohr, clgd.h_cv[0][0] * Bohr) # now find the corners r_gv_cl = clgd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) cind = self.qm.corner1 / np.diag(clgd.h_cv) - 1 n = qmn.shape # print 'Corner points: ', self.qm.corner1*Bohr, ' - ', self.qm.corner2*Bohr # print 'Calculated points: ', r_gv_cl[tuple(cind)]*Bohr, ' - ', r_gv_cl[tuple(cind+n+1)]*Bohr cln[cind[0] + 1:cind[0] + n[0] + 1, cind[1] + 1:cind[1] + n[1] + 1, cind[2] + 1:cind[2] + n[2] + 1] += qmn world.barrier() return cln, clgd
def interpolate_2d(mat): from gpaw.grid_descriptor import GridDescriptor from gpaw.transformers import Transformer nn = 10 N_c = np.zeros([3], dtype=int) N_c[1:] = mat.shape[:2] N_c[0] = nn bmat = np.resize(mat, N_c) gd = GridDescriptor(N_c, N_c) finegd = GridDescriptor(N_c * 2, N_c) interpolator = Transformer(gd, finegd, 3) fine_bmat = finegd.zeros() interpolator.apply(bmat, fine_bmat) return fine_bmat[0]
def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) self.gd = density.gd self.redistributor = density.redistributor use_charge_center = hamiltonian.poisson.use_charge_center # XXX How do we construct a copy of the Poisson solver of the # Hamiltonian? We don't know what class it is, etc., but gd # may differ. # XXX One might consider using a charged centered compensation # charge for the PoissonSolver in the case of EXX as standard self.poissonsolver = PoissonSolver('fd', eps=1e-11, use_charge_center=use_charge_center) # self.poissonsolver = hamiltonian.poisson if self.finegrid: self.finegd = self.gd.refine() # XXX Taking restrictor from Hamiltonian will not work in PW mode, # will it? I think this supports only real-space mode. # self.restrictor = hamiltonian.restrictor self.restrictor = Transformer(self.finegd, self.gd, 3) self.interpolator = Transformer(self.gd, self.finegd, 3) else: self.finegd = self.gd self.ghat = LFC(self.finegd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.poissonsolver.set_grid_descriptor(self.finegd) if self.rsf == 'Yukawa': omega2 = self.omega**2 self.screened_poissonsolver = HelmholtzSolver( k2=-omega2, eps=1e-11, nn=3, use_charge_center=use_charge_center) self.screened_poissonsolver.set_grid_descriptor(self.finegd)
def __init__(self, gd, finegd, nspins, setups, timer, xc, world, kptband_comm, vext=None, collinear=True, psolver=None, stencil=3): Hamiltonian.__init__(self, gd, finegd, nspins, setups, timer, xc, world, kptband_comm, vext, collinear) # Solver for the Poisson equation: if psolver is None: psolver = PoissonSolver(nn=3, relax='J') self.poisson = psolver self.poisson.set_grid_descriptor(finegd) # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.gd, stencil) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.vbar_g = None
def writearray(name): if name.split('_')[0] in writes: a_xg = getattr(self, name) if self.fdtd.cl.gd != self.gd: a_xg = Transformer(self.fdtd.cl.gd, self.gd, self.stencil, a_xg.dtype).apply(a_xg) writer.write(**{name: self.gd.collect(a_xg)})
def set_grid_descriptor(self, gd): # Should probably be renamed initialize self.gd = gd self.dv = gd.dv gd = self.gd scale = -0.25 / pi if self.nn == 'M': raise ValueError('Helmholtz not defined for Mehrstellen stencil') self.operators = [HelmholtzOperator(gd, scale, self.nn, k2=self.k2)] self.B = None self.interpolators = [] self.restrictors = [] level = 0 self.presmooths = [2] self.postsmooths = [1] # Weights for the relaxation, # only used if 'J' (Jacobi) is chosen as method self.weights = [2.0 / 3.0] while level < 4: try: gd2 = gd.coarsen() except ValueError: break self.operators.append(HelmholtzOperator(gd2, scale, 1, k2=self.k2)) self.interpolators.append(Transformer(gd2, gd)) self.restrictors.append(Transformer(gd, gd2)) self.presmooths.append(4) self.postsmooths.append(4) self.weights.append(1.0) level += 1 gd = gd2 self.levels = level if self.relax_method == 1: self.description = 'Gauss-Seidel' else: self.description = 'Jacobi' self.description += ' solver with %d multi-grid levels' % (level + 1) self.description += '\nStencil: ' + self.operators[0].description
def initialize(self, setups, timer, magmom_av, hund): Density.initialize(self, setups, timer, magmom_av, hund) # Interpolation function for the density: self.interpolator = Transformer(self.gd, self.finegd, self.stencil) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True)
def set_grid_descriptor(self, gd): # Should probably be renamed initialize self.gd = gd self.dv = gd.dv gd = self.gd scale = -0.25 / pi if self.nn == 'M': if not gd.orthogonal: raise RuntimeError('Cannot use Mehrstellen stencil with ' 'non orthogonal cell.') self.operators = [LaplaceA(gd, -scale, allocate=False)] self.B = LaplaceB(gd, allocate=False) else: self.operators = [Laplace(gd, scale, self.nn, allocate=False)] self.B = None self.interpolators = [] self.restrictors = [] level = 0 self.presmooths = [2] self.postsmooths = [1] # Weights for the relaxation, # only used if 'J' (Jacobi) is chosen as method self.weights = [2.0 / 3.0] while level < 4: try: gd2 = gd.coarsen() except ValueError: break self.operators.append(Laplace(gd2, scale, 1, allocate=False)) self.interpolators.append(Transformer(gd2, gd, allocate=False)) self.restrictors.append(Transformer(gd, gd2, allocate=False)) self.presmooths.append(4) self.postsmooths.append(4) self.weights.append(1.0) level += 1 gd = gd2 self.levels = level
def __init__(self, gd, finegd, nspins, setups, stencil, timer, xc, psolver, vext_g): """Create the Hamiltonian.""" self.gd = gd self.finegd = finegd self.nspins = nspins self.setups = setups self.timer = timer self.xc = xc # Solver for the Poisson equation: if psolver is None: psolver = PoissonSolver(nn=3, relax='J') self.poisson = psolver self.poisson.set_grid_descriptor(finegd) self.dH_asp = None # The external potential self.vext_g = vext_g self.vt_sG = None self.vHt_g = None self.vt_sg = None self.vbar_g = None self.rank_a = None # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.gd, stencil, allocate=False) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.Ekin0 = None self.Ekin = None self.Epot = None self.Ebar = None self.Eext = None self.Exc = None self.Etot = None self.S = None self.allocated = False
def __init__(self, gd0, kin0, dtype=float, block=1): gd1 = gd0.coarsen() gd2 = gd1.coarsen() self.kin0 = kin0 self.kin1 = Laplace(gd1, -0.5, 1, dtype) self.kin2 = Laplace(gd2, -0.5, 1, dtype) self.scratch0 = gd0.zeros((2, block), dtype, False) self.scratch1 = gd1.zeros((3, block), dtype, False) self.scratch2 = gd2.zeros((3, block), dtype, False) self.step = 0.66666666 / kin0.get_diagonal_element() self.restrictor_object0 = Transformer(gd0, gd1, 1, dtype) self.restrictor_object1 = Transformer(gd1, gd2, 1, dtype) self.interpolator_object2 = Transformer(gd2, gd1, 1, dtype) self.interpolator_object1 = Transformer(gd1, gd0, 1, dtype) self.restrictor0 = self.restrictor_object0.apply self.restrictor1 = self.restrictor_object1.apply self.interpolator2 = self.interpolator_object2.apply self.interpolator1 = self.interpolator_object1.apply
def get_induced_density(self, from_density, gridrefinement): if self.gd == self.fdtd.cl.gd: Frho_wg = self.Fn_wG.copy() else: Frho_wg = Transformer(self.fdtd.cl.gd, self.gd, self.stencil, dtype=self.dtype).apply(self.Fn_wG) Frho_wg, gd = self.interpolate_density(self.gd, Frho_wg, gridrefinement) return Frho_wg, gd
def __init__(self, gd, finegd, nspins, setups, timer, xc, world, vext=None, psolver=None, stencil=3, redistributor=None): Hamiltonian.__init__(self, gd, finegd, nspins, setups, timer, xc, world, vext=vext, redistributor=redistributor) # Solver for the Poisson equation: if psolver is None: psolver = {} if isinstance(psolver, dict): psolver = create_poisson_solver(**psolver) self.poisson = psolver self.poisson.set_grid_descriptor(self.finegd) # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.redistributor.aux_gd, stencil) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.vbar_g = None
def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) self.gd = density.gd self.redistributor = density.redistributor # XXX How do we construct a copy of the Poisson solver of the # Hamiltonian? We don't know what class it is, etc., but gd # may differ. self.poissonsolver = PoissonSolver(eps=1e-11) #self.poissonsolver = hamiltonian.poisson if self.finegrid: self.finegd = self.gd.refine() # XXX Taking restrictor from Hamiltonian will not work in PW mode, # will it? I think this supports only real-space mode. #self.restrictor = hamiltonian.restrictor self.restrictor = Transformer(self.finegd, self.gd, 3) self.interpolator = Transformer(self.gd, self.finegd, 3) else: self.finegd = self.gd self.ghat = LFC(self.finegd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.poissonsolver.set_grid_descriptor(self.finegd) self.poissonsolver.initialize()
def initialize(self, setups, stencil, timer, magmom_a, hund): self.timer = timer self.setups = setups self.hund = hund self.magmom_a = magmom_a # Interpolation function for the density: self.interpolator = Transformer(self.gd, self.finegd, stencil, allocate=False) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) if self.allocated: self.allocated = False self.allocate()
def initialize(self, paw, allocate=True): self.allocated = False assert hasattr(paw, 'time') and hasattr(paw, 'niter'), 'Use TDDFT!' self.time = paw.time self.niter = paw.niter self.world = paw.wfs.world self.gd = paw.density.gd self.finegd = paw.density.finegd self.nspins = paw.density.nspins self.stencil = paw.input_parameters.stencils[1] # i.e. tar['InterpolationStencil'] self.interpolator = paw.density.interpolator self.cinterpolator = Transformer(self.gd, self.finegd, self.stencil, \ dtype=self.dtype, allocate=False) self.phase_cd = np.ones((3, 2), dtype=complex) self.Ant_sG = paw.density.nt_sG.copy() # TODO in allocate instead? # Attach to PAW-type object paw.attach(self, self.interval, density=paw.density) if allocate: self.allocate()
def get_all_electron_density(self, atoms, gridrefinement=2): """Return real all-electron density array.""" # Refinement of coarse grid, for representation of the AE-density if gridrefinement == 1: gd = self.gd n_sg = self.nt_sG.copy() elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = LFC(gd, phi_aj) phit = LFC(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) spos_ac = atoms.get_scaled_positions() % 1.0 phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) all_D_asp = [] for a, setup in enumerate(self.setups): D_sp = self.D_asp.get(a) if D_sp is None: ni = setup.ni D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) if gd.comm.size > 1: gd.comm.broadcast(D_sp, self.rank_a[a]) all_D_asp.append(D_sp) for s in range(self.nspins): I_a = np.zeros(len(atoms)) nc.add1(n_sg[s], 1.0 / self.nspins, I_a) nct.add1(n_sg[s], -1.0 / self.nspins, I_a) phi.add2(n_sg[s], all_D_asp, s, 1.0, I_a) phit.add2(n_sg[s], all_D_asp, s, -1.0, I_a) for a, D_sp in self.D_asp.items(): setup = self.setups[a] I_a[a] -= ((setup.Nc - setup.Nct) / self.nspins + sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd
class Density: """Density object. Attributes: =============== ===================================================== ``gd`` Grid descriptor for coarse grids. ``finegd`` Grid descriptor for fine grids. ``interpolate`` Function for interpolating the electron density. ``mixer`` ``DensityMixer`` object. =============== ===================================================== Soft and smooth pseudo functions on uniform 3D grids: ========== ========================================= ``nt_sG`` Electron density on the coarse grid. ``nt_sg`` Electron density on the fine grid. ``nt_g`` Electron density on the fine grid. ``rhot_g`` Charge density on the fine grid. ``nct_G`` Core electron-density on the coarse grid. ========== ========================================= """ def __init__(self, gd, finegd, nspins, charge): """Create the Density object.""" self.gd = gd self.finegd = finegd self.nspins = nspins self.charge = float(charge) self.charge_eps = 1e-7 self.D_asp = None self.Q_aL = None self.nct_G = None self.nt_sG = None self.rhot_g = None self.nt_sg = None self.nt_g = None self.rank_a = None self.mixer = BaseMixer() self.timer = nulltimer self.allocated = False def initialize(self, setups, stencil, timer, magmom_a, hund): self.timer = timer self.setups = setups self.hund = hund self.magmom_a = magmom_a # Interpolation function for the density: self.interpolator = Transformer(self.gd, self.finegd, stencil, allocate=False) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) if self.allocated: self.allocated = False self.allocate() def allocate(self): assert not self.allocated self.interpolator.allocate() self.allocated = True def reset(self): # TODO: reset other parameters? self.nt_sG = None def set_positions(self, spos_ac, rank_a=None): if not self.allocated: self.allocate() self.nct.set_positions(spos_ac) self.ghat.set_positions(spos_ac) self.mixer.reset() self.nct_G = self.gd.zeros() self.nct.add(self.nct_G, 1.0 / self.nspins) #self.nt_sG = None self.nt_sg = None self.nt_g = None self.rhot_g = None self.Q_aL = None # If both old and new atomic ranks are present, start a blank dict if # it previously didn't exist but it will needed for the new atoms. if (self.rank_a is not None and rank_a is not None and self.D_asp is None and (rank_a == self.gd.comm.rank).any()): self.D_asp = {} if self.rank_a is not None and self.D_asp is not None: self.timer.start('Redistribute') requests = [] flags = (self.rank_a != rank_a) my_incoming_atom_indices = np.argwhere(np.bitwise_and(flags, \ rank_a == self.gd.comm.rank)).ravel() my_outgoing_atom_indices = np.argwhere(np.bitwise_and(flags, \ self.rank_a == self.gd.comm.rank)).ravel() for a in my_incoming_atom_indices: # Get matrix from old domain: ni = self.setups[a].ni D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) requests.append(self.gd.comm.receive(D_sp, self.rank_a[a], tag=a, block=False)) assert a not in self.D_asp self.D_asp[a] = D_sp for a in my_outgoing_atom_indices: # Send matrix to new domain: D_sp = self.D_asp.pop(a) requests.append(self.gd.comm.send(D_sp, rank_a[a], tag=a, block=False)) self.gd.comm.waitall(requests) self.timer.stop('Redistribute') self.rank_a = rank_a def calculate_pseudo_density(self, wfs): """Calculate nt_sG from scratch. nt_sG will be equal to nct_G plus the contribution from wfs.add_to_density(). """ wfs.calculate_density_contribution(self.nt_sG) self.nt_sG += self.nct_G def update(self, wfs): self.timer.start('Density') self.timer.start('Pseudo density') self.calculate_pseudo_density(wfs) self.timer.stop('Pseudo density') self.timer.start('Atomic density matrices') wfs.calculate_atomic_density_matrices(self.D_asp) self.timer.stop('Atomic density matrices') self.timer.start('Multipole moments') comp_charge = self.calculate_multipole_moments() self.timer.stop('Multipole moments') if isinstance(wfs, LCAOWaveFunctions): self.timer.start('Normalize') self.normalize(comp_charge) self.timer.stop('Normalize') self.timer.start('Mix') self.mix(comp_charge) self.timer.stop('Mix') self.timer.stop('Density') def normalize(self, comp_charge=None): """Normalize pseudo density.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() pseudo_charge = self.gd.integrate(self.nt_sG).sum() if pseudo_charge + self.charge + comp_charge != 0: if pseudo_charge != 0: x = -(self.charge + comp_charge) / pseudo_charge self.nt_sG *= x else: # Use homogeneous background: self.nt_sG[:] = (self.charge + comp_charge) * self.gd.dv def calculate_pseudo_charge(self, comp_charge): self.nt_g = self.nt_sg.sum(axis=0) self.rhot_g = self.nt_g.copy() self.ghat.add(self.rhot_g, self.Q_aL) if debug: charge = self.finegd.integrate(self.rhot_g) + self.charge if abs(charge) > self.charge_eps: raise RuntimeError('Charge not conserved: excess=%.9f' % charge) def mix(self, comp_charge): if not self.mixer.mix_rho: self.mixer.mix(self) comp_charge = None self.interpolate(comp_charge) self.calculate_pseudo_charge(comp_charge) if self.mixer.mix_rho: self.mixer.mix(self) def interpolate(self, comp_charge=None): """Interpolate pseudo density to fine grid.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() if self.nt_sg is None: self.nt_sg = self.finegd.empty(self.nspins) for s in range(self.nspins): self.interpolator.apply(self.nt_sG[s], self.nt_sg[s]) # With periodic boundary conditions, the interpolation will # conserve the number of electrons. if not self.gd.pbc_c.all(): # With zero-boundary conditions in one or more directions, # this is not the case. pseudo_charge = -(self.charge + comp_charge) if abs(pseudo_charge) > 1.0e-14: x = pseudo_charge / self.finegd.integrate(self.nt_sg).sum() self.nt_sg *= x def calculate_multipole_moments(self): """Calculate multipole moments of compensation charges. Returns the total compensation charge in units of electron charge, so the number will be negative because of the dominating contribution from the nuclear charge.""" comp_charge = 0.0 self.Q_aL = {} for a, D_sp in self.D_asp.items(): Q_L = self.Q_aL[a] = np.dot(D_sp.sum(0), self.setups[a].Delta_pL) Q_L[0] += self.setups[a].Delta0 comp_charge += Q_L[0] return self.gd.comm.sum(comp_charge) * sqrt(4 * pi) def initialize_from_atomic_densities(self, basis_functions): """Initialize D_asp, nt_sG and Q_aL from atomic densities. nt_sG is initialized from atomic orbitals, and will be constructed with the specified magnetic moments and obeying Hund's rules if ``hund`` is true.""" # XXX does this work with blacs? What should be distributed? # Apparently this doesn't use blacs at all, so it's serial # with respect to the blacs distribution. That means it works # but is not particularly efficient (not that this is a time # consuming step) f_sM = np.empty((self.nspins, basis_functions.Mmax)) self.D_asp = {} f_asi = {} for a in basis_functions.atom_indices: c = self.charge / len(self.setups) # distribute on all atoms f_si = self.setups[a].calculate_initial_occupation_numbers( self.magmom_a[a], self.hund, charge=c, nspins=self.nspins) if a in basis_functions.my_atom_indices: self.D_asp[a] = self.setups[a].initialize_density_matrix(f_si) f_asi[a] = f_si self.nt_sG = self.gd.zeros(self.nspins) basis_functions.add_to_density(self.nt_sG, f_asi) self.nt_sG += self.nct_G self.calculate_normalized_charges_and_mix() def initialize_from_wavefunctions(self, wfs): """Initialize D_asp, nt_sG and Q_aL from wave functions.""" self.nt_sG = self.gd.empty(self.nspins) self.calculate_pseudo_density(wfs) self.D_asp = {} my_atom_indices = np.argwhere(wfs.rank_a == self.gd.comm.rank).ravel() for a in my_atom_indices: ni = self.setups[a].ni self.D_asp[a] = np.empty((self.nspins, ni * (ni + 1) // 2)) wfs.calculate_atomic_density_matrices(self.D_asp) self.calculate_normalized_charges_and_mix() def initialize_directly_from_arrays(self, nt_sG, D_asp): """Set D_asp and nt_sG directly.""" self.nt_sG = nt_sG self.D_asp = D_asp #self.calculate_normalized_charges_and_mix() # No calculate multipole moments? Tests will fail because of # improperly initialized mixer def calculate_normalized_charges_and_mix(self): comp_charge = self.calculate_multipole_moments() self.normalize(comp_charge) self.mix(comp_charge) def set_mixer(self, mixer): if mixer is not None: if self.nspins == 1 and isinstance(mixer, MixerSum): raise RuntimeError('Cannot use MixerSum with nspins==1') self.mixer = mixer else: if self.gd.pbc_c.any(): beta = 0.1 weight = 50.0 else: beta = 0.25 weight = 1.0 if self.nspins == 2: self.mixer = MixerSum(beta=beta, weight=weight) else: self.mixer = Mixer(beta=beta, weight=weight) self.mixer.initialize(self) def estimate_magnetic_moments(self): magmom_a = np.zeros_like(self.magmom_a) if self.nspins == 2: for a, D_sp in self.D_asp.items(): magmom_a[a] = np.dot(D_sp[0] - D_sp[1], self.setups[a].N0_p) self.gd.comm.sum(magmom_a) return magmom_a def get_correction(self, a, spin): """Integrated atomic density correction. Get the integrated correction to the pseuso density relative to the all-electron density. """ setup = self.setups[a] return sqrt(4 * pi) * ( np.dot(self.D_asp[a][spin], setup.Delta_pL[:, 0]) + setup.Delta0 / self.nspins) def get_density_array(self): XXX # XXX why not replace with get_spin_density and get_total_density? """Return pseudo-density array.""" if self.nspins == 2: return self.nt_sG else: return self.nt_sG[0] def get_all_electron_density(self, atoms, gridrefinement=2): """Return real all-electron density array.""" # Refinement of coarse grid, for representation of the AE-density if gridrefinement == 1: gd = self.gd n_sg = self.nt_sG.copy() elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = LFC(gd, phi_aj) phit = LFC(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) spos_ac = atoms.get_scaled_positions() % 1.0 phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) all_D_asp = [] for a, setup in enumerate(self.setups): D_sp = self.D_asp.get(a) if D_sp is None: ni = setup.ni D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) if gd.comm.size > 1: gd.comm.broadcast(D_sp, self.rank_a[a]) all_D_asp.append(D_sp) for s in range(self.nspins): I_a = np.zeros(len(atoms)) nc.add1(n_sg[s], 1.0 / self.nspins, I_a) nct.add1(n_sg[s], -1.0 / self.nspins, I_a) phi.add2(n_sg[s], all_D_asp, s, 1.0, I_a) phit.add2(n_sg[s], all_D_asp, s, -1.0, I_a) for a, D_sp in self.D_asp.items(): setup = self.setups[a] I_a[a] -= ((setup.Nc - setup.Nct) / self.nspins + sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd def new_get_all_electron_density(self, atoms, gridrefinement=2): """Return real all-electron density array.""" # Refinement of coarse grid, for representation of the AE-density if gridrefinement == 1: gd = self.gd n_sg = self.nt_sG.copy() elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = BasisFunctions(gd, phi_aj) phit = BasisFunctions(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) spos_ac = atoms.get_scaled_positions() % 1.0 phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) I_sa = np.zeros((self.nspins, len(atoms))) a_W = np.empty(len(phi.M_W), np.int32) W = 0 for a in phi.atom_indices: nw = len(phi.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw rho_MM = np.zeros((phi.Mmax, phi.Mmax)) for s, I_a in enumerate(I_sa): M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni D_sp = self.D_asp.get(a) if D_sp is None: D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) else: I_a[a] = ((setup.Nct - setup.Nc) / self.nspins - sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) if gd.comm.size > 1: gd.comm.broadcast(D_sp, self.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = unpack2(D_sp[s]) M1 = M2 phi.lfc.ae_valence_density_correction(rho_MM, n_sg[s], a_W, I_a) phit.lfc.ae_valence_density_correction(-rho_MM, n_sg[s], a_W, I_a) a_W = np.empty(len(nc.M_W), np.int32) W = 0 for a in nc.atom_indices: nw = len(nc.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw scale = 1.0 / self.nspins for s, I_a in enumerate(I_sa): nc.lfc.ae_core_density_correction(scale, n_sg[s], a_W, I_a) nct.lfc.ae_core_density_correction(-scale, n_sg[s], a_W, I_a) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd if extra_parameters.get('usenewlfc', True): get_all_electron_density = new_get_all_electron_density def estimate_memory(self, mem): nspins = self.nspins nbytes = self.gd.bytecount() nfinebytes = self.finegd.bytecount() arrays = mem.subnode('Arrays') for name, size in [('nt_sG', nbytes * nspins), ('nt_sg', nfinebytes * nspins), ('nt_g', nfinebytes), ('rhot_g', nfinebytes), ('nct_G', nbytes)]: arrays.subnode(name, size) lfs = mem.subnode('Localized functions') for name, obj in [('nct', self.nct), ('ghat', self.ghat)]: obj.estimate_memory(lfs.subnode(name)) self.mixer.estimate_memory(mem.subnode('Mixer'), self.gd) # TODO # The implementation of interpolator memory use is not very # accurate; 20 MiB vs 13 MiB estimated in one example, probably # worse for parallel calculations. self.interpolator.estimate_memory(mem.subnode('Interpolator')) def get_spin_contamination(self, atoms, majority_spin=0): """Calculate the spin contamination. Spin contamination is defined as the integral over the spin density difference, where it is negative (i.e. the minority spin density is larger than the majority spin density. """ if majority_spin == 0: smaj = 0 smin = 1 else: smaj = 1 smin = 0 nt_sg, gd = self.get_all_electron_density(atoms) dt_sg = nt_sg[smin] - nt_sg[smaj] dt_sg = np.where(dt_sg > 0, dt_sg, 0.0) return gd.integrate(dt_sg)
def __init__(self, calc, wfs, poisson_solver=None, dtype=float, **kwargs): """Store calculator etc. Parameters ---------- calc: Calculator Calculator instance containing a ground-state calculation (calc.set_positions must have been called before this point!). wfs: WaveFunctions Class taking care of wave-functions, projectors, k-point related quantities and symmetries. poisson_solver: PoissonSolver Multigrid or FFT poisson solver (not required if the ``Perturbation`` to be solved for has a ``solve_poisson`` member function). dtype: ... dtype of the density response. """ # Store ground-state quantities self.hamiltonian = calc.hamiltonian self.density = calc.density self.wfs = wfs # Get list of k-point containers self.kpt_u = wfs.kpt_u # Poisson solver if poisson_solver is None: # Solver must be provided by the perturbation self.poisson = None self.solve_poisson = None else: self.poisson = poisson_solver self.solve_poisson = self.poisson.solve_neutral # Store grid-descriptors self.gd = calc.density.gd self.finegd = calc.density.finegd # dtype for ground-state wave-functions self.gs_dtype = calc.wfs.dtype # dtype for the perturbing potential and density self.dtype = dtype # Grid transformer -- convert array from coarse to fine grid self.interpolator = Transformer(self.gd, self.finegd, nn=3, dtype=self.dtype, allocate=False) # Grid transformer -- convert array from fine to coarse grid self.restrictor = Transformer(self.finegd, self.gd, nn=3, dtype=self.dtype, allocate=False) # Sternheimer operator self.sternheimer_operator = None # Krylov solver self.linear_solver = None # Phases for transformer objects - since the perturbation determines # the form of the density response this is obtained from the # perturbation in the ``__call__`` member function below. self.phase_cd = None # Array attributes self.nt1_G = None self.vHXC1_G = None self.nt1_g = None self.vH1_g = None # Perturbation self.perturbation = None # Number of occupied bands nvalence = calc.wfs.nvalence self.nbands = nvalence/2 + nvalence%2 assert self.nbands <= calc.wfs.nbands self.initialized = False self.parameters = {} self.set(**kwargs)
class ResponseCalculator: """This class is a calculator for the sc density variation. From the given perturbation, the set of coupled equations for the first-order density response is solved self-consistently. Parameters ---------- max_iter: int Maximum number of iterations in the self-consistent evaluation of the density variation. tolerance_sc: float Tolerance for the self-consistent loop measured in terms of integrated absolute change of the density derivative between two iterations. tolerance_sternheimer: float Tolerance for the solution of the Sternheimer equation -- passed to the ``LinearSolver``. beta: float (0 < beta < 1) Mixing coefficient. nmaxold: int Length of history for the mixer. weight: int Weight for the mixer metric (=1 -> no metric used). """ parameters = {'verbose': False, 'max_iter': 100, 'max_iter_krylov': 1000, 'krylov_solver': 'cg', 'tolerance_sc': 1.0e-5, 'tolerance_sternheimer': 1.0e-4, 'use_pc': True, 'beta': 0.1, 'nmaxold': 6, 'weight': 50 } def __init__(self, calc, wfs, poisson_solver=None, dtype=float, **kwargs): """Store calculator etc. Parameters ---------- calc: Calculator Calculator instance containing a ground-state calculation (calc.set_positions must have been called before this point!). wfs: WaveFunctions Class taking care of wave-functions, projectors, k-point related quantities and symmetries. poisson_solver: PoissonSolver Multigrid or FFT poisson solver (not required if the ``Perturbation`` to be solved for has a ``solve_poisson`` member function). dtype: ... dtype of the density response. """ # Store ground-state quantities self.hamiltonian = calc.hamiltonian self.density = calc.density self.wfs = wfs # Get list of k-point containers self.kpt_u = wfs.kpt_u # Poisson solver if poisson_solver is None: # Solver must be provided by the perturbation self.poisson = None self.solve_poisson = None else: self.poisson = poisson_solver self.solve_poisson = self.poisson.solve_neutral # Store grid-descriptors self.gd = calc.density.gd self.finegd = calc.density.finegd # dtype for ground-state wave-functions self.gs_dtype = calc.wfs.dtype # dtype for the perturbing potential and density self.dtype = dtype # Grid transformer -- convert array from coarse to fine grid self.interpolator = Transformer(self.gd, self.finegd, nn=3, dtype=self.dtype, allocate=False) # Grid transformer -- convert array from fine to coarse grid self.restrictor = Transformer(self.finegd, self.gd, nn=3, dtype=self.dtype, allocate=False) # Sternheimer operator self.sternheimer_operator = None # Krylov solver self.linear_solver = None # Phases for transformer objects - since the perturbation determines # the form of the density response this is obtained from the # perturbation in the ``__call__`` member function below. self.phase_cd = None # Array attributes self.nt1_G = None self.vHXC1_G = None self.nt1_g = None self.vH1_g = None # Perturbation self.perturbation = None # Number of occupied bands nvalence = calc.wfs.nvalence self.nbands = nvalence/2 + nvalence%2 assert self.nbands <= calc.wfs.nbands self.initialized = False self.parameters = {} self.set(**kwargs) def clean(self): """Cleanup before call to ``__call__``.""" self.perturbation = None self.solve_poisson = None self.nt1_G = None self.vHXC1_G = None self.nt1_g = None self.vH1_g = None def __call__(self, perturbation): """Calculate density response (derivative) to perturbation. Parameters ---------- perturbation: Perturbation Class implementing the perturbing potential. Must provide an ``apply`` member function implementing the multiplication of the perturbing potential to a (set of) state vector(s). """ assert self.initialized, ("Linear response calculator " "not initizalized.") self.clean() if self.poisson is None: assert hasattr(perturbation, 'solve_poisson') self.solve_poisson = perturbation.solve_poisson # Store perturbation - used in other member functions self.perturbation = perturbation # Reset mixer self.mixer.reset() # Reset wave-functions self.wfs.reset() # Set phase attribute for Transformer objects self.phase_cd = self.perturbation.get_phase_cd() # Parameters for the SC-loop p = self.parameters max_iter = p['max_iter'] tolerance = p['tolerance_sc'] for iter in range(max_iter): if iter == 0: self.first_iteration() else: print "iter:%3i\t" % iter, norm = self.iteration() print "abs-norm: %6.3e\t" % norm, print ("integrated density response (abs): % 5.2e (%5.2e) " % (self.gd.integrate(self.nt1_G.real), self.gd.integrate(np.absolute(self.nt1_G)))) if norm < tolerance: print ("self-consistent loop converged in %i iterations" % iter) break if iter == max_iter: raise RuntimeError, ("self-consistent loop did not converge " "in %i iterations" % iter) def set(self, **kwargs): """Set parameters for calculation.""" # Check for legal input parameters for key, value in kwargs.items(): if not key in ResponseCalculator.parameters: raise TypeError("Unknown keyword argument: '%s'" % key) # Insert default values if not given for key, value in ResponseCalculator.parameters.items(): if key not in kwargs: kwargs[key] = value self.parameters.update(kwargs) def initialize(self, spos_ac): """Make the object ready for a calculation.""" # Parameters p = self.parameters beta = p['beta'] nmaxold = p['nmaxold'] weight = p['weight'] use_pc = p['use_pc'] tolerance_sternheimer = p['tolerance_sternheimer'] max_iter_krylov = p['max_iter_krylov'] krylov_solver = p['krylov_solver'] # Initialize WaveFunctions attribute self.wfs.initialize(spos_ac) # Initialize interpolator and restrictor self.interpolator.allocate() self.restrictor.allocate() # Initialize mixer # weight = 1 -> no metric is used self.mixer = BaseMixer(beta=beta, nmaxold=nmaxold, weight=weight, dtype=self.dtype) self.mixer.initialize_metric(self.gd) # Linear operator in the Sternheimer equation self.sternheimer_operator = \ SternheimerOperator(self.hamiltonian, self.wfs, self.gd, dtype=self.gs_dtype) # Preconditioner for the Sternheimer equation if p['use_pc']: pc = ScipyPreconditioner(self.gd, self.sternheimer_operator.project, dtype=self.gs_dtype) else: pc = None #XXX K-point of the pc must be set in the k-point loop -> store a ref. self.pc = pc # Linear solver for the solution of Sternheimer equation self.linear_solver = ScipyLinearSolver(method=krylov_solver, preconditioner=pc, tolerance=tolerance_sternheimer, max_iter=max_iter_krylov) self.initialized = True def first_iteration(self): """Perform first iteration of sc-loop.""" self.wave_function_variations() self.density_response() self.mixer.mix(self.nt1_G, [], phase_cd=self.phase_cd) self.interpolate_density() def iteration(self): """Perform iteration.""" # Update variation in the effective potential self.effective_potential_variation() # Update wave function variations self.wave_function_variations() # Update density self.density_response() # Mix - supply phase_cd here for metric inside the mixer self.mixer.mix(self.nt1_G, [], phase_cd=self.phase_cd) norm = self.mixer.get_charge_sloshing() self.interpolate_density() return norm def interpolate_density(self): """Interpolate density derivative onto the fine grid.""" self.nt1_g = self.finegd.zeros(dtype=self.dtype) self.interpolator.apply(self.nt1_G, self.nt1_g, phases=self.phase_cd) def effective_potential_variation(self): """Calculate derivative of the effective potential (Hartree + XC).""" # Hartree part vHXC1_g = self.finegd.zeros(dtype=self.dtype) self.solve_poisson(vHXC1_g, self.nt1_g) # Store for evaluation of second order derivative self.vH1_g = vHXC1_g.copy() # XC part nt_sg = self.density.nt_sg fxct_sg = np.zeros_like(nt_sg) self.hamiltonian.xc.calculate_fxc(self.finegd, nt_sg, fxct_sg) vHXC1_g += fxct_sg[0] * self.nt1_g # Transfer to coarse grid self.vHXC1_G = self.gd.zeros(dtype=self.dtype) self.restrictor.apply(vHXC1_g, self.vHXC1_G, phases=self.phase_cd) def wave_function_variations(self): """Calculate variation in the wave-functions. Parameters ---------- v1_G: ndarray Variation of the local effective potential (Hartree + XC). """ verbose = self.parameters['verbose'] if verbose: print "Calculating wave function variations" if self.perturbation.has_q(): q_c = self.perturbation.get_q() kplusq_k = self.wfs.kd.find_k_plus_q(q_c) else: kplusq_k = None # Calculate wave-function variations for all k-points. for kpt in self.kpt_u: k = kpt.k if verbose: print "k-point %2.1i" % k # Index of k+q vector if kplusq_k is None: kplusq = k kplusqpt = kpt else: kplusq = kplusq_k[k] kplusqpt = self.kpt_u[kplusq] # Ground-state and first-order wave-functions psit_nG = kpt.psit_nG psit1_nG = kpt.psit1_nG # Update the SternheimerOperator self.sternheimer_operator.set_k(k) self.sternheimer_operator.set_kplusq(kplusq) # Update preconditioner if self.pc is not None: # k+q self.pc.set_kpt(kplusqpt) # Right-hand side of Sternheimer equations # k and k+q # XXX should only be done once for all k-points but maybe too cheap # to bother ?? rhs_nG = self.gd.zeros(n=self.nbands, dtype=self.gs_dtype) self.perturbation.apply(psit_nG, rhs_nG, self.wfs, k, kplusq) if self.vHXC1_G is not None: rhs_nG += self.vHXC1_G * psit_nG # Project out occupied subspace self.sternheimer_operator.project(rhs_nG) # Loop over occupied bands for n in range(self.nbands): # Update band index in SternheimerOperator self.sternheimer_operator.set_band(n) # Get view of the Bloch function derivative psit1_G = psit1_nG[n] # Rhs of Sternheimer equation rhs_G = -1 * rhs_nG[n] # Solve Sternheimer equation iter, info = self.linear_solver.solve(self.sternheimer_operator, psit1_G, rhs_G) if verbose: print "\tBand %2.1i -" % n, if info == 0: if verbose: print "linear solver converged in %i iterations" % iter elif info > 0: assert False, ("linear solver did not converge in maximum " "number (=%i) of iterations for " "k-point number %d" % (iter, k)) else: assert False, ("linear solver failed to converge") def density_response(self): """Calculate density response from variation in the wave-functions.""" # Density might be complex self.nt1_G = self.gd.zeros(dtype=self.dtype) for kpt in self.kpt_u: # The weight of the k-points includes spin-degeneracy w = kpt.weight # Wave functions psit_nG = kpt.psit_nG psit1_nG = kpt.psit1_nG for psit_G, psit1_G in zip(psit_nG, psit1_nG): # NOTICE: this relies on the automatic down-cast of the complex # array on the rhs to a real array when the lhs is real !! # Factor 2 for time-reversal symmetry self.nt1_G += 2 * w * psit_G.conj() * psit1_G
class RealSpaceDensity(Density): def __init__(self, gd, finegd, nspins, charge, collinear=True, stencil=3): Density.__init__(self, gd, finegd, nspins, charge, collinear) self.stencil = stencil def initialize(self, setups, timer, magmom_av, hund): Density.initialize(self, setups, timer, magmom_av, hund) # Interpolation function for the density: self.interpolator = Transformer(self.gd, self.finegd, self.stencil) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) def set_positions(self, spos_ac, rank_a=None): Density.set_positions(self, spos_ac, rank_a) self.nct_G = self.gd.zeros() self.nct.add(self.nct_G, 1.0 / self.nspins) def interpolate_pseudo_density(self, comp_charge=None): """Interpolate pseudo density to fine grid.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() self.nt_sg = self.interpolate(self.nt_sG, self.nt_sg) # With periodic boundary conditions, the interpolation will # conserve the number of electrons. if not self.gd.pbc_c.all(): # With zero-boundary conditions in one or more directions, # this is not the case. pseudo_charge = -(self.charge + comp_charge) if abs(pseudo_charge) > 1.0e-14: x = (pseudo_charge / self.finegd.integrate(self.nt_sg[:self.nspins]).sum()) self.nt_sg *= x def interpolate(self, in_xR, out_xR=None): """Interpolate array(s).""" # ndim will be 3 in finite-difference mode and 1 when working # with the AtomPAW class (spherical atoms and 1d grids) ndim = self.gd.ndim if out_xR is None: out_xR = self.finegd.empty(in_xR.shape[:-ndim]) a_xR = in_xR.reshape((-1,) + in_xR.shape[-ndim:]) b_xR = out_xR.reshape((-1,) + out_xR.shape[-ndim:]) for in_R, out_R in zip(a_xR, b_xR): self.interpolator.apply(in_R, out_R) return out_xR def calculate_pseudo_charge(self): self.nt_g = self.nt_sg[:self.nspins].sum(axis=0) self.rhot_g = self.nt_g.copy() self.ghat.add(self.rhot_g, self.Q_aL) if debug: charge = self.finegd.integrate(self.rhot_g) + self.charge if abs(charge) > self.charge_eps: raise RuntimeError('Charge not conserved: excess=%.9f' % charge) def get_pseudo_core_kinetic_energy_density_lfc(self): return LFC(self.gd, [[setup.tauct] for setup in self.setups], forces=True, cut=True) def calculate_dipole_moment(self): return self.finegd.calculate_dipole_moment(self.rhot_g)
def get_combined_data(self, qmdata=None, cldata=None, spacing=None, qmgd=None, clgd=None): if qmdata is None: qmdata = self.density.rhot_g if cldata is None: cldata = (self.classical_material.charge_density * self.classical_material.sign) if qmgd is None: qmgd = self.qm.gd if clgd is None: clgd = self.cl.gd if spacing is None: spacing = clgd.h_cv[0, 0] spacing_au = spacing / Bohr # from Angstroms to a.u. # Refine classical part clgd = clgd.new_descriptor() cl_g = cldata.copy() while clgd.h_cv[0, 0] > spacing_au * 1.50: # 45: clgd2 = clgd.refine() cl_g = Transformer(clgd, clgd2).apply(cl_g) clgd = clgd2 # Coarse quantum part qmgd = qmgd.new_descriptor() qm_g = qmdata.copy() while qmgd.h_cv[0, 0] < clgd.h_cv[0, 0] * 0.95: qmgd2 = qmgd.coarsen() qm_g = Transformer(qmgd, qmgd2).apply(qm_g) qmgd = qmgd2 assert np.all(np.absolute(qmgd.h_cv - clgd.h_cv) < 1e-12), \ " Spacings %.8f (qm) and %.8f (cl) Angstroms" \ % (qmgd.h_cv[0][0] * Bohr, clgd.h_cv[0][0] * Bohr) # Do distribution on master big_cl_g = clgd.collect(cl_g) big_qm_g = qmgd.collect(qm_g, broadcast=True) if clgd.comm.rank == 0: # now find the corners # r_gv_cl = \ # clgd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) cind = (self.qm.corner1 / np.diag(clgd.h_cv) - 1).astype(int) n = big_qm_g.shape # print 'Corner points: ', self.qm.corner1*Bohr, \ # ' - ', self.qm.corner2*Bohr # print 'Calculated points: ', r_gv_cl[tuple(cind)]*Bohr, \ # ' - ', r_gv_cl[tuple(cind+n+1)]*Bohr big_cl_g[cind[0] + 1:cind[0] + n[0] + 1, cind[1] + 1:cind[1] + n[1] + 1, cind[2] + 1:cind[2] + n[2] + 1] += big_qm_g clgd.distribute(big_cl_g, cl_g) return cl_g, clgd
def create_subsystems(self, atoms_in): # Create new Atoms object atoms_out = atoms_in.copy() # New quantum grid self.qm.cell = \ np.diag([(self.shift_indices_2[w] - self.shift_indices_1[w]) * self.cl.spacing[w] for w in range(3)]) self.qm.spacing = self.cl.spacing / self.hratios N_c = get_number_of_grid_points(self.qm.cell, self.qm.spacing) atoms_out.set_cell(np.diag(self.qm.cell) * Bohr) atoms_out.positions = atoms_in.get_positions() - self.qm.corner1 * Bohr msg = self.messages.append msg("Quantum box readjustment:") msg(" Given cell: [%10.5f %10.5f %10.5f]" % tuple(np.diag(atoms_in.get_cell()))) msg(" Given atomic coordinates:") for s, c in zip(atoms_in.get_chemical_symbols(), atoms_in.get_positions()): msg(" %s %10.5f %10.5f %10.5f" % (s, c[0], c[1], c[2])) msg(" Readjusted cell: [%10.5f %10.5f %10.5f]" % tuple(np.diag(atoms_out.get_cell()))) msg(" Readjusted atomic coordinates:") for s, c in zip(atoms_out.get_chemical_symbols(), atoms_out.get_positions()): msg(" %s %10.5f %10.5f %10.5f" % (s, c[0], c[1], c[2])) msg(" Given corner points: " + "(%10.5f %10.5f %10.5f) - (%10.5f %10.5f %10.5f)" % (tuple(np.concatenate((self.given_corner_v1, self.given_corner_v2))))) msg(" Readjusted corner points: " + "(%10.5f %10.5f %10.5f) - (%10.5f %10.5f %10.5f)" % (tuple(np.concatenate((self.qm.corner1, self.qm.corner2)) * Bohr))) msg(" Indices in classical grid: " + "(%10i %10i %10i) - (%10i %10i %10i)" % (tuple(np.concatenate((self.shift_indices_1, self.shift_indices_2))))) msg(" Grid points in classical grid: " + "(%10i %10i %10i)" % (tuple(self.cl.gd.N_c))) msg(" Grid points in quantum grid: " + "(%10i %10i %10i)" % (tuple(N_c))) msg(" Spacings in quantum grid: " + "(%10.5f %10.5f %10.5f)" % (tuple(np.diag(self.qm.cell) * Bohr / N_c))) msg(" Spacings in classical grid: " + "(%10.5f %10.5f %10.5f)" % (tuple(np.diag(self.cl.cell) * Bohr / get_number_of_grid_points(self.cl.cell, self.cl.spacing)))) # msg(" Ratios of cl/qm spacings: " + # "(%10i %10i %10i)" % (tuple(self.hratios))) # msg(" = (%10.2f %10.2f %10.2f)" % # (tuple((np.diag(self.cl.cell) * Bohr / \ # get_number_of_grid_points(self.cl.cell, # self.cl.spacing)) / \ # (np.diag(self.qm.cell) * Bohr / N_c)))) msg(" Needed number of refinements: %10i" % self.num_refinements) # First, create the quantum grid equivalent GridDescriptor # self.cl.subgd. Then coarsen it until its h_cv equals # that of self.cl.gd. Finally, map the points between # clgd and coarsened subgrid. subcell_cv = np.diag(self.qm.corner2 - self.qm.corner1) N_c = get_number_of_grid_points(subcell_cv, self.cl.spacing) N_c = self.shift_indices_2 - self.shift_indices_1 self.cl.subgds = [] self.cl.subgds.append(GridDescriptor(N_c, subcell_cv, False, serial_comm, self.cl.dparsize)) # msg(" N_c/spacing of the subgrid: " + # "%3i %3i %3i / %.4f %.4f %.4f" % # (self.cl.subgds[0].N_c[0], # self.cl.subgds[0].N_c[1], # self.cl.subgds[0].N_c[2], # self.cl.subgds[0].h_cv[0][0] * Bohr, # self.cl.subgds[0].h_cv[1][1] * Bohr, # self.cl.subgds[0].h_cv[2][2] * Bohr)) # msg(" shape from the subgrid: " + # "%3i %3i %3i" % (tuple(self.cl.subgds[0].empty().shape))) self.cl.coarseners = [] self.cl.refiners = [] for n in range(self.num_refinements): self.cl.subgds.append(self.cl.subgds[n].refine()) self.cl.refiners.append(Transformer(self.cl.subgds[n], self.cl.subgds[n + 1])) # msg(" refiners[%i] can perform the transformation " + # "(%3i %3i %3i) -> (%3i %3i %3i)" % (\ # n, # self.cl.subgds[n].empty().shape[0], # self.cl.subgds[n].empty().shape[1], # self.cl.subgds[n].empty().shape[2], # self.cl.subgds[n + 1].empty().shape[0], # self.cl.subgds[n + 1].empty().shape[1], # self.cl.subgds[n + 1].empty().shape[2])) self.cl.coarseners.append(Transformer(self.cl.subgds[n + 1], self.cl.subgds[n])) self.cl.coarseners[:] = self.cl.coarseners[::-1] # Now extend the grid in order to handle # the zero boundary conditions that the refiner assumes # The default interpolation order self.extend_nn = \ Transformer(GridDescriptor([8, 8, 8], [1, 1, 1], False, serial_comm, None), GridDescriptor([8, 8, 8], [1, 1, 1], False, serial_comm, None).coarsen()).nn self.extended_num_indices = self.num_indices + [2, 2, 2] # Center, left, and right points of the suggested quantum grid extended_cp = 0.5 * (np.array(self.given_corner_v1 / Bohr) + np.array(self.given_corner_v2 / Bohr)) extended_lp = extended_cp - 0.5 * (self.extended_num_indices * self.cl.spacing) # extended_rp = extended_cp + 0.5 * (self.extended_num_indices * # self.cl.spacing) # Indices in the classical grid restricting the quantum grid self.extended_shift_indices_1 = \ np.round(extended_lp / self.cl.spacing).astype(int) self.extended_shift_indices_2 = \ self.extended_shift_indices_1 + self.extended_num_indices # msg(' extended_shift_indices_1: %i %i %i' # % (self.extended_shift_indices_1[0], # self.extended_shift_indices_1[1], # self.extended_shift_indices_1[2])) # msg(' extended_shift_indices_2: %i %i %i' # % (self.extended_shift_indices_2[0], # self.extended_shift_indices_2[1], # self.extended_shift_indices_2[2])) # msg(' cl.gd.N_c: %i %i %i' # % (self.cl.gd.N_c[0], self.cl.gd.N_c[1], self.cl.gd.N_c[2])) # Sanity checks assert(all([self.extended_shift_indices_1[w] >= 0 and self.extended_shift_indices_2[w] <= self.cl.gd.N_c[w] for w in range(3)])), \ "Could not find appropriate quantum grid. " + \ "Move it further away from the boundary." # Corner coordinates self.qm.extended_corner1 = \ self.extended_shift_indices_1 * self.cl.spacing self.qm.extended_corner2 = \ self.extended_shift_indices_2 * self.cl.spacing N_c = self.extended_shift_indices_2 - self.extended_shift_indices_1 self.cl.extended_subgds = [] self.cl.extended_refiners = [] extended_subcell_cv = np.diag(self.qm.extended_corner2 - self.qm.extended_corner1) self.cl.extended_subgds.append(GridDescriptor( N_c, extended_subcell_cv, False, serial_comm, None)) for n in range(self.num_refinements): self.cl.extended_subgds.append(self.cl.extended_subgds[n].refine()) self.cl.extended_refiners.append(Transformer( self.cl.extended_subgds[n], self.cl.extended_subgds[n + 1])) # msg(" extended_refiners[%i] can perform the transformation " + # "(%3i %3i %3i) -> (%3i %3i %3i)" # % (n, # self.cl.extended_subgds[n].empty().shape[0], # self.cl.extended_subgds[n].empty().shape[1], # self.cl.extended_subgds[n].empty().shape[2], # self.cl.extended_subgds[n + 1].empty().shape[0], # self.cl.extended_subgds[n + 1].empty().shape[1], # self.cl.extended_subgds[n + 1].empty().shape[2])) # msg(" N_c/spacing of the refined subgrid: " + # "%3i %3i %3i / %.4f %.4f %.4f" # % (self.cl.subgds[-1].N_c[0], # self.cl.subgds[-1].N_c[1], # self.cl.subgds[-1].N_c[2], # self.cl.subgds[-1].h_cv[0][0] * Bohr, # self.cl.subgds[-1].h_cv[1][1] * Bohr, # self.cl.subgds[-1].h_cv[2][2] * Bohr)) # msg(" shape from the refined subgrid: %3i %3i %3i" # % (tuple(self.cl.subgds[-1].empty().shape))) self.extended_deltaIndex = 2**(self.num_refinements) * self.extend_nn # msg(" self.extended_deltaIndex = %i" % self.extended_deltaIndex) qgpts = self.cl.subgds[-1].coarsen().N_c # Assure that one returns to the original shape dmygd = self.cl.subgds[-1].coarsen() for n in range(self.num_refinements - 1): dmygd = dmygd.coarsen() # msg(" N_c/spacing of the coarsened subgrid: " + # "%3i %3i %3i / %.4f %.4f %.4f" # % (dmygd.N_c[0], dmygd.N_c[1], dmygd.N_c[2], # dmygd.h_cv[0][0] * Bohr, # dmygd.h_cv[1][1] * Bohr, # dmygd.h_cv[2][2] * Bohr)) self.has_subsystems = True return atoms_out, self.qm.spacing[0] * Bohr, qgpts
class Hamiltonian: """Hamiltonian object. Attributes: =============== ===================================================== ``xc`` ``XC3DGrid`` object. ``poisson`` ``PoissonSolver``. ``gd`` Grid descriptor for coarse grids. ``finegd`` Grid descriptor for fine grids. ``restrict`` Function for restricting the effective potential. =============== ===================================================== Soft and smooth pseudo functions on uniform 3D grids: ========== ========================================= ``vHt_g`` Hartree potential on the fine grid. ``vt_sG`` Effective potential on the coarse grid. ``vt_sg`` Effective potential on the fine grid. ========== ========================================= Energy contributions and forces: =========== ========================================== Description =========== ========================================== ``Ekin`` Kinetic energy. ``Epot`` Potential energy. ``Etot`` Total energy. ``Exc`` Exchange-Correlation energy. ``Eext`` Energy of external potential ``Eref`` Reference energy for all-electron atoms. ``S`` Entropy. ``Ebar`` Should be close to zero! =========== ========================================== """ def __init__(self, gd, finegd, nspins, setups, stencil, timer, xc, psolver, vext_g): """Create the Hamiltonian.""" self.gd = gd self.finegd = finegd self.nspins = nspins self.setups = setups self.timer = timer self.xc = xc # Solver for the Poisson equation: if psolver is None: psolver = PoissonSolver(nn=3, relax='J') self.poisson = psolver self.poisson.set_grid_descriptor(finegd) self.dH_asp = None # The external potential self.vext_g = vext_g self.vt_sG = None self.vHt_g = None self.vt_sg = None self.vbar_g = None self.rank_a = None # Restrictor function for the potential: self.restrictor = Transformer(self.finegd, self.gd, stencil, allocate=False) self.restrict = self.restrictor.apply self.vbar = LFC(self.finegd, [[setup.vbar] for setup in setups], forces=True) self.Ekin0 = None self.Ekin = None self.Epot = None self.Ebar = None self.Eext = None self.Exc = None self.Etot = None self.S = None self.allocated = False def allocate(self): # TODO We should move most of the gd.empty() calls here assert not self.allocated self.restrictor.allocate() self.allocated = True def set_positions(self, spos_ac, rank_a=None): self.spos_ac = spos_ac if not self.allocated: self.allocate() self.vbar.set_positions(spos_ac) if self.vbar_g is None: self.vbar_g = self.finegd.empty() self.vbar_g[:] = 0.0 self.vbar.add(self.vbar_g) self.xc.set_positions(spos_ac) # If both old and new atomic ranks are present, start a blank dict if # it previously didn't exist but it will needed for the new atoms. if (self.rank_a is not None and rank_a is not None and self.dH_asp is None and (rank_a == self.gd.comm.rank).any()): self.dH_asp = {} if self.rank_a is not None and self.dH_asp is not None: self.timer.start('Redistribute') requests = [] flags = (self.rank_a != rank_a) my_incoming_atom_indices = np.argwhere(np.bitwise_and(flags, \ rank_a == self.gd.comm.rank)).ravel() my_outgoing_atom_indices = np.argwhere(np.bitwise_and(flags, \ self.rank_a == self.gd.comm.rank)).ravel() for a in my_incoming_atom_indices: # Get matrix from old domain: ni = self.setups[a].ni dH_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) requests.append(self.gd.comm.receive(dH_sp, self.rank_a[a], tag=a, block=False)) assert a not in self.dH_asp self.dH_asp[a] = dH_sp for a in my_outgoing_atom_indices: # Send matrix to new domain: dH_sp = self.dH_asp.pop(a) requests.append(self.gd.comm.send(dH_sp, rank_a[a], tag=a, block=False)) self.gd.comm.waitall(requests) self.timer.stop('Redistribute') self.rank_a = rank_a def aoom(self, DM, a, l, scale=1): """Atomic Orbital Occupation Matrix. Determine the Atomic Orbital Occupation Matrix (aoom) for a given l-quantum number. This operation, takes the density matrix (DM), which for example is given by unpack2(D_asq[i][spin]), and corrects for the overlap between the selected orbitals (l) upon which the the density is expanded (ex <p|p*>,<p|p>,<p*|p*> ). Returned is only the "corrected" part of the density matrix, which represents the orbital occupation matrix for l=2 this is a 5x5 matrix. """ S=self.setups[a] l_j = S.l_j n_j = S.n_j lq = S.lq nl = np.where(np.equal(l_j, l))[0] V = np.zeros(np.shape(DM)) if len(nl) == 2: aa = (nl[0])*len(l_j)-((nl[0]-1)*(nl[0])/2) bb = (nl[1])*len(l_j)-((nl[1]-1)*(nl[1])/2) ab = aa+nl[1]-nl[0] if(scale==0 or scale=='False' or scale =='false'): lq_a = lq[aa] lq_ab = lq[ab] lq_b = lq[bb] else: lq_a = 1 lq_ab = lq[ab]/lq[aa] lq_b = lq[bb]/lq[aa] # and the correct entrances in the DM nn = (2*np.array(l_j)+1)[0:nl[0]].sum() mm = (2*np.array(l_j)+1)[0:nl[1]].sum() # finally correct and add the four submatrices of NC_DM A = DM[nn:nn+2*l+1,nn:nn+2*l+1]*(lq_a) B = DM[nn:nn+2*l+1,mm:mm+2*l+1]*(lq_ab) C = DM[mm:mm+2*l+1,nn:nn+2*l+1]*(lq_ab) D = DM[mm:mm+2*l+1,mm:mm+2*l+1]*(lq_b) V[nn:nn+2*l+1,nn:nn+2*l+1]=+(lq_a) V[nn:nn+2*l+1,mm:mm+2*l+1]=+(lq_ab) V[mm:mm+2*l+1,nn:nn+2*l+1]=+(lq_ab) V[mm:mm+2*l+1,mm:mm+2*l+1]=+(lq_b) return A+B+C+D, V else: nn =(2*np.array(l_j)+1)[0:nl[0]].sum() A=DM[nn:nn+2*l+1,nn:nn+2*l+1]*lq[-1] V[nn:nn+2*l+1,nn:nn+2*l+1]=+lq[-1] return A,V def update(self, density): """Calculate effective potential. The XC-potential and the Hartree potential are evaluated on the fine grid, and the sum is then restricted to the coarse grid.""" self.timer.start('Hamiltonian') if self.vt_sg is None: self.timer.start('Initialize Hamiltonian') self.vt_sg = self.finegd.empty(self.nspins) self.vHt_g = self.finegd.zeros() self.vt_sG = self.gd.empty(self.nspins) self.poisson.initialize() self.timer.stop('Initialize Hamiltonian') self.timer.start('vbar') Ebar = self.finegd.integrate(self.vbar_g, density.nt_g, global_integral=False) vt_g = self.vt_sg[0] vt_g[:] = self.vbar_g self.timer.stop('vbar') Eext = 0.0 if self.vext_g is not None: vt_g += self.vext_g.get_potential(self.finegd) Eext = self.finegd.integrate(vt_g, density.nt_g, global_integral=False) - Ebar if self.nspins == 2: self.vt_sg[1] = vt_g self.timer.start('XC 3D grid') Exc = self.xc.calculate(self.finegd, density.nt_sg, self.vt_sg) Exc /= self.gd.comm.size self.timer.stop('XC 3D grid') self.timer.start('Poisson') # npoisson is the number of iterations: self.npoisson = self.poisson.solve(self.vHt_g, density.rhot_g, charge=-density.charge) self.timer.stop('Poisson') self.timer.start('Hartree integrate/restrict') Epot = 0.5 * self.finegd.integrate(self.vHt_g, density.rhot_g, global_integral=False) Ekin = 0.0 for vt_g, vt_G, nt_G in zip(self.vt_sg, self.vt_sG, density.nt_sG): vt_g += self.vHt_g self.restrict(vt_g, vt_G) Ekin -= self.gd.integrate(vt_G, nt_G - density.nct_G, global_integral=False) self.timer.stop('Hartree integrate/restrict') # Calculate atomic hamiltonians: self.timer.start('Atomic') W_aL = {} for a in density.D_asp: W_aL[a] = np.empty((self.setups[a].lmax + 1)**2) density.ghat.integrate(self.vHt_g, W_aL) self.dH_asp = {} for a, D_sp in density.D_asp.items(): W_L = W_aL[a] setup = self.setups[a] D_p = D_sp.sum(0) dH_p = (setup.K_p + setup.M_p + setup.MB_p + 2.0 * np.dot(setup.M_pp, D_p) + np.dot(setup.Delta_pL, W_L)) Ekin += np.dot(setup.K_p, D_p) + setup.Kc Ebar += setup.MB + np.dot(setup.MB_p, D_p) Epot += setup.M + np.dot(D_p, (setup.M_p + np.dot(setup.M_pp, D_p))) if self.vext_g is not None: vext = self.vext_g.get_taylor(spos_c=self.spos_ac[a, :]) # Tailor expansion to the zeroth order Eext += vext[0][0] * (sqrt(4 * pi) * density.Q_aL[a][0] + setup.Z) dH_p += vext[0][0] * sqrt(4 * pi) * setup.Delta_pL[:, 0] if len(vext) > 1: # Tailor expansion to the first order Eext += sqrt(4 * pi / 3) * np.dot(vext[1], density.Q_aL[a][1:4]) # there must be a better way XXXX Delta_p1 = np.array([setup.Delta_pL[:, 1], setup.Delta_pL[:, 2], setup.Delta_pL[:, 3]]) dH_p += sqrt(4 * pi / 3) * np.dot(vext[1], Delta_p1) self.dH_asp[a] = dH_sp = np.zeros_like(D_sp) self.timer.start('XC Correction') Exc += setup.xc_correction.calculate(self.xc, D_sp, dH_sp, a) self.timer.stop('XC Correction') if setup.HubU is not None: nspins = len(D_sp) l_j = setup.l_j l = setup.Hubl nl = np.where(np.equal(l_j,l))[0] nn = (2*np.array(l_j)+1)[0:nl[0]].sum() for D_p, H_p in zip(D_sp, self.dH_asp[a]): [N_mm,V] =self.aoom(unpack2(D_p),a,l) N_mm = N_mm / 2 * nspins Eorb = setup.HubU / 2. * (N_mm - np.dot(N_mm,N_mm)).trace() Vorb = setup.HubU * (0.5 * np.eye(2*l+1) - N_mm) Exc += Eorb if nspins == 1: # add contribution of other spin manyfold Exc += Eorb if len(nl)==2: mm = (2*np.array(l_j)+1)[0:nl[1]].sum() V[nn:nn+2*l+1,nn:nn+2*l+1] *= Vorb V[mm:mm+2*l+1,nn:nn+2*l+1] *= Vorb V[nn:nn+2*l+1,mm:mm+2*l+1] *= Vorb V[mm:mm+2*l+1,mm:mm+2*l+1] *= Vorb else: V[nn:nn+2*l+1,nn:nn+2*l+1] *= Vorb Htemp = unpack(H_p) Htemp += V H_p[:] = pack2(Htemp) dH_sp += dH_p Ekin -= (D_sp * dH_sp).sum() self.timer.stop('Atomic') # Make corrections due to non-local xc: #xcfunc = self.xc.xcfunc self.Enlxc = 0.0#XXXxcfunc.get_non_local_energy() Ekin += self.xc.get_kinetic_energy_correction() / self.gd.comm.size energies = np.array([Ekin, Epot, Ebar, Eext, Exc]) self.timer.start('Communicate energies') self.gd.comm.sum(energies) self.timer.stop('Communicate energies') (self.Ekin0, self.Epot, self.Ebar, self.Eext, self.Exc) = energies #self.Exc += self.Enlxc #self.Ekin0 += self.Enlkin self.timer.stop('Hamiltonian') def get_energy(self, occupations): self.Ekin = self.Ekin0 + occupations.e_band self.S = occupations.e_entropy # Total free energy: self.Etot = (self.Ekin + self.Epot + self.Eext + self.Ebar + self.Exc - self.S) return self.Etot def apply_local_potential(self, psit_nG, Htpsit_nG, s): """Apply the Hamiltonian operator to a set of vectors. XXX Parameter description is deprecated! Parameters: a_nG: ndarray Set of vectors to which the overlap operator is applied. b_nG: ndarray, output Resulting H times a_nG vectors. kpt: KPoint object k-point object defined in kpoint.py. calculate_projections: bool When True, the integrals of projector times vectors P_ni = <p_i | a_nG> are calculated. When False, existing P_uni are used local_part_only: bool When True, the non-local atomic parts of the Hamiltonian are not applied and calculate_projections is ignored. """ vt_G = self.vt_sG[s] if psit_nG.ndim == 3: Htpsit_nG += psit_nG * vt_G else: for psit_G, Htpsit_G in zip(psit_nG, Htpsit_nG): Htpsit_G += psit_G * vt_G def apply(self, a_xG, b_xG, wfs, kpt, calculate_P_ani=True): """Apply the Hamiltonian operator to a set of vectors. Parameters: a_nG: ndarray Set of vectors to which the overlap operator is applied. b_nG: ndarray, output Resulting S times a_nG vectors. wfs: WaveFunctions Wave-function object defined in wavefunctions.py kpt: KPoint object k-point object defined in kpoint.py. calculate_P_ani: bool When True, the integrals of projector times vectors P_ni = <p_i | a_nG> are calculated. When False, existing P_ani are used """ wfs.kin.apply(a_xG, b_xG, kpt.phase_cd) self.apply_local_potential(a_xG, b_xG, kpt.s) shape = a_xG.shape[:-3] P_axi = wfs.pt.dict(shape) if calculate_P_ani: #TODO calculate_P_ani=False is experimental wfs.pt.integrate(a_xG, P_axi, kpt.q) else: for a, P_ni in kpt.P_ani.items(): P_axi[a][:] = P_ni for a, P_xi in P_axi.items(): dH_ii = unpack(self.dH_asp[a][kpt.s]) P_axi[a] = np.dot(P_xi, dH_ii) wfs.pt.add(b_xG, P_axi, kpt.q) def get_xc_difference(self, xc, density): """Calculate non-selfconsistent XC-energy difference.""" if density.nt_sg is None: density.interpolate() nt_sg = density.nt_sg if hasattr(xc, 'hybrid'): xc.calculate_exx() Exc = xc.calculate(density.finegd, nt_sg) / self.gd.comm.size for a, D_sp in density.D_asp.items(): setup = self.setups[a] Exc += setup.xc_correction.calculate(xc, D_sp) Exc = self.gd.comm.sum(Exc) return Exc - self.Exc def get_vxc(self, density, wfs): """Calculate matrix elements of the xc-potential.""" dtype = wfs.dtype nbands = wfs.nbands nu = len(wfs.kpt_u) if density.nt_sg is None: density.interpolate() # Allocate space for result matrix Vxc_unn = np.empty((nu, nbands, nbands), dtype=dtype) # Get pseudo xc potential on the coarse grid Vxct_sG = self.gd.empty(self.nspins) Vxct_sg = self.finegd.zeros(self.nspins) if nspins == 1: self.xc.get_energy_and_potential(density.nt_sg[0], Vxct_sg[0]) else: self.xc.get_energy_and_potential(density.nt_sg[0], Vxct_sg[0], density.nt_sg[1], Vxct_sg[1]) for Vxct_G, Vxct_g in zip(Vxct_sG, Vxct_sg): self.restrict(Vxct_g, Vxct_G) del Vxct_sg # Get atomic corrections to the xc potential Vxc_asp = {} for a, D_sp in density.D_asp.items(): Vxc_asp[a] = np.zeros_like(D_sp) self.setups[a].xc_correction.calculate_energy_and_derivatives( D_sp, Vxc_asp[a]) # Project potential onto the eigenstates for kpt, Vxc_nn in xip(wfs.kpt_u, Vxc_unn): s, q = kpt.s, kpt.q psit_nG = kpt.psit_nG # Project pseudo part r2k(.5 * self.gd.dv, psit_nG, Vxct_sG[s] * psit_nG, 0.0, Vxc_nn) tri2full(Vxc_nn, 'L') self.gd.comm.sum(Vxc_nn) # Add atomic corrections # H_ij = \int dr phi_i(r) Ĥ phi_j^*(r) # P_ni = \int dr psi_n(r) pt_i^*(r) # Vxc_nm = \int dr phi_n(r) vxc(r) phi_m^*(r) # + sum_ij P_ni H_ij P_mj^* for a, P_ni in kpt.P_ani.items(): Vxc_ii = unpack(Vxc_asp[a][s]) Vxc_nn += np.dot(P_ni, np.inner(H_ii, P_ni).conj()) return Vxc_unn def estimate_memory(self, mem): nbytes = self.gd.bytecount() nfinebytes = self.finegd.bytecount() arrays = mem.subnode('Arrays', 0) arrays.subnode('vHt_g', nfinebytes) arrays.subnode('vt_sG', self.nspins * nbytes) arrays.subnode('vt_sg', self.nspins * nfinebytes) self.restrictor.estimate_memory(mem.subnode('Restrictor')) self.xc.estimate_memory(mem.subnode('XC')) self.poisson.estimate_memory(mem.subnode('Poisson')) self.vbar.estimate_memory(mem.subnode('vbar'))
def __init__(self, calc): #initialization self.calc = calc self.wfs = calc.wfs self.ham = calc.hamiltonian self.den = calc.density self.occ = calc.occupations #initialization plane and grid descriptors from GPAW calculation self.pd = calc.wfs.pd self.gd = calc.wfs.gd self.volume = np.abs(np.linalg.det(self.gd.cell_cv)) #number of k-points self.nq = len(calc.wfs.kpt_u) #number of bands self.nbands = calc.get_number_of_bands() #number of electrons self.nelectrons = calc.get_number_of_electrons() #kinetic operator self.kinetic = np.zeros((self.nq, self.nbands, self.nbands), dtype=complex) #overlap operator self.overlap = np.zeros((self.nq, self.nbands, self.nbands), dtype=complex) #local momentum operator self.local_moment = np.zeros((3, self.nq, self.nbands, self.nbands), dtype=complex) #nonlocal momentum operator self.nonlocal_moment = np.zeros((3, self.nq, self.nbands, self.nbands), dtype=complex) #Fermi-Dirac occupation self.f_n = np.zeros((self.nq, self.nbands), dtype=float) #ground state Kohn-Sham orbitals wavefunctions psi_gs = [] #ground state Kohn-Sham orbitals density den_gs = [] for kpt in self.wfs.kpt_u: self.overlap[kpt.q] = np.eye(self.nbands) self.f_n[kpt.q] = kpt.f_n kinetic = 0.5 * self.pd.G2_qG[kpt.q] # |G+q|^2/2 gradient = self.pd.get_reciprocal_vectors(kpt.q) psi = [] den = [] for n in range(self.nbands): psi.append(self.pd.ifft(kpt.psit_nG[n], kpt.q)) den.append(np.abs(psi[-1])**2) for m in range(self.nbands): self.kinetic[kpt.q, n, m] = self.pd.integrate( kpt.psit_nG[n], kinetic * kpt.psit_nG[m]) #calculation local momentum #<psi_qn|\nabla|psi_qm> for i in range(3): self.local_moment[i, kpt.q, n, m] = self.pd.integrate( kpt.psit_nG[n], gradient[:, i] * kpt.psit_nG[m]) psi_gs.append(psi) den_gs.append(den) self.psi_gs = np.array(psi_gs) self.den_gs = np.array(den_gs, dtype=float) #real space grid points self.r = self.gd.get_grid_point_coordinates() #initialization local and nonlocal part of pseudopotential self.init_potential() self.proj = np.zeros((self.nq, self.nbands, self.norb), dtype=complex) self.proj_r = np.zeros((3, self.nq, self.nbands, self.norb), dtype=complex) self.density = self.den.nt_sG.copy() #initialization charge density (ion+electrons) for Hartree potential self.ion_density = calc.hamiltonian.poisson.pd.ifft( calc.density.rhot_q) - calc.density.nt_sg[0] #plane wave descriptor for Hartree potential self.pd0 = self.ham.poisson.pd #reciprocal |G|^2 vectors for Hartree potential V(G)=4pi/|G|^2 self.G2 = self.ham.poisson.G2_q self.G = self.pd0.get_reciprocal_vectors() #fine to coarse and coarse to fine grids transformers (for correct calculation local potential) self.fine_to_coarse = Transformer(calc.density.finegd, calc.density.gd, 3) self.coarse_to_fine = Transformer(calc.density.gd, calc.density.finegd, 3) #initialization local potenital from ground state density self.update_local_potential() self.update_gauge([0, 0, 0])
class DensityFourierTransform(Observer): def __init__(self, timestep, frequencies, width=None, interval=1): """ Parameters ---------- timestep: float Time step in attoseconds (10^-18 s), e.g., 4.0 or 8.0 frequencies: NumPy array or list of floats Frequencies in eV for Fourier transforms width: float or None Width of Gaussian envelope in eV, otherwise no envelope interval: int Number of timesteps between calls (used when attaching) """ Observer.__init__(self, interval) self.timestep = interval * timestep * attosec_to_autime # autime self.omega_w = np.asarray(frequencies) * eV_to_aufrequency # autime^(-1) if width is None: self.sigma = None else: self.sigma = width * eV_to_aufrequency # autime^(-1) self.nw = len(self.omega_w) self.dtype = complex # np.complex128 really, but hey... self.Fnt_wsG = None self.Fnt_wsg = None self.Ant_sG = None self.Ant_sg = None def initialize(self, paw, allocate=True): self.allocated = False assert hasattr(paw, 'time') and hasattr(paw, 'niter'), 'Use TDDFT!' self.time = paw.time self.niter = paw.niter self.world = paw.wfs.world self.gd = paw.density.gd self.finegd = paw.density.finegd self.nspins = paw.density.nspins self.stencil = paw.input_parameters.stencils[1] # i.e. tar['InterpolationStencil'] self.interpolator = paw.density.interpolator self.cinterpolator = Transformer(self.gd, self.finegd, self.stencil, \ dtype=self.dtype, allocate=False) self.phase_cd = np.ones((3, 2), dtype=complex) self.Ant_sG = paw.density.nt_sG.copy() # TODO in allocate instead? # Attach to PAW-type object paw.attach(self, self.interval, density=paw.density) if allocate: self.allocate() def allocate(self): if not self.allocated: self.Fnt_wsG = self.gd.zeros((self.nw, self.nspins), \ dtype=self.dtype) self.Fnt_wsg = None #self.Ant_sG = ... self.Ant_sg = None self.gamma_w = np.ones(self.nw, dtype=complex) * self.timestep self.cinterpolator.allocate() self.allocated = True if debug: assert is_contiguous(self.Fnt_wsG, self.dtype) def interpolate_fourier_transform(self): if self.Fnt_wsg is None: self.Fnt_wsg = self.finegd.empty((self.nw, self.nspins), \ dtype=self.dtype) if self.dtype == float: intapply = self.interpolator.apply else: intapply = lambda Fnt_G, Fnt_g: self.cinterpolator.apply(Fnt_G, \ Fnt_g, self.phase_cd) for w in range(self.nw): for s in range(self.nspins): intapply(self.Fnt_wsG[w,s], self.Fnt_wsg[w,s]) def interpolate_average(self): if self.Ant_sg is None: self.Ant_sg = self.finegd.empty(self.nspins, dtype=float) for s in range(self.nspins): self.interpolator.apply(self.Ant_sG[s], self.Ant_sg[s]) def update(self, density): # Update time # t[N] = t[N-1] + dt[N-1] #TODO better time-convention? self.time += self.timestep # Complex exponential with/without finite-width envelope f_w = np.exp(1.0j*self.omega_w*self.time) if self.sigma is not None: f_w *= np.exp(-self.time**2*self.sigma**2/2.0) # Update Fourier transformed density components # Fnt_wG[N] = Fnt_wG[N-1] + 1/sqrt(pi) * (nt_G[N]-avg_nt_G[N-1]) \ # * (f[N]*t[N] - gamma[N-1]) * dt[N]/(t[N]+dt[N]) for w in range(self.nw): self.Fnt_wsG[w] += 1/np.pi**0.5 * (density.nt_sG - self.Ant_sG) \ * (f_w[w]*self.time - self.gamma_w[w]) * self.timestep \ / (self.time + self.timestep) # Update the cumulative phase factors # gamma[N] = gamma[N-1] + f[N]*dt[N] self.gamma_w += f_w * self.timestep # If dt[N] = dt for all N and sigma = 0, then this simplifies to: # gamma[N] = Sum_{n=0}^N exp(i*omega*n*dt) * dt # = (1 - exp(i*omega*(N+1)*dt)) / (1 - exp(i*omega*dt)) * dt # Update average density # Ant_G[N] = (t[N]*Ant_G[N-1] + nt_G[N]*dt[N])/(t[N]+dt[N]) self.Ant_sG = (self.time*self.Ant_sG + density.nt_sG*self.timestep) \ / (self.time + self.timestep) def get_fourier_transform(self, frequency=0, spin=0, gridrefinement=1): if gridrefinement == 1: return self.Fnt_wsG[frequency, spin] elif gridrefinement == 2: if self.Fnt_wsg is None: self.interpolate_fourier_transform() return self.Fnt_wsg[frequency, spin] else: raise NotImplementedError('Arbitrary refinement not implemented') def get_average(self, spin=0, gridrefinement=1): if gridrefinement == 1: return self.Ant_sG[spin] elif gridrefinement == 2: if self.Ant_sg is None: self.interpolate_average() return self.Ant_sg[spin] else: raise NotImplementedError('Arbitrary refinement not implemented') def read(self, filename, idiotproof=True): if idiotproof and not filename.endswith('.ftd'): raise IOError('Filename must end with `.ftd`.') tar = Reader(filename) # Test data type dtype = {'Float':float, 'Complex':complex}[tar['DataType']] if dtype != self.dtype: raise IOError('Data is an incompatible type.') # Test time time = tar['Time'] if idiotproof and abs(time-self.time) >= 1e-9: raise IOError('Timestamp is incompatible with calculator.') # Test timestep (non-critical) timestep = tar['TimeStep'] if abs(timestep - self.timestep) > 1e-12: print 'Warning: Time-step has been altered. (%lf -> %lf)' \ % (self.timestep, timestep) self.timestep = timestep # Test dimensions nw = tar.dimension('nw') nspins = tar.dimension('nspins') ng = (tar.dimension('ngptsx'), tar.dimension('ngptsy'), \ tar.dimension('ngptsz'),) if (nw != self.nw or nspins != self.nspins or (ng != self.gd.get_size_of_global_array()).any()): raise IOError('Data has incompatible shapes.') # Test width (non-critical) sigma = tar['Width'] if ((sigma is None)!=(self.sigma is None) or # float <-> None (sigma is not None and self.sigma is not None and \ abs(sigma - self.sigma) > 1e-12)): # float -> float print 'Warning: Width has been altered. (%s -> %s)' \ % (self.sigma, sigma) self.sigma = sigma # Read frequencies self.omega_w[:] = tar.get('Frequency') # Read cumulative phase factors self.gamma_w[:] = tar.get('PhaseFactor') # Read average densities on master and distribute for s in range(self.nspins): all_Ant_G = tar.get('Average', s) self.gd.distribute(all_Ant_G, self.Ant_sG[s]) # Read fourier transforms on master and distribute for w in range(self.nw): for s in range(self.nspins): all_Fnt_G = tar.get('FourierTransform', w, s) self.gd.distribute(all_Fnt_G, self.Fnt_wsG[w,s]) # Close for good measure tar.close() def write(self, filename, idiotproof=True): if idiotproof and not filename.endswith('.ftd'): raise IOError('Filename must end with `.ftd`.') master = self.world.rank == 0 # Open writer on master and set parameters/dimensions if master: tar = Writer(filename) tar['DataType'] = {float:'Float', complex:'Complex'}[self.dtype] tar['Time'] = self.time tar['TimeStep'] = self.timestep #non-essential tar['Width'] = self.sigma tar.dimension('nw', self.nw) tar.dimension('nspins', self.nspins) # Create dimensions for varioius netCDF variables: ng = self.gd.get_size_of_global_array() tar.dimension('ngptsx', ng[0]) tar.dimension('ngptsy', ng[1]) tar.dimension('ngptsz', ng[2]) # Write frequencies tar.add('Frequency', ('nw',), self.omega_w, dtype=float) # Write cumulative phase factors tar.add('PhaseFactor', ('nw',), self.gamma_w, dtype=self.dtype) # Collect average densities on master and write if master: tar.add('Average', ('nspins', 'ngptsx', 'ngptsy', 'ngptsz', ), dtype=float) for s in range(self.nspins): big_Ant_G = self.gd.collect(self.Ant_sG[s]) if master: tar.fill(big_Ant_G) # Collect fourier transforms on master and write if master: tar.add('FourierTransform', ('nw', 'nspins', 'ngptsx', 'ngptsy', \ 'ngptsz', ), dtype=self.dtype) for w in range(self.nw): for s in range(self.nspins): big_Fnt_G = self.gd.collect(self.Fnt_wsG[w,s]) if master: tar.fill(big_Fnt_G) # Close to flush changes if master: tar.close() # Make sure slaves don't return before master is done self.world.barrier() def dump(self, filename): if debug: assert is_contiguous(self.Fnt_wsG, self.dtype) assert is_contiguous(self.Ant_sG, float) all_Fnt_wsG = self.gd.collect(self.Fnt_wsG) all_Ant_sG = self.gd.collect(self.Ant_sG) if self.world.rank == 0: all_Fnt_wsG.dump(filename) all_Ant_sG.dump(filename+'_avg') # crude but easy self.omega_w.dump(filename+'_omega') # crude but easy self.gamma_w.dump(filename+'_gamma') # crude but easy def load(self, filename): if self.world.rank == 0: all_Fnt_wsG = np.load(filename) all_Ant_sG = np.load(filename+'_avg') # crude but easy else: all_Fnt_wsG = None all_Ant_sG = None if debug: assert all_Fnt_wsG is None or is_contiguous(all_Fnt_wsG, self.dtype) assert all_Ant_sG is None or is_contiguous(all_Ant_sG, float) if not self.allocated: self.allocate() self.gd.distribute(all_Fnt_wsG, self.Fnt_wsG) self.gd.distribute(all_Ant_sG, self.Ant_sG) self.omega_w = np.load(filename+'_omega') # crude but easy self.gamma_w = np.load(filename+'_gamma') # crude but easy
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 new_get_all_electron_density(self, atoms, gridrefinement=2): """Return real all-electron density array.""" # Refinement of coarse grid, for representation of the AE-density if gridrefinement == 1: gd = self.gd n_sg = self.nt_sG.copy() elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = BasisFunctions(gd, phi_aj) phit = BasisFunctions(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) spos_ac = atoms.get_scaled_positions() % 1.0 phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) I_sa = np.zeros((self.nspins, len(atoms))) a_W = np.empty(len(phi.M_W), np.int32) W = 0 for a in phi.atom_indices: nw = len(phi.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw rho_MM = np.zeros((phi.Mmax, phi.Mmax)) for s, I_a in enumerate(I_sa): M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni D_sp = self.D_asp.get(a) if D_sp is None: D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) else: I_a[a] = ((setup.Nct - setup.Nc) / self.nspins - sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) if gd.comm.size > 1: gd.comm.broadcast(D_sp, self.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = unpack2(D_sp[s]) M1 = M2 phi.lfc.ae_valence_density_correction(rho_MM, n_sg[s], a_W, I_a) phit.lfc.ae_valence_density_correction(-rho_MM, n_sg[s], a_W, I_a) a_W = np.empty(len(nc.M_W), np.int32) W = 0 for a in nc.atom_indices: nw = len(nc.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw scale = 1.0 / self.nspins for s, I_a in enumerate(I_sa): nc.lfc.ae_core_density_correction(scale, n_sg[s], a_W, I_a) nct.lfc.ae_core_density_correction(-scale, n_sg[s], a_W, I_a) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False, omega=None, excitation=None, excited=0, stencil=2): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? omega: float RSF mixing parameter excitation: string: Apply operator for improved virtual orbitals to unocc states? Possible modes: singlet: excitations to singlets triplet: excitations to triplets average: average between singlets and tripletts see f.e. http://dx.doi.org/10.1021/acs.jctc.8b00238 excited: number Band to excite from - counted from H**O downwards """ self.finegrid = finegrid self.unocc = unocc self.excitation = excitation self.excited = excited HybridXCBase.__init__(self, name, hybrid=hybrid, xc=xc, omega=omega, stencil=stencil) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) self.gd = density.gd self.redistributor = density.redistributor use_charge_center = hamiltonian.poisson.use_charge_center # XXX How do we construct a copy of the Poisson solver of the # Hamiltonian? We don't know what class it is, etc., but gd # may differ. # XXX One might consider using a charged centered compensation # charge for the PoissonSolver in the case of EXX as standard self.poissonsolver = PoissonSolver('fd', eps=1e-11, use_charge_center=use_charge_center) # self.poissonsolver = hamiltonian.poisson if self.finegrid: self.finegd = self.gd.refine() # XXX Taking restrictor from Hamiltonian will not work in PW mode, # will it? I think this supports only real-space mode. # self.restrictor = hamiltonian.restrictor self.restrictor = Transformer(self.finegd, self.gd, 3) self.interpolator = Transformer(self.gd, self.finegd, 3) else: self.finegd = self.gd self.ghat = LFC(self.finegd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.poissonsolver.set_grid_descriptor(self.finegd) if self.rsf == 'Yukawa': omega2 = self.omega**2 self.screened_poissonsolver = HelmholtzSolver( k2=-omega2, eps=1e-11, nn=3, use_charge_center=use_charge_center) self.screened_poissonsolver.set_grid_descriptor(self.finegd) def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Note that the quantities passed are on the # density/Hamiltonian grids! # They may be distributed differently from own quantities. self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups is_cam = self.is_cam vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() if self.rsf == 'Yukawa': y_vt_g = self.finegd.empty() # if self.gd is not self.finegd: # y_vt_G = self.gd.empty() nocc = int(ceil(kpt.f_n.sum())) // (3 - self.nspins) if self.excitation is not None: ex_band = nocc - self.excited - 1 if self.excitation == 'singlet': ex_weight = -1 elif self.excitation == 'triplet': ex_weight = +1 else: ex_weight = 0 if self.unocc or self.excitation is not None: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # XXXX nbands can be different numbers on different cpus! # That means some will execute the loop and others not. # And deadlocks with augment-grids. # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg if n1 != n2 and f1 == 0 and f1 == f2: continue # Don't work on double unocc. bands # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density( n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 # XXXXX This will go wrong because we are solving the # Poisson equation on the distribution of gd, not finegd # Or maybe it's fixed now self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.rsf == 'Yukawa': y_vt_g[:] = 0.0 self.screened_poissonsolver.solve(y_vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) if is_cam: # Cam like correction y_vt_g *= self.cam_beta else: y_vt_g *= hybrid vt_g -= y_vt_g if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G if self.excitation is not None and n1 == ex_band: Htpsit_nG[nocc:] += f1 * vt_G * psit_nG[nocc:] else: if self.excitation is None or n1 != ex_band \ or n2 < nocc: Htpsit_nG[n2] += f1 * vt_G * psit1_G else: Htpsit_nG[n2] += f1 * ex_weight * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: if self.excitation is None or n1 != ex_band or \ n2 < nocc: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: v_ni[n2] += f1 * ex_weight * \ np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii if self.excitation is not None and n1 == ex_band: for nuoc in range(nocc, nbands): v_ni[nuoc] += f1 * \ np.dot(v_ii, P_ni[nuoc]) def calculate_vv(ni, D_ii, M_pp, weight, addme=False): """Calculate the local corrections depending on Mpp.""" dexx = 0 dekin = 0 if not addme: addsign = -2.0 else: addsign = 2.0 for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] += addsign * weight / \ deg * A / ((i1 != i2) + 1) dekin += 2 * weight / deg * D_ii[i1, i2] * A dexx -= weight / deg * D_ii[i1, i2] * A return (dexx, dekin) # Apply the atomic corrections to the energy and the Hamiltonian # matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii (dexx, dekin) = calculate_vv(ni, D_ii, setup.M_pp, hybrid) ekin += dekin exx += dexx if self.rsf is not None: Mg_pp = setup.calculate_yukawa_interaction(self.omega) if is_cam: (dexx, dekin) = calculate_vv(ni, D_ii, Mg_pp, self.cam_beta, addme=True) else: (dexx, dekin) = calculate_vv(ni, D_ii, Mg_pp, hybrid, addme=True) ekin -= dekin exx -= dexx # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) if self.rsf == 'Yukawa' and setup.X_pg is not None: if is_cam: thybrid = self.cam_beta # 0th order else: thybrid = hybrid exx += thybrid * np.dot(D_p, setup.X_pg) if Htpsit_nG is not None: dH_p += thybrid * setup.X_pg ekin -= thybrid * np.dot(D_p, setup.X_pg) elif self.rsf == 'Yukawa' and setup.X_pg is None: thybrid = exp(-3.62e-2 * self.omega) # educated guess if is_cam: thybrid *= self.cam_beta else: thybrid *= hybrid exx += thybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p += thybrid * setup.X_p ekin -= thybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: if self.rsf is None or is_cam: if is_cam: exx += self.cam_alpha * setup.ExxC else: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return # if self.gd.comm.rank > 0: # H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a])) # self.gd.comm.sum(H_nn) if not self.unocc or self.excitation is not None: H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return if self.unocc or self.excitation is not None: nocc = len(kpt.vt_nG) else: nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return U_nn = U_nn.T.copy() nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
def get_all_electron_density(self, atoms=None, gridrefinement=2, spos_ac=None, skip_core=False): """Return real all-electron density array. Usage: Either get_all_electron_density(atoms) or get_all_electron_density(spos_ac=spos_ac) skip_core=True theoretically returns the all-electron valence density (use with care; will not in general integrate to valence) """ if spos_ac is None: spos_ac = atoms.get_scaled_positions() % 1.0 # Refinement of coarse grid, for representation of the AE-density # XXXXXXXXXXXX think about distribution depending on gridrefinement! if gridrefinement == 1: gd = self.redistributor.aux_gd n_sg = self.nt_sG.copy() # This will get the density with the same distribution # as finegd: n_sg = self.redistributor.distribute(n_sg) elif gridrefinement == 2: gd = self.finegd if self.nt_sg is None: self.interpolate_pseudo_density() n_sg = self.nt_sg.copy() elif gridrefinement == 4: # Extra fine grid gd = self.finegd.refine() # Interpolation function for the density: interpolator = Transformer(self.finegd, gd, 3) # XXX grids! # Transfer the pseudo-density to the fine grid: n_sg = gd.empty(self.nspins) if self.nt_sg is None: self.interpolate_pseudo_density() for s in range(self.nspins): interpolator.apply(self.nt_sg[s], n_sg[s]) else: raise NotImplementedError # Add corrections to pseudo-density to get the AE-density splines = {} phi_aj = [] phit_aj = [] nc_a = [] nct_a = [] for a, id in enumerate(self.setups.id_a): if id in splines: phi_j, phit_j, nc, nct = splines[id] else: # Load splines: phi_j, phit_j, nc, nct = self.setups[a].get_partial_waves()[:4] splines[id] = (phi_j, phit_j, nc, nct) phi_aj.append(phi_j) phit_aj.append(phit_j) nc_a.append([nc]) nct_a.append([nct]) # Create localized functions from splines phi = BasisFunctions(gd, phi_aj) phit = BasisFunctions(gd, phit_aj) nc = LFC(gd, nc_a) nct = LFC(gd, nct_a) phi.set_positions(spos_ac) phit.set_positions(spos_ac) nc.set_positions(spos_ac) nct.set_positions(spos_ac) I_sa = np.zeros((self.nspins, len(spos_ac))) a_W = np.empty(len(phi.M_W), np.intc) W = 0 for a in phi.atom_indices: nw = len(phi.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw x_W = phi.create_displacement_arrays()[0] D_asp = self.D_asp # XXX really? rho_MM = np.zeros((phi.Mmax, phi.Mmax)) for s, I_a in enumerate(I_sa): M1 = 0 for a, setup in enumerate(self.setups): ni = setup.ni D_sp = D_asp.get(a) if D_sp is None: D_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) else: I_a[a] = ( (setup.Nct) / self.nspins - sqrt(4 * pi) * np.dot(D_sp[s], setup.Delta_pL[:, 0])) if not skip_core: I_a[a] -= setup.Nc / self.nspins if gd.comm.size > 1: gd.comm.broadcast(D_sp, D_asp.partition.rank_a[a]) M2 = M1 + ni rho_MM[M1:M2, M1:M2] = unpack2(D_sp[s]) M1 = M2 assert np.all(n_sg[s].shape == phi.gd.n_c) phi.lfc.ae_valence_density_correction(rho_MM, n_sg[s], a_W, I_a, x_W) phit.lfc.ae_valence_density_correction(-rho_MM, n_sg[s], a_W, I_a, x_W) a_W = np.empty(len(nc.M_W), np.intc) W = 0 for a in nc.atom_indices: nw = len(nc.sphere_a[a].M_w) a_W[W:W + nw] = a W += nw scale = 1.0 / self.nspins for s, I_a in enumerate(I_sa): if not skip_core: nc.lfc.ae_core_density_correction(scale, n_sg[s], a_W, I_a) nct.lfc.ae_core_density_correction(-scale, n_sg[s], a_W, I_a) gd.comm.sum(I_a) N_c = gd.N_c g_ac = np.around(N_c * spos_ac).astype(int) % N_c - gd.beg_c if not skip_core: for I, g_c in zip(I_a, g_ac): if (g_c >= 0).all() and (g_c < gd.n_c).all(): n_sg[s][tuple(g_c)] -= I / gd.dv return n_sg, gd
class RealSpaceDensity(Density): def __init__(self, gd, finegd, nspins, charge, redistributor, stencil=3, background_charge=None): Density.__init__(self, gd, finegd, nspins, charge, redistributor, background_charge=background_charge) self.stencil = stencil def initialize(self, setups, timer, magmom_a, hund): Density.initialize(self, setups, timer, magmom_a, hund) # Interpolation function for the density: self.interpolator = Transformer(self.redistributor.aux_gd, self.finegd, self.stencil) spline_aj = [] for setup in setups: if setup.nct is None: spline_aj.append([]) else: spline_aj.append([setup.nct]) self.nct = LFC(self.gd, spline_aj, integral=[setup.Nct for setup in setups], forces=True, cut=True) self.ghat = LFC(self.finegd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi), forces=True) def set_positions(self, spos_ac, rank_a=None): Density.set_positions(self, spos_ac, rank_a) self.nct_G = self.gd.zeros() self.nct.add(self.nct_G, 1.0 / self.nspins) def interpolate_pseudo_density(self, comp_charge=None): """Interpolate pseudo density to fine grid.""" if comp_charge is None: comp_charge = self.calculate_multipole_moments() self.nt_sg = self.distribute_and_interpolate(self.nt_sG, self.nt_sg) # With periodic boundary conditions, the interpolation will # conserve the number of electrons. if not self.gd.pbc_c.all(): # With zero-boundary conditions in one or more directions, # this is not the case. pseudo_charge = (self.background_charge.charge - self.charge - comp_charge) if abs(pseudo_charge) > 1.0e-14: x = (pseudo_charge / self.finegd.integrate(self.nt_sg).sum()) self.nt_sg *= x def interpolate(self, in_xR, out_xR=None): """Interpolate array(s).""" # ndim will be 3 in finite-difference mode and 1 when working # with the AtomPAW class (spherical atoms and 1d grids) ndim = self.gd.ndim if out_xR is None: out_xR = self.finegd.empty(in_xR.shape[:-ndim]) a_xR = in_xR.reshape((-1, ) + in_xR.shape[-ndim:]) b_xR = out_xR.reshape((-1, ) + out_xR.shape[-ndim:]) for in_R, out_R in zip(a_xR, b_xR): self.interpolator.apply(in_R, out_R) return out_xR def distribute_and_interpolate(self, in_xR, out_xR=None): in_xR = self.redistributor.distribute(in_xR) return self.interpolate(in_xR, out_xR) def calculate_pseudo_charge(self): self.nt_g = self.nt_sg.sum(axis=0) self.rhot_g = self.nt_g.copy() self.ghat.add(self.rhot_g, self.Q_aL) self.background_charge.add_charge_to(self.rhot_g) if debug: charge = self.finegd.integrate(self.rhot_g) + self.charge if abs(charge) > self.charge_eps: raise RuntimeError('Charge not conserved: excess=%.9f' % charge) def get_pseudo_core_kinetic_energy_density_lfc(self): return LFC(self.gd, [[setup.tauct] for setup in self.setups], forces=True, cut=True) def calculate_dipole_moment(self): return self.finegd.calculate_dipole_moment(self.rhot_g)
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)