darwinning 0.0.1 → 0.0.2
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/README.md +12 -1
- data/lib/darwinning.rb +1 -1
- data/lib/darwinning/config.rb +4 -4
- data/lib/darwinning/gene.rb +15 -15
- data/lib/darwinning/organism.rb +38 -38
- data/lib/darwinning/population.rb +117 -117
- data/lib/darwinning/version.rb +1 -1
- data/test/darwinning_spec.rb +70 -70
- metadata +2 -2
data/README.md
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
Darwinning
|
2
2
|
==========
|
3
|
+
[](http://badge.fury.io/rb/darwinning)
|
4
|
+
|
5
|
+
[gem]: https://rubygems.org/gems/darwinning
|
6
|
+
|
3
7
|
A Ruby gem to aid in the use of genetic algorithms.
|
4
8
|
|
9
|
+
Installation
|
10
|
+
--------
|
11
|
+
|
12
|
+
```
|
13
|
+
gem install darwinning
|
14
|
+
```
|
15
|
+
|
5
16
|
Examples
|
6
17
|
--------
|
7
18
|
|
@@ -28,7 +39,7 @@ class Triple < Darwinning::Organism
|
|
28
39
|
end
|
29
40
|
|
30
41
|
p = Darwinning::Population.new(Triple, 10, 0, 0.1, 100)
|
31
|
-
p.evolve
|
42
|
+
p.evolve!
|
32
43
|
|
33
44
|
p.best_member.nice_print # prints the member representing the solution
|
34
45
|
```
|
data/lib/darwinning.rb
CHANGED
data/lib/darwinning/config.rb
CHANGED
data/lib/darwinning/gene.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
module Darwinning
|
2
|
-
|
3
|
-
|
2
|
+
class Gene
|
3
|
+
attr_accessor :name, :value, :value_range, :invalid_values, :units
|
4
4
|
|
5
|
-
|
5
|
+
def initialize(name = "", value_range = [], invalid_values = [], units = "")
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
@name = name
|
8
|
+
@value_range = value_range.to_a
|
9
|
+
@invalid_values = invalid_values.to_a
|
10
|
+
@units = units
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def express
|
14
|
+
(@value_range - @invalid_values).sample
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def is_valid_value?(value)
|
18
|
+
@value_range.include?(value) and not @invalid_values.include?(value)
|
19
|
+
end
|
20
|
+
end
|
21
21
|
end
|
data/lib/darwinning/organism.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# Found from http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
|
2
2
|
module ClassLevelInheritableAttributes
|
3
3
|
def self.included(base)
|
4
|
-
base.extend(ClassMethods)
|
4
|
+
base.extend(ClassMethods)
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
module ClassMethods
|
8
8
|
def inheritable_attributes(*args)
|
9
9
|
@inheritable_attributes ||= [:inheritable_attributes]
|
@@ -15,7 +15,7 @@ module ClassLevelInheritableAttributes
|
|
15
15
|
end
|
16
16
|
@inheritable_attributes
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def inherited(subclass)
|
20
20
|
@inheritable_attributes.each do |inheritable_attribute|
|
21
21
|
instance_var = "@#{inheritable_attribute}"
|
@@ -26,47 +26,47 @@ module ClassLevelInheritableAttributes
|
|
26
26
|
end
|
27
27
|
|
28
28
|
module Darwinning
|
29
|
-
|
29
|
+
class Organism
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
include ClassLevelInheritableAttributes
|
32
|
+
inheritable_attributes :genes, :name
|
33
|
+
attr_accessor :genotypes, :fitness, :name, :genes
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
@genes = [] # Gene instances
|
36
|
+
@name = ""
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
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
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
def name
|
65
|
+
self.class.name
|
66
|
+
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
def genes
|
69
|
+
self.class.genes
|
70
|
+
end
|
71
|
+
end
|
72
72
|
end
|
@@ -1,120 +1,120 @@
|
|
1
1
|
module Darwinning
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
120
|
end
|
data/lib/darwinning/version.rb
CHANGED
data/test/darwinning_spec.rb
CHANGED
@@ -1,87 +1,87 @@
|
|
1
1
|
require 'darwinning'
|
2
2
|
|
3
3
|
describe Darwinning::Gene do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
it "name should be set" do
|
10
|
+
@digit.name.should == "digit"
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
it "value range should be set" do
|
14
|
+
@digit.value_range.should == (0..9).to_a
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
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
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
it "units should be set" do
|
22
|
+
@day_hour.units.should == "o'clock"
|
23
|
+
end
|
24
24
|
|
25
|
-
|
25
|
+
describe "#express" do
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
it "expressed value should be within range" do
|
28
|
+
(0..9).to_a.include?(@digit.express).should == true # uncertain test
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
35
|
|
36
36
|
end
|
37
37
|
|
38
38
|
describe Darwinning::Organism do
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
85
|
|
86
86
|
end
|
87
87
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: darwinning
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-29 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Darwinning provides tools to build genetic algorithm solutions using
|
15
15
|
a Gene, Organism, and Population structure.
|