GLS.Polynomials.pas 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.Polynomials;
  5. (*
  6. Utility functions for manipulationg and solving polynomials.
  7. Direct solving is supported for polynoms up to the 4th degree.
  8. Polynom solving code based on Jochen Schwarze ([email protected]) solver
  9. published in Graphics Gem (1990).
  10. Adapted to pascal by Eric Grange ([email protected]), if you find
  11. errors, they are probably mine. Note that contrary to the original code,
  12. the functions accept 'zero' values for any of the parameters.
  13. I also made some changes for certain limit cases that (seemingly) weren't
  14. properly handled, these are marked by comments in the code.
  15. Note: in progress - limited precision.
  16. *)
  17. interface
  18. {$I GLScene.inc}
  19. uses
  20. GLS.VectorGeometry;
  21. type
  22. TDoubleArray = array of Double;
  23. // Computes polynom's value for given x
  24. function EvalPolynom(const poly: TDoubleArray; const x: Double): Double;
  25. // Calculates the polynom's derivative
  26. function DerivatedPolynom(const poly: TDoubleArray): TDoubleArray;
  27. (* Finds a root between min and max with a precision of epsilon.
  28. The evaluation of min/max must be of opposit sign *)
  29. function FindRoot(const poly: TDoubleArray; min, max, epsilon: Double): Double;
  30. (* Finds the minimum positive coef in the array in aMin.
  31. Returns true if such an item was found. *)
  32. function MinPositiveCoef(const coefs: TDoubleArray; var aMin: Double): Boolean;
  33. // Calculates the cube root of its parameter.
  34. function cbrt(const x: Double): Double;
  35. (* Computes the real roots of a real polynomial of the 2nd degree.
  36. The polynomial is of the form:
  37. A(0) + A(1)*Z + A(2)*Z**2 *)
  38. function SolveQuadric(const c: PDoubleArray): TDoubleArray;
  39. (* Computes the real roots of a real polynomial of the 3rd degree.
  40. The polynomial is of the form:
  41. A(0) + A(1)*Z + A(2)*Z**2 + A(3)*Z**3 *)
  42. function SolveCubic(const c: PDoubleArray): TDoubleArray;
  43. (* Computes the real roots of a real polynomial of the 4th degree.
  44. The polynomial is of the form:
  45. A(0) + A(1)*Z + ... + A(4)*Z**4 *)
  46. function SolveQuartic(const c: PDoubleArray): TDoubleArray;
  47. // --------------------------------------------------------------
  48. implementation
  49. // --------------------------------------------------------------
  50. const
  51. cEpsilon: Double = 1E-40;
  52. c1div3: Double = 0.3333333333333333333333333333333;
  53. cHalf: Double = 0.5;
  54. function IsZero(var v: Double): Boolean; overload;
  55. begin
  56. Result := (Abs(v) <= cEpsilon);
  57. end;
  58. function EvalPolynom(const poly: TDoubleArray; const x: Double): Double;
  59. var
  60. i, n: Integer;
  61. begin
  62. n := Length(poly);
  63. if n > 0 then
  64. begin
  65. Result := poly[n - 1];
  66. for i := n - 2 downto 0 do
  67. Result := Result * x + poly[i];
  68. end
  69. else
  70. Result := 0;
  71. end;
  72. function DerivatedPolynom(const poly: TDoubleArray): TDoubleArray;
  73. var
  74. n, i: Integer;
  75. begin
  76. n := Length(poly);
  77. if n > 1 then
  78. begin
  79. SetLength(Result, n - 1);
  80. for i := 1 to n - 1 do
  81. Result[i - 1] := poly[i] * i;
  82. end
  83. else
  84. begin
  85. SetLength(Result, 1);
  86. Result[0] := 0;
  87. end;
  88. end;
  89. function FindRoot(const poly: TDoubleArray; min, max, epsilon: Double): Double;
  90. var
  91. evMin, evMax, mid, evMid: Double;
  92. begin
  93. // handle degenerate cases first
  94. Assert(min < max);
  95. evMin := EvalPolynom(poly, min);
  96. if evMin = 0 then
  97. begin
  98. Result := min;
  99. Exit;
  100. end;
  101. evMax := EvalPolynom(poly, max);
  102. if evMax = 0 then
  103. begin
  104. Result := max;
  105. Exit;
  106. end;
  107. if evMax < 0 then
  108. begin
  109. Assert(evMin > 0);
  110. while Abs(max - min) > epsilon do
  111. begin
  112. mid := (max + min) * 0.5;
  113. evMid := EvalPolynom(poly, mid);
  114. if evMid > 0 then
  115. min := mid
  116. else
  117. max := mid;
  118. end;
  119. end
  120. else
  121. begin
  122. Assert(evMin < 0);
  123. while Abs(max - min) > epsilon do
  124. begin
  125. mid := (max + min) * 0.5;
  126. evMid := EvalPolynom(poly, mid);
  127. if evMid > 0 then
  128. max := mid
  129. else
  130. min := mid;
  131. end;
  132. end;
  133. Result := (max + min) * cHalf;
  134. end;
  135. function MinPositiveCoef(const coefs: TDoubleArray; var aMin: Double): Boolean;
  136. var
  137. n, i, j: Integer;
  138. begin
  139. n := Length(coefs);
  140. case n of
  141. 0:
  142. Result := False;
  143. 1:
  144. begin
  145. if coefs[0] >= 0 then
  146. begin
  147. aMin := coefs[0];
  148. Result := True;
  149. end
  150. else
  151. Result := False;
  152. end;
  153. 2:
  154. begin
  155. if coefs[0] >= 0 then
  156. begin
  157. aMin := coefs[0];
  158. if (coefs[1] >= 0) and (coefs[1] < aMin) then
  159. aMin := coefs[1];
  160. Result := True;
  161. end
  162. else if coefs[1] >= 0 then
  163. begin
  164. aMin := coefs[1];
  165. Result := True;
  166. end
  167. else
  168. Result := False;
  169. end;
  170. else
  171. Result := False;
  172. // find a positive value, then find lowest positive
  173. for i := 0 to n - 1 do
  174. begin
  175. if coefs[i] >= 0 then
  176. begin
  177. aMin := coefs[i];
  178. for j := i + 1 to n - 1 do
  179. begin
  180. if (coefs[j] >= 0) and (coefs[j] < aMin) then
  181. aMin := coefs[j];
  182. end;
  183. Result := True;
  184. Break;
  185. end;
  186. end;
  187. end;
  188. end;
  189. function cbrt(const x: Double): Double;
  190. begin
  191. if x > 0 then
  192. Result := PowerSingle(x, c1div3)
  193. else if x < 0 then
  194. Result := -PowerSingle(-x, c1div3)
  195. else
  196. Result := 0;
  197. end;
  198. function SolveQuadric(const c: PDoubleArray): TDoubleArray;
  199. var
  200. p, q, D, sqrt_D: Double;
  201. begin
  202. // normal form: x^2 + px + q = 0
  203. p := c^[1] / (2 * c^[2]);
  204. q := c^[0] / c^[2];
  205. D := Sqr(p) - q;
  206. if IsZero(D) then
  207. begin
  208. SetLength(Result, 1);
  209. Result[0] := -p;
  210. end
  211. else if D > 0 then
  212. begin
  213. sqrt_D := Sqrt(D);
  214. SetLength(Result, 2);
  215. Result[0] := sqrt_D - p;
  216. Result[1] := -sqrt_D - p;
  217. end
  218. else
  219. begin
  220. // if (D < 0)
  221. SetLength(Result, 0);
  222. end;
  223. end;
  224. function SolveCubic(const c: PDoubleArray): TDoubleArray;
  225. var
  226. i: Integer;
  227. sub: Double;
  228. A, B, Cc: Double;
  229. sq_A, p, q: Double;
  230. cb_p, D: Double;
  231. u, v, phi, t, sqrt_D, invC3: Double;
  232. begin
  233. if IsZero(c^[3]) then
  234. begin
  235. Result := SolveQuadric(c);
  236. Exit;
  237. end;
  238. // normal form: x^3 + Ax^2 + Bx + C = 0
  239. invC3 := 1 / c^[3];
  240. A := c^[2] * invC3;
  241. B := c^[1] * invC3;
  242. Cc := c^[0] * invC3;
  243. // substitute x = y - A/3 to eliminate quadric term:
  244. // x^3 +px + q = 0
  245. sq_A := Sqr(A);
  246. p := c1div3 * (B - c1div3 * sq_A);
  247. q := cHalf * (2.0 / 27 * A * sq_A - c1div3 * A * B + Cc);
  248. // use Cardano's formula
  249. cb_p := Sqr(p) * p;
  250. D := Sqr(q) + cb_p;
  251. if IsZero(D) then
  252. begin
  253. if IsZero(q) then
  254. begin // one triple solution
  255. SetLength(Result, 1);
  256. Result[0] := 0;
  257. end
  258. else
  259. begin // one single and one double solution
  260. u := cbrt(-q);
  261. SetLength(Result, 2);
  262. Result[0] := 2 * u;
  263. Result[1] := -u;
  264. end;
  265. end
  266. else if D < 0 then
  267. begin // Casus irreducibilis: three real solutions
  268. phi := c1div3 * ArcCosine(-q * RSqrt(-cb_p));
  269. t := 2 * Sqrt(-p);
  270. SetLength(Result, 3);
  271. Result[0] := t * Cos(phi);
  272. Result[1] := -t * Cos(phi + PI / 3);
  273. Result[2] := -t * Cos(phi - PI / 3);
  274. end
  275. else
  276. begin // one real solution
  277. sqrt_D := Sqrt(D);
  278. u := cbrt(sqrt_D - q);
  279. v := -cbrt(sqrt_D + q);
  280. SetLength(Result, 1);
  281. Result[0] := u + v;
  282. end;
  283. // resubstitute
  284. sub := c1div3 * A;
  285. for i := 0 to High(Result) do
  286. Result[i] := Result[i] - sub;
  287. end;
  288. function SolveQuartic(const c: PDoubleArray): TDoubleArray;
  289. var
  290. coeffs: array [0 .. 3] of Double;
  291. z, u, v, sub: Double;
  292. A, B, Cc, D: Double;
  293. sq_A, p, q, r, invC4: Double;
  294. i, n, nt: Integer;
  295. temp: TDoubleArray;
  296. begin
  297. if IsZero(c^[4]) then
  298. begin
  299. Result := SolveCubic(c);
  300. Exit;
  301. end;
  302. // normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0
  303. invC4 := 1 / c^[4];
  304. A := c^[3] * invC4;
  305. B := c^[2] * invC4;
  306. Cc := c^[1] * invC4;
  307. D := c^[0] * invC4;
  308. // substitute x = y - A/4 to eliminate cubic term:
  309. // x^4 + px^2 + qx + r = 0
  310. sq_A := Sqr(A);
  311. p := -3.0 / 8 * sq_A + B;
  312. q := 0.125 * sq_A * A - 0.5 * A * B + Cc;
  313. r := -3.0 / 256 * Sqr(sq_A) + 1.0 / 16 * sq_A * B - 0.25 * A * Cc + D;
  314. if IsZero(r) then
  315. begin
  316. // no absolute term: y(y^3 + py + q) = 0
  317. coeffs[0] := q;
  318. coeffs[1] := p;
  319. coeffs[2] := 0;
  320. coeffs[3] := 1;
  321. Result := SolveCubic(@coeffs[0]);
  322. n := Length(Result);
  323. SetLength(Result, n + 1);
  324. Result[n] := 0;
  325. SetLength(temp, 0);
  326. end
  327. else
  328. begin
  329. // solve the resolvent cubic ...
  330. coeffs[0] := 0.5 * r * p - 0.125 * Sqr(q);
  331. coeffs[1] := -r;
  332. coeffs[2] := -0.5 * p;
  333. coeffs[3] := 1;
  334. Result := SolveCubic(@coeffs[0]);
  335. // ... and take the one real solution ...
  336. Assert(Length(Result) > 0);
  337. z := Result[0];
  338. // ... to build two quadric equations
  339. u := Sqr(z) - r;
  340. v := 2 * z - p;
  341. if IsZero(u) then
  342. u := 0
  343. else if u > 0 then
  344. u := Sqrt(u)
  345. else
  346. begin
  347. SetLength(Result, 0);
  348. Exit;
  349. end;
  350. if IsZero(v) then
  351. v := 0
  352. else if v > 0 then
  353. v := Sqrt(v)
  354. else
  355. begin
  356. SetLength(Result, 0);
  357. Exit;
  358. end;
  359. coeffs[0] := z - u;
  360. if q < 0 then
  361. coeffs[1] := -v
  362. else
  363. coeffs[1] := v;
  364. coeffs[2] := 1;
  365. Result := SolveQuadric(@coeffs[0]);
  366. coeffs[0] := z + u;
  367. if q < 0 then
  368. coeffs[1] := v
  369. else
  370. coeffs[1] := -v;
  371. coeffs[2] := 1;
  372. temp := SolveQuadric(@coeffs[0]);
  373. nt := Length(temp);
  374. if nt > 0 then
  375. begin
  376. n := Length(Result);
  377. SetLength(Result, n + nt);
  378. for i := 0 to nt - 1 do
  379. Result[n + i] := temp[i];
  380. end;
  381. // resubstitute
  382. sub := 0.25 * A;
  383. for i := 0 to High(Result) do
  384. Result[i] := Result[i] - sub;
  385. end;
  386. end;
  387. end.