Skip to content

Commit b3d61fb

Browse files
authored
Merge pull request #213 from ErenKayacilar/feature/dung-beetle-optimizer
[FEAT] Add Dung Beetle Optimizer (DBO) to swarm_based
2 parents 20b7b7b + 0925ea3 commit b3d61fb

2 files changed

Lines changed: 215 additions & 1 deletion

File tree

mealpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from .math_based import (AOA, CEM, CGO, CircleSA, GBO, HC, INFO, PSS, RUN, SCA, SHIO, TS)
3939
from .physics_based import (ArchOA, ASO, CDO, EFO, EO, EVO, FLA, HGSO, MVO, NRO, RIME, SA, TWO, WDO, ESO, SOO, MSO)
4040
from .swarm_based import (ABC, ACOR, AGTO, ALO, AO, ARO, AVOA, BA, BeesA, BES, BFO, BSA, COA, CoatiOA, CSA, CSO,
41-
DMOA, DO, EHO, ESOA, FA, FFA, FFO, FOA, FOX, GJO, GOA, GTO, GWO, HBA, HGS, HHO, JA,
41+
DBO, DMOA, DO, EHO, ESOA, FA, FFA, FFO, FOA, FOX, GJO, GOA, GTO, GWO, HBA, HGS, HHO, JA,
4242
MFO, MGO, MPA, MRFO, MSA, MShOA, NGO, NMRA, OOA, PFA, POA, PSO, SCSO, SeaHO, ServalOA, SFO,
4343
SHO, SLO, SRSR, SSA, SSO, SSpiderA, SSpiderO, STO, TDO, TSO, WaOA, WOA, ZOA,
4444
EPC, SMO, SquirrelSA, FDO, ChOA, RSA, GJA, BWO)

mealpy/swarm_based/DBO.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#!/usr/bin/env python
2+
# Created by "Eren Kayacilar" at 20:50, 09/12/2025 ----------%
3+
# Email: serenkay01@gmail.com %
4+
# Github: https://github.com/ErenKayacilar %
5+
# -----------------------------------------------------------%
6+
7+
import numpy as np
8+
from mealpy.optimizer import Optimizer
9+
10+
11+
12+
class OriginalDBO(Optimizer):
13+
"""
14+
The original version of: Dung Beetle Optimizer (DBO)
15+
16+
Links:
17+
1. https://doi.org/10.1007/s11227-022-04959-6
18+
2. https://github.com/Lancephil/Dung-Beetle-Optimizer
19+
20+
Hyper-parameters should be fine-tuned in approximate ranges to obtain
21+
faster convergence toward the global optimum:
22+
+ alpha (float): [-2.0, 2.0], direction / step influence, default = 1.0
23+
+ k (float): (0.0, 0.2], deflection coefficient in rolling behavior,
24+
default = 0.1
25+
+ b_const (float): (0.0, 1.0], attraction toward best / worst positions,
26+
default = 0.5
27+
28+
Examples
29+
~~~~~~~~
30+
>>> import numpy as np
31+
>>> from mealpy import FloatVar, DBO
32+
>>>
33+
>>> def objective_function(solution):
34+
>>> return np.sum(solution**2)
35+
>>>
36+
>>> problem_dict = {
37+
>>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
38+
>>> "obj_func": objective_function,
39+
>>> "minmax": "min",
40+
>>> }
41+
>>>
42+
>>> model = DBO.OriginalDBO(epoch=1000, pop_size=50,
43+
>>> alpha=1.0, k=0.1, b_const=0.5)
44+
>>> g_best = model.solve(problem_dict)
45+
>>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}")
46+
>>> print(f"Solution: {model.g_best.solution}, "
47+
>>> f"Fitness: {model.g_best.target.fitness}")
48+
49+
References
50+
~~~~~~~~~~
51+
[1] Xue, J., & Shen, B. (2022). Dung beetle optimizer: A new meta-heuristic
52+
algorithm for global optimization. The Journal of Supercomputing,
53+
79, 7305–7336.
54+
"""
55+
56+
def __init__(self, epoch: int = 10000, pop_size: int = 100, alpha: float = 1.0, k: float = 0.1, b_const: float = 0.5, **kwargs: object) -> None:
57+
"""
58+
Args:
59+
epoch (int): maximum number of iterations, default = 10000
60+
pop_size (int): population size, default = 100
61+
alpha (float): direction / step influence, default = 1.0
62+
k (float): deflection coefficient in rolling behavior, default = 0.1
63+
b_const (float): attraction coefficient, default = 0.5
64+
"""
65+
super().__init__(**kwargs)
66+
self.epoch = self.validator.check_int("epoch", epoch, [1, 100000])
67+
self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 10000])
68+
69+
self.alpha = self.validator.check_float("alpha", alpha, (-2.0, 2.0))
70+
self.k = self.validator.check_float("k", k, (0.0, 0.2))
71+
self.b_const = self.validator.check_float("b_const", b_const, (0.0, 1.0))
72+
73+
self.set_parameters(["epoch", "pop_size", "alpha", "k", "b_const"])
74+
75+
# Similar to some other swarm-based optimizers
76+
self.sort_flag = True
77+
self.is_parallelizable = False
78+
79+
# Previous positions x(t−1), used in rolling behavior
80+
self._prev_positions = None
81+
82+
def initialization(self):
83+
"""
84+
Initialization step of the algorithm.
85+
This method is automatically called inside solve().
86+
"""
87+
if self.pop is None:
88+
self.pop = self.generate_population(self.pop_size)
89+
90+
# Initialize previous positions x(t−1) on the first call
91+
if self._prev_positions is None:
92+
self._prev_positions = np.array(
93+
[agent.solution.copy() for agent in self.pop]
94+
)
95+
96+
97+
def evolve(self, epoch: int):
98+
"""
99+
The main operations (equations) of the algorithm.
100+
Inherited from Optimizer class.
101+
102+
Args:
103+
epoch (int): The current iteration.
104+
"""
105+
106+
pop_array = np.array([agent.solution for agent in self.pop])
107+
108+
# Global best / worst positions (bestX and worstX in the paper)
109+
g_best = self.g_best.solution
110+
g_worst = self.get_worst_agent(self.pop, self.problem.minmax).solution
111+
112+
n = self.pop_size
113+
idx = np.arange(n)
114+
self.generator.shuffle(idx)
115+
116+
# Split population into four behavioral groups:
117+
# ball-rolling, breeding, foraging, and stealing dung beetles.
118+
n_roll = n // 4
119+
n_breed = n // 4
120+
n_forage = n // 4
121+
n_steal = n - (n_roll + n_breed + n_forage)
122+
123+
idx_roll = idx[0:n_roll]
124+
idx_breed = idx[n_roll : n_roll + n_breed]
125+
idx_forage = idx[n_roll + n_breed : n_roll + n_breed + n_forage]
126+
idx_steal = idx[n_roll + n_breed + n_forage :]
127+
128+
pop_new = []
129+
130+
# ===== 1) Ball-rolling dung beetles =====
131+
for i in idx_roll:
132+
x_t = pop_array[i]
133+
x_t_1 = self._prev_positions[i]
134+
135+
# Rolling behavior: a simple approximation of the original equations
136+
step = self.alpha * self.k * x_t_1 + self.b_const * np.abs(x_t - g_worst)
137+
new_pos = x_t + step
138+
new_pos = self.correct_solution(new_pos)
139+
agent = self.generate_empty_agent(new_pos)
140+
if self.mode not in self.AVAILABLE_MODES:
141+
agent.target = self.get_target(agent.solution)
142+
pop_new.append(agent)
143+
144+
# ===== 2) Breeding (reproduction) dung beetles =====
145+
for i in idx_breed:
146+
x_t = pop_array[i]
147+
R = 1.0 - epoch / self.epoch
148+
lb = self.problem.lb
149+
ub = self.problem.ub
150+
151+
Lb_star = np.maximum(g_best * (1 - R), lb)
152+
Ub_star = np.minimum(g_best * (1 + R), ub)
153+
154+
low = np.minimum(Lb_star, Ub_star)
155+
high = np.maximum(Lb_star, Ub_star)
156+
157+
new_pos = self.generator.uniform(low, high)
158+
new_pos = self.correct_solution(new_pos)
159+
agent = self.generate_empty_agent(new_pos)
160+
if self.mode not in self.AVAILABLE_MODES:
161+
agent.target = self.get_target(agent.solution)
162+
pop_new.append(agent)
163+
164+
# ===== 3) Foraging dung beetles =====
165+
for i in idx_forage:
166+
x_t = pop_array[i]
167+
R = 1.0 - epoch / self.epoch
168+
lb = self.problem.lb
169+
ub = self.problem.ub
170+
171+
Lb_b = np.maximum(g_best * (1 - R), lb)
172+
Ub_b = np.minimum(g_best * (1 + R), ub)
173+
174+
low_b = np.minimum(Lb_b, Ub_b)
175+
high_b = np.maximum(Lb_b, Ub_b)
176+
177+
rand_pos = self.generator.uniform(low_b, high_b)
178+
new_pos = x_t + self.generator.random() * (rand_pos - x_t)
179+
180+
new_pos = self.correct_solution(new_pos)
181+
agent = self.generate_empty_agent(new_pos)
182+
if self.mode not in self.AVAILABLE_MODES:
183+
agent.target = self.get_target(agent.solution)
184+
pop_new.append(agent)
185+
186+
# ===== 4) Stealing dung beetles =====
187+
for i in idx_steal:
188+
x_t = pop_array[i]
189+
S = 1.0
190+
g_vec = self.generator.normal(size=self.problem.n_dims)
191+
best_x_star = g_best
192+
193+
step = S * g_vec * (
194+
np.abs(x_t - best_x_star) + np.abs(x_t - g_best)
195+
)
196+
new_pos = g_best + step
197+
198+
new_pos = self.correct_solution(new_pos)
199+
agent = self.generate_empty_agent(new_pos)
200+
if self.mode not in self.AVAILABLE_MODES:
201+
agent.target = self.get_target(agent.solution)
202+
pop_new.append(agent)
203+
204+
# Evaluate the new population (in parallel modes this is done inside)
205+
pop_new = self.update_target_for_population(pop_new)
206+
207+
# Update previous positions x(t−1) before merging
208+
self._prev_positions = np.array([agent.solution.copy() for agent in self.pop])
209+
210+
# Merge old and new populations, then sort and trim to pop_size
211+
self.pop = self.get_sorted_and_trimmed_population(
212+
self.pop + pop_new, self.pop_size, self.problem.minmax
213+
)
214+

0 commit comments

Comments
 (0)