Fixed bugs in "BrentOptimizer".

Renamed "BrentMinimizerTest" to "BrentOptimizerTest".
Modified "MultiStartUnivariateRealOptimizerTest" because it uses
"BrentOptimizer" as the underlying optimizer, and so was affected
by the changes.


git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@979257 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gilles Sadowski 2010-07-26 12:17:45 +00:00
parent d4b02f6a3a
commit 58f407fc8a
5 changed files with 140 additions and 60 deletions

View File

@ -18,19 +18,20 @@ package org.apache.commons.math.optimization.univariate;
import org.apache.commons.math.FunctionEvaluationException; import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.MaxIterationsExceededException; import org.apache.commons.math.MaxIterationsExceededException;
import org.apache.commons.math.exception.NotStrictlyPositiveException;
import org.apache.commons.math.analysis.UnivariateRealFunction; import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.optimization.GoalType; import org.apache.commons.math.optimization.GoalType;
/** /**
* Implements Richard Brent's algorithm (from his book "Algorithms for * Implements Richard Brent's algorithm (from his book "Algorithms for
* Minimization without Derivatives", p. 79) for finding minima of real * Minimization without Derivatives", p. 79) for finding minima of real
* univariate functions. * univariate functions. This implementation is an adaptation partly
* based on the Python code from SciPy (module "optimize.py" v0.5).
* *
* @version $Revision$ $Date$ * @version $Revision$ $Date$
* @since 2.0 * @since 2.0
*/ */
public class BrentOptimizer extends AbstractUnivariateRealOptimizer { public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
/** /**
* Golden section. * Golden section.
*/ */
@ -47,15 +48,16 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
public double optimize(final UnivariateRealFunction f, final GoalType goalType, public double optimize(final UnivariateRealFunction f, final GoalType goalType,
final double min, final double max, final double startValue) final double min, final double max, final double startValue)
throws MaxIterationsExceededException, FunctionEvaluationException { throws MaxIterationsExceededException, FunctionEvaluationException {
return optimize(f, goalType, min, max); clearResult();
return localMin(f, goalType, min, startValue, max,
getRelativeAccuracy(), getAbsoluteAccuracy());
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
public double optimize(final UnivariateRealFunction f, final GoalType goalType, public double optimize(final UnivariateRealFunction f, final GoalType goalType,
final double min, final double max) final double min, final double max)
throws MaxIterationsExceededException, FunctionEvaluationException { throws MaxIterationsExceededException, FunctionEvaluationException {
clearResult(); return optimize(f, goalType, min, max, min + GOLDEN_SECTION * (max - min));
return localMin(f, goalType, min, max, relativeAccuracy, absoluteAccuracy);
} }
/** /**
@ -69,23 +71,41 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
* {@code eps} should be no smaller than <em>2 macheps</em> and preferable not * {@code eps} should be no smaller than <em>2 macheps</em> and preferable not
* much less than <em>sqrt(macheps)</em>, where <em>macheps</em> is the relative * much less than <em>sqrt(macheps)</em>, where <em>macheps</em> is the relative
* machine precision. {@code t} should be positive. * machine precision. {@code t} should be positive.
* @param f the function to solve * @param f the function to solve.
* @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE}
* or {@link GoalType#MINIMIZE} * or {@link GoalType#MINIMIZE}.
* @param a Lower bound of the interval * @param lo Lower bound of the interval.
* @param b Higher bound of the interval * @param mid Point inside the interval {@code [lo, hi]}.
* @param eps Relative accuracy * @param hi Higher bound of the interval.
* @param t Absolute accuracy * @param eps Relative accuracy.
* @return the point at which the function is minimal. * @param t Absolute accuracy.
* @return the optimum point.
* @throws MaxIterationsExceededException if the maximum iteration count * @throws MaxIterationsExceededException if the maximum iteration count
* is exceeded. * is exceeded.
* @throws FunctionEvaluationException if an error occurs evaluating * @throws FunctionEvaluationException if an error occurs evaluating
* the function. * the function.
*/ */
private double localMin(final UnivariateRealFunction f, final GoalType goalType, private double localMin(UnivariateRealFunction f,
double a, double b, final double eps, final double t) GoalType goalType,
double lo, double mid, double hi,
double eps, double t)
throws MaxIterationsExceededException, FunctionEvaluationException { throws MaxIterationsExceededException, FunctionEvaluationException {
double x = a + GOLDEN_SECTION * (b - a); if (eps <= 0) {
throw new NotStrictlyPositiveException(eps);
}
if (t <= 0) {
throw new NotStrictlyPositiveException(t);
}
double a, b;
if (lo < hi) {
a = lo;
b = hi;
} else {
a = hi;
b = lo;
}
double x = mid;
double v = x; double v = x;
double w = x; double w = x;
double e = 0; double e = 0;
@ -99,18 +119,18 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
int count = 0; int count = 0;
while (count < maximalIterationCount) { while (count < maximalIterationCount) {
double m = 0.5 * (a + b); double m = 0.5 * (a + b);
double tol = eps * Math.abs(x) + t; final double tol1 = eps * Math.abs(x) + t;
double t2 = 2 * tol; final double tol2 = 2 * tol1;
// Check stopping criterion. // Check stopping criterion.
if (Math.abs(x - m) > t2 - 0.5 * (b - a)) { if (Math.abs(x - m) > tol2 - 0.5 * (b - a)) {
double p = 0; double p = 0;
double q = 0; double q = 0;
double r = 0; double r = 0;
double d = 0; double d = 0;
double u = 0; double u = 0;
if (Math.abs(e) > tol) { // Fit parabola. if (Math.abs(e) > tol1) { // Fit parabola.
r = (x - w) * (fx - fv); r = (x - w) * (fx - fv);
q = (x - v) * (fx - fw); q = (x - v) * (fx - fw);
p = (x - v) * q - (x - w) * r; p = (x - v) * q - (x - w) * r;
@ -124,24 +144,53 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
r = e; r = e;
e = d; e = d;
}
if (Math.abs(p) < Math.abs(0.5 * q * r) && if (p > q * (a - x)
(p < q * (a - x)) && (p < q * (b - x))) { // Parabolic interpolation step. && p < q * (b - x)
d = p / q; && Math.abs(p) < Math.abs(0.5 * q * r)) {
u = x + d; // Parabolic interpolation step.
d = p / q;
u = x + d;
// f must not be evaluated too close to a or b. // f must not be evaluated too close to a or b.
if (((u - a) < t2) || ((b - u) < t2)) { if (u - a < tol2
d = (x < m) ? tol : -tol; || b - u < tol2) {
if (x <= m) {
d = tol1;
} else {
d = -tol1;
}
}
} else {
// Golden section step.
if (x < m) {
e = b - x;
} else {
e = a - x;
}
d = GOLDEN_SECTION * e;
}
} else {
// Golden section step.
if (x < m) {
e = b - x;
} else {
e = a - x;
} }
} else { // Golden section step.
e = ((x < m) ? b : a) - x;
d = GOLDEN_SECTION * e; d = GOLDEN_SECTION * e;
} }
// f must not be evaluated too close to a or b. // Update by at least "tol1".
u = x + ((Math.abs(d) > tol) ? d : ((d > 0) ? tol : -tol)); if (Math.abs(d) < tol1) {
if (d >= 0) {
u = x + tol1;
} else {
u = x - tol1;
}
} else {
u = x + d;
}
double fu = computeObjectiveValue(f, u); double fu = computeObjectiveValue(f, u);
if (goalType == GoalType.MAXIMIZE) { if (goalType == GoalType.MAXIMIZE) {
fu = -fu; fu = -fu;
@ -166,12 +215,15 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
} else { } else {
b = u; b = u;
} }
if ((fu <= fw) || (w == x)) { if (fu <= fw
|| w == x) {
v = w; v = w;
fv = fw; fv = fw;
w = u; w = u;
fw = fu; fw = fu;
} else if ((fu <= fv) || (v == x) || (v == w)) { } else if (fu <= fv
|| v == x
|| v == w) {
v = u; v = u;
fv = fu; fv = fu;
} }
@ -180,12 +232,8 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
setResult(x, (goalType == GoalType.MAXIMIZE) ? -fx : fx, count); setResult(x, (goalType == GoalType.MAXIMIZE) ? -fx : fx, count);
return x; return x;
} }
++count; ++count;
} }
throw new MaxIterationsExceededException(maximalIterationCount); throw new MaxIterationsExceededException(maximalIterationCount);
} }
} }

View File

@ -52,6 +52,9 @@ The <action> type attribute can be add,update,fix,remove.
If the output is not quite correct, check for invisible trailing spaces! If the output is not quite correct, check for invisible trailing spaces!
--> -->
<release version="2.2" date="TBD" description="TBD"> <release version="2.2" date="TBD" description="TBD">
<action dev="erans" type="fix" issue="MATH-395">
Fixed several bugs in "BrentOptimizer".
</action>
<action dev="erans" type="fix" issue="MATH-393"> <action dev="erans" type="fix" issue="MATH-393">
Fixed inconsistency in return values in "MultiStartUnivariateRealOptimizer". Fixed inconsistency in return values in "MultiStartUnivariateRealOptimizer".
</action> </action>

View File

@ -19,7 +19,7 @@ package org.apache.commons.math.analysis;
import org.apache.commons.math.FunctionEvaluationException; import org.apache.commons.math.FunctionEvaluationException;
/** /**
* Auxillary class for testing solvers. * Auxiliary class for testing solvers.
* *
* @version $Revision$ $Date$ * @version $Revision$ $Date$
*/ */

View File

@ -48,8 +48,8 @@ public class MultiStartUnivariateRealOptimizerTest {
assertEquals(-1.0, f.value(optima[i]), 1.0e-10); assertEquals(-1.0, f.value(optima[i]), 1.0e-10);
assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10); assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10);
} }
assertTrue(minimizer.getEvaluations() > 2900); assertTrue(minimizer.getEvaluations() > 1500);
assertTrue(minimizer.getEvaluations() < 3100); assertTrue(minimizer.getEvaluations() < 1700);
} }
@Test @Test
@ -58,8 +58,9 @@ public class MultiStartUnivariateRealOptimizerTest {
// The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643, // The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643,
UnivariateRealFunction f = new QuinticFunction(); UnivariateRealFunction f = new QuinticFunction();
UnivariateRealOptimizer underlying = new BrentOptimizer(); UnivariateRealOptimizer underlying = new BrentOptimizer();
underlying.setRelativeAccuracy(1e-15);
JDKRandomGenerator g = new JDKRandomGenerator(); JDKRandomGenerator g = new JDKRandomGenerator();
g.setSeed(4312000053l); g.setSeed(4312000053L);
MultiStartUnivariateRealOptimizer minimizer = MultiStartUnivariateRealOptimizer minimizer =
new MultiStartUnivariateRealOptimizer(underlying, 5, g); new MultiStartUnivariateRealOptimizer(underlying, 5, g);
minimizer.setAbsoluteAccuracy(10 * minimizer.getAbsoluteAccuracy()); minimizer.setAbsoluteAccuracy(10 * minimizer.getAbsoluteAccuracy());
@ -82,9 +83,10 @@ public class MultiStartUnivariateRealOptimizerTest {
fail("wrong exception caught"); fail("wrong exception caught");
} }
assertEquals(-0.27195612846834, minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2), 1.0e-13); double result = minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2);
assertEquals(-0.27195612846834, minimizer.getResult(), 1.0e-13); assertEquals(-0.27195612525275803, result, 1.0e-13);
assertEquals(-0.04433426954946, minimizer.getFunctionValue(), 1.0e-13); assertEquals(-0.27195612525275803, minimizer.getResult(), 1.0e-13);
assertEquals(-0.04433426954946637, minimizer.getFunctionValue(), 1.0e-13);
double[] optima = minimizer.getOptima(); double[] optima = minimizer.getOptima();
double[] optimaValues = minimizer.getOptimaValues(); double[] optimaValues = minimizer.getOptimaValues();
@ -92,11 +94,9 @@ public class MultiStartUnivariateRealOptimizerTest {
assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10); assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10);
} }
assertTrue(minimizer.getEvaluations() >= 510); assertTrue(minimizer.getEvaluations() >= 300);
assertTrue(minimizer.getEvaluations() <= 530); assertTrue(minimizer.getEvaluations() <= 420);
assertTrue(minimizer.getIterationCount() >= 150); assertTrue(minimizer.getIterationCount() >= 100);
assertTrue(minimizer.getIterationCount() <= 170); assertTrue(minimizer.getIterationCount() <= 140);
} }
} }

View File

@ -25,15 +25,16 @@ import org.apache.commons.math.MathException;
import org.apache.commons.math.MaxIterationsExceededException; import org.apache.commons.math.MaxIterationsExceededException;
import org.apache.commons.math.analysis.QuinticFunction; import org.apache.commons.math.analysis.QuinticFunction;
import org.apache.commons.math.analysis.SinFunction; import org.apache.commons.math.analysis.SinFunction;
import org.apache.commons.math.analysis.SincFunction;
import org.apache.commons.math.analysis.UnivariateRealFunction; import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.optimization.GoalType; import org.apache.commons.math.optimization.GoalType;
import org.apache.commons.math.optimization.UnivariateRealOptimizer; import org.apache.commons.math.optimization.UnivariateRealOptimizer;
import org.junit.Test; import org.junit.Test;
/** /**
* @version $Revision$ $Date$ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (Sat, 05 Sep 2009) $
*/ */
public final class BrentMinimizerTest { public final class BrentOptimizerTest {
@Test @Test
public void testSinMin() throws MathException { public void testSinMin() throws MathException {
@ -54,7 +55,7 @@ public final class BrentMinimizerTest {
assertEquals(3 * Math.PI / 2, minimizer.optimize(f, GoalType.MINIMIZE, 1, 5), 70 * minimizer.getAbsoluteAccuracy()); assertEquals(3 * Math.PI / 2, minimizer.optimize(f, GoalType.MINIMIZE, 1, 5), 70 * minimizer.getAbsoluteAccuracy());
assertTrue(minimizer.getIterationCount() <= 50); assertTrue(minimizer.getIterationCount() <= 50);
assertTrue(minimizer.getEvaluations() <= 100); assertTrue(minimizer.getEvaluations() <= 100);
assertTrue(minimizer.getEvaluations() >= 90); assertTrue(minimizer.getEvaluations() >= 30);
minimizer.setMaxEvaluations(50); minimizer.setMaxEvaluations(50);
try { try {
minimizer.optimize(f, GoalType.MINIMIZE, 4, 5); minimizer.optimize(f, GoalType.MINIMIZE, 4, 5);
@ -68,8 +69,7 @@ public final class BrentMinimizerTest {
@Test @Test
public void testQuinticMin() throws MathException { public void testQuinticMin() throws MathException {
// The quintic function has zeros at 0, +-0.5 and +-1. // The function has local minima at -0.27195613 and 0.82221643.
// The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643,
UnivariateRealFunction f = new QuinticFunction(); UnivariateRealFunction f = new QuinticFunction();
UnivariateRealOptimizer minimizer = new BrentOptimizer(); UnivariateRealOptimizer minimizer = new BrentOptimizer();
assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2), 1.0e-8); assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2), 1.0e-8);
@ -79,17 +79,48 @@ public final class BrentMinimizerTest {
// search in a large interval // search in a large interval
assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -1.0, 0.2), 1.0e-8); assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -1.0, 0.2), 1.0e-8);
assertTrue(minimizer.getIterationCount() <= 50); assertTrue(minimizer.getIterationCount() <= 50);
}
@Test
public void testQuinticMinPythonComparison() throws MathException {
// The function has local minima at -0.27195613 and 0.82221643.
UnivariateRealFunction f = new QuinticFunction();
UnivariateRealOptimizer minimizer = new BrentOptimizer();
minimizer.setRelativeAccuracy(1e-12);
minimizer.setAbsoluteAccuracy(1e-11);
double result;
int nIter, nEval;
result = minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2, -0.25);
nIter = minimizer.getIterationCount();
nEval = minimizer.getEvaluations();
// XXX Python: -0.27195612805911351 (instead of -0.2719561279558559).
assertEquals(-0.2719561279558559, result, 1e-12);
// XXX Python: 15 (instead of 18).
assertEquals(18, nEval);
// XXX Python: 11 (instead of 17).
assertEquals(17, nIter);
result = minimizer.optimize(f, GoalType.MINIMIZE, 0.7, 0.9, 0.8);
nIter = minimizer.getIterationCount();
nEval = minimizer.getEvaluations();
// XXX Python: 0.82221643488363705 (instead of 0.8222164326561908).
assertEquals(0.8222164326561908, result, 1e-12);
// XXX Python: 25 (instead of 43).
assertEquals(43, nEval);
// XXX Python: 21 (instead of 24).
assertEquals(24, nIter);
} }
@Test @Test
public void testQuinticMax() throws MathException { public void testQuinticMax() throws MathException {
// The quintic function has zeros at 0, +-0.5 and +-1. // The quintic function has zeros at 0, +-0.5 and +-1.
// The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643, // The function has a local maximum at 0.27195613.
UnivariateRealFunction f = new QuinticFunction(); UnivariateRealFunction f = new QuinticFunction();
UnivariateRealOptimizer minimizer = new BrentOptimizer(); UnivariateRealOptimizer minimizer = new BrentOptimizer();
assertEquals(0.27195613, minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3), 1.0e-8); assertEquals(0.27195613, minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3), 1.0e-8);
minimizer.setMaximalIterationCount(30); minimizer.setMaximalIterationCount(20);
try { try {
minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3); minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3);
fail("an exception should have been thrown"); fail("an exception should have been thrown");
@ -110,8 +141,6 @@ public final class BrentMinimizerTest {
assertEquals(3 * Math.PI / 2, result, 70 * solver.getAbsoluteAccuracy()); assertEquals(3 * Math.PI / 2, result, 70 * solver.getAbsoluteAccuracy());
result = solver.optimize(f, GoalType.MINIMIZE, 4, 3 * Math.PI / 2); result = solver.optimize(f, GoalType.MINIMIZE, 4, 3 * Math.PI / 2);
assertEquals(3 * Math.PI / 2, result, 70 * solver.getAbsoluteAccuracy()); assertEquals(3 * Math.PI / 2, result, 80 * solver.getAbsoluteAccuracy());
} }
} }