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
23 package org.hipparchus.optim.nonlinear.scalar.noderiv;
24
25 import java.util.Arrays;
26 import java.util.Comparator;
27
28 import org.hipparchus.analysis.MultivariateFunction;
29 import org.hipparchus.exception.LocalizedCoreFormats;
30 import org.hipparchus.exception.MathIllegalArgumentException;
31 import org.hipparchus.exception.NullArgumentException;
32 import org.hipparchus.optim.LocalizedOptimFormats;
33 import org.hipparchus.optim.OptimizationData;
34 import org.hipparchus.optim.PointValuePair;
35 import org.hipparchus.util.MathUtils;
36
37 /**
38 * This class implements the simplex concept.
39 * It is intended to be used in conjunction with {@link SimplexOptimizer}.
40 * <br>
41 * The initial configuration of the simplex is set by the constructors
42 * {@link #AbstractSimplex(double[])} or {@link #AbstractSimplex(double[][])}.
43 * The other {@link #AbstractSimplex(int) constructor} will set all steps
44 * to 1, thus building a default configuration from a unit hypercube.
45 * <br>
46 * Users <em>must</em> call the {@link #build(double[]) build} method in order
47 * to create the data structure that will be acted on by the other methods of
48 * this class.
49 *
50 * @see SimplexOptimizer
51 */
52 public abstract class AbstractSimplex implements OptimizationData {
53 /** Simplex. */
54 private PointValuePair[] simplex;
55 /** Start simplex configuration. */
56 private double[][] startConfiguration;
57 /** Simplex dimension (must be equal to {@code simplex.length - 1}). */
58 private final int dimension;
59
60 /**
61 * Build a unit hypercube simplex.
62 *
63 * @param n Dimension of the simplex.
64 */
65 protected AbstractSimplex(int n) {
66 this(n, 1d);
67 }
68
69 /**
70 * Build a hypercube simplex with the given side length.
71 *
72 * @param n Dimension of the simplex.
73 * @param sideLength Length of the sides of the hypercube.
74 */
75 protected AbstractSimplex(int n,
76 double sideLength) {
77 this(createHypercubeSteps(n, sideLength));
78 }
79
80 /**
81 * The start configuration for simplex is built from a box parallel to
82 * the canonical axes of the space. The simplex is the subset of vertices
83 * of a box parallel to the canonical axes. It is built as the path followed
84 * while traveling from one vertex of the box to the diagonally opposite
85 * vertex moving only along the box edges. The first vertex of the box will
86 * be located at the start point of the optimization.
87 * As an example, in dimension 3 a simplex has 4 vertices. Setting the
88 * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the
89 * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }.
90 * The first vertex would be set to the start point at (1, 1, 1) and the
91 * last vertex would be set to the diagonally opposite vertex at (2, 11, 3).
92 *
93 * @param steps Steps along the canonical axes representing box edges. They
94 * may be negative but not zero.
95 * @throws NullArgumentException if {@code steps} is {@code null}.
96 * @throws MathIllegalArgumentException if one of the steps is zero.
97 */
98 protected AbstractSimplex(final double[] steps) {
99 if (steps == null) {
100 throw new NullArgumentException();
101 }
102 if (steps.length == 0) {
103 throw new MathIllegalArgumentException(LocalizedCoreFormats.ZERO_NOT_ALLOWED);
104 }
105 dimension = steps.length;
106
107 // Only the relative position of the n final vertices with respect
108 // to the first one are stored.
109 startConfiguration = new double[dimension][dimension];
110 for (int i = 0; i < dimension; i++) {
111 final double[] vertexI = startConfiguration[i];
112 for (int j = 0; j < i + 1; j++) {
113 if (steps[j] == 0) {
114 throw new MathIllegalArgumentException(LocalizedOptimFormats.EQUAL_VERTICES_IN_SIMPLEX);
115 }
116 System.arraycopy(steps, 0, vertexI, 0, j + 1);
117 }
118 }
119 }
120
121 /**
122 * The real initial simplex will be set up by moving the reference
123 * simplex such that its first point is located at the start point of the
124 * optimization.
125 *
126 * @param referenceSimplex Reference simplex.
127 * @throws MathIllegalArgumentException if the reference simplex does not
128 * contain at least one point.
129 * @throws MathIllegalArgumentException if there is a dimension mismatch
130 * in the reference simplex.
131 * @throws IllegalArgumentException if one of its vertices is duplicated.
132 */
133 protected AbstractSimplex(final double[][] referenceSimplex) {
134 if (referenceSimplex.length <= 0) {
135 throw new MathIllegalArgumentException(LocalizedOptimFormats.SIMPLEX_NEED_ONE_POINT,
136 referenceSimplex.length);
137 }
138 dimension = referenceSimplex.length - 1;
139
140 // Only the relative position of the n final vertices with respect
141 // to the first one are stored.
142 startConfiguration = new double[dimension][dimension];
143 final double[] ref0 = referenceSimplex[0];
144
145 // Loop over vertices.
146 for (int i = 0; i < referenceSimplex.length; i++) {
147 final double[] refI = referenceSimplex[i];
148
149 // Safety checks.
150 if (refI.length != dimension) {
151 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
152 refI.length, dimension);
153 }
154 for (int j = 0; j < i; j++) {
155 final double[] refJ = referenceSimplex[j];
156 boolean allEquals = true;
157 for (int k = 0; k < dimension; k++) {
158 if (refI[k] != refJ[k]) {
159 allEquals = false;
160 break;
161 }
162 }
163 if (allEquals) {
164 throw new MathIllegalArgumentException(LocalizedOptimFormats.EQUAL_VERTICES_IN_SIMPLEX,
165 i, j);
166 }
167 }
168
169 // Store vertex i position relative to vertex 0 position.
170 if (i > 0) {
171 final double[] confI = startConfiguration[i - 1];
172 for (int k = 0; k < dimension; k++) {
173 confI[k] = refI[k] - ref0[k];
174 }
175 }
176 }
177 }
178
179 /**
180 * Get simplex dimension.
181 *
182 * @return the dimension of the simplex.
183 */
184 public int getDimension() {
185 return dimension;
186 }
187
188 /**
189 * Get simplex size.
190 * After calling the {@link #build(double[]) build} method, this method will
191 * will be equivalent to {@code getDimension() + 1}.
192 *
193 * @return the size of the simplex.
194 */
195 public int getSize() {
196 return simplex.length;
197 }
198
199 /**
200 * Compute the next simplex of the algorithm.
201 *
202 * @param evaluationFunction Evaluation function.
203 * @param comparator Comparator to use to sort simplex vertices from best
204 * to worst.
205 * @throws org.hipparchus.exception.MathIllegalStateException
206 * if the algorithm fails to converge.
207 */
208 public abstract void iterate(MultivariateFunction evaluationFunction,
209 Comparator<PointValuePair> comparator);
210
211 /**
212 * Build an initial simplex.
213 *
214 * @param startPoint First point of the simplex.
215 * @throws MathIllegalArgumentException if the start point does not match
216 * simplex dimension.
217 */
218 public void build(final double[] startPoint) {
219 if (dimension != startPoint.length) {
220 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
221 dimension, startPoint.length);
222 }
223
224 // Set first vertex.
225 simplex = new PointValuePair[dimension + 1];
226 simplex[0] = new PointValuePair(startPoint, Double.NaN);
227
228 // Set remaining vertices.
229 for (int i = 0; i < dimension; i++) {
230 final double[] confI = startConfiguration[i];
231 final double[] vertexI = new double[dimension];
232 for (int k = 0; k < dimension; k++) {
233 vertexI[k] = startPoint[k] + confI[k];
234 }
235 simplex[i + 1] = new PointValuePair(vertexI, Double.NaN);
236 }
237 }
238
239 /**
240 * Evaluate all the non-evaluated points of the simplex.
241 *
242 * @param evaluationFunction Evaluation function.
243 * @param comparator Comparator to use to sort simplex vertices from best to worst.
244 * @throws org.hipparchus.exception.MathIllegalStateException
245 * if the maximal number of evaluations is exceeded.
246 */
247 public void evaluate(final MultivariateFunction evaluationFunction,
248 final Comparator<PointValuePair> comparator) {
249 // Evaluate the objective function at all non-evaluated simplex points.
250 for (int i = 0; i < simplex.length; i++) {
251 final PointValuePair vertex = simplex[i];
252 final double[] point = vertex.getPointRef();
253 if (Double.isNaN(vertex.getValue())) {
254 simplex[i] = new PointValuePair(point, evaluationFunction.value(point), false);
255 }
256 }
257
258 // Sort the simplex from best to worst.
259 Arrays.sort(simplex, comparator);
260 }
261
262 /**
263 * Replace the worst point of the simplex by a new point.
264 *
265 * @param pointValuePair Point to insert.
266 * @param comparator Comparator to use for sorting the simplex vertices
267 * from best to worst.
268 */
269 protected void replaceWorstPoint(PointValuePair pointValuePair,
270 final Comparator<PointValuePair> comparator) {
271 for (int i = 0; i < dimension; i++) {
272 if (comparator.compare(simplex[i], pointValuePair) > 0) {
273 PointValuePair tmp = simplex[i];
274 simplex[i] = pointValuePair;
275 pointValuePair = tmp;
276 }
277 }
278 simplex[dimension] = pointValuePair;
279 }
280
281 /**
282 * Get the points of the simplex.
283 *
284 * @return all the simplex points.
285 */
286 public PointValuePair[] getPoints() {
287 final PointValuePair[] copy = new PointValuePair[simplex.length];
288 System.arraycopy(simplex, 0, copy, 0, simplex.length);
289 return copy;
290 }
291
292 /**
293 * Get the simplex point stored at the requested {@code index}.
294 *
295 * @param index Location.
296 * @return the point at location {@code index}.
297 */
298 public PointValuePair getPoint(int index) {
299 MathUtils.checkRangeInclusive(index, 0, simplex.length - 1);
300 return simplex[index];
301 }
302
303 /**
304 * Store a new point at location {@code index}.
305 * Note that no deep-copy of {@code point} is performed.
306 *
307 * @param index Location.
308 * @param point New value.
309 */
310 protected void setPoint(int index, PointValuePair point) {
311 MathUtils.checkRangeInclusive(index, 0, simplex.length - 1);
312 simplex[index] = point;
313 }
314
315 /**
316 * Replace all points.
317 * Note that no deep-copy of {@code points} is performed.
318 *
319 * @param points New Points.
320 */
321 protected void setPoints(PointValuePair[] points) {
322 if (points.length != simplex.length) {
323 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
324 points.length, simplex.length);
325 }
326 simplex = points.clone();
327 }
328
329 /**
330 * Create steps for a unit hypercube.
331 *
332 * @param n Dimension of the hypercube.
333 * @param sideLength Length of the sides of the hypercube.
334 * @return the steps.
335 */
336 private static double[] createHypercubeSteps(int n,
337 double sideLength) {
338 final double[] steps = new double[n];
339 for (int i = 0; i < n; i++) {
340 steps[i] = sideLength;
341 }
342 return steps;
343 }
344 }