diff --git a/src/main.odin b/src/main.odin index 7a3eef5..1f00267 100644 --- a/src/main.odin +++ b/src/main.odin @@ -13,6 +13,10 @@ DATA_FILE :: "res/knapPI_12_500_1000_82.csv" NUMBER_OF_ITEMS :: 500 CAPACITY :: 280785 POPULATION_SIZE :: 100 +GENERATIONS :: 100 +TOURNAMENT_SIZE :: 3 +CROSSOVER_RATE :: 0.8 +MUTATION_RATE :: 0.01 Item :: struct { profit, weight: int, @@ -71,7 +75,18 @@ destroy_population :: proc(pop: ^Population) { } } -tournament_selection :: proc(pop: ^Population, fitnesses: []int, k: int = 3) -> ^Chromosome { +evaluate_population :: proc(pop: ^Population) -> (res: [POPULATION_SIZE]int) { + for &chrom, i in pop { + res[i] = fitness(&chrom) + } + return +} + +tournament_selection :: proc( + pop: ^Population, + fitnesses: []int, + k: int = TOURNAMENT_SIZE, +) -> ^Chromosome { best_idx := rand.int_max(POPULATION_SIZE) best_fitness := fitnesses[best_idx] @@ -173,7 +188,7 @@ uniform_crossover :: proc( return } -bit_flip_mutation :: proc(chrom: ^Chromosome, mutation_rate: f32 = 0.01) { +bit_flip_mutation :: proc(chrom: ^Chromosome, mutation_rate: f32 = MUTATION_RATE) { for i in 0 ..< NUMBER_OF_ITEMS { if rand.float32() < mutation_rate { current := bit_array.get(chrom, i) @@ -240,11 +255,102 @@ elitism_survivor_selection :: proc( return } +run_ga :: proc() { + population := generate_population() + defer destroy_population(&population) + + best_fitness := math.min(int) + best_generation := 0 + + 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] + tot_profit, tot_weight := 0, 0 + for idx in 0 ..< bit_array.len(chrom) { + if !bit_array.get(chrom, idx) {continue} + tot_profit += items[idx].profit + tot_weight += items[idx].weight + } + fmt.printfln( + "gen %d: best fitness=%d, profit=%d (capacity=%d)", + gen, + f, + tot_profit, + tot_weight, + CAPACITY, + ) + } + + // 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_fitnesses := evaluate_population(&offspring) + new_population := elitism_survivor_selection( + &population, + &offspring, + fitnesses[:], + 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) +} + main :: proc() { items, ok := read_data(DATA_FILE) if !ok { fmt.eprintln("failed to read data from", DATA_FILE) return } - fmt.println(items) + + fmt.println("running genetic algorithm for binary knapsack problem.") + fmt.printfln( + "items: %d, capacity: %d, population: %d, generations: %d", + NUMBER_OF_ITEMS, + CAPACITY, + POPULATION_SIZE, + GENERATIONS, + ) + fmt.println() + + run_ga() }