time_series_math 0.1.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: 85c802a00527e11700194fcbd661d85943bcd26a
4
+ data.tar.gz: 4cd75e47704fbecd1557cba39cb6fa44f32e29f7
5
+ SHA512:
6
+ metadata.gz: 83e1bb3b5dc55d5f83bfb8ad6abe9d7f8b14f3f2a4acdadc9717968f699d425d7b966d81b5507adf443eb080e5434d939a237195de9daaf007cad565ed808e78
7
+ data.tar.gz: fafb01c6289d5065f7dab7ee7a33f2f8e5e63b7cd88a0cdab343c6a1833ef1a4c8ff26f0b77ce70ce075598865d035e392898b24332b9caf41d9b9b145ab94af
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kukushkin-time_series.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Alex Kukushkin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,64 @@
1
+ # TimeSeriesMath
2
+
3
+ Time series data structures and functions.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'time_series_math'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install time_series_math
18
+
19
+ ## Usage
20
+
21
+ ### TimeSeries
22
+
23
+ `TimeSeries` class is a data structure for storing timestamped values.
24
+
25
+ TimeSeries maintains order of its elements and provides efficient search methods
26
+ for near-constant time access
27
+ (depends strongly on timestamps distribution -- the more even, the better).
28
+
29
+ Examples:
30
+
31
+ ``` ruby
32
+ require 'time_series_math'
33
+
34
+ include TimeSeriesMath
35
+
36
+ # one by one element insertion:
37
+ ts = TimeSeries.new
38
+ ts.push 1.0, { x: 2.0, y: 3.0 }
39
+ ts.push 1.2, { x: 2.0, y: 3.0 }
40
+ ts.push 1.6, { x: 2.0, y: 3.0 }
41
+ ts.push 1.9, { x: 2.0, y: 3.0 }
42
+ ts.push 2.1, { x: 2.0, y: 3.0 }
43
+ # .. or:
44
+ ts[2.3] = { x: 2.5, y: 3.5 }
45
+ ts[2.5] = { x: 2.2, y: 3.7 }
46
+
47
+ # more time-efficient batch insertion using arrays:
48
+ ts = TimeSeries.new
49
+ tt = [ 1.0, 1.2, 1.6, 1.9, 2.1 ]
50
+ dd = [ {x: 2.0}, {x: 2.1}, {x: 2.5}, {x: 2.7}, {x: 2.85} ]
51
+ ts.push_array(tt, dd)
52
+
53
+ # retrieve closest element before given time:
54
+ ts[1.2] # => { x: 2.1 }
55
+ ts[2.095] # => { x: 2.7 }
56
+ ```
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it ( https://github.com/kukushkin/time_series_math/fork )
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require 'rake/extensiontask'
7
+ spec = Gem::Specification.load('time_series_math.gemspec')
8
+ Rake::ExtensionTask.new('time_series_math_c', spec)
9
+
10
+ task spec: :compile
@@ -0,0 +1,75 @@
1
+ $LOAD_PATH.unshift '../lib'
2
+ require 'time_series_math'
3
+ require 'benchmark'
4
+
5
+ include TimeSeriesMath
6
+
7
+ def t_rand
8
+ rand * 1000.0
9
+ end
10
+
11
+ def b_search(ts, t)
12
+ return nil if t < ts.t_first
13
+ return ts.last if t >= ts.t_last
14
+ ileft = 0
15
+ iright = ts.size-1
16
+ while iright - ileft > 1
17
+ icenter = ileft + (iright - ileft) /2
18
+ if t >= ts.data[icenter][0]
19
+ ileft = icenter
20
+ else
21
+ iright = icenter
22
+ end
23
+ end
24
+ if iright - ileft != 1
25
+ fail "ileft:#{ileft} iright:#{iright}"
26
+ elsif ts.data[ileft][0] > t || ts.data[iright][0] <= t
27
+ puts "(ts: t_first:#{ts.t_first} t_last:#{ts.t_last}"
28
+ fail "t:#{t}, t_left:#{ts.data[ileft][0]}, t_right:#{ts.data[iright][0]}"
29
+ end
30
+ v = ts.data[ileft]
31
+ end
32
+
33
+ def b_search_native(ts, t)
34
+ v_right = ts.data.bsearch { |d| d[0] > t }
35
+ v_right.nil? ? ts.last : v_right[1]
36
+ end
37
+
38
+ TS_N = [1_000, 100_000, 1_000_000]
39
+ TESTS = 1000_000
40
+ ts_list = []
41
+
42
+ TS_N.each do |num_samples|
43
+ puts "** initializing time series with #{num_samples} samples"
44
+ tt = []
45
+ dd = []
46
+ num_samples.times do
47
+ tt << t_rand
48
+ dd << { x: rand * 10.0 }
49
+ end
50
+ ts_list << TimeSeries.new(tt, dd)
51
+ end
52
+
53
+ puts '** running benchmarks'
54
+ Benchmark.bmbm do |b|
55
+ ts_list.each do |ts|
56
+ b.report "size: #{ts.size}, running #{TESTS} times #indices_at" do
57
+ TESTS.times { ts.indices_at(t_rand) }
58
+ end
59
+ end
60
+ ts_list.each do |ts|
61
+ b.report "size: #{ts.size}, running #{TESTS} times #bsearch_indices_at" do
62
+ TESTS.times { ts.bsearch_indices_at(t_rand) }
63
+ end
64
+ end
65
+ ts_list.each do |ts|
66
+ b.report "size: #{ts.size}, running #{TESTS} times #b_search" do
67
+ TESTS.times { b_search(ts, t_rand) }
68
+ end
69
+ end
70
+ ts_list.each do |ts|
71
+ b.report "size: #{ts.size}, running #{TESTS} times #b_search_native" do
72
+ TESTS.times { b_search_native(ts, t_rand) }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+ extension_name = 'time_series_math_c'
3
+ dir_config(extension_name)
4
+ create_makefile(extension_name)
@@ -0,0 +1,80 @@
1
+ #include <ruby.h>
2
+
3
+ // Returns timestamp at index +i+.
4
+ //
5
+ static double timestamp_at_i(VALUE rb_data_array, int i ) {
6
+ VALUE m_elem;
7
+
8
+ m_elem = rb_ary_entry(rb_data_array, i);
9
+ return NUM2DBL( rb_ary_entry(m_elem, 0) );
10
+ }
11
+
12
+ // Returns indices of elements surrounding timestamp +t+.
13
+ // The pair of indices is found using binary search over sorted @data array.
14
+ //
15
+ // Ruby equivalent:
16
+ //
17
+ // def indices_at(t)
18
+ // return [nil, nil] if size == 0
19
+ // return [nil, 0] if t < t_first
20
+ // return [size - 1, nil] if t >= t_last
21
+ // ileft = 0
22
+ // iright = size - 1
23
+ // while iright - ileft > 1
24
+ // icenter = ileft + (iright - ileft) / 2
25
+ // if t >= data[icenter][0]
26
+ // ileft = icenter
27
+ // else
28
+ // iright = icenter
29
+ // end
30
+ // end
31
+ // [ileft, iright]
32
+ // end
33
+ //
34
+ static VALUE time_series_bsearch_indices_at(VALUE rb_self, VALUE rb_t) {
35
+ VALUE m_data;
36
+ int _size;
37
+ double _t;
38
+ int _ileft, _iright, _icenter;
39
+
40
+ m_data = rb_iv_get(rb_self, "@data");
41
+ _size = RARRAY_LEN(m_data);
42
+ _t = NUM2DBL(rb_t);
43
+
44
+ // initial conditions check:
45
+ if ( _size == 0 ) {
46
+ return rb_ary_new3( 2, Qnil, Qnil );
47
+ }
48
+ if ( _t < timestamp_at_i( m_data, 0 ) ) {
49
+ return rb_ary_new3( 2, Qnil, INT2FIX(0) );
50
+ }
51
+ if ( _t >= timestamp_at_i( m_data, _size-1 ) ) {
52
+ return rb_ary_new3( 2, INT2FIX(_size-1), Qnil );
53
+ }
54
+
55
+ // find left & right indices using binary search:
56
+ _ileft = 0;
57
+ _iright = _size - 1;
58
+ while ( _iright - _ileft > 1 ) {
59
+ _icenter = _ileft + ( _iright - _ileft ) / 2;
60
+ if ( _t >= timestamp_at_i( m_data, _icenter) ) {
61
+ _ileft = _icenter;
62
+ } else {
63
+ _iright = _icenter;
64
+ }
65
+ }
66
+ return rb_ary_new3( 2, INT2FIX(_ileft), INT2FIX(_iright) );
67
+ }
68
+
69
+ void Init_time_series_math_c() {
70
+ // get TimeSeriesMath module
71
+ ID sym_TimeSeriesMath = rb_intern("TimeSeriesMath");
72
+ VALUE mTimeSeriesMath = rb_const_get(rb_cObject, sym_TimeSeriesMath);
73
+
74
+ // get TimeSeriesMath::TimeSeries class
75
+ ID sym_TimeSeries = rb_intern("TimeSeries");
76
+ VALUE kTimeSeries = rb_const_get(mTimeSeriesMath, sym_TimeSeries);
77
+
78
+ // define TimeSeriesMath::TimeSeries#bsearch_indices_at
79
+ rb_define_method(kTimeSeries, "bsearch_indices_at", time_series_bsearch_indices_at, 1);
80
+ }
@@ -0,0 +1,11 @@
1
+ require 'time_series_math/version'
2
+ require 'time_series_math/time_series'
3
+ require 'time_series_math/elemwise_operators'
4
+ require 'time_series_math/linear_interpolation'
5
+
6
+ # load C extensions
7
+ require 'time_series_math_c'
8
+
9
+ module TimeSeriesMath
10
+ # Your code goes here...
11
+ end
@@ -0,0 +1,56 @@
1
+ module TimeSeriesMath
2
+ #
3
+ # == ElemwiseOperators
4
+ # A collection of helper functions for by-element operations:
5
+ # * addition
6
+ # * substraction
7
+ # * multiplication by scalar
8
+ #
9
+ module ElemwiseOperators
10
+ #
11
+ # Element-wise addition of objects
12
+ #
13
+ def elemwise_add(obj1, obj2)
14
+ case obj1
15
+ when Array
16
+ obj1.clone.zip(obj2).map { |d| d[0] + d[1] }
17
+ when Hash
18
+ out = {}
19
+ obj1.each { |k, v| out[k] = v + obj2[k] }
20
+ out
21
+ else
22
+ obj1 + obj2
23
+ end
24
+ end
25
+
26
+ # Element-wise substraction of objects
27
+ #
28
+ def elemwise_sub(obj1, obj2)
29
+ case obj1
30
+ when Array
31
+ obj1.clone.zip(obj2).map { |d| d[0] - d[1] }
32
+ when Hash
33
+ out = {}
34
+ obj1.each { |k, v| out[k] = v - obj2[k] }
35
+ out
36
+ else
37
+ obj1 - obj2
38
+ end
39
+ end
40
+
41
+ # Element-wise multiplication by scalar
42
+ #
43
+ def elemwise_mul_scalar(scalar, obj)
44
+ case obj
45
+ when Array
46
+ obj.clone.map { |d| d * scalar }
47
+ when Hash
48
+ out = {}
49
+ obj.each { |k, v| out[k] = v * scalar }
50
+ out
51
+ else
52
+ obj * scalar
53
+ end
54
+ end
55
+ end # module ElemwiseOperators
56
+ end # module TimeSeriesMath
@@ -0,0 +1,16 @@
1
+ module TimeSeriesMath
2
+ module LinearInterpolation
3
+ include ElemwiseOperators
4
+ # Returns interpolated value.
5
+ #
6
+ def [](t)
7
+ i0, i1 = indices_at(t)
8
+ return first[1] if i0.nil?
9
+ return last[1] if i1.nil?
10
+
11
+ k = (t - @data[i0][0]) / (@data[i1][0] - @data[i0][0])
12
+ diff_value = elemwise_sub(@data[i1][1], @data[i0][1])
13
+ elemwise_add(@data[i0][1], elemwise_mul_scalar(k, diff_value))
14
+ end
15
+ end
16
+ end # module TimeSeriesMath
@@ -0,0 +1,167 @@
1
+ module TimeSeriesMath
2
+ #
3
+ # = TimeSeries
4
+ # TimeSeries class provides an efficient data structure for storing timestamped data values.
5
+ #
6
+ # TimeSeries maintains order of its elements and provides efficient search methods
7
+ # for near-constant time access
8
+ # (depends strongly on timestamps distribution -- the more even, the better).
9
+ #
10
+ # == Examples:
11
+ #
12
+ # require 'time_series_math'
13
+ # include TimeSeriesMath
14
+ #
15
+ # # one by one element insertion
16
+ # ts = TimeSeries.new
17
+ # ts.push 1.0, { x: 2.0, y: 3.0 }
18
+ # ts.push 1.2, { x: 2.0, y: 3.0 }
19
+ # ts.push 1.6, { x: 2.0, y: 3.0 }
20
+ # ts.push 1.9, { x: 2.0, y: 3.0 }
21
+ # ts.push 2.1, { x: 2.0, y: 3.0 }
22
+ #
23
+ # # more time-efficient batch insertion using arrays:
24
+ # ts = TimeSeries.new
25
+ # tt = [ 1.0, 1.2, 1.6, 1.9, 2.1 ]
26
+ # dd = [ {x: 2.0}, {x: 2.1}, {x: 2.5}, {x: 2.7}, {x: 2.85} ]
27
+ # ts.push_array(tt, dd)
28
+ #
29
+ # # retrieve closest element before given time
30
+ # ts[1.2] # => { x: 2.1 }
31
+ # ts[2.095] # => { x: 2.7 }
32
+
33
+ class TimeSeries
34
+ attr_reader :data, :processor
35
+
36
+ # Creates a TimeSeries object.
37
+ #
38
+ # @param arr_t [Array<Float>] Array of timestamps
39
+ # @param arr_v [Array] Array of corresponding values, should be of the same size as +arr_t+
40
+ #
41
+ # == Examples:
42
+ #
43
+ # ts = TimeSeries.new # creates empty TimeSeries object
44
+ #
45
+ # ts = TimeSeries.new([0.1, 0.5, 1.0], [120.0, 130.0, 140.0])
46
+ #
47
+ def initialize(arr_t = nil, arr_v = nil)
48
+ @data = []
49
+ @processor = nil
50
+ push_array(arr_t, arr_v) if arr_t && arr_v
51
+ end
52
+
53
+ # @return number of values in the series
54
+ #
55
+ def size
56
+ @data.size
57
+ end
58
+
59
+ # @return [Array] Array of timestamps
60
+ #
61
+ def keys
62
+ @data.map { |d| d[0] }
63
+ end
64
+
65
+ # @return [Array] Array of values
66
+ #
67
+ def values
68
+ @data.map { |d| d[1] }
69
+ end
70
+
71
+ # @return [Array, nil] First element of the time series
72
+ #
73
+ def first
74
+ @data.first
75
+ end
76
+
77
+ # @return [Float, nil] Timestamp of the first element
78
+ #
79
+ def t_first
80
+ first && first[0]
81
+ end
82
+
83
+ # @return [Array, nil] Last element of the time series
84
+ #
85
+ def last
86
+ @data.last
87
+ end
88
+
89
+ # @return [Float, nil] Timestamp of the last element
90
+ #
91
+ def t_last
92
+ last && last[0]
93
+ end
94
+
95
+ # Inserts new element into time series.
96
+ # @param t [Float] Timestamp of the new value
97
+ # @param v [Fixnum, Float, Array, Hash] New value
98
+ #
99
+ def push(t, v)
100
+ t = t.to_f
101
+ i = left_index_at(t)
102
+ if i.nil?
103
+ @data.unshift([t, v])
104
+ else
105
+ @data.insert(i + 1, [t, v])
106
+ end
107
+ end
108
+
109
+ # Alias for #push(t, v)
110
+ #
111
+ # == Example:
112
+ #
113
+ # ts = TimeSeries.new
114
+ # ts[1.0] = { x: 123.0 }
115
+ # ts[2.0] = { x: 125.0 }
116
+ #
117
+ # ts[1.0] # => { x: 123.0 }
118
+ #
119
+ # @param (see #push)
120
+ #
121
+ def []=(t, v)
122
+ push(t, v)
123
+ end
124
+
125
+ # Inserts batch of new values into time series. This method is more time efficient
126
+ # than using #push to insert elements one by one.
127
+ #
128
+ # @param arr_t [Array] Array of timestamps
129
+ # @param arr_v [Array] Array of corresponding values, should be of the same size as +arr_t+
130
+ #
131
+ def push_array(arr_t, arr_v)
132
+ arr_data = arr_t.zip(arr_v)
133
+ @data.concat(arr_data)
134
+ @data.sort_by! { |d| d[0] }
135
+ end
136
+
137
+ # @return index of the element preceding +t+
138
+ #
139
+ def left_index_at(t)
140
+ indices_at(t).first
141
+ end
142
+
143
+ # Returns value calculated at +t+.
144
+ #
145
+ # The actual value returned depends on the +processor+
146
+ # used by TimeSeries object. When no +processor+ is used, the returned value is the value
147
+ # of the last element preceding, or exactly at +t+.
148
+ #
149
+ def [](t)
150
+ i = left_index_at(t)
151
+ i && @data[i][1]
152
+ end
153
+
154
+ # @return [Array] indices of the elements surrounding +t+.
155
+ #
156
+ def indices_at(t)
157
+ bsearch_indices_at(t)
158
+ end
159
+
160
+ # Use +processor+
161
+ #
162
+ def use(processor_module)
163
+ @processor = processor_module
164
+ extend processor_module
165
+ end
166
+ end # class TimeSeries
167
+ end # module TimeSeriesMath
@@ -0,0 +1,3 @@
1
+ module TimeSeriesMath
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'time_series_math'
5
+
6
+ include TimeSeriesMath
7
+
8
+ RSpec.configure do |config|
9
+ config.expect_with :rspec do |c|
10
+ c.syntax = :expect
11
+ end
12
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ElemwiseOperators' do
4
+ class A
5
+ end
6
+
7
+ subject { A.new.extend(ElemwiseOperators) }
8
+
9
+ describe '#elemwise_add' do
10
+ it 'should add Fixnum' do
11
+ expect(subject.elemwise_add( 1.0, 2.0 )).to eql 3.0
12
+ end
13
+ it 'should add Array(s)' do
14
+ expect(subject.elemwise_add([1.0], [2.0])).to eql [3.0]
15
+ end
16
+ it 'should add Hash(es)' do
17
+ expect(subject.elemwise_add({ x: 1.0 }, { x: 2.0 })).to eql({ x: 3.0 })
18
+ end
19
+ end
20
+
21
+ describe '#elemwise_sub' do
22
+ it 'should substract Fixnum' do
23
+ expect(subject.elemwise_sub( 1.0, 2.0 )).to eql -1.0
24
+ end
25
+ it 'should substract Array(s)' do
26
+ expect(subject.elemwise_sub([1.0], [2.0])).to eql [-1.0]
27
+ end
28
+ it 'should substract Hash(es)' do
29
+ expect(subject.elemwise_sub({ x: 1.0 }, { x: 2.0 })).to eql({ x: -1.0 })
30
+ end
31
+ end
32
+
33
+ describe '#elemwise_mul_scalar' do
34
+ it 'should multiply Fixnum' do
35
+ expect(subject.elemwise_mul_scalar( 2.0, 3.0 )).to eql 6.0
36
+ end
37
+ it 'should multiply Array(s)' do
38
+ expect(subject.elemwise_mul_scalar(2.0, [3.0])).to eql [6.0]
39
+ end
40
+ it 'should mutiply Hash(es)' do
41
+ expect(subject.elemwise_mul_scalar(2.0, { x: 3.0 })).to eql({ x: 6.0 })
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe LinearInterpolation do
4
+
5
+ let(:arr_t) { [1.0, 2.0, 3.0] }
6
+ let(:arr_v) { [100, 200, 300] }
7
+ let(:arr_v_a) { [[100], [200], [300]] }
8
+ let(:arr_v_h) { [{ x: 100 }, { x: 200 }, { x: 300 }] }
9
+ it { expect { TimeSeries.new.use(LinearInterpolation) }.to_not raise_error }
10
+
11
+ context 'when values are floats' do
12
+ subject { TimeSeries.new(arr_t, arr_v).use(LinearInterpolation) }
13
+ it { expect(subject[1.5]).to eql 150.to_f }
14
+ end
15
+
16
+ context 'when values are arrays' do
17
+ subject { TimeSeries.new(arr_t, arr_v_a).use(LinearInterpolation) }
18
+ it { expect(subject[1.5]).to eql [150.to_f] }
19
+ end
20
+
21
+ context 'when values are hashes' do
22
+ subject { TimeSeries.new(arr_t, arr_v_h).use(LinearInterpolation) }
23
+ it { expect(subject[1.5]).to eql({ x: 150.to_f }) }
24
+ end
25
+
26
+ context 'when requested value is out of range' do
27
+ subject { TimeSeries.new(arr_t, arr_v).use(LinearInterpolation) }
28
+ it { expect(subject[-1.0].to_f).to eql 100.to_f }
29
+ it { expect(subject[10.0].to_f).to eql 300.to_f }
30
+ end
31
+
32
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe TimeSeries do
4
+ it { should respond_to(:bsearch_indices_at) }
5
+ it { expect { subject.bsearch_indices_at(1.0) }.not_to raise_error }
6
+ it { expect(subject.bsearch_indices_at(1.0)).to eql [nil, nil] }
7
+
8
+ let(:arr_t) { [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] }
9
+ let(:arr_t1) { [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] }
10
+ let(:arr_t2) { [1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0] }
11
+ let(:arr_v) { [111, 222, 333, 444, 555, 666, 777] }
12
+
13
+ context 'when initialized with arrays' do
14
+ subject { TimeSeries.new(arr_t, arr_v) }
15
+
16
+ describe '#bsearch_indices_at' do
17
+ it 'should return [nil, 0] if t < first element' do
18
+ expect(subject.bsearch_indices_at(-1.0)).to eql [nil, 0]
19
+ end
20
+ it 'should return [i_k, i_k+1] if T(i_k) <= t < T(i_k+1)' do
21
+ expect(subject.bsearch_indices_at(1.0)).to eql [0, 1]
22
+ expect(subject.bsearch_indices_at(1.5)).to eql [0, 1]
23
+ end
24
+ it 'should return last element index [N, nil] if T(N) <= t' do
25
+ expect(subject.bsearch_indices_at(10.0)).to eql [6, nil]
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'when initialized with several items with same timestamp' do
31
+ let(:ts1) { TimeSeries.new(arr_t1, arr_v) }
32
+ let(:ts2) { TimeSeries.new(arr_t2, arr_v) }
33
+
34
+ describe '#bsearch_indices_at' do
35
+ it 'should return last element index pair [N, nil] if T(N) <= t' do
36
+ expect(ts1.bsearch_indices_at(1.0)).to eql [6, nil]
37
+ expect(ts1.bsearch_indices_at(10.0)).to eql [6, nil]
38
+ end
39
+ it 'should return index pair of last element in serie of equal timestamps' do
40
+ expect(ts2.bsearch_indices_at(1.0)).to eql [2, 3]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe TimeSeries do
4
+ context 'when empty' do
5
+ subject { TimeSeries.new }
6
+ it { expect(subject.data).to eql [] }
7
+ it { expect(subject.first).to be nil }
8
+ it { expect(subject.last).to be nil }
9
+ it { expect(subject.t_first).to be nil }
10
+ it { expect(subject.t_last).to be nil }
11
+ it { expect(subject.size).to eql 0 }
12
+ it { expect(subject.keys).to eql [] }
13
+ it { expect(subject.values).to eql [] }
14
+ it { expect(subject.left_index_at(1.0)).to be nil }
15
+ it { expect(subject.indices_at(1.0)).to eql [nil, nil] }
16
+ it { expect(subject[1.0]).to be nil }
17
+ end
18
+
19
+ let(:arr_t) { [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] }
20
+ let(:arr_t1) { [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] }
21
+ let(:arr_t2) { [1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0] }
22
+ let(:arr_v) { [111, 222, 333, 444, 555, 666, 777] }
23
+ it { expect { TimeSeries.new(arr_t, arr_v) }.to_not raise_error }
24
+
25
+ context 'when initialized with arrays' do
26
+ subject { TimeSeries.new(arr_t, arr_v) }
27
+ it { expect(subject.size).to eql 7 }
28
+ it { expect(subject.first).to eql [1.0, 111] }
29
+ it { expect(subject.last).to eql [7.0, 777] }
30
+ it { expect(subject.t_first).to eql 1.0 }
31
+ it { expect(subject.t_last).to eql 7.0 }
32
+
33
+ describe '#left_index_at' do
34
+ it 'should return nil if t < first element' do
35
+ expect(subject.left_index_at(-1.0)).to be nil
36
+ end
37
+ it 'should return i_k if T(i_k) <= t < T(i_k+1)' do
38
+ expect(subject.left_index_at(1.0)).to be 0
39
+ expect(subject.left_index_at(1.5)).to be 0
40
+ end
41
+ it 'should return last element index (N) if T(N) <= t' do
42
+ expect(subject.left_index_at(10.0)).to be 6
43
+ end
44
+ end
45
+
46
+ describe '#[]' do
47
+ it 'should return nil if t < first element' do
48
+ expect(subject[-1.0]).to be nil
49
+ end
50
+ it 'should return i_k if T(i_k) <= t < T(i_k+1)' do
51
+ expect(subject[1.0]).to be 111
52
+ expect(subject[1.5]).to be 111
53
+ end
54
+ it 'should return last element index (N) if T(N) <= t' do
55
+ expect(subject[10.0]).to be 777
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'when initialized with several items with same timestamp' do
61
+ let(:ts1) { TimeSeries.new(arr_t1, arr_v) }
62
+ let(:ts2) { TimeSeries.new(arr_t2, arr_v) }
63
+
64
+ describe '#left_index_at' do
65
+ it 'should return last element index (N) if T(N) <= t' do
66
+ expect(ts1.left_index_at(1.0)).to be 6
67
+ expect(ts1.left_index_at(10.0)).to be 6
68
+ end
69
+ it 'should return index of last element in serie of equal timestamps' do
70
+ expect(ts2.left_index_at(1.0)).to be 2
71
+ end
72
+ end
73
+
74
+ describe '#indices_at' do
75
+ it 'should return last element index pair [N, nil] if T(N) <= t' do
76
+ expect(ts1.indices_at(1.0)).to eql [6, nil]
77
+ expect(ts1.indices_at(10.0)).to eql [6, nil]
78
+ end
79
+ it 'should return index pair of last element in serie of equal timestamps' do
80
+ expect(ts2.indices_at(1.0)).to eql [2, 3]
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'when adding new items' do
86
+ subject { TimeSeries.new(arr_t, arr_v) }
87
+ it 'should place new items at correct place' do
88
+ expect { subject.push(1.5, 123) }.to_not raise_error
89
+ expect(subject.keys).to eql [1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
90
+ end
91
+ it 'should allow []= syntax' do
92
+ expect { subject[1.5] = 123 }.to_not raise_error
93
+ expect(subject.keys).to eql [1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe TimeSeries do
4
+ module TimeSeriesTestProcessor
5
+ end
6
+
7
+ it { should respond_to(:use) }
8
+ it { should respond_to(:processor) }
9
+ it { expect(subject.processor).to be nil }
10
+ it { expect(subject.use(TimeSeriesTestProcessor)).to eql subject }
11
+
12
+ context 'when using a processor' do
13
+ subject { TimeSeries.new.use(TimeSeriesTestProcessor) }
14
+ it { expect(subject.processor).to eql TimeSeriesTestProcessor }
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe TimeSeriesMath do
4
+ # it { should have_const(TimeSeries) }
5
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'time_series_math/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "time_series_math"
8
+ spec.version = TimeSeriesMath::VERSION
9
+ spec.authors = ["Alex Kukushkin"]
10
+ spec.email = ["alex@kukushk.in"]
11
+ spec.summary = %q{Simple time series math}
12
+ spec.description = %q{Time series math support}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib", "ext"]
20
+
21
+ spec.extensions = Dir['ext/**/extconf.rb']
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "rake-compiler"
27
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: time_series_math
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex Kukushkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Time series math support
70
+ email:
71
+ - alex@kukushk.in
72
+ executables: []
73
+ extensions:
74
+ - ext/time_series_math_c/extconf.rb
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - examples/ts_benchmark.rb
83
+ - ext/time_series_math_c/extconf.rb
84
+ - ext/time_series_math_c/time_series_math_c.c
85
+ - lib/time_series_math.rb
86
+ - lib/time_series_math/elemwise_operators.rb
87
+ - lib/time_series_math/linear_interpolation.rb
88
+ - lib/time_series_math/time_series.rb
89
+ - lib/time_series_math/version.rb
90
+ - spec/spec_helper.rb
91
+ - spec/time_series_math/elemwise_operators_spec.rb
92
+ - spec/time_series_math/linear_interpolation_spec.rb
93
+ - spec/time_series_math/time_series_math_c_spec.rb
94
+ - spec/time_series_math/time_series_spec.rb
95
+ - spec/time_series_math/time_series_use_api_spec.rb
96
+ - spec/time_series_math_spec.rb
97
+ - time_series_math.gemspec
98
+ homepage: ''
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ - ext
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.2.2
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Simple time series math
123
+ test_files:
124
+ - spec/spec_helper.rb
125
+ - spec/time_series_math/elemwise_operators_spec.rb
126
+ - spec/time_series_math/linear_interpolation_spec.rb
127
+ - spec/time_series_math/time_series_math_c_spec.rb
128
+ - spec/time_series_math/time_series_spec.rb
129
+ - spec/time_series_math/time_series_use_api_spec.rb
130
+ - spec/time_series_math_spec.rb
131
+ has_rdoc: