interpolate.tcl

Go to the documentation of this file.
00001 /*  interpolate.tcl --*/
00002 /* */
00003 /*     Package for interpolation methods (one- and two-dimensional)*/
00004 /* */
00005 /*  Remarks:*/
00006 /*     None of the methods deal gracefully with missing values*/
00007 /* */
00008 /*  To do:*/
00009 /*     Add cubic splines and B-splines as methods*/
00010 /*     For spatial interpolation in two dimensions also quadrant method?*/
00011 /*     Method for destroying a table*/
00012 /*     Proper documentation*/
00013 /*     Proper test cases*/
00014 /* */
00015 /*  version 0.1: initial implementation, january 2003*/
00016 /*  version 0.2: added linear and Lagrange interpolation, straightforward*/
00017 /*               spatial interpolation, april 2004*/
00018 /*  version 0.3: added Neville algorithm.*/
00019 /*  version 1.0: added cubic splines, september 2004*/
00020 /* */
00021 /*  Copyright (c) 2004 by Arjen Markus. All rights reserved.*/
00022 /*  Copyright (c) 2004 by Kevin B. Kenny.  All rights reserved.*/
00023 /* */
00024 /*  See the file "license.terms" for information on usage and redistribution*/
00025 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00026 /* */
00027 /*  RCS: @(#) $Id: interpolate.tcl,v 1.9 2006/09/20 22:16:01 andreas_kupries Exp $*/
00028 /* */
00029 /* ----------------------------------------------------------------------*/
00030 
00031 package require Tcl 8.4
00032 package require struct::matrix
00033 
00034 /*  ::math::interpolate --*/
00035 /*    Namespace holding the procedures and variables*/
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 /*  defineTable --*/
00048 /*     Define a two-dimensional table of data*/
00049 /* */
00050 /*  Arguments:*/
00051 /*     name     Name of the table to be created*/
00052 /*     cols     Names of the columns (for convenience and for counting)*/
00053 /*     values   List of values to fill the table with (must be sorted*/
00054 /*              w.r.t. first column or first column and first row)*/
00055 /* */
00056 /*  Results:*/
00057 /*     Name of the new command*/
00058 /* */
00059 /*  Side effects:*/
00060 /*     Creates a new command, which is used in subsequent calls*/
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 /*  inter-1d-table --*/
00088 /*     Interpolate in a one-dimensional table*/
00089 /*     (first column is independent variable, all others dependent)*/
00090 /* */
00091 /*  Arguments:*/
00092 /*     table    Name of the table*/
00093 /*     xval     Value of the independent variable*/
00094 /* */
00095 /*  Results:*/
00096 /*     List of interpolated values, including the x-variable*/
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 /*  interp-table --*/
00128 /*     Interpolate in a two-dimensional table*/
00129 /*     (first column and first row are independent variables)*/
00130 /* */
00131 /*  Arguments:*/
00132 /*     table    Name of the table*/
00133 /*     xval     Value of the independent row-variable*/
00134 /*     yval     Value of the independent column-variable*/
00135 /* */
00136 /*  Results:*/
00137 /*     Interpolated value*/
00138 /* */
00139 /*  Note:*/
00140 /*     Use bilinear interpolation*/
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 /*  FindEnclosingEntries --*/
00202 /*     Search within a sorted list*/
00203 /* */
00204 /*  Arguments:*/
00205 /*     val      Value to be searched*/
00206 /*     values   List of values to be examined*/
00207 /* */
00208 /*  Results:*/
00209 /*     Returns a list of the previous and next indices*/
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 /*  interp-linear --*/
00238 /*     Use linear interpolation*/
00239 /* */
00240 /*  Arguments:*/
00241 /*     xyvalues   List of x/y values to be interpolated*/
00242 /*     xval       x-value for which a value is sought*/
00243 /* */
00244 /*  Results:*/
00245 /*     Estimated value at $xval*/
00246 /* */
00247 /*  Note:*/
00248 /*     The list xyvalues must be sorted w.r.t. the x-value*/
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 /*  interp-lagrange --*/
00286 /*     Use the Lagrange interpolation method*/
00287 /* */
00288 /*  Arguments:*/
00289 /*     xyvalues   List of x/y values to be interpolated*/
00290 /*     xval       x-value for which a value is sought*/
00291 /* */
00292 /*  Results:*/
00293 /*     Estimated value at $xval*/
00294 /* */
00295 /*  Note:*/
00296 /*     The list xyvalues must be sorted w.r.t. the x-value*/
00297 /*     Furthermore the Lagrange method is not a very practical*/
00298 /*     method, as potentially the errors are unbounded*/
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 /*  interp-spatial --*/
00336 /*     Use a straightforward interpolation method with weights as*/
00337 /*     function of the inverse distance to interpolate in 2D and N-D*/
00338 /*     space*/
00339 /* */
00340 /*  Arguments:*/
00341 /*     xyvalues   List of coordinates and values at these coordinates*/
00342 /*     coord      List of coordinates for which a value is sought*/
00343 /* */
00344 /*  Results:*/
00345 /*     Estimated value(s) at $coord*/
00346 /* */
00347 /*  Note:*/
00348 /*     The list xyvalues is a list of lists:*/
00349 /*     { {x1 y1 z1 {v11 v12 v13 v14}*/
00350 /*       {x2 y2 z2 {v21 v22 v23 v24}*/
00351 /*       ...*/
00352 /*     }*/
00353 /*     The last element of each inner list is either a single number*/
00354 /*     or a list in itself. In the latter case the return value is*/
00355 /*     a list with the same number of elements.*/
00356 /* */
00357 /*     The method is influenced by the search radius and the*/
00358 /*     power of the inverse distance*/
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 /*  interp-spatial-params --*/
00414 /*     Set the parameters for spatial interpolation*/
00415 /* */
00416 /*  Arguments:*/
00417 /*     max_search   Search radius (if none: use {} or "")*/
00418 /*     power        Power for the inverse distance (1 or 2, defaults to 2)*/
00419 /* */
00420 /*  Results:*/
00421 /*     None*/
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 /*  neville --*/
00438 /* */
00439 /*  Interpolate a function between tabulated points using Neville's*/
00440 /*  algorithm.*/
00441 /* */
00442 /*  Parameters:*/
00443 /*  xtable - Table of abscissae.*/
00444 /*  ytable - Table of ordinates.  Must be a list of the same*/
00445 /*       length as 'xtable.'*/
00446 /*  x - Abscissa for which the function value is desired.*/
00447 /* */
00448 /*  Results:*/
00449 /*  Returns a two-element list.  The first element is the*/
00450 /*  requested ordinate.  The second element is a rough estimate*/
00451 /*  of the absolute error, that is, the magnitude of the first*/
00452 /*  neglected term of a power series.*/
00453 /* */
00454 /*  Side effects:*/
00455 /*  None.*/
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 /*  prepare-cubic-splines --*/
00522 /*     Prepare interpolation based on cubic splines*/
00523 /* */
00524 /*  Arguments:*/
00525 /*     xcoord    The x-coordinates*/
00526 /*     ycoord    Y-values for these x-coordinates*/
00527 /*  Result:*/
00528 /*     Intermediate parameters describing the spline function,*/
00529 /*     to be used in the second step, interp-cubic-splines.*/
00530 /*  Note:*/
00531 /*     Implicitly it is assumed that the function decribed by xcoord*/
00532 /*     and ycoord has a second derivative 0 at the end points.*/
00533 /*     To minimise the work if more than one value is needed, the*/
00534 /*     algorithm is divided in two steps*/
00535 /*     (Derived from the routine SPLINT in Davis and Rabinowitz:*/
00536 /*     Methods for Numerical Integration, AP, 1984)*/
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 /*  interp-cubic-splines --*/
00611 /*     Interpolate based on cubic splines*/
00612 /* */
00613 /*  Arguments:*/
00614 /*     coeffs    Coefficients resulting from the preparation step*/
00615 /*     x         The x-coordinate for which to estimate the value*/
00616 /*  Result:*/
00617 /*     Interpolated value at x*/
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 /*  Announce our presence*/
00652 /* */
00653 package provide math::interpolate 1.0.2
00654 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1