diff --git a/calc_pi_cython.ipynb b/calc_pi_cython.ipynb new file mode 100644 index 0000000..58280cd --- /dev/null +++ b/calc_pi_cython.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using Cython in a Jupyter notebook\n", + "\n", + "To use Cython, we must first load an extension." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext cython" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can define our functions with pure Python code, and Cython will try to optimise them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%cython --annotate\n", + "import argparse\n", + "import math\n", + "import random\n", + "import time\n", + "\n", + "from utils import format_time\n", + "\n", + "def point_in_circle(x, y, radius=1):\n", + " \"\"\"\n", + " Checks whether a point (x, y) is part of a circle with a set radius.\n", + " example\n", + " -------\n", + " >>> point_in_circle(0, 0)\n", + " True\n", + " \"\"\"\n", + " r = math.sqrt(x ** 2 + y ** 2)\n", + " return r <= radius\n", + "\n", + "def calculate_pi(points):\n", + " \"\"\"\n", + " Calculates an approximated value of pi by the Monte Carlo method.\n", + " \"\"\"\n", + " within_circle = 0\n", + " for _ in range(points):\n", + " within_circle += int(point_in_circle(random.random(), random.random()))\n", + " return 4 * within_circle/points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%timeit\n", + "number_points = 10000\n", + "calculate_pi(number_points)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/calc_pi_mpi.py b/calc_pi_mpi.py new file mode 100644 index 0000000..ce6e7f4 --- /dev/null +++ b/calc_pi_mpi.py @@ -0,0 +1,42 @@ +import argparse +import random + +from mpi4py import MPI + +from calc_pi import point_in_circle +from utils import format_time + + +COMM = MPI.COMM_WORLD +SIZE = COMM.Get_size() +RANK = COMM.Get_rank() + +if RANK == 0: + parser = argparse.ArgumentParser(description="PI value approximated using monte-carlo and MPI") + parser.add_argument('--npoints', '-np', default=10_000, type=int, help="Number of random points to use") + arguments = parser.parse_args() + print(arguments.npoints) + points = arguments.npoints // SIZE + extra = arguments.npoints % SIZE +else: + points = None + +points = COMM.bcast(points, root=0) + +if RANK == 0: + points += extra + +WT = MPI.Wtime() + +within_circle = [ + point_in_circle(random.random(), random.random()) + for _ in range(points) +] + +all_pi = COMM.gather(within_circle, root=0) +if RANK == 0: + pi = 4 * sum(map(sum, all_pi)) / arguments.npoints + print(f"pi = {pi} (with {sum(map(len, all_pi))} points)") + WT = MPI.Wtime() - WT + print(f"It took: {format_time(WT)}") + diff --git a/calc_pi_numba.py b/calc_pi_numba.py new file mode 100644 index 0000000..020cba9 --- /dev/null +++ b/calc_pi_numba.py @@ -0,0 +1,61 @@ +import argparse +import math +import random +import time + +from numba import jit + +from utils import format_time + +@jit(nopython=True) +def point_in_circle(x, y, radius=1): + """ + Checks whether a point (x, y) is part of a circle with a set radius. + + example + ------- + >>> point_in_circle(0, 0) + True + + """ + r = math.sqrt(x ** 2 + y ** 2) + return r <= radius + +@jit(nopython=True) +def calculate_pi(points): + """ + Calculates an approximated value of pi by the Monte Carlo method. + """ + within_circle = 0 + for _ in range(points): + within_circle += int(point_in_circle(random.random(), random.random())) + return 4 * within_circle/points + + +def command(): + """ + entry point of the script to accept arguments + """ + + parser = argparse.ArgumentParser(description="Calculates an approximate value of PI and how long it takes", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--npoints', '-np', default=10_000, type=int, help="Number of random points to use") + + arguments = parser.parse_args() + + start = time.time() + pi = calculate_pi(arguments.npoints) + end = time.time() + print(f"Elapsed (with compilation) = {format_time(end-start)}") + print(f"pi = {pi} (with {arguments.npoints})") + + start = time.time() + pi = calculate_pi(arguments.npoints) + end = time.time() + print(f"Elapsed (after compilation) = {format_time(end-start)}") + print(f"pi = {pi} (with {arguments.npoints})") + + +if __name__ == '__main__': + command() + diff --git a/calc_pi_numpy.py b/calc_pi_numpy.py index af07729..a73f2ff 100644 --- a/calc_pi_numpy.py +++ b/calc_pi_numpy.py @@ -7,7 +7,7 @@ from utils import format_time -def point_in_circle(x, y, radius=1): +def point_in_circle(points, radius=1): """ Checks whether a point (x, y) is part of a circle with a set radius. @@ -17,7 +17,7 @@ def point_in_circle(x, y, radius=1): True """ - ... + return np.sqrt(points[:, 0] ** 2 + points[:, 1] ** 2) <= radius def calculate_pi_timeit(points): """ @@ -28,7 +28,7 @@ def calculate_pi(): """ Calculates an approximated value of pi by the Monte Carlo method. """ - ... + return (point_in_circle(np.random.random(size=(points, 2))).sum() * 4 / points) return calculate_pi