Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Helper functions for creating supercells."""
3import numpy as np
5from ase import Atoms
8class SupercellError(Exception):
9 """Use if construction of supercell fails"""
12def get_deviation_from_optimal_cell_shape(cell, target_shape="sc", norm=None):
13 r"""
14 Calculates the deviation of the given cell metric from the ideal
15 cell metric defining a certain shape. Specifically, the function
16 evaluates the expression `\Delta = || Q \mathbf{h} -
17 \mathbf{h}_{target}||_2`, where `\mathbf{h}` is the input
18 metric (*cell*) and `Q` is a normalization factor (*norm*)
19 while the target metric `\mathbf{h}_{target}` (via
20 *target_shape*) represent simple cubic ('sc') or face-centered
21 cubic ('fcc') cell shapes.
23 Parameters:
25 cell: 2D array of floats
26 Metric given as a (3x3 matrix) of the input structure.
27 target_shape: str
28 Desired supercell shape. Can be 'sc' for simple cubic or
29 'fcc' for face-centered cubic.
30 norm: float
31 Specify the normalization factor. This is useful to avoid
32 recomputing the normalization factor when computing the
33 deviation for a series of P matrices.
35 """
37 if target_shape in ["sc", "simple-cubic"]:
38 target_metric = np.eye(3)
39 elif target_shape in ["fcc", "face-centered cubic"]:
40 target_metric = 0.5 * np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])
41 if not norm:
42 norm = (np.linalg.det(cell) / np.linalg.det(target_metric)) ** (
43 -1.0 / 3
44 )
45 return np.linalg.norm(norm * cell - target_metric)
48def find_optimal_cell_shape(
49 cell,
50 target_size,
51 target_shape,
52 lower_limit=-2,
53 upper_limit=2,
54 verbose=False,
55):
56 """Returns the transformation matrix that produces a supercell
57 corresponding to *target_size* unit cells with metric *cell* that
58 most closely approximates the shape defined by *target_shape*.
60 Parameters:
62 cell: 2D array of floats
63 Metric given as a (3x3 matrix) of the input structure.
64 target_size: integer
65 Size of desired super cell in number of unit cells.
66 target_shape: str
67 Desired supercell shape. Can be 'sc' for simple cubic or
68 'fcc' for face-centered cubic.
69 lower_limit: int
70 Lower limit of search range.
71 upper_limit: int
72 Upper limit of search range.
73 verbose: bool
74 Set to True to obtain additional information regarding
75 construction of transformation matrix.
77 """
79 # Set up target metric
80 if target_shape in ["sc", "simple-cubic"]:
81 target_metric = np.eye(3)
82 elif target_shape in ["fcc", "face-centered cubic"]:
83 target_metric = 0.5 * np.array(
84 [[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=float
85 )
86 if verbose:
87 print("target metric (h_target):")
88 print(target_metric)
90 # Normalize cell metric to reduce computation time during looping
91 norm = (
92 target_size * np.linalg.det(cell) / np.linalg.det(target_metric)
93 ) ** (-1.0 / 3)
94 norm_cell = norm * cell
95 if verbose:
96 print("normalization factor (Q): %g" % norm)
98 # Approximate initial P matrix
99 ideal_P = np.dot(target_metric, np.linalg.inv(norm_cell))
100 if verbose:
101 print("idealized transformation matrix:")
102 print(ideal_P)
103 starting_P = np.array(np.around(ideal_P, 0), dtype=int)
104 if verbose:
105 print("closest integer transformation matrix (P_0):")
106 print(starting_P)
108 # Prepare run.
109 from itertools import product
111 best_score = 1e6
112 optimal_P = None
113 for dP in product(range(lower_limit, upper_limit + 1), repeat=9):
114 dP = np.array(dP, dtype=int).reshape(3, 3)
115 P = starting_P + dP
116 if int(np.around(np.linalg.det(P), 0)) != target_size:
117 continue
118 score = get_deviation_from_optimal_cell_shape(
119 np.dot(P, norm_cell), target_shape=target_shape, norm=1.0
120 )
121 if score < best_score:
122 best_score = score
123 optimal_P = P
125 if optimal_P is None:
126 print("Failed to find a transformation matrix.")
127 return None
129 # Finalize.
130 if verbose:
131 print("smallest score (|Q P h_p - h_target|_2): %f" % best_score)
132 print("optimal transformation matrix (P_opt):")
133 print(optimal_P)
134 print("supercell metric:")
135 print(np.round(np.dot(optimal_P, cell), 4))
136 print(
137 "determinant of optimal transformation matrix: %g"
138 % np.linalg.det(optimal_P)
139 )
140 return optimal_P
143def make_supercell(prim, P, wrap=True, tol=1e-5):
144 r"""Generate a supercell by applying a general transformation (*P*) to
145 the input configuration (*prim*).
147 The transformation is described by a 3x3 integer matrix
148 `\mathbf{P}`. Specifically, the new cell metric
149 `\mathbf{h}` is given in terms of the metric of the input
150 configuration `\mathbf{h}_p` by `\mathbf{P h}_p =
151 \mathbf{h}`.
153 Parameters:
155 prim: ASE Atoms object
156 Input configuration.
157 P: 3x3 integer matrix
158 Transformation matrix `\mathbf{P}`.
159 wrap: bool
160 wrap in the end
161 tol: float
162 tolerance for wrapping
163 """
165 supercell_matrix = P
166 supercell = clean_matrix(supercell_matrix @ prim.cell)
168 # cartesian lattice points
169 lattice_points_frac = lattice_points_in_supercell(supercell_matrix)
170 lattice_points = np.dot(lattice_points_frac, supercell)
172 superatoms = Atoms(cell=supercell, pbc=prim.pbc)
174 for lp in lattice_points:
175 shifted_atoms = prim.copy()
176 shifted_atoms.positions += lp
177 superatoms.extend(shifted_atoms)
179 # check number of atoms is correct
180 n_target = int(np.round(np.linalg.det(supercell_matrix) * len(prim)))
181 if n_target != len(superatoms):
182 msg = "Number of atoms in supercell: {}, expected: {}".format(
183 n_target, len(superatoms)
184 )
185 raise SupercellError(msg)
187 if wrap:
188 superatoms.wrap(eps=tol)
190 return superatoms
193def lattice_points_in_supercell(supercell_matrix):
194 """Find all lattice points contained in a supercell.
196 Adapted from pymatgen, which is available under MIT license:
197 The MIT License (MIT) Copyright (c) 2011-2012 MIT & The Regents of the
198 University of California, through Lawrence Berkeley National Laboratory
199 """
201 diagonals = np.array(
202 [
203 [0, 0, 0],
204 [0, 0, 1],
205 [0, 1, 0],
206 [0, 1, 1],
207 [1, 0, 0],
208 [1, 0, 1],
209 [1, 1, 0],
210 [1, 1, 1],
211 ]
212 )
213 d_points = np.dot(diagonals, supercell_matrix)
215 mins = np.min(d_points, axis=0)
216 maxes = np.max(d_points, axis=0) + 1
218 ar = np.arange(mins[0], maxes[0])[:, None] * np.array([1, 0, 0])[None, :]
219 br = np.arange(mins[1], maxes[1])[:, None] * np.array([0, 1, 0])[None, :]
220 cr = np.arange(mins[2], maxes[2])[:, None] * np.array([0, 0, 1])[None, :]
222 all_points = ar[:, None, None] + br[None, :, None] + cr[None, None, :]
223 all_points = all_points.reshape((-1, 3))
225 frac_points = np.dot(all_points, np.linalg.inv(supercell_matrix))
227 tvects = frac_points[
228 np.all(frac_points < 1 - 1e-10, axis=1)
229 & np.all(frac_points >= -1e-10, axis=1)
230 ]
231 assert len(tvects) == round(abs(np.linalg.det(supercell_matrix)))
232 return tvects
235def clean_matrix(matrix, eps=1e-12):
236 """ clean from small values"""
237 matrix = np.array(matrix)
238 for ij in np.ndindex(matrix.shape):
239 if abs(matrix[ij]) < eps:
240 matrix[ij] = 0
241 return matrix