genetic_framework 0.0.4 → 0.0.5
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/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
|
|