Stage.Polynomials.pas 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit Stage.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 Stage.Defines.inc}
  19. uses
  20. Stage.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. implementation // -------------------------------------------------------------
  48. const
  49. cEpsilon: Double = 1E-40;
  50. c1div3: Double = 0.3333333333333333333333333333333;
  51. cHalf: Double = 0.5;
  52. function IsZero(var v: Double): Boolean; overload;
  53. begin
  54. Result := (Abs(v) <= cEpsilon);
  55. end;
  56. function EvalPolynom(const poly: TDoubleArray; const x: Double): Double;
  57. var
  58. i, n: Integer;
  59. begin
  60. n := Length(poly);
  61. if n > 0 then
  62. begin
  63. Result := poly[n - 1];
  64. for i := n - 2 downto 0 do
  65. Result := Result * x + poly[i];
  66. end
  67. else
  68. Result := 0;
  69. end;
  70. function DerivatedPolynom(const poly: TDoubleArray): TDoubleArray;
  71. var
  72. n, i: Integer;
  73. begin
  74. n := Length(poly);
  75. if n > 1 then
  76. begin
  77. SetLength(Result, n - 1);
  78. for i := 1 to n - 1 do
  79. Result[i - 1] := poly[i] * i;
  80. end
  81. else
  82. begin
  83. SetLength(Result, 1);
  84. Result[0] := 0;
  85. end;
  86. end;
  87. function FindRoot(const poly: TDoubleArray; min, max, epsilon: Double): Double;
  88. var
  89. evMin, evMax, mid, evMid: Double;
  90. begin
  91. // handle degenerate cases first
  92. Assert(min < max);
  93. evMin := EvalPolynom(poly, min);
  94. if evMin = 0 then
  95. begin
  96. Result := min;
  97. Exit;
  98. end;
  99. evMax := EvalPolynom(poly, max);
  100. if evMax = 0 then
  101. begin
  102. Result := max;
  103. Exit;
  104. end;
  105. if evMax < 0 then
  106. begin
  107. Assert(evMin > 0);
  108. while Abs(max - min) > epsilon do
  109. begin
  110. mid := (max + min) * 0.5;
  111. evMid := EvalPolynom(poly, mid);
  112. if evMid > 0 then
  113. min := mid
  114. else
  115. max := mid;
  116. end;
  117. end
  118. else
  119. begin
  120. Assert(evMin < 0);
  121. while Abs(max - min) > epsilon do
  122. begin
  123. mid := (max + min) * 0.5;
  124. evMid := EvalPolynom(poly, mid);
  125. if evMid > 0 then
  126. max := mid
  127. else
  128. min := mid;
  129. end;
  130. end;
  131. Result := (max + min) * cHalf;
  132. end;
  133. function MinPositiveCoef(const coefs: TDoubleArray; var aMin: Double): Boolean;
  134. var
  135. n, i, j: Integer;
  136. begin
  137. n := Length(coefs);
  138. case n of
  139. 0:
  140. Result := False;
  141. 1:
  142. begin
  143. if coefs[0] >= 0 then
  144. begin
  145. aMin := coefs[0];
  146. Result := True;
  147. end
  148. else
  149. Result := False;
  150. end;
  151. 2:
  152. begin
  153. if coefs[0] >= 0 then
  154. begin
  155. aMin := coefs[0];
  156. if (coefs[1] >= 0) and (coefs[1] < aMin) then
  157. aMin := coefs[1];
  158. Result := True;
  159. end
  160. else if coefs[1] >= 0 then
  161. begin
  162. aMin := coefs[1];
  163. Result := True;
  164. end
  165. else
  166. Result := False;
  167. end;
  168. else
  169. Result := False;
  170. // find a positive value, then find lowest positive
  171. for i := 0 to n - 1 do
  172. begin
  173. if coefs[i] >= 0 then
  174. begin
  175. aMin := coefs[i];
  176. for j := i + 1 to n - 1 do
  177. begin
  178. if (coefs[j] >= 0) and (coefs[j] < aMin) then
  179. aMin := coefs[j];
  180. end;
  181. Result := True;
  182. Break;
  183. end;
  184. end;
  185. end;
  186. end;
  187. function cbrt(const x: Double): Double;
  188. begin
  189. if x > 0 then
  190. Result := PowerSingle(x, c1div3)
  191. else if x < 0 then
  192. Result := -PowerSingle(-x, c1div3)
  193. else
  194. Result := 0;
  195. end;
  196. function SolveQuadric(const c: PDoubleArray): TDoubleArray;
  197. var
  198. p, q, D, sqrt_D: Double;
  199. begin
  200. // normal form: x^2 + px + q = 0
  201. p := c^[1] / (2 * c^[2]);
  202. q := c^[0] / c^[2];
  203. D := Sqr(p) - q;
  204. if IsZero(D) then
  205. begin
  206. SetLength(Result, 1);
  207. Result[0] := -p;
  208. end
  209. else if D > 0 then
  210. begin
  211. sqrt_D := Sqrt(D);
  212. SetLength(Result, 2);
  213. Result[0] := sqrt_D - p;
  214. Result[1] := -sqrt_D - p;
  215. end
  216. else
  217. begin
  218. // if (D < 0)
  219. SetLength(Result, 0);
  220. end;
  221. end;
  222. function SolveCubic(const c: PDoubleArray): TDoubleArray;
  223. var
  224. i: Integer;
  225. sub: Double;
  226. A, B, Cc: Double;
  227. sq_A, p, q: Double;
  228. cb_p, D: Double;
  229. u, v, phi, t, sqrt_D, invC3: Double;
  230. begin
  231. if IsZero(c^[3]) then
  232. begin
  233. Result := SolveQuadric(c);
  234. Exit;
  235. end;
  236. // normal form: x^3 + Ax^2 + Bx + C = 0
  237. invC3 := 1 / c^[3];
  238. A := c^[2] * invC3;
  239. B := c^[1] * invC3;
  240. Cc := c^[0] * invC3;
  241. // substitute x = y - A/3 to eliminate quadric term:
  242. // x^3 +px + q = 0
  243. sq_A := Sqr(A);
  244. p := c1div3 * (B - c1div3 * sq_A);
  245. q := cHalf * (2.0 / 27 * A * sq_A - c1div3 * A * B + Cc);
  246. // use Cardano's formula
  247. cb_p := Sqr(p) * p;
  248. D := Sqr(q) + cb_p;
  249. if IsZero(D) then
  250. begin
  251. if IsZero(q) then
  252. begin // one triple solution
  253. SetLength(Result, 1);
  254. Result[0] := 0;
  255. end
  256. else
  257. begin // one single and one double solution
  258. u := cbrt(-q);
  259. SetLength(Result, 2);
  260. Result[0] := 2 * u;
  261. Result[1] := -u;
  262. end;
  263. end
  264. else if D < 0 then
  265. begin // Casus irreducibilis: three real solutions
  266. phi := c1div3 * ArcCosine(-q * RSqrt(-cb_p));
  267. t := 2 * Sqrt(-p);
  268. SetLength(Result, 3);
  269. Result[0] := t * Cos(phi);
  270. Result[1] := -t * Cos(phi + PI / 3);
  271. Result[2] := -t * Cos(phi - PI / 3);
  272. end
  273. else
  274. begin // one real solution
  275. sqrt_D := Sqrt(D);
  276. u := cbrt(sqrt_D - q);
  277. v := -cbrt(sqrt_D + q);
  278. SetLength(Result, 1);
  279. Result[0] := u + v;
  280. end;
  281. // resubstitute
  282. sub := c1div3 * A;
  283. for i := 0 to High(Result) do
  284. Result[i] := Result[i] - sub;
  285. end;
  286. function SolveQuartic(const c: PDoubleArray): TDoubleArray;
  287. var
  288. coeffs: array [0 .. 3] of Double;
  289. z, u, v, sub: Double;
  290. A, B, Cc, D: Double;
  291. sq_A, p, q, r, invC4: Double;
  292. i, n, nt: Integer;
  293. temp: TDoubleArray;
  294. begin
  295. if IsZero(c^[4]) then
  296. begin
  297. Result := SolveCubic(c);
  298. Exit;
  299. end;
  300. // normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0
  301. invC4 := 1 / c^[4];
  302. A := c^[3] * invC4;
  303. B := c^[2] * invC4;
  304. Cc := c^[1] * invC4;
  305. D := c^[0] * invC4;
  306. // substitute x = y - A/4 to eliminate cubic term:
  307. // x^4 + px^2 + qx + r = 0
  308. sq_A := Sqr(A);
  309. p := -3.0 / 8 * sq_A + B;
  310. q := 0.125 * sq_A * A - 0.5 * A * B + Cc;
  311. r := -3.0 / 256 * Sqr(sq_A) + 1.0 / 16 * sq_A * B - 0.25 * A * Cc + D;
  312. if IsZero(r) then
  313. begin
  314. // no absolute term: y(y^3 + py + q) = 0
  315. coeffs[0] := q;
  316. coeffs[1] := p;
  317. coeffs[2] := 0;
  318. coeffs[3] := 1;
  319. Result := SolveCubic(@coeffs[0]);
  320. n := Length(Result);
  321. SetLength(Result, n + 1);
  322. Result[n] := 0;
  323. SetLength(temp, 0);
  324. end
  325. else
  326. begin
  327. // solve the resolvent cubic ...
  328. coeffs[0] := 0.5 * r * p - 0.125 * Sqr(q);
  329. coeffs[1] := -r;
  330. coeffs[2] := -0.5 * p;
  331. coeffs[3] := 1;
  332. Result := SolveCubic(@coeffs[0]);
  333. // ... and take the one real solution ...
  334. Assert(Length(Result) > 0);
  335. z := Result[0];
  336. // ... to build two quadric equations
  337. u := Sqr(z) - r;
  338. v := 2 * z - p;
  339. if IsZero(u) then
  340. u := 0
  341. else if u > 0 then
  342. u := Sqrt(u)
  343. else
  344. begin
  345. SetLength(Result, 0);
  346. Exit;
  347. end;
  348. if IsZero(v) then
  349. v := 0
  350. else if v > 0 then
  351. v := Sqrt(v)
  352. else
  353. begin
  354. SetLength(Result, 0);
  355. Exit;
  356. end;
  357. coeffs[0] := z - u;
  358. if q < 0 then
  359. coeffs[1] := -v
  360. else
  361. coeffs[1] := v;
  362. coeffs[2] := 1;
  363. Result := SolveQuadric(@coeffs[0]);
  364. coeffs[0] := z + u;
  365. if q < 0 then
  366. coeffs[1] := v
  367. else
  368. coeffs[1] := -v;
  369. coeffs[2] := 1;
  370. temp := SolveQuadric(@coeffs[0]);
  371. nt := Length(temp);
  372. if nt > 0 then
  373. begin
  374. n := Length(Result);
  375. SetLength(Result, n + nt);
  376. for i := 0 to nt - 1 do
  377. Result[n + i] := temp[i];
  378. end;
  379. // resubstitute
  380. sub := 0.25 * A;
  381. for i := 0 to High(Result) do
  382. Result[i] := Result[i] - sub;
  383. end;
  384. end;
  385. end.