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_FRPR_HeaderFile
0015 #define _MathOpt_FRPR_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 <cmath>
0024 
0025 namespace MathOpt
0026 {
0027 using namespace MathUtils;
0028 
0029 //! Conjugate gradient formula selection.
0030 enum class ConjugateGradientFormula
0031 {
0032   FletcherReeves,  //!< beta = g_new^T g_new / g^T g (original, guaranteed descent)
0033   PolakRibiere,    //!< beta = g_new^T (g_new - g) / g^T g (often faster, may need restarts)
0034   HestenesStiefel, //!< beta = g_new^T (g_new - g) / d^T (g_new - g)
0035   DaiYuan          //!< beta = g_new^T g_new / d^T (g_new - g)
0036 };
0037 
0038 //! Configuration for FRPR conjugate gradient method.
0039 struct FRPRConfig : Config
0040 {
0041   ConjugateGradientFormula Formula = ConjugateGradientFormula::PolakRibiere; //!< Beta formula
0042   int RestartInterval = 0; //!< Restart every N iterations (0 = n, where n is dimension)
0043 
0044   //! Default constructor.
0045   FRPRConfig() = default;
0046 
0047   //! Constructor with tolerance.
0048   explicit FRPRConfig(double theTolerance, int theMaxIter = 100)
0049       : Config(theTolerance, theMaxIter)
0050   {
0051   }
0052 };
0053 
0054 //! Fletcher-Reeves-Polak-Ribiere conjugate gradient method.
0055 //!
0056 //! Memory-efficient alternative to BFGS for large-scale optimization.
0057 //! Uses only O(n) storage compared to O(n^2) for BFGS.
0058 //!
0059 //! Algorithm:
0060 //! 1. Compute gradient g at current point
0061 //! 2. First iteration: search direction p = -g
0062 //! 3. Perform line search along p
0063 //! 4. Compute new gradient g_new
0064 //! 5. Update: beta = (g_new . g_new) / (g . g) [Fletcher-Reeves]
0065 //!         or beta = (g_new . (g_new - g)) / (g . g) [Polak-Ribiere]
0066 //! 6. New direction: p = -g_new + beta * p
0067 //! 7. Restart with steepest descent if beta < 0 or periodically
0068 //! 8. Repeat until convergence
0069 //!
0070 //! @tparam Function type with:
0071 //!   - Value(const math_Vector&, double&) for function value
0072 //!   - Gradient(const math_Vector&, math_Vector&) for gradient
0073 //! @param theFunc function object with value and gradient
0074 //! @param theStartingPoint initial guess
0075 //! @param theConfig solver configuration
0076 //! @return result containing minimum location and value
0077 template <typename Function>
0078 VectorResult FRPR(Function&          theFunc,
0079                   const math_Vector& theStartingPoint,
0080                   const FRPRConfig&  theConfig = FRPRConfig())
0081 {
0082   VectorResult aResult;
0083 
0084   const int aLower = theStartingPoint.Lower();
0085   const int aUpper = theStartingPoint.Upper();
0086   const int aN     = aUpper - aLower + 1;
0087 
0088   // Restart interval
0089   const int aRestartInterval = (theConfig.RestartInterval > 0) ? theConfig.RestartInterval : aN;
0090 
0091   // Current point
0092   math_Vector aX(aLower, aUpper);
0093   aX = theStartingPoint;
0094 
0095   double aFx = 0.0;
0096   if (!theFunc.Value(aX, aFx))
0097   {
0098     aResult.Status = Status::NumericalError;
0099     return aResult;
0100   }
0101 
0102   // Gradient at current point
0103   math_Vector aGrad(aLower, aUpper);
0104   if (!theFunc.Gradient(aX, aGrad))
0105   {
0106     aResult.Status = Status::NumericalError;
0107     return aResult;
0108   }
0109 
0110   // Check if already at minimum
0111   double aGradNormSq = 0.0;
0112   for (int i = aLower; i <= aUpper; ++i)
0113   {
0114     aGradNormSq += MathUtils::Sqr(aGrad(i));
0115   }
0116 
0117   if (std::sqrt(aGradNormSq) < theConfig.FTolerance)
0118   {
0119     aResult.Status   = Status::OK;
0120     aResult.Solution = aX;
0121     aResult.Value    = aFx;
0122     aResult.Gradient = aGrad;
0123     return aResult;
0124   }
0125 
0126   // Search direction (initially steepest descent)
0127   math_Vector aDir(aLower, aUpper);
0128   for (int i = aLower; i <= aUpper; ++i)
0129   {
0130     aDir(i) = -aGrad(i);
0131   }
0132 
0133   // Working vectors
0134   math_Vector aXNew(aLower, aUpper);
0135   math_Vector aGradNew(aLower, aUpper);
0136   math_Vector aGradDiff(aLower, aUpper);
0137 
0138   int aRestartCount = 0;
0139 
0140   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0141   {
0142     aResult.NbIterations = anIter + 1;
0143 
0144     // Line search
0145     MathUtils::LineSearchResult aLineResult =
0146       MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0147 
0148     if (!aLineResult.IsValid || aLineResult.Alpha < MathUtils::THE_EPSILON)
0149     {
0150       // Line search failed, try steepest descent
0151       for (int i = aLower; i <= aUpper; ++i)
0152       {
0153         aDir(i) = -aGrad(i);
0154       }
0155       aLineResult = MathUtils::ArmijoBacktrack(theFunc, aX, aDir, aGrad, aFx, 1.0, 1.0e-4, 0.5, 50);
0156 
0157       if (!aLineResult.IsValid)
0158       {
0159         aResult.Status   = Status::NotConverged;
0160         aResult.Solution = aX;
0161         aResult.Value    = aFx;
0162         aResult.Gradient = aGrad;
0163         return aResult;
0164       }
0165       aRestartCount = 0; // Reset restart counter after steepest descent
0166     }
0167 
0168     // Compute new point
0169     for (int i = aLower; i <= aUpper; ++i)
0170     {
0171       aXNew(i) = aX(i) + aLineResult.Alpha * aDir(i);
0172     }
0173 
0174     // Check X convergence
0175     double aMaxDiff = 0.0;
0176     for (int i = aLower; i <= aUpper; ++i)
0177     {
0178       aMaxDiff = std::max(aMaxDiff, std::abs(aXNew(i) - aX(i)));
0179     }
0180 
0181     // Evaluate gradient at new point
0182     if (!theFunc.Gradient(aXNew, aGradNew))
0183     {
0184       aResult.Status   = Status::NumericalError;
0185       aResult.Solution = aX;
0186       aResult.Value    = aFx;
0187       return aResult;
0188     }
0189 
0190     // Check gradient convergence
0191     double aGradNewNormSq = 0.0;
0192     for (int i = aLower; i <= aUpper; ++i)
0193     {
0194       aGradNewNormSq += MathUtils::Sqr(aGradNew(i));
0195     }
0196 
0197     if (std::sqrt(aGradNewNormSq) < theConfig.FTolerance)
0198     {
0199       aResult.Status   = Status::OK;
0200       aResult.Solution = aXNew;
0201       aResult.Value    = aLineResult.FNew;
0202       aResult.Gradient = aGradNew;
0203       return aResult;
0204     }
0205 
0206     if (aMaxDiff < theConfig.XTolerance)
0207     {
0208       aResult.Status   = Status::OK;
0209       aResult.Solution = aXNew;
0210       aResult.Value    = aLineResult.FNew;
0211       aResult.Gradient = aGradNew;
0212       return aResult;
0213     }
0214 
0215     // Compute gradient difference for some formulas
0216     for (int i = aLower; i <= aUpper; ++i)
0217     {
0218       aGradDiff(i) = aGradNew(i) - aGrad(i);
0219     }
0220 
0221     // Compute beta based on selected formula
0222     double aBeta = 0.0;
0223     ++aRestartCount;
0224 
0225     if (aRestartCount >= aRestartInterval)
0226     {
0227       // Periodic restart with steepest descent
0228       aBeta         = 0.0;
0229       aRestartCount = 0;
0230     }
0231     else
0232     {
0233       switch (theConfig.Formula)
0234       {
0235         case ConjugateGradientFormula::FletcherReeves:
0236           // beta = g_new^T g_new / g^T g
0237           if (aGradNormSq > MathUtils::THE_ZERO_TOL)
0238           {
0239             aBeta = aGradNewNormSq / aGradNormSq;
0240           }
0241           break;
0242 
0243         case ConjugateGradientFormula::PolakRibiere: {
0244           // beta = g_new^T (g_new - g) / g^T g
0245           double aDot = 0.0;
0246           for (int i = aLower; i <= aUpper; ++i)
0247           {
0248             aDot += aGradNew(i) * aGradDiff(i);
0249           }
0250           if (aGradNormSq > MathUtils::THE_ZERO_TOL)
0251           {
0252             aBeta = aDot / aGradNormSq;
0253           }
0254           // Restart if beta < 0 (PR+ variant)
0255           if (aBeta < 0.0)
0256           {
0257             aBeta         = 0.0;
0258             aRestartCount = 0;
0259           }
0260         }
0261         break;
0262 
0263         case ConjugateGradientFormula::HestenesStiefel: {
0264           // beta = g_new^T (g_new - g) / d^T (g_new - g)
0265           double aNum = 0.0;
0266           double aDen = 0.0;
0267           for (int i = aLower; i <= aUpper; ++i)
0268           {
0269             aNum += aGradNew(i) * aGradDiff(i);
0270             aDen += aDir(i) * aGradDiff(i);
0271           }
0272           if (std::abs(aDen) > MathUtils::THE_ZERO_TOL)
0273           {
0274             aBeta = aNum / aDen;
0275           }
0276           if (aBeta < 0.0)
0277           {
0278             aBeta         = 0.0;
0279             aRestartCount = 0;
0280           }
0281         }
0282         break;
0283 
0284         case ConjugateGradientFormula::DaiYuan: {
0285           // beta = g_new^T g_new / d^T (g_new - g)
0286           double aDen = 0.0;
0287           for (int i = aLower; i <= aUpper; ++i)
0288           {
0289             aDen += aDir(i) * aGradDiff(i);
0290           }
0291           if (std::abs(aDen) > MathUtils::THE_ZERO_TOL)
0292           {
0293             aBeta = aGradNewNormSq / aDen;
0294           }
0295         }
0296         break;
0297       }
0298     }
0299 
0300     // Update search direction: p = -g_new + beta * p
0301     for (int i = aLower; i <= aUpper; ++i)
0302     {
0303       aDir(i) = -aGradNew(i) + aBeta * aDir(i);
0304     }
0305 
0306     // Check if direction is still a descent direction
0307     double aDirDeriv = 0.0;
0308     for (int i = aLower; i <= aUpper; ++i)
0309     {
0310       aDirDeriv += aGradNew(i) * aDir(i);
0311     }
0312 
0313     if (aDirDeriv >= 0.0)
0314     {
0315       // Not a descent direction, restart with steepest descent
0316       for (int i = aLower; i <= aUpper; ++i)
0317       {
0318         aDir(i) = -aGradNew(i);
0319       }
0320       aRestartCount = 0;
0321     }
0322 
0323     // Update for next iteration
0324     aX          = aXNew;
0325     aGrad       = aGradNew;
0326     aGradNormSq = aGradNewNormSq;
0327     aFx         = aLineResult.FNew;
0328   }
0329 
0330   // Maximum iterations reached
0331   aResult.Status   = Status::MaxIterations;
0332   aResult.Solution = aX;
0333   aResult.Value    = aFx;
0334   aResult.Gradient = aGrad;
0335   return aResult;
0336 }
0337 
0338 //! FRPR with numerical gradient.
0339 //! Uses central differences when analytical gradient is not available.
0340 //!
0341 //! @tparam Function type with Value(const math_Vector&, double&) method only
0342 //! @param theFunc function object
0343 //! @param theStartingPoint initial guess
0344 //! @param theGradStep step size for numerical gradient
0345 //! @param theConfig solver configuration
0346 //! @return result containing minimum location and value
0347 template <typename Function>
0348 VectorResult FRPRNumerical(Function&          theFunc,
0349                            const math_Vector& theStartingPoint,
0350                            double             theGradStep = 1.0e-8,
0351                            const FRPRConfig&  theConfig   = FRPRConfig())
0352 {
0353   // Wrapper that adds numerical gradient
0354   class FuncWithGradient
0355   {
0356   public:
0357     FuncWithGradient(Function& theF, double theStep)
0358         : myFunc(theF),
0359           myStep(theStep)
0360     {
0361     }
0362 
0363     bool Value(const math_Vector& theX, double& theF) { return myFunc.Value(theX, theF); }
0364 
0365     bool Gradient(const math_Vector& theX, math_Vector& theGrad)
0366     {
0367       math_Vector aXMod = theX;
0368       return MathUtils::NumericalGradientAdaptive(myFunc, aXMod, theGrad, myStep);
0369     }
0370 
0371   private:
0372     Function& myFunc;
0373     double    myStep;
0374   };
0375 
0376   FuncWithGradient aWrapper(theFunc, theGradStep);
0377   return FRPR(aWrapper, theStartingPoint, theConfig);
0378 }
0379 
0380 } // namespace MathOpt
0381 
0382 #endif // _MathOpt_FRPR_HeaderFile