| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.math.optimization.direct; |
| |
| import java.util.Arrays; |
| import java.util.Comparator; |
| |
| import org.apache.commons.math.FunctionEvaluationException; |
| import org.apache.commons.math.MathRuntimeException; |
| import org.apache.commons.math.MaxEvaluationsExceededException; |
| import org.apache.commons.math.MaxIterationsExceededException; |
| import org.apache.commons.math.analysis.MultivariateRealFunction; |
| import org.apache.commons.math.exception.util.LocalizedFormats; |
| import org.apache.commons.math.optimization.GoalType; |
| import org.apache.commons.math.optimization.MultivariateRealOptimizer; |
| import org.apache.commons.math.optimization.OptimizationException; |
| import org.apache.commons.math.optimization.RealConvergenceChecker; |
| import org.apache.commons.math.optimization.RealPointValuePair; |
| import org.apache.commons.math.optimization.SimpleScalarValueChecker; |
| |
| /** |
| * This class implements simplex-based direct search optimization |
| * algorithms. |
| * |
| * <p>Direct search methods only use objective function values, they don't |
| * need derivatives and don't either try to compute approximation of |
| * the derivatives. According to a 1996 paper by Margaret H. Wright |
| * (<a href="http://cm.bell-labs.com/cm/cs/doc/96/4-02.ps.gz">Direct |
| * Search Methods: Once Scorned, Now Respectable</a>), they are used |
| * when either the computation of the derivative is impossible (noisy |
| * functions, unpredictable discontinuities) or difficult (complexity, |
| * computation cost). In the first cases, rather than an optimum, a |
| * <em>not too bad</em> point is desired. In the latter cases, an |
| * optimum is desired but cannot be reasonably found. In all cases |
| * direct search methods can be useful.</p> |
| * |
| * <p>Simplex-based direct search methods are based on comparison of |
| * the objective function values at the vertices of a simplex (which is a |
| * set of n+1 points in dimension n) that is updated by the algorithms |
| * steps.<p> |
| * |
| * <p>The initial configuration of the simplex can be set using either |
| * {@link #setStartConfiguration(double[])} or {@link |
| * #setStartConfiguration(double[][])}. If neither method has been called |
| * before optimization is attempted, an explicit call to the first method |
| * with all steps set to +1 is triggered, thus building a default |
| * configuration from a unit hypercube. Each call to {@link |
| * #optimize(MultivariateRealFunction, GoalType, double[]) optimize} will reuse |
| * the current start configuration and move it such that its first vertex |
| * is at the provided start point of the optimization. If the {@code optimize} |
| * method is called to solve a different problem and the number of parameters |
| * change, the start configuration will be reset to a default one with the |
| * appropriate dimensions.</p> |
| * |
| * <p>If {@link #setConvergenceChecker(RealConvergenceChecker)} is not called, |
| * a default {@link SimpleScalarValueChecker} is used.</p> |
| * |
| * <p>Convergence is checked by providing the <em>worst</em> points of |
| * previous and current simplex to the convergence checker, not the best ones.</p> |
| * |
| * <p>This class is the base class performing the boilerplate simplex |
| * initialization and handling. The simplex update by itself is |
| * performed by the derived classes according to the implemented |
| * algorithms.</p> |
| * |
| * implements MultivariateRealOptimizer since 2.0 |
| * |
| * @see MultivariateRealFunction |
| * @see NelderMead |
| * @see MultiDirectional |
| * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $ |
| * @since 1.2 |
| */ |
| public abstract class DirectSearchOptimizer implements MultivariateRealOptimizer { |
| |
| /** Simplex. */ |
| protected RealPointValuePair[] simplex; |
| |
| /** Objective function. */ |
| private MultivariateRealFunction f; |
| |
| /** Convergence checker. */ |
| private RealConvergenceChecker checker; |
| |
| /** Maximal number of iterations allowed. */ |
| private int maxIterations; |
| |
| /** Number of iterations already performed. */ |
| private int iterations; |
| |
| /** Maximal number of evaluations allowed. */ |
| private int maxEvaluations; |
| |
| /** Number of evaluations already performed. */ |
| private int evaluations; |
| |
| /** Start simplex configuration. */ |
| private double[][] startConfiguration; |
| |
| /** Simple constructor. |
| */ |
| protected DirectSearchOptimizer() { |
| setConvergenceChecker(new SimpleScalarValueChecker()); |
| setMaxIterations(Integer.MAX_VALUE); |
| setMaxEvaluations(Integer.MAX_VALUE); |
| } |
| |
| /** Set start configuration for simplex. |
| * <p>The start configuration for simplex is built from a box parallel to |
| * the canonical axes of the space. The simplex is the subset of vertices |
| * of a box parallel to the canonical axes. It is built as the path followed |
| * while traveling from one vertex of the box to the diagonally opposite |
| * vertex moving only along the box edges. The first vertex of the box will |
| * be located at the start point of the optimization.</p> |
| * <p>As an example, in dimension 3 a simplex has 4 vertices. Setting the |
| * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the |
| * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }. |
| * The first vertex would be set to the start point at (1, 1, 1) and the |
| * last vertex would be set to the diagonally opposite vertex at (2, 11, 3).</p> |
| * @param steps steps along the canonical axes representing box edges, |
| * they may be negative but not null |
| * @exception IllegalArgumentException if one step is null |
| */ |
| public void setStartConfiguration(final double[] steps) |
| throws IllegalArgumentException { |
| // only the relative position of the n final vertices with respect |
| // to the first one are stored |
| final int n = steps.length; |
| startConfiguration = new double[n][n]; |
| for (int i = 0; i < n; ++i) { |
| final double[] vertexI = startConfiguration[i]; |
| for (int j = 0; j < i + 1; ++j) { |
| if (steps[j] == 0.0) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, j, j + 1); |
| } |
| System.arraycopy(steps, 0, vertexI, 0, j + 1); |
| } |
| } |
| } |
| |
| /** Set start configuration for simplex. |
| * <p>The real initial simplex will be set up by moving the reference |
| * simplex such that its first point is located at the start point of the |
| * optimization.</p> |
| * @param referenceSimplex reference simplex |
| * @exception IllegalArgumentException if the reference simplex does not |
| * contain at least one point, or if there is a dimension mismatch |
| * in the reference simplex or if one of its vertices is duplicated |
| */ |
| public void setStartConfiguration(final double[][] referenceSimplex) |
| throws IllegalArgumentException { |
| |
| // only the relative position of the n final vertices with respect |
| // to the first one are stored |
| final int n = referenceSimplex.length - 1; |
| if (n < 0) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.SIMPLEX_NEED_ONE_POINT); |
| } |
| startConfiguration = new double[n][n]; |
| final double[] ref0 = referenceSimplex[0]; |
| |
| // vertices loop |
| for (int i = 0; i < n + 1; ++i) { |
| |
| final double[] refI = referenceSimplex[i]; |
| |
| // safety checks |
| if (refI.length != n) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, refI.length, n); |
| } |
| for (int j = 0; j < i; ++j) { |
| final double[] refJ = referenceSimplex[j]; |
| boolean allEquals = true; |
| for (int k = 0; k < n; ++k) { |
| if (refI[k] != refJ[k]) { |
| allEquals = false; |
| break; |
| } |
| } |
| if (allEquals) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, i, j); |
| } |
| } |
| |
| // store vertex i position relative to vertex 0 position |
| if (i > 0) { |
| final double[] confI = startConfiguration[i - 1]; |
| for (int k = 0; k < n; ++k) { |
| confI[k] = refI[k] - ref0[k]; |
| } |
| } |
| |
| } |
| |
| } |
| |
| /** {@inheritDoc} */ |
| public void setMaxIterations(int maxIterations) { |
| this.maxIterations = maxIterations; |
| } |
| |
| /** {@inheritDoc} */ |
| public int getMaxIterations() { |
| return maxIterations; |
| } |
| |
| /** {@inheritDoc} */ |
| public void setMaxEvaluations(int maxEvaluations) { |
| this.maxEvaluations = maxEvaluations; |
| } |
| |
| /** {@inheritDoc} */ |
| public int getMaxEvaluations() { |
| return maxEvaluations; |
| } |
| |
| /** {@inheritDoc} */ |
| public int getIterations() { |
| return iterations; |
| } |
| |
| /** {@inheritDoc} */ |
| public int getEvaluations() { |
| return evaluations; |
| } |
| |
| /** {@inheritDoc} */ |
| public void setConvergenceChecker(RealConvergenceChecker convergenceChecker) { |
| this.checker = convergenceChecker; |
| } |
| |
| /** {@inheritDoc} */ |
| public RealConvergenceChecker getConvergenceChecker() { |
| return checker; |
| } |
| |
| /** {@inheritDoc} */ |
| public RealPointValuePair optimize(final MultivariateRealFunction function, |
| final GoalType goalType, |
| final double[] startPoint) |
| throws FunctionEvaluationException, OptimizationException, IllegalArgumentException { |
| |
| if ((startConfiguration == null) || |
| (startConfiguration.length != startPoint.length)) { |
| // no initial configuration has been set up for simplex |
| // build a default one from a unit hypercube |
| final double[] unit = new double[startPoint.length]; |
| Arrays.fill(unit, 1.0); |
| setStartConfiguration(unit); |
| } |
| |
| this.f = function; |
| final Comparator<RealPointValuePair> comparator = |
| new Comparator<RealPointValuePair>() { |
| public int compare(final RealPointValuePair o1, |
| final RealPointValuePair o2) { |
| final double v1 = o1.getValue(); |
| final double v2 = o2.getValue(); |
| return (goalType == GoalType.MINIMIZE) ? |
| Double.compare(v1, v2) : Double.compare(v2, v1); |
| } |
| }; |
| |
| // initialize search |
| iterations = 0; |
| evaluations = 0; |
| buildSimplex(startPoint); |
| evaluateSimplex(comparator); |
| |
| RealPointValuePair[] previous = new RealPointValuePair[simplex.length]; |
| while (true) { |
| |
| if (iterations > 0) { |
| boolean converged = true; |
| for (int i = 0; i < simplex.length; ++i) { |
| converged &= checker.converged(iterations, previous[i], simplex[i]); |
| } |
| if (converged) { |
| // we have found an optimum |
| return simplex[0]; |
| } |
| } |
| |
| // we still need to search |
| System.arraycopy(simplex, 0, previous, 0, simplex.length); |
| iterateSimplex(comparator); |
| |
| } |
| |
| } |
| |
| /** Increment the iterations counter by 1. |
| * @exception OptimizationException if the maximal number |
| * of iterations is exceeded |
| */ |
| protected void incrementIterationsCounter() |
| throws OptimizationException { |
| if (++iterations > maxIterations) { |
| throw new OptimizationException(new MaxIterationsExceededException(maxIterations)); |
| } |
| } |
| |
| /** Compute the next simplex of the algorithm. |
| * @param comparator comparator to use to sort simplex vertices from best to worst |
| * @exception FunctionEvaluationException if the function cannot be evaluated at |
| * some point |
| * @exception OptimizationException if the algorithm fails to converge |
| * @exception IllegalArgumentException if the start point dimension is wrong |
| */ |
| protected abstract void iterateSimplex(final Comparator<RealPointValuePair> comparator) |
| throws FunctionEvaluationException, OptimizationException, IllegalArgumentException; |
| |
| /** Evaluate the objective function on one point. |
| * <p>A side effect of this method is to count the number of |
| * function evaluations</p> |
| * @param x point on which the objective function should be evaluated |
| * @return objective function value at the given point |
| * @exception FunctionEvaluationException if no value can be computed for the |
| * parameters or if the maximal number of evaluations is exceeded |
| * @exception IllegalArgumentException if the start point dimension is wrong |
| */ |
| protected double evaluate(final double[] x) |
| throws FunctionEvaluationException, IllegalArgumentException { |
| if (++evaluations > maxEvaluations) { |
| throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations), x); |
| } |
| return f.value(x); |
| } |
| |
| /** Build an initial simplex. |
| * @param startPoint the start point for optimization |
| * @exception IllegalArgumentException if the start point does not match |
| * simplex dimension |
| */ |
| private void buildSimplex(final double[] startPoint) |
| throws IllegalArgumentException { |
| |
| final int n = startPoint.length; |
| if (n != startConfiguration.length) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, n, startConfiguration.length); |
| } |
| |
| // set first vertex |
| simplex = new RealPointValuePair[n + 1]; |
| simplex[0] = new RealPointValuePair(startPoint, Double.NaN); |
| |
| // set remaining vertices |
| for (int i = 0; i < n; ++i) { |
| final double[] confI = startConfiguration[i]; |
| final double[] vertexI = new double[n]; |
| for (int k = 0; k < n; ++k) { |
| vertexI[k] = startPoint[k] + confI[k]; |
| } |
| simplex[i + 1] = new RealPointValuePair(vertexI, Double.NaN); |
| } |
| |
| } |
| |
| /** Evaluate all the non-evaluated points of the simplex. |
| * @param comparator comparator to use to sort simplex vertices from best to worst |
| * @exception FunctionEvaluationException if no value can be computed for the parameters |
| * @exception OptimizationException if the maximal number of evaluations is exceeded |
| */ |
| protected void evaluateSimplex(final Comparator<RealPointValuePair> comparator) |
| throws FunctionEvaluationException, OptimizationException { |
| |
| // evaluate the objective function at all non-evaluated simplex points |
| for (int i = 0; i < simplex.length; ++i) { |
| final RealPointValuePair vertex = simplex[i]; |
| final double[] point = vertex.getPointRef(); |
| if (Double.isNaN(vertex.getValue())) { |
| simplex[i] = new RealPointValuePair(point, evaluate(point), false); |
| } |
| } |
| |
| // sort the simplex from best to worst |
| Arrays.sort(simplex, comparator); |
| |
| } |
| |
| /** Replace the worst point of the simplex by a new point. |
| * @param pointValuePair point to insert |
| * @param comparator comparator to use to sort simplex vertices from best to worst |
| */ |
| protected void replaceWorstPoint(RealPointValuePair pointValuePair, |
| final Comparator<RealPointValuePair> comparator) { |
| int n = simplex.length - 1; |
| for (int i = 0; i < n; ++i) { |
| if (comparator.compare(simplex[i], pointValuePair) > 0) { |
| RealPointValuePair tmp = simplex[i]; |
| simplex[i] = pointValuePair; |
| pointValuePair = tmp; |
| } |
| } |
| simplex[n] = pointValuePair; |
| } |
| |
| } |