Eigenmath/nroots.cpp

270 lines
3.9 KiB
C++

// find the roots of a polynomial numerically
#include "stdafx.h"
#include "defs.h"
#define YMAX 101
#define DELTA 1.0e-6
#define EPSILON 1.0e-9
#define ABS(z) sqrt((z).r * (z).r + (z).i * (z).i)
#define RANDOM (4.0 * (double) rand() / (double) RAND_MAX - 2.0)
static struct {
double r, i;
} a, b, x, y, fa, fb, dx, df, c[YMAX];
void
eval_nroots(void)
{
int h, i, k, n;
push(cadr(p1));
eval();
push(caddr(p1));
eval();
p2 = pop();
if (p2 == symbol(NIL))
guess();
else
push(p2);
p2 = pop();
p1 = pop();
if (!ispoly(p1, p2))
stop("nroots: polynomial?");
// mark the stack
h = tos;
// get the coefficients
push(p1);
push(p2);
n = coeff();
if (n > YMAX)
stop("nroots: degree?");
// convert the coefficients to real and imaginary doubles
for (i = 0; i < n; i++) {
push(stack[h + i]);
real();
yyfloat();
eval();
p1 = pop();
push(stack[h + i]);
imag();
yyfloat();
eval();
p2 = pop();
if (!isdouble(p1) || !isdouble(p2))
stop("nroots: coefficients?");
c[i].r = p1->u.d;
c[i].i = p2->u.d;
}
// pop the coefficients
tos = h;
// n is the number of coefficients, n = deg(p) + 1
monic(n);
for (k = n; k > 1; k--) {
findroot(k);
if (fabs(a.r) < DELTA)
a.r = 0.0;
if (fabs(a.i) < DELTA)
a.i = 0.0;
push_double(a.r);
push_double(a.i);
push(imaginaryunit);
multiply();
add();
divpoly(k);
}
// now make n equal to the number of roots
n = tos - h;
if (n > 1) {
sort_stack(n);
p1 = alloc_tensor(n);
p1->u.tensor->ndim = 1;
p1->u.tensor->dim[0] = n;
for (i = 0; i < n; i++)
p1->u.tensor->elem[i] = stack[h + i];
tos = h;
push(p1);
}
}
// divide the polynomial by its leading coefficient
void
monic(int n)
{
int k;
double t;
y = c[n - 1];
t = y.r * y.r + y.i * y.i;
for (k = 0; k < n - 1; k++) {
c[k].r = (c[k].r * y.r + c[k].i * y.i) / t;
c[k].i = (c[k].i * y.r - c[k].r * y.i) / t;
}
c[n - 1].r = 1.0;
c[n - 1].i = 0.0;
}
// uses the secant method
void
findroot(int n)
{
int j, k;
double t;
if (ABS(c[0]) < DELTA) {
a.r = 0.0;
a.i = 0.0;
return;
}
for (j = 0; j < 100; j++) {
a.r = RANDOM;
a.i = RANDOM;
compute_fa(n);
b = a;
fb = fa;
a.r = RANDOM;
a.i = RANDOM;
for (k = 0; k < 1000; k++) {
compute_fa(n);
if (ABS(fa) < EPSILON)
return;
if (ABS(fa) < ABS(fb)) {
x = a;
a = b;
b = x;
x = fa;
fa = fb;
fb = x;
}
// dx = b - a
dx.r = b.r - a.r;
dx.i = b.i - a.i;
// df = fb - fa
df.r = fb.r - fa.r;
df.i = fb.i - fa.i;
// y = dx / df
t = df.r * df.r + df.i * df.i;
if (t == 0.0)
break;
y.r = (dx.r * df.r + dx.i * df.i) / t;
y.i = (dx.i * df.r - dx.r * df.i) / t;
// a = b - y * fb
a.r = b.r - (y.r * fb.r - y.i * fb.i);
a.i = b.i - (y.r * fb.i + y.i * fb.r);
}
}
stop("nroots: convergence error");
}
void
compute_fa(int n)
{
int k;
double t;
// x = a
x.r = a.r;
x.i = a.i;
// fa = c0 + c1 * x
fa.r = c[0].r + c[1].r * x.r - c[1].i * x.i;
fa.i = c[0].i + c[1].r * x.i + c[1].i * x.r;
for (k = 2; k < n; k++) {
// x = a * x
t = a.r * x.r - a.i * x.i;
x.i = a.r * x.i + a.i * x.r;
x.r = t;
// fa += c[k] * x
fa.r += c[k].r * x.r - c[k].i * x.i;
fa.i += c[k].r * x.i + c[k].i * x.r;
}
}
// divide the polynomial by x - a
void
divpoly(int n)
{
int k;
for (k = n - 1; k > 0; k--) {
c[k - 1].r += c[k].r * a.r - c[k].i * a.i;
c[k - 1].i += c[k].i * a.r + c[k].r * a.i;
}
if (ABS(c[0]) > DELTA)
stop("nroots: residual error");
for (k = 0; k < n - 1; k++) {
c[k].r = c[k + 1].r;
c[k].i = c[k + 1].i;
}
}
#if SELFTEST
static char *s[] = {
"nroots(x)",
"0",
"nroots((1+i)*x^2+1)",
"(-0.17178-0.727673*i,0.17178+0.727673*i)",
"nroots(sqrt(2)*exp(i*pi/4)*x^2+1)",
"(-0.17178-0.727673*i,0.17178+0.727673*i)",
// "nroots(x^4+1)",
// "(-0.707107+0.707107*i,-0.707107-0.707107*i,0.707107+0.707107*i,0.707107-0.707107*i)",
};
void
test_nroots(void)
{
test(__FILE__, s, sizeof s / sizeof (char *));
}
#endif