Source code for mealpy.swarm_based.EEFO

#!/usr/bin/env python
# Created by "Bora" on 06/01/2026
# Github: https://github.com/Orbadgu
# ---------------------------------%

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


[docs]class OriginalEEFO(Optimizer): """ The original version of: Electric Eel Foraging Optimization (EEFO) Parameters ---------- epoch : int Maximum number of iterations, default = 10000. pop_size : int Number of population size, default = 100. Links ----- 1. https://doi.org/10.1016/j.eswa.2023.122200 2. https://www.mathworks.com/matlabcentral/fileexchange/153461-electric-eel-foraging-optimization-eefo References ~~~~~~~~~~ 1. Zhao, W., Wang, L., Zhang, Z., Fan, H., Zhang, J., Mirjalili, S., Khodadadi, N. and Cao, Q., 2024. Electric eel foraging optimization: A new bio-inspired optimizer for engineering applications. Expert systems with applications, 238, p.122200. Examples ~~~~~~~~ >>> import numpy as np >>> from mealpy import FloatVar >>> from mealpy.swarm_based import EEFO >>> >>> def objective_function(solution): >>> return np.sum(solution**2) >>> >>> problem_dict = { >>> "bounds": FloatVar(lb=[-100.] * 30, ub=[100.] * 30, name="delta"), >>> "minmax": "min", >>> "obj_func": objective_function >>> } >>> >>> model = EEFO.OriginalEEFO(epoch=1000, pop_size=50) >>> g_best = model.solve(problem_dict) >>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}") """ OPT_INFO = OptInfo(name="Electric Eel Foraging Optimization", year=2024, difficulty="medium", kind="original") def __init__(self, epoch=10000, pop_size=100, **kwargs): super().__init__(**kwargs) self.epoch = self.validator.check_int("epoch", epoch, [1, 100000]) self.pop_size = self.validator.check_int("pop_size", pop_size, [10, 10000]) self.set_parameters(["epoch", "pop_size"]) self.sort_flag = False
[docs] def amend_solution(self, solution: np.ndarray) -> np.ndarray: """ Amends the solution by replacing any out-of-bound dimension with a random uniform value between its lower and upper bounds. Args: solution: The position array to check and amend. Returns: The valid solution with out-of-bound dimensions randomized. """ # 1. Create a boolean mask where True indicates a bound violation out_of_bounds = (solution < self.problem.lb) | (solution > self.problem.ub) # 2. Generate random uniform values for all dimensions based on their lb and ub rand_values = self.generator.uniform(self.problem.lb, self.problem.ub, size=solution.shape) # 3. Replace out-of-bound elements with random values, keep valid ones intact return np.where(out_of_bounds, rand_values, solution)
[docs] def evolve(self, epoch): """ The main evolution process of algorithm. """ it = epoch max_it = self.epoch dim = self.problem.n_dims lb = self.problem.lb ub = self.problem.ub # Population mean position (required for Eqs. 20, 24, 25) pop_pos_matrix = np.array([agent.solution for agent in self.pop]) mean_pop_pos = np.mean(pop_pos_matrix, axis=0) # Eq. (30): Energy factor E0 calculation e0 = 4 * np.sin(1 - it / max_it) pop_new = [] for idx in range(self.pop_size): agent = self.pop[idx] x = agent.solution # Eq. (30): Energy factor E calculation e_factor = e0 * np.log(1 / self.generator.random()) # --- Direct Vector Calculation --- # Used for determining which dimensions to update in the interaction phase if dim == 1: direct_vector = np.ones(1) else: direct_vector = np.zeros(dim) rand_num = int(np.ceil((max_it - it) / max_it * self.generator.random() * (dim - 2) + 2)) rand_dim = self.generator.choice(dim, size=rand_num, replace=False) direct_vector[rand_dim] = 1 # --- PHASE 1: Exploration (Interaction) --- # Active when Energy Factor > 1 if e_factor > 1: # Select a random partner 'j' distinct from current agent 'i' jdx = self.sample_indexes_exclude_one(self.generator, self.pop_size, idx, n_samples=1) agent_j = self.pop[jdx] # Eq. (7): Interaction based on fitness comparison if self.compare_target(agent_j.target, agent.target): if self.generator.random() > 0.5: pos_new = agent_j.solution + self.generator.standard_normal() * direct_vector * (mean_pop_pos - x) else: xr = self.generator.uniform(lb, ub, dim) pos_new = agent_j.solution + 1 * self.generator.standard_normal() * direct_vector * (xr - x) else: if self.generator.random() > 0.5: pos_new = x + self.generator.standard_normal() * direct_vector * (mean_pop_pos - agent_j.solution) else: xr = self.generator.uniform(lb, ub, dim) pos_new = x + self.generator.standard_normal() * direct_vector * (xr - agent_j.solution) # --- PHASE 2: Exploitation --- # Active when Energy Factor <= 1 else: rand_prob = self.generator.random() # Mode A: Resting (Eq. 16) if rand_prob < 1/3: # Eq. (15): Alpha calculation alpha = 2 * (np.exp(1) - np.exp(it / max_it)) * np.sin(2 * np.pi * self.generator.random()) rn = self.generator.integers(0, self.pop_size) rd = self.generator.integers(0, dim) agent_rn = self.pop[rn] # Eq. (12) & (13): Z vector calculation z_scalar = (agent_rn.solution[rd] - lb[rd]) / (ub[rd] - lb[rd]) z_vec = lb + z_scalar * (ub - lb) # Eq. (14): Ri calculation (Interaction with global best) r_i = z_vec + alpha * np.abs(z_vec - self.g_best.solution) # Eq. (16): Position update pos_new = r_i + self.generator.standard_normal() * (r_i - np.round(self.generator.random()) * x) # Mode B: Migrating (Eq. 24) elif rand_prob > 2/3: rn = self.generator.integers(0, self.pop_size) rd = self.generator.integers(0, dim) agent_rn = self.pop[rn] z_scalar = (agent_rn.solution[rd] - lb[rd]) / (ub[rd] - lb[rd]) z_vec = lb + z_scalar * (ub - lb) alpha = 2 * (np.exp(1) - np.exp(it / max_it)) * np.sin(2 * np.pi * self.generator.random()) r_i = z_vec + alpha * np.abs(z_vec - self.g_best.solution) # Eq. (21): Beta calculation beta = 2 * (np.exp(1) - np.exp(it / max_it)) * np.sin(2 * np.pi * self.generator.random()) # Eq. (25): Hr calculation (Hunting area) hr = self.g_best.solution + beta * np.abs(mean_pop_pos - self.g_best.solution) # Eq. (26): Levy flight levy = self.get_levy_flight_step(beta=1.5, multiplier=0.01, size=dim, case=-1) # Eq. (24): Position update pos_new = -self.generator.random() * r_i + self.generator.random() * hr - levy * (hr - x) # Mode C: Hunting (Eq. 22) else: # Eq. (21): Beta calculation beta = 2 * (np.exp(1) - np.exp(it / max_it)) * np.sin(2 * np.pi * self.generator.random()) # Eq. (20): Hprey calculation h_prey = self.g_best.solution + beta * np.abs(mean_pop_pos - self.g_best.solution) r4 = self.generator.random() # Eq. (23): Eta calculation eta = np.exp(r4 * (1 - it) / max_it) * np.cos(2 * np.pi * r4) # Eq. (22): Position update pos_new = h_prey + eta * (h_prey - np.round(self.generator.random()) * x) # Check bounds and evaluate 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: pop_new[idx].target = self.get_target(pos_new) if self.mode in self.AVAILABLE_MODES: pop_new = self.update_target_for_population(pop_new) # --- Greedy Selection Mechanism --- # Update population only if the new position provides better fitness self.pop = self.greedy_selection_population(self.pop, pop_new, self.problem.minmax)