1
2
3
4
5 """Classes for handling unit cell transformation.
6 """
7 import math
8
9 try:
10 import numpy
11 try:
12 from numpy.oldnumeric import linear_algebra as linalg
13 except ImportError:
14 from numpy.linalg import old as linalg
15 except ImportError:
16 import NumericCompat as numpy
17 from NumericCompat import linalg
18
19 import AtomMath
20 import SpaceGroups
21
22
24 """Class for storing and performing calculations on unit cell
25 parameters. The constructor expects alpha, beta, and gamma to be in
26 degrees, but converts them to radians. Set angle_units = "rad" if
27 the alpha, beta, and gamma are already in radians.
28 """
29 - def __init__(self,
30 a = 1.0,
31 b = 1.0,
32 c = 1.0,
33 alpha = 90.0,
34 beta = 90.0,
35 gamma = 90.0,
36 space_group = "P1",
37 angle_units = "deg"):
38
39 assert angle_units == "deg" or angle_units == "rad"
40
41 self.a = a
42 self.b = b
43 self.c = c
44
45 if angle_units == "deg":
46 self.alpha = math.radians(alpha)
47 self.beta = math.radians(beta)
48 self.gamma = math.radians(gamma)
49 elif angle_units == "rad":
50 self.alpha = alpha
51 self.beta = beta
52 self.gamma = gamma
53
54 self.space_group = SpaceGroups.GetSpaceGroup(space_group)
55 self.orth_to_frac = self.calc_fractionalization_matrix()
56 self.frac_to_orth = self.calc_orthogonalization_matrix()
57
58
59 assert numpy.allclose(self.orth_to_frac, linalg.inverse(self.frac_to_orth))
60
62 alpha = math.degrees(self.alpha)
63 beta = math.degrees(self.beta)
64 gamma = math.degrees(self.gamma)
65
66 return "UnitCell(a=%f, b=%f, c=%f, alpha=%f, beta=%f, gamma=%f)" % (
67 self.a, self.b, self.c, alpha, beta, gamma)
68
70 """Returns the alpha angle in degrees.
71 """
72 return math.degrees(self.alpha)
73
75 """Returns the beta angle in degrees.
76 """
77 return math.degrees(self.beta)
78
80 """Returns the gamma angle in degrees.
81 """
82 return math.degrees(self.gamma)
83
85 """Calculates the volume of the rhombohedral created by the
86 unit vectors a1/|a1|, a2/|a2|, a3/|a3|.
87 """
88 cos_alpha = math.cos(self.alpha)
89 cos_beta = math.cos(self.beta)
90 cos_gamma = math.cos(self.gamma)
91 return math.sqrt(1 -
92 (cos_alpha * cos_alpha) -
93 (cos_beta * cos_beta) -
94 (cos_gamma * cos_gamma) +
95 (2 * cos_alpha * cos_beta * cos_gamma) )
96
98 """Calculates the volume of the unit cell.
99 """
100 return self.a * self.b * self.c * self.calc_v()
101
103 """Corresponding reciprocal unit cell.
104 """
105 V = self.calc_volume()
106
107 sin_alpha = math.sin(self.alpha)
108 sin_beta = math.sin(self.beta)
109 sin_gamma = math.sin(self.gamma)
110
111 cos_alpha = math.cos(self.alpha)
112 cos_beta = math.cos(self.beta)
113 cos_gamma = math.cos(self.gamma)
114
115 ra = (self.b * self.c * sin_alpha) / V
116 rb = (self.a * self.c * sin_beta) / V
117 rc = (self.a * self.b * sin_gamma) / V
118
119 ralpha = math.acos(
120 (cos_beta * cos_gamma - cos_alpha) / (sin_beta * sin_gamma))
121 rbeta = math.acos(
122 (cos_alpha * cos_gamma - cos_beta) / (sin_alpha * sin_gamma))
123 rgamma = math.acos(
124 (cos_alpha * cos_beta - cos_gamma) / (sin_alpha * sin_beta))
125
126 return UnitCell(ra, rb, rc, ralpha, rbeta, rgamma)
127
129 """Cartesian to fractional coordinates.
130 """
131 sin_alpha = math.sin(self.alpha)
132 sin_beta = math.sin(self.beta)
133 sin_gamma = math.sin(self.gamma)
134
135 cos_alpha = math.cos(self.alpha)
136 cos_beta = math.cos(self.beta)
137 cos_gamma = math.cos(self.gamma)
138
139 v = self.calc_v()
140
141 f11 = self.a
142 f12 = self.b * cos_gamma
143 f13 = self.c * cos_beta
144 f22 = self.b * sin_gamma
145 f23 = (self.c * (cos_alpha - cos_beta * cos_gamma)) / (sin_gamma)
146 f33 = (self.c * v) / sin_gamma
147
148 orth_to_frac = numpy.array([ [f11, f12, f13],
149 [0.0, f22, f23],
150 [0.0, 0.0, f33] ], float)
151
152 return orth_to_frac
153
155 """Fractional to Cartesian coordinates.
156 """
157 sin_alpha = math.sin(self.alpha)
158 sin_beta = math.sin(self.beta)
159 sin_gamma = math.sin(self.gamma)
160
161 cos_alpha = math.cos(self.alpha)
162 cos_beta = math.cos(self.beta)
163 cos_gamma = math.cos(self.gamma)
164
165 v = self.calc_v()
166
167 o11 = 1.0 / self.a
168 o12 = - cos_gamma / (self.a * sin_gamma)
169 o13 = (cos_gamma * cos_alpha - cos_beta) / (self.a * v * sin_gamma)
170 o22 = 1.0 / (self.b * sin_gamma)
171 o23 = (cos_gamma * cos_beta - cos_alpha) / (self.b * v * sin_gamma)
172 o33 = sin_gamma / (self.c * v)
173
174 frac_to_orth = numpy.array([ [o11, o12, o13],
175 [0.0, o22, o23],
176 [0.0, 0.0, o33] ], float)
177
178 return frac_to_orth
179
181 """Calculates and returns the fractional coordinate vector of
182 orthogonal vector v.
183 """
184 return numpy.dot(self.orth_to_frac, v)
185
187 """Calculates and returns the orthogonal coordinate vector of
188 fractional vector v.
189 """
190 return numpy.dot(self.frac_to_orth, v)
191
193 """Calculates the orthogonal space symmetry operation (return SymOp)
194 given a fractional space symmetry operation (argument SymOp).
195 """
196 RF = numpy.dot(symop.R, self.orth_to_frac)
197 ORF = numpy.dot(self.frac_to_orth, RF)
198 Ot = numpy.dot(self.frac_to_orth, symop.t)
199 return SpaceGroups.SymOp(ORF, Ot)
200
202 """Calculates the orthogonal space symmetry operation (return SymOp)
203 given a fractional space symmetry operation (argument SymOp).
204 """
205 RF = numpy.dot(symop.R, self.orth_to_frac)
206 ORF = numpy.dot(self.frac_to_orth, RF)
207 Rt = numpy.dot(symop.R, symop.t)
208 ORt = numpy.dot(self.frac_to_orth, Rt)
209
210 return SpaceGroups.SymOp(ORF, ORt)
211
213 """Returns the cell integer 3-Tuple where the xyz fractional
214 coordinates are located.
215 """
216 if xyz[0] < 0.0:
217 cx = int(xyz[0] - 1.0)
218 else:
219 cx = int(xyz[0] + 1.0)
220
221 if xyz[1] < 0.0:
222 cy = int(xyz[1] - 1.0)
223 else:
224 cy = int(xyz[1] + 1.0)
225
226 if xyz[2] < 0.0:
227 cz = int(xyz[2] - 1.0)
228 else:
229 cz = int(xyz[2] + 1.0)
230
231 return (cx, cy, cz)
232
234 """Yields 3-tuple integer translations over a 3x3x3 cube used by
235 other methods for searching nearby unit cells.
236 """
237 cube = (-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0)
238
239 for i in cube:
240 for j in cube:
241 for k in cube:
242 yield i, j, k
243
245 """Iterate over the orthogonal-space symmetry operations which will
246 place a symmetry related structure near the argument struct.
247 """
248
249 n = 0
250 cent = numpy.zeros(3, float)
251 for atm in struct.iter_all_atoms():
252 n += 1
253 cent += atm.position
254 centroid = cent / n
255
256 ccell = self.calc_cell(self.calc_orth_to_frac(centroid))
257 centroid_cell = numpy.array(ccell, float)
258
259
260
261 max_dist = 0.0
262 for frag in struct.iter_amino_acids():
263 for atm in frag.iter_atoms():
264 dist = AtomMath.length(atm.position - centroid)
265 max_dist = max(max_dist, dist)
266 max_dist2 = 2.0 * max_dist + 5.0
267
268 for symop in self.space_group.iter_symops():
269 for i, j, k in self.cell_search_iter():
270
271 cell_t = numpy.array([i, j, k], float)
272 symop_t = SpaceGroups.SymOp(symop.R, symop.t+cell_t)
273
274 xyz = self.calc_orth_to_frac(centroid)
275 xyz_symm = symop_t(xyz)
276 centroid2 = self.calc_frac_to_orth(xyz_symm)
277
278 if AtomMath.length(centroid - centroid2) <= max_dist2:
279 yield self.calc_orth_symop(symop_t)
280
281
283 """Returns a string for a rotation/translation pair in a readable form.
284 """
285 x = "[%6.3f %6.3f %6.3f %6.3f]\n" % (
286 R[0,0], R[0,1], R[0,2], T[0])
287 x += "[%6.3f %6.3f %6.3f %6.3f]\n" % (
288 R[1,0], R[1,1], R[1,2], T[1])
289 x += "[%6.3f %6.3f %6.3f %6.3f]\n" % (
290 R[2,0], R[2,1], R[2,2], T[2])
291
292 return x
293
294
295
297 print "================================================="
298 print "TEST CASE #1: Triclinic unit cell"
299 print
300
301 uc = UnitCell(7.877, 7.210, 7.891, 105.563, 116.245, 79.836)
302
303 e = numpy.array([[1.0, 0.0, 0.0],
304 [0.0, 1.0, 0.0],
305 [0.0, 0.0, 1.0]], float)
306
307
308 print uc
309 print "volume = ", uc.calc_v()
310 print "cell volume = ", uc.calc_volume()
311 print "fractionalization matrix =\n", uc.calc_fractionalization_matrix()
312 print "orthogonalization matrix =\n", uc.calc_orthogonalization_matrix()
313
314 print "orth * e =\n", numpy.dot(
315 uc.calc_orthogonalization_matrix(), e)
316
317
318 print "calc_frac_to_orth"
319 vlist = [
320 numpy.array([0.0, 0.0, 0.0]),
321 numpy.array([0.5, 0.5, 0.5]),
322 numpy.array([1.0, 1.0, 1.0]),
323 numpy.array([-0.13614, 0.15714, -0.07165]) ]
324
325 for v in vlist:
326 ov = uc.calc_frac_to_orth(v)
327 v2 = uc.calc_orth_to_frac(ov)
328 print "----"
329 print " ",v
330 print " ",ov
331 print " ",v2
332 print "----"
333
334
335 print "================================================="
336
337 print
338
339 print "================================================="
340 print "TEST CASE #2: Reciprocal of above unit cell "
341 print
342
343 ruc = uc.calc_reciprocal_unit_cell()
344 print ruc
345 print "volume = ", ruc.calc_v()
346 print "cell volume = ", ruc.calc_volume()
347
348 print "================================================="
349
350 print
351
352 print "================================================="
353 print "TEST CASE #3: Orthogonal space symmetry operations"
354
355 unitx = UnitCell(a = 64.950,
356 b = 64.950,
357 c = 68.670,
358 alpha = 90.00,
359 beta = 90.00,
360 gamma = 120.00,
361 space_group = "P 32 2 1")
362 print unitx
363 print
364
365 for symop in unitx.space_group.iter_symops():
366 print "Fractional Space SymOp:"
367 print symop
368 print "Orthogonal Space SymOp:"
369 print unitx.calc_orth_symop(symop)
370 print
371
372 if __name__ == "__main__":
373 test_module()
374
375