Best way of solving a polynomial of the fourth degree?

Started by
16 comments, last by pTymN 16 years, 10 months ago
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.
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.
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.
“Always programm as if the person who will be maintaining your program is a violent psychopath that knows where you live”
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
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.

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.
My passive agressiveness can be so devastating. - Alanis Morissette, "Everything"
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?

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.


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.

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. :-)

This topic is closed to new replies.

Advertisement