Jump to content
  • Advertisement
Sign in to follow this  
johnnyBravo

Best way of solving a polynomial of the fourth degree?

This topic is 4052 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to solve a polynomial of the fourth degree, (I am actually trying to find the time of collision of two spheres with the variables position, velocity*time, acceleration*time^2 and radius). that is: kt^4 + nt^3 + pt^2 + qt + s = 0 Anyway I have found this Ferrari method, I just wanted to know if there is a better way of solving it than that? (btw I wouldn't know how to work out one of the roots, so methods requiring that I cannot do). thanks.

Share this post


Link to post
Share on other sites
Advertisement
Considering that a quartic is the highest degree polynomial with a closed-form solution, I'm not surprised that those methods are really nasty. You might have some success by finding the local extrema and then using Newton-Raphson in either direction to get the roots.

Share this post


Link to post
Share on other sites
Quote:
Original post by Zipster
Considering that a quartic is the highest degree polynomial with a closed-form solution . . .


That is not true! You can go up to 4th degree, but as stated the solution will be very complicated. I'm sorry but I can't give you the solutions without my math book.

Share this post


Link to post
Share on other sites
Quote:
Original post by dragongame
Quote:
Original post by Zipster
Considering that a quartic is the highest degree polynomial with a closed-form solution . . .


That is not true! You can go up to 4th degree, but as stated the solution will be very complicated. I'm sorry but I can't give you the solutions without my math book.

quartic = fourth order

Share this post


Link to post
Share on other sites
This should do the trick:


#include <math.h>
#include <complex>

const double PI = 3.14159265358979323846;

//----------------------------------------------------------------------------
bool solveQuadratic(double &a, double &b, double &c, double &root)
{
if(a == 0.0 || abs(a/b) < 1.0e-6)
{
if(abs(b) < 1.0e-4)
return false;
else
{
root = -c/b;
return true;
}
}

double discriminant = b * b - 4.0 * a * c;
if(discriminant >= 0.0)
{
discriminant = sqrt(discriminant);
root = (b + discriminant) * -0.5 / a;
return true;
}

return false;
}
//----------------------------------------------------------------------------
bool solveCubic(double &a, double &b, double &c, double &d, double &root)
{
if(a == 0.0 || abs(a/b) < 1.0e-6)
return solveQuadratic(b, c, d, root);

double B = b/a, C = c/a, D = d/a;

double Q = (B*B - C*3.0)/9.0, QQQ = Q*Q*Q;
double R = (2.0*B*B*B - 9.0*B*C + 27.0*D)/54.0, RR = R*R;

// 3 real roots
if(RR<QQQ)
{
/* This sqrt and division is safe, since RR >= 0, so QQQ > RR, */
/* so QQQ > 0. The acos is also safe, since RR/QQQ < 1, and */
/* thus R/sqrt(QQQ) < 1. */
double theta = acos(R/sqrt(QQQ));
/* This sqrt is safe, since QQQ >= 0, and thus Q >= 0 */
double r1, r2, r3;
r1 = r2 = r3 = -2.0*sqrt(Q);
r1 *= cos(theta/3.0);
r2 *= cos((theta+2*PI)/3.0);
r3 *= cos((theta-2*PI)/3.0);

r1 -= B/3.0;
r2 -= B/3.0;
r3 -= B/3.0;

root = 1000000.0;

if(r1 >= 0.0) root = r1;
if(r2 >= 0.0 && r2 < root) root = r2;
if(r3 >= 0.0 && r3 < root) root = r3;

return true;
}
// 1 real root
else
{
double A2 = -pow(fabs(R)+sqrt(RR-QQQ),1.0/3.0);
if (A2!=0.0) {
if (R<0.0) A2 = -A2;
root = A2 + Q/A2;
}
root -= B/3.0;
return true;
}
}
//----------------------------------------------------------------------------
bool solveQuartic(double &a, double &b, double &c, double &d, double &e, double &root)
{
// When a or (a and b) are magnitudes of order smaller than C,D,E
// just ignore them entirely.
if(a == 0.0 || abs(a/b) < 1.0e-6 || abs(a/c) < 1.0e-6)
return solveCubic(b, c, d, e, root);

// Uses Ferrari's Method
double aa = a*a, aaa=aa*a, bb=b*b, bbb=bb*b;
double alpha = -3.0*bb/(8.0*aa) + c/a, alpha2 = alpha * alpha;
double beta = bbb/(8.0*aaa) + b*c/(-2.0*aa) + d/a;
double gamma = -3.0*bbb*b/(256.0*aaa*a) + c*bb/(16.0*aaa) + b*d/(-4.0*aa) + e/a;

if(beta == 0.0)
{
root = b/(-4.0*a) + sqrt(0.5 * (-alpha + sqrt(alpha2 + 4.0*gamma)));
return true;
}
else
{
std::complex<double> P = -alpha2/12.0 - gamma;
std::complex<double> Q = -alpha2*alpha/108.0 + alpha*gamma/3.0 - beta*beta/8.0;
std::complex<double> R = Q*0.5 + sqrt(Q*Q*0.25 + P*P*P/27.0);
std::complex<double> U = pow(R, 1.0/3.0);
std::complex<double> y = -5.0*alpha/6.0 - U;
if(U != 0.0) y += P/(3.0*U);
std::complex<double> W = sqrt(alpha + y + y);

std::complex<double> aRoot;
bool foundRealRoot = false;

double firstPart = b/(-4.0*a);
std::complex<double> secondPart = -3.0*alpha - 2.0*y;
std::complex<double> thirdPart = 2.0*beta/W;

aRoot = firstPart + 0.5 * (-W - sqrt(secondPart + thirdPart));
if(abs(aRoot.imag()) < 1.0e-10 && aRoot.real() >= 0.0)
{
root = aRoot.real();
foundRealRoot = true;
}

aRoot = firstPart + 0.5 * (-W + sqrt(secondPart + thirdPart));
if(abs(aRoot.imag()) < 1.0e-10 && aRoot.real() >= 0.0 &&
(!foundRealRoot || aRoot.real() < root))
{
root = aRoot.real();
foundRealRoot = true;
}

aRoot = firstPart + 0.5 * (W - sqrt(secondPart - thirdPart));
if(abs(aRoot.imag()) < 1.0e-10 && aRoot.real() >= 0.0 &&
(!foundRealRoot || aRoot.real() < root))
{
root = aRoot.real();
foundRealRoot = true;
}

aRoot = firstPart + 0.5 * (W + sqrt(secondPart - thirdPart));
if(abs(aRoot.imag()) < 1.0e-10 && aRoot.real() >= 0.0 &&
(!foundRealRoot || aRoot.real() < root))
{
root = aRoot.real();
foundRealRoot = true;
}

return foundRealRoot;
}
}
//----------------------------------------------------------------------------


That was such a pain to figure out, because I'm not particularly a math wiz. But it works. :-)

[EDIT] I realized that I should probably note that in any case where there are multiple roots, it tries to return the root that is closest to zero but above it. So if I had, say, root={-1, 1, 0.2}, then the algorithm returns 0.2. Assuming that ABCDE are terms that describe explicitly the movement of the two spheres over a given frame, then 0.2 is the most useful root, since it says that the spheres were able to use 20% of their motions.

Bonus note:

I'm not as sure how the math works out, but for my circle-circle test (2D collision test), I was able to reduce it to second order just by fixing one of the circles at the origin, and making its radius zero. The other circle had all of the motion and radius, so it ended up being a whenDoesCircleTouchOrigin test, which was much faster than 4th order.

Share this post


Link to post
Share on other sites
Quote:
Original post by dragongame
Quote:
Original post by Zipster
Considering that a quartic is the highest degree polynomial with a closed-form solution . . .


That is not true! You can go up to 4th degree, but as stated the solution will be very complicated. I'm sorry but I can't give you the solutions without my math book.


Zipster is correct. Abel's impossibility theorem states that there is no general solution for polynomials equal or above fifth degree.

I think we're all talking about the same thing.

Share this post


Link to post
Share on other sites
haha Silver. You've spoken like a true mathmatician. You are quite correct, but I suspect that Johnny is looking at this from a more pragmatic view, "gimme working code". :-) Just out of curiosity, are there algorithms like Ferrari's method that will solve ANY 5th degree polynomial?

Share this post


Link to post
Share on other sites
Quote:
Original post by pTymN

[EDIT] I realized that I should probably note that in any case where there are multiple roots, it tries to return the root that is closest to zero but above it. So if I had, say, root={-1, 1, 0.2}, then the algorithm returns 0.2. Assuming that ABCDE are terms that describe explicitly the movement of the two spheres over a given frame, then 0.2 is the most useful root, since it says that the spheres were able to use 20% of their motions.


I'm a little confused about that, because with my previous collision detection using position and velocity (second degree), although there were only two, I always took the - root (like from the +- sqrt()). I assumed when doing to the fourth degree, it would be only one of the roots that I'd use aswell.


Quote:
Original post by pTymN
Bonus note:

I'm not as sure how the math works out, but for my circle-circle test (2D collision test), I was able to reduce it to second order just by fixing one of the circles at the origin, and making its radius zero. The other circle had all of the motion and radius, so it ended up being a whenDoesCircleTouchOrigin test, which was much faster than 4th order.


Wait did you put acceleration in there too? Because I'm already doing what you said about the radius etc, and I can't see how you would reduce it to just the second degree.

thanks for the source!


btw I've managed to reduce the equation from:
ax^4 + bx^3 + cx^2 + dx + e = 0

to:
f = b/4a
x = u - f
g = -6f^2 + (c/a)
h = 8f^3 - 2(c/a)f + (d/a)
i = -3f^4 + (c/a)f^2 - (d/a)f + e/a

u^4 + gu^2 + hu + i = 0 //depressed quartic equation


From the wiki link i posted, but I lose them not long after that.


Share this post


Link to post
Share on other sites
Busy friday at the office, heh. :-)

Because my collision detection layer is not aware of "why" a particular corner or edge for an object moved, it doesn't know how linear or non-linear the motion of a particular boundary part was computed. The collision detection assumes that the object's motion is completely linear over the span of a frame, and that assumption has worked out very well after I changed the structure of the game object's code. I moved all accelerations into a method that is called after the collision detection phase, so when the collision detection tries scaling the object's motion down and retrying, it isn't returning linear time of collision for highly non-linear motions. The amount of error is huge, but the simulation looks great.

Share this post


Link to post
Share on other sites
Quote:
I'm a little confused about that, because with my previous collision detection using position and velocity (second degree), although there were only two, I always took the - root (like from the +- sqrt()). I assumed when doing to the fourth degree, it would be only one of the roots that I'd use aswell.


You know, I was only implementing this stuff to get something else working, but unlike quadratic, it does seem that the useful root isn't always the first or third one for quartic. When I got to the cubic code, I didn't even bother looking at that. If you do a more thorough investigation and find a useful pattern, I'd appreciate if you'd share your learnings. :-)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!