experiment 0.2.0 → 0.3.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.
@@ -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