Spaces:
No application file
No application file
# Copyright (C) 2002, Thomas Hamelryck ([email protected]) | |
# | |
# This file is part of the Biopython distribution and governed by your | |
# choice of the "Biopython License Agreement" or the "BSD 3-Clause License". | |
# Please see the LICENSE file that should have been included as part of this | |
# package. | |
"""Align on protein structure onto another using SVD alignment. | |
SVDSuperimposer finds the best rotation and translation to put | |
two point sets on top of each other (minimizing the RMSD). This is | |
eg. useful to superimpose crystal structures. SVD stands for singular | |
value decomposition, which is used in the algorithm. | |
""" | |
try: | |
from numpy import dot, transpose, sqrt | |
from numpy.linalg import svd, det | |
except ImportError: | |
from Bio import MissingPythonDependencyError | |
raise MissingPythonDependencyError( | |
"Install NumPy if you want to use Bio.SVDSuperimposer." | |
) | |
class SVDSuperimposer: | |
"""Class to run SVD alignment. | |
SVDSuperimposer finds the best rotation and translation to put | |
two point sets on top of each other (minimizing the RMSD). This is | |
eg. useful to superimpose crystal structures. | |
SVD stands for Singular Value Decomposition, which is used to calculate | |
the superposition. | |
Reference: | |
Matrix computations, 2nd ed. Golub, G. & Van Loan, CF., The Johns | |
Hopkins University Press, Baltimore, 1989 | |
start with two coordinate sets (Nx3 arrays - float) | |
>>> from Bio.SVDSuperimposer import SVDSuperimposer | |
>>> from numpy import array, dot, set_printoptions | |
>>> | |
>>> x = array([[51.65, -1.90, 50.07], | |
... [50.40, -1.23, 50.65], | |
... [50.68, -0.04, 51.54], | |
... [50.22, -0.02, 52.85]], 'f') | |
>>> | |
>>> y = array([[51.30, -2.99, 46.54], | |
... [51.09, -1.88, 47.58], | |
... [52.36, -1.20, 48.03], | |
... [52.71, -1.18, 49.38]], 'f') | |
start | |
>>> sup = SVDSuperimposer() | |
set the coords y will be rotated and translated on x | |
>>> sup.set(x, y) | |
do the lsq fit | |
>>> sup.run() | |
get the rmsd | |
>>> rms = sup.get_rms() | |
get rotation (right multiplying!) and the translation | |
>>> rot, tran = sup.get_rotran() | |
rotate y on x | |
>>> y_on_x1 = dot(y, rot) + tran | |
same thing | |
>>> y_on_x2 = sup.get_transformed() | |
>>> set_printoptions(precision=2) | |
>>> print(y_on_x1) | |
[[ 5.17e+01 -1.90e+00 5.01e+01] | |
[ 5.04e+01 -1.23e+00 5.06e+01] | |
[ 5.07e+01 -4.16e-02 5.15e+01] | |
[ 5.02e+01 -1.94e-02 5.29e+01]] | |
>>> print(y_on_x2) | |
[[ 5.17e+01 -1.90e+00 5.01e+01] | |
[ 5.04e+01 -1.23e+00 5.06e+01] | |
[ 5.07e+01 -4.16e-02 5.15e+01] | |
[ 5.02e+01 -1.94e-02 5.29e+01]] | |
>>> print("%.2f" % rms) | |
0.00 | |
""" | |
def __init__(self): | |
"""Initialize the class.""" | |
self._clear() | |
# Private methods | |
def _clear(self): | |
self.reference_coords = None | |
self.coords = None | |
self.transformed_coords = None | |
self.rot = None | |
self.tran = None | |
self.rms = None | |
self.init_rms = None | |
def _rms(self, coords1, coords2): | |
"""Return rms deviations between coords1 and coords2 (PRIVATE).""" | |
diff = coords1 - coords2 | |
return sqrt(sum(sum(diff * diff)) / coords1.shape[0]) | |
# Public methods | |
def set(self, reference_coords, coords): | |
"""Set the coordinates to be superimposed. | |
coords will be put on top of reference_coords. | |
- reference_coords: an NxDIM array | |
- coords: an NxDIM array | |
DIM is the dimension of the points, N is the number | |
of points to be superimposed. | |
""" | |
# clear everything from previous runs | |
self._clear() | |
# store coordinates | |
self.reference_coords = reference_coords | |
self.coords = coords | |
n = reference_coords.shape | |
m = coords.shape | |
if n != m or not (n[1] == m[1] == 3): | |
raise Exception("Coordinate number/dimension mismatch.") | |
self.n = n[0] | |
def run(self): | |
"""Superimpose the coordinate sets.""" | |
if self.coords is None or self.reference_coords is None: | |
raise Exception("No coordinates set.") | |
coords = self.coords | |
reference_coords = self.reference_coords | |
# center on centroid | |
av1 = sum(coords) / self.n | |
av2 = sum(reference_coords) / self.n | |
coords = coords - av1 | |
reference_coords = reference_coords - av2 | |
# correlation matrix | |
a = dot(transpose(coords), reference_coords) | |
u, d, vt = svd(a) | |
self.rot = transpose(dot(transpose(vt), transpose(u))) | |
# check if we have found a reflection | |
if det(self.rot) < 0: | |
vt[2] = -vt[2] | |
self.rot = transpose(dot(transpose(vt), transpose(u))) | |
self.tran = av2 - dot(av1, self.rot) | |
def get_transformed(self): | |
"""Get the transformed coordinate set.""" | |
if self.coords is None or self.reference_coords is None: | |
raise Exception("No coordinates set.") | |
if self.rot is None: | |
raise Exception("Nothing superimposed yet.") | |
if self.transformed_coords is None: | |
self.transformed_coords = dot(self.coords, self.rot) + self.tran | |
return self.transformed_coords | |
def get_rotran(self): | |
"""Right multiplying rotation matrix and translation.""" | |
if self.rot is None: | |
raise Exception("Nothing superimposed yet.") | |
return self.rot, self.tran | |
def get_init_rms(self): | |
"""Root mean square deviation of untransformed coordinates.""" | |
if self.coords is None: | |
raise Exception("No coordinates set yet.") | |
if self.init_rms is None: | |
self.init_rms = self._rms(self.coords, self.reference_coords) | |
return self.init_rms | |
def get_rms(self): | |
"""Root mean square deviation of superimposed coordinates.""" | |
if self.rms is None: | |
transformed_coords = self.get_transformed() | |
self.rms = self._rms(transformed_coords, self.reference_coords) | |
return self.rms | |
if __name__ == "__main__": | |
from Bio._utils import run_doctest | |
run_doctest(verbose=0) | |