Как быстрее визуализировать множество Мандельброта?

В настоящее время я рисую набор Мандельброта пиксель за пикселем с помощью PhotoImage и tkinter. Я использую в основном алгоритм напрямую без каких-либо изменений. Есть ли способы сделать расчет быстрее? Может быть, быстрое заполнение больших областей цвета или предварительное вычисление констант?

Часть кода:

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

person qwr    schedule 19.10.2014    source источник
comment
Не могли бы вы добавить здесь, и все уважаемые авторы ответов StackOverflow в своих соответствующих предложениях укажите ваше начальное время выполнения кода [usec] ( + соответствующее решение, где предлагается), чтобы установить эталон и основу для какой бы ни был более быстрый подход, и каков ваш ожидаемый порог ускорения - насколько быстрее вы хотите, чтобы процесс стал быстрее? Спасибо за ваше любезное повторное рассмотрение.   -  person user3666197    schedule 19.10.2014


Ответы (6)


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

Пример можно найти здесь, среди прочего: https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once

person Bryan Oakley    schedule 19.10.2014
comment
Лучшие результаты на сегодняшний день, кроме перезаписи с помощью numpy - person qwr; 20.10.2014
comment
Возвращаясь к этому вопросу спустя годы, я понимаю, что запись в файл изображения (либо что-то простое, например ppm, либо с помощью подушки, преобразующей массив numpy в изображение) будет даже быстрее, чем попытка использовать tk для рендеринга. - person qwr; 05.03.2020

Вот мой код, он рисует Мандельброта 640x480 за 8-9 секунд.

Он выполняет до 256 итераций на пиксель, использует список карт цветов, «помещает» только один раз в PhotoImage и не полагается на симметрию, поэтому может отображать любую увеличенную область набора.

Жаль, что Tkinter не разрешает доступ к растровой информации PhotoImage в качестве буфера и что требуется неуклюжая строка.

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

введите здесь описание изображения

person Antoni Gual Via    schedule 22.04.2015

Чистый питон не так быстр для числового кода. Самый простой способ ускорить процесс — использовать PyPy. Если это недостаточно быстро, векторизуйте свои алгоритмы, используя numpy. Если это все еще недостаточно быстро, используйте Cython или подумайте о том, чтобы переписать его на C.

person Joshua    schedule 19.10.2014

Для небольшого увеличения скорости (но недостаточного, чтобы компенсировать разницу между скомпилированным языком и интерпретируемым) вы можете предварительно вычислить некоторые значения.

Прямо сейчас вы вычисляете DIAMETER / HEIGHT один раз для внутреннего цикла и CENTER[1] - 0.5 * DIAMETER, а также DIAMETER / WIDTH один раз для внешнего цикла. Сделайте это заранее.

len(colors) также не изменится и может быть заменена константой. На самом деле, я бы, вероятно, написал эту функцию как

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

Кроме того, x**2 медленнее, чем x*x (поскольку оператор x**y не сокращает тривиальный случай y==2), поэтому вы можете немного ускорить вычисление.

person Tim Pietzcker    schedule 19.10.2014

Большая часть времени тратится на внутренний цикл в mandel(). z*z вместо z**2 имело небольшой эффект. Насколько я вижу, там больше нечего ускорять. Удаление констант из других циклов малоэффективно, хотя я предпочитаю так делать. Выбор ITERATIONS таким образом, чтобы ITERATIONS//2 % len(colors) == len(colors)-1, как и в 46 //2 % 4 == 3, позволял упростить код. Использование симметрии вокруг оси X сокращает время вдвое. Начальный imag с 0 позволяет избежать ошибки округления 300 вычитаний из +/- DIAMETER / 2 и приводит к чистой центральной линии на изображении.

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()
person Terry Jan Reedy    schedule 19.10.2014

Комплексные числа в Python могут быть медленными, особенно если вы вызываете abs(x) на каждой итерации. Представление комплексного числа с помощью c_r и c_i для действительной и мнимой частей уменьшает количество вычислений, которые вы выполняете на каждой итерации.

def mandel(c):
    z = 0
    for i in range(ITERATIONS):
        z = z**2 + c
        if abs(z) > 2:
            return i     
    return ITERATIONS

вместо z = 0 замените его на z_r,z_i=0,0 так же мы должны изменить c в параметрах. Теперь у нас есть:

def mandel(c_r,c_i):
    z_r = 0
    z_i = 0
    for i in range(ITERATIONS):
        z = z**2 + c
        if abs(z) > 2:
            return i     
    return ITERATIONS

Вместо использования abs(z) > 2 теперь мы можем использовать z_r * z_r + z_i + z_i > 4 Кроме того, мы заменяем z**2 + c новой версией, используя наши новые переменные (знайте, что (a+bi)^2 = a^2 - b^2 + 2abi

def mandel(c_r,c_i):
    z_r = 0
    z_i = 0
    z_r_squared = 0
    z_i_squared = 0
    for i in range(ITERATIONS):
        z_r_squared = z_r * z_r
        z_i_squared = z_i * z_i
        z_r = z_r_squared - z_i_squared + c_r
        z_i = 2 * z_r * z_i + c_i

        if z_r_squared + z_r_squared > 4:
            return i     
    return ITERATIONS

Наконец, вам нужно изменить место вызова функции Мандельброта, поэтому

i = mandel(complex(real, imag))

становится

i = mandel(real, imag)
person Max    schedule 05.03.2020