charlie 0.6.0 → 0.7.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 +14 -0
- data/Manifest.txt +13 -22
- data/README.txt +3 -3
- data/Rakefile +1 -1
- data/TODO.txt +11 -21
- data/data/BENCHMARK +25 -23
- data/data/CROSSOVER +5 -1
- data/data/GENOTYPE +6 -6
- data/data/MUTATION +19 -7
- data/data/SELECTION +2 -1
- data/data/template.html +2 -1
- data/examples/EXAMPLES_README.txt +70 -0
- data/examples/bitstring.rb +72 -0
- data/examples/{gladiatorial_sunburn.rb → coevolution.rb} +80 -22
- data/examples/function_optimization.rb +113 -0
- data/examples/output/{royalroad1_report.html → bitstring_royalroad.html} +822 -655
- data/examples/output/function_optimization_sombrero.html +2289 -0
- data/examples/output/function_optimization_twopeak.csv +210 -0
- data/examples/output/function_optimization_twopeak.html +2477 -0
- data/examples/output/string_weasel.html +513 -0
- data/examples/output/tsp.html +633 -882
- data/examples/{money.rb → permutation.rb} +20 -8
- data/examples/string.rb +98 -0
- data/examples/tree.rb +37 -12
- data/examples/tsp.rb +34 -22
- data/lib/charlie.rb +5 -1
- data/lib/charlie/1.9fixes.rb +46 -0
- data/lib/charlie/crossover.rb +31 -14
- data/lib/charlie/etc/minireport.rb +5 -4
- data/lib/charlie/etc/monkey.rb +11 -8
- data/lib/charlie/gabenchmark.rb +230 -0
- data/lib/charlie/genotype.rb +4 -0
- data/lib/charlie/list/list_crossover.rb +25 -5
- data/lib/charlie/mutate.rb +34 -7
- data/lib/charlie/permutation/permutation.rb +34 -6
- data/lib/charlie/population.rb +12 -122
- data/lib/charlie/selection.rb +1 -0
- data/lib/charlie/tree/tree.rb +179 -17
- data/test/t_common.rb +1 -1
- data/test/test_benchmark.rb +19 -5
- data/test/test_cross.rb +23 -1
- data/test/test_evolve.rb +14 -1
- data/test/test_mutator.rb +28 -2
- data/test/test_permutation.rb +23 -1
- data/test/test_sel.rb +3 -1
- data/test/test_tree.rb +63 -1
- metadata +17 -25
- data/examples/bit.rb +0 -10
- data/examples/function_opt_2peak.rb +0 -24
- data/examples/function_opt_sombero.rb +0 -38
- data/examples/gladiatorial_simple.rb +0 -17
- data/examples/gridwalk.rb +0 -29
- data/examples/output/flattened_sombero.html +0 -6400
- data/examples/output/flattened_sombero2_.html +0 -3576
- data/examples/output/fopt1_dblopt.html +0 -2160
- data/examples/output/hill10.html +0 -5816
- data/examples/output/hill2.csv +0 -24
- data/examples/output/hill2.html +0 -384
- data/examples/output/royalroad2_report.html +0 -1076
- data/examples/output/royalroadquick_report.html +0 -504
- data/examples/output/weasel1_report.html +0 -1076
- data/examples/output/weasel2_report.html +0 -240
- data/examples/royalroad.rb +0 -26
- data/examples/royalroad2.rb +0 -18
- data/examples/simple_climb_hill2.rb +0 -47
- data/examples/weasel.rb +0 -36
data/lib/charlie/etc/monkey.rb
CHANGED
@@ -24,6 +24,10 @@ module Enumerable
|
|
24
24
|
zip(a2).map(&b)
|
25
25
|
end
|
26
26
|
|
27
|
+
def sum
|
28
|
+
r=0; each{|e| r+=e }; r
|
29
|
+
end
|
30
|
+
|
27
31
|
alias_method :enum_slice, :each_slice unless RUBY_VERSION < '1.9' # ruby1.9 replaces enum_* with each_*
|
28
32
|
end
|
29
33
|
|
@@ -33,12 +37,10 @@ class Array
|
|
33
37
|
sort_by{ rand }
|
34
38
|
end if RUBY_VERSION < '1.9'
|
35
39
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def inner_product(v)
|
41
|
-
zip_with(v){|a,b|a*b}.sum
|
40
|
+
def dot_product(v)
|
41
|
+
r=0.0
|
42
|
+
each_with_index{|e,i| r+=e*v[i] }
|
43
|
+
r
|
42
44
|
end
|
43
45
|
|
44
46
|
def rand_index
|
@@ -50,6 +52,7 @@ class Array
|
|
50
52
|
end
|
51
53
|
|
52
54
|
def stats # TODO 1.9, use minmax
|
55
|
+
return transpose.map(&:stats).transpose if at(0).is_a?(Array) # return stats of each component if elements are arrays
|
53
56
|
[min,max,average,stddev]
|
54
57
|
end
|
55
58
|
|
@@ -79,9 +82,9 @@ class String
|
|
79
82
|
self[rand(size)]
|
80
83
|
end
|
81
84
|
|
82
|
-
def chars
|
85
|
+
def chars
|
83
86
|
split('')
|
84
|
-
end
|
87
|
+
end if RUBY_VERSION < '1.9'
|
85
88
|
|
86
89
|
def each_char(&b)
|
87
90
|
chars.each(&b)
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'rbconfig' # for install name
|
2
|
+
|
3
|
+
module GABenchmark
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# This method generates reports comparing several selection/crossover/mutation methods. Check the examples directory for several examples. See the BENCHMARK documentation file for more information.
|
7
|
+
def benchmark(genotype_class, html_outfile='report.html', csv_outfile=nil, &b)
|
8
|
+
start = Time.now
|
9
|
+
|
10
|
+
dsl_obj = StrategiesDSL.new; dsl_obj.instance_eval(&b)
|
11
|
+
all_tests = dsl_obj.get_tests
|
12
|
+
generations = dsl_obj.generations
|
13
|
+
population_size = dsl_obj.population_size
|
14
|
+
repeat_tests = dsl_obj.repeat
|
15
|
+
|
16
|
+
track_stat = dsl_obj.track_stat
|
17
|
+
|
18
|
+
n_tests = all_tests.size
|
19
|
+
tests_done = 0
|
20
|
+
puts "#{n_tests} Total tests:"
|
21
|
+
|
22
|
+
overall_best = [nil, -1.0 / 0.0]
|
23
|
+
|
24
|
+
data = all_tests.map{|selection_module,crossover_module,mutator_module|
|
25
|
+
tests_done += 1
|
26
|
+
print "\nRunning test #{tests_done}/#{n_tests} : #{selection_module} / #{crossover_module} / #{mutator_module}\t"
|
27
|
+
|
28
|
+
gclass = Class.new(genotype_class) { use selection_module,crossover_module,mutator_module }
|
29
|
+
start_test = Time.now
|
30
|
+
|
31
|
+
test_stats = (0...repeat_tests).map{
|
32
|
+
print '.'; $stdout.flush
|
33
|
+
best = Population.new(gclass,population_size).evolve_silent(generations).last
|
34
|
+
stat = track_stat.call(best)
|
35
|
+
overall_best = [best, stat] if overall_best[0].nil? || (overall_best[1] <=> stat) < 0 # use <=> to allow arrays
|
36
|
+
stat
|
37
|
+
}
|
38
|
+
[selection_module, crossover_module,mutator_module,
|
39
|
+
(Time.now-start_test) / repeat_tests, test_stats]
|
40
|
+
}
|
41
|
+
|
42
|
+
html_output(html_outfile, data, genotype_class, Time.now-start, overall_best, dsl_obj)
|
43
|
+
csv_output(csv_outfile , data)
|
44
|
+
|
45
|
+
puts '',table_details(data).to_s
|
46
|
+
return data
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
ST_HEADINGS = %w[min max avg stddev time]
|
53
|
+
|
54
|
+
def format_stat(s)
|
55
|
+
if s.is_a?(Array)
|
56
|
+
s.map{|x|format_stat(x)}.join("\r\n")
|
57
|
+
else
|
58
|
+
'%.5f' % s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def table_details(data)
|
63
|
+
tabledata = data.map{|s,c,m,t,a|
|
64
|
+
[s,c,m] + (a.stats << t).map{|f| format_stat(f)}
|
65
|
+
}.sort_by{|row| -row[-3].to_f } # sort by avg fitness. highest to lowest. to_f on multiple formatted returns first
|
66
|
+
return tabledata.to_table(%w[selection crossover mutation] + ST_HEADINGS)
|
67
|
+
end
|
68
|
+
|
69
|
+
def table_group(datasets,g1_name) # array of title, data rows
|
70
|
+
tabledata = datasets.map{|title,dataset|
|
71
|
+
joined = dataset.map(&:last).inject{|a,b|a+b}
|
72
|
+
avg_time = dataset.map{|r| r[-2] }.average
|
73
|
+
[title] + (joined.stats << avg_time).map{|f| format_stat(f)}
|
74
|
+
}.sort_by{|row| -row[-3].to_f } # sort by average fitness
|
75
|
+
return tabledata.to_table([g1_name]+ST_HEADINGS).to_html
|
76
|
+
end
|
77
|
+
|
78
|
+
def html_output(html_outfile, data, genotype_class, tot_time, overall_best, dsl_obj )
|
79
|
+
return unless html_outfile
|
80
|
+
# Generate HTML
|
81
|
+
html_tables = <<INFO
|
82
|
+
<h1>Information</h1>\n
|
83
|
+
<table>
|
84
|
+
<tr><th colspan=2>Version Info</th></tr>
|
85
|
+
<tr><td>Ruby Install Name</td><td>#{Config::CONFIG['ruby_install_name']}</td></tr>
|
86
|
+
<tr><td>Ruby Version</td><td>#{RUBY_VERSION}</td></tr>
|
87
|
+
<tr><td>Charlie Version</td><td>#{Charlie::VERSION}</td></tr>
|
88
|
+
<tr><th colspan=2>Benchmark Info</th></tr>
|
89
|
+
<tr><td>Genotype class</td><td>#{genotype_class}</td></tr>
|
90
|
+
<tr><td>Population size</td><td>#{dsl_obj.population_size}</td></tr>
|
91
|
+
<tr><td>Number of generations per run</td><td>#{dsl_obj.generations}</td></tr>
|
92
|
+
<tr><td>Number of tests </td><td>#{data.size}</td></tr>
|
93
|
+
<tr><td>Tests repeated </td><td>#{dsl_obj.repeat} times</td></tr>
|
94
|
+
<tr><td>Number of runs </td><td>#{data.size * dsl_obj.repeat}</td></tr>
|
95
|
+
<tr><td>Total number of generations </td><td>#{data.size * dsl_obj.repeat * dsl_obj.generations}</td></tr>
|
96
|
+
<tr><td>Total time</td><td>#{'%.2f' % tot_time} seconds</td></tr>
|
97
|
+
<tr><th colspan=2>Best Solution Info</th></tr>
|
98
|
+
<tr><td>Fitness</td><td>#{overall_best[1].inspect}</td></tr>
|
99
|
+
<tr><td>Solution</td><td><textarea rows=3 cols=40>#{overall_best[0].to_s}</textarea></td></tr>
|
100
|
+
</table>
|
101
|
+
INFO
|
102
|
+
|
103
|
+
|
104
|
+
# - Combined stats
|
105
|
+
html_tables << "<h1>Stats for all</h1>"
|
106
|
+
html_tables << table_group([["All",data]],'')
|
107
|
+
# - stats grouped by selection, crossover, mutation methods
|
108
|
+
["selection","crossover","mutation"].each_with_index{|title,i|
|
109
|
+
html_tables << "<h1>Stats for #{title}</h1>"
|
110
|
+
html_tables << table_group(data.group_by{|x|x[i]}, title)
|
111
|
+
}
|
112
|
+
# - detailed stats
|
113
|
+
html_tables << '<h1>Detailed Stats</h1>' << table_details(data).to_html
|
114
|
+
# write HTML stats
|
115
|
+
File.open(html_outfile,'w'){|f|
|
116
|
+
template = File.read(File.dirname(__FILE__)+"/../../data/template.html")
|
117
|
+
f << template.sub('{{CONTENT}}',html_tables)
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def csv_output(csv_outfile,data)
|
122
|
+
return unless csv_outfile
|
123
|
+
File.open(csv_outfile,'w'){|f|
|
124
|
+
f << data.map{|r|r[0..2] << r[-1].inspect }.to_table.to_csv
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Used in the GABenchmark#benchmark function.
|
130
|
+
class StrategiesDSL
|
131
|
+
class << self
|
132
|
+
def attr_dsl(x)
|
133
|
+
x = x.to_s
|
134
|
+
attr_accessor x
|
135
|
+
alias_method 'get_'+x, x # rename reader
|
136
|
+
define_method(x) {|*args| # reader with 0 args, write with 1 arg
|
137
|
+
return send('get_'+x) if args.empty?
|
138
|
+
args.size > 1 ? send(x+'=',args) : send(x+'=',*args)
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Number of generations run in each test.
|
144
|
+
attr_dsl :generations
|
145
|
+
# Population size used.
|
146
|
+
attr_dsl :population_size
|
147
|
+
# Number of times all tests are run. Default=10. Increase for more accuracy on the benchmark.
|
148
|
+
attr_dsl :repeat
|
149
|
+
|
150
|
+
# Pass several modules to this to test these selection methods.
|
151
|
+
attr_dsl :selection
|
152
|
+
# Pass several modules to this to test these crossover methods.
|
153
|
+
attr_dsl :crossover
|
154
|
+
# Pass several modules to this to test these mutation methods.
|
155
|
+
attr_dsl :mutator
|
156
|
+
alias :mutation :mutator
|
157
|
+
alias :mutation= :mutator=
|
158
|
+
|
159
|
+
def initialize
|
160
|
+
@repeat = 10
|
161
|
+
@population_size = 20
|
162
|
+
@generations = 50
|
163
|
+
selection []
|
164
|
+
crossover []
|
165
|
+
mutator []
|
166
|
+
track_stat{|best| best.fitness } # tracks maximum fitness by default
|
167
|
+
end
|
168
|
+
|
169
|
+
# Pass a block that returns one or more statistics to track. Block is passed the individual with the highest fitness after each run.
|
170
|
+
# * Can be used to track, for example, training error vs generalization error.
|
171
|
+
# * Default is fitness of the best solution.
|
172
|
+
# * When returning multiple values, <=> for arrays is used to determine the best individual in the info table (i.e. second elements only for tie-breaking), but min/max/avg/stddev stats are calculated independently for each component
|
173
|
+
def track_stat(&b)
|
174
|
+
return @track_stat unless block_given?
|
175
|
+
@track_stat = b
|
176
|
+
end
|
177
|
+
alias :track_stats :track_stat
|
178
|
+
|
179
|
+
# Get all the tests. Basically a cartesian product of all selection, crossover and mutation methods.
|
180
|
+
def get_tests
|
181
|
+
t = []
|
182
|
+
|
183
|
+
defmod = Module.new{self.name='default'}
|
184
|
+
|
185
|
+
selection = [@selection].flatten ; selection = [defmod] if selection.empty?
|
186
|
+
crossover = [@crossover].flatten ; crossover = [defmod] if crossover.empty?
|
187
|
+
mutator = [@mutator].flatten ; mutator = [defmod] if mutator.empty?
|
188
|
+
|
189
|
+
selection.each{|s|
|
190
|
+
crossover.each{|c|
|
191
|
+
mutator.each{|m|
|
192
|
+
t << [s,c,m]
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
t
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
|
202
|
+
end # GABenchmark
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
=begin
|
209
|
+
class RoyalRoad < BitStringGenotype(64) # Royal Road problem
|
210
|
+
def fitness
|
211
|
+
1 + genes.enum_slice(8).find_all{|e| e.all?{|x|x==1} }.size # +1 to avoid all fitness 0 for roulette
|
212
|
+
end
|
213
|
+
cache_fitness
|
214
|
+
end
|
215
|
+
|
216
|
+
GABenchmark.benchmark(RoyalRoad,'test_bitstring_royalroad.html','o.csv'){
|
217
|
+
selection TruncationSelection(0.3),
|
218
|
+
Elitism(ScaledRouletteSelection)
|
219
|
+
|
220
|
+
crossover NullCrossover, UniformCrossover
|
221
|
+
|
222
|
+
mutator ListMutator(:expected_n[5],:flip)
|
223
|
+
|
224
|
+
generations 100
|
225
|
+
repeat 2 #
|
226
|
+
population_size 17
|
227
|
+
|
228
|
+
track_stat{|b| [b.fitness,b.genes.count{|x|x==1}] }
|
229
|
+
}
|
230
|
+
=end
|
data/lib/charlie/genotype.rb
CHANGED
@@ -51,6 +51,10 @@ class Genotype
|
|
51
51
|
self
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
# Used by Genotype.cache_fitness. This accessor can be used to clear the cache.
|
56
|
+
# Also could be used by niche selection, etc. as a place to change the effective fitness w/o changing the actual selection algorithms.
|
57
|
+
attr_accessor :fitness_cache
|
54
58
|
|
55
59
|
def dup
|
56
60
|
self.class.from_genes(genes.dup)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# List crossovers: SinglePointCrossover, UniformCrossover
|
1
|
+
# List crossovers: SinglePointCrossover, UniformCrossover, NPointCrossover
|
2
2
|
|
3
3
|
|
4
4
|
# Simple single point crossover, returns two children.
|
@@ -10,15 +10,36 @@ module SinglePointCrossover
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
# n point crossover, returns two children.
|
14
|
+
def NPointCrossover(n=2)
|
15
|
+
Module.new{
|
16
|
+
self.name = "NPointCrossover(#{n})"
|
17
|
+
define_method(:cross){|parent1,parent2|
|
18
|
+
p1 = parent1.genes; p2 = parent2.genes
|
19
|
+
upper_bnd = p1.size + 1
|
20
|
+
cross_pts = (0...n).map{rand(upper_bnd)}.sort
|
21
|
+
|
22
|
+
c1 = []; c2=[]
|
23
|
+
([0] + cross_pts << upper_bnd).each_cons(2){|cp1,cp2|
|
24
|
+
c1 += p1[cp1...cp2]
|
25
|
+
c2 += p2[cp1...cp2]
|
26
|
+
p1,p2 = p2,p1
|
27
|
+
}
|
28
|
+
[c1,c2].map{|x| from_genes(x) }
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
13
33
|
# Uniform crossover, returns two children.
|
14
34
|
module UniformCrossover
|
15
35
|
def cross(parent1,parent2)
|
16
36
|
c1 = []; c2=[]
|
17
|
-
parent1.genes
|
37
|
+
g1 = parent1.genes; g2 = parent2.genes
|
38
|
+
g1.each_with_index{|e,i|
|
18
39
|
if rand(2).zero?
|
19
|
-
c1 <<
|
40
|
+
c1 << e; c2 << g2[i]
|
20
41
|
else
|
21
|
-
c2 <<
|
42
|
+
c2 << e; c1 << g2[i]
|
22
43
|
end
|
23
44
|
}
|
24
45
|
[c1,c2].map{|x| from_genes(x) }
|
@@ -27,4 +48,3 @@ end
|
|
27
48
|
|
28
49
|
|
29
50
|
|
30
|
-
|
data/lib/charlie/mutate.rb
CHANGED
@@ -8,18 +8,45 @@ 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
|
+
return m1 if m1==m2
|
12
|
+
m1_name, m2_name = [m1,m2].map{|c| '_mutate_' + c.to_s.gsub(/[^A-Za-z0-9]/,'') + '!' }
|
11
13
|
Module.new{
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
include m1.dup # dup to avoid bugs on use PMutate(..,m1) .. use m1
|
15
|
+
alias_method m1_name, :mutate!
|
16
|
+
include m2.dup
|
17
|
+
alias_method m2_name, :mutate!
|
18
|
+
|
19
|
+
define_method(:mutate!) {
|
20
|
+
rand < p ? send(m1_name) : send(m2_name)
|
21
|
+
}
|
19
22
|
self.name= "PMutate(#{p},#{m1},#{m2})"
|
20
23
|
}
|
21
24
|
end
|
22
25
|
|
23
26
|
|
27
|
+
# Variant of PMutate for more than 2 mutators
|
28
|
+
# * Pass a hash of Module=>probability pairs. If sum(probability) < 1, NullMutator will be used for the remaining probability.
|
29
|
+
# * example: PCrossN(SinglePointCrossover=>0.33,UniformCrossover=>0.33) for NullCrossover/SinglePointCrossover/UniformCrossover all with probability 1/3
|
30
|
+
def PMutateN(hash)
|
31
|
+
tot_p = hash.inject(0){|s,(m,p)| s+p }
|
32
|
+
if (tot_p - 1.0).abs > 0.01 # close to 1?
|
33
|
+
raise ArgumentError, "PMutateN: sum of probabilities > 1.0" if tot_p > 1.0
|
34
|
+
hash[NullMutator] = (hash[NullMutator] || 0.0) + (1.0 - tot_p)
|
35
|
+
end
|
36
|
+
partial_sums = hash.sort_by{|m,p| -p } # max probability first
|
37
|
+
s = 0.0
|
38
|
+
partial_sums.map!{|m,p| ['_mutate_' + m.to_s.gsub(/[^A-Za-z0-9]/,'') + '!' , s+=p, m] }
|
24
39
|
|
40
|
+
Module.new{
|
41
|
+
partial_sums.each{|name,p,mod|
|
42
|
+
include mod.dup
|
43
|
+
alias_method name, :mutate!
|
44
|
+
}
|
45
|
+
define_method(:mutate!) {
|
46
|
+
r = rand
|
47
|
+
send partial_sums.find{|name,p,mod| p >= r }.first
|
48
|
+
}
|
49
|
+
self.name= "PMutateN(#{hash.inspect})"
|
50
|
+
}
|
51
|
+
end
|
25
52
|
|
@@ -16,12 +16,12 @@ def PermutationGenotype(n,elements=0...n)
|
|
16
16
|
def to_s
|
17
17
|
@genes.inspect
|
18
18
|
end
|
19
|
-
use
|
19
|
+
use TranspositionMutator.dup , PCross(0.75,PermutationCrossover)
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
-
# Transposition mutator for PermutationGenotype
|
24
|
-
module
|
23
|
+
# Transposition mutator for PermutationGenotype. Interchanges two elements and leaves the remaining elements in their original positions.
|
24
|
+
module TranspositionMutator
|
25
25
|
# Transposes two elements
|
26
26
|
def mutate!
|
27
27
|
i1, i2 = @genes.rand_index,@genes.rand_index
|
@@ -30,7 +30,7 @@ module PermutationMutator
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
# Inversion mutator for PermutationGenotype.
|
33
|
+
# Inversion mutator for PermutationGenotype.
|
34
34
|
# Takes two random indices, and reverses the elements in between (includes possible wrapping if index2 < index1)
|
35
35
|
module InversionMutator
|
36
36
|
# Inverts parts of the genes
|
@@ -48,8 +48,36 @@ module InversionMutator
|
|
48
48
|
end
|
49
49
|
|
50
50
|
|
51
|
-
#
|
52
|
-
#
|
51
|
+
# Takes a random element of the permutation, and inserts it at a random position.
|
52
|
+
# * Example: [1 2 3 4 5] to [1 4 2 3 5]
|
53
|
+
module InsertionMutator
|
54
|
+
def mutate!
|
55
|
+
from, to = @genes.rand_index, @genes.rand_index
|
56
|
+
@genes = if to >= from
|
57
|
+
to += 1 # add end of array as possibility
|
58
|
+
(@genes[0...from] + @genes[from+1...to] << @genes[from]) + @genes[to..-1]
|
59
|
+
else
|
60
|
+
(@genes[0...to] << @genes[from]) + @genes[to...from] + @genes[from+1..-1]
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Rotates the representation of the permutation (i.e. effectively does nothing if it represents a cycle)
|
68
|
+
# * Example: [1 2 3 4] to [3 4 1 2]
|
69
|
+
module RotationMutator
|
70
|
+
def mutate!
|
71
|
+
new_start = @genes.rand_index
|
72
|
+
@genes = @genes[new_start..-1] + @genes[0...new_start]
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# * One point partial preservation crossover for PermutationGenotype
|
79
|
+
# * Also known as partial recombination crossover (PRX).
|
80
|
+
# * Child 1 is identical to parent 1 up to the cross point, and contains the remaining elements in the same order as parent 2.
|
53
81
|
module PermutationCrossover
|
54
82
|
def cross(parent1,parent2)
|
55
83
|
p1, p2 = parent1.genes, parent2.genes
|