Program Listing for File PolynomialRoots-3-Quartic.cc¶
↰ Return to documentation for file (PolynomialRoots-3-Quartic.cc)
/*--------------------------------------------------------------------------*\
| |
| Copyright (C) 2017 |
| |
| , __ , __ |
| /|/ \ /|/ \ |
| | __/ _ ,_ | __/ _ ,_ |
| | \|/ / | | | | \|/ / | | | |
| |(__/|__/ |_/ \_/|/|(__/|__/ |_/ \_/|/ |
| /| /| |
| \| \| |
| |
| Enrico Bertolazzi |
| Dipartimento di Ingegneria Industriale |
| Universita` degli Studi di Trento |
| email: enrico.bertolazzi@unitn.it |
| |
\*--------------------------------------------------------------------------*/
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
#ifdef __clang__
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wvla-extension"
#pragma clang diagnostic ignored "-Wvla"
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
#endif
#include "PolynomialRoots.hh"
#include <cmath>
#include <iostream>
#include <algorithm>
#include <limits>
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#define MAX_ITER_SAFETY 50
namespace PolynomialRoots {
using std::abs;
static valueType const machepsi = std::numeric_limits<valueType>::epsilon();
indexType
Quartic::getRealRoots( valueType r[] ) const {
indexType nr = 0;
if ( !cplx0() ) r[nr++] = r0;
if ( !cplx1() ) r[nr++] = r1;
if ( !cplx2() ) r[nr++] = r2;
if ( !cplx3() ) r[nr++] = r3;
return nr;
}
indexType
Quartic::getPositiveRoots( valueType r[] ) const {
indexType nr = 0;
if ( !cplx0() && r0 > 0 ) r[nr++] = r0;
if ( !cplx1() && r1 > 0 ) r[nr++] = r1;
if ( !cplx2() && r2 > 0 ) r[nr++] = r2;
if ( !cplx3() && r3 > 0 ) r[nr++] = r3;
return nr;
}
indexType
Quartic::getNegativeRoots( valueType r[] ) const {
indexType nr = 0;
if ( !cplx0() && r0 < 0 ) r[nr++] = r0;
if ( !cplx1() && r1 < 0 ) r[nr++] = r1;
if ( !cplx2() && r2 < 0 ) r[nr++] = r2;
if ( !cplx3() && r3 < 0 ) r[nr++] = r3;
return nr;
}
indexType
Quartic::getRootsInRange( valueType a, valueType b, valueType r[] ) const {
indexType nr = 0;
if ( !cplx0() && r0 >= a && r0 <= b ) r[nr++] = r0;
if ( !cplx1() && r1 >= a && r1 <= b ) r[nr++] = r1;
if ( !cplx2() && r2 >= a && r2 <= b ) r[nr++] = r2;
if ( !cplx3() && r3 >= a && r3 <= b ) r[nr++] = r3;
return nr;
}
indexType
Quartic::getRootsInOpenRange( valueType a, valueType b, valueType r[] ) const {
indexType nr = 0;
if ( !cplx0() && r0 > a && r0 < b ) r[nr++] = r0;
if ( !cplx1() && r1 > a && r1 < b ) r[nr++] = r1;
if ( !cplx2() && r2 > a && r2 < b ) r[nr++] = r2;
if ( !cplx3() && r3 > a && r3 < b ) r[nr++] = r3;
return nr;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// x^4 + A x^3 + B x^2 + C x + D
static
inline
void
scaleQuarticMonicPolynomial(
valueType A,
valueType B,
valueType C,
valueType D,
valueType & AS,
valueType & BS,
valueType & CS,
valueType & DS,
indexType & i_case,
valueType & scale
) {
valueType a = std::abs(A);
valueType b = std::sqrt(abs(B));
valueType c = std::cbrt(abs(C));
valueType d = std::sqrt(sqrt(abs(D)));
if ( a < b ) {
if ( b < c ) {
if ( c < d ) i_case = 0; // a < b < c < d --> d MAX
else i_case = 1; // a < b < c and d <= c --> c MAX
} else {
if ( b < d ) i_case = 0; // a < b and c <= b < d --> d MAX
else i_case = 2; // a < b and c <= b and d <= b --> b MAX
}
} else {
if ( a < c ) {
if ( c < d ) i_case = 0; // b <= a < c < d --> d MAX
else i_case = 1; // b <= a < c and d <= c --> c MAX
} else {
if ( a < d ) i_case = 0; // b <= a and c <= a and a < d --> d MAX
else i_case = 3; // b <= a and c <= a and d <= a --> a MAX
}
}
switch ( i_case ) {
case 0:
scale = d;
AS = A/d;
BS = (B/d)/d;
CS = ((C/d)/d)/d;
DS = D > 0 ? 1 : -1;
break;
case 1:
scale = c;
AS = A/c;
BS = (B/c)/c;
CS = C > 0 ? 1 : -1;
DS = (((D/c)/c)/c)/c;
break;
case 2:
scale = b;
AS = A/b;
BS = B > 0 ? 1 : -1;
CS = ((C/b)/b)/b;
DS = (((D/b)/b)/b)/b;
break;
case 3:
scale = a;
AS = A > 0 ? 1 : -1;
BS = (B/a)/a;
CS = ((C/a)/a)/a;
DS = (((D/a)/a)/a)/a;
break;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static
inline
valueType
evalHexic(
valueType x,
valueType q3,
valueType q2,
valueType q1,
valueType q0
) {
valueType t1 = x + q3;
valueType t2 = x + t1;
valueType t3 = x + t2;
t1 = t1 * x + q2;
t2 = t2 * x + t1;
t1 = t1 * x + q1;
valueType Q = t1 * x + q0; // Q(x)
valueType dQ = t2 * x + t1; // Q'(x)
valueType ddQ = t3 * x + t2; // Q''(x) / 2
valueType dddQ = x + t3; // Q'''(x) / 6
return Q * dddQ * dddQ - dQ * ddQ * dddQ + dQ * dQ; // H(x), usually < 0
}
static
inline
void
evalHexic(
valueType x,
valueType q3,
valueType q2,
valueType q1,
valueType q0,
valueType & p,
valueType & dp
) {
valueType t1 = x + q3;
valueType t2 = x + t1;
valueType t3 = x + t2;
t1 = t1 * x + q2;
t2 = t2 * x + t1;
t1 = t1 * x + q1;
valueType Q = t1 * x + q0; // Q(x)
valueType dQ = t2 * x + t1; // Q'(x)
valueType ddQ = t3 * x + t2; // Q''(x) / 2
valueType dddQ = x + t3; // Q'''(x) / 6
p = Q * dddQ * dddQ - dQ * ddQ * dddQ + dQ * dQ; // H(x), usually < 0
dp = 2 * dddQ * (4 * Q - dQ * dddQ - ddQ * ddQ); // H'(x)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// a4*x^4 + a3*x^3 + a2*x^2 + a1*x + a0 = (x-r)*(a4*x^3+b2*x^2+b1*x+b0)
static
void
deflateQuarticPolynomial(
valueType a4,
valueType a3,
valueType a2,
valueType a1,
valueType a0,
valueType r,
valueType & b2,
valueType & b1,
valueType & b0
) {
indexType i_cross = 0;
valueType r2 = r*r;
valueType v_cross = std::abs(a0);
valueType v_cross1 = std::abs(a1*r);
if ( v_cross1 > v_cross ) { v_cross = v_cross1; i_cross = 1; }
v_cross1 = std::abs(a2*r2);
if ( v_cross1 > v_cross ) { v_cross = v_cross1; i_cross = 2; }
v_cross1 = std::abs(a3*r*r2);
if ( v_cross1 > v_cross ) { v_cross = v_cross1; i_cross = 3; }
v_cross1 = std::abs(a4*r2*r2);
if ( v_cross1 > v_cross ) i_cross = 4;
switch ( i_cross ) {
case 0: b2 = a3+a4*r; b1 = a2+r*b2; b0 = a1+r*b1; break;
case 1: b2 = a3+a4*r; b1 = a2+r*b2; b0 = -a0/r; break;
case 2: b2 = a3+a4*r; b0 = -a0/r; b1 = (b0-a1)/r; break;
case 3:
case 4: b0 = -a0/r; b1 = (b0-a1)/r; b2 = (b1-a2)/r; break;
}
}
/*
|| _ _ _ ____ _ _ _
|| | \ | | _____ _| |_ ___ _ __ | __ )(_)___ ___ ___| |_(_) ___ _ __
|| | \| |/ _ \ \ /\ / / __/ _ \| '_ \| _ \| / __|/ _ \/ __| __| |/ _ \| '_ \
|| | |\ | __/\ V V /| || (_) | | | | |_) | \__ \ __/ (__| |_| | (_) | | | |
|| |_| \_|\___| \_/\_/ \__\___/|_| |_|____/|_|___/\___|\___|\__|_|\___/|_| |_|
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Translate to C from Polynomial234RootSolvers
static
indexType
zeroQuarticByNewtonBisection(
valueType a,
valueType b,
valueType c,
valueType d,
valueType & x
) {
valueType p, dp;
evalMonicQuartic( x, a, b, c, d, p, dp );
valueType t = p; // save p(x) for sign comparison
x -= p/dp; // 1st improved root
indexType iter = 1;
indexType oscillate = 0;
indexType nconverged = 0;
bool bisection = false;
bool converged = false;
valueType s(0), u(0); // to mute warning
while ( ! ( nconverged > 1 || bisection ) && iter < MAX_ITER_SAFETY ) {
++iter;
valueType ddp;
evalMonicQuartic( x, a, b, c, d, p, dp, ddp );
if ( p*t < 0 ) { // does Newton start oscillating ?
if ( p < 0 ) {
++oscillate; // increment oscillation counter
s = x; // save lower bisection bound
} else {
u = x; // save upper bisection bound
}
t = p; // save current p(x)
}
//dp = (p*dp)/(dp*dp-p*ddp); // double zero correction
dp = (p*dp)/(dp*dp-0.5*p*ddp); // Halley correction
//dp = p/dp; // Newton correction
x -= dp; // new Newton root
bisection = oscillate > 2; // activate bisection
converged = std::abs(dp) <= (1+std::abs(x)) * machepsi; // Newton convergence indicator
if ( converged ) ++nconverged; else nconverged = 0;
}
if ( bisection || !converged ) {
t = u - s; // initial bisection interval
while ( std::abs(t) > (1+std::abs(x)) * machepsi && iter < MAX_ITER_SAFETY ) { // bisection iterates
++iter;
p = evalMonicQuartic( x, a, b, c, d );
if ( p < 0 ) s = x;
else u = x; // keep bracket on root
t = (u-s)/2; // new bisection interval
x = s + t; // new bisection root
}
}
return iter;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Translate to C from Polynomial234RootSolvers
static
indexType
zeroHexicByNewtonBisection(
valueType q3,
valueType q2,
valueType q1,
valueType q0,
valueType & x
) {
valueType p, dp;
evalHexic( x, q3, q2, q1, q0, p, dp );
valueType t = p; // save p(x) for sign comparison
x -= p/dp; // 1st improved root
indexType iter = 1;
indexType oscillate = 0;
bool bisection = false;
bool converged = false;
valueType s(0), u(0); // to mute warning
while ( ! (converged||bisection) ) {
++iter;
evalHexic( x, q3, q2, q1, q0, p, dp );
if ( p*t < 0 ) { // does Newton start oscillating ?
if ( p < 0 ) {
++oscillate; // increment oscillation counter
s = x; // save lower bisection bound
} else {
u = x; // save upper bisection bound
}
t = p; // save current p(x)
}
dp = p/dp; // Newton correction
x -= dp; // new Newton root
bisection = oscillate > 2; // activate bisection
converged = std::abs(dp) <= std::abs(x) * machepsi; // Newton convergence indicator
}
if ( bisection ) {
t = u - s; // initial bisection interval
while ( std::abs(t) > std::abs(x) * machepsi ) { // bisection iterates
++iter;
p = evalHexic( x, q3, q2, q1, q0 );
if ( p < 0 ) s = x;
else u = x; // keep bracket on root
t = (u-s)/2; // new bisection interval
x = s + t; // new bisection root
}
}
return iter;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/*
|| _ __ _ _
|| __| | ___ / _| | __ _| |_ ___
|| / _` |/ _ \ |_| |/ _` | __/ _ \
|| | (_| | __/ _| | (_| | || __/
|| \__,_|\___|_| |_|\__,_|\__\___|
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// a3*x^3 + a2*x^2 + a1*x + a0 = (x-r)*(a3*x^2+b1*x+b0)
/*\
* Calculate the zeros of the quartic a*z^4 + b*z^3 + c*z^2 + d*x + e.
\*/
void
Quartic::findRoots() {
valueType const & A = ABCDE[0];
valueType const & B = ABCDE[1];
valueType const & C = ABCDE[2];
valueType const & D = ABCDE[3];
valueType const & E = ABCDE[4];
iter = nreal = ncplx = 0;
// special cases
if ( isZero(A) ) {
Cubic csolve( B, C, D, E );
nreal = csolve.numRoots();
switch ( nreal ) {
case 3: r2 = csolve.real_root2();
case 2: r1 = csolve.real_root1();
case 1: r0 = csolve.real_root0();
}
if ( csolve.complexRoots() ) { ncplx = 2; nreal -= 2; }
return;
}
if ( isZero(E) ) {
Cubic csolve( A, B, C, D );
nreal = csolve.numRoots();
r0 = r1 = r2 = r3 = 0;
switch ( nreal ) {
case 3: r2 = csolve.real_root2();
case 2: r1 = csolve.real_root1();
case 1: r0 = csolve.real_root0();
}
++nreal;
if ( csolve.complexRoots() ) { ncplx = 2; nreal -= 2; }
if ( nreal == 4 ) { // caso regolare, 4 radici reali maintain order
if ( r3 < r2 ) std::swap(r2,r3);
if ( r2 < r1 ) std::swap(r1,r2);
if ( r1 < r0 ) std::swap(r0,r1);
}
return;
}
if ( isZero(B) && isZero(D) ) { // biquadratic case
// A x^4 + C x^2 + E
Quadratic qsolve( A, C, E ) ;
valueType x = qsolve.real_root0() ;
valueType y = qsolve.real_root1() ;
if ( qsolve.complexRoots() ) {
// complex conjugate pair biquadratic roots x +/- iy.
ncplx = 4;
x /= 2; // re
y /= 2; // im
valueType z = hypot(x,y);
y = std::sqrt(z - x);
x = std::sqrt(z + x);
r0 = -x;
r1 = y;
r2 = x;
r3 = y;
} else {
// real roots of quadratic are ordered x <= y
if ( x >= 0 ) { // y >= 0
nreal = 4;
x = std::sqrt(x); y = std::sqrt(y);
r0 = -y; r1 = -x; r2 = x; r3 = y;
} else if ( y >= 0 ) { // x < 0 && y >= 0
nreal = ncplx = 2;
x = std::sqrt(-x); y = std::sqrt(y);
r0 = 0; r1 = x; // (real,imaginary)
r2 = -y; r3 = y;
} else { // x < 0 && y < 0
ncplx = 4;
x = std::sqrt(-x); y = std::sqrt(-y);
r0 = 0; r1 = x; r2 = 0; r3 = y; // 2 x (real,imaginary)
}
}
return;
}
/*
.. The general case. Rescale quartic polynomial, such that largest absolute
.. coefficient is (exactly!) equal to 1. Honor the presence of a special
.. quartic case that might have been obtained during the rescaling process
.. (due to underflow in the coefficients).
*/
valueType const A3 = B/A;
valueType const A2 = C/A;
valueType const A1 = D/A;
valueType const A0 = E/A;
valueType q0, q1, q2, q3, scale;
indexType i_case;
scaleQuarticMonicPolynomial( A3, A2, A1, A0, q3, q2, q1, q0, i_case, scale );
// Q(x) = q0 + q1 * x + q2 * x^2 + q3 * x^3 + x^4
// Q'(x)/4 = q1/4 + (q2/2) * x + (3/4)*q3 * x^2 + x^3
/*
.. The general quartic case. Search for stationary points. Set the first
.. derivative polynomial (cubic) equal to zero and find its roots.
.. Check, if any minimum point of Q(x) is below zero, in which case we
.. must have real roots for Q(x). Hunt down only the real root, which
.. will potentially converge fastest during Newton iterates. The remaining
.. roots will be determined by deflation Q(x) -> cubic.
..
.. The best roots for the Newton iterations are the two on the opposite
.. ends, i.e. those closest to the +2 and -2. Which of these two roots
.. to take, depends on the location of the Q(x) minima x = s and x = u,
.. with s > u. There are three cases:
..
.. 1) both Q(s) and Q(u) < 0
.. ----------------------
..
.. The best root is the one that corresponds to the lowest of
.. these minima. If Q(s) is lowest -> start Newton from +2
.. downwards (or zero, if s < 0 and a0 > 0). If Q(u) is lowest
.. -> start Newton from -2 upwards (or zero, if u > 0 and a0 > 0).
..
.. 2) only Q(s) < 0
.. -------------
..
.. With both sides +2 and -2 possible as a Newton starting point,
.. we have to avoid the area in the Q(x) graph, where inflection
.. points are present. Solving Q''(x) = 0, leads to solutions
.. x = -a3/4 +/- discriminant, i.e. they are centered around -a3/4.
.. Since both inflection points must be either on the r.h.s or l.h.s.
.. from x = s, a simple test where s is in relation to -a3/4 allows
.. us to avoid the inflection point area.
..
.. 3) only Q(u) < 0
.. -------------
..
.. Same of what has been said under 2) but with x = u.
*/
valueType c2 = 3 * (q3/4);
valueType c1 = q2/2;
valueType c0 = q1/4;
Cubic qsolve( 1, c2, c1, c0 );
valueType u = qsolve.real_root0(); // root according to paper
valueType t = qsolve.real_root1();
valueType s = qsolve.real_root2();
bool must_refine_r3 = true;
if ( !qsolve.complexRoots() ) {
valueType Qs = evalMonicQuartic( s, q3, q2, q1, q0 );
valueType Qu = evalMonicQuartic( u, q3, q2, q1, q0 );
bool q0pos = q0 > 0;
nreal = 1;
if ( Qs < 0 && Qu < 0 ) {
if ( Qs < Qu ) {
r3 = 2;
if ( q0pos && s < 0 ) r3 = 0;
} else {
r3 = -2;
if ( q0pos && u > 0 ) r3 = 0;
}
} else if ( Qs < 0 ) {
if ( 4*s < -q3 ) {
r3 = -2;
if ( q0pos && s > 0 ) r3 = 0;
} else {
r3 = 2;
if ( q0pos && s < 0 ) r3 = 0;
}
} else if ( Qu < 0 ) {
if ( 4*u < -q3 ) {
r3 = -2;
if ( q0pos && u > 0 ) r3 = 0;
} else {
r3 = 2;
if ( q0pos && u < 0 ) r3 = 0;
}
} else {
// check for astrological combination when s or u are root of the quartic
/*
DO NOT WORK
valueType epsi = 10 * ( 1 + std::abs(q3) +
std::abs(q2) +
std::abs(q1) +
std::abs(q0) ) * machepsi;
if ( Qs <= epsi ) r3 = s;
else if ( Qu <= epsi ) r3 = u;
else nreal = 0;
*/
if ( isZero(Qs) ) {
must_refine_r3 = false; r3 = s;
} else if ( isZero(Qu) ) {
must_refine_r3 = false; r3 = u;
} else {
nreal = 0;
}
}
} else {
// one single real root (only 1 minimum)
valueType Qs = evalMonicQuartic( s, q3, q2, q1, q0 );
if ( Qs <= 0 ) {
valueType tmp = q0 >= 0 ? 0 : 2;
r3 = u > 0 ? -tmp : -2;
nreal = 1;
}
}
/*
.. Do all necessary Newton iterations. In case we have more than 2 oscillations,
.. exit the Newton iterations and switch to bisection. Note, that from the
.. definition of the Newton starting point, we always have Q(x) > 0 and Q'(x)
.. starts (-ve/+ve) for the (-2/+2) starting points and (increase/decrease) smoothly
.. and staying (< 0 / > 0). In practice, for extremely shallow Q(x) curves near the
.. root, the Newton procedure can overshoot slightly due to rounding errors when
.. approaching the root. The result are tiny oscillations around the root. If such
.. a situation happens, the Newton iterations are abandoned after 3 oscillations
.. and further location of the root is done using bisection starting with the
.. oscillation brackets.
*/
if ( nreal > 0 ) {
if ( must_refine_r3 )
iter += zeroQuarticByNewtonBisection( q3, q2, q1, q0, r3 );
r3 *= scale;
/*
.. Find remaining roots -> reduce to cubic. The reduction to a cubic polynomial
.. is done using composite deflation to minimize rounding errors. Also, while
.. the composite deflation analysis is done on the reduced quartic, the actual
.. deflation is being performed on the original quartic again to avoid enhanced
.. propagation of root errors.
*/
deflateQuarticPolynomial( A, B, C, D, E, r3, q2, q1, q0 );
Cubic csolve( A, q2, q1, q0 );
r0 = csolve.real_root0();
r1 = csolve.real_root1();
r2 = csolve.real_root2();
if ( csolve.complexRoots() ) {
nreal = ncplx = 2;
if ( r2 > r3 ) std::swap( r2, r3 );
} else {
nreal = 4;
if ( r2 > r3 ) std::swap( r2, r3 );
if ( r1 > r2 ) std::swap( r1, r2 );
if ( r0 > r1 ) std::swap( r0, r1 );
}
} else {
/*
.. If no real roots have been found by now, only complex roots are
.. possible. Find real parts of roots first, followed by imaginary
.. components.
*/
s = q3/2;
t = s * s - q2;
u = s * t + q1; // value of Q'(-q3/4) at stationary point -q3/4
bool notZero = (abs(u) >= machepsi); // H(-a3/4) is considered > 0 at stationary point
bool minimum;
if ( !isZero(q3) ) {
s = q1 / q3;
minimum = q0 > s * s; // H''(-q3/4) > 0 -> minimum
} else {
minimum = 4 * q0 > q2 * q2; // H''(-q3/4) > 0 -> minimum
}
bool iterate = notZero || (!notZero && minimum);
valueType a, b, c, d;
if ( iterate ) {
valueType x = q3 >= 0 ? 2 : -2; // initial root -> target = smaller mag root
iter += zeroHexicByNewtonBisection( q3, q2, q1, q0, x );
a = x*scale; // 1st real component -> a
b = -A3/2 - a; // 2nd real component -> b
x = 4 * a + A3; // Q'''(a)
valueType y = x + 2*A3;
y = y * a + 2*A2; // Q'(a)
y = y * a + A1;
y /= x;
if ( y < 0 ) y = 0; // ensure Q'(a) / Q'''(a) >= 0
x = 4 * b + A3; // Q'''(b)
valueType z = x + 2*A3;
z = z * b + 2*A2; // Q'(b)
z = z * b + A1;
z /= x;
if ( z < 0 ) z = 0; // ensure Q'(b) / Q'''(b) >= 0
c = a * a; // store a^2 for later
d = b * b; // store b^2 for later
valueType ss = c + y; // magnitude^2 of (a + iy) root
valueType tt = d + z; // magnitude^2 of (b + iz) root
if ( ss > tt ) { // minimize imaginary error
c = std::sqrt(y); // 1st imaginary component -> c
d = std::sqrt(A0 / ss - d); // 2nd imaginary component -> d
} else {
c = std::sqrt(A0 / tt - c); // 1st imaginary component -> c
d = std::sqrt(z); // 2nd imaginary component -> d
}
} else { // no bisection -> real components equal
a = -A3/4; // 1st real component -> a
b = a; // 2nd real component -> b = a
valueType x = a + A3;
x = x * a + A2; // Q(a)
x = x * a + A1;
x = x * a + A0;
valueType y = A2/2 - 3*(a*a); // Q''(a) / 2
valueType z = y * y - x;
z = z > 0 ? std::sqrt(z) : 0; // force discriminant to be >= 0
// square root of discriminant
y = y > 0 ? y + z : y - z; // larger magnitude root
x /= y; // smaller magnitude root
c = y < 0 ? 0 : std::sqrt(y); // ensure root of biquadratic > 0
d = x < 0 ? 0 : std::sqrt(x); // large magnitude imaginary component
}
ncplx = 4;
if (a > b) { r0 = a; r1 = c; r2 = b; r3 = d; }
else if (a < b) { r0 = b; r1 = d; r2 = a; r3 = c; }
else { r0 = a; r1 = c; r2 = a; r3 = d; }
}
}
void
Quartic::info( std::ostream & s ) const {
valueType const & A = ABCDE[0];
valueType const & B = ABCDE[1];
valueType const & C = ABCDE[2];
valueType const & D = ABCDE[3];
valueType const & E = ABCDE[4];
s << "\npoly a=" << A << " b=" << B << " c=" << C << " d=" << D << " e=" << E
<< "\nn. complex = " << ncplx
<< "\nn. real = " << nreal;
if ( ncplx > 0 ) {
s << "\nx0 = (" << r0 << "," << r1 << ')'
<< "\nx1 = (" << r0 << "," << -r1 << ')';
} else {
if ( nreal > 0 ) s << "\nx0 = " << r0;
if ( nreal > 1 ) s << "\nx1 = " << r1;
}
if ( ncplx > 2 ) {
s << "\nx2 = (" << r2 << "," << r3 << ')'
<< "\nx3 = (" << r2 << "," << -r3 << ')';
} else {
if ( nreal > 2 || (ncplx > 0 && nreal > 0) ) s << "\nx2 = " << r2;
if ( nreal > 3 || (ncplx > 0 && nreal > 1) ) s << "\nx3 = " << r3;
}
s << '\n';
}
bool
Quartic::check( std::ostream & s ) const {
valueType const & A = ABCDE[0];
valueType const & B = ABCDE[1];
valueType const & C = ABCDE[2];
valueType const & D = ABCDE[3];
valueType const & E = ABCDE[4];
bool ok = true;
valueType epsi = 1000 * ( ( std::abs(A) +
std::abs(B) +
std::abs(C) +
std::abs(D) +
std::abs(E) )*machepsi) ;
if ( ncplx > 0 ) {
valueType z0 = std::abs(eval( root0() ));
valueType z1 = std::abs(eval( root1() ));
s << "|p(r0)| = " << z0 << "\n|p(r1)| = " << z1 << '\n';
ok = ok && std::abs(z0) < epsi && std::abs(z1) < epsi;
} else {
if ( nreal > 0 ) {
valueType z0 = eval( real_root0() );
s << "p(r0) = " << z0 << '\n';
ok = ok && std::abs(z0) < epsi;
}
if ( nreal > 1 ) {
valueType z1 = eval( real_root1() );
s << "p(r1) = " << z1 << '\n';
ok = ok && std::abs(z1) < epsi;
}
}
if ( ncplx > 2 ) {
valueType z2 = std::abs(eval( root2() ));
valueType z3 = std::abs(eval( root3() ));
s << "|p(r2)| = " << z2 << "\n|p(r3)| = " << z3 << '\n';
ok = ok && std::abs(z2) < epsi && std::abs(z3) < epsi;
} else {
if ( nreal > 2 || (ncplx > 0 && nreal > 0) ) {
valueType z2 = eval( real_root2() );
s << "p(r2) = " << z2 << '\n';
ok = ok && std::abs(z2) < epsi;
}
if ( nreal > 3 || (ncplx > 0 && nreal > 1) ) {
valueType z3 = eval( real_root3() );
s << "p(r3) = " << z3 << '\n';
ok = ok && std::abs(z3) < epsi;
}
}
return ok;
}
}
#endif
// EOF: PolynomialRoots-3-Quartic.cc