Why do palindromes form these arcs?

230 Views Asked by At

I was watching 3Blue1Brown's YouTube video "Why do prime numbers make these spirals?", and it inspired me to look for some patterns myself. So I made some Python code as follows below. How it works is that each nonnegative integer is placed along a spiral, with x(n) = n * cos(n) and y(n) = n * sin(n). Only numbers that are palindromes in the chosen base (original question is base 10) are displayed on the spiral, every other number is hidden.

#!/usr/bin/env python3

from typing import Generator
import argparse
import math

from PIL import Image, ImageDraw


def to_base(n: int, base: int) -> list[int]:
    """Converts a number to a base other than 10
    Maximum base: 36
    """
    if base < 2 or base > 36:
        raise ValueError("Base must be greater than 1 and less than 36")

    digits = []
    while n >= base:
        n, m = divmod(n, base)
        digits.append(m)
    digits.append(n)
    return digits


def palindromes(base: int = 10) -> Generator[int, None, None]:
    def is_palindrome(n: int):
        sn = to_base(n, base)
        return sn == sn[::-1]

    n = 1
    while True:
        if is_palindrome(n):
            yield n
        n += 1


def spiral_coords(
    n: float, x_origin: float = 0.0, y_origin: float = 0.0, scale: float = 1.0
) -> tuple[float, float]:
    x = n * math.cos(n)
    y = n * math.sin(n)
    return x * scale + x_origin, y * scale + y_origin


def fill_spiral(
    gen: Generator[int, None, None],
    height: int,
    width: int,
    radius: float = 1.0,
    scale: float = 1.0,
):
    """Fills an Image of size `(height, width)` with zeroes.
    Then, fills each element of `gen` in on the spiral
    """
    x_origin = width / 2
    y_origin = height / 2
    img = Image.new("1", (width, height), 0)
    draw = ImageDraw.Draw(img)
    for n in gen:
        x, y = spiral_coords(n, x_origin, y_origin, scale)
        # if either x or y is in bounds, draw circle
        if 0 <= x < width or 0 <= y < height:
            top_left = (x - radius, y - radius)
            bottom_right = (x + radius, y + radius)
            draw.ellipse([top_left, bottom_right], 1)
        else:
            break
    return img


def main():
    parser = argparse.ArgumentParser(description="Create an image of the prime spiral")
    parser.add_argument("height", type=int, help="Image height")
    parser.add_argument("width", type=int, help="Image width")
    parser.add_argument(
        "--radius", type=float, help="Radius of each point, default 1", default=1.0
    )
    parser.add_argument("--base", type=int, help="Number base, default 10", default=10)
    parser.add_argument(
        "--scale",
        type=float,
        help="Zoom scale. Larger means more zoomed in, smaller is more zoomed out. Default 1",
        default=1.0,
    )
    args = parser.parse_args()

    spiral = fill_spiral(
        palindromes(args.base), args.height, args.width, args.radius, args.scale
    )

    spiral.show()


if __name__ == "__main__":
    main()

And I ran the code as: ./main.py 10000 10000 --radius 10, receiving this image.

Outside of the dense cloud of palindromes in the middle, there is nothing but empty space and these arcs of palindromes. Some observations:

  • The spiral runs counterclockwise but these arcs run clockwise
  • Each arc is five palindromes long
  • Each arc appears to be placed along one of four clockwise spiral arms out of the central cloud

If anyone could make a larger image including more data points that would also be welcome. My image viewer apparently likes to crap out if I make the image any larger than 10000x10000 pixels, so I'm not getting as much data as I would like.

Edit: a comment asked about the number base. The displayed image is in base 10, but changing the base creates different patterns. For instance, base 5base 5, and base 16base 16

2

There are 2 best solutions below

0
On BEST ANSWER

All your points are on the Archimedean spiral that comes from polar plotting $(t,t)$. At the scale you are plotting that spiral is too tight to notice. I plotted the four digit palindromes in a spreadsheet and show the result below. I believe your series of five come from these. The cause of the groups of five is that the palindromes come in groups of $10$ at intervals of $110$. The first group of $10$ starts at $1001$ and ends at $1991$. The next starts at $2002$ and ends at $2992$ and so on. It turns out that $110$ radians is $\frac {110}{2\pi} \approx 17.507$ full turns, so an increment of $220$ results in just over $35$ full turns and the next angle is $0.014$ full turns or $5^\circ$. That explains the groups of $5$. The jump to the next group is $1001$, which represents about $159.314$ full turns. It will be about $1/3$ of a turn around from the previous group.

enter image description here

0
On

Here is Mathematica code for anyone else interested in playing around with this (up to $d$ digits in base-$b$):

b=10;d=4;
palindromes = Select[Tuples[Range[b]-1,d],#==Reverse[#]&];
ListPlot[# Cos[#],Sin[#]}&/@FromDigits/@palindromes,AspectRatio->1]]