Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

337 wiersze
9.0 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. using std::printf;
  31. m_func.parse(func);
  32. if (weight)
  33. {
  34. m_weight.parse(weight);
  35. m_has_weight = true;
  36. }
  37. m_k1 = (b + a) / 2;
  38. m_k2 = (b - a) / 2;
  39. m_epsilon = pow((real)10, (real)-(m_decimals + 2));
  40. remez_init();
  41. print_poly();
  42. for (int n = 0; ; n++)
  43. {
  44. real old_error = m_error;
  45. find_extrema();
  46. remez_step();
  47. if (m_error >= (real)0
  48. && fabs(m_error - old_error) < m_error * m_epsilon)
  49. break;
  50. print_poly();
  51. find_zeroes();
  52. }
  53. print_poly();
  54. }
  55. /*
  56. * This is basically the first Remez step: we solve a system of
  57. * order N+1 and get a good initial polynomial estimate.
  58. */
  59. void remez_solver::remez_init()
  60. {
  61. /* m_order + 1 zeroes of the error function */
  62. m_zeroes.Resize(m_order + 1);
  63. /* m_order + 2 control points */
  64. m_control.Resize(m_order + 2);
  65. /* Initial estimates for the x_i where the error will be zero and
  66. * precompute f(x_i). */
  67. array<real> fxn;
  68. for (int i = 0; i < m_order + 1; i++)
  69. {
  70. m_zeroes[i] = (real)(2 * i - m_order) / (real)(m_order + 1);
  71. fxn.Push(eval_func(m_zeroes[i]));
  72. }
  73. /* We build a matrix of Chebishev evaluations: row i contains the
  74. * evaluations of x_i for polynomial order n = 0, 1, ... */
  75. LinearSystem<real> system(m_order + 1);
  76. for (int n = 0; n < m_order + 1; n++)
  77. {
  78. auto p = polynomial<real>::chebyshev(n);
  79. for (int i = 0; i < m_order + 1; i++)
  80. system[i][n] = p.eval(m_zeroes[i]);
  81. }
  82. /* Solve the system */
  83. system = system.inverse();
  84. /* Compute new Chebyshev estimate */
  85. m_estimate = polynomial<real>();
  86. for (int n = 0; n < m_order + 1; n++)
  87. {
  88. real weight = 0;
  89. for (int i = 0; i < m_order + 1; i++)
  90. weight += system[n][i] * fxn[i];
  91. m_estimate += weight * polynomial<real>::chebyshev(n);
  92. }
  93. }
  94. /*
  95. * Every subsequent iteration of the Remez algorithm: we solve a system
  96. * of order N+2 to both refine the estimate and compute the error.
  97. */
  98. void remez_solver::remez_step()
  99. {
  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 Chebishev evaluations: row i contains the
  105. * evaluations of x_i for polynomial order n = 0, 1, ... */
  106. LinearSystem<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. }
  135. void remez_solver::find_zeroes()
  136. {
  137. m_stats_cheby = m_stats_func = m_stats_weight = 0.f;
  138. /* Find m_order + 1 zeroes of the error function. No need to
  139. * compute the relative error: its zeroes are at the same
  140. * place as the absolute error! */
  141. for (int i = 0; i < m_order + 1; i++)
  142. {
  143. struct { real value, error; } a, b, c;
  144. a.value = m_control[i];
  145. a.error = eval_estimate(a.value) - eval_func(a.value);
  146. b.value = m_control[i + 1];
  147. b.error = eval_estimate(b.value) - eval_func(b.value);
  148. static real limit = ldexp((real)1, -500);
  149. static real zero = (real)0;
  150. while (fabs(a.value - b.value) > limit)
  151. {
  152. /* Interpolate linearly instead of taking the midpoint, this
  153. * leads to far better convergence (6:1 speedups). */
  154. real t = abs(b.error) / (abs(a.error) + abs(b.error));
  155. real newc = b.value + t * (a.value - b.value);
  156. /* If the third point didn't change since last iteration,
  157. * we may be at an inflection point. Use the midpoint to get
  158. * out of this situation. */
  159. c.value = newc == c.value ? (a.value + b.value) / 2 : newc;
  160. c.error = eval_estimate(c.value) - eval_func(c.value);
  161. if (c.error == zero)
  162. break;
  163. if ((a.error < zero && c.error < zero)
  164. || (a.error > zero && c.error > zero))
  165. a = c;
  166. else
  167. b = c;
  168. }
  169. m_zeroes[i] = c.value;
  170. }
  171. printf(" -:- timings for zeroes: estimate %f func %f weight %f\n",
  172. m_stats_cheby, m_stats_func, m_stats_weight);
  173. }
  174. /* XXX: this is the real costly function */
  175. void remez_solver::find_extrema()
  176. {
  177. using std::printf;
  178. /* Find m_order + 2 extrema of the error function. We need to
  179. * compute the relative error, since its extrema are at slightly
  180. * different locations than the absolute error’s. */
  181. m_error = 0;
  182. m_stats_cheby = m_stats_func = m_stats_weight = 0.f;
  183. int evals = 0, rounds = 0;
  184. for (int i = 0; i < m_order + 2; i++)
  185. {
  186. real a = -1, b = 1;
  187. if (i > 0)
  188. a = m_zeroes[i - 1];
  189. if (i < m_order + 1)
  190. b = m_zeroes[i];
  191. for (int r = 0; ; ++r, ++rounds)
  192. {
  193. real maxerror = 0, maxweight = 0;
  194. int best = -1;
  195. real c = a, delta = (b - a) / 4;
  196. for (int k = 0; k <= 4; k++)
  197. {
  198. if (r == 0 || (k & 1))
  199. {
  200. ++evals;
  201. real error = fabs(eval_estimate(c) - eval_func(c));
  202. real weight = fabs(eval_weight(c));
  203. /* if error/weight >= maxerror/maxweight */
  204. if (error * maxweight >= maxerror * weight)
  205. {
  206. maxerror = error;
  207. maxweight = weight;
  208. best = k;
  209. }
  210. }
  211. c += delta;
  212. }
  213. switch (best)
  214. {
  215. case 0:
  216. b = a + delta * 2;
  217. break;
  218. case 4:
  219. a = b - delta * 2;
  220. break;
  221. default:
  222. b = a + delta * (best + 1);
  223. a = a + delta * (best - 1);
  224. break;
  225. }
  226. if (delta < m_epsilon)
  227. {
  228. real e = fabs(maxerror / maxweight);
  229. if (e > m_error)
  230. m_error = e;
  231. m_control[i] = (a + b) / 2;
  232. break;
  233. }
  234. }
  235. }
  236. printf(" -:- timings for extrema: estimate %f func %f weight %f\n",
  237. m_stats_cheby, m_stats_func, m_stats_weight);
  238. printf(" -:- calls: %d rounds, %d evals\n", rounds, evals);
  239. printf(" -:- error: ");
  240. m_error.print(m_decimals);
  241. printf("\n");
  242. }
  243. void remez_solver::print_poly()
  244. {
  245. using std::printf;
  246. /* Transform our polynomial in the [-1..1] range into a polynomial
  247. * in the [a..b] range by composing it with q:
  248. * q(x) = 2x / (b-a) - (b+a) / (b-a) */
  249. polynomial<real> q ({ -m_k1 / m_k2, real(1) / m_k2 });
  250. polynomial<real> r = m_estimate.eval(q);
  251. printf("\n");
  252. for (int j = 0; j < m_order + 1; j++)
  253. {
  254. if (j)
  255. printf(" + x**%i * ", j);
  256. r[j].print(m_decimals);
  257. }
  258. printf("\n\n");
  259. }
  260. real remez_solver::eval_estimate(real const &x)
  261. {
  262. Timer t;
  263. real ret = m_estimate.eval(x);
  264. m_stats_cheby += t.Get();
  265. return ret;
  266. }
  267. real remez_solver::eval_func(real const &x)
  268. {
  269. Timer t;
  270. real ret = m_func.eval(x * m_k2 + m_k1);
  271. m_stats_func += t.Get();
  272. return ret;
  273. }
  274. real remez_solver::eval_weight(real const &x)
  275. {
  276. Timer t;
  277. real ret = m_has_weight ? m_weight.eval(x * m_k2 + m_k1) : real(1);
  278. m_stats_weight += t.Get();
  279. return ret;
  280. }