@@ -31,11 +31,11 @@ extern void DumpStack(); | |||
* on implementing __debugbreak() on POSIX systems. */ | |||
static inline void DebugAbort() | |||
{ | |||
DumpStack(); | |||
lol::DumpStack(); | |||
#if defined _WIN32 | |||
__debugbreak(); | |||
#endif | |||
Abort(); | |||
lol::Abort(); | |||
} | |||
#define LOL_CALL(macro, args) macro args | |||
@@ -111,13 +111,13 @@ static inline void DebugAbort() | |||
*/ | |||
#define LOL_ERROR_1(t) \ | |||
Log::Error("assertion at %s:%d: %s\n", __FILE__, __LINE__, #t) | |||
lol::Log::Error("assertion at %s:%d: %s\n", __FILE__, __LINE__, #t) | |||
#define LOL_ERROR_2(t, s) \ | |||
Log::Error("assertion at %s:%d: %s\n", __FILE__, __LINE__, s) | |||
lol::Log::Error("assertion at %s:%d: %s\n", __FILE__, __LINE__, s) | |||
#define LOL_ERROR_3(t, s, ...) \ | |||
Log::Error("assertion at %s:%d: " s "\n", __FILE__, __LINE__, __VA_ARGS__) | |||
lol::Log::Error("assertion at %s:%d: " s "\n", __FILE__, __LINE__, __VA_ARGS__) | |||
#if FINAL_RELEASE | |||
# define ASSERT(...) UNUSED(LOL_CALL(LOL_1ST, (__VA_ARGS__))) | |||
@@ -128,7 +128,7 @@ static inline void DebugAbort() | |||
LOL_CALL(LOL_CAT(LOL_ERROR_, LOL_CALL(LOL_COUNT_TO_3, \ | |||
(__VA_ARGS__))), \ | |||
(__VA_ARGS__)); \ | |||
DebugAbort(); \ | |||
lol::DebugAbort(); \ | |||
} | |||
#endif | |||
@@ -1918,81 +1918,6 @@ template<typename T> Mat2<T> inverse(Mat2<T> const &); | |||
template<typename T> Mat3<T> inverse(Mat3<T> const &); | |||
template<typename T> Mat4<T> inverse(Mat4<T> const &); | |||
/* | |||
* Arbitrarily-sized square matrices; for now this only supports | |||
* naive inversion and is used for the Remez inversion method. | |||
*/ | |||
template<int N, typename T> struct Mat | |||
{ | |||
inline Mat<N, T>() {} | |||
Mat(T x) | |||
{ | |||
for (int j = 0; j < N; j++) | |||
for (int i = 0; i < N; i++) | |||
if (i == j) | |||
m[i][j] = x; | |||
else | |||
m[i][j] = 0; | |||
} | |||
/* Naive matrix inversion */ | |||
Mat<N, T> inv() const | |||
{ | |||
Mat a = *this, b((T)1); | |||
/* Inversion method: iterate through all columns and make sure | |||
* all the terms are 1 on the diagonal and 0 everywhere else */ | |||
for (int i = 0; i < N; i++) | |||
{ | |||
/* If the expected coefficient is zero, add one of | |||
* the other lines. The first we meet will do. */ | |||
if (!a.m[i][i]) | |||
{ | |||
for (int j = i + 1; j < N; j++) | |||
{ | |||
if (!a.m[i][j]) | |||
continue; | |||
/* Add row j to row i */ | |||
for (int n = 0; n < N; n++) | |||
{ | |||
a.m[n][i] += a.m[n][j]; | |||
b.m[n][i] += b.m[n][j]; | |||
} | |||
break; | |||
} | |||
} | |||
/* Now we know the diagonal term is non-zero. Get its inverse | |||
* and use that to nullify all other terms in the column */ | |||
T x = (T)1 / a.m[i][i]; | |||
for (int j = 0; j < N; j++) | |||
{ | |||
if (j == i) | |||
continue; | |||
T mul = x * a.m[i][j]; | |||
for (int n = 0; n < N; n++) | |||
{ | |||
a.m[n][j] -= mul * a.m[n][i]; | |||
b.m[n][j] -= mul * b.m[n][i]; | |||
} | |||
} | |||
/* Finally, ensure the diagonal term is 1 */ | |||
for (int n = 0; n < N; n++) | |||
{ | |||
a.m[n][i] *= x; | |||
b.m[n][i] *= x; | |||
} | |||
} | |||
return b; | |||
} | |||
T m[N][N]; | |||
}; | |||
} /* namespace lol */ | |||
#endif // __LOL_MATH_VECTOR_H__ | |||
@@ -5,7 +5,8 @@ EXTRA_DIST += NEWS.txt lolremez.sln remez.vcxproj remez.vcxproj.filters | |||
noinst_PROGRAMS = lolremez | |||
lolremez_SOURCES = lolremez.cpp | |||
lolremez_SOURCES = \ | |||
lolremez.cpp solver.cpp solver.h matrix.h | |||
lolremez_CPPFLAGS = $(AM_CPPFLAGS) | |||
lolremez_DEPENDENCIES = @LOL_DEPS@ | |||
@@ -1,5 +1,5 @@ | |||
// | |||
// Lol Engine - Sample math program: Chebyshev polynomials | |||
// LolRemez - Remez algorithm implementation | |||
// | |||
// Copyright: (c) 2005-2013 Sam Hocevar <sam@hocevar.net> | |||
// This program is free software; you can redistribute it and/or | |||
@@ -16,10 +16,9 @@ | |||
#include <lol/math/real.h> | |||
#include "lolremez.h" | |||
#include "solver.h" | |||
using lol::real; | |||
using lol::RemezSolver; | |||
/* See the tutorial at http://lolengine.net/wiki/doc/maths/remez */ | |||
real f(real const &x) | |||
@@ -36,8 +35,9 @@ int main(int argc, char **argv) | |||
{ | |||
UNUSED(argc, argv); | |||
RemezSolver<4, real> solver; | |||
solver.Run(-1, 1, f, g, 40); | |||
RemezSolver<real> solver; | |||
solver.Run(4, 40, -1, 1, f, g); | |||
return 0; | |||
} | |||
@@ -1,376 +0,0 @@ | |||
// | |||
// Lol Engine | |||
// | |||
// Copyright: (c) 2010-2011 Sam Hocevar <sam@hocevar.net> | |||
// This program is free software; you can redistribute it and/or | |||
// modify it under the terms of the Do What The Fuck You Want To | |||
// Public License, Version 2, as published by Sam Hocevar. See | |||
// http://www.wtfpl.net/ for more details. | |||
// | |||
// | |||
// The RemezSolver class | |||
// --------------------- | |||
// | |||
#if !defined __LOL_MATH_REMEZ_H__ | |||
#define __LOL_MATH_REMEZ_H__ | |||
#include <cstdio> | |||
#include "lol/math/vector.h" | |||
namespace lol | |||
{ | |||
template<int ORDER, typename T> class RemezSolver | |||
{ | |||
public: | |||
typedef T RealFunc(T const &x); | |||
RemezSolver() | |||
{ | |||
} | |||
void Run(T a, T b, RealFunc *func, RealFunc *weight, int decimals) | |||
{ | |||
using std::printf; | |||
m_func = func; | |||
m_weight = weight; | |||
m_k1 = (b + a) / 2; | |||
m_k2 = (b - a) / 2; | |||
m_invk2 = re(m_k2); | |||
m_invk1 = -m_k1 * m_invk2; | |||
m_decimals = decimals; | |||
m_epsilon = pow((T)10, (T)-(decimals + 2)); | |||
Init(); | |||
PrintPoly(); | |||
T error = -1; | |||
for (int n = 0; ; n++) | |||
{ | |||
T newerror = FindExtrema(); | |||
printf("Step %i error: ", n); | |||
newerror.print(m_decimals); | |||
printf("\n"); | |||
Step(); | |||
if (error >= (T)0 && fabs(newerror - error) < error * m_epsilon) | |||
break; | |||
error = newerror; | |||
PrintPoly(); | |||
FindZeroes(); | |||
} | |||
PrintPoly(); | |||
} | |||
inline void Run(T a, T b, RealFunc *func, int decimals) | |||
{ | |||
Run(a, b, func, nullptr, decimals); | |||
} | |||
T EvalCheby(T const &x) | |||
{ | |||
T ret = 0.0, xn = 1.0; | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
T mul = 0; | |||
for (int j = 0; j < ORDER + 1; j++) | |||
mul += coeff[j] * (T)Cheby(j, i); | |||
ret += mul * xn; | |||
xn *= x; | |||
} | |||
return ret; | |||
} | |||
void Init() | |||
{ | |||
/* Pick up x_i where error will be 0 and compute f(x_i) */ | |||
T fxn[ORDER + 1]; | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
zeroes[i] = (T)(2 * i - ORDER) / (T)(ORDER + 1); | |||
fxn[i] = EvalFunc(zeroes[i]); | |||
} | |||
/* We build a matrix of Chebishev evaluations: row i contains the | |||
* evaluations of x_i for polynomial order n = 0, 1, ... */ | |||
lol::Mat<ORDER + 1, T> mat; | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
/* Compute the powers of x_i */ | |||
T powers[ORDER + 1]; | |||
powers[0] = 1.0; | |||
for (int n = 1; n < ORDER + 1; n++) | |||
powers[n] = powers[n - 1] * zeroes[i]; | |||
/* Compute the Chebishev evaluations at x_i */ | |||
for (int n = 0; n < ORDER + 1; n++) | |||
{ | |||
T sum = 0.0; | |||
for (int k = 0; k < ORDER + 1; k++) | |||
sum += (T)Cheby(n, k) * powers[k]; | |||
mat.m[i][n] = sum; | |||
} | |||
} | |||
/* Solve the system */ | |||
mat = mat.inv(); | |||
/* Compute interpolation coefficients */ | |||
for (int j = 0; j < ORDER + 1; j++) | |||
{ | |||
coeff[j] = 0; | |||
for (int i = 0; i < ORDER + 1; i++) | |||
coeff[j] += mat.m[j][i] * fxn[i]; | |||
} | |||
} | |||
void FindZeroes() | |||
{ | |||
/* Find ORDER + 1 zeroes of the error function. No need to | |||
* compute the relative error: its zeroes are at the same | |||
* place as the absolute error! */ | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
struct { T value, error; } left, right, mid; | |||
left.value = control[i]; | |||
left.error = EvalCheby(left.value) - EvalFunc(left.value); | |||
right.value = control[i + 1]; | |||
right.error = EvalCheby(right.value) - EvalFunc(right.value); | |||
static T limit = ldexp((T)1, -500); | |||
static T zero = (T)0; | |||
while (fabs(left.value - right.value) > limit) | |||
{ | |||
mid.value = (left.value + right.value) / 2; | |||
mid.error = EvalCheby(mid.value) - EvalFunc(mid.value); | |||
if ((left.error <= zero && mid.error <= zero) | |||
|| (left.error >= zero && mid.error >= zero)) | |||
left = mid; | |||
else | |||
right = mid; | |||
} | |||
zeroes[i] = mid.value; | |||
} | |||
} | |||
real FindExtrema() | |||
{ | |||
using std::printf; | |||
/* Find ORDER + 2 extrema of the error function. We need to | |||
* compute the relative error, since its extrema are at slightly | |||
* different locations than the absolute error’s. */ | |||
T final = 0; | |||
for (int i = 0; i < ORDER + 2; i++) | |||
{ | |||
T a = -1, b = 1; | |||
if (i > 0) | |||
a = zeroes[i - 1]; | |||
if (i < ORDER + 1) | |||
b = zeroes[i]; | |||
for (int round = 0; ; round++) | |||
{ | |||
T maxerror = 0, maxweight = 0; | |||
int best = -1; | |||
T c = a, delta = (b - a) / 4; | |||
for (int k = 0; k <= 4; k++) | |||
{ | |||
if (round == 0 || (k & 1)) | |||
{ | |||
T error = fabs(EvalCheby(c) - EvalFunc(c)); | |||
T weight = fabs(Weight(c)); | |||
/* if error/weight >= maxerror/maxweight */ | |||
if (error * maxweight >= maxerror * weight) | |||
{ | |||
maxerror = error; | |||
maxweight = weight; | |||
best = k; | |||
} | |||
} | |||
c += delta; | |||
} | |||
switch (best) | |||
{ | |||
case 0: | |||
b = a + delta * 2; | |||
break; | |||
case 4: | |||
a = b - delta * 2; | |||
break; | |||
default: | |||
b = a + delta * (best + 1); | |||
a = a + delta * (best - 1); | |||
break; | |||
} | |||
if (delta < m_epsilon) | |||
{ | |||
T e = fabs(maxerror / maxweight); | |||
if (e > final) | |||
final = e; | |||
control[i] = (a + b) / 2; | |||
break; | |||
} | |||
} | |||
} | |||
return final; | |||
} | |||
void Step() | |||
{ | |||
/* Pick up x_i where error will be 0 and compute f(x_i) */ | |||
T fxn[ORDER + 2]; | |||
for (int i = 0; i < ORDER + 2; i++) | |||
fxn[i] = EvalFunc(control[i]); | |||
/* We build a matrix of Chebishev evaluations: row i contains the | |||
* evaluations of x_i for polynomial order n = 0, 1, ... */ | |||
lol::Mat<ORDER + 2, T> mat; | |||
for (int i = 0; i < ORDER + 2; i++) | |||
{ | |||
/* Compute the powers of x_i */ | |||
T powers[ORDER + 1]; | |||
powers[0] = 1.0; | |||
for (int n = 1; n < ORDER + 1; n++) | |||
powers[n] = powers[n - 1] * control[i]; | |||
/* Compute the Chebishev evaluations at x_i */ | |||
for (int n = 0; n < ORDER + 1; n++) | |||
{ | |||
T sum = 0.0; | |||
for (int k = 0; k < ORDER + 1; k++) | |||
sum += (T)Cheby(n, k) * powers[k]; | |||
mat.m[i][n] = sum; | |||
} | |||
if (i & 1) | |||
mat.m[i][ORDER + 1] = fabs(Weight(control[i])); | |||
else | |||
mat.m[i][ORDER + 1] = -fabs(Weight(control[i])); | |||
} | |||
/* Solve the system */ | |||
mat = mat.inv(); | |||
/* Compute interpolation coefficients */ | |||
for (int j = 0; j < ORDER + 1; j++) | |||
{ | |||
coeff[j] = 0; | |||
for (int i = 0; i < ORDER + 2; i++) | |||
coeff[j] += mat.m[j][i] * fxn[i]; | |||
} | |||
/* Compute the error */ | |||
T error = 0; | |||
for (int i = 0; i < ORDER + 2; i++) | |||
error += mat.m[ORDER + 1][i] * fxn[i]; | |||
} | |||
int Cheby(int n, int k) | |||
{ | |||
if (k > n || k < 0) | |||
return 0; | |||
if (n <= 1) | |||
return (n ^ k ^ 1) & 1; | |||
return 2 * Cheby(n - 1, k - 1) - Cheby(n - 2, k); | |||
} | |||
int Comb(int n, int k) | |||
{ | |||
if (k == 0 || k == n) | |||
return 1; | |||
return Comb(n - 1, k - 1) + Comb(n - 1, k); | |||
} | |||
void PrintPoly() | |||
{ | |||
using std::printf; | |||
/* Transform Chebyshev polynomial weights into powers of X^i | |||
* in the [-1..1] range. */ | |||
T bn[ORDER + 1]; | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
bn[i] = 0; | |||
for (int j = 0; j < ORDER + 1; j++) | |||
bn[i] += coeff[j] * (T)Cheby(j, i); | |||
} | |||
/* Transform a polynomial in the [-1..1] range into a polynomial | |||
* in the [a..b] range. */ | |||
T k1p[ORDER + 1], k2p[ORDER + 1]; | |||
T an[ORDER + 1]; | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
k1p[i] = i ? k1p[i - 1] * m_invk1 : (T)1; | |||
k2p[i] = i ? k2p[i - 1] * m_invk2 : (T)1; | |||
} | |||
for (int i = 0; i < ORDER + 1; i++) | |||
{ | |||
an[i] = 0; | |||
for (int j = i; j < ORDER + 1; j++) | |||
an[i] += (T)Comb(j, i) * k1p[j - i] * bn[j]; | |||
an[i] *= k2p[i]; | |||
} | |||
printf("Polynomial estimate: "); | |||
for (int j = 0; j < ORDER + 1; j++) | |||
{ | |||
if (j) | |||
printf(" + x**%i * ", j); | |||
an[j].print(m_decimals); | |||
} | |||
printf("\n\n"); | |||
} | |||
T EvalFunc(T const &x) | |||
{ | |||
return m_func(x * m_k2 + m_k1); | |||
} | |||
T Weight(T const &x) | |||
{ | |||
if (m_weight) | |||
return m_weight(x * m_k2 + m_k1); | |||
return 1; | |||
} | |||
/* ORDER + 1 Chebyshev coefficients and 1 error value */ | |||
T coeff[ORDER + 2]; | |||
/* ORDER + 1 zeroes of the error function */ | |||
T zeroes[ORDER + 1]; | |||
/* ORDER + 2 control points */ | |||
T control[ORDER + 2]; | |||
private: | |||
RealFunc *m_func, *m_weight; | |||
T m_k1, m_k2, m_invk1, m_invk2, m_epsilon; | |||
int m_decimals; | |||
}; | |||
} /* namespace lol */ | |||
#endif /* __LOL_MATH_REMEZ_H__ */ | |||
@@ -0,0 +1,377 @@ | |||
// | |||
// LolRemez - Remez algorithm implementation | |||
// | |||
// Copyright: (c) 2005-2013 Sam Hocevar <sam@hocevar.net> | |||
// This program is free software; you can redistribute it and/or | |||
// modify it under the terms of the Do What The Fuck You Want To | |||
// Public License, Version 2, as published by Sam Hocevar. See | |||
// http://www.wtfpl.net/ for more details. | |||
// | |||
#if defined HAVE_CONFIG_H | |||
# include "config.h" | |||
#endif | |||
#include "core.h" | |||
#include <lol/math/real.h> | |||
#include "matrix.h" | |||
#include "solver.h" | |||
using lol::real; | |||
/* Some forward declarations first. */ | |||
template<> void RemezSolver<real>::Init(); | |||
template<> void RemezSolver<real>::FindZeroes(); | |||
template<> real RemezSolver<real>::FindExtrema(); | |||
template<> void RemezSolver<real>::Step(); | |||
template<> int RemezSolver<real>::Cheby(int n, int k); | |||
template<> void RemezSolver<real>::PrintPoly(); | |||
template<> real RemezSolver<real>::EvalFunc(real const &x); | |||
template<> real RemezSolver<real>::Weight(real const &x); | |||
template<> | |||
void RemezSolver<real>::Run(int order, int decimals, real a, real b, | |||
RemezSolver<real>::RealFunc *func, | |||
RemezSolver<real>::RealFunc *weight) | |||
{ | |||
using std::printf; | |||
m_order = order; | |||
m_func = func; | |||
m_weight = weight; | |||
m_k1 = (b + a) / 2; | |||
m_k2 = (b - a) / 2; | |||
m_invk2 = re(m_k2); | |||
m_invk1 = -m_k1 * m_invk2; | |||
m_decimals = decimals; | |||
m_epsilon = pow((real)10, (real)-(decimals + 2)); | |||
Init(); | |||
PrintPoly(); | |||
real error = -1; | |||
for (int n = 0; ; n++) | |||
{ | |||
real newerror = FindExtrema(); | |||
printf("Step %i error: ", n); | |||
newerror.print(m_decimals); | |||
printf("\n"); | |||
Step(); | |||
if (error >= (real)0 && fabs(newerror - error) < error * m_epsilon) | |||
break; | |||
error = newerror; | |||
PrintPoly(); | |||
FindZeroes(); | |||
} | |||
PrintPoly(); | |||
} | |||
template<> | |||
real RemezSolver<real>::EvalCheby(real const &x) | |||
{ | |||
real ret = 0.0, xn = 1.0; | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
real mul = 0; | |||
for (int j = 0; j < m_order + 1; j++) | |||
mul += m_coeff[j] * (real)Cheby(j, i); | |||
ret += mul * xn; | |||
xn *= x; | |||
} | |||
return ret; | |||
} | |||
template<> | |||
void RemezSolver<real>::Init() | |||
{ | |||
/* m_order + 1 Chebyshev coefficients, plus 1 error value */ | |||
m_coeff.Resize(m_order + 2); | |||
/* m_order + 1 zeroes of the error function */ | |||
m_zeroes.Resize(m_order + 1); | |||
/* m_order + 2 control points */ | |||
m_control.Resize(m_order + 2); | |||
/* Pick up x_i where error will be 0 and compute f(x_i) */ | |||
real fxn[m_order + 1]; | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
m_zeroes[i] = (real)(2 * i - m_order) / (real)(m_order + 1); | |||
fxn[i] = EvalFunc(m_zeroes[i]); | |||
} | |||
/* We build a matrix of Chebishev evaluations: row i contains the | |||
* evaluations of x_i for polynomial order n = 0, 1, ... */ | |||
Matrix<real> mat(m_order + 1, m_order + 1); | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
/* Compute the powers of x_i */ | |||
real powers[m_order + 1]; | |||
powers[0] = 1.0; | |||
for (int n = 1; n < m_order + 1; n++) | |||
powers[n] = powers[n - 1] * m_zeroes[i]; | |||
/* Compute the Chebishev evaluations at x_i */ | |||
for (int n = 0; n < m_order + 1; n++) | |||
{ | |||
real sum = 0.0; | |||
for (int k = 0; k < m_order + 1; k++) | |||
sum += (real)Cheby(n, k) * powers[k]; | |||
mat.m(i, n) = sum; | |||
} | |||
} | |||
/* Solve the system */ | |||
mat = mat.inv(); | |||
/* Compute interpolation coefficients */ | |||
for (int j = 0; j < m_order + 1; j++) | |||
{ | |||
m_coeff[j] = 0; | |||
for (int i = 0; i < m_order + 1; i++) | |||
m_coeff[j] += mat.m(j, i) * fxn[i]; | |||
} | |||
} | |||
template<> | |||
void RemezSolver<real>::FindZeroes() | |||
{ | |||
/* Find m_order + 1 zeroes of the error function. No need to | |||
* compute the relative error: its zeroes are at the same | |||
* place as the absolute error! */ | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
struct { real value, error; } left, right, mid; | |||
left.value = m_control[i]; | |||
left.error = EvalCheby(left.value) - EvalFunc(left.value); | |||
right.value = m_control[i + 1]; | |||
right.error = EvalCheby(right.value) - EvalFunc(right.value); | |||
static real limit = ldexp((real)1, -500); | |||
static real zero = (real)0; | |||
while (fabs(left.value - right.value) > limit) | |||
{ | |||
mid.value = (left.value + right.value) / 2; | |||
mid.error = EvalCheby(mid.value) - EvalFunc(mid.value); | |||
if ((left.error <= zero && mid.error <= zero) | |||
|| (left.error >= zero && mid.error >= zero)) | |||
left = mid; | |||
else | |||
right = mid; | |||
} | |||
m_zeroes[i] = mid.value; | |||
} | |||
} | |||
template<> | |||
real RemezSolver<real>::FindExtrema() | |||
{ | |||
using std::printf; | |||
/* Find m_order + 2 extrema of the error function. We need to | |||
* compute the relative error, since its extrema are at slightly | |||
* different locations than the absolute error’s. */ | |||
real final = 0; | |||
for (int i = 0; i < m_order + 2; i++) | |||
{ | |||
real a = -1, b = 1; | |||
if (i > 0) | |||
a = m_zeroes[i - 1]; | |||
if (i < m_order + 1) | |||
b = m_zeroes[i]; | |||
for (int round = 0; ; round++) | |||
{ | |||
real maxerror = 0, maxweight = 0; | |||
int best = -1; | |||
real c = a, delta = (b - a) / 4; | |||
for (int k = 0; k <= 4; k++) | |||
{ | |||
if (round == 0 || (k & 1)) | |||
{ | |||
real error = fabs(EvalCheby(c) - EvalFunc(c)); | |||
real weight = fabs(Weight(c)); | |||
/* if error/weight >= maxerror/maxweight */ | |||
if (error * maxweight >= maxerror * weight) | |||
{ | |||
maxerror = error; | |||
maxweight = weight; | |||
best = k; | |||
} | |||
} | |||
c += delta; | |||
} | |||
switch (best) | |||
{ | |||
case 0: | |||
b = a + delta * 2; | |||
break; | |||
case 4: | |||
a = b - delta * 2; | |||
break; | |||
default: | |||
b = a + delta * (best + 1); | |||
a = a + delta * (best - 1); | |||
break; | |||
} | |||
if (delta < m_epsilon) | |||
{ | |||
real e = fabs(maxerror / maxweight); | |||
if (e > final) | |||
final = e; | |||
m_control[i] = (a + b) / 2; | |||
break; | |||
} | |||
} | |||
} | |||
return final; | |||
} | |||
template<> | |||
void RemezSolver<real>::Step() | |||
{ | |||
/* Pick up x_i where error will be 0 and compute f(x_i) */ | |||
real fxn[m_order + 2]; | |||
for (int i = 0; i < m_order + 2; i++) | |||
fxn[i] = EvalFunc(m_control[i]); | |||
/* We build a matrix of Chebishev evaluations: row i contains the | |||
* evaluations of x_i for polynomial order n = 0, 1, ... */ | |||
Matrix<real> mat(m_order + 2, m_order + 2); | |||
for (int i = 0; i < m_order + 2; i++) | |||
{ | |||
/* Compute the powers of x_i */ | |||
real powers[m_order + 1]; | |||
powers[0] = 1.0; | |||
for (int n = 1; n < m_order + 1; n++) | |||
powers[n] = powers[n - 1] * m_control[i]; | |||
/* Compute the Chebishev evaluations at x_i */ | |||
for (int n = 0; n < m_order + 1; n++) | |||
{ | |||
real sum = 0.0; | |||
for (int k = 0; k < m_order + 1; k++) | |||
sum += (real)Cheby(n, k) * powers[k]; | |||
mat.m(i, n) = sum; | |||
} | |||
if (i & 1) | |||
mat.m(i, m_order + 1) = fabs(Weight(m_control[i])); | |||
else | |||
mat.m(i, m_order + 1) = -fabs(Weight(m_control[i])); | |||
} | |||
/* Solve the system */ | |||
mat = mat.inv(); | |||
/* Compute interpolation coefficients */ | |||
for (int j = 0; j < m_order + 1; j++) | |||
{ | |||
m_coeff[j] = 0; | |||
for (int i = 0; i < m_order + 2; i++) | |||
m_coeff[j] += mat.m(j, i) * fxn[i]; | |||
} | |||
/* Compute the error */ | |||
real error = 0; | |||
for (int i = 0; i < m_order + 2; i++) | |||
error += mat.m(m_order + 1, i) * fxn[i]; | |||
} | |||
template<> | |||
int RemezSolver<real>::Cheby(int n, int k) | |||
{ | |||
if (k > n || k < 0) | |||
return 0; | |||
if (n <= 1) | |||
return (n ^ k ^ 1) & 1; | |||
return 2 * Cheby(n - 1, k - 1) - Cheby(n - 2, k); | |||
} | |||
template<> | |||
int RemezSolver<real>::Comb(int n, int k) | |||
{ | |||
if (k == 0 || k == n) | |||
return 1; | |||
return Comb(n - 1, k - 1) + Comb(n - 1, k); | |||
} | |||
template<> | |||
void RemezSolver<real>::PrintPoly() | |||
{ | |||
using std::printf; | |||
/* Transform Chebyshev polynomial weights into powers of X^i | |||
* in the [-1..1] range. */ | |||
real bn[m_order + 1]; | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
bn[i] = 0; | |||
for (int j = 0; j < m_order + 1; j++) | |||
bn[i] += m_coeff[j] * (real)Cheby(j, i); | |||
} | |||
/* Transform a polynomial in the [-1..1] range into a polynomial | |||
* in the [a..b] range. */ | |||
real k1p[m_order + 1], k2p[m_order + 1]; | |||
real an[m_order + 1]; | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
k1p[i] = i ? k1p[i - 1] * m_invk1 : (real)1; | |||
k2p[i] = i ? k2p[i - 1] * m_invk2 : (real)1; | |||
} | |||
for (int i = 0; i < m_order + 1; i++) | |||
{ | |||
an[i] = 0; | |||
for (int j = i; j < m_order + 1; j++) | |||
an[i] += (real)Comb(j, i) * k1p[j - i] * bn[j]; | |||
an[i] *= k2p[i]; | |||
} | |||
printf("Polynomial estimate: "); | |||
for (int j = 0; j < m_order + 1; j++) | |||
{ | |||
if (j) | |||
printf(" + x**%i * ", j); | |||
an[j].print(m_decimals); | |||
} | |||
printf("\n\n"); | |||
} | |||
template<> | |||
real RemezSolver<real>::EvalFunc(real const &x) | |||
{ | |||
return m_func(x * m_k2 + m_k1); | |||
} | |||
template<> | |||
real RemezSolver<real>::Weight(real const &x) | |||
{ | |||
if (m_weight) | |||
return m_weight(x * m_k2 + m_k1); | |||
return 1; | |||
} | |||
@@ -0,0 +1,57 @@ | |||
// | |||
// LolRemez - Remez algorithm implementation | |||
// | |||
// Copyright: (c) 2010-2013 Sam Hocevar <sam@hocevar.net> | |||
// This program is free software; you can redistribute it and/or | |||
// modify it under the terms of the Do What The Fuck You Want To | |||
// Public License, Version 2, as published by Sam Hocevar. See | |||
// http://www.wtfpl.net/ for more details. | |||
// | |||
#pragma once | |||
// | |||
// The RemezSolver class | |||
// --------------------- | |||
// | |||
#include <cstdio> | |||
template<typename NUMERIC_T> class RemezSolver | |||
{ | |||
public: | |||
typedef NUMERIC_T RealFunc(NUMERIC_T const &x); | |||
inline RemezSolver() | |||
: m_order(0) | |||
{ | |||
} | |||
void Run(int order, int decimals, NUMERIC_T a, NUMERIC_T b, | |||
RealFunc *func, RealFunc *weight = nullptr); | |||
NUMERIC_T EvalCheby(NUMERIC_T const &x); | |||
void Init(); | |||
void FindZeroes(); | |||
NUMERIC_T FindExtrema(); | |||
void Step(); | |||
int Cheby(int n, int k); | |||
int Comb(int n, int k); | |||
void PrintPoly(); | |||
NUMERIC_T EvalFunc(NUMERIC_T const &x); | |||
NUMERIC_T Weight(NUMERIC_T const &x); | |||
private: | |||
int m_order; | |||
lol::Array<NUMERIC_T> m_coeff; | |||
lol::Array<NUMERIC_T> m_zeroes; | |||
lol::Array<NUMERIC_T> m_control; | |||
RealFunc *m_func, *m_weight; | |||
NUMERIC_T m_k1, m_k2, m_invk1, m_invk2, m_epsilon; | |||
int m_decimals; | |||
}; | |||