#!/usr/bin/env python
# Created by "Thieu" at 22:23, 17/03/2023 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
from mealpy.utils.logger import Logger
from mealpy.utils.validator import Validator
[docs]class Termination:
"""
Define custom single/multiple Stopping Conditions (termination criteria) for the Optimizer.
Notes
~~~~~
+ By default, the stopping condition in the Optimizer class is based on the maximum number of generations (epochs/iterations).
+ Using this class allows you to override the default termination criteria. If multiple stopping conditions are specified, the first one that occurs will be used.
+ In general, there are four types of termination criteria: FE, MG, TB, and ES.
+ MG: Maximum Generations / Epochs / Iterations
+ FE: Maximum Number of Function Evaluations
+ TB: Time Bound - If you want your algorithm to run for a fixed amount of time (e.g., K seconds), especially when comparing different algorithms.
+ ES: Early Stopping - Similar to the idea in training neural networks (stop the program if the global best solution has not improved by epsilon after K epochs).
+ Parameters for Termination class, set it to None if you don't want to use it
+ max_epoch (int): Indicates the maximum number of generations for the MG type.
+ max_fe (int): Indicates the maximum number of function evaluations for the FE type.
+ max_time (float): Indicates the maximum amount of time for the TB type.
+ max_early_stop (int): Indicates the maximum number of epochs for the ES type.
+ epsilon (float): (Optional) This is used for the ES termination type (default value: 1e-10).
+ termination (dict): (Optional) A dictionary of termination criteria.
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, BBO
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> p1 = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="C-params"),
>>> "minmax": "min",
>>> "obj_func": objective_function,
>>> "name": "Test Function"
>>> }
>>>
>>> term_dict = {
>>> "max_epoch": 1000,
>>> "max_fe": 100000, # 100000 number of function evaluation
>>> "max_time": 10, # 10 seconds to run the program
>>> "max_early_stop": 15 # 15 epochs if the best fitness is not getting better we stop the program
>>> }
>>> model1 = BBO.OriginalBBO(epoch=1000, pop_size=50)
>>> model1.solve(p1, termination=term_dict)
"""
def __init__(self, max_epoch=None, max_fe=None, max_time=None, max_early_stop=None, **kwargs):
self.max_epoch = max_epoch
self.max_fe = max_fe
self.max_time = max_time
self.max_early_stop = max_early_stop
self.epsilon = 1e-10
self.__set_keyword_arguments(kwargs)
self.validator = Validator(log_to="console", log_file=None)
self.name, self.message, self.log_to, self.log_file = "Termination", "", None, None
self.__set_condition(self.max_epoch, self.max_fe, self.max_time, self.max_early_stop)
self.logger = Logger(self.log_to, log_file=self.log_file).create_logger(name=f"{__name__}.{__class__.__name__}",
format_str='%(asctime)s, %(levelname)s, %(name)s [line: %(lineno)d]: %(message)s')
self.logger.propagate = False
def __set_keyword_arguments(self, kwargs):
if type(kwargs) == dict:
if type(kwargs.get("termination")) == dict:
for key, value in kwargs.items():
setattr(self, key, value)
for key, value in kwargs.items():
setattr(self, key, value)
def __set_condition(self, max_epoch, max_fe, max_time, max_early_stop):
if (max_epoch is None) and (max_fe is None) and (max_time is None) and (max_early_stop is None):
raise ValueError("Please set at least one stopping condition with parameter 'max_epoch' or 'max_fe' or 'max_time' or 'max_early_stop'")
else:
if max_epoch is not None:
self.max_epoch = self.validator.check_int("max_epoch", max_epoch, [1, 10000000])
if max_fe is not None:
self.max_fe = self.validator.check_int("max_fe", max_fe, [10, 1000000000])
if max_time is not None:
self.max_time = self.validator.check_float("max_time", max_time, [0.1, 1000000])
if max_early_stop is not None:
self.max_early_stop = self.validator.check_int("max_early_stop", max_early_stop, [1, 100000])
[docs] def get_name(self):
return self.name
[docs] def set_start_values(self, start_epoch, start_fe, start_time, start_threshold):
self.start_epoch = start_epoch
self.start_fe = start_fe
self.start_time = start_time
self.start_threshold = start_threshold
[docs] def should_terminate(self, current_epoch, current_fe, current_time, current_threshold):
# Check maximum number of generations
if self.max_epoch is not None and current_epoch >= self.max_epoch:
self.message = "Stopping criterion with maximum number of epochs/generations/iterations (MG) occurred. End program!"
return True
# Check maximum number of function evaluations
if self.max_fe is not None and current_fe >= self.max_fe:
self.message = "Stopping criterion with maximum number of function evaluations (FE) occurred. End program!"
return True
# Check maximum time
if self.max_time is not None and current_time >= self.max_time:
self.message = "Stopping criterion with maximum running time/time bound (TB) (seconds) occurred. End program!"
return True
# Check early stopping
if self.max_early_stop is not None and current_threshold >= self.max_early_stop:
self.message = "Stopping criterion with early stopping (ES) (fitness-based) occurred. End program!"
return True
return False