1 /* Copyright 2017-2021 PaGMO development team
2
3 This file is part of the PaGMO library.
4
5 The PaGMO library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7
8 * the GNU Lesser General Public License as published by the Free
9 Software Foundation; either version 3 of the License, or (at your
10 option) any later version.
11
12 or
13
14 * the GNU General Public License as published by the Free Software
15 Foundation; either version 3 of the License, or (at your option) any
16 later version.
17
18 or both in parallel, as here.
19
20 The PaGMO library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the PaGMO library. If not,
27 see https://www.gnu.org/licenses/. */
28
29 // MINGW-specific warnings.
30 #if defined(__GNUC__) && defined(__MINGW32__)
31 #pragma GCC diagnostic push
32 #pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
33 #endif
34
35 #include <random>
36 #include <string>
37 #include <utility>
38
39 #include <pagmo/exceptions.hpp>
40 #include <pagmo/problem.hpp>
41 #include <pagmo/rng.hpp>
42 #include <pagmo/types.hpp>
43 #include <pagmo/utils/generic.hpp>
44 #include <pagmo/utils/genetic_operators.hpp>
45
46 namespace
47 {
48 // Helper function for implementation of the binary crossover.
sbx_betaq(double beta,double eta_c,double rand01)49 double sbx_betaq(double beta, double eta_c, double rand01)
50 {
51 double alpha = 2. - std::pow(beta, -(eta_c + 1.));
52 if (rand01 < 1. / alpha) {
53 return std::pow(rand01 * alpha, 1. / (eta_c + 1.));
54 } else {
55 return std::pow(1. / (2. - rand01 * alpha), 1. / (eta_c + 1.));
56 }
57 }
58 } // namespace
59
60 namespace pagmo
61 {
62
63 namespace detail
64 {
65
66 // Implementation of the binary crossover.
67 // Requires the distribution index eta_c in [1, 100], crossover probability p_cr in[0,1] -> undefined algo behaviour
68 // otherwise Requires dimensions of the parent and bounds to be equal -> out of bound reads. nix is the integer
69 // dimension (integer alleles assumed at the end of the chromosome)
70
sbx_crossover_impl(const vector_double & parent1,const vector_double & parent2,const std::pair<vector_double,vector_double> & bounds,vector_double::size_type nix,const double p_cr,const double eta_c,detail::random_engine_type & random_engine)71 std::pair<vector_double, vector_double> sbx_crossover_impl(const vector_double &parent1, const vector_double &parent2,
72 const std::pair<vector_double, vector_double> &bounds,
73 vector_double::size_type nix, const double p_cr,
74 const double eta_c,
75 detail::random_engine_type &random_engine)
76 {
77 // Decision vector dimensions
78 auto nx = parent1.size();
79 auto ncx = nx - nix;
80 // Problem bounds
81 const vector_double &lb = bounds.first;
82 const vector_double &ub = bounds.second;
83 vector_double::size_type site1, site2;
84 // Initialize the child decision vectors
85 vector_double child1 = parent1;
86 vector_double child2 = parent2;
87 // Random distributions
88 std::uniform_real_distribution<> drng(0., 1.); // to generate a number in [0, 1)
89
90 // This implements a Simulated Binary Crossover SBX
91 if (drng(random_engine) < p_cr) { // No crossever at all will happen with probability p_cr
92 for (decltype(ncx) i = 0u; i < ncx; i++) {
93 // Each chromosome value has 0.5 probability to be crossovered.
94 if ((drng(random_engine) < 0.5) && (std::abs(parent1[i] - parent2[i])) > 1e-14 && lb[i] != ub[i]) {
95 double y1, y2, yl, yu, beta, betaq, c1, c2, rand01;
96 if (parent1[i] < parent2[i]) {
97 y1 = parent1[i];
98 y2 = parent2[i];
99 } else {
100 y1 = parent2[i];
101 y2 = parent1[i];
102 }
103 yl = lb[i];
104 yu = ub[i];
105
106 rand01 = drng(random_engine);
107 beta = 1. + (2. * (y1 - yl) / (y2 - y1));
108 betaq = sbx_betaq(beta, eta_c, rand01);
109 c1 = 0.5 * ((y1 + y2) - betaq * (y2 - y1));
110
111 beta = 1. + (2. * (yu - y2) / (y2 - y1));
112 betaq = sbx_betaq(beta, eta_c, rand01);
113 c2 = 0.5 * ((y1 + y2) + betaq * (y2 - y1));
114
115 if (c1 < lb[i]) c1 = lb[i];
116 if (c2 < lb[i]) c2 = lb[i];
117 if (c1 > ub[i]) c1 = ub[i];
118 if (c2 > ub[i]) c2 = ub[i];
119 if (drng(random_engine) < .5) {
120 child1[i] = c1;
121 child2[i] = c2;
122 } else {
123 child1[i] = c2;
124 child2[i] = c1;
125 }
126 }
127 }
128
129 // This implements two-points crossover and applies it to the integer part of the chromosome.
130 if (nix > 0u) {
131 std::uniform_int_distribution<vector_double::size_type> ra_num(ncx, nx - 1u);
132 site1 = ra_num(random_engine);
133 site2 = ra_num(random_engine);
134 if (site1 > site2) {
135 std::swap(site1, site2);
136 }
137 for (decltype(site2) j = site1; j <= site2; ++j) {
138 child1[j] = parent2[j];
139 child2[j] = parent1[j];
140 }
141 }
142 }
143 return std::make_pair(std::move(child1), std::move(child2));
144 }
145
146 // Performs polynomial mutation. Requires all sizes to be consistent. Does not check if input is well formed.
147 // p_m is the mutation probability, eta_m the distibution index
polynomial_mutation_impl(vector_double & child,const std::pair<vector_double,vector_double> & bounds,vector_double::size_type nix,const double p_m,const double eta_m,detail::random_engine_type & random_engine)148 void polynomial_mutation_impl(vector_double &child, const std::pair<vector_double, vector_double> &bounds,
149 vector_double::size_type nix, const double p_m, const double eta_m,
150 detail::random_engine_type &random_engine)
151 {
152 // Decision vector dimensions
153 auto nx = child.size();
154 auto ncx = nx - nix;
155 // Problem bounds
156 const auto &lb = bounds.first;
157 const auto &ub = bounds.second;
158 // declarations
159 double rnd, delta1, delta2, mut_pow, deltaq;
160 double y, yl, yu, val, xy;
161 // Random distributions
162 std::uniform_real_distribution<> drng(0., 1.); // to generate a number in [0, 1)
163 // This implements the real polinomial mutation and applies it to the non integer part of the decision vector
164 for (decltype(ncx) j = 0u; j < ncx; ++j) {
165 if (drng(random_engine) < p_m && lb[j] != ub[j]) {
166 y = child[j];
167 yl = lb[j];
168 yu = ub[j];
169 delta1 = (y - yl) / (yu - yl);
170 delta2 = (yu - y) / (yu - yl);
171 rnd = drng(random_engine);
172 mut_pow = 1. / (eta_m + 1.);
173 if (rnd < 0.5) {
174 xy = 1. - delta1;
175 val = 2. * rnd + (1. - 2. * rnd) * (std::pow(xy, (eta_m + 1.)));
176 deltaq = std::pow(val, mut_pow) - 1.;
177 } else {
178 xy = 1. - delta2;
179 val = 2. * (1. - rnd) + 2. * (rnd - 0.5) * (std::pow(xy, (eta_m + 1.)));
180 deltaq = 1. - (std::pow(val, mut_pow));
181 }
182 y = y + deltaq * (yu - yl);
183 if (y < yl) y = yl;
184 if (y > yu) y = yu;
185 child[j] = y;
186 }
187 }
188
189 // This implements the integer mutation for an individual
190 for (decltype(nx) j = ncx; j < nx; ++j) {
191 if (drng(random_engine) < p_m) {
192 // We need to draw a random integer in [lb, ub].
193 auto mutated = uniform_integral_from_range(lb[j], ub[j], random_engine);
194 child[j] = mutated;
195 }
196 }
197 }
198
199 // Multi-objective tournament selection. Requires all sizes to be consistent. Does not check if input is well formed.
mo_tournament_selection_impl(vector_double::size_type idx1,vector_double::size_type idx2,const std::vector<vector_double::size_type> & non_domination_rank,const std::vector<double> & crowding_d,detail::random_engine_type & random_engine)200 vector_double::size_type mo_tournament_selection_impl(vector_double::size_type idx1, vector_double::size_type idx2,
201 const std::vector<vector_double::size_type> &non_domination_rank,
202 const std::vector<double> &crowding_d,
203 detail::random_engine_type &random_engine)
204 {
205 if (non_domination_rank[idx1] < non_domination_rank[idx2]) return idx1;
206 if (non_domination_rank[idx1] > non_domination_rank[idx2]) return idx2;
207 if (crowding_d[idx1] > crowding_d[idx2]) return idx1;
208 if (crowding_d[idx1] < crowding_d[idx2]) return idx2;
209 std::uniform_real_distribution<> drng(0., 1.); // to generate a number in [0, 1)
210 return ((drng(random_engine) < 0.5) ? idx1 : idx2);
211 }
212
213 } // namespace detail
214
215 /// Simulated Binary Crossover
216 /**
217 * This function perform a simulated binary crossover (SBX) among two parents.
218 * The SBX crossover was designed to preserve average property and the spread factor
219 * property of one-point crossover in binary encoded chromosomes. The SBX will act as a simple
220 * two-points crossover over the integer part of the chromosome.
221 *
222 * Example:
223 * @code{.unparsed}
224 * detail::random_engine_type random_engine(32u);
225 * auto children = sbx_crossover({0.1, 0.2, 3}, {0.2, 2.2, -1}, {{-2,-2,-2}, {3,3,3}}, 1u, 0.9, 10, r_engine);
226 * @endcode
227 *
228 * @param parent1 first parent.
229 * @param parent2 second parent.
230 * @param bounds problem bounds.
231 * @param nix integer dimension of the problem.
232 * @param p_cr crossover probability.
233 * @param eta_c crossover distribution index.
234 * @param random_engine the pagmo random engine.
235 *
236 * @throws std::invalid_argument if:
237 * - the *bounds* size is zero.
238 * - inconsistent lengths of *parent1*, *parent2* and *bounds*.
239 * - nans or infs in the bounds.
240 * - lower bounds greater than upper bounds.
241 * - integer part larger than bounds size.
242 * - integer bounds not integers.
243 * - crossover probability or distribution index are not finite numbers.
244 *
245 * @returns the crossovered children
246 */
sbx_crossover(const vector_double & parent1,const vector_double & parent2,const std::pair<vector_double,vector_double> & bounds,vector_double::size_type nix,const double p_cr,const double eta_c,detail::random_engine_type & random_engine)247 std::pair<vector_double, vector_double> sbx_crossover(const vector_double &parent1, const vector_double &parent2,
248 const std::pair<vector_double, vector_double> &bounds,
249 vector_double::size_type nix, const double p_cr,
250 const double eta_c, detail::random_engine_type &random_engine)
251 {
252 // We reuse a check originally implemented for the pagmo::problem constructor. This allows to substantially shortens
253 // the explicit tests needed.
254 detail::check_problem_bounds(bounds, nix);
255 if (parent1.size() != parent2.size()) {
256 pagmo_throw(std::invalid_argument,
257 "The length of the chromosomes of the parents should be equal: parent1 length is "
258 + std::to_string(parent1.size()) + ", while parent2 length is "
259 + std::to_string(parent2.size()));
260 }
261 if (parent1.size() != bounds.first.size()) {
262 pagmo_throw(
263 std::invalid_argument,
264 "The length of the chromosomes of the parents should be the same as that of the bounds: parent1 length is "
265 + std::to_string(parent1.size()) + ", while the bounds length is "
266 + std::to_string(bounds.first.size()));
267 }
268 for (decltype(bounds.first.size()) i = 0u; i < bounds.first.size(); ++i) {
269 if (!(std::isfinite(bounds.first[i]) && std::isfinite(bounds.second[i]))) {
270 pagmo_throw(std::invalid_argument, "Infinite value detected in the bounds at position: " + std::to_string(i)
271 + ". Cannot perform Simulated Binary Crossover.");
272 }
273 }
274 if (!std::isfinite(p_cr)) {
275 pagmo_throw(std::invalid_argument, "Crossover probability is not finite, value is: " + std::to_string(p_cr));
276 }
277 if (!std::isfinite(eta_c)) {
278 pagmo_throw(std::invalid_argument,
279 "Crossover distribution index is not finite, value is: " + std::to_string(eta_c));
280 }
281 return detail::sbx_crossover_impl(parent1, parent2, bounds, nix, p_cr, eta_c, random_engine);
282 }
283
284 /// Polynomial mutation
285 /**
286 * This function performs the polynomial mutation proposed by Agrawal and Deb over some chromosome.
287 *
288 * @see https://www.iitk.ac.in/kangal/papers/k2012016.pdf
289 *
290 * Example:
291 * @code{.unparsed}
292 * detail::random_engine_type random_engine(32u);
293 * vector_double child = {-1.13, 3.21, 4};
294 * polynomial_mutation(child, {{-2,-2,-2}, {5,5,5}}, 1u, 0.9, 35, r_engine);
295 * @endcode
296 *
297 * @param dv chromosome to be mutated.
298 * @param bounds problem bounds.
299 * @param nix integer dimension of the problem.
300 * @param p_m mutation probability.
301 * @param eta_m mutation distribution index (siggested to be in [20, 100]).
302 * @param random_engine the pagmo random engine
303 *
304 * @throws std::invalid_argument if:
305 * - the *bounds* size is zero.
306 * - inconsistent lengths of *child* and *bounds*.
307 * - nans or infs in the bounds.
308 * - lower bounds greater than upper bounds.
309 * - integer part larger than bounds size.
310 * - integer bounds not integers.
311 * - mutation probability or distribution index are not finite numbers.
312 */
polynomial_mutation(vector_double & dv,const std::pair<vector_double,vector_double> & bounds,vector_double::size_type nix,const double p_m,const double eta_m,detail::random_engine_type & random_engine)313 void polynomial_mutation(vector_double &dv, const std::pair<vector_double, vector_double> &bounds,
314 vector_double::size_type nix, const double p_m, const double eta_m,
315 detail::random_engine_type &random_engine)
316 {
317 detail::check_problem_bounds(bounds, nix);
318 if (dv.size() != bounds.first.size()) {
319 pagmo_throw(std::invalid_argument,
320 "The length of the chromosome should be the same as that of the bounds: detected length is "
321 + std::to_string(dv.size()) + ", while the bounds length is "
322 + std::to_string(bounds.first.size()));
323 }
324 for (decltype(bounds.first.size()) i = 0u; i < bounds.first.size(); ++i) {
325 if (!(std::isfinite(bounds.first[i]) && std::isfinite(bounds.second[i]))) {
326 pagmo_throw(std::invalid_argument, "Infinite value detected in the bounds at position: " + std::to_string(i)
327 + ". Cannot perform Simulated Binary Crossover.");
328 }
329 }
330 if (!std::isfinite(p_m)) {
331 pagmo_throw(std::invalid_argument, "Mutation probability is not finite, value is: " + std::to_string(p_m));
332 }
333 if (!std::isfinite(eta_m)) {
334 pagmo_throw(std::invalid_argument,
335 "Mutation distribution index is not finite, value is: " + std::to_string(eta_m));
336 }
337 return detail::polynomial_mutation_impl(dv, bounds, nix, p_m, eta_m, random_engine);
338 }
339
340 } // namespace pagmo
341