EigenValueSymmetric.h 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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/FPException.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 can generate infinite values, see comment below
  28. FPExceptionDisableInvalid disable_invalid;
  29. (void)disable_invalid;
  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. float avg_sm = sm / Square(n);
  61. // Normal return, convergence to machine underflow
  62. if (avg_sm < FLT_MIN) // Original code: sm == 0.0f, when the average is denormal, we also consider it machine underflow
  63. {
  64. // Sanity checks
  65. #ifdef JPH_ENABLE_ASSERTS
  66. for (uint c = 0; c < n; ++c)
  67. {
  68. // Check if the eigenvector is normalized
  69. JPH_ASSERT(outEigVec.GetColumn(c).IsNormalized());
  70. // Check if inMatrix * eigen_vector = eigen_value * eigen_vector
  71. Vector mat_eigvec = inMatrix * outEigVec.GetColumn(c);
  72. Vector eigval_eigvec = outEigVal[c] * outEigVec.GetColumn(c);
  73. JPH_ASSERT(mat_eigvec.IsClose(eigval_eigvec, max(mat_eigvec.LengthSq(), eigval_eigvec.LengthSq()) * 1.0e-6f));
  74. }
  75. #endif
  76. // Success
  77. return true;
  78. }
  79. // On the first three sweeps use a fraction of the sum of the off diagonal elements as threshold
  80. // Note that we pick a minimum threshold of FLT_MIN because dividing by a denormalized number is likely to result in infinity.
  81. float tresh = sweep < 4? 0.2f * avg_sm : FLT_MIN; // Original code: 0.0f instead of FLT_MIN
  82. for (uint ip = 0; ip < n - 1; ++ip)
  83. for (uint iq = ip + 1; iq < n; ++iq)
  84. {
  85. float &a_pq = a(ip, iq);
  86. float &eigval_p = outEigVal[ip];
  87. float &eigval_q = outEigVal[iq];
  88. float abs_a_pq = abs(a_pq);
  89. float g = 100.0f * abs_a_pq;
  90. // After four sweeps, skip the rotation if the off-diagonal element is small
  91. if (sweep > 4
  92. && abs(eigval_p) + g == abs(eigval_p)
  93. && abs(eigval_q) + g == abs(eigval_q))
  94. {
  95. a_pq = 0.0f;
  96. }
  97. else if (abs_a_pq > tresh)
  98. {
  99. float h = eigval_q - eigval_p;
  100. float abs_h = abs(h);
  101. float t;
  102. if (abs_h + g == abs_h)
  103. {
  104. t = a_pq / h;
  105. }
  106. else
  107. {
  108. 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
  109. 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
  110. if (theta < 0.0f) t = -t;
  111. }
  112. float c = 1.0f / sqrt(1.0f + t * t);
  113. float s = t * c;
  114. float tau = s / (1.0f + c);
  115. h = t * a_pq;
  116. a_pq = 0.0f;
  117. z[ip] -= h;
  118. z[iq] += h;
  119. eigval_p -= h;
  120. eigval_q += h;
  121. #define JPH_EVS_ROTATE(a, i, j, k, l) \
  122. g = a(i, j), \
  123. h = a(k, l), \
  124. a(i, j) = g - s * (h + g * tau), \
  125. a(k, l) = h + s * (g - h * tau)
  126. uint j;
  127. for (j = 0; j < ip; ++j) JPH_EVS_ROTATE(a, j, ip, j, iq);
  128. for (j = ip + 1; j < iq; ++j) JPH_EVS_ROTATE(a, ip, j, j, iq);
  129. for (j = iq + 1; j < n; ++j) JPH_EVS_ROTATE(a, ip, j, iq, j);
  130. for (j = 0; j < n; ++j) JPH_EVS_ROTATE(outEigVec, j, ip, j, iq);
  131. #undef JPH_EVS_ROTATE
  132. }
  133. }
  134. // Update eigenvalues with the sum of ta_pq and reinitialize z
  135. for (uint ip = 0; ip < n; ++ip)
  136. {
  137. b[ip] += z[ip];
  138. outEigVal[ip] = b[ip];
  139. z[ip] = 0.0f;
  140. }
  141. }
  142. // Failure
  143. JPH_ASSERT(false, "Too many iterations");
  144. return false;
  145. }
  146. JPH_NAMESPACE_END