combinatorics.tcl

Go to the documentation of this file.
00001 /* ----------------------------------------------------------------------*/
00002 /* */
00003 /*  math/combinatorics.tcl --*/
00004 /* */
00005 /*  This file contains definitions of mathematical functions*/
00006 /*  useful in combinatorial problems.  */
00007 /* */
00008 /*  Copyright (c) 2001, by Kevin B. Kenny.  All rights reserved.*/
00009 /* */
00010 /*  See the file "license.terms" for information on usage and redistribution*/
00011 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00012 /*  */
00013 /*  RCS: @(#) $Id: combinatorics.tcl,v 1.5 2004/02/09 19:31:54 hobbs Exp $*/
00014 /* */
00015 /* ----------------------------------------------------------------------*/
00016 
00017 package require Tcl 8.0
00018 
00019 namespace ::math {
00020 
00021     /*  Commonly used combinatorial functions*/
00022 
00023     /*  ln_Gamma is spelt thus because it's a capital gamma (\u0393)*/
00024 
00025     namespace export ln_Gamma;      /*  Logarithm of the Gamma function*/
00026     namespace export factorial;     /*  Factorial*/
00027     namespace export choose;        /*  Binomial coefficient*/
00028 
00029     /*  Note that Beta is spelt thus because it's conventionally a*/
00030     /*  capital beta (\u0392).  It is exported from the package even*/
00031     /*  though its name is capitalized.*/
00032 
00033     namespace export Beta;      /*  Beta function*/
00034 
00035 }
00036 
00037 /* ----------------------------------------------------------------------*/
00038 /* */
00039 /*  ::math::InitializeFactorial --*/
00040 /* */
00041 /*  Initialize a table of factorials for small integer arguments.*/
00042 /* */
00043 /*  Parameters:*/
00044 /*  None.*/
00045 /* */
00046 /*  Results:*/
00047 /*  None.*/
00048 /* */
00049 /*  Side effects:*/
00050 /*  The variable, ::math::factorialList, is initialized to hold*/
00051 /*  a table of factorial n for 0 <= n <= 170.*/
00052 /* */
00053 /*  This procedure is called once when the 'factorial' procedure is*/
00054 /*  being loaded.*/
00055 /* */
00056 /* ----------------------------------------------------------------------*/
00057 
00058 ret  ::math::InitializeFactorial () {
00059 
00060     variable factorialList
00061 
00062     set factorialList [list 1]
00063     set f 1
00064     for { set i 1 } { $i < 171 } { incr i } {
00065     if { $i > 12. } {
00066         set f [expr { $f * double($i)}]
00067     } else {
00068         set f [expr { $f * $i }]
00069     }
00070     lappend factorialList $f
00071     }
00072 
00073 }
00074 
00075 /* ----------------------------------------------------------------------*/
00076 /* */
00077 /*  ::math::InitializePascal --*/
00078 /* */
00079 /*  Precompute the first few rows of Pascal's triangle and store*/
00080 /*  them in the variable ::math::pascal*/
00081 /* */
00082 /*  Parameters:*/
00083 /*  None.*/
00084 /* */
00085 /*  Results:*/
00086 /*  None.*/
00087 /* */
00088 /*  Side effects:*/
00089 /*  ::math::pascal is initialized to a flat list containing */
00090 /*  the first 34 rows of Pascal's triangle.  C(n,k) is to be found*/
00091 /*  at [lindex $pascal $i] where i = n * ( n + 1 ) + k.  No attempt*/
00092 /*  is made to exploit symmetry.*/
00093 /* */
00094 /* ----------------------------------------------------------------------*/
00095 
00096 ret  ::math::InitializePascal () {
00097 
00098     variable pascal
00099 
00100     set pascal [list 1]
00101     for { set n 1 } { $n < 34 } { incr n } {
00102     lappend pascal 1
00103     set l2 [list 1]
00104     for { set k 1 } { $k < $n } { incr k } {
00105         set km1 [expr { $k - 1 }]
00106         set c [expr { [lindex $l $km1] + [lindex $l $k] }]
00107         lappend pascal $c
00108         lappend l2 $c
00109     }
00110     lappend pascal 1
00111     lappend l2 1
00112     set l $l2
00113     }
00114 
00115 }
00116 
00117 /* ----------------------------------------------------------------------*/
00118 /* */
00119 /*  ::math::ln_Gamma --*/
00120 /* */
00121 /*  Returns ln(Gamma(x)), where x >= 0*/
00122 /* */
00123 /*  Parameters:*/
00124 /*  x - Argument to the Gamma function.*/
00125 /* */
00126 /*  Results:*/
00127 /*  Returns the natural logarithm of Gamma(x).*/
00128 /* */
00129 /*  Side effects:*/
00130 /*  None.*/
00131 /* */
00132 /*  Gamma(x) is defined as:*/
00133 /* */
00134 /*        +inf*/
00135 /*          _*/
00136 /*         |    x-1  -t*/
00137 /*  Gamma(x)= _|   t    e   dt*/
00138 /* */
00139 /*         0*/
00140 /* */
00141 /*  The approximation used here is from Lanczos, SIAM J. Numerical Analysis,*/
00142 /*  series B, volume 1, p. 86.  For x > 1, the absolute error of the*/
00143 /*  result is claimed to be smaller than 5.5 * 10**-10 -- that is, the*/
00144 /*  resulting value of Gamma when exp( ln_Gamma( x ) ) is computed is*/
00145 /*  expected to be precise to better than nine significant figures.*/
00146 /* */
00147 /* ----------------------------------------------------------------------*/
00148 
00149 ret  ::math::ln_Gamma ( type x ) {
00150 
00151     # Handle the common case of a real argument that's within the
00152     # permissible range.
00153 
00154     if { [string is double -strict $x]
00155      && ( $x > 0 )
00156      && ( $x <= 2.5563481638716906e+305 )
00157      } {
00158     set x [expr { $x - 1.0 }]
00159     set tmp [expr { $x + 5.5 }]
00160     set tmp [ expr { ( $x + 0.5 ) * log( $tmp ) - $tmp }]
00161     set ser 1.0
00162     foreach cof {
00163         76.18009173 -86.50532033 24.01409822
00164         -1.231739516 .00120858003 -5.36382e-6
00165     } {
00166         set x [expr { $x + 1.0 }]
00167         set ser [expr { $ser + $cof / $x }]
00168     }
00169     return [expr { $tmp + log( 2.50662827465 * $ser ) }]
00170     } 
00171 
00172     # Handle the error cases.
00173 
00174     if { ![string is double -strict $x] } {
00175     return -code error [expectDouble $x]
00176     }
00177 
00178     if { $x <= 0.0 } {
00179     set proc [lindex [info level 0] 0]
00180     return -code error \
00181         -errorcode [list ARITH DOMAIN \
00182             "argument to $proc must be positive"] \
00183         "argument to $proc must be positive"
00184     }
00185 
00186     return -code error \
00187     -errorcode [list ARITH OVERFLOW \
00188             "floating-point value too large to represent"] \
00189     "floating-point value too large to represent"
00190     
00191 }
00192 
00193 /* ----------------------------------------------------------------------*/
00194 /* */
00195 /*  math::factorial --*/
00196 /* */
00197 /*  Returns the factorial of the argument x.*/
00198 /* */
00199 /*  Parameters:*/
00200 /*  x -- Number whose factorial is to be computed.*/
00201 /* */
00202 /*  Results:*/
00203 /*  Returns x!, the factorial of x.*/
00204 /* */
00205 /*  Side effects:*/
00206 /*  None.*/
00207 /* */
00208 /*  For integer x, 0 <= x <= 12, an exact integer result is returned.*/
00209 /* */
00210 /*  For integer x, 13 <= x <= 21, an exact floating-point result is returned*/
00211 /*  on machines with IEEE floating point.*/
00212 /* */
00213 /*  For integer x, 22 <= x <= 170, the result is exact to 1 ULP.*/
00214 /* */
00215 /*  For real x, x >= 0, the result is approximated by computing*/
00216 /*  Gamma(x+1) using the ::math::ln_Gamma function, and the result is*/
00217 /*  expected to be precise to better than nine significant figures.*/
00218 /* */
00219 /*  It is an error to present x <= -1 or x > 170, or a value of x that*/
00220 /*  is not numeric.*/
00221 /* */
00222 /* ----------------------------------------------------------------------*/
00223 
00224 ret  ::math::factorial ( type x ) {
00225 
00226     variable factorialList
00227 
00228     # Common case: factorial of a small integer
00229 
00230     if { [string is integer -strict $x]
00231      && $x >= 0
00232      && $x < [llength $factorialList] } {
00233     return [lindex $factorialList $x]
00234     }
00235 
00236     # Error case: not a number
00237 
00238     if { ![string is double -strict $x] } {
00239     return -code error [expectDouble $x]
00240     }
00241 
00242     # Error case: gamma in the left half plane
00243 
00244     if { $x <= -1.0 } {
00245     set proc [lindex [info level 0] 0]
00246     set message "argument to $proc must be greater than -1.0"
00247     return -code error -errorcode [list ARITH DOMAIN $message] $message
00248     }
00249 
00250     # Error case - gamma fails
00251 
00252     if { [catch { expr {exp( [ln_Gamma [expr { $x + 1 }]] )} } result] } {
00253     return -code error -errorcode $::errorCode $result
00254     }
00255 
00256     # Success - computed factorial n as Gamma(n+1)
00257 
00258     return $result
00259 
00260 }
00261 
00262 /* ----------------------------------------------------------------------*/
00263 /* */
00264 /*  ::math::choose --*/
00265 /* */
00266 /*  Returns the binomial coefficient C(n,k) = n!/k!(n-k)!*/
00267 /* */
00268 /*  Parameters:*/
00269 /*  n -- Number of objects in the sampling pool*/
00270 /*  k -- Number of objects to be chosen.*/
00271 /* */
00272 /*  Results:*/
00273 /*  Returns C(n,k).  */
00274 /* */
00275 /*  Side effects:*/
00276 /*  None.*/
00277 /* */
00278 /*  Results are expected to be accurate to ten significant figures.*/
00279 /*  If both parameters are integers and the result fits in 32 bits, */
00280 /*  the result is rounded to an integer.*/
00281 /* */
00282 /*  Integer results are exact up to at least n = 34.*/
00283 /*  Floating point results are precise to better than nine significant */
00284 /*  figures.*/
00285 /* */
00286 /* ----------------------------------------------------------------------*/
00287 
00288 ret  ::math::choose ( type n , type k ) {
00289 
00290     variable pascal
00291 
00292     # Use a precomputed table for small integer args
00293 
00294     if { [string is integer -strict $n]
00295      && $n >= 0 && $n < 34
00296      && [string is integer -strict $k]
00297      && $k >= 0 && $k <= $n } {
00298 
00299     set i [expr { ( ( $n * ($n + 1) ) / 2 ) + $k }]
00300 
00301     return [lindex $pascal $i]
00302 
00303     }
00304 
00305     # Test bogus arguments
00306 
00307     if { ![string is double -strict $n] } {
00308     return -code error [expectDouble $n]
00309     }
00310     if { ![string is double -strict $k] } {
00311     return -code error [expectDouble $k]
00312     }
00313 
00314     # Forbid negative n
00315 
00316     if { $n < 0. } {
00317     set proc [lindex [info level 0] 0]
00318     set msg "first argument to $proc must be non-negative"
00319     return -code error -errorcode [list ARITH DOMAIN $msg] $msg
00320     }
00321 
00322     # Handle k out of range
00323 
00324     if { [string is integer -strict $k] && [string is integer -strict $n]
00325      && ( $k < 0 || $k > $n ) } {
00326     return 0
00327     }
00328 
00329     if { $k < 0. } {
00330     set proc [lindex [info level 0] 0]
00331     set msg "second argument to $proc must be non-negative,\
00332                  or both must be integers"
00333     return -code error -errorcode [list ARITH DOMAIN $msg] $msg
00334     }
00335 
00336     # Compute the logarithm of the desired binomial coefficient.
00337 
00338     if { [catch { expr { [ln_Gamma [expr { $n + 1 }]]
00339              - [ln_Gamma [expr { $k + 1 }]]
00340              - [ln_Gamma [expr { $n - $k + 1 }]] } } r] } {
00341     return -code error -errorcode $::errorCode $r
00342     }
00343 
00344     # Compute the binomial coefficient itself
00345 
00346     if { [catch { expr { exp( $r ) } } r] } {
00347     return -code error -errorcode $::errorCode $r
00348     }
00349 
00350     # Round to integer if both args are integers and the result fits
00351 
00352     if { $r <= 2147483647.5 
00353            && [string is integer -strict $n]
00354            && [string is integer -strict $k] } {
00355     return [expr { round( $r ) }]
00356     }
00357 
00358     return $r
00359 
00360 }
00361 
00362 /* ----------------------------------------------------------------------*/
00363 /* */
00364 /*  ::math::Beta --*/
00365 /* */
00366 /*  Return the value of the Beta function of parameters z and w.*/
00367 /* */
00368 /*  Parameters:*/
00369 /*  z, w : Two real parameters to the Beta function*/
00370 /* */
00371 /*  Results:*/
00372 /*  Returns the value of the Beta function.*/
00373 /* */
00374 /*  Side effects:*/
00375 /*  None.*/
00376 /* */
00377 /*  Beta( w, z ) is defined as:*/
00378 /* */
00379 /*                1_*/
00380 /*                |  (z-1)     (w-1)*/
00381 /*  Beta( w, z ) = Beta( z, w ) =     | t     (1-t)      dt*/
00382 /*               _|*/
00383 /*                0*/
00384 /* */
00385 /*         = Gamma( z ) Gamma( w ) / Gamma( z + w )*/
00386 /* */
00387 /*  Results are returned as a floating point number precise to better*/
00388 /*  than nine significant figures for w, z > 1.*/
00389 /* */
00390 /* ----------------------------------------------------------------------*/
00391 
00392 ret  ::math::Beta ( type z , type w ) {
00393 
00394     # Check form of both args so that domain check can be made
00395 
00396     if { ![string is double -strict $z] } {
00397     return -code error [expectDouble $z]
00398     }
00399     if { ![string is double -strict $w] } {
00400     return -code error [expectDouble $w]
00401     }
00402 
00403     # Check sign of both args
00404 
00405     if { $z <= 0.0 } {
00406     set proc [lindex [info level 0] 0]
00407     set msg "first argument to $proc must be positive"
00408     return -code error -errorcode [list ARITH DOMAIN $msg] $msg
00409     }
00410     if { $w <= 0.0 } {
00411     set proc [lindex [info level 0] 0]
00412     set msg "second argument to $proc must be positive"
00413     return -code error -errorcode [list ARITH DOMAIN $msg] $msg
00414     }
00415 
00416     # Compute beta using gamma function, keeping stack trace clean.
00417 
00418     if { [catch { expr { exp( [ln_Gamma $z] + [ln_Gamma $w]
00419                   - [ln_Gamma [ expr { $z + $w }]] ) } } beta] } {
00420 
00421     return -code error -errorcode $::errorCode $beta
00422 
00423     } 
00424 
00425     return $beta
00426 
00427 }
00428 
00429 /* ----------------------------------------------------------------------*/
00430 /* */
00431 /*  Initialization of this file:*/
00432 /* */
00433 /*  Initialize the precomputed tables of factorials and binomial*/
00434 /*  coefficients.*/
00435 /* */
00436 /* ----------------------------------------------------------------------*/
00437 
00438 namespace ::math {
00439     InitializeFactorial
00440     InitializePascal
00441 }
00442 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1