esa / pagmo2

A C++ platform to perform parallel computations of optimisation tasks (global and local) via the asynchronous generalized island model.
https://esa.github.io/pagmo2/
GNU General Public License v3.0
808 stars 160 forks source link

[FEATURE] Evolution callback for PSO algorithm #485

Closed tarcisiofischer closed 2 years ago

tarcisiofischer commented 2 years ago

I'm fairly new to pagmo/pygmo, and I've been testing it with the PSO algorithm over a toy UDP. One thing that bothered me a bit is that, although there are debug messages, I do not have much control over them (They are raw prints on the screen).

What I was trying to do in my toy project was to "steal" the populations over the PSO evolution to plot (really simple 2d case) with matplotlib (and pygmo). AFAIU, I can only do this today by calling evolve configured with only 1 generation, inside a for loop (something similar to the code below):

for i in range(n_generations):
  algo = configure_algo(gen=1)
  pop = algo.evolve(pop)
  callback(pop)

I thought perhaps adding a callback function in the PSO algorithm would be a nice addition. At the same time, I wanna hear if this is something that may be too intrusive / out of the project's scope / not a good idea for more complex cases. I'm opening this issue to see how you feel about this.

I've implemented a possible draft solution on a fork, so that you may take a look and see what I'm thinking about: https://github.com/tarcisiofischer/pagmo2/commit/a1f0ccfc2708a7d51c93e3481df8a350f772f3fd

The usage (in python) is as follows:

def scoped_run(problem):
    algorithm = pg.algorithm(
        pg.pso(gen=2, omega=1, eta1=1, eta2=1, seed=42, memory=True,
            # Dummy new callback:
            evolution_callback=lambda x, f: print(x, f),
        )
    )
    # algorithm.set_verbosity(1)
    population = pg.population(pg.problem(problem), 5, seed=42)
    optimized_pop = algorithm.evolve(population)
    print(optimized_pop.champion_f[0])

Please let me know if this is something you may consider, so I can polish the solution and create a PR specifically for PSO case. If not, I'll keep using my local solution for my experiments, so no problems :)

bluescarni commented 2 years ago

@tarcisiofischer Thanks for opening the report! I think this may be a good idea in principle. I'll ping @darioizzo as well as he's the one most involved with the algorithmic developments, but I think it's a good feature to have.

My initial comments/questions:

darioizzo commented 2 years ago

My first thoughts would be

1) What is the drawback of the current setup where a for loop is used with a gen=1 uda? Is there an added functionality to provide a callback?

2) this would be a PSO specific functionality which we tried to avoid as the spirit of the udas we provide is their symmetry of use. A generic callback for a pagmo problem or a base class for udas as @bluescarni suggests would then be the way ...

3) may the feature could be offered in python only?

bluescarni commented 2 years ago
1. What is the drawback of the current setup where a for loop is used with a gen=1 uda? Is there an added functionality to provide a callback?

Two things come to mind:

3. may the feature could be offered in python only?

The prototype implementation by @tarcisiofischer is already coded in C++ and I don't see the point in arbitrarily restricting it to be Python only? But maybe I misunderstood your point.

darioizzo commented 2 years ago

All algorithms are tested to have the same behaviour in the two cases. Some need a memory=True arg to account for a possible memory effect, but other than that the behaviour is identical in the two cases

In the archipelago cases the need to monitor the population during single evolve runs is not evident. One may want to log stuff after each migration, not in-between?

The python suggestion was to avoid to have a pagmo::function for serialisation... But I agree a C++ solution is better.

Bottom line, it can be a nice addition, surely more elegant and generic than the currently available one, but is also not straight forward to implement, will result in a rather rich PR and is not necessarily a new feature.

tarcisiofischer commented 2 years ago

Thank you for the reply and discussion!

I was under the impression that my "run-one-generation-and-get-the-population" approach was a hack, but now that you explained, it actually seems the way to go, since all algorithms must have the same behavior of one vs multiple generations by design.

Also, to be honest, I don't know how to implement the serialization of functions (and I don't think it would be easy), so... I think I'll just make a project-specific Python wrapper on my project to encapsulate the run of multiple generations and then calling a callback. I'll leave the code here for reference:

for i in range(n_generations):
  algo = configure_algo(gen=1) # Configure the algorithm of your preference with gen=1. Remember that some algorithms may need "memory=True" for this approach to work.
  pop = algo.evolve(pop)
  callback(pop)

Sorry for the noise, but thanks again for your attention!