From 58f407fc8a3329bd2d38451ad4c992dc71441097 Mon Sep 17 00:00:00 2001 From: Gilles Sadowski Date: Mon, 26 Jul 2010 12:17:45 +0000 Subject: [PATCH] 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 --- .../univariate/BrentOptimizer.java | 122 ++++++++++++------ src/site/xdoc/changes.xml | 3 + .../math/analysis/QuinticFunction.java | 2 +- ...MultiStartUnivariateRealOptimizerTest.java | 24 ++-- ...mizerTest.java => BrentOptimizerTest.java} | 49 +++++-- 5 files changed, 140 insertions(+), 60 deletions(-) rename src/test/java/org/apache/commons/math/optimization/univariate/{BrentMinimizerTest.java => BrentOptimizerTest.java} (71%) diff --git a/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.java b/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.java index 097c17c2c..47409a8e8 100644 --- a/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.java +++ b/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.java @@ -18,19 +18,20 @@ package org.apache.commons.math.optimization.univariate; import org.apache.commons.math.FunctionEvaluationException; 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.optimization.GoalType; /** * Implements Richard Brent's algorithm (from his book "Algorithms for * 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$ * @since 2.0 */ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { - /** * Golden section. */ @@ -47,15 +48,16 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { public double optimize(final UnivariateRealFunction f, final GoalType goalType, final double min, final double max, final double startValue) throws MaxIterationsExceededException, FunctionEvaluationException { - return optimize(f, goalType, min, max); + clearResult(); + return localMin(f, goalType, min, startValue, max, + getRelativeAccuracy(), getAbsoluteAccuracy()); } /** {@inheritDoc} */ public double optimize(final UnivariateRealFunction f, final GoalType goalType, final double min, final double max) throws MaxIterationsExceededException, FunctionEvaluationException { - clearResult(); - return localMin(f, goalType, min, max, relativeAccuracy, absoluteAccuracy); + return optimize(f, goalType, min, max, min + GOLDEN_SECTION * (max - min)); } /** @@ -69,23 +71,41 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { * {@code eps} should be no smaller than 2 macheps and preferable not * much less than sqrt(macheps), where macheps is the relative * 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} - * or {@link GoalType#MINIMIZE} - * @param a Lower bound of the interval - * @param b Higher bound of the interval - * @param eps Relative accuracy - * @param t Absolute accuracy - * @return the point at which the function is minimal. + * or {@link GoalType#MINIMIZE}. + * @param lo Lower bound of the interval. + * @param mid Point inside the interval {@code [lo, hi]}. + * @param hi Higher bound of the interval. + * @param eps Relative accuracy. + * @param t Absolute accuracy. + * @return the optimum point. * @throws MaxIterationsExceededException if the maximum iteration count * is exceeded. * @throws FunctionEvaluationException if an error occurs evaluating * the function. */ - private double localMin(final UnivariateRealFunction f, final GoalType goalType, - double a, double b, final double eps, final double t) + private double localMin(UnivariateRealFunction f, + GoalType goalType, + double lo, double mid, double hi, + double eps, double t) 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 w = x; double e = 0; @@ -99,18 +119,18 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { int count = 0; while (count < maximalIterationCount) { double m = 0.5 * (a + b); - double tol = eps * Math.abs(x) + t; - double t2 = 2 * tol; + final double tol1 = eps * Math.abs(x) + t; + final double tol2 = 2 * tol1; // 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 q = 0; double r = 0; double d = 0; double u = 0; - if (Math.abs(e) > tol) { // Fit parabola. + if (Math.abs(e) > tol1) { // Fit parabola. r = (x - w) * (fx - fv); q = (x - v) * (fx - fw); p = (x - v) * q - (x - w) * r; @@ -124,24 +144,53 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { r = e; e = d; - } - if (Math.abs(p) < Math.abs(0.5 * q * r) && - (p < q * (a - x)) && (p < q * (b - x))) { // Parabolic interpolation step. - d = p / q; - u = x + d; + if (p > q * (a - x) + && p < q * (b - x) + && Math.abs(p) < Math.abs(0.5 * q * r)) { + // Parabolic interpolation step. + d = p / q; + u = x + d; - // f must not be evaluated too close to a or b. - if (((u - a) < t2) || ((b - u) < t2)) { - d = (x < m) ? tol : -tol; + // f must not be evaluated too close to a or b. + if (u - a < tol2 + || 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; } - // f must not be evaluated too close to a or b. - u = x + ((Math.abs(d) > tol) ? d : ((d > 0) ? tol : -tol)); + // Update by at least "tol1". + if (Math.abs(d) < tol1) { + if (d >= 0) { + u = x + tol1; + } else { + u = x - tol1; + } + } else { + u = x + d; + } + double fu = computeObjectiveValue(f, u); if (goalType == GoalType.MAXIMIZE) { fu = -fu; @@ -166,12 +215,15 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { } else { b = u; } - if ((fu <= fw) || (w == x)) { + if (fu <= fw + || w == x) { v = w; fv = fw; w = u; fw = fu; - } else if ((fu <= fv) || (v == x) || (v == w)) { + } else if (fu <= fv + || v == x + || v == w) { v = u; fv = fu; } @@ -180,12 +232,8 @@ public class BrentOptimizer extends AbstractUnivariateRealOptimizer { setResult(x, (goalType == GoalType.MAXIMIZE) ? -fx : fx, count); return x; } - ++count; } - throw new MaxIterationsExceededException(maximalIterationCount); - } - } diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml index 07be4a457..d3390e02b 100644 --- a/src/site/xdoc/changes.xml +++ b/src/site/xdoc/changes.xml @@ -52,6 +52,9 @@ The type attribute can be add,update,fix,remove. If the output is not quite correct, check for invisible trailing spaces! --> + + Fixed several bugs in "BrentOptimizer". + Fixed inconsistency in return values in "MultiStartUnivariateRealOptimizer". diff --git a/src/test/java/org/apache/commons/math/analysis/QuinticFunction.java b/src/test/java/org/apache/commons/math/analysis/QuinticFunction.java index f29330228..73dd9dced 100644 --- a/src/test/java/org/apache/commons/math/analysis/QuinticFunction.java +++ b/src/test/java/org/apache/commons/math/analysis/QuinticFunction.java @@ -19,7 +19,7 @@ package org.apache.commons.math.analysis; import org.apache.commons.math.FunctionEvaluationException; /** - * Auxillary class for testing solvers. + * Auxiliary class for testing solvers. * * @version $Revision$ $Date$ */ diff --git a/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java index e066703b6..159588d1e 100644 --- a/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java +++ b/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java @@ -48,8 +48,8 @@ public class MultiStartUnivariateRealOptimizerTest { assertEquals(-1.0, f.value(optima[i]), 1.0e-10); assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10); } - assertTrue(minimizer.getEvaluations() > 2900); - assertTrue(minimizer.getEvaluations() < 3100); + assertTrue(minimizer.getEvaluations() > 1500); + assertTrue(minimizer.getEvaluations() < 1700); } @Test @@ -58,8 +58,9 @@ public class MultiStartUnivariateRealOptimizerTest { // The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643, UnivariateRealFunction f = new QuinticFunction(); UnivariateRealOptimizer underlying = new BrentOptimizer(); + underlying.setRelativeAccuracy(1e-15); JDKRandomGenerator g = new JDKRandomGenerator(); - g.setSeed(4312000053l); + g.setSeed(4312000053L); MultiStartUnivariateRealOptimizer minimizer = new MultiStartUnivariateRealOptimizer(underlying, 5, g); minimizer.setAbsoluteAccuracy(10 * minimizer.getAbsoluteAccuracy()); @@ -82,9 +83,10 @@ public class MultiStartUnivariateRealOptimizerTest { fail("wrong exception caught"); } - assertEquals(-0.27195612846834, minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2), 1.0e-13); - assertEquals(-0.27195612846834, minimizer.getResult(), 1.0e-13); - assertEquals(-0.04433426954946, minimizer.getFunctionValue(), 1.0e-13); + double result = minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2); + assertEquals(-0.27195612525275803, result, 1.0e-13); + assertEquals(-0.27195612525275803, minimizer.getResult(), 1.0e-13); + assertEquals(-0.04433426954946637, minimizer.getFunctionValue(), 1.0e-13); double[] optima = minimizer.getOptima(); double[] optimaValues = minimizer.getOptimaValues(); @@ -92,11 +94,9 @@ public class MultiStartUnivariateRealOptimizerTest { assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10); } - assertTrue(minimizer.getEvaluations() >= 510); - assertTrue(minimizer.getEvaluations() <= 530); - assertTrue(minimizer.getIterationCount() >= 150); - assertTrue(minimizer.getIterationCount() <= 170); - + assertTrue(minimizer.getEvaluations() >= 300); + assertTrue(minimizer.getEvaluations() <= 420); + assertTrue(minimizer.getIterationCount() >= 100); + assertTrue(minimizer.getIterationCount() <= 140); } - } diff --git a/src/test/java/org/apache/commons/math/optimization/univariate/BrentMinimizerTest.java b/src/test/java/org/apache/commons/math/optimization/univariate/BrentOptimizerTest.java similarity index 71% rename from src/test/java/org/apache/commons/math/optimization/univariate/BrentMinimizerTest.java rename to src/test/java/org/apache/commons/math/optimization/univariate/BrentOptimizerTest.java index 0358de428..90091fbb0 100644 --- a/src/test/java/org/apache/commons/math/optimization/univariate/BrentMinimizerTest.java +++ b/src/test/java/org/apache/commons/math/optimization/univariate/BrentOptimizerTest.java @@ -25,15 +25,16 @@ import org.apache.commons.math.MathException; import org.apache.commons.math.MaxIterationsExceededException; import org.apache.commons.math.analysis.QuinticFunction; 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.optimization.GoalType; import org.apache.commons.math.optimization.UnivariateRealOptimizer; 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 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()); assertTrue(minimizer.getIterationCount() <= 50); assertTrue(minimizer.getEvaluations() <= 100); - assertTrue(minimizer.getEvaluations() >= 90); + assertTrue(minimizer.getEvaluations() >= 30); minimizer.setMaxEvaluations(50); try { minimizer.optimize(f, GoalType.MINIMIZE, 4, 5); @@ -68,8 +69,7 @@ public final class BrentMinimizerTest { @Test public void testQuinticMin() throws MathException { - // 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 local minima at -0.27195613 and 0.82221643. UnivariateRealFunction f = new QuinticFunction(); UnivariateRealOptimizer minimizer = new BrentOptimizer(); 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 assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -1.0, 0.2), 1.0e-8); 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 public void testQuinticMax() throws MathException { // 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(); UnivariateRealOptimizer minimizer = new BrentOptimizer(); assertEquals(0.27195613, minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3), 1.0e-8); - minimizer.setMaximalIterationCount(30); + minimizer.setMaximalIterationCount(20); try { minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3); fail("an exception should have been thrown"); @@ -110,8 +141,6 @@ public final class BrentMinimizerTest { assertEquals(3 * Math.PI / 2, result, 70 * solver.getAbsoluteAccuracy()); 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()); } - }