feat: enhance with genetic algorithm framework, selection, crossover, mutation & convergence#3
feat: enhance with genetic algorithm framework, selection, crossover, mutation & convergence#3SuperInstance wants to merge 1 commit into
Conversation
| } | ||
|
|
||
| fn roulette_pick(population: &[Individual], total_fitness: f64) -> usize { | ||
| let mut r = pseudo_random_ga(total_fitness); |
There was a problem hiding this comment.
🔴 roulette_pick always selects the first individual due to incorrect scaling of random value
pseudo_random_ga() returns a value in [0, 1), but roulette_pick subtracts raw fitness values (which can be >> 1.0) from it. For any population where individual fitnesses are ≥ 1.0, r -= f makes r go negative on the very first iteration, so the first individual is always selected regardless of fitness distribution. The fix is to scale the random value to [0, total_fitness): let mut r = pseudo_random_ga(total_fitness) * total_fitness;.
| let mut r = pseudo_random_ga(total_fitness); | |
| let mut r = pseudo_random_ga(total_fitness) * total_fitness; |
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| let total_weight = n as f64 * (n as f64 + 1.0) / 2.0; | ||
|
|
||
| let pick = |total: f64| -> usize { | ||
| let mut r = pseudo_random_ga(total); |
There was a problem hiding this comment.
🔴 RankBasedSelection always picks the lowest-fitness individual due to same scaling bug
Same issue as in roulette_pick: pseudo_random_ga(total) returns [0, 1) but the code subtracts rank weights starting at 1.0. Since r is always < 1.0, r -= 1.0 is immediately ≤ 0 on the first iteration (rank 0, weight 1), so the individual at rank 0 (the worst fitness) is always selected. This inverts the intended selection pressure — the GA will consistently prefer worse individuals.
Fix
Change line 409 to: let mut r = pseudo_random_ga(total) * total;
| let mut r = pseudo_random_ga(total); | |
| let mut r = pseudo_random_ga(total) * total; |
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| child1.extend_from_slice(&parent2[p2..]); | ||
| child2.extend_from_slice(&parent1[p2..]); |
There was a problem hiding this comment.
🔴 crossover_two_point swaps tails from wrong parents, degenerating to single-point crossover
In two-point crossover, the tails (after p2) should come from the same parent as the head (before p1). The code gives child1 parent2[p2..] and child2 parent1[p2..], which means child1 = parent1[..p1] + parent2[p1..] — effectively single-point crossover at p1, completely ignoring p2.
Correct implementation
Lines 473-474 should be:
child1.extend_from_slice(&parent1[p2..]);
child2.extend_from_slice(&parent2[p2..]);
| child1.extend_from_slice(&parent2[p2..]); | |
| child2.extend_from_slice(&parent1[p2..]); | |
| child1.extend_from_slice(&parent1[p2..]); | |
| child2.extend_from_slice(&parent2[p2..]); |
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| let u1 = pseudo_random_ga(std::time::SystemTime::now() | ||
| .duration_since(std::time::UNIX_EPOCH) | ||
| .unwrap_or_default() | ||
| .as_nanos() as f64 + 0.123); | ||
| let u2 = pseudo_random_ga(std::time::SystemTime::now() | ||
| .duration_since(std::time::UNIX_EPOCH) | ||
| .unwrap_or_default() | ||
| .as_nanos() as f64 + 0.456); | ||
| (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos() |
There was a problem hiding this comment.
🔴 gaussian_noise can produce infinity when pseudo_random_ga returns 0.0
pseudo_random_ga() returns (h.finish() % 10000) as f64 / 10000.0, which can be exactly 0.0. The Box-Muller transform at src/lib.rs:580 computes (-2.0 * u1.ln()).sqrt() — when u1 == 0.0, u1.ln() is -inf, producing inf. This propagates through mutate_gaussian into chromosome gene values as inf or -inf, corrupting data for all subsequent fitness evaluations, crossover, and selection. The fix is to clamp u1 away from zero, e.g. let u1 = pseudo_random_ga(...).max(1e-10);.
| let u1 = pseudo_random_ga(std::time::SystemTime::now() | |
| .duration_since(std::time::UNIX_EPOCH) | |
| .unwrap_or_default() | |
| .as_nanos() as f64 + 0.123); | |
| let u2 = pseudo_random_ga(std::time::SystemTime::now() | |
| .duration_since(std::time::UNIX_EPOCH) | |
| .unwrap_or_default() | |
| .as_nanos() as f64 + 0.456); | |
| (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos() | |
| let u1 = pseudo_random_ga(std::time::SystemTime::now() | |
| .duration_since(std::time::UNIX_EPOCH) | |
| .unwrap_or_default() | |
| .as_nanos() as f64 + 0.123).max(1e-10); | |
| let u2 = pseudo_random_ga(std::time::SystemTime::now() | |
| .duration_since(std::time::UNIX_EPOCH) | |
| .unwrap_or_default() | |
| .as_nanos() as f64 + 0.456); | |
| (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos() |
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
… mutation, elitism, convergence - Genetic algorithm framework (population, individuals, chromosomes) - Fitness function abstraction (trait-based, pluggable) - Selection strategies: roulette wheel, tournament, rank-based - Crossover operators: single-point, two-point, uniform - Mutation operators: random replace, swap, scramble, invert, gaussian - Elitism support (preserve top performers each generation) - Convergence detection (fitness diversity threshold) - GA config with tunable parameters - Generation stats tracking (best/worst/avg fitness, diversity) - Custom operator support via run_with_operators - Backward-compatible Engine preserved - 42 tests passing (21 new + 21 original)
104c625 to
df3332d
Compare
|
Closing: superseded by merged work on main. The changes from this PR have been incorporated through other merged PRs. Thank you for the contribution! 🙏 |
Enhancements
Genetic Algorithm Framework
GeneticAlgorithmengine with configurable population, chromosomes, and operatorsIndividualwith chromosome and fitness trackingGAConfigfor tuning all parameters (population size, rates, elitism, convergence)GAResultwith best individual, generation count, convergence flag, and statsFitness Function Abstraction
FitnessFntrait for pluggable fitness evaluationBoxFitnessFnfor dynamic dispatchSelection Strategies
Crossover Operators
Mutation Operators
Elitism Support
Convergence Detection
Generation Statistics
Backward Compatibility
Enginewith behavior evolution fully preservedTests