Skip to content

Commit c9c9639

Browse files
authored
Update genetic_algorithm_optimization.py
1 parent 39be73f commit c9c9639

File tree

1 file changed

+96
-5
lines changed

1 file changed

+96
-5
lines changed

genetic_algorithm/genetic_algorithm_optimization.py

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import random
22
from collections.abc import Callable, Sequence
33
from concurrent.futures import ThreadPoolExecutor
4-
54
import numpy as np
65

76
# Parameters
@@ -40,7 +39,25 @@ def __init__(
4039
self.population = self.initialize_population()
4140

4241
def initialize_population(self) -> list[np.ndarray]:
43-
"""Initialize the population with random individuals within the search space."""
42+
"""
43+
Initialize the population with random individuals within the search space.
44+
45+
Example:
46+
>>> ga = GeneticAlgorithm(
47+
... function=lambda x, y: x**2 + y**2,
48+
... bounds=[(-10, 10), (-10, 10)],
49+
... population_size=5,
50+
... generations=10,
51+
... mutation_prob=0.1,
52+
... crossover_rate=0.8,
53+
... maximize=False
54+
... )
55+
>>> len(ga.initialize_population())
56+
5 # The population size should be equal to 5.
57+
>>> all(len(ind) == 2 for ind in ga.initialize_population())
58+
# Each individual should have 2 variables
59+
True
60+
"""
4461
return [
4562
rng.uniform(
4663
low=[self.bounds[j][0] for j in range(self.dim)],
@@ -50,14 +67,58 @@ def initialize_population(self) -> list[np.ndarray]:
5067
]
5168

5269
def fitness(self, individual: np.ndarray) -> float:
53-
"""Calculate the fitness value (function value) for an individual."""
70+
"""
71+
Calculate the fitness value (function value) for an individual.
72+
73+
Example:
74+
>>> ga = GeneticAlgorithm(
75+
... function=lambda x, y: x**2 + y**2,
76+
... bounds=[(-10, 10), (-10, 10)],
77+
... population_size=10,
78+
... generations=10,
79+
... mutation_prob=0.1,
80+
... crossover_rate=0.8,
81+
... maximize=False
82+
... )
83+
>>> individual = np.array([1.0, 2.0])
84+
>>> ga.fitness(individual)
85+
5.0 # The fitness should be 1^2 + 2^2 = 5
86+
>>> ga.maximize = True
87+
>>> ga.fitness(individual)
88+
-5.0 # The fitness should be -5 when maximizing
89+
"""
5490
value = float(self.function(*individual)) # Ensure fitness is a float
5591
return value if self.maximize else -value # If minimizing, invert the fitness
5692

5793
def select_parents(
5894
self, population_score: list[tuple[np.ndarray, float]]
5995
) -> list[np.ndarray]:
60-
"""Select top N_SELECTED parents based on fitness."""
96+
"""
97+
Select top N_SELECTED parents based on fitness.
98+
99+
Example:
100+
>>> ga = GeneticAlgorithm(
101+
... function=lambda x, y: x**2 + y**2,
102+
... bounds=[(-10, 10), (-10, 10)],
103+
... population_size=10,
104+
... generations=10,
105+
... mutation_prob=0.1,
106+
... crossover_rate=0.8,
107+
... maximize=False
108+
... )
109+
>>> population_score = [
110+
... (np.array([1.0, 2.0]), 5.0),
111+
... (np.array([-1.0, -2.0]), 5.0),
112+
... (np.array([0.0, 0.0]), 0.0),
113+
... ]
114+
>>> selected_parents = ga.select_parents(population_score)
115+
>>> len(selected_parents)
116+
2 # Should select the two parents with the best fitness scores.
117+
>>> np.array_equal(selected_parents[0], np.array([1.0, 2.0])) # Parent 1 should be [1.0, 2.0]
118+
True
119+
>>> np.array_equal(selected_parents[1], np.array([-1.0, -2.0])) # Parent 2 should be [-1.0, -2.0]
120+
True
121+
"""
61122
population_score.sort(key=lambda score_tuple: score_tuple[1], reverse=True)
62123
selected_count = min(N_SELECTED, len(population_score))
63124
return [ind for ind, _ in population_score[:selected_count]]
@@ -67,11 +128,13 @@ def crossover(
67128
) -> tuple[np.ndarray, np.ndarray]:
68129
"""
69130
Perform uniform crossover between two parents to generate offspring.
131+
70132
Args:
71133
parent1 (np.ndarray): The first parent.
72134
parent2 (np.ndarray): The second parent.
73135
Returns:
74136
tuple[np.ndarray, np.ndarray]: The two offspring generated by crossover.
137+
75138
Example:
76139
>>> ga = GeneticAlgorithm(
77140
... lambda x, y: -(x**2 + y**2),
@@ -92,10 +155,13 @@ def crossover(
92155
def mutate(self, individual: np.ndarray) -> np.ndarray:
93156
"""
94157
Apply mutation to an individual.
158+
95159
Args:
96160
individual (np.ndarray): The individual to mutate.
161+
97162
Returns:
98163
np.ndarray: The mutated individual.
164+
99165
Example:
100166
>>> ga = GeneticAlgorithm(
101167
... lambda x, y: -(x**2 + y**2),
@@ -115,9 +181,11 @@ def mutate(self, individual: np.ndarray) -> np.ndarray:
115181
def evaluate_population(self) -> list[tuple[np.ndarray, float]]:
116182
"""
117183
Evaluate the fitness of the entire population in parallel.
184+
118185
Returns:
119186
list[tuple[np.ndarray, float]]:
120187
The population with their respective fitness values.
188+
121189
Example:
122190
>>> ga = GeneticAlgorithm(
123191
... lambda x, y: -(x**2 + y**2),
@@ -141,11 +209,33 @@ def evaluate_population(self) -> list[tuple[np.ndarray, float]]:
141209
)
142210
)
143211

144-
def evolve(self, verbose=True) -> np.ndarray:
212+
def evolve(self, verbose: bool = True) -> np.ndarray:
145213
"""
146214
Evolve the population over the generations to find the best solution.
215+
216+
Args:
217+
verbose (bool): If True, prints the progress of the generations.
218+
147219
Returns:
148220
np.ndarray: The best individual found during the evolution process.
221+
222+
Example:
223+
>>> ga = GeneticAlgorithm(
224+
... function=lambda x, y: x**2 + y**2,
225+
... bounds=[(-10, 10), (-10, 10)],
226+
... population_size=10,
227+
... generations=10,
228+
... mutation_prob=0.1,
229+
... crossover_rate=0.8,
230+
... maximize=False
231+
... )
232+
>>> best_solution = ga.evolve(verbose=False)
233+
>>> len(best_solution)
234+
2 # The best solution should be a 2-element array (var_x, var_y)
235+
>>> isinstance(best_solution[0], float) # First element should be a float
236+
True
237+
>>> isinstance(best_solution[1], float) # Second element should be a float
238+
True
149239
"""
150240
for generation in range(self.generations):
151241
# Evaluate population fitness (multithreaded)
@@ -186,6 +276,7 @@ def target_function(var_x: float, var_y: float) -> float:
186276
var_y (float): The y-coordinate.
187277
Returns:
188278
float: The value of the function at (var_x, var_y).
279+
189280
Example:
190281
>>> target_function(0, 0)
191282
0

0 commit comments

Comments
 (0)