/
rgd045
/
tcllib
Обзор
Документация
Войти
/
rgd045
/
tcllib
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
modules/simulation/random.tcl
645 строк
15 KB
rolf
Bumped package version for all modules [a-z]*.
30 сен 2023, 04:10
30 сен 2023, 04:10
8fa0eec
Код
Авторство
О чём код?
# 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.5 9 # ::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 } # prng_Triangle -- # Create a PRNG with a triangular distribution of points on an interval. # If the argument min is lower than the argument max, then smaller # values have higher probability and vice versa. # # Arguments: # min Minimum value # max Maximum value # # Result: # Name of a procedure that returns the random point # proc ::simulation::random::prng_Triangle {min max} { variable count incr count set name ::simulation::random::PRNG_$count set diff [expr {$max-$min}] if { $diff > 0.0 } { proc $name {} [string map [list MIN $min DIFF $diff] \ { set r [expr {1.0 - sqrt(1.0 - rand())}] set x [expr {MIN + DIFF*$r}] return $x }] } else { proc $name {} [string map [list MAX $max DIFF $diff] \ { set x [expr {MAX - DIFF*sqrt(rand())}] return $x }] } return $name } # prng_SymmetricTriangle -- # Create a PRNG with a symmetric triangular distribution of points on an interval. # # Arguments: # min Minimum value # max Maximum value # # Result: # Name of a procedure that returns the random point # proc ::simulation::random::prng_SymmetricTriangle {min max} { variable count incr count set name ::simulation::random::PRNG_$count set diff2 [expr {0.5 *($max-$min)}] proc $name {} [string map [list MIN $min DIFF2 $diff2] \ { return [expr {MIN + DIFF2 * (rand() + rand())}] }] return $name } # Announce the package # package provide simulation::random 0.5.0 # 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]" }