Skip to content

Commit eac5005

Browse files
xulescpeterjc
authored andcommitted
Add rmsd based structure superimpose that uses quaternions.
1 parent 9258f07 commit eac5005

File tree

5 files changed

+548
-0
lines changed

5 files changed

+548
-0
lines changed

Diff for: Bio/PDB/QCPSuperimposer/__init__.py

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Copyright (C) 2015, Anuj Sharma ([email protected])
2+
# This code is part of the Biopython distribution and governed by its
3+
# license. Please see the LICENSE file that should have been included
4+
# as part of this package.
5+
"""
6+
QCPSuperimposer finds the best rotation and translation to put
7+
two point sets on top of each other (minimizing the RMSD). This is
8+
eg. useful to superimpose crystal structures. QCP stands for
9+
Quaternion Characteristic Polynomial, which is used in the algorithm.
10+
"""
11+
12+
from __future__ import print_function
13+
14+
import warnings
15+
16+
from Bio import BiopythonExperimentalWarning
17+
from numpy import dot, sqrt, array, matrix, inner, zeros
18+
from .qcprotmodule import FastCalcRMSDAndRotation
19+
20+
warnings.warn('Bio.PDB.QCPSuperimposer is an experimental submodule which may undergo '
21+
'significant changes prior to its future official release.',
22+
BiopythonExperimentalWarning)
23+
24+
25+
class QCPSuperimposer(object):
26+
27+
"""
28+
QCPSuperimposer finds the best rotation and translation to put
29+
two point sets on top of each other (minimizing the RMSD). This is
30+
eg. useful to superimposing 3D structures of proteins.
31+
32+
QCP stands for Quaternion Characteristic Polynomial, which is used
33+
in the algorithm.
34+
35+
Reference:
36+
37+
Douglas L Theobald (2005), "Rapid calculation of RMSDs using a
38+
quaternion-based characteristic polynomial.", Acta Crystallogr
39+
A 61(4):478-480
40+
"""
41+
42+
def __init__(self):
43+
self._clear()
44+
45+
# Private methods
46+
47+
def _clear(self):
48+
self.reference_coords = None
49+
self.coords = None
50+
self.transformed_coords = None
51+
self.rot = None
52+
self.tran = None
53+
self.rms = None
54+
self.init_rms = None
55+
56+
def _rms(self, coords1, coords2):
57+
"Return rms deviations between coords1 and coords2."
58+
diff = coords1 - coords2
59+
l = coords1.shape[0]
60+
return sqrt(sum(dot(diff, diff)) / l)
61+
62+
def _inner_product(self, coords1, coords2):
63+
G1 = inner(coords1, coords1).diagonal().sum()
64+
G2 = inner(coords2, coords2).diagonal().sum()
65+
A = dot(coords1.T, coords2)
66+
return ((G1 + G2) / 2, A)
67+
68+
def _align(self, centered_coords1, centered_coords2):
69+
(E0, A) = self._inner_product(centered_coords1, centered_coords2)
70+
(rmsd, r0, r1, r2, r3, r4, r5, r6, r7, r8, q1, q2, q3, q4) = FastCalcRMSDAndRotation(
71+
A[0][0], A[0][1], A[0][2], A[1][0], A[1][1], A[1][2], A[2][0], A[2][1], A[2][2], E0, len(centered_coords1), -1.0)
72+
rot = array([r0, r1, r2, r3, r4, r5, r6, r7, r8]).reshape(3, 3)
73+
return (rmsd, rot.T, [q1, q2, q3, q4])
74+
75+
# Public methods
76+
77+
def set(self, reference_coords, coords):
78+
"""
79+
Set the coordinates to be superimposed.
80+
coords will be put on top of reference_coords.
81+
82+
o reference_coords: an NxDIM array
83+
o coords: an NxDIM array
84+
85+
DIM is the dimension of the points, N is the number
86+
of points to be superimposed.
87+
"""
88+
# clear everything from previous runs
89+
self._clear()
90+
# store cordinates
91+
self.reference_coords = reference_coords
92+
self.coords = coords
93+
n = reference_coords.shape
94+
m = coords.shape
95+
if n != m or not(n[1] == m[1] == 3):
96+
raise Exception("Coordinate number/dimension mismatch.")
97+
self.n = n[0]
98+
99+
def run(self):
100+
"Superimpose the coordinate sets."
101+
if self.coords is None or self.reference_coords is None:
102+
raise Exception("No coordinates set.")
103+
coords = self.coords
104+
reference_coords = self.reference_coords
105+
# center on centroid
106+
av1 = sum(coords) / self.n
107+
av2 = sum(reference_coords) / self.n
108+
coords = coords - av1
109+
reference_coords = reference_coords - av2
110+
#
111+
(self.rms, self.rot, self.lquart) = self._align(
112+
coords, reference_coords)
113+
self.tran = av2 - dot(av1, self.rot)
114+
115+
def get_transformed(self):
116+
"Get the transformed coordinate set."
117+
if self.coords is None or self.reference_coords is None:
118+
raise Exception("No coordinates set.")
119+
if self.rot is None:
120+
raise Exception("Nothing superimposed yet.")
121+
if self.transformed_coords is None:
122+
self.transformed_coords = dot(self.coords, self.rot) + self.tran
123+
return self.transformed_coords
124+
125+
def get_rotran(self):
126+
"Right multiplying rotation matrix and translation."
127+
if self.rot is None:
128+
raise Exception("Nothing superimposed yet.")
129+
return self.rot, self.tran
130+
131+
def get_init_rms(self):
132+
"Root mean square deviation of untransformed coordinates."
133+
if self.coords is None:
134+
raise Exception("No coordinates set yet.")
135+
if self.init_rms is None:
136+
self.init_rms = self._rms(self.coords, self.reference_coords)
137+
return self.init_rms
138+
139+
def get_rms(self):
140+
"Root mean square deviation of superimposed coordinates."
141+
if self.rms is None:
142+
raise Exception("Nothing superimposed yet.")
143+
return self.rms
144+
145+
146+
if __name__ == "__main__":
147+
from datetime import datetime
148+
149+
# start with two coordinate sets (Nx3 arrays - float)
150+
151+
x = array([[-2.803, -15.373, 24.556],
152+
[0.893, -16.062, 25.147],
153+
[1.368, -12.371, 25.885],
154+
[-1.651, -12.153, 28.177],
155+
[-0.440, -15.218, 30.068],
156+
[2.551, -13.273, 31.372],
157+
[0.105, -11.330, 33.567]], 'f')
158+
159+
y = array([[-14.739, -18.673, 15.040],
160+
[-12.473, -15.810, 16.074],
161+
[-14.802, -13.307, 14.408],
162+
[-17.782, -14.852, 16.171],
163+
[-16.124, -14.617, 19.584],
164+
[-15.029, -11.037, 18.902],
165+
[-18.577, -10.001, 17.996]], 'f')
166+
167+
t0 = datetime.now()
168+
for loop in range(0, 10000):
169+
# start!
170+
sup = QCPSuperimposer()
171+
172+
# set the coords
173+
# y will be rotated and translated on x
174+
sup.set(x, y)
175+
176+
# do the lsq fit
177+
sup.run()
178+
179+
# get the rmsd
180+
rms = sup.get_rms()
181+
182+
# get rotation (right multiplying!) and the translation
183+
rot, tran = sup.get_rotran()
184+
185+
# rotate y on x
186+
y_on_x1 = dot(y, rot) + tran
187+
188+
# same thing
189+
y_on_x2 = sup.get_transformed()
190+
t1 = datetime.now()
191+
dif = t1 - t0
192+
print("process wall time (msec): %d" % (dif.total_seconds() * 1000))
193+
194+
print(y_on_x1)
195+
print("")
196+
print(y_on_x2)
197+
print("")
198+
print("%.2f" % rms)

0 commit comments

Comments
 (0)