#!/usr/bin/env python
# Created by "Thieu" at 09:48, 16/03/2020 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
from scipy.stats import cauchy
from copy import deepcopy
from mealpy.utils.agent import Agent
[docs]class OriginalDE(Optimizer):
"""
The original version of: Differential Evolution (DE)
Links:
1. https://doi.org/10.1016/j.swevo.2018.10.006
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ wf (float): [-1., 1.0], weighting factor, default = 0.1
+ cr (float): [0.5, 0.95], crossover rate, default = 0.9
+ strategy (int): [0, 5], there are lots of variant version of DE algorithm,
+ 0: DE/current-to-rand/1/bin
+ 1: DE/best/1/bin
+ 2: DE/best/2/bin
+ 3: DE/rand/2/bin
+ 4: DE/current-to-best/1/bin
+ 5: DE/current-to-rand/1/bin
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, DE
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = DE.OriginalDE(epoch=1000, pop_size=50, wf = 0.7, cr = 0.9, strategy = 0)
>>> 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] Mohamed, A.W., Hadi, A.A. and Jambi, K.M., 2019. Novel mutation strategy for enhancing SHADE and
LSHADE algorithms for global numerical optimization. Swarm and Evolutionary Computation, 50, p.100455.
"""
def __init__(self, epoch: int = 10000, pop_size: int = 100, wf: float = 0.1, cr: float = 0.9, strategy: int = 0, **kwargs: object) -> None:
"""
Args:
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
wf (float): weighting factor, default = 0.1
cr (float): crossover rate, default = 0.9
strategy (int): Different variants of DE, default = 0
"""
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.wf = self.validator.check_float("wf", wf, (-3.0, 3.0))
self.cr = self.validator.check_float("cr", cr, (0, 1.0))
self.strategy = self.validator.check_int("strategy", strategy, [0, 5])
self.set_parameters(["epoch", "pop_size", "wf", "cr", "strategy"])
self.sort_flag = False
[docs] def mutation__(self, current_pos, new_pos):
condition = self.generator.random(self.problem.n_dims) < self.cr
pos_new = np.where(condition, new_pos, current_pos)
return self.correct_solution(pos_new)
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
pop = []
if self.strategy == 0:
# Choose 3 random element and different to i
for idx in range(0, self.pop_size):
idx_list = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 3, replace=False)
pos_new = self.pop[idx_list[0]].solution + self.wf * (self.pop[idx_list[1]].solution - self.pop[idx_list[2]].solution)
pos_new = self.mutation__(self.pop[idx].solution, pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
elif self.strategy == 1:
for idx in range(0, self.pop_size):
idx_list = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 2, replace=False)
pos_new = self.g_best.solution + self.wf * (self.pop[idx_list[0]].solution - self.pop[idx_list[1]].solution)
pos_new = self.mutation__(self.pop[idx].solution, pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
elif self.strategy == 2:
for idx in range(0, self.pop_size):
idx_list = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 4, replace=False)
pos_new = self.g_best.solution + self.wf * (self.pop[idx_list[0]].solution - self.pop[idx_list[1]].solution) + \
self.wf * (self.pop[idx_list[2]].solution - self.pop[idx_list[3]].solution)
pos_new = self.mutation__(self.pop[idx].solution, pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
elif self.strategy == 3:
for idx in range(0, self.pop_size):
idx_list = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 5, replace=False)
pos_new = self.pop[idx_list[0]].solution + self.wf * (self.pop[idx_list[1]].solution - self.pop[idx_list[2]].solution) + \
self.wf * (self.pop[idx_list[3]].solution - self.pop[idx_list[4]].solution)
pos_new = self.mutation__(self.pop[idx].solution, pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
elif self.strategy == 4:
for idx in range(0, self.pop_size):
idx_list = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 2, replace=False)
pos_new = self.pop[idx].solution + self.wf * (self.g_best.solution - self.pop[idx].solution) + \
self.wf * (self.pop[idx_list[0]].solution - self.pop[idx_list[1]].solution)
pos_new = self.mutation__(self.pop[idx].solution, pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
else:
for idx in range(0, self.pop_size):
idx_list = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 3, replace=False)
pos_new = self.pop[idx].solution + self.wf * (self.pop[idx_list[0]].solution - self.pop[idx].solution) + \
self.wf * (self.pop[idx_list[1]].solution - self.pop[idx_list[2]].solution)
pos_new = self.mutation__(self.pop[idx].solution, pos_new)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
if self.mode in self.AVAILABLE_MODES:
pop = self.update_target_for_population(pop)
self.pop = self.greedy_selection_population(self.pop, pop, self.problem.minmax)
[docs]class JADE(Optimizer):
"""
The original version of: Differential Evolution (JADE)
Links:
1. https://doi.org/10.1109/TEVC.2009.2014613
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ miu_f (float): [0.4, 0.6], initial adaptive f, default = 0.5
+ miu_cr (float): [0.4, 0.6], initial adaptive cr, default = 0.5
+ pt (float): [0.05, 0.2], The percent of top best agents (p in the paper), default = 0.1
+ ap (float): [0.05, 0.2], The Adaptation Parameter control value of f and cr (c in the paper), default=0.1
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, DE
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = DE.JADE(epoch=1000, pop_size=50, miu_f = 0.5, miu_cr = 0.5, pt = 0.1, ap = 0.1)
>>> 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] Zhang, J. and Sanderson, A.C., 2009. JADE: adaptive differential evolution with optional
external archive. IEEE Transactions on evolutionary computation, 13(5), pp.945-958.
"""
def __init__(self, epoch: int = 10000, pop_size: int = 100, miu_f: float = 0.5,
miu_cr: float = 0.5, pt: float = 0.1, ap: float = 0.1, **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 adaptive f, default = 0.5
miu_cr (float): initial adaptive cr, default = 0.5
pt (float): The percent of top best agents (p in the paper), default = 0.1
ap (float): The Adaptation Parameter control value of f and cr (c in the paper), default=0.1
"""
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))
# np.random.uniform(0.05, 0.2) # the x_best is select from the top 100p % solutions
self.pt = self.validator.check_float("pt", pt, (0, 1.0))
# np.random.uniform(1/20, 1/5) # the adaptation parameter control value of f and cr
self.ap = self.validator.check_float("ap", ap, (0, 1.0))
self.set_parameters(["epoch", "pop_size", "miu_f", "miu_cr", "pt", "ap"])
self.sort_flag = False
[docs] def initialize_variables(self):
self.dyn_miu_cr = self.miu_cr
self.dyn_miu_f = self.miu_f
self.dyn_pop_archive = list()
### Survivor Selection
[docs] def lehmer_mean(self, list_objects):
temp = np.sum(list_objects)
return 0 if temp == 0 else np.sum(list_objects ** 2) / temp
[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()
temp_f = list()
temp_cr = list()
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
cr = self.generator.normal(self.dyn_miu_cr, 0.1)
cr = np.clip(cr, 0, 1)
while True:
f = cauchy.rvs(self.dyn_miu_f, 0.1)
if f < 0:
continue
elif f > 1:
f = 1
break
temp_f.append(f)
temp_cr.append(cr)
top = int(self.pop_size * self.pt)
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)
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):
self.dyn_pop_archive.append(self.pop[idx].copy())
list_cr.append(temp_cr[idx])
list_f.append(temp_f[idx])
self.pop[idx] = 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, solution in enumerate(self.dyn_pop_archive):
if idx not in idx_list:
archive_pop_new.append(solution)
self.dyn_pop_archive = deepcopy(archive_pop_new)
# Update miu_cr and miu_f
if len(list_cr) == 0:
self.dyn_miu_cr = (1 - self.ap) * self.dyn_miu_cr + self.ap * 0.5
else:
self.dyn_miu_cr = (1 - self.ap) * self.dyn_miu_cr + self.ap * np.mean(np.array(list_cr))
if len(list_f) == 0:
self.dyn_miu_f = (1 - self.ap) * self.dyn_miu_f + self.ap * 0.5
else:
self.dyn_miu_f = (1 - self.ap) * self.dyn_miu_f + self.ap * self.lehmer_mean(np.array(list_f))
[docs]class SADE(Optimizer):
"""
The original version of: Self-Adaptive Differential Evolution (SADE)
Links:
1. https://doi.org/10.1109/CEC.2005.1554904
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, DE
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = DE.SADE(epoch=1000, pop_size=50)
>>> 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] Qin, A.K. and Suganthan, P.N., 2005, September. Self-adaptive differential evolution algorithm for
numerical optimization. In 2005 IEEE congress on evolutionary computation (Vol. 2, pp. 1785-1791). IEEE.
"""
def __init__(self, epoch: int = 10000, pop_size: int = 100, **kwargs: object) -> None:
"""
Args:
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
"""
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.set_parameters(["epoch", "pop_size"])
self.sort_flag = False
[docs] def initialize_variables(self):
self.loop_probability = 50
self.loop_cr = 5
self.ns1 = self.ns2 = self.nf1 = self.nf2 = 0
self.crm = 0.5
self.p1 = 0.5
self.dyn_list_cr = list()
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
pop = []
list_probability = []
list_cr = []
for idx in range(0, self.pop_size):
## Calculate adaptive parameter cr and f
cr = self.generator.normal(self.crm, 0.1)
cr = np.clip(cr, 0, 1)
list_cr.append(cr)
while True:
f = self.generator.normal(0.5, 0.3)
if f < 0:
continue
elif f > 1:
f = 1
break
id1, id2, id3 = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 3, replace=False)
if self.generator.random() < self.p1:
x_new = self.pop[id1].solution + f * (self.pop[id2].solution - self.pop[id3].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)
list_probability.append(True)
else:
x_new = self.pop[idx].solution + f * (self.g_best.solution - self.pop[idx].solution) + \
f * (self.pop[id1].solution - self.pop[id2].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)
list_probability.append(False)
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 list_probability[idx]:
if self.compare_target(pop[idx].target, self.pop[idx].target, self.problem.minmax):
self.ns1 += 1
self.pop[idx] = pop[idx].copy()
else:
self.nf1 += 1
else:
if self.compare_target(pop[idx].target, self.pop[idx].target, self.problem.minmax):
self.ns2 += 1
self.dyn_list_cr.append(list_cr[idx])
self.pop[idx] = pop[idx].copy()
else:
self.nf2 += 1
# Update cr and p1
if epoch / self.loop_cr == 0:
self.crm = np.mean(self.dyn_list_cr)
self.dyn_list_cr = list()
if epoch / self.loop_probability == 0:
self.p1 = self.ns1 * (self.ns2 + self.nf2) / (self.ns2 * (self.ns1 + self.nf1) + self.ns1 * (self.ns2 + self.nf2))
self.ns1 = self.ns2 = self.nf1 = self.nf2 = 0
[docs]class SAP_DE(Optimizer):
"""
The original version of: Differential Evolution with Self-Adaptive Populations (SAP_DE)
Links:
1. https://doi.org/10.1007/s00500-005-0537-1
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ branch (str): ["ABS" or "REL"], gaussian (absolute) or uniform (relative) method
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, DE
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = DE.SAP_DE(epoch=1000, pop_size=50, branch = "ABS")
>>> 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] Teo, J., 2006. Exploring dynamic self-adaptive populations in differential evolution. Soft Computing, 10(8), pp.673-686.
"""
def __init__(self, epoch: int = 1000, pop_size: int = 100, branch: str = "ABS", **kwargs: object) -> None:
"""
Args:
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
branch (str): gaussian (absolute) or uniform (relative) method
"""
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.branch = self.validator.check_str("branch", branch, ["ABS", "REL"])
self.set_parameters(["epoch", "pop_size", "branch"])
self.fixed_pop_size = self.pop_size
self.sort_flag = False
[docs] def generate_empty_agent(self, solution: np.ndarray = None) -> Agent:
if solution is None:
solution = self.problem.generate_solution(encoded=True)
crossover_rate = self.generator.uniform(0, 1)
mutation_rate = self.generator.uniform(0, 1)
if self.branch == "ABS":
pop_size = int(10 * self.problem.n_dims + self.generator.normal(0, 1))
else: # elif self.branch == "REL":
pop_size = int(10 * self.problem.n_dims + self.generator.uniform(-0.5, 0.5))
return Agent(solution=solution, crossover=crossover_rate, mutation=mutation_rate, pop_size=pop_size)
[docs] def edit_to_range__(self, var=None, lower=0, upper=1, func_value=None):
while var <= lower or var >= upper:
if var <= lower:
var += func_value()
if var >= upper:
var -= func_value()
return var
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
pop = []
for idx in range(0, self.pop_size):
# Choose 3 random element and different to idx
idxs = self.generator.choice(list(set(range(0, self.pop_size)) - {idx}), 3, replace=False)
j = self.generator.integers(0, self.pop_size)
self.F = self.generator.normal(0, 1)
## Crossover
if self.generator.uniform(0, 1) < self.pop[idx].crossover or idx == j:
pos_new = self.pop[idxs[0]].solution + self.F * (self.pop[idxs[1]].solution - self.pop[idxs[2]].solution)
cr_new = self.pop[idxs[0]].crossover + self.F * (self.pop[idxs[1]].crossover - self.pop[idxs[2]].crossover)
mr_new = self.pop[idxs[0]].mutation + self.F * (self.pop[idxs[1]].mutation - self.pop[idxs[2]].mutation)
if self.branch == "ABS":
ps_new = self.pop[idxs[0]].pop_size + int(self.F * (self.pop[idxs[1]].pop_size - self.pop[idxs[2]].pop_size))
else: # elif self.branch == "REL":
ps_new = self.pop[idxs[0]].pop_size + self.F * (self.pop[idxs[1]].pop_size - self.pop[idxs[2]].pop_size)
pos_new = self.correct_solution(pos_new)
cr_new = self.edit_to_range__(cr_new, 0, 1, self.generator.random)
mr_new = self.edit_to_range__(mr_new, 0, 1, self.generator.random)
agent = self.generate_empty_agent(pos_new)
pop.append(agent)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
agent.update(crossover=cr_new, mutation=mr_new, pop_size=ps_new)
else:
pop.append(self.pop[idx].copy())
## Mutation
if self.generator.uniform(0, 1) < self.pop[idxs[0]].mutation:
pos_new = self.pop[idx].solution + self.generator.normal(0, self.pop[idxs[0]].mutation)
cr_new = self.generator.normal(0, 1)
mr_new = self.generator.normal(0, 1)
if self.branch == "ABS":
ps_new = self.pop[idx].pop_size + int(self.generator.normal(0.5, 1))
else: # elif self.branch == "REL":
ps_new = self.pop[idx].pop_size + self.generator.normal(0, self.pop[idxs[0]].mutation)
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:
agent.target = self.get_target(pos_new)
agent.update(crossover=cr_new, mutation=mr_new, pop_size=ps_new)
pop = self.update_target_for_population(pop)
# Calculate new population size
total = np.sum([pop[idx].pop_size for idx in range(0, self.pop_size)])
if self.branch == "ABS":
m_new = int(total / self.pop_size)
else: # elif self.branch == "REL":
m_new = int(self.pop_size + total)
if m_new <= 4:
m_new = self.fixed_pop_size + int(self.generator.uniform(0, 4))
elif m_new > 4 * self.fixed_pop_size:
m_new = self.fixed_pop_size - int(self.generator.uniform(0, 4))
## Change population by population size
if m_new <= self.pop_size:
self.pop = pop[:m_new]
else:
pop_sorted = self.get_sorted_population(pop, self.problem.minmax)
self.pop = pop + pop_sorted[:m_new - self.pop_size]
self.pop_size = len(self.pop)