interpolator 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
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