0% found this document useful (0 votes)
7 views22 pages

Genetic Bee Colony

The document describes the implementation of a Genetic Bee Colony (GBC) algorithm, which combines Artificial Bee Colony optimization with Genetic Algorithm operations to improve search capabilities. It outlines the initialization of the algorithm, including parameters such as objective function, population size, and mutation rates, as well as the various phases of the algorithm including employed bee, crossover, onlooker bee, and scout bee phases. The optimize method executes the algorithm and logs progress, returning optimization results.

Uploaded by

elskansah9022
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views22 pages

Genetic Bee Colony

The document describes the implementation of a Genetic Bee Colony (GBC) algorithm, which combines Artificial Bee Colony optimization with Genetic Algorithm operations to improve search capabilities. It outlines the initialization of the algorithm, including parameters such as objective function, population size, and mutation rates, as well as the various phases of the algorithm including employed bee, crossover, onlooker bee, and scout bee phases. The optimize method executes the algorithm and logs progress, returning optimization results.

Uploaded by

elskansah9022
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 22

import numpy as np

import random

import time

from typing import Callable, Tuple, List, Dict

class GeneticBeeColony:

"""

Genetic Bee Colony (GBC) algorithm implementation.

This hybrid algorithm combines Artificial Bee Colony (ABC) optimization


with

Genetic Algorithm (GA) operations to enhance search capabilities.

"""

def __init__(

self,

objective_function: Callable,

dimensions: int,

lower_bound: List[float] or float,

upper_bound: List[float] or float,

minimize: bool = True,

population_size: int = 50,

max_iterations: int = 1000,

abandonment_limit: int = None,

mutation_rate: float = 0.1,

crossover_rate: float = 0.8,

selection_pressure: float = 3.0,


genetic_influence: float = 0.5,

elite_count: int = 2,

seed: int = None

):

"""

Initialize the GBC algorithm.

Args:

objective_function: Function to optimize

dimensions: Number of dimensions in the problem

lower_bound: Lower bounds for each dimension (or a single value for
all dimensions)

upper_bound: Upper bounds for each dimension (or a single value for
all dimensions)

minimize: If True, the objective function will be minimized; otherwise


maximized

population_size: Number of solutions in the population (should be


even)

max_iterations: Maximum number of iterations

abandonment_limit: Maximum number of trials before abandoning a


solution

mutation_rate: Probability of mutation

crossover_rate: Probability of crossover

selection_pressure: Controls bias towards better solutions

genetic_influence: Factor controlling genetic influence on bee


movement

elite_count: Number of elite solutions to preserve

seed: Random seed for reproducibility

"""
# Set random seed if provided

if seed is not None:

np.random.seed(seed)

random.seed(seed)

# Store parameters

self.objective_function = objective_function

self.dimensions = dimensions

self.minimize = minimize

self.population_size = population_size if population_size % 2 == 0 else


population_size + 1

self.max_iterations = max_iterations

self.abandonment_limit = abandonment_limit if abandonment_limit is


not None else dimensions * population_size

self.mutation_rate = mutation_rate

self.crossover_rate = crossover_rate

self.selection_pressure = selection_pressure

self.genetic_influence = genetic_influence

self.elite_count = min(elite_count, population_size // 2)

# Convert bounds to arrays if they are single values

if isinstance(lower_bound, (int, float)):

self.lower_bound = np.array([lower_bound] * self.dimensions)

else:

self.lower_bound = np.array(lower_bound)

if isinstance(upper_bound, (int, float)):


self.upper_bound = np.array([upper_bound] * self.dimensions)

else:

self.upper_bound = np.array(upper_bound)

# Initialize population and other variables

self.population = self._initialize_population()

self.obj_values = np.array([self._evaluate(sol) for sol in self.population])

self.fitness_values = self._calculate_fitness(self.obj_values)

self.trials = np.zeros(self.population_size)

# Keep track of the best solution

self.best_index = np.argmax(self.fitness_values)

self.best_solution = np.copy(self.population[self.best_index])

self.best_obj_value = self.obj_values[self.best_index]

# For statistics

self.convergence_curve = []

self.best_solutions_history = []

self.iteration_times = []

def _initialize_population(self) -> np.ndarray:

"""Initialize the population with random solutions within bounds."""

population = np.zeros((self.population_size, self.dimensions))

for i in range(self.population_size):

population[i] = self.lower_bound +
np.random.random(self.dimensions) * (self.upper_bound - self.lower_bound)

return population
def _evaluate(self, solution: np.ndarray) -> float:

"""Evaluate a solution using the objective function."""

return self.objective_function(solution)

def _calculate_fitness(self, obj_values: np.ndarray) -> np.ndarray:

"""

Convert objective values to fitness values.

For minimization, smaller objective values yield higher fitness.

For maximization, larger objective values yield higher fitness.

"""

fitness = np.zeros_like(obj_values)

if self.minimize:

# For minimization problems

for i in range(len(obj_values)):

if obj_values[i] >= 0:

fitness[i] = 1 / (1 + obj_values[i])

else:

fitness[i] = 1 + abs(obj_values[i])

else:

# For maximization problems

for i in range(len(obj_values)):

if obj_values[i] >= 0:

fitness[i] = obj_values[i]

else:

fitness[i] = 1 + abs(obj_values[i])
return fitness

def _tournament_selection(self, tournament_size: int = 2) -> int:

"""Select a solution using tournament selection."""

candidates = random.sample(range(self.population_size),
tournament_size)

best_candidate = candidates[0]

for candidate in candidates[1:]:

if self.fitness_values[candidate] > self.fitness_values[best_candidate]:

best_candidate = candidate

return best_candidate

def _roulette_wheel_selection(self) -> int:

"""Select a solution using roulette wheel selection."""

# Apply selection pressure to increase bias toward better solutions

selection_probs = (self.fitness_values ** self.selection_pressure)

selection_probs /= np.sum(selection_probs)

return np.random.choice(self.population_size, p=selection_probs)

def _create_neighbor(self, index: int) -> np.ndarray:

"""

Create a neighbor solution by modifying one dimension of the current


solution.

Uses ABC movement formula enhanced with genetic information.


"""

# Select a random dimension to modify

dim = random.randint(0, self.dimensions - 1)

# Select another random solution

other_index = random.randint(0, self.population_size - 1)

while other_index == index:

other_index = random.randint(0, self.population_size - 1)

# Create a copy of the current solution

neighbor = np.copy(self.population[index])

# Calculate genetic factor based on solution ranks

ranks = np.argsort(np.argsort(-self.fitness_values)) # Higher fitness ->


lower rank

genetic_factor = 1.0 - (ranks[index] / self.population_size)

# Apply the modified ABC formula with genetic influence

phi = random.uniform(-1, 1)

neighbor[dim] = neighbor[dim] + phi * (neighbor[dim] -


self.population[other_index][dim]) * (1 + self.genetic_influence *
genetic_factor)

# Ensure the solution is within bounds

neighbor[dim] = np.clip(neighbor[dim], self.lower_bound[dim],


self.upper_bound[dim])

return neighbor
def _mutate(self, solution: np.ndarray) -> np.ndarray:

"""Apply mutation to a solution."""

mutated = np.copy(solution)

for i in range(self.dimensions):

if random.random() < self.mutation_rate:

# Apply random resetting mutation

mutated[i] = self.lower_bound[i] + random.random() *


(self.upper_bound[i] - self.lower_bound[i])

return mutated

def _crossover(self, parent1: np.ndarray, parent2: np.ndarray) ->


Tuple[np.ndarray, np.ndarray]:

"""Apply crossover to two parent solutions to create two offspring."""

if random.random() > self.crossover_rate:

return np.copy(parent1), np.copy(parent2)

# Apply uniform crossover

offspring1 = np.copy(parent1)

offspring2 = np.copy(parent2)

for i in range(self.dimensions):

if random.random() < 0.5:

offspring1[i], offspring2[i] = offspring2[i], offspring1[i]


return offspring1, offspring2

def _preserve_elites(self, new_population: np.ndarray, new_obj_values:


np.ndarray) -> Tuple[np.ndarray, np.ndarray]:

"""Preserve elite solutions by replacing the worst solutions in the new


population."""

if self.elite_count == 0:

return new_population, new_obj_values

# Find the indices of the elite solutions in the current population

elite_indices = np.argsort(self.fitness_values)[-self.elite_count:]

# Find the indices of the worst solutions in the new population

new_fitness = self._calculate_fitness(new_obj_values)

worst_indices = np.argsort(new_fitness)[:self.elite_count]

# Replace worst solutions with elites

for i, elite_idx in enumerate(elite_indices):

worst_idx = worst_indices[i]

new_population[worst_idx] = np.copy(self.population[elite_idx])

new_obj_values[worst_idx] = self.obj_values[elite_idx]

return new_population, new_obj_values

def _employed_bee_phase(self) -> None:

"""

Employed bee phase: Each employed bee is associated with a food


source
and tries to find a better solution in its neighborhood.

"""

for i in range(self.population_size):

# Generate neighbor solution

neighbor = self._create_neighbor(i)

# Apply mutation with probability

if random.random() < self.mutation_rate:

neighbor = self._mutate(neighbor)

# Evaluate neighbor

neighbor_obj_value = self._evaluate(neighbor)

neighbor_fitness =
self._calculate_fitness(np.array([neighbor_obj_value]))[0]

# Apply greedy selection

if neighbor_fitness > self.fitness_values[i]:

self.population[i] = neighbor

self.obj_values[i] = neighbor_obj_value

self.fitness_values[i] = neighbor_fitness

self.trials[i] = 0

# Update best solution if needed

if (self.minimize and neighbor_obj_value < self.best_obj_value) or \

(not self.minimize and neighbor_obj_value > self.best_obj_value):

self.best_solution = np.copy(neighbor)

self.best_obj_value = neighbor_obj_value
self.best_index = i

else:

self.trials[i] += 1

def _crossover_phase(self) -> None:

"""

Crossover phase: Apply genetic crossover to selected pairs of solutions.

"""

# Create a temporary population to store new solutions

temp_population = np.copy(self.population)

temp_obj_values = np.copy(self.obj_values)

for i in range(0, self.population_size, 2):

# Select two parents using tournament selection

parent1_idx = self._tournament_selection()

parent2_idx = self._tournament_selection()

while parent2_idx == parent1_idx:

parent2_idx = self._tournament_selection()

# Apply crossover

offspring1, offspring2 = self._crossover(self.population[parent1_idx],


self.population[parent2_idx])

# Apply mutation

if random.random() < self.mutation_rate:

offspring1 = self._mutate(offspring1)

if random.random() < self.mutation_rate:


offspring2 = self._mutate(offspring2)

# Evaluate offspring

offspring1_obj_value = self._evaluate(offspring1)

offspring2_obj_value = self._evaluate(offspring2)

# Replace two worst solutions

fitness_ranks = np.argsort(self.fitness_values)

worst1_idx, worst2_idx = fitness_ranks[0], fitness_ranks[1]

# Only replace if offspring are better

if self._calculate_fitness(np.array([offspring1_obj_value]))[0] >
self.fitness_values[worst1_idx]:

temp_population[worst1_idx] = offspring1

temp_obj_values[worst1_idx] = offspring1_obj_value

if self._calculate_fitness(np.array([offspring2_obj_value]))[0] >
self.fitness_values[worst2_idx]:

temp_population[worst2_idx] = offspring2

temp_obj_values[worst2_idx] = offspring2_obj_value

# Update population and recalculate fitness

self.population = temp_population

self.obj_values = temp_obj_values

self.fitness_values = self._calculate_fitness(self.obj_values)

# Update best solution


best_idx = np.argmax(self.fitness_values)

if (self.minimize and self.obj_values[best_idx] < self.best_obj_value) or \

(not self.minimize and self.obj_values[best_idx] > self.best_obj_value):

self.best_index = best_idx

self.best_solution = np.copy(self.population[best_idx])

self.best_obj_value = self.obj_values[best_idx]

def _onlooker_bee_phase(self) -> None:

"""

Onlooker bee phase: Onlooker bees select food sources based on

their fitness and try to find better solutions in their neighborhood.

"""

# Calculate selection probabilities

selection_probs = self.fitness_values ** self.selection_pressure

selection_probs = selection_probs / np.sum(selection_probs)

# Onlooker bees

for _ in range(self.population_size):

# Select a food source

selected_idx = np.random.choice(self.population_size,
p=selection_probs)

# Generate neighbor solution

neighbor = self._create_neighbor(selected_idx)

# Apply mutation with probability

if random.random() < self.mutation_rate:


neighbor = self._mutate(neighbor)

# Evaluate neighbor

neighbor_obj_value = self._evaluate(neighbor)

neighbor_fitness =
self._calculate_fitness(np.array([neighbor_obj_value]))[0]

# Apply greedy selection

if neighbor_fitness > self.fitness_values[selected_idx]:

self.population[selected_idx] = neighbor

self.obj_values[selected_idx] = neighbor_obj_value

self.fitness_values[selected_idx] = neighbor_fitness

self.trials[selected_idx] = 0

# Update best solution if needed

if (self.minimize and neighbor_obj_value < self.best_obj_value) or \

(not self.minimize and neighbor_obj_value > self.best_obj_value):

self.best_solution = np.copy(neighbor)

self.best_obj_value = neighbor_obj_value

self.best_index = selected_idx

else:

self.trials[selected_idx] += 1

def _scout_bee_phase(self) -> None:

"""

Scout bee phase: Abandon solutions that have not been improved
for a certain number of trials and replace them with new random
solutions.

"""

for i in range(self.population_size):

if self.trials[i] > self.abandonment_limit:

# Create a new solution

if random.random() < 0.2: # 20% chance to use information from


elite solutions

# Create solution from elite templates

elite_idx = np.argsort(self.fitness_values)[-1] # Best solution

new_solution = np.copy(self.population[elite_idx])

# Add some randomness

for j in range(self.dimensions):

if random.random() < 0.5:

range_width = (self.upper_bound[j] - self.lower_bound[j]) *


0.1

perturbation = random.uniform(-range_width, range_width)

new_solution[j] += perturbation

new_solution[j] = np.clip(new_solution[j],
self.lower_bound[j], self.upper_bound[j])

else:

# Create a completely random solution

new_solution = self.lower_bound +
np.random.random(self.dimensions) * (self.upper_bound - self.lower_bound)

# Evaluate the new solution

new_obj_value = self._evaluate(new_solution)
# Update the population

self.population[i] = new_solution

self.obj_values[i] = new_obj_value

self.fitness_values[i] =
self._calculate_fitness(np.array([new_obj_value]))[0]

self.trials[i] = 0

# Update best solution if needed

if (self.minimize and new_obj_value < self.best_obj_value) or \

(not self.minimize and new_obj_value > self.best_obj_value):

self.best_solution = np.copy(new_solution)

self.best_obj_value = new_obj_value

self.best_index = i

def optimize(self, verbose: bool = True, log_interval: int = 100) -> Dict:

"""

Execute the Genetic Bee Colony optimization algorithm.

Args:

verbose: Whether to print progress information

log_interval: Interval for logging progress

Returns:

Dictionary containing optimization results

"""

start_time = time.time()
for iteration in range(self.max_iterations):

iter_start = time.time()

# Employed bee phase

self._employed_bee_phase()

# Crossover phase (genetic algorithm component)

self._crossover_phase()

# Onlooker bee phase

self._onlooker_bee_phase()

# Scout bee phase

self._scout_bee_phase()

# Record statistics

self.convergence_curve.append(self.best_obj_value)

self.best_solutions_history.append(np.copy(self.best_solution))

self.iteration_times.append(time.time() - iter_start)

# Print progress

if verbose and (iteration + 1) % log_interval == 0:

print(f"Iteration {iteration + 1}/{self.max_iterations}, Best value:


{self.best_obj_value:.6f}, "

f"Time: {self.iteration_times[-1]:.4f}s")
total_time = time.time() - start_time

if verbose:

print(f"\nOptimization completed in {total_time:.2f} seconds")

print(f"Best objective value: {self.best_obj_value}")

print(f"Best solution: {self.best_solution}")

# Prepare results

results = {

"best_solution": self.best_solution,

"best_obj_value": self.best_obj_value,

"convergence_curve": self.convergence_curve,

"best_solutions_history": self.best_solutions_history,

"total_time": total_time,

"iteration_times": self.iteration_times,

"iterations": self.max_iterations

return results

# Example usage with benchmark functions

def sphere(x):

"""Sphere function (minimum at origin)."""

return np.sum(x**2)
def rosenbrock(x):

"""Rosenbrock function (minimum at [1,1,...,1])."""

return np.sum(100.0 * (x[1:] - x[:-1]**2)**2 + (x[:-1] - 1)**2)

def rastrigin(x):

"""Rastrigin function (minimum at origin)."""

return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))

def schwefel(x):

"""Schwefel function."""

return 418.9829 * len(x) - np.sum(x * np.sin(np.sqrt(np.abs(x))))

if __name__ == "__main__":

# Set random seed for reproducibility

np.random.seed(42)

random.seed(42)

print("=== Genetic Bee Colony Optimization ===\n")

# Test with Sphere function

print("Optimizing Sphere function...")

gbc_sphere = GeneticBeeColony(

objective_function=sphere,

dimensions=30,

lower_bound=-100,

upper_bound=100,

minimize=True,
population_size=50,

max_iterations=1000,

mutation_rate=0.05,

crossover_rate=0.8,

selection_pressure=2.0,

genetic_influence=0.3,

elite_count=2,

seed=42

results_sphere = gbc_sphere.optimize(log_interval=200)

# Test with Rosenbrock function

print("\nOptimizing Rosenbrock function...")

gbc_rosenbrock = GeneticBeeColony(

objective_function=rosenbrock,

dimensions=30,

lower_bound=-30,

upper_bound=30,

minimize=True,

population_size=50,

max_iterations=1000,

mutation_rate=0.05,

crossover_rate=0.9,

selection_pressure=2.5,

genetic_influence=0.4,

elite_count=3,
seed=42

results_rosenbrock = gbc_rosenbrock.optimize(log_interval=200)

# Test with Rastrigin function

print("\nOptimizing Rastrigin function...")

gbc_rastrigin = GeneticBeeColony(

objective_function=rastrigin,

dimensions=30,

lower_bound=-5.12,

upper_bound=5.12,

minimize=True,

population_size=100,

max_iterations=1000,

mutation_rate=0.1,

crossover_rate=0.8,

selection_pressure=3.0,

genetic_influence=0.5,

elite_count=5,

seed=42

results_rastrigin = gbc_rastrigin.optimize(log_interval=200)

# Summary

print("\n=== Optimization Results Summary ===")


print(f"Sphere: {results_sphere['best_obj_value']:.6e}")

print(f"Rosenbrock: {results_rosenbrock['best_obj_value']:.6e}")

print(f"Rastrigin: {results_rastrigin['best_obj_value']:.6e}")

You might also like