import random
from .basestopper import BaseStopper
from ..core.optimizerlogger import BaseLogger
from ...plams.core.settings import Settings
__all__ = ("TimeAnnealing",)
[docs]class TimeAnnealing(BaseStopper):
"""Keeps optimizers alive based on how long they have been alive.
Randomly keeps optimizers alive based on how long (in function evaluations) they have been active. The newer an
optimizer is, the more likely it will pass the test and be kept alive. Used to temper very strict termination
conditions.
:Parameters:
crit_ratio
Critical ratio of function calls between best and tested optimizers above which the tested optimizer is
guaranteed to survive. Values lower than one are looser and allow the tested optimizer to survive even if it has
been in operation longer than the best optimizer. Values higher than one are stricter and may stop the tested
optimizer even if it has iterated fewer times than the best optimizer.
:Returns:
bool
``True`` if an optimizer has been alive long enough and fails a comparison test with a uniformly randomly
generated number.
:Notes:
This condition calculates the quotient (``num_bet_fcalls / num_tested_fcalls``). A random number is then
generated between zero and ``crit_ratio``. Only if the quotient is larger than this number does the tested optimizer
remains alive.
"""
def __init__(self, crit_ratio: float = 1):
super().__init__()
if isinstance(crit_ratio, (float, int)) and crit_ratio > 0:
self.crit_ratio = crit_ratio
else:
raise ValueError("threshold should be a positive float.")
def __call__(self, log: BaseLogger, best_opt_id: int, tested_opt_id: int) -> bool:
n_best = log.len(best_opt_id)
n_tested = log.len(tested_opt_id)
ratio = n_best / n_tested
test_num = random.uniform(0, self.crit_ratio)
self.last_result = test_num > ratio
return self.last_result
def __amssettings__(self, s: Settings) -> Settings:
s.input.ams.Stopper.Type = "TimeAnnealing"
s.input.ams.Stopper.TimeAnnealing.CriticalRatio = self.crit_ratio
return s