Back to home page

EIC code displayed by LXR

 
 

    


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

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 _MathOpt_Newton_HeaderFile
0015 #define _MathOpt_Newton_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathLin_Gauss.hxx>
0020 #include <MathUtils_Core.hxx>
0021 #include <MathUtils_LineSearch.hxx>
0022 #include <MathUtils_Deriv.hxx>
0023 
0024 #include <cmath>
0025 
0026 namespace MathOpt
0027 {
0028 using namespace MathUtils;
0029 
0030 //! Configuration for Newton minimization with Hessian.
0031 struct NewtonConfig : Config
0032 {
0033   double Regularization = 1.0e-8; //!< Diagonal regularization for non-positive definite Hessian
0034   bool   UseLineSearch  = true;   //!< Whether to use line search (recommended)
0035 
0036   //! Default constructor.
0037   NewtonConfig() = default;
0038 
0039   //! Constructor with tolerance.
0040   explicit NewtonConfig(double theTolerance, int theMaxIter = 100)
0041       : Config(theTolerance, theMaxIter)
0042   {
0043   }
0044 };
0045 
0046 //! Newton's method for N-dimensional minimization using Hessian.
0047 //!
0048 //! Fastest convergence near minimum (quadratic) but requires Hessian computation.
0049 //! Uses line search for global convergence and Hessian regularization
0050 //! when the Hessian is not positive definite.
0051 //!
0052 //! Algorithm:
0053 //! 1. Compute gradient g and Hessian H at current point
0054 //! 2. If H is not positive definite, regularize: H = H + lambda*I
0055 //! 3. Solve H * p = -g for search direction p
0056 //! 4. Perform line search along p
0057 //! 5. Update x = x + alpha * p
0058 //! 6. Repeat until convergence
0059 //!
0060 //! @tparam Function type with:
0061 //!   - Value(const math_Vector&, double&) for function value
0062 //!   - Gradient(const math_Vector&, math_Vector&) for gradient
0063 //!   - Hessian(const math_Vector&, math_Matrix&) for Hessian
0064 //! @param theFunc function object with value, gradient, and Hessian
0065 //! @param theStartingPoint initial guess
0066 //! @param theConfig solver configuration
0067 //! @return result containing minimum location and value
0068 template <typename Function>
0069 VectorResult Newton(Function&           theFunc,
0070                     const math_Vector&  theStartingPoint,
0071                     const NewtonConfig& theConfig = NewtonConfig())
0072 {
0073   VectorResult aResult;
0074 
0075   const int aLower = theStartingPoint.Lower();
0076   const int aUpper = theStartingPoint.Upper();
0077 
0078   // Current point
0079   math_Vector aX(aLower, aUpper);
0080   aX = theStartingPoint;
0081 
0082   double aFx = 0.0;
0083   if (!theFunc.Value(aX, aFx))
0084   {
0085     aResult.Status = Status::NumericalError;
0086     return aResult;
0087   }
0088 
0089   // Gradient at current point
0090   math_Vector aGrad(aLower, aUpper);
0091   if (!theFunc.Gradient(aX, aGrad))
0092   {
0093     aResult.Status = Status::NumericalError;
0094     return aResult;
0095   }
0096 
0097   // Check if already at minimum
0098   double aGradNorm = 0.0;
0099   for (int i = aLower; i <= aUpper; ++i)
0100   {
0101     aGradNorm += MathUtils::Sqr(aGrad(i));
0102   }
0103   aGradNorm = std::sqrt(aGradNorm);
0104 
0105   if (aGradNorm < theConfig.FTolerance)
0106   {
0107     aResult.Status   = Status::OK;
0108     aResult.Solution = aX;
0109     aResult.Value    = aFx;
0110     aResult.Gradient = aGrad;
0111     return aResult;
0112   }
0113 
0114   // Working vectors and matrices
0115   math_Vector aDir(aLower, aUpper);
0116   math_Vector aXNew(aLower, aUpper);
0117   math_Vector aGradNew(aLower, aUpper);
0118   math_Matrix aHessian(aLower, aUpper, aLower, aUpper);
0119   math_Vector aNegGrad(aLower, aUpper);
0120 
0121   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0122   {
0123     aResult.NbIterations = anIter + 1;
0124 
0125     // Compute Hessian
0126     if (!theFunc.Hessian(aX, aHessian))
0127     {
0128       aResult.Status   = Status::NumericalError;
0129       aResult.Solution = aX;
0130       aResult.Value    = aFx;
0131       return aResult;
0132     }
0133 
0134     // Prepare negative gradient
0135     for (int i = aLower; i <= aUpper; ++i)
0136     {
0137       aNegGrad(i) = -aGrad(i);
0138     }
0139 
0140     // Try to solve H * p = -g
0141     auto aLinResult = MathLin::Solve(aHessian, aNegGrad);
0142 
0143     if (!aLinResult.IsDone())
0144     {
0145       // Hessian is singular or not positive definite, add regularization
0146       double aLambda = theConfig.Regularization;
0147       bool   aSolved = false;
0148 
0149       for (int k = 0; k < 10 && !aSolved; ++k)
0150       {
0151         math_Matrix aRegHessian = aHessian;
0152         for (int i = aLower; i <= aUpper; ++i)
0153         {
0154           aRegHessian(i, i) += aLambda;
0155         }
0156 
0157         aLinResult = MathLin::Solve(aRegHessian, aNegGrad);
0158         if (aLinResult.IsDone())
0159         {
0160           aSolved = true;
0161         }
0162         else
0163         {
0164           aLambda *= 10.0;
0165         }
0166       }
0167 
0168       if (!aSolved)
0169       {
0170         // Fall back to steepest descent
0171         for (int i = aLower; i <= aUpper; ++i)
0172         {
0173           aDir(i) = -aGrad(i);
0174         }
0175         goto perform_line_search;
0176       }
0177     }
0178 
0179     aDir = *aLinResult.Solution;
0180 
0181     // Check if direction is descent
0182     {
0183       double aDirDeriv = 0.0;
0184       for (int i = aLower; i <= aUpper; ++i)
0185       {
0186         aDirDeriv += aGrad(i) * aDir(i);
0187       }
0188 
0189       if (aDirDeriv >= 0.0)
0190       {
0191         // Not a descent direction, use steepest descent
0192         for (int i = aLower; i <= aUpper; ++i)
0193         {
0194           aDir(i) = -aGrad(i);
0195         }
0196       }
0197     }
0198 
0199   perform_line_search:
0200     if (theConfig.UseLineSearch)
0201     {
0202       // Line search
0203       MathUtils::LineSearchResult aLineResult =
0204         MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0205 
0206       if (!aLineResult.IsValid)
0207       {
0208         // Line search failed, try steepest descent
0209         for (int i = aLower; i <= aUpper; ++i)
0210         {
0211           aDir(i) = -aGrad(i);
0212         }
0213         aLineResult =
0214           MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0215 
0216         if (!aLineResult.IsValid)
0217         {
0218           aResult.Status   = Status::NotConverged;
0219           aResult.Solution = aX;
0220           aResult.Value    = aFx;
0221           aResult.Gradient = aGrad;
0222           return aResult;
0223         }
0224       }
0225 
0226       // Compute new point
0227       for (int i = aLower; i <= aUpper; ++i)
0228       {
0229         aXNew(i) = aX(i) + aLineResult.Alpha * aDir(i);
0230       }
0231       aFx = aLineResult.FNew;
0232     }
0233     else
0234     {
0235       // Full Newton step (no line search)
0236       for (int i = aLower; i <= aUpper; ++i)
0237       {
0238         aXNew(i) = aX(i) + aDir(i);
0239       }
0240 
0241       if (!theFunc.Value(aXNew, aFx))
0242       {
0243         aResult.Status   = Status::NumericalError;
0244         aResult.Solution = aX;
0245         return aResult;
0246       }
0247     }
0248 
0249     // Check X convergence
0250     double aMaxDiff = 0.0;
0251     for (int i = aLower; i <= aUpper; ++i)
0252     {
0253       aMaxDiff = std::max(aMaxDiff, std::abs(aXNew(i) - aX(i)));
0254     }
0255 
0256     // Evaluate gradient at new point
0257     if (!theFunc.Gradient(aXNew, aGradNew))
0258     {
0259       aResult.Status   = Status::NumericalError;
0260       aResult.Solution = aXNew;
0261       aResult.Value    = aFx;
0262       return aResult;
0263     }
0264 
0265     // Check gradient convergence
0266     aGradNorm = 0.0;
0267     for (int i = aLower; i <= aUpper; ++i)
0268     {
0269       aGradNorm += MathUtils::Sqr(aGradNew(i));
0270     }
0271     aGradNorm = std::sqrt(aGradNorm);
0272 
0273     if (aGradNorm < theConfig.FTolerance)
0274     {
0275       aResult.Status   = Status::OK;
0276       aResult.Solution = aXNew;
0277       aResult.Value    = aFx;
0278       aResult.Gradient = aGradNew;
0279       return aResult;
0280     }
0281 
0282     if (aMaxDiff < theConfig.XTolerance)
0283     {
0284       aResult.Status   = Status::OK;
0285       aResult.Solution = aXNew;
0286       aResult.Value    = aFx;
0287       aResult.Gradient = aGradNew;
0288       return aResult;
0289     }
0290 
0291     // Update for next iteration
0292     aX    = aXNew;
0293     aGrad = aGradNew;
0294   }
0295 
0296   // Maximum iterations reached
0297   aResult.Status   = Status::MaxIterations;
0298   aResult.Solution = aX;
0299   aResult.Value    = aFx;
0300   aResult.Gradient = aGrad;
0301   return aResult;
0302 }
0303 
0304 //! Modified Newton's method with automatic Hessian regularization.
0305 //! Adds diagonal elements to ensure positive definiteness using
0306 //! an adaptive regularization strategy.
0307 //!
0308 //! @tparam Function type with Value, Gradient, and Hessian methods
0309 //! @param theFunc function object
0310 //! @param theStartingPoint initial guess
0311 //! @param theConfig solver configuration
0312 //! @return result containing minimum location and value
0313 template <typename Function>
0314 VectorResult NewtonModified(Function&           theFunc,
0315                             const math_Vector&  theStartingPoint,
0316                             const NewtonConfig& theConfig = NewtonConfig())
0317 {
0318   return Newton(theFunc, theStartingPoint, theConfig);
0319 }
0320 
0321 //! Newton's method with numerical Hessian.
0322 //! Computes Hessian using finite differences when analytical Hessian
0323 //! is not available.
0324 //!
0325 //! @tparam Function type with:
0326 //!   - Value(const math_Vector&, double&) for function value
0327 //!   - Gradient(const math_Vector&, math_Vector&) for gradient
0328 //! @param theFunc function object
0329 //! @param theStartingPoint initial guess
0330 //! @param theHessStep step size for numerical Hessian
0331 //! @param theConfig solver configuration
0332 //! @return result containing minimum location and value
0333 template <typename Function>
0334 VectorResult NewtonNumericalHessian(Function&           theFunc,
0335                                     const math_Vector&  theStartingPoint,
0336                                     double              theHessStep = 1.0e-6,
0337                                     const NewtonConfig& theConfig   = NewtonConfig())
0338 {
0339   // Wrapper that adds numerical Hessian
0340   class FuncWithHessian
0341   {
0342   public:
0343     FuncWithHessian(Function& theF, double theStep)
0344         : myFunc(theF),
0345           myStep(theStep)
0346     {
0347     }
0348 
0349     bool Value(const math_Vector& theX, double& theF) { return myFunc.Value(theX, theF); }
0350 
0351     bool Gradient(const math_Vector& theX, math_Vector& theGrad)
0352     {
0353       return myFunc.Gradient(theX, theGrad);
0354     }
0355 
0356     bool Hessian(const math_Vector& theX, math_Matrix& theHess)
0357     {
0358       math_Vector aXMod = theX;
0359       return MathUtils::NumericalHessian(myFunc, aXMod, theHess, myStep);
0360     }
0361 
0362   private:
0363     Function& myFunc;
0364     double    myStep;
0365   };
0366 
0367   FuncWithHessian aWrapper(theFunc, theHessStep);
0368   return Newton(aWrapper, theStartingPoint, theConfig);
0369 }
0370 
0371 //! Newton's method with fully numerical derivatives.
0372 //! Computes both gradient and Hessian using finite differences.
0373 //!
0374 //! @tparam Function type with Value(const math_Vector&, double&) method only
0375 //! @param theFunc function object
0376 //! @param theStartingPoint initial guess
0377 //! @param theGradStep step size for numerical gradient
0378 //! @param theHessStep step size for numerical Hessian
0379 //! @param theConfig solver configuration
0380 //! @return result containing minimum location and value
0381 template <typename Function>
0382 VectorResult NewtonNumerical(Function&           theFunc,
0383                              const math_Vector&  theStartingPoint,
0384                              double              theGradStep = 1.0e-8,
0385                              double              theHessStep = 1.0e-6,
0386                              const NewtonConfig& theConfig   = NewtonConfig())
0387 {
0388   // Wrapper that adds numerical gradient and Hessian
0389   class FuncWithDerivatives
0390   {
0391   public:
0392     FuncWithDerivatives(Function& theF, double theGStep, double theHStep)
0393         : myFunc(theF),
0394           myGradStep(theGStep),
0395           myHessStep(theHStep)
0396     {
0397     }
0398 
0399     bool Value(const math_Vector& theX, double& theF) { return myFunc.Value(theX, theF); }
0400 
0401     bool Gradient(const math_Vector& theX, math_Vector& theGrad)
0402     {
0403       math_Vector aXMod = theX;
0404       return MathUtils::NumericalGradientAdaptive(myFunc, aXMod, theGrad, myGradStep);
0405     }
0406 
0407     bool Hessian(const math_Vector& theX, math_Matrix& theHess)
0408     {
0409       // Compute Hessian from finite differences of gradient
0410       const int aLower = theX.Lower();
0411       const int aUpper = theX.Upper();
0412 
0413       math_Vector aXMod = theX;
0414       math_Vector aGradPlus(aLower, aUpper);
0415       math_Vector aGradMinus(aLower, aUpper);
0416 
0417       for (int j = aLower; j <= aUpper; ++j)
0418       {
0419         const double aXj = aXMod(j);
0420 
0421         aXMod(j) = aXj + myHessStep;
0422         if (!MathUtils::NumericalGradientAdaptive(myFunc, aXMod, aGradPlus, myGradStep))
0423         {
0424           aXMod(j) = aXj;
0425           return false;
0426         }
0427 
0428         aXMod(j) = aXj - myHessStep;
0429         if (!MathUtils::NumericalGradientAdaptive(myFunc, aXMod, aGradMinus, myGradStep))
0430         {
0431           aXMod(j) = aXj;
0432           return false;
0433         }
0434 
0435         aXMod(j) = aXj;
0436 
0437         for (int i = aLower; i <= aUpper; ++i)
0438         {
0439           theHess(i, j) = (aGradPlus(i) - aGradMinus(i)) / (2.0 * myHessStep);
0440         }
0441       }
0442 
0443       // Symmetrize
0444       for (int i = aLower; i <= aUpper; ++i)
0445       {
0446         for (int j = i + 1; j <= aUpper; ++j)
0447         {
0448           double aAvg   = 0.5 * (theHess(i, j) + theHess(j, i));
0449           theHess(i, j) = aAvg;
0450           theHess(j, i) = aAvg;
0451         }
0452       }
0453 
0454       return true;
0455     }
0456 
0457   private:
0458     Function& myFunc;
0459     double    myGradStep;
0460     double    myHessStep;
0461   };
0462 
0463   FuncWithDerivatives aWrapper(theFunc, theGradStep, theHessStep);
0464   return Newton(aWrapper, theStartingPoint, theConfig);
0465 }
0466 
0467 //! Newton's method with bound constraints.
0468 //!
0469 //! Minimizes f(x) subject to theLowerBounds <= x <= theUpperBounds.
0470 //! Uses projected gradient approach similar to BFGSBounded.
0471 //!
0472 //! @tparam Function type with Value, Gradient, and Hessian methods
0473 //! @param theFunc function object
0474 //! @param theStartingPoint initial guess
0475 //! @param theLowerBounds lower bounds for each variable
0476 //! @param theUpperBounds upper bounds for each variable
0477 //! @param theConfig solver configuration
0478 //! @return result containing minimum location and value
0479 template <typename Function>
0480 VectorResult NewtonBounded(Function&           theFunc,
0481                            const math_Vector&  theStartingPoint,
0482                            const math_Vector&  theLowerBounds,
0483                            const math_Vector&  theUpperBounds,
0484                            const NewtonConfig& theConfig = NewtonConfig())
0485 {
0486   VectorResult aResult;
0487 
0488   const int aLower = theStartingPoint.Lower();
0489   const int aUpper = theStartingPoint.Upper();
0490   const int aN     = aUpper - aLower + 1;
0491 
0492   // Check dimensions
0493   if (theLowerBounds.Length() != aN || theUpperBounds.Length() != aN)
0494   {
0495     aResult.Status = Status::InvalidInput;
0496     return aResult;
0497   }
0498 
0499   // Lambda to clamp a point to bounds
0500   auto ClampToBounds = [&](math_Vector& theX) {
0501     for (int i = aLower; i <= aUpper; ++i)
0502     {
0503       const int aBndIdx = theLowerBounds.Lower() + (i - aLower);
0504       if (theX(i) < theLowerBounds(aBndIdx))
0505       {
0506         theX(i) = theLowerBounds(aBndIdx);
0507       }
0508       if (theX(i) > theUpperBounds(aBndIdx))
0509       {
0510         theX(i) = theUpperBounds(aBndIdx);
0511       }
0512     }
0513   };
0514 
0515   // Lambda to project gradient (zero components at active bounds)
0516   auto ProjectGradient = [&](const math_Vector& theX, math_Vector& theGrad) {
0517     for (int i = aLower; i <= aUpper; ++i)
0518     {
0519       const int    aBndIdx = theLowerBounds.Lower() + (i - aLower);
0520       const double aTol    = MathUtils::THE_EPSILON * std::max(1.0, std::abs(theX(i)));
0521 
0522       if (theX(i) - theLowerBounds(aBndIdx) < aTol && theGrad(i) > 0.0)
0523       {
0524         theGrad(i) = 0.0;
0525       }
0526       if (theUpperBounds(aBndIdx) - theX(i) < aTol && theGrad(i) < 0.0)
0527       {
0528         theGrad(i) = 0.0;
0529       }
0530     }
0531   };
0532 
0533   // Lambda to compute max step to boundary
0534   auto ComputeAlphaMax = [&](const math_Vector& theX, const math_Vector& theDir) -> double {
0535     double aAlphaMax = 1.0;
0536     for (int i = aLower; i <= aUpper; ++i)
0537     {
0538       const int aBndIdx = theLowerBounds.Lower() + (i - aLower);
0539       if (theDir(i) < -MathUtils::THE_EPSILON)
0540       {
0541         double aMaxStep = (theLowerBounds(aBndIdx) - theX(i)) / theDir(i);
0542         aAlphaMax       = std::min(aAlphaMax, aMaxStep);
0543       }
0544       else if (theDir(i) > MathUtils::THE_EPSILON)
0545       {
0546         double aMaxStep = (theUpperBounds(aBndIdx) - theX(i)) / theDir(i);
0547         aAlphaMax       = std::min(aAlphaMax, aMaxStep);
0548       }
0549     }
0550     return std::max(aAlphaMax, MathUtils::THE_EPSILON);
0551   };
0552 
0553   // Current point
0554   math_Vector aX(aLower, aUpper);
0555   aX = theStartingPoint;
0556   ClampToBounds(aX);
0557 
0558   double aFx = 0.0;
0559   if (!theFunc.Value(aX, aFx))
0560   {
0561     aResult.Status = Status::NumericalError;
0562     return aResult;
0563   }
0564 
0565   // Gradient at current point
0566   math_Vector aGrad(aLower, aUpper);
0567   if (!theFunc.Gradient(aX, aGrad))
0568   {
0569     aResult.Status = Status::NumericalError;
0570     return aResult;
0571   }
0572   ProjectGradient(aX, aGrad);
0573 
0574   // Check if already at minimum
0575   double aGradNorm = 0.0;
0576   for (int i = aLower; i <= aUpper; ++i)
0577   {
0578     aGradNorm += MathUtils::Sqr(aGrad(i));
0579   }
0580   aGradNorm = std::sqrt(aGradNorm);
0581 
0582   if (aGradNorm < theConfig.FTolerance)
0583   {
0584     aResult.Status   = Status::OK;
0585     aResult.Solution = aX;
0586     aResult.Value    = aFx;
0587     aResult.Gradient = aGrad;
0588     return aResult;
0589   }
0590 
0591   // Working vectors and matrices
0592   math_Vector aDir(aLower, aUpper);
0593   math_Vector aXNew(aLower, aUpper);
0594   math_Vector aGradNew(aLower, aUpper);
0595   math_Matrix aHessian(aLower, aUpper, aLower, aUpper);
0596   math_Vector aNegGrad(aLower, aUpper);
0597 
0598   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0599   {
0600     aResult.NbIterations = anIter + 1;
0601 
0602     // Compute Hessian
0603     if (!theFunc.Hessian(aX, aHessian))
0604     {
0605       aResult.Status   = Status::NumericalError;
0606       aResult.Solution = aX;
0607       aResult.Value    = aFx;
0608       return aResult;
0609     }
0610 
0611     // Prepare negative gradient
0612     for (int i = aLower; i <= aUpper; ++i)
0613     {
0614       aNegGrad(i) = -aGrad(i);
0615     }
0616 
0617     // Try to solve H * p = -g
0618     auto aLinResult = MathLin::Solve(aHessian, aNegGrad);
0619 
0620     if (!aLinResult.IsDone())
0621     {
0622       // Add regularization
0623       double aLambda = theConfig.Regularization;
0624       bool   aSolved = false;
0625 
0626       for (int k = 0; k < 10 && !aSolved; ++k)
0627       {
0628         math_Matrix aRegHessian = aHessian;
0629         for (int i = aLower; i <= aUpper; ++i)
0630         {
0631           aRegHessian(i, i) += aLambda;
0632         }
0633 
0634         aLinResult = MathLin::Solve(aRegHessian, aNegGrad);
0635         if (aLinResult.IsDone())
0636         {
0637           aSolved = true;
0638         }
0639         else
0640         {
0641           aLambda *= 10.0;
0642         }
0643       }
0644 
0645       if (!aSolved)
0646       {
0647         // Fall back to steepest descent
0648         for (int i = aLower; i <= aUpper; ++i)
0649         {
0650           aDir(i) = -aGrad(i);
0651         }
0652         goto perform_bounded_line_search;
0653       }
0654     }
0655 
0656     aDir = *aLinResult.Solution;
0657 
0658     // Check if direction is descent
0659     {
0660       double aDirDeriv = 0.0;
0661       for (int i = aLower; i <= aUpper; ++i)
0662       {
0663         aDirDeriv += aGrad(i) * aDir(i);
0664       }
0665 
0666       if (aDirDeriv >= 0.0)
0667       {
0668         for (int i = aLower; i <= aUpper; ++i)
0669         {
0670           aDir(i) = -aGrad(i);
0671         }
0672       }
0673     }
0674 
0675   perform_bounded_line_search:
0676     if (theConfig.UseLineSearch)
0677     {
0678       double aAlphaMax = ComputeAlphaMax(aX, aDir);
0679 
0680       MathUtils::LineSearchResult aLineResult =
0681         MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, aAlphaMax, 1.0e-4, 0.5, 50);
0682 
0683       if (!aLineResult.IsValid)
0684       {
0685         // Try steepest descent
0686         for (int i = aLower; i <= aUpper; ++i)
0687         {
0688           aDir(i) = -aGrad(i);
0689         }
0690         aAlphaMax = ComputeAlphaMax(aX, aDir);
0691         aLineResult =
0692           MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, aAlphaMax, 1.0e-4, 0.5, 50);
0693 
0694         if (!aLineResult.IsValid)
0695         {
0696           aResult.Status   = Status::NotConverged;
0697           aResult.Solution = aX;
0698           aResult.Value    = aFx;
0699           aResult.Gradient = aGrad;
0700           return aResult;
0701         }
0702       }
0703 
0704       for (int i = aLower; i <= aUpper; ++i)
0705       {
0706         aXNew(i) = aX(i) + aLineResult.Alpha * aDir(i);
0707       }
0708       ClampToBounds(aXNew);
0709 
0710       if (!theFunc.Value(aXNew, aFx))
0711       {
0712         aResult.Status   = Status::NumericalError;
0713         aResult.Solution = aX;
0714         return aResult;
0715       }
0716     }
0717     else
0718     {
0719       for (int i = aLower; i <= aUpper; ++i)
0720       {
0721         aXNew(i) = aX(i) + aDir(i);
0722       }
0723       ClampToBounds(aXNew);
0724 
0725       if (!theFunc.Value(aXNew, aFx))
0726       {
0727         aResult.Status   = Status::NumericalError;
0728         aResult.Solution = aX;
0729         return aResult;
0730       }
0731     }
0732 
0733     // Check X convergence
0734     double aMaxDiff = 0.0;
0735     for (int i = aLower; i <= aUpper; ++i)
0736     {
0737       aMaxDiff = std::max(aMaxDiff, std::abs(aXNew(i) - aX(i)));
0738     }
0739 
0740     // Evaluate gradient at new point
0741     if (!theFunc.Gradient(aXNew, aGradNew))
0742     {
0743       aResult.Status   = Status::NumericalError;
0744       aResult.Solution = aXNew;
0745       aResult.Value    = aFx;
0746       return aResult;
0747     }
0748     ProjectGradient(aXNew, aGradNew);
0749 
0750     // Check gradient convergence
0751     aGradNorm = 0.0;
0752     for (int i = aLower; i <= aUpper; ++i)
0753     {
0754       aGradNorm += MathUtils::Sqr(aGradNew(i));
0755     }
0756     aGradNorm = std::sqrt(aGradNorm);
0757 
0758     if (aGradNorm < theConfig.FTolerance)
0759     {
0760       aResult.Status   = Status::OK;
0761       aResult.Solution = aXNew;
0762       aResult.Value    = aFx;
0763       aResult.Gradient = aGradNew;
0764       return aResult;
0765     }
0766 
0767     if (aMaxDiff < theConfig.XTolerance)
0768     {
0769       aResult.Status   = Status::OK;
0770       aResult.Solution = aXNew;
0771       aResult.Value    = aFx;
0772       aResult.Gradient = aGradNew;
0773       return aResult;
0774     }
0775 
0776     aX    = aXNew;
0777     aGrad = aGradNew;
0778   }
0779 
0780   aResult.Status   = Status::MaxIterations;
0781   aResult.Solution = aX;
0782   aResult.Value    = aFx;
0783   aResult.Gradient = aGrad;
0784   return aResult;
0785 }
0786 
0787 } // namespace MathOpt
0788 
0789 #endif // _MathOpt_Newton_HeaderFile