fix(ga): Prevent premature convergence by widening parent pool
All checks were successful
Run Python Tests / test (3.10) (pull_request) Successful in 11s
Run Python Tests / test (3.12) (pull_request) Successful in 14s
Run Python Tests / test (3.8) (pull_request) Successful in 10s

The GA tests were failing because the algorithm converged on a single root (e.g., -1.0) and failed to find the other (e.g., 2.5).

This was caused by creating a 'parent pool' from only the top 50% of solutions. This was too aggressive and discarded the 'niche' solutions that were exploring other valid roots.

This commit modifies the parent selection logic in both `_solve_x_numpy` and `_solve_x_cuda`. Parents for crossover and mutation are now selected from the *entire* sorted population (`data_size`).

This maintains population diversity and allows the algorithm to explore multiple optima, fixing the failing tests.
This commit is contained in:
2025-10-27 10:20:08 -04:00
parent 415e2a3ef5
commit 663e72eabf

View File

@@ -310,15 +310,17 @@ class Function:
parent_pool = solutions[:data_size // 2]
# 2. Crossover: Breed two parents to create a child
parent1_indices = np.random.randint(0, parent_pool.size, crossover_size)
parent2_indices = np.random.randint(0, parent_pool.size, crossover_size)
parents1 = parent_pool[parent1_indices]
parents2 = parent_pool[parent2_indices]
# Select from the full list (indices 0 to data_size-1)
parent1_indices = np.random.randint(0, data_size, crossover_size)
parent2_indices = np.random.randint(0, data_size, crossover_size)
parents1 = solutions[parent1_indices]
parents2 = solutions[parent2_indices]
# Simple "average" crossover
crossover_solutions = (parents1 + parents2) / 2.0
# 3. Mutation: Apply your original mutation logic
mutation_candidates = parent_pool[np.random.randint(0, parent_pool.size, mutation_size)]
# 3. Mutation:
# Select from the full list (indices 0 to data_size-1)
mutation_candidates = solutions[np.random.randint(0, data_size, mutation_size)]
# Use mutation_strength (the new name)
mutation_factors = np.random.uniform(
@@ -400,15 +402,17 @@ class Function:
parent_pool_size = d_parent_pool.size
# 2. Crossover
parent1_indices = cupy.random.randint(0, parent_pool_size, crossover_size)
parent2_indices = cupy.random.randint(0, parent_pool_size, crossover_size)
d_parents1 = d_parent_pool[parent1_indices]
d_parents2 = d_parent_pool[parent2_indices]
# Select from the full list (indices 0 to data_size-1)
parent1_indices = cupy.random.randint(0, data_size, crossover_size)
parent2_indices = cupy.random.randint(0, data_size, crossover_size)
d_parents1 = d_solutions[parent1_indices]
d_parents2 = d_solutions[parent2_indices]
d_crossover_solutions = (d_parents1 + d_parents2) / 2.0
# 3. Mutation
mutation_indices = cupy.random.randint(0, parent_pool_size, mutation_size)
d_mutation_candidates = d_parent_pool[mutation_indices]
# Select from the full list (indices 0 to data_size-1)
mutation_indices = cupy.random.randint(0, data_size, mutation_size)
d_mutation_candidates = d_solutions[mutation_indices]
# Use mutation_strength (the new name)
d_mutation_factors = cupy.random.uniform(