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