Skip to content

Commit

Permalink
Chromosome tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Matte committed Nov 17, 2019
1 parent 512d521 commit 5a6271f
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Style/StringLiterals:
Metrics/LineLength:
Max: 160

Metrics/MethodLength:
Max: 35

Gemspec/OrderedDependencies:
Enabled: false

Expand Down
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"ruby.lintDebounceTime": 500,
"ruby.useBundler": true,
"ruby.useLanguageServer": true,
"ruby.lint": {
"rubocop": true
"rubocop": {
"useBundler": true
}
},
"ruby.format": "rubocop"
}
2 changes: 1 addition & 1 deletion genetic-algorithm-resolver.gemspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "genetic/algorithm/version"
require "genetic/algorithm"

Gem::Specification.new do |spec|
spec.name = "genetic-algorithm-resolver"
Expand Down
1 change: 1 addition & 0 deletions lib/genetic/algorithm.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Genetic
module Algorithm
VERSION = "0.1.0".freeze
end
end
21 changes: 21 additions & 0 deletions lib/genetic/algorithm/chromosome.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# A member of a given population
class Genetic::Algorithm::Chromosome
attr_accessor :genes
attr_accessor :fitness

def initialize(genes:, fitness:)
@genes = genes
@fitness = fitness
end

# Randomly change one of its gene.
# Help maintain diversity within the population and prevent premature convergence.
# @param [(Object | Float)[][]] ranges - The available ranges to mutate from for all genes. ranges.lenght must equals genes.length
# @param [Boolean] sampleFromRanges
# If true: take one of the available values for a given range
# if false: take a random floating value between ranges[i][0] and ranges[i][1]
def mutate(ranges, sample_from_ranges = false)
i = rand(0..(genes.length - 1))
@genes[i] = sample_from_ranges ? ranges[i].sample : rand(ranges[i][0]..ranges[i][1])
end
end
49 changes: 49 additions & 0 deletions lib/genetic/algorithm/fitness_recorder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Record and display best individuals accross generations
class Genetic::Algorithm::FitnessRecorder
attr_accessor :best_individual

def initialize
@best_individual = nil
@max_fitness = []
@max_overall_finess = []
@average_fitness = []
end

def record_generation(generation, population)
best_generation_individual = population.max_by(&:fitness)

# Save best individual across all generations
if best_generation_individual.fitness > (@best_individual&.fitness || -1e10)
@best_individual = best_generation_individual
end

# Record progress information
@max_fitness << best_generation_individual.fitness
@max_overall_finess << @best_individual.fitness
average = population.map(&:fitness).inject(:+) / population.length
@average_fitness << average

puts "Generation #{generation} completed."
puts " Best overall fitness: #{@best_individual.fitness}"
puts " Best population fitness: #{best_generation_individual.fitness}"
puts " Average population fitness: #{average}"
end

def display_best_individual(shifts)
puts "\n"
puts "Result: Best overall individual fitness: #{@best_individual.fitness}"
puts " Shift\t->\tSelected employee:"
print_shift = ->(shift) { "[#{shift.start_at.localtime} - #{shift.end_at.localtime}]: #{shift.position.name}" }
print_employee = ->(employee) { "#{employee.profile.full_name} (#{employee.email})" }
puts shifts.each_with_index.map { |s, i| "#{print_shift[s[:shift]]}\t->\t#{print_employee[@best_individual.genes[i].user]}" }

# puts "#{@max_fitness.each_with_index.map { |_, i| i }},"
# puts "#{@max_fitness},"
# puts "#{@max_overall_finess},"
# puts "#{@average_fitness},"
end

def save_best_individual_genes
raise "not implemented".freeze
end
end
5 changes: 0 additions & 5 deletions lib/genetic/algorithm/version.rb

This file was deleted.

49 changes: 49 additions & 0 deletions test/genetic/algorithm/chromosome_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "test_helper"
require "genetic/algorithm/chromosome"

class Genetic::Algorithm::ChromosomeTest < Minitest::Test
def test_it_can_mutate_floats
20.times do
test_genes = Array.new(rand(1..20)) { rand(0..100) }
test_ranges = Array.new(test_genes.length) do |i|
el = test_genes[i].to_f
[rand(el - 50.0..el), rand(el..el + 50.0)]
end

chromosome = Genetic::Algorithm::Chromosome.new(fitness: nil, genes: test_genes.dup)
chromosome.mutate(test_ranges)

number_of_mutated_genes = 0
chromosome.genes.each_with_index do |el, i|
if el != test_genes[i]
number_of_mutated_genes += 1
end

assert(test_ranges[i][0] < el && el < test_ranges[i][1])
end
assert_equal(1, number_of_mutated_genes)
end
end

def test_it_can_mutate_with_data_range
test_ranges = [%w[male female], [18, 19, 20, 21, 22], ["🔥", "❄️", "🌎", "💨"]]
test_genes = Array.new(test_ranges.length) { |i| test_ranges[i][0] }

chromosome = Genetic::Algorithm::Chromosome.new(fitness: nil, genes: test_genes.dup)
20.times do |i| # Discrete mutations can sometimes return the same value
assert(false, "Unable to mutate") if i == 20
chromosome.mutate(test_ranges, true)
break if chromosome.genes != test_genes
end

number_of_mutated_genes = 0
chromosome.genes.each_with_index do |el, i|
if el != test_genes[i]
number_of_mutated_genes += 1
end

assert((test_ranges[i].include? el))
end
assert_equal(1, number_of_mutated_genes)
end
end
6 changes: 1 addition & 5 deletions test/genetic/algorithm/resolver_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

class Genetic::Algorithm::ResolverTest < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::Genetic::Algorithm::Resolver::VERSION
end

def test_it_does_something_useful
assert false
refute_nil Genetic::Algorithm::VERSION
end
end
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require "genetic/algorithm/resolver"
require "genetic/algorithm"

require "minitest/autorun"

0 comments on commit 5a6271f

Please sign in to comment.