| 1 | # calculus.tcl -- |
|---|
| 2 | # Package that implements several basic numerical methods, such |
|---|
| 3 | # as the integration of a one-dimensional function and the |
|---|
| 4 | # solution of a system of first-order differential equations. |
|---|
| 5 | # |
|---|
| 6 | # Copyright (c) 2002, 2003, 2004, 2006 by Arjen Markus. |
|---|
| 7 | # Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. |
|---|
| 8 | # See the file "license.terms" for information on usage and redistribution |
|---|
| 9 | # of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
|---|
| 10 | # |
|---|
| 11 | # RCS: @(#) $Id: calculus.tcl,v 1.13 2006/03/28 20:36:00 arjenmarkus Exp $ |
|---|
| 12 | |
|---|
| 13 | package require Tcl 8.4 |
|---|
| 14 | package require math::interpolate |
|---|
| 15 | package provide math::calculus 0.7 |
|---|
| 16 | |
|---|
| 17 | # math::calculus -- |
|---|
| 18 | # Namespace for the commands |
|---|
| 19 | |
|---|
| 20 | namespace eval ::math::calculus { |
|---|
| 21 | |
|---|
| 22 | namespace import ::math::interpolate::neville |
|---|
| 23 | |
|---|
| 24 | namespace import ::math::expectDouble ::math::expectInteger |
|---|
| 25 | |
|---|
| 26 | namespace export \ |
|---|
| 27 | integral integralExpr integral2D integral3D \ |
|---|
| 28 | eulerStep heunStep rungeKuttaStep \ |
|---|
| 29 | boundaryValueSecondOrder solveTriDiagonal \ |
|---|
| 30 | newtonRaphson newtonRaphsonParameters |
|---|
| 31 | namespace export \ |
|---|
| 32 | integral2D_2accurate integral3D_accurate |
|---|
| 33 | |
|---|
| 34 | namespace export romberg romberg_infinity |
|---|
| 35 | namespace export romberg_sqrtSingLower romberg_sqrtSingUpper |
|---|
| 36 | namespace export romberg_powerLawLower romberg_powerLawUpper |
|---|
| 37 | namespace export romberg_expLower romberg_expUpper |
|---|
| 38 | |
|---|
| 39 | namespace export regula_falsi |
|---|
| 40 | |
|---|
| 41 | variable nr_maxiter 20 |
|---|
| 42 | variable nr_tolerance 0.001 |
|---|
| 43 | |
|---|
| 44 | } |
|---|
| 45 | |
|---|
| 46 | # integral -- |
|---|
| 47 | # Integrate a function over a given interval using the Simpson rule |
|---|
| 48 | # |
|---|
| 49 | # Arguments: |
|---|
| 50 | # begin Start of the interval |
|---|
| 51 | # end End of the interval |
|---|
| 52 | # nosteps Number of steps in which to divide the interval |
|---|
| 53 | # func Name of the function to be integrated (takes one |
|---|
| 54 | # argument) |
|---|
| 55 | # Return value: |
|---|
| 56 | # Computed integral |
|---|
| 57 | # |
|---|
| 58 | proc ::math::calculus::integral { begin end nosteps func } { |
|---|
| 59 | |
|---|
| 60 | set delta [expr {($end-$begin)/double($nosteps)}] |
|---|
| 61 | set hdelta [expr {$delta/2.0}] |
|---|
| 62 | set result 0.0 |
|---|
| 63 | set xval $begin |
|---|
| 64 | set func_end [uplevel 1 $func $xval] |
|---|
| 65 | for { set i 1 } { $i <= $nosteps } { incr i } { |
|---|
| 66 | set func_begin $func_end |
|---|
| 67 | set func_middle [uplevel 1 $func [expr {$xval+$hdelta}]] |
|---|
| 68 | set func_end [uplevel 1 $func [expr {$xval+$delta}]] |
|---|
| 69 | set result [expr {$result+$func_begin+4.0*$func_middle+$func_end}] |
|---|
| 70 | |
|---|
| 71 | set xval [expr {$begin+double($i)*$delta}] |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | return [expr {$result*$delta/6.0}] |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | # integralExpr -- |
|---|
| 78 | # Integrate an expression with "x" as the integrate according to the |
|---|
| 79 | # Simpson rule |
|---|
| 80 | # |
|---|
| 81 | # Arguments: |
|---|
| 82 | # begin Start of the interval |
|---|
| 83 | # end End of the interval |
|---|
| 84 | # nosteps Number of steps in which to divide the interval |
|---|
| 85 | # expression Expression with "x" as the integrate |
|---|
| 86 | # Return value: |
|---|
| 87 | # Computed integral |
|---|
| 88 | # |
|---|
| 89 | proc ::math::calculus::integralExpr { begin end nosteps expression } { |
|---|
| 90 | |
|---|
| 91 | set delta [expr {($end-$begin)/double($nosteps)}] |
|---|
| 92 | set hdelta [expr {$delta/2.0}] |
|---|
| 93 | set result 0.0 |
|---|
| 94 | set x $begin |
|---|
| 95 | # FRINK: nocheck |
|---|
| 96 | set func_end [expr $expression] |
|---|
| 97 | for { set i 1 } { $i <= $nosteps } { incr i } { |
|---|
| 98 | set func_begin $func_end |
|---|
| 99 | set x [expr {$x+$hdelta}] |
|---|
| 100 | # FRINK: nocheck |
|---|
| 101 | set func_middle [expr $expression] |
|---|
| 102 | set x [expr {$x+$hdelta}] |
|---|
| 103 | # FRINK: nocheck |
|---|
| 104 | set func_end [expr $expression] |
|---|
| 105 | set result [expr {$result+$func_begin+4.0*$func_middle+$func_end}] |
|---|
| 106 | |
|---|
| 107 | set x [expr {$begin+double($i)*$delta}] |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | return [expr {$result*$delta/6.0}] |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | # integral2D -- |
|---|
| 114 | # Integrate a given fucntion of two variables over a block, |
|---|
| 115 | # using bilinear interpolation (for this moment: block function) |
|---|
| 116 | # |
|---|
| 117 | # Arguments: |
|---|
| 118 | # xinterval Start, stop and number of steps of the "x" interval |
|---|
| 119 | # yinterval Start, stop and number of steps of the "y" interval |
|---|
| 120 | # func Function of the two variables to be integrated |
|---|
| 121 | # Return value: |
|---|
| 122 | # Computed integral |
|---|
| 123 | # |
|---|
| 124 | proc ::math::calculus::integral2D { xinterval yinterval func } { |
|---|
| 125 | |
|---|
| 126 | foreach { xbegin xend xnumber } $xinterval { break } |
|---|
| 127 | foreach { ybegin yend ynumber } $yinterval { break } |
|---|
| 128 | |
|---|
| 129 | set xdelta [expr {($xend-$xbegin)/double($xnumber)}] |
|---|
| 130 | set ydelta [expr {($yend-$ybegin)/double($ynumber)}] |
|---|
| 131 | set hxdelta [expr {$xdelta/2.0}] |
|---|
| 132 | set hydelta [expr {$ydelta/2.0}] |
|---|
| 133 | set result 0.0 |
|---|
| 134 | set dxdy [expr {$xdelta*$ydelta}] |
|---|
| 135 | for { set j 0 } { $j < $ynumber } { incr j } { |
|---|
| 136 | set y [expr {$ybegin+$hydelta+double($j)*$ydelta}] |
|---|
| 137 | for { set i 0 } { $i < $xnumber } { incr i } { |
|---|
| 138 | set x [expr {$xbegin+$hxdelta+double($i)*$xdelta}] |
|---|
| 139 | set func_value [uplevel 1 $func $x $y] |
|---|
| 140 | set result [expr {$result+$func_value}] |
|---|
| 141 | } |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | return [expr {$result*$dxdy}] |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | # integral3D -- |
|---|
| 148 | # Integrate a given fucntion of two variables over a block, |
|---|
| 149 | # using trilinear interpolation (for this moment: block function) |
|---|
| 150 | # |
|---|
| 151 | # Arguments: |
|---|
| 152 | # xinterval Start, stop and number of steps of the "x" interval |
|---|
| 153 | # yinterval Start, stop and number of steps of the "y" interval |
|---|
| 154 | # zinterval Start, stop and number of steps of the "z" interval |
|---|
| 155 | # func Function of the three variables to be integrated |
|---|
| 156 | # Return value: |
|---|
| 157 | # Computed integral |
|---|
| 158 | # |
|---|
| 159 | proc ::math::calculus::integral3D { xinterval yinterval zinterval func } { |
|---|
| 160 | |
|---|
| 161 | foreach { xbegin xend xnumber } $xinterval { break } |
|---|
| 162 | foreach { ybegin yend ynumber } $yinterval { break } |
|---|
| 163 | foreach { zbegin zend znumber } $zinterval { break } |
|---|
| 164 | |
|---|
| 165 | set xdelta [expr {($xend-$xbegin)/double($xnumber)}] |
|---|
| 166 | set ydelta [expr {($yend-$ybegin)/double($ynumber)}] |
|---|
| 167 | set zdelta [expr {($zend-$zbegin)/double($znumber)}] |
|---|
| 168 | set hxdelta [expr {$xdelta/2.0}] |
|---|
| 169 | set hydelta [expr {$ydelta/2.0}] |
|---|
| 170 | set hzdelta [expr {$zdelta/2.0}] |
|---|
| 171 | set result 0.0 |
|---|
| 172 | set dxdydz [expr {$xdelta*$ydelta*$zdelta}] |
|---|
| 173 | for { set k 0 } { $k < $znumber } { incr k } { |
|---|
| 174 | set z [expr {$zbegin+$hzdelta+double($k)*$zdelta}] |
|---|
| 175 | for { set j 0 } { $j < $ynumber } { incr j } { |
|---|
| 176 | set y [expr {$ybegin+$hydelta+double($j)*$ydelta}] |
|---|
| 177 | for { set i 0 } { $i < $xnumber } { incr i } { |
|---|
| 178 | set x [expr {$xbegin+$hxdelta+double($i)*$xdelta}] |
|---|
| 179 | set func_value [uplevel 1 $func $x $y $z] |
|---|
| 180 | set result [expr {$result+$func_value}] |
|---|
| 181 | } |
|---|
| 182 | } |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | return [expr {$result*$dxdydz}] |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | # integral2D_accurate -- |
|---|
| 189 | # Integrate a given function of two variables over a block, |
|---|
| 190 | # using a four-point quadrature formula |
|---|
| 191 | # |
|---|
| 192 | # Arguments: |
|---|
| 193 | # xinterval Start, stop and number of steps of the "x" interval |
|---|
| 194 | # yinterval Start, stop and number of steps of the "y" interval |
|---|
| 195 | # func Function of the two variables to be integrated |
|---|
| 196 | # Return value: |
|---|
| 197 | # Computed integral |
|---|
| 198 | # |
|---|
| 199 | proc ::math::calculus::integral2D_accurate { xinterval yinterval func } { |
|---|
| 200 | |
|---|
| 201 | foreach { xbegin xend xnumber } $xinterval { break } |
|---|
| 202 | foreach { ybegin yend ynumber } $yinterval { break } |
|---|
| 203 | |
|---|
| 204 | set alpha [expr {sqrt(2.0/3.0)}] |
|---|
| 205 | set minalpha [expr {-$alpha}] |
|---|
| 206 | set dpoints [list $alpha 0.0 $minalpha 0.0 0.0 $alpha 0.0 $minalpha] |
|---|
| 207 | |
|---|
| 208 | set xdelta [expr {($xend-$xbegin)/double($xnumber)}] |
|---|
| 209 | set ydelta [expr {($yend-$ybegin)/double($ynumber)}] |
|---|
| 210 | set hxdelta [expr {$xdelta/2.0}] |
|---|
| 211 | set hydelta [expr {$ydelta/2.0}] |
|---|
| 212 | set result 0.0 |
|---|
| 213 | set dxdy [expr {0.25*$xdelta*$ydelta}] |
|---|
| 214 | |
|---|
| 215 | for { set j 0 } { $j < $ynumber } { incr j } { |
|---|
| 216 | set y [expr {$ybegin+$hydelta+double($j)*$ydelta}] |
|---|
| 217 | for { set i 0 } { $i < $xnumber } { incr i } { |
|---|
| 218 | set x [expr {$xbegin+$hxdelta+double($i)*$xdelta}] |
|---|
| 219 | |
|---|
| 220 | foreach {dx dy} $dpoints { |
|---|
| 221 | set x1 [expr {$x+$dx}] |
|---|
| 222 | set y1 [expr {$y+$dy}] |
|---|
| 223 | set func_value [uplevel 1 $func $x1 $y1] |
|---|
| 224 | set result [expr {$result+$func_value}] |
|---|
| 225 | } |
|---|
| 226 | } |
|---|
| 227 | } |
|---|
| 228 | |
|---|
| 229 | return [expr {$result*$dxdy}] |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | # integral3D_accurate -- |
|---|
| 233 | # Integrate a given function of three variables over a block, |
|---|
| 234 | # using an 8-point quadrature formula |
|---|
| 235 | # |
|---|
| 236 | # Arguments: |
|---|
| 237 | # xinterval Start, stop and number of steps of the "x" interval |
|---|
| 238 | # yinterval Start, stop and number of steps of the "y" interval |
|---|
| 239 | # zinterval Start, stop and number of steps of the "z" interval |
|---|
| 240 | # func Function of the three variables to be integrated |
|---|
| 241 | # Return value: |
|---|
| 242 | # Computed integral |
|---|
| 243 | # |
|---|
| 244 | proc ::math::calculus::integral3D_accurate { xinterval yinterval zinterval func } { |
|---|
| 245 | |
|---|
| 246 | foreach { xbegin xend xnumber } $xinterval { break } |
|---|
| 247 | foreach { ybegin yend ynumber } $yinterval { break } |
|---|
| 248 | foreach { zbegin zend znumber } $zinterval { break } |
|---|
| 249 | |
|---|
| 250 | set alpha [expr {sqrt(1.0/3.0)}] |
|---|
| 251 | set minalpha [expr {-$alpha}] |
|---|
| 252 | |
|---|
| 253 | set dpoints [list $alpha $alpha $alpha \ |
|---|
| 254 | $alpha $alpha $minalpha \ |
|---|
| 255 | $alpha $minalpha $alpha \ |
|---|
| 256 | $alpha $minalpha $minalpha \ |
|---|
| 257 | $minalpha $alpha $alpha \ |
|---|
| 258 | $minalpha $alpha $minalpha \ |
|---|
| 259 | $minalpha $minalpha $alpha \ |
|---|
| 260 | $minalpha $minalpha $minalpha ] |
|---|
| 261 | |
|---|
| 262 | set xdelta [expr {($xend-$xbegin)/double($xnumber)}] |
|---|
| 263 | set ydelta [expr {($yend-$ybegin)/double($ynumber)}] |
|---|
| 264 | set zdelta [expr {($zend-$zbegin)/double($znumber)}] |
|---|
| 265 | set hxdelta [expr {$xdelta/2.0}] |
|---|
| 266 | set hydelta [expr {$ydelta/2.0}] |
|---|
| 267 | set hzdelta [expr {$zdelta/2.0}] |
|---|
| 268 | set result 0.0 |
|---|
| 269 | set dxdydz [expr {0.125*$xdelta*$ydelta*$zdelta}] |
|---|
| 270 | |
|---|
| 271 | for { set k 0 } { $k < $znumber } { incr k } { |
|---|
| 272 | set z [expr {$zbegin+$hzdelta+double($k)*$zdelta}] |
|---|
| 273 | for { set j 0 } { $j < $ynumber } { incr j } { |
|---|
| 274 | set y [expr {$ybegin+$hydelta+double($j)*$ydelta}] |
|---|
| 275 | for { set i 0 } { $i < $xnumber } { incr i } { |
|---|
| 276 | set x [expr {$xbegin+$hxdelta+double($i)*$xdelta}] |
|---|
| 277 | |
|---|
| 278 | foreach {dx dy dz} $dpoints { |
|---|
| 279 | set x1 [expr {$x+$dx}] |
|---|
| 280 | set y1 [expr {$y+$dy}] |
|---|
| 281 | set z1 [expr {$z+$dz}] |
|---|
| 282 | set func_value [uplevel 1 $func $x1 $y1 $z1] |
|---|
| 283 | set result [expr {$result+$func_value}] |
|---|
| 284 | } |
|---|
| 285 | } |
|---|
| 286 | } |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | return [expr {$result*$dxdydz}] |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | # eulerStep -- |
|---|
| 293 | # Integrate a system of ordinary differential equations of the type |
|---|
| 294 | # x' = f(x,t), where x is a vector of quantities. Integration is |
|---|
| 295 | # done over a single step according to Euler's method. |
|---|
| 296 | # |
|---|
| 297 | # Arguments: |
|---|
| 298 | # t Start value of independent variable (time for instance) |
|---|
| 299 | # tstep Step size of interval |
|---|
| 300 | # xvec Vector of dependent values at the start |
|---|
| 301 | # func Function taking the arguments t and xvec to return |
|---|
| 302 | # the derivative of each dependent variable. |
|---|
| 303 | # Return value: |
|---|
| 304 | # List of values at the end of the step |
|---|
| 305 | # |
|---|
| 306 | proc ::math::calculus::eulerStep { t tstep xvec func } { |
|---|
| 307 | |
|---|
| 308 | set xderiv [uplevel 1 $func $t [list $xvec]] |
|---|
| 309 | set result {} |
|---|
| 310 | foreach xv $xvec dx $xderiv { |
|---|
| 311 | set xnew [expr {$xv+$tstep*$dx}] |
|---|
| 312 | lappend result $xnew |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | return $result |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | # heunStep -- |
|---|
| 319 | # Integrate a system of ordinary differential equations of the type |
|---|
| 320 | # x' = f(x,t), where x is a vector of quantities. Integration is |
|---|
| 321 | # done over a single step according to Heun's method. |
|---|
| 322 | # |
|---|
| 323 | # Arguments: |
|---|
| 324 | # t Start value of independent variable (time for instance) |
|---|
| 325 | # tstep Step size of interval |
|---|
| 326 | # xvec Vector of dependent values at the start |
|---|
| 327 | # func Function taking the arguments t and xvec to return |
|---|
| 328 | # the derivative of each dependent variable. |
|---|
| 329 | # Return value: |
|---|
| 330 | # List of values at the end of the step |
|---|
| 331 | # |
|---|
| 332 | proc ::math::calculus::heunStep { t tstep xvec func } { |
|---|
| 333 | |
|---|
| 334 | # |
|---|
| 335 | # Predictor step |
|---|
| 336 | # |
|---|
| 337 | set funcq [uplevel 1 namespace which -command $func] |
|---|
| 338 | set xpred [eulerStep $t $tstep $xvec $funcq] |
|---|
| 339 | |
|---|
| 340 | # |
|---|
| 341 | # Corrector step |
|---|
| 342 | # |
|---|
| 343 | set tcorr [expr {$t+$tstep}] |
|---|
| 344 | set xcorr [eulerStep $t $tstep $xpred $funcq] |
|---|
| 345 | |
|---|
| 346 | set result {} |
|---|
| 347 | foreach xv $xvec xc $xcorr { |
|---|
| 348 | set xnew [expr {0.5*($xv+$xc)}] |
|---|
| 349 | lappend result $xnew |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | return $result |
|---|
| 353 | } |
|---|
| 354 | |
|---|
| 355 | # rungeKuttaStep -- |
|---|
| 356 | # Integrate a system of ordinary differential equations of the type |
|---|
| 357 | # x' = f(x,t), where x is a vector of quantities. Integration is |
|---|
| 358 | # done over a single step according to Runge-Kutta 4th order. |
|---|
| 359 | # |
|---|
| 360 | # Arguments: |
|---|
| 361 | # t Start value of independent variable (time for instance) |
|---|
| 362 | # tstep Step size of interval |
|---|
| 363 | # xvec Vector of dependent values at the start |
|---|
| 364 | # func Function taking the arguments t and xvec to return |
|---|
| 365 | # the derivative of each dependent variable. |
|---|
| 366 | # Return value: |
|---|
| 367 | # List of values at the end of the step |
|---|
| 368 | # |
|---|
| 369 | proc ::math::calculus::rungeKuttaStep { t tstep xvec func } { |
|---|
| 370 | |
|---|
| 371 | set funcq [uplevel 1 namespace which -command $func] |
|---|
| 372 | |
|---|
| 373 | # |
|---|
| 374 | # Four steps: |
|---|
| 375 | # - k1 = tstep*func(t,x0) |
|---|
| 376 | # - k2 = tstep*func(t+0.5*tstep,x0+0.5*k1) |
|---|
| 377 | # - k3 = tstep*func(t+0.5*tstep,x0+0.5*k2) |
|---|
| 378 | # - k4 = tstep*func(t+ tstep,x0+ k3) |
|---|
| 379 | # - x1 = x0 + (k1+2*k2+2*k3+k4)/6 |
|---|
| 380 | # |
|---|
| 381 | set tstep2 [expr {$tstep/2.0}] |
|---|
| 382 | set tstep6 [expr {$tstep/6.0}] |
|---|
| 383 | |
|---|
| 384 | set xk1 [$funcq $t $xvec] |
|---|
| 385 | set xvec2 {} |
|---|
| 386 | foreach x1 $xvec xv $xk1 { |
|---|
| 387 | lappend xvec2 [expr {$x1+$tstep2*$xv}] |
|---|
| 388 | } |
|---|
| 389 | set xk2 [$funcq [expr {$t+$tstep2}] $xvec2] |
|---|
| 390 | |
|---|
| 391 | set xvec3 {} |
|---|
| 392 | foreach x1 $xvec xv $xk2 { |
|---|
| 393 | lappend xvec3 [expr {$x1+$tstep2*$xv}] |
|---|
| 394 | } |
|---|
| 395 | set xk3 [$funcq [expr {$t+$tstep2}] $xvec3] |
|---|
| 396 | |
|---|
| 397 | set xvec4 {} |
|---|
| 398 | foreach x1 $xvec xv $xk3 { |
|---|
| 399 | lappend xvec4 [expr {$x1+$tstep*$xv}] |
|---|
| 400 | } |
|---|
| 401 | set xk4 [$funcq [expr {$t+$tstep}] $xvec4] |
|---|
| 402 | |
|---|
| 403 | set result {} |
|---|
| 404 | foreach x0 $xvec k1 $xk1 k2 $xk2 k3 $xk3 k4 $xk4 { |
|---|
| 405 | set dx [expr {$k1+2.0*$k2+2.0*$k3+$k4}] |
|---|
| 406 | lappend result [expr {$x0+$dx*$tstep6}] |
|---|
| 407 | } |
|---|
| 408 | |
|---|
| 409 | return $result |
|---|
| 410 | } |
|---|
| 411 | |
|---|
| 412 | # boundaryValueSecondOrder -- |
|---|
| 413 | # Integrate a second-order differential equation and solve for |
|---|
| 414 | # given boundary values. |
|---|
| 415 | # |
|---|
| 416 | # The equation is (see the documentation): |
|---|
| 417 | # d dy d |
|---|
| 418 | # -- A(x) -- + -- B(x) y + C(x) y = D(x) |
|---|
| 419 | # dx dx dx |
|---|
| 420 | # |
|---|
| 421 | # The procedure uses finite differences and tridiagonal matrices to |
|---|
| 422 | # solve the equation. The boundary values are put in the matrix |
|---|
| 423 | # directly. |
|---|
| 424 | # |
|---|
| 425 | # Arguments: |
|---|
| 426 | # coeff_func Name of triple-valued function for coefficients A, B, C |
|---|
| 427 | # force_func Name of the function providing the force term D(x) |
|---|
| 428 | # leftbnd Left boundary condition (list of: xvalue, boundary |
|---|
| 429 | # value or keyword zero-flux, zero-derivative) |
|---|
| 430 | # rightbnd Right boundary condition (ditto) |
|---|
| 431 | # nostep Number of steps |
|---|
| 432 | # Return value: |
|---|
| 433 | # List of x-values and calculated values (x1, y1, x2, y2, ...) |
|---|
| 434 | # |
|---|
| 435 | proc ::math::calculus::boundaryValueSecondOrder { |
|---|
| 436 | coeff_func force_func leftbnd rightbnd nostep } { |
|---|
| 437 | |
|---|
| 438 | set coeffq [uplevel 1 namespace which -command $coeff_func] |
|---|
| 439 | set forceq [uplevel 1 namespace which -command $force_func] |
|---|
| 440 | |
|---|
| 441 | if { [llength $leftbnd] != 2 || [llength $rightbnd] != 2 } { |
|---|
| 442 | error "Boundary condition(s) incorrect" |
|---|
| 443 | } |
|---|
| 444 | if { $nostep < 1 } { |
|---|
| 445 | error "Number of steps must be larger/equal 1" |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | # |
|---|
| 449 | # Set up the matrix, as three different lists and the |
|---|
| 450 | # righthand side as the fourth |
|---|
| 451 | # |
|---|
| 452 | set xleft [lindex $leftbnd 0] |
|---|
| 453 | set xright [lindex $rightbnd 0] |
|---|
| 454 | set xstep [expr {($xright-$xleft)/double($nostep)}] |
|---|
| 455 | |
|---|
| 456 | set acoeff {} |
|---|
| 457 | set bcoeff {} |
|---|
| 458 | set ccoeff {} |
|---|
| 459 | set dvalue {} |
|---|
| 460 | |
|---|
| 461 | set x $xleft |
|---|
| 462 | foreach {A B C} [$coeffq $x] { break } |
|---|
| 463 | |
|---|
| 464 | set A1 [expr {$A/$xstep-0.5*$B}] |
|---|
| 465 | set B1 [expr {$A/$xstep+0.5*$B+0.5*$C*$xstep}] |
|---|
| 466 | set C1 0.0 |
|---|
| 467 | |
|---|
| 468 | for { set i 1 } { $i <= $nostep } { incr i } { |
|---|
| 469 | set x [expr {$xleft+double($i)*$xstep}] |
|---|
| 470 | if { [expr {abs($x)-0.5*abs($xstep)}] < 0.0 } { |
|---|
| 471 | set x 0.0 |
|---|
| 472 | } |
|---|
| 473 | foreach {A B C} [$coeffq $x] { break } |
|---|
| 474 | |
|---|
| 475 | set A2 0.0 |
|---|
| 476 | set B2 [expr {$A/$xstep-0.5*$B+0.5*$C*$xstep}] |
|---|
| 477 | set C2 [expr {$A/$xstep+0.5*$B}] |
|---|
| 478 | lappend acoeff [expr {$A1+$A2}] |
|---|
| 479 | lappend bcoeff [expr {-$B1-$B2}] |
|---|
| 480 | lappend ccoeff [expr {$C1+$C2}] |
|---|
| 481 | set A1 [expr {$A/$xstep-0.5*$B}] |
|---|
| 482 | set B1 [expr {$A/$xstep+0.5*$B+0.5*$C*$xstep}] |
|---|
| 483 | set C1 0.0 |
|---|
| 484 | } |
|---|
| 485 | set xvec {} |
|---|
| 486 | for { set i 0 } { $i < $nostep } { incr i } { |
|---|
| 487 | set x [expr {$xleft+(0.5+double($i))*$xstep}] |
|---|
| 488 | if { [expr {abs($x)-0.25*abs($xstep)}] < 0.0 } { |
|---|
| 489 | set x 0.0 |
|---|
| 490 | } |
|---|
| 491 | lappend xvec $x |
|---|
| 492 | lappend dvalue [expr {$xstep*[$forceq $x]}] |
|---|
| 493 | } |
|---|
| 494 | |
|---|
| 495 | # |
|---|
| 496 | # Substitute the boundary values |
|---|
| 497 | # |
|---|
| 498 | set A [lindex $acoeff 0] |
|---|
| 499 | set D [lindex $dvalue 0] |
|---|
| 500 | set D1 [expr {$D-$A*[lindex $leftbnd 1]}] |
|---|
| 501 | set C [lindex $ccoeff end] |
|---|
| 502 | set D [lindex $dvalue end] |
|---|
| 503 | set D2 [expr {$D-$C*[lindex $rightbnd 1]}] |
|---|
| 504 | set dvalue [concat $D1 [lrange $dvalue 1 end-1] $D2] |
|---|
| 505 | |
|---|
| 506 | set yvec [solveTriDiagonal $acoeff $bcoeff $ccoeff $dvalue] |
|---|
| 507 | |
|---|
| 508 | foreach x $xvec y $yvec { |
|---|
| 509 | lappend result $x $y |
|---|
| 510 | } |
|---|
| 511 | return $result |
|---|
| 512 | } |
|---|
| 513 | |
|---|
| 514 | # solveTriDiagonal -- |
|---|
| 515 | # Solve a system of equations Ax = b where A is a tridiagonal matrix |
|---|
| 516 | # |
|---|
| 517 | # Arguments: |
|---|
| 518 | # acoeff Values on lower diagonal |
|---|
| 519 | # bcoeff Values on main diagonal |
|---|
| 520 | # ccoeff Values on upper diagonal |
|---|
| 521 | # dvalue Values on righthand side |
|---|
| 522 | # Return value: |
|---|
| 523 | # List of values forming the solution |
|---|
| 524 | # |
|---|
| 525 | proc ::math::calculus::solveTriDiagonal { acoeff bcoeff ccoeff dvalue } { |
|---|
| 526 | |
|---|
| 527 | set nostep [llength $acoeff] |
|---|
| 528 | # |
|---|
| 529 | # First step: Gauss-elimination |
|---|
| 530 | # |
|---|
| 531 | set B [lindex $bcoeff 0] |
|---|
| 532 | set C [lindex $ccoeff 0] |
|---|
| 533 | set D [lindex $dvalue 0] |
|---|
| 534 | set bcoeff2 [list $B] |
|---|
| 535 | set dvalue2 [list $D] |
|---|
| 536 | for { set i 1 } { $i < $nostep } { incr i } { |
|---|
| 537 | set A2 [lindex $acoeff $i] |
|---|
| 538 | set B2 [lindex $bcoeff $i] |
|---|
| 539 | set C2 [lindex $ccoeff $i] |
|---|
| 540 | set D2 [lindex $dvalue $i] |
|---|
| 541 | set ratab [expr {$A2/$B}] |
|---|
| 542 | set B2 [expr {$B2-$ratab*$C}] |
|---|
| 543 | set D2 [expr {$D2-$ratab*$D}] |
|---|
| 544 | lappend bcoeff2 $B2 |
|---|
| 545 | lappend dvalue2 $D2 |
|---|
| 546 | set B $B2 |
|---|
| 547 | set D $D2 |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | # |
|---|
| 551 | # Second step: substitution |
|---|
| 552 | # |
|---|
| 553 | set yvec {} |
|---|
| 554 | set B [lindex $bcoeff2 end] |
|---|
| 555 | set D [lindex $dvalue2 end] |
|---|
| 556 | set y [expr {$D/$B}] |
|---|
| 557 | for { set i [expr {$nostep-2}] } { $i >= 0 } { incr i -1 } { |
|---|
| 558 | set yvec [concat $y $yvec] |
|---|
| 559 | set B [lindex $bcoeff2 $i] |
|---|
| 560 | set C [lindex $ccoeff $i] |
|---|
| 561 | set D [lindex $dvalue2 $i] |
|---|
| 562 | set y [expr {($D-$C*$y)/$B}] |
|---|
| 563 | } |
|---|
| 564 | set yvec [concat $y $yvec] |
|---|
| 565 | |
|---|
| 566 | return $yvec |
|---|
| 567 | } |
|---|
| 568 | |
|---|
| 569 | # newtonRaphson -- |
|---|
| 570 | # Determine the root of an equation via the Newton-Raphson method |
|---|
| 571 | # |
|---|
| 572 | # Arguments: |
|---|
| 573 | # func Function (proc) in x |
|---|
| 574 | # deriv Derivative (proc) of func w.r.t. x |
|---|
| 575 | # initval Initial value for x |
|---|
| 576 | # Return value: |
|---|
| 577 | # Estimate of root |
|---|
| 578 | # |
|---|
| 579 | proc ::math::calculus::newtonRaphson { func deriv initval } { |
|---|
| 580 | variable nr_maxiter |
|---|
| 581 | variable nr_tolerance |
|---|
| 582 | |
|---|
| 583 | set funcq [uplevel 1 namespace which -command $func] |
|---|
| 584 | set derivq [uplevel 1 namespace which -command $deriv] |
|---|
| 585 | |
|---|
| 586 | set value $initval |
|---|
| 587 | set diff [expr {10.0*$nr_tolerance}] |
|---|
| 588 | |
|---|
| 589 | for { set i 0 } { $i < $nr_maxiter } { incr i } { |
|---|
| 590 | if { $diff < $nr_tolerance } { |
|---|
| 591 | break |
|---|
| 592 | } |
|---|
| 593 | |
|---|
| 594 | set newval [expr {$value-[$funcq $value]/[$derivq $value]}] |
|---|
| 595 | if { $value != 0.0 } { |
|---|
| 596 | set diff [expr {abs($newval-$value)/abs($value)}] |
|---|
| 597 | } else { |
|---|
| 598 | set diff [expr {abs($newval-$value)}] |
|---|
| 599 | } |
|---|
| 600 | set value $newval |
|---|
| 601 | } |
|---|
| 602 | |
|---|
| 603 | return $newval |
|---|
| 604 | } |
|---|
| 605 | |
|---|
| 606 | # newtonRaphsonParameters -- |
|---|
| 607 | # Set the parameters for the Newton-Raphson method |
|---|
| 608 | # |
|---|
| 609 | # Arguments: |
|---|
| 610 | # maxiter Maximum number of iterations |
|---|
| 611 | # tolerance Relative precisiion of the result |
|---|
| 612 | # Return value: |
|---|
| 613 | # None |
|---|
| 614 | # |
|---|
| 615 | proc ::math::calculus::newtonRaphsonParameters { maxiter tolerance } { |
|---|
| 616 | variable nr_maxiter |
|---|
| 617 | variable nr_tolerance |
|---|
| 618 | |
|---|
| 619 | if { $maxiter > 0 } { |
|---|
| 620 | set nr_maxiter $maxiter |
|---|
| 621 | } |
|---|
| 622 | if { $tolerance > 0 } { |
|---|
| 623 | set nr_tolerance $tolerance |
|---|
| 624 | } |
|---|
| 625 | } |
|---|
| 626 | |
|---|
| 627 | #---------------------------------------------------------------------- |
|---|
| 628 | # |
|---|
| 629 | # midpoint -- |
|---|
| 630 | # |
|---|
| 631 | # Perform one set of steps in evaluating an integral using the |
|---|
| 632 | # midpoint method. |
|---|
| 633 | # |
|---|
| 634 | # Usage: |
|---|
| 635 | # midpoint f a b s ?n? |
|---|
| 636 | # |
|---|
| 637 | # Parameters: |
|---|
| 638 | # f - function to integrate |
|---|
| 639 | # a - One limit of integration |
|---|
| 640 | # b - Other limit of integration. a and b need not be in ascending |
|---|
| 641 | # order. |
|---|
| 642 | # s - Value returned from a previous call to midpoint (see below) |
|---|
| 643 | # n - Step number (see below) |
|---|
| 644 | # |
|---|
| 645 | # Results: |
|---|
| 646 | # Returns an estimate of the integral obtained by dividing the |
|---|
| 647 | # interval into 3**n equal intervals and using the midpoint rule. |
|---|
| 648 | # |
|---|
| 649 | # Side effects: |
|---|
| 650 | # f is evaluated 2*3**(n-1) times and may have side effects. |
|---|
| 651 | # |
|---|
| 652 | # The 'midpoint' procedure is designed for successive approximations. |
|---|
| 653 | # It should be called initially with n==0. On this initial call, s |
|---|
| 654 | # is ignored. The function is evaluated at the midpoint of the interval, and |
|---|
| 655 | # the value is multiplied by the width of the interval to give the |
|---|
| 656 | # coarsest possible estimate of the integral. |
|---|
| 657 | # |
|---|
| 658 | # On each iteration except the first, n should be incremented by one, |
|---|
| 659 | # and the previous value returned from [midpoint] should be supplied |
|---|
| 660 | # as 's'. The function will be evaluated at additional points |
|---|
| 661 | # to give a total of 3**n equally spaced points, and the estimate |
|---|
| 662 | # of the integral will be updated and returned |
|---|
| 663 | # |
|---|
| 664 | # Under normal circumstances, user code will not call this function |
|---|
| 665 | # directly. Instead, it will use ::math::calculus::romberg to |
|---|
| 666 | # do error control and extrapolation to a zero step size. |
|---|
| 667 | # |
|---|
| 668 | #---------------------------------------------------------------------- |
|---|
| 669 | |
|---|
| 670 | proc ::math::calculus::midpoint { f a b { n 0 } { s 0. } } { |
|---|
| 671 | |
|---|
| 672 | if { $n == 0 } { |
|---|
| 673 | |
|---|
| 674 | # First iteration. Simply evaluate the function at the midpoint |
|---|
| 675 | # of the interval. |
|---|
| 676 | |
|---|
| 677 | set cmd $f; lappend cmd [expr { 0.5 * ( $a + $b ) }]; set v [eval $cmd] |
|---|
| 678 | return [expr { ( $b - $a ) * $v }] |
|---|
| 679 | |
|---|
| 680 | } else { |
|---|
| 681 | |
|---|
| 682 | # Subsequent iterations. We've divided the interval into |
|---|
| 683 | # $it subintervals. Evaluate the function at the 1/3 and |
|---|
| 684 | # 2/3 points of each subinterval. Then update the estimate |
|---|
| 685 | # of the integral that we produced on the last step with |
|---|
| 686 | # the new sum. |
|---|
| 687 | |
|---|
| 688 | set it [expr { pow( 3, $n-1 ) }] |
|---|
| 689 | set h [expr { ( $b - $a ) / ( 3. * $it ) }] |
|---|
| 690 | set h2 [expr { $h + $h }] |
|---|
| 691 | set x [expr { $a + 0.5 * $h }] |
|---|
| 692 | set sum 0 |
|---|
| 693 | for { set j 0 } { $j < $it } { incr j } { |
|---|
| 694 | set cmd $f; lappend cmd $x; set y [eval $cmd] |
|---|
| 695 | set sum [expr { $sum + $y }] |
|---|
| 696 | set x [expr { $x + $h2 }] |
|---|
| 697 | set cmd $f; lappend cmd $x; set y [eval $cmd] |
|---|
| 698 | set sum [expr { $sum + $y }] |
|---|
| 699 | set x [expr { $x + $h}] |
|---|
| 700 | } |
|---|
| 701 | return [expr { ( $s + ( $b - $a ) * $sum / $it ) / 3. }] |
|---|
| 702 | |
|---|
| 703 | } |
|---|
| 704 | } |
|---|
| 705 | |
|---|
| 706 | #---------------------------------------------------------------------- |
|---|
| 707 | # |
|---|
| 708 | # romberg -- |
|---|
| 709 | # |
|---|
| 710 | # Compute the integral of a function over an interval using |
|---|
| 711 | # Romberg's method. |
|---|
| 712 | # |
|---|
| 713 | # Usage: |
|---|
| 714 | # romberg f a b ?-option value?... |
|---|
| 715 | # |
|---|
| 716 | # Parameters: |
|---|
| 717 | # f - Function to integrate. Must be a single Tcl command, |
|---|
| 718 | # to which will be appended the abscissa at which the function |
|---|
| 719 | # should be evaluated. f should be analytic over the |
|---|
| 720 | # region of integration, but may have a removable singularity |
|---|
| 721 | # at either endpoint. |
|---|
| 722 | # a - One bound of the interval |
|---|
| 723 | # b - The other bound of the interval. a and b need not be in |
|---|
| 724 | # ascending order. |
|---|
| 725 | # |
|---|
| 726 | # Options: |
|---|
| 727 | # -abserror ABSERROR |
|---|
| 728 | # Requests that the integration be performed to make |
|---|
| 729 | # the estimated absolute error of the integral less than |
|---|
| 730 | # the given value. Default is 1.e-10. |
|---|
| 731 | # -relerror RELERROR |
|---|
| 732 | # Requests that the integration be performed to make |
|---|
| 733 | # the estimated absolute error of the integral less than |
|---|
| 734 | # the given value. Default is 1.e-6. |
|---|
| 735 | # -degree N |
|---|
| 736 | # Specifies the degree of the polynomial that will be |
|---|
| 737 | # used to extrapolate to a zero step size. -degree 0 |
|---|
| 738 | # requests integration with the midpoint rule; -degree 1 |
|---|
| 739 | # is equivalent to Simpson's 3/8 rule; higher degrees |
|---|
| 740 | # are difficult to describe but (within reason) give |
|---|
| 741 | # faster convergence for smooth functions. Default is |
|---|
| 742 | # -degree 4. |
|---|
| 743 | # -maxiter N |
|---|
| 744 | # Specifies the maximum number of triplings of the |
|---|
| 745 | # number of steps to take in integration. At most |
|---|
| 746 | # 3**N function evaluations will be performed in |
|---|
| 747 | # integrating with -maxiter N. The integration |
|---|
| 748 | # will terminate at that time, even if the result |
|---|
| 749 | # satisfies neither the -relerror nor -abserror tests. |
|---|
| 750 | # |
|---|
| 751 | # Results: |
|---|
| 752 | # Returns a two-element list. The first element is the estimated |
|---|
| 753 | # value of the integral; the second is the estimated absolute |
|---|
| 754 | # error of the value. |
|---|
| 755 | # |
|---|
| 756 | #---------------------------------------------------------------------- |
|---|
| 757 | |
|---|
| 758 | proc ::math::calculus::romberg { f a b args } { |
|---|
| 759 | |
|---|
| 760 | # Replace f with a context-independent version |
|---|
| 761 | |
|---|
| 762 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 763 | |
|---|
| 764 | # Assign default parameters |
|---|
| 765 | |
|---|
| 766 | array set params { |
|---|
| 767 | -abserror 1.0e-10 |
|---|
| 768 | -degree 4 |
|---|
| 769 | -relerror 1.0e-6 |
|---|
| 770 | -maxiter 14 |
|---|
| 771 | } |
|---|
| 772 | |
|---|
| 773 | # Extract parameters |
|---|
| 774 | |
|---|
| 775 | if { ( [llength $args] % 2 ) != 0 } { |
|---|
| 776 | return -code error -errorcode [list romberg wrongNumArgs] \ |
|---|
| 777 | "wrong \# args, should be\ |
|---|
| 778 | \"[lreplace [info level 0] 1 end \ |
|---|
| 779 | f x1 x2 ?-option value?...]\"" |
|---|
| 780 | } |
|---|
| 781 | foreach { key value } $args { |
|---|
| 782 | if { ![info exists params($key)] } { |
|---|
| 783 | return -code error -errorcode [list romberg badoption $key] \ |
|---|
| 784 | "unknown option \"$key\",\ |
|---|
| 785 | should be -abserror, -degree, -relerror, or -maxiter" |
|---|
| 786 | } |
|---|
| 787 | set params($key) $value |
|---|
| 788 | } |
|---|
| 789 | |
|---|
| 790 | # Check params |
|---|
| 791 | |
|---|
| 792 | if { ![string is double -strict $a] } { |
|---|
| 793 | return -code error [expectDouble $a] |
|---|
| 794 | } |
|---|
| 795 | if { ![string is double -strict $b] } { |
|---|
| 796 | return -code error [expectDouble $b] |
|---|
| 797 | } |
|---|
| 798 | if { ![string is double -strict $params(-abserror)] } { |
|---|
| 799 | return -code error [expectDouble $params(-abserror)] |
|---|
| 800 | } |
|---|
| 801 | if { ![string is integer -strict $params(-degree)] } { |
|---|
| 802 | return -code error [expectInteger $params(-degree)] |
|---|
| 803 | } |
|---|
| 804 | if { ![string is integer -strict $params(-maxiter)] } { |
|---|
| 805 | return -code error [expectInteger $params(-maxiter)] |
|---|
| 806 | } |
|---|
| 807 | if { ![string is double -strict $params(-relerror)] } { |
|---|
| 808 | return -code error [expectDouble $params(-relerror)] |
|---|
| 809 | } |
|---|
| 810 | foreach key {-abserror -degree -maxiter -relerror} { |
|---|
| 811 | if { $params($key) <= 0 } { |
|---|
| 812 | return -code error -errorcode [list romberg notPositive $key] \ |
|---|
| 813 | "$key must be positive" |
|---|
| 814 | } |
|---|
| 815 | } |
|---|
| 816 | if { $params(-maxiter) <= $params(-degree) } { |
|---|
| 817 | return -code error -errorcode [list romberg tooFewIter] \ |
|---|
| 818 | "-maxiter must be greater than -degree" |
|---|
| 819 | } |
|---|
| 820 | |
|---|
| 821 | # Create lists of step size and sum with the given number of steps. |
|---|
| 822 | |
|---|
| 823 | set x [list] |
|---|
| 824 | set y [list] |
|---|
| 825 | set s 0; # Current best estimate of integral |
|---|
| 826 | set indx end-$params(-degree) |
|---|
| 827 | set pow3 1.; # Current step size (times b-a) |
|---|
| 828 | |
|---|
| 829 | # Perform successive integrations, tripling the number of steps each time |
|---|
| 830 | |
|---|
| 831 | for { set i 0 } { $i < $params(-maxiter) } { incr i } { |
|---|
| 832 | set s [midpoint $f $a $b $i $s] |
|---|
| 833 | lappend x $pow3 |
|---|
| 834 | lappend y $s |
|---|
| 835 | set pow3 [expr { $pow3 / 9. }] |
|---|
| 836 | |
|---|
| 837 | # Once $degree steps have been done, start Richardson extrapolation |
|---|
| 838 | # to a zero step size. |
|---|
| 839 | |
|---|
| 840 | if { $i >= $params(-degree) } { |
|---|
| 841 | set x [lrange $x $indx end] |
|---|
| 842 | set y [lrange $y $indx end] |
|---|
| 843 | foreach {estimate err} [neville $x $y 0.] break |
|---|
| 844 | if { $err < $params(-abserror) |
|---|
| 845 | || $err < $params(-relerror) * abs($estimate) } { |
|---|
| 846 | return [list $estimate $err] |
|---|
| 847 | } |
|---|
| 848 | } |
|---|
| 849 | } |
|---|
| 850 | |
|---|
| 851 | # If -maxiter iterations have been done, give up, and return |
|---|
| 852 | # with the current error estimate. |
|---|
| 853 | |
|---|
| 854 | return [list $estimate $err] |
|---|
| 855 | } |
|---|
| 856 | |
|---|
| 857 | #---------------------------------------------------------------------- |
|---|
| 858 | # |
|---|
| 859 | # u_infinity -- |
|---|
| 860 | # Change of variable for integrating over a half-infinite |
|---|
| 861 | # interval |
|---|
| 862 | # |
|---|
| 863 | # Parameters: |
|---|
| 864 | # f - Function being integrated |
|---|
| 865 | # u - 1/x, where x is the abscissa where f is to be evaluated |
|---|
| 866 | # |
|---|
| 867 | # Results: |
|---|
| 868 | # Returns f(1/u)/(u**2) |
|---|
| 869 | # |
|---|
| 870 | # Side effects: |
|---|
| 871 | # Whatever f does. |
|---|
| 872 | # |
|---|
| 873 | #---------------------------------------------------------------------- |
|---|
| 874 | |
|---|
| 875 | proc ::math::calculus::u_infinity { f u } { |
|---|
| 876 | set cmd $f |
|---|
| 877 | lappend cmd [expr { 1.0 / $u }] |
|---|
| 878 | set y [eval $cmd] |
|---|
| 879 | return [expr { $y / ( $u * $u ) }] |
|---|
| 880 | } |
|---|
| 881 | |
|---|
| 882 | #---------------------------------------------------------------------- |
|---|
| 883 | # |
|---|
| 884 | # romberg_infinity -- |
|---|
| 885 | # Evaluate a function on a half-open interval |
|---|
| 886 | # |
|---|
| 887 | # Usage: |
|---|
| 888 | # Same as 'romberg' |
|---|
| 889 | # |
|---|
| 890 | # The 'romberg_infinity' procedure performs Romberg integration on |
|---|
| 891 | # an interval [a,b] where an infinite a or b may be represented by |
|---|
| 892 | # a large number (e.g. 1.e30). It operates by a change of variable; |
|---|
| 893 | # instead of integrating f(x) from a to b, it makes a change |
|---|
| 894 | # of variable u = 1/x, and integrates from 1/b to 1/a f(1/u)/u**2 du. |
|---|
| 895 | # |
|---|
| 896 | #---------------------------------------------------------------------- |
|---|
| 897 | |
|---|
| 898 | proc ::math::calculus::romberg_infinity { f a b args } { |
|---|
| 899 | if { ![string is double -strict $a] } { |
|---|
| 900 | return -code error [expectDouble $a] |
|---|
| 901 | } |
|---|
| 902 | if { ![string is double -strict $b] } { |
|---|
| 903 | return -code error [expectDouble $b] |
|---|
| 904 | } |
|---|
| 905 | if { $a * $b <= 0. } { |
|---|
| 906 | return -code error -errorcode {romberg_infinity cross-axis} \ |
|---|
| 907 | "limits of integration have opposite sign" |
|---|
| 908 | } |
|---|
| 909 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 910 | set f [list u_infinity $f] |
|---|
| 911 | return [eval [linsert $args 0 \ |
|---|
| 912 | romberg $f [expr { 1.0 / $b }] [expr { 1.0 / $a }]]] |
|---|
| 913 | } |
|---|
| 914 | |
|---|
| 915 | #---------------------------------------------------------------------- |
|---|
| 916 | # |
|---|
| 917 | # u_sqrtSingLower -- |
|---|
| 918 | # Change of variable for integrating over an interval with |
|---|
| 919 | # an inverse square root singularity at the lower bound. |
|---|
| 920 | # |
|---|
| 921 | # Parameters: |
|---|
| 922 | # f - Function being integrated |
|---|
| 923 | # a - Lower bound |
|---|
| 924 | # u - sqrt(x-a), where x is the abscissa where f is to be evaluated |
|---|
| 925 | # |
|---|
| 926 | # Results: |
|---|
| 927 | # Returns 2 * u * f( a + u**2 ) |
|---|
| 928 | # |
|---|
| 929 | # Side effects: |
|---|
| 930 | # Whatever f does. |
|---|
| 931 | # |
|---|
| 932 | #---------------------------------------------------------------------- |
|---|
| 933 | |
|---|
| 934 | proc ::math::calculus::u_sqrtSingLower { f a u } { |
|---|
| 935 | set cmd $f |
|---|
| 936 | lappend cmd [expr { $a + $u * $u }] |
|---|
| 937 | set y [eval $cmd] |
|---|
| 938 | return [expr { 2. * $u * $y }] |
|---|
| 939 | } |
|---|
| 940 | |
|---|
| 941 | #---------------------------------------------------------------------- |
|---|
| 942 | # |
|---|
| 943 | # u_sqrtSingUpper -- |
|---|
| 944 | # Change of variable for integrating over an interval with |
|---|
| 945 | # an inverse square root singularity at the upper bound. |
|---|
| 946 | # |
|---|
| 947 | # Parameters: |
|---|
| 948 | # f - Function being integrated |
|---|
| 949 | # b - Upper bound |
|---|
| 950 | # u - sqrt(b-x), where x is the abscissa where f is to be evaluated |
|---|
| 951 | # |
|---|
| 952 | # Results: |
|---|
| 953 | # Returns 2 * u * f( b - u**2 ) |
|---|
| 954 | # |
|---|
| 955 | # Side effects: |
|---|
| 956 | # Whatever f does. |
|---|
| 957 | # |
|---|
| 958 | #---------------------------------------------------------------------- |
|---|
| 959 | |
|---|
| 960 | proc ::math::calculus::u_sqrtSingUpper { f b u } { |
|---|
| 961 | set cmd $f |
|---|
| 962 | lappend cmd [expr { $b - $u * $u }] |
|---|
| 963 | set y [eval $cmd] |
|---|
| 964 | return [expr { 2. * $u * $y }] |
|---|
| 965 | } |
|---|
| 966 | |
|---|
| 967 | #---------------------------------------------------------------------- |
|---|
| 968 | # |
|---|
| 969 | # math::calculus::romberg_sqrtSingLower -- |
|---|
| 970 | # Integrate a function with an inverse square root singularity |
|---|
| 971 | # at the lower bound |
|---|
| 972 | # |
|---|
| 973 | # Usage: |
|---|
| 974 | # Same as 'romberg' |
|---|
| 975 | # |
|---|
| 976 | # The 'romberg_sqrtSingLower' procedure is a wrapper for 'romberg' |
|---|
| 977 | # for integrating a function with an inverse square root singularity |
|---|
| 978 | # at the lower bound of the interval. It works by making the change |
|---|
| 979 | # of variable u = sqrt( x-a ). |
|---|
| 980 | # |
|---|
| 981 | #---------------------------------------------------------------------- |
|---|
| 982 | |
|---|
| 983 | proc ::math::calculus::romberg_sqrtSingLower { f a b args } { |
|---|
| 984 | if { ![string is double -strict $a] } { |
|---|
| 985 | return -code error [expectDouble $a] |
|---|
| 986 | } |
|---|
| 987 | if { ![string is double -strict $b] } { |
|---|
| 988 | return -code error [expectDouble $b] |
|---|
| 989 | } |
|---|
| 990 | if { $a >= $b } { |
|---|
| 991 | return -code error "limits of integration out of order" |
|---|
| 992 | } |
|---|
| 993 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 994 | set f [list u_sqrtSingLower $f $a] |
|---|
| 995 | return [eval [linsert $args 0 \ |
|---|
| 996 | romberg $f 0 [expr { sqrt( $b - $a ) }]]] |
|---|
| 997 | } |
|---|
| 998 | |
|---|
| 999 | #---------------------------------------------------------------------- |
|---|
| 1000 | # |
|---|
| 1001 | # math::calculus::romberg_sqrtSingUpper -- |
|---|
| 1002 | # Integrate a function with an inverse square root singularity |
|---|
| 1003 | # at the upper bound |
|---|
| 1004 | # |
|---|
| 1005 | # Usage: |
|---|
| 1006 | # Same as 'romberg' |
|---|
| 1007 | # |
|---|
| 1008 | # The 'romberg_sqrtSingUpper' procedure is a wrapper for 'romberg' |
|---|
| 1009 | # for integrating a function with an inverse square root singularity |
|---|
| 1010 | # at the upper bound of the interval. It works by making the change |
|---|
| 1011 | # of variable u = sqrt( b-x ). |
|---|
| 1012 | # |
|---|
| 1013 | #---------------------------------------------------------------------- |
|---|
| 1014 | |
|---|
| 1015 | proc ::math::calculus::romberg_sqrtSingUpper { f a b args } { |
|---|
| 1016 | if { ![string is double -strict $a] } { |
|---|
| 1017 | return -code error [expectDouble $a] |
|---|
| 1018 | } |
|---|
| 1019 | if { ![string is double -strict $b] } { |
|---|
| 1020 | return -code error [expectDouble $b] |
|---|
| 1021 | } |
|---|
| 1022 | if { $a >= $b } { |
|---|
| 1023 | return -code error "limits of integration out of order" |
|---|
| 1024 | } |
|---|
| 1025 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 1026 | set f [list u_sqrtSingUpper $f $b] |
|---|
| 1027 | return [eval [linsert $args 0 \ |
|---|
| 1028 | romberg $f 0. [expr { sqrt( $b - $a ) }]]] |
|---|
| 1029 | } |
|---|
| 1030 | |
|---|
| 1031 | #---------------------------------------------------------------------- |
|---|
| 1032 | # |
|---|
| 1033 | # u_powerLawLower -- |
|---|
| 1034 | # Change of variable for integrating over an interval with |
|---|
| 1035 | # an integrable power law singularity at the lower bound. |
|---|
| 1036 | # |
|---|
| 1037 | # Parameters: |
|---|
| 1038 | # f - Function being integrated |
|---|
| 1039 | # gammaover1mgamma - gamma / (1 - gamma), where gamma is the power |
|---|
| 1040 | # oneover1mgamma - 1 / (1 - gamma), where gamma is the power |
|---|
| 1041 | # a - Lower limit of integration |
|---|
| 1042 | # u - Changed variable u == (x-a)**(1-gamma) |
|---|
| 1043 | # |
|---|
| 1044 | # Results: |
|---|
| 1045 | # Returns u**(1/1-gamma) * f(a + u**(1/1-gamma) ). |
|---|
| 1046 | # |
|---|
| 1047 | # Side effects: |
|---|
| 1048 | # Whatever f does. |
|---|
| 1049 | # |
|---|
| 1050 | #---------------------------------------------------------------------- |
|---|
| 1051 | |
|---|
| 1052 | proc ::math::calculus::u_powerLawLower { f gammaover1mgamma oneover1mgamma |
|---|
| 1053 | a u } { |
|---|
| 1054 | set cmd $f |
|---|
| 1055 | lappend cmd [expr { $a + pow( $u, $oneover1mgamma ) }] |
|---|
| 1056 | set y [eval $cmd] |
|---|
| 1057 | return [expr { $y * pow( $u, $gammaover1mgamma ) }] |
|---|
| 1058 | } |
|---|
| 1059 | |
|---|
| 1060 | #---------------------------------------------------------------------- |
|---|
| 1061 | # |
|---|
| 1062 | # math::calculus::romberg_powerLawLower -- |
|---|
| 1063 | # Integrate a function with an integrable power law singularity |
|---|
| 1064 | # at the lower bound |
|---|
| 1065 | # |
|---|
| 1066 | # Usage: |
|---|
| 1067 | # romberg_powerLawLower gamma f a b ?-option value...? |
|---|
| 1068 | # |
|---|
| 1069 | # Parameters: |
|---|
| 1070 | # gamma - Power (0<gamma<1) of the singularity |
|---|
| 1071 | # f - Function to integrate. Must be a single Tcl command, |
|---|
| 1072 | # to which will be appended the abscissa at which the function |
|---|
| 1073 | # should be evaluated. f is expected to have an integrable |
|---|
| 1074 | # power law singularity at the lower endpoint; that is, the |
|---|
| 1075 | # integrand is expected to diverge as (x-a)**gamma. |
|---|
| 1076 | # a - One bound of the interval |
|---|
| 1077 | # b - The other bound of the interval. a and b need not be in |
|---|
| 1078 | # ascending order. |
|---|
| 1079 | # |
|---|
| 1080 | # Options: |
|---|
| 1081 | # -abserror ABSERROR |
|---|
| 1082 | # Requests that the integration be performed to make |
|---|
| 1083 | # the estimated absolute error of the integral less than |
|---|
| 1084 | # the given value. Default is 1.e-10. |
|---|
| 1085 | # -relerror RELERROR |
|---|
| 1086 | # Requests that the integration be performed to make |
|---|
| 1087 | # the estimated absolute error of the integral less than |
|---|
| 1088 | # the given value. Default is 1.e-6. |
|---|
| 1089 | # -degree N |
|---|
| 1090 | # Specifies the degree of the polynomial that will be |
|---|
| 1091 | # used to extrapolate to a zero step size. -degree 0 |
|---|
| 1092 | # requests integration with the midpoint rule; -degree 1 |
|---|
| 1093 | # is equivalent to Simpson's 3/8 rule; higher degrees |
|---|
| 1094 | # are difficult to describe but (within reason) give |
|---|
| 1095 | # faster convergence for smooth functions. Default is |
|---|
| 1096 | # -degree 4. |
|---|
| 1097 | # -maxiter N |
|---|
| 1098 | # Specifies the maximum number of triplings of the |
|---|
| 1099 | # number of steps to take in integration. At most |
|---|
| 1100 | # 3**N function evaluations will be performed in |
|---|
| 1101 | # integrating with -maxiter N. The integration |
|---|
| 1102 | # will terminate at that time, even if the result |
|---|
| 1103 | # satisfies neither the -relerror nor -abserror tests. |
|---|
| 1104 | # |
|---|
| 1105 | # Results: |
|---|
| 1106 | # Returns a two-element list. The first element is the estimated |
|---|
| 1107 | # value of the integral; the second is the estimated absolute |
|---|
| 1108 | # error of the value. |
|---|
| 1109 | # |
|---|
| 1110 | # The 'romberg_sqrtSingLower' procedure is a wrapper for 'romberg' |
|---|
| 1111 | # for integrating a function with an integrable power law singularity |
|---|
| 1112 | # at the lower bound of the interval. It works by making the change |
|---|
| 1113 | # of variable u = (x-a)**(1-gamma). |
|---|
| 1114 | # |
|---|
| 1115 | #---------------------------------------------------------------------- |
|---|
| 1116 | |
|---|
| 1117 | proc ::math::calculus::romberg_powerLawLower { gamma f a b args } { |
|---|
| 1118 | if { ![string is double -strict $gamma] } { |
|---|
| 1119 | return -code error [expectDouble $gamma] |
|---|
| 1120 | } |
|---|
| 1121 | if { $gamma <= 0.0 || $gamma >= 1.0 } { |
|---|
| 1122 | return -code error -errorcode [list romberg gammaTooBig] \ |
|---|
| 1123 | "gamma must lie in the interval (0,1)" |
|---|
| 1124 | } |
|---|
| 1125 | if { ![string is double -strict $a] } { |
|---|
| 1126 | return -code error [expectDouble $a] |
|---|
| 1127 | } |
|---|
| 1128 | if { ![string is double -strict $b] } { |
|---|
| 1129 | return -code error [expectDouble $b] |
|---|
| 1130 | } |
|---|
| 1131 | if { $a >= $b } { |
|---|
| 1132 | return -code error "limits of integration out of order" |
|---|
| 1133 | } |
|---|
| 1134 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 1135 | set onemgamma [expr { 1. - $gamma }] |
|---|
| 1136 | set f [list u_powerLawLower $f \ |
|---|
| 1137 | [expr { $gamma / $onemgamma }] \ |
|---|
| 1138 | [expr { 1 / $onemgamma }] \ |
|---|
| 1139 | $a] |
|---|
| 1140 | |
|---|
| 1141 | set limit [expr { pow( $b - $a, $onemgamma ) }] |
|---|
| 1142 | set result {} |
|---|
| 1143 | foreach v [eval [linsert $args 0 romberg $f 0 $limit]] { |
|---|
| 1144 | lappend result [expr { $v / $onemgamma }] |
|---|
| 1145 | } |
|---|
| 1146 | return $result |
|---|
| 1147 | |
|---|
| 1148 | } |
|---|
| 1149 | |
|---|
| 1150 | #---------------------------------------------------------------------- |
|---|
| 1151 | # |
|---|
| 1152 | # u_powerLawLower -- |
|---|
| 1153 | # Change of variable for integrating over an interval with |
|---|
| 1154 | # an integrable power law singularity at the upper bound. |
|---|
| 1155 | # |
|---|
| 1156 | # Parameters: |
|---|
| 1157 | # f - Function being integrated |
|---|
| 1158 | # gammaover1mgamma - gamma / (1 - gamma), where gamma is the power |
|---|
| 1159 | # oneover1mgamma - 1 / (1 - gamma), where gamma is the power |
|---|
| 1160 | # b - Upper limit of integration |
|---|
| 1161 | # u - Changed variable u == (b-x)**(1-gamma) |
|---|
| 1162 | # |
|---|
| 1163 | # Results: |
|---|
| 1164 | # Returns u**(1/1-gamma) * f(b-u**(1/1-gamma) ). |
|---|
| 1165 | # |
|---|
| 1166 | # Side effects: |
|---|
| 1167 | # Whatever f does. |
|---|
| 1168 | # |
|---|
| 1169 | #---------------------------------------------------------------------- |
|---|
| 1170 | |
|---|
| 1171 | proc ::math::calculus::u_powerLawUpper { f gammaover1mgamma oneover1mgamma |
|---|
| 1172 | b u } { |
|---|
| 1173 | set cmd $f |
|---|
| 1174 | lappend cmd [expr { $b - pow( $u, $oneover1mgamma ) }] |
|---|
| 1175 | set y [eval $cmd] |
|---|
| 1176 | return [expr { $y * pow( $u, $gammaover1mgamma ) }] |
|---|
| 1177 | } |
|---|
| 1178 | |
|---|
| 1179 | #---------------------------------------------------------------------- |
|---|
| 1180 | # |
|---|
| 1181 | # math::calculus::romberg_powerLawUpper -- |
|---|
| 1182 | # Integrate a function with an integrable power law singularity |
|---|
| 1183 | # at the upper bound |
|---|
| 1184 | # |
|---|
| 1185 | # Usage: |
|---|
| 1186 | # romberg_powerLawLower gamma f a b ?-option value...? |
|---|
| 1187 | # |
|---|
| 1188 | # Parameters: |
|---|
| 1189 | # gamma - Power (0<gamma<1) of the singularity |
|---|
| 1190 | # f - Function to integrate. Must be a single Tcl command, |
|---|
| 1191 | # to which will be appended the abscissa at which the function |
|---|
| 1192 | # should be evaluated. f is expected to have an integrable |
|---|
| 1193 | # power law singularity at the upper endpoint; that is, the |
|---|
| 1194 | # integrand is expected to diverge as (b-x)**gamma. |
|---|
| 1195 | # a - One bound of the interval |
|---|
| 1196 | # b - The other bound of the interval. a and b need not be in |
|---|
| 1197 | # ascending order. |
|---|
| 1198 | # |
|---|
| 1199 | # Options: |
|---|
| 1200 | # -abserror ABSERROR |
|---|
| 1201 | # Requests that the integration be performed to make |
|---|
| 1202 | # the estimated absolute error of the integral less than |
|---|
| 1203 | # the given value. Default is 1.e-10. |
|---|
| 1204 | # -relerror RELERROR |
|---|
| 1205 | # Requests that the integration be performed to make |
|---|
| 1206 | # the estimated absolute error of the integral less than |
|---|
| 1207 | # the given value. Default is 1.e-6. |
|---|
| 1208 | # -degree N |
|---|
| 1209 | # Specifies the degree of the polynomial that will be |
|---|
| 1210 | # used to extrapolate to a zero step size. -degree 0 |
|---|
| 1211 | # requests integration with the midpoint rule; -degree 1 |
|---|
| 1212 | # is equivalent to Simpson's 3/8 rule; higher degrees |
|---|
| 1213 | # are difficult to describe but (within reason) give |
|---|
| 1214 | # faster convergence for smooth functions. Default is |
|---|
| 1215 | # -degree 4. |
|---|
| 1216 | # -maxiter N |
|---|
| 1217 | # Specifies the maximum number of triplings of the |
|---|
| 1218 | # number of steps to take in integration. At most |
|---|
| 1219 | # 3**N function evaluations will be performed in |
|---|
| 1220 | # integrating with -maxiter N. The integration |
|---|
| 1221 | # will terminate at that time, even if the result |
|---|
| 1222 | # satisfies neither the -relerror nor -abserror tests. |
|---|
| 1223 | # |
|---|
| 1224 | # Results: |
|---|
| 1225 | # Returns a two-element list. The first element is the estimated |
|---|
| 1226 | # value of the integral; the second is the estimated absolute |
|---|
| 1227 | # error of the value. |
|---|
| 1228 | # |
|---|
| 1229 | # The 'romberg_PowerLawUpper' procedure is a wrapper for 'romberg' |
|---|
| 1230 | # for integrating a function with an integrable power law singularity |
|---|
| 1231 | # at the upper bound of the interval. It works by making the change |
|---|
| 1232 | # of variable u = (b-x)**(1-gamma). |
|---|
| 1233 | # |
|---|
| 1234 | #---------------------------------------------------------------------- |
|---|
| 1235 | |
|---|
| 1236 | proc ::math::calculus::romberg_powerLawUpper { gamma f a b args } { |
|---|
| 1237 | if { ![string is double -strict $gamma] } { |
|---|
| 1238 | return -code error [expectDouble $gamma] |
|---|
| 1239 | } |
|---|
| 1240 | if { $gamma <= 0.0 || $gamma >= 1.0 } { |
|---|
| 1241 | return -code error -errorcode [list romberg gammaTooBig] \ |
|---|
| 1242 | "gamma must lie in the interval (0,1)" |
|---|
| 1243 | } |
|---|
| 1244 | if { ![string is double -strict $a] } { |
|---|
| 1245 | return -code error [expectDouble $a] |
|---|
| 1246 | } |
|---|
| 1247 | if { ![string is double -strict $b] } { |
|---|
| 1248 | return -code error [expectDouble $b] |
|---|
| 1249 | } |
|---|
| 1250 | if { $a >= $b } { |
|---|
| 1251 | return -code error "limits of integration out of order" |
|---|
| 1252 | } |
|---|
| 1253 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 1254 | set onemgamma [expr { 1. - $gamma }] |
|---|
| 1255 | set f [list u_powerLawUpper $f \ |
|---|
| 1256 | [expr { $gamma / $onemgamma }] \ |
|---|
| 1257 | [expr { 1. / $onemgamma }] \ |
|---|
| 1258 | $b] |
|---|
| 1259 | |
|---|
| 1260 | set limit [expr { pow( $b - $a, $onemgamma ) }] |
|---|
| 1261 | set result {} |
|---|
| 1262 | foreach v [eval [linsert $args 0 romberg $f 0 $limit]] { |
|---|
| 1263 | lappend result [expr { $v / $onemgamma }] |
|---|
| 1264 | } |
|---|
| 1265 | return $result |
|---|
| 1266 | |
|---|
| 1267 | } |
|---|
| 1268 | |
|---|
| 1269 | #---------------------------------------------------------------------- |
|---|
| 1270 | # |
|---|
| 1271 | # u_expUpper -- |
|---|
| 1272 | # |
|---|
| 1273 | # Change of variable to integrate a function that decays |
|---|
| 1274 | # exponentially. |
|---|
| 1275 | # |
|---|
| 1276 | # Parameters: |
|---|
| 1277 | # f - Function to integrate |
|---|
| 1278 | # u - Changed variable u = exp(-x) |
|---|
| 1279 | # |
|---|
| 1280 | # Results: |
|---|
| 1281 | # Returns (1/u)*f(-log(u)) |
|---|
| 1282 | # |
|---|
| 1283 | # Side effects: |
|---|
| 1284 | # Whatever f does. |
|---|
| 1285 | # |
|---|
| 1286 | #---------------------------------------------------------------------- |
|---|
| 1287 | |
|---|
| 1288 | proc ::math::calculus::u_expUpper { f u } { |
|---|
| 1289 | set cmd $f |
|---|
| 1290 | lappend cmd [expr { -log($u) }] |
|---|
| 1291 | set y [eval $cmd] |
|---|
| 1292 | return [expr { $y / $u }] |
|---|
| 1293 | } |
|---|
| 1294 | |
|---|
| 1295 | #---------------------------------------------------------------------- |
|---|
| 1296 | # |
|---|
| 1297 | # romberg_expUpper -- |
|---|
| 1298 | # |
|---|
| 1299 | # Integrate a function that decays exponentially over a |
|---|
| 1300 | # half-infinite interval. |
|---|
| 1301 | # |
|---|
| 1302 | # Parameters: |
|---|
| 1303 | # Same as romberg. The upper limit of integration, 'b', |
|---|
| 1304 | # is expected to be very large. |
|---|
| 1305 | # |
|---|
| 1306 | # Results: |
|---|
| 1307 | # Same as romberg. |
|---|
| 1308 | # |
|---|
| 1309 | # The romberg_expUpper function operates by making the change of |
|---|
| 1310 | # variable, u = exp(-x). |
|---|
| 1311 | # |
|---|
| 1312 | #---------------------------------------------------------------------- |
|---|
| 1313 | |
|---|
| 1314 | proc ::math::calculus::romberg_expUpper { f a b args } { |
|---|
| 1315 | if { ![string is double -strict $a] } { |
|---|
| 1316 | return -code error [expectDouble $a] |
|---|
| 1317 | } |
|---|
| 1318 | if { ![string is double -strict $b] } { |
|---|
| 1319 | return -code error [expectDouble $b] |
|---|
| 1320 | } |
|---|
| 1321 | if { $a >= $b } { |
|---|
| 1322 | return -code error "limits of integration out of order" |
|---|
| 1323 | } |
|---|
| 1324 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 1325 | set f [list u_expUpper $f] |
|---|
| 1326 | return [eval [linsert $args 0 \ |
|---|
| 1327 | romberg $f [expr {exp(-$b)}] [expr {exp(-$a)}]]] |
|---|
| 1328 | } |
|---|
| 1329 | |
|---|
| 1330 | #---------------------------------------------------------------------- |
|---|
| 1331 | # |
|---|
| 1332 | # u_expLower -- |
|---|
| 1333 | # |
|---|
| 1334 | # Change of variable to integrate a function that grows |
|---|
| 1335 | # exponentially. |
|---|
| 1336 | # |
|---|
| 1337 | # Parameters: |
|---|
| 1338 | # f - Function to integrate |
|---|
| 1339 | # u - Changed variable u = exp(x) |
|---|
| 1340 | # |
|---|
| 1341 | # Results: |
|---|
| 1342 | # Returns (1/u)*f(log(u)) |
|---|
| 1343 | # |
|---|
| 1344 | # Side effects: |
|---|
| 1345 | # Whatever f does. |
|---|
| 1346 | # |
|---|
| 1347 | #---------------------------------------------------------------------- |
|---|
| 1348 | |
|---|
| 1349 | proc ::math::calculus::u_expLower { f u } { |
|---|
| 1350 | set cmd $f |
|---|
| 1351 | lappend cmd [expr { log($u) }] |
|---|
| 1352 | set y [eval $cmd] |
|---|
| 1353 | return [expr { $y / $u }] |
|---|
| 1354 | } |
|---|
| 1355 | |
|---|
| 1356 | #---------------------------------------------------------------------- |
|---|
| 1357 | # |
|---|
| 1358 | # romberg_expLower -- |
|---|
| 1359 | # |
|---|
| 1360 | # Integrate a function that grows exponentially over a |
|---|
| 1361 | # half-infinite interval. |
|---|
| 1362 | # |
|---|
| 1363 | # Parameters: |
|---|
| 1364 | # Same as romberg. The lower limit of integration, 'a', |
|---|
| 1365 | # is expected to be very large and negative. |
|---|
| 1366 | # |
|---|
| 1367 | # Results: |
|---|
| 1368 | # Same as romberg. |
|---|
| 1369 | # |
|---|
| 1370 | # The romberg_expUpper function operates by making the change of |
|---|
| 1371 | # variable, u = exp(x). |
|---|
| 1372 | # |
|---|
| 1373 | #---------------------------------------------------------------------- |
|---|
| 1374 | |
|---|
| 1375 | proc ::math::calculus::romberg_expLower { f a b args } { |
|---|
| 1376 | if { ![string is double -strict $a] } { |
|---|
| 1377 | return -code error [expectDouble $a] |
|---|
| 1378 | } |
|---|
| 1379 | if { ![string is double -strict $b] } { |
|---|
| 1380 | return -code error [expectDouble $b] |
|---|
| 1381 | } |
|---|
| 1382 | if { $a >= $b } { |
|---|
| 1383 | return -code error "limits of integration out of order" |
|---|
| 1384 | } |
|---|
| 1385 | set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]] |
|---|
| 1386 | set f [list u_expLower $f] |
|---|
| 1387 | return [eval [linsert $args 0 \ |
|---|
| 1388 | romberg $f [expr {exp($a)}] [expr {exp($b)}]]] |
|---|
| 1389 | } |
|---|
| 1390 | |
|---|
| 1391 | |
|---|
| 1392 | # regula_falsi -- |
|---|
| 1393 | # Compute the zero of a function via regula falsi |
|---|
| 1394 | # Arguments: |
|---|
| 1395 | # f Name of the procedure/command that evaluates the function |
|---|
| 1396 | # xb Start of the interval that brackets the zero |
|---|
| 1397 | # xe End of the interval that brackets the zero |
|---|
| 1398 | # eps Relative error that is allowed (default: 1.0e-4) |
|---|
| 1399 | # Result: |
|---|
| 1400 | # Estimate of the zero, such that the estimated (!) |
|---|
| 1401 | # error < eps * abs(xe-xb) |
|---|
| 1402 | # Note: |
|---|
| 1403 | # f(xb)*f(xe) must be negative and eps must be positive |
|---|
| 1404 | # |
|---|
| 1405 | proc ::math::calculus::regula_falsi { f xb xe {eps 1.0e-4} } { |
|---|
| 1406 | if { $eps <= 0.0 } { |
|---|
| 1407 | return -code error "Relative error must be positive" |
|---|
| 1408 | } |
|---|
| 1409 | |
|---|
| 1410 | set fb [$f $xb] |
|---|
| 1411 | set fe [$f $xe] |
|---|
| 1412 | |
|---|
| 1413 | if { $fb * $fe > 0.0 } { |
|---|
| 1414 | return -code error "Interval must be chosen such that the \ |
|---|
| 1415 | function has a different sign at the beginning than at the end" |
|---|
| 1416 | } |
|---|
| 1417 | |
|---|
| 1418 | set max_error [expr {$eps * abs($xe-$xb)}] |
|---|
| 1419 | set interval [expr {abs($xe-$xb)}] |
|---|
| 1420 | |
|---|
| 1421 | while { $interval > $max_error } { |
|---|
| 1422 | set coeff [expr {($fe-$fb)/($xe-$xb)}] |
|---|
| 1423 | set xi [expr {$xb-$fb/$coeff}] |
|---|
| 1424 | set fi [$f $xi] |
|---|
| 1425 | |
|---|
| 1426 | if { $fi == 0.0 } { |
|---|
| 1427 | break |
|---|
| 1428 | } |
|---|
| 1429 | set diff1 [expr {abs($xe-$xi)}] |
|---|
| 1430 | set diff2 [expr {abs($xb-$xi)}] |
|---|
| 1431 | if { $diff1 > $diff2 } { |
|---|
| 1432 | set interval $diff2 |
|---|
| 1433 | } else { |
|---|
| 1434 | set interval $diff1 |
|---|
| 1435 | } |
|---|
| 1436 | |
|---|
| 1437 | if { $fb*$fi < 0.0 } { |
|---|
| 1438 | set xe $xi |
|---|
| 1439 | set fe $fi |
|---|
| 1440 | } else { |
|---|
| 1441 | set xb $xi |
|---|
| 1442 | set fb $fi |
|---|
| 1443 | } |
|---|
| 1444 | } |
|---|
| 1445 | |
|---|
| 1446 | return $xi |
|---|
| 1447 | } |
|---|