absOrientation.cpp
Go to the documentation of this file.
1 //-----------------------------------------------------------------------------------
2 // d-SEAMS - Deferred Structural Elucidation Analysis for Molecular Simulations
3 //
4 // Copyright (c) 2018--present d-SEAMS core team
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the MIT License as published by
8 // the Open Source Initiative.
9 //
10 // A copy of the MIT License is included in the LICENSE file of this repository.
11 // You should have received a copy of the MIT License along with this program.
12 // If not, see <https://opensource.org/licenses/MIT>.
13 //-----------------------------------------------------------------------------------
14 
15 #include <absOrientation.hpp>
16 
30 int absor::hornAbsOrientation(const Eigen::MatrixXd &refPoints,
31  const Eigen::MatrixXd &targetPoints,
32  std::vector<double> *quat, double *rmsd,
33  std::vector<double> *rmsdList, double *scale) {
34  int nop =
35  refPoints.rows(); // Number of particles (equal to the number of rows)
36  int dim =
37  refPoints.cols(); // Number of dimensions (equal to the number of columns)
38  Eigen::MatrixXd centeredRefPnts(
39  nop, dim); // Reference point set after centering wrt the centroid
40  Eigen::MatrixXd centeredTargetPnts(
41  nop, dim); // Target point set after centering wrt the centroid
42  Eigen::MatrixXd S(dim,
43  dim); // Matrix containing sums of products of coordinates
44  Eigen::MatrixXd N(
45  4, 4); // 4x4 Matrix, whose largest eigenvector must be calculated
46  Eigen::VectorXd calcEigenVec(
47  4); // This should have 4 components (eigen vector calculated from N)
48  // -----
49  // Check that the sizes of the reference point set (right point system) and
50  // the target point set (left point system) are the same
51  if (refPoints.rows() != targetPoints.rows() ||
52  refPoints.cols() != targetPoints.cols()) {
53  // Throw error
54  std::cerr
55  << "The reference and target point sets are not of the same size.\n";
56  return 1;
57  } // unequal size; error!
58  // -----
59  //
60  // ---------------------------------------------------
61  // FINDING THE CENTROIDS AND THE NEW COORDINATES WRT THE CENTROIDS
62  centeredRefPnts = absor::centerWRTcentroid(refPoints);
63  centeredTargetPnts = absor::centerWRTcentroid(targetPoints);
64  // ---------------------------------------------------
65  // FINDING THE ROTATION MATRIX
66  //
67  // Find the 3x3 matrix S
68  S = absor::calcMatrixS(centeredRefPnts, centeredTargetPnts, nop, dim);
69  // Calculate the 4x4 symmetric matrix N, whose largest eigenvector yields the
70  // quaternion in the same direction
71  N = absor::calcMatrixN(S);
72  // --------
73  // Calculate the eigenvector corresponding to the largest eigenvalue
74  //
75  // Construct matrix operation object (op) using the wrapper class
76  // DenseSymMatProd for the matrix N
77  Spectra::DenseSymMatProd<double> op(N);
78  //
79  // Construct eigen solver object, requesting the largest 1 eigenvalue and
80  // eigenvector
81  Spectra::SymEigsSolver<double, Spectra::LARGEST_ALGE,
82  Spectra::DenseSymMatProd<double>>
83  eigs(&op, 1, 4);
84  //
85  // Initialize and compute
86  eigs.init();
87  int nconv = eigs.compute();
88  // Get the eigenvalue and eigenvector
89  if (eigs.info() == Spectra::SUCCESSFUL) {
90  Eigen::VectorXd calcEigenValue = eigs.eigenvalues(); // Eigenvalue
91  calcEigenVec = eigs.eigenvectors();
92  } // end of eigenvector calculation
93  //
94  // --------
95  // Normalize the eigenvector calculated
96  double qNorm = sqrt(calcEigenVec.dot(calcEigenVec));
97  calcEigenVec /= qNorm; // Divide by the square root of the sum
98  // Update the quaternion with the normalized eigenvector
99  (*quat).resize(4); // Output quaternion update
100  for (int i = 0; i < 4; i++) {
101  (*quat)[i] = calcEigenVec(i);
102  } // end of quaternion update
103  // --------
104  // ---------------------------------------------------
105  // COMPUTE THE OPTIMUM SCALE
106  (*scale) = absor::calcScaleFactor(centeredRefPnts, centeredTargetPnts, nop);
107  // ---------------------------------------------------
108  // GETTING THE ERROR
109  (*rmsd) = absor::getRMSD(centeredRefPnts, centeredTargetPnts, calcEigenVec,
110  rmsdList, nop, (*scale));
111  // ---------------------------------------------------
112  return 0;
113 } // end of function
114 
131 Eigen::MatrixXd absor::calcMatrixS(const Eigen::MatrixXd &centeredRefPnts,
132  const Eigen::MatrixXd &centeredTargetPnts,
133  int nop, int dim) {
134  Eigen::MatrixXd S(nop, dim); // Output matrix S
135  Eigen::VectorXd targetCoord(nop); // Column of the target point set
136  Eigen::VectorXd refCoord(nop); // Column of the reference point set
137  double Svalue; // Current value being filled (Sxx, Sxy etc.)
138 
139  // Calculate Sxx, Sxy, Sxz etc
140  for (int iCol = 0; iCol < dim; iCol++) {
141  //
142  for (int jCol = 0; jCol < dim; jCol++) {
143  targetCoord =
144  centeredTargetPnts.col(iCol); // iCol^th column of target point set
145  refCoord = centeredRefPnts.col(jCol); // jCol^th of reference point set
146  Svalue = targetCoord.dot(refCoord);
147  S(iCol, jCol) = Svalue;
148  } // end column wise filling
149  } // end of filling
150 
151  // Output matrix
152  return S;
153 } // end of function
154 
172 Eigen::MatrixXd absor::calcMatrixN(const Eigen::MatrixXd &S) {
173  //
174  Eigen::MatrixXd N(4, 4); // Output matrix N
175  // Components of S
176  double Sxx = S(0, 0);
177  double Sxy = S(0, 1);
178  double Sxz = S(0, 2);
179  double Syx = S(1, 0);
180  double Syy = S(1, 1);
181  double Syz = S(1, 2);
182  double Szx = S(2, 0);
183  double Szy = S(2, 1);
184  double Szz = S(2, 2);
185 
186  // N=[(Sxx+Syy+Szz) (Syz-Szy) (Szx-Sxz) (Sxy-Syx);...
187  // (Syz-Szy) (Sxx-Syy-Szz) (Sxy+Syx) (Szx+Sxz);...
188  // (Szx-Sxz) (Sxy+Syx) (-Sxx+Syy-Szz) (Syz+Szy);...
189  // (Sxy-Syx) (Szx+Sxz) (Syz+Szy) (-Sxx-Syy+Szz)];
190 
191  // ------------------
192  // Calculating N:
193  // Diagonal elements
194  N(0, 0) = Sxx + Syy + Szz;
195  N(1, 1) = Sxx - Syy - Szz;
196  N(2, 2) = -Sxx + Syy - Szz;
197  N(3, 3) = -Sxx - Syy + Szz;
198  // Other elements
199  // First row
200  N(0, 1) = Syz - Szy;
201  N(0, 2) = Szx - Sxz;
202  N(0, 3) = Sxy - Syx;
203  // Second row
204  N(1, 0) = Syz - Szy;
205  N(1, 2) = Sxy + Syx;
206  N(1, 3) = Szx + Sxz;
207  // Third row
208  N(2, 0) = Szx - Sxz;
209  N(2, 1) = Sxy + Syx;
210  N(2, 3) = Syz + Szy;
211  // Fourth row
212  N(3, 0) = Sxy - Syx;
213  N(3, 1) = Szx + Sxz;
214  N(3, 2) = Syz + Szy;
215  // ------------------
216  // Output matrix
217  return N;
218 } // end of function
219 
228 Eigen::MatrixXd absor::centerWRTcentroid(const Eigen::MatrixXd &pointSet) {
229  int nop = pointSet.rows(); // Number of particles
230  int dim = pointSet.cols(); // Number of dimensions
231  Eigen::MatrixXd centeredPointSet(nop, dim); // Output point set
232  Eigen::VectorXd vecOfOnes(nop); // vector of ones
233  std::vector<double> centroid;
234  double coordValue;
235  double centeredVal;
236  //
237  centroid.resize(dim); // Init to zero
238  vecOfOnes = Eigen::VectorXd::Ones(nop);
239  // --------------------------------
240 
241  for (int i = 0; i < nop; i++) {
242  for (int k = 0; k < dim; k++) {
243  coordValue = pointSet(i, k);
244  centroid[k] += coordValue;
245  } // loop through columns
246  } // end of loop through rows
247  // Divide by the total number of particles
248  centroid[0] /= nop; // x
249  centroid[1] /= nop; // y
250  centroid[2] /= nop; // z
251  // --------------------------------
252  // Subtract the centroid from the coordinates to get the centered point set
253  for (int i = 0; i < nop; i++) {
254  for (int k = 0; k < dim; k++) {
255  coordValue = pointSet(i, k);
256  centeredVal = coordValue - centroid[k];
257  centeredPointSet(i, k) = centeredVal;
258  } // end of loop through columns (dimensions)
259  } // end of loop through the rows
260  // --------------------------------
261  return centeredPointSet;
262 } // end of function
263 
276 double absor::calcScaleFactor(const Eigen::MatrixXd &rightSys,
277  const Eigen::MatrixXd &leftSys, int n) {
278  double scale; // Output scale
279  double v1, v2; // Sum of the length of the vector
280  Eigen::VectorXd rightVec(
281  3); // Vector of the i^th particle in the right system
282  Eigen::VectorXd leftVec(3); // Vector of the i^th particle in the right system
283 
284  // scale = (sigma_to_n ||r_r||^2 / ||r_l||^2)^0.5
285  // ref: http://people.csail.mit.edu/bkph/papers/Absolute_Orientation.pdf
286 
287  // Loop through all the points, and get the dot product of the vector for each
288  // point
289  for (int i = 0; i < n; i++) {
290  //
291  rightVec = rightSys.row(i); // i^th row of the right system
292  leftVec = leftSys.row(i); // i^th row of the left system
293  v1 += rightVec.dot(rightVec);
294  v2 += leftVec.dot(leftVec);
295  } // end of loop through all points
296 
297  // The optimum scale is the ratio of v1 and v2
298  scale = std::sqrt(v1 / v2);
299 
300  return scale;
301 } // end of function
302 
309 Eigen::MatrixXd absor::quat2RotMatrix(const Eigen::VectorXd &quat) {
310  //
311  Eigen::MatrixXd R(3, 3); // Rotation matrix
312  // Components of the quaternion
313  double q0 = quat(0);
314  double qx = quat(1);
315  double qy = quat(2);
316  double qz = quat(3);
317 
318  // Quaternion derived rotation matrix, when q (q=q0+qx*i+qy*j+qz*k)
319  // is a unit quaternion:
320  // R=[(q0^2+qx^2+qy^2+qz^2) 2(qx*qy-q0*qz) 2(qx*qz+q0*qy);...
321  // 2(qy*qx-q0*qz) (q0^2+qx^2+qy^2+qz^2) 2(qy*qz-q0*qx);...
322  // 2(qz*qx-q0*qy) 2(qz*qy+q0*qz) (q0^2+qx^2+qy^2+qz^2)];
323 
324  // Fill up the rotation matrix R according to the above formula
325  //
326  // First row
327  R(0, 0) = q0 * q0 + qx * qx + qy * qy + qz * qz;
328  R(0, 1) = 2 * (qx * qy - q0 * qz);
329  R(0, 2) = 2 * (qx * qz + q0 * qy);
330  // Second row
331  R(1, 0) = 2 * (qy * qx + q0 * qz);
332  R(1, 1) = q0 * q0 + qx * qx + qy * qy + qz * qz;
333  R(1, 2) = 2 * (qy * qz - q0 * qy);
334  // Third row
335  R(2, 0) = 2 * (qz * qx - q0 * qy);
336  R(2, 1) = 2 * (qz * qy + q0 * qz);
337  R(2, 2) = q0 * q0 + qx * qx + qy * qy + qz * qz;
338 
339  // return the rotation matrix
340  return R;
341 } // end of function
342 
359 double absor::getRMSD(const Eigen::MatrixXd &centeredRefPnts,
360  const Eigen::MatrixXd &centeredTargetPnts,
361  const Eigen::VectorXd &quat,
362  std::vector<double> *rmsdList, int nop, double scale) {
363  Eigen::MatrixXd R(3, 3); // The (3x3) rotation vector
364  // The RMSD per atom is filled in this vector
365  (*rmsdList).resize(nop);
366  //
368  quat); // orthonormal rotation matrix from the quaternion
369  // The total error is:
370  // sum_over_all_n (r'_r - s*rotated_r_l')
371  double rmsd = 0.0; // Error
372  Eigen::VectorXd errorVec(3); // The vector which is the r_r -s*R
373  Eigen::VectorXd rotatedLeft(3);
374  Eigen::VectorXd targetCol(3);
375  Eigen::VectorXd refCol(3);
376  //
377  for (int i = 0; i < nop; i++) {
378  //
379  // Rotate the left system coordinate using the rotation matrix
380  targetCol = (centeredTargetPnts.row(i)).transpose();
381  refCol = (centeredRefPnts.row(i)).transpose();
382  //
383  rotatedLeft = R * targetCol;
384  errorVec = refCol - scale * rotatedLeft;
385  rmsd += errorVec.dot(errorVec); // Total error
386  (*rmsdList)[i] = sqrt(errorVec.dot(errorVec)); // Error per atom
387  } // end of loop through every row
388  //
389  return sqrt(rmsd / nop);
390 } // end of function
double getRMSD(const Eigen::MatrixXd &centeredRefPnts, const Eigen::MatrixXd &centeredTargetPnts, const Eigen::VectorXd &quat, std::vector< double > *rmsdList, int nop, double scale)
Calculate the RMSD.
Eigen::MatrixXd calcMatrixS(const Eigen::MatrixXd &centeredRefPnts, const Eigen::MatrixXd &centeredTargetPnts, int nop, int dim)
double calcScaleFactor(const Eigen::MatrixXd &rightSys, const Eigen::MatrixXd &leftSys, int n)
Calculate the scale factor from the centered left and right point sets.
Eigen::MatrixXd centerWRTcentroid(const Eigen::MatrixXd &pointSet)
Center a point set wrt the centroid.
int hornAbsOrientation(const Eigen::MatrixXd &refPoints, const Eigen::MatrixXd &targetPoints, std::vector< double > *quat, double *rmsd, std::vector< double > *rmsdList, double *scale)
Get the absolute orientation using Horn's algorithm (with quaternions)
Eigen::MatrixXd calcMatrixN(const Eigen::MatrixXd &S)
Eigen::MatrixXd quat2RotMatrix(const Eigen::VectorXd &quat)
Get a rotation matrix from a unit quaternion.
T resize(T... args)
T sqrt(T... args)