Source code for niapy.algorithms.basic.loa

# encoding=utf8
import copy
import logging
import numpy
import numpy as np

from niapy.algorithms.algorithm import Algorithm, Individual, default_individual_init
from niapy.util import objects_to_array

logging.basicConfig()
logger = logging.getLogger('niapy.algorithms.basic')
logger.setLevel('INFO')

__all__ = ['LionOptimizationAlgorithm']


class Lion(Individual):
    r"""Implementation of population individual that is a lion for Lion Optimization Algorithm.

    Algorithm:
        Lion Optimization Algorithm

    Date:
        2021

    Authors:
        Aljoša Mesarec

    License:
        MIT

    Attributes:
        gender (string): Lion gender.
        has_pride (bool): Lion has a pride.
        pride (int): Lion's pride id.
        hunting_group (int): Lion's hunting group.
        current_x (numpy.ndarray): Lion's current position
        current_f (float): Lion's current fitness
        previous_iter_best_f (float): Lion's fitness at end of previous iteration.
        has_improved (bool): Lion has improved fitness since last iteration.

    See Also:
        * :class:`niapy.algorithms.Individual`

    """

    def __init__(self, gender="m", has_pride=False, pride=-1, hunting_group=0, has_improved=True, **kwargs):
        r"""Initialize the Lion.

        Args:
            gender (Optional[string]): Lion's gender.
            has_pride (Optional[bool]): Lion has a pride.
            pride (Optional[int]): Lion's pride id.
            hunting_group (Optional[int]): Lion's hunting group id.
            has_improved (Optional[bool]): Lion has improved fitness since last iteration.

        See Also:
            * :func:`niapy.algorithms.Individual.__init__`

        """
        super().__init__(**kwargs)
        self.gender = gender
        self.has_pride = has_pride
        self.pride = pride
        self.hunting_group = hunting_group
        self.current_x = np.copy(self.x)
        self.current_f = self.f
        self.previous_iter_best_f = self.f
        self.has_improved = has_improved


[docs]class LionOptimizationAlgorithm(Algorithm): r"""Implementation of lion optimization algorithm. Algorithm: Lion Optimization algorithm Date: 2021 Authors: Aljoša Mesarec License: MIT Reference URL: https://doi.org/10.1016/j.jcde.2015.06.003 Reference paper: Yazdani, Maziar, Jolai, Fariborz. Lion Optimization Algorithm (LOA): A nature-inspired metaheuristic algorithm. Journal of Computational Design and Engineering, Volume 3, Issue 1, Pages 24-36. 2016. Attributes: Name (List[str]): List of strings representing name of the algorithm. population_size (Optional[int]): Population size :math:`\in [1, \infty)`. nomad_ratio (Optional[float]): Ratio of nomad lions :math:`\in [0, 1]`. num_of_prides = Number of prides :math:`\in [1, \infty)`. female_ratio = Ratio of female lions in prides :math:`\in [0, 1]`. roaming_factor = Roaming factor :math:`\in [0, 1]`. mating_factor = Mating factor :math:`\in [0, 1]`. mutation_factor = Mutation factor :math:`\in [0, 1]`. immigration_factor = Immigration factor :math:`\in [0, 1]`. See Also: * :class:`niapy.algorithms.Algorithm` """ Name = ['LionOptimizationAlgorithm', 'LOA']
[docs] @staticmethod def info(): r"""Get information about algorithm. Returns: str: Algorithm information See Also: * :func:`niapy.algorithms.Algorithm.info` """ return r'''Yazdani, Maziar, Jolai, Fariborz. Lion Optimization Algorithm (LOA): A nature-inspired metaheuristic algorithm. Journal of Computational Design and Engineering, Volume 3, Issue 1, Pages 24-36. 2016.'''
[docs] def __init__(self, population_size=50, nomad_ratio=0.2, num_of_prides=5, female_ratio=0.8, roaming_factor=0.2, mating_factor=0.3, mutation_factor=0.2, immigration_factor=0.4, *args, **kwargs): r"""Initialize LionOptimizationAlgorithm. Args: population_size (Optional[int]): Population size :math:`\in [1, \infty)`. nomad_ratio (Optional[float]): Ratio of nomad lions :math:`\in [0, 1]`. num_of_prides = Number of prides :math:`\in [1, \infty)`. female_ratio = Ratio of female lions in prides :math:`\in [0, 1]`. roaming_factor = Roaming factor :math:`\in [0, 1]`. mating_factor = Mating factor :math:`\in [0, 1]`. mutation_factor = Mutation factor :math:`\in [0, 1]`. immigration_factor = Immigration factor :math:`\in [0, 1]`. See Also: * :func:`niapy.algorithms.Algorithm.__init__` """ super().__init__(population_size, individual_type=kwargs.pop('individual_type', Lion), initialization_function=kwargs.pop('initialization_function', default_individual_init), *args, **kwargs) self.nomad_ratio = nomad_ratio self.num_of_prides = num_of_prides self.female_ratio = female_ratio self.roaming_factor = roaming_factor self.mating_factor = mating_factor self.mutation_factor = mutation_factor self.immigration_factor = immigration_factor
[docs] def set_parameters(self, population_size=50, nomad_ratio=0.2, num_of_prides=5, female_ratio=0.8, roaming_factor=0.2, mating_factor=0.3, mutation_factor=0.2, immigration_factor=0.4, **kwargs): r"""Set the arguments of an algorithm. Args: population_size (Optional[int]): Population size :math:`\in [1, \infty)`. nomad_ratio (Optional[float]): Ratio of nomad lions :math:`\in [0, 1]`. num_of_prides = Number of prides :math:`\in [1, \infty)`. female_ratio = Ratio of female lions in prides :math:`\in [0, 1]`. roaming_factor = Roaming factor :math:`\in [0, 1]`. mating_factor = Mating factor :math:`\in [0, 1]`. mutation_factor = Mutation factor :math:`\in [0, 1]`. immigration_factor = Immigration factor :math:`\in [0, 1]`. See Also: * :func:`niapy.algorithms.Algorithm.set_parameters` """ super().set_parameters(population_size=population_size, individual_type=Lion, initialization_function=kwargs.pop('initialization_function', default_individual_init()), **kwargs) self.nomad_ratio = nomad_ratio self.num_of_prides = num_of_prides self.female_ratio = female_ratio self.roaming_factor = roaming_factor self.mating_factor = mating_factor self.mutation_factor = mutation_factor self.immigration_factor = immigration_factor
[docs] def get_parameters(self): r"""Get parameters of the algorithm. Returns: Dict[str, Any]: Algorithm Parameters. """ d = super().get_parameters() d.update({ 'nomad_ratio': self.nomad_ratio, 'num_of_prides': self.num_of_prides, 'female_ratio': self.female_ratio, 'roaming_factor': self.roaming_factor, 'mating_factor': self.mating_factor, 'mutation_factor': self.mutation_factor, 'immigration_factor': self.immigration_factor }) return d
[docs] def init_population(self, task): r"""Initialize starting population. Args: task (Task): Optimization task. Returns: Tuple[numpy.ndarray[Lion], numpy.ndarray[float], Dict[str, Any]]: 1. Initialized population of lions. 2. Initialized populations function/fitness values. 3. Additional arguments: * pride_size (numpy.ndarray): Pride and nomad sizes. * gender_distribution (numpy.ndarray): Pride and nomad gender distributions. """ pop, fpop, d = super().init_population(task) pop, d = self.init_population_data(pop, d) return pop, fpop, d
[docs] def init_population_data(self, pop, d): r"""Initialize data of starting population. Args: pop (numpy.ndarray[Lion]: Starting lion population d (Dict[str, Any]): Additional arguments Returns: Tuple[numpy.ndarray[Lion], Dict[str, Any]]: 1. Initialized population of lions. 2. Additional arguments: * pride_size (numpy.ndarray): Pride and nomad sizes. * gender_distribution (numpy.ndarray): Pride and nomad gender distributions. """ nomad_size = round(self.nomad_ratio * self.population_size) # Creating array of pride sizes. pride_size = np.zeros(self.num_of_prides + 1, dtype=int) pride_size[-1] = nomad_size remaining_lions = self.population_size - nomad_size while remaining_lions > 0: if remaining_lions >= self.num_of_prides: pride_size[:self.num_of_prides] += 1 else: pride_size[:remaining_lions] += 1 remaining_lions -= self.num_of_prides # Setting gender and pride id of pride members. index_counter = 0 for i in range(self.num_of_prides): curr_pride_size = pride_size[i] num_of_females = round(self.female_ratio * curr_pride_size) for lion in pop[index_counter:index_counter + curr_pride_size]: lion.has_pride = True lion.pride = i if num_of_females > 0: lion.gender = "f" num_of_females -= 1 index_counter += curr_pride_size # Setting gender of nomads num_of_females = round((1 - self.female_ratio) * pride_size[-1]) for lion in pop[index_counter:index_counter + num_of_females]: lion.gender = "f" # Creating array of pride gender quantities gender_distribution = np.zeros((self.num_of_prides + 1, 2), dtype=int) index_counter = 0 for i in range(self.num_of_prides): curr_pride_size = pride_size[i] for lion in pop[index_counter:index_counter + curr_pride_size]: if lion.gender == "f": gender_distribution[i][0] += 1 elif lion.gender == "m": gender_distribution[i][1] += 1 index_counter += curr_pride_size # Creating array of nomad gender quantities for lion in pop[index_counter:]: if lion.gender == "f": gender_distribution[self.num_of_prides][0] += 1 elif lion.gender == "m": gender_distribution[self.num_of_prides][1] += 1 # Updating params d.update({'pride_size': pride_size, 'gender_distribution': gender_distribution}) return pop, d
[docs] def hunting(self, population, pride_size, task): r"""Pride female hunters go hunting. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. task (Task): Optimization task. Returns: population (numpy.ndarray[Lion]): Lion population that finished with hunting. """ num_of_prides = len(pride_size) - 1 index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] prey_x = np.zeros(task.dimension, dtype=float) num_of_hunters = 0 hunting_group_fitness = np.zeros(4) for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.gender == "f": lion.hunting_group = self.integers(0, 4) hunting_group_fitness[lion.hunting_group] += lion.current_f if lion.hunting_group != 0: prey_x += lion.current_x num_of_hunters += 1 # Group with highest fitness becomes center group, the rest become left and right groups sorted_hunting_group_indices = np.argsort(hunting_group_fitness[1:]) right_group, left_group, center_group = sorted_hunting_group_indices + 1 # Prey's position is average position of hunters. if num_of_hunters != 0: prey_x /= num_of_hunters # Check if prey's new position is in limits. prey_x = task.repair(prey_x) # Calculate new positions of hunters with "Opposition-Based Learning method". for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.hunting_group == left_group or lion.hunting_group == right_group: for i in range(task.dimension): if (2 * prey_x[i] - lion.current_x[i]) < prey_x[i]: lion.current_x[i] = self.uniform((2 * prey_x[i] - lion.current_x[i]), prey_x[i]) elif (2 * prey_x[i] - lion.current_x[i]) > prey_x[i]: lion.current_x[i] = self.uniform(prey_x[i], (2 * prey_x[i] - lion.current_x[i])) if lion.hunting_group == center_group: for i in range(task.dimension): if lion.current_x[i] < prey_x[i]: lion.current_x[i] = self.uniform(lion.current_x[i], prey_x[i]) elif lion.current_x[i] > prey_x[i]: lion.current_x[i] = self.uniform(prey_x[i], lion.current_x[i]) if lion.hunting_group != 0: # Check if lion's new position is in limits. lion.current_x = task.repair(lion.current_x) lion.current_f = task.eval(lion.current_x) # If hunter's new fitness is better then change prey's position. if lion.current_f < lion.f: lion.x = np.copy(lion.current_x) lion.f = lion.current_f percentage_of_improvement = 1 - lion.f / lion.previous_iter_best_f prey_x = prey_x + self.random() * percentage_of_improvement * (prey_x - lion.current_x) # Check if prey's new position is in limits. prey_x = task.repair(prey_x) index_counter_pride += curr_pride_size return population
[docs] def move_to_safe_place(self, population, pride_size, task): r"""Female pride lions move towards position with good fitness. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. task (Task): Optimization task. Returns: population (numpy.ndarray[Lion]): Lion population that finished with moving to safe place. """ num_of_prides = len(pride_size) - 1 index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] num_of_improvements = 0 pride_territory = [] for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: lion_copy = copy.deepcopy(lion) pride_territory = np.append(pride_territory, objects_to_array([lion_copy])) if lion.has_improved: num_of_improvements += 1 # Tournament selection to select places in territory if there's more than 2 places if len(pride_territory) > 1: tournament_size = max(2, int(np.ceil(num_of_improvements / 2))) tournament_selections = self.rng.choice(pride_territory, tournament_size, replace=False) tournament_winner = tournament_selections[0].x.copy() tournament_min_f = tournament_selections[0].f for candidate in tournament_selections[1:]: if candidate.f < tournament_min_f: tournament_min_f = candidate.f tournament_winner = candidate.x.copy() else: tournament_winner = pride_territory[0].x.copy() tournament_min_f = pride_territory[0].f # Move female non-hunters for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.gender == "f" and lion.hunting_group == 0: # Get vector r_one. r_one = tournament_winner.copy() r_one -= lion.x # Get vector 2_two with Gram-Schmidt process. if np.linalg.norm((r_one).T) == 0: # If r_one vector is 0 then Gram-Schmidt process return wrong values r_two = np.zeros(len(r_one.T)) rand_index = self.integers(0, len(r_one.T)) r_two[rand_index] = 1 else: # Gram-Schmidt process to find orthogonal vector r_two. random_vec = self.standard_normal(len(r_one)) r_two = random_vec - ((r_one.T).dot(random_vec)) / ((r_one.T).dot(r_one)) * r_one # Calculate other variables and new lion's position d = np.linalg.norm(r_one) / np.linalg.norm(task.upper[0] - task.lower[0]) rnd_num = self.random() rnd_num_u = self.uniform(-1, 1) angle = self.uniform(-np.pi / 6, np.pi / 6) lion.current_x += 2 * d * rnd_num * r_one + rnd_num_u * np.tan(angle) * d * r_two # Check if lion's current position is in limits. lion.current_x = task.repair(lion.current_x) lion.current_f = task.eval(lion.current_x) # If lion's position has improved update best position and fitness if lion.current_f < lion.f: lion.x = np.copy(lion.current_x) lion.f = lion.current_f index_counter_pride += curr_pride_size return population
[docs] def roaming(self, population, pride_size, task): r"""Male lions move towards new position. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. task (Task): Optimization task. Returns: population (numpy.ndarray[Lion]): Lion population that finished with roaming. """ num_of_prides = len(pride_size) - 1 index_counter_pride = 0 # Pride lions roam. for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.gender == "m": # Select all lions in pride. pride_lions = [] for p_l in population[index_counter_pride:index_counter_pride + curr_pride_size]: lion_copy = copy.deepcopy(p_l) pride_lions = np.append(pride_lions, objects_to_array([lion_copy])) # Select random lions, their amount is based on roaming factor. num_of_selected_lions = round(len(pride_lions) * self.roaming_factor) selected_lions = self.rng.choice(pride_lions, num_of_selected_lions, replace=False) # Move towards territories of selected lions for selected_lion in selected_lions: d = np.linalg.norm(selected_lion.x - lion.x) / np.linalg.norm(task.upper[0] - task.lower[0]) x = self.uniform(0, 2 * d) angle = self.uniform(-np.pi / 6, np.pi / 6) lion.current_x += x * d * np.tan(angle) # Check if lion's new position is in limits. lion.current_x = task.repair(lion.current_x) lion.current_f = task.eval(lion.current_x) # Update best position/fitness if lion's best position is improved if lion.current_f < lion.f: lion.x = np.copy(lion.current_x) lion.f = lion.current_f index_counter_pride += curr_pride_size # Nomad lions roam. nomad_size = pride_size[-1] for lion in population[len(population) - nomad_size:]: best_nomad_fitness = np.min([c_l.current_f for c_l in population[len(population) - nomad_size:]]) roaming_probability = 0.1 + np.minimum(0.5, (lion.current_f - best_nomad_fitness) / best_nomad_fitness) # If roaming threshold is met, move lion to a random new position. if self.random() <= roaming_probability: lion.current_x = self.uniform(task.lower, task.upper, task.dimension) lion.current_f = task.eval(lion.current_x) # Update best position/fitness if lion's best position is improved if lion.current_f < lion.f: lion.x = np.copy(lion.current_x) lion.f = lion.current_f return population
[docs] def mating(self, population, pride_size, gender_distribution, task): r"""Female lions mate with male lions to produce offspring. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. gender_distribution (numpy.ndarray[int]): Pride and nomad gender distribution. task (Task): Optimization task. Returns: Tuple[numpy.ndarray[Lion], numpy.ndarray[int]): 1. Lion population that finished with mating. 2. Pride and nomad excess gender quantities. """ added_cubs = [] excess_lion_gender_quantities = np.zeros((self.num_of_prides + 1, 2), dtype=int) num_of_prides = len(pride_size) - 1 # Copy of all pride lions. pride_lions = [] index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: lion_copy = copy.deepcopy(lion) pride_lions = np.append(pride_lions, objects_to_array([lion_copy])) index_counter_pride += curr_pride_size # Copy of all nomad lions. nomad_lions = [] nomad_size = pride_size[-1] for lion in population[len(population) - nomad_size]: lion_copy = copy.deepcopy(lion) nomad_lions = np.append(nomad_lions, objects_to_array([lion_copy])) # Prides mating. index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] num_of_males = gender_distribution[pride_i][1] # If there's at least 1 male, proceed if num_of_males != 0: # Array of males. males = [] for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.gender == "m": lion_copy = copy.deepcopy(lion) males = np.append(males, objects_to_array([lion_copy])) # Mate all females with a mating probability. for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.gender == "f" and self.random() < self.mating_factor: # Choose males that will mate. num_of_mating_males = self.integers(1, num_of_males) mating_males = self.rng.choice(males, num_of_mating_males, replace=False) beta = self.normal(0.5, 0.1) # Total position x of mating males. Needed for calculating average. mating_males_x_sum = np.zeros(task.dimension) for mating_male in mating_males: mating_males_x_sum = np.add(mating_males_x_sum, mating_male.x) # Calculate position x for offsprings. offspring_one_position = beta * lion.x + ((1 - beta) * mating_males_x_sum / num_of_mating_males) offspring_two_position = (1 - beta) * lion.x + (beta * mating_males_x_sum / num_of_mating_males) # Mutation of the genes with mutation probability. for i in range(task.dimension): if self.random() < self.mutation_factor: offspring_one_position[i] = self.uniform(task.lower[i], task.upper[i], 1) if self.random() < self.mutation_factor: offspring_two_position[i] = self.uniform(task.lower[i], task.upper[i], 1) # Create offspring Lion objects offspring_one = copy.deepcopy(lion) offspring_two = copy.deepcopy(lion) offspring_one.has_pride = True offspring_two.has_pride = True offspring_one.pride = pride_i offspring_two.pride = pride_i offspring_one.hunting_group = 0 offspring_two.hunting_group = 0 offspring_one.has_improved = True offspring_two.has_improved = True # Randomly assign genders to offsprings. if self.random() < 0.5: offspring_one.gender = "m" offspring_two.gender = "f" else: offspring_one.gender = "f" offspring_two.gender = "m" offspring_one.x = offspring_one_position offspring_two.x = offspring_two_position # Check if offspring's position is in limits. offspring_one.evaluate(task) offspring_two.evaluate(task) # Assign other offspring's values. offspring_one.current_x = np.copy(offspring_one.x) offspring_two.current_x = np.copy(offspring_two.x) offspring_one.current_f = offspring_one.f offspring_two.current_f = offspring_two.f offspring_one.previous_iter_best_f = offspring_one.f + 1 offspring_two.previous_iter_best_f = offspring_two.f + 1 # Add offspring to array of added cubs. added_cubs = np.append(added_cubs, objects_to_array([offspring_one])) added_cubs = np.append(added_cubs, objects_to_array([offspring_two])) excess_lion_gender_quantities[pride_i][0] += 1 excess_lion_gender_quantities[pride_i][1] += 1 pride_size[pride_i] += 2 gender_distribution[pride_i][0] += 1 gender_distribution[pride_i][1] += 1 index_counter_pride += curr_pride_size # Nomads mating. nomad_size = pride_size[-1] num_of_males = gender_distribution[pride_i][1] # If there's at least one male nomad, proceed if num_of_males != 0: # Create array of males that can mate. males = [] for lion in population[len(population) - nomad_size:]: if lion.gender == "m": lion_copy = copy.deepcopy(lion) males = np.append(males, objects_to_array([lion_copy])) # Mate all females with mating probability. for lion in population[len(population) - nomad_size:]: if lion.gender == "f" and self.random() < self.mating_factor: # Choose one male that will mate. mating_male = self.rng.choice(males) beta = self.normal(0.5, 0.1) # Calculate x for offsprings. offspring_one_position = beta * lion.x + ((1 - beta) * mating_male.x) offspring_two_position = (1 - beta) * lion.x + beta * mating_male.x # Mutation of the genes with mutation probability. for i in range(task.dimension): if self.random() < self.mutation_factor: offspring_one_position[i] = self.uniform(task.lower[i], task.upper[i]) if self.random() < self.mutation_factor: offspring_two_position[i] = self.uniform(task.lower[i], task.upper[i]) # Create offspring Lion objects. offspring_one = copy.deepcopy(lion) offspring_two = copy.deepcopy(lion) offspring_one.has_pride = False offspring_two.has_pride = False offspring_one.pride = -1 offspring_two.pride = -1 offspring_one.hunting_group = 0 offspring_two.hunting_group = 0 offspring_one.has_improved = True offspring_two.has_improved = True # Randomly assign genders to offsprings. if self.random() < 0.5: offspring_one.gender = "m" offspring_two.gender = "f" else: offspring_one.gender = "f" offspring_two.gender = "m" offspring_one.x = offspring_one_position offspring_two.x = offspring_two_position # Check if offspring's position is in limits. offspring_one.evaluate(task) offspring_two.evaluate(task) # Assign other offspring's values. offspring_one.current_x = np.copy(offspring_one.x) offspring_two.current_x = np.copy(offspring_two.x) offspring_one.current_f = offspring_one.f offspring_two.current_f = offspring_two.f offspring_one.previous_iter_best_f = offspring_one.f + 1 offspring_two.previous_iter_best_f = offspring_two.f + 1 # Add offspring to array of added cubs added_cubs = np.append(added_cubs, objects_to_array([offspring_one])) added_cubs = np.append(added_cubs, objects_to_array([offspring_two])) excess_lion_gender_quantities[-1][0] += 1 excess_lion_gender_quantities[-1][1] += 1 pride_size[-1] += 2 gender_distribution[-1][0] += 1 gender_distribution[-1][1] += 1 # Add pride originals and cubs to same population. new_population = [] original_index_counter_pride = 0 cub_index_counter_pride = 0 for pride_i in range(num_of_prides): # Append original pride lion. curr_original_pride_size = pride_size[pride_i] - excess_lion_gender_quantities[pride_i][0] - excess_lion_gender_quantities[pride_i][1] for lion in population[original_index_counter_pride:original_index_counter_pride + curr_original_pride_size]: lion_copy = copy.deepcopy(lion) new_population = np.append(new_population, objects_to_array([lion_copy])) # Append cub pride lions. curr_cub_pride_size = excess_lion_gender_quantities[pride_i][0] + excess_lion_gender_quantities[pride_i][1] for lion in added_cubs[cub_index_counter_pride:cub_index_counter_pride + curr_cub_pride_size]: lion_copy = copy.deepcopy(lion) new_population = np.append(new_population, objects_to_array([lion_copy])) original_index_counter_pride += curr_original_pride_size cub_index_counter_pride += curr_cub_pride_size # Add nomad originals and cubs to same population. originals_nomad_size = pride_size[-1] - excess_lion_gender_quantities[-1][0] - excess_lion_gender_quantities[-1][1] for lion in population[len(population) - originals_nomad_size:]: lion_copy = copy.deepcopy(lion) new_population = np.append(new_population, objects_to_array([lion_copy])) cubs_nomad_size = excess_lion_gender_quantities[-1][0] + excess_lion_gender_quantities[-1][1] for lion in added_cubs[len(added_cubs) - cubs_nomad_size:]: lion_copy = copy.deepcopy(lion) new_population = np.append(new_population, objects_to_array([lion_copy])) return new_population, excess_lion_gender_quantities
[docs] def defense(self, population, pride_size, gender_distribution, excess_lion_gender_quantities, task): r"""Male lions attack other lions in pride. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. gender_distribution (numpy.ndarray[int]): Pride and nomad gender distribution. excess_lion_gender_quantities (numpy.ndarray[int]): Pride and nomad excess members. task (Task): Optimization task. Returns: Tuple[numpy.ndarray[Lion], numpy.ndarray[int]): 1. Lion population that finished with defending. 2. Pride and nomad excess gender quantities. """ new_nomads = [] original_pride_lions = [] num_of_prides = len(pride_size) - 1 # Pride lions defense index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] num_of_males_to_be_kicked = excess_lion_gender_quantities[pride_i][1] males = [] # Go through pride for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: lion_copy = copy.deepcopy(lion) if lion.gender == "m": males = np.append(males, objects_to_array([lion_copy])) elif lion.gender == "f": original_pride_lions = np.append(original_pride_lions, objects_to_array([lion_copy])) # Find males with worst fitness that will be kicked, leave the rest males = sorted(males, key=lambda lion: lion.current_f, reverse=True) for lion in males: lion_copy = copy.deepcopy(lion) if num_of_males_to_be_kicked == 0: original_pride_lions = np.append(original_pride_lions, objects_to_array([lion_copy])) else: new_nomads = np.append(new_nomads, objects_to_array([lion_copy])) num_of_males_to_be_kicked -= 1 index_counter_pride += curr_pride_size # Create new population after kicking pride lions moved_population = [] # Append original pride lions. for lion in original_pride_lions: lion_copy = copy.deepcopy(lion) moved_population = np.append(moved_population, objects_to_array([lion_copy])) # Append original nomads. original_nomads_size = pride_size[-1] for lion in population[len(population) - original_nomads_size:]: lion_copy = copy.deepcopy(lion) moved_population = np.append(moved_population, objects_to_array([lion_copy])) # Append new nomads. for lion in new_nomads: lion_copy = copy.deepcopy(lion) excess_lion_gender_quantities[lion_copy.pride][1] -= 1 gender_distribution[lion_copy.pride][1] -= 1 pride_size[lion_copy.pride] -= 1 lion_copy.has_pride = False lion_copy.pride = -1 moved_population = np.append(moved_population, objects_to_array([lion_copy])) excess_lion_gender_quantities[-1][1] += 1 gender_distribution[-1][1] += 1 pride_size[-1] += 1 # Nomad lions defense. nomads_size = pride_size[-1] for nomad_lion in moved_population[len(moved_population) - nomads_size:]: nomad_lion_has_won = False # Create binary template - which prides a nomad lion will attack. pride_index_to_attack = np.zeros(num_of_prides, dtype=int) for i in range(num_of_prides): if self.random() < 0.5: pride_index_to_attack[i] = 1 # Nomad attacks prides based on binary template. index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] if pride_index_to_attack[pride_i] == 1: # Attack all male lions. for pride_lion in moved_population[index_counter_pride:index_counter_pride + curr_pride_size]: if lion.gender == "m": # Swap nomad and pride lion if nomad has better fitness. if nomad_lion.current_f < pride_lion.current_f: copy_nomad_lion = copy.deepcopy(nomad_lion) copy_pride_lion = copy.deepcopy(pride_lion) pride_lion = copy_nomad_lion pride_lion.has_pride = True pride_lion.pride = pride_i nomad_lion = copy_pride_lion nomad_lion.has_pride = False nomad_lion.pride = -1 # If nomad lion won the attack, we continue with next nomad. nomad_lion_has_won = True break if nomad_lion_has_won: break index_counter_pride += curr_pride_size return moved_population, excess_lion_gender_quantities
[docs] def migration(self, population, pride_size, gender_distribution, excess_lion_gender_quantities, task): r"""Female lions randomly become nomad. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. gender_distribution (numpy.ndarray[int]): Pride and nomad gender distribution. excess_lion_gender_quantities (numpy.ndarray[int]): Pride and nomad excess members. task (Task): Optimization task. Returns: Tuple[numpy.ndarray[Lion], numpy.ndarray[int]): 1. Lion population that finished with migration. 2. Pride and nomad excess gender quantities. """ new_nomads = [] original_pride_lions = [] num_of_prides = len(pride_size) - 1 # Pride females migration index_counter_pride = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] num_of_females = gender_distribution[pride_i][0] num_of_excess_females = excess_lion_gender_quantities[pride_i][0] num_of_females_to_migrate = num_of_excess_females + round(((num_of_females - num_of_excess_females) * self.immigration_factor)) females = [] # Go through pride for lion in population[index_counter_pride:index_counter_pride + curr_pride_size]: lion_copy = copy.deepcopy(lion) if lion.gender == "m": original_pride_lions = np.append(original_pride_lions, objects_to_array([lion_copy])) elif lion.gender == "f": females = np.append(females, objects_to_array([lion_copy])) # Migrate random females, leave the rest females_indices_to_migrate = np.zeros(num_of_females, dtype=int) for i in range(num_of_females_to_migrate): females_indices_to_migrate[i] = 1 self.rng.shuffle(females_indices_to_migrate) for i, lion in enumerate(females): lion_copy = copy.deepcopy(lion) if females_indices_to_migrate[i] == 1: new_nomads = np.append(new_nomads, objects_to_array([lion_copy])) else: original_pride_lions = np.append(original_pride_lions, objects_to_array([lion_copy])) index_counter_pride += curr_pride_size # Create new population after migrating pride lions moved_population = [] # Append original pride lions for lion in original_pride_lions: lion_copy = copy.deepcopy(lion) moved_population = np.append(moved_population, objects_to_array([lion_copy])) # Append original nomads original_nomads_size = pride_size[-1] for lion in population[len(population) - original_nomads_size:]: lion_copy = copy.deepcopy(lion) moved_population = np.append(moved_population, objects_to_array([lion_copy])) # Append new nomads for lion in new_nomads: lion_copy = copy.deepcopy(lion) excess_lion_gender_quantities[lion_copy.pride][0] -= 1 gender_distribution[lion_copy.pride][0] -= 1 pride_size[lion_copy.pride] -= 1 lion_copy.has_pride = False lion_copy.pride = -1 moved_population = np.append(moved_population, objects_to_array([lion_copy])) excess_lion_gender_quantities[-1][0] += 1 gender_distribution[-1][0] += 1 pride_size[-1] += 1 # Fill up empty female pride spaces with best nomad female lions prides_spots_to_be_filled = 0 for i in range(num_of_prides): prides_spots_to_be_filled += np.abs(excess_lion_gender_quantities[i][0]) nomad_females = [] nomad_males = [] original_nomads_size = pride_size[-1] for lion in moved_population[len(population) - original_nomads_size:]: lion_copy = copy.deepcopy(lion) if lion.gender == "f": nomad_females = np.append(nomad_females, objects_to_array([lion_copy])) elif lion.gender == "m": nomad_males = np.append(nomad_males, objects_to_array([lion_copy])) nomad_females = sorted(nomad_females, key=lambda lion: lion.current_f, reverse=False) nomad_females_to_move = [] nomad_females_to_keep = [] counter = prides_spots_to_be_filled for lion in nomad_females: lion_copy = copy.deepcopy(lion) if not counter == 0: nomad_females_to_move = np.append(nomad_females_to_move, objects_to_array([lion_copy])) counter -= 1 else: nomad_females_to_keep = np.append(nomad_females_to_keep, objects_to_array([lion_copy])) self.rng.shuffle(nomad_females_to_move) # Append pride lions and moved female nomads final_population = [] index_counter_pride = 0 index_females_to_move = 0 for pride_i in range(num_of_prides): curr_pride_size = pride_size[pride_i] # Append pride lions for lion in moved_population[index_counter_pride:index_counter_pride + curr_pride_size]: lion_copy = copy.deepcopy(lion) final_population = np.append(final_population, objects_to_array([lion_copy])) # Append female nomads curr_pride_spots_empty = np.abs(excess_lion_gender_quantities[pride_i][0]) for lion in nomad_females_to_move[index_females_to_move:index_females_to_move + curr_pride_spots_empty]: lion_copy = copy.deepcopy(lion) excess_lion_gender_quantities[lion_copy.pride][0] -= 1 gender_distribution[lion_copy.pride][0] -= 1 pride_size[lion_copy.pride] -= 1 lion_copy.has_pride = True lion_copy.pride = pride_i final_population = np.append(final_population, objects_to_array([lion_copy])) excess_lion_gender_quantities[pride_i][0] += 1 gender_distribution[pride_i][0] += 1 pride_size[pride_i] += 1 # Increase starting indices for array search index_counter_pride += curr_pride_size index_females_to_move += curr_pride_spots_empty # Append the kept nomad females for lion in nomad_females_to_keep: lion_copy = copy.deepcopy(lion) final_population = np.append(final_population, objects_to_array([lion_copy])) # Append nomad males for lion in nomad_males: lion_copy = copy.deepcopy(lion) final_population = np.append(final_population, objects_to_array([lion_copy])) return final_population, excess_lion_gender_quantities
[docs] def population_equilibrium(self, population, pride_size, gender_distribution, excess_lion_gender_quantities, task): r"""Remove extra nomad lions. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. gender_distribution (numpy.ndarray[int]): Pride and nomad gender distribution. excess_lion_gender_quantities (numpy.ndarray[int]): Pride and nomad excess members. task (Task): Optimization task. Returns: final_population (numpy.ndarray[Lion]): Lion population with removed extra nomads. """ nomad_females = [] nomad_males = [] kept_nomads = [] # Number of males and females that need to be removed. num_of_female_nomads_to_remove = excess_lion_gender_quantities[-1][0] num_of_male_nomads_to_remove = excess_lion_gender_quantities[-1][1] original_nomads_size = pride_size[-1] # Get lists of nomad males and females. for lion in population[len(population) - original_nomads_size:]: lion_copy = copy.deepcopy(lion) if lion.gender == "f": nomad_females = np.append(nomad_females, objects_to_array([lion_copy])) elif lion.gender == "m": nomad_males = np.append(nomad_males, objects_to_array([lion_copy])) # Sort lists descendingly. nomad_males = sorted(nomad_males, key=lambda lion: lion.current_f, reverse=True) nomad_females = sorted(nomad_females, key=lambda lion: lion.current_f, reverse=True) # Remove extra lions that have bad fitness, keep the rest. for lion in nomad_males[num_of_male_nomads_to_remove:]: lion_copy = copy.deepcopy(lion) kept_nomads = np.append(kept_nomads, objects_to_array([lion_copy])) for lion in nomad_females[num_of_female_nomads_to_remove:]: lion_copy = copy.deepcopy(lion) kept_nomads = np.append(kept_nomads, objects_to_array([lion_copy])) # Append pride lions to final population. final_population = [] for lion in population[:len(population) - original_nomads_size]: lion_copy = copy.deepcopy(lion) final_population = np.append(final_population, objects_to_array([lion_copy])) # Append kept nomads to final population. for lion in kept_nomads: lion_copy = copy.deepcopy(lion) final_population = np.append(final_population, objects_to_array([lion_copy])) pride_size[-1] -= (num_of_female_nomads_to_remove + num_of_male_nomads_to_remove) gender_distribution[-1][0] -= num_of_female_nomads_to_remove gender_distribution[-1][1] -= num_of_male_nomads_to_remove return final_population
[docs] def data_correction(self, population, pride_size, task): r"""Update lion's data if his position has improved since last iteration. Args: population (numpy.ndarray[Lion]): Lion population. pride_size (numpy.ndarray[int]): Pride and nomad sizes. task (Task): Optimization task. Returns: population (numpy.ndarray[Lion]): Lion population with corrected data. """ for lion in population: if lion.f < lion.previous_iter_best_f: lion.has_improved = True lion.previous_iter_best_f = lion.f else: lion.has_improved = False return population
[docs] def run_iteration(self, task, population, population_fitness, best_x, best_fitness, **params): pride_size = params.pop('pride_size') gender_distribution = params.pop('gender_distribution') # Algorithm steps lions = self.hunting(population, pride_size, task) lions = self.move_to_safe_place(lions, pride_size, task) lions = self.roaming(lions, pride_size, task) lions, excess_lion_gender_quantities = self.mating(lions, pride_size, gender_distribution, task) lions, excess_lion_gender_quantities = self.defense(lions, pride_size, gender_distribution, excess_lion_gender_quantities, task) lions, excess_lion_gender_quantities = self.migration(lions, pride_size, gender_distribution, excess_lion_gender_quantities, task) lions = self.population_equilibrium(lions, pride_size, gender_distribution, excess_lion_gender_quantities, task) lions = self.data_correction(lions, pride_size, task) lions_fitness = np.asarray([lion.f for lion in lions]) best_x, best_fitness = self.get_best(lions, lions_fitness, best_x, best_fitness) return lions, lions_fitness, best_x, best_fitness, {'pride_size': pride_size, 'gender_distribution': gender_distribution}