Coverage for /builds/ase/ase/ase/cluster/base.py: 54.02%
87 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import numpy as np
6class ClusterBase:
7 def get_layer_distance(self, miller, layers=1, tol=1e-9, new=True):
8 """Returns the distance between planes defined by the given miller
9 index.
10 """
11 if new:
12 # Create lattice sample
13 size = np.zeros(3, int)
14 for i, m in enumerate(miller):
15 size[i] = np.abs(m) + 2
17 m = len(self.atomic_basis)
18 p = np.zeros((size.prod() * m, 3))
19 for h in range(size[0]):
20 for k in range(size[1]):
21 for l_ in range(size[2]):
22 i = h * (size[1] * size[2]) + k * size[2] + l_
23 p[m * i:m * (i + 1)] = np.dot([h, k, l_] +
24 self.atomic_basis,
25 self.lattice_basis)
27 # Project lattice positions on the miller direction.
28 n = self.miller_to_direction(miller)
29 d = np.sum(n * p, axis=1)
30 if np.all(d < tol):
31 # All negative incl. zero
32 d = np.sort(np.abs(d))
33 reverse = True
34 else:
35 # Some or all positive
36 d = np.sort(d[d > -tol])
37 reverse = False
38 d = d[np.concatenate((d[1:] - d[:-1] > tol, [True]))]
39 d = d[1:] - d[:-1]
41 # Look for a pattern in the distances between layers. A pattern is
42 # accepted if more than 50 % of the distances obeys it.
43 pattern = None
44 for i in range(len(d)):
45 for n in range(1, (len(d) - i) // 2 + 1):
46 if np.all(np.abs(d[i:i + n] - d[i + n:i + 2 * n]) < tol):
47 counts = 2
48 for j in range(i + 2 * n, len(d), n):
49 if np.all(np.abs(d[j:j + n] - d[i:i + n]) < tol):
50 counts += 1
51 if counts * n * 1.0 / len(d) > 0.5:
52 pattern = d[i:i + n].copy()
53 break
54 if pattern is not None:
55 break
57 if pattern is None:
58 raise RuntimeError('Could not find layer distance for the ' +
59 '(%i,%i,%i) surface.' % miller)
60 if reverse:
61 pattern = pattern[::-1]
63 if layers < 0:
64 pattern = -1 * pattern[::-1]
65 layers *= -1
67 map = np.arange(layers - layers % 1 + 1, dtype=int) % len(pattern)
68 return pattern[map][:-1].sum() + layers % 1 * pattern[map][-1]
70 n = self.miller_to_direction(miller)
71 d1 = d2 = 0.0
73 d = np.abs(np.sum(n * self.lattice_basis, axis=1))
74 mask = np.greater(d, 1e-10)
75 if mask.sum() > 0:
76 d1 = np.min(d[mask])
78 if len(self.atomic_basis) > 1:
79 atomic_basis = np.dot(self.atomic_basis, self.lattice_basis)
80 d = np.sum(n * atomic_basis, axis=1)
81 s = np.sign(d)
82 d = np.abs(d)
83 mask = np.greater(d, 1e-10)
84 if mask.sum() > 0:
85 d2 = np.min(d[mask])
86 s2 = s[mask][np.argmin(d[mask])]
88 if d2 > 1e-10:
89 if s2 < 0 and d1 - d2 > 1e-10:
90 d2 = d1 - d2
91 elif s2 < 0 and d2 - d1 > 1e-10:
92 d2 = 2 * d1 - d2
93 elif s2 > 0 and d2 - d1 > 1e-10:
94 d2 = d2 - d1
96 if np.abs(d1 - d2) < 1e-10:
97 ld = np.array([d1])
98 elif np.abs(d1 - 2 * d2) < 1e-10:
99 ld = np.array([d2])
100 else:
101 assert d1 > d2, 'Something is wrong with the layer distance.'
102 ld = np.array([d2, d1 - d2])
103 else:
104 ld = np.array([d1])
106 if len(ld) > 1:
107 if layers < 0:
108 ld = np.array([-ld[1], -ld[0]])
109 layers *= -1
111 map = np.arange(layers - (layers % 1), dtype=int) % len(ld)
112 r = ld[map].sum() + (layers % 1) * ld[np.abs(map[-1] - 1)]
113 else:
114 r = ld[0] * layers
116 return r
118 def miller_to_direction(self, miller, norm=True):
119 """Returns the direction corresponding to a given Miller index."""
120 d = np.dot(miller, self.resiproc_basis)
121 if norm:
122 d = d / np.linalg.norm(d)
123 return d