View Javadoc
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  
19  import java.util.Arrays;
20  import org.apache.commons.math4.legacy.analysis.MultivariateFunction;
21  import org.apache.commons.math4.legacy.exception.MathUnsupportedOperationException;
22  import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
23  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
24  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
25  import org.apache.commons.math4.legacy.optim.ConvergenceChecker;
26  import org.apache.commons.math4.legacy.optim.PointValuePair;
27  import org.apache.commons.math4.legacy.optim.nonlinear.scalar.GoalType;
28  import org.apache.commons.math4.legacy.optim.nonlinear.scalar.MultivariateOptimizer;
29  import org.apache.commons.math4.legacy.optim.univariate.UnivariatePointValuePair;
30  import org.apache.commons.math4.core.jdkmath.JdkMath;
31  
32  /**
33   * Powell's algorithm.
34   * This code is translated and adapted from the Python version of this
35   * algorithm (as implemented in module {@code optimize.py} v0.5 of
36   * <em>SciPy</em>).
37   * <br>
38   * The default stopping criterion is based on the differences of the
39   * function value between two successive iterations. It is however possible
40   * to define a custom convergence checker that might terminate the algorithm
41   * earlier.
42   * <br>
43   * Constraints are not supported: the call to
44   * {@link #optimize(org.apache.commons.math4.legacy.optim.OptimizationData...)} will throw
45   * {@link MathUnsupportedOperationException} if bounds are passed to it.
46   * In order to impose simple constraints, the objective function must be
47   * wrapped in an adapter like
48   * {@link org.apache.commons.math4.legacy.optim.nonlinear.scalar.MultivariateFunctionMappingAdapter
49   * MultivariateFunctionMappingAdapter} or
50   * {@link org.apache.commons.math4.legacy.optim.nonlinear.scalar.MultivariateFunctionPenaltyAdapter
51   * MultivariateFunctionPenaltyAdapter}.
52   *
53   * @since 2.2
54   */
55  public class PowellOptimizer
56      extends MultivariateOptimizer {
57      /**
58       * Minimum relative tolerance.
59       */
60      private static final double MIN_RELATIVE_TOLERANCE = 2 * JdkMath.ulp(1d);
61      /** Relative threshold. */
62      private final double relativeThreshold;
63      /** Absolute threshold. */
64      private final double absoluteThreshold;
65      /** Relative threshold. */
66      private final double lineSearchRelativeThreshold;
67      /** Absolute threshold. */
68      private final double lineSearchAbsoluteThreshold;
69  
70      /**
71       * This constructor allows to specify a user-defined convergence checker,
72       * in addition to the parameters that control the default convergence
73       * checking procedure.
74       * <br>
75       * The internal line search tolerances are set to the square-root of their
76       * corresponding value in the multivariate optimizer.
77       *
78       * @param rel Relative threshold.
79       * @param abs Absolute threshold.
80       * @param checker Convergence checker.
81       * @throws NotStrictlyPositiveException if {@code abs <= 0}.
82       * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
83       */
84      public PowellOptimizer(double rel,
85                             double abs,
86                             ConvergenceChecker<PointValuePair> checker) {
87          this(rel, abs, JdkMath.sqrt(rel), JdkMath.sqrt(abs), checker);
88      }
89  
90      /**
91       * This constructor allows to specify a user-defined convergence checker,
92       * in addition to the parameters that control the default convergence
93       * checking procedure and the line search tolerances.
94       *
95       * @param rel Relative threshold for this optimizer.
96       * @param abs Absolute threshold for this optimizer.
97       * @param lineRel Relative threshold for the internal line search optimizer.
98       * @param lineAbs Absolute threshold for the internal line search optimizer.
99       * @param checker Convergence checker.
100      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
101      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
102      */
103     public PowellOptimizer(double rel,
104                            double abs,
105                            double lineRel,
106                            double lineAbs,
107                            ConvergenceChecker<PointValuePair> checker) {
108         super(checker);
109 
110         if (rel < MIN_RELATIVE_TOLERANCE) {
111             throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true);
112         }
113         if (abs <= 0) {
114             throw new NotStrictlyPositiveException(abs);
115         }
116 
117         relativeThreshold = rel;
118         absoluteThreshold = abs;
119         lineSearchRelativeThreshold = lineRel;
120         lineSearchAbsoluteThreshold = lineAbs;
121     }
122 
123     /**
124      * The parameters control the default convergence checking procedure.
125      * <br>
126      * The internal line search tolerances are set to the square-root of their
127      * corresponding value in the multivariate optimizer.
128      *
129      * @param rel Relative threshold.
130      * @param abs Absolute threshold.
131      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
132      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
133      */
134     public PowellOptimizer(double rel,
135                            double abs) {
136         this(rel, abs, null);
137     }
138 
139     /**
140      * Builds an instance with the default convergence checking procedure.
141      *
142      * @param rel Relative threshold.
143      * @param abs Absolute threshold.
144      * @param lineRel Relative threshold for the internal line search optimizer.
145      * @param lineAbs Absolute threshold for the internal line search optimizer.
146      * @throws NotStrictlyPositiveException if {@code abs <= 0}.
147      * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
148      */
149     public PowellOptimizer(double rel,
150                            double abs,
151                            double lineRel,
152                            double lineAbs) {
153         this(rel, abs, lineRel, lineAbs, null);
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     protected PointValuePair doOptimize() {
159         checkParameters();
160 
161         // Line search optimizer.
162         createLineSearch();
163 
164         final GoalType goal = getGoalType();
165         final double[] guess = getStartPoint();
166         final MultivariateFunction func = getObjectiveFunction();
167         final int n = guess.length;
168 
169         final double[][] direc = new double[n][n];
170         for (int i = 0; i < n; i++) {
171             direc[i][i] = 1;
172         }
173 
174         final ConvergenceChecker<PointValuePair> checker
175             = getConvergenceChecker();
176 
177         double[] x = guess;
178         double fVal = func.value(x);
179         double[] x1 = x.clone();
180         while (true) {
181             incrementIterationCount();
182 
183             double fX = fVal;
184             double fX2 = 0;
185             double delta = 0;
186             int bigInd = 0;
187             double alphaMin = 0;
188 
189             for (int i = 0; i < n; i++) {
190                 final double[] d = Arrays.copyOf(direc[i], direc[i].length);
191 
192                 fX2 = fVal;
193 
194                 final UnivariatePointValuePair optimum = lineSearch(x, d);
195                 fVal = optimum.getValue();
196                 alphaMin = optimum.getPoint();
197                 final double[][] result = newPointAndDirection(x, d, alphaMin);
198                 x = result[0];
199 
200                 if ((fX2 - fVal) > delta) {
201                     delta = fX2 - fVal;
202                     bigInd = i;
203                 }
204             }
205 
206             // Default convergence check.
207             boolean stop = 2 * (fX - fVal) <=
208                 (relativeThreshold * (JdkMath.abs(fX) + JdkMath.abs(fVal)) +
209                  absoluteThreshold);
210 
211             final PointValuePair previous = new PointValuePair(x1, fX);
212             final PointValuePair current = new PointValuePair(x, fVal);
213             if (!stop && checker != null) { // User-defined stopping criteria.
214                 stop = checker.converged(getIterations(), previous, current);
215             }
216             if (stop) {
217                 if (goal == GoalType.MINIMIZE) {
218                     return (fVal < fX) ? current : previous;
219                 } else {
220                     return (fVal > fX) ? current : previous;
221                 }
222             }
223 
224             final double[] d = new double[n];
225             final double[] x2 = new double[n];
226             for (int i = 0; i < n; i++) {
227                 d[i] = x[i] - x1[i];
228                 x2[i] = 2 * x[i] - x1[i];
229             }
230 
231             x1 = x.clone();
232             fX2 = func.value(x2);
233 
234             if (fX > fX2) {
235                 double t = 2 * (fX + fX2 - 2 * fVal);
236                 double temp = fX - fVal - delta;
237                 t *= temp * temp;
238                 temp = fX - fX2;
239                 t -= delta * temp * temp;
240 
241                 if (t < 0.0) {
242                     final UnivariatePointValuePair optimum = lineSearch(x, d);
243                     fVal = optimum.getValue();
244                     alphaMin = optimum.getPoint();
245                     final double[][] result = newPointAndDirection(x, d, alphaMin);
246                     x = result[0];
247 
248                     final int lastInd = n - 1;
249                     direc[bigInd] = direc[lastInd];
250                     direc[lastInd] = result[1];
251                 }
252             }
253         }
254     }
255 
256     /**
257      * Compute a new point (in the original space) and a new direction
258      * vector, resulting from the line search.
259      *
260      * @param p Point used in the line search.
261      * @param d Direction used in the line search.
262      * @param optimum Optimum found by the line search.
263      * @return a 2-element array containing the new point (at index 0) and
264      * the new direction (at index 1).
265      */
266     private double[][] newPointAndDirection(double[] p,
267                                             double[] d,
268                                             double optimum) {
269         final int n = p.length;
270         final double[] nP = new double[n];
271         final double[] nD = new double[n];
272         for (int i = 0; i < n; i++) {
273             nD[i] = d[i] * optimum;
274             nP[i] = p[i] + nD[i];
275         }
276 
277         final double[][] result = new double[2][];
278         result[0] = nP;
279         result[1] = nD;
280 
281         return result;
282     }
283 
284     /**
285      * @throws MathUnsupportedOperationException if bounds were passed to the
286      * {@link #optimize(org.apache.commons.math4.legacy.optim.OptimizationData[]) optimize} method.
287      */
288     private void checkParameters() {
289         if (getLowerBound() != null ||
290             getUpperBound() != null) {
291             throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
292         }
293     }
294 }