#!/usr/bin/env python
# Created by "Thieu" at 08:37, 17/06/2023 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
from scipy.stats import cauchy
[docs]class OriginalSHADE(Optimizer):
"""
The original version of: Success-History Adaptation Differential Evolution (OriginalSHADE)
Links:
1. https://doi.org/10.1109/CEC.2013.6557555
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ miu_f (float): [0.4, 0.6], initial weighting factor, default = 0.5
+ miu_cr (float): [0.4, 0.6], initial cross-over probability, default = 0.5
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, SHADE
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "obj_func": objective_function,
>>> "minmax": "min",
>>> }
>>>
>>> model = SHADE.OriginalSHADE(epoch=1000, pop_size=50, miu_f = 0.5, miu_cr = 0.5)
>>> g_best = model.solve(problem_dict)
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
>>> print(f"Solution: {model.g_best.solution}, Fitness: {model.g_best.target.fitness}")
References
~~~~~~~~~~
[1] Tanabe, R. and Fukunaga, A., 2013, June. Success-history based parameter adaptation for
differential evolution. In 2013 IEEE congress on evolutionary computation (pp. 71-78). IEEE.
"""
def __init__(self, epoch: int = 750, pop_size: int = 100, miu_f: float = 0.5, miu_cr: float = 0.5, **kwargs: object) -> None:
"""
Args:
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
miu_f (float): initial weighting factor, default = 0.5
miu_cr (float): initial cross-over probability, default = 0.5
"""
super().__init__(**kwargs)
self.epoch = self.validator.check_int("epoch", epoch, [1, 100000])
self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 10000])
# the initial f, location is changed then that f is good
self.miu_f = self.validator.check_float("miu_f", miu_f, (0, 1.0))
# the initial cr,
self.miu_cr = self.validator.check_float("miu_cr", miu_cr, (0, 1.0))
self.set_parameters(["epoch", "pop_size", "miu_f", "miu_cr"])
self.sort_flag = False
[docs] def initialize_variables(self):
self.dyn_miu_f = self.miu_f * np.ones(self.pop_size) # list the initial f,
self.dyn_miu_cr = self.miu_cr * np.ones(self.pop_size) # list the initial cr,
self.dyn_pop_archive = list()
self.k_counter = 0
### Survivor Selection
[docs] def weighted_lehmer_mean__(self, list_objects, list_weights):
up = list_weights * list_objects ** 2
down = list_weights * list_objects
return np.sum(up) / np.sum(down)
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
list_f = list()
list_cr = list()
list_f_index = list()
list_cr_index = list()
list_f_new = np.ones(self.pop_size)
list_cr_new = np.ones(self.pop_size)
pop_old = [agent.copy() for agent in self.pop]
pop_sorted = self.get_sorted_population(self.pop, self.problem.minmax)
pop = []
for idx in range(0, self.pop_size):
## Calculate adaptive parameter cr and f
idx_rand = self.generator.integers(0, self.pop_size)
cr = self.generator.normal(self.dyn_miu_cr[idx_rand], 0.1)
cr = np.clip(cr, 0, 1)
while True:
f = cauchy.rvs(self.dyn_miu_f[idx_rand], 0.1)
if f < 0:
continue
elif f > 1:
f = 1
break
list_cr_new[idx] = cr
list_f_new[idx] = f
p = self.generator.uniform(2 / self.pop_size, 0.2)
top = int(self.pop_size * p)
x_best = pop_sorted[self.generator.integers(0, top)]
x_r1 = self.pop[self.generator.choice(list(set(range(0, self.pop_size)) - {idx}))]
new_pop = self.pop + self.dyn_pop_archive
while True:
x_r2 = new_pop[self.generator.integers(0, len(new_pop))]
if np.any(x_r2.solution - x_r1.solution) and np.any(x_r2.solution - self.pop[idx].solution):
break
x_new = self.pop[idx].solution + f * (x_best.solution - self.pop[idx].solution) + f * (x_r1.solution - x_r2.solution)
condition = self.generator.random(self.problem.n_dims) < cr
pos_new = np.where(condition, x_new, self.pop[idx].solution)
j_rand = self.generator.integers(0, self.problem.n_dims)
pos_new[j_rand] = x_new[j_rand]
pos_new = self.correct_solution(pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
pop[-1].target = self.get_target(pos_new)
pop = self.update_target_for_population(pop)
for idx in range(0, self.pop_size):
if self.compare_target(pop[idx].target, self.pop[idx].target, self.problem.minmax):
list_cr.append(list_cr_new[idx])
list_f.append(list_f_new[idx])
list_f_index.append(idx)
list_cr_index.append(idx)
self.pop[idx] = pop[idx].copy()
self.dyn_pop_archive.append(pop[idx].copy())
# Randomly remove solution
temp = len(self.dyn_pop_archive) - self.pop_size
if temp > 0:
idx_list = self.generator.choice(range(0, len(self.dyn_pop_archive)), temp, replace=False)
archive_pop_new = []
for idx, agent in enumerate(self.dyn_pop_archive):
if idx not in idx_list:
archive_pop_new.append(agent.copy())
self.dyn_pop_archive = archive_pop_new
# Update miu_cr and miu_f
if len(list_f) != 0 and len(list_cr) != 0:
# Eq.13, 14, 10
list_fit_old = np.ones(len(list_cr_index))
list_fit_new = np.ones(len(list_cr_index))
idx_increase = 0
for idx in range(0, self.pop_size):
if idx in list_cr_index:
list_fit_old[idx_increase] = pop_old[idx].target.fitness
list_fit_new[idx_increase] = self.pop[idx].target.fitness
idx_increase += 1
temp = np.sum(np.abs(list_fit_new - list_fit_old))
if temp == 0:
list_weights = 1.0 / len(list_fit_new) * np.ones(len(list_fit_new))
else:
list_weights = np.abs(list_fit_new - list_fit_old) / temp
self.dyn_miu_cr[self.k_counter] = np.sum(list_weights * np.array(list_cr))
self.dyn_miu_f[self.k_counter] = self.weighted_lehmer_mean__(np.array(list_f), list_weights)
self.k_counter += 1
if self.k_counter >= self.pop_size:
self.k_counter = 0
[docs]class L_SHADE(Optimizer):
"""
The original version of: Linear Population Size Reduction Success-History Adaptation Differential Evolution (LSHADE)
Links:
1. https://metahack.org/CEC2014-Tanabe-Fukunaga.pdf
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ miu_f (float): [0.4, 0.6], initial weighting factor, default = 0.5
+ miu_cr (float): [0.4, 0.6], initial cross-over probability, default = 0.5
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, SHADE
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "obj_func": objective_function,
>>> "minmax": "min",
>>> }
>>>
>>> model = SHADE.L_SHADE(epoch=1000, pop_size=50, miu_f = 0.5, miu_cr = 0.5)
>>> g_best = model.solve(problem_dict)
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
>>> print(f"Solution: {model.g_best.solution}, Fitness: {model.g_best.target.fitness}")
References
~~~~~~~~~~
[1] Tanabe, R. and Fukunaga, A.S., 2014, July. Improving the search performance of SHADE using
linear population size reduction. In 2014 IEEE congress on evolutionary computation (CEC) (pp. 1658-1665). IEEE.
"""
def __init__(self, epoch: int = 750, pop_size: int = 100, miu_f: float = 0.5, miu_cr: float = 0.5, **kwargs: object) -> None:
"""
Args:
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
miu_f (float): initial weighting factor, default = 0.5
miu_cr (float): initial cross-over probability, default = 0.5
"""
super().__init__(**kwargs)
self.epoch = self.validator.check_int("epoch", epoch, [1, 100000])
self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 10000])
self.miu_f = self.validator.check_float("miu_f", miu_f, (0, 1.0))
self.miu_cr = self.validator.check_float("miu_cr", miu_cr, (0, 1.0))
self.set_parameters(["epoch", "pop_size", "miu_f", "miu_cr"])
self.sort_flag = False
[docs] def initialize_variables(self):
# Dynamic variable
self.dyn_miu_f = self.miu_f * np.ones(self.pop_size) # list the initial f,
self.dyn_miu_cr = self.miu_cr * np.ones(self.pop_size) # list the initial cr,
self.dyn_pop_archive = list()
self.dyn_pop_size = self.pop_size
self.k_counter = 0
self.n_min = int(self.pop_size / 5)
### Survivor Selection
[docs] def weighted_lehmer_mean__(self, list_objects, list_weights):
up = np.sum(list_weights * list_objects ** 2)
down = np.sum(list_weights * list_objects)
return up / down if down != 0 else 0.5
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
list_f = list()
list_cr = list()
list_f_index = list()
list_cr_index = list()
list_f_new = np.ones(self.pop_size)
list_cr_new = np.ones(self.pop_size)
pop_old = [agent.copy() for agent in self.pop]
pop_sorted = self.get_sorted_population(self.pop, self.problem.minmax)
pop = []
for idx in range(0, self.pop_size):
## Calculate adaptive parameter cr and f
idx_rand = self.generator.integers(0, self.pop_size)
cr = self.generator.normal(self.dyn_miu_cr[idx_rand], 0.1)
cr = np.clip(cr, 0, 1)
while True:
f = cauchy.rvs(self.dyn_miu_f[idx_rand], 0.1)
if f < 0:
continue
elif f > 1:
f = 1
break
list_cr_new[idx] = cr
list_f_new[idx] = f
p = self.generator.uniform(0.15, 0.2)
top = int(np.ceil(self.dyn_pop_size * p))
x_best = pop_sorted[self.generator.integers(0, top)]
x_r1 = self.pop[self.generator.choice(list(set(range(0, self.dyn_pop_size)) - {idx}))]
new_pop = self.pop + self.dyn_pop_archive
while True:
x_r2 = new_pop[self.generator.integers(0, len(new_pop))]
if np.any(x_r2.solution - x_r1.solution) and np.any(x_r2.solution - self.pop[idx].solution):
break
x_new = self.pop[idx].solution + f * (x_best.solution - self.pop[idx].solution) + f * (x_r1.solution - x_r2.solution)
pos_new = np.where(self.generator.random(self.problem.n_dims) < cr, x_new, self.pop[idx].solution)
j_rand = self.generator.integers(0, self.problem.n_dims)
pos_new[j_rand] = x_new[j_rand]
pos_new = self.correct_solution(pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
pop[-1].target = self.get_target(pos_new)
pop = self.update_target_for_population(pop)
for idx in range(0, self.pop_size):
if self.compare_target(pop[idx].target, self.pop[idx].target, self.problem.minmax):
list_cr.append(list_cr_new[idx])
list_f.append(list_f_new[idx])
list_f_index.append(idx)
list_cr_index.append(idx)
self.pop[idx] = pop[idx].copy()
self.dyn_pop_archive.append(self.pop[idx].copy())
# Randomly remove solution
temp = len(self.dyn_pop_archive) - self.pop_size
if temp > 0:
idx_list = self.generator.choice(range(0, len(self.dyn_pop_archive)), temp, replace=False)
archive_pop_new = []
for idx, agent in enumerate(self.dyn_pop_archive):
if idx not in idx_list:
archive_pop_new.append(agent.copy())
self.dyn_pop_archive = archive_pop_new
# Update miu_cr and miu_f
if len(list_f) != 0 and len(list_cr) != 0:
# Eq.13, 14, 10
list_fit_old = np.ones(len(list_cr_index))
list_fit_new = np.ones(len(list_cr_index))
idx_increase = 0
for idx in range(0, self.dyn_pop_size):
if idx in list_cr_index:
list_fit_old[idx_increase] = pop_old[idx].target.fitness
list_fit_new[idx_increase] = self.pop[idx].target.fitness
idx_increase += 1
total_fit = np.sum(np.abs(list_fit_new - list_fit_old))
list_weights = 0 if total_fit == 0 else np.abs(list_fit_new - list_fit_old) / total_fit
self.dyn_miu_cr[self.k_counter] = np.sum(list_weights * np.array(list_cr))
self.dyn_miu_f[self.k_counter] = self.weighted_lehmer_mean__(np.array(list_f), list_weights)
self.k_counter += 1
if self.k_counter >= self.dyn_pop_size:
self.k_counter = 0
# Linear Population Size Reduction
self.dyn_pop_size = round(self.pop_size + epoch * ((self.n_min - self.pop_size) / self.epoch))