Thank you for taking the time to explore this document. The NiaPy framework has experienced over 5 years of growth, thanks to the helpfulness and willingness of our community contributors. Their significant contributions have played a vital role in the success of this project. While designing and implementing key functions for the main framework is crucial, adding new algorithms is an equally important step in keeping the framework dynamic and up-to-date.
Currently, the NiaPy framework hosts more than 30 algorithms, categorized into three families:
-
Basic Algorithms: These are vanilla implementations of nature-inspired algorithms, often derived from research papers where authors propose new algorithms.
-
Modified Algorithms: This category includes modified, hybrid, and adaptive variants of basic algorithms.
-
Other Algorithms: This encompasses algorithms that fit well under the optimization umbrella but cannot be directly classified into the previously mentioned families (e.g., random search).
For a comprehensive overview of the implemented algorithms, please refer to the following document.
Before diving into the contribution process, ensure you meet the following prerequisites:
- Basic knowledge of optimization algorithms.
- Familiarity with Python and Numpy.
- An understanding of NiaPy's structure and existing algorithms.
We'll delve into the details of the last prerequisite in this document.
Given the current influx of metaphor-based nature-inspired algorithms in the research area, it is essential to make wise choices when selecting which algorithm to implement and include in the NiaPy framework.
-
Create a New File: Create a new file in
niapy/algorithms/{basic/modified/other}
. The file name should correspond to the acronym of the nature-inspired algorithm. -
Define Algorithm Class: Begin by defining a new class for your algorithm, inheriting from the appropriate base class (e.g., Algorithm). Name your class after the nature-inspired algorithm; avoid using acronyms.
import logging
import numpy as np
from niapy.algorithms.algorithm import Algorithm
logging.basicConfig()
logger = logging.getLogger('niapy.algorithms.basic')
logger.setLevel('INFO')
__all__ = ['BatAlgorithm']
class BatAlgorithm(Algorithm):
r"""Implementation of Bat algorithm.
Reference paper:
Yang, Xin-She. "A new metaheuristic bat-inspired algorithm." Nature inspired cooperative strategies for optimization (NICSO 2010). Springer, Berlin, Heidelberg, 2010. 65-74.
Attributes:
Name (List[str]): List of strings representing algorithm name.
loudness (float): Initial loudness.
pulse_rate (float): Initial pulse rate.
alpha (float): Parameter for controlling loudness decrease.
gamma (float): Parameter for controlling pulse rate increase.
min_frequency (float): Minimum frequency.
max_frequency (float): Maximum frequency.
See Also:
* :class:`niapy.algorithms.Algorithm`
"""
Name = ["BatAlgorithm", "BA"]
- Initialization: Implement the
__init__
method to initialize necessary parameters and attributes specific to your algorithm. Ensure clarity and consistency in parameter naming.
def __init__(self, population_size=40, loudness=1.0, pulse_rate=1.0, alpha=0.97, gamma=0.1, min_frequency=0.0,
max_frequency=2.0, *args, **kwargs):
"""Initialize BatAlgorithm.
Args:
population_size (Optional[int]): Population size.
loudness (Optional[float]): Initial loudness.
pulse_rate (Optional[float]): Initial pulse rate.
alpha (Optional[float]): Parameter for controlling loudness decrease.
gamma (Optional[float]): Parameter for controlling pulse rate increase.
min_frequency (Optional[float]): Minimum frequency.
max_frequency (Optional[float]): Maximum frequency.
See Also:
:func:`niapy.algorithms.Algorithm.__init__`
"""
super().__init__(population_size, *args, **kwargs)
self.loudness = loudness
self.pulse_rate = pulse_rate
self.alpha = alpha
self.gamma = gamma
self.min_frequency = min_frequency
self.max_frequency = max_frequency
- Set Parameters Method: Override the
set_parameters
method to set parameters specific to your algorithm. Update the docstring to provide clear information about each parameter.
def set_parameters(self, population_size=20, loudness=1.0, pulse_rate=1.0, alpha=0.97, gamma=0.1, min_frequency=0.0,
max_frequency=2.0, **kwargs):
r"""Set the parameters of the algorithm.
Args:
population_size (Optional[int]): Population size.
loudness (Optional[float]): Initial loudness.
pulse_rate (Optional[float]): Initial pulse rate.
alpha (Optional[float]): Parameter for controlling loudness decrease.
gamma (Optional[float]): Parameter for controlling pulse rate increase.
min_frequency (Optional[float]): Minimum frequency.
max_frequency (Optional[float]): Maximum frequency.
See Also:
* :func:`niapy.algorithms.Algorithm.set_parameters`
"""
super().set_parameters(population_size=population_size, **kwargs)
self.loudness = loudness
self.pulse_rate = pulse_rate
self.alpha = alpha
self.gamma = gamma
self.min_frequency = min_frequency
self.max_frequency = max_frequency
- Get Parameters Method: Override the
get_parameters
method to return your algorithms parameters.
def get_parameters(self):
r"""Get parameters of the algorithm.
Returns:
Dict[str, Any]: Algorithm parameters.
"""
parameters = super().get_parameters()
parameters.update({
'loudness': self.loudness,
'pulse_rate': self.pulse_rate,
'alpha': self.alpha,
'gamma': self.gamma,
'min_frequency': self.min_frequency,
'max_frequency': self.max_frequency
})
return parameters
- Init Population Method: Adjust the
init_population
method to initialize the initial population for your algorithm. Add any additional parameters required by your algorithm.
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:
* velocities (numpy.ndarray[float]): Velocities.
* alpha (float): Previous iterations loudness.
See Also:
* :func:`niapy.algorithms.Algorithm.init_population`
"""
population, fitness, d = super().init_population(task)
velocities = np.zeros((self.population_size, task.dimension))
d.update({'velocities': velocities, 'loudness': self.loudness})
return population, fitness, d
- Run Iteration Method: Customize the
run_iteration
method, the core function of your algorithm. Implement the main logic, considering the optimization task, population, and iteration-specific parameters.
def run_iteration(self, task, population, population_fitness, best_x, best_fitness, **params):
r"""Core function of Bat 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:
* velocities (numpy.ndarray): Velocities.
* alpha (float): Previous iterations loudness.
"""
velocities = params.pop('velocities')
loudness = params.pop('loudness') * self.alpha
pulse_rate = self.pulse_rate * (1 - np.exp(-self.gamma * task.iters))
for i in range(self.population_size):
frequency = self.min_frequency + (self.max_frequency - self.min_frequency) * self.random()
velocities[i] += (population[i] - best_x) * frequency
if self.random() < pulse_rate:
solution = task.repair(best_x + 0.1 * self.standard_normal(task.dimension) * loudness)
else:
solution = task.repair(population[i] + velocities[i], rng=self.rng)
new_fitness = task.eval(solution)
if (new_fitness <= population_fitness[i]) and (self.random() > loudness):
population[i], population_fitness[i] = solution, new_fitness
if new_fitness <= best_fitness:
best_x, best_fitness = solution.copy(), new_fitness
return population, population_fitness, best_x, best_fitness, {'velocities': velocities, 'loudness': loudness}
- Provide Detailed Information: Document your algorithm, including its reference paper, attributes, and usage instructions. We use the google docstring format for documenting our codebase.
- Write Test Cases: Write at least two test cases to ensure the correctness of your algorithm's implementation (see tests/test_ba.py).
- Provide Simple Example: Include a simple example for running your algorithm (see examples/run_ba.py).
- Follow the PEP 8 style guide for Python.
- Maintain consistency in formatting and naming conventions.
- MIT License: Each code contribution in this repository is licensed under the MIT license.