Пример #1
0
 def create_symmetry(self, magmom_a, cell_cv):
     symm = self.parameters.symmetry
     if symm == 'off':
         symm = {'point_group': False, 'time_reversal': False}
     m_a = magmom_a.round(decimals=3)  # round off
     id_a = list(zip(self.setups.id_a, m_a))
     self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm)
     self.symmetry.analyze(self.atoms.get_scaled_positions())
     self.setups.set_symmetry(self.symmetry)
Пример #2
0
 def create_symmetry(self, magmom_av, cell_cv):
     symm = self.parameters.symmetry
     if symm == 'off':
         symm = {'point_group': False, 'time_reversal': False}
     m_av = magmom_av.round(decimals=3)  # round off
     id_a = [id + tuple(m_v) for id, m_v in zip(self.setups.id_a, m_av)]
     self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm)
     self.symmetry.analyze(self.spos_ac)
     self.setups.set_symmetry(self.symmetry)
Пример #3
0
    def set_symmetry(self, atoms, setups, usesymm, N_c=None):
        """Create symmetry object and construct irreducible Brillouin zone.

        Parameters
        ----------
        atoms: Atoms object
            Defines atom positions and types and also unit cell and
            boundary conditions.
        setups: instance of class Setups
            PAW setups for the atoms.
        usesymm: bool
            Symmetry flag.
        N_c: three int's or None
            If not None:  Check also symmetry of grid.
        """

        if (~atoms.pbc & self.bzk_kc.any(0)).any():
            raise ValueError('K-points can only be used with PBCs!')

        # Construct a Symmetry instance containing the identity operation
        # only
        # Round off
        magmom_a = atoms.get_initial_magnetic_moments().round(decimals=3)
        id_a = zip(magmom_a, setups.id_a)
        self.symmetry = Symmetry(id_a, atoms.cell / Bohr, atoms.pbc)
        
        if self.gamma or usesymm is None:
            # Point group and time-reversal symmetry neglected
            nkpts = len(self.bzk_kc)
            self.weight_k = np.ones(nkpts) / nkpts
            self.ibzk_kc = self.bzk_kc.copy()
            self.sym_k = np.zeros(nkpts)
            self.time_reversal_k = np.zeros(nkpts, bool)
            self.kibz_k = np.arange(nkpts)
        else:
            if usesymm:
                # Find symmetry operations of atoms
                self.symmetry.analyze(atoms.get_scaled_positions())
                
                if N_c is not None:
                    self.symmetry.prune_symmetries_grid(N_c)

            (self.ibzk_kc, self.weight_k,
             self.sym_k,
             self.time_reversal_k,
             self.kibz_k) = self.symmetry.reduce(self.bzk_kc)

        setups.set_symmetry(self.symmetry)

        # Number of irreducible k-points and k-point/spin combinations.
        self.nibzkpts = len(self.ibzk_kc)
        self.nks = self.nibzkpts * self.nspins
Пример #4
0
def get_lattice_symmetry(cell_cv, tolerance=1e-7):
    """Return symmetry object of lattice group.

    Parameters
    ----------
    cell_cv : ndarray
        Unit cell.

    Returns
    -------
    gpaw.symmetry object

    """
    latsym = Symmetry([0], cell_cv, tolerance=tolerance)
    latsym.find_lattice_symmetry()
    return latsym
Пример #5
0
def get_realspace_hs(h_skmm, s_kmm, bzk_kc, weight_k,
                     R_c=(0, 0, 0), direction='x', 
                     symmetry={'enabled': False}):

    from gpaw.symmetry import Symmetry
    from ase.dft.kpoints import get_monkhorst_pack_size_and_offset, \
        monkhorst_pack
    
    if symmetry['point_group'] is True:
        raise NotImplementedError, 'Point group symmetry not implemented'

    nspins, nk, nbf = h_skmm.shape[:3]
    dir = 'xyz'.index(direction)
    transverse_dirs = np.delete([0, 1, 2], [dir])
    dtype = float
    if len(bzk_kc) > 1 or np.any(bzk_kc[0] != [0, 0, 0]):
        dtype = complex

    kpts_grid = get_monkhorst_pack_size_and_offset(bzk_kc)[0]

    # kpts in the transport direction
    nkpts_p = kpts_grid[dir]
    bzk_p_kc = monkhorst_pack((nkpts_p,1,1))[:, 0]
    weight_p_k = 1. / nkpts_p

   # kpts in the transverse directions
    bzk_t_kc = monkhorst_pack(tuple(kpts_grid[transverse_dirs]) + (1, ))
    if not 'time_reversal' in symmetry:
        symmetry['time_reversal'] = True
    if symmetry['time_reversal'] is True:
        #XXX a somewhat ugly hack:
        # By default GPAW reduces inversion sym in the z direction. The steps 
        # below assure reduction in the transverse dirs.
        # For now this part seems to do the job, but it may be written
        # in a smarter way in the future.
        symmetry = Symmetry([1], np.eye(3))
        symmetry.prune_symmetries_atoms(np.zeros((1, 3)))
        ibzk_kc, ibzweight_k = symmetry.reduce(bzk_kc)[:2]
        ibzk_t_kc, weights_t_k = symmetry.reduce(bzk_t_kc)[:2]
        ibzk_t_kc = ibzk_t_kc[:, :2]
        nkpts_t = len(ibzk_t_kc)
    else:
        ibzk_kc = bzk_kc.copy()
        ibzk_t_kc = bzk_t_kc
        nkpts_t = len(bzk_t_kc)
        weights_t_k = [1. / nkpts_t for k in range(nkpts_t)]

    h_skii = np.zeros((nspins, nkpts_t, nbf, nbf), dtype)
    if s_kmm is not None:
        s_kii = np.zeros((nkpts_t, nbf, nbf), dtype)

    tol = 7
    for j, k_t in enumerate(ibzk_t_kc):
        for k_p in bzk_p_kc:
            k = np.zeros((3,))
            k[dir] = k_p
            k[transverse_dirs] = k_t
            kpoint_list = [list(np.round(k_kc, tol)) for k_kc in ibzk_kc]
            if list(np.round(k, tol)) not in kpoint_list:
                k = -k # inversion
                index = kpoint_list.index(list(np.round(k,tol)))
                h = h_skmm[:, index].conjugate()
                if s_kmm is not None:
                    s = s_kmm[index].conjugate()
                k=-k
            else: # kpoint in the ibz
                index = kpoint_list.index(list(np.round(k, tol)))
                h = h_skmm[:, index]
                if s_kmm is not None:
                    s = s_kmm[index]

            c_k = np.exp(2.j * np.pi * np.dot(k, R_c)) * weight_p_k
            h_skii[:, j] += c_k * h
            if s_kmm is not None:
                s_kii[j] += c_k * s 
    
    if s_kmm is None:
        return ibzk_t_kc, weights_t_k, h_skii
    else:
        return ibzk_t_kc, weights_t_k, h_skii, s_kii
    )
atoms.set_calculator(calc)
atoms.get_potential_energy()

# "Bandstructure" calculation (only Gamma point here)
kpts = np.array(((0, 0, 0),))
calc.set(fixdensity=True, kpts=kpts)
atoms.get_potential_energy()
calc.write('Si_gamma.gpw', mode='all')

# Analyse symmetries of wave functions
atoms, calc = restart('Si_gamma.gpw', txt=None)

# Find symmetries (in Gamma-point calculations calculator does not
# use symmetry)
sym = Symmetry(calc.wfs.setups.id_a, atoms.cell, atoms.pbc)
sym.analyze(atoms.get_scaled_positions())

def find_classes(op_all_scc):
    # Find classes of group represented by matrices op_all_scc
    # and return representative operations
    op_scc = [op_all_scc[0]]
    for op1 in op_all_scc[1:]:
        new_class = True
        for op2 in op_all_scc:
            op_tmp = (np.dot(np.dot(op2, op1), np.linalg.inv(op2).astype(int)))
            # Check whether operation op1 belongs to existing class 
            for op in op_scc:
                if np.all((op_tmp - op) == 0):
                    new_class = False
                    break
Пример #7
0
    def __init__(self,
                 calc,
                 gamma=True,
                 symmetry=False,
                 e_ph=False,
                 communicator=serial_comm):
        """Inititialize class with a list of atoms.

        The atoms object must contain a converged ground-state calculation.

        The set of q-vectors in which the dynamical matrix will be calculated
        is determined from the ``symmetry`` kwarg. For now, only time-reversal
        symmetry is used to generate the irrecducible BZ.

        Add a little note on parallelization strategy here.

        Parameters
        ----------
        calc: str or Calculator
            Calculator containing a ground-state calculation.
        gamma: bool
            Gamma-point calculation with respect to the q-vector of the
            dynamical matrix. When ``False``, the Monkhorst-Pack grid from the
            ground-state calculation is used.
        symmetry: bool
            Use symmetries to reduce the q-vectors of the dynamcial matrix
            (None, False or True). The different options are equivalent to the
            old style options in a ground-state calculation (see usesymm).
        e_ph: bool
            Save the derivative of the effective potential.
        communicator: Communicator
            Communicator for parallelization over k-points and real-space
            domain.
        """

        # XXX
        assert symmetry in [None, False], "Spatial symmetries not allowed yet"

        if isinstance(calc, str):
            self.calc = GPAW(calc, communicator=serial_comm, txt=None)
        else:
            self.calc = calc

        cell_cv = self.calc.atoms.get_cell()
        setups = self.calc.wfs.setups
        # XXX - no clue how to get magmom - ignore it for the moment
        # m_av = magmom_av.round(decimals=3)  # round off
        # id_a = zip(setups.id_a, *m_av.T)
        id_a = setups.id_a

        if symmetry is None:
            self.symmetry = Symmetry(id_a,
                                     cell_cv,
                                     point_group=False,
                                     time_reversal=False)
        else:
            self.symmetry = Symmetry(id_a,
                                     cell_cv,
                                     point_group=False,
                                     time_reversal=True)

        # Make sure localized functions are initialized
        self.calc.set_positions()
        # Note that this under some circumstances (e.g. when called twice)
        # allocates a new array for the P_ani coefficients !!

        # Store useful objects
        self.atoms = self.calc.get_atoms()
        # Get rid of ``calc`` attribute
        self.atoms.calc = None

        # Boundary conditions
        pbc_c = self.calc.atoms.get_pbc()

        if np.all(pbc_c == False):
            self.gamma = True
            self.dtype = float
            kpts = None
            # Multigrid Poisson solver
            poisson_solver = PoissonSolver()
        else:
            if gamma:
                self.gamma = True
                self.dtype = float
                kpts = None
            else:
                self.gamma = False
                self.dtype = complex
                # Get k-points from ground-state calculation
                kpts = self.calc.input_parameters.kpts

            # FFT Poisson solver
            poisson_solver = FFTPoissonSolver(dtype=self.dtype)

        # K-point descriptor for the q-vectors of the dynamical matrix
        # Note, no explicit parallelization here.
        self.kd = KPointDescriptor(kpts, 1)
        self.kd.set_symmetry(self.atoms, self.symmetry)
        self.kd.set_communicator(serial_comm)

        # Number of occupied bands
        nvalence = self.calc.wfs.nvalence
        nbands = nvalence // 2 + nvalence % 2
        assert nbands <= self.calc.wfs.bd.nbands

        # Extract other useful objects
        # Ground-state k-point descriptor - used for the k-points in the
        # ResponseCalculator
        # XXX replace communicators when ready to parallelize
        kd_gs = self.calc.wfs.kd
        gd = self.calc.density.gd
        kpt_u = self.calc.wfs.kpt_u
        setups = self.calc.wfs.setups
        dtype_gs = self.calc.wfs.dtype

        # WaveFunctions
        wfs = WaveFunctions(nbands, kpt_u, setups, kd_gs, gd, dtype=dtype_gs)

        # Linear response calculator
        self.response_calc = ResponseCalculator(self.calc,
                                                wfs,
                                                dtype=self.dtype)

        # Phonon perturbation
        self.perturbation = PhononPerturbation(self.calc,
                                               self.kd,
                                               poisson_solver,
                                               dtype=self.dtype)

        # Dynamical matrix
        self.dyn = DynamicalMatrix(self.atoms, self.kd, dtype=self.dtype)

        # Electron-phonon couplings
        if e_ph:
            self.e_ph = ElectronPhononCoupling(self.atoms,
                                               gd,
                                               self.kd,
                                               dtype=self.dtype)
        else:
            self.e_ph = None

        # Initialization flag
        self.initialized = False

        # Parallel communicator for parallelization over kpts and domain
        self.comm = communicator
Пример #8
0
class GPAW(Calculator, PAW):
    """This is the ASE-calculator frontend for doing a PAW calculation."""

    implemented_properties = [
        'energy', 'forces', 'stress', 'dipole', 'magmom', 'magmoms'
    ]

    default_parameters = {
        'mode': 'fd',
        'xc': 'LDA',
        'occupations': None,
        'poissonsolver': None,
        'h': None,  # Angstrom
        'gpts': None,
        'kpts': [(0.0, 0.0, 0.0)],
        'nbands': None,
        'charge': 0,
        'setups': {},
        'basis': {},
        'spinpol': None,
        'fixdensity': False,
        'filter': None,
        'mixer': None,
        'eigensolver': None,
        'background_charge': None,
        'external': None,
        'random': False,
        'hund': False,
        'maxiter': 333,
        'idiotproof': True,
        'symmetry': {
            'point_group': True,
            'time_reversal': True,
            'symmorphic': True,
            'tolerance': 1e-7
        },
        'convergence': {
            'energy': 0.0005,  # eV / electron
            'density': 1.0e-4,
            'eigenstates': 4.0e-8,  # eV^2
            'bands': 'occupied',
            'forces': np.inf
        },  # eV / Ang
        'dtype': None,  # Deprecated
        'width': None,  # Deprecated
        'verbose': 0
    }

    default_parallel = {
        'kpt': None,
        'domain': gpaw.parsize_domain,
        'band': gpaw.parsize_bands,
        'order': 'kdb',
        'stridebands': False,
        'augment_grids': gpaw.augment_grids,
        'sl_auto': False,
        'sl_default': gpaw.sl_default,
        'sl_diagonalize': gpaw.sl_diagonalize,
        'sl_inverse_cholesky': gpaw.sl_inverse_cholesky,
        'sl_lcao': gpaw.sl_lcao,
        'sl_lrtddft': gpaw.sl_lrtddft,
        'buffer_size': gpaw.buffer_size
    }

    def __init__(self,
                 restart=None,
                 ignore_bad_restart_file=False,
                 label=None,
                 atoms=None,
                 timer=None,
                 communicator=None,
                 txt='-',
                 parallel=None,
                 **kwargs):

        self.parallel = dict(self.default_parallel)
        if parallel:
            self.parallel.update(parallel)

        if timer is None:
            self.timer = Timer()
        else:
            self.timer = timer

        self.scf = None
        self.wfs = None
        self.occupations = None
        self.density = None
        self.hamiltonian = None

        self.observers = []  # XXX move to self.scf
        self.initialized = False

        self.world = communicator
        if self.world is None:
            self.world = mpi.world
        elif not hasattr(self.world, 'new_communicator'):
            self.world = mpi.world.new_communicator(np.asarray(self.world))

        self.log = GPAWLogger(world=self.world)
        self.log.fd = txt

        self.reader = None

        Calculator.__init__(self, restart, ignore_bad_restart_file, label,
                            atoms, **kwargs)

    def __del__(self):
        self.timer.write(self.log.fd)
        if self.reader is not None:
            self.reader.close()

    def write(self, filename, mode=''):
        self.log('Writing to {0} (mode={1!r})\n'.format(filename, mode))
        writer = Writer(filename, self.world)
        self._write(writer, mode)
        writer.close()
        self.world.barrier()

    def _write(self, writer, mode):
        from ase.io.trajectory import write_atoms
        writer.write(version=1,
                     gpaw_version=gpaw.__version__,
                     ha=Ha,
                     bohr=Bohr)

        write_atoms(writer.child('atoms'), self.atoms)
        writer.child('results').write(**self.results)
        writer.child('parameters').write(**self.todict())

        self.density.write(writer.child('density'))
        self.hamiltonian.write(writer.child('hamiltonian'))
        self.occupations.write(writer.child('occupations'))
        self.scf.write(writer.child('scf'))
        self.wfs.write(writer.child('wave_functions'), mode == 'all')

        return writer

    def read(self, filename):
        from ase.io.trajectory import read_atoms
        self.log('Reading from {0}'.format(filename))

        self.reader = reader = Reader(filename)

        self.atoms = read_atoms(reader.atoms)

        res = reader.results
        self.results = dict((key, res.get(key)) for key in res.keys())
        if self.results:
            self.log('Read {0}'.format(', '.join(sorted(self.results))))

        self.log('Reading input parameters:')
        self.parameters = self.get_default_parameters()
        dct = {}
        for key, value in reader.parameters.asdict().items():
            if (isinstance(value, dict)
                    and isinstance(self.parameters[key], dict)):
                self.parameters[key].update(value)
            else:
                self.parameters[key] = value
            dct[key] = self.parameters[key]

        self.log.print_dict(dct)
        self.log()

        self.initialize(reading=True)

        self.density.read(reader)
        self.hamiltonian.read(reader)
        self.occupations.read(reader)
        self.scf.read(reader)
        self.wfs.read(reader)

        # We need to do this in a better way:  XXX
        from gpaw.utilities.partition import AtomPartition
        atom_partition = AtomPartition(self.wfs.gd.comm,
                                       np.zeros(len(self.atoms), dtype=int))
        self.wfs.atom_partition = atom_partition
        self.density.atom_partition = atom_partition
        self.hamiltonian.atom_partition = atom_partition
        spos_ac = self.atoms.get_scaled_positions() % 1.0
        rank_a = self.density.gd.get_ranks_from_positions(spos_ac)
        new_atom_partition = AtomPartition(self.density.gd.comm, rank_a)
        for obj in [self.density, self.hamiltonian]:
            obj.set_positions_without_ruining_everything(
                spos_ac, new_atom_partition)

        self.hamiltonian.xc.read(reader)

        if self.hamiltonian.xc.name == 'GLLBSC':
            # XXX GLLB: See lcao/tdgllbsc.py test
            self.occupations.calculate(self.wfs)

        return reader

    def check_state(self, atoms, tol=1e-15):
        system_changes = Calculator.check_state(self, atoms, tol)
        if 'positions' not in system_changes:
            if self.hamiltonian:
                if self.hamiltonian.vext:
                    if self.hamiltonian.vext.vext_g is None:
                        # QMMM atoms have moved:
                        system_changes.append('positions')
        return system_changes

    def calculate(self,
                  atoms=None,
                  properties=['energy'],
                  system_changes=['cell']):
        """Calculate things."""

        Calculator.calculate(self, atoms)
        atoms = self.atoms

        if system_changes:
            self.log('System changes:', ', '.join(system_changes), '\n')
            if system_changes == ['positions']:
                # Only positions have changed:
                self.density.reset()
            else:
                # Drastic changes:
                self.wfs = None
                self.occupations = None
                self.density = None
                self.hamiltonian = None
                self.scf = None
                self.initialize(atoms)

            self.set_positions(atoms)

        if not self.initialized:
            self.initialize(atoms)
            self.set_positions(atoms)

        if not (self.wfs.positions_set and self.hamiltonian.positions_set):
            self.set_positions(atoms)

        if not self.scf.converged:
            print_cell(self.wfs.gd, self.atoms.pbc, self.log)

            with self.timer('SCF-cycle'):
                self.scf.run(self.wfs, self.hamiltonian, self.density,
                             self.occupations, self.log, self.call_observers)

            self.log('\nConverged after {0} iterations.\n'.format(
                self.scf.niter))

            e_free = self.hamiltonian.e_total_free
            e_extrapolated = self.hamiltonian.e_total_extrapolated
            self.results['energy'] = e_extrapolated * Ha
            self.results['free_energy'] = e_free * Ha

            if not self.atoms.pbc.all():
                dipole_v = self.density.calculate_dipole_moment() * Bohr
                self.log(
                    'Dipole moment: ({0:.6f}, {1:.6f}, {2:.6f}) |e|*Ang\n'.
                    format(*dipole_v))
                self.results['dipole'] = dipole_v

            if self.wfs.nspins == 2:
                magmom = self.occupations.magmom
                magmom_a = self.density.estimate_magnetic_moments(total=magmom)
                self.log('Total magnetic moment: %f' % magmom)
                self.log('Local magnetic moments:')
                symbols = self.atoms.get_chemical_symbols()
                for a, mom in enumerate(magmom_a):
                    self.log('{0:4} {1:2} {2:.6f}'.format(a, symbols[a], mom))
                self.log()
                self.results['magmom'] = self.occupations.magmom
                self.results['magmoms'] = magmom_a

            self.summary()

            self.call_observers(self.scf.niter, final=True)

        if 'forces' in properties:
            with self.timer('Forces'):
                F_av = calculate_forces(self.wfs, self.density,
                                        self.hamiltonian, self.log)
                self.results['forces'] = F_av * (Ha / Bohr)

        if 'stress' in properties:
            with self.timer('Stress'):
                try:
                    stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]]
                except NotImplementedError:
                    # Our ASE Calculator base class will raise
                    # PropertyNotImplementedError for us.
                    pass
                else:
                    self.results['stress'] = stress * (Ha / Bohr**3)

    def summary(self):
        self.hamiltonian.summary(self.occupations.fermilevel, self.log)
        self.density.summary(self.atoms, self.occupations.magmom, self.log)
        self.occupations.summary(self.log)
        self.wfs.summary(self.log)
        self.log.fd.flush()

    def set(self, **kwargs):
        """Change parameters for calculator.

        Examples::

            calc.set(xc='PBE')
            calc.set(nbands=20, kpts=(4, 1, 1))
        """

        changed_parameters = Calculator.set(self, **kwargs)

        for key in ['setups', 'basis']:
            if key in changed_parameters:
                dct = changed_parameters[key]
                if isinstance(dct, dict) and None in dct:
                    dct['default'] = dct.pop(None)
                    warnings.warn('Please use {key}={dct}'.format(key=key,
                                                                  dct=dct))

        # We need to handle txt early in order to get logging up and running:
        if 'txt' in changed_parameters:
            self.log.fd = changed_parameters.pop('txt')

        if not changed_parameters:
            return {}

        self.initialized = False
        self.scf = None
        self.results = {}

        self.log('Input parameters:')
        self.log.print_dict(changed_parameters)
        self.log()

        for key in changed_parameters:
            if key in ['eigensolver', 'convergence'] and self.wfs:
                self.wfs.set_eigensolver(None)

            if key in [
                    'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver',
                    'idiotproof'
            ]:
                continue

            if key in ['convergence', 'fixdensity', 'maxiter']:
                continue

            # More drastic changes:
            if self.wfs:
                self.wfs.set_orthonormalized(False)
            if key in ['external', 'xc', 'poissonsolver']:
                self.hamiltonian = None
            elif key in ['occupations', 'width']:
                pass
            elif key in ['charge', 'background_charge']:
                self.hamiltonian = None
                self.density = None
                self.wfs = None
            elif key in ['kpts', 'nbands', 'symmetry']:
                self.wfs = None
            elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']:
                self.density = None
                self.hamiltonian = None
                self.wfs = None
            elif key in ['basis']:
                self.wfs = None
            else:
                raise TypeError('Unknown keyword argument: "%s"' % key)

    def initialize_positions(self, atoms=None):
        """Update the positions of the atoms."""
        self.log('Initializing position-dependent things.\n')
        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        mpi.synchronize_atoms(atoms, self.world)

        spos_ac = atoms.get_scaled_positions() % 1.0

        rank_a = self.wfs.gd.get_ranks_from_positions(spos_ac)
        atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd')
        self.wfs.set_positions(spos_ac, atom_partition)
        self.density.set_positions(spos_ac, atom_partition)
        self.hamiltonian.set_positions(spos_ac, atom_partition)

        return spos_ac

    def set_positions(self, atoms=None):
        """Update the positions of the atoms and initialize wave functions."""
        spos_ac = self.initialize_positions(atoms)

        nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian,
                                           spos_ac)
        if nlcao + nrand:
            self.log('Creating initial wave functions:')
            if nlcao:
                self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set')
            if nrand:
                self.log(' ', plural(nrand, 'band'), 'from random numbers')
            self.log()

        self.wfs.eigensolver.reset()
        self.scf.reset()
        print_positions(self.atoms, self.log)

    def initialize(self, atoms=None, reading=False):
        """Inexpensive initialization."""

        self.log('Initialize ...\n')

        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        par = self.parameters

        natoms = len(atoms)

        cell_cv = atoms.get_cell() / Bohr
        number_of_lattice_vectors = cell_cv.any(axis=1).sum()
        if number_of_lattice_vectors < 3:
            raise ValueError(
                'GPAW requires 3 lattice vectors.  Your system has {0}.'.
                format(number_of_lattice_vectors))

        pbc_c = atoms.get_pbc()
        assert len(pbc_c) == 3
        magmom_a = atoms.get_initial_magnetic_moments()

        mpi.synchronize_atoms(atoms, self.world)

        # Generate new xc functional only when it is reset by set
        # XXX sounds like this should use the _changed_keywords dictionary.
        if self.hamiltonian is None or self.hamiltonian.xc is None:
            if isinstance(par.xc, basestring):
                xc = XC(par.xc)
            else:
                xc = par.xc
        else:
            xc = self.hamiltonian.xc

        mode = par.mode
        if isinstance(mode, basestring):
            mode = {'name': mode}
        if isinstance(mode, dict):
            mode = create_wave_function_mode(**mode)

        if par.dtype == complex:
            warnings.warn('Use mode={0}(..., force_complex_dtype=True) '
                          'instead of dtype=complex'.format(mode.name.upper()))
            mode.force_complex_dtype = True
            del par['dtype']
            par.mode = mode

        if xc.orbital_dependent and mode.name == 'lcao':
            raise ValueError('LCAO mode does not support '
                             'orbital-dependent XC functionals.')

        realspace = (mode.name != 'pw' and mode.interpolation != 'fft')

        if not realspace:
            pbc_c = np.ones(3, bool)

        self.create_setups(mode, xc)

        magnetic = magmom_a.any()

        spinpol = par.spinpol
        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True
            magmom_a[0] = self.setups[0].get_hunds_rule_moment(par.charge)

        if spinpol is None:
            spinpol = magnetic
        elif magnetic and not spinpol:
            raise ValueError('Non-zero initial magnetic moment for a ' +
                             'spin-paired calculation!')

        nspins = 1 + int(spinpol)

        if spinpol:
            self.log('Spin-polarized calculation.')
            self.log('Magnetic moment:  {0:.6f}\n'.format(magmom_a.sum()))
        else:
            self.log('Spin-paired calculation\n')

        if isinstance(par.background_charge, dict):
            background = create_background_charge(**par.background_charge)
        else:
            background = par.background_charge

        nao = self.setups.nao
        nvalence = self.setups.nvalence - par.charge
        if par.background_charge is not None:
            nvalence += background.charge
        M = abs(magmom_a.sum())

        nbands = par.nbands

        orbital_free = any(setup.orbital_free for setup in self.setups)
        if orbital_free:
            nbands = 1

        if isinstance(nbands, basestring):
            if nbands[-1] == '%':
                basebands = int(nvalence + M + 0.5) // 2
                nbands = int((float(nbands[:-1]) / 100) * basebands)
            else:
                raise ValueError('Integer expected: Only use a string '
                                 'if giving a percentage of occupied bands')

        if nbands is None:
            nbands = 0
            for setup in self.setups:
                nbands_from_atom = setup.get_default_nbands()

                # Any obscure setup errors?
                if nbands_from_atom < -(-setup.Nv // 2):
                    raise ValueError('Bad setup: This setup requests %d'
                                     ' bands but has %d electrons.' %
                                     (nbands_from_atom, setup.Nv))
                nbands += nbands_from_atom
            nbands = min(nao, nbands)
        elif nbands > nao and mode.name == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: '
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        if nbands <= 0:
            nbands = int(nvalence + M + 0.5) // 2 + (-nbands)

        if nvalence > 2 * nbands and not orbital_free:
            raise ValueError('Too few bands!  Electrons: %f, bands: %d' %
                             (nvalence, nbands))

        self.create_occupations(magmom_a.sum(), orbital_free)

        if self.scf is None:
            self.create_scf(nvalence, mode)

        self.create_symmetry(magmom_a, cell_cv)

        if not self.wfs:
            self.create_wave_functions(mode, realspace, nspins, nbands, nao,
                                       nvalence, self.setups, magmom_a,
                                       cell_cv, pbc_c)
        else:
            self.wfs.set_setups(self.setups)

        if not self.wfs.eigensolver:
            self.create_eigensolver(xc, nbands, mode)

        if self.density is None and not reading:
            assert not par.fixdensity, 'No density to fix!'

        olddens = None
        if (self.density is not None and
            (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()):
            # Domain decomposition has changed, so we need to
            # reinitialize density and hamiltonian:
            if par.fixdensity:
                olddens = self.density

            self.density = None
            self.hamiltonian = None

        if self.density is None:
            self.create_density(realspace, mode, background)

        # XXXXXXXXXX if setups change, then setups.core_charge may change.
        # But that parameter was supplied in Density constructor!
        # This surely is a bug!
        self.density.initialize(self.setups, self.timer, magmom_a, par.hund)
        self.density.set_mixer(par.mixer)
        if self.density.mixer.driver.name == 'dummy' or par.fixdensity:
            self.log('No density mixing\n')
        else:
            self.log(self.density.mixer, '\n')
        self.density.fixed = par.fixdensity
        self.density.log = self.log

        if olddens is not None:
            self.density.initialize_from_other_density(olddens,
                                                       self.wfs.kptband_comm)

        if self.hamiltonian is None:
            self.create_hamiltonian(realspace, mode, xc)

        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)

        if xc.name == 'GLLBSC' and olddens is not None:
            xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens)

        self.print_memory_estimate(maxdepth=memory_estimate_depth + 1)

        print_parallelization_details(self.wfs, self.density, self.log)

        self.log('Number of atoms:', natoms)
        self.log('Number of atomic orbitals:', self.wfs.setups.nao)
        if self.nbands_parallelization_adjustment != 0:
            self.log(
                'Adjusting number of bands by %+d to match parallelization' %
                self.nbands_parallelization_adjustment)
        self.log('Number of bands in calculation:', self.wfs.bd.nbands)
        self.log('Bands to converge: ', end='')
        n = par.convergence.get('bands', 'occupied')
        if n == 'occupied':
            self.log('occupied states only')
        elif n == 'all':
            self.log('all')
        else:
            self.log('%d lowest bands' % n)
        self.log('Number of valence electrons:', self.wfs.nvalence)

        self.log(flush=True)

        self.timer.print_info(self)

        if dry_run:
            self.dry_run()

        if (realspace and self.hamiltonian.poisson.get_description()
                == 'FDTD+TDDFT'):
            self.hamiltonian.poisson.set_density(self.density)
            self.hamiltonian.poisson.print_messages(self.log)
            self.log.fd.flush()

        self.initialized = True
        self.log('... initialized\n')

    def create_setups(self, mode, xc):
        if self.parameters.filter is None and mode.name != 'pw':
            gamma = 1.6
            N_c = self.parameters.get('gpts')
            if N_c is None:
                h = (self.parameters.h or 0.2) / Bohr
            else:
                icell_vc = np.linalg.inv(self.atoms.cell)
                h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr

            def filter(rgd, rcut, f_r, l=0):
                gcut = np.pi / h - 2 / rcut / gamma
                f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l)
        else:
            filter = self.parameters.filter

        Z_a = self.atoms.get_atomic_numbers()
        self.setups = Setups(Z_a, self.parameters.setups,
                             self.parameters.basis, xc, filter, self.world)
        self.log(self.setups)

    def create_grid_descriptor(self, N_c, cell_cv, pbc_c, domain_comm,
                               parsize_domain):
        return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain)

    def create_occupations(self, magmom, orbital_free):
        occ = self.parameters.occupations

        if occ is None:
            if orbital_free:
                occ = {'name': 'orbital-free'}
            else:
                width = self.parameters.width
                if width is not None:
                    warnings.warn(
                        'Please use occupations=FermiDirac({0})'.format(width))
                elif self.atoms.pbc.any():
                    width = 0.1  # eV
                else:
                    width = 0.0
                occ = {'name': 'fermi-dirac', 'width': width}

        if isinstance(occ, dict):
            occ = create_occupation_number_object(**occ)

        if self.parameters.fixdensity:
            occ.fixed_fermilevel = True
            if self.occupations:
                occ.fermilevel = self.occupations.fermilevel

        self.occupations = occ

        # If occupation numbers are changed, and we have wave functions,
        # recalculate the occupation numbers
        if self.wfs is not None:
            self.occupations.calculate(self.wfs)

        self.occupations.magmom = magmom

        self.log(self.occupations)

    def create_scf(self, nvalence, mode):
        if mode.name == 'lcao':
            niter_fixdensity = 0
        else:
            niter_fixdensity = 2

        nv = max(nvalence, 1)
        cc = self.parameters.convergence
        self.scf = SCFLoop(
            cc.get('eigenstates', 4.0e-8) / Ha**2 * nv,
            cc.get('energy', 0.0005) / Ha * nv,
            cc.get('density', 1.0e-4) * nv,
            cc.get('forces', np.inf) / (Ha / Bohr), self.parameters.maxiter,
            niter_fixdensity, nv)
        self.log(self.scf)

    def create_symmetry(self, magmom_a, cell_cv):
        symm = self.parameters.symmetry
        if symm == 'off':
            symm = {'point_group': False, 'time_reversal': False}
        m_a = magmom_a.round(decimals=3)  # round off
        id_a = list(zip(self.setups.id_a, m_a))
        self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm)
        self.symmetry.analyze(self.atoms.get_scaled_positions())
        self.setups.set_symmetry(self.symmetry)

    def create_eigensolver(self, xc, nbands, mode):
        # Number of bands to converge:
        nbands_converge = self.parameters.convergence.get('bands', 'occupied')
        if nbands_converge == 'all':
            nbands_converge = nbands
        elif nbands_converge != 'occupied':
            assert isinstance(nbands_converge, int)
            if nbands_converge < 0:
                nbands_converge += nbands
        eigensolver = get_eigensolver(self.parameters.eigensolver, mode,
                                      self.parameters.convergence)
        eigensolver.nbands_converge = nbands_converge
        # XXX Eigensolver class doesn't define an nbands_converge property

        if isinstance(xc, SIC):
            eigensolver.blocksize = 1

        self.wfs.set_eigensolver(eigensolver)

        self.log(self.wfs.eigensolver, '\n')

    def create_density(self, realspace, mode, background):
        gd = self.wfs.gd

        big_gd = gd.new_descriptor(comm=self.world)
        # Check whether grid is too small.  8 is smallest admissible.
        # (we decide this by how difficult it is to make the tests pass)
        # (Actually it depends on stencils!  But let the user deal with it)
        N_c = big_gd.get_size_of_global_array(pad=True)
        too_small = np.any(N_c / big_gd.parsize_c < 8)
        if self.parallel['augment_grids'] and not too_small:
            aux_gd = big_gd
        else:
            aux_gd = gd

        redistributor = GridRedistributor(self.world, self.wfs.kptband_comm,
                                          gd, aux_gd)

        # Construct grid descriptor for fine grids for densities
        # and potentials:
        finegd = aux_gd.refine()

        kwargs = dict(gd=gd,
                      finegd=finegd,
                      nspins=self.wfs.nspins,
                      charge=self.parameters.charge +
                      self.wfs.setups.core_charge,
                      redistributor=redistributor,
                      background_charge=background)

        if realspace:
            self.density = RealSpaceDensity(stencil=mode.interpolation,
                                            **kwargs)
        else:
            self.density = pw.ReciprocalSpaceDensity(**kwargs)

        self.log(self.density, '\n')

    def create_hamiltonian(self, realspace, mode, xc):
        dens = self.density
        kwargs = dict(gd=dens.gd,
                      finegd=dens.finegd,
                      nspins=dens.nspins,
                      setups=dens.setups,
                      timer=self.timer,
                      xc=xc,
                      world=self.world,
                      redistributor=dens.redistributor,
                      vext=self.parameters.external,
                      psolver=self.parameters.poissonsolver)
        if realspace:
            self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation,
                                                    **kwargs)
            xc.set_grid_descriptor(self.hamiltonian.finegd)  # XXX
        else:
            self.hamiltonian = pw.ReciprocalSpaceHamiltonian(
                pd2=dens.pd2, pd3=dens.pd3, realpbc_c=self.atoms.pbc, **kwargs)
            xc.set_grid_descriptor(dens.xc_redistributor.aux_gd)  # XXX

        self.log(self.hamiltonian, '\n')

    def create_wave_functions(self, mode, realspace, nspins, nbands, nao,
                              nvalence, setups, magmom_a, cell_cv, pbc_c):
        par = self.parameters

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kd = KPointDescriptor(bzkpts_kc, nspins)

        self.timer.start('Set symmetry')
        kd.set_symmetry(self.atoms, self.symmetry, comm=self.world)
        self.timer.stop('Set symmetry')

        self.log(kd)

        parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts)

        parsize_kpt = self.parallel['kpt']
        parsize_domain = self.parallel['domain']
        parsize_bands = self.parallel['band']

        ndomains = None
        if parsize_domain is not None:
            ndomains = np.prod(parsize_domain)
        if mode.name == 'pw':
            if ndomains is not None and ndomains > 1:
                raise ValueError('Planewave mode does not support '
                                 'domain decomposition.')
            ndomains = 1
        parallelization.set(kpt=parsize_kpt,
                            domain=ndomains,
                            band=parsize_bands)
        comms = parallelization.build_communicators()
        domain_comm = comms['d']
        kpt_comm = comms['k']
        band_comm = comms['b']
        kptband_comm = comms['D']
        domainband_comm = comms['K']

        self.comms = comms

        if par.gpts is not None:
            if par.h is not None:
                raise ValueError("""You can't use both "gpts" and "h"!""")
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace,
                                            kd.symmetry)

        self.symmetry.check_grid(N_c)

        kd.set_communicator(kpt_comm)

        parstride_bands = self.parallel['stridebands']

        # Unfortunately we need to remember that we adjusted the
        # number of bands so we can print a warning if it differs
        # from the number specified by the user.  (The number can
        # be inferred from the input parameters, but it's tricky
        # because we allow negative numbers)
        self.nbands_parallelization_adjustment = -nbands % band_comm.size
        nbands += self.nbands_parallelization_adjustment

        bd = BandDescriptor(nbands, band_comm, parstride_bands)

        # Construct grid descriptor for coarse grids for wave functions:
        gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm,
                                         parsize_domain)

        if hasattr(self, 'time') or mode.force_complex_dtype:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        wfs_kwargs = dict(gd=gd,
                          nvalence=nvalence,
                          setups=setups,
                          bd=bd,
                          dtype=dtype,
                          world=self.world,
                          kd=kd,
                          kptband_comm=kptband_comm,
                          timer=self.timer)

        if self.parallel['sl_auto']:
            # Choose scalapack parallelization automatically

            for key, val in self.parallel.items():
                if (key.startswith('sl_') and key != 'sl_auto'
                        and val is not None):
                    raise ValueError("Cannot use 'sl_auto' together "
                                     "with '%s'" % key)
            max_scalapack_cpus = bd.comm.size * gd.comm.size
            nprow = max_scalapack_cpus
            npcol = 1

            # Get a sort of reasonable number of columns/rows
            while npcol < nprow and nprow % 2 == 0:
                npcol *= 2
                nprow //= 2
            assert npcol * nprow == max_scalapack_cpus

            # ScaLAPACK creates trouble if there aren't at least a few
            # whole blocks; choose block size so there will always be
            # several blocks.  This will crash for small test systems,
            # but so will ScaLAPACK in any case
            blocksize = min(-(-nbands // 4), 64)
            sl_default = (nprow, npcol, blocksize)
        else:
            sl_default = self.parallel['sl_default']

        if mode.name == 'lcao':
            # Layouts used for general diagonalizer
            sl_lcao = self.parallel['sl_lcao']
            if sl_lcao is None:
                sl_lcao = sl_default
            lcaoksl = get_KohnSham_layouts(sl_lcao,
                                           'lcao',
                                           gd,
                                           bd,
                                           domainband_comm,
                                           dtype,
                                           nao=nao,
                                           timer=self.timer)

            self.wfs = mode(lcaoksl, **wfs_kwargs)

        elif mode.name == 'fd' or mode.name == 'pw':
            # buffer_size keyword only relevant for fdpw
            buffer_size = self.parallel['buffer_size']
            # Layouts used for diagonalizer
            sl_diagonalize = self.parallel['sl_diagonalize']
            if sl_diagonalize is None:
                sl_diagonalize = sl_default
            diagksl = get_KohnSham_layouts(
                sl_diagonalize,
                'fd',  # XXX
                # choice of key 'fd' not so nice
                gd,
                bd,
                domainband_comm,
                dtype,
                buffer_size=buffer_size,
                timer=self.timer)

            # Layouts used for orthonormalizer
            sl_inverse_cholesky = self.parallel['sl_inverse_cholesky']
            if sl_inverse_cholesky is None:
                sl_inverse_cholesky = sl_default
            if sl_inverse_cholesky != sl_diagonalize:
                message = 'sl_inverse_cholesky != sl_diagonalize ' \
                    'is not implemented.'
                raise NotImplementedError(message)
            orthoksl = get_KohnSham_layouts(sl_inverse_cholesky,
                                            'fd',
                                            gd,
                                            bd,
                                            domainband_comm,
                                            dtype,
                                            buffer_size=buffer_size,
                                            timer=self.timer)

            # Use (at most) all available LCAO for initialization
            lcaonbands = min(nbands, nao // band_comm.size * band_comm.size)

            try:
                lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands)
            except RuntimeError:
                initksl = None
            else:
                # Layouts used for general diagonalizer
                # (LCAO initialization)
                sl_lcao = self.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                initksl = get_KohnSham_layouts(sl_lcao,
                                               'lcao',
                                               gd,
                                               lcaobd,
                                               domainband_comm,
                                               dtype,
                                               nao=nao,
                                               timer=self.timer)

            self.wfs = mode(diagksl, orthoksl, initksl, **wfs_kwargs)
        else:
            self.wfs = mode(self, **wfs_kwargs)

        self.log(self.wfs, '\n')

    def dry_run(self):
        # Can be overridden like in gpaw.atom.atompaw
        print_cell(self.wfs.gd, self.atoms.pbc, self.log)
        print_positions(self.atoms, self.log)
        self.log.fd.flush()
        raise SystemExit
Пример #9
0
class KPointDescriptor:
    """Descriptor-class for k-points."""

    def __init__(self, kpts, nspins):
        """Construct descriptor object for kpoint/spin combinations (ks-pair).

        Parameters
        ----------
        kpts: None, list of ints, or ndarray
            Specification of the k-point grid. None=Gamma, list of
            ints=Monkhorst-Pack, ndarray=user specified.
        nspins: int
            Number of spins.

        Attributes
        ============  ======================================================
        ``N_c``       Number of k-points in the different directions.
        ``nspins``    Number of spins.
        ``nibzkpts``  Number of irreducible kpoints in 1st Brillouin zone.
        ``nks``       Number of k-point/spin combinations in total.
        ``mynks``     Number of k-point/spin combinations on this CPU.
        ``gamma``     Boolean indicator for gamma point calculation.
        ``comm``      MPI-communicator for kpoint distribution.
        ============  ======================================================
        
        """

        if kpts is None:
            self.bzk_kc = np.zeros((1, 3))
            self.N_c = np.array((1, 1, 1), dtype=int)
        elif isinstance(kpts[0], int):
            self.bzk_kc = monkhorst_pack(kpts)
            self.N_c = np.array(kpts, dtype=int)
        else:
            self.bzk_kc = np.array(kpts)
            self.N_c = None

        self.nspins = nspins
        self.nbzkpts = len(self.bzk_kc)
        
        # Gamma-point calculation
        self.gamma = self.nbzkpts == 1 and not self.bzk_kc[0].any()

        self.symmetry = None
        self.comm = None
        self.ibzk_kc = None
        self.weight_k = None
        self.nibzkpts = None

        self.rank0 = None
        self.mynks = None
        self.ks0 = None
        self.ibzk_qc = None

    def __len__(self):
        """Return number of k-point/spin combinations of local CPU."""
        
        return self.mynks

    def set_symmetry(self, atoms, setups, usesymm, N_c=None):
        """Create symmetry object and construct irreducible Brillouin zone.

        Parameters
        ----------
        atoms: Atoms object
            Defines atom positions and types and also unit cell and
            boundary conditions.
        setups: instance of class Setups
            PAW setups for the atoms.
        usesymm: bool
            Symmetry flag.
        N_c: three int's or None
            If not None:  Check also symmetry of grid.
        """

        if (~atoms.pbc & self.bzk_kc.any(0)).any():
            raise ValueError('K-points can only be used with PBCs!')

        # Construct a Symmetry instance containing the identity operation
        # only
        # Round off
        magmom_a = atoms.get_initial_magnetic_moments().round(decimals=3)
        id_a = zip(magmom_a, setups.id_a)
        self.symmetry = Symmetry(id_a, atoms.cell / Bohr, atoms.pbc)
        
        if self.gamma or usesymm is None:
            # Point group and time-reversal symmetry neglected
            nkpts = len(self.bzk_kc)
            self.weight_k = np.ones(nkpts) / nkpts
            self.ibzk_kc = self.bzk_kc.copy()
            self.sym_k = np.zeros(nkpts)
            self.time_reversal_k = np.zeros(nkpts, bool)
            self.kibz_k = np.arange(nkpts)
        else:
            if usesymm:
                # Find symmetry operations of atoms
                self.symmetry.analyze(atoms.get_scaled_positions())
                
                if N_c is not None:
                    self.symmetry.prune_symmetries_grid(N_c)

            (self.ibzk_kc, self.weight_k,
             self.sym_k,
             self.time_reversal_k,
             self.kibz_k) = self.symmetry.reduce(self.bzk_kc)

        setups.set_symmetry(self.symmetry)

        # Number of irreducible k-points and k-point/spin combinations.
        self.nibzkpts = len(self.ibzk_kc)
        self.nks = self.nibzkpts * self.nspins
        
    def set_communicator(self, comm):
        """Set k-point communicator."""

        # Ranks < self.rank0 have mynks0 k-point/spin combinations and
        # ranks >= self.rank0 have mynks0+1 k-point/spin combinations.
        mynks0, x = divmod(self.nks, comm.size)
        self.rank0 = comm.size - x
        self.comm = comm

        # My number and offset of k-point/spin combinations
        self.mynks, self.ks0 = self.get_count(), self.get_offset()

        if self.nspins == 2 and comm.size == 1:
            # Avoid duplicating k-points in local list of k-points.
            self.ibzk_qc = self.ibzk_kc.copy()
        else:
            self.ibzk_qc = np.vstack((self.ibzk_kc,
                                      self.ibzk_kc))[self.get_slice()]

    def create_k_points(self, gd):
        """Return a list of KPoints."""
 
        sdisp_cd = gd.sdisp_cd

        kpt_u = []

        for ks in range(self.ks0, self.ks0 + self.mynks):
            s, k = divmod(ks, self.nibzkpts)
            q = (ks - self.ks0) % self.nibzkpts
            weight = self.weight_k[k] * 2 / self.nspins
            if self.gamma:
                phase_cd = np.ones((3, 2), complex)
            else:
                phase_cd = np.exp(2j * np.pi *
                                  sdisp_cd * self.ibzk_kc[k, :, np.newaxis])
            kpt_u.append(KPoint(weight, s, k, q, phase_cd))

        return kpt_u

    def transform_wave_function(self, psit_G, k):
        """Transform wave function from IBZ to BZ.

        k is the index of the desired k-point in the full BZ."""
        
        s = self.sym_k[k]
        time_reversal = self.time_reversal_k[k]
        op_cc = np.linalg.inv(self.symmetry.op_scc[s]).round().astype(int)

        # Identity
        if (np.abs(op_cc - np.eye(3, dtype=int)) < 1e-10).all():
            if time_reversal:
                return psit_G.conj()
            else:
                return psit_G
        # Inversion symmetry
        elif (np.abs(op_cc + np.eye(3, dtype=int)) < 1e-10).all():
            return psit_G.conj()
        # General point group symmetry
        else:
            ik = self.kibz_k[k]
            kibz_c = self.ibzk_kc[ik]
            kbz_c = self.bzk_kc[k]            
            import _gpaw
            b_g = np.zeros_like(psit_G)
            if time_reversal:
                # assert abs(np.dot(op_cc, kibz_c) - -kbz_c) < tol
                _gpaw.symmetrize_wavefunction(psit_G, b_g, op_cc.copy(),
                                              kibz_c, -kbz_c)
                return b_g.conj()
            else:
                # assert abs(np.dot(op_cc, kibz_c) - kbz_c) < tol
                _gpaw.symmetrize_wavefunction(psit_G, b_g, op_cc.copy(),
                                              kibz_c, kbz_c)
                return b_g

    def find_k_plus_q(self, q_c):
        """Find the indices of k+q for all kpoints in the Brillouin zone.

        In case that k+q is outside the BZ, the k-point inside the BZ
        corresponding to k+q is given.
        
        Parameters
        ----------
        q_c: ndarray
            Coordinates for the q-vector in units of the reciprocal
            lattice vectors.

        """

        # Monkhorst-pack grid
        if self.N_c is not None:
            N_c = self.N_c
            dk_c = 1. / N_c
            kmax_c = (N_c - 1) * dk_c / 2.

        N = np.zeros(3, dtype=int)

        # k+q vectors
        kplusq_kc = self.bzk_kc + q_c
        # Translate back into the first BZ
        kplusq_kc[np.where(kplusq_kc > 0.5)] -= 1.
        kplusq_kc[np.where(kplusq_kc <= -0.5)] += 1.

        # List of k+q indices
        kplusq_k = []
        
        # Find index of k+q vector in the bzk_kc attribute
        for kplusq, kplusq_c in enumerate(kplusq_kc):

            # Calculate index for Monkhorst-Pack grids
            if self.N_c is not None:
                N = np.asarray(np.round((kplusq_c + kmax_c) / dk_c),
                               dtype=int)
                kplusq_k.append(N[2] + N[1] * N_c[2] +
                                N[0] * N_c[2] * N_c[1])
            else:
                k = np.argmin(np.sum(np.abs(self.bzk_kc - kplusq_c), axis=1))
                kplusq_k.append(k)

            # Check the k+q vector index
            k_c = self.bzk_kc[kplusq_k[kplusq]]
            assert abs(kplusq_c - k_c).sum() < 1e-8, "Could not find k+q!"
        
        return kplusq_k

    def get_bz_q_points(self):
        """Return the q=k1-k2."""

        bzk_kc = self.bzk_kc
        # Get all q-points
        all_qs = []
        for k1 in bzk_kc:
            for k2 in bzk_kc:
                all_qs.append(k1-k2)
        all_qs = np.array(all_qs)

        # Fold q-points into Brillouin zone
        all_qs[np.where(all_qs > 0.501)] -= 1.
        all_qs[np.where(all_qs < -0.499)] += 1.

        # Make list of non-identical q-points in full BZ
        bz_qs = [all_qs[0]]
        for q_a in all_qs:
            q_in_list = False
            for q_b in bz_qs:
                if (abs(q_a[0]-q_b[0]) < 0.01 and
                    abs(q_a[1]-q_b[1]) < 0.01 and
                    abs(q_a[2]-q_b[2]) < 0.01):
                    q_in_list = True
                    break
            if q_in_list == False:
                bz_qs.append(q_a)
        self.bzq_kc = bz_qs

        return

    def where_is_q(self, q_c):
        """Find the index of q points."""
        q_c[np.where(q_c>0.499)] -= 1
        q_c[np.where(q_c<-0.499)] += 1

        found = False
        for ik in range(self.nbzkpts):
            if (np.abs(self.bzq_kc[ik] - q_c) < 1e-8).all():
                found = True
                return ik
                break
            
        if found is False:
            print self.bzq_kc, q_c
            raise ValueError('q-points can not be found!')
        

    def get_count(self, rank=None):
        """Return the number of ks-pairs which belong to a given rank."""

        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        mynks0 = self.nks // self.comm.size
        mynks = mynks0
        if rank >= self.rank0:
            mynks += 1
        return mynks

    def get_offset(self, rank=None):
        """Return the offset of the first ks-pair on a given rank."""

        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        mynks0 = self.nks // self.comm.size
        ks0 = rank * mynks0
        if rank >= self.rank0:
            ks0 += rank - self.rank0
        return ks0

    def get_rank_and_index(self, s, k):
        """Find rank and local index of k-point/spin combination."""
        
        u = self.where_is(s, k)
        rank, myu = self.who_has(u)
        return rank, myu
   
    def get_slice(self, rank=None):
        """Return the slice of global ks-pairs which belong to a given rank."""
        
        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        mynks, ks0 = self.get_count(rank), self.get_offset(rank)
        uslice = slice(ks0, ks0 + mynks)
        return uslice

    def get_indices(self, rank=None):
        """Return the global ks-pair indices which belong to a given rank."""
        
        uslice = self.get_slice(rank)
        return np.arange(*uslice.indices(self.nks))

    def get_ranks(self):
        """Return array of ranks as a function of global ks-pair indices."""
        
        ranks = np.empty(self.nks, dtype=int)
        for rank in range(self.comm.size):
            uslice = self.get_slice(rank)
            ranks[uslice] = rank
        assert (ranks >= 0).all() and (ranks < self.comm.size).all()
        return ranks

    def who_has(self, u):
        """Convert global index to rank information and local index."""

        mynks0 = self.nks // self.comm.size
        if u < mynks0 * self.rank0:
            rank, myu = divmod(u, mynks0)
        else:
            rank, myu = divmod(u - mynks0 * self.rank0, mynks0 + 1)
            rank += self.rank0
        return rank, myu

    def global_index(self, myu, rank=None):
        """Convert rank information and local index to global index."""
        
        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        ks0 = self.get_offset(rank)
        u = ks0 + myu
        return u

    def what_is(self, u):
        """Convert global index to corresponding kpoint/spin combination."""
        
        s, k = divmod(u, self.nibzkpts)
        return s, k

    def where_is(self, s, k):
        """Convert kpoint/spin combination to the global index thereof."""
        
        u = k + self.nibzkpts * s
        return u
Пример #10
0
import numpy as np

from gpaw.symmetry import Symmetry
from ase.dft.kpoints import monkhorst_pack

# Primitive diamond lattice, with Si lattice parameter
a = 5.475
cell_cv = .5 * a * np.array([(1, 1, 0), (1, 0, 1), (0, 1, 1)])
spos_ac = np.array([(.00, .00, .00),
                    (.25, .25, .25)])
id_a = [1, 1] # Two identical atoms
pbc_c = np.ones(3, bool)
bzk_kc = monkhorst_pack((4, 4, 4))

# Do check
symm = Symmetry(id_a, cell_cv, pbc_c, fractrans=False)
symm.analyze(spos_ac)
ibzk_kc, w_k = symm.reduce(bzk_kc)[:2]
assert len(symm.op_scc) == 24
assert len(w_k) == 10
a = 3 / 32.; b = 1 / 32.; c = 6 / 32.
assert np.all(w_k == [a, b, a, c, c, a, a, a, a, b])
assert not symm.op_scc.sum(0).any()

# Rotate unit cell and check again:
cell_cv = a / sqrt(2) * np.array([(1, 0, 0),
                                  (0.5, sqrt(3) / 2, 0),
                                  (0.5, sqrt(3) / 6, sqrt(2.0 / 3))])
symm = Symmetry(id_a, cell_cv, pbc_c, fractrans=False)
symm.analyze(spos_ac)
ibzkb_kc, wb_k = symm.reduce(bzk_kc)[:2]
Пример #11
0
class GPAW(PAW, Calculator):
    """This is the ASE-calculator frontend for doing a PAW calculation."""

    implemented_properties = [
        'energy', 'forces', 'stress', 'dipole', 'magmom', 'magmoms'
    ]

    default_parameters = {
        'mode': 'fd',
        'xc': 'LDA',
        'occupations': None,
        'poissonsolver': None,
        'h': None,  # Angstrom
        'gpts': None,
        'kpts': [(0.0, 0.0, 0.0)],
        'nbands': None,
        'charge': 0,
        'setups': {},
        'basis': {},
        'spinpol': None,
        'fixdensity': False,
        'filter': None,
        'mixer': None,
        'eigensolver': None,
        'background_charge': None,
        'experimental': {
            'reuse_wfs_method': 'paw',
            'niter_fixdensity': 0,
            'magmoms': None,
            'soc': None,
            'kpt_refine': None
        },
        'external': None,
        'random': False,
        'hund': False,
        'maxiter': 333,
        'idiotproof': True,
        'symmetry': {
            'point_group': True,
            'time_reversal': True,
            'symmorphic': True,
            'tolerance': 1e-7,
            'do_not_symmetrize_the_density': False
        },
        'convergence': {
            'energy': 0.0005,  # eV / electron
            'density': 1.0e-4,
            'eigenstates': 4.0e-8,  # eV^2
            'bands': 'occupied',
            'forces': np.inf
        },  # eV / Ang
        'dtype': None,  # Deprecated
        'width': None,  # Deprecated
        'verbose': 0
    }

    default_parallel = {
        'kpt': None,
        'domain': gpaw.parsize_domain,
        'band': gpaw.parsize_bands,
        'order': 'kdb',
        'stridebands': False,
        'augment_grids': gpaw.augment_grids,
        'sl_auto': False,
        'sl_default': gpaw.sl_default,
        'sl_diagonalize': gpaw.sl_diagonalize,
        'sl_inverse_cholesky': gpaw.sl_inverse_cholesky,
        'sl_lcao': gpaw.sl_lcao,
        'sl_lrtddft': gpaw.sl_lrtddft,
        'use_elpa': False,
        'elpasolver': '2stage',
        'buffer_size': gpaw.buffer_size
    }

    def __init__(self,
                 restart=None,
                 ignore_bad_restart_file=False,
                 label=None,
                 atoms=None,
                 timer=None,
                 communicator=None,
                 txt='-',
                 parallel=None,
                 **kwargs):

        self.parallel = dict(self.default_parallel)
        if parallel:
            for key in parallel:
                if key not in self.default_parallel:
                    allowed = ', '.join(list(self.default_parallel.keys()))
                    raise TypeError('Unexpected keyword "{}" in "parallel" '
                                    'dictionary.  Must be one of: {}'.format(
                                        key, allowed))
            self.parallel.update(parallel)

        if timer is None:
            self.timer = Timer()
        else:
            self.timer = timer

        self.scf = None
        self.wfs = None
        self.occupations = None
        self.density = None
        self.hamiltonian = None
        self.spos_ac = None  # XXX store this in some better way.

        self.observers = []  # XXX move to self.scf
        self.initialized = False

        self.world = communicator
        if self.world is None:
            self.world = mpi.world
        elif not hasattr(self.world, 'new_communicator'):
            self.world = mpi.world.new_communicator(np.asarray(self.world))

        self.log = GPAWLogger(world=self.world)
        self.log.fd = txt

        self.reader = None

        Calculator.__init__(self, restart, ignore_bad_restart_file, label,
                            atoms, **kwargs)

    def __del__(self):
        # Write timings and close reader if necessary.

        # If we crashed in the constructor (e.g. a bad keyword), we may not
        # have the normally expected attributes:
        if hasattr(self, 'timer'):
            self.timer.write(self.log.fd)

        if hasattr(self, 'reader') and self.reader is not None:
            self.reader.close()

    def write(self, filename, mode=''):
        self.log('Writing to {} (mode={!r})\n'.format(filename, mode))
        writer = Writer(filename, self.world)
        self._write(writer, mode)
        writer.close()
        self.world.barrier()

    def _write(self, writer, mode):
        from ase.io.trajectory import write_atoms
        writer.write(version=1,
                     gpaw_version=gpaw.__version__,
                     ha=Ha,
                     bohr=Bohr)

        write_atoms(writer.child('atoms'), self.atoms)
        writer.child('results').write(**self.results)
        writer.child('parameters').write(**self.todict())

        self.density.write(writer.child('density'))
        self.hamiltonian.write(writer.child('hamiltonian'))
        self.occupations.write(writer.child('occupations'))
        self.scf.write(writer.child('scf'))
        self.wfs.write(writer.child('wave_functions'), mode == 'all')

        return writer

    def _set_atoms(self, atoms):
        check_atoms_too_close(atoms)
        self.atoms = atoms
        # GPAW works in terms of the scaled positions.  We want to
        # extract the scaled positions in only one place, and that is
        # here.  No other place may recalculate them, or we might end up
        # with rounding errors and inconsistencies.
        self.spos_ac = atoms.get_scaled_positions() % 1.0

    def read(self, filename):
        from ase.io.trajectory import read_atoms
        self.log('Reading from {}'.format(filename))

        self.reader = reader = Reader(filename)

        atoms = read_atoms(reader.atoms)
        self._set_atoms(atoms)

        res = reader.results
        self.results = dict((key, res.get(key)) for key in res.keys())
        if self.results:
            self.log('Read {}'.format(', '.join(sorted(self.results))))

        self.log('Reading input parameters:')
        # XXX param
        self.parameters = self.get_default_parameters()
        dct = {}
        for key, value in reader.parameters.asdict().items():
            if (isinstance(value, dict)
                    and isinstance(self.parameters[key], dict)):
                self.parameters[key].update(value)
            else:
                self.parameters[key] = value
            dct[key] = self.parameters[key]

        self.log.print_dict(dct)
        self.log()

        self.initialize(reading=True)

        self.density.read(reader)
        self.hamiltonian.read(reader)
        self.occupations.read(reader)
        self.scf.read(reader)
        self.wfs.read(reader)

        # We need to do this in a better way:  XXX
        from gpaw.utilities.partition import AtomPartition
        atom_partition = AtomPartition(self.wfs.gd.comm,
                                       np.zeros(len(self.atoms), dtype=int))
        self.wfs.atom_partition = atom_partition
        self.density.atom_partition = atom_partition
        self.hamiltonian.atom_partition = atom_partition
        rank_a = self.density.gd.get_ranks_from_positions(self.spos_ac)
        new_atom_partition = AtomPartition(self.density.gd.comm, rank_a)
        for obj in [self.density, self.hamiltonian]:
            obj.set_positions_without_ruining_everything(
                self.spos_ac, new_atom_partition)

        self.hamiltonian.xc.read(reader)

        if self.hamiltonian.xc.name == 'GLLBSC':
            # XXX GLLB: See test/lcaotddft/gllbsc.py
            self.occupations.calculate(self.wfs)

        return reader

    def check_state(self, atoms, tol=1e-15):
        system_changes = Calculator.check_state(self, atoms, tol)
        if 'positions' not in system_changes:
            if self.hamiltonian:
                if self.hamiltonian.vext:
                    if self.hamiltonian.vext.vext_g is None:
                        # QMMM atoms have moved:
                        system_changes.append('positions')
        return system_changes

    def calculate(self,
                  atoms=None,
                  properties=['energy'],
                  system_changes=['cell']):
        """Calculate things."""

        Calculator.calculate(self, atoms)
        atoms = self.atoms

        if system_changes:
            self.log('System changes:', ', '.join(system_changes), '\n')
            if system_changes == ['positions']:
                # Only positions have changed:
                self.density.reset()
            else:
                # Drastic changes:
                self.wfs = None
                self.occupations = None
                self.density = None
                self.hamiltonian = None
                self.scf = None
                self.initialize(atoms)

            self.set_positions(atoms)

        if not self.initialized:
            self.initialize(atoms)
            self.set_positions(atoms)

        if not (self.wfs.positions_set and self.hamiltonian.positions_set):
            self.set_positions(atoms)

        if not self.scf.converged:
            print_cell(self.wfs.gd, self.atoms.pbc, self.log)

            with self.timer('SCF-cycle'):
                self.scf.run(self.wfs, self.hamiltonian, self.density,
                             self.occupations, self.log, self.call_observers)

            self.log('\nConverged after {} iterations.\n'.format(
                self.scf.niter))

            e_free = self.hamiltonian.e_total_free
            e_extrapolated = self.hamiltonian.e_total_extrapolated
            self.results['energy'] = e_extrapolated * Ha
            self.results['free_energy'] = e_free * Ha

            dipole_v = self.density.calculate_dipole_moment() * Bohr
            self.log(
                'Dipole moment: ({:.6f}, {:.6f}, {:.6f}) |e|*Ang\n'.format(
                    *dipole_v))
            self.results['dipole'] = dipole_v

            if self.wfs.nspins == 2 or not self.density.collinear:
                totmom_v, magmom_av = self.density.estimate_magnetic_moments()
                self.log(
                    'Total magnetic moment: ({:.6f}, {:.6f}, {:.6f})'.format(
                        *totmom_v))
                self.log('Local magnetic moments:')
                symbols = self.atoms.get_chemical_symbols()
                for a, mom_v in enumerate(magmom_av):
                    self.log('{:4} {:2} ({:9.6f}, {:9.6f}, {:9.6f})'.format(
                        a, symbols[a], *mom_v))
                self.log()
                self.results['magmom'] = self.occupations.magmom
                self.results['magmoms'] = magmom_av[:, 2].copy()

            self.summary()

            self.call_observers(self.scf.niter, final=True)

        if 'forces' in properties:
            with self.timer('Forces'):
                F_av = calculate_forces(self.wfs, self.density,
                                        self.hamiltonian, self.log)
                self.results['forces'] = F_av * (Ha / Bohr)

        if 'stress' in properties:
            with self.timer('Stress'):
                try:
                    stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]]
                except NotImplementedError:
                    # Our ASE Calculator base class will raise
                    # PropertyNotImplementedError for us.
                    pass
                else:
                    self.results['stress'] = stress * (Ha / Bohr**3)

    def summary(self):
        efermi = self.occupations.fermilevel
        self.hamiltonian.summary(efermi, self.log)
        self.density.summary(self.atoms, self.occupations.magmom, self.log)
        self.occupations.summary(self.log)
        self.wfs.summary(self.log)
        try:
            bandgap(self, output=self.log.fd, efermi=efermi * Ha)
        except ValueError:
            pass
        self.log.fd.flush()

    def set(self, **kwargs):
        """Change parameters for calculator.

        Examples::

            calc.set(xc='PBE')
            calc.set(nbands=20, kpts=(4, 1, 1))
        """

        # Verify that keys are consistent with default ones.
        for key in kwargs:
            if key != 'txt' and key not in self.default_parameters:
                raise TypeError('Unknown GPAW parameter: {}'.format(key))

            if key in ['convergence', 'symmetry', 'experimental'
                       ] and isinstance(kwargs[key], dict):
                # For values that are dictionaries, verify subkeys, too.
                default_dict = self.default_parameters[key]
                for subkey in kwargs[key]:
                    if subkey not in default_dict:
                        allowed = ', '.join(list(default_dict.keys()))
                        raise TypeError('Unknown subkeyword "{}" of keyword '
                                        '"{}".  Must be one of: {}'.format(
                                            subkey, key, allowed))

        changed_parameters = Calculator.set(self, **kwargs)

        for key in ['setups', 'basis']:
            if key in changed_parameters:
                dct = changed_parameters[key]
                if isinstance(dct, dict) and None in dct:
                    dct['default'] = dct.pop(None)
                    warnings.warn('Please use {key}={dct}'.format(key=key,
                                                                  dct=dct))

        # We need to handle txt early in order to get logging up and running:
        if 'txt' in changed_parameters:
            self.log.fd = changed_parameters.pop('txt')

        if not changed_parameters:
            return {}

        self.initialized = False
        self.scf = None
        self.results = {}

        self.log('Input parameters:')
        self.log.print_dict(changed_parameters)
        self.log()

        for key in changed_parameters:
            if key in ['eigensolver', 'convergence'] and self.wfs:
                self.wfs.set_eigensolver(None)

            if key in [
                    'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver',
                    'idiotproof'
            ]:
                continue

            if key in ['convergence', 'fixdensity', 'maxiter']:
                continue

            # Check nested arguments
            if key in ['experimental']:
                changed_parameters2 = changed_parameters[key]
                for key2 in changed_parameters2:
                    if key2 in ['kpt_refine', 'magmoms', 'soc']:
                        self.wfs = None
                    elif key2 in ['reuse_wfs_method', 'niter_fixdensity']:
                        continue
                    else:
                        raise TypeError('Unknown keyword argument:', key2)
                continue

            # More drastic changes:
            if self.wfs:
                self.wfs.set_orthonormalized(False)
            if key in ['external', 'xc', 'poissonsolver']:
                self.hamiltonian = None
            elif key in ['occupations', 'width']:
                pass
            elif key in ['charge', 'background_charge']:
                self.hamiltonian = None
                self.density = None
                self.wfs = None
            elif key in ['kpts', 'nbands', 'symmetry']:
                self.wfs = None
            elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']:
                self.density = None
                self.hamiltonian = None
                self.wfs = None
            elif key in ['basis']:
                self.wfs = None
            else:
                raise TypeError('Unknown keyword argument: "%s"' % key)

    def initialize_positions(self, atoms=None):
        """Update the positions of the atoms."""
        self.log('Initializing position-dependent things.\n')
        if atoms is None:
            atoms = self.atoms
        else:
            atoms = atoms.copy()
            self._set_atoms(atoms)

        mpi.synchronize_atoms(atoms, self.world)

        rank_a = self.wfs.gd.get_ranks_from_positions(self.spos_ac)
        atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd')
        self.wfs.set_positions(self.spos_ac, atom_partition)
        self.density.set_positions(self.spos_ac, atom_partition)
        self.hamiltonian.set_positions(self.spos_ac, atom_partition)

    def set_positions(self, atoms=None):
        """Update the positions of the atoms and initialize wave functions."""
        self.initialize_positions(atoms)

        nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian,
                                           self.spos_ac)
        if nlcao + nrand:
            self.log('Creating initial wave functions:')
            if nlcao:
                self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set')
            if nrand:
                self.log(' ', plural(nrand, 'band'), 'from random numbers')
            self.log()

        self.wfs.eigensolver.reset()
        self.scf.reset()
        print_positions(self.atoms, self.log, self.density.magmom_av)

    def initialize(self, atoms=None, reading=False):
        """Inexpensive initialization."""

        self.log('Initialize ...\n')

        if atoms is None:
            atoms = self.atoms
        else:
            atoms = atoms.copy()
            self._set_atoms(atoms)

        par = self.parameters

        natoms = len(atoms)

        cell_cv = atoms.get_cell() / Bohr
        number_of_lattice_vectors = cell_cv.any(axis=1).sum()
        if number_of_lattice_vectors < 3:
            raise ValueError(
                'GPAW requires 3 lattice vectors.  Your system has {}.'.format(
                    number_of_lattice_vectors))

        pbc_c = atoms.get_pbc()
        assert len(pbc_c) == 3
        magmom_a = atoms.get_initial_magnetic_moments()

        if par.experimental.get('magmoms') is not None:
            magmom_av = np.array(par.experimental['magmoms'], float)
            collinear = False
        else:
            magmom_av = np.zeros((natoms, 3))
            magmom_av[:, 2] = magmom_a
            collinear = True

        mpi.synchronize_atoms(atoms, self.world)

        # Generate new xc functional only when it is reset by set
        # XXX sounds like this should use the _changed_keywords dictionary.
        if self.hamiltonian is None or self.hamiltonian.xc is None:
            if isinstance(par.xc, (basestring, dict)):
                xc = XC(par.xc, collinear=collinear, atoms=atoms)
            else:
                xc = par.xc
        else:
            xc = self.hamiltonian.xc

        mode = par.mode
        if isinstance(mode, basestring):
            mode = {'name': mode}
        if isinstance(mode, dict):
            mode = create_wave_function_mode(**mode)

        if par.dtype == complex:
            warnings.warn('Use mode={}(..., force_complex_dtype=True) '
                          'instead of dtype=complex'.format(mode.name.upper()))
            mode.force_complex_dtype = True
            del par['dtype']
            par.mode = mode

        if xc.orbital_dependent and mode.name == 'lcao':
            raise ValueError('LCAO mode does not support '
                             'orbital-dependent XC functionals.')

        realspace = (mode.name != 'pw' and mode.interpolation != 'fft')

        if not realspace:
            pbc_c = np.ones(3, bool)

        self.create_setups(mode, xc)

        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True
            magmom_av[0, 2] = self.setups[0].get_hunds_rule_moment(par.charge)

        if collinear:
            magnetic = magmom_av.any()

            spinpol = par.spinpol
            if spinpol is None:
                spinpol = magnetic
            elif magnetic and not spinpol:
                raise ValueError('Non-zero initial magnetic moment for a ' +
                                 'spin-paired calculation!')
            nspins = 1 + int(spinpol)

            if spinpol:
                self.log('Spin-polarized calculation.')
                self.log('Magnetic moment: {:.6f}\n'.format(magmom_av.sum()))
            else:
                self.log('Spin-paired calculation\n')
        else:
            nspins = 1
            self.log('Non-collinear calculation.')
            self.log('Magnetic moment: ({:.6f}, {:.6f}, {:.6f})\n'.format(
                *magmom_av.sum(0)))

        if isinstance(par.background_charge, dict):
            background = create_background_charge(**par.background_charge)
        else:
            background = par.background_charge

        nao = self.setups.nao
        nvalence = self.setups.nvalence - par.charge
        if par.background_charge is not None:
            nvalence += background.charge

        M = np.linalg.norm(magmom_av.sum(0))

        nbands = par.nbands

        orbital_free = any(setup.orbital_free for setup in self.setups)
        if orbital_free:
            nbands = 1

        if isinstance(nbands, basestring):
            if nbands == 'nao':
                nbands = nao
            elif nbands[-1] == '%':
                basebands = (nvalence + M) / 2
                nbands = int(np.ceil(float(nbands[:-1]) / 100 * basebands))
            else:
                raise ValueError('Integer expected: Only use a string '
                                 'if giving a percentage of occupied bands')

        if nbands is None:
            # Number of bound partial waves:
            nbandsmax = sum(setup.get_default_nbands()
                            for setup in self.setups)
            nbands = int(np.ceil((1.2 * (nvalence + M) / 2))) + 4
            if nbands > nbandsmax:
                nbands = nbandsmax
            if mode.name == 'lcao' and nbands > nao:
                nbands = nao
        elif nbands <= 0:
            nbands = max(1, int(nvalence + M + 0.5) // 2 + (-nbands))

        if nbands > nao and mode.name == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: '
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        if nvalence > 2 * nbands and not orbital_free:
            raise ValueError('Too few bands!  Electrons: %f, bands: %d' %
                             (nvalence, nbands))

        self.create_occupations(magmom_av[:, 2].sum(), orbital_free)

        if self.scf is None:
            self.create_scf(nvalence, mode)

        self.create_symmetry(magmom_av, cell_cv)

        if not collinear:
            nbands *= 2

        if not self.wfs:
            self.create_wave_functions(mode, realspace, nspins, collinear,
                                       nbands, nao, nvalence, self.setups,
                                       cell_cv, pbc_c)
        else:
            self.wfs.set_setups(self.setups)

        if not self.wfs.eigensolver:
            self.create_eigensolver(xc, nbands, mode)

        if self.density is None and not reading:
            assert not par.fixdensity, 'No density to fix!'

        olddens = None
        if (self.density is not None and
            (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()):
            # Domain decomposition has changed, so we need to
            # reinitialize density and hamiltonian:
            if par.fixdensity:
                olddens = self.density

            self.density = None
            self.hamiltonian = None

        if self.density is None:
            self.create_density(realspace, mode, background)

        # XXXXXXXXXX if setups change, then setups.core_charge may change.
        # But that parameter was supplied in Density constructor!
        # This surely is a bug!
        self.density.initialize(self.setups, self.timer, magmom_av, par.hund)
        self.density.set_mixer(par.mixer)
        if self.density.mixer.driver.name == 'dummy' or par.fixdensity:
            self.log('No density mixing\n')
        else:
            self.log(self.density.mixer, '\n')
        self.density.fixed = par.fixdensity
        self.density.log = self.log

        if olddens is not None:
            self.density.initialize_from_other_density(olddens,
                                                       self.wfs.kptband_comm)

        if self.hamiltonian is None:
            self.create_hamiltonian(realspace, mode, xc)

        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)
        description = xc.get_description()
        if description is not None:
            self.log('XC parameters: {}\n'.format('\n  '.join(
                description.splitlines())))

        if xc.name == 'GLLBSC' and olddens is not None:
            xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens)

        self.print_memory_estimate(maxdepth=memory_estimate_depth + 1)

        print_parallelization_details(self.wfs, self.hamiltonian, self.log)

        self.log('Number of atoms:', natoms)
        self.log('Number of atomic orbitals:', self.wfs.setups.nao)
        self.log('Number of bands in calculation:', self.wfs.bd.nbands)
        self.log('Bands to converge: ', end='')
        n = par.convergence.get('bands', 'occupied')
        if n == 'occupied':
            self.log('occupied states only')
        elif n == 'all':
            self.log('all')
        else:
            self.log('%d lowest bands' % n)
        self.log('Number of valence electrons:', self.wfs.nvalence)

        self.log(flush=True)

        self.timer.print_info(self)

        if dry_run:
            self.dry_run()

        if (realspace and self.hamiltonian.poisson.get_description()
                == 'FDTD+TDDFT'):
            self.hamiltonian.poisson.set_density(self.density)
            self.hamiltonian.poisson.print_messages(self.log)
            self.log.fd.flush()

        self.initialized = True
        self.log('... initialized\n')

    def create_setups(self, mode, xc):
        if self.parameters.filter is None and mode.name != 'pw':
            gamma = 1.6
            N_c = self.parameters.get('gpts')
            if N_c is None:
                h = (self.parameters.h or 0.2) / Bohr
            else:
                icell_vc = np.linalg.inv(self.atoms.cell)
                h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr

            def filter(rgd, rcut, f_r, l=0):
                gcut = np.pi / h - 2 / rcut / gamma
                ftmp = rgd.filter(f_r, rcut * gamma, gcut, l)
                f_r[:] = ftmp[:len(f_r)]
        else:
            filter = self.parameters.filter

        Z_a = self.atoms.get_atomic_numbers()
        self.setups = Setups(Z_a, self.parameters.setups,
                             self.parameters.basis, xc, filter, self.world)
        self.log(self.setups)

    def create_grid_descriptor(self, N_c, cell_cv, pbc_c, domain_comm,
                               parsize_domain):
        return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain)

    def create_occupations(self, magmom, orbital_free):
        occ = self.parameters.occupations

        if occ is None:
            if orbital_free:
                occ = {'name': 'orbital-free'}
            else:
                width = self.parameters.width
                if width is not None:
                    warnings.warn(
                        'Please use occupations=FermiDirac({})'.format(width))
                elif self.atoms.pbc.any():
                    width = 0.1  # eV
                else:
                    width = 0.0
                occ = {'name': 'fermi-dirac', 'width': width}

        if isinstance(occ, dict):
            occ = create_occupation_number_object(**occ)

        if self.parameters.fixdensity:
            occ.fixed_fermilevel = True
            if self.occupations:
                occ.fermilevel = self.occupations.fermilevel

        self.occupations = occ

        # If occupation numbers are changed, and we have wave functions,
        # recalculate the occupation numbers
        if self.wfs is not None:
            self.occupations.calculate(self.wfs)

        self.occupations.magmom = magmom

        self.log(self.occupations)

    def create_scf(self, nvalence, mode):
        # if mode.name == 'lcao':
        #     niter_fixdensity = 0
        # else:
        #     niter_fixdensity = 2

        nv = max(nvalence, 1)
        cc = self.parameters.convergence
        self.scf = SCFLoop(
            cc.get('eigenstates', 4.0e-8) / Ha**2 * nv,
            cc.get('energy', 0.0005) / Ha * nv,
            cc.get('density', 1.0e-4) * nv,
            cc.get('forces', np.inf) / (Ha / Bohr),
            self.parameters.maxiter,
            # XXX make sure niter_fixdensity value is *always* set from default
            # Subdictionary defaults seem to not be set when user provides
            # e.g. {}.  We should change that so it works like the ordinary
            # parameters.
            self.parameters.experimental.get('niter_fixdensity', 0),
            nv)
        self.log(self.scf)

    def create_symmetry(self, magmom_av, cell_cv):
        symm = self.parameters.symmetry
        if symm == 'off':
            symm = {'point_group': False, 'time_reversal': False}
        m_av = magmom_av.round(decimals=3)  # round off
        id_a = [id + tuple(m_v) for id, m_v in zip(self.setups.id_a, m_av)]
        self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm)
        self.symmetry.analyze(self.spos_ac)
        self.setups.set_symmetry(self.symmetry)

    def create_eigensolver(self, xc, nbands, mode):
        # Number of bands to converge:
        nbands_converge = self.parameters.convergence.get('bands', 'occupied')
        if nbands_converge == 'all':
            nbands_converge = nbands
        elif nbands_converge != 'occupied':
            assert isinstance(nbands_converge, int)
            if nbands_converge < 0:
                nbands_converge += nbands
        eigensolver = get_eigensolver(self.parameters.eigensolver, mode,
                                      self.parameters.convergence)
        eigensolver.nbands_converge = nbands_converge
        # XXX Eigensolver class doesn't define an nbands_converge property

        if isinstance(xc, SIC):
            eigensolver.blocksize = 1

        self.wfs.set_eigensolver(eigensolver)

        self.log('Eigensolver\n  ', self.wfs.eigensolver, '\n')

    def create_density(self, realspace, mode, background):
        gd = self.wfs.gd

        big_gd = gd.new_descriptor(comm=self.world)
        # Check whether grid is too small.  8 is smallest admissible.
        # (we decide this by how difficult it is to make the tests pass)
        # (Actually it depends on stencils!  But let the user deal with it)
        N_c = big_gd.get_size_of_global_array(pad=True)
        too_small = np.any(N_c / big_gd.parsize_c < 8)
        if (self.parallel['augment_grids'] and not too_small
                and mode.name != 'pw'):
            aux_gd = big_gd
        else:
            aux_gd = gd

        redistributor = GridRedistributor(self.world, self.wfs.kptband_comm,
                                          gd, aux_gd)

        # Construct grid descriptor for fine grids for densities
        # and potentials:
        finegd = aux_gd.refine()

        kwargs = dict(gd=gd,
                      finegd=finegd,
                      nspins=self.wfs.nspins,
                      collinear=self.wfs.collinear,
                      charge=self.parameters.charge +
                      self.wfs.setups.core_charge,
                      redistributor=redistributor,
                      background_charge=background)

        if realspace:
            self.density = RealSpaceDensity(stencil=mode.interpolation,
                                            **kwargs)
        else:
            self.density = pw.ReciprocalSpaceDensity(**kwargs)

        self.log(self.density, '\n')

    def create_hamiltonian(self, realspace, mode, xc):
        dens = self.density
        kwargs = dict(gd=dens.gd,
                      finegd=dens.finegd,
                      nspins=dens.nspins,
                      collinear=dens.collinear,
                      setups=dens.setups,
                      timer=self.timer,
                      xc=xc,
                      world=self.world,
                      redistributor=dens.redistributor,
                      vext=self.parameters.external,
                      psolver=self.parameters.poissonsolver)
        if realspace:
            self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation,
                                                    **kwargs)
            xc.set_grid_descriptor(self.hamiltonian.finegd)
        else:
            # This code will work if dens.redistributor uses
            # ordinary density.gd as aux_gd
            gd = dens.finegd

            xc_redist = None
            if self.parallel['augment_grids']:
                from gpaw.grid_descriptor import BadGridError
                try:
                    aux_gd = gd.new_descriptor(comm=self.world)
                except BadGridError as err:
                    import warnings
                    warnings.warn('Ignoring augment_grids: {}'.format(err))
                else:
                    bcast_comm = dens.redistributor.broadcast_comm
                    xc_redist = GridRedistributor(self.world, bcast_comm, gd,
                                                  aux_gd)

            self.hamiltonian = pw.ReciprocalSpaceHamiltonian(
                pd2=dens.pd2,
                pd3=dens.pd3,
                realpbc_c=self.atoms.pbc,
                xc_redistributor=xc_redist,
                **kwargs)
            xc.set_grid_descriptor(self.hamiltonian.xc_gd)

        self.hamiltonian.soc = self.parameters.experimental.get('soc')
        self.log(self.hamiltonian, '\n')

    def create_kpoint_descriptor(self, nspins):
        par = self.parameters

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kpt_refine = par.experimental.get('kpt_refine')
        if kpt_refine is None:
            kd = KPointDescriptor(bzkpts_kc, nspins)

            self.timer.start('Set symmetry')
            kd.set_symmetry(self.atoms, self.symmetry, comm=self.world)
            self.timer.stop('Set symmetry')

        else:
            self.timer.start('Set k-point refinement')
            kd = create_kpoint_descriptor_with_refinement(kpt_refine,
                                                          bzkpts_kc,
                                                          nspins,
                                                          self.atoms,
                                                          self.symmetry,
                                                          comm=self.world,
                                                          timer=self.timer)
            self.timer.stop('Set k-point refinement')
            # Update quantities which might have changed, if symmetry
            # was changed
            self.symmetry = kd.symmetry
            self.setups.set_symmetry(kd.symmetry)

        self.log(kd)

        return kd

    def create_wave_functions(self, mode, realspace, nspins, collinear, nbands,
                              nao, nvalence, setups, cell_cv, pbc_c):
        par = self.parameters

        kd = self.create_kpoint_descriptor(nspins)

        parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts)

        parsize_kpt = self.parallel['kpt']
        parsize_domain = self.parallel['domain']
        parsize_bands = self.parallel['band']

        ndomains = None
        if parsize_domain is not None:
            ndomains = np.prod(parsize_domain)
        parallelization.set(kpt=parsize_kpt,
                            domain=ndomains,
                            band=parsize_bands)
        comms = parallelization.build_communicators()
        domain_comm = comms['d']
        kpt_comm = comms['k']
        band_comm = comms['b']
        kptband_comm = comms['D']
        domainband_comm = comms['K']

        self.comms = comms

        if par.gpts is not None:
            if par.h is not None:
                raise ValueError("""You can't use both "gpts" and "h"!""")
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace,
                                            kd.symmetry)

        self.symmetry.check_grid(N_c)

        kd.set_communicator(kpt_comm)

        parstride_bands = self.parallel['stridebands']

        bd = BandDescriptor(nbands, band_comm, parstride_bands)

        # Construct grid descriptor for coarse grids for wave functions:
        gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm,
                                         parsize_domain)

        if hasattr(self, 'time') or mode.force_complex_dtype or not collinear:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        wfs_kwargs = dict(gd=gd,
                          nvalence=nvalence,
                          setups=setups,
                          bd=bd,
                          dtype=dtype,
                          world=self.world,
                          kd=kd,
                          kptband_comm=kptband_comm,
                          timer=self.timer)

        if self.parallel['sl_auto']:
            # Choose scalapack parallelization automatically

            for key, val in self.parallel.items():
                if (key.startswith('sl_') and key != 'sl_auto'
                        and val is not None):
                    raise ValueError("Cannot use 'sl_auto' together "
                                     "with '%s'" % key)

            max_scalapack_cpus = bd.comm.size * gd.comm.size
            sl_default = suggest_blocking(nbands, max_scalapack_cpus)
        else:
            sl_default = self.parallel['sl_default']

        if mode.name == 'lcao':
            assert collinear
            # Layouts used for general diagonalizer
            sl_lcao = self.parallel['sl_lcao']
            if sl_lcao is None:
                sl_lcao = sl_default

            elpasolver = None
            if self.parallel['use_elpa']:
                elpasolver = self.parallel['elpasolver']
            lcaoksl = get_KohnSham_layouts(sl_lcao,
                                           'lcao',
                                           gd,
                                           bd,
                                           domainband_comm,
                                           dtype,
                                           nao=nao,
                                           timer=self.timer,
                                           elpasolver=elpasolver)

            self.wfs = mode(lcaoksl, **wfs_kwargs)

        elif mode.name == 'fd' or mode.name == 'pw':
            # Use (at most) all available LCAO for initialization
            lcaonbands = min(nbands, nao)

            try:
                lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands)
            except RuntimeError:
                initksl = None
            else:
                # Layouts used for general diagonalizer
                # (LCAO initialization)
                sl_lcao = self.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                initksl = get_KohnSham_layouts(sl_lcao,
                                               'lcao',
                                               gd,
                                               lcaobd,
                                               domainband_comm,
                                               dtype,
                                               nao=nao,
                                               timer=self.timer)

            reuse_wfs_method = par.experimental.get('reuse_wfs_method', 'paw')
            sl = (domainband_comm, ) + (self.parallel['sl_diagonalize']
                                        or sl_default or (1, 1, None))
            self.wfs = mode(sl,
                            initksl,
                            reuse_wfs_method=reuse_wfs_method,
                            collinear=collinear,
                            **wfs_kwargs)
        else:
            self.wfs = mode(self, collinear=collinear, **wfs_kwargs)

        self.log(self.wfs, '\n')

    def dry_run(self):
        # Can be overridden like in gpaw.atom.atompaw
        print_cell(self.wfs.gd, self.atoms.pbc, self.log)
        print_positions(self.atoms, self.log, self.density.magmom_av)
        self.log.fd.flush()

        # Write timing info now before the interpreter shuts down:
        self.__del__()

        # Disable timing output during shut-down:
        del self.timer

        raise SystemExit
Пример #12
0
    def initialize(self, atoms=None):
        """Inexpensive initialization."""

        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        par = self.input_parameters

        world = par.communicator
        if world is None:
            world = mpi.world
        elif hasattr(world, 'new_communicator'):
            # Check for whether object has correct type already
            #
            # Using isinstance() is complicated because of all the
            # combinations, serial/parallel/debug...
            pass
        else:
            # world should be a list of ranks:
            world = mpi.world.new_communicator(np.asarray(world))
        self.wfs.world = world

        if 'txt' in self._changed_keywords:
            self.set_txt(par.txt)
        self.verbose = par.verbose

        natoms = len(atoms)

        cell_cv = atoms.get_cell() / Bohr
        pbc_c = atoms.get_pbc()
        Z_a = atoms.get_atomic_numbers()
        magmom_av = atoms.get_initial_magnetic_moments()

        self.check_atoms()

        # Generate new xc functional only when it is reset by set
        # XXX sounds like this should use the _changed_keywords dictionary.
        if self.hamiltonian is None or self.hamiltonian.xc is None:
            if isinstance(par.xc, str):
                xc = XC(par.xc)
            else:
                xc = par.xc
        else:
            xc = self.hamiltonian.xc

        mode = par.mode

        if mode == 'fd':
            mode = FD()
        elif mode == 'pw':
            mode = pw.PW()
        elif mode == 'lcao':
            mode = LCAO()
        else:
            assert hasattr(mode, 'name'), str(mode)

        if xc.orbital_dependent and mode.name == 'lcao':
            raise NotImplementedError('LCAO mode does not support '
                                      'orbital-dependent XC functionals.')

        if par.realspace is None:
            realspace = (mode.name != 'pw')
        else:
            realspace = par.realspace
            if mode.name == 'pw':
                assert not realspace

        if par.filter is None and mode.name != 'pw':
            gamma = 1.6
            if par.gpts is not None:
                h = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5 / par.gpts).max()
            else:
                h = (par.h or 0.2) / Bohr

            def filter(rgd, rcut, f_r, l=0):
                gcut = np.pi / h - 2 / rcut / gamma
                f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l)
        else:
            filter = par.filter

        setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc, filter,
                        world)

        if magmom_av.ndim == 1:
            collinear = True
            magmom_av, magmom_a = np.zeros((natoms, 3)), magmom_av
            magmom_av[:, 2] = magmom_a
        else:
            collinear = False

        magnetic = magmom_av.any()

        spinpol = par.spinpol
        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True
            magmom_av[0] = (0, 0, setups[0].get_hunds_rule_moment(par.charge))

        if spinpol is None:
            spinpol = magnetic
        elif magnetic and not spinpol:
            raise ValueError('Non-zero initial magnetic moment for a ' +
                             'spin-paired calculation!')

        if collinear:
            nspins = 1 + int(spinpol)
            ncomp = 1
        else:
            nspins = 1
            ncomp = 2

        if par.usesymm != 'default':
            warnings.warn('Use "symmetry" keyword instead of ' +
                          '"usesymm" keyword')
            par.symmetry = usesymm2symmetry(par.usesymm)

        symm = par.symmetry
        if symm == 'off':
            symm = {'point_group': False, 'time_reversal': False}

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kd = KPointDescriptor(bzkpts_kc, nspins, collinear)
        m_av = magmom_av.round(decimals=3)  # round off
        id_a = zip(setups.id_a, *m_av.T)
        symmetry = Symmetry(id_a, cell_cv, atoms.pbc, **symm)
        kd.set_symmetry(atoms, symmetry, comm=world)
        setups.set_symmetry(symmetry)

        if par.gpts is not None:
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace,
                                            kd.symmetry)

        symmetry.check_grid(N_c)

        width = par.width
        if width is None:
            if pbc_c.any():
                width = 0.1  # eV
            else:
                width = 0.0
        else:
            assert par.occupations is None

        if hasattr(self, 'time') or par.dtype == complex:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        nao = setups.nao
        nvalence = setups.nvalence - par.charge
        M_v = magmom_av.sum(0)
        M = np.dot(M_v, M_v)**0.5

        nbands = par.nbands

        orbital_free = any(setup.orbital_free for setup in setups)
        if orbital_free:
            nbands = 1

        if isinstance(nbands, basestring):
            if nbands[-1] == '%':
                basebands = int(nvalence + M + 0.5) // 2
                nbands = int((float(nbands[:-1]) / 100) * basebands)
            else:
                raise ValueError('Integer Expected: Only use a string '
                                 'if giving a percentage of occupied bands')

        if nbands is None:
            nbands = 0
            for setup in setups:
                nbands_from_atom = setup.get_default_nbands()

                # Any obscure setup errors?
                if nbands_from_atom < -(-setup.Nv // 2):
                    raise ValueError('Bad setup: This setup requests %d'
                                     ' bands but has %d electrons.' %
                                     (nbands_from_atom, setup.Nv))
                nbands += nbands_from_atom
            nbands = min(nao, nbands)
        elif nbands > nao and mode.name == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: '
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        if nbands <= 0:
            nbands = int(nvalence + M + 0.5) // 2 + (-nbands)

        if nvalence > 2 * nbands and not orbital_free:
            raise ValueError('Too few bands!  Electrons: %f, bands: %d' %
                             (nvalence, nbands))

        nbands *= ncomp

        if par.width is not None:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width).')
        if par.fixmom:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width, fixmagmom=True).')

        if self.occupations is None:
            if par.occupations is None:
                # Create object for occupation numbers:
                if orbital_free:
                    width = 0.0  # even for PBC
                    self.occupations = occupations.TFOccupations(
                        width, par.fixmom)
                else:
                    self.occupations = occupations.FermiDirac(
                        width, par.fixmom)
            else:
                self.occupations = par.occupations

            # If occupation numbers are changed, and we have wave functions,
            # recalculate the occupation numbers
            if self.wfs is not None and not isinstance(self.wfs,
                                                       EmptyWaveFunctions):
                self.occupations.calculate(self.wfs)

        self.occupations.magmom = M_v[2]

        cc = par.convergence

        if mode.name == 'lcao':
            niter_fixdensity = 0
        else:
            niter_fixdensity = None

        if self.scf is None:
            force_crit = cc['forces']
            if force_crit is not None:
                force_crit /= Hartree / Bohr
            self.scf = SCFLoop(cc['eigenstates'] / Hartree**2 * nvalence,
                               cc['energy'] / Hartree * max(nvalence, 1),
                               cc['density'] * nvalence, par.maxiter,
                               par.fixdensity, niter_fixdensity, force_crit)

        parsize_kpt = par.parallel['kpt']
        parsize_domain = par.parallel['domain']
        parsize_bands = par.parallel['band']

        if not realspace:
            pbc_c = np.ones(3, bool)

        if not self.wfs:
            if parsize_domain == 'domain only':  # XXX this was silly!
                parsize_domain = world.size

            parallelization = mpi.Parallelization(world, nspins * kd.nibzkpts)
            ndomains = None
            if parsize_domain is not None:
                ndomains = np.prod(parsize_domain)
            if mode.name == 'pw':
                if ndomains > 1:
                    raise ValueError('Planewave mode does not support '
                                     'domain decomposition.')
                ndomains = 1
            parallelization.set(kpt=parsize_kpt,
                                domain=ndomains,
                                band=parsize_bands)
            comms = parallelization.build_communicators()
            domain_comm = comms['d']
            kpt_comm = comms['k']
            band_comm = comms['b']
            kptband_comm = comms['D']
            domainband_comm = comms['K']

            self.comms = comms
            kd.set_communicator(kpt_comm)

            parstride_bands = par.parallel['stridebands']

            # Unfortunately we need to remember that we adjusted the
            # number of bands so we can print a warning if it differs
            # from the number specified by the user.  (The number can
            # be inferred from the input parameters, but it's tricky
            # because we allow negative numbers)
            self.nbands_parallelization_adjustment = -nbands % band_comm.size
            nbands += self.nbands_parallelization_adjustment

            # I would like to give the following error message, but apparently
            # there are cases, e.g. gpaw/test/gw_ppa.py, which involve
            # nbands > nao and are supposed to work that way.
            #if nbands > nao:
            #    raise ValueError('Number of bands %d adjusted for band '
            #                     'parallelization %d exceeds number of atomic '
            #                     'orbitals %d.  This problem can be fixed '
            #                     'by reducing the number of bands a bit.'
            #                     % (nbands, band_comm.size, nao))
            bd = BandDescriptor(nbands, band_comm, parstride_bands)

            if (self.density is not None
                    and self.density.gd.comm.size != domain_comm.size):
                # Domain decomposition has changed, so we need to
                # reinitialize density and hamiltonian:
                if par.fixdensity:
                    raise RuntimeError(
                        'Density reinitialization conflict ' +
                        'with "fixdensity" - specify domain decomposition.')
                self.density = None
                self.hamiltonian = None

            # Construct grid descriptor for coarse grids for wave functions:
            gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c, domain_comm,
                                            parsize_domain)

            # do k-point analysis here? XXX
            args = (gd, nvalence, setups, bd, dtype, world, kd, kptband_comm,
                    self.timer)

            if par.parallel['sl_auto']:
                # Choose scalapack parallelization automatically

                for key, val in par.parallel.items():
                    if (key.startswith('sl_') and key != 'sl_auto'
                            and val is not None):
                        raise ValueError("Cannot use 'sl_auto' together "
                                         "with '%s'" % key)
                max_scalapack_cpus = bd.comm.size * gd.comm.size
                nprow = max_scalapack_cpus
                npcol = 1

                # Get a sort of reasonable number of columns/rows
                while npcol < nprow and nprow % 2 == 0:
                    npcol *= 2
                    nprow //= 2
                assert npcol * nprow == max_scalapack_cpus

                # ScaLAPACK creates trouble if there aren't at least a few
                # whole blocks; choose block size so there will always be
                # several blocks.  This will crash for small test systems,
                # but so will ScaLAPACK in any case
                blocksize = min(-(-nbands // 4), 64)
                sl_default = (nprow, npcol, blocksize)
            else:
                sl_default = par.parallel['sl_default']

            if mode.name == 'lcao':
                # Layouts used for general diagonalizer
                sl_lcao = par.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                lcaoksl = get_KohnSham_layouts(sl_lcao,
                                               'lcao',
                                               gd,
                                               bd,
                                               domainband_comm,
                                               dtype,
                                               nao=nao,
                                               timer=self.timer)

                self.wfs = mode(collinear, lcaoksl, *args)

            elif mode.name == 'fd' or mode.name == 'pw':
                # buffer_size keyword only relevant for fdpw
                buffer_size = par.parallel['buffer_size']
                # Layouts used for diagonalizer
                sl_diagonalize = par.parallel['sl_diagonalize']
                if sl_diagonalize is None:
                    sl_diagonalize = sl_default
                diagksl = get_KohnSham_layouts(
                    sl_diagonalize,
                    'fd',  # XXX
                    # choice of key 'fd' not so nice
                    gd,
                    bd,
                    domainband_comm,
                    dtype,
                    buffer_size=buffer_size,
                    timer=self.timer)

                # Layouts used for orthonormalizer
                sl_inverse_cholesky = par.parallel['sl_inverse_cholesky']
                if sl_inverse_cholesky is None:
                    sl_inverse_cholesky = sl_default
                if sl_inverse_cholesky != sl_diagonalize:
                    message = 'sl_inverse_cholesky != sl_diagonalize ' \
                        'is not implemented.'
                    raise NotImplementedError(message)
                orthoksl = get_KohnSham_layouts(sl_inverse_cholesky,
                                                'fd',
                                                gd,
                                                bd,
                                                domainband_comm,
                                                dtype,
                                                buffer_size=buffer_size,
                                                timer=self.timer)

                # Use (at most) all available LCAO for initialization
                lcaonbands = min(nbands, nao)

                try:
                    lcaobd = BandDescriptor(lcaonbands, band_comm,
                                            parstride_bands)
                except RuntimeError:
                    initksl = None
                else:
                    # Layouts used for general diagonalizer
                    # (LCAO initialization)
                    sl_lcao = par.parallel['sl_lcao']
                    if sl_lcao is None:
                        sl_lcao = sl_default
                    initksl = get_KohnSham_layouts(sl_lcao,
                                                   'lcao',
                                                   gd,
                                                   lcaobd,
                                                   domainband_comm,
                                                   dtype,
                                                   nao=nao,
                                                   timer=self.timer)

                if hasattr(self, 'time'):
                    assert mode.name == 'fd'
                    from gpaw.tddft import TimeDependentWaveFunctions
                    self.wfs = TimeDependentWaveFunctions(
                        par.stencils[0], diagksl, orthoksl, initksl, gd,
                        nvalence, setups, bd, world, kd, kptband_comm,
                        self.timer)
                elif mode.name == 'fd':
                    self.wfs = mode(par.stencils[0], diagksl, orthoksl,
                                    initksl, *args)
                else:
                    assert mode.name == 'pw'
                    self.wfs = mode(diagksl, orthoksl, initksl, *args)
            else:
                self.wfs = mode(self, *args)
        else:
            self.wfs.set_setups(setups)

        if not self.wfs.eigensolver:
            # Number of bands to converge:
            nbands_converge = cc['bands']
            if nbands_converge == 'all':
                nbands_converge = nbands
            elif nbands_converge != 'occupied':
                assert isinstance(nbands_converge, int)
                if nbands_converge < 0:
                    nbands_converge += nbands
            eigensolver = get_eigensolver(par.eigensolver, mode,
                                          par.convergence)
            eigensolver.nbands_converge = nbands_converge
            # XXX Eigensolver class doesn't define an nbands_converge property

            if isinstance(xc, SIC):
                eigensolver.blocksize = 1
            self.wfs.set_eigensolver(eigensolver)

        if self.density is None:
            gd = self.wfs.gd
            if par.stencils[1] != 9:
                # Construct grid descriptor for fine grids for densities
                # and potentials:
                finegd = gd.refine()
            else:
                # Special case (use only coarse grid):
                finegd = gd

            if realspace:
                self.density = RealSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear, par.stencils[1])
            else:
                self.density = pw.ReciprocalSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear)

        self.density.initialize(setups, self.timer, magmom_av, par.hund)
        self.density.set_mixer(par.mixer)

        if self.hamiltonian is None:
            gd, finegd = self.density.gd, self.density.finegd
            if realspace:
                self.hamiltonian = RealSpaceHamiltonian(
                    gd, finegd, nspins, setups, self.timer, xc, world,
                    self.wfs.kptband_comm, par.external, collinear,
                    par.poissonsolver, par.stencils[1])
            else:
                self.hamiltonian = pw.ReciprocalSpaceHamiltonian(
                    gd, finegd, self.density.pd2, self.density.pd3, nspins,
                    setups, self.timer, xc, world, self.wfs.kptband_comm,
                    par.external, collinear)

        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)

        self.text()
        self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth)
        self.txt.flush()

        self.timer.print_info(self)

        if dry_run:
            self.dry_run()

        if realspace and \
                self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT':
            self.hamiltonian.poisson.set_density(self.density)
            self.hamiltonian.poisson.print_messages(self.text)
            self.txt.flush()

        self.initialized = True
        self._changed_keywords.clear()
Пример #13
0
class KPointDescriptor:
    """Descriptor-class for k-points."""

    def __init__(self, kpts, nspins=1, collinear=True, usefractrans=False):
        """Construct descriptor object for kpoint/spin combinations (ks-pair).

        Parameters
        ----------
        kpts: None, sequence of 3 ints, or (n,3)-shaped array
            Specification of the k-point grid. None=Gamma, list of
            ints=Monkhorst-Pack, ndarray=user specified.
        nspins: int
            Number of spins.
        usefractrans: bool
            Switch for the use of non-symmorphic symmetries aka: symmetries
            with fractional translations. False by default (experimental!!!)

        Attributes
        ===================  =================================================
        ``N_c``               Number of k-points in the different directions.
        ``nspins``            Number of spins in total.
        ``mynspins``          Number of spins on this CPU.
        ``nibzkpts``          Number of irreducible kpoints in 1st BZ.
        ``nks``               Number of k-point/spin combinations in total.
        ``mynks``             Number of k-point/spin combinations on this CPU.
        ``gamma``             Boolean indicator for gamma point calculation.
        ``comm``              MPI-communicator for kpoint distribution.
        ``weight_k``          Weights of each k-point
        ``ibzk_kc``           Unknown
        ``sym_k``             Unknown
        ``time_reversal_k``   Unknown
        ``bz2ibz_k``          Unknown
        ``ibz2bz_k``          Unknown
        ``bz2bz_ks``          Unknown
        ``symmetry``          Object representing symmetries
        ===================  =================================================
        """

        if kpts is None:
            self.bzk_kc = np.zeros((1, 3))
            self.N_c = np.array((1, 1, 1), dtype=int)
            self.offset_c = np.zeros(3)
        elif isinstance(kpts[0], int):
            self.bzk_kc = monkhorst_pack(kpts)
            self.N_c = np.array(kpts, dtype=int)
            self.offset_c = np.zeros(3)
        else:
            self.bzk_kc = np.array(kpts, float)
            try:
                self.N_c, self.offset_c = \
                          get_monkhorst_pack_size_and_offset(self.bzk_kc)
            except ValueError:
                self.N_c = None
                self.offset_c = None

        self.collinear = collinear
        self.nspins = nspins
        self.nbzkpts = len(self.bzk_kc)
        
        # Gamma-point calculation?
        self.usefractrans = usefractrans
        self.gamma = (self.nbzkpts == 1 and np.allclose(self.bzk_kc[0], 0.0))
        self.set_symmetry(None, None, usesymm=None)
        self.set_communicator(mpi.serial_comm)

        if self.gamma:
            self.description = '1 k-point (Gamma)'
        else:
            self.description = '%d k-points' % self.nbzkpts
            if self.N_c is not None:
                self.description += (': %d x %d x %d Monkhorst-Pack grid' %
                                     tuple(self.N_c))
                if self.offset_c.any():
                    self.description += ' + ['
                    for x in self.offset_c:
                        if x != 0 and abs(round(1 / x) - 1 / x) < 1e-12:
                            self.description += '1/%d,' % round(1 / x)
                        else:
                            self.description += '%f,' % x
                    self.description = self.description[:-1] + ']'

    def __len__(self):
        """Return number of k-point/spin combinations of local CPU."""
        
        return self.mynks

    def set_symmetry(self, atoms, setups, magmom_av=None,
                     usesymm=False, N_c=None, comm=None):
        """Create symmetry object and construct irreducible Brillouin zone.

        atoms: Atoms object
            Defines atom positions and types and also unit cell and
            boundary conditions.
        setups: instance of class Setups
            PAW setups for the atoms.
        magmom_av: ndarray
            Initial magnetic moments.
        usesymm: bool
            Symmetry flag.
        N_c: three int's or None
            If not None:  Check also symmetry of grid.
        """

        if atoms is not None:
            for c, periodic in enumerate(atoms.pbc):
                if not periodic and not np.allclose(self.bzk_kc[:, c], 0.0):
                    raise ValueError('K-points can only be used with PBCs!')

            self.cell_cv = atoms.cell / Bohr
     
            if magmom_av is None:
                magmom_av = np.zeros((len(atoms), 3))
                magmom_av[:, 2] = atoms.get_initial_magnetic_moments()

            magmom_av = magmom_av.round(decimals=3)  # round off
            id_a = zip(setups.id_a, *magmom_av.T)

            # Construct a Symmetry instance containing the identity operation
            # only
            self.symmetry = Symmetry(id_a, atoms.cell / Bohr, atoms.pbc, fractrans=self.usefractrans)
            self.usefractrans = self.symmetry.usefractrans
        else:
            self.symmetry = None
        
        if self.gamma or usesymm is None:
            # Point group and time-reversal symmetry neglected
            self.weight_k = np.ones(self.nbzkpts) / self.nbzkpts
            self.ibzk_kc = self.bzk_kc.copy()
            self.sym_k = np.zeros(self.nbzkpts, int)
            self.time_reversal_k = np.zeros(self.nbzkpts, bool)
            self.bz2ibz_k = np.arange(self.nbzkpts)
            self.ibz2bz_k = np.arange(self.nbzkpts)
            self.bz2bz_ks = np.arange(self.nbzkpts)[:, np.newaxis]
        else:
            if usesymm:
                # Find symmetry operations of atoms
                self.symmetry.analyze(atoms.get_scaled_positions())
              
                if N_c is not None:
                    if self.usefractrans:
                        ## adjust N_c to symmetries
                        # the factor (denominator) the grid must follow
                        factor = np.ones(3, float)
                        indexes = np.where(np.abs(self.symmetry.ft_sc) > 1e-3)
                        for i in range(len(indexes[0])):
                            # find smallest common denominator
                            a = factor[indexes[1][i]]
                            b = np.rint(1. /  self.symmetry.ft_sc[indexes[0][i]][indexes[1][i]])
                            factor[indexes[1][i]] = a * b
                            while b != 0:
                                rem = a % b
                                a = b
                                b = rem
                            factor[indexes[1][i]] /= a 
                        Nnew_c = np.array(np.rint(N_c / factor) * factor, int)
                        # make sure new grid is not less dense
                        Nnew_c = np.array(np.where(Nnew_c >= N_c, Nnew_c, Nnew_c + factor), int)
                        N_c = Nnew_c
                    else:
                        ## adjust symmetries to grid 
                        self.symmetry.prune_symmetries_grid(N_c)

            (self.ibzk_kc, self.weight_k,
             self.sym_k,
             self.time_reversal_k,
             self.bz2ibz_k,
             self.ibz2bz_k,
             self.bz2bz_ks) = self.symmetry.reduce(self.bzk_kc, comm)
            
        if setups is not None:
            setups.set_symmetry(self.symmetry)

        # Number of irreducible k-points and k-point/spin combinations.
        self.nibzkpts = len(self.ibzk_kc)
        if self.collinear:
            self.nks = self.nibzkpts * self.nspins
        else:
            self.nks = self.nibzkpts

        return N_c

    def set_communicator(self, comm):
        """Set k-point communicator."""

        # Ranks < self.rank0 have mynks0 k-point/spin combinations and
        # ranks >= self.rank0 have mynks0+1 k-point/spin combinations.
        mynks0, x = divmod(self.nks, comm.size)
        self.rank0 = comm.size - x
        self.comm = comm

        # My number and offset of k-point/spin combinations
        self.mynks = self.get_count()
        self.ks0 = self.get_offset()

        if self.nspins == 2 and comm.size == 1:  # NCXXXXXXXX
            # Avoid duplicating k-points in local list of k-points.
            self.ibzk_qc = self.ibzk_kc.copy()
            self.weight_q = self.weight_k
        else:
            self.ibzk_qc = np.vstack((self.ibzk_kc,
                                      self.ibzk_kc))[self.get_slice()]
            self.weight_q = np.hstack((self.weight_k,
                                       self.weight_k))[self.get_slice()]

    def copy(self, comm=mpi.serial_comm):
        """Create a copy with shared symmetry object."""
        kd = KPointDescriptor(self.bzk_kc, self.nspins)
        kd.weight_k = self.weight_k
        kd.ibzk_kc = self.ibzk_kc
        kd.sym_k = self.sym_k
        kd.time_reversal_k = self.time_reversal_k
        kd.bz2ibz_k = self.bz2ibz_k
        kd.ibz2bz_k = self.ibz2bz_k
        kd.bz2bz_ks = self.bz2bz_ks
        kd.symmetry = self.symmetry
        kd.nibzkpts = self.nibzkpts
        kd.nks = self.nks
        kd.set_communicator(comm)
        return kd

    def create_k_points(self, gd):
        """Return a list of KPoints."""

        sdisp_cd = gd.sdisp_cd

        kpt_u = []

        for ks in range(self.ks0, self.ks0 + self.mynks):
            s, k = divmod(ks, self.nibzkpts)
            q = (ks - self.ks0) % self.nibzkpts
            if self.collinear:
                weight = self.weight_k[k] * 2 / self.nspins
            else:
                weight = self.weight_k[k]
            if self.gamma:
                phase_cd = np.ones((3, 2), complex)
            else:
                phase_cd = np.exp(2j * np.pi *
                                  sdisp_cd * self.ibzk_kc[k, :, np.newaxis])
            kpt_u.append(KPoint(weight, s, k, q, phase_cd))

        return kpt_u

    def collect(self, a_ux, broadcast=True):
        """Collect distributed data to all."""

        if self.comm.rank == 0 or broadcast:
            xshape = a_ux.shape[1:]
            a_skx = np.empty((self.nspins, self.nibzkpts) + xshape, a_ux.dtype)
            a_Ux = a_skx.reshape((-1,) + xshape)
        else:
            a_skx = None

        if self.comm.rank > 0:
            self.comm.send(a_ux, 0)
        else:
            u1 = self.get_count(0)
            a_Ux[0:u1] = a_ux
            requests = []
            for rank in range(1, self.comm.size):
                u2 = u1 + self.get_count(rank)
                requests.append(self.comm.receive(a_Ux[u1:u2], rank,
                                                  block=False))
                u1 = u2
            assert u1 == len(a_Ux)
            self.comm.waitall(requests)
        
        if broadcast:
            self.comm.broadcast(a_Ux, 0)

        return a_skx

    def transform_wave_function(self, psit_G, k, index_G=None, phase_G=None):
        """Transform wave function from IBZ to BZ.

        k is the index of the desired k-point in the full BZ.
        """
        
        s = self.sym_k[k]
        time_reversal = self.time_reversal_k[k]
        op_cc = np.linalg.inv(self.symmetry.op_scc[s]).round().astype(int)

        # Identity
        if (np.abs(op_cc - np.eye(3, dtype=int)) < 1e-10).all():
            if time_reversal:
                return psit_G.conj()
            else:
                return psit_G
        # General point group symmetry
        else:
            ik = self.bz2ibz_k[k]
            kibz_c = self.ibzk_kc[ik]
            b_g = np.zeros_like(psit_G)
            kbz_c = np.dot(self.symmetry.op_scc[s], kibz_c)
            if index_G is not None:
                assert index_G.shape == psit_G.shape == phase_G.shape,\
                    'Shape mismatch %s vs %s vs %s' % (index_G.shape,
                                                       psit_G.shape,
                                                       phase_G.shape)
                _gpaw.symmetrize_with_index(psit_G, b_g, index_G, phase_G)
            else:
                _gpaw.symmetrize_wavefunction(psit_G, b_g, op_cc.copy(),
                                              np.ascontiguousarray(kibz_c),
                                              kbz_c)

            if time_reversal:
                return b_g.conj()
            else:
                return b_g

    def get_transform_wavefunction_index(self, nG, k):
        """Get the "wavefunction transform index".

        This is a permutation of the numbers 1, 2, .. N which
        associates k + q to some k, and where N is the total
        number of grid points as specified by nG which is a
        3D tuple.
        
        Returns index_G and phase_G which are one-dimensional
        arrays on the grid."""
        
        s = self.sym_k[k]
        op_cc = np.linalg.inv(self.symmetry.op_scc[s]).round().astype(int)

        # General point group symmetry
        if (np.abs(op_cc - np.eye(3, dtype=int)) < 1e-10).all():
            nG0 = np.prod(nG)
            index_G = np.arange(nG0).reshape(nG)
            phase_G = np.ones(nG)
        else:
            ik = self.bz2ibz_k[k]
            kibz_c = self.ibzk_kc[ik]
            index_G = np.zeros(nG, dtype=int)
            phase_G = np.zeros(nG, dtype=complex)

            kbz_c = np.dot(self.symmetry.op_scc[s], kibz_c)
            _gpaw.symmetrize_return_index(index_G, phase_G, op_cc.copy(),
                                          np.ascontiguousarray(kibz_c),
                                          kbz_c)
        return index_G, phase_G

    #def find_k_plus_q(self, q_c, k_x=None):
    def find_k_plus_q(self, q_c, kpts_k=None):
        """Find the indices of k+q for all kpoints in the Brillouin zone.
        
        In case that k+q is outside the BZ, the k-point inside the BZ
        corresponding to k+q is given.
        
        Parameters
        ----------
        q_c: ndarray
            Coordinates for the q-vector in units of the reciprocal
            lattice vectors.
        kpts_k: list of ints
            Restrict search to specified k-points.

        """
        k_x = kpts_k
        if k_x is None:
            return self.find_k_plus_q(q_c, range(self.nbzkpts))

        i_x = []
        for k in k_x:
            kpt_c = self.bzk_kc[k] + q_c
            d_kc = kpt_c - self.bzk_kc
            d_k = abs(d_kc - d_kc.round()).sum(1)
            i = d_k.argmin()
            if d_k[i] > 1e-8:
                raise RuntimeError('Could not find k+q!')
            i_x.append(i)
        
        return i_x

    def get_bz_q_points(self, first=False):
        """Return the q=k1-k2. q-mesh is always Gamma-centered."""
        shift_c = 0.5 * ((self.N_c + 1) % 2) / self.N_c
        bzq_qc = monkhorst_pack(self.N_c) + shift_c
        if first:
            return to1bz(bzq_qc, self.cell_cv)
        else:
            return bzq_qc
        
    def get_ibz_q_points(self, bzq_qc, op_scc):
        """Return ibz q points and the corresponding symmetry operations that
        work for k-mesh as well."""

        ibzq_qc_tmp = []
        ibzq_qc_tmp.append(bzq_qc[-1])
        weight_tmp = [0]
        
        for i, op_cc in enumerate(op_scc):
            if np.abs(op_cc - np.eye(3)).sum() < 1e-8:
                identity_iop = i
                break

        ibzq_q_tmp = {}
        iop_q = {}
        timerev_q = {}
        diff_qc = {}

        for i in range(len(bzq_qc) - 1, -1, -1):  # loop opposite to kpoint
            try:
                ibzk, iop, timerev, diff_c = self.find_ibzkpt(
                    op_scc, ibzq_qc_tmp, bzq_qc[i])
                find = False
                for ii, iop1 in enumerate(self.sym_k):
                    if iop1 == iop and self.time_reversal_k[ii] == timerev:
                        find = True
                        break
                if find is False:
                    raise ValueError('cant find k!')
                    
                ibzq_q_tmp[i] = ibzk
                weight_tmp[ibzk] += 1.
                iop_q[i] = iop
                timerev_q[i] = timerev
                diff_qc[i] = diff_c
            except ValueError:
                ibzq_qc_tmp.append(bzq_qc[i])
                weight_tmp.append(1.)
                ibzq_q_tmp[i] = len(ibzq_qc_tmp) - 1
                iop_q[i] = identity_iop
                timerev_q[i] = False
                diff_qc[i] = np.zeros(3)

        # reverse the order.
        nq = len(ibzq_qc_tmp)
        ibzq_qc = np.zeros((nq, 3))
        ibzq_q = np.zeros(len(bzq_qc), dtype=int)
        for i in range(nq):
            ibzq_qc[i] = ibzq_qc_tmp[nq - i - 1]
        for i in range(len(bzq_qc)):
            ibzq_q[i] = nq - ibzq_q_tmp[i] - 1
        self.q_weights = np.array(weight_tmp[::-1]) / len(bzq_qc)
        return ibzq_qc, ibzq_q, iop_q, timerev_q, diff_qc

    def find_ibzkpt(self, symrel, ibzk_kc, bzk_c):
        """Find index in IBZ and related symmetry operations."""
        find = False
        ibzkpt = 0
        iop = 0
        timerev = False

        for sign in (1, -1):
            for ioptmp, op in enumerate(symrel):
                for i, ibzk in enumerate(ibzk_kc):
                    diff_c = bzk_c - sign * np.dot(op, ibzk)
                    if (np.abs(diff_c - diff_c.round()) < 1e-8).all():
                        ibzkpt = i
                        iop = ioptmp
                        find = True
                        if sign == -1:
                            timerev = True
                        break
                if find == True:
                    break
            if find == True:
                break

        if find == False:
            raise ValueError('Cant find corresponding IBZ kpoint!')
        return ibzkpt, iop, timerev, diff_c.round()

    def where_is_q(self, q_c, bzq_qc):
        """Find the index of q points in BZ."""
        d_qc = q_c - bzq_qc
        d_q = abs(d_qc - d_qc.round()).sum(1)
        q = d_q.argmin()
        if d_q[q] > 1e-8:
            raise RuntimeError('Could not find q!')
        return q

    def get_count(self, rank=None):
        """Return the number of ks-pairs which belong to a given rank."""

        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        mynks0 = self.nks // self.comm.size
        mynks = mynks0
        if rank >= self.rank0:
            mynks += 1
        return mynks

    def get_offset(self, rank=None):
        """Return the offset of the first ks-pair on a given rank."""

        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        mynks0 = self.nks // self.comm.size
        ks0 = rank * mynks0
        if rank >= self.rank0:
            ks0 += rank - self.rank0
        return ks0

    def get_rank_and_index(self, s, k):
        """Find rank and local index of k-point/spin combination."""
        
        u = self.where_is(s, k)
        rank, myu = self.who_has(u)
        return rank, myu
   
    def get_slice(self, rank=None):
        """Return the slice of global ks-pairs which belong to a given rank."""
        
        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        mynks, ks0 = self.get_count(rank), self.get_offset(rank)
        uslice = slice(ks0, ks0 + mynks)
        return uslice

    def get_indices(self, rank=None):
        """Return the global ks-pair indices which belong to a given rank."""
        
        uslice = self.get_slice(rank)
        return np.arange(*uslice.indices(self.nks))

    def get_ranks(self):
        """Return array of ranks as a function of global ks-pair indices."""
        
        ranks = np.empty(self.nks, dtype=int)
        for rank in range(self.comm.size):
            uslice = self.get_slice(rank)
            ranks[uslice] = rank
        assert (ranks >= 0).all() and (ranks < self.comm.size).all()
        return ranks

    def who_has(self, u):
        """Convert global index to rank information and local index."""

        mynks0 = self.nks // self.comm.size
        if u < mynks0 * self.rank0:
            rank, myu = divmod(u, mynks0)
        else:
            rank, myu = divmod(u - mynks0 * self.rank0, mynks0 + 1)
            rank += self.rank0
        return rank, myu

    def global_index(self, myu, rank=None):
        """Convert rank information and local index to global index."""
        
        if rank is None:
            rank = self.comm.rank
        assert rank in xrange(self.comm.size)
        ks0 = self.get_offset(rank)
        u = ks0 + myu
        return u

    def what_is(self, u):
        """Convert global index to corresponding kpoint/spin combination."""
        
        s, k = divmod(u, self.nibzkpts)
        return s, k

    def where_is(self, s, k):
        """Convert kpoint/spin combination to the global index thereof."""
        
        u = k + self.nibzkpts * s
        return u
Пример #14
0
    def set_symmetry(self, atoms, setups, magmom_av=None,
                     usesymm=False, N_c=None, comm=None):
        """Create symmetry object and construct irreducible Brillouin zone.

        atoms: Atoms object
            Defines atom positions and types and also unit cell and
            boundary conditions.
        setups: instance of class Setups
            PAW setups for the atoms.
        magmom_av: ndarray
            Initial magnetic moments.
        usesymm: bool
            Symmetry flag.
        N_c: three int's or None
            If not None:  Check also symmetry of grid.
        """

        if atoms is not None:
            for c, periodic in enumerate(atoms.pbc):
                if not periodic and not np.allclose(self.bzk_kc[:, c], 0.0):
                    raise ValueError('K-points can only be used with PBCs!')

            self.cell_cv = atoms.cell / Bohr
     
            if magmom_av is None:
                magmom_av = np.zeros((len(atoms), 3))
                magmom_av[:, 2] = atoms.get_initial_magnetic_moments()

            magmom_av = magmom_av.round(decimals=3)  # round off
            id_a = zip(setups.id_a, *magmom_av.T)

            # Construct a Symmetry instance containing the identity operation
            # only
            self.symmetry = Symmetry(id_a, atoms.cell / Bohr, atoms.pbc, fractrans=self.usefractrans)
            self.usefractrans = self.symmetry.usefractrans
        else:
            self.symmetry = None
        
        if self.gamma or usesymm is None:
            # Point group and time-reversal symmetry neglected
            self.weight_k = np.ones(self.nbzkpts) / self.nbzkpts
            self.ibzk_kc = self.bzk_kc.copy()
            self.sym_k = np.zeros(self.nbzkpts, int)
            self.time_reversal_k = np.zeros(self.nbzkpts, bool)
            self.bz2ibz_k = np.arange(self.nbzkpts)
            self.ibz2bz_k = np.arange(self.nbzkpts)
            self.bz2bz_ks = np.arange(self.nbzkpts)[:, np.newaxis]
        else:
            if usesymm:
                # Find symmetry operations of atoms
                self.symmetry.analyze(atoms.get_scaled_positions())
              
                if N_c is not None:
                    if self.usefractrans:
                        ## adjust N_c to symmetries
                        # the factor (denominator) the grid must follow
                        factor = np.ones(3, float)
                        indexes = np.where(np.abs(self.symmetry.ft_sc) > 1e-3)
                        for i in range(len(indexes[0])):
                            # find smallest common denominator
                            a = factor[indexes[1][i]]
                            b = np.rint(1. /  self.symmetry.ft_sc[indexes[0][i]][indexes[1][i]])
                            factor[indexes[1][i]] = a * b
                            while b != 0:
                                rem = a % b
                                a = b
                                b = rem
                            factor[indexes[1][i]] /= a 
                        Nnew_c = np.array(np.rint(N_c / factor) * factor, int)
                        # make sure new grid is not less dense
                        Nnew_c = np.array(np.where(Nnew_c >= N_c, Nnew_c, Nnew_c + factor), int)
                        N_c = Nnew_c
                    else:
                        ## adjust symmetries to grid 
                        self.symmetry.prune_symmetries_grid(N_c)

            (self.ibzk_kc, self.weight_k,
             self.sym_k,
             self.time_reversal_k,
             self.bz2ibz_k,
             self.ibz2bz_k,
             self.bz2bz_ks) = self.symmetry.reduce(self.bzk_kc, comm)
            
        if setups is not None:
            setups.set_symmetry(self.symmetry)

        # Number of irreducible k-points and k-point/spin combinations.
        self.nibzkpts = len(self.ibzk_kc)
        if self.collinear:
            self.nks = self.nibzkpts * self.nspins
        else:
            self.nks = self.nibzkpts

        return N_c
Пример #15
0
    def set_symmetry(self, atoms, setups, magmom_av=None,
                     usesymm=False, N_c=None, comm=None):
        """Create symmetry object and construct irreducible Brillouin zone.

        atoms: Atoms object
            Defines atom positions and types and also unit cell and
            boundary conditions.
        setups: instance of class Setups
            PAW setups for the atoms.
        magmom_av: ndarray
            Initial magnetic moments.
        usesymm: bool
            Symmetry flag.
        N_c: three int's or None
            If not None:  Check also symmetry of grid.
        """

        if atoms is not None:
            if (~atoms.pbc & self.bzk_kc.any(0)).any():
                raise ValueError('K-points can only be used with PBCs!')

            self.cell_cv = atoms.cell / Bohr
     
            if magmom_av is None:
                magmom_av = np.zeros((len(atoms), 3))
                magmom_av[:, 2] = atoms.get_initial_magnetic_moments()

            magmom_av = magmom_av.round(decimals=3)  # round off
            id_a = zip(setups.id_a, *magmom_av.T)

            # Construct a Symmetry instance containing the identity operation
            # only
            self.symmetry = Symmetry(id_a, atoms.cell / Bohr, atoms.pbc)
        else:
            self.symmetry = None
        
        if self.gamma or usesymm is None:
            # Point group and time-reversal symmetry neglected
            self.weight_k = np.ones(self.nbzkpts) / self.nbzkpts
            self.ibzk_kc = self.bzk_kc.copy()
            self.sym_k = np.zeros(self.nbzkpts, int)
            self.time_reversal_k = np.zeros(self.nbzkpts, bool)
            self.bz2ibz_k = np.arange(self.nbzkpts)
            self.ibz2bz_k = np.arange(self.nbzkpts)
            self.bz2bz_ks = np.arange(self.nbzkpts)[:, np.newaxis]
        else:
            if usesymm:
                # Find symmetry operations of atoms
                self.symmetry.analyze(atoms.get_scaled_positions())
                
                if N_c is not None:
                    self.symmetry.prune_symmetries_grid(N_c)

            (self.ibzk_kc, self.weight_k,
             self.sym_k,
             self.time_reversal_k,
             self.bz2ibz_k,
             self.ibz2bz_k,
             self.bz2bz_ks) = self.symmetry.reduce(self.bzk_kc, comm)
            
        if setups is not None:
            setups.set_symmetry(self.symmetry)

        # Number of irreducible k-points and k-point/spin combinations.
        self.nibzkpts = len(self.ibzk_kc)
        if self.collinear:
            self.nks = self.nibzkpts * self.nspins
        else:
            self.nks = self.nibzkpts
Пример #16
0
def get_realspace_hs(h_skmm, s_kmm, bzk_kc, weight_k,
                     R_c=(0, 0, 0), direction='x', 
                     usesymm=None):

    from gpaw.symmetry import Symmetry
    from ase.dft.kpoints import get_monkhorst_pack_size_and_offset, \
        monkhorst_pack
    
    if usesymm is True:
        raise NotImplementedError, 'Only None and False have been implemented'

    nspins, nk, nbf = h_skmm.shape[:3]
    dir = 'xyz'.index(direction)
    transverse_dirs = np.delete([0, 1, 2], [dir])
    dtype = float
    if len(bzk_kc) > 1 or np.any(bzk_kc[0] != [0, 0, 0]):
        dtype = complex

    kpts_grid = get_monkhorst_pack_size_and_offset(bzk_kc)[0]

    # kpts in the transport direction
    nkpts_p = kpts_grid[dir]
    bzk_p_kc = monkhorst_pack((nkpts_p,1,1))[:, 0]
    weight_p_k = 1. / nkpts_p

   # kpts in the transverse directions
    bzk_t_kc = monkhorst_pack(tuple(kpts_grid[transverse_dirs]) + (1, ))
    if usesymm == False:
        #XXX a somewhat ugly hack:
        # By default GPAW reduces inversion sym in the z direction. The steps 
        # below assure reduction in the transverse dirs.
        # For now this part seems to do the job, but it may be written
        # in a smarter way in the future.
        symmetry = Symmetry([1], np.eye(3))
        symmetry.prune_symmetries(np.zeros((1, 3)))
        ibzk_kc, ibzweight_k = symmetry.reduce(bzk_kc)[:2]
        ibzk_t_kc, weights_t_k = symmetry.reduce(bzk_t_kc)[:2]
        ibzk_t_kc = ibzk_t_kc[:, :2]
        nkpts_t = len(ibzk_t_kc)
    else: # usesymm = None
        ibzk_kc = bzk_kc.copy()
        ibzk_t_kc = bzk_t_kc
        nkpts_t = len(bzk_t_kc)
        weights_t_k = [1. / nkpts_t for k in range(nkpts_t)]

    h_skii = np.zeros((nspins, nkpts_t, nbf, nbf), dtype)
    if s_kmm is not None:
        s_kii = np.zeros((nkpts_t, nbf, nbf), dtype)

    tol = 7
    for j, k_t in enumerate(ibzk_t_kc):
        for k_p in bzk_p_kc:
            k = np.zeros((3,))
            k[dir] = k_p
            k[transverse_dirs] = k_t
            kpoint_list = [list(np.round(k_kc, tol)) for k_kc in ibzk_kc]
            if list(np.round(k, tol)) not in kpoint_list:
                k = -k # inversion
                index = kpoint_list.index(list(np.round(k,tol)))
                h = h_skmm[:, index].conjugate()
                if s_kmm is not None:
                    s = s_kmm[index].conjugate()
                k=-k
            else: # kpoint in the ibz
                index = kpoint_list.index(list(np.round(k, tol)))
                h = h_skmm[:, index]
                if s_kmm is not None:
                    s = s_kmm[index]

            c_k = np.exp(2.j * np.pi * np.dot(k, R_c)) * weight_p_k
            h_skii[:, j] += c_k * h
            if s_kmm is not None:
                s_kii[j] += c_k * s 
    
    if s_kmm is None:
        return ibzk_t_kc, weights_t_k, h_skii
    else:
        return ibzk_t_kc, weights_t_k, h_skii, s_kii
Пример #17
0
from math import sqrt
import numpy as np

from gpaw.symmetry import Symmetry
from ase.dft.kpoints import monkhorst_pack

# Primitive diamond lattice, with Si lattice parameter
a = 5.475
cell_cv = .5 * a * np.array([(1, 1, 0), (1, 0, 1), (0, 1, 1)])
spos_ac = np.array([(.00, .00, .00), (.25, .25, .25)])
id_a = [1, 1]  # Two identical atoms
pbc_c = np.ones(3, bool)
bzk_kc = monkhorst_pack((4, 4, 4))

# Do check
symm = Symmetry(id_a, cell_cv, pbc_c, symmorphic=False)
symm.analyze(spos_ac)
ibzk_kc, w_k = symm.reduce(bzk_kc)[:2]
assert len(symm.op_scc) == 48
assert len(w_k) == 10
a = 3 / 32.
b = 1 / 32.
c = 6 / 32.
assert np.all(w_k == [a, b, a, c, c, a, a, a, a, b])
assert not symm.op_scc.sum(0).any()

# Rotate unit cell and check again:
cell_cv = a / sqrt(2) * np.array([(1, 0, 0), (0.5, sqrt(3) / 2, 0),
                                  (0.5, sqrt(3) / 6, sqrt(2.0 / 3))])
symm = Symmetry(id_a, cell_cv, pbc_c, symmorphic=False)
symm.analyze(spos_ac)
Пример #18
0
)
atoms.set_calculator(calc)
atoms.get_potential_energy()

# "Bandstructure" calculation (only Gamma point here)
kpts = np.array(((0, 0, 0), ))
calc.set(fixdensity=True, kpts=kpts)
atoms.get_potential_energy()
calc.write('Si_gamma.gpw', mode='all')

# Analyse symmetries of wave functions
atoms, calc = restart('Si_gamma.gpw', txt=None)

# Find symmetries (in Gamma-point calculations calculator does not
# use symmetry)
sym = Symmetry(calc.wfs.setups.id_a, atoms.cell, atoms.pbc)
sym.analyze(atoms.get_scaled_positions())


def find_classes(op_all_scc):
    # Find classes of group represented by matrices op_all_scc
    # and return representative operations
    op_scc = [op_all_scc[0]]
    for op1 in op_all_scc[1:]:
        new_class = True
        for op2 in op_all_scc:
            op_tmp = (np.dot(np.dot(op2, op1), np.linalg.inv(op2).astype(int)))
            # Check whether operation op1 belongs to existing class
            for op in op_scc:
                if np.all((op_tmp - op) == 0):
                    new_class = False
Пример #19
0
    def initialize(self, atoms=None):
        """Inexpensive initialization."""

        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        par = self.input_parameters

        world = par.communicator
        if world is None:
            world = mpi.world
        elif hasattr(world, 'new_communicator'):
            # Check for whether object has correct type already
            #
            # Using isinstance() is complicated because of all the
            # combinations, serial/parallel/debug...
            pass
        else:
            # world should be a list of ranks:
            world = mpi.world.new_communicator(np.asarray(world))
        self.wfs.world = world

        if 'txt' in self._changed_keywords:
            self.set_txt(par.txt)
        self.verbose = par.verbose

        natoms = len(atoms)

        cell_cv = atoms.get_cell() / Bohr
        pbc_c = atoms.get_pbc()
        Z_a = atoms.get_atomic_numbers()
        magmom_av = atoms.get_initial_magnetic_moments()

        self.check_atoms()

        # Generate new xc functional only when it is reset by set
        # XXX sounds like this should use the _changed_keywords dictionary.
        if self.hamiltonian is None or self.hamiltonian.xc is None:
            if isinstance(par.xc, str):
                xc = XC(par.xc)
            else:
                xc = par.xc
        else:
            xc = self.hamiltonian.xc

        mode = par.mode

        if mode == 'fd':
            mode = FD()
        elif mode == 'pw':
            mode = pw.PW()
        elif mode == 'lcao':
            mode = LCAO()
        else:
            assert hasattr(mode, 'name'), str(mode)

        if xc.orbital_dependent and mode.name == 'lcao':
            raise NotImplementedError('LCAO mode does not support '
                                      'orbital-dependent XC functionals.')

        if par.realspace is None:
            realspace = (mode.name != 'pw')
        else:
            realspace = par.realspace
            if mode.name == 'pw':
                assert not realspace

        if par.filter is None and mode.name != 'pw':
            gamma = 1.6
            if par.gpts is not None:
                h = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5
                     / par.gpts).max()
            else:
                h = (par.h or 0.2) / Bohr

            def filter(rgd, rcut, f_r, l=0):
                gcut = np.pi / h - 2 / rcut / gamma
                f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l)
        else:
            filter = par.filter

        setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc,
                        filter, world)

        if magmom_av.ndim == 1:
            collinear = True
            magmom_av, magmom_a = np.zeros((natoms, 3)), magmom_av
            magmom_av[:, 2] = magmom_a
        else:
            collinear = False

        magnetic = magmom_av.any()

        spinpol = par.spinpol
        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True
            magmom_av[0] = (0, 0, setups[0].get_hunds_rule_moment(par.charge))

        if spinpol is None:
            spinpol = magnetic
        elif magnetic and not spinpol:
            raise ValueError('Non-zero initial magnetic moment for a ' +
                             'spin-paired calculation!')

        if collinear:
            nspins = 1 + int(spinpol)
            ncomp = 1
        else:
            nspins = 1
            ncomp = 2

        if par.usesymm != 'default':
            warnings.warn('Use "symmetry" keyword instead of ' +
                          '"usesymm" keyword')
            par.symmetry = usesymm2symmetry(par.usesymm)

        symm = par.symmetry
        if symm == 'off':
            symm = {'point_group': False, 'time_reversal': False}

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kd = KPointDescriptor(bzkpts_kc, nspins, collinear)
        m_av = magmom_av.round(decimals=3)  # round off
        id_a = zip(setups.id_a, *m_av.T)
        symmetry = Symmetry(id_a, cell_cv, atoms.pbc, **symm)
        kd.set_symmetry(atoms, symmetry, comm=world)
        setups.set_symmetry(symmetry)

        if par.gpts is not None:
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace,
                                            kd.symmetry)

        symmetry.check_grid(N_c)

        width = par.width
        if width is None:
            if pbc_c.any():
                width = 0.1  # eV
            else:
                width = 0.0
        else:
            assert par.occupations is None

        if hasattr(self, 'time') or par.dtype == complex:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        nao = setups.nao
        nvalence = setups.nvalence - par.charge
        M_v = magmom_av.sum(0)
        M = np.dot(M_v, M_v) ** 0.5

        nbands = par.nbands
        
        orbital_free = any(setup.orbital_free for setup in setups)
        if orbital_free:
            nbands = 1

        if isinstance(nbands, basestring):
            if nbands[-1] == '%':
                basebands = int(nvalence + M + 0.5) // 2
                nbands = int((float(nbands[:-1]) / 100) * basebands)
            else:
                raise ValueError('Integer Expected: Only use a string '
                                 'if giving a percentage of occupied bands')

        if nbands is None:
            nbands = 0
            for setup in setups:
                nbands_from_atom = setup.get_default_nbands()

                # Any obscure setup errors?
                if nbands_from_atom < -(-setup.Nv // 2):
                    raise ValueError('Bad setup: This setup requests %d'
                                     ' bands but has %d electrons.'
                                     % (nbands_from_atom, setup.Nv))
                nbands += nbands_from_atom
            nbands = min(nao, nbands)
        elif nbands > nao and mode.name == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: '
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        if nbands <= 0:
            nbands = int(nvalence + M + 0.5) // 2 + (-nbands)

        if nvalence > 2 * nbands and not orbital_free:
            raise ValueError('Too few bands!  Electrons: %f, bands: %d'
                             % (nvalence, nbands))

        nbands *= ncomp

        if par.width is not None:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width).')
        if par.fixmom:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width, fixmagmom=True).')

        if self.occupations is None:
            if par.occupations is None:
                # Create object for occupation numbers:
                if orbital_free:
                    width = 0.0  # even for PBC
                    self.occupations = occupations.TFOccupations(width,
                                                                 par.fixmom)
                else:
                    self.occupations = occupations.FermiDirac(width,
                                                              par.fixmom)
            else:
                self.occupations = par.occupations

            # If occupation numbers are changed, and we have wave functions,
            # recalculate the occupation numbers
            if self.wfs is not None and not isinstance(
                    self.wfs,
                    EmptyWaveFunctions):
                self.occupations.calculate(self.wfs)

        self.occupations.magmom = M_v[2]

        cc = par.convergence

        if mode.name == 'lcao':
            niter_fixdensity = 0
        else:
            niter_fixdensity = None

        if self.scf is None:
            force_crit = cc['forces']
            if force_crit is not None:
                force_crit /= Hartree / Bohr
            self.scf = SCFLoop(
                cc['eigenstates'] / Hartree**2 * nvalence,
                cc['energy'] / Hartree * max(nvalence, 1),
                cc['density'] * nvalence,
                par.maxiter, par.fixdensity,
                niter_fixdensity,
                force_crit)

        parsize_kpt = par.parallel['kpt']
        parsize_domain = par.parallel['domain']
        parsize_bands = par.parallel['band']

        if not realspace:
            pbc_c = np.ones(3, bool)

        if not self.wfs:
            if parsize_domain == 'domain only':  # XXX this was silly!
                parsize_domain = world.size

            parallelization = mpi.Parallelization(world,
                                                  nspins * kd.nibzkpts)
            ndomains = None
            if parsize_domain is not None:
                ndomains = np.prod(parsize_domain)
            if mode.name == 'pw':
                if ndomains > 1:
                    raise ValueError('Planewave mode does not support '
                                     'domain decomposition.')
                ndomains = 1
            parallelization.set(kpt=parsize_kpt,
                                domain=ndomains,
                                band=parsize_bands)
            comms = parallelization.build_communicators()
            domain_comm = comms['d']
            kpt_comm = comms['k']
            band_comm = comms['b']
            kptband_comm = comms['D']
            domainband_comm = comms['K']

            self.comms = comms
            kd.set_communicator(kpt_comm)

            parstride_bands = par.parallel['stridebands']

            # Unfortunately we need to remember that we adjusted the
            # number of bands so we can print a warning if it differs
            # from the number specified by the user.  (The number can
            # be inferred from the input parameters, but it's tricky
            # because we allow negative numbers)
            self.nbands_parallelization_adjustment = -nbands % band_comm.size
            nbands += self.nbands_parallelization_adjustment

            # I would like to give the following error message, but apparently
            # there are cases, e.g. gpaw/test/gw_ppa.py, which involve
            # nbands > nao and are supposed to work that way.
            #if nbands > nao:
            #    raise ValueError('Number of bands %d adjusted for band '
            #                     'parallelization %d exceeds number of atomic '
            #                     'orbitals %d.  This problem can be fixed '
            #                     'by reducing the number of bands a bit.'
            #                     % (nbands, band_comm.size, nao))
            bd = BandDescriptor(nbands, band_comm, parstride_bands)

            if (self.density is not None and
                    self.density.gd.comm.size != domain_comm.size):
                # Domain decomposition has changed, so we need to
                # reinitialize density and hamiltonian:
                if par.fixdensity:
                    raise RuntimeError(
                        'Density reinitialization conflict ' +
                        'with "fixdensity" - specify domain decomposition.')
                self.density = None
                self.hamiltonian = None

            # Construct grid descriptor for coarse grids for wave functions:
            gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c,
                                            domain_comm, parsize_domain)

            # do k-point analysis here? XXX
            args = (gd, nvalence, setups, bd, dtype, world, kd,
                    kptband_comm, self.timer)

            if par.parallel['sl_auto']:
                # Choose scalapack parallelization automatically

                for key, val in par.parallel.items():
                    if (key.startswith('sl_') and key != 'sl_auto'
                            and val is not None):
                        raise ValueError("Cannot use 'sl_auto' together "
                                         "with '%s'" % key)
                max_scalapack_cpus = bd.comm.size * gd.comm.size
                nprow = max_scalapack_cpus
                npcol = 1

                # Get a sort of reasonable number of columns/rows
                while npcol < nprow and nprow % 2 == 0:
                    npcol *= 2
                    nprow //= 2
                assert npcol * nprow == max_scalapack_cpus

                # ScaLAPACK creates trouble if there aren't at least a few
                # whole blocks; choose block size so there will always be
                # several blocks.  This will crash for small test systems,
                # but so will ScaLAPACK in any case
                blocksize = min(-(-nbands // 4), 64)
                sl_default = (nprow, npcol, blocksize)
            else:
                sl_default = par.parallel['sl_default']

            if mode.name == 'lcao':
                # Layouts used for general diagonalizer
                sl_lcao = par.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao',
                                               gd, bd, domainband_comm, dtype,
                                               nao=nao, timer=self.timer)

                self.wfs = mode(collinear, lcaoksl, *args)

            elif mode.name == 'fd' or mode.name == 'pw':
                # buffer_size keyword only relevant for fdpw
                buffer_size = par.parallel['buffer_size']
                # Layouts used for diagonalizer
                sl_diagonalize = par.parallel['sl_diagonalize']
                if sl_diagonalize is None:
                    sl_diagonalize = sl_default
                diagksl = get_KohnSham_layouts(sl_diagonalize, 'fd',  # XXX
                                               # choice of key 'fd' not so nice
                                               gd, bd, domainband_comm, dtype,
                                               buffer_size=buffer_size,
                                               timer=self.timer)

                # Layouts used for orthonormalizer
                sl_inverse_cholesky = par.parallel['sl_inverse_cholesky']
                if sl_inverse_cholesky is None:
                    sl_inverse_cholesky = sl_default
                if sl_inverse_cholesky != sl_diagonalize:
                    message = 'sl_inverse_cholesky != sl_diagonalize ' \
                        'is not implemented.'
                    raise NotImplementedError(message)
                orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd',
                                                gd, bd, domainband_comm, dtype,
                                                buffer_size=buffer_size,
                                                timer=self.timer)

                # Use (at most) all available LCAO for initialization
                lcaonbands = min(nbands, nao)

                try:
                    lcaobd = BandDescriptor(lcaonbands, band_comm,
                                            parstride_bands)
                except RuntimeError:
                    initksl = None
                else:
                    # Layouts used for general diagonalizer
                    # (LCAO initialization)
                    sl_lcao = par.parallel['sl_lcao']
                    if sl_lcao is None:
                        sl_lcao = sl_default
                    initksl = get_KohnSham_layouts(sl_lcao, 'lcao',
                                                   gd, lcaobd, domainband_comm,
                                                   dtype, nao=nao,
                                                   timer=self.timer)

                if hasattr(self, 'time'):
                    assert mode.name == 'fd'
                    from gpaw.tddft import TimeDependentWaveFunctions
                    self.wfs = TimeDependentWaveFunctions(
                        par.stencils[0],
                        diagksl,
                        orthoksl,
                        initksl,
                        gd,
                        nvalence,
                        setups,
                        bd,
                        world,
                        kd,
                        kptband_comm,
                        self.timer)
                elif mode.name == 'fd':
                    self.wfs = mode(par.stencils[0], diagksl,
                                    orthoksl, initksl, *args)
                else:
                    assert mode.name == 'pw'
                    self.wfs = mode(diagksl, orthoksl, initksl, *args)
            else:
                self.wfs = mode(self, *args)
        else:
            self.wfs.set_setups(setups)

        if not self.wfs.eigensolver:
            # Number of bands to converge:
            nbands_converge = cc['bands']
            if nbands_converge == 'all':
                nbands_converge = nbands
            elif nbands_converge != 'occupied':
                assert isinstance(nbands_converge, int)
                if nbands_converge < 0:
                    nbands_converge += nbands
            eigensolver = get_eigensolver(par.eigensolver, mode,
                                          par.convergence)
            eigensolver.nbands_converge = nbands_converge
            # XXX Eigensolver class doesn't define an nbands_converge property

            if isinstance(xc, SIC):
                eigensolver.blocksize = 1
            self.wfs.set_eigensolver(eigensolver)

        if self.density is None:
            gd = self.wfs.gd
            if par.stencils[1] != 9:
                # Construct grid descriptor for fine grids for densities
                # and potentials:
                finegd = gd.refine()
            else:
                # Special case (use only coarse grid):
                finegd = gd

            if realspace:
                self.density = RealSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear, par.stencils[1])
            else:
                self.density = pw.ReciprocalSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear)

        self.density.initialize(setups, self.timer, magmom_av, par.hund)
        self.density.set_mixer(par.mixer)

        if self.hamiltonian is None:
            gd, finegd = self.density.gd, self.density.finegd
            if realspace:
                self.hamiltonian = RealSpaceHamiltonian(
                    gd, finegd, nspins, setups, self.timer, xc,
                    world, self.wfs.kptband_comm, par.external,
                    collinear, par.poissonsolver, par.stencils[1])
            else:
                self.hamiltonian = pw.ReciprocalSpaceHamiltonian(
                    gd, finegd, self.density.pd2, self.density.pd3,
                    nspins, setups, self.timer, xc, world,
                    self.wfs.kptband_comm, par.external, collinear)

        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)

        self.text()
        self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth)
        self.txt.flush()

        self.timer.print_info(self)

        if dry_run:
            self.dry_run()

        if realspace and \
                self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT':
            self.hamiltonian.poisson.set_density(self.density)
            self.hamiltonian.poisson.print_messages(self.text)
            self.txt.flush()

        self.initialized = True
        self._changed_keywords.clear()