Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

435 lignes
12 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright © 2010—2020 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // Lol Engine is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #pragma once
  13. //
  14. // The polynomial class
  15. // ————————————————————
  16. // The data structure is a simple dynamic array of scalars, with the
  17. // added guarantee that the leading coefficient is always non-zero.
  18. //
  19. #include "../base/private/features.h"
  20. #include <vector> // std::vector
  21. #include <functional> // std::function
  22. #include <tuple> // std::tuple
  23. #include <cassert> // assert()
  24. namespace lol
  25. {
  26. template<typename T>
  27. struct [[nodiscard]] polynomial
  28. {
  29. /* The zero polynomial */
  30. explicit inline polynomial() {}
  31. /* A constant polynomial */
  32. explicit inline polynomial(T const &a)
  33. {
  34. m_coefficients.push_back(a);
  35. reduce_degree();
  36. }
  37. /* Create a polynomial from a list of coefficients */
  38. explicit polynomial(std::initializer_list<T> const &init)
  39. {
  40. for (auto a : init)
  41. m_coefficients.push_back(a);
  42. reduce_degree();
  43. }
  44. /* Factory for Chebyshev polynomials */
  45. static polynomial<T> chebyshev(int n)
  46. {
  47. /* Use T0(x) = 1, T1(x) = x, Tn(x) = 2 x Tn-1(x) - Tn-2(x) */
  48. std::function<int64_t (int, int)> coeff = [&](int i, int j) -> int64_t
  49. {
  50. if (i > j || i < 0 || ((j ^ i) & 1))
  51. return (int64_t)0;
  52. if (j < 2)
  53. return (int64_t)1;
  54. return 2 * coeff(i - 1, j - 1) - coeff(i, j - 2);
  55. };
  56. polynomial<T> ret;
  57. for (int k = 0; k <= n; ++k)
  58. ret.m_coefficients.push_back(T(coeff(k, n)));
  59. return ret;
  60. }
  61. /* We define the zero polynomial (with no coefficients) as having
  62. * degree -1 on purpose. */
  63. inline int degree() const
  64. {
  65. return (int)m_coefficients.size() - 1;
  66. }
  67. /* Set one of the polynomial’s coefficients */
  68. void set(int n, T const &a)
  69. {
  70. assert(n >= 0);
  71. if (n > degree() && !a)
  72. return;
  73. while (n > degree())
  74. m_coefficients.push_back(T(0));
  75. m_coefficients[n] = a;
  76. reduce_degree();
  77. }
  78. /* Evaluate polynomial at a given value. This method can also
  79. * be used to compose polynomials, i.e. using another polynomial
  80. * as the value instead of a scalar. */
  81. template<typename U> [[nodiscard]] U eval(U x) const
  82. {
  83. U ret(leading());
  84. for (int i = degree() - 1; i >= 0; --i)
  85. ret = ret * x + U(m_coefficients[i]);
  86. return ret;
  87. }
  88. polynomial<T> derive() const
  89. {
  90. /* No need to reduce the degree after deriving. */
  91. polynomial<T> ret;
  92. for (int i = 1; i <= degree(); ++i)
  93. ret.m_coefficients.push_back(m_coefficients[i] * T(i));
  94. return ret;
  95. }
  96. std::vector<T> roots() const
  97. {
  98. /* For now we can only solve polynomials of degrees 0, 1, 2 or 3. */
  99. assert(degree() >= 0 && degree() <= 3);
  100. if (degree() == 0)
  101. {
  102. /* p(x) = a > 0 */
  103. return {};
  104. }
  105. else if (degree() == 1)
  106. {
  107. /* p(x) = ax + b */
  108. T const &a = m_coefficients[1];
  109. T const &b = m_coefficients[0];
  110. return { -b / a };
  111. }
  112. else if (degree() == 2)
  113. {
  114. /* p(x) = ax² + bx + c */
  115. T const &a = m_coefficients[2];
  116. T const &b = m_coefficients[1];
  117. T const &c = m_coefficients[0];
  118. T const k = b / (a + a);
  119. T const delta = k * k - c / a;
  120. if (delta < T(0))
  121. {
  122. return {};
  123. }
  124. else if (delta > T(0))
  125. {
  126. T const sqrt_delta = sqrt(delta);
  127. return { -k - sqrt_delta, -k + sqrt_delta };
  128. }
  129. else
  130. {
  131. return { -k };
  132. }
  133. }
  134. else if (degree() == 3)
  135. {
  136. static T const pi = acos(T(-1));
  137. /* p(x) = ax³ + bx² + cx + d */
  138. T const &a = m_coefficients[3];
  139. T const &b = m_coefficients[2];
  140. T const &c = m_coefficients[1];
  141. T const &d = m_coefficients[0];
  142. /* Find k, m, and n such that p(x - k) / a = x³ + mx + n
  143. * q(x) = p(x - k) / a
  144. * = x³ + (b/a-3k) x² + ((c-2bk)/a+3k²) x + (d-ck+bk²)/a-k³
  145. *
  146. * So we want k = b/3a and thus:
  147. * q(x) = x³ + (c-bk)/a x + (d-ck+2bk²/3)/a
  148. *
  149. * k = b / 3a
  150. * m = (c - bk) / a
  151. * n = (d - ck + 2bk²/3) / a */
  152. T const k = b / (T(3) * a);
  153. T const m = (c - b * k) / a;
  154. T const n = ((T(2) / T(3) * b * k - c) * k + d) / a;
  155. /* Let x = u + v, such that 3uv = -m, then:
  156. * q(u + v) = u³ + v³ + n
  157. *
  158. * We then need to solve the following system:
  159. * u³v³ = -m³/27
  160. * u³ + v³ = -n
  161. *
  162. * which gives :
  163. * Δ = n² + 4m³/27
  164. * u³ - v³ = √Δ
  165. * u³ + v³ = -n
  166. *
  167. * u³,v³ = (-n ± √Δ) / 2
  168. */
  169. T const delta = n * n + T(4) / T(27) * m * m * m;
  170. /* Because 3uv = -m and m is not complex
  171. * angle(u³) + angle(v³) must equal 0.
  172. *
  173. * This is why we compute u³ and v³ by norm and angle separately
  174. * instead of using a std::complex class */
  175. T u_norm, u3_angle;
  176. T v_norm, v3_angle;
  177. if (delta < 0)
  178. {
  179. v_norm = u_norm = sqrt(m / T(-3));
  180. u3_angle = atan2(sqrt(-delta), -n);
  181. v3_angle = -u3_angle;
  182. }
  183. else
  184. {
  185. T const sqrt_delta = sqrt(delta);
  186. u_norm = cbrt(abs(n - sqrt_delta) / T(2));
  187. v_norm = cbrt(abs(n + sqrt_delta) / T(2));
  188. u3_angle = (n >= sqrt_delta) ? pi : 0;
  189. v3_angle = (n <= -sqrt_delta) ? 0 : -pi;
  190. }
  191. T solutions[3];
  192. for (int i : { 0, 1, 2 })
  193. {
  194. T u_angle = (u3_angle + T(2 * i) * pi) / T(3);
  195. T v_angle = (v3_angle - T(2 * i) * pi) / T(3);
  196. solutions[i] = u_norm * cos(u_angle) + v_norm * cos(v_angle);
  197. }
  198. if (a == d && b == c && b == T(3)*a) // triple solution
  199. {
  200. return { solutions[0] - k };
  201. }
  202. // if root of the derivative is also root of the current polynomial, we have a double root.
  203. for (auto root : (polynomial<T>{ c, T(2) * b, T(3) * a }).roots())
  204. {
  205. if (eval(root) == T(0))
  206. return { (solutions[0] + solutions[2]) / T(2) - k,
  207. solutions[1] - k };
  208. }
  209. // we have 3 or 1 root depending on delta sign
  210. if (delta > 0)
  211. {
  212. return { solutions[0] - k };
  213. }
  214. else // if (delta < 0) 3 real solutions
  215. {
  216. return { solutions[0] - k,
  217. solutions[1] - k,
  218. solutions[2] - k };
  219. }
  220. }
  221. /* It is an error to reach this point. */
  222. assert(false);
  223. return {};
  224. }
  225. /* Access individual coefficients. This is read-only and returns a
  226. * copy because we cannot let the user mess with the integrity of
  227. * the structure (i.e. the guarantee that the leading coefficient
  228. * remains non-zero). */
  229. [[nodiscard]] inline T operator[](ptrdiff_t n) const
  230. {
  231. if (n < 0 || n > degree())
  232. return T(0);
  233. return m_coefficients[n];
  234. }
  235. /* Return the leading coefficient */
  236. [[nodiscard]] inline T leading() const
  237. {
  238. return (*this)[degree()];
  239. }
  240. /* Boolean operations */
  241. bool operator !() const
  242. {
  243. return degree() < 0;
  244. }
  245. operator bool() const
  246. {
  247. return !!*this;
  248. }
  249. /* Unary plus */
  250. polynomial<T> operator +() const
  251. {
  252. return *this;
  253. }
  254. /* Unary minus */
  255. polynomial<T> operator -() const
  256. {
  257. polynomial<T> ret;
  258. for (auto a : m_coefficients)
  259. ret.m_coefficients.push_back(-a);
  260. return ret;
  261. }
  262. /* Add two polynomials */
  263. polynomial<T> &operator +=(polynomial<T> const &p)
  264. {
  265. int min_degree = std::min(p.degree(), degree());
  266. for (int i = 0; i <= min_degree; ++i)
  267. m_coefficients[i] += p[i];
  268. for (int i = min_degree + 1; i <= p.degree(); ++i)
  269. m_coefficients.push_back(p[i]);
  270. reduce_degree();
  271. return *this;
  272. }
  273. polynomial<T> operator +(polynomial<T> const &p) const
  274. {
  275. return polynomial<T>(*this) += p;
  276. }
  277. /* Subtract two polynomials */
  278. polynomial<T> &operator -=(polynomial<T> const &p)
  279. {
  280. return *this += -p;
  281. }
  282. polynomial<T> operator -(polynomial<T> const &p) const
  283. {
  284. return polynomial<T>(*this) += -p;
  285. }
  286. /* Multiply polynomial by a scalar */
  287. polynomial<T> &operator *=(T const &k)
  288. {
  289. for (auto &a : m_coefficients)
  290. a *= k;
  291. reduce_degree();
  292. return *this;
  293. }
  294. polynomial<T> operator *(T const &k) const
  295. {
  296. return polynomial<T>(*this) *= k;
  297. }
  298. /* Divide polynomial by a scalar */
  299. polynomial<T> &operator /=(T const &k)
  300. {
  301. return *this *= (T(1) / k);
  302. }
  303. polynomial<T> operator /(T const &k) const
  304. {
  305. return *this * (T(1) / k);
  306. }
  307. /* Multiply polynomial by another polynomial */
  308. polynomial<T> &operator *=(polynomial<T> const &p)
  309. {
  310. return *this = *this * p;
  311. }
  312. polynomial<T> operator *(polynomial<T> const &p) const
  313. {
  314. polynomial<T> ret;
  315. polynomial<T> const &q = *this;
  316. if (p.degree() >= 0 && q.degree() >= 0)
  317. {
  318. int n = p.degree() + q.degree();
  319. for (int i = 0; i <= n; ++i)
  320. ret.m_coefficients.push_back(T(0));
  321. for (int i = 0; i <= p.degree(); ++i)
  322. for (int j = 0; j <= q.degree(); ++j)
  323. ret.m_coefficients[i + j] += p[i] * q[j];
  324. ret.reduce_degree();
  325. }
  326. return ret;
  327. }
  328. /* Divide a polynomial by another one. There is no /= variant because
  329. * the return value contains both the quotient and the remainder. */
  330. std::tuple<polynomial<T>, polynomial<T>> operator /(polynomial<T> p) const
  331. {
  332. assert(p.degree() >= 0);
  333. std::tuple<polynomial<T>, polynomial<T>> ret;
  334. polynomial<T> &quotient = std::get<0>(ret);
  335. polynomial<T> &remainder = std::get<1>(ret);
  336. remainder = *this / p.leading();
  337. p /= p.leading();
  338. for (int n = remainder.degree() - p.degree(); n >= 0; --n)
  339. {
  340. quotient.set(n, remainder.leading());
  341. for (int i = 0; i < p.degree(); ++i)
  342. remainder.m_coefficients[n + i] -= remainder.leading() * p[i];
  343. (void)remainder.m_coefficients.pop_back();
  344. }
  345. return ret;
  346. }
  347. private:
  348. /* Enforce the non-zero leading coefficient rule. */
  349. void reduce_degree()
  350. {
  351. while (m_coefficients.size() && !m_coefficients.back())
  352. m_coefficients.pop_back();
  353. }
  354. /* The polynomial coefficients */
  355. std::vector<T> m_coefficients;
  356. };
  357. template<typename T>
  358. polynomial<T> operator *(T const &k, polynomial<T> const &p)
  359. {
  360. /* We assume k * p is the same as p * k */
  361. return p * k;
  362. }
  363. } /* namespace lol */