Package diffpy :: Package Structure :: Module atom
[hide private]
[frames] | no frames]

Source Code for Module diffpy.Structure.atom

  1  ############################################################################## 
  2  # 
  3  # Structure         by DANSE Diffraction group 
  4  #                   Simon J. L. Billinge 
  5  #                   (c) 2006 trustees of the Michigan State University. 
  6  #                   All rights reserved. 
  7  # 
  8  # File coded by:    Pavol Juhas 
  9  # 
 10  # See AUTHORS.txt for a list of people who contributed. 
 11  # See LICENSE.txt for license information. 
 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  # conversion constants 
 24  _BtoU = 1.0/(8 * numpy.pi**2) 
 25  _UtoB = 1.0/_BtoU 
 26   
27 -class CartesianCoordinatesArray(numpy.ndarray):
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
37 - def __new__(self, lattice, xyz):
38 return numpy.zeros(3, dtype=float).view(self)
39
40 - def __init__(self, lattice, xyz):
41 self.lattice = lattice 42 self.xyz = xyz 43 self[:] = self.lattice.cartesian(self.xyz) 44 pass
45
46 - def __setitem__(self, idx, value):
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 # End of CartesianCoordinatesArray 57 58
59 -class Atom(object):
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 # declare data members 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 # assign them as needed 119 if isinstance(atype, Atom): 120 atype_dup = atype.__copy__() 121 self.__dict__.update(atype_dup.__dict__) 122 else: 123 self.element = atype 124 # take care of remaining arguments 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
134 - def msdLat(self, vl):
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 # here we need to calculate msd 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
153 - def msdCart(self, vc):
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 # here we need to calculate msd 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
170 - def __repr__(self):
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
177 - def __copy__(self):
178 """Return a copy of this instance. 179 """ 180 adup = Atom(self.element) 181 adup.__dict__.update(self.__dict__) 182 # create copies for what should be copied 183 adup.xyz = numpy.array(self.xyz) 184 adup._U = numpy.array(self._U) 185 return adup
186 187 #################################################################### 188 # property handlers 189 #################################################################### 190 191 # xyz_cartn 192
193 - def _get_xyz_cartn(self):
194 if not self.lattice: 195 rv = self.xyz 196 else: 197 rv = CartesianCoordinatesArray(self.lattice, self.xyz) 198 return rv
199
200 - def _set_xyz_cartn(self, value):
201 if not self.lattice: 202 self.xyz[:] = value 203 else: 204 self.xyz = self.lattice.fractional(value) 205 return
206 207 xyz_cartn = property(_get_xyz_cartn, _set_xyz_cartn, doc = 208 """absolute Cartesian coordinates of an atom 209 """ ) 210 211 # anisotropy 212
213 - def _get_anisotropy(self):
214 # determine when unknown 215 if self._anisotropy is None: 216 Uisoequiv = self._get_Uisoequiv() 217 # calculate isotropic tensor Uisoij 218 lat = self.lattice or cartesian_lattice 219 Tu = lat.recnormbase 220 Uisoij = numpy.dot(numpy.transpose(Tu), Uisoequiv*Tu) 221 # compare with new value 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
227 - def _set_anisotropy(self, value):
228 if bool(value) == self._anisotropy: return 229 # convert from isotropic to anisotropic 230 if value: 231 self._U = self._get_U() 232 # otherwise convert from anisotropic to isotropic 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 # U 244
245 - def _get_U(self):
246 # for isotropic non-synced case we need to 247 # calculate _U from _Uisoequiv 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 # handle can be changed by the caller 254 self._anisotropy = None 255 return self._U
256
257 - def _set_U(self, value):
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 # Uij elements 266
267 - def _get_Uij(self, i, j):
268 Uij = self._get_U() 269 return Uij[i,j]
270
271 - def _set_Uij(self, i, j, value):
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 # Uisoequiv 296
297 - def _get_Uisoequiv(self):
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
312 - def _set_Uisoequiv(self, value):
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 # scale if Uequiv is not zero 319 if numpy.fabs(Uequiv) > double_eps: 320 self._U *= value/Uequiv 321 # otherwise just convert from Uiso value 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 # Bij elements 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 # Bisoequiv 354
355 - def _get_Bisoequiv(self):
356 return _UtoB * self._get_Uisoequiv()
357
358 - def _set_Bisoequiv(self, value):
359 self._set_Uisoequiv(_BtoU*value)
360 361 Bisoequiv = property(_get_Bisoequiv, _set_Bisoequiv, doc = 362 "Debye-Waler isotropic thermal displacement or equivalent value")
363 364 # End of class Atom 365