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
diffpy/structure/atom.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# diffpy.structure by DANSE Diffraction group
|
|
5
|
+
# Simon J. L. Billinge
|
|
6
|
+
# (c) 2006 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
|
+
"""
|
|
17
|
+
Provide class Atom for managing properties of an atom in structure model.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import numpy
|
|
21
|
+
|
|
22
|
+
from diffpy.structure.lattice import cartesian as cartesian_lattice
|
|
23
|
+
|
|
24
|
+
# conversion constants
|
|
25
|
+
_BtoU = 1.0 / (8 * numpy.pi**2)
|
|
26
|
+
_UtoB = 1.0 / _BtoU
|
|
27
|
+
|
|
28
|
+
# ----------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Atom(object):
|
|
32
|
+
"""Storage of structure information relevant for a single atom.
|
|
33
|
+
|
|
34
|
+
This class manages atom information such as element symbol, position
|
|
35
|
+
in fractional and Cartesian coordinates, atomic displacement parameters
|
|
36
|
+
and so forth.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
atype : str or Atom, Optional
|
|
41
|
+
The string atom type to be set as the `element` attribute.
|
|
42
|
+
By default an empty string. When of the `Atom` type, create
|
|
43
|
+
a copy of `atype` and adjust it per other arguments.
|
|
44
|
+
xyz : numpy.ndarray, Optional
|
|
45
|
+
Fractional coordinates within the associated `lattice`.
|
|
46
|
+
By default ``[0, 0, 0]``.
|
|
47
|
+
label : str, Optional
|
|
48
|
+
A unique string `label` for referring to this `Atom`.
|
|
49
|
+
By default an empty string.
|
|
50
|
+
occupancy : float, Optional
|
|
51
|
+
The initial `occupancy` of this atom, by default ``1``.
|
|
52
|
+
anisotropy : bool, Optional
|
|
53
|
+
The flag for anisotropic thermal displacements parameters.
|
|
54
|
+
This overrides `anisotropy` implied by presence of the
|
|
55
|
+
*U* or *Uisoequiv* arguments. Defaults to ``False``
|
|
56
|
+
when not set in any other way.
|
|
57
|
+
U : numpy.ndarray, Optional
|
|
58
|
+
The 3x3 matrix of anisotropic thermal displacement parameters.
|
|
59
|
+
When present `anisotropy` defaults to ``True``.
|
|
60
|
+
Uisoequiv: float, Optional
|
|
61
|
+
The isotropic atomic displacement parameter. The `anisotropy`
|
|
62
|
+
defaults to ``False`` when present. Only one of the *U* and
|
|
63
|
+
*Uisoequiv* arguments may be provided at the same time. Assume
|
|
64
|
+
zero atomic displacements when *U* and *Uisoequiv* are unset.
|
|
65
|
+
lattice : Lattice, Optional
|
|
66
|
+
Coordinate system for the fractional coordinates `xyz`.
|
|
67
|
+
Use the absolute Cartesian system when ``None``.
|
|
68
|
+
|
|
69
|
+
Attributes
|
|
70
|
+
----------
|
|
71
|
+
element : str
|
|
72
|
+
The string type of the atom. An element or ion symbol.
|
|
73
|
+
xyz : numpy.ndarray
|
|
74
|
+
The fractional coordinates in the associated `lattice`.
|
|
75
|
+
label : str
|
|
76
|
+
A unique string label referring to this atom, for example, "C_1".
|
|
77
|
+
The *label* can be used to reference this atom when contained in
|
|
78
|
+
a `Structure` object.
|
|
79
|
+
occupancy : float
|
|
80
|
+
The fractional occupancy of this atom.
|
|
81
|
+
lattice : Lattice
|
|
82
|
+
Coordinate system for the fractional coordinates `xyz` and
|
|
83
|
+
the tensor of atomic displacement parameters `U`.
|
|
84
|
+
Use the absolute Cartesian coordinates when ``None``.
|
|
85
|
+
|
|
86
|
+
Note
|
|
87
|
+
----
|
|
88
|
+
Cannot use both U and Uisoequiv arguments at the same time.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# Private attributes
|
|
92
|
+
#
|
|
93
|
+
# _U : 3-by-3 ndarray
|
|
94
|
+
# Internal storage of the displacement parameters.
|
|
95
|
+
|
|
96
|
+
# instance attributes that have immutable default values
|
|
97
|
+
element = ""
|
|
98
|
+
"""str: Default values of `element`."""
|
|
99
|
+
|
|
100
|
+
label = ""
|
|
101
|
+
"""str: Default values of `label`."""
|
|
102
|
+
|
|
103
|
+
occupancy = 1.0
|
|
104
|
+
"""float: Default values of `occupancy`."""
|
|
105
|
+
|
|
106
|
+
_anisotropy = False
|
|
107
|
+
lattice = None
|
|
108
|
+
"""None: Default values of `lattice`."""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
atype=None,
|
|
113
|
+
xyz=None,
|
|
114
|
+
label=None,
|
|
115
|
+
occupancy=None,
|
|
116
|
+
anisotropy=None,
|
|
117
|
+
U=None,
|
|
118
|
+
Uisoequiv=None,
|
|
119
|
+
lattice=None,
|
|
120
|
+
):
|
|
121
|
+
# check arguments
|
|
122
|
+
if U is not None and Uisoequiv is not None:
|
|
123
|
+
emsg = "Cannot use both U and Uisoequiv arguments."
|
|
124
|
+
raise ValueError(emsg)
|
|
125
|
+
# declare data members
|
|
126
|
+
self.xyz = numpy.zeros(3, dtype=float)
|
|
127
|
+
self._U = numpy.zeros((3, 3), dtype=float)
|
|
128
|
+
# assign them as needed
|
|
129
|
+
if isinstance(atype, Atom):
|
|
130
|
+
atype.__copy__(target=self)
|
|
131
|
+
elif atype is not None:
|
|
132
|
+
self.element = atype
|
|
133
|
+
# take care of remaining arguments
|
|
134
|
+
if xyz is not None:
|
|
135
|
+
self.xyz[:] = xyz
|
|
136
|
+
if label is not None:
|
|
137
|
+
self.label = label
|
|
138
|
+
if occupancy is not None:
|
|
139
|
+
self.occupancy = float(occupancy)
|
|
140
|
+
if U is not None:
|
|
141
|
+
self.anisotropy = True
|
|
142
|
+
self._U[:] = U
|
|
143
|
+
if Uisoequiv is not None:
|
|
144
|
+
self.anisotropy = False
|
|
145
|
+
self.Uisoequiv = Uisoequiv
|
|
146
|
+
# lattice needs to be set before anisotropy
|
|
147
|
+
if lattice is not None:
|
|
148
|
+
self.lattice = lattice
|
|
149
|
+
# process anisotropy after U, Uisoequiv and lattice.
|
|
150
|
+
if anisotropy is not None:
|
|
151
|
+
self.anisotropy = bool(anisotropy)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
def msdLat(self, vl):
|
|
155
|
+
"""Calculate mean square displacement along the lattice vector.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
vl : array_like
|
|
160
|
+
The vector in lattice coordinates.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
float
|
|
165
|
+
The mean square displacement along *vl*.
|
|
166
|
+
"""
|
|
167
|
+
if not self.anisotropy:
|
|
168
|
+
return self.Uisoequiv
|
|
169
|
+
# here we need to calculate msd
|
|
170
|
+
lat = self.lattice or cartesian_lattice
|
|
171
|
+
vln = numpy.array(vl, dtype=float) / lat.norm(vl)
|
|
172
|
+
G = lat.metrics
|
|
173
|
+
rhs = numpy.array([G[0] * lat.ar, G[1] * lat.br, G[2] * lat.cr], dtype=float)
|
|
174
|
+
rhs = numpy.dot(rhs, vln)
|
|
175
|
+
msd = numpy.dot(rhs, numpy.dot(self.U, rhs))
|
|
176
|
+
return msd
|
|
177
|
+
|
|
178
|
+
def msdCart(self, vc):
|
|
179
|
+
"""Calculate mean square displacement along the Cartesian vector.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
vc : array_like
|
|
184
|
+
Vector in Cartesian coordinates.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
float
|
|
189
|
+
The mean square displacement along *vc*.
|
|
190
|
+
"""
|
|
191
|
+
if not self.anisotropy:
|
|
192
|
+
return self.Uisoequiv
|
|
193
|
+
# here we need to calculate msd
|
|
194
|
+
lat = self.lattice or cartesian_lattice
|
|
195
|
+
vcn = numpy.array(vc, dtype=float)
|
|
196
|
+
vcn /= numpy.sqrt(numpy.sum(vcn**2))
|
|
197
|
+
F1 = lat.normbase
|
|
198
|
+
Uc = numpy.dot(numpy.transpose(F1), numpy.dot(self._U, F1))
|
|
199
|
+
msd = numpy.dot(vcn, numpy.dot(Uc, vcn))
|
|
200
|
+
return msd
|
|
201
|
+
|
|
202
|
+
def __repr__(self):
|
|
203
|
+
"""String representation of this Atom."""
|
|
204
|
+
xyz = self.xyz
|
|
205
|
+
s = "%-4s %8.6f %8.6f %8.6f %6.4f" % (self.element, xyz[0], xyz[1], xyz[2], self.occupancy)
|
|
206
|
+
return s
|
|
207
|
+
|
|
208
|
+
def __copy__(self, target=None):
|
|
209
|
+
"""Create a copy of this instance.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
target : Atom, Optional
|
|
214
|
+
An already existing `Atom` object to be updated to a duplicate
|
|
215
|
+
of this `Atom`. Create a new Atom object when not specified.
|
|
216
|
+
This facilitates extension of the `__copy__` method
|
|
217
|
+
in a derived class.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
Atom
|
|
222
|
+
The copy of this object.
|
|
223
|
+
"""
|
|
224
|
+
if target is None:
|
|
225
|
+
target = Atom()
|
|
226
|
+
elif target is self:
|
|
227
|
+
return target
|
|
228
|
+
target.__dict__.update(self.__dict__)
|
|
229
|
+
target.xyz = numpy.copy(self.xyz)
|
|
230
|
+
target._U = numpy.copy(self._U)
|
|
231
|
+
return target
|
|
232
|
+
|
|
233
|
+
# property handlers ------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
x = property(
|
|
236
|
+
lambda self: self.xyz[0],
|
|
237
|
+
lambda self, val: self.xyz.__setitem__(0, val),
|
|
238
|
+
doc="float : fractional coordinate *x*, same as ``xyz[0]``.",
|
|
239
|
+
)
|
|
240
|
+
y = property(
|
|
241
|
+
lambda self: self.xyz[1],
|
|
242
|
+
lambda self, val: self.xyz.__setitem__(1, val),
|
|
243
|
+
doc="float : fractional coordinate *y*, same as ``xyz[1]``.",
|
|
244
|
+
)
|
|
245
|
+
z = property(
|
|
246
|
+
lambda self: self.xyz[2],
|
|
247
|
+
lambda self, val: self.xyz.__setitem__(2, val),
|
|
248
|
+
doc="float : fractional coordinate *z*, same as ``xyz[2]``.",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# xyz_cartn
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def xyz_cartn(self):
|
|
255
|
+
"""numpy.ndarray: Atom position in absolute Cartesian coordinates.
|
|
256
|
+
|
|
257
|
+
This is computed from fractional coordinates `xyz` and the
|
|
258
|
+
current `lattice` setup. Assignment to *xyz_cartn* or
|
|
259
|
+
its components is applied on fractional coordinates `xyz`.
|
|
260
|
+
"""
|
|
261
|
+
if not self.lattice:
|
|
262
|
+
rv = self.xyz
|
|
263
|
+
else:
|
|
264
|
+
rv = _AtomCartesianCoordinates(self)
|
|
265
|
+
return rv
|
|
266
|
+
|
|
267
|
+
@xyz_cartn.setter
|
|
268
|
+
def xyz_cartn(self, value):
|
|
269
|
+
if not self.lattice:
|
|
270
|
+
self.xyz[:] = value
|
|
271
|
+
else:
|
|
272
|
+
self.xyz[:] = self.lattice.fractional(value)
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
# anisotropy
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def anisotropy(self):
|
|
279
|
+
"""bool : Flag for allowing anisotropic displacement parameters.
|
|
280
|
+
|
|
281
|
+
When ``False`` the tensor of thermal displacement parameters `U`
|
|
282
|
+
must be isotropic and only its diagonal elements are taken into
|
|
283
|
+
account.
|
|
284
|
+
"""
|
|
285
|
+
return self._anisotropy
|
|
286
|
+
|
|
287
|
+
@anisotropy.setter
|
|
288
|
+
def anisotropy(self, value):
|
|
289
|
+
if bool(value) is self._anisotropy:
|
|
290
|
+
return
|
|
291
|
+
# convert from isotropic to anisotropic
|
|
292
|
+
if value:
|
|
293
|
+
self._U = self.U
|
|
294
|
+
# otherwise convert from anisotropic to isotropic
|
|
295
|
+
else:
|
|
296
|
+
self._U[0, 0] = self.Uisoequiv
|
|
297
|
+
self._anisotropy = bool(value)
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# U
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def U(self):
|
|
304
|
+
"""numpy.ndarray : The 3x3 matrix of anisotropic atomic displacements.
|
|
305
|
+
|
|
306
|
+
For isotropic displacements (when `anisotropy` is ``False``)
|
|
307
|
+
assignment to *U* uses only the first ``Unew[0, 0]`` element
|
|
308
|
+
and the remaining components of *U* are adjusted to obtain
|
|
309
|
+
isotropic tensor in the active `lattice`.
|
|
310
|
+
|
|
311
|
+
Note
|
|
312
|
+
----
|
|
313
|
+
Elements of the *U* tensor such as ``U[0, 1]`` should be
|
|
314
|
+
considered read-only as setting them directly leads to
|
|
315
|
+
undefined behavior. Use the `U11`, `U22`, ..., or `B11`,
|
|
316
|
+
`B22`, ..., descriptors to set only some *U* components.
|
|
317
|
+
"""
|
|
318
|
+
if not self.anisotropy:
|
|
319
|
+
# for isotropic displacements assume first element
|
|
320
|
+
# to be equal to the displacement value
|
|
321
|
+
lat = self.lattice or cartesian_lattice
|
|
322
|
+
numpy.multiply(self._U[0, 0], lat.isotropicunit, out=self._U)
|
|
323
|
+
return self._U
|
|
324
|
+
|
|
325
|
+
@U.setter
|
|
326
|
+
def U(self, value):
|
|
327
|
+
self._U[:] = value
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
# Uij elements
|
|
331
|
+
|
|
332
|
+
def _get_Uij(self, i, j):
|
|
333
|
+
"""The getter function for the `U11`, `U22`, ..., properties."""
|
|
334
|
+
if self.anisotropy:
|
|
335
|
+
return self._U[i, j]
|
|
336
|
+
lat = self.lattice or cartesian_lattice
|
|
337
|
+
return self._U[0, 0] * lat.isotropicunit[i, j]
|
|
338
|
+
|
|
339
|
+
def _set_Uij(self, i, j, value):
|
|
340
|
+
"""The setter function for the `U11`, `U22`, ..., properties."""
|
|
341
|
+
self._U[i, j] = value
|
|
342
|
+
self._U[j, i] = value
|
|
343
|
+
if not self._anisotropy and i == j != 0:
|
|
344
|
+
self._U[0, 0] = value
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
# _doc_uii, _doc_uij are temporary local variables.
|
|
348
|
+
|
|
349
|
+
_doc_uii = """
|
|
350
|
+
float : The ``U[{0}, {0}]`` component of the displacement tensor `U`.
|
|
351
|
+
|
|
352
|
+
When `anisotropy` is ``False`` setting a new value updates entire
|
|
353
|
+
tensor *U*.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
U11 = property(
|
|
357
|
+
lambda self: self._get_Uij(0, 0), lambda self, value: self._set_Uij(0, 0, value), doc=_doc_uii.format(0)
|
|
358
|
+
)
|
|
359
|
+
U22 = property(
|
|
360
|
+
lambda self: self._get_Uij(1, 1), lambda self, value: self._set_Uij(1, 1, value), doc=_doc_uii.format(1)
|
|
361
|
+
)
|
|
362
|
+
U33 = property(
|
|
363
|
+
lambda self: self._get_Uij(2, 2), lambda self, value: self._set_Uij(2, 2, value), doc=_doc_uii.format(2)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
_doc_uij = """
|
|
367
|
+
float : The ``U[{0}, {1}]`` element of the displacement tensor `U`.
|
|
368
|
+
|
|
369
|
+
Sets ``U[{1}, {0}]`` together with ``U[{0}, {1}]``. Assignment
|
|
370
|
+
has no effect when `anisotropy` is ``False``.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
U12 = property(
|
|
374
|
+
lambda self: self._get_Uij(0, 1), lambda self, value: self._set_Uij(0, 1, value), doc=_doc_uij.format(0, 1)
|
|
375
|
+
)
|
|
376
|
+
U13 = property(
|
|
377
|
+
lambda self: self._get_Uij(0, 2), lambda self, value: self._set_Uij(0, 2, value), doc=_doc_uij.format(0, 2)
|
|
378
|
+
)
|
|
379
|
+
U23 = property(
|
|
380
|
+
lambda self: self._get_Uij(1, 2), lambda self, value: self._set_Uij(1, 2, value), doc=_doc_uij.format(1, 2)
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# clean local variables
|
|
384
|
+
del _doc_uii, _doc_uij
|
|
385
|
+
|
|
386
|
+
# Uisoequiv
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def Uisoequiv(self):
|
|
390
|
+
"""float : The isotropic displacement parameter or an equivalent value.
|
|
391
|
+
|
|
392
|
+
Setting a new value rescales tensor `U` so it yields equivalent
|
|
393
|
+
direction-averaged displacements.
|
|
394
|
+
"""
|
|
395
|
+
if not self.anisotropy:
|
|
396
|
+
return self._U[0, 0]
|
|
397
|
+
if self.lattice is None:
|
|
398
|
+
return numpy.trace(self._U) / 3.0
|
|
399
|
+
lat = self.lattice
|
|
400
|
+
rv = (
|
|
401
|
+
1.0
|
|
402
|
+
/ 3.0
|
|
403
|
+
* (
|
|
404
|
+
self._U[0, 0] * lat.ar * lat.ar * lat.a * lat.a
|
|
405
|
+
+ self._U[1, 1] * lat.br * lat.br * lat.b * lat.b
|
|
406
|
+
+ self._U[2, 2] * lat.cr * lat.cr * lat.c * lat.c
|
|
407
|
+
+ 2 * self._U[0, 1] * lat.ar * lat.br * lat.a * lat.b * lat.cg
|
|
408
|
+
+ 2 * self._U[0, 2] * lat.ar * lat.cr * lat.a * lat.c * lat.cb
|
|
409
|
+
+ 2 * self._U[1, 2] * lat.br * lat.cr * lat.b * lat.c * lat.ca
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
return rv
|
|
413
|
+
|
|
414
|
+
@Uisoequiv.setter
|
|
415
|
+
def Uisoequiv(self, value):
|
|
416
|
+
if self.anisotropy:
|
|
417
|
+
lat = self.lattice or cartesian_lattice
|
|
418
|
+
uequiv = self.Uisoequiv
|
|
419
|
+
if abs(uequiv) < lat._epsilon:
|
|
420
|
+
self._U = value * lat.isotropicunit
|
|
421
|
+
else:
|
|
422
|
+
self._U *= value / uequiv
|
|
423
|
+
else:
|
|
424
|
+
self._U[0, 0] = value
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
# Bij elements
|
|
428
|
+
|
|
429
|
+
# _doc_bii, _doc_bij are local variables.
|
|
430
|
+
|
|
431
|
+
_doc_bii = """
|
|
432
|
+
float : The ``B{0}{0}`` element of the Debye-Waller matrix.
|
|
433
|
+
|
|
434
|
+
This is equivalent to ``8 * pi**2 * U{0}{0}``. When `anisotropy`
|
|
435
|
+
is ``False`` setting a new value updates entire tensor `U`.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
_doc_bij = """
|
|
439
|
+
float : The ``B{0}{1}`` element of the Debye-Waller matrix.
|
|
440
|
+
|
|
441
|
+
This is equivalent to ``8 * pi**2 * U{0}{1}``. Setting a new
|
|
442
|
+
value updates `U` in a symmetric way. Assignment has no effect
|
|
443
|
+
when `anisotropy` is ``False``.
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
B11 = property(
|
|
447
|
+
lambda self: _UtoB * self._get_Uij(0, 0),
|
|
448
|
+
lambda self, value: self._set_Uij(0, 0, _BtoU * value),
|
|
449
|
+
doc=_doc_bii.format(1),
|
|
450
|
+
)
|
|
451
|
+
B22 = property(
|
|
452
|
+
lambda self: _UtoB * self._get_Uij(1, 1),
|
|
453
|
+
lambda self, value: self._set_Uij(1, 1, _BtoU * value),
|
|
454
|
+
doc=_doc_bii.format(2),
|
|
455
|
+
)
|
|
456
|
+
B33 = property(
|
|
457
|
+
lambda self: _UtoB * self._get_Uij(2, 2),
|
|
458
|
+
lambda self, value: self._set_Uij(2, 2, _BtoU * value),
|
|
459
|
+
doc=_doc_bii.format(3),
|
|
460
|
+
)
|
|
461
|
+
B12 = property(
|
|
462
|
+
lambda self: _UtoB * self._get_Uij(0, 1),
|
|
463
|
+
lambda self, value: self._set_Uij(0, 1, _BtoU * value),
|
|
464
|
+
doc=_doc_bij.format(1, 2),
|
|
465
|
+
)
|
|
466
|
+
B13 = property(
|
|
467
|
+
lambda self: _UtoB * self._get_Uij(0, 2),
|
|
468
|
+
lambda self, value: self._set_Uij(0, 2, _BtoU * value),
|
|
469
|
+
doc=_doc_bij.format(1, 3),
|
|
470
|
+
)
|
|
471
|
+
B23 = property(
|
|
472
|
+
lambda self: _UtoB * self._get_Uij(1, 2),
|
|
473
|
+
lambda self, value: self._set_Uij(1, 2, _BtoU * value),
|
|
474
|
+
doc=_doc_bij.format(2, 3),
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# clean local variables
|
|
478
|
+
del _doc_bii, _doc_bij
|
|
479
|
+
|
|
480
|
+
# Bisoequiv
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def Bisoequiv(self):
|
|
484
|
+
"""float : The Debye-Waller isotropic displacement or an equivalent value.
|
|
485
|
+
|
|
486
|
+
This equals ``8 * pi**2 * Uisoequiv``. Setting a new value
|
|
487
|
+
rescales `U` tensor to yield equivalent direction-average of
|
|
488
|
+
Debye-Waller displacements.
|
|
489
|
+
"""
|
|
490
|
+
return _UtoB * self.Uisoequiv
|
|
491
|
+
|
|
492
|
+
@Bisoequiv.setter
|
|
493
|
+
def Bisoequiv(self, value):
|
|
494
|
+
self.Uisoequiv = _BtoU * value
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
# End of class Atom
|
|
499
|
+
|
|
500
|
+
# Local Helpers --------------------------------------------------------------
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class _AtomCartesianCoordinates(numpy.ndarray):
|
|
504
|
+
"""Specialized `numpy.ndarray` for accessing Cartesian coordinates.
|
|
505
|
+
|
|
506
|
+
Inplace assignments to this array are applied on the *xyz* position
|
|
507
|
+
position of owner `Atom` as per the associated `Atom.lattice`.
|
|
508
|
+
|
|
509
|
+
Parameters
|
|
510
|
+
----------
|
|
511
|
+
atom : Atom
|
|
512
|
+
`Atom` instance to be linked to these coordinate array.
|
|
513
|
+
"""
|
|
514
|
+
|
|
515
|
+
def __new__(self, atom):
|
|
516
|
+
"""Create the underlying numpy array base object."""
|
|
517
|
+
return numpy.empty(3, dtype=float).view(self)
|
|
518
|
+
|
|
519
|
+
def __init__(self, atom):
|
|
520
|
+
self._atom = atom
|
|
521
|
+
self.asarray[:] = atom.lattice.cartesian(atom.xyz)
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
@property
|
|
525
|
+
def asarray(self):
|
|
526
|
+
"""ndarray : This array viewed as standard numpy array."""
|
|
527
|
+
return self.view(numpy.ndarray)
|
|
528
|
+
|
|
529
|
+
def __setitem__(self, idx, value):
|
|
530
|
+
"""Set some element or slice of this Cartesian coordinates.
|
|
531
|
+
|
|
532
|
+
This overrides inplace array assignment to update the
|
|
533
|
+
*xyz* fractional coordinate of the linked `Atom`.
|
|
534
|
+
"""
|
|
535
|
+
self.asarray[idx] = value
|
|
536
|
+
self._atom.xyz[:] = self._atom.lattice.fractional(self)
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
def __array_wrap__(self, out_arr, context=None):
|
|
540
|
+
"""Ensure math operations on this type yield standard numpy array."""
|
|
541
|
+
return out_arr.view(numpy.ndarray)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
# End of _AtomCartesianCoordinates
|
|
@@ -0,0 +1,27 @@
|
|
|
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: Chris Farrow, 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
|
+
"""Methods and classes for manipulating `Structure` instances.
|
|
17
|
+
|
|
18
|
+
Package content:
|
|
19
|
+
* supercell -- create a supercell from an existing `Structure`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Import below whatever should be available at package namespace.
|
|
23
|
+
|
|
24
|
+
from diffpy.structure.expansion.supercell_mod import supercell
|
|
25
|
+
|
|
26
|
+
# silence pyflakes checker
|
|
27
|
+
assert supercell
|
|
@@ -0,0 +1,129 @@
|
|
|
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: Chris Farrow
|
|
10
|
+
#
|
|
11
|
+
# See AUTHORS.txt for a list of people who contributed.
|
|
12
|
+
# See LICENSE_DANSE.txt for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
|
|
16
|
+
"""Make a spheroid nanoparticle from a template structure."""
|
|
17
|
+
|
|
18
|
+
from math import ceil
|
|
19
|
+
|
|
20
|
+
from numpy import array
|
|
21
|
+
|
|
22
|
+
from diffpy.structure import Structure
|
|
23
|
+
from diffpy.structure.expansion.shapeutils import findCenter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def makeSphere(S, radius):
|
|
27
|
+
"""Create a spherical nanoparticle.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
S : Structure
|
|
32
|
+
A `Structure` instance.
|
|
33
|
+
radius : float
|
|
34
|
+
Primary equatorial radius (along x-axis).
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
Structure
|
|
39
|
+
A new `Structure` instance.
|
|
40
|
+
"""
|
|
41
|
+
return makeEllipsoid(S, radius)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def makeEllipsoid(S, a, b=None, c=None):
|
|
45
|
+
"""
|
|
46
|
+
Cut a `Structure` out of another one.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
S : Structure
|
|
51
|
+
A `Structure` instance.
|
|
52
|
+
a : float
|
|
53
|
+
Primary equatorial radius (along x-axis).
|
|
54
|
+
b : float, Optional
|
|
55
|
+
Secondary equatorial radius (along y-axis). If `b` is ``None``
|
|
56
|
+
(default), then it is set equal to `a`.
|
|
57
|
+
c : float, Optional
|
|
58
|
+
Polar radius (along z-axis). If `c` is ``None`` (default), then it is
|
|
59
|
+
set equal to `a`.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
Structure :
|
|
64
|
+
A new `Structure` instance.
|
|
65
|
+
"""
|
|
66
|
+
if b is None:
|
|
67
|
+
b = a
|
|
68
|
+
if c is None:
|
|
69
|
+
c = a
|
|
70
|
+
sabc = array([a, b, c])
|
|
71
|
+
|
|
72
|
+
# Create a supercell large enough for the ellipsoid
|
|
73
|
+
frac = S.lattice.fractional(sabc)
|
|
74
|
+
# FIXME - this looks fishy for non-orthogonal lattices
|
|
75
|
+
mno = max(ceil(2 * xi) for xi in frac) * array([1, 1, 1])
|
|
76
|
+
# Make the supercell
|
|
77
|
+
from diffpy.structure.expansion import supercell
|
|
78
|
+
|
|
79
|
+
newS = supercell(S, mno)
|
|
80
|
+
lat = newS.lattice
|
|
81
|
+
|
|
82
|
+
# Find the central atom
|
|
83
|
+
ncenter = findCenter(newS)
|
|
84
|
+
|
|
85
|
+
cxyz = lat.cartesian(newS[ncenter].xyz)
|
|
86
|
+
|
|
87
|
+
delList = []
|
|
88
|
+
N = len(newS)
|
|
89
|
+
j = N
|
|
90
|
+
for i in range(N):
|
|
91
|
+
j -= 1
|
|
92
|
+
|
|
93
|
+
# Calculate (x/a)**2 + (y/b)**2 + (z/c)**2
|
|
94
|
+
xyz = lat.cartesian(newS[j].xyz)
|
|
95
|
+
darray = ((xyz - cxyz) / sabc) ** 2
|
|
96
|
+
d = sum(darray) ** 0.5
|
|
97
|
+
|
|
98
|
+
# Discard atom if (x/a)**2 + (y/b)**2 + (z/c)**2 > 1
|
|
99
|
+
if d > 1:
|
|
100
|
+
delList.append(j)
|
|
101
|
+
|
|
102
|
+
for i in delList:
|
|
103
|
+
newS.pop(i)
|
|
104
|
+
|
|
105
|
+
return newS
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ----------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
import os.path
|
|
112
|
+
|
|
113
|
+
datadir = "../../tests/testdata"
|
|
114
|
+
S = Structure()
|
|
115
|
+
S.read(os.path.join(datadir, "CdSe_bulk.stru"), "pdffit")
|
|
116
|
+
newS = makeEllipsoid(S, 12)
|
|
117
|
+
newS.write("CdSe_d24.stru", "pdffit")
|
|
118
|
+
newS = makeEllipsoid(S, 20, 10, 10)
|
|
119
|
+
newS.write("CdSe_a20_b10_c10.stru", "pdffit")
|
|
120
|
+
newS = makeEllipsoid(S, 20, 15, 10)
|
|
121
|
+
newS.write("CdSe_a20_b15_c10.stru", "pdffit")
|
|
122
|
+
S = Structure()
|
|
123
|
+
S.read(os.path.join(datadir, "Ni.stru"), "pdffit")
|
|
124
|
+
newS = makeEllipsoid(S, 10)
|
|
125
|
+
newS.write("Ni_d20.stru", "pdffit")
|
|
126
|
+
newS = makeEllipsoid(S, 20, 4)
|
|
127
|
+
newS.write("Ni_a20_b4_c20.stru", "pdffit")
|
|
128
|
+
newS = makeEllipsoid(S, 20, 15, 10)
|
|
129
|
+
newS.write("Ni_a20_b15_c10.stru", "pdffit")
|