#!/usr/bin/env python
# Created by "Mehmet Ali Topkara" at 09:00, 09/12/2025 ---%
# Email: mehmetalitopkara080@gmail.com %
# Github: https://github.com/MAliTopkara %
# --------------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
from mealpy.utils.opt_info import OptInfo
[docs]class OriginalGJA(Optimizer):
"""
The original version of: Gekko Japonicus Algorithm (GJA)
Parameters
----------
epoch : int
Maximum number of iterations. Default is 10000.
pop_size : int
Population size (number of trees). Default is 100.
beta_start : float
Starting value for the beta parameter, in range [1.0, 1.5]. Default is 1.2.
beta_end : float
Ending value for the beta parameter, in range [0.1, 0.5]. Default is 0.3.
alpha_ratio : float
Ratio to calculate alpha from beta, in range [0.4, 0.8]. Default is 0.6.
Note
----
The algorithm draws inspiration from the predation strategies and survival behaviors
of the Gekko japonicus (Japanese gecko). It simulates various biological behaviors including:
1. Hybrid locomotion patterns (Levy flight + Gaussian perturbation)
2. Directional olfactory guidance
3. Implicit group advantage tendencies
4. Tail autotomy mechanism for escaping local optima
5. Historical memory injection for maintaining diversity
Links
-----
1. https://doi.org/10.1007/s42235-025-00805-6
2. https://github.com/zhy1109/Gekko-japonicusalgorithm
References
----------
1. Zhang, K., Zhao, H., Li, X., Fu, C. and Jin, J., 2025. Gekko Japonicus Algorithm: A Novel
Nature-inspired Algorithm for Engineering Problems and Path Planning. Journal of Bionic Engineering.
Examples
--------
>>> import numpy as np
>>> from mealpy import FloatVar, GJA
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(lb=(-100.,) * 30, ub=(100.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = GJA.OriginalGJA(epoch=1000, pop_size=30)
>>> 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="Gekko Japonicus Algorithm", year=2025, difficulty="medium", kind="original")
def __init__(self, epoch: int = 10000, pop_size: int = 100, beta_start: float = 1.2,
beta_end: float = 0.3, alpha_ratio: float = 0.6, **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.beta_start = self.validator.check_float("beta_start", beta_start, [0.5, 2.0])
self.beta_end = self.validator.check_float("beta_end", beta_end, [0.1, 1.0])
self.alpha_ratio = self.validator.check_float("alpha_ratio", alpha_ratio, [0.1, 1.0])
self.set_parameters(["epoch", "pop_size", "beta_start", "beta_end", "alpha_ratio"])
self.sort_flag = False
[docs] def initialize_variables(self) -> None:
"""Initialize variables before the main loop."""
self.memory = []
self.decay = np.linspace(1, 0, self.epoch) ** 2
self.range_bound = self.problem.ub - self.problem.lb
[docs] def evolve(self, epoch: int):
# Equation (4)
t_ratio = epoch / self.epoch
beta = self.beta_start - ((self.beta_start - self.beta_end) * ((np.cosh(t_ratio * 5) - 1) / (np.cosh(5) - 1)))
alpha = beta * self.alpha_ratio
decay_value = self.decay[epoch - 1]
fitness_values = np.array([agent.target.fitness for agent in self.pop])
if self.problem.minmax == "min":
sorted_idx = np.argsort(fitness_values)
else:
sorted_idx = np.argsort(fitness_values)[::-1]
pop_new = []
for idx in range(self.pop_size):
# Stage 1
if self.generator.random() < 0.5:
step = self.get_levy_flight_step(beta=beta, multiplier=alpha, size=self.problem.n_dims, case=-1)
else:
step = self.generator.normal(0, 1, self.problem.n_dims) * alpha * self.range_bound/np.sqrt(epoch)
pos_new = self.pop[idx].solution + step
# Stage 2
if epoch % 3 == 0:
dir_factor = np.sign(self.g_best.solution - pos_new)
pos_new += (0.3 * self.generator.random(self.problem.n_dims) * dir_factor * self.range_bound)
# Stage 3
scent_idx = int(np.ceil(self.generator.random() ** 2 * self.pop_size)) - 1
scent_idx = np.clip(scent_idx, 0, self.pop_size - 1)
scent_source = self.pop[sorted_idx[scent_idx]].solution
pos_new += 0.5 * decay_value * (scent_source - pos_new)
# Stage 4
dist_to_best = np.linalg.norm(pos_new - self.g_best.solution)
dist_threshold = 0.1 * np.linalg.norm(self.range_bound)
if dist_to_best > dist_threshold:
mut_prob = 1 - decay_value
mut_mask = self.generator.random(self.problem.n_dims) < mut_prob
mut_dims = np.where(mut_mask)[0]
if len(mut_dims) > 0:
pos_new[mut_dims] = self.g_best.solution[mut_dims] + self.generator.normal(0, 1, len(mut_dims)) * self.range_bound[mut_dims] / epoch
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)
# Stage 5
if epoch % 5 == 0:
if self.problem.minmax == "min":
top5_idx = np.argsort(fitness_values)[:5]
else:
top5_idx = np.argsort(fitness_values)[-5:][::-1]
for idx in top5_idx:
pos = self.pop[idx].solution.copy()
if not any(np.allclose(pos, m, rtol=1e-10) for m in self.memory):
self.memory.append(pos)
self.memory = self.memory[:5]
if len(self.memory) > 0:
n_worst = max(1, int(np.ceil(0.1 * self.pop_size)))
if self.problem.minmax == "min":
worst_idx = np.argsort(fitness_values)[-n_worst:]
else:
worst_idx = np.argsort(fitness_values)[:n_worst]
for idx in worst_idx:
mem_idx = self.generator.integers(0, len(self.memory))
new_pos = self.correct_solution(self.memory[mem_idx])
self.pop[idx] = self.generate_agent(new_pos)