biopsy 0.1.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,244 @@
1
+ require 'csv'
2
+ require 'pp'
3
+
4
+ $global = 0
5
+
6
+ module Biopsy
7
+
8
+ class Generation
9
+ attr_reader :best, :population_homogenosity
10
+
11
+ def initialize (population_size, parameter_ranges)
12
+ @population_homogenosity = 0
13
+ @population_size = population_size
14
+ @current_generation = []
15
+ @ranges = parameter_ranges
16
+ @MUTATION_RATE = 0.40
17
+ @best = {
18
+ :parameters => nil,
19
+ :score => 0.0
20
+ }
21
+ end
22
+
23
+ # insert the next chromosome into the generation
24
+ def next_chromosome (chromosome)
25
+ @current_generation += [chromosome]
26
+ end
27
+
28
+ def update_best? (current)
29
+ @best = current if current[:score] > @best[:score]
30
+ end
31
+
32
+ # is the generation now full?
33
+ def last?
34
+ return @current_generation.length == @population_size
35
+ end
36
+
37
+ def run_generation
38
+ #pp @current_generation if $global%100 == 0
39
+ $global += 1
40
+ #homogeneous_test
41
+ selection_process
42
+ crossover
43
+
44
+ return @current_generation
45
+ end
46
+
47
+ ###################################
48
+ # ----remainder stochastic sampling (stochastic universal sampling method)----
49
+ # apply obj function on parameter_sets, rank parameter_sets by obj func score
50
+ # scale obj func score to ranking where: highest rank=2, lowest rank=0
51
+ # for each integer in rank reproduce += 1, for decimal allow random reproduction (based on size of decimal)
52
+ def selection_process
53
+ current_generation_temp = []
54
+ #apply obj func on all params, store score in @current_generation[X][:score]
55
+ @current_generation.each do |chromosome|
56
+ current_generation_temp << {:parameters => chromosome[:parameters], :score => chromosome[:score]}
57
+ end
58
+ # sort @current_generation by objective function score (ASC), replace @current_generation w/ temporary array
59
+ @current_generation = current_generation_temp.sort {|a, b| a[:score] <=> b[:score]}
60
+ # the highest rank is 2.0, generate step_size (difference in rank between each element)
61
+ step_size = 2.0/(@current_generation.length-1)
62
+ # counter to be used when assigning rank
63
+ counter = 0
64
+ # next_generation temporary array, @current_generation is replaced by next_generation after loop
65
+ next_generation = []
66
+ # switch scores with ranks
67
+ @current_generation.each do |chromosome|
68
+ # rank (asc) is the order in which the element appears (counter) times step_size so that the max is 2
69
+ rank = counter * step_size
70
+ next_generation << {:parameters => chromosome[:parameters], :score => rank} if rank >= 1.0
71
+ next_generation << {:parameters => chromosome[:parameters], :score => rank} if rank >= 2.0
72
+ next_generation << {:parameters => chromosome[:parameters], :score => rank} if rand <= rank.modulo(1)
73
+ counter += 1
74
+ end
75
+ # if population is too small
76
+ while next_generation.length < @population_size
77
+ select_chromosome = next_generation.sample(1)[0]
78
+ next_generation << select_chromosome
79
+ end
80
+ while next_generation.length > @population_size
81
+ select_chromosome_index = next_generation.index(next_generation.sample(1)[0])
82
+ next_generation.delete_at(select_chromosome_index)
83
+ end
84
+ # sort @current_generation by objective function score (ASC), replace @current_generation w/ temporary array
85
+ @current_generation = next_generation.sort {|a, b| a[:score] <=> b[:score]}
86
+ return
87
+ end
88
+
89
+ def crossover
90
+ def mating_process(mother, father)
91
+ children = [{:parameters=>{}}, {:parameters=>{}}]
92
+ mother[:parameters].each do |mother_key, mother_value|
93
+ if rand <= 0.5
94
+ children[0][:parameters][mother_key.to_sym] = mother_value
95
+ children[1][:parameters][mother_key.to_sym] = father[:parameters][mother_key.to_sym]
96
+ else
97
+ children[0][:parameters][mother_key.to_sym] = father[:parameters][mother_key.to_sym]
98
+ children[1][:parameters][mother_key.to_sym] = mother_value
99
+ end
100
+ end
101
+ return children
102
+ end
103
+ # mate the best quarter with the best half
104
+ best_quarter_num = (@current_generation.length.to_f/4.0).round
105
+ best_half_num = best_quarter_num
106
+
107
+ best_quarter = @current_generation[-best_quarter_num..-1]
108
+ best_half = @current_generation[-(best_quarter_num+best_half_num)..-(best_quarter_num+1)]
109
+ children = []
110
+ best_quarter.each do |father|
111
+ twins = mating_process(best_half.shuffle!.pop, father)
112
+ children += twins.map{|value| value}
113
+ end
114
+ (0..(children.length-1)).each do |num|
115
+ @current_generation.delete_at(0)
116
+ end
117
+ children.each do |child|
118
+ if @MUTATION_RATE > rand
119
+ children.delete_at(children.index(child))
120
+ children += [generateMutation(child)]
121
+ end
122
+ end
123
+
124
+ @current_generation += children
125
+ return true
126
+ end
127
+
128
+ def generateMutation chromosome
129
+ if !@mutation_wheel
130
+ @mutation_wheel = [{}, 0]
131
+ total_param_ranges = 0
132
+ @ranges.each do |key, value|
133
+ next if value.length <= 1
134
+ total_param_ranges += value.length
135
+ @mutation_wheel[0][key.to_sym] = total_param_ranges
136
+ end
137
+ @mutation_wheel[1] = total_param_ranges
138
+ end
139
+ mutation_location = rand(1..@mutation_wheel[1])
140
+ temp_options_params = Marshal.load(Marshal.dump(@ranges))
141
+ @mutation_wheel[0].each do |key, value|
142
+ next if value < mutation_location
143
+ temp_options_params[key.to_sym].delete(chromosome[:parameters][key.to_sym])
144
+ chromosome[:parameters][key.to_sym] = temp_options_params[key.to_sym].sample(1)[0]
145
+ break
146
+ end
147
+ return chromosome
148
+ end
149
+
150
+ def homogeneous_test
151
+ homo_val = 0
152
+ (0..(@current_generation.length-1)).each do |i|
153
+ (i..(@current_generation.length-1)).each do |j|
154
+ next if i == j
155
+ @current_generation[i][:parameters].each do |key, val|
156
+ homo_val += 1 if val == @current_generation[j][:parameters][key.to_sym]
157
+ end
158
+ end
159
+ end
160
+ n_value = @current_generation.length-1
161
+ sum = (n_value/2)*(n_value+1)
162
+ @population_homogenosity = (homo_val/(sum*@current_generation[0][:parameters].length).to_f)
163
+ end
164
+
165
+ def get_population
166
+ if self.last?
167
+ return @current_generation
168
+ else
169
+ return false
170
+ end
171
+ end
172
+
173
+ end # Generation
174
+
175
+
176
+ class GeneticAlgorithm
177
+ attr_reader :current, :best, :generation_no, :get_homog
178
+
179
+ def initialize (population_size, parameter_ranges)
180
+ @ranges = parameter_ranges
181
+ @population_size = population_size
182
+ @current_generation = Generation.new(@population_size, @ranges)
183
+ @best = {
184
+ :parameters => nil,
185
+ :score => 0.0
186
+ }
187
+ end
188
+
189
+ def run
190
+ nil
191
+ end
192
+
193
+ def run_one_iteration (parameters, score)
194
+ @current = {:parameters => parameters, :score => score}
195
+ # update best score?
196
+ self.update_best? @current
197
+ # push next chromosome to GA, generation will compute if population size is full
198
+ return self.next_candidate @current
199
+ # update tabu list
200
+ #self.update_tabu
201
+ #@current
202
+ end
203
+
204
+ def update_best? (current)
205
+ # ... runs an identical method in GenerationHandler
206
+ @current_generation.update_best? current
207
+ @best = current if current[:score] > @best[:score]
208
+ end
209
+
210
+ def next_candidate (chromosome)
211
+ # .. will run update ga if @current_generation.last? is true
212
+ @current_generation.next_chromosome(chromosome)
213
+
214
+ if @current_generation.last?
215
+ return self.update_ga
216
+ end
217
+ return @current
218
+ end
219
+
220
+ def update_ga
221
+ # ... will run to next generation
222
+ store = @current_generation.run_generation
223
+ @current_generation.homogeneous_test
224
+ @get_homog = @current_generation.population_homogenosity
225
+ @current_generation = Generation.new(@population_size, @ranges)
226
+ return store
227
+ end
228
+
229
+ def finished?
230
+ false
231
+ end
232
+
233
+ ##############################
234
+ def generate_chromosome
235
+ return Hash[@ranges.map { |param, range| [param, range.sample] }]
236
+ end
237
+
238
+ def get_population
239
+ return @current_generation.get_population
240
+ end
241
+
242
+ end # GeneticAlgorithm
243
+
244
+ end # Biopsy
@@ -0,0 +1,66 @@
1
+ # ParameterSweeper.new(options, constructor)
2
+ # options = {:settings => {...}, :parameters => {...}}
3
+ #
4
+ # Description:
5
+ # ParameterSweeper generates all combinations of a hash of arrays (options[:parameters]).
6
+ # The generated combinations are each passed in turn to the constructor which returns an execute command
7
+ # incorporating the parameters, and finally the target program is run with each generated command.
8
+ #
9
+ # The constructor will also have access to an unchanging settings hash (options[:settings])
10
+ # constructor proc will be passed multipule hashes in format: {:settings => {...}, :parameters => {...}}
11
+ # where the values in settings remain constant, and the values in parameters vary
12
+
13
+ require 'pp'
14
+ require 'fileutils'
15
+ require 'csv'
16
+ require 'threach'
17
+ require 'logger'
18
+
19
+ module Biopsy
20
+ # options - is a hash of two hashes, :settings and :parameters
21
+ # :ranges are arrays to be parameter sweeped
22
+ # ---(single values may be present, these are also remain unchanged but are accessible within the parameters hash to the constructor)
23
+ class ParameterSweeper
24
+
25
+ attr_reader :combinations
26
+
27
+ def initialize(ranges, threads=8, limit=nil)
28
+ @ranges = ranges
29
+ # parameter_counter: a count of input parameters to be used
30
+ @parameter_counter = 1
31
+ # input_combinations: an array of arrays of input parameters
32
+ @combinations = []
33
+ # if the number of threads is set, update the global variable, if not default to 1
34
+ @threads = threads
35
+ # convert all options to an array so it can be handled by the generate_combinations() method
36
+ # ..this is for users entering single values e.g 4 as a parameter
37
+ @ranges.each { |key, value| value = [value] unless value.kind_of? Array }
38
+ self.generate_combinations
39
+ # restrict to a subsample?
40
+ unless limit.nil?
41
+ @combinations = @combinations.sample limit
42
+ end
43
+ end
44
+
45
+ # return the next parameter set to evaluate
46
+ def run_one_iteration(*args)
47
+ @combinations.pop
48
+ rescue
49
+ nil
50
+ end
51
+
52
+ # generate all the parameter combinations to be applied
53
+ def generate_combinations(index=0, opts={})
54
+ if index == @ranges.length
55
+ @combinations << opts.clone
56
+ return
57
+ end
58
+ # recurse
59
+ key = @ranges.keys[index]
60
+ @ranges[key].each do |value|
61
+ opts[key] = value
62
+ generate_combinations(index + 1, opts)
63
+ end
64
+ end
65
+ end
66
+ end