Package mmLib :: Module AtomMath
[hide private]
[frames] | no frames]

Source Code for Module mmLib.AtomMath

  1  ## Copyright 2002-2010 by PyMMLib Development Group (see AUTHORS file) 
  2  ## This code is part of the PyMMLib distribution and governed by 
  3  ## its license.  Please see the LICENSE file that should have been 
  4  ## included as part of this package. 
  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  ## Linear Algebra 
 24  ## 
25 -def length(u):
26 """Calculates the length of u. 27 """ 28 return math.sqrt(numpy.dot(u, u))
29
30 -def normalize(u):
31 """Returns the normalized vector along u. 32 """ 33 return u/math.sqrt(numpy.dot(u, u))
34
35 -def cross(u, v):
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 ## Internal Linear Algebra (without using numpy) 46 ##
47 -def internal_cross(u, v):
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
55 -def internal_dot(u, v):
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
61 -def internal_inv3x3(u):
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])) ## c[0] 68 c.append(internal_cross(u[2], u[0])) ## c[1] 69 c.append(internal_cross(u[0], u[1])) ## c[2] 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 ## Rotation/Displacement 84 ##
85 -def rmatrix(alpha, beta, gamma):
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
105 -def rmatrixu(u, theta):
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
131 -def dmatrix(alpha, beta, gamma):
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
137 -def dmatrixu(u, theta):
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
143 -def rmatrixz(vec):
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 ## Quaternions 175 ##
176 -def rquaternionu(u, theta):
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 ## create quaternion 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
196 -def addquaternion(q1, q2):
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 ## normalize quaternion 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
218 -def rmatrixquaternion(q):
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
255 -def quaternionrmatrix(R):
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 ## Bond Angles 297 ##
298 -def calc_distance(a1, a2):
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
305 -def calc_angle(a1, a2, a3):
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
318 -def calc_torsion_angle_old(a1, a2, a3, a4):
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 ## E.g, direction = [0.70710678, 0.0, 0.0] 348 if direction.all() < 0.0: 349 ## True if _all_ elements of 'direction' are true (or if 'direction' 350 ## is empty) 351 angle = -angle 352 353 return angle
354
355 -def calc_torsion_angle(a1, a2, a3, a4, sqrt=math.sqrt, acos=math.acos):
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 ## Atomic ADPs 404 ##
405 -def calc_CCuij(U, V):
406 """Calculate the correlation coefficient for anisotropic ADP tensors U 407 and V. 408 """ 409 ## FIXME: Check for non-positive Uij's, 2009-08-19 410 invU = linalg.inverse(U) 411 invV = linalg.inverse(V) 412 #invU = internal_inv3x3(U) 413 #invV = internal_inv3x3(V) 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
421 -def calc_Suij(U, V):
422 """Calculate the similarity of anisotropic ADP tensors U and V. 423 """ 424 ## FIXME: Check for non-positive Uij's, 2009-08-19 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
434 -def calc_DP2uij(U, V):
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
453 -def calc_anisotropy(U):
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
461 -def diff_trace_UV(U, V):
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
466 -def sum_square_diff(U, V):
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
472 -def calc_rosenfeld(a, b, d, U, V):
473 n = numpy.array([(a[0] - b[0])/d, (a[1] - b[1])/d, (a[2] - b[2])/d]) 474 #Un = numpy.dot(numpy.dot(n, U), numpy.transpose(n)) 475 #Vn = numpy.dot(numpy.dot(n, V), numpy.transpose(n)) 476 Un = internal_dot(internal_dot(n, U), numpy.transpose(n)) 477 Vn = internal_dot(internal_dot(n, V), numpy.transpose(n)) 478 return abs(Un - Vn)
479 480 481 ## 482 ## Calculations on groups of atoms 483 ##
484 -def calc_atom_centroid(atom_iter):
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
497 -def calc_atom_mean_temp_factor(atom_iter):
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
512 -def calc_inertia_tensor(atom_iter, origin):
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 ## order the tensor such that the largest 537 ## principal component is along the z-axis, and 538 ## the second largest is along the y-axis 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 ## make sure the tensor is right-handed 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 ### <TESTING>
568 -def test_module():
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 #a4 = Structure.Atom(x=1.0, y=1.0, z=-1.0) 575 a4 = Structure.Atom(res_name='GLY', x=1.0, y=1.0, z=-1.0) 576 577 ## Example taken from 1HMP.pdb 578 #ATOM 25 N VAL A 8 55.799 56.415 16.693 1.00 25.51 N 579 #ATOM 26 CA VAL A 8 55.049 57.431 15.929 1.00 20.42 C 580 #ATOM 27 C VAL A 8 55.655 57.849 14.605 1.00 21.66 C 581 #ATOM 28 O VAL A 8 56.846 58.112 14.504 1.00 31.38 O 582 #ATOM 29 CB VAL A 8 54.697 58.659 16.709 1.00 16.90 C 583 #ATOM 30 CG1 VAL A 8 54.131 59.664 15.699 1.00 19.06 C 584 #ATOM 31 CG2 VAL A 8 53.640 58.304 17.738 1.00 14.10 C 585 #ATOM 32 N ILE A 9 54.810 57.974 13.593 1.00 20.18 N 586 #ATOM 33 CA ILE A 9 55.221 58.358 12.242 1.00 16.49 C 587 #ATOM 34 C ILE A 9 54.461 59.575 11.722 1.00 28.07 C 588 #ATOM 35 O ILE A 9 53.439 59.455 11.009 1.00 31.82 O 589 #ATOM 36 CB ILE A 9 55.028 57.196 11.301 1.00 13.73 C 590 #ATOM 37 CG1 ILE A 9 55.941 56.045 11.712 1.00 20.33 C 591 #ATOM 38 CG2 ILE A 9 55.327 57.611 9.860 1.00 13.91 C 592 #ATOM 39 CD1 ILE A 9 55.871 54.892 10.733 1.00 21.80 C 593 #ATOM 40 N SER A 10 54.985 60.748 12.087 1.00 30.09 N 594 # 595 # PHI: C'-N-CA-C' 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 # PSI: N-CA-C'-N 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 ### </TESTING> 616