{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "import numpy as np\n", "\n", "grid = np.array(\n", " list( \n", " [x == \"#\" for x in line]\n", " for line in Path(\"game_of_life.txt\").read_text().split()\n", " ),\n", " dtype=np.int8,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.imshow(grid, cmap=\"gray_r\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def update(grid: np.ndarray) -> np.ndarray:\n", " n, m = grid.shape\n", " next_grid = np.zeros((n, m), dtype=np.int8)\n", "\n", " for row in range(n):\n", " for col in range(m):\n", " live_neighbors = (\n", " np.sum(grid[row - 1 : row + 2, col - 1 : col + 2]) - grid[row, col]\n", " )\n", " if live_neighbors < 2 or live_neighbors > 3:\n", " next_grid[row, col] = 0\n", " elif live_neighbors == 3 and grid[row, col] == 0:\n", " next_grid[row, col] = 1\n", " else:\n", " next_grid[row, col] = grid[row, col]\n", "\n", " return next_grid\n", "\n", "\n", "plt.imshow(update(grid), cmap=\"gray_r\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib import ticker\n", "\n", "fig, ax = plt.subplots(1, 4, figsize=(20, 5))\n", "ax[0].imshow(grid, cmap=\"gray_r\")\n", "ax[1].imshow(update(grid), cmap=\"gray_r\")\n", "ax[2].imshow(update(update(grid)), cmap=\"gray_r\")\n", "ax[3].imshow(update(update(update(grid))), cmap=\"gray_r\")\n", "for ax_ in ax:\n", " ax_.xaxis.set_major_locator(ticker.NullLocator())\n", " ax_.yaxis.set_major_locator(ticker.NullLocator())\n", "\n", "fig.set_tight_layout(True)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%timeit update(grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numba\n", "\n", "\n", "@numba.jit(nopython=True)\n", "def update_numba(grid: np.ndarray) -> np.ndarray:\n", " n, m = grid.shape\n", " next_grid = np.zeros((n, m), dtype=np.int8)\n", "\n", " for row in range(n):\n", " for col in range(m):\n", " live_neighbors = (\n", " np.sum(grid[row - 1 : row + 2, col - 1 : col + 2]) - grid[row, col]\n", " )\n", " if live_neighbors < 2 or live_neighbors > 3:\n", " next_grid[row][col] = 0\n", " elif live_neighbors == 3 and grid[row][col] == 0:\n", " next_grid[row][col] = 1\n", " else:\n", " next_grid[row][col] = grid[row][col]\n", "\n", " return next_grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.imshow(update_numba(grid), cmap=\"gray_r\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%timeit update_numba(grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load_ext Cython" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%cython -a\n", "import numpy as np\n", "\n", "def update_cython(grid):\n", " n, m = grid.shape\n", " next_grid = np.zeros((n, m), dtype=np.int8)\n", "\n", " for row in range(n):\n", " for col in range(m):\n", " live_neighbors = np.sum(grid[row-1:row+2, col-1:col+2]) - grid[row, col]\n", " if live_neighbors < 2 or live_neighbors > 3:\n", " next_grid[row][col] = 0\n", " elif live_neighbors == 3 and grid[row][col] == 0:\n", " next_grid[row][col] = 1\n", " else:\n", " next_grid[row][col] = grid[row][col]\n", " \n", " return next_grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.imshow(update_cython(grid))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%timeit update_cython(grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%cython -a\n", "import numpy as np\n", "\n", "def update_cython2(grid):\n", " cdef int n, m, row, col, live_neighbors\n", " n, m = grid.shape\n", " next_grid = np.zeros((n, m), dtype=np.int8)\n", "\n", " for row in range(n):\n", " for col in range(m):\n", " live_neighbors = np.sum(grid[row-1:row+2, col-1:col+2]) - grid[row, col]\n", " if live_neighbors < 2 or live_neighbors > 3:\n", " next_grid[row][col] = 0\n", " elif live_neighbors == 3 and grid[row][col] == 0:\n", " next_grid[row][col] = 1\n", " else:\n", " next_grid[row][col] = grid[row][col]\n", " \n", " return next_grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%timeit update_cython2(grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%cython -a\n", "import numpy as np\n", "from cython import boundscheck, wraparound\n", "\n", "@boundscheck(False)\n", "@wraparound(False)\n", "def update_cython3(signed char[:, :] grid):\n", " cdef int n, m, row, col, live_neighbors\n", " cdef signed char[:, :] next_grid\n", " \n", " n = grid.shape[0]\n", " m = grid.shape[1]\n", " next_grid = np.zeros((n, m), dtype=np.int8)\n", "\n", " for row in range(n):\n", " for col in range(m):\n", " live_neighbors = (\n", " grid[row-1, col-1] + grid[row-1, col] + grid[row-1, col+1] +\n", " grid[row, col-1] + grid[row, col+1] +\n", " grid[row+1, col-1] + grid[row+1, col] + grid[row+1, col+1]\n", " )\n", " \n", " if live_neighbors < 2 or live_neighbors > 3:\n", " next_grid[row][col] = 0\n", " elif live_neighbors == 3 and grid[row][col] == 0:\n", " next_grid[row][col] = 1\n", " else:\n", " next_grid[row][col] = grid[row][col]\n", " \n", " return next_grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.imshow(update_cython(grid))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%timeit update_cython3(grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numba\n", "\n", "@numba.jit(nopython=True)\n", "def update_numba(grid: np.ndarray) -> np.ndarray:\n", " next_grid = np.zeros(grid.shape, dtype=np.int8)\n", " n, m = grid.shape\n", " \n", " for row in range(n):\n", " for col in range(m):\n", " live_neighbors = (\n", " grid[row-1, col-1] + grid[row-1, col] + grid[row-1, col+1] +\n", " grid[row, col-1] + grid[row, col+1] +\n", " grid[row+1, col-1] + grid[row+1, col] + grid[row+1, col+1]\n", " )\n", " \n", " # If the number of surrounding live cells is < 2 or > 3 then we make the cell at grid[row][col] a dead cell\n", " if live_neighbors < 2 or live_neighbors > 3:\n", " next_grid[row][col] = 0\n", " # If the number of surrounding live cells is 3 and the cell at grid[row][col] was previously dead then make\n", " # the cell into a live cell\n", " elif live_neighbors == 3 and grid[row][col] == 0:\n", " next_grid[row][col] = 1\n", " # If the number of surrounding live cells is 3 and the cell at grid[row][col] is alive keep it alive\n", " else:\n", " next_grid[row][col] = grid[row][col]\n", " \n", " return next_grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%timeit update_numba(grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "name": "python394jvsc74a57bd06bcae53aecb1f1c9dc0119dd5dc7f19caaf11f615453eac6e31a35d45325ee3b", "display_name": "Python 3.9.4 64-bit ('pybook': conda)" }, "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.4" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } }, "metadata": { "interpreter": { "hash": "6bcae53aecb1f1c9dc0119dd5dc7f19caaf11f615453eac6e31a35d45325ee3b" } } }, "nbformat": 4, "nbformat_minor": 4 }