rmsd.hpp

Go to the documentation of this file.
00001 // -*- mode: c++; indent-tabs-mode: nil; -*-
00002 //
00003 //The Biomolecule Toolkit (BTK) is a C++ library for use in the
00004 //modeling, analysis, and design of biological macromolecules.
00005 //Copyright (C) 2001-2006, Chris Saunders <ctsa@users.sourceforge.net>,
00006 //                         Tim Robertson <kid50@users.sourceforge.net>
00007 //
00008 //This program is free software; you can redistribute it and/or modify
00009 //it under the terms of the GNU Lesser General Public License as published
00010 //by the Free Software Foundation; either version 2.1 of the License, or (at
00011 //your option) any later version.
00012 //
00013 //This program is distributed in the hope that it will be useful,  but
00014 //WITHOUT ANY WARRANTY; without even the implied warranty of
00015 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016 //Lesser General Public License for more details.
00017 //
00018 //You should have received a copy of the GNU Lesser General Public License
00019 //along with this program; if not, write to the Free Software Foundation,
00020 //Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00021 
00024 
00025 #ifndef BTK_ALGORITHMS_RMSD_HPP
00026 #define BTK_ALGORITHMS_RMSD_HPP
00027 
00028 #include <cmath>
00029 #include <functional>
00030 #include <algorithm>
00031 
00032 #include <btk/core/math/btk_vector.hpp>
00033 #include <btk/core/math/btk_matrix.hpp>
00034 #include <btk/core/math/linear_algebra.hpp>
00035 
00036 namespace BTK {
00037 namespace ALGORITHMS {
00038 
00047 
00048 #ifndef DOXYGEN_SHOULD_SKIP_THIS
00049 namespace internal {
00050 
00051 // This routine will calculate the eigenvectors corresponding
00052 // to pre-calculated eigenvalues for use in RMSD calculations.
00053 // It is not intended to be a solution to the full eigenvector
00054 // calculation problem for 3D matrices.
00055 //
00056 // I think this will work for any 3D matrix that produces three
00057 // real eigenvalues, but I make no guarantees. For RMSD calculations,
00058 // it will work b/c we know that we are dealing with three orthogonal
00059 // real vectors.  If you can't make that assumption
00060 // in your application, don't use this code!
00061 //
00062 // The code checks for eigenvalues with multiplicity > 1, but the
00063 // method used in this case may not be correct, so be careful (as I've
00064 // already said, this won't be the case for 3D structural data).
00065 //
00066 void fast_solve_3D_eigenvectors(BTK::MATH::BTKMatrix & M,
00067                                 double const evalues[3],
00068                                 BTK::MATH::BTKVector evecs[3]);
00069 
00070 // Solve eigenvalues (ev) for a 3x3 matrix M quickly by analytically
00071 // finding roots of the characteristic equation: det(M - evI) = 0.
00072 //
00073 // It is possible that the factorization used here
00074 // is numerically unstable (but it likely doesn't matter for RMSD
00075 // calculations).  At the present time, there are no checks or
00076 // warnings on the quality of the result.
00077 void fast_solve_3D_eigenvalues(BTK::MATH::BTKMatrix const & M,
00078                                double e_vals[3]);
00079 
00080 
00081 // Solves the determinant for a 3x3 matrix -- not general!
00082 inline double determinant(BTK::MATH::BTKMatrix const & M)
00083 {
00084   using boost::numeric::ublas::inner_prod;
00085   using BTK::MATH::cross;
00086   return inner_prod(row(M,0),cross(row(M,1),row(M,2)));
00087 }
00088 
00089 template <typename AtomIterator1, typename AtomIterator2>
00090  void
00091  setup_rmsd(AtomIterator1 first1,
00092             AtomIterator1 last1,
00093             AtomIterator2 first2,
00094             AtomIterator2 last2,
00095             BTK::MATH::BTKVector & T1,
00096             BTK::MATH::BTKVector & T2,
00097             BTK::MATH::BTKMatrix & R,
00098             BTK::MATH::BTKMatrix & RtR,
00099             double & Eo,
00100             unsigned & N,
00101             double & omega,
00102             double e_vals[3])
00103 {
00104   // Get T1, the transform from the moved set COM to the origin
00105   //   & T2, the transform from the origin to the fixed set COM
00106   T1 = BTK::MATH::BTKVector(0.0);
00107   T2 = BTK::MATH::BTKVector(0.0);
00108   AtomIterator1 i1(first1);
00109   AtomIterator2 i2(first2);
00110 
00111   for (N = 0;
00112        i1 != last1 && i2 != last2;
00113        ++i1,++i2) {
00114     T1 += i2->position();
00115     T2 += i1->position();
00116     N++;
00117   }
00118 
00119   if (i2 != last2 || i1 != last1) {
00120     std::cerr << "Warning: attempting to compute RMSD between "
00121               << "structures of different lengths! " << std::endl
00122               << "Assuming atom length of smaller structure (" << N << ")...."
00123               << std::endl;
00124   }
00125 
00126   T1 /= -1.*static_cast<double>(N);
00127   T2 /= static_cast<double>(N);
00128 
00129   //
00130   // Get Eo and R
00131   //
00132   int i = 0;
00133   BTK::MATH::BTKMatrix tmp1(3,N),tmp2(3,N);
00134   for (i1 = first1, i2 = first2;
00135        i1 != last1 && i2 != last2;
00136        ++i1,++i2) {
00137     column(tmp1,i) = i1->position() - T2;
00138     column(tmp2,i) = i2->position() + T1;
00139     i++;
00140   }
00141 
00142   Eo = 0;
00143   for (unsigned j = 0; j < 3; ++j) {
00144     Eo += inner_prod(row(tmp1,j),row(tmp1,j)) +
00145       inner_prod(row(tmp2,j),row(tmp2,j));
00146     for (unsigned k = 0; k < 3; ++k) {
00147       R(j,k) = inner_prod(row(tmp1,j),row(tmp2,k));
00148     }
00149   }
00150   Eo /= 2.0;
00151 
00152   // if det(R) < 0, the rotation is degenerate (a flip).
00153   if (determinant(R) > 0) omega = 1;
00154   else omega = -1;
00155 
00156   RtR = prec_prod(trans(R),R);
00157 
00158   internal::fast_solve_3D_eigenvalues(RtR,e_vals);
00159 
00160   // The eigenvalues of R (what we really want) are equal to the square root
00161   // of the eigenvalues of RtR. The fast eigenvalue routine is somewhat 
00162   // unstable, and the if statements correct for small-magnitude negative
00163   // values that will occasionally come out of the routine when the true
00164   // eigenvalue is zero (b/c RtR is a positive definite symmetric real matrix
00165   // we know in advance that its eigenvalues will always be real and posiive, 
00166   // so this correction is always mathematically valid.)
00167   for (unsigned i = 0; i < 3; ++i) {
00168     if (e_vals[i] > 0) e_vals[i] = std::sqrt(e_vals[i]);
00169     else e_vals[i] = 0.0;
00170   }
00171 
00172   std::sort(e_vals,e_vals + 3,
00173             std::greater<double>());
00174 }
00175 
00176 } // namespace internal
00177 #endif // DOXYGEN_SHOULD_SKIP_THIS
00178 
00195 template <typename AtomIterator1, typename AtomIterator2>
00196 double
00197 leastsquares_superposition(AtomIterator1 fixed_first,
00198                            AtomIterator1 fixed_last,
00199                            AtomIterator2 superimpose_first,
00200                            AtomIterator2 superimpose_last,
00201                            BTK::MATH::BTKMatrix & U,
00202                            BTK::MATH::BTKVector & T)
00203  {
00204    double Eo, omega;
00205    double e_vals[3];
00206    BTK::MATH::BTKMatrix R,RtR;
00207    BTK::MATH::BTKVector T1;
00208    BTK::MATH::BTKVector T2;
00209    unsigned N;
00210 
00211    internal::setup_rmsd(fixed_first,
00212                         fixed_last,
00213                         superimpose_first,
00214                         superimpose_last,
00215                         T1,T2,R,RtR,Eo,N,
00216                         omega,e_vals);
00217 
00218    BTK::MATH::BTKVector right_evecs[3];
00219 
00220    // In order to create the rotation matrix, we need the
00221    // eigenvectors of the RtR matrix. To do this, we need
00222    // the eigenvalues of this matrix.  These are the
00223    // squares of the eigenvalues of R.
00224    e_vals[0] *= e_vals[0];
00225    e_vals[1] *= e_vals[1];
00226    e_vals[2] *= e_vals[2];
00227 
00228    internal::fast_solve_3D_eigenvectors(RtR,e_vals,right_evecs);
00229 
00230    // Useful algorithm information:
00231    // The eigenvectors of RtR are equivalent to the right eigenvectors of
00232    // matrix R. We need the left eigenvectors of R, which are determined by
00233    // Left = R * Right.
00234    BTK::MATH::BTKVector left_evecs[3];
00235 
00236    left_evecs[0] = prec_prod(R,right_evecs[0]);
00237    left_evecs[1] = prec_prod(R,right_evecs[1]);
00238 
00239    // normalize the vectors
00240    left_evecs[0] /= norm_2(left_evecs[0]);
00241    left_evecs[1] /= norm_2(left_evecs[1]);
00242 
00243    // in order to guarantee a right-handed system, we'll
00244    // determine the Z vector as the cross of X and Y.
00245    left_evecs[2] = cross(left_evecs[0],left_evecs[1]);
00246 
00247    // Construct the optimal rotation matrix.
00248    //
00249    // More algorithm details:
00250    // The optimal rotation matrix is the product of matrices
00251    // containing the left and right eigenvectors of RtR.
00252    //
00253    // Said another way: if  RtR = BSV
00254    //  (this is the result of singular value decomposition on RtR)
00255    // the optimal rotation is U = B * t(V)
00256    //
00257    U.clear();
00258    for (unsigned i = 0; i < 3; ++i) {
00259      for (unsigned j = 0; j < 3; ++j) {
00260        U(i,j) = (left_evecs[0][i]*right_evecs[0][j]) +
00261                 (left_evecs[1][i]*right_evecs[1][j]) +
00262                 (left_evecs[2][i]*right_evecs[2][j]);
00263      }
00264    }
00265 
00266    // construct the post-rotation translation vector
00267    T = prec_prod(U,T1)+T2;
00268 
00269 
00270    // compute the rmsd between the aligned structures.
00271    double rmsd = (2.0/static_cast<double>(N))*
00272      (Eo - e_vals[0] - e_vals[1] - omega*e_vals[2]);
00273 
00274    double const sqrt_min = 1e-8;
00275    if(rmsd > sqrt_min) { rmsd = std::sqrt(rmsd); }
00276    else                { rmsd = 0; }
00277 
00278    return rmsd;
00279 }
00280 
00291 template <typename AtomIterator1, typename AtomIterator2, typename AtomIterator3>
00292 double
00293 leastsquares_superposition(AtomIterator1 fixed_first,
00294                            AtomIterator1 fixed_last,
00295                            AtomIterator2 superimpose_first,
00296                            AtomIterator2 superimpose_last,
00297                            AtomIterator3 transform_first,
00298                            AtomIterator3 transform_last)
00299 {
00300   BTK::MATH::BTKVector T;
00301   BTK::MATH::BTKMatrix U;
00302   double rmsd =
00303     leastsquares_superposition(fixed_first,fixed_last,
00304                                superimpose_first,superimpose_last,
00305                                U,T);
00306 
00307   for(AtomIterator3 ai=transform_first; ai != transform_last; ++ai)
00308     ai->set_position(prec_prod(U,ai->position())+T);
00309 
00310   return rmsd;
00311 }
00312 
00323 template <typename AtomIterator1, typename AtomIterator2>
00324 double
00325 get_rmsd(AtomIterator1 a1_first,
00326          AtomIterator1 a1_last,
00327          AtomIterator2 a2_first,
00328          AtomIterator2 a2_last)
00329 {
00330   double Eo, omega;
00331   double e_vals[3];
00332   BTK::MATH::BTKMatrix R,RtR;
00333   unsigned N;
00334   BTK::MATH::BTKVector T1,T2;
00335 
00336   internal::setup_rmsd(a1_first,a1_last,a2_first,a2_last,
00337                        T1,T2,R,RtR,Eo,N,omega,e_vals);
00338 
00339   double rmsd = (2.0/static_cast<double>(N))*
00340     (Eo - e_vals[0] - e_vals[1] - omega*e_vals[2]);
00341 
00342   double const sqrt_min = 1e-8;
00343   if(rmsd > sqrt_min) { rmsd = std::sqrt(rmsd); }
00344   else                { rmsd = 0; }
00345 
00346   return rmsd;
00347 }
00348 
00356 template <typename AtomIterator1, typename AtomIterator2>
00357 double
00358 get_trivial_rmsd(AtomIterator1 a1_first,
00359                  AtomIterator1 a1_last,
00360                  AtomIterator2 a2_first,
00361                  AtomIterator2 a2_last)
00362 {
00363   double rmsd2 = 0.;
00364   unsigned N = 0;
00365 
00366   AtomIterator1 a1 = a1_first;
00367   AtomIterator2 a2 = a2_first;
00368 
00369   for(;a1!=a1_last && a2!=a2_last;++a1,++a2){
00370     BTK::MATH::BTKVector v(a1->position()-a2->position());
00371     rmsd2 += inner_prod(v,v);
00372     N++;
00373   }
00374 
00375   if(a1!=a1_last || a2!=a2_last) {
00376     std::cerr << "Warning: attempting to compute RMSD between "
00377               << "structures of different lengths! " << std::endl
00378               << "Assuming atom length of smaller structure (" << N << ")...."
00379               << std::endl;
00380   }
00381 
00382   if(N) rmsd2 /= static_cast<double>(N);
00383 
00384   double const sqrt_min = 1e-8;
00385   if(rmsd2 > sqrt_min) { rmsd2 = std::sqrt(rmsd2); }
00386   else                 { rmsd2 = 0; }
00387 
00388   return rmsd2;
00389 }
00390 
00391 } // ALGORITHMS
00392 } // BTK
00393 
00645 #endif
00646 
00647 

Generated on Sun Jul 15 20:46:27 2007 for BTK Core by  doxygen 1.5.1