svd.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // MIT License
  2. // Copyright (c) 2019 wi-re
  3. // Copyright 2023 The Manifold Authors.
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. // SOFTWARE.
  19. // Modified from https://github.com/wi-re/tbtSVD, removing CUDA dependence and
  20. // approximate inverse square roots.
  21. #include <cmath>
  22. #include "manifold/common.h"
  23. namespace {
  24. using manifold::mat3;
  25. using manifold::vec3;
  26. using manifold::vec4;
  27. // Constants used for calculation of Givens quaternions
  28. inline constexpr double _gamma = 5.82842712474619; // sqrt(8)+3;
  29. inline constexpr double _cStar = 0.9238795325112867; // cos(pi/8)
  30. inline constexpr double _sStar = 0.3826834323650898; // sin(pi/8)
  31. // Threshold value
  32. inline constexpr double _SVD_EPSILON = 1e-6;
  33. // Iteration counts for Jacobi Eigen Analysis, influences precision
  34. inline constexpr int JACOBI_STEPS = 12;
  35. // Helper function used to swap X with Y and Y with X if c == true
  36. inline void CondSwap(bool c, double& X, double& Y) {
  37. double Z = X;
  38. X = c ? Y : X;
  39. Y = c ? Z : Y;
  40. }
  41. // Helper function used to swap X with Y and Y with -X if c == true
  42. inline void CondNegSwap(bool c, double& X, double& Y) {
  43. double Z = -X;
  44. X = c ? Y : X;
  45. Y = c ? Z : Y;
  46. }
  47. // A simple symmetric 3x3 Matrix class (contains no storage for (0, 1) (0, 2)
  48. // and (1, 2)
  49. struct Symmetric3x3 {
  50. double m_00 = 1.0;
  51. double m_10 = 0.0, m_11 = 1.0;
  52. double m_20 = 0.0, m_21 = 0.0, m_22 = 1.0;
  53. Symmetric3x3(double a11 = 1.0, double a21 = 0.0, double a22 = 1.0,
  54. double a31 = 0.0, double a32 = 0.0, double a33 = 1.0)
  55. : m_00(a11), m_10(a21), m_11(a22), m_20(a31), m_21(a32), m_22(a33) {}
  56. Symmetric3x3(mat3 o)
  57. : m_00(o[0][0]),
  58. m_10(o[0][1]),
  59. m_11(o[1][1]),
  60. m_20(o[0][2]),
  61. m_21(o[1][2]),
  62. m_22(o[2][2]) {}
  63. };
  64. // Helper struct to store 2 doubles to avoid OUT parameters on functions
  65. struct Givens {
  66. double ch = _cStar;
  67. double sh = _sStar;
  68. };
  69. // Helper struct to store 2 Matrices to avoid OUT parameters on functions
  70. struct QR {
  71. mat3 Q, R;
  72. };
  73. // Calculates the squared norm of the vector.
  74. inline double Dist2(vec3 v) { return manifold::la::dot(v, v); }
  75. // For an explanation of the math see
  76. // http://pages.cs.wisc.edu/~sifakis/papers/SVD_TR1690.pdf Computing the
  77. // Singular Value Decomposition of 3 x 3 matrices with minimal branching and
  78. // elementary floating point operations See Algorithm 2 in reference. Given a
  79. // matrix A this function returns the Givens quaternion (x and w component, y
  80. // and z are 0)
  81. inline Givens ApproximateGivensQuaternion(Symmetric3x3& A) {
  82. Givens g{2.0 * (A.m_00 - A.m_11), A.m_10};
  83. bool b = _gamma * g.sh * g.sh < g.ch * g.ch;
  84. double w = 1.0 / hypot(g.ch, g.sh);
  85. if (!std::isfinite(w)) b = 0;
  86. return Givens{b ? w * g.ch : _cStar, b ? w * g.sh : _sStar};
  87. }
  88. // Function used to apply a Givens rotation S. Calculates the weights and
  89. // updates the quaternion to contain the cumulative rotation
  90. inline void JacobiConjugation(const int32_t x, const int32_t y, const int32_t z,
  91. Symmetric3x3& S, vec4& q) {
  92. auto g = ApproximateGivensQuaternion(S);
  93. double scale = 1.0 / (g.ch * g.ch + g.sh * g.sh);
  94. double a = (g.ch * g.ch - g.sh * g.sh) * scale;
  95. double b = 2.0 * g.sh * g.ch * scale;
  96. Symmetric3x3 _S = S;
  97. // perform conjugation S = Q'*S*Q
  98. S.m_00 = a * (a * _S.m_00 + b * _S.m_10) + b * (a * _S.m_10 + b * _S.m_11);
  99. S.m_10 = a * (-b * _S.m_00 + a * _S.m_10) + b * (-b * _S.m_10 + a * _S.m_11);
  100. S.m_11 = -b * (-b * _S.m_00 + a * _S.m_10) + a * (-b * _S.m_10 + a * _S.m_11);
  101. S.m_20 = a * _S.m_20 + b * _S.m_21;
  102. S.m_21 = -b * _S.m_20 + a * _S.m_21;
  103. S.m_22 = _S.m_22;
  104. // update cumulative rotation qV
  105. vec3 tmp = g.sh * vec3(q);
  106. g.sh *= q[3];
  107. // (x,y,z) corresponds to ((0,1,2),(1,2,0),(2,0,1)) for (p,q) =
  108. // ((0,1),(1,2),(0,2))
  109. q[z] = q[z] * g.ch + g.sh;
  110. q[3] = q[3] * g.ch + -tmp[z]; // w
  111. q[x] = q[x] * g.ch + tmp[y];
  112. q[y] = q[y] * g.ch + -tmp[x];
  113. // re-arrange matrix for next iteration
  114. _S.m_00 = S.m_11;
  115. _S.m_10 = S.m_21;
  116. _S.m_11 = S.m_22;
  117. _S.m_20 = S.m_10;
  118. _S.m_21 = S.m_20;
  119. _S.m_22 = S.m_00;
  120. S.m_00 = _S.m_00;
  121. S.m_10 = _S.m_10;
  122. S.m_11 = _S.m_11;
  123. S.m_20 = _S.m_20;
  124. S.m_21 = _S.m_21;
  125. S.m_22 = _S.m_22;
  126. }
  127. // Function used to contain the Givens permutations and the loop of the jacobi
  128. // steps controlled by JACOBI_STEPS Returns the quaternion q containing the
  129. // cumulative result used to reconstruct S
  130. inline mat3 JacobiEigenAnalysis(Symmetric3x3 S) {
  131. vec4 q(0, 0, 0, 1);
  132. for (int32_t i = 0; i < JACOBI_STEPS; i++) {
  133. JacobiConjugation(0, 1, 2, S, q);
  134. JacobiConjugation(1, 2, 0, S, q);
  135. JacobiConjugation(2, 0, 1, S, q);
  136. }
  137. return mat3({1.0 - 2.0 * (q.y * q.y + q.z * q.z), //
  138. 2.0 * (q.x * q.y + +q.w * q.z), //
  139. 2.0 * (q.x * q.z + -q.w * q.y)}, //
  140. {2 * (q.x * q.y + -q.w * q.z), //
  141. 1 - 2 * (q.x * q.x + q.z * q.z), //
  142. 2 * (q.y * q.z + q.w * q.x)}, //
  143. {2 * (q.x * q.z + q.w * q.y), //
  144. 2 * (q.y * q.z + -q.w * q.x), //
  145. 1 - 2 * (q.x * q.x + q.y * q.y)});
  146. }
  147. // Implementation of Algorithm 3
  148. inline void SortSingularValues(mat3& B, mat3& V) {
  149. double rho1 = Dist2(B[0]);
  150. double rho2 = Dist2(B[1]);
  151. double rho3 = Dist2(B[2]);
  152. bool c;
  153. c = rho1 < rho2;
  154. CondNegSwap(c, B[0][0], B[1][0]);
  155. CondNegSwap(c, V[0][0], V[1][0]);
  156. CondNegSwap(c, B[0][1], B[1][1]);
  157. CondNegSwap(c, V[0][1], V[1][1]);
  158. CondNegSwap(c, B[0][2], B[1][2]);
  159. CondNegSwap(c, V[0][2], V[1][2]);
  160. CondSwap(c, rho1, rho2);
  161. c = rho1 < rho3;
  162. CondNegSwap(c, B[0][0], B[2][0]);
  163. CondNegSwap(c, V[0][0], V[2][0]);
  164. CondNegSwap(c, B[0][1], B[2][1]);
  165. CondNegSwap(c, V[0][1], V[2][1]);
  166. CondNegSwap(c, B[0][2], B[2][2]);
  167. CondNegSwap(c, V[0][2], V[2][2]);
  168. CondSwap(c, rho1, rho3);
  169. c = rho2 < rho3;
  170. CondNegSwap(c, B[1][0], B[2][0]);
  171. CondNegSwap(c, V[1][0], V[2][0]);
  172. CondNegSwap(c, B[1][1], B[2][1]);
  173. CondNegSwap(c, V[1][1], V[2][1]);
  174. CondNegSwap(c, B[1][2], B[2][2]);
  175. CondNegSwap(c, V[1][2], V[2][2]);
  176. }
  177. // Implementation of Algorithm 4
  178. inline Givens QRGivensQuaternion(double a1, double a2) {
  179. // a1 = pivot point on diagonal
  180. // a2 = lower triangular entry we want to annihilate
  181. double epsilon = _SVD_EPSILON;
  182. double rho = hypot(a1, a2);
  183. Givens g{fabs(a1) + fmax(rho, epsilon), rho > epsilon ? a2 : 0};
  184. bool b = a1 < 0.0;
  185. CondSwap(b, g.sh, g.ch);
  186. double w = 1.0 / hypot(g.ch, g.sh);
  187. g.ch *= w;
  188. g.sh *= w;
  189. return g;
  190. }
  191. // Implements a QR decomposition of a Matrix, see Sec 4.2
  192. inline QR QRDecomposition(mat3& B) {
  193. mat3 Q, R;
  194. // first Givens rotation (ch,0,0,sh)
  195. auto g1 = QRGivensQuaternion(B[0][0], B[0][1]);
  196. auto a = -2.0 * g1.sh * g1.sh + 1.0;
  197. auto b = 2.0 * g1.ch * g1.sh;
  198. // apply B = Q' * B
  199. R[0][0] = a * B[0][0] + b * B[0][1];
  200. R[1][0] = a * B[1][0] + b * B[1][1];
  201. R[2][0] = a * B[2][0] + b * B[2][1];
  202. R[0][1] = -b * B[0][0] + a * B[0][1];
  203. R[1][1] = -b * B[1][0] + a * B[1][1];
  204. R[2][1] = -b * B[2][0] + a * B[2][1];
  205. R[0][2] = B[0][2];
  206. R[1][2] = B[1][2];
  207. R[2][2] = B[2][2];
  208. // second Givens rotation (ch,0,-sh,0)
  209. auto g2 = QRGivensQuaternion(R[0][0], R[0][2]);
  210. a = -2.0 * g2.sh * g2.sh + 1.0;
  211. b = 2.0 * g2.ch * g2.sh;
  212. // apply B = Q' * B;
  213. B[0][0] = a * R[0][0] + b * R[0][2];
  214. B[1][0] = a * R[1][0] + b * R[1][2];
  215. B[2][0] = a * R[2][0] + b * R[2][2];
  216. B[0][1] = R[0][1];
  217. B[1][1] = R[1][1];
  218. B[2][1] = R[2][1];
  219. B[0][2] = -b * R[0][0] + a * R[0][2];
  220. B[1][2] = -b * R[1][0] + a * R[1][2];
  221. B[2][2] = -b * R[2][0] + a * R[2][2];
  222. // third Givens rotation (ch,sh,0,0)
  223. auto g3 = QRGivensQuaternion(B[1][1], B[1][2]);
  224. a = -2.0 * g3.sh * g3.sh + 1.0;
  225. b = 2.0 * g3.ch * g3.sh;
  226. // R is now set to desired value
  227. R[0][0] = B[0][0];
  228. R[1][0] = B[1][0];
  229. R[2][0] = B[2][0];
  230. R[0][1] = a * B[0][1] + b * B[0][2];
  231. R[1][1] = a * B[1][1] + b * B[1][2];
  232. R[2][1] = a * B[2][1] + b * B[2][2];
  233. R[0][2] = -b * B[0][1] + a * B[0][2];
  234. R[1][2] = -b * B[1][1] + a * B[1][2];
  235. R[2][2] = -b * B[2][1] + a * B[2][2];
  236. // construct the cumulative rotation Q=Q1 * Q2 * Q3
  237. // the number of floating point operations for three quaternion
  238. // multiplications is more or less comparable to the explicit form of the
  239. // joined matrix. certainly more memory-efficient!
  240. auto sh12 = 2.0 * (g1.sh * g1.sh + -0.5);
  241. auto sh22 = 2.0 * (g2.sh * g2.sh + -0.5);
  242. auto sh32 = 2.0 * (g3.sh * g3.sh + -0.5);
  243. Q[0][0] = sh12 * sh22;
  244. Q[1][0] =
  245. 4.0 * g2.ch * g3.ch * sh12 * g2.sh * g3.sh + 2.0 * g1.ch * g1.sh * sh32;
  246. Q[2][0] =
  247. 4.0 * g1.ch * g3.ch * g1.sh * g3.sh + -2.0 * g2.ch * sh12 * g2.sh * sh32;
  248. Q[0][1] = -2.0 * g1.ch * g1.sh * sh22;
  249. Q[1][1] = -8.0 * g1.ch * g2.ch * g3.ch * g1.sh * g2.sh * g3.sh + sh12 * sh32;
  250. Q[2][1] =
  251. -2.0 * g3.ch * g3.sh +
  252. 4.0 * g1.sh * (g3.ch * g1.sh * g3.sh + g1.ch * g2.ch * g2.sh * sh32);
  253. Q[0][2] = 2.0 * g2.ch * g2.sh;
  254. Q[1][2] = -2.0 * g3.ch * sh22 * g3.sh;
  255. Q[2][2] = sh22 * sh32;
  256. return QR{Q, R};
  257. }
  258. } // namespace
  259. namespace manifold {
  260. /**
  261. * The three matrices of a Singular Value Decomposition.
  262. */
  263. struct SVDSet {
  264. mat3 U, S, V;
  265. };
  266. /**
  267. * Returns the Singular Value Decomposition of A: A = U * S * la::transpose(V).
  268. *
  269. * @param A The matrix to decompose.
  270. */
  271. inline SVDSet SVD(mat3 A) {
  272. mat3 V = JacobiEigenAnalysis(la::transpose(A) * A);
  273. auto B = A * V;
  274. SortSingularValues(B, V);
  275. QR qr = QRDecomposition(B);
  276. return SVDSet{qr.Q, qr.R, V};
  277. }
  278. /**
  279. * Returns the largest singular value of A.
  280. *
  281. * @param A The matrix to measure.
  282. */
  283. inline double SpectralNorm(mat3 A) {
  284. SVDSet usv = SVD(A);
  285. return usv.S[0][0];
  286. }
  287. } // namespace manifold