# Source code for niapy.algorithms.basic.clonalg

```# 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

MIT

Reference papers:
* \L\. \N\. de Castro and F. J. Von Zuben. Learning and optimization using the clonal selection principle. IEEE Transactions on Evolutionary Computation, 6:239–251, 2002.
* 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.

* :class:`niapy.algorithms.Algorithm`

"""

Name = ['ClonalSelectionAlgorithm', 'CLONALG']

[docs]    @staticmethod
def info():
r"""Get algorithms information.

Returns:
str: Algorithm information.

* :func:`niapy.algorithms.Algorithm.info`

"""
return r"""L. N. de Castro and F. J. Von Zuben. Learning and optimization using the clonal selection principle. IEEE Transactions on Evolutionary Computation, 6:239–251, 2002."""

[docs]    def __init__(self, population_size=10, clone_factor=0.1, mutation_factor=10.0, 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.

: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=10.0, 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.

* :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

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)

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):
clones = np.repeat(bitstrings, self.num_clones, axis=0)
for i in range(clones.shape[0]):
mutation_rate = np.exp(-self.mutation_factor * population_fitness[i // self.num_clones])
clones[i] = self.mutate(clones[i], mutation_rate)

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

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

r"""Initialize the starting population.

Parameters:

Returns:
Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]:
1. New population.
2. New population fitness/function values.
* bitstring (numpy.ndarray): Binary representation of the population.

* :func:`niapy.algorithms.Algorithm.init_population`

"""
bitstrings = self.random((self.population_size, task.dimension, self.bits_per_param)) > 0.5
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:
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
* bitstring (numpy.ndarray): Binary representation of the population.

"""
bitstrings = params.pop('bitstrings')

bitstrings, population, population_fitness = self.clone_and_hypermutate(bitstrings, population,