y_g = coord_vg[1, :] z_g = coord_vg[2, :] rho_g = gd.zeros() for z0 in [1, 2]: rho_g += 10 * (z_g - z0) * \ np.exp(-20 * np.sum((coord_vg.T - np.array([.5, .5, z0])).T**2, axis=0)) if do_plot: big_rho_g = gd.collect(rho_g) if gd.comm.rank == 0: import matplotlib.pyplot as plt fig, ax_ij = plt.subplots(3, 4, figsize=(20, 10)) ax_i = ax_ij.ravel() ploti = 0 Ng_c = gd.get_size_of_global_array() plt.sca(ax_i[ploti]) ploti += 1 plt.pcolormesh(big_rho_g[Ng_c[0] / 2]) plt.sca(ax_i[ploti]) ploti += 1 plt.plot(big_rho_g[Ng_c[0] / 2, Ng_c[1] / 2]) def plot_phi(phi_g): if do_plot: big_phi_g = gd.collect(phi_g) if gd.comm.rank == 0: global ploti if ploti == 4: ploti -= 2
class BaseInducedField(object): """Partially virtual base class for induced field calculations. Attributes: ----------- omega_w: ndarray Frequencies for Fourier transform in atomic units folding: string Folding type ('Gauss' or 'Lorentz' or None) width: float Width parameter for folding Frho_wg: ndarray (complex) Fourier transform of induced charge density Fphi_wg: ndarray (complex) Fourier transform of induced electric potential Fef_wvg: ndarray (complex) Fourier transform of induced electric field Ffe_wg: ndarray (float) Fourier transform of field enhancement Fbgef_v: ndarray (float) Fourier transform of background electric field """ def __init__(self, filename=None, paw=None, frequencies=None, folding='Gauss', width=0.08, readmode=''): """ Parameters: ----------- filename: string Filename of a previous ``InducedField`` to be loaded. Setting filename disables parameters ``frequencies``, ``folding`` and ``width``. paw: PAW object PAW object for InducedField frequencies: ndarray or list of floats Frequencies in eV for Fourier transforms. This parameter is neglected if ``filename`` is given. folding: string Folding type: 'Gauss' or 'Lorentz' or None: Gaussian or Lorentzian folding or no folding. This parameter is neglected if ``filename`` is given. width: float Width in eV for the Gaussian (sigma) or Lorentzian (eta) folding This parameter is neglected if ``filename`` is given. """ self.dtype = complex # These are allocated when calculated self.fieldgd = None self.Frho_wg = None self.Fphi_wg = None self.Fef_wvg = None self.Ffe_wg = None self.Fbgef_v = None # has variables self.has_paw = False self.has_field = False if not hasattr(self, 'readwritemode_str_to_list'): self.readwritemode_str_to_list = \ {'': ['Frho', 'atoms'], 'all': ['Frho', 'Fphi', 'Fef', 'Ffe', 'atoms'], 'field': ['Frho', 'Fphi', 'Fef', 'Ffe', 'atoms']} if filename is not None: self.initialize(paw, allocate=False) self.read(filename, mode=readmode) return self.folding = folding self.width = width * eV_to_aufrequency self.set_folding(folding, width * eV_to_aufrequency) self.omega_w = np.asarray(frequencies) * eV_to_aufrequency self.nw = len(self.omega_w) self.nv = 3 # dimensionality of the space self.initialize(paw, allocate=True) def initialize(self, paw, allocate=True): self.allocated = False self.has_paw = paw is not None if self.has_paw: # If no paw is given, then the variables # marked with "# !" are created in _read(). # Other variables are not accessible without # paw (TODO: could be implemented...). self.paw = paw self.world = paw.wfs.world # ! self.domain_comm = paw.wfs.gd.comm # ! self.band_comm = paw.wfs.bd.comm # ! self.kpt_comm = paw.wfs.kd.comm # ! self.rank_a = paw.wfs.atom_partition.rank_a self.nspins = paw.density.nspins # ! self.setups = paw.wfs.setups self.density = paw.density self.atoms = paw.atoms # ! self.na = len(self.atoms.get_atomic_numbers()) # ! self.gd = self.density.gd # ! self.stencil = self.density.stencil if allocate: self.allocate() def allocate(self): self.allocated = True def deallocate(self): self.fieldgd = None self.Frho_wg = None self.Fphi_wg = None self.Fef_wvg = None self.Ffe_wg = None self.Fbgef_v = None def set_folding(self, folding, width): """ width: float Width in atomic units """ if width is None: folding = None self.folding = folding if self.folding is None: self.width = None else: self.width = width def get_induced_density(self, from_density, gridrefinement): raise RuntimeError('Virtual member function called') def calculate_induced_field(self, from_density='comp', gridrefinement=2, extend_N_cd=None, deextend=False, poissonsolver=None, gradient_n=3): if self.has_field and \ from_density == self.field_from_density and \ self.Frho_wg is not None: Frho_wg = self.Frho_wg gd = self.fieldgd else: Frho_wg, gd = self.get_induced_density(from_density, gridrefinement) # Always extend a bit to get field without jumps if extend_N_cd is None: extend_N = max(8, 2**int(np.ceil(np.log(gradient_n) / np.log(2.)))) extend_N_cd = extend_N * np.ones(shape=(3, 2), dtype=np.int) deextend = True # Extend grid oldgd = gd egd, cell_cv, move_c = \ extended_grid_descriptor(gd, extend_N_cd=extend_N_cd) Frho_we = egd.zeros((self.nw, ), dtype=self.dtype) for w in range(self.nw): extend_array(gd, egd, Frho_wg[w], Frho_we[w]) Frho_wg = Frho_we gd = egd if not deextend: # TODO: this will make atoms unusable with original grid self.atoms.set_cell(cell_cv, scale_atoms=False) move_atoms(self.atoms, move_c) # Allocate arrays Fphi_wg = gd.zeros((self.nw, ), dtype=self.dtype) Fef_wvg = gd.zeros(( self.nw, self.nv, ), dtype=self.dtype) Ffe_wg = gd.zeros((self.nw, ), dtype=float) # Poissonsolver if poissonsolver is None: poissonsolver = PoissonSolver(name='fd', eps=1e-20) poissonsolver.set_grid_descriptor(gd) for w in range(self.nw): # TODO: better output of progress # parprint('%d' % w) calculate_field(gd, Frho_wg[w], self.Fbgef_v, Fphi_wg[w], Fef_wvg[w], Ffe_wg[w], poissonsolver=poissonsolver, nv=self.nv, gradient_n=gradient_n) # De-extend grid if deextend: Frho_wo = oldgd.zeros((self.nw, ), dtype=self.dtype) Fphi_wo = oldgd.zeros((self.nw, ), dtype=self.dtype) Fef_wvo = oldgd.zeros(( self.nw, self.nv, ), dtype=self.dtype) Ffe_wo = oldgd.zeros((self.nw, ), dtype=float) for w in range(self.nw): deextend_array(oldgd, gd, Frho_wo[w], Frho_wg[w]) deextend_array(oldgd, gd, Fphi_wo[w], Fphi_wg[w]) deextend_array(oldgd, gd, Ffe_wo[w], Ffe_wg[w]) for v in range(self.nv): deextend_array(oldgd, gd, Fef_wvo[w][v], Fef_wvg[w][v]) Frho_wg = Frho_wo Fphi_wg = Fphi_wo Fef_wvg = Fef_wvo Ffe_wg = Ffe_wo gd = oldgd # Store results self.has_field = True self.field_from_density = from_density self.fieldgd = gd self.Frho_wg = Frho_wg self.Fphi_wg = Fphi_wg self.Fef_wvg = Fef_wvg self.Ffe_wg = Ffe_wg def _parse_readwritemode(self, mode): if isinstance(mode, str): try: readwrites = self.readwritemode_str_to_list[mode] except KeyError: raise IOError('unknown readwrite mode string') elif isinstance(mode, list): readwrites = mode else: raise IOError('unknown readwrite mode type') if any(k in readwrites for k in ['Frho', 'Fphi', 'Fef', 'Ffe']): readwrites.append('field') return readwrites def read(self, filename, mode='', idiotproof=True): if idiotproof and not filename.endswith('.ind'): raise IOError('Filename must end with `.ind`.') reads = self._parse_readwritemode(mode) # Open reader from gpaw.io import Reader reader = Reader(filename) self._read(reader, reads) reader.close() self.world.barrier() def _read(self, reader, reads): r = reader # Test data type dtype = {'float': float, 'complex': complex}[r.dtype] if dtype != self.dtype: raise IOError('Data is an incompatible type.') # Read dimensions na = r.na self.nv = r.nv nspins = r.nspins ng = r.ng # Background electric field Fbgef_v = r.Fbgef_v if self.has_paw: # Test dimensions if na != self.na: raise IOError('natoms is incompatible with calculator') if nspins != self.nspins: raise IOError('nspins is incompatible with calculator') if (ng != self.gd.get_size_of_global_array()).any(): raise IOError('grid is incompatible with calculator') if (Fbgef_v != self.Fbgef_v).any(): raise IOError('kick is incompatible with calculator') else: # Construct objects / assign values without paw self.na = na self.nspins = nspins self.Fbgef_v = Fbgef_v from ase.io.trajectory import read_atoms self.atoms = read_atoms(r.atoms) self.world = mpi.world self.gd = GridDescriptor(ng + 1, self.atoms.get_cell() / Bohr, pbc_c=False, comm=self.world) self.domain_comm = self.gd.comm self.band_comm = mpi.SerialCommunicator() self.kpt_comm = mpi.SerialCommunicator() # Folding folding = r.folding width = r.width self.set_folding(folding, width) # Frequencies self.omega_w = r.omega_w self.nw = len(self.omega_w) # Read field if 'field' in reads: nfieldg = r.nfieldg self.has_field = True self.field_from_density = r.field_from_density self.fieldgd = self.gd.new_descriptor(N_c=nfieldg + 1) def readarray(name, shape, dtype): if name.split('_')[0] in reads: setattr(self, name, self.fieldgd.empty(shape, dtype=dtype)) self.fieldgd.distribute(r.get(name), getattr(self, name)) readarray('Frho_wg', (self.nw, ), self.dtype) readarray('Fphi_wg', (self.nw, ), self.dtype) readarray('Fef_wvg', (self.nw, self.nv), self.dtype) readarray('Ffe_wg', (self.nw, ), float) def write(self, filename, mode='', idiotproof=True): """ Parameters ---------- mode: string or list of strings """ if idiotproof and not filename.endswith('.ind'): raise IOError('Filename must end with `.ind`.') writes = self._parse_readwritemode(mode) if 'field' in writes and self.fieldgd is None: raise IOError('field variables cannot be written ' + 'before they are calculated') from gpaw.io import Writer writer = Writer(filename, self.world, tag='INDUCEDFIELD') # Actual write self._write(writer, writes) # Make sure slaves don't return before master is done writer.close() self.world.barrier() def _write(self, writer, writes): # Write parameters/dimensions writer.write(dtype={ float: 'float', complex: 'complex' }[self.dtype], folding=self.folding, width=self.width, na=self.na, nv=self.nv, nspins=self.nspins, ng=self.gd.get_size_of_global_array()) # Write field grid if 'field' in writes: writer.write(field_from_density=self.field_from_density, nfieldg=self.fieldgd.get_size_of_global_array()) # Write frequencies writer.write(omega_w=self.omega_w) # Write background electric field writer.write(Fbgef_v=self.Fbgef_v) from ase.io.trajectory import write_atoms write_atoms(writer.child('atoms'), self.atoms) if 'field' in writes: def writearray(name, shape, dtype): if name.split('_')[0] in writes: writer.add_array(name, shape, dtype) a_wxg = getattr(self, name) for w in range(self.nw): writer.fill(self.fieldgd.collect(a_wxg[w])) shape = (self.nw, ) + tuple( self.fieldgd.get_size_of_global_array()) writearray('Frho_wg', shape, self.dtype) writearray('Fphi_wg', shape, self.dtype) writearray('Ffe_wg', shape, float) shape3 = shape[:1] + (self.nv, ) + shape[1:] writearray('Fef_wvg', shape3, self.dtype)
class UTDomainParallelSetup(TestCase): """ Setup a simple domain parallel calculation.""" # Number of bands nbands = 1 # Spin-paired nspins = 1 # Mean spacing and number of grid points per axis h = 0.2 / Bohr # Generic lattice constant for unit cell a = 5.0 / Bohr # Type of boundary conditions employed boundaries = None # Type of unit cell employed celltype = None def setUp(self): for virtvar in ['boundaries', 'celltype']: assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar # Basic unit cell information: pbc_c = {'zero' : (False,False,False), \ 'periodic': (True,True,True), \ 'mixed' : (True, False, True)}[self.boundaries] a, b = self.a, 2**0.5*self.a cell_cv = {'general' : np.array([[0,a,a],[a/2,0,a/2],[a/2,a/2,0]]), 'rotated' : np.array([[0,0,b],[b/2,0,0],[0,b/2,0]]), 'inverted' : np.array([[0,0,b],[0,b/2,0],[b/2,0,0]]), 'orthogonal': np.diag([b, b/2, b/2])}[self.celltype] cell_cv = np.array([(4-3*pbc)*c_v for pbc,c_v in zip(pbc_c, cell_cv)]) # Decide how many kpoints to sample from the 1st Brillouin Zone kpts_c = np.ceil((10/Bohr)/np.sum(cell_cv**2,axis=1)**0.5).astype(int) kpts_c = tuple(kpts_c*pbc_c + 1-pbc_c) bzk_kc = kpts2ndarray(kpts_c) self.gamma = len(bzk_kc) == 1 and not bzk_kc[0].any() #p = InputParameters() #Z_a = self.atoms.get_atomic_numbers() #xcfunc = XC(p.xc) #setups = Setups(Z_a, p.setups, p.basis, p.lmax, xcfunc) #symmetry, weight_k, self.ibzk_kc = reduce_kpoints(self.atoms, bzk_kc, # setups, p.usesymm) self.ibzk_kc = bzk_kc.copy() # don't use symmetry reduction of kpoints self.nibzkpts = len(self.ibzk_kc) self.ibzk_kv = kpoint_convert(cell_cv, skpts_kc=self.ibzk_kc) # Parse parallelization parameters and create suitable communicators. #parsize_domain, parsize_bands = create_parsize_minbands(self.nbands, world.size) parsize_domain, parsize_bands = world.size//gcd(world.size, self.nibzkpts), 1 assert self.nbands % np.prod(parsize_bands) == 0 domain_comm, kpt_comm, band_comm = distribute_cpus(parsize_domain, parsize_bands, self.nspins, self.nibzkpts) # Set up band descriptor: self.bd = BandDescriptor(self.nbands, band_comm) # Set up grid descriptor: N_c = np.round(np.sum(cell_cv**2, axis=1)**0.5 / self.h) N_c += 4-N_c % 4 # makes domain decomposition easier self.gd = GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) self.assertEqual(self.gamma, np.all(~self.gd.pbc_c)) # What to do about kpoints? self.kpt_comm = kpt_comm if debug and world.rank == 0: comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \ self.gd.comm, self.kpt_comm]]) print '%d world, %d band, %d domain, %d kpt' % comm_sizes def tearDown(self): del self.ibzk_kc, self.ibzk_kv, self.bd, self.gd, self.kpt_comm # ================================= def verify_comm_sizes(self): if world.size == 1: return comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \ self.gd.comm, self.kpt_comm]]) self._parinfo = '%d world, %d band, %d domain, %d kpt' % comm_sizes self.assertEqual(self.nbands % self.bd.comm.size, 0) self.assertEqual((self.nspins*self.nibzkpts) % self.kpt_comm.size, 0) def verify_grid_volume(self): gdvol = np.prod(self.gd.get_size_of_global_array())*self.gd.dv self.assertAlmostEqual(self.gd.integrate(1+self.gd.zeros()), gdvol, 10) def verify_grid_point(self): # Volume integral of cartesian coordinates of all available grid points gdvol = np.prod(self.gd.get_size_of_global_array())*self.gd.dv cmr_v = self.gd.integrate(self.gd.get_grid_point_coordinates()) / gdvol # Theoretical center of cell based on all available grid data cm0_v = np.dot((0.5*(self.gd.get_size_of_global_array()-1.0) \ + 1.0-self.gd.pbc_c) / self.gd.N_c, self.gd.cell_cv) self.assertAlmostEqual(np.abs(cmr_v-cm0_v).max(), 0, 10) def verify_non_pbc_spacing(self): atoms = create_random_atoms(self.gd, 1000, 'NH3', self.a/2) pos_ac = atoms.get_positions() cellvol = np.linalg.det(self.gd.cell_cv) if debug: print 'cell volume:', np.abs(cellvol)*Bohr**3, 'Ang^3', cellvol>0 and '(right handed)' or '(left handed)' # Loop over non-periodic axes and check minimum distance requirement for c in np.argwhere(~self.gd.pbc_c).ravel(): a_v = self.gd.cell_cv[(c+1)%3] b_v = self.gd.cell_cv[(c+2)%3] c_v = np.cross(a_v, b_v) for d in range(2): # Inwards unit normal vector of d'th cell face of c'th axis # and point intersected by this plane (d=0,1 / bottom,top). n_v = np.sign(cellvol) * (1-2*d) * c_v / np.linalg.norm(c_v) if debug: print {0:'x',1:'y',2:'z'}[c]+'-'+{0:'bottom',1:'top'}[d]+':', n_v, 'Bohr' if debug: print 'gd.xxxiucell_cv[%d]~' % c, self.gd.xxxiucell_cv[c] / np.linalg.norm(self.gd.xxxiucell_cv[c]), 'Bohr' origin_v = np.dot(d * np.eye(3)[c], self.gd.cell_cv) d_a = np.dot(pos_ac/Bohr - origin_v[np.newaxis,:], n_v) if debug: print 'a:', self.a/2*Bohr, 'min:', np.min(d_a)*Bohr, 'max:', np.max(d_a)*Bohr self.assertAlmostEqual(d_a.min(), self.a/2, 0) #XXX digits!
def test(cellno, cellname, cell_cv, idiv, pbc, nn): N_c = h2gpts(0.12, cell_cv, idiv=idiv) if idiv == 1: N_c += 1 - N_c % 2 # We want especially to test uneven grids gd = GridDescriptor(N_c, cell_cv, pbc_c=pbc) rho_g = gd.zeros() phi_g = gd.zeros() rho_g[:] = -0.3 + rng.rand(*rho_g.shape) # Neutralize charge: charge = gd.integrate(rho_g) magic = gd.get_size_of_global_array().prod() rho_g -= charge / gd.dv / magic charge = gd.integrate(rho_g) assert abs(charge) < 1e-12 # Check use_cholesky=True/False ? from gpaw.poisson import FDPoissonSolver ps = FastPoissonSolver(nn=nn) #print('setgrid') # Will raise BadAxesError for some pbc/cell combinations ps.set_grid_descriptor(gd) ps.solve(phi_g, rho_g) laplace = Laplace(gd, scale=-1.0 / (4.0 * np.pi), n=nn) def get_residual_err(phi_g): rhotest_g = gd.zeros() laplace.apply(phi_g, rhotest_g) residual = np.abs(rhotest_g - rho_g) # Residual is not accurate at end of non-periodic directions # except for nn=1 (since effectively we use the right stencil # only for nn=1 at the boundary). # # To do this check correctly, the Laplacian should have lower # nn at the boundaries. Therefore we do not test the residual # at these ends, only in between, by zeroing the bad ones: if nn > 1: exclude_points = nn - 1 for c in range(3): if nn > 1 and not pbc[c]: # get view ehere axis c refers becomes zeroth dimension: X = residual.transpose(c, (c + 1) % 3, (c + 2) % 3) if gd.beg_c[c] == 1: X[:exclude_points] = 0.0 if gd.end_c[c] == gd.N_c[c]: X[-exclude_points:] = 0.0 return residual.max() maxerr = get_residual_err(phi_g) pbcstring = '{}{}{}'.format(*pbc) if 0: ps2 = FDPoissonSolver(relax='J', nn=nn, eps=1e-18) ps2.set_grid_descriptor(gd) phi2_g = gd.zeros() ps2.solve(phi2_g, rho_g) phimaxerr = np.abs(phi2_g - phi_g).max() maxerr2 = get_residual_err(phi2_g) msg = ('{:2d} {:8s} pbc={} err={:8.5e} err[J]={:8.5e} ' 'err[phi]={:8.5e} nn={:1d}' .format(cellno, cellname, pbcstring, maxerr, maxerr2, phimaxerr, nn)) state = 'ok' if maxerr < tolerance else 'FAIL' msg = ('{:2d} {:8s} grid={} pbc={} err[fast]={:8.5e} nn={:1d} {}' .format(cellno, cellname, N_c, pbcstring, maxerr, nn, state)) if world.rank == 0: print(msg) return maxerr
class UTDomainParallelSetup(TestCase): """ Setup a simple domain parallel calculation.""" # Number of bands nbands = 1 # Spin-paired nspins = 1 # Mean spacing and number of grid points per axis h = 0.2 / Bohr # Generic lattice constant for unit cell a = 5.0 / Bohr # Type of boundary conditions employed boundaries = None # Type of unit cell employed celltype = None def setUp(self): for virtvar in ['boundaries', 'celltype']: assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar # Basic unit cell information: pbc_c = {'zero' : (False,False,False), \ 'periodic': (True,True,True), \ 'mixed' : (True, False, True)}[self.boundaries] a, b = self.a, 2**0.5*self.a cell_cv = {'general' : np.array([[0,a,a],[a/2,0,a/2],[a/2,a/2,0]]), 'rotated' : np.array([[0,0,b],[b/2,0,0],[0,b/2,0]]), 'inverted' : np.array([[0,0,b],[0,b/2,0],[b/2,0,0]]), 'orthogonal': np.diag([b, b/2, b/2])}[self.celltype] cell_cv = np.array([(4-3*pbc)*c_v for pbc,c_v in zip(pbc_c, cell_cv)]) # Decide how many kpoints to sample from the 1st Brillouin Zone kpts_c = np.ceil((10/Bohr)/np.sum(cell_cv**2,axis=1)**0.5).astype(int) kpts_c = tuple(kpts_c*pbc_c + 1-pbc_c) bzk_kc = kpts2ndarray(kpts_c) self.gamma = len(bzk_kc) == 1 and not bzk_kc[0].any() #p = InputParameters() #Z_a = self.atoms.get_atomic_numbers() #xcfunc = XC(p.xc) #setups = Setups(Z_a, p.setups, p.basis, p.lmax, xcfunc) #symmetry, weight_k, self.ibzk_kc = reduce_kpoints(self.atoms, bzk_kc, # setups, p.usesymm) self.ibzk_kc = bzk_kc.copy() # don't use symmetry reduction of kpoints self.nibzkpts = len(self.ibzk_kc) self.ibzk_kv = kpoint_convert(cell_cv, skpts_kc=self.ibzk_kc) # Parse parallelization parameters and create suitable communicators. #parsize, parsize_bands = create_parsize_minbands(self.nbands, world.size) parsize, parsize_bands = world.size//gcd(world.size, self.nibzkpts), 1 assert self.nbands % np.prod(parsize_bands) == 0 domain_comm, kpt_comm, band_comm = distribute_cpus(parsize, parsize_bands, self.nspins, self.nibzkpts) # Set up band descriptor: self.bd = BandDescriptor(self.nbands, band_comm) # Set up grid descriptor: N_c = np.round(np.sum(cell_cv**2, axis=1)**0.5 / self.h) N_c += 4-N_c % 4 # makes domain decomposition easier self.gd = GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize) self.assertEqual(self.gamma, np.all(~self.gd.pbc_c)) # What to do about kpoints? self.kpt_comm = kpt_comm if debug and world.rank == 0: comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \ self.gd.comm, self.kpt_comm]]) print '%d world, %d band, %d domain, %d kpt' % comm_sizes def tearDown(self): del self.ibzk_kc, self.ibzk_kv, self.bd, self.gd, self.kpt_comm # ================================= def verify_comm_sizes(self): if world.size == 1: return comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \ self.gd.comm, self.kpt_comm]]) self._parinfo = '%d world, %d band, %d domain, %d kpt' % comm_sizes self.assertEqual(self.nbands % self.bd.comm.size, 0) self.assertEqual((self.nspins*self.nibzkpts) % self.kpt_comm.size, 0) def verify_grid_volume(self): gdvol = np.prod(self.gd.get_size_of_global_array())*self.gd.dv self.assertAlmostEqual(self.gd.integrate(1+self.gd.zeros()), gdvol, 10) def verify_grid_point(self): # Volume integral of cartesian coordinates of all available grid points gdvol = np.prod(self.gd.get_size_of_global_array())*self.gd.dv cmr_v = self.gd.integrate(self.gd.get_grid_point_coordinates()) / gdvol # Theoretical center of cell based on all available grid data cm0_v = np.dot((0.5*(self.gd.get_size_of_global_array()-1.0) \ + 1.0-self.gd.pbc_c) / self.gd.N_c, self.gd.cell_cv) self.assertAlmostEqual(np.abs(cmr_v-cm0_v).max(), 0, 10) def verify_non_pbc_spacing(self): atoms = create_random_atoms(self.gd, 1000, 'NH3', self.a/2) pos_ac = atoms.get_positions() cellvol = np.linalg.det(self.gd.cell_cv) if debug: print 'cell volume:', np.abs(cellvol)*Bohr**3, 'Ang^3', cellvol>0 and '(right handed)' or '(left handed)' # Loop over non-periodic axes and check minimum distance requirement for c in np.argwhere(~self.gd.pbc_c).ravel(): a_v = self.gd.cell_cv[(c+1)%3] b_v = self.gd.cell_cv[(c+2)%3] c_v = np.cross(a_v, b_v) for d in range(2): # Inwards unit normal vector of d'th cell face of c'th axis # and point intersected by this plane (d=0,1 / bottom,top). n_v = np.sign(cellvol) * (1-2*d) * c_v / np.linalg.norm(c_v) if debug: print {0:'x',1:'y',2:'z'}[c]+'-'+{0:'bottom',1:'top'}[d]+':', n_v, 'Bohr' if debug: print 'gd.xxxiucell_cv[%d]~' % c, self.gd.xxxiucell_cv[c] / np.linalg.norm(self.gd.xxxiucell_cv[c]), 'Bohr' origin_v = np.dot(d * np.eye(3)[c], self.gd.cell_cv) d_a = np.dot(pos_ac/Bohr - origin_v[np.newaxis,:], n_v) if debug: print 'a:', self.a/2*Bohr, 'min:', np.min(d_a)*Bohr, 'max:', np.max(d_a)*Bohr self.assertAlmostEqual(d_a.min(), self.a/2, 0) #XXX digits!