// // Lol Engine // // Copyright: (c) 2010-2011 Sam Hocevar // 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 #include "lol/math/vector.h" namespace lol { template 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 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 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__ */