Почему простые числа, отображенные в полярных координатах, имеют форму спиралей или линий?

Создание сюжета

Для начала нам необходимо увидеть, каковы эти шаблоны на самом деле. Давайте начнем наше исследование с импорта базовых модулей.

import math import sympy import numpy as np import matplotlib.pyplot as plt %matplotlib inline %config InlineBackend.figure_format='retina' plt.style.use('dark_background')


Один из модулей, который я здесь использую, но который я обычно не использую, — это SymPy, библиотека Python для символьной математики. Хотя SymPy предлагает широкий спектр функций для вычислений, я использую его просто для генерации простых чисел.

print(list(sympy.primerange(0, 100)))

[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]


Полярные координаты

Сначала давайте напишем функцию, которая принимает некоторое число в качестве входных данных и преобразует его в декартово представление полярных координат. Выходные данные сами по себе являются декартовыми, но координаты, которые они представляют, соответствуют полярным координатам. Мы могли бы понимать эту функцию как преобразованиеС: Р →Р2С:Р→Р2такой что  

C(x)=(xcos(x),xsin(x))

В Python мы можем реализовать этот перевод следующим образом:

def get_coordinate(num): return num * np.cos(num), num * np.sin(num)

Давайте проведем быструю проверку работоспособности и увидим, чтоС( 1 )С(1)возвращает некоторую точку в первом квадранте.

get_coordinate(1)

(0.5403023058681398, 0.8414709848078965)


Отлично! Однако проблема с текущей настройкой заключается в том, что она не векторизована; чтобы сгенерировать координаты, скажем, для десяти чисел, нам понадобится цикл for для генерации координат для каждого из десяти чисел.

Хорошей новостью является то, что мы можем воспользоваться векторизацией NumPy. А именно, если входные данные — это NumPy ndarray, то мы можем обрабатывать несколько чисел параллельно, без необходимости в трудоемких циклах.

x = np.arange(1, 10) get_coordinate(x)


(array([ 0.54030231, -0.83229367, -2.96997749, -2.61457448, 1.41831093, 5.76102172, 5.27731578, -1.16400027, -8.20017236]), array([ 0.84147098, 1.81859485, 0.42336002, -3.02720998, -4.79462137, -1.67649299, 4.59890619, 7.91486597, 3.70906637]))


Ниже приведена вспомогательная функция, которую мы будем использовать для упрощения создания графика. Она также избавит нас от написания большого количества повторяющегося шаблонного кода.

def create_plot(nums, figsize=8, s=None, show_annot=False): nums = np.array(list(nums)) x, y = get_coordinate(nums) plt.figure(figsize=(figsize, figsize)) plt.axis("off") plt.scatter(x, y, s=s) plt.show()


Теперь у нас есть все необходимые инструменты для визуализации этих закономерностей. Вот визуализация с первыми 1000 простыми числами.

primes = sympy.primerange(0, 500) create_plot(primes)

Немного неясно, но кажется, что здесь может быть некая закономерность. Если мы построим это для 10000 простых чисел, то получим прекрасную закономерность, чем-то напоминающую галактику.

primes = sympy.primerange(0, 20000) create_plot(primes, s=1)

Конечно, мы могли бы пойти еще дальше. Здесь я попытался визуализировать первые 400000 простых чисел. Спиральный узор больше не присутствует, и вместо этого появляется то, что кажется прямыми линиями, пересекающими круглое облако точек.

primes = sympy.primerange(0, 400000) create_plot(primes, s=0.15)

Анализ

На основе трех визуализаций выше, кажется, что существует четкая закономерность в распределении простых чисел. Это какое-то новое открытие, достойное полевой медали?

Ну, вы уже знаете ответ на этот вопрос, который, очевидно, отрицательный. Но я все равно думаю, что это интересные явления, в которые стоит погрузиться.

Сначала давайте посмотрим, что произойдет, если мы просто отобразим на графике все положительные целые числа в определенном диапазоне, скажем, до 1000.

nums = range(1000) create_plot(nums)


Возникает похожая спиральная форма, но она более равномерно распределена и симметрична по сравнению с визуализациями, которые мы видели выше. Эти спиральные формы становятся больше и полнее по мере того, как мы наносим больше чисел.

nums = range(10000) create_plot(nums, s=1)

Итак, здесь ясно, что сами спирали не имеют ничего общего с простыми числами; гораздо более четкую и полную картину можно увидеть, если мы нанесем на график все положительные целые числа (а также ноль). Однако, откуда берутся эти спиральные узоры, пока не дано ответа. Откуда берутся эти узоры?

Оказывается, эти закономерности на самом деле связаны с рациональным приближением2π​2π!

Если вы тщательно подсчитаете общее количество спиральных узоров (я сам этого не делал, потому что мои глаза начали жечь после первых 15), вы увидите, что всего 44 спиральных линии или ответвления. И 44 — это не единственное число: в самом ядре спирали, для небольших чисел, на самом деле всего 6 рукавов. Поэтому естественным вопросом для начала будет: откуда берутся все эти числа?

Геометрическая интерпретация

Мы можем подойти к этому вопросу геометрически, рассмотрев импликацию одного спирального рукава. Если подумать, то то, что мы воспринимаем как спиральные рукава, получается, когда ряд точек наносятся в некотором периодическом порядке вдоль дуги растущей окружности. Говоря более прямо, мы знаем, что координата каждого числа принимает форму

( r , θ ) = ( x , x )(г,θ)=(х,х)

гдеггиθθ— радиус и угол соответственно.

Это означает, что каждая соседняя точка будет находиться на расстоянии ровно одного радиана друг от друга. Это становится очевидным, когда мы рассматриваем координатыннй и( н − 1 )(н−1)й баллы:( н , н )(н,н)и( п − 1 , п − 1 )(н−1,н−1). Обратите внимание, что второй компонент угла смещен ровно на один радиан.

Учитывая это наблюдение, давайте подробнее рассмотрим происходящее.

nums = np.array(range(20)) x, y = get_coordinate(nums) fig, ax = plt.subplots(figsize=(8, 8)) ax.scatter(x, y) for num in nums: ax.annotate(num, (x[num], y[num])) ax.axis("off") plt.show()

При более внимательном рассмотрении мы видим, что каждый рукав спирали образует арифметическую последовательность. А именно,

Ст=Ст − 1+ 6Ст=Ст−1+6

Например, то, что мы могли бы назвать первой рукой, представляет собой последовательность, компонентами которой являются 1, 7, 13, 19 и так далее.

Теперь мы знаем, почему всего 6 плеч: это потому, что начальный элемент каждого плеча идет от 1 до 6 (или от 0 до 5, в зависимости от того, с чего вы начинаете — последовательность, которая начинается с 0, будет иметь 6 в качестве своего следующего элемента).

Подключение к Full Circle

Это наблюдение дает важное представление об углах и2π​2π: для завершения полного цикла этой модели требуется шесть чисел. То, что мы воспринимаем как спиральную модель, возникает из-за того, что 6 радиан близки, но не равны в 100 раз2π​2π, полный круг. Если 6 радиан равнялись2π​2π, то мы бы не увидели спиралей; вместо этого мы увидели бы прямые линии, почти как луч света, исходящий из начала координат. Но поскольку это не так, мы видим небольшие искривления с каждым циклом из 6, и, следовательно, возникает спиральный узор.

def rad2deg(radian): return 180 * radian / math.pi

И мы видим, что 6 радиан действительно довольно близки к 360, или2π​2π.

rad2deg(6)

343.77467707849394

А как насчет 44 спиральных рукавов, которые мы увидели, когда немного уменьшили изображение? Используя ту же самую логику, мы

print(rad2deg(44) // 360) print(rad2deg(44) % 360)


7.0 1.0142985756224334

Другими словами, 44 радиана почти равны 7 полным кругам, или14π​14π. На самом деле, это даже более точно, чем 6 радиан, так как 44 радиана отличаются только от кратности2π​2πпримерно на 1,01 градуса. Это наблюдение часто чаще записывается в виде

44/7≈2π

Теперь, возможно, мы можем экстраполировать то, что происходит в последнем прямолинейном паттерне, который мы видели. Учитывая, что есть некоторый паттерн, мы снова можем предположить, что должно быть некоторое значение, которое очень близко приближается к кратному2π​2π. На самом деле, поскольку это выглядит почти как прямая линия, мы могли бы ожидать, что это приближение будет даже лучше тех, которые мы получили выше.

И действительно, оказывается, что 710 радиан чрезвычайно близки к числу, кратному2π​2π.

rad2deg(710) / 360

113.00000959524569

Фактически, мы видим здесь, что 710 радиан почти равны 113 полным оборотам. Это иногда используется как рациональное приближение для числа Пи.

355/113≈π

Итак, теперь мы решили первую часть головоломки: откуда взялись эти спиральные или прямолинейные узоры. Теперь следующий вопрос, который стоит изучить, касается того, почему мы видим более разреженную визуализацию, когда рисуем только простые числа, и есть ли какое-либо математическое понимание, которое мы можем почерпнуть из узоров разреженности.

Как вы уже могли догадаться из арифметической последовательности, которую мы видели выше, существуют определенные последовательности, которые не могут иметь простых чисел. Например, в последовательностях из 6 следующая последовательность

С= 2 , 8 , 14 , … ,С=2,8,14,…,

не может быть простым числом, и, следовательно, исключается, когда мы только наносим простые числа. То же самое касается последовательности, которая начинается с 3, 4 и 6. В конце дня у нас остается только два таких спиральных рукава.

История ничем не отличается, когда мы переходим к более крупным спиральным узорам с периодом 44 или 710. Во всех таких случаях многие спиральные рукава не будут присутствовать на графике простых чисел из-за ограничения простоты. Точнее, только числа от 1 до 44, которые являются взаимно простыми с 44, выдержат фильтр. Вот почему мы видим гораздо более редкие спирали на рисунках выше.

Заключение

В этом посте мы рассмотрели интересную математическую задачу, связанную с простыми числами. Простые числа чрезвычайно интересны не только потому, что они фактически используются во многих областях прикладной математики и науки, но и из-за присущих им качеств. Я помню, как в прошлом году пытался вывести какое-то математическое решение для простых чисел Мерсенна (разумеется, безуспешно), и я надеюсь узнать о них больше в будущем. Хотя сами простые числа не являются сложными концепциями, они распространяются на многие увлекательные области математики, такие как теорема Дирихле о простых числах или гипотеза Римана. Я надеюсь узнать о них больше в будущем

Комментарии (12)


  1. Ktator
    16.12.2024 10:34

    Мы могли бы понимать эту функцию как преобразованиеС: Р →Р2С:Р→Р2такой что  

    С( х ) = ( х соз( х ) , х грех( х ) )

    Смех и грех!


    1. bogatrv
      16.12.2024 10:34

      я сам этого не делал, потому что мои глаза начали жечь после первых 15
      Это какое-то новое открытие, достойное полевой медали?

      Автор жжет, выдайте ему полевую медаль.


      1. GospodinKolhoznik
        16.12.2024 10:34

        В погоне за полевой медалью, главное не получить биполярную медаль.


  1. Markscheider
    16.12.2024 10:34

    В Python мы можем реализовать этот перевод следующим образом: 

    def get_coordinate(num): return num * np.cos(num), num * np.sin(num)

    Я не совсем понял: как вы переводите.
    Скажем, есть простое число 13. Вы его подаете на вход этой функции и она выдает пару значений: (13 * cos(13), 13 * sin(13)). Верно?


  1. zagart
    16.12.2024 10:34

    Добавьте ссылку на оригинал: https://jaketae.github.io/study/prime-spirals/


    1. Ktator
      16.12.2024 10:34

      Кстати, переводчик в браузере справился лучше! Он и форматирование кода оставил как было и оформление формул и, вроде, не сделал ляпов в тексте.


  1. Markscheider
    16.12.2024 10:34

    Значит, у меня к оригиналу вопросы :)
    Просто взять и в формулу преобразования декартовых координат в полярные ВСЮДУ подставить значение простого числа? И вместо ϕ и вместо ρ одни и те же. Ну, так в любом случае получится какой-то упорядоченный узор, имхо.
    Как по мне - то надо было брать x и y (пусть одинаковые и равные простому числу) и по формуле их преобразовывать в полярные координаты. Но тогда чуда не произойдет: все точки будут находиться на одном луче, идущем под 45 градусов

    ЗЫ И еще у меня смутные сомнения - надо ли скармливать np.cos и np.sin значения в градусах или в радианах.

    Одним словом это исследование в стиле: "я привязал веревочку к палке, вбитой в землю, а на другой конец - острый колышек. И, о чудо, он нарисовал правильную окружность вокруг палки!"


  1. CrazyElf
    16.12.2024 10:34

    Исправьте формулы, это же невозможно читать, все формулы убиты до неузнаваемости.
    А также укажите, что это перевод и дайте ссылку на источник.


  1. diffdev
    16.12.2024 10:34

    Переведено и озвучено профессиональными программистами

    Судя по тому как написана математика - перевод выполнен ИИ-моделью GIGACHAT. Делайте выводы :)



  1. Akina
    16.12.2024 10:34

    В Python мы можем реализовать этот перевод следующим образом:

    def get_coordinate(num):

    return num * np.cos(num), num * np.sin(num)

    Не, я чего-то не понимаю... как мне кажется, для непрерывного аргумента это дело даст тупо спираль. Для целочисленных - отдельные точки на этой спирали. И удивляться, что отдельные точки спирали расположены по спирали - как-то странно.

    А ещё странно - спираль как бы должна быть одна, откуда же там взялось несколько спиралей?


    1. Markscheider
      16.12.2024 10:34

      вот тоже это зацепило (писал выше)
      вообще, глянув первоисточник, толком не понял - это гитхаб студента, на котором он публикует исследовательские работы? так себе инфоповод :)