|
| 1 | +#!/usr/bin/env python |
| 2 | +# Created by "Thieu" at 09:16, 15/08/2025 ----------% |
| 3 | +# Email: nguyenthieu2102@gmail.com % |
| 4 | +# Github: https://github.com/thieu1995 % |
| 5 | +# --------------------------------------------------% |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from mealpy.optimizer import Optimizer |
| 9 | + |
| 10 | + |
| 11 | +class DevEPC(Optimizer): |
| 12 | + """ |
| 13 | + The developer version of: Emperor Penguins Colony (EPC) |
| 14 | +
|
| 15 | + Notes: |
| 16 | + + This algorithm is almost like a trash algorithm. Some comments are as follows: |
| 17 | + + The code is incorrect and incomplete. It updates coefficients either increasing or decreasing, but the paper does not clearly provide any formulas describing how these increases or decreases are calculated. |
| 18 | + + Most of the formulas are wrong and meaningless, with no clear explanation of what the symbols represent. In particular, formulas 12 to 18 are problematic. There is no connection between the position update process in the algorithm and the parameters. |
| 19 | + + This algorithm can only be applied to 2-dimensional problems and cannot be extended to problems with more than 2 dimensions. The entire experimental section of the paper is also limited to 2-dimensional functions. |
| 20 | + + In the code, I simplified the position update process for penguins and modified the algorithm to work on n-dimensional problems. The parameter update rules were also devised by me. Therefore, I named it DevEPC. |
| 21 | +
|
| 22 | + Links: |
| 23 | + 1. https://doi.org/10.1007/s12065-019-00212-x |
| 24 | +
|
| 25 | + Examples |
| 26 | + ~~~~~~~~ |
| 27 | + >>> import numpy as np |
| 28 | + >>> from mealpy import FloatVar, EPC |
| 29 | + >>> |
| 30 | + >>> def objective_function(solution): |
| 31 | + >>> return np.sum(solution**2) |
| 32 | + >>> |
| 33 | + >>> problem_dict = { |
| 34 | + >>> "bounds": FloatVar(lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"), |
| 35 | + >>> "obj_func": objective_function, |
| 36 | + >>> "minmax": "min", |
| 37 | + >>> } |
| 38 | + >>> |
| 39 | + >>> model = EPC.DevEPC(epoch=1000, pop_size=50) |
| 40 | + >>> g_best = model.solve(problem_dict) |
| 41 | + >>> print(f"Solution: {g_best.solution}, Fitness: {g_best.target.fitness}") |
| 42 | + >>> print(f"Solution: {model.g_best.solution}, Fitness: {model.g_best.target.fitness}") |
| 43 | +
|
| 44 | + References |
| 45 | + ~~~~~~~~~~ |
| 46 | + [1] Harifi, S., Khalilian, M., Mohammadzadeh, J. and Ebrahimnejad, S., 2019. |
| 47 | + Emperor Penguins Colony: a new metaheuristic algorithm for optimization. Evolutionary intelligence, 12(2), pp.211-226. |
| 48 | + """ |
| 49 | + |
| 50 | + def __init__(self, epoch=10000, pop_size=100, heat_damping_factor: float = 0.95, |
| 51 | + mutation_factor: float = 0.5, spiral_a: float = 1.0, spiral_b: float = 0.5, **kwargs): |
| 52 | + """ |
| 53 | + Args: |
| 54 | + epoch (int): maximum number of iterations, default = 10000 |
| 55 | + pop_size (int): number of population size, default = 100 |
| 56 | + heat_damping_factor (float): Damping factor for heat radiation, default = 0.95 |
| 57 | + mutation_factor (float): Mutation factor for random movement, default = 0.1 |
| 58 | + spiral_a (float): Constant for logarithmic spiral movement, default = 1.0 |
| 59 | + spiral_b (float): Constant for logarithmic spiral movement, default = 0.5 |
| 60 | + """ |
| 61 | + super().__init__(**kwargs) |
| 62 | + self.epoch = self.validator.check_int("epoch", epoch, [1, 100000]) |
| 63 | + self.pop_size = self.validator.check_int("pop_size", pop_size, [5, 10000]) |
| 64 | + self.heat_damping_factor = self.validator.check_float("heat_damping_factor", heat_damping_factor, [0.0, 1.0]) |
| 65 | + self.mutation_factor = self.validator.check_float("mutation_factor", mutation_factor, [0.0, 1.0]) |
| 66 | + self.spiral_a = self.validator.check_float("spiral_a", spiral_a, [0.0, 100.0]) |
| 67 | + self.spiral_b = self.validator.check_float("spiral_b", spiral_b, [0.0, 100.0]) |
| 68 | + self.set_parameters(["epoch", "pop_size", "heat_damping_factor", "mutation_factor", "spiral_a", "spiral_b"]) |
| 69 | + self.sort_flag = False |
| 70 | + self.is_parallelizable = False |
| 71 | + |
| 72 | + def initialize_variables(self): |
| 73 | + # Physical constants (from paper) |
| 74 | + self.surface_area = 0.56 # m^2 (total surface area of emperor penguin) |
| 75 | + self.emissivity = 0.98 # emissivity of bird plumage |
| 76 | + self.stefan_boltzmann = 5.6703e-8 # W/m^2K^4 |
| 77 | + self.body_temperature = 308.15 # K (35°C) |
| 78 | + self.mu = 0.01 # Attenuation coefficient (can be tuned) |
| 79 | + # Calculate heat radiation using Stefan-Boltzmann law (Equation 6) |
| 80 | + self.heat_radiation = (self.surface_area * self.emissivity * self.stefan_boltzmann * (self.body_temperature ** 4)) |
| 81 | + |
| 82 | + def calculate_attractiveness(self, heat_radiation: float, distance: float) -> float: |
| 83 | + """ |
| 84 | + Calculate attractiveness between two penguins based on heat radiation and distance |
| 85 | +
|
| 86 | + Parameters: |
| 87 | + ----------- |
| 88 | + heat_radiation : float |
| 89 | + Heat radiation of the source penguin |
| 90 | + distance : float |
| 91 | + Distance between penguins |
| 92 | +
|
| 93 | + Returns: |
| 94 | + -------- |
| 95 | + float : Attractiveness value |
| 96 | + """ |
| 97 | + # Linear heat source model with photon attenuation (Equations 9-11) |
| 98 | + if distance == 0: |
| 99 | + return heat_radiation |
| 100 | + else: |
| 101 | + # Heat intensity with linear source and attenuation |
| 102 | + return heat_radiation * np.exp(-self.mu * distance) / distance |
| 103 | + |
| 104 | + def spiral_movement(self, penguin_i: np.ndarray, penguin_j: np.ndarray, attractiveness: float) -> np.ndarray: |
| 105 | + """ |
| 106 | + Calculate spiral-like movement from penguin i towards penguin j |
| 107 | +
|
| 108 | + Parameters: |
| 109 | + ----------- |
| 110 | + penguin_i : np.ndarray |
| 111 | + Position of penguin i (moving penguin) |
| 112 | + penguin_j : np.ndarray |
| 113 | + Position of penguin j (target penguin) |
| 114 | + attractiveness : float |
| 115 | + Attractiveness value between penguins |
| 116 | +
|
| 117 | + Returns: |
| 118 | + -------- |
| 119 | + np.ndarray : New position after spiral movement |
| 120 | + """ |
| 121 | + # Convert to polar coordinates |
| 122 | + diff = penguin_j - penguin_i |
| 123 | + # Simplified spiral movement calculation |
| 124 | + # Instead of complex polar coordinate transformation, use direct approach |
| 125 | + distance = np.linalg.norm(diff) |
| 126 | + if distance == 0 or np.allclose(penguin_i, penguin_j): |
| 127 | + return penguin_i.copy() |
| 128 | + # Direction vector |
| 129 | + direction = diff / distance |
| 130 | + # Spiral movement distance based on attractiveness |
| 131 | + move_distance = attractiveness * distance * self.spiral_a |
| 132 | + # Add spiral rotation effect |
| 133 | + theta = self.spiral_b * np.pi |
| 134 | + rotation_matrix = np.array([[np.cos(theta), -np.sin(theta)], |
| 135 | + [np.sin(theta), np.cos(theta)]]) |
| 136 | + # Apply rotation to direction (for 2D, extend for higher dimensions) |
| 137 | + if self.problem.n_dims >= 2: |
| 138 | + rotated_dir = direction.copy() |
| 139 | + rotated_dir[:2] = rotation_matrix @ direction[:2] |
| 140 | + else: |
| 141 | + rotated_dir = direction |
| 142 | + # Calculate new position - Add random component (mutation) - Equation 19 |
| 143 | + new_position = (penguin_i + move_distance * rotated_dir + |
| 144 | + self.current_mutation_factor * self.generator.uniform(-1, 1, self.problem.n_dims)) |
| 145 | + return new_position |
| 146 | + |
| 147 | + def evolve(self, epoch): |
| 148 | + """ |
| 149 | + The main operations (equations) of algorithm. Inherit from Optimizer class |
| 150 | +
|
| 151 | + Args: |
| 152 | + epoch (int): The current iteration |
| 153 | + """ |
| 154 | + # Decrease heat absorption coefficient |
| 155 | + self.heat_radiation = self.heat_radiation * self.heat_damping_factor |
| 156 | + # Decrease mutation factor |
| 157 | + self.current_mutation_factor = self.mutation_factor * (1 - epoch / self.epoch) |
| 158 | + |
| 159 | + # For each penguin i |
| 160 | + for idx in range(self.pop_size): |
| 161 | + # For each penguin j |
| 162 | + for jdx in range(self.pop_size): |
| 163 | + # Move penguin i towards penguin j if j has better cost |
| 164 | + if self.compare_target(self.pop[jdx].target, self.pop[idx].target, self.problem.minmax): |
| 165 | + # Calculate distance between penguins |
| 166 | + distance = np.linalg.norm(self.pop[jdx].solution - self.pop[idx].solution) |
| 167 | + # Calculate attractiveness |
| 168 | + attractiveness = self.calculate_attractiveness(self.heat_radiation, distance) |
| 169 | + # Normalize attractiveness |
| 170 | + if attractiveness > 1: |
| 171 | + attractiveness = 1.0 / (1.0 + attractiveness) |
| 172 | + # Perform spiral movement |
| 173 | + pos_new = self.spiral_movement(self.pop[idx].solution, self.pop[jdx].solution, attractiveness) |
| 174 | + pos_new = self.correct_solution(pos_new) |
| 175 | + agent = self.generate_agent(pos_new) |
| 176 | + if self.compare_target(agent.target, self.pop[idx].target, self.problem.minmax): |
| 177 | + self.pop[idx] = agent |
0 commit comments