def sendreceive(self, a, dest, b, src, sendtag=123, recvtag=123): assert 0 <= dest < self.size assert dest != self.rank assert is_contiguous(a) assert 0 <= src < self.size assert src != self.rank assert is_contiguous(b) return self.comm.sendreceive(a, dest, b, src, sendtag, recvtag)
def apply(self, input, output=None, phases=None): assert is_contiguous(input, self.dtype) assert input.shape[-3:] == self.ngpin if output is not None: assert is_contiguous(output, self.dtype) assert output.shape[-3:] == self.ngpout assert self.dtype == float or (phases.dtype == complex and phases.shape == (3, 2)) return self.transformer.apply(input, output, phases)
def apply(self, input, output=None, phases=None): assert is_contiguous(input, self.dtype) assert input.shape[-3:] == self.ngpin if output is not None: assert is_contiguous(output, self.dtype) assert output.shape[-3:] == self.ngpout assert (self.dtype == float or (phases.dtype == complex and phases.shape == (3, 2))) return self.transformer.apply(input, output, phases)
def apply(self, input, output, phases=None): assert is_contiguous(input, self.dtype) assert is_contiguous(output, self.dtype) assert input.shape[-3:] == self.ngpin assert output.shape[-3:] == self.ngpout assert (self.dtype == float or (phases.dtype == complex and phases.shape == (3, 2))) assert self.allocated self.transformer.apply(input, output, phases)
def add_density(self, n_G, f_i): """Add atomic electron density to extended density array. Special method for adding the atomic electron density calculated from atomic orbitals and occupation numbers f_i.""" assert is_contiguous(n_G, float) assert is_contiguous(f_i, float) assert n_G.shape == self.shape assert f_i.shape == (self.ni,) self.lfs.add_density(n_G, f_i)
def add(self, coef_xi, a_xg): """Add localized functions to extended arrays. Add the product of coef_xi and the localized functions to a_xg.""" assert is_contiguous(a_xg, self.dtype) assert is_contiguous(coef_xi, self.dtype) assert a_xg.shape[:-3] == coef_xi.shape[:-1] assert a_xg.shape[-3:] == self.shape assert coef_xi.shape[-1] == self.ni self.lfs.add(coef_xi, a_xg)
def integrate(self, a_xg, result_xi): """Calculate integrals of arrays times localized functions. Return the integral of extended arrays times localized functions in result_xi.""" assert is_contiguous(a_xg, self.dtype) assert is_contiguous(result_xi, self.dtype) assert a_xg.shape[:-3] == result_xi.shape[:-1] assert a_xg.shape[-3:] == self.shape assert result_xi.shape[-1] == self.ni self.lfs.integrate(a_xg, result_xi)
def add_density(self, n_G, f_i): """Add atomic electron density to extended density array. Special method for adding the atomic electron density calculated from atomic orbitals and occupation numbers f_i.""" assert is_contiguous(n_G, float) assert is_contiguous(f_i, float) assert n_G.shape == self.shape assert f_i.shape == (self.ni, ) self.lfs.add_density(n_G, f_i)
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 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 derivative(self, a_xg, result_xic): """Calculate x-, y-, z-derivatives of localized integrals. Return the *x*- *y*- and *z*-derivatives of the integral of extended arrays times localized functions in result_xic.""" assert self.forces assert is_contiguous(a_xg, self.dtype) assert is_contiguous(result_xic, self.dtype) assert a_xg.shape[:-3] == result_xic.shape[:-2] assert a_xg.shape[-3:] == self.shape assert result_xic.shape[-2:] == (self.ni, 3) self.lfs.derivative(a_xg, result_xic)
def add_density2(self, n_G, D_p): """Add atomic electron density to extended density array. Special method for adding the atomic electron density calculated from all cross products of atomic orbitals weighted using the density matrix D_p. The method returns the integral of the atomic electron density """ assert is_contiguous(n_G, float) assert is_contiguous(D_p, float) assert n_G.shape == self.shape assert D_p.shape == (self.ni * (self.ni + 1) / 2, ) return self.lfs.add_density2(n_G, D_p)
def add_density2(self, n_G, D_p): """Add atomic electron density to extended density array. Special method for adding the atomic electron density calculated from all cross products of atomic orbitals weighted using the density matrix D_p. The method returns the integral of the atomic electron density """ assert is_contiguous(n_G, float) assert is_contiguous(D_p, float) assert n_G.shape == self.shape assert D_p.shape == (self.ni * (self.ni + 1) / 2,) return self.lfs.add_density2(n_G, D_p)
def allocate(self): if not self.allocated: # Ground state pseudo density self.n0t_sG = self.density.nt_sG.copy() # Fourier transformed pseudo density self.Fnt_wsG = self.gd.zeros((self.nw, self.nspins), dtype=self.dtype) # Ground state D_asp self.D0_asp = {} for a, D_sp in self.density.D_asp.items(): self.D0_asp[a] = D_sp.copy() # Size of D_p for each atom self.np_a = {} for a, D_sp in self.D0_asp.items(): self.np_a[a] = np.array(len(D_sp[0])) # Fourier transformed D_asp self.FD_awsp = {} for a, np_ in self.np_a.items(): self.FD_awsp[a] = np.zeros((self.nw, self.nspins, np_), dtype=self.dtype) self.allocated = True if debug: assert is_contiguous(self.Fnt_wsG, self.dtype)
def new_communicator(self, ranks): """Create a new MPI communicator for a subset of ranks in a group. Must be called with identical arguments by all relevant processes. Note that a valid communicator is only returned to the processes which are included in the new group; other ranks get None returned. Parameters: ranks: ndarray (type int) List of integers of the ranks to include in the new group. Note that these ranks correspond to indices in the current group whereas the rank attribute in the new communicators correspond to their respective index in the subset. """ assert is_contiguous(ranks, int) sranks = np.sort(ranks) # Are all ranks in range? assert 0 <= sranks[0] and sranks[-1] < self.size # No duplicates: for i in range(len(sranks) - 1): assert sranks[i] != sranks[i + 1] assert len(ranks) > 0 comm = self.comm.new_communicator(ranks) if comm is None: # This cpu is not in the new communicator: return None else: return _Communicator(comm, parent=self)
def send(self, a, dest, tag=123, block=True): assert 0 <= dest < self.size assert dest != self.rank assert is_contiguous(a) if not block: pass #assert sys.getrefcount(a) > 3 return self.comm.send(a, dest, tag, block)
def product(self, a, root=-1): """Do multiplication by MPI reduce operations of numerical data. Parameters: a: ndarray or value (type int or float) Numerical data to multiply across all ranks in the communicator group. NB: Find the global product from the local products. If the data is a single value of type int or float (no complex), the result is returned because the input argument is immutable. Otherwise, the reduce operation is carried out in-place such that the elements of the input array will represent the product of the equivalent elements across all processes in the group. root: int (default -1) Rank of the root process, on which the outcome of the reduce operation is valid. A root rank of -1 signifies that the result will be distributed back to all processes, i.e. a broadcast. """ if isinstance(a, (int, float)): return self.comm.product(a, root) else: tc = a.dtype assert tc == int or tc == float assert is_contiguous(a, tc) assert root == -1 or 0 <= root < self.size self.comm.product(a, root)
def allocate(self): if not self.allocated: # Ground state pseudo density self.n0t_sG = self.density.nt_sG.copy() # Fourier transformed pseudo density self.Fnt_wsG = self.gd.zeros((self.nw, self.nspins), dtype=self.dtype) # Ground state D_asp self.D0_asp = {} for a, D_sp in self.density.D_asp.items(): self.D0_asp[a] = D_sp.copy() # Size of D_p for each atom self.np_a = {} for a, D_sp in self.D0_asp.items(): self.np_a[a] = np.array([len(D_sp[0])]) # Fourier transformed D_asp self.FD_awsp = {} for a, np_ in self.np_a.items(): self.FD_awsp[a] = np.zeros((self.nw, self.nspins, np_[0]), dtype=self.dtype) self.allocated = True if debug: assert is_contiguous(self.Fnt_wsG, self.dtype)
def send(self, a, dest, tag=123, block=True): assert 0 <= dest < self.size assert dest != self.rank assert is_contiguous(a) if not block: pass # assert sys.getrefcount(a) > 3 return self.comm.send(a, dest, tag, block)
def dotu(a, b): """Dot product, NOT conjugating the first vector with complex arguments. Returns the value of the operation:: _ \ ) a * b /_ ijk... ijk... ijk... """ assert ((is_contiguous(a, float) and is_contiguous(b, float)) or (is_contiguous(a, complex) and is_contiguous(b,complex))) assert a.shape == b.shape return _gpaw.dotu(a, b)
def dotu(a, b): """Dot product, NOT conjugating the first vector with complex arguments. Returns the value of the operation:: _ \ ) a * b /_ ijk... ijk... ijk... """ assert ((is_contiguous(a, float) and is_contiguous(b, float)) or (is_contiguous(a, complex) and is_contiguous(b, complex))) assert a.shape == b.shape return _gpaw.dotu(a, b)
def axpy(alpha, x, y): """alpha x plus y. Performs the operation:: y <- alpha * x + y """ if isinstance(alpha, complex): assert is_contiguous(x, complex) and is_contiguous(y, complex) else: assert isinstance(alpha, float) assert x.dtype in [float, complex] assert x.dtype == y.dtype assert x.flags.contiguous and y.flags.contiguous assert x.shape == y.shape _gpaw.axpy(alpha, x, y)
def czher(alpha, x, a): """alpha x * x.conj() + a. Performs the operation:: y <- alpha * x * x.conj() + a where x is a N element vector and a is a N by N hermitian matrix, alpha is a real scalar. """ assert isinstance(alpha, float) assert is_contiguous(x, complex) and is_contiguous(a, complex) assert x.flags.contiguous and a.flags.contiguous assert x.ndim == 1 and a.ndim == 2 assert x.shape[0] == a.shape[0] _gpaw.czher(alpha, x, a)
def czher(alpha, x, a): """alpha x * x.conj() + a. Performs the operation:: y <- alpha * x * x.conj() + a where x is a N element vector and a is a N by N hermitian matrix, alpha is a real scalar """ assert isinstance(alpha, float) assert is_contiguous(x, complex) and is_contiguous(a, complex) assert x.flags.contiguous and a.flags.contiguous assert x.ndim == 1 and a.ndim == 2 assert x.shape[0] == a.shape[0] _gpaw.czher(alpha, x, a)
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 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 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.allocated = True if debug: assert is_contiguous(self.Fnt_wsG, self.dtype)
def allocate(self): if not self.allocated: poisson = self.paw.hamiltonian.poisson # Ground state charge density self.n0_G = (-1.0 * poisson.classical_material.sign * poisson.classical_material.charge_density.copy()) # Fourier transformed charge density self.Fn_wG = poisson.cl.gd.zeros((self.nw, ), dtype=self.dtype) self.allocated = True if debug: assert is_contiguous(self.Fn_wG, self.dtype)
def scal(alpha, x): """alpha x Performs the operation:: x <- alpha * x """ if isinstance(alpha, complex): assert is_contiguous(x, complex) else: assert isinstance(alpha, float) assert x.dtype in [float, complex] assert x.flags.contiguous _gpaw.scal(alpha, x)
def allocate(self): if not self.allocated: # Ground state charge density self.n0_G = (-1.0) * self.paw.hamiltonian.poisson.classical_material.sign * \ self.paw.hamiltonian.poisson.classical_material.charge_density.copy() # Fourier transformed charge density self.Fn_wsG = self.paw.hamiltonian.poisson.cl.gd.zeros((self.nw, self.nspins), dtype=self.dtype) self.allocated = True if debug: assert is_contiguous(self.Ft_wG, self.dtype)
def get_functions(self, gd, start_c, end_c, spos_c): h_cv = gd.h_cv # start_c is the new origin so we translate gd.beg_c to start_c origin_c = np.array([0, 0, 0]) pos_v = np.dot(spos_c, gd.cell_cv) - np.dot(start_c, h_cv) A_gm, G_b = _gpaw.spline_to_grid(self.spline, origin_c, end_c - start_c, pos_v, h_cv, end_c - start_c, origin_c) if debug: assert G_b.ndim == 1 and G_b.shape[0] % 2 == 0 assert is_contiguous(G_b, np.intc) assert A_gm.shape[:-1] == np.sum(G_b[1::2] - G_b[::2]) indices_gm, ng, nm = self.spline.get_indices_from_zranges(start_c, end_c, G_b) shape = (nm,) + tuple(end_c - start_c) work_mB = np.zeros(shape, dtype=A_gm.dtype) np.put(work_mB, indices_gm, A_gm) return work_mB
def get_functions(self, gd, start_c, end_c, spos_c): h_cv = gd.h_cv # start_c is the new origin so we translate gd.beg_c to start_c origin_c = np.array([0,0,0]) pos_v = np.dot(spos_c, gd.cell_cv) - np.dot(start_c, h_cv) A_gm, G_b = _gpaw.spline_to_grid(self.spline, origin_c, end_c-start_c, pos_v, h_cv, end_c-start_c, origin_c) if debug: assert G_b.ndim == 1 and G_b.shape[0] % 2 == 0 assert is_contiguous(G_b, np.intc) assert A_gm.shape[:-1] == np.sum(G_b[1::2]-G_b[::2]) indices_gm, ng, nm = self.spline.get_indices_from_zranges(start_c, end_c, G_b) shape = (nm,) + tuple(end_c-start_c) work_mB = np.zeros(shape, dtype=A_gm.dtype) np.put(work_mB, indices_gm, A_gm) return work_mB
def broadcast(self, a, root): """Share data from a single process to all ranks in a group. Parameters: a: ndarray Data, i.e. send buffer on root rank, receive buffer elsewhere. Note that after the broadcast, all ranks have the same data. root: int Rank of the root process, from which the data is to be shared. Example:: # All ranks have parts of interesting data. Take a given index. mydata[:] = np.random.normal(size=N) # Who has the element at global index 13? Everybody needs it! index = 13 root, myindex = divmod(index, N) element = np.empty(1, dtype=float) if comm.rank == root: # This process has the requested element so extract it element[:] = mydata[myindex] # Broadcast from owner to everyone else comm.broadcast(element, root) # .. which is equivalent to .. if comm.rank == root: # We are root so send it to the other ranks for rank in range(comm.size): if rank != root: comm.send(element, rank, tag=123) else: # We don't have it so receive from root comm.receive(element, root, tag=123) """ assert 0 <= root < self.size assert is_contiguous(a) self.comm.broadcast(a, root)
def receive(self, a, src, tag=123, block=True): assert 0 <= src < self.size assert src != self.rank assert is_contiguous(a) return self.comm.receive(a, src, tag, block)
def ssend(self, a, dest, tag=123): assert 0 <= dest < self.size assert dest != self.rank assert is_contiguous(a) return self.comm.ssend(a, dest, tag)
def gaussian_wave(r_vG, r0_v, sigma, k_v=None, A=None, dtype=float, out_G=None): """Generates function values for atom-centered Gaussian waves. :: _ _ _ / -|r-r0|^2 \ _ _ f(r) = A * exp( ----------- ) * exp( i k.r ) \ 2 sigma^2 / If the parameter A is not specified, the Gaussian wave is normalized:: oo / ____ \ -3/2 / _ 2 2 A = ( / ' ) => 4 pi | dr |f(r)| r = 1 \ \/ pi sigma / / 0 Parameters: r_vG: ndarray Set of coordinates defining the grid positions. r0_v: ndarray Set of coordinates defining the center of the Gaussian envelope. sigma: float Specifies the spatial width of the Gaussian envelope. k_v: ndarray or None Set of reciprocal lattice coordinates defining the wave vector. An argument of None is interpreted as the gamma point i.e. k_v=0. A: float, complex or None Specifies the amplitude of the Gaussian wave. Normalizes if None. dtype: type, defaults to float Specifies the output data type. Only returns the real-part if float. out_G: ndarray or None Optional pre-allocated buffer to fill in values. Allocates if None. """ if k_v is None: k_v = np.zeros(r0_v.shape) if A is None: # 4*pi*int(exp(-r^2/(2*sigma^2))^2 * r^2, r=0...infinity) # = sigma^3*pi^(3/2) = 1/A^2 -> A = (sqrt(Pi)*sigma)^(-3/2) A = 1 / (sigma * np.pi**0.5)**1.5 if debug: assert is_contiguous(r_vG, float) assert is_contiguous(r0_v, float) assert is_contiguous(k_v, float) assert r_vG.ndim >= 2 and r_vG.shape[0] > 0 assert r0_v.ndim == 1 and r0_v.shape[0] > 0 assert k_v.ndim == 1 and k_v.shape[0] > 0 assert (r_vG.shape[0], ) == r0_v.shape == k_v.shape assert sigma > 0 if out_G is None: out_G = np.empty(r_vG.shape[1:], dtype=dtype) elif debug: assert is_contiguous(out_G) assert out_G.shape == r_vG.shape[1:] # slice_v2vG = [slice(None)] + [np.newaxis]*3 # gw = lambda r_vG, r0_v, sigma, k_v, A=1/(sigma*np.pi**0.5)**1.5: \ # * np.exp(-np.sum((r_vG-r0_v[slice_v2vG])**2, axis=0)/(2*sigma**2)) \ # * np.exp(1j*np.sum(np.r_vG*k_v[slice_v2vG], axis=0)) * A _gpaw.utilities_gaussian_wave(A, r_vG, r0_v, sigma, k_v, out_G) return out_G
def gaussian_wave(r_vG, r0_v, sigma, k_v=None, A=None, dtype=float, out_G=None): """Generates function values for atom-centered Gaussian waves. :: _ _ _ / -|r-r0|^2 \ _ _ f(r) = A * exp( ----------- ) * exp( i k.r ) \ 2 sigma^2 / If the parameter A is not specified, the Gaussian wave is normalized:: oo / ____ \ -3/2 / _ 2 2 A = ( / ' ) => 4 pi | dr |f(r)| r = 1 \ \/ pi sigma / / 0 Parameters: r_vG: ndarray Set of coordinates defining the grid positions. r0_v: ndarray Set of coordinates defining the center of the Gaussian envelope. sigma: float Specifies the spatial width of the Gaussian envelope. k_v: ndarray or None Set of reciprocal lattice coordinates defining the wave vector. An argument of None is interpreted as the gamma point i.e. k_v=0. A: float, complex or None Specifies the amplitude of the Gaussian wave. Normalizes if None. dtype: type, defaults to float Specifies the output data type. Only returns the real-part if float. out_G: ndarray or None Optional pre-allocated buffer to fill in values. Allocates if None. """ if k_v is None: k_v = np.zeros(r0_v.shape) if A is None: # 4*pi*int(exp(-r^2/(2*sigma^2))^2 * r^2, r=0...infinity) # = sigma^3*pi^(3/2) = 1/A^2 -> A = (sqrt(Pi)*sigma)^(-3/2) A = 1/(sigma*np.pi**0.5)**1.5 if debug: assert is_contiguous(r_vG, float) assert is_contiguous(r0_v, float) assert is_contiguous(k_v, float) assert r_vG.ndim >= 2 and r_vG.shape[0] > 0 assert r0_v.ndim == 1 and r0_v.shape[0] > 0 assert k_v.ndim == 1 and k_v.shape[0] > 0 assert (r_vG.shape[0],) == r0_v.shape == k_v.shape assert sigma > 0 if out_G is None: out_G = np.empty(r_vG.shape[1:], dtype=dtype) elif debug: assert is_contiguous(out_G) assert out_G.shape == r_vG.shape[1:] # slice_v2vG = [slice(None)] + [np.newaxis]*3 # gw = lambda r_vG, r0_v, sigma, k_v, A=1/(sigma*np.pi**0.5)**1.5: \ # * np.exp(-np.sum((r_vG-r0_v[slice_v2vG])**2, axis=0)/(2*sigma**2)) \ # * np.exp(1j*np.sum(np.r_vG*k_v[slice_v2vG], axis=0)) * A _gpaw.utilities_gaussian_wave(A, r_vG, r0_v, sigma, k_v, out_G) return out_G