Coding a User Defined Island#
While pagmo provides a number of UDIs (see List of islands) to provide access to a number of parallelization technologies, the expert user
can code his own expanding pygmo functionalities. In this tutorial we will show how to code a UDI. Remember that UDIs are classes that can be used
to construct a island
which, in turn, is what a archipelago
can use to distribute / parallelize optimization
tasks and have solutions migrate improving the overall optimization quality via the generalized island model.
We encourage the user to read the documentation of the class island
to have a detailed list of methods that can be, or have to be,
implemented in a UDI. Also the tutorial Use of the class island is a good starting point to understand the overall use.
A UDI is a python class which, in its simplest form, contains the method run_evolve(self, algo, pop)
which uses the algorithm
algo
to evolve
the population
pop
.
>>> import pygmo as pg
>>> class my_isl:
... def run_evolve(self, algo, pop):
... new_pop = algo.evolve(pop)
... return algo, new_pop
... def get_name(self):
... return "It's my island!"
We have also included above the optional method get_name(self)
that will be used by various __repr__(self)
to provide human readable information
on some pygmo classes. The above UDI can then be used to construct a island
(similarly to how UDP can be used to construct problem
, etc..).
>>> isl = pg.island(algo = pg.de(100), prob = pg.ackley(5), udi = my_isl(), size = 20)
>>> print(isl)
Island name: It's my island!
C++ class name: ...
Status: idle
Algorithm: DE: Differential Evolution
Problem: Ackley Function
Replacement policy: Fair replace
Selection policy: Select best
Population size: 20
Champion decision vector: [...
Champion fitness: [...
That was easy! Lets now understand what we actually did. The object isl
now contains our UDI and, upon construction will open a thread and delegate the execution of
run_evolve(self, algo, pop)
to it upon call to evolve()
. The run_evolve()
method must return the algorithm object used for
the evolution and the evolved population.
But there is a catch. We are in CPython! CPython, generally speaking, serialises the execution of Python code due to the presence
of the global interpreter lock (GIL). So, while the code above is perfectly fine and will
work with pygmo, a set of my_isl
running evolutions will not run in parallel as each island
, when executing its evolve()
method, acquires the GIL and holds it during the evolve()
execution.
As a consequence, the following code:
>>> archi = pg.archipelago(n = 5, algo = pg.de(100), prob = pg.rosenbrock(10), pop_size = 20, udi = my_isl())
>>> archi.evolve()
will not run evolution in parallel (only using different threads).
To code properly an UDI one need to code the def run_evolve(self, algo, pop)
so that the GIL is released during the offload of the evolution task to a separate process.
An example on how this can be achieved using, for example the multiprocessing module of python. Let us have a look at some code snippets from the mp_island
>>> def _evolve_func(algo, pop): # doctest : +SKIP
... new_pop = algo.evolve(pop)
... return algo, new_pop
>>> class mp_island(object): # doctest : +SKIP
... def __init__(self):
... # Init the process pool, if necessary.
... mp_island.init_pool()
...
... def run_evolve(self, algo, pop):
... with mp_island._pool_lock:
... res = mp_island._pool.apply_async(_evolve_func, (algo, pop))
... return res.get()
The full details are here not reported and can be read in the mp_island
code. In a nutshell, what happens is that the algo.evolve(pop)
gets offloaded to
a process (in a shared pool inited upon construction calling the init_pool()
static method). The instruction res.get()
, makes the thread where run_evolve
remain waiting for the process execution and while doing so it releases the GIL, making parallelization effective.