# !/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 copy import deepcopy
from mealpy.utils.visualize import export_convergence_chart, export_explore_exploit_chart, \
export_diversity_chart, export_objectives_chart, export_trajectory_chart
[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_population: List of POPULATION in each generations
+ **Warning**, the last variable 'list_population' can cause the error related to 'memory' when using pickle to save model.\
Better to delete that variable or assign to empty list [] to reduce the 'memory'.
+ 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.swarm_based.PSO import BasePSO
>>>
>>> def fitness_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "fit_func": fitness_function,
>>> "lb": [-10, -15, -4, -2, -8],
>>> "ub": [10, 15, 12, 8, 20],
>>> "minmax": "min",
>>> "verbose": True,
>>> }
>>> model = BasePSO(problem_dict, epoch=1000, pop_size=50)
>>>
>>> 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], list_dimensions=[3], filename="hello/tc")
>>>
>>> ## Get list of population after all generations
>>> print(model.history.list_population)
"""
def __init__(self):
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 generations
self.list_diversity = None # List of diversity of swarm in all generations
self.list_exploitation = None # List of exploitation percentages for all generations
self.list_exploration = None # List of exploration percentages for all generations
[docs] def save_initial_best(self, best_agent):
self.list_global_best = [best_agent]
self.list_current_best = deepcopy(self.list_global_best)
[docs] def get_global_repeated_times(self, id_fitness, id_target, epsilon):
count = 0
for i in range(0, len(self.list_global_best) - 1):
temp = np.abs(self.list_global_best[i][id_fitness][id_target] - self.list_global_best[i + 1][id_fitness][id_target])
if temp <= epsilon:
count += 1
else:
count = 0
return count
[docs] def save_global_best_fitness_chart(self, title='Global Best Fitness', color='b', x_label="#Iteration", y_label="Function Value",
filename="global-best-fitness-chart", verbose=True):
# Draw global best fitness found so far in previous generations
export_convergence_chart(self.list_global_best_fit, title=title, color=color, x_label=x_label,
y_label=y_label, filename=filename, verbose=verbose)
[docs] def save_local_best_fitness_chart(self, title='Local Best Fitness', color='b', x_label="#Iteration", y_label="Function Value",
filename="local-best-fitness-chart", verbose=True):
# Draw current best fitness in each previous generation
export_convergence_chart(self.list_current_best_fit, title=title, color=color, x_label=x_label,
y_label=y_label, filename=filename, verbose=verbose)
[docs] def save_runtime_chart(self, title='Runtime chart', color='b', x_label="#Iteration", y_label='Second',
filename="runtime-chart", verbose=True):
# Draw runtime for each generation
export_convergence_chart(self.list_epoch_time, title=title, color=color, x_label=x_label,
y_label=y_label, filename=filename, 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
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='GA',
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
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_label="Function Value",
filename="global-objectives-chart", verbose=True):
# 2D array / matrix 2D
global_obj_list = np.array([agent[1][-1] 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]))]
export_objectives_chart(global_obj_list, title=title, x_label=x_label, y_label=y_label, filename=filename, verbose=verbose)
[docs] def save_local_objectives_chart(self, title='Local Objectives Chart', x_label="#Iteration", y_label="Objective Function Value",
filename="local-objectives-chart", verbose=True):
current_obj_list = np.array([agent[1][-1] 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]))]
export_objectives_chart(current_obj_list, title=title, x_label=x_label, y_label=y_label,
filename=filename, verbose=verbose)
[docs] def save_trajectory_chart(self, title="Trajectory of some first agents after generations",
list_agent_idx=(1, 2, 3), list_dimensions=(1, 2),
filename="trajectory-chart", verbose=True):
## Drawing trajectory of some agents in the first and second dimensions
# Need a little bit more pre-processing
list_agent_idx = set(list_agent_idx)
list_dimensions = set(list_dimensions)
list_agent_idx = sorted(list_agent_idx)
list_dimensions = sorted(list_dimensions)
n_dim = len(list_dimensions)
if n_dim not in [1, 2]:
print("Can draw trajectory only for 1 or 2 dimensions!")
exit(0)
if len(list_agent_idx) < 1 or len(list_agent_idx) > 10:
print("Can draw trajectory for 1 to 10 agents only!")
exit(0)
if list_agent_idx[-1] > len(self.list_population[0]) or list_agent_idx[0] < 1:
print(f"The index of input agent should be in range of [1, {len(self.list_population[0])}]")
exit(0)
if list_dimensions[-1] > len(self.list_population[0][0][0]) or list_dimensions[0] < 1:
print(f"The index of dimension should be in range of [1, {len(self.list_population[0][0][0])}]")
exit(0)
pos_list = []
list_legends = []
# pop[0]: Get the first solution
# pop[0][0]: Get the position of the first solution
# pop[0][0][0]: Get the first dimension of the position of the first solution
if n_dim == 1:
y_label = f"x{list_dimensions[0]}"
for idx, id_agent in enumerate(list_agent_idx):
x = [pop[id_agent - 1][0][list_dimensions[0] - 1] for pop in self.list_population]
pos_list.append(x)
list_legends.append(f"Agent {id_agent}.")
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{list_dimensions[0]}"
y_label = f"x{list_dimensions[1]}"
for idx1, id_agent in enumerate(list_agent_idx):
pos_temp = []
for idx2, id_dim in enumerate(list_dimensions):
x = [pop[id_agent - 1][0][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}.")
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)