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 }