diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolver.java new file mode 100644 index 000000000..dc5f16f0b --- /dev/null +++ b/src/main/java/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolver.java @@ -0,0 +1,397 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math.analysis.solvers; + + +import org.apache.commons.math.analysis.UnivariateRealFunction; +import org.apache.commons.math.exception.MathInternalError; +import org.apache.commons.math.exception.NoBracketingException; +import org.apache.commons.math.exception.NumberIsTooSmallException; +import org.apache.commons.math.util.FastMath; +import org.apache.commons.math.util.MathUtils; + +/** + * This class implements a modification of the Brent algorithm. + *
+ * The changes with respect to the original Brent algorithm are: + *
+ * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q + * is built such that for all considered points (xi, yi), + * Q(yi) = xi. + *
+ * @param targetY target value for y + * @param x reference points abscissas for interpolation, + * note that this array is modified during computation + * @param y reference points ordinates for interpolation + * @param start start index of the points to consider (inclusive) + * @param end end index of the points to consider (exclusive) + * @return guessed root (will be a NaN if two points share the same y) + */ + private double guessX(final double targetY, final double[] x, final double[] y, + final int start, final int end) { + + // compute Q Newton coefficients by divided differences + for (int i = start; i < end - 1; ++i) { + final int delta = i + 1 - start; + for (int j = end - 1; j > i; --j) { + x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]); + } + } + + // evaluate Q(targetY) + double x0 = 0; + for (int j = end - 1; j >= start; --j) { + x0 = x[j] + x0 * (targetY - y[j]); + } + + return x0; + + } + + /** {@inheritDoc} */ + public double solve(int maxEval, UnivariateRealFunction f, double min, + double max, AllowedSolutions allowedSolutions) { + this.allowed = allowedSolutions; + return super.solve(maxEval, f, min, max); + } + + /** {@inheritDoc} */ + public double solve(int maxEval, UnivariateRealFunction f, double min, + double max, double startValue, + AllowedSolutions allowedSolutions) { + this.allowed = allowedSolutions; + return super.solve(maxEval, f, min, max, startValue); + } + +} diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml index 61bae9a52..aa63d6979 100644 --- a/src/site/xdoc/changes.xml +++ b/src/site/xdoc/changes.xml @@ -52,6 +52,10 @@ TheForce bracketing, by refining a base solution found by a non-bracketing solver: @@ -221,9 +228,9 @@ double c = UnivariateRealSolverUtils.forceSide(100, function, baseRoot, 1.0, 5.0, AllowedSolutions.LEFT_SIDE);
- The BrentSolve
uses the Brent-Dekker algorithm which is
- fast and robust. This algorithm is recommended for most users. If there
- are multiple roots in the interval, or there is a large domain of indeterminacy, the
+ The BrentSolver
uses the Brent-Dekker algorithm which is
+ fast and robust. If there are multiple roots in the interval,
+ or there is a large domain of indeterminacy, the
algorithm will converge to a random root in the interval without
indication that there are problems. Interestingly, the examined text
book implementations all disagree in details of the convergence
@@ -232,6 +239,15 @@ double c = UnivariateRealSolverUtils.forceSide(100, function,
get exactly the same root values as for other implementations of this
algorithm.
+ The BracketingNthOrderBrentSolver
uses an extension of the
+ Brent-Dekker algorithm which uses inverse nth order polynomial
+ interpolation instead of inverse quadratic interpolation, and which allows
+ selection of the side of the convergence interval for result bracketing.
+ This is now the recommended algorithm for most users since it has the
+ largest order, doesn't require derivatives, has guaranteed convergence
+ and allows result bracket selection.
+
The SecantSolver
uses a straightforward secant
algorithm which does not bracket the search and therefore does not
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolverTest.java
new file mode 100644
index 000000000..1488a289b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolverTest.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.NumberIsTooSmallException;
+import org.apache.commons.math.exception.TooManyEvaluationsException;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test case for {@link BracketingNthOrderBrentSolver bracketing nth order Brent} solver.
+ *
+ * @version $Id$
+ */
+public final class BracketingNthOrderBrentSolverTest extends BaseSecantSolverAbstractTest {
+ /** {@inheritDoc} */
+ protected UnivariateRealSolver getSolver() {
+ return new BracketingNthOrderBrentSolver();
+ }
+
+ /** {@inheritDoc} */
+ protected int[] getQuinticEvalCounts() {
+ return new int[] {1, 3, 8, 1, 9, 4, 8, 1, 12, 1, 14};
+ }
+
+ @Test(expected=NumberIsTooSmallException.class)
+ public void testInsufficientOrder1() {
+ new BracketingNthOrderBrentSolver(1.0e-10, 1);
+ }
+
+ @Test(expected=NumberIsTooSmallException.class)
+ public void testInsufficientOrder2() {
+ new BracketingNthOrderBrentSolver(1.0e-10, 1.0e-10, 1);
+ }
+
+ @Test(expected=NumberIsTooSmallException.class)
+ public void testInsufficientOrder3() {
+ new BracketingNthOrderBrentSolver(1.0e-10, 1.0e-10, 1.0e-10, 1);
+ }
+
+ @Test
+ public void testConstructorsOK() {
+ Assert.assertEquals(2, new BracketingNthOrderBrentSolver(1.0e-10, 2).getMaximalOrder());
+ Assert.assertEquals(2, new BracketingNthOrderBrentSolver(1.0e-10, 1.0e-10, 2).getMaximalOrder());
+ Assert.assertEquals(2, new BracketingNthOrderBrentSolver(1.0e-10, 1.0e-10, 1.0e-10, 2).getMaximalOrder());
+ }
+
+ @Test
+ public void testConvergenceOnFunctionAccuracy() {
+ BracketingNthOrderBrentSolver solver =
+ new BracketingNthOrderBrentSolver(1.0e-12, 1.0e-10, 0.001, 3);
+ QuinticFunction f = new QuinticFunction();
+ double result = solver.solve(20, f, 0.2, 0.9, 0.4, AllowedSolutions.BELOW_SIDE);
+ Assert.assertEquals(0, f.value(result), solver.getFunctionValueAccuracy());
+ Assert.assertTrue(f.value(result) <= 0);
+ Assert.assertTrue(result - 0.5 > solver.getAbsoluteAccuracy());
+ result = solver.solve(20, f, -0.9, -0.2, -0.4, AllowedSolutions.ABOVE_SIDE);
+ Assert.assertEquals(0, f.value(result), solver.getFunctionValueAccuracy());
+ Assert.assertTrue(f.value(result) >= 0);
+ Assert.assertTrue(result + 0.5 < -solver.getAbsoluteAccuracy());
+ }
+
+ @Test
+ public void testFasterThanNewton() {
+ // the following test functions come from Beny Neta's paper:
+ // "Several New Methods for solving Equations"
+ // intern J. Computer Math Vol 23 pp 265-282
+ // available here: http://www.math.nps.navy.mil/~bneta/SeveralNewMethods.PDF
+ // the reference roots have been computed by the Dfp solver to more than
+ // 80 digits and checked with emacs (only the first 20 digits are reproduced here)
+ compare(new TestFunction(0.0, -2, 2) {
+ public double value(double x) { return FastMath.sin(x) - 0.5 * x; }
+ public double derivative(double x) { return FastMath.cos(x) - 0.5; }
+ });
+ compare(new TestFunction(6.3087771299726890947, -5, 10) {
+ public double value(double x) { return FastMath.pow(x, 5) + x - 10000; }
+ public double derivative(double x) { return 5 * FastMath.pow(x, 4) + 1; }
+ });
+ compare(new TestFunction(9.6335955628326951924, 0.001, 10) {
+ public double value(double x) { return FastMath.sqrt(x) - 1 / x - 3; }
+ public double derivative(double x) { return 0.5 / FastMath.sqrt(x) + 1 / (x * x); }
+ });
+ compare(new TestFunction(2.8424389537844470678, -5, 5) {
+ public double value(double x) { return FastMath.exp(x) + x - 20; }
+ public double derivative(double x) { return FastMath.exp(x) + 1; }
+ });
+ compare(new TestFunction(8.3094326942315717953, 0.001, 10) {
+ public double value(double x) { return FastMath.log(x) + FastMath.sqrt(x) - 5; }
+ public double derivative(double x) { return 1 / x + 0.5 / FastMath.sqrt(x); }
+ });
+ compare(new TestFunction(1.4655712318767680266, -0.5, 1.5) {
+ public double value(double x) { return (x - 1) * x * x - 1; }
+ public double derivative(double x) { return (3 * x - 2) * x; }
+ });
+
+ }
+
+ private void compare(TestFunction f) {
+ compare(f, f.getRoot(), f.getMin(), f.getMax());
+ }
+
+ private void compare(DifferentiableUnivariateRealFunction f,
+ double root, double min, double max) {
+ NewtonSolver newton = new NewtonSolver(1.0e-12);
+ BracketingNthOrderBrentSolver bracketing =
+ new BracketingNthOrderBrentSolver(1.0e-12, 1.0e-12, 1.0e-18, 5);
+ double resultN;
+ try {
+ resultN = newton.solve(100, f, min, max);
+ } catch (TooManyEvaluationsException tmee) {
+ resultN = Double.NaN;
+ }
+ double resultB;
+ try {
+ resultB = bracketing.solve(100, f, min, max);
+ } catch (TooManyEvaluationsException tmee) {
+ resultB = Double.NaN;
+ }
+ Assert.assertEquals(root, resultN, newton.getAbsoluteAccuracy());
+ Assert.assertEquals(root, resultB, bracketing.getAbsoluteAccuracy());
+ Assert.assertTrue(bracketing.getEvaluations() < newton.getEvaluations());
+ }
+
+ private static abstract class TestFunction implements DifferentiableUnivariateRealFunction {
+
+ private final double root;
+ private final double min;
+ private final double max;
+
+ protected TestFunction(final double root, final double min, final double max) {
+ this.root = root;
+ this.min = min;
+ this.max = max;
+ }
+
+ public double getRoot() {
+ return root;
+ }
+
+ public double getMin() {
+ return min;
+ }
+
+ public double getMax() {
+ return max;
+ }
+
+ public abstract double value(double x);
+
+ public abstract double derivative(double x);
+
+ public UnivariateRealFunction derivative() {
+ return new UnivariateRealFunction() {
+ public double value(double x) {
+ return derivative(x);
+ }
+ };
+ }
+
+ }
+
+}