diff --git a/python_for_hpc.pptx b/python_for_hpc.pptx index 62ed640..2437656 100644 Binary files a/python_for_hpc.pptx and b/python_for_hpc.pptx differ diff --git a/source-code/cython/Exceptions/.gitignore b/source-code/cython/Exceptions/.gitignore new file mode 100644 index 0000000..e1f8f88 --- /dev/null +++ b/source-code/cython/Exceptions/.gitignore @@ -0,0 +1,2 @@ +average.c +average_pure.c diff --git a/source-code/cython/Exceptions/Makefile b/source-code/cython/Exceptions/Makefile index 3ff097a..cdcee95 100644 --- a/source-code/cython/Exceptions/Makefile +++ b/source-code/cython/Exceptions/Makefile @@ -1,11 +1,16 @@ -VERSION = cpython-34m +VERSION = cpython-311-x86_64-linux-gnu AVERAGE_LIB = average.$(VERSION).so +AVERAGE_PURE_LIB = average_pure.$(VERSION).so -all: $(AVERAGE_LIB) +all: $(AVERAGE_LIB) $(AVERAGE_PURE_LIB) $(AVERAGE_LIB): average.pyx python setup.py build_ext --inplace +$(AVERAGE_PURE_LIB): average_pure.py + python setup.py build_ext --inplace + clean: python setup.py clean - rm -f average.c $(AVERAGE_LIB) + $(RM) average.c average_pure.c $(AVERAGE_LIB) $(AVERAGE_PURE_LIB) + $(RM) -r build diff --git a/source-code/cython/Exceptions/README.md b/source-code/cython/Exceptions/README.md index ace95f8..a07086e 100644 --- a/source-code/cython/Exceptions/README.md +++ b/source-code/cython/Exceptions/README.md @@ -2,9 +2,12 @@ Error handling in Cython code. ## What is it? -1. `average.pyx`: code to be compiled using Cython, implements two +1. `average.pyx`: Cython code to be compiled using Cython, implements two functions to compute the average of an array slice, one with, the other without error handling. +1. `average_pure.py`: pure Python code to be compiled using Cython, + implements two functions to compute the average of an array slice, + one with, the other without error handling. 1. `setup.py`: Python build script. 1. `Makefile`: make file to build the extension. 1. `compute_average.py`: script to load the compiled module and call the diff --git a/source-code/cython/Exceptions/average_pure.py b/source-code/cython/Exceptions/average_pure.py new file mode 100644 index 0000000..a98d384 --- /dev/null +++ b/source-code/cython/Exceptions/average_pure.py @@ -0,0 +1,29 @@ +import cython + + +def average(data, m=0, n=None): + if n is None: + n = len(data) + return _average(memoryview(data), m, n) + +def average_no_except(data, m=0, n=None): + if n is None: + n = len(data) + return _average_no_except(memoryview(data), m, n) + +@cython.cfunc +@cython.exceptval(-1.0, check=True) +def _average(data, m: cython.int=0, n: cython.int=-1) -> cython.double: + i: cython.int + mean: cython.double = 0.0 + for i in range(m, n): + mean += data[i] + return mean/(n - m + 1) + +@cython.cfunc +def _average_no_except(data, m: cython.int=0, n: cython.int=-1) -> cython.double: + i: cython.int + mean: cython.double = 0.0 + for i in range(m, n): + mean += data[i] + return mean/(n - m + 1) diff --git a/source-code/cython/Exceptions/compute_average.py b/source-code/cython/Exceptions/compute_average.py index f69e81b..95ad4ea 100755 --- a/source-code/cython/Exceptions/compute_average.py +++ b/source-code/cython/Exceptions/compute_average.py @@ -2,24 +2,31 @@ from argparse import ArgumentParser import array -import average - -size = 10 +DEFAULT_SIZE = 10 arg_parser = ArgumentParser(description='test Cython errors') arg_parser.add_argument('--m', type=int, default=0, help='lower bound') -arg_parser.add_argument('--n', type=int, default=size, help='upper bound') +arg_parser.add_argument('--n', type=int, default=DEFAULT_SIZE, + help='upper bound') +arg_parser.add_argument('--size', type=int, default=DEFAULT_SIZE, + help='array size') +arg_parser.add_argument('--implementation', choices=['pure', 'cython'], + default='cython', help='implementation to use') options = arg_parser.parse_args() -data = array.array('d', list(range(size))) +if options.implementation == 'cython': + from average import average, average_no_except +elif options.implementation == 'pure': + from average_pure import average, average_no_except +data = array.array('d', list(range(options.size))) print('with except:') try: - print(average.average(data, options.m, options.n)) + print(average(data, options.m, options.n)) except Exception as e: print('caught exception {0}: {1}'.format(str(e.__class__), str(e))) print('without except:') try: - print(average.average_no_except(data, options.m, options.n)) + print(average_no_except(data, options.m, options.n)) print('no exception caught') except Exception as e: print('caught exception {0}: {1}'.format(e.__class__, str(e))) diff --git a/source-code/cython/Exceptions/setup.py b/source-code/cython/Exceptions/setup.py index c7bcf1f..209583c 100644 --- a/source-code/cython/Exceptions/setup.py +++ b/source-code/cython/Exceptions/setup.py @@ -4,5 +4,6 @@ from Cython.Build import cythonize setup( - ext_modules=cythonize('average.pyx') + ext_modules=cythonize(['average.pyx', 'average_pure.py'], + language_level='3str') ) diff --git a/source-code/cython/Numpy/Convolution/Makefile b/source-code/cython/Numpy/Convolution/Makefile new file mode 100644 index 0000000..3633ab4 --- /dev/null +++ b/source-code/cython/Numpy/Convolution/Makefile @@ -0,0 +1,20 @@ +VERSION = cpython-311-x86_64-linux-gnu +CONVOLUTION_LIB = convolution_cython.$(VERSION).so +CONVOLUTION_INDEXED_LIB = convolution_cython_indexed.$(VERSION).so +CONVOLUTION_NO_CHECKS_LIB = convolution_cython_no_checks.$(VERSION).so + +all: $(CONVOLUTION_LIB) $(CONVOLUTION_INDEXED_LIB) $(CONVOLUTION_NO_CHECKS_LIB) + +$(CONVOLUTION_LIB): convolution.pyx + python setup.py build_ext --inplace + +$(CONVOLUTION_LIB): convolution_indexed.pyx + python setup.py build_ext --inplace + +$(CONVOLUTION_LIB): convolution_no_checks.pyx + python setup.py build_ext --inplace + +clean: + python setup.py clean + $(RM) $(wildcard *.c) $(wildcard *.so) + $(RM) -r build diff --git a/source-code/cython/Numpy/Convolution/README.md b/source-code/cython/Numpy/Convolution/README.md new file mode 100644 index 0000000..eace28e --- /dev/null +++ b/source-code/cython/Numpy/Convolution/README.md @@ -0,0 +1,19 @@ +# Convolution + +Illustration of how to improve perfomance when using numpy arrays in Cython +by doing indexing right. The algorithm used is a naive implementation of +convolution. + + +## What is it? + +1. `convolution.py`: Python implementation of the algorithm (uses numpy). +1. `convolution.pyx`: Cython implementation that adds types. +1. `convolution_indexed.pyx`: Cython implementation that adds + numpy array element types and indexing information. +1. `convolution_no_checks.pyx`: Cython implementation that adds + function decorator to avoid index checks for numpy arrays. +1. `setup.py`: script to build the extensions. +1. `Makefile`: make file to conveniently build the extensions. +1. `driver.py`: Python scripts to run benchmark tests on the various + implementations. diff --git a/source-code/cython/Numpy/Convolution/convolution.py b/source-code/cython/Numpy/Convolution/convolution.py new file mode 100644 index 0000000..53639c0 --- /dev/null +++ b/source-code/cython/Numpy/Convolution/convolution.py @@ -0,0 +1,53 @@ +import numpy as np + + +def convolve(input, kernel): + '''Naive convolution of matrices input and kernel + + Parameters + ---------- + input : numpy.ndarray + input matrix + kernel : numpy.ndarray + filter kernel matrix, dimensions must be odd + + Returns + ------- + output : numpy.ndarray + output matrix, it is not cropped + + Raises + ------ + ValueError + if dimensions of kernel are not odd + ''' + if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1: + raise ValueError("Only odd dimensions on filter supported") + # s_mid and t_mid are number of pixels between the center pixel + # and the edge, ie for a 5x5 filter they will be 2. + # + # The output size is calculated by adding s_mid, t_mid to each + # side of the dimensions of the input image. + s_mid = kernel.shape[0] // 2 + t_mid = kernel.shape[1] // 2 + x_max = input.shape[0] + 2 * s_mid + y_max = input.shape[1] + 2 * t_mid + # Allocate result image. + output = np.zeros([x_max, y_max], dtype=input.dtype) + # Do convolution + for x in range(x_max): + for y in range(y_max): + # Calculate pixel value for h at (x,y). Sum one component + # for each pixel (s, t) of the filter kernel. + s_from = max(s_mid - x, -s_mid) + s_to = min((x_max - x) - s_mid, s_mid + 1) + t_from = max(t_mid - y, -t_mid) + t_to = min((y_max - y) - t_mid, t_mid + 1) + value = 0 + for s in range(s_from, s_to): + for t in range(t_from, t_to): + v = x - s_mid + s + w = y - t_mid + t + value += kernel[s_mid - s, t_mid - t]*input[v, w] + output[x, y] = value + return output diff --git a/source-code/cython/Numpy/Convolution/convolution.pyx b/source-code/cython/Numpy/Convolution/convolution.pyx new file mode 100644 index 0000000..434c477 --- /dev/null +++ b/source-code/cython/Numpy/Convolution/convolution.pyx @@ -0,0 +1,61 @@ +import numpy as np +cimport numpy as cnp +cnp.import_array() + +DTYPE = np.float64 +ctypedef cnp.float64_t DTYPE_t + +def convolve(cnp.ndarray input, cnp.ndarray kernel): + '''Naive convolution of matrices input and kernel + + Parameters + ---------- + input : numpy.ndarray + input matrix + kernel : numpy.ndarray + filter kernel matrix, dimensions must be odd + + Returns + ------- + output : numpy.ndarray + output matrix, it is not cropped + + Raises + ------ + ValueError + if dimensions of kernel are not odd + ''' + if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1: + raise ValueError("Only odd dimensions on filter supported") + assert input.dtype == DTYPE and kernel.dtype == DTYPE + # s_mid and t_mid are number of pixels between the center pixel + # and the edge, ie for a 5x5 filter they will be 2. + # + # The output size is calculated by adding s_mid, t_mid to each + # side of the dimensions of the input image. + cdef int s_mid = kernel.shape[0] // 2 + cdef int t_mid = kernel.shape[1] // 2 + cdef int x_max = input.shape[0] + 2 * s_mid + cdef int y_max = input.shape[1] + 2 * t_mid + # Allocate result image. + cdef cnp.ndarray output = np.zeros([x_max, y_max], dtype=input.dtype) + # Do convolution + cdef int x, y, s, t, v, w + cdef int s_from, s_to, t_from, t_to + cdef DTYPE_t value + for x in range(x_max): + for y in range(y_max): + # Calculate pixel value for h at (x,y). Sum one component + # for each pixel (s, t) of the filter kernel. + s_from = max(s_mid - x, -s_mid) + s_to = min((x_max - x) - s_mid, s_mid + 1) + t_from = max(t_mid - y, -t_mid) + t_to = min((y_max - y) - t_mid, t_mid + 1) + value = 0 + for s in range(s_from, s_to): + for t in range(t_from, t_to): + v = x - s_mid + s + w = y - t_mid + t + value += kernel[s_mid - s, t_mid - t]*input[v, w] + output[x, y] = value + return output diff --git a/source-code/cython/Numpy/Convolution/convolution_indexed.pyx b/source-code/cython/Numpy/Convolution/convolution_indexed.pyx new file mode 100644 index 0000000..2e8830a --- /dev/null +++ b/source-code/cython/Numpy/Convolution/convolution_indexed.pyx @@ -0,0 +1,61 @@ +import numpy as np +cimport numpy as cnp +cnp.import_array() + +DTYPE = np.float64 +ctypedef cnp.float64_t DTYPE_t + +def convolve(cnp.ndarray[DTYPE_t, ndim=2] input, cnp.ndarray[DTYPE_t, ndim=2] kernel): + '''Naive convolution of matrices input and kernel + + Parameters + ---------- + input : numpy.ndarray + input matrix + kernel : numpy.ndarray + filter kernel matrix, dimensions must be odd + + Returns + ------- + output : numpy.ndarray + output matrix, it is not cropped + + Raises + ------ + ValueError + if dimensions of kernel are not odd + ''' + if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1: + raise ValueError("Only odd dimensions on filter supported") + assert input.dtype == DTYPE and kernel.dtype == DTYPE + # s_mid and t_mid are number of pixels between the center pixel + # and the edge, ie for a 5x5 filter they will be 2. + # + # The output size is calculated by adding s_mid, t_mid to each + # side of the dimensions of the input image. + cdef int s_mid = kernel.shape[0] // 2 + cdef int t_mid = kernel.shape[1] // 2 + cdef int x_max = input.shape[0] + 2 * s_mid + cdef int y_max = input.shape[1] + 2 * t_mid + # Allocate result image. + cdef cnp.ndarray[DTYPE_t, ndim=2] output = np.zeros([x_max, y_max], dtype=input.dtype) + # Do convolution + cdef int x, y, s, t, v, w + cdef int s_from, s_to, t_from, t_to + cdef DTYPE_t value + for x in range(x_max): + for y in range(y_max): + # Calculate pixel value for h at (x,y). Sum one component + # for each pixel (s, t) of the filter kernel. + s_from = max(s_mid - x, -s_mid) + s_to = min((x_max - x) - s_mid, s_mid + 1) + t_from = max(t_mid - y, -t_mid) + t_to = min((y_max - y) - t_mid, t_mid + 1) + value = 0 + for s in range(s_from, s_to): + for t in range(t_from, t_to): + v = x - s_mid + s + w = y - t_mid + t + value += kernel[s_mid - s, t_mid - t]*input[v, w] + output[x, y] = value + return output diff --git a/source-code/cython/Numpy/Convolution/convolution_no_checks.pyx b/source-code/cython/Numpy/Convolution/convolution_no_checks.pyx new file mode 100644 index 0000000..39bc494 --- /dev/null +++ b/source-code/cython/Numpy/Convolution/convolution_no_checks.pyx @@ -0,0 +1,64 @@ +cimport cython +import numpy as np +cimport numpy as cnp +cnp.import_array() + +DTYPE = np.float64 +ctypedef cnp.float64_t DTYPE_t + +@cython.boundscheck(False) +@cython.wraparound(False) +def convolve(cnp.ndarray[DTYPE_t, ndim=2] input, cnp.ndarray[DTYPE_t, ndim=2] kernel): + '''Naive convolution of matrices input and kernel + + Parameters + ---------- + input : numpy.ndarray + input matrix + kernel : numpy.ndarray + filter kernel matrix, dimensions must be odd + + Returns + ------- + output : numpy.ndarray + output matrix, it is not cropped + + Raises + ------ + ValueError + if dimensions of kernel are not odd + ''' + if kernel.shape[0] % 2 != 1 or kernel.shape[1] % 2 != 1: + raise ValueError("Only odd dimensions on filter supported") + assert input.dtype == DTYPE and kernel.dtype == DTYPE + # s_mid and t_mid are number of pixels between the center pixel + # and the edge, ie for a 5x5 filter they will be 2. + # + # The output size is calculated by adding s_mid, t_mid to each + # side of the dimensions of the input image. + cdef int s_mid = kernel.shape[0] // 2 + cdef int t_mid = kernel.shape[1] // 2 + cdef int x_max = input.shape[0] + 2 * s_mid + cdef int y_max = input.shape[1] + 2 * t_mid + # Allocate result image. + cdef cnp.ndarray[DTYPE_t, ndim=2] output = np.zeros([x_max, y_max], dtype=input.dtype) + # Do convolution + cdef int x, y, s, t, v, w + cdef int s_from, s_to, t_from, t_to + cdef DTYPE_t value + for x in range(x_max): + for y in range(y_max): + # Calculate pixel value for h at (x,y). Sum one component + # for each pixel (s, t) of the filter kernel. + s_from = max(s_mid - x, -s_mid) + s_to = min((x_max - x) - s_mid, s_mid + 1) + t_from = max(t_mid - y, -t_mid) + t_to = min((y_max - y) - t_mid, t_mid + 1) + value = 0 + for s in range(s_from, s_to): + for t in range(t_from, t_to): + v = x - s_mid + s + w = y - t_mid + t + value += kernel[s_mid - s, t_mid - t]*input[v, w] + output[x, y] = value + return output diff --git a/source-code/cython/Numpy/Convolution/driver.py b/source-code/cython/Numpy/Convolution/driver.py new file mode 100755 index 0000000..6345a4c --- /dev/null +++ b/source-code/cython/Numpy/Convolution/driver.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# script to run various implementations of the convolution filter +# to compare their performance. +# The script has the following input parameters: +# --input_x: width of the input matrix +# --input_y: height of the input matrix +# --kernel_x: width of the kernel matrix +# --kernel_y: height of the kernel matrix +# --iterations: number of iterations to run +# --implementation: which implementation to run + +import argparse +import numpy as np +import timeit +import sys + +def main(): + parser = argparse.ArgumentParser(description='Run convolution filter') + parser.add_argument('--input_x', type=int, default=100, + help='width of the input matrix') + parser.add_argument('--input_y', type=int, default=100, + help='height of the input matrix') + parser.add_argument('--kernel_x', type=int, default=9, + help='width of the kernel matrix') + parser.add_argument('--kernel_y', type=int, default=9, + help='height of the kernel matrix') + parser.add_argument('--iterations', type=int, default=1, + help='number of iterations to run') + parser.add_argument('--implementation', type=str, + choices=['python', 'cython', 'cython_indexed', 'cython_no_checks'], + default='python', + help='which implementation to run') + parser.add_argument('--check', action='store_true', + help='check the results') + args = parser.parse_args() + + # Create input and kernel matrices + input = np.random.rand(args.input_x, args.input_y) + kernel = np.random.rand(args.kernel_x, args.kernel_y) + + # Run the requested implementation + if args.implementation == 'python': + from convolution import convolve + elif args.implementation == 'cython': + from convolution_cython import convolve + elif args.implementation == 'cython_indexed': + from convolution_cython_indexed import convolve + elif args.implementation == 'cython_no_checks': + from convolution_cython_no_checks import convolve + + # Measure the execution time + time = timeit.timeit(lambda: convolve(input, kernel), number=args.iterations) + print(f'Time: {time/args.iterations} s per iteration, {args.iterations} iterations') + + # Check the results + if args.check: + from convolution import convolve as convolve_python + result_python = convolve_python(input, kernel) + result = convolve(input, kernel) + if np.allclose(result_python, result): + print('Results are correct') + else: + print('Results are incorrect') + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/source-code/cython/Numpy/Convolution/setup.py b/source-code/cython/Numpy/Convolution/setup.py new file mode 100644 index 0000000..6a3e172 --- /dev/null +++ b/source-code/cython/Numpy/Convolution/setup.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +from distutils.core import setup +from Cython.Build import cythonize +from pathlib import Path +from distutils.extension import Extension + +home_dir = str(Path.home()) + +extensions = [ + Extension( + "convolution_cython", + ["convolution.pyx"], + include_dirs=[f'{home_dir}/mambaforge/envs/python_for_hpc/lib/python3.11/site-packages/numpy/core/include/'], + ), + Extension( + "convolution_cython_indexed", + ["convolution_indexed.pyx"], + include_dirs=[f'{home_dir}/mambaforge/envs/python_for_hpc/lib/python3.11/site-packages/numpy/core/include/'], + ), + Extension( + "convolution_cython_no_checks", + ["convolution_no_checks.pyx"], + include_dirs=[f'{home_dir}/mambaforge/envs/python_for_hpc/lib/python3.11/site-packages/numpy/core/include/'], + ), +] + +setup( + ext_modules=cythonize(extensions, + language_level='3str') +) diff --git a/source-code/cython/Numpy/README.md b/source-code/cython/Numpy/README.md index 53de525..5444e18 100644 --- a/source-code/cython/Numpy/README.md +++ b/source-code/cython/Numpy/README.md @@ -24,3 +24,5 @@ to `np.sum`. the pure Python Cython implementation of the array sum. 1. `Makefile`: make file for building the cython extension and profiling the three implementations. +1. `Convolution`: illustration of using numpy arrays directly in Cython + code. diff --git a/source-code/cython/Pointers/.gitignore b/source-code/cython/Pointers/.gitignore new file mode 100644 index 0000000..0aef374 --- /dev/null +++ b/source-code/cython/Pointers/.gitignore @@ -0,0 +1,2 @@ +pointers_cython.c +pointers_pure.c diff --git a/source-code/cython/Pointers/Makefile b/source-code/cython/Pointers/Makefile index 19027be..9359b0a 100644 --- a/source-code/cython/Pointers/Makefile +++ b/source-code/cython/Pointers/Makefile @@ -1,11 +1,16 @@ -VERSION = cpython-34m -POINTERS_LIB = pointers.$(VERSION).so +VERSION = cpython-311-x86_64-linux-gnu +CYTHON_LIB = pointers_cython.$(VERSION).so +PURE_LIB = pointers_pure.$(VERSION).so -all: $(POINTERS_LIB) +all: $(CYTHON_LIB) $(PURE_LIB) -$(POINTERS_LIB): pointers.pyx +$(CYTHON_LIB): pointers_cython.pyx + python setup.py build_ext --inplace + +$(PURE_LIB): pointers_pure.py python setup.py build_ext --inplace clean: python setup.py clean - rm -f pointers.c $(POINTERS_LIB) + $(RM) pointers_cython.c pointers_pure.c $(CYTHON_LIB) $(PURE_LIB) + $(RM) -r build diff --git a/source-code/cython/Pointers/README.md b/source-code/cython/Pointers/README.md index 98030b0..5a02cca 100644 --- a/source-code/cython/Pointers/README.md +++ b/source-code/cython/Pointers/README.md @@ -2,9 +2,13 @@ Simple example of using pointers in Cython code. ## What is it? -1. `pointers.pyx`: code that computes a list of squares using a cdef - function that increments an integer value referred to by a pointer. +1. `pointers_cython.pyx`: Cython code that computes a list of squares + using a cdef function that increments an integer value referred to + by a pointer. +1. `pointers_pure.pyx`: pure Python code that computes a list of squares + using a cfunc function that increments an integer value referred to by + a pointer. 1. `setup.py`: Python build script. 1. `Makefile`: make file to build the extension. -1. `squares.py`: script to load the compiled module, and compute the - squares of the first few positive integers. +1. `squares.py`: script to load one of the compiled module, and compute + the squares of the first few positive integers. diff --git a/source-code/cython/Pointers/pointers.pyx b/source-code/cython/Pointers/pointers_cython.pyx similarity index 100% rename from source-code/cython/Pointers/pointers.pyx rename to source-code/cython/Pointers/pointers_cython.pyx diff --git a/source-code/cython/Pointers/pointers_pure.py b/source-code/cython/Pointers/pointers_pure.py new file mode 100644 index 0000000..4dc2c32 --- /dev/null +++ b/source-code/cython/Pointers/pointers_pure.py @@ -0,0 +1,14 @@ +import cython + + +def squares(n: cython.int): + sqrs: list = [] + i: cython.int = 0 + while i < n: + sqrs.append(i**2) + incr(cython.address(i)) + return sqrs + +@cython.cfunc +def incr(i: cython.p_int): + i[0] += 1 diff --git a/source-code/cython/Pointers/setup.py b/source-code/cython/Pointers/setup.py index 409132c..8a156a9 100644 --- a/source-code/cython/Pointers/setup.py +++ b/source-code/cython/Pointers/setup.py @@ -4,5 +4,6 @@ from Cython.Build import cythonize setup( - ext_modules=cythonize('pointers.pyx') + ext_modules=cythonize(['pointers_cython.pyx', 'pointers_pure.py'], + language_level='3str') ) diff --git a/source-code/cython/Pointers/squares.py b/source-code/cython/Pointers/squares.py old mode 100644 new mode 100755 index 52c5a42..ee3e76b --- a/source-code/cython/Pointers/squares.py +++ b/source-code/cython/Pointers/squares.py @@ -1,6 +1,18 @@ #!/usr/bin/env python -import pointers +import argparse -print(pointers.squares(5)) +arg_parser = argparse.ArgumentParser(description='create a list of squares') +arg_parser.add_argument('--n', type=int, default=5, + help='number of squares to create') +arg_parser.add_argument('module', choices=['pure_python', 'cython'], + help='module to use for creating squares') +options = arg_parser.parse_args() + +if options.module == 'pure_python': + import pointers_pure as pointers +elif options.module == 'cython': + import pointers_cython as pointers + +print(pointers.squares(options.n)) diff --git a/source-code/cython/Primes/.gitignore b/source-code/cython/Primes/.gitignore index 19cd7ba..9bf687e 100644 --- a/source-code/cython/Primes/.gitignore +++ b/source-code/cython/Primes/.gitignore @@ -2,3 +2,4 @@ primes_cython.c primes_cython3.c primes_pure_python.c primes_malloc.c +primes_pure_malloc.c diff --git a/source-code/cython/Primes/primes.py b/source-code/cython/Primes/primes.py index 5ff75e6..8c42754 100755 --- a/source-code/cython/Primes/primes.py +++ b/source-code/cython/Primes/primes.py @@ -6,7 +6,7 @@ if __name__ == '__main__': arg_parser = ArgumentParser(description='compute primes') - arg_parser.add_argument('version', choices=['python', 'cython', 'pure_python'], + arg_parser.add_argument('version', choices=['python', 'cython', 'pure_python', 'cython_malloc', 'pure_malloc'], help='version to run') arg_parser.add_argument('--n', type=int, default=10, help='number of primes') @@ -15,7 +15,11 @@ from primes_vanilla import primes elif options.version == 'cython': from primes_cython import primes + elif options.version == 'cython_malloc': + from primes_malloc import primes elif options.version == 'pure_python': from primes_pure_python import primes + elif options.version == 'pure_malloc': + from primes_pure_malloc import primes results = primes(options.n) print(', '.join(map(str, results))) diff --git a/source-code/cython/Primes/primes_cython.pyx b/source-code/cython/Primes/primes_cython.pyx index 2a72396..5a0a411 100644 --- a/source-code/cython/Primes/primes_cython.pyx +++ b/source-code/cython/Primes/primes_cython.pyx @@ -1,18 +1,15 @@ -def primes(int kmax): - cdef int n, k, i - cdef int p[1000] - result = [] - if kmax > 1000: - kmax = 1000 - k = 0 - n = 2 - while k < kmax: - i = 0 - while i < k and n % p[i] != 0: - i = i + 1 - if i == k: - p[k] = n - k = k + 1 - result.append(n) - n = n + 1 - return result +def primes(int nr_primes): + cdef int primes[1000] + if nr_primes > 1000: + nr_primes = 1000 + cdef int n = 2 + cdef int nr_found = 0 + while nr_found < nr_primes: + for prime in primes[:nr_found]: + if n % prime == 0: + break + else: + primes[nr_found] = n + nr_found += 1 + n += 1 + return [prime for prime in primes[:nr_found]] diff --git a/source-code/cython/Primes/primes_malloc.pyx b/source-code/cython/Primes/primes_malloc.pyx index ed13cc5..1dc174a 100644 --- a/source-code/cython/Primes/primes_malloc.pyx +++ b/source-code/cython/Primes/primes_malloc.pyx @@ -1,22 +1,21 @@ from libc.stdlib cimport malloc, free -def primes(int kmax): - cdef int n, k, i - cdef int *p = malloc(kmax*sizeof(int)) - result = [] - if kmax > 1000: - kmax = 1000 - k = 0 - n = 2 - while k < kmax: - i = 0 - while i < k and n % p[i] != 0: - i = i + 1 - if i == k: - p[k] = n - k = k + 1 - result.append(n) - n = n + 1 - free(p) +def primes(int nr_primes): + cdef int nr_bytes = nr_primes*sizeof(int) + cdef int *primes = malloc(nr_bytes) + if nr_primes > 1000: + nr_primes = 1000 + cdef int n = 2 + cdef int nr_found = 0 + while nr_found < nr_primes: + for prime in primes[:nr_found]: + if n % prime == 0: + break + else: + primes[nr_found] = n + nr_found += 1 + n += 1 + result = [prime for prime in primes[:nr_found]] + free(primes) return result diff --git a/source-code/cython/Primes/primes_pure_malloc.py b/source-code/cython/Primes/primes_pure_malloc.py index 357e535..2594915 100644 --- a/source-code/cython/Primes/primes_pure_malloc.py +++ b/source-code/cython/Primes/primes_pure_malloc.py @@ -1,26 +1,22 @@ import cython from cython.cimports.libc.stdlib import malloc, free -def primes(nb_primes: cython.int): - i: cython.int - p: cython.p_int = cython.cast(cython.p_int, malloc(nb_primes*cython.sizeof(cython.int))) - - - len_p: cython.int = 0 # The current number of elements in p. +def primes(nr_primes: cython.int): + nr_bytes: cython.int = nr_primes*cython.sizeof(cython.int) + primes: cython.p_int = cython.cast( + cython.p_int, + malloc(nr_bytes) + ) n: cython.int = 2 - while len_p < nb_primes: - # Is n prime? - for i in p[:len_p]: - if n % i == 0: + nr_found: cython.int = 0 + while nr_found < nr_primes: + for prime in primes[:nr_found]: + if n % prime == 0: break - - # If no break occurred in the loop, we have a prime. else: - p[len_p] = n - len_p += 1 + primes[nr_found] = n + nr_found += 1 n += 1 - - # Let's copy the result into a Python list: - result_as_list = [prime for prime in p[:len_p]] - free(p) - return result_as_list + result = [prime for prime in primes[:nr_found]] + free(primes) + return result diff --git a/source-code/cython/Primes/primes_pure_python.py b/source-code/cython/Primes/primes_pure_python.py index b42a3c8..aeceabc 100644 --- a/source-code/cython/Primes/primes_pure_python.py +++ b/source-code/cython/Primes/primes_pure_python.py @@ -1,29 +1,21 @@ import cython +import sys -def primes(nb_primes: cython.int): - i: cython.int - p: cython.int[1000] - - if nb_primes > 1000: - nb_primes = 1000 - - if not cython.compiled: # Only if regular Python is running - p = [0] * 1000 # Make p work almost like a C array - - len_p: cython.int = 0 # The current number of elements in p. +def primes(nr_primes: cython.int): + primes: cython.int[1000] + if nr_primes > 1000: + nr_primes = 1000 + if not cython.compiled: + primes = [0] * 1000 + print('fall back on Python', file=sys.stderr) n: cython.int = 2 - while len_p < nb_primes: - # Is n prime? - for i in p[:len_p]: - if n % i == 0: + nr_found: cython.int = 0 + while nr_found < nr_primes: + for prime in primes[:nr_found]: + if n % prime == 0: break - - # If no break occurred in the loop, we have a prime. else: - p[len_p] = n - len_p += 1 + primes[nr_found] = n + nr_found += 1 n += 1 - - # Let's copy the result into a Python list: - result_as_list = [prime for prime in p[:len_p]] - return result_as_list \ No newline at end of file + return [prime for prime in primes[:nr_found]] diff --git a/source-code/cython/Primes/primes_vanilla.py b/source-code/cython/Primes/primes_vanilla.py index 7e25b1a..7cc3d1b 100644 --- a/source-code/cython/Primes/primes_vanilla.py +++ b/source-code/cython/Primes/primes_vanilla.py @@ -1,17 +1,15 @@ -def primes(kmax): - p = [0]*1000 - result = [] - if kmax > 1000: - kmax = 1000 - k = 0 +def primes(nr_primes): + primes = [0]*1000 + if nr_primes > 1000: + nr_primes = 1000 n = 2 - while k < kmax: - i = 0 - while i < k and n % p[i] != 0: - i = i + 1 - if i == k: - p[k] = n - k = k + 1 - result.append(n) - n = n + 1 - return result + nr_found = 0 + while nr_found < nr_primes: + for prime in primes[:nr_found]: + if n % prime == 0: + break + else: + primes[nr_found] = n + nr_found += 1 + n += 1 + return primes[:nr_found] diff --git a/source-code/cython/Primes/setup.py b/source-code/cython/Primes/setup.py index 252a498..3ff8cf2 100644 --- a/source-code/cython/Primes/setup.py +++ b/source-code/cython/Primes/setup.py @@ -4,6 +4,7 @@ from Cython.Build import cythonize setup( - ext_modules=cythonize(['primes_cython.pyx', 'primes_pure_python.py', 'primes_malloc.pyx'], + ext_modules=cythonize(['primes_cython.pyx', 'primes_pure_python.py', + 'primes_malloc.pyx', 'primes_pure_malloc.py'], language_level='3str') ) diff --git a/source-code/cython/Structures/.gitignore b/source-code/cython/Structures/.gitignore new file mode 100644 index 0000000..aa6f5e2 --- /dev/null +++ b/source-code/cython/Structures/.gitignore @@ -0,0 +1,2 @@ +point_cython.c +point_pure.c diff --git a/source-code/cython/Structures/Makefile b/source-code/cython/Structures/Makefile index 9032212..87b9d15 100644 --- a/source-code/cython/Structures/Makefile +++ b/source-code/cython/Structures/Makefile @@ -1,11 +1,16 @@ -VERSION = cpython-34m -POINT_LIB = point.$(VERSION).so +VERSION = cpython-311-x86_64-linux-gnu +CYTHON_LIB = point_cython.$(VERSION).so +PURE_LIB = point_pure.$(VERSION).so -all: $(POINT_LIB) +all: $(CYTHON_LIB) $(PURE_LIB) -$(POINT_LIB): point.pyx +$(CYTHON_LIB): point_cython.pyx + python setup.py build_ext --inplace + +$(PURE_LIB): point_pure.py python setup.py build_ext --inplace clean: python setup.py clean - rm -f point.c $(POINT_LIB) + $(RM) point_cython.c point_pure.c $(CYTHON_LIB) $(PURE_LIB) + $(RM) -r build diff --git a/source-code/cython/Structures/README.md b/source-code/cython/Structures/README.md new file mode 100644 index 0000000..0eecf0b --- /dev/null +++ b/source-code/cython/Structures/README.md @@ -0,0 +1,17 @@ +# Structues + +It is easy to define structures in Cython and pure Python +syntax. + + +## What is it? + +1. `point_cython.pyx`: Cython file that defines structure and + some functions that use this structure. +1. `point_pure.py`: pure Python file that defines structure and + some functions that use this structure. +1. `setup.py`: Python setup file to build the Cython extensions. +1. `Makefile`: make file to build the Cython extensions. +1. `driver.py`: Python script to illustrate the use of the + structure type, either the Cython or the pure Python version + can be selected. diff --git a/source-code/cython/Structures/driver.py b/source-code/cython/Structures/driver.py index d9effb6..c192784 100755 --- a/source-code/cython/Structures/driver.py +++ b/source-code/cython/Structures/driver.py @@ -1,6 +1,16 @@ #!/usr/bin/env python -import point +import argparse + +arg_parser = argparse.ArgumentParser(description='Driver for point modules') +arg_parser.add_argument('module', choices=['cython', 'pure_python'], + help='module to use') +options = arg_parser.parse_args() + +if options.module == 'cython': + import point_cython as point +elif options.module == 'pure_python': + import point_pure as point p = point.create(1.0, -2.0, 1) print(p) diff --git a/source-code/cython/Structures/point.pyx b/source-code/cython/Structures/point_cython.pyx similarity index 100% rename from source-code/cython/Structures/point.pyx rename to source-code/cython/Structures/point_cython.pyx diff --git a/source-code/cython/Structures/point_pure.py b/source-code/cython/Structures/point_pure.py new file mode 100644 index 0000000..a5b744b --- /dev/null +++ b/source-code/cython/Structures/point_pure.py @@ -0,0 +1,27 @@ +import cython +import math + +Point = cython.struct( + x=cython.double, + y=cython.double, + id=cython.int +) + +def create(x: cython.double, y: cython.double, id: cython.int) -> Point: + point: Point + point.x = x + point.y = y + point.id = id + return point + +def radius(point: Point) -> cython.double: + return math.sqrt(point.x**2 + point.y**2) + +def azimuth(point: Point) -> cython.double: + return math.atan2(point.y, point.x) + +def project(point: Point) -> None: + r: cython.double = radius(point) + point.x /= r + point.y /= r + print(point) diff --git a/source-code/cython/Structures/setup.py b/source-code/cython/Structures/setup.py index d900057..7e35f8e 100644 --- a/source-code/cython/Structures/setup.py +++ b/source-code/cython/Structures/setup.py @@ -4,5 +4,6 @@ from Cython.Build import cythonize setup( - ext_modules=cythonize("point.pyx") + ext_modules=cythonize(['point_cython.pyx', 'point_pure.py'], + language_level='3str') )