rugal 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.
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: []