#!/usr/bin/env python
# Created by "Eren Kayacilar" at 20:50, 09/12/2025 ----------%
# Email: serenkay01@gmail.com %
# Github: https://github.com/ErenKayacilar %
# -----------------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
from mealpy.utils.opt_info import OptInfo
[docs]class OriginalDBO(Optimizer):
"""
The original version of: Dung Beetle Optimizer (DBO)
Parameters
----------
epoch : int
Maximum number of iterations, default = 10000.
pop_size : int
Number of population size, default = 100.
kk : float
Deflection coefficient in rolling behavior, in range [0.0, 2.0]. Default is 0.1.
bb : float
Attraction toward worst position, in range [0.0, 1.0]. Default is 0.3.
ss : float
Attraction factor toward local best position, in range [0.0, 1.0]. Default is 0.3.
Links
-----
1. https://doi.org/10.1007/s11227-022-04959-6
2. https://github.com/Lancephil/Dung-Beetle-Optimizer
References
~~~~~~~~~~
1. Xue, J., & Shen, B. (2022). Dung beetle optimizer: A new meta-heuristic
algorithm for global optimization. The Journal of Supercomputing, 79, 7305â7336.
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, DBO
>>>
>>> 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 = DBO.OriginalDBO(epoch=1000, pop_size=50, kk=0.1, bb=0.5, ss=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="Dung Beetle Optimizer", year=2022, difficulty="medium", kind="original")
def __init__(self, epoch: int = 10000, pop_size: int = 100, kk: float = 0.1, bb: float = 0.3, ss: float = 0.5, **kwargs: object) -> None:
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.kk = self.validator.check_float("kk", kk, [0.0, 2.0])
self.bb = self.validator.check_float("bb", bb, [0.0, 1.0])
self.ss = self.validator.check_float("ss", ss, [0.0, 1.0])
self.set_parameters(["epoch", "pop_size", "kk", "bb", "ss"])
self.sort_flag = True
# Previous positions x(tâ1), used in rolling behavior
self._prev_positions = None
[docs] def initialization(self):
if self.pop is None:
self.pop = self.generate_population(self.pop_size)
# Initialize previous positions x(tâ1) on the first call
if self._prev_positions is None:
self._prev_positions = np.array([agent.solution.copy() for agent in self.pop])
[docs] def evolve(self, epoch: int):
"""
The main operations (equations) of the algorithm.
Inherited from Optimizer class.
Args:
epoch (int): The current iteration.
"""
# Local best and local worst
_, best, worst = self.get_special_agents(self.pop, n_best=1, n_worst=1, minmax=self.problem.minmax)
x_best = best[0].solution
x_worst = worst[0].solution
# Split population into four behavioral groups: ball-rolling, breeding, foraging, and stealing dung beetles.
n_roll = self.pop_size // 4
n_breed = self.pop_size // 4 + n_roll
n_forage = self.pop_size // 4 + n_breed
pop_new = []
## R for area
RR = 1.0 - epoch / self.epoch
## Bound for breeding and spawning area (Eq. 3)
Lb_star = np.maximum(x_best * (1 - RR), self.problem.lb)
Ub_star = np.minimum(x_best * (1 + RR), self.problem.ub)
## Bound for foraging area (Eq. 5)
Lb_b = np.maximum(self.g_best.solution * (1 - RR), self.problem.lb)
Ub_b = np.minimum(self.g_best.solution * (1 + RR), self.problem.ub)
for idx in range(self.pop_size):
x_curr = self.pop[idx].solution
x_new = x_curr.copy() # Default to current position if no update occurs
if idx <= n_roll:
# Ball-rolling dung beetles
delta = self.generator.random()
if delta < 0.9: # Eq. 1
alpha = 1 if self.generator.random() > 0.5 else -1
delta_x = np.abs(x_curr - x_worst)
x_new = x_curr + alpha * self.kk * self._prev_positions[idx] + self.bb * delta_x
else: # Eq. 2
theta = self.generator.random() * np.pi
if np.abs(theta) > 1e-6 and np.abs(theta - np.pi / 2) > 1e-6 and np.abs(theta - np.pi) > 1e-6:
x_new = x_curr + np.tan(theta) * np.abs(x_curr - self._prev_positions[idx])
elif idx <= n_breed:
# Breeding dung beetles. Eq. 4
b1 = self.generator.random(self.problem.n_dims)
b2 = self.generator.random(self.problem.n_dims)
x_new = x_best + b1 * (x_curr - Lb_star) + b2 * (x_curr - Ub_star)
x_new = np.maximum(x_new, Lb_star)
x_new = np.minimum(x_new, Ub_star)
elif idx <= n_forage:
# Small dung beetle (Eq 6)
C1 = self.generator.normal()
C2 = self.generator.random(self.problem.n_dims)
x_new = x_curr + C1 * (x_curr - Lb_b) + C2 * (x_curr - Ub_b)
else:
# Stealing dung beetles (Eq. 7)
g_vec = self.generator.normal(size=self.problem.n_dims)
x_new = self.g_best.solution + self.ss * g_vec * (np.abs(x_curr - x_best) + np.abs(x_curr - self.g_best.solution))
## Set up bound
if idx <= n_roll or idx > n_breed:
x_new = np.maximum(x_new, self.problem.lb)
x_new = np.minimum(x_new, self.problem.ub)
## Correct solution based on problem
x_new = self.correct_solution(x_new)
agent = self.generate_empty_agent(x_new)
pop_new.append(agent)
self._prev_positions[idx] = x_curr.copy() # Update previous position for next iteration
# In sequential modes: evaluate and greedy-update immediately
if self.mode not in self.AVAILABLE_MODES:
agent.target = self.get_target(x_new)
self.pop[idx] = self.get_better_agent(agent, self.pop[idx], self.problem.minmax)
# In parallel modes: evaluate batch and greedy-select
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)