experiment 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,180 +1,184 @@
1
- # This class is responsible for UI goodness in letting you know
2
- # about the progress of your experiments
3
1
  require "drb/drb"
4
- class Notify
2
+ module Experiment
3
+ # This class is responsible for UI goodness in letting you know
4
+ # about the progress of your experiments
5
+ # @private
6
+ class Notify
5
7
 
6
- class << self
7
- include DRb::DRbUndumped
8
- # initialize display
9
- def init(total, out = STDERR, growl = true, mode = :normal)
10
- @curent_experiment = ""
11
- @current_cv = 0
12
- @cv_prog = {}
13
- @total = total
14
- @out = out
15
- @terminal_width = 80
16
- @bar_mark = "o"
17
- @current = 0
18
- @previous = 0
19
- @finished_p = false
20
- @start_time = Time.now
21
- @previous_time = @start_time
22
- @growl = growl
23
- @mode = mode
24
- show if @mode == :normal
25
- end
8
+ class << self
9
+ include DRb::DRbUndumped
10
+
11
+ # initialize display
12
+ def init(total, out = STDERR, growl = true, mode = :normal)
13
+ @curent_experiment = ""
14
+ @current_cv = 0
15
+ @cv_prog = {}
16
+ @total = total
17
+ @out = out
18
+ @terminal_width = 80
19
+ @bar_mark = "o"
20
+ @current = 0
21
+ @previous = 0
22
+ @finished_p = false
23
+ @start_time = Time.now
24
+ @previous_time = @start_time
25
+ @growl = growl
26
+ @mode = mode
27
+ show if @mode == :normal
28
+ end
26
29
 
27
- # Called when starting work on a particular experiment
28
- def started(experiment)
29
- @curent_experiment = experiment
30
- @current_cv = 1
31
- @cv_prog[experiment] = []
32
- show_if_needed
33
- end
30
+ # Called when starting work on a particular experiment
31
+ def started(experiment)
32
+ @curent_experiment = experiment
33
+ @current_cv = 1
34
+ @cv_prog[experiment] = []
35
+ show_if_needed
36
+ end
34
37
 
35
- # Called when experiment completed.
36
- # Shows a Growl notification on OSX.
37
- # The message can be expanded by overriding the result_line
38
- # method in the experiment class
39
- def completed(experiment, msg = "")
40
- if @growl
41
- begin
42
- `G_TITLE="Experiment Complete" #{File.dirname(__FILE__)}/../../bin/growl.sh -nosticky "Experimental condition #{experiment} complete. #{msg}"`
43
- rescue
44
- # probably not on OSX
38
+ # Called when experiment completed.
39
+ # Shows a Growl notification on OSX.
40
+ # The message can be expanded by overriding the result_line
41
+ # method in the experiment class
42
+ def completed(experiment, msg = "")
43
+ if @growl
44
+ begin
45
+ `G_TITLE="Experiment Complete" #{File.dirname(__FILE__)}/../../bin/growl.sh -nosticky "Experimental condition #{experiment} complete. #{msg}"`
46
+ rescue
47
+ # probably not on OSX
48
+ end
45
49
  end
50
+ m = "Condition #{experiment} complete. #{msg}"
51
+ puts m + " " * @terminal_width
52
+ @curent_experiment = nil
46
53
  end
47
- m = "Condition #{experiment} complete. #{msg}"
48
- puts m + " " * @terminal_width
49
- @curent_experiment = nil
50
- end
51
54
 
52
- # called after a crossvalidation has completed
53
- def cv_done(experiment, num)
54
- @cv_prog[experiment][num] ||= 0
55
- inc(1 - @cv_prog[experiment][num])
56
- #@cv_prog = 0
57
- end
55
+ # called after a crossvalidation has completed
56
+ def cv_done(experiment, num)
57
+ @cv_prog[experiment][num] ||= 0
58
+ inc(1 - @cv_prog[experiment][num])
59
+ #@cv_prog = 0
60
+ end
58
61
 
59
- # Wrap up
60
- def done
61
- @current = @total
62
- @finished_p = true
63
- #show
64
- end
62
+ # Wrap up
63
+ def done
64
+ @current = @total
65
+ @finished_p = true
66
+ #show
67
+ end
65
68
 
66
- # Use this in experiment after each (potentially time consuming) task
67
- # The argument should be a fraction (0 < num < 1) which tells
68
- # how big a portion the task was of the complete run (eg. your
69
- # calls should sum up to 1).
70
- def step(experiment, cv, num)
71
- if @mode == :normal
72
- if num > 1
73
- num = num / 100
69
+ # Use this in experiment after each (potentially time consuming) task
70
+ # The argument should be a fraction (0 < num < 1) which tells
71
+ # how big a portion the task was of the complete run (eg. your
72
+ # calls should sum up to 1).
73
+ def step(experiment, cv, num)
74
+ if @mode == :normal
75
+ if num > 1
76
+ num = num / 100
77
+ end
78
+ inc(num)
79
+ @cv_prog[experiment][cv] ||= 0
80
+ @cv_prog[experiment][cv] += num
81
+ else
82
+ @mode.notify.step(experiment, cv, num)
74
83
  end
75
- inc(num)
76
- @cv_prog[experiment][cv] ||= 0
77
- @cv_prog[experiment][cv] += num
78
- else
79
- @mode.notify.step(experiment, cv, num)
80
84
  end
81
- end
82
85
 
83
- end
84
-
85
- # a big part of this module is copied/inspired by Satoru Takabayashi's <satoru@namazu.org> ProgressBar class at http://0xcc.net/ruby-progressbar/index.html.en
86
- module ProgressBar #:nodoc
87
- def inc(step = 1)
88
- @current += step
89
- @current = @total if @current > @total
90
- show_if_needed
91
- @previous = @current
92
86
  end
93
-
94
- def show_if_needed
95
- if @total.zero?
96
- cur_percentage = 100
97
- prev_percentage = 0
98
- else
99
- cur_percentage = (@current * 100 / @total).to_i
100
- prev_percentage = (@previous * 100 / @total).to_i
87
+
88
+ # a big part of this module is copied/inspired by Satoru Takabayashi's <satoru@namazu.org> ProgressBar class at http://0xcc.net/ruby-progressbar/index.html.en
89
+ module ProgressBar #:nodoc
90
+ def inc(step = 1)
91
+ @current += step
92
+ @current = @total if @current > @total
93
+ show_if_needed
94
+ @previous = @current
101
95
  end
102
- @finished_p = cur_percentage == 100
103
- # Use "!=" instead of ">" to support negative changes
104
- if cur_percentage != prev_percentage ||
105
- Time.now - @previous_time >= 1 || @finished_p
106
- show
96
+
97
+ def show_if_needed
98
+ if @total.zero?
99
+ cur_percentage = 100
100
+ prev_percentage = 0
101
+ else
102
+ cur_percentage = (@current * 100 / @total).to_i
103
+ prev_percentage = (@previous * 100 / @total).to_i
104
+ end
105
+ @finished_p = cur_percentage == 100
106
+ # Use "!=" instead of ">" to support negative changes
107
+ if cur_percentage != prev_percentage ||
108
+ Time.now - @previous_time >= 1 || @finished_p
109
+ show
110
+ end
107
111
  end
108
- end
109
112
 
110
113
 
111
114
 
112
- def show
113
- percent = @current * 100 / @total
114
- bar_width = percent * @terminal_width / 100
115
- line = sprintf "%3d%% |%s%s| %s", percent, "=" * bar_width, "-" * (@terminal_width - bar_width), stat
115
+ def show
116
+ percent = @current * 100 / @total
117
+ bar_width = percent * @terminal_width / 100
118
+ line = sprintf "%3d%% |%s%s| %s", percent, "=" * bar_width, "-" * (@terminal_width - bar_width), stat
116
119
 
117
120
 
118
- width = get_width
119
- if line.length == width - 1
120
- @out.print(line + (@finished_p ? "\n" : "\r"))
121
- @out.flush
122
- elsif line.length >= width
123
- @terminal_width = [@terminal_width - (line.length - width + 1), 0].max
124
- if @terminal_width == 0 then @out.print(line + eol) else show end
125
- else # line.length < width - 1
126
- @terminal_width += width - line.length + 1
127
- show
121
+ width = get_width
122
+ if line.length == width - 1
123
+ @out.print(line + (@finished_p ? "\n" : "\r"))
124
+ @out.flush
125
+ elsif line.length >= width
126
+ @terminal_width = [@terminal_width - (line.length - width + 1), 0].max
127
+ if @terminal_width == 0 then @out.print(line + eol) else show end
128
+ else # line.length < width - 1
129
+ @terminal_width += width - line.length + 1
130
+ show
131
+ end
132
+ @previous_time = Time.now
128
133
  end
129
- @previous_time = Time.now
130
- end
131
134
 
132
- def stat
133
- if @finished_p then elapsed else eta end
134
- end
135
+ def stat
136
+ if @finished_p then elapsed else eta end
137
+ end
135
138
 
136
- def eta
137
- if @current == 0
138
- "ETA: --:--:--"
139
- else
140
- elapsed = Time.now - @start_time
141
- eta = elapsed * @total / @current - elapsed;
142
- sprintf("ETA: %s", format_time(eta))
139
+ def eta
140
+ if @current == 0
141
+ "ETA: --:--:--"
142
+ else
143
+ elapsed = Time.now - @start_time
144
+ eta = elapsed * @total / @current - elapsed;
145
+ sprintf("ETA: %s", format_time(eta))
146
+ end
143
147
  end
144
- end
145
148
 
146
- def elapsed
147
- elapsed = Time.now - @start_time
148
- sprintf("Time: %s", format_time(elapsed))
149
- end
149
+ def elapsed
150
+ elapsed = Time.now - @start_time
151
+ sprintf("Time: %s", format_time(elapsed))
152
+ end
150
153
 
151
- def format_time (t)
152
- t = t.to_i
153
- sec = t % 60
154
- min = (t / 60) % 60
155
- hour = t / 3600
156
- sprintf("%02d:%02d:%02d", hour, min, sec);
157
- end
154
+ def format_time (t)
155
+ t = t.to_i
156
+ sec = t % 60
157
+ min = (t / 60) % 60
158
+ hour = t / 3600
159
+ sprintf("%02d:%02d:%02d", hour, min, sec);
160
+ end
158
161
 
159
162
 
160
- def get_width
161
- # FIXME: I don't know how portable it is.
162
- default_width = 80
163
- begin
164
- tiocgwinsz = 0x5413
165
- data = [0, 0, 0, 0].pack("SSSS")
166
- if @out.ioctl(tiocgwinsz, data) >= 0 then
167
- rows, cols, xpixels, ypixels = data.unpack("SSSS")
168
- if cols >= 0 then cols else default_width end
169
- else
163
+ def get_width
164
+ # FIXME: I don't know how portable it is.
165
+ default_width = 80
166
+ begin
167
+ tiocgwinsz = 0x5413
168
+ data = [0, 0, 0, 0].pack("SSSS")
169
+ if @out.ioctl(tiocgwinsz, data) >= 0 then
170
+ rows, cols, xpixels, ypixels = data.unpack("SSSS")
171
+ if cols >= 0 then cols else default_width end
172
+ else
173
+ default_width
174
+ end
175
+ rescue Exception
170
176
  default_width
171
177
  end
172
- rescue Exception
173
- default_width
174
178
  end
175
- end
176
- end
179
+ end
177
180
 
178
- extend ProgressBar
181
+ extend ProgressBar
179
182
 
183
+ end
180
184
  end
@@ -0,0 +1,18 @@
1
+ module Experiment
2
+ class Params
3
+
4
+ # Return if set the value of the current param.
5
+ #
6
+ # If it is not defined fallback to {Experiment::Config#[]}.
7
+ def self.[](h)
8
+ @@params[h] || Config[h]
9
+ end
10
+
11
+
12
+ # @private
13
+ def self.set(a) # :nodoc:
14
+ @@params = a
15
+ end
16
+ end
17
+ Params.set({})
18
+ end
@@ -1,10 +1,20 @@
1
+ require File.dirname(__FILE__) + "/config"
1
2
  module Experiment
2
3
 
3
- # This is the class behind the command line magic
4
+ # This is the class behind the command line magic.
5
+ # It is possible to use it programatically, though.
6
+ # @see https://github.com/gampleman/Experiment/wiki/Command-Line-Interface
7
+ # @example For documentation on the CLI run
8
+ # experiment -h
4
9
  class Runner
5
10
 
6
11
  attr_reader :options
7
12
 
13
+ # If you are using this programmatically you need to set these params correctly:
14
+ # @param [Array<String>] arg Typically the name of the experiment the operation
15
+ # needs to operate on.
16
+ # @param [Struct, OpenStruct] opt an options object that should respond according
17
+ # to the CLI.
8
18
  def initialize(arg, opt)
9
19
  @arguments, @options = arg, opt
10
20
  end
@@ -51,13 +61,13 @@ module Experiment
51
61
  end
52
62
  FileUtils::cp File.join(basedir, "generator/readme_template.txt"), File.join(dir, "README")
53
63
  FileUtils::cp File.join(basedir, "generator/Rakefile"), File.join(dir, "Rakefile")
54
- FileUtils::cp File.join(basedir, "generator/experiment_template.rb"), File.join(dir, "experiments", "experiment.rb")
64
+ FileUtils::cp File.join(basedir, "generator/experiment_template.rb.txt"), File.join(dir, "experiments", "experiment.rb")
55
65
  end
56
66
 
57
67
  # Lists available experiments
58
68
  def list
59
69
  puts "Available experiments:"
60
- puts " " + Dir["./experiments/*"].map{|a| File.basename(a) }.join(", ")
70
+ puts " " + Dir["./experiments/*"].map{|a| File.dirname(a) }.join(", ")
61
71
  end
62
72
 
63
73
  # Generates 2 files in the report directory
@@ -109,14 +119,14 @@ module Experiment
109
119
  end
110
120
 
111
121
 
112
- # runs experiments passed aa arguments
122
+ # runs experiments passed as arguments
113
123
  # use the -o option to override configuration
114
124
  def run
115
125
  require File.dirname(__FILE__) + "/base"
116
126
 
117
127
  require "./experiments/experiment"
118
128
  Experiment::Config::init @options.env
119
-
129
+ @options.cv = Experiment::Config.get :cross_validations, 5 if @options.cv.nil?
120
130
  if @options.distributed
121
131
  require "drb/drb"
122
132
  require File.dirname(__FILE__) + "/work_server"
@@ -130,15 +140,45 @@ module Experiment
130
140
  @arguments.each do |exp|
131
141
  require "./experiments/#{exp}/#{exp}"
132
142
  cla = eval(as_class_name(exp))
133
- experiment = cla.new :normal, exp, @options.opts, @options.env
143
+ experiment = cla.new :normal, exp, @options
134
144
  experiment.normal_run! @options.cv
135
145
  end
136
146
  Notify::done
137
147
  end
138
148
  end
139
149
 
150
+ # Creates an IRB console useful for debugging experiments
151
+ # Loads up the environment for the condition passed
152
+ def console
153
+ cla = as_class_name(@arguments.first) if @arguments.length == 1
154
+ File.open("./tmp/irb-setup.rb", 'w') do |f|
155
+ f.puts "Experiment::Config::init #{@options.env.inspect}"
156
+ f.puts "def reload!"
157
+ f.puts " "
158
+ f.puts "end"
159
+ if @arguments.length == 1
160
+ f.puts "def experiment"
161
+ f.puts " @experiment ||= #{cla}.new :normal, #{@arguments.first.inspect}, OpenStruct.new(#{@options.marshal_dump})"
162
+ f.puts "end"
163
+ f.puts "experiment #load up the configs"
164
+ else
165
+ f.puts 'Dir["./app/*.rb"].each{|e| require e }'
166
+ f.puts "Experiment::Config::load '', #{options.opts.inspect}"
167
+ end
168
+
169
+ end
170
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
171
+ libs = " -r irb/completion"
172
+ libs << " -r #{File.dirname(__FILE__) + "/base"}"
173
+ libs << " -r./experiments/experiment"
174
+ libs << " -r ./experiments/#{@arguments.first}/#{@arguments.first}" if @arguments.length == 1
175
+ libs << " -r ./tmp/irb-setup.rb"
176
+ puts "Loading #{@options.env} environment..."
177
+ exec "#{irb} #{libs} --simple-prompt"
178
+ end
179
+
140
180
 
141
- # This is a Worker implementation. It requires an --address option
181
+ # Starts a Worker implementation. It requires an --address option
142
182
  # of it's master server and will recieve tasks (experiments and
143
183
  # cross-validations) and compute them.
144
184
  def worker
@@ -155,13 +195,15 @@ module Experiment
155
195
  require "./experiments/experiment"
156
196
  require "./experiments/#{exp}/#{exp}"
157
197
  cla = eval(as_class_name(exp))
158
- experiment = cla.new :slave, exp, @options.opts, @options.env
198
+ experiment = cla.new :slave, exp, @options
159
199
  experiment.master = @master.instance item
160
200
  experiment.slave_run!
161
201
  end
162
202
  end
163
203
  end
164
204
 
205
+
206
+
165
207
  private
166
208
 
167
209
  require 'socket'
@@ -0,0 +1,58 @@
1
+ module Experiment
2
+ module Stats
3
+ module Descriptive
4
+
5
+ def sum(ar = self, &block)
6
+ ar.reduce(0.0) {|asum, a| (block_given? ? yield(a) : a) + asum}
7
+ end
8
+
9
+ def variance(ar = self)
10
+ v = sum(ar) {|x| (mean(ar) - x)**2.0 }
11
+ v/(ar.count - 1.0)
12
+ end
13
+
14
+ def standard_deviation(ar = self)
15
+ Math.sqrt(variance(ar))
16
+ end
17
+
18
+ def z_scores(ar = self)
19
+ ar.map {|x| z_score(ar, x)}
20
+ end
21
+
22
+ def z_score(ar = self, x)
23
+ (x - mean(ar)) / standard_deviation(ar)
24
+ end
25
+
26
+ def range(ar = self)
27
+ ar.max - ar.min
28
+ end
29
+
30
+ def mean(ar = self)
31
+ sum(ar) / ar.count
32
+ end
33
+
34
+ def median(ar = self)
35
+ a = ar.sort
36
+ if ar.count.odd?
37
+ a[(ar.count-1)/2]
38
+ else
39
+ (a[ar.count/2 - 1] + a[ar.count/2]) / 2.0
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ class << self
46
+ # Monkey pathces the Array class to accept the methods in this class
47
+ # as it's own - so instead of `Stats::variance([1, 2, 3])`
48
+ # you can call [1, 2, 3].variance
49
+ def monkey_patch!
50
+ Array.send :include, Descriptive
51
+ end
52
+
53
+ include Descriptive
54
+ end
55
+
56
+ end
57
+ end
58
+
@@ -1,4 +1,8 @@
1
1
  module Experiment
2
+ # This class is responsible for disrtibuting work
3
+ # and instantiating experimental conditions according
4
+ # to available resources
5
+ # @private
2
6
  class WorkServer
3
7
  def initialize(experiments, options, ip = "localhost")
4
8
  uri="druby://#{ip}:8787"
@@ -15,20 +19,29 @@ module Experiment
15
19
  DRb.thread.join
16
20
  end
17
21
 
18
- def ready?
22
+ # @deprecated
23
+ def ready? # TODO: get rid of this
19
24
  true
20
25
  end
21
26
 
27
+ # Workers call this method and recieve a new work object incrementally
22
28
  def new_item
23
29
  @experiments.each_with_index do |e, i|
24
30
  if @experiment_instances[i].nil?
25
31
  exp = @experiments[i]
26
32
  require "./experiments/#{exp}/#{exp}"
27
33
  cla = eval(as_class_name(exp))
28
- experiment = cla.new :master, exp, @options.opts, @options.env
29
- experiment.master_run! @options.cv
30
- @experiment_instances[i] = experiment
31
- return i
34
+ experiment = cla.new :master, exp, @options
35
+ if experiment.respond_to? :master_sub_experiments
36
+ subs = experiment.master_sub_experiments @options.cv
37
+ @experiments += subs.map { exp }
38
+ @experiment_instances += subs
39
+ return i + 1
40
+ else
41
+ experiment.master_run! @options.cv
42
+ @experiment_instances[i] = experiment
43
+ return i
44
+ end
32
45
  elsif !@experiment_instances[i].distribution_done?
33
46
  return i
34
47
  end
@@ -38,10 +51,12 @@ module Experiment
38
51
  false
39
52
  end
40
53
 
54
+ # accessor for the remote notification service
41
55
  def notify
42
56
  Notify
43
57
  end
44
58
 
59
+
45
60
  def experiment(num)
46
61
  @experiments[num]
47
62
  end
data/lib/experiment.rb CHANGED
@@ -2,5 +2,5 @@ $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  module Experiment
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
data/test/test_stats.rb CHANGED
@@ -1,8 +1,9 @@
1
- require File.dirname(__FILE__) + "/../lib/experiment/stats"
2
- #require "wrong"
1
+ require File.dirname(__FILE__) + "/../lib/experiment/stats/descriptive"
2
+ #require "wrong"require "../lib/experiment/stats/descriptive"
3
+
3
4
  class TestStats < Test::Unit::TestCase
4
5
  #include Wrong
5
-
6
+ include Experiment
6
7
  def setup
7
8
  @data = [1, 2, 3, 4]
8
9
  end
@@ -31,4 +32,9 @@ class TestStats < Test::Unit::TestCase
31
32
  def test_median
32
33
  assert_equal 2.5, Stats::median(@data)
33
34
  end
35
+
36
+ def test_monkey_patch
37
+ Stats::monkey_patch!
38
+ assert_equal 1.6666666666666667, [1, 2, 3, 4].variance
39
+ end
34
40
  end