1
2
3
4
5 """Mathematical operations performed on mmLib.Strcuture.Atom objects.
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 Constants
20
21
22
23
24
26 """Calculates the length of u.
27 """
28 return math.sqrt(numpy.dot(u, u))
29
31 """Returns the normalized vector along u.
32 """
33 return u/math.sqrt(numpy.dot(u, u))
34
36 """Cross product of u and v:
37 Cross[u,v] = {-u3 v2 + u2 v3, u3 v1 - u1 v3, -u2 v1 + u1 v2}
38 """
39 return numpy.array([ u[1]*v[2] - u[2]*v[1],
40 u[2]*v[0] - u[0]*v[2],
41 u[0]*v[1] - u[1]*v[0] ], float)
42
43
44
45
46
48 """Returns the cross product of two vectors. Should be identical to the
49 output of numpy.cross(u, v).
50 """
51 return(u[1]*v[2] - v[1]*u[2],
52 u[2]*v[0] - v[2]*u[0],
53 u[0]*v[1] - v[0]*u[1])
54
56 """Returns the dot product of two vectors. Should be identical to the
57 output of numpy.dot(u, v).
58 """
59 return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]
60
62 """Returns the inverse of a 3x3 matrix. Should be identical to the
63 output of numpy.linalg.inv(u).
64 """
65 inv = [[0,0,0],[0,0,0],[0,0,0]]
66 c = []
67 c.append(internal_cross(u[1], u[2]))
68 c.append(internal_cross(u[2], u[0]))
69 c.append(internal_cross(u[0], u[1]))
70
71 d = internal_dot(u[0], c[0])
72 if(abs(d) < 1e-30):
73 return 0.0, inv
74
75 for i in range(0,3):
76 for j in range(0,3):
77 inv[i][j] = float(c[j][i]) / float(d)
78
79 return d, inv
80
81
82
83
84
86 """Return a rotation matrix based on the Euler angles alpha,
87 beta, and gamma in radians.
88 """
89 cosA = math.cos(alpha)
90 cosB = math.cos(beta)
91 cosG = math.cos(gamma)
92
93 sinA = math.sin(alpha)
94 sinB = math.sin(beta)
95 sinG = math.sin(gamma)
96
97 R = numpy.array(
98 [[cosB*cosG, cosG*sinA*sinB-cosA*sinG, cosA*cosG*sinB+sinA*sinG],
99 [cosB*sinG, cosA*cosG+sinA*sinB*sinG, cosA*sinB*sinG-cosG*sinA ],
100 [-sinB, cosB*sinA, cosA*cosB ]], float)
101
102 assert numpy.allclose(linalg.determinant(R), 1.0)
103 return R
104
106 """Return a rotation matrix caused by a right hand rotation of theta
107 radians around vector u.
108 """
109 if numpy.allclose(theta, 0.0) or numpy.allclose(numpy.dot(u,u), 0.0):
110 return numpy.identity(3, float)
111
112 x, y, z = normalize(u)
113 sa = math.sin(theta)
114 ca = math.cos(theta)
115
116 R = numpy.array(
117 [[1.0+(1.0-ca)*(x*x-1.0), -z*sa+(1.0-ca)*x*y, y*sa+(1.0-ca)*x*z],
118 [z*sa+(1.0-ca)*x*y, 1.0+(1.0-ca)*(y*y-1.0), -x*sa+(1.0-ca)*y*z],
119 [-y*sa+(1.0-ca)*x*z, x*sa+(1.0-ca)*y*z, 1.0+(1.0-ca)*(z*z-1.0)]], float)
120
121 try:
122 assert numpy.allclose(linalg.determinant(R), 1.0)
123 except AssertionError:
124 print "rmatrixu(%s, %f) determinant(R)=%f" % (
125 u, theta, linalg.determinant(R))
126 raise
127
128 return R
129
130
132 """Returns the displacement matrix based on rotation about Euler
133 angles alpha, beta, and gamma.
134 """
135 return rmatrix(alpha, beta, gamma) - numpy.identity(3, float)
136
138 """Return a displacement matrix caused by a right hand rotation of theta
139 radians around vector u.
140 """
141 return rmatrixu(u, theta) - numpy.identity(3, float)
142
144 """Return a rotation matrix which transforms the coordinate system
145 such that the vector vec is aligned along the z axis.
146 """
147 u, v, w = normalize(vec)
148
149 d = math.sqrt(u*u + v*v)
150
151 if d != 0.0:
152 Rxz = numpy.array([ [ u/d, v/d, 0.0 ],
153 [ -v/d, u/d, 0.0 ],
154 [ 0.0, 0.0, 1.0 ] ], float)
155 else:
156 Rxz = numpy.identity(3, float)
157
158
159 Rxz2z = numpy.array([ [ w, 0.0, -d],
160 [ 0.0, 1.0, 0.0],
161 [ d, 0.0, w] ], float)
162
163 R = numpy.dot(Rxz2z, Rxz)
164
165 try:
166 assert numpy.allclose(linalg.determinant(R), 1.0)
167 except AssertionError:
168 print "rmatrixz(%s) determinant(R)=%f" % (vec, linalg.determinant(R))
169 raise
170
171 return R
172
173
174
175
177 """Returns a quaternion representing the right handed rotation of theta
178 radians about vector u. Quaternions are typed as Numeric Python
179 numpy.arrays of length 4.
180 """
181 u = normalize(u)
182
183 half_sin_theta = math.sin(theta / 2.0)
184
185 x = u[0] * half_sin_theta
186 y = u[1] * half_sin_theta
187 z = u[2] * half_sin_theta
188 w = math.cos(theta / 2.0)
189
190
191 q = numpy.array((x, y, z, w), float)
192 assert numpy.allclose(math.sqrt(numpy.dot(q,q)), 1.0)
193
194 return q
195
197 """Adds quaternions q1 and q2. Quaternions are typed as Numeric
198 Python numpy.arrays of length 4.
199 """
200 assert numpy.allclose(math.sqrt(numpy.dot(q1,q1)), 1.0)
201 assert numpy.allclose(math.sqrt(numpy.dot(q2,q2)), 1.0)
202
203 x1, y1, z1, w1 = q1
204 x2, y2, z2, w2 = q2
205
206 x = w1*x2 + x1*w2 + y1*z2 - z1*y2
207 y = w1*y2 + y1*w2 + z1*x2 - x1*z2
208 z = w1*z2 + z1*w2 + x1*y2 - y1*x2
209 w = w1*w2 - x1*x2 - y1*y2 - z1*z2
210 q = numpy.array((x, y, z, w), float)
211
212
213 q = q / math.sqrt(numpy.dot(q,q))
214 assert numpy.allclose(math.sqrt(numpy.dot(q,q)), 1.0)
215
216 return q
217
219 """Create a rotation matrix from q quaternion rotation.
220 Quaternions are typed as Numeric Python numpy.arrays of length 4.
221 """
222 assert numpy.allclose(math.sqrt(numpy.dot(q,q)), 1.0)
223
224 x, y, z, w = q
225
226 xx = x*x
227 xy = x*y
228 xz = x*z
229 xw = x*w
230 yy = y*y
231 yz = y*z
232 yw = y*w
233 zz = z*z
234 zw = z*w
235
236 r00 = 1.0 - 2.0 * (yy + zz)
237 r01 = 2.0 * (xy - zw)
238 r02 = 2.0 * (xz + yw)
239
240 r10 = 2.0 * (xy + zw)
241 r11 = 1.0 - 2.0 * (xx + zz)
242 r12 = 2.0 * (yz - xw)
243
244 r20 = 2.0 * (xz - yw)
245 r21 = 2.0 * (yz + xw)
246 r22 = 1.0 - 2.0 * (xx + yy)
247
248 R = numpy.array([[r00, r01, r02],
249 [r10, r11, r12],
250 [r20, r21, r22]], float)
251
252 assert numpy.allclose(linalg.determinant(R), 1.0)
253 return R
254
256 """Return a quaternion calculated from the argument rotation matrix R.
257 """
258 assert numpy.allclose(linalg.determinant(R), 1.0)
259
260 t = numpy.trace(R) + 1.0
261
262 if t>1e-5:
263 w = math.sqrt(1.0 + numpy.trace(R)) / 2.0
264 w4 = 4.0 * w
265
266 x = (R[2,1] - R[1,2]) / w4
267 y = (R[0,2] - R[2,0]) / w4
268 z = (R[1,0] - R[0,1]) / w4
269
270 else:
271 if R[0,0]>R[1,1] and R[0,0]>R[2,2]:
272 S = math.sqrt(1.0 + R[0,0] - R[1,1] - R[2,2]) * 2.0
273 x = 0.25 * S
274 y = (R[0,1] + R[1,0]) / S
275 z = (R[0,2] + R[2,0]) / S
276 w = (R[1,2] - R[2,1]) / S
277 elif R[1,1]>R[2,2]:
278 S = math.sqrt(1.0 + R[1,1] - R[0,0] - R[2,2]) * 2.0;
279 x = (R[0,1] + R[1,0]) / S;
280 y = 0.25 * S;
281 z = (R[1,2] + R[2,1]) / S;
282 w = (R[0,2] - R[2,0]) / S;
283 else:
284 S = math.sqrt(1.0 + R[2,2] - R[0,0] - R[1,1]) * 2;
285 x = (R[0,2] + R[2,0]) / S;
286 y = (R[1,2] + R[2,1]) / S;
287 z = 0.25 * S;
288 w = (R[0,1] - R[1,0] ) / S;
289
290 q = numpy.array((x, y, z, w), float)
291 assert numpy.allclose(math.sqrt(numpy.dot(q,q)), 1.0)
292 return q
293
294
295
296
297
299 """Returns the distance between two argument atoms.
300 """
301 if a1 == None or a2 == None:
302 return None
303 return length(a1.position - a2.position)
304
306 """Return the angle between the three argument atoms.
307 """
308 if a1 == None or a2 == None or a3 == None:
309 return None
310 a21 = a1.position - a2.position
311 a21 = a21 / (length(a21))
312
313 a23 = a3.position - a2.position
314 a23 = a23 / (length(a23))
315
316 return math.acos(numpy.dot(a21, a23))
317
319 """Calculates the torsion angle between the four argument atoms.
320 Note: This "old" subroutine doesn't appear to do what it claims. Please
321 see the 'new' calc_torsion_angle() function below.
322 """
323 if a1 == None or a2 == None or a3 == None or a4 == None:
324 return None
325
326 a12 = a2.position - a1.position
327 a23 = a3.position - a2.position
328 a34 = a4.position - a3.position
329
330 n12 = cross(a12, a23)
331 n34 = cross(a23, a34)
332
333 n12 = n12 / length(n12)
334 n34 = n34 / length(n34)
335
336 cross_n12_n34 = cross(n12, n34)
337 direction = cross_n12_n34 * a23
338 scalar_product = numpy.dot(n12, n34)
339
340 if scalar_product > 1.0:
341 scalar_product = 1.0
342 if scalar_product < -1.0:
343 scalar_product = -1.0
344
345 angle = math.acos(scalar_product)
346
347
348 if direction.all() < 0.0:
349
350
351 angle = -angle
352
353 return angle
354
356 """Calculates the torsion angle between the four argument atoms.
357 """
358 if a1 == None or a2 == None or a3 == None or a4 == None:
359 return None
360
361 v12x = a1.position[0] - a2.position[0]
362 v12y = a1.position[1] - a2.position[1]
363 v12z = a1.position[2] - a2.position[2]
364
365 v32x = a3.position[0] - a2.position[0]
366 v32y = a3.position[1] - a2.position[1]
367 v32z = a3.position[2] - a2.position[2]
368
369 v43x = a4.position[0] - a3.position[0]
370 v43y = a4.position[1] - a3.position[1]
371 v43z = a4.position[2] - a3.position[2]
372
373 vn13x = v12y*v32z - v12z*v32y
374 vn13y = v12z*v32x - v12x*v32z
375 vn13z = v12x*v32y - v12y*v32x
376
377 vn24x = v32z*v43y - v32y*v43z
378 vn24y = v32x*v43z - v32z*v43x
379 vn24z = v32y*v43x - v32x*v43y
380
381 v12 = vn13x*vn24x + vn13y*vn24y + vn13z*vn24z
382 v11 = vn13x**2 + vn13y**2 + vn13z**2
383 v22 = vn24x**2 + vn24y**2 + vn24z**2
384
385 angle = v12/sqrt(v11*v22)
386 if angle >= 1.0:
387 return 0.0
388 elif angle <= -1.0:
389 return -180.0
390 else:
391 angle = acos(angle) * Constants.RAD2DEG
392
393 vtmp = vn13x * (vn24y*v32z - vn24z*v32y) + \
394 vn13y * (vn24z*v32x - vn24x*v32z) + \
395 vn13z * (vn24x*v32y - vn24y*v32x) < 0.0
396 if vtmp:
397 return -angle
398 else:
399 return angle
400
401
402
403
404
406 """Calculate the correlation coefficient for anisotropic ADP tensors U
407 and V.
408 """
409
410 invU = linalg.inverse(U)
411 invV = linalg.inverse(V)
412
413
414
415 det_invU = linalg.determinant(invU)
416 det_invV = linalg.determinant(invV)
417
418 return ( math.sqrt(math.sqrt(det_invU * det_invV)) /
419 math.sqrt((1.0/8.0) * linalg.determinant(invU + invV)) )
420
422 """Calculate the similarity of anisotropic ADP tensors U and V.
423 """
424
425 eqU = numpy.trace(U) / 3.0
426 eqV = numpy.trace(V) / 3.0
427
428 isoU = eqU * numpy.identity(3, float)
429 isoV = eqV * numpy.identity(3, float)
430
431 return ( calc_CCuij(U, (eqU/eqV)*V) /
432 (calc_CCuij(U, isoU) * calc_CCuij(V, isoV)) )
433
435 """Calculate the square of the volumetric difference in the probability
436 density function of anisotropic ADP tensors U and V.
437 """
438 invU = linalg.inverse(U)
439 invV = linalg.inverse(V)
440
441 det_invU = linalg.determinant(invU)
442 det_invV = linalg.determinant(invV)
443
444 Pu2 = math.sqrt( det_invU / (64.0 * Constants.PI3) )
445 Pv2 = math.sqrt( det_invV / (64.0 * Constants.PI3) )
446 Puv = math.sqrt(
447 (det_invU * det_invV) / (8.0*Constants.PI3 * linalg.determinant(invU + invV)))
448
449 dP2 = Pu2 + Pv2 - (2.0 * Puv)
450
451 return dP2
452
454 """Calculates the anisotropy of a atomic ADP tensor U. Anisotropy is
455 defined as the smallest eigenvalue of U divided by the largest eigenvalue
456 of U.
457 """
458 evals = linalg.eigenvalues(U)
459 return min(evals) / max(evals)
460
462 """Calculates the trace difference of anisotropic ADP tensors U and V.
463 """
464 return abs((numpy.trace(U) - numpy.trace(V))/ 3.0)
465
467 """Calculates the sum of the differences of anisotropic ADP tensors
468 U and V squared.
469 """
470 return abs(numpy.sum(numpy.subtract(U,V)**2))
471
479
480
481
482
483
485 """Calculates the centroid of all contained Atom instances and
486 returns a Vector to the centroid.
487 """
488 num = 0
489 centroid = numpy.zeros(3, float)
490 for atm in atom_iter:
491 if atm.position != None:
492 centroid += atm.position
493 num += 1
494
495 return centroid / num
496
498 """Calculates the average temperature factor of all contained
499 Atom instances and returns the average temperature factor.
500 """
501 num_tf = 0
502 adv_tf = 0.0
503
504 for atm in atom_iter:
505 if atm.temp_factor != None:
506 adv_tf += atm.temp_factor
507 num_tf += 1
508
509 return adv_tf / num_tf
510
511
513 """Calculate a moment-of-inertia tensor at the given origin assuming all
514 atoms have the same mass.
515 """
516 I = numpy.zeros((3,3), float)
517
518 for atm in atom_iter:
519 x = atm.position - origin
520
521 I[0,0] += x[1]**2 + x[2]**2
522 I[1,1] += x[0]**2 + x[2]**2
523 I[2,2] += x[0]**2 + x[1]**2
524
525 I[0,1] += - x[0]*x[1]
526 I[1,0] += - x[0]*x[1]
527
528 I[0,2] += - x[0]*x[2]
529 I[2,0] += - x[0]*x[2]
530
531 I[1,2] += - x[1]*x[2]
532 I[2,1] += - x[1]*x[2]
533
534 evals, evecs = linalg.eigenvectors(I)
535
536
537
538
539 if evals[0] >= evals[1] and evals[0] >= evals[2]:
540 if evals[1] >= evals[2]:
541 R = numpy.array((evecs[2], evecs[1], evecs[0]), float)
542 else:
543 R = numpy.array((evecs[1], evecs[2], evecs[0]), float)
544
545 elif evals[1] >= evals[0] and evals[1] >= evals[2]:
546 if evals[0] >= evals[2]:
547 R = numpy.array((evecs[2], evecs[0], evecs[1]), float)
548 else:
549 R = numpy.array((evecs[0], evecs[2], evecs[1]), float)
550
551 elif evals[2] >= evals[0] and evals[2] >= evals[1]:
552 if evals[0] >= evals[1]:
553 R = numpy.array((evecs[1], evecs[0], evecs[2]), float)
554 else:
555 R = numpy.array((evecs[0], evecs[1], evecs[2]), float)
556
557
558 if numpy.allclose(linalg.determinant(R), -1.0):
559 I = numpy.identity(3, float)
560 I[0,0] = -1.0
561 R = numpy.dot(I, R)
562
563 assert numpy.allclose(linalg.determinant(R), 1.0)
564 return R
565
566
567
569 import Structure
570
571 a1 = Structure.Atom(x=0.0, y=-1.0, z=0.0)
572 a2 = Structure.Atom(x=0.0, y=0.0, z=0.0)
573 a3 = Structure.Atom(x=1.0, y=0.0, z=0.0)
574
575 a4 = Structure.Atom(res_name='GLY', x=1.0, y=1.0, z=-1.0)
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596 a1 = Structure.Atom(x=55.655, y=57.849, z=14.605, res_name='VAL')
597 a2 = Structure.Atom(x=54.810, y=57.974, z=13.593, res_name='ILE')
598 a3 = Structure.Atom(x=55.221, y=58.358, z=12.242, res_name='ILE')
599 a4 = Structure.Atom(x=54.461, y=59.575, z=11.722, res_name='ILE')
600 print "PHI: %.3f" % calc_torsion_angle(a1, a2, a3, a4)
601
602 a1 = Structure.Atom(x=54.810, y=57.974, z=13.593, res_name='ILE')
603 a2 = Structure.Atom(x=55.221, y=58.358, z=12.242, res_name='ILE')
604 a3 = Structure.Atom(x=54.461, y=59.575, z=11.722, res_name='ILE')
605 a4 = Structure.Atom(x=54.985, y=60.748, z=12.087, res_name='SER')
606 print "PSI: %.3f" % calc_torsion_angle(a1, a2, a3, a4)
607 print "="*40
608
609 print "a1:", a1.position
610 print "calc_angle:", calc_angle(a1, a2, a3)
611 print "calc_torsion_angle:", calc_torsion_angle(a1, a2, a3, a4)
612
613 if __name__ == "__main__":
614 test_module()
615
616