Source code for mealpy.swarm_based.CCO

#!/usr/bin/env python
# Created by "Karahan Ballı" on 07/01/2026
# Github: https://github.com/karahanballi
# ---------------------------------------%
# Updated by "Thieu" on 12/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 OriginalCCO(Optimizer): """ The original version of: Cuckoo Catfish Optimizer (CCO) Parameters ---------- epoch : int Maximum number of iterations, default = 10000. pop_size : int Number of population size, default = 100. alpha : float Good range (0, 10.0), alpha parameter, default = 1.34 (as in Matlab code). beta : float Good range (0, 10.0), beta parameter, default = 0.3 (as in Matlab code). Danger ------ 1. Excessive Complexity: This is the most complex and convoluted algorithm we have ever implemented. It is packed with nested operators that seem entirely disconnected from the actual behavior of a Cuckoo Catfish. 2. Arbitrary Logic: We get the distinct impression that the authors simply fabricated the equations and an excessive number of if-else conditions just to force the algorithm to perform well. 3. Lack of Conceptual Alignment: While the algorithm may appear mathematically sound, it lacks any real connection to its stated inspiration, the Cuckoo Catfish. We would advise researchers, especially those new to the field to avoid using such overly complex heuristics for development. 4. Computational Inefficiency: A major issue is that the actual computational complexity does not align with the claims made in the paper. The excessive sorting processes within the population update loops make the algorithm significantly slower than others. 5. Lack of Parallelizability: Furthermore, the algorithm is not inherently parallelizable, as the population updates are strictly interdependent. Links ----- 1. https://www.mathworks.com/matlabcentral/fileexchange/176828-cuckoo-catfish-optimizer-a-new-meta-heuristic-optimization 2. https://doi.org/10.1007/s10462-025-11291-x References ---------- 1. Wang, T. L., Gu, S. W., Liu, R. J., Chen, L. Q., Wang, Z., & Zeng, Z. Q. (2025). Cuckoo catfish optimizer: a new meta-heuristic optimization algorithm. Artificial Intelligence Review, 58(10), 326. Examples -------- :: import numpy as np from mealpy import FloatVar, CCO def objective_function(solution): return np.sum(solution**2) problem_dict = { "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="x"), "minmax": "min", "obj_func": objective_function, } model = CCO.OriginalCCO(epoch=1000, pop_size=50, alpha=0.5, beta=1.0) g_best = model.solve(problem_dict) print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}") """ OPT_INFO = OptInfo(name="Cuckoo Catfish Optimizer", year=2025, difficulty="hard", kind="original", scientific_status="questionable", concerns=( ScientificConcern.LACK_OF_NOVELTY, ScientificConcern.QUESTIONABLE_MATH, ScientificConcern.POOR_REPRODUCIBILITY, ScientificConcern.AMBIGUOUS_METHODOLOGY )) def __init__(self, epoch=10000, pop_size=100, alpha=1.34, beta=0.3, **kwargs): super().__init__(**kwargs) self.epoch = self.validator.check_int("epoch", epoch, [1, 1000000]) self.pop_size = self.validator.check_int("pop_size", pop_size, [10, 100000]) self.alpha = self.validator.check_float("alpha", alpha, (0, 10.0)) self.beta = self.validator.check_float("beta", beta, (0, 10.0)) self.set_parameters(["epoch", "pop_size", "alpha", "beta"]) self.sort_flag = False self.is_parallelizable = False
[docs] def before_main_loop(self): """ Initialize algorithm-specific variables based on the MATLAB initialization block. """ self.x = np.zeros(self.pop_size) self.y = np.zeros(self.pop_size) # Reproducing MATLAB's theta, x, and y initialization for i in range(self.pop_size): # i+1 to match MATLAB's 1-based index calculation theta = (1 - 10 * (i + 1) / self.pop_size) * np.pi r = self.alpha * np.exp(self.beta * theta / 3) self.x[i] = r * np.cos(theta) self.y[i] = r * np.sin(theta) self.s = 0 self.z = 0 self.tt = 0 # Calculate initial Dis and Lx pop_pos = np.array([ag.solution for ag in self.pop]) # Dis = abs(mean(mean((PopPos-BestX)./(WorstX-BestX+eps)))) diff = self.g_worst.solution - self.g_best.solution + self.EPSILON self.Dis = np.abs(np.mean((pop_pos - self.g_best.solution) / diff)) self.Lx = np.abs(self.generator.standard_normal()) * self.generator.random()
[docs] def evolve(self, epoch): """ The main evolution step called by the Mealpy framework. """ # Time-dependent parameters C = 1.0 - (epoch / self.epoch) T = (1.0 - np.sin((np.pi * epoch) / (2.0 * self.epoch))) ** (epoch / self.epoch) # Dynamic probability for catfish mechanism (die probability) if self.tt < 15: die = 0.02 * T else: die = 0.02 C = 0.8 _, _, worsts = self.get_special_agents(self.pop, n_best=1, n_worst=1, minmax=self.problem.minmax) worst = worsts[0].solution best = self.g_best.solution n_val = 1 rng = self.generator for idx in range(self.pop_size): pos_ii = self.pop[idx].solution # Stochastic switch Q = 0 F = np.sign(0.5 - rng.random()) E = n_val * T + rng.random() R1 = rng.random(self.problem.n_dims) R4 = rng.random(self.problem.n_dims) r1 = rng.random() r2 = rng.random() S = np.sin(np.pi * R4 * C) kk = rng.permutation(self.pop_size) if rng.random() > C: diff = worst - best + self.EPSILON J_i = np.abs(np.mean((pos_ii - best) / diff)) if rng.random() > C: Cy = 1 / (np.pi * (1 + C ** 2)) if J_i > self.Dis: pos_new = best + F * S * (best - pos_ii) else: if self.Dis * self.Lx < J_i: pos_new = best * (1 + (T ** 5) * Cy * E) + F * (S * (best - pos_ii)) else: pos_new = best * (1 + (T ** 5) * rng.normal(0, C ** 2)) + F * S * (best - pos_ii) else: if rng.random() > C: if (idx + 1) % 2 == 1: r3 = rng.random() step = best - E * pos_ii pos_new = ((C / epoch) * (r1 * best - r3 * pos_ii) + (T ** 2) * self.get_levy_flight_step(1.5, multiplier=0.05, size=self.problem.n_dims, case=-1) * np.abs(step)) else: R2 = rng.random(self.problem.n_dims) R3 = rng.random(self.problem.n_dims) step = pos_ii - E * best DE = C * F pos_new = 0.5 * (best + self.pop[kk[0]].solution) + DE * (2 * R1 * step - (R2 / 2) * (DE * R3 - 1)) else: if rng.random() < rng.random(): if J_i < self.Dis: mean_pop = np.mean([ag.solution for ag in self.pop], axis=0) V = 2 * (rng.random() * (mean_pop - pos_ii) + rng.random() * (best - pos_ii)) else: V = 2 * (rng.random() * (self.pop[kk[1]].solution - self.pop[kk[2]].solution) + rng.random() * (self.pop[kk[0]].solution - pos_ii)) if self.compare_target(self.pop[idx].target, self.pop[kk[idx]].target): step = pos_ii - E * self.pop[kk[idx]].solution if (idx + 1) % 2 == 1: pos_new = (pos_ii + (T ** 2) * self.y[idx] * (1 - R1) * np.abs(step)) + F * R1 * step / 2 + V * J_i / epoch else: pos_new = (pos_ii + (T ** 2) * self.x[idx] * (1 - R1) * np.abs(step)) + F * R1 * step / 2 + V * J_i / epoch else: step = self.pop[kk[idx]].solution - E * pos_ii if (idx + 1) % 2 == 1: pos_new = (self.pop[kk[idx]].solution + (T ** 2) * self.y[idx] * (1 - R1) * np.abs(step)) + F * R1 * step / 2 + V * J_i / epoch else: pos_new = (self.pop[kk[idx]].solution + (T ** 2) * self.x[idx] * (1 - R1) * np.abs(step)) + F * R1 * step / 2 + V * J_i / epoch self.s += 1 if self.s > 10: rp1, rp2 = rng.choice(self.pop_size, 2, replace=False) lesp1 = r1 * self.pop[rp1].solution + (1 - r1) * self.pop[rp2].solution pos_new = np.round(lesp1) + F * r1 * R1 / (epoch ** 4) * pos_new self.s = 0 else: pop_sorted, _ = self.get_sorted_population(self.pop, self.problem.minmax) A2 = rng.integers(4) if A2 == 3: Q = 1 A1 = rng.integers(4) mean_pop = np.mean([ag.solution for ag in self.pop], axis=0) D = np.array([pop_sorted[0].solution, pop_sorted[1].solution, pop_sorted[2].solution, mean_pop]) B = D[A1] Rt1 = rng.choice(np.arange(1, 361), self.problem.n_dims, replace=True) * np.pi / 360 Rt2 = rng.choice(np.arange(1, 361), self.problem.n_dims, replace=True) * np.pi / 360 w = 1 - ((np.exp(epoch / self.epoch) - 1) / (np.exp(1) - 1)) ** 2 rand_val = rng.random() if rand_val < 0.33: pos_new = B + 2 * w * F * np.cos(Rt1) * np.sin(Rt2) * (B - pos_ii) elif rand_val < 0.66: pos_new = B + 2 * w * F * np.sin(Rt1) * np.cos(Rt2) * (B - pos_ii) else: pos_new = B + 2 * w * F * np.cos(Rt2) * (B - pos_ii) self.z += 1 if self.z > 5: rp3 = rng.choice(self.pop_size) pos_new = best * (1.0 - (1.0 - 1.0 / (self.pop[rp3].solution + self.EPSILON)) * R1) self.z = 0 else: if rng.random() > C: if rng.random() > C: pos_new = self.pop[kk[2]].solution + np.abs(rng.normal()) * (best - pos_ii + self.pop[kk[0]].solution - self.pop[kk[1]].solution) else: Z2 = rng.random(self.problem.n_dims) < rng.random() pos_new = Z2 * (self.pop[kk[2]].solution + np.abs(rng.normal()) * (self.pop[kk[0]].solution - self.pop[kk[1]].solution)) + (~Z2) * pos_ii else: Z1 = int(rng.random() < rng.random()) pos_new = pos_ii + (Z1 * np.abs(rng.normal()) * ((best + self.pop[kk[0]].solution) / 2 - self.pop[kk[1]].solution) + rng.random() / 2 * (self.pop[kk[2]].solution - self.pop[kk[3]].solution)) if rng.random() > C or self.tt > 0.8 * self.pop_size: mask = rng.random(self.problem.n_dims) < (0.2 * C + 0.2) pos_new = np.where(mask, pos_new, pos_ii) # Die condition block if rng.random() < die: if rng.random() > C: pos_new = rng.random() * (self.problem.ub - self.problem.lb) + self.problem.lb else: term1 = self.get_levy_flight_step(beta=1.5, multiplier=0.05, case=-1) * int(r1 > r2) term2 = np.abs(rng.normal()) * int(r1 <= r2) best_vec = best * (term1 + term2) Upc = np.max(best_vec) Lowc = np.min(best_vec) pos_new = np.ones(self.problem.n_dims) * (rng.random() * (Upc - Lowc) + Lowc) # Boundary control pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) # Replacement logic if self.compare_target(agent.target, self.pop[idx].target): self.pop[idx] = agent # Q=1 logic: Replace the current worst in population if Q == 1: _, worst_idx = self.get_worst_agent(self.pop, self.problem.minmax) self.pop[worst_idx] = agent.copy() self.tt = 0 else: self.tt += 1 # Update Best and worst sequentially _, bests, worsts = self.get_special_agents(self.pop, n_best=1, n_worst=1, minmax=self.problem.minmax) best = bests[0].solution worst = worsts[0].solution