Source code for mealpy.swarm_based.BFO

#!/usr/bin/env python
# Created by "Thieu" at 10:21, 17/03/2020 ----------%
#       Email: nguyenthieu2102@gmail.com            %
#       Github: https://github.com/thieu1995        %
# --------------------------------------------------%

import numpy as np
from mealpy.optimizer import Optimizer
from mealpy.utils.agent import Agent


[docs]class OriginalBFO(Optimizer): """ The original version of: Bacterial Foraging Optimization (BFO) Notes: + Ned and Nre parameters are replaced by epoch (generation) + The Nc parameter will also decrease to reduce the computation time. + Cost in this version equal to Fitness value in the paper. + https://www.cleveralgorithms.com/nature-inspired/swarm/bfoa.html Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum: + Ci (float): [0.01, 0.3], step size, default=0.01 + Ped (float): [0.1, 0.5], probability of elimination, default=0.25 + Ned (int): elim_disp_steps (Removed), Ned=5, + Nre (int): reproduction_steps (Removed), Nre=50, + Nc (int): [3, 10], chem_steps (Reduce), Nc = Original Nc/2, default = 5 + Ns (int): [2, 10], swim length, default=4 + d_attract (float): coefficient to calculate attract force, default = 0.1 + w_attract (float): coefficient to calculate attract force, default = 0.2 + h_repels (float): coefficient to calculate repel force, default = 0.1 + w_repels (float): coefficient to calculate repel force, default = 10 Examples ~~~~~~~~ >>> import numpy as np >>> from mealpy import FloatVar, BFO >>> >>> def objective_function(solution): >>> return np.sum(solution**2) >>> >>> problem_dict = { >>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"), >>> "obj_func": objective_function, >>> "minmax": "min", >>> } >>> >>> model = BFO.OriginalBFO(epoch=1000, pop_size=50, Ci = 0.01, Ped = 0.25, Nc = 5, Ns = 4, d_attract=0.1, w_attract=0.2, h_repels=0.1, w_repels=10) >>> 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}") References ~~~~~~~~~~ [1] Passino, K.M., 2002. Biomimicry of bacterial foraging for distributed optimization and control. IEEE control systems magazine, 22(3), pp.52-67. """ def __init__(self, epoch: int = 10000, pop_size: int = 100, Ci: float = 0.01, Ped: float = 0.25, Nc: int = 5, Ns: int = 4, d_attract: float = 0.1, w_attract: float = 0.2, h_repels: float = 0.1, w_repels: float = 10, **kwargs: object) -> None: """ Args: epoch (int): maximum number of iterations, default = 10000 pop_size (int): number of population size, default = 100 Ci (float): step size, default=0.01 Ped (float): p_eliminate, default=0.25 Ned (int): elim_disp_steps (Removed) Ned=5, Nre (int): reproduction_steps (Removed) Nre=50, Nc (int): chem_steps (Reduce) Nc = Original Nc/2, default = 5 Ns (int): swim_length, default=4 d_attract (float): coefficient to calculate attract force, default = 0.1 w_attract (float): coefficient to calculate attract force, default = 0.2 h_repels (float): coefficient to calculate repel force, default = 0.1 w_repels (float): coefficient to calculate repel force, default = 10 """ 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.step_size = self.Ci = self.validator.check_int("Ci", Ci, (0, 5.0)) self.p_eliminate = self.Ped = self.validator.check_float("Ped", Ped, (0, 1.0)) self.chem_steps = self.Nc = self.validator.check_int("Nc", Nc, [2, 100]) self.swim_length = self.Ns = self.validator.check_int("Ns", Ns, [2, 100]) self.d_attract = self.validator.check_float("d_attract", d_attract, (0, 1.0)) self.w_attract = self.validator.check_float("w_attract", w_attract, (0, 1.0)) self.h_repels = self.validator.check_float("h_repels", h_repels, (0, 1.0)) self.w_repels = self.validator.check_float("w_repels", w_repels, (2.0, 20.0)) self.set_parameters(["epoch", "pop_size", "Ci", "Ped", "Nc", "Ns", "d_attract", "w_attract", "h_repels", "w_repels"]) self.half_pop_size = int(self.pop_size / 2) self.is_parallelizable = False self.sort_flag = False
[docs] def generate_empty_agent(self, solution: np.ndarray = None) -> Agent: if solution is None: solution = self.problem.generate_solution(encoded=True) cost = 0.0 interaction = 0.0 nutrients = 0.0 return Agent(solution=solution, cost=cost, interaction=interaction, nutrients=nutrients)
[docs] def compute_cell_interaction__(self, cell, cells, d, w): sum_inter = 0.0 for other in cells: diff = self.problem.n_dims * ((cell.solution - other.solution) ** 2).mean(axis=None) sum_inter += d * np.exp(w * diff) return sum_inter
[docs] def attract_repel__(self, idx, cells): attract = self.compute_cell_interaction__(cells[idx], cells, -self.d_attract, -self.w_attract) repel = self.compute_cell_interaction__(cells[idx], cells, self.h_repels, -self.w_repels) return attract + repel
[docs] def evaluate__(self, idx, cells): cells[idx].interaction = self.attract_repel__(idx, cells) cells[idx].cost = cells[idx].target.fitness + cells[idx].interaction return cells
[docs] def tumble_cell__(self, cell, step_size): delta_i = self.generator.uniform(self.problem.lb, self.problem.ub) unit_vector = delta_i / np.sqrt(np.abs(np.dot(delta_i, delta_i.T))) vector = cell.solution + step_size * unit_vector return [vector, 0.0, 0.0, 0.0, 0.0]
[docs] def evolve(self, epoch): """ The main operations (equations) of algorithm. Inherit from Optimizer class Args: epoch (int): The current iteration """ for j in range(0, self.chem_steps): for idx in range(0, self.pop_size): sum_nutrients = 0.0 self.pop = self.evaluate__(idx, self.pop) sum_nutrients += self.pop[idx].cost for m in range(0, self.swim_length): delta_i = self.generator.uniform(self.problem.lb, self.problem.ub) unit_vector = delta_i / np.sqrt(np.abs(np.dot(delta_i, delta_i.T))) pos_new = self.pop[idx].solution + self.step_size * unit_vector pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop[idx].target, self.problem.minmax): self.pop[idx] = agent break sum_nutrients += self.pop[idx].cost self.pop[idx].nutrients = sum_nutrients cells = sorted(self.pop, key=lambda cell: cell.nutrients) self.pop = cells[0:self.half_pop_size].copy() + cells[0:self.half_pop_size].copy() for idc in range(self.pop_size): if self.generator.random() < self.p_eliminate: self.pop[idc] = self.generate_agent()
[docs]class ABFO(Optimizer): """ The original version of: Adaptive Bacterial Foraging Optimization (ABFO) Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum: + C_s (float): step size start, default=0.1 + C_e (float): step size end, default=0.001 + Ped (float): Probability eliminate, default=0.01 + Ns (int): swim_length, default=4 + N_adapt (int): Dead threshold value default=2 + N_split (int): Split threshold value, default=40 Examples ~~~~~~~~ >>> import numpy as np >>> from mealpy import FloatVar, BFO >>> >>> def objective_function(solution): >>> return np.sum(solution**2) >>> >>> problem_dict = { >>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"), >>> "obj_func": objective_function, >>> "minmax": "min", >>> } >>> >>> model = BFO.ABFO(epoch=1000, pop_size=50, C_s=0.1, C_e=0.001, Ped = 0.01, Ns = 4, N_adapt = 2, N_split = 40) >>> 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}") References ~~~~~~~~~~ [1] Nguyen, T., Nguyen, B.M. and Nguyen, G., 2019, April. Building resource auto-scaler with functional-link neural network and adaptive bacterial foraging optimization. In International Conference on Theory and Applications of Models of Computation (pp. 501-517). Springer, Cham. """ def __init__(self, epoch: int = 10000, pop_size: int = 100, C_s: float = 0.1, C_e: float = 0.001, Ped: float = 0.01, Ns: int = 4, N_adapt: int = 2, N_split: int = 40, **kwargs: object) -> None: """ Args: epoch (int): maximum number of iterations, default = 10000 pop_size (int): number of population size, default = 100 C_s (float): step size start, default=0.1 C_e (float): step size end, default=0.001 Ped (float): Probability eliminate, default=0.01 Ns (int): swim_length, default=4 N_adapt (int): Dead threshold value default=2 N_split (int): Split threshold value, default=40 """ 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.C_s = self.Ped = self.validator.check_float("C_s", C_s, (0, 2.0)) self.C_e = self.Ped = self.validator.check_float("C_e", C_e, (0, 1.0)) self.p_eliminate = self.Ped = self.validator.check_float("Ped", Ped, (0, 1.0)) self.swim_length = self.Ns = self.validator.check_int("Ns", Ns, [2, 100]) self.N_adapt = self.validator.check_int("N_adapt", N_adapt, [0, 4]) self.N_split = self.validator.check_int("N_split", N_split, [5, 50]) self.set_parameters(["epoch", "pop_size", "C_s", "C_e", "Ped", "Ns", "N_adapt", "N_split"]) self.support_parallel_modes = False self.sort_flag = False
[docs] def initialize_variables(self): self.C_s = self.C_s * (self.problem.ub - self.problem.lb) self.C_e = self.C_e * (self.problem.ub - self.problem.lb)
[docs] def generate_empty_agent(self, solution: np.ndarray = None) -> Agent: if solution is None: solution = self.problem.generate_solution(encoded=True) nutrients = 0 # total nutrient gained by the bacterium in its whole searching process.(int number) local_solution = solution.copy() return Agent(solution=solution, nutrients=nutrients, local_solution=local_solution)
[docs] def generate_agent(self, solution: np.ndarray = None) -> Agent: agent = self.generate_empty_agent(solution) agent.target = self.get_target(agent.solution) agent.local_target = agent.target.copy() return agent
[docs] def update_step_size__(self, pop=None, idx=None): total_fitness = np.sum([agent.target.fitness for agent in pop]) step_size = self.C_s - (self.C_s - self.C_e) * pop[idx].target.fitness / total_fitness step_size = step_size / self.pop[idx].nutrients if self.pop[idx].nutrients > 0 else step_size return step_size
[docs] def evolve(self, epoch): """ The main operations (equations) of algorithm. Inherit from Optimizer class Args: epoch (int): The current iteration """ for idx in range(0, self.pop_size): step_size = self.update_step_size__(self.pop, idx) for m in range(0, self.swim_length): # Ns delta_i = (self.g_best.solution - self.pop[idx].solution) + (self.pop[idx].local_solution - self.pop[idx].solution) delta = np.sqrt(np.abs(np.dot(delta_i, delta_i.T))) unit_vector = self.generator.uniform(self.problem.lb, self.problem.ub) if delta == 0 else (delta_i / delta) pos_new = self.pop[idx].solution + step_size * unit_vector pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) if self.compare_target(agent.target, self.pop[idx].target, self.problem.minmax): agent.nutrients += 1 self.pop[idx] = agent # Update personal best if self.compare_target(agent.target, self.pop[idx].local_target, self.problem.minmax): self.pop[idx].update(local_solution=pos_new.copy(), local_target=agent.target.copy()) else: self.pop[idx].nutrients -= 1 if self.pop[idx].nutrients > max(self.N_split, self.N_split + (len(self.pop) - self.pop_size) / self.N_adapt): tt = self.generator.normal(0, 1, self.problem.n_dims) pos_new = tt * self.pop[idx].solution + (1 - tt) * (self.g_best.solution - self.pop[idx].solution) pos_new = self.correct_solution(pos_new) agent = self.generate_agent(pos_new) self.pop.append(agent) nut_min = min(self.N_adapt, self.N_adapt + (len(self.pop) - self.pop_size) / self.N_adapt) if self.pop[idx].nutrients < nut_min or self.generator.random() < self.p_eliminate: self.pop[idx] = self.generate_agent() ## Make sure the population does not have duplicates. new_set = set() for idx, obj in enumerate(self.pop): if tuple(obj.solution.tolist()) in new_set: self.pop.pop(idx) else: new_set.add(tuple(obj.solution.tolist())) ## Balance the population by adding more agents or remove some agents n_agents = len(self.pop) - self.pop_size if n_agents < 0: for idx in range(0, n_agents): agent = self.generate_agent() self.pop.append(agent) elif n_agents > 0: list_idx_removed = self.generator.choice(range(0, len(self.pop)), n_agents, replace=False) pop_new = [] for idx in range(0, len(self.pop)): if idx not in list_idx_removed: pop_new.append(self.pop[idx]) self.pop = pop_new