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 }