HP Forums

Full Version: (PC-12xx~14xx) Weierstrass / Durand-Kerner
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
This is another one of my NumAlg programs sitting in my collection for years. I've added my tested and verified C version for comparison and commented the code. Perhaps someone finds this useful.

WEIERSTRASS / DURAND-KERNER POLYNOMIAL ROOT FINDER
Estimate the complex roots of a polynomial
Note: Weierstrass/Durand-Kerner is not guaranteed to converge
2nd order convergence, worst-case linear convergence for roots with multiplicities

Optimized implementations in C and BASIC:
- Finds complex roots z of polynomial p such that |p(z)|<T given tolerance T
- Prevents overflow/nan/inf by bounding the root shift adjustment magnitude during polishing
- Prevents loss of precision when possible, e.g. in reciprocals
- Tested with a large sample of polynomials

See also: https://en.wikipedia.org/wiki/Durand–Kerner_method

For SHARP PC-12xx to 14xx

To use with SHARP PC models with a single display line:
- remove CURSOR and CLS
- replace PRINT with PAUSE except at line 80

954 bytes BASIC image (PC-1350)

Degree: max polynomial degree limited by time and memory only
Tolerance: T=1E-6 (adjustable)
Levels: up to L=20 iterations (adjustable)

Example:

(Solve x^2+2x+3=0)
[DEF-A]
N=2 A2=1 A1=2 A0=3
T= L=
CALCULATING...
RE          IM
         -1.  -1.414214.
         -1.   1.414214.

(Solve x^3-2x^2-x+2=0)
[DEF-A]
N=3 A3=1 A2=-2 A1=-1 A0=2
T= L=
CALCULATING...
RE          IM
          1.          0.
          2.          0.
         -1.          0.

(Solve x^3+3x^2+x+3=0)
[DEF-A]
N=3 A3=1 A2=3 A1=1 A0=3
T= L=
CALCULATING...
RE          IM
          0.         -1.
          0.          1.
         -3.          0.

(Solve x^4+2999x^3-10003e3x^2-2399e7x+24e9=0)
[DEF-A]
N=4 A4=1 A3=2999 A2=-10003E3 A1=-2399E7 A0=24E9
T= L=
CALCULATING...
RE          IM
          1.          0.
      -4000.          0.
       3000.          0.
      -2000.          0.

(Solve 5x^6-45x^5+225x^4-425x^3+170x^2+370x-500=0)
[DEF-A]
N=6 A6=5 A5=-45 A4=225 A3=-425 A2=170 A1=370 A0=-500
T= L=
CALCULATING...
RE          IM
          2.          0.
          1.          1.
          3.          4.
         -1.          0.
          3.         -4.
          1.         -1.


Code:
' WEIERSTRASS / DURAND-KERNER POLYNOMIAL ROOT FINDER
' Estimate the complex roots of a polynomial
' 2nd order convergence, worst-case linear convergence for roots with multiplicities
'
' For SHARP PC-12xx to 14xx by Robert van Engelen
' To use with SHARP PC models with a single display line:
' - remove CURSOR and CLS
' - replace PRINT with PAUSE except at line 80
'
' Optimized implementations in C and BASIC:
' - Finds complex roots z of polynomial p such that |p(z)|<T given tolerance T
' - Prevents overflow/nan/inf by bounding the root shift adjustment magnitude during polishing
' - Prevents loss of precision when possible, e.g. in reciprocals
' - Tested with a large sample of polynomials
'
' See also: https://en.wikipedia.org/wiki/Durand–Kerner_method

' Algorithm to find complex double precision roots of a polynomial with complex coefficients:
'   int WDK(int n, const complex double a[], complex double z[], int maxit, double toler) {
'     int i, j, k, m;
'     complex double r, p, d;
'     double err, max, h, nprec, dprec;
'     if (maxit <= 0)
'       maxit = 100;
'     if (toler <= 0 || toler >= 1)
'       toler = 1e-8;
'     // optimize by removing trailing zero coefficients
'     for (m = n; m > 1 && a[m] == 0; --m)
'       z[m-1] = 0;
'     // leading coefficient cannot be zero
'     if (m < 1 || a[0] == 0)
'       return -1; // error
'     // generate initial roots
'     for (i = 1; i <= m; ++i)
'       z[i-1] = cpow(CMPLX(.4, .9), i-1);
'     // max root stride modulus permitted to prevent overflow
'     max = pow(DBL_MAX, 1./m);
'     if (max < 1)
'       max = 1;
'     // Weierstrass/Durand-Kerner root polishing
'     for (k = 1; k <= maxit; ++k) {
'       err = 0;
'       for (i = 1; i <= m; ++i) {
'         // compute p(z) using Horner's rule
'         p = a[0];
'         r = z[i-1];
'         for (j = 1; j <= m; ++j)
'           p = p * r + a[j];
'         // if p(z) = 0 within acceptable tolerance, then do not update
'         if (cabs(p) <= toler)
'           continue;
'         // compute denominator d multiplied by a[0] to force monic polynomial
'         d = a[0];
'         for (j = 1; j <= m; ++j)
'           if (i != j)
'             d *= r - z[j-1];
'         // compute p/d if d is not too small to prevent overflow
'         if (cabs(d) > toler)
'           p /= d;
'         h = cabs(p);
'         err += h;
'         // polish root if its shift adjustment is within floating point bounds to prevent overflow
'         if (h < max)
'           z[i-1] = r - p;
'       }
'       // check if convergence error is below our tolerance threshold
'       if (err <= toler)
'         break;
'     }
'     // round results (optional)
'     nprec = toler;
'     dprec = 1/toler;
'     for (i = 1; i <= m; ++i) {
'       r = z[i-1];
'       z[i-1] = nprec*CMPLX(round(creal(r)*dprec), round(cimag(r)*dprec));
'     }
'     return k; // number of iterations >0
'   }
'
' Algorithm to find complex roots of a polynomial with real coefficients, using separate real and imaginary variables:
'   int WDK(int n, const double a[], double zr[], double zi[], int maxit, double toler) {
'     int i, j, k, m;
'     double rr, pr, dr;
'     double ri, pi, di;
'     double s1, s2, k1, k2, k3;
'     double err2, max2, tol2; // err, max, toler squared
'     double h, t, nprec, dprec;
'     const double zh = sqrt(.97), zt = atan(2.25);
'     if (maxit <= 0)
'       maxit = 100;
'     if (toler <= 0 || toler >= 1)
'       toler = 1e-8;
'     tol2 = toler * toler;
'     // optimize by removing trailing zero coefficients
'     for (m = n; m > 1 && a[m] == 0; --m)
'       zr[m-1] = zi[m-1] = 0;
'     // leading coefficient cannot be zero
'     if (m < 1 || a[0] == 0)
'       return -1; // error
'     // generate initial roots z[i]=(.4+.9I)^i for i=0 to m-1
'     h = zh;
'     t = zt;
'     zr[0] = 1;
'     zi[0] = 0;
'     for (i = 2; i <= m; ++i) {
'       zr[i-1] = h * cos(t);
'       zi[i-1] = h * sin(t);
'       h *= zh;
'       t += zt;
'     }
'     // square of max root stride modulus permitted to prevent overflow
'     max2 = m > 2 ? pow(DBL_MAX, 2./m) : DBL_MAX;
'     if (max2 < 1)
'       max2 = 1;
'     // Weierstrass/Durand-Kerner root polishing
'     for (k = 1; k <= maxit; ++k) {
'       err2 = 0;
'       for (i = 1; i <= m; ++i) {
'         // compute p(z) using Horner's rule
'         pr = a[0];
'         pi = 0;
'         rr = zr[i-1];
'         ri = zi[i-1];
'         for (j = 1; j <= m; ++j) {
'           // compute p = p*r + a[j]
'           k1 = pr;
'           pr = pr * rr - pi * ri + a[j];
'           pi = pi * rr + k1 * ri;
'         }
'         // if p(z) = 0 within acceptable tolerance, then do not update
'         if (pr * pr + pi * pi <= tol2)
'           continue;
'         // compute denominator d multiplied by a[0] to force monic polynomial
'         dr = a[0];
'         di = 0;
'         for (j = 1; j <= m; ++j) {
'           if (i != j) {
'             // compute d = d * (r - z[j])
'             s1 = rr - zr[j-1];
'             s2 = ri - zi[j-1];
'             k1 = dr;
'             dr = dr * s1 - di * s2;
'             di = di * s1 + k1 * s2;
'           }
'         }
'         // compute p/d if d is not too small to prevent overflow
'         if (dr * dr + di * di > tol2) {
'           // compute p/d without loss of precision
'           if (fabs(dr) >= fabs(di)) {
'             k1 = di/dr;
'             k2 = k1*di + dr;
'             k3 = pr;
'             pr = (k1*pi + pr)/k2;
'             pi = (pi - k1*k3)/k2;
'           }
'           else {
'             k1 = dr/di;
'             k2 = k1*dr + di;
'             k3 = pr;
'             pr = (k1*pr + pi)/k2;
'             pi = (k1*pi - k3)/k2;
'           }
'         }
'         // polish root if its shift adjustment is within floating point bounds to prevent overflow
'         h = pr * pr + pi * pi;
'         err2 += h;
'         if (h < max2) {
'           zr[i-1] = rr - pr;
'           zi[i-1] = ri - pi;
'         }
'       }
'       // check if convergence error is below our tolerance threshold
'       if (err2 <= tol2)
'         break;
'     }
'     // round results (optional)
'     nprec = toler;
'     dprec = 1/toler;
'     for (i = 1; i <= m; ++i) {
'       zr[i-1] = nprec*round(zr[i-1]*dprec);
'       zi[i-1] = nprec*round(zi[i-1]*dprec);
'     }
'     return k; // number of iterations >0
'   }

' VARIABLES
'  N           degree
'  T           tolerance (1E-6)
'  L           max iterations (20), updated to the iterations executed
'  A(27..27+N) N+1 coefficients indexed from highest to lowest
'  A(O..O+N-1) N roots real parts
'  A(Z..Z+N-1) N roots imag parts
'  E           estimated error of the roots squared
'  O           array offset in A() with the real parts of the roots, O=28+N
'  Z           array offset in A() with the imag parts of the roots, Z=O+N
'  S           tolerance T squared
'  R           max root stride modulus squared
'  I,J,K       loop iterators
'  P,Q         real and imag parts of p(z)
'  A,B,C,D     scratch
'  U,V,W,X,Y   scratch

' driver program
10 "A" CLS: WAIT 0: INPUT "N=";N: IF N<2 GOTO 10
20 FOR I=0 TO N: CLS: PRINT "A";STR$(N-I);"=";A(27+I): CURSOR 24: INPUT A(27+I)
30 NEXT I
40 CURSOR: T=1E-6,L=20: INPUT "T=";T
50 INPUT "L=";L
60 CLS: PRINT "CALCULATING...": O=28+N: GOSUB "WDK"
70 CURSOR: PRINT "RE","IM": WAIT
80 FOR I=0 TO N-1: PRINT A(O+I),A(Z+I): NEXT I
90 END

' polynomial root finder: N degree, T tolerance, L max iters, A(27..27+N) coefficients
100 "WDK" S=T*T,O=28+N,Z=O+N,M=N
' optimize: remove leading zero coefficients (trailing A)
110 IF M>1 IF A(27+M)=0 LET M=M-1,A(O+M)=0,A(Z+M)=0: GOTO 110
120 IF M<1 OR A(27)=0 RETURN
' generate initial roots, square of max root shift distance bounded to prevent overflow
130 RADIAN: X=SQR .97,Y=ATN 2.25,U=X,V=Y,A(O)=1,A(Z)=0,R=1E99: IF M>2 LET R=R^(2/M): IF R<1 LET R=1
140 IF M>1 FOR I=1 TO M-1: A(O+I)=U*COS V,A(Z+I)=U*SIN V,U=U*X,V=V+Y: NEXT I
' iterative root polishing until convergence or max iterations reached
150 FOR K=1 TO L: E=0
160 FOR I=0 TO M-1
' compute p(z) using Horner's rule
170 A=A(O+I),B=A(Z+I),C=A(27),D=0,P=C,Q=0
180 FOR J=0 TO M-1: U=P,P=P*A-Q*B+A(28+J),Q=Q*A+U*B: NEXT J
' optimized: if |p(z)|<tolerance then do not update root z
190 IF P*P+Q*Q<=S GOTO 270
' compute denominator d multiplied by a[0] to force monic polynomial
200 FOR J=0 TO M-1
210 IF I<>J LET X=A-A(O+J),Y=B-A(Z+J),U=C,C=C*X-D*Y,D=D*X+U*Y
220 NEXT J
' compute p/d if d is not too small to prevent overflow
230 X=C*C+D*D: IF X<=S GOTO 260
240 IF ABS C>=ABS D LET U=D/C,V=U*D+C,W=P,P=(U*Q+P)/V,Q=(Q-U*W)/V: GOTO 260
250 U=C/D,V=U*C+D,W=P,P=(U*P+Q)/V,Q=(U*Q-W)/V
' polish root if its shift adjustment is within max bounds to prevent overflow
260 X=P*P+Q*Q,E=E+X: IF X<R LET A(O+I)=A-P,A(Z+I)=B-Q
270 NEXT I
280 CURSOR 24: PRINT "K=";STR$ K;":E=";SQR E: IF E<=S LET U=L,L=K,K=U
290 NEXT K
' round results (optional)
300 U=1/T
310 FOR I=0 TO M-1: A(O+I)=T*INT(U*A(O+I)+.5),A(Z+I)=T*INT(U*A(Z+I)+.5): NEXT I
' return error estimate
320 E=(E=0)*T+SQR E: RETURN

Edit: minor changes to reduce program size.
Reference URL's