Фракталы, порожденные zeta-функцией

В своей последней статье я попытался создать фрактал, порожденный простыми числами. Но он меня не очень устроил эстетически. Поэтому я решил воспользоваться zeta функцией Римана для создания фракталов.

Самый популярный фрактал, фрактал Мандельброта, создается итерацией функции:

X_{n+1} = X_n^2 + c

где X — комплексное число, а с — значение, которое для каждого пикселя берется согласно позиции этого пикселя. Если итерация уходит в бесконечность, то мы рисуем цвет согласно числу итераций до того, как функция превысила некоторое значение, а если такого не происходит, то мы рисуем пиксель черным. В качестве начального значения. X0. используется 0

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

Первый эксперимент был с формулой

X_{n+1} = \frac{1}{Zeta(X_n)}

Особенно интересно поведение итераций вблизи нетривиальных корней, например первого (real = 0.5, img = 14.13…):

2c043803aa20d1ab2bbb2c58e6a25e19.png

Желтая стрелка показывает, где находится первый корень. Цвета соответствуют числу итераций до abs (Z)>100. Сделаем zoom:

cb57912dba3a5ff34c8dc02c4d3005b4.png

И еще:

0b0cc7ba0dfdcbf798ec7f78eb8a3ea6.png

И еще, мы сдвинулись вбок и корень уже ушел из поля зрения:

5e18f792839c6c3b1d9f87112210821d.png

Мне кажется, этот фрактал можно назвать «бабочки Римана»?

Лирическое отступление о Julia

Я продолжал эксперименты с другими формулами, но процесс генерации миллиона точек (1000×1000) на Python занимал иногда до одного дня. Дело не только в медленности самого Python — подходящий пакет с zeta я нашел только в mpmath — вычисления с произвольной точностью. Это замечательный пакет, но очень медленный.

Я решил перескочить на другой язык, и мое внимание привлек язык Julia. Быстрый и полностью компилируемый, он позволяет вольготно обращаться с типами, почти как Python. Кроме того, он очень хорошо организует multi-threading. Например, перед циклом расчета фрактала, где все точки вычисляются независимо, достаточно написать

Threads.@threads for j in 0:iry
  for i in 0:irx
    ...

и все. Вот она, кнопка «сделать все зашибись». (да, да, не все так просто). В итоге то, что на Python занимало день, считалось несколько секунд. Я вышел на новый уровень и вот некоторые новые вычисления снова уперлись в продолжительность ожидания)

Я перебрал очень много вариантов, некоторые не давали интересных фракталов, зато итерация точки создавала интересные аттракторы:

e60c6cc00f303ae4c5b0a1dcc653ec6d.png

Нетривиальные нули zeta

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

X_{n+1} = X_n + exp(\frac{1}{Zeta(X_n)})

Сам по себе фрактал не был очень красивым, но у одного из его элементов была такая картина:

60deb227fe9ce850bc52e797d69745db.png

Как вы видите, расстояние между ножками соответствует расстоянию между корнями. Если увеличить острие хвостика второго корня:

ab59b826b5d45a08440db416f9ab1924.png

то мы увидим ту же картину в более мелком масштабе.

Видео!

Но самой красивой оказалась формула

X_{n+1} = X_n + \frac{i}{Zeta(X_n)}

(условие выхода: Re (Xn)<-1.0)

284933daafecb7b8e30f5c9e528107e3.png

Цвета соответствуют ближайшему номеру корня, у которого остановился расчет.

Сделаем zoom:

6209a71e8584c1ebf469af52eff94682.png

Можно заметить, что дерево 'растет' за счет все бОльших и бОльших корней. Это можно показать на видео:

https://www.youtube.com/watch? v=kTQHnw7ORqc

© Habrahabr.ru