#!/usr/bin/env python
# Created by "Thieu" at 22:08, 28/08/2025 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
from mealpy.utils.opt_info import OptInfo, ScientificConcern
[docs]class OriginalSOO(Optimizer):
"""
The original version of: Stellar Oscillation Optimizer (SOO)
Parameters
----------
epoch : int
Maximum number of iterations, in range [1, 100000]. Default is 10000.
pop_size : int
Number of population size, in range [5, 10000]. Default is 100.
Danger
------
1. The MATLAB code in the link below by the author is completely different from the pseudocode in
the original paper. I don’t understand how the first author could write such an incorrect
implementation and still obtain good results. There are only two possibilities: either the author
fabricated the results in the paper, or the paper itself is fundamentally flawed.
2. For example, you can see equation number 8 — it involves taking the average of two new positions.
However, in the code, it is incorrectly implemented as position 1 plus half of position 2.
Even more concerning is that the pseudocode in the paper is completely different from the actual code.
The MATLAB coding quality is really poor. In the pseudocode, it states that the fitness
should be calculated and the global best as well as the top 3 best should be updated,
yet this is entirely missing in the code.
3. Therefore, I do not recommend users to use this algorithm, as it lacks integrity between
the results in the paper and the actual experimental implementation.
Links
-----
1. https://mathworks.com/matlabcentral/fileexchange/161921-stellar-oscillation-optimizer-meta-heuristic-optimimization
2. https://doi.org/10.1007/s10586-024-04976-5
References
----------
1. Rodan, A., Al-Tamimi, A. K., Al-Alnemer, L., & Mirjalili, S. (2025). Stellar oscillation
optimizer: a nature-inspired metaheuristic optimization algorithm. Cluster Computing, 28(6), 362.
Examples
--------
>>> import numpy as np
>>> from mealpy import FloatVar, SOO
>>>
>>> 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 = SOO.OriginalSOO(epoch=1000, pop_size=50)
>>> 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="Stellar Oscillation Optimizer", year=2025, difficulty="medium", kind="original",
scientific_status="questionable",
concerns=(
ScientificConcern.INCORRECT_EQUATIONS, ScientificConcern.CODE_PSEUDOCODE_MISMATCH,
ScientificConcern.AMBIGUOUS_METHODOLOGY, ScientificConcern.POOR_REPRODUCIBILITY
))
def __init__(self, epoch: int = 10000, pop_size: int = 100, **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.set_parameters(["epoch", "pop_size"])
self.sort_flag = False
[docs] def initialize_variables(self):
self.initial_period = 3
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
# Update period and angular frequency
caf = 2 * np.pi / (self.initial_period + 0.001 * epoch)
# Update scaling factor
scaler = 2 * (1.0 - epoch / self.epoch)
# Update positions of star oscillators
pop_new = []
for idx in range(self.pop_size):
r1 = self.generator.random(size=self.problem.n_dims)
r2 = self.generator.random(size=self.problem.n_dims)
r3 = self.generator.random(size=self.problem.n_dims)
# Calculate oscillation positions
osc1 = scaler * (caf * r1 - 1) * (self.pop[idx].solution - np.abs(r1 * np.sin(r2) * np.abs(r3 * self.g_best.solution)))
osc1_pos = self.g_best.solution - r1 * r3 * osc1
osc2 = scaler * (caf * r1 - 1) * (self.pop[idx].solution - np.abs(r1 * np.cos(r2) * np.abs(r3 * self.g_best.solution)))
osc2_pos = self.g_best.solution - r2 * r3 * osc2
pos_new = r3 * (osc1_pos + osc2_pos) / 2
pos_new = self.correct_solution(pos_new)
agent = self.generate_empty_agent(pos_new)
pop_new.append(agent)
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)
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)
# Get top 3 stars
_, best3, _ = self.get_special_agents(self.pop, n_best=3, minmax=self.problem.minmax)
# Perform oscillatory movement update
for idx in range(self.pop_size):
# Average of top star positions
avg3 = np.mean([agent.solution for agent in best3], axis=0)
# Select 3 random indices different from current
r1, r2, r3 = self.generator.choice(list(set(range(self.pop_size)) - {idx}), size=3, replace=False)
# Generate new position based on oscillatory movement
rf = self.generator.random()
pos_new = avg3 + 0.5 * (np.sin(rf * np.pi) * (self.pop[r1].solution - self.pop[r2].solution) +
np.cos((1 - rf) * np.pi) * (self.pop[r1].solution - self.pop[r3].solution))
## Probabilistic update
pos_new = np.where(self.generator.random(size=self.problem.n_dims) <= 0.5, pos_new, self.pop[idx].solution)
# Apply boundary constraints
pos_new = self.correct_solution(pos_new)
agent = self.generate_empty_agent(pos_new)
pop_new.append(agent)
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)
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)