# -*- tcl -*- # quasirandom.test -- # Tests for the quasi-random numbers package # source [file join \ [file dirname [file dirname [file join [pwd] [info script]]]] \ devtools testutilities.tcl] testsNeedTcl 8.5 testsNeedTcltest 2.1 testing { useLocal quasirandom.tcl math::quasirandom } # # Functions for integration tests # proc const {coords} { return 1.0 } proc fx {coords} { set x [lindex $coords 0] return $x } proc fy {coords} { set y [lindex $coords 1] return $y } proc fz {coords} { set z [lindex $coords 2] return $z } proc fxyz4 {coords} { lassign $coords x y z return [expr {($x*$y*$z)**4}] } # # Auxiliary proc # proc equalCoords {coords1 coords2} { set equal 1 foreach c1 $coords1 c2 $coords2 { if { $c1 != $c2 } { set equal 0 break } } return $equal } # # Create and register (in that order!) custom matching procedures # proc matchTolerant { expected actual } { set match 1 foreach a $actual e $expected { if { $e != 0.0 } { if { abs($e-$a)>1.0e-7*abs($e) && abs($e-$a)>1.0e-7*abs($a) } { set match 0 break } } else { if { abs($a) > 1.0e-7 } { set match 0 } } } return $match } proc matchOnePercent { expected actual } { set match 1 foreach a $actual e $expected { if { $e != 0.0 } { if { abs($e-$a)>1.0e-2*abs($e) && abs($e-$a)>1.0e-2*abs($a) } { set match 0 break } } else { if { abs($a) > 1.0e-2 } { set match 0 } } } return $match } ::tcltest::customMatch tolerant matchTolerant ::tcltest::customMatch error1percent matchOnePercent ::tcltest::customMatch equal equalCoords # # Testing CoordFactors: the basis of the algorithm # Note: exact matching # test "Quasirandom-0.1" "Check basic factor for 1 dimension" -body { set f [::math::quasirandom::CoordFactors 1] return [expr {1.0/$f}] } -result 1.618033988749895 test "Quasirandom-0.2" "Check basic factor for 2 dimensions" -body { set f [lindex [::math::quasirandom::CoordFactors 2] 0] return [expr {1.0/$f}] } -result 1.324717957244746 test "Quasirandom-0.3" "Check basic factor for 3 dimensions" -body { set f [lindex [::math::quasirandom::CoordFactors 3] 0] return [expr {1.0/$f}] } -result 1.2207440846057596 test "Quasirandom-0.4" "Check number of factors for 10 dimensions" -body { return [llength [::math::quasirandom::CoordFactors 10]] } -result 10 # # Basic interface to the qrpoints class # test "Quasirandom-1.0" "Simple QR generator for two dimensions" -match tolerant -body { ::math::quasirandom::qrpoints create simple 2 return [simple next] } -result {0.7548776662466927 0.5698402909980532} -cleanup {simple destroy} test "Quasirandom-1.1" "Simple QR generator - negative dimension" -body { ::math::quasirandom::qrpoints create simple -1 } -returnCodes {error} -result {The dimension argument should be a positive integer value or one of circle, disk, sphere or ball} test "Quasirandom-1.2" "Simple QR generator - set start" -body { ::math::quasirandom::qrpoints create simple 2 ::math::quasirandom::qrpoints create simple2 2 -start 2 simple next set coords [simple next] set coords2 [simple2 next] ;# Should be equal to the second point for the [simple] generator equalCoords $coords $coords2 } -result 1 -cleanup {simple destroy; simple2 destroy} # # Test simple methods # test "Quasirandom-2.1" "set-step sets and returns the value" -match equal -body { ::math::quasirandom::qrpoints create simple 2 simple set-step 100 } -result 100 -cleanup {simple destroy} test "Quasirandom-2.2" "set-evaluations sets and returns the value" -match equal -body { ::math::quasirandom::qrpoints create simple 2 simple set-evaluations 100 } -result 100 -cleanup {simple destroy} test "Quasirandom-2.3" "set-step returns the value" -match equal -body { ::math::quasirandom::qrpoints create simple 2 simple set-step 100 simple set-step } -result 100 -cleanup {simple destroy} test "Quasirandom-2.4" "set-evaluations returns the value" -match equal -body { ::math::quasirandom::qrpoints create simple 2 simple set-evaluations 100 simple set-evaluations } -result 100 -cleanup {simple destroy} # # Test of bounds on points # test "Quasirandom-3.1" "Points should fall within block" -body { ::math::quasirandom::qrpoints create simple 10 set correct_bound 1 for {set i 0} {$i < 100} {incr i} { set coords [simple next] foreach c $coords { if { $c < 0.0 || $c > 1.0 } { set correct_bound 0 break } } } return $correct_bound } -result 1 -cleanup {simple destroy} test "Quasirandom-3.2" "Points should fall on a circle" -match tolerant -body { ::math::quasirandom::qrpoints create simple circle set correct_bound 1 set radii {} for {set i 0} {$i < 100} {incr i} { set coords [simple next] lassign $coords x y lappend radii [expr {hypot($x,$y)}] } return $radii } -result [lrepeat 100 1.0] -cleanup {simple destroy} test "Quasirandom-3.3" "Points should fall within a disk" -match equal -body { ::math::quasirandom::qrpoints create simple disk set correct_bounds {} for {set i 0} {$i < 100} {incr i} { set coords [simple next] lassign $coords x y lappend correct_bounds [expr {hypot($x,$y) <= 1.0}] } return $correct_bounds } -result [lrepeat 100 1] -cleanup {simple destroy} test "Quasirandom-3.4" "Points should fall on a sphere" -match tolerant -body { ::math::quasirandom::qrpoints create simple sphere set correct_bound 1 set radii {} for {set i 0} {$i < 100} {incr i} { set coords [simple next] lassign $coords x y z lappend radii [expr {sqrt($x**2 + $y**2 + $z**2)}] } return $radii } -result [lrepeat 100 1.0] -cleanup {simple destroy} test "Quasirandom-3.5" "Points should fall within a ball" -match equal -body { ::math::quasirandom::qrpoints create simple ball set correct_bounds {} for {set i 0} {$i < 100} {incr i} { set coords [simple next] lassign $coords x y lappend correct_bounds [expr {sqrt($x**2 + $y**2 + $z**2) <= 1.0}] } return $correct_bounds } -result [lrepeat 100 1] -cleanup {simple destroy} # # Test of integral methods # # Integrating a constant function means the result is the volume # test "Quasirandom-4.1" "Integrate constant function - volume = 1" -match tolerant -body { ::math::quasirandom::qrpoints create simple 3 set result [simple integral const {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] } -result 1.0 -cleanup {simple destroy} test "Quasirandom-4.2" "Integrate constant function - volume = 8" -match tolerant -body { ::math::quasirandom::qrpoints create simple 3 set result [simple integral const {{0.0 2.0} {0.0 2.0} {0.0 2.0}}] } -result 8.0 -cleanup {simple destroy} test "Quasirandom-4.3" "Integrate constant function - circle" -match tolerant -body { ::math::quasirandom::qrpoints create simple circle set result [simple integral const 2.0] } -result [expr {2.0 * 2.0 * cos(-1.0)}] -cleanup {simple destroy} test "Quasirandom-4.3" "Integrate constant function - disk" -match tolerant -body { ::math::quasirandom::qrpoints create simple disk set result [simple integral const 2.0] } -result [expr {2.0**2 * cos(-1.0)}] -cleanup {simple destroy} test "Quasirandom-4.4" "Integrate constant function - sphere" -match tolerant -body { ::math::quasirandom::qrpoints create simple sphere set result [simple integral const 2.0] } -result [expr {4.0 * 2.0**2 * cos(-1.0)}] -cleanup {simple destroy} test "Quasirandom-4.5" "Integrate constant function - ball" -match tolerant -body { ::math::quasirandom::qrpoints create simple ball set result [simple integral const 2.0] } -result [expr {4.0/3.0 * 2.0**3 * cos(-1.0)}] -cleanup {simple destroy} # We do not use too many evaluations ... error less than 1% test "Quasirandom-4.6" "Integrate linear function (x, y, z)" -match error1percent -body { ::math::quasirandom::qrpoints create simple 3 set result [list [simple integral fx {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] \ [simple integral fy {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] \ [simple integral fz {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] ] } -result {0.5 0.5 0.5} -cleanup {simple destroy} # # The function varies "sharply", so we need more evaluations # test "Quasirandom-4.7" "Integrate (xyz)**4" -match error1percent -body { ::math::quasirandom::qrpoints create simple 3 # Exact answer is 1/125 set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 1000] } -result 0.0080 -cleanup {simple destroy} # # Detailed integration: provides error estimates but also an indication that # the values can differ quite a bit # test "Quasirandom-5.1" "Integrate constant function with details - volume = 1" -match tolerant -body { ::math::quasirandom::qrpoints create simple 3 set result [simple integral-detailed const {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] set rawvalues [dict get $result -rawvalues] } -result {1.0 1.0 1.0 1.0} -cleanup {simple destroy} test "Quasirandom-5.2" "Integrate linear function with details - volume = 1" -match tolerant -body { ::math::quasirandom::qrpoints create simple 3 set result [simple integral-detailed fx {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] set rawvalues [dict get $result -rawvalues] } -result {0.48924267415013695 0.48855550905424594 0.5278683439583554 0.48718117886246404} -cleanup {simple destroy} test "Quasirandom-5.3" "Integrate (xyz)**4 with details - volume = 1" -match tolerant -body { ::math::quasirandom::qrpoints create simple 3 set result [simple integral-detailed fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] set rawvalues [dict get $result -rawvalues] } -result {0.0022115062627913935 0.009840104253511376 0.014937934937801888 0.007838969739655276} -cleanup {simple destroy} # # Test integration procedures in a different namespace # test "Quasirandom-6.1" "Integrate ::func::func" -match tolerant -body { namespace eval ::func { proc func {xy} { lassign $xy x y expr {$x**2+$y**2} } ::math::quasirandom::qrpoints create simple 2 set ::result [simple integral func {{0.0 1.0} {0.0 1.0}}] } return $result } -result {0.67353777} -cleanup {::func::simple destroy} test "Quasirandom-6.2" "Integrate (details) ::func::func" -match tolerant -body { namespace eval ::func { proc func {xy} { lassign $xy x y expr {$x**2+$y**2} } ::math::quasirandom::qrpoints create simple 2 set ::result [simple integral-detailed func {{0.0 1.0} {0.0 1.0}}] } return [dict get $result -estimate] } -result {0.67353777} -cleanup {::func::simple destroy} # TODO: # - func in different namespace # - implement detailed integration and test the details # - implement minimization # # Hm, the less than 1% error in the above test is a coincidence. The error is more # likely to be 10%. # if {0} { ::math::quasirandom::qrpoints create simple 3 # Exact answer is 1/125 set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 100] puts "fxyz4: $result" simple set-step 0 set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 1000] puts "fxyz4: $result" set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 1000] puts "fxyz4: $result" set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 1000] puts "fxyz4: $result" package require math::statistics set samples {} for {set trial 0} {$trial < 10} {incr trial} { set sum 0.0 for {set p 0} {$p < 100} {incr p} { set x [expr {rand()}] set y [expr {rand()}] set z [expr {rand()}] set sum [expr {$sum + [fxyz4 [list $x $y $z]]}] } puts "Trial $trial: [expr {$sum/100.0}]" lappend samples [expr {$sum/100.0}] } puts "MonteCarlo (100):" puts [::math::statistics::mean $samples] puts [::math::statistics::stdev $samples] set samples {} for {set trial 0} {$trial < 10} {incr trial} { set sum 0.0 for {set p 0} {$p < 1000} {incr p} { set x [expr {rand()}] set y [expr {rand()}] set z [expr {rand()}] set sum [expr {$sum + [fxyz4 [list $x $y $z]]}] } puts "Trial $trial: [expr {$sum/1000.0}]" lappend samples [expr {$sum/1000.0}] } puts "MonteCarlo (1000):" puts [::math::statistics::mean $samples] puts [::math::statistics::stdev $samples] set samples {} for {set trial 0} {$trial < 10} {incr trial} { set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 100] lappend samples $result } puts "Quasi-random (100):" puts [::math::statistics::mean $samples] puts [::math::statistics::stdev $samples] set samples {} for {set trial 0} {$trial < 10} {incr trial} { set result [simple integral fxyz4 {{0.0 1.0} {0.0 1.0} {0.0 1.0}} -evaluations 1000] lappend samples $result } puts "Quasi-random (1000):" puts [::math::statistics::mean $samples] puts [::math::statistics::stdev $samples] puts [simple integral-detailed fx {{0.0 1.0} {0.0 1.0} {0.0 1.0}}] } # ------------------------------------------------------------------------- # End of test cases testsuiteCleanup