metaheuristics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []