biopsy 0.2.0 → 0.2.1
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/README.md +31 -34
- data/lib/biopsy/experiment.rb +13 -6
- data/lib/biopsy/objective_handler.rb +0 -6
- data/lib/biopsy/optimisers/parameter_sweeper.rb +60 -47
- data/lib/biopsy/version.rb +1 -1
- data/lib/biopsy.rb +2 -2
- data/test/helper.rb +8 -3
- data/test/test_experiment.rb +24 -8
- data/test/test_parametersweep.rb +31 -3
- data/test/test_target.rb +4 -3
- metadata +52 -33
- data/lib/biopsy/optimisers/genetic_algorithm.rb +0 -244
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f45b9377c3de44caf8abf21b07576a6e89e0c03
|
4
|
+
data.tar.gz: 94e4a81fe98380482adc7b290f8067174684850d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76d90535589d1f09f74bf424d7437a45855b1065b802bc3ba4d562cc30c1c8fd775583cd856e6de02fee919a9b34619875693a96b228ef686c7e658bb9b29ea5
|
7
|
+
data.tar.gz: 0dbc5e5d21297759affc2199ffff2fe90d25c1958a7380d531811bdded9fbe8caf8eb9fc0aed8b62ecb5d52d6f0f2e2292b480ae8a8e84c79f0e457f44f45c3a
|
data/README.md
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
biopsy
|
2
2
|
==========
|
3
3
|
|
4
|
+
[][gem]
|
5
|
+
[][travis]
|
6
|
+
[][gemnasium]
|
7
|
+
[][codeclimate]
|
8
|
+
[][coveralls]
|
9
|
+
|
10
|
+
[gem]: https://badge.fury.io/rb/biopsy
|
11
|
+
[travis]: https://travis-ci.org/Blahah/biopsy
|
12
|
+
[gemnasium]: https://gemnasium.com/Blahah/biopsy
|
13
|
+
[codeclimate]: https://codeclimate.com/github/Blahah/biopsy
|
14
|
+
[coveralls]: https://coveralls.io/r/Blahah/biopsy
|
15
|
+
|
4
16
|
An automatic optimisation framework for programs and pipelines.
|
5
17
|
|
6
|
-
Biopsy is a framework for optimising any program or pipeline which produces a measurable output.
|
18
|
+
Biopsy is a framework for optimising the settings of any program or pipeline which produces a measurable output. It is particularly intended for bioinformatics, where computational pipelines take a long time to run, making optimisation of parameters using crude methods unfeasible. Biopsy will use a range of discrete optimisation strategies to rapidly find the settings that perform the best.
|
19
|
+
|
20
|
+
It can handle parameter spaces of any size: if it is possible to try every parameter combination in the time you have available, Biopsy will do this. However, Biopsy really shines when handling large numbers of parameter combinations.
|
7
21
|
|
8
|
-
|
22
|
+
## Development status
|
23
|
+
|
24
|
+
This project is in early development and is not yet ready for deployment.
|
25
|
+
Please don't report issues or request documentation until we are ready for release. If you have a burning desire to use biopsy, get in touch: rds45@cam.ac.uk.
|
9
26
|
|
10
27
|
## Installation
|
11
28
|
|
12
29
|
Make sure you have Ruby installed, then:
|
13
30
|
|
14
|
-
`gem install biopsy
|
31
|
+
`gem install biopsy`
|
15
32
|
|
16
33
|
## Usage
|
17
34
|
|
18
35
|
Detailed usage instructions are on the wiki. Here's a quick overview:
|
19
36
|
|
20
|
-
1. Define your optimisation target. This is a program or pipeline you want to optimise, and you define it by filling in a template
|
37
|
+
1. Define your optimisation target. This is a program or pipeline you want to optimise, and you define it by filling in a template YAML file and wrapping your program in a tiny Ruby launcher.
|
21
38
|
2. Define your objective function. This is a program that analyses the output of your program and gives it a score. You define it by writing a small amount of Ruby code. Don't worry - there's a template and detailed instructions on the wiki.
|
22
|
-
3. Run Biopsy, and wait while the experiment runs.
|
39
|
+
3. Run Biopsy, and wait while the experiment runs. Maybe grab a cup of tea, read some [hacker news](http://news.ycombinator.com).
|
40
|
+
4. Bask in the brilliance of your new optimal settings.
|
23
41
|
|
24
42
|
### Command line examples
|
25
43
|
|
@@ -27,36 +45,11 @@ Detailed usage instructions are on the wiki. Here's a quick overview:
|
|
27
45
|
`biopsy list objectives`
|
28
46
|
`biposy run --target test_target --objective test_objective --input test_file.txt --time-limit 24h`
|
29
47
|
|
30
|
-
|
48
|
+
### Optimisation algorithms
|
31
49
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
[][codeclimate]
|
36
|
-
[][coveralls]
|
37
|
-
|
38
|
-
[gem]: https://badge.fury.io/rb/biopsy
|
39
|
-
[travis]: https://travis-ci.org/Blahah/biopsy
|
40
|
-
[gemnasium]: https://gemnasium.com/Blahah/biopsy
|
41
|
-
[codeclimate]: https://codeclimate.com/github/Blahah/biopsy
|
42
|
-
[coveralls]: https://coveralls.io/r/Blahah/biopsy
|
43
|
-
|
44
|
-
This project is in alpha development and is not yet ready for deployment.
|
45
|
-
Please don't report issues or request documentation until we are ready for beta release (see below for estimated timeframe).
|
46
|
-
|
47
|
-
### Roadmap
|
48
|
-
|
49
|
-
| Class | Code | Tests | Docs |
|
50
|
-
| ------------ | :----: | ------: | -----: |
|
51
|
-
| Settings | DONE | DONE | DONE |
|
52
|
-
| Target | DONE | DONE | DONE |
|
53
|
-
| Domain | DONE | DONE | DONE |
|
54
|
-
| Experiment | DONE | DONE | DONE |
|
55
|
-
| TabuSearch | DONE | - | - |
|
56
|
-
| ParameterSweeper | DONE | - | - |
|
57
|
-
| ObjectiveHandler | DONE | DONE | DONE |
|
58
|
-
|
59
|
-
* ~ 20/24 tasks completed, ~83% done overall
|
50
|
+
1. Parameter Sweeper - a simple combinatorial parameter sweep, with optional subsampling of the parameter space
|
51
|
+
2. Tabu Search - a local search with a long memory that takes the consensus of multiple searchers
|
52
|
+
3. SPEA2 - a high performance general-purpose genetic algorithm
|
60
53
|
|
61
54
|
### Documentation
|
62
55
|
|
@@ -68,3 +61,7 @@ This is *pre-release*, *pre-publication* academic software. In lieu of a paper t
|
|
68
61
|
)](http://dx.doi.org/10.6084/m9.figshare.790660) if your use of the software leads to a publication.
|
69
62
|
|
70
63
|
[](https://github.com/Blahah/biopsy)
|
64
|
+
|
65
|
+
### Software using Biopsy
|
66
|
+
|
67
|
+
- [Assemblotron](https://github.com/Blahah/assemblotron) can fully optimise any *de-novo* transcriptome assembler to produce the optimal assembly possible given a particular input. This typically takes little more time than running a single assembly.
|
data/lib/biopsy/experiment.rb
CHANGED
@@ -23,10 +23,11 @@ module Biopsy
|
|
23
23
|
|
24
24
|
# Returns a new Experiment
|
25
25
|
def initialize(target, options:{}, threads:4, start:nil, algorithm:nil,
|
26
|
-
verbosity: :quiet)
|
26
|
+
timelimit:nil, verbosity: :quiet)
|
27
27
|
@threads = threads
|
28
28
|
@start = start
|
29
29
|
@algorithm = algorithm
|
30
|
+
@timelimit = timelimit
|
30
31
|
@verbosity = verbosity
|
31
32
|
if target.is_a? Target
|
32
33
|
@target = target
|
@@ -78,6 +79,7 @@ module Biopsy
|
|
78
79
|
# are met. On completion, returns the best parameter
|
79
80
|
# set.
|
80
81
|
def run
|
82
|
+
start_time = Time.now
|
81
83
|
in_progress = true
|
82
84
|
@algorithm.setup @start
|
83
85
|
@current_params = @start
|
@@ -92,16 +94,21 @@ module Biopsy
|
|
92
94
|
best &&
|
93
95
|
best.key?(:score) &&
|
94
96
|
@best[:score] > best[:score]
|
95
|
-
|
96
|
-
|
97
|
+
unless @verbosity == :silent
|
98
|
+
puts "found a new best score: #{@best[:score]} "+
|
99
|
+
"for parameters #{ptext}"
|
100
|
+
end
|
97
101
|
end
|
98
102
|
# have we finished?
|
99
103
|
in_progress = !@algorithm.finished?
|
104
|
+
if in_progress && !(@timelimit.nil?)
|
105
|
+
in_progress = (Time.now - start_time < @timelimit)
|
106
|
+
end
|
100
107
|
end
|
101
108
|
@algorithm.write_data if @algorithm.respond_to? :write_data
|
102
109
|
unless @verbosity == :silent
|
103
|
-
puts "found optimum score: #{@best[:score]} for parameters
|
104
|
-
|
110
|
+
puts "found optimum score: #{@best[:score]} for parameters "+
|
111
|
+
"#{@best[:parameters]} in #{@iteration_count} iterations."
|
105
112
|
end
|
106
113
|
return @best
|
107
114
|
end
|
@@ -139,7 +146,7 @@ module Biopsy
|
|
139
146
|
puts msg
|
140
147
|
end
|
141
148
|
end
|
142
|
-
|
149
|
+
|
143
150
|
def cleanup
|
144
151
|
# TODO: make this work
|
145
152
|
# remove all but essential files
|
@@ -93,12 +93,6 @@ module Biopsy
|
|
93
93
|
# objective function(s), with keys as the keys expected by the
|
94
94
|
# objective function
|
95
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
|
102
96
|
end
|
103
97
|
|
104
98
|
# Perform a euclidean distance dimension reduction of multiple objectives
|
@@ -2,13 +2,16 @@
|
|
2
2
|
# options = {:settings => {...}, :parameters => {...}}
|
3
3
|
#
|
4
4
|
# Description:
|
5
|
-
# ParameterSweeper generates all combinations of a hash of arrays
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# ParameterSweeper generates all combinations of a hash of arrays
|
6
|
+
# (options[:parameters]).
|
7
|
+
# The generated combinations are each passed in turn to the constructor
|
8
|
+
# which returns an execute command incorporating the parameters, and finally
|
9
|
+
# the target program is run with each generated command.
|
8
10
|
#
|
9
|
-
# The constructor will also have access to an unchanging settings hash
|
10
|
-
# constructor proc will be passed multipule hashes in
|
11
|
-
#
|
11
|
+
# The constructor will also have access to an unchanging settings hash
|
12
|
+
# (options[:settings]) constructor proc will be passed multipule hashes in
|
13
|
+
# format: {:settings => {...}, :parameters => {...}} where the values in
|
14
|
+
# settings remain constant, and the values in parameters vary
|
12
15
|
|
13
16
|
require 'pp'
|
14
17
|
require 'fileutils'
|
@@ -17,32 +20,52 @@ require 'threach'
|
|
17
20
|
require 'logger'
|
18
21
|
|
19
22
|
module Biopsy
|
23
|
+
|
24
|
+
class Combinator
|
25
|
+
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
def initialize parameters
|
29
|
+
@parameters = parameters
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_combinations(index, opts, &block)
|
33
|
+
if index == @parameters.length
|
34
|
+
block.call opts.clone
|
35
|
+
return
|
36
|
+
end
|
37
|
+
# recurse
|
38
|
+
key = @parameters.keys[index]
|
39
|
+
@parameters[key].each do |value|
|
40
|
+
opts[key] = value
|
41
|
+
generate_combinations(index + 1, opts, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def each &block
|
46
|
+
generate_combinations(0, {}, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
20
50
|
# options - is a hash of two hashes, :settings and :parameters
|
21
51
|
# :ranges are arrays to be parameter sweeped
|
22
|
-
# ---(single values may be present, these are also remain unchanged
|
52
|
+
# ---(single values may be present, these are also remain unchanged
|
53
|
+
# but are accessible within the parameters hash to the constructor)
|
23
54
|
class ParameterSweeper
|
24
55
|
|
25
|
-
attr_reader :combinations
|
56
|
+
attr_reader :combinator, :combinations, :best
|
26
57
|
|
27
|
-
def initialize(ranges
|
58
|
+
def initialize(ranges)
|
28
59
|
@ranges = ranges
|
29
|
-
#
|
30
|
-
|
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
|
-
# set the limit to the number of parameters
|
36
|
-
@limit = limit
|
37
|
-
# convert all options to an array so it can be handled by the generate_combinations() method
|
60
|
+
# convert all options to an array so it can be handled by the
|
61
|
+
# generate_combinations() method
|
38
62
|
# ..this is for users entering single values e.g 4 as a parameter
|
39
|
-
@ranges.
|
40
|
-
self.generate_combinations(0, {})
|
63
|
+
@ranges.each_value{ |value| value = [value] unless value.kind_of? Array }
|
41
64
|
# restrict to a subsample?
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
65
|
+
@combinations = 1
|
66
|
+
@ranges.each { |r| @combinations *= r[1].size }
|
67
|
+
@is_finished = false
|
68
|
+
@combinator = (Combinator.new @ranges).to_enum
|
46
69
|
end
|
47
70
|
|
48
71
|
def setup(*_args)
|
@@ -56,33 +79,23 @@ module Biopsy
|
|
56
79
|
def run_one_iteration(parameters, score)
|
57
80
|
@current = { :parameters => parameters, :score => score }
|
58
81
|
self.update_best?
|
59
|
-
@
|
60
|
-
rescue
|
61
|
-
|
82
|
+
return @combinator.next
|
83
|
+
rescue
|
84
|
+
@is_finished = true
|
85
|
+
return nil
|
62
86
|
end
|
63
87
|
|
64
88
|
def update_best?
|
89
|
+
raise "best is nil. should run setup first" if @best.nil?
|
65
90
|
if @best[:score].nil? || @current[:score] > @best[:score]
|
66
91
|
@best = @current.clone
|
67
92
|
end
|
68
93
|
end
|
69
94
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
return
|
75
|
-
end
|
76
|
-
# recurse
|
77
|
-
key = @ranges.keys[index]
|
78
|
-
@ranges[key].each do |value|
|
79
|
-
opts[key] = value
|
80
|
-
generate_combinations(index + 1, opts)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def best
|
85
|
-
@best
|
95
|
+
def next
|
96
|
+
@combinator.next
|
97
|
+
rescue
|
98
|
+
nil
|
86
99
|
end
|
87
100
|
|
88
101
|
def knows_starting_point?
|
@@ -90,15 +103,15 @@ module Biopsy
|
|
90
103
|
end
|
91
104
|
|
92
105
|
def select_starting_point
|
93
|
-
@
|
106
|
+
@combinator.next
|
94
107
|
end
|
95
108
|
|
96
109
|
def random_start_point
|
97
|
-
@
|
110
|
+
@combinator.next
|
98
111
|
end
|
99
112
|
|
100
113
|
def finished?
|
101
|
-
@
|
114
|
+
@is_finished
|
102
115
|
end
|
103
116
|
|
104
117
|
# True if this algorithm chooses its own starting point
|
@@ -106,4 +119,4 @@ module Biopsy
|
|
106
119
|
true
|
107
120
|
end
|
108
121
|
end
|
109
|
-
end
|
122
|
+
end
|
data/lib/biopsy/version.rb
CHANGED
data/lib/biopsy.rb
CHANGED
@@ -5,7 +5,7 @@ require "biopsy/experiment"
|
|
5
5
|
require "biopsy/target"
|
6
6
|
require "biopsy/objective_handler"
|
7
7
|
require "biopsy/objective_function"
|
8
|
-
require "biopsy/optimisers/
|
8
|
+
require "biopsy/optimisers/spea2"
|
9
9
|
require "biopsy/optimisers/tabu_search"
|
10
10
|
require "biopsy/optimisers/parameter_sweeper"
|
11
|
-
require "biopsy/objectives/fastest_optimum"
|
11
|
+
require "biopsy/objectives/fastest_optimum"
|
data/test/helper.rb
CHANGED
@@ -57,14 +57,14 @@ class Helper
|
|
57
57
|
opt: true,
|
58
58
|
min: -40,
|
59
59
|
max: 40,
|
60
|
-
step:
|
60
|
+
step: 5
|
61
61
|
},
|
62
62
|
:b => {
|
63
63
|
type: 'integer',
|
64
64
|
opt: true,
|
65
65
|
min: 0,
|
66
66
|
max: 40,
|
67
|
-
step:
|
67
|
+
step: 5
|
68
68
|
},
|
69
69
|
:c => {
|
70
70
|
type: 'integer',
|
@@ -84,8 +84,9 @@ class Helper
|
|
84
84
|
end
|
85
85
|
|
86
86
|
# Create a valid target definition in the target dir
|
87
|
-
def create_valid_target
|
87
|
+
def create_valid_target slow: false
|
88
88
|
data = self.target_data
|
89
|
+
data[:slow] = slow
|
89
90
|
name = 'target_test'
|
90
91
|
@target_path = File.join(@target_dir, name + '.yml')
|
91
92
|
self.yaml_dump data, @target_path
|
@@ -99,6 +100,9 @@ class TargetTest
|
|
99
100
|
require 'yaml'
|
100
101
|
|
101
102
|
def run(params)
|
103
|
+
if (params[:slow])
|
104
|
+
sleep 0.1
|
105
|
+
end
|
102
106
|
File.open('output.txt', 'w') do |f|
|
103
107
|
f.puts(params.to_yaml)
|
104
108
|
end
|
@@ -139,6 +143,7 @@ class TestObjective < Biopsy::ObjectiveFunction
|
|
139
143
|
a = input[:a].to_i
|
140
144
|
b = input[:b].to_i
|
141
145
|
c = input[:c].to_i
|
146
|
+
|
142
147
|
value = - Math.sqrt((a-4)**2) - Math.sqrt((b-4)**2) - Math.sqrt((c-4)**2)
|
143
148
|
{
|
144
149
|
:optimum => @optimum,
|
data/test/test_experiment.rb
CHANGED
@@ -49,7 +49,7 @@ class TestExperiment < Test::Unit::TestCase
|
|
49
49
|
end
|
50
50
|
|
51
51
|
should "respect user's choice of starting point" do
|
52
|
-
s = {:a => 4, :b => 2}
|
52
|
+
s = {:a => 4, :b => 2}
|
53
53
|
e = Biopsy::Experiment.new('target_test', start: s, verbosity: :silent)
|
54
54
|
assert_equal s, e.start
|
55
55
|
end
|
@@ -68,13 +68,29 @@ class TestExperiment < Test::Unit::TestCase
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
should '
|
72
|
-
|
71
|
+
should 'respect time limits' do
|
72
|
+
# create a slow target
|
73
|
+
target_name = @h.create_valid_target(slow: true)
|
74
|
+
@target = Biopsy::Target.new
|
75
|
+
@target.load_by_name target_name
|
76
|
+
limits = [0.1, 0.2, 0.5, 1]
|
77
|
+
limits.each do |limit|
|
78
|
+
e = Biopsy::Experiment.new('target_test',
|
79
|
+
verbosity: :silent, timelimit: limit)
|
80
|
+
start = Time.now
|
81
|
+
e.run
|
82
|
+
finish = Time.now
|
83
|
+
assert (finish - start) < (limit + 0.2)
|
84
|
+
end
|
85
|
+
# reset the target
|
86
|
+
target_name = @h.create_valid_target(slow: false)
|
87
|
+
@target = Biopsy::Target.new
|
88
|
+
@target.load_by_name target_name
|
73
89
|
end
|
74
90
|
|
75
91
|
should 'run really quickly when starting from the optimal parameters' do
|
76
92
|
Dir.chdir @h.tmp_dir do
|
77
|
-
s = {:a => 4, :b => 4, :c => 4}
|
93
|
+
s = {:a => 4, :b => 4, :c => 4}
|
78
94
|
e = Biopsy::Experiment.new('target_test', start: s, verbosity: :silent)
|
79
95
|
known_best = -4
|
80
96
|
best_found = e.run[:score]
|
@@ -82,12 +98,12 @@ class TestExperiment < Test::Unit::TestCase
|
|
82
98
|
end
|
83
99
|
end
|
84
100
|
|
85
|
-
should 'run using the parameter sweeper
|
101
|
+
should 'run using the parameter sweeper' do
|
86
102
|
Dir.chdir @h.tmp_dir do
|
87
|
-
p = Biopsy::ParameterSweeper.new(@target.parameters
|
103
|
+
p = Biopsy::ParameterSweeper.new(@target.parameters)
|
88
104
|
e = Biopsy::Experiment.new('target_test', algorithm: p, verbosity: :silent)
|
89
|
-
best_found = e.run[:
|
90
|
-
|
105
|
+
best_found = e.run[:parameters]
|
106
|
+
assert_equal best_found, {:a=>4, :b=>4, :c=>4}
|
91
107
|
end
|
92
108
|
end
|
93
109
|
end # Experiment context
|
data/test/test_parametersweep.rb
CHANGED
@@ -7,16 +7,44 @@ class TestParameterSweeper < Test::Unit::TestCase
|
|
7
7
|
setup do
|
8
8
|
ranges = {:a => [1,2,3], :b => [1,2,3]}
|
9
9
|
@sweep = Biopsy::ParameterSweeper.new(ranges)
|
10
|
+
@sweep.setup
|
10
11
|
end
|
11
12
|
|
12
|
-
should "
|
13
|
+
should "calculate number of combinations" do
|
13
14
|
c = @sweep.combinations
|
14
|
-
assert_equal c
|
15
|
-
|
15
|
+
assert_equal c, 9
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'generate list of combinations' do
|
19
|
+
list=[]
|
20
|
+
9.times do
|
21
|
+
list << @sweep.next
|
22
|
+
end
|
23
|
+
assert_equal list, [{:a=>1, :b=>1}, {:a=>1, :b=>2}, {:a=>1, :b=>3},
|
16
24
|
{:a=>2, :b=>1}, {:a=>2, :b=>2}, {:a=>2, :b=>3},
|
17
25
|
{:a=>3, :b=>1}, {:a=>3, :b=>2}, {:a=>3, :b=>3}]
|
18
26
|
end
|
19
27
|
|
28
|
+
should "exit gracefully when you ask for too much" do
|
29
|
+
c = 1
|
30
|
+
10.times do
|
31
|
+
c = @sweep.run_one_iteration(nil, 0)
|
32
|
+
end
|
33
|
+
assert_equal c, nil
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'check if finished' do
|
37
|
+
assert_equal @sweep.finished?, false, "at the start"
|
38
|
+
8.times do
|
39
|
+
@sweep.run_one_iteration(nil, 0)
|
40
|
+
end
|
41
|
+
assert_equal @sweep.finished?, false, "after 8"
|
42
|
+
@sweep.run_one_iteration(nil, 0)
|
43
|
+
assert_equal @sweep.finished?, false, "after 9"
|
44
|
+
@sweep.run_one_iteration(nil, 0)
|
45
|
+
assert_equal @sweep.finished?, true, "after 10"
|
46
|
+
end
|
47
|
+
|
20
48
|
end # Experiment context
|
21
49
|
|
22
50
|
end # TestExperiment
|
data/test/test_target.rb
CHANGED
@@ -162,11 +162,12 @@ class TestTarget < Test::Unit::TestCase
|
|
162
162
|
}
|
163
163
|
@target.generate_parameters params
|
164
164
|
assert_equal @target.parameters, {:a => ["yes", "no", "maybe"]}
|
165
|
-
assert_equal @target.options,
|
165
|
+
assert_equal @target.options,
|
166
|
+
{:b=> { :type=>"integer", :opt=>false, :values=>0 } }
|
166
167
|
end
|
167
168
|
|
168
|
-
should "pass missing method calls to constructor iff
|
169
|
-
|
169
|
+
should "pass missing method calls to constructor iff "+
|
170
|
+
"it directly defines them" do
|
170
171
|
# this method is defined on the constructor in helper.rb
|
171
172
|
assert_send([@target, :fake_method],
|
172
173
|
'valid method not passed to constructor')
|
metadata
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: biopsy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Richard Smith
|
7
|
+
- Richard Smith-Unna
|
8
|
+
- Parsa Akbari
|
9
|
+
- Chris Bournsell
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
date:
|
13
|
+
date: 2015-06-02 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: rake
|
@@ -16,56 +18,74 @@ dependencies:
|
|
16
18
|
requirements:
|
17
19
|
- - "~>"
|
18
20
|
- !ruby/object:Gem::Version
|
19
|
-
version: '10.
|
21
|
+
version: '10.4'
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 10.4.2
|
20
25
|
type: :runtime
|
21
26
|
prerelease: false
|
22
27
|
version_requirements: !ruby/object:Gem::Requirement
|
23
28
|
requirements:
|
24
29
|
- - "~>"
|
25
30
|
- !ruby/object:Gem::Version
|
26
|
-
version: '10.
|
31
|
+
version: '10.4'
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 10.4.2
|
27
35
|
- !ruby/object:Gem::Dependency
|
28
36
|
name: threach
|
29
37
|
requirement: !ruby/object:Gem::Requirement
|
30
38
|
requirements:
|
31
|
-
- - "
|
39
|
+
- - "~>"
|
32
40
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
41
|
+
version: '0.2'
|
34
42
|
type: :runtime
|
35
43
|
prerelease: false
|
36
44
|
version_requirements: !ruby/object:Gem::Requirement
|
37
45
|
requirements:
|
38
|
-
- - "
|
46
|
+
- - "~>"
|
39
47
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
48
|
+
version: '0.2'
|
41
49
|
- !ruby/object:Gem::Dependency
|
42
50
|
name: rubystats
|
43
51
|
requirement: !ruby/object:Gem::Requirement
|
44
52
|
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.2'
|
45
56
|
- - ">="
|
46
57
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
58
|
+
version: 0.2.3
|
48
59
|
type: :runtime
|
49
60
|
prerelease: false
|
50
61
|
version_requirements: !ruby/object:Gem::Requirement
|
51
62
|
requirements:
|
63
|
+
- - "~>"
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0.2'
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
68
|
+
version: 0.2.3
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: statsample
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.4'
|
59
76
|
- - ">="
|
60
77
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
78
|
+
version: 1.4.3
|
62
79
|
type: :runtime
|
63
80
|
prerelease: false
|
64
81
|
version_requirements: !ruby/object:Gem::Requirement
|
65
82
|
requirements:
|
83
|
+
- - "~>"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.4'
|
66
86
|
- - ">="
|
67
87
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
88
|
+
version: 1.4.3
|
69
89
|
- !ruby/object:Gem::Dependency
|
70
90
|
name: minitest
|
71
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,46 +148,45 @@ dependencies:
|
|
128
148
|
requirements:
|
129
149
|
- - "~>"
|
130
150
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0.
|
151
|
+
version: '0.8'
|
132
152
|
type: :development
|
133
153
|
prerelease: false
|
134
154
|
version_requirements: !ruby/object:Gem::Requirement
|
135
155
|
requirements:
|
136
156
|
- - "~>"
|
137
157
|
- !ruby/object:Gem::Version
|
138
|
-
version: 0.
|
158
|
+
version: '0.8'
|
139
159
|
description:
|
140
160
|
email: rds45@cam.ac.uk
|
141
161
|
executables: []
|
142
162
|
extensions: []
|
143
163
|
extra_rdoc_files: []
|
144
164
|
files:
|
165
|
+
- LICENSE.txt
|
166
|
+
- README.md
|
145
167
|
- Rakefile
|
146
168
|
- lib/biopsy.rb
|
147
|
-
- lib/biopsy/
|
169
|
+
- lib/biopsy/base_extensions.rb
|
170
|
+
- lib/biopsy/experiment.rb
|
171
|
+
- lib/biopsy/objective_function.rb
|
148
172
|
- lib/biopsy/objective_handler.rb
|
149
173
|
- lib/biopsy/objectives/fastest_optimum.rb
|
150
|
-
- lib/biopsy/settings.rb
|
151
|
-
- lib/biopsy/optimisers/tabu_search.rb
|
152
|
-
- lib/biopsy/optimisers/genetic_algorithm.rb
|
153
|
-
- lib/biopsy/optimisers/spea2.rb
|
154
174
|
- lib/biopsy/optimisers/parameter_sweeper.rb
|
155
|
-
- lib/biopsy/
|
175
|
+
- lib/biopsy/optimisers/spea2.rb
|
176
|
+
- lib/biopsy/optimisers/tabu_search.rb
|
177
|
+
- lib/biopsy/settings.rb
|
156
178
|
- lib/biopsy/target.rb
|
157
|
-
- lib/biopsy/
|
158
|
-
- lib/biopsy/experiment.rb
|
159
|
-
- test/test_parametersweep.rb
|
160
|
-
- test/test_target.rb
|
161
|
-
- test/test_hash.rb
|
162
|
-
- test/test_string.rb
|
163
|
-
- test/helper.rb
|
179
|
+
- lib/biopsy/version.rb
|
164
180
|
- test/brokenconfig.yml
|
165
|
-
- test/
|
181
|
+
- test/helper.rb
|
182
|
+
- test/test_experiment.rb
|
166
183
|
- test/test_file.rb
|
184
|
+
- test/test_hash.rb
|
185
|
+
- test/test_objective_handler.rb
|
186
|
+
- test/test_parametersweep.rb
|
167
187
|
- test/test_settings.rb
|
168
|
-
- test/
|
169
|
-
-
|
170
|
-
- LICENSE.txt
|
188
|
+
- test/test_string.rb
|
189
|
+
- test/test_target.rb
|
171
190
|
homepage: https://github.com/Blahah/biopsy
|
172
191
|
licenses: []
|
173
192
|
metadata: {}
|
@@ -187,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
206
|
version: '0'
|
188
207
|
requirements: []
|
189
208
|
rubyforge_project:
|
190
|
-
rubygems_version: 2.
|
209
|
+
rubygems_version: 2.2.2
|
191
210
|
signing_key:
|
192
211
|
specification_version: 4
|
193
212
|
summary: framework for optimising any computational pipeline or program
|
@@ -1,244 +0,0 @@
|
|
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
|