Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:20:50

0001 // Copyright (c) 2025 OPEN CASCADE SAS
0002 //
0003 // This file is part of Open CASCADE Technology software library.
0004 //
0005 // This library is free software; you can redistribute it and/or modify it under
0006 // the terms of the GNU Lesser General Public License version 2.1 as published
0007 // by the Free Software Foundation, with special exception defined in the file
0008 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
0009 // distribution for complete text of the license and disclaimer of any warranty.
0010 //
0011 // Alternatively, this file may be used under the terms of Open CASCADE
0012 // commercial license or contractual agreement.
0013 
0014 #ifndef _MathLin_Jacobi_HeaderFile
0015 #define _MathLin_Jacobi_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <math_Recipes.hxx>
0020 #include <MathUtils_Core.hxx>
0021 
0022 #include <cmath>
0023 #include <algorithm>
0024 
0025 namespace MathLin
0026 {
0027 using namespace MathUtils;
0028 
0029 //! Compute eigenvalues and eigenvectors of a symmetric matrix
0030 //! using the Jacobi iterative method.
0031 //!
0032 //! The Jacobi method applies a sequence of plane rotations (Givens rotations)
0033 //! to diagonalize the symmetric matrix A:
0034 //!   A' = R^T * A * R
0035 //! where R is a rotation that zeroes one off-diagonal element.
0036 //!
0037 //! After convergence, A is diagonal with eigenvalues on the diagonal,
0038 //! and the accumulated rotations form the eigenvector matrix.
0039 //!
0040 //! Properties:
0041 //! - Only works for symmetric matrices
0042 //! - Eigenvalues are always real for symmetric matrices
0043 //! - Eigenvectors are orthonormal
0044 //! - Numerically stable
0045 //!
0046 //! Complexity: O(n^3) per sweep, typically needs 5-10 sweeps.
0047 //!
0048 //! @param theA input symmetric matrix (n x n)
0049 //! @param theSortDescending if true, eigenvalues are sorted in descending order
0050 //! @return eigenvalue result
0051 inline EigenResult Jacobi(const math_Matrix& theA, bool theSortDescending = true)
0052 {
0053   EigenResult aResult;
0054 
0055   const int aRowLower = theA.LowerRow();
0056   const int aRowUpper = theA.UpperRow();
0057   const int aColLower = theA.LowerCol();
0058   const int aColUpper = theA.UpperCol();
0059   const int aN        = aRowUpper - aRowLower + 1;
0060 
0061   // Check for square matrix
0062   if (aColUpper - aColLower + 1 != aN)
0063   {
0064     aResult.Status = Status::InvalidInput;
0065     return aResult;
0066   }
0067 
0068   // Create 1-based working copies as required by the Jacobi function
0069   math_Matrix aWorkA(1, aN, 1, aN);
0070   math_Vector aEigenVals(1, aN);
0071   math_Matrix aEigenVecs(1, aN, 1, aN);
0072 
0073   // Copy input to working matrix
0074   for (int i = aRowLower; i <= aRowUpper; ++i)
0075   {
0076     for (int j = aColLower; j <= aColUpper; ++j)
0077     {
0078       aWorkA(i - aRowLower + 1, j - aColLower + 1) = theA(i, j);
0079     }
0080   }
0081 
0082   // Call the Jacobi function from math_Recipes
0083   int aNbRotations = 0;
0084   if (::Jacobi(aWorkA, aEigenVals, aEigenVecs, aNbRotations) != 0)
0085   {
0086     aResult.Status = Status::NotConverged;
0087     return aResult;
0088   }
0089 
0090   aResult.NbIterations = static_cast<size_t>(aNbRotations);
0091 
0092   // Sort eigenvalues and eigenvectors if requested
0093   if (theSortDescending && aN > 1)
0094   {
0095     // Simple selection sort
0096     for (int i = 1; i < aN; ++i)
0097     {
0098       int    aMaxIdx = i;
0099       double aMaxVal = aEigenVals(i);
0100 
0101       for (int j = i + 1; j <= aN; ++j)
0102       {
0103         if (aEigenVals(j) > aMaxVal)
0104         {
0105           aMaxVal = aEigenVals(j);
0106           aMaxIdx = j;
0107         }
0108       }
0109 
0110       if (aMaxIdx != i)
0111       {
0112         // Swap eigenvalues
0113         std::swap(aEigenVals(i), aEigenVals(aMaxIdx));
0114 
0115         // Swap corresponding eigenvector columns
0116         for (int k = 1; k <= aN; ++k)
0117         {
0118           std::swap(aEigenVecs(k, i), aEigenVecs(k, aMaxIdx));
0119         }
0120       }
0121     }
0122   }
0123 
0124   // Copy results with original indexing
0125   aResult.EigenValues = math_Vector(aRowLower, aRowUpper);
0126   for (int i = 1; i <= aN; ++i)
0127   {
0128     (*aResult.EigenValues)(aRowLower + i - 1) = aEigenVals(i);
0129   }
0130 
0131   aResult.EigenVectors = math_Matrix(aRowLower, aRowUpper, aColLower, aColUpper);
0132   for (int i = 1; i <= aN; ++i)
0133   {
0134     for (int j = 1; j <= aN; ++j)
0135     {
0136       (*aResult.EigenVectors)(aRowLower + i - 1, aColLower + j - 1) = aEigenVecs(i, j);
0137     }
0138   }
0139 
0140   aResult.Status = Status::OK;
0141   return aResult;
0142 }
0143 
0144 //! Compute only eigenvalues of a symmetric matrix (faster).
0145 //!
0146 //! Uses the same Jacobi method but may be optimized to not store
0147 //! eigenvectors if not needed.
0148 //!
0149 //! @param theA input symmetric matrix (n x n)
0150 //! @param theSortDescending if true, eigenvalues are sorted in descending order
0151 //! @return eigenvalue result (only EigenValues is set)
0152 inline EigenResult EigenValues(const math_Matrix& theA, bool theSortDescending = true)
0153 {
0154   // Currently delegates to full Jacobi, but eigenvectors are computed
0155   // A future optimization could avoid eigenvector computation
0156   return Jacobi(theA, theSortDescending);
0157 }
0158 
0159 //! Compute spectral decomposition A = V * D * V^T.
0160 //!
0161 //! For symmetric matrix A, decomposes into:
0162 //! - V: orthogonal matrix of eigenvectors (columns)
0163 //! - D: diagonal matrix of eigenvalues
0164 //!
0165 //! Such that A = V * D * V^T
0166 //!
0167 //! @param theA input symmetric matrix (n x n)
0168 //! @return eigenvalue result with EigenValues (diagonal of D) and EigenVectors (V)
0169 inline EigenResult SpectralDecomposition(const math_Matrix& theA)
0170 {
0171   return Jacobi(theA, false);
0172 }
0173 
0174 //! Compute matrix power A^p for symmetric positive semi-definite matrix.
0175 //!
0176 //! Uses spectral decomposition: A^p = V * D^p * V^T
0177 //! where D^p is the diagonal matrix with eigenvalues raised to power p.
0178 //!
0179 //! @param theA input symmetric positive semi-definite matrix
0180 //! @param thePower exponent (can be fractional, e.g., 0.5 for sqrt)
0181 //! @return A^p matrix
0182 inline std::optional<math_Matrix> MatrixPower(const math_Matrix& theA, double thePower)
0183 {
0184   EigenResult aEigen = Jacobi(theA, false);
0185   if (!aEigen.IsDone())
0186   {
0187     return std::nullopt;
0188   }
0189 
0190   const math_Vector& aD = *aEigen.EigenValues;
0191   const math_Matrix& aV = *aEigen.EigenVectors;
0192 
0193   const int aLower = aD.Lower();
0194   const int aUpper = aD.Upper();
0195 
0196   // Compute D^p
0197   math_Vector aDp(aLower, aUpper);
0198   for (int i = aLower; i <= aUpper; ++i)
0199   {
0200     if (aD(i) < 0.0 && thePower != std::floor(thePower))
0201     {
0202       // Can't take fractional power of negative eigenvalue
0203       return std::nullopt;
0204     }
0205     aDp(i) = (aD(i) >= 0.0) ? std::pow(aD(i), thePower) : std::pow(-aD(i), thePower);
0206     if (aD(i) < 0.0 && static_cast<int>(thePower) % 2 != 0)
0207     {
0208       aDp(i) = -aDp(i);
0209     }
0210   }
0211 
0212   // Compute V * D^p * V^T
0213   math_Matrix aResult(aLower, aUpper, aLower, aUpper, 0.0);
0214   for (int i = aLower; i <= aUpper; ++i)
0215   {
0216     for (int j = aLower; j <= aUpper; ++j)
0217     {
0218       double aSum = 0.0;
0219       for (int k = aLower; k <= aUpper; ++k)
0220       {
0221         aSum += aV(i, k) * aDp(k) * aV(j, k);
0222       }
0223       aResult(i, j) = aSum;
0224     }
0225   }
0226 
0227   return aResult;
0228 }
0229 
0230 //! Compute matrix square root of symmetric positive semi-definite matrix.
0231 //!
0232 //! @param theA input symmetric positive semi-definite matrix
0233 //! @return sqrt(A) such that sqrt(A) * sqrt(A) = A
0234 inline std::optional<math_Matrix> MatrixSqrt(const math_Matrix& theA)
0235 {
0236   return MatrixPower(theA, 0.5);
0237 }
0238 
0239 //! Compute matrix inverse square root of symmetric positive definite matrix.
0240 //!
0241 //! @param theA input symmetric positive definite matrix
0242 //! @return A^(-1/2) such that A^(-1/2) * A * A^(-1/2) = I
0243 inline std::optional<math_Matrix> MatrixInvSqrt(const math_Matrix& theA)
0244 {
0245   return MatrixPower(theA, -0.5);
0246 }
0247 
0248 } // namespace MathLin
0249 
0250 #endif // _MathLin_Jacobi_HeaderFile