major refactor
This commit is contained in:
229
src/main.odin
229
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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user