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;
23
24 import org.hipparchus.exception.LocalizedCoreFormats;
25 import org.hipparchus.exception.MathIllegalArgumentException;
26 import org.hipparchus.exception.MathIllegalStateException;
27 import org.hipparchus.random.RandomVectorGenerator;
28
29 /**
30 * Base class multi-start optimizer for a multivariate function.
31 * <br>
32 * This class wraps an optimizer in order to use it several times in
33 * turn with different starting points (trying to avoid being trapped
34 * in a local extremum when looking for a global one).
35 * <em>It is not a "user" class.</em>
36 *
37 * @param <P> Type of the point/value pair returned by the optimization
38 * algorithm.
39 *
40 */
41 public abstract class BaseMultiStartMultivariateOptimizer<P>
42 extends BaseMultivariateOptimizer<P> {
43 /** Underlying classical optimizer. */
44 private final BaseMultivariateOptimizer<P> optimizer;
45 /** Number of evaluations already performed for all starts. */
46 private int totalEvaluations;
47 /** Number of starts to go. */
48 private int starts;
49 /** Random generator for multi-start. */
50 private RandomVectorGenerator generator;
51 /** Optimization data. */
52 private OptimizationData[] optimData;
53 /**
54 * Location in {@link #optimData} where the updated maximum
55 * number of evaluations will be stored.
56 */
57 private int maxEvalIndex = -1;
58 /**
59 * Location in {@link #optimData} where the updated start value
60 * will be stored.
61 */
62 private int initialGuessIndex = -1;
63
64 /**
65 * Create a multi-start optimizer from a single-start optimizer.
66 * <p>
67 * Note that if there are bounds constraints (see {@link #getLowerBound()}
68 * and {@link #getUpperBound()}), then a simple rejection algorithm is used
69 * at each restart. This implies that the random vector generator should have
70 * a good probability to generate vectors in the bounded domain, otherwise the
71 * rejection algorithm will hit the {@link #getMaxEvaluations()} count without
72 * generating a proper restart point. Users must be take great care of the <a
73 * href="http://en.wikipedia.org/wiki/Curse_of_dimensionality">curse of dimensionality</a>.
74 * </p>
75 * @param optimizer Single-start optimizer to wrap.
76 * @param starts Number of starts to perform. If {@code starts == 1},
77 * the {@link #optimize(OptimizationData[]) optimize} will return the
78 * same solution as the given {@code optimizer} would return.
79 * @param generator Random vector generator to use for restarts.
80 * @throws MathIllegalArgumentException if {@code starts < 1}.
81 */
82 protected BaseMultiStartMultivariateOptimizer(final BaseMultivariateOptimizer<P> optimizer, final int starts,
83 final RandomVectorGenerator generator) {
84 super(optimizer.getConvergenceChecker());
85
86 if (starts < 1) {
87 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
88 starts, 1);
89 }
90
91 this.optimizer = optimizer;
92 this.starts = starts;
93 this.generator = generator;
94 }
95
96 /** {@inheritDoc} */
97 @Override
98 public int getEvaluations() {
99 return totalEvaluations;
100 }
101
102 /**
103 * Gets all the optima found during the last call to {@code optimize}.
104 * The optimizer stores all the optima found during a set of
105 * restarts. The {@code optimize} method returns the best point only.
106 * This method returns all the points found at the end of each starts,
107 * including the best one already returned by the {@code optimize} method.
108 * <br>
109 * The returned array as one element for each start as specified
110 * in the constructor. It is ordered with the results from the
111 * runs that did converge first, sorted from best to worst
112 * objective value (i.e in ascending order if minimizing and in
113 * descending order if maximizing), followed by {@code null} elements
114 * corresponding to the runs that did not converge. This means all
115 * elements will be {@code null} if the {@code optimize} method did throw
116 * an exception.
117 * This also means that if the first element is not {@code null}, it is
118 * the best point found across all starts.
119 * <br>
120 * The behaviour is undefined if this method is called before
121 * {@code optimize}; it will likely throw {@code NullPointerException}.
122 *
123 * @return an array containing the optima sorted from best to worst.
124 */
125 public abstract P[] getOptima();
126
127 /**
128 * {@inheritDoc}
129 *
130 * @throws MathIllegalStateException if {@code optData} does not contain an
131 * instance of {@link MaxEval} or {@link InitialGuess}.
132 */
133 @Override
134 public P optimize(OptimizationData... optData) {
135 // Store arguments in order to pass them to the internal optimizer.
136 optimData = optData.clone();
137 // Set up base class and perform computations.
138 return super.optimize(optData);
139 }
140
141 /** {@inheritDoc} */
142 @Override
143 protected P doOptimize() {
144 // Remove all instances of "MaxEval" and "InitialGuess" from the
145 // array that will be passed to the internal optimizer.
146 // The former is to enforce smaller numbers of allowed evaluations
147 // (according to how many have been used up already), and the latter
148 // to impose a different start value for each start.
149 for (int i = 0; i < optimData.length; i++) {
150 if (optimData[i] instanceof MaxEval) {
151 optimData[i] = null;
152 maxEvalIndex = i;
153 }
154 if (optimData[i] instanceof InitialGuess) {
155 optimData[i] = null;
156 initialGuessIndex = i;
157 continue;
158 }
159 }
160 if (maxEvalIndex == -1) {
161 throw new MathIllegalStateException(LocalizedCoreFormats.ILLEGAL_STATE);
162 }
163 if (initialGuessIndex == -1) {
164 throw new MathIllegalStateException(LocalizedCoreFormats.ILLEGAL_STATE);
165 }
166
167 RuntimeException lastException = null;
168 totalEvaluations = 0;
169 clear();
170
171 final int maxEval = getMaxEvaluations();
172 final double[] min = getLowerBound();
173 final double[] max = getUpperBound();
174 final double[] startPoint = getStartPoint();
175
176 // Multi-start loop.
177 for (int i = 0; i < starts; i++) {
178 // CHECKSTYLE: stop IllegalCatch
179 try {
180 // Decrease number of allowed evaluations.
181 optimData[maxEvalIndex] = new MaxEval(maxEval - totalEvaluations);
182 // New start value.
183 double[] s = null;
184 if (i == 0) {
185 s = startPoint;
186 } else {
187 int attempts = 0;
188 while (s == null) {
189 if (attempts >= getMaxEvaluations()) {
190 throw new MathIllegalStateException(LocalizedCoreFormats.MAX_COUNT_EXCEEDED,
191 getMaxEvaluations());
192 }
193 s = generator.nextVector();
194 for (int k = 0; s != null && k < s.length; ++k) {
195 if ((min != null && s[k] < min[k]) || (max != null && s[k] > max[k])) {
196 // reject the vector
197 s = null;
198 }
199 }
200 ++attempts;
201 }
202 }
203 optimData[initialGuessIndex] = new InitialGuess(s);
204 // Optimize.
205 final P result = optimizer.optimize(optimData);
206 store(result);
207 } catch (RuntimeException mue) { // NOPMD - caching a RuntimeException is intentional here, it will be rethrown later
208 lastException = mue;
209 }
210 // CHECKSTYLE: resume IllegalCatch
211
212 totalEvaluations += optimizer.getEvaluations();
213 }
214
215 final P[] optima = getOptima();
216 if (optima.length == 0) {
217 // All runs failed.
218 throw lastException; // Cannot be null if starts >= 1.
219 }
220
221 // Return the best optimum.
222 return optima[0];
223 }
224
225 /**
226 * Method that will be called in order to store each found optimum.
227 *
228 * @param optimum Result of an optimization run.
229 */
230 protected abstract void store(P optimum);
231 /**
232 * Method that will called in order to clear all stored optima.
233 */
234 protected abstract void clear();
235 }