rugal 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 96e1028fc4474ef75947b5ebf8651de4069c16a8ed9c866f87fb9069e88e6dbc
4
+ data.tar.gz: ce64b85b190763f57d1e82883bba6ad02c1473e3862831bbe7fe545be9bf19b7
5
+ SHA512:
6
+ metadata.gz: e7e625f0dadad4179733793b23e10fd0c1dd01d2a97bb19b0d4b5c8147a77234720ad0994ae8d2a8e02f1d67b91096c937c22267e62eb6e85618b547f3223b3b
7
+ data.tar.gz: 073ff9d28d33a60082b070758e10742bad476366e49669228147d8420a87b8bd79d08a529359ecc9d9f5c0233e625ec79fd801f9654a75f04de62428912bb07c
@@ -0,0 +1,110 @@
1
+ require_relative "operators"
2
+ require_relative "rugal"
3
+ require_relative "solution"
4
+
5
+ module GA
6
+ class Algorithm
7
+ def initialize(factory, options={})
8
+ @factory = factory
9
+
10
+ @selection = options[:selection] || GA::Operators::Selection::RouletteWheel.new
11
+ @crossover = options[:crossover] || GA::Operators::Crossover::SinglePoint.new
12
+ @mutation = options[:mutation] || GA::Operators::Mutation::Uniform.new
13
+ @operators = options[:operators] || []
14
+
15
+ @population_size = options[:population_size] || 100
16
+ @elite = options[:elite] || 0.1
17
+
18
+ GA.contract("Factory must not be nil") { factory }
19
+ GA.contract("Factory must be a SolutionFactory") { factory.is_a?(GA::Solutions::SolutionFactory) }
20
+ GA.contract("Elite must be non-negative (#@elite given)") { @elite >= 0 }
21
+ GA.contract("All the additional operators must be Operator") { @operators.all? { |op| op.is_a? GA::Operators::Operator } }
22
+ end
23
+ end
24
+
25
+ class ClassicGenetic < Algorithm
26
+ def initialize(factory, options={})
27
+ super
28
+ end
29
+
30
+ def maximize(iterations, options = {}, &fitness)
31
+ optimize(iterations, options, &fitness)
32
+ end
33
+
34
+ def minimize(iterations, options = {}, &fitness)
35
+ optimize(iterations, options) { |chromosome| -fitness.call(chromosome) }
36
+ end
37
+
38
+ def optimize(iterations, options = {}, &fitness)
39
+ GA.contract("You must pass the fitness function as a block") { fitness }
40
+ optimum = options[:optimum] || nil
41
+
42
+ GA.contract("Iterations must be more than 0") { iterations & iterations > 0 }
43
+ GA.contract("Optimum must be numeric (or nil)") { !optimum || optimum.is_a?(Numeric) }
44
+
45
+ population = @factory.population(@population_size)
46
+
47
+ elite = []
48
+ iterations.times do |i|
49
+ GA::Logger.info "Generation #{i}"
50
+ population.each { |chromosome| chromosome.fitness = fitness.call(chromosome) }
51
+
52
+ population = population - population.min(elite.size) { |chromosome| chromosome.fitness } + elite
53
+
54
+ elite = population.max(@elite * @population_size) { |chromosome| chromosome.fitness }
55
+ if optimum
56
+ return elite[0] if elite[0].fitness == optimum
57
+ end
58
+
59
+ population = @selection.apply(population)
60
+ population = @crossover.apply(population)
61
+ population = @mutation.apply(population)
62
+
63
+ # Uses additional operators
64
+ @operators.each do |operator|
65
+ population = operator.apply(population)
66
+ end
67
+
68
+ GA::Logger.info { "Average fitness: #{population.map { |c| c.fitness }.sum.to_f / population.size}" }
69
+ end
70
+
71
+ return population.max { |chromosome| fitness.call(chromosome) }
72
+ end
73
+ end
74
+
75
+ class RandomSearch
76
+ def initialize(factory, options = {})
77
+ @factory = factory
78
+ @population_size = options[:population_size] || 1
79
+ end
80
+
81
+ def maximize(iterations, options = {}, &fitness)
82
+ optimize(iterations, options, &fitness)
83
+ end
84
+
85
+ def minimize(iterations, options = {}, &fitness)
86
+ optimize(iterations, options) { |chromosome| -fitness.call(chromosome) }
87
+ end
88
+
89
+ def optimize(iterations, options = {}, &fitness)
90
+ optimum = options[:optimum] || nil
91
+
92
+ GA.contract("Iterations must be more than 0") { iterations > 0 }
93
+ GA.contract("Optimum must be numeric (or nil)") { !optimum || optimum.is_a?(Numeric) }
94
+
95
+ best = nil
96
+ iterations.times do |i|
97
+ GA::Logger.info "Generation #{i}"
98
+ population = @factory.population(@population_size)
99
+ population.each { |chromosome| chromosome.fitness = fitness.call(chromosome) }
100
+
101
+ current_best = population.max { |chromosome| chromosome.fitness}
102
+ best = current_best if !best || current_best.fitness > best.fitness
103
+
104
+ GA::Logger.info { "Average fitness: #{population.map { |c| c.fitness }.sum.to_f / population.size}" }
105
+ end
106
+
107
+ return best
108
+ end
109
+ end
110
+ end
data/lib/operators.rb ADDED
@@ -0,0 +1,295 @@
1
+ require_relative "genetic_algorithm"
2
+ require_relative "rugal"
3
+ require_relative "solution"
4
+
5
+ module GA::Operators
6
+ class Operator
7
+ end
8
+ end
9
+
10
+ module GA::Operators::Selection
11
+ class Basic < GA::Operators::Operator
12
+ end
13
+
14
+ class RouletteWheel < Basic
15
+ def apply(population)
16
+ min_fitness = population.map(&:fitness).min.abs + 1
17
+ fitnesses = population.map { |chromosome| chromosome.fitness != 0 ? chromosome.fitness + min_fitness : 0}
18
+
19
+ total_fitness = fitnesses.sum
20
+
21
+ return population.dup if total_fitness == 0
22
+
23
+ current_total = 0.0
24
+ selection_array = []
25
+ fitnesses.each do |fitness|
26
+ current_total += fitness.to_f / total_fitness
27
+ selection_array.push current_total
28
+ end
29
+
30
+ offspring = []
31
+ population.size.times do
32
+ value = GA::Random.rand
33
+ selected_index = selection_array.index { |v| value <= v }
34
+ offspring.push population[selected_index].dup
35
+ end
36
+
37
+ return offspring
38
+ end
39
+ end
40
+
41
+ class Random < Basic
42
+ def apply(population)
43
+ offspring = []
44
+ population.size.times do
45
+ index = GA::Random.rand(population.size)
46
+ offspring.push population[index].dup
47
+ end
48
+ return offspring
49
+ end
50
+ end
51
+ end
52
+
53
+ module GA::Operators::Crossover
54
+ class Basic < GA::Operators::Operator
55
+ def initialize(options = {})
56
+ @forbidden = []
57
+ end
58
+
59
+ protected
60
+ def crossover(population)
61
+ result = []
62
+
63
+ shuffled = GA::Random.shuffle(population)
64
+ shuffled.each_slice(2) do |c1, c2|
65
+ crossed = yield(c1, c2)
66
+ result += crossed
67
+ end
68
+
69
+ return result
70
+ end
71
+
72
+ def forbid(type)
73
+ @forbidden.push type
74
+ end
75
+
76
+ def apply(population)
77
+ check_population(population)
78
+ end
79
+
80
+ def check_population(population)
81
+ GA.contract("#{self.class} crossover does not accept #{@forbidden.join(", ")}") { population.none? { |c| @forbidden.include?(c.class) } }
82
+ end
83
+ end
84
+
85
+ class SinglePoint < Basic
86
+ def initialize(options = {})
87
+ super
88
+
89
+ @probability = options[:probability] || 0.8
90
+ GA.contract("Probability must be in the range [0, 1]") { @probability.between?(0, 1) }
91
+
92
+ forbid GA::Solutions::PermutationSolution
93
+ end
94
+
95
+ def apply(population)
96
+ super
97
+
98
+ crossover(population) do |c1, c2|
99
+ c1 = c1.dup
100
+ c2 = c2.dup
101
+
102
+ unless c2
103
+ [c1]
104
+ else
105
+ if GA::Random.with_probability(@probability)
106
+ crossover_position = GA::Random.rand([c1.size, c2.size].min)
107
+
108
+ for i in 0...crossover_position
109
+ c1.data[i], c2.data[i] = c2.data[i], c1.data[i]
110
+ end
111
+ end
112
+
113
+ [c1, c2]
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ class PMX < Basic
120
+ def initialize(options = {})
121
+ super
122
+
123
+ @probability = options[:probability] || 0.8
124
+ @percent = options[:size] || 0.5
125
+
126
+ GA.contract("Probability must be in the range [0, 1]") { @probability.between?(0, 1) }
127
+ GA.contract("Swath size percentage must be in the range (0, 1)") { @percent > 0 && @percent < 1 }
128
+
129
+ forbid GA::Solutions::IntegerSolution
130
+ forbid GA::Solutions::FloatSolution
131
+ forbid GA::Solutions::StringSolution
132
+ end
133
+
134
+ def apply(population)
135
+ super
136
+
137
+ crossover(population) do |c1, c2|
138
+ unless c2
139
+ [c1]
140
+ else
141
+ t1 = c1
142
+ t2 = c2
143
+ if GA::Random.with_probability(@probability)
144
+ size = GA::Random.rand_int(@percent * c1.size)
145
+ from = GA::Random.rand_int(c1.size - size)
146
+ to = from + size
147
+
148
+ c1, c2 = pmx(t1, t2, from, to)
149
+ end
150
+
151
+ [c1, c2]
152
+ end
153
+ end
154
+ end
155
+
156
+ private
157
+ def pmx(c1, c2, from, to)
158
+ range = from...to
159
+
160
+ child1 = c1.dup
161
+ child2 = c2.dup
162
+
163
+ swath1 = child1.data[range]
164
+ swath2 = child2.data[range]
165
+
166
+ uswath1 = swath1 - swath2
167
+ uswath2 = swath2 - swath1
168
+
169
+ mapping1to2 = {}
170
+ mapping2to1 = {}
171
+ for i in 0...uswath1.size
172
+ mapping1to2[uswath1[i]] = uswath2[i]
173
+ mapping2to1[uswath2[i]] = uswath1[i]
174
+ end
175
+
176
+ child1.data[range], child2.data[range] = child2.data[range], child1.data[range]
177
+
178
+ changed_indices = ((0...child1.size).to_a - range.to_a)
179
+ for i in changed_indices
180
+ child1.data[i] = mapping2to1[child1.data[i]] if swath2.include?(child1.data[i])
181
+ child2.data[i] = mapping1to2[child2.data[i]] if swath1.include?(child2.data[i])
182
+ end
183
+
184
+ # for i in ((0...child1.size).to_a - range.to_a)
185
+ # v = child2.data[i]
186
+ #
187
+ # if swath1.include?(v)
188
+ # rep = swath2[swath1.index(v)]
189
+ # child2.data[i] = rep
190
+ # end
191
+ # end
192
+
193
+ GA.postcondition("All data in the permutation must be used: #{child1.to_s}") { child1.valid? }
194
+ GA.postcondition("All data in the permutation must be used: #{child2.to_s}") { child2.valid? }
195
+
196
+ return child1, child2
197
+ end
198
+ end
199
+ end
200
+
201
+ module GA::Operators::Mutation
202
+ class Basic < GA::Operators::Operator
203
+ protected
204
+ def mutation(population)
205
+ result = []
206
+
207
+ population.each do |chromosome|
208
+ result.push yield(chromosome)
209
+ end
210
+
211
+ return result
212
+ end
213
+ end
214
+
215
+ class Uniform < Basic
216
+ def initialize(options = {})
217
+ @probability = options[:probability] || 0.01
218
+ @radius = options[:radius] || 1
219
+ @epsilon = options[:epsilon] || 0.01
220
+
221
+ @epsilon = @epsilon.to_f
222
+
223
+ GA.contract("Probability must be in the range [0, 1]") { @probability.between?(0, 1) }
224
+ GA.contract("Epsilon must be greater than 0") { @epsilon > 0 }
225
+ end
226
+
227
+ def apply(population)
228
+ mutation(population) do |chromosome|
229
+ if GA::Random.with_probability(@probability)
230
+ chromosome = chromosome.mutate(GA::Random.rand(chromosome.size)) do |v|
231
+ GA::Random.random_sign * [GA::Random.rand * @radius, @epsilon].max
232
+ end
233
+ end
234
+
235
+ chromosome
236
+ end
237
+ end
238
+ end
239
+
240
+ class Positional < Basic
241
+ def initialize(options = {})
242
+ @probability = options[:probability] || 0.01
243
+ @radius = options[:radius] || 1
244
+ @epsilon = options[:epsilon] || 1
245
+
246
+ GA.contract("Probability must be in the range [0, 1]") { @probability.between?(0, 1) }
247
+ GA.contract("Epsilon must be greater than 0") { @epsilon > 0 }
248
+ end
249
+
250
+ def apply(population)
251
+ mutation(population) do |chromosome|
252
+ if GA::Random.with_probability(@probability)
253
+ chromosome = chromosome.dup
254
+
255
+ i1 = GA::Random.rand(chromosome.size)
256
+ i2 = (i1 + [GA::Random.rand(@epsilon+1), @epsilon].max) % chromosome.size
257
+
258
+ chromosome.data[i1], chromosome.data[i2] = chromosome.data[i2], chromosome.data[i1]
259
+ end
260
+
261
+ chromosome
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ module GA::Operators::Additional
268
+ class Injector < GA::Operators::Operator
269
+ def initialize(factory, options = {})
270
+ @factory = factory
271
+
272
+ @probability = options[:probability] || 0.01
273
+ @size = options[:size] || 1
274
+
275
+ GA.contract("Factory cannot be nil") { @factory }
276
+ GA.contract("Factory must be a SolutionFactory") { @factory.is_a?(GA::Solutions::SolutionFactory) }
277
+ GA.contract("Probability must be in the range [0, 1]") { @probability.between?(0, 1) }
278
+ GA.contract("Size must be greater than 0") { @size > 0 }
279
+ end
280
+
281
+ def apply(population)
282
+ if GA::Random.with_probability @probability
283
+ population = population.dup
284
+ size = [GA::Random.rand(@size + 1), 1].max
285
+
286
+ to_remove = population.sample size
287
+
288
+ population -= to_remove
289
+ population += @factory.population size
290
+ end
291
+
292
+ return population
293
+ end
294
+ end
295
+ end
data/lib/rugal.rb ADDED
@@ -0,0 +1,179 @@
1
+ module Math
2
+ Random = Random
3
+ end
4
+
5
+ module GA
6
+ module Random
7
+ @@random = Math::Random.new
8
+
9
+ def self.set_seed(i)
10
+ @@random = Math::Random.new i
11
+ end
12
+
13
+ def self.random
14
+ @@random
15
+ end
16
+
17
+ def self.shuffle(array)
18
+ GA.contract("Shuffle needs an array") { array.is_a?(Array) }
19
+
20
+ return array.shuffle(random: @@random)
21
+ end
22
+
23
+ def self.rand(*args)
24
+ @@random.rand *args
25
+ end
26
+
27
+ def self.rand_int(*args)
28
+ self.rand(*args).to_i
29
+ end
30
+
31
+ def self.with_probability(probability)
32
+ GA.contract("The probability must be in the range [0, 1] (#{probability} given)") { probability.between?(0, 1) }
33
+
34
+ return self.rand <= probability
35
+ end
36
+
37
+ def self.random_sign
38
+ value = self.rand(2)
39
+
40
+ return value == 0 ? -1 : 1
41
+ end
42
+ end
43
+
44
+ module Logger
45
+ LEVEL_DEBUG = 0
46
+ LEVEL_INFO = 1
47
+ LEVEL_WARNING = 2
48
+ LEVEL_SEVERE = 3
49
+
50
+ @@level = 0
51
+ def self.debug(*data, &block)
52
+ return if @@level > LEVEL_DEBUG
53
+ self.log "DEBUG", *data, &block
54
+ end
55
+
56
+ def self.info(*data, &block)
57
+ return if @@level > LEVEL_INFO
58
+ self.log "INFO", *data, &block
59
+ end
60
+
61
+ def self.warning(*data, &block)
62
+ return if @@level > LEVEL_WARNING
63
+ self.log "WARNING", *data, &block
64
+ end
65
+
66
+ def self.severe(*data, &block)
67
+ return if @@level > LEVEL_SEVERE
68
+ self.log "SEVERE", *data, &block
69
+ end
70
+
71
+ def self.level=(level)
72
+ GA.contract("The logger level must be between 0 and 3 (#{level} given)") { level.between?(0, 3) }
73
+ @@level = level
74
+ end
75
+
76
+ private
77
+ def self.log(type, *data, &block)
78
+ if block_given?
79
+ d = block.call
80
+ puts "#{type}: #{d}"
81
+ else
82
+ data.each do |d|
83
+ puts "#{type}: #{d}"
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.profile(profile)
90
+ case profile
91
+ when :development
92
+ @@assumptions_checking = :hard
93
+ GA::Logger.level = GA::Logger::LEVEL_DEBUG
94
+
95
+ when :testing
96
+ @@assumptions_checking = :hard
97
+ GA::Logger.level = GA::Logger::LEVEL_INFO
98
+
99
+ when :production
100
+ @@assumptions_checking = :soft
101
+ GA::Logger.level = GA::Logger::LEVEL_WARNING
102
+
103
+ when :stable_production
104
+ @@assumptions_checking = false
105
+ GA::Logger.level = GA::Logger::LEVEL_SEVERE
106
+
107
+ else
108
+ raise "Illegal profile #{profile}"
109
+ end
110
+ end
111
+
112
+ class ContractException < StandardError; end
113
+ class PostConditionException < StandardError; end
114
+ def self.contract(message = "Contract violated", condition = false, &block)
115
+ return unless @@assumptions_checking
116
+
117
+ raise TypeError, "no implicit conversion of #{message.class} into String" unless message.is_a? String
118
+
119
+ any_problem = false
120
+ if block_given?
121
+ any_problem = true unless yield
122
+ else
123
+ any_problem = true unless condition
124
+ end
125
+
126
+ if any_problem
127
+ case @@assumptions_checking
128
+ when :hard
129
+ raise ContractException, message
130
+ when :soft
131
+ GA::Logger.severe(message)
132
+ end
133
+ end
134
+ end
135
+
136
+ def self.postcondition(message = "Postcondition violated", condition = false, &block)
137
+ return unless @@assumptions_checking
138
+
139
+ raise TypeError, "no implicit conversion of #{message.class} into String" unless message.is_a? String
140
+
141
+ any_problem = false
142
+ if block_given?
143
+ any_problem = true unless yield
144
+ else
145
+ any_problem = true unless condition
146
+ end
147
+
148
+ if any_problem
149
+ case @@assumptions_checking
150
+ when :hard
151
+ raise ContractException, message
152
+ when :soft
153
+ GA::Logger.severe(message)
154
+ end
155
+ end
156
+ end
157
+
158
+ GA.profile :testing
159
+ end
160
+
161
+ def gadebug(*data)
162
+ GA::Logger.debug(*data)
163
+ end
164
+
165
+ def gainfo(*data)
166
+ GA::Logger.info(*data)
167
+ end
168
+
169
+ def gawarn(*data)
170
+ GA::Logger.warning(*data)
171
+ end
172
+
173
+ def gasevere(*data)
174
+ GA::Logger.severe(*data)
175
+ end
176
+
177
+ require_relative "genetic_algorithm"
178
+ require_relative "operators"
179
+ require_relative "solution"
data/lib/solution.rb ADDED
@@ -0,0 +1,181 @@
1
+ require_relative "operators"
2
+ require_relative "rugal"
3
+ require_relative "genetic_algorithm"
4
+
5
+ module GA::Solutions
6
+ class Solution
7
+ attr_accessor :fitness
8
+ attr_reader :data
9
+
10
+ def initialize
11
+ @data = nil
12
+ @fitness = nil
13
+ end
14
+
15
+ def size
16
+ @data.size
17
+ end
18
+
19
+ def mutate(i, &block)
20
+ duplicate = self.dup
21
+ duplicate.mutate!(i, &block)
22
+ return duplicate
23
+ end
24
+
25
+ def mutate!(i)
26
+ @data[i] = neighbour(@data[i], yield(@data[i]))
27
+ end
28
+
29
+ def neighbour(value, by)
30
+ raise "Cannot infer the neighbours. Must be implemented in #{self.class}."
31
+ end
32
+
33
+ def dup
34
+ duplicate = super
35
+ duplicate.internal_dup!
36
+
37
+ return duplicate
38
+ end
39
+
40
+ def inspect
41
+ "<" + self.class.to_s + " " + @data.inspect + ": #@fitness>"
42
+ end
43
+
44
+ def to_s
45
+ @data.to_s
46
+ end
47
+
48
+ protected
49
+ def internal_dup!
50
+ @data = @data.dup
51
+ @fitness
52
+ end
53
+ end
54
+
55
+ class IntegerSolution < Solution
56
+ def initialize(size, options = {})
57
+ @min = options[:min] || 0
58
+ @max = options[:max] || 999999999
59
+
60
+ GA.contract("Min must be an Integer") { @min.is_a?(Integer) }
61
+ GA.contract("Max must be an Integer") { @max.is_a?(Integer) }
62
+ GA.contract("Max must be greater than min") { @max > @min }
63
+
64
+
65
+ @data = size.times.map { GA::Random.rand(@max+1) + @min }
66
+ end
67
+
68
+ def neighbour(value, by)
69
+ GA.contract("Value value must be an Integer") { value.is_a?(Integer) }
70
+ GA.contract("By must be a Numeric (#{by.class} given)") { by.is_a?(Numeric) }
71
+ GA.contract("By cannot be 0") { by != 0 }
72
+ by = by > 0 ? by.ceil : by.floor
73
+
74
+ return [[value + by, @max].min, @min].max
75
+ end
76
+ end
77
+
78
+ class FloatSolution < Solution
79
+ def initialize(size, options = {})
80
+ @min = (options[:min] || 0.0).to_f
81
+ @max = (options[:max] || 1.0).to_f
82
+
83
+ GA.contract("Max and min must not be equal") { @max > @min }
84
+
85
+ @data = size.times.map { ((@max-@min) * GA::Random.rand) + @min }
86
+ end
87
+
88
+ def neighbour(value, by)
89
+ GA.contract("Value value must be a Float") { value.is_a?(Float) }
90
+ GA.contract("By must be a Numeric (#{by.class} given)") { by.is_a?(Numeric) }
91
+ GA.contract("By cannot be 0") { by != 0 }
92
+
93
+ return [[value + by, @max].min, @min].max
94
+ end
95
+ end
96
+
97
+ class StringSolution < Solution
98
+ def initialize(size, options = {})
99
+ @allowed = options[:allowed] || [('A'..'Z'), ('a'..'z'), ('0'..'9')].map { |e| e.to_a }.flatten
100
+
101
+ GA.contract("Allowed must be an array (#{@allowed.class} given)") { @allowed.is_a?(Array) }
102
+ GA.contract("Allowed must be an array of characters, i.e., 1-sized Strings") { @allowed.all? { |c| c.is_a?(String) && c.size == 1 } }
103
+ GA.contract("There must be at least an allowed character") { @allowed.size > 0 }
104
+
105
+ @data = size.times.map { @allowed[GA::Random.rand(@allowed.size)]}
106
+ end
107
+
108
+ def neighbour(value, by)
109
+ GA.contract("Value must be a character (1-sized String)") { value.is_a?(String) && value.length == 1 }
110
+ GA.contract("Value must be an allowed character: <#{@allowed.join(", ")}>") { @allowed.include?(value) }
111
+ GA.contract("By must be a Numeric (#{by.class} given)") { by.is_a?(Numeric) }
112
+ GA.contract("By cannot be 0") { by != 0 }
113
+
114
+ by = by > 0 ? by.ceil : by.floor
115
+
116
+ return @allowed[@allowed.index(value) % @allowed.size]
117
+ end
118
+
119
+ def to_s
120
+ @data.join ""
121
+ end
122
+ end
123
+
124
+ class PermutationSolution < Solution
125
+ def self.force(data)
126
+ chromosome = self.new(data)
127
+ for i in 0...chromosome.size
128
+ chromosome.data[i] = data[i]
129
+ end
130
+
131
+ GA.postcondition("Invalid forced chromosome") { chromosome.valid? }
132
+
133
+ return chromosome
134
+ end
135
+
136
+ def initialize(content)
137
+ @content = content
138
+
139
+ GA.contract("Content must not be nil") { @content }
140
+ GA.contract("Content must contain at least an element") { @content.size > 0 }
141
+
142
+ @data = GA::Random.shuffle @content
143
+ end
144
+
145
+ def mutate!(index)
146
+ by = yield(@data[index])
147
+
148
+ switch_with = (index + by) % @data.size
149
+ switch_with = (index + 1) % @data.size if switch_with == index
150
+
151
+ GA.contract("By must be a Numeric (#{by.class} given)") { by.is_a?(Numeric) }
152
+ GA.contract("By cannot be 0") { by != 0 }
153
+
154
+ @data[index], @data[switch_with] = @data[switch_with], @data[index]
155
+ end
156
+
157
+ def valid?
158
+ ((@data - @content) + (@content - @data)) == []
159
+ end
160
+ end
161
+
162
+ class SolutionFactory
163
+ def initialize(&block)
164
+ @generator = block
165
+ end
166
+
167
+ def chromosome
168
+ return @generator.call
169
+ end
170
+
171
+ def population(size)
172
+ size.times.map { chromosome }
173
+ end
174
+ end
175
+ end
176
+
177
+ class Array
178
+ def sample(n)
179
+ GA::Random.shuffle(self)[0...n]
180
+ end
181
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rugal
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Simone Scalabrino
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Pure-ruby implementation of genetic algorithms
14
+ email: s.scalabrino9@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/genetic_algorithm.rb
20
+ - lib/operators.rb
21
+ - lib/rugal.rb
22
+ - lib/solution.rb
23
+ homepage:
24
+ licenses:
25
+ - GPL-2.0
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.7.7
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Genetic algorithms for ruby
47
+ test_files: []