kalibera 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7716023defd20bb3f94d5c7f2f26959dc92ec502
4
+ data.tar.gz: 86a9b2b33cf855415087f6a0b9c807442447a205
5
+ SHA512:
6
+ metadata.gz: cc782c6b650f3aaa9ea72752fa7cc1cf0ad3d9181f93733568716cd380400564311f231f1f63e6ac3e7093f36358b048d55c1929f399b3bb72f7f38323712dd2
7
+ data.tar.gz: 9c122a46376f5af2d83d4ec97de5a4388c1713ed89608bc5359fbe19b5f6caf670ea84872e6e741d9ac70fc2ed40a5f48973e427369aef9ff56c18b4d2edbffd
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gem 'rbzip2', '~> 0.2.0'
3
+ gem 'memoist', '~> 0.11.0'
@@ -0,0 +1,12 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ memoist (0.11.0)
5
+ rbzip2 (0.2.0)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ memoist (~> 0.11.0)
12
+ rbzip2 (~> 0.2.0)
@@ -0,0 +1,22 @@
1
+ Copyright (C) King's College London, created by Edd Barrett and Carl
2
+ Friedrich Bolz
3
+
4
+ Ruby transliteration (C) Chris Seaton 2014
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,24 @@
1
+ require 'json'
2
+
3
+ metadata = JSON.parse(IO.read(File.expand_path('../../shared_metadata.json', __FILE__)))
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "kalibera"
7
+ spec.version = metadata["metadata"]["version"]
8
+ spec.authors = ["Edd Barrett", "Carl Friedrich Bolz", "Chris Seaton"]
9
+ spec.email = ["chris@chrisseaton.com"]
10
+ spec.summary = metadata["metadata"]["short_descr"]
11
+ spec.description = metadata["metadata"]["long_descr"]
12
+ spec.homepage = metadata["metadata"]["url"]
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.test_files = spec.files.grep(%r{^test/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.7"
20
+ spec.add_development_dependency "rake", "~> 10.0"
21
+
22
+ spec.add_dependency "rbzip2", "~> 0.2.0"
23
+ spec.add_dependency "memoist", "~> 0.11.0"
24
+ end
@@ -0,0 +1 @@
1
+ require "kalibera/data"
@@ -0,0 +1,369 @@
1
+ require "stringio"
2
+ require "base64"
3
+ require "rbzip2"
4
+ require "bigdecimal"
5
+ require "memoist"
6
+
7
+ module Kalibera
8
+
9
+ CONSTANTS = RBzip2::Decompressor.new(StringIO.new(Base64.decode64("""\
10
+ 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
52
+ NLhVQA=="""))).read.split().map { |x| Float(x) }
53
+
54
+ # Look up the 95% quantile from constant table.
55
+ def self.student_t_quantile95(ndeg)
56
+ index = ndeg - 1
57
+ if index >= CONSTANTS.size
58
+ index = -1 # the quantile converges, we just take the last value
59
+ end
60
+ CONSTANTS[index]
61
+ end
62
+
63
+ ConfRange = Struct.new(:lower, :median, :upper) do
64
+ def error
65
+ Kalibera.mean([upper - median, median - lower])
66
+ end
67
+ end
68
+
69
+ # Returns a tuples (lower, median, upper), where:
70
+ # lower: lower bound of 95% confidence interval
71
+ # median: the median value of the data
72
+ # upper: upper bound of 95% confidence interval
73
+ #
74
+ # Arguments:
75
+ # means -- the list of means (need not be sorted).
76
+ def self.confidence_slice(means, confidence="0.95")
77
+ means = means.sort
78
+ # There may be >1 median indicies, i.e. data is even-sized.
79
+ lower, middle_indicies, upper = confidence_slice_indicies(means.size, confidence)
80
+ median = mean(middle_indicies.map { |i| means[i] })
81
+ ConfRange.new(means[lower], median, means[upper - 1]) # upper is *exclusive*
82
+ end
83
+
84
+ # Returns a triple (lower, mean_indicies, upper) so that l[lower:upper]
85
+ # gives confidence_level of all samples. Mean_indicies is a tuple of one or
86
+ # two indicies that correspond to the mean position
87
+ #
88
+ # Keyword arguments:
89
+ # confidence_level -- desired level of confidence as a Decimal instance.
90
+ def self.confidence_slice_indicies(length, confidence_level=BigDecimal.new('0.95'))
91
+ raise unless !confidence_level.instance_of?(Float)
92
+ confidence_level = BigDecimal.new(confidence_level)
93
+ raise unless confidence_level.instance_of?(BigDecimal)
94
+ exclude = (1 - confidence_level) / 2
95
+
96
+ if length % 2 == 0
97
+ mean_indicies = [length / 2 - 1, length / 2] # TRANSLITERATION: was //
98
+ else
99
+ mean_indicies = [length / 2] # TRANSLITERATION: was //
100
+ end
101
+
102
+ lower_index = Integer(
103
+ (exclude * length).round(0, BigDecimal::ROUND_DOWN) # TRANSLITERATION: was quantize 1.
104
+ )
105
+
106
+ upper_index = Integer(
107
+ ((1 - exclude) * length).round(0, BigDecimal::ROUND_UP) # TRANSLITERATION: was quantize 1.
108
+ )
109
+
110
+ [lower_index, mean_indicies, upper_index]
111
+ end
112
+
113
+ def self.mean(l)
114
+ l.inject(0, :+) / Float(l.size)
115
+ end
116
+
117
+ def self.geomean(l)
118
+ l.inject(1, :*) ** (1.0 / Float(l.size))
119
+ end
120
+
121
+ class Data
122
+
123
+ extend Memoist
124
+
125
+ # Instances of this class store measurements (corresponding to
126
+ # the Y_... in the papers).
127
+ #
128
+ # Arguments:
129
+ # data -- Dict mapping tuples of all but the last index to lists of values.
130
+ # reps -- List of reps for each level, high to low.
131
+ def initialize(data, reps)
132
+ @data = data
133
+ @reps = reps
134
+
135
+ # check that all data is there
136
+
137
+ array = reps.map { |i| (0...i).to_a }
138
+ array[0].product(*array.drop(1)).each do |index|
139
+ self[*index] # does not crash
140
+ end
141
+ end
142
+
143
+ def [](*indicies)
144
+ raise unless indicies.size == @reps.size
145
+ x = @data[indicies[0...indicies.size-1]]
146
+ raise unless !x.nil?
147
+ x[indicies[-1]]
148
+ end
149
+
150
+ # Computes a list of all possible data indcies gievn that
151
+ # start <= index <= stop are fixed.
152
+ def index_iterator(start=0, stop=nil)
153
+ if stop.nil?
154
+ stop = n
155
+ end
156
+
157
+ maximum_indicies = @reps[start...stop]
158
+ remaining_indicies = maximum_indicies.map { |maximum| (0...maximum).to_a }
159
+ return [[]] if remaining_indicies.empty?
160
+ remaining_indicies[0].product(*remaining_indicies.drop(1))
161
+ end
162
+
163
+ # The number of levels in the experiment.
164
+ def n
165
+ @reps.size
166
+ end
167
+
168
+ # The number of repetitions for level i.
169
+ #
170
+ # Arguments:
171
+ # i -- mathematical index.
172
+ def r(i)
173
+ raise unless 1 <= i
174
+ raise unless i <= n
175
+ index = n - i
176
+ @reps[index]
177
+ end
178
+
179
+ # Compute the mean across a number of values.
180
+ #
181
+ # Keyword arguments:
182
+ # indicies -- tuple of fixed indicies over which to compute the mean,
183
+ # given from left to right. The remaining indicies are variable.
184
+ def mean(indicies=[])
185
+ remaining_indicies_cross_product =
186
+ index_iterator(start=indicies.size)
187
+ alldata = remaining_indicies_cross_product.map { |remaining| self[*(indicies + remaining)] }
188
+ Kalibera.mean(alldata)
189
+ end
190
+
191
+ memoize :mean
192
+
193
+ # Biased estimator S_i^2.
194
+ #
195
+ # Arguments:
196
+ # i -- the mathematical index of the level from which to compute S_i^2
197
+ def Si2(i)
198
+ raise unless 1 <= i
199
+ raise unless i <= n
200
+ # @reps is indexed from the left to right
201
+ index = n - i
202
+ factor = 1.0
203
+
204
+ # We compute this iteratively leveraging the fact that
205
+ # 1 / (a * b) = (1 / a) / b
206
+ for rep in @reps[0, index]
207
+ factor /= rep
208
+ end
209
+ # Then at this point we have:
210
+ # factor * (1 / (r_i - 1)) = factor / (r_i - 1)
211
+ factor /= @reps[index] - 1
212
+
213
+ # Second line of the above definition, the lines are multiplied.
214
+ indicies = index_iterator(0, index+1)
215
+ sum = 0.0
216
+ for index in indicies
217
+ a = mean(index)
218
+ b = mean(index[0,index.size-1])
219
+ sum += (a - b) ** 2
220
+ end
221
+ factor * sum
222
+ end
223
+
224
+ memoize :Si2
225
+
226
+ # Compute the unbiased T_i^2 variance estimator.
227
+ #
228
+ # Arguments:
229
+ # i -- the mathematical index from which to compute T_i^2.
230
+ def Ti2(i)
231
+ # This is the broken implementation of T_i^2 shown in the pubslished
232
+ # version of "Rigorous benchmarking in reasonable time". Tomas has
233
+ # since fixed this in local versions of the paper.
234
+ #@memoize
235
+ #def broken_Ti2(self, i)
236
+ # """ Compute the unbiased T_i^2 variance estimator.
237
+ #
238
+ # Arguments:
239
+ # i -- the mathematical index from which to compute T_i^2.
240
+ # """
241
+ #
242
+ # raise unless 1 <= i <= n
243
+ # if i == 1:
244
+ # return self.Si2(1)
245
+ # return self.Si2(i) - self.Ti2(i - 1) / self.r(i - 1)
246
+
247
+ # This is the correct definition of T_i^2
248
+
249
+ raise unless 1 <= i
250
+ raise unless i <= n
251
+ if i == 1
252
+ return Si2(1)
253
+ end
254
+ Si2(i) - Si2(i - 1) / r(i - 1)
255
+ end
256
+
257
+ memoize :Ti2
258
+
259
+ # Computes the optimal number of repetitions for a given level.
260
+ #
261
+ # Note that the resulting number of reps is not rounded.
262
+ #
263
+ # Arguments:
264
+ # i -- the mathematical level of which to compute optimal reps.
265
+ # costs -- A list of costs for each level, *high* to *low*.
266
+ def optimalreps(i, costs)
267
+ # NOTE: Does not round
268
+ costs = costs.map { |x| Float(x) }
269
+ raise unless 1 <= i
270
+ raise unless i < n
271
+ index = n - i
272
+ return (costs[index - 1] / costs[index] *
273
+ Ti2(i) / Ti2(i + 1)) ** 0.5
274
+ end
275
+
276
+ memoize :optimalreps
277
+
278
+ # Compute the 95% confidence interval.
279
+ def confidence95
280
+ degfreedom = @reps[0] - 1
281
+ student_t_quantile95(degfreedom) *
282
+ (Si2(n) / @reps[0]) ** 0.5
283
+ end
284
+
285
+ # Compute a list of simulated means from bootstrap resampling.
286
+ #
287
+ # Note that, resampling occurs with replacement.
288
+ #
289
+ # Keyword arguments:
290
+ # iterations -- Number of resamples (and thus means) generated.
291
+ def bootstrap_means(iterations=1000)
292
+ means = []
293
+ for i in 0...iterations
294
+ values = bootstrap_sample()
295
+ means.push(Kalibera.mean(values))
296
+ end
297
+ means.sort()
298
+ means
299
+ end
300
+
301
+ # Compute a confidence interval via bootstrap method.
302
+ #
303
+ # Keyword arguments:
304
+ # iterations -- Number of resamplings to base result upon. Default is 10000.
305
+ # confidence -- The required confidence. Default is "0.95" (95%).
306
+ def bootstrap_confidence_interval(iterations=10000, confidence="0.95")
307
+ means = bootstrap_means(iterations)
308
+ Kalibera.confidence_slice(means, confidence)
309
+ end
310
+
311
+ def random_measurement_sample(index=[])
312
+ results = []
313
+ if index.size == n
314
+ results.push self[*index]
315
+ else
316
+ indicies = (0...@reps[index.size]).map { |i| rand(@reps[index.size]) }
317
+ for single_index in indicies
318
+ newindex = index + [single_index]
319
+ for value in random_measurement_sample(newindex)
320
+ results.push value
321
+ end
322
+ end
323
+ end
324
+ results
325
+ end
326
+
327
+ def bootstrap_sample
328
+ random_measurement_sample
329
+ end
330
+
331
+ def bootstrap_quotient(other, iterations=10000, confidence='0.95')
332
+ ratios = []
333
+ for _ in 0...iterations
334
+ ra = bootstrap_sample()
335
+ rb = other.bootstrap_sample()
336
+ mean_ra = Kalibera.mean(ra)
337
+ mean_rb = Kalibera.mean(rb)
338
+
339
+ if mean_rb == 0 # protect against divide by zero
340
+ ratios.push(Float::INFINITY)
341
+ else
342
+ ratios.push(mean_ra / mean_rb)
343
+ end
344
+ end
345
+ ratios.sort!
346
+ Kalibera.confidence_slice(ratios, confidence).values
347
+ end
348
+
349
+ end
350
+
351
+ def self.bootstrap_geomean(l_data_a, l_data_b, iterations=10000, confidence='0.95')
352
+ raise "lists need to match" unless l_data_a.size == l_data_b.size
353
+ geomeans = []
354
+ iterations.times do
355
+ ratios = []
356
+ l_data_a.zip(l_data_b).each do |a, b|
357
+ ra = a.bootstrap_sample
358
+ rb = b.bootstrap_sample
359
+ mean_ra = mean(ra)
360
+ mean_rb = mean(rb)
361
+ ratios << mean_ra / mean_rb
362
+ end
363
+ geomeans << geomean(ratios)
364
+ end
365
+ geomeans.sort!
366
+ confidence_slice(geomeans, confidence)
367
+ end
368
+
369
+ end
@@ -0,0 +1,410 @@
1
+ require "test/unit"
2
+
3
+ require "kalibera"
4
+
5
+ # We need to match Python's random numbers when testing
6
+
7
+ class TestData < Kalibera::Data
8
+
9
+ RAND = [0, 2, 2, 0, 1, 1, 1, 2, 0, 0, 2, 1, 2, 0, 1, 2, 0, 2, 2, 0, 0, 1,
10
+ 2, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 2, 1, 1, 0, 2, 2, 0, 0]
11
+
12
+ def initialize(data, reps)
13
+ super
14
+ @rand_counter = 0
15
+ end
16
+
17
+ def reset_local_rand
18
+ @rand_counter = 0
19
+ end
20
+
21
+ def rand(r)
22
+ raise "mock rand designed for range=3" unless r == 3
23
+ raise "mock rand out of data" unless @rand_counter < RAND.size
24
+
25
+ n = RAND[@rand_counter]
26
+ @rand_counter += 1
27
+ n
28
+ end
29
+
30
+ end
31
+
32
+ class TestKaliberaData < Test::Unit::TestCase
33
+
34
+ def test_indicies
35
+ d = TestData.new({
36
+ [0, 0] => [1, 2, 3, 4, 5],
37
+ [0, 1] => [3, 4, 5, 6, 7]
38
+ }, [1, 2, 5])
39
+
40
+ assert_equal 1, d[0, 0, 0]
41
+ assert_equal 5, d[0, 0, 4]
42
+ assert_equal 5, d[0, 1, 2]
43
+ end
44
+
45
+ def test_rep_levels
46
+ d = TestData.new({
47
+ [0, 0] => [1, 2, 3, 4, 5],
48
+ [0, 1] => [3, 4, 5, 6, 7]
49
+ }, [1, 2, 5])
50
+
51
+ assert_equal 5, d.r(1) # lowest level, i.e. arity of the lists in the map
52
+ assert_equal 2, d.r(2)
53
+ assert_equal 1, d.r(3)
54
+
55
+ # indexs are one based, so 0 or less is invalid
56
+ assert_raise RuntimeError do
57
+ d.r(0)
58
+ end
59
+
60
+ assert_raise RuntimeError do
61
+ d.r(-1337)
62
+ end
63
+
64
+ # Since we have 3 levels here, levels 4 and above are bogus
65
+ assert_raise RuntimeError do
66
+ d.r(4)
67
+ end
68
+
69
+ assert_raise RuntimeError do
70
+ d.r(666)
71
+ end
72
+ end
73
+
74
+ def test_index_iter
75
+ d = TestData.new({
76
+ [0, 0] => [1, 2, 3, 4, 5],
77
+ [0, 1] => [3, 4, 5, 6, 7]
78
+ }, [1, 2, 5])
79
+
80
+ assert_equal [
81
+ [0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4],
82
+ [0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4],
83
+ ], d.index_iterator()
84
+ assert_equal [
85
+ [0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
86
+ [1, 0], [1, 1], [1, 2], [1, 3], [1, 4],
87
+ ], d.index_iterator(start=1)
88
+ assert_equal [[0]], d.index_iterator(start=0, stop=1)
89
+ assert_equal [[0], [1]], d.index_iterator(start=1, stop=2)
90
+ end
91
+
92
+ def test_index_means
93
+ d = TestData.new({
94
+ [0, 0] => [0, 2]
95
+ }, [1, 1, 2])
96
+
97
+ assert_equal 1, d.mean([])
98
+ assert_equal 1, d.mean([0, 0])
99
+ assert_equal d[0, 0, 0], d.mean([0, 0, 0])
100
+ assert_equal d[0, 0, 1], d.mean([0, 0, 1])
101
+ end
102
+
103
+ def test_index_means2
104
+ # Suppose we have three levels, so n = 3.
105
+ # For the sake of example, level 1 is repetitions, level 2 is executions,
106
+ # and level 3 is compilations. Now suppose we repeat level 3 twice,
107
+ # level 2 twice and level 3 five times.
108
+ #
109
+ # This would be a valid data set:
110
+ # Note that the indicies are off-by-one due to python indicies starting
111
+ # from 0.
112
+ d = TestData.new({ [0, 0] => [ 3, 4, 4, 1, 2 ], # times for compile 1, execution 1
113
+ [0, 1] => [ 3, 3, 3, 3, 3 ], # compile 1, execution 2
114
+ [1, 0] => [ 1, 2, 3, 4, 5 ], # compile 2, execution 1
115
+ [1, 1] => [ 1, 1, 4, 4, 1 ], # compile 2, execution 2
116
+ }, [2, 2, 5]) # counts for each level (highest to lowest)
117
+
118
+ # By calling mean with an empty tuple we compute the mean at all levels
119
+ # i.e. the mean of all times:
120
+ x = [3, 4, 4, 1, 2, 3, 3, 3, 3, 3, 1, 2, 3, 4, 5, 1, 1, 4, 4, 1]
121
+ expect = x.inject(0, :+)/Float(x.size)
122
+ assert_equal d.mean([]), expect
123
+
124
+ # By calling with a singleton tuple we compute the mean for a given
125
+ #compilation. E.g. compilation 2
126
+ x = [1, 2, 3, 4, 5, 1, 1, 4, 4, 1]
127
+ expect = x.inject(0, :+) / Float(x.size)
128
+ assert_equal d.mean([1]), expect
129
+
130
+ # By calling with a pair we compute the mean for a given compile
131
+ # and execution combo.
132
+ # E.g. compile 1, execution 2, which is obviously a mean of 3.
133
+ assert_equal 3, d.mean([0, 1])
134
+ end
135
+
136
+ def test_si2
137
+ d = TestData.new({
138
+ [0, 0] => [0, 0]
139
+ }, [1, 1, 2])
140
+
141
+ assert_equal 0, d.Si2(1)
142
+ end
143
+
144
+ def test_si2_bigger_example
145
+ # Let's compute S_1^2 for the following data
146
+ d = TestData.new({
147
+ [0, 0] => [3,4,3],
148
+ [0, 1] => [1.2, 3.1, 3],
149
+ [1, 0] => [0.2, 1, 1.5],
150
+ [1, 1] => [1, 2, 3]
151
+ }, [2, 2, 3])
152
+
153
+ # So we have n = 3, r = (2, 2, 3)
154
+ # By my reckoning we should get something close to 0.72667 (working below)
155
+ # XXX Explanation from whiteboard need to go here XXX
156
+
157
+ assert_less_equal (d.Si2(1)-0.72667).abs, 0.0001
158
+ end
159
+
160
+ def test_ti2
161
+ # To verify this, consider the following data:
162
+ d = TestData.new({
163
+ [0, 0] => [3,4,3],
164
+ [0, 1] => [1.2, 3.1, 3],
165
+ [1, 0] => [0.2, 1, 1.5],
166
+ [1, 1] => [1, 2, 3]
167
+ }, [2, 2, 3])
168
+
169
+ # Let's manually look at S_i^2 where 1 <= i <= n:
170
+ #si_vec = [ d.Si2(i) for i in range(1, 4) ]
171
+ #print(si_vec)
172
+
173
+ ti_vec = (1...4).map { |i| d.Ti2(i) }
174
+ expect = [ 0.7266667, 0.262777778, 0.7747 ]
175
+
176
+ (0...expect.size).each do |i|
177
+ assert (ti_vec[i] - expect[i]).abs <= 0.0001, "#{} <= 0.0001"
178
+ end
179
+ end
180
+
181
+ def test_optimal_reps
182
+ d = TestData.new({
183
+ [0, 0] => [3,4,3],
184
+ [0, 1] => [1.2, 3.1, 3],
185
+ [1, 0] => [0.2, 1, 1.5],
186
+ [1, 1] => [1, 2, 3]
187
+ }, [2, 2, 3])
188
+
189
+ #ti_vec = [ d.Ti2(i) for i in range (1, 4) ]
190
+ #print(ti_vec)
191
+
192
+ # And suppose the costs (high level to low) are 100, 20 and 3 (seconds)
193
+ # By my reckoning, the optimal repetition counts should be r_1 = 5, r_2 = 2
194
+ # XXX show working XXX
195
+ got = [1,2].map { |i|d.optimalreps(i, [100, 20, 3]) }
196
+ expect = [4.2937, 1.3023]
197
+
198
+ (0...got.size).each do |i|
199
+ assert_less_equal (got[i] - expect[i]).abs, 0.001
200
+ end
201
+ end
202
+
203
+ def test_worked_example_3_level
204
+ # three level experiment
205
+ # This is the worked example from the paper.
206
+ data = TestData.new({
207
+ [0, 0] => [9.0, 5.0], [0, 1] => [8.0, 3.0],
208
+ [1, 0] => [10.0, 6.0], [1, 1] => [7.0, 11.0],
209
+ [2, 0] => [1.0, 12.0], [2, 1] => [2.0, 4.0],
210
+ }, [3, 2, 2])
211
+
212
+ correct = {
213
+ [0, 0] => 7.0,
214
+ [0, 1] => 5.5,
215
+ [1, 0] => 8.0,
216
+ [1, 1] => 9.0,
217
+ [2, 0] => 6.5,
218
+ [2, 1] => 3.0,
219
+ }
220
+
221
+ data.index_iterator(0, 2).each do |index|
222
+ assert data.mean(index) == correct[index]
223
+ end
224
+
225
+ assert_equal 6.5, data.mean()
226
+
227
+ assert_equal 16.5, data.Si2(1).round(1)
228
+ assert_equal 2.6, data.Si2(2).round(1)
229
+ assert_equal 3.6, data.Si2(3).round(1)
230
+ assert_equal 16.5, data.Ti2(1).round(1)
231
+ assert_equal -5.7, data.Ti2(2).round(1)
232
+ assert_equal 2.3, data.Ti2(3).round(1)
233
+ end
234
+
235
+ def test_worked_example_2_level
236
+ data = TestData.new({
237
+ [0] => [9.0, 5.0, 8.0, 3.0],
238
+ [1] => [10.0, 6.0, 7.0, 11.0],
239
+ [2] => [1.0, 12.0, 2.0, 4.0],
240
+ }, [3, 4])
241
+
242
+ correct = {[0] => 6.3,
243
+ [1] => 8.5,
244
+ [2] => 4.8,
245
+ }
246
+ data.index_iterator(0, 1).each do |index|
247
+ assert data.mean(index).round(1) == correct[index]
248
+ end
249
+
250
+ assert_equal 6.5, data.mean()
251
+
252
+ assert_equal 12.7, data.Si2(1).round(1)
253
+ assert_equal 3.6, data.Si2(2).round(1)
254
+
255
+ assert_equal 12.7, data.Ti2(1).round(1)
256
+ assert_equal 0.4, data.Ti2(2).round(1)
257
+ end
258
+
259
+ def test_bootstrap
260
+ # XXX needs info on how expected val was computed
261
+ data = TestData.new({
262
+ [0] => [ 2.5, 3.1, 2.7 ],
263
+ [1] => [ 5.1, 1.1, 2.3 ],
264
+ [2] => [ 4.7, 5.5, 7.1 ],
265
+ }, [3, 3])
266
+ data.reset_local_rand
267
+
268
+ expect = 4.8111111111
269
+ got = data.bootstrap_means(1) # one iteration
270
+
271
+ assert_less_equal (got[0] - expect).abs, 0.0001
272
+ end
273
+
274
+ def test_confidence_slice_indicies
275
+ assert_equal [1, [4, 5], 9], Kalibera.confidence_slice_indicies(10, '0.8')
276
+ assert_equal [1, [5], 10], Kalibera.confidence_slice_indicies(11, '0.8')
277
+ assert_equal [25, [499, 500], 975], Kalibera.confidence_slice_indicies(1000)
278
+ end
279
+
280
+ def test_confidence_slice
281
+ # Suppose we get back the means:
282
+ means = (0...1000).map { |x| x + 15 } # already sorted
283
+
284
+ # For a data set of size 1000, we expect alpha/2 to be 25
285
+ # (for a 95% confidence interval)
286
+ alpha_over_two = means.size * 0.025
287
+ assert(alpha_over_two) == 25
288
+
289
+ # Therefore we lose 25 items off each end of the means list.
290
+ # The first 25 indicies are 0, ...0, 24, so lower bound should be index 25.
291
+ # The last 25 indicies are -1, ...0, -25, so upper bound is index -26
292
+ # Put differently, the last 25 indicies are 999, ...0, 975
293
+
294
+ lower_index = Integer(alpha_over_two.floor)
295
+ upper_index = Integer(-alpha_over_two.ceil - 1)
296
+ lobo, hibo = [means[lower_index], means[upper_index]]
297
+
298
+ # Since the data is the index plus 15, we should get an
299
+ # interval: [25+15, 974+15]
300
+ expect = [25+15, 974+15]
301
+ assert_equal expect, [lobo, hibo]
302
+
303
+ # There is strictly speaking no median of 1000 items.
304
+ # We take the mean of the two middle items items 500 and 501 at indicies
305
+ # 499 and 500. Since the data is the index + 15, the middle values are
306
+ # 514 and 515, the mean of which is 514.5
307
+ median = 514.5
308
+
309
+ # Check the implementation.
310
+ confrange = Kalibera.confidence_slice(means)
311
+ got_lobo, got_median, got_hibo = confrange.values
312
+ assert_equal got_lobo, confrange.lower
313
+ assert_equal got_median, confrange.median
314
+ assert_equal got_hibo, confrange.upper
315
+
316
+ assert_equal lobo, got_lobo
317
+ assert_equal hibo, got_hibo
318
+ assert_equal got_median, median
319
+
320
+ assert_equal Kalibera.mean([median - lobo, hibo - median]), confrange.error
321
+ end
322
+
323
+ def test_confidence_slice_pass_confidence_level
324
+ means = (0...10).map { |x| Float(x) }
325
+ low, mean, high = Kalibera.confidence_slice(means, '0.8').values
326
+ assert_equal (4 + 5) / 2.0, mean
327
+ assert_equal 1, low
328
+ assert_equal 8, high
329
+
330
+
331
+ means = (0...11).map { |x| Float(x) }
332
+ low, mean, high = Kalibera.confidence_slice(means, '0.8').values
333
+ assert_equal 5, mean
334
+ assert_equal 1, low
335
+ assert_equal 9, high
336
+ end
337
+
338
+ def test_confidence_quotient
339
+ data1 = TestData.new({
340
+ [0] => [ 2.5, 3.1, 2.7 ],
341
+ [1] => [ 5.1, 1.1, 2.3 ],
342
+ [2] => [ 4.7, 5.5, 7.1 ],
343
+ }, [3, 3])
344
+ data2 = TestData.new({
345
+ [0] => [ 3.5, 4.1, 3.7 ],
346
+ [1] => [ 6.1, 2.1, 3.3 ],
347
+ [2] => [ 5.7, 6.5, 8.1 ],
348
+ }, [3, 3])
349
+
350
+ data1.reset_local_rand
351
+ data2.reset_local_rand
352
+ a = data1.bootstrap_sample
353
+ b = data2.bootstrap_sample
354
+
355
+ data1.reset_local_rand
356
+ data2.reset_local_rand
357
+ _, mean, _ = data1.bootstrap_quotient(data2, iterations=1)
358
+ assert_equal Kalibera.mean(a) / Kalibera.mean(b), mean
359
+ end
360
+
361
+ def test_confidence_quotient_div_zero
362
+ data1 = TestData.new({
363
+ [0] => [ 2.5, 3.1, 2.7 ],
364
+ [1] => [ 5.1, 1.1, 2.3 ],
365
+ [2] => [ 4.7, 5.5, 7.1 ],
366
+ }, [3, 3])
367
+ data2 = TestData.new({ # This has a mean of zero
368
+ [0] => [ 0, 0, 0],
369
+ [1] => [ 0, 0, 0],
370
+ [2] => [ 0, 0, 0],
371
+ }, [3, 3])
372
+
373
+ # Since all ratios will be +inf, the median should also be +inf
374
+ _, median, _ = data1.bootstrap_quotient(data2, iterations=1)
375
+ assert_equal Float::INFINITY, median
376
+ end
377
+
378
+ def test_geomean
379
+ assert_equal 1, Kalibera.geomean([10, 0.1])
380
+ assert_equal 1, Kalibera.geomean([1])
381
+ end
382
+
383
+ # This requires a very large volume of random data, which we can't easily
384
+ # just store in the mock random generator above.
385
+
386
+ #def test_geomean_data
387
+ # data1 = TestData.new({
388
+ # [0] => [ 2.9, 3.1, 3.0 ],
389
+ # [1] => [ 3.1, 2.6, 3.3 ],
390
+ # [2] => [ 3.2, 3.0, 2.9 ],
391
+ # }, [3, 3])
392
+ # data2 = TestData.new({
393
+ # [0] => [ 3.9, 4.1, 4.0 ],
394
+ # [1] => [ 4.1, 3.6, 4.3 ],
395
+ # [2] => [ 4.2, 4.0, 3.9 ],
396
+ # }, [3, 3])
397
+ #
398
+ # _, mean1, _ = data1.bootstrap_quotient(data2)
399
+ # _, mean2, _ = Kalibera.bootstrap_geomean([data1], [data2])
400
+ # assert_equal mean2.round(3), mean1.round(3)
401
+ #
402
+ # (_, mean, _) = Kalibera.bootstrap_geomean([data1, data2], [data2, data1])
403
+ # assert_equal 1.0, mean.round(5)
404
+ #end
405
+
406
+ def assert_less_equal(x, y)
407
+ assert x <= y, "#{x.inspect} <= #{y.inspect}"
408
+ end
409
+
410
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kalibera
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Edd Barrett
8
+ - Carl Friedrich Bolz
9
+ - Chris Seaton
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2016-05-16 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.7'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.7'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '10.0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '10.0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rbzip2
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 0.2.0
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 0.2.0
57
+ - !ruby/object:Gem::Dependency
58
+ name: memoist
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 0.11.0
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 0.11.0
71
+ description: 'libkalibera contains reimplementations of the statistical computations
72
+ for benchmarking evaluation from the following papers by Tomas Kalibera and Richard
73
+ Jones: ''Rigorous benchmarking in reasonable time''; ''Quantifying performance changes
74
+ with effect size confidence intervals''.'
75
+ email:
76
+ - chris@chrisseaton.com
77
+ executables: []
78
+ extensions: []
79
+ extra_rdoc_files: []
80
+ files:
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - LICENSE.txt
84
+ - Rakefile
85
+ - kalibera.gemspec
86
+ - lib/kalibera.rb
87
+ - lib/kalibera/data.rb
88
+ - test/test_data.rb
89
+ homepage: http://soft-dev.org/src/libkalibera/
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.5.1
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: An implementation of Tomas Kalibera's statistically rigorous benchmarking
113
+ method.
114
+ test_files:
115
+ - test/test_data.rb