Source code for scm.glompo.stoppers.maxinteroptimizerdistance

from typing import Sequence, Tuple

import numpy as np

from .basestopper import BaseStopper
from ..common.helpers import distance, is_bounds_valid
from ..core.optimizerlogger import BaseLogger
from ...plams.core.settings import Settings

__all__ = ("MaxInteroptimizerDistance",)


[docs]class MaxInteroptimizerDistance(BaseStopper): """Terminates optimizers which are too close in parameter space. :Parameters: bounds Bounds of each parameter. relative_distance Fraction (between 0 and 1) of the maximum distance in the space (from the point at all lower bounds to the point at all upper bounds) below which the optimizers are deemed too close and the tested optimizer will be stopped. test_all If ``True`` the distance between the tested optimizer and all other optimizers is tested, else only the best and tested optimizers are compared. :Returns: bool Returns ``True`` if optimizers are calculated to be too close together. :Notes: Calculates the maximum Euclidean distance in parameter space between the point of lower bounds and the point of upper bounds (``max_distance``). Calculates the Euclidean distance between the final iterations of the best and tested optimizers (``inter_optimizer_distance``). Returns ``True`` if:: inter_optimizer_distance <= max_distance * relative_distance .. caution:: Use this stopper with care and only in problems where the parameters have been standardised so that every parameter is dimensionless and on the same order of magnitude. """ def __init__(self, bounds: Sequence[Tuple[float, float]], relative_distance: float, test_all: bool = False): super().__init__() if isinstance(relative_distance, (float, int)) and relative_distance > 0: self.relative_distance = relative_distance else: raise ValueError("relative_distance should be a positive float.") if is_bounds_valid(bounds): lower_pt, upper_pt = tuple(np.transpose(bounds)) self.trans_space_dist = distance(lower_pt, upper_pt) self.test_all = test_all def __call__(self, log: BaseLogger, best_opt_id: int, tested_opt_id: int) -> bool: if self.test_all: compare_to = range(1, log.n_optimizers + 1) else: compare_to = [best_opt_id] for opt_id in compare_to: if opt_id != tested_opt_id: try: h1 = np.array(log.get_history(opt_id, "x")[-1]) except IndexError: self.logger.debug("Unable to compare to Opt%d, no points in log", opt_id) continue v1 = np.array(log.get_history(tested_opt_id, "x")[-1]) opt_dist = distance(h1, v1) ratio = opt_dist / self.trans_space_dist self.last_result = ratio <= self.relative_distance if self.last_result: self.logger.debug( "MaxInteroptimizerDistance: Best=%d, Tested=%d, " "Result=%.2f / %.2f <= %.2f} = " "%s.", opt_id, tested_opt_id, opt_dist, self.trans_space_dist, self.relative_distance, self.last_result, ) return self.last_result self.last_result = False return self.last_result def __amssettings__(self, s: Settings) -> Settings: s.input.ams.Stopper.Type = "MaxInteroptimizerDistance" s.input.ams.Stopper.MaxInteroptimizerDistance.MaxRelativeDistance = self.relative_distance s.input.ams.Stopper.MaxInteroptimizerDistance.CompareAllOptimizers = self.test_all return s