diff --git a/CMakeLists.txt b/CMakeLists.txt index 766ba2afd..b433b573f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,9 @@ if(PAGMO_BUILD_PAGMO) "${CMAKE_CURRENT_SOURCE_DIR}/src/io.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rng.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/threading.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/topology.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/r_policy.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/s_policy.cpp" # UDP. "${CMAKE_CURRENT_SOURCE_DIR}/src/problems/null_problem.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/problems/cec2006.cpp" @@ -214,12 +217,22 @@ if(PAGMO_BUILD_PAGMO) "${CMAKE_CURRENT_SOURCE_DIR}/src/batch_evaluators/default_bfe.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/batch_evaluators/member_bfe.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/batch_evaluators/thread_bfe.cpp" + # UDT. + "${CMAKE_CURRENT_SOURCE_DIR}/src/topologies/base_bgl_topology.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/topologies/unconnected.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/topologies/fully_connected.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/topologies/ring.cpp" + # UDRP. + "${CMAKE_CURRENT_SOURCE_DIR}/src/r_policies/fair_replace.cpp" + # UDSP. + "${CMAKE_CURRENT_SOURCE_DIR}/src/s_policies/select_best.cpp" # Utils. "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/constrained.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/discrepancy.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/generic.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/multi_objective.cpp" # Detail. + "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/base_sr_policy.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/bfe_impl.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/task_queue.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/prime_numbers.cpp" diff --git a/doc/sphinx/changelog.rst b/doc/sphinx/changelog.rst index b484c4337..494c420e8 100644 --- a/doc/sphinx/changelog.rst +++ b/doc/sphinx/changelog.rst @@ -7,6 +7,15 @@ Changelog New ~~~ +- NSGA2 can optionally use the batch fitness evaluation framework + (`#308 `__). + +- Implement the WFG test suite + (`#298 `__). + +- Migration framework + (`#296 `__). + - Various additions to the C++ API of user-defined classes (`#294 `__). @@ -23,6 +32,8 @@ New - Implement the Lennard-Jones and Golomb ruler problems (`#247 `__). +- Batch fitness evaluation framework (`#226 `__). + Changes ~~~~~~~ @@ -34,6 +45,21 @@ Changes OSX and MinGW, as they cause build issues (`#266 `__, `#292 `__). +- **BREAKING**: the serialization backend was switched from the + Cereal library to the Boost.serialization library. This change has + no consequences + for Python users, nor for C++ users who use pagmo's CMake machinery. + For those C++ users who don't use CMake, + this means that in order to use pagmo it is now necessary to link + to the Boost.serialization library (`#278 `__). + +- **BREAKING**: pagmo is not any more a header-only library, it has now + a compiled component. This change has no consequences + for Python users, nor for C++ users who use pagmo's CMake machinery. + For those C++ users who don't use CMake, + this means that in order to use pagmo it is now necessary to link + to a compiled library (`#278 `__). + - Various performance improvements in the :cpp:class:`~pagmo::population` API (`#250 `__). - **BREAKING**: :class:`pygmo.problem` and :class:`pygmo.algorithm` @@ -43,6 +69,17 @@ Changes Fix ~~~ +- Fix a bug in pygmo's plotting utils (`#330 `__). + +- Fix a bug in PSO's error handling (`#323 `__). + +- Fix a bug in MOEA/D when ``m_neighbours<2`` (`#320 `__). + +- Fix type mismatches in the constrained/MO utils (`#315 `__). + +- Fix a potential deadlock when setting/getting an island's + population/algorithm (`#309 `__). + - Fix a build failure when pagmo is configured without Eigen3 (`#281 `__). - Fix a build failure in the Ipopt algorithm wrapper when using the Debian/Ubuntu Ipopt packages (`#266 `__). @@ -56,7 +93,8 @@ Fix `#257 `__, `#262 `__, `#265 `__, `#266 `__, `#279 `__, `#287 `__, - `#288 `__). + `#288 `__, `#327 `__, + `#328 `__). - The :cpp:class:`~pagmo::fork_island` UDI now properly cleans up zombie processes (`#242 `__). diff --git a/doc/sphinx/docs/cpp/archipelago.rst b/doc/sphinx/docs/cpp/archipelago.rst index 012bd5f07..0149ecf81 100644 --- a/doc/sphinx/docs/cpp/archipelago.rst +++ b/doc/sphinx/docs/cpp/archipelago.rst @@ -1,5 +1,14 @@ Archipelago =========== +*#include * + .. doxygenclass:: pagmo::archipelago :members: + +Types +----- + +.. doxygenenum:: pagmo::migration_type + +.. doxygenenum:: pagmo::migrant_handling diff --git a/doc/sphinx/docs/cpp/cpp_docs.rst b/doc/sphinx/docs/cpp/cpp_docs.rst index 0f5641d31..0a6b9b1c9 100644 --- a/doc/sphinx/docs/cpp/cpp_docs.rst +++ b/doc/sphinx/docs/cpp/cpp_docs.rst @@ -17,6 +17,9 @@ we suggest to follow the tutorials / examples. island archipelago bfe + topology + r_policy + s_policy Implemented algorithms ^^^^^^^^^^^^^^^^^^^^^^ @@ -96,6 +99,30 @@ Implemented batch evaluators batch_evaluators/thread_bfe batch_evaluators/member_bfe +Implemented topologies +^^^^^^^^^^^^^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + topologies/unconnected + topologies/fully_connected + topologies/base_bgl_topology + topologies/ring + +Implemented replacement policies +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + r_policies/fair_replace + +Implemented selection policies +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + s_policies/select_best + Utilities ^^^^^^^^^ Various optimization utilities. diff --git a/doc/sphinx/docs/cpp/island.rst b/doc/sphinx/docs/cpp/island.rst index 48d439e6e..ddec66232 100644 --- a/doc/sphinx/docs/cpp/island.rst +++ b/doc/sphinx/docs/cpp/island.rst @@ -1,7 +1,35 @@ Island ====== +*#include * + .. doxygenclass:: pagmo::island :members: +Functions +--------- + +.. cpp:namespace-push:: pagmo + +.. cpp:function:: std::ostream &operator<<(std::ostream &os, const island &isl) + + Stream operator for :cpp:class:`pagmo::island`. + + This operator will stream to *os* a human-readable representation of *isl*. + + It is safe to call this method while the island is evolving. + + :param os: the target stream. + :param isl: the input island. + + :return: a reference to *os*. + + :exception unspecified: any exception trown by the stream operators of fundamental types or by + the public interface of :cpp:class:`pagmo::island` and of all its members. + +.. cpp:namespace-pop:: + +Types +----- + .. doxygenenum:: pagmo::evolve_status diff --git a/doc/sphinx/docs/cpp/r_policies/fair_replace.rst b/doc/sphinx/docs/cpp/r_policies/fair_replace.rst new file mode 100644 index 000000000..7c7a644d2 --- /dev/null +++ b/doc/sphinx/docs/cpp/r_policies/fair_replace.rst @@ -0,0 +1,128 @@ +Fair replacement policy +======================= + +.. versionadded:: 2.11 + +*#include * + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: fair_replace + + This user-defined replacement policy (UDRP) will replace individuals in + a group only if the candidate replacement individuals are *better* than + the original individuals. + + In this context, *better* means the following: + + * in single-objective unconstrained problems, an individual is better + than another one if its fitness is lower, + * in single-objective constrained problems, individuals are ranked + via :cpp:func:`~pagmo::sort_population_con()`, + * in multi-objective unconstrained problems, individuals are ranked + via :cpp:func:`~pagmo::sort_population_mo()`. + + See the documentation of :cpp:func:`~pagmo::fair_replace::replace()` for + more details on the replacement algorithm implemented by this UDRP. + + Note that this user-defined replacement policy currently does *not* support + multi-objective constrained problems. + + .. cpp:function:: fair_replace() + + Default constructor. + + The default constructor initialises a policy with an absolute migration rate of 1 + (that is, 1 individual in the original population is considered for replacement). + + .. cpp:function:: template explicit fair_replace(T x) + + Constructor from a migration rate. + + This constructor participates in overload resolution only if ``T`` is a C++ + integral or a floating-point type. The input migration rate, *x*, is used to indicate + how many individuals will be replaced in an input population by the + :cpp:func:`~pagmo::fair_replace::replace()` member function. + + If *x* is a floating point value in the :math:`\left[0,1\right]` range, + then it represents a *fractional* migration rate. That is, it indicates, + the fraction of individuals that may be replaced in the input population: + a value of 0 means that no individuals will be replaced, a value of 1 means that + all individuals may be replaced. + + If *x* is an integral value, then it represents an *absolute* migration rate, that is, + the exact number of individuals that may be replaced in the input population. + + :param x: the fractional or absolute migration rate. + + :exception std\:\:invalid_argument: if the supplied fractional migration rate is not finite + or not in the :math:`\left[0,1\right]` range. + :exception unspecified: any exception raised by ``boost::numeric_cast()`` while trying + to convert the input absolute migration rate to :cpp:type:`~pagmo::pop_size_t`. + + .. cpp:function:: individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, \ + const vector_double::size_type &, const vector_double::size_type &nobj, \ + const vector_double::size_type &nec, const vector_double::size_type &nic, \ + const vector_double &tol, const individuals_group_t &mig) const + + This member function will replace individuals in *inds* with individuals from *mig*. + + The replacement algorithm determines first how many individuals in *inds* can be replaced. This depends both on + the migration rate specified upon construction, and on the size :math:`S` of *inds*. + + After having established the number :math:`N` of individuals that can be replaced in *inds*, + the algorithm then selects the top :math:`N` individuals from *mig*, merges them + with *inds* into a new population, and returns the top :math:`S` individuals + from the new population. The ranking of individuals in *mig* and in the new population + depends on the problem's properties: + + * in single-objective unconstrained problems, the individuals are ranked according to their + (scalar) fitnesses, + * in single-objective constrained problems, the ranking of individuals + is done via :cpp:func:`~pagmo::sort_population_con()`, + * in multi-objective unconstrained problems, the ranking of individuals + is done via :cpp:func:`~pagmo::sort_population_mo()`. + + Note that this user-defined replacement policy currently does *not* support + multi-objective constrained problems. + + :param inds: the input individuals. + :param nobj: the number of objectives of the problem the individuals in *inds* and *mig* refer to. + :param nec: the number of equality constraints of the problem the individuals in *inds* and *mig* refer to. + :param nic: the number of inequality constraints of the problem the individuals in *inds* and *mig* refer to. + :param tol: the vector of constraint tolerances of the problem the individuals in *inds* and *mig* refer to. + :param mig: the individuals that may replace individuals in *inds*. + + :return: the new population resulting from replacing individuals in *inds* with individuals from *mig*. + + :exception std\:\:invalid_argument: in the following cases: + + * the problem the individuals in *inds* and *mig* refer to is + multi-objective and constrained, + * an absolute migration rate larger than the number of input individuals + was specified. + + :exception unspecified: any exception raised by one of the invoked ranking functions or by memory + allocation errors in standard containers. + + .. cpp:function:: std::string get_name() const + + Get the name of the policy. + + :return: ``"Fair replace"``. + + .. cpp:function:: std::string get_extra_info() const + + :return: Human-readable extra info about this replacement policy. + + .. cpp:function:: template void serialize(Archive &ar, unsigned) + + Serialisation support. + + This member function is used to implement the (de)serialisation of this replacement policy to/from an archive. + + :param ar: the input/output archive. + + :exception unspecified: any exception raised by the (de)serialisation of primitive types. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/r_policy.rst b/doc/sphinx/docs/cpp/r_policy.rst new file mode 100644 index 000000000..d853d1903 --- /dev/null +++ b/doc/sphinx/docs/cpp/r_policy.rst @@ -0,0 +1,285 @@ +Replacement policy +================== + +.. versionadded:: 2.11 + +*#include * + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: r_policy + + Replacement policy. + + A replacement policy establishes + how, during migration within an :cpp:class:`~pagmo::archipelago`, + a group of migrants replaces individuals in an existing + :cpp:class:`~pagmo::population`. In other words, a replacement + policy is tasked with producing a new set of individuals from + an original set of individuals and a set of candidate migrants. + + Following the same schema adopted for :cpp:class:`~pagmo::problem`, :cpp:class:`~pagmo::algorithm`, etc., + :cpp:class:`~pagmo::r_policy` exposes a type-erased generic + interface to *user-defined replacement policies* (or UDRP for short). + UDRPs are classes providing a certain set + of member functions that implement the logic of the replacement policy. Once + defined and instantiated, a UDRP can then be used to construct an instance of this class, + :cpp:class:`~pagmo::r_policy`, which + provides a generic interface to replacement policies for use by :cpp:class:`~pagmo::island`. + + Every UDRP must implement at least the following member function: + + .. code-block:: c++ + + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + + The ``replace()`` function takes in input the following parameters: + + * a group of individuals *inds* (represented as an :cpp:type:`~pagmo::individuals_group_t`), + * a set of arguments describing the properties of the :cpp:class:`~pagmo::problem` the individuals refer to: + + * the total dimension *nx*, + * the integral dimension *nix*, + * the number of objectives *nobj*, + * the number of equality constraints *nec*, + * the number of inequality constraints *nic*, + * the problem's constraint tolerances *tol*, + + * a set of migrants *mig*, + + and it produces in output another set of individuals resulting from replacing individuals in *inds* with + individuals from *mig* (following some logic established by the UDRP). + + In addition to providing the above member function, a UDRP must also be default, copy and move constructible. + + Additional optional member functions can be implemented in a UDRP: + + .. code-block:: c++ + + std::string get_name() const; + std::string get_extra_info() const; + + See the documentation of the corresponding member functions in this class for details on how the optional + member functions in the UDRP are used by :cpp:class:`~pagmo::r_policy`. + + Replacement policies are used in asynchronous operations involving migration in archipelagos, + and thus they need to provide a certain degree of thread safety. Specifically, the + ``replace()`` member function of the UDRP might be invoked concurrently with + any other member function of the UDRP interface (except for the destructor, the move + constructor, and, if implemented, the deserialisation function). It is up to the + authors of user-defined replacement policies to ensure that this safety requirement is satisfied. + + .. warning:: + + The only operations allowed on a moved-from :cpp:class:`pagmo::r_policy` are destruction, + assignment, and the invocation of the :cpp:func:`~pagmo::r_policy::is_valid()` member function. + Any other operation will result in undefined behaviour. + + .. cpp:function:: r_policy() + + Default constructor. + + The default constructor will initialize an :cpp:class:`~pagmo::r_policy` containing a + :cpp:class:`~pagmo::fair_replace` replacement policy. + + :exception unspecified: any exception raised by the constructor from a generic UDRP. + + .. cpp:function:: r_policy(const r_policy &) + .. cpp:function:: r_policy(r_policy &&) noexcept + .. cpp:function:: r_policy &operator=(const r_policy &) + .. cpp:function:: r_policy &operator=(r_policy &&) noexcept + + :cpp:class:`~pagmo::r_policy` is copy/move constructible, and copy/move assignable. + Copy construction/assignment will perform deep copies, move operations will leave the moved-from object in + a state which is destructible and assignable. + + :exception unspecified: when performing copy operations, any exception raised by the UDRP upon copying, or by memory allocation failures. + + .. cpp:function:: template explicit r_policy(T &&x) + + Generic constructor from a UDRP. + + This constructor participates in overload resolution only if ``T``, after the removal of reference + and cv qualifiers, is not :cpp:class:`~pagmo::r_policy` and if it satisfies :cpp:class:`pagmo::is_udrp`. + + This constructor will construct an :cpp:class:`~pagmo::r_policy` from the UDRP (user-defined replacement policy) + *x* of type ``T``. The input parameter *x* will be perfectly forwarded to construct the internal UDRP instance. + + :param x: the input UDRP. + + :exception unspecified: any exception thrown by the public API of the UDRP, or by memory allocation failures. + + .. cpp:function:: template r_policy &operator=(T &&x) + + Generic assignment operator from a UDRP. + + This operator participates in overload resolution only if ``T``, after the removal of reference + and cv qualifiers, is not :cpp:class:`~pagmo::r_policy` and if it satisfies :cpp:class:`pagmo::is_udrp`. + + This operator will set the internal UDRP to *x* by constructing an :cpp:class:`~pagmo::r_policy` from *x*, + and then move-assigning the result to *this*. + + :param x: the input UDRP. + + :return: a reference to *this*. + + :exception unspecified: any exception thrown by the generic constructor from a UDRP. + + .. cpp:function:: template const T *extract() const noexcept + .. cpp:function:: template T *extract() noexcept + + Extract a (const) pointer to the internal UDRP instance. + + If ``T`` is the type of the UDRP currently stored within this object, then this function + will return a (const) pointer to the internal UDRP instance. Otherwise, ``nullptr`` will be returned. + + The returned value is a raw non-owning pointer: the lifetime of the pointee is tied to the lifetime + of ``this``, and ``delete`` must never be called on the pointer. + + .. warning:: + + The non-const overload of this function is provided only in order to allow to call non-const + member functions on the internal UDRP instance. Assigning a new UDRP via pointers obtained + through this function is undefined behaviour. + + :return: a (const) pointer to the internal UDRP instance, or ``nullptr``. + + .. cpp:function:: template bool is() const noexcept + + Check the type of the UDRP. + + :return: ``true`` if ``T`` is the type of the UDRP currently stored within this object, ``false`` otherwise. + + .. cpp:function:: individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &nx, \ + const vector_double::size_type &nix, const vector_double::size_type &nobj, \ + const vector_double::size_type &nec, const vector_double::size_type &nic, \ + const vector_double &tol, const individuals_group_t &mig) const + + Replace individuals in a group with migrants from another group. + + This member function will invoke the ``replace()`` member function of the UDRP. + Given a set of individuals, *inds*, and a set of migrants, *mig*, the ``replace()`` member function of the UDRP + is expected to replace individuals in *inds* + with individuals from *mig*, and return the new set of individuals resulting from the replacement. + The other arguments of this member function describe the properties of the :cpp:class:`~pagmo::problem` + that the individuals in *inds* and *mig* refer to. + + In addition to invoking the ``replace()`` member function of the UDRP, this function will also + perform a variety of sanity checks on both the input arguments and on the output produced by the + UDRP. + + :param inds: the original group of individuals. + :param nx: the dimension of the problem *inds* and *mig* refer to. + :param nix: the integral dimension of the problem *inds* and *mig* refer to. + :param nobj: the number of objectives of the problem *inds* and *mig* refer to. + :param nec: the number of equality constraints of the problem *inds* and *mig* refer to. + :param nic: the number of inequality constraints of the problem *inds* and *mig* refer to. + :param tol: the vector of constraints tolerances of the problem *inds* and *mig* refer to. + :param mig: the group of migrants. + + :return: a new set of individuals resulting from replacing individuals in *inds* with individuals from *mig*. + + :exception std\:\:invalid_argument: if either: + + * *inds*, *mig* or the return value are not consistent with the problem properties, + * the ID, decision and fitness vectors in *inds*, *mig* or the return value have inconsistent sizes, + * the problem properties are invalid (e.g., *nobj* is zero, *nix* > *nx*, etc.). + + :exception unspecified: any exception raised by the ``replace()`` member function of the UDRP. + + .. cpp:function:: std::string get_name() const + + Get the name of this replacement policy. + + If the UDRP satisfies :cpp:class:`pagmo::has_name`, then this member function will return the output of its ``get_name()`` member function. + Otherwise, an implementation-defined name based on the type of the UDRP will be returned. + + :return: the name of this replacement policy. + + :exception unspecified: any exception thrown by copying an ``std::string`` object. + + .. cpp:function:: std::string get_extra_info() const + + Extra info for this replacement policy. + + If the UDRP satisfies :cpp:class:`pagmo::has_extra_info`, then this member function will return the output of its + ``get_extra_info()`` member function. Otherwise, an empty string will be returned. + + :return: extra info about the UDRP. + + :exception unspecified: any exception thrown by the ``get_extra_info()`` member function of the UDRP, or by copying an ``std::string`` object. + + .. cpp:function:: bool is_valid() const + + Check if this replacement policy is in a valid state. + + :return: ``false`` if *this* was moved from, ``true`` otherwise. + + .. cpp:function:: template void save(Archive &ar, unsigned) const + .. cpp:function:: template void load(Archive &ar, unsigned) + + Serialisation support. + + These two member functions are used to implement the (de)serialisation of a replacement policy to/from an archive. + + :param ar: the input/output archive. + + :exception unspecified: any exception raised by the (de)serialisation of primitive types or of the UDRP. + +Functions +--------- + +.. cpp:function:: std::ostream &operator<<(std::ostream &os, const r_policy &r) + + Stream insertion operator. + + This function will direct to *os* a human-readable representation of the input + :cpp:class:`~pagmo::r_policy` *r*. + + :param os: the input ``std::ostream``. + :param r: the replacement policy that will be directed to *os*. + + :return: a reference to *os*. + + :exception unspecified: any exception thrown by querying various properties of the replacement policy and directing them to *os*. + +Associated type traits +---------------------- + +.. cpp:class:: template has_replace + + The :cpp:any:`value` of this type trait will be ``true`` if + ``T`` provides a member function with signature: + + .. code-block:: c++ + + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + + The ``replace()`` member function is part of the interface for the definition of an + :cpp:class:`~pagmo::r_policy`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:class:: template is_udrp + + This type trait detects if ``T`` is a user-defined replacement policy (or UDRP). + + Specifically, the :cpp:any:`value` of this type trait will be ``true`` if: + + * ``T`` is not a reference or cv qualified, + * ``T`` is destructible, default, copy and move constructible, and + * ``T`` satisfies :cpp:class:`pagmo::has_replace`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/s_policies/select_best.rst b/doc/sphinx/docs/cpp/s_policies/select_best.rst new file mode 100644 index 000000000..3239b1297 --- /dev/null +++ b/doc/sphinx/docs/cpp/s_policies/select_best.rst @@ -0,0 +1,124 @@ +Best selection policy +===================== + +.. versionadded:: 2.11 + +*#include * + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: select_best + + This user-defined selection policy (UDSP) will select the *best* + individuals from a group. + + In this context, *best* means the following: + + * in single-objective unconstrained problems, individuals are ranked + according to their fitness function, + * in single-objective constrained problems, individuals are ranked + via :cpp:func:`~pagmo::sort_population_con()`, + * in multi-objective unconstrained problems, individuals are ranked + via :cpp:func:`~pagmo::sort_population_mo()`. + + See the documentation of :cpp:func:`~pagmo::select_best::select()` for + more details on the selection algorithm implemented by this UDSP. + + Note that this user-defined selection policy currently does *not* support + multi-objective constrained problems. + + .. cpp:function:: select_best() + + Default constructor. + + The default constructor initialises a policy with an absolute migration rate + of 1 (that is, 1 individual will be selected from the input population). + + .. cpp:function:: template explicit select_best(T x) + + Constructor from a migration rate. + + This constructor participates in overload resolution only if ``T`` is a C++ + integral or a floating-point type. The input migration rate, *x*, is used to indicate + how many individuals will be selected from an input population by the + :cpp:func:`~pagmo::select_best::select()` member function. + + If *x* is a floating point value in the :math:`\left[0,1\right]` range, + then it represents a *fractional* migration rate. That is, it indicates, + the fraction of individuals that will be selected from the input population: + a value of 0 means that no individuals will be selected, a value of 1 means that + all individuals will be selected. + + If *x* is an integral value, then it represents an *absolute* migration rate, that is, + the exact number of individuals that will be selected from the input population. + + :param x: the fractional or absolute migration rate. + + :exception std\:\:invalid_argument: if the supplied fractional migration rate is not finite + or not in the :math:`\left[0,1\right]` range. + :exception unspecified: any exception raised by ``boost::numeric_cast()`` while trying + to convert the input absolute migration rate to :cpp:type:`~pagmo::pop_size_t`. + + .. cpp:function:: individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, \ + const vector_double::size_type &, const vector_double::size_type &nobj, \ + const vector_double::size_type &nec, const vector_double::size_type &nic, \ + const vector_double &tol) const + + This member function will select individuals from *inds*. + + The selection algorithm determines first how many individuals in *inds* will be selected. This depends both on + the migration rate specified upon construction, and on the size of *inds*. + + After having established the number :math:`N` of individuals to be selected from *inds*, + the algorithm then ranks the individuals in *inds* and selects the top :math:`N` individuals. + The ranking method depends on the problem's properties: + + * in single-objective unconstrained problems, the individuals are ranked according to their + (scalar) fitnesses, + * in single-objective constrained problems, the ranking of individuals + is done via :cpp:func:`~pagmo::sort_population_con()`, + * in multi-objective unconstrained problems, the ranking of individuals + is done via :cpp:func:`~pagmo::sort_population_mo()`. + + Note that this user-defined selection policy currently does *not* support + multi-objective constrained problems. + + :param inds: the input individuals. + :param nobj: the number of objectives of the problem the individuals in *inds* refer to. + :param nec: the number of equality constraints of the problem the individuals in *inds* refer to. + :param nic: the number of inequality constraints of the problem the individuals in *inds* refer to. + :param tol: the vector of constraint tolerances of the problem the individuals in *inds* refer to. + + :return: the group of top :math:`N` individuals from *inds*. + + :exception std\:\:invalid_argument: in the following cases: + + * the problem the individuals in *inds* refer to is + multi-objective and constrained, + * an absolute migration rate larger than the number of input individuals + was specified. + + :exception unspecified: any exception raised by one of the invoked ranking functions or by memory + allocation errors in standard containers. + + .. cpp:function:: std::string get_name() const + + Get the name of the policy. + + :return: ``"Select best"``. + + .. cpp:function:: std::string get_extra_info() const + + :return: Human-readable extra info about this selection policy. + + .. cpp:function:: template void serialize(Archive &ar, unsigned) + + Serialisation support. + + This member function is used to implement the (de)serialisation of this selection policy to/from an archive. + + :param ar: the input/output archive. + + :exception unspecified: any exception raised by the (de)serialisation of primitive types. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/s_policy.rst b/doc/sphinx/docs/cpp/s_policy.rst new file mode 100644 index 000000000..8094406ea --- /dev/null +++ b/doc/sphinx/docs/cpp/s_policy.rst @@ -0,0 +1,278 @@ +Selection policy +================ + +.. versionadded:: 2.11 + +*#include * + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: s_policy + + Selection policy. + + A selection policy establishes + how, during migration within an :cpp:class:`~pagmo::archipelago`, + candidate migrants are selected from an :cpp:class:`~pagmo::island`. + + Following the same schema adopted for :cpp:class:`~pagmo::problem`, :cpp:class:`~pagmo::algorithm`, etc., + :cpp:class:`~pagmo::s_policy` exposes a type-erased generic + interface to *user-defined selection policies* (or UDSP for short). + UDSPs are classes providing a certain set + of member functions that implement the logic of the selection policy. Once + defined and instantiated, a UDSP can then be used to construct an instance of this class, + :cpp:class:`~pagmo::s_policy`, which + provides a generic interface to selection policies for use by :cpp:class:`~pagmo::island`. + + Every UDSP must implement at least the following member function: + + .. code-block:: c++ + + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + + The ``select()`` function takes in input the following parameters: + + * a group of individuals *inds* (represented as an :cpp:type:`~pagmo::individuals_group_t`), + * a set of arguments describing the properties of the :cpp:class:`~pagmo::problem` the individuals refer to: + + * the total dimension *nx*, + * the integral dimension *nix*, + * the number of objectives *nobj*, + * the number of equality constraints *nec*, + * the number of inequality constraints *nic*, + * the problem's constraint tolerances *tol*, + + and it produces in output another set of individuals resulting from selecting individuals in *inds* + (following some logic established by the UDSP). + + In addition to providing the above member function, a UDSP must also be default, copy and move constructible. + + Additional optional member functions can be implemented in a UDSP: + + .. code-block:: c++ + + std::string get_name() const; + std::string get_extra_info() const; + + See the documentation of the corresponding member functions in this class for details on how the optional + member functions in the UDSP are used by :cpp:class:`~pagmo::s_policy`. + + Selection policies are used in asynchronous operations involving migration in archipelagos, + and thus they need to provide a certain degree of thread safety. Specifically, the + ``select()`` member function of the UDSP might be invoked concurrently with + any other member function of the UDSP interface (except for the destructor, the move + constructor, and, if implemented, the deserialisation function). It is up to the + authors of user-defined selection policies to ensure that this safety requirement is satisfied. + + .. warning:: + + The only operations allowed on a moved-from :cpp:class:`pagmo::s_policy` are destruction, + assignment, and the invocation of the :cpp:func:`~pagmo::s_policy::is_valid()` member function. + Any other operation will result in undefined behaviour. + + .. cpp:function:: s_policy() + + Default constructor. + + The default constructor will initialize an :cpp:class:`~pagmo::s_policy` containing a + :cpp:class:`~pagmo::select_best` selection policy. + + :exception unspecified: any exception raised by the constructor from a generic UDSP. + + .. cpp:function:: s_policy(const s_policy &) + .. cpp:function:: s_policy(s_policy &&) noexcept + .. cpp:function:: s_policy &operator=(const s_policy &) + .. cpp:function:: s_policy &operator=(s_policy &&) noexcept + + :cpp:class:`~pagmo::s_policy` is copy/move constructible, and copy/move assignable. + Copy construction/assignment will perform deep copies, move operations will leave the moved-from object in + a state which is destructible and assignable. + + :exception unspecified: when performing copy operations, any exception raised by the UDSP upon copying, or by memory allocation failures. + + .. cpp:function:: template explicit s_policy(T &&x) + + Generic constructor from a UDSP. + + This constructor participates in overload resolution only if ``T``, after the removal of reference + and cv qualifiers, is not :cpp:class:`~pagmo::s_policy` and if it satisfies :cpp:class:`pagmo::is_udsp`. + + This constructor will construct an :cpp:class:`~pagmo::s_policy` from the UDSP (user-defined selection policy) + *x* of type ``T``. The input parameter *x* will be perfectly forwarded to construct the internal UDSP instance. + + :param x: the input UDSP. + + :exception unspecified: any exception thrown by the public API of the UDSP, or by memory allocation failures. + + .. cpp:function:: template s_policy &operator=(T &&x) + + Generic assignment operator from a UDSP. + + This operator participates in overload resolution only if ``T``, after the removal of reference + and cv qualifiers, is not :cpp:class:`~pagmo::s_policy` and if it satisfies :cpp:class:`pagmo::is_udsp`. + + This operator will set the internal UDSP to *x* by constructing an :cpp:class:`~pagmo::s_policy` from *x*, + and then move-assigning the result to *this*. + + :param x: the input UDSP. + + :return: a reference to *this*. + + :exception unspecified: any exception thrown by the generic constructor from a UDSP. + + .. cpp:function:: template const T *extract() const noexcept + .. cpp:function:: template T *extract() noexcept + + Extract a (const) pointer to the internal UDSP instance. + + If ``T`` is the type of the UDSP currently stored within this object, then this function + will return a (const) pointer to the internal UDSP instance. Otherwise, ``nullptr`` will be returned. + + The returned value is a raw non-owning pointer: the lifetime of the pointee is tied to the lifetime + of ``this``, and ``delete`` must never be called on the pointer. + + .. warning:: + + The non-const overload of this function is provided only in order to allow to call non-const + member functions on the internal UDSP instance. Assigning a new UDSP via pointers obtained + through this function is undefined behaviour. + + :return: a (const) pointer to the internal UDSP instance, or ``nullptr``. + + .. cpp:function:: template bool is() const noexcept + + Check the type of the UDSP. + + :return: ``true`` if ``T`` is the type of the UDSP currently stored within this object, ``false`` otherwise. + + .. cpp:function:: individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &nx, \ + const vector_double::size_type &nix, const vector_double::size_type &nobj, \ + const vector_double::size_type &nec, const vector_double::size_type &nic, \ + const vector_double &tol) const + + Select individuals from a group. + + This member function will invoke the ``select()`` member function of the UDSP. + Given a set of individuals, *inds*, the ``select()`` member function of the UDSP + is expected to return a new set of individuals selected from *inds*. + The other arguments of this member function describe the properties of the :cpp:class:`~pagmo::problem` + that the individuals in *inds* refer to. + + In addition to invoking the ``select()`` member function of the UDSP, this function will also + perform a variety of sanity checks on both the input arguments and on the output produced by the + UDSP. + + :param inds: the original group of individuals. + :param nx: the dimension of the problem *inds* refers to. + :param nix: the integral dimension of the problem *inds* refers to. + :param nobj: the number of objectives of the problem *inds* refers to. + :param nec: the number of equality constraints of the problem *inds* refers to. + :param nic: the number of inequality constraints of the problem *inds* refers to. + :param tol: the vector of constraints tolerances of the problem *inds* refers to. + + :return: a new set of individuals resulting from selecting individuals in *inds*. + + :exception std\:\:invalid_argument: if either: + + * *inds* or the return value are not consistent with the problem properties, + * the ID, decision and fitness vectors in *inds* or the return value have inconsistent sizes, + * the problem properties are invalid (e.g., *nobj* is zero, *nix* > *nx*, etc.). + + :exception unspecified: any exception raised by the ``select()`` member function of the UDSP. + + .. cpp:function:: std::string get_name() const + + Get the name of this selection policy. + + If the UDSP satisfies :cpp:class:`pagmo::has_name`, then this member function will return the output of its ``get_name()`` member function. + Otherwise, an implementation-defined name based on the type of the UDSP will be returned. + + :return: the name of this selection policy. + + :exception unspecified: any exception thrown by copying an ``std::string`` object. + + .. cpp:function:: std::string get_extra_info() const + + Extra info for this selection policy. + + If the UDSP satisfies :cpp:class:`pagmo::has_extra_info`, then this member function will return the output of its + ``get_extra_info()`` member function. Otherwise, an empty string will be returned. + + :return: extra info about the UDSP. + + :exception unspecified: any exception thrown by the ``get_extra_info()`` member function of the UDSP, or by copying an ``std::string`` object. + + .. cpp:function:: bool is_valid() const + + Check if this selection policy is in a valid state. + + :return: ``false`` if *this* was moved from, ``true`` otherwise. + + .. cpp:function:: template void save(Archive &ar, unsigned) const + .. cpp:function:: template void load(Archive &ar, unsigned) + + Serialisation support. + + These two member functions are used to implement the (de)serialisation of a selection policy to/from an archive. + + :param ar: the input/output archive. + + :exception unspecified: any exception raised by the (de)serialisation of primitive types or of the UDSP. + +Functions +--------- + +.. cpp:function:: std::ostream &operator<<(std::ostream &os, const s_policy &s) + + Stream insertion operator. + + This function will direct to *os* a human-readable representation of the input + :cpp:class:`~pagmo::s_policy` *s*. + + :param os: the input ``std::ostream``. + :param s: the selection policy that will be directed to *os*. + + :return: a reference to *os*. + + :exception unspecified: any exception thrown by querying various properties of the selection policy and directing them to *os*. + +Associated type traits +---------------------- + +.. cpp:class:: template has_select + + The :cpp:any:`value` of this type trait will be ``true`` if + ``T`` provides a member function with signature: + + .. code-block:: c++ + + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + + The ``select()`` member function is part of the interface for the definition of an + :cpp:class:`~pagmo::s_policy`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:class:: template is_udsp + + This type trait detects if ``T`` is a user-defined selections policy (or UDSP). + + Specifically, the :cpp:any:`value` of this type trait will be ``true`` if: + + * ``T`` is not a reference or cv qualified, + * ``T`` is destructible, default, copy and move constructible, and + * ``T`` satisfies :cpp:class:`pagmo::has_select`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/topologies/base_bgl_topology.rst b/doc/sphinx/docs/cpp/topologies/base_bgl_topology.rst new file mode 100644 index 000000000..56acf0da4 --- /dev/null +++ b/doc/sphinx/docs/cpp/topologies/base_bgl_topology.rst @@ -0,0 +1,164 @@ +Base BGL topology +================= + +.. versionadded:: 2.11 + +*#include * + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: base_bgl_topology + + This class provides the basic building blocks to implement + user-defined topologies (UDTs) based on the Boost Graph Library (BGL). + + Note that, by itself, this class does **not** satisfy all the requirements + of a UDT. Specifically, this class is missing the mandatory ``push_back()`` member function, + which has to be implemented in a derived class (see :cpp:class:`~pagmo::is_udt` for the + full list of requirements a UDT must satisfy). + + This class provides a strong thread safety guarantee: any member function can be invoked + concurrently with any other member function. + + .. seealso:: + + https://www.boost.org/doc/libs/1_70_0/libs/graph/doc/index.html + + .. cpp:function:: base_bgl_topology() + + Default constructor. + + The default constructor will initialize an empty graph with no vertices and no edges. + + .. cpp:function:: base_bgl_topology(const base_bgl_topology &) + .. cpp:function:: base_bgl_topology(base_bgl_topology &&) noexcept + .. cpp:function:: base_bgl_topology &operator=(const base_bgl_topology &) + .. cpp:function:: base_bgl_topology &operator=(base_bgl_topology &&) noexcept + + :cpp:class:`~pagmo::base_bgl_topology` is copy/move constructible, and copy/move assignable. + Copy construction/assignment will perform deep copies, move operations will leave the moved-from object in + an unspecified but valid state. + + :exception unspecified: when performing copy operations, any exception raised by the copy of the underlying graph object. + + .. cpp:function:: std::size_t num_vertices() const + + :return: the number of vertices in the topology. + + .. cpp:function:: bool are_adjacent(std::size_t i, std::size_t j) const + + Check if two vertices are adjacent. + + Two vertices *i* and *j* are adjacent if there is a directed edge connecting *i* to *j*. + + :param i: the first vertex index. + :param j: the second vertex index. + + :return: ``true`` if *i* and *j* are adjacent, ``false`` otherwise. + + :exception std\:\:invalid_argument: if *i* or *j* are not smaller than the number of vertices. + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: std::pair, vector_double> get_connections(std::size_t i) const + + Fetch the edges connecting to *i*. + + This function will return a pair of vectors of equal size, containing: + + * the list of all vertices connecting to *i*, + * the weights of the edges. + + :param i: the vertex index. + + :return: the list of connections to *i*. + + :exception std\:\:invalid_argument: if *i* is not smaller than the number of vertices. + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: void add_vertex() + + Add a vertex. + + This function will add a new vertex to the topology. The newly-added vertex + will be disjoint from any other vertex in the topology (i.e., there are no + connections to/from the new vertex). + + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: void add_edge(std::size_t i, std::size_t j, double w = 1) + + Add a new edge. + + This function will add a new edge of weight *w* connecting *i* to *j*. + + :param i: the first vertex index. + :param j: the second vertex index. + :param w: the edge's weight. + + :exception std\:\:invalid_argument: if either: + + * *i* or *j* are not smaller than the number of vertices, + * *i* and *j* are already adjacent, + * *w* is not in the :math:`\left[0, 1\right]` range. + + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: void remove_edge(std::size_t i, std::size_t j) + + Remove an existing edge. + + This function will remove the edge connecting *i* to *j*. + + :param i: the first vertex index. + :param j: the second vertex index. + + :exception std\:\:invalid_argument: if either: + + * *i* or *j* are not smaller than the number of vertices, + * *i* and *j* are not adjacent. + + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: void set_weight(std::size_t i, std::size_t j, double w) + + Set the weight of an edge. + + This function will set to *w* the weight of the edge connecting *i* to *j*. + + :param i: the first vertex index. + :param j: the second vertex index. + :param w: the desired weight. + + :exception std\:\:invalid_argument: if either: + + * *i* or *j* are not smaller than the number of vertices, + * *i* and *j* are not adjacent, + * *w* is not in the :math:`\left[0, 1\right]` range. + + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: void set_all_weights(double w) + + This function will set the weights of all edges in the topology to *w*. + + :param w: the edges' weight. + + :exception std\:\:invalid_argument: if *w* is not in the :math:`\left[0, 1\right]` range. + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: std::string get_extra_info() const + + :return: a string containing human-readable information about the topology. + + :exception unspecified: any exception thrown by the public BGL API. + + .. cpp:function:: template void load(Archive &ar, unsigned) + .. cpp:function:: template void save(Archive &ar, unsigned) const + + These functions implement the serialisation of a :cpp:class:`~pagmo::base_bgl_topology`. + + :param ar: the input/output archive. + + :exception unspecified: any exception thrown by the public BGL API. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/topologies/fully_connected.rst b/doc/sphinx/docs/cpp/topologies/fully_connected.rst new file mode 100644 index 000000000..ac14356df --- /dev/null +++ b/doc/sphinx/docs/cpp/topologies/fully_connected.rst @@ -0,0 +1,92 @@ +Fully connected +=============== + +.. versionadded:: 2.11 + +*#include * + +.. image:: ../../images/fully_connected.png + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: fully_connected + + This user-defined topology (UDT) represents a *complete graph* (that is, a topology in which + all vertices connect to all other vertices). The edge weight is configurable at construction, + and it will be the same for all the edges in the topology. + + .. cpp:function:: fully_connected() + + Default constructor. + + Equivalent to the constructor from edge weight with *w* = 1. + + .. cpp:function:: explicit fully_connected(double w) + + Constructor from edge weight. + + Equivalent to the constructor from number of vertices *n* = 0 and edge + weight *w*. + + :param w: the weight of the edges. + + :except std\:\:invalid_argument: if *w* is not in the :math:`\left[0, 1\right]` range. + + .. cpp:function:: explicit fully_connected(std::size_t n, double w) + + Constructor from number of vertices and edge weight. + + This constructor will initialise a :cpp:class:`~pagmo::fully_connected` topology + with *n* vertices and whose edges will all have a weight of *w*. + + :param n: the desired number of vertices. + :param w: the weight of the edges. + + :except std\:\:invalid_argument: if *w* is not in the :math:`\left[0, 1\right]` range. + + .. cpp:function:: fully_connected(const fully_connected &) + .. cpp:function:: fully_connected(fully_connected &&) noexcept + + :cpp:class:`~pagmo::fully_connected` is copy and move constructible. + + .. cpp:function:: void push_back() + + Add a new vertex. + + .. cpp:function:: std::pair, vector_double> get_connections(std::size_t i) const + + Get the list of connections to the *i*-th vertex. + + :param i: the index of the vertex whose connections will be returned. + + :return: the list of vertices connecting to the *i*-th vertex (that is, all vertices apart from *i* + itself) and the corresponding edge weights. + + :exception std\:\:invalid_argument: if *i* is not smaller than the current size of the topology. + + .. cpp:function:: std::string get_name() const + + :return: ``"Fully connected"``. + + .. cpp:function:: std::string get_extra_info() const + + :return: a human-readable string containing additional info about this topology. + + .. cpp:function:: double get_weight() const + + :return: the weight *w* used when constructing this topology. + + .. cpp:function:: std::size_t num_vertices() const + + :return: the number of vertices in the topology. + + .. cpp:function:: template void save(Archive &ar, unsigned) const + .. cpp:function:: template void load(Archive &ar, unsigned) + + These functions implement the (de)serialisation of a :cpp:class:`~pagmo::fully_connected` topology. + + :param ar: the input/output archive. + + :exception unspecified: any exception thrown by the (de)serialisation of primitive types. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/topologies/ring.rst b/doc/sphinx/docs/cpp/topologies/ring.rst new file mode 100644 index 000000000..dba68fd9b --- /dev/null +++ b/doc/sphinx/docs/cpp/topologies/ring.rst @@ -0,0 +1,75 @@ +Ring +==== + +.. versionadded:: 2.11 + +*#include * + +.. image:: ../../images/ring.png + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: ring: public base_bgl_topology + + This user-defined topology (UDT) represents a bidirectional ring (that is, a ring + in which each node connects to both the previous and the following nodes). + + .. cpp:function:: ring() + + Default constructor. + + Equivalent to the constructor from edge weight with *w* = 1. + + .. cpp:function:: explicit ring(double w) + + Constructor from edge weight. + + New edges created via :cpp:func:`~pagmo::ring::push_back()` will have + a weight of *w*. + + :param w: the weight of the edges. + + :except std\:\:invalid_argument: if *w* is not in the :math:`\left[0, 1\right]` range. + + .. cpp:function:: explicit ring(std::size_t n, double w) + + Constructor from number of vertices and edge weight. + + This constructor will initialise a ring topology with *n* vertices and whose + edges will have a weight of *w*. + + New edges created via subsequent :cpp:func:`~pagmo::ring::push_back()` calls + will also have a weight of *w*. + + :param n: the desired number of vertices. + :param w: the weight of the edges. + + :except std\:\:invalid_argument: if *w* is not in the :math:`\left[0, 1\right]` range. + :except unspecified: any exception thrown by :cpp:func:`~pagmo::ring::push_back()`. + + .. cpp:function:: void push_back() + + Add the next vertex. + + :except unspecified: any exception thrown by the public API of :cpp:class:`~pagmo::base_bgl_topology`. + + .. cpp:function:: double get_weight() const + + :return: the weight *w* used when constructing this topology. + + .. cpp:function:: std::string get_name() const + + Get the name of the topology. + + :return: ``"Ring"``. + + .. cpp:function:: template void serialize(Archive &ar, unsigned) + + This function implements the serialisation of a :cpp:class:`~pagmo::ring`. + + :param ar: the input/output archive. + + :exception unspecified: any exception thrown by the serialisation of a :cpp:class:`~pagmo::base_bgl_topology` + or of primitive types. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/topologies/unconnected.rst b/doc/sphinx/docs/cpp/topologies/unconnected.rst new file mode 100644 index 000000000..e732bc1fe --- /dev/null +++ b/doc/sphinx/docs/cpp/topologies/unconnected.rst @@ -0,0 +1,42 @@ +Unconnected topology +==================== + +.. versionadded:: 2.11 + +*#include * + +.. image:: ../../images/unconnected.png + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: unconnected + + This user-defined topology (UDT) represents an unconnected graph. + + .. cpp:function:: std::pair, vector_double> get_connections(std::size_t) const + + Get the list of connections. + + In an unconnected topology there are no connections for any vertex. + + :return: a pair of empty vectors. + + .. cpp:function:: void push_back() + + Add the next vertex. + + This method is a no-op. + + .. cpp:function:: std::string get_name() const + + Get the name of the topology. + + :return: ``"Unconnected"``. + + .. cpp:function:: template void serialize(Archive &, unsigned) + + Serialisation support. + + This class is stateless, no data will be loaded or saved during serialization. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/topology.rst b/doc/sphinx/docs/cpp/topology.rst new file mode 100644 index 000000000..2a5bcdda8 --- /dev/null +++ b/doc/sphinx/docs/cpp/topology.rst @@ -0,0 +1,302 @@ +Topology +======== + +.. versionadded:: 2.11 + +*#include * + +.. cpp:namespace-push:: pagmo + +.. cpp:class:: topology + + In the jargon of pagmo, a topology is an object that represents connections among + :cpp:class:`islands ` in an :cpp:class:`~pagmo::archipelago`. + In essence, a topology is a *weighted directed graph* in which + + * the *vertices* (or *nodes*) are islands, + * the *edges* (or *arcs*) are directed connections between islands across which information flows during the + optimisation process (via the migration of individuals), + * the *weights* of the edges (whose numerical values are the :math:`[0.,1.]` range) represent the migration + probability. + + Following the same schema adopted for :cpp:class:`~pagmo::problem`, :cpp:class:`~pagmo::algorithm`, etc., + :cpp:class:`~pagmo::topology` exposes a type-erased generic + interface to *user-defined topologies* (or UDT for short). UDTs are classes providing a certain set + of member functions that describe the properties of (and allow to interact with) a topology. Once + defined and instantiated, a UDT can then be used to construct an instance of this class, + :cpp:class:`~pagmo::topology`, which + provides a generic interface to topologies for use by :cpp:class:`~pagmo::archipelago`. + + In a :cpp:class:`~pagmo::topology`, vertices in the graph are identified by a zero-based unique + integral index (represented by a ``std::size_t``). This integral index corresponds to the index of an + :cpp:class:`~pagmo::island` in an :cpp:class:`~pagmo::archipelago`. + + Every UDT must implement at least the following member functions: + + .. code-block:: c++ + + std::pair, vector_double> get_connections(std::size_t) const; + void push_back(); + + The ``get_connections()`` function takes as input a vertex index ``n``, and it is expected to return + a pair of vectors containing respectively: + + * the indices of the vertices which are connecting to ``n`` (that is, the list of vertices for which a directed edge + towards ``n`` exists), + * the weights (i.e., the migration probabilities) of the edges linking the connecting vertices to ``n``. + + The ``push_back()`` member function is expected to add a new vertex to the topology, assigning it the next + available index and establishing connections to other vertices. The ``push_back()`` member function is invoked + by :cpp:func:`pagmo::archipelago::push_back()` upon the insertion of a new island into an archipelago, + and it is meant + to allow the incremental construction of a topology. That is, after ``N`` calls to ``push_back()`` + on an initially-empty topology, the topology should contain ``N`` vertices and any number of edges (depending + on the specifics of the topology). + + In addition to providing the above member functions, a UDT must also be default, copy and move constructible. + + Additional optional member functions can be implemented in a UDT: + + .. code-block:: c++ + + std::string get_name() const; + std::string get_extra_info() const; + + See the documentation of the corresponding member functions in this class for details on how the optional + member functions in the UDT are used by :cpp:class:`~pagmo::topology`. + + Topologies are used in asynchronous operations involving migration in archipelagos, + and thus they need to provide a certain degree of thread safety. Specifically, the + ``get_connections()`` member function of the UDT might be invoked concurrently with + any other member function of the UDT interface (except for the destructor, the move + constructor, and, if implemented, the deserialisation function). It is up to the + authors of user-defined topologies to ensure that this safety requirement is satisfied. + + .. warning:: + + The only operations allowed on a moved-from :cpp:class:`pagmo::topology` are destruction, + assignment, and the invocation of the :cpp:func:`~pagmo::topology::is_valid()` member function. + Any other operation will result in undefined behaviour. + + .. cpp:function:: topology() + + Default constructor. + + The default constructor will initialize a :cpp:class:`~pagmo::topology` containing an + :cpp:class:`~pagmo::unconnected` topology. + + :exception unspecified: any exception raised by the constructor from a generic UDT. + + .. cpp:function:: topology(const topology &) + .. cpp:function:: topology(topology &&) noexcept + .. cpp:function:: topology &operator=(const topology &) + .. cpp:function:: topology &operator=(topology &&) noexcept + + :cpp:class:`~pagmo::topology` is copy/move constructible, and copy/move assignable. + Copy construction/assignment will perform deep copies, move operations will leave the moved-from object in + a state which is destructible and assignable. + + :exception unspecified: when performing copy operations, any exception raised by the UDT upon copying, or by memory allocation failures. + + .. cpp:function:: template explicit topology(T &&x) + + Generic constructor from a UDT. + + This constructor participates in overload resolution only if ``T``, after the removal of reference + and cv qualifiers, is not :cpp:class:`~pagmo::topology` and if it satisfies :cpp:class:`pagmo::is_udt`. + + This constructor will construct a :cpp:class:`~pagmo::topology` from the UDT (user-defined topology) + *x* of type ``T``. The input parameter *x* will be perfectly forwarded to construct the internal UDT instance. + + :param x: the input UDT. + + :exception unspecified: any exception thrown by the public API of the UDT, or by memory allocation failures. + + .. cpp:function:: template topology &operator=(T &&x) + + Generic assignment operator from a UDT. + + This operator participates in overload resolution only if ``T``, after the removal of reference + and cv qualifiers, is not :cpp:class:`~pagmo::topology` and if it satisfies :cpp:class:`pagmo::is_udt`. + + This operator will set the internal UDT to *x* by constructing a :cpp:class:`~pagmo::topology` from *x*, + and then move-assigning the result to *this*. + + :param x: the input UDT. + + :return: a reference to *this*. + + :exception unspecified: any exception thrown by the generic constructor from a UDT. + + .. cpp:function:: template const T *extract() const noexcept + .. cpp:function:: template T *extract() noexcept + + Extract a (const) pointer to the internal UDT instance. + + If ``T`` is the type of the UDT currently stored within this object, then this function + will return a (const) pointer to the internal UDT instance. Otherwise, ``nullptr`` will be returned. + + The returned value is a raw non-owning pointer: the lifetime of the pointee is tied to the lifetime + of ``this``, and ``delete`` must never be called on the pointer. + + .. warning:: + + The non-const overload of this function is provided only in order to allow to call non-const + member functions on the internal UDT instance. Assigning a new UDT via pointers obtained + through this function is undefined behaviour. + + :return: a (const) pointer to the internal UDT instance, or ``nullptr``. + + .. cpp:function:: template bool is() const noexcept + + Check the type of the UDT. + + :return: ``true`` if ``T`` is the type of the UDT currently stored within this object, ``false`` otherwise. + + .. cpp:function:: std::pair, vector_double> get_connections(std::size_t n) const + + Get the connections to a vertex. + + This function will invoke the ``get_connections()`` member function of the UDT, which is expected to return + a pair of vectors containing respectively: + + * the indices of the vertices which are connecting to *n* (that is, the list of vertices for which a directed + edge towards *n* exists), + * the weights (i.e., the migration probabilities) of the edges linking the connecting vertices to *n*. + + This function will also run sanity checks on the output of the ``get_connections()`` member function of the UDT. + + :param n: the index of the vertex whose incoming connections' details will be returned. + + :return: a pair of vectors describing *n*'s incoming connections. + + :exception std\:\:invalid_argument: if the sizes of the returned vectors differ, or if any element of the second + vector is not in the :math:`[0.,1.]` range. + :exception unspecified: any exception thrown by the ``get_connections()`` member function of the UDT. + + .. cpp:function:: void push_back() + + Add a vertex. + + This member function will invoke the ``push_back()`` member function of the UDT, which is expected to add a new vertex to the + topology, assigning it the next available index and establishing connections to other vertices. + + :exception unspecified: any exception thrown by the ``push_back()`` member function of the UDT. + + .. cpp:function:: void push_back(unsigned n) + + Add multiple vertices. + + This member function will call :cpp:func:`~pagmo::topology::push_back()` *n* times. + + :param n: the number of times :cpp:func:`~pagmo::topology::push_back()` will be called. + + :exception unspecified: any exception thrown by :cpp:func:`~pagmo::topology::push_back()`. + + .. cpp:function:: std::string get_name() const + + Get the name of this topology. + + If the UDT satisfies :cpp:class:`pagmo::has_name`, then this member function will return the output of its ``get_name()`` member function. + Otherwise, an implementation-defined name based on the type of the UDT will be returned. + + :return: the name of this topology. + + :exception unspecified: any exception thrown by copying an ``std::string`` object. + + .. cpp:function:: std::string get_extra_info() const + + Extra info for this topology. + + If the UDT satisfies :cpp:class:`pagmo::has_extra_info`, then this member function will return the output of its + ``get_extra_info()`` member function. Otherwise, an empty string will be returned. + + :return: extra info about the UDT. + + :exception unspecified: any exception thrown by the ``get_extra_info()`` member function of the UDT, or by copying an ``std::string`` object. + + .. cpp:function:: bool is_valid() const + + Check if this topology is in a valid state. + + :return: ``false`` if *this* was moved from, ``true`` otherwise. + + .. cpp:function:: template void save(Archive &ar, unsigned) const + .. cpp:function:: template void load(Archive &ar, unsigned) + + Serialisation support. + + These two member functions are used to implement the (de)serialisation of a topology to/from an archive. + + :param ar: the input/output archive. + + :exception unspecified: any exception raised by the (de)serialisation of primitive types or of the UDT. + +Functions +--------- + +.. cpp:function:: std::ostream &operator<<(std::ostream &os, const topology &t) + + Stream insertion operator. + + This function will direct to *os* a human-readable representation of the input + :cpp:class:`~pagmo::topology` *t*. + + :param os: the input ``std::ostream``. + :param t: the topology that will be directed to *os*. + + :return: a reference to *os*. + + :exception unspecified: any exception thrown by querying various properties of the topology and directing them to *os*. + +Associated type traits +---------------------- + +.. cpp:class:: template has_get_connections + + The :cpp:any:`value` of this type trait will be ``true`` if + ``T`` provides a member function with signature: + + .. code-block:: c++ + + std::pair, vector_double> get_connections(std::size_t) const; + + The ``get_connections()`` member function is part of the interface for the definition of a + :cpp:class:`~pagmo::topology`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:class:: template has_push_back + + The :cpp:any:`value` of this type trait will be ``true`` if + ``T`` provides a member function with signature: + + .. code-block:: c++ + + void push_back(); + + The ``push_back()`` member function is part of the interface for the definition of a + :cpp:class:`~pagmo::topology`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:class:: template is_udt + + This type trait detects if ``T`` is a user-defined topology (or UDT). + + Specifically, the :cpp:any:`value` of this type trait will be ``true`` if: + + * ``T`` is not a reference or cv qualified, + * ``T`` is destructible, default, copy and move constructible, and + * ``T`` satisfies :cpp:class:`pagmo::has_get_connections` and + :cpp:class:`pagmo::has_push_back`. + + .. cpp:member:: static const bool value + + The value of the type trait. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/cpp/types.rst b/doc/sphinx/docs/cpp/types.rst index 2a4d76782..a3b600ae3 100644 --- a/doc/sphinx/docs/cpp/types.rst +++ b/doc/sphinx/docs/cpp/types.rst @@ -1,6 +1,32 @@ Types ===== +*#include * + .. doxygentypedef:: pagmo::vector_double -.. doxygentypedef:: pagmo::sparsity_pattern \ No newline at end of file +.. doxygentypedef:: pagmo::sparsity_pattern + +.. doxygentypedef:: pagmo::pop_size_t + +.. cpp:namespace-push:: pagmo + +.. cpp:type:: individuals_group_t = std::tuple, std::vector, std::vector> + + .. versionadded:: 2.11 + + Group of individuals. + + This tuple represents a group of individuals via: + + * a vector of ``unsigned long long`` representing the IDs of the individuals, + * a vector of :cpp:type:`~pagmo::vector_double` representing the decision vectors + (or chromosomes) of the individuals, + * another vector of :cpp:type:`~pagmo::vector_double` representing the fitness + vectors of the individuals. + + In other words, :cpp:type:`~pagmo::individuals_group_t` is a stripped-down version of + :cpp:class:`~pagmo::population` without the :cpp:class:`~pagmo::problem`. :cpp:type:`~pagmo::individuals_group_t` + is used to exchange individuals between the islands of an :cpp:class:`~pagmo::archipelago` during migration. + +.. cpp:namespace-pop:: diff --git a/doc/sphinx/docs/images/fully_connected.png b/doc/sphinx/docs/images/fully_connected.png new file mode 100644 index 000000000..106660105 Binary files /dev/null and b/doc/sphinx/docs/images/fully_connected.png differ diff --git a/doc/sphinx/docs/images/ring.png b/doc/sphinx/docs/images/ring.png new file mode 100644 index 000000000..61326be28 Binary files /dev/null and b/doc/sphinx/docs/images/ring.png differ diff --git a/doc/sphinx/docs/images/unconnected.png b/doc/sphinx/docs/images/unconnected.png new file mode 100644 index 000000000..f4aa115fe Binary files /dev/null and b/doc/sphinx/docs/images/unconnected.png differ diff --git a/doc/sphinx/docs/python/py_bfe.rst b/doc/sphinx/docs/python/py_bfe.rst index 7c1e39fd2..e2437c503 100644 --- a/doc/sphinx/docs/python/py_bfe.rst +++ b/doc/sphinx/docs/python/py_bfe.rst @@ -1,5 +1,5 @@ -Bfe class -========= +Batch fitness evaluator +======================= .. autoclass:: pygmo.bfe :members: diff --git a/doc/sphinx/docs/python/py_misc.rst b/doc/sphinx/docs/python/py_misc.rst index 4d4ffe80f..799fe2c3b 100644 --- a/doc/sphinx/docs/python/py_misc.rst +++ b/doc/sphinx/docs/python/py_misc.rst @@ -12,6 +12,18 @@ Miscellanea -------------------------------------- +.. autoclass:: pygmo.migration_type + :members: + :member-order: bysource + +-------------------------------------- + +.. autoclass:: pygmo.migrant_handling + :members: + :member-order: bysource + +-------------------------------------- + .. autofunction:: pygmo.set_serialization_backend() .. autofunction:: pygmo.get_serialization_backend() diff --git a/doc/sphinx/docs/python/py_r_policy.rst b/doc/sphinx/docs/python/py_r_policy.rst new file mode 100644 index 000000000..32bf5c08b --- /dev/null +++ b/doc/sphinx/docs/python/py_r_policy.rst @@ -0,0 +1,6 @@ +Replacement policy +================== + +.. autoclass:: pygmo.r_policy + :members: + :special-members: diff --git a/doc/sphinx/docs/python/py_s_policy.rst b/doc/sphinx/docs/python/py_s_policy.rst new file mode 100644 index 000000000..da465a188 --- /dev/null +++ b/doc/sphinx/docs/python/py_s_policy.rst @@ -0,0 +1,6 @@ +Selection policy +================ + +.. autoclass:: pygmo.s_policy + :members: + :special-members: diff --git a/doc/sphinx/docs/python/py_topology.rst b/doc/sphinx/docs/python/py_topology.rst new file mode 100644 index 000000000..b662bf0a5 --- /dev/null +++ b/doc/sphinx/docs/python/py_topology.rst @@ -0,0 +1,6 @@ +Topology +======== + +.. autoclass:: pygmo.topology + :members: + :special-members: diff --git a/doc/sphinx/docs/python/python_docs.rst b/doc/sphinx/docs/python/python_docs.rst index 24dec1860..9db253a42 100644 --- a/doc/sphinx/docs/python/python_docs.rst +++ b/doc/sphinx/docs/python/python_docs.rst @@ -17,12 +17,15 @@ These are the core PyGMO classes. py_island py_archipelago py_bfe + py_topology + py_r_policy + py_s_policy py_misc -Implemented problems, algorithms, islands and bfes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -These are the user-defined problems, algorithms, islands and -bfes implemented in PyGMO. +Implemented user-defined classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +These are the user-defined problems, algorithms, islands, +batch evaluators and topologies implemented in PyGMO. .. toctree:: :maxdepth: 1 @@ -31,6 +34,9 @@ bfes implemented in PyGMO. algorithms/py_algorithms islands/py_islands bfes/py_bfes + topologies/py_topologies + r_policies/py_r_policies + s_policies/py_s_policies Utilities ^^^^^^^^^ diff --git a/doc/sphinx/docs/python/r_policies/py_r_policies.rst b/doc/sphinx/docs/python/r_policies/py_r_policies.rst new file mode 100644 index 000000000..39acd554f --- /dev/null +++ b/doc/sphinx/docs/python/r_policies/py_r_policies.rst @@ -0,0 +1,12 @@ +.. _py_r_policies: + +List of replacement policies available in pygmo +=============================================== + +.. _py_r_policies_cpp: + +Replacement policies exposed from C++ +------------------------------------- + +.. autoclass:: pygmo.fair_replace + :members: diff --git a/doc/sphinx/docs/python/s_policies/py_s_policies.rst b/doc/sphinx/docs/python/s_policies/py_s_policies.rst new file mode 100644 index 000000000..611285be0 --- /dev/null +++ b/doc/sphinx/docs/python/s_policies/py_s_policies.rst @@ -0,0 +1,12 @@ +.. _py_s_policies: + +List of selection policies available in pygmo +============================================= + +.. _py_s_policies_cpp: + +Selection policies exposed from C++ +----------------------------------- + +.. autoclass:: pygmo.select_best + :members: diff --git a/doc/sphinx/docs/python/topologies/py_topologies.rst b/doc/sphinx/docs/python/topologies/py_topologies.rst new file mode 100644 index 000000000..7fc308f97 --- /dev/null +++ b/doc/sphinx/docs/python/topologies/py_topologies.rst @@ -0,0 +1,22 @@ +.. _py_topologies: + +List of topologies available in pygmo +===================================== + +.. _py_topologies_cpp: + +Topologies exposed from C++ +--------------------------- + +.. autoclass:: pygmo.unconnected + :members: + +------------------------------------------------------------- + +.. autoclass:: pygmo.ring + :members: + +------------------------------------------------------------- + +.. autoclass:: pygmo.fully_connected + :members: diff --git a/doc/sphinx/docs/python/tutorials/coding_udi.rst b/doc/sphinx/docs/python/tutorials/coding_udi.rst index 1b1ac3df0..83e633e5f 100644 --- a/doc/sphinx/docs/python/tutorials/coding_udi.rst +++ b/doc/sphinx/docs/python/tutorials/coding_udi.rst @@ -38,6 +38,10 @@ on some pygmo classes. The above UDI can then be used to construct a :class:`~py Problem: Ackley Function + Replacement policy: Fair replace + + Selection policy: Select best + Population size: 20 Champion decision vector: [... Champion fitness: [... diff --git a/doc/sphinx/docs/python/tutorials/using_archipelago.rst b/doc/sphinx/docs/python/tutorials/using_archipelago.rst index e40d9f5a1..c93962808 100644 --- a/doc/sphinx/docs/python/tutorials/using_archipelago.rst +++ b/doc/sphinx/docs/python/tutorials/using_archipelago.rst @@ -206,7 +206,7 @@ scope. To inspect what happened we can write: in () ----> 1 archi.wait_check() - RuntimeError: The asynchronous evolution of a Pythonic island of type 'Multiprocessing island' raised an error: + RuntimeError: The asynchronous evolution of a pythonic island of type 'Multiprocessing island' raised an error: multiprocessing.pool.RemoteTraceback: """ Traceback (most recent call last): diff --git a/doc/sphinx/docs/python/tutorials/using_island.rst b/doc/sphinx/docs/python/tutorials/using_island.rst index 36125b06f..65eb2cf4b 100644 --- a/doc/sphinx/docs/python/tutorials/using_island.rst +++ b/doc/sphinx/docs/python/tutorials/using_island.rst @@ -153,7 +153,7 @@ What has happened? I need to retrieve that message! compileflags, 1), test.globs) File "", line 1, in isl.wait_check() - RuntimeError: The asynchronous evolution of a Pythonic island of type 'Ipyparallel island' raised an error: + RuntimeError: The asynchronous evolution of a pythonic island of type 'Ipyparallel island' raised an error: Traceback (most recent call last): File "/Users/darioizzo/.local/lib/python3.6/site-packages/pygmo/_py_islands.py", line 403, in run_evolve return ret.get() diff --git a/include/pagmo/archipelago.hpp b/include/pagmo/archipelago.hpp index 52bc54b6a..31fa07ca3 100644 --- a/include/pagmo/archipelago.hpp +++ b/include/pagmo/archipelago.hpp @@ -29,10 +29,14 @@ see https://www.gnu.org/licenses/. */ #ifndef PAGMO_ARCHIPELAGO_HPP #define PAGMO_ARCHIPELAGO_HPP +#include #include #include +#include #include +#include #include +#include #include #include @@ -46,35 +50,105 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include +#include +#include #include #include namespace pagmo { +/// Migration type. +/** + * \verbatim embed:rst:leading-asterisk + * This enumeration represents the available migration policies + * in an :cpp:class:`~pagmo::archipelago`: + * + * - with the point-to-point migration policy, during migration an island will + * consider individuals from only one of the connecting islands; + * - with the broadcast migration policy, during migration an island will consider + * individuals from *all* the connecting islands. + * + * \endverbatim + */ +enum class migration_type { + p2p, ///< Point-to-point migration. + broadcast ///< Broadcast migration. +}; + +/// Migrant handling policy. +/** + * \verbatim embed:rst:leading-asterisk + * This enumeration represents the available migrant handling + * policies in an :cpp:class:`~pagmo::archipelago`. + * + * During migration, + * individuals are selected from the islands and copied into a migration + * database, from which they can be fetched by other islands. + * This policy establishes what happens to the migrants in the database + * after they have been fetched by a destination island: + * + * - with the preserve policy, a copy of the candidate migrants + * remains in the database; + * - with the evict policy, the candidate migrants are + * removed from the database. + * + * \endverbatim + */ +enum class migrant_handling { + preserve, ///< Perserve migrants in the database. + evict ///< Evict migrants from the database. +}; + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Provide the stream operator overloads for migration_type and migrant_handling. +PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, migration_type); +PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, migrant_handling); + +#endif + /// Archipelago. /** * \image html archi_no_text.png * - * An archipelago is a collection of pagmo::island objects which provides a convenient way to perform - * multiple optimisations in parallel. + * \verbatim embed:rst:leading-asterisk + * An archipelago is a collection of :cpp:class:`~pagmo::island` objects connected by a + * :cpp:class:`~pagmo::topology`. The islands in the archipelago can exchange individuals + * (i.e., candidate solutions) via a process called *migration*. The individuals migrate + * across the routes described by the topology, and the islands' replacement + * and selection policies (see :cpp:class:`~pagmo::r_policy` and :cpp:class:`~pagmo::s_policy`) + * establish how individuals are replaced in and selected from the islands' populations. * - * The interface of pagmo::archipelago mirrors partially the interface - * of pagmo::island: the evolution is initiated by a call to evolve(), and at any time the user can query the + * The interface of :cpp:class:`~pagmo::archipelago` mirrors partially the interface + * of :cpp:class:`~pagmo::island`: the evolution is initiated by a call to :cpp:func:`~pagmo::archipelago::evolve()`, + * and at any time the user can query the * state of the archipelago and access its island members. The user can explicitly wait for pending evolutions - * to conclude by calling the wait() and wait_check() methods. The status of - * ongoing evolutions in the archipelago can be queried via status(). + * to conclude by calling the :cpp:func:`~pagmo::archipelago::wait()` and :cpp:func:`~pagmo::archipelago::wait_check()` + * methods. The status of + * ongoing evolutions in the archipelago can be queried via :cpp:func:`~pagmo::archipelago::status()`. + * + * .. warning:: + * + * The only operations allowed on a moved-from :cpp:class:`pagmo::archipelago` are destruction + * and assignment. Any other operation will result in undefined behaviour. + * + * \endverbatim */ class PAGMO_DLL_PUBLIC archipelago { + // Make friends with island. + friend class PAGMO_DLL_PUBLIC island; + using container_t = std::vector>; using size_type_implementation = container_t::size_type; using iterator_implementation = boost::indirect_iterator; using const_iterator_implementation = boost::indirect_iterator; // NOTE: same utility method as in pagmo::island, see there. - void wait_check_ignore(); + PAGMO_DLL_LOCAL void wait_check_ignore(); public: /// The size type of the archipelago. @@ -83,15 +157,61 @@ class PAGMO_DLL_PUBLIC archipelago * archipelago. */ using size_type = size_type_implementation; + + /// Database of migrants. + /** + * \verbatim embed:rst:leading-asterisk + * During the evolution of an archipelago, islands will periodically + * store the individuals selected for migration in a *migrant database*. + * This is a vector of :cpp:type:`~pagmo::individuals_group_t` whose + * size is equal to the number of islands in the archipelago, and which + * contains the current candidate outgoing migrants for each island. + * \endverbatim + */ + using migrants_db_t = std::vector; + + /// Entry for the migration log. + /** + * \verbatim embed:rst:leading-asterisk + * Each time an individual migrates from an island (the source) to another + * (the destination), an entry will be added to the migration log. + * The entry is a tuple containing: + * + * - a timestamp of the migration, + * - the ID of the individual that migrated, + * - the decision and fitness vectors of the individual that migrated, + * - the indices of the source and destination islands. + * + * \endverbatim + */ + using migration_entry_t + = std::tuple; + + /// Migration log. + /** + * \verbatim embed:rst:leading-asterisk + * The migration log is a collection of :cpp:type:`~pagmo::archipelago::migration_entry_t` entries. + * \endverbatim + */ + using migration_log_t = std::vector; + +private: + // A map to connect island pointers to an idx + // in the archipelago. This will be used by islands + // during migration in order to establish the island + // indices within the archipelago. + using idx_map_t = std::unordered_map; + +public: /// Mutable iterator. /** * Dereferencing a mutable iterator will yield a reference to an island within the archipelago. * * \verbatim embed:rst:leading-asterisk - * .. note:: + * .. warning:: * * Mutable iterators are provided solely in order to allow calling non-const methods - * on the islands. Assigning an island via a mutable iterator will be undefined behaviour. + * on the islands. Assigning an island via a mutable iterator will result in undefined behaviour. * * \endverbatim */ @@ -106,7 +226,36 @@ class PAGMO_DLL_PUBLIC archipelago // Copy constructor. archipelago(const archipelago &); // Move constructor. - archipelago(archipelago &&other) noexcept; + archipelago(archipelago &&) noexcept; + +private: + template + using topo_ctor_enabler = enable_if_t::value, int>; + +public: + /// Constructor from a topology. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``t`` + * can be used to construct a :cpp:class:`pagmo::topology`. + * + * This constructor is equivalent to the default constructor, but it will additionally + * allow to select the archipelago's topology. + * + * \endverbatim + * + * @param t the desired (user-defined) topology. + * + * @throws unspecified any exception thrown by the invoked topology constructor. + */ + template = 0> + explicit archipelago(Topo &&t) + : m_topology(std::forward(t)), m_migr_type(migration_type::p2p), + m_migr_handling(migrant_handling::preserve) + { + } private: #if defined(_MSC_VER) @@ -129,9 +278,16 @@ class PAGMO_DLL_PUBLIC archipelago push_back(args...); } } - // These implement the constructor which contains a seed argument. - // We need to constrain with enable_if otherwise, due to the default - // arguments in the island constructors, the wrong constructor would be called. + // The following functions are used to implement construction + // from n islands when a seed argument is used. In that case, + // we will reinterpret the meaning of the seed argument (as + // explained below). Thus, we need to provide one implementation + // of these functions for each island constructor that accepts a seed + // argument. There's repetition, and probably it could be improved + // with some metaprogramming, but then we would probably have + // to fight hard against older MSVC versions. Perhaps in the future? + // + // algo, prob. template , std::is_constructible, std::is_integral, @@ -145,7 +301,23 @@ class PAGMO_DLL_PUBLIC archipelago push_back(a, p, boost::numeric_cast(size), udist(eng)); } } - // Same as previous, with bfe argument. + // algo, prob, rpol, spol. + template < + typename Algo, typename Prob, typename S1, typename RPol, typename SPol, typename S2, + enable_if_t, std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_integral, std::is_integral>::value, + int> = 0> + void n_ctor(size_type n, const Algo &a, const Prob &p, S1 size, const RPol &r_pol, const SPol &s_pol, S2 seed) + { + std::mt19937 eng(static_cast(static_cast(seed))); + std::uniform_int_distribution udist; + for (size_type i = 0; i < n; ++i) { + push_back(a, p, boost::numeric_cast(size), r_pol, s_pol, udist(eng)); + } + } + // algo, prob, bfe. // NOTE: performance wise, it would be better for these constructors from bfe // to batch initialise *all* archi individuals // (whereas now we batch init n times, one for each island). Keep this in mind @@ -164,7 +336,24 @@ class PAGMO_DLL_PUBLIC archipelago push_back(a, p, b, boost::numeric_cast(size), udist(eng)); } } - // Constructor with UDI argument. + // algo, prob, bfe, rpol, spol. + template , std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_constructible, std::is_integral, std::is_integral>::value, + int> = 0> + void n_ctor(size_type n, const Algo &a, const Prob &p, const Bfe &b, S1 size, const RPol &r_pol, const SPol &s_pol, + S2 seed) + { + std::mt19937 eng(static_cast(static_cast(seed))); + std::uniform_int_distribution udist; + for (size_type i = 0; i < n; ++i) { + push_back(a, p, b, boost::numeric_cast(size), r_pol, s_pol, udist(eng)); + } + } + // isl, algo, prob. template , std::is_constructible, std::is_constructible, std::is_integral, @@ -178,7 +367,24 @@ class PAGMO_DLL_PUBLIC archipelago push_back(isl, a, p, boost::numeric_cast(size), udist(eng)); } } - // Same as previous, with bfe argument. + // isl, algo, prob, rpol, spol. + template , std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_constructible, std::is_integral, std::is_integral>::value, + int> = 0> + void n_ctor(size_type n, const Isl &isl, const Algo &a, const Prob &p, S1 size, const RPol &r_pol, + const SPol &s_pol, S2 seed) + { + std::mt19937 eng(static_cast(static_cast(seed))); + std::uniform_int_distribution udist; + for (size_type i = 0; i < n; ++i) { + push_back(isl, a, p, boost::numeric_cast(size), r_pol, s_pol, udist(eng)); + } + } + // isl, algo, prob, bfe. template , std::is_constructible, std::is_constructible, @@ -193,6 +399,25 @@ class PAGMO_DLL_PUBLIC archipelago push_back(isl, a, p, b, boost::numeric_cast(size), udist(eng)); } } + // isl, algo, prob, bfe, rpol, spol. + template < + typename Isl, typename Algo, typename Prob, typename Bfe, typename S1, typename RPol, typename SPol, + typename S2, + enable_if_t, std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_integral, std::is_integral>::value, + int> = 0> + void n_ctor(size_type n, const Isl &isl, const Algo &a, const Prob &p, const Bfe &b, S1 size, const RPol &r_pol, + const SPol &s_pol, S2 seed) + { + std::mt19937 eng(static_cast(static_cast(seed))); + std::uniform_int_distribution udist; + for (size_type i = 0; i < n; ++i) { + push_back(isl, a, p, b, boost::numeric_cast(size), r_pol, s_pol, udist(eng)); + } + } public: /// Constructor from \p n islands. @@ -200,13 +425,16 @@ class PAGMO_DLL_PUBLIC archipelago * \verbatim embed:rst:leading-asterisk * .. note:: * - * This constructor is enabled only if the parameter pack ``Args`` + * This constructor is enabled only if the parameter pack ``args`` * can be used to construct a :cpp:class:`pagmo::island`. * * \endverbatim * - * This constructor will forward \p n times the input arguments \p args to the - * push_back() method. If, however, the parameter pack contains an argument which + * This constructor will first initialise an empty archipelago with a default-constructed + * topology, and then forward \p n times the input arguments \p args to the + * push_back() method, thus creating and inserting \p n new islands into the archipelago. + * + * If, however, the parameter pack \p args contains an argument which * would be interpreted as a seed by the invoked island constructor, then this seed * will be used to initialise a random number generator that in turn will be used to generate * the seeds of populations of the islands that will be created within the archipelago. In other words, @@ -217,11 +445,47 @@ class PAGMO_DLL_PUBLIC archipelago * @param n the desired number of islands. * @param args the arguments that will be used for the construction of each island. * - * @throws unspecified any exception thrown by the invoked pagmo::island constructor - * or by archipelago::push_back(). + * @throws unspecified any exception thrown by archipelago::push_back(). */ template = 0> explicit archipelago(size_type n, const Args &... args) + : // NOTE: explicitly delegate to the default constructor, so that + // we get the default migration type and migrant handling. + archipelago() + { + n_ctor(n, args...); + } + +private: + template + using topo_n_ctor_enabler = enable_if_t< + detail::conjunction, std::is_constructible>::value, + int>; + +public: + /// Constructor from a topology and \p n islands. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``t`` + * can be used to construct a :cpp:class:`~pagmo::topology` and if the parameter pack ``args`` + * can be used to construct a :cpp:class:`~pagmo::island`. + * + * This constructor is equivalent to the previous one, but it will additionally + * allow to select the archipelago's topology. + * + * \endverbatim + * + * @param t the desired (user-defined) topology. + * @param n the desired number of islands. + * @param args the arguments that will be used for the construction of each island. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the constructor from a topology. + */ + template = 0> + explicit archipelago(Topo &&t, size_type n, const Args &... args) : archipelago(std::forward(t)) { n_ctor(n, args...); } @@ -234,14 +498,8 @@ class PAGMO_DLL_PUBLIC archipelago island &operator[](size_type); // Const island access. const island &operator[](size_type) const; - /// Size. - /** - * @return the number of islands in the archipelago. - */ - size_type size() const - { - return m_islands.size(); - } + // Size. + size_type size() const; private: #if defined(_MSC_VER) @@ -252,32 +510,42 @@ class PAGMO_DLL_PUBLIC archipelago using push_back_enabler = enable_if_t::value, int>; #endif + // Implementation of push_back(). + void push_back_impl(std::unique_ptr &&); + public: - /// Add island. + /// Add a new island. /** * \verbatim embed:rst:leading-asterisk * .. note:: * - * This method is enabled only if the parameter pack ``Args`` + * This method is enabled only if the parameter pack ``args`` * can be used to construct a :cpp:class:`pagmo::island`. * - * \endverbatim - * * This method will construct an island from the supplied arguments and add it to the archipelago. * Islands are added at the end of the archipelago (that is, the new island will have an index - * equal to the value of size() before the call to this method). + * equal to the value of :cpp:func:`~pagmo::archipelago::size()` before the call to this method). + * :cpp:func:`pagmo::topology::push_back()` + * will also be called on the :cpp:class:`~pagmo::topology` associated to this archipelago, so that + * the addition of a new island to the archipelago is mirrored by the addition of a new vertex + * to the topology. + * + * \endverbatim * * @param args the arguments that will be used for the construction of the island. * - * @throws unspecified any exception thrown by memory allocation errors or by the invoked constructor - * of pagmo::island. + * @throws std::overflow_error if the size of the archipelago is greater than an + * implementation-defined maximum. + * @throws unspecified any exception thrown by: + * - memory allocation errors, + * - threading primitives, + * - pagmo::topology::push_back(), + * - the invoked constructor of pagmo::island. */ template = 0> void push_back(Args &&... args) { - m_islands.emplace_back(detail::make_unique(std::forward(args)...)); - // NOTE: this is noexcept. - m_islands.back()->m_ptr->archi_ptr = this; + push_back_impl(detail::make_unique(std::forward(args)...)); } /// Evolve archipelago. /** @@ -297,6 +565,7 @@ class PAGMO_DLL_PUBLIC archipelago void wait_check(); // Status of the archipelago. evolve_status status() const; + /// Mutable begin iterator. /** * This method will return a mutable iterator pointing to the beginning of the internal island container. That is, @@ -306,10 +575,10 @@ class PAGMO_DLL_PUBLIC archipelago * Adding an island to the archipelago will invalidate all existing iterators. * * \verbatim embed:rst:leading-asterisk - * .. note:: + * .. warning:: * * Mutable iterators are provided solely in order to allow calling non-const methods - * on the islands. Assigning an island via a mutable iterator will be undefined behaviour. + * on the islands. Assigning an island via a mutable iterator will result in undefined behaviour. * * \endverbatim * @@ -326,10 +595,10 @@ class PAGMO_DLL_PUBLIC archipelago * Adding an island to the archipelago will invalidate all existing iterators. * * \verbatim embed:rst:leading-asterisk - * .. note:: + * .. warning:: * * Mutable iterators are provided solely in order to allow calling non-const methods - * on the islands. Assigning an island via a mutable iterator will be undefined behaviour. + * on the islands. Assigning an island via a mutable iterator will result in undefined behaviour. * * \endverbatim * @@ -365,22 +634,43 @@ class PAGMO_DLL_PUBLIC archipelago { return const_iterator(m_islands.end()); } + // Get the fitness vectors of the islands' champions. std::vector get_champions_f() const; // Get the decision vectors of the islands' champions. std::vector get_champions_x() const; + + // Get the migration log. + migration_log_t get_migration_log() const; + // Get the database of migrants. + migrants_db_t get_migrants_db() const; + + // Topology get/set. + topology get_topology() const; + void set_topology(topology); + + // Getters/setters for the migration type and + // the migrant handling policy. + migration_type get_migration_type() const; + void set_migration_type(migration_type); + migrant_handling get_migrant_handling() const; + void set_migrant_handling(migrant_handling); + /// Save to archive. /** * This method will save to \p ar the islands of the archipelago. * * @param ar the output archive. * - * @throws unspecified any exception thrown by the serialization of pagmo::island. + * @throws unspecified any exception thrown by the serialization of pagmo::island, pagmo::topology + * or primitive types. */ template void save(Archive &ar, unsigned) const { - ar << m_islands; + detail::to_archive(ar, m_islands, get_migrants_db(), get_migration_log(), get_topology(), + m_migr_type.load(std::memory_order_relaxed), + m_migr_handling.load(std::memory_order_relaxed)); } /// Load from archive. /** @@ -389,31 +679,100 @@ class PAGMO_DLL_PUBLIC archipelago * * @param ar the input archive. * - * @throws unspecified any exception thrown by the deserialization of pagmo::island. + * @throws unspecified any exception thrown by the deserialization of pagmo::island, pagmo::topology + * or primitive types, or by memory errors in standard containers. */ template void load(Archive &ar, unsigned) { + // NOTE: the idea here is that we will be loading the member of archi one by one in + // separate variables, move assign the loaded data into a tmp archi and finally move-assign + // the tmp archi into this. This allows the method to be exception safe, and to have + // archi objects always in a consistent state at every stage of the deserialization. + + // The tmp archi. This is def-cted and idle, we will be able to move-in data without + // worrying about synchronization. archipelago tmp; - try { - ar >> tmp.m_islands; - // LCOV_EXCL_START - } catch (...) { - // Clear the islands vector before re-throwing if anything goes wrong. - // The islands in tmp will not have the archi ptr set correctly, - // and thus an assertion would fail in the archi dtor in debug mode. - tmp.m_islands.clear(); - throw; + + // The islands. + container_t tmp_islands; + ar >> tmp_islands; + + // Map the islands to indices. + idx_map_t tmp_idx_map; + for (size_type i = 0; i < tmp_islands.size(); ++i) { + tmp_idx_map.emplace(tmp_islands[i].get(), i); } - // LCOV_EXCL_STOP - // This will set the island archi pointers - // to the correct value. + + // The migrants. + migrants_db_t tmp_migrants; + ar >> tmp_migrants; + + // The migration log. + migration_log_t tmp_migr_log; + ar >> tmp_migr_log; + + // The topology. + topology tmp_topo; + ar >> tmp_topo; + + // Migration type and migrant handling policy. + migration_type tmp_migr_type; + migrant_handling tmp_migr_handling; + + ar >> tmp_migr_type; + ar >> tmp_migr_handling; + + // From now on, everything is noexcept. Thus, there is + // no danger that tmp is destructed while in an inconsistent + // state. + tmp.m_islands = std::move(tmp_islands); + tmp.m_idx_map = std::move(tmp_idx_map); + tmp.m_migrants = std::move(tmp_migrants); + tmp.m_migr_log = std::move(tmp_migr_log); + tmp.m_topology = std::move(tmp_topo); + tmp.m_migr_type.store(tmp_migr_type, std::memory_order_relaxed); + tmp.m_migr_handling.store(tmp_migr_handling, std::memory_order_relaxed); + + // NOTE: this final assignment will take care of setting the islands' archi pointers + // appropriately via archi's move assignment operator. *this = std::move(tmp); } BOOST_SERIALIZATION_SPLIT_MEMBER() +private: + // Private utilities for use only by island. + // Extract/get/set migrants for the island at the given index. + PAGMO_DLL_LOCAL individuals_group_t extract_migrants(size_type); + PAGMO_DLL_LOCAL individuals_group_t get_migrants(size_type) const; + PAGMO_DLL_LOCAL void set_migrants(size_type, individuals_group_t &&); + // Helper to add entries to the migration log. + PAGMO_DLL_LOCAL void append_migration_log(const migration_log_t &); + // Get the index of an island. + PAGMO_DLL_LOCAL size_type get_island_idx(const island &) const; + // Get the connections to the island at the given index. + PAGMO_DLL_LOCAL std::pair, vector_double> get_island_connections(size_type) const; + private: container_t m_islands; + // The map from island pointers to indices in the archi. + // It needs to be protected by a mutex. + mutable std::mutex m_idx_map_mutex; + idx_map_t m_idx_map; + // The migrants. + mutable std::mutex m_migrants_mutex; + migrants_db_t m_migrants; + // The migration log. + mutable std::mutex m_migr_log_mutex; + migration_log_t m_migr_log; + // The topology. + // NOTE: the topology does not need + // an associated mutex as it is supposed + // to be thread-safe already. + topology m_topology; + // Migration type and migrant handling policy. + std::atomic m_migr_type; + std::atomic m_migr_handling; }; // Stream operator. diff --git a/include/pagmo/bfe.hpp b/include/pagmo/bfe.hpp index 0a267c384..42db52df7 100644 --- a/include/pagmo/bfe.hpp +++ b/include/pagmo/bfe.hpp @@ -285,8 +285,10 @@ class PAGMO_DLL_PUBLIC bfe { return extract() != nullptr; } + // Call operator. vector_double operator()(const problem &, const vector_double &) const; + // Name. std::string get_name() const { @@ -294,6 +296,7 @@ class PAGMO_DLL_PUBLIC bfe } // Extra info. std::string get_extra_info() const; + // Thread safety level. thread_safety get_thread_safety() const { diff --git a/include/pagmo/detail/base_sr_policy.hpp b/include/pagmo/detail/base_sr_policy.hpp new file mode 100644 index 000000000..f47743ed0 --- /dev/null +++ b/include/pagmo/detail/base_sr_policy.hpp @@ -0,0 +1,99 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_DETAIL_BASE_SR_POLICY_HPP +#define PAGMO_DETAIL_BASE_SR_POLICY_HPP + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +class PAGMO_DLL_PUBLIC base_sr_policy +{ + void verify_fp_ctor() const; + + // Dispatching for the generic ctor + // via two private constructors: one + // for absolute migration rate, one + // for fractional migration rate. + struct ptag { + }; + // Absolute migration rate. + template ::value, int> = 0> + explicit base_sr_policy(ptag, T n) : m_migr_rate(boost::numeric_cast(n)) + { + } + // Fractional migration rate. + template ::value, int> = 0> + explicit base_sr_policy(ptag, T x) : m_migr_rate(static_cast(x)) + { + verify_fp_ctor(); + } + +public: + // Constructor from fractional or absolute migration policy. + template , std::is_floating_point>::value, int> = 0> + explicit base_sr_policy(T x) : base_sr_policy(ptag{}, x) + { + } + + // Serialization support. + template + void serialize(Archive &ar, unsigned) + { + detail::archive(ar, m_migr_rate); + } + + const boost::variant &get_migr_rate() const; + +protected: + boost::variant m_migr_rate; +}; + +} // namespace detail + +} // namespace pagmo + +// Disable tracking for the serialisation of base_sr_policy. +BOOST_CLASS_TRACKING(pagmo::detail::base_sr_policy, boost::serialization::track_never) + +#endif diff --git a/include/pagmo/detail/island_fwd.hpp b/include/pagmo/detail/island_fwd.hpp new file mode 100644 index 000000000..b9d6ded98 --- /dev/null +++ b/include/pagmo/detail/island_fwd.hpp @@ -0,0 +1,42 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_DETAIL_ISLAND_FWD_HPP +#define PAGMO_DETAIL_ISLAND_FWD_HPP + +#include + +namespace pagmo +{ + +// Fwd declaration. +class PAGMO_DLL_PUBLIC island; + +} // namespace pagmo + +#endif diff --git a/include/pagmo/io.hpp b/include/pagmo/io.hpp index 9f3da0d5f..2bfaf7412 100644 --- a/include/pagmo/io.hpp +++ b/include/pagmo/io.hpp @@ -80,26 +80,54 @@ constexpr unsigned max_stream_output_length() return 5u; } -template -inline void stream_impl(std::ostream &os, const std::vector &v) +// Helper to stream a [begin, end) range. +template +inline void stream_range(std::ostream &os, It begin, It end) { - auto len = v.size(); - if (len <= max_stream_output_length()) { - os << '['; - for (decltype(v.size()) i = 0u; i < v.size(); ++i) { - stream(os, v[i]); - if (i != v.size() - 1u) { - os << ", "; - } + // Special-case an empty range. + if (begin == end) { + os << "[]"; + return; + } + + os << '['; + + for (auto counter = 0u;; ++counter) { + if (counter == max_stream_output_length()) { + // NOTE: if we are here, it means we have more + // elements in the range to print, but we already + // printed the maximum number of elements. + // Add the ellipsis and exit. + os << "... "; + break; } - os << ']'; - } else { - os << '['; - for (decltype(v.size()) i = 0u; i < max_stream_output_length(); ++i) { - stream(os, v[i], ", "); + + // Stream the current element of the range. + stream(os, *begin); + + // NOTE: because we handled the empty range earlier, + // ++begin is always well-defined at the first iteration + // of this loop. Following iterations will happen only + // if begin != end. + if (++begin == end) { + // We printed the last element. Omit the comma, + // and exit. + break; } - os << "... ]"; + + // We have more elements to print, or perhaps the + // ellipsis. Print comma and add space. + os << ", "; } + + os << ']'; +} + +// Implementation for vector. +template +inline void stream_impl(std::ostream &os, const std::vector &v) +{ + stream_range(os, v.begin(), v.end()); } template diff --git a/include/pagmo/island.hpp b/include/pagmo/island.hpp index 28e5b6829..18ba9a07e 100644 --- a/include/pagmo/island.hpp +++ b/include/pagmo/island.hpp @@ -29,7 +29,6 @@ see https://www.gnu.org/licenses/. */ #ifndef PAGMO_ISLAND_HPP #define PAGMO_ISLAND_HPP -#include #include #include #include @@ -37,9 +36,9 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include -#include #include #include @@ -50,14 +49,18 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include #include #include #include +#include #include #include +#include #include +#include #define PAGMO_S11N_ISLAND_EXPORT_KEY(isl) \ BOOST_CLASS_EXPORT_KEY2(pagmo::detail::isl_inner, "udi " #isl) \ @@ -72,9 +75,6 @@ see https://www.gnu.org/licenses/. */ namespace pagmo { -// Fwd declaration. -class PAGMO_DLL_PUBLIC island; - /// Detect \p run_evolve() method. /** * This type trait will be \p true if \p T provides a method with @@ -110,6 +110,7 @@ namespace detail template struct disable_udi_checks : std::false_type { }; + } // namespace detail /// Detect user-defined islands (UDI). @@ -271,14 +272,33 @@ struct PAGMO_DLL_PUBLIC island_data { pop(std::make_shared(std::forward(p))) { } - // This is used only in the copy ctor of island. It's equivalent to the ctor from Algo + pop, - // the island will come from the clone() method of an isl_inner. - template - explicit island_data(std::unique_ptr &&ptr, Algo &&a, Pop &&p) - : isl_ptr(std::move(ptr)), algo(std::make_shared(std::forward(a))), - pop(std::make_shared(std::forward(p))) + // A tag to distinguish ctors with policy arguments. + struct ptag { + }; + // Constructor from algo, pop and r/s policies. + template + explicit island_data(ptag, Algo &&a, Pop &&p, RPol &&r, SPol &&s) + : algo(std::make_shared(std::forward(a))), + pop(std::make_shared(std::forward(p))), r_pol(std::forward(r)), + s_pol(std::forward(s)) { + island_factory(*algo, *pop, isl_ptr); } + // As above, but the UDI is explicitly passed by the user. + template + explicit island_data(ptag, Isl &&isl, Algo &&a, Pop &&p, RPol &&r, SPol &&s) + : isl_ptr(detail::make_unique>>(std::forward(isl))), + algo(std::make_shared(std::forward(a))), + pop(std::make_shared(std::forward(p))), r_pol(std::forward(r)), + s_pol(std::forward(s)) + { + } + // This is used only in the copy ctor of island. The island will come from the clone() + // method of an isl_inner, the algo/pop from the island's getters. The r/s_policies + // will come directly from the island's data member, as they are supposed + // to be thread-safe. + explicit island_data(std::unique_ptr &&, algorithm &&, population &&, const r_policy &, + const s_policy &); // Delete all the rest, make sure we don't implicitly rely on any of this. island_data(const island_data &) = delete; island_data(island_data &&) = delete; @@ -296,6 +316,16 @@ struct PAGMO_DLL_PUBLIC island_data { std::shared_ptr algo; std::mutex pop_mutex; std::shared_ptr pop; + // The replacement/selection policies. They are supposed to be thread-safe, + // thus no protection is needed. + // NOTE: additionally, contrary to algo/pop, we never need to copy + // r/s_pol during evolution, as all the replace/select logic + // is currently always happening within the thread of execution of the + // island. Thus, all the machinery above for safely copying out algo + // and pop is not necessary. + r_policy r_pol; + s_policy s_pol; + // The vector of futures. std::vector> futures; // This will be explicitly set only during archipelago::push_back(). // In all other situations, it will be null. @@ -327,27 +357,14 @@ enum class evolve_status { /// was generated by an asynchronous operation in the past }; -namespace detail -{ - -// NOTE: in C++11 hashing of enums might not be available. Provide our own. -struct island_status_hasher { - std::size_t operator()(evolve_status es) const noexcept - { - return std::hash{}(static_cast(es)); - } -}; - -// A map to link a human-readable description to evolve_status. -PAGMO_DLL_PUBLIC extern const std::unordered_map island_statuses; - -} // namespace detail - #if !defined(PAGMO_DOXYGEN_INVOKED) // Provide the stream operator overload for evolve_status. PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, evolve_status); +// Stream operator for pagmo::island. +PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const island &); + #endif /// Island class. @@ -356,11 +373,13 @@ PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, evolve_status); * * \verbatim embed:rst:leading-asterisk * - * In the pagmo jargon, an island is a class that encapsulates three entities: + * In the pagmo jargon, an island is a class that encapsulates the following entities: * * - a user-defined island (UDI), * - an :cpp:class:`~pagmo::algorithm`, - * - a :cpp:class:`~pagmo::population`. + * - a :cpp:class:`~pagmo::population`, + * - a replacement policy (of type :cpp:class:`~pagmo::r_policy`), + * - a selection policy (of type :cpp:class:`~pagmo::s_policy`). * * Through the UDI, the island class manages the asynchronous evolution (or optimisation) * of its :cpp:class:`~pagmo::population` via the algorithm's :cpp:func:`~pagmo::algorithm::evolve()` @@ -373,6 +392,11 @@ PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, evolve_status); * to conclude by calling the :cpp:func:`~pagmo::island::wait()` and :cpp:func:`~pagmo::island::wait_check()` * methods. The status of ongoing evolutions in the island can be queried via :cpp:func:`~pagmo::island::status()`. * + * The replacement and selection policies are used when the island is part of an :cpp:class:`~pagmo::archipelago`. + * They establish how individuals are selected and replaced from the island when migration across islands occurs within + * the :cpp:class:`~pagmo::archipelago`. If the island is not part of an :cpp:class:`~pagmo::archipelago`, + * the replacement and selection policies play no role. + * * \endverbatim * * Typically, pagmo users will employ an already-available UDI (such as pagmo::thread_island) in @@ -420,10 +444,14 @@ class PAGMO_DLL_PUBLIC island using idata_t = detail::island_data; // archi needs access to the internal of island. friend class PAGMO_DLL_PUBLIC archipelago; +#if !defined(PAGMO_DOXYGEN_INVOKED) + // Make friends with the stream operator. + friend PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const island &); +#endif // NOTE: the idea in the move members and the dtor is that // we want to wait *and* erase any future in the island, before doing // the move/destruction. Thus we use this small wrapper. - void wait_check_ignore(); + PAGMO_DLL_LOCAL void wait_check_ignore(); public: // Default constructor. @@ -446,7 +474,7 @@ class PAGMO_DLL_PUBLIC island * .. note:: * * This constructor is enabled only if ``a`` can be used to construct a - * :cpp:class:`pagmo::algorithm` and :cpp:class:`p` is an instance of :cpp:class:`pagmo::population`. + * :cpp:class:`pagmo::algorithm` and ``p`` is an instance of :cpp:class:`pagmo::population`. * * This constructor will use *a* to construct the internal algorithm, and *p* to construct * the internal population. The UDI type will be inferred from the :cpp:type:`~pagmo::thread_safety` properties @@ -461,6 +489,8 @@ class PAGMO_DLL_PUBLIC island * but island evolutions will fail if the algorithm and/or problem do not provide at least the * basic :cpp:type:`~pagmo::thread_safety` guarantee. * + * The replacement/selection policies will be default-constructed. + * * \endverbatim * * @param a the input algorithm. @@ -476,6 +506,45 @@ class PAGMO_DLL_PUBLIC island { } +private: + template + using algo_pop_pol_enabler = enable_if_t< + detail::conjunction, std::is_same>, + std::is_constructible, std::is_constructible>::value, + int>; + +public: + /// Constructor from algorithm, population and replacement/selection policies. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``a`` can be used to construct a + * :cpp:class:`pagmo::algorithm`, ``p`` is an instance of :cpp:class:`pagmo::population`, + * and ``r`` and ``s`` can be used to construct, respectively, a :cpp:class:`pagmo::r_policy` + * and a :cpp:class:`pagmo::s_policy`. + * + * This constructor is equivalent to the previous one, but it additionally allows + * to specify the replacement and selection policies for the island. + * + * \endverbatim + * + * @param a the input algorithm. + * @param p the input population. + * @param r the input replacement policy. + * @param s the input selection policy. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the construction of the replacement/selection policies. + */ + template = 0> + explicit island(Algo &&a, Pop &&p, RPol &&r, SPol &&s) + : m_ptr(detail::make_unique(idata_t::ptag{}, std::forward(a), std::forward(p), + std::forward(r), std::forward(s))) + { + } + private: // NOTE: here and elsewhere, we don't have to put the constraint that Isl is not pagmo::island, // like we do in pagmo::problem/pagmo::algorithm: pagmo::island does not satisfy the interface @@ -518,6 +587,50 @@ class PAGMO_DLL_PUBLIC island { } +private: + template + using isl_algo_pop_pol_enabler = enable_if_t< + detail::conjunction>, std::is_constructible, + std::is_same>, std::is_constructible, + std::is_constructible>::value, + int>; + +public: + /// Constructor from UDI, algorithm, population and replacement/selection policies. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if: + * + * - ``Isl`` satisfies :cpp:class:`pagmo::is_udi`, + * - ``a`` can be used to construct a :cpp:class:`pagmo::algorithm`, + * - ``p`` is an instance of :cpp:class:`pagmo::population`, + * - ``r`` and ``s`` can be used to construct, respectively, a + * :cpp:class:`pagmo::r_policy` and a :cpp:class:`pagmo::s_policy`. + * + * \endverbatim + * + * This constructor is equivalent to the previous one, but it additionally allows + * to specify the replacement and selection policies for the island. + * + * @param isl the input UDI. + * @param a the input algorithm. + * @param p the input population. + * @param r the input replacement policy. + * @param s the input selection policy. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the construction of the replacement/selection policies. + */ + template = 0> + explicit island(Isl &&isl, Algo &&a, Pop &&p, RPol &&r, SPol &&s) + : m_ptr(detail::make_unique(idata_t::ptag{}, std::forward(isl), std::forward(a), + std::forward(p), std::forward(r), std::forward(s))) + { + } + private: template using algo_prob_enabler = enable_if_t< @@ -553,6 +666,48 @@ class PAGMO_DLL_PUBLIC island { } +private: + template + using algo_prob_pol_enabler = enable_if_t< + detail::conjunction, std::is_constructible, + std::is_constructible, std::is_constructible>::value, + int>; + +public: + /// Constructor from algorithm, problem, size, replacement/selections policies and seed. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``a`` can be used to construct a + * :cpp:class:`pagmo::algorithm`, ``p`` can be used to construct a + * :cpp:class:`pagmo::problem`, and ``r`` and ``s`` can be used to construct, respectively, a + * :cpp:class:`pagmo::r_policy` and a :cpp:class:`pagmo::s_policy`. + * + * \endverbatim + * + * This constructor is equivalent to the previous one, but it additionally allows + * to specify the replacement and selection policies for the island. + * + * @param a the input algorithm. + * @param p the input problem. + * @param size the population size. + * @param r the input replacement policy. + * @param s the input selection policy. + * @param seed the population seed. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the construction of the replacement/selection policies. + */ + template = 0> + explicit island(Algo &&a, Prob &&p, population::size_type size, RPol &&r, SPol &&s, + unsigned seed = pagmo::random_device::next()) + : island(std::forward(a), population(std::forward(p), size, seed), std::forward(r), + std::forward(s)) + { + } + private: template using algo_prob_bfe_enabler = enable_if_t< @@ -564,7 +719,15 @@ class PAGMO_DLL_PUBLIC island /// Constructor from algorithm, problem, batch fitness evaluator, size and seed. /** * \verbatim embed:rst:leading-asterisk - * This constructor is equivalent to the previous one, the only difference being that + * .. note:: + * + * This constructor is enabled only if ``a`` can be used to construct a + * :cpp:class:`pagmo::algorithm`, ``p`` can be used to construct a + * :cpp:class:`pagmo::problem`, and ``b`` can be used to construct a + * :cpp:class:`pagmo::bfe`. + * + * This constructor is equivalent to the constructor from algorithm, problem, size and seed, + * the only difference being that * the population's individuals will be initialised using the input :cpp:class:`~pagmo::bfe` * or UDBFE *b*. * \endverbatim @@ -585,6 +748,50 @@ class PAGMO_DLL_PUBLIC island { } +private: + template + using algo_prob_bfe_pol_enabler = enable_if_t< + detail::conjunction, std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_constructible>::value, + int>; + +public: + /// Constructor from algorithm, problem, batch fitness evaluator, size, replacement/selection policies and seed. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``a`` can be used to construct a + * :cpp:class:`pagmo::algorithm`, ``p`` can be used to construct a + * :cpp:class:`pagmo::problem`, ``b`` can be used to construct a + * :cpp:class:`pagmo::bfe`, and ``r`` and ``s`` can be used to construct, respectively, a + * :cpp:class:`pagmo::r_policy` and a :cpp:class:`pagmo::s_policy`. + * + * This constructor is equivalent to the previous one, but it additionally allows + * to specify the replacement and selection policies for the island. + * \endverbatim + * + * @param a the input algorithm. + * @param p the input problem. + * @param b the input (user-defined) batch fitness evaluator. + * @param size the population size. + * @param r the input replacement policy. + * @param s the input selection policy. + * @param seed the population seed. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the construction of the replacement/selection policies. + */ + template = 0> + explicit island(Algo &&a, Prob &&p, Bfe &&b, population::size_type size, RPol &&r, SPol &&s, + unsigned seed = pagmo::random_device::next()) + : island(std::forward(a), population(std::forward(p), std::forward(b), size, seed), + std::forward(r), std::forward(s)) + { + } + private: template using isl_algo_prob_enabler @@ -625,6 +832,50 @@ class PAGMO_DLL_PUBLIC island { } +private: + template + using isl_algo_prob_pol_enabler = enable_if_t< + detail::conjunction>, std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_constructible>::value, + int>; + +public: + /// Constructor from UDI, algorithm, problem, size, replacement/selection policies and seed. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``Isl`` satisfies :cpp:class:`pagmo::is_udi`, ``a`` can be used to + * construct a :cpp:class:`pagmo::algorithm`, ``p`` can be used to construct a + * :cpp:class:`pagmo::problem`, and ``r`` and ``s`` can be used to construct, respectively, a + * :cpp:class:`pagmo::r_policy` and a :cpp:class:`pagmo::s_policy`. + * + * \endverbatim + * + * This constructor is equivalent to the previous one, but it additionally allows + * to specify the replacement and selection policies for the island. + * + * @param isl the input UDI. + * @param a the input algorithm. + * @param p the input problem. + * @param size the population size. + * @param r the input replacement policy. + * @param s the input selection policy. + * @param seed the population seed. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the construction of the replacement/selection policies. + */ + template = 0> + explicit island(Isl &&isl, Algo &&a, Prob &&p, population::size_type size, RPol &&r, SPol &&s, + unsigned seed = pagmo::random_device::next()) + : island(std::forward(isl), std::forward(a), population(std::forward(p), size, seed), + std::forward(r), std::forward(s)) + { + } + private: template using isl_algo_prob_bfe_enabler = enable_if_t< @@ -636,7 +887,14 @@ class PAGMO_DLL_PUBLIC island /// Constructor from UDI, algorithm, problem, batch fitness evaluator, size and seed. /** * \verbatim embed:rst:leading-asterisk - * This constructor is equivalent to the previous one, the only difference being that + * .. note:: + * + * This constructor is enabled only if ``Isl`` satisfies :cpp:class:`pagmo::is_udi`, ``a`` can be used to + * construct a :cpp:class:`pagmo::algorithm`, ``p`` can be used to construct a + * :cpp:class:`pagmo::problem`, and ``b`` can be used to construct a :cpp:class:`pagmo::bfe`. + * + * This constructor is equivalent to the constructor from UDI, algorithm, problem, size and seed, + * the only difference being that * the population's individuals will be initialised using the input :cpp:class:`~pagmo::bfe` * or UDBFE *b*. * \endverbatim @@ -659,6 +917,54 @@ class PAGMO_DLL_PUBLIC island population(std::forward(p), std::forward(b), size, seed)) { } + +private: + template + using isl_algo_prob_bfe_pol_enabler = enable_if_t< + detail::conjunction>, std::is_constructible, + std::is_constructible, std::is_constructible, + std::is_constructible, std::is_constructible>::value, + int>; + +public: + /// Constructor from UDI, algorithm, problem, batch fitness evaluator, size, replacement/selection policies and + /// seed. + /** + * \verbatim embed:rst:leading-asterisk + * .. note:: + * + * This constructor is enabled only if ``Isl`` satisfies :cpp:class:`pagmo::is_udi`, ``a`` can be used to + * construct a :cpp:class:`pagmo::algorithm`, ``p`` can be used to construct a + * :cpp:class:`pagmo::problem`, ``b`` can be used to construct a :cpp:class:`pagmo::bfe`, + * and ``r`` and ``s`` can be used to construct, respectively, a + * :cpp:class:`pagmo::r_policy` and a :cpp:class:`pagmo::s_policy`. + * + * This constructor is equivalent to the previous one, the only difference being that + * the population's individuals will be initialised using the input :cpp:class:`~pagmo::bfe` + * or UDBFE *b*. + * \endverbatim + * + * @param isl the input UDI. + * @param a the input algorithm. + * @param p the input problem. + * @param b the input (user-defined) batch fitness evaluator. + * @param size the population size. + * @param r the input replacement policy. + * @param s the input selection policy. + * @param seed the population seed. + * + * @throws unspecified any exception thrown by the previous constructor or by + * the construction of the replacement/selection policies. + */ + template = 0> + explicit island(Isl &&isl, Algo &&a, Prob &&p, Bfe &&b, population::size_type size, RPol &&r, SPol &&s, + unsigned seed = pagmo::random_device::next()) + : island(std::forward(isl), std::forward(a), + population(std::forward(p), std::forward(b), size, seed), std::forward(r), + std::forward(s)) + { + } // Destructor. ~island(); // Move assignment. @@ -740,18 +1046,36 @@ class PAGMO_DLL_PUBLIC island * invocation. Exceptions raised inside the * tasks are stored within the island object, and can be re-raised by calling wait_check(). * + * If the island is part of a pagmo::archipelago, then migration of individuals to/from other + * islands might occur. The migration algorithm consists of the following steps: + * + * - before invoking run_evolve() on the UDI, the island will ask the + * archipelago if there are candidate incoming individuals from other islands. + * If so, the replacement policy is invoked and + * the current population of the island is updated with the migrants; + * - run_evolve() is then invoked and the current population is evolved; + * - after run_evolve() has concluded, individuals are selected in the + * evolved population and copied into the migration database of the archipelago + * for future migrations. + * * It is possible to call this method multiple times to enqueue multiple evolution tasks, which * will be consumed in a FIFO (first-in first-out) fashion. The user may call island::wait() or island::wait_check() * to block until all tasks have been completed, and to fetch exceptions raised during the execution of the tasks. * island::status() can be used to query the status of the asynchronous operations in the island. * * @param n the number of times the run_evolve() method of the UDI will be called - * within the evolution task. + * within the evolution task. This corresponds also to the number of times migration can + * happen, if the island belongs to an archipelago. * + * @throws std::out_of_range if the island is part of an archipelago and during migration + * an invalid island index is used (this can happen if the archipelago's topology is + * malformed). * @throws unspecified any exception thrown by: * - threading primitives, * - memory allocation errors, - * - the public interface of \p std::future. + * - the public interface of \p std::future, + * - the public interface of pagmo::archipelago, + * - the public interface of the replacement/selection policies. */ void evolve(unsigned n = 1); // Block until evolution ends and re-raise the first stored exception. @@ -760,6 +1084,7 @@ class PAGMO_DLL_PUBLIC island void wait(); // Status of the island. evolve_status status() const; + // Get the algorithm. algorithm get_algorithm() const; // Set the algorithm. @@ -768,10 +1093,15 @@ class PAGMO_DLL_PUBLIC island population get_population() const; // Set the population. void set_population(const population &); + // Get the replacement policy. + r_policy get_r_policy() const; + // Get the selection policy. + s_policy get_s_policy() const; // Island's name. std::string get_name() const; // Island's extra info. std::string get_extra_info() const; + // Check if the island is valid. bool is_valid() const; /// Save to archive. @@ -789,7 +1119,7 @@ class PAGMO_DLL_PUBLIC island template void save(Archive &ar, unsigned) const { - detail::to_archive(ar, m_ptr->isl_ptr, get_algorithm(), get_population()); + detail::to_archive(ar, m_ptr->isl_ptr, get_algorithm(), get_population(), m_ptr->r_pol, m_ptr->s_pol); } /// Load from archive. /** @@ -808,18 +1138,28 @@ class PAGMO_DLL_PUBLIC island // Deserialize into tmp island, and then move assign it. island tmp_island; // NOTE: no need to lock access to these, as there is no evolution going on in tmp_island. - detail::from_archive(ar, tmp_island.m_ptr->isl_ptr, *tmp_island.m_ptr->algo, *tmp_island.m_ptr->pop); + detail::from_archive(ar, tmp_island.m_ptr->isl_ptr, *tmp_island.m_ptr->algo, *tmp_island.m_ptr->pop, + tmp_island.m_ptr->r_pol, tmp_island.m_ptr->s_pol); *this = std::move(tmp_island); } BOOST_SERIALIZATION_SPLIT_MEMBER() +private: + // Data used in the migration machinery: + // - the island's population (represented via individuals_group_t), + // - the problem's nx, nix, nobj, nec, nic, and tolerances. + using migration_data_t + = std::tuple; + // Fetch the migration data. + PAGMO_DLL_LOCAL migration_data_t get_migration_data() const; + // Set all the individuals in the population. + PAGMO_DLL_LOCAL void set_individuals(const individuals_group_t &); + private: std::unique_ptr m_ptr; }; -// Stream operator for pagmo::island. -PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const island &); - } // namespace pagmo // Disable tracking for the serialisation of island. diff --git a/include/pagmo/pagmo.hpp b/include/pagmo/pagmo.hpp index 8cf5abfd5..eb353c60d 100644 --- a/include/pagmo/pagmo.hpp +++ b/include/pagmo/pagmo.hpp @@ -40,9 +40,12 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include +#include #include +#include #include #include @@ -139,4 +142,16 @@ see https://www.gnu.org/licenses/. */ #include #include +// Replacement policies. +#include + +// Selection policies. +#include + +// Topologies. +#include +#include +#include +#include + #endif diff --git a/include/pagmo/population.hpp b/include/pagmo/population.hpp index 7df15f64c..e3ca48f9b 100644 --- a/include/pagmo/population.hpp +++ b/include/pagmo/population.hpp @@ -37,6 +37,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include #include @@ -76,6 +77,11 @@ namespace pagmo */ class PAGMO_DLL_PUBLIC population { + // Make friends with island for direct + // access to the population's members during + // evolution. + friend class PAGMO_DLL_PUBLIC island; + public: /// The size type of the population. typedef pop_size_t size_type; diff --git a/include/pagmo/problem.hpp b/include/pagmo/problem.hpp index 3903711ca..0c4b5c14b 100644 --- a/include/pagmo/problem.hpp +++ b/include/pagmo/problem.hpp @@ -1044,7 +1044,7 @@ PAGMO_DLL_PUBLIC vector_double prob_invoke_mem_batch_fitness(const problem &, co */ class PAGMO_DLL_PUBLIC problem { - // Make friend with the streaming operator, which needs access + // Make friends with the streaming operator, which needs access // to the internals. friend PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const problem &); diff --git a/include/pagmo/r_policies/fair_replace.hpp b/include/pagmo/r_policies/fair_replace.hpp new file mode 100644 index 000000000..e8f4f6750 --- /dev/null +++ b/include/pagmo/r_policies/fair_replace.hpp @@ -0,0 +1,78 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_R_POLICIES_FAIR_REPLACE_HPP +#define PAGMO_R_POLICIES_FAIR_REPLACE_HPP + +#include +#include + +#include +#include +#include +#include +#include + +namespace pagmo +{ + +class PAGMO_DLL_PUBLIC fair_replace : public detail::base_sr_policy +{ +public: + // Default ctor. + fair_replace(); + + // Constructor from migration rate. + template , std::is_floating_point>::value, int> = 0> + explicit fair_replace(T x) : detail::base_sr_policy(x) + { + } + + // Replacement. + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + + std::string get_name() const + { + return "Fair replace"; + } + std::string get_extra_info() const; + + // Serialization support. + template + void serialize(Archive &, unsigned); +}; + +} // namespace pagmo + +PAGMO_S11N_R_POLICY_EXPORT_KEY(pagmo::fair_replace) + +#endif diff --git a/include/pagmo/r_policy.hpp b/include/pagmo/r_policy.hpp new file mode 100644 index 000000000..5625ae701 --- /dev/null +++ b/include/pagmo/r_policy.hpp @@ -0,0 +1,338 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_R_POLICY_HPP +#define PAGMO_R_POLICY_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define PAGMO_S11N_R_POLICY_EXPORT_KEY(r) \ + BOOST_CLASS_EXPORT_KEY2(pagmo::detail::r_pol_inner, "udrp " #r) \ + BOOST_CLASS_TRACKING(pagmo::detail::r_pol_inner, boost::serialization::track_never) + +#define PAGMO_S11N_R_POLICY_IMPLEMENT(r) BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::detail::r_pol_inner) + +#define PAGMO_S11N_R_POLICY_EXPORT(r) \ + PAGMO_S11N_R_POLICY_EXPORT_KEY(r) \ + PAGMO_S11N_R_POLICY_IMPLEMENT(r) + +namespace pagmo +{ + +// Check if T has a replace() member function conforming to the UDRP requirements. +template +class has_replace +{ + template + using replace_t = decltype(std::declval().replace( + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval(), std::declval())); + static const bool implementation_defined = std::is_same, individuals_group_t>::value; + +public: + static const bool value = implementation_defined; +}; + +template +const bool has_replace::value; + +namespace detail +{ + +// Specialise this to true in order to disable all the UDRP checks and mark a type +// as a UDRP regardless of the features provided by it. +// NOTE: this is needed when implementing the machinery for Python r_policies. +// NOTE: leave this as an implementation detail for now. +template +struct disable_udrp_checks : std::false_type { +}; + +} // namespace detail + +// Detect UDRPs +template +class is_udrp +{ + static const bool implementation_defined + = detail::disjunction>, std::is_default_constructible, + std::is_copy_constructible, std::is_move_constructible, + std::is_destructible, has_replace>, + detail::disable_udrp_checks>::value; + +public: + // Value of the type trait. + static const bool value = implementation_defined; +}; + +template +const bool is_udrp::value; + +namespace detail +{ + +struct PAGMO_DLL_PUBLIC_INLINE_CLASS r_pol_inner_base { + virtual ~r_pol_inner_base() {} + virtual std::unique_ptr clone() const = 0; + virtual individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const = 0; + virtual std::string get_name() const = 0; + virtual std::string get_extra_info() const = 0; + template + void serialize(Archive &, unsigned) + { + } +}; + +template +struct PAGMO_DLL_PUBLIC_INLINE_CLASS r_pol_inner final : r_pol_inner_base { + // We just need the def ctor, delete everything else. + r_pol_inner() = default; + r_pol_inner(const r_pol_inner &) = delete; + r_pol_inner(r_pol_inner &&) = delete; + r_pol_inner &operator=(const r_pol_inner &) = delete; + r_pol_inner &operator=(r_pol_inner &&) = delete; + // Constructors from T. + explicit r_pol_inner(const T &x) : m_value(x) {} + explicit r_pol_inner(T &&x) : m_value(std::move(x)) {} + // The clone method, used in the copy constructor of r_policy. + virtual std::unique_ptr clone() const override final + { + return detail::make_unique(m_value); + } + // The mandatory replace() method. + virtual individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol, const individuals_group_t &mig) const override final + { + return m_value.replace(inds, nx, nix, nobj, nec, nic, tol, mig); + } + // Optional methods. + virtual std::string get_name() const override final + { + return get_name_impl(m_value); + } + virtual std::string get_extra_info() const override final + { + return get_extra_info_impl(m_value); + } + template ::value, int> = 0> + static std::string get_name_impl(const U &value) + { + return value.get_name(); + } + template ::value, int> = 0> + static std::string get_name_impl(const U &) + { + return typeid(U).name(); + } + template ::value, int> = 0> + static std::string get_extra_info_impl(const U &value) + { + return value.get_extra_info(); + } + template ::value, int> = 0> + static std::string get_extra_info_impl(const U &) + { + return ""; + } + // Serialization + template + void serialize(Archive &ar, unsigned) + { + detail::archive(ar, boost::serialization::base_object(*this), m_value); + } + T m_value; +}; + +} // namespace detail + +} // namespace pagmo + +namespace boost +{ + +template +struct is_virtual_base_of> : false_type { +}; + +} // namespace boost + +namespace pagmo +{ + +// Replacement policy. +class PAGMO_DLL_PUBLIC r_policy +{ + // Enable the generic ctor only if T is not an r_policy (after removing + // const/reference qualifiers), and if T is a udrp. + template + using generic_ctor_enabler = enable_if_t< + detail::conjunction>>, is_udrp>>::value, int>; + // Implementation of the generic ctor. + void generic_ctor_impl(); + +public: + // Default constructor. + r_policy(); + // Constructor from a UDRP. + template = 0> + explicit r_policy(T &&x) : m_ptr(detail::make_unique>>(std::forward(x))) + { + generic_ctor_impl(); + } + // Copy constructor. + r_policy(const r_policy &); + // Move constructor. + r_policy(r_policy &&) noexcept; + // Move assignment operator + r_policy &operator=(r_policy &&) noexcept; + // Copy assignment operator + r_policy &operator=(const r_policy &); + // Assignment from a UDRP. + template = 0> + r_policy &operator=(T &&x) + { + return (*this) = r_policy(std::forward(x)); + } + + // Extraction and related. + template + const T *extract() const noexcept + { + auto p = dynamic_cast *>(ptr()); + return p == nullptr ? nullptr : &(p->m_value); + } + template + T *extract() noexcept + { + auto p = dynamic_cast *>(ptr()); + return p == nullptr ? nullptr : &(p->m_value); + } + template + bool is() const noexcept + { + return extract() != nullptr; + } + + // Replace. + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + + // Name. + std::string get_name() const + { + return m_name; + } + // Extra info. + std::string get_extra_info() const; + + // Check if the r_policy is valid. + bool is_valid() const; + + // Serialisation support. + template + void save(Archive &ar, unsigned) const + { + detail::to_archive(ar, m_ptr, m_name); + } + template + void load(Archive &ar, unsigned) + { + // Deserialize in a separate object and move it in later, for exception safety. + r_policy tmp_r_pol; + detail::from_archive(ar, tmp_r_pol.m_ptr, tmp_r_pol.m_name); + *this = std::move(tmp_r_pol); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + +private: + // Just two small helpers to make sure that whenever we require + // access to the pointer it actually points to something. + detail::r_pol_inner_base const *ptr() const + { + assert(m_ptr.get() != nullptr); + return m_ptr.get(); + } + detail::r_pol_inner_base *ptr() + { + assert(m_ptr.get() != nullptr); + return m_ptr.get(); + } + // Helper to check the inputs and outputs of the replace() function. + PAGMO_DLL_LOCAL void verify_replace_input(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + PAGMO_DLL_LOCAL void verify_replace_output(const individuals_group_t &, vector_double::size_type, + vector_double::size_type) const; + +private: + // Pointer to the inner base r_pol. + std::unique_ptr m_ptr; + // Various properties determined at construction time + // from the udrp. These will be constant for the lifetime + // of r_policy, but we cannot mark them as such because we want to be + // able to assign and deserialise r_policies. + std::string m_name; +}; + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Stream operator. +PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const r_policy &); + +#endif + +} // namespace pagmo + +// Disable tracking for the serialisation of r_policy. +BOOST_CLASS_TRACKING(pagmo::r_policy, boost::serialization::track_never) + +#endif diff --git a/include/pagmo/s_policies/select_best.hpp b/include/pagmo/s_policies/select_best.hpp new file mode 100644 index 000000000..84e85a92c --- /dev/null +++ b/include/pagmo/s_policies/select_best.hpp @@ -0,0 +1,74 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_S_POLICIES_SELECT_BEST_HPP +#define PAGMO_S_POLICIES_SELECT_BEST_HPP + +#include +#include + +#include +#include +#include +#include +#include + +namespace pagmo +{ + +class PAGMO_DLL_PUBLIC select_best : public detail::base_sr_policy +{ +public: + select_best(); + template , std::is_floating_point>::value, int> = 0> + explicit select_best(T x) : detail::base_sr_policy(x) + { + } + + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + + std::string get_name() const + { + return "Select best"; + } + std::string get_extra_info() const; + + // Serialization support. + template + void serialize(Archive &, unsigned); +}; + +} // namespace pagmo + +PAGMO_S11N_S_POLICY_EXPORT_KEY(pagmo::select_best) + +#endif diff --git a/include/pagmo/s_policy.hpp b/include/pagmo/s_policy.hpp new file mode 100644 index 000000000..dbacb7d10 --- /dev/null +++ b/include/pagmo/s_policy.hpp @@ -0,0 +1,338 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_S_POLICY_HPP +#define PAGMO_S_POLICY_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define PAGMO_S11N_S_POLICY_EXPORT_KEY(s) \ + BOOST_CLASS_EXPORT_KEY2(pagmo::detail::s_pol_inner, "udsp " #s) \ + BOOST_CLASS_TRACKING(pagmo::detail::s_pol_inner, boost::serialization::track_never) + +#define PAGMO_S11N_S_POLICY_IMPLEMENT(s) BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::detail::s_pol_inner) + +#define PAGMO_S11N_S_POLICY_EXPORT(s) \ + PAGMO_S11N_S_POLICY_EXPORT_KEY(s) \ + PAGMO_S11N_S_POLICY_IMPLEMENT(s) + +namespace pagmo +{ + +// Check if T has a select() member function conforming to the UDSP requirements. +template +class has_select +{ + template + using select_t = decltype(std::declval().select( + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval())); + static const bool implementation_defined = std::is_same, individuals_group_t>::value; + +public: + static const bool value = implementation_defined; +}; + +template +const bool has_select::value; + +namespace detail +{ + +// Specialise this to true in order to disable all the UDSP checks and mark a type +// as a UDSP regardless of the features provided by it. +// NOTE: this is needed when implementing the machinery for Python s_policies. +// NOTE: leave this as an implementation detail for now. +template +struct disable_udsp_checks : std::false_type { +}; + +} // namespace detail + +// Detect UDSPs +template +class is_udsp +{ + static const bool implementation_defined + = detail::disjunction>, std::is_default_constructible, + std::is_copy_constructible, std::is_move_constructible, + std::is_destructible, has_select>, + detail::disable_udsp_checks>::value; + +public: + // Value of the type trait. + static const bool value = implementation_defined; +}; + +template +const bool is_udsp::value; + +namespace detail +{ + +struct PAGMO_DLL_PUBLIC_INLINE_CLASS s_pol_inner_base { + virtual ~s_pol_inner_base() {} + virtual std::unique_ptr clone() const = 0; + virtual individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const = 0; + virtual std::string get_name() const = 0; + virtual std::string get_extra_info() const = 0; + template + void serialize(Archive &, unsigned) + { + } +}; + +template +struct PAGMO_DLL_PUBLIC_INLINE_CLASS s_pol_inner final : s_pol_inner_base { + // We just need the def ctor, delete everything else. + s_pol_inner() = default; + s_pol_inner(const s_pol_inner &) = delete; + s_pol_inner(s_pol_inner &&) = delete; + s_pol_inner &operator=(const s_pol_inner &) = delete; + s_pol_inner &operator=(s_pol_inner &&) = delete; + // Constructors from T. + explicit s_pol_inner(const T &x) : m_value(x) {} + explicit s_pol_inner(T &&x) : m_value(std::move(x)) {} + // The clone method, used in the copy constructor of s_policy. + virtual std::unique_ptr clone() const override final + { + return detail::make_unique(m_value); + } + // The mandatory select() method. + virtual individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol) const override final + { + return m_value.select(inds, nx, nix, nobj, nec, nic, tol); + } + // Optional methods. + virtual std::string get_name() const override final + { + return get_name_impl(m_value); + } + virtual std::string get_extra_info() const override final + { + return get_extra_info_impl(m_value); + } + template ::value, int> = 0> + static std::string get_name_impl(const U &value) + { + return value.get_name(); + } + template ::value, int> = 0> + static std::string get_name_impl(const U &) + { + return typeid(U).name(); + } + template ::value, int> = 0> + static std::string get_extra_info_impl(const U &value) + { + return value.get_extra_info(); + } + template ::value, int> = 0> + static std::string get_extra_info_impl(const U &) + { + return ""; + } + // Serialization + template + void serialize(Archive &ar, unsigned) + { + detail::archive(ar, boost::serialization::base_object(*this), m_value); + } + T m_value; +}; + +} // namespace detail + +} // namespace pagmo + +namespace boost +{ + +template +struct is_virtual_base_of> : false_type { +}; + +} // namespace boost + +namespace pagmo +{ + +// Selection policy. +class PAGMO_DLL_PUBLIC s_policy +{ + // Enable the generic ctor only if T is not an s_policy (after removing + // const/reference qualifiers), and if T is a udsp. + template + using generic_ctor_enabler = enable_if_t< + detail::conjunction>>, is_udsp>>::value, int>; + // Implementation of the generic ctor. + void generic_ctor_impl(); + +public: + // Default constructor. + s_policy(); + // Constructor from a UDSP. + template = 0> + explicit s_policy(T &&x) : m_ptr(detail::make_unique>>(std::forward(x))) + { + generic_ctor_impl(); + } + // Copy constructor. + s_policy(const s_policy &); + // Move constructor. + s_policy(s_policy &&) noexcept; + // Move assignment operator + s_policy &operator=(s_policy &&) noexcept; + // Copy assignment operator + s_policy &operator=(const s_policy &); + // Assignment from a UDSP. + template = 0> + s_policy &operator=(T &&x) + { + return (*this) = s_policy(std::forward(x)); + } + + // Extraction and related. + template + const T *extract() const noexcept + { + auto p = dynamic_cast *>(ptr()); + return p == nullptr ? nullptr : &(p->m_value); + } + template + T *extract() noexcept + { + auto p = dynamic_cast *>(ptr()); + return p == nullptr ? nullptr : &(p->m_value); + } + template + bool is() const noexcept + { + return extract() != nullptr; + } + + // Select. + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + + // Name. + std::string get_name() const + { + return m_name; + } + // Extra info. + std::string get_extra_info() const; + + // Check if the s_policy is valid. + bool is_valid() const; + + // Serialisation support. + template + void save(Archive &ar, unsigned) const + { + detail::to_archive(ar, m_ptr, m_name); + } + template + void load(Archive &ar, unsigned) + { + // Deserialize in a separate object and move it in later, for exception safety. + s_policy tmp_s_pol; + detail::from_archive(ar, tmp_s_pol.m_ptr, tmp_s_pol.m_name); + *this = std::move(tmp_s_pol); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + +private: + // Just two small helpers to make sure that whenever we require + // access to the pointer it actually points to something. + detail::s_pol_inner_base const *ptr() const + { + assert(m_ptr.get() != nullptr); + return m_ptr.get(); + } + detail::s_pol_inner_base *ptr() + { + assert(m_ptr.get() != nullptr); + return m_ptr.get(); + } + // Helper to check the inputs and outputs of the select() function. + PAGMO_DLL_LOCAL void verify_select_input(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + PAGMO_DLL_LOCAL void verify_select_output(const individuals_group_t &, vector_double::size_type, + vector_double::size_type) const; + +private: + // Pointer to the inner base s_pol. + std::unique_ptr m_ptr; + // Various properties determined at construction time + // from the udsp. These will be constant for the lifetime + // of s_policy, but we cannot mark them as such because we want to be + // able to assign and deserialise s_policies. + std::string m_name; +}; + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Stream operator. +PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const s_policy &); + +#endif + +} // namespace pagmo + +// Disable tracking for the serialisation of s_policy. +BOOST_CLASS_TRACKING(pagmo::s_policy, boost::serialization::track_never) + +#endif diff --git a/include/pagmo/topologies/base_bgl_topology.hpp b/include/pagmo/topologies/base_bgl_topology.hpp new file mode 100644 index 000000000..952b7c493 --- /dev/null +++ b/include/pagmo/topologies/base_bgl_topology.hpp @@ -0,0 +1,153 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_TOPOLOGIES_BASE_BGL_TOPOLOGY_HPP +#define PAGMO_TOPOLOGIES_BASE_BGL_TOPOLOGY_HPP + +#include +#include +#include +#include +#include + +#include + +#if defined(_MSC_VER) + +// Disable a warning from MSVC in the graph serialization code. +#pragma warning(push) +#pragma warning(disable : 4267) + +#endif + +#include + +#if defined(_MSC_VER) + +#pragma warning(pop) + +#endif + +#include +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +// NOTE: the definition of the graph type is taken from pagmo 1. We might +// want to consider alternative storage classes down the line, as the complexity +// of some graph operations is not that great when using vecs and lists. +using bgl_topology_graph_t + = boost::adjacency_list; + +} // namespace detail + +// Helper for the implementation of topologies +// based on the Boost Graph library. +class PAGMO_DLL_PUBLIC base_bgl_topology +{ + using graph_t = detail::bgl_topology_graph_t; + + // NOTE: all these methods do *not* lock the mutex, + // hence they are marked as "unsafe". These should + // be invoked only if the mutex is already being + // held by the calling thread. + // + // Small helper function that checks that the input vertices are in the graph. + // It will throw otherwise. + PAGMO_DLL_LOCAL void unsafe_check_vertex_indices() const; + template + PAGMO_DLL_LOCAL void unsafe_check_vertex_indices(std::size_t, Args...) const; + // Helper to detect adjacent vertices. + PAGMO_DLL_LOCAL bool unsafe_are_adjacent(std::size_t, std::size_t) const; + + // A few helpers to set/get the integral graph + // object. These will lock the mutex, so they + // are safe for general use. + graph_t get_graph() const; + PAGMO_DLL_LOCAL graph_t move_graph(); + PAGMO_DLL_LOCAL void set_graph(graph_t &&); + +public: + base_bgl_topology() = default; + base_bgl_topology(const base_bgl_topology &); + base_bgl_topology(base_bgl_topology &&) noexcept; + base_bgl_topology &operator=(const base_bgl_topology &); + base_bgl_topology &operator=(base_bgl_topology &&) noexcept; + + std::size_t num_vertices() const; + bool are_adjacent(std::size_t, std::size_t) const; + std::pair, vector_double> get_connections(std::size_t) const; + + void add_vertex(); + void add_edge(std::size_t, std::size_t, double = 1.); + void remove_edge(std::size_t, std::size_t); + void set_weight(std::size_t, std::size_t, double); + void set_all_weights(double); + + std::string get_extra_info() const; + + template + void save(Archive &ar, unsigned) const + { + detail::to_archive(ar, get_graph()); + } + template + void load(Archive &ar, unsigned) + { + base_bgl_topology tmp; + // NOTE: no need to protect the + // access to the graph of the + // newly-constructed tmp topology. + detail::from_archive(ar, tmp.m_graph); + *this = std::move(tmp); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + +private: + mutable std::mutex m_mutex; + graph_t m_graph; +}; + +} // namespace pagmo + +// Disable tracking for the serialisation of base_bgl_topology. +BOOST_CLASS_TRACKING(pagmo::base_bgl_topology, boost::serialization::track_never) + +#endif diff --git a/include/pagmo/topologies/fully_connected.hpp b/include/pagmo/topologies/fully_connected.hpp new file mode 100644 index 000000000..21bb1c5e9 --- /dev/null +++ b/include/pagmo/topologies/fully_connected.hpp @@ -0,0 +1,80 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_TOPOLOGIES_FULLY_CONNECTED_HPP +#define PAGMO_TOPOLOGIES_FULLY_CONNECTED_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace pagmo +{ + +// Fully connected topology. +class PAGMO_DLL_PUBLIC fully_connected +{ +public: + fully_connected(); + explicit fully_connected(double); + explicit fully_connected(std::size_t, double); + fully_connected(const fully_connected &); + fully_connected(fully_connected &&) noexcept; + + void push_back(); + std::pair, vector_double> get_connections(std::size_t) const; + + std::string get_name() const; + std::string get_extra_info() const; + + double get_weight() const; + std::size_t num_vertices() const; + + template + void save(Archive &, unsigned) const; + template + void load(Archive &, unsigned); + BOOST_SERIALIZATION_SPLIT_MEMBER() + +private: + double m_weight; + std::atomic m_num_vertices; +}; + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_EXPORT_KEY(pagmo::fully_connected) + +#endif diff --git a/include/pagmo/topologies/ring.hpp b/include/pagmo/topologies/ring.hpp new file mode 100644 index 000000000..42c6e6bde --- /dev/null +++ b/include/pagmo/topologies/ring.hpp @@ -0,0 +1,66 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_TOPOLOGIES_RING_HPP +#define PAGMO_TOPOLOGIES_RING_HPP + +#include +#include + +#include +#include +#include + +namespace pagmo +{ + +// Ring topology. +class PAGMO_DLL_PUBLIC ring : public base_bgl_topology +{ +public: + ring(); + explicit ring(double); + explicit ring(std::size_t, double); + + void push_back(); + + std::string get_name() const; + double get_weight() const; + + template + void serialize(Archive &, unsigned); + +private: + double m_weight; +}; + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_EXPORT_KEY(pagmo::ring) + +#endif diff --git a/include/pagmo/topologies/unconnected.hpp b/include/pagmo/topologies/unconnected.hpp new file mode 100644 index 000000000..ebf691bf1 --- /dev/null +++ b/include/pagmo/topologies/unconnected.hpp @@ -0,0 +1,64 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_TOPOLOGIES_UNCONNECTED_HPP +#define PAGMO_TOPOLOGIES_UNCONNECTED_HPP + +#include +#include +#include +#include + +#include +#include +#include + +namespace pagmo +{ + +// Unconnected topology. +struct PAGMO_DLL_PUBLIC unconnected { + // Get the connections. + std::pair, vector_double> get_connections(std::size_t) const; + // Add the next vertex (no-op). + void push_back() {} + // Name. + std::string get_name() const + { + return "Unconnected"; + } + // Serialization. + template + void serialize(Archive &, unsigned); +}; + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_EXPORT_KEY(pagmo::unconnected) + +#endif diff --git a/include/pagmo/topology.hpp b/include/pagmo/topology.hpp new file mode 100644 index 000000000..2d65cd1c6 --- /dev/null +++ b/include/pagmo/topology.hpp @@ -0,0 +1,358 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PAGMO_TOPOLOGY_HPP +#define PAGMO_TOPOLOGY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define PAGMO_S11N_TOPOLOGY_EXPORT_KEY(topo) \ + BOOST_CLASS_EXPORT_KEY2(pagmo::detail::topo_inner, "udt " #topo) \ + BOOST_CLASS_TRACKING(pagmo::detail::topo_inner, boost::serialization::track_never) + +#define PAGMO_S11N_TOPOLOGY_IMPLEMENT(topo) BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::detail::topo_inner) + +#define PAGMO_S11N_TOPOLOGY_EXPORT(topo) \ + PAGMO_S11N_TOPOLOGY_EXPORT_KEY(topo) \ + PAGMO_S11N_TOPOLOGY_IMPLEMENT(topo) + +namespace pagmo +{ + +// Detect the get_connections() method. +template +class has_get_connections +{ + template + using get_connections_t = decltype(std::declval().get_connections(std::size_t(0))); + static const bool implementation_defined + = std::is_same, vector_double>, detected_t>::value; + +public: + // Value of the type trait. + static const bool value = implementation_defined; +}; + +template +const bool has_get_connections::value; + +// Detect the push_back() method. +template +class has_push_back +{ + template + using push_back_t = decltype(std::declval().push_back()); + static const bool implementation_defined = std::is_same>::value; + +public: + // Value of the type trait. + static const bool value = implementation_defined; +}; + +template +const bool has_push_back::value; + +namespace detail +{ + +// Specialise this to true in order to disable all the UDT checks and mark a type +// as a UDT regardless of the features provided by it. +// NOTE: this is needed when implementing the machinery for Python topos. +// NOTE: leave this as an implementation detail for now. +template +struct disable_udt_checks : std::false_type { +}; + +} // namespace detail + +// Detect user-defined topologies (UDT). +template +class is_udt +{ + static const bool implementation_defined + = detail::disjunction>, std::is_default_constructible, + std::is_copy_constructible, std::is_move_constructible, + std::is_destructible, has_get_connections, has_push_back>, + detail::disable_udt_checks>::value; + +public: + // Value of the type trait. + static const bool value = implementation_defined; +}; + +template +const bool is_udt::value; + +namespace detail +{ + +struct PAGMO_DLL_PUBLIC_INLINE_CLASS topo_inner_base { + virtual ~topo_inner_base() {} + virtual std::unique_ptr clone() const = 0; + virtual std::string get_name() const = 0; + virtual std::string get_extra_info() const = 0; + virtual std::pair, vector_double> get_connections(std::size_t) const = 0; + virtual void push_back() = 0; + template + void serialize(Archive &, unsigned) + { + } +}; + +template +struct PAGMO_DLL_PUBLIC_INLINE_CLASS topo_inner final : topo_inner_base { + // We just need the def ctor, delete everything else. + topo_inner() = default; + topo_inner(const topo_inner &) = delete; + topo_inner(topo_inner &&) = delete; + topo_inner &operator=(const topo_inner &) = delete; + topo_inner &operator=(topo_inner &&) = delete; + // Constructors from T (copy and move variants). + explicit topo_inner(const T &x) : m_value(x) {} + explicit topo_inner(T &&x) : m_value(std::move(x)) {} + // The clone method, used in the copy constructor of topology. + virtual std::unique_ptr clone() const override final + { + return detail::make_unique(m_value); + } + // The mandatory methods. + virtual std::pair, vector_double> get_connections(std::size_t n) const override final + { + return m_value.get_connections(n); + } + virtual void push_back() override final + { + m_value.push_back(); + } + // Optional methods. + virtual std::string get_name() const override final + { + return get_name_impl(m_value); + } + virtual std::string get_extra_info() const override final + { + return get_extra_info_impl(m_value); + } + // Implementation of the optional methods. + template ::value, int> = 0> + static std::string get_name_impl(const U &value) + { + return value.get_name(); + } + template ::value, int> = 0> + static std::string get_name_impl(const U &) + { + return typeid(U).name(); + } + template ::value, int> = 0> + static std::string get_extra_info_impl(const U &value) + { + return value.get_extra_info(); + } + template ::value, int> = 0> + static std::string get_extra_info_impl(const U &) + { + return ""; + } + // Serialization + template + void serialize(Archive &ar, unsigned) + { + detail::archive(ar, boost::serialization::base_object(*this), m_value); + } + T m_value; +}; + +} // namespace detail + +} // namespace pagmo + +namespace boost +{ + +template +struct is_virtual_base_of> : false_type { +}; + +} // namespace boost + +namespace pagmo +{ + +// Topology class. +class PAGMO_DLL_PUBLIC topology +{ + // Enable the generic ctor only if T is not a topology (after removing + // const/reference qualifiers), and if T is a udt. + template + using generic_ctor_enabler = enable_if_t< + detail::conjunction>>, is_udt>>::value, int>; + +public: + // Default constructor. + topology(); + +private: + void generic_ctor_impl(); + +public: + // Generic constructor. + template = 0> + explicit topology(T &&x) : m_ptr(detail::make_unique>>(std::forward(x))) + { + generic_ctor_impl(); + } + // Copy ctor. + topology(const topology &); + // Move ctor. + topology(topology &&) noexcept; + // Move assignment. + topology &operator=(topology &&) noexcept; + // Copy assignment. + topology &operator=(const topology &); + // Generic assignment. + template = 0> + topology &operator=(T &&x) + { + return (*this) = topology(std::forward(x)); + } + + // Extract. + template + const T *extract() const noexcept + { + auto p = dynamic_cast *>(ptr()); + return p == nullptr ? nullptr : &(p->m_value); + } + template + T *extract() noexcept + { + auto p = dynamic_cast *>(ptr()); + return p == nullptr ? nullptr : &(p->m_value); + } + template + bool is() const noexcept + { + return extract() != nullptr; + } + + // Name. + std::string get_name() const + { + return m_name; + } + + // Extra info. + std::string get_extra_info() const; + + // Check if the topology is valid. + bool is_valid() const; + + // Get the connections to a vertex. + std::pair, vector_double> get_connections(std::size_t) const; + + // Add a vertex. + void push_back(); + // Add multiple vertices. + void push_back(unsigned); + + // Serialization. + template + void save(Archive &ar, unsigned) const + { + detail::to_archive(ar, m_ptr, m_name); + } + template + void load(Archive &ar, unsigned) + { + topology tmp; + detail::from_archive(ar, tmp.m_ptr, tmp.m_name); + *this = std::move(tmp); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + +private: + // Two small helpers to make sure that whenever we require + // access to the pointer it actually points to something. + detail::topo_inner_base const *ptr() const + { + assert(m_ptr.get() != nullptr); + return m_ptr.get(); + } + detail::topo_inner_base *ptr() + { + assert(m_ptr.get() != nullptr); + return m_ptr.get(); + } + +private: + std::unique_ptr m_ptr; + // Various topology properties determined at construction time + // from the concrete topology. These will be constant for the lifetime + // of topology, but we cannot mark them as such because of serialization. + std::string m_name; +}; + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Streaming operator for topology. +PAGMO_DLL_PUBLIC std::ostream &operator<<(std::ostream &, const topology &); + +#endif + +namespace detail +{ + +// A small helper for checking the weight of an edge in a topology. +PAGMO_DLL_PUBLIC void topology_check_edge_weight(double); + +} // namespace detail + +} // namespace pagmo + +// Disable tracking for the serialisation of topology. +BOOST_CLASS_TRACKING(pagmo::topology, boost::serialization::track_never) + +#endif diff --git a/include/pagmo/types.hpp b/include/pagmo/types.hpp index 33b4ec541..ad57a1d37 100644 --- a/include/pagmo/types.hpp +++ b/include/pagmo/types.hpp @@ -29,6 +29,7 @@ see https://www.gnu.org/licenses/. */ #ifndef PAGMO_TYPES_HPP #define PAGMO_TYPES_HPP +#include #include #include @@ -39,6 +40,9 @@ namespace pagmo /// Alias for an std::vector of doubles. typedef std::vector vector_double; +/// Alias for an std::vector of std::pairs of the size type of pagmo::vector_double. +typedef std::vector> sparsity_pattern; + /// Population size type. /** * This unsigned integral types is used to represent the size @@ -47,8 +51,13 @@ typedef std::vector vector_double; */ typedef std::vector::size_type pop_size_t; -/// Alias for an std::vector of std::pairs of the size type of pagmo::vector_double. -typedef std::vector> sparsity_pattern; +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// A group of individuals: IDs, dvs and fvs. +using individuals_group_t + = std::tuple, std::vector, std::vector>; + +#endif } // namespace pagmo diff --git a/pygmo/CMakeLists.txt b/pygmo/CMakeLists.txt index 42da14512..f972385f9 100644 --- a/pygmo/CMakeLists.txt +++ b/pygmo/CMakeLists.txt @@ -46,8 +46,11 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/pygmo/config.hpp" DESTINATION YACMA_PYTHON_MODULE(core algorithm.cpp bfe.cpp + topology.cpp island.cpp problem.cpp + r_policy.cpp + s_policy.cpp common_base.cpp object_serialization.cpp handle_thread_py_exception.cpp @@ -59,6 +62,9 @@ YACMA_PYTHON_MODULE(core expose_problems_1.cpp expose_islands.cpp expose_bfes.cpp + expose_topologies.cpp + expose_r_policies.cpp + expose_s_policies.cpp ) target_link_libraries(core PRIVATE Pagmo::pagmo ${PYGMO_BP_TARGET} Boost::disable_autolinking NumPy::numpy pygmo) target_compile_options(core PRIVATE "$<$:${PAGMO_CXX_FLAGS_DEBUG}>" "$<$:${PAGMO_CXX_FLAGS_RELEASE}>") @@ -89,8 +95,9 @@ install(TARGETS core add_subdirectory(plotting) # Add the Python files. -install(FILES __init__.py test.py _patch_problem.py _patch_algorithm.py _patch_bfe.py _patch_island.py _problem_test.py - _algorithm_test.py _island_test.py _py_islands.py _py_problems.py _mp_utils.py +install(FILES __init__.py test.py _patch_problem.py _patch_algorithm.py _patch_bfe.py _patch_island.py _patch_topology.py _patch_r_policy.py + _patch_s_policy.py _problem_test.py _algorithm_test.py _island_test.py _topology_test.py _r_policy_test.py _s_policy_test.py + _py_islands.py _py_problems.py _mp_utils.py "${CMAKE_CURRENT_BINARY_DIR}/_version.py" DESTINATION ${PYGMO_INSTALL_PATH}) # pygmo's public headers, to be installed. diff --git a/pygmo/__init__.py b/pygmo/__init__.py index b441567e8..2caf6b68b 100644 --- a/pygmo/__init__.py +++ b/pygmo/__init__.py @@ -43,6 +43,12 @@ from . import _patch_problem # Patch the bfe class. from . import _patch_bfe +# Patch the topology class. +from . import _patch_topology +# Patch the r_policy class. +from . import _patch_r_policy +# Patch the s_policy class. +from . import _patch_s_policy # And we explicitly import the test submodule from . import test @@ -77,11 +83,14 @@ class thread_safety(object): """ - #: No thread safety: concurrent operations on distinct objects are unsafe + #: No thread safety - concurrent operations on distinct objects are unsafe + #: (value = 0) none = core._thread_safety.none - #: Basic thread safety: concurrent operations on distinct objects are safe + #: Basic thread safety - concurrent operations on distinct objects are safe + #: (value = 1) basic = core._thread_safety.basic - #: Constant thread safety: constant (i.e., read-only) concurrent operations on the same object are safe + #: Constant thread safety - constant (i.e., read-only) concurrent operations on the same object are safe + #: (value = 2) constant = core._thread_safety.constant @@ -97,20 +106,67 @@ class evolve_status(object): """ - #: Idle: no asynchronous operations are ongoing, and no error was generated by an asynchronous operation in the past + #: Idle - no asynchronous operations are ongoing, and no error was generated by an asynchronous operation in the past #: (value = 0) idle = core._evolve_status.idle - #: Busy: asynchronous operations are ongoing, and no error was generated by an asynchronous operation in the past + #: Busy - asynchronous operations are ongoing, and no error was generated by an asynchronous operation in the past #: (value = 1) busy = core._evolve_status.busy - #: Idle with error: no asynchronous operations are ongoing, but an error was generated by an asynchronous operation + #: Idle with error - no asynchronous operations are ongoing, but an error was generated by an asynchronous operation #: in the past (value = 2) idle_error = core._evolve_status.idle_error - #: Busy with error: asynchronous operations are ongoing, and an error was generated by an asynchronous operation in + #: Busy with error - asynchronous operations are ongoing, and an error was generated by an asynchronous operation in #: the past (value = 3) busy_error = core._evolve_status.busy_error +class migration_type(object): + """Migration type. + + This enumeration represents the available migration policies in an :class:`~pygmo.archipelago`: + + * with the point-to-point migration policy, during migration an island will + consider individuals from only one of the connecting islands; + * with the broadcast migration policy, during migration an island will consider + individuals from *all* the connecting islands. + + """ + + #: Point-to-point migration + #: (value = 0) + p2p = core._migration_type.p2p + #: Broadcast migration + #: (value = 1) + broadcast = core._migration_type.broadcast + + +class migrant_handling(object): + """Migrant handling policy. + + This enumeration represents the available migrant handling + policies in an :class:`~pygmo.archipelago`. + + During migration, + individuals are selected from the islands and copied into a migration + database, from which they can be fetched by other islands. + This policy establishes what happens to the migrants in the database + after they have been fetched by a destination island: + + * with the preserve policy, a copy of the candidate migrants + remains in the database; + * with the evict policy, the candidate migrants are + removed from the database. + + """ + + #: Perserve migrants in the database + #: (value = 0) + preserve = core._migrant_handling.preserve + #: Evict migrants from the database + #: (value = 1) + evict = core._migrant_handling.evict + + # Override of the translate meta-problem constructor. __original_translate_init = translate.__init__ @@ -411,19 +467,18 @@ def _island_init(self, **kwargs): algo: a user-defined algorithm (either Python or C++), or an instance of :class:`~pygmo.algorithm` pop (:class:`~pygmo.population`): a population prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem` - size (int): the number of individuals b: a user-defined batch fitness evaluator (either Python or C++), or an instance of :class:`~pygmo.bfe` + size (int): the number of individuals + r_pol: a user-defined replacement policy (either Python or C++), or an instance of :class:`~pygmo.r_policy` + s_pol: a user-defined selection policy (either Python or C++), or an instance of :class:`~pygmo.s_policy` seed (int): the random seed (if not specified, it will be randomly-generated) Raises: KeyError: if the set of keyword arguments is invalid - unspecified: any exception thrown by: - - * the invoked C++ constructors, - * the deep copy of the UDI, - * the constructors of :class:`~pygmo.algorithm` and :class:`~pygmo.population`, - * failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function - signatures, etc.) + unspecified: any exception thrown by the invoked C++ constructors, + the deep copy of the UDI, the constructors of :class:`~pygmo.algorithm` and :class:`~pygmo.population`, + failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function + signatures, etc.) """ if len(kwargs) == 0: @@ -464,6 +519,21 @@ def _island_init(self, **kwargs): else: args = [algo, pop] + # Replace/selection policies, if any. + if 'r_pol' in kwargs: + r_pol = kwargs.pop('r_pol') + r_pol = r_pol if type(r_pol) == r_policy else r_policy(r_pol) + args.append(r_pol) + else: + args.append(r_policy()) + + if 's_pol' in kwargs: + s_pol = kwargs.pop('s_pol') + s_pol = s_pol if type(s_pol) == s_policy else s_policy(s_pol) + args.append(s_pol) + else: + args.append(s_policy()) + if len(kwargs) != 0: raise KeyError( 'unrecognised keyword arguments: {}'.format(list(kwargs.keys()))) @@ -477,9 +547,11 @@ def _island_init(self, **kwargs): __original_archi_init = archipelago.__init__ -def _archi_init(self, n=0, **kwargs): - """ - The constructor will initialise an archipelago with *n* islands built from *kwargs*. +def _archi_init(self, n=0, t=topology(), **kwargs): + """__init__(self, n=0, t=topology(), **kwargs) + + The constructor will initialise an archipelago with a topology *t* and + *n* islands built from *kwargs*. The keyword arguments accept the same format as explained in the constructor of :class:`~pygmo.island`, with the following differences: @@ -494,21 +566,25 @@ def _archi_init(self, n=0, **kwargs): Args: n (int): the number of islands in the archipelago + t: a user-defined topology (either Python or C++), or an instance of :class:`~pygmo.topology` Keyword Args: udi: a user-defined island, either Python or C++ algo: a user-defined algorithm (either Python or C++), or an instance of :class:`~pygmo.algorithm` pop (:class:`~pygmo.population`): a population prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem` - pop_size (int): the number of individuals for each island b: a user-defined batch fitness evaluator (either Python or C++), or an instance of :class:`~pygmo.bfe` + pop_size (int): the number of individuals for each island + r_pol: a user-defined replacement policy (either Python or C++), or an instance of :class:`~pygmo.r_policy` + s_pol: a user-defined selection policy (either Python or C++), or an instance of :class:`~pygmo.s_policy` seed (int): the random seed Raises: TypeError: if *n* is not an integral type ValueError: if *n* is negative - unspecified: any exception thrown by the constructor of :class:`~pygmo.island` - or by the underlying C++ constructor + unspecified: any exception thrown by the constructor of :class:`~pygmo.island`, + by the underlying C++ constructor, :func:`~pygmo.archipelago.push_back()` or + by the public interface of :class:`~pygmo.topology` Examples: >>> from pygmo import * @@ -582,8 +658,9 @@ def _archi_init(self, n=0, **kwargs): ps_val = kwargs.pop('pop_size') kwargs['size'] = ps_val - # Call the original init, which constructs an empty archi. - __original_archi_init(self) + # Call the original init, which constructs an empty archi from a topology. + t = t if type(t) == topology else topology(t) + __original_archi_init(self, t) if 'seed' in kwargs: # Special handling of the 'seed' argument. @@ -613,18 +690,21 @@ def _archi_init(self, n=0, **kwargs): def _archi_push_back(self, **kwargs): - """Add island. + """Add an island. This method will construct an island from the supplied arguments and add it to the archipelago. Islands are added at the end of the archipelago (that is, the new island will have an index - equal to the size of the archipelago before the call to this method). + equal to the size of the archipelago before the call to this method). :func:`pygmo.topology.push_back()` + will also be called on the :class:`~pygmo.topology` associated to this archipelago, so that + the addition of a new island to the archipelago is mirrored by the addition of a new vertex + to the topology. The keyword arguments accept the same format as explained in the constructor of :class:`~pygmo.island`. Raises: - unspecified: any exception thrown by the constructor of :class:`~pygmo.island` or by - the underlying C++ method + unspecified: any exception thrown by the constructor of :class:`~pygmo.island`, + :func:`pygmo.topology.push_back()` or by the underlying C++ method """ self._push_back(island(**kwargs)) @@ -632,6 +712,28 @@ def _archi_push_back(self, **kwargs): setattr(archipelago, "push_back", _archi_push_back) + +def _archi_set_topology(self, t): + """This method will wait for any ongoing evolution in the archipelago to finish, + and it will then set the topology of the archipelago to *t*. + + Note that it is the user's responsibility to ensure that the new topology is + consistent with the archipelago's properties. + + Args: + t: a user-defined topology (either Python or C++), or an instance of :class:`~pygmo.topology` + + Raises: + unspecified: any exception thrown by copying the topology + + """ + t = t if type(t) == topology else topology(t) + self._set_topology(t) + + +setattr(archipelago, "set_topology", _archi_set_topology) + + # Machinery for the setup of the serialization backend. _serialization_backend = _cloudpickle diff --git a/pygmo/_island_test.py b/pygmo/_island_test.py index fff1d6fdf..742f200d0 100644 --- a/pygmo/_island_test.py +++ b/pygmo/_island_test.py @@ -33,6 +33,18 @@ import unittest as _ut +class _r_pol(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + +class _s_pol(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + class _udi_01(object): def run_evolve(self, algo, pop): @@ -101,8 +113,10 @@ def runTest(self): self.run_stateful_algo_tests() def run_basic_tests(self): - from .core import island, thread_island, null_algorithm, null_problem, de, rosenbrock + from .core import island, thread_island, null_algorithm, null_problem, de, rosenbrock, r_policy, s_policy, fair_replace, select_best, population isl = island() + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) self.assertTrue(isl.get_algorithm().is_(null_algorithm)) self.assertTrue(isl.get_population().problem.is_(null_problem)) self.assertTrue(isl.extract(thread_island) is not None) @@ -110,17 +124,30 @@ def run_basic_tests(self): self.assertTrue(isl.extract(int) is None) self.assertEqual(len(isl.get_population()), 0) isl = island(algo=de(), prob=rosenbrock(), size=10) + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) self.assertTrue(isl.get_algorithm().is_(de)) self.assertTrue(isl.get_population().problem.is_(rosenbrock)) self.assertEqual(len(isl.get_population()), 10) isl = island(prob=rosenbrock(), udi=thread_island(), size=11, algo=de(), seed=15) + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) self.assertTrue(isl.get_algorithm().is_(de)) self.assertTrue(isl.get_population().problem.is_(rosenbrock)) self.assertEqual(len(isl.get_population()), 11) self.assertEqual(isl.get_population().get_seed(), 15) + isl = island(udi=thread_island(), + algo=de(), pop=population()) + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) + self.assertTrue(isl.get_algorithm().is_(de)) + self.assertTrue(isl.get_population().problem.is_(null_problem)) + self.assertEqual(len(isl.get_population()), 0) isl = island(prob=rosenbrock(), udi=_udi_01(), size=11, algo=de(), seed=15) + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) self.assertEqual(isl.get_name(), "udi_01") self.assertEqual(isl.get_extra_info(), "extra bits") self.assertTrue(isl.get_algorithm().is_(de)) @@ -177,6 +204,50 @@ def run_evolve(self, algo, pop): with self.assertRaises(NotImplementedError) as cm: island(prob=rosenbrock(), udi=isl, size=11, algo=de(), seed=15) + # Constructors with r/s_pol arguments. + isl = island(prob=rosenbrock(), udi=thread_island(), + size=11, algo=de(), seed=15, r_pol=r_policy()) + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) + self.assertTrue(isl.get_algorithm().is_(de)) + self.assertTrue(isl.get_population().problem.is_(rosenbrock)) + self.assertEqual(len(isl.get_population()), 11) + self.assertEqual(isl.get_population().get_seed(), 15) + + isl = island(prob=rosenbrock(), udi=thread_island(), + size=11, algo=de(), seed=15, r_pol=_r_pol()) + self.assertFalse("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) + self.assertTrue(isl.get_algorithm().is_(de)) + self.assertTrue(isl.get_population().problem.is_(rosenbrock)) + self.assertEqual(len(isl.get_population()), 11) + self.assertEqual(isl.get_population().get_seed(), 15) + + isl = island(prob=rosenbrock(), udi=thread_island(), + size=11, algo=de(), seed=15, s_pol=s_policy()) + self.assertTrue("Fair replace" in repr(isl)) + self.assertTrue("Select best" in repr(isl)) + self.assertTrue(isl.get_algorithm().is_(de)) + self.assertTrue(isl.get_population().problem.is_(rosenbrock)) + self.assertEqual(len(isl.get_population()), 11) + self.assertEqual(isl.get_population().get_seed(), 15) + + isl = island(prob=rosenbrock(), udi=thread_island(), + size=11, algo=de(), seed=15, s_pol=_s_pol()) + self.assertTrue("Fair replace" in repr(isl)) + self.assertFalse("Select best" in repr(isl)) + self.assertTrue(isl.get_algorithm().is_(de)) + self.assertTrue(isl.get_population().problem.is_(rosenbrock)) + self.assertEqual(len(isl.get_population()), 11) + self.assertEqual(isl.get_population().get_seed(), 15) + + # Test the r/s_policy getters. + isl = island(prob=rosenbrock(), udi=thread_island(), + size=11, algo=de(), seed=15, r_pol=_r_pol(), s_pol=_s_pol()) + + self.assertTrue(isl.get_r_policy().is_(_r_pol)) + self.assertTrue(isl.get_s_policy().is_(_s_pol)) + def run_concurrent_access_tests(self): import threading as thr from .core import island, de, rosenbrock @@ -248,6 +319,13 @@ def run_serialization_tests(self): isl = loads(dumps(isl)) self.assertEqual(tmp, repr(isl)) + # Check with custom policies as well. + isl = island(algo=de(), prob=rosenbrock(), size=25, + r_pol=_r_pol(), s_pol=_s_pol()) + tmp = repr(isl) + isl = loads(dumps(isl)) + self.assertEqual(tmp, repr(isl)) + def run_stateful_algo_tests(self): from .core import island, rosenbrock isl = island(algo=_stateful_algo(), prob=rosenbrock(), size=25) diff --git a/pygmo/_patch_r_policy.py b/pygmo/_patch_r_policy.py new file mode 100644 index 000000000..fca1a6242 --- /dev/null +++ b/pygmo/_patch_r_policy.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 PaGMO development team +# +# This file is part of the PaGMO library. +# +# The PaGMO library is free software; you can redistribute it and/or modify +# it under the terms of either: +# +# * the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# or +# +# * the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# or both in parallel, as here. +# +# The PaGMO library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received copies of the GNU General Public License and the +# GNU Lesser General Public License along with the PaGMO library. If not, +# see https://www.gnu.org/licenses/. + +# for python 2.0 compatibility +from __future__ import absolute_import as _ai + +from .core import r_policy + + +def _r_policy_extract(self, t): + """Extract the user-defined replacement policy. + + This method allows to extract a reference to the user-defined replacement policy (UDRP) stored within this + :class:`~pygmo.r_policy` instance. The behaviour of this function depends on the value + of *t* (which must be a :class:`type`) and on the type of the internal UDRP: + + * if the type of the UDRP is *t*, then a reference to the UDRP will be returned + (this mirrors the behaviour of the corresponding C++ method + :cpp:func:`pagmo::r_policy::extract()`), + * if *t* is :class:`object` and the UDRP is a Python object (as opposed to an + :ref:`exposed C++ replacement policy `), then a reference to the + UDRP will be returned (this allows to extract a Python UDRP without knowing its type), + * otherwise, :data:`None` will be returned. + + Args: + t (:class:`type`): the type of the user-defined replacement policy to extract + + Returns: + a reference to the internal user-defined replacement policy, or :data:`None` if the extraction fails + + Raises: + TypeError: if *t* is not a :class:`type` + + """ + if not isinstance(t, type): + raise TypeError("the 't' parameter must be a type") + if hasattr(t, "_pygmo_cpp_r_policy"): + return self._cpp_extract(t()) + return self._py_extract(t) + + +def _r_policy_is(self, t): + """Check the type of the user-defined replacement policy. + + This method returns :data:`False` if :func:`extract(t) ` returns + :data:`None`, and :data:`True` otherwise. + + Args: + t (:class:`type`): the type that will be compared to the type of the UDRP + + Returns: + bool: whether the UDRP is of type *t* or not + + Raises: + unspecified: any exception thrown by :func:`~pygmo.r_policy.extract()` + + """ + return not self.extract(t) is None + + +# Do the actual patching. +setattr(r_policy, "extract", _r_policy_extract) +setattr(r_policy, "is_", _r_policy_is) diff --git a/pygmo/_patch_s_policy.py b/pygmo/_patch_s_policy.py new file mode 100644 index 000000000..2496978a0 --- /dev/null +++ b/pygmo/_patch_s_policy.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 PaGMO development team +# +# This file is part of the PaGMO library. +# +# The PaGMO library is free software; you can redistribute it and/or modify +# it under the terms of either: +# +# * the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# or +# +# * the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# or both in parallel, as here. +# +# The PaGMO library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received copies of the GNU General Public License and the +# GNU Lesser General Public License along with the PaGMO library. If not, +# see https://www.gnu.org/licenses/. + +# for python 2.0 compatibility +from __future__ import absolute_import as _ai + +from .core import s_policy + + +def _s_policy_extract(self, t): + """Extract the user-defined selection policy. + + This method allows to extract a reference to the user-defined selection policy (UDSP) stored within this + :class:`~pygmo.s_policy` instance. The behaviour of this function depends on the value + of *t* (which must be a :class:`type`) and on the type of the internal UDSP: + + * if the type of the UDSP is *t*, then a reference to the UDSP will be returned + (this mirrors the behaviour of the corresponding C++ method + :cpp:func:`pagmo::s_policy::extract()`), + * if *t* is :class:`object` and the UDSP is a Python object (as opposed to an + :ref:`exposed C++ selection policy `), then a reference to the + UDSP will be returned (this allows to extract a Python UDSP without knowing its type), + * otherwise, :data:`None` will be returned. + + Args: + t (:class:`type`): the type of the user-defined selection policy to extract + + Returns: + a reference to the internal user-defined selection policy, or :data:`None` if the extraction fails + + Raises: + TypeError: if *t* is not a :class:`type` + + """ + if not isinstance(t, type): + raise TypeError("the 't' parameter must be a type") + if hasattr(t, "_pygmo_cpp_s_policy"): + return self._cpp_extract(t()) + return self._py_extract(t) + + +def _s_policy_is(self, t): + """Check the type of the user-defined selection policy. + + This method returns :data:`False` if :func:`extract(t) ` returns + :data:`None`, and :data:`True` otherwise. + + Args: + t (:class:`type`): the type that will be compared to the type of the UDSP + + Returns: + bool: whether the UDSP is of type *t* or not + + Raises: + unspecified: any exception thrown by :func:`~pygmo.s_policy.extract()` + + """ + return not self.extract(t) is None + + +# Do the actual patching. +setattr(s_policy, "extract", _s_policy_extract) +setattr(s_policy, "is_", _s_policy_is) diff --git a/pygmo/_patch_topology.py b/pygmo/_patch_topology.py new file mode 100644 index 000000000..6f2d865d0 --- /dev/null +++ b/pygmo/_patch_topology.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 PaGMO development team +# +# This file is part of the PaGMO library. +# +# The PaGMO library is free software; you can redistribute it and/or modify +# it under the terms of either: +# +# * the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# or +# +# * the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# or both in parallel, as here. +# +# The PaGMO library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received copies of the GNU General Public License and the +# GNU Lesser General Public License along with the PaGMO library. If not, +# see https://www.gnu.org/licenses/. + +# for python 2.0 compatibility +from __future__ import absolute_import as _ai + +from .core import topology + + +def _topology_extract(self, t): + """Extract the user-defined topology. + + This method allows to extract a reference to the user-defined topology (UDT) stored within this + :class:`~pygmo.topology` instance. The behaviour of this function depends on the value + of *t* (which must be a :class:`type`) and on the type of the internal UDT: + + * if the type of the UDT is *t*, then a reference to the UDT will be returned + (this mirrors the behaviour of the corresponding C++ method + :cpp:func:`pagmo::topology::extract()`), + * if *t* is :class:`object` and the UDT is a Python object (as opposed to an + :ref:`exposed C++ topology `), then a reference to the + UDT will be returned (this allows to extract a Python UDT without knowing its type), + * otherwise, :data:`None` will be returned. + + Args: + t (:class:`type`): the type of the user-defined topology to extract + + Returns: + a reference to the internal user-defined topology, or :data:`None` if the extraction fails + + Raises: + TypeError: if *t* is not a :class:`type` + + Examples: + >>> import pygmo as pg + >>> t1 = pg.topology(pg.ring()) + >>> t1.extract(pg.ring) # doctest: +SKIP + + >>> t1.extract(pg.unconnected) is None + True + >>> class topo: + ... def get_connections(self, n): + ... return [[], []] + ... def push_back(self): + ... return + >>> t2 = pg.topology(topo()) + >>> t2.extract(object) # doctest: +SKIP + <__main__.topo at 0x7f8e478c04e0> + >>> t2.extract(topo) # doctest: +SKIP + <__main__.topo at 0x7f8e478c04e0> + >>> t2.extract(pg.unconnected) is None + True + + """ + if not isinstance(t, type): + raise TypeError("the 't' parameter must be a type") + if hasattr(t, "_pygmo_cpp_topology"): + return self._cpp_extract(t()) + return self._py_extract(t) + + +def _topology_is(self, t): + """Check the type of the user-defined topology. + + This method returns :data:`False` if :func:`extract(t) ` returns + :data:`None`, and :data:`True` otherwise. + + Args: + t (:class:`type`): the type that will be compared to the type of the UDT + + Returns: + bool: whether the UDT is of type *t* or not + + Raises: + unspecified: any exception thrown by :func:`~pygmo.topology.extract()` + + """ + return not self.extract(t) is None + + +# Do the actual patching. +setattr(topology, "extract", _topology_extract) +setattr(topology, "is_", _topology_is) diff --git a/pygmo/_problem_test.py b/pygmo/_problem_test.py index 088b9e131..6c5a7f727 100644 --- a/pygmo/_problem_test.py +++ b/pygmo/_problem_test.py @@ -1969,7 +1969,8 @@ def fitness(self, a): return [42] self.assertTrue(problem(p()).get_thread_safety() == ts.none) - self.assertTrue(problem(rosenbrock()).get_thread_safety() == ts.constant) + self.assertTrue( + problem(rosenbrock()).get_thread_safety() == ts.constant) self.assertTrue( problem(_tu_test_problem()).get_thread_safety() == ts.none) self.assertTrue( diff --git a/pygmo/_r_policy_test.py b/pygmo/_r_policy_test.py new file mode 100644 index 000000000..7583489dc --- /dev/null +++ b/pygmo/_r_policy_test.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 PaGMO development team +# +# This file is part of the PaGMO library. +# +# The PaGMO library is free software; you can redistribute it and/or modify +# it under the terms of either: +# +# * the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# or +# +# * the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# or both in parallel, as here. +# +# The PaGMO library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received copies of the GNU General Public License and the +# GNU Lesser General Public License along with the PaGMO library. If not, +# see https://www.gnu.org/licenses/. + +from __future__ import absolute_import as _ai + +import unittest as _ut + + +class _r_pol(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + +class r_policy_test_case(_ut.TestCase): + """Test case for the :class:`~pygmo.r_policy` class. + + """ + + def runTest(self): + self.run_basic_tests() + self.run_extract_tests() + self.run_name_info_tests() + self.run_pickle_tests() + + def run_basic_tests(self): + # Tests for minimal r_policy, and mandatory methods. + import numpy as np + from .core import r_policy, fair_replace + # Def construction. + r = r_policy() + self.assertTrue(r.extract(fair_replace) is not None) + self.assertTrue(r.extract(int) is None) + + # First a few non-r_pols. + self.assertRaises(NotImplementedError, lambda: r_policy(1)) + self.assertRaises(NotImplementedError, lambda: r_policy([])) + self.assertRaises(TypeError, lambda: r_policy(int)) + # Some policies missing methods, wrong arity, etc. + + class nr0(object): + pass + self.assertRaises(NotImplementedError, lambda: r_policy(nr0())) + + class nr1(object): + + replace = 45 + self.assertRaises(NotImplementedError, lambda: r_policy(nr1())) + + # The minimal good citizen. + glob = [] + + class r(object): + + def __init__(self, g): + self.g = g + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + self.g.append(2) + return inds + + r_inst = r(glob) + r_pol = r_policy(r_inst) + + # Test the keyword arg. + r_pol = r_policy(udrp=fair_replace()) + r_pol = r_policy(udrp=r_inst) + + # Check a few r_pol properties. + self.assertEqual(r_pol.get_extra_info(), "") + self.assertTrue(r_pol.extract(int) is None) + self.assertTrue(r_pol.extract(fair_replace) is None) + self.assertFalse(r_pol.extract(r) is None) + self.assertTrue(r_pol.is_(r)) + + # Check the replace method. + self.assertTrue(isinstance(r_pol.replace( + ([], [], []), 1, 0, 1, 0, 0, [], ([], [], [])), tuple)) + r_out = r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], [])) + self.assertTrue(np.all(r_out[0] == np.array([1, 2]))) + self.assertTrue(r_out[1].dtype == np.dtype(float)) + self.assertTrue(r_out[2].dtype == np.dtype(float)) + self.assertTrue(np.all(r_out[1] == np.array([[.1, .2], [.3, .4]]))) + self.assertTrue(np.all(r_out[2] == np.array([[1.1], [2.2]]))) + self.assertTrue(len(r_out) == 3) + # Assert that r_inst was deep-copied into r_pol: + # the instance in r_pol will have its own copy of glob + # and it will not be a reference the outside object. + self.assertEqual(len(glob), 0) + self.assertEqual(len(r_pol.extract(r).g), 2) + self.assertEqual(r_pol.extract(r).g, [2]*2) + + r_pol = r_policy(fair_replace()) + self.assertTrue(r_pol.get_extra_info() != "") + self.assertTrue(r_pol.extract(int) is None) + self.assertTrue(r_pol.extract(r) is None) + self.assertFalse(r_pol.extract(fair_replace) is None) + self.assertTrue(r_pol.is_(fair_replace)) + + # Wrong retvals for replace(). + + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return [] + r_pol = r_policy(r()) + self.assertRaises(RuntimeError, lambda: r_pol.replace(inds=([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), nx=2, nix=0, nobj=1, nec=0, nic=0, tol=[], mig=([], [], []))) + # Try also flipping around the named argument. + self.assertRaises(RuntimeError, lambda: r_pol.replace(nx=2, inds=([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), nec=0, nix=0, nobj=1, nic=0, mig=([], [], []), tol=[])) + + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return [1] + r_pol = r_policy(r()) + self.assertRaises(RuntimeError, lambda: r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], []))) + + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return [1, 2] + r_pol = r_policy(r()) + self.assertRaises(RuntimeError, lambda: r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], []))) + + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return [1, 2, 3, 4] + r_pol = r_policy(r()) + self.assertRaises(RuntimeError, lambda: r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], []))) + + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return 1 + r_pol = r_policy(r()) + self.assertRaises(RuntimeError, lambda: r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], []))) + + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return ([1], [], []) + r_pol = r_policy(r()) + with self.assertRaises(ValueError) as cm: + r_pol.replace(([1, 2], [[.1], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], [])) + err = cm.exception + self.assertTrue( + "not all the individuals passed to a replacement policy of type " in str(err)) + with self.assertRaises(ValueError) as cm: + r_pol.replace(([1, 2], [[.1, .2]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], [])) + err = cm.exception + self.assertTrue( + "must all have the same sizes, but instead their sizes are " in str(err)) + with self.assertRaises(ValueError) as cm: + r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], [])) + err = cm.exception + self.assertTrue( + "must all have the same sizes, but instead their sizes are " in str(err)) + + # Test wrong array construction of IDs. + class r(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return (np.array([1], dtype=int), [[1, 2]], [[1]]) + r_pol = r_policy(r()) + with self.assertRaises(RuntimeError) as cm: + r_pol.replace(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [], ([], [], [])) + + # Test that construction from another pygmo.r_policy fails. + with self.assertRaises(TypeError) as cm: + r_policy(r_pol) + err = cm.exception + self.assertTrue( + "a pygmo.r_policy cannot be used as a UDRP for another pygmo.r_policy (if you need to copy a replacement policy please use the standard Python copy()/deepcopy() functions)" in str(err)) + + def run_extract_tests(self): + from .core import r_policy, _test_r_policy, fair_replace + import sys + + # First we try with a C++ test r_pol. + t = r_policy(_test_r_policy()) + # Verify the refcount of p is increased after extract(). + rc = sys.getrefcount(t) + tr_pol = t.extract(_test_r_policy) + self.assertEqual(sys.getrefcount(t), rc + 1) + del tr_pol + self.assertEqual(sys.getrefcount(t), rc) + # Verify we are modifying the inner object. + t.extract(_test_r_policy).set_n(5) + self.assertEqual(t.extract(_test_r_policy).get_n(), 5) + + class tr_policy(object): + + def __init__(self): + self._n = 1 + + def get_n(self): + return self._n + + def set_n(self, n): + self._n = n + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + # Test with Python r_policy. + t = r_policy(tr_policy()) + rc = sys.getrefcount(t) + tr_pol = t.extract(tr_policy) + # Reference count does not increase because + # tr_policy is stored as a proper Python object + # with its own refcount. + self.assertTrue(sys.getrefcount(t) == rc) + self.assertTrue(tr_pol.get_n() == 1) + tr_pol.set_n(12) + self.assert_(t.extract(tr_policy).get_n() == 12) + + # Check that we can extract Python UDTs also via Python's object type. + t = r_policy(tr_policy()) + self.assertTrue(not t.extract(object) is None) + # Check we are referring to the same object. + self.assertEqual(id(t.extract(object)), id(t.extract(tr_policy))) + # Check that it will not work with exposed C++ replacement policies. + t = r_policy(fair_replace()) + self.assertTrue(t.extract(object) is None) + self.assertTrue(not t.extract(fair_replace) is None) + + def run_name_info_tests(self): + from .core import r_policy + + class t(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + r_pol = r_policy(t()) + self.assertTrue(r_pol.get_name() != '') + self.assertTrue(r_pol.get_extra_info() == '') + + class t(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + def get_name(self): + return 'pippo' + + r_pol = r_policy(t()) + self.assertTrue(r_pol.get_name() == 'pippo') + self.assertTrue(r_pol.get_extra_info() == '') + + class t(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + def get_extra_info(self): + return 'pluto' + + r_pol = r_policy(t()) + self.assertTrue(r_pol.get_name() != '') + self.assertTrue(r_pol.get_extra_info() == 'pluto') + + class t(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + def get_name(self): + return 'pippo' + + def get_extra_info(self): + return 'pluto' + + r_pol = r_policy(t()) + self.assertTrue(r_pol.get_name() == 'pippo') + self.assertTrue(r_pol.get_extra_info() == 'pluto') + + def run_pickle_tests(self): + from .core import r_policy, fair_replace + from pickle import dumps, loads + t_ = r_policy(fair_replace()) + t = loads(dumps(t_)) + self.assertEqual(repr(t), repr(t_)) + self.assertTrue(t.is_(fair_replace)) + + t_ = r_policy(_r_pol()) + t = loads(dumps(t_)) + self.assertEqual(repr(t), repr(t_)) + self.assertTrue(t.is_(_r_pol)) diff --git a/pygmo/_s_policy_test.py b/pygmo/_s_policy_test.py new file mode 100644 index 000000000..8f7a41843 --- /dev/null +++ b/pygmo/_s_policy_test.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 PaGMO development team +# +# This file is part of the PaGMO library. +# +# The PaGMO library is free software; you can redistribute it and/or modify +# it under the terms of either: +# +# * the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# or +# +# * the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# or both in parallel, as here. +# +# The PaGMO library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received copies of the GNU General Public License and the +# GNU Lesser General Public License along with the PaGMO library. If not, +# see https://www.gnu.org/licenses/. + +from __future__ import absolute_import as _ai + +import unittest as _ut + + +class _s_pol(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + +class s_policy_test_case(_ut.TestCase): + """Test case for the :class:`~pygmo.s_policy` class. + + """ + + def runTest(self): + self.run_basic_tests() + self.run_extract_tests() + self.run_name_info_tests() + self.run_pickle_tests() + + def run_basic_tests(self): + # Tests for minimal s_policy, and mandatory methods. + import numpy as np + from .core import s_policy, select_best + # Def construction. + r = s_policy() + self.assertTrue(r.extract(select_best) is not None) + self.assertTrue(r.extract(int) is None) + + # First a few non-s_pols. + self.assertRaises(NotImplementedError, lambda: s_policy(1)) + self.assertRaises(NotImplementedError, lambda: s_policy([])) + self.assertRaises(TypeError, lambda: s_policy(int)) + # Some policies missing methods, wrong arity, etc. + + class nr0(object): + pass + self.assertRaises(NotImplementedError, lambda: s_policy(nr0())) + + class nr1(object): + + select = 45 + self.assertRaises(NotImplementedError, lambda: s_policy(nr1())) + + # The minimal good citizen. + glob = [] + + class r(object): + + def __init__(self, g): + self.g = g + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + self.g.append(2) + return inds + + r_inst = r(glob) + s_pol = s_policy(r_inst) + + # Test the keyword arg. + s_pol = s_policy(udsp=select_best()) + s_pol = s_policy(udsp=r_inst) + + # Check a few s_pol properties. + self.assertEqual(s_pol.get_extra_info(), "") + self.assertTrue(s_pol.extract(int) is None) + self.assertTrue(s_pol.extract(select_best) is None) + self.assertFalse(s_pol.extract(r) is None) + self.assertTrue(s_pol.is_(r)) + + # Check the select method. + self.assertTrue(isinstance(s_pol.select( + ([], [], []), 1, 0, 1, 0, 0, []), tuple)) + r_out = s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, []) + self.assertTrue(np.all(r_out[0] == np.array([1, 2]))) + self.assertTrue(r_out[1].dtype == np.dtype(float)) + self.assertTrue(r_out[2].dtype == np.dtype(float)) + self.assertTrue(np.all(r_out[1] == np.array([[.1, .2], [.3, .4]]))) + self.assertTrue(np.all(r_out[2] == np.array([[1.1], [2.2]]))) + self.assertTrue(len(r_out) == 3) + # Assert that r_inst was deep-copied into s_pol: + # the instance in s_pol will have its own copy of glob + # and it will not be a reference the outside object. + self.assertEqual(len(glob), 0) + self.assertEqual(len(s_pol.extract(r).g), 2) + self.assertEqual(s_pol.extract(r).g, [2]*2) + + s_pol = s_policy(select_best()) + self.assertTrue(s_pol.get_extra_info() != "") + self.assertTrue(s_pol.extract(int) is None) + self.assertTrue(s_pol.extract(r) is None) + self.assertFalse(s_pol.extract(select_best) is None) + self.assertTrue(s_pol.is_(select_best)) + + # Wrong retvals for select(). + + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return [] + s_pol = s_policy(r()) + self.assertRaises(RuntimeError, lambda: s_pol.select(inds=([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), nx=2, nix=0, nobj=1, nec=0, nic=0, tol=[])) + # Try also flipping around the named argument. + self.assertRaises(RuntimeError, lambda: s_pol.select(nx=2, inds=([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), nec=0, nix=0, nobj=1, nic=0, tol=[])) + + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return [1] + s_pol = s_policy(r()) + self.assertRaises(RuntimeError, lambda: s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [])) + + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return [1, 2] + s_pol = s_policy(r()) + self.assertRaises(RuntimeError, lambda: s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [])) + + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return [1, 2, 3, 4] + s_pol = s_policy(r()) + self.assertRaises(RuntimeError, lambda: s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [])) + + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return 1 + s_pol = s_policy(r()) + self.assertRaises(RuntimeError, lambda: s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, [])) + + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return ([1], [], []) + s_pol = s_policy(r()) + with self.assertRaises(ValueError) as cm: + s_pol.select(([1, 2], [[.1], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, []) + err = cm.exception + self.assertTrue( + "not all the individuals passed to a selection policy of type " in str(err)) + with self.assertRaises(ValueError) as cm: + s_pol.select(([1, 2], [[.1, .2]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, []) + err = cm.exception + self.assertTrue( + "must all have the same sizes, but instead their sizes are " in str(err)) + with self.assertRaises(ValueError) as cm: + s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, []) + err = cm.exception + self.assertTrue( + "must all have the same sizes, but instead their sizes are " in str(err)) + + # Test wrong array construction of IDs. + class r(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return (np.array([1], dtype=int), [[1, 2]], [[1]]) + s_pol = s_policy(r()) + with self.assertRaises(RuntimeError) as cm: + s_pol.select(([1, 2], [[.1, .2], [.3, .4]], [ + [1.1], [2.2]]), 2, 0, 1, 0, 0, []) + + # Test that construction from another pygmo.s_policy fails. + with self.assertRaises(TypeError) as cm: + s_policy(s_pol) + err = cm.exception + self.assertTrue( + "a pygmo.s_policy cannot be used as a UDSP for another pygmo.s_policy (if you need to copy a selection policy please use the standard Python copy()/deepcopy() functions)" in str(err)) + + def run_extract_tests(self): + from .core import s_policy, _test_s_policy, select_best + import sys + + # First we try with a C++ test s_pol. + t = s_policy(_test_s_policy()) + # Verify the refcount of p is increased after extract(). + rc = sys.getrefcount(t) + ts_pol = t.extract(_test_s_policy) + self.assertEqual(sys.getrefcount(t), rc + 1) + del ts_pol + self.assertEqual(sys.getrefcount(t), rc) + # Verify we are modifying the inner object. + t.extract(_test_s_policy).set_n(5) + self.assertEqual(t.extract(_test_s_policy).get_n(), 5) + + class ts_policy(object): + + def __init__(self): + self._n = 1 + + def get_n(self): + return self._n + + def set_n(self, n): + self._n = n + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + # Test with Python s_policy. + t = s_policy(ts_policy()) + rc = sys.getrefcount(t) + ts_pol = t.extract(ts_policy) + # Reference count does not increase because + # ts_policy is stored as a proper Python object + # with its own refcount. + self.assertTrue(sys.getrefcount(t) == rc) + self.assertTrue(ts_pol.get_n() == 1) + ts_pol.set_n(12) + self.assert_(t.extract(ts_policy).get_n() == 12) + + # Check that we can extract Python UDTs also via Python's object type. + t = s_policy(ts_policy()) + self.assertTrue(not t.extract(object) is None) + # Check we are referring to the same object. + self.assertEqual(id(t.extract(object)), id(t.extract(ts_policy))) + # Check that it will not work with exposed C++ selection policies. + t = s_policy(select_best()) + self.assertTrue(t.extract(object) is None) + self.assertTrue(not t.extract(select_best) is None) + + def run_name_info_tests(self): + from .core import s_policy + + class t(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + s_pol = s_policy(t()) + self.assertTrue(s_pol.get_name() != '') + self.assertTrue(s_pol.get_extra_info() == '') + + class t(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + def get_name(self): + return 'pippo' + + s_pol = s_policy(t()) + self.assertTrue(s_pol.get_name() == 'pippo') + self.assertTrue(s_pol.get_extra_info() == '') + + class t(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + def get_extra_info(self): + return 'pluto' + + s_pol = s_policy(t()) + self.assertTrue(s_pol.get_name() != '') + self.assertTrue(s_pol.get_extra_info() == 'pluto') + + class t(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + def get_name(self): + return 'pippo' + + def get_extra_info(self): + return 'pluto' + + s_pol = s_policy(t()) + self.assertTrue(s_pol.get_name() == 'pippo') + self.assertTrue(s_pol.get_extra_info() == 'pluto') + + def run_pickle_tests(self): + from .core import s_policy, select_best + from pickle import dumps, loads + t_ = s_policy(select_best()) + t = loads(dumps(t_)) + self.assertEqual(repr(t), repr(t_)) + self.assertTrue(t.is_(select_best)) + + t_ = s_policy(_s_pol()) + t = loads(dumps(t_)) + self.assertEqual(repr(t), repr(t_)) + self.assertTrue(t.is_(_s_pol)) diff --git a/pygmo/_topology_test.py b/pygmo/_topology_test.py new file mode 100644 index 000000000..9f6fa419b --- /dev/null +++ b/pygmo/_topology_test.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017-2018 PaGMO development team +# +# This file is part of the PaGMO library. +# +# The PaGMO library is free software; you can redistribute it and/or modify +# it under the terms of either: +# +# * the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# or +# +# * the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# or both in parallel, as here. +# +# The PaGMO library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received copies of the GNU General Public License and the +# GNU Lesser General Public License along with the PaGMO library. If not, +# see https://www.gnu.org/licenses/. + +from __future__ import absolute_import as _ai + +import unittest as _ut + + +class _topo(object): + + def get_connections(self, n): + return [[], []] + + def push_back(self): + return + + +class topology_test_case(_ut.TestCase): + """Test case for the :class:`~pygmo.topology` class. + + """ + + def runTest(self): + self.run_basic_tests() + self.run_extract_tests() + self.run_name_info_tests() + self.run_pickle_tests() + + def run_basic_tests(self): + # Tests for minimal topology, and mandatory methods. + from numpy import ndarray, dtype + from .core import topology, ring, unconnected + # Def construction. + t = topology() + self.assertTrue(t.extract(unconnected) is not None) + self.assertTrue(t.extract(ring) is None) + + # First a few non-topos. + self.assertRaises(NotImplementedError, lambda: topology(1)) + self.assertRaises(NotImplementedError, + lambda: topology("hello world")) + self.assertRaises(NotImplementedError, lambda: topology([])) + self.assertRaises(TypeError, lambda: topology(int)) + # Some topologies missing methods, wrong arity, etc. + + class nt0(object): + pass + self.assertRaises(NotImplementedError, lambda: topology(nt0())) + + class nt1(object): + + get_connections = 45 + push_back = 45 + self.assertRaises(NotImplementedError, lambda: topology(nt1())) + + # The minimal good citizen. + glob = [] + + class t(object): + + def __init__(self, g): + self.g = g + + def push_back(self): + self.g.append(1) + return 1 + + def get_connections(self, n): + self.g.append(2) + return [[], []] + + t_inst = t(glob) + topo = topology(t_inst) + + with self.assertRaises(OverflowError) as cm: + topo.push_back(n=-1) + + # Test the keyword arg. + topo = topology(udt=ring()) + topo = topology(udt=t_inst) + + # Check a few topo properties. + self.assertEqual(topo.get_extra_info(), "") + self.assertTrue(topo.extract(int) is None) + self.assertTrue(topo.extract(ring) is None) + self.assertFalse(topo.extract(t) is None) + self.assertTrue(topo.is_(t)) + self.assertTrue(isinstance(topo.get_connections(0), tuple)) + self.assertTrue(isinstance(topo.get_connections(0)[0], ndarray)) + self.assertTrue(isinstance(topo.get_connections(0)[1], ndarray)) + self.assertTrue(topo.get_connections(0)[1].dtype == dtype(float)) + # Assert that t_inst was deep-copied into topo: + # the instance in topo will have its own copy of glob + # and it will not be a reference the outside object. + self.assertEqual(len(glob), 0) + self.assertEqual(len(topo.extract(t).g), 4) + self.assertEqual(topo.extract(t).g, [2]*4) + self.assertTrue(topo.push_back() is None) + self.assertEqual(topo.extract(t).g, [2]*4 + [1]) + + topo = topology(ring()) + self.assertTrue(topo.get_extra_info() != "") + self.assertTrue(topo.extract(int) is None) + self.assertTrue(topo.extract(t) is None) + self.assertFalse(topo.extract(ring) is None) + self.assertTrue(topo.is_(ring)) + self.assertTrue(isinstance(topo.push_back(), type(None))) + + # Wrong retval for get_connections(). + + class t(object): + + def push_back(self): + pass + + def get_connections(self, n): + return [] + topo = topology(t()) + self.assertRaises(RuntimeError, lambda: topo.get_connections(0)) + + class t(object): + + def push_back(self): + pass + + def get_connections(self, n): + return [1] + topo = topology(t()) + self.assertRaises(RuntimeError, lambda: topo.get_connections(0)) + + class t(object): + + def push_back(self): + pass + + def get_connections(self, n): + return [1, 2, 3] + topo = topology(t()) + self.assertRaises(RuntimeError, lambda: topo.get_connections(0)) + + class t(object): + + def push_back(self): + pass + + def get_connections(self, n): + return [[1, 2, 3], [.5]] + topo = topology(t()) + with self.assertRaises(ValueError) as cm: + topo.get_connections(0) + err = cm.exception + self.assertTrue( + "while the vector of migration probabilities has a size of" in str(err)) + + class t(object): + + def push_back(self): + pass + + def get_connections(self, n): + return [[1, 2, 3], [.5, .6, 1.4]] + topo = topology(t()) + with self.assertRaises(ValueError) as cm: + topo.get_connections(0) + err = cm.exception + self.assertTrue( + "An invalid migration probability of " in str(err)) + + class t(object): + + def push_back(self): + pass + + def get_connections(self, n): + return [[1, 2, 3], [.5, .6, float("inf")]] + topo = topology(t()) + with self.assertRaises(ValueError) as cm: + topo.get_connections(0) + err = cm.exception + self.assertTrue( + "An invalid non-finite migration probability of " in str(err)) + + # Test that construction from another pygmo.topology fails. + with self.assertRaises(TypeError) as cm: + topology(topo) + err = cm.exception + self.assertTrue( + "a pygmo.topology cannot be used as a UDT for another pygmo.topology (if you need to copy a topology please use the standard Python copy()/deepcopy() functions)" in str(err)) + + def run_extract_tests(self): + from .core import topology, _test_topology, ring + import sys + + # First we try with a C++ test topo. + t = topology(_test_topology()) + # Verify the refcount of p is increased after extract(). + rc = sys.getrefcount(t) + ttopo = t.extract(_test_topology) + self.assertEqual(sys.getrefcount(t), rc + 1) + del ttopo + self.assertEqual(sys.getrefcount(t), rc) + # Verify we are modifying the inner object. + t.extract(_test_topology).set_n(5) + self.assertEqual(t.extract(_test_topology).get_n(), 5) + + class ttopology(object): + + def __init__(self): + self._n = 1 + + def get_n(self): + return self._n + + def set_n(self, n): + self._n = n + + def get_connections(self, n): + return [[], []] + + def push_back(self): + pass + + # Test with Python topology. + t = topology(ttopology()) + rc = sys.getrefcount(t) + ttopo = t.extract(ttopology) + # Reference count does not increase because + # ttopology is stored as a proper Python object + # with its own refcount. + self.assertTrue(sys.getrefcount(t) == rc) + self.assertTrue(ttopo.get_n() == 1) + ttopo.set_n(12) + self.assert_(t.extract(ttopology).get_n() == 12) + + # Check that we can extract Python UDTs also via Python's object type. + t = topology(ttopology()) + self.assertTrue(not t.extract(object) is None) + # Check we are referring to the same object. + self.assertEqual(id(t.extract(object)), id(t.extract(ttopology))) + # Check that it will not work with exposed C++ topologies. + t = topology(ring()) + self.assertTrue(t.extract(object) is None) + self.assertTrue(not t.extract(ring) is None) + + def run_name_info_tests(self): + from .core import topology + + class t(object): + + def get_connections(self, n): + return [[], []] + + def push_back(self): + pass + + topo = topology(t()) + self.assertTrue(topo.get_name() != '') + self.assertTrue(topo.get_extra_info() == '') + + class t(object): + + def get_connections(self, n): + return [[], []] + + def push_back(self): + pass + + def get_name(self): + return 'pippo' + + topo = topology(t()) + self.assertTrue(topo.get_name() == 'pippo') + self.assertTrue(topo.get_extra_info() == '') + + class t(object): + + def get_connections(self, n): + return [[], []] + + def push_back(self): + pass + + def get_extra_info(self): + return 'pluto' + + topo = topology(t()) + self.assertTrue(topo.get_name() != '') + self.assertTrue(topo.get_extra_info() == 'pluto') + + class t(object): + + def get_connections(self, n): + return [[], []] + + def push_back(self): + pass + + def get_name(self): + return 'pippo' + + def get_extra_info(self): + return 'pluto' + + topo = topology(t()) + self.assertTrue(topo.get_name() == 'pippo') + self.assertTrue(topo.get_extra_info() == 'pluto') + + def run_pickle_tests(self): + from .core import topology, ring + from pickle import dumps, loads + t_ = topology(ring()) + t = loads(dumps(t_)) + self.assertEqual(repr(t), repr(t_)) + self.assertTrue(t.is_(ring)) + + t_ = topology(_topo()) + t = loads(dumps(t_)) + self.assertEqual(repr(t), repr(t_)) + self.assertTrue(t.is_(_topo)) diff --git a/pygmo/common_utils.hpp b/pygmo/common_utils.hpp index 5f1964ef2..d953e7137 100644 --- a/pygmo/common_utils.hpp +++ b/pygmo/common_utils.hpp @@ -168,10 +168,14 @@ inline bp::object callable_attribute(const bp::object &o, const char *s) } // Convert a vector of arithmetic types into a 1D numpy array. -template -using v_to_a_enabler = pagmo::enable_if_t::value, int>; - -template = 0> +// NOTE: provide two overloads, one for unsigned integral types and the +// other for the other arithmetic types. The idea is that we may not want +// to produce numpy arrays of unsigned integrals, as they are more painful +// to deal with in Python than just plain signed integrals. +template , + pagmo::detail::conjunction, std::is_signed>>::value, + int> = 0> inline bp::object v_to_a(const std::vector &v) { // The dimensions of the array to be created. @@ -191,11 +195,42 @@ inline bp::object v_to_a(const std::vector &v) return retval; } -// Convert a vector of vectors of arithmetic types into a 2D numpy array. -template -using vv_to_a_enabler = pagmo::enable_if_t::value, int>; +template , std::is_unsigned>::value, int> = 0> +inline bp::object v_to_a(const std::vector &v, bool keep_unsigned = false) +{ + // The dimensions of the array to be created. + npy_intp dims[] = {boost::numeric_cast(v.size())}; + // Attempt creating the array. The ndtype will be the signed counterpart of T, + // if keep_unsigned is false. + using int_type = typename std::make_signed::type; + PyObject *ret = PyArray_SimpleNew(1, dims, keep_unsigned ? cpp_npy::value : cpp_npy::value); + if (!ret) { + pygmo_throw(PyExc_RuntimeError, "couldn't create a NumPy array: the 'PyArray_SimpleNew()' function failed"); + } + // Hand over to BP for exception-safe behaviour. + bp::object retval{bp::handle<>(ret)}; + if (v.size()) { + if (keep_unsigned) { + // Copy over the data. + std::copy(v.begin(), v.end(), static_cast(PyArray_DATA(reinterpret_cast(ret)))); + } else { + // Copy over the data, transforming from unsigned to signed on the fly. + std::transform(v.begin(), v.end(), + static_cast(PyArray_DATA(reinterpret_cast(ret))), + [](const T &n) { return boost::numeric_cast(n); }); + } + } + // Hand over to boost python. + return retval; +} -template = 0> +// Convert a vector of vectors of arithmetic types into a 2D numpy array. +// NOTE: same scheme as above. +template , + pagmo::detail::conjunction, std::is_signed>>::value, + int> = 0> inline bp::object vv_to_a(const std::vector> &v) { // The dimensions of the array to be created. @@ -223,6 +258,49 @@ inline bp::object vv_to_a(const std::vector> &v) return retval; } +template , std::is_unsigned>::value, int> = 0> +inline bp::object vv_to_a(const std::vector> &v, bool keep_unsigned = false) +{ + // The dimensions of the array to be created. + const auto nrows = v.size(); + const auto ncols = nrows ? v[0].size() : 0u; + npy_intp dims[] = {boost::numeric_cast(nrows), boost::numeric_cast(ncols)}; + // Attempt creating the array. The ndtype will be the signed counterpart of T, + // if keep_unsigned is false. + using int_type = typename std::make_signed::type; + PyObject *ret = PyArray_SimpleNew(2, dims, keep_unsigned ? cpp_npy::value : cpp_npy::value); + if (!ret) { + pygmo_throw(PyExc_RuntimeError, "couldn't create a NumPy array: the 'PyArray_SimpleNew()' function failed"); + } + // Hand over to BP for exception-safe behaviour. + bp::object retval{bp::handle<>(ret)}; + if (nrows) { + if (keep_unsigned) { + auto data = static_cast(PyArray_DATA(reinterpret_cast(ret))); + for (const auto &i : v) { + if (i.size() != ncols) { + pygmo_throw(PyExc_ValueError, "cannot convert a vector of vectors to a NumPy 2D array " + "if the vector instances don't have all the same size"); + } + std::copy(i.begin(), i.end(), data); + data += ncols; + } + } else { + auto data = static_cast(PyArray_DATA(reinterpret_cast(ret))); + for (const auto &i : v) { + if (i.size() != ncols) { + pygmo_throw(PyExc_ValueError, "cannot convert a vector of vectors to a NumPy 2D array " + "if the vector instances don't have all the same size"); + } + std::transform(i.begin(), i.end(), data, [](const T &n) { return boost::numeric_cast(n); }); + data += ncols; + } + } + return retval; + } +} + // isinstance wrapper. inline bool isinstance(const bp::object &o, const bp::object &t) { @@ -347,52 +425,65 @@ inline std::vector to_vvd(const bp::object &o) .c_str()); } -// Convert a numpy array to an std::vector of unsigned integral type. -template +// Convert a 1D numpy array of input integral type SourceInt to an std::vector of +// unsigned integral type UInt. +template inline std::vector a_to_vuint(PyArrayObject *o) { static_assert(std::is_integral::value && std::is_unsigned::value, - "This function must be used only with unsigned integral types."); + "The UInt type must be an unsigned integral type."); + + static_assert(std::is_integral::value, "The SourceInt type must be an integral type."); using size_type = typename std::vector::size_type; - using int_type = std::make_signed::type; if (!PyArray_ISCARRAY_RO(o)) { - pygmo_throw(PyExc_RuntimeError, "cannot convert NumPy array to a vector of unsigned integrals: " + pygmo_throw(PyExc_RuntimeError, "cannot convert a NumPy array to a vector of unsigned integrals: " "data must be C-style contiguous, aligned, and in machine byte-order"); } if (PyArray_NDIM(o) != 1) { - pygmo_throw(PyExc_ValueError, "cannot convert NumPy array to a vector of unsigned integrals: " + pygmo_throw(PyExc_ValueError, "cannot convert a NumPy array to a vector of unsigned integrals: " "the array must be unidimensional"); } - if (PyArray_TYPE(o) != cpp_npy::value) { - pygmo_throw(PyExc_TypeError, "cannot convert NumPy array to a vector of unsigned integrals: " - "invalid scalar type"); + if (PyArray_TYPE(o) != cpp_npy::value) { + pygmo_throw(PyExc_TypeError, "cannot convert a NumPy array to a vector of unsigned integrals: " + "the input scalar type is inconsistent with the expected integral type"); } - if (PyArray_STRIDES(o)[0] != sizeof(int_type)) { - pygmo_throw(PyExc_RuntimeError, ("cannot convert NumPy array to a vector of unsigned integrals: " + if (PyArray_STRIDES(o)[0] != sizeof(SourceInt)) { + pygmo_throw(PyExc_RuntimeError, ("cannot convert a NumPy array to a vector of unsigned integrals: " "the stride value must be " - + std::to_string(sizeof(int_type))) + + std::to_string(sizeof(SourceInt))) .c_str()); } - if (PyArray_ITEMSIZE(o) != sizeof(int_type)) { - pygmo_throw(PyExc_RuntimeError, ("cannot convert NumPy array to a vector of unsigned integrals: " + if (PyArray_ITEMSIZE(o) != sizeof(SourceInt)) { + pygmo_throw(PyExc_RuntimeError, ("cannot convert a NumPy array to a vector of unsigned integrals: " "the size of the scalar type must be " - + std::to_string(sizeof(int_type))) + + std::to_string(sizeof(SourceInt))) .c_str()); } const auto size = boost::numeric_cast(PyArray_SHAPE(o)[0]); std::vector retval; + retval.resize(boost::numeric_cast(size)); + if (size) { - auto data = static_cast(PyArray_DATA(o)); - std::transform(data, data + size, std::back_inserter(retval), - [](int_type n) { return boost::numeric_cast(n); }); + auto data = static_cast(PyArray_DATA(o)); + std::transform(data, data + size, retval.data(), [](SourceInt n) { return boost::numeric_cast(n); }); } + return retval; } // Convert an arbitrary python object to a vector of unsigned integrals. +// This function expects in input either a list of something convertible +// to UInt, or a NumPy array of *signed* integrals. The point of expecting +// signed integrals is that, except in rare occasions, we just want to +// deal with signed integral types on the Python side and convert back +// to unsigned only when crossing over to C++. This is much more natural +// for the user, who does not have to deal with the creation of NumPy +// arrays with a non-default dtype, etc. The only exception to this +// rule is when we are dealing with individual IDs, which must be kept +// unsigned. template inline std::vector to_vuint(const bp::object &o) { @@ -406,8 +497,13 @@ inline std::vector to_vuint(const bp::object &o) bp::stl_input_iterator begin(o), end; return std::vector(begin, end); } else if (isinstance(o, a)) { - // NOTE: as usual, we try first to create an array of signed ints, - // and we convert to unsigned in a_to_vuint(). + // NOTE: the idea here is that we expect in input a numpy array of + // the "natural sized" signed int type for the platform, which we + // heuristically assume to be the signed counterpart of size_t. The idea + // is that we basically want the users to use signed integral arrays + // when talking to pygmo, because they are the easiest to work with + // and I don't see much benefit in making the distinction between signed/unsigned + // in Python. We can rework this approach if it turns out to be too restrictive. using int_type = std::make_signed::type; auto n = PyArray_FROM_OTF(o.ptr(), cpp_npy::value, NPY_ARRAY_IN_ARRAY); @@ -415,7 +511,7 @@ inline std::vector to_vuint(const bp::object &o) bp::throw_error_already_set(); } - return a_to_vuint(reinterpret_cast(bp::object(bp::handle<>(n)).ptr())); + return a_to_vuint(reinterpret_cast(bp::object(bp::handle<>(n)).ptr())); } pygmo_throw(PyExc_TypeError, ("cannot convert the type '" + str(type(o)) @@ -536,8 +632,8 @@ inline pagmo::sparsity_pattern to_sp(const bp::object &o) // vector_double::size_type (most likely long or long long) from whatever type of NumPy array was passed as // input, and then we will convert the elements to the appropriate size_type inside the a_to_sp routine. The // reason for doing this is that in typical usage Python integers are converted to signed integers when used - // inside NumPy arrays, so we want to work with signed ints here as well in order no to force the user to create - // sparsity patterns like array(...,dtype='ulonglong'). + // inside NumPy arrays, so we want to work with signed ints here as well in order no to force the user to + // create sparsity patterns like array(...,dtype='ulonglong'). auto n = PyArray_FROM_OTF(o.ptr(), cpp_npy::type>::value, NPY_ARRAY_IN_ARRAY); if (!n) { // NOTE: PyArray_FROM_OTF already sets the exception at the Python level with an appropriate message, @@ -745,11 +841,11 @@ inline auto lcast(T func) -> decltype(+func) // NOTE: these are alternative implementations of BP's add_property() functionality for classes. // The reason they exist (and why they should be used instead of the BP implementation) is because -// we are running into a nasty crash on MinGW upon module import that I did not manage to debug fully, but which seems -// to be somehow related to BP's add_property() (at least judging from the limited stacktrace -// I could obtain on windows). These alternative wrappers seem to sidestep the issue, at least so far. -// They can be used exactly like BP's add_property(), the only difference being that they are functions -// rather than methods, and they thus require the BP class to be passed in as first argument. +// we are running into a nasty crash on MinGW upon module import that I did not manage to debug fully, but which +// seems to be somehow related to BP's add_property() (at least judging from the limited stacktrace I could obtain +// on windows). These alternative wrappers seem to sidestep the issue, at least so far. They can be used exactly +// like BP's add_property(), the only difference being that they are functions rather than methods, and they thus +// require the BP class to be passed in as first argument. template inline void add_property(bp::class_ &c, const char *name, const bp::object &getter, const char *doc = "") { @@ -819,6 +915,94 @@ inline void import_aps(const bp::list &l) } } +// Convert an individuals_group_t into a Python tuple of: +// - 1D integral array of IDs, +// - 2D float array of dvs, +// - 2D float array of fvs. +inline bp::tuple inds_to_tuple(const pagmo::individuals_group_t &inds) +{ + // Do the IDs. + // NOTE: as usual, keep the IDs as unsigned values. + auto ID_arr = v_to_a(std::get<0>(inds), true); + + // Decision vectors. + auto dv_arr = vv_to_a(std::get<1>(inds)); + + // Fitness vectors. + auto fv_arr = vv_to_a(std::get<2>(inds)); + + return bp::make_tuple(ID_arr, dv_arr, fv_arr); +} + +// Convert a generic Python object into a vector of individual IDs. +// NOTE: this is very similar to to_vuint(), but with the difference +// that we want to keep the unsignedness of the IDs. Perhaps +// in the future we can refactor these functions to have less code +// duplication. +inline std::vector to_ID_vector(const bp::object &o) +{ + bp::object l = builtin().attr("list"); + bp::object a = bp::import("numpy").attr("ndarray"); + + if (isinstance(o, l)) { + bp::stl_input_iterator begin(o), end; + return std::vector(begin, end); + } else if (isinstance(o, a)) { + auto n = PyArray_FROM_OTF(o.ptr(), cpp_npy::value, NPY_ARRAY_IN_ARRAY); + if (!n) { + bp::throw_error_already_set(); + } + + return a_to_vuint( + reinterpret_cast(bp::object(bp::handle<>(n)).ptr())); + } + pygmo_throw(PyExc_TypeError, ("cannot convert the type '" + str(type(o)) + + "' to a vector of individual IDs: only lists of unsigned ints and NumPy arrays of " + "unsigned ints are supported") + .c_str()); +} + +// Convert a generic Python object into an individuals_group_t. +inline pagmo::individuals_group_t to_inds(const bp::object &o) +{ + // We assume we receive in input something we can iterate over. + bp::stl_input_iterator begin(o), end; + + if (begin == end) { + // Empty iteratable. + pygmo_throw(PyExc_ValueError, "cannot convert an empty iteratable into a pagmo::individuals_group_t"); + } + + // Try fetching the IDs. + auto ID_vec = to_ID_vector(*begin); + + if (++begin == end) { + // Iteratable with only 1 element. + pygmo_throw(PyExc_ValueError, "cannot convert an iteratable with only 1 element into a " + "pagmo::individuals_group_t (exactly 3 elements are needed)"); + } + + // Try fetching the decision vectors. + auto dvs_vec = to_vvd(*begin); + + if (++begin == end) { + // Iteratable with only 2 elements. + pygmo_throw(PyExc_ValueError, "cannot convert an iteratable with only 2 elements into a " + "pagmo::individuals_group_t (exactly 3 elements are needed)"); + } + + // Try fetching the fitness vectors. + auto fvs_vec = to_vvd(*begin); + + if (++begin != end) { + // Iteratable with too many elements. + pygmo_throw(PyExc_ValueError, "cannot convert an iteratable with more than 3 elements into a " + "pagmo::individuals_group_t (exactly 3 elements are needed)"); + } + + return pagmo::individuals_group_t(std::move(ID_vec), std::move(dvs_vec), std::move(fvs_vec)); +} + } // namespace pygmo #endif diff --git a/pygmo/core.cpp b/pygmo/core.cpp index 17a230ec5..492d0addb 100644 --- a/pygmo/core.cpp +++ b/pygmo/core.cpp @@ -44,6 +44,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include #include @@ -86,9 +87,12 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include +#include #include +#include #include #include #include @@ -110,10 +114,16 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include +#include +#include #include #include #include #include +#include +#include +#include namespace bp = boost::python; using namespace pagmo; @@ -133,6 +143,15 @@ std::unique_ptr> island_ptr; // Exposed pagmo::bfe. std::unique_ptr> bfe_ptr; +// Exposed pagmo::topology. +std::unique_ptr> topology_ptr; + +// Exposed pagmo::r_policy. +std::unique_ptr> r_policy_ptr; + +// Exposed pagmo::s_policy. +std::unique_ptr> s_policy_ptr; + namespace detail { @@ -178,6 +197,12 @@ void cleanup() island_ptr.reset(); bfe_ptr.reset(); + + topology_ptr.reset(); + + r_policy_ptr.reset(); + + s_policy_ptr.reset(); } // Serialization support for the population class. @@ -419,6 +444,16 @@ BOOST_PYTHON_MODULE(core) .value("idle_error", evolve_status::idle_error) .value("busy_error", evolve_status::busy_error); + // Migration type enum. + bp::enum_("_migration_type") + .value("p2p", migration_type::p2p) + .value("broadcast", migration_type::broadcast); + + // Migrant handling policy enum. + bp::enum_("_migrant_handling") + .value("preserve", migrant_handling::preserve) + .value("evict", migrant_handling::evict); + // Expose utility functions for testing purposes. bp::def("_builtin", &pygmo::builtin); bp::def("_type", &pygmo::type); @@ -476,11 +511,41 @@ BOOST_PYTHON_MODULE(core) auto batch_evaluators_module = bp::object(bp::handle<>(bp::borrowed(batch_evaluators_module_ptr))); bp::scope().attr("batch_evaluators") = batch_evaluators_module; + // Create the topologies submodule. + std::string topologies_module_name = bp::extract(bp::scope().attr("__name__") + ".topologies"); + PyObject *topologies_module_ptr = PyImport_AddModule(topologies_module_name.c_str()); + if (!topologies_module_ptr) { + pygmo_throw(PyExc_RuntimeError, "error while creating the 'topologies' submodule"); + } + auto topologies_module = bp::object(bp::handle<>(bp::borrowed(topologies_module_ptr))); + bp::scope().attr("topologies") = topologies_module; + + // Create the r_policies submodule. + std::string r_policies_module_name = bp::extract(bp::scope().attr("__name__") + ".r_policies"); + PyObject *r_policies_module_ptr = PyImport_AddModule(r_policies_module_name.c_str()); + if (!r_policies_module_ptr) { + pygmo_throw(PyExc_RuntimeError, "error while creating the 'r_policies' submodule"); + } + auto r_policies_module = bp::object(bp::handle<>(bp::borrowed(r_policies_module_ptr))); + bp::scope().attr("r_policies") = r_policies_module; + + // Create the s_policies submodule. + std::string s_policies_module_name = bp::extract(bp::scope().attr("__name__") + ".s_policies"); + PyObject *s_policies_module_ptr = PyImport_AddModule(s_policies_module_name.c_str()); + if (!s_policies_module_ptr) { + pygmo_throw(PyExc_RuntimeError, "error while creating the 's_policies' submodule"); + } + auto s_policies_module = bp::object(bp::handle<>(bp::borrowed(s_policies_module_ptr))); + bp::scope().attr("s_policies") = s_policies_module; + // Store the pointers to the classes that can be extended by APs. bp::scope().attr("_problem_address") = reinterpret_cast(&pygmo::problem_ptr); bp::scope().attr("_algorithm_address") = reinterpret_cast(&pygmo::algorithm_ptr); bp::scope().attr("_island_address") = reinterpret_cast(&pygmo::island_ptr); bp::scope().attr("_bfe_address") = reinterpret_cast(&pygmo::bfe_ptr); + bp::scope().attr("_topology_address") = reinterpret_cast(&pygmo::topology_ptr); + bp::scope().attr("_r_policy_address") = reinterpret_cast(&pygmo::r_policy_ptr); + bp::scope().attr("_s_policy_address") = reinterpret_cast(&pygmo::s_policy_ptr); // Store the address to the list of registered APs. bp::scope().attr("_ap_set_address") = reinterpret_cast(&pygmo::detail::ap_set); @@ -536,7 +601,7 @@ BOOST_PYTHON_MODULE(core) pygmo::population_get_f_docstring().c_str()) .def("get_x", lcast([](const population &pop) { return pygmo::vv_to_a(pop.get_x()); }), pygmo::population_get_x_docstring().c_str()) - .def("get_ID", lcast([](const population &pop) { return pygmo::v_to_a(pop.get_ID()); }), + .def("get_ID", lcast([](const population &pop) { return pygmo::v_to_a(pop.get_ID(), true); }), pygmo::population_get_ID_docstring().c_str()) .def("get_seed", &population::get_seed, pygmo::population_get_seed_docstring().c_str()); pygmo::add_property(pop_class, "champion_x", @@ -879,8 +944,8 @@ BOOST_PYTHON_MODULE(core) pygmo::island_ptr = detail::make_unique>("island", pygmo::island_docstring().c_str(), bp::init<>()); auto &island_class = pygmo::get_island_class(); - island_class.def(bp::init()) - .def(bp::init()) + island_class.def(bp::init()) + .def(bp::init()) .def(repr(bp::self)) .def_pickle(pygmo::island_pickle_suite()) // Copy and deepcopy. @@ -898,7 +963,9 @@ BOOST_PYTHON_MODULE(core) bp::arg("pop")) .def("set_algorithm", &island::set_algorithm, pygmo::island_set_algorithm_docstring().c_str(), bp::arg("algo")) .def("get_name", &island::get_name, pygmo::island_get_name_docstring().c_str()) - .def("get_extra_info", &island::get_extra_info, pygmo::island_get_extra_info_docstring().c_str()); + .def("get_extra_info", &island::get_extra_info, pygmo::island_get_extra_info_docstring().c_str()) + .def("get_r_policy", &island::get_r_policy, pygmo::island_get_r_policy_docstring().c_str()) + .def("get_s_policy", &island::get_s_policy, pygmo::island_get_s_policy_docstring().c_str()); pygmo::add_property(island_class, "status", &island::status, pygmo::island_status_docstring().c_str()); // Expose islands. @@ -906,7 +973,8 @@ BOOST_PYTHON_MODULE(core) // Archi. bp::class_ archi_class("archipelago", pygmo::archipelago_docstring().c_str(), bp::init<>()); - archi_class.def(repr(bp::self)) + archi_class.def(bp::init()) + .def(repr(bp::self)) .def_pickle(pygmo::detail::archipelago_pickle_suite()) // Copy and deepcopy. .def("__copy__", &pygmo::generic_copy_wrapper) @@ -939,7 +1007,36 @@ BOOST_PYTHON_MODULE(core) } return retval; }), - pygmo::archipelago_get_champions_x_docstring().c_str()); + pygmo::archipelago_get_champions_x_docstring().c_str()) + .def("get_migrants_db", lcast([](const archipelago &archi) -> bp::list { + bp::list retval; + const auto tmp = archi.get_migrants_db(); + for (const auto &ig : tmp) { + retval.append(pygmo::inds_to_tuple(ig)); + } + return retval; + }), + pygmo::archipelago_get_migrants_db_docstring().c_str()) + .def("get_migration_log", lcast([](const archipelago &archi) -> bp::list { + bp::list retval; + const auto tmp = archi.get_migration_log(); + for (const auto &le : tmp) { + retval.append(bp::make_tuple(std::get<0>(le), std::get<1>(le), pygmo::v_to_a(std::get<2>(le)), + pygmo::v_to_a(std::get<3>(le)), std::get<4>(le), std::get<5>(le))); + } + return retval; + }), + pygmo::archipelago_get_migration_log_docstring().c_str()) + .def("get_topology", &archipelago::get_topology, pygmo::archipelago_get_topology_docstring().c_str()) + .def("_set_topology", &archipelago::set_topology) + .def("set_migration_type", &archipelago::set_migration_type, + pygmo::archipelago_set_migration_type_docstring().c_str(), (bp::arg("mt"))) + .def("set_migrant_handling", &archipelago::set_migrant_handling, + pygmo::archipelago_set_migrant_handling_docstring().c_str(), (bp::arg("mh"))) + .def("get_migration_type", &archipelago::get_migration_type, + pygmo::archipelago_get_migration_type_docstring().c_str()) + .def("get_migrant_handling", &archipelago::get_migrant_handling, + pygmo::archipelago_get_migrant_handling_docstring().c_str()); pygmo::add_property(archi_class, "status", &archipelago::status, pygmo::archipelago_status_docstring().c_str()); // Bfe class. @@ -964,4 +1061,91 @@ BOOST_PYTHON_MODULE(core) // Expose bfes. pygmo::expose_bfes(); + + // Topology class. + pygmo::topology_ptr + = detail::make_unique>("topology", pygmo::topology_docstring().c_str(), bp::init<>()); + auto &topology_class = pygmo::get_topology_class(); + topology_class.def(bp::init((bp::arg("udt")))) + .def(repr(bp::self)) + .def_pickle(pygmo::topology_pickle_suite()) + // Copy and deepcopy. + .def("__copy__", &pygmo::generic_copy_wrapper) + .def("__deepcopy__", &pygmo::generic_deepcopy_wrapper) + // UDT extraction. + .def("_py_extract", &pygmo::generic_py_extract) + // Topology methods. + .def("get_connections", lcast([](const topology &t, std::size_t n) -> bp::tuple { + auto ret = t.get_connections(n); + return bp::make_tuple(pygmo::v_to_a(ret.first), pygmo::v_to_a(ret.second)); + }), + pygmo::topology_get_connections_docstring().c_str(), (bp::arg("prob"), bp::arg("dvs"))) + .def("push_back", lcast([](topology &t, unsigned n) { t.push_back(n); }), + pygmo::topology_push_back_docstring().c_str(), (bp::arg("n") = std::size_t(1))) + .def("get_name", &topology::get_name, pygmo::topology_get_name_docstring().c_str()) + .def("get_extra_info", &topology::get_extra_info, pygmo::topology_get_extra_info_docstring().c_str()); + + // Expose topologies. + pygmo::expose_topologies(); + + // Replacement policy class. + pygmo::r_policy_ptr + = detail::make_unique>("r_policy", pygmo::r_policy_docstring().c_str(), bp::init<>()); + auto &r_policy_class = pygmo::get_r_policy_class(); + r_policy_class.def(bp::init((bp::arg("udrp")))) + .def(repr(bp::self)) + .def_pickle(pygmo::r_policy_pickle_suite()) + // Copy and deepcopy. + .def("__copy__", &pygmo::generic_copy_wrapper) + .def("__deepcopy__", &pygmo::generic_deepcopy_wrapper) + // UDRP extraction. + .def("_py_extract", &pygmo::generic_py_extract) + // r_policy methods. + .def("replace", + lcast([](const r_policy &r, const bp::object &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, const bp::object &tol, + const bp::object &mig) -> bp::tuple { + auto ret + = r.replace(pygmo::to_inds(inds), nx, nix, nobj, nec, nic, pygmo::to_vd(tol), pygmo::to_inds(mig)); + return pygmo::inds_to_tuple(ret); + }), + pygmo::r_policy_replace_docstring().c_str(), + (bp::arg("inds"), bp::arg("nx"), bp::arg("nix"), bp::arg("nobj"), bp::arg("nec"), bp::arg("nic"), + bp::arg("tol"), bp::arg("mig"))) + .def("get_name", &r_policy::get_name, pygmo::r_policy_get_name_docstring().c_str()) + .def("get_extra_info", &r_policy::get_extra_info, pygmo::r_policy_get_extra_info_docstring().c_str()); + + // Expose r_policies. + pygmo::expose_r_policies(); + + // Selection policy class. + pygmo::s_policy_ptr + = detail::make_unique>("s_policy", pygmo::s_policy_docstring().c_str(), bp::init<>()); + auto &s_policy_class = pygmo::get_s_policy_class(); + s_policy_class.def(bp::init((bp::arg("udsp")))) + .def(repr(bp::self)) + .def_pickle(pygmo::s_policy_pickle_suite()) + // Copy and deepcopy. + .def("__copy__", &pygmo::generic_copy_wrapper) + .def("__deepcopy__", &pygmo::generic_deepcopy_wrapper) + // UDSP extraction. + .def("_py_extract", &pygmo::generic_py_extract) + // s_policy methods. + .def("select", + lcast([](const s_policy &s, const bp::object &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const bp::object &tol) -> bp::tuple { + auto ret = s.select(pygmo::to_inds(inds), nx, nix, nobj, nec, nic, pygmo::to_vd(tol)); + return pygmo::inds_to_tuple(ret); + }), + pygmo::s_policy_select_docstring().c_str(), + (bp::arg("inds"), bp::arg("nx"), bp::arg("nix"), bp::arg("nobj"), bp::arg("nec"), bp::arg("nic"), + bp::arg("tol"))) + .def("get_name", &s_policy::get_name, pygmo::s_policy_get_name_docstring().c_str()) + .def("get_extra_info", &s_policy::get_extra_info, pygmo::s_policy_get_extra_info_docstring().c_str()); + + // Expose s_policies. + pygmo::expose_s_policies(); } diff --git a/pygmo/docstrings.cpp b/pygmo/docstrings.cpp index cf05ebe91..0f9cbcc55 100644 --- a/pygmo/docstrings.cpp +++ b/pygmo/docstrings.cpp @@ -85,7 +85,7 @@ std::string population_random_decision_vector_docstring() This method will create a random decision vector within the problem's bounds. Returns: - :class:`numpy.ndarray`: a random decision vector within the problem’s bounds + :class:`numpy.ndarray`: a random decision vector within the problem's bounds Raises: unspecified: any exception thrown by :func:`pygmo.random_decision_vector()` @@ -201,7 +201,7 @@ Sets simultaneously the :math:`i`-th individual decision vector and fitness thus The user must make sure that the input fitness *f* makes sense as pygmo will only check its dimension. Args: - i (``int``): individual’s index in the population + i (``int``): individual's index in the population x (array-like object): a decision vector (chromosome) f (array-like object): a fitness vector @@ -228,7 +228,7 @@ individual's ID remains the same. A call to this method triggers one fitness function evaluation. Args: - i (``int``): individual’s index in the population + i (``int``): individual's index in the population x (array-like object): a decision vector (chromosome) Raises: @@ -358,7 +358,7 @@ provides a generic interface to optimization problems. Every UDP must implement at least the following two methods: -.. code-block:: python +.. code-block:: def fitness(self, dv): ... @@ -378,7 +378,7 @@ The two mandatory methods above allow to define a single objective, deterministi optimization problem. In order to consider more complex cases, the UDP may implement one or more of the following methods: -.. code-block:: python +.. code-block:: def get_nobj(self): ... @@ -1203,8 +1203,6 @@ Problem's name. If the UDP provides a ``get_name()`` method, then this method will return the output of its ``get_name()`` method. Otherwise, an implementation-defined name based on the type of the UDP will be returned. -The ``get_name()`` method of the UDP must return a ``str``. - Returns: ``str``: the problem's name @@ -1220,8 +1218,6 @@ Problem's extra info. If the UDP provides a ``get_extra_info()`` method, then this method will return the output of its ``get_extra_info()`` method. Otherwise, an empty string will be returned. -The ``get_extra_info()`` method of the UDP must return a ``str``. - Returns: ``str``: extra info about the UDP @@ -1306,7 +1302,7 @@ defined and instantiated, a UDA can then be used to construct an instance of thi Every UDA must implement at least the following method: -.. code-block:: python +.. code-block:: def evolve(self, pop): ... @@ -1316,7 +1312,7 @@ a new population generated by the *evolution* (or *optimisation*) of the origina Additional optional methods can be implemented in a UDA: -.. code-block:: python +.. code-block:: def has_set_seed(self): ... @@ -1478,8 +1474,6 @@ Algorithm's name. If the UDA provides a ``get_name()`` method, then this method will return the output of its ``get_name()`` method. Otherwise, an implementation-defined name based on the type of the UDA will be returned. -The ``get_name()`` method of the UDA must return a ``str``. - Returns: ``str``: the algorithm's name @@ -1495,8 +1489,6 @@ Algorithm's extra info. If the UDA provides a ``get_extra_info()`` method, then this method will return the output of its ``get_extra_info()`` method. Otherwise, an empty string will be returned. -The ``get_extra_info()`` method of the UDA must return a ``str``. - Returns: ``str``: extra info about the UDA @@ -3243,7 +3235,7 @@ For both the continuous and discrete parts of the decision vector, if :math:`lb_ prob (:class:`~pygmo.problem`): the input problem Returns: - :class:`numpy.ndarray`: a random decision vector within the problem’s bounds + :class:`numpy.ndarray`: a random decision vector within the problem's bounds Raises: ValueError: if the problem's bounds are not finite or larger than an implementation-defined limit @@ -3276,7 +3268,7 @@ For both the continuous and discrete parts of the decision vectors, if :math:`lb n (int): the number of decision vectors that will be generated Returns: - :class:`numpy.ndarray`: a random decision vector within the problem’s bounds + :class:`numpy.ndarray`: a random decision vector within the problem's bounds Raises: OverflowError: in case of (unlikely) overflows @@ -3510,7 +3502,7 @@ IEEE Transactions on Evolutionary Computation 7.5 (2003): 503-515. Examples: >>> import pygmo as pg >>> pg.non_dominated_front_2d(points = [[0,5],[1,4],[2,3],[3,2],[4,1],[2,2]]) - array([0, 1, 5, 4], dtype=uint64) + array([0, 1, 5, 4]) )"; } @@ -3573,7 +3565,7 @@ Complexity is :math:`\mathcal{O}(M N^2)` where :math:`M` is the size of the obje >>> import pygmo as pg >>> pop = pg.population(prob = pg.dtlz(prob_id = 3, dim=10, fdim=4), size = 20) >>> pg.sort_population_mo(points = pop.get_f()) # doctest: +SKIP - array([ 4, 7, 14, 15, 16, 18, 9, 13, 5, 3, 6, 2, 12, 0, 1, 19, 17, 8, 10, 11], dtype=uint64) + array([ 4, 7, 14, 15, 16, 18, 9, 13, 5, 3, 6, 2, 12, 0, 1, 19, 17, 8, 10, 11]) )"; } @@ -3606,7 +3598,7 @@ non-dominated front containing individuals included in the best N. >>> import pygmo as pg >>> pop = pg.population(prob = pg.dtlz(prob_id = 3, dim=10, fdim=4), size = 20) >>> pg.select_best_N_mo(points = pop.get_f(), N = 13) # doctest: +SKIP - array([ 2, 3, 4, 5, 6, 7, 9, 12, 13, 14, 15, 16, 18], dtype=uint64) + array([ 2, 3, 4, 5, 6, 7, 9, 12, 13, 14, 15, 16, 18]) )"; } @@ -3763,7 +3755,7 @@ The following strict ordering is used: - :math:`f_1 \prec f_2` if :math:`f_1` is feasible and :math:`f_2` is not. - :math:`f_1 \prec f_2` if :math:`f_1` is they are both infeasible, but :math:`f_1` - violates less constraints than :math:`f_2`, or in case they both violate the same + violates fewer constraints than :math:`f_2`, or in case they both violate the same number of constraints, if the :math:`L_2` norm of the overall constraint violation is smaller. - :math:`f_1 \prec f_2` if both fitness vectors are feasible and the objective value @@ -3806,7 +3798,7 @@ The following strict ordering is used (same as the one used in :func:`pygmo.comp - :math:`f_1 \prec f_2` if :math:`f_1` is feasible and :math:`f_2` is not. - :math:`f_1 \prec f_2` if :math:`f_1` is they are both infeasible, but :math:`f_1` - violates less constraints than :math:`f_2`, or in case they both violate the same + violates fewer constraints than :math:`f_2`, or in case they both violate the same number of constraints, if the :math:`L_2` norm of the overall constraint violation is smaller. - :math:`f_1 \prec f_2` if both fitness vectors are feasible and the objective value @@ -4267,11 +4259,13 @@ std::string island_docstring() { return R"(Island class. -In the pygmo jargon, an island is a class that encapsulates three entities: +In the pygmo jargon, an island is a class that encapsulates the following entities: * a user-defined island (**UDI**), * an :class:`~pygmo.algorithm`, -* a :class:`~pygmo.population`. +* a :class:`~pygmo.population`, +* a replacement policy (of type :class:`~pygmo.r_policy`), +* a selection policy (of type :class:`~pygmo.s_policy`). Through the UDI, the island class manages the asynchronous evolution (or optimisation) of its :class:`~pygmo.population` via the algorithm's :func:`~pygmo.algorithm.evolve()` @@ -4285,11 +4279,16 @@ wait for pending evolutions to conclude by calling the :func:`~pygmo.island.wait :func:`~pygmo.island.wait_check()` methods. The status of ongoing evolutions in the island can be queried via the :attr:`~pygmo.island.status` attribute. +The replacement and selection policies are used when the island is part of an :class:`~pygmo.archipelago`. +They establish how individuals are selected and replaced from the island when migration across islands occurs within +the :class:`~pygmo.archipelago`. If the island is not part of an :class:`~pygmo.archipelago`, +the replacement and selection policies play no role. + Typically, pygmo users will employ an already-available UDI in conjunction with this class (see :ref:`here ` for a full list), but advanced users can implement their own UDI types. A user-defined island must implement the following method: -.. code-block:: python +.. code-block:: def run_evolve(self, algo, pop): ... @@ -4300,7 +4299,7 @@ is finished, it will return the algorithm used for the evolution and the evolved In addition to the mandatory ``run_evolve()`` method, a UDI may implement the following optional methods: -.. code-block:: python +.. code-block:: def get_name(self): ... @@ -4322,21 +4321,24 @@ thread safety. Specifically, ``run_evolve()`` is always called in a separate thr An island can be initialised in a variety of ways using keyword arguments: * if the arguments list is empty, a default :class:`~pygmo.island` is constructed, containing a - :class:`~pygmo.thread_island` UDI, a :class:`~pygmo.null_algorithm` algorithm and an empty - population with problem type :class:`~pygmo.null_problem`; -* if the arguments list contains *algo*, *pop* and, optionally, *udi*, then the constructor will initialise - an :class:`~pygmo.island` containing the specified algorithm, population and UDI. If the *udi* parameter - is not supplied, the UDI type is chosen according to a heuristic which depends on the platform, the - Python version and the supplied *algo* and *pop* parameters: + :class:`~pygmo.thread_island` UDI, a :class:`~pygmo.null_algorithm` algorithm, an empty + population with problem type :class:`~pygmo.null_problem`, and default-constructed + :class:`~pygmo.r_policy` and :class:`~pygmo.s_policy`; +* if the arguments list contains *algo*, *pop* and, optionally, *udi*, *r_pol* and *s_pol*, then the constructor will + initialise an :class:`~pygmo.island` containing the specified algorithm, population, UDI and replacement/selection + policies. If *r_pol* and/or *s_pol* are not supplied, the replacement/selection policies will be default-constructed. + If the *udi* parameter is not supplied, the UDI type is chosen according to a heuristic which depends + on the platform, the Python version and the supplied *algo* and *pop* parameters: * if *algo* and *pop*'s problem provide at least the :attr:`~pygmo.thread_safety.basic` thread safety guarantee, then :class:`~pygmo.thread_island` will be selected as UDI type; * otherwise, if the current platform is Windows or the Python version is at least 3.4, then :class:`~pygmo.mp_island` will be selected as UDI type, else :class:`~pygmo.ipyparallel_island` will be chosen; -* if the arguments list contains *algo*, *prob*, *size* and, optionally, *udi*, *b* and *seed*, then a :class:`~pygmo.population` - will be constructed from *prob*, *size*, *b* and *seed*, and the construction will then proceed in the same way detailed - above (i.e., *algo* and the newly-created population are used to initialise the island's algorithm and population, - and the UDI, if not specified, will be chosen according to the heuristic detailed above). +* if the arguments list contains *algo*, *prob*, *size* and, optionally, *udi*, *b*, *seed*, *r_pol* and *s_pol*, + then a :class:`~pygmo.population` will be constructed from *prob*, *size*, *b* and *seed*, and the construction will + then proceed in the same way detailed above (i.e., *algo* and the newly-created population are used to initialise the + island's algorithm and population, the UDI, if not specified, will be chosen according to the heuristic detailed above, + and the replacement/selection policies are given by *r_pol* and *s_pol*). If the keyword arguments list is invalid, a :exc:`KeyError` exception will be raised. @@ -4351,7 +4353,7 @@ std::string island_evolve_docstring() Launch evolution. -This method will evolve the island’s :class:`~pygmo.population` using the island’s :class:`~pygmo.algorithm`. +This method will evolve the island's :class:`~pygmo.population` using the island's :class:`~pygmo.algorithm`. The evolution happens asynchronously: a call to :func:`~pygmo.island.evolve()` will create an evolution task that will be pushed to a queue, and then return immediately. The tasks in the queue are consumed by a separate thread of execution managed by the :class:`~pygmo.island` object. Each task will invoke the ``run_evolve()`` method of the UDI *n* @@ -4359,18 +4361,33 @@ times consecutively to perform the actual evolution. The island's algorithm and end of each ``run_evolve()`` invocation. Exceptions raised inside the tasks are stored within the island object, and can be re-raised by calling :func:`~pygmo.island.wait_check()`. +If the island is part of an :class:`~pygmo.archipelago`, then migration of individuals to/from other +islands might occur. The migration algorithm consists of the following steps: + +* before invoking ``run_evolve()`` on the UDI, the island will ask the + archipelago if there are candidate incoming individuals from other islands + If so, the replacement policy is invoked and the current population of the island is updated with the migrants; +* ``run_evolve()`` is then invoked and the current population is evolved; +* after ``run_evolve()`` has concluded, individuals are selected in the + evolved population and copied into the migration database of the archipelago + for future migrations. + It is possible to call this method multiple times to enqueue multiple evolution tasks, which will be consumed in a FIFO (first-in first-out) fashion. The user may call :func:`~pygmo.island.wait()` or :func:`~pygmo.island.wait_check()` to block until all tasks have been completed, and to fetch exceptions raised during the execution of the tasks. The :attr:`~pygmo.island.status` attribute can be used to query the status of the asynchronous operations in the island. Args: - n (``int``): the number of times the ``run_evolve()`` method of the UDI will be called within the evolution task + n (int): the number of times the ``run_evolve()`` method of the UDI will be called within the evolution task + (this corresponds also to the number of times migration can happen, if the island belongs to an archipelago) Raises: + IndexError: if the island is part of an archipelago and during migration an invalid island index is used (this can + happen if the archipelago's topology is malformed) OverflowError: if *n* is negative or larger than an implementation-defined value - unspecified: any exception thrown by the underlying C++ method, or by failures at the intersection between C++ and - Python (e.g., type conversion errors, mismatched function signatures, etc.) + unspecified: any exception thrown by the public interface of :class:`~pygmo.archipelago`, the public interface of + the replacement/selection policies, the underlying C++ method, or by failures at the intersection between C++ and + Python (e.g., type conversion errors, mismatched function signatures, etc.) )"; } @@ -4513,10 +4530,8 @@ Otherwise, an implementation-defined name based on the type of the UDI will be r It is safe to call this method while the island is evolving. -The ``get_name()`` method of the UDI must return a ``str``. - Returns: - ``str``: the name of the UDI + str: the name of the UDI Raises: unspecified: any exception thrown by the ``get_name()`` method of the UDI @@ -4535,10 +4550,8 @@ method. Otherwise, an empty string will be returned. It is safe to call this method while the island is evolving. -The ``get_extra_info()`` method of the UDI must return a ``str``. - Returns: - ``str``: extra info about the UDI + str: extra info about the UDI Raises: unspecified: any exception thrown by the ``get_extra_info()`` method of the UDI @@ -4546,6 +4559,30 @@ The ``get_extra_info()`` method of the UDI must return a ``str``. )"; } +std::string island_get_r_policy_docstring() +{ + return R"(get_r_policy() + +Get the replacement policy. + +Returns: + :class:`~pygmo.r_policy`: a copy of the current replacement policy + +)"; +} + +std::string island_get_s_policy_docstring() +{ + return R"(get_s_policy() + +Get the selection policy. + +Returns: + :class:`~pygmo.s_policy`: a copy of the current selection policy + +)"; +} + std::string thread_island_docstring() { return R"(__init__() @@ -4570,14 +4607,20 @@ std::string archipelago_docstring() { return R"(Archipelago. -An archipelago is a collection of :class:`~pygmo.island` objects which provides a convenient way to perform -multiple optimisations in parallel. +An archipelago is a collection of :class:`~pygmo.island` objects connected by a +:class:`~pygmo.topology`. The islands in the archipelago can exchange individuals +(i.e., candidate solutions) via a process called *migration*. The individuals migrate +across the routes described by the topology, and the islands' replacement +and selection policies (see :class:`~pygmo.r_policy` and :class:`~pygmo.s_policy`) +establish how individuals are replaced in and selected from the islands' populations. -The interface of :class:`~pygmo.archipelago` mirrors partially the interface of :class:`~pygmo.island`: the -evolution is initiated by a call to :func:`~pygmo.archipelago.evolve()`, and at any time the user can query the +The interface of :class:`~pygmo.archipelago` mirrors partially the interface +of :class:`~pygmo.island`: the evolution is initiated by a call to :func:`~pygmo.archipelago.evolve()`, +and at any time the user can query the state of the archipelago and access its island members. The user can explicitly wait for pending evolutions -to conclude by calling the :func:`~pygmo.archipelago.wait()` and :func:`~pygmo.archipelago.wait_check()` methods. -The status of ongoing evolutions in the archipelago can be queried via the :attr:`~pygmo.archipelago.status` attribute. +to conclude by calling the :func:`~pygmo.archipelago.wait()` and :func:`~pygmo.archipelago.wait_check()` +methods. The status of ongoing evolutions in the archipelago can be queried via +:func:`~pygmo.archipelago.status()`. )"; } @@ -4710,6 +4753,113 @@ Get the decision vectors of the islands' champions. )"; } +std::string archipelago_get_migrants_db_docstring() +{ + return R"(get_migrants_db() + +During the evolution of an archipelago, islands will periodically +store the individuals selected for migration in a *migrant database*. +This is a :class:`list` of :class:`tuple` objects whose +size is equal to the number of islands in the archipelago, and which +contains the current candidate outgoing migrants for each island. + +The migrants tuples consist of 3 values each: + +* a 1D NumPy array of individual IDs (represented as 64-bit unsigned integrals), +* a 2D NumPy array of decision vectors (i.e., the decision vectors of each individual, + stored in row-major order), +* a 2D NumPy array of fitness vectors (i.e., the fitness vectors of each individual, + stored in row-major order). + +Returns: + list: a copy of the database of migrants + +Raises: + unspecified: any exception thrown by failures at the intersection between C++ and Python (e.g., type conversion errors, + mismatched function signatures, etc.) + +)"; +} + +std::string archipelago_get_migration_log_docstring() +{ + return R"(get_migration_log() + +Each time an individual migrates from an island (the source) to another +(the destination), an entry will be added to the migration log. +The entry is a :class:`tuple` of 6 elements containing: + +* a timestamp of the migration, +* the ID of the individual that migrated, +* the decision and fitness vectors of the individual that migrated, +* the indices of the source and destination islands. + +The migration log is a :class:`list` of migration entries. + +Returns: + list: a copy of the migration log + +Raises: + unspecified: any exception thrown by failures at the intersection between C++ and Python (e.g., type conversion errors, + mismatched function signatures, etc.) + +)"; +} + +std::string archipelago_get_topology_docstring() +{ + return R"(get_topology() + +Returns: + :class:`~pygmo.tyopology`: a copy of the current topology + +)"; +} + +std::string archipelago_get_migration_type_docstring() +{ + return R"(get_migration_type() + +Returns: + :class:`~pygmo.migration_type`: the current migration type for this archipelago + +)"; +} + +std::string archipelago_set_migration_type_docstring() +{ + return R"(set_migration_type(mt) + +Set a new migration type for this archipelago. + +Args: + mt (:class:`~pygmo.migration_type`): the desired migration type for this archipelago + +)"; +} + +std::string archipelago_get_migrant_handling_docstring() +{ + return R"(get_migrant_handling() + +Returns: + :class:`~pygmo.migrant_handling`: the current migrant handling policy for this archipelago + +)"; +} + +std::string archipelago_set_migrant_handling_docstring() +{ + return R"(set_migrant_handling(mh) + +Set a new migrant handling policy for this archipelago. + +Args: + mh (:class:`~pygmo.migrant_handling`): the desired migrant handling policy for this archipelago + +)"; +} + std::string nlopt_docstring() { return R"(__init__(solver = "cobyla") @@ -5982,7 +6132,7 @@ use one of the evaluators provided with pagmo, or to write their own UDBFE. Every UDBFE must be a callable (i.e., a function or a class with a call operator) with a signature equivalent to -.. code-block:: python +.. code-block:: def __call__(self, prob, dvs): ... @@ -5995,15 +6145,15 @@ stored contiguously). UDBFEs can also implement the following (optional) methods: -.. code-block:: python +.. code-block:: def get_name(self): ... def get_extra_info(self): ... -See the documentation of the corresponding member functions in this class for details on how the optional -member functions in the UDBFE are used by :class:`~pygmo.bfe`. +See the documentation of the corresponding methods in this class for details on how the optional +methods in the UDBFE are used by :class:`~pygmo.bfe`. This class is the Python counterpart of the C++ class :cpp:class:`pagmo::bfe`. @@ -6149,11 +6299,810 @@ This class is a user-defined batch fitness evaluator (UDBFE) that can be used to construct a :class:`~pygmo.bfe`. :class:`~pygmo.member_bfe` is a simple wrapper which delegates batch fitness evaluations -to the input problem's :func:`pygmo.problem.batch_fitness()` member function. +to the input problem's :func:`pygmo.problem.batch_fitness()` method. See also the docs of the C++ class :cpp:class:`pagmo::member_bfe`. )"; } +std::string topology_docstring() +{ + return R"(__init__(udt = unconnected()) + +Topology. + +In the jargon of pagmo, a topology is an object that represents connections among +:class:`islands ` in an :class:`~pygmo.archipelago`. +In essence, a topology is a *weighted directed graph* in which + +* the *vertices* (or *nodes*) are islands, +* the *edges* (or *arcs*) are directed connections between islands across which information flows during the + optimisation process (via the migration of individuals), +* the *weights* of the edges (whose numerical values are the :math:`[0.,1.]` range) represent the migration + probability. + +Following the same schema adopted for :class:`~pygmo.problem`, :class:`~pygmo.algorithm`, etc., +:class:`~pygmo.topology` exposes a generic interface to *user-defined topologies* (or UDT for short). +UDTs are classes providing a certain set +of methods that describe the properties of (and allow to interact with) a topology. Once +defined and instantiated, a UDT can then be used to construct an instance of this class, +:class:`~pygmo.topology`, which provides a generic interface to topologies for use by +:class:`~pygmo.archipelago`. + +In a :class:`~pygmo.topology`, vertices in the graph are identified by a zero-based unique +integral index. This integral index corresponds to the index of an +:class:`~pygmo.island` in an :class:`~pygmo.archipelago`. + +Every UDT must implement at least the following methods: + +.. code-block:: + + def get_connections(self, n): + ... + def push_back(self): + ... + +The ``get_connections()`` method takes as input a vertex index ``n``, and it is expected to return +a pair of array-like values containing respectively: + +* the indices of the vertices which are connecting to ``n`` (that is, the list of vertices for which a directed edge + towards ``n`` exists), +* the weights (i.e., the migration probabilities) of the edges linking the connecting vertices to ``n``. + +The ``push_back()`` method is expected to add a new vertex to the topology, assigning it the next +available index and establishing connections to other vertices. The ``push_back()`` method is invoked +by :func:`pygmo.archipelago.push_back()` upon the insertion of a new island into an archipelago, +and it is meant to allow the incremental construction of a topology. That is, after ``N`` calls to ``push_back()`` +on an initially-empty topology, the topology should contain ``N`` vertices and any number of edges (depending +on the specifics of the topology). + +Additional optional methods can be implemented in a UDT: + +.. code-block:: + + def get_name(self): + ... + def get_extra_info(self): + ... + +See the documentation of the corresponding methods in this class for details on how the optional +methods in the UDT are used by :class:`~pygmo.topology`. + +Topologies are used in asynchronous operations involving migration in archipelagos, +and thus they need to provide a certain degree of thread safety. Specifically, the +``get_connections()`` method of the UDT might be invoked concurrently with +any other method of the UDT interface. It is up to the +authors of user-defined topologies to ensure that this safety requirement is satisfied. + +This class is the Python counterpart of the C++ class :cpp:class:`pagmo::topology`. + +Args: + udt: a user-defined topology, either C++ or Python + +Raises: + NotImplementedError: if *udt* does not implement the mandatory methods detailed above + unspecified: any exception thrown by methods of the UDT invoked during construction, + the deep copy of the UDT, the constructor of the underlying C++ class, or + failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function + signatures, etc.) + +)"; +} + +std::string topology_get_connections_docstring() +{ + return R"(get_connections(n) + +Get the connections to a vertex. + +This method will invoke the ``get_connections()`` method of the UDT, which is expected to return +a pair of array-like objects containing respectively: + +* the indices of the vertices which are connecting to *n* (that is, the list of vertices for which a directed + edge towards *n* exists), +* the weights (i.e., the migration probabilities) of the edges linking the connecting vertices to *n*. + +This method will also run sanity checks on the output of the ``get_connections()`` method of the UDT. + +Args: + n (int): the index of the vertex whose incoming connections' details will be returned + +Returns: + Pair of 1D NumPy arrays: a pair of arrays describing *n*'s incoming connections + +Raises: + RuntimeError: if the object returned by a pythonic UDT is not iteratable, or it is an iteratable + whose number of elements is not exactly 2, or if the invocation of the ``get_connections()`` + method of the UDT raises an exception + ValueError: if the sizes of the returned arrays differ, or if any element of the second + array is not in the :math:`[0.,1.]` range + unspecified: any exception raised by failures at the intersection + between C++ and Python (e.g., type conversion errors, mismatched function signatures, etc.) + +)"; +} + +std::string topology_push_back_docstring() +{ + return R"(push_back(n=1) + +Add vertices. + +This method will invoke the ``push_back()`` method of the UDT *n* times. The ``push_back()`` method +of the UDT is expected to add a new vertex to the +topology, assigning it the next available index and establishing connections to other vertices. + +Args: + n (int): the number of times the ``push_back()`` method of the UDT will be invoked + +Raises: + OverflowError: if *n* is negative or too large + unspecified: any exception thrown by the ``push_back()`` method of the UDT + +)"; +} + +std::string topology_get_name_docstring() +{ + return R"(get_name() + +Topology's name. + +If the UDT provides a ``get_name()`` method, then this method will return the output of its ``get_name()`` method. +Otherwise, an implementation-defined name based on the type of the UDT will be returned. + +Returns: + str: the topology's name + +)"; +} + +std::string topology_get_extra_info_docstring() +{ + return R"(get_extra_info() + +Topology's extra info. + +If the UDT provides a ``get_extra_info()`` method, then this method will return the output of its ``get_extra_info()`` +method. Otherwise, an empty string will be returned. + +Returns: + str: extra info about the UDT + +Raises: + unspecified: any exception thrown by the ``get_extra_info()`` method of the UDT + +)"; +} + +std::string unconnected_docstring() +{ + return R"(__init__() + +Unconnected topology. + +This user-defined topology (UDT) represents an unconnected graph. This is the default +UDT used by :class:`pygmo.topology`. + +See also the docs of the C++ class :cpp:class:`pagmo::unconnected`. + +)"; +} + +std::string ring_docstring() +{ + return R"(__init__(n=0, w=1.) + +Ring topology. + +This user-defined topology (UDT) represents a bidirectional ring (that is, a ring in +which each node connects to both the previous and the following nodes). + +See also the docs of the C++ class :cpp:class:`pagmo::ring`. + +Args: + n (int): the desired number of vertices + w (float): the weight of the edges + +Raises: + OverflowError: if *n* is negative or too large + ValueError: if *w* is not in the :math:`\left[0, 1\right]` range + +)"; +} + +std::string ring_get_weight_docstring() +{ + return R"(get_weight() + +Returns: + float: the weight *w* used in the construction of this topology + +)"; +} + +std::string base_bgl_num_vertices_docstring() +{ + return R"(num_vertices() + +Returns: + int: the number of vertices in the topology + +)"; +} + +std::string base_bgl_are_adjacent_docstring() +{ + return R"(are_adjacent(i, j) + +Check if two vertices are adjacent. + +Two vertices *i* and *j* are adjacent if there is a directed edge connecting *i* to *j*. + +Args: + i (int): the first vertex index + j (int): the second vertex index + +Returns: + bool: :data:`True` if *i* and *j* are adjacent, :data:`False` otherwise + +Raises: + ValueError: if *i* or *j* are not smaller than the number of vertices + OverflowError: if *i* or *j* are negative or too large + +)"; +} + +std::string base_bgl_add_vertex_docstring() +{ + return R"(add_vertex() + +Add a vertex. + +This method will add a new vertex to the topology. + +The newly-added vertex will be disjoint from any other vertex in the topology (i.e., there are no connections to/from the new vertex). + +)"; +} + +std::string base_bgl_add_edge_docstring() +{ + return R"(add_edge(i, j, w=1.) + +Add a new edge. + +This method will add a new edge of weight *w* connecting *i* to *j*. + +Args: + i (int): the first vertex index + j (int): the second vertex index + w (float): the edge's weight + +Raises: + OverflowError: if *i* or *j* are negative or too large + ValueError: if *i* or *j* are not smaller than the number of vertices, *i* and *j* are already adjacent, or + if *w* is not in the :math:`\left[0, 1\right]` range + +)"; +} + +std::string base_bgl_remove_edge_docstring() +{ + return R"(remove_edge(i, j) + +Remove an existing edge. + +This method will remove the edge connecting *i* to *j*. + +Args: + i (int): the first vertex index + j (int): the second vertex index + +Raises: + ValueError: if *i* or *j* are not smaller than the number of vertices, or *i* and *j* are not adjacent + OverflowError: if *i* or *j* are negative or too large + +)"; +} + +std::string base_bgl_set_weight_docstring() +{ + return R"(set_weight(i, j, w) + +Set the weight of an edge. + +This method will set to *w* the weight of the edge connecting *i* to *j*. + +Args: + i (int): the first vertex index + j (int): the second vertex index + w (float): the desired weight + +Raises: + OverflowError: if *i* or *j* are negative or too large + ValueError: if *i* or *j* are not smaller than the number of vertices, *i* and *j* are not adjacent, or + if *w* is not in the :math:`\left[0, 1\right]` range + +)"; +} + +std::string base_bgl_set_all_weights_docstring() +{ + return R"(set_all_weights(w) + +This method will set the weights of all edges in the topology to *w*. + +Args: + w (float): the edges' weight + +Raises: + ValueError: if *w* is not in the :math:`\left[0, 1\right]` range + +)"; +} + +std::string fully_connected_docstring() +{ + return R"(__init__(n=0, w=1.) + +Fully connected topology. + +This user-defined topology (UDT) represents a *complete graph* (that is, a topology +in which all vertices connect to all other vertices). The edge weight is configurable +at construction, and it will be the same for all the edges in the topology. + +See also the docs of the C++ class :cpp:class:`pagmo::fully_connected`. + +Args: + n (int): the desired number of vertices + w (float): the weight of the edges + +Raises: + OverflowError: if *n* is negative or too large + ValueError: if *w* is not in the :math:`\left[0, 1\right]` range + +)"; +} + +std::string fully_connected_get_weight_docstring() +{ + return ring_get_weight_docstring(); +} + +std::string fully_connected_num_vertices_docstring() +{ + return base_bgl_num_vertices_docstring(); +} + +std::string r_policy_docstring() +{ + return R"(__init__(udrp = fair_replace()) + +Replacement policy. + +A replacement policy establishes +how, during migration within an :class:`~pygmo.archipelago`, +a group of migrants replaces individuals in an existing +:class:`~pygmo.population`. In other words, a replacement +policy is tasked with producing a new set of individuals from +an original set of individuals and a set of candidate migrants. + +Following the same schema adopted for :class:`~pygmo.problem`, :class:`~pygmo.algorithm`, etc., +:class:`~pygmo.r_policy` exposes a generic +interface to *user-defined replacement policies* (or UDRP for short). +UDRPs are classes providing a certain set +of methods that implement the logic of the replacement policy. Once +defined and instantiated, a UDRP can then be used to construct an instance of this class, +:class:`~pygmo.r_policy`, which +provides a generic interface to replacement policies for use by :class:`~pygmo.island`. + +Every UDRP must implement at least the following method: + +.. code-block:: + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + ... + +The ``replace()`` method takes in input the following parameters: + +* a group of individuals *inds*, +* a set of arguments describing the properties of the :class:`~pygmo.problem` the individuals refer to: + + * the total dimension *nx*, + * the integral dimension *nix*, + * the number of objectives *nobj*, + * the number of equality constraints *nec*, + * the number of inequality constraints *nic*, + * the problem's constraint tolerances *tol*, + +* a set of migrants *mig*, + +and it produces in output another set of individuals resulting from replacing individuals in *inds* with +individuals from *mig* (following some logic established by the UDRP). The sets of individuals *inds* and +*mig*, and the return value of the ``replace()`` method are represented as tuples of 3 elements containing: + +* a 1D NumPy array of individual IDs (represented as 64-bit unsigned integrals), +* a 2D NumPy array of decision vectors (i.e., the decision vectors of each individual, + stored in row-major order), +* a 2D NumPy array of fitness vectors (i.e., the fitness vectors of each individual, + stored in row-major order). + +Additional optional methods can be implemented in a UDRP: + +.. code-block:: + + def get_name(self): + ... + def get_extra_info(self): + ... + +See the documentation of the corresponding methods in this class for details on how the optional +methods in the UDRP are used by :class:`~pygmo.r_policy`. + +Replacement policies are used in asynchronous operations involving migration in archipelagos, +and thus they need to provide a certain degree of thread safety. Specifically, the +``replace()`` method of the UDRP might be invoked concurrently with +any other method of the UDRP interface. It is up to the +authors of user-defined replacement policies to ensure that this safety requirement is satisfied. + +This class is the Python counterpart of the C++ class :cpp:class:`pagmo::r_policy`. + +Args: + udrp: a user-defined replacement policy, either C++ or Python + +Raises: + NotImplementedError: if *udrp* does not implement the mandatory methods detailed above + unspecified: any exception thrown by methods of the UDRP invoked during construction, + the deep copy of the UDRP, the constructor of the underlying C++ class, or + failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function + signatures, etc.) + +)"; +} + +std::string r_policy_replace_docstring() +{ + return R"(replace(inds, nx, nix, nobj, nec, nic, tol, mig) + +Replace individuals in a group with migrants from another group. + +This method will invoke the ``replace()`` method of the UDRP. +Given a set of individuals, *inds*, and a set of migrants, *mig*, the ``replace()`` method of the UDRP +is expected to replace individuals in *inds* +with individuals from *mig*, and return the new set of individuals resulting from the replacement. +The other arguments of this method describe the properties of the :class:`~pygmo.problem` +that the individuals in *inds* and *mig* refer to. + +The sets of individuals *inds* and *mig*, and the return value of this method are +represented as tuples of 3 elements containing: + +* a 1D NumPy array of individual IDs (represented as 64-bit unsigned integrals), +* a 2D NumPy array of decision vectors (i.e., the decision vectors of each individual, + stored in row-major order), +* a 2D NumPy array of fitness vectors (i.e., the fitness vectors of each individual, + stored in row-major order). + +In addition to invoking the ``replace()`` method of the UDRP, this method will also +perform a variety of sanity checks on both the input arguments and on the output produced by the +UDRP. + +Args: + inds (tuple): the original group of individuals + nx (int): the dimension of the problem *inds* and *mig* refer to + nix (int): the integral dimension of the problem *inds* and *mig* refer to + nobj (int): the number of objectives of the problem *inds* and *mig* refer to + nec (int): the number of equality constraints of the problem *inds* and *mig* refer to + nic (int): the number of inequality constraints of the problem *inds* and *mig* refer to + tol (array-like object): the vector of constraints tolerances of the problem *inds* and *mig* refer to + mig (tuple): the group of migrants + +Returns: + tuple: a new set of individuals resulting from replacing individuals in *inds* with individuals from *mig* + +Raises: + RuntimeError: if the object returned by a pythonic UDRP is not iteratable, or it is an iteratable + whose number of elements is not exactly 3, or if the invocation of the ``replace()`` + method of the UDRP raises an exception + ValueError: if *inds*, *mig* or the return value are not consistent with the problem properties, + or the ID, decision and fitness vectors in *inds*, *mig* or the return value have inconsistent sizes, + or the problem properties are invalid (e.g., *nobj* is zero, *nix* > *nx*, etc.) + unspecified: any exception raised by failures at the intersection + between C++ and Python (e.g., type conversion errors, mismatched function signatures, etc.) + +)"; +} + +std::string r_policy_get_name_docstring() +{ + return R"(get_name() + +Name of the replacement policy. + +If the UDRP provides a ``get_name()`` method, then this method will return the output of its ``get_name()`` method. +Otherwise, an implementation-defined name based on the type of the UDRP will be returned. + +Returns: + str: the name of the replacement policy + +)"; +} + +std::string r_policy_get_extra_info_docstring() +{ + return R"(get_extra_info() + +Replacement policy's extra info. + +If the UDRP provides a ``get_extra_info()`` method, then this method will return the output of its ``get_extra_info()`` +method. Otherwise, an empty string will be returned. + +Returns: + str: extra info about the UDRP + +Raises: + unspecified: any exception thrown by the ``get_extra_info()`` method of the UDRP + +)"; +} + +std::string fair_replace_docstring() +{ + return R"(__init__(rate=1) + +Fair replacement policy. + +This user-defined replacement policy (UDRP) will replace individuals in +a group only if the candidate replacement individuals are *better* than +the original individuals. + +In this context, *better* means the following: + +* in single-objective unconstrained problems, an individual is better + than another one if its fitness is lower, +* in single-objective constrained problems, individuals are ranked + via :func:`~pygmo.sort_population_con()`, +* in multi-objective unconstrained problems, individuals are ranked + via :func:`~pygmo.sort_population_mo()`. + +Note that this user-defined replacement policy currently does *not* support +multi-objective constrained problems. + +A fair replacement policy is constructed from a *rate* argument, which +can be either an integral or a floating-point value. + +If *rate* is a floating point value in the :math:`\left[0,1\right]` range, +then it represents a *fractional* migration rate. That is, it indicates, +the fraction of individuals that may be replaced in the input population: +a value of 0 means that no individuals will be replaced, a value of 1 means that +all individuals may be replaced. + +If *rate* is an integral value, then it represents an *absolute* migration rate, that is, +the exact number of individuals that may be replaced in the input population. + +See also the docs of the C++ class :cpp:class:`pagmo::fair_replace`. + +Args: + rate (int, float): the desired migration rate + +Raises: + ValueError: if the supplied fractional migration rate is not finite + or not in the :math:`\left[0,1\right]` range + TypeError: if *rate* is not an instance of :class:`int` or :class:`float` + unspecified: any exception raised by the invoked C++ constructor + +)"; +} + +std::string s_policy_docstring() +{ + return R"(__init__(udsp = select_best()) + +Selection policy. + +A selection policy establishes +how, during migration within an :class:`~pygmo.archipelago`, +candidate migrants are selected from an :class:`~pygmo.island`. + +Following the same schema adopted for :class:`~pygmo.problem`, :class:`~pygmo.algorithm`, etc., +:class:`~pygmo.s_policy` exposes a generic +interface to *user-defined selection policies* (or UDSP for short). +UDSPs are classes providing a certain set of methods that implement the logic of the selection policy. Once +defined and instantiated, a UDSP can then be used to construct an instance of this class, +:class:`~pygmo.s_policy`, which +provides a generic interface to selection policies for use by :class:`~pygmo.island`. + +Every UDSP must implement at least the following method: + +.. code-block:: + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + ... + +The ``select()`` method takes in input the following parameters: + +* a group of individuals *inds*, +* a set of arguments describing the properties of the :class:`~pygmo.problem` the individuals refer to: + + * the total dimension *nx*, + * the integral dimension *nix*, + * the number of objectives *nobj*, + * the number of equality constraints *nec*, + * the number of inequality constraints *nic*, + * the problem's constraint tolerances *tol*, + +and it produces in output another set of individuals resulting from selecting individuals in *inds* +(following some logic established by the UDSP). The sets of individuals *inds* +and the return value of the ``select()`` method are represented as tuples of 3 elements containing: + +* a 1D NumPy array of individual IDs (represented as 64-bit unsigned integrals), +* a 2D NumPy array of decision vectors (i.e., the decision vectors of each individual, + stored in row-major order), +* a 2D NumPy array of fitness vectors (i.e., the fitness vectors of each individual, + stored in row-major order). + +Additional optional methods can be implemented in a UDSP: + +.. code-block:: + + def get_name(self): + ... + def get_extra_info(self): + ... + +See the documentation of the corresponding methods in this class for details on how the optional +methods in the UDSP are used by :class:`~pygmo.s_policy`. + +Selection policies are used in asynchronous operations involving migration in archipelagos, +and thus they need to provide a certain degree of thread safety. Specifically, the +``select()`` method of the UDSP might be invoked concurrently with +any other method of the UDSP interface. It is up to the +authors of user-defined selection policies to ensure that this safety requirement is satisfied. + +This class is the Python counterpart of the C++ class :cpp:class:`pagmo::s_policy`. + +Args: + udsp: a user-defined selection policy, either C++ or Python + +Raises: + NotImplementedError: if *udsp* does not implement the mandatory methods detailed above + unspecified: any exception thrown by methods of the UDSP invoked during construction, + the deep copy of the UDSP, the constructor of the underlying C++ class, or + failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function + signatures, etc.) + +)"; +} + +std::string s_policy_select_docstring() +{ + return R"(select(inds, nx, nix, nobj, nec, nic, tol) + +Select individuals from a group. + +This method will invoke the ``select()`` method of the UDSP. +Given a set of individuals, *inds*, the ``select()`` method of the UDSP +is expected to return a new set of individuals selected from *inds*. +The other arguments of this method describe the properties of the :class:`~pygmo.problem` +that the individuals in *inds* refer to. + +The set of individuals *inds* and the return value of this method are +represented as tuples of 3 elements containing: + +* a 1D NumPy array of individual IDs (represented as 64-bit unsigned integrals), +* a 2D NumPy array of decision vectors (i.e., the decision vectors of each individual, + stored in row-major order), +* a 2D NumPy array of fitness vectors (i.e., the fitness vectors of each individual, + stored in row-major order). + +In addition to invoking the ``select()`` method of the UDSP, this function will also +perform a variety of sanity checks on both the input arguments and on the output produced by the +UDSP. + +Args: + inds (tuple): the original group of individuals + nx (int): the dimension of the problem *inds* refers to + nix (int): the integral dimension of the problem *inds* refers to + nobj (int): the number of objectives of the problem *inds* refers to + nec (int): the number of equality constraints of the problem *inds* refers to + nic (int): the number of inequality constraints of the problem *inds* refers to + tol (array-like object): the vector of constraints tolerances of the problem *inds* refers to + +Returns: + tuple: a new set of individuals resulting from selecting individuals in *inds*. + +Raises: + RuntimeError: if the object returned by a pythonic UDSP is not iteratable, or it is an iteratable + whose number of elements is not exactly 3, or if the invocation of the ``select()`` + method of the UDSP raises an exception + ValueError: if *inds* or the return value are not consistent with the problem properties, + or the ID, decision and fitness vectors in *inds* or the return value have inconsistent sizes, + or the problem properties are invalid (e.g., *nobj* is zero, *nix* > *nx*, etc.) + unspecified: any exception raised by failures at the intersection + between C++ and Python (e.g., type conversion errors, mismatched function signatures, etc.) + +)"; +} + +std::string s_policy_get_name_docstring() +{ + return R"(get_name() + +Name of the selection policy. + +If the UDSP provides a ``get_name()`` method, then this method will return the output of its ``get_name()`` method. +Otherwise, an implementation-defined name based on the type of the UDSP will be returned. + +Returns: + str: the name of the selection policy + +)"; +} + +std::string s_policy_get_extra_info_docstring() +{ + return R"(get_extra_info() + +Selection policy's extra info. + +If the UDSP provides a ``get_extra_info()`` method, then this method will return the output of its ``get_extra_info()`` +method. Otherwise, an empty string will be returned. + +Returns: + str: extra info about the UDSP + +Raises: + unspecified: any exception thrown by the ``get_extra_info()`` method of the UDSP + +)"; +} + +std::string select_best_docstring() +{ + return R"(__init__(rate=1) + +Select best selection policy. + +This user-defined selection policy (UDSP) will select the *best* +individuals from a group. + +In this context, *best* means the following: + +* in single-objective unconstrained problems, individuals are ranked + according to their fitness function, +* in single-objective constrained problems, individuals are ranked + via :func:`~pygmo.sort_population_con()`, +* in multi-objective unconstrained problems, individuals are ranked + via :func:`~pygmo.sort_population_mo()`. + +Note that this user-defined selection policy currently does *not* support +multi-objective constrained problems. + +A select best policy is constructed from a *rate* argument, which +can be either an integral or a floating-point value. + +If *rate* is a floating point value in the :math:`\left[0,1\right]` range, +then it represents a *fractional* migration rate. That is, it indicates, +the fraction of individuals that will be selected from the input population: +a value of 0 means that no individuals will be selected, a value of 1 means that +all individuals will be selected. + +If *rate* is an integral value, then it represents an *absolute* migration rate, that is, +the exact number of individuals that will be selected from the input population. + +See also the docs of the C++ class :cpp:class:`pagmo::select_best`. + +Args: + rate (int, float): the desired migration rate + +Raises: + ValueError: if the supplied fractional migration rate is not finite + or not in the :math:`\left[0,1\right]` range + TypeError: if *rate* is not an instance of :class:`int` or :class:`float` + unspecified: any exception raised by the invoked C++ constructor + +)"; +} + } // namespace pygmo diff --git a/pygmo/docstrings.hpp b/pygmo/docstrings.hpp index 2fd363629..4b7ebc8f3 100644 --- a/pygmo/docstrings.hpp +++ b/pygmo/docstrings.hpp @@ -255,6 +255,8 @@ std::string island_get_population_docstring(); std::string island_set_population_docstring(); std::string island_get_name_docstring(); std::string island_get_extra_info_docstring(); +std::string island_get_r_policy_docstring(); +std::string island_get_s_policy_docstring(); // udi. std::string thread_island_docstring(); @@ -268,6 +270,13 @@ std::string archipelago_wait_check_docstring(); std::string archipelago_getitem_docstring(); std::string archipelago_get_champions_f_docstring(); std::string archipelago_get_champions_x_docstring(); +std::string archipelago_get_migrants_db_docstring(); +std::string archipelago_get_migration_log_docstring(); +std::string archipelago_get_topology_docstring(); +std::string archipelago_get_migration_type_docstring(); +std::string archipelago_set_migration_type_docstring(); +std::string archipelago_get_migrant_handling_docstring(); +std::string archipelago_set_migrant_handling_docstring(); // bfe. std::string bfe_docstring(); @@ -281,6 +290,46 @@ std::string default_bfe_docstring(); std::string thread_bfe_docstring(); std::string member_bfe_docstring(); +// topology. +std::string topology_docstring(); +std::string topology_get_connections_docstring(); +std::string topology_push_back_docstring(); +std::string topology_get_name_docstring(); +std::string topology_get_extra_info_docstring(); + +// udt. +std::string unconnected_docstring(); +std::string base_bgl_num_vertices_docstring(); +std::string base_bgl_are_adjacent_docstring(); +std::string base_bgl_add_vertex_docstring(); +std::string base_bgl_add_edge_docstring(); +std::string base_bgl_remove_edge_docstring(); +std::string base_bgl_set_weight_docstring(); +std::string base_bgl_set_all_weights_docstring(); +std::string ring_docstring(); +std::string ring_get_weight_docstring(); +std::string fully_connected_docstring(); +std::string fully_connected_get_weight_docstring(); +std::string fully_connected_num_vertices_docstring(); + +// r_policy. +std::string r_policy_docstring(); +std::string r_policy_replace_docstring(); +std::string r_policy_get_name_docstring(); +std::string r_policy_get_extra_info_docstring(); + +// udrp. +std::string fair_replace_docstring(); + +// s_policy +std::string s_policy_docstring(); +std::string s_policy_select_docstring(); +std::string s_policy_get_name_docstring(); +std::string s_policy_get_extra_info_docstring(); + +// udsp. +std::string select_best_docstring(); + } // namespace pygmo #endif diff --git a/pygmo/expose_islands.hpp b/pygmo/expose_islands.hpp index 8c4d35842..f31f37289 100644 --- a/pygmo/expose_islands.hpp +++ b/pygmo/expose_islands.hpp @@ -37,6 +37,8 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include +#include #include #include @@ -63,7 +65,8 @@ inline bp::class_ expose_island_pygmo(const char *name, const char *descr) auto &isl = get_island_class(); // Expose the island constructor from Isl. - isl.def(bp::init()); + isl.def(bp::init()); // Expose extract. isl.def("_cpp_extract", &generic_cpp_extract, bp::return_internal_reference<>()); diff --git a/pygmo/expose_r_policies.cpp b/pygmo/expose_r_policies.cpp new file mode 100644 index 000000000..d8c782cd8 --- /dev/null +++ b/pygmo/expose_r_policies.cpp @@ -0,0 +1,105 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#if defined(_MSC_VER) + +// Disable various warnings from MSVC. +#pragma warning(disable : 4275) +#pragma warning(disable : 4996) +#pragma warning(disable : 4503) +#pragma warning(disable : 4244) + +#endif + +#include + +// See: https://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api +// In every cpp file we need to make sure this is included before everything else, +// with the correct #defines. +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL pygmo_ARRAY_API +#include + +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; +namespace bp = boost::python; + +namespace pygmo +{ + +namespace detail +{ + +namespace +{ + +// A test r_policy. +struct test_r_policy { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + // Set/get an internal value to test extraction semantics. + void set_n(int n) + { + m_n = n; + } + int get_n() const + { + return m_n; + } + int m_n = 1; +}; + +} // namespace + +} // namespace detail + +void expose_r_policies() +{ + // Test r_policy. + auto t_r_policy = expose_r_policy_pygmo("_test_r_policy", "A test replacement policy."); + t_r_policy.def("get_n", &detail::test_r_policy::get_n); + t_r_policy.def("set_n", &detail::test_r_policy::set_n); + + // Fair replacement policy. + auto fair_replace_ = expose_r_policy_pygmo("fair_replace", fair_replace_docstring().c_str()); + detail::sr_policy_add_rate_constructor(fair_replace_); +} + +} // namespace pygmo diff --git a/pygmo/expose_r_policies.hpp b/pygmo/expose_r_policies.hpp new file mode 100644 index 000000000..5b500c2e9 --- /dev/null +++ b/pygmo/expose_r_policies.hpp @@ -0,0 +1,80 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_EXPOSE_R_POLICIES_HPP +#define PYGMO_EXPOSE_R_POLICIES_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace pygmo +{ + +// Replacement policies exposition function. +void expose_r_policies(); + +namespace bp = boost::python; + +// Main r_policy exposition function - for *internal* use by pygmo. The exposition function +// for APs needs to be different. +template +inline bp::class_ expose_r_policy_pygmo(const char *name, const char *descr) +{ + // We require all replacement policies to be def-ctible at the bare minimum. + bp::class_ c(name, descr, bp::init<>()); + + // Mark it as a C++ replacement policy. + c.attr("_pygmo_cpp_r_policy") = true; + + // Get reference to the r_policy class. + auto &t = get_r_policy_class(); + + // Expose the r_policy constructor from RPol. + t.def(bp::init((bp::arg("udrp")))); + + // Expose extract. + t.def("_cpp_extract", &generic_cpp_extract, bp::return_internal_reference<>()); + + // Add the r_policy to the replacement policies submodule. + bp::scope().attr("r_policies").attr(name) = c; + + return c; +} +} // namespace pygmo + +#endif diff --git a/pygmo/expose_s_policies.cpp b/pygmo/expose_s_policies.cpp new file mode 100644 index 000000000..ffd8968ea --- /dev/null +++ b/pygmo/expose_s_policies.cpp @@ -0,0 +1,105 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#if defined(_MSC_VER) + +// Disable various warnings from MSVC. +#pragma warning(disable : 4275) +#pragma warning(disable : 4996) +#pragma warning(disable : 4503) +#pragma warning(disable : 4244) + +#endif + +#include + +// See: https://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api +// In every cpp file we need to make sure this is included before everything else, +// with the correct #defines. +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL pygmo_ARRAY_API +#include + +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; +namespace bp = boost::python; + +namespace pygmo +{ + +namespace detail +{ + +namespace +{ + +// A test s_policy. +struct test_s_policy { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + // Set/get an internal value to test extraction semantics. + void set_n(int n) + { + m_n = n; + } + int get_n() const + { + return m_n; + } + int m_n = 1; +}; + +} // namespace + +} // namespace detail + +void expose_s_policies() +{ + // Test s_policy. + auto t_s_policy = expose_s_policy_pygmo("_test_s_policy", "A test selection policy."); + t_s_policy.def("get_n", &detail::test_s_policy::get_n); + t_s_policy.def("set_n", &detail::test_s_policy::set_n); + + // Select best policy. + auto select_best_ = expose_s_policy_pygmo("select_best", select_best_docstring().c_str()); + detail::sr_policy_add_rate_constructor(select_best_); +} + +} // namespace pygmo diff --git a/pygmo/expose_s_policies.hpp b/pygmo/expose_s_policies.hpp new file mode 100644 index 000000000..f6e89e8e2 --- /dev/null +++ b/pygmo/expose_s_policies.hpp @@ -0,0 +1,80 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_EXPOSE_S_POLICIES_HPP +#define PYGMO_EXPOSE_S_POLICIES_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace pygmo +{ + +// Selection policies exposition function. +void expose_s_policies(); + +namespace bp = boost::python; + +// Main s_policy exposition function - for *internal* use by pygmo. The exposition function +// for APs needs to be different. +template +inline bp::class_ expose_s_policy_pygmo(const char *name, const char *descr) +{ + // We require all selection policies to be def-ctible at the bare minimum. + bp::class_ c(name, descr, bp::init<>()); + + // Mark it as a C++ selection policy. + c.attr("_pygmo_cpp_s_policy") = true; + + // Get reference to the s_policy class. + auto &t = get_s_policy_class(); + + // Expose the s_policy constructor from SPol. + t.def(bp::init((bp::arg("udsp")))); + + // Expose extract. + t.def("_cpp_extract", &generic_cpp_extract, bp::return_internal_reference<>()); + + // Add the s_policy to the selection policies submodule. + bp::scope().attr("s_policies").attr(name) = c; + + return c; +} +} // namespace pygmo + +#endif diff --git a/pygmo/expose_topologies.cpp b/pygmo/expose_topologies.cpp new file mode 100644 index 000000000..3c497e57d --- /dev/null +++ b/pygmo/expose_topologies.cpp @@ -0,0 +1,140 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#if defined(_MSC_VER) + +// Disable various warnings from MSVC. +#pragma warning(disable : 4275) +#pragma warning(disable : 4996) +#pragma warning(disable : 4503) +#pragma warning(disable : 4244) + +#endif + +#include + +// See: https://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api +// In every cpp file we need to make sure this is included before everything else, +// with the correct #defines. +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL pygmo_ARRAY_API +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace pagmo; +namespace bp = boost::python; + +namespace pygmo +{ + +namespace detail +{ + +namespace +{ + +// A test topology. +struct test_topology { + std::pair, vector_double> get_connections(std::size_t) const + { + return std::pair, vector_double>{}; + } + void push_back() {} + // Set/get an internal value to test extraction semantics. + void set_n(int n) + { + m_n = n; + } + int get_n() const + { + return m_n; + } + int m_n = 1; +}; + +// Expose the methods from the base BGL topology. +template +void expose_base_bgl_topo(bp::class_ &c) +{ + c.def("num_vertices", &Topo::num_vertices, pygmo::base_bgl_num_vertices_docstring().c_str()); + c.def("are_adjacent", &Topo::are_adjacent, pygmo::base_bgl_are_adjacent_docstring().c_str(), + (bp::arg("i"), bp::arg("j"))); + c.def("add_vertex", &Topo::add_vertex, pygmo::base_bgl_add_vertex_docstring().c_str()); + c.def("add_edge", &Topo::add_edge, pygmo::base_bgl_add_edge_docstring().c_str(), + (bp::arg("i"), bp::arg("j"), bp::arg("w") = 1.)); + c.def("remove_edge", &Topo::remove_edge, pygmo::base_bgl_remove_edge_docstring().c_str(), + (bp::arg("i"), bp::arg("j"))); + c.def("set_weight", &Topo::set_weight, pygmo::base_bgl_set_weight_docstring().c_str(), + (bp::arg("i"), bp::arg("j"), bp::arg("w"))); + c.def("set_all_weights", &Topo::set_all_weights, pygmo::base_bgl_set_all_weights_docstring().c_str(), + (bp::arg("w"))); +} + +} // namespace + +} // namespace detail + +void expose_topologies() +{ + // Test topology. + auto t_topology = expose_topology_pygmo("_test_topology", "A test topology."); + t_topology.def("get_n", &detail::test_topology::get_n); + t_topology.def("set_n", &detail::test_topology::set_n); + + // Unconnected topology. + expose_topology_pygmo("unconnected", unconnected_docstring().c_str()); + + // Ring. + auto ring_ = expose_topology_pygmo("ring", ring_docstring().c_str()); + ring_.def(bp::init((bp::arg("n") = std::size_t(0), bp::arg("w") = 1.))) + .def("get_weight", &ring::get_weight, pygmo::ring_get_weight_docstring().c_str()); + detail::expose_base_bgl_topo(ring_); + + // Fully connected. + auto fully_connected_ + = expose_topology_pygmo("fully_connected", fully_connected_docstring().c_str()); + fully_connected_.def(bp::init((bp::arg("n") = std::size_t(0), bp::arg("w") = 1.))) + .def("get_weight", &fully_connected::get_weight, pygmo::fully_connected_get_weight_docstring().c_str()) + .def("num_vertices", &fully_connected::num_vertices, pygmo::fully_connected_num_vertices_docstring().c_str()); +} + +} // namespace pygmo diff --git a/pygmo/expose_topologies.hpp b/pygmo/expose_topologies.hpp new file mode 100644 index 000000000..3a742f7e6 --- /dev/null +++ b/pygmo/expose_topologies.hpp @@ -0,0 +1,80 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_EXPOSE_TOPOLOGIES_HPP +#define PYGMO_EXPOSE_TOPOLOGIES_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace pygmo +{ + +// Topologies exposition function. +void expose_topologies(); + +namespace bp = boost::python; + +// Main topology exposition function - for *internal* use by pygmo. The exposition function +// for APs needs to be different. +template +inline bp::class_ expose_topology_pygmo(const char *name, const char *descr) +{ + // We require all topologies to be def-ctible at the bare minimum. + bp::class_ c(name, descr, bp::init<>()); + + // Mark it as a C++ topology. + c.attr("_pygmo_cpp_topology") = true; + + // Get reference to the topology class. + auto &t = get_topology_class(); + + // Expose the topology constructor from Topo. + t.def(bp::init((bp::arg("udt")))); + + // Expose extract. + t.def("_cpp_extract", &generic_cpp_extract, bp::return_internal_reference<>()); + + // Add the topology to the topologies submodule. + bp::scope().attr("topologies").attr(name) = c; + + return c; +} +} // namespace pygmo + +#endif diff --git a/pygmo/island.cpp b/pygmo/island.cpp index 0f8076d4b..073e6be65 100644 --- a/pygmo/island.cpp +++ b/pygmo/island.cpp @@ -95,7 +95,7 @@ void isl_inner::run_evolve(island &isl) const try { isl_name = get_name(); } catch (const bp::error_already_set &) { - pygmo::handle_thread_py_exception("Could not fetch the name of the pythonic island. The error is:\n"); + pygmo::handle_thread_py_exception("Could not fetch the name of a pythonic island. The error is:\n"); } try { @@ -133,7 +133,7 @@ void isl_inner::run_evolve(island &isl) const isl.set_algorithm(ret_algo); isl.set_population(ret_pop); } catch (const bp::error_already_set &) { - pygmo::handle_thread_py_exception("The asynchronous evolution of a Pythonic island of type '" + isl_name + pygmo::handle_thread_py_exception("The asynchronous evolution of a pythonic island of type '" + isl_name + "' raised an error:\n"); } } diff --git a/pygmo/island_exposition_suite.hpp b/pygmo/island_exposition_suite.hpp index 54298b9de..f4826b733 100644 --- a/pygmo/island_exposition_suite.hpp +++ b/pygmo/island_exposition_suite.hpp @@ -42,6 +42,8 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include +#include #include @@ -65,7 +67,8 @@ inline bp::class_ expose_island(const char *name, const char *descr) bp::extract(bp::import("pygmo").attr("core").attr("_island_address"))()); // Expose the island constructor from Isl. - isl.def(bp::init()); + isl.def(bp::init()); // Expose extract. isl.def("_cpp_extract", &generic_cpp_extract, bp::return_internal_reference<>()); diff --git a/pygmo/plotting/__init__.py b/pygmo/plotting/__init__.py index d3c3ba69f..1188faa12 100644 --- a/pygmo/plotting/__init__.py +++ b/pygmo/plotting/__init__.py @@ -30,23 +30,24 @@ # for python 2.0 compatibility from __future__ import absolute_import as _ai +from ..core import dtlz # Plotting functions -def plot_non_dominated_fronts(points, marker='o', comp=[0, 1], axes = None): +def plot_non_dominated_fronts(points, marker='o', comp=[0, 1], axes=None): """ Plots the nondominated fronts of a set of points. Makes use of :class:`~pygmo.fast_non_dominated_sorting` to compute the non dominated fronts. Args: points (2d array-like): points to plot - marker (``str``): matplotlib marker used to plot the *points* - comp (``list``): Components to be considered in the two dimensional plot (useful in many-objectives cases) - axes (matplotlib.axes.Axes): plot axes + marker (str): matplotlib marker used to plot the *points* + comp (list): Components to be considered in the two dimensional plot (useful in many-objectives cases) + axes (matplotlib.axes.Axes): plot axes (if :data:`None`, new axes will be created) Returns: - ``matplotlib.axes.Axes``: the current ``matplotlib.axes.Axes`` instance on the current figure + matplotlib.axes.Axes: the input *axes* or a new :class:`matplotlib.axes.Axes` instance Examples: >>> from pygmo import * @@ -54,6 +55,7 @@ def plot_non_dominated_fronts(points, marker='o', comp=[0, 1], axes = None): >>> pop = population(prob, 40) >>> ax = plot_non_dominated_fronts(pop.get_f()) # doctest: +SKIP """ + from matplotlib import pyplot as plt from ..core import fast_non_dominated_sorting, population from numpy import linspace @@ -76,14 +78,14 @@ def plot_non_dominated_fronts(points, marker='o', comp=[0, 1], axes = None): comp[1]], marker=marker, color=cl[ndr]) # We plot the fronts # Frist compute the points coordinates - x = [points[idx][comp[0]] for idx in front] + x = [points[idx][comp[0]] for idx in front] y = [points[idx][comp[1]] for idx in front] # Then sort them by the first objective tmp = [(a, b) for a, b in zip(x, y)] tmp = sorted(tmp, key=lambda k: k[0]) # Now plot using step axes.step([c[0] for c in tmp], [c[1] - for c in tmp], color=cl[ndr], where='post') + for c in tmp], color=cl[ndr], where='post') return axes @@ -175,5 +177,4 @@ def _dtlz_plot(self, pop, az=40, comp=[0, 1, 2]): return ax -from ..core import dtlz dtlz.plot = _dtlz_plot diff --git a/pygmo/pygmo_classes.hpp b/pygmo/pygmo_classes.hpp index b7efdbe6a..0e38821d0 100644 --- a/pygmo/pygmo_classes.hpp +++ b/pygmo/pygmo_classes.hpp @@ -40,6 +40,9 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include +#include +#include namespace pygmo { @@ -58,6 +61,15 @@ extern std::unique_ptr> island_ptr; // pagmo::bfe. extern std::unique_ptr> bfe_ptr; +// pagmo::topology. +extern std::unique_ptr> topology_ptr; + +// pagmo::r_policy. +extern std::unique_ptr> r_policy_ptr; + +// pagmo::s_policy. +extern std::unique_ptr> s_policy_ptr; + // Getters for the objects above. inline bp::class_ &get_problem_class() { @@ -95,6 +107,33 @@ inline bp::class_ &get_bfe_class() return *bfe_ptr; } +inline bp::class_ &get_topology_class() +{ + if (!topology_ptr) { + std::cerr << "Null topology class pointer." << std::endl; + std::abort(); + } + return *topology_ptr; +} + +inline bp::class_ &get_r_policy_class() +{ + if (!r_policy_ptr) { + std::cerr << "Null r_policy class pointer." << std::endl; + std::abort(); + } + return *r_policy_ptr; +} + +inline bp::class_ &get_s_policy_class() +{ + if (!s_policy_ptr) { + std::cerr << "Null s_policy class pointer." << std::endl; + std::abort(); + } + return *s_policy_ptr; +} + } // namespace pygmo #endif diff --git a/pygmo/r_policy.cpp b/pygmo/r_policy.cpp new file mode 100644 index 000000000..02e517058 --- /dev/null +++ b/pygmo/r_policy.cpp @@ -0,0 +1,191 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include + +// See: https://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api +// In every cpp file we need to make sure this is included before everything else, +// with the correct #defines. +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL pygmo_ARRAY_API +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +r_pol_inner::r_pol_inner(const bp::object &o) +{ + // Forbid the use of a pygmo.r_policy as a UDRP. + // The motivation here is consistency with C++. In C++, the use of + // a pagmo::r_policy as a UDRP is forbidden and prevented by the fact + // that the generic constructor from UDRP is disabled if the input + // object is a pagmo::r_policy (the copy/move constructor is + // invoked instead). In order to achieve an equivalent behaviour + // in pygmo, we throw an error if o is an r_policy, and instruct + // the user to employ the standard copy/deepcopy facilities + // for creating a copy of the input r_policy. + if (pygmo::type(o) == bp::import("pygmo").attr("r_policy")) { + pygmo_throw(PyExc_TypeError, + ("a pygmo.r_policy cannot be used as a UDRP for another pygmo.r_policy (if you need to copy a " + "replacement policy please use the standard Python copy()/deepcopy() functions)")); + } + // Check that o is an instance of a class, and not a type. + check_not_type(o, "r_policy"); + check_mandatory_method(o, "replace", "r_policy"); + m_value = pygmo::deepcopy(o); +} + +std::unique_ptr r_pol_inner::clone() const +{ + // This will make a deep copy using the ctor above. + return detail::make_unique(m_value); +} + +individuals_group_t +r_pol_inner::replace(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol, const individuals_group_t &mig) const +{ + // NOTE: replace() may be called from a separate thread in pagmo::island, need to construct a GTE before + // doing anything with the interpreter (including the throws in the checks below). + pygmo::gil_thread_ensurer gte; + + // NOTE: every time we call into the Python interpreter from a separate thread, we need to + // handle Python exceptions in a special way. + std::string r_pol_name; + try { + r_pol_name = get_name(); + } catch (const bp::error_already_set &) { + pygmo::handle_thread_py_exception("Could not fetch the name of a pythonic replacement policy. The error is:\n"); + } + + try { + // Fetch the new individuals in Python form. + bp::object o = m_value.attr("replace")(pygmo::inds_to_tuple(inds), nx, nix, nobj, nec, nic, pygmo::v_to_a(tol), + pygmo::inds_to_tuple(mig)); + + // Convert back to C++ form and return. + return pygmo::to_inds(o); + } catch (const bp::error_already_set &) { + pygmo::handle_thread_py_exception("The replace() method of a pythonic replacement policy of type '" + r_pol_name + + "' raised an error:\n"); + } +} + +std::string r_pol_inner::get_name() const +{ + return getter_wrapper(m_value, "get_name", pygmo::str(pygmo::type(m_value))); +} + +std::string r_pol_inner::get_extra_info() const +{ + return getter_wrapper(m_value, "get_extra_info", std::string{}); +} + +} // namespace detail + +} // namespace pagmo + +PAGMO_S11N_R_POLICY_IMPLEMENT(boost::python::object) + +namespace pygmo +{ + +namespace bp = boost::python; + +bp::tuple r_policy_pickle_suite::getstate(const pagmo::r_policy &r) +{ + // The idea here is that first we extract a char array + // into which t has been serialized, then we turn + // this object into a Python bytes object and return that. + std::ostringstream oss; + { + boost::archive::binary_oarchive oarchive(oss); + oarchive << r; + } + auto s = oss.str(); + // Store the serialized r_policy plus the list of currently-loaded APs. + return bp::make_tuple(make_bytes(s.data(), boost::numeric_cast(s.size())), get_ap_list()); +} + +void r_policy_pickle_suite::setstate(pagmo::r_policy &r, const bp::tuple &state) +{ + // Similarly, first we extract a bytes object from the Python state, + // and then we build a C++ string from it. The string is then used + // to deserialize the object. + if (len(state) != 2) { + pygmo_throw(PyExc_ValueError, ("the state tuple passed for r_policy deserialization " + "must have 2 elements, but instead it has " + + std::to_string(len(state)) + " elements") + .c_str()); + } + + // Make sure we import all the aps specified in the archive. + import_aps(bp::list(state[1])); + + auto ptr = PyBytes_AsString(bp::object(state[0]).ptr()); + if (!ptr) { + pygmo_throw(PyExc_TypeError, "a bytes object is needed to deserialize a r_policy"); + } + const auto size = len(state[0]); + std::string s(ptr, ptr + size); + std::istringstream iss; + iss.str(s); + { + boost::archive::binary_iarchive iarchive(iss); + iarchive >> r; + } +} + +} // namespace pygmo diff --git a/pygmo/r_policy.hpp b/pygmo/r_policy.hpp new file mode 100644 index 000000000..ad8a00fe5 --- /dev/null +++ b/pygmo/r_policy.hpp @@ -0,0 +1,119 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_R_POLICY_HPP +#define PYGMO_R_POLICY_HPP + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +// Disable the static UDRP checks for bp::object. +template <> +struct disable_udrp_checks : std::true_type { +}; + +template <> +struct r_pol_inner final : r_pol_inner_base, pygmo::common_base { + // Just need the def ctor, delete everything else. + r_pol_inner() = default; + r_pol_inner(const r_pol_inner &) = delete; + r_pol_inner(r_pol_inner &&) = delete; + r_pol_inner &operator=(const r_pol_inner &) = delete; + r_pol_inner &operator=(r_pol_inner &&) = delete; + explicit r_pol_inner(const bp::object &); + virtual std::unique_ptr clone() const override final; + // Mandatory methods. + virtual individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const override final; + // Optional methods. + virtual std::string get_name() const override final; + virtual std::string get_extra_info() const override final; + template + void save(Archive &ar, unsigned) const + { + ar << boost::serialization::base_object(*this); + ar << pygmo::object_to_vchar(m_value); + } + template + void load(Archive &ar, unsigned) + { + ar >> boost::serialization::base_object(*this); + std::vector v; + ar >> v; + m_value = pygmo::vchar_to_object(v); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + bp::object m_value; +}; + +} // namespace detail + +} // namespace pagmo + +// Register the r_pol_inner specialisation for bp::object. +PAGMO_S11N_R_POLICY_EXPORT_KEY(boost::python::object) + +namespace pygmo +{ + +namespace bp = boost::python; + +// Serialization support for the r_policy class. +struct r_policy_pickle_suite : bp::pickle_suite { + static bp::tuple getstate(const pagmo::r_policy &); + static void setstate(pagmo::r_policy &, const bp::tuple &); +}; + +} // namespace pygmo + +#endif diff --git a/pygmo/s_policy.cpp b/pygmo/s_policy.cpp new file mode 100644 index 000000000..8640ee9c5 --- /dev/null +++ b/pygmo/s_policy.cpp @@ -0,0 +1,190 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include + +// See: https://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api +// In every cpp file we need to make sure this is included before everything else, +// with the correct #defines. +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL pygmo_ARRAY_API +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +s_pol_inner::s_pol_inner(const bp::object &o) +{ + // Forbid the use of a pygmo.s_policy as a UDSP. + // The motivation here is consistency with C++. In C++, the use of + // a pagmo::s_policy as a UDSP is forbidden and prevented by the fact + // that the generic constructor from UDSP is disabled if the input + // object is a pagmo::s_policy (the copy/move constructor is + // invoked instead). In order to achieve an equivalent behaviour + // in pygmo, we throw an error if o is an s_policy, and instruct + // the user to employ the standard copy/deepcopy facilities + // for creating a copy of the input s_policy. + if (pygmo::type(o) == bp::import("pygmo").attr("s_policy")) { + pygmo_throw(PyExc_TypeError, + ("a pygmo.s_policy cannot be used as a UDSP for another pygmo.s_policy (if you need to copy a " + "selection policy please use the standard Python copy()/deepcopy() functions)")); + } + // Check that o is an instance of a class, and not a type. + check_not_type(o, "s_policy"); + check_mandatory_method(o, "select", "s_policy"); + m_value = pygmo::deepcopy(o); +} + +std::unique_ptr s_pol_inner::clone() const +{ + // This will make a deep copy using the ctor above. + return detail::make_unique(m_value); +} + +individuals_group_t s_pol_inner::select(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, + const vector_double::size_type &nobj, + const vector_double::size_type &nec, + const vector_double::size_type &nic, const vector_double &tol) const +{ + // NOTE: select() may be called from a separate thread in pagmo::island, need to construct a GTE before + // doing anything with the interpreter (including the throws in the checks below). + pygmo::gil_thread_ensurer gte; + + // NOTE: every time we call into the Python interpreter from a separate thread, we need to + // handle Python exceptions in a special way. + std::string s_pol_name; + try { + s_pol_name = get_name(); + } catch (const bp::error_already_set &) { + pygmo::handle_thread_py_exception("Could not fetch the name of a pythonic selection policy. The error is:\n"); + } + + try { + // Fetch the new individuals in Python form. + bp::object o = m_value.attr("select")(pygmo::inds_to_tuple(inds), nx, nix, nobj, nec, nic, pygmo::v_to_a(tol)); + + // Convert back to C++ form and return. + return pygmo::to_inds(o); + } catch (const bp::error_already_set &) { + pygmo::handle_thread_py_exception("The select() method of a pythonic selection policy of type '" + s_pol_name + + "' raised an error:\n"); + } +} + +std::string s_pol_inner::get_name() const +{ + return getter_wrapper(m_value, "get_name", pygmo::str(pygmo::type(m_value))); +} + +std::string s_pol_inner::get_extra_info() const +{ + return getter_wrapper(m_value, "get_extra_info", std::string{}); +} + +} // namespace detail + +} // namespace pagmo + +PAGMO_S11N_S_POLICY_IMPLEMENT(boost::python::object) + +namespace pygmo +{ + +namespace bp = boost::python; + +bp::tuple s_policy_pickle_suite::getstate(const pagmo::s_policy &sp) +{ + // The idea here is that first we extract a char array + // into which t has been serialized, then we turn + // this object into a Python bytes object and return that. + std::ostringstream oss; + { + boost::archive::binary_oarchive oarchive(oss); + oarchive << sp; + } + auto s = oss.str(); + // Store the serialized s_policy plus the list of currently-loaded APs. + return bp::make_tuple(make_bytes(s.data(), boost::numeric_cast(s.size())), get_ap_list()); +} + +void s_policy_pickle_suite::setstate(pagmo::s_policy &sp, const bp::tuple &state) +{ + // Similarly, first we extract a bytes object from the Python state, + // and then we build a C++ string from it. The string is then used + // to deserialize the object. + if (len(state) != 2) { + pygmo_throw(PyExc_ValueError, ("the state tuple passed for s_policy deserialization " + "must have 2 elements, but instead it has " + + std::to_string(len(state)) + " elements") + .c_str()); + } + + // Make sure we import all the aps specified in the archive. + import_aps(bp::list(state[1])); + + auto ptr = PyBytes_AsString(bp::object(state[0]).ptr()); + if (!ptr) { + pygmo_throw(PyExc_TypeError, "a bytes object is needed to deserialize a s_policy"); + } + const auto size = len(state[0]); + std::string s(ptr, ptr + size); + std::istringstream iss; + iss.str(s); + { + boost::archive::binary_iarchive iarchive(iss); + iarchive >> sp; + } +} + +} // namespace pygmo diff --git a/pygmo/s_policy.hpp b/pygmo/s_policy.hpp new file mode 100644 index 000000000..14f1c2b03 --- /dev/null +++ b/pygmo/s_policy.hpp @@ -0,0 +1,119 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_S_POLICY_HPP +#define PYGMO_S_POLICY_HPP + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +// Disable the static UDSP checks for bp::object. +template <> +struct disable_udsp_checks : std::true_type { +}; + +template <> +struct s_pol_inner final : s_pol_inner_base, pygmo::common_base { + // Just need the def ctor, delete everything else. + s_pol_inner() = default; + s_pol_inner(const s_pol_inner &) = delete; + s_pol_inner(s_pol_inner &&) = delete; + s_pol_inner &operator=(const s_pol_inner &) = delete; + s_pol_inner &operator=(s_pol_inner &&) = delete; + explicit s_pol_inner(const bp::object &); + virtual std::unique_ptr clone() const override final; + // Mandatory methods. + virtual individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const override final; + // Optional methods. + virtual std::string get_name() const override final; + virtual std::string get_extra_info() const override final; + template + void save(Archive &ar, unsigned) const + { + ar << boost::serialization::base_object(*this); + ar << pygmo::object_to_vchar(m_value); + } + template + void load(Archive &ar, unsigned) + { + ar >> boost::serialization::base_object(*this); + std::vector v; + ar >> v; + m_value = pygmo::vchar_to_object(v); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + bp::object m_value; +}; + +} // namespace detail + +} // namespace pagmo + +// Register the s_pol_inner specialisation for bp::object. +PAGMO_S11N_S_POLICY_EXPORT_KEY(boost::python::object) + +namespace pygmo +{ + +namespace bp = boost::python; + +// Serialization support for the s_policy class. +struct s_policy_pickle_suite : bp::pickle_suite { + static bp::tuple getstate(const pagmo::s_policy &); + static void setstate(pagmo::s_policy &, const bp::tuple &); +}; + +} // namespace pygmo + +#endif diff --git a/pygmo/sr_policy_add_rate_constructor.hpp b/pygmo/sr_policy_add_rate_constructor.hpp new file mode 100644 index 000000000..0c4ff45fa --- /dev/null +++ b/pygmo/sr_policy_add_rate_constructor.hpp @@ -0,0 +1,77 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_SR_POLICY_ADD_RATE_CONSTRUCTOR_HPP +#define PYGMO_SR_POLICY_ADD_RATE_CONSTRUCTOR_HPP + +#include +#include +#include +#include +#include +#include + +#include + +namespace pygmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +// An helper to add a constructor from a migration rate to a +// replacement/selection policy. +template +inline void sr_policy_add_rate_constructor(bp::class_ &c) +{ + c.def("__init__", + bp::make_constructor( + lcast([](const bp::object &o) -> Pol * { + if (pygmo::isinstance(o, pygmo::builtin().attr("int"))) { + const int r = bp::extract(o); + return ::new Pol(r); + } else if (pygmo::isinstance(o, pygmo::builtin().attr("float"))) { + const double r = bp::extract(o); + return ::new Pol(r); + } else { + pygmo_throw(PyExc_TypeError, + ("cannot construct a replacement/selection policy from a migration rate of type '" + + str(type(o)) + "': the migration rate must be an integral or floating-point value") + .c_str()); + } + }), + bp::default_call_policies(), (bp::arg("rate")))); +} + +} // namespace detail + +} // namespace pygmo + +#endif diff --git a/pygmo/test.py b/pygmo/test.py index 6cc270933..1ed888b5b 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -80,6 +80,18 @@ def get_bounds(self): return ([0], [1]) +class _r_pol(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return inds + + +class _s_pol(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return inds + + class core_test_case(_ut.TestCase): """Test case for core PyGMO functionality. @@ -371,7 +383,7 @@ def run_pickle_test(self): class golomb_ruler_test_case(_ut.TestCase): - """Test case for the UDA de + """Test case for the Golomb ruler UDP """ @@ -382,7 +394,7 @@ def runTest(self): class lennard_jones_test_case(_ut.TestCase): - """Test case for the UDA de + """Test case for the Lennard-Jones UDP """ @@ -1814,6 +1826,9 @@ def runTest(self): self.run_pickle_tests() self.run_champions_tests() self.run_status_tests() + self.run_mig_log_db_tests() + self.run_get_set_topo_tests() + self.run_mt_mh_tests() if self._level > 0: self.run_torture_test_0() # NOTE: skip this test for the time being. @@ -1827,9 +1842,11 @@ def runTest(self): # investigate further if we ever want to # turn it back on. # self.run_torture_test_1() + self.run_migration_torture_test() def run_init_tests(self): - from . import archipelago, de, rosenbrock, population, null_problem, thread_island, mp_island + from . import (archipelago, de, rosenbrock, population, null_problem, thread_island, + mp_island, topology, unconnected, ring, r_policy, s_policy, fair_replace, select_best) a = archipelago() self.assertEqual(len(a), 0) self.assertRaises(IndexError, lambda: a[0]) @@ -1896,6 +1913,89 @@ def run_init_tests(self): self.assertRaises(KeyError, lambda: archipelago( 5, pop=population(), algo=de(), seed=1)) + # Constructors from topology and custom r_pol, s_pol. + a = archipelago(5, t=topology(), algo=de(), prob=rosenbrock(), + pop_size=10, udi=mp_island(), seed=5) + self.assertTrue(a.get_topology().is_(unconnected)) + self.assertTrue( + all([isl.get_r_policy().is_(fair_replace) for isl in a])) + self.assertTrue( + all([isl.get_s_policy().is_(select_best) for isl in a])) + + a = archipelago(5, t=topology(ring()), algo=de(), prob=rosenbrock(), + pop_size=10, udi=mp_island(), seed=5) + self.assertTrue(a.get_topology().is_(ring)) + + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), + pop_size=10, udi=mp_island(), seed=5) + self.assertTrue(a.get_topology().is_(ring)) + + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), + pop_size=10, udi=mp_island(), seed=5, r_pol=r_policy()) + self.assertTrue( + all([isl.get_r_policy().is_(fair_replace) for isl in a])) + self.assertTrue( + all([isl.get_s_policy().is_(select_best) for isl in a])) + + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), + pop_size=10, udi=mp_island(), seed=5, r_pol=r_policy(), s_pol=s_policy()) + self.assertTrue( + all([isl.get_r_policy().is_(fair_replace) for isl in a])) + self.assertTrue( + all([isl.get_s_policy().is_(select_best) for isl in a])) + + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), + pop_size=10, udi=mp_island(), seed=5, r_pol=_r_pol(), s_pol=_s_pol()) + self.assertTrue(all([isl.get_r_policy().is_(_r_pol) for isl in a])) + self.assertTrue(all([isl.get_s_policy().is_(_s_pol) for isl in a])) + + def run_mig_log_db_tests(self): + from . import archipelago, de, rosenbrock, ring + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), pop_size=10) + a.evolve(10) + a.wait_check() + + db = a.get_migrants_db() + self.assertEqual(len(db), 5) + for g in db: + self.assertEqual(len(g), 3) + self.assertEqual(len(g[0]), 1) + self.assertEqual(g[1].shape, (1, 2)) + self.assertEqual(g[2].shape, (1, 1)) + + log = a.get_migration_log() + for e in log: + self.assertEqual(len(e), 6) + self.assertEqual(e[2].shape, (2,)) + self.assertEqual(e[3].shape, (1,)) + self.assertTrue(e[4] < 5) + self.assertTrue(e[5] < 5) + + def run_get_set_topo_tests(self): + from . import archipelago, de, rosenbrock, ring, topology, fully_connected + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), pop_size=10) + self.assertTrue(a.get_topology().is_(ring)) + + # Evolve, then set topology (will trigger wait()). + a.evolve(100) + a.set_topology(topology(fully_connected(5))) + self.assertTrue(a.get_topology().is_(fully_connected)) + a.set_topology(ring(5)) + self.assertTrue(a.get_topology().is_(ring)) + + def run_mt_mh_tests(self): + from . import archipelago, de, rosenbrock, migration_type, ring, migrant_handling + + a = archipelago(5, t=ring(), algo=de(), prob=rosenbrock(), pop_size=10) + self.assertEqual(a.get_migration_type(), migration_type.p2p) + self.assertEqual(a.get_migrant_handling(), migrant_handling.preserve) + a.evolve(100) + a.set_migration_type(migration_type.broadcast) + self.assertEqual(a.get_migration_type(), migration_type.broadcast) + a.set_migrant_handling(migrant_handling.evict) + self.assertEqual(a.get_migrant_handling(), migrant_handling.evict) + a.wait_check() + def run_evolve_tests(self): from . import archipelago, de, rosenbrock, mp_island, evolve_status from copy import deepcopy @@ -1975,7 +2075,8 @@ def run_access_tests(self): del i0, i1, i2, i3 def run_push_back_tests(self): - from . import archipelago, de, rosenbrock + from . import (archipelago, de, rosenbrock, r_policy, + s_policy, fair_replace, select_best) a = archipelago(5, algo=de(), prob=rosenbrock(), pop_size=10) # Push back while evolving. a.evolve(10) @@ -2005,13 +2106,37 @@ def run_push_back_tests(self): self.assertTrue(a[i].get_population().problem.is_(rosenbrock)) self.assertEqual(len(a[i].get_population()), 11) + # Push back with custom policies. + a = archipelago(5, algo=de(), prob=rosenbrock(), pop_size=10) + self.assertTrue( + all([isl.get_r_policy().is_(fair_replace) for isl in a])) + self.assertTrue( + all([isl.get_s_policy().is_(select_best) for isl in a])) + + a.push_back(algo=de(), prob=rosenbrock(), size=11, r_pol=r_policy()) + a.push_back(algo=de(), prob=rosenbrock(), size=11, + r_pol=r_policy(), s_pol=s_policy()) + self.assertTrue( + all([isl.get_r_policy().is_(fair_replace) for isl in a])) + self.assertTrue( + all([isl.get_s_policy().is_(select_best) for isl in a])) + + a.push_back(algo=de(), prob=rosenbrock(), size=11, + r_pol=_r_pol(), s_pol=_s_pol()) + a.push_back(algo=de(), prob=rosenbrock(), size=11, + r_pol=_r_pol(), s_pol=_s_pol()) + self.assertTrue(all([a[i].get_r_policy().is_(_r_pol) + for i in range(7, 9)])) + self.assertTrue(all([a[i].get_s_policy().is_(_s_pol) + for i in range(7, 9)])) + def run_io_tests(self): from . import archipelago, de, rosenbrock a = archipelago(5, algo=de(), prob=rosenbrock(), pop_size=10) self.assertFalse(repr(a) == "") def run_pickle_tests(self): - from . import archipelago, de, rosenbrock, mp_island + from . import archipelago, de, rosenbrock, mp_island, ring, migration_type, migrant_handling from pickle import dumps, loads import sys import os @@ -2024,6 +2149,18 @@ def run_pickle_tests(self): pop_size=10, udi=mp_island()) self.assertEqual(repr(a), repr(loads(dumps(a)))) + # Test also with custom topology, mh and mt. + a = archipelago(n=5, t=ring(), algo=de(), + prob=rosenbrock(), pop_size=10) + a.set_migration_type(migration_type.broadcast) + a.set_migrant_handling(migrant_handling.evict) + self.assertEqual(repr(a), repr(loads(dumps(a)))) + self.assertTrue(loads(dumps(a)).get_topology().is_(ring)) + self.assertEqual(loads(dumps(a)).get_migration_type(), + migration_type.broadcast) + self.assertEqual(loads(dumps(a)).get_migrant_handling(), + migrant_handling.evict) + def run_champions_tests(self): from . import archipelago, de, rosenbrock, zdt from numpy import ndarray @@ -2104,6 +2241,273 @@ def run_torture_test_1(self): 500), prob=ackley(50), pop_size=50) archi.evolve() + def run_migration_torture_test(self): + from . import archipelago, de, rosenbrock, fair_replace, select_best, r_policy, s_policy + import threading + + # Use custom UDT, UDRP and UDSP for the torture test. + + # NOTE: re-implementation of a fully_connected + # topology. + class udt(object): + def __init__(self, n=0): + self._n = n + self._lock = threading.Lock() + + def get_n(self): + with self._lock: + n = self._n + return n + + def __copy__(self): + return udt(self.get_n()) + + def __deepcopy__(self, d): + return self.__copy__() + + def push_back(self): + with self._lock: + self._n = self._n + 1 + + def get_connections(self, i): + with self._lock: + n = self._n + return (list(range(0, i)) + list(range(i+1, n)), [1.]*(n-1)) + + # NOTE: these two will just re-use fair_replace/select_best internally. + class udrp(object): + + def replace(self, inds, nx, nix, nobj, nec, nic, tol, mig): + return r_policy(fair_replace()).replace(inds, nx, nix, nobj, nec, nic, tol, mig) + + class udsp(object): + + def select(self, inds, nx, nix, nobj, nec, nic, tol): + return s_policy(select_best()).select(inds, nx, nix, nobj, nec, nic, tol) + + archi = archipelago(n=100, t=udt(), algo=de( + 1), prob=rosenbrock(), pop_size=10, seed=32, r_pol=udrp(), s_pol=udsp()) + + archi.evolve(100) + archi.wait_check() + + +class unconnected_test_case(_ut.TestCase): + """Test case for the unconnected UDT + + """ + + def runTest(self): + from .core import unconnected, topology + udt = unconnected() + topo = topology(udt=udt) + self.assertTrue(len(topo.get_connections(100)[0]) == 0) + self.assertTrue(len(topo.get_connections(100)[1]) == 0) + topo.push_back() + topo.push_back() + topo.push_back() + self.assertTrue(len(topo.get_connections(100)[0]) == 0) + self.assertTrue(len(topo.get_connections(100)[1]) == 0) + self.assertEqual(topo.get_name(), "Unconnected") + + +class fair_replace_test_case(_ut.TestCase): + """Test case for the fair replace UDRP + + """ + + def runTest(self): + from .core import fair_replace, r_policy + udrp = fair_replace() + r_pol = r_policy(udrp=udrp) + self.assertEqual(r_pol.get_name(), "Fair replace") + self.assertTrue("Absolute migration rate: 1" in repr(r_pol)) + r_pol = r_policy(udrp=fair_replace(rate=0)) + self.assertTrue("Absolute migration rate: 0" in repr(r_pol)) + r_pol = r_policy(udrp=fair_replace(2)) + self.assertTrue("Absolute migration rate: 2" in repr(r_pol)) + r_pol = r_policy(udrp=fair_replace(rate=.5)) + self.assertTrue("Fractional migration rate: 0.5" in repr(r_pol)) + + with self.assertRaises(ValueError) as cm: + r_policy(udrp=fair_replace(-1.2)) + err = cm.exception + self.assertTrue( + "Invalid fractional migration rate " in str(err)) + + with self.assertRaises(TypeError) as cm: + r_policy(udrp=fair_replace(rate="dsadasdas")) + err = cm.exception + self.assertTrue( + "the migration rate must be an integral or floating-point value" in str(err)) + + +class select_best_test_case(_ut.TestCase): + """Test case for the select best UDSP + + """ + + def runTest(self): + from .core import select_best, s_policy + udsp = select_best() + s_pol = s_policy(udsp=udsp) + self.assertEqual(s_pol.get_name(), "Select best") + self.assertTrue("Absolute migration rate: 1" in repr(s_pol)) + s_pol = s_policy(udsp=select_best(rate=0)) + self.assertTrue("Absolute migration rate: 0" in repr(s_pol)) + s_pol = s_policy(udsp=select_best(2)) + self.assertTrue("Absolute migration rate: 2" in repr(s_pol)) + s_pol = s_policy(udsp=select_best(rate=.5)) + self.assertTrue("Fractional migration rate: 0.5" in repr(s_pol)) + + with self.assertRaises(ValueError) as cm: + s_policy(udsp=select_best(-1.2)) + err = cm.exception + self.assertTrue( + "Invalid fractional migration rate " in str(err)) + + with self.assertRaises(TypeError) as cm: + s_policy(udsp=select_best(rate="dsadasdas")) + err = cm.exception + self.assertTrue( + "the migration rate must be an integral or floating-point value" in str(err)) + + +class ring_test_case(_ut.TestCase): + """Test case for the ring UDT + + """ + + def runTest(self): + from .core import ring, topology + + udt = ring() + self.assertTrue(udt.num_vertices() == 0) + self.assertTrue(udt.get_weight() == 1.) + + udt = ring(w=.5) + self.assertTrue(udt.num_vertices() == 0) + self.assertTrue(udt.get_weight() == .5) + + udt = ring(n=10, w=.1) + self.assertTrue(udt.num_vertices() == 10) + self.assertTrue(udt.get_weight() == .1) + + with self.assertRaises(ValueError) as cm: + ring(n=10, w=2.) + err = cm.exception + self.assertTrue( + "invalid weight for the edge of a topology: the value " in str(err)) + + with self.assertRaises(OverflowError) as cm: + ring(n=-10, w=.5) + + udt = ring(n=5) + self.assertTrue(udt.are_adjacent(4, 0)) + self.assertTrue(udt.are_adjacent(0, 4)) + self.assertTrue(not udt.are_adjacent(4, 1)) + self.assertTrue(not udt.are_adjacent(1, 4)) + udt.add_edge(1, 4) + self.assertTrue(not udt.are_adjacent(4, 1)) + self.assertTrue(udt.are_adjacent(1, 4)) + udt.remove_edge(1, 4) + self.assertTrue(not udt.are_adjacent(1, 4)) + udt.add_vertex() + self.assertTrue(not udt.are_adjacent(5, 1)) + self.assertTrue(not udt.are_adjacent(1, 5)) + self.assertEqual(udt.num_vertices(), 6) + udt.set_weight(0, 4, .5) + self.assertTrue("0.5" in repr(topology(udt))) + udt.set_all_weights(0.25) + self.assertTrue("0.25" in repr(topology(udt))) + self.assertEqual(topology(udt).get_name(), "Ring") + + with self.assertRaises(ValueError) as cm: + udt.are_adjacent(100, 101) + err = cm.exception + self.assertTrue( + "invalid vertex index in a BGL topology: the index is 100, but the number of vertices is only 6" in str(err)) + + with self.assertRaises(OverflowError) as cm: + udt.are_adjacent(-1, -1) + + with self.assertRaises(ValueError) as cm: + udt.add_edge(100, 101) + err = cm.exception + self.assertTrue( + "invalid vertex index in a BGL topology: the index is 100, but the number of vertices is only 6" in str(err)) + + with self.assertRaises(OverflowError) as cm: + udt.add_edge(-1, -1) + + with self.assertRaises(ValueError) as cm: + udt.add_edge(1, 4, -1.) + + with self.assertRaises(ValueError) as cm: + udt.remove_edge(100, 101) + err = cm.exception + self.assertTrue( + "invalid vertex index in a BGL topology: the index is 100, but the number of vertices is only 6" in str(err)) + + with self.assertRaises(OverflowError) as cm: + udt.remove_edge(-1, -1) + + with self.assertRaises(ValueError) as cm: + udt.set_weight(100, 101, .5) + err = cm.exception + self.assertTrue( + "invalid vertex index in a BGL topology: the index is 100, but the number of vertices is only 6" in str(err)) + + with self.assertRaises(OverflowError) as cm: + udt.set_weight(-1, -1, .5) + + with self.assertRaises(ValueError) as cm: + udt.set_weight(2, 3, -.5) + + with self.assertRaises(ValueError) as cm: + udt.set_all_weights(-.5) + + topo = topology(udt=ring(3)) + self.assertTrue(len(topo.get_connections(0)[0]) == 2) + self.assertTrue(len(topo.get_connections(0)[1]) == 2) + topo.push_back() + topo.push_back() + topo.push_back() + self.assertTrue(len(topo.get_connections(3)[0]) == 2) + self.assertTrue(len(topo.get_connections(3)[1]) == 2) + self.assertEqual(topo.get_name(), "Ring") + + +class fully_connected_test_case(_ut.TestCase): + """Test case for the fully_connected UDT + + """ + + def runTest(self): + from .core import fully_connected, topology + + udt = fully_connected() + self.assertEqual(udt.num_vertices(), 0) + self.assertEqual(udt.get_weight(), 1.) + + udt = fully_connected(w=.5) + self.assertEqual(udt.num_vertices(), 0) + self.assertEqual(udt.get_weight(), .5) + + udt = fully_connected(w=.5, n=10) + self.assertEqual(udt.num_vertices(), 10) + self.assertEqual(udt.get_weight(), .5) + + topo = topology(udt=fully_connected(10)) + self.assertTrue(len(topo.get_connections(1)[0]) == 9) + self.assertTrue(len(topo.get_connections(1)[1]) == 9) + topo.push_back() + topo.push_back() + topo.push_back() + self.assertTrue(len(topo.get_connections(5)[0]) == 12) + self.assertTrue(len(topo.get_connections(5)[1]) == 12) + self.assertEqual(topo.get_name(), "Fully connected") + def run_test_suite(level=0): """Run the full test suite. @@ -2114,7 +2518,7 @@ def run_test_suite(level=0): level(``int``): the test level (higher values run longer tests) """ - from . import _problem_test, _algorithm_test, _island_test, set_global_rng_seed + from . import _problem_test, _algorithm_test, _island_test, _topology_test, _r_policy_test, _s_policy_test, set_global_rng_seed # Make test runs deterministic. # NOTE: we'll need to place the async/migration tests at the end, so that at @@ -2123,10 +2527,19 @@ def run_test_suite(level=0): retval = 0 suite = _ut.TestLoader().loadTestsFromTestCase(core_test_case) + suite.addTest(archipelago_test_case(level)) + suite.addTest(_island_test.island_test_case()) + suite.addTest(_s_policy_test.s_policy_test_case()) + suite.addTest(_r_policy_test.r_policy_test_case()) + suite.addTest(_topology_test.topology_test_case()) + suite.addTest(fair_replace_test_case()) + suite.addTest(select_best_test_case()) + suite.addTest(unconnected_test_case()) + suite.addTest(ring_test_case()) + suite.addTest(fully_connected_test_case()) suite.addTest(thread_island_torture_test_case()) suite.addTest(_problem_test.problem_test_case()) suite.addTest(_algorithm_test.algorithm_test_case()) - suite.addTest(_island_test.island_test_case()) suite.addTest(_island_test.mp_island_test_case(level)) suite.addTest(_island_test.ipyparallel_island_test_case(level)) suite.addTest(golomb_ruler_test_case()) @@ -2146,7 +2559,6 @@ def run_test_suite(level=0): suite.addTest(sga_test_case()) suite.addTest(ihs_test_case()) suite.addTest(population_test_case()) - suite.addTest(archipelago_test_case(level)) suite.addTest(null_problem_test_case()) suite.addTest(hypervolume_test_case()) suite.addTest(mo_utils_test_case()) diff --git a/pygmo/topology.cpp b/pygmo/topology.cpp new file mode 100644 index 000000000..058fadb89 --- /dev/null +++ b/pygmo/topology.cpp @@ -0,0 +1,226 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include + +// See: https://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api +// In every cpp file we need to make sure this is included before everything else, +// with the correct #defines. +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL pygmo_ARRAY_API +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +topo_inner::topo_inner(const bp::object &o) +{ + // Forbid the use of a pygmo.topology as a UDT. + // The motivation here is consistency with C++. In C++, the use of + // a pagmo::topology as a UDT is forbidden and prevented by the fact + // that the generic constructor from UDT is disabled if the input + // object is a pagmo::topology (the copy/move constructor is + // invoked instead). In order to achieve an equivalent behaviour + // in pygmo, we throw an error if o is a topology, and instruct + // the user to employ the standard copy/deepcopy facilities + // for creating a copy of the input topology. + if (pygmo::type(o) == bp::import("pygmo").attr("topology")) { + pygmo_throw(PyExc_TypeError, + ("a pygmo.topology cannot be used as a UDT for another pygmo.topology (if you need to copy a " + "topology please use the standard Python copy()/deepcopy() functions)")); + } + // Check that o is an instance of a class, and not a type. + check_not_type(o, "topology"); + check_mandatory_method(o, "get_connections", "topology"); + check_mandatory_method(o, "push_back", "topology"); + m_value = pygmo::deepcopy(o); +} + +std::unique_ptr topo_inner::clone() const +{ + // This will make a deep copy using the ctor above. + return detail::make_unique(m_value); +} + +std::pair, vector_double> topo_inner::get_connections(std::size_t n) const +{ + // NOTE: get_connections() may be called from a separate thread in pagmo::island, need to construct a GTE before + // doing anything with the interpreter (including the throws in the checks below). + pygmo::gil_thread_ensurer gte; + + // NOTE: every time we call into the Python interpreter from a separate thread, we need to + // handle Python exceptions in a special way. + std::string topo_name; + try { + topo_name = get_name(); + } catch (const bp::error_already_set &) { + pygmo::handle_thread_py_exception("Could not fetch the name of a pythonic topology. The error is:\n"); + } + + try { + // Fetch the connections in Python form. + bp::object o = m_value.attr("get_connections")(n); + + // Prepare the return value. + std::pair, vector_double> retval; + + // We will try to interpret o as a collection of generic python objects. + bp::stl_input_iterator begin(o), end; + + if (begin == end) { + // Empty iteratable. + pygmo_throw(PyExc_ValueError, ("the iteratable returned by a topology of type '" + topo_name + + "' is empty (it should contain 2 elements)") + .c_str()); + } + + retval.first = pygmo::to_vuint(*begin); + + if (++begin == end) { + // Only one element in the iteratable. + pygmo_throw(PyExc_ValueError, ("the iteratable returned by a topology of type '" + topo_name + + "' has only 1 element (it should contain 2 elements)") + .c_str()); + } + + retval.second = pygmo::to_vd(*begin); + + if (++begin != end) { + // Too many elements. + pygmo_throw(PyExc_ValueError, ("the iteratable returned by a topology of type '" + topo_name + + "' has more than 2 elements (it should contain 2 elements)") + .c_str()); + } + + return retval; + } catch (const bp::error_already_set &) { + pygmo::handle_thread_py_exception("The get_connections() method of a pythonic topology of type '" + topo_name + + "' raised an error:\n"); + } +} + +void topo_inner::push_back() +{ + m_value.attr("push_back")(); +} + +std::string topo_inner::get_name() const +{ + return getter_wrapper(m_value, "get_name", pygmo::str(pygmo::type(m_value))); +} + +std::string topo_inner::get_extra_info() const +{ + return getter_wrapper(m_value, "get_extra_info", std::string{}); +} + +} // namespace detail + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_IMPLEMENT(boost::python::object) + +namespace pygmo +{ + +namespace bp = boost::python; + +bp::tuple topology_pickle_suite::getstate(const pagmo::topology &t) +{ + // The idea here is that first we extract a char array + // into which t has been serialized, then we turn + // this object into a Python bytes object and return that. + std::ostringstream oss; + { + boost::archive::binary_oarchive oarchive(oss); + oarchive << t; + } + auto s = oss.str(); + // Store the serialized topology plus the list of currently-loaded APs. + return bp::make_tuple(make_bytes(s.data(), boost::numeric_cast(s.size())), get_ap_list()); +} + +void topology_pickle_suite::setstate(pagmo::topology &t, const bp::tuple &state) +{ + // Similarly, first we extract a bytes object from the Python state, + // and then we build a C++ string from it. The string is then used + // to deserialize the object. + if (len(state) != 2) { + pygmo_throw(PyExc_ValueError, ("the state tuple passed for topology deserialization " + "must have 2 elements, but instead it has " + + std::to_string(len(state)) + " elements") + .c_str()); + } + + // Make sure we import all the aps specified in the archive. + import_aps(bp::list(state[1])); + + auto ptr = PyBytes_AsString(bp::object(state[0]).ptr()); + if (!ptr) { + pygmo_throw(PyExc_TypeError, "a bytes object is needed to deserialize a topology"); + } + const auto size = len(state[0]); + std::string s(ptr, ptr + size); + std::istringstream iss; + iss.str(s); + { + boost::archive::binary_iarchive iarchive(iss); + iarchive >> t; + } +} + +} // namespace pygmo diff --git a/pygmo/topology.hpp b/pygmo/topology.hpp new file mode 100644 index 000000000..9592045e2 --- /dev/null +++ b/pygmo/topology.hpp @@ -0,0 +1,119 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#ifndef PYGMO_TOPOLOGY_HPP +#define PYGMO_TOPOLOGY_HPP + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace bp = boost::python; + +// Disable the static UDT checks for bp::object. +template <> +struct disable_udt_checks : std::true_type { +}; + +template <> +struct topo_inner final : topo_inner_base, pygmo::common_base { + // Just need the def ctor, delete everything else. + topo_inner() = default; + topo_inner(const topo_inner &) = delete; + topo_inner(topo_inner &&) = delete; + topo_inner &operator=(const topo_inner &) = delete; + topo_inner &operator=(topo_inner &&) = delete; + explicit topo_inner(const bp::object &); + virtual std::unique_ptr clone() const override final; + // Mandatory methods. + virtual std::pair, vector_double> get_connections(std::size_t) const override final; + virtual void push_back() override final; + // Optional methods. + virtual std::string get_name() const override final; + virtual std::string get_extra_info() const override final; + template + void save(Archive &ar, unsigned) const + { + ar << boost::serialization::base_object(*this); + ar << pygmo::object_to_vchar(m_value); + } + template + void load(Archive &ar, unsigned) + { + ar >> boost::serialization::base_object(*this); + std::vector v; + ar >> v; + m_value = pygmo::vchar_to_object(v); + } + BOOST_SERIALIZATION_SPLIT_MEMBER() + bp::object m_value; +}; + +} // namespace detail + +} // namespace pagmo + +// Register the topo_inner specialisation for bp::object. +PAGMO_S11N_TOPOLOGY_EXPORT_KEY(boost::python::object) + +namespace pygmo +{ + +namespace bp = boost::python; + +// Serialization support for the topology class. +struct topology_pickle_suite : bp::pickle_suite { + static bp::tuple getstate(const pagmo::topology &); + static void setstate(pagmo::topology &, const bp::tuple &); +}; + +} // namespace pygmo + +#endif diff --git a/src/archipelago.cpp b/src/archipelago.cpp index a2ff223e4..89a26ffbf 100644 --- a/src/archipelago.cpp +++ b/src/archipelago.cpp @@ -27,20 +27,37 @@ GNU Lesser General Public License along with the PaGMO library. If not, see https://www.gnu.org/licenses/. */ #include +#include #include +#include +#include #include +#include +#include #include +#include #include #include +#include +#include #include #include +#include + #include #include #include #include +#include #include +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + namespace pagmo { @@ -55,32 +72,55 @@ void archipelago::wait_check_ignore() /// Default constructor. /** - * The default constructor will initialise an empty archipelago. + * \verbatim embed:rst:leading-asterisk + * The default constructor will initialise an empty archipelago with a + * default-constructed (i.e., :cpp:class:`~pagmo::unconnected`) topology, + * a point-to-point :cpp:enum:`~pagmo::migration_type` and a + * preserve :cpp:enum:`~pagmo::migrant_handling` policy. + * \endverbatim */ -archipelago::archipelago() {} +archipelago::archipelago() + : m_migr_type(migration_type::p2p), // Default: point-to-point migration type. + m_migr_handling(migrant_handling::preserve) // Default: preserve migrants. +{ +} /// Copy constructor. /** - * The islands of \p other will be copied into \p this via archipelago::push_back(). + * The copy constructor will perform a deep copy of \p other. * * @param other the archipelago that will be copied. * - * @throws unspecified any exception thrown by archipelago::push_back(). + * @throws unspecified any exception thrown by the public interface + * of pagmo::archipelago. */ archipelago::archipelago(const archipelago &other) { for (const auto &iptr : other.m_islands) { // This will end up copying the island members, - // and assign the archi pointer as well. + // and assign the archi pointer, and associating ids to island pointers. push_back(*iptr); } + + // Set the migrants. + m_migrants = other.get_migrants_db(); + + // Set the migration log. + m_migr_log = other.get_migration_log(); + + // Set the topology. + m_topology = other.get_topology(); + + // Migration type and migrant handling policy. + m_migr_type.store(other.m_migr_type.load(std::memory_order_relaxed), std::memory_order_relaxed); + m_migr_handling.store(other.m_migr_handling.load(std::memory_order_relaxed), std::memory_order_relaxed); } /// Move constructor. /** * The move constructor will wait for any ongoing evolution in \p other to finish * and it will then transfer the state of \p other into \p this. After the move, - * \p other is left in an unspecified but valid state. + * \p other is left in a state which is assignable and destructible. * * @param other the archipelago that will be moved. */ @@ -89,13 +129,40 @@ archipelago::archipelago(archipelago &&other) noexcept // NOTE: in move operations we have to wait, because the ongoing // island evolutions are interacting with their hosting archi 'other'. // We cannot just move in the vector of islands. + // NOTE: we want to ensure that other is in a known state + // after the move, so that we can run assertion checks in + // the destructor in debug mode. other.wait_check_ignore(); - // Move in the islands. + + // Move in the islands, make sure that other is cleared. m_islands = std::move(other.m_islands); + other.m_islands.clear(); // Re-direct the archi pointers to point to this. for (const auto &iptr : m_islands) { iptr->m_ptr->archi_ptr = this; } + + // Move over the indices, clear other. + // NOTE: the indices are still valid as above we just moved in a vector + // of unique_ptrs, without changing their content. + m_idx_map = std::move(other.m_idx_map); + other.m_idx_map.clear(); + + // Move over the migrants, clear other. + m_migrants = std::move(other.m_migrants); + other.m_migrants.clear(); + + // Move over the migration log, clear other. + m_migr_log = std::move(other.m_migr_log); + other.m_migr_log.clear(); + + // Move over the topology. No need to clear here as we know + // in which state the topology will be in after the move. + m_topology = std::move(other.m_topology); + + // Migration type and migrant handling policy. + m_migr_type.store(other.m_migr_type.load(std::memory_order_relaxed), std::memory_order_relaxed); + m_migr_handling.store(other.m_migr_handling.load(std::memory_order_relaxed), std::memory_order_relaxed); } /// Copy assignment. @@ -119,7 +186,8 @@ archipelago &archipelago::operator=(const archipelago &other) /// Move assignment. /** * Move assignment will transfer the state of \p other into \p this, after any ongoing - * evolution in \p this and \p other has finished. + * evolution in \p this and \p other has finished. After the move, + * \p other is left in a state which is assignable and destructible. * * @param other the assignment argument. * @@ -130,14 +198,38 @@ archipelago &archipelago::operator=(archipelago &&other) noexcept if (this != &other) { // NOTE: as in the move ctor, we need to wait on other and this as well. // This mirrors the island's behaviour. + // NOTE: we want to ensure that other is in a known state + // after the move, so that we can run assertion checks in + // the destructor in debug mode. wait_check_ignore(); other.wait_check_ignore(); - // Move in the islands. + + // Move in the islands, clear other. m_islands = std::move(other.m_islands); + other.m_islands.clear(); // Re-direct the archi pointers to point to this. for (const auto &iptr : m_islands) { iptr->m_ptr->archi_ptr = this; } + + // Move the indices map, clear other. + m_idx_map = std::move(other.m_idx_map); + other.m_idx_map.clear(); + + // Move over the migrants, clear other. + m_migrants = std::move(other.m_migrants); + other.m_migrants.clear(); + + // Move over the migration log, clear other. + m_migr_log = std::move(other.m_migr_log); + other.m_migr_log.clear(); + + // Move over the topology. + m_topology = std::move(other.m_topology); + + // Migration type and migrant handling policy. + m_migr_type.store(other.m_migr_type.load(std::memory_order_relaxed), std::memory_order_relaxed); + m_migr_handling.store(other.m_migr_handling.load(std::memory_order_relaxed), std::memory_order_relaxed); } return *this; } @@ -149,11 +241,29 @@ archipelago &archipelago::operator=(archipelago &&other) noexcept */ archipelago::~archipelago() { - // NOTE: this is not strictly necessary, but it will not hurt. And, if we add further - // sanity checks, we know the archi is stopped. + // NOTE: make sure we stop the archi before running checks below without locking. + // NOTE: this is also important to ensure everything is stopped before we start + // destroying things, so that the destruction order will not matter. wait_check_ignore(); + + // NOTE: we made sure in the move ctor/assignment that the island vector, the migrants and the indices + // map are all cleared out after a move. Thus we can safely assert the following. assert(std::all_of(m_islands.begin(), m_islands.end(), [this](const std::unique_ptr &iptr) { return iptr->m_ptr->archi_ptr == this; })); + assert(m_idx_map.size() == m_islands.size()); + assert(m_migrants.size() == m_islands.size()); +#if !defined(NDEBUG) + for (size_type i = 0; i < m_islands.size(); ++i) { + // Ensure that the vectors in the migrant db have + // consistent sizes. + assert(std::get<0>(m_migrants[i]).size() == std::get<1>(m_migrants[i]).size()); + assert(std::get<1>(m_migrants[i]).size() == std::get<2>(m_migrants[i]).size()); + + // Ensure the map of indices is correct. + assert(m_idx_map.find(m_islands[i].get()) != m_idx_map.end()); + assert(m_idx_map.find(m_islands[i].get())->second == i); + } +#endif } /// Mutable island access. @@ -164,7 +274,7 @@ archipelago::~archipelago() * obtained via this method. * * \verbatim embed:rst:leading-asterisk - * .. note:: + * .. warning:: * * The mutable version of the subscript operator exists solely to allow calling non-const methods * on the islands. Assigning an island via a reference obtained through this operator will result @@ -209,6 +319,15 @@ const island &archipelago::operator[](size_type i) const return *m_islands[i]; } +/// Size. +/** + * @return the number of islands in the archipelago. + */ +archipelago::size_type archipelago::size() const +{ + return m_islands.size(); +} + void archipelago::evolve(unsigned n) { for (auto &iptr : m_islands) { @@ -303,11 +422,11 @@ evolve_status archipelago::status() const return evolve_status::busy_error; } - // The other error case. + // The other error case: at least one island is idle with error. if (n_idle_error) { if (n_busy) { - // At least one island is idle with error. If any other - // island is busy, we return busy error. + // At least one island is idle with error and at least one island is busy: + // return busy error. return evolve_status::busy_error; } // No island is busy at all, at least one island is idle with error. @@ -351,6 +470,325 @@ std::vector archipelago::get_champions_x() const return retval; } +void archipelago::push_back_impl(std::unique_ptr &&new_island) +{ + // Assign the pointer to this. + // NOTE: perhaps this can be delayed until the last line? + // The reason would be that, in case of exceptions, + // new_island will be destroyed while pointing + // to an archipelago, although the island is not + // actually in the archipelago. In theory this + // could lead to assertion failures on destruction, if + // we implement archipelago-based checks in the dtor + // of island. This is not the case at the moment. + new_island->m_ptr->archi_ptr = this; + + // Try to make space for the new island in the islands vector. + // LCOV_EXCL_START + if (m_islands.size() == std::numeric_limits::max()) { + pagmo_throw(std::overflow_error, "cannot add a new island to an archipelago due to an overflow condition"); + } + // LCOV_EXCL_STOP + m_islands.reserve(m_islands.size() + 1u); + + // Try to make space for the new migrants entry. + // LCOV_EXCL_START + if (m_migrants.size() == std::numeric_limits::max()) { + pagmo_throw(std::overflow_error, "cannot add a new island to an archipelago due to an overflow condition"); + } + // LCOV_EXCL_STOP + { + std::lock_guard lock(m_migrants_mutex); + m_migrants.reserve(m_migrants.size() + 1u); + } + + // Map the new island idx. + { + // NOTE: if anything fails here, we won't have modified the state of the archi + // (apart from reserving memory). + std::lock_guard lock(m_idx_map_mutex); + assert(m_idx_map.find(new_island.get()) == m_idx_map.end()); + m_idx_map.emplace(new_island.get(), m_islands.size()); + } + + // Add an empty entry to the migrants db. + try { + std::lock_guard lock(m_migrants_mutex); + m_migrants.emplace_back(); + } catch (...) { + // LCOV_EXCL_START + // NOTE: we get here only if the lock throws, because we made space for the + // new migrants above already. Better to abort in such case, as we have no + // reasonable path for recovering from this. + std::cerr << "An unrecoverable error arose while adding an island to the archipelago, aborting now." + << std::endl; + std::abort(); + // LCOV_EXCL_STOP + } + + // Actually add the island. This cannot fail as we already reserved space. + m_islands.push_back(std::move(new_island)); + + // Finally, push back the topology. This is required to be thread safe, no need for locks. + // If this fails, we will have a possibly *bad* topology in the archi, but this can + // always happen via a bogus set_topology() and there's nothing we can do about it. + m_topology.push_back(); +} + +// Get the index of an island. +// This function will return the index of the island \p isl in the archipelago. If \p isl does +// not belong to the archipelago, an error will be reaised. +archipelago::size_type archipelago::get_island_idx(const island &isl) const +{ + std::lock_guard lock(m_idx_map_mutex); + const auto ret = m_idx_map.find(&isl); + if (ret == m_idx_map.end()) { + pagmo_throw(std::invalid_argument, + "the index of an island in an archipelago was requested, but the island is not in the archipelago"); + } + return ret->second; +} + +/// Get the database of migrants. +/** + * \verbatim embed:rst:leading-asterisk + * During the evolution of an archipelago, islands will periodically + * store the individuals selected for migration in a *migrant database*. + * This is a vector of :cpp:type:`~pagmo::individuals_group_t` whose + * size is equal to the number of islands in the archipelago, and which + * contains the current candidate outgoing migrants for each island. + * \endverbatim + * + * @return a copy of the database of migrants. + * + * @throws unspecified any exception thrown by threading primitives or by memory allocation errors. + */ +archipelago::migrants_db_t archipelago::get_migrants_db() const +{ + std::lock_guard lock(m_migrants_mutex); + return m_migrants; +} + +/// Get the migration log. +/** + * \verbatim embed:rst:leading-asterisk + * Each time an individual migrates from an island (the source) to another + * (the destination), an entry will be added to the migration log. + * The entry is a tuple containing: + * + * - a timestamp of the migration, + * - the ID of the individual that migrated, + * - the decision and fitness vectors of the individual that migrated, + * - the indices of the source and destination islands. + * + * The migration log is a collection of migration entries. + * + * \endverbatim + * + * @return a copy of the migration log. + * + * @throws unspecified any exception thrown by threading primitives or by memory allocation errors. + */ +archipelago::migration_log_t archipelago::get_migration_log() const +{ + std::lock_guard lock(m_migr_log_mutex); + return m_migr_log; +} + +// Append entries to the migration log. +void archipelago::append_migration_log(const migration_log_t &mlog) +{ + // Don't do anything if mlog is empty. + if (mlog.empty()) { + return; + } + + // Lock & append. + std::lock_guard lock(m_migr_log_mutex); + m_migr_log.insert(m_migr_log.end(), mlog.begin(), mlog.end()); +} + +// Extract the migrants in the db entry for island i. +// After extraction, the db entry will be empty. +individuals_group_t archipelago::extract_migrants(size_type i) +{ + std::lock_guard lock(m_migrants_mutex); + + if (i >= m_migrants.size()) { + pagmo_throw(std::out_of_range, "cannot access the migrants of the island at index " + std::to_string(i) + + ": the migrants database has a size of only " + + std::to_string(m_migrants.size())); + } + + // Move-construct the return value. + individuals_group_t retval(std::move(m_migrants[i])); + + // Ensure the tuple we moved-from is completely + // cleared out. + std::get<0>(m_migrants[i]).clear(); + std::get<1>(m_migrants[i]).clear(); + std::get<2>(m_migrants[i]).clear(); + + return retval; +} + +// Get the migrants in the db entry for island i. +// This function will *not* clear out the db entry. +individuals_group_t archipelago::get_migrants(size_type i) const +{ + std::lock_guard lock(m_migrants_mutex); + + if (i >= m_migrants.size()) { + pagmo_throw(std::out_of_range, "cannot access the migrants of the island at index " + std::to_string(i) + + ": the migrants database has a size of only " + + std::to_string(m_migrants.size())); + } + + // Return a copy of the migrants for island i. + return m_migrants[i]; +} + +// Move-insert in the db entry for island i a set of migrants. +void archipelago::set_migrants(size_type i, individuals_group_t &&inds) +{ + std::lock_guard lock(m_migrants_mutex); + + if (i >= m_migrants.size()) { + pagmo_throw(std::out_of_range, "cannot access the migrants of the island at index " + std::to_string(i) + + ": the migrants database has a size of only " + + std::to_string(m_migrants.size())); + } + + // Move in the new individuals. + std::get<0>(m_migrants[i]) = std::move(std::get<0>(inds)); + std::get<1>(m_migrants[i]) = std::move(std::get<1>(inds)); + std::get<2>(m_migrants[i]) = std::move(std::get<2>(inds)); +} + +/// Get a copy of the topology. +/** + * @return a copy of the topology. + * + * @throws unspecified any exception thrown by copying the topology. + */ +topology archipelago::get_topology() const +{ + // NOTE: topology is supposed to be thread-safe, + // no need to protect the access. + return m_topology; +} + +/// Set a new topology. +/** + * This function will first wait for any ongoing evolution in the archipelago to conclude, + * and it will then set a new topology for the archipelago. + * + * Note that it is the user's responsibility to ensure that the new topology + * is consistent with the archipelago's properties. + * + * @param topo the new topology. + * + * @throws unspecified any exception thrown by copying the topology. + */ +void archipelago::set_topology(topology topo) +{ + // NOTE: make sure we finish any ongoing evolution before setting the topology. + // The assignment will trigger the destructor of the UDT, so we need to make + // sure there's no interaction with the UDT happening. + wait_check_ignore(); + m_topology = std::move(topo); +} + +namespace detail +{ + +namespace +{ + +// Helpers to implement the archipelago::get_island_connections() function below. +template +std::pair, vector_double> get_island_connections_impl(const T &topo, std::size_t i, + std::true_type) +{ + // NOTE: get_connections() is required to be thread-safe. + return topo.get_connections(i); +} + +template +std::pair, vector_double> get_island_connections_impl(const T &topo, std::size_t i, + std::false_type) +{ + // NOTE: get_connections() is required to be thread-safe. + auto tmp = topo.get_connections(i); + + std::pair, vector_double> retval; + retval.first.reserve(boost::numeric_cast(tmp.first.size())); + + std::transform(tmp.first.begin(), tmp.first.end(), std::back_inserter(retval.first), + [](const std::size_t &n) { return boost::numeric_cast(n); }); + retval.second = std::move(tmp.second); + + return retval; +} + +} // namespace + +} // namespace detail + +// Get the list of connection to the island at index i. +// The returned value is made of two vectors of equal size: +// - the indicies of the connecting islands, +// - the weights of the connections. +// This function will take care of safely converting the topology +// indices to island indices, if necessary. +std::pair, vector_double> archipelago::get_island_connections(size_type i) const +{ + // NOTE: the get_connections() method of the topology + // returns indices represented by std::size_t, but this method + // returns indices represented by size_type. Hence, we formally + // need to go through a conversion. We do a bit of TMP to avoid + // the conversion in the likely case that std::size_t and size_type + // are the same type. + return detail::get_island_connections_impl(m_topology, boost::numeric_cast(i), + std::is_same{}); +} + +/// Get the migration type. +/** + * @return the migration type for this archipelago. + */ +migration_type archipelago::get_migration_type() const +{ + return m_migr_type.load(std::memory_order_relaxed); +} + +/// Set a new migration type. +/** + * @param mt a new migration type for this archipelago. + */ +void archipelago::set_migration_type(migration_type mt) +{ + m_migr_type.store(mt, std::memory_order_relaxed); +} + +/// Get the migrant handling policy. +/** + * @return the migrant handling policy for this archipelago. + */ +migrant_handling archipelago::get_migrant_handling() const +{ + return m_migr_handling.load(std::memory_order_relaxed); +} + +/// Set a new migrant handling policy. +/** + * @param mh a new migrant handling policy for this archipelago. + */ +void archipelago::set_migrant_handling(migrant_handling mh) +{ + m_migr_handling.store(mh, std::memory_order_relaxed); +} + /// Stream operator. /** * This operator will stream to \p os a human-readable representation of the input @@ -368,6 +806,9 @@ std::vector archipelago::get_champions_x() const std::ostream &operator<<(std::ostream &os, const archipelago &archi) { stream(os, "Number of islands: ", archi.size(), "\n"); + stream(os, "Topology: ", archi.get_topology().get_name(), "\n"); + stream(os, "Migration type: ", archi.get_migration_type(), "\n"); + stream(os, "Migrant handling policy: ", archi.get_migrant_handling(), "\n"); stream(os, "Status: ", archi.status(), "\n\n"); stream(os, "Islands summaries:\n\n"); detail::table t({"#", "Type", "Algo", "Prob", "Size", "Status"}, "\t"); @@ -380,4 +821,21 @@ std::ostream &operator<<(std::ostream &os, const archipelago &archi) return os; } +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Provide the stream operator overloads for migration_type and migrant_handling. +std::ostream &operator<<(std::ostream &os, migration_type mt) +{ + os << (mt == migration_type::p2p ? "point-to-point" : "broadcast"); + return os; +} + +std::ostream &operator<<(std::ostream &os, migrant_handling mh) +{ + os << (mh == migrant_handling::preserve ? "preserve" : "evict"); + return os; +} + +#endif + } // namespace pagmo diff --git a/src/detail/base_sr_policy.cpp b/src/detail/base_sr_policy.cpp new file mode 100644 index 000000000..85697beb8 --- /dev/null +++ b/src/detail/base_sr_policy.cpp @@ -0,0 +1,76 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=const" +#endif + +namespace pagmo +{ + +namespace detail +{ + +// Helper to verify the ctor from a fractional rate. +void base_sr_policy::verify_fp_ctor() const +{ + assert(m_migr_rate.which() == 1); + + const auto rate = boost::get(m_migr_rate); + + if (!std::isfinite(rate) || rate < 0. || rate > 1.) { + pagmo_throw(std::invalid_argument, + "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is " + + std::to_string(rate) + " instead"); + } +} + +// Getter for the migration rate variant. +const boost::variant &base_sr_policy::get_migr_rate() const +{ + return m_migr_rate; +} + +} // namespace detail + +} // namespace pagmo diff --git a/src/island.cpp b/src/island.cpp index 63d4f9210..29ea9f544 100644 --- a/src/island.cpp +++ b/src/island.cpp @@ -30,6 +30,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include #include @@ -37,19 +38,31 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include +#include #include +#include #include #include +#include #include +#include #include +#include +#include #include +#include #include #include #include #include +#include +#include +#include #include +#include #if defined(PAGMO_WITH_FORK_ISLAND) #include @@ -67,6 +80,16 @@ namespace pagmo namespace detail { +namespace +{ + +// This will contain the time point at which +// the pagmo library is loaded. It is used in the migration +// logging to record the migration time. +const auto initial_timestamp = std::chrono::steady_clock::now(); + +} // namespace + // NOTE: this is just a simple wrapper to force noexcept behaviour on std::future::wait(). // If f.wait() throws something, the program will terminate. A valid std::future should not // throw, but technically the standard does not guarantee that. Having this noexcept wrapper @@ -162,12 +185,37 @@ island_data::island_data() { } +// This is used only in the copy ctor of island. The island will come from the clone() +// method of an isl_inner, the algo/pop from the island's getters. The r_policy +// will come directly from the island's data member, as the r_policy is supposed +// to be thread-safe. +island_data::island_data(std::unique_ptr &&ptr, algorithm &&a, population &&p, const r_policy &r, + const s_policy &s) + : isl_ptr(std::move(ptr)), algo(std::make_shared(std::move(a))), + pop(std::make_shared(std::move(p))), r_pol(r), s_pol(s) +{ +} + +namespace +{ + +// NOTE: in C++11 hashing of enums might not be available. Provide our own. +struct island_status_hasher { + std::size_t operator()(evolve_status es) const noexcept + { + return std::hash{}(static_cast(es)); + } +}; + +// A map to link a human-readable description to evolve_status. const std::unordered_map island_statuses = {{evolve_status::idle, "idle"}, {evolve_status::busy, "busy"}, {evolve_status::idle_error, "idle - **error occurred**"}, {evolve_status::busy_error, "busy - **error occurred**"}}; +} // namespace + } // namespace detail #if !defined(PAGMO_DOXYGEN_INVOKED) @@ -196,7 +244,7 @@ void island::wait_check_ignore() /// Default constructor. /** * The default constructor will initialise an island containing a UDI of type pagmo::thread_island, - * and default-constructed pagmo::algorithm and pagmo::population. + * and default-constructed algorithm, population and replacement/selection policies. * * @throws unspecified any exception thrown by any invoked constructor or by memory allocation failures. */ @@ -204,18 +252,19 @@ island::island() : m_ptr(detail::make_unique()) {} /// Copy constructor. /** - * The copy constructor will initialise an island containing a copy of other's UDI, population - * and algorithm. It is safe to call this constructor while \p other is evolving. + * The copy constructor will initialise an island containing a copy of other's UDI, population, + * algorithm and replacement/selection policies. It is safe to call this constructor while \p other is evolving. * * @param other the island tht will be copied. * * @throws unspecified any exception thrown by: * - get_population() and get_algorithm(), * - memory allocation errors, - * - the copy constructors of pagmo::algorithm and pagmo::population. + * - copying the island's members. */ island::island(const island &other) - : m_ptr(detail::make_unique(other.m_ptr->isl_ptr->clone(), other.get_algorithm(), other.get_population())) + : m_ptr(detail::make_unique(other.m_ptr->isl_ptr->clone(), other.get_algorithm(), other.get_population(), + other.m_ptr->r_pol, other.m_ptr->s_pol)) { // NOTE: the idata_t ctor will set the archi ptr to null. The archi ptr is never copied. assert(m_ptr->archi_ptr == nullptr); @@ -297,8 +346,210 @@ void island::evolve(unsigned n) // NOTE: enqueue either returns a valid future, or throws without // having enqueued any task. m_ptr->futures.back() = m_ptr->queue.enqueue([this, n]() { + // Random engine for use in the migration logic. + // Wrap it in an optional so that, if we don't need + // it, we don't waste CPU/memory. + boost::optional migr_eng; + + // Cache the archi pointer. + const auto aptr = this->m_ptr->archi_ptr; + + // Figure out what is the island's index in the archi, if we are + // in an archi. Otherwise, this variable will be unused. + const auto isl_idx = aptr ? aptr->get_island_idx(*this) : 0u; + for (auto i = 0u; i < n; ++i) { + if (aptr) { + // If the island is in an archi, before + // launching the evolution migrate the + // individuals from the connecting islands. + + // Get the indices of the islands with a connection + // towards this. + // NOTE: the get_island_connections() helper will take care + // of converting topology indices to island indices. + const auto connections = aptr->get_island_connections(isl_idx); + assert(connections.first.size() == connections.second.size()); + + // Do something only if we actually have connections. + if (connections.first.size()) { + // Init the rng engine, if necessary. + if (!migr_eng) { + migr_eng.emplace(static_cast(random_device::next())); + } + + // Fetch the migration type and the migrant handling policy + // from the archipelago. + const auto mt = aptr->get_migration_type(); + const auto mh = aptr->get_migrant_handling(); + + // Small helper to turn a group of individuals into + // an ID -> (dv, fv) map. inds will be destroyed + // in the process. + using inds_map_t + = std::unordered_map>; + auto group_to_map = [](individuals_group_t &&inds) -> inds_map_t { + inds_map_t retval; + + for (decltype(std::get<0>(inds).size()) j = 0; j < std::get<0>(inds).size(); ++j) { + retval[std::get<0>(inds)[j]] + = std::make_pair(std::move(std::get<1>(inds)[j]), std::move(std::get<2>(inds)[j])); + } + + return retval; + }; + + if (mt == migration_type::p2p) { + // Point-to-point migration. + + // Pick a random island among the islands connecting to this. + const auto conn_idx = std::uniform_int_distribution( + 0, connections.first.size() - 1u)(*migr_eng); + + // Throw the dice against the migration probability. + if (std::uniform_real_distribution<>{}(*migr_eng) < connections.second[conn_idx]) { + // Get the source island's index. + const auto src_idx = connections.first[conn_idx]; + + // Extract or copy the candidate migrants from the archipelago. + const auto migrants = (mh == migrant_handling::preserve) + ? aptr->get_migrants(src_idx) + : aptr->extract_migrants(src_idx); + + // Extract the migration data from this island. + const auto mig_data = this->get_migration_data(); + + // Run the replacement policy. + auto new_inds = this->m_ptr->r_pol.replace(std::get<0>(mig_data), std::get<1>(mig_data), + std::get<2>(mig_data), std::get<3>(mig_data), + std::get<4>(mig_data), std::get<5>(mig_data), + std::get<6>(mig_data), migrants); + + // Set the new individuals. + this->set_individuals(new_inds); + + // Compute the migration timestamp. + const std::chrono::duration mig_ts + = std::chrono::steady_clock::now() - detail::initial_timestamp; + + // Turn new_inds into an ID -> (dv, fv) map in order to build the log. + const auto new_inds_map = group_to_map(std::move(new_inds)); + + // Build the migration log. + archipelago::migration_log_t mlog; + for (auto mig_ID : std::get<0>(migrants)) { + const auto it = new_inds_map.find(mig_ID); + + if (it != new_inds_map.end()) { + mlog.emplace_back(mig_ts.count(), mig_ID, it->second.first, it->second.second, + src_idx, isl_idx); + } + } + + // Append it. + aptr->append_migration_log(mlog); + } + } else { + // Broadcast migration. + + // Group of candidate migrants from the all + // the islands connecting to this. + // We will build this below iteratively. + individuals_group_t migrants; + + // Vector to pair source island indices to the corresponding + // candidate migrants. This will contain the same set of individuals + // as migrants, but split according to the source island. This is needed + // to build the migration log. + std::vector> split_migrants; + + for (decltype(connections.first.size()) j = 0; j < connections.first.size(); ++j) { + // Throw the dice against the migration probability. + if (std::uniform_real_distribution<>{}(*migr_eng) < connections.second[j]) { + // Get the source island's index. + const auto src_idx = connections.first[j]; + + // Extract or copy the candidate migrants from the archipelago. + auto cur_migrants = (mh == migrant_handling::preserve) + ? aptr->get_migrants(src_idx) + : aptr->extract_migrants(src_idx); + + // Add them to the global migrants vector. + std::get<0>(migrants).insert(std::get<0>(migrants).end(), + std::get<0>(cur_migrants).begin(), + std::get<0>(cur_migrants).end()); + std::get<1>(migrants).insert(std::get<1>(migrants).end(), + std::get<1>(cur_migrants).begin(), + std::get<1>(cur_migrants).end()); + std::get<2>(migrants).insert(std::get<2>(migrants).end(), + std::get<2>(cur_migrants).begin(), + std::get<2>(cur_migrants).end()); + + // Add them to split_migrants too. + split_migrants.emplace_back(src_idx, std::move(cur_migrants)); + } + } + + // Extract the migration data from this island. + const auto mig_data = this->get_migration_data(); + + // Run the replacement policy. + auto new_inds = this->m_ptr->r_pol.replace(std::get<0>(mig_data), std::get<1>(mig_data), + std::get<2>(mig_data), std::get<3>(mig_data), + std::get<4>(mig_data), std::get<5>(mig_data), + std::get<6>(mig_data), migrants); + + // Set the new individuals. + this->set_individuals(new_inds); + + // Compute the migration timestamp. + const std::chrono::duration mig_ts + = std::chrono::steady_clock::now() - detail::initial_timestamp; + + // Turn new_inds into an ID -> (dv, fv) map in order to build the log. + const auto new_inds_map = group_to_map(std::move(new_inds)); + + // Build the migration log. + archipelago::migration_log_t mlog; + for (const auto &p : split_migrants) { + const auto src_idx = p.first; + + for (auto mig_ID : std::get<0>(p.second)) { + const auto it = new_inds_map.find(mig_ID); + + if (it != new_inds_map.end()) { + mlog.emplace_back(mig_ts.count(), mig_ID, it->second.first, it->second.second, + src_idx, isl_idx); + } + } + } + + // Append it. + aptr->append_migration_log(mlog); + } + } + } + + // Run the evolution. this->m_ptr->isl_ptr->run_evolve(*this); + + if (aptr) { + // If the island is in an archi, after evolution select + // the migrating individuals and place them in the archi's migrants + // database. + + // Extract the migration data from this island. + const auto mig_data = this->get_migration_data(); + + // Select the individuals to place into the archi's + // migration database. + auto mig_inds = this->m_ptr->s_pol.select( + std::get<0>(mig_data), std::get<1>(mig_data), std::get<2>(mig_data), std::get<3>(mig_data), + std::get<4>(mig_data), std::get<5>(mig_data), std::get<6>(mig_data)); + + // Place them in the database. + aptr->set_migrants(isl_idx, std::move(mig_inds)); + } } }); // LCOV_EXCL_START @@ -553,6 +804,33 @@ void island::set_population(const population &pop) } } +/// Get the replacement policy. +/** + * @return a copy of the current replacement policy. + * + * @throws unspecified any exception thrown by the copy constructor + * of the replacement policy. + */ +r_policy island::get_r_policy() const +{ + // NOTE: replacement/selection policies + // are supposed to provide thread-safe + // copy constructors. + return m_ptr->r_pol; +} + +/// Get the selection policy. +/** + * @return a copy of the current selection policy. + * + * @throws unspecified any exception thrown by the copy constructor + * of the selection policy. + */ +s_policy island::get_s_policy() const +{ + return m_ptr->s_pol; +} + /// Island's name. /** * If the UDI satisfies pagmo::has_name, then this method will return the output of its %get_name() method. @@ -585,21 +863,9 @@ std::string island::get_extra_info() const return m_ptr->isl_ptr->get_extra_info(); } -/// Stream operator for pagmo::island. -/** - * This operator will stream to \p os a human-readable representation of \p isl. - * - * It is safe to call this method while the island is evolving. - * - * @param os the target stream. - * @param isl the island. - * - * @return a reference to \p os. - * - * @throws unspecified any exception thrown by: - * - the stream operators of fundamental types, pagmo::algorithm and pagmo::population, - * - pagmo::island::get_extra_info(), pagmo::island::get_algorithm(), pagmo::island::get_population(). - */ +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Stream operator for pagmo::island. std::ostream &operator<<(std::ostream &os, const island &isl) { stream(os, "Island name: ", isl.get_name()); @@ -610,12 +876,16 @@ std::ostream &operator<<(std::ostream &os, const island &isl) } stream(os, "Algorithm: " + isl.get_algorithm().get_name(), "\n\n"); stream(os, "Problem: " + isl.get_population().get_problem().get_name(), "\n\n"); + stream(os, "Replacement policy: " + isl.m_ptr->r_pol.get_name(), "\n\n"); + stream(os, "Selection policy: " + isl.m_ptr->s_pol.get_name(), "\n\n"); stream(os, "Population size: ", isl.get_population().size(), "\n"); stream(os, "\tChampion decision vector: ", isl.get_population().champion_x(), "\n"); stream(os, "\tChampion fitness: ", isl.get_population().champion_f(), "\n"); return os; } +#endif + /// Check if the island is in a valid state. /** * @return ``false`` if ``this`` was moved from, ``true`` otherwise. @@ -625,4 +895,64 @@ bool island::is_valid() const return static_cast(m_ptr); } +// Get the migration data. +island::migration_data_t island::get_migration_data() const +{ + migration_data_t retval; + + { + // NOTE: this helper is called from the separate + // thread of execution within pagmo::island. We need to protect + // with a gte. + auto gte = detail::gte_getter(); + (void)gte; + + // Get a copy of the population. + auto tmp_pop(get_population()); + + // Move out the individuals. + std::get<0>(std::get<0>(retval)) = std::move(tmp_pop.m_ID); + std::get<1>(std::get<0>(retval)) = std::move(tmp_pop.m_x); + std::get<2>(std::get<0>(retval)) = std::move(tmp_pop.m_f); + + // nx, nix, nobj, nec, nic. + std::get<1>(retval) = tmp_pop.get_problem().get_nx(); + std::get<2>(retval) = tmp_pop.get_problem().get_nix(); + std::get<3>(retval) = tmp_pop.get_problem().get_nobj(); + std::get<4>(retval) = tmp_pop.get_problem().get_nec(); + std::get<5>(retval) = tmp_pop.get_problem().get_nic(); + + // The vector of tolerances. + std::get<6>(retval) = tmp_pop.get_problem().get_c_tol(); + } + + return retval; +} + +// Set all the individuals in the population. +void island::set_individuals(const individuals_group_t &inds) +{ + // Create copy for move semantics below. + auto tmp_inds(inds); + + { + // NOTE: this helper is called from the separate + // thread of execution within pagmo::island. We need to protect + // with a gte. + auto gte = detail::gte_getter(); + (void)gte; + + // Get out a copy of the population. + auto tmp_pop(get_population()); + + // Move in the individuals. + tmp_pop.m_ID = std::move(std::get<0>(tmp_inds)); + tmp_pop.m_x = std::move(std::get<1>(tmp_inds)); + tmp_pop.m_f = std::move(std::get<2>(tmp_inds)); + + // Set the new population. + set_population(tmp_pop); + } +} + } // namespace pagmo diff --git a/src/problem.cpp b/src/problem.cpp index a50c215c6..20f500c2f 100644 --- a/src/problem.cpp +++ b/src/problem.cpp @@ -169,16 +169,16 @@ void problem::generic_ctor_impl() pagmo_throw(std::invalid_argument, "The number of objectives cannot be zero"); } // NOTE: here we check that we can always compute nobj + nec + nic safely. - if (m_nobj > std::numeric_limits::max() / 3u) { + if (m_nobj > std::numeric_limits::max() / 3u) { pagmo_throw(std::invalid_argument, "The number of objectives is too large"); } // 3 - Constraints. m_nec = ptr()->get_nec(); - if (m_nec > std::numeric_limits::max() / 3u) { + if (m_nec > std::numeric_limits::max() / 3u) { pagmo_throw(std::invalid_argument, "The number of equality constraints is too large"); } m_nic = ptr()->get_nic(); - if (m_nic > std::numeric_limits::max() / 3u) { + if (m_nic > std::numeric_limits::max() / 3u) { pagmo_throw(std::invalid_argument, "The number of inequality constraints is too large"); } // 4 - Presence of batch_fitness(). diff --git a/src/r_policies/fair_replace.cpp b/src/r_policies/fair_replace.cpp new file mode 100644 index 000000000..936a24b52 --- /dev/null +++ b/src/r_policies/fair_replace.cpp @@ -0,0 +1,242 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +// Default constructor: absolute rate, 1 individual. +fair_replace::fair_replace() : fair_replace(1) {} + +// Implementation of the replacement. +individuals_group_t fair_replace::replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol, const individuals_group_t &mig) const +{ + if (nobj > 1u && (nic || nec)) { + pagmo_throw(std::invalid_argument, "The 'fair_replace' replacement policy is unable to deal with " + "multiobjective constrained optimisation problems"); + } + + // Cache the sizes of the input pop and the migrants. + // NOTE: use the size type of the dvs, which is pop_size_t. + const auto inds_size = std::get<1>(inds).size(); + const auto mig_size = std::get<1>(mig).size(); + + // Establish how many individuals we want to migrate from mig into inds. + const auto n_migr = [this, inds_size, mig_size]() -> pop_size_t { + pop_size_t candidate; + + if (this->m_migr_rate.which()) { + // Fractional migration rate: scale it by the number + // of input individuals. + // NOTE: use std::min() to make absolutely sure we don't exceed inds_size + // due to FP shenanigans. + candidate = std::min( + boost::numeric_cast(boost::get(m_migr_rate) * static_cast(inds_size)), + inds_size); + } else { + // Absolute migration rate: check that it's not higher than the input population size. + candidate = boost::get(m_migr_rate); + if (candidate > inds_size) { + pagmo_throw( + std::invalid_argument, + "The absolute migration rate (" + std::to_string(candidate) + + ") in a 'fair_replace' replacement policy is larger than the number of input individuals (" + + std::to_string(inds_size) + ")"); + } + } + + // We cannot migrate more individuals than we have available + // in mig, so clamp the candidate value. + return std::min(candidate, mig_size); + }(); + + // Make extra sure that the number of individuals selected + // for migration is not larger than mig_size. + assert(n_migr <= mig_size); + + // NOTE: currently this replacement policy can handle: + // - single-ojective (un)constrained optimisation, + // - multiobjective unconstrained optimisation. + // We already checked above that we are not in an MO + // constrained case. + if (nobj == 1u && !nic && !nec) { + // Single-objective, unconstrained. + + // Sort (indirectly) the migrants according to their fitness. + std::vector mig_ind_sort; + mig_ind_sort.resize(boost::numeric_cast(mig_size)); + std::iota(mig_ind_sort.begin(), mig_ind_sort.end(), pop_size_t(0)); + std::sort(mig_ind_sort.begin(), mig_ind_sort.end(), + [&mig](pop_size_t idx1, pop_size_t idx2) { return std::get<2>(mig)[idx1] < std::get<2>(mig)[idx2]; }); + + // Build the merged population from the original individuals plus the + // top n_migr migrants. + auto merged_pop(inds); + for (pop_size_t i = 0; i < n_migr; ++i) { + std::get<0>(merged_pop).push_back(std::get<0>(mig)[mig_ind_sort[i]]); + std::get<1>(merged_pop).push_back(std::get<1>(mig)[mig_ind_sort[i]]); + std::get<2>(merged_pop).push_back(std::get<2>(mig)[mig_ind_sort[i]]); + } + + // Sort (indirectly) the merged population. + std::vector merged_pop_ind_sort; + merged_pop_ind_sort.resize( + boost::numeric_cast(std::get<0>(merged_pop).size())); + std::iota(merged_pop_ind_sort.begin(), merged_pop_ind_sort.end(), pop_size_t(0)); + std::sort(merged_pop_ind_sort.begin(), merged_pop_ind_sort.end(), + [&merged_pop](pop_size_t idx1, pop_size_t idx2) { + return std::get<2>(merged_pop)[idx1] < std::get<2>(merged_pop)[idx2]; + }); + + // Create and return the output pop. + individuals_group_t retval; + std::get<0>(retval).reserve(std::get<0>(inds).size()); + std::get<1>(retval).reserve(std::get<1>(inds).size()); + std::get<2>(retval).reserve(std::get<2>(inds).size()); + for (pop_size_t i = 0; i < inds_size; ++i) { + std::get<0>(retval).push_back(std::get<0>(merged_pop)[merged_pop_ind_sort[i]]); + std::get<1>(retval).push_back(std::get<1>(merged_pop)[merged_pop_ind_sort[i]]); + std::get<2>(retval).push_back(std::get<2>(merged_pop)[merged_pop_ind_sort[i]]); + } + + return retval; + } else if (nobj == 1u && (nic || nec)) { + // Single-objective, constrained. + + // Sort indirectly the input migrants, taking into accounts + // constraints satisfaction and tolerances. + const auto mig_ind_sort = sort_population_con(std::get<2>(mig), nec, tol); + + // Build the merged population from the original individuals plus the + // top n_migr migrants. + auto merged_pop(inds); + for (pop_size_t i = 0; i < n_migr; ++i) { + std::get<0>(merged_pop).push_back(std::get<0>(mig)[mig_ind_sort[i]]); + std::get<1>(merged_pop).push_back(std::get<1>(mig)[mig_ind_sort[i]]); + std::get<2>(merged_pop).push_back(std::get<2>(mig)[mig_ind_sort[i]]); + } + + // Sort indirectly the merged population. + const auto merged_pop_ind_sort = sort_population_con(std::get<2>(merged_pop), nec, tol); + + // Create and return the output pop. + individuals_group_t retval; + std::get<0>(retval).reserve(std::get<0>(inds).size()); + std::get<1>(retval).reserve(std::get<1>(inds).size()); + std::get<2>(retval).reserve(std::get<2>(inds).size()); + for (pop_size_t i = 0; i < inds_size; ++i) { + std::get<0>(retval).push_back(std::get<0>(merged_pop)[merged_pop_ind_sort[i]]); + std::get<1>(retval).push_back(std::get<1>(merged_pop)[merged_pop_ind_sort[i]]); + std::get<2>(retval).push_back(std::get<2>(merged_pop)[merged_pop_ind_sort[i]]); + } + + return retval; + } else { + // Multi-objective, unconstrained. + assert(nobj > 1u && !nic && !nec); + + // Get the best n_migr migrants. + const auto mig_ind_sort = select_best_N_mo(std::get<2>(mig), n_migr); + + // Build the merged population from the original individuals plus the + // top n_migr migrants. + auto merged_pop(inds); + for (pop_size_t i = 0; i < n_migr; ++i) { + std::get<0>(merged_pop).push_back(std::get<0>(mig)[mig_ind_sort[i]]); + std::get<1>(merged_pop).push_back(std::get<1>(mig)[mig_ind_sort[i]]); + std::get<2>(merged_pop).push_back(std::get<2>(mig)[mig_ind_sort[i]]); + } + + // Get the best inds_size individuals from the merged population. + const auto merged_pop_ind_sort = select_best_N_mo(std::get<2>(merged_pop), inds_size); + + // Create and return the output pop. + individuals_group_t retval; + std::get<0>(retval).reserve(std::get<0>(inds).size()); + std::get<1>(retval).reserve(std::get<1>(inds).size()); + std::get<2>(retval).reserve(std::get<2>(inds).size()); + for (pop_size_t i = 0; i < inds_size; ++i) { + std::get<0>(retval).push_back(std::get<0>(merged_pop)[merged_pop_ind_sort[i]]); + std::get<1>(retval).push_back(std::get<1>(merged_pop)[merged_pop_ind_sort[i]]); + std::get<2>(retval).push_back(std::get<2>(merged_pop)[merged_pop_ind_sort[i]]); + } + + return retval; + } +} + +// Extra info. +std::string fair_replace::get_extra_info() const +{ + if (m_migr_rate.which()) { + const auto rate = boost::get(m_migr_rate); + return "\tFractional migration rate: " + std::to_string(rate); + } else { + const auto rate = boost::get(m_migr_rate); + return "\tAbsolute migration rate: " + std::to_string(rate); + } +} + +// Serialization support. +template +void fair_replace::serialize(Archive &ar, unsigned) +{ + detail::archive(ar, boost::serialization::base_object(*this)); +} + +} // namespace pagmo + +PAGMO_S11N_R_POLICY_IMPLEMENT(pagmo::fair_replace) diff --git a/src/r_policy.cpp b/src/r_policy.cpp new file mode 100644 index 000000000..c78c7870d --- /dev/null +++ b/src/r_policy.cpp @@ -0,0 +1,254 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +// Default constructor: fair_replace with default parameters. +r_policy::r_policy() : r_policy(fair_replace{}) {} + +// Implementation of the generic constructor. +void r_policy::generic_ctor_impl() +{ + // Assign the name. + m_name = ptr()->get_name(); +} + +// Copy constructor. +r_policy::r_policy(const r_policy &other) : m_ptr(other.ptr()->clone()), m_name(other.m_name) {} + +// Move constructor. The default implementation is fine. +r_policy::r_policy(r_policy &&) noexcept = default; + +// Move assignment operator +r_policy &r_policy::operator=(r_policy &&other) noexcept +{ + if (this != &other) { + m_ptr = std::move(other.m_ptr); + m_name = std::move(other.m_name); + } + return *this; +} + +// Copy assignment operator +r_policy &r_policy::operator=(const r_policy &other) +{ + // Copy ctor + move assignment. + return *this = r_policy(other); +} + +// Verify the input arguments for the replace() function. +void r_policy::verify_replace_input(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol, const individuals_group_t &mig) const +{ + // 1 - verify that the elements of inds all have the same size. + if (std::get<0>(inds).size() != std::get<1>(inds).size() || std::get<0>(inds).size() != std::get<2>(inds).size()) { + pagmo_throw(std::invalid_argument, + "an invalid group of individuals was passed to a replacement policy of type '" + get_name() + + "': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are " + + std::to_string(std::get<0>(inds).size()) + ", " + std::to_string(std::get<1>(inds).size()) + + " and " + std::to_string(std::get<2>(inds).size())); + } + + // 2 - same for mig. + if (std::get<0>(mig).size() != std::get<1>(mig).size() || std::get<0>(mig).size() != std::get<2>(mig).size()) { + pagmo_throw(std::invalid_argument, + "an invalid group of migrants was passed to a replacement policy of type '" + get_name() + + "': the sets of migrants IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are " + + std::to_string(std::get<0>(mig).size()) + ", " + std::to_string(std::get<1>(mig).size()) + + " and " + std::to_string(std::get<2>(mig).size())); + } + + // 3 - make sure nx, nix, nobj, nec, nic are sane and consistent. + // Check that the problem dimension is not zero. + if (!nx) { + pagmo_throw(std::invalid_argument, + "a problem dimension of zero was passed to a replacement policy of type '" + get_name() + "'"); + } + // Verify that it is consistent with nix. + if (nix > nx) { + pagmo_throw(std::invalid_argument, + "the integer dimension (" + std::to_string(nix) + ") passed to a replacement policy of type '" + + get_name() + "' is larger than the supplied problem dimension (" + std::to_string(nx) + ")"); + } + if (!nobj) { + pagmo_throw(std::invalid_argument, + "an invalid number of objectives (0) was passed to a replacement policy of type '" + get_name() + + "'"); + } + if (nobj > std::numeric_limits::max() / 3u) { + pagmo_throw(std::invalid_argument, "the number of objectives (" + std::to_string(nobj) + + ") passed to a replacement policy of type '" + get_name() + + "' is too large"); + } + if (nec > std::numeric_limits::max() / 3u) { + pagmo_throw(std::invalid_argument, "the number of equality constraints (" + std::to_string(nec) + + ") passed to a replacement policy of type '" + get_name() + + "' is too large"); + } + if (nic > std::numeric_limits::max() / 3u) { + pagmo_throw(std::invalid_argument, "the number of inequality constraints (" + std::to_string(nic) + + ") passed to a replacement policy of type '" + get_name() + + "' is too large"); + } + // Verify that the tol vector size is correct. + if (tol.size() != nec + nic) { + pagmo_throw(std::invalid_argument, "the vector of tolerances passed to a replacement policy of type '" + + get_name() + "' has a dimension (" + std::to_string(tol.size()) + + ") which is inconsistent with the total number of constraints (" + + std::to_string(nec + nic) + ")"); + } + // Determine the fitness dimension. + const auto nf = nobj + nec + nic; + + // 4 - verify inds/migs. + auto dv_checker = [nx](const vector_double &dv) { return dv.size() != nx; }; + auto fv_checker = [nf](const vector_double &fv) { return fv.size() != nf; }; + + if (std::any_of(std::get<1>(inds).begin(), std::get<1>(inds).end(), dv_checker)) { + pagmo_throw(std::invalid_argument, "not all the individuals passed to a replacement policy of type '" + + get_name() + "' have the expected dimension (" + std::to_string(nx) + + ")"); + } + if (std::any_of(std::get<2>(inds).begin(), std::get<2>(inds).end(), fv_checker)) { + pagmo_throw(std::invalid_argument, "not all the individuals passed to a replacement policy of type '" + + get_name() + "' have the expected fitness dimension (" + + std::to_string(nf) + ")"); + } + if (std::any_of(std::get<1>(mig).begin(), std::get<1>(mig).end(), dv_checker)) { + pagmo_throw(std::invalid_argument, "not all the migrants passed to a replacement policy of type '" + get_name() + + "' have the expected dimension (" + std::to_string(nx) + ")"); + } + if (std::any_of(std::get<2>(mig).begin(), std::get<2>(mig).end(), fv_checker)) { + pagmo_throw(std::invalid_argument, "not all the migrants passed to a replacement policy of type '" + get_name() + + "' have the expected fitness dimension (" + std::to_string(nf) + ")"); + } +} + +// Verify the output of replace(). +void r_policy::verify_replace_output(const individuals_group_t &retval, vector_double::size_type nx, + vector_double::size_type nf) const +{ + // 1 - verify that the elements of retval all have the same size. + if (std::get<0>(retval).size() != std::get<1>(retval).size() + || std::get<0>(retval).size() != std::get<2>(retval).size()) { + pagmo_throw(std::invalid_argument, + "an invalid group of individuals was returned by a replacement policy of type '" + get_name() + + "': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are " + + std::to_string(std::get<0>(retval).size()) + ", " + std::to_string(std::get<1>(retval).size()) + + " and " + std::to_string(std::get<2>(retval).size())); + } + + // 2 - verify that the decision/fitness vectors in retval have all + // the expected dimensions. + if (std::any_of(std::get<1>(retval).begin(), std::get<1>(retval).end(), + [nx](const vector_double &dv) { return dv.size() != nx; })) { + pagmo_throw(std::invalid_argument, "not all the individuals returned by a replacement policy of type '" + + get_name() + "' have the expected dimension (" + std::to_string(nx) + + ")"); + } + if (std::any_of(std::get<2>(retval).begin(), std::get<2>(retval).end(), + [nf](const vector_double &fv) { return fv.size() != nf; })) { + pagmo_throw(std::invalid_argument, "not all the individuals returned by a replacement policy of type '" + + get_name() + "' have the expected fitness dimension (" + + std::to_string(nf) + ")"); + } +} + +// Replace individuals in inds with the input migrants mig. +individuals_group_t r_policy::replace(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol, const individuals_group_t &mig) const +{ + // Verify the input. + verify_replace_input(inds, nx, nix, nobj, nec, nic, tol, mig); + + // Call the replace() method from the UDRP. + auto retval = ptr()->replace(inds, nx, nix, nobj, nec, nic, tol, mig); + + // Verify the output. + // NOTE: we checked in verify_replace_input() that we can + // compute nobj + nec + nic safely. + verify_replace_output(retval, nx, nobj + nec + nic); + + return retval; +} + +// Extra info. +std::string r_policy::get_extra_info() const +{ + return ptr()->get_extra_info(); +} + +// Check if the r_policy is in a valid state. +bool r_policy::is_valid() const +{ + return static_cast(m_ptr); +} + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Stream operator. +std::ostream &operator<<(std::ostream &os, const r_policy &r) +{ + os << "Replacement policy name: " << r.get_name() << '\n'; + const auto extra_str = r.get_extra_info(); + if (!extra_str.empty()) { + os << "\nExtra info:\n" << extra_str << '\n'; + } + return os; +} + +#endif + +} // namespace pagmo diff --git a/src/s_policies/select_best.cpp b/src/s_policies/select_best.cpp new file mode 100644 index 000000000..05ad7c370 --- /dev/null +++ b/src/s_policies/select_best.cpp @@ -0,0 +1,194 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +// Default constructor: absolute migration rate, 1 individual. +select_best::select_best() : select_best(1) {} + +// Implementation of the selection. +individuals_group_t select_best::select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol) const +{ + if (nobj > 1u && (nic || nec)) { + pagmo_throw(std::invalid_argument, "The 'Select best' selection policy is unable to deal with " + "multiobjective constrained optimisation problems"); + } + + // Cache the sizes of the input pop. + // NOTE: use the size type of the dvs, which is pop_size_t. + const auto inds_size = std::get<1>(inds).size(); + + // Establish how many individuals we want to select from inds. + const auto n_migr = [this, inds_size]() -> pop_size_t { + if (this->m_migr_rate.which()) { + // Fractional migration rate: scale it by the number + // of input individuals. + // NOTE: use std::min() to make absolutely sure we don't exceed inds_size + // due to FP shenanigans. + return std::min( + boost::numeric_cast(boost::get(m_migr_rate) * static_cast(inds_size)), + inds_size); + } else { + // Absolute migration rate: check that it's not higher than the input population size. + const auto candidate = boost::get(m_migr_rate); + if (candidate > inds_size) { + pagmo_throw( + std::invalid_argument, + "The absolute migration rate (" + std::to_string(candidate) + + ") in a 'Select best' selection policy is larger than the number of input individuals (" + + std::to_string(inds_size) + ")"); + } + return candidate; + } + }(); + + // Make extra sure that the number of individuals selected + // is not larger than inds_size. + assert(n_migr <= inds_size); + + // NOTE: currently this selection policy can handle: + // - single-ojective (un)constrained optimisation, + // - multiobjective unconstrained optimisation. + // We already checked above that we are not in an MO + // constrained case. + if (nobj == 1u && !nic && !nec) { + // Single-objective, unconstrained. + + // Sort (indirectly) the input individuals according to their fitness. + std::vector inds_ind_sort; + inds_ind_sort.resize(boost::numeric_cast(inds_size)); + std::iota(inds_ind_sort.begin(), inds_ind_sort.end(), pop_size_t(0)); + std::sort(inds_ind_sort.begin(), inds_ind_sort.end(), [&inds](pop_size_t idx1, pop_size_t idx2) { + return std::get<2>(inds)[idx1] < std::get<2>(inds)[idx2]; + }); + + // Create and return the output pop. + individuals_group_t retval; + std::get<0>(retval).reserve(n_migr); + std::get<1>(retval).reserve(n_migr); + std::get<2>(retval).reserve(n_migr); + for (pop_size_t i = 0; i < n_migr; ++i) { + std::get<0>(retval).push_back(std::get<0>(inds)[inds_ind_sort[i]]); + std::get<1>(retval).push_back(std::get<1>(inds)[inds_ind_sort[i]]); + std::get<2>(retval).push_back(std::get<2>(inds)[inds_ind_sort[i]]); + } + + return retval; + } else if (nobj == 1u && (nic || nec)) { + // Single-objective, constrained. + + // Sort indirectly the input individuals, taking into accounts + // constraints satisfaction and tolerances. + const auto inds_ind_sort = sort_population_con(std::get<2>(inds), nec, tol); + + // Create and return the output pop. + individuals_group_t retval; + std::get<0>(retval).reserve(n_migr); + std::get<1>(retval).reserve(n_migr); + std::get<2>(retval).reserve(n_migr); + for (pop_size_t i = 0; i < n_migr; ++i) { + std::get<0>(retval).push_back(std::get<0>(inds)[inds_ind_sort[i]]); + std::get<1>(retval).push_back(std::get<1>(inds)[inds_ind_sort[i]]); + std::get<2>(retval).push_back(std::get<2>(inds)[inds_ind_sort[i]]); + } + + return retval; + } else { + // Multi-objective, unconstrained. + assert(nobj > 1u && !nic && !nec); + + // Get the best n_migr individuals. + const auto inds_ind_sort = select_best_N_mo(std::get<2>(inds), n_migr); + + // Create and return the output pop. + individuals_group_t retval; + std::get<0>(retval).reserve(n_migr); + std::get<1>(retval).reserve(n_migr); + std::get<2>(retval).reserve(n_migr); + for (pop_size_t i = 0; i < n_migr; ++i) { + std::get<0>(retval).push_back(std::get<0>(inds)[inds_ind_sort[i]]); + std::get<1>(retval).push_back(std::get<1>(inds)[inds_ind_sort[i]]); + std::get<2>(retval).push_back(std::get<2>(inds)[inds_ind_sort[i]]); + } + + return retval; + } +} + +// Extra info. +std::string select_best::get_extra_info() const +{ + if (m_migr_rate.which()) { + const auto rate = boost::get(m_migr_rate); + return "\tFractional migration rate: " + std::to_string(rate); + } else { + const auto rate = boost::get(m_migr_rate); + return "\tAbsolute migration rate: " + std::to_string(rate); + } +} + +// Serialization support. +template +void select_best::serialize(Archive &ar, unsigned) +{ + detail::archive(ar, boost::serialization::base_object(*this)); +} + +} // namespace pagmo + +PAGMO_S11N_S_POLICY_IMPLEMENT(pagmo::select_best) diff --git a/src/s_policy.cpp b/src/s_policy.cpp new file mode 100644 index 000000000..b9a3b89e8 --- /dev/null +++ b/src/s_policy.cpp @@ -0,0 +1,237 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +// Default constructor: select_best with default parameters. +s_policy::s_policy() : s_policy(select_best{}) {} + +// Implementation of the generic constructor. +void s_policy::generic_ctor_impl() +{ + // Assign the name. + m_name = ptr()->get_name(); +} + +// Copy constructor. +s_policy::s_policy(const s_policy &other) : m_ptr(other.ptr()->clone()), m_name(other.m_name) {} + +// Move constructor. The default implementation is fine. +s_policy::s_policy(s_policy &&) noexcept = default; + +// Move assignment operator +s_policy &s_policy::operator=(s_policy &&other) noexcept +{ + if (this != &other) { + m_ptr = std::move(other.m_ptr); + m_name = std::move(other.m_name); + } + return *this; +} + +// Copy assignment operator +s_policy &s_policy::operator=(const s_policy &other) +{ + // Copy ctor + move assignment. + return *this = s_policy(other); +} + +// Verify the input arguments for the select() function. +// NOTE: these verification functions are very similar +// to those in r_policy. Perhaps in the future we can +// factor them out. +void s_policy::verify_select_input(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol) const +{ + // 1 - verify that the elements of inds all have the same size. + if (std::get<0>(inds).size() != std::get<1>(inds).size() || std::get<0>(inds).size() != std::get<2>(inds).size()) { + pagmo_throw(std::invalid_argument, + "an invalid group of individuals was passed to a selection policy of type '" + get_name() + + "': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are " + + std::to_string(std::get<0>(inds).size()) + ", " + std::to_string(std::get<1>(inds).size()) + + " and " + std::to_string(std::get<2>(inds).size())); + } + + // 2 - make sure nx, nix, nobj, nec, nic are sane and consistent. + // Check that the problem dimension is not zero. + if (!nx) { + pagmo_throw(std::invalid_argument, + "a problem dimension of zero was passed to a selection policy of type '" + get_name() + "'"); + } + // Verify that it is consistent with nix. + if (nix > nx) { + pagmo_throw(std::invalid_argument, + "the integer dimension (" + std::to_string(nix) + ") passed to a selection policy of type '" + + get_name() + "' is larger than the supplied problem dimension (" + std::to_string(nx) + ")"); + } + if (!nobj) { + pagmo_throw(std::invalid_argument, + "an invalid number of objectives (0) was passed to a selection policy of type '" + get_name() + + "'"); + } + if (nobj > std::numeric_limits::max() / 3u) { + pagmo_throw(std::invalid_argument, "the number of objectives (" + std::to_string(nobj) + + ") passed to a selection policy of type '" + get_name() + + "' is too large"); + } + if (nec > std::numeric_limits::max() / 3u) { + pagmo_throw(std::invalid_argument, "the number of equality constraints (" + std::to_string(nec) + + ") passed to a selection policy of type '" + get_name() + + "' is too large"); + } + if (nic > std::numeric_limits::max() / 3u) { + pagmo_throw(std::invalid_argument, "the number of inequality constraints (" + std::to_string(nic) + + ") passed to a selection policy of type '" + get_name() + + "' is too large"); + } + // Verify that the tol vector size is correct. + if (tol.size() != nec + nic) { + pagmo_throw(std::invalid_argument, "the vector of tolerances passed to a selection policy of type '" + + get_name() + "' has a dimension (" + std::to_string(tol.size()) + + ") which is inconsistent with the total number of constraints (" + + std::to_string(nec + nic) + ")"); + } + // Determine the fitness dimension. + const auto nf = nobj + nec + nic; + + // 3 - verify inds. + auto dv_checker = [nx](const vector_double &dv) { return dv.size() != nx; }; + auto fv_checker = [nf](const vector_double &fv) { return fv.size() != nf; }; + + if (std::any_of(std::get<1>(inds).begin(), std::get<1>(inds).end(), dv_checker)) { + pagmo_throw(std::invalid_argument, "not all the individuals passed to a selection policy of type '" + get_name() + + "' have the expected dimension (" + std::to_string(nx) + ")"); + } + if (std::any_of(std::get<2>(inds).begin(), std::get<2>(inds).end(), fv_checker)) { + pagmo_throw(std::invalid_argument, "not all the individuals passed to a selection policy of type '" + get_name() + + "' have the expected fitness dimension (" + std::to_string(nf) + ")"); + } +} + +// Verify the output of select(). +void s_policy::verify_select_output(const individuals_group_t &retval, vector_double::size_type nx, + vector_double::size_type nf) const +{ + // 1 - verify that the elements of retval all have the same size. + if (std::get<0>(retval).size() != std::get<1>(retval).size() + || std::get<0>(retval).size() != std::get<2>(retval).size()) { + pagmo_throw(std::invalid_argument, + "an invalid group of individuals was returned by a selection policy of type '" + get_name() + + "': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are " + + std::to_string(std::get<0>(retval).size()) + ", " + std::to_string(std::get<1>(retval).size()) + + " and " + std::to_string(std::get<2>(retval).size())); + } + + // 2 - verify that the decision/fitness vectors in retval have all + // the expected dimensions. + if (std::any_of(std::get<1>(retval).begin(), std::get<1>(retval).end(), + [nx](const vector_double &dv) { return dv.size() != nx; })) { + pagmo_throw(std::invalid_argument, "not all the individuals returned by a selection policy of type '" + + get_name() + "' have the expected dimension (" + std::to_string(nx) + + ")"); + } + if (std::any_of(std::get<2>(retval).begin(), std::get<2>(retval).end(), + [nf](const vector_double &fv) { return fv.size() != nf; })) { + pagmo_throw(std::invalid_argument, "not all the individuals returned by a selection policy of type '" + + get_name() + "' have the expected fitness dimension (" + + std::to_string(nf) + ")"); + } +} + +// Select individuals in inds. +individuals_group_t s_policy::select(const individuals_group_t &inds, const vector_double::size_type &nx, + const vector_double::size_type &nix, const vector_double::size_type &nobj, + const vector_double::size_type &nec, const vector_double::size_type &nic, + const vector_double &tol) const +{ + // Verify the input. + verify_select_input(inds, nx, nix, nobj, nec, nic, tol); + + // Call the select() method from the UDSP. + auto retval = ptr()->select(inds, nx, nix, nobj, nec, nic, tol); + + // Verify the output. + // NOTE: we checked in verify_select_input() that we can + // compute nobj + nec + nic safely. + verify_select_output(retval, nx, nobj + nec + nic); + + return retval; +} + +// Extra info. +std::string s_policy::get_extra_info() const +{ + return ptr()->get_extra_info(); +} + +// Check if the s_policy is in a valid state. +bool s_policy::is_valid() const +{ + return static_cast(m_ptr); +} + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +// Stream operator. +std::ostream &operator<<(std::ostream &os, const s_policy &s) +{ + os << "Selection policy name: " << s.get_name() << '\n'; + const auto extra_str = s.get_extra_info(); + if (!extra_str.empty()) { + os << "\nExtra info:\n" << extra_str << '\n'; + } + return os; +} + +#endif + +} // namespace pagmo diff --git a/src/topologies/base_bgl_topology.cpp b/src/topologies/base_bgl_topology.cpp new file mode 100644 index 000000000..7b606cc2b --- /dev/null +++ b/src/topologies/base_bgl_topology.cpp @@ -0,0 +1,273 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace pagmo +{ + +namespace detail +{ + +namespace +{ + +// Small helpers to reduce typing when converting to/from std::size_t. +template +std::size_t scast(I n) +{ + return boost::numeric_cast(n); +} + +bgl_topology_graph_t::vertices_size_type vcast(std::size_t n) +{ + return boost::numeric_cast(n); +} + +} // namespace + +} // namespace detail + +// Small helper function that checks that the input vertices are in the graph. +// It will throw otherwise. +void base_bgl_topology::unsafe_check_vertex_indices() const {} + +template +void base_bgl_topology::unsafe_check_vertex_indices(std::size_t idx, Args... others) const +{ + const auto nv = boost::num_vertices(m_graph); + if (idx >= nv) { + pagmo_throw(std::invalid_argument, "invalid vertex index in a BGL topology: the index is " + std::to_string(idx) + + ", but the number of vertices is only " + std::to_string(nv)); + } + unsafe_check_vertex_indices(others...); +} + +base_bgl_topology::graph_t base_bgl_topology::get_graph() const +{ + std::lock_guard lock(m_mutex); + return m_graph; +} + +base_bgl_topology::graph_t base_bgl_topology::move_graph() +{ + std::lock_guard lock(m_mutex); + return std::move(m_graph); +} + +void base_bgl_topology::set_graph(graph_t &&g) +{ + std::lock_guard lock(m_mutex); + m_graph = std::move(g); +} + +base_bgl_topology::base_bgl_topology(const base_bgl_topology &other) : m_graph(other.get_graph()) {} + +base_bgl_topology::base_bgl_topology(base_bgl_topology &&other) noexcept : m_graph(other.move_graph()) {} + +base_bgl_topology &base_bgl_topology::operator=(const base_bgl_topology &other) +{ + if (this != &other) { + set_graph(other.get_graph()); + } + return *this; +} + +base_bgl_topology &base_bgl_topology::operator=(base_bgl_topology &&other) noexcept +{ + if (this != &other) { + set_graph(other.move_graph()); + } + return *this; +} + +void base_bgl_topology::add_vertex() +{ + std::lock_guard lock(m_mutex); + boost::add_vertex(m_graph); +} + +std::size_t base_bgl_topology::num_vertices() const +{ + std::lock_guard lock(m_mutex); + return detail::scast(boost::num_vertices(m_graph)); +} + +bool base_bgl_topology::unsafe_are_adjacent(std::size_t i, std::size_t j) const +{ + unsafe_check_vertex_indices(i, j); + const auto a_vertices = boost::adjacent_vertices(boost::vertex(detail::vcast(i), m_graph), m_graph); + return std::find(a_vertices.first, a_vertices.second, boost::vertex(detail::vcast(j), m_graph)) + != a_vertices.second; +} + +bool base_bgl_topology::are_adjacent(std::size_t i, std::size_t j) const +{ + std::lock_guard lock(m_mutex); + return unsafe_are_adjacent(i, j); +} + +void base_bgl_topology::add_edge(std::size_t i, std::size_t j, double w) +{ + detail::topology_check_edge_weight(w); + + std::lock_guard lock(m_mutex); + + if (unsafe_are_adjacent(i, j)) { + pagmo_throw(std::invalid_argument, "cannot add an edge in a BGL topology: there is already an edge connecting " + + std::to_string(i) + " to " + std::to_string(j)); + } + + const auto result + = boost::add_edge(boost::vertex(detail::vcast(i), m_graph), boost::vertex(detail::vcast(j), m_graph), m_graph); + assert(result.second); + m_graph[result.first] = w; +} + +void base_bgl_topology::remove_edge(std::size_t i, std::size_t j) +{ + std::lock_guard lock(m_mutex); + + if (!unsafe_are_adjacent(i, j)) { + pagmo_throw(std::invalid_argument, "cannot remove an edge in a BGL topology: there is no edge connecting " + + std::to_string(i) + " to " + std::to_string(j)); + } + boost::remove_edge(boost::vertex(detail::vcast(i), m_graph), boost::vertex(detail::vcast(j), m_graph), m_graph); +} + +void base_bgl_topology::set_all_weights(double w) +{ + detail::topology_check_edge_weight(w); + + std::lock_guard lock(m_mutex); + + for (auto e_range = boost::edges(m_graph); e_range.first != e_range.second; ++e_range.first) { + m_graph[*e_range.first] = w; + } +} + +void base_bgl_topology::set_weight(std::size_t i, std::size_t j, double w) +{ + detail::topology_check_edge_weight(w); + + std::lock_guard lock(m_mutex); + + unsafe_check_vertex_indices(i, j); + + const auto ret + = boost::edge(boost::vertex(detail::vcast(i), m_graph), boost::vertex(detail::vcast(j), m_graph), m_graph); + if (ret.second) { + m_graph[ret.first] = w; + } else { + pagmo_throw(std::invalid_argument, "cannot set the weight of an edge in a BGL topology: the vertex " + + std::to_string(i) + " is not connected to vertex " + + std::to_string(j)); + } +} + +std::pair, vector_double> base_bgl_topology::get_connections(std::size_t i) const +{ + std::lock_guard lock(m_mutex); + + unsafe_check_vertex_indices(i); + + std::pair, vector_double> retval; + + const auto vi = boost::vertex(detail::vcast(i), m_graph); + for (auto iav = boost::inv_adjacent_vertices(vi, m_graph); iav.first != iav.second; ++iav.first) { + const auto e = boost::edge(boost::vertex(*iav.first, m_graph), vi, m_graph); + assert(e.second); + retval.first.emplace_back(detail::scast(*iav.first)); + retval.second.emplace_back(m_graph[e.first]); + } + return retval; +} + +std::string base_bgl_topology::get_extra_info() const +{ + std::ostringstream oss; + + { + std::lock_guard lock(m_mutex); + + oss << "\tNumber of vertices: " << boost::num_vertices(m_graph) << '\n'; + oss << "\tNumber of edges: " << boost::num_edges(m_graph) << '\n'; + oss << "\tAdjacency list:\n\n"; + + for (auto vs = boost::vertices(m_graph); vs.first != vs.second; ++vs.first) { + // Get the list of outgoing edges from the current vertex. + const auto erange = boost::out_edges(*vs.first, m_graph); + + // Helper to extract the target vertex from an edge descriptor (that is, + // the vertex a directed edge points to). + auto target_getter = [this](decltype(*erange.first) ed) { return boost::target(ed, m_graph); }; + + // Helper to extract the edge weight from an edge descriptor. + auto weight_getter = [this](decltype(*erange.first) ed) { return m_graph[ed]; }; + + // Make zip iterators for bundling together the target of an edge + // and its weight. + auto z_begin = boost::make_zip_iterator( + boost::make_tuple(boost::make_transform_iterator(erange.first, target_getter), + boost::make_transform_iterator(erange.first, weight_getter))); + auto z_end = boost::make_zip_iterator( + boost::make_tuple(boost::make_transform_iterator(erange.second, target_getter), + boost::make_transform_iterator(erange.second, weight_getter))); + + // Print the vertex, and its adjacent vertices together with the edges' weights. + oss << "\t\t" << *vs.first << ": "; + detail::stream_range(oss, z_begin, z_end); + oss << '\n'; + } + } + + return oss.str(); +} + +} // namespace pagmo diff --git a/src/topologies/fully_connected.cpp b/src/topologies/fully_connected.cpp new file mode 100644 index 000000000..3a95528ef --- /dev/null +++ b/src/topologies/fully_connected.cpp @@ -0,0 +1,161 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +// Default constructor: weight of 1, zero vertices. +fully_connected::fully_connected() : fully_connected(1.) {} + +// Ctor from edge weight, zero vertices. +fully_connected::fully_connected(double w) : fully_connected(0, w) {} + +// Ctor from number of vertices and edge weight +fully_connected::fully_connected(std::size_t n, double w) : m_weight(w), m_num_vertices(n) +{ + detail::topology_check_edge_weight(m_weight); +} + +// Identical copy/move constructors. +fully_connected::fully_connected(const fully_connected &other) + : m_weight(other.m_weight), m_num_vertices(other.m_num_vertices.load(std::memory_order_relaxed)) +{ +} + +fully_connected::fully_connected(fully_connected &&other) noexcept + : fully_connected(static_cast(other)) +{ +} + +// Push back implementation. +void fully_connected::push_back() +{ + m_num_vertices.fetch_add(1u, std::memory_order_relaxed); +} + +// Get connections. +std::pair, vector_double> fully_connected::get_connections(std::size_t i) const +{ + // Fetch the number of vertices. + const auto num_vertices = m_num_vertices.load(std::memory_order_relaxed); + + if (i >= num_vertices) { + pagmo_throw(std::invalid_argument, + "Cannot get the connections to the vertex at index " + std::to_string(i) + + " in a fully connected topology: the number of vertices in the topology is only " + + std::to_string(num_vertices)); + } + + // Init the retval. + std::pair, vector_double> retval; + + // Prepare storage for the indices list. + retval.first.resize(boost::numeric_cast(num_vertices - 1u)); + + // Fill in the indices list. + for (std::size_t j = 0; j < i; ++j) { + retval.first[j] = j; + } + for (std::size_t j = i + 1u; j < num_vertices; ++j) { + retval.first[j - 1u] = j; + } + + // Fill the weights list with m_weight. + retval.second.resize(boost::numeric_cast(num_vertices - 1u), m_weight); + + return retval; +} + +// Topology name. +std::string fully_connected::get_name() const +{ + return "Fully connected"; +} + +// Topology extra info. +std::string fully_connected::get_extra_info() const +{ + return "\tNumber of vertices: " + std::to_string(m_num_vertices.load(std::memory_order_relaxed)) + + "\n\tEdges' weight: " + std::to_string(m_weight) + "\n"; +} + +// Get the edge weight. +double fully_connected::get_weight() const +{ + return m_weight; +} + +// Get the number of vertices. +std::size_t fully_connected::num_vertices() const +{ + return m_num_vertices.load(std::memory_order_relaxed); +} + +// Serialization. +template +void fully_connected::save(Archive &ar, unsigned) const +{ + detail::archive(ar, m_weight, m_num_vertices.load(std::memory_order_relaxed)); +} + +template +void fully_connected::load(Archive &ar, unsigned) +{ + std::size_t num_vertices; + + ar >> m_weight; + ar >> num_vertices; + + m_num_vertices.store(num_vertices, std::memory_order_relaxed); +} + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_IMPLEMENT(pagmo::fully_connected) diff --git a/src/topologies/ring.cpp b/src/topologies/ring.cpp new file mode 100644 index 000000000..9a0d8e6fe --- /dev/null +++ b/src/topologies/ring.cpp @@ -0,0 +1,130 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include + +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +// Default ctor. +ring::ring() : m_weight(1) {} + +// Ctor from edge weight. +ring::ring(double w) : m_weight(w) +{ + detail::topology_check_edge_weight(m_weight); +} + +// Ctor from number of vertices and edge weight. +ring::ring(std::size_t n, double w) : m_weight(w) +{ + detail::topology_check_edge_weight(m_weight); + + for (std::size_t i = 0; i < n; ++i) { + push_back(); + } +} + +// Serialization. +template +void ring::serialize(Archive &ar, unsigned) +{ + detail::archive(ar, boost::serialization::base_object(*this), m_weight); +} + +// Add vertex. +void ring::push_back() +{ + // Add the new vertex. + add_vertex(); + + // Connect it. + const auto size = num_vertices(); + assert(size); + + switch (size) { + case 1u: { + // If the topology was empty, no connections need to be established. + break; + } + case 2u: { + // The two elements connect each other. + add_edge(0, 1, m_weight); + add_edge(1, 0, m_weight); + break; + } + case 3u: { + // A triangle of double links. + add_edge(1, 2, m_weight); + add_edge(2, 1, m_weight); + add_edge(2, 0, m_weight); + add_edge(0, 2, m_weight); + break; + } + default: { + // Remove the pair of links that close the current ring. + remove_edge(size - 2u, 0); + remove_edge(0, size - 2u); + // Connect the new "last" vertex to the previous "last" vertex. + add_edge(size - 2u, size - 1u, m_weight); + add_edge(size - 1u, size - 2u, m_weight); + // Connect the new last vertex to the first. + add_edge(0, size - 1u, m_weight); + add_edge(size - 1u, 0, m_weight); + } + } +} + +// Get the edge weight used for construction. +double ring::get_weight() const +{ + return m_weight; +} + +// Topology name. +std::string ring::get_name() const +{ + return "Ring"; +} + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_IMPLEMENT(pagmo::ring) diff --git a/src/topologies/unconnected.cpp b/src/topologies/unconnected.cpp new file mode 100644 index 000000000..bac54b65a --- /dev/null +++ b/src/topologies/unconnected.cpp @@ -0,0 +1,55 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include + +#include +#include +#include +#include + +namespace pagmo +{ + +// Get connections (returns empty vectors). +std::pair, vector_double> unconnected::get_connections(std::size_t) const +{ + return std::make_pair(std::vector{}, vector_double{}); +} + +// Serialization. +template +void unconnected::serialize(Archive &, unsigned) +{ +} + +} // namespace pagmo + +PAGMO_S11N_TOPOLOGY_IMPLEMENT(pagmo::unconnected) diff --git a/src/topology.cpp b/src/topology.cpp new file mode 100644 index 000000000..0d70b8806 --- /dev/null +++ b/src/topology.cpp @@ -0,0 +1,169 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// MINGW-specific warnings. +#if defined(__GNUC__) && defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +namespace pagmo +{ + +namespace detail +{ + +// Helper to check if w can be used as edge weight +// in a topology. +void topology_check_edge_weight(double w) +{ + if (!std::isfinite(w)) { + pagmo_throw(std::invalid_argument, + "invalid weight for the edge of a topology: the value " + std::to_string(w) + " is not finite"); + } + if (w < 0. || w > 1.) { + pagmo_throw(std::invalid_argument, "invalid weight for the edge of a topology: the value " + std::to_string(w) + + " is not in the [0., 1.] range"); + } +} + +} // namespace detail + +topology::topology() : topology(unconnected{}) {} + +void topology::generic_ctor_impl() +{ + // We store at construction the value returned from the user implemented get_name(). + m_name = ptr()->get_name(); +} + +topology::topology(const topology &other) : m_ptr(other.m_ptr->clone()), m_name(other.m_name) {} + +topology::topology(topology &&other) noexcept : m_ptr(std::move(other.m_ptr)), m_name(std::move(other.m_name)) {} + +topology &topology::operator=(topology &&other) noexcept +{ + if (this != &other) { + m_ptr = std::move(other.m_ptr); + m_name = std::move(other.m_name); + } + return *this; +} + +topology &topology::operator=(const topology &other) +{ + // Copy ctor + move assignment. + return *this = topology(other); +} + +std::string topology::get_extra_info() const +{ + return ptr()->get_extra_info(); +} + +bool topology::is_valid() const +{ + return static_cast(m_ptr); +} + +std::pair, vector_double> topology::get_connections(std::size_t n) const +{ + auto retval = ptr()->get_connections(n); + + // Check the returned value. + if (retval.first.size() != retval.second.size()) { + pagmo_throw(std::invalid_argument, + "An invalid pair of vectors was returned by the 'get_connections()' method of the '" + get_name() + + "' topology: the vector of connecting islands has a size of " + + std::to_string(retval.first.size()) + + ", while the vector of migration probabilities has a size of " + + std::to_string(retval.second.size()) + " (the two sizes must be equal)"); + } + + for (const auto &p : retval.second) { + if (!std::isfinite(p)) { + pagmo_throw( + std::invalid_argument, + "An invalid non-finite migration probability of " + std::to_string(p) + + " was detected in the vector of migration probabilities returned by the 'get_connections()' " + "method of the '" + + get_name() + "' topology"); + } + if (p < 0. || p > 1.) { + pagmo_throw( + std::invalid_argument, + "An invalid migration probability of " + std::to_string(p) + + " was detected in the vector of migration probabilities returned by the 'get_connections()' " + "method of the '" + + get_name() + "' topology: the value must be in the [0., 1.] range"); + } + } + + return retval; +} + +void topology::push_back() +{ + ptr()->push_back(); +} + +void topology::push_back(unsigned n) +{ + for (auto i = 0u; i < n; ++i) { + push_back(); + } +} + +#if !defined(PAGMO_DOXYGEN_INVOKED) + +std::ostream &operator<<(std::ostream &os, const topology &t) +{ + os << "Topology name: " << t.get_name(); + const auto extra_str = t.get_extra_info(); + if (!extra_str.empty()) { + os << "\nExtra info:\n" << extra_str; + } + return os; +} + +#endif + +} // namespace pagmo diff --git a/src/utils/constrained.cpp b/src/utils/constrained.cpp index 1d8128d30..089537fd5 100644 --- a/src/utils/constrained.cpp +++ b/src/utils/constrained.cpp @@ -47,7 +47,7 @@ namespace pagmo * with respect to the following strict ordering: * - \f$f_1 \prec f_2\f$ if \f$f_1\f$ is feasible and \f$f_2\f$ is not. * - \f$f_1 \prec f_2\f$ if \f$f_1\f$ is they are both infeasible, but \f$f_1\f$ - * violates less constraints than \f$f_2\f$, or in case they both violate the same + * violates fewer constraints than \f$f_2\f$, or in case they both violate the same * number of constraints, if the \f$L_2\f$ norm of the overall constraint violation is smaller. * - \f$f_1 \prec f_2\f$ if both fitness vectors are feasible and the objective value @@ -152,7 +152,7 @@ bool compare_fc(const vector_double &f1, const vector_double &f2, vector_double: * with respect to the following strict ordering: * - \f$f_1 \prec f_2\f$ if \f$f_1\f$ is feasible and \f$f_2\f$ is not. * - \f$f_1 \prec f_2\f$ if \f$f_1\f$ is they are both infeasible, but \f$f_1\f$ - * violates less constraints than \f$f_2\f$, or in case they both violate the same + * violates fewer constraints than \f$f_2\f$, or in case they both violate the same * number of constraints, if the \f$L_2\f$ norm of the overall constraint violation * is smaller. * - \f$f_1 \prec f_2\f$ if both fitness vectors are feasible and the objective value diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 465c008bd..6f6a0e27b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,8 @@ ADD_PAGMO_TESTCASE(algorithm) ADD_PAGMO_TESTCASE(algorithm_type_traits) ADD_PAGMO_TESTCASE(archipelago) ADD_PAGMO_TESTCASE(archipelago_torture_test) +ADD_PAGMO_TESTCASE(base_bgl_topology) +ADD_PAGMO_TESTCASE(base_sr_policy) ADD_PAGMO_TESTCASE(bfe) ADD_PAGMO_TESTCASE(bee_colony) ADD_PAGMO_TESTCASE(cec2006) @@ -41,6 +43,8 @@ ADD_PAGMO_TESTCASE(decompose) ADD_PAGMO_TESTCASE(default_bfe) ADD_PAGMO_TESTCASE(discrepancy) ADD_PAGMO_TESTCASE(dtlz) +ADD_PAGMO_TESTCASE(fair_replace) +ADD_PAGMO_TESTCASE(fully_connected) ADD_PAGMO_TESTCASE(gwo) ADD_PAGMO_TESTCASE(gaco) ADD_PAGMO_TESTCASE(generic) @@ -51,6 +55,7 @@ ADD_PAGMO_TESTCASE(hypervolume) ADD_PAGMO_TESTCASE(hock_schittkowsky_71) ADD_PAGMO_TESTCASE(inventory) ADD_PAGMO_TESTCASE(lennard_jones) +ADD_PAGMO_TESTCASE(migration_torture_test) ADD_PAGMO_TESTCASE(minlp_rastrigin) ADD_PAGMO_TESTCASE(ihs) ADD_PAGMO_TESTCASE(io) @@ -67,20 +72,26 @@ ADD_PAGMO_TESTCASE(problem) ADD_PAGMO_TESTCASE(problem_type_traits) ADD_PAGMO_TESTCASE(pso) ADD_PAGMO_TESTCASE(pso_gen) +ADD_PAGMO_TESTCASE(r_policy) ADD_PAGMO_TESTCASE(rastrigin) +ADD_PAGMO_TESTCASE(ring) ADD_PAGMO_TESTCASE(rng) ADD_PAGMO_TESTCASE(rng_serialization) ADD_PAGMO_TESTCASE(rosenbrock) +ADD_PAGMO_TESTCASE(s_policy) ADD_PAGMO_TESTCASE(sade) ADD_PAGMO_TESTCASE(simulated_annealing) ADD_PAGMO_TESTCASE(sga) ADD_PAGMO_TESTCASE(schwefel) ADD_PAGMO_TESTCASE(sea) +ADD_PAGMO_TESTCASE(select_best) ADD_PAGMO_TESTCASE(threading) ADD_PAGMO_TESTCASE(thread_bfe) ADD_PAGMO_TESTCASE(thread_island) +ADD_PAGMO_TESTCASE(topology) ADD_PAGMO_TESTCASE(translate) ADD_PAGMO_TESTCASE(type_traits) +ADD_PAGMO_TESTCASE(unconnected) ADD_PAGMO_TESTCASE(unconstrain) ADD_PAGMO_TESTCASE(wfg) ADD_PAGMO_TESTCASE(zdt) diff --git a/tests/archipelago.cpp b/tests/archipelago.cpp index 60cb5cd0f..eae08b722 100644 --- a/tests/archipelago.cpp +++ b/tests/archipelago.cpp @@ -38,7 +38,9 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include +#include #include #include #include @@ -47,6 +49,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include @@ -60,8 +63,14 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include +#include +#include +#include +#include +#include #include using namespace pagmo; @@ -73,11 +82,45 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) using size_type = archipelago::size_type; archipelago archi; + + std::cout << archi << '\n'; + + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.get_topology().is()); BOOST_CHECK(archi.size() == 0u); + BOOST_CHECK(archi.get_migration_log().empty()); + BOOST_CHECK(archi.get_migrants_db().empty()); + + // Copy constructor. + archi.set_topology(topology{ring{}}); + archi.set_migration_type(migration_type::broadcast); + archi.set_migrant_handling(migrant_handling::evict); + auto archi_copy(archi); + BOOST_CHECK(archi_copy.get_migration_type() == migration_type::broadcast); + BOOST_CHECK(archi_copy.get_migrant_handling() == migrant_handling::evict); + BOOST_CHECK(archi_copy.get_topology().is()); + BOOST_CHECK(archi_copy.size() == 0u); + BOOST_CHECK(archi_copy.get_migration_log().empty()); + BOOST_CHECK(archi_copy.get_migrants_db().empty()); + + // Move constructor. + auto archi_move(std::move(archi_copy)); + BOOST_CHECK(archi_move.get_migration_type() == migration_type::broadcast); + BOOST_CHECK(archi_move.get_migrant_handling() == migrant_handling::evict); + BOOST_CHECK(archi_move.get_topology().is()); + BOOST_CHECK(archi_move.size() == 0u); + BOOST_CHECK(archi_move.get_migration_log().empty()); + BOOST_CHECK(archi_move.get_migrants_db().empty()); + archipelago archi2(0u, de{}, rosenbrock{}, 10u); BOOST_CHECK(archi2.size() == 0u); + BOOST_CHECK(archi2.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi2.get_migrant_handling() == migrant_handling::preserve); archipelago archi3(5u, de{}, rosenbrock{}, 10u); BOOST_CHECK(archi3.size() == 5u); + BOOST_CHECK(archi3.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3.get_migrant_handling() == migrant_handling::preserve); for (size_type i = 0; i < 5u; ++i) { BOOST_CHECK(archi3[i].status() != evolve_status::busy); BOOST_CHECK(archi3[i].get_algorithm().is()); @@ -86,19 +129,25 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) } archi3 = archipelago{5u, thread_island{}, de{}, rosenbrock{}, 10u}; BOOST_CHECK(archi3.size() == 5u); + BOOST_CHECK(archi3.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3.get_migrant_handling() == migrant_handling::preserve); std::vector seeds; for (size_type i = 0; i < 5u; ++i) { BOOST_CHECK(archi3[i].status() != evolve_status::busy); BOOST_CHECK(archi3[i].get_algorithm().is()); BOOST_CHECK(archi3[i].get_population().size() == 10u); BOOST_CHECK(archi3[i].get_population().get_problem().is()); + BOOST_CHECK(archi3[i].get_r_policy().is()); + BOOST_CHECK(archi3[i].get_s_policy().is()); seeds.push_back(archi3[i].get_population().get_seed()); } - // Check seeds are different. + // Check seeds are different (not guaranteed but very likely). std::sort(seeds.begin(), seeds.end()); BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); archi3 = archipelago{5u, thread_island{}, de{}, population{rosenbrock{}, 10u}}; BOOST_CHECK(archi3.size() == 5u); + BOOST_CHECK(archi3.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3.get_migrant_handling() == migrant_handling::preserve); for (size_type i = 0; i < 5u; ++i) { BOOST_CHECK(archi3[i].status() != evolve_status::busy); BOOST_CHECK(archi3[i].get_algorithm().is()); @@ -107,6 +156,8 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) } archi3 = archipelago{5u, thread_island{}, de{}, population{rosenbrock{}, 10u, 123u}}; BOOST_CHECK(archi3.size() == 5u); + BOOST_CHECK(archi3.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3.get_migrant_handling() == migrant_handling::preserve); for (size_type i = 0; i < 5u; ++i) { BOOST_CHECK(archi3[i].status() != evolve_status::busy); BOOST_CHECK(archi3[i].get_algorithm().is()); @@ -115,6 +166,8 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) } // A couple of tests for the constructor which contains a seed argument. archipelago archi3a{5u, thread_island{}, de{}, rosenbrock{}, 10u, 123u}; + BOOST_CHECK(archi3a.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3a.get_migrant_handling() == migrant_handling::preserve); seeds.clear(); std::transform(archi3a.begin(), archi3a.end(), std::back_inserter(seeds), [](const island &isl) { return isl.get_population().get_seed(); }); @@ -122,6 +175,8 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); std::vector seeds2; archipelago archi3b{5u, de{}, rosenbrock{}, 10u, 123u}; + BOOST_CHECK(archi3b.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3b.get_migrant_handling() == migrant_handling::preserve); std::transform(archi3b.begin(), archi3b.end(), std::back_inserter(seeds2), [](const island &isl) { return isl.get_population().get_seed(); }); std::sort(seeds2.begin(), seeds2.end()); @@ -155,6 +210,7 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) })); auto archi4 = archi3; BOOST_CHECK(archi4.size() == 5u); + BOOST_CHECK(archi4.get_migrants_db().size() == 5u); for (size_type i = 0; i < 5u; ++i) { BOOST_CHECK(archi4[i].status() != evolve_status::busy); BOOST_CHECK(archi4[i].get_algorithm().is()); @@ -164,6 +220,7 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) archi4.evolve(10); auto archi5 = archi4; BOOST_CHECK(archi5.size() == 5u); + BOOST_CHECK(archi5.get_migrants_db().size() == 5u); for (size_type i = 0; i < 5u; ++i) { BOOST_CHECK(archi5[i].status() != evolve_status::busy); BOOST_CHECK(archi5[i].get_algorithm().is()); @@ -222,6 +279,251 @@ BOOST_AUTO_TEST_CASE(archipelago_construction) #endif } +struct udrp00 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return individuals_group_t{}; + } +}; + +struct udsp00 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return individuals_group_t{}; + } +}; + +BOOST_AUTO_TEST_CASE(archipelago_policy_constructors) +{ + // algo, prob, size, policies, no seed. + archipelago archi{5u, de{}, rosenbrock{}, 10u, udrp00{}, udsp00{}}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + } + + // algo, prob, size, policies, seed. + std::vector seeds; + archi = archipelago{5u, de{}, rosenbrock{}, 10u, udrp00{}, udsp00{}, 5}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + BOOST_CHECK(archi.get_migrants_db().size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + seeds.push_back(isl.get_population().get_seed()); + } + // Check seeds are different (not guaranteed but very likely). + std::sort(seeds.begin(), seeds.end()); + BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); + + // algo, prob, bfe, rpol, spol, no seed. + archi = archipelago{5u, de{}, rosenbrock{}, bfe{}, 10u, udrp00{}, udsp00{}}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + } + + // algo, prob, bfe, rpol, spol, seed. + seeds.clear(); + archi = archipelago{5u, de{}, rosenbrock{}, bfe{}, 10u, udrp00{}, udsp00{}, 5}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + seeds.push_back(isl.get_population().get_seed()); + } + std::sort(seeds.begin(), seeds.end()); + BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); + + // isl, algo, prob, rpol, spol, no seed. + archi = archipelago{5u, thread_island{}, de{}, rosenbrock{}, 10u, udrp00{}, udsp00{}}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + } + + // isl, algo, prob, rpol, spol, seed. + seeds.clear(); + archi = archipelago{5u, thread_island{}, de{}, rosenbrock{}, 10u, udrp00{}, udsp00{}, 5}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + BOOST_CHECK(archi.get_migrants_db().size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + seeds.push_back(isl.get_population().get_seed()); + } + std::sort(seeds.begin(), seeds.end()); + BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); + + // isl, algo, prob, bfe, rpol, spol, no seed. + archi = archipelago{5u, thread_island{}, de{}, rosenbrock{}, bfe{}, 10u, udrp00{}, udsp00{}}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + } + + // isl, algo, prob, bfe, rpol, spol, seed. + seeds.clear(); + archi = archipelago{5u, thread_island{}, de{}, rosenbrock{}, bfe{}, 10u, udrp00{}, udsp00{}, 5}; + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + BOOST_CHECK(archi.size() == 5u); + BOOST_CHECK(archi.get_migrants_db().size() == 5u); + for (const auto &isl : archi) { + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + seeds.push_back(isl.get_population().get_seed()); + } + std::sort(seeds.begin(), seeds.end()); + BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); +} + +BOOST_AUTO_TEST_CASE(archipelago_topology_constructors) +{ + archipelago archi{topology{}}; + + BOOST_CHECK(archi.get_topology().is()); + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + + archi = archipelago{ring{}}; + BOOST_CHECK(archi.get_topology().is()); + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + + // Check that the topology is preserved in copy/move ops. + auto archi2(archi); + BOOST_CHECK(archi2.get_topology().is()); + BOOST_CHECK(archi2.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi2.get_migrant_handling() == migrant_handling::preserve); + + auto archi3(std::move(archi2)); + BOOST_CHECK(archi3.get_topology().is()); + BOOST_CHECK(archi3.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi3.get_migrant_handling() == migrant_handling::preserve); + + archipelago archi4; + archi4 = archi3; + BOOST_CHECK(archi4.get_topology().is()); + BOOST_CHECK(archi4.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi4.get_migrant_handling() == migrant_handling::preserve); + + archipelago archi5; + archi5 = std::move(archi4); + BOOST_CHECK(archi5.get_topology().is()); + BOOST_CHECK(archi5.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi5.get_migrant_handling() == migrant_handling::preserve); + + // Ctors from topology and number of islands. + archi = archipelago{topology{}, 5}; + BOOST_CHECK(archi.size() == 5u); + BOOST_CHECK(archi.get_topology().is()); + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + + // Invoke one of the complicated ctors + archi = archipelago{ring{}, 5u, thread_island{}, de{}, rosenbrock{}, bfe{}, 10u, udrp00{}, udsp00{}, 5}; + BOOST_CHECK(archi.size() == 5u); + BOOST_CHECK(archi.get_topology().is()); + BOOST_CHECK(archi.get_migration_type() == migration_type::p2p); + BOOST_CHECK(archi.get_migrant_handling() == migrant_handling::preserve); + std::vector seeds; + for (const auto &isl : archi) { + BOOST_CHECK(isl.is()); + BOOST_CHECK(isl.get_algorithm().is()); + BOOST_CHECK(isl.get_population().get_problem().is()); + BOOST_CHECK(isl.get_population().size() == 10u); + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); + seeds.push_back(isl.get_population().get_seed()); + } + std::sort(seeds.begin(), seeds.end()); + BOOST_CHECK(std::unique(seeds.begin(), seeds.end()) == seeds.end()); +} + +BOOST_AUTO_TEST_CASE(archipelago_push_back_migr) +{ + // Verify that push_back() also calls the topology's push_back() and adds + // entrie to the migrants db. + archipelago archi{ring{}}; + + BOOST_CHECK(archi.get_topology().extract()->num_vertices() == 0u); + BOOST_CHECK(archi.get_migrants_db().size() == 0u); + + archi.push_back(); + + BOOST_CHECK(archi.get_topology().extract()->num_vertices() == 1u); + BOOST_CHECK(!archi.get_topology().extract()->are_adjacent(0, 0)); + BOOST_CHECK(archi.get_migrants_db().size() == 1u); + + archi.push_back(); + BOOST_CHECK(archi.get_topology().extract()->num_vertices() == 2u); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(1, 0)); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(0, 1)); + BOOST_CHECK(archi.get_migrants_db().size() == 2u); + + archi.push_back(); + BOOST_CHECK(archi.get_topology().extract()->num_vertices() == 3u); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(0, 1)); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(1, 0)); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(1, 2)); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(2, 1)); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(0, 2)); + BOOST_CHECK(archi.get_topology().extract()->are_adjacent(2, 0)); + BOOST_CHECK(archi.get_migrants_db().size() == 3u); +} + +BOOST_AUTO_TEST_CASE(archipelago_topology_setter) +{ + archipelago archi{ring{}}; + + archi.push_back(); + archi.push_back(); + archi.push_back(); + archi.push_back(); + + BOOST_CHECK(archi.get_topology().is()); + + topology new_top{fully_connected{}}; + new_top.push_back(); + new_top.push_back(); + new_top.push_back(); + new_top.push_back(); + + archi.set_topology(new_top); + + BOOST_CHECK(archi.get_topology().is()); + + BOOST_CHECK((archi.get_topology().get_connections(0).first == std::vector{1, 2, 3})); + BOOST_CHECK((archi.get_topology().get_connections(1).first == std::vector{0, 2, 3})); + BOOST_CHECK((archi.get_topology().get_connections(2).first == std::vector{0, 1, 3})); + BOOST_CHECK((archi.get_topology().get_connections(3).first == std::vector{0, 1, 2})); +} + BOOST_AUTO_TEST_CASE(archipelago_island_access) { archipelago archi0; @@ -335,28 +637,41 @@ BOOST_AUTO_TEST_CASE(archipelago_stream) std::ostringstream oss; oss << a; BOOST_CHECK(!oss.str().empty()); + BOOST_CHECK(boost::contains(oss.str(), "Topology:")); + BOOST_CHECK(boost::contains(oss.str(), "Migration type:")); + BOOST_CHECK(boost::contains(oss.str(), "Migrant handling policy:")); } BOOST_AUTO_TEST_CASE(archipelago_serialization) { - archipelago a{10, de{}, population{rosenbrock{}, 25}}; - a.evolve(); + archipelago a{ring{}, 10, de{}, population{rosenbrock{}, 25}}; + a.evolve(10); a.wait_check(); BOOST_CHECK(a.status() == evolve_status::idle); + a.set_migration_type(migration_type::broadcast); + a.set_migrant_handling(migrant_handling::evict); std::stringstream ss; auto before = boost::lexical_cast(a); + const auto mig_db_before = a.get_migrants_db(); + const auto mig_log_before = a.get_migration_log(); // Now serialize, deserialize and compare the result. { boost::archive::binary_oarchive oarchive(ss); oarchive << a; } - a = archipelago{10, de{}, population{rosenbrock{}, 25}}; + a = archipelago{}; { boost::archive::binary_iarchive iarchive(ss); iarchive >> a; } auto after = boost::lexical_cast(a); BOOST_CHECK_EQUAL(before, after); + BOOST_CHECK(a.get_migration_type() == migration_type::broadcast); + BOOST_CHECK(a.get_migrant_handling() == migrant_handling::evict); + BOOST_CHECK(a.get_migrants_db() == mig_db_before); + BOOST_CHECK(a.get_migration_log() == mig_log_before); + BOOST_CHECK(a.get_topology().is()); + BOOST_CHECK(a.get_topology().extract()->num_vertices() == 10u); } BOOST_AUTO_TEST_CASE(archipelago_iterator_tests) diff --git a/tests/base_bgl_topology.cpp b/tests/base_bgl_topology.cpp new file mode 100644 index 000000000..efcd378af --- /dev/null +++ b/tests/base_bgl_topology.cpp @@ -0,0 +1,287 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE base_bgl_topology +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +using namespace pagmo; + +using bbt = base_bgl_topology; + +BOOST_AUTO_TEST_CASE(basic_test) +{ + bbt t0; + BOOST_CHECK(t0.num_vertices() == 0u); + + t0.add_vertex(); + BOOST_CHECK(t0.num_vertices() == 1u); + BOOST_CHECK(t0.get_connections(0).first.empty()); + BOOST_CHECK(t0.get_connections(0).second.empty()); + + t0.add_vertex(); + t0.add_vertex(); + t0.add_vertex(); + BOOST_CHECK(t0.num_vertices() == 4u); + BOOST_CHECK(t0.get_connections(0).first.empty()); + BOOST_CHECK(t0.get_connections(0).second.empty()); + BOOST_CHECK(t0.get_connections(1).first.empty()); + BOOST_CHECK(t0.get_connections(1).second.empty()); + BOOST_CHECK(t0.get_connections(2).first.empty()); + BOOST_CHECK(t0.get_connections(2).second.empty()); + BOOST_CHECK(t0.get_connections(3).first.empty()); + BOOST_CHECK(t0.get_connections(3).second.empty()); + + t0.add_edge(0, 1); + t0.add_edge(0, 2); + BOOST_CHECK(t0.are_adjacent(0, 1)); + BOOST_CHECK(t0.are_adjacent(0, 2)); + BOOST_CHECK(!t0.are_adjacent(1, 0)); + BOOST_CHECK(!t0.are_adjacent(2, 0)); + + t0.add_edge(1, 0); + t0.add_edge(2, 0); + + auto conns = t0.get_connections(0); + using c_vec = decltype(conns.first); + using w_vec = decltype(conns.second); + + BOOST_CHECK((conns.first == c_vec{1, 2} || conns.first == c_vec{2, 1})); + BOOST_CHECK((conns.second == w_vec{1., 1.})); + + conns = t0.get_connections(1); + + BOOST_CHECK((conns.first == c_vec{0})); + BOOST_CHECK((conns.second == w_vec{1.})); + + conns = t0.get_connections(2); + + BOOST_CHECK((conns.first == c_vec{0})); + BOOST_CHECK((conns.second == w_vec{1.})); + + t0.remove_edge(0, 2); + BOOST_CHECK(t0.get_connections(2).first.empty()); + BOOST_CHECK(t0.get_connections(2).second.empty()); + + t0.set_weight(0, 1, .5); + + conns = t0.get_connections(1); + + BOOST_CHECK((conns.first == c_vec{0})); + BOOST_CHECK((conns.second == w_vec{.5})); + + t0.set_all_weights(.25); + BOOST_CHECK(t0.get_connections(0).second.size() == 2u); + BOOST_CHECK(t0.get_connections(0).second[0] == .25); + BOOST_CHECK(t0.get_connections(0).second[1] == .25); + + // Test copy/move. + auto t1(t0); + BOOST_CHECK(t1.get_connections(0).second.size() == 2u); + BOOST_CHECK(t1.get_connections(0).second[0] == .25); + BOOST_CHECK(t1.get_connections(0).second[1] == .25); + + auto t2(std::move(t1)); + BOOST_CHECK(t2.get_connections(0).second.size() == 2u); + BOOST_CHECK(t2.get_connections(0).second[0] == .25); + BOOST_CHECK(t2.get_connections(0).second[1] == .25); + + bbt t3; + t3 = t2; + BOOST_CHECK(t3.get_connections(0).second.size() == 2u); + BOOST_CHECK(t3.get_connections(0).second[0] == .25); + BOOST_CHECK(t3.get_connections(0).second[1] == .25); + + bbt t4; + t4 = std::move(t3); + BOOST_CHECK(t4.get_connections(0).second.size() == 2u); + BOOST_CHECK(t4.get_connections(0).second[0] == .25); + BOOST_CHECK(t4.get_connections(0).second[1] == .25); + + const auto str = t4.get_extra_info(); + BOOST_CHECK(boost::contains(str, "Number of vertices: 4")); + BOOST_CHECK(boost::contains(str, "Number of edges: 3")); +} + +BOOST_AUTO_TEST_CASE(error_handling) +{ + bbt t0; + + BOOST_CHECK_EXCEPTION(t0.are_adjacent(0, 1), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "invalid vertex index in a BGL topology: the index is 0, but the number of vertices is only 0"); + }); + + t0.add_vertex(); + t0.add_vertex(); + t0.add_vertex(); + + BOOST_CHECK_EXCEPTION(t0.get_connections(42), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "invalid vertex index in a BGL topology: the index is 42, but the number of vertices is only 3"); + }); + + BOOST_CHECK_EXCEPTION(t0.add_edge(4, 5), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "invalid vertex index in a BGL topology: the index is 4, but the number of vertices is only 3"); + }); + + t0.add_edge(0, 2); + BOOST_CHECK_EXCEPTION(t0.add_edge(0, 2), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "cannot add an edge in a BGL topology: there is already an edge connecting 0 to 2"); + }); + + t0.remove_edge(0, 2); + BOOST_CHECK_EXCEPTION(t0.remove_edge(0, 2), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "cannot remove an edge in a BGL topology: there is no edge connecting 0 to 2"); + }); + + t0.add_edge(0, 2); + t0.set_weight(0, 2, .2); + BOOST_CHECK_EXCEPTION(t0.set_weight(0, 2, -1.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), " is not in the [0., 1.] range"); + }); + BOOST_CHECK_EXCEPTION(t0.set_all_weights(-1.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), " is not in the [0., 1.] range"); + }); + BOOST_CHECK_EXCEPTION(t0.set_weight(0, 2, std::numeric_limits::infinity()), std::invalid_argument, + [](const std::invalid_argument &ia) { return boost::contains(ia.what(), " is not finite"); }); + BOOST_CHECK_EXCEPTION(t0.set_all_weights(std::numeric_limits::infinity()), std::invalid_argument, + [](const std::invalid_argument &ia) { return boost::contains(ia.what(), " is not finite"); }); + BOOST_CHECK_EXCEPTION(t0.set_weight(0, 1, .2), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "cannot set the weight of an edge in a BGL topology: the vertex 0 is not connected to vertex 1"); + }); +} + +BOOST_AUTO_TEST_CASE(s11n_test) +{ + bbt t0; + t0.add_vertex(); + t0.add_vertex(); + t0.add_vertex(); + t0.add_vertex(); + t0.add_edge(0, 1); + t0.add_edge(0, 2); + t0.add_edge(1, 0); + t0.set_weight(0, 1, .5); + + // Minimal serialization test. + { + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << t0; + } + bbt t1; + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> t1; + } + BOOST_CHECK(t1.num_vertices() == 4u); + BOOST_CHECK(t1.are_adjacent(0, 1)); + BOOST_CHECK(t1.are_adjacent(0, 2)); + BOOST_CHECK(t1.are_adjacent(1, 0)); + BOOST_CHECK(!t1.are_adjacent(2, 0)); + BOOST_CHECK(t1.get_connections(1).second.size() == 1u); + BOOST_CHECK(t1.get_connections(1).second[0] == .5); + } +} + +BOOST_AUTO_TEST_CASE(thread_torture_test) +{ + std::atomic barrier(0), failures(0); + + bbt t0; + t0.add_vertex(); + t0.add_vertex(); + t0.add_vertex(); + t0.add_vertex(); + t0.add_edge(0, 1); + t0.add_edge(0, 2); + t0.add_edge(1, 0); + t0.set_weight(0, 1, .5); + + std::vector threads; + for (auto i = 0; i < 10; ++i) { + threads.emplace_back([&barrier, &failures, &t0]() { + ++barrier; + while (barrier.load() != 10) { + } + + for (int j = 0; j < 100; ++j) { + auto t1(t0); + bbt t2; + t2 = t0; + + t0.add_vertex(); + failures += t0.num_vertices() < 4u; + t0.add_vertex(); + failures += !t0.are_adjacent(0, 1); + t0.add_vertex(); + failures += t0.get_connections(0).first.size() == 0u; + t0.add_vertex(); + + try { + t0.add_edge(0u, 4u); + t0.set_weight(0u, 4u, .3); + t0.remove_edge(0u, 4u); + } catch (const std::invalid_argument &) { + } + + t0.set_all_weights(.1); + + failures += t0.get_extra_info().empty(); + + t0 = std::move(t2); + } + }); + } + + for (auto &t : threads) { + t.join(); + } + + BOOST_CHECK(failures.load() == 0); +} diff --git a/tests/base_sr_policy.cpp b/tests/base_sr_policy.cpp new file mode 100644 index 000000000..f9c529eed --- /dev/null +++ b/tests/base_sr_policy.cpp @@ -0,0 +1,109 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE base_sr_policy +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace pagmo; + +using bsrp = detail::base_sr_policy; + +BOOST_AUTO_TEST_CASE(basic_test) +{ + bsrp b0(0); + BOOST_CHECK(b0.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(b0.get_migr_rate()) == 0); + + b0 = bsrp(4u); + BOOST_CHECK(b0.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(b0.get_migr_rate()) == 4); + + b0 = bsrp(.1); + BOOST_CHECK(b0.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(b0.get_migr_rate()) == .1); + + b0 = bsrp(0.l); + BOOST_CHECK(b0.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(b0.get_migr_rate()) == 0.); + + b0 = bsrp(1.f); + BOOST_CHECK(b0.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(b0.get_migr_rate()) == 1.); + + BOOST_CHECK((!std::is_constructible::value)); + + // Minimal serialization test. + { + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << b0; + } + bsrp b1(0); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> b1; + } + BOOST_CHECK(b1.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(b1.get_migr_rate()) == 1.); + } + + // Error handling. + BOOST_CHECK_EXCEPTION(b0 = bsrp(-1.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_EXCEPTION(b0 = bsrp(2.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_EXCEPTION( + b0 = bsrp(std::numeric_limits::infinity()), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_THROW(b0 = bsrp(-1), boost::numeric::negative_overflow); +} diff --git a/tests/bfe.cpp b/tests/bfe.cpp index 333897dcd..2f9f1bd64 100644 --- a/tests/bfe.cpp +++ b/tests/bfe.cpp @@ -42,6 +42,7 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include diff --git a/tests/fair_replace.cpp b/tests/fair_replace.cpp new file mode 100644 index 000000000..2160cd111 --- /dev/null +++ b/tests/fair_replace.cpp @@ -0,0 +1,228 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE fair_replace_test +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; + +BOOST_AUTO_TEST_CASE(fair_replace_basic) +{ + fair_replace f00; + BOOST_CHECK(f00.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f00.get_migr_rate()) == 1u); + + fair_replace f01(.2); + BOOST_CHECK(f01.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(f01.get_migr_rate()) == .2); + + fair_replace f02(2); + BOOST_CHECK(f02.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f02.get_migr_rate()) == 2u); + + BOOST_CHECK_EXCEPTION(f02 = fair_replace(-1.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_EXCEPTION(f02 = fair_replace(2.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_EXCEPTION( + f02 = fair_replace(std::numeric_limits::infinity()), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_THROW(f02 = fair_replace(-1), boost::numeric::negative_overflow); + + auto f03(f02); + BOOST_CHECK(f03.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f03.get_migr_rate()) == 2u); + + auto f04(std::move(f01)); + BOOST_CHECK(f04.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(f04.get_migr_rate()) == .2); + + f03 = f04; + BOOST_CHECK(f03.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(f03.get_migr_rate()) == .2); + + f04 = std::move(f02); + BOOST_CHECK(f04.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f04.get_migr_rate()) == 2u); + + BOOST_CHECK(f04.get_name() == "Fair replace"); + BOOST_CHECK(boost::contains(f04.get_extra_info(), "Absolute migration rate:")); + BOOST_CHECK(boost::contains(f03.get_extra_info(), "Fractional migration rate:")); + + // Minimal serialization test. + { + r_policy r0(f04); + + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << r0; + } + r_policy r1; + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> r1; + } + BOOST_CHECK(r1.is()); + BOOST_CHECK(r1.extract()->get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(r1.extract()->get_migr_rate()) == 2u); + } +} + +BOOST_AUTO_TEST_CASE(fair_replace_replace) +{ + fair_replace f00; + + BOOST_CHECK_EXCEPTION(f00.replace(individuals_group_t{}, 0, 0, 2, 1, 0, vector_double{}, individuals_group_t{}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "The 'fair_replace' replacement policy is unable to deal with " + "multiobjective constrained optimisation problems"); + }); + + f00 = fair_replace(100); + + BOOST_CHECK_EXCEPTION(f00.replace(individuals_group_t{}, 0, 0, 1, 0, 0, vector_double{}, individuals_group_t{}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "The absolute migration rate (100) in a 'fair_replace' replacement policy " + "is larger than the number of input individuals (0)"); + }); + + // Single-objective, unconstrained. + f00 = fair_replace(.1); + + individuals_group_t inds{{1, 2, 3}, {{0}, {0}, {0}}, {{1}, {2}, {3}}}; + individuals_group_t mig{{4, 5, 6}, {{0}, {0}, {0}}, {{0.1}, {0.2}, {0.3}}}; + + // Too few individuals in inds for fractional migration, no migration will happen. + auto new_inds = f00.replace(inds, 1, 0, 1, 0, 0, {}, mig); + BOOST_CHECK(new_inds == inds); + + // Top mig replaces the worst in inds. + f00 = fair_replace(0.5); + new_inds = f00.replace(inds, 1, 0, 1, 0, 0, {}, mig); + BOOST_CHECK((new_inds == individuals_group_t{{4, 1, 2}, {{0}, {0}, {0}}, {{0.1}, {1}, {2}}})); + + // All replaced. + f00 = fair_replace(1.); + new_inds = f00.replace(inds, 1, 0, 1, 0, 0, {}, mig); + BOOST_CHECK((new_inds == individuals_group_t{{4, 5, 6}, {{0}, {0}, {0}}, {{0.1}, {0.2}, {0.3}}})); + + // Absolute rate, no replacement. + f00 = fair_replace(0); + new_inds = f00.replace(inds, 1, 0, 1, 0, 0, {}, mig); + BOOST_CHECK(new_inds == inds); + + // Absolute rate, replace 2. + f00 = fair_replace(2); + new_inds = f00.replace(inds, 1, 0, 1, 0, 0, {}, mig); + BOOST_CHECK((new_inds == individuals_group_t{{4, 5, 1}, {{0}, {0}, {0}}, {{0.1}, {0.2}, {1}}})); + + // Absolute rate, replace all. + f00 = fair_replace(3); + new_inds = f00.replace(inds, 1, 0, 1, 0, 0, {}, mig); + BOOST_CHECK((new_inds == individuals_group_t{{4, 5, 6}, {{0}, {0}, {0}}, {{0.1}, {0.2}, {0.3}}})); + + // Single-objective, constrained. + inds = individuals_group_t{{1, 2, 3}, {{0}, {0}, {0}}, {{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}}; + mig = individuals_group_t{{4, 5, 6}, {{0}, {0}, {0}}, {{0.1, 0.1, 0.1}, {0.2, 0.2, 0.2}, {0.3, 0.3, 0.3}}}; + f00 = fair_replace(.1); + + // Too few individuals in inds for fractional migration, no migration will happen. + new_inds = f00.replace(inds, 1, 0, 1, 1, 1, {0., 0.}, mig); + BOOST_CHECK(new_inds == inds); + + // Top mig replaces the worst in inds. + f00 = fair_replace(0.5); + new_inds = f00.replace(inds, 1, 0, 1, 1, 1, {0., 0.}, mig); + BOOST_CHECK((new_inds == individuals_group_t{{4, 1, 2}, {{0}, {0}, {0}}, {{0.1, 0.1, 0.1}, {1, 1, 1}, {2, 2, 2}}})); + + // All replaced. + f00 = fair_replace(1.); + new_inds = f00.replace(inds, 1, 0, 1, 1, 1, {0., 0.}, mig); + BOOST_CHECK( + (new_inds + == individuals_group_t{{4, 5, 6}, {{0}, {0}, {0}}, {{0.1, 0.1, 0.1}, {0.2, 0.2, 0.2}, {0.3, 0.3, 0.3}}})); + + // Absolute rate, no replacement. + f00 = fair_replace(0); + new_inds = f00.replace(inds, 1, 0, 1, 1, 1, {0., 0.}, mig); + BOOST_CHECK(new_inds == inds); + + // Absolute rate, replace 2. + f00 = fair_replace(2); + new_inds = f00.replace(inds, 1, 0, 1, 1, 1, {0., 0.}, mig); + BOOST_CHECK( + (new_inds == individuals_group_t{{4, 5, 1}, {{0}, {0}, {0}}, {{0.1, 0.1, 0.1}, {0.2, 0.2, 0.2}, {1, 1, 1}}})); + + // Absolute rate, replace all. + f00 = fair_replace(3); + new_inds = f00.replace(inds, 1, 0, 1, 1, 1, {0., 0.}, mig); + BOOST_CHECK( + (new_inds + == individuals_group_t{{4, 5, 6}, {{0}, {0}, {0}}, {{0.1, 0.1, 0.1}, {0.2, 0.2, 0.2}, {0.3, 0.3, 0.3}}})); + + // Multi-objective, unconstrained. + // NOTE: these values are taken from a test in multi_objective.cpp. + inds = individuals_group_t{{1, 2, 3, 4, 5}, {{0}, {0}, {0}, {0}, {0}}, {{0, 7}, {1, 5}, {2, 3}, {4, 2}, {7, 1}}}; + mig = individuals_group_t{ + {6, 7, 8, 9, 10, 11}, {{0}, {0}, {0}, {0}, {0}, {0}}, {{10, 0}, {2, 6}, {4, 4}, {10, 2}, {6, 6}, {9, 5}}}; + f00 = fair_replace(1.); + + new_inds = f00.replace(inds, 1, 0, 2, 0, 0, {}, mig); + BOOST_CHECK(( + new_inds + == individuals_group_t{{1, 6, 5, 4, 2}, {{0}, {0}, {0}, {0}, {0}}, {{0, 7}, {10, 0}, {7, 1}, {4, 2}, {1, 5}}})); +} diff --git a/tests/fully_connected.cpp b/tests/fully_connected.cpp new file mode 100644 index 000000000..483be2757 --- /dev/null +++ b/tests/fully_connected.cpp @@ -0,0 +1,208 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE fully_connected +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace pagmo; + +void verify_fully_connected_topology(const fully_connected &f) +{ + const auto s = f.num_vertices(); + + if (!s) { + BOOST_CHECK_EXCEPTION(f.get_connections(0), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), "Cannot get the connections to the vertex at index 0 in a fully " + "connected topology: the number of vertices in the topology is only 0"); + }); + + return; + } + + if (s == 1u) { + BOOST_CHECK(f.get_connections(0).first.empty()); + BOOST_CHECK(f.get_connections(0).second.empty()); + + BOOST_CHECK_EXCEPTION(f.get_connections(1), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), "Cannot get the connections to the vertex at index 1 in a fully " + "connected topology: the number of vertices in the topology is only 1"); + }); + + return; + } + + const auto w = f.get_weight(); + + for (std::size_t i = 0; i < s; ++i) { + const auto conns = f.get_connections(i); + + BOOST_CHECK(conns.first.size() == s - 1u); + BOOST_CHECK(conns.second.size() == s - 1u); + + BOOST_CHECK(std::all_of(conns.second.begin(), conns.second.end(), [w](double x) { return x == w; })); + + for (std::size_t j = 0; j < i; ++j) { + BOOST_CHECK(std::find(conns.first.begin(), conns.first.end(), j) != conns.first.end()); + } + for (std::size_t j = i + 1u; j < s; ++j) { + BOOST_CHECK(std::find(conns.first.begin(), conns.first.end(), j) != conns.first.end()); + } + } +} + +BOOST_AUTO_TEST_CASE(basic_test) +{ + { + // Default construct, push back a few times. + fully_connected r0; + BOOST_CHECK(r0.get_weight() == 1); + BOOST_CHECK(r0.num_vertices() == 0u); + verify_fully_connected_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 1u); + verify_fully_connected_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 2u); + verify_fully_connected_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 3u); + verify_fully_connected_topology(r0); + + r0.push_back(); + r0.push_back(); + r0.push_back(); + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 7u); + verify_fully_connected_topology(r0); + } + + { + // Ctor from weight. + fully_connected r0(.2); + BOOST_CHECK(r0.get_weight() == .2); + BOOST_CHECK(r0.num_vertices() == 0u); + verify_fully_connected_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 1u); + verify_fully_connected_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 2u); + verify_fully_connected_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 3u); + verify_fully_connected_topology(r0); + + r0.push_back(); + r0.push_back(); + r0.push_back(); + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 7u); + verify_fully_connected_topology(r0); + } + + { + // Ctor from nedges and weight. + fully_connected r0(0, .2); + BOOST_CHECK(r0.get_weight() == .2); + BOOST_CHECK(r0.num_vertices() == 0u); + verify_fully_connected_topology(r0); + } + + { + // Ctor from nedges and weight. + fully_connected r0(7, .2); + BOOST_CHECK(r0.get_weight() == .2); + BOOST_CHECK(r0.num_vertices() == 7u); + verify_fully_connected_topology(r0); + } + + { + // Copy/move ctors. + fully_connected r0(7, .2), r1(r0), r2(std::move(r0)); + + BOOST_CHECK(r1.get_weight() == .2); + BOOST_CHECK(r1.num_vertices() == 7u); + + BOOST_CHECK(r1.get_weight() == .2); + BOOST_CHECK(r2.num_vertices() == 7u); + + verify_fully_connected_topology(r1); + verify_fully_connected_topology(r2); + } + + { + // Name/extra info. + fully_connected r0(7, .2); + + BOOST_CHECK(r0.get_name() == "Fully connected"); + BOOST_CHECK(boost::contains(r0.get_extra_info(), "Edges' weight:")); + + std::cout << r0.get_extra_info() << '\n'; + } + + // Minimal serialization test. + fully_connected r0(7, .2); + { + topology t0(r0); + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << t0; + } + topology t1; + BOOST_CHECK(!t1.is()); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> t1; + } + BOOST_CHECK(t1.is()); + BOOST_CHECK(t1.extract()->num_vertices() == 7u); + BOOST_CHECK(t1.extract()->get_weight() == .2); + verify_fully_connected_topology(*t1.extract()); + } +} diff --git a/tests/gaco.cpp b/tests/gaco.cpp index b64717075..248dba637 100644 --- a/tests/gaco.cpp +++ b/tests/gaco.cpp @@ -256,8 +256,6 @@ struct udp_inf { { return {{0., 0.}, {1., 1.}}; } - /// Problem dimensions - unsigned m_dim; }; struct udp_nan { @@ -278,8 +276,6 @@ struct udp_nan { { return {{0., 0.}, {1., 1.}}; } - /// Problem dimensions - unsigned m_dim; }; BOOST_AUTO_TEST_CASE(test_for_inf_and_nan) diff --git a/tests/io.cpp b/tests/io.cpp index 4bbaec5f9..dcfaa77ea 100644 --- a/tests/io.cpp +++ b/tests/io.cpp @@ -78,14 +78,28 @@ BOOST_AUTO_TEST_CASE(stream_print_test_00) BOOST_CHECK_EQUAL(ss1.str(), "true false"); ss1.str(""); // Vectors. + // Empty vector. stream(ss1, std::vector{}); BOOST_CHECK_EQUAL(ss1.str(), "[]"); ss1.str(""); + // Single element. + stream(ss1, std::vector{1}); + ss2 << "[" << 1 << "]"; + BOOST_CHECK_EQUAL(ss1.str(), ss2.str()); + ss1.str(""); + ss2.str(""); + // Multiple elements. stream(ss1, std::vector{1, 2, 3}); ss2 << "[" << 1 << ", " << 2 << ", " << 3 << "]"; BOOST_CHECK_EQUAL(ss1.str(), ss2.str()); ss1.str(""); ss2.str(""); + // Vector equal to the print limit. + stream(ss1, std::vector{1, 2, 3, 4, 5}); + ss2 << "[" << 1 << ", " << 2 << ", " << 3 << ", " << 4 << ", " << 5 << "]"; + BOOST_CHECK_EQUAL(ss1.str(), ss2.str()); + ss1.str(""); + ss2.str(""); // Vector larger than the print limit. stream(ss1, std::vector{1, 2, 3, 4, 5, 6}); ss2 << "[" << 1 << ", " << 2 << ", " << 3 << ", " << 4 << ", " << 5 << ", ... ]"; diff --git a/tests/island.cpp b/tests/island.cpp index be97ca81b..13780bd99 100644 --- a/tests/island.cpp +++ b/tests/island.cpp @@ -32,6 +32,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include @@ -54,7 +56,11 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include +#include #include +#include +#include #include #include @@ -93,75 +99,189 @@ BOOST_AUTO_TEST_CASE(island_type_traits) BOOST_CHECK(is_udi::value); } +// Minimal udrp/udsp to test the constructors +// with policies arguments. + +struct udrp00 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return individuals_group_t{}; + } + template + void serialize(Archive &, unsigned) + { + } +}; + +PAGMO_S11N_R_POLICY_EXPORT(udrp00) + +struct udsp00 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return individuals_group_t{}; + } + template + void serialize(Archive &, unsigned) + { + } +}; + +PAGMO_S11N_S_POLICY_EXPORT(udsp00) + BOOST_AUTO_TEST_CASE(island_constructors) { // Various constructors. island isl; BOOST_CHECK(isl.get_algorithm().is()); BOOST_CHECK(isl.get_population().get_problem().is()); + BOOST_CHECK(isl.get_r_policy().is()); + BOOST_CHECK(isl.get_s_policy().is()); BOOST_CHECK(isl.get_population().size() == 0u); - auto isl2 = isl; + auto isl2(isl); BOOST_CHECK(isl2.get_algorithm().is()); BOOST_CHECK(isl2.get_population().get_problem().is()); BOOST_CHECK(isl2.get_population().size() == 0u); + BOOST_CHECK(isl2.get_r_policy().is()); + BOOST_CHECK(isl2.get_s_policy().is()); island isl3{de{}, population{rosenbrock{}, 25}}; BOOST_CHECK(isl3.get_algorithm().is()); BOOST_CHECK(isl3.get_population().get_problem().is()); BOOST_CHECK(isl3.get_population().size() == 25u); - auto isl4 = isl3; + BOOST_CHECK(isl3.get_r_policy().is()); + BOOST_CHECK(isl3.get_s_policy().is()); + auto isl4(isl3); BOOST_CHECK(isl4.get_algorithm().is()); BOOST_CHECK(isl4.get_population().get_problem().is()); BOOST_CHECK(isl4.get_population().size() == 25u); + BOOST_CHECK(isl4.get_r_policy().is()); + BOOST_CHECK(isl4.get_s_policy().is()); + // Ctor from algo, pop, policies. + island isl4a{de{}, population{rosenbrock{}, 25}, udrp00{}, udsp00{}}; + BOOST_CHECK(isl4a.get_algorithm().is()); + BOOST_CHECK(isl4a.get_population().get_problem().is()); + BOOST_CHECK(isl4a.get_population().size() == 25u); + BOOST_CHECK(isl4a.get_r_policy().is()); + BOOST_CHECK(isl4a.get_s_policy().is()); + auto isl4b(isl4a); + BOOST_CHECK(isl4b.get_algorithm().is()); + BOOST_CHECK(isl4b.get_population().get_problem().is()); + BOOST_CHECK(isl4b.get_population().size() == 25u); + BOOST_CHECK(isl4b.get_r_policy().is()); + BOOST_CHECK(isl4b.get_s_policy().is()); + // Ctor from UDI, algo, pop. island isl5{thread_island{}, de{}, population{rosenbrock{}, 26}}; BOOST_CHECK(isl5.get_algorithm().is()); BOOST_CHECK(isl5.get_population().get_problem().is()); BOOST_CHECK(isl5.get_population().size() == 26u); + BOOST_CHECK(isl5.get_r_policy().is()); + BOOST_CHECK(isl5.get_s_policy().is()); + // Ctor from UDI, algo, pop and policies. + island isl5a{thread_island{}, de{}, population{rosenbrock{}, 26}, udrp00{}, udsp00{}}; + BOOST_CHECK(isl5a.get_algorithm().is()); + BOOST_CHECK(isl5a.get_population().get_problem().is()); + BOOST_CHECK(isl5a.get_population().size() == 26u); + BOOST_CHECK(isl5a.get_r_policy().is()); + BOOST_CHECK(isl5a.get_s_policy().is()); + // Ctor form algo, prob, size and seed. island isl6{de{}, rosenbrock{}, 27}; BOOST_CHECK(isl6.get_algorithm().is()); BOOST_CHECK(isl6.get_population().get_problem().is()); BOOST_CHECK(isl6.get_population().size() == 27u); + BOOST_CHECK(isl6.get_r_policy().is()); + BOOST_CHECK(isl6.get_s_policy().is()); island isl7{de{}, rosenbrock{}, 27, 123}; BOOST_CHECK(isl7.get_algorithm().is()); BOOST_CHECK(isl7.get_population().get_problem().is()); BOOST_CHECK(isl7.get_population().size() == 27u); BOOST_CHECK(isl7.get_population().get_seed() == 123u); + BOOST_CHECK(isl7.get_r_policy().is()); + BOOST_CHECK(isl7.get_s_policy().is()); + // Ctor form algo, prob, size, policies and seed. + island isl6a{de{}, rosenbrock{}, 27, udrp00{}, udsp00{}}; + BOOST_CHECK(isl6a.get_algorithm().is()); + BOOST_CHECK(isl6a.get_population().get_problem().is()); + BOOST_CHECK(isl6a.get_population().size() == 27u); + BOOST_CHECK(isl6a.get_r_policy().is()); + BOOST_CHECK(isl6a.get_s_policy().is()); + island isl7a{de{}, rosenbrock{}, 27, udrp00{}, udsp00{}, 123}; + BOOST_CHECK(isl7a.get_algorithm().is()); + BOOST_CHECK(isl7a.get_population().get_problem().is()); + BOOST_CHECK(isl7a.get_population().size() == 27u); + BOOST_CHECK(isl7a.get_population().get_seed() == 123u); + BOOST_CHECK(isl7a.get_r_policy().is()); + BOOST_CHECK(isl7a.get_s_policy().is()); + // Ctor from UDI, algo, prob and size. island isl8{thread_island{}, de{}, rosenbrock{}, 28}; BOOST_CHECK(isl8.get_algorithm().is()); BOOST_CHECK(isl8.get_population().get_problem().is()); BOOST_CHECK(isl8.get_population().size() == 28u); + BOOST_CHECK(isl8.get_r_policy().is()); + BOOST_CHECK(isl8.get_s_policy().is()); island isl9{thread_island{}, de{}, rosenbrock{}, 29, 124}; BOOST_CHECK(isl9.get_algorithm().is()); BOOST_CHECK(isl9.get_population().get_problem().is()); BOOST_CHECK(isl9.get_population().size() == 29u); BOOST_CHECK(isl9.get_population().get_seed() == 124u); - island isl10{std::move(isl9)}; + BOOST_CHECK(isl9.get_r_policy().is()); + BOOST_CHECK(isl9.get_s_policy().is()); + // Ctor from UDI, algo, prob, size and policies. + island isl8a{thread_island{}, de{}, rosenbrock{}, 28, udrp00{}, udsp00{}}; + BOOST_CHECK(isl8a.get_algorithm().is()); + BOOST_CHECK(isl8a.get_population().get_problem().is()); + BOOST_CHECK(isl8a.get_population().size() == 28u); + BOOST_CHECK(isl8a.get_r_policy().is()); + BOOST_CHECK(isl8a.get_s_policy().is()); + island isl9a{thread_island{}, de{}, rosenbrock{}, 29, udrp00{}, udsp00{}, 124}; + BOOST_CHECK(isl9a.get_algorithm().is()); + BOOST_CHECK(isl9a.get_population().get_problem().is()); + BOOST_CHECK(isl9a.get_population().size() == 29u); + BOOST_CHECK(isl9a.get_population().get_seed() == 124u); + BOOST_CHECK(isl9a.get_r_policy().is()); + BOOST_CHECK(isl9a.get_s_policy().is()); + island isl10{std::move(isl9a)}; BOOST_CHECK(isl10.get_algorithm().is()); BOOST_CHECK(isl10.get_population().get_problem().is()); BOOST_CHECK(isl10.get_population().size() == 29u); BOOST_CHECK(isl10.get_population().get_seed() == 124u); - // Revive isl9; - isl9 = island{thread_island{}, de{}, rosenbrock{}, 29, 124}; - BOOST_CHECK(isl9.get_algorithm().is()); - BOOST_CHECK(isl9.get_population().get_problem().is()); - BOOST_CHECK(isl9.get_population().size() == 29u); - BOOST_CHECK(isl9.get_population().get_seed() == 124u); + BOOST_CHECK(isl10.get_r_policy().is()); + BOOST_CHECK(isl10.get_s_policy().is()); + // Revive isl9a; + isl9a = island{thread_island{}, de{}, rosenbrock{}, 29, 124}; + BOOST_CHECK(isl9a.get_algorithm().is()); + BOOST_CHECK(isl9a.get_population().get_problem().is()); + BOOST_CHECK(isl9a.get_population().size() == 29u); + BOOST_CHECK(isl9a.get_population().get_seed() == 124u); + BOOST_CHECK(isl9a.get_r_policy().is()); + BOOST_CHECK(isl9a.get_s_policy().is()); // Copy assignment. - isl9 = isl8; - BOOST_CHECK(isl9.get_algorithm().is()); - BOOST_CHECK(isl9.get_population().get_problem().is()); - BOOST_CHECK(isl9.get_population().size() == 28u); + isl9a = isl8a; + BOOST_CHECK(isl9a.get_algorithm().is()); + BOOST_CHECK(isl9a.get_population().get_problem().is()); + BOOST_CHECK(isl9a.get_population().size() == 28u); + BOOST_CHECK(isl9a.get_r_policy().is()); + BOOST_CHECK(isl9a.get_s_policy().is()); // Self assignment. - BOOST_CHECK((std::is_same::value)); - isl9 = *&isl9; - BOOST_CHECK(isl9.get_algorithm().is()); - BOOST_CHECK(isl9.get_population().get_problem().is()); - BOOST_CHECK(isl9.get_population().size() == 28u); + BOOST_CHECK((std::is_same::value)); + isl9a = *&isl9a; + BOOST_CHECK(isl9a.get_algorithm().is()); + BOOST_CHECK(isl9a.get_population().get_problem().is()); + BOOST_CHECK(isl9a.get_population().size() == 28u); + BOOST_CHECK(isl9a.get_r_policy().is()); + BOOST_CHECK(isl9a.get_s_policy().is()); #if !defined(__clang__) - BOOST_CHECK((std::is_same::value)); - isl9 = std::move(isl9); - BOOST_CHECK(isl9.get_algorithm().is()); - BOOST_CHECK(isl9.get_population().get_problem().is()); - BOOST_CHECK(isl9.get_population().size() == 28u); + BOOST_CHECK((std::is_same::value)); + isl9a = std::move(isl9a); + BOOST_CHECK(isl9a.get_algorithm().is()); + BOOST_CHECK(isl9a.get_population().get_problem().is()); + BOOST_CHECK(isl9a.get_population().size() == 28u); + BOOST_CHECK(isl9a.get_r_policy().is()); + BOOST_CHECK(isl9a.get_s_policy().is()); #endif // Some type-traits. BOOST_CHECK((std::is_constructible::value)); @@ -270,11 +390,14 @@ BOOST_AUTO_TEST_CASE(island_name_info_stream) BOOST_CHECK(!oss.str().empty()); BOOST_CHECK(isl.get_name() == "udi_01"); BOOST_CHECK(isl.get_extra_info() == "extra bits"); + BOOST_CHECK(boost::contains(oss.str(), "Replacement policy: Fair replace")); + BOOST_CHECK(boost::contains(oss.str(), "Selection policy: Select best")); + std::cout << isl << '\n'; } BOOST_AUTO_TEST_CASE(island_serialization) { - island isl{de{}, population{rosenbrock{}, 25}}; + island isl{de{}, population{rosenbrock{}, 25}, udrp00{}, udsp00{}}; isl.evolve(); isl.wait_check(); std::stringstream ss; @@ -404,6 +527,8 @@ BOOST_AUTO_TEST_CASE(island_bfe_ctors) BOOST_CHECK(isl00.get_algorithm().is()); BOOST_CHECK(isl00.get_population().get_problem().is()); BOOST_CHECK(isl00.get_population().size() == 1000u); + BOOST_CHECK(isl00.get_r_policy().is()); + BOOST_CHECK(isl00.get_s_policy().is()); auto pop = isl00.get_population(); BOOST_CHECK(pop.get_problem().get_fevals() == 1000u); for (auto i = 0u; i < 1000u; ++i) { @@ -415,6 +540,21 @@ BOOST_AUTO_TEST_CASE(island_bfe_ctors) BOOST_CHECK(isl00.get_algorithm().is()); BOOST_CHECK(isl00.get_population().get_problem().is()); BOOST_CHECK(isl00.get_population().size() == 1000u); + BOOST_CHECK(isl00.get_r_policy().is()); + BOOST_CHECK(isl00.get_s_policy().is()); + pop = isl00.get_population(); + BOOST_CHECK(pop.get_problem().get_fevals() == 1000u); + for (auto i = 0u; i < 1000u; ++i) { + BOOST_CHECK(pop.get_f()[i] == pop.get_problem().fitness(pop.get_x()[i])); + } + + // With policies. + isl00 = island{stateful_algo{}, rosenbrock{10}, thread_bfe{}, 1000, udrp00{}, udsp00{}}; + BOOST_CHECK(isl00.get_algorithm().is()); + BOOST_CHECK(isl00.get_population().get_problem().is()); + BOOST_CHECK(isl00.get_population().size() == 1000u); + BOOST_CHECK(isl00.get_r_policy().is()); + BOOST_CHECK(isl00.get_s_policy().is()); pop = isl00.get_population(); BOOST_CHECK(pop.get_problem().get_fevals() == 1000u); for (auto i = 0u; i < 1000u; ++i) { @@ -425,6 +565,8 @@ BOOST_AUTO_TEST_CASE(island_bfe_ctors) BOOST_CHECK(isl00.get_algorithm().is()); BOOST_CHECK(isl00.get_population().get_problem().is()); BOOST_CHECK(isl00.get_population().size() == 1000u); + BOOST_CHECK(isl00.get_r_policy().is()); + BOOST_CHECK(isl00.get_s_policy().is()); pop = isl00.get_population(); BOOST_CHECK(pop.get_problem().get_fevals() == 1000u); for (auto i = 0u; i < 1000u; ++i) { @@ -436,6 +578,21 @@ BOOST_AUTO_TEST_CASE(island_bfe_ctors) BOOST_CHECK(isl00.get_algorithm().is()); BOOST_CHECK(isl00.get_population().get_problem().is()); BOOST_CHECK(isl00.get_population().size() == 1000u); + BOOST_CHECK(isl00.get_r_policy().is()); + BOOST_CHECK(isl00.get_s_policy().is()); + pop = isl00.get_population(); + BOOST_CHECK(pop.get_problem().get_fevals() == 1000u); + for (auto i = 0u; i < 1000u; ++i) { + BOOST_CHECK(pop.get_f()[i] == pop.get_problem().fitness(pop.get_x()[i])); + } + + // With policies. + isl00 = island{thread_island{}, stateful_algo{}, rosenbrock{10}, thread_bfe{}, 1000, udrp00{}, udsp00{}}; + BOOST_CHECK(isl00.get_algorithm().is()); + BOOST_CHECK(isl00.get_population().get_problem().is()); + BOOST_CHECK(isl00.get_population().size() == 1000u); + BOOST_CHECK(isl00.get_r_policy().is()); + BOOST_CHECK(isl00.get_s_policy().is()); pop = isl00.get_population(); BOOST_CHECK(pop.get_problem().get_fevals() == 1000u); for (auto i = 0u; i < 1000u; ++i) { diff --git a/tests/migration_torture_test.cpp b/tests/migration_torture_test.cpp new file mode 100644 index 000000000..3f35c7d84 --- /dev/null +++ b/tests/migration_torture_test.cpp @@ -0,0 +1,88 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE migration_torture_test +#define BOOST_TEST_DYN_LINK +#include + +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; + +// A test to stress the migration machinery: +// do only 1 generation per evolve, 100 individuals +// per island, many evolves. +BOOST_AUTO_TEST_CASE(migration_torture_00) +{ + for (auto mt : {migration_type::p2p, migration_type::broadcast}) { + for (auto mh : {migrant_handling::preserve, migrant_handling::evict}) { + archipelago archi{ring{.8}, 20, de{1}, rosenbrock{100}, 100u}; + + archi.set_migration_type(mt); + archi.set_migrant_handling(mh); + + archi.evolve(500); + + // Add a few islands while evolving. + for (auto i = 0; i < 20; ++i) { + archi.push_back(de{1}, rosenbrock{100}, 100u); + } + + for (auto i = 0; i < 20; ++i) { + // Get out a few archi members while evolving. + (void)archi.get_topology(); + (void)archi.get_migration_log(); + const auto mig_db = archi.get_migrants_db(); + BOOST_CHECK(mig_db.size() == 40u); + for (const auto &mig_g : mig_db) { + BOOST_CHECK(std::get<0>(mig_g).size() == std::get<1>(mig_g).size()); + BOOST_CHECK(std::get<0>(mig_g).size() == std::get<2>(mig_g).size()); + for (decltype(std::get<0>(mig_g).size()) j = 0; j < std::get<0>(mig_g).size(); ++j) { + BOOST_CHECK(std::get<1>(mig_g)[j].size() == 100u); + BOOST_CHECK(std::get<2>(mig_g)[j].size() == 1u); + } + } + } + + BOOST_CHECK_NO_THROW(archi.wait_check()); + + for (const auto &t : archi.get_migration_log()) { + // Check timestamp. + BOOST_CHECK(std::get<0>(t) >= 0.); + // Check that source and destination islands are different. + BOOST_CHECK(std::get<4>(t) != std::get<5>(t)); + } + } + } +} diff --git a/tests/r_policy.cpp b/tests/r_policy.cpp new file mode 100644 index 000000000..a9ca2f4a1 --- /dev/null +++ b/tests/r_policy.cpp @@ -0,0 +1,588 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#if defined(_MSC_VER) + +// Disable warnings from MSVC. +#pragma warning(disable : 4822) + +#endif + +#define BOOST_TEST_MODULE r_policy_test +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; + +BOOST_AUTO_TEST_CASE(type_traits_tests) +{ + BOOST_CHECK(!is_udrp::value); + BOOST_CHECK(!is_udrp::value); + BOOST_CHECK(!is_udrp::value); + + struct udrp00 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + }; + + BOOST_CHECK(is_udrp::value); + BOOST_CHECK(!is_udrp::value); + BOOST_CHECK(!is_udrp::value); + BOOST_CHECK(!is_udrp::value); + + struct no_udrp00 { + void replace(const individuals_group_t &, const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double &, const individuals_group_t &) const; + }; + + BOOST_CHECK(!is_udrp::value); + + struct no_udrp01 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &); + }; + + BOOST_CHECK(!is_udrp::value); + + struct no_udrp02 { + no_udrp02() = delete; + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const; + }; + + BOOST_CHECK(!is_udrp::value); +} + +struct udrp1 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + std::string foo = "hello world"; +}; + +struct udrp2 { + udrp2() = default; + udrp2(const udrp2 &other) : foo{new std::string{*other.foo}} {} + udrp2(udrp2 &&) = default; + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + std::string get_name() const + { + return "frobniz"; + } + std::unique_ptr foo = std::unique_ptr{new std::string{"hello world"}}; +}; + +BOOST_AUTO_TEST_CASE(basic_tests) +{ + r_policy r; + + BOOST_CHECK(r.is()); + BOOST_CHECK(!r.is()); + + BOOST_CHECK(r.extract() != nullptr); + BOOST_CHECK(r.extract() == nullptr); + + BOOST_CHECK(static_cast(r).extract() != nullptr); + BOOST_CHECK(static_cast(r).extract() == nullptr); + + BOOST_CHECK(r.get_name() == "Fair replace"); + BOOST_CHECK(!r.get_extra_info().empty()); + + BOOST_CHECK(r_policy(udrp1{}).get_extra_info().empty()); + BOOST_CHECK(!r_policy(udrp1{}).get_name().empty()); + + // Constructors, assignments. + // Generic constructor with copy. + udrp1 r1; + r_policy r_pol1{r1}; + BOOST_CHECK(r1.foo == "hello world"); + BOOST_CHECK(r_pol1.extract()->foo == "hello world"); + // Generic constructor with move. + udrp2 r2; + r_policy r_pol2{std::move(r2)}; + BOOST_CHECK(r2.foo.get() == nullptr); + BOOST_CHECK(r_pol2.extract()->foo.get() != nullptr); + BOOST_CHECK(*r_pol2.extract()->foo == "hello world"); + // Copy constructor. + udrp2 r3; + r_policy r_pol3{r3}, r_pol4{r_pol3}; + BOOST_CHECK(*r_pol4.extract()->foo == "hello world"); + BOOST_CHECK(r_pol4.extract()->foo.get() != r_pol3.extract()->foo.get()); + BOOST_CHECK(r_pol4.get_name() == "frobniz"); + // Move constructor. + r_policy r_pol5{std::move(r_pol4)}; + BOOST_CHECK(*r_pol5.extract()->foo == "hello world"); + BOOST_CHECK(r_pol5.get_name() == "frobniz"); + // Revive r_pol4 via copy assignment. + r_pol4 = r_pol5; + BOOST_CHECK(*r_pol4.extract()->foo == "hello world"); + BOOST_CHECK(r_pol4.get_name() == "frobniz"); + // Revive r_pol4 via move assignment. + r_policy r_pol6{std::move(r_pol4)}; + r_pol4 = std::move(r_pol5); + BOOST_CHECK(*r_pol4.extract()->foo == "hello world"); + BOOST_CHECK(r_pol4.get_name() == "frobniz"); + // Self move-assignment. + r_pol4 = std::move(*&r_pol4); + BOOST_CHECK(*r_pol4.extract()->foo == "hello world"); + BOOST_CHECK(r_pol4.get_name() == "frobniz"); + + // Minimal iostream test. + { + std::ostringstream oss; + oss << r; + BOOST_CHECK(!oss.str().empty()); + } + + // Minimal serialization test. + { + std::string before; + std::stringstream ss; + { + before = boost::lexical_cast(r); + boost::archive::binary_oarchive oarchive(ss); + oarchive << r; + } + r = r_policy{udrp1{}}; + BOOST_CHECK(r.is()); + BOOST_CHECK(before != boost::lexical_cast(r)); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> r; + } + BOOST_CHECK(before == boost::lexical_cast(r)); + BOOST_CHECK(r.is()); + } +} + +BOOST_AUTO_TEST_CASE(optional_tests) +{ + // get_name(). + struct udrp_00 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + std::string get_name() const + { + return "frobniz"; + } + }; + BOOST_CHECK_EQUAL(r_policy{udrp_00{}}.get_name(), "frobniz"); + struct udrp_01 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + // Missing const. + std::string get_name() + { + return "frobniz"; + } + }; + BOOST_CHECK(r_policy{udrp_01{}}.get_name() != "frobniz"); + + // get_extra_info(). + struct udrp_02 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + std::string get_extra_info() const + { + return "frobniz"; + } + }; + BOOST_CHECK_EQUAL(r_policy{udrp_02{}}.get_extra_info(), "frobniz"); + struct udrp_03 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + // Missing const. + std::string get_extra_info() + { + return "frobniz"; + } + }; + BOOST_CHECK(r_policy{udrp_03{}}.get_extra_info().empty()); +} + +BOOST_AUTO_TEST_CASE(stream_operator) +{ + struct udrp_00 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + }; + { + std::ostringstream oss; + oss << r_policy{udrp_00{}}; + BOOST_CHECK(!oss.str().empty()); + } + struct udrp_01 { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + std::string get_extra_info() const + { + return "bartoppo"; + } + }; + { + std::ostringstream oss; + oss << r_policy{udrp_01{}}; + const auto st = oss.str(); + BOOST_CHECK(boost::contains(st, "bartoppo")); + BOOST_CHECK(boost::contains(st, "Extra info:")); + } +} + +BOOST_AUTO_TEST_CASE(replace) +{ + r_policy r0; + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0}, {}, {}}, 0, 0, 0, 0, 0, {}, individuals_group_t{}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "an invalid group of individuals was passed to a replacement policy of type 'Fair " + "replace': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are 1, 0 and 0"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 0, 0, 0, 0, 0, {}, + individuals_group_t{{0}, {{1.}, {1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "an invalid group of migrants was passed to a replacement policy of type 'Fair " + "replace': the sets of migrants IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are 1, 2 and 1"); + }); + + BOOST_CHECK_EXCEPTION( + r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 0, 0, 0, 0, 0, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "a problem dimension of zero was passed to a replacement policy of type 'Fair replace'"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 2, 0, 0, 0, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "the integer dimension (2) passed to a replacement policy of type " + "'Fair replace' is larger than the supplied problem dimension (1)"); + }); + + BOOST_CHECK_EXCEPTION( + r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 0, 0, 0, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "an invalid number of objectives (0) was passed to a replacement policy of type 'Fair replace'"); + }); + + BOOST_CHECK_EXCEPTION( + r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, std::numeric_limits::max(), + 0, 0, {}, individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "the number of objectives (" + + std::to_string(std::numeric_limits::max()) + + ") passed to a replacement policy of type 'Fair replace' is too large"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 1, + std::numeric_limits::max(), 0, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "the number of equality constraints (" + + std::to_string(std::numeric_limits::max()) + + ") passed to a replacement policy of type 'Fair replace' is too large"); + }); + + BOOST_CHECK_EXCEPTION( + r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 1, 0, + std::numeric_limits::max(), {}, individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "the number of inequality constraints (" + + std::to_string(std::numeric_limits::max()) + + ") passed to a replacement policy of type 'Fair replace' is too large"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 1, 1, 1, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "the vector of tolerances passed to a replacement policy of type 'Fair replace' has " + "a dimension (0) which is inconsistent with the total number of constraints (2)"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0, 1}, {{1.}, {}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "not all the individuals passed to a replacement policy of type 'Fair " + "replace' have the expected dimension (1)"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {}}}, 1, 0, 1, 0, 0, {}, + individuals_group_t{{0}, {{1.}}, {{1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "not all the individuals passed to a replacement policy of type 'Fair " + "replace' have the expected fitness dimension (1)"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}, + individuals_group_t{{0, 1}, {{1.}, {}}, {{1.}, {1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "not all the migrants passed to a replacement policy of type " + "'Fair replace' have the expected dimension (1)"); + }); + + BOOST_CHECK_EXCEPTION(r0.replace(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}, + individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "not all the migrants passed to a replacement policy of type " + "'Fair replace' have the expected fitness dimension (1)"); + }); + + struct fail_0 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return individuals_group_t{{0}, {}, {}}; + } + std::string get_name() const + { + return "fail_0"; + } + }; + + BOOST_CHECK_EXCEPTION(r_policy{fail_0{}}.replace(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, + 0, 0, {}, individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "an invalid group of individuals was returned by a replacement policy of type " + "'fail_0': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are 1, 0 and 0"); + }); + + struct fail_1 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return individuals_group_t{{0, 1}, {{1}, {}}, {{1}, {1}}}; + } + std::string get_name() const + { + return "fail_1"; + } + }; + + BOOST_CHECK_EXCEPTION(r_policy{fail_1{}}.replace(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, + 0, 0, {}, individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "not all the individuals returned by a replacement " + "policy of type 'fail_1' have the expected dimension (1)"); + }); + + struct fail_2 { + individuals_group_t replace(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return individuals_group_t{{0, 1}, {{1}, {1}}, {{1}, {}}}; + } + std::string get_name() const + { + return "fail_2"; + } + }; + + BOOST_CHECK_EXCEPTION(r_policy{fail_2{}}.replace(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, + 0, 0, {}, individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "not all the individuals returned by a replacement policy of type " + "'fail_2' have the expected fitness dimension (1)"); + }); +} + +struct udrp_a { + individuals_group_t replace(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &, const individuals_group_t &) const + { + return inds; + } + std::string get_name() const + { + return "abba"; + } + std::string get_extra_info() const + { + return "dabba"; + } + template + void serialize(Archive &ar, unsigned) + { + ar &state; + } + int state = 42; +}; + +PAGMO_S11N_R_POLICY_EXPORT(udrp_a) + +// Serialization tests. +BOOST_AUTO_TEST_CASE(s11n) +{ + r_policy r_pol0{udrp_a{}}; + BOOST_CHECK(r_pol0.extract()->state == 42); + r_pol0.extract()->state = -42; + // Store the string representation. + std::stringstream ss; + auto before = boost::lexical_cast(r_pol0); + // Now serialize, deserialize and compare the result. + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << r_pol0; + } + // Change the content of p before deserializing. + r_pol0 = r_policy{}; + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> r_pol0; + } + auto after = boost::lexical_cast(r_pol0); + BOOST_CHECK_EQUAL(before, after); + BOOST_CHECK(r_pol0.is()); + BOOST_CHECK(r_pol0.extract()->state = -42); +} + +BOOST_AUTO_TEST_CASE(is_valid) +{ + r_policy p0; + BOOST_CHECK(p0.is_valid()); + r_policy p1(std::move(p0)); + BOOST_CHECK(!p0.is_valid()); + p0 = r_policy{udrp_a{}}; + BOOST_CHECK(p0.is_valid()); + p1 = std::move(p0); + BOOST_CHECK(!p0.is_valid()); + p0 = r_policy{udrp_a{}}; + BOOST_CHECK(p0.is_valid()); +} + +BOOST_AUTO_TEST_CASE(generic_assignment) +{ + r_policy p0; + BOOST_CHECK(p0.is()); + BOOST_CHECK(&(p0 = udrp_a{}) == &p0); + BOOST_CHECK(p0.is_valid()); + BOOST_CHECK(p0.is()); + p0 = udrp1{}; + BOOST_CHECK(p0.is()); + BOOST_CHECK((!std::is_assignable::value)); + BOOST_CHECK((!std::is_assignable::value)); + BOOST_CHECK((!std::is_assignable::value)); + BOOST_CHECK((!std::is_assignable::value)); +} diff --git a/tests/ring.cpp b/tests/ring.cpp new file mode 100644 index 000000000..69e5f2ad2 --- /dev/null +++ b/tests/ring.cpp @@ -0,0 +1,199 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE ring +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace pagmo; + +void verify_ring_topology(const ring &r) +{ + const auto s = r.num_vertices(); + + if (s < 2u) { + return; + } + + const auto w = r.get_weight(); + + for (std::size_t i = 0; i < s; ++i) { + const auto conn = r.get_connections(i); + if (s > 2u) { + BOOST_CHECK(conn.first.size() == 2u); + BOOST_CHECK(conn.second.size() == 2u); + } else { + BOOST_CHECK(conn.first.size() == 1u); + BOOST_CHECK(conn.second.size() == 1u); + } + BOOST_CHECK(std::all_of(conn.second.begin(), conn.second.end(), [w](double x) { return x == w; })); + + const auto next = (i + 1u) % s; + const auto prev = (i == 0u) ? (s - 1u) : (i - 1u); + BOOST_CHECK(std::find(conn.first.begin(), conn.first.end(), next) != conn.first.end()); + BOOST_CHECK(std::find(conn.first.begin(), conn.first.end(), prev) != conn.first.end()); + } +} + +BOOST_AUTO_TEST_CASE(basic_test) +{ + ring r0; + BOOST_CHECK(r0.get_weight() == 1); + BOOST_CHECK(r0.num_vertices() == 0u); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 1u); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 2u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 3u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 4u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 5u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 6u); + verify_ring_topology(r0); + + r0 = ring(.5); + BOOST_CHECK(r0.get_weight() == .5); + BOOST_CHECK(r0.num_vertices() == 0u); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 1u); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 2u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 3u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 4u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 5u); + verify_ring_topology(r0); + + r0.push_back(); + BOOST_CHECK(r0.num_vertices() == 6u); + verify_ring_topology(r0); + + BOOST_CHECK(r0.get_name() == "Ring"); + + // Minimal serialization test. + { + topology t0(r0); + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << t0; + } + topology t1; + BOOST_CHECK(!t1.is()); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> t1; + } + BOOST_CHECK(t1.is()); + BOOST_CHECK(t1.extract()->num_vertices() == 6u); + BOOST_CHECK(t1.extract()->get_weight() == .5); + verify_ring_topology(*t1.extract()); + } + + // Ctor from edge weight. + BOOST_CHECK_EXCEPTION(r0 = ring(-2), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), " is not in the [0., 1.] range"); + }); + + // Ctor from number of vertices and edge weight. + BOOST_CHECK_EXCEPTION(r0 = ring(0, -2), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), " is not in the [0., 1.] range"); + }); + + r0 = ring(0, 0); + + BOOST_CHECK(r0.get_weight() == 0.); + BOOST_CHECK(r0.num_vertices() == 0u); + verify_ring_topology(r0); + + r0 = ring(1, .2); + BOOST_CHECK(r0.get_weight() == .2); + BOOST_CHECK(r0.num_vertices() == 1u); + verify_ring_topology(r0); + + r0 = ring(2, .3); + BOOST_CHECK(r0.get_weight() == .3); + BOOST_CHECK(r0.num_vertices() == 2u); + verify_ring_topology(r0); + + r0 = ring(3, .4); + BOOST_CHECK(r0.get_weight() == .4); + BOOST_CHECK(r0.num_vertices() == 3u); + verify_ring_topology(r0); + + r0 = ring(4, .5); + BOOST_CHECK(r0.get_weight() == .5); + BOOST_CHECK(r0.num_vertices() == 4u); + verify_ring_topology(r0); + + // Example of cout printing for ring. + r0.push_back(); + r0.push_back(); + r0.push_back(); + r0.push_back(); + + r0.set_weight(0, 1, .1); + r0.set_weight(4, 5, .7); + + std::cout << r0.get_extra_info() << '\n'; +} diff --git a/tests/s_policy.cpp b/tests/s_policy.cpp new file mode 100644 index 000000000..61aeb9d19 --- /dev/null +++ b/tests/s_policy.cpp @@ -0,0 +1,549 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#if defined(_MSC_VER) + +// Disable warnings from MSVC. +#pragma warning(disable : 4822) + +#endif + +#define BOOST_TEST_MODULE s_policy_test +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; + +BOOST_AUTO_TEST_CASE(type_traits_tests) +{ + BOOST_CHECK(!is_udsp::value); + BOOST_CHECK(!is_udsp::value); + BOOST_CHECK(!is_udsp::value); + + struct udsp00 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + }; + + BOOST_CHECK(is_udsp::value); + BOOST_CHECK(!is_udsp::value); + BOOST_CHECK(!is_udsp::value); + BOOST_CHECK(!is_udsp::value); + + struct no_udsp00 { + void select(const individuals_group_t &, const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double &) const; + }; + + BOOST_CHECK(!is_udsp::value); + + struct no_udsp01 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &); + }; + + BOOST_CHECK(!is_udsp::value); + + struct no_udsp02 { + no_udsp02() = delete; + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const; + }; + + BOOST_CHECK(!is_udsp::value); +} + +struct udsp1 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + std::string foo = "hello world"; +}; + +struct udsp2 { + udsp2() = default; + udsp2(const udsp2 &other) : foo{new std::string{*other.foo}} {} + udsp2(udsp2 &&) = default; + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + std::string get_name() const + { + return "frobniz"; + } + std::unique_ptr foo = std::unique_ptr{new std::string{"hello world"}}; +}; + +BOOST_AUTO_TEST_CASE(basic_tests) +{ + s_policy r; + + BOOST_CHECK(r.is()); + BOOST_CHECK(!r.is()); + + BOOST_CHECK(r.extract() != nullptr); + BOOST_CHECK(r.extract() == nullptr); + + BOOST_CHECK(static_cast(r).extract() != nullptr); + BOOST_CHECK(static_cast(r).extract() == nullptr); + + BOOST_CHECK(r.get_name() == "Select best"); + BOOST_CHECK(!r.get_extra_info().empty()); + + BOOST_CHECK(s_policy(udsp1{}).get_extra_info().empty()); + BOOST_CHECK(!s_policy(udsp1{}).get_name().empty()); + + // Constructors, assignments. + // Generic constructor with copy. + udsp1 r1; + s_policy s_pol1{r1}; + BOOST_CHECK(r1.foo == "hello world"); + BOOST_CHECK(s_pol1.extract()->foo == "hello world"); + // Generic constructor with move. + udsp2 r2; + s_policy s_pol2{std::move(r2)}; + BOOST_CHECK(r2.foo.get() == nullptr); + BOOST_CHECK(s_pol2.extract()->foo.get() != nullptr); + BOOST_CHECK(*s_pol2.extract()->foo == "hello world"); + // Copy constructor. + udsp2 r3; + s_policy s_pol3{r3}, s_pol4{s_pol3}; + BOOST_CHECK(*s_pol4.extract()->foo == "hello world"); + BOOST_CHECK(s_pol4.extract()->foo.get() != s_pol3.extract()->foo.get()); + BOOST_CHECK(s_pol4.get_name() == "frobniz"); + // Move constructor. + s_policy s_pol5{std::move(s_pol4)}; + BOOST_CHECK(*s_pol5.extract()->foo == "hello world"); + BOOST_CHECK(s_pol5.get_name() == "frobniz"); + // Revive s_pol4 via copy assignment. + s_pol4 = s_pol5; + BOOST_CHECK(*s_pol4.extract()->foo == "hello world"); + BOOST_CHECK(s_pol4.get_name() == "frobniz"); + // Revive s_pol4 via move assignment. + s_policy s_pol6{std::move(s_pol4)}; + s_pol4 = std::move(s_pol5); + BOOST_CHECK(*s_pol4.extract()->foo == "hello world"); + BOOST_CHECK(s_pol4.get_name() == "frobniz"); + // Self move-assignment. + s_pol4 = std::move(*&s_pol4); + BOOST_CHECK(*s_pol4.extract()->foo == "hello world"); + BOOST_CHECK(s_pol4.get_name() == "frobniz"); + + // Minimal iostream test. + { + std::ostringstream oss; + oss << r; + BOOST_CHECK(!oss.str().empty()); + } + + // Minimal serialization test. + { + std::string before; + std::stringstream ss; + { + before = boost::lexical_cast(r); + boost::archive::binary_oarchive oarchive(ss); + oarchive << r; + } + r = s_policy{udsp1{}}; + BOOST_CHECK(r.is()); + BOOST_CHECK(before != boost::lexical_cast(r)); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> r; + } + BOOST_CHECK(before == boost::lexical_cast(r)); + BOOST_CHECK(r.is()); + } +} + +BOOST_AUTO_TEST_CASE(optional_tests) +{ + // get_name(). + struct udsp_00 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + std::string get_name() const + { + return "frobniz"; + } + }; + BOOST_CHECK_EQUAL(s_policy{udsp_00{}}.get_name(), "frobniz"); + struct udsp_01 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + // Missing const. + std::string get_name() + { + return "frobniz"; + } + }; + BOOST_CHECK(s_policy{udsp_01{}}.get_name() != "frobniz"); + + // get_extra_info(). + struct udsp_02 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + std::string get_extra_info() const + { + return "frobniz"; + } + }; + BOOST_CHECK_EQUAL(s_policy{udsp_02{}}.get_extra_info(), "frobniz"); + struct udsp_03 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + // Missing const. + std::string get_extra_info() + { + return "frobniz"; + } + }; + BOOST_CHECK(s_policy{udsp_03{}}.get_extra_info().empty()); +} + +BOOST_AUTO_TEST_CASE(stream_operator) +{ + struct udsp_00 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + }; + { + std::ostringstream oss; + oss << s_policy{udsp_00{}}; + BOOST_CHECK(!oss.str().empty()); + } + struct udsp_01 { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + std::string get_extra_info() const + { + return "bartoppo"; + } + }; + { + std::ostringstream oss; + oss << s_policy{udsp_01{}}; + const auto st = oss.str(); + BOOST_CHECK(boost::contains(st, "bartoppo")); + BOOST_CHECK(boost::contains(st, "Extra info:")); + } +} + +BOOST_AUTO_TEST_CASE(selection) +{ + s_policy r0; + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {}, {}}, 0, 0, 0, 0, 0, {}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "an invalid group of individuals was passed to a selection policy of type 'Select " + "best': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are 1, 0 and 0"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 0, 0, 0, 0, 0, {}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "a problem dimension of zero was passed to a selection policy of type 'Select best'"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 2, 0, 0, 0, {}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "the integer dimension (2) passed to a selection policy of type " + "'Select best' is larger than the supplied problem dimension (1)"); + }); + + BOOST_CHECK_EXCEPTION( + r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 0, 0, 0, {}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "an invalid number of objectives (0) was passed to a selection policy of type 'Select best'"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, + std::numeric_limits::max(), 0, 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "the number of objectives (" + + std::to_string(std::numeric_limits::max()) + + ") passed to a selection policy of type 'Select best' is too large"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 1, + std::numeric_limits::max(), 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "the number of equality constraints (" + + std::to_string(std::numeric_limits::max()) + + ") passed to a selection policy of type 'Select best' is too large"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 1, 0, + std::numeric_limits::max(), {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "the number of inequality constraints (" + + std::to_string(std::numeric_limits::max()) + + ") passed to a selection policy of type 'Select best' is too large"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0}, {{1.}}, {{1.}}}, 1, 0, 1, 1, 1, {}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "the vector of tolerances passed to a selection policy of type 'Select best' has " + "a dimension (0) which is inconsistent with the total number of constraints (2)"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0, 1}, {{1.}, {}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "not all the individuals passed to a selection policy of type 'Select " + "best' have the expected dimension (1)"); + }); + + BOOST_CHECK_EXCEPTION(r0.select(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {}}}, 1, 0, 1, 0, 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "not all the individuals passed to a selection policy of type 'Select " + "best' have the expected fitness dimension (1)"); + }); + + struct fail_0 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return individuals_group_t{{0}, {}, {}}; + } + std::string get_name() const + { + return "fail_0"; + } + }; + + BOOST_CHECK_EXCEPTION( + s_policy{fail_0{}}.select(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "an invalid group of individuals was returned by a selection policy of type " + "'fail_0': the sets of individuals IDs, decision vectors and fitness vectors " + "must all have the same sizes, but instead their sizes are 1, 0 and 0"); + }); + + struct fail_1 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return individuals_group_t{{0, 1}, {{1}, {}}, {{1}, {1}}}; + } + std::string get_name() const + { + return "fail_1"; + } + }; + + BOOST_CHECK_EXCEPTION( + s_policy{fail_1{}}.select(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), "not all the individuals returned by a selection " + "policy of type 'fail_1' have the expected dimension (1)"); + }); + + struct fail_2 { + individuals_group_t select(const individuals_group_t &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return individuals_group_t{{0, 1}, {{1}, {1}}, {{1}, {}}}; + } + std::string get_name() const + { + return "fail_2"; + } + }; + + BOOST_CHECK_EXCEPTION( + s_policy{fail_2{}}.select(individuals_group_t{{0, 1}, {{1.}, {1.}}, {{1.}, {1.}}}, 1, 0, 1, 0, 0, {}), + std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), "not all the individuals returned by a selection policy of type " + "'fail_2' have the expected fitness dimension (1)"); + }); +} + +struct udsp_a { + individuals_group_t select(const individuals_group_t &inds, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double::size_type &, const vector_double::size_type &, + const vector_double &) const + { + return inds; + } + std::string get_name() const + { + return "abba"; + } + std::string get_extra_info() const + { + return "dabba"; + } + template + void serialize(Archive &ar, unsigned) + { + ar &state; + } + int state = 42; +}; + +PAGMO_S11N_S_POLICY_EXPORT(udsp_a) + +// Serialization tests. +BOOST_AUTO_TEST_CASE(s11n) +{ + s_policy s_pol0{udsp_a{}}; + BOOST_CHECK(s_pol0.extract()->state == 42); + s_pol0.extract()->state = -42; + // Store the string representation. + std::stringstream ss; + auto before = boost::lexical_cast(s_pol0); + // Now serialize, deserialize and compare the result. + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << s_pol0; + } + // Change the content of p before deserializing. + s_pol0 = s_policy{}; + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> s_pol0; + } + auto after = boost::lexical_cast(s_pol0); + BOOST_CHECK_EQUAL(before, after); + BOOST_CHECK(s_pol0.is()); + BOOST_CHECK(s_pol0.extract()->state = -42); +} + +BOOST_AUTO_TEST_CASE(is_valid) +{ + s_policy p0; + BOOST_CHECK(p0.is_valid()); + s_policy p1(std::move(p0)); + BOOST_CHECK(!p0.is_valid()); + p0 = s_policy{udsp_a{}}; + BOOST_CHECK(p0.is_valid()); + p1 = std::move(p0); + BOOST_CHECK(!p0.is_valid()); + p0 = s_policy{udsp_a{}}; + BOOST_CHECK(p0.is_valid()); +} + +BOOST_AUTO_TEST_CASE(generic_assignment) +{ + s_policy p0; + BOOST_CHECK(p0.is()); + BOOST_CHECK(&(p0 = udsp_a{}) == &p0); + BOOST_CHECK(p0.is_valid()); + BOOST_CHECK(p0.is()); + p0 = udsp1{}; + BOOST_CHECK(p0.is()); + BOOST_CHECK((!std::is_assignable::value)); + BOOST_CHECK((!std::is_assignable::value)); + BOOST_CHECK((!std::is_assignable::value)); + BOOST_CHECK((!std::is_assignable::value)); +} diff --git a/tests/select_best.cpp b/tests/select_best.cpp new file mode 100644 index 000000000..340674691 --- /dev/null +++ b/tests/select_best.cpp @@ -0,0 +1,217 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE select_best_test +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace pagmo; + +BOOST_AUTO_TEST_CASE(select_best_basic) +{ + select_best f00; + BOOST_CHECK(f00.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f00.get_migr_rate()) == 1u); + + select_best f01(.2); + BOOST_CHECK(f01.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(f01.get_migr_rate()) == .2); + + select_best f02(2); + BOOST_CHECK(f02.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f02.get_migr_rate()) == 2u); + + BOOST_CHECK_EXCEPTION(f02 = select_best(-1.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_EXCEPTION(f02 = select_best(2.), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_EXCEPTION( + f02 = select_best(std::numeric_limits::infinity()), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "Invalid fractional migration rate specified in the constructor of a replacement/selection " + "policy: the rate must be in the [0., 1.] range, but it is "); + }); + BOOST_CHECK_THROW(f02 = select_best(-1), boost::numeric::negative_overflow); + + auto f03(f02); + BOOST_CHECK(f03.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f03.get_migr_rate()) == 2u); + + auto f04(std::move(f01)); + BOOST_CHECK(f04.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(f04.get_migr_rate()) == .2); + + f03 = f04; + BOOST_CHECK(f03.get_migr_rate().which() == 1); + BOOST_CHECK(boost::get(f03.get_migr_rate()) == .2); + + f04 = std::move(f02); + BOOST_CHECK(f04.get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(f04.get_migr_rate()) == 2u); + + BOOST_CHECK(f04.get_name() == "Select best"); + BOOST_CHECK(boost::contains(f04.get_extra_info(), "Absolute migration rate:")); + BOOST_CHECK(boost::contains(f03.get_extra_info(), "Fractional migration rate:")); + + // Minimal serialization test. + { + s_policy r0(f04); + + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << r0; + } + s_policy r1; + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> r1; + } + BOOST_CHECK(r1.is()); + BOOST_CHECK(r1.extract()->get_migr_rate().which() == 0); + BOOST_CHECK(boost::get(r1.extract()->get_migr_rate()) == 2u); + } +} + +BOOST_AUTO_TEST_CASE(select_best_select) +{ + select_best f00; + + BOOST_CHECK_EXCEPTION(f00.select(individuals_group_t{}, 0, 0, 2, 1, 0, vector_double{}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "The 'Select best' selection policy is unable to deal with " + "multiobjective constrained optimisation problems"); + }); + + f00 = select_best(100); + + BOOST_CHECK_EXCEPTION(f00.select(individuals_group_t{}, 0, 0, 1, 0, 0, vector_double{}), std::invalid_argument, + [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), "The absolute migration rate (100) in a 'Select best' selection policy " + "is larger than the number of input individuals (0)"); + }); + + // Single-objective, unconstrained. + f00 = select_best(.1); + + individuals_group_t inds{{1, 2, 3}, {{0}, {0}, {0}}, {{1}, {2}, {3}}}; + + // Too few individuals in inds for fractional migration, no migration will happen. + auto new_inds = f00.select(inds, 1, 0, 1, 0, 0, {}); + BOOST_CHECK(new_inds == individuals_group_t{}); + + // Select top 1. + f00 = select_best(0.5); + new_inds = f00.select(inds, 1, 0, 1, 0, 0, {}); + BOOST_CHECK((new_inds == individuals_group_t{{1}, {{0}}, {{1}}})); + + // All selected. + f00 = select_best(1.); + new_inds = f00.select(inds, 1, 0, 1, 0, 0, {}); + BOOST_CHECK((new_inds == inds)); + + // Absolute rate, no selection. + f00 = select_best(0); + new_inds = f00.select(inds, 1, 0, 1, 0, 0, {}); + BOOST_CHECK(new_inds == individuals_group_t{}); + + // Absolute rate, select 2. + f00 = select_best(2); + new_inds = f00.select(inds, 1, 0, 1, 0, 0, {}); + BOOST_CHECK((new_inds == individuals_group_t{{1, 2}, {{0}, {0}}, {{1}, {2}}})); + + // Absolute rate, select all. + f00 = select_best(3); + new_inds = f00.select(inds, 1, 0, 1, 0, 0, {}); + BOOST_CHECK((new_inds == inds)); + + // Single-objective, constrained. + inds = individuals_group_t{{1, 2, 3}, {{0}, {0}, {0}}, {{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}}; + f00 = select_best(.1); + + // Too few individuals in inds for fractional migration, no migration will happen. + new_inds = f00.select(inds, 1, 0, 1, 1, 1, {0., 0.}); + BOOST_CHECK(new_inds == individuals_group_t{}); + + // Select top 1. + f00 = select_best(0.5); + new_inds = f00.select(inds, 1, 0, 1, 1, 1, {0., 0.}); + BOOST_CHECK((new_inds == individuals_group_t{{1}, {{0}}, {{1, 1, 1}}})); + + // All selected. + f00 = select_best(1.); + new_inds = f00.select(inds, 1, 0, 1, 1, 1, {0., 0.}); + BOOST_CHECK((new_inds == inds)); + + // Absolute rate, no selection. + f00 = select_best(0); + new_inds = f00.select(inds, 1, 0, 1, 1, 1, {0., 0.}); + BOOST_CHECK(new_inds == individuals_group_t{}); + + // Absolute rate, select 2. + f00 = select_best(2); + new_inds = f00.select(inds, 1, 0, 1, 1, 1, {0., 0.}); + BOOST_CHECK((new_inds == individuals_group_t{{1, 2}, {{0}, {0}}, {{1, 1, 1}, {2, 2, 2}}})); + + // Absolute rate, select all. + f00 = select_best(3); + new_inds = f00.select(inds, 1, 0, 1, 1, 1, {0., 0.}); + BOOST_CHECK((new_inds == inds)); + + // Multi-objective, unconstrained. + // NOTE: these values are taken from a test in multi_objective.cpp. + inds = individuals_group_t{{1, 2, 3, 4, 5}, {{0}, {0}, {0}, {0}, {0}}, {{0, 7}, {1, 5}, {2, 3}, {4, 2}, {7, 1}}}; + f00 = select_best(1.); + + new_inds = f00.select(inds, 1, 0, 2, 0, 0, {}); + BOOST_CHECK((new_inds == inds)); +} diff --git a/tests/topology.cpp b/tests/topology.cpp new file mode 100644 index 000000000..1ab39e1b2 --- /dev/null +++ b/tests/topology.cpp @@ -0,0 +1,335 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE topology_test +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace pagmo; + +struct gc00 { + std::pair, vector_double> get_connections(std::size_t) const; +}; + +struct ngc00 { + void get_connections(std::size_t) const; +}; + +struct ngc01 { + std::pair, vector_double> get_connections(std::size_t); +}; + +struct pb00 { + void push_back(); +}; + +struct npb00 { +}; + +struct npb01 { + int push_back(); +}; + +struct udt00 { + std::pair, vector_double> get_connections(std::size_t) const + { + return {{0, 1, 2}, {0.1, 0.2, 0.3}}; + } + void push_back() + { + ++n_pushed; + } + std::string get_name() const + { + return "udt00"; + } + template + void serialize(Archive &ar, unsigned) + { + ar &n_pushed; + } + int n_pushed = 0; +}; + +PAGMO_S11N_TOPOLOGY_EXPORT(udt00) + +struct udt01 { + std::pair, vector_double> get_connections(std::size_t) const + { + return {{3, 4, 5}, {0.1, 0.2}}; + } + void push_back() + { + ++n_pushed; + } + std::string get_extra_info() const + { + return "hello"; + } + int n_pushed = 0; +}; + +BOOST_AUTO_TEST_CASE(topology_type_traits_test) +{ + BOOST_CHECK(!has_get_connections::value); + BOOST_CHECK(has_get_connections::value); + BOOST_CHECK(!has_get_connections::value); + BOOST_CHECK(!has_get_connections::value); + + BOOST_CHECK(!has_push_back::value); + BOOST_CHECK(has_push_back::value); + BOOST_CHECK(!has_push_back::value); + BOOST_CHECK(!has_push_back::value); + + BOOST_CHECK(!is_udt::value); + BOOST_CHECK(is_udt::value); + BOOST_CHECK(!is_udt::value); + BOOST_CHECK(!is_udt::value); +} + +BOOST_AUTO_TEST_CASE(topology_basic_tests) +{ + topology def00; + BOOST_CHECK(def00.is()); + + BOOST_CHECK((!std::is_constructible::value)); + BOOST_CHECK((!std::is_constructible::value)); + + topology t0{udt00{}}, t1{udt01{}}; + + BOOST_CHECK(t0.is_valid()); + BOOST_CHECK(t0.is()); + BOOST_CHECK(!t0.is()); + BOOST_CHECK(t0.extract() != nullptr); + BOOST_CHECK(static_cast(t0).extract() != nullptr); + BOOST_CHECK(t0.extract() == nullptr); + BOOST_CHECK(static_cast(t0).extract() == nullptr); + BOOST_CHECK(t0.get_name() == "udt00"); + BOOST_CHECK(t0.get_extra_info().empty()); + + t0.push_back(); + t0.push_back(); + + BOOST_CHECK(t0.extract()->n_pushed == 2); + + // Copy construction. + auto t3(t0); + BOOST_CHECK(t3.is_valid()); + BOOST_CHECK(t3.is()); + BOOST_CHECK(t3.extract()->n_pushed == 2); + BOOST_CHECK(static_cast(t3).extract()->n_pushed == 2); + BOOST_CHECK(t3.get_name() == "udt00"); + BOOST_CHECK(t3.get_extra_info().empty()); + + // Copy assignment. + topology t4; + t4 = t3; + BOOST_CHECK(t4.is_valid()); + BOOST_CHECK(t4.is()); + BOOST_CHECK(t4.extract()->n_pushed == 2); + BOOST_CHECK(static_cast(t4).extract()->n_pushed == 2); + BOOST_CHECK(t4.get_name() == "udt00"); + + // Move construction. + auto t5(std::move(t4)); + BOOST_CHECK(!t4.is_valid()); + BOOST_CHECK(t5.is_valid()); + BOOST_CHECK(t5.is()); + BOOST_CHECK(t5.extract()->n_pushed == 2); + BOOST_CHECK(t5.get_name() == "udt00"); + BOOST_CHECK(t5.get_extra_info().empty()); + + // Move assignment. + topology t6; + t6 = std::move(t5); + BOOST_CHECK(!t5.is_valid()); + BOOST_CHECK(t6.is_valid()); + BOOST_CHECK(t6.is()); + BOOST_CHECK(t6.extract()->n_pushed == 2); + BOOST_CHECK(t6.get_name() == "udt00"); + + // Generic assignment. + BOOST_CHECK((!std::is_assignable::value)); + t6 = udt01{}; + BOOST_CHECK(t6.is_valid()); + BOOST_CHECK(t6.is()); + BOOST_CHECK(t6.extract()->n_pushed == 0); + BOOST_CHECK(t6.get_extra_info() == "hello"); +} + +// Bad connections. +struct bc00 : udt00 { + // Inconsistent vector sizes. + std::pair, vector_double> get_connections(std::size_t) const + { + return {{0, 1}, {0.1, 0.2, 0.3}}; + } +}; + +struct bc01 : udt00 { + // Non-finite weight. + std::pair, vector_double> get_connections(std::size_t) const + { + return {{0, 1}, {0.1, std::numeric_limits::infinity()}}; + } +}; + +struct bc02 : udt00 { + // Weight outside the probability range. + std::pair, vector_double> get_connections(std::size_t) const + { + return {{0, 1}, {0.1, 2.}}; + } +}; + +BOOST_AUTO_TEST_CASE(topology_get_connections_test) +{ + topology t0{udt00{}}; + BOOST_CHECK(t0.get_connections(0) == t0.get_connections(1)); + BOOST_CHECK((t0.get_connections(0).first == std::vector{0, 1, 2})); + BOOST_CHECK((t0.get_connections(0).second == std::vector{.1, .2, .3})); + + t0 = bc00{}; + + BOOST_CHECK_EXCEPTION(t0.get_connections(0), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains(ia.what(), + "An invalid pair of vectors was returned by the 'get_connections()' method " + "of the 'udt00' topology: the vector of connecting islands has a size of 2, while the " + "vector of migration probabilities has a size of 3 (the two sizes must be equal)"); + }); + + t0 = bc01{}; + + BOOST_CHECK_EXCEPTION(t0.get_connections(0), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "An invalid non-finite migration probability of " + std::to_string(std::numeric_limits::infinity()) + + " was detected in the vector of migration probabilities returned by the 'get_connections()' " + "method of the 'udt00' topology"); + }); + + t0 = bc02{}; + + BOOST_CHECK_EXCEPTION(t0.get_connections(0), std::invalid_argument, [](const std::invalid_argument &ia) { + return boost::contains( + ia.what(), + "An invalid migration probability of " + std::to_string(2.) + + " was detected in the vector of migration probabilities returned by the 'get_connections()' " + "method of the 'udt00' topology: the value must be in the [0., 1.] range"); + }); +} + +BOOST_AUTO_TEST_CASE(topology_s11n_test) +{ + topology t0{udt00{}}; + t0.push_back(); + t0.push_back(); + + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << t0; + } + topology t1; + BOOST_CHECK(!t1.is()); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> t1; + } + + BOOST_CHECK(t1.is()); + BOOST_CHECK(t1.get_name() == "udt00"); + BOOST_CHECK(t1.extract()->n_pushed == 2); +} + +BOOST_AUTO_TEST_CASE(topology_stream_test) +{ + { + topology t0{udt01{}}; + + std::ostringstream oss; + + oss << t0; + + auto str = oss.str(); + + BOOST_CHECK(boost::contains(str, "Topology name:")); + BOOST_CHECK(boost::contains(str, "hello")); + } + + { + topology t0{udt00{}}; + + std::ostringstream oss; + + oss << t0; + + auto str = oss.str(); + + BOOST_CHECK(boost::contains(str, "Topology name: udt00")); + } +} + +BOOST_AUTO_TEST_CASE(topology_push_back_n_test) +{ + topology t0{ring{}}; + + t0.push_back(0); + + BOOST_CHECK(t0.extract()->num_vertices() == 0u); + + t0.push_back(2); + + BOOST_CHECK(t0.extract()->num_vertices() == 2u); + BOOST_CHECK(t0.get_connections(0).first.size() == 1u); + BOOST_CHECK(t0.get_connections(0).first[0] == 1u); + BOOST_CHECK(t0.get_connections(1).first.size() == 1u); + BOOST_CHECK(t0.get_connections(1).first[0] == 0u); + + t0.push_back(5); + + BOOST_CHECK(t0.extract()->num_vertices() == 7u); +} diff --git a/tests/unconnected.cpp b/tests/unconnected.cpp new file mode 100644 index 000000000..cd74c3e9a --- /dev/null +++ b/tests/unconnected.cpp @@ -0,0 +1,70 @@ +/* Copyright 2017-2018 PaGMO development team + +This file is part of the PaGMO library. + +The PaGMO library is free software; you can redistribute it and/or modify +it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + +or + + * the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any + later version. + +or both in parallel, as here. + +The PaGMO library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received copies of the GNU General Public License and the +GNU Lesser General Public License along with the PaGMO library. If not, +see https://www.gnu.org/licenses/. */ + +#define BOOST_TEST_MODULE unconnected +#define BOOST_TEST_DYN_LINK +#include + +#include + +#include +#include +#include +#include + +using namespace pagmo; + +BOOST_AUTO_TEST_CASE(basic_test) +{ + unconnected r0; + + BOOST_CHECK(r0.get_connections(0).first.empty()); + BOOST_CHECK(r0.get_connections(0).second.empty()); + + r0.push_back(); + + BOOST_CHECK(r0.get_connections(1).first.empty()); + BOOST_CHECK(r0.get_connections(1).second.empty()); + + // Minimal serialization test. + { + topology t0(r0); + std::stringstream ss; + { + boost::archive::binary_oarchive oarchive(ss); + oarchive << t0; + } + topology t1(ring{}); + BOOST_CHECK(!t1.is()); + { + boost::archive::binary_iarchive iarchive(ss); + iarchive >> t1; + } + BOOST_CHECK(t1.is()); + } +}