Skip to content

Instantly share code, notes, and snippets.

@tomofuminijo
Last active April 9, 2021 08:31
Show Gist options
  • Save tomofuminijo/643b529365dc458ff34f93381cca3af8 to your computer and use it in GitHub Desktop.
Save tomofuminijo/643b529365dc458ff34f93381cca3af8 to your computer and use it in GitHub Desktop.
Sample code to output Ulam spiral image in Python
from PIL import Image
from sympy import isprime
import math
def ulam_spiral(length: int, start_num: int = 1) -> list[list[int]]:
"""
ulam_spiral plots the spiral of Ulam on a square with a side length of
the inputed length.
The result is returned in the coordinates of a two-dimensional array.
Prime numbers are set to 1 and non-prime numbers are set to 0.
Parameters
----------
length : int
The length of one side of the ulam spiral to be created.
start_num : int, optional
Starting number of Ulam spiral, default is 1
Returns
-------
list[list[int]]
A two-dimensional array of prime numbers plotted on a Ulam spiral
with the length on one side.
Set to 1 for prime numbers and 0 for non-prime numbers.
"""
# Ulam spiral container, length * length, all initialized to 0
spiral = [[0 for x in range(length)] for i in range(length)]
# Center point of the spiral (odd numbers will be moved up)
start_x = math.ceil(length/2)
start_y = math.ceil(length/2)
# Position of the spiral
spiral_pos = SpiralPosition(start_x, start_y)
# Number to be checked
number = start_num
for i in range(length*length):
x, y = spiral_pos.next()
if any([x < 0, y < 0, x >= length, y >= length]):
continue
if isprime(number):
spiral[x][y] = 1
number += 1
return spiral
class Direction():
"""
Direction keeps track of the direction the spiral is moving on and
the number of move.
"""
# RIGHT: x+
RIGHT = (1, 0)
# UP: y-
UP = (0, -1)
# LEFT: x-
LEFT = (-1, 0)
# DOWN: y+
DOWN = (0, 1)
def __init__(self) -> None:
self._direction = []
self._cycle = 0
def next_directions(self) -> list[tuple[int]]:
"""
Returns the next direction in the spiral as an array of movement counts.
Returns
-------
list[tupple[int]]
List with the next direction arrayed by the number of moves.
"""
if not self._direction:
self._create_direction_list()
direction = self._direction.pop(0)
# right/up move count increase 1, 3, 5, 7, ... per cycle
# left/down move count increase 2, 4, 6, 8, ... per cycle
if direction in [Direction.RIGHT, Direction.UP]:
move_count = self._cycle*2 - 1
else:
move_count = self._cycle*2
next_directions = [direction for i in range(move_count)]
return next_directions
def _create_direction_list(self):
self._direction = [Direction.RIGHT,
Direction.UP,
Direction.LEFT,
Direction.DOWN]
self._cycle += 1
class SpiralPosition:
"""
SpiralPosition manages the current position of the spiral and
returns the next position.
"""
def __init__(self, start_x: int = 0, start_y: int = 0) -> None:
"""
When instantiating SpiralPosition, pass the starting position of
the spiral.
Parameters
----------
start_x : int, optional
Start x-position of the spiral, default is 0
start_y : int, optional
Start y position of the spiral, default is 0
"""
self.current_pos = [start_x, start_y]
self.direction = Direction()
self.side_position_list = []
def next(self) -> tuple[int, int]:
"""
Returns the next position on the spiral.
Returns
-------
tupple(int, int)
next position(x, y) on the spiral
"""
if not self.side_position_list:
self._create_next_side_positions()
cp = self.side_position_list.pop(0)
return cp[0], cp[1]
def _create_next_side_positions(self):
# Get next directions and create next positions
next_directions = self.direction.next_directions()
for d in next_directions:
self.current_pos = [x + y
for x, y
in zip(self.current_pos, d)]
self.side_position_list.append(self.current_pos)
if __name__ == '__main__':
SIDE_LENGTH = 200
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
# Generate an array of Ulam spiral
ulam = ulam_spiral(SIDE_LENGTH)
# Drawing ural spiral on an image
image = Image.new('RGB', (SIDE_LENGTH, SIDE_LENGTH), BLACK)
for x in range(len(ulam)):
for y in range(len(ulam[x])):
if ulam[x][y]:
image.putpixel(
(x, y),
WHITE
)
image.save('ulam_spiral.png')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment