Source code for glompo.generators.exploit_explore

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