statsample-timeseries 0.0.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +13 -10
- data/Gemfile +2 -21
- data/History.md +4 -0
- data/LICENSE.txt +1 -1
- data/README.md +62 -0
- data/Rakefile +12 -17
- data/lib/statsample-timeseries.rb +3 -13
- data/lib/statsample-timeseries/arima.rb +72 -74
- data/lib/statsample-timeseries/arima/kalman.rb +20 -40
- data/lib/statsample-timeseries/arima/likelihood.rb +3 -4
- data/lib/statsample-timeseries/daru_monkeys.rb +78 -0
- data/lib/statsample-timeseries/timeseries/pacf.rb +47 -38
- data/lib/statsample-timeseries/utility.rb +105 -133
- data/lib/statsample-timeseries/version.rb +5 -0
- data/statsample-timeseries.gemspec +31 -0
- data/test/helper.rb +6 -29
- data/test/test_acf.rb +41 -0
- data/test/test_arima_ks.rb +28 -12
- data/test/test_arima_simulators.rb +59 -42
- data/test/test_matrix.rb +1 -1
- data/test/test_pacf.rb +7 -2
- data/test/test_wald.rb +7 -3
- metadata +81 -132
- data/README.rdoc +0 -72
- data/VERSION +0 -1
- data/bin/bio-statsample-timeseries +0 -74
- data/features/acf.feature +0 -31
- data/features/pacf.feature +0 -42
- data/features/step_definitions/bio-statsample-timeseries_steps.rb +0 -0
- data/features/step_definitions/step_definitions.rb +0 -37
- data/features/step_definitions/step_definitions_acf.rb +0 -8
- data/features/support/env.rb +0 -15
- data/lib/statsample-timeseries/timeseries.rb +0 -291
- data/test/test_tseries.rb +0 -103
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2aa5a74001152045a97547f2341b4a03f23ed408
|
4
|
+
data.tar.gz: 9f1c370a27c0b3ffb99d13f0d96eb6bfc6ca93bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 75272189f0b3f8f242820dc5693d335abb8d160d9322c36f7e82372d282ad2f7368ddb5fbedea0ebb2afd6bab21559651f8faaf224a02d462758131cd48098e5
|
7
|
+
data.tar.gz: ca16839c93757d521eb4322640b425fb0672187fa4c05d8adc6ed741fcc74271e5d67d074898a757a1814ff2c45d4a3e86e32d4a540d5cb16fc98b7504f7c8d7
|
data/.gitignore
ADDED
data/.travis.yml
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
language: ruby
|
2
|
+
|
2
3
|
rvm:
|
3
|
-
- 1.9.2
|
4
|
-
- 1.9.3
|
5
4
|
- 2.0.0
|
6
|
-
-
|
7
|
-
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
- 2.1.0
|
6
|
+
- 2.2.1
|
7
|
+
|
8
|
+
script: "bundle exec rake test"
|
9
|
+
|
10
|
+
install:
|
11
|
+
- gem install bundler
|
12
|
+
- bundle install
|
13
|
+
|
14
|
+
before_install:
|
15
|
+
- sudo apt-get update -qq
|
16
|
+
- sudo apt-get install -y libgsl0-dev
|
data/Gemfile
CHANGED
@@ -1,21 +1,2 @@
|
|
1
|
-
source "
|
2
|
-
|
3
|
-
gem 'statsample', '=1.2.0'
|
4
|
-
|
5
|
-
# Add dependencies required to use your gem here.
|
6
|
-
# Example:
|
7
|
-
gem "activesupport", "= 3.2.10"
|
8
|
-
|
9
|
-
# Add dependencies to develop your gem here.
|
10
|
-
# Include everything needed to run rake, tests, features, etc.
|
11
|
-
group :development do
|
12
|
-
gem "shoulda", ">= 0"
|
13
|
-
gem "rdoc", "~> 3.12"
|
14
|
-
gem "minitest", "~> 4.7.5"
|
15
|
-
gem "cucumber", ">= 0"
|
16
|
-
gem "bundler", "~> 1.3.5"
|
17
|
-
gem "jeweler", "~> 1.8.4"
|
18
|
-
gem "bio", ">= 1.4.2"
|
19
|
-
gem "rdoc", "~> 3.12"
|
20
|
-
gem 'mocha', '~> 0.14.0'
|
21
|
-
end
|
1
|
+
source "https://rubygems.org"
|
2
|
+
gemspec
|
data/History.md
ADDED
data/LICENSE.txt
CHANGED
@@ -9,7 +9,7 @@ You *must* read the Contributor Agreement before contributing code to the SciRub
|
|
9
9
|
|
10
10
|
-----
|
11
11
|
|
12
|
-
Copyright (c)
|
12
|
+
Copyright (c) 2013, Ankur Goel and Ruby Science Foundation
|
13
13
|
All rights reserved.
|
14
14
|
|
15
15
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# statsample-timeseries
|
2
|
+
|
3
|
+
{<img
|
4
|
+
src="https://secure.travis-ci.org/SciRuby/statsample-timeseries.png"
|
5
|
+
/>}[http://travis-ci.org/#!/SciRuby/statsample-timeseries]
|
6
|
+
|
7
|
+
Statsample-Timeseries is an extension to [Statsample](https://github.com/sciruby/statsample), a suite for advanced statistics with Ruby.
|
8
|
+
|
9
|
+
## Description
|
10
|
+
|
11
|
+
Statsample-Timeseries is extension of Statsample, and incorporates helpful time series functions, estimation techniques, and modules, such as:
|
12
|
+
|
13
|
+
* ACF
|
14
|
+
* PACF
|
15
|
+
* ARIMA
|
16
|
+
* Kalman Filter
|
17
|
+
* Log Likelihood
|
18
|
+
* Autocovariances
|
19
|
+
* Moving Averages
|
20
|
+
|
21
|
+
Statsample-Timeseries is part of the [SciRuby project](http://sciruby.com).
|
22
|
+
|
23
|
+
## Dependency
|
24
|
+
|
25
|
+
Please install [rb-gsl]() which is a Ruby wrapper over GNU Scientific Library.
|
26
|
+
It enables us to use various minimization techniques during estimations.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
`gem install statsample-timeseries`
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
To use the library:
|
35
|
+
|
36
|
+
`require 'statsample-timeseries'`
|
37
|
+
|
38
|
+
See [Ankur's blog posts](http://ankurgoel.com) for explanations and examples.
|
39
|
+
|
40
|
+
## Documentation
|
41
|
+
|
42
|
+
The API doc is [online](http://rubygems.org/gems/statsample-timeseries). For more code examples see also the test files in the source tree.
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
* Fork the project.
|
47
|
+
* Create your feature branch
|
48
|
+
* Add/Modify code.
|
49
|
+
* Write equivalent documentation and *tests*.
|
50
|
+
* Run `rake test` to verify that all tests pass.
|
51
|
+
* Push your branch.
|
52
|
+
* Pull request. :)
|
53
|
+
|
54
|
+
## Project home page
|
55
|
+
|
56
|
+
For information on the source tree, documentation, issues and how to contribute, see [http://github.com/SciRuby/statsample-timeseries](git@github.com:SciRuby/statsample-timeseries.git).
|
57
|
+
|
58
|
+
== Copyright
|
59
|
+
|
60
|
+
Copyright (c) 2013 Ankur Goel and the Ruby Science Foundation. See LICENSE.txt for further details.
|
61
|
+
|
62
|
+
Statsample is (c) 2009-2013 Claudio Bustos and the Ruby Science Foundation.
|
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler/gem_tasks'
|
2
4
|
|
3
|
-
|
5
|
+
# Setup the necessary gems, specified in the gemspec.
|
4
6
|
require 'bundler'
|
5
7
|
begin
|
6
8
|
Bundler.setup(:default, :development)
|
@@ -9,20 +11,16 @@ rescue Bundler::BundlerError => e
|
|
9
11
|
$stderr.puts "Run `bundle install` to install missing gems"
|
10
12
|
exit e.status_code
|
11
13
|
end
|
12
|
-
require 'rake'
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
gem.authors = ["Ankur Goel", "Claudio Bustos"]
|
23
|
-
# dependencies defined in Gemfile
|
15
|
+
desc "Open IRB with statsample-timeseries loaded."
|
16
|
+
task :console do
|
17
|
+
require 'irb'
|
18
|
+
require 'irb/completion'
|
19
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
20
|
+
require 'statsample-timeseries'
|
21
|
+
ARGV.clear
|
22
|
+
IRB.start
|
24
23
|
end
|
25
|
-
Jeweler::RubygemsDotOrgTasks.new
|
26
24
|
|
27
25
|
require 'rake/testtask'
|
28
26
|
Rake::TestTask.new(:test) do |test|
|
@@ -31,14 +29,11 @@ Rake::TestTask.new(:test) do |test|
|
|
31
29
|
test.verbose = true
|
32
30
|
end
|
33
31
|
|
34
|
-
require 'cucumber/rake/task'
|
35
|
-
Cucumber::Rake::Task.new(:features)
|
36
|
-
|
37
32
|
task :default => :test
|
38
33
|
|
39
34
|
require 'rdoc/task'
|
40
35
|
Rake::RDocTask.new do |rdoc|
|
41
|
-
version =
|
36
|
+
version = Statsample::TimeSeries::VERSION
|
42
37
|
|
43
38
|
rdoc.rdoc_dir = 'rdoc'
|
44
39
|
rdoc.title = "statsample-timeseries #{version}"
|
@@ -1,18 +1,8 @@
|
|
1
|
-
# Please require your code below, respecting the naming conventions in the
|
2
|
-
# bioruby directory tree.
|
3
|
-
#
|
4
|
-
# For example, say you have a plugin named bio-plugin, the only uncommented
|
5
|
-
# line in this file would be
|
6
|
-
#
|
7
|
-
# require 'bio/bio-plugin/plugin'
|
8
|
-
#
|
9
|
-
# In this file only require other files. Avoid other source code.
|
10
|
-
|
11
1
|
require 'statsample'
|
12
|
-
|
2
|
+
require 'statsample-timeseries/version'
|
3
|
+
|
4
|
+
require_relative 'statsample-timeseries/daru_monkeys.rb'
|
13
5
|
require_relative 'statsample-timeseries/arima.rb'
|
14
6
|
require_relative 'statsample-timeseries/arima/kalman'
|
15
7
|
require_relative 'statsample-timeseries/arima/likelihood'
|
16
8
|
require_relative 'statsample-timeseries/utility.rb'
|
17
|
-
|
18
|
-
|
@@ -1,4 +1,3 @@
|
|
1
|
-
#require 'debugger'
|
2
1
|
require 'statsample-timeseries/arima/kalman'
|
3
2
|
require 'statsample-timeseries/arima/likelihood'
|
4
3
|
module Statsample
|
@@ -10,31 +9,31 @@ module Statsample
|
|
10
9
|
Statsample::TimeSeries::ARIMA.new
|
11
10
|
end
|
12
11
|
|
13
|
-
class ARIMA
|
14
|
-
include Statsample::TimeSeries
|
12
|
+
class ARIMA
|
15
13
|
|
16
14
|
#= Kalman filter on ARIMA model
|
17
|
-
|
15
|
+
# == Arguments
|
18
16
|
#
|
19
17
|
#* *ts*: timeseries object
|
20
18
|
#* *p*: AR order
|
21
19
|
#* *i*: Integerated part order
|
22
20
|
#* *q*: MA order
|
23
21
|
#
|
24
|
-
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
22
|
+
# == Usage
|
23
|
+
#
|
24
|
+
# ts = (1..100).map { rand }.to_ts
|
25
|
+
# k_obj = Statsample::TimeSeries::ARIMA.ks(ts, 2, 1, 1)
|
26
|
+
# k_obj.ar
|
27
|
+
# #=> AR's phi coefficients
|
28
|
+
# k_obj.ma
|
29
|
+
# #=> MA's theta coefficients
|
31
30
|
#
|
32
31
|
#== Returns
|
33
|
-
#Kalman filter object
|
32
|
+
# Kalman filter object
|
34
33
|
def self.ks(ts, p, i, q)
|
35
34
|
#prototype
|
36
35
|
if i > 0
|
37
|
-
ts = ts.diff(i).reject { |x| x.nil? }
|
36
|
+
ts = ts.diff(i).reject { |x| x.nil? }
|
38
37
|
end
|
39
38
|
if Statsample.has_gsl?
|
40
39
|
filter = Arima::KalmanFilter.new(ts, p, i, q)
|
@@ -49,14 +48,15 @@ module Statsample
|
|
49
48
|
#http://en.wikipedia.org/wiki/Autoregressive_model#Definition
|
50
49
|
#For finding parameters(to fit), we will use either Yule-walker
|
51
50
|
#or Burg's algorithm(more efficient)
|
51
|
+
raise NotImplementedError
|
52
52
|
end
|
53
53
|
|
54
|
-
#Converts a linear array into a
|
54
|
+
# Converts a linear array into a Daru vector
|
55
55
|
#== Parameters
|
56
56
|
#
|
57
|
-
#* *arr*: Array which has to be converted
|
57
|
+
#* *arr*: Array which has to be converted to Daru vector
|
58
58
|
def create_vector(arr)
|
59
|
-
|
59
|
+
Daru::Vector.new(arr)
|
60
60
|
end
|
61
61
|
|
62
62
|
#=Yule Walker
|
@@ -70,7 +70,7 @@ module Statsample
|
|
70
70
|
#==Returns
|
71
71
|
#phi and sigma vectors
|
72
72
|
def yule_walker(ts, n, k)
|
73
|
-
phi, sigma =
|
73
|
+
phi, sigma = Statsample::TimeSeries::Pacf.yule_walker(ts, k)
|
74
74
|
return phi, sigma
|
75
75
|
#return ar_sim(n, phi, sigma)
|
76
76
|
end
|
@@ -89,22 +89,23 @@ module Statsample
|
|
89
89
|
intermediate = Pacf::Pacf.levinson_durbin(ts, k)
|
90
90
|
phi, sigma = intermediate[1], intermediate[0]
|
91
91
|
return phi, sigma
|
92
|
-
#return ar_sim(n, phi, sigma)
|
93
92
|
end
|
94
93
|
|
95
94
|
#=Autoregressive Simulator
|
96
95
|
#Simulates an autoregressive AR(p) model with specified number of
|
97
96
|
#observations(n), with phi number of values for order p and sigma.
|
98
97
|
#
|
99
|
-
|
100
|
-
# [http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/](http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/)
|
98
|
+
# == Analysis
|
101
99
|
#
|
102
|
-
|
100
|
+
# http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/
|
101
|
+
#
|
102
|
+
# == Parameters
|
103
103
|
#* *n*: integer, number of observations
|
104
104
|
#* *phi* :array of phi values, e.g: [0.35, 0.213] for p = 2
|
105
105
|
#* *sigma*: float, sigma value for error generalization
|
106
106
|
#
|
107
|
-
|
107
|
+
# == Usage
|
108
|
+
#
|
108
109
|
# ar = ARIMA.new
|
109
110
|
# ar.ar_sim(1500, [0.3, 0.9], 0.12)
|
110
111
|
# # => AR(2) autoregressive series of 1500 values
|
@@ -127,14 +128,13 @@ module Statsample
|
|
127
128
|
11.upto(n+11) do |i|
|
128
129
|
if i <= phi.size
|
129
130
|
#dependent on previous accumulation of x
|
130
|
-
backshifts =
|
131
|
+
backshifts = x[0...i].reverse
|
131
132
|
else
|
132
133
|
#dependent on number of phi size/order
|
133
|
-
backshifts =
|
134
|
+
backshifts = x[(i - phi.size)...i].reverse
|
134
135
|
end
|
135
|
-
parameters =
|
136
|
-
|
137
|
-
summation = (backshifts * parameters).inject(:+)
|
136
|
+
parameters = phi[0...backshifts.size]
|
137
|
+
summation = (backshifts.map.with_index { |e,i| e*parameters[i] }).inject(:+)
|
138
138
|
x[i] = summation + err_nor.call()
|
139
139
|
end
|
140
140
|
x - buffer
|
@@ -144,70 +144,74 @@ module Statsample
|
|
144
144
|
#Simulates a moving average model with specified number of
|
145
145
|
#observations(n), with theta values for order k and sigma
|
146
146
|
#
|
147
|
-
|
147
|
+
# == Parameters
|
148
148
|
#* *n*: integer, number of observations
|
149
149
|
#* *theta*: array of floats, e.g: [0.23, 0.732], must be < 1
|
150
150
|
#* *sigma*: float, sigma value for whitenoise error
|
151
151
|
#
|
152
|
-
|
153
|
-
#
|
154
|
-
#
|
152
|
+
# == Usage
|
153
|
+
# ar = ARIMA.new
|
154
|
+
# ar.ma_sim(1500, [0.23, 0.732], 0.27)
|
155
155
|
#
|
156
|
-
|
157
|
-
#Array of generated MA(q) model
|
156
|
+
# == Returns
|
157
|
+
# Array of generated MA(q) model
|
158
|
+
#
|
159
|
+
# == Notes
|
160
|
+
# n is number of observations (eg: 1000). theta are the model parameters
|
161
|
+
# containting q values. q is the order of MA
|
158
162
|
def ma_sim(n, theta, sigma)
|
159
|
-
|
160
|
-
|
161
|
-
#q is the order of MA
|
162
|
-
mean = theta.to_ts.mean()
|
163
|
+
q = theta.size
|
164
|
+
mean = theta.inject(:+) / q
|
163
165
|
whitenoise_gen = Distribution::Normal.rng(0, sigma)
|
164
166
|
x = Array.new(n, 0)
|
165
|
-
q = theta.size
|
166
167
|
noise_arr = (n+1).times.map { whitenoise_gen.call() }
|
167
168
|
|
168
169
|
1.upto(n) do |i|
|
169
170
|
#take care that noise vector doesn't try to index -ve value:
|
170
171
|
if i <= q
|
171
|
-
noises =
|
172
|
+
noises = noise_arr[0..i].reverse
|
172
173
|
else
|
173
|
-
noises =
|
174
|
+
noises = noise_arr[(i-q)..i].reverse
|
174
175
|
end
|
175
|
-
weights =
|
176
|
+
weights = [1] + theta[0...noises.size - 1]
|
176
177
|
|
177
|
-
summation =
|
178
|
+
summation = weights.map.with_index { |e,i| e*noises[i] }.inject(:+)
|
178
179
|
x[i] = mean + summation
|
179
180
|
end
|
181
|
+
|
180
182
|
x
|
181
183
|
end
|
182
184
|
|
183
185
|
#=ARMA(Autoregressive and Moving Average) Simulator
|
184
|
-
#ARMA is represented by:
|
185
|
-
#http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
186
|
-
#This simulates the ARMA model against p, q and sigma.
|
187
|
-
#If p = 0, then model is pure MA(q),
|
188
|
-
#If q = 0, then model is pure AR(p),
|
189
|
-
#otherwise, model is ARMA(p, q) represented by above.
|
186
|
+
# ARMA is represented by:
|
187
|
+
# http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
188
|
+
# This simulates the ARMA model against p, q and sigma.
|
189
|
+
# If p = 0, then model is pure MA(q),
|
190
|
+
# If q = 0, then model is pure AR(p),
|
191
|
+
# otherwise, model is ARMA(p, q) represented by above.
|
190
192
|
#
|
191
|
-
|
192
|
-
#
|
193
|
+
# == Detailed analysis:
|
194
|
+
#
|
195
|
+
# http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/
|
196
|
+
#
|
197
|
+
# == Parameters
|
193
198
|
#
|
194
|
-
#==Parameters
|
195
199
|
#* *n*: integer, number of observations
|
196
200
|
#* *p*: array, contains p number of phi values for AR(p) process
|
197
201
|
#* *q*: array, contains q number of theta values for MA(q) process
|
198
202
|
#* *sigma*: float, sigma value for whitenoise error generation
|
199
203
|
#
|
200
|
-
|
201
|
-
# ar = ARIMA.new
|
202
|
-
# ar.arma_sim(1500, [0.3, 0.272], [0.8, 0.317], 0.92)
|
204
|
+
# == Usage
|
203
205
|
#
|
204
|
-
|
205
|
-
#
|
206
|
+
# ar = ARIMA.new
|
207
|
+
# ar.arma_sim(1500, [0.3, 0.272], [0.8, 0.317], 0.92)
|
208
|
+
#
|
209
|
+
# == Returns
|
210
|
+
#
|
211
|
+
# array of generated ARMA model values
|
206
212
|
def arma_sim(n, p, q, sigma)
|
207
213
|
#represented by :
|
208
214
|
#http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
209
|
-
|
210
|
-
|
211
215
|
whitenoise_gen = Distribution::Normal.rng(0, sigma)
|
212
216
|
noise_arr = (n+11).times.map { whitenoise_gen.call() }
|
213
217
|
|
@@ -216,35 +220,29 @@ module Statsample
|
|
216
220
|
|
217
221
|
11.upto(n+11) do |i|
|
218
222
|
if i <= p.size
|
219
|
-
backshifts =
|
223
|
+
backshifts = x[0...i].reverse
|
220
224
|
else
|
221
|
-
backshifts =
|
225
|
+
backshifts = x[(i - p.size)...i].reverse
|
222
226
|
end
|
223
|
-
parameters =
|
224
|
-
|
225
|
-
ar_summation = (backshifts * parameters).inject(:+)
|
227
|
+
parameters = p[0...backshifts.size]
|
228
|
+
ar_summation = backshifts.map.with_index { |e,i| e*parameters[i] }.inject(:+)
|
226
229
|
|
227
230
|
if i <= q.size
|
228
|
-
noises =
|
231
|
+
noises = noise_arr[0..i].reverse
|
229
232
|
else
|
230
|
-
noises =
|
233
|
+
noises = noise_arr[(i-q.size)..i].reverse
|
231
234
|
end
|
232
|
-
weights =
|
233
|
-
|
234
|
-
ma_summation = (weights * noises).inject(:+)
|
235
|
-
|
235
|
+
weights = [1] + q[0...noises.size - 1]
|
236
|
+
ma_summation = (weights.map.with_index { |e,i| e*noises[i] }).inject(:+)
|
236
237
|
x[i] = ar_summation + ma_summation
|
237
238
|
end
|
238
239
|
x - buffer
|
239
240
|
end
|
240
241
|
|
241
|
-
|
242
|
+
# Hannan-Rissanen for ARMA fit
|
242
243
|
def self.hannan(ts, p, q, k)
|
243
|
-
|
244
|
-
ts_dup = ts.dup
|
245
|
-
|
244
|
+
raise NotImplementedError
|
246
245
|
end
|
247
246
|
end
|
248
|
-
|
249
247
|
end
|
250
248
|
end
|