Back to home page

EIC code displayed by LXR

 
 

    


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

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 _MathSys_LevenbergMarquardt_HeaderFile
0015 #define _MathSys_LevenbergMarquardt_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathLin_Gauss.hxx>
0020 #include <MathUtils_Core.hxx>
0021 
0022 #include <cmath>
0023 
0024 namespace MathSys
0025 {
0026 using namespace MathUtils;
0027 
0028 //! Configuration for Levenberg-Marquardt algorithm.
0029 //! Extends base Config with damping parameter settings.
0030 struct LMConfig : Config
0031 {
0032   double LambdaInit     = 1.0e-3;  //!< Initial damping parameter
0033   double LambdaIncrease = 10.0;    //!< Factor to increase lambda on rejected step
0034   double LambdaDecrease = 0.1;     //!< Factor to decrease lambda on accepted step
0035   double LambdaMax      = 1.0e10;  //!< Maximum lambda value before failing
0036   double LambdaMin      = 1.0e-12; //!< Minimum lambda value
0037 
0038   //! Default constructor.
0039   LMConfig() = default;
0040 
0041   //! Constructor with custom tolerance.
0042   //! @param theTolerance convergence tolerance
0043   //! @param theMaxIter maximum iterations
0044   explicit LMConfig(double theTolerance, int theMaxIter = 100)
0045       : Config(theTolerance, theMaxIter)
0046   {
0047   }
0048 };
0049 
0050 //! Levenberg-Marquardt algorithm for nonlinear least squares.
0051 //!
0052 //! Minimizes ||F(X)||^2 where F is a vector function of vector X.
0053 //! Combines Gauss-Newton method (fast near minimum) with gradient
0054 //! descent (robust far from minimum) using adaptive damping.
0055 //!
0056 //! Algorithm:
0057 //! 1. Compute F(X) and Jacobian J at current point
0058 //! 2. Solve (J^T*J + lambda*I) * dX = -J^T*F for correction dX
0059 //! 3. If ||F(X+dX)||^2 < ||F(X)||^2: accept step, decrease lambda
0060 //! 4. Otherwise: reject step, increase lambda
0061 //! 5. Repeat until convergence
0062 //!
0063 //! @tparam FuncSetType type with NbVariables(), NbEquations(),
0064 //!         Value(const math_Vector& X, math_Vector& F) and
0065 //!         Derivatives(const math_Vector& X, math_Matrix& J) or
0066 //!         Values(const math_Vector& X, math_Vector& F, math_Matrix& J)
0067 //! @param theFunc function set providing residuals and Jacobian
0068 //! @param theStart initial guess vector
0069 //! @param theConfig Levenberg-Marquardt configuration
0070 //! @return result containing solution vector if converged
0071 template <typename FuncSetType>
0072 VectorResult LevenbergMarquardt(FuncSetType&       theFunc,
0073                                 const math_Vector& theStart,
0074                                 const LMConfig&    theConfig = LMConfig())
0075 {
0076   VectorResult aResult;
0077 
0078   const int aNbVars = theFunc.NbVariables();
0079   const int aNbEqs  = theFunc.NbEquations();
0080 
0081   // Check dimensions
0082   if (theStart.Length() != aNbVars)
0083   {
0084     aResult.Status = Status::InvalidInput;
0085     return aResult;
0086   }
0087 
0088   const int aVarLower = theStart.Lower();
0089   const int aVarUpper = theStart.Upper();
0090 
0091   // Working vectors and matrices
0092   math_Vector aSol = theStart;
0093   math_Vector aF(1, aNbEqs);
0094   math_Vector aFNew(1, aNbEqs);
0095   math_Vector aDeltaX(aVarLower, aVarUpper);
0096   math_Vector aGrad(aVarLower, aVarUpper);
0097   math_Matrix aJac(1, aNbEqs, aVarLower, aVarUpper);
0098   math_Matrix aJtJ(aVarLower, aVarUpper, aVarLower, aVarUpper);
0099   math_Vector aJtF(aVarLower, aVarUpper);
0100 
0101   double aLambda = theConfig.LambdaInit;
0102 
0103   // Evaluate initial residual
0104   if (!theFunc.Value(aSol, aF))
0105   {
0106     aResult.Status = Status::NumericalError;
0107     return aResult;
0108   }
0109 
0110   // Compute initial ||F||^2
0111   double aChi2 = 0.0;
0112   for (int i = 1; i <= aNbEqs; ++i)
0113   {
0114     aChi2 += aF(i) * aF(i);
0115   }
0116 
0117   // Main iteration loop
0118   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0119   {
0120     aResult.NbIterations = anIter + 1;
0121 
0122     // Check F convergence
0123     bool aFConverged = true;
0124     for (int i = 1; i <= aNbEqs; ++i)
0125     {
0126       if (std::abs(aF(i)) > theConfig.FTolerance)
0127       {
0128         aFConverged = false;
0129         break;
0130       }
0131     }
0132 
0133     if (aFConverged)
0134     {
0135       aResult.Status   = Status::OK;
0136       aResult.Solution = aSol;
0137       aResult.Value    = aChi2;
0138       return aResult;
0139     }
0140 
0141     // Compute Jacobian
0142     if (!theFunc.Derivatives(aSol, aJac))
0143     {
0144       aResult.Status = Status::NumericalError;
0145       return aResult;
0146     }
0147 
0148     // Compute J^T * J
0149     for (int i = aVarLower; i <= aVarUpper; ++i)
0150     {
0151       for (int j = aVarLower; j <= aVarUpper; ++j)
0152       {
0153         double aSum = 0.0;
0154         for (int k = 1; k <= aNbEqs; ++k)
0155         {
0156           aSum += aJac(k, i) * aJac(k, j);
0157         }
0158         aJtJ(i, j) = aSum;
0159       }
0160     }
0161 
0162     // Compute J^T * F (negative gradient of chi^2)
0163     for (int i = aVarLower; i <= aVarUpper; ++i)
0164     {
0165       double aSum = 0.0;
0166       for (int k = 1; k <= aNbEqs; ++k)
0167       {
0168         aSum += aJac(k, i) * aF(k);
0169       }
0170       aJtF(i)  = aSum;
0171       aGrad(i) = 2.0 * aSum; // Gradient of chi^2 = 2 * J^T * F
0172     }
0173 
0174     // Check gradient convergence
0175     double aGradNorm = 0.0;
0176     for (int i = aVarLower; i <= aVarUpper; ++i)
0177     {
0178       aGradNorm += aGrad(i) * aGrad(i);
0179     }
0180     aGradNorm = std::sqrt(aGradNorm);
0181 
0182     if (aGradNorm < theConfig.Tolerance)
0183     {
0184       aResult.Status   = Status::OK;
0185       aResult.Solution = aSol;
0186       aResult.Value    = aChi2;
0187       aResult.Gradient = aGrad;
0188       return aResult;
0189     }
0190 
0191     // Inner loop: try to find an acceptable step
0192     bool aStepAccepted = false;
0193     for (int aLamIter = 0; aLamIter < 20 && !aStepAccepted; ++aLamIter)
0194     {
0195       // Add damping: J^T*J + lambda*I
0196       math_Matrix aDamped = aJtJ;
0197       for (int i = aVarLower; i <= aVarUpper; ++i)
0198       {
0199         aDamped(i, i) += aLambda;
0200       }
0201 
0202       // Solve (J^T*J + lambda*I) * dX = -J^T*F
0203       math_Vector aNegJtF(aVarLower, aVarUpper);
0204       for (int i = aVarLower; i <= aVarUpper; ++i)
0205       {
0206         aNegJtF(i) = -aJtF(i);
0207       }
0208 
0209       auto aLinResult = MathLin::Solve(aDamped, aNegJtF);
0210       if (!aLinResult.IsDone())
0211       {
0212         // Matrix is singular, increase damping
0213         aLambda *= theConfig.LambdaIncrease;
0214         if (aLambda > theConfig.LambdaMax)
0215         {
0216           aResult.Status   = Status::Singular;
0217           aResult.Solution = aSol;
0218           aResult.Value    = aChi2;
0219           return aResult;
0220         }
0221         continue;
0222       }
0223 
0224       aDeltaX = *aLinResult.Solution;
0225 
0226       // Compute new solution
0227       math_Vector aSolNew(aVarLower, aVarUpper);
0228       for (int i = aVarLower; i <= aVarUpper; ++i)
0229       {
0230         aSolNew(i) = aSol(i) + aDeltaX(i);
0231       }
0232 
0233       // Evaluate new residual
0234       if (!theFunc.Value(aSolNew, aFNew))
0235       {
0236         // Function evaluation failed, increase damping
0237         aLambda *= theConfig.LambdaIncrease;
0238         if (aLambda > theConfig.LambdaMax)
0239         {
0240           aResult.Status   = Status::NumericalError;
0241           aResult.Solution = aSol;
0242           aResult.Value    = aChi2;
0243           return aResult;
0244         }
0245         continue;
0246       }
0247 
0248       // Compute new ||F||^2
0249       double aChi2New = 0.0;
0250       for (int i = 1; i <= aNbEqs; ++i)
0251       {
0252         aChi2New += aFNew(i) * aFNew(i);
0253       }
0254 
0255       // Accept or reject step
0256       if (aChi2New < aChi2)
0257       {
0258         // Step accepted
0259         aSol  = aSolNew;
0260         aF    = aFNew;
0261         aChi2 = aChi2New;
0262         aLambda *= theConfig.LambdaDecrease;
0263         if (aLambda < theConfig.LambdaMin)
0264         {
0265           aLambda = theConfig.LambdaMin;
0266         }
0267         aStepAccepted = true;
0268 
0269         // Check X convergence
0270         bool aXConverged = true;
0271         for (int i = aVarLower; i <= aVarUpper; ++i)
0272         {
0273           if (std::abs(aDeltaX(i)) > theConfig.XTolerance * (1.0 + std::abs(aSol(i))))
0274           {
0275             aXConverged = false;
0276             break;
0277           }
0278         }
0279 
0280         if (aXConverged)
0281         {
0282           aResult.Status   = Status::OK;
0283           aResult.Solution = aSol;
0284           aResult.Value    = aChi2;
0285           return aResult;
0286         }
0287       }
0288       else
0289       {
0290         // Step rejected, increase damping
0291         aLambda *= theConfig.LambdaIncrease;
0292         if (aLambda > theConfig.LambdaMax)
0293         {
0294           aResult.Status   = Status::NotConverged;
0295           aResult.Solution = aSol;
0296           aResult.Value    = aChi2;
0297           return aResult;
0298         }
0299       }
0300     }
0301 
0302     if (!aStepAccepted)
0303     {
0304       // Failed to find acceptable step
0305       aResult.Status   = Status::NotConverged;
0306       aResult.Solution = aSol;
0307       aResult.Value    = aChi2;
0308       return aResult;
0309     }
0310   }
0311 
0312   // Max iterations reached
0313   aResult.Status   = Status::MaxIterations;
0314   aResult.Solution = aSol;
0315   aResult.Value    = aChi2;
0316   return aResult;
0317 }
0318 
0319 //! Levenberg-Marquardt with bounds constraints.
0320 //!
0321 //! Minimizes ||F(X)||^2 subject to theInfBound <= X <= theSupBound.
0322 //! Solution is clamped to bounds after each step.
0323 //!
0324 //! @param theFunc function set providing residuals and Jacobian
0325 //! @param theStart initial guess vector
0326 //! @param theInfBound lower bounds for solution
0327 //! @param theSupBound upper bounds for solution
0328 //! @param theConfig Levenberg-Marquardt configuration
0329 //! @return result containing solution vector if converged
0330 template <typename FuncSetType>
0331 VectorResult LevenbergMarquardtBounded(FuncSetType&       theFunc,
0332                                        const math_Vector& theStart,
0333                                        const math_Vector& theInfBound,
0334                                        const math_Vector& theSupBound,
0335                                        const LMConfig&    theConfig = LMConfig())
0336 {
0337   VectorResult aResult;
0338 
0339   const int aNbVars = theFunc.NbVariables();
0340   const int aNbEqs  = theFunc.NbEquations();
0341 
0342   // Check dimensions
0343   if (theStart.Length() != aNbVars || theInfBound.Length() != aNbVars
0344       || theSupBound.Length() != aNbVars)
0345   {
0346     aResult.Status = Status::InvalidInput;
0347     return aResult;
0348   }
0349 
0350   const int aVarLower = theStart.Lower();
0351   const int aVarUpper = theStart.Upper();
0352 
0353   // Working vectors and matrices
0354   math_Vector aSol = theStart;
0355   math_Vector aF(1, aNbEqs);
0356   math_Vector aFNew(1, aNbEqs);
0357   math_Vector aDeltaX(aVarLower, aVarUpper);
0358   math_Vector aGrad(aVarLower, aVarUpper);
0359   math_Matrix aJac(1, aNbEqs, aVarLower, aVarUpper);
0360   math_Matrix aJtJ(aVarLower, aVarUpper, aVarLower, aVarUpper);
0361   math_Vector aJtF(aVarLower, aVarUpper);
0362 
0363   // Clamp initial solution to bounds
0364   for (int i = aVarLower; i <= aVarUpper; ++i)
0365   {
0366     aSol(i) = MathUtils::Clamp(aSol(i), theInfBound(i), theSupBound(i));
0367   }
0368 
0369   double aLambda = theConfig.LambdaInit;
0370 
0371   // Evaluate initial residual
0372   if (!theFunc.Value(aSol, aF))
0373   {
0374     aResult.Status = Status::NumericalError;
0375     return aResult;
0376   }
0377 
0378   // Compute initial ||F||^2
0379   double aChi2 = 0.0;
0380   for (int i = 1; i <= aNbEqs; ++i)
0381   {
0382     aChi2 += aF(i) * aF(i);
0383   }
0384 
0385   // Main iteration loop
0386   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0387   {
0388     aResult.NbIterations = anIter + 1;
0389 
0390     // Check F convergence
0391     bool aFConverged = true;
0392     for (int i = 1; i <= aNbEqs; ++i)
0393     {
0394       if (std::abs(aF(i)) > theConfig.FTolerance)
0395       {
0396         aFConverged = false;
0397         break;
0398       }
0399     }
0400 
0401     if (aFConverged)
0402     {
0403       aResult.Status   = Status::OK;
0404       aResult.Solution = aSol;
0405       aResult.Value    = aChi2;
0406       return aResult;
0407     }
0408 
0409     // Compute Jacobian
0410     if (!theFunc.Derivatives(aSol, aJac))
0411     {
0412       aResult.Status = Status::NumericalError;
0413       return aResult;
0414     }
0415 
0416     // Compute J^T * J
0417     for (int i = aVarLower; i <= aVarUpper; ++i)
0418     {
0419       for (int j = aVarLower; j <= aVarUpper; ++j)
0420       {
0421         double aSum = 0.0;
0422         for (int k = 1; k <= aNbEqs; ++k)
0423         {
0424           aSum += aJac(k, i) * aJac(k, j);
0425         }
0426         aJtJ(i, j) = aSum;
0427       }
0428     }
0429 
0430     // Compute J^T * F
0431     for (int i = aVarLower; i <= aVarUpper; ++i)
0432     {
0433       double aSum = 0.0;
0434       for (int k = 1; k <= aNbEqs; ++k)
0435       {
0436         aSum += aJac(k, i) * aF(k);
0437       }
0438       aJtF(i)  = aSum;
0439       aGrad(i) = 2.0 * aSum;
0440     }
0441 
0442     // Check gradient convergence
0443     double aGradNorm = 0.0;
0444     for (int i = aVarLower; i <= aVarUpper; ++i)
0445     {
0446       aGradNorm += aGrad(i) * aGrad(i);
0447     }
0448     aGradNorm = std::sqrt(aGradNorm);
0449 
0450     if (aGradNorm < theConfig.Tolerance)
0451     {
0452       aResult.Status   = Status::OK;
0453       aResult.Solution = aSol;
0454       aResult.Value    = aChi2;
0455       aResult.Gradient = aGrad;
0456       return aResult;
0457     }
0458 
0459     // Inner loop: try to find an acceptable step
0460     bool aStepAccepted = false;
0461     for (int aLamIter = 0; aLamIter < 20 && !aStepAccepted; ++aLamIter)
0462     {
0463       // Add damping
0464       math_Matrix aDamped = aJtJ;
0465       for (int i = aVarLower; i <= aVarUpper; ++i)
0466       {
0467         aDamped(i, i) += aLambda;
0468       }
0469 
0470       // Solve (J^T*J + lambda*I) * dX = -J^T*F
0471       math_Vector aNegJtF(aVarLower, aVarUpper);
0472       for (int i = aVarLower; i <= aVarUpper; ++i)
0473       {
0474         aNegJtF(i) = -aJtF(i);
0475       }
0476 
0477       auto aLinResult = MathLin::Solve(aDamped, aNegJtF);
0478       if (!aLinResult.IsDone())
0479       {
0480         aLambda *= theConfig.LambdaIncrease;
0481         if (aLambda > theConfig.LambdaMax)
0482         {
0483           aResult.Status   = Status::Singular;
0484           aResult.Solution = aSol;
0485           aResult.Value    = aChi2;
0486           return aResult;
0487         }
0488         continue;
0489       }
0490 
0491       aDeltaX = *aLinResult.Solution;
0492 
0493       // Compute new solution with bounds clamping
0494       math_Vector aSolNew(aVarLower, aVarUpper);
0495       for (int i = aVarLower; i <= aVarUpper; ++i)
0496       {
0497         aSolNew(i) = MathUtils::Clamp(aSol(i) + aDeltaX(i), theInfBound(i), theSupBound(i));
0498       }
0499 
0500       // Evaluate new residual
0501       if (!theFunc.Value(aSolNew, aFNew))
0502       {
0503         aLambda *= theConfig.LambdaIncrease;
0504         if (aLambda > theConfig.LambdaMax)
0505         {
0506           aResult.Status   = Status::NumericalError;
0507           aResult.Solution = aSol;
0508           aResult.Value    = aChi2;
0509           return aResult;
0510         }
0511         continue;
0512       }
0513 
0514       // Compute new ||F||^2
0515       double aChi2New = 0.0;
0516       for (int i = 1; i <= aNbEqs; ++i)
0517       {
0518         aChi2New += aFNew(i) * aFNew(i);
0519       }
0520 
0521       // Accept or reject step
0522       if (aChi2New < aChi2)
0523       {
0524         aSol  = aSolNew;
0525         aF    = aFNew;
0526         aChi2 = aChi2New;
0527         aLambda *= theConfig.LambdaDecrease;
0528         if (aLambda < theConfig.LambdaMin)
0529         {
0530           aLambda = theConfig.LambdaMin;
0531         }
0532         aStepAccepted = true;
0533 
0534         // Check X convergence
0535         bool aXConverged = true;
0536         for (int i = aVarLower; i <= aVarUpper; ++i)
0537         {
0538           if (std::abs(aDeltaX(i)) > theConfig.XTolerance * (1.0 + std::abs(aSol(i))))
0539           {
0540             aXConverged = false;
0541             break;
0542           }
0543         }
0544 
0545         if (aXConverged)
0546         {
0547           aResult.Status   = Status::OK;
0548           aResult.Solution = aSol;
0549           aResult.Value    = aChi2;
0550           return aResult;
0551         }
0552       }
0553       else
0554       {
0555         aLambda *= theConfig.LambdaIncrease;
0556         if (aLambda > theConfig.LambdaMax)
0557         {
0558           aResult.Status   = Status::NotConverged;
0559           aResult.Solution = aSol;
0560           aResult.Value    = aChi2;
0561           return aResult;
0562         }
0563       }
0564     }
0565 
0566     if (!aStepAccepted)
0567     {
0568       aResult.Status   = Status::NotConverged;
0569       aResult.Solution = aSol;
0570       aResult.Value    = aChi2;
0571       return aResult;
0572     }
0573   }
0574 
0575   // Max iterations reached
0576   aResult.Status   = Status::MaxIterations;
0577   aResult.Solution = aSol;
0578   aResult.Value    = aChi2;
0579   return aResult;
0580 }
0581 
0582 } // namespace MathSys
0583 
0584 #endif // _MathSys_LevenbergMarquardt_HeaderFile