Added a Brent-like solver that has higher (user specified) order and does

bracket selection on the result: BracketingNthOrderBrentSolver.

JIRA: MATH-635

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1152276 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Luc Maisonobe 2011-07-29 15:44:21 +00:00
parent 21d9fa79fb
commit c68662e257
4 changed files with 602 additions and 4 deletions

View File

@ -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 <a
* href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
* <p>
* The changes with respect to the original Brent algorithm are:
* <ul>
* <li>the returned value is chosen in the current interval according
* to user specified {@link AllowedSolutions},</li>
* <li>the maximal order for the invert polynomial root search is
* user-specified instead of being invert quadratic only</li>
* </ul>
* </p>
* The given interval must bracket the root.
*
* @version $Id$
*/
public class BracketingNthOrderBrentSolver
extends AbstractUnivariateRealSolver
implements BracketedUnivariateRealSolver<UnivariateRealFunction> {
/** Default absolute accuracy. */
private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
/** Default maximal order. */
private static final int DEFAULT_MAXIMAL_ORDER = 5;
/** Maximal aging triggering an attempt to balance the bracketing interval. */
private static final int MAXIMAL_AGING = 2;
/** Reduction factor for attempts to balance the bracketing interval. */
private static final double REDUCTION_FACTOR = 1.0 / 16.0;
/** Maximal order. */
private final int maximalOrder;
/** The kinds of solutions that the algorithm may accept. */
private AllowedSolutions allowed;
/**
* Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively)
*/
public BracketingNthOrderBrentSolver() {
this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER);
}
/**
* Construct a solver.
*
* @param absoluteAccuracy Absolute accuracy.
* @param maximalOrder maximal order.
* @exception NumberIsTooSmallException if maximal order is lower than 2
*/
public BracketingNthOrderBrentSolver(final double absoluteAccuracy,
final int maximalOrder)
throws NumberIsTooSmallException {
super(absoluteAccuracy);
if (maximalOrder < 2) {
throw new NumberIsTooSmallException(maximalOrder, 2, true);
}
this.maximalOrder = maximalOrder;
this.allowed = AllowedSolutions.ANY_SIDE;
}
/**
* Construct a solver.
*
* @param relativeAccuracy Relative accuracy.
* @param absoluteAccuracy Absolute accuracy.
* @param maximalOrder maximal order.
* @exception NumberIsTooSmallException if maximal order is lower than 2
*/
public BracketingNthOrderBrentSolver(final double relativeAccuracy,
final double absoluteAccuracy,
final int maximalOrder)
throws NumberIsTooSmallException {
super(relativeAccuracy, absoluteAccuracy);
if (maximalOrder < 2) {
throw new NumberIsTooSmallException(maximalOrder, 2, true);
}
this.maximalOrder = maximalOrder;
this.allowed = AllowedSolutions.ANY_SIDE;
}
/**
* Construct a solver.
*
* @param relativeAccuracy Relative accuracy.
* @param absoluteAccuracy Absolute accuracy.
* @param functionValueAccuracy Function value accuracy.
* @param maximalOrder maximal order.
* @exception NumberIsTooSmallException if maximal order is lower than 2
*/
public BracketingNthOrderBrentSolver(final double relativeAccuracy,
final double absoluteAccuracy,
final double functionValueAccuracy,
final int maximalOrder)
throws NumberIsTooSmallException {
super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
if (maximalOrder < 2) {
throw new NumberIsTooSmallException(maximalOrder, 2, true);
}
this.maximalOrder = maximalOrder;
this.allowed = AllowedSolutions.ANY_SIDE;
}
/** Get the maximal order.
* @return maximal order
*/
public int getMaximalOrder() {
return maximalOrder;
}
/**
* {@inheritDoc}
*/
@Override
protected double doSolve() {
// prepare arrays with the first points
final double[] x = new double[maximalOrder + 1];
final double[] y = new double[maximalOrder + 1];
x[0] = getMin();
x[1] = getStartValue();
x[2] = getMax();
verifySequence(x[0], x[1], x[2]);
// evaluate initial guess
y[1] = computeObjectiveValue(x[1]);
if (MathUtils.equals(y[1], 0.0, 1)) {
// return the initial guess if it is a perfect root.
return x[1];
}
// evaluate first endpoint
y[0] = computeObjectiveValue(x[0]);
if (MathUtils.equals(y[0], 0.0, 1)) {
// return the first endpoint if it is a perfect root.
return x[0];
}
int nbPoints;
int signChangeIndex;
if (y[0] * y[1] < 0) {
// reduce interval if it brackets the root
nbPoints = 2;
signChangeIndex = 1;
} else {
// evaluate second endpoint
y[2] = computeObjectiveValue(x[2]);
if (MathUtils.equals(y[2], 0.0, 1)) {
// return the second endpoint if it is a perfect root.
return x[2];
}
if (y[1] * y[2] < 0) {
// use all computed point as a start sampling array for solving
nbPoints = 3;
signChangeIndex = 2;
} else {
throw new NoBracketingException(x[0], x[2], y[0], y[2]);
}
}
// prepare a work array for inverse polynomial interpolation
final double[] tmpX = new double[x.length];
// current tightest bracketing of the root
double xA = x[signChangeIndex - 1];
double yA = y[signChangeIndex - 1];
double absYA = FastMath.abs(yA);
int agingA = 0;
double xB = x[signChangeIndex];
double yB = y[signChangeIndex];
double absYB = FastMath.abs(yB);
int agingB = 0;
// search loop
while (true) {
// check convergence of bracketing interval
final double xTol = getAbsoluteAccuracy() +
getRelativeAccuracy() * FastMath.max(FastMath.abs(xA), FastMath.abs(xB));
if (((xB - xA) <= xTol) || (FastMath.max(absYA, absYB) < getFunctionValueAccuracy())) {
switch (allowed) {
case ANY_SIDE :
return absYA < absYB ? xA : xB;
case LEFT_SIDE :
return xA;
case RIGHT_SIDE :
return xB;
case BELOW_SIDE :
return (yA <= 0) ? xA : xB;
case ABOVE_SIDE :
return (yA < 0) ? xB : xA;
default :
// this should never happen
throw new MathInternalError(null);
}
}
// target for the next evaluation point
double targetY;
if (agingA >= MAXIMAL_AGING) {
// we keep updating the high bracket, try to compensate this
targetY = -REDUCTION_FACTOR * yB;
} else if (agingB >= MAXIMAL_AGING) {
// we keep updating the low bracket, try to compensate this
targetY = -REDUCTION_FACTOR * yA;
} else {
// bracketing is balanced, try to find the root itself
targetY = 0;
}
// make a few attempts to guess a root,
double nextX;
int start = 0;
int end = nbPoints;
do {
// guess a value for current target, using inverse polynomial interpolation
System.arraycopy(x, start, tmpX, start, end - start);
nextX = guessX(targetY, tmpX, y, start, end);
if (!((nextX > xA) && (nextX < xB))) {
// the guessed root is not strictly inside of the tightest bracketing interval
// the guessed root is either not strictly inside the interval or it
// is a NaN (which occurs when some sampling points share the same y)
// we try again with a lower interpolation order
if (signChangeIndex - start >= end - signChangeIndex) {
// we have more points before the sign change, drop the lowest point
++start;
} else {
// we have more points after sign change, drop the highest point
--end;
}
// we need to do one more attempt
nextX = Double.NaN;
}
} while (Double.isNaN(nextX) && (end - start > 1));
if (Double.isNaN(nextX)) {
// fall back to bisection
nextX = xA + 0.5 * (xB - xA);
start = signChangeIndex - 1;
end = signChangeIndex;
}
// evaluate the function at the guessed root
final double nextY = computeObjectiveValue(nextX);
if (MathUtils.equals(nextY, 0.0, 1)) {
// we have found an exact root, since it is not an approximation
// we don't need to bother about the allowed solutions setting
return nextX;
}
if ((nbPoints > 2) && (end - start != nbPoints)) {
// we have been forced to ignore some points to keep bracketing,
// they are probably too far from the root, drop them from now on
nbPoints = end - start;
System.arraycopy(x, start, x, 0, nbPoints);
System.arraycopy(y, start, y, 0, nbPoints);
signChangeIndex -= start;
} else if (nbPoints == x.length) {
// we have to drop one point in order to insert the new one
nbPoints--;
// keep the tightest bracketing interval as centered as possible
if (signChangeIndex >= (x.length + 1) / 2) {
// we drop the lowest point, we have to shift the arrays and the index
System.arraycopy(x, 1, x, 0, nbPoints);
System.arraycopy(y, 1, y, 0, nbPoints);
--signChangeIndex;
}
}
// insert the last computed point
//(by construction, we know it lies inside the tightest bracketing interval)
System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
x[signChangeIndex] = nextX;
System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
y[signChangeIndex] = nextY;
++nbPoints;
// update the bracketing interval
if (nextY * yA <= 0) {
// the sign change occurs before the inserted point
xB = nextX;
yB = nextY;
absYB = FastMath.abs(yB);
++agingA;
agingB = 0;
} else {
// the sign change occurs after the inserted point
xA = nextX;
yA = nextY;
absYA = FastMath.abs(yA);
agingA = 0;
++agingB;
// update the sign change index
signChangeIndex++;
}
}
}
/** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
* <p>
* The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
* is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
* Q(y<sub>i</sub>) = x<sub>i</sub>.
* </p>
* @param targetY target value for y
* @param x reference points abscissas for interpolation,
* note that this array <em>is</em> 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);
}
}

View File

@ -52,6 +52,10 @@ The <action> type attribute can be add,update,fix,remove.
If the output is not quite correct, check for invisible trailing spaces!
-->
<release version="3.0" date="TBD" description="TBD">
<action dev="luc" type="add" issue="MATH-635" >
Added a Brent-like solver that has higher (user specified) order and does
bracket selection on the result: BracketingNthOrderBrentSolver.
</action>
<action dev="luc" type="add" >
Added a few shortcut methods and predicates to Dfp (abs, isZero,
negativeOrNull, strictlyNegative, positiveOrNull, strictlyPositive).

View File

@ -73,6 +73,12 @@
<td>yes</td>
<td>no</td>
</tr>
<tr>
<td><a href="../apidocs/org/apache/commons/math/analysis/solvers/BracketingNthOrderBrentSolver.html">bracketing n<sup>th</sup> order Brent</a></td>
<td>variable order, guaranteed</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td><a href="../apidocs/org/apache/commons/math/analysis/solvers/IllinoisSolver.html">Illinois Method</a></td>
<td><a href="../apidocs/org/apache/commons/math/analysis/UnivariateRealFunction.html">univariate real-valued functions</a></td>
@ -206,7 +212,8 @@
<source>UnivariateRealFunction function = // some user defined function object
final double relativeAccuracy = 1.0e-12;
final double absoluteAccuracy = 1.0e-8;
UnivariateRealSolver solver = new PegasusSolver(relativeAccuracy, absoluteAccuracy);
final int maxOrder = 5;
UnivariateRealSolver solver = new BracketingNthOrderBrentSolver(relativeAccuracy, absoluteAccuracy, maxOrder);
double c = solver.solve(100, function, 1.0, 5.0, AllowedSolutions.LEFT_SIDE);</source>
<p>
Force 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);
</source>
<p>
The <code>BrentSolve</code> 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 <code>BrentSolver</code> 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.
</p>
<p>
The <code>BracketingNthOrderBrentSolver</code> uses an extension of the
Brent-Dekker algorithm which uses inverse n<sup>th</sup> 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.
</p>
<p>
The <code>SecantSolver</code> uses a straightforward secant
algorithm which does not bracket the search and therefore does not

View File

@ -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 n<sup>th</sup> 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);
}
};
}
}
}