Skip to content

Commit e46df11

Browse files
committed
Merge branch 'development' into feature/update_cython_slides
2 parents de8df40 + 3314ff8 commit e46df11

37 files changed

+641
-127
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
average.c
2+
average_pure.c
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
VERSION = cpython-34m
1+
VERSION = cpython-311-x86_64-linux-gnu
22
AVERAGE_LIB = average.$(VERSION).so
3+
AVERAGE_PURE_LIB = average_pure.$(VERSION).so
34

4-
all: $(AVERAGE_LIB)
5+
all: $(AVERAGE_LIB) $(AVERAGE_PURE_LIB)
56

67
$(AVERAGE_LIB): average.pyx
78
python setup.py build_ext --inplace
89

10+
$(AVERAGE_PURE_LIB): average_pure.py
11+
python setup.py build_ext --inplace
12+
913
clean:
1014
python setup.py clean
11-
rm -f average.c $(AVERAGE_LIB)
15+
$(RM) average.c average_pure.c $(AVERAGE_LIB) $(AVERAGE_PURE_LIB)
16+
$(RM) -r build

source-code/cython/Exceptions/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
Error handling in Cython code.
33

44
## What is it?
5-
1. `average.pyx`: code to be compiled using Cython, implements two
5+
1. `average.pyx`: Cython code to be compiled using Cython, implements two
66
functions to compute the average of an array slice, one with, the
77
other without error handling.
8+
1. `average_pure.py`: pure Python code to be compiled using Cython,
9+
implements two functions to compute the average of an array slice,
10+
one with, the other without error handling.
811
1. `setup.py`: Python build script.
912
1. `Makefile`: make file to build the extension.
1013
1. `compute_average.py`: script to load the compiled module and call the
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import cython
2+
3+
4+
def average(data, m=0, n=None):
5+
if n is None:
6+
n = len(data)
7+
return _average(memoryview(data), m, n)
8+
9+
def average_no_except(data, m=0, n=None):
10+
if n is None:
11+
n = len(data)
12+
return _average_no_except(memoryview(data), m, n)
13+
14+
@cython.cfunc
15+
@cython.exceptval(-1.0, check=True)
16+
def _average(data, m: cython.int=0, n: cython.int=-1) -> cython.double:
17+
i: cython.int
18+
mean: cython.double = 0.0
19+
for i in range(m, n):
20+
mean += data[i]
21+
return mean/(n - m + 1)
22+
23+
@cython.cfunc
24+
def _average_no_except(data, m: cython.int=0, n: cython.int=-1) -> cython.double:
25+
i: cython.int
26+
mean: cython.double = 0.0
27+
for i in range(m, n):
28+
mean += data[i]
29+
return mean/(n - m + 1)

source-code/cython/Exceptions/compute_average.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@
22

33
from argparse import ArgumentParser
44
import array
5-
import average
65

7-
8-
size = 10
6+
DEFAULT_SIZE = 10
97

108
arg_parser = ArgumentParser(description='test Cython errors')
119
arg_parser.add_argument('--m', type=int, default=0, help='lower bound')
12-
arg_parser.add_argument('--n', type=int, default=size, help='upper bound')
10+
arg_parser.add_argument('--n', type=int, default=DEFAULT_SIZE,
11+
help='upper bound')
12+
arg_parser.add_argument('--size', type=int, default=DEFAULT_SIZE,
13+
help='array size')
14+
arg_parser.add_argument('--implementation', choices=['pure', 'cython'],
15+
default='cython', help='implementation to use')
1316
options = arg_parser.parse_args()
14-
data = array.array('d', list(range(size)))
17+
if options.implementation == 'cython':
18+
from average import average, average_no_except
19+
elif options.implementation == 'pure':
20+
from average_pure import average, average_no_except
21+
data = array.array('d', list(range(options.size)))
1522
print('with except:')
1623
try:
17-
print(average.average(data, options.m, options.n))
24+
print(average(data, options.m, options.n))
1825
except Exception as e:
1926
print('caught exception {0}: {1}'.format(str(e.__class__), str(e)))
2027
print('without except:')
2128
try:
22-
print(average.average_no_except(data, options.m, options.n))
29+
print(average_no_except(data, options.m, options.n))
2330
print('no exception caught')
2431
except Exception as e:
2532
print('caught exception {0}: {1}'.format(e.__class__, str(e)))

source-code/cython/Exceptions/setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
from Cython.Build import cythonize
55

66
setup(
7-
ext_modules=cythonize('average.pyx')
7+
ext_modules=cythonize(['average.pyx', 'average_pure.py'],
8+
language_level='3str')
89
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
VERSION = cpython-311-x86_64-linux-gnu
2+
CONVOLUTION_LIB = convolution_cython.$(VERSION).so
3+
CONVOLUTION_INDEXED_LIB = convolution_cython_indexed.$(VERSION).so
4+
CONVOLUTION_NO_CHECKS_LIB = convolution_cython_no_checks.$(VERSION).so
5+
6+
all: $(CONVOLUTION_LIB) $(CONVOLUTION_INDEXED_LIB) $(CONVOLUTION_NO_CHECKS_LIB)
7+
8+
$(CONVOLUTION_LIB): convolution.pyx
9+
python setup.py build_ext --inplace
10+
11+
$(CONVOLUTION_LIB): convolution_indexed.pyx
12+
python setup.py build_ext --inplace
13+
14+
$(CONVOLUTION_LIB): convolution_no_checks.pyx
15+
python setup.py build_ext --inplace
16+
17+
clean:
18+
python setup.py clean
19+
$(RM) $(wildcard *.c) $(wildcard *.so)
20+
$(RM) -r build
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Convolution
2+
3+
Illustration of how to improve perfomance when using numpy arrays in Cython
4+
by doing indexing right. The algorithm used is a naive implementation of
5+
convolution.
6+
7+
8+
## What is it?
9+
10+
1. `convolution.py`: Python implementation of the algorithm (uses numpy).
11+
1. `convolution.pyx`: Cython implementation that adds types.
12+
1. `convolution_indexed.pyx`: Cython implementation that adds
13+
numpy array element types and indexing information.
14+
1. `convolution_no_checks.pyx`: Cython implementation that adds
15+
function decorator to avoid index checks for numpy arrays.
16+
1. `setup.py`: script to build the extensions.
17+
1. `Makefile`: make file to conveniently build the extensions.
18+
1. `driver.py`: Python scripts to run benchmark tests on the various
19+
implementations.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import numpy as np
2+
3+
4+
def convolve(input, kernel):
5+
'''Naive convolution of matrices input and kernel
6+
7+
Parameters
8+
----------
9+
input : numpy.ndarray
10+
input matrix
11+
kernel : numpy.ndarray
12+
filter kernel matrix, dimensions must be odd
13+
14+
Returns
15+
-------
16+
output : numpy.ndarray
17+
output matrix, it is not cropped
18+
19+
Raises
20+
------
21+
ValueError
22+
if dimensions of kernel are not odd
23+
'''
24+
if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1:
25+
raise ValueError("Only odd dimensions on filter supported")
26+
# s_mid and t_mid are number of pixels between the center pixel
27+
# and the edge, ie for a 5x5 filter they will be 2.
28+
#
29+
# The output size is calculated by adding s_mid, t_mid to each
30+
# side of the dimensions of the input image.
31+
s_mid = kernel.shape[0] // 2
32+
t_mid = kernel.shape[1] // 2
33+
x_max = input.shape[0] + 2 * s_mid
34+
y_max = input.shape[1] + 2 * t_mid
35+
# Allocate result image.
36+
output = np.zeros([x_max, y_max], dtype=input.dtype)
37+
# Do convolution
38+
for x in range(x_max):
39+
for y in range(y_max):
40+
# Calculate pixel value for h at (x,y). Sum one component
41+
# for each pixel (s, t) of the filter kernel.
42+
s_from = max(s_mid - x, -s_mid)
43+
s_to = min((x_max - x) - s_mid, s_mid + 1)
44+
t_from = max(t_mid - y, -t_mid)
45+
t_to = min((y_max - y) - t_mid, t_mid + 1)
46+
value = 0
47+
for s in range(s_from, s_to):
48+
for t in range(t_from, t_to):
49+
v = x - s_mid + s
50+
w = y - t_mid + t
51+
value += kernel[s_mid - s, t_mid - t]*input[v, w]
52+
output[x, y] = value
53+
return output
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import numpy as np
2+
cimport numpy as cnp
3+
cnp.import_array()
4+
5+
DTYPE = np.float64
6+
ctypedef cnp.float64_t DTYPE_t
7+
8+
def convolve(cnp.ndarray input, cnp.ndarray kernel):
9+
'''Naive convolution of matrices input and kernel
10+
11+
Parameters
12+
----------
13+
input : numpy.ndarray
14+
input matrix
15+
kernel : numpy.ndarray
16+
filter kernel matrix, dimensions must be odd
17+
18+
Returns
19+
-------
20+
output : numpy.ndarray
21+
output matrix, it is not cropped
22+
23+
Raises
24+
------
25+
ValueError
26+
if dimensions of kernel are not odd
27+
'''
28+
if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1:
29+
raise ValueError("Only odd dimensions on filter supported")
30+
assert input.dtype == DTYPE and kernel.dtype == DTYPE
31+
# s_mid and t_mid are number of pixels between the center pixel
32+
# and the edge, ie for a 5x5 filter they will be 2.
33+
#
34+
# The output size is calculated by adding s_mid, t_mid to each
35+
# side of the dimensions of the input image.
36+
cdef int s_mid = kernel.shape[0] // 2
37+
cdef int t_mid = kernel.shape[1] // 2
38+
cdef int x_max = input.shape[0] + 2 * s_mid
39+
cdef int y_max = input.shape[1] + 2 * t_mid
40+
# Allocate result image.
41+
cdef cnp.ndarray output = np.zeros([x_max, y_max], dtype=input.dtype)
42+
# Do convolution
43+
cdef int x, y, s, t, v, w
44+
cdef int s_from, s_to, t_from, t_to
45+
cdef DTYPE_t value
46+
for x in range(x_max):
47+
for y in range(y_max):
48+
# Calculate pixel value for h at (x,y). Sum one component
49+
# for each pixel (s, t) of the filter kernel.
50+
s_from = max(s_mid - x, -s_mid)
51+
s_to = min((x_max - x) - s_mid, s_mid + 1)
52+
t_from = max(t_mid - y, -t_mid)
53+
t_to = min((y_max - y) - t_mid, t_mid + 1)
54+
value = 0
55+
for s in range(s_from, s_to):
56+
for t in range(t_from, t_to):
57+
v = x - s_mid + s
58+
w = y - t_mid + t
59+
value += kernel[s_mid - s, t_mid - t]*input[v, w]
60+
output[x, y] = value
61+
return output
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import numpy as np
2+
cimport numpy as cnp
3+
cnp.import_array()
4+
5+
DTYPE = np.float64
6+
ctypedef cnp.float64_t DTYPE_t
7+
8+
def convolve(cnp.ndarray[DTYPE_t, ndim=2] input, cnp.ndarray[DTYPE_t, ndim=2] kernel):
9+
'''Naive convolution of matrices input and kernel
10+
11+
Parameters
12+
----------
13+
input : numpy.ndarray
14+
input matrix
15+
kernel : numpy.ndarray
16+
filter kernel matrix, dimensions must be odd
17+
18+
Returns
19+
-------
20+
output : numpy.ndarray
21+
output matrix, it is not cropped
22+
23+
Raises
24+
------
25+
ValueError
26+
if dimensions of kernel are not odd
27+
'''
28+
if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1:
29+
raise ValueError("Only odd dimensions on filter supported")
30+
assert input.dtype == DTYPE and kernel.dtype == DTYPE
31+
# s_mid and t_mid are number of pixels between the center pixel
32+
# and the edge, ie for a 5x5 filter they will be 2.
33+
#
34+
# The output size is calculated by adding s_mid, t_mid to each
35+
# side of the dimensions of the input image.
36+
cdef int s_mid = kernel.shape[0] // 2
37+
cdef int t_mid = kernel.shape[1] // 2
38+
cdef int x_max = input.shape[0] + 2 * s_mid
39+
cdef int y_max = input.shape[1] + 2 * t_mid
40+
# Allocate result image.
41+
cdef cnp.ndarray[DTYPE_t, ndim=2] output = np.zeros([x_max, y_max], dtype=input.dtype)
42+
# Do convolution
43+
cdef int x, y, s, t, v, w
44+
cdef int s_from, s_to, t_from, t_to
45+
cdef DTYPE_t value
46+
for x in range(x_max):
47+
for y in range(y_max):
48+
# Calculate pixel value for h at (x,y). Sum one component
49+
# for each pixel (s, t) of the filter kernel.
50+
s_from = max(s_mid - x, -s_mid)
51+
s_to = min((x_max - x) - s_mid, s_mid + 1)
52+
t_from = max(t_mid - y, -t_mid)
53+
t_to = min((y_max - y) - t_mid, t_mid + 1)
54+
value = 0
55+
for s in range(s_from, s_to):
56+
for t in range(t_from, t_to):
57+
v = x - s_mid + s
58+
w = y - t_mid + t
59+
value += kernel[s_mid - s, t_mid - t]*input[v, w]
60+
output[x, y] = value
61+
return output
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
cimport cython
2+
import numpy as np
3+
cimport numpy as cnp
4+
cnp.import_array()
5+
6+
DTYPE = np.float64
7+
ctypedef cnp.float64_t DTYPE_t
8+
9+
@cython.boundscheck(False)
10+
@cython.wraparound(False)
11+
def convolve(cnp.ndarray[DTYPE_t, ndim=2] input, cnp.ndarray[DTYPE_t, ndim=2] kernel):
12+
'''Naive convolution of matrices input and kernel
13+
14+
Parameters
15+
----------
16+
input : numpy.ndarray
17+
input matrix
18+
kernel : numpy.ndarray
19+
filter kernel matrix, dimensions must be odd
20+
21+
Returns
22+
-------
23+
output : numpy.ndarray
24+
output matrix, it is not cropped
25+
26+
Raises
27+
------
28+
ValueError
29+
if dimensions of kernel are not odd
30+
'''
31+
if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1:
32+
raise ValueError("Only odd dimensions on filter supported")
33+
assert input.dtype == DTYPE and kernel.dtype == DTYPE
34+
# s_mid and t_mid are number of pixels between the center pixel
35+
# and the edge, ie for a 5x5 filter they will be 2.
36+
#
37+
# The output size is calculated by adding s_mid, t_mid to each
38+
# side of the dimensions of the input image.
39+
cdef int s_mid = kernel.shape[0] // 2
40+
cdef int t_mid = kernel.shape[1] // 2
41+
cdef int x_max = input.shape[0] + 2 * s_mid
42+
cdef int y_max = input.shape[1] + 2 * t_mid
43+
# Allocate result image.
44+
cdef cnp.ndarray[DTYPE_t, ndim=2] output = np.zeros([x_max, y_max], dtype=input.dtype)
45+
# Do convolution
46+
cdef int x, y, s, t, v, w
47+
cdef int s_from, s_to, t_from, t_to
48+
cdef DTYPE_t value
49+
for x in range(x_max):
50+
for y in range(y_max):
51+
# Calculate pixel value for h at (x,y). Sum one component
52+
# for each pixel (s, t) of the filter kernel.
53+
s_from = max(s_mid - x, -s_mid)
54+
s_to = min((x_max - x) - s_mid, s_mid + 1)
55+
t_from = max(t_mid - y, -t_mid)
56+
t_to = min((y_max - y) - t_mid, t_mid + 1)
57+
value = 0
58+
for s in range(s_from, s_to):
59+
for t in range(t_from, t_to):
60+
v = x - s_mid + s
61+
w = y - t_mid + t
62+
value += kernel[s_mid - s, t_mid - t]*input[v, w]
63+
output[x, y] = value
64+
return output

0 commit comments

Comments
 (0)