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