# encoding=utf8
import numpy as np
import logging
from niapy.algorithms.algorithm import Algorithm
logging.basicConfig()
logger = logging.getLogger('niapy.algorithms.basic')
logger.setLevel('INFO')
__all__ = ['ClonalSelectionAlgorithm']
[docs]class ClonalSelectionAlgorithm(Algorithm):
r"""Implementation of Clonal Selection Algorithm.
Algorithm:
Clonal selection algorithm
Date:
2021
Authors:
Andraž Peršon
License:
MIT
Reference paper:
Brownlee, J. "Clever Algorithms: Nature-Inspired Programming Recipes" Revision 2. 2012. 280-286.
Attributes:
population_size (int): Population size.
clone_factor (float): Clone factor.
mutation_factor (float): Mutation factor.
num_rand (int): Number of random antibodies to be added to the population each generation.
bits_per_param (int): Number of bits per parameter of solution vector.
See Also:
* :class:`niapy.algorithms.Algorithm`
"""
Name = ['ClonalSelectionAlgorithm', 'CLONALG']
[docs] @staticmethod
def info():
r"""Get algorithms information.
Returns:
str: Algorithm information.
See Also:
* :func:`niapy.algorithms.Algorithm.info`
"""
return r"""Brownlee, J. "Clever Algorithms: Nature-Inspired Programming Recipes" Revision 2. 2012. 280-286."""
[docs] def __init__(self, population_size=10, clone_factor=0.1, mutation_factor=-2.5, num_rand=1, bits_per_param=16, *args,
**kwargs):
"""Initialize ClonalSelectionAlgorithm.
Args:
population_size (Optional[int]): Population size.
clone_factor (Optional[float]): Clone factor.
mutation_factor (Optional[float]): Mutation factor.
num_rand (Optional[int]): Number of random antibodies to be added to the population each generation.
bits_per_param (Optional[int]): Number of bits per parameter of solution vector.
See Also:
:func:`niapy.algorithms.Algorithm.__init__`
"""
super().__init__(population_size, *args, **kwargs)
self.clone_factor = clone_factor
self.num_clones = int(self.population_size * self.clone_factor)
self.mutation_factor = mutation_factor
self.num_rand = num_rand
self.bits_per_param = bits_per_param
[docs] def set_parameters(self, population_size=10, clone_factor=0.1, mutation_factor=-2.5, num_rand=1, bits_per_param=16,
**kwargs):
r"""Set the parameters of the algorithm.
Args:
population_size (Optional[int]): Population size.
clone_factor (Optional[float]): Clone factor.
mutation_factor (Optional[float]): Mutation factor.
num_rand (Optional[int]): Random number.
bits_per_param (Optional[int]): Number of bits per parameter of solution vector.
See Also:
* :func:`niapy.algorithms.Algorithm.set_parameters`
"""
super().set_parameters(population_size=population_size, **kwargs)
self.clone_factor = clone_factor
self.num_clones = int(self.population_size * self.clone_factor)
self.mutation_factor = mutation_factor
self.num_rand = num_rand
self.bits_per_param = bits_per_param
[docs] def get_parameters(self):
r"""Get parameters of the algorithm.
Returns:
Dict[str, Any]: Algorithm parameters.
"""
params = super().get_parameters()
params.update({
'clone_factor': self.clone_factor,
'mutation_factor': self.mutation_factor,
'num_rand': self.num_rand,
'bits_per_param': self.bits_per_param,
})
return params
[docs] def decode(self, bitstrings, task):
bits = np.flip(np.arange(self.bits_per_param))
z = np.sum(bitstrings * 2 ** bits, axis=-1)
return task.lower + task.range * z / (2 ** self.bits_per_param - 1)
[docs] def evaluate(self, bitstrings, task):
population = self.decode(bitstrings, task)
fitness = np.apply_along_axis(task.eval, 1, population)
return population, fitness
[docs] def mutate(self, bitstring, mutation_rate):
flip = self.random(bitstring.shape) < mutation_rate
bitstring[flip] = np.logical_not(bitstring[flip])
return bitstring
[docs] def clone_and_hypermutate(self, bitstrings, population, population_fitness, task):
range_ = np.max(population_fitness) - np.min(population_fitness)
affinity = np.ones(self.population_size) if range_ == 0.0 else 1.0 - (population_fitness / range_)
clones = np.repeat(bitstrings, self.num_clones, axis=0)
for i in range(clones.shape[0]):
mutation_rate = np.exp(self.mutation_factor * affinity[i // self.num_clones])
clones[i] = self.mutate(clones[i], mutation_rate)
clones_pop, clones_fitness = self.evaluate(clones, task)
all_bitstrings = np.concatenate((bitstrings, clones), axis=0)
all_population = np.concatenate((population, clones_pop), axis=0)
all_fitness = np.concatenate((population_fitness, clones_fitness))
sorted_ind = np.argsort(all_fitness)
new_bitstrings = all_bitstrings[sorted_ind][:self.population_size]
new_population = all_population[sorted_ind][:self.population_size]
new_fitness = all_fitness[sorted_ind][:self.population_size]
return new_bitstrings, new_population, new_fitness
[docs] def random_insertion(self, bitstrings, population, population_fitness, task):
if self.num_rand == 0:
return bitstrings, population, population_fitness
new_bitstrings = self.random((self.num_rand, task.dimension, self.bits_per_param)) > 0.5
new_population, new_fitness = self.evaluate(new_bitstrings, task)
all_bitstrings = np.concatenate((bitstrings, new_bitstrings), axis=0)
all_population = np.concatenate((population, new_population), axis=0)
all_fitness = np.concatenate((population_fitness, new_fitness))
sorted_ind = np.argsort(all_fitness)
next_bitstrings = all_bitstrings[sorted_ind][:self.population_size]
next_population = all_population[sorted_ind][:self.population_size]
next_fitness = all_fitness[sorted_ind][:self.population_size]
return next_bitstrings, next_population, next_fitness
[docs] def init_population(self, task):
r"""Initialize the starting population.
Parameters:
task (Task): Optimization task
Returns:
Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]:
1. New population.
2. New population fitness/function values.
3. Additional arguments:
* bitstring (numpy.ndarray): Binary representation of the population.
See Also:
* :func:`niapy.algorithms.Algorithm.init_population`
"""
bitstrings = self.random((self.population_size, task.dimension, self.bits_per_param)) > 0.5
population, fitness = self.evaluate(bitstrings, task)
return population, fitness, {'bitstrings': bitstrings}
[docs] def run_iteration(self, task, population, population_fitness, best_x, best_fitness, **params):
r"""Core function of Clonal Selection Algorithm.
Parameters:
task (Task): Optimization task.
population (numpy.ndarray): Current population
population_fitness (numpy.ndarray[float]): Current population fitness/function values
best_x (numpy.ndarray): Current best individual
best_fitness (float): Current best individual function/fitness value
params (Dict[str, Any]): Additional algorithm arguments
Returns:
Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, float, Dict[str, Any]]:
1. New population
2. New population fitness/function values
3. New global best solution
4. New global best fitness/objective value
5. Additional arguments:
* bitstring (numpy.ndarray): Binary representation of the population.
"""
bitstrings = params.pop('bitstrings')
bitstrings, population, population_fitness = self.clone_and_hypermutate(bitstrings, population,
population_fitness, task)
best_x, best_fitness = self.get_best(population, population_fitness, best_x, best_fitness)
bitstrings, population, population_fitness = self.random_insertion(bitstrings, population, population_fitness,
task)
best_x, best_fitness = self.get_best(population, population_fitness, best_x, best_fitness)
return population, population_fitness, best_x, best_fitness, {'bitstrings': bitstrings}