modules/chempy/cpv.py (269 lines of code) (raw):

#A* ------------------------------------------------------------------- #B* This file contains source code for the PyMOL computer program #C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific. #D* ------------------------------------------------------------------- #E* It is unlawful to modify or remove this copyright notice. #F* ------------------------------------------------------------------- #G* Please see the accompanying LICENSE file for further information. #H* ------------------------------------------------------------------- #I* Additional authors of this source file include: #-* #-* #-* #Z* ------------------------------------------------------------------- # Generic vector and matrix routines for 3-Space # Assembled for usage in PyMOL and Chemical Python # # Assumes row-major matrices and arrays # [ [vector 1], [vector 2], [vector 3] ] # # Raises ValueError when given bad input # # TODO: documentation! import math import random import copy RSMALL4 = 0.0001 #------------------------------------------------------------------------------ def get_null(): return [0.0,0.0,0.0] #------------------------------------------------------------------------------ def get_identity(): return [[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]] #------------------------------------------------------------------------------ def distance_sq(v1, v2): d0 = v2[0] - v1[0] d1 = v2[1] - v1[1] d2 = v2[2] - v1[2] return (d0*d0) + (d1*d1) + (d2*d2) #------------------------------------------------------------------------------ def distance(v1, v2): d0 = v2[0] - v1[0] d1 = v2[1] - v1[1] d2 = v2[2] - v1[2] return math.sqrt((d0*d0) + (d1*d1) + (d2*d2)) #------------------------------------------------------------------------------ def length(v): return math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) #------------------------------------------------------------------------------ def random_displacement(v,radius): r_vect = lambda r=random.random:[r()-0.5,r()-0.5,r()-0.5] while 1: vect = r_vect() v_len = length(vect) if (v_len<=0.5): break; if v_len > 0.00000000001: v_len = random.random()*radius / v_len return add(v,scale([vect[0], vect[1], vect[2]],v_len)) else: return v #------------------------------------------------------------------------------ def random_sphere(v,radius): r_vect = lambda r=random.random:[r()-0.5,r()-0.5,r()-0.5] while 1: vect = r_vect() v_len = length(vect) if (v_len<=0.5) and (v_len!=0.0): break; return add(v,scale([vect[0], vect[1], vect[2]],2*radius/v_len)) #------------------------------------------------------------------------------ def random_vector(): r_vect = lambda r=random.random:[r()-0.5,r()-0.5,r()-0.5] while 1: vect = r_vect() if length(vect)<=0.5: break; return scale([vect[0], vect[1], vect[2]],2.0) #------------------------------------------------------------------------------ def add(v1,v2): return [v1[0]+v2[0],v1[1]+v2[1],v1[2]+v2[2]] #------------------------------------------------------------------------------ def average(v1,v2): return [(v1[0]+v2[0])/2.0,(v1[1]+v2[1])/2.0,(v1[2]+v2[2])/2.0] #------------------------------------------------------------------------------ def scale(v,factor): return [v[0]*factor,v[1]*factor,v[2]*factor] #------------------------------------------------------------------------------ def negate(v): return [-v[0],-v[1],-v[2]] #------------------------------------------------------------------------------ def sub(v1,v2): return [v1[0]-v2[0],v1[1]-v2[1],v1[2]-v2[2]] #------------------------------------------------------------------------------ def dot_product(v1,v2): return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2] #------------------------------------------------------------------------------ def cross_product(v1,v2): return [(v1[1]*v2[2]) - (v1[2]*v2[1]), (v1[2]*v2[0]) - (v1[0]*v2[2]), (v1[0]*v2[1]) - (v1[1]*v2[0])] #------------------------------------------------------------------------------ def transform(m,v): return [m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2], m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2], m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2]] #------------------------------------------------------------------------------ def inverse_transform(m,v): return [m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2], m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2], m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2]] #------------------------------------------------------------------------------ def multiply(m1,m2): # HAVEN'T YET VERIFIED THAT THIS CONFORMS TO STANDARD DEFT return [[m1[0][0]*m2[0][0] + m1[0][1]*m2[1][0] + m1[0][2]*m2[2][0], m1[1][0]*m2[0][0] + m1[1][1]*m2[1][0] + m1[1][2]*m2[2][0], m1[2][0]*m2[0][0] + m1[2][1]*m2[1][0] + m1[2][2]*m2[2][0]], [m1[0][0]*m2[0][1] + m1[0][1]*m2[1][1] + m1[0][2]*m2[2][1], m1[1][0]*m2[0][1] + m1[1][1]*m2[1][1] + m1[1][2]*m2[2][1], m1[2][0]*m2[0][1] + m1[2][1]*m2[1][1] + m1[2][2]*m2[2][1]], [m1[0][0]*m2[0][2] + m1[0][1]*m2[1][2] + m1[0][2]*m2[2][2], m1[1][0]*m2[0][2] + m1[1][1]*m2[1][2] + m1[1][2]*m2[2][2], m1[2][0]*m2[0][2] + m1[2][1]*m2[1][2] + m1[2][2]*m2[2][2]]] #------------------------------------------------------------------------------ def transpose(m1): return [[m1[0][0], m1[1][0], m1[2][0]], [m1[0][1], m1[1][1], m1[2][1]], [m1[0][2], m1[1][2], m1[2][2]]] #------------------------------------------------------------------------------ def get_system2(x,y): z = cross_product(x,y) z = normalize(z) y = cross_product(z,x); y = normalize(y); x = normalize(x); return [x,y,z] #------------------------------------------------------------------------------ def scale_system(s,factor): r = [] for a in s: r.append([a[0]*factor,a[1]*factor,a[2]*factor]) return r #------------------------------------------------------------------------------ def transpose(m): return [[m[0][0], m[1][0], m[2][0]], [m[0][1], m[1][1], m[2][1]], [m[0][2], m[1][2], m[2][2]]] #------------------------------------------------------------------------------ def transform_about_point(m,v,p): return add(transform(m,sub(v,p)),p) #------------------------------------------------------------------------------ def get_angle(v1,v2): # v1,v2 must be unit vectors denom = (math.sqrt(((v1[0]*v1[0]) + (v1[1]*v1[1]) + (v1[2]*v1[2]))) * math.sqrt(((v2[0]*v2[0]) + (v2[1]*v2[1]) + (v2[2]*v2[2])))) if denom>1e-10: result = ( (v1[0]*v2[0]) + (v1[1]*v2[1]) + (v1[2]*v2[2]) ) / denom else: result = 0.0 result = math.acos(result) return result #------------------------------------------------------------------------------ def get_angle_formed_by(p1,p2,p3): # angle formed by three positions in space # based on code submitted by Paul Sherwood r1 = distance(p1,p2) r2 = distance(p2,p3) r3 = distance(p1,p3) small = 1.0e-10 if (r1 + r2 - r3) < small: # This seems to happen occasionally for 180 angles theta = math.pi else: theta = math.acos( (r1*r1 + r2*r2 - r3*r3) / (2.0 * r1*r2) ) return theta; #------------------------------------------------------------------------------ def project(v,n): dot = v[0]*n[0] + v[1]*n[1] + v[2]*n[2] return [ dot * n[0], dot * n[1], dot * n[2] ] #------------------------------------------------------------------------------ def remove_component(v, n): dot = v[0]*n[0] + v[1]*n[1] + v[2]*n[2] return [v[0] - dot * n[0], v[1] - dot * n[1], v[2] - dot * n[2]] #------------------------------------------------------------------------------ def normalize(v): vlen = math.sqrt((v[0]*v[0]) + (v[1]*v[1]) + (v[2]*v[2])) if vlen>RSMALL4: return [v[0]/vlen,v[1]/vlen,v[2]/vlen] else: return get_null() #------------------------------------------------------------------------------ def reverse(v): return [ -v[0], -v[1], -v[2] ] #------------------------------------------------------------------------------ def normalize_failsafe(v): vlen = math.sqrt((v[0]*v[0]) + (v[1]*v[1]) + (v[2]*v[2])) if vlen>RSMALL4: return [v[0]/vlen,v[1]/vlen,v[2]/vlen] else: return [1.0,0.0,0.0] #------------------------------------------------------------------------------ def rotation_matrix(angle,axis): x=axis[0] y=axis[1] z=axis[2] s = math.sin(angle) c = math.cos(angle) mag = math.sqrt( x*x + y*y + z*z ) if abs(mag)<RSMALL4: return get_identity() x = x / mag y = y / mag z = z / mag xx = x * x yy = y * y zz = z * z xy = x * y yz = y * z zx = z * x xs = x * s ys = y * s zs = z * s one_c = 1.0 - c return [[ (one_c * xx) + c , (one_c * xy) - zs, (one_c * zx) + ys], [ (one_c * xy) + zs, (one_c * yy) + c , (one_c * yz) - xs], [ (one_c * zx) - ys, (one_c * yz) + xs, (one_c * zz) + c ]] #------------------------------------------------------------------------------ def transform_array(rot_mtx,vec_array): '''transform_array( matrix, vector_array ) -> vector_array ''' return [transform(rot_mtx, x) for x in vec_array] #------------------------------------------------------------------------------ def translate_array(trans_vec,vec_array): '''translate_array(trans_vec,vec_array) -> vec_array Adds 'mult'*'trans_vec' to each element in vec_array, and returns the translated vector. ''' return [add(trans_vec, x) for x in vec_array] #------------------------------------------------------------------------------ def fit_apply(fit_result,vec_array): '''fit_apply(fir_result,vec_array) -> vec_array Applies a fit result to an array of vectors ''' t1, mt2, m = fit_result[:3] return [add(t1, transform(m, add(mt2, x))) for x in vec_array] #------------------------------------------------------------------------------ def fit(target_array, source_array): '''fit(target_array, source_array) -> (t1, t2, rot_mtx, rmsd) [fit_result] Calculates the translation vectors and rotation matrix required to superimpose source_array onto target_array. Original arrays are not modified. NOTE: Currently assumes 3-dimensional coordinates t1,t2 are vectors from origin to centers of mass... ''' # Check dimensions of input arrays if len(target_array) != len(source_array): print ("Error: arrays must be of same length for RMS fitting.") raise ValueError if len(target_array[0]) != 3 or len(source_array[0]) != 3: print ("Error: arrays must be dimension 3 for RMS fitting.") raise ValueError nvec = len(target_array) ndim = 3 maxiter = 200 tol = 0.001 # Calculate translation vectors (center-of-mass). t1 = get_null() t2 = get_null() tvec1 = get_null() tvec2 = get_null() for i in range(nvec): for j in range(ndim): t1[j] = t1[j] + target_array[i][j] t2[j] = t2[j] + source_array[i][j] for j in range(ndim): t1[j] = t1[j] / nvec t2[j] = t2[j] / nvec # Calculate correlation matrix. corr_mtx = [] for i in range(ndim): temp_vec = [] for j in range(ndim): temp_vec.append(0.0) corr_mtx.append(temp_vec) rot_mtx = [] for i in range(ndim): temp_vec = [] for j in range(ndim): temp_vec.append(0.0) rot_mtx.append(temp_vec) for i in range(ndim): rot_mtx[i][i] = 1. for i in range(nvec): for j in range(ndim): tvec1[j] = target_array[i][j] - t1[j] tvec2[j] = source_array[i][j] - t2[j] for j in range(ndim): for k in range(ndim): corr_mtx[j][k] = corr_mtx[j][k] + tvec2[j]*tvec1[k] # Main iteration scheme (hardwired for 3X3 matrix, but could be extended). iters = 0 while (iters < maxiter): iters = iters + 1 ix = (iters-1)%ndim iy = iters%ndim iz = (iters+1)%ndim sig = corr_mtx[iz][iy] - corr_mtx[iy][iz] gam = corr_mtx[iy][iy] + corr_mtx[iz][iz] sg = (sig**2 + gam**2)**0.5 if sg != 0.0 and (abs(sig) > tol*abs(gam)): sg = 1.0 / sg for i in range(ndim): bb = gam*corr_mtx[iy][i] + sig*corr_mtx[iz][i] cc = gam*corr_mtx[iz][i] - sig*corr_mtx[iy][i] corr_mtx[iy][i] = bb*sg corr_mtx[iz][i] = cc*sg bb = gam*rot_mtx[iy][i] + sig*rot_mtx[iz][i] cc = gam*rot_mtx[iz][i] - sig*rot_mtx[iy][i] rot_mtx[iy][i] = bb*sg rot_mtx[iz][i] = cc*sg else: # We have a converged rotation matrix. Calculate RMS deviation. vt1 = translate_array(negate(t1),target_array) vt2 = translate_array(negate(t2),source_array) vt3 = transform_array(rot_mtx,vt2) rmsd = 0.0 for i in range(nvec): rmsd = rmsd + distance_sq(vt1[i], vt3[i]) rmsd = math.sqrt(rmsd/nvec) return(t1, t2, rot_mtx, rmsd) # Too many iterations; something wrong. print ("Error: Too many iterations in RMS fit.") raise ValueError