6

This is my attempt to program the Mandelbrot set in Python 3.5 using the Pygame module.

import math, pygame
pygame.init()

def mapMandelbrot(c,r,dim,xRange,yRange):
    x = (dim-c)/dim
    y = (dim-r)/dim
    #print([x,y])
    x = x*(xRange[1]-xRange[0])
    y = y*(yRange[1]-yRange[0])
    x = xRange[0] + x
    y = yRange[0] + y
    return [x,y]

def checkDrawBox(surface):
    for i in pygame.event.get():
        if i.type == pygame.QUIT:
            pygame.quit()
        elif i.type == pygame.MOUSEBUTTONDOWN:
            startXY = pygame.mouse.get_pos()
            boxExit = False
            while boxExit == False:
                for event in pygame.event.get():
                    if event.type == pygame.MOUSEBUTTONUP:
                        boxExit = True
                if boxExit == True:
                    return [startXY,pygame.mouse.get_pos()]
                pygame.draw.rect(surface,[255,0,0],[startXY,[pygame.mouse.get_pos()[0]-startXY[0],pygame.mouse.get_pos()[1]-startXY[1]]],1)
                pygame.display.update()

def setup():
    dimensions = 500
    white = [255,255,255]
    black = [0,0,0]
    checkIterations = 100
    canvas = pygame.display.set_mode([dimensions,dimensions])
    canvas.fill(black)
    xRange = [-2,2]
    yRange = [-2,2]
    xRangePrev = [0,0]
    yRangePrev = [0,0]
    newxRange = [0,0]
    newyRange = [0,0]
    while True:
        if not ([xRange,yRange] == [xRangePrev,yRangePrev]):
            draw(dimensions, canvas, xRange, yRange, checkIterations)
            pygame.display.update()
            xRangePrev = xRange
            yRangePrev = yRange
        box = checkDrawBox(canvas)
        if box != None:
            maxX = max([box[0][0],box[1][0]])
            maxY = max([box[0][1],box[1][1]])
            newxRange[0] = mapMandelbrot(box[0][0],0,dimensions,xRange,yRange)[0]
            newxRange[1] = mapMandelbrot(box[1][0],0,dimensions,xRange,yRange)[0]
            newyRange[0] = mapMandelbrot(0,box[0][1],dimensions,xRange,yRange)[1]
            newyRange[1] = mapMandelbrot(0,box[1][1],dimensions,xRange,yRange)[1]
            xRange = newxRange
            yRange = newyRange

def draw(dim, surface, xRange, yRange, checkIterations):
    for column in range(dim):
        for row in range(dim):
            greyVal = iteration(0,0,mapMandelbrot(column,row,dim,xRange,yRange),checkIterations,checkIterations)    
            surface.set_at([dim-column,row],greyVal)

def iteration(a, b, c, iterCount, maxIter):
    a = (a*a) - (b*b) + c[0]
    b = (2*a*b) + c[1]
    iterCount = iterCount - 1
    if iterCount == 0:
        return [0,0,0]
    elif abs(a+b) > 17:
        b = (iterCount/maxIter)*255
        return [b,b,b]
    else:
        return iteration(a,b,c,iterCount,maxIter)


setup()

I believe that the iteration algorithm is correct, but the output doesn't look right:

enter image description here

Wondering what might be the problem? Sorry for the code dump, just not sure which part may cause it to look like that.

John Coleman
  • 51,337
  • 7
  • 54
  • 119

2 Answers2

10

Fascinating bug -- it literally looks like a squashed bug :)

The problem lies in the two lines:

a = (a*a) - (b*b) + c[0]
b = (2*a*b) + c[1]

You are changing the meaning of a in the first line, hence using the wrong a in the second.

The fix is as simple as:

a, b  = (a*a) - (b*b) + c[0], (2*a*b) + c[1]

which will cause the same value of a to be used in calculating the right hand side.

It would be interesting to work out just what your bug has produced. Even though it isn't the Mandelbrot set, it seems to be an interesting fractal in its own right. In that sense, you had a very lucky bug. 99% percent of the times, bugs lead to garbage, but every now and then they produce something quite interesting, but simply unintended.

On Edit:

The Mandelbrot set is based on iterating the complex polynomial:

f(z) = z^2 + c

The pseudo-Mandelbrot set which this bug has produced is based on iterating the function

f(z) = Re(z^2 + c) + i*[2*Re(z^2 + c)*Im(z) + Im(c)]

where Re() and Im() are the operators which extract the real and imaginary parts of a complex number. This isn't a polynomial in z, though it is easy to see that it is a polynomial in z,z* (where z* is the complex conjugate of z). Since it is a fairly natural bug, it is almost certain that this has appeared somewhere in the literature on the Mandelbrot set, though I don't remember ever seeing it.

John Coleman
  • 51,337
  • 7
  • 54
  • 119
0

I decided to learn about the mandelbrot set and wrote my own version! I used python's complex data type, which should make the mandelbrot calculation for each pixel a bit clearer. Here is a screenshot of the result:

enter image description here

And here is the source code/code dump:

import pygame
import sys

def calc_complex_coord(x, y):
    real = min_corner.real + x * (max_corner.real - min_corner.real) / (width - 1.0)
    imag = min_corner.imag + y * (max_corner.imag - min_corner.imag) / (height - 1.0)
    return complex(real, imag)

def calc_mandelbrot(c):
    z = c
    for i in range(1, max_iterations+1):
        if abs(z) > 2:
            return i
        z = z*z + c
    return i

def calc_color_score(i):
    if i == max_iterations:
        return black
    frac = 255.0 - (255.0 * i / max_iterations)
    return (frac, frac, frac)

def update_mandelbrot():
    for y in range(height):
        for x in range(width):
            c = calc_complex_coord(x, y)
            mandel_score = calc_mandelbrot(c)
            color = calc_color_score(mandel_score)
            mandel_surface.set_at((x, y), color)

if __name__ == "__main__":
    pygame.init()
    (width, height) = (500, 500)
    display = pygame.display.set_mode((width, height))
    pygame.display.set_caption("Mandelbrot Magic")
    clock = pygame.time.Clock()
    mandel_surface = pygame.Surface((width, height))
    black = (0, 0, 0)
    red = (255, 0, 0)
    max_iterations = 50
    min_corner = complex(-2, -2)
    max_corner = complex(2, 2)
    box = pygame.Rect(0, 0, width, height)
    update_mandel = True
    draw_box = False

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                x, y = event.pos
                box = pygame.Rect(x, y, 0, 0)
                draw_box = True
            elif event.type == pygame.MOUSEMOTION:
                x, y = event.pos
                if draw_box:
                    box = pygame.Rect(box.left, box.top, x - box.left, y - box.top)
            elif event.type == pygame.MOUSEBUTTONUP:
                x, y = event.pos
                update_mandel = True

        display.blit(mandel_surface, (0, 0))
        if draw_box:
            pygame.draw.rect(display, red, box, 1)
        if update_mandel:
            box.normalize()
            new_min_corner = calc_complex_coord(box.left, box.top)
            new_max_corner = calc_complex_coord(box.right, box.bottom)
            min_corner, max_corner = new_min_corner, new_max_corner
            update_mandelbrot()
            update_mandel = False
            draw_box = False

        pygame.display.update()
        clock.tick(60)

The two issues with this code are that one, it is quite slow in updating the mandelbrot set, and two, the aspect ratios get distorted if you work with non-square windows or box-selections. Let me know if any of the code is unclear!

CodeSurgeon
  • 2,435
  • 2
  • 15
  • 36
  • 1
    Looks good man and thanks for including the box drawing as well, however I was kinda looking for the specific problem in my code, as technically I think I have emulated the complex data type in a way that gives the same value for each pixel as you did. – lollingbirdz Feb 05 '17 at 13:10