package org.matheclipse.core.reflection.system;

import static org.matheclipse.core.expression.F.List;
import static org.matheclipse.core.expression.F.evalExpandAll;

import org.matheclipse.core.convert.Expr2Object;
import org.matheclipse.core.convert.ExprVariables;
import org.matheclipse.core.eval.interfaces.AbstractFunctionEvaluator;
import org.matheclipse.core.expression.F;
import org.matheclipse.core.interfaces.IAST;
import org.matheclipse.core.interfaces.IExpr;
import org.matheclipse.core.interfaces.ISymbol;

/**
 * Determine the numerical roots of a univariate polynomial
 * 
 * See Wikipedia entries for: Quadratic equation
 * , Cubic
 * function and Quartic function
 * 
 * @see Roots
 */
public class NRoots extends AbstractFunctionEvaluator {

	public NRoots() {
	}

	@Override
	public IExpr evaluate(final IAST ast) {
		if (ast.size() != 2) {
			return null;
		}
		IExpr temp = roots(ast);
		if (temp == null || !temp.isList()) {
			return null;
		}
		IAST list = (IAST) temp;
		IAST result = F.List();
		for (int i = 1; i < list.size(); i++) {
			result.add(F.evaln(list.get(i)));
		}
		return result;
	}

	protected static IAST roots(final IAST ast) {
		ExprVariables eVar = new ExprVariables(ast.arg1());
		if (!eVar.isSize(1)) {
			// factor only possible for univariate polynomials
			return null;
		}
		IExpr expr = evalExpandAll(ast.arg1());
		IAST variables = eVar.getVarList();
		ISymbol sym = (ISymbol) variables.arg1();
		double[] poly = Expr2Object.toPolynomial(expr, sym);
		IAST result = null;
		if (poly != null) {
			if (poly[3] == 0.0) {
				return quadratic(poly[2], poly[1], poly[0]);
			}
			result = cubic(poly[3], poly[2], poly[1], poly[0]);
			if (result != null) {
				return result;
			}
		}
		IExpr denom = F.C1;
		if (expr.isAST()) {
			expr = Together.together((IAST) expr);

			// split expr into numerator and denominator
			denom = F.eval(F.Denominator(expr));
			if (!denom.isOne()) {
				// search roots for the numerator expression
				expr = F.eval(F.Numerator(expr));
			}
		}
		return rootsOfVariable(expr, denom);
	}

	protected static IAST rootsOfVariable(final IExpr expr, final IExpr denom) {
		IAST result = List();
		IAST resultList = RootIntervals.croots(expr, true);
		if (resultList != null) {
			if (resultList.size() > 0) {
				result.addAll(resultList);
			}
			return result;
		}
		return null;
	}

	private static IAST quadratic(double a, double b, double c) {
		IAST result = F.List();
		double discriminant = (b * b - (4 * a * c));
		if (F.isZero(discriminant)) {
			double bothEqual = ((-b / (2.0 * a)));
			result.add(F.num(bothEqual));
			result.add(F.num(bothEqual));
		} else if (discriminant < 0.0) {
			// two complex roots
			double imaginaryPart = Math.sqrt(-discriminant) / (2 * a);
			double realPart = (-b / (2.0 * a));
			result.add(F.complex(realPart, imaginaryPart));
			result.add(F.complex(realPart, -imaginaryPart));
		} else {
			// two real roots
			double real1 = ((-b + Math.sqrt(discriminant)) / (2.0 * a));
			double real2 = ((-b - Math.sqrt(discriminant)) / (2.0 * a));
			result.add(F.num(real1));
			result.add(F.num(real2));
		}
		return result;
	}

	/**
	 * See http
	 * ://stackoverflow.com/questions/13328676/c-solving-cubic-equations
	 * 
	 * @param a
	 * @param b
	 * @param c
	 * @param d
	 */
	private static IAST cubic(double a, double b, double c, double d) {
		if (F.isZero(a)) {
			return null;
		}
		if (F.isZero(d)) {
			return null;
		}
		IAST result = F.List();
		b /= a;
		c /= a;
		d /= a;

		double q = (3.0 * c - (b * b)) / 9.0;
		double r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b));
		r /= 54.0;
		double discriminant = q * q * q + r * r;

		double term1 = (b / 3.0);
		if (discriminant > 0) {
			// one root real, two are complex
			double s = r + Math.sqrt(discriminant);
			s = ((s < 0) ? -Math.pow(-s, (1.0 / 3.0)) : Math.pow(s, (1.0 / 3.0)));
			double t = r - Math.sqrt(discriminant);
			t = ((t < 0) ? -Math.pow(-t, (1.0 / 3.0)) : Math.pow(t, (1.0 / 3.0)));
			result.add(F.num(-term1 + s + t));
			term1 += (s + t) / 2.0;
			double realPart = -term1;
			term1 = Math.sqrt(3.0) * (-t + s) / 2;
			result.add(F.complex(realPart, term1));
			result.add(F.complex(realPart, -term1));
			return result;
		}

		// The remaining options are all real
		double r13;
		if (F.isZero(discriminant)) {
			// All roots real, at least two are equal.
			r13 = ((r < 0) ? -Math.pow(-r, (1.0 / 3.0)) : Math.pow(r, (1.0 / 3.0)));
			result.add(F.num(-term1 + 2.0 * r13));
			result.add(F.num(-(r13 + term1)));
			result.add(F.num(-(r13 + term1)));
			return result;
		}

		// Only option left is that all roots are real and unequal (to get here,
		// q < 0)
		q = -q;
		double dum1 = q * q * q;
		dum1 = Math.acos(r / Math.sqrt(dum1));
		r13 = 2.0 * Math.sqrt(q);
		result.add(F.num(-term1 + r13 * Math.cos(dum1 / 3.0)));
		result.add(F.num(-term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0)));
		result.add(F.num(-term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0)));
		return result;
	}
}
 

Ads help maintain this website.