Source code for mealpy.bio_based.APO

#!/usr/bin/env python
# Created by "Thieu" at 19:49, 09/07/2026 ----------%                                                                               
#       Email: nguyenthieu2102@gmail.com            %                                                    
#       Github: https://github.com/thieu1995        %                         
# --------------------------------------------------%

import numpy as np
from mealpy.optimizer import Optimizer
from mealpy.utils.opt_info import OptInfo


[docs]class OriginalAPO(Optimizer): """ The original version of: Artificial Protozoa Optimizer (APO) Parameters ---------- epoch : int Maximum number of iterations. Default is 10000. pop_size : int Population size. Default is 100. pf_max : float Proportion fraction maximum, in range (0.0, 1.0), better [0.1, 0.3]. n_pairs : int Number of neighbor pairs, in range [1, floor(pop_size/2)], better [2, 5]. Links ----- 1. https://doi.org/10.1016/j.knosys.2024.111737 2. https://www.mathworks.com/matlabcentral/fileexchange/162656-artificial-protozoa-optimizer References ~~~~~~~~~~ 1. Wang, X., Snášel, V., Mirjalili, S., Pan, J. S., Kong, L., & Shehadeh, H. A. (2024). Artificial Protozoa Optimizer (APO): A novel bio-inspired metaheuristic algorithm for engineering optimization. Knowledge-based systems, 295, 111737. Examples ~~~~~~~~ >>> import numpy as np >>> from mealpy import FloatVar, APO >>> >>> def objective_function(solution): >>> return np.sum(solution**2) >>> >>> problem_dict = { >>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"), >>> "minmax": "min", >>> "obj_func": objective_function >>> } >>> >>> model = APO.OriginalAPO(epoch=1000, pop_size=50, pf_max=0.1, n_pairs=2) >>> g_best = model.solve(problem_dict) >>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}") >>> print(f"Solution: {model.g_best.solution}, Fitness: {model.g_best.target.fitness}") """ OPT_INFO = OptInfo(name="Artificial Protozoa Optimizer", year=2024, difficulty="medium", kind="original") def __init__(self, epoch: int = 10000, pop_size: int = 100, pf_max: float = 0.1, n_pairs: int = 2, **kwargs: object) -> None: super().__init__(**kwargs) self.epoch = self.validator.check_int("epoch", epoch, [1, 100000]) self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 10000]) self.pf_max = self.validator.check_float("pf_max", pf_max, (0., 1.0)) self.n_pairs = self.validator.check_int("n_pairs", n_pairs, [1, int(np.floor(self.pop_size / 2))]) self.set_parameters(["epoch", "pop_size", "pf_max", "n_pairs"]) self.sort_flag = True
[docs] def evolve(self, epoch: int) -> None: """ The main operations (equations) of algorithm. Inherit from Optimizer class Args: epoch: The current iteration """ pf = self.pf_max * self.generator.random() # Random indices for dormancy or reproduction forms ri = self.generator.choice(self.pop_size, size=int(np.ceil(self.pop_size * pf)), replace=False) pop_new = [] for idx in range(self.pop_size): if idx in ri: # Dormancy or Reproduction form pdr = 0.5 * (1 + np.cos((1 - (idx + 1) / self.pop_size) * np.pi)) pos_rand = self.problem.lb + self.generator.random(self.problem.n_dims) * (self.problem.ub - self.problem.lb) if self.generator.random() < pdr: # Dormancy form pos_new = pos_rand else: # Reproduction form flag = 1 if self.generator.random() < 0.5 else -1 mr = np.zeros(self.problem.n_dims) rand_indices = self.generator.choice(self.problem.n_dims, size=int(np.ceil(self.generator.random() * self.problem.n_dims)), replace=False) mr[rand_indices] = 1 pos_new = self.pop[idx].solution + flag * self.generator.random() * pos_rand * mr else: # Foraging form ff = self.generator.random() * (1 + np.cos(epoch / self.epoch * np.pi)) mf = np.zeros(self.problem.n_dims) rand_indices = self.generator.choice(self.problem.n_dims, size=int(np.ceil(self.problem.n_dims * (idx + 1) / self.pop_size)), replace=False) mf[rand_indices] = 1 pah = 0.5 * (1 + np.cos(epoch / self.epoch * np.pi)) if self.generator.random() < pah: # Autotroph form jdx = self.generator.integers(0, self.pop_size) epn = np.zeros((self.n_pairs, self.problem.n_dims)) for kdx in range(self.n_pairs): # Index selection adjusted for Python 0-based indexing if idx == 0: km = idx kp = self.generator.integers(idx + 1, self.pop_size) elif idx == self.pop_size - 1: km = self.generator.integers(0, self.pop_size - 1) kp = idx else: km = self.generator.integers(0, idx) kp = self.generator.integers(idx + 1, self.pop_size) wa = np.exp(-np.abs(self.pop[km].target.fitness / (self.pop[kp].target.fitness + self.EPSILON))) epn[kdx] = wa * (self.pop[km].solution - self.pop[kp].solution) pos_new = self.pop[idx].solution + ff * (self.pop[jdx].solution - self.pop[idx].solution + (1 / self.n_pairs) * np.sum(epn, axis=0)) * mf else: # Heterotroph form epn = np.zeros((self.n_pairs, self.problem.n_dims)) for k_idx in range(self.n_pairs): k = k_idx + 1 imk = max(0, idx - k) ipk = min(self.pop_size - 1, idx + k) wh = np.exp(-np.abs(self.pop[imk].target.fitness / (self.pop[ipk].target.fitness + self.EPSILON))) epn[k_idx] = wh * (self.pop[imk].solution - self.pop[ipk].solution) flag = 1 if self.generator.random() < 0.5 else -1 x_near = (1 + flag * self.generator.random(self.problem.n_dims) * (1 - epoch / self.epoch)) * self.pop[idx].solution pos_new = self.pop[idx].solution + ff * (x_near - self.pop[idx].solution + (1 / self.n_pairs) * np.sum(epn, axis=0)) * mf pos_new = self.correct_solution(pos_new) agent_new = self.generate_empty_agent(pos_new) pop_new.append(agent_new) if self.mode not in self.AVAILABLE_MODES: agent_new.target = self.get_target(pos_new) self.pop[idx] = self.get_better_agent(self.pop[idx], agent_new, minmax=self.problem.minmax) if self.mode in self.AVAILABLE_MODES: pop = self.update_target_for_population(pop_new) self.pop = self.greedy_selection_population(self.pop, pop, self.problem.minmax)