darwinning 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +90 -0
- data/Rakefile +0 -0
- data/lib/darwinning.rb +8 -0
- data/lib/darwinning/config.rb +6 -0
- data/lib/darwinning/gene.rb +21 -0
- data/lib/darwinning/organism.rb +72 -0
- data/lib/darwinning/population.rb +120 -0
- data/lib/darwinning/version.rb +3 -0
- data/test/darwinning_spec.rb +90 -0
- metadata +55 -0
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
Darwinning
|
2
|
+
==========
|
3
|
+
A Ruby gem to aid in the use of genetic algorithms.
|
4
|
+
|
5
|
+
Examples
|
6
|
+
--------
|
7
|
+
|
8
|
+
### Fifteen
|
9
|
+
|
10
|
+
Here's an dumb example of how you might use Darwinning to solve a pointless problem:
|
11
|
+
|
12
|
+
Let's say for some reason you need a set of 3 number that add up to 15. This is a strange problem to have, but let's solve it anyway.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class Triple < Darwinning::Organism
|
16
|
+
|
17
|
+
@name = "Triple"
|
18
|
+
@genes = [
|
19
|
+
Darwinning::Gene.new("first digit", (0..9)),
|
20
|
+
Darwinning::Gene.new("second digit", (0..9)),
|
21
|
+
Darwinning::Gene.new("third digit", (0..9))
|
22
|
+
]
|
23
|
+
|
24
|
+
def fitness
|
25
|
+
# Try to get the sum of the 3 digits to add up to 15
|
26
|
+
(genotypes.inject{ |sum, x| sum + x } - 15).abs
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
p = Darwinning::Population.new(Triple, 10, 0, 0.1, 100)
|
31
|
+
p.evolve
|
32
|
+
|
33
|
+
p.best_member.nice_print # prints the member representing the solution
|
34
|
+
```
|
35
|
+
|
36
|
+
This code declares an organism class that inherits from Darwinning's Organism parent class to represent solutions. Then we create a population of these solution organisms and evolve the population until a solution meets the fitness threshold or the generation limit is met.
|
37
|
+
|
38
|
+
### Cookies
|
39
|
+
|
40
|
+
Or let's say you want to find the perfect chocolate chip cookie recipie. Sure you could ask your grandmother, but why not let a genetic algorithm do all the work for you? Some baking may be required for this one.
|
41
|
+
|
42
|
+
Define a cookie Organism class, generate an initial population, bake a batch of each and have your friends rate each batch. Use that rating as the fitness value for each recipie and then generate the next generation of cookie recipies. Repeat until you have optimized the recipie or you are sick from eating too many cookies.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class Cookie < Darwinning::Organism
|
46
|
+
|
47
|
+
@name = "Chocolate Chip Cookie"
|
48
|
+
@genes = [
|
49
|
+
Darwinning::Gene.new("white sugar", (0..1), [], "cup"),
|
50
|
+
Darwinning::Gene.new("brown sugar", (0..1), [], "cup"),
|
51
|
+
Darwinning::Gene.new("flour", (0..3), [], "cup"),
|
52
|
+
Darwinning::Gene.new("eggs", (0..3)),
|
53
|
+
Darwinning::Gene.new("baking powder", (0..2), [], "teaspoon"),
|
54
|
+
Darwinning::Gene.new("salt", (0..2), [], "teaspoon"),
|
55
|
+
Darwinning::Gene.new("butter", (0..2), [], "cup"),
|
56
|
+
Darwinning::Gene.new("vanilla extract", (0..2), [], "teaspoon"),
|
57
|
+
Darwinning::Gene.new("chocolate chips", (0..20), [], "ounce"),
|
58
|
+
Darwinning::Gene.new("oven temp", (300..400), [], "degrees F"),
|
59
|
+
Darwinning::Gene.new("cook time", (5..20), [], "minute")
|
60
|
+
]
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
p = Darwinning::Population.new(Cookie, 10, 5, 0.1, 100, true)
|
65
|
+
|
66
|
+
first_gen_ratings = [1.5, 4, 3, 3.5, 2, 1, 1.5, 3, 2.5, 0.5]
|
67
|
+
p.set_members_fitness!(first_gen_ratings)
|
68
|
+
|
69
|
+
p.make_next_generation!
|
70
|
+
|
71
|
+
p.members.each { |m| m.nice_print } # print second generation of cookie recipies
|
72
|
+
```
|
73
|
+
|
74
|
+
### Binary String Organism
|
75
|
+
|
76
|
+
A simple binary string representation of an organism can be easily created thusly:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class BinaryOrganism < Darwinning::Organism
|
80
|
+
|
81
|
+
10.times { |s| @genes << Darwinning::Gene.new("", [0,1]) }
|
82
|
+
|
83
|
+
def fitness
|
84
|
+
# whatever makes sense here
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
## Built by:
|
90
|
+
* [Dave Schwantes](https://github.com/dorkrawk "dorkrawk")
|
data/Rakefile
ADDED
File without changes
|
data/lib/darwinning.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Darwinning
|
2
|
+
class Gene
|
3
|
+
attr_accessor :name, :value, :value_range, :invalid_values, :units
|
4
|
+
|
5
|
+
def initialize(name = "", value_range = [], invalid_values = [], units = "")
|
6
|
+
|
7
|
+
@name = name
|
8
|
+
@value_range = value_range.to_a
|
9
|
+
@invalid_values = invalid_values.to_a
|
10
|
+
@units = units
|
11
|
+
end
|
12
|
+
|
13
|
+
def express
|
14
|
+
(@value_range - @invalid_values).sample
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_valid_value?(value)
|
18
|
+
@value_range.include?(value) and not @invalid_values.include?(value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Found from http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
|
2
|
+
module ClassLevelInheritableAttributes
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def inheritable_attributes(*args)
|
9
|
+
@inheritable_attributes ||= [:inheritable_attributes]
|
10
|
+
@inheritable_attributes += args
|
11
|
+
args.each do |arg|
|
12
|
+
class_eval %(
|
13
|
+
class << self; attr_accessor :#{arg} end
|
14
|
+
)
|
15
|
+
end
|
16
|
+
@inheritable_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def inherited(subclass)
|
20
|
+
@inheritable_attributes.each do |inheritable_attribute|
|
21
|
+
instance_var = "@#{inheritable_attribute}"
|
22
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module Darwinning
|
29
|
+
class Organism
|
30
|
+
|
31
|
+
include ClassLevelInheritableAttributes
|
32
|
+
inheritable_attributes :genes, :name
|
33
|
+
attr_accessor :genotypes, :fitness, :name, :genes
|
34
|
+
|
35
|
+
@genes = [] # Gene instances
|
36
|
+
@name = ""
|
37
|
+
|
38
|
+
def initialize(genotypes = [])
|
39
|
+
#TODO: catch errors if genotype.length != @genotypes.length
|
40
|
+
# catch if genotype[x] is not a valid value for @gene[x]
|
41
|
+
|
42
|
+
if genotypes == []
|
43
|
+
# fill genotypes with expressed Genes
|
44
|
+
@genotypes = self.class.genes.map { |g| g.express } # Gene expressions
|
45
|
+
else
|
46
|
+
@genotypes = genotypes
|
47
|
+
end
|
48
|
+
@fitness = -1
|
49
|
+
end
|
50
|
+
|
51
|
+
# Selects a random genotype from the organism and rexpresses its gene
|
52
|
+
def mutate!
|
53
|
+
random_index = (0..@genotypes.length-1).to_a.sample
|
54
|
+
@genotypes[random_index] = self.class.genes[random_index].express
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def nice_print
|
59
|
+
puts self.class.name == "" ? "[no name]" : self.class.name
|
60
|
+
self.class.genes.to_enum.each_with_index { |g, i| puts " #{g.name}: #{@genotypes[i]} #{g.units}" }
|
61
|
+
puts " fitness: #{fitness}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def name
|
65
|
+
self.class.name
|
66
|
+
end
|
67
|
+
|
68
|
+
def genes
|
69
|
+
self.class.genes
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Darwinning
|
2
|
+
|
3
|
+
class Population
|
4
|
+
attr_accessor :members, :mutation_rate, :generations_limit, :fitness_goal, :organism, :generation
|
5
|
+
|
6
|
+
def initialize(organism, population_size, fitness_goal, mutation_rate = 0.0, generations_limit = 0, manual_fitness = false)
|
7
|
+
@organism = organism
|
8
|
+
@fitness_goal = fitness_goal
|
9
|
+
@mutation_rate = mutation_rate
|
10
|
+
@generations_limit = generations_limit
|
11
|
+
@manual_fitness = manual_fitness
|
12
|
+
@members = []
|
13
|
+
@generation = 0 # initial population is generation 0
|
14
|
+
|
15
|
+
build_population(population_size)
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_population(population_size)
|
19
|
+
population_size.times do |i|
|
20
|
+
@members << @organism.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def evolve
|
25
|
+
until evolution_over?
|
26
|
+
make_next_generation!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def crossover(m1, m2)
|
31
|
+
genotypes1 = []
|
32
|
+
genotypes2 = []
|
33
|
+
|
34
|
+
m1.genotypes.zip(m2.genotypes).each do |g|
|
35
|
+
if m1.genotypes.index(g[0]) % 2 == 0
|
36
|
+
genotypes1 << g[0]
|
37
|
+
genotypes2 << g[1]
|
38
|
+
else
|
39
|
+
genotypes1 << g[1]
|
40
|
+
genotypes2 << g[0]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
[@organism.new(genotypes1), @organism.new(genotypes2)]
|
45
|
+
end
|
46
|
+
|
47
|
+
def sexytimes(m1, m2)
|
48
|
+
crossover(m1, m2)
|
49
|
+
end
|
50
|
+
|
51
|
+
def weighted_select(members)
|
52
|
+
e = 0.01
|
53
|
+
fitness_sum = members.inject(0) { |sum, m| sum + m.fitness }
|
54
|
+
|
55
|
+
weighted_members = members.sort_by { |m| (m.fitness - @fitness_goal).abs }.map { |m| [m, fitness_sum / ((m.fitness - @fitness_goal).abs + e)] }
|
56
|
+
|
57
|
+
weight_sum = weighted_members.inject(0) { |sum, m| sum + m[1] }
|
58
|
+
pick = (0..weight_sum).to_a.sample
|
59
|
+
|
60
|
+
weighted_members.reverse! # In order to pop from the end we need the lowest ranked first
|
61
|
+
pick_sum = 0
|
62
|
+
|
63
|
+
until pick_sum > pick do
|
64
|
+
selected_member = weighted_members.pop
|
65
|
+
pick_sum += selected_member[1]
|
66
|
+
end
|
67
|
+
|
68
|
+
selected_member[0]
|
69
|
+
end
|
70
|
+
|
71
|
+
def mutate!
|
72
|
+
@members.map! { |m|
|
73
|
+
if (0..100).to_a.sample < @mutation_rate*100
|
74
|
+
m.mutate!
|
75
|
+
else
|
76
|
+
m
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_members_fitness!(fitness_values)
|
82
|
+
@members.to_enum.each_with_index { |m, i| m.fitness = fitness_values[i] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def make_next_generation!
|
86
|
+
temp_members = @members
|
87
|
+
used_members = []
|
88
|
+
new_members = []
|
89
|
+
|
90
|
+
until new_members.length == @members.length/2
|
91
|
+
m1 = weighted_select(@members - used_members)
|
92
|
+
used_members << m1
|
93
|
+
m2 = weighted_select(@members - used_members)
|
94
|
+
used_members << m2
|
95
|
+
|
96
|
+
new_members << crossover(m1,m2)
|
97
|
+
end
|
98
|
+
|
99
|
+
new_members.flatten!
|
100
|
+
|
101
|
+
@members = new_members
|
102
|
+
|
103
|
+
mutate!
|
104
|
+
@generation += 1
|
105
|
+
end
|
106
|
+
|
107
|
+
def evolution_over?
|
108
|
+
# check if the fiteness goal or generation limit has been met
|
109
|
+
@generation == @generations_limit or best_member.fitness == @fitness_goal
|
110
|
+
end
|
111
|
+
|
112
|
+
def best_member
|
113
|
+
@members.sort_by { |m| m.fitness }[0]
|
114
|
+
end
|
115
|
+
|
116
|
+
def size
|
117
|
+
@members.length
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'darwinning'
|
2
|
+
|
3
|
+
describe Darwinning::Gene do
|
4
|
+
before do
|
5
|
+
@digit = Darwinning::Gene.new("digit", (0..9))
|
6
|
+
@day_hour = Darwinning::Gene.new("hour", (0..23), [0,1,2,3,4,20,21,22,23], "o'clock")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "name should be set" do
|
10
|
+
@digit.name.should == "digit"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "value range should be set" do
|
14
|
+
@digit.value_range.should == (0..9).to_a
|
15
|
+
end
|
16
|
+
|
17
|
+
it "invalid values should be set" do
|
18
|
+
@day_hour.invalid_values.should == [0,1,2,3,4,20,21,22,23]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "units should be set" do
|
22
|
+
@day_hour.units.should == "o'clock"
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#express" do
|
26
|
+
|
27
|
+
it "expressed value should be within range" do
|
28
|
+
(0..9).to_a.include?(@digit.express).should == true # uncertain test
|
29
|
+
end
|
30
|
+
|
31
|
+
it "expressed value should not be invalid value" do
|
32
|
+
@day_hour.invalid_values.include?(@digit.express).should == false # uncertain test
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe Darwinning::Organism do
|
39
|
+
before do
|
40
|
+
@org = Darwinning::Organism.new
|
41
|
+
|
42
|
+
class Triple < Darwinning::Organism
|
43
|
+
@name = "Triple"
|
44
|
+
@genes = [
|
45
|
+
Darwinning::Gene.new("first digit", (0..9)),
|
46
|
+
Darwinning::Gene.new("second digit", (0..9)),
|
47
|
+
Darwinning::Gene.new("third digit", (0..9))
|
48
|
+
]
|
49
|
+
|
50
|
+
def fitness
|
51
|
+
# Try to get the sum of the 3 digits to add up to 15
|
52
|
+
(genotypes.inject{ |sum, x| sum + x } - 15).abs
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@triple = Triple.new
|
56
|
+
end
|
57
|
+
|
58
|
+
it "name should default to blank" do
|
59
|
+
@org.name.should == ""
|
60
|
+
end
|
61
|
+
|
62
|
+
it "genes should default to empty array" do
|
63
|
+
@org.genes.should == []
|
64
|
+
end
|
65
|
+
|
66
|
+
it "genotypes should initialize to empty array if genes is empty" do
|
67
|
+
@org.genotypes.should == []
|
68
|
+
end
|
69
|
+
|
70
|
+
it "fitness should default to -1" do
|
71
|
+
@org.fitness.should == -1
|
72
|
+
end
|
73
|
+
|
74
|
+
it "child class should set name" do
|
75
|
+
@triple.name.should == "Triple"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "child class should set genes" do
|
79
|
+
@triple.genes.length.should == 3 # not the best test...
|
80
|
+
end
|
81
|
+
|
82
|
+
it "child class should initialize genotypes from genes" do
|
83
|
+
@triple.genotypes.length.should == 3 # not the best test...
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
describe Darwinning::Population do
|
89
|
+
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: darwinning
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dave Schwantes
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-23 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Darwinning provides tools to build genetic algorithm solutions using
|
15
|
+
a Gene, Organism, and Population structure.
|
16
|
+
email: dave.schwantes@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/darwinning/config.rb
|
22
|
+
- lib/darwinning/gene.rb
|
23
|
+
- lib/darwinning/organism.rb
|
24
|
+
- lib/darwinning/population.rb
|
25
|
+
- lib/darwinning/version.rb
|
26
|
+
- lib/darwinning.rb
|
27
|
+
- Rakefile
|
28
|
+
- README.md
|
29
|
+
- test/darwinning_spec.rb
|
30
|
+
homepage: https://github.com/dorkrawk/darwinning
|
31
|
+
licenses: []
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 1.8.24
|
51
|
+
signing_key:
|
52
|
+
specification_version: 3
|
53
|
+
summary: A Ruby gem to aid in the use of genetic algorithms.
|
54
|
+
test_files:
|
55
|
+
- test/darwinning_spec.rb
|