Skip to content

Commit b093145

Browse files
committed
Update genetic_algorithm_optimization.py
1 parent 162792e commit b093145

File tree

1 file changed

+106
-141
lines changed

1 file changed

+106
-141
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,113 @@
11
import numpy as np
2-
import sympy as sp
32

3+
class GeneticAlgorithmOptimizer:
4+
def __init__(self, func, bounds, population_size=100, generations=500, crossover_prob=0.9, mutation_prob=0.01):
5+
"""
6+
Initialize the Genetic Algorithm optimizer.
7+
8+
:param func: The function to optimize.
9+
:param bounds: List of tuples defining the lower and upper bounds for each variable.
10+
:param population_size: Number of individuals in the population.
11+
:param generations: Number of generations to evolve.
12+
:param crossover_prob: Probability of crossover.
13+
:param mutation_prob: Probability of mutation.
14+
"""
15+
self.func = func
16+
self.bounds = np.array(bounds)
17+
self.population_size = population_size
18+
self.generations = generations
19+
self.crossover_prob = crossover_prob
20+
self.mutation_prob = mutation_prob
21+
self.num_variables = len(bounds)
22+
23+
def initialize_population(self):
24+
"""
25+
Initialize a population of random solutions within the bounds.
26+
"""
27+
return np.random.uniform(low=self.bounds[:, 0], high=self.bounds[:, 1], size=(self.population_size, self.num_variables))
28+
29+
def fitness(self, individual):
30+
"""
31+
Evaluate the fitness of an individual.
32+
In minimization problems, we aim to minimize the function value.
33+
"""
34+
return self.func(*individual)
35+
36+
def select_parents(self, population, fitness_scores):
37+
"""
38+
Select parents using tournament selection.
39+
"""
40+
selected_indices = np.random.choice(range(self.population_size), size=2, replace=False)
41+
return population[selected_indices[np.argmin(fitness_scores[selected_indices])]]
42+
43+
def crossover(self, parent1, parent2):
44+
"""
45+
Perform one-point crossover to create offspring.
46+
Skip crossover for single-variable functions.
47+
"""
48+
if self.num_variables == 1:
49+
return parent1, parent2 # No crossover needed for single-variable functions
50+
51+
if np.random.rand() < self.crossover_prob:
52+
point = np.random.randint(1, self.num_variables) # Updated to handle the edge case
53+
child1 = np.concatenate((parent1[:point], parent2[point:]))
54+
child2 = np.concatenate((parent2[:point], parent1[point:]))
55+
return child1, child2
56+
return parent1, parent2
57+
58+
def mutate(self, individual):
59+
"""
60+
Apply mutation to an individual with a given mutation probability.
61+
"""
62+
if np.random.rand() < self.mutation_prob:
63+
index = np.random.randint(0, self.num_variables)
64+
individual[index] = np.random.uniform(self.bounds[index, 0], self.bounds[index, 1])
65+
return individual
66+
67+
def evolve(self):
68+
"""
69+
Run the genetic algorithm for a number of generations.
70+
"""
71+
population = self.initialize_population()
72+
best_solution = None
73+
best_fitness = float('inf')
74+
75+
for gen in range(self.generations):
76+
fitness_scores = np.array([self.fitness(individual) for individual in population])
77+
78+
new_population = []
79+
for _ in range(self.population_size // 2):
80+
parent1 = self.select_parents(population, fitness_scores)
81+
parent2 = self.select_parents(population, fitness_scores)
82+
child1, child2 = self.crossover(parent1, parent2)
83+
child1 = self.mutate(child1)
84+
child2 = self.mutate(child2)
85+
new_population.extend([child1, child2])
86+
87+
population = np.array(new_population)
88+
89+
# Track the best solution
90+
min_fitness_index = np.argmin(fitness_scores)
91+
if fitness_scores[min_fitness_index] < best_fitness:
92+
best_fitness = fitness_scores[min_fitness_index]
93+
best_solution = population[min_fitness_index]
94+
95+
print(f"Generation {gen + 1}, Best Fitness: {best_fitness}")
96+
97+
return best_solution, best_fitness
498

5-
def parse_function(user_input: str) -> callable:
6-
"""
7-
Convert user input from f(x, y) = x^2 + y^2 to a valid Python function.
899

9-
Parameters:
10-
user_input (str): The user-defined fitness function in string format.
11-
12-
Returns:
13-
callable: A callable fitness function.
14-
15-
Examples:
16-
>>> parse_function("f(x, y) = x^2 + y^2")
17-
<function fitness at 0x...>
18-
"""
19-
user_input = user_input.strip()
20-
21-
if "=" in user_input:
22-
_, expression = user_input.split("=", 1)
23-
expression = expression.strip()
24-
else:
25-
raise ValueError("Invalid function format. Please use 'f(x, y) = ...'.")
26-
27-
# Create sympy symbols for x and y
28-
x, y = sp.symbols("x y")
29-
30-
# Replace power operator and parse the expression safely
31-
expression = expression.replace("^", "**")
32-
33-
# Use sympy to parse the expression
34-
func_expr = sp.sympify(expression)
35-
36-
# Create the fitness function using sympy
37-
fitness = sp.lambdify((x, y), func_expr)
38-
39-
return fitness
40-
41-
42-
def genetic_algorithm(user_fitness_function: callable) -> None:
43-
"""
44-
Execute the genetic algorithm to optimize the user-defined fitness function.
45-
46-
Parameters:
47-
user_fitness_function (callable): The fitness function to be optimized.
48-
49-
Returns:
50-
None
51-
52-
Example:
53-
>>> def user_fitness_function(x, y):
54-
... return x**2 + y**2
55-
>>> genetic_algorithm(user_fitness_function) # This will print outputs
56-
"""
57-
rng = np.random.default_rng() # New random number generator
58-
population_size = 100
59-
num_generations = 500
60-
mutation_rate = 0.01
61-
chromosome_length = 2
62-
best_fitness = np.inf
63-
best_solution = None
64-
65-
# Initialize the population
66-
population = rng.random((population_size, chromosome_length))
67-
68-
for generation in range(num_generations):
69-
fitness_values = []
70-
71-
for individual in population:
72-
# Call the fitness function with individual x and y
73-
fitness_value = user_fitness_function(individual[0], individual[1])
74-
75-
if fitness_value is None or not isinstance(fitness_value, (int, float)):
76-
print(
77-
f"Warning: Fitness function returned an invalid value "
78-
f"for individual {individual}."
79-
)
80-
fitness_value = np.inf
81-
else:
82-
print(
83-
f"Evaluating individual {individual}, Fitness: {fitness_value:.6f}"
84-
)
85-
86-
fitness_values.append(fitness_value)
87-
88-
fitness_values = np.array(fitness_values)
89-
90-
# Update the best solution
91-
best_idx = np.argmin(fitness_values)
92-
if fitness_values[best_idx] < best_fitness:
93-
best_fitness = fitness_values[best_idx]
94-
best_solution = population[best_idx]
95-
96-
print(f"Generation {generation + 1}, Best Fitness: {best_fitness:.6f}")
97-
98-
# Selection
99-
selected_parents = population[rng.choice(population_size, population_size)]
100-
101-
# Crossover
102-
offspring = []
103-
for i in range(0, population_size - 1, 2): # Ensure even number of parents
104-
parent1, parent2 = selected_parents[i], selected_parents[i + 1]
105-
cross_point = rng.integers(1, chromosome_length)
106-
child1 = np.concatenate((parent1[:cross_point], parent2[cross_point:]))
107-
child2 = np.concatenate((parent2[:cross_point], parent1[cross_point:]))
108-
offspring.append(child1)
109-
offspring.append(child2)
110-
111-
# Handle odd population size if necessary
112-
if population_size % 2 == 1:
113-
offspring.append(selected_parents[-1]) # Include last parent if odd
114-
115-
offspring = np.array(offspring)
116-
117-
# Mutation
118-
mutation_mask = rng.random(offspring.shape) < mutation_rate
119-
offspring[mutation_mask] = rng.random(np.sum(mutation_mask))
120-
121-
population = offspring
122-
123-
print("\n--- Optimization Results ---")
124-
print(f"Best Fitness Value (Minimum): {best_fitness:.6f}")
125-
print(
126-
f"Optimal Solution Found: x = {best_solution[0]:.6f}, "
127-
f"y = {best_solution[1]:.6f}"
128-
)
100+
if __name__ == "__main__":
101+
# Define the function to optimize
102+
def func(x, y):
103+
return x**2 + y**2 # Example: Minimizing x^2 + y^2
129104

130-
function_value = best_fitness
131-
print(
132-
f"Function Value at Optimal Solution: f({best_solution[0]:.6f}, "
133-
f"{best_solution[1]:.6f}) = {function_value:.6f}"
134-
)
105+
# Define the bounds for each variable
106+
bounds = [(-10, 10), (-10, 10)]
135107

108+
# Initialize and run the optimizer
109+
optimizer = GeneticAlgorithmOptimizer(func=func, bounds=bounds)
110+
best_solution, best_fitness = optimizer.evolve()
136111

137-
if __name__ == "__main__":
138-
user_input = input(
139-
"Please enter your fitness function in the format 'f(x, y) = ...':\n"
140-
)
141-
142-
try:
143-
fitness_function = parse_function(user_input)
144-
genetic_algorithm(fitness_function)
145-
except (SyntaxError, ValueError) as e:
146-
print(f"Error: {e}")
147-
except NameError as e:
148-
print(f"Error: {e}")
112+
print("Best Solution:", best_solution)
113+
print("Best Fitness:", best_fitness)

0 commit comments

Comments
 (0)