388 lines
9.8 KiB

  1. //
  2. // Lol Engine - Sample math program: Chebyshev polynomials
  3. //
  4. // Copyright: (c) 2005-2011 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://sam.zoy.org/projects/COPYING.WTFPL for more details.
  9. //
  10. #if defined HAVE_CONFIG_H
  11. # include "config.h"
  12. #endif
  13. #include <cstring>
  14. #include <cstdio>
  15. #include "core.h"
  16. using namespace lol;
  17. using namespace std;
  18. /* The order of the approximation we're looking for */
  19. static int const ORDER = 4;
  20. /* The function we want to approximate */
  21. static real myfun(real const &x)
  22. {
  23. return exp(x);
  24. //if (!x)
  25. // return real::R_PI_2;
  26. //return sin(x * real::R_PI_2) / x;
  27. }
  28. static real myerror(real const &x)
  29. {
  30. return myfun(x);
  31. //return real::R_1;
  32. }
  33. /* Naive matrix inversion */
  34. template<int N> struct Matrix
  35. {
  36. inline Matrix() {}
  37. Matrix(real x)
  38. {
  39. for (int j = 0; j < N; j++)
  40. for (int i = 0; i < N; i++)
  41. if (i == j)
  42. m[i][j] = x;
  43. else
  44. m[i][j] = 0;
  45. }
  46. Matrix<N> inv() const
  47. {
  48. Matrix a = *this, b((real)1.0);
  49. /* Inversion method: iterate through all columns and make sure
  50. * all the terms are 1 on the diagonal and 0 everywhere else */
  51. for (int i = 0; i < N; i++)
  52. {
  53. /* If the expected coefficient is zero, add one of
  54. * the other lines. The first we meet will do. */
  55. if ((double)a.m[i][i] == 0.0)
  56. {
  57. for (int j = i + 1; j < N; j++)
  58. {
  59. if ((double)a.m[i][j] == 0.0)
  60. continue;
  61. /* Add row j to row i */
  62. for (int n = 0; n < N; n++)
  63. {
  64. a.m[n][i] += a.m[n][j];
  65. b.m[n][i] += b.m[n][j];
  66. }
  67. break;
  68. }
  69. }
  70. /* Now we know the diagonal term is non-zero. Get its inverse
  71. * and use that to nullify all other terms in the column */
  72. real x = (real)1.0 / a.m[i][i];
  73. for (int j = 0; j < N; j++)
  74. {
  75. if (j == i)
  76. continue;
  77. real mul = x * a.m[i][j];
  78. for (int n = 0; n < N; n++)
  79. {
  80. a.m[n][j] -= mul * a.m[n][i];
  81. b.m[n][j] -= mul * b.m[n][i];
  82. }
  83. }
  84. /* Finally, ensure the diagonal term is 1 */
  85. for (int n = 0; n < N; n++)
  86. {
  87. a.m[n][i] *= x;
  88. b.m[n][i] *= x;
  89. }
  90. }
  91. return b;
  92. }
  93. void print() const
  94. {
  95. for (int j = 0; j < N; j++)
  96. {
  97. for (int i = 0; i < N; i++)
  98. printf("%9.5f ", (double)m[j][i]);
  99. printf("\n");
  100. }
  101. }
  102. real m[N][N];
  103. };
  104. static int cheby[ORDER + 1][ORDER + 1];
  105. /* Fill the Chebyshev tables */
  106. static void cheby_init()
  107. {
  108. memset(cheby, 0, sizeof(cheby));
  109. cheby[0][0] = 1;
  110. cheby[1][1] = 1;
  111. for (int i = 2; i < ORDER + 1; i++)
  112. {
  113. cheby[i][0] = -cheby[i - 2][0];
  114. for (int j = 1; j < ORDER + 1; j++)
  115. cheby[i][j] = 2 * cheby[i - 1][j - 1] - cheby[i - 2][j];
  116. }
  117. }
  118. static void cheby_coeff(real *coeff, real *bn)
  119. {
  120. for (int i = 0; i < ORDER + 1; i++)
  121. {
  122. bn[i] = 0;
  123. for (int j = 0; j < ORDER + 1; j++)
  124. if (cheby[j][i])
  125. bn[i] += coeff[j] * (real)cheby[j][i];
  126. }
  127. }
  128. static real cheby_eval(real *coeff, real const &x)
  129. {
  130. real ret = 0.0, xn = 1.0;
  131. for (int i = 0; i < ORDER + 1; i++)
  132. {
  133. real mul = 0;
  134. for (int j = 0; j < ORDER + 1; j++)
  135. if (cheby[j][i])
  136. mul += coeff[j] * (real)cheby[j][i];
  137. ret += mul * xn;
  138. xn *= x;
  139. }
  140. return ret;
  141. }
  142. static void remez_init(real *coeff, real *zeroes)
  143. {
  144. /* Pick up x_i where error will be 0 and compute f(x_i) */
  145. real fxn[ORDER + 1];
  146. for (int i = 0; i < ORDER + 1; i++)
  147. {
  148. zeroes[i] = (real)(2 * i - ORDER) / (real)(ORDER + 1);
  149. fxn[i] = myfun(zeroes[i]);
  150. }
  151. /* We build a matrix of Chebishev evaluations: row i contains the
  152. * evaluations of x_i for polynomial order n = 0, 1, ... */
  153. Matrix<ORDER + 1> mat;
  154. for (int i = 0; i < ORDER + 1; i++)
  155. {
  156. /* Compute the powers of x_i */
  157. real powers[ORDER + 1];
  158. powers[0] = 1.0;
  159. for (int n = 1; n < ORDER + 1; n++)
  160. powers[n] = powers[n - 1] * zeroes[i];
  161. /* Compute the Chebishev evaluations at x_i */
  162. for (int n = 0; n < ORDER + 1; n++)
  163. {
  164. real sum = 0.0;
  165. for (int k = 0; k < ORDER + 1; k++)
  166. if (cheby[n][k])
  167. sum += (real)cheby[n][k] * powers[k];
  168. mat.m[i][n] = sum;
  169. }
  170. }
  171. /* Solve the system */
  172. mat = mat.inv();
  173. /* Compute interpolation coefficients */
  174. for (int j = 0; j < ORDER + 1; j++)
  175. {
  176. coeff[j] = 0;
  177. for (int i = 0; i < ORDER + 1; i++)
  178. coeff[j] += mat.m[j][i] * fxn[i];
  179. }
  180. }
  181. static void remez_findzeroes(real *coeff, real *zeroes, real *control)
  182. {
  183. for (int i = 0; i < ORDER + 1; i++)
  184. {
  185. real a = control[i];
  186. real ea = cheby_eval(coeff, a) - myfun(a);
  187. real b = control[i + 1];
  188. real eb = cheby_eval(coeff, b) - myfun(b);
  189. while (fabs(a - b) > (real)1e-140)
  190. {
  191. real c = (a + b) * (real)0.5;
  192. real ec = cheby_eval(coeff, c) - myfun(c);
  193. if ((ea < (real)0 && ec < (real)0)
  194. || (ea > (real)0 && ec > (real)0))
  195. {
  196. a = c;
  197. ea = ec;
  198. }
  199. else
  200. {
  201. b = c;
  202. eb = ec;
  203. }
  204. }
  205. zeroes[i] = a;
  206. }
  207. }
  208. static void remez_finderror(real *coeff, real *zeroes, real *control)
  209. {
  210. real final = 0;
  211. for (int i = 0; i < ORDER + 2; i++)
  212. {
  213. real a = -1, b = 1;
  214. if (i > 0)
  215. a = zeroes[i - 1];
  216. if (i < ORDER + 1)
  217. b = zeroes[i];
  218. printf("Error for [%g..%g]: ", (double)a, (double)b);
  219. for (;;)
  220. {
  221. real c = a, delta = (b - a) / (real)10.0;
  222. real maxerror = 0;
  223. int best = -1;
  224. for (int k = 0; k <= 10; k++)
  225. {
  226. real e = fabs(cheby_eval(coeff, c) - myfun(c));
  227. if (e > maxerror)
  228. {
  229. maxerror = e;
  230. best = k;
  231. }
  232. c += delta;
  233. }
  234. if (best == 0)
  235. best = 1;
  236. if (best == 10)
  237. best = 9;
  238. b = a + (real)(best + 1) * delta;
  239. a = a + (real)(best - 1) * delta;
  240. if (b - a < (real)1e-15)
  241. {
  242. if (maxerror > final)
  243. final = maxerror;
  244. control[i] = (a + b) * (real)0.5;
  245. printf("%g (at %g)\n", (double)maxerror, (double)control[i]);
  246. break;
  247. }
  248. }
  249. }
  250. printf("Final error: %g\n", (double)final);
  251. }
  252. static void remez_step(real *coeff, real *control)
  253. {
  254. /* Pick up x_i where error will be 0 and compute f(x_i) */
  255. real fxn[ORDER + 2];
  256. for (int i = 0; i < ORDER + 2; i++)
  257. fxn[i] = myfun(control[i]);
  258. /* We build a matrix of Chebishev evaluations: row i contains the
  259. * evaluations of x_i for polynomial order n = 0, 1, ... */
  260. Matrix<ORDER + 2> mat;
  261. for (int i = 0; i < ORDER + 2; i++)
  262. {
  263. /* Compute the powers of x_i */
  264. real powers[ORDER + 1];
  265. powers[0] = 1.0;
  266. for (int n = 1; n < ORDER + 1; n++)
  267. powers[n] = powers[n - 1] * control[i];
  268. /* Compute the Chebishev evaluations at x_i */
  269. for (int n = 0; n < ORDER + 1; n++)
  270. {
  271. real sum = 0.0;
  272. for (int k = 0; k < ORDER + 1; k++)
  273. if (cheby[n][k])
  274. sum += (real)cheby[n][k] * powers[k];
  275. mat.m[i][n] = sum;
  276. }
  277. if (i & 1)
  278. mat.m[i][ORDER + 1] = fabs(myerror(control[i]));
  279. else
  280. mat.m[i][ORDER + 1] = -fabs(myerror(control[i]));
  281. }
  282. /* Solve the system */
  283. mat = mat.inv();
  284. /* Compute interpolation coefficients */
  285. for (int j = 0; j < ORDER + 1; j++)
  286. {
  287. coeff[j] = 0;
  288. for (int i = 0; i < ORDER + 2; i++)
  289. coeff[j] += mat.m[j][i] * fxn[i];
  290. }
  291. /* Compute the error */
  292. real error = 0;
  293. for (int i = 0; i < ORDER + 2; i++)
  294. error += mat.m[ORDER + 1][i] * fxn[i];
  295. }
  296. int main(int argc, char **argv)
  297. {
  298. cheby_init();
  299. /* ORDER + 1 chebyshev coefficients and 1 error value */
  300. real coeff[ORDER + 2];
  301. /* ORDER + 1 zeroes of the error function */
  302. real zeroes[ORDER + 1];
  303. /* ORDER + 2 control points */
  304. real control[ORDER + 2];
  305. real bn[ORDER + 1];
  306. remez_init(coeff, zeroes);
  307. cheby_coeff(coeff, bn);
  308. for (int j = 0; j < ORDER + 1; j++)
  309. printf("%s%12.10gx^%i", j ? "+" : "", (double)bn[j], j);
  310. printf("\n");
  311. for (int n = 0; n < 200; n++)
  312. {
  313. remez_finderror(coeff, zeroes, control);
  314. remez_step(coeff, control);
  315. cheby_coeff(coeff, bn);
  316. for (int j = 0; j < ORDER + 1; j++)
  317. printf("%s%12.10gx^%i", j ? "+" : "", (double)bn[j], j);
  318. printf("\n");
  319. remez_findzeroes(coeff, zeroes, control);
  320. }
  321. remez_finderror(coeff, zeroes, control);
  322. remez_step(coeff, control);
  323. cheby_coeff(coeff, bn);
  324. for (int j = 0; j < ORDER + 1; j++)
  325. printf("%s%12.10gx^%i", j ? "+" : "", (double)bn[j], j);
  326. printf("\n");
  327. return EXIT_SUCCESS;
  328. }