kalibera 0.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.
@@ -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