123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- //
- // The graphics engine GLScene
- //
- unit Stage.Polynomials;
- (*
- Utility functions for manipulationg and solving polynomials.
- Direct solving is supported for polynoms up to the 4th degree.
- Polynom solving code based on Jochen Schwarze ([email protected]) solver
- published in Graphics Gem (1990).
- Adapted to pascal by Eric Grange ([email protected]), if you find
- errors, they are probably mine. Note that contrary to the original code,
- the functions accept 'zero' values for any of the parameters.
- I also made some changes for certain limit cases that (seemingly) weren't
- properly handled, these are marked by comments in the code.
- Note: in progress - limited precision.
- *)
- interface
- {$I Stage.Defines.inc}
- uses
- Stage.VectorGeometry;
- type
- TDoubleArray = array of Double;
- // Computes polynom's value for given x
- function EvalPolynom(const poly: TDoubleArray; const x: Double): Double;
- // Calculates the polynom's derivative
- function DerivatedPolynom(const poly: TDoubleArray): TDoubleArray;
- (* Finds a root between min and max with a precision of epsilon.
- The evaluation of min/max must be of opposit sign *)
- function FindRoot(const poly: TDoubleArray; min, max, epsilon: Double): Double;
- (* Finds the minimum positive coef in the array in aMin.
- Returns true if such an item was found. *)
- function MinPositiveCoef(const coefs: TDoubleArray; var aMin: Double): Boolean;
- // Calculates the cube root of its parameter.
- function cbrt(const x: Double): Double;
- (* Computes the real roots of a real polynomial of the 2nd degree.
- The polynomial is of the form:
- A(0) + A(1)*Z + A(2)*Z**2 *)
- function SolveQuadric(const c: PDoubleArray): TDoubleArray;
- (* Computes the real roots of a real polynomial of the 3rd degree.
- The polynomial is of the form:
- A(0) + A(1)*Z + A(2)*Z**2 + A(3)*Z**3 *)
- function SolveCubic(const c: PDoubleArray): TDoubleArray;
- (* Computes the real roots of a real polynomial of the 4th degree.
- The polynomial is of the form:
- A(0) + A(1)*Z + ... + A(4)*Z**4 *)
- function SolveQuartic(const c: PDoubleArray): TDoubleArray;
- implementation // -------------------------------------------------------------
- const
- cEpsilon: Double = 1E-40;
- c1div3: Double = 0.3333333333333333333333333333333;
- cHalf: Double = 0.5;
- function IsZero(var v: Double): Boolean; overload;
- begin
- Result := (Abs(v) <= cEpsilon);
- end;
- function EvalPolynom(const poly: TDoubleArray; const x: Double): Double;
- var
- i, n: Integer;
- begin
- n := Length(poly);
- if n > 0 then
- begin
- Result := poly[n - 1];
- for i := n - 2 downto 0 do
- Result := Result * x + poly[i];
- end
- else
- Result := 0;
- end;
- function DerivatedPolynom(const poly: TDoubleArray): TDoubleArray;
- var
- n, i: Integer;
- begin
- n := Length(poly);
- if n > 1 then
- begin
- SetLength(Result, n - 1);
- for i := 1 to n - 1 do
- Result[i - 1] := poly[i] * i;
- end
- else
- begin
- SetLength(Result, 1);
- Result[0] := 0;
- end;
- end;
- function FindRoot(const poly: TDoubleArray; min, max, epsilon: Double): Double;
- var
- evMin, evMax, mid, evMid: Double;
- begin
- // handle degenerate cases first
- Assert(min < max);
- evMin := EvalPolynom(poly, min);
- if evMin = 0 then
- begin
- Result := min;
- Exit;
- end;
- evMax := EvalPolynom(poly, max);
- if evMax = 0 then
- begin
- Result := max;
- Exit;
- end;
- if evMax < 0 then
- begin
- Assert(evMin > 0);
- while Abs(max - min) > epsilon do
- begin
- mid := (max + min) * 0.5;
- evMid := EvalPolynom(poly, mid);
- if evMid > 0 then
- min := mid
- else
- max := mid;
- end;
- end
- else
- begin
- Assert(evMin < 0);
- while Abs(max - min) > epsilon do
- begin
- mid := (max + min) * 0.5;
- evMid := EvalPolynom(poly, mid);
- if evMid > 0 then
- max := mid
- else
- min := mid;
- end;
- end;
- Result := (max + min) * cHalf;
- end;
- function MinPositiveCoef(const coefs: TDoubleArray; var aMin: Double): Boolean;
- var
- n, i, j: Integer;
- begin
- n := Length(coefs);
- case n of
- 0:
- Result := False;
- 1:
- begin
- if coefs[0] >= 0 then
- begin
- aMin := coefs[0];
- Result := True;
- end
- else
- Result := False;
- end;
- 2:
- begin
- if coefs[0] >= 0 then
- begin
- aMin := coefs[0];
- if (coefs[1] >= 0) and (coefs[1] < aMin) then
- aMin := coefs[1];
- Result := True;
- end
- else if coefs[1] >= 0 then
- begin
- aMin := coefs[1];
- Result := True;
- end
- else
- Result := False;
- end;
- else
- Result := False;
- // find a positive value, then find lowest positive
- for i := 0 to n - 1 do
- begin
- if coefs[i] >= 0 then
- begin
- aMin := coefs[i];
- for j := i + 1 to n - 1 do
- begin
- if (coefs[j] >= 0) and (coefs[j] < aMin) then
- aMin := coefs[j];
- end;
- Result := True;
- Break;
- end;
- end;
- end;
- end;
- function cbrt(const x: Double): Double;
- begin
- if x > 0 then
- Result := PowerSingle(x, c1div3)
- else if x < 0 then
- Result := -PowerSingle(-x, c1div3)
- else
- Result := 0;
- end;
- function SolveQuadric(const c: PDoubleArray): TDoubleArray;
- var
- p, q, D, sqrt_D: Double;
- begin
- // normal form: x^2 + px + q = 0
- p := c^[1] / (2 * c^[2]);
- q := c^[0] / c^[2];
- D := Sqr(p) - q;
- if IsZero(D) then
- begin
- SetLength(Result, 1);
- Result[0] := -p;
- end
- else if D > 0 then
- begin
- sqrt_D := Sqrt(D);
- SetLength(Result, 2);
- Result[0] := sqrt_D - p;
- Result[1] := -sqrt_D - p;
- end
- else
- begin
- // if (D < 0)
- SetLength(Result, 0);
- end;
- end;
- function SolveCubic(const c: PDoubleArray): TDoubleArray;
- var
- i: Integer;
- sub: Double;
- A, B, Cc: Double;
- sq_A, p, q: Double;
- cb_p, D: Double;
- u, v, phi, t, sqrt_D, invC3: Double;
- begin
- if IsZero(c^[3]) then
- begin
- Result := SolveQuadric(c);
- Exit;
- end;
- // normal form: x^3 + Ax^2 + Bx + C = 0
- invC3 := 1 / c^[3];
- A := c^[2] * invC3;
- B := c^[1] * invC3;
- Cc := c^[0] * invC3;
- // substitute x = y - A/3 to eliminate quadric term:
- // x^3 +px + q = 0
- sq_A := Sqr(A);
- p := c1div3 * (B - c1div3 * sq_A);
- q := cHalf * (2.0 / 27 * A * sq_A - c1div3 * A * B + Cc);
- // use Cardano's formula
- cb_p := Sqr(p) * p;
- D := Sqr(q) + cb_p;
- if IsZero(D) then
- begin
- if IsZero(q) then
- begin // one triple solution
- SetLength(Result, 1);
- Result[0] := 0;
- end
- else
- begin // one single and one double solution
- u := cbrt(-q);
- SetLength(Result, 2);
- Result[0] := 2 * u;
- Result[1] := -u;
- end;
- end
- else if D < 0 then
- begin // Casus irreducibilis: three real solutions
- phi := c1div3 * ArcCosine(-q * RSqrt(-cb_p));
- t := 2 * Sqrt(-p);
- SetLength(Result, 3);
- Result[0] := t * Cos(phi);
- Result[1] := -t * Cos(phi + PI / 3);
- Result[2] := -t * Cos(phi - PI / 3);
- end
- else
- begin // one real solution
- sqrt_D := Sqrt(D);
- u := cbrt(sqrt_D - q);
- v := -cbrt(sqrt_D + q);
- SetLength(Result, 1);
- Result[0] := u + v;
- end;
- // resubstitute
- sub := c1div3 * A;
- for i := 0 to High(Result) do
- Result[i] := Result[i] - sub;
- end;
- function SolveQuartic(const c: PDoubleArray): TDoubleArray;
- var
- coeffs: array [0 .. 3] of Double;
- z, u, v, sub: Double;
- A, B, Cc, D: Double;
- sq_A, p, q, r, invC4: Double;
- i, n, nt: Integer;
- temp: TDoubleArray;
- begin
- if IsZero(c^[4]) then
- begin
- Result := SolveCubic(c);
- Exit;
- end;
- // normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0
- invC4 := 1 / c^[4];
- A := c^[3] * invC4;
- B := c^[2] * invC4;
- Cc := c^[1] * invC4;
- D := c^[0] * invC4;
- // substitute x = y - A/4 to eliminate cubic term:
- // x^4 + px^2 + qx + r = 0
- sq_A := Sqr(A);
- p := -3.0 / 8 * sq_A + B;
- q := 0.125 * sq_A * A - 0.5 * A * B + Cc;
- r := -3.0 / 256 * Sqr(sq_A) + 1.0 / 16 * sq_A * B - 0.25 * A * Cc + D;
- if IsZero(r) then
- begin
- // no absolute term: y(y^3 + py + q) = 0
- coeffs[0] := q;
- coeffs[1] := p;
- coeffs[2] := 0;
- coeffs[3] := 1;
- Result := SolveCubic(@coeffs[0]);
- n := Length(Result);
- SetLength(Result, n + 1);
- Result[n] := 0;
- SetLength(temp, 0);
- end
- else
- begin
- // solve the resolvent cubic ...
- coeffs[0] := 0.5 * r * p - 0.125 * Sqr(q);
- coeffs[1] := -r;
- coeffs[2] := -0.5 * p;
- coeffs[3] := 1;
- Result := SolveCubic(@coeffs[0]);
- // ... and take the one real solution ...
- Assert(Length(Result) > 0);
- z := Result[0];
- // ... to build two quadric equations
- u := Sqr(z) - r;
- v := 2 * z - p;
- if IsZero(u) then
- u := 0
- else if u > 0 then
- u := Sqrt(u)
- else
- begin
- SetLength(Result, 0);
- Exit;
- end;
- if IsZero(v) then
- v := 0
- else if v > 0 then
- v := Sqrt(v)
- else
- begin
- SetLength(Result, 0);
- Exit;
- end;
- coeffs[0] := z - u;
- if q < 0 then
- coeffs[1] := -v
- else
- coeffs[1] := v;
- coeffs[2] := 1;
- Result := SolveQuadric(@coeffs[0]);
- coeffs[0] := z + u;
- if q < 0 then
- coeffs[1] := v
- else
- coeffs[1] := -v;
- coeffs[2] := 1;
- temp := SolveQuadric(@coeffs[0]);
- nt := Length(temp);
- if nt > 0 then
- begin
- n := Length(Result);
- SetLength(Result, n + nt);
- for i := 0 to nt - 1 do
- Result[n + i] := temp[i];
- end;
- // resubstitute
- sub := 0.25 * A;
- for i := 0 to High(Result) do
- Result[i] := Result[i] - sub;
- end;
- end;
- end.
|