Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
differentialevolution_mt.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2012 Ralph Schreyer
3 Copyright (C) 2012 Mateusz Kapturski
4
5 This file is part of QuantLib, a free-software/open-source library
6 for financial quantitative analysts and developers - http://quantlib.org/
7
8 QuantLib is free software: you can redistribute it and/or modify it
9 under the terms of the QuantLib license. You should have received a
10 copy of the license along with this program; if not, please email
11 <quantlib-dev@lists.sf.net>. The license is also available online at
12 <http://quantlib.org/license.shtml>.
13
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19/*
20 Copyright (C) 2016 Quaternion Risk Management Ltd.
21 All rights reserved.
22
23 This file is part of ORE, a free-software/open-source library
24 for transparent pricing and risk analysis - http://opensourcerisk.org
25
26 ORE is free software: you can redistribute it and/or modify it
27 under the terms of the Modified BSD License. You should have received a
28 copy of the license along with this program.
29 The license is also available online at <http://opensourcerisk.org>
30
31 This program is distributed on the basis that it will form a useful
32 contribution to risk analytics and model standardisation, but WITHOUT
33 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
34 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
35*/
36
38
39#include <boost/date_time/posix_time/posix_time.hpp>
40#include <boost/make_shared.hpp>
41
42#include <thread>
43
44namespace QuantExt {
45using namespace QuantLib;
46
47namespace {
48
49struct sort_by_cost {
50 bool operator()(const DifferentialEvolution_MT::Candidate& left, const DifferentialEvolution_MT::Candidate& right) {
51 return left.cost < right.cost;
52 }
53};
54
55template <class I> void randomize(I begin, I end, const MersenneTwisterUniformRng& rng) {
56 Size n = static_cast<Size>(end - begin);
57 for (Size i = n - 1; i > 0; --i) {
58 std::swap(begin[i], begin[rng.nextInt32() % (i + 1)]);
59 }
60}
61
62} // namespace
63
65 if (maxTime_.empty())
66 return false;
67 QL_REQUIRE(maxTime_.length() == 15, "maxTime (" << maxTime_ << ") must have format YYYYMMDDTHHMMSS");
68 std::string ts = to_iso_string(boost::posix_time::microsec_clock::local_time()).substr(0, 15);
69 return ts > maxTime_;
70}
71
72EndCriteria::Type DifferentialEvolution_MT::minimize(Problem_MT& p, const EndCriteria& endCriteria) {
73 EndCriteria::Type ecType;
74
75 if (configuration().upperBound.empty()) {
76 upperBound_ = p.constraint().upperBound(p.currentValue());
77 } else {
78 QL_REQUIRE(configuration().upperBound.size() == p.currentValue().size(),
79 "wrong upper bound size in differential evolution configuration");
80 upperBound_ = configuration().upperBound;
81 }
82 if (configuration().lowerBound.empty()) {
83 lowerBound_ = p.constraint().lowerBound(p.currentValue());
84 } else {
85 QL_REQUIRE(configuration().lowerBound.size() == p.currentValue().size(),
86 "wrong lower bound size in differential evolution configuration");
87 lowerBound_ = configuration().lowerBound;
88 }
89
90 currGenSizeWeights_ = Array(configuration().populationMembers, configuration().stepsizeWeight);
91 currGenCrossover_ = Array(configuration().populationMembers, configuration().crossoverProbability);
92
93 std::vector<Candidate> population;
94 if (!configuration().initialPopulation.empty()) {
95 population.resize(configuration().initialPopulation.size());
96 for (Size i = 0; i < population.size(); ++i) {
97 population[i].values = configuration().initialPopulation[i];
98 QL_REQUIRE(population[i].values.size() == p.currentValue().size(),
99 "wrong values size in initial population");
100 }
101 } else {
102 population = std::vector<Candidate>(configuration().populationMembers, Candidate(p.currentValue().size()));
103 fillInitialPopulation(population, p);
104 }
105
106 updateCost(population, p);
107
108 std::partial_sort(population.begin(), population.begin() + 1, population.end(), sort_by_cost());
109 bestMemberEver_ = population.front();
110 Real fxOld = population.front().cost;
111 Size iteration = 0, stationaryPointIteration = 0;
112
113 // main loop - calculate consecutive emerging populations
114 while (!endCriteria.checkMaxIterations(iteration++, ecType) && !checkMaxTime()) {
115 calculateNextGeneration(population, p);
116 std::partial_sort(population.begin(), population.begin() + 1, population.end(), sort_by_cost());
117 if (population.front().cost < bestMemberEver_.cost)
118 bestMemberEver_ = population.front();
119 Real fxNew = population.front().cost;
120 if (endCriteria.checkStationaryFunctionValue(fxOld, fxNew, stationaryPointIteration, ecType))
121 break;
122 fxOld = fxNew;
123 };
126 if (checkMaxTime())
127 ecType = EndCriteria::Type::Unknown;
128 return ecType;
129}
130
131void DifferentialEvolution_MT::calculateNextGeneration(std::vector<Candidate>& population, Problem_MT& p) const {
132
133 std::vector<Candidate> mirrorPopulation;
134 std::vector<Candidate> oldPopulation = population;
135
136 switch (configuration().strategy) {
137
138 case QuantLib::DifferentialEvolution::Rand1Standard: {
139 randomize(population.begin(), population.end(), rng_);
140 std::vector<Candidate> shuffledPop1 = population;
141 randomize(population.begin(), population.end(), rng_);
142 std::vector<Candidate> shuffledPop2 = population;
143 randomize(population.begin(), population.end(), rng_);
144 mirrorPopulation = shuffledPop1;
145
146 for (Size popIter = 0; popIter < population.size(); popIter++) {
147 population[popIter].values =
148 population[popIter].values +
149 configuration().stepsizeWeight * (shuffledPop1[popIter].values - shuffledPop2[popIter].values);
150 }
151 } break;
152
153 case QuantLib::DifferentialEvolution::BestMemberWithJitter: {
154 randomize(population.begin(), population.end(), rng_);
155 std::vector<Candidate> shuffledPop1 = population;
156 randomize(population.begin(), population.end(), rng_);
157 Array jitter(population[0].values.size(), 0.0);
158
159 for (Size popIter = 0; popIter < population.size(); popIter++) {
160 for (double& jitterIter : jitter) {
161 jitterIter = rng_.nextReal();
162 }
163 population[popIter].values =
164 bestMemberEver_.values + (shuffledPop1[popIter].values - population[popIter].values) *
165 (0.0001 * jitter + configuration().stepsizeWeight);
166 }
167 mirrorPopulation = std::vector<Candidate>(population.size(), bestMemberEver_);
168 } break;
169
170 case QuantLib::DifferentialEvolution::CurrentToBest2Diffs: {
171 randomize(population.begin(), population.end(), rng_);
172 std::vector<Candidate> shuffledPop1 = population;
173 randomize(population.begin(), population.end(), rng_);
174
175 for (Size popIter = 0; popIter < population.size(); popIter++) {
176 population[popIter].values =
177 oldPopulation[popIter].values +
178 configuration().stepsizeWeight * (bestMemberEver_.values - oldPopulation[popIter].values) +
179 configuration().stepsizeWeight * (population[popIter].values - shuffledPop1[popIter].values);
180 }
181 mirrorPopulation = shuffledPop1;
182 } break;
183
184 case QuantLib::DifferentialEvolution::Rand1DiffWithPerVectorDither: {
185 randomize(population.begin(), population.end(), rng_);
186 std::vector<Candidate> shuffledPop1 = population;
187 randomize(population.begin(), population.end(), rng_);
188 std::vector<Candidate> shuffledPop2 = population;
189 randomize(population.begin(), population.end(), rng_);
190 mirrorPopulation = shuffledPop1;
191 Array FWeight = Array(population.front().values.size(), 0.0);
192 for (double& fwIter : FWeight)
193 fwIter = (1.0 - configuration().stepsizeWeight) * rng_.nextReal() + configuration().stepsizeWeight;
194 for (Size popIter = 0; popIter < population.size(); popIter++) {
195 population[popIter].values =
196 population[popIter].values + FWeight * (shuffledPop1[popIter].values - shuffledPop2[popIter].values);
197 }
198 } break;
199
200 case QuantLib::DifferentialEvolution::Rand1DiffWithDither: {
201 randomize(population.begin(), population.end(), rng_);
202 std::vector<Candidate> shuffledPop1 = population;
203 randomize(population.begin(), population.end(), rng_);
204 std::vector<Candidate> shuffledPop2 = population;
205 randomize(population.begin(), population.end(), rng_);
206 mirrorPopulation = shuffledPop1;
207 Real FWeight = (1.0 - configuration().stepsizeWeight) * rng_.nextReal() + configuration().stepsizeWeight;
208 for (Size popIter = 0; popIter < population.size(); popIter++) {
209 population[popIter].values =
210 population[popIter].values + FWeight * (shuffledPop1[popIter].values - shuffledPop2[popIter].values);
211 }
212 } break;
213
214 case QuantLib::DifferentialEvolution::EitherOrWithOptimalRecombination: {
215 randomize(population.begin(), population.end(), rng_);
216 std::vector<Candidate> shuffledPop1 = population;
217 randomize(population.begin(), population.end(), rng_);
218 std::vector<Candidate> shuffledPop2 = population;
219 randomize(population.begin(), population.end(), rng_);
220 mirrorPopulation = shuffledPop1;
221 Real probFWeight = 0.5;
222 if (rng_.nextReal() < probFWeight) {
223 for (Size popIter = 0; popIter < population.size(); popIter++) {
224 population[popIter].values =
225 oldPopulation[popIter].values +
226 configuration().stepsizeWeight * (shuffledPop1[popIter].values - shuffledPop2[popIter].values);
227 }
228 } else {
229 Real K = 0.5 * (configuration().stepsizeWeight + 1); // invariant with respect to probFWeight used
230 for (Size popIter = 0; popIter < population.size(); popIter++) {
231 population[popIter].values =
232 oldPopulation[popIter].values + K * (shuffledPop1[popIter].values - shuffledPop2[popIter].values -
233 2.0 * population[popIter].values);
234 }
235 }
236 } break;
237
238 case QuantLib::DifferentialEvolution::Rand1SelfadaptiveWithRotation: {
239 randomize(population.begin(), population.end(), rng_);
240 std::vector<Candidate> shuffledPop1 = population;
241 randomize(population.begin(), population.end(), rng_);
242 std::vector<Candidate> shuffledPop2 = population;
243 randomize(population.begin(), population.end(), rng_);
244 mirrorPopulation = shuffledPop1;
245
247
248 for (Size popIter = 0; popIter < population.size(); popIter++) {
249 if (rng_.nextReal() < 0.1) {
250 population[popIter].values = rotateArray(bestMemberEver_.values);
251 } else {
252 population[popIter].values =
253 bestMemberEver_.values +
254 currGenSizeWeights_[popIter] * (shuffledPop1[popIter].values - shuffledPop2[popIter].values);
255 }
256 }
257 } break;
258
259 default:
260 QL_FAIL("Unknown strategy (" << Integer(configuration().strategy) << ")");
261 }
262 // in order to avoid unnecessary copying we use the same population object for mutants
263 crossover(oldPopulation, population, population, mirrorPopulation, p);
264}
265
266void DifferentialEvolution_MT::crossover(const std::vector<Candidate>& oldPopulation,
267 std::vector<Candidate>& population,
268 const std::vector<Candidate>& mutantPopulation,
269 const std::vector<Candidate>& mirrorPopulation, Problem_MT& p) const {
270
271 if (configuration().crossoverIsAdaptive) {
273 }
274
275 Array mutationProbabilities = getMutationProbabilities(population);
276
277 std::vector<Array> crossoverMask(population.size(), Array(population.front().values.size(), 1.0));
278 std::vector<Array> invCrossoverMask = crossoverMask;
279 getCrossoverMask(crossoverMask, invCrossoverMask, mutationProbabilities);
280
281 // crossover of the old and mutant population
282
283 for (Size popIter = 0; popIter < population.size(); popIter++) {
284 population[popIter].values = oldPopulation[popIter].values * invCrossoverMask[popIter] +
285 mutantPopulation[popIter].values * crossoverMask[popIter];
286 // immediately apply bounds if specified
287 if (configuration().applyBounds) {
288 for (Size memIter = 0; memIter < population[popIter].values.size(); memIter++) {
289 if (population[popIter].values[memIter] > upperBound_[memIter])
290 population[popIter].values[memIter] =
291 upperBound_[memIter] +
292 rng_.nextReal() * (mirrorPopulation[popIter].values[memIter] - upperBound_[memIter]);
293 if (population[popIter].values[memIter] < lowerBound_[memIter])
294 population[popIter].values[memIter] =
295 lowerBound_[memIter] +
296 rng_.nextReal() * (mirrorPopulation[popIter].values[memIter] - lowerBound_[memIter]);
297 }
298 }
299 }
300
301 updateCost(population, p);
302}
303
304void DifferentialEvolution_MT::updateCost(std::vector<Candidate>& population, Problem_MT& p) const {
305 struct Worker {
306 Worker(std::vector<Candidate>& p, const Size s, const Size e, const QuantLib::ext::shared_ptr<CostFunction> c)
307 : population(p), start(s), end(e), costFunction(c) {}
308 void operator()() {
309 for (Size popIter = start; popIter < end; popIter++) {
310 // evaluate objective function as soon as possible to avoid unnecessary loops
311 try {
312 population[popIter].cost = costFunction->value(population[popIter].values);
313 } catch (Error&) {
314 population[popIter].cost = QL_MAX_REAL;
315 }
316 if (!std::isfinite(population[popIter].cost))
317 population[popIter].cost = QL_MAX_REAL;
318 }
319 }
320 std::vector<Candidate>& population;
321 const Size start, end;
322 const QuantLib::ext::shared_ptr<CostFunction> costFunction;
323 };
324
325 Size threads = p.costFunctions().size();
326 QL_REQUIRE(threads > 0, "DifferentialEvolution_MT: number of available threads is zero");
327
328 std::vector<Size> chunks(threads, std::max<Size>(population.size() / threads, 1));
329 int rest = population.size() - threads * chunks[0];
330 do {
331 Size i = 0;
332 while (i < chunks.size() && rest > 0) {
333 chunks[i]++;
334 rest--;
335 ++i;
336 }
337 } while (rest > 0);
338
339 std::vector<QuantLib::ext::shared_ptr<std::thread>> workers(threads);
340
341 Size start = 0, end;
342 for (Size thread = 0; thread < threads; ++thread) {
343 end = std::min(start + chunks[thread], population.size());
344 Worker worker(population, start, end, p.costFunctions()[thread]);
345 workers[thread] = QuantLib::ext::make_shared<std::thread>(worker);
346 start = end;
347 }
348
349 for (Size thread = 0; thread < threads; ++thread) {
350 workers[thread]->join();
351 }
352}
353
354void DifferentialEvolution_MT::getCrossoverMask(std::vector<Array>& crossoverMask, std::vector<Array>& invCrossoverMask,
355 const Array& mutationProbabilities) const {
356 for (Size cmIter = 0; cmIter < crossoverMask.size(); cmIter++) {
357 for (Size memIter = 0; memIter < crossoverMask[cmIter].size(); memIter++) {
358 if (rng_.nextReal() < mutationProbabilities[cmIter]) {
359 invCrossoverMask[cmIter][memIter] = 0.0;
360 } else {
361 crossoverMask[cmIter][memIter] = 0.0;
362 }
363 }
364 }
365}
366
367Array DifferentialEvolution_MT::getMutationProbabilities(const std::vector<Candidate>& population) const {
368 Array mutationProbabilities = currGenCrossover_;
369 switch (configuration().crossoverType) {
370 case DifferentialEvolution::Normal:
371 break;
372 case DifferentialEvolution::Binomial:
373 mutationProbabilities =
374 currGenCrossover_ * (1.0 - 1.0 / population.front().values.size()) + 1.0 / population.front().values.size();
375 break;
376 case DifferentialEvolution::Exponential:
377 for (Size coIter = 0; coIter < currGenCrossover_.size(); coIter++) {
378 mutationProbabilities[coIter] =
379 (1.0 - std::pow(currGenCrossover_[coIter], (int)population.front().values.size())) /
380 (population.front().values.size() * (1.0 - currGenCrossover_[coIter]));
381 }
382 break;
383 default:
384 QL_FAIL("Unknown crossover type (" << Integer(configuration().crossoverType) << ")");
385 break;
386 }
387 return mutationProbabilities;
388}
389
391 randomize(a.begin(), a.end(), rng_);
392 return a;
393}
394
396 // [=Fl & =Fu] respectively see Brest, J. et al., 2006,
397 // "Self-Adapting Control Parameters in Differential
398 // Evolution"
399 Real sizeWeightLowerBound = 0.1, sizeWeightUpperBound = 0.9;
400 // [=tau1] A Comparative Study on Numerical Benchmark
401 // Problems." page 649 for reference
402 Real sizeWeightChangeProb = 0.1;
403 for (Size coIter = 0; coIter < currGenSizeWeights_.size(); coIter++) {
404 if (rng_.nextReal() < sizeWeightChangeProb)
405 currGenSizeWeights_[coIter] = sizeWeightLowerBound + rng_.nextReal() * sizeWeightUpperBound;
406 }
407}
408
410 Real crossoverChangeProb = 0.1; // [=tau2]
411 for (Size coIter = 0; coIter < currGenCrossover_.size(); coIter++) {
412 if (rng_.nextReal() < crossoverChangeProb)
413 currGenCrossover_[coIter] = rng_.nextReal();
414 }
415}
416
417void DifferentialEvolution_MT::fillInitialPopulation(std::vector<Candidate>& population, const Problem_MT& p) const {
418
419 // use initial values provided by the user
420 population.front().values = p.currentValue();
421 // rest of the initial population is random
422 for (Size j = 1; j < population.size(); ++j) {
423 for (Size i = 0; i < p.currentValue().size(); ++i) {
424 Real l = lowerBound_[i], u = upperBound_[i];
425 population[j].values[i] = l + (u - l) * rng_.nextReal();
426 }
427 }
428}
429
430} // namespace QuantExt
void crossover(const std::vector< Candidate > &oldPopulation, std::vector< Candidate > &population, const std::vector< Candidate > &mutantPopulation, const std::vector< Candidate > &mirrorPopulation, Problem_MT &p) const
const Configuration & configuration() const
void getCrossoverMask(std::vector< Array > &crossoverMask, std::vector< Array > &invCrossoverMask, const Array &mutationProbabilities) const
virtual EndCriteria::Type minimize(Problem_MT &p, const EndCriteria &endCriteria) override
minimize the optimization problem P
void calculateNextGeneration(std::vector< Candidate > &population, Problem_MT &p) const
Array getMutationProbabilities(const std::vector< Candidate > &population) const
DifferentialEvolution::Candidate Candidate
Array rotateArray(Array inputArray) const
void fillInitialPopulation(std::vector< Candidate > &population, const Problem_MT &p) const
void updateCost(std::vector< Candidate > &population, Problem_MT &p) const
Constrained optimization problem.
Definition: problem_mt.hpp:64
const Array & currentValue() const
current value of the local minimum
Definition: problem_mt.hpp:115
const std::vector< QuantLib::ext::shared_ptr< CostFunction > > & costFunctions() const
Cost funcionts.
Definition: problem_mt.hpp:108
Constraint & constraint() const
Constraint.
Definition: problem_mt.hpp:99
void setFunctionValue(Real functionValue)
Definition: problem_mt.hpp:117
void setCurrentValue(const Array &currentValue)
Definition: problem_mt.hpp:110
Multithreaded version of QL class.