omega_h
Reliable mesh adaptation
Omega_h_svd.hpp
1 #ifndef OMEGA_H_SVD_HPP
2 #define OMEGA_H_SVD_HPP
3 
4 #include <Omega_h_eigen.hpp>
5 
6 namespace Omega_h {
7 
8 //
9 // Givens rotation. [c, -s; s, c] [a; b] = [r; 0]
10 // \param a, b
11 // \return c, s
12 //
13 
14 struct Givens {
15  Real c;
16  Real s;
17 };
18 
19 OMEGA_H_INLINE
20 Givens givens(Real const a, Real const b) noexcept {
21  auto c = 1.0;
22  auto s = 0.0;
23  if (b != 0.0) {
24  if (std::abs(b) > std::abs(a)) {
25  auto const t = -a / b;
26  s = 1.0 / std::sqrt(1.0 + t * t);
27  c = t * s;
28  } else {
29  auto const t = -b / a;
30  c = 1.0 / std::sqrt(1.0 + t * t);
31  s = t * c;
32  }
33  }
34  return {c, s};
35 }
36 
37 template <Int dim>
38 struct SVD {
42 };
43 
44 //
45 // Singular value decomposition (SVD) for 2x2
46 // bidiagonal matrix. Used for general 2x2 SVD.
47 // Adapted from LAPAPCK's DLASV2, Netlib's dlasv2.c
48 // and LBNL computational crystallography toolbox
49 // \param f, g, h where A = [f, g; 0, h]
50 // \return \f$ A = USV^T\f$
51 //
52 OMEGA_H_INLINE
53 SVD<2> svd_bidiagonal(Real f, Real const g, Real h) noexcept {
54  auto fa = std::abs(f);
55  auto ga = std::abs(g);
56  auto ha = std::abs(h);
57  auto s0 = 0.0;
58  auto s1 = 0.0;
59  auto cu = 1.0;
60  auto su = 0.0;
61  auto cv = 1.0;
62  auto sv = 0.0;
63  auto const swap_diag = (ha > fa);
64  if (swap_diag == true) {
65  swap2(fa, ha);
66  swap2(f, h);
67  }
68  // diagonal matrix
69  if (ga == 0.0) {
70  s1 = ha;
71  s0 = fa;
72  } else if (ga > fa && fa / ga < DBL_EPSILON) {
73  // case of very large ga
74  s0 = ga;
75  s1 = ha > 1.0 ? Real(fa / (ga / ha)) : Real((fa / ga) * ha);
76  cu = 1.0;
77  su = h / g;
78  cv = f / g;
79  sv = 1.0;
80  } else {
81  // normal case
82  auto const d = fa - ha;
83  auto const l = d / fa; // l \in [0,1]
84  auto const m = g / f; // m \in (-1/macheps, 1/macheps)
85  auto const t = 2.0 - l; // t \in [1,2]
86  auto const mm = m * m;
87  auto const tt = t * t;
88  auto const s = std::sqrt(tt + mm); // s \in [1,1 + 1/macheps]
89  auto const r = ((l != 0.0) ? (std::sqrt(l * l + mm))
90  : (std::abs(m))); // r \in [0,1 + 1/macheps]
91  auto const a = 0.5 * (s + r); // a \in [1,1 + |m|]
92  s1 = ha / a;
93  s0 = fa * a;
94  // Compute singular vectors
95  Real tau; // second assignment to T in DLASV2
96  if (mm != 0.0) {
97  tau = (m / (s + t) + m / (r + l)) * (1.0 + a);
98  } else {
99  // note that m is very tiny
100  tau = (l == 0.0) ? (std::copysign(2.0, f) * std::copysign(1.0, g))
101  : (g / std::copysign(d, f) + m / t);
102  }
103  auto const lv =
104  std::sqrt(tau * tau + 4.0); // second assignment to L in DLASV2
105  cv = 2.0 / lv;
106  sv = tau / lv;
107  cu = (cv + sv * m) / a;
108  su = (h / f) * sv / a;
109  }
110  // Fix signs of singular values in accordance to sign of singular vectors
111  s0 = std::copysign(s0, f);
112  s1 = std::copysign(s1, h);
113  if (swap_diag == true) {
114  swap2(cu, sv);
115  swap2(su, cv);
116  }
117  auto const U = matrix_2x2(cu, -su, su, cu);
118  auto const S = matrix_2x2(s0, 0.0, 0.0, s1);
119  auto const V = matrix_2x2(cv, -sv, sv, cv);
120  return {U, S, V};
121 }
122 
123 OMEGA_H_INLINE
124 SVD<2> svd_2x2(Matrix<2, 2> const A) OMEGA_H_NOEXCEPT {
125  // First compute a givens rotation to eliminate 1,0 entry in tensor
126  auto const cs = givens(A(0, 0), A(1, 0));
127  auto const c = cs.c;
128  auto const s = cs.s;
129  auto const R = matrix_2x2(c, -s, s, c);
130  auto const B = R * A;
131  // B is bidiagonal. Use specialized algorithm to compute its SVD
132  auto const XSV = svd_bidiagonal(B(0, 0), B(0, 1), B(1, 1));
133  auto const X = XSV.U;
134  auto const S = XSV.S;
135  auto const V = XSV.V;
136  // Complete general 2x2 SVD with givens rotation calculated above
137  auto const U = transpose(R) * X;
138  return {U, S, V};
139 }
140 
141 //
142 // R^N singular value decomposition (SVD)
143 // \param A tensor
144 // \return \f$ A = USV^T\f$
145 //
146 template <Int dim>
147 OMEGA_H_INLINE SVD<dim> decompose_svd(
148  Matrix<dim, dim> const A) OMEGA_H_NOEXCEPT {
149  // Scale first
150  auto const norm_a = norm(A);
151  auto const scale = norm_a > 0.0 ? norm_a : Real(1.0);
152  auto S = A / scale;
153  auto U = identity_matrix<dim, dim>();
154  auto V = identity_matrix<dim, dim>();
155  auto off = norm_off_diag(S);
156  auto const tol = DBL_EPSILON;
157  Int const max_iter = 2048;
158  Int num_iter = 0;
159  while (off > tol && num_iter < max_iter) {
160  // Find largest off-diagonal entry
161  auto const pq = arg_max_off_diag(S);
162  auto p = pq[0];
163  auto q = pq[1];
164  if (p > q) swap2(p, q);
165  // Obtain left and right Givens rotations by using 2x2 SVD
166  auto const Spq = matrix_2x2(S(p, p), S(p, q), S(q, p), S(q, q));
167  auto const LDR = svd_2x2(Spq);
168  auto const L = LDR.U;
169  auto const R = LDR.V;
170  auto const cl = L(0, 0);
171  auto const sl = L(0, 1);
172  auto const cr = R(0, 0);
173  auto const sr = (sign(R(0, 1)) == sign(R(1, 0))) ? (-R(0, 1)) : (R(0, 1));
174  // Apply both Givens rotations to matrices
175  // that are converging to singular values and singular vectors
176  S = givens_left(cl, sl, p, q, S);
177  S = givens_right(cr, sr, p, q, S);
178  U = givens_right(cl, sl, p, q, U);
179  V = givens_left(cr, sr, p, q, V);
180  off = norm_off_diag(S);
181  ++num_iter;
182  }
183  // Fix signs for entries in the diagonal matrix S
184  // that are negative
185  for (Int i = 0; i < dim; ++i) {
186  if (S(i, i) < 0.0) {
187  S(i, i) = -S(i, i);
188  for (Int j = 0; j < dim; ++j) {
189  U(j, i) = -U(j, i);
190  }
191  }
192  }
193  S *= scale;
194  return {U, S, V};
195 }
196 
197 } // namespace Omega_h
198 
199 #endif
Definition: amr_mpi_test.cpp:6
Definition: Omega_h_svd.hpp:14
Definition: Omega_h_svd.hpp:38