biopsy 0.1.0.alpha

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.
@@ -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