Source code for piglot.optimisers.aoa

"""AOA optimiser module."""
from typing import Tuple, Callable, Optional
import numpy as np
from piglot.objective import Objective
from piglot.optimiser import ScalarOptimiser


[docs] class AOA(ScalarOptimiser): """ AOA optimiser. Documentation: https://www.sciencedirect.com/science/article/pii/S0045782520307945 Attributes ---------- n_solutions : integer population size (number of candidate solutions) alpha : float non-negative sensitive parameter used to define the accuracy of the exploitation over the iterations mu : float non-negative control parameter to adjust the search process epsilon : float small number to avoid division by zero seed : int random state seed MOA_start : float Math Optimizer Accelerated function initial value MOA_end : float Math Optimizer Accelerated function end value Methods ------- _optimise(self, func, n_dim, n_iter, bound, init_shot): Solves the optimization problem """ def __init__(self, objective: Objective, n_solutions=10, alpha=5.0, mu=0.5, epsilon=1e-12, seed=1, MOA_start=0.2, MOA_end=1.0): """ Constructs all the necessary attributes for the AOA optimiser Parameters ---------- objective : Objective Objective function to optimise. n_solutions : integer population size (number of candidate solutions) alpha : float non-negative sensitive parameter used to define the accuracy of the exploitation over the iterations mu : float non-negative control parameter to adjust the search process epsilon : float small number to avoid division by zero seed : int random state seed MOA_start : float Math Optimizer Accelerated function initial value MOA_end : float Math Optimizer Accelerated function end value """ super().__init__('AOA', objective) self.n_solutions = n_solutions self.alpha = alpha self.mu = mu self.epsilon = epsilon self.rng = np.random.default_rng(seed) self.MOA_start = MOA_start self.MOA_end = MOA_end def _scalar_optimise( self, objective: Callable[[np.ndarray, Optional[bool]], float], n_dim: int, n_iter: int, bound: np.ndarray, init_shot: np.ndarray, ) -> Tuple[float, np.ndarray]: """ Abstract method for optimising the objective. Parameters ---------- objective : Callable[[np.ndarray], float] Objective function to optimise. n_dim : int Number of parameters to optimise. n_iter : int Maximum number of iterations. bound : np.ndarray Array where first and second columns correspond to lower and upper bounds, respectively. init_shot : np.ndarray Initial shot for the optimisation problem. Returns ------- float Best observed objective value. np.ndarray Observed optimum of the objective. """ # Initiate candidate solutions randomly solutions = ((bound[:, 1] - bound[:, 0]) * self.rng.random(size=(self.n_solutions, n_dim)) + bound[:, 0]) # Replace first candidate solution by the initial shot solutions[0, :] = init_shot # beta = (bound[:, 1] - bound[:, 0])*self.mu # Evaluate fitness fitness = np.zeros(self.n_solutions) for i in range(0, self.n_solutions): fitness[i] = objective(solutions[i, :]) # Update best solution pos_best_solution = np.argmin(fitness) best_value = fitness[pos_best_solution] best_solution = solutions[pos_best_solution, :] # Check solution progress if self._progress_check(0, best_value, best_solution): return best_value, best_solution # # Start iterative cycle for i in range(0, n_iter): # Update MOA and MOP MOA = self.MOA_start + i * (self.MOA_end - self.MOA_start) / n_iter MOP = 1.0 - (i ** (1 / self.alpha)) / (n_iter ** (1 / self.alpha)) # Update solutions for i_solution in range(0, self.n_solutions): new_solution = np.zeros(n_dim) for i_pos in range(0, n_dim): # Random values r1, r2, r3 = self.rng.random(size=3) if r1 > MOA: # Exploration phase if r2 > 0.5: # Division operator new_solution[i_pos] = (best_solution[i_pos] / (MOP + self.epsilon) * beta[i_pos]) else: # Multiplication operator new_solution[i_pos] = best_solution[i_pos] * MOP * beta[i_pos] else: # Exploitation phase if r3 > 0.5: # Subtraction operator new_solution[i_pos] = best_solution[i_pos] - MOP * beta[i_pos] else: # Addition operator new_solution[i_pos] = best_solution[i_pos] + MOP * beta[i_pos] # Boundary check new_solution = np.where(new_solution > bound[:, 1], bound[:, 1], new_solution) new_solution = np.where(new_solution < bound[:, 0], bound[:, 0], new_solution) # Update fitness function new_fitness = objective(new_solution) if new_fitness < fitness[i_solution]: fitness[i_solution] = new_fitness solutions[i_solution, :] = new_solution if fitness[i_solution] < best_value: best_value = fitness[i_solution] best_solution = solutions[i_solution, :] # Update progress and check convergence if self._progress_check(i + 1, best_value, best_solution): break return best_value, best_solution