bullshit 0.1.1 → 0.1.2
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.
- data/.gitignore +3 -0
- data/.travis.yml +7 -0
- data/CHANGES +3 -0
- data/Gemfile +5 -0
- data/{README → README.rdoc} +0 -0
- data/Rakefile +31 -83
- data/VERSION +1 -1
- data/bullshit.gemspec +43 -0
- data/data/.keep +0 -0
- data/lib/bullshit.rb +9 -942
- data/lib/bullshit/version.rb +1 -1
- metadata +96 -37
- data/install.rb +0 -15
- data/make_doc.rb +0 -5
- data/tests/test_analysis.rb +0 -321
- data/tests/test_continued_fraction.rb +0 -40
- data/tests/test_distribution.rb +0 -69
- data/tests/test_functions.rb +0 -33
- data/tests/test_newton_bisection.rb +0 -28
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGES
CHANGED
data/Gemfile
ADDED
data/{README → README.rdoc}
RENAMED
File without changes
|
data/Rakefile
CHANGED
@@ -1,85 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
ruby 'make_doc.rb'
|
33
|
-
end
|
34
|
-
|
35
|
-
if defined? Gem
|
36
|
-
spec = Gem::Specification.new do |s|
|
37
|
-
s.name = PKG_NAME
|
38
|
-
s.version = PKG_VERSION
|
39
|
-
s.summary = "Benchmarking is Bullshit"
|
40
|
-
s.description = ""
|
41
|
-
|
42
|
-
s.add_dependency('dslkit', '>= 0.2.5')
|
43
|
-
|
44
|
-
s.files = PKG_FILES
|
45
|
-
|
46
|
-
s.require_path = 'lib'
|
47
|
-
s.executables = 'bs_compare'
|
48
|
-
|
49
|
-
s.has_rdoc = true
|
50
|
-
s.rdoc_options <<
|
51
|
-
'--title' << 'Bullshit -- Benchmarking in Ruby' << '--main' << 'README'
|
52
|
-
s.test_files = Dir['tests/*.rb']
|
53
|
-
|
54
|
-
s.author = "Florian Frank"
|
55
|
-
s.email = "flori@ping.de"
|
56
|
-
s.homepage = "http://flori.github.com/#{PKG_NAME}"
|
57
|
-
s.rubyforge_project = PKG_NAME
|
58
|
-
end
|
59
|
-
|
60
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
61
|
-
pkg.need_tar = true
|
62
|
-
pkg.package_files += PKG_FILES
|
1
|
+
# vim: set filetype=ruby et sw=2 ts=2:
|
2
|
+
|
3
|
+
require 'gem_hadar'
|
4
|
+
|
5
|
+
GemHadar do
|
6
|
+
name 'bullshit'
|
7
|
+
author 'Florian Frank'
|
8
|
+
email 'flori@ping.de'
|
9
|
+
homepage "http://flori.github.com/#{name}"
|
10
|
+
summary 'Benchmarking is Bullshit'
|
11
|
+
description 'Library to benchmark ruby code and analyse the results'
|
12
|
+
test_dir 'tests'
|
13
|
+
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock'
|
14
|
+
readme 'README.rdoc'
|
15
|
+
title "#{name.camelize} -- Benchmarking in Ruby"
|
16
|
+
executables << 'bs_compare'
|
17
|
+
|
18
|
+
dependency 'spruz', '~>0.2'
|
19
|
+
dependency 'dslkit', '~>0.2'
|
20
|
+
dependency 'more_math', '~>0.0.1'
|
21
|
+
clobber 'data/*.{dat,log}'
|
22
|
+
|
23
|
+
install_library do
|
24
|
+
libdir = CONFIG["sitelibdir"]
|
25
|
+
install('lib/bullshit.rb', libdir, :mode => 0644)
|
26
|
+
mkdir_p subdir = File.join(libdir, 'bullshit')
|
27
|
+
for f in Dir['lib/bullshit/*.rb']
|
28
|
+
install(f, subdir)
|
29
|
+
end
|
30
|
+
bindir = CONFIG["bindir"]
|
31
|
+
install('bin/bs_compare', bindir, :mode => 0755)
|
63
32
|
end
|
64
33
|
end
|
65
|
-
|
66
|
-
desc m = "Writing version information for #{PKG_VERSION}"
|
67
|
-
task :version do
|
68
|
-
puts m
|
69
|
-
File.open(File.join('lib', PKG_NAME, 'version.rb'), 'w') do |v|
|
70
|
-
v.puts <<EOT
|
71
|
-
module Bullshit
|
72
|
-
# Bullshit version
|
73
|
-
VERSION = '#{PKG_VERSION}'
|
74
|
-
VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
|
75
|
-
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
76
|
-
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
77
|
-
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
78
|
-
end
|
79
|
-
EOT
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
task :default => [ :version, :test ]
|
84
|
-
|
85
|
-
task :release => [ :clobber, :version, :package ]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/bullshit.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{bullshit}
|
5
|
+
s.version = "0.1.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Florian Frank"]
|
9
|
+
s.date = %q{2011-07-17}
|
10
|
+
s.default_executable = %q{bs_compare}
|
11
|
+
s.description = %q{Library to benchmark ruby code and analyse the results}
|
12
|
+
s.email = %q{flori@ping.de}
|
13
|
+
s.executables = ["bs_compare"]
|
14
|
+
s.extra_rdoc_files = ["README.rdoc", "lib/bullshit/version.rb", "lib/bullshit.rb"]
|
15
|
+
s.files = [".gitignore", ".travis.yml", "CHANGES", "COPYING", "Gemfile", "README.rdoc", "Rakefile", "VERSION", "bin/bs_compare", "bullshit.gemspec", "data/.keep", "examples/compare.rb", "examples/fibonacci.rb", "examples/iteration.rb", "examples/josephus.rb", "examples/sorting.rb", "examples/throw_raise.rb", "lib/bullshit.rb", "lib/bullshit/version.rb", "tests/test_bullshit.rb", "tests/test_window.rb"]
|
16
|
+
s.homepage = %q{http://flori.github.com/bullshit}
|
17
|
+
s.rdoc_options = ["--title", "Bullshit -- Benchmarking in Ruby", "--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubygems_version = %q{1.6.2}
|
20
|
+
s.summary = %q{Benchmarking is Bullshit}
|
21
|
+
s.test_files = ["tests/test_bullshit.rb", "tests/test_window.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
s.specification_version = 3
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_development_dependency(%q<gem_hadar>, ["~> 0.0.5"])
|
28
|
+
s.add_runtime_dependency(%q<spruz>, ["~> 0.2"])
|
29
|
+
s.add_runtime_dependency(%q<dslkit>, ["~> 0.2"])
|
30
|
+
s.add_runtime_dependency(%q<more_math>, ["~> 0.0.1"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<gem_hadar>, ["~> 0.0.5"])
|
33
|
+
s.add_dependency(%q<spruz>, ["~> 0.2"])
|
34
|
+
s.add_dependency(%q<dslkit>, ["~> 0.2"])
|
35
|
+
s.add_dependency(%q<more_math>, ["~> 0.0.1"])
|
36
|
+
end
|
37
|
+
else
|
38
|
+
s.add_dependency(%q<gem_hadar>, ["~> 0.0.5"])
|
39
|
+
s.add_dependency(%q<spruz>, ["~> 0.2"])
|
40
|
+
s.add_dependency(%q<dslkit>, ["~> 0.2"])
|
41
|
+
s.add_dependency(%q<more_math>, ["~> 0.0.1"])
|
42
|
+
end
|
43
|
+
end
|
data/data/.keep
ADDED
File without changes
|
data/lib/bullshit.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
require 'dslkit'
|
2
2
|
require 'enumerator'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
rescue LoadError
|
7
|
-
end
|
4
|
+
require 'bullshit/version'
|
5
|
+
require 'more_math'
|
8
6
|
|
9
7
|
# Module that includes all constants of the bullshit library.
|
10
8
|
module Bullshit
|
@@ -12,147 +10,10 @@ module Bullshit
|
|
12
10
|
|
13
11
|
NAME_COLUMN_SIZE = 5 # Number of columns used for row names.
|
14
12
|
|
15
|
-
Infinity = 1.0 / 0 # Refers to floating point infinity.
|
16
|
-
|
17
13
|
RUBY_DESCRIPTION = "ruby %s (%s patchlevel %s) [%s]" %
|
18
14
|
[ RUBY_VERSION, RUBY_RELEASE_DATE, RUBY_PATCHLEVEL, RUBY_PLATFORM ]
|
19
15
|
|
20
|
-
|
21
|
-
#
|
22
|
-
# b_1
|
23
|
-
# a_0 + -------------------------
|
24
|
-
# b_2
|
25
|
-
# a_1 + --------------------
|
26
|
-
# b_3
|
27
|
-
# a_2 + ---------------
|
28
|
-
# b_4
|
29
|
-
# a_3 + ----------
|
30
|
-
# b_5
|
31
|
-
# a_4 + -----
|
32
|
-
# ...
|
33
|
-
#
|
34
|
-
class ContinuedFraction
|
35
|
-
# Creates a continued fraction instance. With the defaults for_a { 1 } and
|
36
|
-
# for_b { 1 } it approximates the golden ration phi if evaluated.
|
37
|
-
def initialize
|
38
|
-
@a = proc { 1.0 }
|
39
|
-
@b = proc { 1.0 }
|
40
|
-
end
|
41
|
-
|
42
|
-
# Creates a ContinuedFraction instances and passes its arguments to a call
|
43
|
-
# to for_a.
|
44
|
-
def self.for_a(arg = nil, &block)
|
45
|
-
new.for_a(arg, &block)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Creates a ContinuedFraction instances and passes its arguments to a call
|
49
|
-
# to for_b.
|
50
|
-
def self.for_b(arg = nil, &block)
|
51
|
-
new.for_b(arg, &block)
|
52
|
-
end
|
53
|
-
|
54
|
-
# This method either takes a block or an argument +arg+. The argument +arg+
|
55
|
-
# has to respond to an integer index n >= 0 and return the value a_n. The
|
56
|
-
# block has to return the value for a_n when +n+ is passed as the first
|
57
|
-
# argument to the block. If a_n is dependent on an +x+ value (see the call
|
58
|
-
# method) the +x+ will be the second argument of the block.
|
59
|
-
def for_a(arg = nil, &block)
|
60
|
-
if arg and !block
|
61
|
-
@a = arg
|
62
|
-
elsif block and !arg
|
63
|
-
@a = block
|
64
|
-
else
|
65
|
-
raise ArgumentError, "exactly one argument or one block required"
|
66
|
-
end
|
67
|
-
self
|
68
|
-
end
|
69
|
-
|
70
|
-
# This method either takes a block or an argument +arg+. The argument +arg+
|
71
|
-
# has to respond to an integer index n >= 1 and return the value b_n. The
|
72
|
-
# block has to return the value for b_n when +n+ is passed as the first
|
73
|
-
# argument to the block. If b_n is dependent on an +x+ value (see the call
|
74
|
-
# method) the +x+ will be the second argument of the block.
|
75
|
-
def for_b(arg = nil, &block)
|
76
|
-
if arg and !block
|
77
|
-
@b = arg
|
78
|
-
elsif block and !arg
|
79
|
-
@b = block
|
80
|
-
else
|
81
|
-
raise ArgumentError, "exactly one argument or one block required"
|
82
|
-
end
|
83
|
-
self
|
84
|
-
end
|
85
|
-
|
86
|
-
# Returns the value for a_n or a_n(x).
|
87
|
-
def a(n, x = nil)
|
88
|
-
result = if x
|
89
|
-
@a[n, x]
|
90
|
-
else
|
91
|
-
@a[n]
|
92
|
-
end and result.to_f
|
93
|
-
end
|
94
|
-
|
95
|
-
# Returns the value for b_n or b_n(x).
|
96
|
-
def b(n, x = nil)
|
97
|
-
result = if x
|
98
|
-
@b[n, x]
|
99
|
-
else
|
100
|
-
@b[n]
|
101
|
-
end and result.to_f
|
102
|
-
end
|
103
|
-
|
104
|
-
# Evaluates the continued fraction for the value +x+ (if any) with the
|
105
|
-
# accuracy +epsilon+ and +max_iterations+ as the maximum number of
|
106
|
-
# iterations using the Wallis-method with scaling.
|
107
|
-
def call(x = nil, epsilon = 1E-16, max_iterations = 1 << 31)
|
108
|
-
c_0, c_1 = 1.0, a(0, x)
|
109
|
-
c_1 == nil and return 0 / 0.0
|
110
|
-
d_0, d_1 = 0.0, 1.0
|
111
|
-
result = c_1 / d_1
|
112
|
-
n = 0
|
113
|
-
error = 1 / 0.0
|
114
|
-
$DEBUG and warn "n=%u, a=%f, b=nil, c=%f, d=%f result=%f, error=nil" %
|
115
|
-
[ n, c_1, c_1, d_1, result ]
|
116
|
-
while n < max_iterations and error > epsilon
|
117
|
-
n += 1
|
118
|
-
a_n, b_n = a(n, x), b(n, x)
|
119
|
-
a_n and b_n or break
|
120
|
-
c_2 = a_n * c_1 + b_n * c_0
|
121
|
-
d_2 = a_n * d_1 + b_n * d_0
|
122
|
-
if c_2.infinite? or d_2.infinite?
|
123
|
-
if a_n != 0
|
124
|
-
c_2 = c_1 + (b_n / a_n * c_0)
|
125
|
-
d_2 = d_1 + (b_n / a_n * d_0)
|
126
|
-
elsif b_n != 0
|
127
|
-
c_2 = (a_n / b_n * c_1) + c_0
|
128
|
-
d_2 = (a_n / b_n * d_1) + d_0
|
129
|
-
else
|
130
|
-
raise Errno::ERANGE
|
131
|
-
end
|
132
|
-
end
|
133
|
-
r = c_2 / d_2
|
134
|
-
error = (r / result - 1).abs
|
135
|
-
|
136
|
-
result = r
|
137
|
-
|
138
|
-
$DEBUG and warn "n=%u, a=%f, b=%f, c=%f, d=%f, result=%f, error=%.16f" %
|
139
|
-
[ n, a_n, b_n, c_1, d_1, result, error ]
|
140
|
-
|
141
|
-
c_0, c_1 = c_1, c_2
|
142
|
-
d_0, d_1 = d_1, d_2
|
143
|
-
end
|
144
|
-
n >= max_iterations and raise Errno::ERANGE
|
145
|
-
result
|
146
|
-
end
|
147
|
-
|
148
|
-
alias [] call
|
149
|
-
|
150
|
-
# Returns this continued fraction as a Proc object which takes the same
|
151
|
-
# arguments like its call method does.
|
152
|
-
def to_proc
|
153
|
-
proc { |*a| call(*a) }
|
154
|
-
end
|
155
|
-
end
|
16
|
+
include MoreMath
|
156
17
|
|
157
18
|
module ModuleFunctions
|
158
19
|
module_function
|
@@ -203,6 +64,8 @@ module Bullshit
|
|
203
64
|
|
204
65
|
# A Clock instance is used to take measurements while benchmarking.
|
205
66
|
class Clock
|
67
|
+
include MoreMath
|
68
|
+
|
206
69
|
TIMES = [ :real, :total, :user, :system ]
|
207
70
|
|
208
71
|
ALL_COLUMNS = [ :scatter ] + TIMES + [ :repeat ]
|
@@ -314,12 +177,12 @@ module Bullshit
|
|
314
177
|
self
|
315
178
|
end
|
316
179
|
|
317
|
-
# Returns a Hash of
|
180
|
+
# Returns a Hash of Sequence object for all of TIMES's time keys.
|
318
181
|
def analysis
|
319
182
|
@analysis ||= Hash.new do |h, time|
|
320
183
|
time = time.to_sym
|
321
184
|
times = @times[time]
|
322
|
-
h[time] =
|
185
|
+
h[time] = MoreMath::Sequence.new(times)
|
323
186
|
end
|
324
187
|
end
|
325
188
|
|
@@ -342,7 +205,7 @@ module Bullshit
|
|
342
205
|
def to_a
|
343
206
|
if @repeat >= 1
|
344
207
|
(::Bullshit::Clock::ALL_COLUMNS).map do |t|
|
345
|
-
analysis[t].
|
208
|
+
analysis[t].elements
|
346
209
|
end.transpose
|
347
210
|
else
|
348
211
|
[]
|
@@ -530,7 +393,7 @@ module Bullshit
|
|
530
393
|
truncation = self.case.truncate_data
|
531
394
|
slope_angle = self.case.truncate_data.slope_angle.abs
|
532
395
|
time = self.case.compare_time.to_sym
|
533
|
-
ms = analysis[time].
|
396
|
+
ms = analysis[time].elements.reverse
|
534
397
|
offset = ms.size - 1
|
535
398
|
@slopes = []
|
536
399
|
ModuleFunctions.array_window(ms, truncation.window_size) do |data|
|
@@ -544,802 +407,6 @@ module Bullshit
|
|
544
407
|
end
|
545
408
|
end
|
546
409
|
|
547
|
-
# A histogram gives an overview of measurement time values.
|
548
|
-
class Histogram
|
549
|
-
# Create a Histogram for +clock+ using the measurements for +time+.
|
550
|
-
def initialize(analysis, bins)
|
551
|
-
@analysis = analysis
|
552
|
-
@bins = bins
|
553
|
-
@result = compute
|
554
|
-
end
|
555
|
-
|
556
|
-
# Number of bins for this Histogram.
|
557
|
-
attr_reader :bins
|
558
|
-
|
559
|
-
# Return the computed histogram as an array of arrays.
|
560
|
-
def to_a
|
561
|
-
@result
|
562
|
-
end
|
563
|
-
|
564
|
-
# Display this histogram to +output+, +width+ is the parameter for
|
565
|
-
# +prepare_display+
|
566
|
-
def display(output = $stdout, width = 50)
|
567
|
-
d = prepare_display(width)
|
568
|
-
for l, bar, r in d
|
569
|
-
output << "%11.5f -|%s\n" % [ (l + r) / 2.0, "*" * bar ]
|
570
|
-
end
|
571
|
-
self
|
572
|
-
end
|
573
|
-
|
574
|
-
private
|
575
|
-
|
576
|
-
# Returns an array of tuples (l, c, r) where +l+ is the left bin edge, +c+
|
577
|
-
# the +width+-normalized frequence count value, and +r+ the right bin
|
578
|
-
# edge. +width+ is usually an integer number representing the width of a
|
579
|
-
# histogram bar.
|
580
|
-
def prepare_display(width)
|
581
|
-
r = @result.reverse
|
582
|
-
factor = width.to_f / (r.transpose[1].max)
|
583
|
-
r.map { |l, c, r| [ l, (c * factor).round, r ] }
|
584
|
-
end
|
585
|
-
|
586
|
-
# Computes the histogram and returns it as an array of tuples (l, c, r).
|
587
|
-
def compute
|
588
|
-
@analysis.measurements.empty? and return []
|
589
|
-
last_r = -Infinity
|
590
|
-
min = @analysis.min
|
591
|
-
max = @analysis.max
|
592
|
-
step = (max - min) / bins.to_f
|
593
|
-
Array.new(bins) do |i|
|
594
|
-
l = min + i * step
|
595
|
-
r = min + (i + 1) * step
|
596
|
-
c = 0
|
597
|
-
@analysis.measurements.each do |x|
|
598
|
-
x > last_r and (x <= r || i == bins - 1) and c += 1
|
599
|
-
end
|
600
|
-
last_r = r
|
601
|
-
[ l, c, r ]
|
602
|
-
end
|
603
|
-
end
|
604
|
-
end
|
605
|
-
|
606
|
-
# This class is used to find the root of a function with Newton's bisection
|
607
|
-
# method.
|
608
|
-
class NewtonBisection
|
609
|
-
# Creates a NewtonBisection instance for +function+, a one-argument block.
|
610
|
-
def initialize(&function)
|
611
|
-
@function = function
|
612
|
-
end
|
613
|
-
|
614
|
-
# The function, passed into the constructor.
|
615
|
-
attr_reader :function
|
616
|
-
|
617
|
-
# Return a bracket around a root, starting from the initial +range+. The
|
618
|
-
# method returns nil, if no such bracket around a root could be found after
|
619
|
-
# +n+ tries with the scaling +factor+.
|
620
|
-
def bracket(range = -1..1, n = 50, factor = 1.6)
|
621
|
-
x1, x2 = range.first.to_f, range.last.to_f
|
622
|
-
x1 >= x2 and raise ArgumentError, "bad initial range #{range}"
|
623
|
-
f1, f2 = @function[x1], @function[x2]
|
624
|
-
n.times do
|
625
|
-
f1 * f2 < 0 and return x1..x2
|
626
|
-
if f1.abs < f2.abs
|
627
|
-
f1 = @function[x1 += factor * (x1 - x2)]
|
628
|
-
else
|
629
|
-
f2 = @function[x2 += factor * (x2 - x1)]
|
630
|
-
end
|
631
|
-
end
|
632
|
-
return
|
633
|
-
end
|
634
|
-
|
635
|
-
# Find the root of function in +range+ and return it. The method raises a
|
636
|
-
# BullshitException, if no such root could be found after +n+ tries and in
|
637
|
-
# the +epsilon+ environment.
|
638
|
-
def solve(range = nil, n = 1 << 16, epsilon = 1E-16)
|
639
|
-
if range
|
640
|
-
x1, x2 = range.first.to_f, range.last.to_f
|
641
|
-
x1 >= x2 and raise ArgumentError, "bad initial range #{range}"
|
642
|
-
elsif range = bracket
|
643
|
-
x1, x2 = range.first, range.last
|
644
|
-
else
|
645
|
-
raise ArgumentError, "bracket could not be determined"
|
646
|
-
end
|
647
|
-
f = @function[x1]
|
648
|
-
fmid = @function[x2]
|
649
|
-
f * fmid >= 0 and raise ArgumentError, "root must be bracketed in #{range}"
|
650
|
-
root = if f < 0
|
651
|
-
dx = x2 - x1
|
652
|
-
x1
|
653
|
-
else
|
654
|
-
dx = x1 - x2
|
655
|
-
x2
|
656
|
-
end
|
657
|
-
n.times do
|
658
|
-
fmid = @function[xmid = root + (dx *= 0.5)]
|
659
|
-
fmid < 0 and root = xmid
|
660
|
-
dx.abs < epsilon or fmid == 0 and return root
|
661
|
-
end
|
662
|
-
raise BullshitException, "too many iterations (#{n})"
|
663
|
-
end
|
664
|
-
end
|
665
|
-
|
666
|
-
module Functions
|
667
|
-
module_function
|
668
|
-
|
669
|
-
include Math
|
670
|
-
extend Math
|
671
|
-
|
672
|
-
LANCZOS_COEFFICIENTS = [
|
673
|
-
0.99999999999999709182,
|
674
|
-
57.156235665862923517,
|
675
|
-
-59.597960355475491248,
|
676
|
-
14.136097974741747174,
|
677
|
-
-0.49191381609762019978,
|
678
|
-
0.33994649984811888699e-4,
|
679
|
-
0.46523628927048575665e-4,
|
680
|
-
-0.98374475304879564677e-4,
|
681
|
-
0.15808870322491248884e-3,
|
682
|
-
-0.21026444172410488319e-3,
|
683
|
-
0.21743961811521264320e-3,
|
684
|
-
-0.16431810653676389022e-3,
|
685
|
-
0.84418223983852743293e-4,
|
686
|
-
-0.26190838401581408670e-4,
|
687
|
-
0.36899182659531622704e-5,
|
688
|
-
]
|
689
|
-
|
690
|
-
HALF_LOG_2_PI = 0.5 * log(2 * Math::PI)
|
691
|
-
|
692
|
-
# Returns the natural logarithm of Euler gamma function value for +x+ using
|
693
|
-
# the Lanczos approximation.
|
694
|
-
if method_defined?(:lgamma)
|
695
|
-
def log_gamma(x)
|
696
|
-
lgamma(x).first
|
697
|
-
end
|
698
|
-
else
|
699
|
-
def log_gamma(x)
|
700
|
-
if x.nan? || x <= 0
|
701
|
-
0 / 0.0
|
702
|
-
else
|
703
|
-
sum = 0.0
|
704
|
-
(LANCZOS_COEFFICIENTS.size - 1).downto(1) do |i|
|
705
|
-
sum += LANCZOS_COEFFICIENTS[i] / (x + i)
|
706
|
-
end
|
707
|
-
sum += LANCZOS_COEFFICIENTS[0]
|
708
|
-
tmp = x + 607.0 / 128 + 0.5
|
709
|
-
(x + 0.5) * log(tmp) - tmp + HALF_LOG_2_PI + log(sum / x)
|
710
|
-
end
|
711
|
-
rescue Errno::ERANGE, Errno::EDOM
|
712
|
-
0 / 0.0
|
713
|
-
end
|
714
|
-
end
|
715
|
-
|
716
|
-
# Returns the natural logarithm of the beta function value for +(a, b)+.
|
717
|
-
def log_beta(a, b)
|
718
|
-
log_gamma(a) + log_gamma(b) - log_gamma(a + b)
|
719
|
-
rescue Errno::ERANGE, Errno::EDOM
|
720
|
-
0 / 0.0
|
721
|
-
end
|
722
|
-
|
723
|
-
# Return an approximation value of Euler's regularized beta function for
|
724
|
-
# +x+, +a+, and +b+ with an error <= +epsilon+, but only iterate
|
725
|
-
# +max_iterations+-times.
|
726
|
-
def beta_regularized(x, a, b, epsilon = 1E-16, max_iterations = 1 << 16)
|
727
|
-
x, a, b = x.to_f, a.to_f, b.to_f
|
728
|
-
case
|
729
|
-
when a.nan? || b.nan? || x.nan? || a <= 0 || b <= 0 || x < 0 || x > 1
|
730
|
-
0 / 0.0
|
731
|
-
when x > (a + 1) / (a + b + 2)
|
732
|
-
1 - beta_regularized(1 - x, b, a, epsilon, max_iterations)
|
733
|
-
else
|
734
|
-
fraction = ContinuedFraction.for_b do |n, x|
|
735
|
-
if n % 2 == 0
|
736
|
-
m = n / 2.0
|
737
|
-
(m * (b - m) * x) / ((a + (2 * m) - 1) * (a + (2 * m)))
|
738
|
-
else
|
739
|
-
m = (n - 1) / 2.0
|
740
|
-
-((a + m) * (a + b + m) * x) / ((a + 2 * m) * (a + 2 * m + 1))
|
741
|
-
end
|
742
|
-
end
|
743
|
-
exp(a * log(x) + b * log(1.0 - x) - log(a) - log_beta(a, b)) /
|
744
|
-
fraction[x, epsilon, max_iterations]
|
745
|
-
end
|
746
|
-
rescue Errno::ERANGE, Errno::EDOM
|
747
|
-
0 / 0.0
|
748
|
-
end
|
749
|
-
|
750
|
-
# Return an approximation of the regularized gammaP function for +x+ and
|
751
|
-
# +a+ with an error of <= +epsilon+, but only iterate
|
752
|
-
# +max_iterations+-times.
|
753
|
-
def gammaP_regularized(x, a, epsilon = 1E-16, max_iterations = 1 << 16)
|
754
|
-
x, a = x.to_f, a.to_f
|
755
|
-
case
|
756
|
-
when a.nan? || x.nan? || a <= 0 || x < 0
|
757
|
-
0 / 0.0
|
758
|
-
when x == 0
|
759
|
-
0.0
|
760
|
-
when 1 <= a && a < x
|
761
|
-
1 - gammaQ_regularized(x, a, epsilon, max_iterations)
|
762
|
-
else
|
763
|
-
n = 0
|
764
|
-
an = 1 / a
|
765
|
-
sum = an
|
766
|
-
while an.abs > epsilon && n < max_iterations
|
767
|
-
n += 1
|
768
|
-
an *= x / (a + n)
|
769
|
-
sum += an
|
770
|
-
end
|
771
|
-
if n >= max_iterations
|
772
|
-
raise Errno::ERANGE
|
773
|
-
else
|
774
|
-
exp(-x + a * log(x) - log_gamma(a)) * sum
|
775
|
-
end
|
776
|
-
end
|
777
|
-
rescue Errno::ERANGE, Errno::EDOM
|
778
|
-
0 / 0.0
|
779
|
-
end
|
780
|
-
|
781
|
-
# Return an approximation of the regularized gammaQ function for +x+ and
|
782
|
-
# +a+ with an error of <= +epsilon+, but only iterate
|
783
|
-
# +max_iterations+-times.
|
784
|
-
def gammaQ_regularized(x, a, epsilon = 1E-16, max_iterations = 1 << 16)
|
785
|
-
x, a = x.to_f, a.to_f
|
786
|
-
case
|
787
|
-
when a.nan? || x.nan? || a <= 0 || x < 0
|
788
|
-
0 / 0.0
|
789
|
-
when x == 0
|
790
|
-
1.0
|
791
|
-
when a > x || a < 1
|
792
|
-
1 - gammaP_regularized(x, a, epsilon, max_iterations)
|
793
|
-
else
|
794
|
-
fraction = ContinuedFraction.for_a do |n, x|
|
795
|
-
(2 * n + 1) - a + x
|
796
|
-
end.for_b do |n, x|
|
797
|
-
n * (a - n)
|
798
|
-
end
|
799
|
-
exp(-x + a * log(x) - log_gamma(a)) *
|
800
|
-
fraction[x, epsilon, max_iterations] ** -1
|
801
|
-
end
|
802
|
-
rescue Errno::ERANGE, Errno::EDOM
|
803
|
-
0 / 0.0
|
804
|
-
end
|
805
|
-
|
806
|
-
ROOT2 = sqrt(2)
|
807
|
-
|
808
|
-
A = -8 * (Math::PI - 3) / (3 * Math::PI * (Math::PI - 4))
|
809
|
-
|
810
|
-
# Returns an approximate value for the error function's value for +x+.
|
811
|
-
def erf(x)
|
812
|
-
r = sqrt(1 - exp(-x ** 2 * (4 / Math::PI + A * x ** 2) / (1 + A * x ** 2)))
|
813
|
-
x < 0 ? -r : r
|
814
|
-
end unless method_defined?(:erf)
|
815
|
-
end
|
816
|
-
|
817
|
-
# This class is used to compute the T-Distribution.
|
818
|
-
class TDistribution
|
819
|
-
include Functions
|
820
|
-
|
821
|
-
# Returns a TDistribution instance for the degrees of freedom +df+.
|
822
|
-
def initialize(df)
|
823
|
-
@df = df
|
824
|
-
end
|
825
|
-
|
826
|
-
# Degrees of freedom.
|
827
|
-
attr_reader :df
|
828
|
-
|
829
|
-
# Returns the cumulative probability (p-value) of the TDistribution for the
|
830
|
-
# t-value +x+.
|
831
|
-
def probability(x)
|
832
|
-
if x == 0
|
833
|
-
0.5
|
834
|
-
else
|
835
|
-
t = beta_regularized(@df / (@df + x ** 2.0), 0.5 * @df, 0.5)
|
836
|
-
if x < 0.0
|
837
|
-
0.5 * t
|
838
|
-
else
|
839
|
-
1 - 0.5 * t
|
840
|
-
end
|
841
|
-
end
|
842
|
-
end
|
843
|
-
|
844
|
-
# Returns the inverse cumulative probability (t-value) of the TDistribution
|
845
|
-
# for the probability +p+.
|
846
|
-
def inverse_probability(p)
|
847
|
-
case
|
848
|
-
when p <= 0
|
849
|
-
-1 / 0.0
|
850
|
-
when p >= 1
|
851
|
-
1 / 0.0
|
852
|
-
else
|
853
|
-
begin
|
854
|
-
bisect = NewtonBisection.new { |x| probability(x) - p }
|
855
|
-
range = bisect.bracket(-10..10)
|
856
|
-
bisect.solve(range, 1_000_000)
|
857
|
-
rescue
|
858
|
-
0 / 0.0
|
859
|
-
end
|
860
|
-
end
|
861
|
-
end
|
862
|
-
end
|
863
|
-
|
864
|
-
# This class is used to compute the Normal Distribution.
|
865
|
-
class NormalDistribution
|
866
|
-
include Functions
|
867
|
-
|
868
|
-
# Creates a NormalDistribution instance for the values +mu+ and +sigma+.
|
869
|
-
def initialize(mu = 0.0, sigma = 1.0)
|
870
|
-
@mu, @sigma = mu.to_f, sigma.to_f
|
871
|
-
end
|
872
|
-
|
873
|
-
attr_reader :mu
|
874
|
-
|
875
|
-
attr_reader :sigma
|
876
|
-
|
877
|
-
# Returns the cumulative probability (p-value) of the NormalDistribution
|
878
|
-
# for the value +x+.
|
879
|
-
def probability(x)
|
880
|
-
0.5 * (1 + erf((x - @mu) / (@sigma * ROOT2)))
|
881
|
-
end
|
882
|
-
|
883
|
-
# Returns the inverse cumulative probability value of the
|
884
|
-
# NormalDistribution for the probability +p+.
|
885
|
-
def inverse_probability(p)
|
886
|
-
case
|
887
|
-
when p <= 0
|
888
|
-
-1 / 0.0
|
889
|
-
when p >= 1
|
890
|
-
1 / 0.0
|
891
|
-
when p == 0.5 # This is a bit sloppy, maybe improve this later.
|
892
|
-
@mu
|
893
|
-
else
|
894
|
-
begin
|
895
|
-
NewtonBisection.new { |x| probability(x) - p }.solve(nil, 1_000_000)
|
896
|
-
rescue
|
897
|
-
0 / 0.0
|
898
|
-
end
|
899
|
-
end
|
900
|
-
end
|
901
|
-
end
|
902
|
-
|
903
|
-
STD_NORMAL_DISTRIBUTION = NormalDistribution.new
|
904
|
-
|
905
|
-
# This class is used to compute the Chi-Square Distribution.
|
906
|
-
class ChiSquareDistribution
|
907
|
-
include Functions
|
908
|
-
|
909
|
-
# Creates a ChiSquareDistribution for +df+ degrees of freedom.
|
910
|
-
def initialize(df)
|
911
|
-
@df = df
|
912
|
-
@df_half = @df / 2.0
|
913
|
-
end
|
914
|
-
|
915
|
-
attr_reader :df
|
916
|
-
|
917
|
-
# Returns the cumulative probability (p-value) of the ChiSquareDistribution
|
918
|
-
# for the value +x+.
|
919
|
-
def probability(x)
|
920
|
-
if x < 0
|
921
|
-
0.0
|
922
|
-
else
|
923
|
-
gammaP_regularized(x / 2, @df_half)
|
924
|
-
end
|
925
|
-
end
|
926
|
-
|
927
|
-
# Returns the inverse cumulative probability value of the
|
928
|
-
# NormalDistribution for the probability +p+.
|
929
|
-
def inverse_probability(p)
|
930
|
-
case
|
931
|
-
when p <= 0, p >= 1
|
932
|
-
0.0
|
933
|
-
else
|
934
|
-
begin
|
935
|
-
bisect = NewtonBisection.new { |x| probability(x) - p }
|
936
|
-
range = bisect.bracket 0.5..10
|
937
|
-
bisect.solve(range, 1_000_000)
|
938
|
-
rescue
|
939
|
-
0 / 0.0
|
940
|
-
end
|
941
|
-
end
|
942
|
-
end
|
943
|
-
end
|
944
|
-
|
945
|
-
# This class computes a linear regression for the given image and domain data
|
946
|
-
# sets.
|
947
|
-
class LinearRegression
|
948
|
-
def initialize(image, domain = (0...image.size).to_a)
|
949
|
-
image.size != domain.size and raise ArgumentError,
|
950
|
-
"image and domain have unequal sizes"
|
951
|
-
@image, @domain = image, domain
|
952
|
-
compute
|
953
|
-
end
|
954
|
-
|
955
|
-
# The image data as an array.
|
956
|
-
attr_reader :image
|
957
|
-
|
958
|
-
# The domain data as an array.
|
959
|
-
attr_reader :domain
|
960
|
-
|
961
|
-
# The slope of the line.
|
962
|
-
attr_reader :a
|
963
|
-
|
964
|
-
# The offset of the line.
|
965
|
-
attr_reader :b
|
966
|
-
|
967
|
-
# Return true if the slope of the underlying data (not the sample data
|
968
|
-
# passed into the constructor of this LinearRegression instance) is likely
|
969
|
-
# (with alpha level _alpha_) to be zero.
|
970
|
-
def slope_zero?(alpha = 0.05)
|
971
|
-
df = @image.size - 2
|
972
|
-
return true if df <= 0 # not enough values to check
|
973
|
-
t = tvalue(alpha)
|
974
|
-
td = TDistribution.new df
|
975
|
-
t.abs <= td.inverse_probability(1 - alpha.abs / 2.0).abs
|
976
|
-
end
|
977
|
-
|
978
|
-
# Returns the residues of this linear regression in relation to the given
|
979
|
-
# domain and image.
|
980
|
-
def residues
|
981
|
-
result = []
|
982
|
-
@domain.zip(@image) do |x, y|
|
983
|
-
result << y - (@a * x + @b)
|
984
|
-
end
|
985
|
-
result
|
986
|
-
end
|
987
|
-
|
988
|
-
private
|
989
|
-
|
990
|
-
def compute
|
991
|
-
size = @image.size
|
992
|
-
sum_xx = sum_xy = sum_x = sum_y = 0.0
|
993
|
-
@domain.zip(@image) do |x, y|
|
994
|
-
x += 1
|
995
|
-
sum_xx += x ** 2
|
996
|
-
sum_xy += x * y
|
997
|
-
sum_x += x
|
998
|
-
sum_y += y
|
999
|
-
end
|
1000
|
-
@a = (size * sum_xy - sum_x * sum_y) / (size * sum_xx - sum_x ** 2)
|
1001
|
-
@b = (sum_y - @a * sum_x) / size
|
1002
|
-
self
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
def tvalue(alpha = 0.05)
|
1006
|
-
df = @image.size - 2
|
1007
|
-
return 0.0 if df <= 0
|
1008
|
-
sse_y = 0.0
|
1009
|
-
@domain.zip(@image) do |x, y|
|
1010
|
-
f_x = a * x + b
|
1011
|
-
sse_y += (y - f_x) ** 2
|
1012
|
-
end
|
1013
|
-
mean = @image.inject(0.0) { |s, y| s + y } / @image.size
|
1014
|
-
sse_x = @domain.inject(0.0) { |s, x| s + (x - mean) ** 2 }
|
1015
|
-
t = a / (Math.sqrt(sse_y / df) / Math.sqrt(sse_x))
|
1016
|
-
t.nan? ? 0.0 : t
|
1017
|
-
end
|
1018
|
-
end
|
1019
|
-
|
1020
|
-
# This class is used to analyse the time measurements and compute their
|
1021
|
-
# statistics.
|
1022
|
-
class Analysis
|
1023
|
-
def initialize(measurements)
|
1024
|
-
@measurements = measurements
|
1025
|
-
@measurements.freeze
|
1026
|
-
end
|
1027
|
-
|
1028
|
-
# Returns the array of measurements.
|
1029
|
-
attr_reader :measurements
|
1030
|
-
|
1031
|
-
# Returns the number of measurements, on which the analysis is based.
|
1032
|
-
def size
|
1033
|
-
@measurements.size
|
1034
|
-
end
|
1035
|
-
|
1036
|
-
# Returns the variance of the measurements.
|
1037
|
-
def variance
|
1038
|
-
@variance ||= sum_of_squares / size
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
# Returns the sample_variance of the measurements.
|
1042
|
-
def sample_variance
|
1043
|
-
@sample_variance ||= size > 1 ? sum_of_squares / (size - 1.0) : 0.0
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
# Returns the sum of squares (the sum of the squared deviations) of the
|
1047
|
-
# measurements.
|
1048
|
-
def sum_of_squares
|
1049
|
-
@sum_of_squares ||= @measurements.inject(0.0) { |s, t| s + (t - arithmetic_mean) ** 2 }
|
1050
|
-
end
|
1051
|
-
|
1052
|
-
# Returns the standard deviation of the measurements.
|
1053
|
-
def standard_deviation
|
1054
|
-
@sample_deviation ||= Math.sqrt(variance)
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
# Returns the standard deviation of the measurements in percentage of the
|
1058
|
-
# arithmetic mean.
|
1059
|
-
def standard_deviation_percentage
|
1060
|
-
@standard_deviation_percentage ||= 100.0 * standard_deviation / arithmetic_mean
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
# Returns the sample standard deviation of the measurements.
|
1064
|
-
def sample_standard_deviation
|
1065
|
-
@sample_standard_deviation ||= Math.sqrt(sample_variance)
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
# Returns the sample standard deviation of the measurements in percentage
|
1069
|
-
# of the arithmetic mean.
|
1070
|
-
def sample_standard_deviation_percentage
|
1071
|
-
@sample_standard_deviation_percentage ||= 100.0 * sample_standard_deviation / arithmetic_mean
|
1072
|
-
end
|
1073
|
-
|
1074
|
-
# Returns the sum of all measurements.
|
1075
|
-
def sum
|
1076
|
-
@sum ||= @measurements.inject(0.0) { |s, t| s + t }
|
1077
|
-
end
|
1078
|
-
|
1079
|
-
# Returns the arithmetic mean of the measurements.
|
1080
|
-
def arithmetic_mean
|
1081
|
-
@arithmetic_mean ||= sum / size
|
1082
|
-
end
|
1083
|
-
|
1084
|
-
alias mean arithmetic_mean
|
1085
|
-
|
1086
|
-
# Returns the harmonic mean of the measurements. If any of the measurements
|
1087
|
-
# is less than or equal to 0.0, this method returns NaN.
|
1088
|
-
def harmonic_mean
|
1089
|
-
@harmonic_mean ||= (
|
1090
|
-
sum = @measurements.inject(0.0) { |s, t|
|
1091
|
-
if t > 0
|
1092
|
-
s + 1.0 / t
|
1093
|
-
else
|
1094
|
-
break nil
|
1095
|
-
end
|
1096
|
-
}
|
1097
|
-
sum ? size / sum : 0 / 0.0
|
1098
|
-
)
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
# Returns the geometric mean of the measurements. If any of the
|
1102
|
-
# measurements is less than 0.0, this method returns NaN.
|
1103
|
-
def geometric_mean
|
1104
|
-
@geometric_mean ||= (
|
1105
|
-
sum = @measurements.inject(0.0) { |s, t|
|
1106
|
-
case
|
1107
|
-
when t > 0
|
1108
|
-
s + Math.log(t)
|
1109
|
-
when t == 0
|
1110
|
-
break :null
|
1111
|
-
else
|
1112
|
-
break nil
|
1113
|
-
end
|
1114
|
-
}
|
1115
|
-
case sum
|
1116
|
-
when :null
|
1117
|
-
0.0
|
1118
|
-
when Float
|
1119
|
-
Math.exp(sum / size)
|
1120
|
-
else
|
1121
|
-
0 / 0.0
|
1122
|
-
end
|
1123
|
-
)
|
1124
|
-
end
|
1125
|
-
|
1126
|
-
# Returns the minimum of the measurements.
|
1127
|
-
def min
|
1128
|
-
@min ||= @measurements.min
|
1129
|
-
end
|
1130
|
-
|
1131
|
-
# Returns the maximum of the measurements.
|
1132
|
-
def max
|
1133
|
-
@max ||= @measurements.max
|
1134
|
-
end
|
1135
|
-
|
1136
|
-
# Returns the +p+-percentile of the measurements.
|
1137
|
-
# There are many methods to compute the percentile, this method uses the
|
1138
|
-
# the weighted average at x_(n + 1)p, which allows p to be in 0...100
|
1139
|
-
# (excluding the 100).
|
1140
|
-
def percentile(p = 50)
|
1141
|
-
(0...100).include?(p) or
|
1142
|
-
raise ArgumentError, "p = #{p}, but has to be in (0...100)"
|
1143
|
-
p /= 100.0
|
1144
|
-
@sorted ||= @measurements.sort
|
1145
|
-
r = p * (@sorted.size + 1)
|
1146
|
-
r_i = r.to_i
|
1147
|
-
r_f = r - r_i
|
1148
|
-
if r_i >= 1
|
1149
|
-
result = @sorted[r_i - 1]
|
1150
|
-
if r_i < @sorted.size
|
1151
|
-
result += r_f * (@sorted[r_i] - @sorted[r_i - 1])
|
1152
|
-
end
|
1153
|
-
else
|
1154
|
-
result = @sorted[0]
|
1155
|
-
end
|
1156
|
-
result
|
1157
|
-
end
|
1158
|
-
|
1159
|
-
alias median percentile
|
1160
|
-
|
1161
|
-
# Use an approximation of the Welch-Satterthwaite equation to compute the
|
1162
|
-
# degrees of freedom for Welch's t-test.
|
1163
|
-
def compute_welch_df(other)
|
1164
|
-
(sample_variance / size + other.sample_variance / other.size) ** 2 / (
|
1165
|
-
(sample_variance ** 2 / (size ** 2 * (size - 1))) +
|
1166
|
-
(other.sample_variance ** 2 / (other.size ** 2 * (other.size - 1))))
|
1167
|
-
end
|
1168
|
-
|
1169
|
-
# Returns the t value of the Welch's t-test between this Analysis
|
1170
|
-
# instance and the +other+.
|
1171
|
-
def t_welch(other)
|
1172
|
-
signal = arithmetic_mean - other.arithmetic_mean
|
1173
|
-
noise = Math.sqrt(sample_variance / size +
|
1174
|
-
other.sample_variance / other.size)
|
1175
|
-
signal / noise
|
1176
|
-
rescue Errno::EDOM
|
1177
|
-
0.0
|
1178
|
-
end
|
1179
|
-
|
1180
|
-
# Returns an estimation of the common standard deviation of the
|
1181
|
-
# measurements of this and +other+.
|
1182
|
-
def common_standard_deviation(other)
|
1183
|
-
Math.sqrt(common_variance(other))
|
1184
|
-
end
|
1185
|
-
|
1186
|
-
# Returns an estimation of the common variance of the measurements of this
|
1187
|
-
# and +other+.
|
1188
|
-
def common_variance(other)
|
1189
|
-
(size - 1) * sample_variance + (other.size - 1) * other.sample_variance /
|
1190
|
-
(size + other.size - 2)
|
1191
|
-
end
|
1192
|
-
|
1193
|
-
# Compute the # degrees of freedom for Student's t-test.
|
1194
|
-
def compute_student_df(other)
|
1195
|
-
size + other.size - 2
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
# Returns the t value of the Student's t-test between this Analysis
|
1199
|
-
# instance and the +other+.
|
1200
|
-
def t_student(other)
|
1201
|
-
signal = arithmetic_mean - other.arithmetic_mean
|
1202
|
-
noise = common_standard_deviation(other) *
|
1203
|
-
Math.sqrt(size ** -1 + size ** -1)
|
1204
|
-
rescue Errno::EDOM
|
1205
|
-
0.0
|
1206
|
-
end
|
1207
|
-
|
1208
|
-
# Compute a sample size, that will more likely yield a mean difference
|
1209
|
-
# between this instance's measurements and those of +other+. Use +alpha+
|
1210
|
-
# and +beta+ as levels for the first- and second-order errors.
|
1211
|
-
def suggested_sample_size(other, alpha = 0.05, beta = 0.05)
|
1212
|
-
alpha, beta = alpha.abs, beta.abs
|
1213
|
-
signal = arithmetic_mean - other.arithmetic_mean
|
1214
|
-
df = size + other.size - 2
|
1215
|
-
pooled_variance_estimate = (sum_of_squares + other.sum_of_squares) / df
|
1216
|
-
td = TDistribution.new df
|
1217
|
-
(((td.inverse_probability(alpha) + td.inverse_probability(beta)) *
|
1218
|
-
Math.sqrt(pooled_variance_estimate)) / signal) ** 2
|
1219
|
-
end
|
1220
|
-
|
1221
|
-
# Return true, if the Analysis instance covers the +other+, that is their
|
1222
|
-
# arithmetic mean value is most likely to be equal for the +alpha+ error
|
1223
|
-
# level.
|
1224
|
-
def cover?(other, alpha = 0.05)
|
1225
|
-
t = t_welch(other)
|
1226
|
-
td = TDistribution.new(compute_welch_df(other))
|
1227
|
-
t.abs < td.inverse_probability(1 - alpha.abs / 2.0)
|
1228
|
-
end
|
1229
|
-
|
1230
|
-
# Return the confidence interval for the arithmetic mean with alpha level +alpha+ of
|
1231
|
-
# the measurements of this Analysis instance as a Range object.
|
1232
|
-
def confidence_interval(alpha = 0.05)
|
1233
|
-
td = TDistribution.new(size - 1)
|
1234
|
-
t = td.inverse_probability(alpha / 2).abs
|
1235
|
-
delta = t * sample_standard_deviation / Math.sqrt(size)
|
1236
|
-
(arithmetic_mean - delta)..(arithmetic_mean + delta)
|
1237
|
-
end
|
1238
|
-
|
1239
|
-
# Returns the array of autovariances (of length size - 1).
|
1240
|
-
def autovariance
|
1241
|
-
Array.new(size - 1) do |k|
|
1242
|
-
s = 0.0
|
1243
|
-
0.upto(size - k - 1) do |i|
|
1244
|
-
s += (@measurements[i] - arithmetic_mean) * (@measurements[i + k] - arithmetic_mean)
|
1245
|
-
end
|
1246
|
-
s / size
|
1247
|
-
end
|
1248
|
-
end
|
1249
|
-
|
1250
|
-
# Returns the array of autocorrelation values c_k / c_0 (of length size -
|
1251
|
-
# 1).
|
1252
|
-
def autocorrelation
|
1253
|
-
c = autovariance
|
1254
|
-
Array.new(c.size) { |k| c[k] / c[0] }
|
1255
|
-
end
|
1256
|
-
|
1257
|
-
# Returns the d-value for the Durbin-Watson statistic. The value is d << 2
|
1258
|
-
# for positive, d >> 2 for negative and d around 2 for no autocorrelation.
|
1259
|
-
def durbin_watson_statistic
|
1260
|
-
e = linear_regression.residues
|
1261
|
-
e.size <= 1 and return 2.0
|
1262
|
-
(1...e.size).inject(0.0) { |s, i| s + (e[i] - e[i - 1]) ** 2 } /
|
1263
|
-
e.inject(0.0) { |s, x| s + x ** 2 }
|
1264
|
-
end
|
1265
|
-
|
1266
|
-
# Returns the q value of the Ljung-Box statistic for the number of lags
|
1267
|
-
# +lags+. A higher value might indicate autocorrelation in the measurements of
|
1268
|
-
# this Analysis instance. This method returns nil if there weren't enough
|
1269
|
-
# (at least lags) lags available.
|
1270
|
-
def ljung_box_statistic(lags = 20)
|
1271
|
-
r = autocorrelation
|
1272
|
-
lags >= r.size and return
|
1273
|
-
n = size
|
1274
|
-
n * (n + 2) * (1..lags).inject(0.0) { |s, i| s + r[i] ** 2 / (n - i) }
|
1275
|
-
end
|
1276
|
-
|
1277
|
-
# This method tries to detect autocorrelation with the Ljung-Box
|
1278
|
-
# statistic. If enough lags can be considered it returns a hash with
|
1279
|
-
# results, otherwise nil is returned. The keys are
|
1280
|
-
# :lags:: the number of lags,
|
1281
|
-
# :alpha_level:: the alpha level for the test,
|
1282
|
-
# :q:: the value of the ljung_box_statistic,
|
1283
|
-
# :p:: the p-value computed, if p is higher than alpha no correlation was detected,
|
1284
|
-
# :detected:: true if a correlation was found.
|
1285
|
-
def detect_autocorrelation(lags = 20, alpha_level = 0.05)
|
1286
|
-
if q = ljung_box_statistic(lags)
|
1287
|
-
p = ChiSquareDistribution.new(lags).probability(q)
|
1288
|
-
return {
|
1289
|
-
:lags => lags,
|
1290
|
-
:alpha_level => alpha_level,
|
1291
|
-
:q => q,
|
1292
|
-
:p => p,
|
1293
|
-
:detected => p >= 1 - alpha_level,
|
1294
|
-
}
|
1295
|
-
end
|
1296
|
-
end
|
1297
|
-
|
1298
|
-
# Return a result hash with the number of :very_low, :low, :high, and
|
1299
|
-
# :very_high outliers, determined by the box plotting algorithm run with
|
1300
|
-
# :median and :iqr parameters. If no outliers were found or the iqr is
|
1301
|
-
# less than epsilon, nil is returned.
|
1302
|
-
def detect_outliers(factor = 3.0, epsilon = 1E-5)
|
1303
|
-
half_factor = factor / 2.0
|
1304
|
-
quartile1 = percentile(25)
|
1305
|
-
quartile3 = percentile(75)
|
1306
|
-
iqr = quartile3 - quartile1
|
1307
|
-
iqr < epsilon and return
|
1308
|
-
result = @measurements.inject(Hash.new(0)) do |h, t|
|
1309
|
-
extreme =
|
1310
|
-
case t
|
1311
|
-
when -Infinity..(quartile1 - factor * iqr)
|
1312
|
-
:very_low
|
1313
|
-
when (quartile1 - factor * iqr)..(quartile1 - half_factor * iqr)
|
1314
|
-
:low
|
1315
|
-
when (quartile1 + half_factor * iqr)..(quartile3 + factor * iqr)
|
1316
|
-
:high
|
1317
|
-
when (quartile3 + factor * iqr)..Infinity
|
1318
|
-
:very_high
|
1319
|
-
end and h[extreme] += 1
|
1320
|
-
h
|
1321
|
-
end
|
1322
|
-
unless result.empty?
|
1323
|
-
result[:median] = median
|
1324
|
-
result[:iqr] = iqr
|
1325
|
-
result[:factor] = factor
|
1326
|
-
result
|
1327
|
-
end
|
1328
|
-
end
|
1329
|
-
|
1330
|
-
# Returns the LinearRegression object for the equation a * x + b which
|
1331
|
-
# represents the line computed by the linear regression algorithm.
|
1332
|
-
def linear_regression
|
1333
|
-
@linear_regression ||= LinearRegression.new @measurements
|
1334
|
-
end
|
1335
|
-
|
1336
|
-
# Returns a Histogram instance with +bins+ as the number of bins for this
|
1337
|
-
# analysis' measurements.
|
1338
|
-
def histogram(bins)
|
1339
|
-
Histogram.new(self, bins)
|
1340
|
-
end
|
1341
|
-
end
|
1342
|
-
|
1343
410
|
CaseMethod = Struct.new(:name, :case, :clock)
|
1344
411
|
|
1345
412
|
# This class' instance represents a method to be benchmarked.
|