spliner 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +15 -1
- data/lib/spliner.rb +2 -151
- data/lib/spliner/spliner.rb +135 -0
- data/lib/spliner/spliner_section.rb +86 -0
- data/spec/spliner_spec.rb +41 -3
- metadata +5 -2
data/README.markdown
CHANGED
@@ -19,7 +19,7 @@ Spliner requires Ruby 1.9 or later. Install with rubygems:
|
|
19
19
|
Quick Start
|
20
20
|
-----------
|
21
21
|
|
22
|
-
require 'spliner'
|
22
|
+
require 'spliner'
|
23
23
|
|
24
24
|
# Initialize a spline interpolation with x range 0.0..2.0
|
25
25
|
my_spline = Spliner::Spliner.new({0.0 => 0.0, 1.0 => 1.0, 2.0 => 0.5})
|
@@ -37,6 +37,14 @@ require 'spliner'
|
|
37
37
|
ex_spline = Spliner::Spliner.new({0.0 => 0.0, 1.0 => 1.0, 2.0 => 0.5}, :extrapolate => '10%', :emethod => :hold)
|
38
38
|
xx = ex_spline[2.1] # returns 0.5
|
39
39
|
|
40
|
+
# Alternative intialization using X and Y arrays
|
41
|
+
ar_spline = Spliner::Spliner.new [1.0, 2.0, 3.0], [0.0, 3.0, 1.0]
|
42
|
+
|
43
|
+
# When duplicate X values are encountered, two or more discontinuous curves are used
|
44
|
+
two_spline = Spliner::Spliner.new [1.0, 2.0, 2.0, 3.0], [0.0, 3.0, 0.0, 1.0]
|
45
|
+
puts two_spline.sections # prints 2
|
46
|
+
|
47
|
+
|
40
48
|
Spliner is based on the interpolation described on this page
|
41
49
|
http://en.wikipedia.org/wiki/Spline_interpolation
|
42
50
|
|
@@ -48,6 +56,12 @@ Feel free to fork the project on GitHub and send fork requests. Please
|
|
48
56
|
try to have each feature separated in commits.
|
49
57
|
|
50
58
|
|
59
|
+
Home page
|
60
|
+
---------
|
61
|
+
|
62
|
+
http://www.github.com/tallakt/spliner
|
63
|
+
|
64
|
+
http://rubygems.org/gems/spliner
|
51
65
|
|
52
66
|
License
|
53
67
|
-------
|
data/lib/spliner.rb
CHANGED
@@ -1,154 +1,5 @@
|
|
1
|
-
|
2
|
-
# Spliner::Spliner
|
3
|
-
#
|
4
|
-
require 'matrix'
|
1
|
+
require 'spliner/spliner'
|
5
2
|
|
6
3
|
module Spliner
|
7
|
-
VERSION = '1.0.
|
8
|
-
|
9
|
-
# Spliner::Spliner provides cubic spline interpolation based on provided
|
10
|
-
# key points on a X-Y curve.
|
11
|
-
#
|
12
|
-
# == Example
|
13
|
-
# require 'spliner'
|
14
|
-
# # Initialize a spline interpolation with x range 0.0..2.0
|
15
|
-
# my_spline = Spliner::Spliner.new({0.0 => 0.0, 1.0 => 1.0, 2.0 => 0.5})
|
16
|
-
# # Perform interpolation on 31 values ranging from 0..2.0
|
17
|
-
# x_values = (0..30).map {|x| x / 30.0 * 2.0 }
|
18
|
-
# y_values = x_values.map {|x| my_spline[x] }
|
19
|
-
#
|
20
|
-
# http://en.wikipedia.org/wiki/Spline_interpolation
|
21
|
-
#
|
22
|
-
class Spliner
|
23
|
-
attr_reader :range
|
24
|
-
|
25
|
-
# Creates a new Spliner::Spliner object to interpolate between
|
26
|
-
# the supplied key points. The key points are provided in a hash where
|
27
|
-
# the key is the X value, and the value is the Y value. The X values
|
28
|
-
# mush be increasing and not duplicate. You must provide at least
|
29
|
-
# two values.
|
30
|
-
#
|
31
|
-
# options may take the following keys:
|
32
|
-
#
|
33
|
-
# :extrapolate
|
34
|
-
# Specify an area outside the given X values provided that should return
|
35
|
-
# a valid number. The value may be either a range (eg. -10..110) or a
|
36
|
-
# percentage value written as a string (eg '10%'). Default is no
|
37
|
-
# extrapolation.
|
38
|
-
#
|
39
|
-
# :emethod
|
40
|
-
# Specify a method of extrapolation, one of :linear (continue curve as
|
41
|
-
# a straigt line, default), or :hold (use Y values at the curve endpoints)
|
42
|
-
#
|
43
|
-
def initialize(key_points, options = {})
|
44
|
-
|
45
|
-
@points = key_points
|
46
|
-
@x = @points.keys
|
47
|
-
@y = @points.values
|
48
|
-
|
49
|
-
check_points_increasing
|
50
|
-
raise 'Interpolation needs at least two points' unless @points.size >= 2
|
51
|
-
|
52
|
-
@x_pairs = @points.keys.each_cons(2).map {|pair| pair.first..pair.last }
|
53
|
-
|
54
|
-
inv_diff = @x.each_cons(2).map {|x1, x2| 1 / (x2 - x1) }
|
55
|
-
a_diag = 2.0 * Matrix::diagonal(*vector_helper(inv_diff))
|
56
|
-
a_non_diag = Matrix::build(@points.size) do |row, col|
|
57
|
-
if row == col+ 1
|
58
|
-
inv_diff[col]
|
59
|
-
elsif col == row + 1
|
60
|
-
inv_diff[row]
|
61
|
-
else
|
62
|
-
0.0
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
a = a_diag + a_non_diag
|
67
|
-
|
68
|
-
tmp = @points.each_cons(2).map do |p1, p2|
|
69
|
-
x1, y1 = p1
|
70
|
-
x2, y2 = p2
|
71
|
-
3.0 * (y2 - y1) / (x2 - x1) ** 2.0
|
72
|
-
end
|
73
|
-
b = vector_helper(tmp)
|
74
|
-
|
75
|
-
@k = a.inv * b
|
76
|
-
|
77
|
-
options[:extrapolate].tap do |ex|
|
78
|
-
case ex
|
79
|
-
when /^\d+(\.\d+)?\s?%$/
|
80
|
-
percentage = ex[/\d+(\.\d+)?/].to_f
|
81
|
-
span = @x.last - @x.first
|
82
|
-
extra = span * percentage * 0.01
|
83
|
-
@range = (@x.first - extra)..(@x.last + extra)
|
84
|
-
when Range
|
85
|
-
@range = ex
|
86
|
-
when nil
|
87
|
-
@range = @x.first..@x.last
|
88
|
-
else
|
89
|
-
raise 'Unable to use extrapolation parameter'
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
@extrapolation_method = options[:emethod] || :linear
|
94
|
-
end
|
95
|
-
|
96
|
-
# returns an interpolated value
|
97
|
-
def get(v)
|
98
|
-
i = @x_pairs.find_index {|pair| pair.member? v }
|
99
|
-
if i
|
100
|
-
dx = @x[i + 1] - @x[i]
|
101
|
-
dy = @y[i + 1] - @y[i]
|
102
|
-
t = (v - @x[i]) / dx
|
103
|
-
a = @k[i] * dx - dy
|
104
|
-
b = -(@k[i + 1] * dx - dy)
|
105
|
-
(1 - t) * @y[i] + t * @y[i + 1] + t * (1 - t) * (a * (1 - t) + b * t)
|
106
|
-
elsif range.member? v
|
107
|
-
extrapolate(v)
|
108
|
-
else
|
109
|
-
nil
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
alias :'[]' :get
|
114
|
-
|
115
|
-
|
116
|
-
# for a vector [a, b, c] returns [a, a + b, b + c, c]
|
117
|
-
# :nodoc:
|
118
|
-
def vector_helper(a)
|
119
|
-
Vector[*([0.0] + a)] + Vector[*(a + [0.0])]
|
120
|
-
end
|
121
|
-
private :vector_helper
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# :nodoc:
|
126
|
-
def check_points_increasing
|
127
|
-
@x.each_cons(2) do |x1, x2|
|
128
|
-
raise 'Points must form a series of x and y values where x is increasing' unless x2 > x1
|
129
|
-
end
|
130
|
-
end
|
131
|
-
private :check_points_increasing
|
132
|
-
|
133
|
-
# :nodoc:
|
134
|
-
def extrapolate(v)
|
135
|
-
case @extrapolation_method
|
136
|
-
when :hold
|
137
|
-
if v < @x.first
|
138
|
-
@y.first
|
139
|
-
else
|
140
|
-
@y.last
|
141
|
-
end
|
142
|
-
else
|
143
|
-
x, y, k = if v < @x.first
|
144
|
-
[@x.first, @y.first, @k.first]
|
145
|
-
else
|
146
|
-
[@x.last, @y.last, @k[-1]]
|
147
|
-
end
|
148
|
-
y + k * (v - x)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
private :extrapolate
|
152
|
-
|
153
|
-
end
|
4
|
+
VERSION = '1.0.2'
|
154
5
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
require 'spliner/spliner_section'
|
3
|
+
|
4
|
+
module Spliner
|
5
|
+
# Spliner::Spliner provides cubic spline interpolation based on provided
|
6
|
+
# key points on a X-Y curve.
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# require 'spliner'
|
11
|
+
# # Initialize a spline interpolation with x range 0.0..2.0
|
12
|
+
# my_spline = Spliner::Spliner.new [0.0, 1.0, 2.0], [0.0, 1.0, 0.5]
|
13
|
+
# # Perform interpolation on 31 values ranging from 0..2.0
|
14
|
+
# x_values = (0..30).map {|x| x / 30.0 * 2.0 }
|
15
|
+
# y_values = x_values.map {|x| my_spline[x] }
|
16
|
+
#
|
17
|
+
# Algorithm based on http://en.wikipedia.org/wiki/Spline_interpolation
|
18
|
+
#
|
19
|
+
class Spliner
|
20
|
+
attr_reader :range
|
21
|
+
|
22
|
+
# Creates a new Spliner::Spliner object to interpolate between
|
23
|
+
# the supplied key points.
|
24
|
+
#
|
25
|
+
# The key points shoul be in increaing X order. When duplicate X
|
26
|
+
# values are encountered, the spline is split into two or more
|
27
|
+
# discontinuous sections.
|
28
|
+
#
|
29
|
+
# The extrapolation method may be :linear by default, using a linear
|
30
|
+
# extrapolation at the curve ends using the curve derivative at the
|
31
|
+
# end points. The :hold method will use the Y value at the nearest end
|
32
|
+
# point of the curve.
|
33
|
+
#
|
34
|
+
# @overload initialize(key_points, options)
|
35
|
+
# @param key_points [Hash{Float => Float}] keys are X values in increasing order, values Y
|
36
|
+
# @param options [Hash]
|
37
|
+
# @option options [Range,String] :extrapolate ('0%') either a range or percentage, eg '10.0%'
|
38
|
+
# @option options [Symbol] :emethod (:linear) extrapolation method
|
39
|
+
#
|
40
|
+
# @overload initialize(x, y, options)
|
41
|
+
# @param x [Array(Float),Vector] the X values of the key points
|
42
|
+
# @param y [Array(Float),Vector] the Y values of the key points
|
43
|
+
# @param options [Hash]
|
44
|
+
# @option options [Range,String] :extrapolate ('0%') either a range or percentage, eg '10.0%'
|
45
|
+
# @option options [Symbol] :emethod (:linear) extrapolation method
|
46
|
+
#
|
47
|
+
def initialize(*param)
|
48
|
+
# sort parameters from two alternative initializer signatures
|
49
|
+
x, y = nil
|
50
|
+
case param.first
|
51
|
+
when Array, Vector
|
52
|
+
xx,yy, options = param
|
53
|
+
x = xx.to_a
|
54
|
+
y = yy.to_a
|
55
|
+
else
|
56
|
+
points, options = param
|
57
|
+
x = points.keys
|
58
|
+
y = points.values
|
59
|
+
end
|
60
|
+
options ||= {}
|
61
|
+
|
62
|
+
@sections = split_at_duplicates(x).map {|slice| SplinerSection.new x[slice], y[slice] }
|
63
|
+
|
64
|
+
# Handle extrapolation option parameter
|
65
|
+
options[:extrapolate].tap do |ex|
|
66
|
+
case ex
|
67
|
+
when /^\d+(\.\d+)?\s?%$/
|
68
|
+
percentage = ex[/\d+(\.\d+)?/].to_f
|
69
|
+
span = x.last - x.first
|
70
|
+
extra = span * percentage * 0.01
|
71
|
+
@range = (x.first - extra)..(x.last + extra)
|
72
|
+
when Range
|
73
|
+
@range = ex
|
74
|
+
when nil
|
75
|
+
@range = x.first..x.last
|
76
|
+
else
|
77
|
+
raise 'Unable to use extrapolation parameter'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@extrapolation_method = options[:emethod] || :linear
|
81
|
+
end
|
82
|
+
|
83
|
+
# returns the ranges at each slice between duplicate X values
|
84
|
+
def split_at_duplicates(x)
|
85
|
+
# find all indices with duplicate x values
|
86
|
+
dups = x.each_cons(2).map{|a,b| a== b}.each_with_index.select {|b,i| b }.map {|b,i| i}
|
87
|
+
([-1] + dups + [x.size - 1]).each_cons(2).map {|end0, end1| (end0 + 1)..end1 }
|
88
|
+
end
|
89
|
+
private :split_at_duplicates
|
90
|
+
|
91
|
+
|
92
|
+
# returns an interpolated value
|
93
|
+
def get(v)
|
94
|
+
i = @sections.find_index {|section| section.range.member? v }
|
95
|
+
if i
|
96
|
+
@sections[i].get v
|
97
|
+
elsif range.member? v
|
98
|
+
extrapolate(v)
|
99
|
+
else
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
alias :'[]' :get
|
105
|
+
|
106
|
+
# The number of non-continuous sections used
|
107
|
+
def sections
|
108
|
+
@sections.size
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
def extrapolate(v)
|
114
|
+
x, y, k = if v < first_x
|
115
|
+
[@sections.first.x.first, @sections.first.y.first, @sections.first.k.first]
|
116
|
+
else
|
117
|
+
[@sections.last.x.last, @sections.last.y.last, @sections.last.k[-1]]
|
118
|
+
end
|
119
|
+
|
120
|
+
case @extrapolation_method
|
121
|
+
when :hold
|
122
|
+
y
|
123
|
+
else
|
124
|
+
y + k * (v - x)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private :extrapolate
|
128
|
+
|
129
|
+
def first_x
|
130
|
+
@sections.first.x.first
|
131
|
+
end
|
132
|
+
private :first_x
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
require 'spliner/spliner_section'
|
3
|
+
|
4
|
+
module Spliner
|
5
|
+
|
6
|
+
# Spliner::SplinerSection is only used via Spliner::Spliner
|
7
|
+
#
|
8
|
+
# As the spline algorithm does not handle duplicate X values well, the
|
9
|
+
# curve is split into two non continuous parts where duplicate X values
|
10
|
+
# appear. Each such part is represented by a SplinerSection
|
11
|
+
class SplinerSection
|
12
|
+
attr_reader :k, :x, :y
|
13
|
+
|
14
|
+
def initialize(x, y)
|
15
|
+
@x, @y = x, y
|
16
|
+
@x_pairs = @x.each_cons(2).map {|pair| pair.first..pair.last }
|
17
|
+
check_points_increasing
|
18
|
+
calculate_a_k
|
19
|
+
end
|
20
|
+
|
21
|
+
def range
|
22
|
+
@x.first..@x.last
|
23
|
+
end
|
24
|
+
|
25
|
+
def calculate_a_k
|
26
|
+
if @x.size > 1
|
27
|
+
inv_diff = @x.each_cons(2).map {|x1, x2| 1 / (x2 - x1) }
|
28
|
+
a_diag = 2.0 * Matrix::diagonal(*vector_helper(inv_diff))
|
29
|
+
a_non_diag = Matrix::build(@x.size) do |row, col|
|
30
|
+
if row == col+ 1
|
31
|
+
inv_diff[col]
|
32
|
+
elsif col == row + 1
|
33
|
+
inv_diff[row]
|
34
|
+
else
|
35
|
+
0.0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
a = a_diag + a_non_diag
|
40
|
+
|
41
|
+
tmp = @x.zip(@y).each_cons(2).map do |p1, p2|
|
42
|
+
x1, y1 = p1
|
43
|
+
x2, y2 = p2
|
44
|
+
3.0 * (y2 - y1) / (x2 - x1) ** 2.0
|
45
|
+
end
|
46
|
+
b = vector_helper(tmp)
|
47
|
+
|
48
|
+
@k = a.inv * b
|
49
|
+
else
|
50
|
+
@k = Vector[0.0]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
private :calculate_a_k
|
54
|
+
|
55
|
+
# returns an interpolated value
|
56
|
+
def get(v)
|
57
|
+
i = @x_pairs.find_index {|pair| pair.member? v }
|
58
|
+
if i
|
59
|
+
dx = @x[i + 1] - @x[i]
|
60
|
+
dy = @y[i + 1] - @y[i]
|
61
|
+
t = (v - @x[i]) / dx
|
62
|
+
a = @k[i] * dx - dy
|
63
|
+
b = -(@k[i + 1] * dx - dy)
|
64
|
+
(1 - t) * @y[i] + t * @y[i + 1] + t * (1 - t) * (a * (1 - t) + b * t)
|
65
|
+
elsif @x.size == 1 && @x.first == v
|
66
|
+
@y.first
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# for a vector [a, b, c] returns [a, a + b, b + c, c]
|
73
|
+
def vector_helper(a)
|
74
|
+
Vector[*([0.0] + a)] + Vector[*(a + [0.0])]
|
75
|
+
end
|
76
|
+
private :vector_helper
|
77
|
+
|
78
|
+
def check_points_increasing
|
79
|
+
@x.each_cons(2) do |x1, x2|
|
80
|
+
raise 'Key point\'s X values should be in increasing order' unless x2 > x1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
private :check_points_increasing
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
data/spec/spliner_spec.rb
CHANGED
@@ -6,11 +6,38 @@ describe Spliner::Spliner do
|
|
6
6
|
|
7
7
|
|
8
8
|
it 'should not accept x values that are not increasing' do
|
9
|
-
expect(lambda { Spliner::Spliner.new
|
9
|
+
expect(lambda { Spliner::Spliner.new [0.0, -1.0], [0.0, 1.0] }).to raise_exception
|
10
10
|
end
|
11
11
|
|
12
|
-
it 'should
|
13
|
-
|
12
|
+
it 'should support key points with a single value' do
|
13
|
+
s1 = Spliner::Spliner.new Hash[0.0, 0.0]
|
14
|
+
expect(s1.get 0.0).to be_within(0.0001).of(0.0)
|
15
|
+
expect(s1.get 1.5).to be_nil
|
16
|
+
|
17
|
+
s2 = Spliner::Spliner.new Hash[0.0, 0.0], :extrapolate => -1..1
|
18
|
+
expect(s2.get 0.0).to be_within(0.0001).of(0.0)
|
19
|
+
expect(s2.get 0.5).to be_within(0.0001).of(0.0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'supports the Hash initializer' do
|
23
|
+
s1 = Spliner::Spliner.new Hash[0.0, 0.0, 1.0, 1.0]
|
24
|
+
expect(s1[0.5]).to be_within(0.0001).of(0.5)
|
25
|
+
|
26
|
+
s2 = Spliner::Spliner.new Hash[0.0, 0.0, 1.0, 1.0], :extrapolate => '100%'
|
27
|
+
expect(s2[0.5]).to be_within(0.0001).of(0.5)
|
28
|
+
expect(s2.range.first).to be_within(0.0001).of(-1.0)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'supports the x-y array/vector initializer' do
|
32
|
+
s1 = Spliner::Spliner.new [0.0, 1.0], [0.0, 1.0]
|
33
|
+
expect(s1[0.5]).to be_within(0.0001).of(0.5)
|
34
|
+
|
35
|
+
s2= Spliner::Spliner.new [0.0, 1.0], [0.0, 1.0], :extrapolate => '100%'
|
36
|
+
expect(s2[0.5]).to be_within(0.0001).of(0.5)
|
37
|
+
expect(s2.range.first).to be_within(0.0001).of(-1.0)
|
38
|
+
|
39
|
+
s3 = Spliner::Spliner.new Vector[0.0, 1.0], Vector[0.0, 1.0]
|
40
|
+
expect(s3[0.5]).to be_within(0.0001).of(0.5)
|
14
41
|
end
|
15
42
|
|
16
43
|
it 'should return the data points themselves' do
|
@@ -71,4 +98,15 @@ describe Spliner::Spliner do
|
|
71
98
|
expect(s3.range.first).to be_within(0.0001).of(-10.0)
|
72
99
|
expect(s3.range.last).to be_within(0.0001).of(110.0)
|
73
100
|
end
|
101
|
+
|
102
|
+
it 'splits data points with duplicate X values into separate sections' do
|
103
|
+
s = Spliner::Spliner.new [0.0, 1.0, 1.0, 2.0, 2.0, 3.0], [0.0, 0.0, 1.0, 1.0, 2.0, 2.0], :extrapolate => 3.0..4.0
|
104
|
+
expect(s.sections).to eq(3)
|
105
|
+
expect(s[-1.0]).to be_nil
|
106
|
+
expect(s[0.5]).to be_within(0.0001).of(0.0)
|
107
|
+
expect(s[1.5]).to be_within(0.0001).of(1.0)
|
108
|
+
expect(s[2.5]).to be_within(0.0001).of(2.0)
|
109
|
+
expect(s[3.5]).to be_within(0.0001).of(2.0)
|
110
|
+
expect(s[5.0]).to be_nil
|
111
|
+
end
|
74
112
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spliner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -57,6 +57,8 @@ files:
|
|
57
57
|
- README.markdown
|
58
58
|
- Rakefile
|
59
59
|
- lib/spliner.rb
|
60
|
+
- lib/spliner/spliner.rb
|
61
|
+
- lib/spliner/spliner_section.rb
|
60
62
|
- spec/spliner_spec.rb
|
61
63
|
- spliner.gemspec
|
62
64
|
homepage: http://www.github.com/tallakt/spliner
|
@@ -84,3 +86,4 @@ signing_key:
|
|
84
86
|
specification_version: 3
|
85
87
|
summary: Cubic spline interpolation library
|
86
88
|
test_files: []
|
89
|
+
has_rdoc:
|