/* 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 org.geogebra.common.kernel.algos; import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; import org.apache.commons.math3.analysis.solvers.BrentSolver; import org.apache.commons.math3.analysis.solvers.NewtonSolver; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.roots.RealRootUtil; /** * Finds one real root of a function with newtons method. The first derivative * of this function must exist. */ public class AlgoRootNewton extends AlgoIntersectAbstract { /** max iterations for newton method */ public static final int MAX_ITERATIONS = 100; private GeoFunction f; // input, g for intersection of functions private NumberValue start; // start value for root of f private GeoPoint rootPoint; // output private GeoElement startGeo; private NewtonSolver rootFinderNewton; private BrentSolver rootFinderBrent; /** * @param cons * construction * @param label * output label * @param f * function * @param start * start value */ public AlgoRootNewton(Construction cons, String label, GeoFunction f, GeoNumberValue start) { super(cons); this.f = f; this.start = start; startGeo = start.toGeoElement(); // output rootPoint = new GeoPoint(cons); setInputOutput(); // for AlgoElement compute(); rootPoint.setLabel(label); } /** * Constructor for extending algos * * @param cons * construction */ AlgoRootNewton(Construction cons) { super(cons); } @Override public Commands getClassName() { return Commands.Root; } // for AlgoElement @Override protected void setInputOutput() { input = new GeoElement[2]; input[0] = f; input[1] = startGeo; super.setOutputLength(1); super.setOutput(0, rootPoint); setDependencies(); } /** * @return root */ public GeoPoint getRootPoint() { return rootPoint; } @Override public void compute() { if (!(f.isDefined() && startGeo.isDefined())) { rootPoint.setUndefined(); } else { double startValue = start.getDouble(); Function fun = f.getFunction(startValue); // calculate root rootPoint.setCoords(calcRoot(fun, startValue), 0.0, 1.0); } } /** * @param fun * function * @param startX * start x-value * @return root */ public final double calcRoot(Function fun, double startX) { double root = Double.NaN; if (rootFinderBrent == null) { rootFinderBrent = new BrentSolver(Kernel.STANDARD_PRECISION); } // try Brent method with borders close to start value try { // arbitrary (used to depend on screen width) double step = 1; root = rootFinderBrent.solve(MAX_ITERATIONS, fun, startX - step, startX + step, startX); if (checkRoot(fun, root)) { // System.out.println("1. Brent worked: " + root); return root; } } catch (RuntimeException e) { root = Double.NaN; } // try Brent method on valid interval around start double[] borders = getDomain(fun, startX); try { root = rootFinderBrent.solve(MAX_ITERATIONS, fun, borders[0], borders[1], startX); if (checkRoot(fun, root)) { // System.out.println("2. Brent worked: " + root); return root; } } catch (RuntimeException e) { root = Double.NaN; } // try Newton's method DifferentiableUnivariateFunction derivFun = fun; // check if fun(start) is defined double eval = fun.value(startX); double start1 = startX; if (Double.isNaN(eval) || Double.isInfinite(eval)) { // shift left border slightly right borders[0] = 0.9 * borders[0] + 0.1 * borders[1]; start1 = (borders[0] + borders[1]) / 2; } if (rootFinderNewton == null) { rootFinderNewton = new NewtonSolver(); } try { root = rootFinderNewton.solve(MAX_ITERATIONS, derivFun, borders[0], borders[1], start1); if (checkRoot(fun, root)) { // System.out.println("Newton worked: " + root); return root; } } catch (RuntimeException e) { // } // neither Brent nor Newton worked return Double.NaN; } private static boolean checkRoot(Function fun, double root) { // check what we got return !Double.isNaN(root) && (Math.abs(fun.value(root)) < Kernel.MIN_PRECISION); } /** * Tries to find a valid domain for the given function around it's starting * value. */ private static double[] getDomain(Function fun, double start) { // arbitrary interval (used to depend on screen width) return RealRootUtil.getDefinedInterval(fun, start - 0.5, start + 0.5); } @Override public String toString(StringTemplate tpl) { // Michael Borcherds 2008-03-30 // simplified to allow better Chinese translation return getLoc().getPlain("RootOfAWithInitialValueB", f.getLabel(tpl), startGeo.getLabel(tpl)); } }