def make_precon(self, atoms, recalc_mu=None): if self.r_NN is None: self.r_NN = estimate_nearest_neighbour_distance(atoms) if self.r_cut is None: # This is the first time this function has been called, and no # cutoff radius has been specified, so calculate it automatically. self.r_cut = 2.0 * self.r_NN elif self.r_cut < self.r_NN: warning = ('WARNING: r_cut (%.2f) < r_NN (%.2f), ' 'increasing to 1.1*r_NN = %.2f' % (self.r_cut, self.r_NN, 1.1 * self.r_NN)) logger.info(warning) print(warning) self.r_cut = 1.1 * self.r_NN if recalc_mu is None: # The caller has not specified whether or not to recalculate mu, # so the Precon's setting is used. recalc_mu = self.recalc_mu if self.mu is None: # Regardless of what the caller has specified, if we don't # currently have a value of mu, then we need one. recalc_mu = True if recalc_mu: self.estimate_mu(atoms) if self.P is not None: real_atoms = atoms if isinstance(atoms, Filter): real_atoms = atoms.atoms if self.old_positions is None: self.old_positions = wrap_positions(real_atoms.positions, real_atoms.cell) displacement = wrap_positions(real_atoms.positions, real_atoms.cell) - self.old_positions self.old_positions = real_atoms.get_positions() max_abs_displacement = abs(displacement).max() logger.info('max(abs(displacements)) = %.2f A (%.2f r_NN)', max_abs_displacement, max_abs_displacement / self.r_NN) if max_abs_displacement < 0.5 * self.r_NN: return self.P start_time = time.time() # Create the preconditioner: self._make_sparse_precon(atoms, force_stab=self.force_stab) logger.info('--- Precon created in %s seconds ---', time.time() - start_time) return self.P
def get_check_point(self): if hasattr(self, 'log'): states = [ torch.Tensor(self.log[key][-1]).to(self.device) for key in self.log ] if self.wrap: wrapped_xyz = wrap_positions(self.log['positions'][-1], self.system.get_cell()) states[1] = torch.Tensor(wrapped_xyz).to(self.device) return states else: raise ValueError("No log available")
def cell_to_hmatrix_impl(cell, positions, pbc): """ Rotate ASE atoms cell to the h-matrix format used by LAMMPS. Arguments: cell: 3x3 array-like, the 3x3 cell matrix. positions: array-like, positions of atoms. pbc: tuple of bools, whether the cell is periodic in the direction? Returns: cell and positions under the new h-matrix coordination system. """ a, b, c = cell an, bn, cn = [np.linalg.norm(v) for v in cell] alpha = np.arccos(np.dot(b, c)/(bn*cn)) beta = np.arccos(np.dot(a, c)/(an*cn)) gamma = np.arccos(np.dot(a, b)/(an*bn)) xhi = an xyp = np.cos(gamma)*bn yhi = np.sin(gamma)*bn xzp = np.cos(beta)*cn yzp = (bn*cn*np.cos(alpha) - xyp*xzp)/yhi zhi = np.sqrt(cn**2 - xzp**2 - yzp**2) Hpre = np.array([[xhi, 0.0, 0.0], [xyp, yhi, 0.0], [xzp, yzp, zhi]]) R = np.dot(np.linalg.inv(cell), Hpre) def fold(vec, pvec, i): p = pvec[i] x = vec[i] + 0.5*p n = (np.mod(x, p) - x) / p return vec + n*pvec Hpre[1, :] = fold(Hpre[1, :], Hpre[0, :], 0) Hpre[2, :] = fold(Hpre[2, :], Hpre[1, :], 1) Hpre[2, :] = fold(Hpre[2, :], Hpre[0, :], 0) H = Hpre rot_positions = np.dot(positions, R) rot_positions = wrap_positions(rot_positions, H, pbc) return H, rot_positions
def vector_to_lammps(self, vec, wrap=False): """Rotate vector from ase coordinate system to lammps one :param vec: to be rotated ase-vector :returns: lammps-vector :rtype: np.array """ # !TODO: right eps-limit # lammps might not like atoms outside the cell if wrap: return wrap_positions( np.dot(vec, self.rot_mat), self.lammps_cell, pbc=self.pbc, eps=1e-18, ) return np.dot(vec, self.rot_mat)
def proj_dict_to_string(dct, atoms): site = [] site_outside_pc = False if 'csite' in dct: coords = np.array(dct['csite']) # Check coords are inside the cell [wrapped_coords] = wrap_positions([coords], atoms.cell) if np.linalg.norm(wrapped_coords - coords) > 1e-3: site_outside_pc = True coords = wrapped_coords site.append('c=' + ','.join([str(c) for c in coords])) elif 'fsite' in dct: coords = dct['fsite'] # Check coords are inside the cell if any([x // 1 != 0 for x in coords]): site_outside_pc = True coords = [x % 1 for x in coords] site.append('f=' + ','.join([str(c) for c in coords])) elif 'site' in dct: site.append(dct['site']) else: raise ValueError('w90 projections block is missing a "site" entry') if site_outside_pc: warnings.warn('A projection site lies outside the primitive cell. It has been wrapped.') if 'ang_mtm' not in dct: raise ValueError('w90 projections block is missing an "ang_mtm" entry') site.append(dct['ang_mtm']) if 'zaxis' in dct: site.append('z=' + ','.join(str(x) for x in dct['zaxis'])) if 'xaxis' in dct: site.append('x=' + ','.join(str(x) for x in dct['zaxis'])) if 'radial' in dct: site.append(f'r={dct["radial"]}') if 'zona' in dct: site.append(f'zona={dct["zona"]}') return ':'.join(site)
def set_lammps_pos(self, atoms): # Create local copy of positions that are wrapped along any periodic # directions pos = wrap_positions(atoms.get_positions(), atoms.get_cell(), atoms.get_pbc()) pos = convert(pos, "distance", "ASE", self.units) # If necessary, transform the positions to new coordinate system if self.coord_transform is not None: pos = np.dot(self.coord_transform, pos.transpose()) pos = pos.transpose() # Convert ase position matrix to lammps-style position array # contiguous in memory lmp_positions = list(pos.ravel()) # Convert that lammps-style array into a C object c_double_array = (ctypes.c_double * len(lmp_positions)) lmp_c_positions = c_double_array(*lmp_positions) # self.lmp.put_coosrds(lmp_c_positions) self.lmp.scatter_atoms('x', 1, 3, lmp_c_positions)
def unpack_ase(frame, wrap_pos=False): """ Convert ASE Atoms object to rascal's equivalent Parameters ---------- frame : ase.Atoms Atomic structure Returns ------- StructureManagerCenters base structure manager. If the frame has an ase.atoms.arrays entry called 'center_atoms_mask' then it will be used as the center mask (surprise) for any representations computed on this StructureManager. """ cell = frame.get_cell() positions = frame.get_positions() numbers = frame.get_atomic_numbers() pbc = frame.get_pbc().astype(int) if wrap_pos: positions = wrap_positions(positions, cell, frame.get_pbc(), eps=1e-11) if "center_atoms_mask" in frame.arrays.keys(): center_atoms_mask = frame.get_array("center_atoms_mask") else: center_atoms_mask = np.ones_like(numbers, dtype=bool) return adapt_structure( cell=cell, positions=positions, atom_types=numbers, pbc=pbc, center_atoms_mask=center_atoms_mask, )
def set_lammps_pos(self, atoms): # Create local copy of positions that are wrapped along any periodic # directions cell = convert(atoms.cell, "distance", "ASE", self.units) pos = convert(atoms.positions, "distance", "ASE", self.units) # If necessary, transform the positions to new coordinate system if self.coord_transform is not None: pos = np.dot(pos, self.coord_transform.T) cell = np.dot(cell, self.coord_transform.T) # wrap only after scaling and rotating to reduce chances of # lammps neighbor list bugs. pos = wrap_positions(pos, cell, atoms.get_pbc()) # Convert ase position matrix to lammps-style position array # contiguous in memory lmp_positions = list(pos.ravel()) # Convert that lammps-style array into a C object c_double_array = (ctypes.c_double * len(lmp_positions)) lmp_c_positions = c_double_array(*lmp_positions) # self.lmp.put_coosrds(lmp_c_positions) self.lmp.scatter_atoms('x', 1, 3, lmp_c_positions)
def moveAtoms( atoms, n_atoms_to_shift, alat=4.05 ): """ To move an atom from A->B or B->C it has to be translated a distance r*(1,1/sqrt(3) ) in the plane orthogonal to the (1,1,1) plane r is the radius of the spheres """ r = alat/(2.0*np.sqrt(2.0)) # For FCC lattice xhat = np.array( [1,-1,0] )/np.sqrt(2) yhat = np.array( [1,1,-2] )/np.sqrt(6) # Translation vec normal t0 (1,1,1) d = np.array( [1.0,1/np.sqrt(3),0] )*r translation = np.zeros(3) translation += d[0]*xhat translation += d[1]*yhat for i in range(n_atoms_to_shift): atoms[i].x += translation[0] atoms[i].y += translation[1] # Wrap positions to cell positions = geometry.wrap_positions( atoms.get_positions(), atoms.get_cell() ) atoms.set_positions( positions ) return atoms
def make_precon(self, atoms, recalc_mu=None): """Create a preconditioner matrix based on the passed set of atoms. Creates a general-purpose preconditioner for use with optimization algorithms, based on examining distances between pairs of atoms in the lattice. The matrix will be stored in the attribute self.P and returned. Args: atoms: the Atoms object used to create the preconditioner. Can also recalc_mu: if True, self.mu (and self.mu_c for variable cell) will be recalculated by calling self.estimate_mu(atoms) before the preconditioner matrix is created. If False, self.mu will be calculated only if it does not currently have a value (ie, the first time this function is called). Returns: A two-element tuple: P: A sparse scipy csr_matrix. BE AWARE that using numpy.dot() with sparse matrices will result in errors/incorrect results - use the .dot method directly on the matrix instead. """ if self.r_NN is None: self.r_NN = estimate_nearest_neighbour_distance(atoms) if self.r_cut is None: # This is the first time this function has been called, and no # cutoff radius has been specified, so calculate it automatically. self.r_cut = 2.0 * self.r_NN elif self.r_cut < self.r_NN: warning = ('WARNING: r_cut (%.2f) < r_NN (%.2f), ' 'increasing to 1.1*r_NN = %.2f' % (self.r_cut, self.r_NN, 1.1 * self.r_NN)) warnings.warn(warning) self.r_cut = 1.1 * self.r_NN if recalc_mu is None: # The caller has not specified whether or not to recalculate mu, # so the Precon's setting is used. recalc_mu = self.recalc_mu if self.mu is None: # Regardless of what the caller has specified, if we don't # currently have a value of mu, then we need one. recalc_mu = True if recalc_mu: self.estimate_mu(atoms) if self.P is not None: real_atoms = atoms if isinstance(atoms, Filter): real_atoms = atoms.atoms if self.old_positions is None: self.old_positions = wrap_positions(real_atoms.positions, real_atoms.cell) displacement = wrap_positions(real_atoms.positions, real_atoms.cell) - self.old_positions self.old_positions = real_atoms.get_positions() max_abs_displacement = abs(displacement).max() #print('max(abs(displacements)) = %.2f A (%.2f r_NN)' % # (max_abs_displacement, max_abs_displacement / self.r_NN)) if max_abs_displacement < 0.5 * self.r_NN: return self.P #start_time = time.time() # Create the preconditioner: self._make_sparse_precon(atoms, force_stab=self.force_stab) #print('--- Precon created in %s seconds ---' % # (time.time() - start_time)) return self.P
def surface_energy(): conc_args = { "conc_ratio_min_1": [[64, 0, 0]], "conc_ratio_max_1": [[24, 40, 0]], "conc_ratio_min_2": [[64, 0, 0]], "conc_ratio_max_2": [[22, 21, 21]] } kwargs = { "crystalstructure": "fcc", "a": 4.05, "size": [4, 4, 4], "basis_elements": [["Al", "Mg", "Si"]], "conc_args": conc_args, "db_name": "data/almgsi.db", "max_cluster_size": 4 } ceBulk = CEBulk(**kwargs) eci_file = "data/almgsi_fcc_eci_newconfig.json" with open(eci_file, 'r') as infile: ecis = json.load(infile) size = (8, 8, 8) db_name = "large_cell_db{}x{}x{}.db".format(size[0], size[1], size[2]) calc = get_ce_calc(ceBulk, kwargs, ecis, size=size, db_name=db_name) print(calc.get_energy() / len(calc.atoms)) ceBulk = calc.BC ceBulk.atoms.set_calculator(calc) # Get the energy of MgSi mgsi = wrap_and_sort_by_position(get_mgsi() * (4, 4, 4)) symbs = [atom.symbol for atom in mgsi] calc.set_symbols(symbs) view(calc.atoms) mgsi_energy = calc.get_energy() / len(calc.atoms) print("Energy MgSi: {} eV/atom".format(mgsi_energy)) # Get the energy of pure al symbs = ["Al" for _ in range(len(mgsi))] calc.set_symbols(symbs) view(calc.atoms) al_energy = calc.get_energy() / len(calc.atoms) print("Energy Al: {} eV/atom".format(al_energy)) # Get the energy of a 50% mixture mgsi = wrap_and_sort_by_position(get_mgsi() * (4, 4, 2)) # mgsi = get_mgsi_surface100_si_si() from scipy.spatial import cKDTree as KDTree cell = calc.atoms.get_cell() tree = KDTree(calc.atoms.get_positions()) symbs = [atom.symbol for atom in calc.atoms] for atom in mgsi: pos = np.zeros((1, 3)) pos[0, :] = atom.position wrapped = wrap_positions(pos, cell) if not np.allclose(wrapped, atom.position): continue _, indx = tree.query(atom.position) symbs[indx] = atom.symbol calc.set_symbols(symbs) view(calc.atoms) # Surface energy mix_energy = calc.get_energy() print("Mix energy: {} eV ({} eV/atom)".format(mix_energy, mix_energy / len(calc.atoms))) num_al = 0 num_mgsi = 0 for atom in calc.atoms: if atom.symbol == "Al": num_al += 1 elif atom.symbol == "Mg" or atom.symbol == "Si": num_mgsi += 1 assert num_al + num_mgsi == len(calc.atoms) dE = mix_energy - num_al * al_energy - num_mgsi * mgsi_energy cell = calc.atoms.get_cell() a1 = cell[0, :] a2 = cell[1, :] normal = np.cross(a1, a2) area = np.sqrt(normal.dot(normal)) # Convert units surface_tension = dE / area mJ = J / 1000.0 # milli joules surface_tension *= (m * m / mJ) unit_normal = normal / area print("Surface tension: {} mJ/m^2".format(surface_tension)) print("Direction: {}".format(unit_normal))
def build(self, pbc, cell, coordinates): """Build the list. Coordinates are taken to be scaled or not according to self.use_scaled_positions. """ self.pbc = pbc = np.array(pbc, copy=True) self.cell = cell = Cell(cell) self.coordinates = coordinates = np.array(coordinates, copy=True) if len(self.cutoffs) != len(coordinates): raise ValueError('Wrong number of cutoff radii: {0} != {1}'.format( len(self.cutoffs), len(coordinates))) if len(self.cutoffs) > 0: rcmax = self.cutoffs.max() else: rcmax = 0.0 if self.use_scaled_positions: positions0 = cell.cartesian_positions(coordinates) else: positions0 = coordinates rcell, op = minkowski_reduce(cell, pbc) positions = wrap_positions(positions0, rcell, pbc=pbc, eps=0) natoms = len(positions) self.nneighbors = 0 self.npbcneighbors = 0 self.neighbors = [np.empty(0, int) for a in range(natoms)] self.displacements = [np.empty((0, 3), int) for a in range(natoms)] self.nupdates += 1 if natoms == 0: return N = [] ircell = np.linalg.pinv(rcell) for i in range(3): if self.pbc[i]: v = ircell[:, i] h = 1 / np.linalg.norm(v) n = int(2 * rcmax / h) + 1 else: n = 0 N.append(n) tree = cKDTree(positions, copy_data=True) offsets = cell.scaled_positions(positions - positions0) offsets = offsets.round().astype(np.int) for n1, n2, n3 in itertools.product(range(0, N[0] + 1), range(-N[1], N[1] + 1), range(-N[2], N[2] + 1)): if n1 == 0 and (n2 < 0 or n2 == 0 and n3 < 0): continue displacement = (n1, n2, n3) @ rcell for a in range(natoms): indices = tree.query_ball_point(positions[a] - displacement, r=self.cutoffs[a] + rcmax) if not len(indices): continue indices = np.array(indices) delta = positions[indices] + displacement - positions[a] cutoffs = self.cutoffs[indices] + self.cutoffs[a] i = indices[np.linalg.norm(delta, axis=1) < cutoffs] if n1 == 0 and n2 == 0 and n3 == 0: if self.self_interaction: i = i[i >= a] else: i = i[i > a] self.nneighbors += len(i) self.neighbors[a] = np.concatenate((self.neighbors[a], i)) disp = (n1, n2, n3) @ op + offsets[i] - offsets[a] self.npbcneighbors += disp.any(1).sum() self.displacements[a] = np.concatenate( (self.displacements[a], disp)) if self.bothways: neighbors2 = [[] for a in range(natoms)] displacements2 = [[] for a in range(natoms)] for a in range(natoms): for b, disp in zip(self.neighbors[a], self.displacements[a]): neighbors2[b].append(a) displacements2[b].append(-disp) for a in range(natoms): nbs = np.concatenate((self.neighbors[a], neighbors2[a])) disp = np.array( list(self.displacements[a]) + displacements2[a]) # Force correct type and shape for case of no neighbors: self.neighbors[a] = nbs.astype(int) self.displacements[a] = disp.astype(int).reshape((-1, 3)) if self.sorted: for a, i in enumerate(self.neighbors): mask = (i < a) if mask.any(): j = i[mask] offsets = self.displacements[a][mask] for b, offset in zip(j, offsets): self.neighbors[b] = np.concatenate( (self.neighbors[b], [a])) self.displacements[b] = np.concatenate( (self.displacements[b], [-offset])) mask = np.logical_not(mask) self.neighbors[a] = self.neighbors[a][mask] self.displacements[a] = self.displacements[a][mask]
def test_geometry(): """Test the ase.geometry module and ase.build.cut() function.""" import numpy as np from ase.build import cut, bulk, fcc111 from ase.cell import Cell from ase.geometry import get_layers, wrap_positions from ase.spacegroup import crystal, get_spacegroup al = crystal('Al', [(0, 0, 0)], spacegroup=225, cellpar=4.05) # Cut out slab of 5 Al(001) layers al001 = cut(al, nlayers=5) correct_pos = np.array([[0., 0., 0.], [0., 0.5, 0.2], [0.5, 0., 0.2], [0.5, 0.5, 0.], [0., 0., 0.4], [0., 0.5, 0.6], [0.5, 0., 0.6], [0.5, 0.5, 0.4], [0., 0., 0.8], [0.5, 0.5, 0.8]]) assert np.allclose(correct_pos, al001.get_scaled_positions()) # Check layers along 001 tags, levels = get_layers(al001, (0, 0, 1)) assert np.allclose(tags, [0, 1, 1, 0, 2, 3, 3, 2, 4, 4]) assert np.allclose(levels, [0., 2.025, 4.05, 6.075, 8.1]) # Check layers along 101 tags, levels = get_layers(al001, (1, 0, 1)) assert np.allclose(tags, [0, 1, 5, 3, 2, 4, 8, 7, 6, 9]) assert np.allclose(levels, [0.000, 0.752, 1.504, 1.880, 2.256, 2.632, 3.008, 3.384, 4.136, 4.888], atol=0.001) # Check layers along 111 tags, levels = get_layers(al001, (1, 1, 1)) assert np.allclose(tags, [0, 2, 2, 4, 1, 5, 5, 6, 3, 7]) assert np.allclose(levels, [0.000, 1.102, 1.929, 2.205, 2.756, 3.031, 3.858, 4.960], atol=0.001) # Cut out slab of three Al(111) layers al111 = cut(al, (1, -1, 0), (0, 1, -1), nlayers=3) correct_pos = np.array([[0.5, 0., 0.], [0., 0.5, 0.], [0.5, 0.5, 0.], [0., 0., 0.], [1 / 6., 1 / 3., 1 / 3.], [1 / 6., 5 / 6., 1 / 3.], [2 / 3., 5 / 6., 1 / 3.], [2 / 3., 1 / 3., 1 / 3.], [1 / 3., 1 / 6., 2 / 3.], [5 / 6., 1 / 6., 2 / 3.], [5 / 6., 2 / 3., 2 / 3.], [1 / 3., 2 / 3., 2 / 3.]]) assert np.allclose(correct_pos, al111.get_scaled_positions()) # Cut out cell including all corner and edge atoms (non-periodic structure) al = cut(al, extend=1.1) correct_pos = np.array([[0., 0., 0.], [0., 2.025, 2.025], [2.025, 0., 2.025], [2.025, 2.025, 0.], [0., 0., 4.05], [2.025, 2.025, 4.05], [0., 4.05, 0.], [2.025, 4.05, 2.025], [0., 4.05, 4.05], [4.05, 0., 0.], [4.05, 2.025, 2.025], [4.05, 0., 4.05], [4.05, 4.05, 0.], [4.05, 4.05, 4.05]]) assert np.allclose(correct_pos, al.positions) # Create an Ag(111)/Si(111) interface ag = crystal(['Ag'], basis=[(0, 0, 0)], spacegroup=225, cellpar=4.09) si = crystal(['Si'], basis=[(0, 0, 0)], spacegroup=227, cellpar=5.43) try: assert get_spacegroup(ag).no == 225 assert get_spacegroup(si).no == 227 except ImportError: pass ag111 = cut(ag, a=(4, -4, 0), b=(4, 4, -8), nlayers=5) # noqa si111 = cut(si, a=(3, -3, 0), b=(3, 3, -6), nlayers=5) # noqa # # interface = stack(ag111, si111) # assert len(interface) == 1000 # assert np.allclose(interface.positions[::100], # [[ 4.08125 , -2.040625 , -2.040625 ], # [ 8.1625 , 6.121875 , -14.284375 ], # [ 10.211875 , 0.00875 , 2.049375 ], # [ 24.49041667, -4.07833333, -16.32208333], # [ 18.37145833, 14.29020833, -24.48166667], # [ 24.49916667, 12.25541667, -20.39458333], # [ 18.36854167, 16.32791667, -30.60645833], # [ 19.0575 , 0.01166667, 5.45333333], # [ 23.13388889, 6.80888889, 1.36722222], # [ 35.3825 , 5.45333333, -16.31333333]]) # # Test the wrap_positions function. positions = np.array([ [4.0725, -4.0725, -1.3575], [1.3575, -1.3575, -1.3575], [2.715, -2.715, 0.], [4.0725, 1.3575, -1.3575], [0., 0., 0.], [2.715, 2.715, 0.], [6.7875, -1.3575, -1.3575], [5.43, 0., 0.]]) cell = np.array([[5.43, 5.43, 0.0], [5.43, -5.43, 0.0], [0.00, 0.00, 40.0]]) positions += np.array([6.1, -0.1, 10.1]) result_positions = wrap_positions(positions=positions, cell=cell) correct_pos = np.array([ [4.7425, 1.2575, 8.7425], [7.4575, -1.4575, 8.7425], [3.385, 2.615, 10.1], [4.7425, -4.1725, 8.7425], [6.1, -0.1, 10.1], [3.385, -2.815, 10.1], [2.0275, -1.4575, 8.7425], [0.67, -0.1, 10.1]]) assert np.allclose(correct_pos, result_positions) positions = wrap_positions(positions, cell, pbc=[False, True, False]) correct_pos = np.array([ [4.7425, 1.2575, 8.7425], [7.4575, -1.4575, 8.7425], [3.385, 2.615, 10.1], [10.1725, 1.2575, 8.7425], [6.1, -0.1, 10.1], [8.815, 2.615, 10.1], [7.4575, 3.9725, 8.7425], [6.1, 5.33, 10.1]]) assert np.allclose(correct_pos, positions) # Test center away from values 0, 0.5 result_positions = wrap_positions(positions, cell, pbc=[True, True, False], center=0.2) correct_pos = [[4.7425, 1.2575, 8.7425], [2.0275, 3.9725, 8.7425], [3.385, 2.615, 10.1], [-0.6875, 1.2575, 8.7425], [6.1, -0.1, 10.1], [3.385, -2.815, 10.1], [2.0275, -1.4575, 8.7425], [0.67, -0.1, 10.1]] assert np.allclose(correct_pos, result_positions) # Test pretty_translation keyword positions = np.array([ [0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0.]]) cell = np.diag([2, 2, 2]) result = wrap_positions(positions, cell, pbc=[True, True, True], pretty_translation=True) assert np.max(result) < 1 + 1E-10 assert np.min(result) > -1E-10 result = wrap_positions(positions - 5, cell, pbc=[True, True, True], pretty_translation=True) assert np.max(result) < 1 + 1E-10 result = wrap_positions(positions - 5, cell, pbc=[False, True, True], pretty_translation=True) assert np.max(result[:, 0]) < -3 assert np.max(result[:, 1:]) < 1 + 1E-10 # Get the correct crystal structure from a range of different cells def checkcell(cell, name): cell = Cell.ascell(cell) lat = cell.get_bravais_lattice() assert lat.name == name, (lat.name, name) checkcell(bulk('Al').cell, 'FCC') checkcell(bulk('Fe').cell, 'BCC') checkcell(bulk('Zn').cell, 'HEX') checkcell(fcc111('Au', size=(1, 1, 3), periodic=True).cell, 'HEX') checkcell([[1, 0, 0], [0, 1, 0], [0, 0, 1]], 'CUB') checkcell([[1, 0, 0], [0, 1, 0], [0, 0, 2]], 'TET') checkcell([[1, 0, 0], [0, 2, 0], [0, 0, 3]], 'ORC') checkcell([[1, 0, 0], [0, 2, 0], [0.5, 0, 3]], 'ORCC') checkcell([[1, 0, 0], [0, 2, 0], [0.501, 0, 3]], 'MCL') checkcell([[1, 0, 0], [0.5, 3**0.5 / 2, 0], [0, 0, 3]], 'HEX')
# [ 35.3825 , 5.45333333, -16.31333333]]) # # Test the wrap_positions function. positions = np.array([ [4.0725, -4.0725, -1.3575], [1.3575, -1.3575, -1.3575], [2.715, -2.715, 0.], [4.0725, 1.3575, -1.3575], [0., 0., 0.], [2.715, 2.715, 0.], [6.7875, -1.3575, -1.3575], [5.43, 0., 0.]]) cell = np.array([[5.43, 5.43, 0.0], [5.43, -5.43, 0.0], [0.00, 0.00, 40.0]]) positions += np.array([6.1, -0.1, 10.1]) result_positions = wrap_positions(positions=positions, cell=cell) correct_pos = np.array([ [4.7425, 1.2575, 8.7425], [7.4575, -1.4575, 8.7425], [3.385, 2.615, 10.1], [4.7425, -4.1725, 8.7425], [6.1, -0.1, 10.1], [3.385, -2.815, 10.1], [2.0275, -1.4575, 8.7425], [0.67, -0.1, 10.1]]) assert np.allclose(correct_pos, result_positions) positions = wrap_positions(positions, cell, pbc=[False, True, False]) correct_pos = np.array([ [4.7425, 1.2575, 8.7425], [7.4575, -1.4575, 8.7425],
# so you want it to wrap to the higher value (e.g., BAZJET). centercorrection = np.identity(3) * 0.00001 center1 = np.full(DIM, 0.5) + np.dot(centercorrection, deltapos) #center2 = np.full(DIM,0.5) - np.dot(centercorrection,deltapos) # Wrap node positions so they start in the same unit cell # because zeo++ sometimes gives position of node1 in the # extended unit cell. # (Not sure this is also happening with node2, but wrap just in case) #node1 = [x1,y1,z1] # wrap node 1 node1 = [x1, y1, z1] node2 = [x2, y2, z2] print("node1: ", node1) print("node2: ", node2) node1 = geometry.wrap_positions([node1], cell, center=center1)[0] node1_frac = cartn_to_frac(node1, invcell) # wrap node 2 to same image as node 1 node2 = geometry.wrap_positions([node2], cell, center=node1_frac)[0] # translate node2 if across unit cell #node2 = shift_point(node2, deltapos, cell) print("wrapped node1: ", node1) print("wrapped node2: ", node2) # compute midpoint and vectors mid = np.zeros(DIM) # FIXME declare array of length DIM edgevec = np.zeros(DIM) edgelength = 0.0 normvec = np.zeros(DIM) for i in range(DIM):
def make_precon(self, atoms, recalc_mu=None): """Create a preconditioner matrix based on the passed set of atoms. Creates a general-purpose preconditioner for use with optimization algorithms, based on examining distances between pairs of atoms in the lattice. The matrix will be stored in the attribute self.P and returned. Args: atoms: the Atoms object used to create the preconditioner. Can also recalc_mu: if True, self.mu (and self.mu_c for variable cell) will be recalculated by calling self.estimate_mu(atoms) before the preconditioner matrix is created. If False, self.mu will be calculated only if it does not currently have a value (ie, the first time this function is called). Returns: A two-element tuple: P: A sparse scipy csr_matrix. BE AWARE that using numpy.dot() with sparse matrices will result in errors/incorrect results - use the .dot method directly on the matrix instead. """ if self.r_NN is None: self.r_NN = estimate_nearest_neighbour_distance(atoms) if self.r_cut is None: # This is the first time this function has been called, and no # cutoff radius has been specified, so calculate it automatically. self.r_cut = 2.0 * self.r_NN elif self.r_cut < self.r_NN: warning = ('WARNING: r_cut (%.2f) < r_NN (%.2f), ' 'increasing to 1.1*r_NN = %.2f' % (self.r_cut, self.r_NN, 1.1 * self.r_NN)) logger.info(warning) print(warning) self.r_cut = 1.1 * self.r_NN if recalc_mu is None: # The caller has not specified whether or not to recalculate mu, # so the Precon's setting is used. recalc_mu = self.recalc_mu if self.mu is None: # Regardless of what the caller has specified, if we don't # currently have a value of mu, then we need one. recalc_mu = True if recalc_mu: self.estimate_mu(atoms) if self.P is not None: real_atoms = atoms if isinstance(atoms, Filter): real_atoms = atoms.atoms if self.old_positions is None: self.old_positions = wrap_positions(real_atoms.positions, real_atoms.cell) displacement = wrap_positions(real_atoms.positions, real_atoms.cell) - self.old_positions self.old_positions = real_atoms.get_positions() max_abs_displacement = abs(displacement).max() logger.info('max(abs(displacements)) = %.2f A (%.2f r_NN)', max_abs_displacement, max_abs_displacement / self.r_NN) if max_abs_displacement < 0.5 * self.r_NN: return self.P start_time = time.time() # Create the preconditioner: self._make_sparse_precon(atoms, force_stab=self.force_stab) logger.info('--- Precon created in %s seconds ---', time.time() - start_time) return self.P