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.
- data/LICENSE.txt +7 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/lib/biopsy/base_extensions.rb +64 -0
- data/lib/biopsy/domain.rb +156 -0
- data/lib/biopsy/experiment.rb +103 -0
- data/lib/biopsy/objective_function.rb +38 -0
- data/lib/biopsy/objective_handler.rb +170 -0
- data/lib/biopsy/objectives/fastest_optimum.rb +26 -0
- data/lib/biopsy/opt_algorithm.rb +0 -0
- data/lib/biopsy/optimisers/genetic_algorithm.rb +244 -0
- data/lib/biopsy/optimisers/parameter_sweeper.rb +66 -0
- data/lib/biopsy/optimisers/tabu_search.rb +437 -0
- data/lib/biopsy/settings.rb +110 -0
- data/lib/biopsy/target.rb +113 -0
- data/lib/biopsy/version.rb +12 -0
- data/lib/biopsy.rb +13 -0
- data/test/helper.rb +187 -0
- data/test/test_domain.rb +61 -0
- data/test/test_experiment.rb +84 -0
- data/test/test_file.rb +20 -0
- data/test/test_hash.rb +55 -0
- data/test/test_objective_handler.rb +99 -0
- data/test/test_settings.rb +74 -0
- data/test/test_string.rb +14 -0
- data/test/test_target.rb +89 -0
- metadata +198 -0
@@ -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
|