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.
- data/History.txt +3 -0
- data/Manifest.txt +53 -0
- data/README.txt +90 -0
- data/Rakefile +43 -0
- data/TODO.txt +28 -0
- data/data/BENCHMARK +53 -0
- data/data/CROSSOVER +49 -0
- data/data/GENOTYPE +49 -0
- data/data/MUTATION +43 -0
- data/data/SELECTION +48 -0
- data/data/template.html +34 -0
- data/examples/bit.rb +10 -0
- data/examples/function_opt_2peak.rb +24 -0
- data/examples/function_opt_sombero.rb +38 -0
- data/examples/gladiatorial_simple.rb +17 -0
- data/examples/gladiatorial_sunburn.rb +89 -0
- data/examples/gridwalk.rb +29 -0
- data/examples/output/flattened_sombero.html +6400 -0
- data/examples/output/flattened_sombero2_.html +3576 -0
- data/examples/output/fopt1_dblopt.html +2160 -0
- data/examples/output/hill10.html +5816 -0
- data/examples/output/hill2.csv +24 -0
- data/examples/output/hill2.html +384 -0
- data/examples/output/royalroad1_report.html +1076 -0
- data/examples/output/royalroad2_report.html +1076 -0
- data/examples/output/royalroadquick_report.html +504 -0
- data/examples/output/tsp.html +632 -0
- data/examples/output/weasel1_report.html +1076 -0
- data/examples/output/weasel2_report.html +240 -0
- data/examples/royalroad.rb +26 -0
- data/examples/royalroad2.rb +18 -0
- data/examples/simple_climb_hill2.rb +47 -0
- data/examples/tsp.rb +35 -0
- data/examples/weasel.rb +36 -0
- data/lib/charlie.rb +35 -0
- data/lib/charlie/crossover.rb +49 -0
- data/lib/charlie/etc/minireport.rb +45 -0
- data/lib/charlie/etc/monkey.rb +136 -0
- data/lib/charlie/genotype.rb +45 -0
- data/lib/charlie/list/list_crossover.rb +30 -0
- data/lib/charlie/list/list_genotype.rb +53 -0
- data/lib/charlie/list/list_mutate.rb +75 -0
- data/lib/charlie/mutate.rb +25 -0
- data/lib/charlie/permutation/permutation.rb +47 -0
- data/lib/charlie/population.rb +156 -0
- data/lib/charlie/selection.rb +162 -0
- data/test/t_common.rb +32 -0
- data/test/test_basic.rb +32 -0
- data/test/test_benchmark.rb +56 -0
- data/test/test_cross.rb +28 -0
- data/test/test_mutator.rb +44 -0
- data/test/test_permutation.rb +23 -0
- data/test/test_sel.rb +39 -0
- 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('<','<')}</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
|
+
|