From e45062507786183a2f28da18f76798f3d18e38c7 Mon Sep 17 00:00:00 2001 From: Fredrik Robertsen Date: Sat, 31 Jan 2026 17:05:35 +0100 Subject: [PATCH] major refactor --- src/main.odin | 229 +++++++++++++++++++++++++------------------------- 1 file changed, 113 insertions(+), 116 deletions(-) diff --git a/src/main.odin b/src/main.odin index dc7e8e8..b5f1d24 100644 --- a/src/main.odin +++ b/src/main.odin @@ -22,86 +22,97 @@ Item :: struct { profit, weight: int, } +Chromosome :: ^bit_array.Bit_Array +Population :: [POPULATION_SIZE]Chromosome + items: [NUMBER_OF_ITEMS]Item read_data :: proc(file: string) -> (res: [NUMBER_OF_ITEMS]Item, ok := true) { data := string(os.read_entire_file(file) or_return) - data = strings.trim_space(data) defer delete(data) - for line, idx in strings.split_lines(string(data))[1:] { + for line, idx in strings.split_lines(strings.trim_space(data))[1:] { s := strings.split(line, ",") res[idx] = {strconv.parse_int(s[1]) or_return, strconv.parse_int(s[2]) or_return} } return } -Chromosome :: bit_array.Bit_Array - -// side-effect: reads global `items` -fitness :: proc(chrom: ^Chromosome) -> int { +fitness :: proc(chrom: Chromosome) -> int { tot_profit, tot_weight := 0, 0 for idx in 0 ..< bit_array.len(chrom) { - if !bit_array.get(chrom, idx) {continue} // nb: eliminating branch may be faster + if !bit_array.get(chrom, idx) {continue} tot_profit += items[idx].profit tot_weight += items[idx].weight } - return tot_profit - 3 * math.max(tot_weight - CAPACITY, 0) + return tot_profit - 500 * max(tot_weight - CAPACITY, 0) } -Population :: [POPULATION_SIZE]Chromosome - -generate_population :: proc() -> (res: Population) { - for &chrom in res { - chrom = bit_array.create(NUMBER_OF_ITEMS)^ - for i in 0 ..< NUMBER_OF_ITEMS { - bit_array.set(&chrom, i, rand.int_max(2) == 1) - } +create_random_chromosome :: proc() -> Chromosome { + chrom := bit_array.create(NUMBER_OF_ITEMS) + for i in 0 ..< NUMBER_OF_ITEMS { + bit_array.set(chrom, i, rand.int_max(2) == 1) } - return + return chrom +} + +copy_chromosome :: proc(src: Chromosome) -> Chromosome { + dest := bit_array.create(NUMBER_OF_ITEMS) + for i in 0 ..< NUMBER_OF_ITEMS { + bit_array.set(dest, i, bit_array.get(src, i)) + } + return dest +} + +generate_population :: proc() -> Population { + pop: Population + for i in 0 ..< POPULATION_SIZE { + pop[i] = create_random_chromosome() + } + return pop } destroy_population :: proc(pop: ^Population) { - for &chrom in pop { - if chrom.free_pointer {continue} - bit_array.destroy(&chrom) + for chrom in pop { + bit_array.destroy(chrom) } } -evaluate_population :: proc(pop: ^Population) -> (res: [POPULATION_SIZE]int) { - for &chrom, i in pop { - res[i] = fitness(&chrom) +evaluate_population :: proc(pop: ^Population) -> [POPULATION_SIZE]int { + fitnesses: [POPULATION_SIZE]int + for chrom, i in pop { + fitnesses[i] = fitness(chrom) } - return + return fitnesses } tournament_selection :: proc( pop: ^Population, fitnesses: []int, - k: int = TOURNAMENT_SIZE, -) -> ^Chromosome { + k := TOURNAMENT_SIZE, +) -> Chromosome { best_idx := rand.int_max(POPULATION_SIZE) best_fitness := fitnesses[best_idx] for _ in 1 ..< k { idx := rand.int_max(POPULATION_SIZE) - if fitnesses[idx] < best_fitness { + if fitnesses[idx] > best_fitness { best_idx = idx best_fitness = fitnesses[idx] } } - return &pop[best_idx] + return pop[best_idx] } -roulette_selection :: proc(pop: ^Population, fitnesses: []int) -> ^Chromosome { +roulette_selection :: proc(pop: ^Population, fitnesses: []int) -> Chromosome { total_fitness := 0 for f in fitnesses { - total_fitness += max(f, 0) // ignore negative fitness + total_fitness += max(f, 0) } if total_fitness == 0 { - return &pop[rand.int_max(POPULATION_SIZE)] + return pop[rand.int_max(POPULATION_SIZE)] } spin := rand.float32() * f32(total_fitness) @@ -110,49 +121,49 @@ roulette_selection :: proc(pop: ^Population, fitnesses: []int) -> ^Chromosome { for fitness, idx in fitnesses { running_sum += max(fitness, 0) if f32(running_sum) >= spin { - return &pop[idx] + return pop[idx] } } - return &pop[POPULATION_SIZE - 1] + return pop[POPULATION_SIZE - 1] } -single_point_crossover :: proc(parent1, parent2: ^Chromosome) -> (child1, child2: Chromosome) { +single_point_crossover :: proc(parent1, parent2: Chromosome) -> (child1, child2: Chromosome) { point := rand.int_max(NUMBER_OF_ITEMS) - child1 = bit_array.create(NUMBER_OF_ITEMS)^ - child2 = bit_array.create(NUMBER_OF_ITEMS)^ + child1 = bit_array.create(NUMBER_OF_ITEMS) + child2 = bit_array.create(NUMBER_OF_ITEMS) for i in 0 ..< NUMBER_OF_ITEMS { if i < point { - bit_array.set(&child1, i, bit_array.get(parent1, i)) - bit_array.set(&child2, i, bit_array.get(parent2, i)) + bit_array.set(child1, i, bit_array.get(parent1, i)) + bit_array.set(child2, i, bit_array.get(parent2, i)) } else { - bit_array.set(&child1, i, bit_array.get(parent2, i)) - bit_array.set(&child2, i, bit_array.get(parent1, i)) + bit_array.set(child1, i, bit_array.get(parent2, i)) + bit_array.set(child2, i, bit_array.get(parent1, i)) } } return } -two_point_crossover :: proc(parent1, parent2: ^Chromosome) -> (child1, child2: Chromosome) { +two_point_crossover :: proc(parent1, parent2: Chromosome) -> (child1, child2: Chromosome) { point1 := rand.int_max(NUMBER_OF_ITEMS) point2 := rand.int_max(NUMBER_OF_ITEMS) - if point1 > point2 { point1, point2 = point2, point1 } - child1 = bit_array.create(NUMBER_OF_ITEMS)^ - child2 = bit_array.create(NUMBER_OF_ITEMS)^ + child1 = bit_array.create(NUMBER_OF_ITEMS) + child2 = bit_array.create(NUMBER_OF_ITEMS) for i in 0 ..< NUMBER_OF_ITEMS { - if i >= point1 && i < point2 { - bit_array.set(&child1, i, bit_array.get(parent2, i)) - bit_array.set(&child2, i, bit_array.get(parent1, i)) + in_swap := i >= point1 && i < point2 + if in_swap { + bit_array.set(child1, i, bit_array.get(parent2, i)) + bit_array.set(child2, i, bit_array.get(parent1, i)) } else { - bit_array.set(&child1, i, bit_array.get(parent1, i)) - bit_array.set(&child2, i, bit_array.get(parent2, i)) + bit_array.set(child1, i, bit_array.get(parent1, i)) + bit_array.set(child2, i, bit_array.get(parent2, i)) } } @@ -160,51 +171,47 @@ two_point_crossover :: proc(parent1, parent2: ^Chromosome) -> (child1, child2: C } uniform_crossover :: proc( - parent1, parent2: ^Chromosome, - prob: f32 = 0.5, + parent1, parent2: Chromosome, + prob := f32(0.5), ) -> ( child1, child2: Chromosome, ) { - child1 = bit_array.create(NUMBER_OF_ITEMS)^ - child2 = bit_array.create(NUMBER_OF_ITEMS)^ + child1 = bit_array.create(NUMBER_OF_ITEMS) + child2 = bit_array.create(NUMBER_OF_ITEMS) for i in 0 ..< NUMBER_OF_ITEMS { if rand.float32() < prob { - bit_array.set(&child1, i, bit_array.get(parent1, i)) - bit_array.set(&child2, i, bit_array.get(parent2, i)) + bit_array.set(child1, i, bit_array.get(parent1, i)) + bit_array.set(child2, i, bit_array.get(parent2, i)) } else { - bit_array.set(&child1, i, bit_array.get(parent2, i)) - bit_array.set(&child2, i, bit_array.get(parent1, i)) + bit_array.set(child1, i, bit_array.get(parent2, i)) + bit_array.set(child2, i, bit_array.get(parent1, i)) } } return } -bit_flip_mutation :: proc(chrom: ^Chromosome, mutation_rate: f32 = MUTATION_RATE) { +bit_flip_mutation :: proc(chrom: Chromosome, mutation_rate := MUTATION_RATE) { for i in 0 ..< NUMBER_OF_ITEMS { - if rand.float32() < mutation_rate { - current := bit_array.get(chrom, i) - bit_array.set(chrom, i, !current) + if rand.float32() < f32(mutation_rate) { + bit_array.set(chrom, i, !bit_array.get(chrom, i)) } } } -swap_mutation :: proc(chrom: ^Chromosome) { +swap_mutation :: proc(chrom: Chromosome) { idx1 := rand.int_max(NUMBER_OF_ITEMS) idx2 := rand.int_max(NUMBER_OF_ITEMS) - bit1 := bit_array.get(chrom, idx1) bit2 := bit_array.get(chrom, idx2) - bit_array.set(chrom, idx1, bit2) bit_array.set(chrom, idx2, bit1) } -inversion_mutation :: proc(chrom: ^Chromosome) { +inversion_mutation :: proc(chrom: Chromosome) { point1 := rand.int_max(NUMBER_OF_ITEMS) point2 := rand.int_max(NUMBER_OF_ITEMS) - if point1 > point2 { point1, point2 = point2, point1 } @@ -212,15 +219,42 @@ inversion_mutation :: proc(chrom: ^Chromosome) { for i in 0 ..< (point2 - point1) / 2 { idx1 := point1 + i idx2 := point2 - i - 1 - bit1 := bit_array.get(chrom, idx1) bit2 := bit_array.get(chrom, idx2) - bit_array.set(chrom, idx1, bit2) bit_array.set(chrom, idx2, bit1) } } +create_offspring :: proc(population: ^Population, fitnesses: []int) -> Population { + offspring: Population + + for i := 0; i < POPULATION_SIZE; i += 2 { + parent1 := tournament_selection(population, fitnesses) + parent2 := tournament_selection(population, fitnesses) + + child1, child2: Chromosome + if rand.float32() < CROSSOVER_RATE { + child1, child2 = single_point_crossover(parent1, parent2) + } else { + child1 = copy_chromosome(parent1) + child2 = copy_chromosome(parent2) + } + + bit_flip_mutation(child1) + bit_flip_mutation(child2) + + offspring[i] = child1 + if i + 1 < POPULATION_SIZE { + offspring[i + 1] = child2 + } else { + bit_array.destroy(child2) + } + } + + return offspring +} + elitism_survivor_selection :: proc( parents: ^Population, offspring: ^Population, @@ -233,8 +267,7 @@ elitism_survivor_selection :: proc( is_parent: bool, } - combined_size := POPULATION_SIZE * 2 - combined := make([]Index_Fitness, combined_size) + combined := make([]Index_Fitness, POPULATION_SIZE * 2) defer delete(combined) for i in 0 ..< POPULATION_SIZE { @@ -249,19 +282,13 @@ elitism_survivor_selection :: proc( survivors: Population for i in 0 ..< POPULATION_SIZE { entry := combined[i] - source := entry.is_parent ? &parents[entry.idx] : &offspring[entry.idx] - - // Create NEW bit array and copy bits - survivors[i] = bit_array.create(NUMBER_OF_ITEMS)^ - for j in 0 ..< NUMBER_OF_ITEMS { - bit_array.set(&survivors[i], j, bit_array.get(source, j)) - } + source := parents[entry.idx] if entry.is_parent else offspring[entry.idx] + survivors[i] = copy_chromosome(source) } return survivors } - run_ga :: proc() { population := generate_population() defer destroy_population(&population) @@ -272,13 +299,12 @@ run_ga :: proc() { for gen in 0 ..< GENERATIONS { fitnesses := evaluate_population(&population) - // log best fitnesses for f, i in fitnesses { if f <= best_fitness {continue} best_fitness = f best_generation = gen - chrom := &population[i] + chrom := population[i] tot_profit, tot_weight := 0, 0 for idx in 0 ..< bit_array.len(chrom) { if !bit_array.get(chrom, idx) {continue} @@ -286,7 +312,7 @@ run_ga :: proc() { tot_weight += items[idx].weight } fmt.printfln( - "gen %d: best fitness=%d, profit=%d, weight=%d (capacity=%d)", + "Gen %d: Fitness=%d, Profit=%d, Weight=%d/%d", gen, f, tot_profit, @@ -295,35 +321,9 @@ run_ga :: proc() { ) } - // create offspring - offspring: Population - for i := 0; i < POPULATION_SIZE; i += 2 { - parent1 := tournament_selection(&population, fitnesses[:]) - parent2 := tournament_selection(&population, fitnesses[:]) - - child1, child2: Chromosome - if rand.float32() < CROSSOVER_RATE { - child1, child2 = single_point_crossover(parent1, parent2) - } else { - child1 = bit_array.create(NUMBER_OF_ITEMS)^ - child2 = bit_array.create(NUMBER_OF_ITEMS)^ - for j in 0 ..< NUMBER_OF_ITEMS { - bit_array.set(&child1, j, bit_array.get(parent1, j)) - bit_array.set(&child2, j, bit_array.get(parent2, j)) - } - } - - bit_flip_mutation(&child1) - bit_flip_mutation(&child2) - - offspring[i] = child1 - if i + 1 < POPULATION_SIZE { - offspring[i + 1] = child2 - } - } - - // survivor selection + offspring := create_offspring(&population, fitnesses[:]) offspring_fitnesses := evaluate_population(&offspring) + new_population := elitism_survivor_selection( &population, &offspring, @@ -331,34 +331,31 @@ run_ga :: proc() { offspring_fitnesses[:], ) - // clean-up destroy_population(&population) destroy_population(&offspring) population = new_population } - fmt.println() - fmt.printfln("final best: fitness=%d (found at generation %d)", best_fitness, best_generation) + fmt.printfln("\nFinal Best: Fitness=%d (Generation %d)", best_fitness, best_generation) } main :: proc() { data, ok := read_data(DATA_FILE) if !ok { - fmt.eprintln("failed to read data from", DATA_FILE) + fmt.eprintln("Failed to read data from", DATA_FILE) return } items = data - fmt.println("running genetic algorithm for binary knapsack problem.") + fmt.println("Running Genetic Algorithm for Binary Knapsack Problem") fmt.printfln( - "items: %d, capacity: %d, population: %d, generations: %d", + "Items: %d, Capacity: %d, Population: %d, Generations: %d\n", NUMBER_OF_ITEMS, CAPACITY, POPULATION_SIZE, GENERATIONS, ) - fmt.println() run_ga() }