charlie 0.5.0 → 0.6.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.
@@ -22,7 +22,6 @@ end
22
22
 
23
23
  # Transposition mutator for PermutationGenotype
24
24
  module PermutationMutator
25
-
26
25
  # Transposes two elements
27
26
  def mutate!
28
27
  i1, i2 = @genes.rand_index,@genes.rand_index
@@ -31,6 +30,24 @@ module PermutationMutator
31
30
  end
32
31
  end
33
32
 
33
+ # Inversion mutator for PermutationGenotype. May work on other array/string-based genotypes as well, but this is untested.
34
+ # Takes two random indices, and reverses the elements in between (includes possible wrapping if index2 < index1)
35
+ module InversionMutator
36
+ # Inverts parts of the genes
37
+ def mutate!
38
+ i1, i2 = @genes.rand_index,@genes.rand_index
39
+ if i2 >= i1
40
+ @genes[i1..i2] = @genes[i1..i2].reverse unless i1==i2
41
+ else
42
+ reversed = (@genes[i1..-1] + @genes[0..i2]).reverse
43
+ @genes[i1..-1] = reversed.slice!(0,@genes.size-i1)
44
+ @genes[0..i2] = reversed
45
+ end
46
+ self
47
+ end
48
+ end
49
+
50
+
34
51
  # One point partial preservation crossover for PermutationGenotype
35
52
  # Child 1 is identical to parent 1 up to the cross point, and contains the remaining elements in the same order as parent 2.
36
53
  module PermutationCrossover
@@ -44,4 +61,38 @@ module PermutationCrossover
44
61
  end
45
62
 
46
63
 
64
+ # Edge recombination operator (http://en.wikipedia.org/wiki/Edge_recombination_operator)
65
+ # * Useful in permutations representing a path, e.g. travelling salesperson.
66
+ # * Returns a single child.
67
+ # * Permutations of 0...n only, i.e. default second parameter to PermutationGenotype
68
+ # * Rather slow.
69
+ module EdgeRecombinationCrossover
70
+ def cross(parent1,parent2)
71
+ p1, p2 = parent1.genes, parent2.genes
72
+
73
+ nb = Array.new(parent1.size){[]}
74
+ (p1 + p1[0..1]).each_cons(3){|l,m,r| nb[m] += [l,r] } # build neighbour lists
75
+ (p2 + p2[0..1]).each_cons(3){|l,m,r| nb[m] += [l,r] } # build neighbour lists
76
+ nb.map(&:uniq!)
77
+
78
+ n = (rand < 0.5 ? p1.first : p2.first)
79
+ child = [n]
80
+ (nb.size-1).times{
81
+ nb.each{|l| l.delete n unless l==:done } # remove n from the lists
82
+
83
+ if nb[n].empty? # nb[n] empty, pick random next
84
+ nb[n] = :done
85
+ n = (0...nb.size).find_all{|x| nb[x] != :done }.at_rand # no neighbors left, pick random
86
+ else # pick neighbour with minimal degree, random tie-breaking
87
+ min_deg = nb[n].map{|x| nb[x].size }.min
88
+ next_n = nb[n].find_all{|x| nb[x].size == min_deg }.at_rand # pick random neighbor with min. degree
89
+ nb[n] = :done
90
+ n = next_n
91
+ end
92
+ child << n # add new n to path
93
+ }
94
+
95
+ return from_genes(child)
96
+ end
97
+ end
47
98
 
@@ -4,6 +4,8 @@
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
6
  class Population
7
+ DEFAULT_MAX_GENS = 100
8
+
7
9
  attr_reader :size, :population
8
10
  def initialize(genotype_class,population_size=20)
9
11
  @size = population_size
@@ -11,36 +13,66 @@ class Population
11
13
  @population = Array.new(population_size){ genotype_class.new }
12
14
  end
13
15
 
14
- # Runs the genetic algorithm with some stats on the console. Returns the population sorted by fitness.
15
- def evolve_on_console(generations=100)
16
- puts "Generation\tBest Fitness\t\tBest Individual"
17
- generations.times{|g|
18
- best = @population.max
19
- puts "#{g}\t\t#{best.fitness}\t\t#{best.to_s}"
16
+ # yields population and generation number to block each generation, for a maximum of max_generations.
17
+ def evolve_block(max_generations=DEFAULT_MAX_GENS)
18
+ yield @population, 0
19
+ (max_generations || DEFAULT_MAX_GENS).times {|generation|
20
20
  @population = @genotype_class.next_generation(@population){|*parents|
21
21
  ch = [*@genotype_class.cross(*parents)]
22
22
  ch.each{|c| c.mutate! }
23
23
  ch
24
24
  }
25
+ yield @population, generation+1
26
+ }
27
+ @population
28
+ end
29
+
30
+ # Runs the genetic algorithm without any output. Returns the population sorted by fitness (unsorted for co-evolution).
31
+ def evolve_silent(generations=DEFAULT_MAX_GENS)
32
+ evolve_block(generations){}
33
+ @population.sort_by{|x|x.fitness} rescue @population
34
+ end
35
+
36
+ # Runs the genetic algorithm with some stats on the console. Returns the population sorted by fitness.
37
+ def evolve_on_console(generations=DEFAULT_MAX_GENS)
38
+ puts "Generation\tBest Fitness\t\tBest Individual"
39
+ evolve_block(generations) {|p,g|
40
+ best = p.max
41
+ puts "#{g}\t\t#{best.fitness}\t\t#{best.to_s}"
25
42
  }
26
43
  @population = @population.sort_by{|x|x.fitness}
27
44
  puts "Finished: Best fitness = #{@population[-1].fitness}"
28
45
  @population
29
46
  end
47
+ alias :evolve :evolve_on_console
30
48
 
31
- # Runs the genetic algorithm without any output. Returns the population sorted by fitness (unsorted for co-evolution).
32
- def evolve_silent(generations=100)
33
- generations.times{|g|
34
- @population = @genotype_class.next_generation(@population){|*parents|
35
- ch = [*@genotype_class.cross(*parents)]
36
- ch.each{|c| c.mutate! }
37
- ch
38
- }
49
+ # breaks if the block (which is passed the population each generation) returns true.
50
+ # returns an array [population, generations needed]. generations needed==nil for no convergence.
51
+ def evolve_until_population(generations=DEFAULT_MAX_GENS)
52
+ tot_gens = nil
53
+ evolve_block(generations) {|p,g|
54
+ if yield(p)
55
+ tot_gens = g
56
+ break
57
+ end
39
58
  }
40
- @population.sort_by{|x|x.fitness} rescue @population
59
+ [@population, tot_gens]
41
60
  end
42
61
 
43
- alias :evolve :evolve_on_console
62
+ # breaks if the block (which is passed the best individual each generation) returns true.
63
+ # returns an array [population, generations needed]. generations needed==nil for no convergence.
64
+ def evolve_until_best(generations=DEFAULT_MAX_GENS)
65
+ tot_gens = nil
66
+ evolve_block(generations) {|p,g|
67
+ if yield(p.max)
68
+ tot_gens = g
69
+ break
70
+ end
71
+ }
72
+ [@population, tot_gens]
73
+ end
74
+ alias :evolve_until :evolve_until_best
75
+
44
76
 
45
77
  class << self
46
78
 
@@ -105,7 +137,7 @@ INFO
105
137
  File.open(html_outfile,'w'){|f| f << File.read(File.dirname(__FILE__)+"/../../data/template.html").sub('{{CONTENT}}',html_tables) } if html_outfile
106
138
 
107
139
  # write csv file, contains raw stats
108
- File.open(csv_outfile,'w'){|f| f << data.map{|r|r[0..-2] << r[-1].join(', ') }.to_table.to_csv } if csv_outfile
140
+ File.open(csv_outfile,'w'){|f| f << data.map{|r|r[0..2] << r[-1].join(', ') }.to_table.to_csv } if csv_outfile
109
141
 
110
142
  puts '',tbl.to_s
111
143
  data
@@ -140,9 +172,15 @@ class StrategiesDSL
140
172
  # Get all the tests. Basically a cartesian product of all selection, crossover and mutation methods.
141
173
  def get_tests
142
174
  t = []
143
- raise 'No selection modules defined' unless @s
144
- raise 'No crossover modules defined' unless @c
145
- raise 'No mutation modules defined' unless @m
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
146
184
  @s.each{|s|
147
185
  @c.each{|c|
148
186
  @m.each{|m|
@@ -15,7 +15,7 @@ end
15
15
  # and crosses them randomly to replace all the others.
16
16
  # +best+ can be an float < 1 (fraction of population size), or a number >=1 (number of individuals)
17
17
  def TruncationSelection(best=0.3)
18
- Module::new{
18
+ Module.new{
19
19
  @@best = best
20
20
  def next_generation(population)
21
21
  k = if @@best >= 1 # best is an integer >= 1 -> select this much
@@ -0,0 +1,128 @@
1
+ #Tree genotype, crossover, mutation etc.
2
+
3
+ # some general helper functions, which are independant of the operator arrays
4
+ module GPTreeHelper
5
+ def dup_tree(t)
6
+ if t.first==:term
7
+ t.clone # avoid inf recursion here
8
+ else
9
+ t.map{|st| st.is_a?(Symbol) ? st : dup_tree(st) }
10
+ end
11
+ end
12
+
13
+ def tree_size(t)
14
+ if t.first==:term
15
+ 1
16
+ else
17
+ t[1..-1].inject(1){|sum,st| sum + tree_size(st) }
18
+ end
19
+ end
20
+
21
+ def all_subtrees(t)
22
+ if t.first==:term
23
+ [t]
24
+ else
25
+ t[1..-1].map{|st| all_subtrees(st) }.inject{|a,b|a+b} << t
26
+ end
27
+ end
28
+
29
+ def random_subtree(t=@genes)
30
+ all_subtrees(t).at_rand
31
+ end
32
+
33
+ def eval_tree(tree,values_hash)
34
+ if tree.first == :term
35
+ termval = tree[1]
36
+ if termval.is_a?(Symbol) # look up symbols in the hash
37
+ termval = values_hash[termval]
38
+ termval = termval.call if termval.is_a?(Proc) # and if hash value is a proc, evaluate it
39
+ end
40
+ termval
41
+ else # tree.first is an operator
42
+ eval_tree(tree[1],values_hash).send(tree.first, *tree[2..-1].map{|t| eval_tree(t,values_hash) } )
43
+ end
44
+ end
45
+
46
+ extend self
47
+ end
48
+
49
+ # Tree genotype, for genetic programming etc.
50
+ # * Pass arrays of terminals/binary operators and unary operators to this function to generate a class.
51
+ # * 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
+ # * This needs more options. Depth of initial trees, etc. Also needs a better mutator.
53
+ def TreeGenotype(terminals = [proc{rand},:x], binary_ops = [:+,:*,:-,:/], unary_ops = nil)
54
+ Class.new(Genotype) {
55
+ @@terms = terminals
56
+ @@binary_ops = binary_ops
57
+ @@unary_ops = unary_ops
58
+
59
+ def initialize
60
+ self.genes = random_tree(3)
61
+ end
62
+
63
+ def genes=(g)
64
+ class << g # ensures a genes.dup call is a deep copy
65
+ def dup
66
+ GPTreeHelper.dup_tree(self)
67
+ end
68
+ end
69
+ @genes = g
70
+ end
71
+
72
+ def size
73
+ tree_size(@genes)
74
+ end
75
+
76
+ def random_tree(d)
77
+ if d == 0 || rand < 1.0 / 3
78
+ e = @@terms.at_rand
79
+ [:term, e.is_a?(Proc) ? e.call : e]
80
+ else
81
+ if @@unary_ops.nil? || rand < 0.5
82
+ [@@binary_ops.at_rand,random_tree(d-1),random_tree(d-1)]
83
+ else
84
+ [@@unary_ops.at_rand,random_tree(d-1)]
85
+ end
86
+ end
87
+ end
88
+
89
+ def eval_genes(terminals_value_hash = {})
90
+ eval_tree(@genes,terminals_value_hash)
91
+ end
92
+
93
+ def to_s
94
+ @genes.inspect
95
+ end
96
+
97
+ use PCross(0.7,TreeCrossover), PMutate(0.5,TreeMutator) # TODO: test what probabilities are best
98
+
99
+ # make helper functions available at both class and instance level
100
+ include GPTreeHelper
101
+ class << self
102
+ include GPTreeHelper
103
+ end
104
+ }
105
+ end
106
+
107
+ # TreeCrossover does a standard subtree swapping crossover for trees.
108
+ module TreeCrossover
109
+ def cross(parent1,parent2)
110
+ child1 = parent1.dup
111
+ child2 = parent2.dup
112
+ c1_st = child1.random_subtree
113
+ c2_st = child1.random_subtree
114
+ c1_copy = dup_tree(c1_st)
115
+ c1_st.replace dup_tree(c2_st)
116
+ c2_st.replace c1_copy
117
+ [child1,child2]
118
+ end
119
+ end
120
+
121
+ # TreeMutator replaces a randomly chosen subtree with a new, randomly generated, subtree of depth <= 2.
122
+ module TreeMutator
123
+ def mutate!
124
+ random_subtree.replace random_tree(2)
125
+ self
126
+ end
127
+ end
128
+
data/lib/charlie.rb CHANGED
@@ -4,7 +4,7 @@ $:.unshift File.dirname(__FILE__)
4
4
 
5
5
  # This is just a dummy module to avoid making the VERSION constant a global.
6
6
  module Charlie
7
- VERSION = '0.5.0'
7
+ VERSION = '0.6.0'
8
8
  end
9
9
 
10
10
  require 'charlie/etc/monkey'
@@ -26,10 +26,7 @@ require 'charlie/list/list_genotype'
26
26
 
27
27
  require 'charlie/permutation/permutation'
28
28
 
29
-
30
-
31
-
32
-
29
+ require 'charlie/tree/tree'
33
30
 
34
31
 
35
32
 
data/test/t_common.rb CHANGED
@@ -1,7 +1,5 @@
1
1
 
2
2
 
3
-
4
-
5
3
  require File.dirname(__FILE__) + '/../lib/charlie' unless Object.const_defined?('Charlie')
6
4
  require 'test/unit'
7
5
 
@@ -30,3 +28,21 @@ class StringA < StringGenotype(20,'a'..'d')
30
28
  end
31
29
  use UniformCrossover
32
30
  end
31
+
32
+ class RRTest < BitStringGenotype(8) # easier convergence, fitness 0 at max. for testing evolve_until, etc.
33
+ def fitness
34
+ genes.enum_slice(2).find_all{|e| e.all?{|x|x==1} }.size - 4
35
+ end
36
+ use ListMutator(:expected_n[1],:flip), TruncationSelection(0.3), UniformCrossover
37
+ end
38
+
39
+ class TestTest < Test::Unit::TestCase
40
+ def test_testclass
41
+ assert_nothing_raised{
42
+ Population.new(TestClass(Module::new),10).evolve_silent(2)
43
+ Population.new(TestClass(UniformCrossover),10).evolve_silent(2)
44
+ Population.new(TestClass(ListMutator(:single_point,:gaussian) ),10).evolve_silent(2)
45
+ Population.new(TestClass(TournamentSelection(4)),10).evolve_silent(2)
46
+ }
47
+ end
48
+ end
@@ -4,6 +4,7 @@ CDIR = File.dirname(__FILE__)
4
4
 
5
5
  class BMTest < Test::Unit::TestCase
6
6
  def setup
7
+ Dir.mkdir(CDIR+'/output/') rescue nil
7
8
  Dir[CDIR+'/output/*'].each{|f| File.unlink f}
8
9
  end
9
10
 
@@ -33,23 +34,24 @@ class BMTest < Test::Unit::TestCase
33
34
  assert( File.readlines(CDIR+'/output/test_benchmark.csv').size == 4, 'Output file not generated.')
34
35
  end
35
36
 
36
- def test_raise
37
- assert_raises(RuntimeError) {
38
- Population.benchmark(StringA,nil,nil){
39
- selection TruncationSelection(0.2)
37
+ def test_defaults
38
+ assert_nothing_raised {
39
+ r = Population.benchmark(StringA,nil,nil){
40
+ selection TruncationSelection(0.2), TournamentSelection(3)
40
41
  # no crossover, etc specified.
41
42
  }
43
+ assert_equal 2,r.size
42
44
  }
43
- assert_raises(RuntimeError) {
44
- Population.benchmark(StringA,nil,nil){
45
+ assert_nothing_raised {
46
+ r=Population.benchmark(StringA,nil,nil){
45
47
  crossover NullCrossover
46
48
  }
49
+ assert_equal 1,r.size
47
50
  }
48
- assert_raises(RuntimeError) {
49
- Population.benchmark(StringA,nil,nil){
50
- mutation Module.new
51
- selection Module.new
51
+ assert_nothing_raised {
52
+ r=Population.benchmark(StringA,nil,nil){
52
53
  }
54
+ assert_equal 1,r.size
53
55
  }
54
56
  end
55
57
 
@@ -0,0 +1,87 @@
1
+ require 't_common'
2
+
3
+ class EvolveTest < Test::Unit::TestCase
4
+ def test_console
5
+ r = Population.new(TestProblem,10).evolve_on_console(10)
6
+ assert_respond_to r, :[]
7
+ assert_respond_to r[-1], :fitness
8
+ end
9
+
10
+ def test_silent
11
+ r = Population.new(TestProblem,10).evolve_silent(10)
12
+ assert_respond_to r, :[]
13
+ assert_respond_to r[-1], :fitness
14
+ end
15
+
16
+ def test_continue # test if calling evolve twice does not reset the population
17
+ 5.times{
18
+ p = Population.new(TestProblem,10)
19
+ best3 = p.evolve_silent(3)[-1].fitness
20
+ best6 = p.evolve_silent(3)[-1].fitness
21
+ assert( best6 >= best3 )
22
+ best36 = p.evolve_silent(30)[-1].fitness
23
+ # Not true for all problems, but in this case the probability of failure is negligible
24
+ assert( best36 > best6, "test_continue failed. Please rerun test to make sure this isn't just extremely bad luck.")
25
+ }
26
+ end
27
+
28
+ def test_until_best # test evolve_until_best
29
+ start = Time.now
30
+ r,g = Population.new(RRTest,10).evolve_until_best(1000_000_000){|b| b.fitness == 0}
31
+ assert_not_nil g
32
+ assert g >= 0 && g < 1000, "test_until_pop failed to converge within 1000 generations"
33
+ assert( (Time.now - start < 1), "test_until failed to converge within 1 second.")
34
+ assert_equal r.max.fitness, 0 # actually converged
35
+ assert_respond_to r, :[]
36
+ assert_respond_to r[-1], :fitness
37
+ # test on failure to converge
38
+ r,g = Population.new(RRTest,10).evolve_until_best(13){|b| false }
39
+ assert_nil g # did not converge
40
+ assert_respond_to r, :[]
41
+ assert_respond_to r[-1], :fitness
42
+ # alias
43
+ Population.new(RRTest,10).evolve_until{|b| b.fitness == 0}
44
+ end
45
+
46
+
47
+ def test_until_pop # test evolve_until_population
48
+ start = Time.now
49
+ r,g = Population.new(RRTest,10).evolve_until_population(1000_000_000){|p| p.count{|x| x.fitness == 0 } > 3}
50
+ assert_not_nil g
51
+ assert g >= 0 && g < 1000, "test_until_pop failed to converge within 1000 generations"
52
+ assert( (Time.now - start < 1), "test_until_pop failed to converge within 1 second.")
53
+ assert r.count{|x| x.fitness == 0 } > 3 # actually converged to > 3 clones of best solution
54
+ assert_respond_to r, :[]
55
+ assert_respond_to r[-1], :fitness
56
+ # test on failure to converge
57
+ r,g = Population.new(RRTest,10).evolve_until_population(13){|b| false }
58
+ assert_nil g # did not converge
59
+ assert_respond_to r, :[]
60
+ assert_respond_to r[-1], :fitness
61
+ end
62
+
63
+ # doesn't really belong here, and should be moved...eventually
64
+ def test_cache
65
+ klass_r = Class.new(TestProblem){ def fitness; rand; end }
66
+
67
+ # test cache in random fitness
68
+ e = klass_r.new;
69
+ assert_not_equal e.fitness, e.fitness
70
+ klass_r.class_eval{ cache_fitness }
71
+ assert_equal e.fitness, e.fitness
72
+ # test performance gain of cache. using tournamentselection(3) and sleep for maximum effect
73
+ klass_nc = Class.new(TestProblem){ def fitness; sleep(0.01); rand; end; use TournamentSelection(3) }
74
+ klass_c = Class.new(klass_nc){ cache_fitness }
75
+ 3.times { # consistently
76
+ s = Time.now
77
+ Population.new(klass_nc,5).evolve_silent(2)
78
+ t_nc = Time.now - s
79
+
80
+ s = Time.now
81
+ Population.new(klass_c,5).evolve_silent(2)
82
+ t_c = Time.now - s
83
+ assert t_c < t_nc # 3x as fast, so fairly safe test
84
+ }
85
+ end
86
+
87
+ end
@@ -2,15 +2,21 @@ require 't_common'
2
2
 
3
3
  N=10
4
4
 
5
- #CITIES = (0...N).map{|i| th = i * 2 * Math::PI / N; [Math.cos(th),Math.sin(th)] }
6
- #p CITIES
7
-
8
5
  class PermutationTest < PermutationGenotype(N)
9
6
  def fitness
10
7
  (0...N).zip_with(genes){|a,b| a==b ? 1 : 0}.sum
11
8
  end
12
9
  end
13
10
 
11
+ class PermutationTestERO < PermutationTest
12
+ use EdgeRecombinationCrossover
13
+ use PMutate(0.5,PermutationMutator)
14
+ end
15
+
16
+ class PermutationTestInvert < PermutationTest
17
+ use InversionMutator, RandomSelection
18
+ end
19
+
14
20
 
15
21
  class PermTests < Test::Unit::TestCase
16
22
  def test_evolve
@@ -20,4 +26,32 @@ class PermTests < Test::Unit::TestCase
20
26
  }
21
27
  p.each{|s| assert_equal s.genes.sort, (0...N).to_a }
22
28
  end
29
+
30
+
31
+ def test_edge_recombination
32
+ p=nil
33
+ assert_nothing_raised{
34
+ p=Population.new(PermutationTestERO,20).evolve_silent(20)
35
+ }
36
+ p.each{|s| assert_equal s.genes.sort, (0...N).to_a }
37
+ end
38
+
39
+ def test_edge_recombination_rand # test if permutation doesn't just stay preserved because of fitness
40
+ p=nil
41
+ assert_nothing_raised{
42
+ klass = Class.new(PermutationTestERO){ use RandomSelection }
43
+ p=Population.new(klass,20).evolve_silent(20)
44
+ }
45
+ p.each{|s| assert_equal s.genes.sort, (0...N).to_a }
46
+ end
47
+
48
+ def test_inversion_mutator # test if inversion mutator works
49
+ p=nil
50
+ assert_nothing_raised{
51
+ p=Population.new(PermutationTestInvert,20).evolve_silent(20)
52
+ }
53
+ p.each{|s| assert_equal s.genes.sort, (0...N).to_a }
54
+ end
55
+
56
+
23
57
  end
data/test/test_tree.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 't_common'
2
+
3
+ class TreeTest < TreeGenotype([1,:x,proc{rand}], [:+], [:+@])
4
+ def fitness
5
+ - (eval_genes(:x=>1) - 5).abs
6
+ end
7
+ end
8
+
9
+ class BloatTest < TreeTest
10
+ def fitness
11
+ size
12
+ end
13
+ end
14
+
15
+ # hard to test, also see examples/tree.rb
16
+ class TreeTests < Test::Unit::TestCase
17
+ def test_evolve
18
+ p=nil
19
+ assert_nothing_raised{
20
+ p=Population.new(TreeTest,20).evolve_silent(20)
21
+ }
22
+ p.each{|s| assert s.genes.is_a?(Array) }
23
+ end
24
+
25
+ def test_eval
26
+ p=TreeTest.new
27
+ p.genes = [:term,5]
28
+ assert_equal 0, p.fitness
29
+ p.genes = [:+, [:term,:x], [:term,:x]]
30
+ assert_equal 2, p.eval_genes(:x=>1)
31
+ assert_equal 6, p.eval_genes(:x=>3)
32
+ assert_equal( -3, p.fitness)
33
+ p.genes = [:term,:x]
34
+ assert_not_equal p.eval_genes(:x=>proc{rand}), p.eval_genes(:x=>proc{rand})
35
+ assert p.eval_genes(:x=>proc{rand}).is_a?(Float)
36
+ end
37
+
38
+ def test_size
39
+ p=TreeTest.new
40
+ p.genes = [:term,5]
41
+ assert_equal 1, p.size
42
+ p.genes = [:+, [:term,:x], [:term,:x]]
43
+ assert_equal 3, p.size
44
+ end
45
+
46
+ def test_bloat
47
+ p=nil
48
+ assert_nothing_raised{
49
+ p=Population.new(BloatTest,20).evolve_silent(50)
50
+ }
51
+ p.each{|s| assert s.genes.is_a?(Array) }
52
+ assert p.max.size > 20, "tree bloat test failed, please rerun to check if this isn't just extremely bad luck"
53
+ end
54
+
55
+
56
+
57
+ end