Passed
Push — master ( c026c2...632976 )
by Daniel
06:58
created

amd.utils.lattice_cubic()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
"""General utility functions."""
2
3
from typing import Tuple
4
5
import numpy as np
6
import numba
7
from scipy.spatial.distance import squareform
8
9
10
def diameter(cell):
11
    """Diameter of a unit cell (as a square matrix in Cartesian/Orthogonal form)
12
    in 3 or fewer dimensions."""
13
14
    dims = cell.shape[0]
15
    if dims == 1:
16
        return cell[0][0]
17
    if dims == 2:
18
        d = np.amax(np.linalg.norm(np.array([cell[0] + cell[1], cell[0] - cell[1]]), axis=-1))
19
    elif dims == 3:
20
        diams = np.array([
21
              cell[0] + cell[1] + cell[2],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 2 spaces).
Loading history...
22
              cell[0] + cell[1] - cell[2],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 2 spaces).
Loading history...
23
              cell[0] - cell[1] + cell[2],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 2 spaces).
Loading history...
24
            - cell[0] + cell[1] + cell[2]
25
        ])
26
        d =  np.amax(np.linalg.norm(diams, axis=-1))
0 ignored issues
show
Coding Style introduced by
Exactly one space required after assignment
Loading history...
27
    else:
28
        raise ValueError(f'diameter only implimented for dimensions <= 3.')
0 ignored issues
show
introduced by
Using an f-string that does not have any interpolated variables
Loading history...
29
    return d
30
31
32
@numba.njit()
33
def cellpar_to_cell(a, b, c, alpha, beta, gamma):
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
34
    """Simplified version of function from :mod:`ase.geometry` of the same name.
35
    3D unit cell parameters a,b,c,α,β,γ --> cell as 3x3 NumPy array.
36
    """
37
38
    eps = 2 * np.spacing(90.0)  # ~1.4e-14
39
40
    cos_alpha = 0. if abs(abs(alpha) - 90.) < eps else np.cos(alpha * np.pi / 180.)
41
    cos_beta = 0. if abs(abs(beta) - 90.) < eps else np.cos(beta * np.pi / 180.)
42
    cos_gamma = 0. if abs(abs(gamma) - 90.) < eps else np.cos(gamma * np.pi / 180.)
43
44
    if abs(gamma - 90) < eps:
45
        sin_gamma = 1.
46
    elif abs(gamma + 90) < eps:
47
        sin_gamma = -1.
48
    else:
49
        sin_gamma = np.sin(gamma * np.pi / 180.)
50
51
    cy = (cos_alpha - cos_beta * cos_gamma) / sin_gamma
52
    cz_sqr = 1. - cos_beta ** 2 - cy ** 2
53
    if cz_sqr < 0:
54
        raise RuntimeError('Could not create unit cell from given parameters.')
55
56
    cell = np.zeros((3, 3))
57
    cell[0, 0] = a
58
    cell[1, 0] = b * cos_gamma
59
    cell[1, 1] = b * sin_gamma
60
    cell[2, 0] = c * cos_beta
61
    cell[2, 1] = c * cy
62
    cell[2, 2] = c * np.sqrt(cz_sqr)
63
64
    return cell
65
66
67
@numba.njit()
68
def cellpar_to_cell_2D(a, b, alpha):
69
    """2D unit cell parameters a,b,α --> cell as 2x2 ndarray."""
70
71
    cell = np.zeros((2, 2))
72
    cell[0, 0] = a
73
    cell[1, 0] = b * np.cos(alpha * np.pi / 180.)
74
    cell[1, 1] = b * np.sin(alpha * np.pi / 180.)
75
76
    return cell
77
78
79
def cell_to_cellpar(cell):
80
    """Unit cell as a 3x3 NumPy array -> list of 6 lengths + angles."""
81
    lengths = np.linalg.norm(cell, axis=-1)
82
    angles = []
83
    for i, j in [(1, 2), (0, 2), (0, 1)]:
84
        ang_rad = np.arccos(np.dot(cell[i], cell[j]) / (lengths[i] * lengths[j]))
85
        angles.append(np.rad2deg(ang_rad))
86
    return np.concatenate((lengths, np.array(angles)))
87
88
89
def cell_to_cellpar_2D(cell):
90
    """Unit cell as a 2x2 NumPy array -> list of 2 lengths and an angle."""
91
    cellpar = np.zeros((3, ))
92
    lengths = np.linalg.norm(cell, axis=-1)
93
    ang_rad = np.arccos(np.dot(cell[0], cell[1]) / (lengths[0] * lengths[1]))
94
    cellpar[0] = lengths[0]
95
    cellpar[1] = lengths[1]
96
    cellpar[2] = np.rad2deg(ang_rad)
97
    return cellpar
98
99
100
def neighbours_from_distance_matrix(
101
        n: int,
102
        dm: np.ndarray
103
) -> Tuple[np.ndarray, np.ndarray]:
104
    """Given a distance matrix, find the n nearest neighbours of each item.
105
106
    Parameters
107
    ----------
108
    n : int
109
        Number of nearest neighbours to find for each item.
110
    dm : numpy.ndarray
111
        2D distance matrix or 1D condensed distance matrix.
112
113
    Returns
114
    -------
115
    nn_dm, inds : Tuple[numpy.ndarray, numpy.ndarray] 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
116
        ``nn_dm[i][j]`` is the distance from item ``i`` to its ``j+1`` st
117
        nearest neighbour, and ``inds[i][j]`` is the index of this neighbour 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
118
        (``j+1`` since index 0 is the first nearest neighbour).
119
    """
120
121
    inds = None
122
123
    # 2D distance matrix
124
    if len(dm.shape) == 2:
125
        inds = np.array([np.argpartition(row, n)[:n] for row in dm])
126
127
    # 1D condensed distance vector
128
    elif len(dm.shape) == 1:
129
        dm = squareform(dm)
130
        inds = []
131
        for i, row in enumerate(dm):
132
            inds_row = np.argpartition(row, n+1)[:n+1]
133
            inds_row = inds_row[inds_row != i][:n]
134
            inds.append(inds_row)
135
        inds = np.array(inds)
136
137
    else:
138
        ValueError(
139
            'Input must be an ndarray, either a 2D distance matrix '
140
            'or a condensed distance matrix (returned by pdist).')
141
142
    # inds are the indexes of nns: inds[i,j] is the j-th nn to point i
143
    nn_dm = np.take_along_axis(dm, inds, axis=-1)
144
    sorted_inds = np.argsort(nn_dm, axis=-1)
145
    inds = np.take_along_axis(inds, sorted_inds, axis=-1)
146
    nn_dm = np.take_along_axis(nn_dm, sorted_inds, axis=-1)
147
    return nn_dm, inds
148
149
150
def random_cell(length_bounds=(1, 2), angle_bounds=(60, 120), dims=3):
151
    """Dimensions 2 and 3 only. Random unit cell with uniformally chosen length and 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
152
    angle parameters between bounds."""
153
154
    if dims == 3:
155
        while True:
156
            lengths = [np.random.uniform(low=length_bounds[0],
157
                                         high=length_bounds[1])
158
                       for _ in range(dims)]
159
            angles = [np.random.uniform(low=angle_bounds[0],
160
                                        high=length_bounds[1])
161
                      for _ in range(dims)]
162
163
            try:
164
                cell = cellpar_to_cell(*lengths, *angles)
165
                break
166
            except RuntimeError:
167
                continue
168
169
    elif dims == 2:
170
        lengths = [np.random.uniform(low=length_bounds[0],
171
                                     high=length_bounds[1])
172
                       for _ in range(dims)]
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (remove 4 spaces).
Loading history...
173
        alpha = np.random.uniform(low=angle_bounds[0],
174
                                  high=length_bounds[1])
175
        cell = cellpar_to_cell_2D(*lengths, alpha)
176
177
    else:
178
        raise ValueError(f'random_cell only implimented for dimensions 2 and 3 (passed {dims})')
179
180
    return cell
181