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.nonlinear.scalar.noderiv;
23  
24  import org.hipparchus.analysis.MultivariateFunction;
25  import org.hipparchus.exception.MathIllegalArgumentException;
26  import org.hipparchus.exception.MathIllegalStateException;
27  import org.hipparchus.optim.InitialGuess;
28  import org.hipparchus.optim.MaxEval;
29  import org.hipparchus.optim.PointValuePair;
30  import org.hipparchus.optim.SimpleBounds;
31  import org.hipparchus.optim.nonlinear.scalar.GoalType;
32  import org.hipparchus.optim.nonlinear.scalar.ObjectiveFunction;
33  import org.hipparchus.util.FastMath;
34  import org.junit.jupiter.api.Test;
35  
36  import java.util.Arrays;
37  import java.util.Random;
38  
39  import static org.junit.jupiter.api.Assertions.assertEquals;
40  import static org.junit.jupiter.api.Assertions.assertThrows;
41  
42  /**
43   * Test for {@link BOBYQAOptimizer}.
44   */
45  class BOBYQAOptimizerTest {
46  
47      static final int DIM = 13;
48  
49      @Test
50      void testInitOutOfBounds() {
51          assertThrows(MathIllegalArgumentException.class, () -> {
52              double[] startPoint = point(DIM, 3);
53              double[][] boundaries = boundaries(DIM, -1, 2);
54              doTest(new Rosen(), startPoint, boundaries,
55                  GoalType.MINIMIZE,
56                  1e-13, 1e-6, 2000, null);
57          });
58      }
59  
60      @Test
61      void testBoundariesDimensionMismatch() {
62          assertThrows(MathIllegalArgumentException.class, () -> {
63              double[] startPoint = point(DIM, 0.5);
64              double[][] boundaries = boundaries(DIM + 1, -1, 2);
65              doTest(new Rosen(), startPoint, boundaries,
66                  GoalType.MINIMIZE,
67                  1e-13, 1e-6, 2000, null);
68          });
69      }
70  
71      @Test
72      void testProblemDimensionTooSmall() {
73          assertThrows(MathIllegalArgumentException.class, () -> {
74              double[] startPoint = point(1, 0.5);
75              doTest(new Rosen(), startPoint, null,
76                  GoalType.MINIMIZE,
77                  1e-13, 1e-6, 2000, null);
78          });
79      }
80  
81      @Test
82      void testMaxEvaluations() {
83          assertThrows(MathIllegalStateException.class, () -> {
84              final int lowMaxEval = 2;
85              double[] startPoint = point(DIM, 0.1);
86              double[][] boundaries = null;
87              doTest(new Rosen(), startPoint, boundaries,
88                  GoalType.MINIMIZE,
89                  1e-13, 1e-6, lowMaxEval, null);
90          });
91      }
92  
93      @Test
94      void testRosen() {
95          double[] startPoint = point(DIM,0.1);
96          double[][] boundaries = null;
97          PointValuePair expected = new PointValuePair(point(DIM,1.0),0.0);
98          doTest(new Rosen(), startPoint, boundaries,
99                  GoalType.MINIMIZE,
100                 1e-13, 1e-6, 2000, expected);
101      }
102 
103     @Test
104     void testMaximize() {
105         double[] startPoint = point(DIM,1.0);
106         double[][] boundaries = null;
107         PointValuePair expected = new PointValuePair(point(DIM,0.0),1.0);
108         doTest(new MinusElli(), startPoint, boundaries,
109                 GoalType.MAXIMIZE,
110                 2e-10, 5e-6, 1000, expected);
111         boundaries = boundaries(DIM,-0.3,0.3);
112         startPoint = point(DIM,0.1);
113         doTest(new MinusElli(), startPoint, boundaries,
114                 GoalType.MAXIMIZE,
115                 2e-10, 5e-6, 1000, expected);
116     }
117 
118     @Test
119     void testEllipse() {
120         double[] startPoint = point(DIM,1.0);
121         double[][] boundaries = null;
122         PointValuePair expected =
123             new PointValuePair(point(DIM,0.0),0.0);
124         doTest(new Elli(), startPoint, boundaries,
125                 GoalType.MINIMIZE,
126                 1e-13, 1e-6, 1000, expected);
127      }
128 
129     @Test
130     void testElliRotated() {
131         double[] startPoint = point(DIM,1.0);
132         double[][] boundaries = null;
133         PointValuePair expected =
134             new PointValuePair(point(DIM,0.0),0.0);
135         doTest(new ElliRotated(), startPoint, boundaries,
136                 GoalType.MINIMIZE,
137                 1e-12, 1e-6, 10000, expected);
138     }
139 
140     @Test
141     void testCigar() {
142         double[] startPoint = point(DIM,1.0);
143         double[][] boundaries = null;
144         PointValuePair expected =
145             new PointValuePair(point(DIM,0.0),0.0);
146         doTest(new Cigar(), startPoint, boundaries,
147                 GoalType.MINIMIZE,
148                 1e-13, 1e-6, 100, expected);
149     }
150 
151     @Test
152     void testTwoAxes() {
153         double[] startPoint = point(DIM,1.0);
154         double[][] boundaries = null;
155         PointValuePair expected =
156             new PointValuePair(point(DIM,0.0),0.0);
157         doTest(new TwoAxes(), startPoint, boundaries,
158                 GoalType.MINIMIZE, 2*
159                 1e-13, 1e-6, 100, expected);
160      }
161 
162     @Test
163     void testCigTab() {
164         double[] startPoint = point(DIM,1.0);
165         double[][] boundaries = null;
166         PointValuePair expected =
167             new PointValuePair(point(DIM,0.0),0.0);
168         doTest(new CigTab(), startPoint, boundaries,
169                 GoalType.MINIMIZE,
170                 1e-13, 5e-5, 100, expected);
171      }
172 
173     @Test
174     void testSphere() {
175         double[] startPoint = point(DIM,1.0);
176         double[][] boundaries = null;
177         PointValuePair expected =
178             new PointValuePair(point(DIM,0.0),0.0);
179         doTest(new Sphere(), startPoint, boundaries,
180                 GoalType.MINIMIZE,
181                 1e-13, 1e-6, 100, expected);
182     }
183 
184     @Test
185     void testTablet() {
186         double[] startPoint = point(DIM,1.0);
187         double[][] boundaries = null;
188         PointValuePair expected =
189             new PointValuePair(point(DIM,0.0),0.0);
190         doTest(new Tablet(), startPoint, boundaries,
191                 GoalType.MINIMIZE,
192                 1e-13, 1e-6, 100, expected);
193     }
194 
195     @Test
196     void testDiffPow() {
197         double[] startPoint = point(DIM/2,1.0);
198         double[][] boundaries = null;
199         PointValuePair expected =
200             new PointValuePair(point(DIM/2,0.0),0.0);
201         doTest(new DiffPow(), startPoint, boundaries,
202                 GoalType.MINIMIZE,
203                 1e-8, 1e-1, 21000, expected);
204     }
205 
206     @Test
207     void testSsDiffPow() {
208         double[] startPoint = point(DIM/2,1.0);
209         double[][] boundaries = null;
210         PointValuePair expected =
211             new PointValuePair(point(DIM/2,0.0),0.0);
212         doTest(new SsDiffPow(), startPoint, boundaries,
213                 GoalType.MINIMIZE,
214                 1e-2, 1.3e-1, 50000, expected);
215     }
216 
217     @Test
218     void testAckley() {
219         double[] startPoint = point(DIM,0.1);
220         double[][] boundaries = null;
221         PointValuePair expected =
222             new PointValuePair(point(DIM,0.0),0.0);
223         doTest(new Ackley(), startPoint, boundaries,
224                 GoalType.MINIMIZE,
225                 1e-7, 1e-5, 1000, expected);
226     }
227 
228     @Test
229     void testRastrigin() {
230         double[] startPoint = point(DIM,1.0);
231 
232         double[][] boundaries = null;
233         PointValuePair expected =
234             new PointValuePair(point(DIM,0.0),0.0);
235         doTest(new Rastrigin(), startPoint, boundaries,
236                 GoalType.MINIMIZE,
237                 1e-13, 1e-6, 1000, expected);
238     }
239 
240     @Test
241     void testConstrainedRosen() {
242         double[] startPoint = point(DIM,0.1);
243 
244         double[][] boundaries = boundaries(DIM,-1,2);
245         PointValuePair expected =
246             new PointValuePair(point(DIM,1.0),0.0);
247         doTest(new Rosen(), startPoint, boundaries,
248                 GoalType.MINIMIZE,
249                 1e-13, 1e-6, 2000, expected);
250     }
251 
252     /**
253      * @param func Function to optimize.
254      * @param startPoint Starting point.
255      * @param boundaries Upper / lower point limit.
256      * @param goal Minimization or maximization.
257      * @param fTol Tolerance relative error on the objective function.
258      * @param pointTol Tolerance for checking that the optimum is correct.
259      * @param maxEvaluations Maximum number of evaluations.
260      * @param expected Expected point / value.
261      */
262     private void doTest(MultivariateFunction func,
263                         double[] startPoint,
264                         double[][] boundaries,
265                         GoalType goal,
266                         double fTol,
267                         double pointTol,
268                         int maxEvaluations,
269                         PointValuePair expected) {
270         doTest(func,
271                startPoint,
272                boundaries,
273                goal,
274                fTol,
275                pointTol,
276                maxEvaluations,
277                0,
278                expected,
279                "");
280     }
281 
282     /**
283      * @param func Function to optimize.
284      * @param startPoint Starting point.
285      * @param boundaries Upper / lower point limit.
286      * @param goal Minimization or maximization.
287      * @param fTol Tolerance relative error on the objective function.
288      * @param pointTol Tolerance for checking that the optimum is correct.
289      * @param maxEvaluations Maximum number of evaluations.
290      * @param additionalInterpolationPoints Number of interpolation to used
291      * in addition to the default (2 * dim + 1).
292      * @param expected Expected point / value.
293      */
294     private void doTest(MultivariateFunction func,
295                         double[] startPoint,
296                         double[][] boundaries,
297                         GoalType goal,
298                         double fTol,
299                         double pointTol,
300                         int maxEvaluations,
301                         int additionalInterpolationPoints,
302                         PointValuePair expected,
303                         String assertMsg) {
304 
305 //         System.out.println(func.getClass().getName() + " BEGIN"); // XXX
306 
307         int dim = startPoint.length;
308         final int numIterpolationPoints = 2 * dim + 1 + additionalInterpolationPoints;
309         BOBYQAOptimizer optim = new BOBYQAOptimizer(numIterpolationPoints);
310         PointValuePair result = boundaries == null ?
311             optim.optimize(new MaxEval(maxEvaluations),
312                            new ObjectiveFunction(func),
313                            goal,
314                            SimpleBounds.unbounded(dim),
315                            new InitialGuess(startPoint)) :
316             optim.optimize(new MaxEval(maxEvaluations),
317                            new ObjectiveFunction(func),
318                            goal,
319                            new InitialGuess(startPoint),
320                            new SimpleBounds(boundaries[0],
321                                             boundaries[1]));
322 //        System.out.println(func.getClass().getName() + " = "
323 //              + optim.getEvaluations() + " f(");
324 //        for (double x: result.getPoint())  System.out.print(x + " ");
325 //        System.out.println(") = " +  result.getValue());
326         assertEquals(expected.getValue(), result.getValue(), fTol, assertMsg);
327         for (int i = 0; i < dim; i++) {
328             assertEquals(expected.getPoint()[i],
329                                 result.getPoint()[i], pointTol);
330         }
331 
332 //         System.out.println(func.getClass().getName() + " END"); // XXX
333     }
334 
335     private static double[] point(int n, double value) {
336         double[] ds = new double[n];
337         Arrays.fill(ds, value);
338         return ds;
339     }
340 
341     private static double[][] boundaries(int dim,
342             double lower, double upper) {
343         double[][] boundaries = new double[2][dim];
344         for (int i = 0; i < dim; i++)
345             boundaries[0][i] = lower;
346         for (int i = 0; i < dim; i++)
347             boundaries[1][i] = upper;
348         return boundaries;
349     }
350 
351     private static class Sphere implements MultivariateFunction {
352 
353         public double value(double[] x) {
354             double f = 0;
355             for (int i = 0; i < x.length; ++i)
356                 f += x[i] * x[i];
357             return f;
358         }
359     }
360 
361     private static class Cigar implements MultivariateFunction {
362         private double factor;
363 
364         Cigar() {
365             this(1e3);
366         }
367 
368         Cigar(double axisratio) {
369             factor = axisratio * axisratio;
370         }
371 
372         public double value(double[] x) {
373             double f = x[0] * x[0];
374             for (int i = 1; i < x.length; ++i)
375                 f += factor * x[i] * x[i];
376             return f;
377         }
378     }
379 
380     private static class Tablet implements MultivariateFunction {
381         private double factor;
382 
383         Tablet() {
384             this(1e3);
385         }
386 
387         Tablet(double axisratio) {
388             factor = axisratio * axisratio;
389         }
390 
391         public double value(double[] x) {
392             double f = factor * x[0] * x[0];
393             for (int i = 1; i < x.length; ++i)
394                 f += x[i] * x[i];
395             return f;
396         }
397     }
398 
399     private static class CigTab implements MultivariateFunction {
400         private double factor;
401 
402         CigTab() {
403             this(1e4);
404         }
405 
406         CigTab(double axisratio) {
407             factor = axisratio;
408         }
409 
410         public double value(double[] x) {
411             int end = x.length - 1;
412             double f = x[0] * x[0] / factor + factor * x[end] * x[end];
413             for (int i = 1; i < end; ++i)
414                 f += x[i] * x[i];
415             return f;
416         }
417     }
418 
419     private static class TwoAxes implements MultivariateFunction {
420 
421         private double factor;
422 
423         TwoAxes() {
424             this(1e6);
425         }
426 
427         TwoAxes(double axisratio) {
428             factor = axisratio * axisratio;
429         }
430 
431         public double value(double[] x) {
432             double f = 0;
433             for (int i = 0; i < x.length; ++i)
434                 f += (i < x.length / 2 ? factor : 1) * x[i] * x[i];
435             return f;
436         }
437     }
438 
439     private static class ElliRotated implements MultivariateFunction {
440         private Basis B = new Basis();
441         private double factor;
442 
443         ElliRotated() {
444             this(1e3);
445         }
446 
447         ElliRotated(double axisratio) {
448             factor = axisratio * axisratio;
449         }
450 
451         public double value(double[] x) {
452             double f = 0;
453             x = B.Rotate(x);
454             for (int i = 0; i < x.length; ++i)
455                 f += FastMath.pow(factor, i / (x.length - 1.)) * x[i] * x[i];
456             return f;
457         }
458     }
459 
460     private static class Elli implements MultivariateFunction {
461 
462         private double factor;
463 
464         Elli() {
465             this(1e3);
466         }
467 
468         Elli(double axisratio) {
469             factor = axisratio * axisratio;
470         }
471 
472         public double value(double[] x) {
473             double f = 0;
474             for (int i = 0; i < x.length; ++i)
475                 f += FastMath.pow(factor, i / (x.length - 1.)) * x[i] * x[i];
476             return f;
477         }
478     }
479 
480     private static class MinusElli implements MultivariateFunction {
481         private final Elli elli = new Elli();
482         public double value(double[] x) {
483             return 1.0 - elli.value(x);
484         }
485     }
486 
487     private static class DiffPow implements MultivariateFunction {
488 //        private int fcount = 0;
489         public double value(double[] x) {
490             double f = 0;
491             for (int i = 0; i < x.length; ++i)
492                 f += FastMath.pow(FastMath.abs(x[i]), 2. + 10 * (double) i
493                         / (x.length - 1.));
494 //            System.out.print("" + (fcount++) + ") ");
495 //            for (int i = 0; i < x.length; i++)
496 //                System.out.print(x[i] +  " ");
497 //            System.out.println(" = " + f);
498             return f;
499         }
500     }
501 
502     private static class SsDiffPow implements MultivariateFunction {
503 
504         public double value(double[] x) {
505             double f = FastMath.pow(new DiffPow().value(x), 0.25);
506             return f;
507         }
508     }
509 
510     private static class Rosen implements MultivariateFunction {
511 
512         public double value(double[] x) {
513             double f = 0;
514             for (int i = 0; i < x.length - 1; ++i)
515                 f += 1e2 * (x[i] * x[i] - x[i + 1]) * (x[i] * x[i] - x[i + 1])
516                 + (x[i] - 1.) * (x[i] - 1.);
517             return f;
518         }
519     }
520 
521     private static class Ackley implements MultivariateFunction {
522         private double axisratio;
523 
524         Ackley(double axra) {
525             axisratio = axra;
526         }
527 
528         public Ackley() {
529             this(1);
530         }
531 
532         public double value(double[] x) {
533             double f = 0;
534             double res2 = 0;
535             double fac = 0;
536             for (int i = 0; i < x.length; ++i) {
537                 fac = FastMath.pow(axisratio, (i - 1.) / (x.length - 1.));
538                 f += fac * fac * x[i] * x[i];
539                 res2 += FastMath.cos(2. * FastMath.PI * fac * x[i]);
540             }
541             f = (20. - 20. * FastMath.exp(-0.2 * FastMath.sqrt(f / x.length))
542                     + FastMath.exp(1.) - FastMath.exp(res2 / x.length));
543             return f;
544         }
545     }
546 
547     private static class Rastrigin implements MultivariateFunction {
548 
549         private double axisratio;
550         private double amplitude;
551 
552         Rastrigin() {
553             this(1, 10);
554         }
555 
556         Rastrigin(double axisratio, double amplitude) {
557             this.axisratio = axisratio;
558             this.amplitude = amplitude;
559         }
560 
561         public double value(double[] x) {
562             double f = 0;
563             double fac;
564             for (int i = 0; i < x.length; ++i) {
565                 fac = FastMath.pow(axisratio, (i - 1.) / (x.length - 1.));
566                 if (i == 0 && x[i] < 0)
567                     fac *= 1.;
568                 f += fac * fac * x[i] * x[i] + amplitude
569                 * (1. - FastMath.cos(2. * FastMath.PI * fac * x[i]));
570             }
571             return f;
572         }
573     }
574 
575     private static class Basis {
576         double[][] basis;
577         Random rand = new Random(2); // use not always the same basis
578 
579         double[] Rotate(double[] x) {
580             GenBasis(x.length);
581             double[] y = new double[x.length];
582             for (int i = 0; i < x.length; ++i) {
583                 y[i] = 0;
584                 for (int j = 0; j < x.length; ++j)
585                     y[i] += basis[i][j] * x[j];
586             }
587             return y;
588         }
589 
590         void GenBasis(int DIM) {
591             if (basis != null ? basis.length == DIM : false)
592                 return;
593 
594             double sp;
595             int i, j, k;
596 
597             /* generate orthogonal basis */
598             basis = new double[DIM][DIM];
599             for (i = 0; i < DIM; ++i) {
600                 /* sample components gaussian */
601                 for (j = 0; j < DIM; ++j)
602                     basis[i][j] = rand.nextGaussian();
603                 /* substract projection of previous vectors */
604                 for (j = i - 1; j >= 0; --j) {
605                     for (sp = 0., k = 0; k < DIM; ++k)
606                         sp += basis[i][k] * basis[j][k]; /* scalar product */
607                     for (k = 0; k < DIM; ++k)
608                         basis[i][k] -= sp * basis[j][k]; /* substract */
609                 }
610                 /* normalize */
611                 for (sp = 0., k = 0; k < DIM; ++k)
612                     sp += basis[i][k] * basis[i][k]; /* squared norm */
613                 for (k = 0; k < DIM; ++k)
614                     basis[i][k] /= FastMath.sqrt(sp);
615             }
616         }
617     }
618 }