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