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.

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