1# Copyright 2020, 2021 PaGMO development team
2#
3# This file is part of the pygmo library.
4#
5# This Source Code Form is subject to the terms of the Mozilla
6# Public License v. 2.0. If a copy of the MPL was not distributed
7# with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9from ._check_deps import *
10# Version setup.
11from ._version import __version__
12# We import the sub-modules into the root namespace
13from .core import *
14from .plotting import *
15from ._py_islands import *
16from ._py_problems import *
17from ._py_bfes import *
18from ._py_algorithms import *
19# Patch the problem class.
20from . import _patch_problem
21# Patch the algorithm class.
22from . import _patch_algorithm
23# Patch the bfe class.
24from . import _patch_bfe
25# Patch the island class.
26from . import _patch_island
27# Patch the policies.
28from . import _patch_r_policy
29from . import _patch_s_policy
30# Patch the topology.
31from . import _patch_topology
32import cloudpickle as _cloudpickle
33# Explicitly import the test submodule
34from . import test
35import atexit as _atexit
36
37
38# We move into the problems, algorithms, etc. namespaces
39# all the pure python UDAs, UDPs and UDIs.
40for _item in dir(_py_islands):
41    if not _item.startswith("_"):
42        setattr(islands, _item, getattr(_py_islands, _item))
43del _py_islands
44
45for _item in dir(_py_problems):
46    if not _item.startswith("_"):
47        setattr(problems, _item, getattr(_py_problems, _item))
48del _py_problems
49
50for _item in dir(_py_bfes):
51    if not _item.startswith("_"):
52        setattr(batch_evaluators, _item, getattr(_py_bfes, _item))
53del _py_bfes
54
55for _item in dir(_py_algorithms):
56    if not _item.startswith("_"):
57        setattr(algorithms, _item, getattr(_py_algorithms, _item))
58del _py_algorithms
59
60del _item
61
62# Machinery for the setup of the serialization backend.
63_serialization_backend = _cloudpickle
64
65# Override of the translate meta-problem constructor.
66__original_translate_init = translate.__init__
67
68# NOTE: the idea of having the translate init here instead of exposed from C++ is to allow the use
69# of the syntax translate(udp, translation) for all udps
70
71
72def _translate_init(self, prob=None, translation=[0.]):
73    """
74    Args:
75        prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem`
76            (if *prob* is :data:`None`, a :class:`~pygmo.null_problem` will be used in its stead)
77        translation (array-like object): an array containing the translation to be applied
78
79    Raises:
80        ValueError: if the length of *translation* is not equal to the dimension of *prob*
81
82        unspecified: any exception thrown by:
83
84           * the constructor of :class:`pygmo.problem`,
85           * the constructor of the underlying C++ class,
86           * failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function
87             signatures, etc.)
88    """
89    if prob is None:
90        # Use the null problem for default init.
91        prob = null_problem()
92    if type(prob) == problem:
93        # If prob is a pygmo problem, we will pass it as-is to the
94        # original init.
95        prob_arg = prob
96    else:
97        # Otherwise, we attempt to create a problem from it. This will
98        # work if prob is an exposed C++ problem or a Python UDP.
99        prob_arg = problem(prob)
100    __original_translate_init(self, prob_arg, translation)
101
102
103setattr(translate, "__init__", _translate_init)
104
105# Override of the decompose meta-problem constructor.
106__original_decompose_init = decompose.__init__
107
108# NOTE: the idea of having the translate init here instead of exposed from C++ is to allow the use
109# of the syntax decompose(udp, ..., ) for all udps
110
111
112def _decompose_init(self, prob=None, weight=[0.5, 0.5], z=[0., 0.], method='weighted', adapt_ideal=False):
113    """
114    Args:
115        prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem`
116            (if *prob* is :data:`None`, a :class:`~pygmo.null_problem` will be used in its stead)
117        weight (array-like object): the vector of weights :math:`\\boldsymbol \lambda`
118        z (array-like object): the reference point :math:`\mathbf z^*`
119        method (str): a string containing the decomposition method chosen
120        adapt_ideal (bool): when :data:`True`, the reference point is adapted at each fitness evaluation
121            to be the ideal point
122
123    Raises:
124        ValueError: if either:
125
126           * *prob* is single objective or constrained,
127           * *method* is not one of [``'weighted'``, ``'tchebycheff'``, ``'bi'``],
128           * *weight* is not of size :math:`n`,
129           * *z* is not of size :math:`n`
130           * *weight* is not such that :math:`\\lambda_i > 0, \\forall i=1..n`,
131           * *weight* is not such that :math:`\\sum_i \\lambda_i = 1`
132
133        unspecified: any exception thrown by:
134
135           * the constructor of :class:`pygmo.problem`,
136           * the constructor of the underlying C++ class,
137           * failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function
138             signatures, etc.)
139
140    """
141    if prob is None:
142        # Use the null problem for default init.
143        prob = null_problem(nobj=2)
144    if type(prob) == problem:
145        # If prob is a pygmo problem, we will pass it as-is to the
146        # original init.
147        prob_arg = prob
148    else:
149        # Otherwise, we attempt to create a problem from it. This will
150        # work if prob is an exposed C++ problem or a Python UDP.
151        prob_arg = problem(prob)
152    __original_decompose_init(self, prob_arg, weight, z, method, adapt_ideal)
153
154
155setattr(decompose, "__init__", _decompose_init)
156
157# Override of the unconstrain meta-problem constructor.
158__original_unconstrain_init = unconstrain.__init__
159
160# NOTE: the idea of having the unconstrain init here instead of exposed from C++ is to allow the use
161# of the syntax unconstrain(udp, ... ) for all udps
162
163
164def _unconstrain_init(self, prob=None, method="death penalty", weights=[]):
165    """
166    Args:
167        prob: a :class:`~pygmo.problem` or a user-defined problem, either C++ or Python (if
168              *prob* is :data:`None`, a :class:`~pygmo.null_problem` will be used in its stead)
169        method (str): a string containing the unconstrain method chosen, one of [``'death penalty'``, ``'kuri'``, ``'weighted'``, ``'ignore_c'``, ``'ignore_o'``]
170        weights (array-like object): the vector of weights to be used if the method chosen is ``'weighted'``
171
172    Raises:
173        ValueError: if either:
174
175           * *prob* is unconstrained,
176           * *method* is not one of [``'death penalty'``, ``'kuri'``, ``'weighted'``, ``'ignore_c'``, ``'ignore_o'``],
177           * *weight* is not of the same size as the problem constraints (if the method ``'weighted'`` is selected), or not empty otherwise.
178
179        unspecified: any exception thrown by:
180
181           * the constructor of :class:`pygmo.problem`,
182           * the constructor of the underlying C++ class,
183           * failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function
184             signatures, etc.)
185
186    """
187    if prob is None:
188        # Use the null problem for default init.
189        prob = null_problem(nobj=2, nec=3, nic=4)
190    if type(prob) == problem:
191        # If prob is a pygmo problem, we will pass it as-is to the
192        # original init.
193        prob_arg = prob
194    else:
195        # Otherwise, we attempt to create a problem from it. This will
196        # work if prob is an exposed C++ problem or a Python UDP.
197        prob_arg = problem(prob)
198    __original_unconstrain_init(self, prob_arg, method, weights)
199
200
201setattr(unconstrain, "__init__", _unconstrain_init)
202
203# Override of the cstrs_self_adaptive meta-algorithm constructor.
204__original_cstrs_self_adaptive_init = cstrs_self_adaptive.__init__
205# NOTE: the idea of having the cstrs_self_adaptive init here instead of exposed from C++ is to allow the use
206# of the syntax cstrs_self_adaptive(uda, ...) for all udas
207
208
209def _cstrs_self_adaptive_init(self, iters=1, algo=None, seed=None):
210    """
211    Args:
212        iter (int): number of iterations (i.e., calls to the inner algorithm evolve)
213        algo: an :class:`~pygmo.algorithm` or a user-defined algorithm, either C++ or Python (if
214             *algo* is :data:`None`, a :class:`~pygmo.de` algorithm will be used in its stead)
215        seed (int): seed used by the internal random number generator (if *seed* is :data:`None`, a
216             randomly-generated value will be used in its stead)
217
218    Raises:
219        ValueError: if *iters* is negative or greater than an implementation-defined value
220        unspecified: any exception thrown by the constructor of :class:`pygmo.algorithm`, or by
221             failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function
222             signatures, etc.)
223
224    """
225    if algo is None:
226        # Use the null problem for default init.
227        algo = de()
228    if type(algo) == algorithm:
229        # If algo is a pygmo algorithm, we will pass it as-is to the
230        # original init.
231        algo_arg = algo
232    else:
233        # Otherwise, we attempt to create an algorithm from it. This will
234        # work if algo is an exposed C++ algorithm or a Python UDA.
235        algo_arg = algorithm(algo)
236    if seed is None:
237        __original_cstrs_self_adaptive_init(self, iters, algo_arg)
238    else:
239        __original_cstrs_self_adaptive_init(self, iters, algo_arg, seed)
240
241
242setattr(cstrs_self_adaptive, "__init__", _cstrs_self_adaptive_init)
243
244
245# Override of the population constructor.
246__original_population_init = population.__init__
247
248
249def _population_init(self, prob=None, size=0, b=None, seed=None):
250    # NOTE: the idea of having the pop init here instead of exposed from C++ is that like this we don't need
251    # to expose a new pop ctor each time we expose a new problem: in this method we will use the problem ctor
252    # from a C++ problem, and on the C++ exposition side we need only to
253    # expose the ctor of pop from pagmo::problem.
254    """
255    Args:
256        prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem`
257             (if *prob* is :data:`None`, a default-constructed :class:`~pygmo.problem` will be used
258             in its stead)
259        size (:class:`int`): the number of individuals
260        b: a user-defined batch fitness evaluator (either Python or C++), or an instance of :class:`~pygmo.bfe`
261             (if *b* is :data:`None`, the evaluation of the population's individuals will be performed
262             in sequential mode)
263        seed (:class:`int`): the random seed (if *seed* is :data:`None`, a randomly-generated value will be used
264             in its stead)
265
266    Raises:
267        TypeError: if *size* is not an :class:`int` or *seed* is not :data:`None` and not an :class:`int`
268        OverflowError:  is *size* or *seed* are negative
269        unspecified: any exception thrown by the invoked C++ constructors, by the constructor of
270            :class:`~pygmo.problem`, or the constructor of :class:`~pygmo.bfe`, or by failures at
271            the intersection between C++ and
272            Python (e.g., type conversion errors, mismatched function signatures, etc.)
273
274    """
275    from .core import _random_device_next
276    # Check input params.
277    if not isinstance(size, int):
278        raise TypeError("the 'size' parameter must be an integer")
279    if not seed is None and not isinstance(seed, int):
280        raise TypeError("the 'seed' parameter must be None or an integer")
281    if prob is None:
282        # Problem not specified, def-construct it.
283        prob = problem()
284    elif type(prob) != problem:
285        # If prob is not a problem, we attempt to create a problem from it. This will
286        # work if prob is an exposed C++ problem or a Python UDP.
287        prob = problem(prob)
288
289    if seed is None:
290        # Seed not specified, randomly generate it
291        # with the global pagmo rng.
292        seed = _random_device_next()
293
294    if b is None:
295        # No bfe specified, init in sequential mode.
296        __original_population_init(self, prob, size, seed)
297    else:
298        # A bfe was specified. Same as above with the problem.
299        __original_population_init(self, prob, b if type(
300            b) == bfe else bfe(b), size, seed)
301
302
303setattr(population, "__init__", _population_init)
304
305
306# Override of the island constructor.
307__original_island_init = island.__init__
308
309
310def _island_init(self, **kwargs):
311    """
312    Keyword Args:
313        udi: a user-defined island, either Python or C++
314        algo: a user-defined algorithm (either Python or C++), or an instance of :class:`~pygmo.algorithm`
315        pop (:class:`~pygmo.population`): a population
316        prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem`
317        b: a user-defined batch fitness evaluator (either Python or C++), or an instance of :class:`~pygmo.bfe`
318        size (:class:`int`): the number of individuals
319        r_pol: a user-defined replacement policy (either Python or C++), or an instance of :class:`~pygmo.r_policy`
320        s_pol: a user-defined selection policy (either Python or C++), or an instance of :class:`~pygmo.s_policy`
321        seed (:class:`int`): the random seed (if not specified, it will be randomly-generated)
322
323    Raises:
324        KeyError: if the set of keyword arguments is invalid
325        unspecified: any exception thrown by the invoked C++ constructors,
326          the deep copy of the UDI, the constructors of :class:`~pygmo.algorithm` and :class:`~pygmo.population`,
327          failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function
328          signatures, etc.)
329
330    """
331    if len(kwargs) == 0:
332        # Default constructor.
333        __original_island_init(self)
334        return
335
336    # If we are not dealing with a def ctor, we always need the algo argument.
337    if not 'algo' in kwargs:
338        raise KeyError(
339            "the mandatory 'algo' parameter is missing from the list of arguments "
340            "of the island constructor")
341    algo = kwargs.pop('algo')
342    algo = algo if type(algo) == algorithm else algorithm(algo)
343
344    # Population setup. We either need an input pop, or the prob and size,
345    # plus optionally seed and b.
346    if 'pop' in kwargs and ('prob' in kwargs or 'size' in kwargs or 'seed' in kwargs or 'b' in kwargs):
347        raise KeyError(
348            "if the 'pop' argument is provided, the 'prob', 'size', 'seed' and 'b' "
349            "arguments must not be provided")
350    elif 'pop' in kwargs:
351        pop = kwargs.pop('pop')
352    elif 'prob' in kwargs and 'size' in kwargs:
353        pop = population(prob=kwargs.pop('prob'),
354                         size=kwargs.pop('size'), seed=kwargs.pop('seed') if 'seed' in kwargs else None,
355                         b=kwargs.pop('b') if 'b' in kwargs else None)
356    else:
357        raise KeyError(
358            "unable to construct a population from the arguments of "
359            "the island constructor: you must either pass a population "
360            "('pop') or a set of arguments that can be used to build one "
361            "('prob', 'size' and, optionally, 'seed' and 'b')")
362
363    # UDI, if any.
364    if 'udi' in kwargs:
365        args = [kwargs.pop('udi'), algo, pop]
366    else:
367        args = [algo, pop]
368
369    # Replace/selection policies, if any.
370    if 'r_pol' in kwargs:
371        r_pol = kwargs.pop('r_pol')
372        r_pol = r_pol if type(r_pol) == r_policy else r_policy(r_pol)
373        args.append(r_pol)
374    else:
375        args.append(r_policy())
376
377    if 's_pol' in kwargs:
378        s_pol = kwargs.pop('s_pol')
379        s_pol = s_pol if type(s_pol) == s_policy else s_policy(s_pol)
380        args.append(s_pol)
381    else:
382        args.append(s_policy())
383
384    if len(kwargs) != 0:
385        raise KeyError(
386            'unrecognised keyword arguments: {}'.format(list(kwargs.keys())))
387
388    __original_island_init(self, *args)
389
390
391setattr(island, "__init__", _island_init)
392
393
394# Override of the mbh meta-algorithm constructor.
395__original_mbh_init = mbh.__init__
396# NOTE: the idea of having the mbh init here instead of exposed from C++ is to allow the use
397# of the syntax mbh(uda, ...) for all udas
398
399
400def _mbh_init(self, algo=None, stop=5, perturb=1e-2, seed=None):
401    """
402    Args:
403        algo: an :class:`~pygmo.algorithm` or a user-defined algorithm, either C++ or Python (if
404              *algo* is :data:`None`, a :class:`~pygmo.compass_search` algorithm will be used in its stead)
405        stop (int): consecutive runs of the inner algorithm that need to result in no improvement for
406             :class:`~pygmo.mbh` to stop
407        perturb (float or array-like object): the perturbation to be applied to each component
408        seed (int): seed used by the internal random number generator (if *seed* is :data:`None`, a
409             randomly-generated value will be used in its stead)
410
411    Raises:
412        ValueError: if *perturb* (or one of its components, if *perturb* is an array) is not in the
413             (0,1] range
414        unspecified: any exception thrown by the constructor of :class:`pygmo.algorithm`, or by
415             failures at the intersection between C++ and Python (e.g., type conversion errors, mismatched function
416             signatures, etc.)
417
418    """
419    import numbers
420    if algo is None:
421        # Use the compass search algo for default init.
422        algo = compass_search()
423    if type(algo) == algorithm:
424        # If algo is a pygmo algorithm, we will pass it as-is to the
425        # original init.
426        algo_arg = algo
427    else:
428        # Otherwise, we attempt to create an algorithm from it. This will
429        # work if algo is an exposed C++ algorithm or a Python UDA.
430        algo_arg = algorithm(algo)
431    if isinstance(perturb, numbers.Number):
432        perturb = [perturb]
433    if seed is None:
434        __original_mbh_init(self, algo_arg, stop, perturb)
435    else:
436        __original_mbh_init(self, algo_arg, stop, perturb, seed)
437
438
439setattr(mbh, "__init__", _mbh_init)
440
441
442# Override of the archi constructor.
443__original_archi_init = archipelago.__init__
444
445
446def _archi_init(self, n=0, t=topology(), **kwargs):
447    """__init__(self, n=0, t=topology(), **kwargs)
448
449    The constructor will initialise an archipelago with a topology *t* and
450    *n* islands built from *kwargs*.
451    The keyword arguments accept the same format as explained in the constructor of
452    :class:`~pygmo.island`, with the following differences:
453
454    * *size* is replaced by *pop_size*, for clarity,
455    * the *seed* argument, if present, is used to initialise a random number generator
456      that, in turn, is used to generate random seeds for each island population. In other
457      words, the *seed* argument allows to generate randomly (but deterministically)
458      the seeds of the populations in the archipelago. If *seed* is not provided, the seeds
459      of the populations will be random and non-deterministic.
460
461    This class is the Python counterpart of the C++ class :cpp:class:`pagmo::archipelago`.
462
463    Args:
464        n (:class:`int`): the number of islands in the archipelago
465        t: a user-defined topology (either Python or C++), or an instance of :class:`~pygmo.topology`
466
467    Keyword Args:
468        udi: a user-defined island, either Python or C++
469        algo: a user-defined algorithm (either Python or C++), or an instance of :class:`~pygmo.algorithm`
470        pop (:class:`~pygmo.population`): a population
471        prob: a user-defined problem (either Python or C++), or an instance of :class:`~pygmo.problem`
472        b: a user-defined batch fitness evaluator (either Python or C++), or an instance of :class:`~pygmo.bfe`
473        pop_size (:class:`int`): the number of individuals for each island
474        r_pol: a user-defined replacement policy (either Python or C++), or an instance of :class:`~pygmo.r_policy`
475        s_pol: a user-defined selection policy (either Python or C++), or an instance of :class:`~pygmo.s_policy`
476        seed (:class:`int`): the random seed
477
478    Raises:
479        TypeError: if *n* is not an integral type
480        ValueError: if *n* is negative
481        unspecified: any exception thrown by the constructor of :class:`~pygmo.island`,
482          by the underlying C++ constructor, :func:`~pygmo.archipelago.push_back()` or
483          by the public interface of :class:`~pygmo.topology`
484
485    Examples:
486        >>> from pygmo import *
487        >>> archi = archipelago(n = 16, algo = de(), prob = rosenbrock(10), pop_size = 20, seed = 32)
488        >>> archi.evolve()
489        >>> archi #doctest: +SKIP
490        Number of islands: 16
491        Status: busy
492        <BLANKLINE>
493        Islands summaries:
494        <BLANKLINE>
495                #   Type           Algo                    Prob                                  Size  Status
496                -----------------------------------------------------------------------------------------------
497                0   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
498                1   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
499                2   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
500                3   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
501                4   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    busy
502                5   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
503                6   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    busy
504                7   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    busy
505                8   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
506                9   Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
507                10  Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
508                11  Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
509                12  Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
510                13  Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    busy
511                14  Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    busy
512                15  Thread island  Differential Evolution  Multidimensional Rosenbrock Function  20    idle
513        <BLANKLINE>
514        >>> archi.wait()
515        >>> res = archi.get_champions_f()
516        >>> res #doctest: +SKIP
517        [array([ 125441.77328885]),
518        array([ 144025.63904164]),
519        array([ 25387.38989711]),
520        array([ 56029.44160232]),
521        array([ 47760.99082202]),
522        array([ 118552.94891993]),
523        array([ 118405.29575447]),
524        array([ 101866.81846325]),
525        array([ 166106.12039851]),
526        array([ 167408.00058506]),
527        array([ 148815.47953885]),
528        array([ 165186.74476375]),
529        array([ 326615.28881936]),
530        array([ 167301.16445135]),
531        array([ 166871.42760503]),
532        array([ 75133.38815736])]
533
534
535    """
536    # Check n.
537    if not isinstance(n, int):
538        raise TypeError("the 'n' parameter must be an integer")
539    if n < 0:
540        raise ValueError(
541            "the 'n' parameter must be non-negative, but it is {} instead".format(n))
542
543    # Replace the 'pop_size' kw arg with just 'size', for later use in the
544    # island ctor.
545
546    if 'size' in kwargs:
547        raise KeyError(
548            "the 'size' argument cannot appear among the named arguments of the archipelago constructor")
549
550    if 'pop_size' in kwargs:
551        # Extract 'pop_size', replace with just 'size'.
552        ps_val = kwargs.pop('pop_size')
553        kwargs['size'] = ps_val
554
555    # Call the original init, which constructs an empty archi from a topology.
556    t = t if type(t) == topology else topology(t)
557    __original_archi_init(self, t)
558
559    if 'seed' in kwargs:
560        # Special handling of the 'seed' argument.
561        from random import Random
562        from .core import _max_unsigned
563        # Create a random engine with own state.
564        RND = Random()
565        # Get the seed from kwargs.
566        seed = kwargs.pop('seed')
567        if not isinstance(seed, int):
568            raise TypeError("the 'seed' parameter must be an integer")
569        # Seed the rng.
570        RND.seed(seed)
571        u_max = _max_unsigned()
572        # Push back the islands with different seed.
573        for _ in range(n):
574            kwargs['seed'] = RND.randint(0, u_max)
575            self.push_back(**kwargs)
576
577    else:
578        # Push back islands.
579        for _ in range(n):
580            self.push_back(**kwargs)
581
582
583setattr(archipelago, "__init__", _archi_init)
584
585
586def _archi_push_back(self, *args, **kwargs):
587    """Add an island.
588
589    This method will construct an island from the supplied arguments and add it to the archipelago.
590    Islands are added at the end of the archipelago (that is, the new island will have an index
591    equal to the size of the archipelago before the call to this method). :func:`pygmo.topology.push_back()`
592    will also be called on the :class:`~pygmo.topology` associated to this archipelago, so that
593    the addition of a new island to the archipelago is mirrored by the addition of a new vertex
594    to the topology.
595
596    This method accepts either a single positional argument, or a set of
597    keyword arguments:
598
599    * if no positional arguments are provided, then the keyword arguments will
600      be used to construct a :class:`~pygmo.island` which will then be added
601      to the archipelago; otherwise,
602    * if a single positional argument is provided and no keyword arguments are
603      provided, then the positional argument is interpreted as an :class:`~pygmo.island`
604      object to be added to the archipelago.
605
606    Any other combination of positional/keyword arguments will result in an error.
607
608    Raises:
609        ValueError: if, when using positional arguments, there are more than 1 positional arguments,
610          or if keyword arguments are also used at the same time
611        TypeError: if, when using a single positional argument, the type of that argument
612          is not :class:`~pygmo.island`
613        unspecified: any exception thrown by the constructor of :class:`~pygmo.island`,
614          :func:`pygmo.topology.push_back()` or by the underlying C++ method
615
616    """
617    from . import island
618
619    if len(args) == 0:
620        self._push_back(island(**kwargs))
621    else:
622        if len(args) != 1:
623            raise ValueError(
624                "{} positional arguments were provided, but this method accepts only a single positional argument".format(len(args)))
625        if len(kwargs) != 0:
626            raise ValueError(
627                "if a positional argument is passed to this method, then no keyword arguments must be passed, but {} keyword arguments were passed instead".format(len(kwargs)))
628        if type(args[0]) != island:
629            raise TypeError(
630                "the positional argument passed to this method must be an island, but the type of the argument is '{}' instead".format(type(args[0])))
631        self._push_back(args[0])
632
633
634setattr(archipelago, "push_back", _archi_push_back)
635
636
637def _archi_set_topology(self, t):
638    """This method will wait for any ongoing evolution in the archipelago to finish,
639    and it will then set the topology of the archipelago to *t*.
640
641    Note that it is the user's responsibility to ensure that the new topology is
642    consistent with the archipelago's properties.
643
644    Args:
645        t: a user-defined topology (either Python or C++), or an instance of :class:`~pygmo.topology`
646
647    Raises:
648        unspecified: any exception thrown by copying the topology
649
650    """
651    t = t if type(t) == topology else topology(t)
652    self._set_topology(t)
653
654
655setattr(archipelago, "set_topology", _archi_set_topology)
656
657
658# Override of the free_form constructor.
659__original_free_form_init = free_form.__init__
660
661
662def _free_form_init(self, t=None):
663    """
664    Args:
665        t: the object that will be used for construction
666
667    Raises:
668        ValueError: if the edges of the input :class:`networkx.DiGraph`
669            do not all have a ``weight`` attribute, or if any edge weight
670            is outside the :math:`\\left[ 0, 1 \\right]` range
671        unspecified: any exception thrown by :func:`pygmo.topology.to_networkx()`, or by
672            the construction of a :class:`~pygmo.topology` from a UDT
673
674    """
675    import networkx as nx
676
677    if t is None:
678        # Default ctor.
679        __original_free_form_init(self)
680    elif type(t) == topology or isinstance(t, nx.DiGraph):
681        # Ctors from topology or DiGraph are exposed
682        # directly.
683        __original_free_form_init(self, t)
684    else:
685        # If t is neither a topology, nor a DiGraph,
686        # assume that it is a UDT.
687        __original_free_form_init(self, topology(t))
688
689
690setattr(free_form, "__init__", _free_form_init)
691
692
693def set_serialization_backend(name):
694    """Set pygmo's serialization backend.
695
696    This function allows to specify the serialization backend that is used internally by pygmo
697    for the (de)serialization of pythonic user-defined entities (e.g., user-defined pythonic
698    problems, algorithms, etc.).
699
700    By default, pygmo uses the `cloudpickle <https://github.com/cloudpipe/cloudpickle>`__
701    module, which extends the capabilities of the standard :mod:`pickle` module with support
702    for lambdas, functions and classes defined interactively in the ``__main__`` module, etc.
703
704    In some specific cases, however, different serialization backends might work better than cloudpickle,
705    and thus pygmo provides the possibility for the cognizant user to switch to another
706    serialization backend.
707
708    The valid backends are:
709
710    * ``'pickle'`` (i.e., the standard Python :mod:`pickle` module),
711    * ``'cloudpickle'``,
712    * ``'dill'`` (from the `dill <https://pypi.org/project/dill/>`__ library).
713
714    .. warning::
715
716       Setting the serialization backend is not thread-safe: do **not** set
717       the serialization backend while concurrently setting/getting it from another thread,
718       or while asynchronous evolutions/optimisations are ongoing.
719
720    Args:
721        name (str): the name of the desired backend
722
723    Raises:
724        TypeError: if *name* is not a :class:`str`
725        ValueError: if *name* is not one of ``['pickle', 'cloudpickle', 'dill']``
726        ImportError: if *name* is ``'dill'`` but the dill module is not installed
727
728    """
729    if not isinstance(name, str):
730        raise TypeError(
731            "The serialization backend must be specified as a string, but an object of type {} was provided instead".format(type(name)))
732    global _serialization_backend
733    if name == "pickle":
734        import pickle
735        _serialization_backend = pickle
736    elif name == "cloudpickle":
737        _serialization_backend = _cloudpickle
738    elif name == "dill":
739        try:
740            import dill
741            _serialization_backend = dill
742        except ImportError:
743            raise ImportError(
744                "The 'dill' serialization backend was specified, but the dill module is not installed.")
745    else:
746        raise ValueError(
747            "The serialization backend '{}' is not valid. The valid backends are: ['pickle', 'cloudpickle', 'dill']".format(name))
748
749
750def get_serialization_backend():
751    """Get pygmo's serialization backend.
752
753    This function will return pygmo's current serialization backend (see
754    :func:`~pygmo.set_serialization_backend()` for an explanation of the
755    available backends).
756
757    Returns:
758       types.ModuleType: the current serialization backend (as a Python module)
759
760    """
761    return _serialization_backend
762
763
764def _cleanup():
765    mp_island.shutdown_pool()
766    mp_bfe.shutdown_pool()
767    ipyparallel_island.shutdown_view()
768    ipyparallel_bfe.shutdown_view()
769
770
771_atexit.register(_cleanup)
772