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.
 
 
 

360 lines
9.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 defined HAVE_CONFIG_H
  11. # include "config.h"
  12. #endif
  13. #include "core.h"
  14. #include <lol/math/real.h>
  15. #include "matrix.h"
  16. #include "solver.h"
  17. using lol::real;
  18. RemezSolver::RemezSolver(int order, int decimals)
  19. : m_order(order),
  20. m_decimals(decimals)
  21. {
  22. }
  23. void RemezSolver::Run(real a, real b,
  24. RemezSolver::RealFunc *func,
  25. RemezSolver::RealFunc *weight)
  26. {
  27. using std::printf;
  28. m_func = func;
  29. m_weight = weight;
  30. m_k1 = (b + a) / 2;
  31. m_k2 = (b - a) / 2;
  32. m_invk2 = re(m_k2);
  33. m_invk1 = -m_k1 * m_invk2;
  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. real RemezSolver::EvalCheby(real const &x)
  54. {
  55. real ret = 0.0, xn = 1.0;
  56. for (int i = 0; i < m_order + 1; i++)
  57. {
  58. real mul = 0;
  59. for (int j = 0; j < m_order + 1; j++)
  60. mul += m_coeff[j] * (real)Cheby(j, i);
  61. ret += mul * xn;
  62. xn *= x;
  63. }
  64. return ret;
  65. }
  66. void RemezSolver::Init()
  67. {
  68. /* m_order + 1 Chebyshev coefficients, plus 1 error value */
  69. m_coeff.Resize(m_order + 2);
  70. /* m_order + 1 zeroes of the error function */
  71. m_zeroes.Resize(m_order + 1);
  72. /* m_order + 2 control points */
  73. m_control.Resize(m_order + 2);
  74. /* Pick up x_i where error will be 0 and compute f(x_i) */
  75. Array<real> fxn;
  76. for (int i = 0; i < m_order + 1; i++)
  77. {
  78. m_zeroes[i] = (real)(2 * i - m_order) / (real)(m_order + 1);
  79. fxn.Push(EvalFunc(m_zeroes[i]));
  80. }
  81. /* We build a matrix of Chebishev evaluations: row i contains the
  82. * evaluations of x_i for polynomial order n = 0, 1, ... */
  83. Matrix<real> mat(m_order + 1, m_order + 1);
  84. for (int i = 0; i < m_order + 1; i++)
  85. {
  86. /* Compute the powers of x_i */
  87. Array<real> powers;
  88. powers.Push(real(1.0));
  89. for (int n = 1; n < m_order + 1; n++)
  90. powers.Push(powers.Last() * m_zeroes[i]);
  91. /* Compute the Chebishev evaluations at x_i */
  92. for (int n = 0; n < m_order + 1; n++)
  93. {
  94. real sum = 0.0;
  95. for (int k = 0; k < m_order + 1; k++)
  96. sum += (real)Cheby(n, k) * powers[k];
  97. mat.m(i, n) = sum;
  98. }
  99. }
  100. /* Solve the system */
  101. mat = mat.inv();
  102. /* Compute interpolation coefficients */
  103. for (int j = 0; j < m_order + 1; j++)
  104. {
  105. m_coeff[j] = 0;
  106. for (int i = 0; i < m_order + 1; i++)
  107. m_coeff[j] += mat.m(j, i) * fxn[i];
  108. }
  109. }
  110. void RemezSolver::FindZeroes()
  111. {
  112. /* Find m_order + 1 zeroes of the error function. No need to
  113. * compute the relative error: its zeroes are at the same
  114. * place as the absolute error! */
  115. for (int i = 0; i < m_order + 1; i++)
  116. {
  117. struct { real value, error; } left, right, mid;
  118. left.value = m_control[i];
  119. left.error = EvalCheby(left.value) - EvalFunc(left.value);
  120. right.value = m_control[i + 1];
  121. right.error = EvalCheby(right.value) - EvalFunc(right.value);
  122. static real limit = ldexp((real)1, -500);
  123. static real zero = (real)0;
  124. while (fabs(left.value - right.value) > limit)
  125. {
  126. mid.value = (left.value + right.value) / 2;
  127. mid.error = EvalCheby(mid.value) - EvalFunc(mid.value);
  128. if ((left.error <= zero && mid.error <= zero)
  129. || (left.error >= zero && mid.error >= zero))
  130. left = mid;
  131. else
  132. right = mid;
  133. }
  134. m_zeroes[i] = mid.value;
  135. }
  136. }
  137. real RemezSolver::FindExtrema()
  138. {
  139. using std::printf;
  140. /* Find m_order + 2 extrema of the error function. We need to
  141. * compute the relative error, since its extrema are at slightly
  142. * different locations than the absolute error’s. */
  143. real final = 0;
  144. for (int i = 0; i < m_order + 2; i++)
  145. {
  146. real a = -1, b = 1;
  147. if (i > 0)
  148. a = m_zeroes[i - 1];
  149. if (i < m_order + 1)
  150. b = m_zeroes[i];
  151. for (int round = 0; ; round++)
  152. {
  153. real maxerror = 0, maxweight = 0;
  154. int best = -1;
  155. real c = a, delta = (b - a) / 4;
  156. for (int k = 0; k <= 4; k++)
  157. {
  158. if (round == 0 || (k & 1))
  159. {
  160. real error = fabs(EvalCheby(c) - EvalFunc(c));
  161. real weight = fabs(Weight(c));
  162. /* if error/weight >= maxerror/maxweight */
  163. if (error * maxweight >= maxerror * weight)
  164. {
  165. maxerror = error;
  166. maxweight = weight;
  167. best = k;
  168. }
  169. }
  170. c += delta;
  171. }
  172. switch (best)
  173. {
  174. case 0:
  175. b = a + delta * 2;
  176. break;
  177. case 4:
  178. a = b - delta * 2;
  179. break;
  180. default:
  181. b = a + delta * (best + 1);
  182. a = a + delta * (best - 1);
  183. break;
  184. }
  185. if (delta < m_epsilon)
  186. {
  187. real e = fabs(maxerror / maxweight);
  188. if (e > final)
  189. final = e;
  190. m_control[i] = (a + b) / 2;
  191. break;
  192. }
  193. }
  194. }
  195. return final;
  196. }
  197. void RemezSolver::Step()
  198. {
  199. /* Pick up x_i where error will be 0 and compute f(x_i) */
  200. Array<real> fxn;
  201. for (int i = 0; i < m_order + 2; i++)
  202. fxn.Push(EvalFunc(m_control[i]));
  203. /* We build a matrix of Chebishev evaluations: row i contains the
  204. * evaluations of x_i for polynomial order n = 0, 1, ... */
  205. Matrix<real> mat(m_order + 2, m_order + 2);
  206. for (int i = 0; i < m_order + 2; i++)
  207. {
  208. /* Compute the powers of x_i */
  209. Array<real> powers;
  210. powers.Push(real(1.0));
  211. for (int n = 1; n < m_order + 1; n++)
  212. powers.Push(powers.Last() * m_control[i]);
  213. /* Compute the Chebishev evaluations at x_i */
  214. for (int n = 0; n < m_order + 1; n++)
  215. {
  216. real sum = 0.0;
  217. for (int k = 0; k < m_order + 1; k++)
  218. sum += (real)Cheby(n, k) * powers[k];
  219. mat.m(i, n) = sum;
  220. }
  221. if (i & 1)
  222. mat.m(i, m_order + 1) = fabs(Weight(m_control[i]));
  223. else
  224. mat.m(i, m_order + 1) = -fabs(Weight(m_control[i]));
  225. }
  226. /* Solve the system */
  227. mat = mat.inv();
  228. /* Compute interpolation coefficients */
  229. for (int j = 0; j < m_order + 1; j++)
  230. {
  231. m_coeff[j] = 0;
  232. for (int i = 0; i < m_order + 2; i++)
  233. m_coeff[j] += mat.m(j, i) * fxn[i];
  234. }
  235. /* Compute the error */
  236. real error = 0;
  237. for (int i = 0; i < m_order + 2; i++)
  238. error += mat.m(m_order + 1, i) * fxn[i];
  239. }
  240. int RemezSolver::Cheby(int n, int k)
  241. {
  242. if (k > n || k < 0)
  243. return 0;
  244. if (n <= 1)
  245. return (n ^ k ^ 1) & 1;
  246. return 2 * Cheby(n - 1, k - 1) - Cheby(n - 2, k);
  247. }
  248. int RemezSolver::Comb(int n, int k)
  249. {
  250. if (k == 0 || k == n)
  251. return 1;
  252. return Comb(n - 1, k - 1) + Comb(n - 1, k);
  253. }
  254. void RemezSolver::PrintPoly()
  255. {
  256. using std::printf;
  257. /* Transform Chebyshev polynomial weights into powers of X^i
  258. * in the [-1..1] range. */
  259. Array<real> bn;
  260. for (int i = 0; i < m_order + 1; i++)
  261. {
  262. real tmp = 0;
  263. for (int j = 0; j < m_order + 1; j++)
  264. tmp += m_coeff[j] * (real)Cheby(j, i);
  265. bn.Push(tmp);
  266. }
  267. /* Transform a polynomial in the [-1..1] range into a polynomial
  268. * in the [a..b] range. */
  269. Array<real> k1p, k2p, an;
  270. for (int i = 0; i < m_order + 1; i++)
  271. {
  272. k1p.Push(i ? k1p[i - 1] * m_invk1 : (real)1);
  273. k2p.Push(i ? k2p[i - 1] * m_invk2 : (real)1);
  274. }
  275. for (int i = 0; i < m_order + 1; i++)
  276. {
  277. real tmp = 0;
  278. for (int j = i; j < m_order + 1; j++)
  279. tmp += (real)Comb(j, i) * k1p[j - i] * bn[j];
  280. an.Push(tmp * k2p[i]);
  281. }
  282. printf("Polynomial estimate: ");
  283. for (int j = 0; j < m_order + 1; j++)
  284. {
  285. if (j)
  286. printf(" + x**%i * ", j);
  287. an[j].print(m_decimals);
  288. }
  289. printf("\n\n");
  290. }
  291. real RemezSolver::EvalFunc(real const &x)
  292. {
  293. return m_func(x * m_k2 + m_k1);
  294. }
  295. real RemezSolver::Weight(real const &x)
  296. {
  297. if (m_weight)
  298. return m_weight(x * m_k2 + m_k1);
  299. return 1;
  300. }