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_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 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 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
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
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
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
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
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]
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
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()
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
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_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
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
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)
) 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
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()