You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

339 lines
11 KiB

// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2021
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 4.0.2019.08.13
#pragma once
#include <Mathematics/Logger.h>
#include <Mathematics/Math.h>
#include <algorithm>
#include <functional>
// The interval [t0,t1] provided to GetMinimum(Real,Real,Real,Real&,Real&)
// is processed by examining subintervals. On each subinteral [a,b], the
// values f0 = F(a), f1 = F((a+b)/2), and f2 = F(b) are examined. If
// {f0,f1,f2} is monotonic, then [a,b] is subdivided and processed. The
// maximum depth of the recursion is limited by 'maxLevel'. If {f0,f1,f2}
// is not monotonic, then two cases arise. First, if f1 = min{f0,f1,f2},
// then {f0,f1,f2} is said to "bracket a minimum" and GetBracketedMinimum(*)
// is called to locate the function minimum. The process uses a form of
// bisection called "parabolic interpolation" and the maximum number of
// bisection steps is 'maxBracket'. Second, if f1 = max{f0,f1,f2}, then
// {f0,f1,f2} brackets a maximum. The minimum search continues recursively
// as before on [a,(a+b)/2] and [(a+b)/2,b].
namespace gte
{
template <typename Real>
class Minimize1
{
public:
// Construction.
Minimize1(std::function<Real(Real)> const& F, int maxLevel, int maxBracket,
Real epsilon = (Real)1e-08, Real tolerance = (Real)1e-04)
:
mFunction(F),
mMaxLevel(maxLevel),
mMaxBracket(maxBracket),
mEpsilon(0),
mTolerance(0)
{
SetEpsilon(epsilon);
SetTolerance(tolerance);
}
// Member access.
inline void SetEpsilon(Real epsilon)
{
mEpsilon = (epsilon > (Real)0 ? epsilon : (Real)0);
}
inline void SetTolerance(Real tolerance)
{
mTolerance = (tolerance > (Real)0 ? tolerance : (Real)0);
}
inline Real GetEpsilon() const
{
return mEpsilon;
}
inline Real GetTolerance() const
{
return mTolerance;
}
// Search for a minimum of 'function' on the interval [t0,t1] using an
// initial guess of 'tInitial'. The location of the minimum is 'tMin'
// and/ the value of the minimum is 'fMin'.
void GetMinimum(Real t0, Real t1, Real tInitial, Real& tMin, Real& fMin)
{
LogAssert(t0 <= tInitial && tInitial <= t1, "Invalid initial t value.");
mTMin = std::numeric_limits<Real>::max();
mFMin = std::numeric_limits<Real>::max();
Real f0 = mFunction(t0);
if (f0 < mFMin)
{
mTMin = t0;
mFMin = f0;
}
Real fInitial = mFunction(tInitial);
if (fInitial < mFMin)
{
mTMin = tInitial;
mFMin = fInitial;
}
Real f1 = mFunction(t1);
if (f1 < mFMin)
{
mTMin = t1;
mFMin = f1;
}
GetMinimum(t0, f0, tInitial, fInitial, t1, f1, mMaxLevel);
tMin = mTMin;
fMin = mFMin;
}
private:
// This is called to start the search on [t0,tInitial] and
// [tInitial,t1].
void GetMinimum(Real t0, Real f0, Real t1, Real f1, int level)
{
if (level-- == 0)
{
return;
}
Real tm = (Real)0.5 * (t0 + t1);
Real fm = mFunction(tm);
if (fm < mFMin)
{
mTMin = tm;
mFMin = fm;
}
if (f0 - (Real)2 * fm + f1 > (Real)0)
{
// The quadratic fit has positive second derivative at the
// midpoint.
if (f1 > f0)
{
if (fm >= f0)
{
// Increasing, repeat on [t0,tm].
GetMinimum(t0, f0, tm, fm, level);
}
else
{
// Not monotonic, have a bracket.
GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level);
}
}
else if (f1 < f0)
{
if (fm >= f1)
{
// Decreasing, repeat on [tm,t1].
GetMinimum(tm, fm, t1, f1, level);
}
else
{
// Not monotonic, have a bracket.
GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level);
}
}
else
{
// Constant, repeat on [t0,tm] and [tm,t1].
GetMinimum(t0, f0, tm, fm, level);
GetMinimum(tm, fm, t1, f1, level);
}
}
else
{
// The quadratic fit has nonpositive second derivative at the
// midpoint.
if (f1 > f0)
{
// Repeat on [t0,tm].
GetMinimum(t0, f0, tm, fm, level);
}
else if (f1 < f0)
{
// Repeat on [tm,t1].
GetMinimum(tm, fm, t1, f1, level);
}
else
{
// Repeat on [t0,tm] and [tm,t1].
GetMinimum(t0, f0, tm, fm, level);
GetMinimum(tm, fm, t1, f1, level);
}
}
}
// This is called recursively to search [a,(a+b)/2] and [(a+b)/2,b].
void GetMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, Real f1, int level)
{
if (level-- == 0)
{
return;
}
if ((t1 - tm) * (f0 - fm) > (tm - t0) * (fm - f1))
{
// The quadratic fit has positive second derivative at the
// midpoint.
if (f1 > f0)
{
if (fm >= f0)
{
// Increasing, repeat on [t0,tm].
GetMinimum(t0, f0, tm, fm, level);
}
else
{
// Not monotonic, have a bracket.
GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level);
}
}
else if (f1 < f0)
{
if (fm >= f1)
{
// Decreasing, repeat on [tm,t1].
GetMinimum(tm, fm, t1, f1, level);
}
else
{
// Not monotonic, have a bracket.
GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level);
}
}
else
{
// Constant, repeat on [t0,tm] and [tm,t1].
GetMinimum(t0, f0, tm, fm, level);
GetMinimum(tm, fm, t1, f1, level);
}
}
else
{
// The quadratic fit has a nonpositive second derivative at
// the midpoint.
if (f1 > f0)
{
// Repeat on [t0,tm].
GetMinimum(t0, f0, tm, fm, level);
}
else if (f1 < f0)
{
// Repeat on [tm,t1].
GetMinimum(tm, fm, t1, f1, level);
}
else
{
// Repeat on [t0,tm] and [tm,t1].
GetMinimum(t0, f0, tm, fm, level);
GetMinimum(tm, fm, t1, f1, level);
}
}
}
// This is called when {f0,f1,f2} brackets a minimum.
void GetBracketedMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, Real f1, int level)
{
for (int i = 0; i < mMaxBracket; ++i)
{
// Update minimum value.
if (fm < mFMin)
{
mTMin = tm;
mFMin = fm;
}
// Test for convergence.
if (std::fabs(t1 - t0) <= (Real)2 * mTolerance * std::fabs(tm) + mEpsilon)
{
break;
}
// Compute vertex of interpolating parabola.
Real dt0 = t0 - tm;
Real dt1 = t1 - tm;
Real df0 = f0 - fm;
Real df1 = f1 - fm;
Real tmp0 = dt0 * df1;
Real tmp1 = dt1 * df0;
Real denom = tmp1 - tmp0;
if (std::fabs(denom) <= mEpsilon)
{
return;
}
// Compute tv and clamp to [t0,t1] to offset floating-point
// rounding errors.
Real tv = tm + (Real)0.5 * (dt1 * tmp1 - dt0 * tmp0) / denom;
tv = std::max(t0, std::min(tv, t1));
Real fv = mFunction(tv);
if (fv < mFMin)
{
mTMin = tv;
mFMin = fv;
}
if (tv < tm)
{
if (fv < fm)
{
t1 = tm;
f1 = fm;
tm = tv;
fm = fv;
}
else
{
t0 = tv;
f0 = fv;
}
}
else if (tv > tm)
{
if (fv < fm)
{
t0 = tm;
f0 = fm;
tm = tv;
fm = fv;
}
else
{
t1 = tv;
f1 = fv;
}
}
else
{
// The vertex of parabola is already at middle sample point.
GetMinimum(t0, f0, tm, fm, level);
GetMinimum(tm, fm, t1, f1, level);
}
}
}
std::function<Real(Real)> mFunction;
int mMaxLevel;
int mMaxBracket;
Real mTMin, mFMin;
Real mEpsilon, mTolerance;
};
}