Graphics demo: RANDU Spectral Test

dusthillresident 2023-09-13 : RANDU is a random number generator that was in widespread use in the 1960s and 1970s. It's famous for being really bad.

This graphics demo implements the spectral test, as seen in the picture on the wikipedia page about RANDU. It's a cube containing a number of points whose positions were generated by the RANDU function.

Adjust the sliders to rotate the cube around. From some angles, the points visually appear to be randomly distributed... but from other angles, it becomes obvious that the points all lie on 15 distinct planes.


Jeff Smith 2023-09-15 : Below is an online demo using CloudTk. This demo runs “Graphics demo: RANDU Spectral Test” in an Alpine Linux Docker Container. It is a 27.4MB image which is made up of Alpine Linux + tclkit + Graphics-demo-RANDU-Spectral-Test.kit + libx11 + libxft + fontconfig. It is run under a user account in the Container. The Container is restrictive with permissions for "Other" removed for "execute" and "read" for certain directories.


package require Tk
tk appname "RANDU Spectral Test"

pack [canvas .c -background black] -fill both -expand 1
foreach i {X Y Z} {
 pack [scale .s$i -variable $i -from -3.141593 -to 3.141593 -resolution 0 -orient h -command {rotateCube $X $Y $Z; return}] -fill x
}
set X -0.44
set Y 0.16

# The RANDU pseudo-random number generator.
# RANDU is widely considered to be one of the most ill-conceived
# random number generators ever designed, and was described
# as "truly horrible" by Donald Knuth.
set randu_seed 1
proc randu {} {
 global randu_seed
 set randu_seed [expr {($randu_seed * 65539) % 2147483648}]
 return [expr {$randu_seed / 2147483648.0}]
}

# Generate 1000 points in 3D space with RANDU
for {set i 0} {$i<1000} {incr i} {
 lappend points [list [expr {[randu]*2.0-1.0}] [expr {[randu]*2.0-1.0}] [expr {[randu]*2.0-1.0}]]
}

# Procedure for calculating the dot product of two matrices
proc dotProduct {a b} {
 set a_w [llength [lindex $a 0] ]
 set a_h [llength $a]
 set b_w [llength [lindex $b 0] ]
 set b_h [llength $b]
 set c_w $b_w
 set c_h $a_h
 set c   {}
 set row {}
 for {set i 0} {$i<$c_w} {incr i} {
  lappend row 0
 }
 for {set i 0} {$i<$c_h} {incr i} {
  lappend c $row
 }
 for {set i 0} {$i<$a_h} {incr i} {
  for {set j 0} {$j<$b_w} {incr j} {
   set product 0
   for {set k 0} {$k<$a_w} {incr k} {
    set product [expr {[lindex $a $i $k] * [lindex $b $k $j] + $product}]
   }
   lset c $i $j $product
  }
 }
 set c
}

# Define the cube
set cube {
 {-1.0 -1.0 -1.0}
 {-1.0 -1.0  1.0}
 { 1.0 -1.0  1.0}
 { 1.0 -1.0 -1.0}

 {-1.0  1.0 -1.0}
 {-1.0  1.0  1.0}
 { 1.0  1.0  1.0}
 { 1.0  1.0 -1.0}
}

proc rotateCube {xa ya za} {
 # Prepare rotation matrix
 set rx [list \
  [list 1.0 0.0 0.0] \
  [list 0.0 [expr {cos($xa)}] [expr {-sin($xa)}]] \
  [list 0.0 [expr {sin($xa)}] [expr {cos($xa)}]] \
 ]
 set ry [list \
  [list [expr {cos($ya)}] 0.0 [expr {sin($ya)}]] \
  {0.0 1.0 0.0} \
  [list [expr {-sin($ya)}] 0.0 [expr {cos($ya)}]] \
 ]
 set rz [list \
  [list [expr {cos($za)}] [expr {-sin($za)}] 0.0] \
  [list [expr {sin($za)}] [expr {cos($za)}] 0.0] \
  {0.0 0.0 1.0} \
 ]
 set rr [dotProduct $rz [dotProduct $rx $ry]]
 # Perform rotation
 global cube points
 set rotatedPoints [dotProduct $points $rr]
 set rotatedCube [dotProduct $cube $rr]
 # Draw result
 set wh [expr {min([winfo width .c],[winfo height .c])*0.26}]
 set cx [expr {[winfo width .c]*0.5}]
 set cy [expr {[winfo height .c]*0.5}]
 set l [llength $points]
 for {set i 0} {$i<$l} {incr i} {
  lset rotatedPoints $i 0 [expr {[lindex $rotatedPoints $i 0]*$wh+$cx}]
  lset rotatedPoints $i 1 [expr {[lindex $rotatedPoints $i 1]*$wh+$cy}]
 }
 for {set i 0} {$i<8} {incr i} {
  lappend xx [expr {[lindex $rotatedCube $i 0]*$wh+$cx}]
  lappend yy [expr {[lindex $rotatedCube $i 1]*$wh+$cy}]
 }
 .c delete all
 # Draw points
 set pointSize [expr {max(int($wh/70),1)}]
 foreach i $rotatedPoints {
  lassign $i x y
  .c create rectangle $x $y $x $y -width $pointSize -outline blue
 }
 # Draw cube
 foreach j {0 4} {
  for {set i 0} {$i<4} {incr i} {
   set a [expr {$i+$j}]
   set b [expr {(($i+1)&3)+$j}]
   .c create line \
          [lindex $xx $a] \
          [lindex $yy $a] \
          [lindex $xx $b] \
          [lindex $yy $b] -fill white -width 2
  }
 }
 foreach i {0 1 2 3} {
  .c create line [lindex $xx $i] [lindex $yy $i] [lindex $xx $i+4] [lindex $yy $i+4] -fill white -width 2
 }
 for {set i 0} {$i<8} {incr i} {
  set x [lindex $xx $i]
  set y [lindex $yy $i]
  .c create oval [expr {$x-4}] [expr {$y-4}] [expr {$x+4}] [expr {$y+4}] -fill white -outline grey
 }
}

bind .c <Configure> [.sX cget -command]