# special.tcl -- # Provide well-known special mathematical functions # # This file contains a collection of tests for one or more of the Tcllib # procedures. Sourcing this file into Tcl runs the tests and # generates output for errors. No output means no errors were found. # # Copyright (c) 2004 by Arjen Markus. All rights reserved. # # RCS: @(#) $Id: special.tcl,v 1.13 2008/08/13 07:28:47 arjenmarkus Exp $ # package require math package require math::constants package require math::statistics # namespace special # Create a convenient namespace for the "special" mathematical functions # namespace eval ::math::special { # # Define a number of common mathematical constants # ::math::constants::constants pi variable halfpi [expr {$pi/2.0}] # # Functions defined in other math submodules # if { [info commands Beta] == {} } { namespace import ::math::Beta namespace import ::math::ln_Gamma } # # Export the various functions # namespace export Beta ln_Gamma Gamma erf erfc fresnel_C fresnel_S sinc } # Gamma -- # The Gamma function - synonym for "factorial" # proc ::math::special::Gamma {x} { if { [catch { expr {exp( [ln_Gamma $x] )} } result] } { return -code error -errorcode $::errorCode $result } return $result } # erf -- # The error function # Arguments: # x The value for which the function must be evaluated # Result: # erf(x) # Note: # The algoritm used is due to George Marsaglia # See: http://www.velocityreviews.com/forums/t317358-erf-function-in-c.html # I did not want to copy and convert the even more accurate but # rather lengthy algorithm used by lcc-win32/Sun # proc ::math::special::erf {x} { set x [expr {$x*sqrt(2.0)}] if { $x > 10.0 } { return 1.0 } if { $x < -10.0 } { return -1.0 } set a 1.2533141373155 set b -1.0 set pwr 1.0 set t 0.0 set z 0.0 set s [expr {$a+$b*$x}] set i 2 while { $s != $t } { set a [expr {($a+$z*$b)/double($i)}] set b [expr {($b+$z*$a)/double($i+1)}] set pwr [expr {$pwr*$x*$x}] set t $s set s [expr {$s+$pwr*($a+$x*$b)}] incr i 2 } return [expr {1.0-2.0*$s*exp(-0.5*$x*$x-0.9189385332046727418)}] } # erfc -- # The complement of the error function # Arguments: # x The value for which the function must be evaluated # Result: # erfc(x) = 1.0-erf(x) # proc ::math::special::erfc {x} { set x [expr {$x*sqrt(2.0)}] if { $x > 10.0 } { return 0.0 } if { $x < -10.0 } { return 0.0 } set a 1.2533141373155 set b -1.0 set pwr 1.0 set t 0.0 set z 0.0 set s [expr {$a+$b*$x}] set i 2 while { $s != $t } { set a [expr {($a+$z*$b)/double($i)}] set b [expr {($b+$z*$a)/double($i+1)}] set pwr [expr {$pwr*$x*$x}] set t $s set s [expr {$s+$pwr*($a+$x*$b)}] incr i 2 } return [expr {2.0*$s*exp(-0.5*$x*$x-0.9189385332046727418)}] } # ComputeFG -- # Compute the auxiliary functions f and g # # Arguments: # x Parameter of the integral (x>=0) # Result: # Approximate values for f and g # Note: # See Abramowitz and Stegun. The accuracy is 2.0e-3. # proc ::math::special::ComputeFG {x} { list [expr {(1.0+0.926*$x)/(2.0+1.792*$x+3.104*$x*$x)}] \ [expr {1.0/(2.0+4.142*$x+3.492*$x*$x+6.670*$x*$x*$x)}] } # fresnel_C -- # Compute the Fresnel cosine integral # # Arguments: # x Parameter of the integral (x>=0) # Result: # Value of C(x) = integral from 0 to x of cos(0.5*pi*x^2) # Note: # This relies on a rational approximation of the two auxiliary functions f and g # proc ::math::special::fresnel_C {x} { variable halfpi if { $x < 0.0 } { error "Domain error: x must be non-negative" } if { $x == 0.0 } { return 0.0 } foreach {f g} [ComputeFG $x] {break} set xarg [expr {$halfpi*$x*$x}] return [expr {0.5+$f*sin($xarg)-$g*cos($xarg)}] } # fresnel_S -- # Compute the Fresnel sine integral # # Arguments: # x Parameter of the integral (x>=0) # Result: # Value of S(x) = integral from 0 to x of sin(0.5*pi*x^2) # Note: # This relies on a rational approximation of the two auxiliary functions f and g # proc ::math::special::fresnel_S {x} { variable halfpi if { $x < 0.0 } { error "Domain error: x must be non-negative" } if { $x == 0.0 } { return 0.0 } foreach {f g} [ComputeFG $x] {break} set xarg [expr {$halfpi*$x*$x}] return [expr {0.5-$f*cos($xarg)-$g*sin($xarg)}] } # sinc -- # Compute the sinc function # Arguments: # x Value of the argument # Result: # sin(x)/x # proc ::math::special::sinc {x} { if { $x == 0.0 } { return 1.0 } else { return [expr {sin($x)/$x}] } } # Bessel functions and elliptic integrals -- # if 0 { # commented by GJFB in 2011-05-26 source [file join [file dirname [info script]] "bessel.tcl"] source [file join [file dirname [info script]] "classic_polyns.tcl"] source [file join [file dirname [info script]] "elliptic.tcl"] source [file join [file dirname [info script]] "exponential.tcl"] } else { regsub {/[^/]*/?$} [info script] {} dirName source [join [list $dirName bessel.tcl] /] regsub {/[^/]*/?$} [info script] {} dirName source [join [list $dirName classic_polyns.tcl] /] regsub {/[^/]*/?$} [info script] {} dirName source [join [list $dirName elliptic.tcl] /] regsub {/[^/]*/?$} [info script] {} dirName source [join [list $dirName exponential.tcl] /] } package provide math::special 0.2.2