Привет, Хабр! Сегодняшний пост про фракталы попался в рамках проработки темы Python, в частности, Matplotlib. Последуем примеру автора и предупредим, что в посте много тяжелой анимации, которая может даже не работать на мобильном устройстве. Зато как красиво.
Всем приятного чтения
Фракталы прекрасны. Они выстраиваются в соответствии с очень сложным паттерном и сохраняются без искажения при любом увеличении! В этой статье мы рассмотрим, как можно с легкостью начертить фракталы нескольких видов, воспользовавшись инструментом под названием L-Systems и модулем Turtle для Python, чтобы выполнить пошаговое вычерчивание.
В этой статье мы не будем чрезмерно вдаваться в технические детали; вместо этого я ограничусь кратким введением, покажу много анимированных примеров и код, при помощи которого вы сможете сами сгенерировать такой пример. Если желаете пропустить теорию и только посмотреть анимацию, сразу переходите к примерам. Кроме того, в конце я укажу несколько ресурсов, как по написанию кода, так и по базовой математике, которые вы, возможно, захотите исследовать.
Что такое фрактал?
Для начала давайте дадим «нестрогое» определение фрактала. В принципе, фрактал — это геометрическая фигура, демонстрирующая одни и те же свойства независимо от степени увеличения.
Это определение небезупречно, поэтому вот более точное с сайта Math World:
Фрактал – это объект или величина, демонстрирующие самоподобие (в формальном смысле) в любых масштабах. Объект демонстрирует при разных масштабах не идентичные структуры, но на всех уровнях фрактала должны проявляться структуры одного и того же «типа». В таком случае график, откладываемый в системе координат с логарифмическим масштабом, где по осям отсчитываются величина и масштаб, то график представляет собой прямую линию с наклоном, отражающим размерность фрактала. — Math World
Как чертить фракталы при помощи Python?
Как правило, отрисовка фракталов сложна, так как глубинная природа фракталов определяется концепцией рекурсии. Говоря о графиках и их вычерчивании, мы обычно считаем, что они образованы пикселями или векторами, но количество пикселей или векторов всегда ограничено, а фракталы по определению бесконечно рекурсивны. Таким образом, попытавшись нанести фрактал на координатную сетку, мы в какой-то момент должны будем остановиться, и именно поэтому мы в данном случае говорим об «итерациях». На каждой итерации фрактал становится все сложнее, и в какой-то момент становится невозможно отличить две его итерации, следующие друг за другом (такой момент наступает, когда изменения происходят на уровне, сравнимом с размером пикселя). Здесь логично остановиться, но, как правило, форма фрактала вырисовывается быстрее, и остановиться можно еще раньше.
Два подобных примера – квадратный остров Коха, чья структура четко вырисовывается после трех итераций, и дракон Картера-Хейтуэя, для построения полной структуры которого достаточно 8 итераций. Необходимое количество итераций сильно зависит от конкретного фрактала, с которым мы работаем.
Разумеется, существует множество библиотек на Python для построения графиков, среди которых наиболее популярна Matplotlib, но они обычно рассчитаны на нанесение статистических данных и вычерчивание хорошо известных графиков. В частности, Matplotlib содержит некоторые низкоуровневые конструкции, при помощи которых можно строить фракталы, но на этот раз мы сосредоточимся на незаслуженно малоизвестном модуле стандартной библиотеки, который называется Turtle.
Модуль Turtle
В документации Python читаем: «графика Turtle – популярный инструмент для первого знакомства детей с программированием. Он входил в состав оригинального языка программирования Logo, разработанного Уолли Фёрзегом и Сеймуром Пейпертом в 1966 году.»
Суть заключается в том, что черепаха по умолчанию распознает 3 команды:
- Ползти вперед
- Повернуть влево на угол
- Повернуть вправо на угол
Примечание: в стандартной библиотеке предоставляются и другие команды, но здесь мы будем пользоваться только этими тремя.
Также мы можем:
- Отключить запись
- Включить запись
Эти характеристики кажутся слишком простыми, чтобы, опираясь только на них, вычертить такой сложный график как фрактал, но мы воспользуемся другим инструментом, который использует только этот небольшой набор инструкций. Я говорю о L-системах.
L-системы
L-система – это способ представления рекурсивных структур (например, фракталов) в виде строки символов и многократной перезаписи такой строки. Опять же, дадим формальное определение:
Система Линденмайера, также известная как L-система, это механизм перезаписи строк, который может использоваться для генерации фракталов с размерностью от 1 до 2 — Math World
Поняв, что такое L-система, мы сможем создавать рекурсивные структуры, но прежде давайте разберемся, какие компоненты нам для этого понадобятся. В каждой L-системе есть:
- Алфавит: множество символов, которые будет использовать L-система.
- Аксиома: исходная строка для генерации.
- Набор инструкций создания строк: эти инструкции описывают, как каждый символ должен заменяться на следующей итерации.
Примечание для фанатов Computer Science: если вы глубоко изучали Computer Science, то все вышесказанное может о чем-то вам напоминать. Действительно, очень схожим образом определяются формальные грамматики; ключевое же отличие состоит в том, что, в отличие от грамматик, здесь на каждой итерации применяется столько правил, сколько возможно, а не всего одно. Поэтому L-системы являются подмножеством контекстно-свободных грамматик.
Учитывая, что мы собираемся использовать Turtle для построения графиков и L-системы для представления того, что собираемся наносить на график, нам необходимо создать взаимосвязь между ними.
Поскольку в Turtle мы располагаем только теми командами, что перечислены выше, присвоим каждой из них символ; из этих символов и будет состоять алфавит.
- F: ползти вперед
- +: повернуть вправо
- -: повернуть влево
Чтобы это заработало, для каждого фрактала должен предоставляться угол; это и будет угол, на который черепаха будет поворачивать вправо или влево. Для простоты условимся, что предоставлен должен быть только один угол, и мы будем писать L-систему, держа это в уме.
Аксиома и инструкции создания строк будут зависеть только от фрактала, но фрактал должен быть написан таким образом, чтобы его можно было представить только этими тремя символами. Так возникает ограничение, в силу которого мы сможем строить только однострочные фракталы, то есть, нечто наподобие множества Кантора таким образом получить невозможно. Но это всего лишь упрощение, ведь мы всегда можем ввести две другие команды для движения вперед без записи, и то же самое для движения назад.
Теперь давайте перейдем к примерам!
Анимированные примеры
Следующие примеры были взяты в Интернете из нескольких общедоступных источников, и я решил портировать их на Python при помощи модуля Turtle, центрировать их, раскрасить и предоставить способ экспорта в векторный формат.
ВНИМАНИЕ: предлагаемые анимации достаточно велики, рекомендуется смотреть их лишь с хорошим интернетом. Код Repl может не работать, так как потребляет ваши ресурсы, и с отображением фракталов на мобильных устройствах возможны проблемы.
Внимание: Skulpt использует для рендеринга и создания анимации ВАШ БРАУЗЕР, так что при подвисании, лагах или любом странном поведении обычно достаточно просто заново воспроизвести анимацию или перезагрузить страницу. На мобильном устройстве может не работать.
Примеры даны в порядке усложнения (на мой субъективный взгляд), так что самое интересное – в конце.
Снежинка Коха
axiom = "F--F--F"
rules = {"F":"F+F--F+F"}
iterations = 4 # TOP: 7
angle = 60
Квадратный остров Коха
axiom = "F+F+F+F"
rules = {"F":"F-F+F+FFF-F-F+F"}
iterations = 2 # TOP: 4
angle = 90
Кристалл
axiom = "F+F+F+F"
rules = {"F":"FF+F++F+F"}
iterations = 3 # TOP: 6
angle = 90
Квадратная снежинка
axiom = "F--F"
rules = {"F":"F-F+F+F-F"}
iterations = 4 # TOP: 6
angle = 90
Фрактал Вичека
axiom = "F-F-F-F"
rules = {"F":"F-F+F+F-F"}
iterations = 4 # TOP: 6
angle = 90
Кривая Леви
axiom = "F"
rules = {"F":"+F--F+"}
iterations = 10 # TOP: 16
angle = 45
Ковер Серпинского
axiom = "YF"
rules = {"X":"YF+XF+Y", "Y":"XF-YF-X"}
iterations = 1 # TOP: 10
angle = 60
Решетка Серпинского
axiom = "FXF--FF--FF"
rules = {"F":"FF", "X":"--FXF++FXF++FXF--"}
iterations = 7 # TOP: 8
angle = 60
Квадрат
axiom = "F+F+F+F"
rules = {"F":"FF+F+F+F+FF"}
iterations = 3 # TOP: 5
angle = 90
Плитки
axiom = "F+F+F+F"
rules = {"F":"FF+F-F+F+FF"}
iterations = 3 # TOP: 4
angle = 90
Кольца
axiom = "F+F+F+F"
rules = {"F":"FF+F+F+F+F+F-F"}
iterations = 2 # TOP: 4
angle = 90
Крест-2
axiom = "F+F+F+F"
rules = {"F":"F+F-F+F+F"}
iterations = 3 # TOP: 6
angle = 90
Pentaplexity
axiom = "F++F++F++F++F"
rules = {"F":"F++F++F+++++F-F++F"}
iterations = 1 # TOP: 5
angle = 36
32-сегментная кривая
axiom = "F+F+F+F"
rules = {"F":"-F+F-F-F+F+FF-F+F+FF+F-F-FF+FF-FF+F+F-FF-F-F+FF-F-F+F+F-F+"}
iterations = 3 # TOP: 3
angle = 90
Кривая Пеано-Госпера
axiom = "FX"
rules = {"X":"X+YF++YF-FX--FXFX-YF+", "Y":"-FX+YFYF++YF+FX--FX-Y"}
iterations = 4 # TOP: 6
angle = 60
Кривая Серпинского
axiom = "F+XF+F+XF"
rules = {"X":"XF-F+F-XF+F+XF-F+F-X"}
iterations = 4 # TOP: 8
angle = 90
Анклеты Кришны
axiom = " -X--X"
rules = {"X":"XFX--XFX"}
iterations = 3 # TOP: 9
angle = 45
Квадратный фрактал Госпера
axiom = "YF"
rules = {"X": "XFX-YF-YF+FX+FX-YF-YFFX+YF+FXFXYF-FX+YF+FXFX+YF-FXYF-YF-FX+FX+YFYF-",
"Y": "+FXFX-YF-YF+FX+FXYF+FX-YFYF-FX-YF+FXYFYF-FX-YFFX+FX+YF-YF-FX+FX+YFY"}
iterations = 2 # TOP: 3
angle = 90
Кривая Мура
axiom = "LFL-F-LFL"
rules = {"L":"+RF-LFL-FR+", "R":"-LF+RFR+FL-"}
iterations = 0 # TOP: 8
angle = 90
Кривая Гильберта
axiom = "L"
rules = {"L":"+RF-LFL-FR+", "R":"-LF+RFR+FL-"}
iterations = 8 # TOP: 9
angle = 90
Кривая Гильберта-II
axiom = "X"
rules = {"X":"XFYFX+F+YFXFY-F-XFYFX", "Y":"YFXFY-F-XFYFX+F+YFXFY"}
iterations = 4 # TOP: 6
angle = 90
Кривая Пеано
axiom = "F"
rules = {"F":"F+F-F-F-F+F+F+F-F"}
iterations = 2 # TOP: 5
angle = 90
Крест
axiom = "F+F+F+F"
rules = {"F":"F+FF++F+F"}
iterations = 3 # TOP: 6
angle = 90
Треугольник
axiom = "F+F+F"
rules = {"F":"F-F+F"}
iterations = 2 # TOP: 9
angle = 120
Кривая дракона
axiom = "FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 8 # TOP: 16
angle = 90
Кривая Terdragon
axiom = "F"
rules = {"F":"F-F+F"}
iterations = 5 # TOP: 10
angle = 120
Двойная кривая дракона
axiom = "FX+FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 6 # TOP: 16
angle = 90
Тройная кривая дракона
axiom = "FX+FX+FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 7 # TOP: 15
angle = 90
Код
Все вышеприведенные примеры были получены при помощи одного и того же кода, и при работе над ними возникли некоторые сложности (например, как удержать фрактал в центре, насколько это возможно), работа с цветом, инверсией, смещениями, а также обеспечение быстрого экспорта в векторный формат. Здесь я демонстрирую вам лишь простейшую версию.
Данная версия выводит фракталы в черно-белом виде и не оснащена функционалом для экспорта
import turtle
def create_l_system(iters, axiom, rules):
start_string = axiom
if iters == 0:
return axiom
end_string = ""
for _ in range(iters):
end_string = "".join(rules[i] if i in rules else i for i in start_string)
start_string = end_string
return end_string
def draw_l_system(t, instructions, angle, distance):
for cmd in instructions:
if cmd == 'F':
t.forward(distance)
elif cmd == '+':
t.right(angle)
elif cmd == '-':
t.left(angle)
def main(iterations, axiom, rules, angle, length=8, size=2, y_offset=0,
x_offset=0, offset_angle=0, width=450, height=450):
inst = create_l_system(iterations, axiom, rules)
t = turtle.Turtle()
wn = turtle.Screen()
wn.setup(width, height)
t.up()
t.backward(-x_offset)
t.left(90)
t.backward(-y_offset)
t.left(offset_angle)
t.down()
t.speed(0)
t.pensize(size)
draw_l_system(t, inst, angle, length)
t.hideturtle()
wn.exitonclick()
Пояснение кода
import turtle
Сначала нужно импортировать модуль Turtle
def create_l_system(iters, axiom, rules):
start_string = axiom
if iters == 0:
return axiom
end_string = ""
for _ in range(iters):
end_string = "".join(rules[i] if i in rules else i for i in start_string)
start_string = end_string
return end_string
Затем потребуется сгенерировать L-систему, которая будет представлять собой набор инструкций для черепахи. Определяем функцию create_l_system
, которая получает количество итераций, аксиому и правила построения. Она начинает работу с аксиомы и использует вспомогательную переменную end_string
, если итерация равна 0, то она вернет аксиому, поскольку некоторые фракталы можно наносить и нулевыми итерациями. В данном случае предполагается, что правила имеют вид словарей, поэтому каждый ключ уникален, представляет собой символ, а значение указывает, что и чем нужно заменить. Так мы объединяем все замены для каждого символа и в итоге получаем строку для следующей итерации.
def draw_l_system(t, instructions, angle, distance):
for cmd in instructions:
if cmd == 'F':
t.forward(distance)
elif cmd == '+':
t.right(angle)
elif cmd == '-':
t.left(angle)
Затем определяем draw_l_system
, которая принимает черепаху, набор инструкций (вывод L-системы), угол для поворота влево или вправо и длину каждой отдельной линии. Она состоит из простой структуры elif
для каждой из ранее определенных команд.
def main(iterations, axiom, rules, angle, length=8, size=2, y_offset=0,
x_offset=0, offset_angle=0, width=450, height=450):
inst = create_l_system(iterations, axiom, rules)
t = turtle.Turtle()
wn = turtle.Screen()
wn.setup(width, height)
t.up()
t.backward(-x_offset)
t.left(90)
t.backward(-y_offset)
t.left(offset_angle)
t.down()
t.speed(0)
t.pensize(size)
draw_l_system(t, inst, angle, length)
t.hideturtle()
wn.exitonclick()
Наконец, поговорим о функции main
, которая принимает все параметры, необходимые для генерации L-систем, а также y_offset
, x_offset
, offset_angle
, width
и height
. Три первых описывают смещение черепахи, это необходимо просто для позиционирования графика на холсте так, как нам того хочется.
Функция сначала генерирует набор инструкций и сохраняет их в inst, затем инициализирует черепаху и экран и ставит черепаху в определенную точку, затем наносит график в соответствии с инструкциями и ожидает клика, чтобы закрыться.
Особые соображения
Как я упоминал выше, здесь оставлено множество ограничений. Во-первых, мы не предусмотрели для черепахи возможность двигаться без отрисовки; для этого потребовался бы еще один символ. Нет здесь и символа для отступления назад, и для запоминания предыдущих позиций. Они не требовались для всех фракталов, рассмотренных выше, но обязательны для некоторых других (например, фрактальных деревьев).
Дополнительные ресурсы
В Интернете множество ресурсов по фракталам, где они рассматриваются как с точки зрения программирования, так и с точки зрения математики. Следующие два показались мне особенно интересными: 3Blue1Brown (математика) и CodingTrain (код).
Статья написана под впечатлением поста с Math World и статьи Пола Бурка.
Создание фракталов проходит в два этапа:
- создание L-системой (что это мы подробно разберём) строки, состоящей из символов, которая будет представлять для нас подобие набора команд;
- чтение символов строки и выполнение каждого символа как конкретной команды (делать мы это будем при помощи исполнителя черепашки(turtle)).
Итак, начнём.
L-система
Давайте создадим самую простейшую L-систему – водоросли:
- Для этого возьмём аксиому (строку с которой мы начнём) A. Поместим её в функцию (назовём её seaweed()), которая и будет нашей L-системой (например seaweed(‘A‘)).
- Что же будет делать наша функция? Всё очень просто. Она заданное количество раз (пусть будет n) пробегается по нашей строке и заменяет (A → AB), (B → A). Например:
n = 0 : A
n = 1 : AB
n = 2 : ABA
n = 3 : ABAAB
n = 4 : ABAABABA
n = 5 : ABAABABAABAAB
n = 6 : ABAABABAABAABABAABABA
n = 7 : ABAABABAABAABABAABABAABAABABAABAAB
Тадам! Проще говоря у нас есть кусочек строки, который меняется на кусочек побольше, в нём происходят те же самые действия. На этом и основывается принцип самоподобия фракталов. Как же провернуть всё это в нашей функции? Я думаю, никто не будет спорить, что это рекурсия.
Итак, n – это ограничение нашей рекурсии поэтому в самом начале зададим условия выхода. Наша рекурсия выполнится 6 раз т.к. 7 раз ей не даст выполниться выход из рекурсии (в котором мы возвращаем готовую строку).
Что мы видим дальше. newlevel – это строка которая в цикле преобразуется в строку, полученную заменой по нашему условию ((A → AB), (B → A)) символов старой.
А затем просто присвоим старой строке новое значение и пойдём рекурсией дальше на новый уровень. В конце концов программа напечатает: ABAABABAABAABABAABABA.
Единственное, мы не сможем её красиво визуализировать, т.к. она состоит всего из двух букв и после визуализации будет выглядеть довольно скучно. Но на ней мы поняли принцип L-систем. Кстати, по ссылке можно найти ещё несколько классных L-систем, которые уже можно преобразовать во фракталы.
Возьмём, например, фрактал кривая Дракона. Посмотрим его условия:
переменные : X, Y
константы : F, +, −
старт : FX
правила : (X → X+YF+), (Y → −FX−Y)
угол : 90°
Здесь F означает «рисуем отрезок», − означает «повернуть влево на 90°», а + означает «повернуть вправо на 90°». X и Y не соответствуют какому-либо действию при рисовании, а используются только для построения кривой.
Константы − это символы, которые при создании в рекурсии новой строки, мы просто переписываем неизменными.
Итак, создадим L-систему:
Действовали, по одному и тому же принципу. Ну, что приступим к исполнению!
Исполнение
Для начала импортируем модуль исполнитель черепашка. Он не встроен поэтому предварительно его необходимо загрузить с командной строки.
import turtle as tl
И произведем необходимые нам настройки исполнителя.
Скорость на максимум, иначе придётся долго ждать. Оптимальное расположение начала отрисовки фрактала, я выбрал уже в конце так, чтобы наш фрактал после отображения не заходил за края экрана.
И конечно, само исполнение. Его мы осуществляем по данному условию:
Здесь F означает «рисуем отрезок», − означает «повернуть влево на 90°», а + означает «повернуть вправо на 90°». X и Y не соответствуют какому-либо действию при рисовании, а используются только для построения кривой.
Команды turtle вы можете посмотреть по этой ссылке.
Ну, что взглянем на общий код:
Запустим его!
Если у вас получилось, то вы создали ваш первый фрактал. Попробуйте создать другие фракталы сами. Например треугольник Серпинского, созданный по одному и тому же принципу:
Надеюсь статья была интересной и полезной. Спасибо, что дочитали до конца.
Вступление
Фразу “Я никогда не видел ничего прекраснее” следует использовать только для фракталов. Конечно, есть “Мона Лиза”, “Звёздная ночь” и “Рождение Венеры”, но я не думаю, что какой-либо художник или человек смог бы создать что-то по-королевски удивительное в виде фракталов.
Слева у нас есть культовый фрактал, множество Мандельброта, обнаруженный в 1979 году, когда не было ни Python, ни программного обеспечения для построения графиков.
Множество Мандельброта – это набор комплексных чисел, которые, при нанесении на комплексную плоскость, образуют самоповторяющуюся фигуру, которую мы видим. Каждое число в наборе также может быть начальным для множеств Жюлиа, и вы можете видеть красоты, появляющиеся, когда я перемещаю курсор мыши внутри границы множества Мандельброта.
Прежде чем мы сможем построить множества Мандельброта или Жюлиа (но, поверьте мне, мы это сделаем), нам предстоит проделать большую работу. Если вы здесь просто для того, чтобы посмотреть классные картинки, я настоятельно рекомендую скачать программное обеспечение Fraqtive с открытым исходным кодом (и сходить с ума!), которое я использовал для создания приведённых в статье GIF:
Если вы просто хотите отобразить Множество Мандельброта на Python с помощью одной строки кода, вот она (нет, подзаголовок не был кликбейтом):
from PIL import Image
Image.effect_mandelbrot((512, 512), (-3, -2.5, 2, 2.5), 100).show()
Но если вы хотите проникнуть в прекрасную кроличью нору фракталов, научиться их отображать и, самое главное, соответствующим образом раскрашивать, тогда продолжайте чтение данной статьи!
В этой статье вы узнаете, как построить базовые (но очень красивые) множества Мандельброта, используя Matplotlib и NumPy.
Давайте начинать!
Комплексные числа в Python
Программисты на Python не каждый день имеют дела со сложными числами. Поскольку в этом руководстве мы будем много работать с ними, этот раздел послужит в качестве основы.
Вы можете создать мнимую часть комплексного числа, добавив литерал j к целым числам или числам с плавающей точкой:
num1 = 2 + 1j
num2 = 12.3 + 23.1j
type(num1)
complex
Если вас смущает вид мнимых чисел, представленных буквой j вместо i (привет, математики), вы можете воспользоваться встроенной функцией complex:
2 + 3j == complex(2, 3)
True
После создания вы можете получить доступ к действительным и мнимым компонентам комплексных чисел с атрибутами real и imag:
num1.real
2.0
num2.imag
23.1
Другим важным свойством комплексных чисел для целей этой статьи является их абсолютное значение. Абсолютное значение или величина комплексного числа измеряет его расстояние от начала координат (0, 0) в комплексной плоскости. Оно определяется как квадратный корень из суммы его действительной и мнимой частей (спасибо тебе, Пифагор).
abs(1 + 3.14j)
3.295390720385065
Этого нам будет достаточно, чтобы создать несколько потрясающих вещей. Давайте продолжим!
Простая формула, грандиозное Множество
Наше путешествие начинается с выяснения, принадлежит ли некоторое комплексное число c множеству Мандельброта, что на удивление просто реализовать. Всё, что нам нужно сделать, это использовать приведённую ниже формулу и создать последовательность значений z:
Первый z всегда равен 0, как определено выше. Последующие элементы находятся путем возведения в квадрат предыдущего z и добавления c к результату.
Давайте реализуем этот процесс на Python. Мы определим функцию sequence, которая возвращает первые n элементов для данного c:
def sequence(c, n=7) -> list:
z_list = list()
z = 0
for _ in range(n):
z = z ** 2 + c
z_list.append(z)
return z_list
Теперь мы возьмём функцию на тест-драйв для набора чисел:
import pandas as pd
df = pd.DataFrame()
df[‘element’] = [f”z_{i}” for i in range(7)]
# Random numbers
cs = [0, 1, -1, 2, 0.25, -.1]
for c in cs:
df[f”c={c}”] = sequence(c)
df
Мы видим три типа результатов: когда c равно либо 1, либо 2, последовательность неограниченна (расходится в бесконечность) по мере её роста. Когда оно равно -1, оно перемещается взад и вперед между 0 и -1. Что касается 0.25 и -0.1, они остаются маленькими или ограниченными.
Итак, кому из этих пятерых повезло войти в Множество Мандельброта?
Стабильность чисел
Наш процесс отбора очень прост — если c расходится в последовательности до бесконечности, то её нет в Множестве Мандельброта. На жаргоне фракталов этот c называется нестабильным. Или давайте отбросим отрицательность — данное комплексное число c стабильно, если соответствующая ему последовательность Z остаётся ограниченной.
Теперь мы должны выяснить, на сколько членов Z следует обратить внимание, прежде чем классифицировать их как стабильные или нестабильные. Это количество итераций найти неочевидно, поскольку формула чувствительна даже к мельчайшим изменениям в c.
Но, к счастью, люди изучали множества достаточно долго, чтобы знать, что все Множества Мандельброта остаются ограниченными в радиусе двух. Это означает, что мы можем выполнить несколько десятков итераций, и числа, которые остаются относительно небольшими или ниже 2, вероятно, находятся в Множестве Мандельброта.
Итак, давайте создадим новую функцию is_stable, используя эту логику, которая возвращает True, если число входит в Множество Мандельброта:
def is_stable(c, n_iterations=20):
z = 0
for _ in range(n_iterations):
z = z ** 2 + c
if abs(z) > 2:
return False
return True
В теле этой логической функции мы устанавливаем z равным 0 и запускаем её по алгоритму в цикле, управляемом n_iterations. На каждой итерации мы проверяем величину z, чтобы мы могли завершить цикл, если она превысит 2 на ранней стадии, и не тратить время на выполнение остальных итераций.
Последний оператор return выполняется только в том случае, если z меньше 2 после всех итераций. Давайте проверим несколько цифр:
is_stable(1)
False
is_stable(0.2)
True
is_stable(0.26)
True
is_stable(0.26, n_iterations=30)
False
Обратите внимание, как увеличение n_iterations до 30 изменяет стабильность 0.26. Как правило, значения, близкие к границе фракталов, требуют большего количества итераций для более точной классификации и создания более детализированных визуальных эффектов.
Как построить Множество Мандельброта в Matplotlib
Конечная цель данной статьи – создать Фрактальный рисунок в Matplotib (предупреждение о спойлере: мы создадим что-то ещё лучше!):
Изображение было создано путём раскрашивания всех чисел Мандельброта в чёрный цвет, а нестабильных элементов – в белый. В Matplotlib оттенки серого имеют 256 оттенков или находятся в диапазоне от 0 до 255, 0 – полностью белый, а 255 – черный, как смоль. Но вы можете нормализовать этот диапазон на 0 и 1 так, чтобы 0 было белым, а 1 – черным.
Эта нормализация пригодится нам. Мы можем создать 2D-массив комплексных чисел и запустить нашу функцию is_stable для каждого элемента. Результирующий массив будет содержать единицы для чисел Мандельброта и 0 для нестабильных. Когда мы выводим этот массив в виде изображения — вуаля, мы получаем желаемый чёрно-белый визуал.
Давайте перейдём к этому. Сначала мы создаем функцию, которая генерирует матрицу возможных значений, по которым мы будем выполнять итерации:
import numpy as np
def candidate_values(xmin, xmax, ymin, ymax, pixel_density):
# Generate a 2D grid of real and imaginary values
real = np.linspace(xmin, xmax, num=int((xmax-xmin) * pixel_density))
imag = np.linspace(ymin, ymax, num=int((ymax-ymin) * pixel_density))
# Cross each row of `xx` with each column of `yy` to create a grid of values
xx, yy = np.meshgrid(real, imag)
# Combine the real and imaginary parts into complex numbers
matrix = xx + 1j * yy
return matrix
Мы будем использовать функцию np.linspace для создания равномерно распределённых чисел в пределах диапазона. Параметр pixel_density динамически задаёт количество пикселей на единицу.
Например, матрица с горизонтальным диапазоном (-2, 0), вертикальным диапазоном (-1,2, 1,2) и pixel_density, равной 1, имела бы форму (2, 2). Это означает, что наше результирующее изображение Мандельброта было бы 2 пикселя в ширину и 2 пикселя в высоту, что заставило бы Бенуа Мандельброта перевернуться в могиле.
c = candidate_values(-2, 0, -1.2, 1.2, 1)
c.shape
(2, 2)
Итак, нам лучше использовать более высокую плотность, например 25:
c = candidate_values(-2, 0, -1.2, 1.2, 25)
c.shape
(60, 50)
Теперь, чтобы запустить нашу функцию is_stable для каждого элемента c, мы векторизуем её с помощью np.vectorize и вызываем её с 20 итерациями:
c = candidate_values(-2, 0.7, -1.2, 1.2, pixel_density=25)
mandelbrot_mask = np.vectorize(is_stable)(c, n_iterations=20)
mandelbrot_mask.shape
(60, 67)
Мы называем результирующий массив mandelbrot_mask, поскольку он возвращает True (1) для каждого числа Мандельброта. Чтобы отобразить этот массив, мы используем функцию imshow Matplpotlib с двоичной цветовой картой. Это сделает изображение чёрно-белым.
import matplotlib.pyplot as plt
plt.imshow(mandelbrot_mask, cmap=”binary”)
# Turn off the axes and use tight layout
plt.axis(“off”)
plt.tight_layout()
Что ж, получилось вот такое уродливое Фрактальное изображение. Как насчёт того, чтобы увеличить плотность пикселей до 1024 и количество итераций до 30?
c = candidate_values(-2, 0.7, -1.2, 1.2, pixel_density=1024)
mandelbrot_mask = np.vectorize(is_stable)(c, n_iterations=30)
plt.imshow(mandelbrot_mask, cmap=”binary”)
plt.gca().set_aspect(“equal”)
plt.axis(“off”)
plt.tight_layout()
Вот, это больше похоже на желаемый результат! Поздравляем с созданием вашего первого изображения Мандельброта!
Подождите, это ещё не всё!
Несмотря на то, что наш нынешний фрактал по-прежнему выглядит очень круто, он далёк от того искусства, которое я обещал.
Итак, давайте изменим его, сосредоточив внимание не только на цифрах чёрного набора, но и на цифрах по краю. Потому что, глядя на это изображение, мы можем видеть все типы интересных узоров, возникающих вокруг границ:
Давайте начнём переделку с организации нашего кода в класс, потому что нам не нужен беспорядок.
Имя класса будет Mandelbrot, и мы будем использовать классы данных, чтобы нам не пришлось создавать конструктор __init__, как пещерному человеку:
from dataclasses import dataclass
@dataclass
class Mandelbrot: # Inspired by the Real Python article shared above
n_iterations: int
def is_stable(self, c: complex) -> bool:
z = 0
for _ in range(self.n_iterations):
z = z ** 2 + c
if abs(z) > 2:
return False
return True
Классу требуется только инициализация параметра max_iteration. Мы также добавляем функцию is_stable в качестве метода класса:
<>
mandelbrot = Mandelbrot(n_iterations=30)
mandelbrot.is_stable(0.1)
True
mandelbrot.is_stable(1 + 4.4j)
False
До сих пор мы раскрашивали числа Мандельброта только в чёрный цвет, а остальные – в белый. Но если мы хотим оживить края набора, мы должны придумать логику, чтобы раскрасить нестабильные элементы в другой цвет.
Один из способов, которым мы можем это сделать, – определить, за сколько итераций комплексное число становится нестабильным. Некоторые из них очень быстро станут нестабильными, но другим могут потребоваться сотни или тысячи итераций. В целом, однако, числа, близкие к краю фрактала, менее нестабильны (требуют большего количества итераций), чем те, которые находятся далеко.
Используя эту информацию, мы можем придать каждому пикселю (комплексному числу) различную глубину цвета в зависимости от итерации, которую они завершают. Это называется алгоритмом подсчета побегов. Давайте реализуем это в нашем классе:
@dataclass
class Mandelbrot:
max_iterations: int
def escape_count(self, c: complex) -> int:
z = 0
for iteration in range(self.max_iterations):
z = z ** 2 + c
if abs(z) > 2:
return iteration
return self.max_iterations
Сначала мы меняем n_iterations на max_iterations, так как это имеет больше смысла. Затем мы создаём метод escape_count, который:
- если c нестабилен, возвращает итерацию, в которой он превышает величину 2
- если c стабилен, возвращает максимальное количество итераций
mandelbrot = Mandelbrot(max_iterations=50)
mandelbrot.escape_count(-0.1) # stable
50
mandelbrot.escape_count(0.26) # unstable
29
Теперь мы создаём другой метод для измерения стабильности на основе количества итераций:
@dataclass
class Mandelbrot:
max_iterations: int
def escape_count(self, c: complex) -> int:
z = 0
for i in range(self.max_iterations):
z = z ** 2 + c
if abs(z) > 2:
return i
return self.max_iterations
def stability(self, c: complex) -> float:
return self.escape_count(c) / self.max_iterations
Метод стабильности возвращает значение от 0 до 1, которое позже мы можем использовать для определения глубины цвета. Только числа Мандельброта вернут max_iterations, поэтому они будут отмечены 1. Числам, близким к краю, потребуется больше времени, чтобы стать нестабильными, поэтому они будут иметь значения, всё более близкие к 1.
С помощью этой логики мы можем вернуть нашу функцию is_stable, но сделать её намного короче:
@dataclass
class Mandelbrot:
max_iterations: int
def escape_count(self, c: complex) -> int:
z = 0
for i in range(self.max_iterations):
z = z ** 2 + c
if abs(z) > 2:
return i
return self.max_iterations
def stability(self, c: complex) -> float:
return self.escape_count(c) / self.max_iterations
def is_stable(self, c: complex) -> bool:
# Return True only when stability is 1
return self.stability(c) == 1
mandelbrot = Mandelbrot(max_iterations=50)
mandelbrot.stability(-.1)
1.0
mandelbrot.is_stable(-.1)
True
mandelbrot.stability(2)
0.02
mandelbrot.is_stable(2)
False
Теперь мы создаём окончательный метод для построения множества с помощью Matplotlib:
@dataclass
class Mandelbrot:
max_iterations: int
# … The rest of the code from above
@staticmethod
def candidate_values(xmin, xmax, ymin, ymax, pixel_density):
real = np.linspace(xmin, xmax, num=int((xmax-xmin) * pixel_density))
imag = np.linspace(ymin, ymax, num=int((ymax-ymin) * pixel_density))
xx, yy = np.meshgrid(real, imag)
matrix = xx + 1j * yy
return matrix
def plot(self, xmin, xmax, ymin, ymax, pixel_density=64, cmap=”gray_r”):
c = Mandelbrot.candidate_values(xmin, xmax, ymin, ymax, pixel_density)
# Apply `stability` over all elements of `c`
c = np.vectorize(self.stability)(c)
plt.imshow(c, cmap=cmap, extent=[0, 1, 0, 1])
plt.gca().set_aspect(“equal”)
plt.axis(‘off’)
plt.tight_layout()
В plot мы применяем метод stability ко всем элементам c, поэтому результирующая матрица сохраняет глубину цвета в каждой ячейке. Когда мы наносим эту матрицу на цветовую карту в перевёрнутых оттенках серого (так, чтобы числа Мандельброта оставались чёрными), мы получаем следующее изображение:
mandelbrot = Mandelbrot(max_iterations=30)
mandelbrot.plot(
xmin=-2, xmax=0.5,
ymin=-1.5, ymax=1.5,
pixel_density=1024,
)
Обратите внимание на то, что граничные линии являются самыми яркими и что белые пятна всё ещё появляются там, где множество повторяется. Классно!
Заключение
Наш конечный результат почти можно назвать искусством. Но есть много улучшений, которые мы можем сделать. Первое, что нужно сделать, – это улучшить разрешение изображения за счёт более точного контроля над каждым пикселем. Затем мы должны удалить это раздражающее пустое пространство вокруг изображения (если вы используете в тёмную тему).
Все эти задачи являются недостатками Matplotlib, но в следующей статье мы выведем работу на совершенно новый уровень с помощью Pillow, библиотеки для работы с изображениями на Python.
Статья была взята из этого источника:
Сейчас придумано большое число алгоритмов рисования фракталов. В интернете можно найти и скачать готовые программы. Но обладателю даже не очень мощного компьютера не составит большого труда нарисовать фрактал Жюлиа, Мандельброта, Галлея или Ньютона в достаточно хорошем качестве своими руками. Опишем в общих чертах процедуру рисования множества Жюлиа многочлена z2 + c для конкретного значения комплексного параметра c = p + iq.
Будем считать, что экран прямоугольный и состоит из a × b точек и что изображение будет покрашено в K + 1 цвет (то есть цвета пронумерованы от 0 до K, причем цвет номер 0 — черный, а для других цветов условимся, что чем больше номер цвета, тем быстрее «убегает на бесконечность» точка, которую мы покрасим в этот цвет). Еще необходимо выбрать область плоскости, которую выведем на экран (для начала подойдет квадрат {|Re z| ≤ 1,5, |Im z| ≤ 1,5}; его нужно разрезать на a × b прямоугольников, каждый из которых будет выступать в роли точки экрана), и радиус R круга D, точки снаружи которого будем считать «бесконечно далекими» (можно взять R = 10).
Для каждой точки z0 = (x0; y0) экрана (то есть центра соответствующего прямоугольника) нужно в цикле последовательно вычислять zk+1 по zk, используя формулу (в координатах это выглядит так: , yk+1 = 2xkyk + q). Признаком остановки цикла является выполнение одного из двух условий: либо на k-м шаге точка zk вышла из круга D (то есть верно неравенство , и тогда точку z0 нужно покрасить в цвет номер k, либо оказалось, что k = K + 1, тогда мы считаем, что точка z0 лежит внутри множества Жюлиа, и красим ее в черный.
В результате работы программы на экран будет выведена квадратная область комплексной плоскости {|Re z| ≤ 1,5, |Im z| ≤ 1,5}, на которой черным цветом будет изображено множество Жюлиа многочлена z2 + c для выбранного параметра c = p + iq, а остальные точки будут раскрашены в K цветов.
Увеличивая числа a и b, можно повышать разрешение экрана и тем самым улучшать качество изображения. Меняя K и подбирая соответствие между цветами и их номерами, можно добиться довольно красивых картинок.
Это — самая простая процедура построения множества Жюлиа. В программах, которые легко найти и скачать в интернете, используются более сложные алгоритмы рисования. Но в основе большинства из них лежит этот.
Далее: Фракталы в природе
1. Art Dabbler
Знакомство с основами фрактальной графики лучше всего начать с пакета Art Dabbler. Этот редактор (созданный фирмой Fractal Design, а теперь принадлежащий Corel) фактически представляет собой усеченный вариант программы Painter. Это отличная программа для обучения не только компьютерной графике, но прежде всего азам рисования. Малый объем требуемой памяти (для его установки необходимо всего 10 Мбайт), а также простой интерфейс, доступный даже ребенку, позволяют использовать его в школьной программе. Как и растровый редактор MS Paint, фрактальный редактор Art Dabbler особенно эффективен на начальном этапе освоения компьютерной графики.
2. Ultra Fractal
Ultra Fractal — программа, позволяющая создавать изображения фрактальных множеств, а также выполнять их анимацию. Процесс построения изображений определяется набором алгоритмов, описывающих разновидности визуализируемых фракталов, методы их раскраски и применяемые к ним трансформации.
Указанные алгоритмы представляются в виде текстовых файлов, написанных на специальном языке программирования, поддерживающем такие базовые конструкции как массивы, функции, циклы и классы. Такая модульная архитектура позволяет пользователю не только использовать кем-то уже созданные алгоритмы, но и создавать собственные. На сайте программы существуют разделы, предназначенные для помощи пользователям в обмене алгоритмами и получении ответов на интересующие их вопросы.
Уникальность создаваемых изображений обеспечивается благодаря наличию у алгоритмов набора параметров, с помощью которых можно управлять расположением фрактала, точностью расчётов, количеством итераций и многим другим. Одним из примеров такой настройки является изменение коэффициентов градиентной заливки, напрямую определяющее цветовую гамму создаваемого изображения.
3. Fractal Explorer
Fractal Explorer – программа для создания изображений фракталов и трехмерных аттракторов с достаточно впечатляющими возможностями. Имеет интуитивно понятный классический интерфейс, который может быть настроен в соответствии с пользовательскими предпочтениями, и поддерживает стандартные форматы фрактальных изображений (*.frp; *.frs; *.fri; *.fro; *.fr3, *.fr4 и др.). Готовые фрактальные изображения сохраняются в формате *.frs и могут быть экспортированы в один из растровых графических форматов (jpg, bmp, png и gif), а фрактальные анимации сохраняются как AVI-файлы.
Генерация фракталов возможна двумя способами – на основе базовых фрактальных изображений, построенных по входящим в поставку формулам, или с нуля. Первый вариант позволяет получить интересные результаты сравнительно просто, ведь выбрать подходящую формулу несложно, тем более что удобный файловый браузер позволит оценить качество фрактала из базы еще до создания на его основе фрактального изображения. У полученного таким путем фрактального изображения можно сменить цветовую палитру, добавить к нему фоновое изображение и определить режим смешивания фрактального и фонового слоев, а также степень прозрачности фрактального слоя. Затем можно будет подвергнуть фрактальное изображение трансформации, при необходимости масштабировать, определить размеры изображения и провести рендеринг. Создание изображения с нуля гораздо сложнее и предполагает выбор одного из двух способов. Можно выбрать тип фрактала почти из 150 вариантов. А затем уже перейти к изменению разнообразных параметров: настройке палитры, фона и пр. А можно попробовать создать свою пользовательскую формулу, воспользовавшись встроенным компилятором. Перед рендерингом готового изображения может потребоваться проведение автоматической коррекции цветового баланса и/или ручной коррекции яркости, контрастности и насыщенности.
4. ChaosPro
ChaosPro – один из лучших бесплатных генераторов фрактальных изображений, с помощью которого нетрудно создать бесконечное множество удивительных по красоте фрактальных изображений. Программа имеет очень простой и удобный интерфейс и наряду с возможностью автоматического построения фракталов позволяет полностью управлять данным процессом за счет изменения большого количества настроек (число итераций, цветовая палитра, степень размытия, особенности проецирования, размер изображения и др.). Кроме того, создаваемые изображения могут быть многослойными (режимом смешивания слоев можно управлять) и к ним можно применить целую серию фильтров. Все накладываемые на строящиеся фракталы изменения тут же отражаются в окне просмотра. Созданные фракталы могут быть сохранены в собственном формате программы, либо в одном из основных фрактальных типов благодаря наличию встроенного компилятора. Или экспортированы в растровые изображения или 3D-объекты (если предварительно было получено трехмерное представление фрактала).
5. Apophysis
Apophysis – интересный инструмент для генерации фракталов на основе базовых фрактальных формул. Созданные по готовым формулам фракталы можно редактировать и неузнаваемо изменять, регулируя разнообразные параметры. Так, например, в редакторе их можно трансформировать, либо изменив лежащие в основе фракталов треугольники, либо применив понравившийся метод преобразования: волнообразное искажение, перспективу, размытие по Гауссу и др. Затем стоит поэкспериментировать с цветами, выбрав один из базовых вариантов градиентной заливки. Список встроенных заливок достаточно внушителен, и при необходимости можно автоматически подобрать наиболее подходящую заливку к имеющемуся растровому изображению, что актуально, например, при создании фрактального фона в том же стиле, что и иные изображения некоего проекта. При необходимости несложно подрегулировать гамму и яркость, изменить фон, масштабировать фрактальный объект и уточнить его расположение на фоне. Можно также подвергнуть результат разнообразным мутациям в нужном стиле. По окончании следует задать размеры конечного фрактального изображения и записать его визуализированный вариант в виде графического файла (jpg, bmp, png).
6. Mystica
Mystica – универсальный генератор уникальных фантастических двумерных и трехмерных изображений и текстур, которые в дальнейшем можно использовать в разных проектах, например в качестве реальных текстур для Web-страниц, фонов Рабочего стола или фантастических фоновых изображений, которые могут быть задействованы, например, при оформлении детских книг. Пакет отличается нестандартным и достаточно сложным интерфейсом и может работать в двух режимах: Sample (ориентирован на новичков и содержит минимум настроек) и Expert (предназначен для профессионалов). Создаваемые изображения могут иметь любой размер и затем экспортироваться в популярные графические 2D-форматы. Встроенный трехмерный движок программы может быть использован при создании трехмерных сцен для компьютерных игр, например фантастических фонов и ландшафтов.
Генерация изображений осуществляется на основе заложенных в пакете фрактальных формул, а система подготовки изображения многоуровневая и включает очень подробную настройку цветов, возможность простейших трансформаций генерируемых элементов и массу прочих преобразований. В их числе применение фильтров, изменение освещения, корректировка цветовой гаммы, яркости и контрастности, изменение использованного при генерации материала, добавление к изображению “хаотических” структур и пр.