1
|
|
|
"""Implements the :class:`PeriodicSet` class representing a periodic |
2
|
|
|
set defined by a motif (collection of points) and unit cell (basis) |
3
|
|
|
modelling a crystal with a point at the center of each atom. |
4
|
|
|
|
5
|
|
|
:class:`PeriodicSet` s are yielded by the children of |
6
|
|
|
:class:`amd.io._Reader <.io._Reader>` and are passed to |
7
|
|
|
:func:`PDD() <.calculate.PDD>`, :func:`AMD() <.calculate.AMD>` etc to |
8
|
|
|
calculate the invariants of crystals. |
9
|
|
|
""" |
10
|
|
|
|
11
|
|
|
from __future__ import annotations |
12
|
|
|
from typing import Optional, List, NamedTuple |
13
|
|
|
import math |
14
|
|
|
|
15
|
|
|
import numpy as np |
16
|
|
|
from scipy.special import factorial |
17
|
|
|
|
18
|
|
|
from .utils import ( |
19
|
|
|
cellpar_to_cell, |
20
|
|
|
cellpar_to_cell_2D, |
21
|
|
|
random_cell, |
22
|
|
|
cell_to_cellpar, |
23
|
|
|
) |
24
|
|
|
from ._types import FloatArray, UIntArray, AtomTypeArray |
25
|
|
|
from .globals_ import ATOMIC_MASSES, ATOMIC_NUMBERS_TO_SYMS |
26
|
|
|
|
27
|
|
|
|
28
|
|
|
__all__ = ["PeriodicSet", "DisorderGroup", "DisorderAssembly"] |
29
|
|
|
|
30
|
|
|
|
31
|
|
|
class DisorderGroup(NamedTuple): |
32
|
|
|
indices: list[int] |
33
|
|
|
occupancy: float |
34
|
|
|
|
35
|
|
|
|
36
|
|
|
class DisorderAssembly(NamedTuple): |
37
|
|
|
groups: list[DisorderGroup] |
38
|
|
|
is_substitutional: bool |
39
|
|
|
|
40
|
|
|
|
41
|
|
|
class PeriodicSet: |
42
|
|
|
"""A periodic set is a collection of points (motif) which |
43
|
|
|
periodically repeats according to a lattice (unit cell), which can |
44
|
|
|
model a crystal with a point in the centre of each atom. |
45
|
|
|
|
46
|
|
|
:class:`PeriodicSet` s are yielded by the children of |
47
|
|
|
:class:`amd.io._Reader <.io._Reader>` and are passed to |
48
|
|
|
:func:`PDD() <.calculate.PDD>`, :func:`AMD() <.calculate.AMD>` etc to |
49
|
|
|
calculate the invariants of crystals. |
50
|
|
|
|
51
|
|
|
Parameters |
52
|
|
|
---------- |
53
|
|
|
motif : :class:`numpy.ndarray` |
54
|
|
|
Collection of motif points in Cartesian (orthogonal) |
55
|
|
|
coordinates, shape ``(m, dims)``. |
56
|
|
|
cell : :class:`numpy.ndarray` |
57
|
|
|
Square array representing the unit cell in Cartesian |
58
|
|
|
(orthogonal) coordinates, shape ``(dims, dims)``. Use |
59
|
|
|
:func:`amd.cellpar_to_cell <.utils.cellpar_to_cell>` to convert |
60
|
|
|
a vector of 6 conventional cell parameters a,b,c,α,β,γ to a |
61
|
|
|
square matrix. |
62
|
|
|
name : str, optional |
63
|
|
|
Name of the periodic set. |
64
|
|
|
asym_unit : :class:`numpy.ndarray`, optional |
65
|
|
|
Indices of motif points constituting an asymmetric unit, so that |
66
|
|
|
``motif[asym_unit]`` is an asymmetric unit. If given, the motif |
67
|
|
|
should be such that ``motif[asym_unit[i]:asym_unit[i+1]]`` are |
68
|
|
|
points corresponding to ``motif[asym_unit[i]``. |
69
|
|
|
multiplicities : :class:`numpy.ndarray`, optional |
70
|
|
|
Wyckoff multiplicities of points in the asymmetric unit (the |
71
|
|
|
number of unique sites generated under symmetries). |
72
|
|
|
types : list, optional |
73
|
|
|
Atomic numbers of atoms in the asymmetric unit. |
74
|
|
|
labels : list, optional |
75
|
|
|
Labels of atoms in the asymmetric unit. |
76
|
|
|
disorder : list, optional |
77
|
|
|
Contains disorder assemblies of the crystal. |
78
|
|
|
""" |
79
|
|
|
|
80
|
|
|
def __init__( |
81
|
|
|
self, |
82
|
|
|
motif: FloatArray, |
83
|
|
|
cell: FloatArray, |
84
|
|
|
name: Optional[str] = None, |
85
|
|
|
asym_unit: Optional[UIntArray] = None, |
86
|
|
|
multiplicities: Optional[UIntArray] = None, |
87
|
|
|
types: Optional[AtomTypeArray] = None, |
88
|
|
|
labels: Optional[List[str]] = None, |
89
|
|
|
disorder: Optional[list[DisorderAssembly]] = None, |
90
|
|
|
): |
91
|
|
|
self.motif = motif |
92
|
|
|
self.cell = cell |
93
|
|
|
self.name = name |
94
|
|
|
self._asym_unit = asym_unit |
95
|
|
|
self._multiplicities = multiplicities |
96
|
|
|
self.types = types |
97
|
|
|
self.labels = labels |
98
|
|
|
self.disorder = disorder |
99
|
|
|
|
100
|
|
|
@property |
101
|
|
|
def ndim(self) -> int: |
102
|
|
|
"""Dimension of the ambient space.""" |
103
|
|
|
return self.cell.shape[0] |
104
|
|
|
|
105
|
|
|
@property |
106
|
|
|
def m(self) -> int: |
107
|
|
|
"""Number of motif points.""" |
108
|
|
|
return self.motif.shape[0] |
109
|
|
|
|
110
|
|
|
@property |
111
|
|
|
def n_asym(self) -> int: |
112
|
|
|
"""Number of points in the asymmetric unit.""" |
113
|
|
|
if self.asym_unit is None: |
114
|
|
|
return self.m |
115
|
|
|
else: |
116
|
|
|
return self.asym_unit.shape[0] |
117
|
|
|
|
118
|
|
|
@property |
119
|
|
|
def asym_unit(self) -> np.ndarray: |
120
|
|
|
if self._asym_unit is None: |
121
|
|
|
return np.arange(self.m) |
122
|
|
|
else: |
123
|
|
|
return self._asym_unit |
124
|
|
|
|
125
|
|
|
@property |
126
|
|
|
def multiplicities(self) -> np.ndarray: |
127
|
|
|
if self._multiplicities is None: |
128
|
|
|
return np.ones((self.m,), dtype=np.uint64) |
129
|
|
|
else: |
130
|
|
|
return self._multiplicities |
131
|
|
|
|
132
|
|
|
@property |
133
|
|
|
def occupancies(self) -> np.ndarray: |
134
|
|
|
occs = np.ones((self.n_asym, ), dtype=np.float64) |
135
|
|
|
if self.disorder: |
136
|
|
|
for asm in self.disorder: |
137
|
|
|
for grp in asm.groups: |
138
|
|
|
occs[grp.indices] = grp.occupancy |
139
|
|
|
return occs |
140
|
|
|
|
141
|
|
|
def __str__(self) -> str: |
142
|
|
|
m, n = self.motif.shape |
143
|
|
|
m_pl = "" if m == 1 else "s" |
144
|
|
|
n_pl = "" if n == 1 else "s" |
145
|
|
|
name_str = f"{self.name}: " if self.name is not None else "" |
146
|
|
|
return f"PeriodicSet({name_str}{m} point{m_pl} in {n} dimension{n_pl})" |
147
|
|
|
|
148
|
|
|
def __repr__(self) -> str: |
149
|
|
|
motif_str = str(self.motif).replace("\n ", "\n" + " " * 11) |
150
|
|
|
cell_str = str(self.cell).replace("\n ", "\n" + " " * 10) |
151
|
|
|
|
152
|
|
|
optional_attrs = [] |
153
|
|
|
for attr in ("_asym_unit", "_multiplicities"): |
154
|
|
|
val = getattr(self, attr) |
155
|
|
|
if val is not None: |
156
|
|
|
st = str(val).replace("\n ", "\n" + " " * (len(attr) + 6)) |
157
|
|
|
optional_attrs.append(f"{attr[1:]}={st}") |
158
|
|
|
|
159
|
|
|
for attr in ("types", "labels"): |
160
|
|
|
val = getattr(self, attr) |
161
|
|
|
if val is not None: |
162
|
|
|
st = str(val).replace("\n ", "\n" + " " * (len(attr) + 6)) |
163
|
|
|
optional_attrs.append(f"{attr}={st}") |
164
|
|
|
|
165
|
|
|
optional_attrs_str = ",\n " if optional_attrs else "" |
166
|
|
|
optional_attrs_str += ",\n ".join(optional_attrs) |
167
|
|
|
|
168
|
|
|
return ( |
169
|
|
|
f"PeriodicSet(name={self.name},\n" |
170
|
|
|
f" motif={motif_str},\n" |
171
|
|
|
f" cell={cell_str}{optional_attrs_str})" |
172
|
|
|
) |
173
|
|
|
|
174
|
|
|
def PPC(self): |
175
|
|
|
r"""Return the point packing coefficient (PPC) of the |
176
|
|
|
PeriodicSet. |
177
|
|
|
|
178
|
|
|
The PPC is a constant of any periodic set determining the |
179
|
|
|
asymptotic behaviour of its isometry invariants. As |
180
|
|
|
:math:`k \rightarrow \infty`, the ratio |
181
|
|
|
:math:`\text{AMD}_k / \sqrt[n]{k}` converges to the PPC, as does |
182
|
|
|
any row of its PDD. |
183
|
|
|
|
184
|
|
|
For a unit cell :math:`U` and :math:`m` motif points in |
185
|
|
|
:math:`n` dimensions, |
186
|
|
|
|
187
|
|
|
.. math:: |
188
|
|
|
|
189
|
|
|
\text{PPC} = \sqrt[n]{\frac{\text{Vol}[U]}{m V_n}} |
190
|
|
|
|
191
|
|
|
where :math:`V_n` is the volume of a unit sphere in :math:`n` |
192
|
|
|
dimensions. |
193
|
|
|
|
194
|
|
|
Returns |
195
|
|
|
------- |
196
|
|
|
ppc : float |
197
|
|
|
The PPC of ``self``. |
198
|
|
|
""" |
199
|
|
|
m, n = self.motif.shape |
200
|
|
|
t = int(n // 2) |
201
|
|
|
if n % 2 == 0: |
202
|
|
|
ball_vol = (np.pi ** t) / factorial(t) |
203
|
|
|
else: |
204
|
|
|
ball_vol = (2 * factorial(t) * (4 * np.pi) ** t) / factorial(n) |
205
|
|
|
return (np.abs(np.linalg.det(self.cell)) / (m * ball_vol)) ** (1.0 / n) |
206
|
|
|
|
207
|
|
|
def has_positional_disorder(self): |
208
|
|
|
if self.disorder is not None: |
209
|
|
|
return any(not asm.is_substitutional for asm in self.disorder) |
210
|
|
|
return False |
211
|
|
|
|
212
|
|
|
def has_substitutional_disorder(self): |
213
|
|
|
if self.disorder is not None: |
214
|
|
|
return any(asm.is_substitutional for asm in self.disorder) |
215
|
|
|
return False |
216
|
|
|
|
217
|
|
|
def density(self): |
218
|
|
|
"""Return the physical density of the PeriodicSet representing a |
219
|
|
|
crystal in g/cm3. |
220
|
|
|
|
221
|
|
|
Returns |
222
|
|
|
------- |
223
|
|
|
density : float |
224
|
|
|
The physical density of ``self`` in g/cm3. |
225
|
|
|
""" |
226
|
|
|
if self.types is None: |
227
|
|
|
raise ValueError(f"PeriodicSet(name={self.name}) has no types") |
228
|
|
|
cell_mass = sum( |
229
|
|
|
ATOMIC_MASSES[num] * tot_occ |
230
|
|
|
for num, tot_occ in zip(self.types, self.multiplicities * self.occupancies) |
231
|
|
|
) |
232
|
|
|
cell_vol = np.linalg.det(self.cell) |
233
|
|
|
return (cell_mass / cell_vol) * 1.66053907 |
234
|
|
|
|
235
|
|
|
def formula(self): |
236
|
|
|
"""Return the reduced formula of the PeriodicSet representing a |
237
|
|
|
crystal. |
238
|
|
|
|
239
|
|
|
Returns |
240
|
|
|
------- |
241
|
|
|
formula : str |
242
|
|
|
The reduced formula of ``self``. |
243
|
|
|
""" |
244
|
|
|
counter = self._formula_dict() |
245
|
|
|
counter = {ATOMIC_NUMBERS_TO_SYMS[n]: count for n, count in counter.items()} |
246
|
|
|
return "".join( |
247
|
|
|
f"{lab}{num:.3g}" if num != 1 else lab for lab, num in counter.items() |
248
|
|
|
) |
249
|
|
|
|
250
|
|
|
def _formula_dict(self): |
251
|
|
|
"""Return a dictionary of atomic types in the form |
252
|
|
|
{atomic_num: count}. |
253
|
|
|
""" |
254
|
|
|
|
255
|
|
|
if self.types is None: |
256
|
|
|
raise ValueError(f"PeriodicSet(name={self.name}) has no types") |
257
|
|
|
|
258
|
|
|
counter = {} |
259
|
|
|
for num, mul, occ in zip(self.types, self.multiplicities, self.occupancies): |
260
|
|
|
if num not in counter: |
261
|
|
|
counter[num] = 0 |
262
|
|
|
counter[num] += occ * mul |
263
|
|
|
|
264
|
|
|
if 0 in counter: |
265
|
|
|
del counter[0] |
266
|
|
|
|
267
|
|
|
gcd = math.gcd(*(int(v) for v in counter.values() if np.mod(v, 1.0) == 0.0)) |
|
|
|
|
268
|
|
|
if gcd == 0: |
269
|
|
|
gcd = 1 |
270
|
|
|
|
271
|
|
|
counter_ = {} |
272
|
|
|
if 6 in counter: |
273
|
|
|
counter_[6] = counter[6] / gcd |
274
|
|
|
del counter[6] |
275
|
|
|
if 1 in counter: |
276
|
|
|
counter_[1] = counter[1] / gcd |
277
|
|
|
del counter[1] |
278
|
|
|
|
279
|
|
|
for n, count in sorted(counter.items(), key=lambda x: x[0]): |
280
|
|
|
counter_[int(n)] = count / gcd |
281
|
|
|
|
282
|
|
|
return counter_ |
283
|
|
|
|
284
|
|
|
def _equal_cell_and_motif(self, other: PeriodicSet, tol: float = 1e-8) -> bool: |
285
|
|
|
"""Used for debugging. True if both 1) unit cells are (close to) |
286
|
|
|
identical, and 2) the motifs are the same shape, and |
287
|
|
|
every point in one motif has a (close to) identical point |
288
|
|
|
somewhere in the other, accounting for pbc. |
289
|
|
|
""" |
290
|
|
|
|
291
|
|
|
if ( |
292
|
|
|
self.cell.shape != other.cell.shape |
293
|
|
|
or self.motif.shape != other.motif.shape |
294
|
|
|
or not np.allclose(self.cell, other.cell) |
295
|
|
|
): |
296
|
|
|
return False |
297
|
|
|
|
298
|
|
|
cell_inv = np.linalg.inv(self.cell) |
299
|
|
|
fm1 = np.mod(self.motif @ cell_inv, 1) |
300
|
|
|
fm2 = np.mod(other.motif @ cell_inv, 1) |
301
|
|
|
d1 = np.abs(fm2[:, None] - fm1) |
302
|
|
|
d2 = np.abs(d1 - 1) |
303
|
|
|
diffs = np.amax(np.minimum(d1, d2), axis=-1) |
304
|
|
|
|
305
|
|
|
if not np.all( |
306
|
|
|
(np.amin(diffs, axis=0) <= tol) | (np.amin(diffs, axis=-1) <= tol) |
307
|
|
|
): |
308
|
|
|
return False |
309
|
|
|
return True |
310
|
|
|
|
311
|
|
|
@staticmethod |
312
|
|
|
def cubic(scale: float = 1.0, dims: int = 3) -> PeriodicSet: |
313
|
|
|
"""Return a :class:`PeriodicSet` representing a cubic lattice.""" |
314
|
|
|
return PeriodicSet(np.zeros((1, dims)), np.identity(dims) * scale) |
315
|
|
|
|
316
|
|
|
@staticmethod |
317
|
|
|
def hexagonal(scale: float = 1.0, dims: int = 3) -> PeriodicSet: |
318
|
|
|
"""Return a :class:`PeriodicSet` representing a hexagonal |
319
|
|
|
lattice. Implemented for dimensions 2 and 3 only. |
320
|
|
|
""" |
321
|
|
|
|
322
|
|
|
if dims == 3: |
323
|
|
|
cellpar = np.array([scale, scale, scale, 90.0, 90.0, 120.0]) |
324
|
|
|
cell = cellpar_to_cell(cellpar) |
325
|
|
|
elif dims == 2: |
326
|
|
|
cell = cellpar_to_cell_2D(np.array([scale, scale, 60.0])) |
327
|
|
|
else: |
328
|
|
|
raise NotImplementedError( |
329
|
|
|
"amd.PeriodicSet.hexagonal() only implemented for dimensions " |
330
|
|
|
f"2 and 3, passed {dims}" |
331
|
|
|
) |
332
|
|
|
return PeriodicSet(np.zeros((1, dims)), cell) |
333
|
|
|
|
334
|
|
|
@staticmethod |
335
|
|
|
def random( |
336
|
|
|
n_points: int, |
337
|
|
|
length_bounds: tuple = (1.0, 2.0), |
338
|
|
|
angle_bounds: tuple = (60.0, 120.0), |
339
|
|
|
dims: int = 3, |
340
|
|
|
) -> PeriodicSet: |
341
|
|
|
"""Return a :class:`PeriodicSet` with a chosen number of |
342
|
|
|
randomly placed points in a random cell with edges between |
343
|
|
|
``length_bounds`` and angles between ``angle_bounds``. |
344
|
|
|
Implemented for dimensions 2 and 3 only. |
345
|
|
|
""" |
346
|
|
|
|
347
|
|
|
if dims not in (2, 3): |
348
|
|
|
raise NotImplementedError( |
349
|
|
|
"amd.PeriodicSet._random only implemented for dimensions 2 " |
350
|
|
|
f"and 3, passed {dims}" |
351
|
|
|
) |
352
|
|
|
|
353
|
|
|
cell = random_cell( |
354
|
|
|
length_bounds=length_bounds, angle_bounds=angle_bounds, dims=dims |
355
|
|
|
) |
356
|
|
|
frac_motif = np.random.uniform(size=(n_points, dims)) |
357
|
|
|
return PeriodicSet(frac_motif @ cell, cell) |
358
|
|
|
|
359
|
|
|
def to_cif_str(self) -> str: |
360
|
|
|
"""Return a CIF string representing the PeriodicSet. Does not |
361
|
|
|
write disorder data, atom labels or symmetries.""" |
362
|
|
|
|
363
|
|
|
a, b, c, al, be, ga = cell_to_cellpar(self.cell) |
364
|
|
|
out = [ |
365
|
|
|
f"data_{self.name}", |
366
|
|
|
"_symmetry_space_group_name_H-M 'P 1'", |
367
|
|
|
f"_cell_length_a {a}", |
368
|
|
|
f"_cell_length_b {b}", |
369
|
|
|
f"_cell_length_c {c}", |
370
|
|
|
f"_cell_angle_alpha {al}", |
371
|
|
|
f"_cell_angle_beta {be}", |
372
|
|
|
f"_cell_angle_gamma {ga}", |
373
|
|
|
"loop_", |
374
|
|
|
" _symmetry_equiv_pos_site_id", |
375
|
|
|
" _symmetry_equiv_pos_as_xyz", |
376
|
|
|
" 1 'x, y, z'", |
377
|
|
|
"loop_", |
378
|
|
|
" _atom_site_label", |
379
|
|
|
" _atom_site_type_symbol", |
380
|
|
|
" _atom_site_fract_x", |
381
|
|
|
" _atom_site_fract_y", |
382
|
|
|
" _atom_site_fract_z", |
383
|
|
|
] |
384
|
|
|
|
385
|
|
|
frac_motif = self.motif @ np.linalg.inv(self.cell) |
386
|
|
|
ind = 0 |
387
|
|
|
label_nums = {} |
388
|
|
|
for asym_ind, mul in enumerate(self.multiplicities): |
389
|
|
|
for _ in range(mul): |
390
|
|
|
|
391
|
|
|
sym = ATOMIC_NUMBERS_TO_SYMS[self.types[asym_ind]] |
392
|
|
|
if sym in label_nums: |
393
|
|
|
label_nums[sym] += 1 |
394
|
|
|
else: |
395
|
|
|
label_nums[sym] = 1 |
396
|
|
|
|
397
|
|
|
out.append( |
398
|
|
|
" ".join( |
399
|
|
|
[ |
400
|
|
|
f"{sym}{label_nums[sym]}", |
401
|
|
|
sym, |
402
|
|
|
f"{frac_motif[ind, 0]:.4f}", |
403
|
|
|
f"{frac_motif[ind, 1]:.4f}", |
404
|
|
|
f"{frac_motif[ind, 2]:.4f}", |
405
|
|
|
] |
406
|
|
|
) |
407
|
|
|
) |
408
|
|
|
ind += 1 |
409
|
|
|
|
410
|
|
|
return "\n".join(out) |
411
|
|
|
|
412
|
|
|
def to_pymatgen_structure(self): |
413
|
|
|
|
414
|
|
|
if self.types is None: |
415
|
|
|
raise ValueError( |
416
|
|
|
"Cannot convert PeriodicSet to pymatgen Structure without " |
417
|
|
|
"atomic types." |
418
|
|
|
) |
419
|
|
|
|
420
|
|
|
from pymatgen.core import Structure |
421
|
|
|
|
422
|
|
|
species = [ |
423
|
|
|
ATOMIC_NUMBERS_TO_SYMS[t] |
424
|
|
|
for m, t in zip(self.multiplicities, self.types) |
425
|
|
|
for _ in range(m) |
426
|
|
|
] |
427
|
|
|
|
428
|
|
|
labels = None |
429
|
|
|
if self.labels is not None: |
430
|
|
|
labels = [ |
431
|
|
|
f"{t}_{i+1}" |
432
|
|
|
for m, t in zip(self.multiplicities, self.labels) |
433
|
|
|
for i in range(m) |
434
|
|
|
] |
435
|
|
|
|
436
|
|
|
return Structure( |
437
|
|
|
self.cell, species, self.motif, coords_are_cartesian=True, labels=labels |
438
|
|
|
) |
439
|
|
|
|
440
|
|
|
def majority_occupancy_config(self) -> PeriodicSet: |
441
|
|
|
|
442
|
|
|
if not self.disorder: |
443
|
|
|
return self |
444
|
|
|
|
445
|
|
|
asym_mask = np.full((self.n_asym, ), fill_value=True) |
446
|
|
|
motif_mask = np.full((self.m, ), fill_value=True) |
447
|
|
|
|
448
|
|
|
for asm in self.disorder: |
449
|
|
|
|
450
|
|
|
# find majority occupancy group |
451
|
|
|
majority_occ, majority_ind = 0, 0 |
452
|
|
|
for i, grp in enumerate(asm.groups): |
453
|
|
|
if grp.occupancy > majority_occ: |
454
|
|
|
majority_ind = i |
455
|
|
|
majority_occ = grp.occupancy |
456
|
|
|
|
457
|
|
|
# mask all other groups |
458
|
|
|
for i, grp in enumerate(asm.groups): |
459
|
|
|
|
460
|
|
|
if i == majority_ind: |
461
|
|
|
continue |
462
|
|
|
|
463
|
|
|
for j in grp.indices: |
464
|
|
|
asym_mask[j] = False |
465
|
|
|
m_i = self.asym_unit[j] |
466
|
|
|
mul = self.multiplicities[j] |
467
|
|
|
motif_mask[m_i : m_i + mul] = False |
468
|
|
|
|
469
|
|
|
muls = self.multiplicities[asym_mask] |
470
|
|
|
asym_unit = np.zeros_like(muls, dtype=np.int64) |
471
|
|
|
asym_unit[1:] = np.cumsum(muls)[:-1] |
472
|
|
|
masked_labels = [l for l, flag in zip(self.labels, asym_mask) if flag] |
473
|
|
|
|
474
|
|
|
return PeriodicSet( |
475
|
|
|
self.motif[motif_mask], |
476
|
|
|
self.cell, |
477
|
|
|
self.name, |
478
|
|
|
asym_unit, |
479
|
|
|
self.multiplicities[asym_mask], |
480
|
|
|
self.types[asym_mask], |
481
|
|
|
masked_labels |
482
|
|
|
) |
483
|
|
|
|
484
|
|
|
def compressed_dtypes(self) -> PeriodicSet: |
485
|
|
|
"""Return a PeriodicSet using float32, uint32 and uint8 for |
486
|
|
|
atomic types.""" |
487
|
|
|
|
488
|
|
|
if self._asym_unit is not None: |
489
|
|
|
asym_unit = np.array(self._asym_unit, dtype=np.uint32) |
490
|
|
|
else: |
491
|
|
|
asym_unit = None |
492
|
|
|
|
493
|
|
|
if self._multiplicities is not None: |
494
|
|
|
multiplicities = np.array(self._multiplicities, dtype=np.uint32) |
495
|
|
|
else: |
496
|
|
|
multiplicities = None |
497
|
|
|
|
498
|
|
|
if self.types is not None: |
499
|
|
|
types = np.array(self.types, dtype=np.uint8) |
500
|
|
|
else: |
501
|
|
|
types = None |
502
|
|
|
|
503
|
|
|
return PeriodicSet( |
504
|
|
|
np.array(self.motif, dtype=np.float32), |
505
|
|
|
np.array(self.cell, dtype=np.float32), |
506
|
|
|
name=self.name, |
507
|
|
|
asym_unit=asym_unit, |
508
|
|
|
multiplicities=multiplicities, |
509
|
|
|
types=types, |
510
|
|
|
labels=self.labels, |
511
|
|
|
disorder=self.disorder |
512
|
|
|
) |
513
|
|
|
|