interpolate.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 package require Tcl 8.4
00032 package require struct::matrix
00033
00034
00035
00036
00037
00038 namespace ::math::interpolate {
00039 variable search_radius {}
00040 variable inv_dist_pow 2
00041
00042 namespace export interp-1d-table interp-table interp-linear \
00043 interp-lagrange
00044 namespace export neville
00045 }
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062 ret ::math::interpolate::defineTable ( type name , type cols , type values ) {
00063
00064 set table ::math::interpolate::__$name
00065 ::struct::matrix $table
00066
00067 $table add columns [llength $cols]
00068 $table add row
00069 $table set row 0 $cols
00070
00071 set row 1
00072 set first 0
00073 set nocols [llength $cols]
00074 set novals [llength $values]
00075 while { $first < $novals } {
00076 set last [expr {$first+$nocols-1}]
00077 $table add row
00078 $table set row $row [lrange $values $first $last]
00079
00080 incr first $nocols
00081 incr row
00082 }
00083
00084 return $table
00085 }
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098 ret ::math::interpolate::interp-1d-table ( type table , type xval ) {
00099
00100 #
00101 # Search for the records that enclose the x-value
00102 #
00103 set xvalues [lrange [$table get column 0] 1 end]
00104
00105 foreach {row row2} [FindEnclosingEntries $xval $xvalues] break
00106
00107 set prev_values [$table get row $row]
00108 set next_values [$table get row $row2]
00109
00110 set xprev [lindex $prev_values 0]
00111 set xnext [lindex $next_values 0]
00112
00113 if { $row == $row2 } {
00114 return [concat $xval [lrange $prev_values 1 end]]
00115 } else {
00116 set wprev [expr {($xnext-$xval)/($xnext-$xprev)}]
00117 set wnext [expr {1.0-$wprev}]
00118 set results {}
00119 foreach vprev $prev_values vnext $next_values {
00120 set vint [expr {$vprev*$wprev+$vnext*$wnext}]
00121 lappend results $vint
00122 }
00123 return $results
00124 }
00125 }
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142 ret ::math::interpolate::interp-table ( type table , type xval , type yval ) {
00143
00144 #
00145 # Search for the records that enclose the x-value
00146 #
00147 set xvalues [lrange [$table get column 0] 2 end]
00148
00149 foreach {row row2} [FindEnclosingEntries $xval $xvalues] break
00150 incr row
00151 incr row2
00152
00153 #
00154 # Search for the columns that enclose the y-value
00155 #
00156 set yvalues [lrange [$table get row 1] 1 end]
00157
00158 foreach {col col2} [FindEnclosingEntries $yval $yvalues] break
00159
00160 set yvalues [concat "." $yvalues] ;# Prepend a dummy column!
00161
00162 set prev_values [$table get row $row]
00163 set next_values [$table get row $row2]
00164
00165 set x1 [lindex $prev_values 0]
00166 set x2 [lindex $next_values 0]
00167 set y1 [lindex $yvalues $col]
00168 set y2 [lindex $yvalues $col2]
00169
00170 set v11 [lindex $prev_values $col]
00171 set v12 [lindex $prev_values $col2]
00172 set v21 [lindex $next_values $col]
00173 set v22 [lindex $next_values $col2]
00174
00175 #
00176 # value = v0 + a*(x-x1) + b*(y-y1) + c*(x-x1)*(y-y1)
00177 # if x == x1 and y == y1: value = v11
00178 # if x == x1 and y == y2: value = v12
00179 # if x == x2 and y == y1: value = v21
00180 # if x == x2 and y == y2: value = v22
00181 #
00182 set a 0.0
00183 if { $x1 != $x2 } {
00184 set a [expr {($v21-$v11)/($x2-$x1)}]
00185 }
00186 set b 0.0
00187 if { $y1 != $y2 } {
00188 set b [expr {($v12-$v11)/($y2-$y1)}]
00189 }
00190 set c 0.0
00191 if { $x1 != $x2 && $y1 != $y2 } {
00192 set c [expr {($v11+$v22-$v12-$v21)/($x2-$x1)/($y2-$y1)}]
00193 }
00194
00195 set result \
00196 [expr {$v11+$a*($xval-$x1)+$b*($yval-$y1)+$c*($xval-$x1)*($yval-$y1)}]
00197
00198 return $result
00199 }
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211 ret FindEnclosingEntries ( type val , type values ) {
00212 set found 0
00213 set row2 1
00214 foreach v $values {
00215 if { $val <= $v } {
00216 set row [expr {$row2-1}]
00217 set found 1
00218 break
00219 }
00220 incr row2
00221 }
00222
00223 #
00224 # Border cases: extrapolation needed
00225 #
00226 if { ! $found } {
00227 incr row2 -1
00228 set row $row2
00229 }
00230 if { $row == 0 } {
00231 set row $row2
00232 }
00233
00234 return [list $row $row2]
00235 }
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250 ret ::math::interpolate::interp-linear ( type xyvalues , type xval ) {
00251 #
00252 # Border cases first
00253 #
00254 if { [lindex $xyvalues 0] > $xval } {
00255 return [lindex $xyvalues 1]
00256 }
00257 if { [lindex $xyvalues end-1] < $xval } {
00258 return [lindex $xyvalues end]
00259 }
00260
00261 #
00262 # The ordinary case
00263 #
00264 set idxx -2
00265 set idxy -1
00266 foreach { x y } $xyvalues {
00267 if { $xval < $x } {
00268 break
00269 }
00270 incr idxx 2
00271 incr idxy 2
00272 }
00273
00274 set x2 [lindex $xyvalues $idxx]
00275 set y2 [lindex $xyvalues $idxy]
00276
00277 if { $x2 != $x } {
00278 set yval [expr {$y+($y2-$y)*($xval-$x)/($x2-$x)}]
00279 } else {
00280 set yval $y
00281 }
00282 return $yval
00283 }
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300 ret ::math::interpolate::interp-lagrange ( type xyvalues , type xval ) {
00301 #
00302 # Border case: xval equals one of the "nodes"
00303 #
00304 foreach { x y } $xyvalues {
00305 if { $x == $xval } {
00306 return $y
00307 }
00308 }
00309
00310 #
00311 # Ordinary case
00312 #
00313 set nonodes2 [llength $xyvalues]
00314
00315 set yval 0.0
00316
00317 for { set i 0 } { $i < $nonodes2 } { incr i 2 } {
00318 set idxn 0
00319 set xn [lindex $xyvalues $i]
00320 set yn [lindex $xyvalues [expr {$i+1}]]
00321
00322 foreach { x y } $xyvalues {
00323 if { $idxn != $i } {
00324 set yn [expr {$yn*($x-$xval)/($x-$xn)}]
00325 }
00326 incr idxn 2
00327 }
00328
00329 set yval [expr {$yval+$yn}]
00330 }
00331
00332 return $yval
00333 }
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360 ret ::math::interpolate::interp-spatial ( type xyvalues , type coord ) {
00361 variable search_radius
00362 variable inv_dist_pow
00363
00364 set result {}
00365 foreach v [lindex [lindex $xyvalues 0] end] {
00366 lappend result 0.0
00367 }
00368
00369 set total_weight 0.0
00370
00371 if { $search_radius != {} } {
00372 set max_radius2 [expr {$search_radius*$search_radius}]
00373 } else {
00374 set max_radius2 {}
00375 }
00376
00377 foreach point $xyvalues {
00378 set dist 0.0
00379 foreach c [lrange $point 0 end-1] cc $coord {
00380 set dist [expr {$dist+($c-$cc)*($c-$cc)}]
00381 }
00382 if { $max_radius2 == {} || $dist <= $max_radius2 } {
00383 if { $inv_dist_pow == 1 } {
00384 set dist [expr {sqrt($dist)}]
00385 }
00386 set total_weight [expr {$total_weight+1.0/$dist}]
00387
00388 set idx 0
00389 foreach v [lindex $point end] r $result {
00390 lset result $idx [expr {$r+$v/$dist}]
00391 incr idx
00392 }
00393 }
00394 }
00395
00396 if { $total_weight == 0.0 } {
00397 set idx 0
00398 foreach r $result {
00399 lset result $idx {}
00400 incr idx
00401 }
00402 } else {
00403 set idx 0
00404 foreach r $result {
00405 lset result $idx [expr {$r/$total_weight}]
00406 incr idx
00407 }
00408 }
00409
00410 return $result
00411 }
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423 ret ::math::interpolate::interp-spatial-params ( type max_, type search , optional power =2 ) {
00424 variable search_radius
00425 variable inv_dist_pow
00426
00427 set search_radius $max_search
00428 if { $power == 1 } {
00429 set inv_dist_pow 1
00430 } else {
00431 set inv_dist_pow 2
00432 }
00433 }
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459 ret ::math::interpolate::neville ( type xtable , type ytable , type x ) {
00460
00461 set n [llength $xtable]
00462
00463 # Initialization. Set c and d to the ordinates, and set ns to the
00464 # index of the nearest abscissa. Set y to the zero-order approximation
00465 # of the nearest ordinate, and dif to the difference between x
00466 # and the nearest tabulated abscissa.
00467
00468 set c [list]
00469 set d [list]
00470 set i 0
00471 set ns 0
00472 set dif [expr { abs( $x - [lindex $xtable 0] ) }]
00473 set y [lindex $ytable 0]
00474 foreach xi $xtable yi $ytable {
00475 set dift [expr { abs ( $x - $xi ) }]
00476 if { $dift < $dif } {
00477 set ns $i
00478 set y $yi
00479 set dif $dift
00480 }
00481 lappend c $yi
00482 lappend d $yi
00483 incr i
00484 }
00485
00486 # Compute successively higher-degree approximations to the fit
00487 # function by using the recurrence:
00488 # d_m[i] = ( c_{m-1}[i+1] - d{m-1}[i] ) * (x[i+m]-x) /
00489 # (x[i] - x[i+m])
00490 # c_m[i] = ( c_{m-1}[i+1] - d{m-1}[i] ) * (x[i]-x) /
00491 # (x[i] - x[i+m])
00492
00493 for { set m 1 } { $m < $n } { incr m } {
00494 for { set i 0 } { $i < $n - $m } { set i $ip1 } {
00495 set ip1 [expr { $i + 1 }]
00496 set ipm [expr { $i + $m }]
00497 set ho [expr { [lindex $xtable $i] - $x }]
00498 set hp [expr { [lindex $xtable $ipm] - $x }]
00499 set w [expr { [lindex $c $ip1] - [lindex $d $i] }]
00500 set q [expr { $w / ( $ho - $hp ) }]
00501 lset d $i [expr { $hp * $q }]
00502 lset c $i [expr { $ho * $q }]
00503 }
00504
00505 # Take the straighest path possible through the tableau of c
00506 # and d approximations back to the tabulated value
00507 if { 2 * $ns < $n - $m } {
00508 set dy [lindex $c $ns]
00509 } else {
00510 incr ns -1
00511 set dy [lindex $d $ns]
00512 }
00513 set y [expr { $y + $dy }]
00514 }
00515
00516 # Return the approximation and the highest-order correction term.
00517
00518 return [list $y [expr { abs($dy) }]]
00519 }
00520
00521
00522
00523
00524
00525
00526
00527
00528
00529
00530
00531
00532
00533
00534
00535
00536
00537
00538 ret ::math::interpolate::prepare-cubic-splines (type xcoord , type ycoord) {
00539
00540 if { [llength $xcoord] < 3 } {
00541 return -code error "At least three points are required"
00542 }
00543 if { [llength $xcoord] != [llength $ycoord] } {
00544 return -code error "Equal number of x and y values required"
00545 }
00546
00547 set m2 [expr {[llength $xcoord]-1}]
00548
00549 set s 0.0
00550 set h {}
00551 set c {}
00552 for { set i 0 } { $i < $m2 } { incr i } {
00553 set ip1 [expr {$i+1}]
00554 set h1 [expr {[lindex $xcoord $ip1]-[lindex $xcoord $i]}]
00555 lappend h $h1
00556 if { $h1 <= 0.0 } {
00557 return -code error "X values must be strictly ascending"
00558 }
00559 set r [expr {([lindex $ycoord $ip1]-[lindex $ycoord $i])/$h1}]
00560 lappend c [expr {$r-$s}]
00561 set s $r
00562 }
00563 set s 0.0
00564 set r 0.0
00565 set t {--}
00566 lset c 0 0.0
00567
00568 for { set i 1 } { $i < $m2 } { incr i } {
00569 set ip1 [expr {$i+1}]
00570 set im1 [expr {$i-1}]
00571 set y2 [expr {[lindex $c $i]+$r*[lindex $c $im1]}]
00572 set t1 [expr {2.0*([lindex $xcoord $im1]-[lindex $xcoord $ip1])-$r*$s}]
00573 set s [lindex $h $i]
00574 set r [expr {$s/$t1}]
00575 lset c $i $y2
00576 lappend t $t1
00577 }
00578 lappend c 0.0
00579
00580 for { set j 1 } { $j < $m2 } { incr j } {
00581 set i [expr {$m2-$j}]
00582 set ip1 [expr {$i+1}]
00583 set h1 [lindex $h $i]
00584 set yp1 [lindex $c $ip1]
00585 set y1 [lindex $c $i]
00586 set t1 [lindex $t $i]
00587 lset c $i [expr {($h1*$yp1-$y1)/$t1}]
00588 }
00589
00590 set b {}
00591 set d {}
00592 for { set i 0 } { $i < $m2 } { incr i } {
00593 set ip1 [expr {$i+1}]
00594 set s [lindex $h $i]
00595 set yp1 [lindex $c $ip1]
00596 set y1 [lindex $c $i]
00597 set r [expr {$yp1-$y1}]
00598 lappend d [expr {$r/$s}]
00599 lset c $i [expr {3.0*$y1}]
00600 lappend b [expr {([lindex $ycoord $ip1]-[lindex $ycoord $i])/$s
00601 -($y1+$r)*$s}]
00602 }
00603
00604 lappend d 0.0
00605 lappend b 0.0
00606
00607 return [list $d $c $b $ycoord $xcoord]
00608 }
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619 ret ::math::interpolate::interp-cubic-splines (type coeffs , type x) {
00620 foreach {dcoef ccoef bcoef acoef xcoord} $coeffs {break}
00621
00622 #
00623 # Check the bounds - no extrapolation
00624 #
00625 if { $x < [lindex $xcoord 0] } {error "X value too small"}
00626 if { $x > [lindex $xcoord end] } {error "X value too large"}
00627
00628 #
00629 # Which interval?
00630 #
00631 set idx -1
00632 foreach xv $xcoord {
00633 if { $xv > $x } {
00634 break
00635 }
00636 incr idx
00637 }
00638
00639 set a [lindex $acoef $idx]
00640 set b [lindex $bcoef $idx]
00641 set c [lindex $ccoef $idx]
00642 set d [lindex $dcoef $idx]
00643 set dx [expr {$x-[lindex $xcoord $idx]}]
00644
00645 return [expr {(($d*$dx+$c)*$dx+$b)*$dx+$a}]
00646 }
00647
00648
00649
00650
00651
00652
00653 package provide math::interpolate 1.0.2
00654