// 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_Gauss_HeaderFile #define _MathLin_Gauss_HeaderFile #include "MathUtils_Types.hxx" #include "MathUtils_Config.hxx" #include "MathUtils_Core.hxx" #include "math_Vector.hxx" #include "math_Matrix.hxx" #include "math_IntegerVector.hxx" #include namespace MathLin { using namespace MathUtils; //! Result for LU decomposition. struct LUResult { MathUtils::Status Status = MathUtils::Status::NotConverged; std::optional LU; //!< Combined L and U matrices std::optional Pivot; //!< Pivot indices std::optional Determinant; int Sign = 1; //!< Sign from row interchanges bool IsDone() const { return Status == MathUtils::Status::OK; } explicit operator bool() const { return IsDone(); } }; //! Perform LU decomposition of matrix A with partial pivoting. //! Decomposes A into L*U where L is lower triangular with unit diagonal //! and U is upper triangular. The result stores L and U in a combined matrix. //! //! @param theA input square matrix //! @param theMinPivot minimum pivot value (smaller treated as singular) //! @return LU decomposition result inline LUResult LU(const math_Matrix& theA, double theMinPivot = 1.0e-20) { LUResult aResult; const int aRowLower = theA.LowerRow(); const int aRowUpper = theA.UpperRow(); const int aColLower = theA.LowerCol(); const int aColUpper = theA.UpperCol(); const int aRowCount = aRowUpper - aRowLower + 1; const int aColCount = aColUpper - aColLower + 1; // Check for square matrix if (aRowCount != aColCount) { aResult.Status = Status::InvalidInput; return aResult; } // Initialize LU matrix as copy of A aResult.LU = theA; aResult.Pivot = math_IntegerVector(aRowLower, aRowUpper); aResult.Sign = 1; math_Matrix& aLU = *aResult.LU; math_IntegerVector& aPivot = *aResult.Pivot; // Scaling factors for implicit pivoting math_Vector aScale(aRowLower, aRowUpper); for (int i = aRowLower; i <= aRowUpper; ++i) { double aMax = 0.0; for (int j = aColLower; j <= aColUpper; ++j) { const double aAbs = std::abs(aLU(i, j)); if (aAbs > aMax) { aMax = aAbs; } } if (aMax < theMinPivot) { aResult.Status = Status::NumericalError; // Singular matrix return aResult; } aScale(i) = 1.0 / aMax; } // Crout's algorithm with partial pivoting for (int k = aRowLower; k <= aRowUpper; ++k) { // Find pivot double aMaxScaled = 0.0; int aPivotRow = k; for (int i = k; i <= aRowUpper; ++i) { const double aScaled = aScale(i) * std::abs(aLU(i, k)); if (aScaled > aMaxScaled) { aMaxScaled = aScaled; aPivotRow = i; } } // Swap rows if needed if (aPivotRow != k) { for (int j = aColLower; j <= aColUpper; ++j) { std::swap(aLU(aPivotRow, j), aLU(k, j)); } aResult.Sign = -aResult.Sign; std::swap(aScale(aPivotRow), aScale(k)); } aPivot(k) = aPivotRow; // Check for singular matrix if (std::abs(aLU(k, k)) < theMinPivot) { aResult.Status = Status::NumericalError; return aResult; } // Eliminate below diagonal for (int i = k + 1; i <= aRowUpper; ++i) { aLU(i, k) /= aLU(k, k); const double aFactor = aLU(i, k); for (int j = k + 1; j <= aColUpper; ++j) { aLU(i, j) -= aFactor * aLU(k, j); } } } // Compute determinant aResult.Determinant = static_cast(aResult.Sign); for (int i = aRowLower; i <= aRowUpper; ++i) { *aResult.Determinant *= aLU(i, i); } aResult.Status = Status::OK; return aResult; } //! Solve linear system AX = B using LU decomposition. //! //! @param theA coefficient matrix (square) //! @param theB right-hand side vector //! @param theMinPivot minimum pivot value //! @return result containing solution vector inline LinearResult Solve(const math_Matrix& theA, const math_Vector& theB, double theMinPivot = 1.0e-20) { LinearResult aResult; // Perform LU decomposition LUResult aLURes = LU(theA, theMinPivot); if (!aLURes.IsDone()) { aResult.Status = aLURes.Status; return aResult; } const int aRowLower = theA.LowerRow(); const int aRowUpper = theA.UpperRow(); // Check dimensions if (theB.Lower() != aRowLower || theB.Upper() != aRowUpper) { aResult.Status = Status::InvalidInput; return aResult; } const math_Matrix& aLU = *aLURes.LU; const math_IntegerVector& aPivot = *aLURes.Pivot; // Copy B to working vector (we modify it in-place like the legacy algorithm) math_Vector aX = theB; // Forward substitution with in-place permutation (matches legacy LU_Solve) int aFirstNonZero = 0; for (int i = aRowLower; i <= aRowUpper; ++i) { const int aPivotIdx = aPivot(i); double aSum = aX(aPivotIdx); aX(aPivotIdx) = aX(i); // Swap elements in-place if (aFirstNonZero != 0) { for (int j = aFirstNonZero; j < i; ++j) { aSum -= aLU(i, j) * aX(j); } } else if (!MathUtils::IsZero(aSum)) { aFirstNonZero = i; } aX(i) = aSum; } // Back substitution (solve Ux = y) for (int i = aRowUpper; i >= aRowLower; --i) { double aSum = aX(i); for (int j = i + 1; j <= aRowUpper; ++j) { aSum -= aLU(i, j) * aX(j); } aX(i) = aSum / aLU(i, i); } aResult.Status = Status::OK; aResult.Solution = aX; aResult.Determinant = aLURes.Determinant; return aResult; } //! Solve multiple linear systems AX = B where B is a matrix. //! Each column of B is a separate right-hand side. //! //! @param theA coefficient matrix (square) //! @param theB right-hand side matrix //! @param theMinPivot minimum pivot value //! @return result containing solution matrix inline LinearMultipleResult SolveMultiple(const math_Matrix& theA, const math_Matrix& theB, double theMinPivot = 1.0e-20) { LinearMultipleResult aResult; // Perform LU decomposition LUResult aLURes = LU(theA, theMinPivot); if (!aLURes.IsDone()) { aResult.Status = aLURes.Status; return aResult; } const int aRowLower = theA.LowerRow(); const int aRowUpper = theA.UpperRow(); // Check dimensions if (theB.LowerRow() != aRowLower || theB.UpperRow() != aRowUpper) { aResult.Status = Status::InvalidInput; return aResult; } const math_Matrix& aLU = *aLURes.LU; const math_IntegerVector& aPivot = *aLURes.Pivot; // Solve for each column of B math_Matrix aX(aRowLower, aRowUpper, theB.LowerCol(), theB.UpperCol()); for (int col = theB.LowerCol(); col <= theB.UpperCol(); ++col) { // Extract column as working vector math_Vector aWork(aRowLower, aRowUpper); for (int i = aRowLower; i <= aRowUpper; ++i) { aWork(i) = theB(i, col); } // Forward substitution with in-place permutation int aFirstNonZero = 0; for (int i = aRowLower; i <= aRowUpper; ++i) { const int aPivotIdx = aPivot(i); double aSum = aWork(aPivotIdx); aWork(aPivotIdx) = aWork(i); // Swap elements in-place if (aFirstNonZero != 0) { for (int j = aFirstNonZero; j < i; ++j) { aSum -= aLU(i, j) * aWork(j); } } else if (!MathUtils::IsZero(aSum)) { aFirstNonZero = i; } aWork(i) = aSum; } // Back substitution for (int i = aRowUpper; i >= aRowLower; --i) { double aSum = aWork(i); for (int j = i + 1; j <= aRowUpper; ++j) { aSum -= aLU(i, j) * aWork(j); } aWork(i) = aSum / aLU(i, i); } // Copy result to output matrix for (int i = aRowLower; i <= aRowUpper; ++i) { aX(i, col) = aWork(i); } } aResult.Status = Status::OK; aResult.Determinant = aLURes.Determinant; aResult.Solutions = aX; return aResult; } //! Compute determinant of matrix A. //! //! @param theA input square matrix //! @param theMinPivot minimum pivot value //! @return result containing determinant value inline LinearResult Determinant(const math_Matrix& theA, double theMinPivot = 1.0e-20) { LinearResult aResult; LUResult aLURes = LU(theA, theMinPivot); if (!aLURes.IsDone()) { aResult.Status = aLURes.Status; aResult.Determinant = 0.0; return aResult; } aResult.Status = Status::OK; aResult.Determinant = aLURes.Determinant; return aResult; } //! Compute inverse of matrix A. //! //! @param theA input square matrix //! @param theMinPivot minimum pivot value //! @return result containing inverse matrix inline InverseResult Invert(const math_Matrix& theA, double theMinPivot = 1.0e-20) { InverseResult aResult; // Perform LU decomposition LUResult aLURes = LU(theA, theMinPivot); if (!aLURes.IsDone()) { aResult.Status = aLURes.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& aLU = *aLURes.LU; const math_IntegerVector& aPivot = *aLURes.Pivot; // Compute inverse column by column using LU_Solve approach math_Matrix aInv(aRowLower, aRowUpper, aColLower, aColUpper, 0.0); math_Vector aCol(aRowLower, aRowUpper); for (int col = aColLower; col <= aColUpper; ++col) { // Initialize column as unit vector for (int i = aRowLower; i <= aRowUpper; ++i) { aCol(i) = 0.0; } aCol(col - aColLower + aRowLower) = 1.0; // Forward substitution with in-place permutation int aFirstNonZero = 0; for (int i = aRowLower; i <= aRowUpper; ++i) { const int aPivotIdx = aPivot(i); double aSum = aCol(aPivotIdx); aCol(aPivotIdx) = aCol(i); // Swap elements in-place if (aFirstNonZero != 0) { for (int j = aFirstNonZero; j < i; ++j) { aSum -= aLU(i, j) * aCol(j); } } else if (!MathUtils::IsZero(aSum)) { aFirstNonZero = i; } aCol(i) = aSum; } // Back substitution for (int i = aRowUpper; i >= aRowLower; --i) { double aSum = aCol(i); for (int j = i + 1; j <= aRowUpper; ++j) { aSum -= aLU(i, j) * aCol(j); } aCol(i) = aSum / aLU(i, i); } // Copy result to inverse matrix for (int i = aRowLower; i <= aRowUpper; ++i) { aInv(i, col) = aCol(i); } } aResult.Status = Status::OK; aResult.Inverse = aInv; aResult.Determinant = aLURes.Determinant; return aResult; } } // namespace MathLin #endif // _MathLin_Gauss_HeaderFile