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.
 
 
 

327 line
8.4 KiB

  1. //
  2. // LolRemez - Remez algorithm implementation
  3. //
  4. // Copyright © 2005—2015 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // This program is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #if HAVE_CONFIG_H
  13. # include "config.h"
  14. #endif
  15. #include <functional>
  16. #include <lol/engine.h>
  17. #include <lol/math/real.h>
  18. #include <lol/math/polynomial.h>
  19. #include "matrix.h"
  20. #include "solver.h"
  21. using lol::real;
  22. remez_solver::remez_solver(int order, int decimals)
  23. : m_order(order),
  24. m_decimals(decimals),
  25. m_has_weight(false)
  26. {
  27. }
  28. void remez_solver::run(real a, real b, char const *func, char const *weight)
  29. {
  30. m_func.parse(func);
  31. if (weight)
  32. {
  33. m_weight.parse(weight);
  34. m_has_weight = true;
  35. }
  36. m_k1 = (b + a) / 2;
  37. m_k2 = (b - a) / 2;
  38. m_epsilon = pow((real)10, (real)-(m_decimals + 2));
  39. remez_init();
  40. print_poly();
  41. for (int n = 0; ; n++)
  42. {
  43. real old_error = m_error;
  44. find_extrema();
  45. remez_step();
  46. if (m_error >= (real)0
  47. && fabs(m_error - old_error) < m_error * m_epsilon)
  48. break;
  49. print_poly();
  50. find_zeroes();
  51. }
  52. print_poly();
  53. }
  54. /*
  55. * This is basically the first Remez step: we solve a system of
  56. * order N+1 and get a good initial polynomial estimate.
  57. */
  58. void remez_solver::remez_init()
  59. {
  60. /* m_order + 1 zeroes of the error function */
  61. m_zeroes.Resize(m_order + 1);
  62. /* m_order + 2 control points */
  63. m_control.Resize(m_order + 2);
  64. /* Initial estimates for the x_i where the error will be zero and
  65. * precompute f(x_i). */
  66. array<real> fxn;
  67. for (int i = 0; i < m_order + 1; i++)
  68. {
  69. m_zeroes[i] = (real)(2 * i - m_order) / (real)(m_order + 1);
  70. fxn.Push(eval_func(m_zeroes[i]));
  71. }
  72. /* We build a matrix of Chebyshev evaluations: row i contains the
  73. * evaluations of x_i for polynomial order n = 0, 1, ... */
  74. linear_system<real> system(m_order + 1);
  75. for (int n = 0; n < m_order + 1; n++)
  76. {
  77. auto p = polynomial<real>::chebyshev(n);
  78. for (int i = 0; i < m_order + 1; i++)
  79. system[i][n] = p.eval(m_zeroes[i]);
  80. }
  81. /* Solve the system */
  82. system = system.inverse();
  83. /* Compute new Chebyshev estimate */
  84. m_estimate = polynomial<real>();
  85. for (int n = 0; n < m_order + 1; n++)
  86. {
  87. real weight = 0;
  88. for (int i = 0; i < m_order + 1; i++)
  89. weight += system[n][i] * fxn[i];
  90. m_estimate += weight * polynomial<real>::chebyshev(n);
  91. }
  92. }
  93. /*
  94. * Every subsequent iteration of the Remez algorithm: we solve a system
  95. * of order N+2 to both refine the estimate and compute the error.
  96. */
  97. void remez_solver::remez_step()
  98. {
  99. Timer t;
  100. /* Pick up x_i where error will be 0 and compute f(x_i) */
  101. array<real> fxn;
  102. for (int i = 0; i < m_order + 2; i++)
  103. fxn.Push(eval_func(m_control[i]));
  104. /* We build a matrix of Chebyshev evaluations: row i contains the
  105. * evaluations of x_i for polynomial order n = 0, 1, ... */
  106. linear_system<real> system(m_order + 2);
  107. for (int n = 0; n < m_order + 1; n++)
  108. {
  109. auto p = polynomial<real>::chebyshev(n);
  110. for (int i = 0; i < m_order + 2; i++)
  111. system[i][n] = p.eval(m_control[i]);
  112. }
  113. /* The last line of the system is the oscillating error */
  114. for (int i = 0; i < m_order + 2; i++)
  115. {
  116. real error = fabs(eval_weight(m_control[i]));
  117. system[i][m_order + 1] = (i & 1) ? error : -error;
  118. }
  119. /* Solve the system */
  120. system = system.inverse();
  121. /* Compute new polynomial estimate */
  122. m_estimate = polynomial<real>();
  123. for (int n = 0; n < m_order + 1; n++)
  124. {
  125. real weight = 0;
  126. for (int i = 0; i < m_order + 2; i++)
  127. weight += system[n][i] * fxn[i];
  128. m_estimate += weight * polynomial<real>::chebyshev(n);
  129. }
  130. /* Compute the error (FIXME: unused?) */
  131. real error = 0;
  132. for (int i = 0; i < m_order + 2; i++)
  133. error += system[m_order + 1][i] * fxn[i];
  134. using std::printf;
  135. printf(" -:- timing for inversion: %f ms\n", t.Get() * 1000.f);
  136. }
  137. /*
  138. * Find m_order + 1 zeroes of the error function. No need to compute the
  139. * relative error: its zeroes are at the same place as the absolute error!
  140. *
  141. * The algorithm used here is naïve regula falsi. It still performs a lot
  142. * better than the midpoint algorithm.
  143. */
  144. void remez_solver::find_zeroes()
  145. {
  146. Timer t;
  147. for (int i = 0; i < m_order + 1; i++)
  148. {
  149. struct { real x, err; } a, b, c;
  150. a.x = m_control[i];
  151. a.err = eval_estimate(a.x) - eval_func(a.x);
  152. b.x = m_control[i + 1];
  153. b.err = eval_estimate(b.x) - eval_func(b.x);
  154. static real limit = ldexp((real)1, -500);
  155. static real zero = (real)0;
  156. while (fabs(a.x - b.x) > limit)
  157. {
  158. real s = abs(b.err) / (abs(a.err) + abs(b.err));
  159. real newc = b.x + s * (a.x - b.x);
  160. /* If the third point didn't change since last iteration,
  161. * we may be at an inflection point. Use the midpoint to get
  162. * out of this situation. */
  163. c.x = newc == c.x ? (a.x + b.x) / 2 : newc;
  164. c.err = eval_estimate(c.x) - eval_func(c.x);
  165. if (c.err == zero)
  166. break;
  167. if ((a.err < zero && c.err < zero)
  168. || (a.err > zero && c.err > zero))
  169. a = c;
  170. else
  171. b = c;
  172. }
  173. m_zeroes[i] = c.x;
  174. }
  175. using std::printf;
  176. printf(" -:- timing for zeroes: %f ms\n", t.Get() * 1000.f);
  177. }
  178. /*
  179. * Find m_order extrema of the error function. We maximise the relative
  180. * error, since its extrema are at slightly different locations than the
  181. * absolute error’s.
  182. *
  183. * The algorithm used here is successive parabolic interpolation. FIXME: we
  184. * could use Brent’s method instead, which combines parabolic interpolation
  185. * and golden ratio search and has superlinear convergence.
  186. */
  187. void remez_solver::find_extrema()
  188. {
  189. Timer t;
  190. m_control[0] = -1;
  191. m_control[m_order + 1] = 1;
  192. m_error = 0;
  193. for (int i = 1; i < m_order + 1; i++)
  194. {
  195. struct { real x, err; } a, b, c, d;
  196. a.x = m_zeroes[i - 1];
  197. b.x = m_zeroes[i];
  198. c.x = a.x + (b.x - a.x) * real(rand(0.4f, 0.6f));
  199. a.err = eval_error(a.x);
  200. b.err = eval_error(b.x);
  201. c.err = eval_error(c.x);
  202. while (b.x - a.x > m_epsilon)
  203. {
  204. real d1 = c.x - a.x, d2 = c.x - b.x;
  205. real k1 = d1 * (c.err - b.err);
  206. real k2 = d2 * (c.err - a.err);
  207. d.x = c.x - (d1 * k1 - d2 * k2) / (k1 - k2) / 2;
  208. /* If parabolic interpolation failed, pick a number
  209. * inbetween. */
  210. if (d.x <= a.x || d.x >= b.x)
  211. d.x = (a.x + b.x) / 2;
  212. d.err = eval_error(d.x);
  213. /* Update bracketing depending on the new point. */
  214. if (d.err < c.err)
  215. {
  216. (d.x > c.x ? b : a) = d;
  217. }
  218. else
  219. {
  220. (d.x > c.x ? a : b) = c;
  221. c = d;
  222. }
  223. }
  224. if (c.err > m_error)
  225. m_error = c.err;
  226. m_control[i] = c.x;
  227. }
  228. using std::printf;
  229. printf(" -:- timing for extrema: %f ms\n", t.Get() * 1000.f);
  230. printf(" -:- error: ");
  231. m_error.print(m_decimals);
  232. printf("\n");
  233. }
  234. void remez_solver::print_poly()
  235. {
  236. /* Transform our polynomial in the [-1..1] range into a polynomial
  237. * in the [a..b] range by composing it with q:
  238. * q(x) = 2x / (b-a) - (b+a) / (b-a) */
  239. polynomial<real> q ({ -m_k1 / m_k2, real(1) / m_k2 });
  240. polynomial<real> r = m_estimate.eval(q);
  241. using std::printf;
  242. printf("\n");
  243. for (int j = 0; j < m_order + 1; j++)
  244. {
  245. if (j)
  246. printf(" + x**%i * ", j);
  247. r[j].print(m_decimals);
  248. }
  249. printf("\n\n");
  250. }
  251. real remez_solver::eval_estimate(real const &x)
  252. {
  253. return m_estimate.eval(x);
  254. }
  255. real remez_solver::eval_func(real const &x)
  256. {
  257. return m_func.eval(x * m_k2 + m_k1);
  258. }
  259. real remez_solver::eval_weight(real const &x)
  260. {
  261. return m_has_weight ? m_weight.eval(x * m_k2 + m_k1) : real(1);
  262. }
  263. real remez_solver::eval_error(real const &x)
  264. {
  265. return fabs((eval_estimate(x) - eval_func(x)) / eval_weight(x));
  266. }