biopsy 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/biopsy/experiment.rb +32 -20
- data/lib/biopsy/objective_function.rb +1 -1
- data/lib/biopsy/objective_handler.rb +56 -43
- data/lib/biopsy/optimisers/parameter_sweeper.rb +10 -8
- data/lib/biopsy/optimisers/tabu_search.rb +4 -4
- data/lib/biopsy/target.rb +23 -7
- data/lib/biopsy/version.rb +4 -2
- data/test/brokenconfig.yml +3 -0
- data/test/helper.rb +19 -1
- data/test/test_experiment.rb +17 -12
- data/test/test_objective_handler.rb +57 -1
- data/test/test_parametersweep.rb +22 -0
- data/test/test_settings.rb +12 -5
- data/test/test_target.rb +77 -5
- metadata +25 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a78e571268545fdd69d110f2b789bb53fa5d100
|
4
|
+
data.tar.gz: 3255fd3579a242fa6277c9b16d5128fe4d3c6250
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd5c793e5af0042dd69066de1c96695e519d619a8738ad066baefc0d3015dc89ec6247d573090263f45ad3f78ea2bb8fa3df17945197d29bd48b23cee84556bc
|
7
|
+
data.tar.gz: 6a9d2b2dcc4463c1ad8afe3311af4b3912b0263f53c7b1aa609a16319aa5c2348c62a3c48b4caaf7760fa6e0d18df1423644772d7bde2285e10b866f223e4aed
|
data/lib/biopsy/experiment.rb
CHANGED
@@ -18,10 +18,12 @@ module Biopsy
|
|
18
18
|
|
19
19
|
class Experiment
|
20
20
|
|
21
|
-
attr_reader :inputs, :outputs, :retain_intermediates
|
21
|
+
attr_reader :inputs, :outputs, :retain_intermediates
|
22
|
+
attr_reader :target, :start, :algorithm
|
22
23
|
|
23
24
|
# Returns a new Experiment
|
24
|
-
def initialize(target, options:{}, threads:4, start:nil, algorithm:nil,
|
25
|
+
def initialize(target, options:{}, threads:4, start:nil, algorithm:nil,
|
26
|
+
verbosity: :quiet)
|
25
27
|
@threads = threads
|
26
28
|
@start = start
|
27
29
|
@algorithm = algorithm
|
@@ -51,12 +53,12 @@ module Biopsy
|
|
51
53
|
|
52
54
|
# Return a random set of parameters from the parameter space.
|
53
55
|
def random_start_point
|
54
|
-
Hash[@target.parameters.map { |p, r| [p, r.sample] }]
|
56
|
+
Hash[@target.parameters.map { |p, r| [p, r.sample] }]
|
55
57
|
end
|
56
58
|
|
57
59
|
# select the optimisation algorithm to use
|
58
60
|
def select_algorithm
|
59
|
-
return if
|
61
|
+
return if algorithm
|
60
62
|
max = Settings.instance.sweep_cutoff
|
61
63
|
n = @target.count_parameter_permutations
|
62
64
|
if n < max
|
@@ -67,7 +69,7 @@ module Biopsy
|
|
67
69
|
end
|
68
70
|
|
69
71
|
# load the target named +:target_name+
|
70
|
-
def load_target
|
72
|
+
def load_target(target_name)
|
71
73
|
@target = Target.new
|
72
74
|
@target.load_by_name target_name
|
73
75
|
end
|
@@ -79,21 +81,27 @@ module Biopsy
|
|
79
81
|
in_progress = true
|
80
82
|
@algorithm.setup @start
|
81
83
|
@current_params = @start
|
82
|
-
while in_progress
|
84
|
+
while in_progress
|
83
85
|
run_iteration
|
84
86
|
# update the best result
|
85
87
|
best = @best
|
86
88
|
@best = @algorithm.best
|
87
89
|
ptext = @best[:parameters].each_pair.map{ |k, v| "#{k}:#{v}" }.join(", ")
|
88
|
-
if
|
89
|
-
|
90
|
+
if @best &&
|
91
|
+
@best.key?(:score) &&
|
92
|
+
best &&
|
93
|
+
best.key?(:score) &&
|
94
|
+
@best[:score] > best[:score]
|
95
|
+
puts "found a new best score: #{@best[:score]} \
|
96
|
+
for parameters #{ptext}"
|
90
97
|
end
|
91
98
|
# have we finished?
|
92
99
|
in_progress = !@algorithm.finished?
|
93
100
|
end
|
94
101
|
@algorithm.write_data if @algorithm.respond_to? :write_data
|
95
102
|
unless @verbosity == :silent
|
96
|
-
puts "found optimum score: #{@best[:score]} for parameters
|
103
|
+
puts "found optimum score: #{@best[:score]} for parameters \
|
104
|
+
#{@best[:parameters]} in #{@iteration_count} iterations."
|
97
105
|
end
|
98
106
|
return @best
|
99
107
|
end
|
@@ -103,16 +111,16 @@ module Biopsy
|
|
103
111
|
# Returns the output of the optimiser.
|
104
112
|
def run_iteration
|
105
113
|
# create temp dir
|
106
|
-
|
114
|
+
Dir.chdir(self.create_tempdir) do
|
107
115
|
# run the target
|
108
116
|
raw_output = @target.run @current_params.merge(@options)
|
109
117
|
# evaluate with objectives
|
110
118
|
param_key = @current_params.to_s
|
111
119
|
result = nil
|
112
|
-
if @scores.
|
120
|
+
if @scores.key? param_key
|
113
121
|
result = @scores[param_key]
|
114
122
|
else
|
115
|
-
result = @objective.run_for_output(raw_output, @threads)
|
123
|
+
result = @objective.run_for_output(raw_output, @threads, nil)
|
116
124
|
@iteration_count += 1
|
117
125
|
self.print_progress(@iteration_count, @current_params, result, @best)
|
118
126
|
end
|
@@ -135,17 +143,21 @@ module Biopsy
|
|
135
143
|
def cleanup
|
136
144
|
# TODO: make this work
|
137
145
|
# remove all but essential files
|
146
|
+
essential_files = ""
|
138
147
|
if Settings.instance.keep_intermediates
|
139
|
-
@objectives
|
148
|
+
# @objectives isn't mentioned anywhere in the rest of this file
|
149
|
+
@objectives.values.each do |objective|
|
150
|
+
essential_files += objective.essential_files
|
151
|
+
end
|
140
152
|
end
|
141
153
|
Dir["*"].each do |file|
|
142
154
|
next
|
143
155
|
# TODO: implement this
|
144
|
-
next if File.directory? file
|
145
|
-
if essential_files && essential_files.include?(file)
|
146
|
-
|
147
|
-
|
148
|
-
end
|
156
|
+
# next if File.directory? file
|
157
|
+
# if essential_files && essential_files.include?(file)
|
158
|
+
# `gzip #{file}` if Settings.instance.gzip_intermediates
|
159
|
+
# FileUtils.mv("#{file}.gz", '../output')
|
160
|
+
# end
|
149
161
|
end
|
150
162
|
FileUtils.rm_rf @last_tempdir
|
151
163
|
end
|
@@ -157,11 +169,11 @@ module Biopsy
|
|
157
169
|
# generate random dirnames until we find one that
|
158
170
|
# doesn't exist
|
159
171
|
test_token = SecureRandom.hex
|
160
|
-
break test_token unless File.
|
172
|
+
break test_token unless File.exist? test_token
|
161
173
|
end
|
162
174
|
Dir.mkdir(token)
|
163
175
|
@last_tempdir = token
|
164
|
-
|
176
|
+
token
|
165
177
|
end
|
166
178
|
|
167
179
|
end # end of class RunHandler
|
@@ -25,7 +25,7 @@ module Biopsy
|
|
25
25
|
#
|
26
26
|
# objective = ObjectiveFunction.new
|
27
27
|
# result = objective.run('example.fasta')
|
28
|
-
def run(
|
28
|
+
def run(raw_output, output_files, threads)
|
29
29
|
raise NotImplementedError.new("You must implement a run method for each objective function")
|
30
30
|
end
|
31
31
|
|
@@ -6,21 +6,25 @@ require 'fileutils'
|
|
6
6
|
# == Description
|
7
7
|
#
|
8
8
|
# The Handler manages the objective functions for the optimisation experiment.
|
9
|
-
# Specifically, it finds all the objective functions and runs them when
|
10
|
-
# outputting the results to the main Optimiser.
|
9
|
+
# Specifically, it finds all the objective functions and runs them when
|
10
|
+
# requested, outputting the results to the main Optimiser.
|
11
11
|
#
|
12
12
|
# == Explanation
|
13
13
|
#
|
14
14
|
# === Loading objective functions
|
15
15
|
#
|
16
|
-
# The Handler expects a directory containing objectives (by default it looks
|
16
|
+
# The Handler expects a directory containing objectives (by default it looks
|
17
|
+
# in *currentdir/objectives*).
|
17
18
|
# The *objectives* directory should contain the following:
|
18
19
|
#
|
19
|
-
# * a *.rb* file for each objective function. The file should define a subclass
|
20
|
-
#
|
20
|
+
# * a *.rb* file for each objective function. The file should define a subclass
|
21
|
+
# of ObjectiveFunction
|
22
|
+
# * (optionally) a file *objectives.txt* which lists the objective function
|
23
|
+
# files to use
|
21
24
|
#
|
22
|
-
# If the objectives.txt file is absent, the subset of objectives to use can be
|
23
|
-
# , or if no such restriction is set, the whole
|
25
|
+
# If the objectives.txt file is absent, the subset of objectives to use can be
|
26
|
+
# set directly in the Optimiser, or if no such restriction is set, the whole
|
27
|
+
# set of objectives will be run.
|
24
28
|
#
|
25
29
|
# Each file listed in *objectives.txt* is loaded if it exists.
|
26
30
|
#
|
@@ -39,22 +43,30 @@ module Biopsy
|
|
39
43
|
attr_reader :last_tempdir
|
40
44
|
attr_accessor :objectives
|
41
45
|
|
42
|
-
def initialize
|
46
|
+
def initialize(target)
|
43
47
|
@target = target
|
44
48
|
@objectives_dir = Settings.instance.objectives_dir.first
|
45
49
|
@objectives = {}
|
46
50
|
$LOAD_PATH.unshift(@objectives_dir)
|
47
|
-
|
48
|
-
|
51
|
+
if Settings.instance.respond_to?(:objectives_subset)
|
52
|
+
@subset = Settings.instance.objectives_subset
|
53
|
+
else
|
54
|
+
@subset = nil
|
55
|
+
end
|
56
|
+
load_objectives
|
49
57
|
# pass objective list back to caller
|
50
|
-
|
58
|
+
@objectives.keys
|
51
59
|
end
|
52
60
|
|
53
61
|
def load_objectives
|
54
62
|
# load objectives
|
55
63
|
# load subset list if available
|
56
64
|
subset_file = @objectives_dir + '/objectives.txt'
|
57
|
-
|
65
|
+
if File.exist?(subset_file)
|
66
|
+
subset = File.open(subset_file).readlines.map { |l| l.strip }
|
67
|
+
else
|
68
|
+
subset = nil
|
69
|
+
end
|
58
70
|
subset = @subset if subset.nil?
|
59
71
|
# parse in objectives
|
60
72
|
Dir.chdir @objectives_dir do
|
@@ -63,7 +75,7 @@ module Biopsy
|
|
63
75
|
require file_name
|
64
76
|
objective_name = file_name.camelize
|
65
77
|
objective = Module.const_get(objective_name).new
|
66
|
-
if subset.nil?
|
78
|
+
if subset.nil? || subset.include?(file_name)
|
67
79
|
# this objective is included
|
68
80
|
@objectives[objective_name] = objective
|
69
81
|
end
|
@@ -74,47 +86,49 @@ module Biopsy
|
|
74
86
|
|
75
87
|
# Run a specific +:objective+ on the +:output+ of a target
|
76
88
|
# with max +:threads+.
|
77
|
-
def run_objective(objective,
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
def run_objective(objective, _name, raw_output, output_files, threads)
|
90
|
+
# output is a, array: [raw_output, output_files].
|
91
|
+
# output_files is a hash containing the absolute paths
|
92
|
+
# to file(s) output by the target in the format expected by the
|
93
|
+
# objective function(s), with keys as the keys expected by the
|
94
|
+
# objective function
|
95
|
+
return objective.run(raw_output, output_files, threads)
|
96
|
+
rescue
|
97
|
+
error = "Error: objective function #{objective.class} does not "
|
98
|
+
error << "implement the run() method\nPlease refer to the "
|
99
|
+
error << "documentation for instructions on adding objective functions"
|
100
|
+
# raise NotImplementedError.new("message")
|
101
|
+
raise NotImplementedError, error
|
90
102
|
end
|
91
103
|
|
92
104
|
# Perform a euclidean distance dimension reduction of multiple objectives
|
93
105
|
def dimension_reduce(results)
|
94
106
|
# calculate the weighted Euclidean distance from optimal
|
95
|
-
# d(p, q) = \sqrt{(p_1 - q_1)^2 + (p_2 - q_2)^2+...+(
|
96
|
-
# here the max value is sqrt(n) where n is no. of results,
|
107
|
+
# d(p, q) = \sqrt{(p_1 - q_1)^2 + (p_2 - q_2)^2+...+(p_n - q_n)^2}
|
108
|
+
# here the max value is sqrt(n) where n is no. of results,
|
109
|
+
# min value (optimum) is 0
|
97
110
|
total = 0
|
98
|
-
results.
|
111
|
+
results.each_value do |value|
|
99
112
|
o = value[:optimum]
|
100
113
|
w = value[:weighting]
|
101
114
|
a = value[:result]
|
102
115
|
m = value[:max]
|
103
|
-
total += w * (((o - a)/m)
|
116
|
+
total += w * (((o - a) / m)**2) if m != 0
|
104
117
|
end
|
105
|
-
|
118
|
+
Math.sqrt(total) / results.length
|
106
119
|
end
|
107
120
|
|
108
|
-
# Run all objectives functions for +:output+.
|
109
|
-
def run_for_output(raw_output, threads
|
121
|
+
# Run all objectives functions for +:output+.
|
122
|
+
def run_for_output(raw_output, threads, allresults)
|
110
123
|
# check output files exist
|
111
124
|
output_files = {}
|
112
125
|
@target.output.each_pair do |key, glob|
|
113
126
|
files = Dir[glob]
|
114
|
-
|
115
|
-
if files.empty? ||
|
116
|
-
|
117
|
-
|
127
|
+
size = files.reduce(1) { |sum, f| sum * File.size(f)}
|
128
|
+
if files.empty? || size==0
|
129
|
+
error = "output files for #{key} matching #{glob} do not exist" +
|
130
|
+
" or are empty"
|
131
|
+
raise ObjectiveHandlerError, error
|
118
132
|
end
|
119
133
|
output_files[key] = files.map { |f| File.expand_path(f) }
|
120
134
|
end
|
@@ -122,19 +136,18 @@ module Biopsy
|
|
122
136
|
# run all objectives for output
|
123
137
|
results = {}
|
124
138
|
@objectives.each_pair do |name, objective|
|
125
|
-
results[name] = self.run_objective(objective, name, raw_output,
|
139
|
+
results[name] = self.run_objective(objective, name, raw_output,
|
140
|
+
output_files, threads)
|
126
141
|
end
|
127
142
|
|
128
143
|
if allresults
|
129
|
-
return {:results => results,
|
130
|
-
|
144
|
+
return { :results => results,
|
145
|
+
:reduced => self.dimension_reduce(results) }
|
131
146
|
else
|
132
|
-
results.
|
147
|
+
results.each_value do |value|
|
133
148
|
return value.kind_of?(Hash) ? value[:result] : value
|
134
149
|
end
|
135
150
|
end
|
136
151
|
end
|
137
|
-
|
138
152
|
end
|
139
|
-
|
140
153
|
end
|
@@ -18,7 +18,7 @@ require 'logger'
|
|
18
18
|
|
19
19
|
module Biopsy
|
20
20
|
# options - is a hash of two hashes, :settings and :parameters
|
21
|
-
# :ranges are arrays to be parameter sweeped
|
21
|
+
# :ranges are arrays to be parameter sweeped
|
22
22
|
# ---(single values may be present, these are also remain unchanged but are accessible within the parameters hash to the constructor)
|
23
23
|
class ParameterSweeper
|
24
24
|
|
@@ -45,7 +45,7 @@ module Biopsy
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def setup
|
48
|
+
def setup(*_args)
|
49
49
|
@best = {
|
50
50
|
:parameters => nil,
|
51
51
|
:score => nil
|
@@ -54,9 +54,11 @@ module Biopsy
|
|
54
54
|
|
55
55
|
# return the next parameter set to evaluate
|
56
56
|
def run_one_iteration(parameters, score)
|
57
|
-
@current = {:parameters => parameters, :score => score}
|
57
|
+
@current = { :parameters => parameters, :score => score }
|
58
58
|
self.update_best?
|
59
|
-
@combinations.pop
|
59
|
+
@combinations.pop
|
60
|
+
rescue
|
61
|
+
nil
|
60
62
|
end
|
61
63
|
|
62
64
|
def update_best?
|
@@ -68,7 +70,7 @@ module Biopsy
|
|
68
70
|
# generate all the parameter combinations to be applied
|
69
71
|
def generate_combinations(index, opts)
|
70
72
|
if index == @ranges.length
|
71
|
-
@combinations << opts.clone
|
73
|
+
@combinations << opts.clone
|
72
74
|
return
|
73
75
|
end
|
74
76
|
# recurse
|
@@ -88,15 +90,15 @@ module Biopsy
|
|
88
90
|
end
|
89
91
|
|
90
92
|
def select_starting_point
|
91
|
-
|
93
|
+
@combinations.pop
|
92
94
|
end
|
93
95
|
|
94
96
|
def random_start_point
|
95
|
-
|
97
|
+
@combinations.pop
|
96
98
|
end
|
97
99
|
|
98
100
|
def finished?
|
99
|
-
|
101
|
+
@combinations.empty?
|
100
102
|
end
|
101
103
|
|
102
104
|
# True if this algorithm chooses its own starting point
|
@@ -30,7 +30,7 @@ module Biopsy
|
|
30
30
|
@range = range
|
31
31
|
@sd_increment_proportion = sd_increment_proportion
|
32
32
|
self.generate_distribution
|
33
|
-
|
33
|
+
rescue
|
34
34
|
raise "generation of distribution with mean: #{@mean}, sd: #{@sd} failed."
|
35
35
|
end
|
36
36
|
|
@@ -45,7 +45,7 @@ module Biopsy
|
|
45
45
|
end
|
46
46
|
|
47
47
|
# loosen the distribution by increasing the sd
|
48
|
-
# and
|
48
|
+
# and regenerating
|
49
49
|
def loosen(factor=1)
|
50
50
|
@sd += @sd_increment_proportion * factor * @range.size
|
51
51
|
self.limit_sd
|
@@ -159,8 +159,8 @@ module Biopsy
|
|
159
159
|
class TabuSearch #< OptmisationAlgorithm
|
160
160
|
|
161
161
|
attr_reader :current, :best, :hood_no
|
162
|
-
attr_accessor :max_hood_size, :sd_increment_proportion
|
163
|
-
attr_accessor :jump_cutoff
|
162
|
+
attr_accessor :max_hood_size, :sd_increment_proportion
|
163
|
+
attr_accessor :starting_sd_divisor, :backtrack_cutoff, :jump_cutoff
|
164
164
|
|
165
165
|
Thread = Struct.new(:best, :tabu, :distributions,
|
166
166
|
:standard_deviations, :recent_scores,
|
data/lib/biopsy/target.rb
CHANGED
@@ -5,6 +5,9 @@ module Biopsy
|
|
5
5
|
class TargetLoadError < Exception
|
6
6
|
end
|
7
7
|
|
8
|
+
class TypeLoadError < Exception
|
9
|
+
end
|
10
|
+
|
8
11
|
class Target
|
9
12
|
require 'yaml'
|
10
13
|
require 'set'
|
@@ -104,12 +107,25 @@ module Biopsy
|
|
104
107
|
# optimise this parameter
|
105
108
|
if data[:values]
|
106
109
|
# definition has provided an array of values
|
107
|
-
|
110
|
+
if !data[:values].is_a? Array
|
111
|
+
raise TargetLoadError.new("'values' for parameter #{param} is not an array")
|
112
|
+
end
|
113
|
+
if data[:type] == 'integer'
|
114
|
+
data[:values].each do |v|
|
115
|
+
raise TypeLoadError.new("'values' for parameter #{param} expected integer") unless v.is_a? Integer
|
116
|
+
end
|
117
|
+
elsif data[:type] == 'string'
|
118
|
+
data[:values].each do |v|
|
119
|
+
raise TypeLoadError.new("'values' for parameter #{param} expected string") unless v.is_a? String
|
120
|
+
end
|
121
|
+
end
|
108
122
|
@parameters[param] = data[:values]
|
109
123
|
else
|
110
124
|
# definition has specified a range
|
111
125
|
min, max, step = data[:min], data[:max], data[:step]
|
112
|
-
|
126
|
+
unless min && max
|
127
|
+
raise TargetLoadError.new("min and max must be set for parameter #{param}")
|
128
|
+
end
|
113
129
|
range = (min..max)
|
114
130
|
range = range.step(step) if step
|
115
131
|
@parameters[param] = range.to_a
|
@@ -128,10 +144,10 @@ module Biopsy
|
|
128
144
|
|
129
145
|
# pass calls to missing methods to the constructor iff
|
130
146
|
# the constructor's class directly defines that method
|
131
|
-
def method_missing(
|
147
|
+
def method_missing(method, *args, &block)
|
132
148
|
const_methods = @constructor.class.instance_methods(false)
|
133
|
-
if const_methods.include?
|
134
|
-
@constructor
|
149
|
+
if const_methods.include? method
|
150
|
+
return @constructor.send(method, *args, &block)
|
135
151
|
else
|
136
152
|
super
|
137
153
|
end
|
@@ -139,9 +155,9 @@ module Biopsy
|
|
139
155
|
|
140
156
|
# accurately report ability to respond to methods passed
|
141
157
|
# to constructor
|
142
|
-
def
|
158
|
+
def respond_to?(method, *args, &block)
|
143
159
|
const_methods = @constructor.class.instance_methods(false)
|
144
|
-
if const_methods.include?
|
160
|
+
if const_methods.include? method
|
145
161
|
true
|
146
162
|
else
|
147
163
|
super
|
data/lib/biopsy/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -106,7 +106,7 @@ class TargetTest
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def fake_method
|
109
|
-
|
109
|
+
:fake_method_success
|
110
110
|
end
|
111
111
|
|
112
112
|
end
|
@@ -153,6 +153,24 @@ end
|
|
153
153
|
self.string_dump objective, @objective_path
|
154
154
|
end
|
155
155
|
|
156
|
+
def create_invalid_objective
|
157
|
+
objective = %Q{
|
158
|
+
class TestObjective2 < Biopsy::ObjectiveFunction
|
159
|
+
|
160
|
+
require 'yaml'
|
161
|
+
|
162
|
+
def initialize
|
163
|
+
@optimum = 0
|
164
|
+
@max = 0
|
165
|
+
@weighting = 1
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
}
|
170
|
+
@objective_path = File.join(@objective_dir, 'test_objective2.rb')
|
171
|
+
self.string_dump objective, @objective_path
|
172
|
+
end
|
173
|
+
|
156
174
|
# Dump +:object+ as YAML to +:file+
|
157
175
|
def yaml_dump object, file
|
158
176
|
self.string_dump object.to_yaml, file
|
data/test/test_experiment.rb
CHANGED
@@ -24,25 +24,27 @@ class TestExperiment < Test::Unit::TestCase
|
|
24
24
|
@h.cleanup
|
25
25
|
end
|
26
26
|
|
27
|
-
should
|
27
|
+
should 'fail to init when passed a non existent target' do
|
28
28
|
assert_raise Biopsy::TargetLoadError do
|
29
29
|
Biopsy::Experiment.new('fake_target')
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
should
|
33
|
+
should 'be able to select a valid point from the parameter space' do
|
34
34
|
e = Biopsy::Experiment.new('target_test')
|
35
35
|
start_point = e.random_start_point
|
36
36
|
start_point.each_pair do |param, value|
|
37
|
-
assert @target.parameters[param].include?(value),
|
37
|
+
assert @target.parameters[param].include?(value),
|
38
|
+
"#{value} not in #{@target.parameters[param]}"
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
|
-
should
|
42
|
+
should 'be able to select a starting point' do
|
42
43
|
e = Biopsy::Experiment.new('target_test')
|
43
44
|
start_point = e.start
|
44
45
|
start_point.each_pair do |param, value|
|
45
|
-
assert @target.parameters[param].include?(value),
|
46
|
+
assert @target.parameters[param].include?(value),
|
47
|
+
"#{value} not in #{@target.parameters[param]}"
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
@@ -52,12 +54,12 @@ class TestExperiment < Test::Unit::TestCase
|
|
52
54
|
assert_equal s, e.start
|
53
55
|
end
|
54
56
|
|
55
|
-
should
|
57
|
+
should 'automatically select an optimiser if none is specified' do
|
56
58
|
e = Biopsy::Experiment.new('target_test')
|
57
59
|
assert e.algorithm.kind_of? Biopsy::TabuSearch
|
58
60
|
end
|
59
61
|
|
60
|
-
should
|
62
|
+
should 'return an optimal set of parameters and score when run' do
|
61
63
|
Dir.chdir @h.tmp_dir do
|
62
64
|
e = Biopsy::Experiment.new('target_test', verbosity: :silent)
|
63
65
|
known_best = -4
|
@@ -66,7 +68,11 @@ class TestExperiment < Test::Unit::TestCase
|
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
69
|
-
should
|
71
|
+
should 'always finish running an experiment' do
|
72
|
+
assert_equal false, true, 'not yet implemented'
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'run really quickly when starting from the optimal parameters' do
|
70
76
|
Dir.chdir @h.tmp_dir do
|
71
77
|
s = {:a => 4, :b => 4, :c => 4}
|
72
78
|
e = Biopsy::Experiment.new('target_test', start: s, verbosity: :silent)
|
@@ -76,14 +82,13 @@ class TestExperiment < Test::Unit::TestCase
|
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
79
|
-
should
|
85
|
+
should 'run using the parameter sweeper (with limit)' do
|
80
86
|
Dir.chdir @h.tmp_dir do
|
81
87
|
p = Biopsy::ParameterSweeper.new(@target.parameters, limit: 250)
|
82
88
|
e = Biopsy::Experiment.new('target_test', algorithm: p, verbosity: :silent)
|
83
89
|
best_found = e.run[:score]
|
84
|
-
assert best_found
|
90
|
+
assert best_found
|
85
91
|
end
|
86
92
|
end
|
87
93
|
end # Experiment context
|
88
|
-
|
89
|
-
end # TestExperiment
|
94
|
+
end # TestExperiment
|
@@ -61,11 +61,32 @@ class TestObjectiveHandler < Test::Unit::TestCase
|
|
61
61
|
f.puts values.to_yaml
|
62
62
|
end
|
63
63
|
Dir.chdir(@h.tmp_dir) do
|
64
|
-
result = oh.run_for_output(nil,
|
64
|
+
result = oh.run_for_output(nil, 1, nil)
|
65
65
|
assert_equal 0, result
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
should "run an objective and return all the results" do
|
70
|
+
oh = Biopsy::ObjectiveHandler.new @target
|
71
|
+
values = {
|
72
|
+
:a => 4,
|
73
|
+
:b => 4,
|
74
|
+
:c => 4
|
75
|
+
}
|
76
|
+
file = File.expand_path(File.join(@h.tmp_dir, 'output.txt'))
|
77
|
+
File.open(file, 'w') do |f|
|
78
|
+
f.puts values.to_yaml
|
79
|
+
end
|
80
|
+
expected = {:results=> {
|
81
|
+
"TestObjective"=>{:optimum=>0, :max=>0, :weighting=>1, :result=>-0.0}
|
82
|
+
},
|
83
|
+
:reduced => 0.0}
|
84
|
+
Dir.chdir(@h.tmp_dir) do
|
85
|
+
result = oh.run_for_output(nil, 1, 1)
|
86
|
+
assert_equal result, expected
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
69
90
|
should "perform euclidean distance dimension reduction" do
|
70
91
|
oh = Biopsy::ObjectiveHandler.new @target
|
71
92
|
results = {
|
@@ -91,6 +112,41 @@ class TestObjectiveHandler < Test::Unit::TestCase
|
|
91
112
|
assert_equal 0.47140452079103173, oh.dimension_reduce(results)
|
92
113
|
end
|
93
114
|
|
115
|
+
should "raise NotImplementedError" do
|
116
|
+
@h.create_invalid_objective
|
117
|
+
oh = Biopsy::ObjectiveHandler.new @target
|
118
|
+
values = {
|
119
|
+
:a => 4,
|
120
|
+
:b => 4,
|
121
|
+
:c => 4
|
122
|
+
}
|
123
|
+
file = File.expand_path(File.join(@h.tmp_dir, 'output.txt'))
|
124
|
+
File.open(file, 'w') do |f|
|
125
|
+
f.puts values.to_yaml
|
126
|
+
end
|
127
|
+
Dir.chdir(@h.tmp_dir) do
|
128
|
+
assert_raise NotImplementedError do
|
129
|
+
result = oh.run_for_output(nil, 1, nil)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
should "raise ObjectiveHandlerError" do
|
136
|
+
oh = Biopsy::ObjectiveHandler.new @target
|
137
|
+
values = {}
|
138
|
+
# file = File.expand_path(File.join(@h.tmp_dir, 'output.txt'))
|
139
|
+
# File.open(file, 'w') do |f|
|
140
|
+
# f.puts values.to_yaml
|
141
|
+
# end
|
142
|
+
Dir.chdir(@h.tmp_dir) do
|
143
|
+
assert_raise Biopsy::ObjectiveHandlerError do
|
144
|
+
result = oh.run_for_output(nil, 1, nil)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
94
150
|
end # Experiment context
|
95
151
|
|
96
152
|
end # TestExperiment
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestParameterSweeper < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "ParameterSweeper" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
ranges = {:a => [1,2,3], :b => [1,2,3]}
|
9
|
+
@sweep = Biopsy::ParameterSweeper.new(ranges)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "generate a list of combinations" do
|
13
|
+
c = @sweep.combinations
|
14
|
+
assert_equal c.size, 9
|
15
|
+
assert_equal c, [{:a=>1, :b=>1}, {:a=>1, :b=>2}, {:a=>1, :b=>3},
|
16
|
+
{:a=>2, :b=>1}, {:a=>2, :b=>2}, {:a=>2, :b=>3},
|
17
|
+
{:a=>3, :b=>1}, {:a=>3, :b=>2}, {:a=>3, :b=>3}]
|
18
|
+
end
|
19
|
+
|
20
|
+
end # Experiment context
|
21
|
+
|
22
|
+
end # TestExperiment
|
data/test/test_settings.rb
CHANGED
@@ -17,13 +17,19 @@ class TestSettings < Test::Unit::TestCase
|
|
17
17
|
end
|
18
18
|
|
19
19
|
teardown do
|
20
|
-
File.delete @config_file if File.
|
20
|
+
File.delete @config_file if File.exist? @config_file
|
21
21
|
end
|
22
22
|
|
23
23
|
should "load the specified config file" do
|
24
24
|
assert @settings.objectives_dir == @data[:objectives_dir]
|
25
25
|
end
|
26
26
|
|
27
|
+
should "raise an error on loading invalid YAML file" do
|
28
|
+
assert_raise(Biopsy::SettingsError) do
|
29
|
+
@settings.load(File.expand_path('test/brokenconfig.yml'))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
27
33
|
should "complain about malformed config file" do
|
28
34
|
# write non-YAML data to file
|
29
35
|
File.open(@config_file, 'w') do |f|
|
@@ -38,7 +44,7 @@ class TestSettings < Test::Unit::TestCase
|
|
38
44
|
@settings.save @config_file
|
39
45
|
@settings.load @config_file
|
40
46
|
@data.each_pair do |key, value|
|
41
|
-
varname = "@#{key
|
47
|
+
varname = "@#{key}".to_sym
|
42
48
|
assert_equal value, @settings.instance_variable_get(varname)
|
43
49
|
end
|
44
50
|
end
|
@@ -48,18 +54,19 @@ class TestSettings < Test::Unit::TestCase
|
|
48
54
|
end
|
49
55
|
|
50
56
|
should "make loaded settings available as methods" do
|
51
|
-
assert @settings.objectives_dir == @data[:objectives_dir],
|
57
|
+
assert @settings.objectives_dir == @data[:objectives_dir],
|
58
|
+
'objectives_dir key not loaded as method'
|
52
59
|
end
|
53
60
|
|
54
61
|
should "produce a YAML string representation" do
|
55
62
|
s = @settings.to_s
|
56
63
|
h = YAML.load(s)
|
57
64
|
h.each_pair do |key, value|
|
58
|
-
varname = "@#{key
|
65
|
+
varname = "@#{key}".to_sym
|
59
66
|
assert_equal value, @settings.instance_variable_get(varname)
|
60
67
|
end
|
61
68
|
end
|
62
69
|
|
63
70
|
end # RunHandler context
|
64
71
|
|
65
|
-
end # TestRunHandler
|
72
|
+
end # TestRunHandler
|
data/test/test_target.rb
CHANGED
@@ -56,6 +56,7 @@ class TestTarget < Test::Unit::TestCase
|
|
56
56
|
|
57
57
|
should "be able to store a loaded config file" do
|
58
58
|
config = YAML::load_file(@h.target_path).deep_symbolize
|
59
|
+
config[:shortname] = 'tt'
|
59
60
|
@target.store_config config
|
60
61
|
@h.target_data.each_pair do |key, value|
|
61
62
|
if key == :parameters
|
@@ -65,10 +66,10 @@ class TestTarget < Test::Unit::TestCase
|
|
65
66
|
r = (spec[:min]..spec[:max])
|
66
67
|
r = spec[:step] ? r.step(spec[:step]) : r
|
67
68
|
parsed[param] = r.to_a
|
68
|
-
elsif spec[:values]
|
69
|
-
|
70
|
-
else
|
71
|
-
|
69
|
+
# elsif spec[:values]
|
70
|
+
# parsed[param] = spec[:values]
|
71
|
+
# else
|
72
|
+
# assert false, "parameter #{param} with spec #{spec} has no range or values"
|
72
73
|
end
|
73
74
|
end
|
74
75
|
parsed.each_pair do |param, spec|
|
@@ -95,10 +96,81 @@ class TestTarget < Test::Unit::TestCase
|
|
95
96
|
File.delete @target.constructor_path
|
96
97
|
end
|
97
98
|
|
98
|
-
should "
|
99
|
+
should "raise an exception if values doesn't provide an array" do
|
100
|
+
params = {
|
101
|
+
:a => {
|
102
|
+
:type => 'integer',
|
103
|
+
:opt => true,
|
104
|
+
:values => 3
|
105
|
+
}
|
106
|
+
}
|
107
|
+
assert_raise Biopsy::TargetLoadError do
|
108
|
+
@target.generate_parameters params
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
should "raise an exception for trying to load a string as an integer" do
|
113
|
+
params = {
|
114
|
+
:a => {
|
115
|
+
:type => 'integer',
|
116
|
+
:opt => true,
|
117
|
+
:values => ["yes","no","maybe"]
|
118
|
+
}
|
119
|
+
}
|
120
|
+
assert_raise Biopsy::TypeLoadError do
|
121
|
+
@target.generate_parameters params
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
should "raise an exception for trying to load an integer as a string" do
|
126
|
+
params = {
|
127
|
+
:b => {
|
128
|
+
:type => 'string',
|
129
|
+
:opt => true,
|
130
|
+
:values => [1,2,3]
|
131
|
+
}
|
132
|
+
}
|
133
|
+
assert_raise Biopsy::TypeLoadError do
|
134
|
+
@target.generate_parameters params
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
should "raise an exception of type TargetLoadError" do
|
139
|
+
params = {
|
140
|
+
:a => {
|
141
|
+
:type => 'integer',
|
142
|
+
:opt => true
|
143
|
+
}
|
144
|
+
}
|
145
|
+
assert_raise Biopsy::TargetLoadError do
|
146
|
+
@target.generate_parameters params
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
should "load array" do
|
151
|
+
params = {
|
152
|
+
:a => {
|
153
|
+
:type => 'string',
|
154
|
+
:opt => true,
|
155
|
+
:values => ["yes","no","maybe"]
|
156
|
+
},
|
157
|
+
:b => {
|
158
|
+
:type => 'integer',
|
159
|
+
:opt => false,
|
160
|
+
:values => 0
|
161
|
+
}
|
162
|
+
}
|
163
|
+
@target.generate_parameters params
|
164
|
+
assert_equal @target.parameters, {:a => ["yes", "no", "maybe"]}
|
165
|
+
assert_equal @target.options, {:b=>{:type=>"integer", :opt=>false, :values=>0}}
|
166
|
+
end
|
167
|
+
|
168
|
+
should "pass missing method calls to constructor iff \
|
169
|
+
it directly defines them" do
|
99
170
|
# this method is defined on the constructor in helper.rb
|
100
171
|
assert_send([@target, :fake_method],
|
101
172
|
'valid method not passed to constructor')
|
173
|
+
assert_equal @target.fake_method, :fake_method_success
|
102
174
|
assert_raise NoMethodError do
|
103
175
|
@target.totes_fake_method
|
104
176
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: biopsy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '10.
|
19
|
+
version: '10.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '10.
|
26
|
+
version: '10.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: threach
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,16 +84,16 @@ dependencies:
|
|
84
84
|
name: turn
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
89
|
+
version: '0.9'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
96
|
+
version: '0.9'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,30 +142,32 @@ executables: []
|
|
142
142
|
extensions: []
|
143
143
|
extra_rdoc_files: []
|
144
144
|
files:
|
145
|
-
- LICENSE.txt
|
146
|
-
- README.md
|
147
145
|
- Rakefile
|
148
146
|
- lib/biopsy.rb
|
149
|
-
- lib/biopsy/
|
150
|
-
- lib/biopsy/experiment.rb
|
151
|
-
- lib/biopsy/objective_function.rb
|
147
|
+
- lib/biopsy/version.rb
|
152
148
|
- lib/biopsy/objective_handler.rb
|
153
149
|
- lib/biopsy/objectives/fastest_optimum.rb
|
150
|
+
- lib/biopsy/settings.rb
|
151
|
+
- lib/biopsy/optimisers/tabu_search.rb
|
154
152
|
- lib/biopsy/optimisers/genetic_algorithm.rb
|
155
|
-
- lib/biopsy/optimisers/parameter_sweeper.rb
|
156
153
|
- lib/biopsy/optimisers/spea2.rb
|
157
|
-
- lib/biopsy/optimisers/
|
158
|
-
- lib/biopsy/
|
154
|
+
- lib/biopsy/optimisers/parameter_sweeper.rb
|
155
|
+
- lib/biopsy/objective_function.rb
|
159
156
|
- lib/biopsy/target.rb
|
160
|
-
- lib/biopsy/
|
161
|
-
-
|
162
|
-
- test/
|
163
|
-
- test/
|
157
|
+
- lib/biopsy/base_extensions.rb
|
158
|
+
- lib/biopsy/experiment.rb
|
159
|
+
- test/test_parametersweep.rb
|
160
|
+
- test/test_target.rb
|
164
161
|
- test/test_hash.rb
|
162
|
+
- test/test_string.rb
|
163
|
+
- test/helper.rb
|
164
|
+
- test/brokenconfig.yml
|
165
165
|
- test/test_objective_handler.rb
|
166
|
+
- test/test_file.rb
|
166
167
|
- test/test_settings.rb
|
167
|
-
- test/
|
168
|
-
-
|
168
|
+
- test/test_experiment.rb
|
169
|
+
- README.md
|
170
|
+
- LICENSE.txt
|
169
171
|
homepage: https://github.com/Blahah/biopsy
|
170
172
|
licenses: []
|
171
173
|
metadata: {}
|
@@ -185,9 +187,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
187
|
version: '0'
|
186
188
|
requirements: []
|
187
189
|
rubyforge_project:
|
188
|
-
rubygems_version: 2.
|
190
|
+
rubygems_version: 2.1.4
|
189
191
|
signing_key:
|
190
192
|
specification_version: 4
|
191
193
|
summary: framework for optimising any computational pipeline or program
|
192
194
|
test_files: []
|
193
|
-
has_rdoc:
|