# Tests for combinatorics functions in math library  -*- tcl -*-
#
# 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) 2001 by Kevin B. Kenny
# All rights reserved.
#
# RCS: @(#) $Id: combinatorics.test,v 1.14 2006/10/09 21:41:41 andreas_kupries Exp $

# -------------------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.5
testsNeedTcltest 1.0

testing {
    useLocal math.tcl math
}

# -------------------------------------------------------------------------

# Fake [lset] for Tcl releases that don't have it.  We need only
# lset into a flat list.

if { [string compare lset [info commands lset]] } {
    proc K { x y } { set x }
    proc lset { listVar index var } {
	upvar 1 $listVar list
	set list [lreplace [K $list [set list {}]] $index $index $var]
    }
}

# -------------------------------------------------------------------------

test combinatorics-1.1 { math::ln_Gamma, wrong num args } {
    catch { math::ln_Gamma } msg
    set msg
} [tcltest::wrongNumArgs math::ln_Gamma x 0]

test combinatorics-1.2 { math::ln_Gamma, main line code } {
    set maxerror 0.
    set f 1.
    for { set i 1 } { $i < 171 } { set i $ip1 } {
	set f [expr { $f * $i }]
	set ip1 [expr { $i + 1 }]
	set f2 [expr { exp( [math::ln_Gamma $ip1] ) }]
	set error [expr { abs( $f2 - $f ) / $f }]
	if { $error > $maxerror } {
	    set maxerror $error
	}
    }
    if { $maxerror > 5e-10 } {
	error "max error of factorials computed using math::ln_Gamma\
               specified to be 5e-10, was $maxerror"
    }
    concat
} {}

test combinatorics-1.3 { math::ln_Gamma, half integer args } {
    set maxerror 0.
    set z 0.5
    set pi 3.1415926535897932
    set g [expr { sqrt( $pi ) }]
    while { $z < 170. } {
	set g2 [expr { exp( [::math::ln_Gamma $z] ) }]
	set error [expr { abs( $g2 - $g ) / $g }]
	if { $error > $maxerror } {
	    set maxerror $error
	}
	set g [expr { $g * $z }]
	set z [expr { $z + 1. }]
    }
    if { $maxerror > 5e-10 } {
	error "max error of half integer gamma computed using math::ln_Gamma\
               specified to be 5e-10, was $maxerror"
    }
    concat
} {}

test combinatorics-1.4 { math::ln_Gamma, bogus arg } {
    catch { math::ln_Gamma bogus } msg
    set msg
} {expected a floating-point number but found "bogus"}

test combinatorics-1.5 { math::ln_Gamma, evaluate at pole } {
    catch { math::ln_Gamma 0.0 } msg
    list $msg $::errorCode
} {{argument to math::ln_Gamma must be positive} {ARITH DOMAIN {argument to math::ln_Gamma must be positive}}}

test combinatorics-1.6 { math::ln_Gamma, exponent overflow } {
    catch { math::ln_Gamma 2.556348163871691e+305 } msg
    list $msg $::errorCode
} {{floating-point value too large to represent} {ARITH OVERFLOW {floating-point value too large to represent}}}

test combinatorics-2.1 { math::factorial, wrong num args } {
    catch { math::factorial } msg
    set msg
} [tcltest::wrongNumArgs math::factorial x 0]

test combinatorics-2.2 { math::factorial 0 } {
    math::factorial 0
} 1

test combinatorics-2.3 { math::factorial, main line } {
    set maxerror 0.
    set f 1.
    for { set i 1 } { $i < 171 } { set i $ip1 } {
	set f [expr { $f * $i }]
	set ip1 [expr { $i + 1 }]
	set f2 [math::factorial $i]
	set error [expr { abs( $f2 - $f ) / $f }]
	if { $error > $maxerror } {
	    set maxerror $error
	}
    }
    if { $maxerror > 1e-16 } {
	error "max error of factorials computed using math::factorial\
               specified to be 1e-16, was $maxerror"
    }
    concat
} {}

test combinatorics-2.4 { math::factorial, half integer args } {
    set maxerror 0.
    set z -0.5
    set pi 3.1415926535897932
    set g [expr { sqrt( $pi ) }]
    while { $z < 169. } {
	set g2 [math::factorial $z]
	set error [expr { abs( $g2 - $g ) / $g }]
	if { $error > $maxerror } {
	    set maxerror $error
	}
	set z [expr { $z + 1. }]
	set g [expr { $g * $z }]
    }
    if { $maxerror > 1e-9 } {
	error "max error of half integer factorial\
               specified to be 1e-9, was $maxerror"
    }
    concat
} {}

test combinatorics-2.5 { math::factorial, bogus arg } {
    catch { math::factorial bogus } msg
    set msg
} {expected a floating-point number but found "bogus"}

test combinatorics-2.6 { math::factorial, evaluate at pole } {
    catch { math::factorial -1.0 } msg
    list $msg $::errorCode
} {{argument to math::factorial must be greater than -1.0} {ARITH DOMAIN {argument to math::factorial must be greater than -1.0}}}

test combinatorics-2.7 { math::factorial, exponent overflow } {
    if {![catch {
	math::factorial 171
    } msg]} {
	if { [string equal $msg Infinity] || [string equal $msg Inf] } {
	    set result ok
	} else {
	    set result "result of factorial was [list $msg],\
	                should be Infinity"
	}
    } else {
	if { [string equal [lrange $::errorCode 0 1] {ARITH OVERFLOW}] } {
	    set result ok
	} else {
	    set result "error from factorial was [list $::errorCode],\
                        should be {ARITH IOVERFLOW *}"
	}
    }
    set result
} ok

test combinatorics-2.8 { math::factorial, "" arg } {
    catch { math::factorial "" } msg
    list $msg
} {{expected a floating-point number but found ""}}

test combinatorics-3.1 { math::choose, wrong num args } {
    catch { math::choose } msg
    set msg
} [tcltest::wrongNumArgs math::choose {n k} 0]

test combinatorics-3.2 { math::choose, wrong num args } {
    catch { math::choose 1 } msg
    set msg
} [tcltest::wrongNumArgs math::choose {n k} 1]

test combinatorics-3.3 { math::choose, precomputed table and gamma evals } {
    set maxError 0
    set l {}
    for { set n 0 } { $n < 100 } { incr n } {
	lappend l 1.
	for { set k [expr { $n - 1 }] } { $k > 0 } { set k $km1 } {
	    set km1 [expr { $k - 1 }]
	    set cnk [expr { [lindex $l $k] + [lindex $l $km1] }]
	    lset l $k $cnk
	    set ccnk [math::choose $n $k]
	    set error [expr { abs( $ccnk - $cnk ) / $cnk }]
	    if { $error > $maxError } {
		set maxError $error
	    }
	}
    }
    if { $maxError > 5e-10 } {
	error "max error in math::choose was $maxError, specified to be 5e-10"
    }
    concat
} {}

test combinatorics-3.4 { math::choose, bogus n } {
    catch { math::choose bogus 0 } msg
    set msg
} {expected a floating-point number but found "bogus"}

test combinatorics-3.5 { math::choose bogus k } {
    catch { math::choose 0 bogus } msg
    set msg
} {expected a floating-point number but found "bogus"}

test combinatorics-3.6 { match::choose negative n } {
    catch { math::choose -1 0 } msg
    list $msg $::errorCode
} {{first argument to math::choose must be non-negative} {ARITH DOMAIN {first argument to math::choose must be non-negative}}}

test combinatorics-3.7 { math::choose negative k } {
    math::choose 17 -1
} 0

test combinatorics-3.8 { math::choose excess k } {
    math::choose 17 18
} 0

test combinatorics-3.9 {math::choose negative fraction } {
    catch { math::choose 17 -0.5 } msg
    list $msg $::errorCode
} {{second argument to math::choose must be non-negative, or both must be integers} {ARITH DOMAIN {second argument to math::choose must be non-negative, or both must be integers}}}

test combinatorics-3.10 { math::choose big args } {
    if {![catch {
	math::choose 1500 750
    } msg]} {
	if { [string equal $msg Infinity] || [string equal $msg Inf] } {
	    set result ok
	} else {
	    set result "result of choose was [list $msg],\
	                should be Infinity"
	}
    } else {
	if { [string equal [lrange $::errorCode 0 1] {ARITH OVERFLOW}] } {
	    set result ok
	} else {
	    set result "error from choose was [list $::errorCode],\
                        should be {ARITH IOVERFLOW *}"
	}
    }
    set result
} ok

test combinatorics-4.1 { math::Beta, wrong num args } {
    catch { math::Beta } msg
    set msg
} [tcltest::wrongNumArgs math::Beta {z w} 0]

test combinatorics-4.2 { math::Beta, wrong num args } {
    catch { math::Beta 1 } msg
    set msg
} [tcltest::wrongNumArgs math::Beta {z w} 1]

test combinatorics-4.3 { math::Beta, bogus z } {
    catch { math::Beta bogus 1 } msg
    set msg
} {expected a floating-point number but found "bogus"}

test combinatorics-4.4 { math::Beta, bogus w } {
    catch { math::Beta 1 bogus } msg
    set msg
} {expected a floating-point number but found "bogus"}

test combinatorics-4.5 { math::Beta, negative z } {
    catch { math::Beta 0 1 } msg
    list $msg $::errorCode
} {{first argument to math::Beta must be positive} {ARITH DOMAIN {first argument to math::Beta must be positive}}}

test combinatorics-4.6 { math::Beta, negative w } {
    catch { math::Beta 1 0 } msg
    list $msg $::errorCode
} {{second argument to math::Beta must be positive} {ARITH DOMAIN {second argument to math::Beta must be positive}}}

test combinatorics-4.7 { math::Beta, test with Pascal } {
    set maxError 0
    set l {}
    for { set n 0 } { $n < 100 } { incr n } {
	lappend l 1.
	for { set k [expr { $n - 1 }] } { $k > 0 } { set k $km1 } {
	    set km1 [expr { $k - 1 }]
	    set cnk [expr { [lindex $l $k] + [lindex $l $km1] }]
	    lset l $k $cnk
	    set w [expr { $k + 1 }]
	    set z [expr { $n - $k + 1 }]
	    set beta [expr { 1.0 / $cnk / ( $z + $w - 1 )}]
	    set cbeta [math::Beta $z $w]
	    set error [expr { abs( $cbeta - $beta ) / $beta }]
	    if { $error > $maxError } {
		set maxError $error
	    }
	}
    }
    if { $maxError > 5e-10 } {
	error "max error in math::Beta was $maxError, specified to be 5e-10"
    }
    concat
} {}


testsuiteCleanup