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;
18  
19  import java.util.function.BiFunction;
20  import java.util.function.DoublePredicate;
21  import org.apache.commons.rng.UniformRandomProvider;
22  import org.apache.commons.math4.legacy.stat.descriptive.moment.StandardDeviation;
23  import org.apache.commons.math4.legacy.optim.OptimizationData;
24  import org.apache.commons.math4.legacy.optim.nonlinear.scalar.noderiv.Simplex;
25  
26  /**
27   * Simulated annealing setup.
28   *
29   * @since 4.0
30   */
31  public class SimulatedAnnealing implements OptimizationData {
32      /** Number of iterations at fixed temperature. */
33      private final int epochDuration;
34      /** Initial acceptance probability. */
35      private final double startProbability;
36      /** Final acceptance probability. */
37      private final double endProbability;
38      /** Cooling function. */
39      private final CoolingSchedule coolingSchedule;
40      /** RNG. */
41      private final UniformRandomProvider rng;
42  
43      /**
44       * @param epoch Number of iterations performed at fixed temperature.
45       * @param startProb Initial acceptance probablility.
46       * @param endProb Final acceptance probablility.
47       * @param cooling Computes the temperature as a function of the initial
48       * temperature and the epoch.
49       * It is called for computing a new temperature after each cycle of
50       * {@code epoch} iterations.
51       * Simulated annealing <em>assumes</em> that the function decreases
52       * monotically wrt the epoch (cf. {@link CoolingSchedule#decreasingExponential(double)
53       * provided implementation}).
54       * @param random Random number generator.
55       * @throws IllegalArgumentException if {@code epoch < 1} or
56       * {@code startProb} or {@code endProb} is outside the {@code [0, 1]}
57       * interval.
58       */
59      public SimulatedAnnealing(int epoch,
60                                double startProb,
61                                double endProb,
62                                CoolingSchedule cooling,
63                                UniformRandomProvider random) {
64          if (epoch < 1) {
65              throw new IllegalArgumentException("Epoch out of range: " +
66                                                 epoch);
67          }
68          if (startProb < 0 ||
69              startProb > 1) {
70              throw new IllegalArgumentException("Initial acceptance probability out of range: " +
71                                                 startProb);
72          }
73          if (endProb < 0 ||
74              endProb > 1) {
75              throw new IllegalArgumentException("Final acceptance probability out of range: " +
76                                                 endProb);
77          }
78          if (endProb >= startProb) {
79              throw new IllegalArgumentException("Final probability larger than initial probability");
80          }
81  
82          epochDuration = epoch;
83          startProbability = startProb;
84          endProbability = endProb;
85          coolingSchedule = cooling;
86          rng = random;
87      }
88  
89      /**
90       * @return the epoch duration.
91       */
92      public int getEpochDuration() {
93          return epochDuration;
94      }
95  
96      /**
97       * @return the acceptance probability at the beginning of the SA process.
98       */
99      public double getStartProbability() {
100         return startProbability;
101     }
102 
103     /**
104      * @return the acceptance probability at the end of the SA process.
105      */
106     public double getEndProbability() {
107         return endProbability;
108     }
109 
110     /**
111      * @return the cooling schedule.
112      */
113     public CoolingSchedule getCoolingSchedule() {
114         return coolingSchedule;
115     }
116 
117     /**
118      * Specifies the cooling schedule.
119      * It computes the current temperature as a function of two arguments:
120      * <ol>
121      *  <li>the previous temperature,</li>
122      *  <li>the current simplex.</li>
123      * </ol>
124      */
125     public interface CoolingSchedule extends BiFunction<Double, Simplex, Double> {
126         /**
127          * Power-law cooling scheme:
128          * \[
129          *   T_i = T_0 * f^i
130          * \], where \( i \) is the current iteration.
131          * <p>
132          * Note: Simplex argument (of the returned function) is not used.
133          *
134          * @param f Factor by which the temperature is decreased.
135          * @return the cooling schedule.
136          */
137         static CoolingSchedule decreasingExponential(final double f) {
138             if (f <= 0 ||
139                 f >= 1) {
140                 throw new IllegalArgumentException("Factor out of range: " + f);
141             }
142 
143             return (previousTemperature, simplex) -> f * previousTemperature;
144         }
145 
146         /**
147          * Aarst and van Laarhoven (1985) scheme:
148          * \[
149          *   T_{i + 1} = \frac{T_{i}}{1 + \frac{T_i \ln(1 + \delta)}{3 \sigma}}
150          * \]
151          * <p>
152          * The simplex argument is used to compute the standard deviation
153          * (\(\sigma\)) of all the vertices' objective function value.
154          *
155          * @param delta Trajectory parameter. Values smaller than 1 entail slow
156          * convergence; values larger than 1 entail convergence to local optimum.
157          * @return the cooling schedule.
158          */
159         static CoolingSchedule aarstAndVanLaarhoven(final double delta) {
160             if (delta <= 0) {
161                 throw new IllegalArgumentException("Trajectory parameter out of range: " +
162                                                    delta);
163             }
164 
165             return (previousTemperature, simplex) -> {
166                 // Standard deviation of the values of the objective function.
167                 final StandardDeviation stddev = new StandardDeviation();
168                 for (int i = 0; i < simplex.getSize(); i++) {
169                     stddev.increment(simplex.get(i).getValue());
170                 }
171                 final double sigma = stddev.getResult();
172 
173                 final double a = previousTemperature * Math.log(1 + delta);
174                 final double b = 3 * sigma;
175                 return previousTemperature / (1 + a / b);
176             };
177         }
178     }
179 
180     /**
181      * Factory for the Metropolis check for accepting a worse state.
182      * \( e^{-|\Delta E| / T} \geq \mathrm{rand}(0, 1) \).
183      *
184      * <p>
185      * It is assumed that this check is performed <em>after</em> ensuring
186      * that the alternate state is <em>worse</em> than the current best state.
187      *
188      * @param temperature Current temperature.
189      * @return the acceptance test.
190      */
191     public DoublePredicate metropolis(final double temperature) {
192         // Absolute value takes care of both minimization and maximization cases.
193         return deltaE -> Math.exp(Math.abs(deltaE) / temperature) >= rng.nextDouble();
194     }
195 }