CHAPTER
PURE PYTHON (CPYTHON) IMPLEMENTATION
Below we have the basic pure-python implementation. Typically you’ll be using CPython to run the code (CPython being the Python language running in a C-language interpreter). This is the most common way to run Python code (on Windows you use python.exe, on Linux and Mac it is often just python).
In each example we have a calculate_z function (here it is calculate_z_serial_purepython), this does the hard work calculating the output vector which we’ll display. This is called by a calc function (in this case it is calc_pure_python) which sets up the input and displays the output.
In calc I use a simple routine to prepare the x and y co-ordinates which is compatible between all the techniques we’re using. These co-ordinates are appended to the array q as complex numbers. We also initialise z as an array of the same length using complex(0,0). The motivation here is to setup some input data that is non-trivial which might match your own input in a real-world problem.
For my examples I used a 500 by 500 pixel plot with 1000 maximum iterations. Setting w and h to 1000 and using the default x1, x2, y1, y2 space we have a 500 by 500 pixel space that needs to be calculated. This means that z and q are 250,000 elements in length. Using a complex datatype (16 bytes) we have a total of 16 bytes * 250,000 items * 2 arrays == 8,000,000 bytes (i.e. roughly 8MB of input data).
In the pure Python implementation on a core 2 duo MacBook using CPython 2.7.2 it takes roughly 52 seconds to solve this task. We run it using:
>> python pure_python.py 1000 1000
If you have PIL and numpy installed then you’ll get the graphical plot.
NOTE that the first argument is 1000 and this results in a 500 by 500 pixel plot. This is confusing (and is based on inherited code that I should have fixed...) - I’ll fix the *2 oddness in a future version of this document. For now I’m more interested in writing this up before I’m back from EuroPython!
# \python\pure_python.py
import sys
import datetime
# area of space to investigate
x1, x2, y1, y2=-2.13,0.77,-1.3,1.3
# Original code, prints progress (because it is slow)
# Uses complex datatype
def calculate_z_serial_purepython(q, maxiter, z):
"""Pure python with complex datatype, iterating over list of q and z"""
output=[0] * len(q)
for i in range(len(q)):
if i%1000==0:
# print out some progress info since it is so slow...
print "%0.2f%% complete"%(1.0/len(q)* i * 100)
for iteration in range(maxiter):
z[i]=z[i] * z[i]+q[i]
if abs(z[i])>2.0:
output[i]=iteration
break
return output
def calc_pure_python(show_output):
# make a list of x and y values which will represent q
# xx and yy are the co-ordinates, for the default configuration they’ll look like:
# if we have a 500x500 plot
# xx = [-2.13, -2.1242, -2.1184000000000003, ..., 0.7526000000000064, 0.7584000000000064, 0.7642000000000064]
# yy = [1.3, 1.2948, 1.2895999999999999, ..., -1.2844000000000058, -1.2896000000000059, -1.294800000000006]
x_step=(float(x2-x1)/float(w))* 2
y_step=(float(y1-y2)/float(h)) * 2
x=[]
y=[]
ycoord=y2
while ycoord>y1:
y.append(ycoord)
ycoord+=y_step
xcoord=x1
while xcoord<x2:
x.append(xcoord)
xcoord+=x_step
q=[]
for ycoord in y:
for xcoord in x:
q.append(complex(xcoord,ycoord))
z=[0+0j] *len(q)
print "Total elements:",len(z)
start_time=datetime.datetime.now()
output=calculate_z_serial_purepython(q, maxiter, z)
end_time=datetime.datetime.now()
secs=end_time-start_time
print "Main took", secs
validation_sum=sum(output)
print "Total sum of elements (for validation):", validation_sum
if show_output:
try:
import Image
import numpy as nm
output=nm.array(output)
output=(output+(256 * output)+(256 ** 2)* output)* 8
im=Image.new("RGB", (w/2, h/2))
im.fromstring (output.tostring(),"raw","RGBX",0,-1)
im.show()
except ImportError as err:
# Bail gracefully if we’re using PyPy
print "Couldn’t import Image or numpy:",str(err)
if __name__=="__main__":
# get width, height and max iterations from cmd line
# ’python mandelbrot_pypy.py 100 300’
w=int(sys.argv[1]) # e.g. 100
h=int(sys.argv[1]) # e.g. 100
maxiter=int(sys.argv[2]) # e.g. 300
# we can show_output for Python, not for PyPy
calc_pure_python(True)
When you run it you’ll also see a validation sum - this is the summation of all the values in the output list, if this is the same between executions then your program’s math is progressing in exactly the same way (if it is different then something different is happening!). This is very useful when you’re changing one form of the code into another - it should always produce the same validation sum.