001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.math.optimization.direct;
019    
020    import java.util.Arrays;
021    import java.util.Comparator;
022    
023    import org.apache.commons.math.FunctionEvaluationException;
024    import org.apache.commons.math.MathRuntimeException;
025    import org.apache.commons.math.MaxEvaluationsExceededException;
026    import org.apache.commons.math.MaxIterationsExceededException;
027    import org.apache.commons.math.analysis.MultivariateRealFunction;
028    import org.apache.commons.math.exception.util.LocalizedFormats;
029    import org.apache.commons.math.optimization.GoalType;
030    import org.apache.commons.math.optimization.MultivariateRealOptimizer;
031    import org.apache.commons.math.optimization.OptimizationException;
032    import org.apache.commons.math.optimization.RealConvergenceChecker;
033    import org.apache.commons.math.optimization.RealPointValuePair;
034    import org.apache.commons.math.optimization.SimpleScalarValueChecker;
035    
036    /**
037     * This class implements simplex-based direct search optimization
038     * algorithms.
039     *
040     * <p>Direct search methods only use objective function values, they don't
041     * need derivatives and don't either try to compute approximation of
042     * the derivatives. According to a 1996 paper by Margaret H. Wright
043     * (<a href="http://cm.bell-labs.com/cm/cs/doc/96/4-02.ps.gz">Direct
044     * Search Methods: Once Scorned, Now Respectable</a>), they are used
045     * when either the computation of the derivative is impossible (noisy
046     * functions, unpredictable discontinuities) or difficult (complexity,
047     * computation cost). In the first cases, rather than an optimum, a
048     * <em>not too bad</em> point is desired. In the latter cases, an
049     * optimum is desired but cannot be reasonably found. In all cases
050     * direct search methods can be useful.</p>
051     *
052     * <p>Simplex-based direct search methods are based on comparison of
053     * the objective function values at the vertices of a simplex (which is a
054     * set of n+1 points in dimension n) that is updated by the algorithms
055     * steps.<p>
056     *
057     * <p>The initial configuration of the simplex can be set using either
058     * {@link #setStartConfiguration(double[])} or {@link
059     * #setStartConfiguration(double[][])}. If neither method has been called
060     * before optimization is attempted, an explicit call to the first method
061     * with all steps set to +1 is triggered, thus building a default
062     * configuration from a unit hypercube. Each call to {@link
063     * #optimize(MultivariateRealFunction, GoalType, double[]) optimize} will reuse
064     * the current start configuration and move it such that its first vertex
065     * is at the provided start point of the optimization. If the {@code optimize}
066     * method is called to solve a different problem and the number of parameters
067     * change, the start configuration will be reset to a default one with the
068     * appropriate dimensions.</p>
069     *
070     * <p>If {@link #setConvergenceChecker(RealConvergenceChecker)} is not called,
071     * a default {@link SimpleScalarValueChecker} is used.</p>
072     *
073     * <p>Convergence is checked by providing the <em>worst</em> points of
074     * previous and current simplex to the convergence checker, not the best ones.</p>
075     *
076     * <p>This class is the base class performing the boilerplate simplex
077     * initialization and handling. The simplex update by itself is
078     * performed by the derived classes according to the implemented
079     * algorithms.</p>
080     *
081     * implements MultivariateRealOptimizer since 2.0
082     *
083     * @see MultivariateRealFunction
084     * @see NelderMead
085     * @see MultiDirectional
086     * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 f??vr. 2011) $
087     * @since 1.2
088     */
089    public abstract class DirectSearchOptimizer implements MultivariateRealOptimizer {
090    
091        /** Simplex. */
092        protected RealPointValuePair[] simplex;
093    
094        /** Objective function. */
095        private MultivariateRealFunction f;
096    
097        /** Convergence checker. */
098        private RealConvergenceChecker checker;
099    
100        /** Maximal number of iterations allowed. */
101        private int maxIterations;
102    
103        /** Number of iterations already performed. */
104        private int iterations;
105    
106        /** Maximal number of evaluations allowed. */
107        private int maxEvaluations;
108    
109        /** Number of evaluations already performed. */
110        private int evaluations;
111    
112        /** Start simplex configuration. */
113        private double[][] startConfiguration;
114    
115        /** Simple constructor.
116         */
117        protected DirectSearchOptimizer() {
118            setConvergenceChecker(new SimpleScalarValueChecker());
119            setMaxIterations(Integer.MAX_VALUE);
120            setMaxEvaluations(Integer.MAX_VALUE);
121        }
122    
123        /** Set start configuration for simplex.
124         * <p>The start configuration for simplex is built from a box parallel to
125         * the canonical axes of the space. The simplex is the subset of vertices
126         * of a box parallel to the canonical axes. It is built as the path followed
127         * while traveling from one vertex of the box to the diagonally opposite
128         * vertex moving only along the box edges. The first vertex of the box will
129         * be located at the start point of the optimization.</p>
130         * <p>As an example, in dimension 3 a simplex has 4 vertices. Setting the
131         * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the
132         * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }.
133         * The first vertex would be set to the start point at (1, 1, 1) and the
134         * last vertex would be set to the diagonally opposite vertex at (2, 11, 3).</p>
135         * @param steps steps along the canonical axes representing box edges,
136         * they may be negative but not null
137         * @exception IllegalArgumentException if one step is null
138         */
139        public void setStartConfiguration(final double[] steps)
140            throws IllegalArgumentException {
141            // only the relative position of the n final vertices with respect
142            // to the first one are stored
143            final int n = steps.length;
144            startConfiguration = new double[n][n];
145            for (int i = 0; i < n; ++i) {
146                final double[] vertexI = startConfiguration[i];
147                for (int j = 0; j < i + 1; ++j) {
148                    if (steps[j] == 0.0) {
149                        throw MathRuntimeException.createIllegalArgumentException(
150                              LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, j, j + 1);
151                    }
152                    System.arraycopy(steps, 0, vertexI, 0, j + 1);
153                }
154            }
155        }
156    
157        /** Set start configuration for simplex.
158         * <p>The real initial simplex will be set up by moving the reference
159         * simplex such that its first point is located at the start point of the
160         * optimization.</p>
161         * @param referenceSimplex reference simplex
162         * @exception IllegalArgumentException if the reference simplex does not
163         * contain at least one point, or if there is a dimension mismatch
164         * in the reference simplex or if one of its vertices is duplicated
165         */
166        public void setStartConfiguration(final double[][] referenceSimplex)
167            throws IllegalArgumentException {
168    
169            // only the relative position of the n final vertices with respect
170            // to the first one are stored
171            final int n = referenceSimplex.length - 1;
172            if (n < 0) {
173                throw MathRuntimeException.createIllegalArgumentException(
174                        LocalizedFormats.SIMPLEX_NEED_ONE_POINT);
175            }
176            startConfiguration = new double[n][n];
177            final double[] ref0 = referenceSimplex[0];
178    
179            // vertices loop
180            for (int i = 0; i < n + 1; ++i) {
181    
182                final double[] refI = referenceSimplex[i];
183    
184                // safety checks
185                if (refI.length != n) {
186                    throw MathRuntimeException.createIllegalArgumentException(
187                          LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, refI.length, n);
188                }
189                for (int j = 0; j < i; ++j) {
190                    final double[] refJ = referenceSimplex[j];
191                    boolean allEquals = true;
192                    for (int k = 0; k < n; ++k) {
193                        if (refI[k] != refJ[k]) {
194                            allEquals = false;
195                            break;
196                        }
197                    }
198                    if (allEquals) {
199                        throw MathRuntimeException.createIllegalArgumentException(
200                              LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, i, j);
201                    }
202                }
203    
204                // store vertex i position relative to vertex 0 position
205                if (i > 0) {
206                    final double[] confI = startConfiguration[i - 1];
207                    for (int k = 0; k < n; ++k) {
208                        confI[k] = refI[k] - ref0[k];
209                    }
210                }
211    
212            }
213    
214        }
215    
216        /** {@inheritDoc} */
217        public void setMaxIterations(int maxIterations) {
218            this.maxIterations = maxIterations;
219        }
220    
221        /** {@inheritDoc} */
222        public int getMaxIterations() {
223            return maxIterations;
224        }
225    
226        /** {@inheritDoc} */
227        public void setMaxEvaluations(int maxEvaluations) {
228            this.maxEvaluations = maxEvaluations;
229        }
230    
231        /** {@inheritDoc} */
232        public int getMaxEvaluations() {
233            return maxEvaluations;
234        }
235    
236        /** {@inheritDoc} */
237        public int getIterations() {
238            return iterations;
239        }
240    
241        /** {@inheritDoc} */
242        public int getEvaluations() {
243            return evaluations;
244        }
245    
246        /** {@inheritDoc} */
247        public void setConvergenceChecker(RealConvergenceChecker convergenceChecker) {
248            this.checker = convergenceChecker;
249        }
250    
251        /** {@inheritDoc} */
252        public RealConvergenceChecker getConvergenceChecker() {
253            return checker;
254        }
255    
256        /** {@inheritDoc} */
257        public RealPointValuePair optimize(final MultivariateRealFunction function,
258                                           final GoalType goalType,
259                                           final double[] startPoint)
260            throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
261    
262            if ((startConfiguration == null) ||
263                (startConfiguration.length != startPoint.length)) {
264                // no initial configuration has been set up for simplex
265                // build a default one from a unit hypercube
266                final double[] unit = new double[startPoint.length];
267                Arrays.fill(unit, 1.0);
268                setStartConfiguration(unit);
269            }
270    
271            this.f = function;
272            final Comparator<RealPointValuePair> comparator =
273                new Comparator<RealPointValuePair>() {
274                    public int compare(final RealPointValuePair o1,
275                                       final RealPointValuePair o2) {
276                        final double v1 = o1.getValue();
277                        final double v2 = o2.getValue();
278                        return (goalType == GoalType.MINIMIZE) ?
279                                Double.compare(v1, v2) : Double.compare(v2, v1);
280                    }
281                };
282    
283            // initialize search
284            iterations  = 0;
285            evaluations = 0;
286            buildSimplex(startPoint);
287            evaluateSimplex(comparator);
288    
289            RealPointValuePair[] previous = new RealPointValuePair[simplex.length];
290            while (true) {
291    
292                if (iterations > 0) {
293                    boolean converged = true;
294                    for (int i = 0; i < simplex.length; ++i) {
295                        converged &= checker.converged(iterations, previous[i], simplex[i]);
296                    }
297                    if (converged) {
298                        // we have found an optimum
299                        return simplex[0];
300                    }
301                }
302    
303                // we still need to search
304                System.arraycopy(simplex, 0, previous, 0, simplex.length);
305                iterateSimplex(comparator);
306    
307            }
308    
309        }
310    
311        /** Increment the iterations counter by 1.
312         * @exception OptimizationException if the maximal number
313         * of iterations is exceeded
314         */
315        protected void incrementIterationsCounter()
316            throws OptimizationException {
317            if (++iterations > maxIterations) {
318                throw new OptimizationException(new MaxIterationsExceededException(maxIterations));
319            }
320        }
321    
322        /** Compute the next simplex of the algorithm.
323         * @param comparator comparator to use to sort simplex vertices from best to worst
324         * @exception FunctionEvaluationException if the function cannot be evaluated at
325         * some point
326         * @exception OptimizationException if the algorithm fails to converge
327         * @exception IllegalArgumentException if the start point dimension is wrong
328         */
329        protected abstract void iterateSimplex(final Comparator<RealPointValuePair> comparator)
330            throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
331    
332        /** Evaluate the objective function on one point.
333         * <p>A side effect of this method is to count the number of
334         * function evaluations</p>
335         * @param x point on which the objective function should be evaluated
336         * @return objective function value at the given point
337         * @exception FunctionEvaluationException if no value can be computed for the
338         * parameters or if the maximal number of evaluations is exceeded
339         * @exception IllegalArgumentException if the start point dimension is wrong
340         */
341        protected double evaluate(final double[] x)
342            throws FunctionEvaluationException, IllegalArgumentException {
343            if (++evaluations > maxEvaluations) {
344                throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations), x);
345            }
346            return f.value(x);
347        }
348    
349        /** Build an initial simplex.
350         * @param startPoint the start point for optimization
351         * @exception IllegalArgumentException if the start point does not match
352         * simplex dimension
353         */
354        private void buildSimplex(final double[] startPoint)
355            throws IllegalArgumentException {
356    
357            final int n = startPoint.length;
358            if (n != startConfiguration.length) {
359                throw MathRuntimeException.createIllegalArgumentException(
360                      LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, n, startConfiguration.length);
361            }
362    
363            // set first vertex
364            simplex = new RealPointValuePair[n + 1];
365            simplex[0] = new RealPointValuePair(startPoint, Double.NaN);
366    
367            // set remaining vertices
368            for (int i = 0; i < n; ++i) {
369                final double[] confI   = startConfiguration[i];
370                final double[] vertexI = new double[n];
371                for (int k = 0; k < n; ++k) {
372                    vertexI[k] = startPoint[k] + confI[k];
373                }
374                simplex[i + 1] = new RealPointValuePair(vertexI, Double.NaN);
375            }
376    
377        }
378    
379        /** Evaluate all the non-evaluated points of the simplex.
380         * @param comparator comparator to use to sort simplex vertices from best to worst
381         * @exception FunctionEvaluationException if no value can be computed for the parameters
382         * @exception OptimizationException if the maximal number of evaluations is exceeded
383         */
384        protected void evaluateSimplex(final Comparator<RealPointValuePair> comparator)
385            throws FunctionEvaluationException, OptimizationException {
386    
387            // evaluate the objective function at all non-evaluated simplex points
388            for (int i = 0; i < simplex.length; ++i) {
389                final RealPointValuePair vertex = simplex[i];
390                final double[] point = vertex.getPointRef();
391                if (Double.isNaN(vertex.getValue())) {
392                    simplex[i] = new RealPointValuePair(point, evaluate(point), false);
393                }
394            }
395    
396            // sort the simplex from best to worst
397            Arrays.sort(simplex, comparator);
398    
399        }
400    
401        /** Replace the worst point of the simplex by a new point.
402         * @param pointValuePair point to insert
403         * @param comparator comparator to use to sort simplex vertices from best to worst
404         */
405        protected void replaceWorstPoint(RealPointValuePair pointValuePair,
406                                         final Comparator<RealPointValuePair> comparator) {
407            int n = simplex.length - 1;
408            for (int i = 0; i < n; ++i) {
409                if (comparator.compare(simplex[i], pointValuePair) > 0) {
410                    RealPointValuePair tmp = simplex[i];
411                    simplex[i]         = pointValuePair;
412                    pointValuePair     = tmp;
413                }
414            }
415            simplex[n] = pointValuePair;
416        }
417    
418    }