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.
530 lines
17 KiB
530 lines
17 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.8.2020.08.11
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Logger.h>
|
|
#include <Mathematics/Math.h>
|
|
#include <Mathematics/Polynomial1.h>
|
|
#include <functional>
|
|
|
|
namespace gte
|
|
{
|
|
template <typename T>
|
|
class RemezAlgorithm
|
|
{
|
|
public:
|
|
using Function = std::function<T(T const&)>;
|
|
|
|
RemezAlgorithm()
|
|
:
|
|
mF{},
|
|
mFDer{},
|
|
mXMin(0),
|
|
mXMax(0),
|
|
mDegree(0),
|
|
mMaxRemezIterations(0),
|
|
mMaxBisectionIterations(0),
|
|
mMaxBracketIterations(0),
|
|
mPCoefficients{},
|
|
mEstimatedMaxError(0),
|
|
mXNodes{},
|
|
mErrors{},
|
|
mFValues{},
|
|
mUCoefficients{},
|
|
mVCoefficients{},
|
|
mPartition{}
|
|
{
|
|
}
|
|
|
|
size_t Execute(Function const& F, Function const& FDer, T const& xMin,
|
|
T const& xMax, size_t degree, size_t maxRemezIterations,
|
|
size_t maxBisectionIterations, size_t maxBracketIterations)
|
|
{
|
|
LogAssert(xMin < xMax&& degree > 0 && maxRemezIterations > 0
|
|
&& maxBisectionIterations > 0 && maxBracketIterations,
|
|
"Invalid argument.");
|
|
|
|
mF = F;
|
|
mFDer = FDer;
|
|
mXMin = xMin;
|
|
mXMax = xMax;
|
|
mDegree = degree;
|
|
mMaxRemezIterations = maxRemezIterations;
|
|
mMaxBisectionIterations = maxBisectionIterations;
|
|
mMaxBracketIterations = maxBracketIterations;
|
|
|
|
mPCoefficients.resize(mDegree + 1);
|
|
mEstimatedMaxError = static_cast<T>(0);
|
|
mXNodes.resize(mDegree + 2);
|
|
mErrors.resize(mDegree + 2);
|
|
|
|
mFValues.resize(mDegree + 2);
|
|
mUCoefficients.resize(mDegree + 1);
|
|
mVCoefficients.resize(mDegree + 1);
|
|
mEstimatedMaxError = static_cast<T>(0);
|
|
mPartition.resize(mDegree + 3);
|
|
|
|
ComputeInitialXNodes();
|
|
size_t iteration;
|
|
for (iteration = 0; iteration < mMaxRemezIterations; ++iteration)
|
|
{
|
|
ComputeFAtXNodes();
|
|
ComputeUCoefficients();
|
|
ComputeVCoefficients();
|
|
ComputeEstimatedError();
|
|
ComputePCoefficients();
|
|
if (IsOscillatory())
|
|
{
|
|
ComputePartition();
|
|
ComputeXExtremes();
|
|
}
|
|
else
|
|
{
|
|
iteration = std::numeric_limits<size_t>::max();
|
|
break;
|
|
}
|
|
}
|
|
return iteration;
|
|
}
|
|
|
|
// The output of the algorithm.
|
|
inline std::vector<T> const& GetCoefficients() const
|
|
{
|
|
return mPCoefficients;
|
|
}
|
|
|
|
inline T GetEstimatedMaxError() const
|
|
{
|
|
return mEstimatedMaxError;
|
|
}
|
|
|
|
inline std::vector<T> const& GetXNodes() const
|
|
{
|
|
return mXNodes;
|
|
}
|
|
|
|
inline std::vector<T> const& GetErrors() const
|
|
{
|
|
return mErrors;
|
|
}
|
|
|
|
private:
|
|
void ComputeInitialXNodes()
|
|
{
|
|
// Get the Chebyshev nodes for the interval [-1,1].
|
|
size_t const numNodes = mXNodes.size();
|
|
T const halfPiDivN = static_cast<T>(GTE_C_HALF_PI / numNodes);
|
|
std::vector<T> cosAngles(numNodes);
|
|
for (size_t i = 0, j = 2 * numNodes - 1; i < numNodes; ++i, j -= 2)
|
|
{
|
|
T angle = static_cast<T>(j) * halfPiDivN;
|
|
cosAngles[i] = std::cos(angle);
|
|
}
|
|
if (numNodes & 1)
|
|
{
|
|
// Avoid the rounding errors when the angle is pi/2, where
|
|
// cos(pi/2) is theoretically zero.
|
|
cosAngles[numNodes / 2] = static_cast<T>(0);
|
|
}
|
|
|
|
// Transform the nodes to the interval [xMin, xMax].
|
|
T const half(0.5);
|
|
T const center = half * (mXMax + mXMin);
|
|
T const radius = half * (mXMax - mXMin);
|
|
for (size_t i = 0; i < mXNodes.size(); ++i)
|
|
{
|
|
mXNodes[i] = center + radius * cosAngles[i];
|
|
}
|
|
}
|
|
|
|
void ComputeFAtXNodes()
|
|
{
|
|
for (size_t i = 0; i < mXNodes.size(); ++i)
|
|
{
|
|
mFValues[i] = mF(mXNodes[i]);
|
|
}
|
|
}
|
|
|
|
// Compute polynomial u(x) for which u(x[i]) = F(x[i]).
|
|
void ComputeUCoefficients()
|
|
{
|
|
for (size_t i = 0; i < mUCoefficients.size(); ++i)
|
|
{
|
|
mUCoefficients[i] = mFValues[i];
|
|
for (size_t j = 0; j < i; ++j)
|
|
{
|
|
mUCoefficients[i] -= mUCoefficients[j];
|
|
mUCoefficients[i] /= mXNodes[i] - mXNodes[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute polynomial v(x) for which v(x[i]) = (-1)^i.
|
|
void ComputeVCoefficients()
|
|
{
|
|
T sign(1);
|
|
for (size_t i = 0; i < mVCoefficients.size(); ++i)
|
|
{
|
|
mVCoefficients[i] = sign;
|
|
for (size_t j = 0; j < i; ++j)
|
|
{
|
|
mVCoefficients[i] -= mVCoefficients[j];
|
|
mVCoefficients[i] /= mXNodes[i] - mXNodes[j];
|
|
}
|
|
sign = -sign;
|
|
}
|
|
}
|
|
|
|
void ComputeEstimatedError()
|
|
{
|
|
T const powNegOne = static_cast<T>((mDegree & 1) ? -1 : +1);
|
|
T const& xBack = mXNodes.back();
|
|
T const& fBack = mFValues.back();
|
|
T uBack = EvaluateU(xBack);
|
|
T vBack = EvaluateV(xBack);
|
|
mEstimatedMaxError = (uBack - fBack) / (vBack + powNegOne);
|
|
}
|
|
|
|
void ComputePCoefficients()
|
|
{
|
|
// Compute the P-polynomial symbolically as a Newton polynomial
|
|
// in order to obtain the coefficients from the t-powers.
|
|
size_t const numCoefficients = mUCoefficients.size();
|
|
std::vector<T> constant(numCoefficients);
|
|
for (size_t i = 0; i < numCoefficients; ++i)
|
|
{
|
|
constant[i] = mUCoefficients[i] - mEstimatedMaxError * mVCoefficients[i];
|
|
}
|
|
|
|
T const one(1);
|
|
size_t index = numCoefficients - 1;
|
|
Polynomial1<T> poly{ constant[index--] };
|
|
for (size_t i = 1; i < numCoefficients; ++i, --index)
|
|
{
|
|
Polynomial1<T> linear{ -mXNodes[index], one };
|
|
poly = constant[index] + linear * poly;
|
|
}
|
|
|
|
for (size_t i = 0; i < numCoefficients; ++i)
|
|
{
|
|
mPCoefficients[i] = poly[static_cast<uint32_t>(i)];
|
|
}
|
|
}
|
|
|
|
bool IsOscillatory()
|
|
{
|
|
// Compute the errors |F(x)-P(x)| for the current nodes and
|
|
// verify they are oscillatory.
|
|
for (size_t i = 0; i < mXNodes.size(); ++i)
|
|
{
|
|
mErrors[i] = mF(mXNodes[i]) - EvaluateP(mXNodes[i]);
|
|
}
|
|
|
|
T const zero(0);
|
|
for (size_t i0 = 0, i1 = 1; i1 < mXNodes.size(); i0 = i1++)
|
|
{
|
|
if ((mErrors[i0] > zero && mErrors[i1] > zero) ||
|
|
(mErrors[i0] < zero && mErrors[i1] < zero))
|
|
{
|
|
// The process terminates when the errors are not
|
|
// oscillatory.
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ComputePartition()
|
|
{
|
|
// Define E(x) = F(x) - P(x). Use bisection to compute the roots
|
|
// of E(x). The algorithm partitions [xMin, xMax] into degree+2
|
|
// subintervals, each subinterval with E(x) positive or with E(x)
|
|
// negative. Later, the local extrema on the subintervals are
|
|
// computed using a quadratic-fit line-search algorithm. The
|
|
// extreme locations become the next set of x-nodes.
|
|
T const zero(0), half(0.5);
|
|
mPartition.front() = mXMin;
|
|
mPartition.back() = mXMax;
|
|
for (size_t i0 = 0, i1 = 1; i1 < mXNodes.size(); i0 = i1++)
|
|
{
|
|
T x0 = mXNodes[i0], x1 = mXNodes[i1], xMid(0), eMid(0);
|
|
int32_t sign0 = (mErrors[i0] > zero ? 1 : -1);
|
|
int32_t sign1 = (mErrors[i1] > zero ? 1 : -1);
|
|
int32_t signMid;
|
|
|
|
size_t iteration;
|
|
for (iteration = 0; iteration < mMaxBisectionIterations; ++iteration)
|
|
{
|
|
xMid = half * (x0 + x1);
|
|
if (xMid == x0 || xMid == x1)
|
|
{
|
|
// We are at the limit of floating-point precision for
|
|
// the average of endpoints.
|
|
break;
|
|
}
|
|
|
|
// Update the correct endpoint to the midpoint.
|
|
eMid = mF(xMid) - EvaluateP(xMid);
|
|
signMid = (eMid > zero ? 1 : (eMid < zero ? -1 : 0));
|
|
if (signMid == sign0)
|
|
{
|
|
x0 = xMid;
|
|
}
|
|
else if (signMid == sign1)
|
|
{
|
|
x1 = xMid;
|
|
}
|
|
else
|
|
{
|
|
// Found a root (numerically rounded to zero).
|
|
break;
|
|
}
|
|
}
|
|
|
|
// It is possible that the maximum number of bisections was
|
|
// applied without convergence. Use the last computed xMid
|
|
// as the root.
|
|
mPartition[i1] = xMid;
|
|
}
|
|
}
|
|
|
|
// Use a quadratic-fit line-search (QFLS) to find the local extrema.
|
|
void ComputeXExtremes()
|
|
{
|
|
T const zero(0), half(0.5);
|
|
T const w0 = static_cast<T>(0.6);
|
|
T const w1 = static_cast<T>(0.4);
|
|
T x0, xm, x1, e0, em, e1;
|
|
std::vector<T> nextXNodes(mXNodes.size());
|
|
|
|
// The interval [xMin,mPartition[1]] might not have an interior
|
|
// local extrema. If the QFLS does not produce a bracket, use
|
|
// the average (x0+x1)/2 as the local extreme location.
|
|
x0 = mPartition[0];
|
|
x1 = mPartition[1];
|
|
xm = w0 * x0 + w1 * x1;
|
|
e0 = -std::fabs(mF(x0) - EvaluateP(x0));
|
|
e1 = zero;
|
|
em = -std::fabs(mF(xm) - EvaluateP(xm));
|
|
if (em < e0 && em < e1)
|
|
{
|
|
nextXNodes[0] = GetXExtreme(x0, e0, xm, em, x1, e1);
|
|
}
|
|
else
|
|
{
|
|
nextXNodes[0] = half * (x0 + x1);
|
|
}
|
|
|
|
// These subintervals have interior local extrema.
|
|
for (size_t i0 = 1, i1 = 2; i0 < mDegree + 1; i0 = i1++)
|
|
{
|
|
nextXNodes[i0] = GetXExtreme(mPartition[i0], mPartition[i1]);
|
|
}
|
|
|
|
// The interval [mPartition[deg+1],xMax] might not have an
|
|
// interior local extrema. If the QFLS does not produce a
|
|
// bracket, use xMax as the local extreme location.
|
|
x0 = mPartition[mDegree + 1];
|
|
x1 = mPartition[mDegree + 2];
|
|
xm = w0 * x0 + w1 * x1;
|
|
e0 = zero;
|
|
e1 = -std::fabs(mF(x1) - EvaluateP(x1));
|
|
em = -std::fabs(mF(xm) - EvaluateP(xm));
|
|
if (em < e0 && em < e1)
|
|
{
|
|
nextXNodes[mDegree + 1] = GetXExtreme(x0, e0, xm, em, x1, e1);
|
|
}
|
|
else
|
|
{
|
|
nextXNodes[mDegree + 1] = half * (x0 + x1);
|
|
}
|
|
|
|
mXNodes = nextXNodes;
|
|
}
|
|
|
|
// Let E(x) = -|F(x) - P(x)|. This function is called when
|
|
// {(x0,E(x0)), (xm,E(xm)), (x1,E(x1)} brackets a minimum of E(x).
|
|
T GetXExtreme(T x0, T e0, T xm, T em, T x1, T e1)
|
|
{
|
|
T const zero(0), half(0.5);
|
|
for (size_t iteration = 0; iteration < mMaxBracketIterations; ++iteration)
|
|
{
|
|
// Compute the vertex of the interpolating parabola.
|
|
T difXmX1 = xm - x0;
|
|
T difX1X0 = x1 - x0;
|
|
T difX0Xm = x0 - xm;
|
|
T sumXmX1 = xm + x1;
|
|
T sumX1X0 = x1 + x0;
|
|
T sumX0Xm = x0 + xm;
|
|
T tmp0 = difXmX1 * e0;
|
|
T tmpm = difX1X0 * em;
|
|
T tmp1 = difX0Xm * e1;
|
|
T numer = sumXmX1 * tmp0 + sumX1X0 * tmpm + sumX0Xm * tmp1;
|
|
T denom = tmp0 + tmpm + tmp1;
|
|
if (denom == zero)
|
|
{
|
|
return xm;
|
|
}
|
|
|
|
T xv = half * numer / denom;
|
|
if (xv <= x0 || xv >= x1)
|
|
{
|
|
return xv;
|
|
}
|
|
|
|
T ev = -std::fabs(mF(xv) - EvaluateP(xv));
|
|
if (xv < xm)
|
|
{
|
|
if (ev < em)
|
|
{
|
|
x1 = xm;
|
|
e1 = em;
|
|
xm = xv;
|
|
em = ev;
|
|
}
|
|
else
|
|
{
|
|
x0 = xv;
|
|
e0 = ev;
|
|
}
|
|
}
|
|
else if (xv > xm)
|
|
{
|
|
if (ev < em)
|
|
{
|
|
x0 = xm;
|
|
e0 = em;
|
|
xm = xv;
|
|
em = ev;
|
|
}
|
|
else
|
|
{
|
|
x1 = xv;
|
|
e1 = ev;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return xv;
|
|
}
|
|
}
|
|
|
|
return xm;
|
|
}
|
|
|
|
T GetXExtreme(T x0, T x1)
|
|
{
|
|
T const zero(0);
|
|
T eder0 = mFDer(x0) - EvaluatePDer(x0);
|
|
T eder1 = mFDer(x1) - EvaluatePDer(x1);
|
|
int32_t signEDer0 = (eder0 > zero ? 1 : (eder0 < zero ? -1 : 0));
|
|
int32_t signEDer1 = (eder1 > zero ? 1 : (eder1 < zero ? -1 : 0));
|
|
LogAssert(signEDer0 * signEDer1 == -1, "Opposite signs required.");
|
|
|
|
T const half(0.5);
|
|
T xmid(0), ederMid(0);
|
|
int32_t signEMid = 0;
|
|
for (size_t i = 0; i < mMaxBisectionIterations; ++i)
|
|
{
|
|
xmid = half * (x0 + x1);
|
|
if (xmid == x0 || xmid == x1)
|
|
{
|
|
return xmid;
|
|
}
|
|
|
|
ederMid = mFDer(xmid) - EvaluatePDer(xmid);
|
|
signEMid = (ederMid > zero ? 1 : (ederMid < zero ? -1 : 0));
|
|
if (signEMid == signEDer0)
|
|
{
|
|
x0 = xmid;
|
|
}
|
|
else if (signEMid == signEDer1)
|
|
{
|
|
x1 = xmid;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return xmid;
|
|
}
|
|
|
|
// Evaluate u(x) =
|
|
// u[0]+(x-xn[0])*(u[1]+(x-xn[1])*(u[2]+...+(x-xn[n-1])*u[n-1]))
|
|
T EvaluateU(T const& x)
|
|
{
|
|
size_t index = mUCoefficients.size() - 1;
|
|
T result = mUCoefficients[index--];
|
|
for (size_t i = 1; i < mUCoefficients.size(); ++i, --index)
|
|
{
|
|
result = mUCoefficients[index] + (x - mXNodes[index]) * result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Evaluate v(x) =
|
|
// v[0]+(x-xn[0])*(v[1]+(x-xn[1])*(v[2]+...+(x-xn[n-1])*v[n-1]))
|
|
T EvaluateV(T const& x)
|
|
{
|
|
size_t index = mVCoefficients.size() - 1;
|
|
T result = mVCoefficients[index--];
|
|
for (size_t i = 1; i < mVCoefficients.size(); ++i, --index)
|
|
{
|
|
result = mVCoefficients[index] + (x - mXNodes[index]) * result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Evaluate p(x) = sum_{i=0}^{n} p[i] * x^i.
|
|
T EvaluateP(T const& x)
|
|
{
|
|
size_t index = mPCoefficients.size() - 1;
|
|
T result = mPCoefficients[index--];
|
|
for (size_t i = 1; i < mPCoefficients.size(); ++i, --index)
|
|
{
|
|
result = mPCoefficients[index] + x * result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
T EvaluatePDer(T const& x)
|
|
{
|
|
size_t index = mPCoefficients.size() - 1;
|
|
T result = static_cast<T>(index) * mPCoefficients[index];
|
|
--index;
|
|
for (size_t i = 2; i < mPCoefficients.size(); ++i, --index)
|
|
{
|
|
result = static_cast<T>(index) * mPCoefficients[index] + x * result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Inputs to Execute(...).
|
|
Function mF;
|
|
Function mFDer;
|
|
T mXMin;
|
|
T mXMax;
|
|
size_t mDegree;
|
|
size_t mMaxRemezIterations;
|
|
size_t mMaxBisectionIterations;
|
|
size_t mMaxBracketIterations;
|
|
|
|
// Outputs from Execute(...).
|
|
std::vector<T> mPCoefficients;
|
|
T mEstimatedMaxError;
|
|
std::vector<T> mXNodes;
|
|
std::vector<T> mErrors;
|
|
|
|
// Members used in the intermediate computations.
|
|
std::vector<T> mFValues;
|
|
std::vector<T> mUCoefficients;
|
|
std::vector<T> mVCoefficients;
|
|
std::vector<T> mPartition;
|
|
};
|
|
}
|
|
|