# encoding=utf8
import numpy as np
from niapy.algorithms.algorithm import Algorithm, Individual
from niapy.util import objects_to_array
class Fish(Individual):
r"""Fish individual class.
Attributes:
weight (float): Weight of fish.
delta_pos (float): Displacement due to individual movement.
delta_cost (float): Cost at `delta_apos`.
has_improved (bool): True if the fish has improved.
See Also:
* :class:`niapy.algorithms.algorithm.Individual`
"""
def __init__(self, weight, **kwargs):
r"""Initialize fish individual.
Args:
weight (float): Weight of fish.
See Also:
* :func:`niapy.algorithms.algorithm.Individual`
"""
super().__init__(**kwargs)
self.weight = weight
self.delta_pos = np.nan
self.delta_cost = np.nan
self.has_improved = False
[docs]class FishSchoolSearch(Algorithm):
r"""Implementation of Fish School Search algorithm.
Algorithm:
Fish School Search algorithm
Date:
2019
Authors:
Clodomir Santana Jr, Elliackin Figueredo, Mariana Maceds, Pedro Santos.
Ported to niapy with small changes by Kristian Järvenpää (2018).
Ported to niapy 2.0 by Klemen Berkovič (2019).
License:
MIT
Reference paper:
Bastos Filho, Lima Neto, Lins, D. O. Nascimento and P. Lima,
“A novel search algorithm based on fish school behavior,”
in 2008 IEEE International Conference on Systems, Man and Cybernetics, Oct 2008, pp. 2646–2651.
Attributes:
Name (List[str]): List of strings representing algorithm name.
step_individual_init (float): Length of initial individual step.
step_individual_final (float): Length of final individual step.
step_volitive_init (float): Length of initial volatile step.
step_volitive_final (float): Length of final volatile step.
min_w (float): Minimum weight of a fish.
w_scale (float): Maximum weight of a fish.
See Also:
* :class:`niapy.algorithms.algorithm.Algorithm`
"""
Name = ['FSS', 'FishSchoolSearch']
[docs] @staticmethod
def info():
r"""Get default information of algorithm.
Returns:
str: Basic information.
See Also:
* :func:`niapy.algorithms.Algorithm.info`
"""
return r"""Bastos Filho, Lima Neto, Lins, D. O. Nascimento and P. Lima,
“A novel search algorithm based on fish school behavior,”
in 2008 IEEE International Conference on Systems, Man and Cybernetics, Oct 2008, pp. 2646–2651."""
[docs] def __init__(self, population_size=30, step_individual_init=0.1, step_individual_final=0.0001,
step_volitive_init=0.01, step_volitive_final=0.001, min_w=1.0, w_scale=500.0, *args, **kwargs):
"""Initialize FishSchoolSearch.
Args:
population_size (Optional[int]): Number of fishes in school.
step_individual_init (Optional[float]): Length of initial individual step.
step_individual_final (Optional[float]): Length of final individual step.
step_volitive_init (Optional[float]): Length of initial volatile step.
step_volitive_final (Optional[float]): Length of final volatile step.
min_w (Optional[float]): Minimum weight of a fish.
w_scale (Optional[float]): Maximum weight of a fish. Recommended value: max_iterations / 2
See Also:
* :func:`niapy.algorithms.Algorithm.__init__`
"""
super().__init__(population_size, *args, **kwargs)
self.step_individual_init = step_individual_init
self.step_individual_final = step_individual_final
self.step_volitive_init = step_volitive_init
self.step_volitive_final = step_volitive_final
self.min_w = min_w
self.w_scale = w_scale
[docs] def set_parameters(self, population_size=30, step_individual_init=0.1, step_individual_final=0.0001,
step_volitive_init=0.01, step_volitive_final=0.001, min_w=1.0, w_scale=5000.0, **kwargs):
r"""Set core arguments of FishSchoolSearch algorithm.
Args:
population_size (Optional[int]): Number of fishes in school.
step_individual_init (Optional[float]): Length of initial individual step.
step_individual_final (Optional[float]): Length of final individual step.
step_volitive_init (Optional[float]): Length of initial volatile step.
step_volitive_final (Optional[float]): Length of final volatile step.
min_w (Optional[float]): Minimum weight of a fish.
w_scale (Optional[float]): Maximum weight of a fish. Recommended value: max_iterations / 2
See Also:
* :func:`niapy.algorithms.Algorithm.set_parameters`
"""
super().set_parameters(population_size=population_size, **kwargs)
self.step_individual_init = step_individual_init
self.step_individual_final = step_individual_final
self.step_volitive_init = step_volitive_init
self.step_volitive_final = step_volitive_final
self.min_w = min_w
self.w_scale = w_scale
[docs] def get_parameters(self):
r"""Get algorithm parameters.
Returns:
Dict[str, Any]: Algorithm parameters.
See Also:
* :func:`niapy.algorithms.Algorithm.set_parameters`
"""
d = super().get_parameters()
d.update({
'step_individual_init': self.step_individual_init,
'step_individual_final': self.step_individual_final,
'step_volitive_init': self.step_volitive_init,
'step_volitive_final': self.step_volitive_final,
'min_w': self.min_w,
'w_scale': self.w_scale
})
return d
[docs] def init_school(self, task):
"""Initialize fish school with uniform distribution."""
step_individual = self.step_individual_init * task.range
step_volitive = self.step_volitive_init * task.range
school = [Fish(weight=self.w_scale / 2.0, task=task, e=True, rng=self.rng) for _ in range(self.population_size)]
school_weight = self.population_size * self.w_scale / 2.0
return step_individual, step_volitive, school_weight, objects_to_array(school)
[docs] def update_steps(self, task):
r"""Update step length for individual and volatile steps.
Args:
task (Task): Optimization task
Returns:
Tuple[numpy.ndarray, numpy.ndarray]:
1. New individual step.
2. New volitive step.
"""
step_individual = np.full(task.dimension, self.step_individual_init - (task.iters + 1) * (
self.step_individual_init - self.step_individual_final) / task.max_iters)
step_volitive = np.full(task.dimension, self.step_volitive_init - (task.iters + 1) * (
self.step_volitive_init - self.step_volitive_final) / task.max_iters)
return step_individual, step_volitive
[docs] def feeding(self, school):
r"""Feed all fishes.
Args:
school (numpy.ndarray): Current school fish population.
Returns:
numpy.ndarray: New school fish population.
"""
max_delta_cost = max(fish.delta_cost for fish in school)
for fish in school:
if max_delta_cost:
fish.weight = fish.weight + (fish.delta_cost / max_delta_cost)
fish.weight = np.clip(fish.weight, self.min_w, self.w_scale)
return school
[docs] def individual_movement(self, school, step_individual, xb, fxb, task):
r"""Perform individual movement for each fish.
Args:
school (numpy.ndarray): School fish population.
step_individual (numpy.ndarray): Current individual step.
xb (numpy.ndarray): Global best solution.
fxb (float): Global best solutions fitness/objective value.
task (Task): Optimization task.
Returns:
Tuple[numpy.ndarray, numpy.ndarray, float]:
1. New school of fishes.
2. New global best position.
3. New global best fitness.
"""
for fish in school:
new_pos = task.repair(fish.x + (step_individual * self.uniform(-1, 1, task.dimension)), rng=self.rng)
cost = task.eval(new_pos)
if cost < fish.f:
xb, fxb = self.get_best(new_pos, cost, xb, fxb)
fish.delta_cost = abs(cost - fish.f)
fish.f = cost
fish.delta_pos = new_pos - fish.x
fish.x = new_pos
else:
fish.delta_pos = np.zeros(task.dimension)
fish.delta_cost = 0
return school, xb, fxb
[docs] def collective_instinctive_movement(self, school, task):
r"""Perform collective instinctive movement.
Args:
school (numpy.ndarray): Current population.
task (Task): Optimization task.
Returns:
numpy.ndarray: New population
"""
cost_eval_enhanced = sum((fish.delta_cost * fish.delta_pos for fish in school), start=np.zeros(task.dimension))
density = sum(f.delta_cost for f in school)
if density != 0:
cost_eval_enhanced /= density
for fish in school:
fish.x = task.repair(fish.x + cost_eval_enhanced, rng=self.rng)
return school
[docs] def collective_volitive_movement(self, school, step_volitive, school_weight, xb, fxb, task):
r"""Perform collective volitive movement.
Args:
school (numpy.ndarray):
step_volitive :
school_weight:
xb (numpy.ndarray): Global best solution.
fxb (float): Global best solutions fitness/objective value.
task (Task): Optimization task.
Returns:
Tuple[numpy.ndarray, numpy.ndarray, float]:
1. New population.
2. New global best individual.
3. New global best fitness.
"""
prev_weight_school = school_weight
school_weight = sum(fish.weight for fish in school)
barycenter = sum((fish.x * fish.weight for fish in school), start=np.zeros(task.dimension))
barycenter /= sum(fish.weight for fish in school)
for fish in school:
if school_weight > prev_weight_school:
fish.x -= (fish.x - barycenter) * step_volitive * self.uniform(0, 1, task.dimension)
else:
fish.x += (fish.x - barycenter) * step_volitive * self.uniform(0, 1, task.dimension)
fish.evaluate(task, rng=self.rng)
xb, fxb = self.get_best(fish.x, fish.f, xb, fxb)
return school, xb, fxb
[docs] def init_population(self, task):
r"""Initialize the school.
Args:
task (Task): Optimization task.
Returns:
Tuple[numpy.ndarray, numpy.ndarray, dict]:
1. Population.
2. Population fitness.
3. Additional arguments:
* step_individual (float): Current individual step.
* step_volitive (float): Current volitive step.
* school_weight (float): Current school weight.
"""
step_individual, step_volitive, school_weight, school = self.init_school(task)
return school, np.asarray([f.f for f in school]), {'step_individual': step_individual,
'step_volitive': step_volitive,
'school_weight': school_weight}
[docs] def run_iteration(self, task, population, population_fitness, best_x, best_fitness, **params):
r"""Core function of algorithm.
Args:
task (Task): Optimization task.
population (numpy.ndarray): Current population.
population_fitness (numpy.ndarray): Current population fitness.
best_x (numpy.ndarray): Current global best individual.
best_fitness (float): Current global best fitness.
**params: Additional parameters.
Returns:
Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, float, dict]:
1. New Population.
2. New Population fitness.
3. New global best individual.
4. New global best fitness.
5. Additional parameters:
* step_individual (float): Current individual step.
* step_volitive (float): Current volitive step.
* school_weight (float): Current school weight.
"""
step_individual = params.pop('step_individual')
step_volitive = params.pop('step_volitive')
school_weight = params.pop('school_weight')
population, best_x, best_fitness = self.individual_movement(population, step_individual, best_x, best_fitness, task)
population = self.feeding(population)
population = self.collective_instinctive_movement(population, task)
population, best_x, best_fitness = self.collective_volitive_movement(population, step_volitive, school_weight,
best_x, best_fitness, task)
step_individual, step_volitive = self.update_steps(task)
return population, np.asarray([f.f for f in population]), best_x, best_fitness, {'step_individual': step_individual,
'step_volitive': step_volitive,
'school_weight': school_weight
}