Coverage for /builds/alexhroom/ase/ase/build/general_surface.py: 83.61%
61 statements
« prev ^ index » next coverage.py v7.5.3, created at 2024-08-05 14:37 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2024-08-05 14:37 +0000
1from math import gcd
2import numpy as np
3from numpy.linalg import norm, solve
5from ase.build import bulk
6from ase.build.surface import create_tags
9def surface(lattice, indices, layers, vacuum=None, tol=1e-10, periodic=False):
10 """Create surface from a given lattice and Miller indices.
12 lattice: Atoms object or str
13 Bulk lattice structure of alloy or pure metal. Note that the
14 unit-cell must be the conventional cell - not the primitive cell.
15 One can also give the chemical symbol as a string, in which case the
16 correct bulk lattice will be generated automatically.
17 indices: sequence of three int
18 Surface normal in Miller indices (h,k,l).
19 layers: int
20 Number of equivalent layers of the slab.
21 vacuum: float
22 Amount of vacuum added on both sides of the slab.
23 periodic: bool
24 Whether the surface is periodic in the normal to the surface
25 """
27 indices = np.asarray(indices)
29 if indices.shape != (3,) or not indices.any() or indices.dtype != int:
30 raise ValueError(f'{indices} is an invalid surface type')
32 if isinstance(lattice, str):
33 lattice = bulk(lattice, cubic=True)
35 h, k, l = indices # noqa (E741, the variable l)
36 h0, k0, l0 = (indices == 0)
38 if h0 and k0 or h0 and l0 or k0 and l0: # if two indices are zero
39 if not h0:
40 c1, c2, c3 = [(0, 1, 0), (0, 0, 1), (1, 0, 0)]
41 if not k0:
42 c1, c2, c3 = [(0, 0, 1), (1, 0, 0), (0, 1, 0)]
43 if not l0:
44 c1, c2, c3 = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
45 else:
46 p, q = ext_gcd(k, l)
47 a1, a2, a3 = lattice.cell
49 # constants describing the dot product of basis c1 and c2:
50 # dot(c1,c2) = k1+i*k2, i in Z
51 k1 = np.dot(p * (k * a1 - h * a2) + q * (l * a1 - h * a3),
52 l * a2 - k * a3)
53 k2 = np.dot(l * (k * a1 - h * a2) - k * (l * a1 - h * a3),
54 l * a2 - k * a3)
56 if abs(k2) > tol:
57 i = -int(round(k1 / k2)) # i corresponding to the optimal basis
58 p, q = p + i * l, q - i * k
60 a, b = ext_gcd(p * k + q * l, h)
62 c1 = (p * k + q * l, -p * h, -q * h)
63 c2 = np.array((0, l, -k)) // abs(gcd(l, k))
64 c3 = (b, a * p, a * q)
66 surf = build(lattice, np.array([c1, c2, c3]), layers, tol, periodic)
67 if vacuum is not None:
68 surf.center(vacuum=vacuum, axis=2)
69 return surf
72def build(lattice, basis, layers, tol, periodic):
73 surf = lattice.copy()
74 scaled = solve(basis.T, surf.get_scaled_positions().T).T
75 scaled -= np.floor(scaled + tol)
76 surf.set_scaled_positions(scaled)
77 surf.set_cell(np.dot(basis, surf.cell), scale_atoms=True)
78 surf *= (1, 1, layers)
79 surf.set_tags(create_tags((1, len(lattice), layers)))
81 a1, a2, a3 = surf.cell
82 surf.set_cell([a1, a2,
83 np.cross(a1, a2) * np.dot(a3, np.cross(a1, a2)) /
84 norm(np.cross(a1, a2))**2])
86 # Change unit cell to have the x-axis parallel with a surface vector
87 # and z perpendicular to the surface:
88 a1, a2, a3 = surf.cell
89 surf.set_cell([(norm(a1), 0, 0),
90 (np.dot(a1, a2) / norm(a1),
91 np.sqrt(norm(a2)**2 - (np.dot(a1, a2) / norm(a1))**2), 0),
92 (0, 0, norm(a3))],
93 scale_atoms=True)
95 surf.pbc = (True, True, periodic)
97 # Move atoms into the unit cell:
98 scaled = surf.get_scaled_positions()
99 scaled[:, :2] %= 1
100 surf.set_scaled_positions(scaled)
102 if not periodic:
103 surf.cell[2] = 0.0
105 return surf
108def ext_gcd(a, b):
109 if b == 0:
110 return 1, 0
111 elif a % b == 0:
112 return 0, 1
113 else:
114 x, y = ext_gcd(b, a % b)
115 return y, x - y * (a // b)