genetic_algorithm 0.5
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 +25 -0
- data/lib/genetic_algorithm.rb +325 -0
- data/test/test.rb +23 -0
- metadata +57 -0
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Ruby Genetic Algorithm Library
|
2
|
+
==============================
|
3
|
+
|
4
|
+
**Author: Carlos Maximiliano Giorgio Bort**
|
5
|
+
**Date: 16/03/2011**
|
6
|
+
|
7
|
+
This is a simple implementation of a genetic algorithm.
|
8
|
+
|
9
|
+
This library works with Ruby 1.9.2 or newer versions
|
10
|
+
Together with this gem you may install:
|
11
|
+
|
12
|
+
* gnuplot (if you have a mac you can do this via mac port)
|
13
|
+
* gnuolotr which is a gem that you can install simply typing into your terminal: `gem install gnuplotr`
|
14
|
+
|
15
|
+
Gnuplot and gnuplotr are used to plot the evolution of the best chromosome in the population.
|
16
|
+
|
17
|
+
Run the `test/test.rb` to test the algorithm. Otherwise you can just the run `ga_gem.rb` file.
|
18
|
+
|
19
|
+
|
20
|
+
Note: If you are using a mac, maybe you'll need to install the newer version of ruby. Try the [rvm: Ruby Version Manager](http://rvm.beginrescueend.com)!
|
21
|
+
|
22
|
+
If you find some bugs please contact me at:
|
23
|
+
maximiliano_giorgio at yahoo dot it
|
24
|
+
|
25
|
+
Have a nice day!!
|
@@ -0,0 +1,325 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ga_my
|
3
|
+
#
|
4
|
+
# Created by Carlos Maximiliano Giorgio Bort on 2011-03-06.
|
5
|
+
# Copyright (c) 2011 University of Trento. All rights reserved.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'gnuplotr'
|
10
|
+
|
11
|
+
class Vector
|
12
|
+
if RUBY_VERSION.split(".").join.to_i < 190
|
13
|
+
# Add method for division under Ruby 1.8.x
|
14
|
+
def /(other); self * (1.0 / other); end
|
15
|
+
end
|
16
|
+
# More compact inspect version
|
17
|
+
def inspect; "V[#{self.to_a * ","}]"; end
|
18
|
+
end
|
19
|
+
class Integer
|
20
|
+
def to_bin(siz) # size is the number of bits used
|
21
|
+
bin = self.to_s(2)
|
22
|
+
bin.size <= siz ? inc = (siz - bin.size) : (raise "Use more bits for a proper binary convertion, you used #{size} bits for a #{bin.size} binary number")
|
23
|
+
inc.times { bin = "0" + bin }
|
24
|
+
return bin
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module GA
|
29
|
+
class Population
|
30
|
+
attr_reader :population , :nbit
|
31
|
+
|
32
|
+
# i_o is the inverval of values used for define the first population
|
33
|
+
# values for the parameters of the first population
|
34
|
+
def initialize( dim, i_o = {}, prec = 1E-3 )
|
35
|
+
raise ArgumentError, "Need an Hash instead of #{i_o.class}" unless i_o.kind_of? Hash
|
36
|
+
@population = [] # initialize the population, is an hash which keys are: :chromosome and :fitness
|
37
|
+
dim.times do # for each chromosome of the initial population do...
|
38
|
+
cr = []
|
39
|
+
# generate the gene randomly (it must lie in the domain defined by i_o)
|
40
|
+
i_o.each_value{ |v| cr << rand()*(v.max-v.min) + v.min }
|
41
|
+
@population << { :chromosome => cr }
|
42
|
+
end
|
43
|
+
@prec = prec # the precision, i.e. the number considered at the left of the comma
|
44
|
+
end
|
45
|
+
|
46
|
+
end # class Population
|
47
|
+
|
48
|
+
# Class that implements a general n-dimensional Nelder-Meade Method (NMM).
|
49
|
+
# @author Paolo Bosetti
|
50
|
+
class Optimizer
|
51
|
+
attr_reader :simplex, :status, :iteration, :sorted
|
52
|
+
def initialize(args = {})
|
53
|
+
@cfg = {
|
54
|
+
:toll => 1E-3 , # accurancy of the solution
|
55
|
+
:nbit => 10 , # number of bits used to encode the cromosomes into a binary string
|
56
|
+
:p_mutation => 0.2 , # probability of mutation
|
57
|
+
:p_crossover=> 0.8 , # probability of cross over
|
58
|
+
:i_o => {} , # inverval of values used for define the first population
|
59
|
+
:npop => 50 , # number of population to be computed
|
60
|
+
:ncr => 100 , # number of chromosomes in each population
|
61
|
+
:pconv => true ,
|
62
|
+
:nelitist => 3 , # the 'n' best chromosomes that will automatically be copied in the new population
|
63
|
+
:plotopt => {:xlabel => 'No. iteration',
|
64
|
+
:ylabel => 'Objective function value',
|
65
|
+
:yrange => [ -10 , 10 ],
|
66
|
+
:grid => "set grid"
|
67
|
+
}
|
68
|
+
}
|
69
|
+
@cfg[:plotopt][:title] = "Population size: #{@cfg[:ncr]} chromosomes"
|
70
|
+
raise "Error with the assigned mutation probability:\n it is #{@cfg[:p_mutation]} but must be 0 <= p_mutation <= 1 " unless @cfg[:p_mutation] >= 0 and @cfg[:p_mutation] <= 1
|
71
|
+
raise "Error with the assigned crossover probability:\n it is #{@cfg[:p_crossover]} but must be 0 <= p_crossover <= 1 " unless @cfg[:p_crossover] >= 0 and @cfg[:p_crossover] <= 1
|
72
|
+
@cfg.merge! args
|
73
|
+
@nbit = @cfg[:nbit] #@pop.max_bit # is the number f bits required to encode into a binary string the chromosome
|
74
|
+
@pop = Population.new( @cfg[:ncr] , @cfg[:i_o])
|
75
|
+
@population = @pop.population
|
76
|
+
@start_points = []
|
77
|
+
@status = :filling
|
78
|
+
@iteration = 0
|
79
|
+
@best = []
|
80
|
+
if @cfg[:pconv] == true # this is the plot
|
81
|
+
@gp = GNUPlotr.new
|
82
|
+
# enable command history recording
|
83
|
+
@gp.record = true
|
84
|
+
# Issue raw gnuplot commands
|
85
|
+
@gp.raw @cfg[:plotopt][:grid]
|
86
|
+
# Some magic mapping works too:
|
87
|
+
@gp.set_grid
|
88
|
+
@gp.set_title @cfg[:plotopt][:title] , :font => "Times New Roman,18"
|
89
|
+
@gp.set_xlabel @cfg[:plotopt][:xlabel], :font => "Times New Roman,18"
|
90
|
+
@gp.set_ylabel @cfg[:plotopt][:ylabel], :font => "Times New Roman,18"
|
91
|
+
@gp.set_xrange( 0 .. @cfg[:npop])
|
92
|
+
#@gp.set_yrange(@cfg[:plotopt][:yrange][0] .. @cfg[:plotopt][:yrange][1])
|
93
|
+
end # if @cfg
|
94
|
+
end
|
95
|
+
|
96
|
+
def loop( ary = nil )
|
97
|
+
raise ArgumentError, "Block needed" unless block_given?
|
98
|
+
# evaluates the cromosomes and converts them into a string of bits
|
99
|
+
@population.each do |c|
|
100
|
+
c[:fitness] = yield( c[:chromosome] ) unless c[:fitness] # evaluates the chromosome
|
101
|
+
c[:bitstring] = encode( c[:chromosome] ) unless c[:bitstring] # converts the chromosome into a string of bits
|
102
|
+
end
|
103
|
+
|
104
|
+
until converged? or @iteration > @cfg[:npop]
|
105
|
+
@population.each{ |v| v[:fitness] = 1.0/0.0 if v[:fitness].nan?}
|
106
|
+
@sorted = @population.sort!{ |x, y| x[:fitness] <=> y[:fitness] }
|
107
|
+
selected = selection( @population )
|
108
|
+
bit_selected = []
|
109
|
+
selected.each{ |v| bit_selected << v[:bitstring] }
|
110
|
+
# @population.size is used to set the number of chromosomes in the new generation
|
111
|
+
childs = evolve( bit_selected, @population.size-@cfg[:nelitist] , @cfg[:p_crossover], @cfg[:p_mutation] )
|
112
|
+
# child is converted into an array of hashes, each with keys: :chromosome, :bitstring , :fitness
|
113
|
+
@population = @sorted[ 0 .. @cfg[:nelitist]-1 ] # reset the population and then update it
|
114
|
+
childs.each do |c|
|
115
|
+
dec = decode( c[:bitstring] )
|
116
|
+
ftn = yield( dec )
|
117
|
+
ftn = 1.0/0.0 if ftn.nan?
|
118
|
+
@population << {
|
119
|
+
:bitstring => c[:bitstring],
|
120
|
+
:chromosome => dec, # converts the string of bits into an array of floats
|
121
|
+
:fitness => ftn # evaluates the array of floats
|
122
|
+
}
|
123
|
+
end # childs do
|
124
|
+
@sorted = @population.sort!{ |x, y| x[:fitness] <=> y[:fitness] }
|
125
|
+
@best << @sorted.first
|
126
|
+
puts "#{@iteration}th generation, best: #{@best.last.inspect}" ##########
|
127
|
+
puts "#{@iteration}th generation, worst: #{ @sorted.last.inspect }" ###########
|
128
|
+
"Maximum number of iteration reached: #{@cfg[:npop]}" if @iteration == @cfg[:npop]
|
129
|
+
puts "_________________________________________________________"
|
130
|
+
|
131
|
+
# these lines ar used to do a convergence plot, i.e. all the fitnesses for the current population
|
132
|
+
if @cfg[:pconv] == true
|
133
|
+
|
134
|
+
# a. initialize the data sets for the plot
|
135
|
+
@gp.new_series(:population)
|
136
|
+
|
137
|
+
# b. fill the data sets
|
138
|
+
if @iteration == 0 # initialize the matrix containing simplex data
|
139
|
+
sdata = []
|
140
|
+
it = []
|
141
|
+
end
|
142
|
+
it << @iteration # array of integers
|
143
|
+
|
144
|
+
### these lines are usefull to plot the evolution of entire population
|
145
|
+
#f_a = []
|
146
|
+
#@population.each{ |v| f_a << v[:fitness] } # array of array of floats
|
147
|
+
#sdata << f_a
|
148
|
+
# b. fill the data sets
|
149
|
+
#it.each do |i|
|
150
|
+
# #sdata[i].each do |v|
|
151
|
+
# sdata.each do |v|
|
152
|
+
# @gp.series[:population] << [ i , v ]
|
153
|
+
# end
|
154
|
+
#end
|
155
|
+
|
156
|
+
### these lines are used to track the best chromosome in the population
|
157
|
+
sdata << @best[-1][:fitness]
|
158
|
+
it.each do |i|
|
159
|
+
@gp.series[:population] << [ i , sdata[i] ]
|
160
|
+
end
|
161
|
+
# c. close the data sets
|
162
|
+
@gp.series[:population].close
|
163
|
+
# d. plot the data sets
|
164
|
+
if @iteration == 0
|
165
|
+
@gp.plot :population , "with points lt 9 pt 2 notitle"
|
166
|
+
@gp.plot :population , "with line lt 9 pt 2 notitle"
|
167
|
+
else
|
168
|
+
@gp.replot :population , "with points lt 9 pt 2 notitle"
|
169
|
+
@gp.replot :population , "with line lt 9 pt 2 notitle"
|
170
|
+
end
|
171
|
+
end # if @cfg
|
172
|
+
ary[ @iteration.to_s.to_sym ] = @sorted if ary.kind_of? Hash
|
173
|
+
@iteration += 1
|
174
|
+
end # until converged
|
175
|
+
return @best[-1]
|
176
|
+
end # def loop
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
# The solution converges if the fitness for the best chromosome of the latter 3 population is the same
|
181
|
+
# Input: array of hashes. Output: boolean value
|
182
|
+
def converged?
|
183
|
+
if @iteration > 1
|
184
|
+
xx = 0.0
|
185
|
+
3.times{ |p| xx += @sorted[p][:fitness]**2 }
|
186
|
+
if xx**0.5 <= @cfg[:toll]
|
187
|
+
return true
|
188
|
+
else
|
189
|
+
return false
|
190
|
+
end # if xx
|
191
|
+
end # if @iteration
|
192
|
+
end # converged
|
193
|
+
|
194
|
+
# Input: array of bit strings. Output: array of bit strings
|
195
|
+
def evolve( selected, pop_size, p_cross, p_mut )
|
196
|
+
children = []
|
197
|
+
selected.each_with_index do |p1, i|
|
198
|
+
# crossing over
|
199
|
+
############################## non me piase sto metodo di scelgiere moglie e marito
|
200
|
+
# 1 ::: p2 = (i.modulo(2) == 0) ? selected[i+1] : selected[i-1] # i.modulo(2) is the reminder of the division i / 2
|
201
|
+
# p2 = selected[0] if i == selected.size - 1
|
202
|
+
# 2 ::: i == selected.size-1 ? p2 = selected[0] : p2 = selected[i+1]
|
203
|
+
# 3 ::: random choise:
|
204
|
+
j = rand(selected.size)
|
205
|
+
j = rand(selected.size) while j == i
|
206
|
+
p2 = selected[j]
|
207
|
+
ch1 = {} ; ch2 = {} ; ch3 = {}
|
208
|
+
ch1[:bitstring] , ch2[:bitstring] = crossover( p1, p2, p_cross )
|
209
|
+
children.concat( [ ch1 , ch2 ] )
|
210
|
+
|
211
|
+
# mutation
|
212
|
+
#i.modulo(2) == 1 ? p3 = selected[i+1] : p3 = selected[i-1] # p3 is a chromosome not yet used
|
213
|
+
k = rand(selected.size)
|
214
|
+
k = rand(selected.size) while k == j and k == i # random choise of the chromosome that might mutate
|
215
|
+
p3 = selected[k]
|
216
|
+
ch3[:bitstring] = mutation( p3, p_mut )
|
217
|
+
children << ch3
|
218
|
+
break if children.size >= pop_size
|
219
|
+
end
|
220
|
+
return children
|
221
|
+
end
|
222
|
+
|
223
|
+
# compares two chromosomes and selects the one with the best fitting.
|
224
|
+
# the size of selected is the half of the population size
|
225
|
+
# This is a binary tournament.
|
226
|
+
# Input: array of hashes. Output: array of hashes
|
227
|
+
def selection(pop)
|
228
|
+
raise "Error in selection: input must be a Array instead of a #{pop.class}" unless pop.kind_of? Array
|
229
|
+
sel = []
|
230
|
+
pop.size.times do
|
231
|
+
i , j = rand( pop.size ) , rand( pop.size )
|
232
|
+
j = rand( pop.size ) while j == i # if unfortunatly j = i, evaluates j again
|
233
|
+
(pop[i][:fitness] < pop[j][:fitness]) ? sel << pop[i] : sel << pop[j]
|
234
|
+
end
|
235
|
+
return sel
|
236
|
+
end
|
237
|
+
|
238
|
+
# the chromosome is a string of '0' and '1', rate [0,1]. mutant is also a string
|
239
|
+
# Input: a bit string, Output: a bit string
|
240
|
+
def mutation( bitstring , rate )
|
241
|
+
raise "Error in mutation: input must be a String instead of a #{bitstring.class}" unless bitstring.kind_of? String
|
242
|
+
mutant = ""
|
243
|
+
bitstring.size.times do |i|
|
244
|
+
gene = bitstring[i]
|
245
|
+
# change the bit value only if the rand [0,1] is minor than the mutation probability
|
246
|
+
mutant << ((rand < rate) ? ((gene == '1') ? "0" : "1") : gene)
|
247
|
+
end # chromosome
|
248
|
+
return mutant
|
249
|
+
end # def mutation
|
250
|
+
|
251
|
+
# both father and mather are strings, rate [0,1] is the crossover probability
|
252
|
+
# the crossover returns two childs.
|
253
|
+
# Input: 2 bit strings + 1 float. Output: 2 bit strings
|
254
|
+
def crossover( father, mother, rate )
|
255
|
+
raise "Error in crossover: father must be a String instead of a #{father.class}" unless father.kind_of? String
|
256
|
+
raise "Error in crossover: mother must be a String instead of a #{mother.class}" unless mother.kind_of? String
|
257
|
+
# don't do the cross over if rand is maior or equal than the crossover probability
|
258
|
+
return father , mother if rand >= rate
|
259
|
+
raise "Error in crossover, father and mother must have the same dimension" unless father.size == mother.size
|
260
|
+
point = 0.0
|
261
|
+
point = rand(mother.size) while point == 0 or point == mother.size # sets the crossover point randomly
|
262
|
+
return father[0..point-1] + mother[point..(mother.size)] , mother[0..point-1] + father[point..(father.size)]
|
263
|
+
end
|
264
|
+
|
265
|
+
# this is used to convert the input values into a binary string (the chromosome)
|
266
|
+
# Input: array of floats. Output: one bit string
|
267
|
+
def encode( ary = [] )
|
268
|
+
ary_b = ""
|
269
|
+
ary.each { |v|
|
270
|
+
i_b = v.to_i.abs.to_bin( @nbit )
|
271
|
+
# how to encode the sign: the first bit is 0 if i_b is >= 0, and 1 if it's < 0
|
272
|
+
v.to_i >= 0 ? i_b[0] = "0" : i_b[0] = "1"
|
273
|
+
d_b = ( ( (v-v.to_i)/@cfg[:tol] ).to_i.abs ).to_bin( @nbit )
|
274
|
+
ary_b += i_b+d_b
|
275
|
+
}
|
276
|
+
return ary_b
|
277
|
+
end
|
278
|
+
|
279
|
+
# this is used to decode the binary chromosome string into an array of floats
|
280
|
+
# Input: one bit string. Output: array of floats
|
281
|
+
def decode( str )
|
282
|
+
ng = str.size / @nbit # is the number of genes in one chromosome
|
283
|
+
raise "Error in the chromosome decodification: you have #{ng} genes of #{@nbit} bits, and #{str.size} bits in the chromosome" unless ng * @nbit == str.size
|
284
|
+
cr = []
|
285
|
+
dots = ""
|
286
|
+
@nbit.times{ dots += "." } # generates a string with @nbit dots: "..."
|
287
|
+
dots = "(" + dots + ")" # adds the parentesys: "(...)"
|
288
|
+
rexp = Regexp.new( dots ) # converst dots into a regulare expression: /(...)/
|
289
|
+
str_a = str.split( rexp ) # splits the string: eg."000111110011" -> ["","000","","111","","110","","011"]
|
290
|
+
str_a = str_a.delete_if{ |v| v == ""} # ["000", "111", "110", "011"]
|
291
|
+
str_a.size.times do |i|
|
292
|
+
if i <= str_a.size-2
|
293
|
+
# the first bit of the element with an odd index is the sign of the floating number
|
294
|
+
str_a[ 2*i ][0..0] == "0" ? sign = "+" : sign = "-"
|
295
|
+
# this returns: [ +00.7 , +2.6 ], each element in the array is in decimal codification
|
296
|
+
cr << (sign+str_a[ 2*i ][1..-1].to_i(2).to_s+"."+str_a[2*i+1].to_i(2).to_s).to_f
|
297
|
+
end # if
|
298
|
+
return cr if cr.size == str_a.size / 2
|
299
|
+
end # str_a.size.times
|
300
|
+
end # decode
|
301
|
+
end # class Optimizer
|
302
|
+
end # module GA#
|
303
|
+
|
304
|
+
|
305
|
+
if __FILE__ == $0
|
306
|
+
# Test function
|
307
|
+
f = lambda {|p| p[0]**2 + p[1]**2 } # a trivial parabola
|
308
|
+
#f = lambda { |p| p[0] ** 2 - 4 * p[0] + p[1] ** 2 -p[1] - p[0] * p[1]}
|
309
|
+
#f = lambda { |x| 10*(x[1] - x[0]**2)** 2 + (1 - x[0])** 2 } # Rosenbroke function
|
310
|
+
|
311
|
+
# Instantiate the optimizer, with tolerance and dimension
|
312
|
+
opt = GA::Optimizer.new( :tol => 1,
|
313
|
+
:p_mutation => 0.2,
|
314
|
+
:p_crossover => 0.8,
|
315
|
+
:i_o => { :X =>[5,10] , :Y=>[-10.23,5.234] },
|
316
|
+
:npop => 50,
|
317
|
+
:ncr => 200
|
318
|
+
)
|
319
|
+
arr = {}
|
320
|
+
opt.loop(arr) {|p| f.call(p)}
|
321
|
+
p "*"*15
|
322
|
+
p "*** The last population is: ***"
|
323
|
+
sort = opt.sorted # sort is an array with the last population in ascending order, the first element is the chromosome with the lowest fitness
|
324
|
+
sort.each{|v| puts v}
|
325
|
+
end
|
data/test/test.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ga_my
|
3
|
+
#
|
4
|
+
# Created by Carlos Maximiliano Giorgio Bort on 2011-03-06.
|
5
|
+
# Copyright (c) 2011 University of Trento. All rights reserved.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'gnuplotr'
|
10
|
+
require 'genetic_algorithm'
|
11
|
+
# Test function
|
12
|
+
f = lambda {|p| p[0]**2 + p[1]**2 } # a trivial parabola
|
13
|
+
|
14
|
+
# Instantiate the optimizer, with tolerance and dimension
|
15
|
+
opt = GA::Optimizer.new( :tol => 1e-3,
|
16
|
+
:p_mutation => 0.2,
|
17
|
+
:p_crossover => 0.8,
|
18
|
+
:i_o => { :X =>[3,-4, 5] , :Y=>[3,-4, 5] , :Z=>[3,-4, 5] },
|
19
|
+
:npop => 50,
|
20
|
+
:ncr => 150,
|
21
|
+
:nbit => 10
|
22
|
+
)
|
23
|
+
opt.loop {|p| f.call(p)}
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: genetic_algorithm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: "0.5"
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Carlos Maximiliano Giorgio Bort
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-06 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Simple genetic algorithm for functions minimization.
|
17
|
+
email: maximiliano_giorgio@yahoo.it
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.md
|
26
|
+
- lib/genetic_algorithm.rb
|
27
|
+
- test/test.rb
|
28
|
+
homepage: https://github.com/maximiliano1985
|
29
|
+
licenses: []
|
30
|
+
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options:
|
33
|
+
- --inline-source
|
34
|
+
- --charset=UTF-8
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: "0"
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project: genetic_algorithm
|
52
|
+
rubygems_version: 1.8.5
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: Simple genetic algorithm for function minimization. Needs gnuplot and the gem gnuplotr to work properly.
|
56
|
+
test_files: []
|
57
|
+
|