EigenValueSymmetric.h 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #pragma once
  5. #include <Jolt/Core/FPFlushDenormals.h>
  6. JPH_NAMESPACE_BEGIN
  7. /// Function to determine the eigen vectors and values of a N x N real symmetric matrix
  8. /// by Jacobi transformations. This method is most suitable for N < 10.
  9. ///
  10. /// Taken and adapted from Numerical Recipies paragraph 11.1
  11. ///
  12. /// An eigen vector is a vector v for which \f$A \: v = \lambda \: v\f$
  13. ///
  14. /// Where:
  15. /// A: A square matrix.
  16. /// \f$\lambda\f$: a non-zero constant value.
  17. ///
  18. /// @see https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors
  19. ///
  20. /// Matrix is a matrix type, which has dimensions N x N.
  21. /// @param inMatrix is the matrix of which to return the eigenvalues and vectors
  22. /// @param outEigVec will contain a matrix whose columns contain the normalized eigenvectors (must be identity before call)
  23. /// @param outEigVal will contain the eigenvalues
  24. template <class Vector, class Matrix>
  25. bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outEigVal)
  26. {
  27. // This algorithm works with very small numbers and can trigger invalid float exceptions when not flushing denormals
  28. FPFlushDenormals flush_denormals;
  29. (void)flush_denormals;
  30. // Maximum number of sweeps to make
  31. const int cMaxSweeps = 50;
  32. // Get problem dimension
  33. const uint n = inMatrix.GetRows();
  34. // Make sure the dimensions are right
  35. JPH_ASSERT(inMatrix.GetRows() == n);
  36. JPH_ASSERT(inMatrix.GetCols() == n);
  37. JPH_ASSERT(outEigVec.GetRows() == n);
  38. JPH_ASSERT(outEigVec.GetCols() == n);
  39. JPH_ASSERT(outEigVal.GetRows() == n);
  40. JPH_ASSERT(outEigVec.IsIdentity());
  41. // Get the matrix in a so we can mess with it
  42. Matrix a = inMatrix;
  43. Vector b, z;
  44. for (uint ip = 0; ip < n; ++ip)
  45. {
  46. // Initialize b to diagonal of a
  47. b[ip] = a(ip, ip);
  48. // Initialize output to diagonal of a
  49. outEigVal[ip] = a(ip, ip);
  50. // Reset z
  51. z[ip] = 0.0f;
  52. }
  53. for (int sweep = 0; sweep < cMaxSweeps; ++sweep)
  54. {
  55. // Get the sum of the off-diagonal elements of a
  56. float sm = 0.0f;
  57. for (uint ip = 0; ip < n - 1; ++ip)
  58. for (uint iq = ip + 1; iq < n; ++iq)
  59. sm += abs(a(ip, iq));
  60. // Normal return, convergence to machine underflow
  61. if (sm == 0.0f)
  62. {
  63. // Sanity checks
  64. #ifdef JPH_ENABLE_ASSERTS
  65. for (uint c = 0; c < n; ++c)
  66. {
  67. // Check if the eigenvector is normalized
  68. JPH_ASSERT(outEigVec.GetColumn(c).IsNormalized());
  69. // Check if inMatrix * eigen_vector = eigen_value * eigen_vector
  70. Vector mat_eigvec = inMatrix * outEigVec.GetColumn(c);
  71. Vector eigval_eigvec = outEigVal[c] * outEigVec.GetColumn(c);
  72. JPH_ASSERT(mat_eigvec.IsClose(eigval_eigvec, max(mat_eigvec.LengthSq(), eigval_eigvec.LengthSq()) * 1.0e-6f));
  73. }
  74. #endif
  75. // Success
  76. return true;
  77. }
  78. // On the first three sweeps use a fraction of the sum of the off diagonal elements as treshold
  79. float tresh = sweep < 4? 0.2f * sm / Square(n) : 0.0f;
  80. for (uint ip = 0; ip < n - 1; ++ip)
  81. for (uint iq = ip + 1; iq < n; ++iq)
  82. {
  83. float g = 100.0f * abs(a(ip, iq));
  84. // After four sweeps, skip the rotation if the off-diagonal element is small
  85. if (sweep > 4
  86. && abs(outEigVal[ip]) + g == abs(outEigVal[ip])
  87. && abs(outEigVal[iq]) + g == abs(outEigVal[iq]))
  88. {
  89. a(ip, iq) = 0.0f;
  90. }
  91. else if (abs(a(ip, iq)) > tresh)
  92. {
  93. float h = outEigVal[iq] - outEigVal[ip];
  94. float t;
  95. if (abs(h) + g == abs(h))
  96. {
  97. t = a(ip, iq) / h;
  98. }
  99. else
  100. {
  101. float theta = 0.5f * h / a(ip, iq); // Warning: Can become inf if a(ip, iq) too small
  102. t = 1.0f / (abs(theta) + sqrt(1.0f + theta * theta)); // Warning: Squaring large value can make it inf
  103. if (theta < 0.0f) t = -t;
  104. }
  105. float c = 1.0f / sqrt(1.0f + t * t);
  106. float s = t * c;
  107. float tau = s / (1.0f + c);
  108. h = t * a(ip, iq);
  109. a(ip, iq) = 0.0f;
  110. // !Modification from Numerical Recipes!
  111. // h can become infinite due to numerical overflow, this only happens when a(ip, iq) is very small
  112. // so we can safely set a(ip, iq) to zero and skip the rotation, see lines marked with 'Warning' above.
  113. if (!isnan(h))
  114. {
  115. z[ip] -= h;
  116. z[iq] += h;
  117. outEigVal[ip] -= h;
  118. outEigVal[iq] += h;
  119. #define JPH_EVS_ROTATE(a, i, j, k, l) \
  120. g = a(i, j), \
  121. h = a(k, l), \
  122. a(i, j) = g - s * (h + g * tau), \
  123. a(k, l) = h + s * (g - h * tau)
  124. uint j;
  125. for (j = 0; j < ip; ++j) JPH_EVS_ROTATE(a, j, ip, j, iq);
  126. for (j = ip + 1; j < iq; ++j) JPH_EVS_ROTATE(a, ip, j, j, iq);
  127. for (j = iq + 1; j < n; ++j) JPH_EVS_ROTATE(a, ip, j, iq, j);
  128. for (j = 0; j < n; ++j) JPH_EVS_ROTATE(outEigVec, j, ip, j, iq);
  129. #undef JPH_EVS_ROTATE
  130. }
  131. }
  132. }
  133. // Update eigenvalues with the sum of ta_pq and reinitialize z
  134. for (uint ip = 0; ip < n; ++ip)
  135. {
  136. b[ip] += z[ip];
  137. outEigVal[ip] = b[ip];
  138. z[ip] = 0.0f;
  139. }
  140. }
  141. // Failure
  142. JPH_ASSERT(false, "Too many iterations");
  143. return false;
  144. }
  145. JPH_NAMESPACE_END