Update genetic_algorithm_optimization.py

This commit is contained in:
UTSAV SINGHAL 2024-11-15 14:29:51 +05:30 committed by GitHub
parent 39be73f0b5
commit c9c9639803
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,7 +1,6 @@
import random
from collections.abc import Callable, Sequence
from concurrent.futures import ThreadPoolExecutor
import numpy as np
# Parameters
@ -40,7 +39,25 @@ class GeneticAlgorithm:
self.population = self.initialize_population()
def initialize_population(self) -> list[np.ndarray]:
"""Initialize the population with random individuals within the search space."""
"""
Initialize the population with random individuals within the search space.
Example:
>>> ga = GeneticAlgorithm(
... function=lambda x, y: x**2 + y**2,
... bounds=[(-10, 10), (-10, 10)],
... population_size=5,
... generations=10,
... mutation_prob=0.1,
... crossover_rate=0.8,
... maximize=False
... )
>>> len(ga.initialize_population())
5 # The population size should be equal to 5.
>>> all(len(ind) == 2 for ind in ga.initialize_population())
# Each individual should have 2 variables
True
"""
return [
rng.uniform(
low=[self.bounds[j][0] for j in range(self.dim)],
@ -50,14 +67,58 @@ class GeneticAlgorithm:
]
def fitness(self, individual: np.ndarray) -> float:
"""Calculate the fitness value (function value) for an individual."""
"""
Calculate the fitness value (function value) for an individual.
Example:
>>> ga = GeneticAlgorithm(
... function=lambda x, y: x**2 + y**2,
... bounds=[(-10, 10), (-10, 10)],
... population_size=10,
... generations=10,
... mutation_prob=0.1,
... crossover_rate=0.8,
... maximize=False
... )
>>> individual = np.array([1.0, 2.0])
>>> ga.fitness(individual)
5.0 # The fitness should be 1^2 + 2^2 = 5
>>> ga.maximize = True
>>> ga.fitness(individual)
-5.0 # The fitness should be -5 when maximizing
"""
value = float(self.function(*individual)) # Ensure fitness is a float
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]:
"""Select top N_SELECTED parents based on fitness."""
"""
Select top N_SELECTED parents based on fitness.
Example:
>>> ga = GeneticAlgorithm(
... function=lambda x, y: x**2 + y**2,
... bounds=[(-10, 10), (-10, 10)],
... population_size=10,
... generations=10,
... mutation_prob=0.1,
... crossover_rate=0.8,
... maximize=False
... )
>>> population_score = [
... (np.array([1.0, 2.0]), 5.0),
... (np.array([-1.0, -2.0]), 5.0),
... (np.array([0.0, 0.0]), 0.0),
... ]
>>> selected_parents = ga.select_parents(population_score)
>>> len(selected_parents)
2 # Should select the two parents with the best fitness scores.
>>> np.array_equal(selected_parents[0], np.array([1.0, 2.0])) # Parent 1 should be [1.0, 2.0]
True
>>> np.array_equal(selected_parents[1], np.array([-1.0, -2.0])) # Parent 2 should be [-1.0, -2.0]
True
"""
population_score.sort(key=lambda score_tuple: score_tuple[1], reverse=True)
selected_count = min(N_SELECTED, len(population_score))
return [ind for ind, _ in population_score[:selected_count]]
@ -67,11 +128,13 @@ class GeneticAlgorithm:
) -> 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),
@ -92,10 +155,13 @@ class GeneticAlgorithm:
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.
Example:
>>> ga = GeneticAlgorithm(
... lambda x, y: -(x**2 + y**2),
@ -115,9 +181,11 @@ class GeneticAlgorithm:
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.
Example:
>>> ga = GeneticAlgorithm(
... lambda x, y: -(x**2 + y**2),
@ -141,11 +209,33 @@ class GeneticAlgorithm:
)
)
def evolve(self, verbose=True) -> np.ndarray:
def evolve(self, verbose: bool = True) -> np.ndarray:
"""
Evolve the population over the generations to find the best solution.
Args:
verbose (bool): If True, prints the progress of the generations.
Returns:
np.ndarray: The best individual found during the evolution process.
Example:
>>> ga = GeneticAlgorithm(
... function=lambda x, y: x**2 + y**2,
... bounds=[(-10, 10), (-10, 10)],
... population_size=10,
... generations=10,
... mutation_prob=0.1,
... crossover_rate=0.8,
... maximize=False
... )
>>> best_solution = ga.evolve(verbose=False)
>>> len(best_solution)
2 # The best solution should be a 2-element array (var_x, var_y)
>>> isinstance(best_solution[0], float) # First element should be a float
True
>>> isinstance(best_solution[1], float) # Second element should be a float
True
"""
for generation in range(self.generations):
# Evaluate population fitness (multithreaded)
@ -186,6 +276,7 @@ def target_function(var_x: float, var_y: float) -> float:
var_y (float): The y-coordinate.
Returns:
float: The value of the function at (var_x, var_y).
Example:
>>> target_function(0, 0)
0