genetic_algorithms 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rvmrc +48 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +35 -0
- data/README.rdoc +17 -0
- data/genetic_algorithms.gemspec +20 -0
- data/lib/genetic_algorithms.rb +24 -0
- data/lib/genetic_algorithms/chromosome.rb +64 -0
- data/lib/genetic_algorithms/engine.rb +32 -0
- data/lib/genetic_algorithms/exceptions.rb +4 -0
- data/lib/genetic_algorithms/fitness_functions/all_off_sample.rb +19 -0
- data/lib/genetic_algorithms/fitness_functions/alternating_on_off_sample.rb +20 -0
- data/lib/genetic_algorithms/natural_hash.rb +22 -0
- data/lib/genetic_algorithms/population.rb +46 -0
- data/lib/genetic_algorithms/roulette_wheel.rb +30 -0
- data/lib/genetic_algorithms/version.rb +3 -0
- data/spec/chromosome_spec.rb +74 -0
- data/spec/engine_spec.rb +37 -0
- data/spec/natural_hash_spec.rb +23 -0
- data/spec/population_spec.rb +52 -0
- data/spec/roulette_wheel_spec.rb +18 -0
- data/spec/spec_helper.rb +7 -0
- metadata +120 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
+
environment_id="ruby-1.9.3@genetic_algorithms"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.16.6 (stable)" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
else
|
29
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
+
rvm --create "$environment_id" || {
|
31
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
+
return 1
|
33
|
+
}
|
34
|
+
fi
|
35
|
+
|
36
|
+
# If you use bundler, this might be useful to you:
|
37
|
+
# if [[ -s Gemfile ]] && {
|
38
|
+
# ! builtin command -v bundle >/dev/null ||
|
39
|
+
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
|
40
|
+
# }
|
41
|
+
# then
|
42
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
+
# gem install bundler
|
44
|
+
# fi
|
45
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
+
# then
|
47
|
+
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
|
48
|
+
# fi
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
genetic_algorithms (0.0.1)
|
5
|
+
logging (~> 1.8.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
little-plugger (1.1.3)
|
12
|
+
logging (1.8.0)
|
13
|
+
little-plugger (>= 1.1.3)
|
14
|
+
multi_json (>= 1.3.6)
|
15
|
+
multi_json (1.3.6)
|
16
|
+
rspec (2.11.0)
|
17
|
+
rspec-core (~> 2.11.0)
|
18
|
+
rspec-expectations (~> 2.11.0)
|
19
|
+
rspec-mocks (~> 2.11.0)
|
20
|
+
rspec-core (2.11.1)
|
21
|
+
rspec-expectations (2.11.3)
|
22
|
+
diff-lcs (~> 1.1.3)
|
23
|
+
rspec-mocks (2.11.3)
|
24
|
+
simplecov (0.6.4)
|
25
|
+
multi_json (~> 1.0)
|
26
|
+
simplecov-html (~> 0.5.3)
|
27
|
+
simplecov-html (0.5.3)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
genetic_algorithms!
|
34
|
+
rspec
|
35
|
+
simplecov
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
{<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/execdd17/genetic_algorithms]
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
This project contains my work in progress on Genetic Algorithms. The basic premise involves creating an initial random population of chromosomes and evolving them until they reach some ideal state.
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
A chromosome is a solution to a problem, and represented as a bit string. It is intentionally general in order to lend itself to multiple domains. The only thing linking it to a particular problem set is the fitness function. A fitness function evaluates a chromosome, that is, the effectiveness of a proposed solution.
|
10
|
+
|
11
|
+
In my GeneticAlgorithms project, I use the engine to encapsulate the entire evolution process. You can quickly use it with default values like this:
|
12
|
+
|
13
|
+
<tt>GeneticAlgorithms::Engine.new.start "AllOffSample"</tt>
|
14
|
+
|
15
|
+
The argument to the start method is a fitness function. I have included a few with this project as a basic guide for creating your own.
|
16
|
+
|
17
|
+
The engine has three optional parameters that can be utilized during construction. The population size, chromosome length, and number of generations to evolve (in that order). You'll notice that modifying these values can have a tremendous effect on the algorithm itself, and that is one of the interesting characteristics of genetic algorithms. In the future I will be opening up more parameters as well; currently many of them are tightly coupled to their respective classes.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
require 'genetic_algorithms/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'genetic_algorithms'
|
6
|
+
s.version = GeneticAlgorithms::VERSION
|
7
|
+
s.authors = ["Alexander Vanadio"]
|
8
|
+
s.email = 'execdd17@gmail.com'
|
9
|
+
s.homepage = 'https://github.com/execdd17/genetic_algorithms'
|
10
|
+
s.summary = "A simple API for using genetic algorithms in Ruby"
|
11
|
+
s.description = "This gem allows you to evolve chromosomes in order to solve problems"
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.add_dependency("logging", "~> 1.8.0")
|
18
|
+
s.add_development_dependency("rspec")
|
19
|
+
s.add_development_dependency("simplecov")
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib/genetic_algorithms'))
|
3
|
+
|
4
|
+
require 'logging'
|
5
|
+
|
6
|
+
%W{Population Chromosome RouletteWheel}.each do |logger_type|
|
7
|
+
log = Logging.logger["GeneticAlgorithms::#{logger_type}"]
|
8
|
+
log.level = :debug
|
9
|
+
log.add_appenders Logging.appenders.file("results.log")
|
10
|
+
end
|
11
|
+
|
12
|
+
module GeneticAlgorithms
|
13
|
+
require 'genetic_algorithms/chromosome'
|
14
|
+
require 'genetic_algorithms/exceptions'
|
15
|
+
require 'genetic_algorithms/roulette_wheel'
|
16
|
+
require 'genetic_algorithms/population'
|
17
|
+
|
18
|
+
ff_path = File.join(File.expand_path("..", __FILE__), "genetic_algorithms/fitness_functions")
|
19
|
+
Dir[File.join ff_path, "**/*.rb"].each do |f|
|
20
|
+
require f
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'genetic_algorithms/engine'
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module GeneticAlgorithms
|
2
|
+
class Chromosome < String
|
3
|
+
|
4
|
+
OFF, ON = "0", "1"
|
5
|
+
|
6
|
+
def initialize(string)
|
7
|
+
unless string.each_char.all? { |s| s == OFF or s == ON }
|
8
|
+
raise InvalidChromosome, "A chromosome can only have 0's and 1's"
|
9
|
+
end
|
10
|
+
|
11
|
+
@logger = Logging.logger[self.class]
|
12
|
+
super(string)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.random(length)
|
16
|
+
chromosome = (0...length).inject(String.new) do |chromosome, i|
|
17
|
+
chromosome += (rand(2) == 0 ? OFF : ON)
|
18
|
+
end
|
19
|
+
|
20
|
+
Chromosome.new chromosome
|
21
|
+
end
|
22
|
+
|
23
|
+
def mutate(prob=0.05)
|
24
|
+
@logger.debug "MUTATION: Starting on\t#{self}"
|
25
|
+
|
26
|
+
chromosome = self.each_char.map do |char|
|
27
|
+
mutate?(prob) ? flip(char) : char
|
28
|
+
end.join
|
29
|
+
|
30
|
+
@logger.debug "MUTATION: Result\t\t#{chromosome}"
|
31
|
+
Chromosome.new chromosome
|
32
|
+
end
|
33
|
+
|
34
|
+
def flip(char)
|
35
|
+
@logger.debug "MUTATION: Triggered on #{self}" if char == ON
|
36
|
+
char == ON ? OFF : ON
|
37
|
+
end
|
38
|
+
|
39
|
+
def crossover(chromosome, probability=0.7)
|
40
|
+
unless self.size == chromosome.size
|
41
|
+
raise IncompatibleChromosomes, "Both chromosomes need to be the same size"
|
42
|
+
end
|
43
|
+
|
44
|
+
offspring = self
|
45
|
+
|
46
|
+
if crossover?(probability)
|
47
|
+
index = rand(0...length)
|
48
|
+
offspring = Chromosome.new self[0...index] + chromosome[index...length]
|
49
|
+
@logger.debug "CROSSOVER: Using the first #{index} bits from #{self} and " +
|
50
|
+
"the last #{length - index} bits from #{chromosome}"
|
51
|
+
@logger.debug "CROSSOVER: Result\t#{offspring}"
|
52
|
+
end
|
53
|
+
|
54
|
+
offspring.mutate
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_prob(prob)
|
58
|
+
rand <= prob
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :crossover?, :check_prob
|
62
|
+
alias_method :mutate?, :check_prob
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GeneticAlgorithms
|
2
|
+
class Engine
|
3
|
+
|
4
|
+
def initialize(population_size=10, chromosome_length=10, num_generations=5)
|
5
|
+
@population_size = population_size
|
6
|
+
@chromosome_length = chromosome_length
|
7
|
+
@num_generations = num_generations
|
8
|
+
|
9
|
+
@chromosomes = Population.random_chromosomes @population_size, @chromosome_length
|
10
|
+
@population = Population.new @chromosomes
|
11
|
+
end
|
12
|
+
|
13
|
+
def start(fitness_function_type)
|
14
|
+
extend GeneticAlgorithms::FitnessFunctions.const_get(fitness_function_type)
|
15
|
+
|
16
|
+
highest_score, best_gen = 0, nil
|
17
|
+
|
18
|
+
(0...@num_generations).inject(@population) do |newest_population, i|
|
19
|
+
next_gen = newest_population.evolve(&fitness_function)
|
20
|
+
|
21
|
+
if newest_population.highest_score > highest_score
|
22
|
+
highest_score, best_gen = newest_population.highest_score, newest_population
|
23
|
+
break if highest_score == self.best_possible_score
|
24
|
+
end
|
25
|
+
|
26
|
+
next_gen
|
27
|
+
end
|
28
|
+
|
29
|
+
{best_gen.best_solution => best_gen.highest_score}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module GeneticAlgorithms
|
2
|
+
module FitnessFunctions
|
3
|
+
module AllOffSample
|
4
|
+
|
5
|
+
def best_possible_score
|
6
|
+
@chromosome_length
|
7
|
+
end
|
8
|
+
|
9
|
+
def fitness_function
|
10
|
+
lambda do |chromosome|
|
11
|
+
chromosome.each_char.inject(0) do |accum, char|
|
12
|
+
accum += 1 if char == Chromosome::OFF
|
13
|
+
accum
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GeneticAlgorithms
|
2
|
+
module FitnessFunctions
|
3
|
+
module AlternatingOnOffSample
|
4
|
+
|
5
|
+
def best_possible_score
|
6
|
+
@chromosome_length
|
7
|
+
end
|
8
|
+
|
9
|
+
def fitness_function
|
10
|
+
lambda do |chromosome|
|
11
|
+
(0...chromosome.length).inject(0) do |accum, index|
|
12
|
+
accum += 1 if index % 2 == 0 and chromosome[index] == Chromosome::ON
|
13
|
+
accum += 1 if index % 2 == 1 and chromosome[index] == Chromosome::OFF
|
14
|
+
accum
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GeneticAlgorithms
|
2
|
+
class NaturalHash < Hash
|
3
|
+
def map
|
4
|
+
result = super()
|
5
|
+
hash_structure?(result) ? NaturalHash[ result ] : result
|
6
|
+
end
|
7
|
+
|
8
|
+
def sort_by
|
9
|
+
NaturalHash[ super ]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def hash_structure?(obj)
|
15
|
+
if obj.is_a? Array and not obj.empty?
|
16
|
+
return true if obj.all? { |i| i.is_a? Array and i.length == 2}
|
17
|
+
end
|
18
|
+
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module GeneticAlgorithms
|
2
|
+
|
3
|
+
#TODO: add input validation
|
4
|
+
class Population
|
5
|
+
|
6
|
+
attr_reader :chromosomes, :highest_score, :best_solution
|
7
|
+
|
8
|
+
def initialize(chromosomes)
|
9
|
+
@chromosomes = chromosomes
|
10
|
+
@logger = Logging.logger[self.class]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.random_chromosomes(total_chromosomes, chromosome_length)
|
14
|
+
Array.new(total_chromosomes).map do
|
15
|
+
Chromosome.random(chromosome_length)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def evolve(&block)
|
20
|
+
weighted_chromosomes = @chromosomes.inject(Hash.new) do |memo, chromosome|
|
21
|
+
memo[chromosome] = block.call(chromosome)
|
22
|
+
memo
|
23
|
+
end
|
24
|
+
|
25
|
+
highest_weighted = Hash[[ weighted_chromosomes.invert.sort.last ]].invert
|
26
|
+
@best_solution = highest_weighted.keys.first
|
27
|
+
@highest_score = highest_weighted.values.first
|
28
|
+
|
29
|
+
roulette_wheel = RouletteWheel.new weighted_chromosomes
|
30
|
+
|
31
|
+
offspring = (0...(@chromosomes.size)).inject(Array.new) do |offspring|
|
32
|
+
mates = Array.new(2).map do
|
33
|
+
roulette_wheel.spin
|
34
|
+
end
|
35
|
+
|
36
|
+
child_chromosome = mates.first.crossover(mates.last)
|
37
|
+
offspring << child_chromosome
|
38
|
+
end
|
39
|
+
|
40
|
+
@logger.info "Highest Score in population: #@highest_score"
|
41
|
+
@logger.info "Best Solution in population: #@best_solution"
|
42
|
+
|
43
|
+
Population.new offspring
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'genetic_algorithms/natural_hash'
|
2
|
+
|
3
|
+
module GeneticAlgorithms
|
4
|
+
class RouletteWheel
|
5
|
+
def initialize chromosomes_and_scores
|
6
|
+
chromosomes_and_scores = NaturalHash[ chromosomes_and_scores ]
|
7
|
+
total = chromosomes_and_scores.values.inject(:+)
|
8
|
+
@logger = Logging.logger[self]
|
9
|
+
|
10
|
+
normalized = chromosomes_and_scores.map do |chromosome, score|
|
11
|
+
[chromosome, score.to_f/total]
|
12
|
+
end
|
13
|
+
|
14
|
+
@normalized = normalized.sort_by { |chromosome, probability| probability }
|
15
|
+
end
|
16
|
+
|
17
|
+
def spin
|
18
|
+
accumulator, prng = 0, rand
|
19
|
+
|
20
|
+
@normalized.each_pair do |chromosome, probability|
|
21
|
+
if prng <= probability + accumulator
|
22
|
+
@logger.debug "Choosing #{chromosome} with probability #{probability}"
|
23
|
+
return chromosome
|
24
|
+
end
|
25
|
+
|
26
|
+
accumulator += probability
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include GeneticAlgorithms
|
3
|
+
|
4
|
+
describe Chromosome do
|
5
|
+
describe ".initialize" do
|
6
|
+
it "should raise an exception when given non 0 and 1 characters" do
|
7
|
+
lambda { Chromosome.new("01AB") }.should raise_error InvalidChromosome
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should not raise an exception when given good data" do
|
11
|
+
lambda { Chromosome.new("01110010") }.should_not raise_error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#mutate" do
|
16
|
+
subject { Chromosome.new "0000" }
|
17
|
+
|
18
|
+
it "should flip bits when triggered" do
|
19
|
+
subject.mutate(1.0).should == "1111"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not flip bits when it isn't triggered" do
|
23
|
+
subject.mutate(0.0).should == "0000"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#crossover" do
|
28
|
+
ROUNDS = 1000
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
@chromosome_1 = Chromosome.new "0000"
|
32
|
+
@chromosome_2 = Chromosome.new "1111"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return a single Chromosome" do
|
36
|
+
@chromosome_1.crossover(@chromosome_2).is_a?(Chromosome).should == true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return a chromosome identical to the caller over time" do
|
40
|
+
(0...ROUNDS).any? do |i|
|
41
|
+
@chromosome_1.crossover(@chromosome_2) == @chromosome_1
|
42
|
+
end.should == true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return a chromosome identical to the receiver over time" do
|
46
|
+
(0...ROUNDS).any? do |i|
|
47
|
+
@chromosome_1.crossover(@chromosome_2) == @chromosome_2
|
48
|
+
end.should == true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return a distinct new chromosome over time" do
|
52
|
+
(0...ROUNDS).any? do |i|
|
53
|
+
offspring = @chromosome_1.crossover(@chromosome_2)
|
54
|
+
offspring != @chromosome_1 and offspring != @chromosome_2
|
55
|
+
end.should == true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise an exception when trying to swap chromosomes of different sizes" do
|
59
|
+
lambda do
|
60
|
+
@chromosome_1.crossover(Chromosome.new("01"), 0.0)
|
61
|
+
end.should raise_error IncompatibleChromosomes
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".random" do
|
66
|
+
it "should return an instance of Chromosome" do
|
67
|
+
Chromosome.random(10).is_a?(Chromosome).should == true
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should be the length specified" do
|
71
|
+
Chromosome.random(10).length.should == 10
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include GeneticAlgorithms
|
3
|
+
|
4
|
+
describe Engine do
|
5
|
+
subject { Engine.new }
|
6
|
+
|
7
|
+
# TODO: DRY out this code
|
8
|
+
describe "#start" do
|
9
|
+
context "AllOffSample fitness function" do
|
10
|
+
it "returns a hash" do
|
11
|
+
subject.start("AllOffSample").is_a?(Hash).should == true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the best solution as a Chromosome" do
|
15
|
+
subject.start("AllOffSample").keys.first.is_a?(Chromosome).should == true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns the best score as a Fixnum" do
|
19
|
+
subject.start("AllOffSample").values.first.is_a?(Fixnum).should == true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "AlternatingOnOffSample fitness function" do
|
24
|
+
it "returns a hash" do
|
25
|
+
subject.start("AlternatingOnOffSample").is_a?(Hash).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the best solution as a Chromosome" do
|
29
|
+
subject.start("AlternatingOnOffSample").keys.first.is_a?(Chromosome).should == true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns the best score as a Fixnum" do
|
33
|
+
subject.start("AlternatingOnOffSample").values.first.is_a?(Fixnum).should == true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include GeneticAlgorithms
|
3
|
+
|
4
|
+
describe NaturalHash do
|
5
|
+
|
6
|
+
subject { NaturalHash[ {a:1, b:2} ] }
|
7
|
+
|
8
|
+
describe "#map" do
|
9
|
+
it "should return a NaturalHash when mapping a 2d array" do
|
10
|
+
subject.map { |k,v| [k,v] }.is_a?(NaturalHash).should == true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the normal value when mapping anything else" do
|
14
|
+
subject.map { |k,v| "test" }.is_a?(NaturalHash).should == false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#sort_by" do
|
19
|
+
it "should return a NaturalHash" do
|
20
|
+
subject.sort_by { |k,v| v }.is_a?(NaturalHash).should == true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include GeneticAlgorithms
|
3
|
+
|
4
|
+
# NOTE: changing the constants can alter the tests dramatically
|
5
|
+
describe Population do
|
6
|
+
|
7
|
+
POPULATION_SIZE = 100 # the number of chromosomes
|
8
|
+
CHROMOSOME_LENGTH = 30
|
9
|
+
NUM_GENERATIONS = 50
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@chromosomes = Population.random_chromosomes POPULATION_SIZE, CHROMOSOME_LENGTH
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".random_chromosomes" do
|
16
|
+
it "should return an array of chromosomes" do
|
17
|
+
@chromosomes.all? { |element| element.is_a? Chromosome }.should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return the correct number of chromosomes" do
|
21
|
+
@chromosomes.length.should == POPULATION_SIZE
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return chromosomes of the correct length" do
|
25
|
+
@chromosomes.all? { |chromosome| chromosome.length == CHROMOSOME_LENGTH }.should == true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#evolve" do
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
@population = Population.new @chromosomes
|
33
|
+
@best_score = CHROMOSOME_LENGTH
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should accept a block that holds the fitness function" do
|
37
|
+
lambda do
|
38
|
+
@population.evolve { |chromosome| rand(@best_score) }
|
39
|
+
end.should_not raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return a population" do
|
43
|
+
next_gen = @population.evolve { |chromosome| rand(chromosome.length) }
|
44
|
+
next_gen.is_a?(Population).should == true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return a population different than the one before evolution" do
|
48
|
+
next_gen = @population.evolve { |chromosome| rand(chromosome.length) }
|
49
|
+
next_gen.eql?(@population).should == false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include GeneticAlgorithms
|
3
|
+
|
4
|
+
describe RouletteWheel do
|
5
|
+
subject { RouletteWheel.new({a: 3, b: 1, c: 2}) }
|
6
|
+
|
7
|
+
describe "#spin" do
|
8
|
+
|
9
|
+
SPINS = 1000
|
10
|
+
|
11
|
+
it "should return the highest score on a long enough timeline" do
|
12
|
+
results = {a: 0, b: 0, c: 0}
|
13
|
+
SPINS.times { results[subject.spin] += 1 }
|
14
|
+
|
15
|
+
(results[:a] > results[:b] and results[:a] > results[:c]).should == true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: genetic_algorithms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alexander Vanadio
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: logging
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.8.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.8.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: simplecov
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: This gem allows you to evolve chromosomes in order to solve problems
|
63
|
+
email: execdd17@gmail.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- .gitignore
|
69
|
+
- .rvmrc
|
70
|
+
- Gemfile
|
71
|
+
- Gemfile.lock
|
72
|
+
- README.rdoc
|
73
|
+
- genetic_algorithms.gemspec
|
74
|
+
- lib/genetic_algorithms.rb
|
75
|
+
- lib/genetic_algorithms/chromosome.rb
|
76
|
+
- lib/genetic_algorithms/engine.rb
|
77
|
+
- lib/genetic_algorithms/exceptions.rb
|
78
|
+
- lib/genetic_algorithms/fitness_functions/all_off_sample.rb
|
79
|
+
- lib/genetic_algorithms/fitness_functions/alternating_on_off_sample.rb
|
80
|
+
- lib/genetic_algorithms/natural_hash.rb
|
81
|
+
- lib/genetic_algorithms/population.rb
|
82
|
+
- lib/genetic_algorithms/roulette_wheel.rb
|
83
|
+
- lib/genetic_algorithms/version.rb
|
84
|
+
- spec/chromosome_spec.rb
|
85
|
+
- spec/engine_spec.rb
|
86
|
+
- spec/natural_hash_spec.rb
|
87
|
+
- spec/population_spec.rb
|
88
|
+
- spec/roulette_wheel_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
homepage: https://github.com/execdd17/genetic_algorithms
|
91
|
+
licenses: []
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.8.24
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: A simple API for using genetic algorithms in Ruby
|
114
|
+
test_files:
|
115
|
+
- spec/chromosome_spec.rb
|
116
|
+
- spec/engine_spec.rb
|
117
|
+
- spec/natural_hash_spec.rb
|
118
|
+
- spec/population_spec.rb
|
119
|
+
- spec/roulette_wheel_spec.rb
|
120
|
+
- spec/spec_helper.rb
|