A representation of the Mandelbrot Set I've written up some code that represents the Mandelbrot set in an, admittedly, quite inefficient way. It probably looks awful due to the fact that I did this completely solo, without any external sources aside from the wiki page. What I was wondering is, how can I, without rewriting it completely, fix my code so that it either runs faster, or is higher definition?

Final image: 



import turtle
x = -200
y = 99
RN = -2
i = float(1.0)
c = complex(RN+i)
win = turtle.Screen()
win.setup(400,400)
t = turtle.Turtle()
t.speed(0)
t.penup()
t.ht()
pattern = []

#this calculates what the color hex value each pixel will be. if the 10th
#iteration results in a number greater than 2, then that pixel is outside
#of the set.
def set_color(RN,pattern,z,c):
    for v in range(10):
        z = z**2 + c
        pattern.append(z)
    if abs(z) &gt; 3.7918623e+90:
        t.color("#191970")
    elif abs(z) &gt; 1.9472705e+45:
        t.color("#16167F")
    elif abs(z) &gt; 4.4127888e+22:
        t.color("#13138F")
    elif abs(z) &gt; 210066388901:
        t.color("#10109F")
    elif abs(z) &gt; 458330:
        t.color("#0D0DAF")
    elif abs(z) &gt; 677:
        t.color("#0B0BBF")
    elif abs(z) &gt; 26:
        t.color("#0808CF")
    elif abs(z) &gt; 5:
        t.color("#0505DF")
    elif abs(z) &gt; 2:
        t.color("#0202EF")
    elif abs(z) &gt; 1.75:
        t.color("yellow")
    elif abs(z) &gt; 1.5:
        t.color("gold")
    elif abs(z) &gt; 1.25:
        t.color("goldenrod")
    elif abs(z) &gt; 1:
        t.color("darkgoldenrod")
    else:
        t.color("black")

#draws the set
def Mandelbrot(RN,pattern,precision,check_rate,move_rate,x,y,i,c):
#draws line by line, 100 times
    for e in range(100):
        t.pendown()
#draws one line
        for e in range(precision):
            z = 0
            set_color(RN,pattern,z,c)
            pattern = []
            RN = float(RN + move_rate)
            c = complex(RN,i)
            x = x + check_rate
            t.setposition(x,y)
#sets up values you'll need next row
        RN = -2.0
        i = i-.02
        t.penup()
        x = -200
        y = y - 2
        t.setposition(x,y)
    t.penup()

#purely decorative black background for the set
def borders():
    t.color("black")
    t.setposition(-200,200)
    t.pendown()
    t.begin_fill()
    for i in range(4):
        t.forward(400)
        t.right(90)
    t.end_fill()

precision = 400
check_rate = float(400 / precision)
move_rate = float(check_rate/100)

t.penup()
borders()
t.setposition(x,y)
Mandelbrot(RN,pattern,precision,check_rate,move_rate,x,y,i,c)
Wow!  That is really slow.  I changed range(100) to range(5), just to get some timing numbers without dying of boredom.  So with that as my baseline, let's work on speeding it up.

set_color

def set_color(RN,pattern,z,c):
    for v in range(10):
        z = z**2 + c
        pattern.append(z)
    if abs(z) &gt; 3.7918623e+90:
        t.color("#191970")
    elif abs(z) &gt; 1.9472705e+45:
        t.color("#16167F")
    elif abs(z) &gt; 4.4127888e+22:
        t.color("#13138F")
    elif abs(z) &gt; 210066388901:
        t.color("#10109F")
    elif abs(z) &gt; 458330:
        t.color("#0D0DAF")
    elif abs(z) &gt; 677:
        t.color("#0B0BBF")
    elif abs(z) &gt; 26:
        t.color("#0808CF")
    elif abs(z) &gt; 5:
        t.color("#0505DF")
    elif abs(z) &gt; 2:
        t.color("#0202EF")
    elif abs(z) &gt; 1.75:
        t.color("yellow")
    elif abs(z) &gt; 1.5:
        t.color("gold")
    elif abs(z) &gt; 1.25:
        t.color("goldenrod")
    elif abs(z) &gt; 1:
        t.color("darkgoldenrod")
    else:
        t.color("black")


The first argument, RN, is not used in this function.  So we can omit that, and save a little time not passing a useless argument.

        z = 0
        set_color(RN,pattern,z,c)
        pattern = []


The second argument, pattern, is being accumulated, and then discarded.  So busy work involving memory allocation for extending a list who's value is never used.  We can remove that argument and pattern.append(z).

The third argument, z, is always coming in as zero.  No point passing it as an argument; just initialize it inside the set_color function, which will save a little bit of time because we don't pass the extra argument.

Which just leaves passing in the argument c.  Getting better.

Then, you have a long string of if abs(z) &gt; # checks.  How many times are you computing abs of a complex number?  How many times will the result be different?  Maybe we can compute it just once?

    abs_z = abs(z)
    if z &gt; 3.7918623e+90:
        t.color("#191970")
    elif abs_z &gt; 1.9472705e+45:
        ...


Unfortunately, that still leaves an ugly long string of if/elif/elif/else statements.  With 14 possibilities, you take an average of 7 tests to determine the correct value; with a binary search, this would reduce to 4.  We just need to put the values in a list, and use bisect.bisect to determine where the abs(z) would be inserted in the list.

import bisect

MAG = (1, 1.25, 1.5, 1.75, 2, 5,
       26, 677, 458330, 210066388901, 4.4127888e+22, 1.9472705e+45,
       3.7918623e+90)

COLOR = ("black", "darkgoldenrod", "goldenrod", "gold", "yellow", "#0202EF",
         "#0505DF", "#0808CF", "#0B0BBF", "#0D0DAF", "#10109F", "#13138F",
         "#16167F", "#191970")

def set_color(c):
    z = 0
    for _ in range(10):
        z = z**2 + c
    t.color(COLOR[bisect.bisect(MAG, abs(z))])


My timing hasn't shown a significant impact in speed yet, but I certainly like the set_color function much better now.  If you want to add more colours, it is simply a matter of filling in additional values in the arrays.

Mandelbrot

def Mandelbrot(RN,pattern,precision,check_rate,move_rate,x,y,i,c):
#draws line by line, 100 times
    for e in range(100):
        t.pendown()
#draws one line
        for e in range(precision):
            set_color(c)                 # Note: modified for new set_color
            RN = float(RN + move_rate)
            c = complex(RN,i)
            x = x + check_rate
            t.setposition(x,y)
#sets up values you'll need next row
        RN = -2.0
        i = i-.02
        t.penup()
        x = -200
        y = y - 2
        t.setposition(x,y)
    t.penup()


This looks backwards.  You pass in RN, but RN is computed in the loop.  You pass in x, but x is initialize to -200 each time through the loop.  You pass in c, but c is computed in the loop.  And most things seem to be computed just after they've been used for the next loop iteration!

Let's re-work this, moving calculation to before they are used, and see what it looks like.  Along the way, variable = variable + adjustment will be replaced with variable += adjustment, because it results in one less variable lookup in the Python interpreter.  Also, RN + move_rate is already a float, so we can omit the redundant float() call.  When looping over a range of values (y starting at 99, and going down by two 100 times, a for y in range(...) construct is used:

def mandelbrot(precision, check_rate, move_rate):
    i = 1.0
    t.penup()

    for y in range(99, 99 - 2 * 100, -2):
        rn = -2.0
        x = -200
        t.setposition(x, y)

        t.pendown()
        for _ in range(precision):
            c = complex(rn, i)
            set_color(c)

            rn += move_rate
            x += check_rate
            t.setposition(x, y)

        i -= 0.02
        t.penup()


Several PEP-8 changes: commas are followed by a space, variable names and method names are lowercase (technically, snake_case).  _ is used as the unused variable.  Operators have a space on each side.

Main

Again, float( ) calls are unnecessary.

if __name__ == '__main__':
    precision = 400
    check_rate = 400 / precision
    move_rate = check_rate / 100

    t.penup()
    borders()
    mandelbrot(precision, check_rate, move_rate)


Speed Up?

Despite my initial efforts, my very rough timing measurements don't show I've sped things up any.  But I do think the code has been cleaned up significantly.

Next step in my efforts will be to split this up into two parts:


computing a grid of Mandelbrot colour index values
drawing the image (using the computed grid of colour index values) with turtle graphics


Then, I can get more accurate timing information for each section.

Step 1 is open to parallel processing (Python threads won't help, due to the Global Interpreter Lock (GIL), but using processes should help), but that will only help if it is the large consumer of time.  I fear it is the turtle graphics.