Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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