Fractales en Python. Tutorial

Hola Habr! La publicación de hoy sobre fractales ha aparecido como parte de un tema de Python , en particular, Matplotlib. Sigue el ejemplo del autor y advierte que en la publicación hay mucha animación pesada que puede que ni siquiera funcione en un dispositivo móvil. Pero que hermoso.



Todos disfrutan leyendo

Los fractales son hermosos. ¡Se alinean de acuerdo con un patrón muy complejo y se conservan sin distorsiones con ningún aumento! En este artículo, veremos cómo puede dibujar fácilmente fractales de varios tipos, utilizando una herramienta llamada L-Systems y el módulo Turtle para Python para realizar un dibujo paso a paso.

En este artículo, no entraremos excesivamente en detalles técnicos; en cambio, me limito a una breve introducción, muestro muchos ejemplos animados y código con el que puede generar tal ejemplo. Si quieres saltarte la teoría y solo mirar la animación, ve directamente a los ejemplos. Además, al final señalaré varios recursos, tanto en escritura de código como en matemática básica, que tal vez desee explorar.

¿Qué es un fractal?

Para comenzar, demos una definición "suelta" de un fractal. En principio, un fractal es una figura geométrica que muestra las mismas propiedades independientemente del grado de aumento.

Esta definición no es perfecta, así que aquí hay una más precisa del sitio web de Math World:
Un fractal es un objeto o cantidad que demuestra auto-similitud (en el sentido formal) a cualquier escala. Un objeto exhibe estructuras no idénticas a diferentes escalas, pero las estructuras del mismo "tipo" deben aparecer en todos los niveles del fractal. En este caso, el gráfico se traza en un sistema de coordenadas con una escala logarítmica, donde la magnitud y la escala se cuentan a lo largo de los ejes, el gráfico es una línea recta con una pendiente que refleja la dimensión del fractal. - Math World

¿Cómo dibujar fractales usando Python?

Típicamente, la representación de fractales es compleja, ya que la naturaleza profunda de los fractales está determinada por el concepto de recursión. Hablando de gráficos y su dibujo, generalmente pensamos que están formados por píxeles o vectores, pero el número de píxeles o vectores siempre es limitado, y los fractales, por definición, son infinitamente recursivos. Por lo tanto, al intentar aplicar un fractal a la cuadrícula de coordenadas, tendremos que parar en algún momento, y es por eso que estamos hablando de "iteraciones" en este caso. En cada iteración, el fractal se vuelve más complicado, y en algún momento se hace imposible distinguir dos de sus iteraciones que se suceden (tal momento ocurre cuando ocurren cambios a un nivel comparable al tamaño del píxel). Es lógico detenerse aquí, pero, por regla general, la forma del fractal se avecina más rápido, y puede detenerse incluso antes.

Dos ejemplos similares son la isla cuadrada de Koch, cuya estructura emerge claramente después de tres iteraciones, y el dragón Carter-Heitway, para el cual 8 iteraciones son suficientes para construir la estructura completa. El número requerido de iteraciones depende en gran medida del fractal específico con el que estamos trabajando.

Por supuesto, hay muchas bibliotecas de Python para trazar, entre las cuales la más popular es Matplotlib, pero generalmente están diseñadas para dibujar estadísticas y dibujar gráficos conocidos. En particular, Matplotlib contiene algunas construcciones de bajo nivel con las que puedes construir fractales, pero esta vez nos centraremos en el módulo inmerecidamente poco conocido de la biblioteca estándar llamado Turtle.

Módulo de tortuga

en la documentación de Pythonleemos: “Turtle graphics es una herramienta popular para los primeros conocidos de los niños con la programación. Formó parte del lenguaje de programación Logo original, desarrollado por Wally Förzeg y Seymour Peiper en 1966 ".

La conclusión es que la tortuga reconoce 3 comandos por defecto:

  • Arrastrarse hacia adelante
  • Girar ángulo izquierdo
  • Girar ángulo recto

Nota: se proporcionan otros comandos en la biblioteca estándar, pero aquí solo usaremos estos tres.

También podemos:

  • Registro de silencio
  • Habilitar grabación

Estas características parecen demasiado simples para dibujar en un gráfico tan complejo como un fractal, confiando solo en ellas, pero usaremos otra herramienta que use solo este pequeño conjunto de instrucciones. Estoy hablando de los sistemas L.

Sistemas

L Un sistema L es una forma de representar estructuras recursivas (por ejemplo, fractales) como una cadena de caracteres y reescribir dicha cadena varias veces. Nuevamente, damos una definición formal:
El sistema Lindenmeyer, también conocido como el sistema L, es un mecanismo de reescritura de líneas que puede usarse para generar fractales con dimensiones del 1 al 2 - Math World

Habiendo entendido qué es un sistema L, podemos crear estructuras recursivas, pero primero, descubramos qué componentes necesitamos para esto. Cada sistema L tiene:

  • : , L-.
  • : .
  • : , .

Nota para los fanáticos de la informática: si has estudiado informática en profundidad, todo lo anterior puede recordarte algo. De hecho, las gramáticas formales se definen de manera muy similar; La diferencia clave es que, a diferencia de las gramáticas, aquí en cada iteración se aplican tantas reglas como sea posible, y no solo una. Por lo tanto, los sistemas L son un subconjunto de gramáticas libres de contexto.

Dado que vamos a usar Turtle para construir gráficos y el sistema L para representar lo que vamos a trazar, necesitamos crear una relación entre ellos.

Como en Turtle solo tenemos los equipos enumerados anteriormente, asignamos a cada uno de ellos un símbolo; El alfabeto consistirá en estos caracteres.

  • F: gatear hacia adelante
  • +: gire a la derecha
  • -: girar a la izquierda

Para que esto funcione, se debe proporcionar un ángulo para cada fractal; este será el ángulo en el cual la tortuga girará hacia la derecha o hacia la izquierda. Para simplificar, estamos de acuerdo en que solo se debe proporcionar una esquina, y escribiremos el sistema L, teniendo esto en cuenta.
El axioma y las instrucciones para crear cadenas dependerán solo del fractal, pero el fractal debe escribirse de tal manera que solo pueda representarse con estos tres caracteres. Por lo tanto, existe una limitación en virtud de la cual solo podemos construir fractales de una sola línea, es decir, algo como el conjunto de Cantor no se puede obtener de esta manera. Pero esto es solo una simplificación, porque siempre podemos ingresar otros dos comandos para avanzar sin grabar, y lo mismo para retroceder.

Ahora vamos a los ejemplos!

Ejemplos animados

Los siguientes ejemplos se tomaron en línea de varias fuentes disponibles públicamente, y decidí portarlos a Python usando el módulo Turtle, centrarlos, colorearlos y proporcionar una forma de exportar a un formato vectorial.

ATENCIÓN: las animaciones propuestas son bastante grandes, se recomienda verlas solo con buena conexión a Internet. El código de respuesta puede no funcionar porque consume sus recursos, y puede haber problemas con la visualización de fractales en dispositivos móviles.

Nota: Skulpt usa SU NAVEGADOR para renderizar y crear animaciones, por lo que cuando cuelga, se retrasa o cualquier comportamiento extraño, generalmente es suficiente reproducir la animación o volver a cargar la página. Puede no funcionar en dispositivos móviles.

Los ejemplos se dan en orden de complejidad (en mi opinión subjetiva), por lo que lo más interesante es al final.

Copo de nieve Koch

axiom = "F--F--F"
rules = {"F":"F+F--F+F"}
iterations = 4 # TOP: 7
angle = 60


Isla Koch Square

axiom = "F+F+F+F"
rules = {"F":"F-F+F+FFF-F-F+F"}
iterations = 2 # TOP: 4
angle = 90


Cristal

axiom = "F+F+F+F"
rules = {"F":"FF+F++F+F"}
iterations = 3 # TOP: 6
angle = 90


Copo de nieve cuadrado

axiom = "F--F"
rules = {"F":"F-F+F+F-F"}
iterations = 4 # TOP: 6
angle = 90


Fractal Wicheka

axiom = "F-F-F-F"
rules = {"F":"F-F+F+F-F"}
iterations = 4 # TOP: 6
angle = 90


Curva de Levy

axiom = "F"
rules = {"F":"+F--F+"}
iterations = 10 # TOP: 16
angle = 45


Alfombra Sierpinski

axiom = "YF"
rules = {"X":"YF+XF+Y", "Y":"XF-YF-X"}
iterations = 1 # TOP: 10
angle = 60


Celosía Sierpinski

axiom = "FXF--FF--FF"
rules = {"F":"FF", "X":"--FXF++FXF++FXF--"}
iterations = 7 # TOP: 8
angle = 60


Cuadrado

axiom = "F+F+F+F"
rules = {"F":"FF+F+F+F+FF"}
iterations = 3 # TOP: 5
angle = 90


Losas

axiom = "F+F+F+F"
rules = {"F":"FF+F-F+F+FF"}
iterations = 3 # TOP: 4
angle = 90


Anillos

axiom = "F+F+F+F"
rules = {"F":"FF+F+F+F+F+F-F"}
iterations = 2 # TOP: 4
angle = 90


Cruz 2

axiom = "F+F+F+F"
rules = {"F":"F+F-F+F+F"}
iterations = 3 # TOP: 6
angle = 90


Pentaplejidad

axiom = "F++F++F++F++F"
rules = {"F":"F++F++F+++++F-F++F"}
iterations = 1 # TOP: 5
angle = 36


Curva de 32 segmentos

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


Curva de Peano Gosper

axiom = "FX"
rules = {"X":"X+YF++YF-FX--FXFX-YF+", "Y":"-FX+YFYF++YF+FX--FX-Y"}
iterations = 4 # TOP: 6
angle = 60


Curva de Sierpinski

axiom = "F+XF+F+XF"
rules = {"X":"XF-F+F-XF+F+XF-F+F-X"}
iterations = 4 # TOP: 8
angle = 90


Los cuestionarios de Krishna

axiom = " -X--X"
rules = {"X":"XFX--XFX"}
iterations = 3 # TOP: 9
angle = 45


Fractal Cuadrado Gosper

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


Curva de Moore

axiom = "LFL-F-LFL"
rules = {"L":"+RF-LFL-FR+", "R":"-LF+RFR+FL-"}
iterations = 0 # TOP: 8
angle = 90


Curva de Hilbert

axiom = "L"
rules = {"L":"+RF-LFL-FR+", "R":"-LF+RFR+FL-"}
iterations = 8 # TOP: 9
angle = 90


Curva de Hilbert II

axiom = "X"
rules = {"X":"XFYFX+F+YFXFY-F-XFYFX", "Y":"YFXFY-F-XFYFX+F+YFXFY"}
iterations = 4 # TOP: 6
angle = 90


Curva de Peano

axiom = "F"
rules = {"F":"F+F-F-F-F+F+F+F-F"}
iterations = 2 # TOP: 5
angle = 90


Cruzar

axiom = "F+F+F+F"
rules = {"F":"F+FF++F+F"}
iterations = 3 # TOP: 6
angle = 90


Triángulo

axiom = "F+F+F"
rules = {"F":"F-F+F"}
iterations = 2 # TOP: 9
angle = 120


Curva del dragón

axiom = "FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 8 # TOP: 16
angle = 90


Curva Terdragon

axiom = "F"
rules = {"F":"F-F+F"}
iterations = 5 # TOP: 10
angle = 120


Doble curva de dragón

axiom = "FX+FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 6 # TOP: 16
angle = 90


Triple curva de dragón

axiom = "FX+FX+FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 7 # TOP: 15
angle = 90


Código

Todos los ejemplos anteriores se obtuvieron utilizando el mismo código, y al trabajar en ellos, hubo algunas dificultades (por ejemplo, cómo mantener el fractal en el centro tanto como sea posible), trabajar con color, inversión, compensaciones, así como proporcionar una exportación rápida a formato vectorial Aquí te muestro solo la versión más simple.
Esta versión muestra fractales en blanco y negro y no está equipada con la funcionalidad de exportación

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()


Explicación del código

import turtle


Primero debes importar el módulo 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

Luego debe generar un sistema L, que será un conjunto de instrucciones para la tortuga. Definimos una función create_l_systemque recibe el número de iteraciones, un axioma y reglas de construcción. Comienza con un axioma y usa una variable auxiliar end_string, si la iteración es 0, entonces devolverá el axioma, ya que algunos fractales también pueden aplicarse con cero iteraciones. En este caso, se supone que las reglas tienen la forma de diccionarios, por lo que cada clave es única, representa un símbolo y el valor indica qué y qué se debe reemplazar. Entonces combinamos todos los reemplazos para cada personaje y eventualmente obtenemos una cadena para la próxima iteración.

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)

Luego determinamos quién draw_l_systemacepta la tortuga, un conjunto de instrucciones (salida del sistema L), el ángulo para girar a la izquierda o la derecha y la longitud de cada línea individual. Consiste en una estructura simple elifpara cada uno de los equipos previamente definidos.

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()

Por último, Hablemos de la función main, que tiene todos los parámetros necesarios para la generación de sistemas-L, así como y_offset, x_offset, offset_angle, widthy height. Los primeros tres describen el desplazamiento de la tortuga, es necesario colocar el gráfico en el lienzo de la manera que queremos.

La función primero genera un conjunto de instrucciones y las almacena en inst, luego inicializa la tortuga y la pantalla y la coloca en un punto específico, luego dibuja un gráfico de acuerdo con las instrucciones y espera un clic para cerrar.

Consideraciones Especiales

Como mencioné anteriormente, muchas restricciones quedan aquí. En primer lugar, no le dimos a la tortuga la capacidad de moverse sin renderizar; Esto requeriría otro personaje. Tampoco hay un símbolo para retirarse y para recordar posiciones anteriores. No fueron necesarios para todos los fractales discutidos anteriormente, pero son necesarios para algunos otros (por ejemplo, árboles fractales).

Recursos adicionales

Internet tiene muchos recursos sobre fractales, donde se consideran tanto desde el punto de vista de la programación como desde el punto de vista de las matemáticas. Los dos siguientes me parecieron especialmente interesantes: 3Blue1Brown (matemáticas) y CodingTrain (código).

El artículo fue inspirado por una publicación de Math World y el artículo Paula Burka.

All Articles