#!/usr/bin/env python
# Created by "Thieu" at 12:24, 18/03/2020 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from mealpy.optimizer import Optimizer
[docs]class OriginalBBO(Optimizer):
"""
The original version of: Biogeography-Based Optimization (BBO)
Links:
1. https://ieeexplore.ieee.org/abstract/document/4475427
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ p_m (float): (0, 1) -> better [0.01, 0.2], Mutation probability
+ n_elites (int): (2, pop_size/2) -> better [2, 5], Number of elites will be keep for next generation
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, BBO
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = BBO.OriginalBBO(epoch=1000, pop_size=50, p_m=0.01, n_elites=2)
>>> 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] Simon, D., 2008. Biogeography-based optimization. IEEE transactions on evolutionary computation, 12(6), pp.702-713.
"""
def __init__(self, epoch: int = 10000, pop_size: int = 100, p_m: float = 0.01, n_elites: int = 2, **kwargs: object) -> None:
"""
Initialize the algorithm components.
Args:
epoch: Maximum number of iterations, default = 10000
pop_size: Number of population size, default = 100
p_m: Mutation probability, default=0.01
n_elites: Number of elites will be keep for next generation, default=2
"""
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.p_m = self.validator.check_float("p_m", p_m, (0., 1.0))
self.n_elites = self.validator.check_int("n_elites", n_elites, [2, int(self.pop_size / 2)])
self.set_parameters(["epoch", "pop_size", "p_m", "n_elites"])
self.sort_flag = False
self.mu = (self.pop_size + 1 - np.array(range(1, self.pop_size + 1))) / (self.pop_size + 1)
self.mr = 1 - self.mu
[docs] def evolve(self, epoch: int) -> None:
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch: The current iteration
"""
_, pop_elites, _ = self.get_special_agents(self.pop, n_best=self.n_elites, minmax=self.problem.minmax)
pop = []
for idx in range(0, self.pop_size):
# Probabilistic migration to the i-th position
pos_new = self.pop[idx].solution.copy()
for j in range(self.problem.n_dims):
if self.generator.random() < self.mr[idx]: # Should we immigrate?
# Pick a position from which to emigrate (roulette wheel selection)
random_number = self.generator.random() * np.sum(self.mu)
select = self.mu[0]
select_index = 0
while (random_number > select) and (select_index < self.pop_size - 1):
select_index += 1
select += self.mu[select_index]
# this is the migration step
pos_new[j] = self.pop[select_index].solution[j]
noise = self.generator.uniform(self.problem.lb, self.problem.ub)
condition = self.generator.random(self.problem.n_dims) < self.p_m
pos_new = np.where(condition, noise, pos_new)
pos_new = self.correct_solution(pos_new)
agent_new = self.generate_empty_agent(pos_new)
pop.append(agent_new)
if self.mode not in self.AVAILABLE_MODES:
agent_new.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(self.pop[idx], agent_new, minmax=self.problem.minmax)
if self.mode in self.AVAILABLE_MODES:
pop = self.update_target_for_population(pop)
self.pop = self.greedy_selection_population(self.pop, pop, self.problem.minmax)
# replace the solutions with their new migrated and mutated versions then Merge Populations
self.pop = self.get_sorted_and_trimmed_population(self.pop + pop_elites, self.pop_size, self.problem.minmax)
[docs]class DevBBO(OriginalBBO):
"""
The developed version: Biogeography-Based Optimization (BBO)
Hyper-parameters should fine-tune in approximate range to get faster convergence toward the global optimum:
+ p_m (float): (0, 1) -> better [0.01, 0.2], Mutation probability
+ n_elites (int): (2, pop_size/2) -> better [2, 5], Number of elites will be keep for next generation
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, BBO
>>>
>>> def objective_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict = {
>>> "bounds": FloatVar(n_vars=30, lb=(-10.,) * 30, ub=(10.,) * 30, name="delta"),
>>> "minmax": "min",
>>> "obj_func": objective_function
>>> }
>>>
>>> model = BBO.DevBBO(epoch=1000, pop_size=50, p_m=0.01, n_elites=2)
>>> 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, p_m: float = 0.01, n_elites: int = 2, **kwargs: object) -> None:
"""
Initialize the algorithm components.
Args:
epoch: Maximum number of iterations, default = 10000
pop_size: Number of population size, default = 100
p_m: Mutation probability, default=0.01
n_elites: Number of elites will be keep for next generation, default=2
"""
super().__init__(epoch, pop_size, p_m, n_elites, **kwargs)
[docs] def evolve(self, epoch: int) -> None:
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
_, pop_elites, _ = self.get_special_agents(self.pop, n_best=self.n_elites, minmax=self.problem.minmax)
list_fitness = [agent.target.fitness for agent in self.pop]
pop_new = []
for idx in range(0, self.pop_size):
# Probabilistic migration to the i-th position
# Pick a position from which to emigrate (roulette wheel selection)
idx_selected = self.get_index_roulette_wheel_selection(list_fitness)
# this is the migration step
condition = self.generator.random(self.problem.n_dims) < self.mr[idx]
pos_new = np.where(condition, self.pop[idx_selected].solution, self.pop[idx].solution)
# Mutation
mutated = self.generator.uniform(self.problem.lb, self.problem.ub)
pos_new = np.where(self.generator.random(self.problem.n_dims) < self.p_m, mutated, pos_new)
pos_new = self.correct_solution(pos_new)
agent_new = self.generate_empty_agent(pos_new)
pop_new.append(agent_new)
if self.mode not in self.AVAILABLE_MODES:
agent_new.target = self.get_target(pos_new)
self.pop[idx] = self.get_better_agent(self.pop[idx], agent_new, minmax=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)
# replace the solutions with their new migrated and mutated versions then Merge Populations
self.pop = self.get_sorted_and_trimmed_population(self.pop + pop_elites, self.pop_size, self.problem.minmax)