mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-09-06 07:07:48 -04:00
650 lines
26 KiB
C++
650 lines
26 KiB
C++
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include "PFiftyOneRevE.h"
|
|
#include "QuadRootsRevH.h"
|
|
#include <math.h>
|
|
#include <complex>
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
By Daniel Klostermann
|
|
Iowa Hills Software, LLC IowaHills.com
|
|
Sept 21, 2016 Rev E
|
|
|
|
If you find a problem with this code, please leave a note on:
|
|
http://www.iowahills.com/feedbackcomments.html
|
|
|
|
This is for polynomials with real coefficients, up to 100th order.
|
|
|
|
PFiftyOne has 4 loops. The outermost while loop runs until the polynomial's order
|
|
N, has been reduced to 1 or 2.
|
|
|
|
The TypeOfK loop controls how the K polynomial is initialized. About 99.999% of all
|
|
roots are found with TypeOfK = 0.
|
|
|
|
The AngleNumber loop controls how we initialize the 2nd order polynomial TUV.
|
|
The angles move between the 1st and 2nd quadrants and are defined in the
|
|
SetTUVandK() function. About 99% of roots are found in the 1st 4 angles.
|
|
|
|
The Iter loop first calls the QuadIterate routine, which finds quadratic factors,
|
|
real or complex. If QuadIterate fails to converge in QUAD_ITER_MAX iterations,
|
|
the RealIterate routine is called to try to extract a single real root.
|
|
|
|
The input array Coeff[] contains the coefficients arranged in descending order.
|
|
For example, a 4th order poly would be:
|
|
P(x) = Coeff[0]*x^4 + Coeff[1]*x^3 + Coeff[2]*x^2 + Coeff[3]*x + Coeff[4]
|
|
|
|
On return, there is no particular ordering to the roots, but complex root pairs
|
|
will be in adjacent array cells. It may be helpful to know that the complex root pairs
|
|
are exact cunjugates of each other, meaning that you can use the == operator
|
|
to search for complex pairs in the arrays.F
|
|
|
|
This returns Degree on success, 0 on failure.
|
|
*/
|
|
|
|
namespace kitiirfir
|
|
{
|
|
|
|
//---------------------------------------------------------------------------
|
|
// None of the code uses long double variables except for the P51 root finder code
|
|
// where long doubles were essential. FindRoots is an interface to P51 for code that is
|
|
// using complex variables CplxD.
|
|
int FindRoots(int N, double *Coeff, std::complex<double> *Roots) {
|
|
int j;
|
|
long double P[P51_ARRAY_SIZE], RealRoot[P51_ARRAY_SIZE],
|
|
ImagRoot[P51_ARRAY_SIZE];
|
|
|
|
for (j = 0; j <= N; j++)
|
|
P[j] = Coeff[j]; // double to long double
|
|
N = PFiftyOne(P, N, RealRoot, ImagRoot);
|
|
for (j = 0; j < N; j++)
|
|
Roots[j] = std::complex<double>((double) RealRoot[j],
|
|
(double) ImagRoot[j]); // long double to double
|
|
return (N);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int PFiftyOne(long double *Coeff, int Degree, long double *RealRoot,
|
|
long double *ImagRoot) {
|
|
if (Degree > P51_MAXDEGREE || Degree < 0) {
|
|
//ShowMessage("Poly Degree is out of range in the P51 Root Finder.");
|
|
return (0);
|
|
}
|
|
|
|
TUpdateStatus UpdateStatus;
|
|
int N, NZ, j, Iter, AngleNumber, TypeOfK;
|
|
long double RealZero, QuadX;
|
|
long double TUV[3];
|
|
long double *P, *QuadQP, *RealQP, *QuadK, *RealK, *QK;
|
|
|
|
N = Degree; // N is decremented as roots are found.
|
|
|
|
P = new (std::nothrow) long double[N + 2];
|
|
QuadQP = new (std::nothrow) long double[N + 2];
|
|
RealQP = new (std::nothrow) long double[N + 2];
|
|
QuadK = new (std::nothrow) long double[N + 2];
|
|
RealK = new (std::nothrow) long double[N + 2];
|
|
QK = new (std::nothrow) long double[N + 2];
|
|
if (P == NULL || QuadQP == NULL || RealQP == NULL || QuadK == NULL
|
|
|| RealK == NULL || QK == NULL) {
|
|
//ShowMessage("Memory not Allocated in PFiftyOne root finder.");
|
|
return (0);
|
|
}
|
|
|
|
for (j = 0; j <= N; j++)
|
|
P[j] = Coeff[j]; // Copy the coeff. P gets modified.
|
|
for (j = 0; j < N; j++)
|
|
RealRoot[j] = ImagRoot[j] = 0.0; // Init to zero, in case any leading or trailing zeros are removed.
|
|
|
|
// Remove trailing zeros. A tiny P[N] relative to P[N-1] is not allowed.
|
|
while (N > 0 && fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1])) {
|
|
N--;
|
|
}
|
|
|
|
// Remove leading zeros.
|
|
while (N > 0 && P[0] == 0.0) {
|
|
for (j = 0; j < N; j++)
|
|
P[j] = P[j + 1];
|
|
N--;
|
|
}
|
|
|
|
// P[0] must = 1
|
|
if (P[0] != 1.0) {
|
|
for (j = 1; j <= N; j++)
|
|
P[j] /= P[0];
|
|
P[0] = 1.0;
|
|
}
|
|
|
|
TypeOfK = 0;
|
|
while (N > 4 && TypeOfK < MAX_NUM_K) {
|
|
NZ = 0; // Num Zeros found. (Used in the loop controls below.)
|
|
QuadX = powl(fabsl(P[N]), 1.0 / (long double) N) / 2.0; // QuadX is used to init TUV
|
|
|
|
for (TypeOfK = 0; TypeOfK < MAX_NUM_K && NZ == 0; TypeOfK++) // Iterate on the different possible QuadK inits.
|
|
{
|
|
for (AngleNumber = 0; AngleNumber < MAX_NUM_ANGLES && NZ == 0;
|
|
AngleNumber++) // Iterate on the angle used to init TUV.
|
|
{
|
|
SetTUVandK(P, N, TUV, RealK, QuadK, QuadX, AngleNumber,
|
|
TypeOfK); // Init TUV and both K's
|
|
for (Iter = 0; Iter < N && NZ == 0; Iter++) // Allow N calls to QuadIterate for N*QUAD_ITER_MAX iterations, then try a different angle.
|
|
{
|
|
NZ = QuadIterate(Iter, P, QuadQP, QuadK, QK, N, TUV,
|
|
&UpdateStatus); // NZ = 2 for a pair of complex roots or 2 real roots.
|
|
|
|
if (NZ == 0) // Try for a real root.
|
|
{
|
|
if (fabsl(QuadK[N - 2]) > TINY_VALUE * fabsl(P[N]))
|
|
RealZero = -P[N] / QuadK[N - 2]; // This value gets refined by QuadIterate.
|
|
else
|
|
RealZero = 0.0;
|
|
NZ = RealIterate(Iter, P, RealQP, RealK, QK, N,
|
|
&RealZero); // NZ = 1 for a single real root.
|
|
}
|
|
|
|
if (NZ == 0 && UpdateStatus == BAD_ANGLE)
|
|
break; // If RealIterate failed and UpdateTUV called this a bad angle, it's pointless to iterate further on this angle.
|
|
|
|
} // Iter loop Note the use of NZ in the loop controls.
|
|
} // AngleNumber loop
|
|
} // TypeOfK loop
|
|
|
|
// Done iterating. If NZ==0 at this point, we failed and will exit below.
|
|
// Decrement N, and set P to the quotient QP. QP = P/TUV or QP = P/(x-RealZero)
|
|
if (NZ == 2) // Store a pair of complex roots or 2 real roots.
|
|
{
|
|
j = Degree - N;
|
|
QuadRoots(TUV, &RealRoot[j], &ImagRoot[j]);
|
|
N -= NZ;
|
|
for (j = 0; j <= N; j++)
|
|
P[j] = QuadQP[j];
|
|
TypeOfK = 0;
|
|
}
|
|
|
|
if (NZ == 1) // Store a single real root
|
|
{
|
|
j = Degree - N;
|
|
RealRoot[j] = RealZero;
|
|
ImagRoot[j] = 0.0;
|
|
N -= NZ;
|
|
for (j = 0; j <= N; j++)
|
|
P[j] = RealQP[j];
|
|
TypeOfK = 0;
|
|
}
|
|
|
|
// Remove any trailing zeros on P. P[N] should never equal zero, but can approach zero
|
|
// because of roundoff errors. If P[N] is zero or tiny relative to P[N-1], we take the hit,
|
|
// and place a root at the origin. This needs to be checked, but virtually never happens.
|
|
while (fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1]) && N > 0) {
|
|
j = Degree - N;
|
|
RealRoot[j] = 0.0;
|
|
ImagRoot[j] = 0.0;
|
|
N--;
|
|
//ShowMessage("Removed a zero at the origin.");
|
|
}
|
|
|
|
} // The outermost loop while(N > 2)
|
|
|
|
delete[] QuadQP;
|
|
delete[] RealQP;
|
|
delete[] QuadK;
|
|
delete[] RealK;
|
|
delete[] QK;
|
|
|
|
// Done, except for the last 1 or 2 roots. If N isn't 1 or 2 at this point, we failed.
|
|
if (N == 1) {
|
|
j = Degree - N;
|
|
RealRoot[j] = -P[1] / P[0];
|
|
ImagRoot[j] = 0.0;
|
|
delete[] P;
|
|
return (Degree);
|
|
}
|
|
|
|
if (N == 2) {
|
|
j = Degree - N;
|
|
QuadRoots(P, &RealRoot[j], &ImagRoot[j]);
|
|
delete[] P;
|
|
return (Degree);
|
|
}
|
|
|
|
if (N == 3) {
|
|
j = Degree - N;
|
|
CubicRoots(P, &RealRoot[j], &ImagRoot[j]);
|
|
delete[] P;
|
|
return (Degree);
|
|
}
|
|
|
|
if (N == 4) {
|
|
j = Degree - N;
|
|
BiQuadRoots(P, &RealRoot[j], &ImagRoot[j]);
|
|
delete[] P;
|
|
return (Degree);
|
|
}
|
|
|
|
// ShowMessage("The P51 root finder failed to converge on a solution.");
|
|
return (0);
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// The purpose of this function is to find a 2nd order polynomial, TUV, which is a factor of P.
|
|
// When called, the TUV and K polynomials have been initialized to our best guess.
|
|
// This function can call UpdateTUV() at most QUAD_ITER_MAX times. This returns 2 if we find
|
|
// 2 roots, else 0. UpdateStatus has two possible values on return, UPDATED and BAD_ANGLE.
|
|
// UPDATED means the UpdateTUV function is able to do its calculations with TUV at this angle.
|
|
// BAD_ANGLE tells P51 to move TUV to a different angle before calling this function again.
|
|
// We use ErrScalar to account for the wide variations in N and P[N]. ErrScalar can vary by
|
|
// as much as 1E50 if all the root locations are much less than 1 or much greater than 1.
|
|
// The ErrScalar equation was determined empirically. If this root finder is used in an app
|
|
// where the root locations and poly orders fall within a narrow range, esp low order, then
|
|
// ErrScalar can be modified to give more accurate results.
|
|
|
|
int QuadIterate(int P51_Iter, long double *P, long double *QP, long double *K,
|
|
long double *QK, int N, long double *TUV, TUpdateStatus *UpdateStatus) {
|
|
int Iter;
|
|
long double Err, MinErr, ErrScalar, QKCheck;
|
|
|
|
ErrScalar = 1.0 / (16.0 * powl((long double) N, 3.0) * fabsl(P[N]));
|
|
|
|
P51_Iter *= QUAD_ITER_MAX;
|
|
Err = MinErr = 1.0E100;
|
|
*UpdateStatus = UPDATED;
|
|
QuadSynDiv(P, N, TUV, QP); // Init QP
|
|
QuadSynDiv(K, N - 1, TUV, QK); // Init QK
|
|
|
|
for (Iter = 0; Iter < QUAD_ITER_MAX; Iter++) {
|
|
UpdateTUV(P51_Iter + Iter, P, N, QP, K, QK, TUV, UpdateStatus);
|
|
if (*UpdateStatus == BAD_ANGLE) {
|
|
return (0); // Failure, need a different angle.
|
|
}
|
|
|
|
Err = fabsl(QP[N - 1]) + fabsl(QP[N + 1]); // QP[N-1] & QP[N+1] are the remainder terms of P/TUV.
|
|
Err *= ErrScalar; // Normalize the error.
|
|
|
|
if (Err < LDBL_EPSILON) {
|
|
return (2); // Success!! 2 roots have been found.
|
|
}
|
|
|
|
// ZERO_DEL means both DelU and DelV were ~ 0 in UpdateTUV which means the algorithm has stalled.
|
|
// It might be stalled in a dead zone with large error, or stalled because it can't adjust u and v with a resolution fine enough to meet our convergence criteria.
|
|
if (*UpdateStatus == ZERO_DEL) {
|
|
if (Err < 4.0 * (long double) N * LDBL_EPSILON) // Small error, this is the best we can do.
|
|
{
|
|
*UpdateStatus = UPDATED;
|
|
return (2);
|
|
} else // Large error, get a different angle
|
|
{
|
|
*UpdateStatus = BAD_ANGLE;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
QKCheck = fabsl(QK[N - 2]) + fabsl(QK[N]); // QK[N-2] & QK[N] are the remainder terms of K/TUV.
|
|
QKCheck *= ErrScalar;
|
|
|
|
// Huge values indicate the algorithm is diverging and overflow is imminent. This can indicate
|
|
// a single real root, or that we simply need a different angle on TUV. This happens frequently.
|
|
if (Err > HUGE_VALUE || QKCheck > HUGE_VALUE) {
|
|
*UpdateStatus = BAD_ANGLE;
|
|
return (0);
|
|
}
|
|
|
|
// Record our best result thus far. We turn on the damper in UpdateTUV if the errs increase.
|
|
if (Err < MinErr) {
|
|
*UpdateStatus = DAMPER_OFF;
|
|
MinErr = Err;
|
|
} else if (Iter > 2) {
|
|
*UpdateStatus = DAMPER_ON;
|
|
}
|
|
}
|
|
|
|
// If we get here, we didn't converge, but TUV is getting updated.
|
|
// If RealIterate can't find a real zero, this function will get called again.
|
|
*UpdateStatus = UPDATED;
|
|
return (0);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This function updates TUV[1] and TUV[2] using the J-T mathematics.
|
|
// When called, UpdateStatus equals either DAMPER_ON or DAMPER_OFF, which controls the damping factor.
|
|
// On return UpdateStatus equals UPDATED, BAD_ANGLE or ZERO_DEL;
|
|
// BAD_ANGLE indicates imminent overflow, or TUV[2] is going to zero. In either case the QuadIterate
|
|
// function will immediately return to P51 which will change the angle on TUV. ZERO_DEL indicates
|
|
// we have either stalled in a dead zone, or we lack the needed precision to further refine TUV.
|
|
// TUV = tx^2 + ux + v t = 1 always. v = 0 (a root at the origin) isn't allowed.
|
|
// The poly degrees are P[N] QP[N-2] K[N-1] QK[N-3]
|
|
void UpdateTUV(int Iter, long double *P, int N, long double *QP, long double *K,
|
|
long double *QK, long double *TUV, TUpdateStatus *UpdateStatus) {
|
|
int j;
|
|
long double DelU, DelV, Denom;
|
|
long double E2, E3, E4, E5;
|
|
static int FirstDamperlIter;
|
|
|
|
if (Iter == 0)
|
|
FirstDamperlIter = 0; // Reset this static var.
|
|
if (*UpdateStatus == DAMPER_ON)
|
|
FirstDamperlIter = Iter;
|
|
|
|
// Update K, unless E3 is tiny relative to E2. The algorithm will work its way out of a tiny E3.
|
|
// These equations are from the Jenkins and Traub paper "A Three Stage Algorithm for Real Polynomials Using Quadratic Iteration" Equation 9.8
|
|
E2 = QP[N] * QP[N] + TUV[1] * QP[N] * QP[N - 1]
|
|
+ TUV[2] * QP[N - 1] * QP[N - 1];
|
|
E3 = QP[N] * QK[N - 1] + TUV[1] * QP[N] * QK[N - 2]
|
|
+ TUV[2] * QP[N - 1] * QK[N - 2];
|
|
|
|
if (fabsl(E3) * HUGE_VALUE > fabsl(E2)) {
|
|
E2 /= -E3;
|
|
for (j = 1; j <= N - 2; j++)
|
|
K[j] = E2 * QK[j - 1] + QP[j]; // At covergence, E2 ~ 0, so K ~ QP.
|
|
} else {
|
|
for (j = 1; j <= N - 2; j++)
|
|
K[j] = QK[j - 1];
|
|
}
|
|
K[0] = QP[0]; // QP[0] = 1.0 always
|
|
K[N - 1] = 0.0;
|
|
|
|
QuadSynDiv(K, N - 1, TUV, QK); // Update QK QK = K/TUV
|
|
|
|
// These equations are modified versions of those used in the original Jenkins Traub Fortran algorithm RealPoly, found at www.netlib.org/toms/493
|
|
E3 = QP[N] * QK[N - 1] + TUV[1] * QP[N] * QK[N - 2]
|
|
+ TUV[2] * QP[N - 1] * QK[N - 2];
|
|
E4 = QK[N - 1] * QK[N - 1] + TUV[1] * QK[N - 1] * QK[N - 2]
|
|
+ TUV[2] * QK[N - 2] * QK[N - 2];
|
|
E5 = QP[N - 1] * QK[N - 1] - QP[N] * QK[N - 2];
|
|
|
|
Denom = E5 * K[N - 2] * TUV[2] + E4 * P[N];
|
|
if (fabsl(Denom) * HUGE_VALUE < fabsl(P[N])) {
|
|
*UpdateStatus = BAD_ANGLE; // Denom is tiny, overflow is imminent, get a new angle.
|
|
return;
|
|
}
|
|
|
|
// Calc DelU and DelV. If they are effectively zero, bump them by epsilon.
|
|
DelU = E3 * K[N - 2] * TUV[2] / Denom;
|
|
if (fabsl(DelU) < LDBL_EPSILON * fabsl(TUV[1])) {
|
|
if (DelU < 0.0)
|
|
DelU = -fabsl(TUV[1]) * LDBL_EPSILON;
|
|
else
|
|
DelU = fabsl(TUV[1]) * LDBL_EPSILON;
|
|
}
|
|
|
|
DelV = -E5 * K[N - 2] * TUV[2] * TUV[2] / Denom;
|
|
if (fabsl(DelV) < LDBL_EPSILON * fabsl(TUV[2])) {
|
|
if (DelV < 0.0)
|
|
DelV = -fabsl(TUV[2]) * LDBL_EPSILON;
|
|
else
|
|
DelV = fabsl(TUV[2]) * LDBL_EPSILON;
|
|
}
|
|
|
|
// If we haven't converged by QUAD_ITER_MAX iters, we need to test DelU and DelV for effectiveness.
|
|
if (Iter >= QUAD_ITER_MAX - 1) {
|
|
// We can't improve u and v any further because both DelU and DelV ~ 0 This usually happens when we are near a solution, but we don't have the precision needed to ine u and v enough to meet our convergence criteria. This can also happen in a dead zone where the errors are large, which means we need a different angle on TUV. We test for this in the QuadIterate function.
|
|
if (fabsl(DelU) < 8.0 * LDBL_EPSILON * fabsl(TUV[1])
|
|
&& fabsl(DelV) < 8.0 * LDBL_EPSILON * fabsl(TUV[2])) {
|
|
*UpdateStatus = ZERO_DEL;
|
|
return;
|
|
}
|
|
// A big change after this many iterations means we are wasting our time on this angle.
|
|
if (fabsl(DelU) > 10.0 * fabsl(TUV[1])
|
|
|| fabsl(DelV) > 10.0 * fabsl(TUV[2])) {
|
|
*UpdateStatus = BAD_ANGLE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Dampen the changes for 3 iterations after Damper was set in QuadIterate.
|
|
if (Iter - FirstDamperlIter < 3) {
|
|
DelU *= 0.75;
|
|
DelV *= 0.75;
|
|
}
|
|
|
|
// Update U and V
|
|
TUV[1] += DelU;
|
|
if (fabsl(TUV[2] + DelV) < TINY_VALUE)
|
|
DelV *= 0.9; // If this, we would set TUV[2] = 0 which we can't allow, so we use 90% of DelV.
|
|
TUV[2] += DelV;
|
|
|
|
if (fabsl(TUV[2]) < fabsl(TUV[1]) * TINY_VALUE) {
|
|
*UpdateStatus = BAD_ANGLE; // TUV[2] is effectively 0, which is never allowed.
|
|
return;
|
|
}
|
|
|
|
*UpdateStatus = UPDATED; // TUV was updated.
|
|
QuadSynDiv(P, N, TUV, QP); // Update QP QP = P/TUV
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This function is used to find single real roots. It is similar to Newton's Method.
|
|
// Horners method is used to calculate QK and QP. For an explanation of these methods, see these 2 links.
|
|
// http://mathworld.wolfram.com/NewtonsMethod.html http://en.wikipedia.org/wiki/Horner%27s_method
|
|
|
|
// When called, RealZero contains our best guess for a root, and K is init to the 1st derivative of P.
|
|
// The return value is either 1 or 0, the number of roots found. On return, RealZero contains
|
|
// the root, and QP contains the next P (i.e. P with this root removed).
|
|
// As with QuadIterate, at convergence, K = QP
|
|
int RealIterate(int P51_Iter, long double *P, long double *QP, long double *K,
|
|
long double *QK, int N, long double *RealZero) {
|
|
int Iter, k;
|
|
long double X, DelX, Damper, Err, ErrScalar;
|
|
static long double PrevQPN;
|
|
|
|
ErrScalar = 1.0 / (16.0 * powl((long double) N, 2.0) * fabsl(P[N]));
|
|
|
|
X = *RealZero; // Init with our best guess for X.
|
|
if (P51_Iter == 0)
|
|
PrevQPN = 0.0;
|
|
QK[0] = K[0];
|
|
for (k = 1; k <= N - 1; k++) {
|
|
QK[k] = QK[k - 1] * X + K[k];
|
|
}
|
|
|
|
for (Iter = 0; Iter < REAL_ITER_MAX; Iter++) {
|
|
// Calculate a new QP. This is poly division QP = P/(x+X) QP[0] to QP[N-1] is the quotient.
|
|
// The remainder is QP[N], which is P(X), the error term.
|
|
QP[0] = P[0];
|
|
for (k = 1; k <= N; k++) {
|
|
QP[k] = QP[k - 1] * X + P[k];
|
|
}
|
|
Err = fabsl(QP[N]) * ErrScalar; // QP[N] is the error. ErrScalar accounts for the wide variations in N and P[N].
|
|
|
|
if (Err < LDBL_EPSILON) {
|
|
*RealZero = X;
|
|
return (1); // Success!!
|
|
} else if (Err > HUGE_VALUE)
|
|
return (0); // Overflow is imminent.
|
|
|
|
// Calculate a new K. QK[N-1] is the remainder of K /(x-X).
|
|
// QK[N-1] is approximately P'(X) when P(X) = QP[N] ~ 0
|
|
if (fabsl(QK[N - 1]) > fabsl(P[N] * TINY_VALUE)) {
|
|
DelX = -QP[N] / QK[N - 1];
|
|
K[0] = QP[0];
|
|
for (k = 1; k <= N - 1; k++) {
|
|
K[k] = DelX * QK[k - 1] + QP[k];
|
|
}
|
|
} else // Use this if QK[N-1] ~ 0
|
|
{
|
|
K[0] = 0.0;
|
|
for (k = 1; k <= N - 1; k++)
|
|
K[k] = QK[k - 1];
|
|
}
|
|
|
|
if (fabsl(K[N - 1]) > HUGE_VALUE)
|
|
return (0); // Overflow is imminent.
|
|
|
|
// Calculate a new QK. This is poly division QK = K /(x+X). QK[0] to QK[N-2] is the quotient.
|
|
QK[0] = K[0];
|
|
for (k = 1; k <= N - 1; k++) {
|
|
QK[k] = QK[k - 1] * X + K[k];
|
|
}
|
|
if (fabsl(QK[N - 1]) <= TINY_VALUE * fabsl(P[N]))
|
|
return (0); // QK[N-1] ~ 0 will cause overflow below.
|
|
|
|
// This algorithm routinely oscillates back and forth about a zero with nearly equal pos and neg error magnitudes.
|
|
// If the current and previous error add to give a value less than the current error, we dampen the change.
|
|
Damper = 1.0;
|
|
if (fabsl(QP[N] + PrevQPN) < fabsl(QP[N]))
|
|
Damper = 0.5;
|
|
PrevQPN = QP[N];
|
|
|
|
// QP[N] is P(X) and at convergence QK[N-1] ~ P'(X), so this is ~ Newtons Method.
|
|
DelX = QP[N] / QK[N - 1] * Damper;
|
|
if (X != 0.0) {
|
|
if (fabsl(DelX) < LDBL_EPSILON * fabsl(X)) // If true, the algorithm is stalled, so bump X by 2 epsilons.
|
|
{
|
|
if (DelX < 0.0)
|
|
DelX = -2.0 * X * LDBL_EPSILON;
|
|
else
|
|
DelX = 2.0 * X * LDBL_EPSILON;
|
|
}
|
|
} else // X = 0
|
|
{
|
|
if (DelX == 0.0)
|
|
return (0); // Stalled at the origin, so exit.
|
|
}
|
|
X -= DelX; // Update X
|
|
|
|
} // end of loop
|
|
|
|
// If we get here, we failed to converge.
|
|
return (0);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Derivative of P. Called from SetTUVandK().
|
|
void DerivOfP(long double *P, int N, long double *dP) {
|
|
int j;
|
|
long double Power;
|
|
for (j = 0; j < N; j++) {
|
|
Power = N - j;
|
|
dP[j] = Power * P[j];
|
|
}
|
|
dP[N] = 0.0;
|
|
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Synthetic Division of P by x^2 + ux + v (TUV)
|
|
// The qotient is Q[0] to Q[N-2]. The actual poly remainder terms are Q[N-1] and Q[N+1]
|
|
// The JT math requires the values Q[N-1] and Q[N] to calculate K.
|
|
// These form a 1st order remainder b*x + (b*u + a).
|
|
void QuadSynDiv(long double *P, int N, long double *TUV, long double *Q) {
|
|
int j;
|
|
Q[0] = P[0];
|
|
Q[1] = P[1] - TUV[1] * Q[0];
|
|
for (j = 2; j <= N; j++) {
|
|
Q[j] = P[j] - TUV[1] * Q[j - 1] - TUV[2] * Q[j - 2];
|
|
}
|
|
|
|
// Here we calculate the final remainder term used to calculate the convergence error.
|
|
// This and Q[N-1] are the remainder values you get if you do this poly division manually.
|
|
Q[N + 1] = Q[N - 1] * TUV[1] + Q[N]; // = b*u + a
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This function intializes the TUV and K polynomials.
|
|
void SetTUVandK(long double *P, int N, long double *TUV, long double *RealK,
|
|
long double *QuadK, long double X, int AngleNumber, int TypeOfQuadK) {
|
|
int j;
|
|
long double a, Theta;
|
|
|
|
// These angles define our search pattern in the complex plane. We start in the 1st quadrant,
|
|
// go to the 2nd, then the real axis, etc. The roots are conjugates, so there isn't a need
|
|
// to go to the 3rd or 4th quadrants. The first 2 angles find about 99% of all roots.
|
|
const long double Angle[] = { 45.0, 135.0, 0.0, 90.0, 15.0, 30.0, 60.0,
|
|
75.0, 105.0, 120.0, 150.0,
|
|
165.0, // 12 angles
|
|
6.0, 51.0, 96.0, 141.0, 12.0, 57.0, 102.0, 147.0, 21.0, 66.0, 111.0,
|
|
156.0, 27.0, 72.0, 117.0, 162.0, 36.0, 81.0, 126.0, 171.0, 42.0,
|
|
87.0, 132.0, 177.0, 3.0, 48.0, 93.0, 138.0, 9.0, 54.0, 99.0, 144.0,
|
|
18.0, 63.0, 108.0, 153.0, 24.0, 69.0, 114.0, 159.0, 33.0, 78.0,
|
|
123.0, 168.0, 39.0, 84.0, 129.0,
|
|
174.0, // 60 angles
|
|
46.0, 136.0, 91.0, 1.0, 16.0, 31.0, 61.0, 76.0, 106.0, 121.0, 151.0,
|
|
166.0, 7.0, 52.0, 97.0, 142.0, 13.0, 58.0, 103.0, 148.0, 22.0, 67.0,
|
|
112.0, 157.0, 28.0, 73.0, 118.0, 163.0, 37.0, 82.0, 127.0, 172.0,
|
|
43.0, 88.0, 133.0, 178.0, 4.0, 49.0, 94.0, 139.0, 10.0, 55.0, 100.0,
|
|
145.0, 19.0, 64.0, 109.0, 154.0, 25.0, 70.0, 115.0, 160.0, 34.0,
|
|
79.0, 124.0, 169.0, 40.0, 85.0, 130.0, 175.0, 47.0, 137.0, 92.0,
|
|
2.0, 17.0, 32.0, 62.0, 77.0, 107.0, 122.0, 152.0, 167.0, 8.0, 53.0,
|
|
98.0, 143.0, 14.0, 59.0, 104.0, 149.0, 23.0, 68.0, 113.0, 158.0,
|
|
29.0, 74.0, 119.0, 164.0, 38.0, 83.0, 128.0, 173.0, 44.0, 89.0,
|
|
134.0, 179.0, 5.0, 50.0, 95.0, 140.0, 11.0, 56.0, 101.0, 146.0,
|
|
20.0, 65.0, 110.0, 155.0, 26.0, 71.0, 116.0, 161.0, 35.0, 80.0,
|
|
125.0, 170.0, 41.0, 86.0, 131.0, 176.0 }; // 180 angles
|
|
|
|
// Initialize TUV to form (x - (a + jb)) * (x - (a - jb)) = x^2 - 2ax + a^2 + b^2
|
|
// We init TUV for complex roots, except at angle 0, where we use real roots at +/- X
|
|
if (AngleNumber == 2) // At 0 degrees we int to 2 real roots at +/- X.
|
|
{
|
|
TUV[0] = 1.0; // t
|
|
TUV[1] = 0.0; // u
|
|
TUV[2] = -(X * X); // v
|
|
} else // We init to a complex root at a +/- jb
|
|
{
|
|
Theta = Angle[AngleNumber] / 180.0 * M_PI;
|
|
a = X * cosl(Theta);
|
|
//b = X * sinl(Theta);
|
|
TUV[0] = 1.0;
|
|
TUV[1] = -2.0 * a;
|
|
TUV[2] = X * X; // = a*a + b*b because cos^2 + sin^2 = 1
|
|
}
|
|
|
|
// The code below initializes the K polys used by RealIterate and QuadIterate.
|
|
|
|
// Initialize the K poly used in RealIterate to P'.
|
|
DerivOfP(P, N, RealK);
|
|
RealK[N] = 0.0;
|
|
|
|
// Initialize the K poly used in QuadIterate. Initializing QuadK to P" works virtually
|
|
// 100% of the time, but if P51 stalls on a difficult root, these alternate inits give
|
|
// us a way to work out of the stall. All these inits work almost as well as P".
|
|
if (TypeOfQuadK == 0) // Init QuadK 2nd derivative of P
|
|
{
|
|
long double *Temp = new (std::nothrow) long double[N + 2];
|
|
if (Temp == NULL) {
|
|
//ShowMessage("Memory not Allocated in PFiftyOne SetTUVandK.");
|
|
return;
|
|
}
|
|
|
|
DerivOfP(P, N, Temp);
|
|
DerivOfP(Temp, N - 1, QuadK);
|
|
QuadK[N] = QuadK[N - 1] = 0.0;
|
|
delete[] Temp;
|
|
}
|
|
|
|
else if (TypeOfQuadK == 1) // Set QuadK to QP, because K = QP at convergence.
|
|
{
|
|
QuadSynDiv(P, N, TUV, QuadK);
|
|
QuadK[N] = QuadK[N - 1] = 0.0;
|
|
}
|
|
|
|
else if (TypeOfQuadK == 2) // Set QuadK to the 1st derivative of P
|
|
{
|
|
for (j = 0; j <= N - 2; j++)
|
|
QuadK[j] = RealK[j + 1];
|
|
QuadK[N] = QuadK[N - 1] = 0.0;
|
|
}
|
|
|
|
else // Set QuadK to zero, except QuadK[0].
|
|
{
|
|
for (j = 1; j <= N; j++)
|
|
QuadK[j] = 0.0;
|
|
QuadK[0] = 1.0;
|
|
}
|
|
|
|
if (QuadK[0] == 0.0)
|
|
QuadK[0] = 1.0; // This can happen if TypeOfQuadK == 2 and P[1] == 0.0
|
|
for (j = N - 2; j > 0; j--)
|
|
QuadK[j] /= QuadK[0];
|
|
QuadK[0] = 1.0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
} // namespace
|