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