from .basestopper import BaseStopper
from ..core.optimizerlogger import BaseLogger
from ...plams.core.settings import Settings
__all__ = ("BestFunctionValueUnmoving",)
[docs]class BestFunctionValueUnmoving(BaseStopper):
"""Considers the lowest function value seen by the optimizer thus far.
Returns ``True`` if the tested optimizer's best value has not changed significantly in a given amount of time.
:Parameters:
calls
Number of function evaluations between comparison points.
tol
Tolerance fraction between 0 and 1.
:Returns:
bool
``True`` if::
abs(latest_f_value - f_value_calls_ago) <= abs(f_value_calls_ago * self.tol)
"""
def __init__(self, calls: int, tol: float = 0):
super().__init__()
self.calls = calls
self.tol = tol
def __call__(self, log: BaseLogger, best_opt_id: int, tested_opt_id: int) -> bool:
vals = log.get_history(tested_opt_id, "fx")
fcalls = log.len(tested_opt_id)
if fcalls <= self.calls:
# If there are insufficient iterations the stopper will return False
self.last_result = False
return self.last_result
best_at_calls = min(vals[: -self.calls])
best_at_end = min(vals)
self.last_result = abs(best_at_end - best_at_calls) <= abs(best_at_calls * self.tol)
return self.last_result
def __amssettings__(self, s: Settings) -> Settings:
s.input.ams.Stopper.Type = "BestFunctionValueUnmoving"
s.input.ams.Stopper.BestFunctionValueUnmoving.NumberOfFunctionCalls = self.calls
s.input.ams.Stopper.BestFunctionValueUnmoving.Tolerance = self.tol
return s