From 7e97562fa85360a3c45b05665924189f8ec5ed34 Mon Sep 17 00:00:00 2001 From: UTSAV SINGHAL Date: Wed, 2 Oct 2024 13:41:36 +0530 Subject: [PATCH 01/11] Implement genetic algorithm for optimizing continuous functions - Added a flexible genetic algorithm that allows users to define their own target functions for optimization. - Included features for population initialization, fitness evaluation, selection, crossover, and mutation. - Example function provided for minimizing f(x, y) = x^2 + y^2. - Configurable parameters for population size, mutation probability, and generations. --- .../genetic_algorithm_optimization.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 genetic_algorithm/genetic_algorithm_optimization.py diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py new file mode 100644 index 000000000000..90585f14efd7 --- /dev/null +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -0,0 +1,109 @@ +import random +import numpy as np + +# Parameters +N_POPULATION = 100 # Population size +N_GENERATIONS = 500 # Maximum number of generations +N_SELECTED = 50 # Number of parents selected for the next generation +MUTATION_PROBABILITY = 0.1 # Mutation probability +CROSSOVER_RATE = 0.8 # Probability of crossover +SEARCH_SPACE = (-10, 10) # Search space for the variables + +# Genetic Algorithm for Function Optimization +class GeneticAlgorithm: + def __init__(self, function, bounds, population_size, generations, mutation_prob, crossover_rate, maximize=True): + self.function = function # Target function to optimize + self.bounds = bounds # Search space bounds (for each variable) + self.population_size = population_size + self.generations = generations + self.mutation_prob = mutation_prob + self.crossover_rate = crossover_rate + self.maximize = maximize + self.dim = len(bounds) # Dimensionality of the function (number of variables) + + # Initialize population + self.population = self.initialize_population() + + def initialize_population(self): + # Generate random initial population within the search space + return [np.random.uniform(low=self.bounds[i][0], high=self.bounds[i][1], size=self.dim) + for i in range(self.population_size)] + + def fitness(self, individual): + # Calculate the fitness value (function value) + value = self.function(*individual) + return value if self.maximize else -value # If minimizing, invert the fitness + + def select_parents(self): + # Rank individuals based on fitness and select top individuals for mating + scores = [(individual, self.fitness(individual)) for individual in self.population] + scores.sort(key=lambda x: x[1], reverse=True) + selected = [ind for ind, _ in scores[:N_SELECTED]] + return selected + + def crossover(self, parent1, parent2): + # Perform uniform crossover + if random.random() < self.crossover_rate: + cross_point = random.randint(1, self.dim - 1) + child1 = np.concatenate((parent1[:cross_point], parent2[cross_point:])) + child2 = np.concatenate((parent2[:cross_point], parent1[cross_point:])) + return child1, child2 + return parent1, parent2 + + def mutate(self, individual): + # Apply mutation to an individual with some probability + for i in range(self.dim): + if random.random() < self.mutation_prob: + individual[i] = np.random.uniform(self.bounds[i][0], self.bounds[i][1]) + return individual + + def evolve(self): + for generation in range(self.generations): + # Select parents based on fitness + parents = self.select_parents() + next_generation = [] + + # Generate offspring using crossover and mutation + for i in range(0, len(parents), 2): + parent1, parent2 = parents[i], parents[(i + 1) % len(parents)] + child1, child2 = self.crossover(parent1, parent2) + next_generation.append(self.mutate(child1)) + next_generation.append(self.mutate(child2)) + + # Ensure population size remains the same + self.population = next_generation[:self.population_size] + + # Track the best solution so far + best_individual = max(self.population, key=self.fitness) + best_fitness = self.fitness(best_individual) + + if generation % 10 == 0: + print(f"Generation {generation}: Best Fitness = {best_fitness}, Best Individual = {best_individual}") + + # Return the best individual found + return max(self.population, key=self.fitness) + + +# Define a sample function to optimize (e.g., minimize the sum of squares) +def target_function(x, y): + return x**2 + y**2 # Example: simple parabolic surface (minimization) + +# Set bounds for the variables (x, y) +bounds = [(-10, 10), (-10, 10)] # Both x and y range from -10 to 10 + +# Instantiate the genetic algorithm +ga = GeneticAlgorithm( + function=target_function, + bounds=bounds, + population_size=N_POPULATION, + generations=N_GENERATIONS, + mutation_prob=MUTATION_PROBABILITY, + crossover_rate=CROSSOVER_RATE, + maximize=False # Set to False for minimization +) + +# Run the genetic algorithm and find the optimal solution +best_solution = ga.evolve() + +print(f"Best solution found: {best_solution}") +print(f"Best fitness (minimum value of function): {target_function(*best_solution)}") From 19e0461a99d4f359ca7eaafbe8cb3b551b15e15c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:16:33 +0000 Subject: [PATCH 02/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../genetic_algorithm_optimization.py | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index 90585f14efd7..cb915e5da447 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -2,16 +2,26 @@ import numpy as np # Parameters -N_POPULATION = 100 # Population size +N_POPULATION = 100 # Population size N_GENERATIONS = 500 # Maximum number of generations -N_SELECTED = 50 # Number of parents selected for the next generation +N_SELECTED = 50 # Number of parents selected for the next generation MUTATION_PROBABILITY = 0.1 # Mutation probability CROSSOVER_RATE = 0.8 # Probability of crossover SEARCH_SPACE = (-10, 10) # Search space for the variables + # Genetic Algorithm for Function Optimization class GeneticAlgorithm: - def __init__(self, function, bounds, population_size, generations, mutation_prob, crossover_rate, maximize=True): + def __init__( + self, + function, + bounds, + population_size, + generations, + mutation_prob, + crossover_rate, + maximize=True, + ): self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) self.population_size = population_size @@ -20,27 +30,33 @@ def __init__(self, function, bounds, population_size, generations, mutation_prob self.crossover_rate = crossover_rate self.maximize = maximize self.dim = len(bounds) # Dimensionality of the function (number of variables) - + # Initialize population self.population = self.initialize_population() - + def initialize_population(self): # Generate random initial population within the search space - return [np.random.uniform(low=self.bounds[i][0], high=self.bounds[i][1], size=self.dim) - for i in range(self.population_size)] - + return [ + np.random.uniform( + low=self.bounds[i][0], high=self.bounds[i][1], size=self.dim + ) + for i in range(self.population_size) + ] + def fitness(self, individual): # Calculate the fitness value (function value) value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - + def select_parents(self): # Rank individuals based on fitness and select top individuals for mating - scores = [(individual, self.fitness(individual)) for individual in self.population] + scores = [ + (individual, self.fitness(individual)) for individual in self.population + ] scores.sort(key=lambda x: x[1], reverse=True) selected = [ind for ind, _ in scores[:N_SELECTED]] return selected - + def crossover(self, parent1, parent2): # Perform uniform crossover if random.random() < self.crossover_rate: @@ -49,14 +65,14 @@ def crossover(self, parent1, parent2): child2 = np.concatenate((parent2[:cross_point], parent1[cross_point:])) return child1, child2 return parent1, parent2 - + def mutate(self, individual): # Apply mutation to an individual with some probability for i in range(self.dim): if random.random() < self.mutation_prob: individual[i] = np.random.uniform(self.bounds[i][0], self.bounds[i][1]) return individual - + def evolve(self): for generation in range(self.generations): # Select parents based on fitness @@ -71,15 +87,17 @@ def evolve(self): next_generation.append(self.mutate(child2)) # Ensure population size remains the same - self.population = next_generation[:self.population_size] + self.population = next_generation[: self.population_size] # Track the best solution so far best_individual = max(self.population, key=self.fitness) best_fitness = self.fitness(best_individual) - + if generation % 10 == 0: - print(f"Generation {generation}: Best Fitness = {best_fitness}, Best Individual = {best_individual}") - + print( + f"Generation {generation}: Best Fitness = {best_fitness}, Best Individual = {best_individual}" + ) + # Return the best individual found return max(self.population, key=self.fitness) @@ -88,6 +106,7 @@ def evolve(self): def target_function(x, y): return x**2 + y**2 # Example: simple parabolic surface (minimization) + # Set bounds for the variables (x, y) bounds = [(-10, 10), (-10, 10)] # Both x and y range from -10 to 10 @@ -99,7 +118,7 @@ def target_function(x, y): generations=N_GENERATIONS, mutation_prob=MUTATION_PROBABILITY, crossover_rate=CROSSOVER_RATE, - maximize=False # Set to False for minimization + maximize=False, # Set to False for minimization ) # Run the genetic algorithm and find the optimal solution From 7b85f11cd25b5eb68b1746d769cee65243b25761 Mon Sep 17 00:00:00 2001 From: UTSAV SINGHAL Date: Wed, 2 Oct 2024 14:33:49 +0530 Subject: [PATCH 03/11] Update genetic_algorithm_optimization.py --- .../genetic_algorithm_optimization.py | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index cb915e5da447..1787bca4cfa3 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -1,5 +1,6 @@ -import random import numpy as np +import random +from concurrent.futures import ThreadPoolExecutor # Parameters N_POPULATION = 100 # Population size @@ -9,8 +10,9 @@ CROSSOVER_RATE = 0.8 # Probability of crossover SEARCH_SPACE = (-10, 10) # Search space for the variables +# Random number generator +rng = np.random.default_rng() -# Genetic Algorithm for Function Optimization class GeneticAlgorithm: def __init__( self, @@ -35,11 +37,9 @@ def __init__( self.population = self.initialize_population() def initialize_population(self): - # Generate random initial population within the search space + # Generate random initial population within the search space using the generator return [ - np.random.uniform( - low=self.bounds[i][0], high=self.bounds[i][1], size=self.dim - ) + rng.uniform(low=self.bounds[i][0], high=self.bounds[i][1], size=self.dim) for i in range(self.population_size) ] @@ -48,14 +48,10 @@ def fitness(self, individual): value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - def select_parents(self): - # Rank individuals based on fitness and select top individuals for mating - scores = [ - (individual, self.fitness(individual)) for individual in self.population - ] - scores.sort(key=lambda x: x[1], reverse=True) - selected = [ind for ind, _ in scores[:N_SELECTED]] - return selected + def select_parents(self, population_score): + # Select top N_SELECTED parents based on fitness + population_score.sort(key=lambda x: x[1], reverse=True) + return [ind for ind, _ in population_score[:N_SELECTED]] def crossover(self, parent1, parent2): # Perform uniform crossover @@ -67,16 +63,28 @@ def crossover(self, parent1, parent2): return parent1, parent2 def mutate(self, individual): - # Apply mutation to an individual with some probability + # Apply mutation to an individual using the new random generator for i in range(self.dim): if random.random() < self.mutation_prob: - individual[i] = np.random.uniform(self.bounds[i][0], self.bounds[i][1]) + individual[i] = rng.uniform(self.bounds[i][0], self.bounds[i][1]) return individual + def evaluate_population(self): + # Multithreaded evaluation of population fitness + with ThreadPoolExecutor() as executor: + return list(executor.map(lambda ind: (ind, self.fitness(ind)), self.population)) + def evolve(self): for generation in range(self.generations): - # Select parents based on fitness - parents = self.select_parents() + # Evaluate population fitness (multithreaded) + population_score = self.evaluate_population() + + # Check the best individual + best_individual = max(population_score, key=lambda x: x[1])[0] + best_fitness = self.fitness(best_individual) + + # Select parents for next generation + parents = self.select_parents(population_score) next_generation = [] # Generate offspring using crossover and mutation @@ -87,30 +95,23 @@ def evolve(self): next_generation.append(self.mutate(child2)) # Ensure population size remains the same - self.population = next_generation[: self.population_size] - - # Track the best solution so far - best_individual = max(self.population, key=self.fitness) - best_fitness = self.fitness(best_individual) + self.population = next_generation[:self.population_size] if generation % 10 == 0: - print( - f"Generation {generation}: Best Fitness = {best_fitness}, Best Individual = {best_individual}" - ) + print(f"Generation {generation}: Best Fitness = {best_fitness}") - # Return the best individual found - return max(self.population, key=self.fitness) + return best_individual -# Define a sample function to optimize (e.g., minimize the sum of squares) +# Example target function for optimization def target_function(x, y): - return x**2 + y**2 # Example: simple parabolic surface (minimization) + return x**2 + y**2 # Simple parabolic surface (minimization) # Set bounds for the variables (x, y) bounds = [(-10, 10), (-10, 10)] # Both x and y range from -10 to 10 -# Instantiate the genetic algorithm +# Instantiate and run the genetic algorithm ga = GeneticAlgorithm( function=target_function, bounds=bounds, @@ -118,11 +119,9 @@ def target_function(x, y): generations=N_GENERATIONS, mutation_prob=MUTATION_PROBABILITY, crossover_rate=CROSSOVER_RATE, - maximize=False, # Set to False for minimization + maximize=False # Minimize the function ) -# Run the genetic algorithm and find the optimal solution best_solution = ga.evolve() - print(f"Best solution found: {best_solution}") print(f"Best fitness (minimum value of function): {target_function(*best_solution)}") From 21bcad9b0c32aa6a26eff6eab5f065ee7cb07242 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:05:31 +0000 Subject: [PATCH 04/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- genetic_algorithm/genetic_algorithm_optimization.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index 1787bca4cfa3..8708b5387510 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -13,6 +13,7 @@ # Random number generator rng = np.random.default_rng() + class GeneticAlgorithm: def __init__( self, @@ -72,7 +73,9 @@ def mutate(self, individual): def evaluate_population(self): # Multithreaded evaluation of population fitness with ThreadPoolExecutor() as executor: - return list(executor.map(lambda ind: (ind, self.fitness(ind)), self.population)) + return list( + executor.map(lambda ind: (ind, self.fitness(ind)), self.population) + ) def evolve(self): for generation in range(self.generations): @@ -95,7 +98,7 @@ def evolve(self): next_generation.append(self.mutate(child2)) # Ensure population size remains the same - self.population = next_generation[:self.population_size] + self.population = next_generation[: self.population_size] if generation % 10 == 0: print(f"Generation {generation}: Best Fitness = {best_fitness}") @@ -119,7 +122,7 @@ def target_function(x, y): generations=N_GENERATIONS, mutation_prob=MUTATION_PROBABILITY, crossover_rate=CROSSOVER_RATE, - maximize=False # Minimize the function + maximize=False, # Minimize the function ) best_solution = ga.evolve() From 24df5d17b7caff77362697460dc539868bde47b0 Mon Sep 17 00:00:00 2001 From: UTSAV SINGHAL Date: Fri, 4 Oct 2024 12:09:09 +0530 Subject: [PATCH 05/11] Update genetic_algorithm_optimization.py --- genetic_algorithm/genetic_algorithm_optimization.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index 8708b5387510..9ea7a757a125 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -2,6 +2,7 @@ import random from concurrent.futures import ThreadPoolExecutor + # Parameters N_POPULATION = 100 # Population size N_GENERATIONS = 500 # Maximum number of generations @@ -10,6 +11,7 @@ CROSSOVER_RATE = 0.8 # Probability of crossover SEARCH_SPACE = (-10, 10) # Search space for the variables + # Random number generator rng = np.random.default_rng() @@ -25,6 +27,7 @@ def __init__( crossover_rate, maximize=True, ): + self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) self.population_size = population_size @@ -44,16 +47,19 @@ def initialize_population(self): for i in range(self.population_size) ] + def fitness(self, individual): # Calculate the fitness value (function value) value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness + def select_parents(self, population_score): # Select top N_SELECTED parents based on fitness population_score.sort(key=lambda x: x[1], reverse=True) return [ind for ind, _ in population_score[:N_SELECTED]] + def crossover(self, parent1, parent2): # Perform uniform crossover if random.random() < self.crossover_rate: @@ -63,6 +69,7 @@ def crossover(self, parent1, parent2): return child1, child2 return parent1, parent2 + def mutate(self, individual): # Apply mutation to an individual using the new random generator for i in range(self.dim): @@ -70,6 +77,7 @@ def mutate(self, individual): individual[i] = rng.uniform(self.bounds[i][0], self.bounds[i][1]) return individual + def evaluate_population(self): # Multithreaded evaluation of population fitness with ThreadPoolExecutor() as executor: @@ -77,6 +85,7 @@ def evaluate_population(self): executor.map(lambda ind: (ind, self.fitness(ind)), self.population) ) + def evolve(self): for generation in range(self.generations): # Evaluate population fitness (multithreaded) @@ -114,6 +123,7 @@ def target_function(x, y): # Set bounds for the variables (x, y) bounds = [(-10, 10), (-10, 10)] # Both x and y range from -10 to 10 + # Instantiate and run the genetic algorithm ga = GeneticAlgorithm( function=target_function, @@ -125,6 +135,7 @@ def target_function(x, y): maximize=False, # Minimize the function ) + best_solution = ga.evolve() print(f"Best solution found: {best_solution}") print(f"Best fitness (minimum value of function): {target_function(*best_solution)}") From 2a29980364c27137f47b579270b5e62956364f8c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 06:39:36 +0000 Subject: [PATCH 06/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- genetic_algorithm/genetic_algorithm_optimization.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index 9ea7a757a125..6e86828d0c59 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -27,7 +27,6 @@ def __init__( crossover_rate, maximize=True, ): - self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) self.population_size = population_size @@ -47,19 +46,16 @@ def initialize_population(self): for i in range(self.population_size) ] - def fitness(self, individual): # Calculate the fitness value (function value) value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - def select_parents(self, population_score): # Select top N_SELECTED parents based on fitness population_score.sort(key=lambda x: x[1], reverse=True) return [ind for ind, _ in population_score[:N_SELECTED]] - def crossover(self, parent1, parent2): # Perform uniform crossover if random.random() < self.crossover_rate: @@ -69,7 +65,6 @@ def crossover(self, parent1, parent2): return child1, child2 return parent1, parent2 - def mutate(self, individual): # Apply mutation to an individual using the new random generator for i in range(self.dim): @@ -77,7 +72,6 @@ def mutate(self, individual): individual[i] = rng.uniform(self.bounds[i][0], self.bounds[i][1]) return individual - def evaluate_population(self): # Multithreaded evaluation of population fitness with ThreadPoolExecutor() as executor: @@ -85,7 +79,6 @@ def evaluate_population(self): executor.map(lambda ind: (ind, self.fitness(ind)), self.population) ) - def evolve(self): for generation in range(self.generations): # Evaluate population fitness (multithreaded) From dff47dfae0f272b4aa75896edd986cfb4cd76cf0 Mon Sep 17 00:00:00 2001 From: UTSAV SINGHAL Date: Fri, 4 Oct 2024 14:47:38 +0530 Subject: [PATCH 07/11] Update genetic_algorithm_optimization.py --- genetic_algorithm/genetic_algorithm_optimization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index 6e86828d0c59..d9d8e80fb4d0 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -1,5 +1,5 @@ -import numpy as np import random +import numpy as np from concurrent.futures import ThreadPoolExecutor From 04605a0f96eef3c78c34b96a4a0a1777127d9b66 Mon Sep 17 00:00:00 2001 From: UTSAV SINGHAL <119779889+UTSAVS26@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:07:38 +0530 Subject: [PATCH 08/11] Update genetic_algorithm_optimization.py --- .../genetic_algorithm_optimization.py | 138 ++++++++++++++---- 1 file changed, 113 insertions(+), 25 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index d9d8e80fb4d0..b5347bee8f44 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -19,14 +19,14 @@ class GeneticAlgorithm: def __init__( self, - function, - bounds, - population_size, - generations, - mutation_prob, - crossover_rate, - maximize=True, - ): + function: callable, + bounds: list[tuple[float, float]], + population_size: int, + generations: int, + mutation_prob: float, + crossover_rate: float, + maximize: bool = True + ) -> None: self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) self.population_size = population_size @@ -39,25 +39,78 @@ def __init__( # Initialize population self.population = self.initialize_population() - def initialize_population(self): - # Generate random initial population within the search space using the generator + def initialize_population(self) -> list[np.ndarray]: + """ + Initialize the population with random individuals within the search space. + + Returns: + list[np.ndarray]: A list of individuals represented as numpy arrays. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: x**2 + y**2, [(-10, 10), (-10, 10)], 10, 100, 0.1, 0.8, False) + >>> len(ga.initialize_population()) == ga.population_size + True + """ return [ rng.uniform(low=self.bounds[i][0], high=self.bounds[i][1], size=self.dim) for i in range(self.population_size) ] - def fitness(self, individual): - # Calculate the fitness value (function value) + def fitness(self, individual: np.ndarray) -> float: + """ + Calculate the fitness value (function value) for an individual. + + Args: + individual (np.ndarray): The individual to evaluate. + + Returns: + float: The fitness value of the individual. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: -(x**2 + y**2), [(-10, 10), (-10, 10)], 10, 100, 0.1, 0.8, True) + >>> ind = np.array([1, 2]) + >>> ga.fitness(ind) + -5.0 + """ value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - def select_parents(self, population_score): - # Select top N_SELECTED parents based on fitness + def select_parents(self, population_score: list[tuple[np.ndarray, float]]) -> list[np.ndarray]: + """ + Select top N_SELECTED parents based on fitness. + + Args: + population_score (list[tuple[np.ndarray, float]]): The population with their respective fitness scores. + + Returns: + list[np.ndarray]: The selected parents for the next generation. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: -(x**2 + y**2), [(-10, 10), (-10, 10)], 10, 100, 0.1, 0.8, True) + >>> pop_score = [(np.array([1, 2]), -5), (np.array([3, 4]), -25)] + >>> len(ga.select_parents(pop_score)) == N_SELECTED + True + """ population_score.sort(key=lambda x: x[1], reverse=True) return [ind for ind, _ in population_score[:N_SELECTED]] - def crossover(self, parent1, parent2): - # Perform uniform crossover + def crossover(self, parent1: np.ndarray, parent2: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """ + Perform uniform crossover between two parents to generate offspring. + + Args: + parent1 (np.ndarray): The first parent. + parent2 (np.ndarray): The second parent. + + Returns: + tuple[np.ndarray, np.ndarray]: The two offspring generated by crossover. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: -(x**2 + y**2), [(-10, 10), (-10, 10)], 10, 100, 0.1, 0.8, True) + >>> parent1, parent2 = np.array([1, 2]), np.array([3, 4]) + >>> len(ga.crossover(parent1, parent2)) == 2 + True + """ if random.random() < self.crossover_rate: cross_point = random.randint(1, self.dim - 1) child1 = np.concatenate((parent1[:cross_point], parent2[cross_point:])) @@ -65,21 +118,40 @@ def crossover(self, parent1, parent2): return child1, child2 return parent1, parent2 - def mutate(self, individual): - # Apply mutation to an individual using the new random generator + def mutate(self, individual: np.ndarray) -> np.ndarray: + """ + Apply mutation to an individual. + + Args: + individual (np.ndarray): The individual to mutate. + + Returns: + np.ndarray: The mutated individual. + """ for i in range(self.dim): if random.random() < self.mutation_prob: individual[i] = rng.uniform(self.bounds[i][0], self.bounds[i][1]) return individual - def evaluate_population(self): - # Multithreaded evaluation of population fitness + def evaluate_population(self) -> list[tuple[np.ndarray, float]]: + """ + Evaluate the fitness of the entire population in parallel. + + Returns: + list[tuple[np.ndarray, float]]: The population with their respective fitness values. + """ with ThreadPoolExecutor() as executor: return list( executor.map(lambda ind: (ind, self.fitness(ind)), self.population) ) - def evolve(self): + def evolve(self) -> np.ndarray: + """ + Evolve the population over the generations to find the best solution. + + Returns: + np.ndarray: The best individual found during the evolution process. + """ for generation in range(self.generations): # Evaluate population fitness (multithreaded) population_score = self.evaluate_population() @@ -109,12 +181,28 @@ def evolve(self): # Example target function for optimization -def target_function(x, y): - return x**2 + y**2 # Simple parabolic surface (minimization) +def target_function(var_x: float, var_y: float) -> float: + """ + Example target function (parabola) for optimization. + + Args: + var_x (float): The x-coordinate. + var_y (float): The y-coordinate. + + Returns: + float: The value of the function at (var_x, var_y). + + Example: + >>> target_function(0, 0) + 0 + >>> target_function(1, 1) + 2 + """ + return var_x**2 + var_y**2 # Simple parabolic surface (minimization) -# Set bounds for the variables (x, y) -bounds = [(-10, 10), (-10, 10)] # Both x and y range from -10 to 10 +# Set bounds for the variables (var_x, var_y) +bounds = [(-10, 10), (-10, 10)] # Both var_x and var_y range from -10 to 10 # Instantiate and run the genetic algorithm From 4cd9c61b857f3cce38c811273b3e23a8aa407d21 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 05:38:00 +0000 Subject: [PATCH 09/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- genetic_algorithm/genetic_algorithm_optimization.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index b5347bee8f44..3b91e7ba66a3 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -25,7 +25,7 @@ def __init__( generations: int, mutation_prob: float, crossover_rate: float, - maximize: bool = True + maximize: bool = True, ) -> None: self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) @@ -75,7 +75,9 @@ def fitness(self, individual: np.ndarray) -> float: value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - def select_parents(self, population_score: list[tuple[np.ndarray, float]]) -> list[np.ndarray]: + def select_parents( + self, population_score: list[tuple[np.ndarray, float]] + ) -> list[np.ndarray]: """ Select top N_SELECTED parents based on fitness. @@ -94,7 +96,9 @@ def select_parents(self, population_score: list[tuple[np.ndarray, float]]) -> li population_score.sort(key=lambda x: x[1], reverse=True) return [ind for ind, _ in population_score[:N_SELECTED]] - def crossover(self, parent1: np.ndarray, parent2: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + def crossover( + self, parent1: np.ndarray, parent2: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: """ Perform uniform crossover between two parents to generate offspring. From bddbe5216c8b525c09152bb7b2388847ff9dffb8 Mon Sep 17 00:00:00 2001 From: UTSAV SINGHAL <119779889+UTSAVS26@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:22:10 +0530 Subject: [PATCH 10/11] Update genetic_algorithm_optimization.py --- .../genetic_algorithm_optimization.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index 3b91e7ba66a3..f947675b0137 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -25,7 +25,7 @@ def __init__( generations: int, mutation_prob: float, crossover_rate: float, - maximize: bool = True, + maximize: bool = True ) -> None: self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) @@ -75,9 +75,7 @@ def fitness(self, individual: np.ndarray) -> float: value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - def select_parents( - self, population_score: list[tuple[np.ndarray, float]] - ) -> list[np.ndarray]: + def select_parents(self, population_score: list[tuple[np.ndarray, float]]) -> list[np.ndarray]: """ Select top N_SELECTED parents based on fitness. @@ -93,12 +91,10 @@ def select_parents( >>> len(ga.select_parents(pop_score)) == N_SELECTED True """ - population_score.sort(key=lambda x: x[1], reverse=True) + population_score.sort(key=lambda score_tuple: score_tuple[1], reverse=True) return [ind for ind, _ in population_score[:N_SELECTED]] - def crossover( - self, parent1: np.ndarray, parent2: np.ndarray - ) -> tuple[np.ndarray, np.ndarray]: + def crossover(self, parent1: np.ndarray, parent2: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ Perform uniform crossover between two parents to generate offspring. @@ -131,6 +127,13 @@ def mutate(self, individual: np.ndarray) -> np.ndarray: Returns: np.ndarray: The mutated individual. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: -(x**2 + y**2), [(-10, 10), (-10, 10)], 10, 100, 0.1, 0.8, True) + >>> ind = np.array([1.0, 2.0]) + >>> mutated = ga.mutate(ind) + >>> len(mutated) == 2 # Ensure it still has the correct number of dimensions + True """ for i in range(self.dim): if random.random() < self.mutation_prob: @@ -143,10 +146,18 @@ def evaluate_population(self) -> list[tuple[np.ndarray, float]]: Returns: list[tuple[np.ndarray, float]]: The population with their respective fitness values. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: -(x**2 + y**2), [(-10, 10), (-10, 10)], 10, 100, 0.1, 0.8, True) + >>> eval_population = ga.evaluate_population() + >>> len(eval_population) == ga.population_size # Ensure the population size is correct + True + >>> all(isinstance(ind, tuple) and isinstance(ind[1], float) for ind in eval_population) + True """ with ThreadPoolExecutor() as executor: return list( - executor.map(lambda ind: (ind, self.fitness(ind)), self.population) + executor.map(lambda individual: (individual, self.fitness(individual)), self.population) ) def evolve(self) -> np.ndarray: @@ -155,13 +166,19 @@ def evolve(self) -> np.ndarray: Returns: np.ndarray: The best individual found during the evolution process. + + Example: + >>> ga = GeneticAlgorithm(lambda x, y: -(x**2 + y**2), [(-10, 10), (-10, 10)], 10, 10, 0.1, 0.8, True) + >>> best_solution = ga.evolve() + >>> len(best_solution) == 2 # Ensure the best solution is a valid individual with correct dimensions + True """ for generation in range(self.generations): # Evaluate population fitness (multithreaded) population_score = self.evaluate_population() # Check the best individual - best_individual = max(population_score, key=lambda x: x[1])[0] + best_individual = max(population_score, key=lambda score_tuple: score_tuple[1])[0] best_fitness = self.fitness(best_individual) # Select parents for next generation From 4d0492e8741fb0018c771261bd3f83156506c9fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 05:52:32 +0000 Subject: [PATCH 11/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../genetic_algorithm_optimization.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/genetic_algorithm/genetic_algorithm_optimization.py b/genetic_algorithm/genetic_algorithm_optimization.py index f947675b0137..6df52698f621 100644 --- a/genetic_algorithm/genetic_algorithm_optimization.py +++ b/genetic_algorithm/genetic_algorithm_optimization.py @@ -25,7 +25,7 @@ def __init__( generations: int, mutation_prob: float, crossover_rate: float, - maximize: bool = True + maximize: bool = True, ) -> None: self.function = function # Target function to optimize self.bounds = bounds # Search space bounds (for each variable) @@ -75,7 +75,9 @@ def fitness(self, individual: np.ndarray) -> float: value = self.function(*individual) return value if self.maximize else -value # If minimizing, invert the fitness - def select_parents(self, population_score: list[tuple[np.ndarray, float]]) -> list[np.ndarray]: + def select_parents( + self, population_score: list[tuple[np.ndarray, float]] + ) -> list[np.ndarray]: """ Select top N_SELECTED parents based on fitness. @@ -94,7 +96,9 @@ def select_parents(self, population_score: list[tuple[np.ndarray, float]]) -> li population_score.sort(key=lambda score_tuple: score_tuple[1], reverse=True) return [ind for ind, _ in population_score[:N_SELECTED]] - def crossover(self, parent1: np.ndarray, parent2: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + def crossover( + self, parent1: np.ndarray, parent2: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: """ Perform uniform crossover between two parents to generate offspring. @@ -157,7 +161,10 @@ def evaluate_population(self) -> list[tuple[np.ndarray, float]]: """ with ThreadPoolExecutor() as executor: return list( - executor.map(lambda individual: (individual, self.fitness(individual)), self.population) + executor.map( + lambda individual: (individual, self.fitness(individual)), + self.population, + ) ) def evolve(self) -> np.ndarray: @@ -178,7 +185,9 @@ def evolve(self) -> np.ndarray: population_score = self.evaluate_population() # Check the best individual - best_individual = max(population_score, key=lambda score_tuple: score_tuple[1])[0] + best_individual = max( + population_score, key=lambda score_tuple: score_tuple[1] + )[0] best_fitness = self.fitness(best_individual) # Select parents for next generation