omega_h
Reliable mesh adaptation
Omega_h_eigen.hpp
1 #ifndef OMEGA_H_EIGEN_HPP
2 #define OMEGA_H_EIGEN_HPP
3 
4 #include <Omega_h_matrix.hpp>
5 
6 namespace Omega_h {
7 
8 template <Int degree>
9 struct Roots {
10  Int n; // number of unique roots
11  Few<Real, degree> values;
12  Few<Int, degree> mults; // multiplicities
13 };
14 
15 // solve cubic equation x^3 + a_2 * x^2 + a_1 * x + a_0 = 0
16 // this code assumes that the solution does not have complex roots !
17 // the return value is the number of distinct roots,
18 // the two output arrays contain root values and multiplicities.
19 // roots within an *absolute* distance of (eps) are considered
20 // the same.
21 OMEGA_H_INLINE Roots<3> find_polynomial_roots(
22  Few<Real, 3> coeffs, Real eps = 1e-6) {
23  auto a_0 = coeffs[0];
24  auto a_1 = coeffs[1];
25  auto a_2 = coeffs[2];
26  Few<Real, 3> roots;
27  roots[0] = roots[1] = roots[2] = 0.0;
28  Few<Int, 3> mults;
29  mults[0] = mults[1] = mults[2] = 0;
30  // http://mathworld.wolfram.com/CubicFormula.html
31  Real p = (3. * a_1 - square(a_2)) / 3.;
32  Real q = (9. * a_1 * a_2 - 27. * a_0 - 2. * cube(a_2)) / 27.;
33  Real Q = p / 3.;
34  Real R = q / 2.;
35  Real D = cube(Q) + square(R);
36  Real shift = -a_2 / 3.;
37  if (D >= 0.0) {
38  Real S = std::cbrt(R + std::sqrt(D));
39  Real T = std::cbrt(R - std::sqrt(D));
40  Real B = S + T;
41  Real z_1 = shift + B;
42  Real z_23_real = shift - (1. / 2.) * B;
43  // warning: we simply assume the imaginary component is small !
44  // Real z_23_imag = (1. / 2.) * sqrt(3.) * A;
45  roots[0] = z_1;
46  roots[1] = roots[2] = z_23_real;
47  } else {
48  // D < 0 implies Q < 0, since R^2 must be positive
49  auto cos_theta = R / std::sqrt(-cube(Q));
50  Real theta = std::acos(clamp(cos_theta, -1.0, 1.0));
51  Real radius = 2. * std::sqrt(-Q);
52  Real z_1 = radius * std::cos((theta) / 3.) + shift;
53  Real z_2 = radius * std::cos((theta + 2. * PI) / 3.) + shift;
54  Real z_3 = radius * std::cos((theta - 2. * PI) / 3.) + shift;
55  roots[0] = z_1;
56  roots[1] = z_2;
57  roots[2] = z_3;
58  }
59  mults[0] = mults[1] = mults[2] = 1;
60  // post-processing, decide whether pairs of roots are
61  // close enough to be called repeated roots
62  // first step, if two roots are close, then
63  // move them to the second and third slots
64  if (std::abs(roots[0] - roots[1]) < eps) {
65  swap2(roots[0], roots[2]);
66  } else if (std::abs(roots[0] - roots[2]) < eps) {
67  swap2(roots[0], roots[1]);
68  } else if (std::abs(roots[1] - roots[2]) < eps) {
69  // no need to swap, they're already there
70  } else {
71  // no pairs were close, all three roots are distinct
72  return {3, roots, mults};
73  }
74  // if we're here, two close roots are in [1] and [2]
75  roots[1] = average(roots[1], roots[2]);
76  mults[1] = 2;
77  // lets see if they are all the same
78  if (std::abs(roots[0] - roots[1]) < eps) {
79  // roots[1] is already an average, weight it properly
80  roots[0] = (1. / 3.) * roots[0] + (2. / 3.) * roots[1];
81  mults[0] = 3;
82  return {1, roots, mults};
83  }
84  return {2, roots, mults};
85 }
86 
87 // solve quadratic equation x^2 + a * x + b = 0
88 OMEGA_H_INLINE Roots<2> find_polynomial_roots(
89  Few<Real, 2> coeffs, Real eps = 1e-6) {
90  auto a = coeffs[1];
91  auto b = coeffs[0];
92  Few<Real, 2> roots;
93  roots[0] = roots[1] = 0.0;
94  Few<Int, 2> mults;
95  mults[0] = mults[1] = 0;
96  Real disc = square(a) - 4. * b;
97  if (std::abs(disc) < eps) {
98  mults[0] = 2;
99  roots[0] = -a / 2.;
100  roots[1] = roots[0];
101  mults[1] = mults[0];
102  return {1, roots, mults};
103  }
104  if (disc > 0.0) {
105  mults[0] = 1;
106  mults[1] = 1;
107  roots[0] = (-a + std::sqrt(disc)) / 2.;
108  roots[1] = (-a - std::sqrt(disc)) / 2.;
109  return {2, roots, mults};
110  }
111  return {0, roots, mults};
112 }
113 
114 // solve linear equation x + a = 0
115 OMEGA_H_INLINE Roots<1> find_polynomial_roots(
116  Few<Real, 1> coeffs, Real eps = 1e-6) {
117  (void)eps;
118  auto a = coeffs[0];
119  Few<Real, 1> roots;
120  roots[0] = -a;
121  Few<Int, 1> mults;
122  mults[0] = 1;
123  return {1, roots, mults};
124 }
125 
126 /* http://mathworld.wolfram.com/CharacteristicPolynomial.html */
127 OMEGA_H_INLINE Few<Real, 3> characteristic_polynomial(Tensor<3> A) {
128  auto tA = trace(A);
129  Few<Real, 3> coeffs;
130  coeffs[2] = -tA;
131  coeffs[1] = (1. / 2.) * ((tA * tA) - trace(A * A));
132  coeffs[0] = -determinant(A);
133  return coeffs;
134 }
135 
136 /* http://mathworld.wolfram.com/CharacteristicPolynomial.html */
137 OMEGA_H_INLINE Few<Real, 2> characteristic_polynomial(Tensor<2> A) {
138  Few<Real, 2> coeffs;
139  coeffs[1] = -trace(A);
140  coeffs[0] = determinant(A);
141  return coeffs;
142 }
143 
144 OMEGA_H_INLINE Few<Real, 1> characteristic_polynomial(Tensor<1> A) {
145  Few<Real, 1> coeffs;
146  coeffs[0] = -determinant(A);
147  return coeffs;
148 }
149 
150 template <Int n>
151 OMEGA_H_INLINE Roots<n> get_eigenvalues(Tensor<n> A) {
152  auto poly = characteristic_polynomial(A);
153  // WARNING: I no longer remember the source of this magic number.
154  // was probably tuned to avoid failures with the cubic solver
155  return find_polynomial_roots(poly, 5e-5);
156 }
157 
158 template <Int m>
159 OMEGA_H_INLINE Tensor<m> subtract_from_diag(Tensor<m> a, Real const mu) {
160  for (Int i = 0; i < m; ++i) a[i][i] -= mu;
161  return a;
162 }
163 
164 /* the null space of the matrix (s = m - l*I)
165  is the space spanned by the eigenvectors.
166  the multiplicity of this root is the dimensionality
167  of that space, i.e. the number of eigenvectors.
168  since the null space is the orthogonal component of
169  the row span, we essentially first find the row space */
170 
171 /* in the case that the null space is 1D and space is 3D,
172  then the row space is 2D (a plane in 3D)
173  take the largest cross product of any pair of rows,
174  that should give a vector in the null space */
175 OMEGA_H_INLINE Vector<3> single_eigenvector(Tensor<3> const m, Real const l) {
176  auto s = transpose(subtract_from_diag(m, l));
177  auto v = cross(s[0], s[1]);
178  auto v_norm = norm(v);
179  auto c = cross(s[1], s[2]);
180  auto c_norm = norm(c);
181  if (c_norm > v_norm) {
182  v = c;
183  v_norm = c_norm;
184  }
185  c = cross(s[0], s[2]);
186  c_norm = norm(c);
187  if (c_norm > v_norm) {
188  v = c;
189  v_norm = c_norm;
190  }
191  OMEGA_H_CHECK(v_norm > EPSILON);
192  v = v / v_norm;
193  return v;
194 }
195 
196 /* in the case that all rows of (a) are linearly dependent,
197  this function will return the unit vector of the row
198  that had the highest norm, which is a basis for the row space */
199 template <Int m>
200 OMEGA_H_INLINE Vector<m> get_1d_row_space(Tensor<m> const a) {
201  auto ta = transpose(a);
202  auto best_row = 0;
203  auto best_norm = norm(ta[best_row]);
204  for (Int i = 1; i < m; ++i) {
205  auto row_norm = norm(ta[i]);
206  if (row_norm > best_norm) {
207  best_row = i;
208  best_norm = row_norm;
209  }
210  }
211  OMEGA_H_CHECK(best_norm > EPSILON);
212  return ta[best_row] / best_norm;
213 }
214 
215 /* in the case that the null space is 2D and space is 3D,
216  find two vectors that are orthogonal to the 1D row space */
217 OMEGA_H_INLINE Few<Vector<3>, 2> double_eigenvector(
218  Tensor<3> const m, Real const l) {
219  auto s = subtract_from_diag(m, l);
220  auto n = get_1d_row_space(s);
221  auto b = form_ortho_basis(n);
222  Few<Vector<3>, 2> o;
223  o[0] = b[1];
224  o[1] = b[2];
225  return o;
226 }
227 
228 template <Int dim>
229 struct DiagDecomp {
230  Tensor<dim> q;
231  Vector<dim> l;
232 };
233 
234 OMEGA_H_INLINE DiagDecomp<3> decompose_eigen_dim(Tensor<3> const m) {
235  auto roots_obj = get_eigenvalues(m);
236  auto nroots = roots_obj.n;
237  auto roots = roots_obj.values;
238  auto mults = roots_obj.mults;
239  /* there are only a few output cases, see solve_cubic() */
240  Tensor<3> q;
241  Vector<3> l;
242  if (nroots == 3) {
243  for (Int i = 0; i < 3; ++i) {
244  q[i] = single_eigenvector(m, roots[i]);
245  l[i] = roots[i];
246  }
247  } else if (nroots == 2 && mults[1] == 2) {
248  q[0] = single_eigenvector(m, roots[0]);
249  l[0] = roots[0];
250  auto dev = double_eigenvector(m, roots[1]);
251  q[1] = dev[0];
252  q[2] = dev[1];
253  l[1] = l[2] = roots[1];
254  } else {
255  OMEGA_H_CHECK(nroots == 1 && mults[0] == 3);
256  l[0] = l[1] = l[2] = roots[0];
257  q = identity_matrix<3, 3>();
258  }
259  return {q, l};
260 }
261 
262 /* in the case that the null space is 1D and space is 2D,
263  find the basis vector for the 1D row space and rotate it 90 deg */
264 OMEGA_H_INLINE Vector<2> single_eigenvector(Tensor<2> const m, Real const l) {
265  return perp(get_1d_row_space(subtract_from_diag(m, l)));
266 }
267 
268 OMEGA_H_INLINE DiagDecomp<2> decompose_eigen_dim(Tensor<2> const m) {
269  auto roots_obj = get_eigenvalues(m);
270  auto nroots = roots_obj.n;
271  auto roots = roots_obj.values;
272  /* there are only a few output cases, see solve_quadratic() */
273  Tensor<2> q;
274  Vector<2> l;
275  if (nroots == 2) {
276  for (Int i = 0; i < 2; ++i) {
277  q[i] = single_eigenvector(m, roots[i]);
278  l[i] = roots[i];
279  }
280  } else {
281  OMEGA_H_CHECK(nroots == 1 && roots_obj.mults[0] == 2);
282  l[0] = l[1] = roots[0];
283  q = identity_matrix<2, 2>();
284  }
285  return {q, l};
286 }
287 
288 OMEGA_H_INLINE DiagDecomp<1> decompose_eigen_dim(Tensor<1> const m) {
289  auto roots_obj = get_eigenvalues(m);
290  auto roots = roots_obj.values;
291  OMEGA_H_CHECK(are_close(roots[0], m[0][0]));
292  return {matrix_1x1(1.0), vector_1(roots[0])};
293 }
294 
295 /* decompose an m x m matrix (where m <= 3) into
296  eigenvalues and eigenvectors.
297 
298  note that (q) in the output is a matrix whose
299  columns are the right eigenvectors.
300  hence it actually corresponds to (Q^{-T})
301  in the eigendecomposition
302  M = Q \Lambda Q^{-1}
303  where Q is the change of basis matrix
304  the output should satisfy
305  m ~= transpose(q * diagonal(l) * invert(q)) */
306 template <Int dim>
307 OMEGA_H_INLINE DiagDecomp<dim> decompose_eigen(Tensor<dim> m) {
308  /* the cubic solver is especially sensitive to dynamic
309  range. what we can do is to normalize the input matrix
310  and then re-apply that norm to the resulting roots */
311  Real nm = max_norm(m);
312  if (nm <= EPSILON) {
313  /* this is the zero matrix... */
314  return {identity_matrix<dim, dim>(), zero_vector<dim>()};
315  }
316  m = m / nm;
317  auto decomp = decompose_eigen_dim(m);
318  return {decomp.q, decomp.l * nm};
319 }
320 
321 /* Q, again, being the matrix whose columns
322  are the right eigenvectors, but not necessarily unitary */
323 template <Int dim>
324 OMEGA_H_INLINE Tensor<dim> compose_eigen(
325  Tensor<dim> const q, Vector<dim> const l) {
326  return q * diagonal(l) * invert(q);
327 }
328 
329 /* like the above, but knowing Q is unitary,
330  so the transpose is the inverse */
331 template <Int dim>
332 OMEGA_H_INLINE Tensor<dim> compose_ortho(Tensor<dim> const q, Vector<dim> l) {
333  return q * diagonal(l) * transpose(q);
334 }
335 
336 // R^N off-diagonal norm. Useful for SVD and other algorithms
337 // that rely on Jacobi-type procedures.
338 // \param a
339 // \return \f$ \sqrt(\sum_i \sum_{j, j\neq i} a_{ij}^2) \f$
340 template <Int dim>
341 OMEGA_H_INLINE Real norm_off_diag(Tensor<dim> const a) {
342  Real s = 0.0;
343  for (Int j = 0; j < dim; ++j) {
344  for (Int i = 0; i < dim; ++i) {
345  if (i != j) {
346  s += square(a(i, j));
347  }
348  }
349  }
350  return std::sqrt(s);
351 }
352 
353 // R^N arg max off-diagonal. Useful for SVD and other algorithms
354 // that rely on Jacobi-type procedures.
355 // \param a
356 // \return \f$ (p,q) = arg max_{i \neq j} |a_{ij}| \f$
357 template <Int dim>
358 OMEGA_H_INLINE Few<Int, 2> arg_max_off_diag(Tensor<dim> const a) {
359  Int p = 0;
360  Int q = 0;
361  auto s = -1.0;
362  for (Int j = 0; j < dim; ++j) {
363  for (Int i = 0; i < dim; ++i) {
364  auto s2 = std::abs(a(i, j));
365  if (i != j && s2 > s) {
366  p = i;
367  q = j;
368  s = s2;
369  }
370  }
371  }
372  Few<Int, 2> out;
373  out[0] = min2(p, q);
374  out[1] = max2(p, q);
375  return out;
376 }
377 
378 // Symmetric Schur algorithm for R^2.
379 // \param \f$ A = [f, g; g, h] \in S(2) \f$
380 // \return \f$ c, s \rightarrow [c, -s; s, c]\f diagonalizes A$
381 OMEGA_H_INLINE Vector<2> schur_sym(Real f, Real g, Real h) {
382  Real c = 1.0;
383  Real s = 0.0;
384  if (g != 0.0) {
385  Real t = (h - f) / (2.0 * g);
386  if (t >= 0.0) {
387  t = 1.0 / (std::sqrt(1.0 + square(t)) + t);
388  } else {
389  t = -1.0 / (std::sqrt(1.0 + square(t)) - t);
390  }
391  c = 1.0 / std::sqrt(1.0 + square(t));
392  s = t * c;
393  }
394  return vector_2(c, s);
395 }
396 
397 /* Apply Givens-Jacobi rotation on the left */
398 template <Int dim>
399 OMEGA_H_INLINE Tensor<dim> givens_left(
400  Real const c, Real const s, Int const i, Int const k, Tensor<dim> a) {
401  for (Int j = 0; j < dim; ++j) {
402  auto t1 = a(i, j);
403  auto t2 = a(k, j);
404  a(i, j) = c * t1 - s * t2;
405  a(k, j) = s * t1 + c * t2;
406  }
407  return a;
408 }
409 
410 /* Apply Givens-Jacobi rotation on the right */
411 template <Int dim>
412 OMEGA_H_INLINE Tensor<dim> givens_right(
413  Real const c, Real const s, Int const i, Int const k, Tensor<dim> a) {
414  for (Int j = 0; j < dim; ++j) {
415  auto t1 = a(j, i);
416  auto t2 = a(j, k);
417  a(j, i) = c * t1 - s * t2;
418  a(j, k) = s * t1 + c * t2;
419  }
420  return a;
421 }
422 
423 /* the following is the Classic Jacobi algorithm, copied
424  from the MiniTensor package, which in turn is based on
425  algorithm 8.4.2 in Matrix Computations, Golub & Van Loan 1996 */
426 template <Int dim>
427 OMEGA_H_INLINE DiagDecomp<dim> decompose_eigen_jacobi(
428  Tensor<dim> a, Real const eps = DBL_EPSILON, Int max_iter = -1) {
429  // Estimate based on random generation and linear regression.
430  // Golub & Van Loan p 429 expect ~ dimension * log(dimension)
431  if (max_iter == -1) max_iter = (5 * dim * dim) / 2;
432  auto v = identity_matrix<dim, dim>();
433  auto tol = eps * norm(a);
434  Int iter = 0;
435  while (norm_off_diag(a) > tol && iter < max_iter) {
436  auto pq = arg_max_off_diag(a);
437  auto p = pq[0];
438  auto q = pq[1];
439  auto f = a(p, p);
440  auto g = a(p, q);
441  auto h = a(q, q);
442  auto cs = schur_sym(f, g, h);
443  auto c = cs[0];
444  auto s = cs[1];
445  a = givens_left(c, s, p, q, a);
446  a = givens_right(c, s, p, q, a);
447  v = givens_right(c, s, p, q, v);
448  ++iter;
449  }
450  return {v, diagonal(a)};
451 }
452 
453 template <Int dim>
454 OMEGA_H_INLINE DiagDecomp<dim> sort_by_magnitude(DiagDecomp<dim> const dd) {
455  Few<Int, dim> perm;
456  for (Int i = 0; i < dim; ++i) perm[i] = i;
457  for (Int i = 0; i < dim; ++i) {
458  auto max_j = i;
459  auto max_m = std::abs(dd.l[perm[max_j]]);
460  for (Int j = i + 1; j < dim; ++j) {
461  auto m = std::abs(dd.l[perm[j]]);
462  if (m > max_m) max_j = j;
463  }
464  swap2(perm[i], perm[max_j]);
465  }
466  DiagDecomp<dim> out;
467  for (Int i = 0; i < dim; ++i) out.l[i] = dd.l[perm[i]];
468  for (Int i = 0; i < dim; ++i) out.q[i] = dd.q[perm[i]];
469  return out;
470 }
471 
472 // logarithm of a symmetric positive definite tensor
473 template <Int dim>
474 OMEGA_H_INLINE_BIG Tensor<dim> log_spd_old(
475  Tensor<dim> const m) OMEGA_H_NOEXCEPT {
476  auto decomp = decompose_eigen(m);
477  for (Int i = 0; i < dim; ++i) decomp.l[i] = std::log(decomp.l[i]);
478  return compose_ortho(decomp.q, decomp.l);
479 }
480 
481 // exponential resulting in a symmetric positive definite tensor
482 template <Int dim>
483 OMEGA_H_INLINE_BIG Tensor<dim> exp_spd_old(
484  Tensor<dim> const m) OMEGA_H_NOEXCEPT {
485  auto decomp = decompose_eigen(m);
486  for (Int i = 0; i < dim; ++i) decomp.l[i] = std::exp(decomp.l[i]);
487  return compose_ortho(decomp.q, decomp.l);
488 }
489 
490 // logarithm of a symmetric positive definite tensor
491 template <Int dim>
492 OMEGA_H_INLINE_BIG Tensor<dim> log_spd(Tensor<dim> const m) OMEGA_H_NOEXCEPT {
493  auto decomp = decompose_eigen_jacobi(m);
494  for (Int i = 0; i < dim; ++i) decomp.l[i] = std::log(decomp.l[i]);
495  return compose_ortho(decomp.q, decomp.l);
496 }
497 
498 // exponential resulting in a symmetric positive definite tensor
499 template <Int dim>
500 OMEGA_H_INLINE_BIG Tensor<dim> exp_spd(Tensor<dim> const m) OMEGA_H_NOEXCEPT {
501  auto decomp = decompose_eigen_jacobi(m);
502  for (Int i = 0; i < dim; ++i) decomp.l[i] = std::exp(decomp.l[i]);
503  return compose_ortho(decomp.q, decomp.l);
504 }
505 
506 // exponential resulting in a symmetric positive definite tensor
507 template <Int dim>
508 OMEGA_H_INLINE_BIG Tensor<dim> sqrt_spd(Tensor<dim> const m) OMEGA_H_NOEXCEPT {
509  auto decomp = decompose_eigen_jacobi(m);
510  for (Int i = 0; i < dim; ++i) decomp.l[i] = std::sqrt(decomp.l[i]);
511  return compose_ortho(decomp.q, decomp.l);
512 }
513 
514 Reals get_max_eigenvalues(Int dim, Reals symms);
515 
516 } // end namespace Omega_h
517 
518 #endif
Definition: amr_mpi_test.cpp:6
Definition: Omega_h_eigen.hpp:229
Definition: Omega_h_eigen.hpp:9