Path: blob/master/thirdparty/jolt_physics/Jolt/Math/EigenValueSymmetric.h
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Core/FPException.h>78JPH_NAMESPACE_BEGIN910/// Function to determine the eigen vectors and values of a N x N real symmetric matrix11/// by Jacobi transformations. This method is most suitable for N < 10.12///13/// Taken and adapted from Numerical Recipes paragraph 11.114///15/// An eigen vector is a vector v for which \f$A \: v = \lambda \: v\f$16///17/// Where:18/// A: A square matrix.19/// \f$\lambda\f$: a non-zero constant value.20///21/// @see https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors22///23/// Matrix is a matrix type, which has dimensions N x N.24/// @param inMatrix is the matrix of which to return the eigenvalues and vectors25/// @param outEigVec will contain a matrix whose columns contain the normalized eigenvectors (must be identity before call)26/// @param outEigVal will contain the eigenvalues27template <class Vector, class Matrix>28bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outEigVal)29{30// This algorithm can generate infinite values, see comment below31FPExceptionDisableInvalid disable_invalid;32JPH_UNUSED(disable_invalid);3334// Maximum number of sweeps to make35const int cMaxSweeps = 50;3637// Get problem dimension38const uint n = inMatrix.GetRows();3940// Make sure the dimensions are right41JPH_ASSERT(inMatrix.GetRows() == n);42JPH_ASSERT(inMatrix.GetCols() == n);43JPH_ASSERT(outEigVec.GetRows() == n);44JPH_ASSERT(outEigVec.GetCols() == n);45JPH_ASSERT(outEigVal.GetRows() == n);46JPH_ASSERT(outEigVec.IsIdentity());4748// Get the matrix in a so we can mess with it49Matrix a = inMatrix;5051Vector b, z;5253for (uint ip = 0; ip < n; ++ip)54{55// Initialize b to diagonal of a56b[ip] = a(ip, ip);5758// Initialize output to diagonal of a59outEigVal[ip] = a(ip, ip);6061// Reset z62z[ip] = 0.0f;63}6465for (int sweep = 0; sweep < cMaxSweeps; ++sweep)66{67// Get the sum of the off-diagonal elements of a68float sm = 0.0f;69for (uint ip = 0; ip < n - 1; ++ip)70for (uint iq = ip + 1; iq < n; ++iq)71sm += abs(a(ip, iq));72float avg_sm = sm / Square(n);7374// Normal return, convergence to machine underflow75if (avg_sm < FLT_MIN) // Original code: sm == 0.0f, when the average is denormal, we also consider it machine underflow76{77// Sanity checks78#ifdef JPH_ENABLE_ASSERTS79for (uint c = 0; c < n; ++c)80{81// Check if the eigenvector is normalized82JPH_ASSERT(outEigVec.GetColumn(c).IsNormalized());8384// Check if inMatrix * eigen_vector = eigen_value * eigen_vector85Vector mat_eigvec = inMatrix * outEigVec.GetColumn(c);86Vector eigval_eigvec = outEigVal[c] * outEigVec.GetColumn(c);87JPH_ASSERT(mat_eigvec.IsClose(eigval_eigvec, max(mat_eigvec.LengthSq(), eigval_eigvec.LengthSq()) * 1.0e-6f));88}89#endif9091// Success92return true;93}9495// On the first three sweeps use a fraction of the sum of the off diagonal elements as threshold96// Note that we pick a minimum threshold of FLT_MIN because dividing by a denormalized number is likely to result in infinity.97float thresh = sweep < 4? 0.2f * avg_sm : FLT_MIN; // Original code: 0.0f instead of FLT_MIN9899for (uint ip = 0; ip < n - 1; ++ip)100for (uint iq = ip + 1; iq < n; ++iq)101{102float &a_pq = a(ip, iq);103float &eigval_p = outEigVal[ip];104float &eigval_q = outEigVal[iq];105106float abs_a_pq = abs(a_pq);107float g = 100.0f * abs_a_pq;108109// After four sweeps, skip the rotation if the off-diagonal element is small110if (sweep > 4111&& abs(eigval_p) + g == abs(eigval_p)112&& abs(eigval_q) + g == abs(eigval_q))113{114a_pq = 0.0f;115}116else if (abs_a_pq > thresh)117{118float h = eigval_q - eigval_p;119float abs_h = abs(h);120121float t;122if (abs_h + g == abs_h)123{124t = a_pq / h;125}126else127{128float theta = 0.5f * h / a_pq; // Warning: Can become infinite if a(ip, iq) is very small which may trigger an invalid float exception129t = 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 algorithm130if (theta < 0.0f) t = -t;131}132133float c = 1.0f / sqrt(1.0f + t * t);134float s = t * c;135float tau = s / (1.0f + c);136h = t * a_pq;137138a_pq = 0.0f;139140z[ip] -= h;141z[iq] += h;142143eigval_p -= h;144eigval_q += h;145146#define JPH_EVS_ROTATE(a, i, j, k, l) \147g = a(i, j), \148h = a(k, l), \149a(i, j) = g - s * (h + g * tau), \150a(k, l) = h + s * (g - h * tau)151152uint j;153for (j = 0; j < ip; ++j) JPH_EVS_ROTATE(a, j, ip, j, iq);154for (j = ip + 1; j < iq; ++j) JPH_EVS_ROTATE(a, ip, j, j, iq);155for (j = iq + 1; j < n; ++j) JPH_EVS_ROTATE(a, ip, j, iq, j);156for (j = 0; j < n; ++j) JPH_EVS_ROTATE(outEigVec, j, ip, j, iq);157158#undef JPH_EVS_ROTATE159}160}161162// Update eigenvalues with the sum of ta_pq and reinitialize z163for (uint ip = 0; ip < n; ++ip)164{165b[ip] += z[ip];166outEigVal[ip] = b[ip];167z[ip] = 0.0f;168}169}170171// Failure172JPH_ASSERT(false, "Too many iterations");173return false;174}175176JPH_NAMESPACE_END177178179