Decomposing the Ulam spiral

By: on September 27, 2012

The Ulam spiral is a well-known mystery to mathematicians: draw a 1 in a grid, 2 in the cell to the right, 3 above the 2, and so on in a spiral:

17 16 15 14 13
18 5 4 3 12
19 6 1 2 11
20 7 8 9 10
21 22 23 24 25

Colour the primes:

17 16 15 14 13
18 5 4 3 12
19 6 1 2 11
20 7 8 9 10
21 22 23 24 25

For reasons unknown, long diagonal chains of primes appear. In this very small Ulam spiral, we don’t really see them (19, 7, 23), but we will.

How can we draw our own spiral? The essential elements of the spiral are two-fold: a primality test, and winding the line of positive integers round and round the origin of the complex plane. Hm, composing functions sounds promising!

Our primality test is a function from the positive integers to (True, False). Our traversal/map is a function from the positive integers to C, the complex plane. That is, given an integer n it tells us where on the plane that point will be after traversing the spiral n steps.

Squeak images have Integer >> #isPrime built in, so we need just write the map function.

Integer >> ulam
    "Ulam maps integers to (the integral values of) the complex plane.
     1 maps to 0@0, 2 maps to 1@0, 3 maps to 1@1, and so on in an
     anticlockwise square spiral."

    | n lastCorner squareSize sqModulus bottomLeft topLeft topRight |
    n := self leastSatisfier: [:k | ((2 * k) + 1) squared].
    squareSize := (2*n) + 1.
    lastCorner := squareSize squared.
    bottomLeft := lastCorner - (1 * (squareSize - 1)).
    topLeft := lastCorner - (2 * (squareSize - 1)).
    topRight := lastCorner - (3 * (squareSize - 1)).
    sqModulus := squareSize // 2.
    self = lastCorner ifTrue: [^ sqModulus @ sqModulus negated].
    bottomLeft <= self ifTrue:
        ["bottom edge of square"
        ^ (sqModulus negated + (self - bottomLeft)) @ sqModulus negated].
    topLeft < self ifTrue:
        ["left edge of square"
        ^ sqModulus negated @ (sqModulus - (self - topLeft))].
    topRight < self ifTrue:
        ["top edge of square"        
        ^ (sqModulus - (self - topRight)) @ sqModulus].
    ^ sqModulus @ (sqModulus - (topRight - self)).

Integer >> leastSatisfier: aUnaryBlock
    "Can we find an integer value for which (aUnaryBlock value: n) = anInteger?"
    | k n max |
    "While this prevents nontermination, it means not being able to generate
     large spirals. Hold your nose and move along."

    max := 10000.
    n := 0.
    [k := aUnaryBlock value: n.
    k >= self ifTrue: [^ n].
    n = max ifTrue: [Error signal: 'Couldn''t satisfy equation ',
        aUnaryBlock decompile printString, ' with value ', self printString].
    n := n + 1] repeat.

With this in hand, the solution is simple: create a canvas, select the primes, and paint those points representing primes.

| form primes width |
width := 400.

primes := (1 to: width squared) select: #isPrime.
form := Form extent: width@width depth: 1.
primes do: [:p | | u |
    u := p ulam.
    "ulam returns origin-centred values, so we translate the points to the
     middle of the form. Too, we flip the points in the x-axis to match
     Form's layout, which as 1@1 at the top left."

    u := (u flipBy: #vertical centerAt: 0@0)
        translateBy: (width // 2)@(width // 2).

    "Filling always fills Rectangles. The smallest Rectangle is
     thus 1px wide. Let u = (x,y). Then u + 1 = (x + 1, y + 1)."

    form fill: (u corner: (u + 1)) fillColor: Color black].
PNGReadWriter putForm: form onFileNamed: 'ulam.png'

And result!

Ulam spiral

As a result of our insight – decomposing the problem into separate traversal, find and do-something functions, we may now construct different spirals: rectangular spirals, clockwise spirals, and so on.

FacebookTwitterGoogle+

Post a comment

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>