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 <atomic>
31 #include <cassert>
32 #include <cstddef>
33 #include <cstdlib>
34 #include <iostream>
35 #include <iterator>
36 #include <limits>
37 #include <memory>
38 #include <mutex>
39 #include <stdexcept>
40 #include <string>
41 #include <tuple>
42 #include <type_traits>
43 #include <utility>
44 #include <vector>
45
46 #include <boost/numeric/conversion/cast.hpp>
47
48 #include <pagmo/archipelago.hpp>
49 #include <pagmo/exceptions.hpp>
50 #include <pagmo/io.hpp>
51 #include <pagmo/island.hpp>
52 #include <pagmo/topology.hpp>
53 #include <pagmo/types.hpp>
54
55 // MINGW-specific warnings.
56 #if defined(__GNUC__) && defined(__MINGW32__)
57 #pragma GCC diagnostic push
58 #pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
59 #endif
60
61 namespace pagmo
62 {
63
64 // NOTE: same utility method as in pagmo::island, see there.
wait_check_ignore()65 void archipelago::wait_check_ignore()
66 {
67 try {
68 wait_check();
69 } catch (...) {
70 }
71 }
72
73 /// Default constructor.
74 /**
75 * \verbatim embed:rst:leading-asterisk
76 * The default constructor will initialise an empty archipelago with a
77 * default-constructed (i.e., :cpp:class:`~pagmo::unconnected`) topology,
78 * a point-to-point :cpp:enum:`~pagmo::migration_type` and a
79 * preserve :cpp:enum:`~pagmo::migrant_handling` policy.
80 * \endverbatim
81 */
archipelago()82 archipelago::archipelago()
83 : m_migr_type(migration_type::p2p), // Default: point-to-point migration type.
84 m_migr_handling(migrant_handling::preserve) // Default: preserve migrants.
85 {
86 }
87
88 /// Copy constructor.
89 /**
90 * The copy constructor will perform a deep copy of \p other.
91 *
92 * @param other the archipelago that will be copied.
93 *
94 * @throws unspecified any exception thrown by the public interface
95 * of pagmo::archipelago.
96 */
archipelago(const archipelago & other)97 archipelago::archipelago(const archipelago &other)
98 {
99 for (const auto &iptr : other.m_islands) {
100 // This will end up copying the island members,
101 // and assign the archi pointer, and associating ids to island pointers.
102 push_back(*iptr);
103 }
104
105 // Set the migrants.
106 m_migrants = other.get_migrants_db();
107
108 // Set the migration log.
109 m_migr_log = other.get_migration_log();
110
111 // Set the topology.
112 m_topology = other.get_topology();
113
114 // Migration type and migrant handling policy.
115 m_migr_type.store(other.m_migr_type.load(std::memory_order_relaxed), std::memory_order_relaxed);
116 m_migr_handling.store(other.m_migr_handling.load(std::memory_order_relaxed), std::memory_order_relaxed);
117 }
118
119 /// Move constructor.
120 /**
121 * The move constructor will wait for any ongoing evolution in \p other to finish
122 * and it will then transfer the state of \p other into \p this. After the move,
123 * \p other is left in a state which is assignable and destructible.
124 *
125 * @param other the archipelago that will be moved.
126 */
archipelago(archipelago && other)127 archipelago::archipelago(archipelago &&other) noexcept
128 {
129 // NOTE: in move operations we have to wait, because the ongoing
130 // island evolutions are interacting with their hosting archi 'other'.
131 // We cannot just move in the vector of islands.
132 // NOTE: we want to ensure that other is in a known state
133 // after the move, so that we can run assertion checks in
134 // the destructor in debug mode.
135 other.wait_check_ignore();
136
137 // Move in the islands, make sure that other is cleared.
138 m_islands = std::move(other.m_islands);
139 other.m_islands.clear();
140 // Re-direct the archi pointers to point to this.
141 for (const auto &iptr : m_islands) {
142 iptr->m_ptr->archi_ptr = this;
143 }
144
145 // Move over the indices, clear other.
146 // NOTE: the indices are still valid as above we just moved in a vector
147 // of unique_ptrs, without changing their content.
148 m_idx_map = std::move(other.m_idx_map);
149 other.m_idx_map.clear();
150
151 // Move over the migrants, clear other.
152 m_migrants = std::move(other.m_migrants);
153 other.m_migrants.clear();
154
155 // Move over the migration log, clear other.
156 m_migr_log = std::move(other.m_migr_log);
157 other.m_migr_log.clear();
158
159 // Move over the topology. No need to clear here as we know
160 // in which state the topology will be in after the move.
161 m_topology = std::move(other.m_topology);
162
163 // Migration type and migrant handling policy.
164 m_migr_type.store(other.m_migr_type.load(std::memory_order_relaxed), std::memory_order_relaxed);
165 m_migr_handling.store(other.m_migr_handling.load(std::memory_order_relaxed), std::memory_order_relaxed);
166 }
167
168 /// Copy assignment.
169 /**
170 * Copy assignment is implemented as copy construction followed by a move assignment.
171 *
172 * @param other the assignment argument.
173 *
174 * @return a reference to \p this.
175 *
176 * @throws unspecified any exception thrown by the copy constructor.
177 */
operator =(const archipelago & other)178 archipelago &archipelago::operator=(const archipelago &other)
179 {
180 if (this != &other) {
181 *this = archipelago(other);
182 }
183 return *this;
184 }
185
186 /// Move assignment.
187 /**
188 * Move assignment will transfer the state of \p other into \p this, after any ongoing
189 * evolution in \p this and \p other has finished. After the move,
190 * \p other is left in a state which is assignable and destructible.
191 *
192 * @param other the assignment argument.
193 *
194 * @return a reference to \p this.
195 */
operator =(archipelago && other)196 archipelago &archipelago::operator=(archipelago &&other) noexcept
197 {
198 if (this != &other) {
199 // NOTE: as in the move ctor, we need to wait on other and this as well.
200 // This mirrors the island's behaviour.
201 // NOTE: we want to ensure that other is in a known state
202 // after the move, so that we can run assertion checks in
203 // the destructor in debug mode.
204 wait_check_ignore();
205 other.wait_check_ignore();
206
207 // Move in the islands, clear other.
208 m_islands = std::move(other.m_islands);
209 other.m_islands.clear();
210 // Re-direct the archi pointers to point to this.
211 for (const auto &iptr : m_islands) {
212 iptr->m_ptr->archi_ptr = this;
213 }
214
215 // Move the indices map, clear other.
216 m_idx_map = std::move(other.m_idx_map);
217 other.m_idx_map.clear();
218
219 // Move over the migrants, clear other.
220 m_migrants = std::move(other.m_migrants);
221 other.m_migrants.clear();
222
223 // Move over the migration log, clear other.
224 m_migr_log = std::move(other.m_migr_log);
225 other.m_migr_log.clear();
226
227 // Move over the topology.
228 m_topology = std::move(other.m_topology);
229
230 // Migration type and migrant handling policy.
231 m_migr_type.store(other.m_migr_type.load(std::memory_order_relaxed), std::memory_order_relaxed);
232 m_migr_handling.store(other.m_migr_handling.load(std::memory_order_relaxed), std::memory_order_relaxed);
233 }
234 return *this;
235 }
236
237 /// Destructor.
238 /**
239 * The destructor will call archipelago::wait_check() internally, ignoring any exception that might be thrown,
240 * and run checks in debug mode.
241 */
~archipelago()242 archipelago::~archipelago()
243 {
244 // NOTE: make sure we stop the archi before running checks below without locking.
245 // NOTE: this is also important to ensure everything is stopped before we start
246 // destroying things, so that the destruction order will not matter.
247 wait_check_ignore();
248
249 // NOTE: we made sure in the move ctor/assignment that the island vector, the migrants and the indices
250 // map are all cleared out after a move. Thus we can safely assert the following.
251 assert(std::all_of(m_islands.begin(), m_islands.end(),
252 [this](const std::unique_ptr<island> &iptr) { return iptr->m_ptr->archi_ptr == this; }));
253 assert(m_idx_map.size() == m_islands.size());
254 #if !defined(NDEBUG)
255 for (size_type i = 0; i < m_islands.size(); ++i) {
256 // Ensure the map of indices is correct.
257 assert(m_idx_map.find(m_islands[i].get()) != m_idx_map.end());
258 assert(m_idx_map.find(m_islands[i].get())->second == i);
259 }
260 #endif
261 }
262
263 /// Mutable island access.
264 /**
265 * This subscript operator can be used to access the <tt>i</tt>-th island of the archipelago (that is,
266 * the <tt>i</tt>-th island that was inserted via push_back()). References returned by this method are valid even
267 * after a push_back() invocation. Assignment and destruction of the archipelago will invalidate island references
268 * obtained via this method.
269 *
270 * \verbatim embed:rst:leading-asterisk
271 * .. warning::
272 *
273 * The mutable version of the subscript operator exists solely to allow calling non-const methods
274 * on the islands. Assigning an island via a reference obtained through this operator will result
275 * in undefined behaviour.
276 *
277 * \endverbatim
278 *
279 * @param i the index of the island to be accessed.
280 *
281 * @return a reference to the <tt>i</tt>-th island of the archipelago.
282 *
283 * @throws std::out_of_range if \p i is not less than the size of the archipelago.
284 */
operator [](size_type i)285 island &archipelago::operator[](size_type i)
286 {
287 if (i >= size()) {
288 pagmo_throw(std::out_of_range, "cannot access the island at index " + std::to_string(i)
289 + ": the archipelago has a size of only " + std::to_string(size()));
290 }
291 return *m_islands[i];
292 }
293
294 /// Const island access.
295 /**
296 * This subscript operator can be used to access the <tt>i</tt>-th island of the archipelago (that is,
297 * the <tt>i</tt>-th island that was inserted via push_back()). References returned by this method are valid even
298 * after a push_back() invocation. Assignment and destruction of the archipelago will invalidate island references
299 * obtained via this method.
300 *
301 * @param i the index of the island to be accessed.
302 *
303 * @return a const reference to the <tt>i</tt>-th island of the archipelago.
304 *
305 * @throws std::out_of_range if \p i is not less than the size of the archipelago.
306 */
operator [](size_type i) const307 const island &archipelago::operator[](size_type i) const
308 {
309 if (i >= size()) {
310 pagmo_throw(std::out_of_range, "cannot access the island at index " + std::to_string(i)
311 + ": the archipelago has a size of only " + std::to_string(size()));
312 }
313 return *m_islands[i];
314 }
315
316 /// Size.
317 /**
318 * @return the number of islands in the archipelago.
319 */
size() const320 archipelago::size_type archipelago::size() const
321 {
322 return m_islands.size();
323 }
324
evolve(unsigned n)325 void archipelago::evolve(unsigned n)
326 {
327 for (auto &iptr : m_islands) {
328 iptr->evolve(n);
329 }
330 }
331
332 /// Block until all evolutions have finished.
333 /**
334 * This method will call island::wait() on all the islands of the archipelago. Exceptions thrown by island
335 * evolutions can be re-raised via wait_check(): they will **not** be re-thrown by this method. Also, contrary to
336 * wait_check(), this method will **not** reset the status of the archipelago: after a call to wait(), status() will
337 * always return either evolve_status::idle or evolve_status::idle_error.
338 */
wait()339 void archipelago::wait() noexcept
340 {
341 for (const auto &iptr : m_islands) {
342 iptr->wait();
343 }
344 }
345
346 /// Block until all evolutions have finished and raise the first exception that was encountered.
347 /**
348 * This method will call island::wait_check() on all the islands of the archipelago (following
349 * the order in which the islands were inserted into the archipelago).
350 * The first exception raised by island::wait_check() will be re-raised by this method,
351 * and all the exceptions thrown by the other calls to island::wait_check() will be ignored.
352 *
353 * Note that wait_check() resets the status of the archipelago: after a call to wait_check(), status() will always
354 * return evolve_status::idle.
355 *
356 * @throws unspecified any exception thrown by any evolution task queued in the archipelago's
357 * islands.
358 */
wait_check()359 void archipelago::wait_check()
360 {
361 for (auto it = m_islands.begin(); it != m_islands.end(); ++it) {
362 try {
363 (*it)->wait_check();
364 } catch (...) {
365 for (it = it + 1; it != m_islands.end(); ++it) {
366 (*it)->wait_check_ignore();
367 }
368 throw;
369 }
370 }
371 }
372
373 /// Status of the archipelago.
374 /**
375 * This method will return a pagmo::evolve_status flag indicating the current status of
376 * asynchronous operations in the archipelago. The flag will be:
377 *
378 * * evolve_status::idle if, for all the islands in the archipelago, island::status() returns
379 * evolve_status::idle;
380 * * evolve_status::busy if, for at least one island in the archipelago, island::status() returns
381 * evolve_status::busy, and for no island island::status() returns an error status;
382 * * evolve_status::idle_error if no island in the archipelago is busy and for at least one island
383 * island::status() returns evolve_status::idle_error;
384 * * evolve_status::busy_error if, for at least one island in the archipelago, island::status() returns
385 * an error status and at least one island is busy.
386 *
387 * Note that after a call to wait_check(), status() will always return evolve_status::idle,
388 * and after a call to wait(), status() will always return either evolve_status::idle or
389 * evolve_status::idle_error.
390 *
391 * @return a flag indicating the current status of asynchronous operations in the archipelago.
392 */
status() const393 evolve_status archipelago::status() const
394 {
395 decltype(m_islands.size()) n_idle = 0, n_busy = 0, n_idle_error = 0, n_busy_error = 0;
396 for (const auto &iptr : m_islands) {
397 switch (iptr->status()) {
398 case evolve_status::idle:
399 ++n_idle;
400 break;
401 case evolve_status::busy:
402 ++n_busy;
403 break;
404 case evolve_status::idle_error:
405 ++n_idle_error;
406 break;
407 case evolve_status::busy_error:
408 ++n_busy_error;
409 break;
410 }
411 }
412
413 // If we have at last one busy error, the global state
414 // is also busy error.
415 if (n_busy_error) {
416 return evolve_status::busy_error;
417 }
418
419 // The other error case: at least one island is idle with error.
420 if (n_idle_error) {
421 if (n_busy) {
422 // At least one island is idle with error and at least one island is busy:
423 // return busy error.
424 return evolve_status::busy_error;
425 }
426 // No island is busy at all, at least one island is idle with error.
427 return evolve_status::idle_error;
428 }
429
430 // No error in any island. If they are all idle, the global state is idle,
431 // otherwise busy.
432 return n_idle == m_islands.size() ? evolve_status::idle : evolve_status::busy;
433 }
434
435 /// Get the fitness vectors of the islands' champions.
436 /**
437 * @return a collection of the fitness vectors of the islands' champions.
438 *
439 * @throws unspecified any exception thrown by population::champion_f() or
440 * by memory errors in standard containers.
441 */
get_champions_f() const442 std::vector<vector_double> archipelago::get_champions_f() const
443 {
444 std::vector<vector_double> retval;
445 for (const auto &isl_ptr : m_islands) {
446 retval.emplace_back(isl_ptr->get_population().champion_f());
447 }
448 return retval;
449 }
450
451 /// Get the decision vectors of the islands' champions.
452 /**
453 * @return a collection of the decision vectors of the islands' champions.
454 *
455 * @throws unspecified any exception thrown by population::champion_x() or
456 * by memory errors in standard containers.
457 */
get_champions_x() const458 std::vector<vector_double> archipelago::get_champions_x() const
459 {
460 std::vector<vector_double> retval;
461 for (const auto &isl_ptr : m_islands) {
462 retval.emplace_back(isl_ptr->get_population().champion_x());
463 }
464 return retval;
465 }
466
push_back_impl(std::unique_ptr<island> && new_island)467 void archipelago::push_back_impl(std::unique_ptr<island> &&new_island)
468 {
469 // Assign the pointer to this.
470 // NOTE: perhaps this can be delayed until the last line?
471 // The reason would be that, in case of exceptions,
472 // new_island will be destroyed while pointing
473 // to an archipelago, although the island is not
474 // actually in the archipelago. In theory this
475 // could lead to assertion failures on destruction, if
476 // we implement archipelago-based checks in the dtor
477 // of island. This is not the case at the moment.
478 new_island->m_ptr->archi_ptr = this;
479
480 // Try to make space for the new island in the islands vector.
481 // LCOV_EXCL_START
482 if (m_islands.size() == std::numeric_limits<decltype(m_islands.size())>::max()) {
483 pagmo_throw(std::overflow_error, "cannot add a new island to an archipelago due to an overflow condition");
484 }
485 // LCOV_EXCL_STOP
486 m_islands.reserve(m_islands.size() + 1u);
487
488 // Try to make space for the new migrants entry.
489 // LCOV_EXCL_START
490 if (m_migrants.size() == std::numeric_limits<decltype(m_migrants.size())>::max()) {
491 pagmo_throw(std::overflow_error, "cannot add a new island to an archipelago due to an overflow condition");
492 }
493 // LCOV_EXCL_STOP
494 {
495 std::lock_guard<std::mutex> lock(m_migrants_mutex);
496 m_migrants.reserve(m_migrants.size() + 1u);
497 }
498
499 // Map the new island idx.
500 {
501 // NOTE: if anything fails here, we won't have modified the state of the archi
502 // (apart from reserving memory).
503 std::lock_guard<std::mutex> lock(m_idx_map_mutex);
504 assert(m_idx_map.find(new_island.get()) == m_idx_map.end());
505 m_idx_map.emplace(new_island.get(), m_islands.size());
506 }
507
508 // Add an empty entry to the migrants db.
509 try {
510 std::lock_guard<std::mutex> lock(m_migrants_mutex);
511 m_migrants.emplace_back();
512 } catch (...) {
513 // LCOV_EXCL_START
514 // NOTE: we get here only if the lock throws, because we made space for the
515 // new migrants above already. Better to abort in such case, as we have no
516 // reasonable path for recovering from this.
517 std::cerr << "An unrecoverable error arose while adding an island to the archipelago, aborting now."
518 << std::endl;
519 std::abort();
520 // LCOV_EXCL_STOP
521 }
522
523 // Actually add the island. This cannot fail as we already reserved space.
524 m_islands.push_back(std::move(new_island));
525
526 // Finally, push back the topology. This is required to be thread safe, no need for locks.
527 // If this fails, we will have a possibly *bad* topology in the archi, but this can
528 // always happen via a bogus set_topology() and there's nothing we can do about it.
529 m_topology.push_back();
530 }
531
532 // Get the index of an island.
533 // This function will return the index of the island \p isl in the archipelago. If \p isl does
534 // not belong to the archipelago, an error will be reaised.
get_island_idx(const island & isl) const535 archipelago::size_type archipelago::get_island_idx(const island &isl) const
536 {
537 std::lock_guard<std::mutex> lock(m_idx_map_mutex);
538 const auto ret = m_idx_map.find(&isl);
539 if (ret == m_idx_map.end()) {
540 pagmo_throw(std::invalid_argument,
541 "the index of an island in an archipelago was requested, but the island is not in the archipelago");
542 }
543 return ret->second;
544 }
545
546 /// Get the database of migrants.
547 /**
548 * \verbatim embed:rst:leading-asterisk
549 * During the evolution of an archipelago, islands will periodically
550 * store the individuals selected for migration in a *migrant database*.
551 * This is a vector of :cpp:type:`~pagmo::individuals_group_t` whose
552 * size is equal to the number of islands in the archipelago, and which
553 * contains the current candidate outgoing migrants for each island.
554 * \endverbatim
555 *
556 * @return a copy of the database of migrants.
557 *
558 * @throws unspecified any exception thrown by threading primitives or by memory allocation errors.
559 */
get_migrants_db() const560 archipelago::migrants_db_t archipelago::get_migrants_db() const
561 {
562 std::lock_guard<std::mutex> lock(m_migrants_mutex);
563 return m_migrants;
564 }
565
566 /// Set the database of migrants.
567 /**
568 * \verbatim embed:rst:leading-asterisk
569 * During the evolution of an archipelago, islands will periodically
570 * store the individuals selected for migration in a *migrant database*.
571 * This is a vector of :cpp:type:`~pagmo::individuals_group_t` whose
572 * size is equal to the number of islands in the archipelago, and which
573 * contains the current candidate outgoing migrants for each island.
574 *
575 * This setter allows to replace the current database of migrants
576 * with a new one.
577 *
578 * Note that this setter will accept in input a malformed database
579 * of migrants without complaining. An invalid database of migrants
580 * will however result in exceptions being raised when migration
581 * occurs.
582 * \endverbatim
583 *
584 * @param mig the new database of migrants.
585 *
586 * @throws unspecified any exception thrown by threading primitives.
587 */
set_migrants_db(migrants_db_t mig)588 void archipelago::set_migrants_db(migrants_db_t mig)
589 {
590 // NOTE: don't run any check on mig, since:
591 // - if mig.size() is wrong, we have checks in
592 // get/extract/set_migrants() in order to avoid
593 // reading/writing into invalid indices;
594 // - if the groups of individuals in mig have bogus
595 // properties (i.e., inconsistent lengths,
596 // wrong dimensions of the dvs
597 // fvs, etc.), we have checks in r_policy
598 // to ensure the incoming individuals are sane and compatible
599 // with the problem in the destination island.
600 //
601 // NOTE: currently r_policy.replace() is the only
602 // function doing something with individuals
603 // from a migration database (s_policy.select() *puts*
604 // individuals in the db), and that function
605 // has checks in it. If we ever start using individuals
606 // in the db in other ways, we'll have to keep in mind
607 // that we cannot make any assumption on the sanity
608 // of the individuals in the db.
609 // NOTE: we used to have sanity checks on the mig
610 // db in the archipelago dtor, but they have been
611 // removed.
612
613 std::lock_guard<std::mutex> lock(m_migrants_mutex);
614 m_migrants = std::move(mig);
615 }
616
617 /// Get the migration log.
618 /**
619 * \verbatim embed:rst:leading-asterisk
620 * Each time an individual migrates from an island (the source) to another
621 * (the destination), an entry will be added to the migration log.
622 * The entry is a tuple containing:
623 *
624 * - a timestamp of the migration,
625 * - the ID of the individual that migrated,
626 * - the decision and fitness vectors of the individual that migrated,
627 * - the indices of the source and destination islands.
628 *
629 * The migration log is a collection of migration entries.
630 *
631 * \endverbatim
632 *
633 * @return a copy of the migration log.
634 *
635 * @throws unspecified any exception thrown by threading primitives or by memory allocation errors.
636 */
get_migration_log() const637 archipelago::migration_log_t archipelago::get_migration_log() const
638 {
639 std::lock_guard<std::mutex> lock(m_migr_log_mutex);
640 return m_migr_log;
641 }
642
643 // Append entries to the migration log.
append_migration_log(const migration_log_t & mlog)644 void archipelago::append_migration_log(const migration_log_t &mlog)
645 {
646 // Don't do anything if mlog is empty.
647 if (mlog.empty()) {
648 return;
649 }
650
651 // Lock & append.
652 std::lock_guard<std::mutex> lock(m_migr_log_mutex);
653 m_migr_log.insert(m_migr_log.end(), mlog.begin(), mlog.end());
654 }
655
656 // Extract the migrants in the db entry for island i.
657 // After extraction, the db entry will be empty.
extract_migrants(size_type i)658 individuals_group_t archipelago::extract_migrants(size_type i)
659 {
660 std::lock_guard<std::mutex> lock(m_migrants_mutex);
661
662 if (i >= m_migrants.size()) {
663 pagmo_throw(std::out_of_range, "cannot access the migrants of the island at index " + std::to_string(i)
664 + ": the migrants database has a size of only "
665 + std::to_string(m_migrants.size()));
666 }
667
668 // Move-construct the return value.
669 individuals_group_t retval(std::move(m_migrants[i]));
670
671 // Ensure the tuple we moved-from is completely
672 // cleared out.
673 std::get<0>(m_migrants[i]).clear();
674 std::get<1>(m_migrants[i]).clear();
675 std::get<2>(m_migrants[i]).clear();
676
677 return retval;
678 }
679
680 // Get the migrants in the db entry for island i.
681 // This function will *not* clear out the db entry.
get_migrants(size_type i) const682 individuals_group_t archipelago::get_migrants(size_type i) const
683 {
684 std::lock_guard<std::mutex> lock(m_migrants_mutex);
685
686 if (i >= m_migrants.size()) {
687 pagmo_throw(std::out_of_range, "cannot access the migrants of the island at index " + std::to_string(i)
688 + ": the migrants database has a size of only "
689 + std::to_string(m_migrants.size()));
690 }
691
692 // Return a copy of the migrants for island i.
693 return m_migrants[i];
694 }
695
696 // Move-insert in the db entry for island i a set of migrants.
set_migrants(size_type i,individuals_group_t && inds)697 void archipelago::set_migrants(size_type i, individuals_group_t &&inds)
698 {
699 std::lock_guard<std::mutex> lock(m_migrants_mutex);
700
701 if (i >= m_migrants.size()) {
702 pagmo_throw(std::out_of_range, "cannot access the migrants of the island at index " + std::to_string(i)
703 + ": the migrants database has a size of only "
704 + std::to_string(m_migrants.size()));
705 }
706
707 // Move in the new individuals.
708 std::get<0>(m_migrants[i]) = std::move(std::get<0>(inds));
709 std::get<1>(m_migrants[i]) = std::move(std::get<1>(inds));
710 std::get<2>(m_migrants[i]) = std::move(std::get<2>(inds));
711 }
712
713 /// Get a copy of the topology.
714 /**
715 * @return a copy of the topology.
716 *
717 * @throws unspecified any exception thrown by copying the topology.
718 */
get_topology() const719 topology archipelago::get_topology() const
720 {
721 // NOTE: topology is supposed to be thread-safe,
722 // no need to protect the access.
723 return m_topology;
724 }
725
726 /// Set a new topology.
727 /**
728 * This function will first wait for any ongoing evolution in the archipelago to conclude,
729 * and it will then set a new topology for the archipelago.
730 *
731 * Note that it is the user's responsibility to ensure that the new topology
732 * is consistent with the archipelago's properties.
733 *
734 * @param topo the new topology.
735 *
736 * @throws unspecified any exception thrown by copying the topology.
737 */
set_topology(topology topo)738 void archipelago::set_topology(topology topo)
739 {
740 // NOTE: make sure we finish any ongoing evolution before setting the topology.
741 // The assignment will trigger the destructor of the UDT, so we need to make
742 // sure there's no interaction with the UDT happening.
743 wait_check_ignore();
744 m_topology = std::move(topo);
745 }
746
747 namespace detail
748 {
749
750 namespace
751 {
752
753 // Helpers to implement the archipelago::get_island_connections() function below.
754 template <typename T>
get_island_connections_impl(const T & topo,std::size_t i,std::true_type)755 std::pair<std::vector<archipelago::size_type>, vector_double> get_island_connections_impl(const T &topo, std::size_t i,
756 std::true_type)
757 {
758 // NOTE: get_connections() is required to be thread-safe.
759 return topo.get_connections(i);
760 }
761
762 template <typename T>
get_island_connections_impl(const T & topo,std::size_t i,std::false_type)763 std::pair<std::vector<archipelago::size_type>, vector_double> get_island_connections_impl(const T &topo, std::size_t i,
764 std::false_type)
765 {
766 // NOTE: get_connections() is required to be thread-safe.
767 auto tmp = topo.get_connections(i);
768
769 std::pair<std::vector<archipelago::size_type>, vector_double> retval;
770 retval.first.reserve(boost::numeric_cast<decltype(retval.first.size())>(tmp.first.size()));
771
772 std::transform(tmp.first.begin(), tmp.first.end(), std::back_inserter(retval.first),
773 [](const std::size_t &n) { return boost::numeric_cast<archipelago::size_type>(n); });
774 retval.second = std::move(tmp.second);
775
776 return retval;
777 }
778
779 } // namespace
780
781 } // namespace detail
782
783 // Get the list of connection to the island at index i.
784 // The returned value is made of two vectors of equal size:
785 // - the indicies of the connecting islands,
786 // - the weights of the connections.
787 // This function will take care of safely converting the topology
788 // indices to island indices, if necessary.
get_island_connections(size_type i) const789 std::pair<std::vector<archipelago::size_type>, vector_double> archipelago::get_island_connections(size_type i) const
790 {
791 // NOTE: the get_connections() method of the topology
792 // returns indices represented by std::size_t, but this method
793 // returns indices represented by size_type. Hence, we formally
794 // need to go through a conversion. We do a bit of TMP to avoid
795 // the conversion in the likely case that std::size_t and size_type
796 // are the same type.
797 return detail::get_island_connections_impl(m_topology, boost::numeric_cast<std::size_t>(i),
798 std::is_same<std::size_t, size_type>{});
799 }
800
801 /// Get the migration type.
802 /**
803 * @return the migration type for this archipelago.
804 */
get_migration_type() const805 migration_type archipelago::get_migration_type() const
806 {
807 return m_migr_type.load(std::memory_order_relaxed);
808 }
809
810 /// Set a new migration type.
811 /**
812 * @param mt a new migration type for this archipelago.
813 */
set_migration_type(migration_type mt)814 void archipelago::set_migration_type(migration_type mt)
815 {
816 m_migr_type.store(mt, std::memory_order_relaxed);
817 }
818
819 /// Get the migrant handling policy.
820 /**
821 * @return the migrant handling policy for this archipelago.
822 */
get_migrant_handling() const823 migrant_handling archipelago::get_migrant_handling() const
824 {
825 return m_migr_handling.load(std::memory_order_relaxed);
826 }
827
828 /// Set a new migrant handling policy.
829 /**
830 * @param mh a new migrant handling policy for this archipelago.
831 */
set_migrant_handling(migrant_handling mh)832 void archipelago::set_migrant_handling(migrant_handling mh)
833 {
834 m_migr_handling.store(mh, std::memory_order_relaxed);
835 }
836
837 /// Stream operator.
838 /**
839 * This operator will stream to \p os a human-readable representation of the input
840 * archipelago \p archi.
841 *
842 * @param os the target stream.
843 * @param archi the archipelago that will be streamed.
844 *
845 * @return a reference to \p os.
846 *
847 * @throws unspecified any exception thrown by:
848 * - the streaming of primitive types,
849 * - island::get_algorithm(), island::get_population().
850 */
operator <<(std::ostream & os,const archipelago & archi)851 std::ostream &operator<<(std::ostream &os, const archipelago &archi)
852 {
853 stream(os, "Number of islands: ", archi.size(), "\n");
854 stream(os, "Topology: ", archi.get_topology().get_name(), "\n");
855 stream(os, "Migration type: ", archi.get_migration_type(), "\n");
856 stream(os, "Migrant handling policy: ", archi.get_migrant_handling(), "\n");
857 stream(os, "Status: ", archi.status(), "\n\n");
858 stream(os, "Islands summaries:\n\n");
859 detail::table t({"#", "Type", "Algo", "Prob", "Size", "Status"}, "\t");
860 for (decltype(archi.size()) i = 0; i < archi.size(); ++i) {
861 const auto pop = archi[i].get_population();
862 t.add_row(i, archi[i].get_name(), archi[i].get_algorithm().get_name(), pop.get_problem().get_name(), pop.size(),
863 archi[i].status());
864 }
865 stream(os, t);
866 return os;
867 }
868
869 #if !defined(PAGMO_DOXYGEN_INVOKED)
870
871 // Provide the stream operator overloads for migration_type and migrant_handling.
operator <<(std::ostream & os,migration_type mt)872 std::ostream &operator<<(std::ostream &os, migration_type mt)
873 {
874 os << (mt == migration_type::p2p ? "point-to-point" : "broadcast");
875 return os;
876 }
877
operator <<(std::ostream & os,migrant_handling mh)878 std::ostream &operator<<(std::ostream &os, migrant_handling mh)
879 {
880 os << (mh == migrant_handling::preserve ? "preserve" : "evict");
881 return os;
882 }
883
884 #endif
885
886 } // namespace pagmo
887