compsci 0.3.0.1 → 0.3.2.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.
- checksums.yaml +5 -5
- data/README.md +138 -79
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/compsci.gemspec +2 -2
- data/examples/binary_search_tree.rb +43 -8
- data/examples/complete_tree.rb +21 -22
- data/examples/flex_node.rb +117 -0
- data/examples/heap.rb +40 -2
- data/examples/heap_push.rb +7 -1
- data/examples/ternary_search_tree.rb +30 -0
- data/examples/tree.rb +27 -25
- data/lib/compsci/complete_tree.rb +6 -7
- data/lib/compsci/fit.rb +17 -0
- data/lib/compsci/flex_node.rb +90 -0
- data/lib/compsci/heap.rb +1 -2
- data/lib/compsci/names/pokemon.rb +62 -0
- data/lib/compsci/node.rb +109 -59
- data/lib/compsci/simplex/parse.rb +10 -6
- data/lib/compsci/simplex.rb +19 -20
- data/lib/compsci/timer.rb +61 -1
- data/lib/compsci.rb +10 -1
- data/test/bench/fibonacci.rb +29 -128
- data/test/bench/flex_node.rb +30 -0
- data/test/complete_tree.rb +16 -14
- data/test/compsci.rb +25 -0
- data/test/fibonacci.rb +6 -5
- data/test/fit.rb +84 -42
- data/test/flex_node.rb +226 -0
- data/test/heap.rb +46 -46
- data/test/names.rb +95 -56
- data/test/node.rb +177 -85
- data/test/simplex_parse.rb +23 -15
- data/test/timer.rb +10 -10
- metadata +17 -17
- data/examples/tree_push.rb +0 -72
- data/lib/compsci/binary_search_tree.rb +0 -86
- data/lib/compsci/tree.rb +0 -142
- data/test/bench/tree.rb +0 -31
- data/test/binary_search_tree.rb +0 -98
- data/test/tree.rb +0 -200
data/lib/compsci/timer.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module CompSci
|
2
|
-
|
2
|
+
class Timer
|
3
|
+
SECS_PER_MIN = 60
|
4
|
+
MINS_PER_HOUR = 60
|
5
|
+
SECS_PER_HOUR = SECS_PER_MIN * MINS_PER_HOUR
|
6
|
+
|
3
7
|
# lifted from seattlerb/minitest
|
4
8
|
if defined? Process::CLOCK_MONOTONIC
|
5
9
|
def self.now
|
@@ -32,5 +36,61 @@ module CompSci
|
|
32
36
|
}
|
33
37
|
return val, self.since(start) / i.to_f
|
34
38
|
end
|
39
|
+
|
40
|
+
# YYYY-MM-DD HH::MM::SS.mmm
|
41
|
+
def self.timestamp(t)
|
42
|
+
t.strftime "%Y-%m-%d %H:%M:%S.%L"
|
43
|
+
end
|
44
|
+
|
45
|
+
# HH::MM::SS.mmm.uuuuuuuu
|
46
|
+
def self.elapsed_display(elapsed_ms, show_us: false)
|
47
|
+
elapsed_s, ms = elapsed_ms.divmod 1000
|
48
|
+
ms_only, ms_fraction = ms.round(8).divmod 1
|
49
|
+
|
50
|
+
h = elapsed_s / SECS_PER_HOUR
|
51
|
+
elapsed_s -= h * SECS_PER_HOUR
|
52
|
+
m, s = elapsed_s.divmod SECS_PER_MIN
|
53
|
+
|
54
|
+
hmsms = [[h, m, s].map { |i| i.to_s.rjust(2, '0') }.join(':'),
|
55
|
+
ms_only.to_s.rjust(3, '0')]
|
56
|
+
hmsms << (ms_fraction * 10 ** 8).round.to_s.ljust(8, '0') if show_us
|
57
|
+
hmsms.join('.')
|
58
|
+
end
|
59
|
+
|
60
|
+
def restart(t = Time.now)
|
61
|
+
@start = t
|
62
|
+
self
|
63
|
+
end
|
64
|
+
alias_method :initialize, :restart
|
65
|
+
|
66
|
+
def timestamp(t = Time.now)
|
67
|
+
self.class.timestamp t
|
68
|
+
end
|
69
|
+
|
70
|
+
def timestamp!(t = Time.now)
|
71
|
+
puts '-' * 70, timestamp(t), '-' * 70
|
72
|
+
end
|
73
|
+
|
74
|
+
def elapsed(t = Time.now)
|
75
|
+
t - @start
|
76
|
+
end
|
77
|
+
|
78
|
+
def elapsed_ms(t = Time.now)
|
79
|
+
elapsed(t) * 1000
|
80
|
+
end
|
81
|
+
|
82
|
+
def elapsed_display(t = Time.now)
|
83
|
+
self.class.elapsed_display(elapsed_ms(t))
|
84
|
+
end
|
85
|
+
alias_method :to_s, :elapsed_display
|
86
|
+
alias_method :inspect, :elapsed_display
|
87
|
+
|
88
|
+
def stamp(msg = '', t = Time.now)
|
89
|
+
format("%s %s", elapsed_display(t), msg)
|
90
|
+
end
|
91
|
+
|
92
|
+
def stamp!(msg = '', t = Time.now)
|
93
|
+
puts stamp(msg, t)
|
94
|
+
end
|
35
95
|
end
|
36
96
|
end
|
data/lib/compsci.rb
CHANGED
@@ -1 +1,10 @@
|
|
1
|
-
module CompSci
|
1
|
+
module CompSci
|
2
|
+
# thanks apeiros
|
3
|
+
# https://gist.github.com/rickhull/d0b579aa08c85430b0dc82a791ff12d6
|
4
|
+
def self.power_of?(num, base)
|
5
|
+
return false if base <= 1
|
6
|
+
mod = 0
|
7
|
+
num, mod = num.divmod(base) until num == 1 || mod > 0
|
8
|
+
mod == 0
|
9
|
+
end
|
10
|
+
end
|
data/test/bench/fibonacci.rb
CHANGED
@@ -1,143 +1,44 @@
|
|
1
1
|
require 'compsci/fibonacci'
|
2
|
-
require '
|
3
|
-
require 'minitest/benchmark'
|
2
|
+
require 'benchmark/ips'
|
4
3
|
|
5
4
|
include CompSci
|
6
5
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# CACHE_RANGE = [100, 1000, 10000, 100000, 112500, 125000]
|
12
|
-
CACHE_RANGE = [100, 1000, 10000, 100000]
|
6
|
+
# recursive benchmarks with low N; iterative for comparison
|
7
|
+
Benchmark.ips do |b|
|
8
|
+
b.config time: 3, warmup: 0.5
|
9
|
+
num = 25
|
13
10
|
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
MATRIX_RANGE = [100, 1000, 10000, 100000]
|
11
|
+
b.report("Fibonacci.classic(#{num})") {
|
12
|
+
Fibonacci.classic(num)
|
13
|
+
}
|
18
14
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
SPEC_BENCHMARK = false
|
23
|
-
CLASS_BENCHMARK = false
|
24
|
-
#BENCHMARK_IPS = false
|
15
|
+
b.report("Fibonacci.cache_recursive(#{num})") {
|
16
|
+
Fibonacci.cache_recursive(num)
|
17
|
+
}
|
25
18
|
|
19
|
+
b.report("Fibonacci.cache_iterative(#{num})") {
|
20
|
+
Fibonacci.cache_iterative(num)
|
21
|
+
}
|
26
22
|
|
27
|
-
|
28
|
-
describe "Fibonacci.classic Benchmark" do
|
29
|
-
bench_range do
|
30
|
-
CLASSIC_RANGE
|
31
|
-
end
|
32
|
-
|
33
|
-
fc = ["Fibonacci.classic (exponential, 0.95)", 0.95]
|
34
|
-
bench_performance_exponential(*fc) do |n|
|
35
|
-
Fibonacci.classic(n)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "Fibonacci.cache_recursive Benchmark" do
|
40
|
-
bench_range do
|
41
|
-
RECURSIVE_RANGE
|
42
|
-
end
|
43
|
-
|
44
|
-
fcr = ["Fibonacci.cache_recursive (linear, 0.95)", 0.95]
|
45
|
-
bench_performance_linear(*fcr) do |n|
|
46
|
-
Fibonacci.cache_recursive(n)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "Fibonacci.cache_iterative Benchmark" do
|
51
|
-
bench_range do
|
52
|
-
CACHE_RANGE
|
53
|
-
end
|
54
|
-
|
55
|
-
fci = ["Fibonacci.cache_iterative (linear, 0.99)", 0.99]
|
56
|
-
bench_performance_linear(*fci) do |n|
|
57
|
-
Fibonacci.cache_iterative(n)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
describe "Fibonacci.dynamic Benchmark" do
|
62
|
-
bench_range do
|
63
|
-
DYNAMIC_RANGE
|
64
|
-
end
|
65
|
-
|
66
|
-
fd = ["Fibonacci.dynamic (linear, 0.99)", 0.99]
|
67
|
-
bench_performance_linear(*fd) do |n|
|
68
|
-
Fibonacci.dynamic(n)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "Fibonacci.matrix Benchmark" do
|
73
|
-
bench_range do
|
74
|
-
MATRIX_RANGE
|
75
|
-
end
|
76
|
-
|
77
|
-
fd = ["Fibonacci.matrix (linear, 0.99)", 0.99]
|
78
|
-
bench_performance_linear(*fd) do |n|
|
79
|
-
Fibonacci.matrix(n)
|
80
|
-
end
|
81
|
-
end
|
23
|
+
b.compare!
|
82
24
|
end
|
83
25
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
def bench_fib
|
89
|
-
times = CLASSIC_RANGE.map { |n|
|
90
|
-
_answer, elapsed = Timer.elapsed { Fibonacci.classic(n) }
|
91
|
-
elapsed
|
92
|
-
}
|
93
|
-
_a, _b, r2 = self.fit_exponential(CLASSIC_RANGE, times)
|
94
|
-
puts
|
95
|
-
puts "self-timed Fibonacci.classic(n) exponential fit: %0.3f" % r2
|
96
|
-
puts
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
if BENCHMARK_IPS
|
102
|
-
require 'benchmark/ips'
|
103
|
-
|
104
|
-
# recursive benchmarks with low N; iterative for comparison
|
105
|
-
Benchmark.ips do |b|
|
106
|
-
b.config time: 3, warmup: 0.5
|
107
|
-
num = 25
|
108
|
-
|
109
|
-
b.report("Fibonacci.classic(#{num})") {
|
110
|
-
Fibonacci.classic(num)
|
111
|
-
}
|
112
|
-
|
113
|
-
b.report("Fibonacci.cache_recursive(#{num})") {
|
114
|
-
Fibonacci.cache_recursive(num)
|
115
|
-
}
|
116
|
-
|
117
|
-
b.report("Fibonacci.cache_iterative(#{num})") {
|
118
|
-
Fibonacci.cache_iterative(num)
|
119
|
-
}
|
120
|
-
|
121
|
-
b.compare!
|
122
|
-
end
|
123
|
-
|
124
|
-
# nonrecursive benchmarks with high N
|
125
|
-
Benchmark.ips do |b|
|
126
|
-
b.config time: 3, warmup: 0.5
|
127
|
-
num = 500
|
26
|
+
# nonrecursive benchmarks with high N
|
27
|
+
Benchmark.ips do |b|
|
28
|
+
b.config time: 3, warmup: 0.5
|
29
|
+
num = 500
|
128
30
|
|
129
|
-
|
130
|
-
|
131
|
-
|
31
|
+
b.report("Fibonacci.cache_iterative(#{num})") {
|
32
|
+
Fibonacci.cache_iterative(num)
|
33
|
+
}
|
132
34
|
|
133
|
-
|
134
|
-
|
135
|
-
|
35
|
+
b.report("Fibonacci.dynamic(#{num})") {
|
36
|
+
Fibonacci.dynamic(num)
|
37
|
+
}
|
136
38
|
|
137
|
-
|
138
|
-
|
139
|
-
|
39
|
+
b.report("Fibonacci.matrix(#{num})") {
|
40
|
+
Fibonacci.matrix(num)
|
41
|
+
}
|
140
42
|
|
141
|
-
|
142
|
-
end
|
43
|
+
b.compare!
|
143
44
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'compsci/flex_node'
|
2
|
+
require 'benchmark/ips'
|
3
|
+
|
4
|
+
include CompSci
|
5
|
+
|
6
|
+
Benchmark.ips do |b|
|
7
|
+
b.config time: 3, warmup: 0.5
|
8
|
+
|
9
|
+
b.report("99x Binary ChildFlexNode#push") do
|
10
|
+
@root = ChildFlexNode.new 42
|
11
|
+
99.times { @root.push rand(99), 2 }
|
12
|
+
end
|
13
|
+
|
14
|
+
b.report("99x Binary FlexNode#push") do
|
15
|
+
@root = FlexNode.new 42
|
16
|
+
99.times { @root.push rand(99), 2 }
|
17
|
+
end
|
18
|
+
|
19
|
+
b.report("99x Ternary ChildFlexNode#push") do
|
20
|
+
@root = ChildFlexNode.new 42
|
21
|
+
99.times { @root.push rand(99), 3 }
|
22
|
+
end
|
23
|
+
|
24
|
+
b.report("99x Ternary FlexNode#push") do
|
25
|
+
@root = FlexNode.new 42
|
26
|
+
99.times { @root.push rand(99), 3 }
|
27
|
+
end
|
28
|
+
|
29
|
+
b.compare!
|
30
|
+
end
|
data/test/complete_tree.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'compsci/complete_tree'
|
2
2
|
require 'minitest/autorun'
|
3
3
|
|
4
|
+
Minitest::Test.parallelize_me!
|
5
|
+
|
4
6
|
include CompSci
|
5
7
|
|
6
|
-
describe
|
8
|
+
describe CompleteTree do
|
7
9
|
it "must calculate a parent index for N=2" do
|
8
10
|
valid = {
|
9
11
|
1 => 0,
|
@@ -24,10 +26,10 @@ describe CompleteNaryTree do
|
|
24
26
|
-2 => -2,
|
25
27
|
}
|
26
28
|
valid.each { |idx, pidx|
|
27
|
-
|
29
|
+
expect(CompleteTree.parent_idx(idx, 2)).must_equal pidx
|
28
30
|
}
|
29
31
|
invalid.each { |idx, pidx|
|
30
|
-
|
32
|
+
expect(CompleteTree.parent_idx(idx, 2)).must_equal pidx
|
31
33
|
}
|
32
34
|
end
|
33
35
|
|
@@ -53,10 +55,10 @@ describe CompleteNaryTree do
|
|
53
55
|
}
|
54
56
|
|
55
57
|
valid.each { |idx, cidx|
|
56
|
-
|
58
|
+
expect(CompleteTree.children_idx(idx, 2)).must_equal cidx
|
57
59
|
}
|
58
60
|
invalid.each { |idx, cidx|
|
59
|
-
|
61
|
+
expect(CompleteTree.children_idx(idx, 2)).must_equal cidx
|
60
62
|
}
|
61
63
|
end
|
62
64
|
|
@@ -80,10 +82,10 @@ describe CompleteNaryTree do
|
|
80
82
|
-2 => -1,
|
81
83
|
}
|
82
84
|
valid.each { |idx, pidx|
|
83
|
-
|
85
|
+
expect(CompleteTree.parent_idx(idx, 3)).must_equal pidx
|
84
86
|
}
|
85
87
|
invalid.each { |idx, pidx|
|
86
|
-
|
88
|
+
expect(CompleteTree.parent_idx(idx, 3)).must_equal pidx
|
87
89
|
}
|
88
90
|
end
|
89
91
|
|
@@ -102,28 +104,28 @@ describe CompleteNaryTree do
|
|
102
104
|
}
|
103
105
|
|
104
106
|
valid.each { |idx, cidx|
|
105
|
-
|
107
|
+
expect(CompleteTree.children_idx(idx, 3)).must_equal cidx
|
106
108
|
}
|
107
109
|
invalid.each { |idx, cidx|
|
108
|
-
|
110
|
+
expect(CompleteTree.children_idx(idx, 3)).must_equal cidx
|
109
111
|
}
|
110
112
|
end
|
111
113
|
|
112
114
|
describe "instance" do
|
113
115
|
before do
|
114
116
|
@array = (0..99).sort_by { rand }
|
115
|
-
@empty =
|
117
|
+
@empty = CompleteTree.new(child_slots: 5)
|
116
118
|
@nonempty = CompleteQuaternaryTree.new(array: @array)
|
117
119
|
end
|
118
120
|
|
119
121
|
it "must have a size" do
|
120
|
-
@empty.size.must_equal 0
|
121
|
-
@nonempty.size.must_equal @array.size
|
122
|
+
expect(@empty.size).must_equal 0
|
123
|
+
expect(@nonempty.size).must_equal @array.size
|
122
124
|
end
|
123
125
|
|
124
126
|
it "must have a last_idx, nil when empty" do
|
125
|
-
@empty.last_idx.nil
|
126
|
-
@nonempty.last_idx.must_equal 99
|
127
|
+
expect(@empty.last_idx.nil?).must_equal true
|
128
|
+
expect(@nonempty.last_idx).must_equal 99
|
127
129
|
end
|
128
130
|
|
129
131
|
# TODO: push, pop, display
|
data/test/compsci.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'compsci'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
Minitest::Test.parallelize_me!
|
5
|
+
|
6
|
+
describe CompSci do
|
7
|
+
it "must determine if num is a power of base" do
|
8
|
+
powers = {}
|
9
|
+
basemax = 12
|
10
|
+
expmax = 10
|
11
|
+
2.upto(basemax) { |base|
|
12
|
+
0.upto(expmax) { |exp|
|
13
|
+
powers[base] ||= []
|
14
|
+
powers[base] << base**exp
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
# 12k assertions below!
|
19
|
+
2.upto(basemax) { |base|
|
20
|
+
1.upto(2**expmax) { |num|
|
21
|
+
expect(CompSci.power_of?(num, base)).must_equal powers[base].include?(num)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
data/test/fibonacci.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'compsci/fibonacci'
|
2
2
|
require 'minitest/autorun'
|
3
3
|
|
4
|
+
Minitest::Test.parallelize_me!
|
5
|
+
|
4
6
|
include CompSci
|
5
7
|
|
6
8
|
describe Fibonacci do
|
@@ -10,11 +12,10 @@ describe Fibonacci do
|
|
10
12
|
|
11
13
|
it "must calculate fib(0..10)" do
|
12
14
|
@answers.each_with_index { |ans, i|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Fibonacci.matrix(i).must_equal ans
|
15
|
+
[:classic, :cache_recursive, :cache_iterative,
|
16
|
+
:dynamic, :matrix].each { |m|
|
17
|
+
expect(Fibonacci.send(m, i)).must_equal ans
|
18
|
+
}
|
18
19
|
}
|
19
20
|
end
|
20
21
|
end
|
data/test/fit.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'compsci/fit'
|
2
2
|
require 'minitest/autorun'
|
3
3
|
|
4
|
+
Minitest::Test.parallelize_me!
|
5
|
+
|
4
6
|
include CompSci
|
5
7
|
|
6
8
|
def noise # range: -0.5 to 0.5
|
@@ -13,16 +15,18 @@ describe Fit do
|
|
13
15
|
end
|
14
16
|
|
15
17
|
describe "Fit.sigma" do
|
16
|
-
it "
|
17
|
-
Fit.sigma([1, 2, 3]).must_equal 6
|
18
|
-
Fit.sigma([1, 2, 3]) { |n| n ** 2 }.must_equal 14
|
18
|
+
it "answers correctly" do
|
19
|
+
expect(Fit.sigma([1, 2, 3])).must_equal 6
|
20
|
+
expect(Fit.sigma([1, 2, 3]) { |n| n ** 2 }).must_equal 14
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
24
|
describe "Fit.error" do
|
23
|
-
it "
|
24
|
-
Fit.error([[1, 1], [2, 2], [3, 3]]) { |x| x }.must_equal 1.0
|
25
|
-
Fit.error([[1, 1], [2, 2], [3, 4]]) { |x|
|
25
|
+
it "calculates r^2" do
|
26
|
+
expect(Fit.error([[1, 1], [2, 2], [3, 3]]) { |x| x }).must_equal 1.0
|
27
|
+
expect(Fit.error([[1, 1], [2, 2], [3, 4]]) { |x|
|
28
|
+
x
|
29
|
+
}).must_be_close_to 0.785
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
@@ -32,24 +36,24 @@ describe Fit do
|
|
32
36
|
# alternate measure. A low slope and r2 for linear fit, maybe.
|
33
37
|
#
|
34
38
|
describe "Fit.constant" do
|
35
|
-
it "
|
39
|
+
it "returns zero variance with truly constant inputs" do
|
36
40
|
[0, 1, 10, 100, 1000, 9999].each { |a|
|
37
41
|
y_bar, variance = Fit.constant(@xs, @xs.map { |x| a })
|
38
|
-
y_bar.must_equal a
|
39
|
-
variance.must_equal 0
|
42
|
+
expect(y_bar).must_equal a
|
43
|
+
expect(variance).must_equal 0
|
40
44
|
}
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
# y = a + b*ln(x)
|
45
49
|
describe "Fit.logarithmic" do
|
46
|
-
it "
|
50
|
+
it "accepts logarithmic data" do
|
47
51
|
[-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |a|
|
48
52
|
[-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |b|
|
49
53
|
ary = Fit.logarithmic(@xs, @xs.map { |x| a + b * Math.log(x) })
|
50
|
-
ary[0].must_be_close_to a
|
51
|
-
ary[1].must_be_close_to b
|
52
|
-
ary[2].must_equal 1.0
|
54
|
+
expect(ary[0]).must_be_close_to a
|
55
|
+
expect(ary[1]).must_be_close_to b
|
56
|
+
expect(ary[2]).must_equal 1.0
|
53
57
|
}
|
54
58
|
}
|
55
59
|
end
|
@@ -57,65 +61,63 @@ describe Fit do
|
|
57
61
|
|
58
62
|
# y = a + bx
|
59
63
|
describe "Fit.linear" do
|
60
|
-
it "
|
64
|
+
it "accepts linear data" do
|
61
65
|
[-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |a|
|
62
66
|
[-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |b|
|
63
67
|
ary = Fit.linear(@xs, @xs.map { |x| a + b * x })
|
64
|
-
ary[0].must_be_close_to a
|
65
|
-
ary[1].must_be_close_to b
|
66
|
-
ary[2].must_equal 1.0
|
68
|
+
expect(ary[0]).must_be_close_to a
|
69
|
+
expect(ary[1]).must_be_close_to b
|
70
|
+
expect(ary[2]).must_equal 1.0
|
67
71
|
}
|
68
72
|
}
|
69
73
|
end
|
70
74
|
|
71
|
-
it "
|
75
|
+
it "accepts constant data" do
|
72
76
|
[0, 1, 10, 100, 1000, 9999].each { |a|
|
73
77
|
ary = Fit.linear(@xs, @xs.map { |x| a })
|
74
|
-
ary[0].must_equal a
|
75
|
-
ary[1].must_equal 0
|
76
|
-
ary[2].nan
|
78
|
+
expect(ary[0]).must_equal a
|
79
|
+
expect(ary[1]).must_equal 0
|
80
|
+
expect(ary[2].nan?).must_equal true
|
77
81
|
}
|
78
82
|
end
|
79
83
|
|
80
84
|
# note, this test can possibly fail depending on the uniformity of
|
81
85
|
# rand's output for our sample
|
82
86
|
#
|
83
|
-
it "
|
87
|
+
it "accepts noisy constant data" do
|
84
88
|
r2s = []
|
85
89
|
[0, 1, 10, 100, 1000, 9999].each { |a|
|
86
90
|
ary = Fit.linear(@xs, @xs.map { |x| a + noise() })
|
87
|
-
ary[0].must_be_close_to a, 0.4
|
88
|
-
ary[1].must_be_close_to 0, 0.05
|
91
|
+
expect(ary[0]).must_be_close_to a, 0.4
|
92
|
+
expect(ary[1]).must_be_close_to 0, 0.05
|
89
93
|
r2s << ary[2]
|
90
94
|
}
|
91
95
|
mean_r2 = Fit.sigma(r2s) / r2s.size
|
92
|
-
mean_r2.must_be_close_to 0.15, 0.15
|
96
|
+
expect(mean_r2).must_be_close_to 0.15, 0.15
|
93
97
|
end
|
94
98
|
|
95
|
-
it "
|
96
|
-
|
97
|
-
xs = [1, 10, 100, 1000]
|
99
|
+
it "rejects x^2" do
|
100
|
+
xs = Array.new(99) { |i| i }
|
98
101
|
_a, _b, r2 = Fit.linear(xs, xs.map { |x| x**2 })
|
99
|
-
r2.must_be :<, 0.99
|
102
|
+
expect(r2).must_be :<, 0.99
|
100
103
|
end
|
101
104
|
|
102
|
-
it "
|
103
|
-
|
104
|
-
xs = [1, 10, 100, 1000]
|
105
|
+
it "rejects x^3" do
|
106
|
+
xs = Array.new(99) { |i| i }
|
105
107
|
_a, _b, r2 = Fit.linear(xs, xs.map { |x| x**3 })
|
106
|
-
r2.must_be :<, 0.99
|
108
|
+
expect(r2).must_be :<, 0.99
|
107
109
|
end
|
108
110
|
end
|
109
111
|
|
110
112
|
# y = ae^(bx)
|
111
113
|
describe "Fit.exponential" do
|
112
|
-
it "
|
114
|
+
it "accepts exponential data" do
|
113
115
|
[0.001, 7.5, 500, 1000, 5000, 9999].each { |a|
|
114
116
|
[-1.4, -1.1, -0.1, 0.01, 0.5, 0.75].each { |b|
|
115
117
|
ary = Fit.exponential(@xs, @xs.map { |x| a * Math::E**(b * x) })
|
116
|
-
ary[0].must_be_close_to a
|
117
|
-
ary[1].must_be_close_to b
|
118
|
-
ary[2].must_equal 1.0
|
118
|
+
expect(ary[0]).must_be_close_to a
|
119
|
+
expect(ary[1]).must_be_close_to b
|
120
|
+
expect(ary[2]).must_equal 1.0
|
119
121
|
}
|
120
122
|
}
|
121
123
|
end
|
@@ -123,16 +125,56 @@ describe Fit do
|
|
123
125
|
|
124
126
|
# y = ax^b
|
125
127
|
describe "Fit.power" do
|
126
|
-
it "
|
128
|
+
it "accepts power data" do
|
127
129
|
[0.01, 7.5, 500, 1000, 5000, 9999].each { |a|
|
128
130
|
[-114, -100, -10, -0.5, -0.1, 0.1, 0.75, 10, 50, 60].each { |b|
|
129
|
-
|
131
|
+
# [ -100, -10, -0.5, -0.1, 0.1, 0.75, 10, 50, 60].each { |b|
|
132
|
+
# note: on Ruby 2.4.x and older, b == -114 throws
|
133
|
+
# warning: Bignum out of Float range
|
134
|
+
# also: TruffleRuby as of Jan '22: ary[2] is NaN rather than 1.0
|
130
135
|
ary = Fit.power(@xs, @xs.map { |x| a * x**b })
|
131
|
-
ary[0].must_be_close_to a
|
132
|
-
ary[1].must_be_close_to b
|
133
|
-
ary[2].must_equal 1.0
|
136
|
+
expect(ary[0]).must_be_close_to a
|
137
|
+
expect(ary[1]).must_be_close_to b
|
138
|
+
expect(ary[2]).must_equal 1.0
|
134
139
|
}
|
135
140
|
}
|
136
141
|
end
|
137
142
|
end
|
143
|
+
|
144
|
+
describe "Fit.predict" do
|
145
|
+
before do
|
146
|
+
@a, @b, @x = 1, 2, 3
|
147
|
+
end
|
148
|
+
|
149
|
+
it "accepts a few different models" do
|
150
|
+
[:constant, :logarithmic, :linear, :exponential, :power].each { |model|
|
151
|
+
expect(Fit.predict(model, @a, @b, @x)).must_be_kind_of(Numeric)
|
152
|
+
}
|
153
|
+
expect { Fit.predict(:invalid, @a, @b, @x) }.must_raise RuntimeError
|
154
|
+
end
|
155
|
+
|
156
|
+
it "predicts constant relationships" do
|
157
|
+
expect(Fit.predict(:constant, @a, @b, @x)).must_equal @a
|
158
|
+
expect(Fit.predict(:constant, @a, @x, @b)).must_equal @a
|
159
|
+
expect(Fit.predict(:constant, @x, @a, @b)).must_equal @x
|
160
|
+
end
|
161
|
+
|
162
|
+
it "predicts logarithmic relationships" do
|
163
|
+
expect(Fit.predict(:logarithmic, @a, @b, @x)
|
164
|
+
).must_equal @a + @b * Math.log(@x)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "predicts linear relationships" do
|
168
|
+
expect(Fit.predict(:linear, @a, @b, @x)).must_equal @a + @b * @x
|
169
|
+
end
|
170
|
+
|
171
|
+
it "predicts exponential relationships" do
|
172
|
+
expect(Fit.predict(:exponential, @a, @b, @x)
|
173
|
+
).must_equal @a * Math::E ** (@b * @x)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "predicts power relationships" do
|
177
|
+
expect(Fit.predict(:power, @a, @b, @x)).must_equal @a * @x ** @b
|
178
|
+
end
|
179
|
+
end
|
138
180
|
end
|