revac 1.0.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 (9) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/LICENSE +21 -0
  4. data/README.md +4 -0
  5. data/Rakefile +54 -0
  6. data/VERSION +1 -0
  7. data/lib/revac.rb +207 -0
  8. data/test/.gitkeep +0 -0
  9. metadata +112 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d7c4ca351b69ea97d4e61ce1a0e2a615204c22b4
4
+ data.tar.gz: fa2f20c8a5f837c9ebe29db0e95fdffb0f9733c0
5
+ SHA512:
6
+ metadata.gz: e78e574cde74e8822b3dbf17b4766e208cb63c87ad6c3cd5b6e64cbe506c5bfc87fc4d8635a60431189770246526a26552280e23e7510f156762ae2438ddf26b
7
+ data.tar.gz: a05be6bc722d77ddf0006e18f75eac50d906a5be3d468f7b84ac4b96274a2d0fa834caefe04a823a9f64ad5b820c52b856eb788690db205eefd4eab1383a1137
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ group :development do
6
+ gem 'jeweler'
7
+ gem 'rake'
8
+ gem 'rdoc'
9
+ gem 'bundler'
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Chris Timperley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ RubyREVAC
2
+ =========
3
+
4
+ A Ruby-based implementation of the REVAC meta-heuristic tuner (Relevance Estimation and Value Calibration of Evolutionary Algorithm Parameters).
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ # Load all dependencies.
4
+ require 'rubygems'
5
+ require 'bundler'
6
+ require 'rake'
7
+ require 'rake/testtask'
8
+ require 'rdoc/task'
9
+ require 'jeweler'
10
+
11
+ begin
12
+ Bundler.setup(:default, :development)
13
+ rescue Bundler::BundlerError => e
14
+ $stderr.puts e.message
15
+ $stderr.puts "Run `bundle install` to install missing gems"
16
+ exit e.status_code
17
+ end
18
+
19
+ # Gem Specification.
20
+ Jeweler::Tasks.new do |gem|
21
+ gem.name = "revac"
22
+ gem.homepage = "http://github.com/ChrisTimperley/RubyREVAC"
23
+ gem.license = "MIT"
24
+ gem.summary = "Implementation of REVAC tuner for meta-heuristics (Relevance Estimation and Value Calibration
25
+ of Evolutionary Algorithm Parameters)."
26
+ gem.description = <<-EOF
27
+ An implementation of the REVAC tuner (Relevance Estimation and Value Calibration
28
+ of Evolutionary Algorithm Parameters) proposed by V. Nannen and A.E. Eiben, extended to work with arbitrary meta-heuristics
29
+ implemented on arbitrary platforms.
30
+ EOF
31
+ gem.email = "christimperley@gmail.com"
32
+ gem.author = "Chris Timperley"
33
+ end
34
+
35
+ # Gem Management.
36
+ Jeweler::RubygemsDotOrgTasks.new
37
+
38
+ # Unit Testing.
39
+ Rake::TestTask.new(:test) do |test|
40
+ test.libs << 'lib' << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ end
44
+
45
+ # Documentation.
46
+ task :default => :test
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "revac #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,207 @@
1
+ # encoding: utf-8
2
+ require 'CSV' unless defined?(CSV)
3
+
4
+ # Implements the REVAC tuning method (Relevance Estimation and Value
5
+ # Calibration of Evolutionary Algorithm Parameters), proposed by Nannen and
6
+ # Eiben, which provides a fast and rational approach to parameter tuning for
7
+ # meta-heuristics.
8
+ #
9
+ # Although originally designed for finding the optimal parameter setup for
10
+ # evolutionary algorithms, this REVAC implementation may also be used to
11
+ # tune other meta-heuristics, such as Ant Colony Optimisation or Particle
12
+ # Swarm Optimisation.
13
+ module REVAC
14
+
15
+ # Used to hold the name and range of legal values for a search parameter.
16
+ Parameter = Struct.new(:name, :range)
17
+
18
+ # Logs a parameter vector and its associated utility function value using
19
+ # a given CSV file.
20
+ #
21
+ # ==== Parameters
22
+ # [+path+] The path to the output CSV file.
23
+ # [+evaluation+] The current evaluation.
24
+ # [+vector+] The parameter vector table.
25
+ # [+utility+] The utility of the given vector.
26
+ def self.log(path, evaluation, vector, utility)
27
+ CSV.open(path, 'ab') do |f|
28
+ f << [evaluation] + vector + [utility]
29
+ end
30
+ end
31
+
32
+ # Computes the utility (response) of a given parameter vector.
33
+ #
34
+ # ==== Parameters
35
+ # [+vector+] The parameter vector to evaluate.
36
+ # [+parameters+] The list of tunable parameters.
37
+ # [+algorithm+] A function which takes the a hash of parameter
38
+ # values as input, performs a given algorithm and
39
+ # returns the best fitness value found.
40
+ # [+runs+] The number of runs to use for each parameter
41
+ # vector.
42
+ def self.evaluate_vector(vector, parameters, algorithm, runs)
43
+
44
+ # Transform the vector into a hash of parameter names and
45
+ # settings and compute the mean best fitness using this
46
+ # parameter vector across a given number of runs.
47
+ vector = Hash[vector.to_a.each_with_index.map { |v, i|
48
+ [parameters[i].name, v]
49
+ }]
50
+ Array.new(runs) { algorithm[vector] }.inject(:+).to_f / runs
51
+
52
+ end
53
+
54
+ # Performs REVAC mutation on an individual at a given index within the vector
55
+ # table to produce a new mutated vector.
56
+ #
57
+ # ==== Parameters
58
+ # [+random+] The random number generator.
59
+ # [+table+] The vector table.
60
+ # [+index+] The index of the individual to mutate.
61
+ # [+h+] The radius of the partial marginal density function.
62
+ #
63
+ # ==== Returns
64
+ # The resulting vector.
65
+ def self.mutate(random, table, index, h)
66
+
67
+ # Sort the values of this parameter for all vectors into ascending order
68
+ # before calculating the upper and lower bounds of the mutation distribution
69
+ # and drawing a new parameter value at uniform random.
70
+ return Array.new(table[0].length) do |i|
71
+ window = (0...table.length).sort { |x, y| table[x][i] <=> table[y][i] }
72
+ position = window.find_index(index)
73
+ window = [
74
+ window[[position - h, 0].max],
75
+ window[[position + h, table.length - 1].min]
76
+ ]
77
+ window = table[window[0]][i] .. table[window[1]][i]
78
+ random.rand(window)
79
+ end
80
+
81
+ end
82
+
83
+ # Performs a multi-parent crossover on a list of parent chromosomes to produce
84
+ # a new chromosome.
85
+ #
86
+ # ==== Parameters
87
+ # [+random+] The random number generator.
88
+ # [+parents+] The parents of the child chromosome.
89
+ #
90
+ # ==== Returns
91
+ # The proto-child vector of the provided parent vectors.
92
+ def self.crossover(random, parents)
93
+ Array.new(parents[0].length) { |i| parents[random.rand(parents.length)][i] }
94
+ end
95
+
96
+ # Tunes a given algorithm using REVAC.
97
+ #
98
+ # ==== Parameters
99
+ # [+parameters+] A list of parameters to optimise. Each entry in the
100
+ # list contains the name of the parameter and the range
101
+ # of values that it can take.
102
+ # [+opts+] A hash of keyword options to this method.
103
+ # [+&algorithm+] A lambda function which takes a hash of named parameters
104
+ # as its input, uses them to perform a given algorithm,
105
+ # and returns the fitness of the best individual found.
106
+ #
107
+ # ==== Options
108
+ # [+evaluations+] The maximum number of evaluations to perform.
109
+ # [+parents+] The number of parents to use when creating each child.
110
+ # [+runs+] The number of runs to perform for each vector.
111
+ # [+vectors+] The number of parameter vectors in the population.
112
+ # [+h+] The radius of the partial marginal density function.
113
+ # [+log+] The path to the output CSV file.
114
+ # [+debug+] Used to enable / disable evaluation logging to the console.
115
+ #
116
+ # ==== Returns
117
+ # A hash containing the `optimal' parameter values for the given problem.
118
+ def self.tune(parameters, opts = {}, &algorithm)
119
+
120
+ # Load the default values for any omitted parameters.
121
+ opts[:vectors] ||= 80
122
+ opts[:parents] ||= 40
123
+ opts[:h] ||= 5
124
+ opts[:runs] ||= 5
125
+ opts[:evaluations] ||= 5000
126
+ opts[:debug] ||= false
127
+
128
+ # The RNG to use during optimisation.
129
+ random = Random.new
130
+
131
+ # Convert the list of parameter name/range pairs into a list of parameter
132
+ # objects.
133
+ parameters = parameters.map { |n, r| Parameter.new(n, r) }
134
+
135
+ # Initialise evolution statistics.
136
+ oldest = 0
137
+ iterations = 0
138
+ evaluations = 0
139
+
140
+ # Initialise the output CSV file (if one has been given).
141
+ CSV.open(opts[:log], 'wb') do |f|
142
+ f << ['Evaluation'] + parameters.map { |p| p.name } + ['Utility']
143
+ end if opts[:log]
144
+
145
+ # Draw an initial set of parameter vectors at uniform random from
146
+ # their initial distributions.
147
+ table = Array.new(opts[:vectors]) do
148
+ parameters.map { |p| random.rand(p.range) }
149
+ end
150
+
151
+ # Compute the utility of each parameter vector, before finding and recording
152
+ # the best parameter vector.
153
+ utility = table.map do |v|
154
+ u = evaluate_vector(v, parameters, algorithm, opts[:runs])
155
+ log(opts[:output], evaluations, v, u) if opts[:log]
156
+ puts "Evaluation #{evaluations}: #{u}" if opts[:debug]
157
+ evaluations += 1
158
+ u
159
+ end
160
+ best_utility, best_vector = utility.each_with_index.min
161
+ best_vector = table[best_vector]
162
+
163
+ # Keep optimising until the termination condition is met.
164
+ until evaluations == opts[:evaluations]
165
+
166
+ # Select the N-best vectors from the table as the parents
167
+ # of the next parameter vector.
168
+ parents = (0...opts[:vectors]).sort { |x, y|
169
+ utility[x] <=> utility[y]
170
+ }.take(opts[:parents]).map { |i|
171
+ table[i]
172
+ }
173
+
174
+ # Perform multi-parent crossover on the N parents to create
175
+ # a proto-child vector.
176
+ child = crossover(random, parents)
177
+
178
+ # Replace the oldest vector from the population with this
179
+ # proto-child vector, before mutating it and computing its
180
+ # utility.
181
+ table[oldest] = child
182
+ table[oldest] = mutate(random, table, oldest, opts[:h])
183
+ utility[oldest] = evaluate_vector(child, parameters, algorithm, opts[:runs])
184
+
185
+ # Update the best vector if the child vector is an improvement.
186
+ if utility[oldest] < best_utility
187
+ best_vector = table[oldest]
188
+ best_utility = utility[oldest]
189
+ end
190
+
191
+ # Update evolution statistics and perform logging.
192
+ iterations += 1
193
+ evaluations += 1
194
+ log(opts[:output], evaluations, child, utility[oldest]) if opts[:log]
195
+ puts "Evaluation #{evaluations}: #{utility[oldest]}" if opts[:debug]
196
+ oldest = (oldest + 1) % opts[:vectors]
197
+
198
+ end
199
+
200
+ # Return the optimal parameter vector as a hash.
201
+ return Hash[parameters.zip(0...parameters.length).map { |param, index|
202
+ [param.name, best_vector[index]]
203
+ }]
204
+
205
+ end
206
+
207
+ end
File without changes
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: revac
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Timperley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jeweler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: |2
70
+ An implementation of the REVAC tuner (Relevance Estimation and Value Calibration
71
+ of Evolutionary Algorithm Parameters) proposed by V. Nannen and A.E. Eiben, extended to work with arbitrary meta-heuristics
72
+ implemented on arbitrary platforms.
73
+ email: christimperley@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files:
77
+ - LICENSE
78
+ - README.md
79
+ files:
80
+ - Gemfile
81
+ - LICENSE
82
+ - README.md
83
+ - Rakefile
84
+ - VERSION
85
+ - lib/revac.rb
86
+ - test/.gitkeep
87
+ homepage: http://github.com/ChrisTimperley/RubyREVAC
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.0.14
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Implementation of REVAC tuner for meta-heuristics (Relevance Estimation and
111
+ Value Calibration of Evolutionary Algorithm Parameters).
112
+ test_files: []