1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """class Atom for storing properties of a single atom"""
16
17 __id__ = "$Id: atom.py 2825 2009-03-09 04:33:12Z juhas $"
18
19 import numpy
20 from diffpy.Structure.lattice import cartesian as cartesian_lattice
21 from diffpy.Structure import IsotropyError
22
23
24 _BtoU = 1.0/(8 * numpy.pi**2)
25 _UtoB = 1.0/_BtoU
26
28 """Helper array for accessing Cartesian coordinates.
29 Converts and updates related array of corresponding fractional
30 coordinates.
31
32 Data members:
33 lattice -- instance of Lattice defining fractional coordinates
34 xyz -- instance of numpy.array storing fractional coordinates
35 """
36
38 return numpy.zeros(3, dtype=float).view(self)
39
45
47 """Set idx-th coordinate and update linked self.xyz
48
49 idx -- index in xyz array
50 value -- new value of x, y or z
51 """
52 numpy.ndarray.__setitem__(self, idx, value)
53 self.xyz[:] = self.lattice.fractional(self)
54 return
55
56
57
58
60 """Atom --> class for storing atom information
61
62 Data members:
63 element -- type of the atom
64 xyz -- fractional coordinates
65 name -- atom label
66 occupancy -- fractional occupancy
67 xyz_cartn -- absolute Cartesian coordinates, property synced with xyz
68 anisotropy -- flag for anisotropic thermal displacements, property
69 U -- anisotropic thermal displacement tensor, property
70 Uij -- elements of U tensor, where i, j are from (1, 2, 3),
71 property
72 Uisoequiv -- isotropic thermal displacement or equivalent value,
73 property
74 Bisoequiv -- Debye-Waler isotropic temperature factor or equivalent
75 value, property
76 lattice -- coordinate system for fractional coordinates,
77 an instance of Lattice or None for Cartesian system
78
79 Private data:
80 _U -- storage of U property, 3x3 numpy matrix
81 _Uisoequiv -- storage of Uisoequiv property, float
82 _anisotropy -- storage of anisotropy property, bool or
83 None when not determined
84 _Uijsynced -- flag for consistency of _U with _Uisoequiv,
85 it is meaningful only for isotropic atoms
86
87 Class data:
88 tol_anisotropy -- tolerated U matrix deviation for isotropic atom
89 """
90
91 tol_anisotropy = 1.0e-6
92
93 - def __init__(self, atype=None, xyz=None, name=None, occupancy=None,
94 anisotropy=None, U=None, Uisoequiv=None, lattice=None):
95 """Create atom of a specified type at given lattice coordinates.
96 Atom(a) creates a copy of Atom instance a.
97
98 atype -- element symbol string or Atom instance
99 xyz -- fractional coordinates
100 name -- atom label
101 occupancy -- fractional occupancy
102 anisotropy -- flag for anisotropic thermal displacements
103 U -- anisotropic thermal displacement tensor, property
104 Uisoequiv -- isotropic thermal displacement or equivalent value,
105 property
106 lattice -- coordinate system for fractional coordinates
107 """
108
109 self.element = None
110 self.xyz = numpy.zeros(3, dtype=float)
111 self.name = ''
112 self.occupancy = 1.0
113 self._anisotropy = None
114 self._U = numpy.zeros((3,3), dtype=float)
115 self._Uisoequiv = 0.0
116 self._Usynced = True
117 self.lattice = None
118
119 if isinstance(atype, Atom):
120 atype_dup = atype.__copy__()
121 self.__dict__.update(atype_dup.__dict__)
122 else:
123 self.element = atype
124
125 if xyz is not None: self.xyz[:] = xyz
126 if name is not None: self.name = name
127 if occupancy is not None: self.occupancy = float(occupancy)
128 if anisotropy is not None: self._anisotropy = bool(anisotropy)
129 if U is not None: self._U = U
130 if Uisoequiv is not None: self._Uisoequiv = Uisoequiv
131 if lattice is not None: self.lattice = lattice
132 return
133
135 """mean square displacement of an atom along lattice vector
136
137 vl -- vector in lattice coordinates
138
139 return mean square displacement
140 """
141 if not self.anisotropy: return self._Uisoequiv
142
143 lat = self.lattice or cartesian_lattice
144 vln = numpy.array(vl, dtype=float)/lat.norm(vl)
145 G = lat.metrics
146 rhs = numpy.array([ G[0]*lat.ar,
147 G[1]*lat.br,
148 G[2]*lat.cr ], dtype=float)
149 rhs = numpy.dot(rhs, vln)
150 msd = numpy.dot(rhs, numpy.dot(self._U, rhs))
151 return msd
152
154 """mean square displacement of an atom along cartesian vector
155
156 vc -- vector in absolute cartesian coordinates
157
158 return mean square displacement
159 """
160 if not self.anisotropy: return self._Uisoequiv
161
162 lat = self.lattice or cartesian_lattice
163 vcn = numpy.array(vc, dtype=float)
164 vcn /= numpy.sqrt(numpy.sum(vcn**2))
165 F1 = lat.normbase
166 Uc = numpy.dot(numpy.transpose(F1), numpy.dot(self._U, F1))
167 msd = numpy.dot(vcn, numpy.dot(Uc, vcn))
168 return msd
169
171 """simple string representation"""
172 xyz = self.xyz
173 s = "%-4s %8.6f %8.6f %8.6f %6.4f" % \
174 (self.element, xyz[0], xyz[1], xyz[2], self.occupancy)
175 return s
176
178 """Return a copy of this instance.
179 """
180 adup = Atom(self.element)
181 adup.__dict__.update(self.__dict__)
182
183 adup.xyz = numpy.array(self.xyz)
184 adup._U = numpy.array(self._U)
185 return adup
186
187
188
189
190
191
192
199
206
207 xyz_cartn = property(_get_xyz_cartn, _set_xyz_cartn, doc =
208 """absolute Cartesian coordinates of an atom
209 """ )
210
211
212
214
215 if self._anisotropy is None:
216 Uisoequiv = self._get_Uisoequiv()
217
218 lat = self.lattice or cartesian_lattice
219 Tu = lat.recnormbase
220 Uisoij = numpy.dot(numpy.transpose(Tu), Uisoequiv*Tu)
221
222 maxUdiff = numpy.max(numpy.fabs(self._U - Uisoij))
223 self._anisotropy = maxUdiff > Atom.tol_anisotropy
224 self._Uijsynced = False
225 return self._anisotropy
226
228 if bool(value) == self._anisotropy: return
229
230 if value:
231 self._U = self._get_U()
232
233 else:
234 self._Uisoequiv = self._get_Uisoequiv()
235 self._Uijsynced = False
236 self._anisotropy = bool(value)
237 return
238
239 anisotropy = property(_get_anisotropy, _set_anisotropy, doc =
240 """flag for anisotropic thermal displacements.
241 """ )
242
243
244
246
247
248 if self._anisotropy is False and not self._Uijsynced:
249 lat = self.lattice or cartesian_lattice
250 Tu = lat.recnormbase
251 self._U = numpy.dot(numpy.transpose(Tu), self._Uisoequiv*Tu)
252 self._Uijsynced = True
253
254 self._anisotropy = None
255 return self._U
256
258 self._anisotropy = None
259 self._U = numpy.array(value, dtype=float)
260 return
261
262 U = property(_get_U, _set_U, doc =
263 "anisotropic thermal displacement tensor.")
264
265
266
268 Uij = self._get_U()
269 return Uij[i,j]
270
272 self._anisotropy = None
273 self._U[i,j] = value
274 self._U[j,i] = value
275
276 U11 = property(lambda self: self._get_Uij(0, 0),
277 lambda self, value: self._set_Uij(0, 0, value), doc =
278 "U11 element of anisotropic displacement tensor")
279 U22 = property(lambda self: self._get_Uij(1, 1),
280 lambda self, value: self._set_Uij(1, 1, value), doc =
281 "U22 element of anisotropic displacement tensor")
282 U33 = property(lambda self: self._get_Uij(2, 2),
283 lambda self, value: self._set_Uij(2, 2, value), doc =
284 "U33 element of anisotropic displacement tensor")
285 U12 = property(lambda self: self._get_Uij(0, 1),
286 lambda self, value: self._set_Uij(0, 1, value), doc =
287 "U12 element of anisotropic displacement tensor")
288 U13 = property(lambda self: self._get_Uij(0, 2),
289 lambda self, value: self._set_Uij(0, 2, value), doc =
290 "U13 element of anisotropic displacement tensor")
291 U23 = property(lambda self: self._get_Uij(1, 2),
292 lambda self, value: self._set_Uij(1, 2, value), doc =
293 "U23 element of anisotropic displacement tensor")
294
295
296
298 if self._anisotropy is None or self._anisotropy is True:
299 lat = self.lattice or cartesian_lattice
300 Uequiv = (
301 self._U[0,0]*lat.ar*lat.ar*lat.a*lat.a +
302 self._U[1,1]*lat.br*lat.br*lat.b*lat.b +
303 self._U[2,2]*lat.cr*lat.cr*lat.c*lat.c +
304 2*self._U[0,1]*lat.ar*lat.br*lat.a*lat.b*lat.cg +
305 2*self._U[0,2]*lat.ar*lat.cr*lat.a*lat.c*lat.cb +
306 2*self._U[1,2]*lat.br*lat.cr*lat.b*lat.c*lat.ca ) / 3.0
307 self._Uisoequiv = Uequiv
308 else:
309 self._Uisoequiv = self._U[0,0]
310 return self._Uisoequiv
311
313 double_eps = (1.0 + numpy.sqrt(2.0**-52)) - 1.0
314 self._Uisoequiv = float(value)
315 self._Uijsynced = False
316 if self._get_anisotropy():
317 Uequiv = self._get_Uisoequiv()
318
319 if numpy.fabs(Uequiv) > double_eps:
320 self._U *= value/Uequiv
321
322 else:
323 lat = self.lattice or cartesian_lattice
324 Tu = lat.recnormbase
325 self._U = numpy.dot(numpy.transpose(Tu), value*Tu)
326 self._Uijsynced = True
327 return
328
329 Uisoequiv = property(_get_Uisoequiv, _set_Uisoequiv, doc =
330 "isotropic thermal displacement or equivalent value")
331
332
333
334 B11 = property(lambda self: _UtoB*self._get_Uij(0, 0),
335 lambda self, value: self._set_Uij(0, 0, _BtoU*value), doc =
336 "B11 element of Debye-Waler displacement tensor")
337 B22 = property(lambda self: _UtoB*self._get_Uij(1, 1),
338 lambda self, value: self._set_Uij(1, 1, _BtoU*value), doc =
339 "B22 element of Debye-Waler displacement tensor")
340 B33 = property(lambda self: _UtoB*self._get_Uij(2, 2),
341 lambda self, value: self._set_Uij(2, 2, _BtoU*value), doc =
342 "B33 element of Debye-Waler displacement tensor")
343 B12 = property(lambda self: _UtoB*self._get_Uij(0, 1),
344 lambda self, value: self._set_Uij(0, 1, _BtoU*value), doc =
345 "B12 element of Debye-Waler displacement tensor")
346 B13 = property(lambda self: _UtoB*self._get_Uij(0, 2),
347 lambda self, value: self._set_Uij(0, 2, _BtoU*value), doc =
348 "B13 element of Debye-Waler displacement tensor")
349 B23 = property(lambda self: _UtoB*self._get_Uij(1, 2),
350 lambda self, value: self._set_Uij(1, 2, _BtoU*value), doc =
351 "B23 element of Debye-Waler displacement tensor")
352
353
354
357
360
361 Bisoequiv = property(_get_Bisoequiv, _set_Bisoequiv, doc =
362 "Debye-Waler isotropic thermal displacement or equivalent value")
363
364
365