biopsy 0.1.9 → 0.2.0
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.
- 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:
|