diff options
Diffstat (limited to 'tcllib/modules/simulation/random.tcl')
-rwxr-xr-x | tcllib/modules/simulation/random.tcl | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/tcllib/modules/simulation/random.tcl b/tcllib/modules/simulation/random.tcl new file mode 100755 index 0000000..4541761 --- /dev/null +++ b/tcllib/modules/simulation/random.tcl @@ -0,0 +1,577 @@ +# random.tcl -- +# Create procedures that return various types of pseudo-random +# number generators (PRNGs) +# +# Copyright (c) 2007 by Arjen Markus <arjenmarkus@users.sourceforge.net> +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# TODO: +# - Beta +# - Weighted discrete +# - Poisson +# - Cauchy +# - Binomial +# +# Note: +# Several formulae and algorithms come from "Monte Carlo Simulation" +# by C. Mooney (Sage Publications, 1997) +# +# RCS: @(#) $Id: random.tcl,v 1.5 2012/08/15 04:38:48 arjenmarkus Exp $ +#------------------------------------------------------------------------------ + +package require Tcl 8.4 + +# ::simulation::random -- +# Create the namespace +# +namespace eval ::simulation::random { + variable count 0 + variable pi [expr {4.0*atan(1.0)}] +} + + +# prng_Bernoulli -- +# Create a PRNG with a Bernoulli distribution +# +# Arguments: +# p Probability that the outcome will be 1 +# +# Result: +# Name of a procedure that returns a Bernoulli-distributed random number +# +proc ::simulation::random::prng_Bernoulli {p} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list P $p] {return [expr {rand()<P? 1 : 0}]}] + + return $name +} + + +# prng_Uniform -- +# Create a PRNG with a uniform distribution in a given range +# +# Arguments: +# min Minimum value +# max Maximum value +# +# Result: +# Name of a procedure that returns a uniformly distributed +# random number +# +proc ::simulation::random::prng_Uniform {min max} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + set range [expr {$max-$min}] + proc $name {} [string map [list MIN $min RANGE $range] {return [expr {MIN+RANGE*rand()}]}] + + return $name +} + + +# prng_Exponential -- +# Create a PRNG with an exponential distribution with given mean +# +# Arguments: +# min Minimum value +# mean Mean value +# +# Result: +# Name of a procedure that returns an exponentially distributed +# random number +# +proc ::simulation::random::prng_Exponential {min mean} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + set b [expr {$mean-$min}] + proc $name {} [string map [list MIN $min B $b] {return [expr {MIN-B*log(rand())}]}] + + return $name +} + + +# prng_Discrete -- +# Create a PRNG with a uniform but discrete distribution +# +# Arguments: +# n Outcome is an integer between 0 and n-1 +# +# Result: +# Name of a procedure that returns such a random number +# +proc ::simulation::random::prng_Discrete {n} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list N $n] {return [expr {int(N*rand())}]}] + + return $name +} + + +# prng_Poisson -- +# Create a PRNG with a Poisson distribution +# +# Arguments: +# lambda The one parameter of the Poisson distribution +# +# Result: +# Name of a procedure that returns such a random number +# +proc ::simulation::random::prng_Poisson {lambda} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + + set explambda [expr {exp(-$lambda)}] + + proc $name {} [string map [list INIT $explambda LAMBDA $lambda] { + set r [expr {rand()}] + set number 0 + set sum INIT + set rfact INIT + + while { $r > $sum } { + set rfact [expr {$rfact * LAMBDA /($number+1.0)}] + set sum [expr {$sum + $rfact}] + incr number + } + return $number + }] + + return $name +} + + +# prng_Normal -- +# Create a PRNG with a normal distribution +# +# Arguments: +# mean Mean of the distribution +# stdev Standard deviation of the distribution +# +# Result: +# Name of a procedure that returns such a random number +# +# Note: +# Use the Box-Mueller method to generate a normal random number +# +proc ::simulation::random::prng_Normal {mean stdev} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list MEAN $mean STDEV $stdev] \ + { + variable pi + set rad [expr {sqrt(-2.0*log(rand()))}] + set phi [expr {2.0*$pi*rand()}] + set r [expr {$rad*cos($phi)}] + return [expr {MEAN + STDEV*$r}] + }] + + return $name +} + + +# prng_Pareto -- +# Create a PRNG with a Pareto distribution +# +# Arguments: +# min Minimum value for the distribution +# steep Steepness of the descent +# +# Result: +# Name of a procedure that returns a Pareto-distributed number +# +proc ::simulation::random::prng_Pareto {min steep} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + + set rsteep [expr {1.0/$steep}] + proc $name {} [string map [list MIN $min RSTEEP $rsteep] \ + { + return [expr {MIN * pow(1.0-rand(),RSTEEP)}] + }] + + return $name +} + + +# prng_Gumbel -- +# Create a PRNG with a Gumbel distribution +# +# Arguments: +# min Minimum value for the distribution +# f Factor to scale the value +# +# Result: +# Name of a procedure that returns a Gumbel-distributed number +# +# Note: +# The chance P(v) = exp( -exp( f*(v-min) ) ) +# +proc ::simulation::random::prng_Gumbel {min f} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + + proc $name {} [string map [list MIN $min F $f] \ + { + return [expr {MIN + log( -log(1.0-rand()) ) / F}] + }] + + return $name +} + + +# prng_chiSquared -- +# Create a PRNG with a chi-squared distribution +# +# Arguments: +# df Degrees of freedom +# +# Result: +# Name of a procedure that returns a chi-squared distributed number +# with mean 0 and standard deviation 1 +# +proc ::simulation::random::prng_chiSquared {df} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list DF $df] \ + { + variable pi + set y 0.0 + for { set i 0 } { $i < DF } { incr i } { + set rad [expr {sqrt(-log(rand()))}] + set phi [expr {2.0*$pi*rand()}] + set r [expr {$rad*cos($phi)}] + set y [expr {$y+$r*$r}] + } + return [expr {($y-DF)/sqrt(2.0*DF)}] + }] + + return $name +} + + +# prng_Disk -- +# Create a PRNG with a uniform distribution of points on a disk +# +# Arguments: +# rad Radius of the disk +# +# Result: +# Name of a procedure that returns the x- and y-coordinates of +# such a random point +# +proc ::simulation::random::prng_Disk {rad} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list RAD $rad] \ + { + variable pi + set rad [expr {RAD*sqrt(rand())}] + set phi [expr {2.0*$pi*rand()}] + set x [expr {$rad*cos($phi)}] + set y [expr {$rad*sin($phi)}] + return [list $x $y] + }] + + return $name +} + + +# prng_Ball -- +# Create a PRNG with a uniform distribution of points within a ball +# +# Arguments: +# rad Radius of the ball +# +# Result: +# Name of a procedure that returns the x-, y- and z-coordinates of +# such a random point +# +proc ::simulation::random::prng_Ball {rad} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list RAD $rad] \ + { + variable pi + set rad [expr {RAD*pow(rand(),0.333333333333)}] + set phi [expr {2.0*$pi*rand()}] + set theta [expr {acos(2.0*rand()-1.0)}] + set x [expr {$rad*cos($phi)*cos($theta)}] + set y [expr {$rad*sin($phi)*cos($theta)}] + set z [expr {$rad*sin($theta)}] + return [list $x $y $z] + }] + + return $name +} + + +# prng_Sphere -- +# Create a PRNG with a uniform distribution of points on the surface +# of a sphere +# +# Arguments: +# rad Radius of the sphere +# +# Result: +# Name of a procedure that returns the x-, y- and z-coordinates of +# such a random point +# +proc ::simulation::random::prng_Sphere {rad} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + proc $name {} [string map [list RAD $rad] \ + { + variable pi + set phi [expr {2.0*$pi*rand()}] + set theta [expr {acos(2.0*rand()-1.0)}] + set x [expr {RAD*cos($phi)*cos($theta)}] + set y [expr {RAD*sin($phi)*cos($theta)}] + set z [expr {RAD*sin($theta)}] + return [list $x $y $z] + }] + + return $name +} + + +# prng_Rectangle -- +# Create a PRNG with a uniform distribution of points in a rectangle +# +# Arguments: +# length Length of the rectangle (x-direction) +# width Width of the rectangle (y-direction) +# +# Result: +# Name of a procedure that returns the x- and y-coordinates of +# such a random point +# +proc ::simulation::random::prng_Rectangle {length width} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + + proc $name {} [string map [list LENGTH $length WIDTH $width] \ + { + set x [expr {LENGTH*rand()}] + set y [expr {WIDTH*rand()}] + return [list $x $y] + }] + + return $name +} + + +# prng_Block -- +# Create a PRNG with a uniform distribution of points in a block +# +# Arguments: +# length Length of the block (x-direction) +# width Width of the block (y-direction) +# depth Depth of the block (y-direction) +# +# Result: +# Name of a procedure that returns the x-, y- and z-coordinates of +# such a random point +# +proc ::simulation::random::prng_Block {length width depth} { + variable count + + incr count + + set name ::simulation::random::PRNG_$count + + proc $name {} [string map [list LENGTH $length WIDTH $width DEPTH $depth] \ + { + set x [expr {LENGTH*rand()}] + set y [expr {WIDTH*rand()}] + set z [expr {DEPTH*rand()}] + return [list $x $y $z] + }] + + return $name +} + +# Announce the package +# +package provide simulation::random 0.3.1 + + +# main -- +# Test code +# +if { 0 } { +set bin [::simulation::random::prng_Bernoulli 0.2] + +set ones 0 +set zeros 0 +for { set i 0} {$i < 100000} {incr i} { + if { [$bin] } { + incr ones + } else { + incr zeros + } +} + +puts "Bernoulli: $ones - $zeros" + +set discrete [::simulation::random::prng_Discrete 10] + +for { set i 0} {$i < 100000} {incr i} { + set v [$discrete] + + if { [info exists count($v)] } { + incr count($v) + } else { + set count($v) 1 + } +} + +puts "Discrete:" +parray count + +set rect [::simulation::random::prng_Rectangle 10 3] + +puts "Rectangle:" +for { set i 0} {$i < 10} {incr i} { + puts [$rect] +} + +set normal [::simulation::random::prng_Normal 0 1] + +puts "Normal:" +for { set i 0} {$i < 10} {incr i} { + puts [$normal] +} + +# +# Timing: how fast is the normal random number generator? +# +# Surprising speed: 15 million numbers per minute! +# +puts "Normal random number generator:" +puts "[time {set value [$normal]} 30000]" +set result [lindex [time {set value [$normal]} 30000] 0] +puts "[expr {60.0e6/$result}] numbers per minute" +puts "Creating a long list: [time {lappend value [$normal]} 30000]" +puts "[lrange $value 0 20] - [llength $value] numbers in total" +set value {} +set result [lindex [time {lappend value [$normal]} 30000] 0] +puts "[expr {60.0e6/$result}] numbers per minute" + +puts "Points in a rectangle:" +puts "[time {set value [$rect]} 30000]" +set result [lindex [time {set value [$rect]} 30000] 0] +puts "[expr {60.0e6/$result}] numbers per minute" + +# +# A more formal test +# +package require math +unset count +set samples 100000 + +set lambda 10.0 +set poisson [::simulation::random::prng_Poisson $lambda] +for { set i 0 } { $i < $samples } { incr i } { + set number [$poisson] + if { [info exists count($number)] } { + incr count($number) + } else { + set count($number) 1 + } +} +parray count +for { set i 0 } { $i < 30 } { incr i } { + set expected [expr {int($samples * pow($lambda,$i) * exp(-$lambda) / [::math::factorial $i])}] + set exp_error [expr {sqrt($expected)}] + if { [info exists count($i)] } { + if { $expected-$exp_error < $count($i) && + $expected+$exp_error > $count($i) } { + set okay "okay" + } else { + set okay "difference too large" + } + puts "$i $expected $count($i) - [expr {$expected/double($count($i))}] - $okay" + } else { + puts "$i $expected none" + } +} +} + +# +# Test hypothesis concerning rectangle +# +if { 0 } { +set r2 [::simulation::random::prng_Rectangle2 10 1] + +set count_down 0 +set count_up 0 +set count_left 0 +set count_right 0 +for { set i 0 } { $i < 1000000 } { incr i } { + + foreach {x y} [$r2] { + if { $x < 2.0 } { incr count_left } + if { $x > 8.0 } { incr count_right } + if { $y < 0.2 } { incr count_down } + if { $y > 0.8 } { incr count_up } + } +} +puts "Left-right:\t$count_left\t$count_right" +puts "Up-down: \t$count_up\t$count_down" +} + +# +# Check normal distribution +# +if { 0 } { + package require math::statistics + set normal [::simulation::random::prng_Normal 0 1] + + for { set i 0} {$i < 1000} {incr i} { + lappend numbers [$normal] + } + puts "Mean: [::math::statistics::mean $numbers]" + puts "Stdev: [::math::statistics::stdev $numbers]" +} |