import copy
import functools
import random
import numpy as np
from detpy.DETAlgs.base import BaseAlg
from detpy.DETAlgs.data.alg_data import EPSDEAGData
from detpy.DETAlgs.methods.methods_de import crossing
from detpy.DETAlgs.methods.methods_eps_de import calculate_epsilon_constrained
from detpy.DETAlgs.methods.methods_eps_deag import mutation, selection, calculate_init_epsilon_level, \
epsilon_constrained_comparator, gradient_mutation, calculate_epsilon_level
from detpy.models.enums.boundary_constrain import fix_boundary_constraints
from detpy.models.enums.crossingtype import CrossingType
from detpy.models.population import Population
[docs]
class EPSDEAG(BaseAlg):
"""
EPSDEAG - Epsilon Constrained Differential Evolution with an Archive and Gradient-Based Mutation
Links:
https://ieeexplore.ieee.org/document/5586484
References:
Tetsuyuki Takahama; Setsuko Sakai;
"Constrained optimization by the ε constrained differential evolution with an archive and gradient-based mutation",
2010 IEEE Congress on Evolutionary Computation,
18-23 July 2010, Barcelona, Spain doi: 10.1109/CEC.2010.5586484.
"""
def __init__(self, params: EPSDEAGData, db_conn=None, db_auto_write=False):
super().__init__(EPSDEAG.__name__, params, db_conn, db_auto_write)
self.number_of_repeating_de_operations = params.number_of_repeating_de_operations
self.gradient_mutation_interval = params.gradient_mutation_interval
self.derivative_method = params.derivative_method
self.init_mutation_factor = params.init_mutation_factor # F
self.init_crossover_rate = params.init_crossover_rate # Cr
self.g_funcs = params.g_funcs # Inequality constraints functions
self.h_funcs = params.h_funcs # Equality constraints functions
self.tolerance_h = params.tolerance_h
self.theta = params.theta
self.penalty_power = params.penalty_power
self.archive_size = params.archive_size
self.init_epsilon_level = None
self.archive_members = None
self.control_generations = params.control_generations
self.gradient_base_mutation_rate = params.gradient_base_mutation_rate
self.number_of_repeating_mutation = params.number_of_repeating_mutation
self.min_epsilon_scaling_factory = 3
self._initialize_population()
self.epsilon_constrained = calculate_epsilon_constrained(self._pop, self.g_funcs, self.h_funcs,
self.penalty_power, self.tolerance_h)
self.epsilon_level = self.init_epsilon_level
calculate_epsilon_scaling_factory = (-5 - np.log(self.init_epsilon_level)) / np.log(0.05)
self.epsilon_scaling_factor = calculate_epsilon_scaling_factory \
if calculate_epsilon_scaling_factory >= self.min_epsilon_scaling_factory else self.min_epsilon_scaling_factory
def _initialize_population(self):
population = Population(
lb=self.lb,
ub=self.ub,
arg_num=self.nr_of_args,
size=self.archive_size,
optimization=self.optimization_type
)
population.generate_population()
population.update_fitness_values(self._function.eval, self.parallel_processing)
epsilon_constrained = calculate_epsilon_constrained(population, self.g_funcs, self.h_funcs, self.penalty_power,
self.tolerance_h)
self.init_epsilon_level = calculate_init_epsilon_level(epsilon_constrained, self.theta)
archive_members = sorted(population.members, key=functools.cmp_to_key(
functools.partial(
epsilon_constrained_comparator,
g_funcs=self.g_funcs, h_funcs=self.h_funcs,
penalty_power=self.penalty_power,
epsilon_level=self.init_epsilon_level,
optimization=self.optimization_type,
tolerance_h=self.tolerance_h)))
self._pop.members = copy.deepcopy(np.array(archive_members[:self.population_size]))
self.archive_members = archive_members[self.population_size:]
[docs]
def next_epoch(self):
mutation_factory = self.init_mutation_factor
crossover_rate = self.init_crossover_rate
if self._epoch_number > (self.control_generations * 0.95) and self._epoch_number < self.control_generations:
mutation_factory = 0.3 * self.init_crossover_rate + 0.7
self.epsilon_scaling_factor = 0.3 * self.epsilon_scaling_factor + 0.7 * self.min_epsilon_scaling_factory
if random.uniform(0, 1) < 0.05:
mutation_factory = min(1 + abs(np.random.normal(0, 0.05)), 1.1)
crossover_rate = random.uniform(0, 1)
new_pop = self._pop
selected_child = set()
gradient_mutation_flag = self._epoch_number % self.gradient_mutation_interval == 0
for i in range(self.number_of_repeating_de_operations):
# New population after mutation
v_pop = mutation(new_pop, self.archive_members, mutation_factory)
# Apply boundary constrains on population in place
fix_boundary_constraints(v_pop, self.boundary_constraints_fun)
# New population after crossing
u_pop = crossing(new_pop, v_pop, cr=crossover_rate, crossing_type=CrossingType.EXPOTENTIAL)
gradient_mutation_pop = gradient_mutation(u_pop, self.number_of_repeating_mutation,
self.gradient_base_mutation_rate,
self.derivative_method, self.g_funcs, self.h_funcs,
self.penalty_power, gradient_mutation_flag,
self.boundary_constraints_fun, self.tolerance_h)
# Update values before selection
gradient_mutation_pop.update_fitness_values(self._function.eval, self.parallel_processing)
u_gradient_epsilon_constrained = calculate_epsilon_constrained(gradient_mutation_pop, self.g_funcs,
self.h_funcs, self.penalty_power,
self.tolerance_h)
# Select new population
new_pop = selection(new_pop, gradient_mutation_pop, self.epsilon_constrained,
u_gradient_epsilon_constrained, self.archive_members, selected_child,
self.epsilon_level)
if len(selected_child) == 0:
break
self.epsilon_level = calculate_epsilon_level(self.init_epsilon_level, self._epoch_number,
self.control_generations, self.epsilon_scaling_factor)
self.epsilon_constrained = calculate_epsilon_constrained(new_pop, self.g_funcs, self.h_funcs,
self.penalty_power, self.tolerance_h)
# Override data
self._pop = new_pop