blob: 3f9fac2fc877d37356bec1803e3c25790d719f9a [file] [log] [blame]
/*
* 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;
}
}