PowellOptimizer.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.math4.legacy.optim.nonlinear.scalar.noderiv;

  18. import java.util.Arrays;
  19. import org.apache.commons.math4.legacy.analysis.MultivariateFunction;
  20. import org.apache.commons.math4.legacy.exception.MathUnsupportedOperationException;
  21. import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
  22. import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
  23. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  24. import org.apache.commons.math4.legacy.optim.ConvergenceChecker;
  25. import org.apache.commons.math4.legacy.optim.PointValuePair;
  26. import org.apache.commons.math4.legacy.optim.nonlinear.scalar.GoalType;
  27. import org.apache.commons.math4.legacy.optim.nonlinear.scalar.MultivariateOptimizer;
  28. import org.apache.commons.math4.legacy.optim.univariate.UnivariatePointValuePair;
  29. import org.apache.commons.math4.core.jdkmath.JdkMath;

  30. /**
  31.  * Powell's algorithm.
  32.  * This code is translated and adapted from the Python version of this
  33.  * algorithm (as implemented in module {@code optimize.py} v0.5 of
  34.  * <em>SciPy</em>).
  35.  * <br>
  36.  * The default stopping criterion is based on the differences of the
  37.  * function value between two successive iterations. It is however possible
  38.  * to define a custom convergence checker that might terminate the algorithm
  39.  * earlier.
  40.  * <br>
  41.  * Constraints are not supported: the call to
  42.  * {@link #optimize(org.apache.commons.math4.legacy.optim.OptimizationData...)} will throw
  43.  * {@link MathUnsupportedOperationException} if bounds are passed to it.
  44.  * In order to impose simple constraints, the objective function must be
  45.  * wrapped in an adapter like
  46.  * {@link org.apache.commons.math4.legacy.optim.nonlinear.scalar.MultivariateFunctionMappingAdapter
  47.  * MultivariateFunctionMappingAdapter} or
  48.  * {@link org.apache.commons.math4.legacy.optim.nonlinear.scalar.MultivariateFunctionPenaltyAdapter
  49.  * MultivariateFunctionPenaltyAdapter}.
  50.  *
  51.  * @since 2.2
  52.  */
  53. public class PowellOptimizer
  54.     extends MultivariateOptimizer {
  55.     /**
  56.      * Minimum relative tolerance.
  57.      */
  58.     private static final double MIN_RELATIVE_TOLERANCE = 2 * JdkMath.ulp(1d);
  59.     /** Relative threshold. */
  60.     private final double relativeThreshold;
  61.     /** Absolute threshold. */
  62.     private final double absoluteThreshold;
  63.     /** Relative threshold. */
  64.     private final double lineSearchRelativeThreshold;
  65.     /** Absolute threshold. */
  66.     private final double lineSearchAbsoluteThreshold;

  67.     /**
  68.      * This constructor allows to specify a user-defined convergence checker,
  69.      * in addition to the parameters that control the default convergence
  70.      * checking procedure.
  71.      * <br>
  72.      * The internal line search tolerances are set to the square-root of their
  73.      * corresponding value in the multivariate optimizer.
  74.      *
  75.      * @param rel Relative threshold.
  76.      * @param abs Absolute threshold.
  77.      * @param checker Convergence checker.
  78.      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
  79.      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
  80.      */
  81.     public PowellOptimizer(double rel,
  82.                            double abs,
  83.                            ConvergenceChecker<PointValuePair> checker) {
  84.         this(rel, abs, JdkMath.sqrt(rel), JdkMath.sqrt(abs), checker);
  85.     }

  86.     /**
  87.      * This constructor allows to specify a user-defined convergence checker,
  88.      * in addition to the parameters that control the default convergence
  89.      * checking procedure and the line search tolerances.
  90.      *
  91.      * @param rel Relative threshold for this optimizer.
  92.      * @param abs Absolute threshold for this optimizer.
  93.      * @param lineRel Relative threshold for the internal line search optimizer.
  94.      * @param lineAbs Absolute threshold for the internal line search optimizer.
  95.      * @param checker Convergence checker.
  96.      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
  97.      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
  98.      */
  99.     public PowellOptimizer(double rel,
  100.                            double abs,
  101.                            double lineRel,
  102.                            double lineAbs,
  103.                            ConvergenceChecker<PointValuePair> checker) {
  104.         super(checker);

  105.         if (rel < MIN_RELATIVE_TOLERANCE) {
  106.             throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true);
  107.         }
  108.         if (abs <= 0) {
  109.             throw new NotStrictlyPositiveException(abs);
  110.         }

  111.         relativeThreshold = rel;
  112.         absoluteThreshold = abs;
  113.         lineSearchRelativeThreshold = lineRel;
  114.         lineSearchAbsoluteThreshold = lineAbs;
  115.     }

  116.     /**
  117.      * The parameters control the default convergence checking procedure.
  118.      * <br>
  119.      * The internal line search tolerances are set to the square-root of their
  120.      * corresponding value in the multivariate optimizer.
  121.      *
  122.      * @param rel Relative threshold.
  123.      * @param abs Absolute threshold.
  124.      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
  125.      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
  126.      */
  127.     public PowellOptimizer(double rel,
  128.                            double abs) {
  129.         this(rel, abs, null);
  130.     }

  131.     /**
  132.      * Builds an instance with the default convergence checking procedure.
  133.      *
  134.      * @param rel Relative threshold.
  135.      * @param abs Absolute threshold.
  136.      * @param lineRel Relative threshold for the internal line search optimizer.
  137.      * @param lineAbs Absolute threshold for the internal line search optimizer.
  138.      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
  139.      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
  140.      */
  141.     public PowellOptimizer(double rel,
  142.                            double abs,
  143.                            double lineRel,
  144.                            double lineAbs) {
  145.         this(rel, abs, lineRel, lineAbs, null);
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     protected PointValuePair doOptimize() {
  150.         checkParameters();

  151.         // Line search optimizer.
  152.         createLineSearch();

  153.         final GoalType goal = getGoalType();
  154.         final double[] guess = getStartPoint();
  155.         final MultivariateFunction func = getObjectiveFunction();
  156.         final int n = guess.length;

  157.         final double[][] direc = new double[n][n];
  158.         for (int i = 0; i < n; i++) {
  159.             direc[i][i] = 1;
  160.         }

  161.         final ConvergenceChecker<PointValuePair> checker
  162.             = getConvergenceChecker();

  163.         double[] x = guess;
  164.         double fVal = func.value(x);
  165.         double[] x1 = x.clone();
  166.         while (true) {
  167.             incrementIterationCount();

  168.             double fX = fVal;
  169.             double fX2 = 0;
  170.             double delta = 0;
  171.             int bigInd = 0;
  172.             double alphaMin = 0;

  173.             for (int i = 0; i < n; i++) {
  174.                 final double[] d = Arrays.copyOf(direc[i], direc[i].length);

  175.                 fX2 = fVal;

  176.                 final UnivariatePointValuePair optimum = lineSearch(x, d);
  177.                 fVal = optimum.getValue();
  178.                 alphaMin = optimum.getPoint();
  179.                 final double[][] result = newPointAndDirection(x, d, alphaMin);
  180.                 x = result[0];

  181.                 if ((fX2 - fVal) > delta) {
  182.                     delta = fX2 - fVal;
  183.                     bigInd = i;
  184.                 }
  185.             }

  186.             // Default convergence check.
  187.             boolean stop = 2 * (fX - fVal) <=
  188.                 (relativeThreshold * (JdkMath.abs(fX) + JdkMath.abs(fVal)) +
  189.                  absoluteThreshold);

  190.             final PointValuePair previous = new PointValuePair(x1, fX);
  191.             final PointValuePair current = new PointValuePair(x, fVal);
  192.             if (!stop && checker != null) { // User-defined stopping criteria.
  193.                 stop = checker.converged(getIterations(), previous, current);
  194.             }
  195.             if (stop) {
  196.                 if (goal == GoalType.MINIMIZE) {
  197.                     return (fVal < fX) ? current : previous;
  198.                 } else {
  199.                     return (fVal > fX) ? current : previous;
  200.                 }
  201.             }

  202.             final double[] d = new double[n];
  203.             final double[] x2 = new double[n];
  204.             for (int i = 0; i < n; i++) {
  205.                 d[i] = x[i] - x1[i];
  206.                 x2[i] = 2 * x[i] - x1[i];
  207.             }

  208.             x1 = x.clone();
  209.             fX2 = func.value(x2);

  210.             if (fX > fX2) {
  211.                 double t = 2 * (fX + fX2 - 2 * fVal);
  212.                 double temp = fX - fVal - delta;
  213.                 t *= temp * temp;
  214.                 temp = fX - fX2;
  215.                 t -= delta * temp * temp;

  216.                 if (t < 0.0) {
  217.                     final UnivariatePointValuePair optimum = lineSearch(x, d);
  218.                     fVal = optimum.getValue();
  219.                     alphaMin = optimum.getPoint();
  220.                     final double[][] result = newPointAndDirection(x, d, alphaMin);
  221.                     x = result[0];

  222.                     final int lastInd = n - 1;
  223.                     direc[bigInd] = direc[lastInd];
  224.                     direc[lastInd] = result[1];
  225.                 }
  226.             }
  227.         }
  228.     }

  229.     /**
  230.      * Compute a new point (in the original space) and a new direction
  231.      * vector, resulting from the line search.
  232.      *
  233.      * @param p Point used in the line search.
  234.      * @param d Direction used in the line search.
  235.      * @param optimum Optimum found by the line search.
  236.      * @return a 2-element array containing the new point (at index 0) and
  237.      * the new direction (at index 1).
  238.      */
  239.     private double[][] newPointAndDirection(double[] p,
  240.                                             double[] d,
  241.                                             double optimum) {
  242.         final int n = p.length;
  243.         final double[] nP = new double[n];
  244.         final double[] nD = new double[n];
  245.         for (int i = 0; i < n; i++) {
  246.             nD[i] = d[i] * optimum;
  247.             nP[i] = p[i] + nD[i];
  248.         }

  249.         final double[][] result = new double[2][];
  250.         result[0] = nP;
  251.         result[1] = nD;

  252.         return result;
  253.     }

  254.     /**
  255.      * @throws MathUnsupportedOperationException if bounds were passed to the
  256.      * {@link #optimize(org.apache.commons.math4.legacy.optim.OptimizationData[]) optimize} method.
  257.      */
  258.     private void checkParameters() {
  259.         if (getLowerBound() != null ||
  260.             getUpperBound() != null) {
  261.             throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
  262.         }
  263.     }
  264. }