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)
# Check that atoms object mismatches are detected properly across CPUs. from ase.build import molecule from gpaw.mpi import world, synchronize_atoms system = molecule('H2O') synchronize_atoms(system, world) if world.rank == 1: system.positions[1, 1] += 1e-8 # fail (above tolerance) if world.rank == 2: system.cell[0, 0] += 1e-15 # fail (zero tolerance) if world.rank == 3: system.positions[1, 1] += 1e-10 # pass (below tolerance) expected_err_ranks = {1: [], 2: [1]}.get(world.size, [1, 2]) try: synchronize_atoms(system, world, tolerance=1e-9) except ValueError as e: assert (expected_err_ranks == e.args[1]).all() else: assert world.size == 1
def create_random_atoms(gd, nmolecules=10, name='NH2', mindist=4.5 / Bohr): """Create gas-like collection of atoms from randomly placed molecules. Applies rigid motions to molecules, translating the COM and/or rotating by a given angle around an axis of rotation through the new COM. These atomic positions obey the minimum distance requirement to zero-boundaries. Warning: This is only intended for testing parallel grid/LFC consistency. """ atoms = Atoms(cell=gd.cell_cv * Bohr, pbc=gd.pbc_c) # Store the original state of the random number generator randstate = np.random.get_state() seed = np.array([ md5_array(data, numeric=True) for data in [nmolecules, gd.cell_cv, gd.pbc_c, gd.N_c] ]).astype(int) #np.random.seed(seed % 4294967296) np.random.seed(seed % 1073741824) for m in range(nmolecules): amol = molecule(name) amol.set_cell(gd.cell_cv * Bohr) # Rotate the molecule around COM according to three random angles # The rotation axis is given by spherical angles phi and theta v, phi, theta = np.random.uniform(0.0, 2 * np.pi, 3) # theta [0,pi[ really axis = np.array( [cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta)]) amol.rotate(axis, v) # Find the scaled length we must transverse along the given axes such # that the resulting displacement vector is `mindist` from the cell # face corresponding to that direction (plane with unit normal n_v). sdist_c = np.empty(3) if not gd.orthogonal: for c in range(3): n_v = gd.xxxiucell_cv[c] / np.linalg.norm(gd.xxxiucell_cv[c]) sdist_c[c] = mindist / np.dot(gd.cell_cv[c], n_v) else: sdist_c[:] = mindist / gd.cell_cv.diagonal() assert np.all(sdist_c > 0), 'Displacment vectors must be inside cell.' # Scaled dimensions of the smallest possible box centered on the COM spos_ac = amol.get_scaled_positions() # NB! must not do a "% 1.0" scom_c = np.dot(gd.icell_cv, amol.get_center_of_mass()) sbox_c = np.abs(spos_ac - scom_c[np.newaxis, :]).max(axis=0) sdelta_c = (1 - np.array(gd.pbc_c)) * (sbox_c + sdist_c) assert (sdelta_c < 1.0 - sdelta_c).all(), 'Box is too tight to fit atoms.' scenter_c = [np.random.uniform(d, 1 - d) for d in sdelta_c] center_v = np.dot(scenter_c, gd.cell_cv) # Translate the molecule such that COM is located at random center offset_av = (center_v - amol.get_center_of_mass() / Bohr)[np.newaxis, :] amol.set_positions(amol.get_positions() + offset_av * Bohr) assert np.linalg.norm(center_v - amol.get_center_of_mass() / Bohr) < 1e-9 atoms.extend(amol) # Restore the original state of the random number generator np.random.set_state(randstate) synchronize_atoms(atoms, world) return atoms
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')