Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Math/EigenValueSymmetric.h
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#pragma once
6
7
#include <Jolt/Core/FPException.h>
8
9
JPH_NAMESPACE_BEGIN
10
11
/// Function to determine the eigen vectors and values of a N x N real symmetric matrix
12
/// by Jacobi transformations. This method is most suitable for N < 10.
13
///
14
/// Taken and adapted from Numerical Recipes paragraph 11.1
15
///
16
/// An eigen vector is a vector v for which \f$A \: v = \lambda \: v\f$
17
///
18
/// Where:
19
/// A: A square matrix.
20
/// \f$\lambda\f$: a non-zero constant value.
21
///
22
/// @see https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors
23
///
24
/// Matrix is a matrix type, which has dimensions N x N.
25
/// @param inMatrix is the matrix of which to return the eigenvalues and vectors
26
/// @param outEigVec will contain a matrix whose columns contain the normalized eigenvectors (must be identity before call)
27
/// @param outEigVal will contain the eigenvalues
28
template <class Vector, class Matrix>
29
bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outEigVal)
30
{
31
// This algorithm can generate infinite values, see comment below
32
FPExceptionDisableInvalid disable_invalid;
33
JPH_UNUSED(disable_invalid);
34
35
// Maximum number of sweeps to make
36
const int cMaxSweeps = 50;
37
38
// Get problem dimension
39
const uint n = inMatrix.GetRows();
40
41
// Make sure the dimensions are right
42
JPH_ASSERT(inMatrix.GetRows() == n);
43
JPH_ASSERT(inMatrix.GetCols() == n);
44
JPH_ASSERT(outEigVec.GetRows() == n);
45
JPH_ASSERT(outEigVec.GetCols() == n);
46
JPH_ASSERT(outEigVal.GetRows() == n);
47
JPH_ASSERT(outEigVec.IsIdentity());
48
49
// Get the matrix in a so we can mess with it
50
Matrix a = inMatrix;
51
52
Vector b, z;
53
54
for (uint ip = 0; ip < n; ++ip)
55
{
56
// Initialize b to diagonal of a
57
b[ip] = a(ip, ip);
58
59
// Initialize output to diagonal of a
60
outEigVal[ip] = a(ip, ip);
61
62
// Reset z
63
z[ip] = 0.0f;
64
}
65
66
for (int sweep = 0; sweep < cMaxSweeps; ++sweep)
67
{
68
// Get the sum of the off-diagonal elements of a
69
float sm = 0.0f;
70
for (uint ip = 0; ip < n - 1; ++ip)
71
for (uint iq = ip + 1; iq < n; ++iq)
72
sm += abs(a(ip, iq));
73
float avg_sm = sm / Square(n);
74
75
// Normal return, convergence to machine underflow
76
if (avg_sm < FLT_MIN) // Original code: sm == 0.0f, when the average is denormal, we also consider it machine underflow
77
{
78
// Sanity checks
79
#ifdef JPH_ENABLE_ASSERTS
80
for (uint c = 0; c < n; ++c)
81
{
82
// Check if the eigenvector is normalized
83
JPH_ASSERT(outEigVec.GetColumn(c).IsNormalized());
84
85
// Check if inMatrix * eigen_vector = eigen_value * eigen_vector
86
Vector mat_eigvec = inMatrix * outEigVec.GetColumn(c);
87
Vector eigval_eigvec = outEigVal[c] * outEigVec.GetColumn(c);
88
JPH_ASSERT(mat_eigvec.IsClose(eigval_eigvec, max(mat_eigvec.LengthSq(), eigval_eigvec.LengthSq()) * 1.0e-6f));
89
}
90
#endif
91
92
// Success
93
return true;
94
}
95
96
// On the first three sweeps use a fraction of the sum of the off diagonal elements as threshold
97
// Note that we pick a minimum threshold of FLT_MIN because dividing by a denormalized number is likely to result in infinity.
98
float thresh = sweep < 4? 0.2f * avg_sm : FLT_MIN; // Original code: 0.0f instead of FLT_MIN
99
100
for (uint ip = 0; ip < n - 1; ++ip)
101
for (uint iq = ip + 1; iq < n; ++iq)
102
{
103
float &a_pq = a(ip, iq);
104
float &eigval_p = outEigVal[ip];
105
float &eigval_q = outEigVal[iq];
106
107
float abs_a_pq = abs(a_pq);
108
float g = 100.0f * abs_a_pq;
109
110
// After four sweeps, skip the rotation if the off-diagonal element is small
111
if (sweep > 4
112
&& abs(eigval_p) + g == abs(eigval_p)
113
&& abs(eigval_q) + g == abs(eigval_q))
114
{
115
a_pq = 0.0f;
116
}
117
else if (abs_a_pq > thresh)
118
{
119
float h = eigval_q - eigval_p;
120
float abs_h = abs(h);
121
122
float t;
123
if (abs_h + g == abs_h)
124
{
125
t = a_pq / h;
126
}
127
else
128
{
129
float theta = 0.5f * h / a_pq; // Warning: Can become infinite if a(ip, iq) is very small which may trigger an invalid float exception
130
t = 1.0f / (abs(theta) + sqrt(1.0f + theta * theta)); // If theta becomes inf, t will be 0 so the infinite is not a problem for the algorithm
131
if (theta < 0.0f) t = -t;
132
}
133
134
float c = 1.0f / sqrt(1.0f + t * t);
135
float s = t * c;
136
float tau = s / (1.0f + c);
137
h = t * a_pq;
138
139
a_pq = 0.0f;
140
141
z[ip] -= h;
142
z[iq] += h;
143
144
eigval_p -= h;
145
eigval_q += h;
146
147
#define JPH_EVS_ROTATE(a, i, j, k, l) \
148
g = a(i, j), \
149
h = a(k, l), \
150
a(i, j) = g - s * (h + g * tau), \
151
a(k, l) = h + s * (g - h * tau)
152
153
uint j;
154
for (j = 0; j < ip; ++j) JPH_EVS_ROTATE(a, j, ip, j, iq);
155
for (j = ip + 1; j < iq; ++j) JPH_EVS_ROTATE(a, ip, j, j, iq);
156
for (j = iq + 1; j < n; ++j) JPH_EVS_ROTATE(a, ip, j, iq, j);
157
for (j = 0; j < n; ++j) JPH_EVS_ROTATE(outEigVec, j, ip, j, iq);
158
159
#undef JPH_EVS_ROTATE
160
}
161
}
162
163
// Update eigenvalues with the sum of ta_pq and reinitialize z
164
for (uint ip = 0; ip < n; ++ip)
165
{
166
b[ip] += z[ip];
167
outEigVal[ip] = b[ip];
168
z[ip] = 0.0f;
169
}
170
}
171
172
// Failure
173
JPH_ASSERT(false, "Too many iterations");
174
return false;
175
}
176
177
JPH_NAMESPACE_END
178
179