In [ ]:
%load_ext cythonmagic
In [ ]:
import numpy as np
from time import time
def kernel(z, c, lim, cutoff=1e6):
''' Computes the number, `n`, of iterations necessary such that
|z_n| > `lim`, where `z_n = z_{n-1}**2 + c`.
'''
count = 0
while abs(z) < lim and count < cutoff:
z = z * z + c
count += 1
return count
def compute_julia(c, N, bound=2, lim=1000., kernel=kernel):
''' Pure Python calculation of the Julia set for a given `c`. No NumPy
array operations are used.
'''
julia = np.empty((N, N), dtype=np.uint32)
grid_x = np.linspace(-bound, bound, N)
grid_y = grid_x * 1j
c = complex(c)
t0 = time()
for i, x in enumerate(grid_x):
for j, y in enumerate(grid_y):
julia[i,j] = kernel(x+y, c, lim)
return julia, time() - t0
In [ ]:
import pylab as pl
def plot_julia(kwargs, compute_julia):
''' Given parameters dict in `kwargs` and a function to compute the Julia
set (`compute_julia`), plots the resulting Julia set with appropriately
labeled axes.
'''
kwargs = kwargs.copy()
def _plotter(kwargs):
bound = kwargs['bound']
julia, time = compute_julia(**kwargs)
print "execution time (s):", time
julia = np.log(julia)
pl.imshow(julia,
interpolation='nearest',
extent=(-bound, bound)*2)
pl.colorbar()
title = r"Julia set for $C={0.real:5.3f}+{0.imag:5.3f}i$ $[{1}\times{1}]$"
pl.title(title.format(kwargs['c'], kwargs['N']))
pl.xlabel("$Re(z)$")
pl.ylabel("$Im(z)$")
pl.figure(figsize=(14, 12))
cvals = [0.285+0.01j, -0.1+0.651j, -0.4+0.6j, -0.8+0.156j]
subplots = ['221', '222', '223', '224' ]
for c, sp in zip(cvals, subplots):
kwargs.update(c=c)
pl.subplot(sp)
_plotter(kwargs)
pl.show()
In [ ]:
kwargs = dict(N=100, bound=1.5)
plot_julia(kwargs, compute_julia)
In [ ]:
%%cython
from time import time
import numpy as np
cimport cython
cimport numpy as cnp
ctypedef double complex cpx_t
ctypedef double real_t
cdef inline real_t cabs_sq(cpx_t z) nogil:
''' Helper inline function, computes the square of the abs. value of the
complex number `z`.
'''
return z.real * z.real + z.imag * z.imag
cpdef unsigned int kernel(cpx_t z,
cpx_t c,
real_t lim,
real_t cutoff=1e6) nogil:
''' Cython implementation of the kernel computation.
This is implemented so that no C-API calls are made inside the function
body. Even still, there is some overhead as compared with a pure C
implementation.
'''
cdef unsigned int count = 0
cdef real_t lim_sq = lim * lim
while cabs_sq(z) < lim_sq and count < cutoff:
z = z * z + c
count += 1
return count
@cython.boundscheck(False)
@cython.wraparound(False)
def compute_julia_opt(cpx_t c,
unsigned int N,
real_t bound=1.5,
real_t lim=1000.):
'''
Cython `compute_julia()` implementation with Numpy array buffer
declarations and appropriate compiler directives. The body of this
function is nearly identical to the `compute_julia_no_opt()` function.
'''
cdef cnp.ndarray[cnp.uint32_t, ndim=2, mode='c'] julia
cdef cnp.ndarray[real_t, ndim=1, mode='c'] grid
cdef unsigned int i, j
cdef real_t x, y
julia = np.empty((N, N), dtype=np.uint32)
grid = np.linspace(-bound, bound, N)
t0 = time()
for i in range(N):
x = grid[i]
for j in range(N):
y = grid[j]
julia[i,j] = kernel(x+y*1j, c, lim)
return julia, time() - t0
In [ ]:
kwargs = dict(N=1000, bound=1.5)
plot_julia(kwargs, compute_julia_opt)
In [ ]: