Source code for mealpy.game_based.THRO

#!/usr/bin/env python
# Created by "Thieu" at 21:42, 13/09/2025 ----------%                                                                               
#       Email: nguyenthieu2102@gmail.com            %                                                    
#       Github: https://github.com/thieu1995        %                         
# --------------------------------------------------%

import numpy as np
from mealpy.optimizer import Optimizer


[docs]class OriginalTHRO(Optimizer): """ The original version of: Tianji's Horse Racing Optimization (THRO) Parameters ---------- epoch : int Maximum number of iterations, in range [1, 100000]. Default is 750. pop_size : int Number of population size, in range [5, 10000]. Default is 100. Links ----- 1. https://www.mathworks.com/matlabcentral/fileexchange/181341-tianji-s-horse-racing-optimization-thro 2. https://doi.org/10.1007/s10462-025-11269-9 Danger ------ 1. This algorithm has several drawbacks, especially during training, where such cases rarely occur. 2. As a result, scenarios 3, 4, and 5 are highly unlikely to happen, since identical fitness values between two solutions are extremely rare in practice References ---------- 1. Wang, L., Du, H., Zhang, Z., Hu, G., Mirjalili, S., Khodadadi, N., Hussien, A.G., Liao, Y. and Zhao, W., 2025. Tianji’s horse racing optimization (THRO): a new metaheuristic inspired by ancient wisdom and its engineering optimization applications. Artificial Intelligence Review, 58(9), p.282. Examples -------- >>> import numpy as np >>> from mealpy import FloatVar, THRO >>> >>> 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 = THRO.OriginalTHRO(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}") """ def __init__(self, epoch: int = 10000, pop_size: int = 100, **kwargs: object) -> None: """ Args: epoch (int): maximum number of iterations, default = 10000 pop_size (int): number of population size, default = 100 """ super().__init__(**kwargs) self.epoch = self.validator.check_int("epoch", epoch, [1, 100000]) self.pop_size = self.validator.check_int("pop_size", pop_size, [10, 10000]) self.set_parameters(["epoch", "pop_size"]) self.sort_flag = False self.is_parallelizable = False
[docs] def before_main_loop(self): # Split to two groups: tianji and king (50%-50%) self.n_pop = self.pop_size // 2 self.pop_tianji = self.pop[:self.n_pop] self.pop_king = self.pop[self.n_pop:]
[docs] def evolve(self, epoch): """ The main operations (equations) of algorithm. Inherit from Optimizer class Args: epoch (int): The current iteration """ ### """Main racing phase with five scenarios""" # Randomly shuffle and redistribute populations self.pop = self.rng.sample(self.pop, len(self.pop)) self.pop_tianji = self.pop[:self.n_pop].copy() self.pop_king = self.pop[self.n_pop:].copy() # Sort populations by fitness self.pop_tianji = self.get_sorted_population(self.pop_tianji, self.problem.minmax) self.pop_king = self.get_sorted_population(self.pop_king, self.problem.minmax) # Generate binary matrices T_B and K_B t_b = np.zeros((self.n_pop, self.problem.n_dims)) k_b = np.zeros((self.n_pop, self.problem.n_dims)) for idx in range(self.n_pop): # For Tianji rand_dim = self.generator.permutation(self.problem.n_dims) rand_num = int(np.ceil(np.sin(np.pi / 2 * self.generator.random()) * self.problem.n_dims)) t_b[idx, rand_dim[:rand_num]] = 1 # For King rand_dim = self.generator.permutation(self.problem.n_dims) rand_num = int(np.ceil(np.sin(np.pi / 2 * self.generator.random()) * self.problem.n_dims)) k_b[idx, rand_dim[:rand_num]] = 1 # Weight parameter p = 1 - epoch / self.epoch # Current horse indices tianji_slowest_id = self.n_pop - 1 tianji_fastest_id = 0 king_slowest_id = self.n_pop - 1 king_fastest_id = 0 # Racing scenarios for idx in range(self.n_pop): tianji_alpha = 1 + np.round(0.5 * (0.5 + self.generator.random())) * self.generator.standard_normal() t_beta = np.round(0.5 * (0.1 + self.generator.random())) * self.generator.standard_normal() king_alpha = 1 + np.round(0.5 * (0.5 + self.generator.random())) * self.generator.standard_normal() k_beta = np.round(0.5 * (0.1 + self.generator.random())) * self.generator.standard_normal() tianji_r = self.get_levy_flight_step(beta=1.5, multiplier=1, size=None, case=-1) * t_b[idx] king_r = self.get_levy_flight_step(beta=1.5, multiplier=1, size=None, case=-1) * k_b[idx] fit_t = self.pop_tianji[tianji_slowest_id].target.fitness fit_k = self.pop_king[king_slowest_id].target.fitness if self.problem.minmax == "min": if fit_t < fit_k: case = 1 elif fit_t > fit_k: case = 2 else: case = 3 else: if fit_t > fit_k: case = 1 elif fit_t < fit_k: case = 2 else: case = 3 tianji_mean = np.mean([agent.solution for agent in self.pop_tianji], axis=0) king_mean = np.mean([agent.solution for agent in self.pop_king], axis=0) # Scenario 1: Tianji's slowest < King's slowest if case == 1: # Update Tianji's slowest horse pos_new = ((p * self.pop_tianji[tianji_slowest_id].solution + (1 - p) * self.pop_tianji[0].solution) + tianji_r * (self.pop_tianji[0].solution - self.pop_tianji[tianji_slowest_id].solution + p * (tianji_mean - king_mean))) * tianji_alpha + t_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_tianji[tianji_slowest_id].target, self.problem.minmax): self.pop_tianji[tianji_slowest_id] = agent # Update King's slowest horse pos_new = ((p * self.pop_king[king_slowest_id].solution + (1 - p) * self.pop_tianji[tianji_slowest_id].solution) + king_r * (self.pop_tianji[tianji_slowest_id].solution - self.pop_king[king_slowest_id].solution + p * (tianji_mean - king_mean))) * king_alpha + k_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_king[king_slowest_id].target, self.problem.minmax): self.pop_king[king_slowest_id] = agent tianji_slowest_id = max(0, tianji_slowest_id - 1) king_slowest_id = max(0, king_slowest_id - 1) # Scenario 2: Tianji's slowest > King's slowest elif case == 2: tr1 = self.generator.choice(list(set(range(self.n_pop)) - {tianji_slowest_id})) # Update Tianji's slowest horse pos_new = ((p * self.pop_tianji[tianji_slowest_id].solution + (1 - p) * self.pop_tianji[tr1].solution) + tianji_r * (self.pop_tianji[tr1].solution - self.pop_tianji[tianji_slowest_id].solution + p * (tianji_mean - king_mean))) * tianji_alpha + t_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_tianji[tianji_slowest_id].target, self.problem.minmax): self.pop_tianji[tianji_slowest_id] = agent # Update King's fastest horse pos_new = ((p * self.pop_king[king_fastest_id].solution + (1 - p) * self.pop_king[0].solution) + king_r * (self.pop_king[0].solution - self.pop_king[king_fastest_id].solution + p * (tianji_mean - king_mean))) * king_alpha + k_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_king[king_fastest_id].target, self.problem.minmax): self.pop_king[king_fastest_id] = agent tianji_slowest_id = max(0, tianji_slowest_id - 1) king_fastest_id = min(self.n_pop - 1, king_fastest_id + 1) else: # Equal slowest speeds fit_t = self.pop_tianji[tianji_fastest_id].target.fitness fit_k = self.pop_king[king_fastest_id].target.fitness if self.problem.minmax == "min": if fit_t < fit_k: case_fast = 1 elif fit_t > fit_k: case_fast = 2 else: case_fast = 3 else: if fit_t > fit_k: case_fast = 1 elif fit_t < fit_k: case_fast = 2 else: case_fast = 3 # Scenario 3: Tianji's fastest < King's fastest if case_fast == 1: # Update Tianji's fastest horse pos_new = ((p * self.pop_tianji[tianji_fastest_id].solution + (1 - p) * self.pop_tianji[0].solution) + tianji_r * (self.pop_tianji[0].solution - self.pop_tianji[tianji_fastest_id].solution + p * (tianji_mean - king_mean))) * tianji_alpha + t_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_tianji[tianji_fastest_id].target, self.problem.minmax): self.pop_tianji[tianji_fastest_id] = agent # Update King's fastest horse pos_new = ((p * self.pop_king[king_fastest_id].solution + (1 - p) * self.pop_tianji[tianji_fastest_id].solution) + king_r * (self.pop_tianji[tianji_fastest_id].solution - self.pop_king[king_fastest_id].solution + p * (tianji_mean - king_mean))) * king_alpha + k_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_king[king_fastest_id].target, self.problem.minmax): self.pop_king[king_fastest_id] = agent tianji_fastest_id = min(self.n_pop - 1, tianji_fastest_id + 1) king_fastest_id = min(self.n_pop - 1, king_fastest_id + 1) # Scenario 4: Tianji's fastest > King's fastest elif case_fast == 2: tr2 = self.generator.choice(list(set(range(self.n_pop)) - {tianji_fastest_id})) # Update Tianji's slowest horse pos_new = ((p * self.pop_tianji[tianji_slowest_id].solution + (1 - p) * self.pop_tianji[tr2].solution) + tianji_r * (self.pop_tianji[tr2].solution - self.pop_tianji[tianji_slowest_id].solution + p * (tianji_mean - king_mean))) * tianji_alpha + t_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_tianji[tianji_slowest_id].target, self.problem.minmax): self.pop_tianji[tianji_slowest_id] = agent # Update King's fastest horse pos_new = ((p * self.pop_king[king_fastest_id].solution + (1 - p) * self.pop_king[0].solution) + king_r * (self.pop_king[0].solution - self.pop_king[king_fastest_id].solution + p * (tianji_mean - king_mean))) * king_alpha + k_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_king[king_fastest_id].target, self.problem.minmax): self.pop_king[king_fastest_id] = agent tianji_slowest_id = max(0, tianji_slowest_id - 1) king_fastest_id = min(self.n_pop - 1, king_fastest_id + 1) # Scenario 5: Equal fastest speeds else: tr3 = self.generator.choice(list(set(range(self.n_pop)) - {tianji_slowest_id})) # Update Tianji's slowest horse pos_new = ((p * self.pop_tianji[tianji_slowest_id].solution + (1 - p) * self.pop_tianji[tr3].solution) + tianji_r * (self.pop_tianji[tr3].solution - self.pop_tianji[tianji_slowest_id].solution + p * (tianji_mean - king_mean))) * tianji_alpha + t_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_tianji[tianji_slowest_id].target, self.problem.minmax): self.pop_tianji[tianji_slowest_id] = agent # Update King's fastest horse pos_new = ((p * self.pop_king[king_fastest_id].solution + (1 - p) * self.pop_king[0].solution) + king_r * (self.pop_king[0].solution - self.pop_king[king_fastest_id].solution + p * (tianji_mean - king_mean))) * king_alpha + k_beta pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_king[king_fastest_id].target, self.problem.minmax): self.pop_king[king_fastest_id] = agent tianji_slowest_id = max(0, tianji_slowest_id - 1) king_fastest_id = min(self.n_pop - 1, king_fastest_id + 1) ## Update global best self.update_global_best_agent(self.pop_tianji + self.pop_king, save=False) # Training phase best_tianji = self.get_best_agent(self.pop_tianji, self.problem.minmax) best_king = self.get_best_agent(self.pop_king, self.problem.minmax) for idx in range(self.n_pop): # Training for Tianji's population pos_new = self.pop_tianji[idx].solution for jdx in range(self.problem.n_dims): if self.generator.random() > 0.5: # Levy flight based training tr4, tr5 = self.generator.choice(list(set(range(self.n_pop)) - {idx}), size=2, replace=False) lt = self.get_levy_flight_step(beta=1.5, multiplier=0.2, size=None, case=-1) pos_new[jdx] = pos_new[jdx] + lt * (self.pop_tianji[tr4].solution[jdx] - self.pop_tianji[tr5].solution[jdx]) else: # Best-guided training mt = 0.5 * (1 + 0.001 * (1 - epoch / self.epoch) ** 2 * np.sin(np.pi * self.generator.random())) pos_new[jdx] = best_tianji.solution[jdx] + mt * (best_tianji.solution[jdx] - pos_new[jdx]) pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_tianji[idx].target, self.problem.minmax): self.pop_tianji[idx] = agent # Training for King's population pos_new = self.pop_tianji[idx].solution for jdx in range(self.problem.n_dims): if self.generator.random() > 0.5: # Levy flight based training kr1, kr2 = self.generator.choice(list(set(range(self.n_pop)) - {idx}), size=2, replace=False) lk = self.get_levy_flight_step(beta=1.5, multiplier=0.2, size=None, case=-1) pos_new[jdx] = pos_new[jdx] + lk * (self.pop_king[kr1].solution[jdx] - self.pop_king[kr2].solution[jdx]) else: # Best-guided training mk = 0.5 * (1 + 0.001 * (1 - epoch / self.epoch) ** 2 * np.sin(np.pi * self.generator.random())) pos_new[jdx] = best_king.solution[jdx] + mk * (best_king.solution[jdx] - pos_new[jdx]) pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop_king[idx].target, self.problem.minmax): self.pop_king[idx] = agent # Merge populations back self.pop = self.pop_tianji + self.pop_king