Skip to content

Commit dc4074e

Browse files
committed
Add Chameleon Swarm Algorithm (ChameleonSA) and its improved version IChameleonSA to the swarm-based group
1 parent a822e4f commit dc4074e

4 files changed

Lines changed: 300 additions & 135 deletions

File tree

examples/optimizers/run_bwoa_example.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import numpy as np
88
from mealpy import (FloatVar, BWOA, APO, GRSA, KLA, MGOA, AAA, NWOA, OSA, DandelionO, RFO, CrayfishOA, SPBO,
9-
CCO, AHO, MSA, TSeedA, SBOA)
9+
CCO, AHO, MSA, TSeedA, SBOA, ChameleonSA)
1010

1111

1212
def objective_function(solution):
@@ -39,6 +39,8 @@ def objective_function(solution):
3939
model = MSA.OriginalMSA(epoch=1000, pop_size=50, n_best = 5, partition = 0.5, max_step_size = 1.0)
4040
model = TSeedA.OriginalTSeedA(epoch=1000, pop_size=50, st=0.1)
4141
model = SBOA.OriginalSBOA(epoch=1000, pop_size=50)
42+
model = ChameleonSA.OriginalChameleonSA(epoch=1000, pop_size=50, pp=0.2, p1=0.5, p2=2.5, c1=1.4, c2=1.6, gama=1.0, alpha=5.0, rho=1.5)
43+
model = ChameleonSA.IChameleonSA(epoch=1000, pop_size=50, r_chaos=0.5, k_spiral=10., p1=5.0, p2=3.0)
4244

4345
g_best = model.solve(problem, seed=10)
4446
print(f"Best fitness: {g_best.target.fitness}")

mealpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
MFO, MGO, MPA, MRFO, MSA, MShOA, NGO, NMRA, OOA, PFA, POA, PSO, SCSO, SeaHO, ServalOA, SFO,
4545
SHO, SLO, SRSR, SSA, SSO, SSpiderA, SSpiderO, STO, TDO, TSO, WaOA, WOA, ZOA,
4646
EPC, SMO, SquirrelSA, FDO, ChOA, RSA, GJA, BWO, DBO, EEFO, NWOA, OSA, DandelionO, RFO,
47-
CrayfishOA, CCO, AHO)
47+
CrayfishOA, CCO, AHO, ChameleonSA)
4848
from .system_based import AEO, GCO, WCA
4949
from .music_based import HS
5050
from .game_based import THRO

mealpy/swarm_based/ChameleonSA.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
#!/usr/bin/env python
2+
# Created by "Ulaş Görkem Kazan" on 05/01/2026
3+
# Github: https://github.com/gorkemulas2005
4+
# --------------------------------------------------%
5+
# Updated by "Thieu" on 16/07/2026
6+
# Github: https://github.com/thieu1995
7+
# --------------------------------------------------%
8+
9+
import numpy as np
10+
from mealpy.optimizer import Optimizer
11+
12+
13+
class OriginalChameleonSA(Optimizer):
14+
"""
15+
The original version of: Chameleon Swarm Algorithm (ChameleonSA)
16+
17+
Hyperparameters
18+
---------------
19+
+ epoch (int): Maximum number of iterations, default = 10000
20+
+ pop_size (int): Population size, default = 100
21+
+ pp (float): [0, 1] Probability of the chameleon perceiving prey, default=0.1
22+
+ p1 (float): [0, 5.0] Exploration control parameter 1 (From PSO), default=0.25
23+
+ p2 (float): [0, 5.0] Exploration control parameter 2 (From PSO), (default=1.50)
24+
+ c1 (float): [0, 5.0] Personal best influence (From PSO), default=1.75
25+
+ c2 (float): [0, 5.0] Global best influence (From PSO), default=1.75
26+
+ gama (float): [0, 2] Constant controlling the exploration rate decay over iterations, default=1.0
27+
+ alpha (float): [0, 10] Constant defining the steepness of the exploration decay curve, default=3.5
28+
+ rho (float): [0, 2] Positive number, default=1.0.
29+
30+
Warnings
31+
--------
32+
1. This algorithm essentially relies on the update operators of the PSO algorithm. It has too many
33+
parameters, and the results are nowhere near as good as those presented in the paper
34+
2. Please note that the official MATLAB code deviates from the paper, using undocumented
35+
modifications to artificially boost performance.
36+
3. This pure implementation is provided specifically so users can independently evaluate
37+
the algorithm's true performance based solely on the published mathematical model,
38+
allowing you to verify whether the paper's claims and results are legitimate.
39+
40+
Links
41+
-----
42+
1. https://www.mathworks.com/matlabcentral/fileexchange/98014-chameleon-swarm-algorithm
43+
2. https://doi.org/10.1016/j.eswa.2021.114685
44+
45+
References
46+
----------
47+
.. [1] Braik, M. S. (2021). Chameleon Swarm Algorithm: A bio-inspired optimizer for solving
48+
engineering design problems. Expert Systems with Applications, 174, 114685.
49+
50+
Examples
51+
~~~~~~~~
52+
>>> import numpy as np
53+
>>> from mealpy import FloatVar, ChameleonSA
54+
>>>
55+
>>> def objective_function(solution):
56+
>>> return np.sum(solution**2)
57+
>>>
58+
>>> problem_dict = {
59+
>>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
60+
>>> "minmax": "min",
61+
>>> "obj_func": objective_function
62+
>>> }
63+
>>>
64+
>>> model = ChameleonSA.OriginalChameleonSA(epoch=1000, pop_size=50, pp=0.2, p1=0.3, p2=2.0, c1=2.0, c2=2.0, gama=1.0, alpha=5.0, rho=1.5)
65+
>>> g_best = model.solve(problem_dict)
66+
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
67+
>>> print(f"Solution: {model.g_best.solution}, Fitness: {model.g_best.target.fitness}")
68+
"""
69+
70+
def __init__(self, epoch=10000, pop_size=100, pp: float=0.1, p1: float=0.25, p2: float=1.50,
71+
c1: float=1.75, c2: float=1.75, gama: float=1.0, alpha: float=3.5, rho: float=1.0, **kwargs):
72+
super().__init__(**kwargs)
73+
self.epoch = self.validator.check_int("epoch", epoch, [1, 100000])
74+
self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 100000])
75+
self.pp = self.validator.check_float("pp", pp, [0, 1])
76+
self.p1 = self.validator.check_float("p1", p1, [0, 10.])
77+
self.p2 = self.validator.check_float("p2", p2, [0, 10.])
78+
self.c1 = self.validator.check_float("c1", c1, [0, 10.])
79+
self.c2 = self.validator.check_float("c2", c2, [0, 10.])
80+
self.gama = self.validator.check_float("gama", gama, [0, 10.])
81+
self.alpha = self.validator.check_float("alpha", alpha, [0, 10.])
82+
self.rho = self.validator.check_float("rho", rho, [0, 10.])
83+
84+
self.set_parameters(["epoch", "pop_size", "pp", "p1", "p2", "c1", "c2", "gama", "alpha", "rho"])
85+
self.sort_flag = True
86+
self.pop_personal = None
87+
self.velocities = None
88+
self.prev_velocities = None
89+
90+
def before_main_loop(self):
91+
self.pop_personal = self.pop.copy()
92+
self.velocities = np.zeros((self.pop_size, self.problem.n_dims))
93+
self.prev_velocities = np.zeros((self.pop_size, self.problem.n_dims))
94+
95+
def evolve(self, epoch):
96+
"""
97+
The main evolution step.
98+
"""
99+
# Dynamic parameters update
100+
mu = self.gama * np.exp(-self.alpha * epoch / self.epoch) # Eq. 6
101+
omega = (1 - epoch / self.epoch) ** (self.rho * np.sqrt(epoch / self.epoch)) # Eq. 19
102+
a = 2590 * (1 - np.exp(-np.log(epoch + 1))) # Eq. 21 (acceleration)
103+
104+
for idx in range(self.pop_size):
105+
# Phase 1: Search for prey (Eq. 3)
106+
rr = self.generator.random()
107+
r1, r2, r3 = self.generator.random(3)
108+
if rr >= self.pp:
109+
pos_new = self.pop[idx].solution + self.p1 * (self.pop_personal[idx].solution - self.g_best.solution) * r1 + \
110+
self.p2 * (self.g_best.solution - self.pop[idx].solution) * r2
111+
else:
112+
direction = np.sign(self.generator.random(self.problem.n_dims) - 0.5)
113+
pos_new = self.pop[idx].solution + mu * (((self.problem.ub - self.problem.lb) * r3 + self.problem.lb) * direction)
114+
self.pop[idx].solution = pos_new
115+
116+
center = np.mean([agent.solution for agent in self.pop], axis=0)
117+
for idx in range(self.pop_size):
118+
# Phase 2: Chameleon eyes rotation (Eq. 11 - 15)
119+
yc = self.pop[idx].solution - center # Eq. 13
120+
theta = self.generator.random(self.problem.n_dims) * np.sign(self.generator.random(self.problem.n_dims) - 0.5) * np.pi # Eq. 15
121+
# Apply rotation (Eq. 12)
122+
yr = np.cos(theta) * yc
123+
self.pop[idx].solution = yr + center # Eq. 11
124+
125+
for idx in range(self.pop_size):
126+
# Phase 3: Hunting prey - Tongue projection (Eq. 18 - 20)
127+
r1, r2 = self.generator.random(2)
128+
self.velocities[idx] = omega * self.velocities[idx] + \
129+
self.c1 * r1 * (self.g_best.solution - self.pop[idx].solution) + \
130+
self.c2 * r2 * (self.pop_personal[idx].solution - self.pop[idx].solution)
131+
delta_v_squared = (self.velocities[idx] ** 2 - self.prev_velocities[idx] ** 2)
132+
tongue_step = delta_v_squared / (2 * (a + self.EPSILON))
133+
self.pop[idx].solution = self.pop[idx].solution + tongue_step
134+
self.prev_velocities[idx] = self.velocities[idx].copy()
135+
136+
# Adjust boundaries (Line 32)
137+
for idx in range(self.pop_size):
138+
self.pop[idx].solution = self.correct_solution(self.pop[idx].solution)
139+
if self.mode not in self.AVAILABLE_MODES:
140+
self.pop[idx].target = self.get_target(self.pop[idx].solution)
141+
# Update fitness in parallel modes
142+
if self.mode in self.AVAILABLE_MODES:
143+
self.pop = self.update_target_for_population(self.pop)
144+
145+
# Update personal best positions
146+
for idx in range(self.pop_size):
147+
if self.compare_target(self.pop[idx].target, self.pop_personal[idx].target, self.problem.minmax):
148+
self.pop_personal[idx] = self.pop[idx].copy()
149+
150+
151+
class IChameleonSA(Optimizer):
152+
"""
153+
The original version of: Improved Chameleon Swarm Algorithm (ICSA)
154+
155+
Hyperparameters
156+
---------------
157+
+ epoch (int): Maximum number of iterations, default = 10000
158+
+ pop_size (int): Population size, default = 100
159+
+ beta (float): Lévy flight constant (Eq. 13), default = 1.5
160+
+ r_chaos (float): Control parameter for logistic mapping (Eq. 10), default = 0.3
161+
+ k_spiral (int): Variation coefficient for spiral search (Eq. 11), default = 5
162+
+ p1 (float): [0, 10.] Personal best influence (From PSO), default=2.0
163+
+ p2 (float): [0, 10.] Global best influence (From PSO), default=2.0
164+
165+
Warnings
166+
--------
167+
1. Despite being claimed as an improved version, this algorithm still requires too many
168+
parameters and relies on standard PSO update operators
169+
2. Additionally, its NFE per iteration is 3x times higher than typical algorithms,
170+
so users should be mindful of the execution time.
171+
172+
References
173+
----------
174+
.. [1] Chen, Yaodan, Li Cao, and Yinggao Yue. "Hybrid Multi-Objective Chameleon Optimization Algorithm
175+
Based on Multi-Strategy Fusion and Its Applications." Biomimetics 9.10 (2024): 583.
176+
https://doi.org/10.3390/biomimetics9100583
177+
178+
Examples
179+
~~~~~~~~
180+
>>> import numpy as np
181+
>>> from mealpy import FloatVar, ChameleonSA
182+
>>>
183+
>>> def objective_function(solution):
184+
>>> return np.sum(solution**2)
185+
>>>
186+
>>> problem_dict = {
187+
>>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
188+
>>> "minmax": "min",
189+
>>> "obj_func": objective_function
190+
>>> }
191+
>>>
192+
>>> model = ChameleonSA.IChameleonSA(epoch=1000, pop_size=50, r_chaos=0.5, k_spiral=10., p1=5.0, p2=3.0)
193+
>>> g_best = model.solve(problem_dict)
194+
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
195+
"""
196+
197+
def __init__(self, epoch=1000, pop_size=100, r_chaos: float=0.3, k_spiral: float=5.0, p1: float=2.0, p2: float=2.0, **kwargs):
198+
super().__init__(**kwargs)
199+
self.epoch = self.validator.check_int("epoch", epoch, [1, 100000])
200+
self.pop_size = self.validator.check_int("pop_size", pop_size, [10, 10000])
201+
self.r_chaos = self.validator.check_float("r_chaos", r_chaos, [0.0, 1.0])
202+
self.k_spiral = self.validator.check_float("k_spiral", k_spiral, [0., 100.0])
203+
self.p1 = self.validator.check_float("p1", p1, [0., 100.0])
204+
self.p2 = self.validator.check_float("p2", p2, [0., 100.0])
205+
self.set_parameters(["epoch", "pop_size", "r_chaos", "k_spiral", "p1", "p2"])
206+
self.sort_flag = False
207+
self.V = self.pop_personal = None
208+
209+
def initialization(self) -> None:
210+
if self.pop is None:
211+
# 4.1. Logistic Chaotic Map Initialization (Eq. 9 & 10)
212+
l_seq = self.generator.random((self.pop_size, self.problem.n_dims))
213+
for j in range(self.pop_size - 1):
214+
l_seq[j + 1] = self.r_chaos * l_seq[j] * (1 - l_seq[j]) # Eq. (10)
215+
pop_pos = self.problem.lb + l_seq * (self.problem.ub - self.problem.lb) # Eq. (9)
216+
self.pop = []
217+
for idx in range(self.pop_size):
218+
pos_new = self.correct_solution(pop_pos[idx])
219+
self.pop.append(self.generate_empty_agent(pos_new))
220+
if self.mode not in self.AVAILABLE_MODES:
221+
self.pop[idx].target = self.get_target(pos_new)
222+
# Update fitness in parallel modes
223+
if self.mode in self.AVAILABLE_MODES:
224+
self.pop = self.update_target_for_population(self.pop)
225+
self.V = np.zeros((self.pop_size, self.problem.n_dims))
226+
self.pop_personal = self.pop.copy()
227+
228+
def evolve(self, epoch):
229+
mu = np.exp(-(3.5 * epoch/ self.epoch) ** 3) # Eq. (2)
230+
231+
# Phase 1: Search for prey
232+
mean_fit = np.mean([agent.target.fitness for agent in self.pop])
233+
for idx in range(self.pop_size):
234+
if self.compare_fitness(self.pop[idx].target.fitness, mean_fit, self.problem.minmax):
235+
r1, r2 = self.generator.random(2)
236+
pos_new = self.pop[idx].solution + self.p1 * r1 * (self.pop_personal[idx].solution - self.g_best.solution) + \
237+
self.p2 * r1 * (self.g_best.solution - self.pop[idx].solution)
238+
else:
239+
ll = self.generator.uniform(-1, 1)
240+
sgn = np.sign(self.generator.random() - 0.5)
241+
pos_new = self.g_best.solution + np.exp(self.k_spiral * ll) * np.cos(2 * np.pi * ll) * (self.g_best.solution - self.pop[idx].solution) * sgn
242+
self.pop[idx].solution = pos_new
243+
244+
# Phase 2: Chameleon eyes' rotation
245+
c_t = 0.075 * (1 + np.cos((np.pi * epoch) / self.epoch))
246+
pop_new = []
247+
for idx in range(self.pop_size):
248+
# Levy flight step calculation
249+
levy_step = self.get_levy_flight_step(1.5, multiplier=1, size=self.problem.n_dims, case=-1)
250+
# Rotation & Levy flight combination (Eq. 15)
251+
xl = self.pop[idx].solution + c_t * (self.g_best.solution - self.pop[idx].solution) * levy_step
252+
pos_new = self.correct_solution(xl)
253+
agent = self.generate_empty_agent(pos_new)
254+
pop_new.append(agent)
255+
# Greedy selection (Eq. 16)
256+
if self.mode not in self.AVAILABLE_MODES:
257+
agent.target = self.get_target(pos_new)
258+
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
259+
# Parallel mode
260+
if self.mode in self.AVAILABLE_MODES:
261+
pop_new = self.update_target_for_population(pop_new)
262+
self.pop = self.greedy_selection_population(self.pop, pop_new, self.problem.minmax)
263+
264+
# Phase 3: Hunting prey
265+
omega_t = (1 - epoch / self.epoch) ** (2 * np.sqrt(epoch / self.epoch))
266+
lambda_t = (1 - epoch / self.epoch) ** np.sqrt(epoch / self.epoch)
267+
a = 2590 * (1 - np.exp(-np.log(epoch)))
268+
269+
for idx in range(self.pop_size):
270+
# Update velocity (Eq. 18)
271+
r1, r2 = self.generator.random(2)
272+
V_new = lambda_t * self.V[idx] + omega_t * r1 * (self.g_best.solution - self.pop[idx].solution) + \
273+
omega_t * r2 * (self.pop_personal[idx].solution - self.pop[idx].solution)
274+
# Update position (Eq. 7)
275+
if a == 0:
276+
X_hunt = self.pop[idx].solution
277+
else:
278+
X_hunt = self.pop[idx].solution + ((V_new ** 2) - (self.V[idx] ** 2)) / (2 * a)
279+
self.V[idx] = V_new
280+
# Refraction reverse learning (Eq. 20)
281+
X_refract = ((self.problem.lb + self.problem.ub) / 2) + ((self.problem.lb + self.problem.ub) / (2 * epoch)) - (X_hunt / epoch)
282+
X_hunt = self.correct_solution(X_hunt)
283+
X_refract = self.correct_solution(X_refract)
284+
agent_hunt = self.generate_agent(X_hunt)
285+
agent_refract = self.generate_agent(X_refract)
286+
287+
# Greedy selection (Eq. 16)
288+
if self.compare_target(agent_refract.target, agent_hunt.target, self.problem.minmax):
289+
self.pop[idx] = agent_refract
290+
else:
291+
self.pop[idx] = agent_hunt
292+
293+
# Evaluate fitness and update Personal
294+
for idx in range(self.pop_size):
295+
if self.compare_target(self.pop[idx].target, self.pop_personal[idx].target, self.problem.minmax):
296+
self.pop_personal[idx] = self.pop[idx].copy()

0 commit comments

Comments
 (0)