#!/usr/bin/env python
# Created by "Thieu" at 14:51, 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 OriginalSRSR(Optimizer):
"""
The original version of: Swarm Robotics Search And Rescue (SRSR)
Links:
1. https://doi.org/10.1016/j.asoc.2017.02.028
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy import FloatVar, SRSR
>>>
>>> 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 = SRSR.OriginalSRSR(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}")
References
~~~~~~~~~~
[1] Bakhshipour, M., Ghadi, M.J. and Namdari, F., 2017. Swarm robotics search & rescue: A novel
artificial intelligence-inspired optimization approach. Applied Soft Computing, 57, pp.708-726.
"""
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, [5, 10000])
self.set_parameters(["epoch", "pop_size"])
self.sort_flag = True
[docs] def generate_empty_agent(self, solution: np.ndarray = None) -> Agent:
if solution is None:
solution = self.problem.generate_solution(encoded=True)
mu = 0
sigma = 0
x_new = solution.copy()
target_move = 0
return Agent(solution=solution, mu=mu, sigma=sigma, solution_new=x_new, target_move=target_move)
[docs] def generate_agent(self, solution: np.ndarray = None) -> Agent:
agent = self.generate_empty_agent(solution)
agent.target = self.get_target(agent.solution)
agent.target_new = agent.target.copy()
return agent
[docs] def initialize_variables(self):
# Control Parameters Of Algorithm
# ==============================================================================================
# [c1] movement_factor : Determines Movement Pace Of Robots During Exploration Policy
# [c2] sigma_factor : Determines Level Of Divergence Of Slave Robots From Master Robot
# [c3] sigma_limit : Limits Value Of Sigma Factor
# [c4] mu_factor : Controls Mean Value For Master And Slave Robots
# Control Parameters C1, C2 And C3 Are Automatically Tuned While C4 Should Be Set By User
# ==============================================================================================
self.mu_factor = 2 / 3 # [0.1-0.9] Controls Dominance Of Master Robot, Preferably 2/3
self.sigma_temp = np.zeros(self.pop_size) # Initializing Temporary Stacks
self.SIF = 2
self.movement_factor = self.problem.ub - self.problem.lb
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
# ========================================================================================= %%
# PHASE 1 (ACCUMULATION): CALCULATING Mu AND SIGMA values FOR SOLUTIONS %
# ===========================================================================================%%
# ------ CALCULATING MU AND SIGMA FOR MASTER ROBOT ----------
self.pop[0].sigma = self.generator.uniform()
if epoch % 2 == 1:
self.pop[0].mu = (1 - self.pop[0].sigma) * self.pop[0].solution
else:
self.pop[0].mu = (1 + (1 - self.mu_factor) * self.pop[0].sigma) * self.pop[0].solution
pop_new = []
for idx in range(0, self.pop_size):
agent = self.pop[idx].copy()
# ---------- CALCULATING MU AND SIGMA FOR SLAVE ROBOTS ---------
self.pop[idx].mu = self.mu_factor * self.pop[0].solution + (1 - self.mu_factor) * self.pop[idx].solution
if epoch == 0:
self.SIF = 6
self.sigma_temp[idx] = self.SIF * self.generator.uniform()
self.pop[idx].sigma = self.sigma_temp[idx] * np.abs(self.pop[0].solution - self.pop[idx].solution) + \
self.generator.uniform() ** 2 * ((self.pop[0].solution - self.pop[idx].solution) < 0.05)
# ----- Generating New Positions Using New Obtained Mu And Sigma Values --------------
pos_new = self.generator.normal(self.pop[idx].mu, self.pop[idx].sigma, self.problem.n_dims)
pos_new = self.correct_solution(pos_new)
agent.solution = pos_new
pop_new.append(agent)
if self.mode not in self.AVAILABLE_MODES:
pop_new[-1].target = self.get_target(pos_new)
pop_new = self.update_target_for_population(pop_new)
for idx in range(0, self.pop_size):
# --------- Calculate Degree Of Cost Movement Of Robots During Movement --------------
self.pop[idx].target_move = self.pop[idx].target.fitness - self.pop[idx].target_new.fitness
self.pop[idx].solution_new = pop_new[idx].solution.copy()
self.pop[idx].target_new = pop_new[idx].target.copy()
# ---------- Progress Assessment: Replacing More Quality Solutions With Previous Ones ------
# Replace Solution If It Reached To A More Quality Position
if self.compare_target(pop_new[idx].target, self.pop[idx].target, self.problem.minmax):
self.pop[idx].solution = pop_new[idx].solution.copy()
self.pop[idx].target = pop_new[idx].target.copy()
# --------- Determining Sigma Improvement Factor (Sif) Based On Vvss Movement -------------------
## Get best improved fitness
fit_id = np.argmax([agent.target_move for agent in self.pop])
sigma_factor = 1 + self.generator.uniform() * np.max(self.problem.ub - self.problem.lb)
self.SIF = sigma_factor * self.sigma_temp[fit_id]
# Controlling Parameter Of Algorithm
if self.SIF > np.max(self.problem.ub):
self.SIF = np.max(self.problem.ub) * self.generator.uniform()
# ========================================================================================= %%
# Phase 2 (Exploration): Moving Slave Robots Toward Master Robot %
# ===========================================================================================%%
pop_new = []
for idx in range(0, self.pop_size):
agent = self.pop[idx].copy()
gb = self.generator.uniform(-1, 1, self.problem.n_dims)
gb[gb >= 0] = 1
gb[gb < 0] = -1
pos_new = self.pop[idx].solution * self.generator.uniform() + gb * (self.pop[0].solution - self.pop[idx].solution) + \
self.movement_factor * self.generator.uniform(self.problem.lb, self.problem.ub)
pos_new = self.correct_solution(pos_new)
agent.solution = pos_new
pop_new.append(agent)
if self.mode not in self.AVAILABLE_MODES:
pop_new[-1].target = self.get_target(pos_new)
pop_new = self.update_target_for_population(pop_new)
for idx in range(0, self.pop_size):
# --------- Calculate Degree Of Cost Movement Of Robots During Movement --------------
self.pop[idx].target_move = self.pop[idx].target.fitness - self.pop[idx].target_new.fitness
self.pop[idx].solution_new = pop_new[idx].solution.copy()
self.pop[idx].target_new = pop_new[idx].target.copy()
# ---------- Progress Assessment: Replacing More Quality Solutions With Previous Ones ------
# Replace Solution If It Reached To A More Quality Position
if self.compare_target(pop_new[idx].target, self.pop[idx].target, self.problem.minmax):
self.pop[idx].solution = pop_new[idx].solution.copy()
self.pop[idx].target = pop_new[idx].target.copy()
# ========================================================================================= %%
# PHASE 3 (LOCAL SEARCH): CREATING SOME WORKER ROBOTS ASSIGNED TO SEARCH %
# LOCATIONS AROUND POSITION OF MASTER ROBOT %
# ===========================================================================================%%
if epoch > 0:
# --- EXTRACTING "INTEGER PART" AND "FRACTIONAL PART" OF THE ELEMENTS OF MASTER RPBOT POSITION------
master_robot = {"original": np.reshape(self.pop[0].solution, (self.problem.n_dims, 1)),
"sign": np.reshape(np.sign(self.pop[0].solution), (self.problem.n_dims, 1)),
"abs": np.reshape(abs(self.pop[0].solution), (self.problem.n_dims, 1)),
"int": np.reshape(np.floor(abs(self.pop[0].solution)), (self.problem.n_dims, 1)), # INTEGER PART
"frac": np.reshape(abs(self.pop[0].solution) - np.floor(abs(self.pop[0].solution)), (self.problem.n_dims, 1))
} # FRACTIONAL PART
# ------- Applying Nth-root And Nth-exponent Operators To Create Position Of New Worker Robots -------
worker_robot1 = (master_robot["int"] + np.power(master_robot["frac"], 1 / (1 + self.generator.integers(1, 4)))) * master_robot["sign"]
id_changed1 = np.argwhere(np.round(self.generator.uniform(self.problem.lb, self.problem.ub)))
id_changed1 = np.reshape(id_changed1, (len(id_changed1)))
worker_robot1 = np.reshape(worker_robot1, (self.problem.n_dims, 1))
worker_robot1[id_changed1] = master_robot["original"][id_changed1]
worker_robot2 = (master_robot["int"] + np.power(master_robot["frac"], (1 + self.generator.integers(1, 4)))) * master_robot["sign"]
id_changed2 = np.argwhere(np.round(self.generator.uniform(self.problem.lb, self.problem.ub)))
id_changed2 = np.reshape(id_changed2, (len(id_changed2)))
worker_robot2 = np.reshape(worker_robot2, (self.problem.n_dims, 1))
worker_robot2[id_changed2] = master_robot["original"][id_changed2]
# -------- Applying A Combined Ga-like Operator To Create Position Of New Worker Robot -------------
random_per_mutation = self.generator.permutation(self.problem.n_dims)
sec1 = random_per_mutation[0: int(self.problem.n_dims / 2)]
sec2 = random_per_mutation[int(self.problem.n_dims / 2):]
worker_robot3 = np.zeros((self.problem.n_dims, 1))
worker_robot3[sec1] = (master_robot["int"][sec1] + np.power(master_robot["frac"][sec1],
1 / (1 + self.generator.integers(1, 4)))) * master_robot["sign"][sec1]
worker_robot3[sec2] = (master_robot["int"][sec2] + master_robot["frac"][sec2] **
(1 + self.generator.integers(1, 4))) * master_robot["sign"][sec2]
id_changed3 = np.argwhere(np.round(self.generator.uniform(self.problem.lb, self.problem.ub)))
id_changed3 = np.reshape(id_changed3, (len(id_changed3)))
worker_robot3[id_changed3] = master_robot["original"][id_changed3]
# ------- Applying Round Operators To Create Position Of New Worker Robot -------------------
worker_robot4 = np.ceil(master_robot["abs"]) * master_robot["sign"]
id_changed4 = np.argwhere(np.round(self.generator.uniform(self.problem.lb, self.problem.ub)))
id_changed4 = np.reshape(id_changed4, (len(id_changed4)))
worker_robot4[id_changed4] = master_robot["original"][id_changed4]
worker_robot5 = np.floor(master_robot["abs"]) * master_robot["sign"]
id_changed5 = np.argwhere(np.round(self.generator.uniform(self.problem.lb, self.problem.ub)))
id_changed5 = np.reshape(id_changed5, (len(id_changed5)))
worker_robot5[id_changed5] = master_robot["original"][id_changed5]
# --------- Progress Assessment: Replacing More Quality Solutions With Previous Ones ---------------
workers = np.concatenate((worker_robot1.T, worker_robot2.T, worker_robot3.T, worker_robot4.T, worker_robot5.T), axis=0)
pop_workers = []
for idx in range(0, 5):
pos_new = self.correct_solution(workers[idx])
agent = self.generate_empty_agent(pos_new)
pop_workers.append(agent)
if self.mode not in self.AVAILABLE_MODES:
pop_workers[-1].target = self.get_target(pos_new)
pop_workers = self.update_target_for_population(pop_workers)
for idx in range(0, 5):
if self.compare_target(pop_workers[idx].target, self.pop[1].target, self.problem.minmax):
self.pop[-(idx + 1)].solution = pop_workers[idx].solution.copy()
self.pop[-(idx + 1)].target = pop_workers[idx].target.copy()