from typing import Sequence, Tuple
import numpy as np
from .basegenerator import BaseGenerator
from ..common.helpers import is_bounds_valid
__all__ = ("ExploitExploreGenerator",)
[docs]class ExploitExploreGenerator(BaseGenerator):
""" This generator blends a randomly generated point with the location of an existing optimizer.
The optimizer is chosen based on a roulette selection.
Parameters
----------
bounds
Min and max bounds for each parameter.
max_func_calls
Maximum function calls allowed for the optimization, at and beyond this point there is a 100% chance that a
previously evaluated point will be returned by the generator. If the optimization is not limited by the number
of function calls, provide an estimate.
focus
The blend parameter between random point and incumbent points.
Notes
-----
`focus` is used as follows::
p=(f_calls / max_f_calls) ** focus
At :code:`p=0` the random point is taken. At :code:`p=1` the incumbent is chosen. If :code:`focus < 1` points are
more like the incumbent, if :code:`focus > 1` points are more like the random. Default is :code:`focus = 1` which
has a linear growth from random to incumbent. The new point is calculated as::
new_pt = p*incumbent_pt + (1-p)*random_pt.
"""
def __init__(self, bounds: Sequence[Tuple[float, float]], max_func_calls: int, focus: float = 1):
super().__init__()
if not isinstance(max_func_calls, int):
raise TypeError("Cannot parse max_func_calls, int required.")
if max_func_calls < 2:
raise ValueError("Cannot parse max_func_calls, int > 1 required.")
self.max_func_calls = max_func_calls
if focus <= 0:
raise ValueError("Cannot parse focus, float larger than 0 required.")
self.focus = focus
if is_bounds_valid(bounds):
self.bounds = np.array(bounds)
self.n_params = len(bounds)
def generate(self, manager: 'GloMPOManager') -> np.ndarray:
# Random Point
random = (self.bounds[:, 1] - self.bounds[:, 0]) * np.random.random(self.n_params) + self.bounds[:, 0]
self.logger.debug("Random = %s", random)
f_track = np.array([best['fx'] for best in manager.opt_log._best_iters.values()])[1:]
if len(f_track) == 0:
return random
# Roulette Selection
shift = f_track - np.min(f_track)
revert = np.max(shift) - shift
if np.all(revert == 0):
return random
prob = revert / np.sum(revert)
select = np.random.choice(range(len(f_track)), p=prob)
incumbent = np.array(manager.opt_log.get_best_iter(select + 1)['x'])
self.logger.debug("Selected incumbent from Optimizer %d = %s", select, incumbent)
# Blending parameter
f_calls = np.clip(manager.f_counter / self.max_func_calls, 0, 1)
alpha = f_calls ** self.focus
self.logger.debug("Selected alpha = %f", alpha)
# New point
generated = alpha * incumbent + (1 - alpha) * random
self.logger.debug("Generated = %f", generated)
return generated