"""Genetic Algorithm optimiser module."""
from typing import Tuple, Callable, Optional
import sys
import time
import numpy as np
try:
from geneticalgorithm import geneticalgorithm
except ImportError:
# Show a nice exception when this package is used
from piglot.optimiser import missing_method
geneticalgorithm = missing_method("Genetic algorithm", "geneticalgorithm")
from piglot.objective import Objective
from piglot.optimiser import ScalarOptimiser
[docs]
class geneticalgorithmMod(geneticalgorithm):
[docs]
def run(self, optimiser, init_shot):
# Initial Population
self.integers=np.where(self.var_type=='int')
self.reals=np.where(self.var_type=='real')
pop=np.array([np.zeros(self.dim+1)]*self.pop_s)
solo=np.zeros(self.dim+1)
var=np.zeros(self.dim)
for p in range(0,self.pop_s):
for i in self.integers[0]:
var[i]=np.random.randint(self.var_bound[i][0],
self.var_bound[i][1]+1)
solo[i]=var[i].copy()
for i in self.reals[0]:
var[i]=self.var_bound[i][0]+np.random.random()*\
(self.var_bound[i][1]-self.var_bound[i][0])
solo[i]=var[i].copy()
# Use initial shot
if p==0:
var = init_shot
solo[0:self.dim] = var.copy()
obj=self.sim(var)
solo[self.dim]=obj
pop[p]=solo.copy()
# Report
self.report=[]
self.test_obj=obj
self.best_variable=var.copy()
self.best_function=obj
t=1
counter=0
while t<=self.iterate:
if self.progress_bar==True:
self.progress(t,self.iterate,status="GA is running...")
#Sort
pop = pop[pop[:,self.dim].argsort()]
if pop[0,self.dim]<self.best_function:
counter=0
self.best_function=pop[0,self.dim].copy()
self.best_variable=pop[0,: self.dim].copy()
else:
counter+=1
# Report
self.report.append(pop[0,self.dim])
# Normalizing objective function
normobj=np.zeros(self.pop_s)
minobj=pop[0,self.dim]
if minobj<0:
normobj=pop[:,self.dim]+abs(minobj)
else:
normobj=pop[:,self.dim].copy()
maxnorm=np.amax(normobj)
normobj=maxnorm-normobj+1
# Calculate probability
sum_normobj=np.sum(normobj)
prob=np.zeros(self.pop_s)
prob=normobj/sum_normobj
cumprob=np.cumsum(prob)
# Select parents
par=np.array([np.zeros(self.dim+1)]*self.par_s)
for k in range(0,self.num_elit):
par[k]=pop[k].copy()
for k in range(self.num_elit,self.par_s):
index=np.searchsorted(cumprob,np.random.random())
par[k]=pop[index].copy()
ef_par_list=np.array([False]*self.par_s)
par_count=0
while par_count==0:
for k in range(0,self.par_s):
if np.random.random()<=self.prob_cross:
ef_par_list[k]=True
par_count+=1
ef_par=par[ef_par_list].copy()
#New generation
pop=np.array([np.zeros(self.dim+1)]*self.pop_s)
for k in range(0,self.par_s):
pop[k]=par[k].copy()
for k in range(self.par_s, self.pop_s, 2):
r1=np.random.randint(0,par_count)
r2=np.random.randint(0,par_count)
pvar1=ef_par[r1,: self.dim].copy()
pvar2=ef_par[r2,: self.dim].copy()
ch=self.cross(pvar1,pvar2,self.c_type)
ch1=ch[0].copy()
ch2=ch[1].copy()
ch1=self.mut(ch1)
ch2=self.mutmidle(ch2,pvar1,pvar2)
solo[: self.dim]=ch1.copy()
obj=self.sim(ch1)
solo[self.dim]=obj
pop[k]=solo.copy()
solo[: self.dim]=ch2.copy()
obj=self.sim(ch2)
solo[self.dim]=obj
pop[k+1]=solo.copy()
# Check progress
if optimiser._progress_check(t, self.best_function, self.best_variable):
break
t+=1
if counter > self.mniwi:
pop = pop[pop[:,self.dim].argsort()]
if pop[0,self.dim]>=self.best_function:
t=self.iterate
if self.progress_bar==True:
self.progress(t,self.iterate,status="GA is running...")
time.sleep(2)
t+=1
self.stop_mniwi=True
#Sort
pop = pop[pop[:,self.dim].argsort()]
if pop[0,self.dim]<self.best_function:
self.best_function=pop[0,self.dim].copy()
self.best_variable=pop[0,: self.dim].copy()
# Report
self.report.append(pop[0,self.dim])
self.output_dict={'variable': self.best_variable, 'function': self.best_function}
if self.progress_bar==True:
show=' '*100
sys.stdout.write('\r%s' % (show))
#sys.stdout.write('\r The best solution found:\n %s' % (self.best_variable))
#sys.stdout.write('\n\n Objective function:\n %s\n' % (self.best_function))
#sys.stdout.flush()
re=np.array(self.report)
if self.stop_mniwi==True:
sys.stdout.write('\nWarning: GA is terminated due to the'+
' maximum number of iterations without improvement was met!')
[docs]
class GA(ScalarOptimiser):
"""
Genetic Algorithm optimiser.
Documentation: https://pypi.org/project/geneticalgorithm/
Attributes
----------
variable_type : string
'bool' if all variables are Boolean;
'int' if all variables are integer;
and 'real' if all variables are real value or continuous (for mixed type see
parameter variable_type_mixed)
variable_type_mixed : numpy array/None
Default None; leave it None if all variables have the same type;
otherwise this can be used to specify the type of each variable separately.
For example if the first variable is integer but the second one is real the
input is: np.array(['int'],['real']). NOTE: it does not accept 'bool'.
If variable type is Boolean use 'int' and provide a boundary as [0,1] in
variable_boundaries. Also if variable_type_mixed is applied,
variable_boundaries has to be defined.
function_timeout : float
if the given function does not provide output before function_timeout (unit is
seconds) the algorithm raise error. For example, when there is an infinite
loop in the given function.
algorithm_parameters : dictionary
Algorithm parameters.
convergence_curve : True/False
Plot the convergence curve or not. Default is True.
progress_bar : True/False
Show progress bar or not. Default is True.
Methods
-------
_optimise(self, func, n_dim, n_iter, bound, init_shot):
Solves the optimization problem
"""
def __init__(
self,
objective: Objective,
variable_type='real',
variable_type_mixed=None,
function_timeout=3600,
algorithm_parameters={
'max_num_iteration': None,
'population_size': 100,
'mutation_probability': 0.1,
'elit_ratio': 0.01,
'crossover_probability': 0.5,
'parents_portion': 0.3,
'crossover_type': 'uniform',
'max_iteration_without_improv': None,
},
convergence_curve=True,
progress_bar=False,
):
"""
Constructs all the necessary attributes for the Genetic Algorithm optimiser
Parameters
----------
objective : Objective
Objective function to optimise.
variable_type : string
'bool' if all variables are Boolean;
'int' if all variables are integer;
and 'real' if all variables are real value or continuous (for mixed type see
parameter variable_type_mixed)
variable_type_mixed : numpy array/None
Default None; leave it None if all variables have the same type;
otherwise this can be used to specify the type of each variable separately.
For example if the first variable is integer but the second one is real the
input is: np.array(['int'],['real']). NOTE: it does not accept 'bool'.
If variable type is Boolean use 'int' and provide a boundary as [0,1] in
variable_boundaries. Also if variable_type_mixed is applied,
variable_boundaries has to be defined.
function_timeout : float
if the given function does not provide output before function_timeout (unit is
seconds) the algorithm raise error. For example, when there is an infinite
loop in the given function.
algorithm_parameters : dictionary
max_num_iteration : int
population_size : int
mutation_probability : float in [0,1]
elit_ration : float in [0,1]
crossover_probability : float in [0,1]
parents_portion : float in [0,1]
crossover_type : string
Default is 'uniform'; 'one_point' or two_point' are other options
max_iteration_without_improv : int
maximum number of successive iterations without improvement. If None it is
ineffective
convergence_curve : True/False
Plot the convergence curve or not. Default is True.
progress_bar : True/False
Show progress bar or not. Default is True.
"""
super().__init__('GA', objective)
self.variable_type = variable_type
self.variable_type_mixed = variable_type_mixed
self.function_timeout = function_timeout
self.algorithm_parameters = algorithm_parameters
self.convergence_curve = convergence_curve
self.progress_bar = progress_bar
def _scalar_optimise(
self,
objective: Callable[[np.ndarray, Optional[bool]], float],
n_dim: int,
n_iter: int,
bound: np.ndarray,
init_shot: np.ndarray,
) -> Tuple[float, np.ndarray]:
"""
Abstract method for optimising the objective.
Parameters
----------
objective : Callable[[np.ndarray], float]
Objective function to optimise.
n_dim : int
Number of parameters to optimise.
n_iter : int
Maximum number of iterations.
bound : np.ndarray
Array where first and second columns correspond to lower and upper bounds, respectively.
init_shot : np.ndarray
Initial shot for the optimisation problem.
Returns
-------
float
Best observed objective value.
np.ndarray
Observed optimum of the objective.
"""
self.algorithm_parameters['max_num_iteration'] = n_iter
model = geneticalgorithmMod(objective, n_dim, self.variable_type, bound,
self.variable_type_mixed,
self.function_timeout, self.algorithm_parameters,
self.convergence_curve, self.progress_bar)
model.run(self, init_shot)
return model.output_dict.get('variable'), model.output_dict.get('function')