25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

311 lines
8.0 KiB

  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. void RemezSolver::Init()
  54. {
  55. /* m_order + 1 zeroes of the error function */
  56. m_zeroes.Resize(m_order + 1);
  57. /* m_order + 2 control points */
  58. m_control.Resize(m_order + 2);
  59. /* Initial estimates for the x_i where the error will be zero and
  60. * precompute f(x_i). */
  61. array<real> fxn;
  62. for (int i = 0; i < m_order + 1; i++)
  63. {
  64. m_zeroes[i] = (real)(2 * i - m_order) / (real)(m_order + 1);
  65. fxn.Push(EvalFunc(m_zeroes[i]));
  66. }
  67. /* We build a matrix of Chebishev evaluations: row i contains the
  68. * evaluations of x_i for polynomial order n = 0, 1, ... */
  69. LinearSystem<real> system(m_order + 1);
  70. for (int n = 0; n < m_order + 1; n++)
  71. {
  72. auto p = polynomial<real>::chebyshev(n);
  73. for (int i = 0; i < m_order + 1; i++)
  74. system.m(i, n) = p.eval(m_zeroes[i]);
  75. }
  76. /* Solve the system */
  77. system = system.inv();
  78. /* Compute new Chebyshev estimate */
  79. m_estimate = polynomial<real>();
  80. for (int n = 0; n < m_order + 1; n++)
  81. {
  82. real weight = 0;
  83. for (int i = 0; i < m_order + 1; i++)
  84. weight += system.m(n, i) * fxn[i];
  85. m_estimate += weight * polynomial<real>::chebyshev(n);
  86. }
  87. }
  88. void RemezSolver::FindZeroes()
  89. {
  90. /* Find m_order + 1 zeroes of the error function. No need to
  91. * compute the relative error: its zeroes are at the same
  92. * place as the absolute error! */
  93. for (int i = 0; i < m_order + 1; i++)
  94. {
  95. struct { real value, error; } left, right, mid;
  96. left.value = m_control[i];
  97. left.error = EvalCheby(left.value) - EvalFunc(left.value);
  98. right.value = m_control[i + 1];
  99. right.error = EvalCheby(right.value) - EvalFunc(right.value);
  100. static real limit = ldexp((real)1, -500);
  101. static real zero = (real)0;
  102. while (fabs(left.value - right.value) > limit)
  103. {
  104. mid.value = (left.value + right.value) / 2;
  105. mid.error = EvalCheby(mid.value) - EvalFunc(mid.value);
  106. if ((left.error <= zero && mid.error <= zero)
  107. || (left.error >= zero && mid.error >= zero))
  108. left = mid;
  109. else
  110. right = mid;
  111. }
  112. m_zeroes[i] = mid.value;
  113. }
  114. }
  115. /* XXX: this is the real costly function */
  116. real RemezSolver::FindExtrema()
  117. {
  118. using std::printf;
  119. /* Find m_order + 2 extrema of the error function. We need to
  120. * compute the relative error, since its extrema are at slightly
  121. * different locations than the absolute error’s. */
  122. real final = 0;
  123. m_stats_cheby = m_stats_func = m_stats_weight = 0.f;
  124. int evals = 0, rounds = 0;
  125. for (int i = 0; i < m_order + 2; i++)
  126. {
  127. real a = -1, b = 1;
  128. if (i > 0)
  129. a = m_zeroes[i - 1];
  130. if (i < m_order + 1)
  131. b = m_zeroes[i];
  132. for (int r = 0; ; ++r, ++rounds)
  133. {
  134. real maxerror = 0, maxweight = 0;
  135. int best = -1;
  136. real c = a, delta = (b - a) / 4;
  137. for (int k = 0; k <= 4; k++)
  138. {
  139. if (r == 0 || (k & 1))
  140. {
  141. ++evals;
  142. real error = fabs(EvalCheby(c) - EvalFunc(c));
  143. real weight = fabs(EvalWeight(c));
  144. /* if error/weight >= maxerror/maxweight */
  145. if (error * maxweight >= maxerror * weight)
  146. {
  147. maxerror = error;
  148. maxweight = weight;
  149. best = k;
  150. }
  151. }
  152. c += delta;
  153. }
  154. switch (best)
  155. {
  156. case 0:
  157. b = a + delta * 2;
  158. break;
  159. case 4:
  160. a = b - delta * 2;
  161. break;
  162. default:
  163. b = a + delta * (best + 1);
  164. a = a + delta * (best - 1);
  165. break;
  166. }
  167. if (delta < m_epsilon)
  168. {
  169. real e = fabs(maxerror / maxweight);
  170. if (e > final)
  171. final = e;
  172. m_control[i] = (a + b) / 2;
  173. break;
  174. }
  175. }
  176. }
  177. printf("Iterations: Rounds %d Evals %d\n", rounds, evals);
  178. printf("Times: Cheby %f Func %f EvalWeight %f\n",
  179. m_stats_cheby, m_stats_func, m_stats_weight);
  180. return final;
  181. }
  182. void RemezSolver::Step()
  183. {
  184. /* Pick up x_i where error will be 0 and compute f(x_i) */
  185. array<real> fxn;
  186. for (int i = 0; i < m_order + 2; i++)
  187. fxn.Push(EvalFunc(m_control[i]));
  188. /* We build a matrix of Chebishev evaluations: row i contains the
  189. * evaluations of x_i for polynomial order n = 0, 1, ... */
  190. LinearSystem<real> system(m_order + 2);
  191. for (int n = 0; n < m_order + 1; n++)
  192. {
  193. auto p = polynomial<real>::chebyshev(n);
  194. for (int i = 0; i < m_order + 2; i++)
  195. system.m(i, n) = p.eval(m_control[i]);
  196. }
  197. /* The last line of the system is the oscillating error */
  198. for (int i = 0; i < m_order + 2; i++)
  199. {
  200. real error = fabs(EvalWeight(m_control[i]));
  201. system.m(i, m_order + 1) = (i & 1) ? error : -error;
  202. }
  203. /* Solve the system */
  204. system = system.inv();
  205. /* Compute new polynomial estimate */
  206. m_estimate = polynomial<real>();
  207. for (int n = 0; n < m_order + 1; n++)
  208. {
  209. real weight = 0;
  210. for (int i = 0; i < m_order + 2; i++)
  211. weight += system.m(n, i) * fxn[i];
  212. m_estimate += weight * polynomial<real>::chebyshev(n);
  213. }
  214. /* Compute the error */
  215. real error = 0;
  216. for (int i = 0; i < m_order + 2; i++)
  217. error += system.m(m_order + 1, i) * fxn[i];
  218. }
  219. void RemezSolver::PrintPoly()
  220. {
  221. using std::printf;
  222. /* Transform our polynomial in the [-1..1] range into a polynomial
  223. * in the [a..b] range by composing it with q:
  224. * q(x) = 2x / (b-a) - (b+a) / (b-a) */
  225. polynomial<real> q ({ -m_k1 / m_k2, real(1) / m_k2 });
  226. polynomial<real> r = m_estimate.eval(q);
  227. printf("Polynomial estimate: ");
  228. for (int j = 0; j < m_order + 1; j++)
  229. {
  230. if (j)
  231. printf(" + x**%i * ", j);
  232. r[j].print(m_decimals);
  233. }
  234. printf("\n\n");
  235. }
  236. real RemezSolver::EvalCheby(real const &x)
  237. {
  238. Timer t;
  239. real ret = m_estimate.eval(x);
  240. m_stats_cheby += t.Get();
  241. return ret;
  242. }
  243. real RemezSolver::EvalFunc(real const &x)
  244. {
  245. Timer t;
  246. real ret = m_func(x * m_k2 + m_k1);
  247. m_stats_func += t.Get();
  248. return ret;
  249. }
  250. real RemezSolver::EvalWeight(real const &x)
  251. {
  252. Timer t;
  253. real ret = m_weight ? m_weight(x * m_k2 + m_k1) : real(1);
  254. m_stats_weight += t.Get();
  255. return ret;
  256. }