charlie 0.6.0 → 0.7.0
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/History.txt +14 -0
- data/Manifest.txt +13 -22
- data/README.txt +3 -3
- data/Rakefile +1 -1
- data/TODO.txt +11 -21
- data/data/BENCHMARK +25 -23
- data/data/CROSSOVER +5 -1
- data/data/GENOTYPE +6 -6
- data/data/MUTATION +19 -7
- data/data/SELECTION +2 -1
- data/data/template.html +2 -1
- data/examples/EXAMPLES_README.txt +70 -0
- data/examples/bitstring.rb +72 -0
- data/examples/{gladiatorial_sunburn.rb → coevolution.rb} +80 -22
- data/examples/function_optimization.rb +113 -0
- data/examples/output/{royalroad1_report.html → bitstring_royalroad.html} +822 -655
- data/examples/output/function_optimization_sombrero.html +2289 -0
- data/examples/output/function_optimization_twopeak.csv +210 -0
- data/examples/output/function_optimization_twopeak.html +2477 -0
- data/examples/output/string_weasel.html +513 -0
- data/examples/output/tsp.html +633 -882
- data/examples/{money.rb → permutation.rb} +20 -8
- data/examples/string.rb +98 -0
- data/examples/tree.rb +37 -12
- data/examples/tsp.rb +34 -22
- data/lib/charlie.rb +5 -1
- data/lib/charlie/1.9fixes.rb +46 -0
- data/lib/charlie/crossover.rb +31 -14
- data/lib/charlie/etc/minireport.rb +5 -4
- data/lib/charlie/etc/monkey.rb +11 -8
- data/lib/charlie/gabenchmark.rb +230 -0
- data/lib/charlie/genotype.rb +4 -0
- data/lib/charlie/list/list_crossover.rb +25 -5
- data/lib/charlie/mutate.rb +34 -7
- data/lib/charlie/permutation/permutation.rb +34 -6
- data/lib/charlie/population.rb +12 -122
- data/lib/charlie/selection.rb +1 -0
- data/lib/charlie/tree/tree.rb +179 -17
- data/test/t_common.rb +1 -1
- data/test/test_benchmark.rb +19 -5
- data/test/test_cross.rb +23 -1
- data/test/test_evolve.rb +14 -1
- data/test/test_mutator.rb +28 -2
- data/test/test_permutation.rb +23 -1
- data/test/test_sel.rb +3 -1
- data/test/test_tree.rb +63 -1
- metadata +17 -25
- data/examples/bit.rb +0 -10
- data/examples/function_opt_2peak.rb +0 -24
- data/examples/function_opt_sombero.rb +0 -38
- data/examples/gladiatorial_simple.rb +0 -17
- data/examples/gridwalk.rb +0 -29
- data/examples/output/flattened_sombero.html +0 -6400
- data/examples/output/flattened_sombero2_.html +0 -3576
- data/examples/output/fopt1_dblopt.html +0 -2160
- data/examples/output/hill10.html +0 -5816
- data/examples/output/hill2.csv +0 -24
- data/examples/output/hill2.html +0 -384
- data/examples/output/royalroad2_report.html +0 -1076
- data/examples/output/royalroadquick_report.html +0 -504
- data/examples/output/weasel1_report.html +0 -1076
- data/examples/output/weasel2_report.html +0 -240
- data/examples/royalroad.rb +0 -26
- data/examples/royalroad2.rb +0 -18
- data/examples/simple_climb_hill2.rb +0 -47
- data/examples/weasel.rb +0 -36
data/lib/charlie/population.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
class Population
|
7
7
|
DEFAULT_MAX_GENS = 100
|
8
8
|
|
9
|
-
attr_reader :size, :population
|
9
|
+
attr_reader :size, :population, :genotype_class
|
10
10
|
def initialize(genotype_class,population_size=20)
|
11
11
|
@size = population_size
|
12
12
|
@genotype_class = genotype_class
|
@@ -46,12 +46,12 @@ class Population
|
|
46
46
|
end
|
47
47
|
alias :evolve :evolve_on_console
|
48
48
|
|
49
|
-
# breaks if the block (which is passed the population each
|
49
|
+
# breaks if the block (which is passed the population each "check_every" generations) returns true.
|
50
50
|
# returns an array [population, generations needed]. generations needed==nil for no convergence.
|
51
|
-
def evolve_until_population(generations=DEFAULT_MAX_GENS)
|
51
|
+
def evolve_until_population(generations=DEFAULT_MAX_GENS,check_every=10)
|
52
52
|
tot_gens = nil
|
53
53
|
evolve_block(generations) {|p,g|
|
54
|
-
if yield(p)
|
54
|
+
if (g % check_every).zero? && yield(p)
|
55
55
|
tot_gens = g
|
56
56
|
break
|
57
57
|
end
|
@@ -59,12 +59,12 @@ class Population
|
|
59
59
|
[@population, tot_gens]
|
60
60
|
end
|
61
61
|
|
62
|
-
# breaks if the block (which is passed the best individual each
|
62
|
+
# breaks if the block (which is passed the best individual each "check_every" generations) returns true.
|
63
63
|
# returns an array [population, generations needed]. generations needed==nil for no convergence.
|
64
|
-
def evolve_until_best(generations=DEFAULT_MAX_GENS)
|
64
|
+
def evolve_until_best(generations=DEFAULT_MAX_GENS,check_every=10)
|
65
65
|
tot_gens = nil
|
66
66
|
evolve_block(generations) {|p,g|
|
67
|
-
if yield(p.max)
|
67
|
+
if (g % check_every).zero? && yield(p.max)
|
68
68
|
tot_gens = g
|
69
69
|
break
|
70
70
|
end
|
@@ -74,121 +74,11 @@ class Population
|
|
74
74
|
alias :evolve_until :evolve_until_best
|
75
75
|
|
76
76
|
|
77
|
-
|
77
|
+
# accessors for population
|
78
|
+
[:[],:max,:min].each{|m|
|
79
|
+
define_method(m){|*args| population.send(m,*args) }
|
80
|
+
}
|
81
|
+
alias :best :max
|
78
82
|
|
79
|
-
# This method generates reports comparing several selection/crossover/mutation methods. Check the examples directory for several examples. See the BENCHMARK documentation file for more information.
|
80
|
-
def benchmark(genotype_class, html_outfile='report.html', csv_outfile=nil, &b)
|
81
|
-
start = Time.now
|
82
|
-
|
83
|
-
d = StrategiesDSL.new; d.instance_eval(&b)
|
84
|
-
tries = d.get_tests
|
85
|
-
gens = d.generations
|
86
|
-
pop_size = d.population_size
|
87
|
-
rep = d.repeat
|
88
|
-
puts "#{tries.size} Total tests:"; done=0
|
89
|
-
|
90
|
-
data = tries.map{|s,c,m|
|
91
|
-
print "\nRunning test #{done+=1}/#{tries.size} : #{s} / #{c} / #{m}\t"
|
92
|
-
gclass = Class.new(genotype_class) { use s,c,m }
|
93
|
-
start_try = Time.now
|
94
|
-
bestf = (0...rep).map{
|
95
|
-
print '.'; $stdout.flush
|
96
|
-
Population.new(gclass,pop_size).evolve_silent(gens)[-1].fitness # todo: generations
|
97
|
-
}
|
98
|
-
[s,c,m, (Time.now-start_try) / rep, bestf]
|
99
|
-
}
|
100
|
-
|
101
|
-
# Generate HTML
|
102
|
-
html_tables = <<INFO
|
103
|
-
<h1>Information</h1>\n
|
104
|
-
<table>
|
105
|
-
<tr><td>Genotype class</td><td>#{genotype_class}</td></tr>
|
106
|
-
<tr><td>Population size</td><td>#{pop_size}</td></tr>
|
107
|
-
<tr><td># of generations per run</td><td>#{gens}</td></tr>
|
108
|
-
<tr><td>Number of tests </td><td>#{tries.size}</td></tr>
|
109
|
-
<tr><td>Tests repeated </td><td>#{rep} times</td></tr>
|
110
|
-
<tr><td>Number of runs </td><td>#{tries.size*rep}</td></tr>
|
111
|
-
<tr><td>Total number of generations </td><td>#{tries.size*rep*gens}</td></tr>
|
112
|
-
<tr><td>Total time</td><td>#{'%.2f' % (Time.now-start)} seconds</td></tr>
|
113
|
-
</table
|
114
|
-
INFO
|
115
|
-
|
116
|
-
# - combined stats
|
117
|
-
joined = data.map(&:last).flatten
|
118
|
-
avg_time = data.map{|r| r[-2] }.average
|
119
|
-
|
120
|
-
html_tables << "<h1>Stats for all</h1>"
|
121
|
-
html_tables << [["All"] + (joined.stats << avg_time).map{|f| '%.5f' % f } ].to_table(%w[. min max avg stddev avg-time]).to_html
|
122
|
-
# - stats grouped by selection, crossover, mutation methods
|
123
|
-
["selection","crossover","mutation"].each_with_index{|title,i|
|
124
|
-
data_grp = data.group_by{|x|x[i]}.map{|k,a|
|
125
|
-
joined = a.map(&:last).flatten
|
126
|
-
avg_time = a.map{|r| r[-2] }.average
|
127
|
-
[k] + (joined.stats << avg_time).map{|f| '%.5f' % f }
|
128
|
-
}.sort_by{|row| -row[-3].to_f } # sort by average
|
129
|
-
html_tables << "<h1>Stats for #{title}</h1>"
|
130
|
-
html_tables << data_grp.to_table([title]+%w[min max avg stddev avg-time]).to_html
|
131
|
-
}
|
132
|
-
# - detailed stats
|
133
|
-
|
134
|
-
tbl = data.map{|s,c,m,t,a| [s,c,m] + (a.stats << t).map{|f| '%.5f' % f } }.sort_by{|row| -row[-3].to_f }.to_table(%w[selection crossover mutation min max avg stddev avg-time])
|
135
|
-
html_tables << '<h1>Raw Stats</h1>' << tbl.to_html
|
136
|
-
# write HTML stats
|
137
|
-
File.open(html_outfile,'w'){|f| f << File.read(File.dirname(__FILE__)+"/../../data/template.html").sub('{{CONTENT}}',html_tables) } if html_outfile
|
138
|
-
|
139
|
-
# write csv file, contains raw stats
|
140
|
-
File.open(csv_outfile,'w'){|f| f << data.map{|r|r[0..2] << r[-1].join(', ') }.to_table.to_csv } if csv_outfile
|
141
|
-
|
142
|
-
puts '',tbl.to_s
|
143
|
-
data
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
# Used in the Population#benchmark function.
|
151
|
-
class StrategiesDSL
|
152
|
-
# Number of generations run in each test.
|
153
|
-
attr_accessor :generations
|
154
|
-
# Population size used.
|
155
|
-
attr_accessor :population_size
|
156
|
-
# Number of times all tests are run. Default=10. Increase for more accuracy on the benchmark.
|
157
|
-
attr_accessor:repeat
|
158
|
-
|
159
|
-
def initialize
|
160
|
-
@repeat = 10
|
161
|
-
@population_size = 20
|
162
|
-
@generations = 50
|
163
|
-
end
|
164
|
-
|
165
|
-
# Pass several modules to this to test these selection methods.
|
166
|
-
def selection(*s); @s=s; end
|
167
|
-
# Pass several modules to this to test these crossover methods.
|
168
|
-
def crossover(*c); @c=c; end
|
169
|
-
# Pass several modules to this to test these mutation methods.
|
170
|
-
def mutator(*m) ; @m=m; end
|
171
|
-
alias :mutation :mutator
|
172
|
-
# Get all the tests. Basically a cartesian product of all selection, crossover and mutation methods.
|
173
|
-
def get_tests
|
174
|
-
t = []
|
175
|
-
|
176
|
-
defmod = Module.new{self.name='default'}
|
177
|
-
@s ||= [defmod]
|
178
|
-
@c ||= [defmod]
|
179
|
-
@m ||= [defmod]
|
180
|
-
|
181
|
-
#raise 'No selection modules defined' unless @s
|
182
|
-
#raise 'No crossover modules defined' unless @c
|
183
|
-
#raise 'No mutation modules defined' unless @m
|
184
|
-
@s.each{|s|
|
185
|
-
@c.each{|c|
|
186
|
-
@m.each{|m|
|
187
|
-
t << [s,c,m]
|
188
|
-
}
|
189
|
-
}
|
190
|
-
}
|
191
|
-
t
|
192
|
-
end
|
193
83
|
end
|
194
84
|
|
data/lib/charlie/selection.rb
CHANGED
@@ -94,6 +94,7 @@ end
|
|
94
94
|
|
95
95
|
ScaledRouletteSelection = ScaledRouletteSelection()
|
96
96
|
|
97
|
+
|
97
98
|
# Generates a selection module with elitism from a normal selection module.
|
98
99
|
# Elitism is saving the best +elite_n+ individuals each generation, to ensure the best solutions are never lost.
|
99
100
|
def Elitism(sel_module,elite_n=1)
|
data/lib/charlie/tree/tree.rb
CHANGED
@@ -18,7 +18,15 @@ module GPTreeHelper
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def tree_depth(t)
|
22
|
+
if t.first==:term
|
23
|
+
0
|
24
|
+
else
|
25
|
+
1 + t[1..-1].map{|st| tree_depth(st) }.max
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_subtrees(t=@genes)
|
22
30
|
if t.first==:term
|
23
31
|
[t]
|
24
32
|
else
|
@@ -30,6 +38,18 @@ module GPTreeHelper
|
|
30
38
|
all_subtrees(t).at_rand
|
31
39
|
end
|
32
40
|
|
41
|
+
def all_terminals(t=@genes)
|
42
|
+
if t.first==:term
|
43
|
+
[t]
|
44
|
+
else
|
45
|
+
t[1..-1].map{|st| all_terminals(st) }.inject{|a,b|a+b}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def random_terminal(t=@genes)
|
50
|
+
all_terminals(t).at_rand
|
51
|
+
end
|
52
|
+
|
33
53
|
def eval_tree(tree,values_hash)
|
34
54
|
if tree.first == :term
|
35
55
|
termval = tree[1]
|
@@ -47,17 +67,21 @@ module GPTreeHelper
|
|
47
67
|
end
|
48
68
|
|
49
69
|
# Tree genotype, for genetic programming etc.
|
50
|
-
# * Pass arrays of terminals/
|
70
|
+
# * Pass arrays of terminals/unary operators/binary operators to this function to generate a class.
|
51
71
|
# * terminals can be procs (eval'd on initialization), symbols (replaced by values in calls to eval_genes) or anything else (not changed, so make sure all operators are defined for these)
|
52
72
|
# * This needs more options. Depth of initial trees, etc. Also needs a better mutator.
|
53
|
-
def TreeGenotype(terminals
|
73
|
+
def TreeGenotype(terminals, unary_ops, binary_ops, init_depth = 3, init_type = :half)
|
74
|
+
unary_ops = nil if unary_ops.empty?
|
54
75
|
Class.new(Genotype) {
|
55
|
-
|
56
|
-
|
57
|
-
|
76
|
+
|
77
|
+
define_method(:unary_ops) {unary_ops }
|
78
|
+
define_method(:binary_ops) {binary_ops}
|
79
|
+
define_method(:terminals) {terminals }
|
80
|
+
define_method(:init_depth) {init_depth}
|
81
|
+
define_method(:init_type) {init_type }
|
58
82
|
|
59
83
|
def initialize
|
60
|
-
self.genes =
|
84
|
+
self.genes = generate_random_tree(init_depth,init_type)
|
61
85
|
end
|
62
86
|
|
63
87
|
def genes=(g)
|
@@ -73,15 +97,48 @@ def TreeGenotype(terminals = [proc{rand},:x], binary_ops = [:+,:*,:-,:/], unary_
|
|
73
97
|
tree_size(@genes)
|
74
98
|
end
|
75
99
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
100
|
+
def depth
|
101
|
+
tree_depth(@genes)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Generates a random tree.
|
106
|
+
# * type = grow uses generate_random_tree_grow
|
107
|
+
# * type = full uses generate_random_tree_full
|
108
|
+
# * type = half uses one of them, with 50% probability each.
|
109
|
+
def generate_random_tree(depth, type=:half)
|
110
|
+
if type==:full || ( type == :half && rand < 0.5 )
|
111
|
+
generate_random_tree_full(depth)
|
112
|
+
else
|
113
|
+
generate_random_tree_grow(depth,true)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Generates a random tree of a certain maximum depth.
|
118
|
+
# * <tt>no_term=true</tt> makes sure the root node is a function.
|
119
|
+
def generate_random_tree_grow(depth,no_term=nil)
|
120
|
+
if depth.zero? || (rand(3).zero? && !no_term)
|
121
|
+
e = terminals.at_rand
|
79
122
|
[:term, e.is_a?(Proc) ? e.call : e]
|
80
123
|
else
|
81
|
-
if
|
82
|
-
[
|
124
|
+
if unary_ops.nil? || rand(2).zero?
|
125
|
+
[binary_ops.at_rand,generate_random_tree_grow(depth-1),generate_random_tree_grow(depth-1)]
|
83
126
|
else
|
84
|
-
[
|
127
|
+
[unary_ops.at_rand,generate_random_tree_grow(depth-1)]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Generates a random tree, with all terminals at 'depth'.
|
133
|
+
def generate_random_tree_full(depth)
|
134
|
+
if depth.zero?
|
135
|
+
e = terminals.at_rand
|
136
|
+
[:term, e.is_a?(Proc) ? e.call : e]
|
137
|
+
else
|
138
|
+
if unary_ops.nil? || rand(2).zero?
|
139
|
+
[binary_ops.at_rand,generate_random_tree_full(depth-1),generate_random_tree_full(depth-1)]
|
140
|
+
else
|
141
|
+
[unary_ops.at_rand,generate_random_tree_full(depth-1)]
|
85
142
|
end
|
86
143
|
end
|
87
144
|
end
|
@@ -94,7 +151,7 @@ def TreeGenotype(terminals = [proc{rand},:x], binary_ops = [:+,:*,:-,:/], unary_
|
|
94
151
|
@genes.inspect
|
95
152
|
end
|
96
153
|
|
97
|
-
use PCross(0.7,TreeCrossover), PMutate(0.5,
|
154
|
+
use PCross(0.7,TreeCrossover), PMutate(0.5,TreeReplaceMutator) # TODO: test what options are best -- benchmark show that these are ok for simple settings.
|
98
155
|
|
99
156
|
# make helper functions available at both class and instance level
|
100
157
|
include GPTreeHelper
|
@@ -118,11 +175,116 @@ module TreeCrossover
|
|
118
175
|
end
|
119
176
|
end
|
120
177
|
|
121
|
-
#
|
122
|
-
|
178
|
+
# TreeReplaceMutator replaces a randomly chosen subtree with a new, randomly generated, subtree.
|
179
|
+
# * depth and type are arguments for TreeGenotype#generate_random_tree
|
180
|
+
# * depth == [1,2,3,..] or depth==(1..3) uses one of the elements in the range for the depth.
|
181
|
+
# * depth == :same to use the depth of the replaced subtree.
|
182
|
+
# * depth == :same[min,max] for depth of the replaced subtree plus a random offset between min and max.
|
183
|
+
def TreeReplaceMutator(depth=2,type=:half)
|
184
|
+
Module.new{
|
185
|
+
self.name = "TreeReplaceMutator(#{depth.inspect},#{type.inspect})"
|
186
|
+
if depth.is_a? Numeric
|
187
|
+
define_method(:mutate!) {
|
188
|
+
random_subtree.replace generate_random_tree(depth,type)
|
189
|
+
self
|
190
|
+
}
|
191
|
+
elsif depth==:same || (depth.is_a?(Array) && depth[0]==:same)
|
192
|
+
s, dd_min, dd_max = *depth
|
193
|
+
possible_deltas = (dd_min||0..dd_max||0).to_a
|
194
|
+
define_method(:mutate!) {
|
195
|
+
st = random_subtree
|
196
|
+
st.replace generate_random_tree([tree_depth(st) + possible_deltas.at_rand,0].max, type)
|
197
|
+
self
|
198
|
+
}
|
199
|
+
elsif depth.respond_to?(:to_a)
|
200
|
+
possible_depths = depth.to_a
|
201
|
+
define_method(:mutate!) {
|
202
|
+
random_subtree.replace generate_random_tree(possible_depths.at_rand,type)
|
203
|
+
self
|
204
|
+
}
|
205
|
+
else
|
206
|
+
raise ArgumentError, "invalid option for depth"
|
207
|
+
end
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
211
|
+
TreeReplaceMutator = TreeReplaceMutator()
|
212
|
+
TreePruneMutator = TreeReplaceMutator(0)
|
213
|
+
|
214
|
+
# replace root by one of its children, i.e. TreeRemoveNodeMutator with the root instead of a random node.
|
215
|
+
module TreeChopMutator
|
216
|
+
def mutate!
|
217
|
+
return self if genes.first==:term
|
218
|
+
genes.replace genes[1..-1].at_rand # replace root with child
|
219
|
+
self
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# replaces a random node by one of its children. does nothing if the randomly chosen node is a terminal.
|
224
|
+
module TreeRemoveNodeMutator
|
123
225
|
def mutate!
|
124
|
-
random_subtree
|
226
|
+
st = random_subtree
|
227
|
+
return self if st.first==:term
|
228
|
+
st.replace st[1..-1].at_rand
|
125
229
|
self
|
126
230
|
end
|
127
231
|
end
|
128
232
|
|
233
|
+
# replaces a random node x by a new operator node having x as one of its children. When inserting a binary operator the other node will be a terminal.
|
234
|
+
module TreeInsertNodeMutator
|
235
|
+
def mutate!
|
236
|
+
st = random_subtree
|
237
|
+
if rand < 0.5 || unary_ops.nil?
|
238
|
+
st.replace [binary_ops.at_rand, st.dup, generate_random_tree_full(0)]
|
239
|
+
else
|
240
|
+
st.replace [unary_ops.at_rand, st.dup]
|
241
|
+
end
|
242
|
+
self
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# replaces a random terminal by a new one.
|
247
|
+
module TreeTerminalMutator
|
248
|
+
def mutate!
|
249
|
+
random_terminal.replace generate_random_tree(0)
|
250
|
+
self
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# mutates a random numeric terminal using a point mutator (cf. ListMutator) or a block (e.g. {|x| x-rand+0.5}
|
255
|
+
def TreeNumTerminalMutator(mutate=:uniform[0.1], &b)
|
256
|
+
if block_given?
|
257
|
+
mutate_proc = b
|
258
|
+
else
|
259
|
+
mut_name, *mut_arg = mutate
|
260
|
+
mut_fn = PointMutators[mut_name]
|
261
|
+
mutate_proc = proc{|x| mut_fn.call(x,*mut_arg) }
|
262
|
+
end
|
263
|
+
|
264
|
+
Module.new{
|
265
|
+
self.name = "TreeNumTerminalMutator(#{mutate.inspect})"
|
266
|
+
define_method(:mutate!) {
|
267
|
+
numterms = all_terminals.select{|x| x[1].is_a? Numeric }
|
268
|
+
unless numterms.empty?
|
269
|
+
random_term = numterms.at_rand
|
270
|
+
random_term[1] = mutate_proc.call(random_term[1])
|
271
|
+
end
|
272
|
+
self
|
273
|
+
}
|
274
|
+
}
|
275
|
+
end
|
276
|
+
TreeNumTerminalMutator = TreeNumTerminalMutator()
|
277
|
+
|
278
|
+
# Replaces a random subtree by the result of its evaluation. value_hash is passed to eval_tree.
|
279
|
+
def TreeEvalMutator(value_hash=Hash.new{0})
|
280
|
+
Module.new{
|
281
|
+
define_method(:mutate!) {
|
282
|
+
st = random_subtree
|
283
|
+
st.replace [:term,eval_tree(st,value_hash)]
|
284
|
+
self
|
285
|
+
}
|
286
|
+
}
|
287
|
+
end
|
288
|
+
TreeEvalMutator = TreeEvalMutator()
|
289
|
+
|
290
|
+
|
data/test/t_common.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require File.dirname(__FILE__) + '/../lib/charlie' unless Object.const_defined?('Charlie')
|
4
4
|
require 'test/unit'
|
5
5
|
|
6
|
-
$crs_mth = [NullCrossover, SinglePointCrossover, UniformCrossover]
|
6
|
+
$crs_mth = [NullCrossover, SinglePointCrossover, UniformCrossover, NPointCrossover(1), NPointCrossover(2), NPointCrossover(24)]
|
7
7
|
|
8
8
|
|
9
9
|
class TestProblem < FloatListGenotype(2,0..1)
|
data/test/test_benchmark.rb
CHANGED
@@ -10,7 +10,7 @@ class BMTest < Test::Unit::TestCase
|
|
10
10
|
|
11
11
|
def test_basic_bm
|
12
12
|
assert_nothing_raised {
|
13
|
-
|
13
|
+
GABenchmark.benchmark(RoyalRoad,CDIR+'/output/test_benchmark.html'){
|
14
14
|
selection TruncationSelection(0.2), Elitism(ScaledRouletteSelection(),1)
|
15
15
|
crossover NullCrossover, UniformCrossover
|
16
16
|
mutator ListMutator(:single_point,:flip), ListMutator(:expected_n[4],:flip)
|
@@ -25,7 +25,7 @@ class BMTest < Test::Unit::TestCase
|
|
25
25
|
|
26
26
|
def test_csv
|
27
27
|
assert_nothing_raised {
|
28
|
-
|
28
|
+
GABenchmark.benchmark(StringA,nil,CDIR+'/output/test_benchmark.csv'){
|
29
29
|
selection TruncationSelection(0.2)
|
30
30
|
crossover NullCrossover, Module.new{self.name='default'}
|
31
31
|
mutation ListMutator(:expected_n[1],:replace[*'a'..'d']), NullMutator
|
@@ -36,23 +36,37 @@ class BMTest < Test::Unit::TestCase
|
|
36
36
|
|
37
37
|
def test_defaults
|
38
38
|
assert_nothing_raised {
|
39
|
-
r =
|
39
|
+
r = GABenchmark.benchmark(StringA,nil,nil){
|
40
40
|
selection TruncationSelection(0.2), TournamentSelection(3)
|
41
41
|
# no crossover, etc specified.
|
42
42
|
}
|
43
43
|
assert_equal 2,r.size
|
44
44
|
}
|
45
45
|
assert_nothing_raised {
|
46
|
-
r=
|
46
|
+
r=GABenchmark.benchmark(StringA,nil,nil){
|
47
47
|
crossover NullCrossover
|
48
48
|
}
|
49
49
|
assert_equal 1,r.size
|
50
50
|
}
|
51
51
|
assert_nothing_raised {
|
52
|
-
r=
|
52
|
+
r=GABenchmark.benchmark(StringA,nil,nil){
|
53
53
|
}
|
54
54
|
assert_equal 1,r.size
|
55
55
|
}
|
56
56
|
end
|
57
57
|
|
58
|
+
|
59
|
+
def test_multiple_stats
|
60
|
+
d = nil
|
61
|
+
assert_nothing_raised {
|
62
|
+
d = GABenchmark.benchmark(StringA,nil,nil){
|
63
|
+
track_stats{|b| [0,1] }
|
64
|
+
repeat 11
|
65
|
+
generations 17
|
66
|
+
}
|
67
|
+
}
|
68
|
+
assert_equal 1,d.size
|
69
|
+
assert_equal 11,d[0][-1].size
|
70
|
+
assert d.all?{|r| r[-1].all?{|e| e.size==2 } }
|
71
|
+
end
|
58
72
|
end
|