TooN
SymEigen.h
1 // -*- c++ -*-
2 
3 // Copyright (C) 2005,2009 Tom Drummond (twd20@cam.ac.uk)
4 
5 //All rights reserved.
6 //
7 //Redistribution and use in source and binary forms, with or without
8 //modification, are permitted provided that the following conditions
9 //are met:
10 //1. Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //2. Redistributions in binary form must reproduce the above copyright
13 // notice, this list of conditions and the following disclaimer in the
14 // documentation and/or other materials provided with the distribution.
15 //
16 //THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
17 //AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 //IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 //ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
20 //LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 //CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 //SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 //INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 //CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 //ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 //POSSIBILITY OF SUCH DAMAGE.
27 
28 #ifndef __SYMEIGEN_H
29 #define __SYMEIGEN_H
30 
31 #include <algorithm>
32 #include <iostream>
33 #include <cassert>
34 #include <cmath>
35 #include <utility>
36 #include <complex>
37 #include <TooN/lapack.h>
38 
39 #include <TooN/TooN.h>
40 
41 namespace TooN {
42 static const double root3=1.73205080756887729352744634150587236694280525381038062805580;
43 
44 namespace Internal{
45 
46  using std::swap;
47 
49  static const double symeigen_condition_no=1e9;
50 
56  template <int Size> struct ComputeSymEigen {
57 
63  template<int Rows, int Cols, typename P, typename B>
64  static inline void compute(const Matrix<Rows,Cols,P, B>& m, Matrix<Size,Size,P> & evectors, Vector<Size, P>& evalues) {
65 
66  SizeMismatch<Rows, Cols>::test(m.num_rows(), m.num_cols()); //m must be square
67  SizeMismatch<Size, Rows>::test(m.num_rows(), evalues.size()); //m must be the size of the system
68 
69 
70  evectors = m;
71  FortranInteger N = evalues.size();
72  FortranInteger lda = evalues.size();
73  FortranInteger info;
74  FortranInteger lwork=-1;
75  P size;
76 
77  // find out how much space fortran needs
78  syev_((char*)"V",(char*)"U",&N,&evectors[0][0],&lda,&evalues[0], &size,&lwork,&info);
79  lwork = int(size);
80  Vector<Dynamic, P> WORK(lwork);
81 
82  // now compute the decomposition
83  syev_((char*)"V",(char*)"U",&N,&evectors[0][0],&lda,&evalues[0], &WORK[0],&lwork,&info);
84 
85  if(info!=0){
86  std::cerr << "In SymEigen<"<<Size<<">: " << info
87  << " off-diagonal elements of an intermediate tridiagonal form did not converge to zero." << std::endl
88  << "M = " << m << std::endl;
89  }
90  }
91  };
92 
97  template <> struct ComputeSymEigen<2> {
98 
104  template<typename P, typename B>
105  static inline void compute(const Matrix<2,2,P,B>& m, Matrix<2,2,P>& eig, Vector<2, P>& ev) {
106  double trace = m[0][0] + m[1][1];
107  //Only use the upper triangular elements.
108  double det = m[0][0]*m[1][1] - m[0][1]*m[0][1];
109  double disc = trace*trace - 4 * det;
110 
111  //Mathematically, disc >= 0 always.
112  //Numerically, it can drift very slightly below zero.
113  if(disc < 0)
114  disc = 0;
115 
116  using std::sqrt;
117  double root_disc = sqrt(disc);
118  ev[0] = 0.5 * (trace - root_disc);
119  ev[1] = 0.5 * (trace + root_disc);
120  double a = m[0][0] - ev[0];
121  double b = m[0][1];
122  double magsq = a*a + b*b;
123  if (magsq == 0) {
124  eig[0][0] = 1.0;
125  eig[0][1] = 0;
126  } else {
127  eig[0][0] = -b;
128  eig[0][1] = a;
129  eig[0] *= 1.0/sqrt(magsq);
130  }
131  eig[1][0] = -eig[0][1];
132  eig[1][1] = eig[0][0];
133  }
134  };
135 
140  template <> struct ComputeSymEigen<3> {
141 
147  template<typename P, typename B>
148  static inline void compute(const Matrix<3,3,P,B>& m, Matrix<3,3,P>& eig, Vector<3, P>& ev) {
149  //method uses closed form solution of cubic equation to obtain roots of characteristic equation.
150  using std::sqrt;
151  using std::min;
152  using std::swap;
153 
154  double a_norm = norm_1(m);
155  double eps = 1e-7 * a_norm;
156 
157  if(a_norm == 0)
158  {
159  eig = TooN::Identity;
160  return;
161  }
162 
163  //Polynomial terms of |a - l * Identity|
164  //l^3 + a*l^2 + b*l + c
165 
166  const double& a11 = m[0][0];
167  const double& a12 = m[0][1];
168  const double& a13 = m[0][2];
169 
170  const double& a22 = m[1][1];
171  const double& a23 = m[1][2];
172 
173  const double& a33 = m[2][2];
174 
175  //From matlab:
176  double a = -a11-a22-a33;
177  double b = a11*a22+a11*a33+a22*a33-a12*a12-a13*a13-a23*a23;
178  double c = a11*(a23*a23)+(a13*a13)*a22+(a12*a12)*a33-a12*a13*a23*2.0-a11*a22*a33;
179 
180  //Using Cardano's method:
181  double p = b - a*a/3;
182  double q = c + (2*a*a*a - 9*a*b)/27;
183 
184  double alpha = -q/2;
185 
186  //beta_descriminant <= 0 for real roots!
187  //force to zero to avoid nasty rounding issues.
188  double beta_descriminant = std::min(0.0, q*q/4 + p*p*p/27);
189 
190  double beta = sqrt(-beta_descriminant);
191  double r2 = alpha*alpha - beta_descriminant;
192 
196  double cuberoot_r = pow(r2, 1./6);
197 
198  double theta3 = atan2(beta, alpha)/3;
199 
200  double A_plus_B = 2*cuberoot_r*cos(theta3);
201  double A_minus_B = -2*cuberoot_r*sin(theta3);
202 
203  //calculate eigenvalues
204  ev = makeVector(A_plus_B, -A_plus_B/2 + A_minus_B * sqrt(3)/2, -A_plus_B/2 - A_minus_B * sqrt(3)/2) - Ones * a/3;
205 
206  if(ev[0] > ev[1])
207  swap(ev[0], ev[1]);
208  if(ev[1] > ev[2])
209  swap(ev[1], ev[2]);
210  if(ev[0] > ev[1])
211  swap(ev[0], ev[1]);
212 
213  // for the vector [x y z]
214  // eliminate to compute the ratios x/z and y/z
215  // in both cases, the denominator is the same, so in the absence of
216  // any other scaling, choose the denominator to be z and
217  // tne numerators to be x and y.
218  //
219  // x/z and y/z, implies ax, ay, az which vanishes
220  // if a=0
221  //calculate the eigenvectors
222  eig[0][0]=a12 * a23 - a13 * (a22 - ev[0]);
223  eig[0][1]=a12 * a13 - a23 * (a11 - ev[0]);
224  eig[0][2]=(a11-ev[0])*(a22-ev[0]) - a12*a12;
225  eig[1][0]=a12 * a23 - a13 * (a22 - ev[1]);
226  eig[1][1]=a12 * a13 - a23 * (a11 - ev[1]);
227  eig[1][2]=(a11-ev[1])*(a22-ev[1]) - a12*a12;
228  eig[2][0]=a12 * a23 - a13 * (a22 - ev[2]);
229  eig[2][1]=a12 * a13 - a23 * (a11 - ev[2]);
230  eig[2][2]=(a11-ev[2])*(a22-ev[2]) - a12*a12;
231 
232  //Check to see if we have any zero vectors
233  double e0norm = norm_1(eig[0]);
234  double e1norm = norm_1(eig[1]);
235  double e2norm = norm_1(eig[2]);
236 
237  //Some of the vectors vanish: we're computing
238  // x/z and y/z, which implies ax, ay, az which vanishes
239  // if a=0;
240  //
241  // So compute the other two choices.
242  if(e0norm < eps)
243  {
244  eig[0][0] += a12 * (ev[0] - a33) + a23 * a13;
245  eig[0][1] += (ev[0]-a11)*(ev[0]-a33) - a13*a13;
246  eig[0][2] += a23 * (ev[0] - a11) + a12 * a13;
247  e0norm = norm_1(eig[0]);
248  }
249 
250  if(e1norm < eps)
251  {
252  eig[1][0] += a12 * (ev[1] - a33) + a23 * a13;
253  eig[1][1] += (ev[1]-a11)*(ev[1]-a33) - a13*a13;
254  eig[1][2] += a23 * (ev[1] - a11) + a12 * a13;
255  e1norm = norm_1(eig[1]);
256  }
257 
258  if(e2norm < eps)
259  {
260  eig[2][0] += a12 * (ev[2] - a33) + a23 * a13;
261  eig[2][1] += (ev[2]-a11)*(ev[2]-a33) - a13*a13;
262  eig[2][2] += a23 * (ev[2] - a11) + a12 * a13;
263  e2norm = norm_1(eig[2]);
264  }
265 
266 
267  //OK, a AND b might be 0
268  //Check for vanishing and add in y/x, z/x which implies cx, cy, cz
269  if(e0norm < eps)
270  {
271  eig[0][0] +=(ev[0]-a22)*(ev[0]-a33) - a23*a23;
272  eig[0][1] +=a12 * (ev[0] - a33) + a23 * a13;
273  eig[0][2] +=a13 * (ev[0] - a22) + a23 * a12;
274  e0norm = norm_1(eig[0]);
275  }
276 
277  if(e1norm < eps)
278  {
279  eig[1][0] +=(ev[1]-a22)*(ev[1]-a33) - a23*a23;
280  eig[1][1] +=a12 * (ev[1] - a33) + a23 * a13;
281  eig[1][2] +=a13 * (ev[1] - a22) + a23 * a12;
282  e1norm = norm_1(eig[1]);
283  }
284 
285  if(e2norm < eps)
286  {
287  eig[2][0] +=(ev[2]-a22)*(ev[2]-a33) - a23*a23;
288  eig[2][1] +=a12 * (ev[2] - a33) + a23 * a13;
289  eig[2][2] +=a13 * (ev[2] - a22) + a23 * a12;
290  e2norm = norm_1(eig[2]);
291  }
292 
293  //Oh, COME ON!!!
294  if(e0norm < eps || e1norm < eps || e2norm < eps)
295  {
296  //This is blessedly rare
297 
298  double ns[] = {e0norm, e1norm, e2norm};
299  double is[] = {0, 1, 2};
300 
301  //Sort them
302  if(ns[0] > ns[1])
303  {
304  swap(ns[0], ns[1]);
305  swap(is[0], is[1]);
306  }
307  if(ns[1] > ns[2])
308  {
309  swap(ns[1], ns[2]);
310  swap(is[1], is[2]);
311  }
312  if(ns[0] > ns[1])
313  {
314  swap(ns[0], ns[1]);
315  swap(is[0], is[1]);
316  }
317 
318 
319  if(ns[1] >= eps)
320  {
321  //one small one. Use the cross product of the other two
322  normalize(eig[1]);
323  normalize(eig[2]);
324  eig[is[0]] = eig[is[1]]^eig[is[2]];
325  }
326  else if(ns[2] >= eps)
327  {
328  normalize(eig[is[2]]);
329 
330  //Permute vector to get a new vector with some orthogonal components.
331  Vector<3> p = makeVector(eig[is[2]][1], eig[is[2]][2], eig[is[2]][0]);
332 
333  //Gram-Schmit
334  Vector<3> v1 = unit(p - eig[is[2]] * (p * eig[is[2]]));
335  Vector<3> v2 = v1^eig[is[2]];
336 
337  eig[is[0]] = v1;
338  eig[is[1]] = v2;
339  }
340  else
341  eig = TooN::Identity;
342  }
343  else
344  {
345  normalize(eig[0]);
346  normalize(eig[1]);
347  normalize(eig[2]);
348  }
349  }
350  };
351 
352 };
353 
402 template <int Size=Dynamic, typename Precision = double>
403 class SymEigen {
404 public:
405  inline SymEigen(){}
406 
411  inline SymEigen(int m) : my_evectors(m,m), my_evalues(m) {}
412 
415  template<int R, int C, typename B>
416  inline SymEigen(const Matrix<R, C, Precision, B>& m) : my_evectors(m.num_rows(), m.num_cols()), my_evalues(m.num_rows()) {
417  compute(m);
418  }
419 
421  template<int R, int C, typename B>
422  inline void compute(const Matrix<R,C,Precision,B>& m){
423  SizeMismatch<R, C>::test(m.num_rows(), m.num_cols());
424  SizeMismatch<R, Size>::test(m.num_rows(), my_evectors.num_rows());
425  Internal::ComputeSymEigen<Size>::compute(m, my_evectors, my_evalues);
426  }
427 
432  template <int S, typename P, typename B>
434  return (my_evectors.T() * diagmult(get_inv_diag(Internal::symeigen_condition_no),(my_evectors * rhs)));
435  }
436 
441  template <int R, int C, typename P, typename B>
443  return (my_evectors.T() * diagmult(get_inv_diag(Internal::symeigen_condition_no),(my_evectors * rhs)));
444  }
445 
451  Matrix<Size, Size, Precision> get_pinv(const double condition=Internal::symeigen_condition_no) const {
452  return my_evectors.T() * diagmult(get_inv_diag(condition),my_evectors);
453  }
454 
461  Vector<Size, Precision> get_inv_diag(const double condition) const {
462  Precision max_diag = -my_evalues[0] > my_evalues[my_evalues.size()-1] ? -my_evalues[0]:my_evalues[my_evalues.size()-1];
463  Vector<Size, Precision> invdiag(my_evalues.size());
464  for(int i=0; i<my_evalues.size(); i++){
465  if(fabs(my_evalues[i]) * condition > max_diag) {
466  invdiag[i] = 1/my_evalues[i];
467  } else {
468  invdiag[i]=0;
469  }
470  }
471  return invdiag;
472  }
473 
479  Matrix<Size,Size,Precision>& get_evectors() {return my_evectors;}
480 
483  const Matrix<Size,Size,Precision>& get_evectors() const {return my_evectors;}
484 
485 
489  Vector<Size, Precision>& get_evalues() {return my_evalues;}
492  const Vector<Size, Precision>& get_evalues() const {return my_evalues;}
493 
495  bool is_posdef() const {
496  for (int i = 0; i < my_evalues.size(); ++i) {
497  if (my_evalues[i] <= 0.0)
498  return false;
499  }
500  return true;
501  }
502 
504  bool is_negdef() const {
505  for (int i = 0; i < my_evalues.size(); ++i) {
506  if (my_evalues[i] >= 0.0)
507  return false;
508  }
509  return true;
510  }
511 
513  Precision get_determinant () const {
514  Precision det = 1.0;
515  for (int i = 0; i < my_evalues.size(); ++i) {
516  det *= my_evalues[i];
517  }
518  return det;
519  }
520 
524  Vector<Size, Precision> diag_sqrt(my_evalues.size());
525  // In the future, maybe throw an exception if an
526  // eigenvalue is negative?
527  for (int i = 0; i < my_evalues.size(); ++i) {
528  diag_sqrt[i] = std::sqrt(my_evalues[i]);
529  }
530  return my_evectors.T() * diagmult(diag_sqrt, my_evectors);
531  }
532 
539  Matrix<Size, Size, Precision> get_isqrtm (const double condition=Internal::symeigen_condition_no) const {
540  Vector<Size, Precision> diag_isqrt(my_evalues.size());
541 
542  // Because sqrt is a monotonic-preserving transformation,
543  Precision max_diag = -my_evalues[0] > my_evalues[my_evalues.size()-1] ? (-std::sqrt(my_evalues[0])):(std::sqrt(my_evalues[my_evalues.size()-1]));
544  // In the future, maybe throw an exception if an
545  // eigenvalue is negative?
546  for (int i = 0; i < my_evalues.size(); ++i) {
547  diag_isqrt[i] = std::sqrt(my_evalues[i]);
548  if(fabs(diag_isqrt[i]) * condition > max_diag) {
549  diag_isqrt[i] = 1/diag_isqrt[i];
550  } else {
551  diag_isqrt[i] = 0;
552  }
553  }
554  return my_evectors.T() * diagmult(diag_isqrt, my_evectors);
555  }
556 
557 private:
558  // eigen vectors laid out row-wise so evectors[i] is the ith evector
559  Matrix<Size,Size,Precision> my_evectors;
560 
561  Vector<Size, Precision> my_evalues;
562 };
563 
564 }
565 
566 #endif
567 
const Matrix< Size, Size, Precision > & get_evectors() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: SymEigen.h:483
void compute(const Matrix< R, C, Precision, B > &m)
Perform the eigen decomposition of a matrix.
Definition: SymEigen.h:422
Precision trace(const Matrix< Rows, Cols, Precision, Base > &m)
computes the trace of a square matrix
Definition: helpers.h:432
Matrix< Size, Size, Precision > get_pinv(const double condition=Internal::symeigen_condition_no) const
Calculate (pseudo-)inverse of the matrix.
Definition: SymEigen.h:451
Matrix< Size, C, Precision > backsub(const Matrix< R, C, P, B > &rhs) const
Calculate result of multiplying the (pseudo-)inverse of M by another matrix.
Definition: SymEigen.h:442
Pretty generic SFINAE introspection generator.
Definition: vec_test.cc:21
A vector.
Definition: vector.hh:126
void normalize(Vector< Size, Precision, Base > &&v)
Normalize a vector in place.
Definition: helpers.h:162
Vector< Size, Precision > backsub(const Vector< S, P, B > &rhs) const
Calculate result of multiplying the (pseudo-)inverse of M by a vector.
Definition: SymEigen.h:433
Precision get_determinant() const
Get the determinant of the matrix.
Definition: SymEigen.h:513
A matrix.
Definition: matrix.hh:105
Matrix< Size, Size, Precision > get_sqrtm() const
Calculate the square root of a matrix which is a matrix M such that M.T*M=A.
Definition: SymEigen.h:523
Precision norm_1(const Vector< Size, Precision, Base > &v)
Compute the norm of v.
Definition: helpers.h:114
bool is_posdef() const
Is the matrix positive definite?
Definition: SymEigen.h:495
Vector< Size, Precision > & get_evalues()
Returns the eigenvalues of the matrix.
Definition: SymEigen.h:489
Vector< Size, Precision > get_inv_diag(const double condition) const
Calculates the reciprocals of the eigenvalues of the matrix.
Definition: SymEigen.h:461
Definition: SymEigen.h:56
const Vector< Size, Precision > & get_evalues() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: SymEigen.h:492
Matrix< R, C, P > sqrt(const Matrix< R, C, P, B > &m)
computes a matrix square root of a matrix m by the product form of the Denman and Beavers iteration a...
Definition: helpers.h:350
Definition: size_mismatch.hh:103
bool is_negdef() const
Is the matrix negative definite?
Definition: SymEigen.h:504
Performs eigen decomposition of a matrix.
Definition: SymEigen.h:403
Matrix< Size, Size, Precision > get_isqrtm(const double condition=Internal::symeigen_condition_no) const
Calculate the inverse square root of a matrix which is a matrix M such that M.T*M=A^-1.
Definition: SymEigen.h:539
static void compute(const Matrix< 3, 3, P, B > &m, Matrix< 3, 3, P > &eig, Vector< 3, P > &ev)
Definition: SymEigen.h:148
Matrix< Size, Size, Precision > & get_evectors()
Returns the eigenvectors of the matrix.
Definition: SymEigen.h:479
SymEigen(int m)
Initialise this eigen decomposition but do no immediately perform a decomposition.
Definition: SymEigen.h:411
SymEigen(const Matrix< R, C, Precision, B > &m)
Construct the eigen decomposition of a matrix.
Definition: SymEigen.h:416
Vector< Size, Precision > unit(const Vector< Size, Precision, Base > &v)
Compute a the unit vector .
Definition: helpers.h:153