genetic_framework 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/genetic_framework.rb +46 -25
- metadata +3 -3
data/lib/genetic_framework.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
class Array
|
2
|
-
|
3
2
|
def random_element
|
4
3
|
self[Random.rand(self.size)]
|
5
4
|
end
|
6
5
|
end
|
7
6
|
|
7
|
+
#represents a pool of evolving strings towards a solution
|
8
8
|
class GenePool
|
9
|
+
|
10
|
+
#represents a single potential solution to the problem - contains
|
11
|
+
#the string encoding and the fitness of that encoding.
|
9
12
|
class Spawn
|
10
|
-
|
11
13
|
attr_reader :string, :fitness
|
12
14
|
|
13
15
|
def initialize(fitness, string)
|
@@ -18,9 +20,20 @@ class GenePool
|
|
18
20
|
def <=>(other)
|
19
21
|
return self.fitness <=> other.fitness
|
20
22
|
end
|
21
|
-
|
22
|
-
end
|
23
|
+
end#end class Spawn
|
23
24
|
|
25
|
+
#gen_size is the number of spawn per generation, should be even
|
26
|
+
#encoding_map is a list of lists, where each position in the
|
27
|
+
#containing list is the equivalent position in the string encoding,
|
28
|
+
#and the list at that position contains all possible characters allowed
|
29
|
+
#at that position.
|
30
|
+
#TODO might not be wise to couple GenePool to the generation initialization.
|
31
|
+
#mutation_prob is the probability of mutation at each character of the encoding
|
32
|
+
#cull_threshold, if provided, is the minimum fitness score allowed to reproduce.
|
33
|
+
# will not activate until enough sufficiently fit spawn are created
|
34
|
+
#elite, if provided, saves that number of the highest scoring spawn and passes
|
35
|
+
# them directly into the next generation. Should be even.
|
36
|
+
#fitness is the function that evaluates a string (in this case, I try to maximize fitness)
|
24
37
|
def initialize(gen_size, encoding_map, mutation_prob, cull_threshold = nil, elite = 0, initial_gen = nil, &fitness)
|
25
38
|
@gen_size = gen_size
|
26
39
|
@encoding_map = encoding_map
|
@@ -31,7 +44,7 @@ class GenePool
|
|
31
44
|
if !initial_gen
|
32
45
|
@gen = initialize_gen.map {|str| Spawn.new(yield(str), str)}
|
33
46
|
end
|
34
|
-
end
|
47
|
+
end#end initialize
|
35
48
|
|
36
49
|
attr_accessor :gen_size, :cull_threshold, :mutation_prob, :elite
|
37
50
|
attr_reader :gen, :encoding_map
|
@@ -46,24 +59,27 @@ class GenePool
|
|
46
59
|
spawn << position.random_element
|
47
60
|
end
|
48
61
|
return spawn.join
|
49
|
-
end
|
62
|
+
end#end build_spawn
|
50
63
|
|
51
64
|
#char_map should be an array with length identical to string encoding length
|
52
65
|
#and each position contains a list of chars possible for that position
|
66
|
+
#creates and returns an initial generation of @gen_size according
|
67
|
+
#to @encoding_map
|
53
68
|
def initialize_gen
|
54
69
|
pop = []
|
55
70
|
1.upto(@gen_size) do |spawn_num|
|
56
71
|
pop << build_spawn
|
57
72
|
end
|
58
73
|
return pop
|
59
|
-
end
|
74
|
+
end#end initialize_gen
|
60
75
|
|
61
|
-
|
62
|
-
|
76
|
+
#finds and returns the "parent" solutions who will "mate".
|
77
|
+
#takes a number of parents to select
|
78
|
+
def random_sexual_selection(num_parents)
|
63
79
|
selected = []
|
64
80
|
candidates = []
|
65
81
|
|
66
|
-
#create a list of candidates who meet the requirements (all if there is no cull)
|
82
|
+
#create a list of candidates who meet the requirements (all if there is no cull or not enough fit spawn)
|
67
83
|
#a candidate shows up in the list once for each fitness level. This may be memory heavy,
|
68
84
|
#so TODO improve the selection based on fitness
|
69
85
|
strong = @gen.select {|spawn| !@cull_threshold or spawn.fitness >= @cull_threshold}
|
@@ -80,11 +96,11 @@ class GenePool
|
|
80
96
|
|
81
97
|
#select from amount candidates a generation_size number of parents. Candidates can be selected
|
82
98
|
#more than once, and have a higher chance of being selected for a higher fitness score
|
83
|
-
1.upto(
|
99
|
+
1.upto(num_parents) do
|
84
100
|
selected << candidates.random_element
|
85
101
|
end
|
86
102
|
return selected
|
87
|
-
end
|
103
|
+
end#end random_sexual_selection
|
88
104
|
|
89
105
|
#mates two parent strings to create two child strings
|
90
106
|
def sexually_reproduce(parent1, parent2)
|
@@ -92,8 +108,11 @@ class GenePool
|
|
92
108
|
child1 = parent1[0, crossover] + parent2[crossover, parent2.size]
|
93
109
|
child2 = parent2[0, crossover] + parent1[crossover, parent2.size]
|
94
110
|
return [child1, child2]
|
95
|
-
end
|
111
|
+
end#end sexually_reproduce
|
96
112
|
|
113
|
+
#traverses the string and with probability @mutation_prob randomly
|
114
|
+
#reassigns one of the characters to a different character in the
|
115
|
+
#map for that position
|
97
116
|
def mutate(string)
|
98
117
|
0.upto(string.size - 1) do |str_index|
|
99
118
|
if Random.rand < @mutation_prob
|
@@ -101,35 +120,37 @@ class GenePool
|
|
101
120
|
end
|
102
121
|
end
|
103
122
|
return string
|
104
|
-
end
|
123
|
+
end#end mutate
|
105
124
|
|
106
125
|
public
|
107
126
|
|
108
|
-
#
|
109
|
-
#the char_map is a list of lists - each position has a list of chars that are valid for that position.
|
127
|
+
#evolves the current pool one generation. if generation is nil, returns an initial, random generation
|
110
128
|
#it tells us both how long each string is and what sort of encodings are valid for creation.
|
111
129
|
#Resulting generation is sorted according to fitness from least to greatest.
|
112
130
|
def evolve
|
113
131
|
#select spawn who will breed
|
114
|
-
|
132
|
+
|
115
133
|
#generation size should be even... or the last parent selected will be ignored
|
116
134
|
#breed new spawn
|
117
135
|
new_gen = []
|
118
|
-
|
119
136
|
if @elite
|
120
137
|
1.upto(@elite) do |offset|
|
121
|
-
if offset > @gen.size
|
138
|
+
break if offset > @gen.size
|
122
139
|
new_gen << @gen[-offset]
|
123
140
|
end
|
124
|
-
end
|
125
|
-
|
126
|
-
(
|
141
|
+
end#end elite selection
|
142
|
+
|
143
|
+
parents = random_sexual_selection(@gen_size - new_gen.size)
|
144
|
+
|
145
|
+
#fill remaining slots with bred children
|
146
|
+
|
147
|
+
(0..parents.size - 1).step(2) do |index|
|
127
148
|
new_gen += sexually_reproduce(parents[index].string, parents[index + 1].string).map do |child_string|
|
128
149
|
s = mutate(child_string)
|
129
150
|
Spawn.new(@fitness.call(s), s)
|
130
151
|
end
|
131
|
-
end
|
152
|
+
end#end create children
|
132
153
|
|
133
154
|
@gen = new_gen.sort
|
134
|
-
end
|
135
|
-
end
|
155
|
+
end#end evolve
|
156
|
+
end#end class GenePool
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 5
|
9
|
+
version: 0.0.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Brian Fults
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-
|
17
|
+
date: 2012-04-17 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|