charlie 0.7.1 → 0.8.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 +10 -0
- data/Manifest.txt +6 -1
- data/README.txt +1 -1
- data/Rakefile +1 -1
- data/TODO.txt +2 -7
- data/data/BENCHMARK +4 -0
- data/data/CROSSOVER +10 -2
- data/data/GENOTYPE +23 -3
- data/data/MUTATION +15 -3
- data/data/SELECTION +15 -8
- data/examples/EXAMPLES_README.txt +58 -37
- data/examples/coevolution.rb +42 -1
- data/examples/matrix.rb +49 -0
- data/examples/neural.rb +20 -0
- data/examples/output/function_optimization_sombrero.html +1518 -1518
- data/examples/output/tsp.html +551 -551
- data/examples/string.rb +3 -0
- data/examples/tree.rb +15 -1
- data/examples/tsp.rb +1 -2
- data/lib/charlie.rb +5 -1
- data/lib/charlie/crossover.rb +1 -0
- data/lib/charlie/etc/monkey.rb +5 -4
- data/lib/charlie/gabenchmark.rb +24 -3
- data/lib/charlie/genotype.rb +19 -2
- data/lib/charlie/list/list_crossover.rb +40 -0
- data/lib/charlie/list/list_genotype.rb +6 -2
- data/lib/charlie/list/matrix.rb +121 -0
- data/lib/charlie/list/neural.rb +52 -0
- data/lib/charlie/mutate.rb +1 -0
- data/lib/charlie/population.rb +84 -21
- data/lib/charlie/selection.rb +88 -30
- data/lib/charlie/tree/tree.rb +1 -0
- data/test/t_common.rb +1 -1
- data/test/test_benchmark.rb +18 -0
- data/test/test_cross.rb +20 -0
- data/test/test_evolve.rb +25 -1
- data/test/test_matrix.rb +36 -0
- data/test/test_neural.rb +24 -0
- data/test/test_sel.rb +36 -0
- metadata +10 -3
- data/examples/output/bitstring_royalroad.html +0 -1243
data/lib/charlie/mutate.rb
CHANGED
@@ -8,6 +8,7 @@ end
|
|
8
8
|
|
9
9
|
# Takes mutator m1 with probability p, and mutator m2 with probability 1-p
|
10
10
|
def PMutate(p,m1,m2=NullMutator)
|
11
|
+
raise ArgumentError, "first argument to PMutate should be numeric (probability)." unless p.is_a?(Numeric)
|
11
12
|
return m1 if m1==m2
|
12
13
|
m1_name, m2_name = [m1,m2].map{|c| '_mutate_' + c.to_s.gsub(/[^A-Za-z0-9]/,'') + '!' }
|
13
14
|
Module.new{
|
data/lib/charlie/population.rb
CHANGED
@@ -3,34 +3,34 @@
|
|
3
3
|
|
4
4
|
# The population class represents an array of genotypes.
|
5
5
|
# Create an instance of this, and call one of the evolve functions to run the genetic algorithm.
|
6
|
-
class Population
|
6
|
+
class Population < Array
|
7
7
|
DEFAULT_MAX_GENS = 100
|
8
|
-
|
9
|
-
attr_reader :
|
10
|
-
def initialize(genotype_class,population_size=
|
11
|
-
@size = population_size
|
8
|
+
DEFAULT_POP_SIZE = 20
|
9
|
+
attr_reader :genotype_class
|
10
|
+
def initialize(genotype_class,population_size=DEFAULT_POP_SIZE)
|
12
11
|
@genotype_class = genotype_class
|
13
|
-
|
12
|
+
replace Array.new(population_size){ genotype_class.new }
|
14
13
|
end
|
15
14
|
|
16
15
|
# yields population and generation number to block each generation, for a maximum of max_generations.
|
17
16
|
def evolve_block(max_generations=DEFAULT_MAX_GENS)
|
18
|
-
yield
|
17
|
+
yield self, 0
|
19
18
|
(max_generations || DEFAULT_MAX_GENS).times {|generation|
|
20
|
-
|
19
|
+
self.population = @genotype_class.next_generation(self){|*parents|
|
21
20
|
ch = [*@genotype_class.cross(*parents)]
|
22
21
|
ch.each{|c| c.mutate! }
|
23
22
|
ch
|
24
23
|
}
|
25
|
-
yield
|
24
|
+
yield self, generation+1
|
26
25
|
}
|
27
|
-
|
26
|
+
self
|
28
27
|
end
|
29
28
|
|
30
29
|
# Runs the genetic algorithm without any output. Returns the population sorted by fitness (unsorted for co-evolution).
|
31
30
|
def evolve_silent(generations=DEFAULT_MAX_GENS)
|
32
31
|
evolve_block(generations){}
|
33
|
-
|
32
|
+
sort_by!{|x|x.fitness} rescue nil
|
33
|
+
self
|
34
34
|
end
|
35
35
|
|
36
36
|
# Runs the genetic algorithm with some stats on the console. Returns the population sorted by fitness.
|
@@ -40,9 +40,9 @@ class Population
|
|
40
40
|
best = p.max
|
41
41
|
puts "#{g}\t\t#{best.fitness}\t\t#{best.to_s}"
|
42
42
|
}
|
43
|
-
|
44
|
-
puts "Finished: Best fitness = #{
|
45
|
-
|
43
|
+
self.population = self.sort_by{|x|x.fitness}
|
44
|
+
puts "Finished: Best fitness = #{self[-1].fitness}"
|
45
|
+
self
|
46
46
|
end
|
47
47
|
alias :evolve :evolve_on_console
|
48
48
|
|
@@ -56,7 +56,7 @@ class Population
|
|
56
56
|
break
|
57
57
|
end
|
58
58
|
}
|
59
|
-
[
|
59
|
+
[self, tot_gens]
|
60
60
|
end
|
61
61
|
|
62
62
|
# breaks if the block (which is passed the best individual each "check_every" generations) returns true.
|
@@ -69,16 +69,79 @@ class Population
|
|
69
69
|
break
|
70
70
|
end
|
71
71
|
}
|
72
|
-
[
|
72
|
+
[self, tot_gens]
|
73
73
|
end
|
74
74
|
alias :evolve_until :evolve_until_best
|
75
|
+
alias :best :max
|
75
76
|
|
77
|
+
# backwards compatibility, returns self
|
78
|
+
def population; self; end
|
79
|
+
# backwards compatibility, replaces population array
|
80
|
+
alias :population= :replace
|
76
81
|
|
77
|
-
# accessors for population
|
78
|
-
[:[],:max,:min].each{|m|
|
79
|
-
define_method(m){|*args| population.send(m,*args) }
|
80
|
-
}
|
81
|
-
alias :best :max
|
82
82
|
|
83
|
+
# Effectively runs Population#evolve_block <tt>n_runs</tt> times.
|
84
|
+
# Returns the entire population of all results (<tt>n_runs * population_size</tt> elements)
|
85
|
+
def self.evolve_multiple(genotype_class,population_size=DEFAULT_POP_SIZE,
|
86
|
+
n_runs=25,
|
87
|
+
generations=DEFAULT_MAX_GENS)
|
88
|
+
r = evolve_multiple_until_population(genotype_class,population_size, n_runs, generations, 10000) {|pop| false }
|
89
|
+
r.first
|
90
|
+
end
|
91
|
+
|
92
|
+
# Effectively runs Population#evolve_until_best multiple times.
|
93
|
+
# * See Population.evolve_multiple_until_population for arguments
|
94
|
+
def self.evolve_multiple_until_best(*args,&b)
|
95
|
+
evolve_multiple_until_population(*args) {|pop| b.call(pop.max) }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Runs Population#evolve_until_population multiple times.
|
99
|
+
# * genotype_class,population_size are arguments for Population.new
|
100
|
+
# * max_tries is the maximum number of restarts.
|
101
|
+
# * Returns [population, generations needed] on success where population is the population of the successful run.
|
102
|
+
# * Returns [population_all, nil] on failure, where population_all is the combined population of all runs.
|
103
|
+
def self.evolve_multiple_until_population(genotype_class,population_size=DEFAULT_POP_SIZE,
|
104
|
+
max_tries=25,
|
105
|
+
generations=DEFAULT_MAX_GENS,check_every=10)
|
106
|
+
tot_gens = 0
|
107
|
+
all_pop = []
|
108
|
+
max_tries.times{
|
109
|
+
pop, gens = Population.new(genotype_class,population_size).evolve_until_population(generations,check_every){|p|
|
110
|
+
yield(p)
|
111
|
+
}
|
112
|
+
all_pop += pop
|
113
|
+
tot_gens += gens || generations
|
114
|
+
if gens
|
115
|
+
return [pop, tot_gens]
|
116
|
+
end
|
117
|
+
}
|
118
|
+
[all_pop, nil]
|
119
|
+
end
|
120
|
+
class << self; alias :evolve_multiple_until :evolve_multiple_until_best; end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# Uses GP-style evolution (i.e. crossover OR mutation instead of crossover AND mutation) with a selection method.
|
126
|
+
# * Works by discarding and replacing the block given to next_generation in Population#evolve_block
|
127
|
+
# * Is not mandatory for GP, and can just as easily be used for GA or not used in GP.
|
128
|
+
# * Bit of a strange place to do this, but works nicely with the benchmarking
|
129
|
+
def GP(sel_module,crossover_probability=0.75)
|
130
|
+
ng_name = sel_module.to_s.gsub(/[^A-Za-z0-9]/,'_')
|
131
|
+
Module.new{
|
132
|
+
include sel_module.dup
|
133
|
+
alias_method ng_name, :next_generation
|
134
|
+
define_method(:next_generation){|population|
|
135
|
+
send(ng_name,population){|*parents|
|
136
|
+
if rand < crossover_probability
|
137
|
+
[*parents[0].class.cross(*parents)]
|
138
|
+
else
|
139
|
+
parents.map{|c| c.mutate }
|
140
|
+
end
|
141
|
+
}
|
142
|
+
}
|
143
|
+
self.name= "GP(#{sel_module.to_s},#{crossover_probability})"
|
144
|
+
}
|
83
145
|
end
|
84
146
|
|
147
|
+
|
data/lib/charlie/selection.rb
CHANGED
@@ -37,28 +37,44 @@ def TruncationSelection(best=0.3)
|
|
37
37
|
}
|
38
38
|
end
|
39
39
|
|
40
|
+
TruncationSelection = TruncationSelection()
|
41
|
+
|
40
42
|
# This selection algorithm is basically randomized hill climbing.
|
41
43
|
BestOnlySelection = TruncationSelection(1)
|
42
44
|
|
45
|
+
|
43
46
|
# Roulette selection without replacement. Probability of individual i being selected is fitness(i) / sum fitness(1..population size)
|
44
47
|
module RouletteSelection
|
45
48
|
def next_generation(population)
|
46
49
|
partial_sum = []
|
47
|
-
sum =
|
50
|
+
sum = 0
|
51
|
+
population.each{|e| partial_sum << (sum += e.fitness) }
|
48
52
|
|
53
|
+
n = population.size
|
49
54
|
new_pop = []
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
i = [0,0]
|
56
|
+
while new_pop.size < n
|
57
|
+
i[0] = i[1]
|
58
|
+
until i[0]!=i[1]
|
59
|
+
i.map!{ # binary search
|
60
|
+
r = rand * sum
|
61
|
+
l = 0; u = n-1;
|
62
|
+
while l!=u
|
63
|
+
m = (l+u)/2
|
64
|
+
if partial_sum[m] < r
|
65
|
+
l = m+1
|
66
|
+
else
|
67
|
+
u = m
|
68
|
+
end
|
69
|
+
end
|
70
|
+
l
|
71
|
+
}
|
72
|
+
end
|
73
|
+
new_pop += yield(population[i[0]],population[i[1]])
|
57
74
|
end
|
58
75
|
new_pop.pop until new_pop.size == population.size
|
59
76
|
new_pop
|
60
77
|
end
|
61
|
-
|
62
78
|
end
|
63
79
|
|
64
80
|
# Scaled Roulette selection without replacement.
|
@@ -78,16 +94,20 @@ def ScaledRouletteSelection(&block)
|
|
78
94
|
@@index = []
|
79
95
|
(0...population.size).map(&@@block).each_with_index{|e,i| @@index += Array.new(e.round,i) }
|
80
96
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
97
|
+
population = population.sort_by(&:fitness)
|
98
|
+
|
99
|
+
new_pop = []
|
100
|
+
index = @@index
|
101
|
+
while new_pop.size < population.size
|
102
|
+
i1 = index.at_rand
|
103
|
+
i2 = index.at_rand
|
104
|
+
if i1==i2
|
105
|
+
i1,i2 = @@index.at_rand, @@index.at_rand until i1!=i2 # no replacement
|
106
|
+
end
|
107
|
+
new_pop += yield(population[i1],population[i2])
|
108
|
+
end
|
109
|
+
new_pop.pop until new_pop.size == population.size
|
110
|
+
new_pop
|
91
111
|
end
|
92
112
|
|
93
113
|
self.name= "ScaledRouletteSelection[#{(0..3).map(&block).map(&:to_s).join(',')},...]"
|
@@ -95,7 +115,7 @@ def ScaledRouletteSelection(&block)
|
|
95
115
|
end
|
96
116
|
|
97
117
|
ScaledRouletteSelection = ScaledRouletteSelection()
|
98
|
-
|
118
|
+
RankSelection = ScaledRouletteSelection()
|
99
119
|
|
100
120
|
# Generates a selection module with elitism from a normal selection module.
|
101
121
|
# Elitism is saving the best +elite_n+ individuals each generation, to ensure the best solutions are never lost.
|
@@ -121,18 +141,16 @@ end
|
|
121
141
|
# Default: select the 2 individuals with the highest fitness out of a random population with size group_size
|
122
142
|
# and replaces the others with offspring of these 2.
|
123
143
|
# Does this n_times. n_times==nil takes population size / (group_size-2) , i.e. about the same number of new individuals as roulette selection etc.
|
124
|
-
def TournamentSelection(group_size=4,n_times=nil)
|
144
|
+
def TournamentSelection(group_size=4,n_times=nil)
|
125
145
|
Module::new{
|
126
146
|
@@group_size = group_size
|
127
147
|
@@n_times = n_times
|
128
148
|
def next_generation(population)
|
129
|
-
|
130
|
-
@@n_times
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end while ix.uniq.size != @@group_size
|
135
|
-
ix=ix.sort_by{|i| population[i].fitness }
|
149
|
+
psz = population.size
|
150
|
+
n_times = @@n_times || (psz / (@@group_size-2))
|
151
|
+
n_times.times{
|
152
|
+
population.shuffle!
|
153
|
+
ix = (0...@@group_size).sort_by{|i| population[i].fitness }
|
136
154
|
p1,p2 = population[ix[-1]],population[ix[-2]]
|
137
155
|
nw = [];
|
138
156
|
nw += yield(p1,p2) while nw.size < @@group_size-2
|
@@ -146,8 +164,7 @@ end
|
|
146
164
|
TournamentSelection = TournamentSelection()
|
147
165
|
|
148
166
|
|
149
|
-
|
150
|
-
# Direct competition (gladiatorial) selection
|
167
|
+
# Co-evolution: Direct competition (gladiatorial) selection. Define a Genotype#fight function to use this
|
151
168
|
module GladiatorialSelection
|
152
169
|
def next_generation(population)
|
153
170
|
(population.size/2).times{
|
@@ -163,3 +180,44 @@ module GladiatorialSelection
|
|
163
180
|
end
|
164
181
|
end
|
165
182
|
|
183
|
+
# Co-evolution: competition in tournaments selection.
|
184
|
+
# * Define a fight_points(other) function in your genotype to use this. The function should return [points for self,points for other]
|
185
|
+
# * Point-proportial selection is used within tournaments. Entire groups are replaced.
|
186
|
+
# * full_tournament = true calls both fight_points(population[i],population[j]) AND fight_points(population[j],population[i]) instead of only i < j
|
187
|
+
# Does this n_times. n_times==nil takes population size / group_size , i.e. about the same number of new individuals as roulette selection etc.
|
188
|
+
def CoTournamentSelection(group_size=4,full_tournament=false,n_times=nil)
|
189
|
+
Module::new{
|
190
|
+
@@group_size = group_size
|
191
|
+
@@full_tournament = full_tournament
|
192
|
+
@@n_times = n_times
|
193
|
+
def next_generation(population)
|
194
|
+
psz = population.size
|
195
|
+
n_times = @@n_times || (psz / @@group_size) #(psz / (@@group_size-2))
|
196
|
+
n_times.times{
|
197
|
+
population.shuffle!
|
198
|
+
points = Array.new(@@group_size,0.0)
|
199
|
+
for i in 0...@@group_size
|
200
|
+
for j in 0...@@group_size
|
201
|
+
next if j==i || (i > j && !@@full_tournament)
|
202
|
+
r = population[i].fight_points(population[j])
|
203
|
+
points[i] += r[0]
|
204
|
+
points[j] += r[1]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
# points-proportional selection, with replacement b.c. 1 individual with 100% of the points is not implausible.
|
208
|
+
|
209
|
+
partial_sum = []; sum = 0; points.each{|p| partial_sum << (sum += p) }
|
210
|
+
partial_sum.map!{|x|x+1} if sum.abs < 1e-10 # sum==0 -> random
|
211
|
+
newgroup = [];
|
212
|
+
(@@group_size / 2.0).ceil.times{
|
213
|
+
sel_ix = [0,0].map{ r=rand*sum; partial_sum.find_index{|ps| ps >= r } }
|
214
|
+
newgroup += yield(population[sel_ix[0]],population[sel_ix[1]])
|
215
|
+
}
|
216
|
+
population[0...@@group_size] = newgroup[0...@@group_size]
|
217
|
+
}
|
218
|
+
population
|
219
|
+
end
|
220
|
+
self.name= "CoTournamentSelection(#{group_size},#{full_tournament},#{n_times.inspect})"
|
221
|
+
}
|
222
|
+
end
|
223
|
+
CoTournamentSelection = CoTournamentSelection()
|
data/lib/charlie/tree/tree.rb
CHANGED
@@ -278,6 +278,7 @@ TreeNumTerminalMutator = TreeNumTerminalMutator()
|
|
278
278
|
# Replaces a random subtree by the result of its evaluation. value_hash is passed to eval_tree.
|
279
279
|
def TreeEvalMutator(value_hash=Hash.new{0})
|
280
280
|
Module.new{
|
281
|
+
self.name = "TreeEvalMutator(#{value_hash.inspect})"
|
281
282
|
define_method(:mutate!) {
|
282
283
|
st = random_subtree
|
283
284
|
st.replace [:term,eval_tree(st,value_hash)]
|
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, NPointCrossover(1), NPointCrossover(2), NPointCrossover(24)]
|
6
|
+
$crs_mth = [NullCrossover, SinglePointCrossover, UniformCrossover, NPointCrossover(1), NPointCrossover(2), NPointCrossover(24),BlendingCrossover(0.1,:line),BlendingCrossover(1.3,:cube)]
|
7
7
|
|
8
8
|
|
9
9
|
class TestProblem < FloatListGenotype(2,0..1)
|
data/test/test_benchmark.rb
CHANGED
@@ -69,4 +69,22 @@ class BMTest < Test::Unit::TestCase
|
|
69
69
|
assert_equal 11,d[0][-1].size
|
70
70
|
assert d.all?{|r| r[-1].all?{|e| e.size==2 } }
|
71
71
|
end
|
72
|
+
|
73
|
+
|
74
|
+
def test_setup_teardown
|
75
|
+
d = nil
|
76
|
+
s = t = 0 # count setup/teardown calls/args
|
77
|
+
assert_nothing_raised {
|
78
|
+
d = GABenchmark.benchmark(StringA,nil,nil){
|
79
|
+
selection TruncationSelection, TournamentSelection
|
80
|
+
setup{ s+=1 }
|
81
|
+
teardown{|p| t += p.size }
|
82
|
+
population_size 7
|
83
|
+
repeat 10
|
84
|
+
}
|
85
|
+
}
|
86
|
+
assert_equal 2, d.size
|
87
|
+
assert_equal 10 * 7 * 2,t
|
88
|
+
assert_equal 10 * 2, s
|
89
|
+
end
|
72
90
|
end
|
data/test/test_cross.rb
CHANGED
@@ -8,6 +8,26 @@ class TestCrossover < Test::Unit::TestCase
|
|
8
8
|
}
|
9
9
|
end
|
10
10
|
|
11
|
+
def test_blend
|
12
|
+
klass = FloatListGenotype(100)
|
13
|
+
klass.use BlendingCrossover(0.0)
|
14
|
+
p = Array.new(2){klass.new}
|
15
|
+
p[0].genes.map!{0.0}
|
16
|
+
p[1].genes.map!{1.0}
|
17
|
+
10.times{
|
18
|
+
ch = klass.cross(p[0],p[1])
|
19
|
+
assert ch.all?{|c| c.genes.all?{|g| g > 0.0 && g < 1.0 } }
|
20
|
+
}
|
21
|
+
assert p[0].genes.all?{|g|g==0.0}
|
22
|
+
assert p[1].genes.all?{|g|g==1.0}
|
23
|
+
klass.use BlendingCrossover(0.0,:line)
|
24
|
+
3.times{
|
25
|
+
ch = klass.cross(p[0],p[1])
|
26
|
+
assert ch.all?{|c| gn = c.genes; gn[0] > 0.0 && gn[0] < 1.0 && gn.all?{|g| g==gn[0] } }
|
27
|
+
}
|
28
|
+
assert_raises(ArgumentError) { BlendingCrossover(0.0,:foo) }
|
29
|
+
end
|
30
|
+
|
11
31
|
def test_singlechild
|
12
32
|
$crs_mth.map{|c| SingleChild(c) }.each{|s|
|
13
33
|
klass = TestClass(s)
|
data/test/test_evolve.rb
CHANGED
@@ -91,10 +91,34 @@ class EvolveTest < Test::Unit::TestCase
|
|
91
91
|
end
|
92
92
|
|
93
93
|
|
94
|
-
def test_acc # test accessors
|
94
|
+
def test_acc # test accessors. backward compatibility
|
95
95
|
p = Population.new(RRTest,10)
|
96
96
|
assert_equal p.max, p.population.max
|
97
97
|
assert_equal p.max, p.best
|
98
98
|
assert_equal p[6], p.population[6]
|
99
99
|
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
def test_multiple_until_best # test evolve_multiple_until
|
104
|
+
r,g = Population.evolve_multiple_until(RRTest,5,5){false}
|
105
|
+
assert_equal 5*5, r.size
|
106
|
+
assert_nil g
|
107
|
+
|
108
|
+
r,g = Population.evolve_multiple_until(RRTest,10,500){|b| b.fitness == 0}
|
109
|
+
assert_equal 10, r.size
|
110
|
+
assert_not_nil g
|
111
|
+
assert_equal r.max.fitness, 0 # actually converged
|
112
|
+
assert_respond_to r, :[]
|
113
|
+
assert_respond_to r[-1], :fitness
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def test_multiple # test evolve_multiple_until
|
118
|
+
r = Population.evolve_multiple(RRTest,5,5)
|
119
|
+
assert_equal 5*5, r.size
|
120
|
+
assert_respond_to r, :[]
|
121
|
+
assert_respond_to r[-1], :fitness
|
122
|
+
end
|
123
|
+
|
100
124
|
end
|
data/test/test_matrix.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 't_common'
|
2
|
+
|
3
|
+
class BMatrixTest < BitMatrixGenotype(16,4)
|
4
|
+
def fitness
|
5
|
+
genes.flatten.count{|b| b==1 }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class MatrixTests < Test::Unit::TestCase
|
10
|
+
def test_evolve
|
11
|
+
p=nil
|
12
|
+
assert_nothing_raised{
|
13
|
+
p=Population.new(BMatrixTest,20).evolve_silent(20)
|
14
|
+
}
|
15
|
+
assert p.all?{|s| s.fitness > 32 }, "Insufficient fitness gain, can be extremely bad luck but probably a sign of a bug in the dup code"
|
16
|
+
p.each{|s|
|
17
|
+
assert s.genes.is_a?(Array) && s.genes.size == 16
|
18
|
+
assert s.genes[0].is_a?(Array) && s.genes.all?{|a| a.size==4 }
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_listxover
|
23
|
+
p=nil
|
24
|
+
klass = Class.new(BMatrixTest) {use NPointCrossover(3) }
|
25
|
+
assert_nothing_raised{
|
26
|
+
p=Population.new(klass,20).evolve_silent(20)
|
27
|
+
}
|
28
|
+
assert p.all?{|s| s.fitness > 32 }, "Insufficient fitness gain, can be extremely bad luck but probably a sign of a bug in the dup code"
|
29
|
+
p.each{|s|
|
30
|
+
assert s.genes.is_a?(Array) && s.genes.size == 16
|
31
|
+
assert s.genes[0].is_a?(Array) && s.genes.all?{|a| a.size==4 }
|
32
|
+
}
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|