¿Cómo renderizar Mandelbrot Set más rápido?

Actualmente estoy dibujando el conjunto de Mandelbrot píxel por píxel con PhotoImage y tkinter. Básicamente estoy usando el algoritmo directamente sin modificaciones. ¿Existen métodos para hacer el cálculo más rápido? ¿Tal vez rellenar grandes áreas de color rápidamente, o precalcular constantes?

Parte del código:

ITERATIONS = 50 WIDTH, HEIGHT = 600, 600 CENTER = (-.5, 0) DIAMETER = 2.5 def mandel(c): z = 0 for i in range(ITERATIONS): z = z**2 + c if abs(z) > 2: return i return ITERATIONS root = Tk() canvas = Canvas(root, width=WIDTH,height=HEIGHT) canvas.pack() img = PhotoImage(width=WIDTH, height=HEIGHT) canvas.create_image((WIDTH/2, HEIGHT/2), image=img, state="normal") real = CENTER[0] - 0.5 * DIAMETER imag = CENTER[1] - 0.5 * DIAMETER def color(i): colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000") if i == ITERATIONS: return colors[-1] else: choice = (i//2) % len(colors) return colors[choice] for x in range(WIDTH): for y in range(HEIGHT): i = mandel(complex(real, imag)) img.put(color(i), (x, HEIGHT-y)) imag += DIAMETER / HEIGHT imag = CENTER[1] - 0.5 * DIAMETER real += DIAMETER / WIDTH mainloop() 

La configuración de un píxel a la vez es probablemente la fuente principal de la desaceleración. En lugar de llamar a poner por cada píxel, compute una fila completa de píxeles o una matriz completa de píxeles y luego coloque una vez al final del bucle.

Puede encontrar un ejemplo aquí, entre otros lugares: https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once

Aquí está mi código, dibuja un Mandelbrot 640×480 en 8-9 segundos.

Hace hasta 256 iteraciones por píxel, usa una lista de mapas de color, ‘pone’ solo una vez a PhotoImage y no se basa en la simetría, por lo que podría mostrar cualquier área ampliada del conjunto.

Es una pena que Tkinter no permita el acceso a la información rasterizada de PhotoImage como un búfer y que se requiera la cadena torpe.

 from tkinter import Tk, Canvas, PhotoImage,NW,mainloop from time import clock def mandel(kx,ky): """ calculates the pixel color of the point of mandelbrot plane passed in the arguments """ global clr maxIt = 256 c = complex(kx, ky) z = complex(0.0, 0.0) for i in range(maxIt): z = z * z + c if abs(z) >= 2.0: return (255-clr[i],0,0) return(0,0,0) def prepare_mdb(xa,xb,ya,yb): """ pre-calculates coordinates of the mandelbrot plane required for each pixel in the screen""" global x,y,xm,ym xm.clear ym.clear xm=[xa + (xb - xa) * kx /x for kx in range(x)] ym=[ya + (yb - ya) * ky /y for ky in range(y)] x=640 y=480 #corners of the mandelbrot plan to display xa = -2.0; xb = 1.0 ya = -1.5; yb = 1.5 #precalculated color table clr=[ int(255*((i/255)**12)) for i in range(255,-1,-1)] xm=[] ym=[] prepare_mdb(xa,xb,ya,yb) #Tk window = Tk() canvas = Canvas(window, width = x, height = y, bg = "#000000") t1=clock() img = PhotoImage(width = x, height = y) canvas.create_image((0, 0), image = img, state = "normal", anchor = NW) pixels=" ".join(("{"+" ".join(('#%02x%02x%02x' % mandel(i,j) for i in xm))+"}" for j in ym)) img.put(pixels) canvas.pack() print(clock()-t1) mainloop() 

introduzca la descripción de la imagen aquí

Pure python no es tan rápido para el código numérico. La forma más fácil de acelerar las cosas sería usar PyPy. Si eso no es lo suficientemente rápido, vectorice sus algoritmos utilizando numpy. Si todavía no es lo suficientemente rápido, use Cython, o considere reescribirlo en C.

Para un aumento moderado de la velocidad (pero no lo suficiente para compensar la diferencia entre un idioma comstackdo y uno interpretado), puede precalcular algunos de los valores.

En este momento, está calculando DIAMETER / HEIGHT una vez por bucle interno, y CENTER[1] - 0.5 * DIAMETER , así como también DIAMETER / WIDTH una vez por bucle externo. Haz esto de antemano.

len(colors) tampoco cambiará y puede ser reemplazado por una constante. De hecho, probablemente escribiría esa función como

 def color(i): if i == ITERATIONS: return "#000000" else: return ("#0000AA", "#88DDFF", "#FF8800", "#000000")[(i//2) % 4] # are you sure you don't want ("#0000AA", "#88DDFF", "#FF8800")[(i//2) % 3] ? 

Además, x**2 es más lento que x*x (porque el operador x**y no tiene un acceso directo para el caso trivial de y==2 ), por lo que puede acelerar ese cálculo un poco.

La mayor parte del tiempo se gasta en el bucle interno en mandel (). z*z lugar de z**2 tuvo un ligero efecto. No hay mucho más para acelerar allí que pueda ver. Eliminar constantes de otros bucles tuvo poco efecto, aunque tiendo a preferir hacerlo. Eligiendo ITERACIONES de modo que ITERATIONS//2 % len(colors) == len(colors)-1 , como en 46 //2 % 4 == 3 , permita la simplificación del código. Aprovechar la simetría alrededor del eje x reduce el tiempo a la mitad. La imagen de inicio en 0 evita el error de redondeo de 300 restas de +/- DIAMETER / 2 y da como resultado una línea central limpia en la imagen.

 from tkinter import * ITERATIONS = 46 WIDTH, HEIGHT = 601, 601 # odd for centering and exploiting symmetry DIAMETER = 2.5 start = (-.5 - DIAMETER / 2, 0) # Start y on centerline d_over_h = DIAMETER / HEIGHT d_over_w = DIAMETER / WIDTH def mandel(c): z = 0 for i in range(ITERATIONS): z = z*z + c if abs(z) > 2: return i return ITERATIONS root = Tk() canvas = Canvas(root, width=WIDTH,height=HEIGHT) canvas.pack() img = PhotoImage(width=WIDTH, height=HEIGHT) canvas.create_image(((WIDTH+1)//2, (HEIGHT+1)//2), image=img, state="normal") real, imag = start colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000") ncolors = len(colors) yrange = range(HEIGHT//2, -1, -1) # up from centerline ymax = HEIGHT - 1 for x in range(WIDTH): for y in yrange: i = mandel(complex(real, imag)) color = colors[i//2 % ncolors] img.put(color, (x, y)) img.put(color, (x, ymax - y)) imag += d_over_h imag = start[1] real += d_over_w mainloop()