/* * 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 org.apache.commons.math.MaxIterationsExceededException; import org.apache.commons.math.analysis.UnivariateRealFunction; import org.apache.commons.math.FunctionEvaluationException; import org.apache.commons.math.optimization.GoalType; import org.apache.commons.math.optimization.OptimizationException; import org.apache.commons.math.optimization.RealPointValuePair; import org.apache.commons.math.optimization.general.AbstractScalarDifferentiableOptimizer; import org.apache.commons.math.optimization.univariate.AbstractUnivariateRealOptimizer; import org.apache.commons.math.optimization.univariate.BracketFinder; import org.apache.commons.math.optimization.univariate.BrentOptimizer; /** * Powell algorithm. * This code is translated and adapted from the Python version of this * algorithm (as implemented in module {@code optimize.py} v0.5 of * <em>SciPy</em>). * * @version $Revision$ $Date$ * @since 2.2 */ public class PowellOptimizer extends AbstractScalarDifferentiableOptimizer { /** * Default relative tolerance for line search ({@value}). */ public static final double DEFAULT_LS_RELATIVE_TOLERANCE = 1e-7; /** * Default absolute tolerance for line search ({@value}). */ public static final double DEFAULT_LS_ABSOLUTE_TOLERANCE = 1e-11; /** * Line search. */ private final LineSearch line; /** * Constructor with default line search tolerances (see the * {@link #PowellOptimizer(double,double) other constructor}). */ public PowellOptimizer() { this(DEFAULT_LS_RELATIVE_TOLERANCE, DEFAULT_LS_ABSOLUTE_TOLERANCE); } /** * Constructor with default absolute line search tolerances (see * the {@link #PowellOptimizer(double,double) other constructor}). * * @param lsRelativeTolerance Relative error tolerance for * the line search algorithm ({@link BrentOptimizer}). */ public PowellOptimizer(double lsRelativeTolerance) { this(lsRelativeTolerance, DEFAULT_LS_ABSOLUTE_TOLERANCE); } /** * @param lsRelativeTolerance Relative error tolerance for * the line search algorithm ({@link BrentOptimizer}). * @param lsAbsoluteTolerance Relative error tolerance for * the line search algorithm ({@link BrentOptimizer}). */ public PowellOptimizer(double lsRelativeTolerance, double lsAbsoluteTolerance) { line = new LineSearch(lsRelativeTolerance, lsAbsoluteTolerance); } /** {@inheritDoc} */ @Override protected RealPointValuePair doOptimize() throws FunctionEvaluationException, OptimizationException { final double[] guess = point.clone(); final int n = guess.length; final double[][] direc = new double[n][n]; for (int i = 0; i < n; i++) { direc[i][i] = 1; } double[] x = guess; double fVal = computeObjectiveValue(x); double[] x1 = x.clone(); while (true) { incrementIterationsCounter(); double fX = fVal; double fX2 = 0; double delta = 0; int bigInd = 0; double alphaMin = 0; for (int i = 0; i < n; i++) { final double[] d = /* Arrays.*/ copyOf(direc[i], n); // Java 1.5 does not support Arrays.copyOf() fX2 = fVal; line.search(x, d); fVal = line.getValueAtOptimum(); alphaMin = line.getOptimum(); final double[][] result = newPointAndDirection(x, d, alphaMin); x = result[0]; if ((fX2 - fVal) > delta) { delta = fX2 - fVal; bigInd = i; } } final RealPointValuePair previous = new RealPointValuePair(x1, fX); final RealPointValuePair current = new RealPointValuePair(x, fVal); if (getConvergenceChecker().converged(getIterations(), previous, current)) { if (goal == GoalType.MINIMIZE) { return (fVal < fX) ? current : previous; } else { return (fVal > fX) ? current : previous; } } final double[] d = new double[n]; final double[] x2 = new double[n]; for (int i = 0; i < n; i++) { d[i] = x[i] - x1[i]; x2[i] = 2 * x[i] - x1[i]; } x1 = x.clone(); fX2 = computeObjectiveValue(x2); if (fX > fX2) { double t = 2 * (fX + fX2 - 2 * fVal); double temp = fX - fVal - delta; t *= temp * temp; temp = fX - fX2; t -= delta * temp * temp; if (t < 0.0) { line.search(x, d); fVal = line.getValueAtOptimum(); alphaMin = line.getOptimum(); final double[][] result = newPointAndDirection(x, d, alphaMin); x = result[0]; final int lastInd = n - 1; direc[bigInd] = direc[lastInd]; direc[lastInd] = result[1]; } } } } /** * Compute a new point (in the original space) and a new direction * vector, resulting from the line search. * The parameters {@code p} and {@code d} will be changed in-place. * * @param p Point used in the line search. * @param d Direction used in the line search. * @param optimum Optimum found by the line search. * @return a 2-element array containing the new point (at index 0) and * the new direction (at index 1). */ private double[][] newPointAndDirection(double[] p, double[] d, double optimum) { final int n = p.length; final double[][] result = new double[2][n]; final double[] nP = result[0]; final double[] nD = result[1]; for (int i = 0; i < n; i++) { nD[i] = d[i] * optimum; nP[i] = p[i] + nD[i]; } return result; } /** * Class for finding the minimum of the objective function along a given * direction. */ private class LineSearch { /** * Optimizer. */ private final AbstractUnivariateRealOptimizer optim = new BrentOptimizer(); /** * Automatic bracketing. */ private final BracketFinder bracket = new BracketFinder(); /** * Value of the optimum. */ private double optimum = Double.NaN; /** * Value of the objective function at the optimum. */ private double valueAtOptimum = Double.NaN; /** * @param relativeTolerance Relative tolerance. * @param absoluteTolerance Absolute tolerance. */ public LineSearch(double relativeTolerance, double absoluteTolerance) { optim.setRelativeAccuracy(relativeTolerance); optim.setAbsoluteAccuracy(absoluteTolerance); } /** * Find the minimum of the function {@code f(p + alpha * d)}. * * @param p Starting point. * @param d Search direction. * @throws FunctionEvaluationException if function cannot be evaluated at some test point * @throws OptimizationException if algorithm fails to converge */ public void search(final double[] p, final double[] d) throws OptimizationException, FunctionEvaluationException { // Reset. optimum = Double.NaN; valueAtOptimum = Double.NaN; try { final int n = p.length; final UnivariateRealFunction f = new UnivariateRealFunction() { public double value(double alpha) throws FunctionEvaluationException { final double[] x = new double[n]; for (int i = 0; i < n; i++) { x[i] = p[i] + alpha * d[i]; } final double obj; obj = computeObjectiveValue(x); return obj; } }; bracket.search(f, goal, 0, 1); optimum = optim.optimize(f, goal, bracket.getLo(), bracket.getHi(), bracket.getMid()); valueAtOptimum = optim.getFunctionValue(); } catch (MaxIterationsExceededException e) { throw new OptimizationException(e); } } /** * @return the optimum. */ public double getOptimum() { return optimum; } /** * @return the value of the function at the optimum. */ public double getValueAtOptimum() { return valueAtOptimum; } } /** * Java 1.5 does not support Arrays.copyOf() * * @param source the array to be copied * @param newLen the length of the copy to be returned * @return the copied array, truncated or padded as necessary. */ private double[] copyOf(double[] source, int newLen) { double[] output = new double[newLen]; System.arraycopy(source, 0, output, 0, Math.min(source.length, newLen)); return output; } }