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_GlobOpt_HeaderFile
0015 #define _MathOpt_GlobOpt_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathUtils_Random.hxx>
0020 #include <MathOpt_PSO.hxx>
0021 #include <MathOpt_BFGS.hxx>
0022 #include <MathOpt_Powell.hxx>
0023 #include <MathUtils_Core.hxx>
0024 
0025 #include <NCollection_DynamicArray.hxx>
0026 
0027 #include <cmath>
0028 
0029 namespace MathOpt
0030 {
0031 using namespace MathUtils;
0032 
0033 //! Global optimization strategy selection.
0034 enum class GlobalStrategy
0035 {
0036   PSO,                  //!< Particle Swarm Optimization only
0037   MultiStart,           //!< Multiple local optimizations from random starts
0038   PSOHybrid,            //!< PSO followed by local refinement
0039   DifferentialEvolution //!< Differential Evolution algorithm
0040 };
0041 
0042 //! Configuration for global optimization.
0043 struct GlobalConfig : NDimConfig
0044 {
0045   GlobalStrategy Strategy           = GlobalStrategy::PSOHybrid; //!< Algorithm to use
0046   int            NbPopulation       = 40;                        //!< Population/swarm size
0047   int            NbStarts           = 10;  //!< Number of random starts (for MultiStart)
0048   double         MutationScale      = 0.8; //!< Mutation scale (for DE)
0049   double         CrossoverProb      = 0.9; //!< Crossover probability (for DE)
0050   unsigned int   Seed               = 6;   //!< Random seed
0051   int            PolishBudgetPerDim = 50;  //!< Max polishing evals per dimension (0 = no polishing)
0052 
0053   //! Default constructor.
0054   GlobalConfig()
0055       : NDimConfig(1.0e-8, 200, true)
0056   {
0057   }
0058 
0059   //! Constructor with strategy.
0060   GlobalConfig(GlobalStrategy theStrategy, int theMaxIter = 200)
0061       : NDimConfig(1.0e-8, theMaxIter, true),
0062         Strategy(theStrategy)
0063   {
0064   }
0065 };
0066 
0067 //! Differential Evolution algorithm for global optimization.
0068 //!
0069 //! DE is a stochastic, population-based optimization algorithm.
0070 //! It uses mutation, crossover, and selection operations to evolve
0071 //! a population of candidate solutions.
0072 //!
0073 //! @tparam Function type with Value(const math_Vector&, double&) method
0074 //! @param theFunc function to minimize
0075 //! @param theLowerBounds lower bounds
0076 //! @param theUpperBounds upper bounds
0077 //! @param theConfig optimization configuration
0078 //! @return result containing best solution found
0079 template <typename Function>
0080 VectorResult DifferentialEvolution(Function&           theFunc,
0081                                    const math_Vector&  theLowerBounds,
0082                                    const math_Vector&  theUpperBounds,
0083                                    const GlobalConfig& theConfig = GlobalConfig())
0084 {
0085   VectorResult aResult;
0086 
0087   const int aLower  = theLowerBounds.Lower();
0088   const int aUpper  = theLowerBounds.Upper();
0089   const int aNbDims = aUpper - aLower + 1;
0090 
0091   if (theUpperBounds.Length() != aNbDims)
0092   {
0093     aResult.Status = Status::InvalidInput;
0094     return aResult;
0095   }
0096 
0097   const int aNbPop = theConfig.NbPopulation;
0098   if (aNbPop < 4)
0099   {
0100     // DE requires at least 4 population members for mutation (3 distinct + target)
0101     aResult.Status = Status::InvalidInput;
0102     return aResult;
0103   }
0104 
0105   const double aMutScale  = theConfig.MutationScale;
0106   const double aCrossProb = theConfig.CrossoverProb;
0107 
0108   // Random number generator
0109   MathUtils::RandomGenerator aRNG(theConfig.Seed);
0110 
0111   // Population: vector of candidate solutions
0112   NCollection_DynamicArray<math_Vector> aPopulation;
0113   math_Vector                           aFitness(0, aNbPop - 1);
0114 
0115   // Initialize population
0116   for (int aMemberIdx = 0; aMemberIdx < aNbPop; ++aMemberIdx)
0117   {
0118     aPopulation.Append(math_Vector(aLower, aUpper));
0119     for (int aDimIdx = aLower; aDimIdx <= aUpper; ++aDimIdx)
0120     {
0121       const double aRandVal = aRNG.NextReal();
0122       aPopulation.ChangeValue(aMemberIdx)(aDimIdx) =
0123         theLowerBounds(aDimIdx) + aRandVal * (theUpperBounds(aDimIdx) - theLowerBounds(aDimIdx));
0124     }
0125 
0126     double aFitVal;
0127     if (!theFunc.Value(aPopulation.Value(aMemberIdx), aFitVal))
0128     {
0129       aFitVal = std::numeric_limits<double>::max();
0130     }
0131     aFitness(aMemberIdx) = aFitVal;
0132   }
0133 
0134   // Find best
0135   int    aBestIdx   = 0;
0136   double aBestValue = aFitness(0);
0137   for (int aMemberIdx = 1; aMemberIdx < aNbPop; ++aMemberIdx)
0138   {
0139     if (aFitness(aMemberIdx) < aBestValue)
0140     {
0141       aBestValue = aFitness(aMemberIdx);
0142       aBestIdx   = aMemberIdx;
0143     }
0144   }
0145 
0146   // Trial vector
0147   math_Vector aTrial(aLower, aUpper);
0148 
0149   // Evolution loop
0150   for (int anIter = 0; anIter < theConfig.MaxIterations; ++anIter)
0151   {
0152     aResult.NbIterations = anIter + 1;
0153 
0154     for (int aMemberIdx = 0; aMemberIdx < aNbPop; ++aMemberIdx)
0155     {
0156       // Select 3 distinct random indices different from current member
0157       int anIdxA, anIdxB, anIdxC;
0158       do
0159       {
0160         anIdxA = static_cast<int>(aRNG.NextReal() * aNbPop);
0161       } while (anIdxA == aMemberIdx);
0162       do
0163       {
0164         anIdxB = static_cast<int>(aRNG.NextReal() * aNbPop);
0165       } while (anIdxB == aMemberIdx || anIdxB == anIdxA);
0166       do
0167       {
0168         anIdxC = static_cast<int>(aRNG.NextReal() * aNbPop);
0169       } while (anIdxC == aMemberIdx || anIdxC == anIdxA || anIdxC == anIdxB);
0170 
0171       // Mutation and crossover
0172       const int aJRand = aLower + static_cast<int>(aRNG.NextReal() * aNbDims);
0173 
0174       for (int aDimIdx = aLower; aDimIdx <= aUpper; ++aDimIdx)
0175       {
0176         if (aRNG.NextReal() < aCrossProb || aDimIdx == aJRand)
0177         {
0178           // Mutation: DE/rand/1
0179           const double aMutVal =
0180             aPopulation.Value(anIdxA)(aDimIdx)
0181             + aMutScale * (aPopulation.Value(anIdxB)(aDimIdx) - aPopulation.Value(anIdxC)(aDimIdx));
0182           // Clamp to bounds
0183           aTrial(aDimIdx) =
0184             MathUtils::Clamp(aMutVal, theLowerBounds(aDimIdx), theUpperBounds(aDimIdx));
0185         }
0186         else
0187         {
0188           aTrial(aDimIdx) = aPopulation.Value(aMemberIdx)(aDimIdx);
0189         }
0190       }
0191 
0192       // Selection
0193       double aTrialFitness;
0194       if (!theFunc.Value(aTrial, aTrialFitness))
0195       {
0196         aTrialFitness = std::numeric_limits<double>::max();
0197       }
0198 
0199       if (aTrialFitness <= aFitness(aMemberIdx))
0200       {
0201         aPopulation.ChangeValue(aMemberIdx) = aTrial;
0202         aFitness(aMemberIdx)                = aTrialFitness;
0203 
0204         if (aTrialFitness < aBestValue)
0205         {
0206           aBestValue = aTrialFitness;
0207           aBestIdx   = aMemberIdx;
0208         }
0209       }
0210     }
0211 
0212     // Check convergence
0213     double aMaxDiff = 0.0;
0214     for (int aMemberIdx = 0; aMemberIdx < aNbPop; ++aMemberIdx)
0215     {
0216       aMaxDiff = std::max(aMaxDiff, std::abs(aFitness(aMemberIdx) - aBestValue));
0217     }
0218 
0219     if (aMaxDiff < theConfig.Tolerance)
0220     {
0221       break;
0222     }
0223   }
0224 
0225   // Polish the best solution using coordinate-wise Brent's method
0226   math_Vector aPolished      = aPopulation.Value(aBestIdx);
0227   double      aPolishedValue = aBestValue;
0228   if (theConfig.PolishBudgetPerDim > 0)
0229   {
0230     int aPolishEvals = 0;
0231     PolishCoordinateWise(theFunc,
0232                          aPolished,
0233                          aPolishedValue,
0234                          theLowerBounds,
0235                          theUpperBounds,
0236                          theConfig.Tolerance,
0237                          theConfig.PolishBudgetPerDim * aNbDims,
0238                          aPolishEvals);
0239   }
0240 
0241   aResult.Status   = Status::OK;
0242   aResult.Solution = aPolished;
0243   aResult.Value    = aPolishedValue;
0244   return aResult;
0245 }
0246 
0247 //! Multi-start local optimization.
0248 //!
0249 //! Runs multiple local optimizations from random starting points
0250 //! and returns the best result found. Uses Powell's method for
0251 //! local optimization (gradient-free).
0252 //!
0253 //! @tparam Function type with Value(const math_Vector&, double&) method
0254 //! @param theFunc function to minimize
0255 //! @param theLowerBounds lower bounds
0256 //! @param theUpperBounds upper bounds
0257 //! @param theConfig optimization configuration
0258 //! @return result containing best solution found
0259 template <typename Function>
0260 VectorResult MultiStart(Function&           theFunc,
0261                         const math_Vector&  theLowerBounds,
0262                         const math_Vector&  theUpperBounds,
0263                         const GlobalConfig& theConfig = GlobalConfig())
0264 {
0265   VectorResult aResult;
0266 
0267   const int aLower  = theLowerBounds.Lower();
0268   const int aUpper  = theLowerBounds.Upper();
0269   const int aNbDims = aUpper - aLower + 1;
0270 
0271   if (theUpperBounds.Length() != aNbDims)
0272   {
0273     aResult.Status = Status::InvalidInput;
0274     return aResult;
0275   }
0276 
0277   MathUtils::RandomGenerator aRNG(theConfig.Seed);
0278 
0279   math_Vector aBestSolution(aLower, aUpper);
0280   double      aBestValue = std::numeric_limits<double>::max();
0281   bool        aFound     = false;
0282 
0283   // Configure Powell optimization for local refinement
0284   Config aPowellConfig;
0285   aPowellConfig.Tolerance     = theConfig.Tolerance;
0286   aPowellConfig.XTolerance    = theConfig.Tolerance;
0287   aPowellConfig.FTolerance    = theConfig.Tolerance;
0288   aPowellConfig.MaxIterations = theConfig.MaxIterations / theConfig.NbStarts;
0289   if (aPowellConfig.MaxIterations < 10)
0290   {
0291     aPowellConfig.MaxIterations = 10;
0292   }
0293 
0294   for (int aStartIdx = 0; aStartIdx < theConfig.NbStarts; ++aStartIdx)
0295   {
0296     // Random starting point within bounds
0297     math_Vector aStart(aLower, aUpper);
0298     for (int aDimIdx = aLower; aDimIdx <= aUpper; ++aDimIdx)
0299     {
0300       const double aRandVal = aRNG.NextReal();
0301       aStart(aDimIdx) =
0302         theLowerBounds(aDimIdx) + aRandVal * (theUpperBounds(aDimIdx) - theLowerBounds(aDimIdx));
0303     }
0304 
0305     // Run local optimization from this starting point
0306     VectorResult aLocalResult = Powell(theFunc, aStart, aPowellConfig);
0307 
0308     if (aLocalResult.IsDone() && aLocalResult.Value)
0309     {
0310       // Clamp solution to bounds
0311       math_Vector aSol = *aLocalResult.Solution;
0312       for (int aDimIdx = aLower; aDimIdx <= aUpper; ++aDimIdx)
0313       {
0314         aSol(aDimIdx) =
0315           MathUtils::Clamp(aSol(aDimIdx), theLowerBounds(aDimIdx), theUpperBounds(aDimIdx));
0316       }
0317 
0318       // Re-evaluate at clamped point
0319       double aValue;
0320       if (theFunc.Value(aSol, aValue) && aValue < aBestValue)
0321       {
0322         aBestValue    = aValue;
0323         aBestSolution = aSol;
0324         aFound        = true;
0325       }
0326     }
0327     else
0328     {
0329       // Powell failed, just evaluate at starting point
0330       double aValue;
0331       if (theFunc.Value(aStart, aValue) && aValue < aBestValue)
0332       {
0333         aBestValue    = aValue;
0334         aBestSolution = aStart;
0335         aFound        = true;
0336       }
0337     }
0338   }
0339 
0340   if (!aFound)
0341   {
0342     aResult.Status = Status::NumericalError;
0343     return aResult;
0344   }
0345 
0346   aResult.Status   = Status::OK;
0347   aResult.Solution = aBestSolution;
0348   aResult.Value    = aBestValue;
0349   return aResult;
0350 }
0351 
0352 //! Unified global optimization interface.
0353 //!
0354 //! Selects appropriate algorithm based on configuration and
0355 //! provides a consistent interface for all global optimization methods.
0356 //!
0357 //! @tparam Function type with Value(const math_Vector&, double&) method
0358 //! @param theFunc function to minimize
0359 //! @param theLowerBounds lower bounds for each variable
0360 //! @param theUpperBounds upper bounds for each variable
0361 //! @param theConfig solver configuration
0362 //! @return result containing best solution found
0363 template <typename Function>
0364 VectorResult GlobalMinimum(Function&           theFunc,
0365                            const math_Vector&  theLowerBounds,
0366                            const math_Vector&  theUpperBounds,
0367                            const GlobalConfig& theConfig = GlobalConfig())
0368 {
0369   return GlobalMinimum(theFunc,
0370                        theLowerBounds,
0371                        theUpperBounds,
0372                        theConfig,
0373                        nullptr,
0374                        nullptr,
0375                        nullptr);
0376 }
0377 
0378 //! Unified global optimization interface with PSO-specific options.
0379 //!
0380 //! For PSO and PSOHybrid strategies, uses the provided PSO configuration,
0381 //! seed particles, and stats output. For other strategies, these are ignored.
0382 //!
0383 //! @tparam Function type with Value(const math_Vector&, double&) method
0384 //! @param theFunc function to minimize
0385 //! @param theLowerBounds lower bounds for each variable
0386 //! @param theUpperBounds upper bounds for each variable
0387 //! @param theConfig solver configuration
0388 //! @param thePSOConfig optional PSO-specific configuration (overrides auto-generated)
0389 //! @param theSeeds optional seed particles for PSO initialization
0390 //! @param theStats optional PSO statistics output
0391 //! @return result containing best solution found
0392 template <typename Function>
0393 VectorResult GlobalMinimum(Function&                                        theFunc,
0394                            const math_Vector&                               theLowerBounds,
0395                            const math_Vector&                               theUpperBounds,
0396                            const GlobalConfig&                              theConfig,
0397                            const PSOConfig*                                 thePSOConfig,
0398                            const NCollection_DynamicArray<PSOSeedParticle>* theSeeds = nullptr,
0399                            PSOStats*                                        theStats = nullptr)
0400 {
0401   switch (theConfig.Strategy)
0402   {
0403     case GlobalStrategy::PSO: {
0404       PSOConfig aPSOConfig;
0405       if (thePSOConfig != nullptr)
0406       {
0407         aPSOConfig = *thePSOConfig;
0408       }
0409       else
0410       {
0411         aPSOConfig.NbParticles        = theConfig.NbPopulation;
0412         aPSOConfig.MaxIterations      = theConfig.MaxIterations;
0413         aPSOConfig.Tolerance          = theConfig.Tolerance;
0414         aPSOConfig.Seed               = theConfig.Seed;
0415         aPSOConfig.PolishBudgetPerDim = theConfig.PolishBudgetPerDim;
0416       }
0417       return PSO(theFunc, theLowerBounds, theUpperBounds, aPSOConfig, theSeeds, theStats);
0418     }
0419 
0420     case GlobalStrategy::MultiStart:
0421       return MultiStart(theFunc, theLowerBounds, theUpperBounds, theConfig);
0422 
0423     case GlobalStrategy::PSOHybrid: {
0424       // Run PSO first for global exploration
0425       PSOConfig aPSOConfig;
0426       if (thePSOConfig != nullptr)
0427       {
0428         // Honor caller's PSO configuration fully
0429         aPSOConfig = *thePSOConfig;
0430       }
0431       else
0432       {
0433         // Auto-generate PSO config from GlobalConfig:
0434         // use half iterations and relaxed tolerance for the PSO phase
0435         aPSOConfig.NbParticles        = theConfig.NbPopulation;
0436         aPSOConfig.MaxIterations      = theConfig.MaxIterations / 2;
0437         aPSOConfig.Tolerance          = theConfig.Tolerance * 10.0;
0438         aPSOConfig.Seed               = theConfig.Seed;
0439         aPSOConfig.PolishBudgetPerDim = theConfig.PolishBudgetPerDim;
0440       }
0441 
0442       VectorResult aPSOResult =
0443         PSO(theFunc, theLowerBounds, theUpperBounds, aPSOConfig, theSeeds, theStats);
0444 
0445       if (!aPSOResult.IsDone() || !aPSOResult.Solution)
0446       {
0447         return aPSOResult;
0448       }
0449 
0450       // Local refinement using Powell (gradient-free)
0451       Config aPowellConfig;
0452       aPowellConfig.Tolerance     = theConfig.Tolerance;
0453       aPowellConfig.XTolerance    = theConfig.Tolerance;
0454       aPowellConfig.FTolerance    = theConfig.Tolerance;
0455       aPowellConfig.MaxIterations = theConfig.MaxIterations / 2;
0456 
0457       VectorResult aLocalResult = Powell(theFunc, *aPSOResult.Solution, aPowellConfig);
0458 
0459       if (aLocalResult.IsDone() && aLocalResult.Value && aPSOResult.Value
0460           && *aLocalResult.Value < *aPSOResult.Value)
0461       {
0462         // Clamp to bounds
0463         const int   aLower = theLowerBounds.Lower();
0464         const int   aUpper = theLowerBounds.Upper();
0465         math_Vector aSol   = *aLocalResult.Solution;
0466         for (int aDimIdx = aLower; aDimIdx <= aUpper; ++aDimIdx)
0467         {
0468           aSol(aDimIdx) =
0469             MathUtils::Clamp(aSol(aDimIdx), theLowerBounds(aDimIdx), theUpperBounds(aDimIdx));
0470         }
0471         aLocalResult.Solution = aSol;
0472 
0473         // Re-evaluate at clamped point
0474         double aValue;
0475         if (theFunc.Value(aSol, aValue))
0476         {
0477           aLocalResult.Value = aValue;
0478         }
0479         return aLocalResult;
0480       }
0481 
0482       return aPSOResult;
0483     }
0484 
0485     case GlobalStrategy::DifferentialEvolution:
0486       return DifferentialEvolution(theFunc, theLowerBounds, theUpperBounds, theConfig);
0487 
0488     default:
0489       VectorResult aResult;
0490       aResult.Status = Status::InvalidInput;
0491       return aResult;
0492   }
0493 }
0494 
0495 } // namespace MathOpt
0496 
0497 #endif // _MathOpt_GlobOpt_HeaderFile