View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.optimization;
17  
18  import com.irurueta.numerical.EvaluationException;
19  import com.irurueta.numerical.InvalidBracketRangeException;
20  import com.irurueta.numerical.LockedException;
21  import com.irurueta.numerical.NotAvailableException;
22  import com.irurueta.numerical.NotReadyException;
23  import com.irurueta.numerical.SingleDimensionFunctionEvaluatorListener;
24  
25  /**
26   * This class searches for brackets of values containing a minimum in a single
27   * dimension function.
28   * A bracket is a set of points: "a" a minimum evaluation point,
29   * "b" a middle evaluation point and "c" a maximum evaluation where a <= b
30   * <= c, and where f(b) <= f(a) and f(b) <= f(c).
31   * This class uses a downhill algorithm that is better suited to continuous
32   * functions. Other functions might not obtain reliable results when using this
33   * algorithm to obtain a bracket of points.
34   * Some subclasses of this class will implement algorithms to refine the
35   * solution obtained in a bracket in order to find an accurate estimation of a
36   * minimum.
37   * Some algorithms might not need to previously compute a bracket and will
38   * simply search for a minimum in all the range of possible values, whereas
39   * other algorithms will require first the computation of a bracket.
40   * In either case, computing a bracket prior estimating a minimum will always
41   * ensure that a more reliable solution will be found.
42   * Besides, bracket computation is required when a function contains several
43   * minima and search of an accurate minimum estimation is desired to be
44   * restricted to a certain range of values.
45   */
46  public abstract class BracketedSingleOptimizer extends SingleOptimizer {
47  
48      /**
49       * The default ratio by which intervals are magnified and.
50       */
51      public static final double GOLD = 1.618034;
52  
53      /**
54       * The maximum magnification allowed for a parabolic-fit step.
55       */
56      public static final double GLIMIT = 100.0;
57  
58      /**
59       * Small value representing machine precision.
60       */
61      public static final double TINY = 1e-20;
62  
63      /**
64       * Default minimum evaluation point where the bracket is supposed to start
65       * By default, if no bracket is computed, the whole range of values is used
66       * for minimum estimation.
67       */
68      public static final double DEFAULT_MIN_EVAL_POINT = -Double.MAX_VALUE;
69  
70      /**
71       * Default middle evaluation point where the bracket is supposed to start
72       * By default, if no bracket is computed, the whole range of values is used
73       * for minimum estimation.
74       */
75      public static final double DEFAULT_MIDDLE_EVAL_POINT = 0.0;
76  
77      /**
78       * Default maximum evaluation point where the bracket is supposed to start
79       * By default, if no bracket is computed, the whole range of values is used
80       * for minimum estimation.
81       */
82      public static final double DEFAULT_MAX_EVAL_POINT = Double.MAX_VALUE;
83  
84      /**
85       * Minimum evaluation point inside the bracket.
86       */
87      protected double ax;
88  
89      /**
90       * Middle evaluation point inside the bracket.
91       */
92      protected double bx;
93  
94      /**
95       * Maximum evaluation point inside the bracket.
96       */
97      protected double cx;
98  
99      /**
100      * Boolean indicating whether a bracket has been provided or computed.
101      */
102     private boolean bracketAvailable;
103 
104     /**
105      * Function evaluation value at minimum evaluation point inside the bracket.
106      */
107     private double fa;
108 
109     /**
110      * Function evaluation value at middle evaluation point inside the bracket.
111      */
112     private double fb;
113 
114     /**
115      * Function evaluation value at maximum evaluation point inside the bracket.
116      */
117     private double fc;
118 
119     /**
120      * Boolean indicating whether function evaluation at bracket limits and
121      * middle point are available or not.
122      */
123     private boolean bracketEvaluationAvailable;
124 
125     /**
126      * Constructor. Creates an instance with provided bracket of values.
127      *
128      * @param minEvalPoint    Minimum bracket evaluation point.
129      * @param middleEvalPoint Middle bracket evaluation point.
130      * @param maxEvalPoint    Maximum bracket evaluation point.
131      * @throws InvalidBracketRangeException Raised if the following condition is
132      *                                      not met: minEvalPoint <= middleEvalPoint <= maxEvalPoint.
133      */
134     protected BracketedSingleOptimizer(
135             final double minEvalPoint, final double middleEvalPoint, final double maxEvalPoint)
136             throws InvalidBracketRangeException {
137         internalSetBracket(minEvalPoint, middleEvalPoint, maxEvalPoint);
138     }
139 
140     /**
141      * Empty Constructor. Creates an instance using default bracket values.
142      */
143     protected BracketedSingleOptimizer() {
144         ax = DEFAULT_MIN_EVAL_POINT;
145         bx = DEFAULT_MIDDLE_EVAL_POINT;
146         cx = DEFAULT_MAX_EVAL_POINT;
147         bracketAvailable = true;
148     }
149 
150     /**
151      * Constructor. Creates an instance with provided bracket of values and a
152      * listener to get single dimension function evaluations.
153      *
154      * @param listener        Listener to evaluate a function.
155      * @param minEvalPoint    Minimum bracket evaluation point.
156      * @param middleEvalPoint Middle bracket evaluation point.
157      * @param maxEvalPoint    Maximum bracket evaluation point.
158      * @throws InvalidBracketRangeException Raised if the following condition is
159      *                                      not met: minEvalPoint <= middleEvalPoint <= maxEvalPoint.
160      */
161     protected BracketedSingleOptimizer(
162             final SingleDimensionFunctionEvaluatorListener listener, final double minEvalPoint,
163             final double middleEvalPoint, final double maxEvalPoint) throws InvalidBracketRangeException {
164         super(listener);
165         internalSetBracket(minEvalPoint, middleEvalPoint, maxEvalPoint);
166     }
167 
168     /**
169      * Sets a bracket of values to later search for a minimum. A local minimum
170      * will only be search within the minimum and maximum evaluation points of
171      * a given bracket.
172      * If bracket is not provided, it can also be computed from a default or
173      * coarse set of points in order to obtain a more refined bracket so that
174      * a minimum search can be estimated more precisely.
175      *
176      * @param minEvalPoint    Minimum bracket evaluation point.
177      * @param middleEvalPoint Middle bracket evaluation point.
178      * @param maxEvalPoint    Maximum bracket evaluation point.
179      * @throws InvalidBracketRangeException Raised if the following condition is
180      *                                      not met: minEvalPoint <= middleEvalPoint <= maxEvalPoint.
181      * @throws LockedException              Raised if this instance is locked. This instance
182      *                                      will be locked while doing some operations. Attempting to change any
183      *                                      parameter while being locked will raise this exception.
184      */
185     public void setBracket(
186             final double minEvalPoint, final double middleEvalPoint, final double maxEvalPoint) throws LockedException,
187             InvalidBracketRangeException {
188 
189         if (isLocked()) {
190             throw new LockedException();
191         }
192         internalSetBracket(minEvalPoint, middleEvalPoint, maxEvalPoint);
193     }
194 
195     /**
196      * Returns boolean indicating whether a bracket has been provided or
197      * computed and is available for retrieval.
198      *
199      * @return true if a bracket has been provided, false otherwise.
200      */
201     public boolean isBracketAvailable() {
202         return bracketAvailable;
203     }
204 
205     /**
206      * Returns minimum evaluation point where the bracket starts
207      *
208      * @return Minimum evaluation point.
209      * @throws NotAvailableException Raised if not provided or computed.
210      */
211     public double getMinEvaluationPoint() throws NotAvailableException {
212         if (!isBracketAvailable()) {
213             throw new NotAvailableException();
214         }
215 
216         return ax;
217     }
218 
219     /**
220      * Returns middle evaluation point within the bracket.
221      *
222      * @return Middle evaluation point.
223      * @throws NotAvailableException Raised if not provided or computed.
224      */
225     public double getMiddleEvaluationPoint() throws NotAvailableException {
226         if (!isBracketAvailable()) {
227             throw new NotAvailableException();
228         }
229 
230         return bx;
231     }
232 
233     /**
234      * Returns maximum evaluation point whether the bracket finishes.
235      *
236      * @return Maximum evaluation point.
237      * @throws NotAvailableException Raised if not provided or computed.
238      */
239     public double getMaxEvaluationPoint() throws NotAvailableException {
240         if (!isBracketAvailable()) {
241             throw new NotAvailableException();
242         }
243 
244         return cx;
245     }
246 
247     /**
248      * Returns single dimension function evaluation at provided or computed
249      * minimum evaluation point where the bracket starts.
250      *
251      * @return Function evaluation at bracket's minimum evaluation point.
252      * @throws NotAvailableException Raised if bracket evaluations are not
253      *                               available.
254      */
255     public double getEvaluationAtMin() throws NotAvailableException {
256         if (!areBracketEvaluationsAvailable()) {
257             throw new NotAvailableException();
258         }
259 
260         return fa;
261     }
262 
263     /**
264      * Returns single dimension function evaluation at provided or computed
265      * middle evaluation point within the bracket.
266      *
267      * @return Function evaluation at bracket's middle evaluation point.
268      * @throws NotAvailableException Raised if bracket evaluations are not
269      *                               available.
270      */
271     public double getEvaluationAtMiddle() throws NotAvailableException {
272         if (!areBracketEvaluationsAvailable()) {
273             throw new NotAvailableException();
274         }
275 
276         return fb;
277     }
278 
279     /**
280      * Returns single dimension function evaluation at provided or computed
281      * maximum evaluation point where the bracket finishes.
282      *
283      * @return Function evaluation at bracket's maximum evaluation point.
284      * @throws NotAvailableException Raised if bracket evaluations are not
285      *                               available.
286      */
287     public double getEvaluationAtMax() throws NotAvailableException {
288         if (!areBracketEvaluationsAvailable()) {
289             throw new NotAvailableException();
290         }
291 
292         return fc;
293     }
294 
295     /**
296      * Computes a bracket of values using provided values as a starting point.
297      * Given a function f, and given distinct initial points ax and bx, this
298      * routine searches in the downhill direction (defined by the function as
299      * evaluated at the initial points) and returns.
300      * ax (minimum evaluation point), bx (middle evaluation point), cx (maximum
301      * evaluation point) that bracket a minimum of the function. Also returned
302      * are the function values at the three points fa, fb, and fc, which are the
303      * function evaluations at minimum, middle and maximum bracket points.
304      *
305      * @param minEvalPoint    Initial minimum evaluation point of bracket.
306      * @param middleEvalPoint Initial middle evaluation point of bracket.
307      * @throws LockedException              Raised if this instance is locked. This instance
308      *                                      will be locked while doing some operations. Attempting to change any
309      *                                      parameter while being locked will raise this exception.
310      * @throws NotReadyException            Raised if this instance is not ready because a
311      *                                      listener has not yet been provided.
312      * @throws InvalidBracketRangeException Raised if minEvalPoint <
313      *                                      middleEvalPoint.
314      * @throws OptimizationException        Raised if a bracket couldn't be found .
315      *                                      because convergence was not achieved or function evaluation failed.
316      */
317     @SuppressWarnings("Duplicates")
318     public void computeBracket(final double minEvalPoint, final double middleEvalPoint) throws LockedException,
319             NotReadyException, InvalidBracketRangeException, OptimizationException {
320 
321         if (isLocked()) {
322             throw new LockedException();
323         }
324         if (!isReady()) {
325             throw new NotReadyException();
326         }
327         if (minEvalPoint > middleEvalPoint) {
328             throw new InvalidBracketRangeException();
329         }
330 
331         locked = true;
332 
333         final var a = new double[1];
334         final var b = new double[1];
335         final var c = new double[1];
336 
337         try {
338             ax = minEvalPoint;
339             bx = middleEvalPoint;
340             double fu;
341             fa = listener.evaluate(ax);
342             fb = listener.evaluate(bx);
343 
344             //switch roles of a and b so that we can go downhill in the
345             //direction from a to b
346             if (fb > fa) {
347                 a[0] = ax;
348                 b[0] = bx;
349                 swap(a, b);
350                 ax = a[0];
351                 bx = b[0];
352 
353                 a[0] = fa;
354                 b[0] = fb;
355                 swap(a, b);
356                 a[0] = fa;
357                 b[0] = fb;
358             }
359 
360             //First guess for c
361             cx = bx + GOLD * (bx - ax);
362             fc = listener.evaluate(cx);
363 
364             //Keep returning here until we bracket.
365             while (fb > fc) {
366                 //Compute u by parabolic extrapolation from a, b, c. TINY is
367                 //used to prevent any possible division by zero.
368                 final var r = (bx - ax) * (fb - fc);
369                 final var q = (bx - cx) * (fb - fa);
370                 var u = bx - ((bx - cx) * q - (bx - ax) * r) /
371                         (2.0 * sign(Math.max(Math.abs(q - r), TINY), q - r));
372                 final var ulim = bx + GLIMIT * (cx - bx);
373 
374                 //We won't go farther than this. Test various possibilities:
375                 if ((bx - u) * (u - cx) > 0.0) {
376                     //Parabolic u is between b and c: try it.
377                     fu = listener.evaluate(u);
378                     if (fu < fc) {
379                         //Got a minimum between b and c.
380                         ax = bx;
381                         bx = u;
382                         fa = fb;
383                         fb = fu;
384                         break;
385 
386                     } else if (fu > fb) {
387                         //Got a minimum between a and u
388                         cx = u;
389                         fc = fu;
390                         break;
391                     }
392 
393                     //Parabolic fit was no use. Use default magnification.
394                     u = cx + GOLD * (cx - bx);
395                     fu = listener.evaluate(u);
396 
397                 } else if ((cx - u) * (u - ulim) > 0.0) {
398                     //Parabolic fit is between c and its allowed limit
399                     fu = listener.evaluate(u);
400 
401                     if (fu < fc) {
402                         a[0] = bx;
403                         b[0] = cx;
404                         c[0] = u;
405                         shift3(a, b, c, u + GOLD * (u - cx));
406                         bx = a[0];
407                         cx = b[0];
408                         u = c[0];
409 
410                         a[0] = fb;
411                         b[0] = fc;
412                         c[0] = fu;
413                         shift3(a, b, c, listener.evaluate(u));
414                         fb = a[0];
415                         fc = b[0];
416                         fu = c[0];
417                     }
418                 } else if ((u - ulim) * (ulim - cx) >= 0.0) {
419                     //Limit parabolic u to maximum allowed value.
420                     u = ulim;
421                     fu = listener.evaluate(u);
422                 } else {
423                     //Reject parabolic u, use default magnification
424                     u = cx + GOLD * (cx - bx);
425                     fu = listener.evaluate(u);
426                 }
427                 //Eliminate the oldest point and continue
428                 a[0] = ax;
429                 b[0] = bx;
430                 c[0] = cx;
431                 shift3(a, b, c, u);
432                 ax = a[0];
433                 bx = b[0];
434                 cx = c[0];
435 
436                 a[0] = fa;
437                 b[0] = fb;
438                 c[0] = fc;
439                 shift3(a, b, c, fu);
440                 fa = a[0];
441                 fb = b[0];
442                 fc = c[0];
443             }
444         } catch (final EvaluationException e) {
445             throw new OptimizationException(e);
446         } finally {
447             locked = false;
448         }
449 
450         bracketAvailable = true;
451         bracketEvaluationAvailable = true;
452     }
453 
454     /**
455      * Computes a bracket of values using provided value as a starting point,
456      * and assuming that bracket finishes at Double.MAX_VALUE.
457      * Given a function f, and given distinct initial points ax and bx = 0.0,
458      * this routine searches in the downhill direction (defined by the function
459      * as evaluated at the initial points) and returns
460      * ax (minimum evaluation point), bx (middle evaluation point), cx (maximum
461      * evaluation point) that bracket a minimum of the function. Also returned
462      * are the function values at the three points fa, fb, and fc, which are the
463      * function evaluations at minimum, middle and maximum bracket points.
464      *
465      * @param minEvalPoint Initial minimum evaluation point of bracket.
466      * @throws LockedException              Raised if this instance is locked. This instance
467      *                                      will be locked while doing some operations. Attempting to change any
468      *                                      parameter while being locked will raise this exception.
469      * @throws NotReadyException            Raised if this instance is not ready because a
470      *                                      listener has not yet been provided.
471      * @throws InvalidBracketRangeException Raised if minEvalPoint &lt; 0.0.
472      * @throws OptimizationException        Raised if a bracket couldn't be found
473      *                                      because convergence was not achieved or function evaluation failed.
474      */
475     public void computeBracket(final double minEvalPoint) throws LockedException, NotReadyException,
476             OptimizationException, InvalidBracketRangeException {
477         computeBracket(minEvalPoint, DEFAULT_MIDDLE_EVAL_POINT);
478     }
479 
480     /**
481      * Computes a bracket of values using the whole range of possible values as
482      * an initial guess.
483      * Given a function f, and given distinct initial points ax =
484      * -Double.MAX_VALUE and bx = 0.0, this
485      * routine searches in the downhill direction (defined by the function as
486      * evaluated at the initial points) and returns
487      * ax (minimum evaluation point), bx (middle evaluation point), cx (maximum
488      * evaluation point) that bracket a minimum of the function. Also returned
489      * are the function values at the three points fa, fb, and fc, which are the
490      * function evaluations at minimum, middle and maximum bracket points
491      *
492      * @throws LockedException       Raised if this instance is locked. This instance
493      *                               will be locked while doing some operations. Attempting to change any
494      *                               parameter while being locked will raise this exception.
495      * @throws NotReadyException     Raised if this instance is not ready because a
496      *                               listener has not yet been provided.
497      * @throws OptimizationException Raised if a bracket couldn't be found
498      *                               because convergence was not achieved or function evaluation failed.
499      */
500     public void computeBracket() throws LockedException, NotReadyException, OptimizationException {
501         try {
502             computeBracket(DEFAULT_MIN_EVAL_POINT, DEFAULT_MIDDLE_EVAL_POINT);
503         } catch (InvalidBracketRangeException ignore) {
504             //never happens
505         }
506     }
507 
508     /**
509      * Computes function evaluations at provided or estimated bracket locations.
510      * After calling this method bracket evaluations will be available.
511      *
512      * @throws LockedException       Raised if this instance is locked. This instance
513      *                               will be locked while doing some operations. Attempting to change any
514      *                               parameter while being locked will raise this exception.
515      * @throws NotReadyException     Raised if this instance is not ready because a
516      *                               listener has not yet been provided.
517      * @throws OptimizationException Raised if function evaluation failed.
518      */
519     public void evaluateBracket() throws LockedException, NotReadyException, OptimizationException {
520 
521         if (isLocked()) {
522             throw new LockedException();
523         }
524         if (!isReady()) {
525             throw new NotReadyException();
526         }
527 
528         locked = true;
529 
530         try {
531             fa = listener.evaluate(ax);
532             fb = listener.evaluate(bx);
533             fc = listener.evaluate(cx);
534         } catch (final EvaluationException e) {
535             throw new OptimizationException(e);
536         } finally {
537             locked = false;
538         }
539 
540         bracketEvaluationAvailable = true;
541     }
542 
543     /**
544      * Returns boolean indicating whether bracket evaluations are available for
545      * retrieval.
546      *
547      * @return True if bracket evaluations are available, false otherwise.
548      */
549     public boolean areBracketEvaluationsAvailable() {
550         return bracketEvaluationAvailable;
551     }
552 
553     /**
554      * Internal method to determine whether a and b have the same sign.
555      *
556      * @param a Value to be compared.
557      * @param b Value to be compared.
558      * @return Returns "a" if "a" and "b" have the same sign or "-a" otherwise.
559      */
560     protected double sign(final double a, final double b) {
561         if (b >= 0.0) {
562             return a >= 0.0 ? a : -a;
563         } else {
564             return a >= 0.0 ? -a : a;
565         }
566     }
567 
568     /**
569      * Pushes "b" value into "a", and "c" value into "b". "a" and "b" are in/out parameters.
570      * Results will be available at a[0] and b[0] after executing this method.
571      *
572      * @param a a value to be lost.
573      * @param b a value to be shifted into "a".
574      * @param c a value to be shifted into "b".
575      */
576     protected void shift2(final double[] a, final double[] b, final double c) {
577         a[0] = b[0];
578         b[0] = c;
579     }
580 
581     /**
582      * Pushes "b" value into "a", and "c" value into "b" and "d" value into "c". "a", "b" and "c"
583      * are in/out parameters.
584      * Results will be available at a[0], b[0] and c[0] after executing this
585      * method.
586      *
587      * @param a a value to be lost.
588      * @param b a value to be shifted into "a".
589      * @param c a value to be shifted into "b".
590      * @param d a value to be shifted into "c".
591      */
592     protected void shift3(final double[] a, final double[] b, final double[] c, final double d) {
593         a[0] = b[0];
594         b[0] = c[0];
595         c[0] = d;
596     }
597 
598     /**
599      * Moves d, e and f into a[0], b[0] and c[0]. Previously existing values
600      * into a, b, c will be lost after executing this method.
601      *
602      * @param a a value to be set.
603      * @param b a value to be set.
604      * @param c a value to be set.
605      * @param d a value to be copied.
606      * @param e a value to be copied.
607      * @param f a value to be copied.
608      */
609     protected void mov3(final double[] a, final double[] b, final double[] c, final double d, final double e,
610                         final double f) {
611         a[0] = d;
612         b[0] = e;
613         c[0] = f;
614     }
615 
616     /**
617      * Internal method to swap two values. Value inside a[0] will be swapped
618      * with value provided in b[0].
619      *
620      * @param a Value to be swapped.
621      * @param b Value to be swapped.
622      */
623     private void swap(final double[] a, final double[] b) {
624         final var tmp = a[0];
625         a[0] = b[0];
626         b[0] = tmp;
627     }
628 
629     /**
630      * Internal method to set a bracket of values. This method does not check
631      * whether this instance is locked.
632      *
633      * @param minEvalPoint    Minimum bracket evaluation point.
634      * @param middleEvalPoint Middle bracket evaluation point.
635      * @param maxEvalPoint    Maximum bracket evaluation point.
636      * @throws InvalidBracketRangeException Raised if the following condition is
637      *                                      not met: minEvalPoint &lt;= middleEvalPoint &lt;= maxEvalPoint.
638      */
639     private void internalSetBracket(final double minEvalPoint, final double middleEvalPoint,
640                                     final double maxEvalPoint) throws InvalidBracketRangeException {
641 
642         if ((minEvalPoint > middleEvalPoint) || (middleEvalPoint > maxEvalPoint)) {
643             //which also means || (minEvalPoint > maxEvalPoint))
644             throw new InvalidBracketRangeException();
645         }
646 
647         ax = minEvalPoint;
648         bx = middleEvalPoint;
649         cx = maxEvalPoint;
650 
651         bracketAvailable = true;
652     }
653 }