// Copyright (c) 2025 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #ifndef _MathLin_SVD_HeaderFile #define _MathLin_SVD_HeaderFile #include "MathUtils_Types.hxx" #include "MathUtils_Config.hxx" #include "math_Recipes.hxx" #include "MathUtils_Core.hxx" #include #include namespace MathLin { using namespace MathUtils; //! Result for SVD decomposition. struct SVDResult { MathUtils::Status Status = MathUtils::Status::NotConverged; std::optional U; //!< Left singular vectors (m x n) std::optional SingularValues; //!< Singular values (n elements) std::optional V; //!< Right singular vectors (n x n) int Rank = 0; //!< Numerical rank bool IsDone() const { return Status == MathUtils::Status::OK; } explicit operator bool() const { return IsDone(); } }; //! Singular Value Decomposition: A = U * diag(S) * V^T. //! //! Decomposes an m x n matrix A into: //! - U: m x n matrix of left singular vectors (orthonormal columns) //! - S: n singular values in descending order //! - V: n x n matrix of right singular vectors (orthonormal) //! //! Properties: //! - Works for any m x n matrix (m can be less, equal, or greater than n) //! - Singular values are always non-negative //! - Provides the best low-rank approximation of a matrix //! - Useful for solving ill-conditioned linear systems //! //! @param theA input matrix A (m x n) //! @param theTolerance for rank determination (relative to largest singular value) //! @return SVD decomposition result inline SVDResult SVD(const math_Matrix& theA, double theTolerance = 1.0e-15) { SVDResult aResult; const int aRowLower = theA.LowerRow(); const int aRowUpper = theA.UpperRow(); const int aColLower = theA.LowerCol(); const int aColUpper = theA.UpperCol(); const int aM = aRowUpper - aRowLower + 1; // Number of rows const int aN = aColUpper - aColLower + 1; // Number of columns // U matrix needs to be at least n x n for the algorithm const int aURows = std::max(aM, aN); // Create working matrices with 1-based indexing as required by SVD_Decompose math_Matrix aU(1, aURows, 1, aN, 0.0); math_Vector aW(1, aN); math_Matrix aV(1, aN, 1, aN); // Copy input matrix to U for (int i = aRowLower; i <= aRowUpper; ++i) { for (int j = aColLower; j <= aColUpper; ++j) { aU(i - aRowLower + 1, j - aColLower + 1) = theA(i, j); } } // Perform SVD decomposition if (SVD_Decompose(aU, aW, aV) != 0) { aResult.Status = Status::NumericalError; return aResult; } // Determine numerical rank double aMaxSV = 0.0; for (int i = 1; i <= aN; ++i) { aMaxSV = std::max(aMaxSV, aW(i)); } double aThreshold = theTolerance * aMaxSV; aResult.Rank = 0; for (int i = 1; i <= aN; ++i) { if (aW(i) > aThreshold) { ++aResult.Rank; } } // Copy results back with original indexing aResult.U = math_Matrix(aRowLower, aRowLower + aM - 1, aColLower, aColLower + aN - 1); for (int i = 1; i <= aM; ++i) { for (int j = 1; j <= aN; ++j) { (*aResult.U)(aRowLower + i - 1, aColLower + j - 1) = aU(i, j); } } aResult.SingularValues = math_Vector(aColLower, aColLower + aN - 1); for (int i = 1; i <= aN; ++i) { (*aResult.SingularValues)(aColLower + i - 1) = aW(i); } aResult.V = math_Matrix(aColLower, aColLower + aN - 1, aColLower, aColLower + aN - 1); for (int i = 1; i <= aN; ++i) { for (int j = 1; j <= aN; ++j) { (*aResult.V)(aColLower + i - 1, aColLower + j - 1) = aV(i, j); } } aResult.Status = Status::OK; return aResult; } //! Solve linear system Ax = b using SVD decomposition. //! This is particularly useful for ill-conditioned or singular systems. //! //! For overdetermined systems (m > n), finds the least squares solution. //! For underdetermined systems (m < n), finds the minimum norm solution. //! //! @param theA coefficient matrix (m x n) //! @param theB right-hand side vector (length m) //! @param theTolerance for singular value threshold //! @return result containing solution vector inline LinearResult SolveSVD(const math_Matrix& theA, const math_Vector& theB, double theTolerance = 1.0e-6) { LinearResult aResult; // Perform SVD SVDResult aSVD = SVD(theA, theTolerance); if (!aSVD.IsDone()) { aResult.Status = aSVD.Status; return aResult; } const int aRowLower = theA.LowerRow(); const int aRowUpper = theA.UpperRow(); const int aColLower = theA.LowerCol(); const int aColUpper = theA.UpperCol(); const int aM = aRowUpper - aRowLower + 1; // Check dimensions if (theB.Length() != aM) { aResult.Status = Status::InvalidInput; return aResult; } const int aBLower = theB.Lower(); // B vector may have different indexing const math_Matrix& aU = *aSVD.U; const math_Vector& aW = *aSVD.SingularValues; const math_Matrix& aV = *aSVD.V; // Compute threshold for singular values double aMaxSV = 0.0; for (int i = aColLower; i <= aColUpper; ++i) { aMaxSV = std::max(aMaxSV, aW(i)); } double aWMin = theTolerance * aMaxSV; // Solve: x = V * diag(1/w) * U^T * b // First compute tmp = U^T * b math_Vector aTmp(aColLower, aColUpper, 0.0); for (int j = aColLower; j <= aColUpper; ++j) { double aSum = 0.0; for (int i = aRowLower; i <= aRowUpper; ++i) { // Map i to B's index space: B[aBLower + (i - aRowLower)] aSum += aU(i, j) * theB(aBLower + (i - aRowLower)); } // Divide by singular value if above threshold if (aW(j) > aWMin) { aTmp(j) = aSum / aW(j); } // else aTmp(j) remains 0 (regularization) } // Compute x = V * tmp aResult.Solution = math_Vector(aColLower, aColUpper, 0.0); for (int i = aColLower; i <= aColUpper; ++i) { double aSum = 0.0; for (int j = aColLower; j <= aColUpper; ++j) { aSum += aV(i, j) * aTmp(j); } (*aResult.Solution)(i) = aSum; } aResult.Status = Status::OK; return aResult; } //! Compute pseudo-inverse (Moore-Penrose inverse) of matrix A. //! A^+ = V * diag(1/w) * U^T where singular values below threshold are set to 0. //! //! Properties: //! - A * A^+ * A = A //! - A^+ * A * A^+ = A^+ //! - (A * A^+)^T = A * A^+ //! - (A^+ * A)^T = A^+ * A //! //! @param theA input matrix (m x n) //! @param theTolerance for singular value threshold //! @return result containing pseudo-inverse matrix (n x m) inline InverseResult PseudoInverse(const math_Matrix& theA, double theTolerance = 1.0e-6) { InverseResult aResult; // Perform SVD SVDResult aSVD = SVD(theA, theTolerance); if (!aSVD.IsDone()) { aResult.Status = aSVD.Status; return aResult; } const int aRowLower = theA.LowerRow(); const int aRowUpper = theA.UpperRow(); const int aColLower = theA.LowerCol(); const int aColUpper = theA.UpperCol(); const math_Matrix& aU = *aSVD.U; const math_Vector& aW = *aSVD.SingularValues; const math_Matrix& aV = *aSVD.V; // Compute threshold double aMaxSV = 0.0; for (int i = aColLower; i <= aColUpper; ++i) { aMaxSV = std::max(aMaxSV, aW(i)); } double aWMin = theTolerance * aMaxSV; // Compute A^+ = V * diag(1/w) * U^T // Result is n x m aResult.Inverse = math_Matrix(aColLower, aColUpper, aRowLower, aRowUpper, 0.0); for (int i = aColLower; i <= aColUpper; ++i) { for (int j = aRowLower; j <= aRowUpper; ++j) { double aSum = 0.0; for (int k = aColLower; k <= aColUpper; ++k) { if (aW(k) > aWMin) { aSum += aV(i, k) * aU(j, k) / aW(k); } } (*aResult.Inverse)(i, j) = aSum; } } aResult.Status = Status::OK; return aResult; } //! Compute condition number of matrix using SVD. //! Condition number = sigma_max / sigma_min (ratio of largest to smallest singular value). //! //! High condition number (> 1e10) indicates ill-conditioned matrix. //! //! @param theA input matrix //! @return condition number (infinity if matrix is singular) inline double ConditionNumber(const math_Matrix& theA) { SVDResult aSVD = SVD(theA); if (!aSVD.IsDone()) { return std::numeric_limits::infinity(); } const math_Vector& aW = *aSVD.SingularValues; const int aLower = aW.Lower(); const int aUpper = aW.Upper(); double aMaxSV = 0.0; double aMinSV = std::numeric_limits::max(); for (int i = aLower; i <= aUpper; ++i) { if (aW(i) > 0.0) { aMaxSV = std::max(aMaxSV, aW(i)); aMinSV = std::min(aMinSV, aW(i)); } } if (aMinSV <= 0.0 || aMaxSV <= 0.0) { return std::numeric_limits::infinity(); } return aMaxSV / aMinSV; } //! Compute numerical rank of matrix using SVD. //! Rank is the number of singular values above the threshold. //! //! @param theA input matrix //! @param theTolerance relative tolerance for singular values //! @return numerical rank inline int NumericalRank(const math_Matrix& theA, double theTolerance = 1.0e-15) { SVDResult aSVD = SVD(theA, theTolerance); return aSVD.IsDone() ? aSVD.Rank : 0; } } // namespace MathLin #endif // _MathLin_SVD_HeaderFile