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.
@@ -92,7 +92,10 @@ loop{
92
92
  }
93
93
  puts 'Found walk of length 25:', pop[-1].genes.inspect
94
94
 
95
+ puts "Second try, no output but shorter code."
96
+ pop, gens = Population.evolve_multiple_until(Walk,20,1000){|b| b.fitness == 25 }
95
97
 
98
+ puts "Found walk of length 25 in #{gens} generations:", pop.best.genes.inspect
96
99
 
97
100
 
98
101
  end
@@ -92,6 +92,20 @@ solutions found for N=31:
92
92
  pop = Population.new(PORS).evolve_on_console(500)
93
93
  pp pop.max.genes
94
94
 
95
+ elsif ARGV[0]=='xor'
96
+ class Object; def not; !self; end;end
97
+
98
+ class XOR < TreeGenotype([:A,:B,true,false],[:not], [:&,:|])
99
+ def fitness
100
+ [[false,false],[false,true],[true,false],[true,true]].count{|a,b| eval_genes(:A=>a,:B=>b) == a^b } - $size_fac * size
101
+ end
102
+ cache_fitness
103
+ end
104
+ $size_fac = 0.01
105
+ pop, gen = Population.new(XOR).evolve_until(2000){|best| best.fitness > 3.9 }
106
+ puts "Generations needed (nil=no convergence) : #{gen}"
107
+ puts "Solution: #{pop.best}, fitness #{pop.best.fitness}"
108
+
95
109
 
96
110
  elsif ARGV[0]=='bloat'
97
111
 
@@ -111,5 +125,5 @@ pop = Population.new(Bloat).evolve_on_console(200)
111
125
 
112
126
  else
113
127
  puts "Several examples for tree genotypes."
114
- puts "Usage: ruby tree.rb cos|pors|porsx|bloat"
128
+ puts "Usage: ruby tree.rb cos|pors|porsx|xor|parity6|bloat"
115
129
  end
@@ -26,8 +26,7 @@ class TSP < PermutationGenotype(CITIES.size)
26
26
  end
27
27
 
28
28
  puts "Running GA until the optimal solution has been found."
29
- gen = nil
30
- pop, gen = Population.new(TSP,30).evolve_until(1000,100){|b| puts b.fitness; b.fitness > -(N*N+1) } while gen.nil?
29
+ pop, gen = Population.evolve_multiple_until(TSP,30,100,1000,100){|b| puts b.fitness; b.fitness > -(N*N+1) }
31
30
  puts "Fitness: #{pop.max.fitness}"
32
31
  puts "Generations needed: #{gen.inspect}"
33
32
  p pop.max.genes.map{|a| CITIES[a]}
@@ -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.7.1'
7
+ VERSION = '0.8.0'
8
8
  end
9
9
 
10
10
  require 'charlie/etc/monkey'
@@ -24,6 +24,10 @@ require 'charlie/list/list_mutate'
24
24
  require 'charlie/list/list_crossover'
25
25
  require 'charlie/list/list_genotype'
26
26
 
27
+ require 'charlie/list/matrix'
28
+
29
+ require 'charlie/list/neural'
30
+
27
31
  require 'charlie/permutation/permutation'
28
32
 
29
33
  require 'charlie/tree/tree'
@@ -23,6 +23,7 @@ end
23
23
 
24
24
  # Takes crossover c1 with probability p, and crossover c2 with probability 1-p
25
25
  def PCross(p,c1,c2=NullCrossover)
26
+ raise ArgumentError, "first argument to PCross should be numeric (probability)." unless p.is_a?(Numeric)
26
27
  return c1 if c1==c2
27
28
  c1_name, c2_name = [c1,c2].map{|c| '_cross_' + c.to_s.gsub(/[^A-Za-z0-9]/,'') }
28
29
  Module.new{
@@ -32,10 +32,11 @@ module Enumerable
32
32
  end
33
33
 
34
34
  class Array
35
-
36
- def shuffle
37
- sort_by{ rand }
38
- end if RUBY_VERSION < '1.9'
35
+ if RUBY_VERSION < '1.9'
36
+ def shuffle ; sort_by { rand }; end
37
+ def shuffle!; replace shuffle; end
38
+ def find_index(&b); index find(&b); end
39
+ end
39
40
 
40
41
  def dot_product(v)
41
42
  r=0.0
@@ -12,7 +12,9 @@ module GABenchmark
12
12
  generations = dsl_obj.generations
13
13
  population_size = dsl_obj.population_size
14
14
  repeat_tests = dsl_obj.repeat
15
-
15
+ setup_proc = dsl_obj.setup
16
+ teardown_proc = dsl_obj.teardown
17
+
16
18
  track_stat = dsl_obj.track_stat
17
19
 
18
20
  n_tests = all_tests.size
@@ -30,8 +32,14 @@ module GABenchmark
30
32
 
31
33
  test_stats = (0...repeat_tests).map{
32
34
  print '.'; $stdout.flush
33
- best = Population.new(gclass,population_size).evolve_silent(generations).last
35
+
36
+ population = Population.new(gclass,population_size)
37
+ setup_proc.call(population)
38
+ best = population.evolve_silent(generations).last
39
+
34
40
  stat = track_stat.call(best)
41
+ teardown_proc.call(population)
42
+
35
43
  overall_best = [best, stat] if overall_best[0].nil? || (overall_best[1] <=> stat) < 0 # use <=> to allow arrays
36
44
  stat
37
45
  }
@@ -159,7 +167,8 @@ class StrategiesDSL
159
167
  def initialize
160
168
  @repeat = 10
161
169
  @population_size = 20
162
- @generations = 50
170
+ @generations = 50
171
+ @setup = @teardown = proc{}
163
172
  selection []
164
173
  crossover []
165
174
  mutator []
@@ -176,6 +185,18 @@ class StrategiesDSL
176
185
  end
177
186
  alias :track_stats :track_stat
178
187
 
188
+ # Pass a block that does the setup. Rarely needed. The proc is passes the population before each test (i.e. between Population.new and #evolve_silent)
189
+ def setup(&b)
190
+ return @setup unless block_given?
191
+ @setup = b
192
+ end
193
+
194
+ # Pass a block that does the setup. Rarely needed. Called with the population as argument AFTER track_stats.
195
+ def teardown(&b)
196
+ return @teardown unless block_given?
197
+ @teardown = b
198
+ end
199
+
179
200
  # Get all the tests. Basically a cartesian product of all selection, crossover and mutation methods.
180
201
  def get_tests
181
202
  t = []
@@ -36,7 +36,7 @@ class Genotype
36
36
 
37
37
  # Creates a new instance of your genotype class given its genes.
38
38
  def from_genes(g)
39
- r = allocate # r = new # how to avoid initialize here? -- fixed
39
+ r = allocate
40
40
  r.genes = g
41
41
  r
42
42
  end
@@ -52,6 +52,9 @@ class Genotype
52
52
  end
53
53
  end
54
54
 
55
+ def clear_cache
56
+ @fitness_cache = nil
57
+ end
55
58
  # Used by Genotype.cache_fitness. This accessor can be used to clear the cache.
56
59
  # Also could be used by niche selection, etc. as a place to change the effective fitness w/o changing the actual selection algorithms.
57
60
  attr_accessor :fitness_cache
@@ -61,13 +64,27 @@ class Genotype
61
64
  end
62
65
 
63
66
  def mutate
64
- dup.mutate!
67
+ cp = dup
68
+ cp.mutate!
69
+ cp
65
70
  end
66
71
 
67
72
  def to_s
68
73
  @genes.to_s
69
74
  end
70
75
 
76
+ # Defines fight in terms of fight_points (self wins if it has at least as many points as other). Used in co-evolution.
77
+ #def fight(other)
78
+ # r = fight_points(other)
79
+ # return r[0]>=r[1]
80
+ #end
81
+
82
+ # Defines fight_points in terms of fight (1 point for the winner, 0 for the loser). Used in co-evolution.
83
+ ##def fight_points(other)
84
+ # fight(other) ? [1,0] : [0,1]
85
+ #end
86
+
87
+
71
88
  include Comparable
72
89
  def <=>(b)
73
90
  fitness <=> b.fitness
@@ -29,6 +29,8 @@ def NPointCrossover(n=2)
29
29
  }
30
30
  }
31
31
  end
32
+ TwoPointCrossover = NPointCrossover(2)
33
+ ThreePointCrossover = NPointCrossover(2)
32
34
 
33
35
  # Uniform crossover, returns two children.
34
36
  module UniformCrossover
@@ -47,4 +49,42 @@ module UniformCrossover
47
49
  end
48
50
 
49
51
 
52
+ # Blending crossover, common in evolutionary strategies.
53
+ # * Given two parents x1(i), x2(i)
54
+ # * Returns two children with as i'th elements y(i)x1(i)+(1-y(i))x2(i) and y(i)x2(i)+(1-y(i))x1(i)
55
+ # * type=:cube takes y(i) independent, so children roughly within the hypercube spanned by the parents.
56
+ # * type=:line takes y(i)=y(1), so children roughly on the line between the parents.
57
+ # * <tt>exploration_alpha</tt> defines how far outside the hypercube/line spanned by the parents the children can be. (more specifically, y(i) = (1+2*alpha)*rand - alpha)
58
+ def BlendingCrossover(exploration_alpha=0.1,type=:cube)
59
+ sz_rand = 1 + 2 * exploration_alpha
60
+ Module.new{
61
+ self.name = "BlendingCrossover(#{exploration_alpha},#{type})"
62
+ if(type==:cube)
63
+ define_method(:cross){|parent1,parent2|
64
+ c1 = []; c2=[]; g1 = parent1.genes; g2 = parent2.genes
65
+ g1.each_with_index{|e,i|
66
+ y = rand*sz_rand - exploration_alpha
67
+ x2 = g2[i]
68
+ c1 << y*e + (1-y)*x2
69
+ c2 << y*x2 + (1-y)*e
70
+ }
71
+ [c1,c2].map{|x| from_genes(x) }
72
+ }
73
+ elsif(type==:line)
74
+ define_method(:cross){|parent1,parent2|
75
+ c1 = []; c2=[]; g1 = parent1.genes; g2 = parent2.genes
76
+ y = rand*sz_rand - exploration_alpha
77
+ g1.each_with_index{|e,i|
78
+ x2 = g2[i]
79
+ c1 << y*e + (1-y)*x2
80
+ c2 << y*x2 + (1-y)*e
81
+ }
82
+ [c1,c2].map{|x| from_genes(x) }
83
+ }
84
+ else
85
+ raise ArgumentError,"Invalid BlendingCrossover type #{type}"
86
+ end
87
+ }
88
+ end
89
+ BlendingCrossover=BlendingCrossover()
50
90
 
@@ -8,7 +8,9 @@
8
8
  def FloatListGenotype(n,range=0..1)
9
9
  Class.new(Genotype) {
10
10
  @@range = range
11
- define_method(:size){ n }
11
+ [self,metaclass].each{|c| c.class_eval{ # include both in class and metaclass
12
+ define_method(:size){ n }
13
+ }}
12
14
  def initialize
13
15
  @genes = Array.new(size){ rand * (@@range.end - @@range.begin) + @@range.begin }
14
16
  end
@@ -23,7 +25,9 @@ end
23
25
  # Individuals are initialized as an array of +n+ random bits.
24
26
  def BitStringGenotype(n)
25
27
  Class.new(Genotype) {
26
- define_method(:size){ n }
28
+ [self,metaclass].each{|c| c.class_eval{ # include both in class and metaclass
29
+ define_method(:size){ n }
30
+ }}
27
31
  def initialize
28
32
  @genes = Array.new(size){ rand(2) }
29
33
  end
@@ -0,0 +1,121 @@
1
+
2
+
3
+
4
+ # Generic ancestor for matrix genotypes
5
+ def MatrixGenotype(rows,columns=rows)
6
+ Class.new(Genotype) {
7
+ [self,metaclass].each{|c| c.class_eval{
8
+ define_method(:rows) { rows }
9
+ define_method(:columns) { columns }
10
+ define_method(:size) { rows * columns }
11
+ }}
12
+ def genes=(g)
13
+ @genes = g.map(&:dup)
14
+ end
15
+ use MatrixUniformCrossover.dup
16
+ }
17
+ end
18
+
19
+ # Genotype for a 2D array of floats.
20
+ def FloatMatrixGenotype(rows,columns=rows,range=0..1)
21
+ Class.new(MatrixGenotype(rows,columns)) {
22
+ @@range = range
23
+ def initialize
24
+ self.genes = Array.new(rows){ Array.new(columns){ rand * (@@range.end - @@range.begin) + @@range.begin } }
25
+ end
26
+ def to_s
27
+ @genes.inspect
28
+ end
29
+ use MatrixMutator()
30
+ }
31
+ end
32
+
33
+ # Genotype for a 2D array of bits, for example: connection matrices for graphs
34
+ def BitMatrixGenotype(rows,columns=rows)
35
+ Class.new(MatrixGenotype(rows,columns)) {
36
+ def initialize
37
+ self.genes = Array.new(rows){ Array.new(columns){ rand(2) } }
38
+ end
39
+ def to_s
40
+ @genes.map{|r| r.map(&:to_s).join }.join("\n")
41
+ end
42
+ use MatrixMutator(:expected_n,:flip)
43
+ }
44
+ end
45
+
46
+ # Uniform crossover for matrices
47
+ module MatrixUniformCrossover
48
+ def cross(parent1,parent2)
49
+ tc1 = []; tc2=[]
50
+ g1 = parent1.genes; g2 = parent2.genes
51
+ g1.each_with_index{|rg1,ri|
52
+ tc1 << (r1=[]); tc2 << (r2=[])
53
+ rg2 = g2[ri];
54
+ rg1.each_with_index{|e,i|
55
+ if rand(2).zero?
56
+ r1 << e; r2 << rg2[i]
57
+ else
58
+ r2 << e; r1 << rg2[i]
59
+ end
60
+ }
61
+ }
62
+ [tc1,tc2].map{|x| from_genes(x) }
63
+ end
64
+ end
65
+
66
+ # The mutation strategies for MatrixMutator
67
+ # * :n_point[n=3] : Mutates a single element, n times. Example: :n_point, :n_point[2]
68
+ # * :probability[ p=0.05 ] : Mutates each element with probability p
69
+ # * :expected_n[n=3] : Mutates each element with probability n/genes.size, i.e. such that the expected # of mutations is n
70
+ # * :expected_n_per_row[n=3], :expected_n_per_column[n=3] : Likewise
71
+ MatrixMutationStrategies = {
72
+ :probability => MMSPRB = Proc.new{|genes,pointmut,p|
73
+ p ||= 0.05
74
+ genes.map!{|r| r.map!{|e| rand < p ? pointmut.call(e) : e } }
75
+ },
76
+ :expected_n => Proc.new{|genes,pointmut,n| n ||= 3
77
+ MMSPRB.call(genes,pointmut,n.to_f / (genes.size * genes[0].size))
78
+ },
79
+ :expected_n_per_row => Proc.new{|genes,pointmut,n| n ||= 3
80
+ MMSPRB.call(genes,pointmut,n.to_f / genes[0].size)
81
+ },
82
+ :expected_n_per_column => Proc.new{|genes,pointmut,n| n ||= 3
83
+ MMSPRB.call(genes,pointmut,n.to_f / genes.size)
84
+ },
85
+ :n_point => Proc.new{|genes,pointmut,n|
86
+ n ||= 3
87
+ s, r = genes.size, genes.rows;
88
+ n.times{ i,j = rand(s).divmod(r); genes[i][j] = pointmut.call(genes[i][j]) }
89
+ }
90
+ }
91
+
92
+ # Generates a module which can be used as a mutator for matrix-based genotypes like FloatMatrixGenotype and BitMatrixGenotype
93
+ # * strategy should be one of the MatrixMutationStrategies, or a proc
94
+ # * point_mutator should be one of the PointMutators (like in ListMutator), or a proc
95
+ # * nil is equivalent to proc{} for the point mutator
96
+ def MatrixMutator(strategy=:expected_n ,point_mutator=:uniform)
97
+ strat, *strat_args = *strategy
98
+ pm , *pm_args = *point_mutator
99
+
100
+ pm ||= proc{}
101
+
102
+ strat = MatrixMutationStrategies[strat.intern] unless strat.is_a? Proc
103
+ pm = PointMutators[pm.intern] unless pm.is_a? Proc
104
+
105
+ raise ArgumentError,"Invalid mutation strategy" if strat.nil?
106
+ raise ArgumentError,"Invalid point mutator" if point_mutator.nil?
107
+
108
+ if pm_args.empty?
109
+ point_mutator_with_args = pm
110
+ else
111
+ point_mutator_with_args = proc{|*args| pm.call(*(args+pm_args) ) }
112
+ end
113
+
114
+ Module.new{
115
+ define_method(:mutate!) {
116
+ strat.call(@genes,point_mutator_with_args,*strat_args)
117
+ self
118
+ }
119
+ self.name= "MatrixMutator(#{strategy.inspect},#{point_mutator.inspect})"
120
+ }
121
+ end
@@ -0,0 +1,52 @@
1
+ # This file contains everything related to neural network genotypes
2
+
3
+ # The tanh function is the default
4
+ NN_TANH = proc{|x| Math.tanh x }
5
+ # The sign function is also commonly used
6
+ NN_SIGN = proc{|x| x>=0?1:-1 }
7
+
8
+ # Genotype for neural networks with a single hidden layer.
9
+ # * Inherits FloatListGenotype
10
+ # * input_n, hidden_n, output_n are the number of neurons at each layer
11
+ # * scaling determines how the floats in genes relate to the weights of the links (important for mutation size, initial values).
12
+ # * input to hidden weights are (list element) multiplied by scaling / input_n
13
+ # * hidden to output weights are (list element) multiplied by scaling / hidden_n
14
+ # * hidden_f, output_f are the (usually sigmoidal) functions to determine the output of nodes.
15
+ # * Output of hidden node i is hidden_f( weights . input - threshold)
16
+ # ----------------------------------------------------
17
+ # functions of the returned class:
18
+ # * output(input) - runs the neural network in some input
19
+ def NeuralNetworkGenotype(input_n,hidden_n,output_n=1,scaling=1.0,hidden_f=NN_TANH,output_f=NN_TANH)
20
+ links_n = hidden_n * (input_n + output_n)
21
+ thr_n = hidden_n + output_n
22
+ Class.new(FloatListGenotype(links_n+thr_n, -1..1 )) {
23
+ define_method(:output){|input|
24
+ raise ArgumentError unless input.size==input_n
25
+ si = scaling / input_n
26
+ sh = scaling / hidden_n
27
+
28
+ wts = genes
29
+ input = input.map{|i| i * si } # scale inputs instead of weights: same effect, but faster
30
+
31
+ hidden_val = Array.new(hidden_n,0.0)
32
+ output_val = Array.new(output_n,0.0)
33
+ (0...hidden_n).each{|i|
34
+ wi = i * input_n
35
+ thr = wts[links_n + i]
36
+ v = 0.0
37
+ (0...input_n).each{|j| v += input[j] * wts[wi + j] }
38
+ hidden_val[i] = hidden_f.call(v - thr) * sh # again, scale this instead of hidden->output weights
39
+ }
40
+ oi = hidden_n * input_n
41
+ (0...output_n).each{|i|
42
+ wi = oi + i * hidden_n
43
+ thr = wts[links_n + hidden_n + i]
44
+ v = 0.0
45
+ (0...hidden_n).each{|j| v += hidden_val[j] * wts[wi + j] }
46
+ output_val[i] = output_f.call(v - thr)
47
+ }
48
+ output_val
49
+ }
50
+ use UniformCrossover.dup
51
+ }
52
+ end