-
Notifications
You must be signed in to change notification settings - Fork 0
/
genetic_algorithm.rb
129 lines (110 loc) · 3.66 KB
/
genetic_algorithm.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
require 'rubygems'
require 'chromosome'
require 'json'
class GeneticAlgorithm
attr_reader :chrome_length, :target, :pop_size, :chromosomes, :cross_rate, :mutate_rate,
:generation, :cross_amt, :mutate_amt
Infinity = 1.0 / 0
def initialize( options = {} )
raise "Target needs to be a Float" unless options['target'].instance_of? Float
raise "Cross Rate must be between 0 and 1" unless (0..1).include? options['cross_rate']
raise "Mutation Rate must be between 0 and 1" unless (0..1).include? options['mutate_rate']
@target = options['target']
@pop_size = options['pop_size']
@chrome_length = options['chrome_length']
@cross_rate = options['cross_rate']
@mutate_rate = options['mutate_rate']
@cross_amt = 0
@mutate_amt = 0
@chromosomes = populate @pop_size, @chrome_length
@generation = 1
end
def random_gen length
bin_string = (1..length).inject("") { |str, foo| str += rand(2).to_s }
end
def populate size, length
population = []
size.times { population.push Chromosome.new(random_gen length) }
population
end
def mutate bin_string
@mutate_amt += 1
bin_string = bin_string.gsub(/1/,'a').gsub(/0/,'1').gsub(/a/,'0')
end
def cross(x_chrome, y_chrome)
@cross_amt += 1
cross_spot = rand x_chrome.size
return [y_chrome, x_chrome] if cross_spot == 0
crossed_y = y_chrome.slice(0..cross_spot-1) + x_chrome.slice(cross_spot..-1)
crossed_x = x_chrome.slice(0..cross_spot-1) + y_chrome.slice(cross_spot..-1)
[crossed_x, crossed_y]
end
def calculate_fitness actual, target
fitness = (100.0 / (target - actual)).abs
return Infinity if fitness == Infinity
fitness >= 100 ? 99.0 : ("%.4f" % fitness).to_f
end
def update_fitness! target
@chromosomes.each do |chrome|
chrome.fitness = calculate_fitness(chrome.numeric_value, target)
throw :win_condition if chrome.fitness == Infinity
end
end
def total_fitness
total_fitness = 0.0
@chromosomes.each do |chrome|
total_fitness += chrome.fitness
end
total_fitness
end
def roulette (fit_level = total_fitness)
current_fitness = 0.0
slice = rand(fit_level.ceil)
@chromosomes.each do |chrome|
current_fitness += chrome.fitness
return chrome if current_fitness >= slice
end
end
def decide? rate
rand < rate.to_f
end
def new_generation population
@chromosomes.replace population
@generation += 1
end
def create_children parent_one, parent_two
if decide? @cross_rate
offspring_one, offspring_two = cross(parent_one.bit_value, parent_two.bit_value)
else
decide? @mutate_rate ? offspring_one = mutate(parent_one.bit_value) : offspring_one = parent_one.bit_value
decide? @mutate_rate ? offspring_two = mutate(parent_two.bit_value) : offspring_two = parent_two.bit_value
end
return [offspring_one, offspring_two]
end
def simulate!
catch :win_condition do
while true do
update_fitness! @target
new_pop = []
(@pop_size / 2).times do
child_one, child_two = create_children(roulette, roulette)
new_pop << Chromosome.new(child_one) << Chromosome.new(child_two)
end
new_generation new_pop
end
end
@chromosomes.each { |c| puts c.string_value if c.fitness == Infinity}
end
def stats &block
stats = {
:pop_size => @pop_size,
:total_fitness => @total_fitness,
:cross_rate => @cross_rate,
:mutate_rate => @mutate_rate,
:current_gen => @generation,
:mutate_amt => @mutate_amt,
:cross_amt => @cross_amt
}
yield stats
end
end