/* * 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.math3.optimization.general; import org.apache.commons.math3.exception.MathIllegalStateException; import org.apache.commons.math3.analysis.UnivariateFunction; import org.apache.commons.math3.analysis.solvers.BrentSolver; import org.apache.commons.math3.analysis.solvers.UnivariateSolver; import org.apache.commons.math3.exception.util.LocalizedFormats; import org.apache.commons.math3.optimization.GoalType; import org.apache.commons.math3.optimization.PointValuePair; import org.apache.commons.math3.optimization.SimpleValueChecker; import org.apache.commons.math3.optimization.ConvergenceChecker; import org.apache.commons.math3.util.FastMath; /** * Non-linear conjugate gradient optimizer. * <p> * This class supports both the Fletcher-Reeves and the Polak-Ribière * update formulas for the conjugate search directions. It also supports * optional preconditioning. * </p> * * @deprecated As of 3.1 (to be removed in 4.0). * @since 2.0 * */ @Deprecated public class NonLinearConjugateGradientOptimizer extends AbstractScalarDifferentiableOptimizer { /** Update formula for the beta parameter. */ private final ConjugateGradientFormula updateFormula; /** Preconditioner (may be null). */ private final Preconditioner preconditioner; /** solver to use in the line search (may be null). */ private final UnivariateSolver solver; /** Initial step used to bracket the optimum in line search. */ private double initialStep; /** Current point. */ private double[] point; /** * Constructor with default {@link SimpleValueChecker checker}, * {@link BrentSolver line search solver} and * {@link IdentityPreconditioner preconditioner}. * * @param updateFormula formula to use for updating the β parameter, * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link * ConjugateGradientFormula#POLAK_RIBIERE}. * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} */ @Deprecated public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula) { this(updateFormula, new SimpleValueChecker()); } /** * Constructor with default {@link BrentSolver line search solver} and * {@link IdentityPreconditioner preconditioner}. * * @param updateFormula formula to use for updating the β parameter, * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link * ConjugateGradientFormula#POLAK_RIBIERE}. * @param checker Convergence checker. */ public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula, ConvergenceChecker<PointValuePair> checker) { this(updateFormula, checker, new BrentSolver(), new IdentityPreconditioner()); } /** * Constructor with default {@link IdentityPreconditioner preconditioner}. * * @param updateFormula formula to use for updating the β parameter, * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link * ConjugateGradientFormula#POLAK_RIBIERE}. * @param checker Convergence checker. * @param lineSearchSolver Solver to use during line search. */ public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula, ConvergenceChecker<PointValuePair> checker, final UnivariateSolver lineSearchSolver) { this(updateFormula, checker, lineSearchSolver, new IdentityPreconditioner()); } /** * @param updateFormula formula to use for updating the β parameter, * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link * ConjugateGradientFormula#POLAK_RIBIERE}. * @param checker Convergence checker. * @param lineSearchSolver Solver to use during line search. * @param preconditioner Preconditioner. */ public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula, ConvergenceChecker<PointValuePair> checker, final UnivariateSolver lineSearchSolver, final Preconditioner preconditioner) { super(checker); this.updateFormula = updateFormula; solver = lineSearchSolver; this.preconditioner = preconditioner; initialStep = 1.0; } /** * Set the initial step used to bracket the optimum in line search. * <p> * The initial step is a factor with respect to the search direction, * which itself is roughly related to the gradient of the function * </p> * @param initialStep initial step used to bracket the optimum in line search, * if a non-positive value is used, the initial step is reset to its * default value of 1.0 */ public void setInitialStep(final double initialStep) { if (initialStep <= 0) { this.initialStep = 1.0; } else { this.initialStep = initialStep; } } /** {@inheritDoc} */ @Override protected PointValuePair doOptimize() { final ConvergenceChecker<PointValuePair> checker = getConvergenceChecker(); point = getStartPoint(); final GoalType goal = getGoalType(); final int n = point.length; double[] r = computeObjectiveGradient(point); if (goal == GoalType.MINIMIZE) { for (int i = 0; i < n; ++i) { r[i] = -r[i]; } } // Initial search direction. double[] steepestDescent = preconditioner.precondition(point, r); double[] searchDirection = steepestDescent.clone(); double delta = 0; for (int i = 0; i < n; ++i) { delta += r[i] * searchDirection[i]; } PointValuePair current = null; int iter = 0; int maxEval = getMaxEvaluations(); while (true) { ++iter; final double objective = computeObjectiveValue(point); PointValuePair previous = current; current = new PointValuePair(point, objective); if (previous != null && checker.converged(iter, previous, current)) { // We have found an optimum. return current; } // Find the optimal step in the search direction. final UnivariateFunction lsf = new LineSearchFunction(searchDirection); final double uB = findUpperBound(lsf, 0, initialStep); // XXX Last parameters is set to a value close to zero in order to // work around the divergence problem in the "testCircleFitting" // unit test (see MATH-439). final double step = solver.solve(maxEval, lsf, 0, uB, 1e-15); maxEval -= solver.getEvaluations(); // Subtract used up evaluations. // Validate new point. for (int i = 0; i < point.length; ++i) { point[i] += step * searchDirection[i]; } r = computeObjectiveGradient(point); if (goal == GoalType.MINIMIZE) { for (int i = 0; i < n; ++i) { r[i] = -r[i]; } } // Compute beta. final double deltaOld = delta; final double[] newSteepestDescent = preconditioner.precondition(point, r); delta = 0; for (int i = 0; i < n; ++i) { delta += r[i] * newSteepestDescent[i]; } final double beta; if (updateFormula == ConjugateGradientFormula.FLETCHER_REEVES) { beta = delta / deltaOld; } else { double deltaMid = 0; for (int i = 0; i < r.length; ++i) { deltaMid += r[i] * steepestDescent[i]; } beta = (delta - deltaMid) / deltaOld; } steepestDescent = newSteepestDescent; // Compute conjugate search direction. if (iter % n == 0 || beta < 0) { // Break conjugation: reset search direction. searchDirection = steepestDescent.clone(); } else { // Compute new conjugate search direction. for (int i = 0; i < n; ++i) { searchDirection[i] = steepestDescent[i] + beta * searchDirection[i]; } } } } /** * Find the upper bound b ensuring bracketing of a root between a and b. * * @param f function whose root must be bracketed. * @param a lower bound of the interval. * @param h initial step to try. * @return b such that f(a) and f(b) have opposite signs. * @throws MathIllegalStateException if no bracket can be found. */ private double findUpperBound(final UnivariateFunction f, final double a, final double h) { final double yA = f.value(a); double yB = yA; for (double step = h; step < Double.MAX_VALUE; step *= FastMath.max(2, yA / yB)) { final double b = a + step; yB = f.value(b); if (yA * yB <= 0) { return b; } } throw new MathIllegalStateException(LocalizedFormats.UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH); } /** Default identity preconditioner. */ public static class IdentityPreconditioner implements Preconditioner { /** {@inheritDoc} */ public double[] precondition(double[] variables, double[] r) { return r.clone(); } } /** Internal class for line search. * <p> * The function represented by this class is the dot product of * the objective function gradient and the search direction. Its * value is zero when the gradient is orthogonal to the search * direction, i.e. when the objective function value is a local * extremum along the search direction. * </p> */ private class LineSearchFunction implements UnivariateFunction { /** Search direction. */ private final double[] searchDirection; /** Simple constructor. * @param searchDirection search direction */ public LineSearchFunction(final double[] searchDirection) { this.searchDirection = searchDirection; } /** {@inheritDoc} */ public double value(double x) { // current point in the search direction final double[] shiftedPoint = point.clone(); for (int i = 0; i < shiftedPoint.length; ++i) { shiftedPoint[i] += x * searchDirection[i]; } // gradient of the objective function final double[] gradient = computeObjectiveGradient(shiftedPoint); // dot product with the search direction double dotProduct = 0; for (int i = 0; i < gradient.length; ++i) { dotProduct += gradient[i] * searchDirection[i]; } return dotProduct; } } }