def _build_basis(self, bulk): """Get the basis unit cell from bulk unit cell. This basis is effectively the same as the bulk, but rotated such that the z-axis is aligned with the surface termination. The basis is stored separately from the slab generated and is only intended for internal use. Returns: -------- basis : atoms object The basis slab corresponding to the provided bulk. """ h, k, l = self.miller_index h0, k0, l0 = (self.miller_index == 0) if h0 and k0 or h0 and l0 or k0 and l0: if not h0: c1, c2, c3 = [(0, 1, 0), (0, 0, 1), (1, 0, 0)] if not k0: c1, c2, c3 = [(0, 0, 1), (1, 0, 0), (0, 1, 0)] if not l0: c1, c2, c3 = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] else: p, q = ext_gcd(k, l) a1, a2, a3 = bulk.cell # constants describing the dot product of basis c1 and c2: # dot(c1,c2) = k1+i*k2, i in Z k1 = np.dot(p * (k * a1 - h * a2) + q * (l * a1 - h * a3), l * a2 - k * a3) k2 = np.dot(l * (k * a1 - h * a2) - k * (l * a1 - h * a3), l * a2 - k * a3) if abs(k2) > self.tol: # i corresponding to the optimal basis i = -int(np.round(k1 / k2)) p, q = p + i * l, q - i * k a, b = ext_gcd(p * k + q * l, h) c1 = (p * k + q * l, -p * h, -q * h) c2 = np.array((0, l, -k)) // abs(gcd(l, k)) c3 = (b, a * p, a * q) basis_atoms = Gratoms(positions=bulk.positions, numbers=bulk.get_atomic_numbers(), cell=bulk.cell, pbc=True) basis = np.array([c1, c2, c3]) scaled = solve(basis.T, basis_atoms.get_scaled_positions().T).T scaled -= np.floor(scaled + self.tol) basis_atoms.set_scaled_positions(scaled) basis_atoms.set_cell(np.dot(basis, basis_atoms.cell), scale_atoms=True) a1, a2, a3 = basis_atoms.cell a3 = np.cross(a1, a2) / norm(np.cross(a1, a2)) rotate(basis_atoms, a3, (0, 0, 1), a1, (1, 0, 0)) return basis_atoms
def orient(atoms, zone_axis, new_x=None, new_y=None): ''' Orient an atoms object along a particular zone axis (zone axis along the Cartesian z axis) Parameters ---------- atoms : Atoms object an Atoms object from ASE package, created by reading any file supported by ASE zone_axis : iterable the zone axis, align to the Cartesian z axis (optical axis) new_x : iterable, optional the new crystal axis along the Cartesian x axis, orthogonal to the zone axis and the crystal axis along y axis new_y : iterable, optional the new crystal axis along the Cartesian y axis, orthogonal to the zone axis and the crystal axis along x axis Returns ------- rotated : Atoms object the oriented Atoms object with the specified crystal axes along x, y, z ''' # define the Cartesian coordinate system x = [1,0,0] y = [0,1,0] z = [0,0,1] # don't alter the orginal Atoms object rotated = atoms.copy() # get information of unit cell a, b, c, alpha, beta, gamma = rotated.cell.cellpar() # get the crystal axes along the basis of Cartesian system new_x, new_y, new_z = _parse_crystal_axes(zone_axis, new_x, new_y, a, b, c, alpha, beta, gamma) print('Crystal axis along x-axis: ', new_x) print('Crystal axis along y-axis: ', new_y) print('Crystal axis along z-axis: ', new_z) new_x_car = toCartesian(new_x, a, b, c, alpha, beta, gamma) new_y_car = toCartesian(new_y, a, b, c, alpha, beta, gamma) new_z_car = toCartesian(new_z, a, b, c, alpha, beta, gamma) # rotate such that the new crystal axes along x and y are aligned with the coordinate system if new_x_car is not None and new_y_car is not None: build.rotate(rotated, new_x_car, x, new_y_car, y, rotate_cell=True) else: rotated.rotate(new_z_car, z, rotate_cell=True) return rotated
def _build_basis(self, bulk): """Get the basis unit cell from bulk unit cell. This basis is effectively the same as the bulk, but rotated such that the z-axis is aligned with the surface termination. The basis is stored separately from the slab generated and is only intended for internal use. Returns ------- basis : atoms object The basis slab corresponding to the provided bulk. """ del bulk.constraints if len(np.nonzero(self.miller_index)[0]) == 1: mi = max(np.abs(self.miller_index)) basis = circulant(self.miller_index[::-1] / mi).astype(int) else: h, k, l = self.miller_index p, q = ext_gcd(k, l) a1, a2, a3 = bulk.cell k1 = np.dot(p * (k * a1 - h * a2) + q * (l * a1 - h * a3), l * a2 - k * a3) k2 = np.dot(l * (k * a1 - h * a2) - k * (l * a1 - h * a3), l * a2 - k * a3) if abs(k2) > self.tol: i = -int(np.round(k1 / k2)) p, q = p + i * l, q - i * k a, b = ext_gcd(p * k + q * l, h) c1 = (p * k + q * l, -p * h, -q * h) c2 = np.array((0, l, -k)) // abs(gcd(l, k)) c3 = (b, a * p, a * q) basis = np.array([c1, c2, c3]) basis_atoms = Gratoms(positions=bulk.positions, numbers=bulk.get_atomic_numbers(), cell=bulk.cell, pbc=True) scaled = solve(basis.T, basis_atoms.get_scaled_positions().T).T scaled -= np.floor(scaled + self.tol) basis_atoms.set_scaled_positions(scaled) basis_atoms.set_cell(np.dot(basis, basis_atoms.cell), scale_atoms=True) a1, a2, a3 = basis_atoms.cell n1 = np.cross(a1, a2) a3 = n1 / norm(n1) rotate(basis_atoms, a3, (0, 0, 1), a1, (1, 0, 0)) return basis_atoms
def align_crystal(self, bulk, miller_index): """Return an aligned unit cell from bulk unit cell. This alignment rotates the a and b basis vectors to be parallel to the Miller index. Parameters ---------- bulk : Atoms object Bulk system to be standardized. miller_index : list (3,) Miller indices to align with the basis vectors. Returns ------- new_bulk : Gratoms object Standardized bulk unit cell. """ del bulk.constraints if len(np.nonzero(miller_index)[0]) == 1: mi = max(np.abs(miller_index)) new_lattice = scipy.linalg.circulant(miller_index[::-1] / mi).astype(int) else: h, k, l = miller_index p, q = utils.ext_gcd(k, l) a1, a2, a3 = bulk.cell k1 = np.dot(p * (k * a1 - h * a2) + q * (l * a1 - h * a3), l * a2 - k * a3) k2 = np.dot(l * (k * a1 - h * a2) - k * (l * a1 - h * a3), l * a2 - k * a3) if abs(k2) > self.tol: i = -int(np.round(k1 / k2)) p, q = p + i * l, q - i * k a, b = utils.ext_gcd(p * k + q * l, h) c1 = (p * k + q * l, -p * h, -q * h) c2 = np.array((0, l, -k)) // abs(gcd(l, k)) c3 = (b, a * p, a * q) new_lattice = np.array([c1, c2, c3]) scaled = np.linalg.solve(new_lattice.T, bulk.get_scaled_positions().T).T scaled -= np.floor(scaled + self.tol) new_bulk = Gratoms(positions=bulk.positions, numbers=bulk.get_atomic_numbers(), pbc=True) if not self.attach_graph: del new_bulk._graph new_bulk.set_scaled_positions(scaled) new_bulk.set_cell(np.dot(new_lattice, bulk.cell), scale_atoms=True) # Align the longest of the ab basis vectors with x d = np.linalg.norm(new_bulk.cell[:2], axis=1) if d[1] > d[0]: new_bulk.cell[[0, 1]] = new_bulk.cell[[1, 0]] a = new_bulk.cell[0] a3 = np.cross(a, new_bulk.cell[1]) / np.max(d) rotate(new_bulk, a3, (0, 0, 1), a, (1, 0, 0)) # Ensure the remaining basis vectors are positive in their # corresponding axis for i in range(1, 3): if new_bulk.cell[i][i] < 0: new_bulk.cell[i] *= -1 new_bulk.wrap(eps=1e-3) return new_bulk