#!/usr/bin/env python
# Created by "Thieu" at 09:16, 15/08/2025 ----------%
# 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, ScientificConcern
[docs]class DevEPC(Optimizer):
"""
Our developed version of: Emperor Penguins Colony (EPC)
Parameters
----------
epoch : int
Maximum number of iterations, in range [1, 100000]. Default is 10000.
pop_size : int
Number of population size, in range [5, 10000]. Default is 100.
heat_damping_factor : float
Damping factor for heat radiation, in range [0.0, 1.0]. Default is 0.95.
mutation_factor : float
Mutation factor for random movement, in range [0.0, 1.0]. Default is 0.5.
spiral_a : float
Constant for logarithmic spiral movement, in range [0.0, 100.0]. Default is 1.0.
spiral_b : float
Constant for logarithmic spiral movement, in range [0.0, 100.0]. Default is 0.5.
.. error::
+ This algorithm is almost like a trash algorithm. Some comments are as follows:
+ The pseudocode is incorrect and incomplete. It updates coefficients either increasing or decreasing,
but the paper does not clearly provide any formulas describing how these increases or decreases are calculated.
+ Most of the formulas are wrong and meaningless, with no clear explanation of what the
symbols represent. In particular, formulas 12 to 18 are problematic. There is no connection
between the position update process in the algorithm and the parameters.
+ This algorithm can only be applied to 2-dimensional problems and cannot be extended to
problems with more than 2 dimensions. The entire experimental section of the paper
is also limited to 2-dimensional functions.
+ In the code, I simplified the position update process for penguins and modified the
algorithm to work on n-dimensional problems. The parameter update rules were also
devised by me. Therefore, I named it DevEPC.
References
~~~~~~~~~~
1. Harifi, S., Khalilian, M., Mohammadzadeh, J. and Ebrahimnejad, S., 2019. Emperor Penguins Colony: a new
metaheuristic algorithm for optimization. Evolutionary intelligence, 12(2), pp.211-226.
https://doi.org/10.1007/s12065-019-00212-x
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, EPC
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "obj_func": objective_function,
>>> "minmax": "min",
>>> }
>>>
>>> model = EPC.DevEPC(epoch=1000, pop_size=50, heat_damping_factor=0.95, mutation_factor=0.1,
>>> spiral_a=1.0, spiral_b=0.5)
>>> 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="Emperor Penguins Colony", year=2019, difficulty="hard", kind="original",
scientific_status="questionable",
concerns=(
ScientificConcern.FABRICATED_RESULTS, ScientificConcern.LACK_OF_NOVELTY,
ScientificConcern.RESEARCH_MISCONDUCT, ScientificConcern.POOR_REPRODUCIBILITY,
ScientificConcern.AMBIGUOUS_METHODOLOGY, ScientificConcern.CODE_PSEUDOCODE_MISMATCH
))
def __init__(self, epoch=10000, pop_size=100, heat_damping_factor: float = 0.95,
mutation_factor: float = 0.5, spiral_a: float = 1.0, spiral_b: float = 0.5, **kwargs):
"""
Args:
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
heat_damping_factor (float): Damping factor for heat radiation, default = 0.95
mutation_factor (float): Mutation factor for random movement, default = 0.1
spiral_a (float): Constant for logarithmic spiral movement, default = 1.0
spiral_b (float): Constant for logarithmic spiral movement, default = 0.5
"""
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.heat_damping_factor = self.validator.check_float("heat_damping_factor", heat_damping_factor, [0.0, 1.0])
self.mutation_factor = self.validator.check_float("mutation_factor", mutation_factor, [0.0, 1.0])
self.spiral_a = self.validator.check_float("spiral_a", spiral_a, [0.0, 100.0])
self.spiral_b = self.validator.check_float("spiral_b", spiral_b, [0.0, 100.0])
self.set_parameters(["epoch", "pop_size", "heat_damping_factor", "mutation_factor", "spiral_a", "spiral_b"])
self.sort_flag = False
self.is_parallelizable = False
[docs] def initialize_variables(self):
# Physical constants (from paper)
self.surface_area = 0.56 # m^2 (total surface area of emperor penguin)
self.emissivity = 0.98 # emissivity of bird plumage
self.stefan_boltzmann = 5.6703e-8 # W/m^2K^4
self.body_temperature = 308.15 # K (35°C)
self.mu = 0.01 # Attenuation coefficient (can be tuned)
# Calculate heat radiation using Stefan-Boltzmann law (Equation 6)
self.heat_radiation = (self.surface_area * self.emissivity * self.stefan_boltzmann * (self.body_temperature ** 4))
[docs] def calculate_attractiveness(self, heat_radiation: float, distance: float) -> float:
"""
Calculate attractiveness between two penguins based on heat radiation and distance
Parameters:
-----------
heat_radiation : float
Heat radiation of the source penguin
distance : float
Distance between penguins
Returns:
--------
float : Attractiveness value
"""
# Linear heat source model with photon attenuation (Equations 9-11)
if distance == 0:
return heat_radiation
else:
# Heat intensity with linear source and attenuation
return heat_radiation * np.exp(-self.mu * distance) / distance
[docs] def spiral_movement(self, penguin_i: np.ndarray, penguin_j: np.ndarray, attractiveness: float) -> np.ndarray:
"""
Calculate spiral-like movement from penguin i towards penguin j
Parameters:
-----------
penguin_i : np.ndarray
Position of penguin i (moving penguin)
penguin_j : np.ndarray
Position of penguin j (target penguin)
attractiveness : float
Attractiveness value between penguins
Returns:
--------
np.ndarray : New position after spiral movement
"""
# Convert to polar coordinates
diff = penguin_j - penguin_i
# Simplified spiral movement calculation
# Instead of complex polar coordinate transformation, use direct approach
distance = np.linalg.norm(diff)
if distance == 0 or np.allclose(penguin_i, penguin_j):
return penguin_i.copy()
# Direction vector
direction = diff / distance
# Spiral movement distance based on attractiveness
move_distance = attractiveness * distance * self.spiral_a
# Add spiral rotation effect
theta = self.spiral_b * np.pi
rotation_matrix = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
# Apply rotation to direction (for 2D, extend for higher dimensions)
if self.problem.n_dims >= 2:
rotated_dir = direction.copy()
rotated_dir[:2] = rotation_matrix @ direction[:2]
else:
rotated_dir = direction
# Calculate new position - Add random component (mutation) - Equation 19
new_position = (penguin_i + move_distance * rotated_dir +
self.current_mutation_factor * self.generator.uniform(-1, 1, self.problem.n_dims))
return new_position
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
# Decrease heat absorption coefficient
self.heat_radiation = self.heat_radiation * self.heat_damping_factor
# Decrease mutation factor
self.current_mutation_factor = self.mutation_factor * (1 - epoch / self.epoch)
# For each penguin i
for idx in range(self.pop_size):
# For each penguin j
for jdx in range(self.pop_size):
# Move penguin i towards penguin j if j has better cost
if self.compare_target(self.pop[jdx].target, self.pop[idx].target, self.problem.minmax):
# Calculate distance between penguins
distance = np.linalg.norm(self.pop[jdx].solution - self.pop[idx].solution)
# Calculate attractiveness
attractiveness = self.calculate_attractiveness(self.heat_radiation, distance)
# Normalize attractiveness
if attractiveness > 1:
attractiveness = 1.0 / (1.0 + attractiveness)
# Perform spiral movement
pos_new = self.spiral_movement(self.pop[idx].solution, self.pop[jdx].solution, attractiveness)
pos_new = self.correct_solution(pos_new)
agent = self.generate_agent(pos_new)
if self.compare_target(agent.target, self.pop[idx].target, self.problem.minmax):
self.pop[idx] = agent