#!/usr/bin/env python
# Created by "Thieu" at 14:51, 13/10/2021 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from mealpy.utils import visualize
from mealpy.utils.agent import Agent
from mealpy.utils.logger import Logger
[docs]class History:
"""
A History class is responsible for saving each iteration's output.
Notes
~~~~~
+ Access to variables in this class:
+ list_global_best: List of global best SOLUTION found so far in all previous generations
+ list_current_best: List of current best SOLUTION in each previous generations
+ list_epoch_time: List of runtime for each generation
+ list_global_best_fit: List of global best FITNESS found so far in all previous generations
+ list_current_best_fit: List of current best FITNESS in each previous generations
+ list_diversity: List of DIVERSITY of swarm in all generations
+ list_exploitation: List of EXPLOITATION percentages for all generations
+ list_exploration: List of EXPLORATION percentages for all generations
+ list_global_worst: List of global worst SOLUTION found so far in all previous generations
+ list_current_worst: List of current worst SOLUTION in each previous generations
+ list_population: List of POPULATION in each generations
+ **Warning**, the last variable 'list_population' can cause the error related to 'memory' when saving model.
Better to set parameter 'save_population' to False in the input problem dictionary to not using it.
+ There are 8 methods to draw available in this class:
+ save_global_best_fitness_chart()
+ save_local_best_fitness_chart()
+ save_global_objectives_chart()
+ save_local_objectives_chart()
+ save_exploration_exploitation_chart()
+ save_diversity_chart()
+ save_runtime_chart()
+ save_trajectory_chart()
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="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function,
>>> "save_population": True, # To be able to draw the trajectory figure
>>> "name": "Test Function"
>>> }
>>> model = BBO.OriginalBBO(epoch=1000, pop_size=50)
>>> model.solve(p1)
>>>
>>> model.history.save_global_objectives_chart(filename="hello/goc")
>>> model.history.save_local_objectives_chart(filename="hello/loc")
>>> model.history.save_global_best_fitness_chart(filename="hello/gbfc")
>>> model.history.save_local_best_fitness_chart(filename="hello/lbfc")
>>> model.history.save_runtime_chart(filename="hello/rtc")
>>> model.history.save_exploration_exploitation_chart(filename="hello/eec")
>>> model.history.save_diversity_chart(filename="hello/dc")
>>> model.history.save_trajectory_chart(list_agent_idx=[3, 5], selected_dimensions=[3], filename="hello/tc")
>>>
>>> ## Get list of global best solution after all generations
>>> print(model.history.list_global_best)
"""
def __init__(self, **kwargs):
self.list_global_best = [] # List of global best solution found so far in all previous generations
self.list_current_best = [] # List of current best solution in each previous generations
self.list_epoch_time = [] # List of runtime for each generation
self.list_global_best_fit = [] # List of global best fitness found so far in all previous generations
self.list_current_best_fit = [] # List of current best fitness in each previous generations
self.list_population = [] # List of population in each generation
self.list_diversity = [] # List of diversity of swarm in all generations
self.list_exploitation = [] # List of exploitation percentages for all generations
self.list_exploration = [] # List of exploration percentages for all generations
self.list_global_worst = [] # List of global worst solution found so far in all previous generations
self.list_current_worst = [] # List of current worst solution in each previous generations
self.epoch, self.log_to, self.log_file = None, None, None
self.__set_keyword_arguments(kwargs)
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')
def __set_keyword_arguments(self, kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
[docs] def store_initial_best_worst(self, best_agent: Agent, worst_agent: Agent) -> None:
self.list_global_best = [best_agent.copy()]
self.list_current_best = [best_agent.copy()]
self.list_global_worst = [worst_agent.copy()]
self.list_current_worst = [worst_agent.copy()]
[docs] def get_global_repeated_times(self, epsilon: float) -> int:
count = 0
for idx in range(0, len(self.list_global_best) - 1):
temp = np.abs(self.list_global_best[idx].target.fitness - self.list_global_best[idx + 1].target.fitness)
if temp <= epsilon:
count += 1
else:
count = 0
return count
[docs] def save_global_best_fitness_chart(self, title='Global Best Fitness', legend=None, linestyle='-', color='b', x_label="#Iteration",
y_label="Function Value", filename="global-best-fitness-chart", exts=(".png", ".pdf"), verbose=True):
# Draw global best fitness found so far in previous generations
visualize.export_convergence_chart(data=self.list_global_best_fit, title=title, legend=legend, linestyle=linestyle,
color=color, x_label=x_label, y_label=y_label, filename=filename, exts=exts, verbose=verbose)
[docs] def save_local_best_fitness_chart(self, title='Local Best Fitness', legend=None, linestyle='-', color='b', x_label="#Iteration",
y_label="Function Value", filename="local-best-fitness-chart", exts=(".png", ".pdf"), verbose=True):
# Draw current best fitness in each previous generation
visualize.export_convergence_chart(self.list_current_best_fit, title=title, legend=legend, linestyle=linestyle, color=color,
x_label=x_label, y_label=y_label, filename=filename, exts=exts, verbose=verbose)
[docs] def save_runtime_chart(self, title='Runtime chart', legend=None, linestyle='-', color='b', x_label="#Iteration",
y_label='Second', filename="runtime-chart", exts=(".png", ".pdf"), verbose=True):
# Draw runtime for each generation
visualize.export_convergence_chart(self.list_epoch_time, title=title, legend=legend, linestyle=linestyle, color=color,
x_label=x_label, y_label=y_label, filename=filename, exts=exts, verbose=verbose)
## The paper: On the exploration and exploitation in popular swarm-based metaheuristic algorithms
[docs] def save_exploration_exploitation_chart(self, title="Exploration vs Exploitation Percentages", list_colors=('blue', 'orange'),
filename="exploration-exploitation-chart", verbose=True):
# This exploration/exploitation chart should draws for single algorithm and single fitness function
# Draw exploration and exploitation chart
visualize.export_explore_exploit_chart(data=[self.list_exploration, self.list_exploitation], title=title,
list_colors=list_colors, filename=filename, verbose=verbose)
[docs] def save_diversity_chart(self, title='Diversity Measurement Chart', algorithm_name='Algorithm',
filename="diversity-chart", verbose=True):
# This diversity chart should draws for multiple algorithms for a single fitness function at the same time
# to compare the diversity spreading
visualize.export_diversity_chart(data=[self.list_diversity], title=title, list_legends=[algorithm_name],
filename=filename, verbose=verbose)
## Because convergence chart is formulated from objective values and weights,
## thus we also want to draw objective charts to understand the convergence
## Need a little bit more pre-processing
[docs] def save_global_objectives_chart(self, title='Global Objectives Chart', x_label="#Iteration", y_labels=None,
filename="global-objectives-chart", verbose=True):
# 2D array / matrix 2D
global_obj_list = np.array([agent.target.objectives for agent in self.list_global_best])
# Make each obj_list as a element in array for drawing
global_obj_list = [global_obj_list[:, idx] for idx in range(0, len(global_obj_list[0]))]
visualize.export_objectives_chart(global_obj_list, title=title, x_label=x_label, y_labels=y_labels, filename=filename, verbose=verbose)
[docs] def save_local_objectives_chart(self, title='Local Objectives Chart', x_label="#Iteration", y_labels=None,
filename="local-objectives-chart", verbose=True):
current_obj_list = np.array([agent.target.objectives for agent in self.list_current_best])
# Make each obj_list as a element in array for drawing
current_obj_list = [current_obj_list[:, idx] for idx in range(0, len(current_obj_list[0]))]
visualize.export_objectives_chart(current_obj_list, title=title, x_label=x_label, y_labels=y_labels, filename=filename, verbose=verbose)
[docs] def save_trajectory_chart(self, title="Trajectory of some agents", list_agent_idx=(1, 2, 3, 4, 5),
selected_dimensions=(1, 2), filename="trajectory-chart", verbose=True):
if len(self.list_population) < 2:
raise ValueError(f"Can't draw the trajectory because 'save_population' is set to False or the number of epochs is too small.")
## Drawing trajectory of some agents in the first and second dimensions
# Need a little more pre-processing
list_agent_idx = set(list_agent_idx)
selected_dimensions = set(selected_dimensions)
list_agent_idx = sorted(list_agent_idx)
selected_dimensions = sorted(selected_dimensions)
n_dim = len(selected_dimensions)
if n_dim not in [1, 2]:
raise ValueError(f"Trajectory chart for more than 2 dimensions is not supported.")
if len(list_agent_idx) < 1 or len(list_agent_idx) > 10:
raise ValueError(f"Trajectory chart for more than 10 agents is not supported.")
if list_agent_idx[-1] > len(self.list_population[0]) or list_agent_idx[0] < 1:
raise ValueError(f"Can't draw trajectory chart, the index of selected agents should be in range of [1, {len(self.list_population[0])}]")
if selected_dimensions[-1] > len(self.list_population[0][0].solution) or selected_dimensions[0] < 1:
raise ValueError(f"Can't draw trajectory chart, the index of selected dimensions should be in range of [1, {len(self.list_population[0][0].solution)}]")
pos_list = []
list_legends = []
# pop[0]: Get the first agent
# pop[0].solution: Get the position/solution of the first agent
# pop[0].solution[0]: Get the first dimension of the position of the first agent
if n_dim == 1:
y_label = f"x{selected_dimensions[0]}"
for idx, id_agent in enumerate(list_agent_idx):
x = [pop[id_agent - 1].solution[selected_dimensions[0] - 1] for pop in self.list_population]
pos_list.append(x)
list_legends.append(f"Agent {id_agent}")
visualize.export_trajectory_chart(pos_list, n_dimensions=n_dim, title=title, list_legends=list_legends,
y_label=y_label, filename=filename, verbose=verbose)
elif n_dim == 2:
x_label = f"x{selected_dimensions[0]}"
y_label = f"x{selected_dimensions[1]}"
for idx1, id_agent in enumerate(list_agent_idx):
pos_temp = []
for idx2, id_dim in enumerate(selected_dimensions):
x = [pop[id_agent - 1].solution[id_dim - 1] for pop in self.list_population]
pos_temp.append(x)
pos_list.append(pos_temp)
list_legends.append(f"Agent {id_agent}")
visualize.export_trajectory_chart(pos_list, n_dimensions=n_dim, title=title, list_legends=list_legends, x_label=x_label,
y_label=y_label, filename=filename, verbose=verbose)