File: RootsImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last edited by Bier on December 31, 1984 12:58:55 pm PST
Contents: The quadratic formula, cubic formula and quartic formula as translated from LISP from
"LISP" by Patrick Winston and Berthold Horn. All functions find complex roots.

DIRECTORY
Complex,
FS,
IO,
Polynomial,
RealFns,
Roots,
Rope;

RootsImpl: PROGRAM
IMPORTS Complex, FS, IO, Polynomial, RealFns
EXPORTS Roots =

BEGIN
Vec: TYPE = Complex.Vec;
RootArray: TYPE = Roots.RootArray;
LinRoots: TYPE = Roots.LinRoots;
CubeRoots: TYPE = Roots.CubeRoots;
QuarticRoots: TYPE = Roots.QuarticRoots;
testCount: NAT = 17;
shortTestCount: NAT = 5;
TestVector: TYPE = ARRAY [1..testCount] OF REAL;
ShortTestVector: TYPE = ARRAY [1..shortTestCount] OF REAL;
GLOBALS
Inconsistent: PUBLIC SIGNAL = CODE;
root3over2: REAL ← RealFns.SqRt[3.0]/2.0;
coeffs: TestVector ← [-25.0, -17.0, -12.0, -5.6, -2.2, -1.0, -0.5, -0.001, 0.0, 0.001, 0.5, 1.0, 2.2, 5.6, 12.0, 17.0, 25.0];
shortCoeffs: ShortTestVector ← [-17.0, -1.0, 0.0, 1.0, 17.0];

ComplexLinearFormula: PUBLIC PROC [a, b: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
The solution to the equation "ax + b = 0".
IF a = 0 THEN {
IF b = 0 THEN {roots[1] ← [0.0, 0.0]; rootCount ← 0; RETURN}
ELSE roots[1] ← [0.0, 0.0]; rootCount ← 0; RETURN} -- inconsistent case
ELSE {
IF b = 0 THEN {roots[1] ← [0.0, 0.0]; rootCount ← 1; RETURN}
ELSE {roots[1] ← [-b/a, 0.0]; rootCount ← 1; RETURN};
};
};
ComplexQuadratic: PUBLIC PROC [a, b, c: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
The solution to the equation "ax2+bx+c=0". If a=0 this is just a linear equation. If
c = 0, one root is zero and we solve a linear equation. Otherwise, we use the
quadratic formula in either the form [-b+-(b2-4ac)1/2]/2a or (2c)/[-b-+(b2-4ac)1/2]
depending on ...
IF a = 0 THEN {
linRoots: RootArray;
[linRoots, rootCount] ← ComplexLinearFormula[b, c];
RETURN[linRoots, rootCount];
};
IF c = 0 THEN {
linRoots: RootArray;
roots[1] ← [0.0, 0.0];
[linRoots, rootCount] ← ComplexLinearFormula[a, b];
roots[2] ← linRoots[1];
rootCount ← rootCount + 1;
RETURN};
RETURN QuadraticAux[a, b, c, (b*b)-(4.0*a*c)];
}; -- end of ComplexQuadraticFormula
QuadraticAux: PRIVATE PROC [a, b, c, discriminant: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
IF discriminant < 0 THEN {
twoA: REAL ← 2.0*a;
RETURN QuadraticConjugate[[-b/twoA, RealFns.SqRt[-discriminant]/twoA]];
};
IF discriminant = 0 THEN {
twoA: REAL ← 2.0*a;
roots[1] ← roots[2] ← [-b/twoA, 0.0];
rootCount ← 2;
RETURN;
};
IF b < 0 THEN RETURN QuadraticReal[a, RealFns.SqRt[discriminant] - b, c]
ELSE RETURN QuadraticReal[a, -RealFns.SqRt[discriminant] - b, c];
}; -- end of QuadraticAux
QuadraticReal: PRIVATE PROC [a, term, c: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
roots[1] ← [term/(2.0*a), 0.0];
roots[2] ← [(2.0*c)/term, 0.0];
rootCount ← 2;
RETURN;
}; -- end of QuadraticReal
QuadraticConjugate: PRIVATE PROC [z: Vec] RETURNS [roots: RootArray, rootCount: NAT] = {
roots[1] ← z;
roots[2] ← [z.x, -z.y];
rootCount ← 2;
}; -- end of QuadraticConjugate
ComplexCubic: PUBLIC PROC [a, b, c, d: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
Finds the roots of equations of the form "ax^3+bx^2+cx+d".
bb, bbb, cubeTerm: REAL;
quadRoots: RootArray;
quadCount: NAT;
IF a = 0 THEN RETURN ComplexQuadratic[b, c, d];
bb ← b*b;
bbb ← bb*b;
cubeTerm ← (bb - 3.0*a*c);
cubeTerm ← cubeTerm*cubeTerm*cubeTerm;
[quadRoots, quadCount] ← ComplexQuadratic[1.0, 2.0*bbb + 9.0*a*(3.0*a*d-b*c), cubeTerm];
RETURN CubicAux[a,
     b,
     quadRoots,
     quadCount];
}; -- end of ComplexCubic
CubicAux: PRIVATE PROC [a, b: REAL, quadRoots: RootArray, quadCount: NAT] RETURNS [roots: RootArray, rootCount: NAT] = {
IF quadRoots[1].y = 0 THEN-- quadRoots real
RETURN CubicConjugate[a, b, CubeRoot[quadRoots[1].x], CubeRoot[quadRoots[2].x]]
ELSE RETURN CubicReal[a, b, RealFns.SqRt[(quadRoots[1].x*quadRoots[1].x) +
             (quadRoots[1].y*quadRoots[1].y)],
          RealFns.ArcTan[quadRoots[1].y, quadRoots[1].x]];
}; -- end of CubicAux
CubeRoot: PUBLIC PROCEDURE [z: REAL] RETURNS [root: REAL] = {
IF z < 0 THEN {
posZ: REAL ← -z;
root ← RealFns.Exp[RealFns.Ln[posZ]/3.0];
root ← -(2.0*root + posZ/(root*root))/3.0;
}
ELSE {
root ← RealFns.Exp[RealFns.Ln[z]/3.0];
root ← (2.0*root + z/(root*root))/3.0;
};
};
CubicConjugate: PRIVATE PROC [a, b, r, s: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
r and s are cube roots
threeA: REAL ← 3.0*a;
RETURN CubicConjugateAux[(r+s-b)/threeA, (-(r+s)/2.0 - b)/threeA, (r-s)*root3over2/threeA];
}; -- end of CubicConjugate
CubicConjugateAux: PRIVATE PROC [realRoot, real, imaginary: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
roots[1] ← [realRoot, 0.0];
roots[2] ← [real, imaginary];
roots[3] ← [real, -imaginary];
rootCount ← 3;
}; -- end of CubicConjugateAux
CubicReal: PRIVATE PROC [a, b, rho, theta: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
RETURN CubicRealAux[a, b, 2.0*CubeRoot[rho], RealFns.Cos[theta/3.0]*-0.5,
RealFns.Sin[theta/3.0]*-root3over2];
};
CubicRealAux: PROCEDURE [a, b, rd, cd, sd: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
threeA: REAL ← 3.0*a;
roots[1] ← [((-2.0*rd*cd) - b)/threeA, 0.0];
roots[2] ← [(rd*(cd+sd) - b)/threeA, 0.0];
roots[3] ← [(rd*(cd-sd) - b)/threeA, 0.0];
rootCount ← 3;
};
ComplexQuartic: PROCEDURE [a, b, c, d, e: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
cubicCount: NAT;
cubicRoots: RootArray;
IF a=0 THEN RETURN ComplexCubic[b, c, d, e];
IF e=0 THEN {
[cubicRoots, cubicCount] ← ComplexCubic[a, b, c, d];
roots[1] ← [0.0, 0.0];
FOR i: NAT IN [1..cubicCount] DO
roots[i+1] ← cubicRoots[i];
ENDLOOP;
rootCount ← cubicCount+1;
RETURN;
};
[cubicRoots, cubicCount] ← ComplexCubic[1.0, -c, b*d-4.0*a*e, 4.0*a*c*e-(a*d*d+b*b*e)];
RETURN QuarticAux[a, b, c, d, e, cubicRoots[1].x];
}; -- end of ComplexQuartic
QuarticAux: PROCEDURE [a, b, c, d, e, s: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
RETURN QuarticSplit[a, b, RealFns.SqRt[ABS[b*b - 4.0*a*(c-s)]], s,
   RealFns.SqRt[ABS[s*s-4.0*a*e]], b*s - 2.0*a*d];
}; -- end of QuarticAux
QuarticSplit: PROCEDURE [a, b, r1, s, r2, bsMinus2ad: REAL] RETURNS [roots: RootArray, rootCount: NAT] = {
quadRoots: RootArray;
quadCount: NAT;
IF (r1*r2*bsMinus2ad) < 0 THEN {
[roots, rootCount] ← ComplexQuadratic[2.0*a, b - r1, s+r2];
[quadRoots, quadCount] ← ComplexQuadratic[2.0*a, b+r1, s-r2];
roots[rootCount+1] ← quadRoots[1];
roots[rootCount+2] ← quadRoots[2];
rootCount ← rootCount + quadCount;
RETURN;
}
ELSE {
[roots, rootCount] ← ComplexQuadratic[2.0*a, b - r1, s-r2];
[quadRoots, quadCount] ← ComplexQuadratic[2.0*a, b+r1, s+r2];
roots[rootCount+1] ← quadRoots[1];
roots[rootCount+2] ← quadRoots[2];
rootCount ← rootCount + quadCount;
RETURN;
};
}; -- end of QuarticSplit
RealQuartic: PUBLIC PROC [a, b, c, d, e: REAL] RETURNS [roots: ARRAY[1..4] OF REAL, rootCount: NAT] = {
complexRoots: RootArray;
complexCount: NAT;
recipMaxMag: REAL;
recipMaxMag ← 1.0/MaxFive[a, b, c, d, e];
[complexRoots, complexCount] ← ComplexQuartic[a*recipMaxMag, b*recipMaxMag, c*recipMaxMag, d*recipMaxMag, e*recipMaxMag];
rootCount ← 0;
FOR i: NAT IN [1..complexCount] DO
IF complexRoots[i].y = 0.0 THEN {
rootCount ← rootCount + 1;
roots[rootCount] ← complexRoots[i].x;
};
ENDLOOP;
}; -- end of RealQuartic
MaxFive: PROCEDURE [a, b, c, d, e: REAL] RETURNS [max: REAL] = {
aMag, bMag, cMag, dMag, eMag: REAL;
aMag ← ABS[a]; bMag ← ABS[b]; cMag ← ABS[c]; dMag ← ABS[d]; eMag ← ABS[e];
max ← aMag;
IF bMag>max THEN max ← bMag;
IF cMag>max THEN max ← cMag;
IF dMag>max THEN max ← dMag;
IF eMag>max THEN max ← eMag;
};
EvalQuartic: PROCEDURE [r: Vec, a, b, c, d, e: REAL] RETURNS [result: Vec] = {
Evaluate the quartic polynomial ar4 + br3 + cr2 + dr + e using Horner's rule:
(((ar+b)r+c)r+d)r+e.
OPEN Complex;
t: Vec;
t ← ComplexAdd[ComplexMul[[a, 0.0], r], [b, 0.0]]; -- t ← ar+b;
t ← ComplexAdd[ComplexMul[t, r], [c, 0.0]]; -- t ← (ar+b)r+c;
t ← ComplexAdd[ComplexMul[t, r], [d, 0.0]]; -- t ← ((ar+b)r+c)r+d;
t ← ComplexAdd[ComplexMul[t, r], [e, 0.0]]; -- t ← (((ar+b)r+c)r+d)r+e;
result ← t;
};
EvalQuarticAtReal: PROCEDURE [r: REAL, a, b, c, d, e: REAL] RETURNS [result: REAL] = {
Evaluate the quartic polynomial ar4 + br3 + cr2 + dr + e using Horner's rule:
(((ar+b)r+c)r+d)r+e.
OPEN Complex;
t: REAL;
t ← (((a*r+b)*r+c)*r+d)*r+e;
result ← t;
};
EvalCubic: PROCEDURE [r: Vec, a, b, c, d: REAL] RETURNS [result: Vec] = {
Evaluate the quartic polynomial ar3 + br2 + cr + d using Horner's rule:
((ar+b)r+c)r+d.
OPEN Complex;
t: Vec;
t ← ComplexAdd[ComplexMul[[a, 0.0], r], [b, 0.0]]; -- t ← ar+b;
t ← ComplexAdd[ComplexMul[t, r], [c, 0.0]]; -- t ← (ar+b)r+c;
t ← ComplexAdd[ComplexMul[t, r], [d, 0.0]]; -- t ← ((ar+b)r+c)r+d;
result ← t;
};
EvalQuad: PROCEDURE [r: Vec, a, b, c: REAL] RETURNS [result: Vec] = {
Evaluate the quartic polynomial ar2 + br + c using Horner's rule:
(ar+b)r+c.
OPEN Complex;
t: Vec;
t ← ComplexAdd[ComplexMul[[a, 0.0], r], [b, 0.0]]; -- t ← ar+b;
t ← ComplexAdd[ComplexMul[t, r], [c, 0.0]]; -- t ← (ar+b)r+c;
result ← t;
};
TestQuad: PUBLIC PROC [filename: Rope.ROPE] = {
file: IO.STREAM;
roots: RootArray;
rootCount: NAT;
eval: Vec;
file ← FS.StreamOpen[filename, $create];
Letting coefficients a, and b take on any of the values in the test vector, we use our quad finder to find all of the complex roots of each polynomial. For each root, we evaluate the polynomial at that root. The result should come out zero. For now, write out a file entry for each root
FOR i: NAT IN [1..testCount] DO
FOR j: NAT IN [1..testCount] DO
[roots, rootCount] ← ComplexQuadratic[coeffs[i], coeffs[j], 1.0];
file.PutF["poly: [%g, %g, %g] ", [real[coeffs[i]]], [real[coeffs[j]]], [real[1.0]]];
file.PutF["roots: [(%g, %g), (%g, %g)]\n", [real[roots[1].x]], [real[roots[1].y]],
[real[roots[2].x]], [real[roots[2].y]]];
FOR k: NAT IN [1..rootCount] DO
eval ← EvalQuad[roots[k], coeffs[i], coeffs[j], 1.0];
file.PutF["  root %g evals to (%g, %g).\n", [integer[k]], [real[eval.x]], [real[eval.y]]];
ENDLOOP;
ENDLOOP;
ENDLOOP;
file.Close[];
};
TestCubic: PUBLIC PROC [filename: Rope.ROPE] = {
file: IO.STREAM;
roots: RootArray;
rootCount: NAT;
eval: Vec;
file ← FS.StreamOpen[filename, $create];
Letting coefficients a, b, and c take on any of the values in the test vector, we use our quad finder to find all of the complex roots of each polynomial. For each root, we evaluate the polynomial at that root. The result should come out zero. For now, write out a file entry for each root.
FOR i: NAT IN [1..shortTestCount] DO
FOR j: NAT IN [1..shortTestCount] DO
FOR l: NAT IN [1..shortTestCount] DO
[roots, rootCount] ← ComplexCubic[shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], 1.0];
file.PutF["poly: [%g, %g, %g, %g] ", [real[shortCoeffs[i]]], [real[shortCoeffs[j]]], [real[shortCoeffs[l]]], [real[1.0]]];
file.PutF["roots: ["];
file.PutF["[%g, %g]", [real[roots[1].x]], [real[roots[1].y]]];
FOR k: NAT IN [2..rootCount] DO
file.PutF[", [%g, %g]", [real[roots[k].x]], [real[roots[k].y]]];
ENDLOOP;
file.PutF["]\n"];
FOR k: NAT IN [1..rootCount] DO
eval ← EvalCubic[roots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], 1.0];
file.PutF["  root %g evals to (%g, %g).\n", [integer[k]], [real[eval.x]], [real[eval.y]]];
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
file.Close[];
};
TestQuartic: PUBLIC PROC [filename: Rope.ROPE] = {
file: IO.STREAM;
roots: RootArray;
rootCount: NAT;
eval: Vec;
file ← FS.StreamOpen[filename, $create];
Letting coefficients a, b, and c take on any of the values in the test vector, we use our quad finder to find all of the complex roots of each polynomial. For each root, we evaluate the polynomial at that root. The result should come out zero. For now, write out a file entry for each root.
FOR i: NAT IN [1..shortTestCount] DO
FOR j: NAT IN [1..shortTestCount] DO
FOR l: NAT IN [1..shortTestCount] DO
FOR m: NAT IN [1..shortTestCount] DO
[roots, rootCount] ← ComplexQuartic[shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
file.PutF["poly: [%g, %g, %g, %g, %g] ", [real[shortCoeffs[i]]], [real[shortCoeffs[j]]], [real[shortCoeffs[l]]], [real[shortCoeffs[m]]], [real[1.0]]];
file.PutF["roots: ["];
file.PutF["[%g, %g]", [real[roots[1].x]], [real[roots[1].y]]];
FOR k: NAT IN [2..rootCount] DO
file.PutF[", [%g, %g]", [real[roots[k].x]], [real[roots[k].y]]];
ENDLOOP;
file.PutF["]\n"];
FOR k: NAT IN [1..rootCount] DO
eval ← EvalQuartic[roots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
file.PutF["  root %g evals to (%g, %g).\n", [integer[k]], [real[eval.x]], [real[eval.y]]];
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
file.Close[];
};
globalPoly: Polynomial.Ref;
CompareQuartic: PUBLIC PROC [filename: Rope.ROPE] = {
file: IO.STREAM;
roots: ARRAY[1..4] OF REAL;
rootCount: NAT;
realRoots: REF Polynomial.RealRootRec;
problem: BOOL;
eval: REAL;
file ← FS.StreamOpen[filename, $create];
Letting coefficients a, b, and c take on any of the values in the test vector, we use our two quad finders to find all of the real roots of each polynomial. If the finders disagree on the number of roots, we print out the complete situation. For each root, we evaluate the polynomial at that root. If either finder is off by more than e-3, we print out the situation.
FOR i: NAT IN [1..shortTestCount] DO
FOR j: NAT IN [1..shortTestCount] DO
FOR l: NAT IN [1..shortTestCount] DO
FOR m: NAT IN [1..shortTestCount] DO
[roots, rootCount] ← RealQuartic[shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
globalPoly[4] ← shortCoeffs[i];
globalPoly[3] ← shortCoeffs[j];
globalPoly[2] ← shortCoeffs[l];
globalPoly[1] ← shortCoeffs[m];
globalPoly[0] ← 1.0;
realRoots ← Polynomial.RealRoots[globalPoly];
IF realRoots.nRoots # rootCount THEN {
file.PutF["Different number of roots ** ** ** **\n"];
file.PutF["poly: [%g, %g, %g, %g, %g] ", [real[shortCoeffs[i]]], [real[shortCoeffs[j]]], [real[shortCoeffs[l]]], [real[shortCoeffs[m]]], [real[1.0]]];
file.PutF["Closed form:\n"];
file.PutF["roots: (%g)[", [integer[rootCount]] ];
IF rootCount > 0 THEN {
file.PutF["%g", [real[roots[1]]] ];
FOR k: NAT IN [2..rootCount] DO
file.PutF[", %g", [real[roots[k]]] ];
ENDLOOP;
};
file.PutF["]\n"];
FOR k: NAT IN [1..rootCount] DO
eval ← EvalQuarticAtReal[roots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
file.PutF["  root %g evals to %g.\n", [integer[k]], [real[eval]] ];
ENDLOOP;
file.PutF["Iterative form:\n"];
file.PutF["roots: (%g)[", [integer[realRoots.nRoots]] ];
IF realRoots.nRoots > 0 THEN {
file.PutF["%g", [real[realRoots[0]]] ];
FOR k: NAT IN [1..realRoots.nRoots-1] DO
file.PutF[", %g", [real[realRoots[k]]] ];
ENDLOOP;
};
file.PutF["]\n"];
FOR k: NAT IN [0..realRoots.nRoots) DO
eval ← EvalQuarticAtReal[realRoots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
file.PutF["  root %g evals to %g.\n", [integer[k]], [real[eval]] ];
ENDLOOP;
}
ELSE {
problem ← FALSE;
FOR k: NAT IN [1..rootCount] DO
eval ← EvalQuarticAtReal[roots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
IF eval > 1.0e-3 THEN problem ← TRUE;
ENDLOOP;
FOR k: NAT IN [0..realRoots.nRoots) DO
eval ← EvalQuarticAtReal[realRoots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
IF eval > 1.0e-3 THEN problem ← TRUE;
ENDLOOP;
IF problem THEN {
file.PutF["EVAL TOO LARGE\n"];
file.PutF["poly: [%g, %g, %g, %g, %g] ", [real[shortCoeffs[i]]], [real[shortCoeffs[j]]], [real[shortCoeffs[l]]], [real[shortCoeffs[m]]], [real[1.0]]];
file.PutF["Closed form:\n"];
file.PutF["roots: (%g)[", [integer[rootCount]] ];
file.PutF["%g", [real[roots[1]]] ];
FOR k: NAT IN [2..rootCount] DO
file.PutF[", %g", [real[roots[k]]] ];
ENDLOOP;
file.PutF["]\n"];
FOR k: NAT IN [1..rootCount] DO
eval ← EvalQuarticAtReal[roots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
file.PutF["  root %g evals to %g.\n", [integer[k]], [real[eval]] ];
ENDLOOP;
file.PutF["Iterative form:\n"];
file.PutF["roots: (%g)[", [integer[realRoots.nRoots]] ];
file.PutF["%g", [real[realRoots[0]]] ];
FOR k: NAT IN [1..realRoots.nRoots-1] DO
file.PutF[", %g", [real[realRoots[k]]] ];
ENDLOOP;
file.PutF["]\n"];
FOR k: NAT IN [0..realRoots.nRoots) DO
eval ← EvalQuarticAtReal[realRoots[k], shortCoeffs[i], shortCoeffs[j], shortCoeffs[l], shortCoeffs[m], 1.0];
file.PutF["  root %g evals to %g.\n", [integer[k]], [real[eval]] ];
ENDLOOP;
};
};
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
file.Close[];
};
ComplexMul: PROC [r, s: Vec] RETURNS [z: Vec] = {
z ← Complex.Mul[r, s];
};
ComplexAdd: PROC [r, s: Vec] RETURNS [z: Vec] = {
z ← Complex.Add[r, s];
};
Init: PROC = {
globalPoly ← Polynomial.Quartic[[0,0,0,0,0]];
};
Init[];
END.