[SciPy-Dev] API to control iteration in differential evolution #6923

Andrew Nelson andyfaff at gmail.com
Sat Jan 14 15:20:11 EST 2017


On 15 January 2017 at 05:03, Denis Laxalde <denis at laxalde.org> wrote:

> Evgeni Burovski a écrit :
>
>> Small details, feel free to take or leave:
>>
>> 1. What is `step` in `for step in solver`? If it's a current "state of
>> the minimizer", then can its contents be retrieved from the solver
>> object itself (step vs `solver.result` above)? Or is it just a step
>> number and the solver holds all the information at each step via
>> `solver.result?` This needs to be specified and documented.
>>
>
> This `step` is an `OptimizeStep`. It's a new class introduced here to
> wrap any useful information produced by the solver during one iteration.
> In this case, it holds the current `x` and function value along with
> iteration number. The idea is to return an extensible object instead of,
> say, a tuple that may cause bw compat issues in the future. I think this
> is documented, feel free to comment further if things are not clear enough.


Best `x` and best energy are already available as `solver.x` and
`solver.population_energy[0]`, but I think this kind of object being
returned from `__next__` is a good idea. Does it have to be an
`OptimizeStep`, or can we return an preliminary `OptimizeResult`?

2. Since the solver class is iterable, just iterating it without the
>> context manager should be equivalent. Something like this (pseudocode)
>>
>> solver = DifferentialEvolutionSolver(...)
>> while not_converged:
>>     result = next(solver)
>>
>

> Also, the context manager interface is actually orthogonal to the
> iteration API. The context manager abstracts out the solver
> *initialization* (population initialization and energies calculation)
> and *termination* (possible polishing and build of the
> `OptimizeResult`).
>


Whilst the context manager is orthogonal to the iteration API, Evgeni's
example would not be possible with the PR is it currently exists - the only
way of initialising the solver (population initialization and energies
calculation) would be to go through the context manager. Denis suggested
that a way around this would be to have a separate initialize_population()
method. It's a good idea to have this method (for reasons I outline
later).  However, if you create the object outside a context manager I
don't think that one should have to call an additional
initialize_population() method to be able to iterate the object.  There are
a few reasons for this:
(a) it's an extra line to type
(b) iterating the object without going through the context manager is not
equivalent
(c) the solver object possesses public population and population_energies
attributes, these attributes need to be present in a newly created object,
not be created at a later stage
(d) if the population and population_energies attributes are present I'd
argue it makes more sense for them to contain relevant values
(e) given a solver object how does one know if initialize_population has
already been called (which would be a common occurrence when used in an
interactive interpreter)?
(f) you can't call next(solver) if the population hasn't been initialised.
You shouldn't need to know the state of the solver object to be able to
iterate it, it should be iterable straightaway.

Currently the initial energy calculations are done in `__init__` and one
uses the object as:

# stepwise
solver = DifferentialEvolutionSolver(...)
while not solver.converged:
     next(solver)

-or-

# all at once
solver = DifferentialEvolutionSolver(...)
result = solver.solve()

The proposed PR:

with solver as DifferentialEvolutionSolver(...):
    while not solver.converged():
        next(solver)
result = solver.result

-or possibly-

solver = DifferentialEvolutionSolver(...)
# extra line to type,
solver.initialize_population()
while not solver.converged:
     next(solver)


Perhaps the following course could be followed:

(1) make the __next__ method return an OptimizeStep object (or
OptimizeResult).
(2) add a polish() method that takes the current best solution and polishes
it. Having a separate polish method is useful as you can polish the object
at any step, getting functionality similar to optimize.brute.
(3) Add __enter__() and __exit__() methods to allow the object to be used
as a context manager. The __exit__() method can use the public polish()
method. I'd like to keep the existing solve() method, it can call
__exit__() at the end.
(4) population initialization and the initial energy calculation is moved
to a public method, initialize_population(). This method is either called
from __init__ (the status quo), or it is called on the first call to
__next__(), making the first call to __next__ take twice as long. My
preference is to keep the status quo because one can create the solver and
immediately polish the best population member (c.f. optimize.brute), as
well as the points raised above. The rationale for making it public is that
once you've done one optimization you can re-initialise it with a different
method ('latinhypercube' or 'random') and solve again or you could imagine
re-initialising the population with a given population array (which has
been requested previously) and start iterating again.

A.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/scipy-dev/attachments/20170115/25ddfda1/attachment.html>


More information about the SciPy-Dev mailing list