diff --git a/src/common.odin b/src/common.odin index 40326d0..bf1450b 100644 --- a/src/common.odin +++ b/src/common.odin @@ -23,5 +23,5 @@ Problem :: struct { } Stats :: struct { - best, mean, worst: f64, + best, mean, worst, entropy: f64, } diff --git a/src/ga.odin b/src/ga.odin index be363ef..dd3078c 100644 --- a/src/ga.odin +++ b/src/ga.odin @@ -26,22 +26,21 @@ run_ga :: proc(problem: Problem, survivor_selection: Survivor_Selection) { for gen in 0 ..< GENERATIONS { pop_fitnesses := evaluate_population(&population, problem.fitness_proc) - stats := compute_stats(pop_fitnesses[:], problem.maximize) + stats := compute_stats(&population, pop_fitnesses[:], problem.chromosome_size, problem.maximize) append(&generation_stats, stats) fmt.printfln( - "Gen %d: Best=%.4f Mean=%.4f Worst=%.4f", + "Gen %d: Best=%.4f Mean=%.4f Worst=%.4f Entropy=%.4f", gen, stats.best, stats.mean, stats.worst, + stats.entropy, ) - // Create offspring (no selection decisions here) offspring := create_offspring_simple(&population) offspring_fitnesses := evaluate_population(&offspring, problem.fitness_proc) - // Survivor selection decides who lives next_gen := survivor_selection( &population, &offspring, @@ -86,7 +85,35 @@ evaluate_population :: proc( return fitnesses } -compute_stats :: proc(fitnesses: []f64, maximize: bool) -> Stats { +compute_population_entropy :: proc(pop: ^Population, chromosome_size: int) -> f64 { + bit_counts := make([]int, chromosome_size, context.temp_allocator) + defer delete(bit_counts, context.temp_allocator) + + // Count 1s at each bit position + for i in 0 ..< POPULATION_SIZE { + for j in 0 ..< chromosome_size { + if bit_array.get(pop[i], j) { + bit_counts[j] += 1 + } + } + } + + // Calculate entropy: H = -Σ p_i * log2(p_i) + entropy: f64 = 0.0 + for count in bit_counts { + if count == 0 || count == POPULATION_SIZE { + continue // Skip if all 0s or all 1s (log(0) undefined) + } + + p := f64(count) / f64(POPULATION_SIZE) + entropy -= p * math.log2(p) + } + + return entropy +} + +// Modified compute_stats to include entropy +compute_stats :: proc(pop: ^Population, fitnesses: []f64, chromosome_size: int, maximize: bool) -> Stats { best := maximize ? -math.F64_MAX : math.F64_MAX worst := maximize ? math.F64_MAX : -math.F64_MAX sum := 0.0 @@ -102,7 +129,9 @@ compute_stats :: proc(fitnesses: []f64, maximize: bool) -> Stats { sum += f } - return {best, sum / f64(len(fitnesses)), worst} + entropy := compute_population_entropy(pop, chromosome_size) + + return {best, sum / f64(len(fitnesses)), worst, entropy} } tournament_selection :: proc(pop: ^Population, fitnesses: []f64, maximize: bool) -> Chromosome { @@ -360,7 +389,7 @@ write_results :: proc(filename: string, stats: []Stats) -> bool { w: csv.Writer csv.writer_init(&w, os.stream_from_handle(handle)) - csv.write(&w, []string{"Generation", "Best", "Mean", "Worst"}) + csv.write(&w, []string{"Generation", "Best", "Mean", "Worst", "Entropy"}) for stat, gen in stats { csv.write( @@ -370,6 +399,7 @@ write_results :: proc(filename: string, stats: []Stats) -> bool { fmt.tprintf("%.6f", stat.best), fmt.tprintf("%.6f", stat.mean), fmt.tprintf("%.6f", stat.worst), + fmt.tprintf("%.6f", stat.entropy), }, ) } diff --git a/src/plot.ua b/src/plot.ua index 480229f..013edbd 100644 --- a/src/plot.ua +++ b/src/plot.ua @@ -3,9 +3,13 @@ ↥0⋕↘1°csv ≡°⊂ °⍉≡⊟ -⍜°⊟₃∩₃Line +⍜°⊟₃∩₃Line °⊂⌟ °⊸≡°◇°Data~Label {"best" "mean" "worst"} Plot!( °⊸XLabel "generations" ) &fwa"output/plot.png"img"png" + +°⊸Data~Label "entropy" Line +Plot!(°⊸XLabel "generations") +&fwa"output/entropy.png"img"png"