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.
 
 
 

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