Source code for leaspy.algo.algo_with_annealing
import warnings
import numpy as np
from leaspy.exceptions import LeaspyAlgoInputError
from .settings import AlgorithmSettings
__all__ = ["AlgorithmWithAnnealingMixin"]
[docs]
class AlgorithmWithAnnealingMixin:
"""Mixin class to use in algorithms that requires `temperature_inv`.
Note that this mixin should be used with a class inheriting from `AbstractAlgo`, which must have `algo_parameters`
attribute.
Parameters
----------
settings : :class:`.AlgorithmSettings`
The specifications of the algorithm as a :class:`.AlgorithmSettings` instance.
Please note that you can customize the number of iterations with annealing by setting:
* `annealing.n_iter_frac`, such that iterations with annealing is a ratio of algorithm `n_iter` (default = 50%)
Attributes
----------
annealing_on : :obj:`bool`
Activates annealing.
temperature : float >= 1
temperature_inv : float in [0, 1]
Temperature and its inverse when using annealing
"""
def __init__(self, settings: AlgorithmSettings):
super().__init__(settings)
self.temperature: float = 1.0
self.temperature_inv: float = 1.0
# useful property derived from algo parameters
self.annealing_on: bool = self.algo_parameters.get("annealing", {}).get(
"do_annealing", False
)
self._annealing_period: int = None
self._annealing_temperature_decrement: float = None
if not self.annealing_on:
return
# Dynamic number of iterations for annealing
annealing_n_iter_frac = self.algo_parameters["annealing"]["n_iter_frac"]
if self.algo_parameters["annealing"].get("n_iter", None) is None:
if annealing_n_iter_frac is None:
raise LeaspyAlgoInputError(
"You should NOT have both `annealing.n_iter_frac` and `annealing.n_iter` None."
"\nPlease set a value for at least one of those settings."
)
self.algo_parameters["annealing"]["n_iter"] = int(
annealing_n_iter_frac * self.algo_parameters["n_iter"]
)
elif annealing_n_iter_frac is not None:
warnings.warn(
"`annealing.n_iter` setting is deprecated in favour of `annealing.n_iter_frac` - "
"which defines the duration with annealing as a ratio of the total number of iterations."
"\nPlease use the new setting to suppress this warning "
"or explicitly set `annealing.n_iter_frac=None`."
"\nHowever, note that while `annealing.n_iter` is supported "
"it will always have priority over `annealing.n_iter_frac`.",
FutureWarning,
)
def __str__(self):
out = super().__str__()
if self.annealing_on:
out += "\n= Annealing =\n"
out += f" temperature : {self.temperature:.1f}"
return out
def _initialize_annealing(self):
"""
Initialize annealing, setting initial temperature and number of iterations.
"""
if not self.annealing_on:
return
self.temperature = self.algo_parameters["annealing"]["initial_temperature"]
self.temperature_inv = 1 / self.temperature
if not (
isinstance(self.algo_parameters["annealing"]["n_plateau"], int)
and self.algo_parameters["annealing"]["n_plateau"] > 0
):
raise LeaspyAlgoInputError(
"Your `annealing.n_plateau` should be a positive integer"
)
if self.algo_parameters["annealing"]["n_plateau"] == 1:
warnings.warn(
"You defined `annealing.n_plateau` = 1, so you will stay at initial temperature. "
"Consider setting `annealing.n_plateau` >= 2 for a true annealing scheme."
)
return
self._annealing_period = self.algo_parameters["annealing"]["n_iter"] // (
self.algo_parameters["annealing"]["n_plateau"] - 1
)
self._annealing_temperature_decrement = (
self.algo_parameters["annealing"]["initial_temperature"] - 1.0
) / (self.algo_parameters["annealing"]["n_plateau"] - 1)
if self._annealing_temperature_decrement <= 0:
raise LeaspyAlgoInputError("Your `initial_temperature` should be > 1")
def _update_temperature(self):
"""
Update the temperature according to a plateau annealing scheme.
"""
if not self.annealing_on or self._annealing_period is None:
return
if self.current_iteration <= self.algo_parameters["annealing"]["n_iter"]:
# If we cross a plateau step
if self.current_iteration % self._annealing_period == 0:
# Oscillating scheme
if self.algo_parameters["annealing"].get("oscillations", False):
params = self.algo_parameters["annealing"]
b = params["range"]
c = params["delay"]
r = params["period"]
k = self.current_iteration
kappa = c + 2.0 * float(k) * np.pi / r
self.temperature = max(1.0 + b * np.sin(kappa) / kappa, 0.1)
else:
# Decrease temperature linearly
self.temperature -= self._annealing_temperature_decrement
self.temperature = max(self.temperature, 1)
self.temperature_inv = 1.0 / self.temperature