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