interpolator 0.13 → 0.14

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.
Files changed (2) hide show
  1. data/interpolator.rb +211 -106
  2. metadata +3 -3
data/interpolator.rb CHANGED
@@ -22,6 +22,9 @@
22
22
  # OTHER DEALINGS IN THE SOFTWARE.
23
23
  #
24
24
  #
25
+ # More information, source code and gems @ http://rubyforge.org/projects/interpolator/
26
+ #
27
+ #
25
28
  module Interpolator
26
29
 
27
30
  # Table holds a series of independent and dependent values that can be interpolated or extrapolated.
@@ -30,7 +33,12 @@ module Interpolator
30
33
  # created.
31
34
  #
32
35
  # Tables can be told to not extrapolate by setting .extrapolate=false
33
- # The interpolation method is controlled with .style = LINEAR,LAGRANGE2, or LAGRANGE3
36
+ # The interpolation method is controlled with .style = LINEAR, LAGRANGE2, LAGRANGE3, CUBIC (also known as natural spline), or CATMULL (spline)
37
+ #
38
+ # The style and extrapolate attributes are only applied to that specific Table. They are not propagated to subtables. Each subtable
39
+ # can of course accept it's own attributes.
40
+ #
41
+ # More information, source code and gems @ http://rubyforge.org/projects/interpolator/
34
42
  #
35
43
  class Table
36
44
 
@@ -40,10 +48,12 @@ module Interpolator
40
48
  LINEAR = 1
41
49
  LAGRANGE2 = 2
42
50
  LAGRANGE3 = 3
43
-
51
+ CUBIC = 4
52
+ CATMULL = 5
53
+ #
44
54
  # Tables are constructed from either a pair of Arrays or a single Hash.
45
55
  #
46
- # The 2 argument constructor accepts and array of independents and an array of dependents. The
56
+ # The 2 argument constructor accepts an array of independents and an array of dependents. The
47
57
  # independent Array should be floating point values. The dependent Array can be either floating
48
58
  # point values or Tables (aka sub tables.) There is no limit to how deep a Table can be.
49
59
  #
@@ -80,6 +90,10 @@ module Interpolator
80
90
  # 5=>Table.new([11.0,12.0,13.0],[-14.0,-15.0,-16.0]))
81
91
  # )
82
92
  #
93
+ # Note: notice how the Hash version of the table constructor makes it easier to view multidimensional Tables.
94
+ #
95
+ #
96
+ # The amount of Table nesting is only limited by RAM.
83
97
  #
84
98
  # As a convienance the constructor accepts a block and will pass back the
85
99
  # Table instance. This makes it easy to set the style and extrapolation inline.
@@ -105,17 +119,20 @@ module Interpolator
105
119
  end
106
120
 
107
121
  raise "number of independents must equal the number of dependents" unless @inds.size == @deps.size
108
- ilast = nil
122
+ ii = nil
109
123
  @inds.each do |i|
110
- raise "independents must be monotonically increasing" unless (ilast == nil || i > ilast)
111
- ilast = i
124
+ raise "independents must be monotonically increasing" unless (ii == nil || i > ii)
125
+ ii = i
112
126
  end
113
127
  @extrapolate = true
114
128
  @style = LINEAR
129
+ @ilast = 0 # index of last bracket operation. theory is subsequent table reads may be close to this index so remember it
130
+ @secderivs = []
115
131
  if block_given?
116
132
  yield self # makes it easy for users to set Table attributes inline
117
133
  end
118
134
  end
135
+ #
119
136
  # Interpolate or extrapolate the Table. Pass as many arguments as there are independent dimensions to the table (univariant a
120
137
  # single argument, bivariant 2 arguments, etc.)
121
138
  #
@@ -133,51 +150,31 @@ module Interpolator
133
150
  raise "table requires at least 2 points for linear interpolation" if (@style == LINEAR && @inds.size<2)
134
151
  raise "table requires at least 3 points for lagrange2 interpolation" if (@style == LAGRANGE2 && @inds.size<3)
135
152
  raise "table requires at least 4 points for lagrange3 interpolation" if (@style == LAGRANGE3 && @inds.size<4)
153
+ raise "table requires at least 3 points for cubic spline interpolation" if (@style == CUBIC && @inds.size<3)
154
+ raise "table requires at least 2 points for catmull-rom interpolation" if (@style == CATMULL && @inds.size<2)
136
155
  raise "insufficient number of arguments to read table" unless args.size>=1
156
+ raise "insufficient number of arguments to read table" if (args.size==1 && @deps[0].kind_of?(Table))
157
+ raise "too many arguments to read table" if (args.size>1 && !@deps[0].kind_of?(Table))
137
158
 
138
159
  xval = args[0]
160
+ subargs = args[1..-1]
139
161
 
140
162
  if (@extrapolate == false) && (xval < @inds[0]) then
141
- if (args.size==1) then
142
- ans = @deps[0]
143
- else
144
- subargs = args[1..-1]
145
- tab = @deps[0]
146
- ans = tab.read(*subargs)
147
- end
163
+ ans = subread(0,*subargs)
148
164
 
149
165
  elsif (@extrapolate == false) && (xval > @inds[-1])
150
- if (args.size==1) then
151
- ans = @deps[-1]
152
- else
153
- subargs = args[1..-1]
154
- tab = @deps[-1]
155
- ans = tab.read(*subargs)
156
- end
157
-
166
+ ans = subread(-1,*subargs)
167
+
158
168
  else
159
-
160
169
  ileft = bracket(xval)
161
170
 
162
171
  case @style
163
172
  when LINEAR
164
173
  x1 = @inds[ileft]
165
174
  x2 = @inds[ileft+1]
166
- if (args.size==1) then
167
- # return the answer
168
- y1 = @deps[ileft]
169
- y2 = @deps[ileft+1]
170
- ans = linear(xval,x1,x2,y1,y2)
171
- else
172
- # interpolate the sub tables
173
- tab1 = @deps[ileft]
174
- tab2 = @deps[ileft+1]
175
- subargs = args[1..-1]
176
- y1 = tab1.read(*subargs)
177
- y2 = tab2.read(*subargs)
178
- ans = linear(xval,x1,x2,y1,y2)
179
- end
180
-
175
+ y1 = subread(ileft,*subargs)
176
+ y2 = subread(ileft+1,*subargs)
177
+ ans = linear(xval,x1,x2,y1,y2)
181
178
  when LAGRANGE2
182
179
  indx = ileft
183
180
  if ileft == @inds.size-2
@@ -186,24 +183,11 @@ module Interpolator
186
183
  x1 = @inds[indx]
187
184
  x2 = @inds[indx+1]
188
185
  x3 = @inds[indx+2]
189
- if (args.size==1)
190
- # return the answer
191
- y1 = @deps[indx]
192
- y2 = @deps[indx+1]
193
- y3 = @deps[indx+2]
194
- ans = lagrange2(xval,x1,x2,x3,y1,y2,y3)
195
- else
196
- # interpolate the sub tables
197
- tab1 = @deps[indx]
198
- tab2 = @deps[indx+1]
199
- tab3 = @deps[indx+2]
200
- subargs = args[1..-1]
201
- y1 = tab1.read(*subargs)
202
- y2 = tab2.read(*subargs)
203
- y3 = tab3.read(*subargs)
204
- ans = lagrange2(xval,x1,x2,x3,y1,y2,y3)
205
- end
206
-
186
+ y1 = subread(indx,*subargs)
187
+ y2 = subread(indx+1,*subargs)
188
+ y3 = subread(indx+2,*subargs)
189
+ ans = lagrange2(xval,x1,x2,x3,y1,y2,y3)
190
+
207
191
  when LAGRANGE3
208
192
  indx = ileft
209
193
  if (ileft > @inds.size-3)
@@ -215,26 +199,39 @@ module Interpolator
215
199
  x2 = @inds[indx]
216
200
  x3 = @inds[indx+1]
217
201
  x4 = @inds[indx+2]
218
- if (args.size==1) then
219
- # return the answer
220
- y1 = @deps[indx-1]
221
- y2 = @deps[indx]
222
- y3 = @deps[indx+1]
223
- y4 = @deps[indx+2]
224
- ans = lagrange3(xval,x1,x2,x3,x4,y1,y2,y3,y4)
225
- else
226
- # interpolate the sub tables
227
- tab1 = @deps[indx-1]
228
- tab2 = @deps[indx]
229
- tab3 = @deps[indx+1]
230
- tab4 = @deps[indx+2]
231
- subargs = args[1..-1]
232
- y1 = tab1.read(*subargs)
233
- y2 = tab2.read(*subargs)
234
- y3 = tab3.read(*subargs)
235
- y4 = tab4.read(*subargs)
236
- ans = lagrange3(xval,x1,x2,x3,x4,y1,y2,y3,y4)
237
- end
202
+ y1 = subread(indx-1,*subargs)
203
+ y2 = subread(indx,*subargs)
204
+ y3 = subread(indx+1,*subargs)
205
+ y4 = subread(indx+2,*subargs)
206
+ ans = lagrange3(xval,x1,x2,x3,x4,y1,y2,y3,y4)
207
+
208
+ when CUBIC
209
+ indx = ileft
210
+ x1 = @inds[indx]
211
+ x2 = @inds[indx+1]
212
+ y1 = subread(indx,*subargs)
213
+ y2 = subread(indx+1,*subargs)
214
+ ans = cubic(xval,indx,x1,x2,y1,y2,*subargs)
215
+
216
+ when CATMULL
217
+ indx = ileft
218
+ tinds = @inds.dup # were gonna prepend and append 2 control points temporarily
219
+ tdeps = @deps.dup
220
+ tinds.insert(0,@inds[0])
221
+ tinds << @inds[-1]
222
+ tdeps.insert(0,@deps[0])
223
+ tdeps << @deps[-1]
224
+ indx=indx+1
225
+ x0 = tinds[indx-1]
226
+ x1 = tinds[indx]
227
+ x2 = tinds[indx+1]
228
+ x3 = tinds[indx+2]
229
+ y0 = catsubread(indx-1,tdeps,*subargs)
230
+ y1 = catsubread(indx,tdeps,*subargs)
231
+ y2 = catsubread(indx+1,tdeps,*subargs)
232
+ y3 = catsubread(indx+2,tdeps,*subargs)
233
+ ans = catmull(xval,x0,x1,x2,x3,y0,y1,y2,y3)
234
+
238
235
  else
239
236
  raise("invalid interpolation type")
240
237
  end
@@ -245,28 +242,54 @@ module Interpolator
245
242
  #
246
243
  # Same as read
247
244
  #
248
- def interpolate(*args)
249
- read(*args)
250
- end
245
+ alias_method :interpolate,:read
251
246
 
252
247
  protected
248
+
249
+ def subread (i,*subargs)
250
+ if subargs == []
251
+ @deps[i]
252
+ else
253
+ @deps[i].read(*subargs)
254
+ end
255
+ end
256
+ def catsubread (i,tdeps,*subargs)
257
+ if subargs == []
258
+ tdeps[i]
259
+ else
260
+ tdeps[i].read(*subargs)
261
+ end
262
+ end
253
263
 
254
- # replace later with a high speed bisection
255
264
 
265
+ #
266
+ # high speed bracket via last index and bisection
267
+ #
256
268
  def bracket (x)
257
- ileft=0
258
- for i in (0..@inds.size-2) do
259
- ileft=i
260
- if ( (x >= @inds[i]) && (x < @inds[i+1]) ) then
261
- break
262
- end
269
+ if (x<=@inds[0])
270
+ @ilast=0
271
+ elsif (x>=@inds[-2])
272
+ @ilast = @inds.size-2
273
+ else
274
+ low = 0
275
+ high = @inds.size-1
276
+ while !(x>=@inds[@ilast] && x<@inds[@ilast+1])
277
+ if (x>@inds[@ilast])
278
+ low = @ilast + 1
279
+ @ilast = (high - low) / 2 + low
280
+ else
281
+ high = @ilast - 1
282
+ @ilast = high - (high - low) / 2
283
+ end
284
+ end
263
285
  end
264
- ileft
286
+ @ilast
265
287
  end
266
288
 
267
289
  def linear (x,x1,x2,y1,y2)
268
290
  r = (y2-y1) / (x2-x1) * (x-x1) + y1
269
291
  end
292
+
270
293
  def lagrange2(x,x1,x2,x3,y1,y2,y3)
271
294
  c12 = x1 - x2
272
295
  c13 = x1 - x3
@@ -279,6 +302,7 @@ module Interpolator
279
302
  xx3 = x - x3
280
303
  r = xx3*(q1*xx2 - q2*xx1) + q3*xx1*xx2
281
304
  end
305
+
282
306
  def lagrange3(x,x1,x2,x3,x4,y1,y2,y3,y4)
283
307
  c12 = x1 - x2
284
308
  c13 = x1 - x3
@@ -297,6 +321,53 @@ module Interpolator
297
321
  r = xx4*(xx3*(q1*xx2 - q2*xx1) + q3*xx1*xx2) - q4*xx1*xx2*xx3
298
322
  end
299
323
 
324
+ def catmull(xval,x0,x1,x2,x3,y0,y1,y2,y3)
325
+ m0 = (y2-y0)/(x2-x0)
326
+ m1 = (y3-y1)/(x3-x1)
327
+ h = x2-x1
328
+ t = (xval - x1)/h
329
+ h00 = 2.0*t**3 - 3.0*t**2+1.0
330
+ h10 = t**3-2.0*t**2+t
331
+ h01 = -2.0*t**3+3.0*t**2
332
+ h11 = t**3-t**2
333
+ ans = h00*y1+h10*h*m0+h01*y2+h11*h*m1
334
+ end
335
+
336
+ def cubic(x,indx,x1,x2,y1,y2,*subargs)
337
+ if @secderivs == []
338
+ @secderivs = second_derivs(*subargs) # this is painful so lets just do it once
339
+ end
340
+ step = x2 - x1
341
+ a = (x2 - x) / step
342
+ b = (x - x1) / step
343
+ r = a * y1 + b * y2 + ((a*a*a-a) * @secderivs[indx] + (b*b*b-b) * @secderivs[indx+1]) * (step*step) / 6.0
344
+ end
345
+
346
+ def second_derivs(*subargs)
347
+ # natural spline has 0 second derivative at the ends
348
+ temp = [0.0]
349
+ secder = [0.0]
350
+ if subargs.size==0
351
+ deps2 = @deps
352
+ else
353
+ deps2 = @deps.map do |a|
354
+ a.read(*subargs)
355
+ end
356
+ end
357
+ 1.upto(@inds.size-2) do |i|
358
+ sig = (@inds[i] - @inds[i-1])/(@inds[i+1] - @inds[i-1])
359
+ prtl = sig * secder[i-1] + 2.0
360
+ secder << (sig-1.0)/prtl
361
+ temp << ((deps2[i+1]-deps2[i])/(@inds[i+1]-@inds[i]) - (deps2[i]-deps2[i-1])/(@inds[i]-@inds[i-1]))
362
+ temp[i]=(6.0*temp[i]/(@inds[i+1]-@inds[i-1])-sig*temp[i-1])/prtl
363
+ end
364
+ # natural spline has 0 second derivative at the ends
365
+ secder << 0.0
366
+ (@inds.size-2).downto(0) do |i|
367
+ secder[i]=secder[i]*secder[i+1]+temp[i]
368
+ end
369
+ secder
370
+ end
300
371
  end
301
372
 
302
373
  if __FILE__ == $0 then
@@ -311,25 +382,35 @@ require 'test/unit'
311
382
  @t2 = Table.new([1.0,2.0],[Table.new([1.0,2.0],[3.0,4.0]),Table.new([2.0,3.0,5.0],[6.0,-1.0,7.0])])
312
383
  @t3 = Table.new [1.0,2.0],[3.0,4.0]
313
384
  @t4 = Table.new(
314
- 1=>Table.new(
315
- 1=>Table.new([1.0,2.0,3.0],[4.0,5.0,6.0]),
316
- 4=>Table.new([11.0,12.0,13.0],[14.0,15.0,16.0]),
317
- 5=>Table.new([11.0,12.0,13.0],[-14.0,-15.0,-16.0])),
318
- 2=>Table.new(
319
- 2=>Table.new([1.1,2.0,3.0],[4.0,5.0,6.0]),
320
- 5=>Table.new([11.0,12.5,13.0],[14.0,15.0,16.0]),
321
- 6.2=>Table.new([1.0,12.0],[-14.0,-16.0])),
322
- 8=>Table.new(
323
- 1=>Table.new([1.0,2.0,3.0],[4.0,5.0,6.0]),
324
- 5=>Table.new([11.0,12.0,13.0],[-14.0,-15.0,-16.0]))
385
+ 1.0=>Table.new(
386
+ 1.0=>Table.new([1.0,2.0,3.0],
387
+ [4.0,5.0,6.0]),
388
+ 4.0=>Table.new([11.0,12.0,13.0],
389
+ [14.0,15.0,16.0]),
390
+ 5.0=>Table.new([11.0,12.0,13.0],
391
+ [-14.0,-15.0,-16.0])),
392
+ 2.0=>Table.new(
393
+ 2.0=>Table.new([1.1,2.0,3.0],
394
+ [4.0,5.0,6.0]),
395
+ 5.0=>Table.new([11.0,12.5,13.0],
396
+ [14.0,15.0,16.0]),
397
+ 6.2=>Table.new([1.0,12.0],
398
+ [-14.0,-16.0])),
399
+ 8.0=>Table.new(
400
+ 1.0=>Table.new([1.0,2.0,3.0],
401
+ [4.0,5.0,6.0]),
402
+ 5.0=>Table.new([11.0,12.0,13.0],
403
+ [-14.0,-15.0,-16.0]))
325
404
  )
326
405
  @t5 = Table.new [1.0,2.0,3.0],[1.0,4.0,9.0]
327
406
  @t6 = Table.new [1.0,2.0,3.0,4.0],[1.0,8.0,27.0,64.0]
407
+ @t7 = Table.new [0.0,0.8,1.9,3.1,4.2,5.0],[1.0,1.0,1.0,2.0,2.0,2.0]
408
+ @t8 = Table.new [0.0,1.0,2.0,3.0,4.0,5.0,6.0],[0.0,0.8415,0.9093,0.1411,-0.7568,-0.9589,-0.2794]
409
+ @t9 = Table.new([1.0,2.0,3.0],
410
+ [Table.new([1.0,2.0],[3.0,4.0]),Table.new([2.0,3.0,5.0],[6.0,-1.0,7.0]),Table.new([4.0,5.0,6.0],[7.0,8.0,9.0])])
411
+ @t10 = Table.new [1.5,2.0,3.0,4.0],[4.0,5.0,6.0,7.0]
328
412
  end
329
413
 
330
- # def teardown
331
- # end
332
-
333
414
  def test_uni
334
415
  assert_equal(@t1.read(1.0) , 3.0)
335
416
  assert_equal(@t1.read(2.0) , 4.0)
@@ -375,7 +456,9 @@ require 'test/unit'
375
456
  assert_raise( RuntimeError ) {@t3.read(1.0)}
376
457
  @t3.style=Table::LAGRANGE3
377
458
  assert_raise( RuntimeError ) {@t3.read(1.0)}
378
- @t3.style=Table::LINEAR
459
+ @t3.style=Table::CUBIC
460
+ assert_raise( RuntimeError ) {@t3.read(1.0)}
461
+ @t3.style=Table::LINEAR
379
462
  assert_nothing_raised( RuntimeError ) {@t3.read(1)}
380
463
  end
381
464
  def test_notmono
@@ -404,19 +487,41 @@ require 'test/unit'
404
487
  assert_raise( RuntimeError ) {
405
488
  t = Table.new [1.0,-2.0,1.5],[1.0,2.0,3.0]
406
489
  t.style=10
407
- t.read(1)
490
+ t.read(1.0)
408
491
  }
492
+ @t7.style = Table::CUBIC
493
+ assert_in_delta(0.93261392,@t7.read(1.2),0.000001)
494
+ @t8.style = Table::CUBIC
495
+ assert_in_delta(0.59621,@t8.read(2.5),0.000001)
496
+ @t9.style = Table::CUBIC
497
+ assert_in_delta(8.98175,@t9.read(2.3,1.5),0.000001)
498
+ @t10.style=Table::CATMULL
499
+ assert_in_delta(3.666666,@t10.read(1.0),0.00001)
500
+ assert_in_delta(5.5416666,@t10.read(2.5),0.00001)
501
+ assert_in_delta(6.0,@t10.read(0.5),0.00001)
502
+ assert_in_delta(8.0,@t10.read(5.0),0.00001)
503
+ assert_in_delta(6.5,@t10.read(3.5),0.00001)
504
+ assert_in_delta(4.8427,@t10.read(1.9),0.0001)
505
+ @t10.extrapolate=false
506
+ assert_equal(4.0,@t10.read(1.0))
507
+ assert_equal(7.0,@t10.read(99.0))
409
508
  end
410
509
  def test_block
411
510
  t = Table.new [1.0,2.0,3.0],[3.0,4.0,5.0] do |tab| tab.extrapolate=false;tab.style=Table::LAGRANGE2 end
412
511
  assert_equal(t.read(4.0),5.0)
413
512
  end
513
+ def test_alias
514
+ assert_equal(@t1.read(1.5),@t1.interpolate(1.5))
515
+ end
516
+ def test_numargs
517
+ assert_raise( RuntimeError ) {@t1.read}
518
+ assert_raise( RuntimeError ) {@t2.read(1.0)}
519
+ assert_raise( RuntimeError ) {@t2.read(1.0,1.0,1.0)}
520
+ assert_nothing_raised( RuntimeError ) {@t2.read(1.0,1.0)}
521
+ end
414
522
 
415
523
  end
416
524
 
525
+ end
417
526
 
418
527
  end
419
-
420
-
421
- end
422
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interpolator
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.13"
4
+ version: "0.14"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric T Meyers
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-08 00:00:00 -04:00
12
+ date: 2009-08-15 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -51,6 +51,6 @@ rubyforge_project: interpolator
51
51
  rubygems_version: 1.3.5
52
52
  signing_key:
53
53
  specification_version: 3
54
- summary: Module Interpolator proves a table class that supports n-dimensional numerical table construction, interpolation and extrapolation. Includes linear, 2nd order and 3rd order la grange techniques.
54
+ summary: Module Interpolator proves a table class that supports n-dimensional numerical table construction, interpolation and extrapolation. Includes linear, 2nd order and 3rd order lagrange, natural and catmull-rom spline techniques.
55
55
  test_files: []
56
56