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.DirectionalDerivativeEvaluator;
19  import com.irurueta.numerical.GradientFunctionEvaluatorListener;
20  import com.irurueta.numerical.LockedException;
21  import com.irurueta.numerical.MultiDimensionFunctionEvaluatorListener;
22  import com.irurueta.numerical.NotAvailableException;
23  import com.irurueta.numerical.NumericalException;
24  
25  /**
26   * Class to find a minimum on a multidimensional function along a given line
27   * of input values. The difference between this abstract class and
28   * LineMultiOptimizer is that subclasses of this class will use the function
29   * gradient information. By using gradient information, typically convergence is
30   * faster, although, if gradient does not have a closed expression, then a
31   * GradientEstimator will be needed in the gradient listener provided to this
32   * class. Notice that a GradientEstimator might estimate gradients with a
33   * certain error, so depending on the function topology, LineMultiOptimizer
34   * subclasses might obtain greater accuracy than subclasses of this class.
35   */
36  public abstract class DerivativeLineMultiOptimizer extends MultiOptimizer {
37      /**
38       * n-dimensional point containing a minimum in a given line.
39       */
40      protected double[] p;
41  
42      /**
43       * Direction to make the search.
44       */
45      protected double[] xi;
46  
47      /**
48       * Number of dimensions on function being evaluated.
49       */
50      private int n;
51  
52      /**
53       * Listener to evaluate the function's gradient. If the function's
54       * gradient is not know (e.g. does not have a closed expression), then
55       * a GradientEstimator might be used inside the listener implementation.
56       */
57      protected GradientFunctionEvaluatorListener gradientListener;
58  
59      /**
60       * Class in charge of evaluating a function through a given line.
61       */
62      private DirectionalDerivativeEvaluator evaluator;
63  
64      /**
65       * Internal optimizer to find a minimum of a function along a line of
66       * input values. Hence, input is converted to a single dimension using a
67       * DirectionalEvaluator.
68       */
69      private DerivativeBrentSingleOptimizer dbrent;
70  
71      /**
72       * Empty constructor.
73       */
74      protected DerivativeLineMultiOptimizer() {
75          super();
76          p = xi = null;
77          n = 0;
78          gradientListener = null;
79          evaluator = null;
80          dbrent = null;
81      }
82  
83      /**
84       * Constructor.
85       *
86       * @param listener         Listener to evaluate a multi-dimension function.
87       * @param gradientListener Listener to evaluate the function's gradient.
88       * @param point            Start point where algorithm will be started. Start point
89       *                         should be close to the local minimum to be found. Provided array must
90       *                         have a length equal to the number of dimensions of the function being
91       *                         evaluated, otherwise and exception will be raised when searching for the
92       *                         minimum.
93       * @param direction        Direction to start looking for a minimum. Provided array
94       *                         must have the same length as the number of dimensions of the function
95       *                         being evaluated. Provided direction is considered as a vector pointing
96       *                         to the minimum to be found.
97       * @throws IllegalArgumentException Raised if provided point and direction
98       *                                  don't have the same length.
99       */
100     protected DerivativeLineMultiOptimizer(
101             final MultiDimensionFunctionEvaluatorListener listener,
102             final GradientFunctionEvaluatorListener gradientListener, final double[] point, final double[] direction) {
103         super(listener);
104         internalSetStartPointAndDirection(point, direction);
105         n = 0;
106         this.gradientListener = gradientListener;
107         evaluator = null;
108         dbrent = null;
109     }
110 
111     /**
112      * Returns boolean indicating whether start point has already been provided
113      * and is ready for retrieval.
114      *
115      * @return True if available, false otherwise.
116      */
117 
118     public boolean isStartPointAvailable() {
119         return p != null;
120     }
121 
122     /**
123      * Returns start point where algorithm will be started. Start point should
124      * be close to the local minimum to be found.
125      *
126      * @return Start point where algorithm will be started.
127      * @throws NotAvailableException Raised if start point has not yet been
128      *                               provided and is not available.
129      */
130     public double[] getStartPoint() throws NotAvailableException {
131         if (!isStartPointAvailable()) {
132             throw new NotAvailableException();
133         }
134         return p;
135     }
136 
137     /**
138      * Returns boolean indicating whether direction has already been provided
139      * and is ready for retrieval.
140      *
141      * @return True if available, false otherwise.
142      */
143     public boolean isDirectionAvailable() {
144         return xi != null;
145     }
146 
147     /**
148      * Returns direction to start looking for a minimum. Provided array must
149      * have the same length as the number of dimensions of the function being
150      * evaluated. Provided direction is considered as a vector pointing to the
151      * minimum to be found.
152      *
153      * @return Direction to start looking for a minimum.
154      * @throws NotAvailableException Raised if direction has not yet been
155      *                               provided and is not available.
156      */
157     public double[] getDirection() throws NotAvailableException {
158         if (!isDirectionAvailable()) {
159             throw new NotAvailableException();
160         }
161         return xi;
162     }
163 
164     /**
165      * Internal method to set start point and direction to start the search for
166      * a local minimum.
167      * This method does not check whether this instance is locked.
168      *
169      * @param point     Start point where algorithm will be started. Start point
170      *                  should be close to the local minimum to be found. Provided array must
171      *                  have a length equal to the number of dimensions of the function being
172      *                  evaluated, otherwise and exception will be raised when searching for the
173      *                  minimum.
174      * @param direction Direction to start looking for a minimum. Provided array
175      *                  must have the same length as the number of dimensions of the function
176      *                  being evaluated. Provided direction is considered as a vector pointing
177      *                  to the minimum to be found.
178      * @throws IllegalArgumentException Raised if provided point and direction
179      *                                  don't have the same length.
180      */
181     private void internalSetStartPointAndDirection(final double[] point, final double[] direction) {
182         if (point.length != direction.length) {
183             throw new IllegalArgumentException();
184         }
185 
186         p = point;
187         xi = direction;
188     }
189 
190     /**
191      * Internal method to set start point and direction to start the search for
192      * a local minimum.
193      *
194      * @param point     Start point where algorithm will be started. Start point
195      *                  should be close to the local minimum to be found. Provided array must
196      *                  have a length equal to the number of dimensions of the function being
197      *                  evaluated, otherwise and exception will be raised when searching for the
198      *                  minimum.
199      * @param direction Direction to start looking for a minimum. Provided array
200      *                  must have the same length as the number of dimensions of the function
201      *                  being evaluated. Provided direction is considered as a vector pointing
202      *                  to the minimum to be found.
203      * @throws LockedException          Raised if this instance is locked.
204      * @throws IllegalArgumentException Raised if provided point and direction
205      *                                  don't have the same length.
206      */
207     public void setStartPointAndDirection(final double[] point, final double[] direction) throws LockedException {
208         if (isLocked()) {
209             throw new LockedException();
210         }
211         internalSetStartPointAndDirection(point, direction);
212     }
213 
214     /**
215      * Returns boolean indicating whether this instance is considered to be
216      * ready to start the estimation of a minimum.
217      * This instance is considered to be ready once a listener, gradient
218      * listener, start point and direction are provided.
219      *
220      * @return True if this instance is ready, false otherwise.
221      */
222     @Override
223     public boolean isReady() {
224         return isListenerAvailable() && isGradientListenerAvailable() && isStartPointAvailable()
225                 && isDirectionAvailable();
226     }
227 
228     /**
229      * Returns gradient listener.
230      * The gradient listener is used to evaluate the function's gradient. If the
231      * function's gradient is not know (e.g. does not have a closed expression),
232      * then a GradientEstimator might be used inside the listener
233      * implementation.
234      *
235      * @return Gradient listener.
236      * @throws NotAvailableException Raised if gradient listener is not
237      *                               available for retrieval.
238      */
239     public GradientFunctionEvaluatorListener getGradientListener() throws NotAvailableException {
240         if (!isGradientListenerAvailable()) {
241             throw new NotAvailableException();
242         }
243         return gradientListener;
244     }
245 
246     /**
247      * Sets gradient listener.
248      * The gradient listener is used to evaluate the function's gradient. If the
249      * function's gradient is not know (e.g. does not have a closed expression),
250      * then a GradientEstimator might be used inside the listener
251      * implementation.
252      *
253      * @param gradientListener Gradient listener.
254      * @throws LockedException Raised if this instance is locked.
255      */
256     public void setGradientListener(final GradientFunctionEvaluatorListener gradientListener) throws LockedException {
257         if (isLocked()) {
258             throw new LockedException();
259         }
260         this.gradientListener = gradientListener;
261     }
262 
263     /**
264      * Returns boolean indicating whether the gradient listener has been
265      * provided and is available for retrieval.
266      *
267      * @return True if available, false otherwise.
268      */
269     public boolean isGradientListenerAvailable() {
270         return gradientListener != null;
271     }
272 
273     /**
274      * Searches for a minimum along a given line of input values.
275      * The line being searched is obtained by using a start point and direction.
276      *
277      * @return Returns function evaluation at minimum that has been found.
278      */
279     @SuppressWarnings("Duplicates")
280     protected double linmin() {
281         final double ax;
282         final double xx;
283         final double linxmin;
284         n = p.length;
285 
286         if (evaluator == null) {
287             //attempt to reuse evaluator
288             evaluator = new DirectionalDerivativeEvaluator(listener, gradientListener, p, xi);
289         }
290         if (evaluator.getListener() != listener) {
291             //update listener
292             evaluator.setListener(listener);
293         }
294         if (evaluator.getGradientListener() != gradientListener) {
295             //update gradient listener
296             evaluator.setGradientListener(gradientListener);
297         }
298         if (evaluator.getPoint() != p || evaluator.getDirection() != xi) {
299             evaluator.setPointAndDirection(p, xi);
300         }
301 
302         ax = 0.0;
303         xx = 1.0;
304 
305         try {
306             if (dbrent == null) {
307                 // attempt to reuse brent single optimizer
308                 dbrent = new DerivativeBrentSingleOptimizer(
309                         point -> evaluator.evaluateAt(point),
310                         point -> evaluator.differentiateAt(point),
311                         BracketedSingleOptimizer.DEFAULT_MIN_EVAL_POINT,
312                         BracketedSingleOptimizer.DEFAULT_MIDDLE_EVAL_POINT,
313                         BracketedSingleOptimizer.DEFAULT_MAX_EVAL_POINT,
314                         DerivativeBrentSingleOptimizer.DEFAULT_TOLERANCE);
315             }
316 
317             dbrent.computeBracket(ax, xx);
318             dbrent.minimize();
319             linxmin = dbrent.getResult();
320 
321             for (var j = 0; j < n; j++) {
322                 xi[j] *= linxmin;
323                 p[j] += xi[j];
324             }
325 
326             return dbrent.getEvaluationAtResult();
327         } catch (final NumericalException t) {
328             //if minimization fails we assume that obtained result is the worst
329             //possible one
330             return Double.MAX_VALUE;
331         }
332     }
333 }