def initialize_clgd(self): N_c = get_number_of_grid_points(self.cl.cell, self.cl.spacing) self.cl.spacing = np.diag(self.cl.cell) / N_c self.cl.gd = GridDescriptor(N_c, self.cl.cell, False, self.cl.dcomm, self.cl.dparsize) self.cl.gd_global = GridDescriptor(N_c, self.cl.cell, False, serial_comm, None) self.cl.extrapolated_qm_phi = self.cl.gd.empty()
def __call__(self, name, atoms): kpts = self.calculate_kpts(atoms) kwargs = self.kwargs.copy() # modify a copy if (not atoms.pbc.any() and len(atoms) == 1 and atoms.get_initial_magnetic_moments().any() and 'hund' not in kwargs): kwargs['hund'] = True if atoms.pbc.any() and 'gpts' not in kwargs: # Use fixed number of gpts: h = kwargs.get('h') if h is not None: h /= Bohr cell_cv = atoms.cell / Bohr mode = kwargs.get('mode') if mode == 'pw': mode = PW() gpts = get_number_of_grid_points(cell_cv, h, mode, kwargs.get('realspace')) kwargs['h'] = None kwargs['gpts'] = gpts if isinstance(mode, PW): kwargs['mode'] = PW(mode.ecut * Hartree, mode.fftwflags, atoms.cell) if self.show_text_output: txt = '-' else: txt = name + '.txt' if self.write_gpw_file is not None: from gpaw.hooks import hooks hooks['converged'] = ( lambda calc, name=name + '.gpw', mode=self.write_gpw_file: calc.write(name, mode)) from gpaw import GPAW return GPAW(txt=txt, kpts=kpts, **kwargs)
def __call__(self, name, atoms): kpts = self.calculate_kpts(atoms) kwargs = self.kwargs.copy() # modify a copy if (not atoms.pbc.any() and len(atoms) == 1 and atoms.get_initial_magnetic_moments().any() and 'hund' not in kwargs): kwargs['hund'] = True if atoms.pbc.any() and 'gpts' not in kwargs: # Use fixed number of gpts: h = kwargs.get('h') if h is not None: h /= Bohr cell_cv = atoms.cell / Bohr mode = kwargs.get('mode') if mode == 'pw': mode = PW() gpts = get_number_of_grid_points(cell_cv, h, mode, kwargs.get('realspace')) kwargs['h'] = None kwargs['gpts'] = gpts if isinstance(mode, PW): kwargs['mode'] = PW(mode.ecut * Hartree, mode.fftwflags, atoms.cell) if self.show_text_output: txt = '-' else: txt = name + '.txt' from gpaw import GPAW return GPAW(txt=txt, kpts=kpts, **kwargs)
def create_subsystems(self, atoms_in): # Create new Atoms object atoms_out = atoms_in.copy() # New quantum grid self.qm.cell = \ np.diag([(self.shift_indices_2[w] - self.shift_indices_1[w]) * self.cl.spacing[w] for w in range(3)]) self.qm.spacing = self.cl.spacing / self.hratios N_c = get_number_of_grid_points(self.qm.cell, self.qm.spacing) atoms_out.set_cell(np.diag(self.qm.cell) * Bohr) atoms_out.positions = atoms_in.get_positions() - self.qm.corner1 * Bohr msg = self.messages.append msg("Quantum box readjustment:") msg(" Given cell: [%10.5f %10.5f %10.5f]" % tuple(np.diag(atoms_in.get_cell()))) msg(" Given atomic coordinates:") for s, c in zip(atoms_in.get_chemical_symbols(), atoms_in.get_positions()): msg(" %s %10.5f %10.5f %10.5f" % (s, c[0], c[1], c[2])) msg(" Readjusted cell: [%10.5f %10.5f %10.5f]" % tuple(np.diag(atoms_out.get_cell()))) msg(" Readjusted atomic coordinates:") for s, c in zip(atoms_out.get_chemical_symbols(), atoms_out.get_positions()): msg(" %s %10.5f %10.5f %10.5f" % (s, c[0], c[1], c[2])) msg(" Given corner points: " + "(%10.5f %10.5f %10.5f) - (%10.5f %10.5f %10.5f)" % (tuple(np.concatenate((self.given_corner_v1, self.given_corner_v2))))) msg(" Readjusted corner points: " + "(%10.5f %10.5f %10.5f) - (%10.5f %10.5f %10.5f)" % (tuple(np.concatenate((self.qm.corner1, self.qm.corner2)) * Bohr))) msg(" Indices in classical grid: " + "(%10i %10i %10i) - (%10i %10i %10i)" % (tuple(np.concatenate((self.shift_indices_1, self.shift_indices_2))))) msg(" Grid points in classical grid: " + "(%10i %10i %10i)" % (tuple(self.cl.gd.N_c))) msg(" Grid points in quantum grid: " + "(%10i %10i %10i)" % (tuple(N_c))) msg(" Spacings in quantum grid: " + "(%10.5f %10.5f %10.5f)" % (tuple(np.diag(self.qm.cell) * Bohr / N_c))) msg(" Spacings in classical grid: " + "(%10.5f %10.5f %10.5f)" % (tuple(np.diag(self.cl.cell) * Bohr / get_number_of_grid_points(self.cl.cell, self.cl.spacing)))) # msg(" Ratios of cl/qm spacings: " + # "(%10i %10i %10i)" % (tuple(self.hratios))) # msg(" = (%10.2f %10.2f %10.2f)" % # (tuple((np.diag(self.cl.cell) * Bohr / \ # get_number_of_grid_points(self.cl.cell, # self.cl.spacing)) / \ # (np.diag(self.qm.cell) * Bohr / N_c)))) msg(" Needed number of refinements: %10i" % self.num_refinements) # First, create the quantum grid equivalent GridDescriptor # self.cl.subgd. Then coarsen it until its h_cv equals # that of self.cl.gd. Finally, map the points between # clgd and coarsened subgrid. subcell_cv = np.diag(self.qm.corner2 - self.qm.corner1) N_c = get_number_of_grid_points(subcell_cv, self.cl.spacing) N_c = self.shift_indices_2 - self.shift_indices_1 self.cl.subgds = [] self.cl.subgds.append(GridDescriptor(N_c, subcell_cv, False, serial_comm, self.cl.dparsize)) # msg(" N_c/spacing of the subgrid: " + # "%3i %3i %3i / %.4f %.4f %.4f" % # (self.cl.subgds[0].N_c[0], # self.cl.subgds[0].N_c[1], # self.cl.subgds[0].N_c[2], # self.cl.subgds[0].h_cv[0][0] * Bohr, # self.cl.subgds[0].h_cv[1][1] * Bohr, # self.cl.subgds[0].h_cv[2][2] * Bohr)) # msg(" shape from the subgrid: " + # "%3i %3i %3i" % (tuple(self.cl.subgds[0].empty().shape))) self.cl.coarseners = [] self.cl.refiners = [] for n in range(self.num_refinements): self.cl.subgds.append(self.cl.subgds[n].refine()) self.cl.refiners.append(Transformer(self.cl.subgds[n], self.cl.subgds[n + 1])) # msg(" refiners[%i] can perform the transformation " + # "(%3i %3i %3i) -> (%3i %3i %3i)" % (\ # n, # self.cl.subgds[n].empty().shape[0], # self.cl.subgds[n].empty().shape[1], # self.cl.subgds[n].empty().shape[2], # self.cl.subgds[n + 1].empty().shape[0], # self.cl.subgds[n + 1].empty().shape[1], # self.cl.subgds[n + 1].empty().shape[2])) self.cl.coarseners.append(Transformer(self.cl.subgds[n + 1], self.cl.subgds[n])) self.cl.coarseners[:] = self.cl.coarseners[::-1] # Now extend the grid in order to handle # the zero boundary conditions that the refiner assumes # The default interpolation order self.extend_nn = \ Transformer(GridDescriptor([8, 8, 8], [1, 1, 1], False, serial_comm, None), GridDescriptor([8, 8, 8], [1, 1, 1], False, serial_comm, None).coarsen()).nn self.extended_num_indices = self.num_indices + [2, 2, 2] # Center, left, and right points of the suggested quantum grid extended_cp = 0.5 * (np.array(self.given_corner_v1 / Bohr) + np.array(self.given_corner_v2 / Bohr)) extended_lp = extended_cp - 0.5 * (self.extended_num_indices * self.cl.spacing) # extended_rp = extended_cp + 0.5 * (self.extended_num_indices * # self.cl.spacing) # Indices in the classical grid restricting the quantum grid self.extended_shift_indices_1 = \ np.round(extended_lp / self.cl.spacing).astype(int) self.extended_shift_indices_2 = \ self.extended_shift_indices_1 + self.extended_num_indices # msg(' extended_shift_indices_1: %i %i %i' # % (self.extended_shift_indices_1[0], # self.extended_shift_indices_1[1], # self.extended_shift_indices_1[2])) # msg(' extended_shift_indices_2: %i %i %i' # % (self.extended_shift_indices_2[0], # self.extended_shift_indices_2[1], # self.extended_shift_indices_2[2])) # msg(' cl.gd.N_c: %i %i %i' # % (self.cl.gd.N_c[0], self.cl.gd.N_c[1], self.cl.gd.N_c[2])) # Sanity checks assert(all([self.extended_shift_indices_1[w] >= 0 and self.extended_shift_indices_2[w] <= self.cl.gd.N_c[w] for w in range(3)])), \ "Could not find appropriate quantum grid. " + \ "Move it further away from the boundary." # Corner coordinates self.qm.extended_corner1 = \ self.extended_shift_indices_1 * self.cl.spacing self.qm.extended_corner2 = \ self.extended_shift_indices_2 * self.cl.spacing N_c = self.extended_shift_indices_2 - self.extended_shift_indices_1 self.cl.extended_subgds = [] self.cl.extended_refiners = [] extended_subcell_cv = np.diag(self.qm.extended_corner2 - self.qm.extended_corner1) self.cl.extended_subgds.append(GridDescriptor( N_c, extended_subcell_cv, False, serial_comm, None)) for n in range(self.num_refinements): self.cl.extended_subgds.append(self.cl.extended_subgds[n].refine()) self.cl.extended_refiners.append(Transformer( self.cl.extended_subgds[n], self.cl.extended_subgds[n + 1])) # msg(" extended_refiners[%i] can perform the transformation " + # "(%3i %3i %3i) -> (%3i %3i %3i)" # % (n, # self.cl.extended_subgds[n].empty().shape[0], # self.cl.extended_subgds[n].empty().shape[1], # self.cl.extended_subgds[n].empty().shape[2], # self.cl.extended_subgds[n + 1].empty().shape[0], # self.cl.extended_subgds[n + 1].empty().shape[1], # self.cl.extended_subgds[n + 1].empty().shape[2])) # msg(" N_c/spacing of the refined subgrid: " + # "%3i %3i %3i / %.4f %.4f %.4f" # % (self.cl.subgds[-1].N_c[0], # self.cl.subgds[-1].N_c[1], # self.cl.subgds[-1].N_c[2], # self.cl.subgds[-1].h_cv[0][0] * Bohr, # self.cl.subgds[-1].h_cv[1][1] * Bohr, # self.cl.subgds[-1].h_cv[2][2] * Bohr)) # msg(" shape from the refined subgrid: %3i %3i %3i" # % (tuple(self.cl.subgds[-1].empty().shape))) self.extended_deltaIndex = 2**(self.num_refinements) * self.extend_nn # msg(" self.extended_deltaIndex = %i" % self.extended_deltaIndex) qgpts = self.cl.subgds[-1].coarsen().N_c # Assure that one returns to the original shape dmygd = self.cl.subgds[-1].coarsen() for n in range(self.num_refinements - 1): dmygd = dmygd.coarsen() # msg(" N_c/spacing of the coarsened subgrid: " + # "%3i %3i %3i / %.4f %.4f %.4f" # % (dmygd.N_c[0], dmygd.N_c[1], dmygd.N_c[2], # dmygd.h_cv[0][0] * Bohr, # dmygd.h_cv[1][1] * Bohr, # dmygd.h_cv[2][2] * Bohr)) self.has_subsystems = True return atoms_out, self.qm.spacing[0] * Bohr, qgpts
def cut_cell(self, atoms_in, vacuum=5.0, corners=None, create_subsystems=True): qmh = self.qm.spacing_def if corners is not None: v1 = np.array(corners[0]).ravel() / Bohr v2 = np.array(corners[1]).ravel() / Bohr else: # Use vacuum pos_old = atoms_in.get_positions()[0] dmy_atoms = atoms_in.copy() dmy_atoms.center(vacuum=vacuum) pos_new = dmy_atoms.get_positions()[0] v1 = (pos_old - pos_new) / Bohr v2 = v1 + np.diag(dmy_atoms.get_cell()) / Bohr # Needed for restarting self.given_corner_v1 = v1 * Bohr self.given_corner_v2 = v2 * Bohr self.given_cell = atoms_in.get_cell() # Sanity check: quantum box must be inside the classical one assert (all([v1[w] <= v2[w] and v1[w] >= 0 and v2[w] <= np.diag(self.cl.cell)[w] for w in range(3)])) # Ratios of the user-given spacings self.hratios = self.cl.spacing_def / qmh self.num_refinements = \ 1 + int(round(np.log(self.hratios[0]) / np.log(2.0))) assert ([int(round(np.log(self.hratios[w]) / np.log(2.0))) == self.num_refinements for w in range(3)]) # Create quantum grid self.qm.cell = np.zeros((3, 3)) for w in range(3): self.qm.cell[w, w] = v2[w] - v1[w] N_c = get_number_of_grid_points(self.qm.cell, qmh) self.qm.spacing = np.diag(self.qm.cell) / N_c # Classical corner indices must be divisible with numb if any(self.cl.spacing / self.qm.spacing >= 3): numb = 1 elif any(self.cl.spacing / self.qm.spacing >= 2): numb = 2 else: numb = 4 # The index mismatch of the two simulation cells # Round before taking floor/ceil to avoid floating point # arithmetic errors num_indices_1 = numb * np.floor( np.round(np.array(v1) / self.cl.spacing / numb, 2)).astype(int) num_indices_2 = numb * np.ceil( np.round(np.array(v2) / self.cl.spacing / numb, 2)).astype(int) self.num_indices = num_indices_2 - num_indices_1 # Center, left, and right points of the suggested quantum grid cp = 0.5 * (np.array(v1) + np.array(v2)) lp = cp - 0.5 * self.num_indices * self.cl.spacing # rp = cp + 0.5 * self.num_indices * self.cl.spacing # Indices in the classical grid restricting the quantum grid self.shift_indices_1 = np.round(lp / self.cl.spacing).astype(int) self.shift_indices_2 = self.shift_indices_1 + self.num_indices # Sanity checks assert(all([self.shift_indices_1[w] >= 0 and self.shift_indices_2[w] <= self.cl.gd.N_c[w] for w in range(3)])), \ "Could not find appropriate quantum grid. " + \ "Move it further away from the boundary." # Corner coordinates self.qm.corner1 = self.shift_indices_1 * self.cl.spacing self.qm.corner2 = self.shift_indices_2 * self.cl.spacing # Now the information for creating the subsystems is ready if create_subsystems: return self.create_subsystems(atoms_in)
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 self.set_text(par.txt, 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() # Generate new xc functional only when it is reset by set 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 xc.orbital_dependent and mode == 'lcao': raise NotImplementedError('LCAO mode does not support ' 'orbital-dependent XC functionals.') if mode == 'pw': mode = PW() if mode == 'fd' and par.usefractrans: raise NotImplementedError('FD mode does not support ' 'fractional translations.') if mode == 'lcao' and par.usefractrans: raise Warning('Fractional translations have not been tested ' 'with LCAO mode. Use with care!') if par.realspace is None: realspace = not isinstance(mode, PW) else: realspace = par.realspace if isinstance(mode, PW): assert not realspace 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) if par.filter is None and not isinstance(mode, PW): gamma = 1.6 hmax = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5 / N_c).max() def filter(rgd, rcut, f_r, l=0): gcut = np.pi / hmax - 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 # K-point descriptor bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kd = KPointDescriptor(bzkpts_kc, nspins, collinear, par.usefractrans) 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 ## rbw: If usefractrans=True, kd.set_symmetry might overwrite N_c. ## This is necessary, because N_c must be dividable by 1/(fractional translation), ## f.e. fractional translations of a grid point must land on a grid point. N_c = kd.set_symmetry(atoms, setups, magmom_av, par.usesymm, N_c, world) 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 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 == '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: 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: self.occupations = occupations.FermiDirac(width, par.fixmom) else: self.occupations = par.occupations self.occupations.magmom = M_v[2] cc = par.convergence if mode == 'lcao': niter_fixdensity = 0 else: niter_fixdensity = None if self.scf is None: self.scf = SCFLoop( cc['eigenstates'] / Hartree**2 * nvalence, cc['energy'] / Hartree * max(nvalence, 1), cc['density'] * nvalence, par.maxiter, par.fixdensity, niter_fixdensity) 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 isinstance(mode, 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) domain_comm, kpt_comm, band_comm = \ parallelization.build_communicators() #domain_comm, kpt_comm, band_comm = mpi.distribute_cpus( # parsize_domain, parsize_bands, # nspins, kd.nibzkpts, world, par.idiotproof, mode) 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, 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 == '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, dtype, nao=nao, timer=self.timer) if collinear: self.wfs = LCAOWaveFunctions(lcaoksl, *args) else: from gpaw.xc.noncollinear import \ NonCollinearLCAOWaveFunctions self.wfs = NonCollinearLCAOWaveFunctions(lcaoksl, *args) elif mode == 'fd' or isinstance(mode, 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', gd, bd, 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, 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, dtype, nao=nao, timer=self.timer) if hasattr(self, 'time'): assert mode == 'fd' from gpaw.tddft import TimeDependentWaveFunctions self.wfs = TimeDependentWaveFunctions(par.stencils[0], diagksl, orthoksl, initksl, gd, nvalence, setups, bd, world, kd, self.timer) elif mode == 'fd': self.wfs = FDWaveFunctions(par.stencils[0], diagksl, orthoksl, initksl, *args) else: # Planewave basis: 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 = 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, par.external, collinear, par.poissonsolver, par.stencils[1], world) else: self.hamiltonian = ReciprocalSpaceHamiltonian( gd, finegd, self.density.pd2, self.density.pd3, nspins, setups, self.timer, xc, par.external, collinear, world) 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() self.initialized = True
def get_eigenmodes(self,filename = None, chi0 = None, calc = None, dm = None, xc = 'RPA', sum = None, vcut = None, checkphase = False, return_full = False): """ Calculate the plasmonic eigenmodes as eigenvectors of the dielectric matrix. Parameters: filename: pckl file output from response calculation. chi0: gpw file chi0_wGG from response calculation. calc: gpaw calculator instance ground state calculator used in response calculation. Wavefunctions only needed if chi0 is calculated from scratch dm: gpw file dielectric matrix from response calculation xc: str 'RPA'or 'ALDA' XC- Kernel sum: str '2D': sum in the x and y directions '1D': To be implemented vcut: str '0D','1D' or '2D' Cut the Coulomb potential checkphase: Bool if True, the eigenfunctions id rotated in the complex plane, to be made as real as posible return_full: Bool if True, the eigenvectors in reciprocal space is also returned. """ self.read(filename) self.pbc = [1,1,1] #self.calc.atoms.pbc = [1,1,1] npw = self.npw self.w_w = np.linspace(0, self.dw * (self.Nw - 1)*Hartree, self.Nw) self.vcut = vcut dm_wGG = self.get_dielectric_matrix(xc=xc, symmetric=False, chi0_wGG=chi0, calc=calc, vcut=vcut) q = self.q_c # get grid on which the eigenmodes are calculated #gd = self.calc.wfs.gd #r = gd.get_grid_point_coordinates() #rrr = r*Bohr from gpaw.utilities.gpts import get_number_of_grid_points from gpaw.grid_descriptor import GridDescriptor grid_size = [1,1,1] h=0.2 cell_cv = self.acell_cv*np.diag(grid_size) mode = 'fd' realspace = True h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace) gd = GridDescriptor(N_c, cell_cv, self.pbc) #gd = self.calc.wfs.gd r = gd.get_grid_point_coordinates() rrr = r*Bohr eig_0 = np.array([], dtype = complex) eig_left = np.array([], dtype = complex) eig_right = np.array([], dtype = complex) vec_modes = np.zeros([1, self.npw], dtype = complex) vec_modes_dual = np.zeros([1, self.npw], dtype = complex) vec_modes_density = np.zeros([1, self.npw], dtype = complex) vec_modes_norm = np.zeros([1, self.npw], dtype = complex) eig_all = np.zeros([1, self.npw], dtype = complex) eig_dummy = np.zeros([1, self.npw], dtype = complex) v_dummy = np.zeros([1, self.npw], dtype = complex) vec_dummy = np.zeros([1, self.npw], dtype = complex) vec_dummy2 = np.zeros([1, self.npw], dtype = complex) w_0 = np.array([]) w_left = np.array([]) w_right = np.array([]) if sum == '2D': v_ind = np.zeros([1, r.shape[-1]], dtype = complex) n_ind = np.zeros([1, r.shape[-1]], dtype = complex) elif sum == '1D': self.printtxt('1D sum not implemented') return else: v_ind = np.zeros([1, r.shape[1], r.shape[2], r.shape[3]], dtype = complex) n_ind = np.zeros([1, r.shape[1], r.shape[2], r.shape[3]], dtype = complex) eps_GG_plus = dm_wGG[0] eig_plus, vec_plus = np.linalg.eig(eps_GG_plus) # find eigenvalues and eigenvectors vec_plus_dual = np.linalg.inv(vec_plus) # loop over frequencies, where the eigenvalues for the 2D matrix in G,G' are found. for i in np.array(range(self.Nw-1))+1: eps_GG = eps_GG_plus eig, vec = eig_plus,vec_plus vec_dual = vec_plus_dual eps_GG_plus = dm_wGG[i] # epsilon_GG'(omega + d-omega) eig_plus, vec_plus = np.linalg.eig(eps_GG_plus) vec_plus_dual = np.linalg.inv(vec_plus) eig_dummy[0,:] = eig eig_all = np.append(eig_all, eig_dummy, axis=0) # append all eigenvalues to array # loop to check find the eigenvalues that crosses zero from negative to positive values: for k in range(self.npw): for m in range(self.npw): if eig[k]< 0 and 0 < eig_plus[m]: # check it's the same mode - Overlap between eigenvectors should be large: if abs(np.inner(vec[:,k], vec_plus_dual[m,:])) > 0.95: self.printtxt('crossing found at w = %1.1f eV'%self.w_w[i-1]) eig_left = np.append(eig_left, eig[k]) eig_right = np.append(eig_right, eig_plus[m]) vec_dummy[0, :] = vec[:,k] vec_modes = np.append(vec_modes, vec_dummy, axis = 0) vec_dummy[0, :] = vec_dual[k, :].T vec_modes_dual = np.append(vec_modes_dual, vec_dummy, axis = 0) w1 = self.w_w[i-1] w2 = self.w_w[i] a = np.real((eig_plus[m]-eig[k]) / (w2-w1)) w0 = np.real(-eig[k]) / a + w1 eig0 = a*(w0-w1)+eig[k] w_0 = np.append(w_0,w0) w_left = np.append(w_left, w1) eig_0 = np.append(eig_0,eig0) n_dummy = np.zeros([1, r.shape[1], r.shape[2], r.shape[3]], dtype = complex) v_dummy = np.zeros([1, r.shape[1], r.shape[2], r.shape[3]], dtype = complex) vec_n = np.zeros([self.npw]) for iG in range(self.npw): # Fourier transform qG = np.dot((q + self.Gvec_Gc[iG]), self.bcell_cv) coef_G = np.dot(qG, qG) / (4 * pi) qGr_R = np.inner(qG, r.T).T v_dummy += vec[iG, k] * np.exp(1j * qGr_R) n_dummy += vec[iG, k] * np.exp(1j * qGr_R) * coef_G if checkphase: # rotate eigenvectors in complex plane integral = np.zeros([81]) phases = np.linspace(0,2,81) for ip in range(81): v_int = v_dummy * np.exp(1j * pi * phases[ip]) integral[ip] = abs(np.imag(v_int)).sum() phase = phases[np.argsort(integral)][0] v_dummy *= np.exp(1j * pi * phase) n_dummy *= np.exp(1j * pi * phase) if sum == '2D': i_xyz = 3 v_dummy_z = np.zeros([1,v_dummy.shape[i_xyz]], dtype = complex) n_dummy_z = np.zeros([1,v_dummy.shape[i_xyz]], dtype = complex) v_dummy_z[0,:] = np.sum(np.sum(v_dummy, axis = 1), axis = 1)[0,:] n_dummy_z[0,:] = np.sum(np.sum(n_dummy, axis = 1), axis = 1)[0,:] v_ind = np.append(v_ind, v_dummy_z, axis=0) n_ind = np.append(n_ind, n_dummy_z, axis=0) elif sum == '1D': self.printtxt('1D sum not implemented') else : v_ind = np.append(v_ind, v_dummy, axis=0) n_ind = np.append(n_ind, n_dummy, axis=0) """ returns: grid points, frequency grid, all eigenvalues, mode energies, left point energies, mode eigenvalues, eigenvalues of left and right-side points, (mode eigenvectors, mode dual eigenvectors,) induced potential in real space, induced density in real space """ if return_full: return rrr, self.w_w, eig_all[1:], w_0, eig_0, w_left, eig_left, \ eig_right, vec_modes[1:], vec_modes_dual[1:], v_ind[1:], n_ind[1:] else: return rrr, self.w_w, eig_all[1:], w_0, eig_0, w_left, eig_left, \ eig_right, v_ind[1:], n_ind[1:]
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 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 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()
def get_eigenmodes(self, filename=None, chi0=None, calc=None, dm=None, xc='RPA', sum=None, vcut=None, checkphase=False, return_full=False): """ Calculate the plasmonic eigenmodes as eigenvectors of the dielectric matrix. Parameters: filename: pckl file output from response calculation. chi0: gpw file chi0_wGG from response calculation. calc: gpaw calculator instance ground state calculator used in response calculation. Wavefunctions only needed if chi0 is calculated from scratch dm: gpw file dielectric matrix from response calculation xc: str 'RPA'or 'ALDA' XC- Kernel sum: str '2D': sum in the x and y directions '1D': To be implemented vcut: str '0D','1D' or '2D' Cut the Coulomb potential checkphase: Bool if True, the eigenfunctions id rotated in the complex plane, to be made as real as posible return_full: Bool if True, the eigenvectors in reciprocal space is also returned. """ self.read(filename) self.pbc = [1, 1, 1] #self.calc.atoms.pbc = [1,1,1] npw = self.npw self.w_w = np.linspace(0, self.dw * (self.Nw - 1) * Hartree, self.Nw) self.vcut = vcut dm_wGG = self.get_dielectric_matrix(xc=xc, symmetric=False, chi0_wGG=chi0, calc=calc, vcut=vcut) q = self.q_c # get grid on which the eigenmodes are calculated #gd = self.calc.wfs.gd #r = gd.get_grid_point_coordinates() #rrr = r*Bohr from gpaw.utilities.gpts import get_number_of_grid_points from gpaw.grid_descriptor import GridDescriptor grid_size = [1, 1, 1] h = 0.2 cell_cv = self.acell_cv * np.diag(grid_size) mode = 'fd' realspace = True h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace) gd = GridDescriptor(N_c, cell_cv, self.pbc) #gd = self.calc.wfs.gd r = gd.get_grid_point_coordinates() rrr = r * Bohr eig_0 = np.array([], dtype=complex) eig_left = np.array([], dtype=complex) eig_right = np.array([], dtype=complex) vec_modes = np.zeros([1, self.npw], dtype=complex) vec_modes_dual = np.zeros([1, self.npw], dtype=complex) vec_modes_density = np.zeros([1, self.npw], dtype=complex) vec_modes_norm = np.zeros([1, self.npw], dtype=complex) eig_all = np.zeros([1, self.npw], dtype=complex) eig_dummy = np.zeros([1, self.npw], dtype=complex) v_dummy = np.zeros([1, self.npw], dtype=complex) vec_dummy = np.zeros([1, self.npw], dtype=complex) vec_dummy2 = np.zeros([1, self.npw], dtype=complex) w_0 = np.array([]) w_left = np.array([]) w_right = np.array([]) if sum == '2D': v_ind = np.zeros([1, r.shape[-1]], dtype=complex) n_ind = np.zeros([1, r.shape[-1]], dtype=complex) elif sum == '1D': self.printtxt('1D sum not implemented') return else: v_ind = np.zeros([1, r.shape[1], r.shape[2], r.shape[3]], dtype=complex) n_ind = np.zeros([1, r.shape[1], r.shape[2], r.shape[3]], dtype=complex) eps_GG_plus = dm_wGG[0] eig_plus, vec_plus = np.linalg.eig( eps_GG_plus) # find eigenvalues and eigenvectors vec_plus_dual = np.linalg.inv(vec_plus) # loop over frequencies, where the eigenvalues for the 2D matrix in G,G' are found. for i in np.array(range(self.Nw - 1)) + 1: eps_GG = eps_GG_plus eig, vec = eig_plus, vec_plus vec_dual = vec_plus_dual eps_GG_plus = dm_wGG[i] # epsilon_GG'(omega + d-omega) eig_plus, vec_plus = np.linalg.eig(eps_GG_plus) vec_plus_dual = np.linalg.inv(vec_plus) eig_dummy[0, :] = eig eig_all = np.append(eig_all, eig_dummy, axis=0) # append all eigenvalues to array # loop to check find the eigenvalues that crosses zero from negative to positive values: for k in range(self.npw): for m in range(self.npw): if eig[k] < 0 and 0 < eig_plus[m]: # check it's the same mode - Overlap between eigenvectors should be large: if abs(np.inner(vec[:, k], vec_plus_dual[m, :])) > 0.95: self.printtxt('crossing found at w = %1.1f eV' % self.w_w[i - 1]) eig_left = np.append(eig_left, eig[k]) eig_right = np.append(eig_right, eig_plus[m]) vec_dummy[0, :] = vec[:, k] vec_modes = np.append(vec_modes, vec_dummy, axis=0) vec_dummy[0, :] = vec_dual[k, :].T vec_modes_dual = np.append(vec_modes_dual, vec_dummy, axis=0) w1 = self.w_w[i - 1] w2 = self.w_w[i] a = np.real((eig_plus[m] - eig[k]) / (w2 - w1)) w0 = np.real(-eig[k]) / a + w1 eig0 = a * (w0 - w1) + eig[k] w_0 = np.append(w_0, w0) w_left = np.append(w_left, w1) eig_0 = np.append(eig_0, eig0) n_dummy = np.zeros( [1, r.shape[1], r.shape[2], r.shape[3]], dtype=complex) v_dummy = np.zeros( [1, r.shape[1], r.shape[2], r.shape[3]], dtype=complex) vec_n = np.zeros([self.npw]) for iG in range(self.npw): # Fourier transform qG = np.dot((q + self.Gvec_Gc[iG]), self.bcell_cv) coef_G = np.dot(qG, qG) / (4 * pi) qGr_R = np.inner(qG, r.T).T v_dummy += vec[iG, k] * np.exp(1j * qGr_R) n_dummy += vec[iG, k] * np.exp( 1j * qGr_R) * coef_G if checkphase: # rotate eigenvectors in complex plane integral = np.zeros([81]) phases = np.linspace(0, 2, 81) for ip in range(81): v_int = v_dummy * np.exp( 1j * pi * phases[ip]) integral[ip] = abs(np.imag(v_int)).sum() phase = phases[np.argsort(integral)][0] v_dummy *= np.exp(1j * pi * phase) n_dummy *= np.exp(1j * pi * phase) if sum == '2D': i_xyz = 3 v_dummy_z = np.zeros([1, v_dummy.shape[i_xyz]], dtype=complex) n_dummy_z = np.zeros([1, v_dummy.shape[i_xyz]], dtype=complex) v_dummy_z[0, :] = np.sum(np.sum(v_dummy, axis=1), axis=1)[0, :] n_dummy_z[0, :] = np.sum(np.sum(n_dummy, axis=1), axis=1)[0, :] v_ind = np.append(v_ind, v_dummy_z, axis=0) n_ind = np.append(n_ind, n_dummy_z, axis=0) elif sum == '1D': self.printtxt('1D sum not implemented') else: v_ind = np.append(v_ind, v_dummy, axis=0) n_ind = np.append(n_ind, n_dummy, axis=0) """ returns: grid points, frequency grid, all eigenvalues, mode energies, left point energies, mode eigenvalues, eigenvalues of left and right-side points, (mode eigenvectors, mode dual eigenvectors,) induced potential in real space, induced density in real space """ if return_full: return rrr, self.w_w, eig_all[1:], w_0, eig_0, w_left, eig_left, \ eig_right, vec_modes[1:], vec_modes_dual[1:], v_ind[1:], n_ind[1:] else: return rrr, self.w_w, eig_all[1:], w_0, eig_0, w_left, eig_left, \ eig_right, v_ind[1:], n_ind[1:]
def create_subsystems(self, atoms_in): # Create new Atoms object atoms_out = atoms_in.copy() # New quantum grid self.qm.cell = np.diag([(self.shift_indices_2[w] - self.shift_indices_1[w])*self.cl.spacing[w] for w in range(3)]) self.qm.spacing = self.cl.spacing / self.hratios N_c = get_number_of_grid_points(self.qm.cell, self.qm.spacing) atoms_out.set_cell(np.diag(self.qm.cell) * Bohr) atoms_out.positions = atoms_in.get_positions() - self.qm.corner1 * Bohr self.messages.append("Quantum box readjustment:") self.messages.append(" Given cell: [%10.5f %10.5f %10.5f]" % tuple(np.diag(atoms_in.get_cell()))) self.messages.append(" Given atomic coordinates:") for s, c in zip(atoms_in.get_chemical_symbols(), atoms_in.get_positions()): self.messages.append(" %s %10.5f %10.5f %10.5f" % (s, c[0], c[1], c[2])) self.messages.append(" Readjusted cell: [%10.5f %10.5f %10.5f]" % tuple(np.diag(atoms_out.get_cell()))) self.messages.append(" Readjusted atomic coordinates:") for s, c in zip(atoms_out.get_chemical_symbols(), atoms_out.get_positions()): self.messages.append(" %s %10.5f %10.5f %10.5f" % (s, c[0], c[1], c[2])) self.messages.append(" Given corner points: (%10.5f %10.5f %10.5f) - (%10.5f %10.5f %10.5f)" % (tuple(np.concatenate((self.given_corner_v1, self.given_corner_v2))))) self.messages.append(" Readjusted corner points: (%10.5f %10.5f %10.5f) - (%10.5f %10.5f %10.5f)" % (tuple(np.concatenate((self.qm.corner1, self.qm.corner2)) * Bohr))) self.messages.append(" Indices in classical grid: (%10i %10i %10i) - (%10i %10i %10i)" % (tuple(np.concatenate((self.shift_indices_1, self.shift_indices_2))))) self.messages.append(" Grid points in classical grid: (%10i %10i %10i)" % (tuple(self.cl.gd.N_c))) self.messages.append(" Grid points in quantum grid: (%10i %10i %10i)" % (tuple(N_c))) self.messages.append(" Spacings in quantum grid: (%10.5f %10.5f %10.5f)" % (tuple(np.diag(self.qm.cell) * Bohr / N_c))) self.messages.append(" Spacings in classical grid: (%10.5f %10.5f %10.5f)" % (tuple(np.diag(self.cl.cell) * Bohr / \ get_number_of_grid_points(self.cl.cell, self.cl.spacing)))) #self.messages.append(" Ratios of cl/qm spacings: (%10i %10i %10i)" % (tuple(self.hratios))) #self.messages.append(" = (%10.2f %10.2f %10.2f)" % # (tuple((np.diag(self.cl.cell) * Bohr / \ # get_number_of_grid_points(self.cl.cell, # self.cl.spacing)) / \ # (np.diag(self.qm.cell) * Bohr / N_c)))) self.messages.append(" Needed number of refinements: %10i" % self.num_refinements) # First, create the quantum grid equivalent GridDescriptor self.cl.subgd. # Then coarsen it until its h_cv equals that of self.cl.gd. # Finally, map the points between clgd and coarsened subgrid. subcell_cv = np.diag(self.qm.corner2 - self.qm.corner1) N_c = get_number_of_grid_points(subcell_cv, self.cl.spacing) N_c = self.shift_indices_2 - self.shift_indices_1 self.cl.subgds = [] self.cl.subgds.append(GridDescriptor(N_c, subcell_cv, False, serial_comm, self.cl.dparsize)) #self.messages.append(" N_c/spacing of the subgrid: %3i %3i %3i / %.4f %.4f %.4f" % # (self.cl.subgds[0].N_c[0], # self.cl.subgds[0].N_c[1], # self.cl.subgds[0].N_c[2], # self.cl.subgds[0].h_cv[0][0] * Bohr, # self.cl.subgds[0].h_cv[1][1] * Bohr, # self.cl.subgds[0].h_cv[2][2] * Bohr)) #self.messages.append(" shape from the subgrid: %3i %3i %3i" % (tuple(self.cl.subgds[0].empty().shape))) self.cl.coarseners = [] self.cl.refiners = [] for n in range(self.num_refinements): self.cl.subgds.append(self.cl.subgds[n].refine()) self.cl.refiners.append(Transformer(self.cl.subgds[n], self.cl.subgds[n + 1])) #self.messages.append(" refiners[%i] can perform the transformation (%3i %3i %3i) -> (%3i %3i %3i)" % (\ # n, # self.cl.subgds[n].empty().shape[0], # self.cl.subgds[n].empty().shape[1], # self.cl.subgds[n].empty().shape[2], # self.cl.subgds[n + 1].empty().shape[0], # self.cl.subgds[n + 1].empty().shape[1], # self.cl.subgds[n + 1].empty().shape[2])) self.cl.coarseners.append(Transformer(self.cl.subgds[n + 1], self.cl.subgds[n])) self.cl.coarseners[:] = self.cl.coarseners[::-1] # Now extend the grid in order to handle the zero boundary conditions that the refiner assumes # The default interpolation order self.extend_nn = Transformer(GridDescriptor([8, 8, 8], [1, 1, 1], False, serial_comm, None), GridDescriptor([8, 8, 8], [1, 1, 1], False, serial_comm, None).coarsen()).nn self.extended_num_indices = self.num_indices + [2, 2, 2] # Center, left, and right points of the suggested quantum grid extended_cp = 0.5 * (np.array(self.given_corner_v1/Bohr) + np.array(self.given_corner_v2/Bohr)) extended_lp = extended_cp - 0.5 * (self.extended_num_indices) * self.cl.spacing extended_rp = extended_cp + 0.5 * (self.extended_num_indices) * self.cl.spacing # Indices in the classical grid restricting the quantum grid self.extended_shift_indices_1 = np.round(extended_lp / self.cl.spacing) self.extended_shift_indices_2 = self.extended_shift_indices_1 + self.extended_num_indices #self.messages.append(' extended_shift_indices_1: %i %i %i' % (self.extended_shift_indices_1[0],self.extended_shift_indices_1[1], self.extended_shift_indices_1[2])) #self.messages.append(' extended_shift_indices_2: %i %i %i' % (self.extended_shift_indices_2[0],self.extended_shift_indices_2[1], self.extended_shift_indices_2[2])) #self.messages.append(' cl.gd.N_c: %i %i %i' % (self.cl.gd.N_c[0], self.cl.gd.N_c[1], self.cl.gd.N_c[2])) # Sanity checks assert(all([self.extended_shift_indices_1[w] >= 0 and self.extended_shift_indices_2[w] <= self.cl.gd.N_c[w] for w in range(3)])), \ "Could not find appropriate quantum grid. Move it further away from the boundary." # Corner coordinates self.qm.extended_corner1 = self.extended_shift_indices_1 * self.cl.spacing self.qm.extended_corner2 = self.extended_shift_indices_2 * self.cl.spacing N_c = self.extended_shift_indices_2 - self.extended_shift_indices_1 self.cl.extended_subgds = [] self.cl.extended_refiners = [] extended_subcell_cv = np.diag(self.qm.extended_corner2 - self.qm.extended_corner1) self.cl.extended_subgds.append(GridDescriptor(N_c, extended_subcell_cv, False, serial_comm, None)) for n in range(self.num_refinements): self.cl.extended_subgds.append(self.cl.extended_subgds[n].refine()) self.cl.extended_refiners.append(Transformer(self.cl.extended_subgds[n], self.cl.extended_subgds[n + 1])) #self.messages.append(" extended_refiners[%i] can perform the transformation (%3i %3i %3i) -> (%3i %3i %3i)" % # (n, # self.cl.extended_subgds[n].empty().shape[0], # self.cl.extended_subgds[n].empty().shape[1], # self.cl.extended_subgds[n].empty().shape[2], # self.cl.extended_subgds[n + 1].empty().shape[0], # self.cl.extended_subgds[n + 1].empty().shape[1], # self.cl.extended_subgds[n + 1].empty().shape[2])) #self.messages.append(" N_c/spacing of the refined subgrid: %3i %3i %3i / %.4f %.4f %.4f" % # (self.cl.subgds[-1].N_c[0], # self.cl.subgds[-1].N_c[1], # self.cl.subgds[-1].N_c[2], # self.cl.subgds[-1].h_cv[0][0] * Bohr, # self.cl.subgds[-1].h_cv[1][1] * Bohr, # self.cl.subgds[-1].h_cv[2][2] * Bohr)) #self.messages.append(" shape from the refined subgrid: %3i %3i %3i" % # (tuple(self.cl.subgds[-1].empty().shape))) self.extended_deltaIndex = 2 ** (self.num_refinements) * self.extend_nn #self.messages.append(" self.extended_deltaIndex = %i" % self.extended_deltaIndex) qgpts = self.cl.subgds[-1].coarsen().N_c # Assure that one returns to the original shape dmygd = self.cl.subgds[-1].coarsen() for n in range(self.num_refinements - 1): dmygd = dmygd.coarsen() #self.messages.append(" N_c/spacing of the coarsened subgrid: %3i %3i %3i / %.4f %.4f %.4f" % # (dmygd.N_c[0], dmygd.N_c[1], dmygd.N_c[2], # dmygd.h_cv[0][0] * Bohr, dmygd.h_cv[1][1] * Bohr, dmygd.h_cv[2][2] * Bohr)) return atoms_out, self.qm.spacing[0] * Bohr, qgpts
def cut_cell(self, atoms_in, vacuum=5.0, corners=None, create_subsystems=True): qmh = self.qm.spacing_def if corners is not None: v1 = np.array(corners[0]).ravel() / Bohr v2 = np.array(corners[1]).ravel() / Bohr else: # Use vacuum pos_old = atoms_in.get_positions()[0]; dmy_atoms = atoms_in.copy() dmy_atoms.center(vacuum=vacuum) pos_new = dmy_atoms.get_positions()[0]; v1 = (pos_old - pos_new)/Bohr v2 = v1 + np.diag(dmy_atoms.get_cell())/Bohr # Needed for restarting self.given_corner_v1 = v1 * Bohr self.given_corner_v2 = v2 * Bohr self.given_cell = atoms_in.get_cell() # Sanity check: quantum box must be inside the classical one assert(all([v1[w] <= v2[w] and v1[w] >= 0 and v2[w] <= np.diag(self.cl.cell)[w] for w in range(3)])) # Ratios of the user-given spacings self.hratios = self.cl.spacing_def / qmh self.num_refinements = 1 + int(round(np.log(self.hratios[0]) / np.log(2.0))) assert([int(round(np.log(self.hratios[w]) / np.log(2.0))) == self.num_refinements for w in range(3)]) # Create quantum grid self.qm.cell = np.zeros((3, 3)) for w in range(3): self.qm.cell[w, w] = v2[w] - v1[w] N_c = get_number_of_grid_points(self.qm.cell, qmh) self.qm.spacing = np.diag(self.qm.cell) / N_c # Classical corner indices must be divisible with numb if any(self.cl.spacing / self.qm.spacing >= 3): numb = 1 elif any(self.cl.spacing / self.qm.spacing >= 2): numb = 2 else: numb = 4 # The index mismatch of the two simulation cells self.num_indices = numb * np.ceil((np.array(v2) - np.array(v1)) / self.cl.spacing / numb) self.num_indices_1 = numb * np.floor(np.array(v1) / self.cl.spacing / numb) self.num_indices_2 = numb * np.ceil(np.array(v2) / self.cl.spacing / numb) self.num_indices = self.num_indices_2 - self.num_indices_1 # Center, left, and right points of the suggested quantum grid cp = 0.5 * (np.array(v1) + np.array(v2)) lp = cp - 0.5 * self.num_indices * self.cl.spacing rp = cp + 0.5 * self.num_indices * self.cl.spacing # Indices in the classical grid restricting the quantum grid self.shift_indices_1 = np.round(lp / self.cl.spacing) self.shift_indices_2 = self.shift_indices_1 + self.num_indices # Sanity checks assert(all([self.shift_indices_1[w] >= 0 and self.shift_indices_2[w] <= self.cl.gd.N_c[w] for w in range(3)])), \ "Could not find appropriate quantum grid. Move it further away from the boundary." # Corner coordinates self.qm.corner1 = self.shift_indices_1 * self.cl.spacing self.qm.corner2 = self.shift_indices_2 * self.cl.spacing # Now the information for creating the subsystems is ready if create_subsystems: return self.create_subsystems(atoms_in)