Skip to content

Commit 47a003f

Browse files
author
Eric Matte
committed
Add the Shakespeare monkey test
1 parent 34dcccc commit 47a003f

File tree

6 files changed

+108
-66
lines changed

6 files changed

+108
-66
lines changed

lib/genetic/algorithm/fitness_recorder.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@ def display_best_individual
4747
def save_best_individual_genes
4848
raise "not implemented".freeze
4949
end
50+
51+
def debug
52+
false
53+
end
5054
end

lib/genetic/algorithm/optimiser.rb renamed to lib/genetic/algorithm/optimizer.rb

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
module Genetic
22
module Algorithm
3-
class Optimiser
3+
class Optimizer
44
attr_accessor :crossover_probability # Int
55
attr_accessor :mutation_probability # Int
66
attr_accessor :population_size # Int
7-
attr_accessor :number_of_generations # Int
87
attr_accessor :genes_generator # (index: Int?) -> Array<Gene> | Gene if index.nil?
98
attr_accessor :fitness_calculator # (genes: Array<Gene>) -> Int
109

11-
def execute
12-
population = initialize_population
13-
14-
fitness_recorder = FitnessRecorder.new
15-
@number_of_generations.times.each do |generation|
16-
if generation != @number_of_generations
17-
population = do_natural_selection(population)
18-
end
19-
fitness_recorder.record_generation(generation, population)
20-
end
21-
22-
fitness_recorder.display_best_individual
10+
def execute(population = nil)
11+
population = initialize_population if population.nil?
12+
do_natural_selection(population)
2313
end
2414

2515
private
@@ -34,6 +24,7 @@ def initialize_population
3424

3525
def do_natural_selection(population)
3626
total_fitness = population.map(&:fitness).inject(:+)
27+
3728
new_population = []
3829
(@population_size / 2).times.each do
3930
parent1 = weighted_random_sampling(population, total_fitness)
@@ -49,7 +40,11 @@ def do_natural_selection(population)
4940
end
5041

5142
def weighted_random_sampling(population, total_fitness)
52-
population.max_by { |chromosome| rand**(total_fitness / chromosome.fitness) }
43+
if total_fitness.zero?
44+
population.sample
45+
else
46+
population.max_by { |chromosome| rand * (chromosome.fitness / total_fitness) }
47+
end
5348
end
5449

5550
def mate(parent1, parent2)

lib/genetic/algorithm/resolver.rb

Lines changed: 0 additions & 15 deletions
This file was deleted.

test/genetic/algorithm/optimiser_test.rb renamed to test/genetic/algorithm/optimizer_test.rb

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,84 @@
11
require "test_helper"
2-
require "genetic/algorithm/optimiser"
2+
require "genetic/algorithm/optimizer"
33

4-
class Genetic::Algorithm::OptimiserTest < Minitest::Test
4+
class Genetic::Algorithm::OptimizerTest < Minitest::Test
55
def test_it_initialize_population
6-
ga_optimiser = Genetic::Algorithm::Optimiser.new
7-
ga_optimiser.population_size = population_size
8-
ga_optimiser.genes_generator = method(:genes_generator)
9-
ga_optimiser.fitness_calculator = method(:fitness_calculator)
6+
ga_optimizer = Genetic::Algorithm::Optimizer.new
7+
ga_optimizer.population_size = population_size
8+
ga_optimizer.genes_generator = method(:genes_generator)
9+
ga_optimizer.fitness_calculator = method(:fitness_calculator)
1010

11-
test_population = ga_optimiser.send(:initialize_population)
11+
test_population = ga_optimizer.send(:initialize_population)
1212

1313
assert(test_population.length == population_size)
1414
assert(test_population.map(&:fitness).inject(:+) >= population_size)
1515
end
1616

1717
def test_it_do_natural_selection
18-
ga_optimiser = Genetic::Algorithm::Optimiser.new
19-
ga_optimiser.crossover_probability = 0.7
20-
ga_optimiser.mutation_probability = 0.1
21-
ga_optimiser.population_size = population_size
22-
ga_optimiser.genes_generator = method(:genes_generator)
23-
ga_optimiser.fitness_calculator = method(:fitness_calculator)
24-
first_population = ga_optimiser.send(:initialize_population)
18+
ga_optimizer = Genetic::Algorithm::Optimizer.new
19+
ga_optimizer.crossover_probability = 0.7
20+
ga_optimizer.mutation_probability = 0.5
21+
ga_optimizer.population_size = population_size
22+
ga_optimizer.genes_generator = method(:genes_generator)
23+
ga_optimizer.fitness_calculator = method(:fitness_calculator)
24+
first_population = ga_optimizer.send(:initialize_population)
2525
last_population = first_population
2626

27-
20.times do
28-
last_population = ga_optimiser.send(:do_natural_selection, last_population)
27+
100.times do
28+
last_population = ga_optimizer.send(:do_natural_selection, last_population)
2929
end
3030

3131
assert(first_population.length == population_size)
3232
assert(last_population.length == population_size)
3333

34+
assert(first_population != last_population)
3435
first_gen_fitness = first_population.map(&:fitness).inject(:+)
3536
last_gen_fitness = last_population.map(&:fitness).inject(:+)
36-
assert(first_gen_fitness < last_gen_fitness)
37+
assert(first_gen_fitness != last_gen_fitness)
3738
end
3839

3940
def test_mate_with_0_proability_returns_same_chromosomes
40-
ga_optimiser = Genetic::Algorithm::Optimiser.new
41-
ga_optimiser.crossover_probability = 0.0
42-
ga_optimiser.mutation_probability = 0.0
43-
ga_optimiser.genes_generator = method(:genes_generator)
44-
ga_optimiser.fitness_calculator = method(:fitness_calculator)
41+
ga_optimizer = Genetic::Algorithm::Optimizer.new
42+
ga_optimizer.crossover_probability = 0.0
43+
ga_optimizer.mutation_probability = 0.0
44+
ga_optimizer.genes_generator = method(:genes_generator)
45+
ga_optimizer.fitness_calculator = method(:fitness_calculator)
4546

4647
parent1 = Genetic::Algorithm::Chromosome.new(genes_generator, method(:fitness_calculator))
4748
parent2 = Genetic::Algorithm::Chromosome.new(genes_generator, method(:fitness_calculator))
48-
child1, child2 = ga_optimiser.send(:mate, parent1, parent2)
49+
child1, child2 = ga_optimizer.send(:mate, parent1, parent2)
4950

5051
assert(parent1.genes != parent2.genes)
5152
assert(child1.genes == parent1.genes)
5253
assert(child2.genes == parent2.genes)
5354
end
5455

5556
def test_mate_crossover
56-
ga_optimiser = Genetic::Algorithm::Optimiser.new
57-
ga_optimiser.crossover_probability = 1.0
58-
ga_optimiser.mutation_probability = 0.0
59-
ga_optimiser.genes_generator = method(:genes_generator)
60-
ga_optimiser.fitness_calculator = method(:fitness_calculator)
57+
ga_optimizer = Genetic::Algorithm::Optimizer.new
58+
ga_optimizer.crossover_probability = 1.0
59+
ga_optimizer.mutation_probability = 0.0
60+
ga_optimizer.genes_generator = method(:genes_generator)
61+
ga_optimizer.fitness_calculator = method(:fitness_calculator)
6162

6263
parent1 = Genetic::Algorithm::Chromosome.new(genes_generator, method(:fitness_calculator))
6364
parent2 = Genetic::Algorithm::Chromosome.new(genes_generator, method(:fitness_calculator))
64-
child1, child2 = ga_optimiser.send(:mate, parent1, parent2)
65+
child1, child2 = ga_optimizer.send(:mate, parent1, parent2)
6566

6667
assert(parent1.genes != parent2.genes)
6768
assert(child1.genes != parent1.genes)
6869
assert(child2.genes != parent2.genes)
6970
end
7071

7172
def test_mate_make_one_mutation_per_child
72-
ga_optimiser = Genetic::Algorithm::Optimiser.new
73-
ga_optimiser.crossover_probability = 0.0
74-
ga_optimiser.mutation_probability = 1.0
75-
ga_optimiser.genes_generator = method(:genes_generator)
76-
ga_optimiser.fitness_calculator = method(:fitness_calculator)
73+
ga_optimizer = Genetic::Algorithm::Optimizer.new
74+
ga_optimizer.crossover_probability = 0.0
75+
ga_optimizer.mutation_probability = 1.0
76+
ga_optimizer.genes_generator = method(:genes_generator)
77+
ga_optimizer.fitness_calculator = method(:fitness_calculator)
7778

7879
parent1 = Genetic::Algorithm::Chromosome.new(genes_generator, method(:fitness_calculator))
7980
parent2 = Genetic::Algorithm::Chromosome.new(genes_generator, method(:fitness_calculator))
80-
child1, child2 = ga_optimiser.send(:mate, parent1, parent2)
81+
child1, child2 = ga_optimizer.send(:mate, parent1, parent2)
8182

8283
assert(parent1.genes != parent2.genes)
8384
assert(child1.genes != parent1.genes)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
require "test_helper"
2+
3+
class Genetic::Algorithm::ShakespeareMonkeyTest < Minitest::Test
4+
def test_that_it_resolves_the_monkey_problem
5+
ga_optimizer = Genetic::Algorithm::Optimizer.new
6+
ga_optimizer.population_size = 200
7+
ga_optimizer.crossover_probability = 0.7
8+
ga_optimizer.mutation_probability = 0.5
9+
ga_optimizer.genes_generator = method(:genes_generator)
10+
ga_optimizer.fitness_calculator = method(:fitness_calculator)
11+
12+
goal_phrase = goal.join
13+
14+
generation = 0
15+
last_population = nil
16+
while generation < 500
17+
new_population = ga_optimizer.execute(last_population)
18+
19+
best_one = new_population.max_by(&:fitness)
20+
best_phrase = best_one.genes.join
21+
22+
if best_phrase == goal_phrase
23+
puts "Goal found! \"#{goal_phrase}\" in #{generation} generations."
24+
break
25+
else
26+
puts "#{best_phrase} - #{best_one.fitness * 100}% (G-#{generation})"
27+
end
28+
29+
generation += 1
30+
last_population = new_population
31+
end
32+
33+
assert(true, generation < 500)
34+
end
35+
36+
private
37+
38+
def goal
39+
"Make Time Count.".split("")
40+
end
41+
42+
def letters
43+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.!? ".split("")
44+
end
45+
46+
def genes_generator(index = nil)
47+
index.nil? ? Array.new(goal.length) { letters.sample } : letters.sample
48+
end
49+
50+
def fitness_calculator(genes)
51+
fitness = 0.0
52+
goal.each_with_index do |letter, i|
53+
fitness += 1 if letter == genes[i]
54+
end
55+
fitness / goal.length
56+
end
57+
end

test/genetic/algorithm/resolver_test.rb renamed to test/genetic/algorithm/version_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require "test_helper"
22

3-
class Genetic::Algorithm::ResolverTest < Minitest::Test
3+
class Genetic::Algorithm::VersionTest < Minitest::Test
44
def test_that_it_has_a_version_number
55
refute_nil Genetic::Algorithm::VERSION
66
end

0 commit comments

Comments
 (0)