diffpy.structure 3.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffpy/Structure.py +35 -0
- diffpy/__init__.py +23 -0
- diffpy/structure/__init__.py +93 -0
- diffpy/structure/_legacy_importer.py +88 -0
- diffpy/structure/apps/__init__.py +17 -0
- diffpy/structure/apps/anyeye.py +284 -0
- diffpy/structure/apps/transtru.py +126 -0
- diffpy/structure/atom.py +544 -0
- diffpy/structure/expansion/__init__.py +27 -0
- diffpy/structure/expansion/makeellipsoid.py +129 -0
- diffpy/structure/expansion/shapeutils.py +44 -0
- diffpy/structure/expansion/supercell_mod.py +91 -0
- diffpy/structure/lattice.py +663 -0
- diffpy/structure/mmlibspacegroups.py +8154 -0
- diffpy/structure/parsers/__init__.py +83 -0
- diffpy/structure/parsers/p_auto.py +217 -0
- diffpy/structure/parsers/p_cif.py +876 -0
- diffpy/structure/parsers/p_discus.py +312 -0
- diffpy/structure/parsers/p_pdb.py +405 -0
- diffpy/structure/parsers/p_pdffit.py +290 -0
- diffpy/structure/parsers/p_rawxyz.py +149 -0
- diffpy/structure/parsers/p_xcfg.py +457 -0
- diffpy/structure/parsers/p_xyz.py +161 -0
- diffpy/structure/parsers/parser_index_mod.py +108 -0
- diffpy/structure/parsers/structureparser.py +80 -0
- diffpy/structure/pdffitstructure.py +109 -0
- diffpy/structure/sgtbxspacegroups.py +5198 -0
- diffpy/structure/spacegroupmod.py +329 -0
- diffpy/structure/spacegroups.py +1441 -0
- diffpy/structure/structure.py +866 -0
- diffpy/structure/structureerrors.py +35 -0
- diffpy/structure/symmetryutilities.py +1100 -0
- diffpy/structure/utils.py +126 -0
- diffpy/structure/version.py +26 -0
- diffpy.structure-3.2.0.dist-info/AUTHORS.rst +13 -0
- diffpy.structure-3.2.0.dist-info/LICENSE.rst +141 -0
- diffpy.structure-3.2.0.dist-info/LICENSE_DANSE.rst +50 -0
- diffpy.structure-3.2.0.dist-info/LICENSE_pymmlib.rst +203 -0
- diffpy.structure-3.2.0.dist-info/METADATA +197 -0
- diffpy.structure-3.2.0.dist-info/RECORD +42 -0
- diffpy.structure-3.2.0.dist-info/WHEEL +5 -0
- diffpy.structure-3.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# diffpy.structure by DANSE Diffraction group
|
|
5
|
+
# Simon J. L. Billinge
|
|
6
|
+
# (c) 2008 trustees of the Michigan State University.
|
|
7
|
+
# All rights reserved.
|
|
8
|
+
#
|
|
9
|
+
# File coded by: Pavol Juhas
|
|
10
|
+
#
|
|
11
|
+
# See AUTHORS.txt for a list of people who contributed.
|
|
12
|
+
# See LICENSE_DANSE.txt for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
|
|
16
|
+
"""Class Lattice stores properties and provides simple operations in lattice
|
|
17
|
+
coordinate system.
|
|
18
|
+
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
cartesian : Lattice
|
|
22
|
+
Constant instance of Lattice, default Cartesian system.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import math
|
|
26
|
+
|
|
27
|
+
import numpy
|
|
28
|
+
import numpy.linalg as numalg
|
|
29
|
+
|
|
30
|
+
from diffpy.structure.structureerrors import LatticeError
|
|
31
|
+
|
|
32
|
+
# Helper Functions -----------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
# exact values of cosd
|
|
35
|
+
_EXACT_COSD = {0.0: +1.0, 60.0: +0.5, 90.0: 0.0, 120.0: -0.5, 180.0: -1.0, 240.0: -0.5, 270.0: 0.0, 300.0: +0.5}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cosd(x):
|
|
39
|
+
"""Return the cosine of *x* (measured in degrees).
|
|
40
|
+
|
|
41
|
+
Avoid round-off errors for exact cosine values.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
x : float
|
|
46
|
+
The angle in degrees.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
float
|
|
51
|
+
The cosine of the angle *x*.
|
|
52
|
+
"""
|
|
53
|
+
rv = _EXACT_COSD.get(x % 360.0)
|
|
54
|
+
if rv is None:
|
|
55
|
+
rv = math.cos(math.radians(x))
|
|
56
|
+
return rv
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def sind(x):
|
|
60
|
+
"""Return the sine of *x* (measured in degrees).
|
|
61
|
+
|
|
62
|
+
Avoid round-off errors for exact sine values.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
x : float
|
|
67
|
+
The angle in degrees.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
float
|
|
72
|
+
The sine of the angle *x*.
|
|
73
|
+
"""
|
|
74
|
+
return cosd(90.0 - x)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ----------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Lattice(object):
|
|
81
|
+
"""General coordinate system and associated operations.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
a : float or Lattice, Optional
|
|
86
|
+
The cell length *a*. When present, other cell parameters
|
|
87
|
+
must be also specified. When of the *Lattice* type, create
|
|
88
|
+
a duplicate Lattice.
|
|
89
|
+
b : float
|
|
90
|
+
The cell length *b*.
|
|
91
|
+
c : float
|
|
92
|
+
The cell length *c*.
|
|
93
|
+
alpha : float
|
|
94
|
+
The angle between the *b* and *c* axes in degrees.
|
|
95
|
+
beta : float
|
|
96
|
+
The angle between the *b* and *c* axes in degrees.
|
|
97
|
+
gamma : float
|
|
98
|
+
The angle between the *a* and *b* axes in degrees.
|
|
99
|
+
baserot : array_like, Optional
|
|
100
|
+
The 3x3 rotation matrix of the base vectors with respect
|
|
101
|
+
to their standard setting.
|
|
102
|
+
base : array_like, Optional
|
|
103
|
+
The 3x3 array of row base vectors. This must be the
|
|
104
|
+
only argument when present.
|
|
105
|
+
|
|
106
|
+
Attributes
|
|
107
|
+
----------
|
|
108
|
+
metrics : numpy.ndarray
|
|
109
|
+
The metrics tensor.
|
|
110
|
+
base : numpy.ndarray
|
|
111
|
+
The 3x3 matrix of row base vectors in Cartesian coordinates,
|
|
112
|
+
which may be rotated, i.e., ``base = stdbase @ baserot``.
|
|
113
|
+
stdbase : numpy.ndarray
|
|
114
|
+
The 3x3 matrix of row base vectors in standard orientation.
|
|
115
|
+
baserot : numpy.ndarray
|
|
116
|
+
The rotation matrix for the `base`.
|
|
117
|
+
recbase : numpy.ndarray
|
|
118
|
+
The inverse of the `base` matrix, where the columns give
|
|
119
|
+
reciprocal vectors in Cartesian coordinates.
|
|
120
|
+
normbase : numpy.ndarray
|
|
121
|
+
The `base` vectors scaled by magnitudes of reciprocal cell lengths.
|
|
122
|
+
recnormbase : numpy.ndarray
|
|
123
|
+
The inverse of the `normbase` matrix.
|
|
124
|
+
isotropicunit : numpy.ndarray
|
|
125
|
+
The 3x3 tensor for a unit isotropic displacement parameters in this
|
|
126
|
+
coordinate system. This is an identity matrix when this Lattice
|
|
127
|
+
is orthonormal.
|
|
128
|
+
|
|
129
|
+
Note
|
|
130
|
+
----
|
|
131
|
+
The array attributes are read-only. They get updated by changing
|
|
132
|
+
some lattice parameters or by calling the `setLatPar()` or
|
|
133
|
+
`setLatBase()` methods.
|
|
134
|
+
|
|
135
|
+
Examples
|
|
136
|
+
--------
|
|
137
|
+
Create a Cartesian coordinate system:
|
|
138
|
+
|
|
139
|
+
>>> Lattice()
|
|
140
|
+
|
|
141
|
+
Create coordinate system with the cell lengths `a`, `b`, `c`
|
|
142
|
+
and cell angles `alpha`, `beta`, `gamma` in degrees:
|
|
143
|
+
|
|
144
|
+
>>> Lattice(a, b, c, alpha, beta, gamma)
|
|
145
|
+
|
|
146
|
+
Create a duplicate of an existing Lattice `lat`:
|
|
147
|
+
|
|
148
|
+
>>> Lattice(lat)
|
|
149
|
+
|
|
150
|
+
Create coordinate system with the base vectors given by rows
|
|
151
|
+
of the `abc` matrix:
|
|
152
|
+
|
|
153
|
+
>>> Lattice(base=abc)
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
# round-off tolerance
|
|
157
|
+
_epsilon = 1.0e-8
|
|
158
|
+
|
|
159
|
+
# properties -------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
a = property(
|
|
162
|
+
lambda self: self._a, lambda self, value: self.setLatPar(a=value), doc="The unit cell length *a*."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
b = property(
|
|
166
|
+
lambda self: self._b, lambda self, value: self.setLatPar(b=value), doc="The unit cell length *b*."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
c = property(
|
|
170
|
+
lambda self: self._c, lambda self, value: self.setLatPar(c=value), doc="The unit cell length *c*."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
alpha = property(
|
|
174
|
+
lambda self: self._alpha,
|
|
175
|
+
lambda self, value: self.setLatPar(alpha=value),
|
|
176
|
+
doc="The cell angle *alpha* in degrees.",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
beta = property(
|
|
180
|
+
lambda self: self._beta,
|
|
181
|
+
lambda self, value: self.setLatPar(beta=value),
|
|
182
|
+
doc="The cell angle *beta* in degrees.",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
gamma = property(
|
|
186
|
+
lambda self: self._gamma,
|
|
187
|
+
lambda self, value: self.setLatPar(gamma=value),
|
|
188
|
+
doc="The cell angle *gamma* in degrees.",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# read-only derived properties
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def unitvolume(self):
|
|
195
|
+
"""The unit cell volume when `a = b = c = 1`."""
|
|
196
|
+
# Recalculate lattice cosines to ensure this is right
|
|
197
|
+
# even if ca, cb, cg data were not yet updated.
|
|
198
|
+
ca = cosd(self.alpha)
|
|
199
|
+
cb = cosd(self.beta)
|
|
200
|
+
cg = cosd(self.gamma)
|
|
201
|
+
rv = math.sqrt(1.0 + 2.0 * ca * cb * cg - ca * ca - cb * cb - cg * cg)
|
|
202
|
+
return rv
|
|
203
|
+
|
|
204
|
+
volume = property(lambda self: self.a * self.b * self.c * self.unitvolume, doc="The unit cell volume.")
|
|
205
|
+
|
|
206
|
+
ar = property(lambda self: self._ar, doc="The cell length *a* of the reciprocal lattice.")
|
|
207
|
+
|
|
208
|
+
br = property(lambda self: self._br, doc="The cell length *b* of the reciprocal lattice.")
|
|
209
|
+
|
|
210
|
+
cr = property(lambda self: self._cr, doc="The cell length *c* of the reciprocal lattice.")
|
|
211
|
+
|
|
212
|
+
alphar = property(lambda self: self._alphar, doc="The reciprocal cell angle *alpha* in degrees.")
|
|
213
|
+
|
|
214
|
+
betar = property(lambda self: self._betar, doc="The reciprocal cell angle *beta* in degrees")
|
|
215
|
+
|
|
216
|
+
gammar = property(lambda self: self._gammar, doc="The reciprocal cell angle *gamma* in degrees")
|
|
217
|
+
|
|
218
|
+
ca = property(lambda self: self._ca, doc="The cosine of the cell angle *alpha*.")
|
|
219
|
+
|
|
220
|
+
cb = property(lambda self: self._cb, doc="The cosine of the cell angle *beta*.")
|
|
221
|
+
|
|
222
|
+
cg = property(lambda self: self._cg, doc="The cosine of the cell angle *gamma*.")
|
|
223
|
+
|
|
224
|
+
sa = property(lambda self: self._sa, doc="The sine of the cell angle *alpha*.")
|
|
225
|
+
|
|
226
|
+
sb = property(lambda self: self._sb, doc="The sine of the cell angle *beta*.")
|
|
227
|
+
|
|
228
|
+
sg = property(lambda self: self._sg, doc="The sine of the cell angle *gamma*.")
|
|
229
|
+
|
|
230
|
+
car = property(lambda self: self._car, doc="The cosine of the reciprocal angle *alpha*.")
|
|
231
|
+
|
|
232
|
+
cbr = property(lambda self: self._cbr, doc="The cosine of the reciprocal angle *beta*.")
|
|
233
|
+
|
|
234
|
+
cgr = property(lambda self: self._cgr, doc="The cosine of the reciprocal angle *gamma*.")
|
|
235
|
+
|
|
236
|
+
sar = property(lambda self: self._sar, doc="The sine of the reciprocal angle *alpha*.")
|
|
237
|
+
|
|
238
|
+
sbr = property(lambda self: self._sbr, doc="The sine of the reciprocal angle *beta*.")
|
|
239
|
+
|
|
240
|
+
sgr = property(lambda self: self._sgr, doc="The sine of the reciprocal angle *gamma*.")
|
|
241
|
+
|
|
242
|
+
# done with properties ---------------------------------------------------
|
|
243
|
+
|
|
244
|
+
def __init__(self, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, baserot=None, base=None):
|
|
245
|
+
# build a set of provided argument names for later use.
|
|
246
|
+
apairs = (
|
|
247
|
+
("a", a),
|
|
248
|
+
("b", b),
|
|
249
|
+
("c", c),
|
|
250
|
+
("alpha", alpha),
|
|
251
|
+
("beta", beta),
|
|
252
|
+
("gamma", gamma),
|
|
253
|
+
("baserot", baserot),
|
|
254
|
+
("base", base),
|
|
255
|
+
)
|
|
256
|
+
argset = set(n for n, v in apairs if v is not None)
|
|
257
|
+
# initialize data members, they values will be set by setLatPar()
|
|
258
|
+
self._a = self._b = self._c = None
|
|
259
|
+
self._alpha = self._beta = self._gamma = None
|
|
260
|
+
self._ca = self._cb = self._cg = None
|
|
261
|
+
self._sa = self._sb = self._sg = None
|
|
262
|
+
self._ar = self._br = self._cr = None
|
|
263
|
+
self._alphar = self._betar = self._gammar = None
|
|
264
|
+
self._car = self._cbr = self._cgr = None
|
|
265
|
+
self._sar = self._sbr = self._sgr = None
|
|
266
|
+
self.baserot = numpy.identity(3)
|
|
267
|
+
self.base = self.recbase = None
|
|
268
|
+
self.normbase = self.recnormbase = None
|
|
269
|
+
# work out argument variants
|
|
270
|
+
# Lattice()
|
|
271
|
+
if not argset:
|
|
272
|
+
self.setLatPar(1.0, 1.0, 1.0, 90.0, 90.0, 90.0, baserot)
|
|
273
|
+
# Lattice(base=abc)
|
|
274
|
+
elif base is not None:
|
|
275
|
+
if len(argset) > 1:
|
|
276
|
+
raise ValueError("'base' must be the only argument.")
|
|
277
|
+
self.setLatBase(base)
|
|
278
|
+
# Lattice(lat)
|
|
279
|
+
elif isinstance(a, Lattice):
|
|
280
|
+
if len(argset) > 1:
|
|
281
|
+
raise ValueError("Lattice object must be the only argument.")
|
|
282
|
+
self.__dict__.update(a.__dict__)
|
|
283
|
+
# otherwise do default Lattice(a, b, c, alpha, beta, gamma)
|
|
284
|
+
else:
|
|
285
|
+
abcabg = ("a", "b", "c", "alpha", "beta", "gamma")
|
|
286
|
+
if not argset.issuperset(abcabg):
|
|
287
|
+
raise ValueError("Provide all 6 cell parameters.")
|
|
288
|
+
self.setLatPar(a, b, c, alpha, beta, gamma, baserot=baserot)
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
def setLatPar(self, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, baserot=None):
|
|
292
|
+
"""Set one or more lattice parameters.
|
|
293
|
+
|
|
294
|
+
This updates all attributes that depend on the lattice parameters.
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
a : float, Optional
|
|
299
|
+
The new value of the cell length *a*.
|
|
300
|
+
b : float, Optional
|
|
301
|
+
The new value of the cell length *b*.
|
|
302
|
+
c : float, Optional
|
|
303
|
+
The new value of the cell length *c*.
|
|
304
|
+
alpha : float, Optional
|
|
305
|
+
The new value of the cell angle *alpha* in degrees.
|
|
306
|
+
beta : float, Optional
|
|
307
|
+
The new value of the cell angle *beta* in degrees.
|
|
308
|
+
gamma : float, Optional
|
|
309
|
+
The new value of the cell angle *gamma* in degrees.
|
|
310
|
+
baserot : array_like, Optional
|
|
311
|
+
The new 3x3 rotation matrix of the base vectors with respect
|
|
312
|
+
to their standard setting in Cartesian coordinates.
|
|
313
|
+
|
|
314
|
+
Note
|
|
315
|
+
----
|
|
316
|
+
Parameters that are not specified will keep their initial
|
|
317
|
+
values.
|
|
318
|
+
"""
|
|
319
|
+
if a is not None:
|
|
320
|
+
self._a = float(a)
|
|
321
|
+
if b is not None:
|
|
322
|
+
self._b = float(b)
|
|
323
|
+
if c is not None:
|
|
324
|
+
self._c = float(c)
|
|
325
|
+
if alpha is not None:
|
|
326
|
+
self._alpha = float(alpha)
|
|
327
|
+
if beta is not None:
|
|
328
|
+
self._beta = float(beta)
|
|
329
|
+
if gamma is not None:
|
|
330
|
+
self._gamma = float(gamma)
|
|
331
|
+
if baserot is not None:
|
|
332
|
+
self.baserot = numpy.array(baserot)
|
|
333
|
+
self._ca = ca = cosd(self.alpha)
|
|
334
|
+
self._cb = cb = cosd(self.beta)
|
|
335
|
+
self._cg = cg = cosd(self.gamma)
|
|
336
|
+
self._sa = sa = sind(self.alpha)
|
|
337
|
+
self._sb = sb = sind(self.beta)
|
|
338
|
+
self._sg = sg = sind(self.gamma)
|
|
339
|
+
# cache the unit volume value
|
|
340
|
+
Vunit = self.unitvolume
|
|
341
|
+
# reciprocal lattice
|
|
342
|
+
self._ar = ar = sa / (self.a * Vunit)
|
|
343
|
+
self._br = br = sb / (self.b * Vunit)
|
|
344
|
+
self._cr = cr = sg / (self.c * Vunit)
|
|
345
|
+
self._car = car = (cb * cg - ca) / (sb * sg)
|
|
346
|
+
self._cbr = cbr = (ca * cg - cb) / (sa * sg)
|
|
347
|
+
self._cgr = cgr = (ca * cb - cg) / (sa * sb)
|
|
348
|
+
self._sar = math.sqrt(1.0 - car * car)
|
|
349
|
+
self._sbr = math.sqrt(1.0 - cbr * cbr)
|
|
350
|
+
self._sgr = sgr = math.sqrt(1.0 - cgr * cgr)
|
|
351
|
+
self._alphar = math.degrees(math.acos(car))
|
|
352
|
+
self._betar = math.degrees(math.acos(cbr))
|
|
353
|
+
self._gammar = math.degrees(math.acos(cgr))
|
|
354
|
+
# metrics tensor
|
|
355
|
+
self.metrics = numpy.array(
|
|
356
|
+
[
|
|
357
|
+
[self.a * self.a, self.a * self.b * cg, self.a * self.c * cb],
|
|
358
|
+
[self.b * self.a * cg, self.b * self.b, self.b * self.c * ca],
|
|
359
|
+
[self.c * self.a * cb, self.c * self.b * ca, self.c * self.c],
|
|
360
|
+
],
|
|
361
|
+
dtype=float,
|
|
362
|
+
)
|
|
363
|
+
# standard Cartesian coordinates of lattice vectors
|
|
364
|
+
self.stdbase = numpy.array(
|
|
365
|
+
[[1.0 / ar, -cgr / sgr / ar, cb * self.a], [0.0, self.b * sa, self.b * ca], [0.0, 0.0, self.c]],
|
|
366
|
+
dtype=float,
|
|
367
|
+
)
|
|
368
|
+
# Cartesian coordinates of lattice vectors
|
|
369
|
+
self.base = numpy.dot(self.stdbase, self.baserot)
|
|
370
|
+
self.recbase = numalg.inv(self.base)
|
|
371
|
+
# bases normalized to unit reciprocal vectors
|
|
372
|
+
self.normbase = self.base * [[ar], [br], [cr]]
|
|
373
|
+
self.recnormbase = self.recbase / [ar, br, cr]
|
|
374
|
+
self.isotropicunit = _isotropicunit(self.recnormbase)
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
def setLatBase(self, base):
|
|
378
|
+
"""Set new base vectors for this lattice.
|
|
379
|
+
|
|
380
|
+
This updates the cell lengths and cell angles according to the
|
|
381
|
+
new base. The `stdbase`, `baserot`, and `metrics` attributes
|
|
382
|
+
are also updated.
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
base : array_like
|
|
387
|
+
The 3x3 matrix of row base vectors expressed
|
|
388
|
+
in Cartesian coordinates.
|
|
389
|
+
"""
|
|
390
|
+
self.base = numpy.array(base)
|
|
391
|
+
detbase = numalg.det(self.base)
|
|
392
|
+
if abs(detbase) < 1.0e-8:
|
|
393
|
+
emsg = "base vectors are degenerate"
|
|
394
|
+
raise LatticeError(emsg)
|
|
395
|
+
elif detbase < 0.0:
|
|
396
|
+
emsg = "base is not right-handed"
|
|
397
|
+
raise LatticeError(emsg)
|
|
398
|
+
self._a = a = math.sqrt(numpy.dot(self.base[0, :], self.base[0, :]))
|
|
399
|
+
self._b = b = math.sqrt(numpy.dot(self.base[1, :], self.base[1, :]))
|
|
400
|
+
self._c = c = math.sqrt(numpy.dot(self.base[2, :], self.base[2, :]))
|
|
401
|
+
self._ca = ca = numpy.dot(self.base[1, :], self.base[2, :]) / (b * c)
|
|
402
|
+
self._cb = cb = numpy.dot(self.base[0, :], self.base[2, :]) / (a * c)
|
|
403
|
+
self._cg = cg = numpy.dot(self.base[0, :], self.base[1, :]) / (a * b)
|
|
404
|
+
self._sa = sa = math.sqrt(1.0 - ca**2)
|
|
405
|
+
self._sb = sb = math.sqrt(1.0 - cb**2)
|
|
406
|
+
self._sg = sg = math.sqrt(1.0 - cg**2)
|
|
407
|
+
self._alpha = math.degrees(math.acos(ca))
|
|
408
|
+
self._beta = math.degrees(math.acos(cb))
|
|
409
|
+
self._gamma = math.degrees(math.acos(cg))
|
|
410
|
+
# cache the unit volume value
|
|
411
|
+
Vunit = self.unitvolume
|
|
412
|
+
# reciprocal lattice
|
|
413
|
+
self._ar = ar = sa / (self.a * Vunit)
|
|
414
|
+
self._br = br = sb / (self.b * Vunit)
|
|
415
|
+
self._cr = cr = sg / (self.c * Vunit)
|
|
416
|
+
self._car = car = (cb * cg - ca) / (sb * sg)
|
|
417
|
+
self._cbr = cbr = (ca * cg - cb) / (sa * sg)
|
|
418
|
+
self._cgr = cgr = (ca * cb - cg) / (sa * sb)
|
|
419
|
+
self._sar = math.sqrt(1.0 - car**2)
|
|
420
|
+
self._sbr = math.sqrt(1.0 - cbr**2)
|
|
421
|
+
self._sgr = sgr = math.sqrt(1.0 - cgr**2)
|
|
422
|
+
self._alphar = math.degrees(math.acos(car))
|
|
423
|
+
self._betar = math.degrees(math.acos(cbr))
|
|
424
|
+
self._gammar = math.degrees(math.acos(cgr))
|
|
425
|
+
# standard orientation of lattice vectors
|
|
426
|
+
self.stdbase = numpy.array(
|
|
427
|
+
[[1.0 / ar, -cgr / sgr / ar, cb * a], [0.0, b * sa, b * ca], [0.0, 0.0, c]], dtype=float
|
|
428
|
+
)
|
|
429
|
+
# calculate unit cell rotation matrix, base = stdbase @ baserot
|
|
430
|
+
self.baserot = numpy.dot(numalg.inv(self.stdbase), self.base)
|
|
431
|
+
self.recbase = numalg.inv(self.base)
|
|
432
|
+
# bases normalized to unit reciprocal vectors
|
|
433
|
+
self.normbase = self.base * [[ar], [br], [cr]]
|
|
434
|
+
self.recnormbase = self.recbase / [ar, br, cr]
|
|
435
|
+
self.isotropicunit = _isotropicunit(self.recnormbase)
|
|
436
|
+
# update metrics tensor
|
|
437
|
+
self.metrics = numpy.array(
|
|
438
|
+
[[a * a, a * b * cg, a * c * cb], [b * a * cg, b * b, b * c * ca], [c * a * cb, c * b * ca, c * c]],
|
|
439
|
+
dtype=float,
|
|
440
|
+
)
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
def abcABG(self):
|
|
444
|
+
"""Return the cell parameters in the standard setting.
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
tuple :
|
|
448
|
+
A tuple of ``(a, b, c, alpha, beta, gamma)``.
|
|
449
|
+
"""
|
|
450
|
+
rv = (self.a, self.b, self.c, self.alpha, self.beta, self.gamma)
|
|
451
|
+
return rv
|
|
452
|
+
|
|
453
|
+
def reciprocal(self):
|
|
454
|
+
"""Return the reciprocal lattice of the current lattice.
|
|
455
|
+
Returns
|
|
456
|
+
-------
|
|
457
|
+
Lattice
|
|
458
|
+
The reciprocal lattice of the current lattice.
|
|
459
|
+
"""
|
|
460
|
+
rv = Lattice(base=numpy.transpose(self.recbase))
|
|
461
|
+
return rv
|
|
462
|
+
|
|
463
|
+
def cartesian(self, u):
|
|
464
|
+
"""Transform lattice vector to Cartesian coordinates.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
u : array_like
|
|
469
|
+
Vector of lattice coordinates or an Nx3 array
|
|
470
|
+
of lattice vectors.
|
|
471
|
+
|
|
472
|
+
Returns
|
|
473
|
+
-------
|
|
474
|
+
rc : numpy.ndarray
|
|
475
|
+
Cartesian coordinates of the *u* vector.
|
|
476
|
+
"""
|
|
477
|
+
rc = numpy.dot(u, self.base)
|
|
478
|
+
return rc
|
|
479
|
+
|
|
480
|
+
def fractional(self, rc):
|
|
481
|
+
"""Transform Cartesian vector to fractional lattice coordinates.
|
|
482
|
+
|
|
483
|
+
Parameters
|
|
484
|
+
----------
|
|
485
|
+
rc : array_like
|
|
486
|
+
A vector of Cartesian coordinates or an Nx3 array of
|
|
487
|
+
Cartesian vectors.
|
|
488
|
+
|
|
489
|
+
Returns
|
|
490
|
+
-------
|
|
491
|
+
u : numpy.ndarray
|
|
492
|
+
Fractional coordinates of the Cartesian vector *rc*.
|
|
493
|
+
"""
|
|
494
|
+
u = numpy.dot(rc, self.recbase)
|
|
495
|
+
return u
|
|
496
|
+
|
|
497
|
+
def dot(self, u, v):
|
|
498
|
+
"""Calculate dot product of 2 lattice vectors.
|
|
499
|
+
|
|
500
|
+
Parameters
|
|
501
|
+
----------
|
|
502
|
+
u : array_like
|
|
503
|
+
The first lattice vector or an Nx3 array.
|
|
504
|
+
v : array_like
|
|
505
|
+
The second lattice vector or an array of
|
|
506
|
+
the same shape as *u*.
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
float or numpy.ndarray
|
|
511
|
+
The dot product of lattice vectors *u*, *v*.
|
|
512
|
+
"""
|
|
513
|
+
dp = (u * numpy.dot(v, self.metrics)).sum(axis=-1)
|
|
514
|
+
return dp
|
|
515
|
+
|
|
516
|
+
def norm(self, xyz):
|
|
517
|
+
"""Calculate norm of a lattice vector.
|
|
518
|
+
|
|
519
|
+
Parameters
|
|
520
|
+
----------
|
|
521
|
+
xyz : array_like
|
|
522
|
+
A vector or an Nx3 array of fractional coordinates.
|
|
523
|
+
|
|
524
|
+
Returns
|
|
525
|
+
-------
|
|
526
|
+
float or numpy.ndarray
|
|
527
|
+
The magnitude of the lattice vector *xyz*.
|
|
528
|
+
"""
|
|
529
|
+
# this is a few percent faster than sqrt(dot(u, u)).
|
|
530
|
+
return numpy.sqrt((self.cartesian(xyz) ** 2).sum(axis=-1))
|
|
531
|
+
|
|
532
|
+
def rnorm(self, hkl):
|
|
533
|
+
"""Calculate norm of a reciprocal vector.
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
hkl : array_like
|
|
538
|
+
A vector or an Nx3 array of reciprocal coordinates.
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
float or numpy.ndarray
|
|
543
|
+
The magnitude of the reciprocal vector *hkl*.
|
|
544
|
+
"""
|
|
545
|
+
hklcartn = numpy.dot(hkl, self.recbase.T)
|
|
546
|
+
return numpy.sqrt((hklcartn**2).sum(axis=-1))
|
|
547
|
+
|
|
548
|
+
def dist(self, u, v):
|
|
549
|
+
"""Calculate distance between 2 points in lattice coordinates.
|
|
550
|
+
|
|
551
|
+
Parameters
|
|
552
|
+
----------
|
|
553
|
+
u : array_like
|
|
554
|
+
A vector or an Nx3 matrix of fractional coordinates.
|
|
555
|
+
v : numpy.ndarray
|
|
556
|
+
A vector or an Nx3 matrix of fractional coordinates.
|
|
557
|
+
|
|
558
|
+
Note
|
|
559
|
+
----
|
|
560
|
+
*u* and *v* must be of the same shape when matrices.
|
|
561
|
+
|
|
562
|
+
Returns
|
|
563
|
+
-------
|
|
564
|
+
float or numpy.ndarray
|
|
565
|
+
The distance between lattice points *u* and *v*.
|
|
566
|
+
"""
|
|
567
|
+
duv = numpy.asarray(u) - v
|
|
568
|
+
return self.norm(duv)
|
|
569
|
+
|
|
570
|
+
def angle(self, u, v):
|
|
571
|
+
"""Calculate angle between 2 lattice vectors in degrees.
|
|
572
|
+
|
|
573
|
+
Parameters
|
|
574
|
+
----------
|
|
575
|
+
u : array_like
|
|
576
|
+
The first lattice vector.
|
|
577
|
+
v : array_like
|
|
578
|
+
The second lattice vector.
|
|
579
|
+
|
|
580
|
+
Returns
|
|
581
|
+
-------
|
|
582
|
+
float
|
|
583
|
+
The angle between lattice vectors *u* and *v* in degrees.
|
|
584
|
+
"""
|
|
585
|
+
ca = self.dot(u, v) / (self.norm(u) * self.norm(v))
|
|
586
|
+
# avoid round-off errors that would make abs(ca) greater than 1
|
|
587
|
+
if numpy.isscalar(ca):
|
|
588
|
+
ca = max(min(ca, 1), -1)
|
|
589
|
+
rv = math.degrees(math.acos(ca))
|
|
590
|
+
else:
|
|
591
|
+
ca[ca < -1] = -1
|
|
592
|
+
ca[ca > +1] = +1
|
|
593
|
+
rv = numpy.degrees(numpy.arccos(ca))
|
|
594
|
+
return rv
|
|
595
|
+
|
|
596
|
+
def isanisotropic(self, umx):
|
|
597
|
+
"""True if displacement parameter matrix is anisotropic.
|
|
598
|
+
|
|
599
|
+
This checks if the specified matrix of anisotropic displacement
|
|
600
|
+
parameters (ADP) differs from isotropic values for this lattice
|
|
601
|
+
by more than a small round-off error.
|
|
602
|
+
|
|
603
|
+
Parameters
|
|
604
|
+
----------
|
|
605
|
+
umx : array_like
|
|
606
|
+
The 3x3 matrix of displacement parameters.
|
|
607
|
+
|
|
608
|
+
Returns
|
|
609
|
+
-------
|
|
610
|
+
bool
|
|
611
|
+
True when *umx* is anisotropic by more than a round-off error.
|
|
612
|
+
"""
|
|
613
|
+
umx = numpy.asarray(umx)
|
|
614
|
+
utr = numpy.trace(umx) / umx.shape[0]
|
|
615
|
+
udmax = numpy.fabs(umx - utr * self.isotropicunit).max()
|
|
616
|
+
rv = udmax > self._epsilon
|
|
617
|
+
return rv
|
|
618
|
+
|
|
619
|
+
def __repr__(self):
|
|
620
|
+
"""String representation of this lattice."""
|
|
621
|
+
I3 = numpy.identity(3, dtype=float)
|
|
622
|
+
rotbaseI3diff = max(numpy.reshape(numpy.fabs(self.baserot - I3), 9))
|
|
623
|
+
cartlatpar = numpy.array([1.0, 1.0, 1.0, 90.0, 90.0, 90.0])
|
|
624
|
+
latpardiff = cartlatpar - self.abcABG()
|
|
625
|
+
if rotbaseI3diff > self._epsilon:
|
|
626
|
+
s = "Lattice(base=%r)" % self.base
|
|
627
|
+
elif numpy.fabs(latpardiff).max() < self._epsilon:
|
|
628
|
+
s = "Lattice()"
|
|
629
|
+
else:
|
|
630
|
+
s = "Lattice(a=%g, b=%g, c=%g, alpha=%g, beta=%g, gamma=%g)" % self.abcABG()
|
|
631
|
+
return s
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
# End of class Lattice
|
|
635
|
+
|
|
636
|
+
# Local Helpers --------------------------------------------------------------
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def _isotropicunit(recnormbase):
|
|
640
|
+
"""Calculate tensor of unit isotropic displacement parameters.
|
|
641
|
+
|
|
642
|
+
Parameters
|
|
643
|
+
----------
|
|
644
|
+
recnormbase : numpy.ndarray
|
|
645
|
+
The inverse of normalized base vectors of some lattice.
|
|
646
|
+
|
|
647
|
+
Returns
|
|
648
|
+
-------
|
|
649
|
+
numpy.ndarray
|
|
650
|
+
The 3x3 matrix of displacement parameters corresponding to
|
|
651
|
+
a unit isotropic displacements.
|
|
652
|
+
"""
|
|
653
|
+
isounit = numpy.dot(recnormbase.T, recnormbase)
|
|
654
|
+
# ensure there are no round-off deviations on the diagonal
|
|
655
|
+
isounit[0, 0] = 1
|
|
656
|
+
isounit[1, 1] = 1
|
|
657
|
+
isounit[2, 2] = 1
|
|
658
|
+
return isounit
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
# Module Constants -----------------------------------------------------------
|
|
662
|
+
|
|
663
|
+
cartesian = Lattice()
|