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