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