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 #include <algorithm>
30 #include <cmath>
31 #include <iomanip>
32 #include <sstream>
33 #include <stdexcept>
34 #include <string>
35 
36 #include <pagmo/algorithms/compass_search.hpp>
37 #include <pagmo/algorithms/mbh.hpp>
38 #include <pagmo/exceptions.hpp>
39 #include <pagmo/io.hpp>
40 #include <pagmo/rng.hpp>
41 #include <pagmo/s11n.hpp>
42 #include <pagmo/threading.hpp>
43 #include <pagmo/types.hpp>
44 #include <pagmo/utils/constrained.hpp>
45 #include <pagmo/utils/generic.hpp>
46 
47 // MINGW-specific warnings.
48 #if defined(__GNUC__) && defined(__MINGW32__)
49 #pragma GCC diagnostic push
50 #pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
51 #endif
52 
53 namespace pagmo
54 {
55 
56 /// Default constructor.
57 /**
58  * The default constructor will initialize the algorithm with the following parameters:
59  * - inner algorithm: pagmo::compass_search;
60  * - consecutive runs of the inner algorithm that need to result in no improvement for pagmo::mbh to stop: 5;
61  * - scalar perturbation: 1E-2;
62  * - seed: random.
63  *
64  * @throws unspecified any exception thrown by the constructor of pagmo::algorithm.
65  */
mbh()66 mbh::mbh() : m_algorithm(compass_search{}), m_stop(5u), m_perturb(1, 1e-2), m_verbosity(0u)
67 {
68     const auto rnd = pagmo::random_device::next();
69     m_seed = rnd;
70     m_e.seed(rnd);
71 }
72 
scalar_ctor_impl(double perturb)73 void mbh::scalar_ctor_impl(double perturb)
74 {
75     if (std::isnan(perturb) || perturb > 1. || perturb <= 0.) {
76         pagmo_throw(std::invalid_argument, "The scalar perturbation must be in (0, 1], while a value of "
77                                                + std::to_string(perturb) + " was detected.");
78     }
79 }
80 
vector_ctor_impl(const vector_double & perturb)81 void mbh::vector_ctor_impl(const vector_double &perturb)
82 {
83     if (!std::all_of(perturb.begin(), perturb.end(),
84                      [](double item) { return (!std::isnan(item) && item > 0. && item <= 1.); })) {
85         pagmo_throw(std::invalid_argument,
86                     "The perturbation must have all components in (0, 1], while that is not the case.");
87     }
88 }
89 
90 /// Evolve method.
91 /**
92  * This method will evolve the input population up to when \p stop consecutve runs of the internal
93  * algorithm do not improve the solution.
94  *
95  * @param pop population to be evolved.
96  *
97  * @return evolved population.
98  *
99  * @throws std::invalid_argument if the problem is multi-objective or stochastic, or if the perturbation vector size
100  * does not equal the problem size.
101  */
evolve(population pop) const102 population mbh::evolve(population pop) const
103 {
104     // We store some useful variables
105     const auto &prob = pop.get_problem(); // This is a const reference, so using set_seed for example will not be
106                                           // allowed
107     auto dim = prob.get_nx();             // This getter does not return a const reference but a copy
108     auto nec = prob.get_nec();            // This getter does not return a const reference but a copy
109     const auto bounds = prob.get_bounds();
110     const auto &lb = bounds.first;
111     const auto &ub = bounds.second;
112     auto NP = pop.size();
113 
114     auto fevals0 = prob.get_fevals(); // discount for the already made fevals
115     unsigned count = 1u;              // regulates the screen output
116 
117     // PREAMBLE-------------------------------------------------------------------------------------------------
118     if (prob.get_nobj() != 1u) {
119         pagmo_throw(std::invalid_argument, "Multiple objectives detected in " + prob.get_name() + " instance. "
120                                                + get_name() + " cannot deal with them");
121     }
122     if (prob.is_stochastic()) {
123         pagmo_throw(std::invalid_argument, "The input problem " + prob.get_name() + " appears to be stochastic, "
124                                                + get_name() + " cannot deal with it");
125     }
126     // Get out if there is nothing to do.
127     if (m_stop == 0u) {
128         return pop;
129     }
130     // Check if the perturbation vector has size 1, in which case the whole perturbation vector is filled with
131     // the same value equal to its first entry
132     if (m_perturb.size() == 1u) {
133         for (decltype(dim) i = 1u; i < dim; ++i) {
134             m_perturb.push_back(m_perturb[0]);
135         }
136     }
137     // Check that the perturbation vector size equals the size of the problem
138     if (m_perturb.size() != dim) {
139         pagmo_throw(std::invalid_argument, "The perturbation vector size is: " + std::to_string(m_perturb.size())
140                                                + ", while the problem dimension is: " + std::to_string(dim)
141                                                + ". They need to be equal for MBH to work.");
142     }
143     // ---------------------------------------------------------------------------------------------------------
144 
145     // No throws, all valid: we clear the logs
146     m_log.clear();
147     // mbh main loop
148     unsigned i = 0u;
149     while (i < m_stop) {
150         // 1 - We make a copy of the current population
151         population pop_old(pop);
152         // 2 - We perturb the current population (NP funevals are made here)
153         for (decltype(NP) j = 0u; j < NP; ++j) {
154             vector_double tmp_x(dim);
155             for (decltype(dim) k = 0u; k < dim; ++k) {
156                 tmp_x[k]
157                     = uniform_real_from_range(std::max(pop.get_x()[j][k] - m_perturb[k] * (ub[k] - lb[k]), lb[k]),
158                                               std::min(pop.get_x()[j][k] + m_perturb[k] * (ub[k] - lb[k]), ub[k]), m_e);
159             }
160             pop.set_x(j, tmp_x); // fitness is evaluated here
161         }
162         // 3 - We evolve the current population with the selected algorithm
163         pop = m_algorithm.evolve(pop);
164         i++;
165         // 4 - We reset the counter if we have improved, otherwise we reset the population
166         if (compare_fc(pop.get_f()[pop.best_idx()], pop_old.get_f()[pop_old.best_idx()], nec, prob.get_c_tol())) {
167             i = 0u;
168         } else {
169             for (decltype(NP) j = 0u; j < NP; ++j) {
170                 pop.set_xf(j, pop_old.get_x()[j], pop_old.get_f()[j]);
171             }
172         }
173         // 5 - We log to screen
174         if (m_verbosity > 0u) {
175             // Prints a log line after each call to the inner algorithm
176             // 1 - Every 50 lines print the column names
177             if (count % 50u == 1u) {
178                 print("\n", std::setw(7), "Fevals:", std::setw(15), "Best:", std::setw(15), "Violated:", std::setw(15),
179                       "Viol. Norm:", std::setw(15), "Trial:", '\n');
180             }
181             // 2 - Print
182             auto cur_best_f = pop.get_f()[pop.best_idx()];
183             auto c1eq = detail::test_eq_constraints(cur_best_f.data() + 1, cur_best_f.data() + 1 + nec,
184                                                     prob.get_c_tol().data());
185             auto c1ineq = detail::test_ineq_constraints(
186                 cur_best_f.data() + 1 + nec, cur_best_f.data() + cur_best_f.size(), prob.get_c_tol().data() + nec);
187             auto n = prob.get_nc() - c1eq.first - c1ineq.first;
188             auto l = c1eq.second + c1ineq.second;
189             print(std::setw(7), prob.get_fevals() - fevals0, std::setw(15), cur_best_f[0], std::setw(15), n,
190                   std::setw(15), l, std::setw(15), i);
191             if (!prob.feasibility_f(pop.get_f()[pop.best_idx()])) {
192                 std::cout << " i";
193             }
194             ++count;
195             std::cout << std::endl; // we flush here as we want the user to read in real time ...
196             // Logs
197             m_log.emplace_back(prob.get_fevals() - fevals0, cur_best_f[0], n, l, i);
198         }
199     }
200     // We extract chromosomes and fitnesses
201     return pop;
202 }
203 
204 /// Set the seed.
205 /**
206  * @param seed the seed controlling the algorithm's stochastic behaviour.
207  */
set_seed(unsigned seed)208 void mbh::set_seed(unsigned seed)
209 {
210     m_e.seed(seed);
211     m_seed = seed;
212 }
213 
214 /// Set the perturbation vector.
215 /**
216  * @param perturb the perturbation vector.
217  *
218  * @throws std::invalid_argument if not all the components of the perturbation vector are in the (0,1] range.
219  */
set_perturb(const vector_double & perturb)220 void mbh::set_perturb(const vector_double &perturb)
221 {
222     if (!std::all_of(perturb.begin(), perturb.end(),
223                      [](double item) { return (!std::isnan(item) && item > 0. && item <= 1.); })) {
224         pagmo_throw(std::invalid_argument,
225                     "The perturbation must have all components in (0, 1], while that is not the case.");
226     }
227     m_perturb = perturb;
228 }
229 
230 /// Algorithm's thread safety level.
231 /**
232  * The thread safety of this meta-algorithm is the minimum between the thread safety
233  * of the internal pagmo::algorithm and the basic thread safety level. I.e., this algorithm
234  * never provides more than the basic thread safety level.
235  *
236  * @return the thread safety level of this algorithm.
237  */
get_thread_safety() const238 thread_safety mbh::get_thread_safety() const
239 {
240     return std::min(m_algorithm.get_thread_safety(), thread_safety::basic);
241 }
242 
243 /// Extra info.
244 /**
245  * @return a string containing extra info on the algorithm.
246  */
get_extra_info() const247 std::string mbh::get_extra_info() const
248 {
249     std::ostringstream ss;
250     stream(ss, "\tStop: ", m_stop);
251     stream(ss, "\n\tPerturbation vector: ", m_perturb);
252     stream(ss, "\n\tSeed: ", m_seed);
253     stream(ss, "\n\tVerbosity: ", m_verbosity);
254     stream(ss, "\n\n\tInner algorithm: ", m_algorithm.get_name());
255     stream(ss, "\n\tInner algorithm extra info: ");
256     stream(ss, "\n", m_algorithm.get_extra_info());
257     return ss.str();
258 }
259 
260 // Object serialization.
261 template <typename Archive>
serialize(Archive & ar,unsigned)262 void mbh::serialize(Archive &ar, unsigned)
263 {
264     detail::archive(ar, m_algorithm, m_stop, m_perturb, m_e, m_seed, m_verbosity, m_log);
265 }
266 
267 } // namespace pagmo
268 
269 PAGMO_S11N_ALGORITHM_IMPLEMENT(pagmo::mbh)
270