├── basic_chromosome.rb ├── chromosome.rb ├── ga.rb ├── knapsack_chromosome.rb └── spec ├── chromosome_spec.rb └── ga_spec.rb /basic_chromosome.rb: -------------------------------------------------------------------------------- 1 | require_relative 'ga' 2 | require_relative 'chromosome' 3 | 4 | class BinaryChromosome < Chromosome 5 | def fitness 6 | c1 = value.count("0") * 0 7 | c2 = value.count("1") * 10 8 | 9 | c1 + c2 10 | end 11 | end 12 | 13 | ga = GeneticAlgorithm.new 14 | puts ga.run(BinaryChromosome, 0.2, 0.01, 100) 15 | -------------------------------------------------------------------------------- /chromosome.rb: -------------------------------------------------------------------------------- 1 | 2 | class Chromosome 3 | SIZE = 10 4 | 5 | attr_reader :value 6 | 7 | def initialize(value) 8 | @value = value 9 | end 10 | 11 | def fitness 12 | # Implement in subclass 13 | 1 14 | end 15 | 16 | def [](index) 17 | @value[index] 18 | end 19 | 20 | def mutate(probability_of_mutation) 21 | @value = value.map { |ch| rand < probability_of_mutation ? invert(ch) : ch } 22 | end 23 | 24 | def invert(binary) 25 | binary == "0" ? "1" : "0" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /ga.rb: -------------------------------------------------------------------------------- 1 | 2 | class GeneticAlgorithm 3 | def generate(chromosome) 4 | value = Array.new(chromosome::SIZE) { ["0", "1"].sample } 5 | 6 | chromosome.new(value) 7 | end 8 | 9 | def select(population) 10 | population.sample(2) 11 | end 12 | 13 | def crossover(selection, index, chromosome) 14 | cr1 = selection[0][0...index] + selection[1][index..-1] 15 | cr2 = selection[1][0...index] + selection[0][index..-1] 16 | 17 | [chromosome.new(cr1), chromosome.new(cr2)] 18 | end 19 | 20 | def run(chromosome, p_cross, p_mutation, iterations = 100) 21 | # initial population 22 | population = 100.times.map { generate(chromosome) } 23 | 24 | current_generation = population 25 | next_generation = [] 26 | 27 | iterations.times { 28 | # save best fit 29 | best_fit = current_generation.max_by { |ch| ch.fitness }.dup 30 | 31 | (population.size / 2).times { 32 | 33 | selection = select(current_generation) 34 | 35 | # crossover 36 | if rand < p_cross 37 | selection = crossover(selection, rand(0..chromosome::SIZE), chromosome) 38 | end 39 | 40 | # mutation 41 | selection[0].mutate(p_mutation) 42 | selection[1].mutate(p_mutation) 43 | 44 | next_generation << selection[0] << selection[1] 45 | } 46 | 47 | current_generation = next_generation 48 | next_generation = [] 49 | 50 | # Make sure best fit chromosome carries over 51 | current_generation << best_fit 52 | } 53 | 54 | # return best solution 55 | best_fit = current_generation.max_by { |ch| ch.fitness } 56 | 57 | "#{best_fit.value} => #{best_fit.fitness}" 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /knapsack_chromosome.rb: -------------------------------------------------------------------------------- 1 | require_relative 'ga' 2 | require_relative 'chromosome' 3 | 4 | class KnapsackChromosome < Chromosome 5 | CAPACITY = 20 # Max weight 6 | SIZE = 7 # Slots 7 | 8 | def fitness 9 | weights = [2, 3, 6, 7, 5, 9, 4] 10 | values = [6, 5, 8, 9, 6, 7, 3] 11 | 12 | w = weights 13 | .map 14 | .with_index { |w, idx| value[idx].to_i * w } 15 | .inject(:+) 16 | 17 | v = values 18 | .map 19 | .with_index { |v, idx| value[idx].to_i * v } 20 | .inject(:+) 21 | 22 | w > CAPACITY ? 0 : v 23 | end 24 | end 25 | 26 | ga = GeneticAlgorithm.new 27 | puts ga.run(KnapsackChromosome, 0.2, 0.01, 100) 28 | 29 | # knapsack 1/0 -> Only one copy of each item, 30 | # how many can you fit? 31 | -------------------------------------------------------------------------------- /spec/chromosome_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../chromosome' 2 | 3 | describe Chromosome do 4 | it 'has a fitness method' do 5 | chromo = Chromosome.new("001001") 6 | 7 | expect(chromo.fitness).to eq 1 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/ga_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../ga' 2 | require_relative '../chromosome' 3 | 4 | describe GeneticAlgorithm do 5 | let(:ga) { GeneticAlgorithm.new } 6 | 7 | # chromosome_class, p_cross, p_mutation, iterations 8 | it 'has a run method' do 9 | ga.run(Chromosome, 1, 1) 10 | end 11 | 12 | it 'produces a chromosome' do 13 | random_chromo = ga.generate(Chromosome) 14 | 15 | expect(random_chromo).to be_a Chromosome 16 | expect(random_chromo.value.size).to be Chromosome::SIZE 17 | end 18 | 19 | it 'selects two random chromosomes using wheel selection' do 20 | population = [Chromosome.new("01"), Chromosome.new("10")] 21 | selection = ga.select(population) 22 | 23 | expect(selection).to be_a Array 24 | expect(selection).to have_attributes(size: 2) 25 | end 26 | 27 | it 'calculates the total population fitness' do 28 | population = [Chromosome.new("01"), Chromosome.new("10")] 29 | 30 | expect(ga.total_fitness(population)).to eq 2 31 | end 32 | 33 | it 'calculates the percentage of the wheel for every chromosome' do 34 | population = [Chromosome.new("01"), Chromosome.new("10")] 35 | 36 | expect(ga.calculate_percentages(population, 2)).to eq [500, 500] 37 | end 38 | 39 | it 'generates the wheel array' do 40 | population = [Chromosome.new("01"), Chromosome.new("10")] 41 | percentages = [500, 500] 42 | 43 | expect(ga.generate_wheel(population, percentages).size).to eq 1_000 44 | expect(ga.generate_wheel(population, percentages).first).to eq population.first 45 | expect(ga.generate_wheel(population, percentages).last).to eq population.last 46 | end 47 | 48 | it 'crossovers two chromosomes' do 49 | selection = [Chromosome.new("01011"), Chromosome.new("10101")] 50 | expected = [Chromosome.new("01001"), Chromosome.new("10111")] 51 | 52 | crossover = ga.crossover(selection, 3, Chromosome).map(&:value) 53 | 54 | expect(crossover).to eq expected.map(&:value) 55 | end 56 | end 57 | --------------------------------------------------------------------------------