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_LeastSquares_HeaderFile
0015 #define _MathLin_LeastSquares_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathLin_Gauss.hxx>
0020 #include <MathLin_SVD.hxx>
0021 #include <MathLin_Householder.hxx>
0022 #include <MathUtils_Core.hxx>
0023 
0024 #include <cmath>
0025 
0026 namespace MathLin
0027 {
0028 using namespace MathUtils;
0029 
0030 //! Method for solving least squares problems.
0031 enum class LeastSquaresMethod
0032 {
0033   NormalEquations, //!< A^T*A*x = A^T*b (fast but less stable)
0034   QR,              //!< Householder QR (good balance of speed and stability)
0035   SVD              //!< SVD (most stable, handles rank-deficient matrices)
0036 };
0037 
0038 //! Result for least squares problems.
0039 struct LeastSquaresResult
0040 {
0041   MathUtils::Status          Status = MathUtils::Status::NotConverged;
0042   std::optional<math_Vector> Solution;   //!< Least squares solution x
0043   std::optional<double>      Residual;   //!< ||Ax - b||_2 (L2 norm of residual)
0044   std::optional<double>      ResidualSq; //!< ||Ax - b||_2^2 (squared residual)
0045   int                        Rank = 0;   //!< Numerical rank of A (for SVD)
0046 
0047   bool IsDone() const { return Status == MathUtils::Status::OK; }
0048 
0049   explicit operator bool() const { return IsDone(); }
0050 };
0051 
0052 //! Solve overdetermined linear least squares: minimize ||Ax - b||_2.
0053 //!
0054 //! Given m x n matrix A (m >= n) and m-vector b, finds n-vector x
0055 //! that minimizes the 2-norm of the residual r = Ax - b.
0056 //!
0057 //! Methods:
0058 //! - NormalEquations: Solves A^T*A*x = A^T*b (fastest, may lose precision)
0059 //! - QR: Uses Householder QR decomposition (good general choice)
0060 //! - SVD: Most robust, handles rank-deficient systems
0061 //!
0062 //! @param theA coefficient matrix (m x n, m >= n)
0063 //! @param theB right-hand side vector (length m)
0064 //! @param theMethod solution method (default: QR)
0065 //! @param theTolerance for rank/singularity detection
0066 //! @return least squares result
0067 inline LeastSquaresResult LeastSquares(const math_Matrix& theA,
0068                                        const math_Vector& theB,
0069                                        LeastSquaresMethod theMethod    = LeastSquaresMethod::QR,
0070                                        double             theTolerance = 1.0e-15)
0071 {
0072   LeastSquaresResult aResult;
0073 
0074   const int aRowLower = theA.LowerRow();
0075   const int aRowUpper = theA.UpperRow();
0076   const int aColLower = theA.LowerCol();
0077   const int aColUpper = theA.UpperCol();
0078   const int aM        = aRowUpper - aRowLower + 1;
0079   const int aN        = aColUpper - aColLower + 1;
0080 
0081   // Check dimensions
0082   if (theB.Length() != aM)
0083   {
0084     aResult.Status = Status::InvalidInput;
0085     return aResult;
0086   }
0087 
0088   LinearResult aLinResult;
0089 
0090   switch (theMethod)
0091   {
0092     case LeastSquaresMethod::NormalEquations: {
0093       // Form normal equations: A^T * A * x = A^T * b
0094       math_Matrix aAtA(aColLower, aColUpper, aColLower, aColUpper, 0.0);
0095       math_Vector aAtb(aColLower, aColUpper, 0.0);
0096 
0097       // Compute A^T * A
0098       for (int i = aColLower; i <= aColUpper; ++i)
0099       {
0100         for (int j = aColLower; j <= aColUpper; ++j)
0101         {
0102           double aSum = 0.0;
0103           for (int k = aRowLower; k <= aRowUpper; ++k)
0104           {
0105             aSum += theA(k, i) * theA(k, j);
0106           }
0107           aAtA(i, j) = aSum;
0108         }
0109       }
0110 
0111       // Compute A^T * b
0112       for (int i = aColLower; i <= aColUpper; ++i)
0113       {
0114         double aSum = 0.0;
0115         for (int k = aRowLower; k <= aRowUpper; ++k)
0116         {
0117           aSum += theA(k, i) * theB(theB.Lower() + k - aRowLower);
0118         }
0119         aAtb(i) = aSum;
0120       }
0121 
0122       // Solve the normal equations
0123       aLinResult   = Solve(aAtA, aAtb, theTolerance);
0124       aResult.Rank = (aLinResult.IsDone()) ? aN : 0;
0125     }
0126     break;
0127 
0128     case LeastSquaresMethod::QR: {
0129       aLinResult = SolveQR(theA, theB, theTolerance);
0130       if (aLinResult.IsDone())
0131       {
0132         // Estimate rank from QR (not exact)
0133         aResult.Rank = aN;
0134       }
0135     }
0136     break;
0137 
0138     case LeastSquaresMethod::SVD: {
0139       aLinResult = SolveSVD(theA, theB, theTolerance);
0140       if (aLinResult.IsDone())
0141       {
0142         // Get rank from SVD
0143         SVDResult aSVD = SVD(theA, theTolerance);
0144         aResult.Rank   = aSVD.IsDone() ? aSVD.Rank : 0;
0145       }
0146     }
0147     break;
0148   }
0149 
0150   if (!aLinResult.IsDone())
0151   {
0152     aResult.Status = aLinResult.Status;
0153     return aResult;
0154   }
0155 
0156   aResult.Solution = aLinResult.Solution;
0157 
0158   // Compute residual ||Ax - b||_2
0159   const math_Vector& aX          = *aResult.Solution;
0160   double             aResidualSq = 0.0;
0161 
0162   for (int i = aRowLower; i <= aRowUpper; ++i)
0163   {
0164     double aAxi = 0.0;
0165     for (int j = aColLower; j <= aColUpper; ++j)
0166     {
0167       aAxi += theA(i, j) * aX(j);
0168     }
0169     double aRi = aAxi - theB(theB.Lower() + i - aRowLower);
0170     aResidualSq += aRi * aRi;
0171   }
0172 
0173   aResult.ResidualSq = aResidualSq;
0174   aResult.Residual   = std::sqrt(aResidualSq);
0175   aResult.Status     = Status::OK;
0176   return aResult;
0177 }
0178 
0179 //! Solve weighted least squares: minimize ||W^{1/2}(Ax - b)||_2.
0180 //!
0181 //! Equivalent to minimizing sum of w_i * (a_i^T * x - b_i)^2
0182 //! where w_i are the weights.
0183 //!
0184 //! @param theA coefficient matrix (m x n)
0185 //! @param theB right-hand side vector (length m)
0186 //! @param theW weight vector (length m, positive values)
0187 //! @param theMethod solution method
0188 //! @param theTolerance for rank detection
0189 //! @return weighted least squares result
0190 inline LeastSquaresResult WeightedLeastSquares(
0191   const math_Matrix& theA,
0192   const math_Vector& theB,
0193   const math_Vector& theW,
0194   LeastSquaresMethod theMethod    = LeastSquaresMethod::QR,
0195   double             theTolerance = 1.0e-15)
0196 {
0197   LeastSquaresResult aResult;
0198 
0199   const int aRowLower = theA.LowerRow();
0200   const int aRowUpper = theA.UpperRow();
0201   const int aColLower = theA.LowerCol();
0202   const int aColUpper = theA.UpperCol();
0203   const int aM        = aRowUpper - aRowLower + 1;
0204 
0205   // Check dimensions
0206   if (theB.Length() != aM || theW.Length() != aM)
0207   {
0208     aResult.Status = Status::InvalidInput;
0209     return aResult;
0210   }
0211 
0212   // Check weights are positive
0213   for (int i = theW.Lower(); i <= theW.Upper(); ++i)
0214   {
0215     if (theW(i) <= 0.0)
0216     {
0217       aResult.Status = Status::InvalidInput;
0218       return aResult;
0219     }
0220   }
0221 
0222   // Apply weights: A' = W^{1/2} * A, b' = W^{1/2} * b
0223   math_Matrix aWA(aRowLower, aRowUpper, aColLower, aColUpper);
0224   math_Vector aWB(theB.Lower(), theB.Upper());
0225 
0226   for (int i = aRowLower; i <= aRowUpper; ++i)
0227   {
0228     double aSqrtW = std::sqrt(theW(theW.Lower() + i - aRowLower));
0229     for (int j = aColLower; j <= aColUpper; ++j)
0230     {
0231       aWA(i, j) = aSqrtW * theA(i, j);
0232     }
0233     aWB(theB.Lower() + i - aRowLower) = aSqrtW * theB(theB.Lower() + i - aRowLower);
0234   }
0235 
0236   // Solve weighted system
0237   return LeastSquares(aWA, aWB, theMethod, theTolerance);
0238 }
0239 
0240 //! Solve regularized least squares (Tikhonov/Ridge regression):
0241 //! minimize ||Ax - b||_2^2 + lambda*||x||_2^2
0242 //!
0243 //! Adds regularization to stabilize ill-conditioned problems.
0244 //! The solution is: x = (A^T*A + lambda*I)^{-1} * A^T * b
0245 //!
0246 //! @param theA coefficient matrix (m x n)
0247 //! @param theB right-hand side vector (length m)
0248 //! @param theLambda regularization parameter (>= 0)
0249 //! @param theTolerance for singularity detection
0250 //! @return regularized least squares result
0251 inline LeastSquaresResult RegularizedLeastSquares(const math_Matrix& theA,
0252                                                   const math_Vector& theB,
0253                                                   double             theLambda,
0254                                                   double             theTolerance = 1.0e-15)
0255 {
0256   LeastSquaresResult aResult;
0257 
0258   const int aRowLower = theA.LowerRow();
0259   const int aRowUpper = theA.UpperRow();
0260   const int aColLower = theA.LowerCol();
0261   const int aColUpper = theA.UpperCol();
0262   const int aM        = aRowUpper - aRowLower + 1;
0263   const int aN        = aColUpper - aColLower + 1;
0264 
0265   // Check dimensions
0266   if (theB.Length() != aM)
0267   {
0268     aResult.Status = Status::InvalidInput;
0269     return aResult;
0270   }
0271 
0272   if (theLambda < 0.0)
0273   {
0274     aResult.Status = Status::InvalidInput;
0275     return aResult;
0276   }
0277 
0278   // Form regularized normal equations: (A^T*A + lambda*I) * x = A^T * b
0279   math_Matrix aAtA(aColLower, aColUpper, aColLower, aColUpper, 0.0);
0280   math_Vector aAtb(aColLower, aColUpper, 0.0);
0281 
0282   // Compute A^T * A + lambda*I
0283   for (int i = aColLower; i <= aColUpper; ++i)
0284   {
0285     for (int j = aColLower; j <= aColUpper; ++j)
0286     {
0287       double aSum = 0.0;
0288       for (int k = aRowLower; k <= aRowUpper; ++k)
0289       {
0290         aSum += theA(k, i) * theA(k, j);
0291       }
0292       aAtA(i, j) = aSum;
0293     }
0294     // Add regularization
0295     aAtA(i, i) += theLambda;
0296   }
0297 
0298   // Compute A^T * b
0299   for (int i = aColLower; i <= aColUpper; ++i)
0300   {
0301     double aSum = 0.0;
0302     for (int k = aRowLower; k <= aRowUpper; ++k)
0303     {
0304       aSum += theA(k, i) * theB(theB.Lower() + k - aRowLower);
0305     }
0306     aAtb(i) = aSum;
0307   }
0308 
0309   // Solve the regularized normal equations
0310   LinearResult aLinResult = Solve(aAtA, aAtb, theTolerance);
0311   if (!aLinResult.IsDone())
0312   {
0313     aResult.Status = aLinResult.Status;
0314     return aResult;
0315   }
0316 
0317   aResult.Solution = aLinResult.Solution;
0318   aResult.Rank     = aN;
0319 
0320   // Compute residual
0321   const math_Vector& aX          = *aResult.Solution;
0322   double             aResidualSq = 0.0;
0323 
0324   for (int i = aRowLower; i <= aRowUpper; ++i)
0325   {
0326     double aAxi = 0.0;
0327     for (int j = aColLower; j <= aColUpper; ++j)
0328     {
0329       aAxi += theA(i, j) * aX(j);
0330     }
0331     double aRi = aAxi - theB(theB.Lower() + i - aRowLower);
0332     aResidualSq += aRi * aRi;
0333   }
0334 
0335   aResult.ResidualSq = aResidualSq;
0336   aResult.Residual   = std::sqrt(aResidualSq);
0337   aResult.Status     = Status::OK;
0338   return aResult;
0339 }
0340 
0341 //! Compute optimal regularization parameter using Leave-One-Out Cross-Validation.
0342 //!
0343 //! Minimizes the LOO-CV score: sum_i (a_i^T * x_{-i} - b_i)^2
0344 //! where x_{-i} is the solution with the i-th observation removed.
0345 //!
0346 //! @param theA coefficient matrix
0347 //! @param theB right-hand side vector
0348 //! @param theLambdaMin minimum lambda to consider
0349 //! @param theLambdaMax maximum lambda to consider
0350 //! @param theNbPoints number of lambda values to try
0351 //! @return optimal regularization parameter
0352 inline double OptimalRegularization(const math_Matrix& theA,
0353                                     const math_Vector& theB,
0354                                     double             theLambdaMin = 1.0e-10,
0355                                     double             theLambdaMax = 1.0e2,
0356                                     int                theNbPoints  = 20)
0357 {
0358   double aBestLambda = theLambdaMin;
0359   double aBestScore  = std::numeric_limits<double>::max();
0360 
0361   // Logarithmic grid search
0362   const double aLogMin  = std::log10(theLambdaMin);
0363   const double aLogMax  = std::log10(theLambdaMax);
0364   const double aLogStep = (aLogMax - aLogMin) / (theNbPoints - 1);
0365 
0366   for (int k = 0; k < theNbPoints; ++k)
0367   {
0368     double aLambda = std::pow(10.0, aLogMin + k * aLogStep);
0369 
0370     auto aResult = RegularizedLeastSquares(theA, theB, aLambda);
0371     if (aResult.IsDone() && aResult.ResidualSq)
0372     {
0373       double aScore = *aResult.ResidualSq;
0374       if (aScore < aBestScore)
0375       {
0376         aBestScore  = aScore;
0377         aBestLambda = aLambda;
0378       }
0379     }
0380   }
0381 
0382   return aBestLambda;
0383 }
0384 
0385 } // namespace MathLin
0386 
0387 #endif // _MathLin_LeastSquares_HeaderFile