View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math4.legacy.optim.univariate;
18  
19  
20  import org.apache.commons.math4.legacy.analysis.FunctionUtils;
21  import org.apache.commons.math4.legacy.analysis.QuinticFunction;
22  import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
23  import org.apache.commons.math4.legacy.analysis.function.Sin;
24  import org.apache.commons.math4.legacy.analysis.function.StepFunction;
25  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
26  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
27  import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
28  import org.apache.commons.math4.legacy.optim.ConvergenceChecker;
29  import org.apache.commons.math4.legacy.optim.MaxEval;
30  import org.apache.commons.math4.legacy.optim.nonlinear.scalar.GoalType;
31  import org.apache.commons.math4.legacy.stat.descriptive.DescriptiveStatistics;
32  import org.apache.commons.math4.core.jdkmath.JdkMath;
33  import org.junit.Assert;
34  import org.junit.Test;
35  
36  /**
37   */
38  public final class BrentOptimizerTest {
39  
40      @Test
41      public void testSinMin() {
42          UnivariateFunction f = new Sin();
43          UnivariateOptimizer optimizer = new BrentOptimizer(1e-10, 1e-14);
44          Assert.assertEquals(3 * Math.PI / 2, optimizer.optimize(new MaxEval(200),
45                                                                  new UnivariateObjectiveFunction(f),
46                                                                  GoalType.MINIMIZE,
47                                                                  new SearchInterval(4, 5)).getPoint(), 1e-8);
48          Assert.assertTrue(optimizer.getEvaluations() <= 50);
49          Assert.assertEquals(200, optimizer.getMaxEvaluations());
50          Assert.assertEquals(3 * Math.PI / 2, optimizer.optimize(new MaxEval(200),
51                                                                  new UnivariateObjectiveFunction(f),
52                                                                  GoalType.MINIMIZE,
53                                                                  new SearchInterval(1, 5)).getPoint(), 1e-8);
54          Assert.assertTrue(optimizer.getEvaluations() <= 100);
55          Assert.assertTrue(optimizer.getEvaluations() >= 15);
56          try {
57              optimizer.optimize(new MaxEval(10),
58                                 new UnivariateObjectiveFunction(f),
59                                 GoalType.MINIMIZE,
60                                 new SearchInterval(4, 5));
61              Assert.fail("an exception should have been thrown");
62          } catch (TooManyEvaluationsException fee) {
63              // expected
64          }
65      }
66  
67      @Test
68      public void testSinMinWithValueChecker() {
69          final UnivariateFunction f = new Sin();
70          final ConvergenceChecker<UnivariatePointValuePair> checker = new SimpleUnivariateValueChecker(1e-5, 1e-14);
71          // The default stopping criterion of Brent's algorithm should not
72          // pass, but the search will stop at the given relative tolerance
73          // for the function value.
74          final UnivariateOptimizer optimizer = new BrentOptimizer(1e-10, 1e-14, checker);
75          final UnivariatePointValuePair result = optimizer.optimize(new MaxEval(200),
76                                                                     new UnivariateObjectiveFunction(f),
77                                                                     GoalType.MINIMIZE,
78                                                                     new SearchInterval(4, 5));
79          Assert.assertEquals(3 * Math.PI / 2, result.getPoint(), 1e-3);
80      }
81  
82      @Test
83      public void testBoundaries() {
84          final double lower = -1.0;
85          final double upper = +1.0;
86          UnivariateFunction f = new UnivariateFunction() {
87              @Override
88              public double value(double x) {
89                  if (x < lower) {
90                      throw new NumberIsTooSmallException(x, lower, true);
91                  } else if (x > upper) {
92                      throw new NumberIsTooLargeException(x, upper, true);
93                  } else {
94                      return x;
95                  }
96              }
97          };
98          UnivariateOptimizer optimizer = new BrentOptimizer(1e-10, 1e-14);
99          Assert.assertEquals(lower,
100                             optimizer.optimize(new MaxEval(100),
101                                                new UnivariateObjectiveFunction(f),
102                                                GoalType.MINIMIZE,
103                                                new SearchInterval(lower, upper)).getPoint(),
104                             1.0e-8);
105         Assert.assertEquals(upper,
106                             optimizer.optimize(new MaxEval(100),
107                                                new UnivariateObjectiveFunction(f),
108                                                GoalType.MAXIMIZE,
109                                                new SearchInterval(lower, upper)).getPoint(),
110                             1.0e-8);
111     }
112 
113     @Test
114     public void testQuinticMin() {
115         // The function has local minima at -0.27195613 and 0.82221643.
116         UnivariateFunction f = new QuinticFunction();
117         UnivariateOptimizer optimizer = new BrentOptimizer(1e-10, 1e-14);
118         Assert.assertEquals(-0.27195613, optimizer.optimize(new MaxEval(200),
119                                                             new UnivariateObjectiveFunction(f),
120                                                             GoalType.MINIMIZE,
121                                                             new SearchInterval(-0.3, -0.2)).getPoint(), 1.0e-8);
122         Assert.assertEquals( 0.82221643, optimizer.optimize(new MaxEval(200),
123                                                             new UnivariateObjectiveFunction(f),
124                                                             GoalType.MINIMIZE,
125                                                             new SearchInterval(0.3,  0.9)).getPoint(), 1.0e-8);
126         Assert.assertTrue(optimizer.getEvaluations() <= 50);
127 
128         // search in a large interval
129         Assert.assertEquals(-0.27195613, optimizer.optimize(new MaxEval(200),
130                                                             new UnivariateObjectiveFunction(f),
131                                                             GoalType.MINIMIZE,
132                                                             new SearchInterval(-1.0, 0.2)).getPoint(), 1.0e-8);
133         Assert.assertTrue(optimizer.getEvaluations() <= 50);
134     }
135 
136     @Test
137     public void testQuinticMinStatistics() {
138         // The function has local minima at -0.27195613 and 0.82221643.
139         UnivariateFunction f = new QuinticFunction();
140         UnivariateOptimizer optimizer = new BrentOptimizer(1e-11, 1e-14);
141 
142         final DescriptiveStatistics[] stat = new DescriptiveStatistics[2];
143         for (int i = 0; i < stat.length; i++) {
144             stat[i] = new DescriptiveStatistics();
145         }
146 
147         final double min = -0.75;
148         final double max = 0.25;
149         final int nSamples = 200;
150         final double delta = (max - min) / nSamples;
151         for (int i = 0; i < nSamples; i++) {
152             final double start = min + i * delta;
153             stat[0].addValue(optimizer.optimize(new MaxEval(40),
154                                                 new UnivariateObjectiveFunction(f),
155                                                 GoalType.MINIMIZE,
156                                                 new SearchInterval(min, max, start)).getPoint());
157             stat[1].addValue(optimizer.getEvaluations());
158         }
159 
160         final double meanOptValue = stat[0].getMean();
161         final double medianEval = stat[1].getPercentile(50);
162         Assert.assertTrue(meanOptValue > -0.2719561281);
163         Assert.assertTrue(meanOptValue < -0.2719561280);
164         Assert.assertEquals(23, (int) medianEval);
165 
166         // MATH-1121: Ensure that the iteration counter is incremented.
167         Assert.assertTrue(optimizer.getIterations() > 0);
168     }
169 
170     @Test
171     public void testQuinticMax() {
172         // The quintic function has zeros at 0, +-0.5 and +-1.
173         // The function has a local maximum at 0.27195613.
174         UnivariateFunction f = new QuinticFunction();
175         UnivariateOptimizer optimizer = new BrentOptimizer(1e-12, 1e-14);
176         Assert.assertEquals(0.27195613, optimizer.optimize(new MaxEval(100),
177                                                            new UnivariateObjectiveFunction(f),
178                                                            GoalType.MAXIMIZE,
179                                                            new SearchInterval(0.2, 0.3)).getPoint(), 1e-8);
180         try {
181             optimizer.optimize(new MaxEval(5),
182                                new UnivariateObjectiveFunction(f),
183                                GoalType.MAXIMIZE,
184                                new SearchInterval(0.2, 0.3));
185             Assert.fail("an exception should have been thrown");
186         } catch (TooManyEvaluationsException miee) {
187             // expected
188         }
189     }
190 
191     @Test
192     public void testMinEndpoints() {
193         UnivariateFunction f = new Sin();
194         UnivariateOptimizer optimizer = new BrentOptimizer(1e-8, 1e-14);
195 
196         // endpoint is minimum
197         double result = optimizer.optimize(new MaxEval(50),
198                                            new UnivariateObjectiveFunction(f),
199                                            GoalType.MINIMIZE,
200                                            new SearchInterval(3 * Math.PI / 2, 5)).getPoint();
201         Assert.assertEquals(3 * Math.PI / 2, result, 1e-6);
202 
203         result = optimizer.optimize(new MaxEval(50),
204                                     new UnivariateObjectiveFunction(f),
205                                     GoalType.MINIMIZE,
206                                     new SearchInterval(4, 3 * Math.PI / 2)).getPoint();
207         Assert.assertEquals(3 * Math.PI / 2, result, 1e-6);
208     }
209 
210     @Test
211     public void testMath832() {
212         final UnivariateFunction f = new UnivariateFunction() {
213                 @Override
214                 public double value(double x) {
215                     final double sqrtX = JdkMath.sqrt(x);
216                     final double a = 1e2 * sqrtX;
217                     final double b = 1e6 / x;
218                     final double c = 1e4 / sqrtX;
219 
220                     return a + b + c;
221                 }
222             };
223 
224         UnivariateOptimizer optimizer = new BrentOptimizer(1e-10, 1e-8);
225         final double result = optimizer.optimize(new MaxEval(1484),
226                                                  new UnivariateObjectiveFunction(f),
227                                                  GoalType.MINIMIZE,
228                                                  new SearchInterval(Double.MIN_VALUE,
229                                                                     Double.MAX_VALUE)).getPoint();
230 
231         Assert.assertEquals(804.9355825, result, 1e-6);
232     }
233 
234     /**
235      * Contrived example showing that prior to the resolution of MATH-855
236      * (second revision), the algorithm would not return the best point if
237      * it happened to be the initial guess.
238      */
239     @Test
240     public void testKeepInitIfBest() {
241         final double minSin = 3 * Math.PI / 2;
242         final double offset = 1e-8;
243         final double delta = 1e-7;
244         final UnivariateFunction f1 = new Sin();
245         final UnivariateFunction f2 = new StepFunction(new double[] { minSin, minSin + offset, minSin + 2 * offset},
246                                                        new double[] { 0, -1, 0 });
247         final UnivariateFunction f = FunctionUtils.add(f1, f2);
248         // A slightly less stringent tolerance would make the test pass
249         // even with the previous implementation.
250         final double relTol = 1e-8;
251         final UnivariateOptimizer optimizer = new BrentOptimizer(relTol, 1e-100);
252         final double init = minSin + 1.5 * offset;
253         final UnivariatePointValuePair result
254             = optimizer.optimize(new MaxEval(200),
255                                  new UnivariateObjectiveFunction(f),
256                                  GoalType.MINIMIZE,
257                                  new SearchInterval(minSin - 6.789 * delta,
258                                                     minSin + 9.876 * delta,
259                                                     init));
260 
261         final double sol = result.getPoint();
262         final double expected = init;
263 
264 //         System.out.println("numEval=" + numEval);
265 //         System.out.println("min=" + init + " f=" + f.value(init));
266 //         System.out.println("sol=" + sol + " f=" + f.value(sol));
267 //         System.out.println("exp=" + expected + " f=" + f.value(expected));
268 
269         Assert.assertTrue("Best point not reported", f.value(sol) <= f.value(expected));
270     }
271 
272     /**
273      * Contrived example showing that prior to the resolution of MATH-855,
274      * the algorithm, by always returning the last evaluated point, would
275      * sometimes not report the best point it had found.
276      */
277     @Test
278     public void testMath855() {
279         final double minSin = 3 * Math.PI / 2;
280         final double offset = 1e-8;
281         final double delta = 1e-7;
282         final UnivariateFunction f1 = new Sin();
283         final UnivariateFunction f2 = new StepFunction(new double[] { minSin, minSin + offset, minSin + 5 * offset },
284                                                        new double[] { 0, -1, 0 });
285         final UnivariateFunction f = FunctionUtils.add(f1, f2);
286         final UnivariateOptimizer optimizer = new BrentOptimizer(1e-8, 1e-100);
287         final UnivariatePointValuePair result
288             = optimizer.optimize(new MaxEval(200),
289                                  new UnivariateObjectiveFunction(f),
290                                  GoalType.MINIMIZE,
291                                  new SearchInterval(minSin - 6.789 * delta,
292                                                     minSin + 9.876 * delta));
293 
294         final double sol = result.getPoint();
295         final double expected = 4.712389027602411;
296 
297         // System.out.println("min=" + (minSin + offset) + " f=" + f.value(minSin + offset));
298         // System.out.println("sol=" + sol + " f=" + f.value(sol));
299         // System.out.println("exp=" + expected + " f=" + f.value(expected));
300 
301         Assert.assertTrue("Best point not reported", f.value(sol) <= f.value(expected));
302     }
303 }