charlie 0.5.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.
Files changed (54) hide show
  1. data/History.txt +3 -0
  2. data/Manifest.txt +53 -0
  3. data/README.txt +90 -0
  4. data/Rakefile +43 -0
  5. data/TODO.txt +28 -0
  6. data/data/BENCHMARK +53 -0
  7. data/data/CROSSOVER +49 -0
  8. data/data/GENOTYPE +49 -0
  9. data/data/MUTATION +43 -0
  10. data/data/SELECTION +48 -0
  11. data/data/template.html +34 -0
  12. data/examples/bit.rb +10 -0
  13. data/examples/function_opt_2peak.rb +24 -0
  14. data/examples/function_opt_sombero.rb +38 -0
  15. data/examples/gladiatorial_simple.rb +17 -0
  16. data/examples/gladiatorial_sunburn.rb +89 -0
  17. data/examples/gridwalk.rb +29 -0
  18. data/examples/output/flattened_sombero.html +6400 -0
  19. data/examples/output/flattened_sombero2_.html +3576 -0
  20. data/examples/output/fopt1_dblopt.html +2160 -0
  21. data/examples/output/hill10.html +5816 -0
  22. data/examples/output/hill2.csv +24 -0
  23. data/examples/output/hill2.html +384 -0
  24. data/examples/output/royalroad1_report.html +1076 -0
  25. data/examples/output/royalroad2_report.html +1076 -0
  26. data/examples/output/royalroadquick_report.html +504 -0
  27. data/examples/output/tsp.html +632 -0
  28. data/examples/output/weasel1_report.html +1076 -0
  29. data/examples/output/weasel2_report.html +240 -0
  30. data/examples/royalroad.rb +26 -0
  31. data/examples/royalroad2.rb +18 -0
  32. data/examples/simple_climb_hill2.rb +47 -0
  33. data/examples/tsp.rb +35 -0
  34. data/examples/weasel.rb +36 -0
  35. data/lib/charlie.rb +35 -0
  36. data/lib/charlie/crossover.rb +49 -0
  37. data/lib/charlie/etc/minireport.rb +45 -0
  38. data/lib/charlie/etc/monkey.rb +136 -0
  39. data/lib/charlie/genotype.rb +45 -0
  40. data/lib/charlie/list/list_crossover.rb +30 -0
  41. data/lib/charlie/list/list_genotype.rb +53 -0
  42. data/lib/charlie/list/list_mutate.rb +75 -0
  43. data/lib/charlie/mutate.rb +25 -0
  44. data/lib/charlie/permutation/permutation.rb +47 -0
  45. data/lib/charlie/population.rb +156 -0
  46. data/lib/charlie/selection.rb +162 -0
  47. data/test/t_common.rb +32 -0
  48. data/test/test_basic.rb +32 -0
  49. data/test/test_benchmark.rb +56 -0
  50. data/test/test_cross.rb +28 -0
  51. data/test/test_mutator.rb +44 -0
  52. data/test/test_permutation.rb +23 -0
  53. data/test/test_sel.rb +39 -0
  54. metadata +115 -0
@@ -0,0 +1,45 @@
1
+
2
+ # This was handled by ruport earlier, but that turned out to be overkill and give a huge amount of dependencies.
3
+
4
+ class Array
5
+ def to_table(colnames=nil)
6
+ Table.new(self,colnames)
7
+ end
8
+ end
9
+
10
+ class Table < Array
11
+ def initialize(tbl,colnames=nil)
12
+ @colnames = colnames || tbl[0].map_with_index{|e,i|i+1}
13
+ super(tbl.map{|row|row.dup.map(&:to_s)})
14
+ end
15
+
16
+ def to_html
17
+ "<table>\n" +
18
+ "<tr>" + @colnames.map{|t| "<th>#{t}</th>" }.join + "</tr>\n" +
19
+ map{|r| "<tr>\n" + r.map{|e| "\t<td>#{e.gsub('<','&lt;')}</td>\n" }.join + "</tr>\n" }.join +
20
+ "</table>"
21
+ end
22
+
23
+ def to_csv
24
+ map{|r| r.map(&:inspect).join(', ') }.join("\n")
25
+ end
26
+
27
+ def to_s(csz=nil)
28
+ rsz = at(0).size
29
+ csz ||= [(80-rsz) / rsz]*rsz
30
+ pad = ' ' * csz.max
31
+ sep = csz.map{|x|'-'*x}.join('+')
32
+ ([sep,@colnames.zip_with(csz){|str,sz| (str+pad)[0...sz] }.join('|'),sep]+
33
+ map{|r| r.zip_with(csz){|str,sz| (str+pad)[0...sz] }.join('|') } << sep).join("\n")
34
+ end
35
+
36
+ end
37
+
38
+ if __FILE__ == $0
39
+ t = [['aaaaaaa"a<>aaaaaaa',23],['bb',421]].to_table(['xx','yy'])
40
+ puts t.to_s
41
+ puts
42
+ puts t.to_html
43
+ puts
44
+ puts t.to_csv
45
+ end
@@ -0,0 +1,136 @@
1
+ # I should probably replace some of this with a dependency on facets.
2
+
3
+ class Numeric
4
+ def between(minval,maxval)
5
+ [[self,minval].max,maxval].min
6
+ end
7
+ end
8
+
9
+ module Enumerable
10
+ def group_by
11
+ ret = {}
12
+ each{|e| (ret[yield(e)] ||= []) << e }
13
+ ret
14
+ end
15
+
16
+ def zip_with(a2,&b)
17
+ zip(a2).map(&b)
18
+ end
19
+
20
+ def count
21
+ count = 0
22
+ each {|e| count+=1 if yield(e) }
23
+ count
24
+ end
25
+ end
26
+
27
+ class Array
28
+
29
+ def shuffle
30
+ sort_by{ rand }
31
+ end
32
+
33
+ def sum
34
+ inject(0){|a,b|a+b}
35
+ end
36
+
37
+ def inner_product(v)
38
+ zip_with(v){|a,b|a*b}.sum
39
+ end
40
+
41
+ def rand_index
42
+ rand(size)
43
+ end
44
+
45
+ def at_rand
46
+ self[rand(size)]
47
+ end
48
+
49
+ def stats
50
+ [min,max,average,stddev]
51
+ end
52
+
53
+ def average
54
+ sum.to_f / size
55
+ end
56
+
57
+ def map_with_index
58
+ r=[]
59
+ each_with_index{|e,i| r << yield(e,i) }
60
+ r
61
+ end
62
+
63
+ def stddev
64
+ mu = average
65
+ Math.sqrt( map{|x| (x-mu)*(x-mu) }.sum / size )
66
+ end
67
+ end
68
+
69
+
70
+ class String
71
+ def rand_index
72
+ rand(size)
73
+ end
74
+
75
+ def rand_at
76
+ self[rand(size)]
77
+ end
78
+
79
+ def chars
80
+ split('')
81
+ end
82
+
83
+ def each_char(&b)
84
+ chars.each(&b)
85
+ end
86
+ end
87
+
88
+ class Symbol
89
+ def to_proc
90
+ Proc.new { |*args| args.shift.__send__(self, *args) }
91
+ end
92
+
93
+ # This function was added because :npoint[3] just looks so much nicer than [:npoint,3]
94
+ def [](*args)
95
+ [self,*args]
96
+ end
97
+
98
+ def intern
99
+ self
100
+ end
101
+ end
102
+
103
+ class Module
104
+ def metaclass
105
+ class << self;self;end
106
+ end
107
+
108
+ # Used to give anonymous modules a name.
109
+ def name=(n)
110
+ metaclass.send(:define_method,:to_s) { n }
111
+ end
112
+ end
113
+
114
+ class Class
115
+ # For each module passed:
116
+ # Includes the module if it has a mutate! method, and includes it in the metaclass otherwise.
117
+ # Used to include selection/crossover/mutation modules in one line, or just to avoid all the class<<self;include ...;end
118
+ # class Example < Genotype
119
+ # use RandomSelection, NullCrossover, NullMutator
120
+ # end
121
+ def use(*mods)
122
+ mods.each{|mod|
123
+ if mod.instance_methods.include? 'mutate!'
124
+ include mod
125
+ else
126
+ metaclass.class_eval{
127
+ include mod
128
+ }
129
+ end
130
+ }
131
+ end
132
+
133
+ end
134
+
135
+
136
+
@@ -0,0 +1,45 @@
1
+ # Contains the base class for genotypes
2
+
3
+
4
+ # Base class. Inherit this if you're starting from scratch
5
+ class Genotype
6
+ attr_accessor :genes
7
+
8
+ # raises an exception when called
9
+ def fitness
10
+ raise NoMethodError, "You need to define a fitness function in your genotype class!"
11
+ end
12
+ # raises an exception when called
13
+ def fight(other)
14
+ raise NoMethodError, "You need to define a fight(other) function in your genotype class to use this selection strategy!"
15
+ end
16
+
17
+ class << self
18
+ # Creates a new instance of your genotype class given its genes.
19
+ def from_genes(g)
20
+ r = new # how to avoid initialize here?
21
+ r.genes = g
22
+ r
23
+ end
24
+ end
25
+
26
+ def dup
27
+ self.class.from_genes(genes.dup)
28
+ end
29
+
30
+ def mutate
31
+ dup.mutate!
32
+ end
33
+
34
+ def to_s
35
+ @genes.to_s
36
+ end
37
+
38
+ include Comparable
39
+ def <=>(b)
40
+ fitness <=> b.fitness
41
+ end
42
+ # Higher default max. population size for the selection
43
+ # .dup on constant modules ensures inherited classes can revert back to this
44
+ use NullCrossover.dup, NullMutator.dup, Elitism(ScaledRouletteSelection(),1)
45
+ end
@@ -0,0 +1,30 @@
1
+ # List crossovers: SinglePointCrossover, UniformCrossover
2
+
3
+
4
+ # Simple single point crossover, returns two children.
5
+ module SinglePointCrossover
6
+ def cross(parent1,parent2)
7
+ cross_pt = rand(parent1.size+1)
8
+ [ parent1.genes[0...cross_pt] + parent2.genes[cross_pt..-1],
9
+ parent2.genes[0...cross_pt] + parent1.genes[cross_pt..-1]].map{|x| from_genes(x) }
10
+ end
11
+ end
12
+
13
+ # Uniform crossover, returns two children.
14
+ module UniformCrossover
15
+ def cross(parent1,parent2)
16
+ c1 = []; c2=[]
17
+ parent1.genes.zip(parent2.genes).each{|a,b|
18
+ if rand(2).zero?
19
+ c1 << a; c2 << b
20
+ else
21
+ c2 << a; c1 << b
22
+ end
23
+ }
24
+ [c1,c2].map{|x| from_genes(x) }
25
+ end
26
+ end
27
+
28
+
29
+
30
+
@@ -0,0 +1,53 @@
1
+ # This file defines several different genotype (aka genome, chromosomes) classes.
2
+ # Inherit from one of these classes and define a fitness function to use them.
3
+
4
+
5
+ # Genotype of +n+ floats in the range +range+.
6
+ # Individuals are initialized as an array of +n+ random numbers in this range.
7
+ # Note that mutations may cross range min/max.
8
+ def FloatListGenotype(n,range=0..1)
9
+ Class.new(Genotype) {
10
+ @@range = range
11
+ define_method(:size){ n }
12
+ def initialize
13
+ @genes = Array.new(size){ rand * (@@range.end - @@range.begin) + @@range.begin }
14
+ end
15
+ def to_s
16
+ @genes.inspect
17
+ end
18
+ use ListMutator(), SinglePointCrossover.dup
19
+ }
20
+ end
21
+
22
+ # Genotype of +n+ bits.
23
+ # Individuals are initialized as an array of +n+ random bits.
24
+ def BitStringGenotype(n)
25
+ Class.new(Genotype) {
26
+ define_method(:size){ n }
27
+ def initialize
28
+ @genes = Array.new(size){ rand(2) }
29
+ end
30
+ def to_s
31
+ @genes.map(&:to_s).join
32
+ end
33
+ use ListMutator(:expected_n,:flip), SinglePointCrossover.dup
34
+ }
35
+ end
36
+
37
+ # Genotype of +n+ elements (not necessarily chars).
38
+ # Individuals are initialized as an array of +n+ elements, each randomly chosen from the +elements+ array.
39
+ def StringGenotype(n,elements)
40
+ elements = elements.chars if elements.is_a? String # string to array of chars
41
+ elements = elements.to_a
42
+ Class.new(Genotype) {
43
+ define_method(:size){ n }
44
+ define_method(:elements){ elements }
45
+ def initialize
46
+ @genes = Array.new(size){ elements.at_rand }
47
+ end
48
+ def to_s
49
+ @genes.map(&:to_s).join
50
+ end
51
+ use ListMutator(:expected_n[2],:replace[*elements]), SinglePointCrossover.dup
52
+ }
53
+ end
@@ -0,0 +1,75 @@
1
+ # List mutators: generated by the ListMutator function
2
+
3
+ # The mutation strategies for ListMutator
4
+ # * :single_point : Mutates a single element
5
+ # * :n_point[n=3] : Mutates a single element, n times. Example: :n_point, :n_point[2]
6
+ # * :probability[ p=0.05 ] : Mutates each element with probability p
7
+ # * :expected_n[n=3] : Mutates each element with probability n/genes.size, i.e. such that the expected # of mutations is n
8
+ MutationStrategies = { # TODO: change to default parameters in 1.9
9
+ :probability => Proc.new{|genes,pointmut,p|
10
+ p ||= 0.05
11
+ genes.map!{|e| rand < p ? pointmut.call(e) : e }
12
+ },
13
+ :expected_n => Proc.new{|genes,pointmut,n|
14
+ n ||= 3
15
+ p = n.to_f / genes.size
16
+ genes.map!{|e| rand < p ? pointmut.call(e) : e }
17
+ },
18
+ :single_point => Proc.new{|genes,pointmut|
19
+ i = genes.rand_index
20
+ genes[i] = pointmut.call(genes[i])
21
+ },
22
+ :n_point => Proc.new{|genes,pointmut,n|
23
+ n ||= 3
24
+ n.times{ i = genes.rand_index; genes[i] = pointmut.call(genes[i]) }
25
+ }
26
+ }
27
+
28
+ # The point mutators for ListMutator
29
+ # * :flip : flips bit (x->1-x), use in BitStringGenotype
30
+ # * :replace[ c1,c2,...,cn ] : replaces the element with one of the arguments. use in StringGenotype. Example: :replace[ *'a'..'z' ]
31
+ # * :uniform[ max_size=0.25 ]: adds a random number in the range [-max_size,+max_size], uniformly distributed.
32
+ # * :gaussian[ sigma=0.2 ] : adds a random number, gaussian distributed with standard deviation sigma
33
+ PointMutators = {
34
+ :replace => proc{|x,*pos| pos.at_rand },
35
+ :flip => proc{|x| 1-x },
36
+ :uniform => Proc.new{|x,max_size| max_size ||= 0.25; x - max_size + max_size * 2 * rand },
37
+ :gaussian=> Proc.new{|x,sigma|
38
+ sigma ||= 0.2;
39
+ delta = Math.sqrt(-2 * Math.log(rand)) * Math.cos(2 * Math::PI * rand) # Box-Muller transformation
40
+ x + delta * sigma
41
+ }
42
+ }
43
+
44
+ # Generates a module which can be used as a mutator for array-based genotypes like FloatListGenotype, BitStringGenotype and StringGenotype
45
+ # * strategy should be one of the MutationStrategies, or a proc
46
+ # * point_mutator should be one of the PointMutators, or a proc
47
+ # * nil is equivalent to proc{} for the point mutator
48
+ def ListMutator(strategy=:expected_n ,point_mutator=:uniform)
49
+ strat, *strat_args = *strategy
50
+ pm , *pm_args = *point_mutator
51
+
52
+ pm ||= proc{}
53
+
54
+ strat = MutationStrategies[strat.intern] unless strat.is_a? Proc
55
+ pm = PointMutators[pm.intern] unless pm.is_a? Proc
56
+
57
+ raise ArgumentError,"Invalid mutation strategy" if strat.nil?
58
+ raise ArgumentError,"Invalid point mutator" if point_mutator.nil?
59
+
60
+ if pm_args.empty?
61
+ point_mutator_with_args = pm
62
+ else
63
+ point_mutator_with_args = proc{|*args| pm.call(*(args+pm_args) ) }
64
+ end
65
+
66
+ Module.new{
67
+ define_method(:mutate!) {
68
+ strat.call(@genes,point_mutator_with_args,*strat_args)
69
+ self
70
+ }
71
+ self.name= "ListMutator(#{strategy.inspect},#{point_mutator.inspect})"
72
+ }
73
+ end
74
+
75
+
@@ -0,0 +1,25 @@
1
+ # Contains some basic mutators and functions on mutators
2
+
3
+ # Simply returns the non-mutated genes
4
+ module NullMutator
5
+ def mutate!; self; end
6
+ end
7
+
8
+
9
+ # Takes mutator m1 with probability p, and mutator m2 with probability 1-p
10
+ def PMutate(p,m1,m2=NullMutator)
11
+ Module.new{
12
+ @@p = p
13
+ include m2
14
+ alias :mutate2! :mutate!
15
+ include m1
16
+ def mutate!(*args)
17
+ rand < @@p ? super(*args) : mutate2!(*args)
18
+ end
19
+ self.name= "PMutate(#{p},#{m1},#{m2})"
20
+ }
21
+ end
22
+
23
+
24
+
25
+
@@ -0,0 +1,47 @@
1
+ # Contains genotypes, crossovers and mutators for permutations
2
+
3
+ # Generates a genotype class which represents a permutation of +elements+
4
+ # Includes the PermutationMutator and PermutationCrossover by default
5
+ def PermutationGenotype(n,elements=0...n)
6
+ elements = elements.chars if elements.is_a? String # string to array of chars
7
+ elements = elements.to_a
8
+ Class.new(Genotype) {
9
+ define_method(:size) { n }
10
+ define_method(:elements){ elements }
11
+
12
+ def initialize
13
+ @genes = elements.dup.shuffle
14
+ end
15
+
16
+ def to_s
17
+ @genes.inspect
18
+ end
19
+ use PermutationMutator.dup , PermutationCrossover.dup
20
+ }
21
+ end
22
+
23
+ # Transposition mutator for PermutationGenotype
24
+ module PermutationMutator
25
+
26
+ # Transposes two elements
27
+ def mutate!
28
+ i1, i2 = @genes.rand_index,@genes.rand_index
29
+ @genes[i1],@genes[i2] = @genes[i2], @genes[i1]
30
+ self
31
+ end
32
+ end
33
+
34
+ # One point partial preservation crossover for PermutationGenotype
35
+ # 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
+ module PermutationCrossover
37
+ def cross(parent1,parent2)
38
+ p1, p2 = parent1.genes, parent2.genes
39
+ cross_pt = rand(p1.size+1)
40
+ st1, st2 = p1[0...cross_pt], p2[0...cross_pt]
41
+
42
+ [ st1 + (p2 - st1), st2 + (p1 - st2) ].map{|x| from_genes(x) }
43
+ end
44
+ end
45
+
46
+
47
+