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.

solver.cpp 8.3 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. //
  2. // LolRemez - Remez algorithm implementation
  3. //
  4. // Copyright: (c) 2005-2013 Sam Hocevar <sam@hocevar.net>
  5. // This program is free software; you can redistribute it and/or
  6. // modify it under the terms of the Do What The Fuck You Want To
  7. // Public License, Version 2, as published by Sam Hocevar. See
  8. // http://www.wtfpl.net/ for more details.
  9. //
  10. #if HAVE_CONFIG_H
  11. # include "config.h"
  12. #endif
  13. #include <functional>
  14. #include <lol/engine.h>
  15. #include <lol/math/real.h>
  16. #include <lol/math/polynomial.h>
  17. #include "matrix.h"
  18. #include "solver.h"
  19. using lol::real;
  20. RemezSolver::RemezSolver(int order, int decimals)
  21. : m_order(order),
  22. m_decimals(decimals)
  23. {
  24. }
  25. void RemezSolver::Run(real a, real b,
  26. RemezSolver::RealFunc *func,
  27. RemezSolver::RealFunc *weight)
  28. {
  29. using std::printf;
  30. m_func = func;
  31. m_weight = weight;
  32. m_k1 = (b + a) / 2;
  33. m_k2 = (b - a) / 2;
  34. m_epsilon = pow((real)10, (real)-(m_decimals + 2));
  35. Init();
  36. PrintPoly();
  37. real error = -1;
  38. for (int n = 0; ; n++)
  39. {
  40. real newerror = FindExtrema();
  41. printf("Step %i error: ", n);
  42. newerror.print(m_decimals);
  43. printf("\n");
  44. Step();
  45. if (error >= (real)0 && fabs(newerror - error) < error * m_epsilon)
  46. break;
  47. error = newerror;
  48. PrintPoly();
  49. FindZeroes();
  50. }
  51. PrintPoly();
  52. }
  53. /*
  54. * This is basically the first Remez step: we solve a system of
  55. * order N+1 and get a good initial polynomial estimate.
  56. */
  57. void RemezSolver::Init()
  58. {
  59. /* m_order + 1 zeroes of the error function */
  60. m_zeroes.Resize(m_order + 1);
  61. /* m_order + 2 control points */
  62. m_control.Resize(m_order + 2);
  63. /* Initial estimates for the x_i where the error will be zero and
  64. * precompute f(x_i). */
  65. array<real> fxn;
  66. for (int i = 0; i < m_order + 1; i++)
  67. {
  68. m_zeroes[i] = (real)(2 * i - m_order) / (real)(m_order + 1);
  69. fxn.Push(EvalFunc(m_zeroes[i]));
  70. }
  71. /* We build a matrix of Chebishev evaluations: row i contains the
  72. * evaluations of x_i for polynomial order n = 0, 1, ... */
  73. LinearSystem<real> system(m_order + 1);
  74. for (int n = 0; n < m_order + 1; n++)
  75. {
  76. auto p = polynomial<real>::chebyshev(n);
  77. for (int i = 0; i < m_order + 1; i++)
  78. system[i][n] = p.eval(m_zeroes[i]);
  79. }
  80. /* Solve the system */
  81. system = system.inverse();
  82. /* Compute new Chebyshev estimate */
  83. m_estimate = polynomial<real>();
  84. for (int n = 0; n < m_order + 1; n++)
  85. {
  86. real weight = 0;
  87. for (int i = 0; i < m_order + 1; i++)
  88. weight += system[n][i] * fxn[i];
  89. m_estimate += weight * polynomial<real>::chebyshev(n);
  90. }
  91. }
  92. /*
  93. * Every subsequent iteration of the Remez algorithm: we solve a system
  94. * of order N+2 to both refine the estimate and compute the error.
  95. */
  96. void RemezSolver::Step()
  97. {
  98. /* Pick up x_i where error will be 0 and compute f(x_i) */
  99. array<real> fxn;
  100. for (int i = 0; i < m_order + 2; i++)
  101. fxn.Push(EvalFunc(m_control[i]));
  102. /* We build a matrix of Chebishev evaluations: row i contains the
  103. * evaluations of x_i for polynomial order n = 0, 1, ... */
  104. LinearSystem<real> system(m_order + 2);
  105. for (int n = 0; n < m_order + 1; n++)
  106. {
  107. auto p = polynomial<real>::chebyshev(n);
  108. for (int i = 0; i < m_order + 2; i++)
  109. system[i][n] = p.eval(m_control[i]);
  110. }
  111. /* The last line of the system is the oscillating error */
  112. for (int i = 0; i < m_order + 2; i++)
  113. {
  114. real error = fabs(EvalWeight(m_control[i]));
  115. system[i][m_order + 1] = (i & 1) ? error : -error;
  116. }
  117. /* Solve the system */
  118. system = system.inverse();
  119. /* Compute new polynomial estimate */
  120. m_estimate = polynomial<real>();
  121. for (int n = 0; n < m_order + 1; n++)
  122. {
  123. real weight = 0;
  124. for (int i = 0; i < m_order + 2; i++)
  125. weight += system[n][i] * fxn[i];
  126. m_estimate += weight * polynomial<real>::chebyshev(n);
  127. }
  128. /* Compute the error (FIXME: unused?) */
  129. real error = 0;
  130. for (int i = 0; i < m_order + 2; i++)
  131. error += system[m_order + 1][i] * fxn[i];
  132. }
  133. void RemezSolver::FindZeroes()
  134. {
  135. /* Find m_order + 1 zeroes of the error function. No need to
  136. * compute the relative error: its zeroes are at the same
  137. * place as the absolute error! */
  138. for (int i = 0; i < m_order + 1; i++)
  139. {
  140. struct { real value, error; } left, right, mid;
  141. left.value = m_control[i];
  142. left.error = EvalEstimate(left.value) - EvalFunc(left.value);
  143. right.value = m_control[i + 1];
  144. right.error = EvalEstimate(right.value) - EvalFunc(right.value);
  145. static real limit = ldexp((real)1, -500);
  146. static real zero = (real)0;
  147. while (fabs(left.value - right.value) > limit)
  148. {
  149. mid.value = (left.value + right.value) / 2;
  150. mid.error = EvalEstimate(mid.value) - EvalFunc(mid.value);
  151. if ((left.error <= zero && mid.error <= zero)
  152. || (left.error >= zero && mid.error >= zero))
  153. left = mid;
  154. else
  155. right = mid;
  156. }
  157. m_zeroes[i] = mid.value;
  158. }
  159. }
  160. /* XXX: this is the real costly function */
  161. real RemezSolver::FindExtrema()
  162. {
  163. using std::printf;
  164. /* Find m_order + 2 extrema of the error function. We need to
  165. * compute the relative error, since its extrema are at slightly
  166. * different locations than the absolute error’s. */
  167. real final = 0;
  168. m_stats_cheby = m_stats_func = m_stats_weight = 0.f;
  169. int evals = 0, rounds = 0;
  170. for (int i = 0; i < m_order + 2; i++)
  171. {
  172. real a = -1, b = 1;
  173. if (i > 0)
  174. a = m_zeroes[i - 1];
  175. if (i < m_order + 1)
  176. b = m_zeroes[i];
  177. for (int r = 0; ; ++r, ++rounds)
  178. {
  179. real maxerror = 0, maxweight = 0;
  180. int best = -1;
  181. real c = a, delta = (b - a) / 4;
  182. for (int k = 0; k <= 4; k++)
  183. {
  184. if (r == 0 || (k & 1))
  185. {
  186. ++evals;
  187. real error = fabs(EvalEstimate(c) - EvalFunc(c));
  188. real weight = fabs(EvalWeight(c));
  189. /* if error/weight >= maxerror/maxweight */
  190. if (error * maxweight >= maxerror * weight)
  191. {
  192. maxerror = error;
  193. maxweight = weight;
  194. best = k;
  195. }
  196. }
  197. c += delta;
  198. }
  199. switch (best)
  200. {
  201. case 0:
  202. b = a + delta * 2;
  203. break;
  204. case 4:
  205. a = b - delta * 2;
  206. break;
  207. default:
  208. b = a + delta * (best + 1);
  209. a = a + delta * (best - 1);
  210. break;
  211. }
  212. if (delta < m_epsilon)
  213. {
  214. real e = fabs(maxerror / maxweight);
  215. if (e > final)
  216. final = e;
  217. m_control[i] = (a + b) / 2;
  218. break;
  219. }
  220. }
  221. }
  222. printf("Iterations: Rounds %d Evals %d\n", rounds, evals);
  223. printf("Times: Estimate %f Func %f EvalWeight %f\n",
  224. m_stats_cheby, m_stats_func, m_stats_weight);
  225. return final;
  226. }
  227. void RemezSolver::PrintPoly()
  228. {
  229. using std::printf;
  230. /* Transform our polynomial in the [-1..1] range into a polynomial
  231. * in the [a..b] range by composing it with q:
  232. * q(x) = 2x / (b-a) - (b+a) / (b-a) */
  233. polynomial<real> q ({ -m_k1 / m_k2, real(1) / m_k2 });
  234. polynomial<real> r = m_estimate.eval(q);
  235. printf("Polynomial estimate: ");
  236. for (int j = 0; j < m_order + 1; j++)
  237. {
  238. if (j)
  239. printf(" + x**%i * ", j);
  240. r[j].print(m_decimals);
  241. }
  242. printf("\n\n");
  243. }
  244. real RemezSolver::EvalEstimate(real const &x)
  245. {
  246. Timer t;
  247. real ret = m_estimate.eval(x);
  248. m_stats_cheby += t.Get();
  249. return ret;
  250. }
  251. real RemezSolver::EvalFunc(real const &x)
  252. {
  253. Timer t;
  254. real ret = m_func(x * m_k2 + m_k1);
  255. m_stats_func += t.Get();
  256. return ret;
  257. }
  258. real RemezSolver::EvalWeight(real const &x)
  259. {
  260. Timer t;
  261. real ret = m_weight ? m_weight(x * m_k2 + m_k1) : real(1);
  262. m_stats_weight += t.Get();
  263. return ret;
  264. }