def create_surface(sc_shape): from scipy.spatial import cKDTree as KDTree atoms = bulk("Al") * sc_shape if sc_shape[2] % 2 != 0: raise ValueError("The third direction has to be divisible by 2!") num_layers = int(sc_shape[2] / 2) # Create a cut such that a3 vector is the normal vector slab = cut(atoms, a=(1, 0, 0), b=(0, 1, 0), c=(0, 0, 1), nlayers=num_layers) tags, _ = get_layers(slab, (1, -1, 0)) for tag, atom in zip(tags, slab): if tag % 2 == 0: atom.symbol = "Mg" else: atom.symbol = "Si" tree = KDTree(atoms.get_positions()) used_indices = [] for atom in slab: _, closest_indx = tree.query(atom.position) if closest_indx in used_indices: raise RuntimeError("Two atoms are mapped onto the same index!") atoms[closest_indx].symbol = atom.symbol used_indices.append(closest_indx) return atoms
def main(): aluminum = build.bulk("Al", crystalstructure="fcc") * 4 print(len(aluminum)) # Extract 3 111 planes planes = build.cut(aluminum, (1, -1, 0), (1, 1, -2), nlayers=3) view(planes, viewer="Avogadro")
def main(): atoms = read("data/mgsi100_fully_relaxed.xyz") # Create slab plane1 = atoms slab = cut(plane1, a=(1, 0, -1), b=(0, 1, 0)) # Plane perp to planes plane = slab * (6, 1, 6) write("data/plate_perp2layers.xyz", plane) # Plane parallel to planes plane = slab * (6, 6, 1) write("data/plate_par2planes.xyz", plane) # Needles perp to planes needle = slab * (6, 2, 2) write("data/needle_perp2planes.xyz", needle) view(needle) # Needle par to planes needle = slab * (2, 2, 6) write("data/needle_par2planes.xyz", needle) view(needle) view(slab)
def create_clathrate(size): x = io.read('jp111328v_si_001.cif') # need to reorder things for the tip4p water model, so read the cif file to do so with open('jp111328v_si_001.cif') as f: txt = f.readlines() read = False nums = [] for line in txt: if line.startswith('_geom_bond_site_symmetry_2'): read = True continue elif read and line.startswith('O'): nums.append(int(line.split()[1][1:])) else: continue # now we reorder, because we need our water molecules to list O # first, followed by the two Hs, for the tip4p model # We also replace methane hydrogens with S as a placeholder in ase acc = x[0:1] acc.append(x[nums[0] - 1]) acc.append(x[nums[1] - 1]) for i in range(1, 46): acc.append(x[i]) acc.append(x[nums[2 * i] - 1]) acc.append(x[nums[2 * i + 1] - 1]) for i in range(138, len(x)): if x[i].symbol == 'C': acc.append(x[i]) else: new_xi = x[i] new_xi.symbol = 'S' # replace methane hydrogen with s as placeholder acc.append(new_xi) # now let's create our supercell; need to use cut in order to make sure # atoms aren't removed cell_len = acc.cell[0, 0] m = np.identity(3) * size mh_super = cut(acc, m[0], m[1], m[2], tolerance=1e-5) # now let's move atoms if they were previously across the pbc i = 0 while i < len(mh_super): if mh_super.numbers[i] == 8: natoms = 3 elif mh_super.numbers[i] == 6: natoms = 5 else: raise ValueError for d in range(3): pos_d = mh_super.positions[i:i + natoms, d] max_d = np.max(pos_d) pos_d[np.abs(pos_d - max_d) > cell_len / 2] += acc.cell[d, d] i += natoms mh_super.positions[:, 1] += cell_len / 2 mh_super.positions[:, 1] %= (cell_len * size[1]) # scale positions based off Tung et al, 2011 mh_super.cell *= 23.74 / (2 * cell_len) mh_super.positions *= 23.74 / (2 * cell_len) return mh_super
def passivate_zinc_blende_slab_nonprimitive(slab, passivant, direction): """ The idea is to have the slabs passivated with passivant in the direction. right now only works for the 110 direction default zinc blende sturcture should be input as slab if not in the 001 direction """ import numpy as np if direction == (0, 0, 1): return passivate_zincblende_slab_001(slab, passivant) else: c = np.array(direction, dtype=float) b = np.array((0, 0, 1), dtype=float) a = np.cross(b, direction) y = cut(slab, a, b, nlayers=6) # y.edit() # coords = y.get_positions() # Fixing the amount that you want to displace by d = 2 # displace by this amount xy = .75 if passivant == "H": atoms_up = [ 20, 15, 23, 22 ] # These are atoms that were coreated using an extra layer at the top atoms_down = [ 0, 6, 3, 2 ] # These are atoms that were coreated using an extra layer at the bottom for num in atoms_up: y[num].symbol = f"{passivant}" # Since in the 110 direction we have only the one bond to take care of in zincblende we need only replace the atoms with all of these y[num].position = [ y[num].position[0], y[num].position[1], y[num].position[2] ] for num in atoms_down: y[num].symbol = f"{passivant}" # Since in the 110 direction we have only the one bond to take care of in zincblende we need only replace the atoms with all of these y[num].position = [ y[num].position[0], y[num].position[1], y[num].position[2] ] elif passivant == "O": pass return y
def supercell(atoms, max_cell_length=12, bulk=True): """Returns atoms in supercell from the unit cell. Parameters ---------- atoms : object max_cell_length : desiered maximum unit-cell-parameter bulk : Whether it is a bulk system. """ cell = atoms.get_cell() nkx = int(round(max_cell_length/np.linalg.norm(cell[0]),0)) nky = int(round(max_cell_length/np.linalg.norm(cell[1]),0)) if bulk == True: nkz = int(round(max_cell_length/np.linalg.norm(cell[2]),0)) else: nkz = 1 new_atoms = cut(atoms, a=(nkx,0,0), b=(0,nky,0), c=(0,0,nkz), clength=None, origo=(0, 0, 0), nlayers=None, extend=1.0, tolerance=0.01, maxatoms=None) return(new_atoms)
def gamaToObtuseangle(self): # 把晶胞betta变成钝角方便比较切应变, 注:不改变self.crys cell_par_lst = self.crys.get_cell_lengths_and_angles() if cell_par_lst[5] >= 90: return self.crys else: vector = self.crys.get_cell() b = cell_par_lst[1] gama = cell_par_lst[5] mubiaob = [ b * np.cos((180 - gama) / 180 * np.pi), b * np.sin( (180 - gama) / 180 * np.pi), 0 ] A = vector.T r = np.linalg.solve(A, mubiaob) # 求解线性方程组,直角坐标系下----用晶胞坐标系表示 real = cut(self.crys, a=[1, 0, 0], b=r.T, c=[0, 0, 1], origo=(0, 0, 0)) return real
def make_supercell(prim, P): """Generate a supercell by applying a general transformation (*P*) to the input configuration (*prim*). The transformation is described by a 3x3 integer matrix `\mathbf{P}`. Specifically, the new cell metric `\mathbf{h}` is given in terms of the metric of the input configuraton `\mathbf{h}_p` by `\mathbf{P h}_p = \mathbf{h}`. Internally this function uses the :func:`~ase.build.cut` function. Parameters: prim: ASE Atoms object Input configuration. P: 3x3 integer matrix Transformation matrix `\mathbf{P}`. """ from ase.build import cut return cut(prim, P[0], P[1], P[2])
"""Test the ase.geometry module and ase.build.cut() function.""" from __future__ import division import numpy as np from ase.build import cut, bulk from ase.geometry import (get_layers, wrap_positions, crystal_structure_from_cell) 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])
def crystal(symbols=None, basis=None, occupancies=None, spacegroup=1, setting=1, cell=None, cellpar=None, ab_normal=(0, 0, 1), a_direction=None, size=(1, 1, 1), onduplicates='warn', symprec=0.001, pbc=True, primitive_cell=False, **kwargs): """Create an Atoms instance for a conventional unit cell of a space group. Parameters: symbols : str | sequence of str | sequence of Atom | Atoms Element symbols of the unique sites. Can either be a string formula or a sequence of element symbols. E.g. ('Na', 'Cl') and 'NaCl' are equivalent. Can also be given as a sequence of Atom objects or an Atoms object. basis : list of scaled coordinates Positions of the unique sites corresponding to symbols given either as scaled positions or through an atoms instance. Not needed if *symbols* is a sequence of Atom objects or an Atoms object. occupancies : list of site occupancies Occupancies of the unique sites. Defaults to 1.0 and thus no mixed occupancies are considered if not explicitly asked for. If occupancies are given, the most dominant species will yield the atomic number. spacegroup : int | string | Spacegroup instance Space group given either as its number in International Tables or as its Hermann-Mauguin symbol. setting : 1 | 2 Space group setting. cell : 3x3 matrix Unit cell vectors. cellpar : [a, b, c, alpha, beta, gamma] Cell parameters with angles in degree. Is not used when `cell` is given. ab_normal : vector Is used to define the orientation of the unit cell relative to the Cartesian system when `cell` is not given. It is the normal vector of the plane spanned by a and b. a_direction : vector Defines the orientation of the unit cell a vector. a will be parallel to the projection of `a_direction` onto the a-b plane. size : 3 positive integers How many times the conventional unit cell should be repeated in each direction. onduplicates : 'keep' | 'replace' | 'warn' | 'error' Action if `basis` contain symmetry-equivalent positions: 'keep' - ignore additional symmetry-equivalent positions 'replace' - replace 'warn' - like 'keep', but issue an UserWarning 'error' - raises a SpacegroupValueError symprec : float Minimum "distance" betweed two sites in scaled coordinates before they are counted as the same site. pbc : one or three bools Periodic boundary conditions flags. Examples: True, False, 0, 1, (1, 1, 0), (True, False, False). Default is True. primitive_cell : bool Wheter to return the primitive instead of the conventional unit cell. Keyword arguments: All additional keyword arguments are passed on to the Atoms constructor. Currently, probably the most useful additional keyword arguments are `info`, `constraint` and `calculator`. Examples: Two diamond unit cells (space group number 227) >>> diamond = crystal('C', [(0,0,0)], spacegroup=227, ... cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1)) >>> ase.view(diamond) # doctest: +SKIP A CoSb3 skutterudite unit cell containing 32 atoms >>> skutterudite = crystal(('Co', 'Sb'), ... basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], ... spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90]) >>> len(skutterudite) 32 """ sg = Spacegroup(spacegroup, setting) if (not isinstance(symbols, basestring) and hasattr(symbols, '__getitem__') and len(symbols) > 0 and isinstance(symbols[0], ase.Atom)): symbols = ase.Atoms(symbols) if isinstance(symbols, ase.Atoms): basis = symbols symbols = basis.get_chemical_symbols() if isinstance(basis, ase.Atoms): basis_coords = basis.get_scaled_positions() if cell is None and cellpar is None: cell = basis.cell if symbols is None: symbols = basis.get_chemical_symbols() else: basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2) if occupancies is not None: occupancies_dict = {} for index, coord in enumerate(basis_coords): # Compute all distances and get indices of nearest atoms dist = spatial.distance.cdist(coord.reshape(1, 3), basis_coords) indices_dist = np.flatnonzero(dist < symprec) occ = {symbols[index]: occupancies[index]} # Check nearest and update occupancy for index_dist in indices_dist: if index == index_dist: continue else: occ.update({symbols[index_dist]: occupancies[index_dist]}) occupancies_dict[index] = occ.copy() sites, kinds = sg.equivalent_sites(basis_coords, onduplicates=onduplicates, symprec=symprec) # this is needed to handle deuterium masses masses = None if 'masses' in kwargs: masses = kwargs['masses'][kinds] del kwargs['masses'] symbols = parse_symbols(symbols) if occupancies is None: symbols = [symbols[i] for i in kinds] else: # make sure that we put the dominant species there symbols = [ sorted(occupancies_dict[i].items(), key=lambda x: x[1])[-1][0] for i in kinds ] if cell is None: cell = cellpar_to_cell(cellpar, ab_normal, a_direction) info = dict(spacegroup=sg) if primitive_cell: info['unit_cell'] = 'primitive' else: info['unit_cell'] = 'conventional' if 'info' in kwargs: info.update(kwargs['info']) if occupancies is not None: info['occupancy'] = occupancies_dict kwargs['info'] = info atoms = ase.Atoms(symbols, scaled_positions=sites, cell=cell, pbc=pbc, masses=masses, **kwargs) # if all occupancies are 1, no partial occupancy present if occupancies: if not all([occ == 1 for occ in occupancies]): # use tags to identify sites, and in particular the occupancy atoms.set_tags(kinds) if isinstance(basis, ase.Atoms): for name in basis.arrays: if not atoms.has(name): array = basis.get_array(name) atoms.new_array(name, [array[i] for i in kinds], dtype=array.dtype, shape=array.shape[1:]) if primitive_cell: from ase.build import cut prim_cell = sg.scaled_primitive_cell # Preserve calculator if present: calc = atoms.calc atoms = cut(atoms, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2]) atoms.calc = calc if size != (1, 1, 1): atoms = atoms.repeat(size) return atoms
def orthogonalize_cell(atoms: Atoms, max_repetitions: int = 5, return_transform: bool = False, transform: Union[bool, str] = True, tolerance=0.01): """ Make the cell of an ASE atoms object orthogonal. This is accomplished by repeating the cell until lattice vectors are close to the three principal Cartesian directions. If the structure is not exactly orthogonal after the structure is repeated by a given maximum the remaining difference will be made up by applying strain. Parameters ---------- atoms : ASE atoms object The non-orthogonal atoms object. max_repetitions : int The maximum number of repetions allowed. Increase this to allow more repetitions and hence less strain. return_transform : bool If true, return the transformations that were applied to make the atoms orthogonal. transform : bool If false no transformation is applied to make the cell orthogonal, hence a non-orthogonal cell may be returned. Returns ------- atoms : ASE atoms object The orthogonal atoms. transform : tuple of arrays The applied transform in the form the euler angles """ eps = 1e-12 zero_vectors = np.linalg.norm(atoms.cell, axis=0) < eps if zero_vectors.sum() > 1: raise RuntimeError( "two or more lattice vectors of the provided Atoms has no length") elif zero_vectors.sum() == 1: atoms.center(axis=np.where(zero_vectors)[0], vacuum=tolerance + eps) k = np.arange(-max_repetitions, max_repetitions + 1) l = np.arange(-max_repetitions, max_repetitions + 1) m = np.arange(-max_repetitions, max_repetitions + 1) a, b, c = atoms.cell vectors = np.abs(((k[:, None] * a[None])[:, None, None] + (l[:, None] * b[None])[None, :, None] + (m[:, None] * c[None])[None, None, :])) norm = np.linalg.norm(vectors, axis=-1) nonzero = norm > eps norm[nonzero == 0] = eps new_vectors = [] for i in range(3): angles = vectors[..., i] / norm optimal = np.abs(angles.max() - angles < eps) optimal = np.where(optimal * nonzero) n = np.linalg.norm(vectors[optimal], axis=1) j = np.argmin(n) new_vector = np.array( [k[optimal[0][j]], l[optimal[1][j]], m[optimal[2][j]]]) new_vector = np.sign(np.dot(new_vector, atoms.cell)[i]) * new_vector new_vectors.append(new_vector) atoms = cut(atoms, *new_vectors, tolerance=tolerance) cell = Cell.new(np.linalg.norm(atoms.cell, axis=0)) A = np.linalg.solve(atoms.cell.complete(), cell.complete()) if transform is True: atoms.positions[:] = np.dot(atoms.positions, A) atoms.cell[:] = cell elif transform == 'raise': if not is_cell_orthogonal(atoms): raise RuntimeError() atoms = shrink_cell(atoms, 2) if return_transform and transform: rotation, zoom, shear = decompose_affine_transform(A) return atoms, (np.array(rotation_matrix_to_euler(rotation)), zoom, shear) else: return atoms
"""Test the ase.geometry module and ase.build.cut() function.""" from __future__ import division import numpy as np from ase.build import cut, bulk from ase.geometry import (get_layers, wrap_positions, crystal_structure_from_cell) from ase.spacegroup import crystal 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])
import numpy.testing as npt from ase.build import bulk, cut from icet import ClusterSpace # initializes cluster space and get the internal primitive structure prim = bulk('Au', a=4.0, crystalstructure='hcp') subelements = ['Au', 'Pd'] cutoffs = [0.0] cs = ClusterSpace(prim, cutoffs, subelements) structure_prim = cs.primitive_structure # create a supercell using permutation matrix p_trial = [[1, 0, 0], [0, 1, 5], [0, 0, 2]] supercell = cut(structure_prim, p_trial[0], p_trial[1], p_trial[2]) # setup cartesian input to generate a random population cartesian_product_input = [] for i in range(len(supercell)): cartesian_product_input.append(['Pd', 'Au']) # loop over element combinations and assert expected singlet value for subset in itertools.product(*cartesian_product_input): for atom, element in zip(supercell, subset): atom.symbol = element cv = cs.get_cluster_vector(supercell) expected_singlet = -supercell.get_chemical_symbols().count('Pd') expected_singlet += supercell.get_chemical_symbols().count('Au') expected_singlet /= len(supercell) npt.assert_almost_equal(cv[1], expected_singlet)
def crystal(symbols=None, basis=None, spacegroup=1, setting=1, cell=None, cellpar=None, ab_normal=(0, 0, 1), a_direction=None, size=(1, 1, 1), onduplicates='warn', symprec=0.001, pbc=True, primitive_cell=False, **kwargs): """Create an Atoms instance for a conventional unit cell of a space group. Parameters: symbols : str | sequence of str | sequence of Atom | Atoms Element symbols of the unique sites. Can either be a string formula or a sequence of element symbols. E.g. ('Na', 'Cl') and 'NaCl' are equivalent. Can also be given as a sequence of Atom objects or an Atoms object. basis : list of scaled coordinates Positions of the unique sites corresponding to symbols given either as scaled positions or through an atoms instance. Not needed if *symbols* is a sequence of Atom objects or an Atoms object. spacegroup : int | string | Spacegroup instance Space group given either as its number in International Tables or as its Hermann-Mauguin symbol. setting : 1 | 2 Space group setting. cell : 3x3 matrix Unit cell vectors. cellpar : [a, b, c, alpha, beta, gamma] Cell parameters with angles in degree. Is not used when `cell` is given. ab_normal : vector Is used to define the orientation of the unit cell relative to the Cartesian system when `cell` is not given. It is the normal vector of the plane spanned by a and b. a_direction : vector Defines the orientation of the unit cell a vector. a will be parallel to the projection of `a_direction` onto the a-b plane. size : 3 positive integers How many times the conventional unit cell should be repeated in each direction. onduplicates : 'keep' | 'replace' | 'warn' | 'error' Action if `basis` contain symmetry-equivalent positions: 'keep' - ignore additional symmetry-equivalent positions 'replace' - replace 'warn' - like 'keep', but issue an UserWarning 'error' - raises a SpacegroupValueError symprec : float Minimum "distance" betweed two sites in scaled coordinates before they are counted as the same site. pbc : one or three bools Periodic boundary conditions flags. Examples: True, False, 0, 1, (1, 1, 0), (True, False, False). Default is True. primitive_cell : bool Wheter to return the primitive instead of the conventional unit cell. Keyword arguments: All additional keyword arguments are passed on to the Atoms constructor. Currently, probably the most useful additional keyword arguments are `info`, `constraint` and `calculator`. Examples: Two diamond unit cells (space group number 227) >>> diamond = crystal('C', [(0,0,0)], spacegroup=227, ... cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1)) >>> ase.view(diamond) # doctest: +SKIP A CoSb3 skutterudite unit cell containing 32 atoms >>> skutterudite = crystal(('Co', 'Sb'), ... basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], ... spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90]) >>> len(skutterudite) 32 """ sg = Spacegroup(spacegroup, setting) if (not isinstance(symbols, str) and hasattr(symbols, '__getitem__') and len(symbols) > 0 and isinstance(symbols[0], ase.Atom)): symbols = ase.Atoms(symbols) if isinstance(symbols, ase.Atoms): basis = symbols symbols = basis.get_chemical_symbols() if isinstance(basis, ase.Atoms): basis_coords = basis.get_scaled_positions() if cell is None and cellpar is None: cell = basis.cell if symbols is None: symbols = basis.get_chemical_symbols() else: basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2) sites, kinds = sg.equivalent_sites(basis_coords, onduplicates=onduplicates, symprec=symprec) symbols = parse_symbols(symbols) symbols = [symbols[i] for i in kinds] if cell is None: cell = cellpar_to_cell(cellpar, ab_normal, a_direction) info = dict(spacegroup=sg) if primitive_cell: info['unit_cell'] = 'primitive' else: info['unit_cell'] = 'conventional' if 'info' in kwargs: info.update(kwargs['info']) kwargs['info'] = info atoms = ase.Atoms(symbols, scaled_positions=sites, cell=cell, pbc=pbc, **kwargs) if isinstance(basis, ase.Atoms): for name in basis.arrays: if not atoms.has(name): array = basis.get_array(name) atoms.new_array(name, [array[i] for i in kinds], dtype=array.dtype, shape=array.shape[1:]) if primitive_cell: from ase.build import cut prim_cell = sg.scaled_primitive_cell atoms = cut(atoms, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2]) if size != (1, 1, 1): atoms = atoms.repeat(size) return atoms
""" 读取结构扩胞 """ pri_struct = read('POSCAR1.vasp') # 读取原胞POSCAR结构 write(filename='poscar3.vasp', images=pri_struct, format='vasp') # 默认format为文件名后缀 # # view(pri_struct) # ase.io.vasp.write_vasp("POSCAR4x4x4.vasp", pri_struct*(4,4,4), label='444supercell',direct=True,sort=True) # 扩胞操作 a1 = read('POSCAR4x4x4.vasp') """ 切表面 """ # Cutsurface = cut(a1, c=(0, 0, 1), origo=(0, 0, 0), nlayers=4) # 切(0,0,1)面,保留4层 # Cutsurface.center(vacuum=10, axis=2) # 加真空层,axis=0是x方向,1是y方向,z是c方向 # view(Cutsurface) # alt+x/y/z 切换视图的方向 # print(Cutsurface.cell) # print('--------------') # print(Cutsurface.positions) # print('--------------') # print(Cutsurface.numbers) # write_poscar(Cutsurface, '001.vasp') Cutsurface = cut(a1, c=(1, 1, 1), origo=(0, 0, 0), nlayers=4) # 切(1,1,1)面,保留4层 Cutsurface.center(vacuum=10, axis=2) # 加真空层 # view(Cutsurface) # print(Cutsurface.cell) # write_poscar(Cutsurface, '111.vasp') # write(filename='poscar3.vasp', images=Cutsurface) # Cutsurface = cut(a1, c=(0, 1, 1), origo=(0, 0, 0), nlayers=4) # 切(1,1,1)面,保留4层 # Cutsurface.center(vacuum=10, axis=2) # 加真空层 # # view(Cutsurface) # write_poscar(Cutsurface, '011.vasp')
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')
from ase.build import bulk from ase.spacegroup import crystal from wulffpack import SingleCrystal from ase.visualize import view from ase.spacegroup import get_spacegroup from ase.build import cut gamma1 = 0.33 gamma2 = 0.57 surface_energies = {(0, 0, 1): gamma1, (1, 0, 0): gamma2} prim = bulk('Al', crystalstructure='fcc', cubic=True) prim.symbols[[0, 3]] = 'Mg' prim.symbols[[2, 1]] = 'Si' sg = get_spacegroup(prim) prim_cell = sg.scaled_primitive_cell prim = cut(prim, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2]) from ase.visualize import view view(prim) particle = SingleCrystal(surface_energies, primitive_structure=prim, natoms=1000) view(particle.atoms)
def passivate_zinc_blende_slab(slab, passivant, direction, n_bilayers, primtive=True): """ The idea is to have the slabs passivated with passivant in the direction. right now only works for the 110 direction default zinc blende sturcture should be input as slab if not in the 001 direction n_bilayers is the number of bilayers in the slab """ import numpy as np if direction == (0, 0, 1): c = np.array(direction, dtype=float) a = np.array((1, 0, 0), dtype=float) b = np.cross(c, a) # here we set the scale of the xy directions if primtive == True: xy_scale = 1 if primtive == False: xy_scale = 2 y = cut(slab, xy_scale * a / 2, xy_scale * b / 2, nlayers=2 * n_bilayers + 2) # Fixing the amount that you want to displace by d = .5 # displace by this amount xy = slab.get_cell()[0][0] # print(xy) # xy = .75 # y.edit() if passivant == "H": if primtive == True: atoms_up = [ -1 ] # These are atoms that were coreated using an extra layer at the top atoms_down = [ 0 ] # These are atoms that were coreated using an extra layer at the bottom up_pos = [ y[atoms_up[0]].position[0], y[atoms_up[0]].position[1], y[atoms_up[0]].position[2] ] dwn_pos = [ y[atoms_down[0]].position[0], y[atoms_down[0]].position[1], y[atoms_down[0]].position[2] ] if primtive == False: pass atoms_up = [ -4, -9, -1, -2 ] # These are atoms that were coreated using an extra layer at the top atoms_down = [ 0, 6, 3, 2 ] # These are atoms that were coreated using an extra layer at the bottom for num in atoms_up: y[num].symbol = f"{passivant}" # Since in the 110 direction we have only the one bond to take care of in zincblende we need only replace the atoms with all of these y[num].position = [ up_pos[0] - xy / 8, up_pos[1] - xy / 8, up_pos[2] ] # y[num].position = [y[num].position[0]-xy, y[num].position[1]+xy, y[num].position[2]] for num in atoms_down: y[num].symbol = f"{passivant}" # Since in the 110 direction we have only the one bond to take care of in zincblende we need only replace the atoms with all of these y[num].position = [ dwn_pos[0] + xy / 8, dwn_pos[1] + xy / 8, dwn_pos[2] ] # y[num].position = [y[num].position[0]-xy, y[num].position[1]+xy, y[num].position[2]] # # adding extra atoms for up for num in atoms_up: y.append(f"{passivant}") y[-1].position = [ up_pos[0] - 3 * xy / 8, up_pos[1] - 3 * xy / 8, up_pos[2] ] # y[-1].position = [y[num].position[0]+2*xy, y[num].position[1]-2*xy, y[num].position[2]] for num in atoms_down: y.append(f"{passivant}") y[-1].position = [ dwn_pos[0] + 3 * xy / 8, dwn_pos[1] + 3 * xy / 8, dwn_pos[2] ] # y[-1].position = [y[num].position[0]+2*xy, y[num].position[1]-2*xy, y[num].position[2]] elif passivant == "O": pass # y.edit() # y*= (3,3,1) # y.edit() return y else: c = np.array(direction, dtype=float) b = np.array((0, 0, 1), dtype=float) a = np.cross(b, direction) # here we set the scale of the xy directions if primtive == True: xy_scale = 1 if primtive == False: xy_scale = 2 y = cut(slab, xy_scale * a / 2, b, nlayers=2 * n_bilayers + 2) # y.edit() # coords = y.get_positions() # Fixing the amount that you want to displace by d = 2 # displace by this amount xy = .75 if passivant == "H": if primtive == True: atoms_up = [ -1, -2 ] # These are atoms that were coreated using an extra layer at the top atoms_down = [ 0, 1 ] # These are atoms that were coreated using an extra layer at the bottom if primtive == False: atoms_up = [ -4, -9, -1, -2 ] # These are atoms that were coreated using an extra layer at the top atoms_down = [ 0, 6, 3, 2 ] # These are atoms that were coreated using an extra layer at the bottom for num in atoms_up: y[num].symbol = f"{passivant}" # Since in the 110 direction we have only the one bond to take care of in zincblende we need only replace the atoms with all of these y[num].position = [ y[num].position[0], y[num].position[1], y[num].position[2] ] for num in atoms_down: y[num].symbol = f"{passivant}" # Since in the 110 direction we have only the one bond to take care of in zincblende we need only replace the atoms with all of these y[num].position = [ y[num].position[0], y[num].position[1], y[num].position[2] ] elif passivant == "O": pass return y
#/usr/local/bin/python3 import ase.io as io from ase.build import cut from ase.spacegroup import crystal a = 9.04 skutterudite = crystal(('Co', 'Sb'), basis=[(0.25, 0.25, 0.25), (0.0, 0.335, 0.158)], spacegroup=204, cellpar=[a, a, a, 90, 90, 90]) # Create a new atoms instance with Co at origo including all atoms on the # surface of the unit cell cosb3 = cut(skutterudite, origo=(0.25, 0.25, 0.25), extend=1.01) # Define the atomic bonds to show bondatoms = [] symbols = cosb3.get_chemical_symbols() for i in range(len(cosb3)): for j in range(i): if (symbols[i] == symbols[j] == 'Co' and cosb3.get_distance(i, j) < 4.53): bondatoms.append((i, j)) elif (symbols[i] == symbols[j] == 'Sb' and cosb3.get_distance(i, j) < 2.99): bondatoms.append((i, j)) # Create nice-looking image using povray io.write('spacegroup-cosb3.pov', cosb3, transparent=False, display=False,
import ase.io as io from ase.build import cut from ase.spacegroup import crystal a = 9.04 skutterudite = crystal(('Co', 'Sb'), basis=[(0.25, 0.25, 0.25), (0.0, 0.335, 0.158)], spacegroup=204, cellpar=[a, a, a, 90, 90, 90]) # Create a new atoms instance with Co at origo including all atoms on the # surface of the unit cell cosb3 = cut(skutterudite, origo=(0.25, 0.25, 0.25), extend=1.01) # Define the atomic bonds to show bondatoms = [] symbols = cosb3.get_chemical_symbols() for i in range(len(cosb3)): for j in range(i): if (symbols[i] == symbols[j] == 'Co' and cosb3.get_distance(i, j) < 4.53): bondatoms.append((i, j)) elif (symbols[i] == symbols[j] == 'Sb' and cosb3.get_distance(i, j) < 2.99): bondatoms.append((i, j)) # Create nice-looking image using povray io.write('spacegroup-cosb3.pov', cosb3, transparent=False, display=False, run_povray=True,