metaheuristics 0.1.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/README.md ADDED
@@ -0,0 +1,122 @@
1
+
2
+ Metaheustics
3
+ ============
4
+
5
+ A ruby library of metaheuristics for search/optimisation.
6
+
7
+
8
+ Usage
9
+ -----
10
+
11
+ For a given problem, the following must be implemented:
12
+ * a genome class (details below)
13
+ * a lambda taking no argument and producing a different randomly generated
14
+ genome each time it is called
15
+ * an evaluator lambda taking as argument a `genome` object, and returning a
16
+ fitness object which much implement the `Comparable` interface (usually
17
+ float is used)
18
+
19
+
20
+ The genome class must implements the following methods:
21
+ * `clone()` return a deep copy of the genome
22
+ * `mutate!(mutation_rate)` mutates each gene of the genome with probability `mutation_rate`, return `self`
23
+ * `crossover(genome)` return a new genome which is a combination of this genome and the `genome` parameter
24
+
25
+
26
+ require 'metaheuristics/metaheuristic_init_factory'
27
+
28
+ search_definition = {
29
+ :name => :tournament_selection,
30
+ :mutation_rate => 0.1 # depends on the number of genes in a genome
31
+ }
32
+
33
+ search_init = MetaheuristicInitFactory.from_definition(search_definition)
34
+
35
+ search = search_init.call(
36
+ :genome_init => genome_init, # the lambda generating random genomes
37
+ :evaluator => evaluator_minimiser) # the evaluator lambda
38
+
39
+ # run the search until the fitness is over 9000!
40
+ begin
41
+ search.run_once
42
+ end while search.results.best[:fitness] <= 9000
43
+
44
+
45
+ A rule of thumb for the mutation rate is to aim for one mutation per genome, the
46
+ mutation rate should then be the inverse of the number of genes in a genome.
47
+
48
+ The file `examples/example_minimiser.rb` demonstrates usage for a simple minimisation problem.
49
+
50
+
51
+ Implementing new algorithms
52
+ ---------------------------
53
+
54
+ New algorithms should implement the `MetaheuristicInterface`, and be given an
55
+ entry in `MetaheuristicInitFactory::from_definition()`. Then if you are happy
56
+ with the licensing, please send me a pull request!
57
+
58
+
59
+ Algorithms Details
60
+ ------------------
61
+
62
+ At the moment `metaheuristics` contains only Genetic Algorithms (GAs), but has
63
+ vocation to not only contain genome based methods, but also eventually
64
+ array-of-values based methods.
65
+
66
+ * The traditional tournament selection.
67
+ * An implementation of the ALPS paradigm using tournament selection in the layers.
68
+ * FgrnGa, a GA in which fittest individual are kept in the population across several generations.
69
+
70
+ If you don't know which one to use, ALPS is recommended.
71
+
72
+ For more on metaheuristics, check out [Sean Luke's great
73
+ book][http://cs.gmu.edu/~sean/book/metaheuristics/], available for download for
74
+ free, or in printed paper form for money.
75
+
76
+
77
+
78
+ ### Tournament selection
79
+
80
+ A simple, basic GA, where each parent is selected by taking a random sample
81
+ of the population, and choosing the fittest individual in that sample.
82
+
83
+
84
+ ### Age-Layered Population Structure (ALPS)
85
+
86
+ An implementation of the ALPS paradigm which aims to prevent premature
87
+ convergence, combined here with tournament selection in the layers.
88
+ [Greg Hornby's ALPS paper][http://idesign.ucsc.edu/papers/hornby_gecco06.pdf]
89
+
90
+
91
+ ### FgrnGa
92
+
93
+ An implementation of a GA keeping fit parents across multiple generations.
94
+ It has notably been used to evolve FGRN genomes, which explains the naming.
95
+ A description is available in Peter Bentley's thesis.
96
+
97
+
98
+ License
99
+ -------
100
+
101
+ This software is provided under an open source MIT-like license:
102
+
103
+ Copyright (c) 2012 Jean-Baptiste Krohn <http://susano.org>
104
+
105
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
106
+ this software and associated documentation files (the "Software"), to deal in
107
+ the Software without restriction, including without limitation the rights to
108
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
109
+ of the Software, and to permit persons to whom the Software is furnished to do
110
+ so, subject to the following conditions:
111
+
112
+ The above copyright notice and this permission notice shall be included in all
113
+ copies or substantial portions of the Software.
114
+
115
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
116
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
117
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
118
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
119
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
120
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
121
+ SOFTWARE.
122
+
@@ -0,0 +1,90 @@
1
+ require 'metaheuristics/metaheuristic_init_factory'
2
+
3
+ class GenomeValues
4
+
5
+ attr_reader :values
6
+
7
+ # randomly generate a new random genome with +size+ components in the real range [-1.0, 1.0]
8
+ def self.new_random(size)
9
+ self.new(Array.new(size){ (rand - 0.5) * 2.0 })
10
+ end
11
+
12
+ # ctor
13
+ def initialize(values)
14
+ @values = values
15
+ end
16
+
17
+ # clone
18
+ def clone
19
+ self.new(values.clone)
20
+ end
21
+
22
+ # mutate this genome, return self
23
+ def mutate!(mutation_rate)
24
+ @values.size.times do |i|
25
+ @values[i] += (rand - 0.5) * 0.05 if rand < mutation_rate
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ # crossover this genome with another one, and return the resulting offspring
32
+ def crossover(genome)
33
+ self_values = @values
34
+ other_values = genome.values
35
+
36
+ values =
37
+ Array.new(self_values.size) do |i|
38
+ [self_values, other_values][rand(2)][i]
39
+ end
40
+
41
+ GenomeValues.new(values)
42
+ end
43
+ end # GenomeValues
44
+
45
+
46
+ # lambda: generate a genome with 8 values randomly generated in the real range [-1.0, 1.0]
47
+ genome_init = lambda do
48
+ GenomeValues.new_random(8)
49
+ end
50
+
51
+ search_definition = {
52
+ :name => :tournament_selection,
53
+ :population_size => 100,
54
+ :tournament_size => 10,
55
+ :elitism => 5,
56
+ :mutation_rate => 0.2
57
+ }
58
+
59
+ search_init = MetaheuristicInitFactory.from_definition(search_definition)
60
+
61
+ # an evaluator which returns a higher fitness for components with lower
62
+ # distance to the origin point (i.e minimises the absolute value of the
63
+ # components).
64
+ evaluator_minimiser = lambda do |genome|
65
+ sum_of_squares = genome.values.collect{ |v| v * v }.inject(&:+)
66
+ fitness = 1.0 / (1.0 + sum_of_squares)
67
+
68
+ fitness
69
+ end
70
+
71
+ search = search_init.call(
72
+ :genome_init => genome_init,
73
+ :evaluator => evaluator_minimiser)
74
+
75
+ highest_fitness = 0.0
76
+
77
+ while highest_fitness < 0.99999999995
78
+ search.run_once
79
+
80
+ results = search.results
81
+ best_hash = results.best
82
+ generation = results.generation
83
+
84
+ highest_fitness = best_hash[:fitness]
85
+ fitness_string = ('%.10f' % highest_fitness).rjust(10)
86
+ generation_string = generation.to_s.rjust(5)
87
+ values_string = best_hash[:solution].values.collect{ |v| ('%.6f' % v).rjust(9) }.join(', ')
88
+ puts " generation #{generation_string} fitness: #{fitness_string} values: #{values_string}"
89
+ end
90
+
@@ -0,0 +1,229 @@
1
+ require 'metaheuristics/metaheuristic_interface'
2
+ require 'metaheuristics/search_results'
3
+ require 'utils/assert'
4
+
5
+ class AlpsGa < MetaheuristicInterface
6
+
7
+ # From Hornby 2006 ALPS
8
+ DEFAULTS = {
9
+ :layer_size => 100,
10
+ :max_layer_count => 10,
11
+ :tournament_size => 7,
12
+ :age_gap => 10,
13
+ :aging_scheme => :polynomial,
14
+ :layer_elitism => 3,
15
+ :overall_elitism => 0,
16
+ :parents_from_previous_layer => true
17
+ }
18
+
19
+ attr_reader :results
20
+
21
+ class Individual
22
+ include Comparable
23
+
24
+ attr_reader :age, :solution, :fitness
25
+
26
+ # ctor
27
+ def initialize(solution, age = 0)
28
+ @solution = solution
29
+ @age = age
30
+ @used = false
31
+ @fitness = nil
32
+ end
33
+
34
+ # tag as used
35
+ def tag_as_used
36
+ @used = true
37
+ end
38
+
39
+ # age
40
+ def age!
41
+ if @used
42
+ @used = false
43
+ @age += 1
44
+ end
45
+ end
46
+
47
+ # evaluate, return fitness
48
+ def evaluate(evaluator)
49
+ @fitness = evaluator.call(solution)
50
+ end
51
+
52
+ # <=>
53
+ def <=>(individual)
54
+ self.fitness <=> individual.fitness
55
+ end
56
+ end # Individual
57
+
58
+ # ctor
59
+ def initialize(options)
60
+ @layer_size = options[:layer_size ] || DEFAULTS[:layer_size ]
61
+ @max_layer_count = options[:max_layer_count ] || DEFAULTS[:max_layer_count ]
62
+ @tournament_size = options[:tournament_size ] || DEFAULTS[:tournament_size ]
63
+ @age_gap = options[:age_gap ] || DEFAULTS[:age_gap ]
64
+ @aging_scheme = options[:aging_scheme ] || DEFAULTS[:aging_scheme ]
65
+ @layer_elitism = options[:layer_elitism ] || DEFAULTS[:layer_elitism ]
66
+ @overall_elitism = options[:overall_elitism ] || DEFAULTS[:overall_elitism ]
67
+ @parents_from_previous_layer = options[:parents_from_previous_layer] || DEFAULTS[:parents_from_previous_layer]
68
+
69
+ @evaluator = options[:evaluator ] || (raise ArgumentError)
70
+ @mutation_rate = options[:mutation_rate ] || (raise ArgumentError)
71
+ @genome_init = options[:genome_init ] || (raise ArgumentError)
72
+
73
+ raise ArgumentError unless [:linear, :fibonacci, :polynomial, :exponential].include?(@aging_scheme)
74
+
75
+ @results = SearchResults.new
76
+ @layers = []
77
+ end
78
+
79
+ # run once
80
+ def run_once
81
+ generation = @results.generation
82
+ if generation == 0
83
+ @layers[0] = Array.new(@layer_size){ Individual.new(@genome_init.call) }
84
+ evaluate_layer(0)
85
+ else
86
+ # all but bottom layer
87
+ (@layers.size - 1).downto(1) do |i|
88
+ generate_new_population_layer(i)
89
+ evaluate_layer(i)
90
+ end
91
+
92
+ # bottom layer
93
+ if generation % @age_gap == 0
94
+ move_to_layer(@layers[0], 1)
95
+ @layers[0] = Array.new(@layer_size){ Individual.new(@genome_init.call) }
96
+ else
97
+ generate_new_population_layer(0)
98
+ end
99
+ evaluate_layer(0)
100
+ end
101
+
102
+ # age population
103
+ @layers.each do |l|
104
+ l.each do |individual|
105
+ individual.age!
106
+ end
107
+ end
108
+
109
+ # promote individuals
110
+ promote_individuals
111
+
112
+ @results.increment_generation
113
+
114
+ @layers.each.with_index do |l, idx|
115
+ l.sort!.reverse!
116
+ $stderr << "= layer #{idx} : max age #{max_layer_age(idx)} ---------------------------------------------------------------\n"
117
+ l.each do |i|
118
+ $stderr << " - #{i.solution.__id__.to_s.rjust(7)}, fitness #{i.fitness.to_i.to_s.rjust(6)}, age #{i.age.to_s.rjust(4)}\n"
119
+ end
120
+ end
121
+ $stderr << "\n"
122
+ end
123
+
124
+ private
125
+
126
+ # max age for layer +n+
127
+ def max_layer_age(n)
128
+ case(@aging_scheme)
129
+ when :linear ; n + 1
130
+ when :fibonacci ; fibonacci(n + 1)
131
+ when :polynomial ; n < 2 ? n + 1 : n ** 2
132
+ when :exponential; 2 ** n
133
+ else raise ArgumentError
134
+ end * @age_gap
135
+ end
136
+
137
+ # fibonacci
138
+ def fibonacci(n)
139
+ if n == 0 || n == 1
140
+ 1
141
+ else
142
+ fibonacci(n - 1) + fibonacci(n - 2)
143
+ end
144
+ end
145
+
146
+ # evaluate layer
147
+ def evaluate_layer(n)
148
+ @layers[n].each do |individual|
149
+ fitness = individual.evaluate(@evaluator)
150
+ @results.add_solution(individual.solution, fitness)
151
+ end
152
+ end
153
+
154
+ # generate new population layer
155
+ def generate_new_population_layer(n)
156
+ current_layer = @layers[n]
157
+
158
+ parent_population = ((n == 0 || !@parents_from_previous_layer) ? current_layer : (@layers[n - 1] + current_layer)).sort
159
+
160
+ new_population = []
161
+
162
+ # elitism
163
+ elitism = @layer_elitism
164
+ elitism = @overall_elitism if n == (@layers.size - 1) && @overall_elitism > elitism
165
+
166
+ if elitism > 0
167
+ current_layer.sort.reverse[0, [elitism, current_layer.size].min].each do |i|
168
+ new_population << i
169
+ end
170
+ end
171
+
172
+ (@layer_size - new_population.size).times do
173
+ parent1_index = Array.new(@tournament_size){ rand(parent_population.size) }.max
174
+ parent2_index = Array.new(@tournament_size){ rand(parent_population.size) }.reject{ |v| v == parent1_index }.max
175
+ while parent2_index == nil || parent2_index == parent1_index
176
+ parent2_index = rand(parent_population.size)
177
+ end
178
+
179
+ parent1 = parent_population[parent1_index]
180
+ parent2 = parent_population[parent2_index]
181
+
182
+ parent1.tag_as_used
183
+ parent2.tag_as_used
184
+ new_solution = parent1.solution.crossover(parent2.solution).mutate!(@mutation_rate)
185
+ new_population << Individual.new(new_solution, [parent1.age, parent2.age].max + 1)
186
+ end
187
+
188
+ @layers[n] = new_population
189
+ end
190
+
191
+ # promote individuals
192
+ def promote_individuals
193
+ layer_count = @layers.size
194
+
195
+ ((layer_count == @max_layer_count) ? (layer_count - 2) : (layer_count - 1)).downto(0) do |n|
196
+ layer = @layers[n]
197
+ # take old ones from current layer
198
+ max_age = max_layer_age(n)
199
+ oldies = layer.select{ |i| i.age >= max_age }
200
+ if !oldies.empty?
201
+ layer.delete_if{ |i| i.age >= max_age }
202
+ move_to_layer(oldies, n + 1) # try to fit them in with the layer above
203
+ end
204
+ end
205
+ end
206
+
207
+ # try to fit individual within layer
208
+ def move_to_layer(individuals, n)
209
+ # add layer
210
+ if n > (@layers.size - 1)
211
+ assert{ n < @max_layer_count }
212
+ assert{ n == @layers.size }
213
+ @layers[n] = []
214
+ end
215
+
216
+ # add oldies in layer
217
+ a = (@layers[n] + individuals).sort do |a, b|
218
+ cmp_fitness = a.fitness <=> b.fitness
219
+ if cmp_fitness == 0
220
+ a.age <=> b.age
221
+ else
222
+ cmp_fitness
223
+ end
224
+ end.reverse
225
+
226
+ @layers[n] = a[0, [a.size, @layer_size].min]
227
+ end
228
+ end # AlpsGa
229
+
@@ -0,0 +1,86 @@
1
+ require 'metaheuristics/metaheuristic_interface'
2
+ require 'metaheuristics/individual_solution'
3
+
4
+ class FgrnGa < MetaheuristicInterface
5
+
6
+ DEFAULTS = {
7
+ :age_max => 10,
8
+ :children_count => 80,
9
+ :parents_coeff => 0.4,
10
+ :population_size => 100,
11
+ :random_parent_coeff => 0.01
12
+ }
13
+
14
+ attr_reader :results, :population
15
+
16
+ # ctor
17
+ def initialize(options)
18
+ @children_count = options[:children_count ] || DEFAULTS[:children_count ]
19
+ @parents_coeff = options[:parents_coeff ] || DEFAULTS[:parents_coeff ]
20
+ @population_size = options[:population_size ] || DEFAULTS[:population_size ]
21
+ @random_parent_coeff = options[:random_parent_coeff ] || DEFAULTS[:random_parent_coeff]
22
+ @age_max = options[:age_max ] || DEFAULTS[:age_max ]
23
+ @evaluate_children_only = options[:evaluate_children_only] || false
24
+ @evaluator = options[:evaluator ] || (raise ArgumentError)
25
+ @mutation_rate = options[:mutation_rate ] || (raise ArgumentError)
26
+
27
+ genome_init = options[:genome_init] || (raise ArgumentError)
28
+ @initial_genomes = Array.new(@population_size){ genome_init.call }
29
+
30
+ @results = SearchResults.new
31
+ end
32
+
33
+ # run once
34
+ def run_once
35
+ if @initial_genomes
36
+ @population = @initial_genomes.collect{ |genome| IndividualSolution.new(genome, @evaluator, @results) }
37
+ @initial_genomes = nil
38
+ else
39
+ # generate children
40
+ children =
41
+ Array.new(@children_count) do
42
+ parent1 = pick_parent
43
+ parent2 = pick_parent
44
+
45
+ genome = (parent1.crossover(parent2)).mutate!(@mutation_rate)
46
+ IndividualSolution.new(genome, @evaluator, @results)
47
+ end
48
+
49
+ @population.each{ |individual| individual.increment_age } # increment age
50
+ @population.reject!{ |individual| individual.age >= @age_max } # reject too old
51
+
52
+ # carry over the best
53
+ carried_over_count = @population_size - @children_count
54
+ carried_over = @population[0, [carried_over_count, @population.size].min]
55
+
56
+ # re-evaluate carried over
57
+ if !@evaluate_children_only
58
+ carried_over.each do |individual|
59
+ individual.reevaluate
60
+ end
61
+ end
62
+
63
+ # merge carried over and children
64
+ @population = carried_over + children
65
+ end
66
+
67
+ @population.sort!.reverse!
68
+ @results.increment_generation
69
+
70
+ # debug: print population
71
+ @population.each do |i|
72
+ $stderr << "#{('%d' % i.fitness).rjust(5)} - #{i.__id__.to_s.rjust(10)} - age #{'X' * i.age}\n"
73
+ end
74
+ end # run_once
75
+
76
+ private
77
+ # pick parent
78
+ def pick_parent
79
+ index =
80
+ rand < @random_parent_coeff ?
81
+ rand(@population.size) : # pick a parent at random
82
+ rand([(@parents_coeff * @population_size).round, @population.size].min) # pick a parent amongst the best individuals
83
+ @population[index].solution
84
+ end
85
+ end # FgrnGa
86
+
@@ -0,0 +1,77 @@
1
+ require 'metaheuristics/metaheuristic_interface'
2
+ require 'metaheuristics/individual_solution'
3
+ require 'metaheuristics/search_results'
4
+
5
+ class TournamentSelection < MetaheuristicInterface
6
+
7
+ DEFAULTS = {
8
+ :population_size => 100,
9
+ :tournament_size => 4,
10
+ :elitism => 0
11
+ }
12
+
13
+ attr_reader :results
14
+
15
+ # ctor
16
+ def initialize(options)
17
+ @population_size = options[:population_size] || DEFAULTS[:population_size]
18
+ @tournament_size = options[:tournament_size] || DEFAULTS[:tournament_size]
19
+ @elitism = options[:elitism ] || DEFAULTS[:elitism ]
20
+ @evaluator = options[:evaluator ] || (raise ArgumentError)
21
+ @mutation_rate = options[:mutation_rate ] || (raise ArgumentError)
22
+
23
+ # population
24
+ genome_init = options[:genome_init] || (raise ArgumentError)
25
+ @initial_genomes = Array.new(@population_size){ genome_init.call }
26
+ @population = nil
27
+
28
+ # results
29
+ @results = SearchResults.new
30
+ end
31
+
32
+ # run once
33
+ def run_once
34
+
35
+ # genomes
36
+ genomes =
37
+ if @initial_genomes # use initial genomes
38
+ ig = @initial_genomes
39
+ @initial_genomes = nil
40
+ ig
41
+ else # not initial run: generate new genomes
42
+ array = Array.new(@population_size)
43
+
44
+ # elitism
45
+ @elitism.times do |i|
46
+ array[i] = @population[i].solution
47
+ end
48
+
49
+ # generate children
50
+ (@population_size - @elitism).times do |i|
51
+ parent1 = pick_parent
52
+ parent2 = pick_parent
53
+
54
+ array[@elitism + i] = parent1.crossover(parent2).mutate!(@mutation_rate)
55
+ end
56
+
57
+ array
58
+ end
59
+
60
+ # evaluate genomes into the population
61
+ @population = genomes.collect do |genome|
62
+ IndividualSolution.new(genome, @evaluator, @results)
63
+ end
64
+
65
+ @population.sort!.reverse!
66
+ @results.increment_generation
67
+
68
+ self
69
+ end # run_once
70
+
71
+ private
72
+ # pick parent
73
+ def pick_parent
74
+ @population.sample(@tournament_size).max.solution
75
+ end
76
+ end # TournamentSelection
77
+
@@ -0,0 +1,42 @@
1
+
2
+ class IndividualSolution
3
+ include Comparable
4
+
5
+ attr_reader \
6
+ :age,
7
+ :solution,
8
+ :fitness
9
+
10
+ # ctor
11
+ def initialize(solution, evaluator, results)
12
+ @solution = solution
13
+ @evaluator = evaluator
14
+ @results = results
15
+ @age = 0
16
+ evaluate
17
+ end
18
+
19
+ # increment age
20
+ def increment_age
21
+ @age += 1
22
+ end
23
+
24
+ # re-evaluate fitness
25
+ def reevaluate
26
+ raise RuntimeError if @fitness.nil?
27
+ evaluate
28
+ end
29
+
30
+ # <=>
31
+ def <=>(es)
32
+ @fitness <=> es.fitness
33
+ end
34
+
35
+ private
36
+ # evaluate
37
+ def evaluate
38
+ @fitness = @evaluator.call(@solution)
39
+ @results.add_solution(@solution, @fitness)
40
+ end
41
+ end # IndividualSolution
42
+
@@ -0,0 +1,79 @@
1
+ require 'metaheuristics/genome_search/alps_ga'
2
+ require 'metaheuristics/genome_search/fgrn_ga'
3
+ require 'metaheuristics/genome_search/tournament_selection'
4
+
5
+ class MetaheuristicInitFactory
6
+
7
+ # from definition
8
+ def self.from_definition(definition)
9
+ name = definition[:name]
10
+ case(name)
11
+
12
+ # alps
13
+ when :alps
14
+ args = {
15
+ :layer_size => definition[:layer_size ],
16
+ :max_layer_count => definition[:max_layer_count],
17
+ :tournament_size => definition[:tournament_size],
18
+ :age_gap => definition[:age_gap ],
19
+ :aging_scheme => definition[:aging_scheme ],
20
+ :layer_elitism => definition[:layer_elitism ],
21
+ :overall_elitism => definition[:overall_elitism],
22
+ :mutation_rate => definition[:mutation_rate ] || (raise ArgumentError)
23
+ }
24
+
25
+ # init block
26
+ lambda do |options|
27
+ genome_init = options[:genome_init] || (raise ArgumentError)
28
+ evaluator = options[:evaluator] || (raise ArgumentError)
29
+
30
+ AlpsGa.new(args.merge(
31
+ :genome_init => genome_init,
32
+ :evaluator => evaluator))
33
+ end
34
+
35
+ # fga
36
+ when :fga
37
+ args = {
38
+ :population_size => definition[:population_size ] || (raise ArgumentError),
39
+ :children_count => definition[:children_count ] || (raise ArgumentError),
40
+ :mutation_rate => definition[:mutation_rate ] || (raise ArgumentError),
41
+ :parents_coeff => definition[:parents_coeff ] || (raise ArgumentError),
42
+ :random_parent_coeff => definition[:random_parent_coeff ] || (raise ArgumentError),
43
+ :age_max => definition[:age_max ] || (raise ArgumentError),
44
+ :evaluate_children_only => definition[:evaluate_children_only] || false
45
+ }
46
+
47
+ # init block
48
+ lambda do |options|
49
+ genome_init = options[:genome_init] || (raise ArgumentError)
50
+ evaluator = options[:evaluator] || (raise ArgumentError)
51
+
52
+ FgrnGa.new(args.merge(
53
+ :genome_init => genome_init,
54
+ :evaluator => evaluator))
55
+ end
56
+
57
+ # tournament selection
58
+ when :tournament_selection
59
+ args = {
60
+ :mutation_rate => definition[:mutation_rate ] || (raise ArgumentError),
61
+ :population_size => definition[:population_size],
62
+ :elitism => definition[:elitism ],
63
+ :tournament_size => definition[:tournament_size]
64
+ }
65
+
66
+ # init block
67
+ lambda do |options|
68
+ genome_init = options[:genome_init] || (raise ArgumentError)
69
+ evaluator = options[:evaluator] || (raise ArgumentError)
70
+
71
+ TournamentSelection.new(args.merge(
72
+ :genome_init => genome_init,
73
+ :evaluator => evaluator))
74
+ end
75
+
76
+ else raise ArgumentError, name end
77
+ end # from_definition
78
+ end # MetaheuristicInitFactory
79
+
@@ -0,0 +1,13 @@
1
+
2
+ # The interface to be implemented by metaheuristics algorithms.
3
+ class MetaheuristicInterface
4
+
5
+ # Should return a +SearchResult+ object (or an object with the same interface
6
+ # as +SearchResult+)
7
+ def results ; raise NotImplementedError; end
8
+
9
+ # Run one search iteration, this should include the evaluation of the fitness
10
+ # of one or more individuals (e.g. a population generation for a GA)
11
+ def run_once; raise NotImplementedError; end
12
+ end # MetaheuristicInterface
13
+
@@ -0,0 +1,44 @@
1
+
2
+
3
+ class SearchResults
4
+
5
+ attr_reader \
6
+ :generation,
7
+ :evaluation,
8
+ :best # hash containing the keys : fitness, solution, evaluation, generation
9
+
10
+ # ctor
11
+ def initialize
12
+ @generation = 0
13
+ @evaluation = 0
14
+ @best = nil
15
+ end
16
+
17
+ # increment generation
18
+ def increment_generation
19
+ @generation += 1
20
+ end
21
+
22
+ # add solution
23
+ def add_solution(solution, fitness)
24
+ @evaluation += 1
25
+ if @best.nil? || fitness > @best[:fitness]
26
+ @best = {
27
+ :fitness => fitness,
28
+ :solution => solution,
29
+ :evaluation => @evaluation,
30
+ :generation => @generation
31
+ }
32
+ end
33
+ end
34
+
35
+ # to hash
36
+ def to_hash
37
+ {
38
+ :generation => @generation,
39
+ :evaluation => @evaluation,
40
+ :best => @best.clone
41
+ }
42
+ end
43
+ end # SearchResults
44
+
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metaheuristics
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Jean Krohn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-29 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Metaheuristics include Genetic Algorithms (GAs), such as ALPS and tournament selection.
15
+ email: jbk@susano.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - lib/metaheuristics/individual_solution.rb
22
+ - lib/metaheuristics/search_results.rb
23
+ - lib/metaheuristics/metaheuristic_interface.rb
24
+ - lib/metaheuristics/metaheuristic_init_factory.rb
25
+ - lib/metaheuristics/genome_search/alps_ga.rb
26
+ - lib/metaheuristics/genome_search/fgrn_ga.rb
27
+ - lib/metaheuristics/genome_search/tournament_selection.rb
28
+ - examples/example_minimiser.rb
29
+ homepage: http://github.com/susano/metaheuristics
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: !binary |-
40
+ MA==
41
+ none: false
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: !binary |-
47
+ MA==
48
+ none: false
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.24
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Metaheuristic search/optimisation algorithms.
55
+ test_files: []