PolyFEM
Loading...
Searching...
No Matches
svd.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <Eigen/Dense>
6
7#define USE_IQRSVD
8
9namespace polyfem
10{
11 namespace utils
12 {
13 template <typename MatrixType>
14 class AutoFlipSVD : Eigen::JacobiSVD<MatrixType>
15 {
16 protected:
18
19 typename Eigen::JacobiSVD<MatrixType>::SingularValuesType singularValues_flipped;
21
22 public:
23 AutoFlipSVD(void) {}
24 AutoFlipSVD(const MatrixType &mtr, unsigned int computationOptions = 0)
25 {
26 compute(mtr, computationOptions);
27 }
28
29 public:
30 template <int dim = MatrixType::RowsAtCompileTime>
31 typename std::enable_if<dim == 3, AutoFlipSVD<MatrixType>>::type &
32 compute(const MatrixType &mtr, unsigned int computationOptions)
33 {
35#ifdef USE_IQRSVD
40#else
41 if ((computationOptions & Eigen::ComputeFullU) || (computationOptions & Eigen::ComputeFullV))
42 {
44 }
45 else
46 {
48 }
49#endif
50 return *this;
51 }
52 template <int dim = MatrixType::RowsAtCompileTime>
53 typename std::enable_if<dim == 2, AutoFlipSVD<MatrixType>>::type &
54 compute(const MatrixType &mtr, unsigned int computationOptions)
55 {
56#ifdef USE_IQRSVD
62#else
64 Eigen::JacobiSVD<MatrixType>::compute(mtr, computationOptions);
65 flip2d(mtr, computationOptions);
66#endif
67 return *this;
68 }
69
70 void set(const Eigen::Matrix3d &U, const Eigen::Vector3d &Sigma, const Eigen::Matrix3d &V)
71 {
72 flipped_U = true, flipped_V = true, flipped_sigma = true;
76 }
77
78 protected:
79 void flip2d(const MatrixType &mtr, unsigned int computationOptions)
80 {
82 bool fullUComputed = (computationOptions & Eigen::ComputeFullU);
83 bool fullVComputed = (computationOptions & Eigen::ComputeFullV);
84 if (fullUComputed && fullVComputed)
85 {
86 if (Eigen::JacobiSVD<MatrixType>::m_matrixU.determinant() < 0.0)
87 {
88 matrixU_flipped = Eigen::JacobiSVD<MatrixType>::m_matrixU;
89 matrixU_flipped.col(1) *= -1.0;
90 flipped_U = true;
91
92 if (!flipped_sigma)
93 {
94 singularValues_flipped = Eigen::JacobiSVD<MatrixType>::m_singularValues;
95 }
96 singularValues_flipped[1] *= -1.0;
97 flipped_sigma = true;
98 }
99 if (Eigen::JacobiSVD<MatrixType>::m_matrixV.determinant() < 0.0)
100 {
101 matrixV_flipped = Eigen::JacobiSVD<MatrixType>::m_matrixV;
102 matrixV_flipped.col(1) *= -1.0;
103 flipped_V = true;
104
105 if (!flipped_sigma)
106 {
107 singularValues_flipped = Eigen::JacobiSVD<MatrixType>::m_singularValues;
108 }
109 singularValues_flipped[1] *= -1.0;
110 flipped_sigma = true;
111 }
112 }
113 else if (mtr.determinant() < 0.0)
114 {
115 singularValues_flipped = Eigen::JacobiSVD<MatrixType>::m_singularValues;
116 singularValues_flipped[1] *= -1.0;
117 flipped_sigma = true;
118 }
119
120 if (std::isnan(singularValues()[0]) || std::isnan(singularValues()[1]))
121 {
122 // degenerated case
123 singularValues_flipped.setZero();
124 flipped_sigma = true;
125 if (fullUComputed && fullVComputed)
126 {
127 matrixU_flipped.setIdentity();
128 matrixV_flipped.setIdentity();
129 flipped_U = flipped_V = true;
130 }
131 }
132 }
133
134 // TODO: merge with IglUtils::computeCofactorMtr
135 template <int dim>
136 void computeCofactorMtr(const Eigen::Matrix<double, dim, dim> &F,
137 Eigen::Matrix<double, dim, dim> &A)
138 {
139 switch (dim)
140 {
141 case 2:
142 A(0, 0) = F(1, 1);
143 A(0, 1) = -F(1, 0);
144 A(1, 0) = -F(0, 1);
145 A(1, 1) = F(0, 0);
146 break;
147
148 case 3:
149 A(0, 0) = F(1, 1) * F(2, 2) - F(1, 2) * F(2, 1);
150 A(0, 1) = F(1, 2) * F(2, 0) - F(1, 0) * F(2, 2);
151 A(0, 2) = F(1, 0) * F(2, 1) - F(1, 1) * F(2, 0);
152 A(1, 0) = F(0, 2) * F(2, 1) - F(0, 1) * F(2, 2);
153 A(1, 1) = F(0, 0) * F(2, 2) - F(0, 2) * F(2, 0);
154 A(1, 2) = F(0, 1) * F(2, 0) - F(0, 0) * F(2, 1);
155 A(2, 0) = F(0, 1) * F(1, 2) - F(0, 2) * F(1, 1);
156 A(2, 1) = F(0, 2) * F(1, 0) - F(0, 0) * F(1, 2);
157 A(2, 2) = F(0, 0) * F(1, 1) - F(0, 1) * F(1, 0);
158 break;
159
160 default:
161 assert(0 && "dim not 2 or 3");
162 break;
163 }
164 }
165 void fastEigenvalues(const Eigen::Matrix3d &A_Sym,
166 Eigen::Vector3d &lambda)
167 // 24 mults, 20 adds, 1 atan2, 1 sincos, 2 sqrts
168 {
169 using T = double;
170 using std::max;
171 using std::swap;
172 T m = ((T)1 / 3) * (A_Sym(0, 0) + A_Sym(1, 1) + A_Sym(2, 2));
173 T a00 = A_Sym(0, 0) - m;
174 T a11 = A_Sym(1, 1) - m;
175 T a22 = A_Sym(2, 2) - m;
176 T a12_sqr = A_Sym(0, 1) * A_Sym(0, 1);
177 T a13_sqr = A_Sym(0, 2) * A_Sym(0, 2);
178 T a23_sqr = A_Sym(1, 2) * A_Sym(1, 2);
179 T p = ((T)1 / 6) * (a00 * a00 + a11 * a11 + a22 * a22 + 2 * (a12_sqr + a13_sqr + a23_sqr));
180 T q = (T).5 * (a00 * (a11 * a22 - a23_sqr) - a11 * a13_sqr - a22 * a12_sqr) + A_Sym(0, 1) * A_Sym(0, 2) * A_Sym(1, 2);
181 T sqrt_p = sqrt(p);
182 T disc = p * p * p - q * q;
183 T phi = ((T)1 / 3) * atan2(sqrt(max((T)0, disc)), q);
184 T c = cos(phi), s = sin(phi);
185 T sqrt_p_cos = sqrt_p * c;
186 T root_three_sqrt_p_sin = sqrt((T)3) * sqrt_p * s;
187 lambda(0) = m + 2 * sqrt_p_cos;
188 lambda(1) = m - sqrt_p_cos - root_three_sqrt_p_sin;
189 lambda(2) = m - sqrt_p_cos + root_three_sqrt_p_sin;
190 if (lambda(0) < lambda(1))
191 swap(lambda(0), lambda(1));
192 if (lambda(1) < lambda(2))
193 swap(lambda(1), lambda(2));
194 if (lambda(0) < lambda(1))
195 swap(lambda(0), lambda(1));
196 }
197 void fastEigenvectors(const Eigen::Matrix3d &A_Sym,
198 const Eigen::Vector3d &lambda,
199 Eigen::Matrix3d &V)
200 // 71 mults, 44 adds, 3 divs, 3 sqrts
201 {
202 // flip if necessary so that first eigenvalue is the most different
203 using T = double;
204 using std::sqrt;
205 using std::swap;
206 bool flipped = false;
207 Eigen::Vector3d lambda_flip(lambda);
208 if (lambda(0) - lambda(1) < lambda(1) - lambda(2))
209 { // 2a
210 swap(lambda_flip(0), lambda_flip(2));
211 flipped = true;
212 }
213
214 // get first eigenvector
215 Eigen::Matrix3d C1;
216 computeCofactorMtr<3>(A_Sym - lambda_flip(0) * Eigen::Matrix3d::Identity(), C1);
217 Eigen::Matrix3d::Index i;
218 T norm2 = C1.colwise().squaredNorm().maxCoeff(&i); // 3a + 12m+6a + 9m+6a+1d+1s = 21m+15a+1d+1s
219 Eigen::Vector3d v1;
220 if (norm2 != 0)
221 {
222 T one_over_sqrt = (T)1 / sqrt(norm2);
223 v1 = C1.col(i) * one_over_sqrt;
224 }
225 else
226 v1 << 1, 0, 0;
227
228 // form basis for orthogonal complement to v1, and reduce A to this space
229 Eigen::Vector3d v1_orthogonal = v1.unitOrthogonal(); // 6m+2a+1d+1s (tweak: 5m+1a+1d+1s)
230 Eigen::Matrix<T, 3, 2> other_v;
231 other_v.col(0) = v1_orthogonal;
232 other_v.col(1) = v1.cross(v1_orthogonal); // 6m+3a (tweak: 4m+1a)
233 Eigen::Matrix2d A_reduced = other_v.transpose() * A_Sym * other_v; // 21m+12a (tweak: 18m+9a)
234
235 // find third eigenvector from A_reduced, and fill in second via cross product
236 Eigen::Matrix2d C3;
237 computeCofactorMtr<2>(A_reduced - lambda_flip(2) * Eigen::Matrix2d::Identity(), C3);
238 Eigen::Matrix2d::Index j;
239 norm2 = C3.colwise().squaredNorm().maxCoeff(&j); // 3a + 12m+6a + 9m+6a+1d+1s = 21m+15a+1d+1s
240 Eigen::Vector3d v3;
241 if (norm2 != 0)
242 {
243 T one_over_sqrt = (T)1 / sqrt(norm2);
244 v3 = other_v * C3.col(j) * one_over_sqrt;
245 }
246 else
247 v3 = other_v.col(0);
248
249 Eigen::Vector3d v2 = v3.cross(v1); // 6m+3a
250
251 // finish
252 if (flipped)
253 {
254 V.col(0) = v3;
255 V.col(1) = v2;
256 V.col(2) = -v1;
257 }
258 else
259 {
260 V.col(0) = v1;
261 V.col(1) = v2;
262 V.col(2) = v3;
263 }
264 }
265 void fastSolveEigenproblem(const Eigen::Matrix3d &A_Sym,
266 Eigen::Vector3d &lambda,
267 Eigen::Matrix3d &V)
268 // 71 mults, 44 adds, 3 divs, 3 sqrts
269 {
270 fastEigenvalues(A_Sym, lambda);
271 fastEigenvectors(A_Sym, lambda, V);
272 }
273
274 void fastSVD3d(const Eigen::Matrix3d &A,
275 Eigen::Matrix3d &U,
276 Eigen::Vector3d &singular_values,
277 Eigen::Matrix3d &V)
278 // 182 mults, 112 adds, 6 divs, 11 sqrts, 1 atan2, 1 sincos
279 {
280 using T = double;
281 // decompose normal equations
282 Eigen::Vector3d lambda;
283 fastSolveEigenproblem(A.transpose() * A, lambda, V);
284
285 // compute singular values
286 if (lambda(2) < 0)
287 lambda = (lambda.array() >= (T)0).select(lambda, (T)0);
288 singular_values = lambda.array().sqrt();
289 if (A.determinant() < 0)
291
292 // compute singular vectors
293 U.col(0) = A * V.col(0);
294 T norm = U.col(0).norm();
295 if (norm != 0)
296 {
297 T one_over_norm = (T)1 / norm;
298 U.col(0) = U.col(0) * one_over_norm;
299 }
300 else
301 U.col(0) << 1, 0, 0;
302 Eigen::Vector3d v1_orthogonal = U.col(0).unitOrthogonal();
303 Eigen::Matrix<T, 3, 2> other_v;
304 other_v.col(0) = v1_orthogonal;
305 other_v.col(1) = U.col(0).cross(v1_orthogonal);
306 Eigen::Vector2d w = other_v.transpose() * A * V.col(1);
307 norm = w.norm();
308 if (norm != 0)
309 {
310 T one_over_norm = (T)1 / norm;
311 w = w * one_over_norm;
312 }
313 else
314 w << 1, 0;
315 U.col(1) = other_v * w;
316 U.col(2) = U.col(0).cross(U.col(1));
317 }
318
319 void fastComputeSingularValues3d(const Eigen::Matrix3d &A,
320 Eigen::Vector3d &singular_values)
321 {
322 using T = double;
323 // decompose normal equations
324 Eigen::Vector3d lambda;
325 fastEigenvalues(A.transpose() * A, lambda);
326
327 // compute singular values
328 if (lambda(2) < 0)
329 lambda = (lambda.array() >= (T)0).select(lambda, (T)0);
330 singular_values = lambda.array().sqrt();
331 if (A.determinant() < 0)
333 }
334
335 public:
336 const typename Eigen::JacobiSVD<MatrixType>::SingularValuesType &singularValues(void) const
337 {
338 if (flipped_sigma)
339 {
341 }
342 else
343 {
344 return Eigen::JacobiSVD<MatrixType>::singularValues();
345 }
346 }
347 const MatrixType &matrixU(void) const
348 {
349 if (flipped_U)
350 {
351 return matrixU_flipped;
352 }
353 else
354 {
355 return Eigen::JacobiSVD<MatrixType>::matrixU();
356 }
357 }
358 const MatrixType &matrixV(void) const
359 {
360 if (flipped_V)
361 {
362 return matrixV_flipped;
363 }
364 else
365 {
366 return Eigen::JacobiSVD<MatrixType>::matrixV();
367 }
368 }
369
370 void setIdentity(void)
371 {
372 flipped_sigma = true;
373 flipped_V = true;
374 flipped_U = true;
375
376 matrixU_flipped.setIdentity();
377 matrixV_flipped.setIdentity();
378 singularValues_flipped.setOnes();
379 }
380 };
381
382 template <int dim>
383 Eigen::Vector<double, dim> singular_values(const Eigen::Matrix<double, dim, dim> &A)
384 {
385 AutoFlipSVD<Eigen::Matrix<double, dim, dim>> svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV);
386 return svd.singularValues();
387 }
388 } // namespace utils
389} // namespace polyfem
int V
const Eigen::JacobiSVD< MatrixType >::SingularValuesType & singularValues(void) const
Definition svd.hpp:336
std::enable_if< dim==2, AutoFlipSVD< MatrixType > >::type & compute(const MatrixType &mtr, unsigned int computationOptions)
Definition svd.hpp:54
void fastComputeSingularValues3d(const Eigen::Matrix3d &A, Eigen::Vector3d &singular_values)
Definition svd.hpp:319
void fastSolveEigenproblem(const Eigen::Matrix3d &A_Sym, Eigen::Vector3d &lambda, Eigen::Matrix3d &V)
Definition svd.hpp:265
MatrixType matrixU_flipped
Definition svd.hpp:20
void fastEigenvectors(const Eigen::Matrix3d &A_Sym, const Eigen::Vector3d &lambda, Eigen::Matrix3d &V)
Definition svd.hpp:197
const MatrixType & matrixV(void) const
Definition svd.hpp:358
AutoFlipSVD(const MatrixType &mtr, unsigned int computationOptions=0)
Definition svd.hpp:24
void flip2d(const MatrixType &mtr, unsigned int computationOptions)
Definition svd.hpp:79
void fastSVD3d(const Eigen::Matrix3d &A, Eigen::Matrix3d &U, Eigen::Vector3d &singular_values, Eigen::Matrix3d &V)
Definition svd.hpp:274
void computeCofactorMtr(const Eigen::Matrix< double, dim, dim > &F, Eigen::Matrix< double, dim, dim > &A)
Definition svd.hpp:136
const MatrixType & matrixU(void) const
Definition svd.hpp:347
Eigen::JacobiSVD< MatrixType >::SingularValuesType singularValues_flipped
Definition svd.hpp:19
void fastEigenvalues(const Eigen::Matrix3d &A_Sym, Eigen::Vector3d &lambda)
Definition svd.hpp:165
MatrixType matrixV_flipped
Definition svd.hpp:20
std::enable_if< dim==3, AutoFlipSVD< MatrixType > >::type & compute(const MatrixType &mtr, unsigned int computationOptions)
Definition svd.hpp:32
void set(const Eigen::Matrix3d &U, const Eigen::Vector3d &Sigma, const Eigen::Matrix3d &V)
Definition svd.hpp:70
enable_if_t< isSize< TA >(2, 2) &&isSize< Ts >(2, 1)> singularValueDecomposition(const Eigen::MatrixBase< TA > &A, GivensRotation< T > &U, const Eigen::MatrixBase< Ts > &Sigma, GivensRotation< T > &V, const ScalarType< TA > tol=64 *std::numeric_limits< ScalarType< TA > >::epsilon())
2x2 SVD (singular value decomposition) A=USV'
Eigen::Vector< double, dim > singular_values(const Eigen::Matrix< double, dim, dim > &A)
Definition svd.hpp:383
T determinant(const Eigen::Matrix< T, rows, cols, option, maxRow, maxCol > &mat)