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.
225 lines
8.4 KiB
225 lines
8.4 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 <cmath>
|
|
#include <functional>
|
|
|
|
// This is an implementation of Brent's Method for computing a root of a
|
|
// function on an interval [t0,t1] for which F(t0)*F(t1) < 0. The method
|
|
// uses inverse quadratic interpolation to generate a root estimate but
|
|
// falls back to inverse linear interpolation (secant method) if
|
|
// necessary. Moreover, based on previous iterates, the method will fall
|
|
// back to bisection when it appears the interpolated estimate is not of
|
|
// sufficient quality.
|
|
//
|
|
// maxIterations:
|
|
// The maximum number of iterations used to locate a root. This
|
|
// should be positive.
|
|
// negFTolerance, posFTolerance:
|
|
// The root estimate t is accepted when the function value F(t)
|
|
// satisfies negFTolerance <= F(t) <= posFTolerance. The values
|
|
// must satisfy: negFTolerance <= 0, posFTolerance >= 0.
|
|
// stepTTolerance:
|
|
// Brent's Method requires additional tests before an interpolated
|
|
// t-value is accepted as the next root estimate. One of these
|
|
// tests compares the difference of consecutive iterates and
|
|
// requires it to be larger than a user-specified t-tolerance (to
|
|
// ensure progress is made). This parameter is that tolerance
|
|
// and should be nonnegative.
|
|
// convTTolerance:
|
|
// The root search is allowed to terminate when the current
|
|
// subinterval [tsub0,tsub1] is sufficiently small, say,
|
|
// |tsub1 - tsub0| <= tolerance. This parameter is that tolerance
|
|
// and should be nonnegative.
|
|
|
|
namespace gte
|
|
{
|
|
template <typename Real>
|
|
class RootsBrentsMethod
|
|
{
|
|
public:
|
|
// It is necessary that F(t0)*F(t1) <= 0, in which case the function
|
|
// returns 'true' and the 'root' is valid; otherwise, the function
|
|
// returns 'false' and 'root' is invalid (do not use it). When
|
|
// F(t0)*F(t1) > 0, the interval may very well contain a root but we
|
|
// cannot know that. The function also returns 'false' if t0 >= t1.
|
|
|
|
static bool Find(std::function<Real(Real)> const& F, Real t0, Real t1,
|
|
unsigned int maxIterations, Real negFTolerance, Real posFTolerance,
|
|
Real stepTTolerance, Real convTTolerance, Real& root)
|
|
{
|
|
// Parameter validation.
|
|
if (t1 <= t0
|
|
|| maxIterations == 0
|
|
|| negFTolerance > (Real)0
|
|
|| posFTolerance < (Real)0
|
|
|| stepTTolerance < (Real)0
|
|
|| convTTolerance < (Real)0)
|
|
{
|
|
// The input is invalid.
|
|
return false;
|
|
}
|
|
|
|
Real f0 = F(t0);
|
|
if (negFTolerance <= f0 && f0 <= posFTolerance)
|
|
{
|
|
// This endpoint is an approximate root that satisfies the
|
|
// function tolerance.
|
|
root = t0;
|
|
return true;
|
|
}
|
|
|
|
Real f1 = F(t1);
|
|
if (negFTolerance <= f1 && f1 <= posFTolerance)
|
|
{
|
|
// This endpoint is an approximate root that satisfies the
|
|
// function tolerance.
|
|
root = t1;
|
|
return true;
|
|
}
|
|
|
|
if (f0 * f1 > (Real)0)
|
|
{
|
|
// The input interval must bound a root.
|
|
return false;
|
|
}
|
|
|
|
if (std::fabs(f0) < std::fabs(f1))
|
|
{
|
|
// Swap t0 and t1 so that |F(t1)| <= |F(t0)|. The number t1
|
|
// is considered to be the best estimate of the root.
|
|
std::swap(t0, t1);
|
|
std::swap(f0, f1);
|
|
}
|
|
|
|
// Initialize values for the root search.
|
|
Real t2 = t0, t3 = t0, f2 = f0;
|
|
bool prevBisected = true;
|
|
|
|
// The root search.
|
|
for (unsigned int i = 0; i < maxIterations; ++i)
|
|
{
|
|
Real fDiff01 = f0 - f1, fDiff02 = f0 - f2, fDiff12 = f1 - f2;
|
|
Real invFDiff01 = ((Real)1) / fDiff01;
|
|
Real s;
|
|
if (fDiff02 != (Real)0 && fDiff12 != (Real)0)
|
|
{
|
|
// Use inverse quadratic interpolation.
|
|
Real infFDiff02 = ((Real)1) / fDiff02;
|
|
Real invFDiff12 = ((Real)1) / fDiff12;
|
|
s =
|
|
t0 * f1 * f2 * invFDiff01 * infFDiff02 -
|
|
t1 * f0 * f2 * invFDiff01 * invFDiff12 +
|
|
t2 * f0 * f1 * infFDiff02 * invFDiff12;
|
|
}
|
|
else
|
|
{
|
|
// Use inverse linear interpolation (secant method).
|
|
s = (t1 * f0 - t0 * f1) * invFDiff01;
|
|
}
|
|
|
|
// Compute values need in the accept-or-reject tests.
|
|
Real tDiffSAvr = s - ((Real)0.75) * t0 - ((Real)0.25) * t1;
|
|
Real tDiffS1 = s - t1;
|
|
Real absTDiffS1 = std::fabs(tDiffS1);
|
|
Real absTDiff12 = std::fabs(t1 - t2);
|
|
Real absTDiff23 = std::fabs(t2 - t3);
|
|
|
|
bool currBisected = false;
|
|
if (tDiffSAvr * tDiffS1 > (Real)0)
|
|
{
|
|
// The value s is not between 0.75*t0 + 0.25*t1 and t1.
|
|
// NOTE: The algorithm sometimes has t0 < t1 but sometimes
|
|
// t1 < t0, so the between-ness test does not use simple
|
|
// comparisons.
|
|
currBisected = true;
|
|
}
|
|
else if (prevBisected)
|
|
{
|
|
// The first of Brent's tests to determine whether to
|
|
// accept the interpolated s-value.
|
|
currBisected =
|
|
(absTDiffS1 >= ((Real)0.5) * absTDiff12) ||
|
|
(absTDiff12 <= stepTTolerance);
|
|
}
|
|
else
|
|
{
|
|
// The second of Brent's tests to determine whether to
|
|
// accept the interpolated s-value.
|
|
currBisected =
|
|
(absTDiffS1 >= ((Real)0.5) * absTDiff23) ||
|
|
(absTDiff23 <= stepTTolerance);
|
|
}
|
|
|
|
if (currBisected)
|
|
{
|
|
// One of the additional tests failed, so reject the
|
|
// interpolated s-value and use bisection instead.
|
|
s = ((Real)0.5) * (t0 + t1);
|
|
if (s == t0 || s == t1)
|
|
{
|
|
// The numbers t0 and t1 are consecutive
|
|
// floating-point numbers.
|
|
root = s;
|
|
return true;
|
|
}
|
|
prevBisected = true;
|
|
}
|
|
else
|
|
{
|
|
prevBisected = false;
|
|
}
|
|
|
|
// Evaluate the function at the new estimate and test for
|
|
// convergence.
|
|
Real fs = F(s);
|
|
if (negFTolerance <= fs && fs <= posFTolerance)
|
|
{
|
|
root = s;
|
|
return true;
|
|
}
|
|
|
|
// Update the subinterval to include the new estimate as an
|
|
// endpoint.
|
|
t3 = t2;
|
|
t2 = t1;
|
|
f2 = f1;
|
|
if (f0 * fs < (Real)0)
|
|
{
|
|
t1 = s;
|
|
f1 = fs;
|
|
}
|
|
else
|
|
{
|
|
t0 = s;
|
|
f0 = fs;
|
|
}
|
|
|
|
// Allow the algorithm to terminate when the subinterval is
|
|
// sufficiently small.
|
|
if (std::fabs(t1 - t0) <= convTTolerance)
|
|
{
|
|
root = t1;
|
|
return true;
|
|
}
|
|
|
|
// A loop invariant is that t1 is the root estimate,
|
|
// F(t0)*F(t1) < 0 and |F(t1)| <= |F(t0)|.
|
|
if (std::fabs(f0) < std::fabs(f1))
|
|
{
|
|
std::swap(t0, t1);
|
|
std::swap(f0, f1);
|
|
}
|
|
}
|
|
|
|
// Failed to converge in the specified number of iterations.
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
|