Source code for star_pso.engines.quantum_pso

from numpy import log, where
from numpy.typing import ArrayLike

from star_pso.utils import VOptions
from star_pso.engines.generic_pso import GenericPSO
from star_pso.utils.auxiliary import (nb_clip_inplace,
                                      nb_median_euclidean_distance)

# Public interface.
__all__ = ["QuantumPSO"]


[docs] class QuantumPSO(GenericPSO): """ Description: This class implements a variant of the quantum particle swarm optimization as described in: - M. Xi, J. Sun, W. Xu (2008), "An improved quantum-behaved particle swarm optimization algorithm with weighted mean best position", Applied Mathematics and Computation vol. 205 pp: 751–759, doi: 10.1016/j.amc.2008.05.135. """ def __init__(self, x_min: ArrayLike, x_max: ArrayLike, **kwargs) -> None: """ Default initializer of the QuantumPSO class. :param x_min: lower search space bound. :param x_max: upper search space bound. """ # Call the super initializer with the input parameters. super().__init__(lower_bound=x_min, upper_bound=x_max, **kwargs) # DISABLE the adaptation of model parameters # since this class does not support the same # velocity equations. self.disable_parameters_update() # Generate initial particle "velocities". self._velocities = GenericPSO.rng.uniform(self.lower_bound, self.upper_bound, size=(self.n_rows, self.n_cols)) # _end_def_
[docs] def update_velocities(self, params: VOptions) -> None: """ Performs the update on the "velocity" equations. :param params: VOptions tuple with the PSO options. :return: None. """ # Hardcode the contraction / expansion coefficient. beta_coefficient: float = 0.5 # Get the shape of the velocity array. arr_shape = (self.n_rows, self.n_cols) # Pre-sample the 'phi' parameters. param_phi = GenericPSO.rng.random(size=arr_shape) # Pre-sample the 'u' parameters. param_u = GenericPSO.rng.random(size=arr_shape) # Get the (Global / Local / FIPSO) best positions. m_best = self.get_local_best_positions(params.mode.lower()) # Extract the current positions. x_current = self.swarm.positions_as_array() # Extract the best (historical) positions. p_best = self.swarm.best_positions_as_array() # Extract the global best position. g_best = self.swarm.best_particle().position # Construct the 'p_best' (in-place). p_best *= param_phi # Element-wise multiplication using broadcasting. p_best += (1.0 - param_phi) * g_best # Ensure there are no zero values that would raise # an error below when computing log(param_u). This # is very unlikely but it could happen. param_u[param_u == 0.0] = QuantumPSO.NUMPY_EPS # Compute the offset. p_offset = beta_coefficient * (m_best - x_current) * log(param_u) # Select the directions at random. direction = where(self.rng.random(arr_shape) < 0.5, -1.0, 1.0) # Perform all operations in place. p_best += direction * p_offset # Ensure we stay within limits. nb_clip_inplace(p_best, self.lower_bound, self.upper_bound) # Assign the new "velocities" vectors. self._velocities = p_best
# _end_def_
[docs] def update_positions(self) -> None: """ Updates the positions of the particles in the swarm. :return: None. """ # Update all particle positions. self.swarm.set_positions(self._velocities)
# _end_def_
[docs] def generate_random_positions(self) -> None: """ Generate the population of particles positions by sampling uniform random numbers within the limits. :return: None. """ # Generate uniform FLOAT positions U(x_min, x_max). self._velocities = GenericPSO.rng.uniform(self.lower_bound, self.upper_bound, size=(self.n_rows, self.n_cols)) # Assign the new positions. self.swarm.set_positions(self._velocities)
# _end_def_
[docs] def reset_all(self) -> None: """ Resets the particle positions, velocities and clear all the statistics dictionary. :return: None. """ # Generate random the positions. self.generate_random_positions() # Clear all the internal bookkeeping. self.clear_all()
# _end_def_
[docs] def calculate_spread(self) -> float: """ Calculates a spread measure for the particle positions using the normalized median Euclidean distance. A value close to '0' indicates the swarm is converging to a single value. On the contrary a value close to '1' indicates the swarm is still spread around the search space. :return: an estimated measure (float) for the spread of the particles. """ # Extract the positions in a 2D numpy array. positions = self.swarm.positions_as_array() # Normalized median Euclidean distance. return nb_median_euclidean_distance(positions, normal=True)
# _end_def_ # _end_class_