#!/usr/bin/env python
# Created by "Ulaş Görkem Kazan" on 05/01/2026
# Github: https://github.com/gorkemulas2005
# --------------------------------------------------%
# Updated by "Thieu" on 16/07/2026
# Github: https://github.com/thieu1995
# --------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
from mealpy.utils.opt_info import OptInfo, ScientificConcern
[docs]class OriginalChameleonSA(Optimizer):
"""
The original version of: Chameleon Swarm Algorithm (ChameleonSA)
Parameters
----------
epoch : int
Maximum number of iterations, default = 10000.
pop_size : int
Number of population size, default = 100.
pp : float
Valid range [0, 1] Probability of the chameleon perceiving prey, default=0.1
p1 : float
Valid range [0, 5.0] Exploration control parameter 1 (From PSO), default=0.25.
p2 : float
Valid range [0, 5.0] Exploration control parameter 2 (From PSO), default=1.50.
c1 : float
Valid range [0, 5.0] Personal best influence (From PSO), default=1.75.
c2 : float
Valid range [0, 5.0] Global best influence (From PSO), default=1.75.
gama : float
Valid range [0, 2] Constant controlling the exploration rate decay over iterations, default=1.0.
alpha : float
Valid range [0, 10] Constant defining the steepness of the exploration decay curve, default=3.5.
rho : float
Valid range [0, 2] Positive number, default=1.0.
.. caution::
1. This algorithm essentially relies on the update operators of the PSO algorithm. It has too many
parameters, and the results are nowhere near as good as those presented in the paper.
2. Please note that the official MATLAB code deviates from the paper, using undocumented
modifications to artificially boost performance.
3. This pure implementation is provided specifically so users can independently evaluate
the algorithm's true performance based solely on the published mathematical model,
allowing you to verify whether the paper's claims and results are legitimate.
Links
-----
1. https://www.mathworks.com/matlabcentral/fileexchange/98014-chameleon-swarm-algorithm
2. https://doi.org/10.1016/j.eswa.2021.114685
References
----------
1. Braik, M. S. (2021). Chameleon Swarm Algorithm: A bio-inspired optimizer for solving
engineering design problems. Expert Systems with Applications, 174, 114685.
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, ChameleonSA
>>>
>>> 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 = 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)
>>> 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="Chameleon Swarm Algorithm", year=2021, difficulty="medium", kind="original",
scientific_status="questionable",
concerns=(
ScientificConcern.LACK_OF_NOVELTY, ScientificConcern.SUSPECTED_PLAGIARISM,
ScientificConcern.AMBIGUOUS_METHODOLOGY, ScientificConcern.POOR_REPRODUCIBILITY
))
def __init__(self, epoch=10000, pop_size=100, pp: float=0.1, p1: float=0.25, p2: float=1.50,
c1: float=1.75, c2: float=1.75, gama: float=1.0, alpha: float=3.5, rho: float=1.0, **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, [5, 100000])
self.pp = self.validator.check_float("pp", pp, [0, 1])
self.p1 = self.validator.check_float("p1", p1, [0, 10.])
self.p2 = self.validator.check_float("p2", p2, [0, 10.])
self.c1 = self.validator.check_float("c1", c1, [0, 10.])
self.c2 = self.validator.check_float("c2", c2, [0, 10.])
self.gama = self.validator.check_float("gama", gama, [0, 10.])
self.alpha = self.validator.check_float("alpha", alpha, [0, 10.])
self.rho = self.validator.check_float("rho", rho, [0, 10.])
self.set_parameters(["epoch", "pop_size", "pp", "p1", "p2", "c1", "c2", "gama", "alpha", "rho"])
self.sort_flag = True
self.pop_personal = None
self.velocities = None
self.prev_velocities = None
[docs] def before_main_loop(self):
self.pop_personal = self.pop.copy()
self.velocities = np.zeros((self.pop_size, self.problem.n_dims))
self.prev_velocities = np.zeros((self.pop_size, self.problem.n_dims))
[docs] def evolve(self, epoch):
"""
The main evolution step.
"""
# Dynamic parameters update
mu = self.gama * np.exp(-self.alpha * epoch / self.epoch) # Eq. 6
omega = (1 - epoch / self.epoch) ** (self.rho * np.sqrt(epoch / self.epoch)) # Eq. 19
a = 2590 * (1 - np.exp(-np.log(epoch + 1))) # Eq. 21 (acceleration)
for idx in range(self.pop_size):
# Phase 1: Search for prey (Eq. 3)
rr = self.generator.random()
r1, r2, r3 = self.generator.random(3)
if rr >= self.pp:
pos_new = self.pop[idx].solution + self.p1 * (self.pop_personal[idx].solution - self.g_best.solution) * r1 + \
self.p2 * (self.g_best.solution - self.pop[idx].solution) * r2
else:
direction = np.sign(self.generator.random(self.problem.n_dims) - 0.5)
pos_new = self.pop[idx].solution + mu * (((self.problem.ub - self.problem.lb) * r3 + self.problem.lb) * direction)
self.pop[idx].solution = pos_new
center = np.mean([agent.solution for agent in self.pop], axis=0)
for idx in range(self.pop_size):
# Phase 2: Chameleon eyes rotation (Eq. 11 - 15)
yc = self.pop[idx].solution - center # Eq. 13
theta = self.generator.random(self.problem.n_dims) * np.sign(self.generator.random(self.problem.n_dims) - 0.5) * np.pi # Eq. 15
# Apply rotation (Eq. 12)
yr = np.cos(theta) * yc
self.pop[idx].solution = yr + center # Eq. 11
for idx in range(self.pop_size):
# Phase 3: Hunting prey - Tongue projection (Eq. 18 - 20)
r1, r2 = self.generator.random(2)
self.velocities[idx] = omega * self.velocities[idx] + \
self.c1 * r1 * (self.g_best.solution - self.pop[idx].solution) + \
self.c2 * r2 * (self.pop_personal[idx].solution - self.pop[idx].solution)
delta_v_squared = (self.velocities[idx] ** 2 - self.prev_velocities[idx] ** 2)
tongue_step = delta_v_squared / (2 * (a + self.EPSILON))
self.pop[idx].solution = self.pop[idx].solution + tongue_step
self.prev_velocities[idx] = self.velocities[idx].copy()
# Adjust boundaries (Line 32)
for idx in range(self.pop_size):
self.pop[idx].solution = self.correct_solution(self.pop[idx].solution)
if self.mode not in self.AVAILABLE_MODES:
self.pop[idx].target = self.get_target(self.pop[idx].solution)
# Update fitness in parallel modes
if self.mode in self.AVAILABLE_MODES:
self.pop = self.update_target_for_population(self.pop)
# Update personal best positions
for idx in range(self.pop_size):
if self.compare_target(self.pop[idx].target, self.pop_personal[idx].target, self.problem.minmax):
self.pop_personal[idx] = self.pop[idx].copy()
[docs]class IChameleonSA(Optimizer):
"""
The original version of: Improved Chameleon Swarm Algorithm (ICSA)
Parameters
----------
epoch : int
Maximum number of iterations, default = 10000.
pop_size : int
Number of population size, default = 100.
beta : float
Lévy flight constant (Eq. 13), default = 1.5.
r_chaos : float
Control parameter for logistic mapping (Eq. 10), default = 0.3
k_spiral : int
Variation coefficient for spiral search (Eq. 11), default = 5
p1 : float
Valid range [0, 10.] Personal best influence (From PSO), default=2.0.
p2 : float
Valid range [0, 10.] Global best influence (From PSO), default=2.0.
Warnings
--------
1. Despite being claimed as an improved version, this algorithm still requires too many
parameters and relies on standard PSO update operators.
2. Additionally, its NFE per iteration is 3x times higher than typical algorithms,
so users should be mindful of the execution time.
References
----------
1. Chen, Yaodan, Li Cao, and Yinggao Yue. "Hybrid Multi-Objective Chameleon Optimization Algorithm
Based on Multi-Strategy Fusion and Its Applications." Biomimetics 9.10 (2024): 583.
https://doi.org/10.3390/biomimetics9100583
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, ChameleonSA
>>>
>>> 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 = ChameleonSA.IChameleonSA(epoch=1000, pop_size=50, r_chaos=0.5, k_spiral=10., p1=5.0, p2=3.0)
>>> g_best = model.solve(problem_dict)
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
"""
OPT_INFO = OptInfo(name="Improved Chameleon Swarm Algorithm", year=2024, difficulty="hard", kind="variant",
scientific_status="questionable",
concerns=(
ScientificConcern.FABRICATED_RESULTS, ScientificConcern.QUESTIONABLE_MATH,
ScientificConcern.AMBIGUOUS_METHODOLOGY, ScientificConcern.POOR_REPRODUCIBILITY
))
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):
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.r_chaos = self.validator.check_float("r_chaos", r_chaos, [0.0, 1.0])
self.k_spiral = self.validator.check_float("k_spiral", k_spiral, [0., 100.0])
self.p1 = self.validator.check_float("p1", p1, [0., 100.0])
self.p2 = self.validator.check_float("p2", p2, [0., 100.0])
self.set_parameters(["epoch", "pop_size", "r_chaos", "k_spiral", "p1", "p2"])
self.sort_flag = False
self.V = self.pop_personal = None
[docs] def initialization(self) -> None:
if self.pop is None:
# 4.1. Logistic Chaotic Map Initialization (Eq. 9 & 10)
l_seq = self.generator.random((self.pop_size, self.problem.n_dims))
for j in range(self.pop_size - 1):
l_seq[j + 1] = self.r_chaos * l_seq[j] * (1 - l_seq[j]) # Eq. (10)
pop_pos = self.problem.lb + l_seq * (self.problem.ub - self.problem.lb) # Eq. (9)
self.pop = []
for idx in range(self.pop_size):
pos_new = self.correct_solution(pop_pos[idx])
self.pop.append(self.generate_empty_agent(pos_new))
if self.mode not in self.AVAILABLE_MODES:
self.pop[idx].target = self.get_target(pos_new)
# Update fitness in parallel modes
if self.mode in self.AVAILABLE_MODES:
self.pop = self.update_target_for_population(self.pop)
self.V = np.zeros((self.pop_size, self.problem.n_dims))
self.pop_personal = self.pop.copy()
[docs] def evolve(self, epoch):
mu = np.exp(-(3.5 * epoch/ self.epoch) ** 3) # Eq. (2)
# Phase 1: Search for prey
mean_fit = np.mean([agent.target.fitness for agent in self.pop])
for idx in range(self.pop_size):
if self.compare_fitness(self.pop[idx].target.fitness, mean_fit, self.problem.minmax):
r1, r2 = self.generator.random(2)
pos_new = self.pop[idx].solution + self.p1 * r1 * (self.pop_personal[idx].solution - self.g_best.solution) + \
self.p2 * r1 * (self.g_best.solution - self.pop[idx].solution)
else:
ll = self.generator.uniform(-1, 1)
sgn = np.sign(self.generator.random() - 0.5)
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
self.pop[idx].solution = pos_new
# Phase 2: Chameleon eyes' rotation
c_t = 0.075 * (1 + np.cos((np.pi * epoch) / self.epoch))
pop_new = []
for idx in range(self.pop_size):
# Levy flight step calculation
levy_step = self.get_levy_flight_step(1.5, multiplier=1, size=self.problem.n_dims, case=-1)
# Rotation & Levy flight combination (Eq. 15)
xl = self.pop[idx].solution + c_t * (self.g_best.solution - self.pop[idx].solution) * levy_step
pos_new = self.correct_solution(xl)
agent = self.generate_empty_agent(pos_new)
pop_new.append(agent)
# Greedy selection (Eq. 16)
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
# Parallel mode
if self.mode in self.AVAILABLE_MODES:
pop_new = self.update_target_for_population(pop_new)
self.pop = self.greedy_selection_population(self.pop, pop_new, self.problem.minmax)
# Phase 3: Hunting prey
omega_t = (1 - epoch / self.epoch) ** (2 * np.sqrt(epoch / self.epoch))
lambda_t = (1 - epoch / self.epoch) ** np.sqrt(epoch / self.epoch)
a = 2590 * (1 - np.exp(-np.log(epoch)))
for idx in range(self.pop_size):
# Update velocity (Eq. 18)
r1, r2 = self.generator.random(2)
V_new = lambda_t * self.V[idx] + omega_t * r1 * (self.g_best.solution - self.pop[idx].solution) + \
omega_t * r2 * (self.pop_personal[idx].solution - self.pop[idx].solution)
# Update position (Eq. 7)
if a == 0:
X_hunt = self.pop[idx].solution
else:
X_hunt = self.pop[idx].solution + ((V_new ** 2) - (self.V[idx] ** 2)) / (2 * a)
self.V[idx] = V_new
# Refraction reverse learning (Eq. 20)
X_refract = ((self.problem.lb + self.problem.ub) / 2) + ((self.problem.lb + self.problem.ub) / (2 * epoch)) - (X_hunt / epoch)
X_hunt = self.correct_solution(X_hunt)
X_refract = self.correct_solution(X_refract)
agent_hunt = self.generate_agent(X_hunt)
agent_refract = self.generate_agent(X_refract)
# Greedy selection (Eq. 16)
if self.compare_target(agent_refract.target, agent_hunt.target, self.problem.minmax):
self.pop[idx] = agent_refract
else:
self.pop[idx] = agent_hunt
# Evaluate fitness and update Personal
for idx in range(self.pop_size):
if self.compare_target(self.pop[idx].target, self.pop_personal[idx].target, self.problem.minmax):
self.pop_personal[idx] = self.pop[idx].copy()