2
0

GXS.Polynomials.pas 9.3 KB

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