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 _MathOpt_BFGS_HeaderFile
0015 #define _MathOpt_BFGS_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathUtils_Core.hxx>
0020 #include <MathUtils_LineSearch.hxx>
0021 #include <MathUtils_Deriv.hxx>
0022 
0023 #include <NCollection_DynamicArray.hxx>
0024 
0025 #include <cmath>
0026 
0027 namespace MathOpt
0028 {
0029 using namespace MathUtils;
0030 
0031 //! BFGS (Broyden-Fletcher-Goldfarb-Shanno) quasi-Newton method.
0032 //! One of the most effective algorithms for smooth unconstrained optimization.
0033 //!
0034 //! Algorithm:
0035 //! 1. Start with initial Hessian approximation H (usually identity)
0036 //! 2. Compute search direction p = -H * gradient
0037 //! 3. Perform line search to find step size alpha satisfying Wolfe conditions
0038 //! 4. Update x = x + alpha * p
0039 //! 5. Update Hessian approximation using BFGS formula
0040 //! 6. Repeat until convergence
0041 //!
0042 //! The BFGS update maintains positive definiteness of H if started with
0043 //! a positive definite matrix and using proper line search.
0044 //!
0045 //! Advantages:
0046 //! - Superlinear convergence near minimum
0047 //! - Self-correcting Hessian approximation
0048 //! - No need to compute actual Hessian
0049 //!
0050 //! @tparam Function type with:
0051 //!   - Value(const math_Vector&, double&) for function value
0052 //!   - Gradient(const math_Vector&, math_Vector&) for gradient
0053 //! @param theFunc function object with value and gradient
0054 //! @param theStartingPoint initial guess
0055 //! @param theConfig solver configuration
0056 //! @return result containing minimum location and value
0057 template <typename Function>
0058 VectorResult BFGS(Function&          theFunc,
0059                   const math_Vector& theStartingPoint,
0060                   const Config&      theConfig = Config())
0061 {
0062   VectorResult aResult;
0063 
0064   const int aLower = theStartingPoint.Lower();
0065   const int aUpper = theStartingPoint.Upper();
0066   const int aN     = aUpper - aLower + 1;
0067 
0068   // Current point and function value
0069   math_Vector aX(aLower, aUpper);
0070   aX = theStartingPoint;
0071 
0072   double aFx = 0.0;
0073   if (!theFunc.Value(aX, aFx))
0074   {
0075     aResult.Status = Status::NumericalError;
0076     return aResult;
0077   }
0078 
0079   // Gradient at current point
0080   math_Vector aGrad(aLower, aUpper);
0081   if (!theFunc.Gradient(aX, aGrad))
0082   {
0083     aResult.Status = Status::NumericalError;
0084     return aResult;
0085   }
0086 
0087   // Check if already at minimum (gradient near zero)
0088   double aGradNorm = 0.0;
0089   for (int i = aLower; i <= aUpper; ++i)
0090   {
0091     aGradNorm += MathUtils::Sqr(aGrad(i));
0092   }
0093   aGradNorm = std::sqrt(aGradNorm);
0094 
0095   if (aGradNorm < theConfig.FTolerance)
0096   {
0097     aResult.Status   = Status::OK;
0098     aResult.Solution = aX;
0099     aResult.Value    = aFx;
0100     aResult.Gradient = aGrad;
0101     return aResult;
0102   }
0103 
0104   // Initialize inverse Hessian approximation to identity
0105   math_Matrix aH(1, aN, 1, aN, 0.0);
0106   for (int i = 1; i <= aN; ++i)
0107   {
0108     aH(i, i) = 1.0;
0109   }
0110 
0111   // Working vectors
0112   math_Vector aDir(aLower, aUpper);     // Search direction
0113   math_Vector aXNew(aLower, aUpper);    // New point
0114   math_Vector aGradNew(aLower, aUpper); // New gradient
0115   math_Vector aS(1, aN);                // Step: x_new - x
0116   math_Vector aY(1, aN);                // Gradient difference: grad_new - grad
0117 
0118   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0119   {
0120     aResult.NbIterations = anIter + 1;
0121 
0122     // Compute search direction: p = -H * grad
0123     for (int i = 1; i <= aN; ++i)
0124     {
0125       double aSum = 0.0;
0126       for (int j = 1; j <= aN; ++j)
0127       {
0128         aSum += aH(i, j) * aGrad(aLower + j - 1);
0129       }
0130       aDir(aLower + i - 1) = -aSum;
0131     }
0132 
0133     // Line search with Wolfe conditions
0134     MathUtils::LineSearchResult aLineResult =
0135       MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0136 
0137     if (!aLineResult.IsValid || aLineResult.Alpha < MathUtils::THE_EPSILON)
0138     {
0139       // Line search failed, try steepest descent direction
0140       for (int i = aLower; i <= aUpper; ++i)
0141       {
0142         aDir(i) = -aGrad(i);
0143       }
0144       aLineResult = MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0145 
0146       if (!aLineResult.IsValid)
0147       {
0148         // Both BFGS and steepest descent failed
0149         aResult.Status   = Status::NotConverged;
0150         aResult.Solution = aX;
0151         aResult.Value    = aFx;
0152         aResult.Gradient = aGrad;
0153         return aResult;
0154       }
0155 
0156       // Reset Hessian to identity after steepest descent step
0157       for (int i = 1; i <= aN; ++i)
0158       {
0159         for (int j = 1; j <= aN; ++j)
0160         {
0161           aH(i, j) = (i == j) ? 1.0 : 0.0;
0162         }
0163       }
0164     }
0165 
0166     // Compute new point
0167     for (int i = aLower; i <= aUpper; ++i)
0168     {
0169       aXNew(i) = aX(i) + aLineResult.Alpha * aDir(i);
0170     }
0171 
0172     // Compute s = x_new - x
0173     for (int i = 1; i <= aN; ++i)
0174     {
0175       aS(i) = aXNew(aLower + i - 1) - aX(aLower + i - 1);
0176     }
0177 
0178     // Evaluate gradient at new point
0179     if (!theFunc.Gradient(aXNew, aGradNew))
0180     {
0181       aResult.Status   = Status::NumericalError;
0182       aResult.Solution = aX;
0183       aResult.Value    = aFx;
0184       return aResult;
0185     }
0186 
0187     // Check gradient convergence
0188     aGradNorm = 0.0;
0189     for (int i = aLower; i <= aUpper; ++i)
0190     {
0191       aGradNorm += MathUtils::Sqr(aGradNew(i));
0192     }
0193     aGradNorm = std::sqrt(aGradNorm);
0194 
0195     if (aGradNorm < theConfig.FTolerance)
0196     {
0197       aResult.Status   = Status::OK;
0198       aResult.Solution = aXNew;
0199       aResult.Value    = aLineResult.FNew;
0200       aResult.Gradient = aGradNew;
0201       return aResult;
0202     }
0203 
0204     // Check X convergence
0205     double aMaxDiff = 0.0;
0206     for (int i = aLower; i <= aUpper; ++i)
0207     {
0208       aMaxDiff = std::max(aMaxDiff, std::abs(aXNew(i) - aX(i)));
0209     }
0210     if (aMaxDiff < theConfig.XTolerance)
0211     {
0212       aResult.Status   = Status::OK;
0213       aResult.Solution = aXNew;
0214       aResult.Value    = aLineResult.FNew;
0215       aResult.Gradient = aGradNew;
0216       return aResult;
0217     }
0218 
0219     // Compute y = grad_new - grad
0220     for (int i = 1; i <= aN; ++i)
0221     {
0222       aY(i) = aGradNew(aLower + i - 1) - aGrad(aLower + i - 1);
0223     }
0224 
0225     // Compute s^T * y (curvature condition)
0226     double aSY = 0.0;
0227     for (int i = 1; i <= aN; ++i)
0228     {
0229       aSY += aS(i) * aY(i);
0230     }
0231 
0232     // Skip update if curvature condition is not satisfied
0233     if (aSY > MathUtils::THE_ZERO_TOL)
0234     {
0235       // BFGS update: H_new = (I - rho*sy^T) H (I - rho*ys^T) + rho*ss^T
0236       // where rho = 1 / (s^T y)
0237       const double aRho = 1.0 / aSY;
0238 
0239       // Compute H * y
0240       math_Vector aHy(1, aN, 0.0);
0241       for (int i = 1; i <= aN; ++i)
0242       {
0243         for (int j = 1; j <= aN; ++j)
0244         {
0245           aHy(i) += aH(i, j) * aY(j);
0246         }
0247       }
0248 
0249       // Compute y^T * H * y
0250       double aYHy = 0.0;
0251       for (int i = 1; i <= aN; ++i)
0252       {
0253         aYHy += aY(i) * aHy(i);
0254       }
0255 
0256       // Update H using the formula:
0257       // H_new = H - (Hy*s^T + s*y^T*H)/(s^T*y) + (1 + y^T*H*y/(s^T*y)) * s*s^T/(s^T*y)
0258       const double aFactor = 1.0 + aRho * aYHy;
0259 
0260       for (int i = 1; i <= aN; ++i)
0261       {
0262         for (int j = 1; j <= aN; ++j)
0263         {
0264           aH(i, j) =
0265             aH(i, j) - aRho * (aHy(i) * aS(j) + aS(i) * aHy(j)) + aFactor * aRho * aS(i) * aS(j);
0266         }
0267       }
0268     }
0269 
0270     // Update for next iteration
0271     aX    = aXNew;
0272     aGrad = aGradNew;
0273     aFx   = aLineResult.FNew;
0274   }
0275 
0276   // Maximum iterations reached
0277   aResult.Status   = Status::MaxIterations;
0278   aResult.Solution = aX;
0279   aResult.Value    = aFx;
0280   aResult.Gradient = aGrad;
0281   return aResult;
0282 }
0283 
0284 //! BFGS with numerical gradient.
0285 //! Uses central differences to approximate gradient when analytical
0286 //! gradient is not available.
0287 //!
0288 //! @tparam Function type with Value(const math_Vector&, double&) method only
0289 //! @param theFunc function object
0290 //! @param theStartingPoint initial guess
0291 //! @param theGradStep step size for numerical gradient (default 1e-8)
0292 //! @param theConfig solver configuration
0293 //! @return result containing minimum location and value
0294 template <typename Function>
0295 VectorResult BFGSNumerical(Function&          theFunc,
0296                            const math_Vector& theStartingPoint,
0297                            double             theGradStep = 1.0e-8,
0298                            const Config&      theConfig   = Config())
0299 {
0300   // Wrapper that adds numerical gradient
0301   class FuncWithGradient
0302   {
0303   public:
0304     FuncWithGradient(Function& theF, double theStep)
0305         : myFunc(theF),
0306           myStep(theStep)
0307     {
0308     }
0309 
0310     bool Value(const math_Vector& theX, double& theF) { return myFunc.Value(theX, theF); }
0311 
0312     bool Gradient(const math_Vector& theX, math_Vector& theGrad)
0313     {
0314       math_Vector aXMod = theX; // Make mutable copy
0315       return MathUtils::NumericalGradientAdaptive(myFunc, aXMod, theGrad, myStep);
0316     }
0317 
0318   private:
0319     Function& myFunc;
0320     double    myStep;
0321   };
0322 
0323   FuncWithGradient aWrapper(theFunc, theGradStep);
0324   return BFGS(aWrapper, theStartingPoint, theConfig);
0325 }
0326 
0327 //! L-BFGS (Limited-memory BFGS) for large-scale optimization.
0328 //! Uses only the m most recent {s, y} pairs instead of full Hessian.
0329 //! Memory: O(m*n) instead of O(n^2).
0330 //!
0331 //! @tparam Function type with Value and Gradient methods
0332 //! @param theFunc function object
0333 //! @param theStartingPoint initial guess
0334 //! @param theMemorySize number of gradient pairs to store (default 10)
0335 //! @param theConfig solver configuration
0336 //! @return result containing minimum location and value
0337 template <typename Function>
0338 VectorResult LBFGS(Function&          theFunc,
0339                    const math_Vector& theStartingPoint,
0340                    int                theMemorySize = 10,
0341                    const Config&      theConfig     = Config())
0342 {
0343   VectorResult aResult;
0344 
0345   const int aLower = theStartingPoint.Lower();
0346   const int aUpper = theStartingPoint.Upper();
0347   const int aN     = aUpper - aLower + 1;
0348   const int aM     = theMemorySize;
0349 
0350   math_Vector aX(aLower, aUpper);
0351   aX = theStartingPoint;
0352 
0353   double aFx = 0.0;
0354   if (!theFunc.Value(aX, aFx))
0355   {
0356     aResult.Status = Status::NumericalError;
0357     return aResult;
0358   }
0359 
0360   math_Vector aGrad(aLower, aUpper);
0361   if (!theFunc.Gradient(aX, aGrad))
0362   {
0363     aResult.Status = Status::NumericalError;
0364     return aResult;
0365   }
0366 
0367   // Storage for {s, y} pairs (circular buffer)
0368   NCollection_DynamicArray<math_Vector> aSVec;
0369   NCollection_DynamicArray<math_Vector> aYVec;
0370   math_Vector                           aRhoVec(0, aM - 1, 0.0);
0371   for (int i = 0; i < aM; ++i)
0372   {
0373     aSVec.Append(math_Vector(1, aN));
0374     aYVec.Append(math_Vector(1, aN));
0375   }
0376   int aHead  = 0; // Index of oldest entry
0377   int aCount = 0; // Number of stored pairs
0378 
0379   math_Vector aDir(aLower, aUpper);
0380   math_Vector aXNew(aLower, aUpper);
0381   math_Vector aGradNew(aLower, aUpper);
0382   math_Vector aQ(1, aN);
0383   math_Vector aAlphaVec(0, aM - 1);
0384 
0385   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0386   {
0387     aResult.NbIterations = anIter + 1;
0388 
0389     // Check gradient convergence
0390     double aGradNorm = 0.0;
0391     for (int i = aLower; i <= aUpper; ++i)
0392     {
0393       aGradNorm += MathUtils::Sqr(aGrad(i));
0394     }
0395     if (std::sqrt(aGradNorm) < theConfig.FTolerance)
0396     {
0397       aResult.Status   = Status::OK;
0398       aResult.Solution = aX;
0399       aResult.Value    = aFx;
0400       aResult.Gradient = aGrad;
0401       return aResult;
0402     }
0403 
0404     // L-BFGS two-loop recursion to compute search direction
0405     // q = gradient
0406     for (int i = 1; i <= aN; ++i)
0407     {
0408       aQ(i) = aGrad(aLower + i - 1);
0409     }
0410 
0411     // First loop (backward)
0412     for (int k = aCount - 1; k >= 0; --k)
0413     {
0414       const int aIdx  = (aHead + k) % aM;
0415       aAlphaVec(aIdx) = aRhoVec(aIdx) * MathUtils::DotProduct(aSVec.Value(aIdx), aQ);
0416       for (int i = 1; i <= aN; ++i)
0417       {
0418         aQ(i) -= aAlphaVec(aIdx) * aYVec.Value(aIdx)(i);
0419       }
0420     }
0421 
0422     // Initial Hessian: H0 = gamma*I where gamma = s^T y / y^T y
0423     double aGamma = 1.0;
0424     if (aCount > 0)
0425     {
0426       const int aLastIdx = (aHead + aCount - 1) % aM;
0427       double    aYY      = MathUtils::DotProduct(aYVec.Value(aLastIdx), aYVec.Value(aLastIdx));
0428       if (aYY > MathUtils::THE_ZERO_TOL)
0429       {
0430         aGamma = 1.0 / (aRhoVec(aLastIdx) * aYY);
0431       }
0432     }
0433 
0434     // r = H0 * q = gamma * q
0435     math_Vector aR(1, aN);
0436     for (int i = 1; i <= aN; ++i)
0437     {
0438       aR(i) = aGamma * aQ(i);
0439     }
0440 
0441     // Second loop (forward)
0442     for (int k = 0; k < aCount; ++k)
0443     {
0444       const int    aIdx  = (aHead + k) % aM;
0445       const double aBeta = aRhoVec(aIdx) * MathUtils::DotProduct(aYVec.Value(aIdx), aR);
0446       for (int i = 1; i <= aN; ++i)
0447       {
0448         aR(i) += (aAlphaVec(aIdx) - aBeta) * aSVec.Value(aIdx)(i);
0449       }
0450     }
0451 
0452     // Search direction: p = -r
0453     for (int i = aLower; i <= aUpper; ++i)
0454     {
0455       aDir(i) = -aR(i - aLower + 1);
0456     }
0457 
0458     // Line search
0459     MathUtils::LineSearchResult aLineResult =
0460       MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0461 
0462     if (!aLineResult.IsValid)
0463     {
0464       // Fall back to steepest descent
0465       for (int i = aLower; i <= aUpper; ++i)
0466       {
0467         aDir(i) = -aGrad(i);
0468       }
0469       aLineResult = MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0470 
0471       if (!aLineResult.IsValid)
0472       {
0473         aResult.Status   = Status::NotConverged;
0474         aResult.Solution = aX;
0475         aResult.Value    = aFx;
0476         return aResult;
0477       }
0478       // Reset history after steepest descent
0479       aCount = 0;
0480     }
0481 
0482     // Compute new point
0483     for (int i = aLower; i <= aUpper; ++i)
0484     {
0485       aXNew(i) = aX(i) + aLineResult.Alpha * aDir(i);
0486     }
0487 
0488     // Check X convergence
0489     double aMaxDiff = 0.0;
0490     for (int i = aLower; i <= aUpper; ++i)
0491     {
0492       aMaxDiff = std::max(aMaxDiff, std::abs(aXNew(i) - aX(i)));
0493     }
0494     if (aMaxDiff < theConfig.XTolerance)
0495     {
0496       if (!theFunc.Gradient(aXNew, aGradNew))
0497       {
0498         aGradNew = aGrad;
0499       }
0500       aResult.Status   = Status::OK;
0501       aResult.Solution = aXNew;
0502       aResult.Value    = aLineResult.FNew;
0503       aResult.Gradient = aGradNew;
0504       return aResult;
0505     }
0506 
0507     // Evaluate gradient at new point
0508     if (!theFunc.Gradient(aXNew, aGradNew))
0509     {
0510       aResult.Status   = Status::NumericalError;
0511       aResult.Solution = aX;
0512       aResult.Value    = aFx;
0513       return aResult;
0514     }
0515 
0516     // Store new {s, y} pair
0517     const int aNewIdx = (aHead + aCount) % aM;
0518     for (int i = 1; i <= aN; ++i)
0519     {
0520       aSVec.ChangeValue(aNewIdx)(i) = aXNew(aLower + i - 1) - aX(aLower + i - 1);
0521       aYVec.ChangeValue(aNewIdx)(i) = aGradNew(aLower + i - 1) - aGrad(aLower + i - 1);
0522     }
0523 
0524     double aSY = MathUtils::DotProduct(aSVec.Value(aNewIdx), aYVec.Value(aNewIdx));
0525     if (aSY > MathUtils::THE_ZERO_TOL)
0526     {
0527       aRhoVec(aNewIdx) = 1.0 / aSY;
0528       if (aCount < aM)
0529       {
0530         ++aCount;
0531       }
0532       else
0533       {
0534         aHead = (aHead + 1) % aM;
0535       }
0536     }
0537 
0538     // Update for next iteration
0539     aX    = aXNew;
0540     aGrad = aGradNew;
0541     aFx   = aLineResult.FNew;
0542   }
0543 
0544   aResult.Status   = Status::MaxIterations;
0545   aResult.Solution = aX;
0546   aResult.Value    = aFx;
0547   aResult.Gradient = aGrad;
0548   return aResult;
0549 }
0550 
0551 //! BFGS with bound constraints (box constraints).
0552 //!
0553 //! Minimizes f(x) subject to theLowerBounds <= x <= theUpperBounds.
0554 //! Uses projected gradient approach:
0555 //! - After each step, clamp x to bounds
0556 //! - Zero gradient components at bounds where they point outward
0557 //!
0558 //! @tparam Function type with Value and Gradient methods
0559 //! @param theFunc function object
0560 //! @param theStartingPoint initial guess
0561 //! @param theLowerBounds lower bounds for each variable
0562 //! @param theUpperBounds upper bounds for each variable
0563 //! @param theConfig solver configuration
0564 //! @return result containing minimum location and value
0565 template <typename Function>
0566 VectorResult BFGSBounded(Function&          theFunc,
0567                          const math_Vector& theStartingPoint,
0568                          const math_Vector& theLowerBounds,
0569                          const math_Vector& theUpperBounds,
0570                          const Config&      theConfig = Config())
0571 {
0572   VectorResult aResult;
0573 
0574   const int aLower = theStartingPoint.Lower();
0575   const int aUpper = theStartingPoint.Upper();
0576   const int aN     = aUpper - aLower + 1;
0577 
0578   // Check dimensions
0579   if (theLowerBounds.Length() != aN || theUpperBounds.Length() != aN)
0580   {
0581     aResult.Status = Status::InvalidInput;
0582     return aResult;
0583   }
0584 
0585   // Lambda to clamp a point to bounds
0586   auto ClampToBounds = [&](math_Vector& theX) {
0587     for (int i = aLower; i <= aUpper; ++i)
0588     {
0589       const int aBndIdx = theLowerBounds.Lower() + (i - aLower);
0590       if (theX(i) < theLowerBounds(aBndIdx))
0591       {
0592         theX(i) = theLowerBounds(aBndIdx);
0593       }
0594       if (theX(i) > theUpperBounds(aBndIdx))
0595       {
0596         theX(i) = theUpperBounds(aBndIdx);
0597       }
0598     }
0599   };
0600 
0601   // Lambda to project gradient (zero components at active bounds)
0602   auto ProjectGradient = [&](const math_Vector& theX, math_Vector& theGrad) {
0603     for (int i = aLower; i <= aUpper; ++i)
0604     {
0605       const int    aBndIdx = theLowerBounds.Lower() + (i - aLower);
0606       const double aTol    = MathUtils::THE_EPSILON * std::max(1.0, std::abs(theX(i)));
0607 
0608       // At lower bound and gradient points downward -> zero gradient
0609       if (theX(i) - theLowerBounds(aBndIdx) < aTol && theGrad(i) > 0.0)
0610       {
0611         theGrad(i) = 0.0;
0612       }
0613       // At upper bound and gradient points upward -> zero gradient
0614       if (theUpperBounds(aBndIdx) - theX(i) < aTol && theGrad(i) < 0.0)
0615       {
0616         theGrad(i) = 0.0;
0617       }
0618     }
0619   };
0620 
0621   // Current point and function value
0622   math_Vector aX(aLower, aUpper);
0623   aX = theStartingPoint;
0624   ClampToBounds(aX);
0625 
0626   double aFx = 0.0;
0627   if (!theFunc.Value(aX, aFx))
0628   {
0629     aResult.Status = Status::NumericalError;
0630     return aResult;
0631   }
0632 
0633   // Gradient at current point
0634   math_Vector aGrad(aLower, aUpper);
0635   if (!theFunc.Gradient(aX, aGrad))
0636   {
0637     aResult.Status = Status::NumericalError;
0638     return aResult;
0639   }
0640   ProjectGradient(aX, aGrad);
0641 
0642   // Check if already at minimum
0643   double aGradNorm = 0.0;
0644   for (int i = aLower; i <= aUpper; ++i)
0645   {
0646     aGradNorm += MathUtils::Sqr(aGrad(i));
0647   }
0648   aGradNorm = std::sqrt(aGradNorm);
0649 
0650   if (aGradNorm < theConfig.FTolerance)
0651   {
0652     aResult.Status   = Status::OK;
0653     aResult.Solution = aX;
0654     aResult.Value    = aFx;
0655     aResult.Gradient = aGrad;
0656     return aResult;
0657   }
0658 
0659   // Initialize inverse Hessian approximation to identity
0660   math_Matrix aH(1, aN, 1, aN, 0.0);
0661   for (int i = 1; i <= aN; ++i)
0662   {
0663     aH(i, i) = 1.0;
0664   }
0665 
0666   // Working vectors
0667   math_Vector aDir(aLower, aUpper);
0668   math_Vector aXNew(aLower, aUpper);
0669   math_Vector aGradNew(aLower, aUpper);
0670   math_Vector aS(1, aN);
0671   math_Vector aY(1, aN);
0672 
0673   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0674   {
0675     aResult.NbIterations = anIter + 1;
0676 
0677     // Compute search direction: p = -H * grad
0678     for (int i = 1; i <= aN; ++i)
0679     {
0680       double aSum = 0.0;
0681       for (int j = 1; j <= aN; ++j)
0682       {
0683         aSum += aH(i, j) * aGrad(aLower + j - 1);
0684       }
0685       aDir(aLower + i - 1) = -aSum;
0686     }
0687 
0688     // Line search with bounds-aware step
0689     double aAlphaMax = 1.0;
0690     for (int i = aLower; i <= aUpper; ++i)
0691     {
0692       const int aBndIdx = theLowerBounds.Lower() + (i - aLower);
0693       if (aDir(i) < -MathUtils::THE_EPSILON)
0694       {
0695         // Moving toward lower bound
0696         double aMaxStep = (theLowerBounds(aBndIdx) - aX(i)) / aDir(i);
0697         aAlphaMax       = std::min(aAlphaMax, aMaxStep);
0698       }
0699       else if (aDir(i) > MathUtils::THE_EPSILON)
0700       {
0701         // Moving toward upper bound
0702         double aMaxStep = (theUpperBounds(aBndIdx) - aX(i)) / aDir(i);
0703         aAlphaMax       = std::min(aAlphaMax, aMaxStep);
0704       }
0705     }
0706     aAlphaMax = std::max(aAlphaMax, MathUtils::THE_EPSILON);
0707 
0708     MathUtils::LineSearchResult aLineResult =
0709       MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, aAlphaMax, 1.0e-4, 0.5, 50);
0710 
0711     if (!aLineResult.IsValid || aLineResult.Alpha < MathUtils::THE_EPSILON)
0712     {
0713       // Fall back to projected steepest descent
0714       for (int i = aLower; i <= aUpper; ++i)
0715       {
0716         aDir(i) = -aGrad(i);
0717       }
0718       // Recompute alpha max for steepest descent
0719       aAlphaMax = 1.0;
0720       for (int i = aLower; i <= aUpper; ++i)
0721       {
0722         const int aBndIdx = theLowerBounds.Lower() + (i - aLower);
0723         if (aDir(i) < -MathUtils::THE_EPSILON)
0724         {
0725           aAlphaMax = std::min(aAlphaMax, (theLowerBounds(aBndIdx) - aX(i)) / aDir(i));
0726         }
0727         else if (aDir(i) > MathUtils::THE_EPSILON)
0728         {
0729           aAlphaMax = std::min(aAlphaMax, (theUpperBounds(aBndIdx) - aX(i)) / aDir(i));
0730         }
0731       }
0732       aAlphaMax = std::max(aAlphaMax, MathUtils::THE_EPSILON);
0733 
0734       aLineResult =
0735         MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, aAlphaMax, 1.0e-4, 0.5, 50);
0736 
0737       if (!aLineResult.IsValid)
0738       {
0739         aResult.Status   = Status::NotConverged;
0740         aResult.Solution = aX;
0741         aResult.Value    = aFx;
0742         aResult.Gradient = aGrad;
0743         return aResult;
0744       }
0745 
0746       // Reset Hessian
0747       for (int i = 1; i <= aN; ++i)
0748       {
0749         for (int j = 1; j <= aN; ++j)
0750         {
0751           aH(i, j) = (i == j) ? 1.0 : 0.0;
0752         }
0753       }
0754     }
0755 
0756     // Compute and clamp new point
0757     for (int i = aLower; i <= aUpper; ++i)
0758     {
0759       aXNew(i) = aX(i) + aLineResult.Alpha * aDir(i);
0760     }
0761     ClampToBounds(aXNew);
0762 
0763     // Compute s = x_new - x
0764     for (int i = 1; i <= aN; ++i)
0765     {
0766       aS(i) = aXNew(aLower + i - 1) - aX(aLower + i - 1);
0767     }
0768 
0769     // Evaluate at new point
0770     double aFxNew = 0.0;
0771     if (!theFunc.Value(aXNew, aFxNew))
0772     {
0773       aResult.Status   = Status::NumericalError;
0774       aResult.Solution = aX;
0775       aResult.Value    = aFx;
0776       return aResult;
0777     }
0778 
0779     if (!theFunc.Gradient(aXNew, aGradNew))
0780     {
0781       aResult.Status   = Status::NumericalError;
0782       aResult.Solution = aX;
0783       aResult.Value    = aFx;
0784       return aResult;
0785     }
0786     ProjectGradient(aXNew, aGradNew);
0787 
0788     // Check gradient convergence
0789     aGradNorm = 0.0;
0790     for (int i = aLower; i <= aUpper; ++i)
0791     {
0792       aGradNorm += MathUtils::Sqr(aGradNew(i));
0793     }
0794     aGradNorm = std::sqrt(aGradNorm);
0795 
0796     if (aGradNorm < theConfig.FTolerance)
0797     {
0798       aResult.Status   = Status::OK;
0799       aResult.Solution = aXNew;
0800       aResult.Value    = aFxNew;
0801       aResult.Gradient = aGradNew;
0802       return aResult;
0803     }
0804 
0805     // Check X convergence
0806     double aMaxDiff = 0.0;
0807     for (int i = aLower; i <= aUpper; ++i)
0808     {
0809       aMaxDiff = std::max(aMaxDiff, std::abs(aXNew(i) - aX(i)));
0810     }
0811     if (aMaxDiff < theConfig.XTolerance)
0812     {
0813       aResult.Status   = Status::OK;
0814       aResult.Solution = aXNew;
0815       aResult.Value    = aFxNew;
0816       aResult.Gradient = aGradNew;
0817       return aResult;
0818     }
0819 
0820     // Compute y = grad_new - grad
0821     for (int i = 1; i <= aN; ++i)
0822     {
0823       aY(i) = aGradNew(aLower + i - 1) - aGrad(aLower + i - 1);
0824     }
0825 
0826     // Curvature condition
0827     double aSY = 0.0;
0828     for (int i = 1; i <= aN; ++i)
0829     {
0830       aSY += aS(i) * aY(i);
0831     }
0832 
0833     if (aSY > MathUtils::THE_ZERO_TOL)
0834     {
0835       const double aRho = 1.0 / aSY;
0836 
0837       math_Vector aHy(1, aN, 0.0);
0838       for (int i = 1; i <= aN; ++i)
0839       {
0840         for (int j = 1; j <= aN; ++j)
0841         {
0842           aHy(i) += aH(i, j) * aY(j);
0843         }
0844       }
0845 
0846       double aYHy = 0.0;
0847       for (int i = 1; i <= aN; ++i)
0848       {
0849         aYHy += aY(i) * aHy(i);
0850       }
0851 
0852       const double aFactor = 1.0 + aRho * aYHy;
0853       for (int i = 1; i <= aN; ++i)
0854       {
0855         for (int j = 1; j <= aN; ++j)
0856         {
0857           aH(i, j) =
0858             aH(i, j) - aRho * (aHy(i) * aS(j) + aS(i) * aHy(j)) + aFactor * aRho * aS(i) * aS(j);
0859         }
0860       }
0861     }
0862 
0863     aX    = aXNew;
0864     aGrad = aGradNew;
0865     aFx   = aFxNew;
0866   }
0867 
0868   aResult.Status   = Status::MaxIterations;
0869   aResult.Solution = aX;
0870   aResult.Value    = aFx;
0871   aResult.Gradient = aGrad;
0872   return aResult;
0873 }
0874 
0875 } // namespace MathOpt
0876 
0877 #endif // _MathOpt_BFGS_HeaderFile