/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package geogebra.kernel; import geogebra.kernel.arithmetic.Function; import geogebra.kernel.arithmetic.PolyFunction; import geogebra.kernel.roots.RealRootFunction; import geogebra.main.Application; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; /** * Finds all real roots of a polynomial. * TODO: extend for rational functions * * @author Markus Hohenwarter */ public class AlgoRootsPolynomial extends AlgoIntersect { /** * */ private static final long serialVersionUID = 1L; private static final int ROOTS = 0; private static final int INTERSECT_POLYNOMIALS = 1; private static final int INTERSECT_POLY_LINE = 2; private static final int MULTIPLE_ROOTS = 3; private int mode; GeoFunction f, g; // input (g for intersection of polynomials) private GeoLine line; // input (for intersection of polynomial with line) protected GeoPoint[] rootPoints; // output, inherited from AlgoIntersect //private int rootPointsLength; private String[] labels; private boolean initLabels; protected boolean setLabels; protected EquationSolver eqnSolver; double[] curRoots = new double[30]; // current roots int curRealRoots; Function yValFunction; // used for AlgoExtremumPolynomial, see setRootPoints() private Function diffFunction; // used for intersection of f and g private GeoPoint tempPoint; /** * Computes all roots of f */ public AlgoRootsPolynomial( Construction cons, String[] labels, GeoFunction f) { this(cons, labels, !cons.isSuppressLabelsActive(), f, null, null); } /** * Intersects polynomials f and g. */ AlgoRootsPolynomial(Construction cons, GeoFunction f, GeoFunction g) { this(cons, null, false, f, g, null); } /** * Intersects polynomial f and line l. */ AlgoRootsPolynomial(Construction cons, GeoFunction f, GeoLine l) { this(cons, null, false, f, null, l); } private AlgoRootsPolynomial( Construction cons, String[] labels, boolean setLabels, GeoFunction f, GeoFunction g, GeoLine l) { super(cons); this.f = f; this.g = g; line = l; tempPoint = new GeoPoint(cons); // set mode if (g != null) mode = INTERSECT_POLYNOMIALS; else if (l != null) { mode = INTERSECT_POLY_LINE; } else mode = ROOTS; if (mode != ROOTS) { // for intersection of f and g resp. line diffFunction = new Function(kernel); } this.labels = labels; this.setLabels = setLabels; // should lables be used? eqnSolver = cons.getEquationSolver(); // make sure root points is not null int number = labels == null ? 1 : Math.max(1, labels.length); rootPoints = new GeoPoint[0]; initRootPoints(number); initLabels = true; setInputOutput(); // for AlgoElement compute(); // show at least one root point in algebra view // this is enforced here: if (!rootPoints[0].isDefined()) { rootPoints[0].setCoords(0,0,1); rootPoints[0].update(); rootPoints[0].setUndefined(); rootPoints[0].update(); } } public AlgoRootsPolynomial( GeoFunction f ) { super(f.cons); this.f = f; tempPoint = new GeoPoint(cons); // set mode mode = MULTIPLE_ROOTS; eqnSolver = cons.getEquationSolver(); // make sure root points is not null int number = labels == null ? 1 : Math.max(1, labels.length); rootPoints = new GeoPoint[0]; initRootPoints(number); initLabels = true; setInputOutput(); // for AlgoElement compute(); // show at least one root point in algebra view // this is enforced here: if (!rootPoints[0].isDefined()) { rootPoints[0].setCoords(0,0,1); rootPoints[0].update(); rootPoints[0].setUndefined(); rootPoints[0].update(); } } public AlgoRootsPolynomial( Construction cons, GeoFunction f) { super(cons); this.f = f; tempPoint = new GeoPoint(cons); // set mode mode = ROOTS; eqnSolver = cons.getEquationSolver(); // make sure root points is not null int number = 1; rootPoints = new GeoPoint[0]; initRootPoints(number); initLabels = true; setInputOutput(); // for AlgoElement compute(); } /** * The given labels will be used for the resulting points. */ public void setLabels(String[] labels) { this.labels = labels; setLabels = !cons.isSuppressLabelsActive(); // make sure that there are at least as many // points as labels if (labels != null) initRootPoints(labels.length); update(); } public String getClassName() { return "AlgoRootsPolynomial"; } // for AlgoElement protected void setInputOutput() { switch (mode) { case MULTIPLE_ROOTS: case ROOTS : // roots of f input = new GeoElement[1]; input[0] = f; break; case INTERSECT_POLYNOMIALS : // intersection of f and g input = new GeoElement[2]; input[0] = f; input[1] = g; break; case INTERSECT_POLY_LINE : // intersection of f and line input = new GeoElement[2]; input[0] = f; input[1] = line; break; } output = rootPoints; noUndefinedPointsInAlgebraView(); setDependencies(); } public GeoPoint[] getRootPoints() { return rootPoints; } protected GeoPoint[] getIntersectionPoints() { return rootPoints; } protected GeoPoint[] getLastDefinedIntersectionPoints() { return null; } protected void compute() { switch (mode) { case ROOTS : // roots of f computeRoots(); break; case MULTIPLE_ROOTS: if (f.isDefined()) { Function fun = f.getFunction(); // get polynomial factors anc calc roots calcRootsMultiple(fun, 0); } else { curRealRoots = 0; } break; case INTERSECT_POLYNOMIALS : // intersection of f and g computePolynomialIntersection(); break; case INTERSECT_POLY_LINE : // intersection of f and line computePolyLineIntersection(); break; } setRootPoints(curRoots, curRealRoots); } // roots of f private void computeRoots() { if (f.isDefined()) { Function fun = f.getFunction(); // get polynomial factors anc calc roots calcRoots(fun, 0); } else { curRealRoots = 0; } } // intersection of f and g private void computePolynomialIntersection() { if (f.isDefined() && g.isDefined()) { yValFunction = f.getFunction(); // get difference f - g Function.difference(f.getFunction(), g.getFunction(), diffFunction); calcRoots(diffFunction, 0); // check if the intersection points are really on the functions // due to interval restrictions this might not be the case for (int i = 0; i < curRealRoots; i++) { if ( !Kernel.isEqual(f.evaluate(curRoots[i]),g.evaluate(curRoots[i]), Kernel.MIN_PRECISION)) { removeRoot(i); i--; } } } else { curRealRoots = 0; } } // intersection of f and line private void computePolyLineIntersection() { if (f.isDefined() && line.isDefined()) { yValFunction = f.getFunction(); // check for vertical line a*x + c = 0: intersection at x=-c/a if (Kernel.isZero(line.y)) { double x = -line.z / line.x; curRoots[0] = x; curRealRoots = 1; } // standard case else { // get difference f - line Function.difference(f.getFunction(), line, diffFunction); calcRoots(diffFunction, 0); } // check if the intersection points really are on the line // this is important for segments and rays //Zbynek Konecny 2010-02-12 -- following must be done for both vertical and standard for (int i = 0; i < curRealRoots; i++) { tempPoint.setCoords(curRoots[i], f.evaluate(curRoots[i]), 1.0); if (! line.isIntersectionPointIncident(tempPoint, Kernel.MIN_PRECISION) ){ removeRoot(i); i--; } } //end Zbynek Konecny } else { curRealRoots = 0; } } /** * Calculates the roots of the given function resp. its derivative, * stores them in curRoots and * sets curRealRoots to the number of real roots found. * @param derivDegree degree of derivative to compute roots from */ public final void calcRoots(Function fun, int derivDegree) { RealRootFunction evalFunction = calcRootsMultiple(fun,derivDegree); if (curRealRoots > 1) { // sort roots and eliminate duplicate ones Arrays.sort(curRoots, 0, curRealRoots); // eliminate duplicate roots double maxRoot = curRoots[0]; int maxIndex = 0; for (int i = 1; i < curRealRoots; i++) { if ((curRoots[i] - maxRoot) > Kernel.MIN_PRECISION) { maxRoot = curRoots[i]; maxIndex++; curRoots[maxIndex] = maxRoot; } } curRealRoots = maxIndex + 1; } // for first or second derivative we only // want roots where the signs changed // i.e. we only want extrema and inflection points if (derivDegree > 0) { ensureSignChanged(evalFunction); } } public RealRootFunction calcRootsMultiple(Function fun, int derivDegree) { LinkedList factorList; PolyFunction derivPoly = null;// only needed for derivatives RealRootFunction evalFunction = null; // needed to remove wrong extrema and inflection points // get polynomial factors for this function if (derivDegree > 0) { // try to get the factors of the symbolic derivative factorList = fun.getSymbolicPolynomialDerivativeFactors(derivDegree, true); // if this didn't work take the derivative of the numeric // expansion of this function if (factorList == null) { derivPoly = fun.getNumericPolynomialDerivative(derivDegree); evalFunction = derivPoly; } else { evalFunction = fun.getDerivative(derivDegree); } } else { // standard case factorList = fun.getPolynomialFactors(false); } double[] roots; int realRoots; curRealRoots = 0; // reset curRoots index // we got a list of polynomial factors if (factorList != null) { // compute the roots of every single factor Iterator it = factorList.iterator(); while (it.hasNext()) { PolyFunction polyFun = (PolyFunction) it.next(); // update the current coefficients of polyFun // (this is needed for SymbolicPolyFunction objects) if (!polyFun.updateCoeffValues()) { // current coefficients are not defined curRealRoots = 0; return null; } // now let's compute the roots of this factor // compute all roots of polynomial polyFun roots = polyFun.getCoeffsCopy(); realRoots = eqnSolver.polynomialRoots(roots,true); addToCurrentRoots(roots, realRoots); } } // we've got one factor, i.e. derivPoly else if (derivPoly != null) { // compute all roots of derivPoly roots = derivPoly.getCoeffsCopy(); realRoots = eqnSolver.polynomialRoots(roots,false); addToCurrentRoots(roots, realRoots); } else return null; if(curRealRoots > 1) Arrays.sort(curRoots, 0, curRealRoots); return evalFunction; } // remove roots where the sign of the function's values did not change private void ensureSignChanged(RealRootFunction f) { double left, right, leftEval, rightEval; boolean signUnChanged; for (int i = 0; i < curRealRoots; i++) { left = curRoots[i] - DELTA; right = curRoots[i] + DELTA; // ensure we get a non-zero y value to the left int count = 0; while (Math.abs(leftEval = f.evaluate(left)) < DELTA && count++ < 100) left = left - DELTA; // ensure we get a non-zero y value to the right count = 0; while (Math.abs(rightEval = f.evaluate(right)) < DELTA && count++ < 100) right = right + DELTA; //Application.debug("leftEval: " + leftEval + ", left: " + left); //Application.debug("rightEval: " + rightEval + ", right: " + right); // check if the second derivative changed its sign here signUnChanged = leftEval * rightEval > 0; if (signUnChanged) { // remove root[i] removeRoot(i); i--; } } } private static final double DELTA = Kernel.MIN_PRECISION * 10; // add first number of doubles in roots to current roots private void addToCurrentRoots(double[] roots, int number) { int length = curRealRoots + number; if (length >= curRoots.length) { // ensure space double[] temp = new double[2 * length]; for (int i = 0; i < curRealRoots; i++) { temp[i] = curRoots[i]; } curRoots = temp; } // insert new roots for (int i = 0; i < number; i++) { curRoots[curRealRoots + i] = roots[i]; } curRealRoots += number; } final private void removeRoot(int pos) { for (int i = pos+1; i < curRealRoots; i++) { curRoots[i-1] = curRoots[i]; } curRealRoots--; } // roots array and number of roots protected final void setRootPoints(double[] roots, int number) { initRootPoints(number); // now set the new values of the roots for (int i = 0; i < number; i++) { // Application.debug("root[" + i + "] = " + roots[i]); if (yValFunction == null) { // check if defined //if (Double.isNaN(f.evaluate(roots[i]))) // rootPoints[i].setUndefined(); //else rootPoints[i].setCoords(roots[i], 0.0, 1.0); // root point } else { // extremum or turnal point rootPoints[i].setCoords( roots[i], yValFunction.evaluate(roots[i]), 1.0); // Application.debug(" " + rootPoints[i]); } } // all other roots are undefined for (int i = number; i < rootPoints.length; i++) { rootPoints[i].setUndefined(); } if (setLabels) updateLabels(number); } // number is the number of current roots protected void updateLabels(int number) { if (initLabels) { GeoElement.setLabels(labels, rootPoints); initLabels = false; } else { for (int i = 0; i < number; i++) { // check labeling if (!rootPoints[i].isLabelSet()) { // use user specified label if we have one String newLabel = (labels != null && i < labels.length) ? labels[i] : null; rootPoints[i].setLabel(newLabel); } } } // all other roots are undefined for (int i = number; i < rootPoints.length; i++) { rootPoints[i].setUndefined(); } } /** * Removes only one single output element if possible. * If this is not possible the whole algorithm is removed. */ void remove(GeoElement output) { // only single undefined points may be removed for (int i = 0; i < rootPoints.length; i++) { if (rootPoints[i] == output && !rootPoints[i].isDefined()) { removeRootPoint(i); return; } } // if we get here removing output was not possible // so we remove the whole algorithm super.remove(); } protected void initRootPoints(int number) { // make sure that there are enough points if (rootPoints.length < number) { GeoPoint[] temp = new GeoPoint[number]; for (int i = 0; i < rootPoints.length; i++) { temp[i] = rootPoints[i]; temp[i].setCoords(0, 0, 1); // init as defined } for (int i = rootPoints.length; i < temp.length; i++) { temp[i] = new GeoPoint(cons); temp[i].setCoords(0, 0, 1); // init as defined temp[i].setParentAlgorithm(this); } rootPoints = temp; output = rootPoints; } } private void removeRootPoint(int pos) { rootPoints[pos].doRemove(); // build new rootPoints array without the removed point GeoPoint[] temp = new GeoPoint[rootPoints.length - 1]; int i; for (i=0; i < pos; i++) temp[i] = rootPoints[i]; for (i=pos+1; i < rootPoints.length; i++) temp[i-1] = rootPoints[i]; rootPoints = temp; } public String toString() { // Michael Borcherds 2008-03-30 // simplified to allow better Chinese translation return app.getPlain("RootOfA",f.getLabel()); } }