示例#1
0
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
示例#2
0
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)
示例#3
0
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!
示例#4
0
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
示例#5
0
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!