Ulam Spiral Demo



This is a Tcl/Tk script which illustrates a method for generating Ulam's Spiral, see also Prime Number Browser and Primal Screens. In the figure, we have the integers from 1 to 49 laid out in a spiral pattern as first described by Stanislau Ulam, see [1] http://mathworld.wolfram.com/PrimeSpiral.html . The blue Horizontal and Vertical lines passing through the number 1, are for reference purposes. The two black Diagonal lines traversing the upper left and lower right quadrants pass through integers which are perfect squares. The upper left line passes thru numbers of the form y = M*M, where M is an even integer. The lower right line passes thru values where M is odd.

Let M be 2, and wrap (2*M - 1) cells in an inverted L pattern to the right of and around the single starting cell, creating a square pattern of four cells. For the next iteration, let M be 3, and again, wrap (2*M - 1) cells in an L shaped pattern around the last four cells, creating a new pattern of nine cells. The third iteration for M = 4, wraps another (2*M - 1) cells about these last cells. The terminating cell always contains the integer M*M, and falls on one of the lines of even or odd squres. Prime numbers are printed in bold, non primes are in italics. Every time the "Click_Here" button is depressed, M is incremented by one and a new wrapping layer is added. See Primal Screens for further information about the significance of the lines of Even and Odd Squares. For further background, see the fine page by Gerard Sookahet on the Wiki, at Ulam spiral.

This script was developed on an iMac, running Leopard OS X, with TclTk 8.4.7 installed. It has a 19 inch screen, so I'm not sure how the graphics might appear, or the script behave on other systems, your results may vary. Any helpful comments or modifications are greatly appreciated.

# \
exec wish "$0" ${1+"$@"}

#                   Ulam Spiral Demo Program
#   METHOD---
#   On an integer lattice, place square cells centered on each lattice point.
#   Make cell side lengths equal to the distance between lattice points.  
#   Starting with a single cell at the center of the lattice, wrap it with 
#   three cells that form an inverted "L" shape starting to the right of the 
#   first cell, completing a larger Square Pattern of four cells.  Label the first
#   cell with the number 1, then assign increasing integers to each new cell.  
#   After the last cell has been labeled, wrap an "L" shaped pattern of five 
#   more cells around the the first four cells.  Continue to repeat the
#   wrapping process with inverted and regular "L" shapes.
#   This scheme forms a Counter Clockwise Spiral.  Clockwise creates a 
#   different pattern.  The starting integer and location of the starting
#   integer within the first group of four cells might also be varied.

#  Version of March 16, 2009

#    Comments:
#    M -- the number of cells forming the edge of a larger Square Pattern.
#    primes -- the list containing the prime numbers smaller than 121.
#    primeIndex -- The zero origin index to the list of primes.
#    runningIndex -- The current square cell number to be plotted.
#    The side of a square cell is  2 * $delta in length.

#   Calling Sequence
#   canvasSetup
#   Click_Here
#       drawSquares
#         iterate
#           square
#       drawCoords
#       odd_evenSquares

#  Some initialization.
set bkgndColor {}
set delta 30
set fgndColor black
set nucX {}
set nucY {}
set M 2
set primeFont "Courier 20 bold"
set primeIndex 0

#   The list containing prime numbers smaller than 121.
set primes {
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 
103 107 109 113  

set runningIndex 1
set squareColors { {gainsboro  black} {linen      black} {lavender   black} \
          {gray       black} {"deep sky blue"  white} {firebrick  white} \
          {aquamarine black} {green      black} {sienna     white} \
          {turquoise  black} {maroon     white} {bisque4    white} \
          {SlateBlue  white} 

set textFont "Times 16 italic"

#  Proc Click_Here
#  A proc which responds to the Click_Here button.
#  The next wrapping of (2*M -1) cells will 
#  be added to the existing Ulam Spiral plot.  
#  Also refreshes the Reference Lines.
#             Demo.tcl
proc Click_Here { } {
   global  cX cY M M_Value nucX nucY warnTxt
   if {$M == 2} {
#  Initial Square created here.
      square $cX $cY

   if {$M > 11} {
      $warnTxt delete 1.0 end
      $warnTxt insert insert "That's It!"
   } else {
      set cX $nucX
      set cY $nucY

   $M_Value delete 1.0 end
   $M_Value insert insert [expr {$M -1}]

#  Add Reference Lines.  Do this every cycle so lines overlay numbers.

#  Proc drawCoords.
#  Overlay blue X and Y Coordinate Axes on the Ulam plot
#  for reference purposes.
#             Demo.tcl
proc drawCoords {} {
      global  cX0 cY0 window xMin xMax yMin yMax

#     Draw Coordinate Axes
      set coords {}
      set x1 $xMin
      set y1 $cY0
      set x2 $xMax
      set y2 $cY0
      lappend coords [list $x1 $y1 $x2 $y2]
      eval {$window create line} $coords \
           {-width 0 -fill blue2}
      set coords {}
      set x1 $cX0
      set y1 $yMin
      set x2 $cX0
      set y2 $yMax
      lappend coords [list $x1 $y1 $x2 $y2]
      eval {$window create line} $coords \
           {-width 0 -fill blue2}

#  Proc odd_evenSquares.
#  Draw lines through cells which are the Squares of Odd
#  and Even numbers.
#             Demo.tcl
proc odd_evenSquares {} {

#   Draw lines through the squares of the Odd and Even numbers.
    plotLine 336 276  96 36
    plotLine 336 336  636 636   

#  Proc plotLine.
#  Plot a line connecting the two points, x1 y1 and x2 y2.
#             Demo.tcl
proc plotLine {x1 y1 x2 y2} {
    global  window

    lappend coords [list $x1 $y1 $x2 $y2]
    eval {$window create line} $coords \
         {-width 0 -fill black} 

#  Proc canvasSetup
#  Create the Ulam Spiral canvas with buttons etc.
#             Demo.tcl
proc canvasSetup { } {
   global cX0 cY0 cX cY delta M_Value primeFont warnTxt
   global window xMin xMax yMin yMax

   set window .square
   catch {destroy $window}
   toplevel $window

   wm title $window "Ulam's Spiral Demo"
   wm geometry $window +600+150
   wm withdraw .
   focus $window

#  Determine screen width and height.
   set screenwd [winfo screenwidth .]
   set screenht [winfo screenheight .]

#  Make a canvas to contain the Ulam screen.
#   set winwd [expr {int ( 0.45*$screenwd )}]
   set winwd [expr {int ( 0.40*$screenwd )}]
   set winht $winwd

   set cX [expr {int ($winwd / 2)}]
   set cY $cX

#  Center of the very first Cell.
   set cX0 $cX
   set cY0 $cY
   set xMin [expr {$cX0 - 10 * $delta}]
   set xMax [expr {$cX0 + 10 * $delta}]
   set yMin [expr {$cX0 - 10 * $delta}]
   set yMax [expr {$cX0 + 10 * $delta}]

   set f1 [frame $window.f1 -relief sunken -borderwidth 2 -height 30]
   pack $f1 -fill x -side top

   button $f1.bp -text "Click_Here" -width 9 -bg blue -fg white -font $primeFont \
        -command Click_Here

   label $f1.lbl1 -text "M is:" -width 15  -anchor e -font $primeFont
   text $f1.txt -relief raised -bd 2 -width 2 -height 1 -font $primeFont
   set M_Value $f1.txt
   text $f1.txtw -relief raised -bd 2 -width 10 -height 1 -font $primeFont
   set warnTxt $f1.txtw
   eval pack [winfo children $f1] -side left 

   button  $f1.bq -text Quit -width 5 -bg blue -fg white -font $primeFont \
        -command exit
   pack $f1.bq -side right

   set window $window.c
   pack [canvas $window -width $winwd -height $winht -bg "alice blue"]

   raise .

#  Proc drawSquares
#  A proc to draw a wrapping pattern of (2*$M -1) cells 
#  in an "L" or inverted "L" shape.
#             Demo.tcl
proc drawSquares {} {
   global bkgndColor cX cY  fgndColor M nucX nucY 
   global runningIndex squareColors

   set nucX $cX
   set nucY $cY

   set colors [lindex $squareColors [expr {$M -2}] ]
   set fgndColor [lindex $colors 1]
   set bkgndColor [lindex $colors 0]


   incr M
   incr runningIndex

#  Proc iterate.
#  Create and number the individual cells in the wrapping
#  pattern.  Draw cells in Counter-ClockWise Direction.
#             Demo.tcl
proc iterate { } {
      global   delta M nextOp nucX nucY runningIndex

      set num_Moves [expr {$M -1}]
      set twoDelta [expr {2*$delta}]
      set nextOp "right"
      if {[expr {fmod ($M,2)}] > 0.0} {

         set nextOp "left"

      if {[string compare $nextOp "right"] == 0} {
         set nucX [expr {$nucX + $twoDelta}]
         square $nucX $nucY

         for {set i 1} {$i <= $num_Moves} {incr i 1} {
            set nucY [expr {$nucY - $twoDelta}]
            square $nucX $nucY

         for {set i 1} {$i <= $num_Moves} {incr i 1} {
            set nucX [expr {$nucX - $twoDelta}]
            square $nucX $nucY

      } else {
          set nucX [expr {$nucX - $twoDelta}]
          square $nucX $nucY

          for {set i 1} {$i <= $num_Moves} {incr i 1} {
             set nucY [expr {$nucY + $twoDelta}]
             square $nucX $nucY

          for {set i 1} {$i <= $num_Moves} {incr i 1} {
             set nucX [expr {$nucX + $twoDelta}]
             square $nucX $nucY

      incr runningIndex -1

#  Proc square
#  Draw a square cell centered at x and y.  The cell edge will be 
#  (2 * $delta) in length.  Check to see if runningIndex is a 
#  member of the primes list.  If so, print it's value in blue
#  and primeFont.  Otherwise, print values in black and textFont.
#             Demo.tcl
proc square { x y } {
   global bkgndColor delta fgndColor primeFont primeIndex
   global primes runningIndex textFont window

   update idletasks
   after 125

   if {[lindex $primes $primeIndex] == $runningIndex} {
      set fgnd blue
      set numberFont $primeFont
      incr primeIndex
   } else {
      set fgnd $fgndColor
      set numberFont $textFont

#  Calculate coordinates for square cell.
   set x1 [expr {$x - $delta}]
   set y1 [expr {$y - $delta}]
   set x2 [expr {$x + $delta}]
   set y2 [expr {$y + $delta}]

#  Create cell here with fill bkgndColor. 
   $window create rectangle $x1 $y1 $x2 $y2 -fill $bkgndColor \
         -outline lightblue1 -width 2
#  Label cell with runningIndex.
   $window create text $x $y -text $runningIndex -fill $fgnd -font $numberFont   
   incr runningIndex

#   Start Demo.

See also Prime Number Browser and Primal Screens