Skip to content

Commit 4c9cd35

Browse files
committed
Add Emperor Penguins Colony (EPC) algorithm
1 parent 4175c73 commit 4c9cd35

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed

mealpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from .swarm_based import (ABC, ACOR, AGTO, ALO, AO, ARO, AVOA, BA, BeesA, BES, BFO, BSA, COA, CoatiOA, CSA, CSO,
4040
DMOA, DO, EHO, ESOA, FA, FFA, FFO, FOA, FOX, GJO, GOA, GTO, GWO, HBA, HGS, HHO, JA,
4141
MFO, MGO, MPA, MRFO, MSA, NGO, NMRA, OOA, PFA, POA, PSO, SCSO, SeaHO, ServalOA, SFO,
42-
SHO, SLO, SRSR, SSA, SSO, SSpiderA, SSpiderO, STO, TDO, TSO, WaOA, WOA, ZOA)
42+
SHO, SLO, SRSR, SSA, SSO, SSpiderA, SSpiderO, STO, TDO, TSO, WaOA, WOA, ZOA, EPC)
4343
from .system_based import AEO, GCO, WCA
4444
from .music_based import HS
4545
from .utils.problem import Problem

mealpy/swarm_based/EPC.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python
2+
# Created by "Thieu" at 09:16, 15/08/2025 ----------%
3+
# Email: nguyenthieu2102@gmail.com %
4+
# Github: https://github.com/thieu1995 %
5+
# --------------------------------------------------%
6+
7+
import numpy as np
8+
from mealpy.optimizer import Optimizer
9+
10+
11+
class DevEPC(Optimizer):
12+
"""
13+
The developer version of: Emperor Penguins Colony (EPC)
14+
15+
Notes:
16+
+ This algorithm is almost like a trash algorithm. Some comments are as follows:
17+
+ The code 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.
18+
+ 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.
19+
+ 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.
20+
+ 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.
21+
22+
Links:
23+
1. https://doi.org/10.1007/s12065-019-00212-x
24+
25+
Examples
26+
~~~~~~~~
27+
>>> import numpy as np
28+
>>> from mealpy import FloatVar, EPC
29+
>>>
30+
>>> def objective_function(solution):
31+
>>> return np.sum(solution**2)
32+
>>>
33+
>>> problem_dict = {
34+
>>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
35+
>>> "obj_func": objective_function,
36+
>>> "minmax": "min",
37+
>>> }
38+
>>>
39+
>>> model = EPC.DevEPC(epoch=1000, pop_size=50)
40+
>>> g_best = model.solve(problem_dict)
41+
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
42+
>>> print(f"Solution: {model.g_best.solution}, Fitness: {model.g_best.target.fitness}")
43+
44+
References
45+
~~~~~~~~~~
46+
[1] Harifi, S., Khalilian, M., Mohammadzadeh, J. and Ebrahimnejad, S., 2019.
47+
Emperor Penguins Colony: a new metaheuristic algorithm for optimization. Evolutionary intelligence, 12(2), pp.211-226.
48+
"""
49+
50+
def __init__(self, epoch=10000, pop_size=100, heat_damping_factor: float = 0.95,
51+
mutation_factor: float = 0.5, spiral_a: float = 1.0, spiral_b: float = 0.5, **kwargs):
52+
"""
53+
Args:
54+
epoch (int): maximum number of iterations, default = 10000
55+
pop_size (int): number of population size, default = 100
56+
heat_damping_factor (float): Damping factor for heat radiation, default = 0.95
57+
mutation_factor (float): Mutation factor for random movement, default = 0.1
58+
spiral_a (float): Constant for logarithmic spiral movement, default = 1.0
59+
spiral_b (float): Constant for logarithmic spiral movement, default = 0.5
60+
"""
61+
super().__init__(**kwargs)
62+
self.epoch = self.validator.check_int("epoch", epoch, [1, 100000])
63+
self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 10000])
64+
self.heat_damping_factor = self.validator.check_float("heat_damping_factor", heat_damping_factor, [0.0, 1.0])
65+
self.mutation_factor = self.validator.check_float("mutation_factor", mutation_factor, [0.0, 1.0])
66+
self.spiral_a = self.validator.check_float("spiral_a", spiral_a, [0.0, 100.0])
67+
self.spiral_b = self.validator.check_float("spiral_b", spiral_b, [0.0, 100.0])
68+
self.set_parameters(["epoch", "pop_size", "heat_damping_factor", "mutation_factor", "spiral_a", "spiral_b"])
69+
self.sort_flag = False
70+
self.is_parallelizable = False
71+
72+
def initialize_variables(self):
73+
# Physical constants (from paper)
74+
self.surface_area = 0.56 # m^2 (total surface area of emperor penguin)
75+
self.emissivity = 0.98 # emissivity of bird plumage
76+
self.stefan_boltzmann = 5.6703e-8 # W/m^2K^4
77+
self.body_temperature = 308.15 # K (35°C)
78+
self.mu = 0.01 # Attenuation coefficient (can be tuned)
79+
# Calculate heat radiation using Stefan-Boltzmann law (Equation 6)
80+
self.heat_radiation = (self.surface_area * self.emissivity * self.stefan_boltzmann * (self.body_temperature ** 4))
81+
82+
def calculate_attractiveness(self, heat_radiation: float, distance: float) -> float:
83+
"""
84+
Calculate attractiveness between two penguins based on heat radiation and distance
85+
86+
Parameters:
87+
-----------
88+
heat_radiation : float
89+
Heat radiation of the source penguin
90+
distance : float
91+
Distance between penguins
92+
93+
Returns:
94+
--------
95+
float : Attractiveness value
96+
"""
97+
# Linear heat source model with photon attenuation (Equations 9-11)
98+
if distance == 0:
99+
return heat_radiation
100+
else:
101+
# Heat intensity with linear source and attenuation
102+
return heat_radiation * np.exp(-self.mu * distance) / distance
103+
104+
def spiral_movement(self, penguin_i: np.ndarray, penguin_j: np.ndarray, attractiveness: float) -> np.ndarray:
105+
"""
106+
Calculate spiral-like movement from penguin i towards penguin j
107+
108+
Parameters:
109+
-----------
110+
penguin_i : np.ndarray
111+
Position of penguin i (moving penguin)
112+
penguin_j : np.ndarray
113+
Position of penguin j (target penguin)
114+
attractiveness : float
115+
Attractiveness value between penguins
116+
117+
Returns:
118+
--------
119+
np.ndarray : New position after spiral movement
120+
"""
121+
# Convert to polar coordinates
122+
diff = penguin_j - penguin_i
123+
# Simplified spiral movement calculation
124+
# Instead of complex polar coordinate transformation, use direct approach
125+
distance = np.linalg.norm(diff)
126+
if distance == 0 or np.allclose(penguin_i, penguin_j):
127+
return penguin_i.copy()
128+
# Direction vector
129+
direction = diff / distance
130+
# Spiral movement distance based on attractiveness
131+
move_distance = attractiveness * distance * self.spiral_a
132+
# Add spiral rotation effect
133+
theta = self.spiral_b * np.pi
134+
rotation_matrix = np.array([[np.cos(theta), -np.sin(theta)],
135+
[np.sin(theta), np.cos(theta)]])
136+
# Apply rotation to direction (for 2D, extend for higher dimensions)
137+
if self.problem.n_dims >= 2:
138+
rotated_dir = direction.copy()
139+
rotated_dir[:2] = rotation_matrix @ direction[:2]
140+
else:
141+
rotated_dir = direction
142+
# Calculate new position - Add random component (mutation) - Equation 19
143+
new_position = (penguin_i + move_distance * rotated_dir +
144+
self.current_mutation_factor * self.generator.uniform(-1, 1, self.problem.n_dims))
145+
return new_position
146+
147+
def evolve(self, epoch):
148+
"""
149+
The main operations (equations) of algorithm. Inherit from Optimizer class
150+
151+
Args:
152+
epoch (int): The current iteration
153+
"""
154+
# Decrease heat absorption coefficient
155+
self.heat_radiation = self.heat_radiation * self.heat_damping_factor
156+
# Decrease mutation factor
157+
self.current_mutation_factor = self.mutation_factor * (1 - epoch / self.epoch)
158+
159+
# For each penguin i
160+
for idx in range(self.pop_size):
161+
# For each penguin j
162+
for jdx in range(self.pop_size):
163+
# Move penguin i towards penguin j if j has better cost
164+
if self.compare_target(self.pop[jdx].target, self.pop[idx].target, self.problem.minmax):
165+
# Calculate distance between penguins
166+
distance = np.linalg.norm(self.pop[jdx].solution - self.pop[idx].solution)
167+
# Calculate attractiveness
168+
attractiveness = self.calculate_attractiveness(self.heat_radiation, distance)
169+
# Normalize attractiveness
170+
if attractiveness > 1:
171+
attractiveness = 1.0 / (1.0 + attractiveness)
172+
# Perform spiral movement
173+
pos_new = self.spiral_movement(self.pop[idx].solution, self.pop[jdx].solution, attractiveness)
174+
pos_new = self.correct_solution(pos_new)
175+
agent = self.generate_agent(pos_new)
176+
if self.compare_target(agent.target, self.pop[idx].target, self.problem.minmax):
177+
self.pop[idx] = agent

run.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from mealpy import SBO, SMA, SOA, SOS, TPO, TSA, VCS, WHO, AOA, CEM, CGO, CircleSA, GBO, HC, INFO, PSS, RUN, SCA
1818
from mealpy import SHIO, TS, HS, AEO, GCO, WCA, CRO, DE, EP, ES, FPA, MA, SHADE, BRO, BSO, CA, CHIO, FBIO, GSKA, HBO
1919
from mealpy import TDO, STO, SSpiderO, SSpiderA, SSO, SSA, SRSR, SLO, SHO, SFO, ServalOA, SeaHO, SCSO, POA
20-
from mealpy import ESO
20+
from mealpy import ESO, EPC
2121
from mealpy import (IntegerVar, FloatVar, StringVar, BinaryVar, BoolVar, CategoricalVar,
2222
SequenceVar, PermutationVar, TransferBinaryVar, TransferBoolVar)
2323
from mealpy import Tuner, Multitask, Problem, Optimizer, Termination, ParameterGrid
@@ -72,6 +72,7 @@ def obj_func(self, solution):
7272

7373
model = ESO.OriginalESO(epoch=100, pop_size=20)
7474
model = AO.AAO(epoch=100, pop_size=50, sharpness=10.0, sigmoid_midpoint=0.5)
75+
model = EPC.DevEPC(epoch=100, pop_size=50, heat_damping_factor=0.95, mutation_factor=0.5, spiral_a=1.0, spiral_b=0.5)
7576
model.solve(P1)
7677

7778

0 commit comments

Comments
 (0)