calculus.tcl

Go to the documentation of this file.
00001 /*  calculus.tcl --*/
00002 /*     Package that implements several basic numerical methods, such*/
00003 /*     as the integration of a one-dimensional function and the*/
00004 /*     solution of a system of first-order differential equations.*/
00005 /* */
00006 /*  Copyright (c) 2002, 2003, 2004, 2006 by Arjen Markus.*/
00007 /*  Copyright (c) 2004 by Kevin B. Kenny.  All rights reserved.*/
00008 /*  See the file "license.terms" for information on usage and redistribution*/
00009 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00010 /* */
00011 /*  RCS: @(#) $Id: calculus.tcl,v 1.13 2006/03/28 20:36:00 arjenmarkus Exp $*/
00012 
00013 package require Tcl 8.4
00014 package require math::interpolate
00015 package provide math::calculus 0.7
00016 
00017 /*  math::calculus --*/
00018 /*     Namespace for the commands*/
00019 
00020 namespace ::math::calculus {
00021 
00022     namespace import ::math::interpolate::neville
00023 
00024     namespace import ::math::expectDouble ::math::expectInteger
00025 
00026     namespace export \
00027     integral integralExpr integral2D integral3D \
00028     eulerStep heunStep rungeKuttaStep           \
00029     boundaryValueSecondOrder solveTriDiagonal   \
00030     newtonRaphson newtonRaphsonParameters
00031     namespace export \
00032         integral2D_2accurate integral3D_accurate
00033 
00034     namespace export romberg romberg_infinity
00035     namespace export romberg_sqrtSingLower romberg_sqrtSingUpper
00036     namespace export romberg_powerLawLower romberg_powerLawUpper
00037     namespace export romberg_expLower romberg_expUpper
00038 
00039     namespace export regula_falsi
00040 
00041     variable nr_maxiter    20
00042     variable nr_tolerance   0.001
00043 
00044 }
00045 
00046 /*  integral --*/
00047 /*     Integrate a function over a given interval using the Simpson rule*/
00048 /* */
00049 /*  Arguments:*/
00050 /*     begin       Start of the interval*/
00051 /*     end         End of the interval*/
00052 /*     nosteps     Number of steps in which to divide the interval*/
00053 /*     func        Name of the function to be integrated (takes one*/
00054 /*                 argument)*/
00055 /*  Return value:*/
00056 /*     Computed integral*/
00057 /* */
00058 ret  ::math::calculus::integral ( type begin , type end , type nosteps , type func ) {
00059 
00060    set delta    [expr {($end-$begin)/double($nosteps)}]
00061    set hdelta   [expr {$delta/2.0}]
00062    set result   0.0
00063    set xval     $begin
00064    set func_end [uplevel 1 $func $xval]
00065    for { set i 1 } { $i <= $nosteps } { incr i } {
00066       set func_begin  $func_end
00067       set func_middle [uplevel 1 $func [expr {$xval+$hdelta}]]
00068       set func_end    [uplevel 1 $func [expr {$xval+$delta}]]
00069       set result      [expr  {$result+$func_begin+4.0*$func_middle+$func_end}]
00070 
00071       set xval        [expr {$begin+double($i)*$delta}]
00072    }
00073 
00074    return [expr {$result*$delta/6.0}]
00075 }
00076 
00077 /*  integralExpr --*/
00078 /*     Integrate an expression with "x" as the integrate according to the*/
00079 /*     Simpson rule*/
00080 /* */
00081 /*  Arguments:*/
00082 /*     begin       Start of the interval*/
00083 /*     end         End of the interval*/
00084 /*     nosteps     Number of steps in which to divide the interval*/
00085 /*     expression  Expression with "x" as the integrate*/
00086 /*  Return value:*/
00087 /*     Computed integral*/
00088 /* */
00089 ret  ::math::calculus::integralExpr ( type begin , type end , type nosteps , type expression ) {
00090 
00091    set delta    [expr {($end-$begin)/double($nosteps)}]
00092    set hdelta   [expr {$delta/2.0}]
00093    set result   0.0
00094    set x        $begin
00095    # FRINK: nocheck
00096    set func_end [expr $expression]
00097    for { set i 1 } { $i <= $nosteps } { incr i } {
00098       set func_begin  $func_end
00099       set x           [expr {$x+$hdelta}]
00100        # FRINK: nocheck
00101       set func_middle [expr $expression]
00102       set x           [expr {$x+$hdelta}]
00103        # FRINK: nocheck
00104       set func_end    [expr $expression]
00105       set result      [expr {$result+$func_begin+4.0*$func_middle+$func_end}]
00106 
00107       set x           [expr {$begin+double($i)*$delta}]
00108    }
00109 
00110    return [expr {$result*$delta/6.0}]
00111 }
00112 
00113 /*  integral2D --*/
00114 /*     Integrate a given fucntion of two variables over a block,*/
00115 /*     using bilinear interpolation (for this moment: block function)*/
00116 /* */
00117 /*  Arguments:*/
00118 /*     xinterval   Start, stop and number of steps of the "x" interval*/
00119 /*     yinterval   Start, stop and number of steps of the "y" interval*/
00120 /*     func        Function of the two variables to be integrated*/
00121 /*  Return value:*/
00122 /*     Computed integral*/
00123 /* */
00124 ret  ::math::calculus::integral2D ( type xinterval , type yinterval , type func ) {
00125 
00126    foreach { xbegin xend xnumber } $xinterval { break }
00127    foreach { ybegin yend ynumber } $yinterval { break }
00128 
00129    set xdelta    [expr {($xend-$xbegin)/double($xnumber)}]
00130    set ydelta    [expr {($yend-$ybegin)/double($ynumber)}]
00131    set hxdelta   [expr {$xdelta/2.0}]
00132    set hydelta   [expr {$ydelta/2.0}]
00133    set result   0.0
00134    set dxdy      [expr {$xdelta*$ydelta}]
00135    for { set j 0 } { $j < $ynumber } { incr j } {
00136       set y [expr {$ybegin+$hydelta+double($j)*$ydelta}]
00137       for { set i 0 } { $i < $xnumber } { incr i } {
00138          set x           [expr {$xbegin+$hxdelta+double($i)*$xdelta}]
00139          set func_value  [uplevel 1 $func $x $y]
00140          set result      [expr {$result+$func_value}]
00141       }
00142    }
00143 
00144    return [expr {$result*$dxdy}]
00145 }
00146 
00147 /*  integral3D --*/
00148 /*     Integrate a given fucntion of two variables over a block,*/
00149 /*     using trilinear interpolation (for this moment: block function)*/
00150 /* */
00151 /*  Arguments:*/
00152 /*     xinterval   Start, stop and number of steps of the "x" interval*/
00153 /*     yinterval   Start, stop and number of steps of the "y" interval*/
00154 /*     zinterval   Start, stop and number of steps of the "z" interval*/
00155 /*     func        Function of the three variables to be integrated*/
00156 /*  Return value:*/
00157 /*     Computed integral*/
00158 /* */
00159 ret  ::math::calculus::integral3D ( type xinterval , type yinterval , type zinterval , type func ) {
00160 
00161    foreach { xbegin xend xnumber } $xinterval { break }
00162    foreach { ybegin yend ynumber } $yinterval { break }
00163    foreach { zbegin zend znumber } $zinterval { break }
00164 
00165    set xdelta    [expr {($xend-$xbegin)/double($xnumber)}]
00166    set ydelta    [expr {($yend-$ybegin)/double($ynumber)}]
00167    set zdelta    [expr {($zend-$zbegin)/double($znumber)}]
00168    set hxdelta   [expr {$xdelta/2.0}]
00169    set hydelta   [expr {$ydelta/2.0}]
00170    set hzdelta   [expr {$zdelta/2.0}]
00171    set result   0.0
00172    set dxdydz    [expr {$xdelta*$ydelta*$zdelta}]
00173    for { set k 0 } { $k < $znumber } { incr k } {
00174       set z [expr {$zbegin+$hzdelta+double($k)*$zdelta}]
00175       for { set j 0 } { $j < $ynumber } { incr j } {
00176          set y [expr {$ybegin+$hydelta+double($j)*$ydelta}]
00177          for { set i 0 } { $i < $xnumber } { incr i } {
00178             set x           [expr {$xbegin+$hxdelta+double($i)*$xdelta}]
00179             set func_value  [uplevel 1 $func $x $y $z]
00180             set result      [expr {$result+$func_value}]
00181          }
00182       }
00183    }
00184 
00185    return [expr {$result*$dxdydz}]
00186 }
00187 
00188 /*  integral2D_accurate --*/
00189 /*     Integrate a given function of two variables over a block,*/
00190 /*     using a four-point quadrature formula*/
00191 /* */
00192 /*  Arguments:*/
00193 /*     xinterval   Start, stop and number of steps of the "x" interval*/
00194 /*     yinterval   Start, stop and number of steps of the "y" interval*/
00195 /*     func        Function of the two variables to be integrated*/
00196 /*  Return value:*/
00197 /*     Computed integral*/
00198 /* */
00199 ret  ::math::calculus::integral2D_accurate ( type xinterval , type yinterval , type func ) {
00200 
00201     foreach { xbegin xend xnumber } $xinterval { break }
00202     foreach { ybegin yend ynumber } $yinterval { break }
00203 
00204     set alpha     [expr {sqrt(2.0/3.0)}]
00205     set minalpha  [expr {-$alpha}]
00206     set dpoints   [list $alpha 0.0 $minalpha 0.0 0.0 $alpha 0.0 $minalpha]
00207 
00208     set xdelta    [expr {($xend-$xbegin)/double($xnumber)}]
00209     set ydelta    [expr {($yend-$ybegin)/double($ynumber)}]
00210     set hxdelta   [expr {$xdelta/2.0}]
00211     set hydelta   [expr {$ydelta/2.0}]
00212     set result   0.0
00213     set dxdy      [expr {0.25*$xdelta*$ydelta}]
00214 
00215     for { set j 0 } { $j < $ynumber } { incr j } {
00216         set y [expr {$ybegin+$hydelta+double($j)*$ydelta}]
00217         for { set i 0 } { $i < $xnumber } { incr i } {
00218             set x [expr {$xbegin+$hxdelta+double($i)*$xdelta}]
00219 
00220             foreach {dx dy} $dpoints {
00221                 set x1          [expr {$x+$dx}]
00222                 set y1          [expr {$y+$dy}]
00223                 set func_value  [uplevel 1 $func $x1 $y1]
00224                 set result      [expr {$result+$func_value}]
00225             }
00226         }
00227     }
00228 
00229     return [expr {$result*$dxdy}]
00230 }
00231 
00232 /*  integral3D_accurate --*/
00233 /*     Integrate a given function of three variables over a block,*/
00234 /*     using an 8-point quadrature formula*/
00235 /* */
00236 /*  Arguments:*/
00237 /*     xinterval   Start, stop and number of steps of the "x" interval*/
00238 /*     yinterval   Start, stop and number of steps of the "y" interval*/
00239 /*     zinterval   Start, stop and number of steps of the "z" interval*/
00240 /*     func        Function of the three variables to be integrated*/
00241 /*  Return value:*/
00242 /*     Computed integral*/
00243 /* */
00244 ret  ::math::calculus::integral3D_accurate ( type xinterval , type yinterval , type zinterval , type func ) {
00245 
00246     foreach { xbegin xend xnumber } $xinterval { break }
00247     foreach { ybegin yend ynumber } $yinterval { break }
00248     foreach { zbegin zend znumber } $zinterval { break }
00249 
00250     set alpha     [expr {sqrt(1.0/3.0)}]
00251     set minalpha  [expr {-$alpha}]
00252 
00253     set dpoints   [list $alpha    $alpha    $alpha    \
00254                         $alpha    $alpha    $minalpha \
00255                         $alpha    $minalpha $alpha    \
00256                         $alpha    $minalpha $minalpha \
00257                         $minalpha $alpha    $alpha    \
00258                         $minalpha $alpha    $minalpha \
00259                         $minalpha $minalpha $alpha    \
00260                         $minalpha $minalpha $minalpha ]
00261 
00262     set xdelta    [expr {($xend-$xbegin)/double($xnumber)}]
00263     set ydelta    [expr {($yend-$ybegin)/double($ynumber)}]
00264     set zdelta    [expr {($zend-$zbegin)/double($znumber)}]
00265     set hxdelta   [expr {$xdelta/2.0}]
00266     set hydelta   [expr {$ydelta/2.0}]
00267     set hzdelta   [expr {$zdelta/2.0}]
00268     set result    0.0
00269     set dxdydz    [expr {0.125*$xdelta*$ydelta*$zdelta}]
00270 
00271     for { set k 0 } { $k < $znumber } { incr k } {
00272         set z [expr {$zbegin+$hzdelta+double($k)*$zdelta}]
00273         for { set j 0 } { $j < $ynumber } { incr j } {
00274             set y [expr {$ybegin+$hydelta+double($j)*$ydelta}]
00275             for { set i 0 } { $i < $xnumber } { incr i } {
00276                 set x [expr {$xbegin+$hxdelta+double($i)*$xdelta}]
00277 
00278                 foreach {dx dy dz} $dpoints {
00279                     set x1 [expr {$x+$dx}]
00280                     set y1 [expr {$y+$dy}]
00281                     set z1 [expr {$z+$dz}]
00282                     set func_value  [uplevel 1 $func $x1 $y1 $z1]
00283                     set result      [expr {$result+$func_value}]
00284                 }
00285             }
00286         }
00287     }
00288 
00289     return [expr {$result*$dxdydz}]
00290 }
00291 
00292 /*  eulerStep --*/
00293 /*     Integrate a system of ordinary differential equations of the type*/
00294 /*     x' = f(x,t), where x is a vector of quantities. Integration is*/
00295 /*     done over a single step according to Euler's method.*/
00296 /* */
00297 /*  Arguments:*/
00298 /*     t           Start value of independent variable (time for instance)*/
00299 /*     tstep       Step size of interval*/
00300 /*     xvec        Vector of dependent values at the start*/
00301 /*     func        Function taking the arguments t and xvec to return*/
00302 /*                 the derivative of each dependent variable.*/
00303 /*  Return value:*/
00304 /*     List of values at the end of the step*/
00305 /* */
00306 ret  ::math::calculus::eulerStep ( type t , type tstep , type xvec , type func ) {
00307 
00308    set xderiv   [uplevel 1 $func $t [list $xvec]]
00309    set result   {}
00310    foreach xv $xvec dx $xderiv {
00311       set xnew [expr {$xv+$tstep*$dx}]
00312       lappend result $xnew
00313    }
00314 
00315    return $result
00316 }
00317 
00318 /*  heunStep --*/
00319 /*     Integrate a system of ordinary differential equations of the type*/
00320 /*     x' = f(x,t), where x is a vector of quantities. Integration is*/
00321 /*     done over a single step according to Heun's method.*/
00322 /* */
00323 /*  Arguments:*/
00324 /*     t           Start value of independent variable (time for instance)*/
00325 /*     tstep       Step size of interval*/
00326 /*     xvec        Vector of dependent values at the start*/
00327 /*     func        Function taking the arguments t and xvec to return*/
00328 /*                 the derivative of each dependent variable.*/
00329 /*  Return value:*/
00330 /*     List of values at the end of the step*/
00331 /* */
00332 ret  ::math::calculus::heunStep ( type t , type tstep , type xvec , type func ) {
00333 
00334    #
00335    # Predictor step
00336    #
00337    set funcq    [uplevel 1 namespace which -command $func]
00338    set xpred    [eulerStep $t $tstep $xvec $funcq]
00339 
00340    #
00341    # Corrector step
00342    #
00343    set tcorr    [expr {$t+$tstep}]
00344    set xcorr    [eulerStep $t     $tstep $xpred $funcq]
00345 
00346    set result   {}
00347    foreach xv $xvec xc $xcorr {
00348       set xnew [expr {0.5*($xv+$xc)}]
00349       lappend result $xnew
00350    }
00351 
00352    return $result
00353 }
00354 
00355 /*  rungeKuttaStep --*/
00356 /*     Integrate a system of ordinary differential equations of the type*/
00357 /*     x' = f(x,t), where x is a vector of quantities. Integration is*/
00358 /*     done over a single step according to Runge-Kutta 4th order.*/
00359 /* */
00360 /*  Arguments:*/
00361 /*     t           Start value of independent variable (time for instance)*/
00362 /*     tstep       Step size of interval*/
00363 /*     xvec        Vector of dependent values at the start*/
00364 /*     func        Function taking the arguments t and xvec to return*/
00365 /*                 the derivative of each dependent variable.*/
00366 /*  Return value:*/
00367 /*     List of values at the end of the step*/
00368 /* */
00369 ret  ::math::calculus::rungeKuttaStep ( type t , type tstep , type xvec , type func ) {
00370 
00371    set funcq    [uplevel 1 namespace which -command $func]
00372 
00373    #
00374    # Four steps:
00375    # - k1 = tstep*func(t,x0)
00376    # - k2 = tstep*func(t+0.5*tstep,x0+0.5*k1)
00377    # - k3 = tstep*func(t+0.5*tstep,x0+0.5*k2)
00378    # - k4 = tstep*func(t+    tstep,x0+    k3)
00379    # - x1 = x0 + (k1+2*k2+2*k3+k4)/6
00380    #
00381    set tstep2   [expr {$tstep/2.0}]
00382    set tstep6   [expr {$tstep/6.0}]
00383 
00384    set xk1      [$funcq $t $xvec]
00385    set xvec2    {}
00386    foreach x1 $xvec xv $xk1 {
00387       lappend xvec2 [expr {$x1+$tstep2*$xv}]
00388    }
00389    set xk2      [$funcq [expr {$t+$tstep2}] $xvec2]
00390 
00391    set xvec3    {}
00392    foreach x1 $xvec xv $xk2 {
00393       lappend xvec3 [expr {$x1+$tstep2*$xv}]
00394    }
00395    set xk3      [$funcq [expr {$t+$tstep2}] $xvec3]
00396 
00397    set xvec4    {}
00398    foreach x1 $xvec xv $xk3 {
00399       lappend xvec4 [expr {$x1+$tstep*$xv}]
00400    }
00401    set xk4      [$funcq [expr {$t+$tstep}] $xvec4]
00402 
00403    set result   {}
00404    foreach x0 $xvec k1 $xk1 k2 $xk2 k3 $xk3 k4 $xk4 {
00405       set dx [expr {$k1+2.0*$k2+2.0*$k3+$k4}]
00406       lappend result [expr {$x0+$dx*$tstep6}]
00407    }
00408 
00409    return $result
00410 }
00411 
00412 /*  boundaryValueSecondOrder --*/
00413 /*     Integrate a second-order differential equation and solve for*/
00414 /*     given boundary values.*/
00415 /* */
00416 /*     The equation is (see the documentation):*/
00417 /*        d       dy   d*/
00418 /*        -- A(x) -- + -- B(x) y + C(x) y = D(x)*/
00419 /*        dx      dx   dx*/
00420 /* */
00421 /*     The procedure uses finite differences and tridiagonal matrices to*/
00422 /*     solve the equation. The boundary values are put in the matrix*/
00423 /*     directly.*/
00424 /* */
00425 /*  Arguments:*/
00426 /*     coeff_func  Name of triple-valued function for coefficients A, B, C*/
00427 /*     force_func  Name of the function providing the force term D(x)*/
00428 /*     leftbnd     Left boundary condition (list of: xvalue, boundary*/
00429 /*                 value or keyword zero-flux, zero-derivative)*/
00430 /*     rightbnd    Right boundary condition (ditto)*/
00431 /*     nostep      Number of steps*/
00432 /*  Return value:*/
00433 /*     List of x-values and calculated values (x1, y1, x2, y2, ...)*/
00434 /* */
00435 ret  ::math::calculus::boundaryValueSecondOrder (
00436    type coeff_, type func , type force_, type func , type leftbnd , type rightbnd , type nostep ) {
00437 
00438    set coeffq    [uplevel 1 namespace which -command $coeff_func]
00439    set forceq    [uplevel 1 namespace which -command $force_func]
00440 
00441    if { [llength $leftbnd] != 2 || [llength $rightbnd] != 2 } {
00442       error "Boundary condition(s) incorrect"
00443    }
00444    if { $nostep < 1 } {
00445       error "Number of steps must be larger/equal 1"
00446    }
00447 
00448    #
00449    # Set up the matrix, as three different lists and the
00450    # righthand side as the fourth
00451    #
00452    set xleft  [lindex $leftbnd 0]
00453    set xright [lindex $rightbnd 0]
00454    set xstep  [expr {($xright-$xleft)/double($nostep)}]
00455 
00456    set acoeff {}
00457    set bcoeff {}
00458    set ccoeff {}
00459    set dvalue {}
00460 
00461    set x $xleft
00462    foreach {A B C} [$coeffq $x] { break }
00463 
00464    set A1 [expr {$A/$xstep-0.5*$B}]
00465    set B1 [expr {$A/$xstep+0.5*$B+0.5*$C*$xstep}]
00466    set C1 0.0
00467 
00468    for { set i 1 } { $i <= $nostep } { incr i } {
00469       set x [expr {$xleft+double($i)*$xstep}]
00470       if { [expr {abs($x)-0.5*abs($xstep)}] < 0.0 } {
00471          set x 0.0
00472       }
00473       foreach {A B C} [$coeffq $x] { break }
00474 
00475       set A2 0.0
00476       set B2 [expr {$A/$xstep-0.5*$B+0.5*$C*$xstep}]
00477       set C2 [expr {$A/$xstep+0.5*$B}]
00478       lappend acoeff [expr {$A1+$A2}]
00479       lappend bcoeff [expr {-$B1-$B2}]
00480       lappend ccoeff [expr {$C1+$C2}]
00481       set A1 [expr {$A/$xstep-0.5*$B}]
00482       set B1 [expr {$A/$xstep+0.5*$B+0.5*$C*$xstep}]
00483       set C1 0.0
00484    }
00485    set xvec {}
00486    for { set i 0 } { $i < $nostep } { incr i } {
00487       set x [expr {$xleft+(0.5+double($i))*$xstep}]
00488       if { [expr {abs($x)-0.25*abs($xstep)}] < 0.0 } {
00489          set x 0.0
00490       }
00491       lappend xvec   $x
00492       lappend dvalue [expr {$xstep*[$forceq $x]}]
00493    }
00494 
00495    #
00496    # Substitute the boundary values
00497    #
00498    set A  [lindex $acoeff 0]
00499    set D  [lindex $dvalue 0]
00500    set D1 [expr {$D-$A*[lindex $leftbnd 1]}]
00501    set C  [lindex $ccoeff end]
00502    set D  [lindex $dvalue end]
00503    set D2 [expr {$D-$C*[lindex $rightbnd 1]}]
00504    set dvalue [concat $D1 [lrange $dvalue 1 end-1] $D2]
00505 
00506    set yvec [solveTriDiagonal $acoeff $bcoeff $ccoeff $dvalue]
00507 
00508    foreach x $xvec y $yvec {
00509       lappend result $x $y
00510    }
00511    return $result
00512 }
00513 
00514 /*  solveTriDiagonal --*/
00515 /*     Solve a system of equations Ax = b where A is a tridiagonal matrix*/
00516 /* */
00517 /*  Arguments:*/
00518 /*     acoeff      Values on lower diagonal*/
00519 /*     bcoeff      Values on main diagonal*/
00520 /*     ccoeff      Values on upper diagonal*/
00521 /*     dvalue      Values on righthand side*/
00522 /*  Return value:*/
00523 /*     List of values forming the solution*/
00524 /* */
00525 ret  ::math::calculus::solveTriDiagonal ( type acoeff , type bcoeff , type ccoeff , type dvalue ) {
00526 
00527    set nostep [llength $acoeff]
00528    #
00529    # First step: Gauss-elimination
00530    #
00531    set B [lindex $bcoeff 0]
00532    set C [lindex $ccoeff 0]
00533    set D [lindex $dvalue 0]
00534    set bcoeff2 [list $B]
00535    set dvalue2 [list $D]
00536    for { set i 1 } { $i < $nostep } { incr i } {
00537       set A2    [lindex $acoeff $i]
00538       set B2    [lindex $bcoeff $i]
00539       set C2    [lindex $ccoeff $i]
00540       set D2    [lindex $dvalue $i]
00541       set ratab [expr {$A2/$B}]
00542       set B2    [expr {$B2-$ratab*$C}]
00543       set D2    [expr {$D2-$ratab*$D}]
00544       lappend bcoeff2 $B2
00545       lappend dvalue2 $D2
00546       set B     $B2
00547       set D     $D2
00548    }
00549 
00550    #
00551    # Second step: substitution
00552    #
00553    set yvec {}
00554    set B [lindex $bcoeff2 end]
00555    set D [lindex $dvalue2 end]
00556    set y [expr {$D/$B}]
00557    for { set i [expr {$nostep-2}] } { $i >= 0 } { incr i -1 } {
00558       set yvec  [concat $y $yvec]
00559       set B     [lindex $bcoeff2 $i]
00560       set C     [lindex $ccoeff  $i]
00561       set D     [lindex $dvalue2 $i]
00562       set y     [expr {($D-$C*$y)/$B}]
00563    }
00564    set yvec [concat $y $yvec]
00565 
00566    return $yvec
00567 }
00568 
00569 /*  newtonRaphson --*/
00570 /*     Determine the root of an equation via the Newton-Raphson method*/
00571 /* */
00572 /*  Arguments:*/
00573 /*     func        Function (proc) in x*/
00574 /*     deriv       Derivative (proc) of func w.r.t. x*/
00575 /*     initval     Initial value for x*/
00576 /*  Return value:*/
00577 /*     Estimate of root*/
00578 /* */
00579 ret  ::math::calculus::newtonRaphson ( type func , type deriv , type initval ) {
00580    variable nr_maxiter
00581    variable nr_tolerance
00582 
00583    set funcq  [uplevel 1 namespace which -command $func]
00584    set derivq [uplevel 1 namespace which -command $deriv]
00585 
00586    set value $initval
00587    set diff  [expr {10.0*$nr_tolerance}]
00588 
00589    for { set i 0 } { $i < $nr_maxiter } { incr i } {
00590       if { $diff < $nr_tolerance } {
00591          break
00592       }
00593 
00594       set newval [expr {$value-[$funcq $value]/[$derivq $value]}]
00595       if { $value != 0.0 } {
00596          set diff   [expr {abs($newval-$value)/abs($value)}]
00597       } else {
00598          set diff   [expr {abs($newval-$value)}]
00599       }
00600       set value $newval
00601    }
00602 
00603    return $newval
00604 }
00605 
00606 /*  newtonRaphsonParameters --*/
00607 /*     Set the parameters for the Newton-Raphson method*/
00608 /* */
00609 /*  Arguments:*/
00610 /*     maxiter     Maximum number of iterations*/
00611 /*     tolerance   Relative precisiion of the result*/
00612 /*  Return value:*/
00613 /*     None*/
00614 /* */
00615 ret  ::math::calculus::newtonRaphsonParameters ( type maxiter , type tolerance ) {
00616    variable nr_maxiter
00617    variable nr_tolerance
00618 
00619    if { $maxiter > 0 } {
00620       set nr_maxiter $maxiter
00621    }
00622    if { $tolerance > 0 } {
00623       set nr_tolerance $tolerance
00624    }
00625 }
00626 
00627 /* ----------------------------------------------------------------------*/
00628 /* */
00629 /*  midpoint --*/
00630 /* */
00631 /*  Perform one set of steps in evaluating an integral using the*/
00632 /*  midpoint method.*/
00633 /* */
00634 /*  Usage:*/
00635 /*  midpoint f a b s ?n?*/
00636 /* */
00637 /*  Parameters:*/
00638 /*  f - function to integrate*/
00639 /*  a - One limit of integration*/
00640 /*  b - Other limit of integration.  a and b need not be in ascending*/
00641 /*      order.*/
00642 /*  s - Value returned from a previous call to midpoint (see below)*/
00643 /*  n - Step number (see below)*/
00644 /* */
00645 /*  Results:*/
00646 /*  Returns an estimate of the integral obtained by dividing the*/
00647 /*  interval into 3**n equal intervals and using the midpoint rule.*/
00648 /* */
00649 /*  Side effects:*/
00650 /*  f is evaluated 2*3**(n-1) times and may have side effects.*/
00651 /* */
00652 /*  The 'midpoint' procedure is designed for successive approximations.*/
00653 /*  It should be called initially with n==0.  On this initial call, s*/
00654 /*  is ignored.  The function is evaluated at the midpoint of the interval, and*/
00655 /*  the value is multiplied by the width of the interval to give the*/
00656 /*  coarsest possible estimate of the integral.*/
00657 /* */
00658 /*  On each iteration except the first, n should be incremented by one,*/
00659 /*  and the previous value returned from [midpoint] should be supplied*/
00660 /*  as 's'.  The function will be evaluated at additional points*/
00661 /*  to give a total of 3**n equally spaced points, and the estimate*/
00662 /*  of the integral will be updated and returned*/
00663 /* */
00664 /*  Under normal circumstances, user code will not call this function*/
00665 /*  directly. Instead, it will use ::math::calculus::romberg to*/
00666 /*  do error control and extrapolation to a zero step size.*/
00667 /* */
00668 /* ----------------------------------------------------------------------*/
00669 
00670 ret  ::math::calculus::midpoint ( type f , type a , type b , optional n =0  , optional s =0.  ) {
00671 
00672     if { $n == 0 } {
00673 
00674     # First iteration.  Simply evaluate the function at the midpoint
00675     # of the interval.
00676 
00677     set cmd $f; lappend cmd [expr { 0.5 * ( $a + $b ) }]; set v [eval $cmd]
00678     return [expr { ( $b - $a ) * $v }]
00679 
00680     } else {
00681 
00682     # Subsequent iterations. We've divided the interval into
00683     # $it subintervals.  Evaluate the function at the 1/3 and
00684     # 2/3 points of each subinterval.  Then update the estimate
00685     # of the integral that we produced on the last step with
00686     # the new sum.
00687 
00688     set it [expr { pow( 3, $n-1 ) }]
00689     set h [expr { ( $b - $a ) / ( 3. * $it ) }]
00690     set h2 [expr { $h + $h }]
00691     set x [expr { $a + 0.5 * $h }]
00692     set sum 0
00693     for { set j 0 } { $j < $it } { incr j } {
00694         set cmd $f; lappend cmd $x; set y [eval $cmd]
00695         set sum [expr { $sum + $y }]
00696         set x [expr { $x + $h2 }]
00697         set cmd $f; lappend cmd $x; set y [eval $cmd]
00698         set sum [expr { $sum + $y }]
00699         set x [expr { $x + $h}]
00700     }
00701     return [expr { ( $s + ( $b - $a ) * $sum / $it ) / 3. }]
00702 
00703     }
00704 }
00705 
00706 /* ----------------------------------------------------------------------*/
00707 /* */
00708 /*  romberg --*/
00709 /*  */
00710 /*  Compute the integral of a function over an interval using*/
00711 /*  Romberg's method.*/
00712 /* */
00713 /*  Usage:*/
00714 /*  romberg f a b ?-option value?...*/
00715 /* */
00716 /*  Parameters:*/
00717 /*  f - Function to integrate.  Must be a single Tcl command,*/
00718 /*      to which will be appended the abscissa at which the function*/
00719 /*      should be evaluated.  f should be analytic over the*/
00720 /*      region of integration, but may have a removable singularity*/
00721 /*      at either endpoint.*/
00722 /*  a - One bound of the interval*/
00723 /*  b - The other bound of the interval.  a and b need not be in*/
00724 /*      ascending order.*/
00725 /* */
00726 /*  Options:*/
00727 /*  -abserror ABSERROR*/
00728 /*      Requests that the integration be performed to make*/
00729 /*      the estimated absolute error of the integral less than*/
00730 /*      the given value.  Default is 1.e-10.*/
00731 /*  -relerror RELERROR*/
00732 /*      Requests that the integration be performed to make*/
00733 /*      the estimated absolute error of the integral less than*/
00734 /*      the given value.  Default is 1.e-6.*/
00735 /*  -degree N*/
00736 /*      Specifies the degree of the polynomial that will be*/
00737 /*      used to extrapolate to a zero step size.  -degree 0*/
00738 /*      requests integration with the midpoint rule; -degree 1*/
00739 /*      is equivalent to Simpson's 3/8 rule; higher degrees*/
00740 /*      are difficult to describe but (within reason) give*/
00741 /*      faster convergence for smooth functions.  Default is*/
00742 /*      -degree 4.*/
00743 /*  -maxiter N*/
00744 /*      Specifies the maximum number of triplings of the*/
00745 /*      number of steps to take in integration.  At most*/
00746 /*      3**N function evaluations will be performed in*/
00747 /*      integrating with -maxiter N.  The integration*/
00748 /*      will terminate at that time, even if the result*/
00749 /*      satisfies neither the -relerror nor -abserror tests.*/
00750 /* */
00751 /*  Results:*/
00752 /*  Returns a two-element list.  The first element is the estimated*/
00753 /*  value of the integral; the second is the estimated absolute*/
00754 /*  error of the value.*/
00755 /* */
00756 /* ----------------------------------------------------------------------*/
00757 
00758 ret  ::math::calculus::romberg ( type f , type a , type b , type args ) {
00759 
00760     # Replace f with a context-independent version
00761 
00762     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
00763 
00764     # Assign default parameters
00765 
00766     array set params {
00767     -abserror 1.0e-10
00768     -degree 4
00769     -relerror 1.0e-6
00770     -maxiter 14
00771     }
00772 
00773     # Extract parameters
00774 
00775     if { ( [llength $args] % 2 ) != 0 } {
00776         return -code error -errorcode [list romberg wrongNumArgs] \
00777             "wrong \# args, should be\
00778                  \"[lreplace [info level 0] 1 end \
00779                          f x1 x2 ?-option value?...]\""
00780     }
00781     foreach { key value } $args {
00782         if { ![info exists params($key)] } {
00783             return -code error -errorcode [list romberg badoption $key] \
00784                 "unknown option \"$key\",\
00785                      should be -abserror, -degree, -relerror, or -maxiter"
00786         }
00787         set params($key) $value
00788     }
00789 
00790     # Check params
00791 
00792     if { ![string is double -strict $a] } {
00793     return -code error [expectDouble $a]
00794     }
00795     if { ![string is double -strict $b] } {
00796     return -code error [expectDouble $b]
00797     }
00798     if { ![string is double -strict $params(-abserror)] } {
00799     return -code error [expectDouble $params(-abserror)]
00800     }
00801     if { ![string is integer -strict $params(-degree)] } {
00802     return -code error [expectInteger $params(-degree)]
00803     }
00804     if { ![string is integer -strict $params(-maxiter)] } {
00805     return -code error [expectInteger $params(-maxiter)]
00806     }
00807     if { ![string is double -strict $params(-relerror)] } {
00808     return -code error [expectDouble $params(-relerror)]
00809     }
00810     foreach key {-abserror -degree -maxiter -relerror} {
00811     if { $params($key) <= 0 } {
00812         return -code error -errorcode [list romberg notPositive $key] \
00813         "$key must be positive"
00814     }
00815     }
00816     if { $params(-maxiter) <= $params(-degree) } {
00817     return -code error -errorcode [list romberg tooFewIter] \
00818         "-maxiter must be greater than -degree"
00819     }
00820 
00821     # Create lists of step size and sum with the given number of steps.
00822 
00823     set x [list]
00824     set y [list]
00825     set s 0;                # Current best estimate of integral
00826     set indx end-$params(-degree)
00827     set pow3 1.;            # Current step size (times b-a)
00828 
00829     # Perform successive integrations, tripling the number of steps each time
00830 
00831     for { set i 0 } { $i < $params(-maxiter) } { incr i } {
00832     set s [midpoint $f $a $b $i $s]
00833     lappend x $pow3
00834     lappend y $s
00835     set pow3 [expr { $pow3 / 9. }]
00836 
00837     # Once $degree steps have been done, start Richardson extrapolation
00838     # to a zero step size.
00839 
00840     if { $i >= $params(-degree) } {
00841         set x [lrange $x $indx end]
00842         set y [lrange $y $indx end]
00843         foreach {estimate err} [neville $x $y 0.] break
00844         if { $err < $params(-abserror)
00845          || $err < $params(-relerror) * abs($estimate) } {
00846         return [list $estimate $err]
00847         }
00848     }
00849     }
00850 
00851     # If -maxiter iterations have been done, give up, and return
00852     # with the current error estimate.
00853 
00854     return [list $estimate $err]
00855 }
00856 
00857 /* ----------------------------------------------------------------------*/
00858 /* */
00859 /*  u_infinity --*/
00860 /*  Change of variable for integrating over a half-infinite*/
00861 /*  interval*/
00862 /* */
00863 /*  Parameters:*/
00864 /*  f - Function being integrated*/
00865 /*  u - 1/x, where x is the abscissa where f is to be evaluated*/
00866 /* */
00867 /*  Results:*/
00868 /*  Returns f(1/u)/(u**2)*/
00869 /* */
00870 /*  Side effects:*/
00871 /*  Whatever f does.*/
00872 /* */
00873 /* ----------------------------------------------------------------------*/
00874 
00875 ret  ::math::calculus::u_infinity ( type f , type u ) {
00876     set cmd $f
00877     lappend cmd [expr { 1.0 / $u }]
00878     set y [eval $cmd]
00879     return [expr { $y / ( $u * $u ) }]
00880 }
00881 
00882 /* ----------------------------------------------------------------------*/
00883 /* */
00884 /*  romberg_infinity --*/
00885 /*  Evaluate a function on a half-open interval*/
00886 /* */
00887 /*  Usage:*/
00888 /*  Same as 'romberg'*/
00889 /* */
00890 /*  The 'romberg_infinity' procedure performs Romberg integration on*/
00891 /*  an interval [a,b] where an infinite a or b may be represented by*/
00892 /*  a large number (e.g. 1.e30).  It operates by a change of variable;*/
00893 /*  instead of integrating f(x) from a to b, it makes a change*/
00894 /*  of variable u = 1/x, and integrates from 1/b to 1/a f(1/u)/u**2 du.*/
00895 /* */
00896 /* ----------------------------------------------------------------------*/
00897 
00898 ret  ::math::calculus::romberg_infinity ( type f , type a , type b , type args ) {
00899     if { ![string is double -strict $a] } {
00900     return -code error [expectDouble $a]
00901     }
00902     if { ![string is double -strict $b] } {
00903     return -code error [expectDouble $b]
00904     }
00905     if { $a * $b <= 0. } {
00906         return -code error -errorcode {romberg_infinity cross-axis} \
00907             "limits of integration have opposite sign"
00908     }
00909     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
00910     set f [list u_infinity $f]
00911     return [eval [linsert $args 0 \
00912                       romberg $f [expr { 1.0 / $b }] [expr { 1.0 / $a }]]]
00913 }
00914 
00915 /* ----------------------------------------------------------------------*/
00916 /* */
00917 /*  u_sqrtSingLower --*/
00918 /*  Change of variable for integrating over an interval with*/
00919 /*  an inverse square root singularity at the lower bound.*/
00920 /* */
00921 /*  Parameters:*/
00922 /*  f - Function being integrated*/
00923 /*  a - Lower bound*/
00924 /*  u - sqrt(x-a), where x is the abscissa where f is to be evaluated*/
00925 /* */
00926 /*  Results:*/
00927 /*  Returns 2 * u * f( a + u**2 )*/
00928 /* */
00929 /*  Side effects:*/
00930 /*  Whatever f does.*/
00931 /* */
00932 /* ----------------------------------------------------------------------*/
00933 
00934 ret  ::math::calculus::u_sqrtSingLower ( type f , type a , type u ) {
00935     set cmd $f
00936     lappend cmd [expr { $a + $u * $u }]
00937     set y [eval $cmd]
00938     return [expr { 2. * $u * $y }]
00939 }
00940 
00941 /* ----------------------------------------------------------------------*/
00942 /* */
00943 /*  u_sqrtSingUpper --*/
00944 /*  Change of variable for integrating over an interval with*/
00945 /*  an inverse square root singularity at the upper bound.*/
00946 /* */
00947 /*  Parameters:*/
00948 /*  f - Function being integrated*/
00949 /*  b - Upper bound*/
00950 /*  u - sqrt(b-x), where x is the abscissa where f is to be evaluated*/
00951 /* */
00952 /*  Results:*/
00953 /*  Returns 2 * u * f( b - u**2 )*/
00954 /* */
00955 /*  Side effects:*/
00956 /*  Whatever f does.*/
00957 /* */
00958 /* ----------------------------------------------------------------------*/
00959 
00960 ret  ::math::calculus::u_sqrtSingUpper ( type f , type b , type u ) {
00961     set cmd $f
00962     lappend cmd [expr { $b - $u * $u }]
00963     set y [eval $cmd]
00964     return [expr { 2. * $u * $y }]
00965 }
00966 
00967 /* ----------------------------------------------------------------------*/
00968 /* */
00969 /*  math::calculus::romberg_sqrtSingLower --*/
00970 /*  Integrate a function with an inverse square root singularity*/
00971 /*  at the lower bound*/
00972 /* */
00973 /*  Usage:*/
00974 /*  Same as 'romberg'*/
00975 /* */
00976 /*  The 'romberg_sqrtSingLower' procedure is a wrapper for 'romberg'*/
00977 /*  for integrating a function with an inverse square root singularity*/
00978 /*  at the lower bound of the interval.  It works by making the change*/
00979 /*  of variable u = sqrt( x-a ).*/
00980 /* */
00981 /* ----------------------------------------------------------------------*/
00982 
00983 ret  ::math::calculus::romberg_sqrtSingLower ( type f , type a , type b , type args ) {
00984     if { ![string is double -strict $a] } {
00985     return -code error [expectDouble $a]
00986     }
00987     if { ![string is double -strict $b] } {
00988     return -code error [expectDouble $b]
00989     }
00990     if { $a >= $b } {
00991     return -code error "limits of integration out of order"
00992     }
00993     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
00994     set f [list u_sqrtSingLower $f $a]
00995     return [eval [linsert $args 0 \
00996                  romberg $f 0 [expr { sqrt( $b - $a ) }]]]
00997 }
00998 
00999 /* ----------------------------------------------------------------------*/
01000 /* */
01001 /*  math::calculus::romberg_sqrtSingUpper --*/
01002 /*  Integrate a function with an inverse square root singularity*/
01003 /*  at the upper bound*/
01004 /* */
01005 /*  Usage:*/
01006 /*  Same as 'romberg'*/
01007 /* */
01008 /*  The 'romberg_sqrtSingUpper' procedure is a wrapper for 'romberg'*/
01009 /*  for integrating a function with an inverse square root singularity*/
01010 /*  at the upper bound of the interval.  It works by making the change*/
01011 /*  of variable u = sqrt( b-x ).*/
01012 /* */
01013 /* ----------------------------------------------------------------------*/
01014 
01015 ret  ::math::calculus::romberg_sqrtSingUpper ( type f , type a , type b , type args ) {
01016     if { ![string is double -strict $a] } {
01017     return -code error [expectDouble $a]
01018     }
01019     if { ![string is double -strict $b] } {
01020     return -code error [expectDouble $b]
01021     }
01022     if { $a >= $b } {
01023     return -code error "limits of integration out of order"
01024     }
01025     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
01026     set f [list u_sqrtSingUpper $f $b]
01027     return [eval [linsert $args 0 \
01028               romberg $f 0. [expr { sqrt( $b - $a ) }]]]
01029 }
01030 
01031 /* ----------------------------------------------------------------------*/
01032 /* */
01033 /*  u_powerLawLower --*/
01034 /*  Change of variable for integrating over an interval with*/
01035 /*  an integrable power law singularity at the lower bound.*/
01036 /* */
01037 /*  Parameters:*/
01038 /*  f - Function being integrated*/
01039 /*  gammaover1mgamma - gamma / (1 - gamma), where gamma is the power*/
01040 /*  oneover1mgamma - 1 / (1 - gamma), where gamma is the power*/
01041 /*  a - Lower limit of integration*/
01042 /*  u - Changed variable u == (x-a)**(1-gamma)*/
01043 /* */
01044 /*  Results:*/
01045 /*  Returns u**(1/1-gamma) * f(a + u**(1/1-gamma) ).*/
01046 /* */
01047 /*  Side effects:*/
01048 /*  Whatever f does.*/
01049 /* */
01050 /* ----------------------------------------------------------------------*/
01051 
01052 ret  ::math::calculus::u_powerLawLower ( type f , type gammaover1mgamma , type oneover1mgamma
01053                      , type a , type u ) {
01054     set cmd $f
01055     lappend cmd [expr { $a + pow( $u, $oneover1mgamma ) }]
01056     set y [eval $cmd]
01057     return [expr { $y * pow( $u, $gammaover1mgamma ) }]
01058 }
01059 
01060 /* ----------------------------------------------------------------------*/
01061 /* */
01062 /*  math::calculus::romberg_powerLawLower --*/
01063 /*  Integrate a function with an integrable power law singularity*/
01064 /*  at the lower bound*/
01065 /* */
01066 /*  Usage:*/
01067 /*  romberg_powerLawLower gamma f a b ?-option value...?*/
01068 /* */
01069 /*  Parameters:*/
01070 /*  gamma - Power (0<gamma<1) of the singularity*/
01071 /*  f - Function to integrate.  Must be a single Tcl command,*/
01072 /*      to which will be appended the abscissa at which the function*/
01073 /*      should be evaluated.  f is expected to have an integrable*/
01074 /*      power law singularity at the lower endpoint; that is, the*/
01075 /*      integrand is expected to diverge as (x-a)**gamma.*/
01076 /*  a - One bound of the interval*/
01077 /*  b - The other bound of the interval.  a and b need not be in*/
01078 /*      ascending order.*/
01079 /* */
01080 /*  Options:*/
01081 /*  -abserror ABSERROR*/
01082 /*      Requests that the integration be performed to make*/
01083 /*      the estimated absolute error of the integral less than*/
01084 /*      the given value.  Default is 1.e-10.*/
01085 /*  -relerror RELERROR*/
01086 /*      Requests that the integration be performed to make*/
01087 /*      the estimated absolute error of the integral less than*/
01088 /*      the given value.  Default is 1.e-6.*/
01089 /*  -degree N*/
01090 /*      Specifies the degree of the polynomial that will be*/
01091 /*      used to extrapolate to a zero step size.  -degree 0*/
01092 /*      requests integration with the midpoint rule; -degree 1*/
01093 /*      is equivalent to Simpson's 3/8 rule; higher degrees*/
01094 /*      are difficult to describe but (within reason) give*/
01095 /*      faster convergence for smooth functions.  Default is*/
01096 /*      -degree 4.*/
01097 /*  -maxiter N*/
01098 /*      Specifies the maximum number of triplings of the*/
01099 /*      number of steps to take in integration.  At most*/
01100 /*      3**N function evaluations will be performed in*/
01101 /*      integrating with -maxiter N.  The integration*/
01102 /*      will terminate at that time, even if the result*/
01103 /*      satisfies neither the -relerror nor -abserror tests.*/
01104 /* */
01105 /*  Results:*/
01106 /*  Returns a two-element list.  The first element is the estimated*/
01107 /*  value of the integral; the second is the estimated absolute*/
01108 /*  error of the value.*/
01109 /* */
01110 /*  The 'romberg_sqrtSingLower' procedure is a wrapper for 'romberg'*/
01111 /*  for integrating a function with an integrable power law singularity*/
01112 /*  at the lower bound of the interval.  It works by making the change*/
01113 /*  of variable u = (x-a)**(1-gamma).*/
01114 /* */
01115 /* ----------------------------------------------------------------------*/
01116 
01117 ret  ::math::calculus::romberg_powerLawLower ( type gamma , type f , type a , type b , type args ) {
01118     if { ![string is double -strict $gamma] } {
01119     return -code error [expectDouble $gamma]
01120     }
01121     if { $gamma <= 0.0 || $gamma >= 1.0 } {
01122     return -code error -errorcode [list romberg gammaTooBig] \
01123         "gamma must lie in the interval (0,1)"
01124     }
01125     if { ![string is double -strict $a] } {
01126     return -code error [expectDouble $a]
01127     }
01128     if { ![string is double -strict $b] } {
01129     return -code error [expectDouble $b]
01130     }
01131     if { $a >= $b } {
01132     return -code error "limits of integration out of order"
01133     }
01134     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
01135     set onemgamma [expr { 1. - $gamma }]
01136     set f [list u_powerLawLower $f \
01137            [expr { $gamma / $onemgamma }] \
01138            [expr { 1 / $onemgamma }] \
01139            $a]
01140     
01141     set limit [expr { pow( $b - $a, $onemgamma ) }]
01142     set result {}
01143     foreach v [eval [linsert $args 0 romberg $f 0 $limit]] {
01144     lappend result [expr { $v / $onemgamma }]
01145     }
01146     return $result
01147 
01148 }
01149 
01150 /* ----------------------------------------------------------------------*/
01151 /* */
01152 /*  u_powerLawLower --*/
01153 /*  Change of variable for integrating over an interval with*/
01154 /*  an integrable power law singularity at the upper bound.*/
01155 /* */
01156 /*  Parameters:*/
01157 /*  f - Function being integrated*/
01158 /*  gammaover1mgamma - gamma / (1 - gamma), where gamma is the power*/
01159 /*  oneover1mgamma - 1 / (1 - gamma), where gamma is the power*/
01160 /*  b - Upper limit of integration*/
01161 /*  u - Changed variable u == (b-x)**(1-gamma)*/
01162 /* */
01163 /*  Results:*/
01164 /*  Returns u**(1/1-gamma) * f(b-u**(1/1-gamma) ).*/
01165 /* */
01166 /*  Side effects:*/
01167 /*  Whatever f does.*/
01168 /* */
01169 /* ----------------------------------------------------------------------*/
01170 
01171 ret  ::math::calculus::u_powerLawUpper ( type f , type gammaover1mgamma , type oneover1mgamma
01172                      , type b , type u ) {
01173     set cmd $f
01174     lappend cmd [expr { $b - pow( $u, $oneover1mgamma ) }]
01175     set y [eval $cmd]
01176     return [expr { $y * pow( $u, $gammaover1mgamma ) }]
01177 }
01178 
01179 /* ----------------------------------------------------------------------*/
01180 /* */
01181 /*  math::calculus::romberg_powerLawUpper --*/
01182 /*  Integrate a function with an integrable power law singularity*/
01183 /*  at the upper bound*/
01184 /* */
01185 /*  Usage:*/
01186 /*  romberg_powerLawLower gamma f a b ?-option value...?*/
01187 /* */
01188 /*  Parameters:*/
01189 /*  gamma - Power (0<gamma<1) of the singularity*/
01190 /*  f - Function to integrate.  Must be a single Tcl command,*/
01191 /*      to which will be appended the abscissa at which the function*/
01192 /*      should be evaluated.  f is expected to have an integrable*/
01193 /*      power law singularity at the upper endpoint; that is, the*/
01194 /*      integrand is expected to diverge as (b-x)**gamma.*/
01195 /*  a - One bound of the interval*/
01196 /*  b - The other bound of the interval.  a and b need not be in*/
01197 /*      ascending order.*/
01198 /* */
01199 /*  Options:*/
01200 /*  -abserror ABSERROR*/
01201 /*      Requests that the integration be performed to make*/
01202 /*      the estimated absolute error of the integral less than*/
01203 /*      the given value.  Default is 1.e-10.*/
01204 /*  -relerror RELERROR*/
01205 /*      Requests that the integration be performed to make*/
01206 /*      the estimated absolute error of the integral less than*/
01207 /*      the given value.  Default is 1.e-6.*/
01208 /*  -degree N*/
01209 /*      Specifies the degree of the polynomial that will be*/
01210 /*      used to extrapolate to a zero step size.  -degree 0*/
01211 /*      requests integration with the midpoint rule; -degree 1*/
01212 /*      is equivalent to Simpson's 3/8 rule; higher degrees*/
01213 /*      are difficult to describe but (within reason) give*/
01214 /*      faster convergence for smooth functions.  Default is*/
01215 /*      -degree 4.*/
01216 /*  -maxiter N*/
01217 /*      Specifies the maximum number of triplings of the*/
01218 /*      number of steps to take in integration.  At most*/
01219 /*      3**N function evaluations will be performed in*/
01220 /*      integrating with -maxiter N.  The integration*/
01221 /*      will terminate at that time, even if the result*/
01222 /*      satisfies neither the -relerror nor -abserror tests.*/
01223 /* */
01224 /*  Results:*/
01225 /*  Returns a two-element list.  The first element is the estimated*/
01226 /*  value of the integral; the second is the estimated absolute*/
01227 /*  error of the value.*/
01228 /* */
01229 /*  The 'romberg_PowerLawUpper' procedure is a wrapper for 'romberg'*/
01230 /*  for integrating a function with an integrable power law singularity*/
01231 /*  at the upper bound of the interval.  It works by making the change*/
01232 /*  of variable u = (b-x)**(1-gamma).*/
01233 /* */
01234 /* ----------------------------------------------------------------------*/
01235 
01236 ret  ::math::calculus::romberg_powerLawUpper ( type gamma , type f , type a , type b , type args ) {
01237     if { ![string is double -strict $gamma] } {
01238     return -code error [expectDouble $gamma]
01239     }
01240     if { $gamma <= 0.0 || $gamma >= 1.0 } {
01241     return -code error -errorcode [list romberg gammaTooBig] \
01242         "gamma must lie in the interval (0,1)"
01243     }
01244     if { ![string is double -strict $a] } {
01245     return -code error [expectDouble $a]
01246     }
01247     if { ![string is double -strict $b] } {
01248     return -code error [expectDouble $b]
01249     }
01250     if { $a >= $b } {
01251     return -code error "limits of integration out of order"
01252     }
01253     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
01254     set onemgamma [expr { 1. - $gamma }]
01255     set f [list u_powerLawUpper $f \
01256            [expr { $gamma / $onemgamma }] \
01257            [expr { 1. / $onemgamma }] \
01258            $b]
01259     
01260     set limit [expr { pow( $b - $a, $onemgamma ) }]
01261     set result {}
01262     foreach v [eval [linsert $args 0 romberg $f 0 $limit]] {
01263     lappend result [expr { $v / $onemgamma }]
01264     }
01265     return $result
01266 
01267 }
01268 
01269 /* ----------------------------------------------------------------------*/
01270 /* */
01271 /*  u_expUpper --*/
01272 /* */
01273 /*  Change of variable to integrate a function that decays*/
01274 /*  exponentially.*/
01275 /* */
01276 /*  Parameters:*/
01277 /*  f - Function to integrate*/
01278 /*  u - Changed variable u = exp(-x)*/
01279 /* */
01280 /*  Results:*/
01281 /*  Returns (1/u)*f(-log(u))*/
01282 /* */
01283 /*  Side effects:*/
01284 /*  Whatever f does.*/
01285 /* */
01286 /* ----------------------------------------------------------------------*/
01287 
01288 ret  ::math::calculus::u_expUpper ( type f , type u ) {
01289     set cmd $f
01290     lappend cmd [expr { -log($u) }]
01291     set y [eval $cmd]
01292     return [expr { $y / $u }]
01293 }
01294 
01295 /* ----------------------------------------------------------------------*/
01296 /* */
01297 /*  romberg_expUpper --*/
01298 /* */
01299 /*  Integrate a function that decays exponentially over a*/
01300 /*  half-infinite interval.*/
01301 /* */
01302 /*  Parameters:*/
01303 /*  Same as romberg.  The upper limit of integration, 'b',*/
01304 /*  is expected to be very large.*/
01305 /* */
01306 /*  Results:*/
01307 /*  Same as romberg.*/
01308 /* */
01309 /*  The romberg_expUpper function operates by making the change of*/
01310 /*  variable, u = exp(-x).*/
01311 /* */
01312 /* ----------------------------------------------------------------------*/
01313 
01314 ret  ::math::calculus::romberg_expUpper ( type f , type a , type b , type args ) {
01315     if { ![string is double -strict $a] } {
01316     return -code error [expectDouble $a]
01317     }
01318     if { ![string is double -strict $b] } {
01319     return -code error [expectDouble $b]
01320     }
01321     if { $a >= $b } {
01322     return -code error "limits of integration out of order"
01323     }
01324     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
01325     set f [list u_expUpper $f]
01326     return [eval [linsert $args 0 \
01327               romberg $f [expr {exp(-$b)}] [expr {exp(-$a)}]]]
01328 }
01329 
01330 /* ----------------------------------------------------------------------*/
01331 /* */
01332 /*  u_expLower --*/
01333 /* */
01334 /*  Change of variable to integrate a function that grows*/
01335 /*  exponentially.*/
01336 /* */
01337 /*  Parameters:*/
01338 /*  f - Function to integrate*/
01339 /*  u - Changed variable u = exp(x)*/
01340 /* */
01341 /*  Results:*/
01342 /*  Returns (1/u)*f(log(u))*/
01343 /* */
01344 /*  Side effects:*/
01345 /*  Whatever f does.*/
01346 /* */
01347 /* ----------------------------------------------------------------------*/
01348 
01349 ret  ::math::calculus::u_expLower ( type f , type u ) {
01350     set cmd $f
01351     lappend cmd [expr { log($u) }]
01352     set y [eval $cmd]
01353     return [expr { $y / $u }]
01354 }
01355 
01356 /* ----------------------------------------------------------------------*/
01357 /* */
01358 /*  romberg_expLower --*/
01359 /* */
01360 /*  Integrate a function that grows exponentially over a*/
01361 /*  half-infinite interval.*/
01362 /* */
01363 /*  Parameters:*/
01364 /*  Same as romberg.  The lower limit of integration, 'a',*/
01365 /*  is expected to be very large and negative.*/
01366 /* */
01367 /*  Results:*/
01368 /*  Same as romberg.*/
01369 /* */
01370 /*  The romberg_expUpper function operates by making the change of*/
01371 /*  variable, u = exp(x).*/
01372 /* */
01373 /* ----------------------------------------------------------------------*/
01374 
01375 ret  ::math::calculus::romberg_expLower ( type f , type a , type b , type args ) {
01376     if { ![string is double -strict $a] } {
01377     return -code error [expectDouble $a]
01378     }
01379     if { ![string is double -strict $b] } {
01380     return -code error [expectDouble $b]
01381     }
01382     if { $a >= $b } {
01383     return -code error "limits of integration out of order"
01384     }
01385     set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
01386     set f [list u_expLower $f]
01387     return [eval [linsert $args 0 \
01388               romberg $f [expr {exp($a)}] [expr {exp($b)}]]]
01389 }
01390 
01391 
01392 /*  regula_falsi --*/
01393 /*     Compute the zero of a function via regula falsi*/
01394 /*  Arguments:*/
01395 /*     f       Name of the procedure/command that evaluates the function*/
01396 /*     xb      Start of the interval that brackets the zero*/
01397 /*     xe      End of the interval that brackets the zero*/
01398 /*     eps     Relative error that is allowed (default: 1.0e-4)*/
01399 /*  Result:*/
01400 /*     Estimate of the zero, such that the estimated (!)*/
01401 /*     error < eps * abs(xe-xb)*/
01402 /*  Note:*/
01403 /*     f(xb)*f(xe) must be negative and eps must be positive*/
01404 /* */
01405 ret  ::math::calculus::regula_falsi ( type f , type xb , type xe , optional eps =1.0e-4 ) {
01406     if { $eps <= 0.0 } {
01407        return -code error "Relative error must be positive"
01408     }
01409 
01410     set fb [$f $xb]
01411     set fe [$f $xe]
01412 
01413     if { $fb * $fe > 0.0 } {
01414        return -code error "Interval must be chosen such that the \
01415 function has a different sign at the beginning than at the end"
01416     }
01417 
01418     set max_error [expr {$eps * abs($xe-$xb)}]
01419     set interval  [expr {abs($xe-$xb)}]
01420 
01421     while { $interval > $max_error } {
01422        set coeff [expr {($fe-$fb)/($xe-$xb)}]
01423        set xi    [expr {$xb-$fb/$coeff}]
01424        set fi    [$f $xi]
01425 
01426        if { $fi == 0.0 } {
01427           break
01428        }
01429        set diff1 [expr {abs($xe-$xi)}]
01430        set diff2 [expr {abs($xb-$xi)}]
01431        if { $diff1 > $diff2 } {
01432           set interval $diff2
01433        } else {
01434           set interval $diff1
01435        }
01436 
01437        if { $fb*$fi < 0.0 } {
01438           set xe $xi
01439           set fe $fi
01440        } else {
01441           set xb $xi
01442           set fb $fi
01443        }
01444     }
01445 
01446     return $xi
01447 }
01448 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1