{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pystencils as ps\n", "from pystencils import plot as plt\n", "\n", "import numpy as np\n", "import sympy as sp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 01: Getting Started\n", "\n", "\n", "## Overview\n", "\n", "*pystencils* is a package that can speed up computations on *numpy* arrays. All computations are carried out fully parallel on CPUs (single node with OpenMP, multiple nodes with MPI) or on GPUs.\n", "It is suited for applications that run the same operation on *numpy* arrays multiple times. It can be used to accelerate computations on images or voxel fields. Its main application, however, are numerical simulations using finite differences, finite volumes, or lattice Boltzmann methods. \n", "There already exist a variety of packages to speed up numeric Python code. One could use pure numpy or solutions that compile your code, like *Cython* and *numba*. See [this page](demo_benchmark.ipynb) for a comparison of these tools.\n", "\n", "![Stencil](../img/pystencils_stencil_four_points_with_arrows.svg)\n", "\n", "As the name suggests, *pystencils* was developed for **stencil codes**, i.e. operations that update array elements using only a local neighborhood. \n", "It generates C code, compiles it behind the scenes, and lets you call the compiled C function as if it was a native Python function. \n", "But lets not dive too deep into the concepts of *pystencils* here, they are covered in detail in the following tutorials. Let's instead look at a simple example, that computes the average neighbor values of a *numpy* array. Therefor we first create two rather large arrays for input and output:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "input_arr = np.random.rand(1024, 1024)\n", "output_arr = np.zeros_like(input_arr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We first implement a version of this algorithm using pure numpy and benchmark it." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def numpy_kernel():\n", " output_arr[1:-1, 1:-1] = input_arr[2:, 1:-1] + input_arr[:-2, 1:-1] + \\\n", " input_arr[1:-1, 2:] + input_arr[1:-1, :-2]" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4.78 ms ± 9.53 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%%timeit \n", "numpy_kernel()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets see how to run the same algorithm with *pystencils*." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle {dst}_{(0,0)} \\leftarrow \\frac{{src}_{(1,0)}}{4} + \\frac{{src}_{(0,1)}}{4} + \\frac{{src}_{(0,-1)}}{4} + \\frac{{src}_{(-1,0)}}{4}$" ], "text/plain": [ "Assignment(dst_C, src_E/4 + src_N/4 + src_S/4 + src_W/4)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "src, dst = ps.fields(src=input_arr, dst=output_arr)\n", "\n", "symbolic_description = ps.Assignment(dst[0,0], \n", " (src[1, 0] + src[-1, 0] + src[0, 1] + src[0, -1]) / 4)\n", "symbolic_description" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.figure(figsize=(3,3))\n", "ps.stencil.plot_expression(symbolic_description.rhs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we first have created a symbolic notation of the stencil itself. This representation is built on top of *sympy* and is explained in detail in the next section. \n", "This description is then compiled and loaded as a Python function." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "kernel = ps.create_kernel(symbolic_description).compile()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This whole process might seem overly complicated. We have already spent more lines of code than we needed for the *numpy* implementation and don't have anything running yet! However, this multi-stage process of formulating the algorithm symbolically, and just in the end actually running it, is what makes *pystencils* faster and more flexible than other approaches.\n", "\n", "Now finally lets benchmark the *pystencils* kernel." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def pystencils_kernel():\n", " kernel(src=input_arr, dst=output_arr)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "987 µs ± 5.12 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" ] } ], "source": [ "%%timeit\n", "pystencils_kernel()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This benchmark shows that *pystencils* is a lot faster than pure *numpy*, especially for large arrays. \n", "If you are interested in performance details and comparison to other packages like Cython, have a look at [this page](demo_benchmark.ipynb).\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Short *sympy* introduction\n", "\n", "In this tutorial we continue with a short *sympy* introduction, since the symbolic kernel definition is built on top of this package. If you already know *sympy* you can skip this section. \n", "You can also read the full [sympy documentation here](http://docs.sympy.org/latest/index.html)." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "import sympy as sp\n", "sp.init_printing() # enable nice LaTeX output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*sympy* is a package for symbolic calculation. So first we need some symbols:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sympy.core.symbol.Symbol" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = sp.Symbol(\"x\")\n", "y = sp.Symbol(\"y\")\n", "type(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The usual mathematical operations are defined for symbols:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle x^{2} \\left(x + y + 5\\right) + x^{2}$" ], "text/plain": [ " 2 2\n", "x ⋅(x + y + 5) + x " ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr = x**2 * ( y + x + 5) + x**2\n", "expr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can do all sorts of operations on these expressions: expand them, factor them, substitute variables:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAI0AAAAXCAYAAAA2o8yAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAABJ0AAASdAHeZh94AAAFB0lEQVR4nO2ae4hVVRTGfzMaVho9oDAoCyqUKBky85HKpExWEloplGBNaEYpWOKj6fXx0cPMNC0rJohG6q+yoTQrRSOitLDS1Cihx1SC0UsFsadOf+xz5c6ZM3Pn3nvuvZPMB8Nm9j577W+ts+7aa699qlpbW+lBD/JB72Im254F3A6cG3V9ATwsaV2RvEoK2w3A9cBA4C/gI6BB0q6KEusmyGWf6iLl7wEWApcAlwLvAq/bHlyk3FKjFngWGAmMBf4FNto+rZKkuhFq6cQ+VWlvT7Z/J3hlY6qCSwjb/YADwCRJayvNp7shbp+itqeY4F7AFKAfsDktuWXCSYSou6/SRLoK2+OA2cAI4FTgN2AnsELSWykv18Y+RTuN7YuBLcDxwEHgOkk7i5VbZqwAthP06Paw/Tgwn5AerAF+BU4HhhC2lrSdpo190og0u4Ea4GRgMrDKdm2+SaXteuBF4ApJ76XAq6vrLgNGAaMkHS7TmvUUqKvt2wgOswqYKenv2PhxKdHMyGtnn96xBzYAdcBkSa9l9VcRlLwFWCzpnsxYRPrr6N9PbQ8F7gamp0k+FwrhbvtJ4EbCy/s2QeZcYCkwT9LShPGBwA7gY0ljUlapHWz3AR4BfiDBYQAk/ZMwL2/bROOJ9omfnuYDR4CHohwlgyciwc/HBSegGuiT45lSIC/utlcANwFjJX3VgcwPo3Z4B+NPA70IuUU5UEfYhpqBI7Yn2F5oe47tEZ3My/u9dmafNpFG0ue2X4oETQOabN8LzAVeAe6ICX4MWAf8SEiWphL21Am59U8X+XC3/Uz0zCRgn+3+0dBBSQezxH4G/AEMi69newrhJT4laUf6GiViaNT+CWwDLopxep8QTX7J7i/gvXZqn6Q6zQMRKdmeTQiH64Fpko7Enu0PvEzIazZFSl0t6e0umSB9dJX7nQQn3wTszfqbly0sCvVbgbNtn5npt90XWAb8DDxYMm3a44yonQ+0AqMJegwGNgBjgFc7mJvPe+3UPol1GtuLgEy42gzUSTqUt4odwHYLcE4eU1ZJqu+i7FS5234UaABukNQc9S0GFgC3SmrKMb+FlHS13QjMJFRpB0lqyRo7kfDjPQsYKandSTAt23R0esoOb9PTdJgIy4FTYn01wETCqaAlNrY9D9lpc8/kNcOAZtuDCIn+FgLXXFhOerruj9pt2Q4DIOmQ7fWEA8hlJJcPUrFNO6exPZWQIP1E2H7mENvzioWk5Qnr1hMM2VTokbtE3DcTtoJMMrySkPzOkpSznJ6yrrujdn8H45ni5AkJa6ZmmzY5je1rgCZgF2Gf3A3MiI6W3Rql4i5pH/AlMCQy/DigUdK24hgXhE0EB77QdlI+mkmMv8vuTNs2Rxe2PQpYTagyjo8y8PsJ0WhxIcLLhTJw/wDoCzQSqq/3pSAzb0j6HlgLDCBEiqOwfSUwnhCF3snqT9021ZHgGuBNwqVUnaS9EcnVwCfARNujC1mg1CgT90xe049wGVvJO6pZhBLHMtsbbS+xvZpwdXAYmCHpAJTONtW2zyd4ZivBE7+JPdMQtUvyFV5qlJF7JtxvBV4oUlZRkLSHcMe0EriAEHFqCRHo8kzFt5S2Sf3TiGMRttcQCpbDJW2tNJ9Ko9iPsI55RMnvtcBzPQ4TkNr3NMcSbA8gXImcB9xM+Ix1QUVJdSP0OE0yrgIWEU4ibwB3laDA+b9FT07Tg7zxH/RCc51argrkAAAAAElFTkSuQmCC", "text/latex": [ "$\\displaystyle x^{3} + x^{2} y + 6 x^{2}$" ], "text/plain": [ " 3 2 2\n", "x + x ⋅y + 6⋅x " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr.expand()" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHsAAAAXCAYAAAAr8TBeAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAABJ0AAASdAHeZh94AAAFMklEQVR4nO2afazWYxjHP+eUkTIviQxhXsLMmqQOlV6czmiUxkZblF42imhFefv6Mi9Jb0irZTrzT6hGpVWysAo7KJVVf1DUHCtURoV0/HH/nvb4nd9zTj1vJ+t8t2fX89zXfd/X9buv33293PdTUlNTQyOODTTNZbDtcUA/oC3wJ/AZME7Shjzo1og8ozTH8d2A14BrgR7AAWC57dNynLcRBUBJPt247RbAHqCvpIUJ/ErgRuACSX/kTXAjasF2e+ALYKikWZCjG0/ASQRvsStBeAdgADC60dDZw3ZPYARQBpwK/AKsB6ZKWpzqJ+lL2+8Cz9ieI+n3XN14HFOBtcCnCbxngd+A6XmWeczA9ovAcuBqYAEwEXgfaEUIqXE8D7QGHoA8unHbk4A7gM6SvovxLgE2AbMkDcuLwPr1GQi8AXSX9FExZNaFXPWxPRSYCVQCwyT9FeMfJ+nvhHEbgROBC5rGGMuAcuA2SfPS2ksiRe8GxksaGxs3mWDo7nFDR7gHKAHeyvAgWcktBGyPIuyY0ZImJvDbAuuAzyV1LbQ+kczjCZ7xBxIMDZBk6AhzgKeA8rgbHwMcJPj5JmntLxEWfGaCoacCdwI9JG3KIPAG4B9CaZaEI5ZbQKyKaKcM/FeAJoS4WSyUE1z1fOCg7d62H7E90nZZPWNTz/NfY0v6GngTuIyQTGH7UWAU8DZwb3p/29OAQUB/YJft1tGnRVqf5kA7YGOmxOxI5RYYXwH7gI5xhu3bCQs/TdK6IurUIaL7gTXAIuAFYAqw2vbHtltlGFsV0a5JCdoT0aSyPYLgPpYCAyQdjPW9j5CBfwhUp31Gp/U5m7ATqut5oCORWzBE7rAKONf2Wan26KWdBOwAniyWPhHOiOgYoAboQlj3K4FlQFfgnaSBkvYQ1rVNrdJL0jbbU4CxBJe1GuiXIU6UHIaiLSNaqxzLVm4ctrcC52Vgr7Adb6uUNLCOKVcRFrCM4DohGPgcYFC0gMXUJ7UpDwC3SNoa/V5v+1ZgM3C97TJJSZXQr8CZmersnWnfB0vaW4ci9WFfRE84jL7Zyp0CnBJrawf0IWSvW2O8tfXMl4pzHYH5ti8FHiKUlJUNoM/uiK5JMzQAkvbaXgoMBq4huextBuyrZWzb/QmJ0U+EGm0kucXMHRFtWVenXORKmpIw30DC4s7OotRZTXCXqSTtVUIoGi6p3lq1APpsjujuDPyU12yWILeU8OJtKY0xbgJmAxsI8WAzMCQqN7JFNWHHZpyjQHKzhqRdwEagffQS9gRmSFrTEPoQcqIa4PLIeHFcEdEtCby2hLJ37aGBtjsDc4HtQIWkncDjhCPV8dlqGe2ET4DTbV8U5xdKbh6wEmgOzAB+Bh5rKEUkfQ8sBNoQPN4h2O4FVBB2/ZKE4SnvtKI0GtCOkM7vAcolVUdC5hIO0/vY7pKDvqmDkoqYooWWmwtScbsF4dq2zgSzCBgObAMm2V5ue4LtucBiwhnGkAyJY6+I/15ptNuWENxEhaRvY53HRXRCDorOI8Tuu1INRZKbC1IusQp4vYF0OARJ24H2hPzhYsIO70bY8delnzymYPtkoC+wSNK2vF5x1oXojw7PAVc1YOw7bNheAPQGOkmqqq//0Qjb9wMvA10krcz3rVddmEw42326iDKzQpSU3QxM/x8buhnBO86TtBLyf5+dEZL22x4AdLfd/Gi707bdhnDseyEh3HwDPNygSuWG8wm3ZLNTDUVz40c7bA8jZN67gQ+AByX92KBK5RmNxj6G8C8PHDfc7bZAEwAAAABJRU5ErkJggg==", "text/latex": [ "$\\displaystyle x^{2} \\left(x + y + 6\\right)$" ], "text/plain": [ " 2 \n", "x ⋅(x + y + 6)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr.factor()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle x^{2} \\left(x + \\cos{\\left(x \\right)} + 5\\right) + x^{2}$" ], "text/plain": [ " 2 2\n", "x ⋅(x + cos(x) + 5) + x " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr.subs(y, sp.cos(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also built equations and solve them" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle x^{2} \\left(x + y + 5\\right) + x^{2} = 1$" ], "text/plain": [ " 2 2 \n", "x ⋅(x + y + 5) + x = 1" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "eq = sp.Eq(expr, 1)\n", "eq" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHsAAAAfCAYAAADHorIzAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAABJ0AAASdAHeZh94AAAElUlEQVR4nO2bbYhUVRjHfyuFSH4owaggLbBAERS0YH2prWU1erMXI4oWLTWIiiJcMDH+/C1Js2Kl+tKHWhO/pJKkmUovirFCZrupSBJLppKKvegX20qdPpyzMQw768x65+7o3B8M595z7nnuwzzzPOc855ypy+VyZNQGl+Xf2L4W2ADsBnLAakk7BkKxWsf2bcB8YAJwHfCkpLYS+tUDs+LtROBhSb9AgbGBwVH4FEndCemd0T+GAvuAj+KnJCTtBHYC2M4BQ3raCo2dUSVI2gRsArDdloTMQUkIybg4yDwbsN0IPAfUA1cBvwN7gRXRwy4Jat7Ytt8AWoAjwKfAb8BwwtylgRhKLwVq2ti25xEMvRJ4WtI/Be2X90PmbOBD4A5J2xJQMzFSM7btrUATMFPSurz6OsKXMwtYJmlBSvoMBpYAh+jF0ACS/k1Dl7RI07NbgO+BV22vl3Q21r9JMPT7aRk60kQI163AOdv3AGOBbuDbmMIMGLaHAqPi7SBghO3xwB+SDvVHZmqzcUk/AKuA0UAzgO2FwEvAx8AzaekSuSWW3UAHsBFYSjB+u+3ttoenrFM+E6NeHYRc2fF6cX8Fpj1mvwI8Cij+cpcAW4BmSedS1uXqWLYA+4GpQCdwIyHaTAPWECZpqRPH+7okZZZkbNsHgZFlyF0t6YnCSkmHbbcCC4B3gHbgod7Gy0rpkEdPVDsD3C/pYLzfa/tB4ABwu+36YiH9PDp9bbuwbqWk2SXoXhFK9ewuQrgrlV/7aDuRdz1H0ukB0AHgZCw78gwNgKTTtrcAc4BbicuPvdAKXFlQNx6YQZjhHyxo6zyPThWlJGNLakziZbYfJ4TIY8A1wAuUOFYnpUMeB2J5skj7n7EcUqQdSa2FdTH1mgG0lZN6xXXsC0JSn2E/zdTrbqCNsLjfCOwA5tpulXSgr74V4kvCzt4Y24N6mTOMjeXPaShzPkMlQSqzcdtTgLWEVarpkk4Aiwg/tmVp6FBI3PbbAIwgRJj/sT0NmE7w+s2pK1cE29fb3mZ7v+09th8pp3/FjR1zw43AKaBJ0lEASWuB74AZtqdWWo8iPAscBt62/YXt5bbXEpZIzwJzJZ0aIN164wzwoqQxhGyh1fYVpXauqLFtjyJ4Ro7g0V0Fj7wcy+WV1KMYko4Q1sDfBW4ieHgDweMn56/0VQOSjkrqjNfHCOv4w0rtX5d/LMn2DYQxakh2eKG6sT2BkMqN7eOZHDBa0o9Q4xshFyu2hxFOr8wrp19m7CrC9kxgNXBzz7kx2yuAe4FJko7HDZz1wFJJ7eXIz06qVBfrCIcmFgHYng88BtwVDV1HSF+/krSqXOGZZ1cRknJxc+gz213AQqBR0k/xkcmEvYU9th+Idc2S9pYiPzN2lSFpq+1dwGvAfZJ25bV9wwVE4yyMVxm27wTGEXa8jicpOzN2FWF7HPAJ8DxhEvZ6kvIzY1cJtkcCnwNvSfoAENBkuyGpdxSO2X8T/vrzXkzIV0nantTLMnon5s2bgQ2SFgNI2md7DcG768uQNQl4Kt7uBv7qaavL/thXO2RhvIb4DyfqqX/syaykAAAAAElFTkSuQmCC", "text/latex": [ "$\\displaystyle \\left[ - x - 6 + \\frac{1}{x^{2}}\\right]$" ], "text/plain": [ "⎡ 1 ⎤\n", "⎢-x - 6 + ──⎥\n", "⎢ 2⎥\n", "⎣ x ⎦" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.solve(sp.Eq(expr, 1), y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A *sympy* expression is represented by an abstract syntax tree (AST), which can be inspected and modified." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAKsAAAAXCAYAAAB04L8XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAABJ0AAASdAHeZh94AAAFUklEQVR4nO2aaahUZRzGf1eFMo2igozIbiiZHypJQgtNr2bShrZhSea1RagsyyzSlqenULnkWqkphkp9MFGxssU2I8wSW0wMjaikTdPAjErbvH14z+Tc8cxMd+acuS7zg+Fc3u3/3Pc8857/+86paWxspEqVg4E25XS2PQ64EugC/AF8AIyTtDEBbVUOM4r5qVWZ4/cFZgHnA/2Av4E3bR9X5rhVDk/6UsBPNUmmAbbbA7uAwZJeiqlfCFwMnCbpt8QCV9kP292BD4FbJM1raT2lkOunstKAGI4mrNY7YwKfCwwDxlaN2nxsbwFOzVP9o6QO2QWSPrK9HHjM9iJJv6YsMQ2a+Clps84A1gPvx9RNAH4BZicc83BiFzA9pjyfEScBa4E7gYkpaUqTJn5KLA2wPRW4Fugl6aucutOBzcA8SSMTCVhcTz0wH6iT9E4lYhaiXD3Ryoqk2mb22wQcRUi99jazbz0tNIdxfmqT0+B1YABwtaSlWeU1BNHDgQZJ9+f0mxYNXJdr1IgbgRrg+TzCSoqbBrbHAFMI6cqUmPouwAZgraQL0taTAIuARwjzu7KSgZP2U+5pwL3AXkKe0zqrfHI08NyYgWcA1wH9JG3Oo/tC4B/CUUQczY6bIu9F15556p8EWgOjKiOnCUfYvt72eNujbdflzFccmf9nQNriYkjUT03MKulT4FmgK2EzhO3xwBhgMXBrzsAzgRHAUGCn7Q7Rp31Wm3ZAN2BTvo1Vc+OmzMfAbqBHboXtawg3faakDRXUlKEDYZ4mEHLXt4EvbPcp0GdddK34UyBpP8Wdsz4E7AFkexRhYlYCw2JyntsIO7a3gK1Zn7FZbU4mrERbi/xvzYmbGpL+ItzgU2yflCmPvnRTge3Aw5XSk8V8oD/BsO2AM4E5QC3wqu2z4zpJ2kWY146VkbkfifkpdoNlexKQWZ7XAAMk/V6KUtvnRWMsljSkSNuS4hY51oljoaT6AuNNBMYBV0laFpU1APcBIyQtqKSeIrEmA/cAyyVdkafN98CJkvKe/qSpOSk/5RO/I+vvm0o1asTu6Hrk/2hbatzpwLE5Zd2AQcBCYEtO3foi42XyvB7AMttnAHcTjlAWtoCeQjxNMGuhx3xb9t2HfEwnPc2J+Gk/s9oeSkiAtxEeOaMpL2fcHl2PL9SonLiSpseMV0+Y6AUlHLusARrZt8l6ipDK3C6p6FlfCnoKkTFCu7hK260IJvy60CBpaU7ST01yVtuXAAuAjcBZwOfAzdFxTalsJUxo3jFSilsyknYCm4Du0WT3B+ZI+qQl9BQh84WKOzKEMO81lLd6l0TS9/U/s9ruBSwBvgMGStoBPEhYfRtKFRytRO8CJ9junFufVtwEWE1YreYAPwEPtJQQ212jDV5ueS1h1Qd4Lk/3jJlXpSAtL2nc11bRwN2AFYSf8wZI2gogaQnhZYhBtnuXoT1zIDwwu7ACccshk7e2J7ymtt/7DhVkCLDN9su2Z9lusL2EsPp3Bl4hPGrjuIhwxv1CZaSmd19bRavda4QcbaCkL3PajIuuj5eoHYJZtwM3ZAoqFLccMjneOuCZFtKQYRXh5ncinEGOAfoQVv/hwGWS/sztZPsYYDCwQtK3lRCa5n1N9BXBQkQv1k4EzjlAc78m2H4RuBToKWldsfYHIrbvAJ4Aekta3dJ6yqXcl6+bwzTgG+DRCsYsiWhTdTkw+yA2alvCKrb0UDAqJP+KYF4k7bE9DKiz3e5Ae6fVdkfCI7YTIV35jPAjwMFKLTCXsBs/JKhYGnCgY3skYef/M/AGcJekH1pUVJUmVM1a5aDhXzRxoz8ghtcJAAAAAElFTkSuQmCC", "text/latex": [ "$\\displaystyle x^{2} \\left(x + y + 5\\right) + x^{2}$" ], "text/plain": [ " 2 2\n", "x ⋅(x + y + 5) + x " ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "Add(Pow(Symbol('x'), Integer(2)), Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y'))))_()\n", "\n", "Add\n", "\n", "\n", "\n", "Pow(Symbol('x'), Integer(2))_(0,)\n", "\n", "Pow\n", "\n", "\n", "\n", "Add(Pow(Symbol('x'), Integer(2)), Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y'))))_()->Pow(Symbol('x'), Integer(2))_(0,)\n", "\n", "\n", "\n", "\n", "\n", "Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y')))_(1,)\n", "\n", "Mul\n", "\n", "\n", "\n", "Add(Pow(Symbol('x'), Integer(2)), Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y'))))_()->Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y')))_(1,)\n", "\n", "\n", "\n", "\n", "\n", "Symbol('x')_(0, 0)\n", "\n", "x\n", "\n", "\n", "\n", "Pow(Symbol('x'), Integer(2))_(0,)->Symbol('x')_(0, 0)\n", "\n", "\n", "\n", "\n", "\n", "Integer(2)_(0, 1)\n", "\n", "2\n", "\n", "\n", "\n", "Pow(Symbol('x'), Integer(2))_(0,)->Integer(2)_(0, 1)\n", "\n", "\n", "\n", "\n", "\n", "Pow(Symbol('x'), Integer(2))_(1, 0)\n", "\n", "Pow\n", "\n", "\n", "\n", "Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y')))_(1,)->Pow(Symbol('x'), Integer(2))_(1, 0)\n", "\n", "\n", "\n", "\n", "\n", "Add(Integer(5), Symbol('x'), Symbol('y'))_(1, 1)\n", "\n", "Add\n", "\n", "\n", "\n", "Mul(Pow(Symbol('x'), Integer(2)), Add(Integer(5), Symbol('x'), Symbol('y')))_(1,)->Add(Integer(5), Symbol('x'), Symbol('y'))_(1, 1)\n", "\n", "\n", "\n", "\n", "\n", "Symbol('x')_(1, 0, 0)\n", "\n", "x\n", "\n", "\n", "\n", "Pow(Symbol('x'), Integer(2))_(1, 0)->Symbol('x')_(1, 0, 0)\n", "\n", "\n", "\n", "\n", "\n", "Integer(2)_(1, 0, 1)\n", "\n", "2\n", "\n", "\n", "\n", "Pow(Symbol('x'), Integer(2))_(1, 0)->Integer(2)_(1, 0, 1)\n", "\n", "\n", "\n", "\n", "\n", "Integer(5)_(1, 1, 0)\n", "\n", "5\n", "\n", "\n", "\n", "Add(Integer(5), Symbol('x'), Symbol('y'))_(1, 1)->Integer(5)_(1, 1, 0)\n", "\n", "\n", "\n", "\n", "\n", "Symbol('x')_(1, 1, 1)\n", "\n", "x\n", "\n", "\n", "\n", "Add(Integer(5), Symbol('x'), Symbol('y'))_(1, 1)->Symbol('x')_(1, 1, 1)\n", "\n", "\n", "\n", "\n", "\n", "Symbol('y')_(1, 1, 2)\n", "\n", "y\n", "\n", "\n", "\n", "Add(Integer(5), Symbol('x'), Symbol('y'))_(1, 1)->Symbol('y')_(1, 1, 2)\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ps.to_dot(expr, graph_style={'size': \"9.5,12.5\"} )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Programatically the children node type is acessible as ``expr.func`` and its children as ``expr.args``.\n", "With these members a tree can be traversed and modified." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sympy.core.add.Add" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr.func" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle \\left( x^{2}, \\ x^{2} \\left(x + y + 5\\right)\\right)$" ], "text/plain": [ "⎛ 2 2 ⎞\n", "⎝x , x ⋅(x + y + 5)⎠" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr.args" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using *pystencils* \n", "\n", "\n", "### Fields\n", "\n", "*pystencils* is a module to generate code for stencil operations. \n", "One has to specify an update rule for each element of an array, with optional dependencies to neighbors.\n", "This is done use pure *sympy* with one addition: **Fields**.\n", "\n", "Fields represent a multidimensional array, where some dimensions are considered *spatial*, and some as *index* dimensions. Spatial coordinates are given relative (i.e. one can specify \"the current cell\" and \"the left neighbor\") whereas index dimensions are used to index multiple values per cell." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "my_field = ps.fields(\"f(3) : double[2D]\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Neighbors are labeled according to points on a compass where the first coordinate is west/east, second coordinate north/south and third coordinate top/bottom. " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAC0AAAAaCAYAAAAjZdWPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAABJ0AAASdAHeZh94AAAC/ElEQVR4nM3YT6hVVRQG8N/Vil5YYAaShBVEhET/aSDZIBLCIogGFTQoalCGJJIDJVgsqahBgYNEtOBBNYhq4uCVjooo7IG9oL9UBkVZkgXhoCjlNjjn3jbnHd87z/fepQ8O+55vr7Xvxzprr73O6fX7fV2QmTfjCVyPVXgwIsY7OS8wlszBdhk+w+P4c3HkdMMZbWRmnoencKcqqmdiW0Rsr+fHRyWwDa2i8RruwARexQnsG5Wo2TBNdGZeoRK8PyJuH72k2dGW07fU41ujFDIXDCOdmXfjzWJuT2buqX+viYgvR6psBpTp8TMSG7EcT9d8H1+PWNeM6JV1OjOX4ji+jYirSsPMXIbL6tsP8axqc/4eET+MRm6FZk6vwRg+brG9AVP1NaZ6KlPYsZgC29CsHtfV4zTREfEueostqAuakR6Inhq1kLmgLdJ9fNI0zMzl+AprI+Lw4ksjM9/AwYh4vuTLktfD1fgmIo63rLEdEwPBC9FAZeZGbMWF+BybI+L9wmQH3svMlyLijwFZpsflOFdLamTmOXgYLxf0vBqozLwHO/EMrlVVpLczc/XAJiI+xXe4v/Qt0+OUmxAbVGnzQbHghKo3Od0GagvGI2Jvfb8pM2/Do9hW2O3DfXhxQJSRnkn0OhyKiG7N9yzIzLNUaXWgMXUAaxvcJG7MzLEBMYx0RGxV5VcbLsaReav9DxdgKY42+KO4tcEdUbXGq3CY7i8BY/jr9DXOC4P9Mox0V9HHVP3IQuEYTmJlg1+JXxrc+fX464DoKnpKdcQvCCLibxzC+sbUelUVKXElfoqIYSqd6s2lif14LjNXRMRvTGuglmB1Zl6jewP1Al7JzElVVXpElbe7G3br6v8folOk63o5iXsLetYGKjMfyMx+Zl7Ssubr2IwnVSfwTdgQEd8X/mfjLuwtfbtGWi1qZ2bujoiTHRuoS/EFfmybjIhd2DWD/0P4KCIOlmTnTwgR8Y6qwF/U1Ud1KD0WESfm4FPiH2xqkr2uH2v+T/gXmdDo73n+u0IAAAAASUVORK5CYII=", "text/latex": [ "$\\displaystyle {f}_{(1,0)}^{1}$" ], "text/plain": [ "f_E__1" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "field_access = my_field[1, 0](1)\n", "field_access" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result of indexing a field is an instance of ``Field.Access``. This class is a subclass of a *sympy* Symbol and thus can be used whereever normal symbols can be used. It is just like a normal symbol with some additional information attached to it." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(field_access, sp.Symbol)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Building our first stencil kernel\n", "\n", "Lets start by building a simple filter kernel. We create a field representing an image, then define a edge detection filter on the third pixel component which is blue for an RGB image." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "img_field = ps.fields(\"img(4): [2D]\")" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle \\left({img}_{(1,0)}^{2} w_{2} - {img}_{(1,1)}^{2} w_{1} - {img}_{(-1,1)}^{2} w_{1} + {img}_{(1,-1)}^{2} w_{1} - {img}_{(-1,-1)}^{2} w_{1} - {img}_{(-1,0)}^{2} w_{2}\\right)^{2}$" ], "text/plain": [ " 2\n", "(img_E__2⋅w₂ - img_NE__2⋅w₁ - img_NW__2⋅w₁ + img_SE__2⋅w₁ - img_SW__2⋅w₁ - img_W__2⋅w₂) " ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "w1, w2 = sp.symbols(\"w_1 w_2\")\n", "color = 2\n", "sobel_x = (-w2 * img_field[-1,0](color) - w1 * img_field[-1,-1](color) - w1 * img_field[-1, +1](color) \\\n", " +w2 * img_field[+1,0](color) + w1 * img_field[+1,-1](color) - w1 * img_field[+1, +1](color))**2\n", "sobel_x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have mixed some standard *sympy* symbols into this expression to possibly give the different directions different weights. The complete expression is still a valid *sympy* expression, so all features of *sympy* work on it. Lets for example now fix one weight by substituting it with a constant." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle \\left({img}_{(1,0)}^{2} w_{2} - 0.5 {img}_{(1,1)}^{2} - 0.5 {img}_{(-1,1)}^{2} + 0.5 {img}_{(1,-1)}^{2} - 0.5 {img}_{(-1,-1)}^{2} - {img}_{(-1,0)}^{2} w_{2}\\right)^{2}$" ], "text/plain": [ " 2\n", "(img_E__2⋅w₂ - 0.5⋅img_NE__2 - 0.5⋅img_NW__2 + 0.5⋅img_SE__2 - 0.5⋅img_SW__2 - img_W__2⋅w₂) " ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sobel_x = sobel_x.subs(w1, 0.5)\n", "sobel_x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets built an executable kernel out of it, which writes the result to a second field. Assignments are created using *pystencils* `Assignment` class, that gets the left- and right hand side of the assignment." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/latex": [ "$\\displaystyle {dst}_{(0,0)} \\leftarrow \\left({img}_{(1,0)}^{2} w_{2} - 0.5 {img}_{(1,1)}^{2} - 0.5 {img}_{(-1,1)}^{2} + 0.5 {img}_{(1,-1)}^{2} - 0.5 {img}_{(-1,-1)}^{2} - {img}_{(-1,0)}^{2} w_{2}\\right)^{2}$" ], "text/plain": [ " 2\n", "dst_C := (img_E__2⋅w₂ - 0.5⋅img_NE__2 - 0.5⋅img_NW__2 + 0.5⋅img_SE__2 - 0.5⋅img_SW__2 - img_W__2⋅w₂) " ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dst_field = ps.fields('dst: [2D]' )\n", "update_rule = ps.Assignment(dst_field[0,0], sobel_x)\n", "update_rule" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we can see *pystencils* in action which creates a kernel for us." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "from pystencils import create_kernel\n", "ast = create_kernel(update_rule, cpu_openmp=False)\n", "compiled_kernel = ast.compile()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This compiled kernel is now just an ordinary Python function. \n", "Now lets grab an image to apply this filter to:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "image/png": "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t9sNp9MpexEEQRAEQQhMW9DwPI+nnnoKGzZswPLlywEA3d3dMBgMsFqtsrwFBQXo7u4W80jFjHBeOKfGrl27YLFYxFdpael0m00QBEEQxCxEN92CO3bswPnz53H48OFYtkeVp59+Gjt37hSPHQ4HysrKyFJDEARBECmEMG7PINolLNMSNE8++STef/99HDx4ECUlJWK6zWaDx+OB3W6XWWl6enpgs9nEPMePH5fVJ8yCEvIoMRqNMBqN4rHQIWSpIQiCIIjUY2BgABaLJaZ1RiVoGGP43ve+h7fffhv79+9HZWWl7HxNTQ30ej327duHbdu2AQAuX76MtrY21NXVAQDq6urwd3/3d+jt7UV+fj4AYO/evTCbzVi2bFlE7SgqKsLFixexbNkytLe3U2DwDHA6nSgtLaV+jAHUl7GB+jF2UF/GBurH2CF4WITY21gSlaDZsWMHXnvtNfzud79DVlaWGPNisVhgMplgsVjwxBNPYOfOncjOzobZbMb3vvc91NXVYf369QCAe+65B8uWLcPXv/51/MM//AO6u7vxV3/1V9ixY4fMCjMZGo0GxcXFAACz2UxfsBhA/Rg7qC9jA/Vj7KC+jA3Uj7FDo4n9ur5RCZrnn38eAHDnnXfK0l966SV84xvfAAD80z/9EzQaDbZt2wa3243NmzfjX/7lX8S8Wq0W77//Pr773e+irq4OGRkZePzxx/GTn/xkZndCEARBEMScJWqX01SkpaXhueeew3PPPRc2T3l5OT744INoLk0QBEEQBBGWlN3LyWg04plnnonYTUWoQ/0YO6gvYwP1Y+ygvowN1I+xI559OaOVggmCIAiCIG4GUtZCQxAEQRAEIUCChiAIgiCIlIcEDUEQBEEQKQ8JGoIgCIIgUp6UFDTPPfccKioqkJaWhtra2pCtFOY6Bw8exAMPPICioiJwHId33nlHdp4xhh//+McoLCyEyWRCfX09rly5IsszODiI7du3w2w2w2q14oknnoDL5UrgXSSfXbt24dZbb0VWVhby8/Px0EMP4fLly7I84+Pj2LFjB3JycpCZmYlt27aJW3kItLW14b777kN6ejry8/PxZ3/2Z/D5fIm8laTz/PPPY+XKleLCZHV1ddi9e7d4nvpxevz93/89OI7DU089JaZRX0bG3/zN34DjONmrqqpKPE/9GDkdHR342te+hpycHJhMJqxYsQInT54UzydszGEpxuuvv84MBgP75S9/yS5cuMC+9a1vMavVynp6epLdtJuGDz74gP3lX/4le+uttxgA9vbbb8vO//3f/z2zWCzsnXfeYWfOnGFf+MIXWGVlJRsbGxPzbNmyha1atYp9+umn7NChQ2zhwoXs0UcfTfCdJJfNmzezl156iZ0/f56dPn2a3XvvvaysrIy5XC4xz3e+8x1WWlrK9u3bx06ePMnWr1/PPve5z4nnfT4fW758Oauvr2enTp1iH3zwAcvNzWVPP/10Mm4pabz77rvs97//PWtqamKXL19mf/EXf8H0ej07f/48Y4z6cTocP36cVVRUsJUrV7I/+ZM/EdOpLyPjmWeeYdXV1ayrq0t89fX1ieepHyNjcHCQlZeXs2984xvs2LFj7Nq1a+zDDz9kV69eFfMkasxJOUGzbt06tmPHDvHY7/ezoqIitmvXriS26uZFKWh4nmc2m439z//5P8U0u93OjEYj+/Wvf80YY+zixYsMADtx4oSYZ/fu3YzjONbR0ZGwtt9s9Pb2MgDswIEDjLFAv+n1evbGG2+IeS5dusQAsKNHjzLGAuJSo9Gw7u5uMc/zzz/PzGYzc7vdib2Bm4x58+axF198kfpxGgwPD7NFixaxvXv3so0bN4qChvoycp555hm2atUq1XPUj5Hz53/+5+y2224Lez6RY05KuZw8Hg8aGhpQX18vpmk0GtTX1+Po0aNJbFnq0NLSgu7ublkfWiwW1NbWin149OhRWK1WrF27VsxTX18PjUaDY8eOJbzNNwsOhwMAxE3VGhoa4PV6ZX1ZVVWFsrIyWV+uWLECBQUFYp7NmzfD6XTiwoULCWz9zYPf78frr7+OkZER1NXVUT9Ogx07duC+++6T9RlA38louXLlCoqKijB//nxs374dbW1tAKgfo+Hdd9/F2rVr8eUvfxn5+fm45ZZb8G//9m/i+USOOSklaPr7++H3+2VfIAAoKCgQN8okJkfop8n6sLu7W9wJXUCn0yE7O3vO9jPP83jqqaewYcMGLF++HECgnwwGA6xWqyyvsi/V+lo4N5c4d+4cMjMzYTQa8Z3vfAdvv/02li1bRv0YJa+//jo+++wz7Nq1K+Qc9WXk1NbW4uWXX8aePXvw/PPPo6WlBbfffjuGh4epH6Pg2rVreP7557Fo0SJ8+OGH+O53v4vvf//7eOWVVwAkdsyJai8ngpir7NixA+fPn8fhw4eT3ZSUZcmSJTh9+jQcDgf+4z/+A48//jgOHDiQ7GalFO3t7fiTP/kT7N27F2lpacluTkqzdetW8fPKlStRW1uL8vJy/Pa3v4XJZEpiy1ILnuexdu1a/I//8T8AALfccgvOnz+PF154AY8//nhC25JSFprc3FxotdqQSPOenh7YbLYktSq1EPppsj602Wzo7e2Vnff5fBgcHJyT/fzkk0/i/fffx8cff4ySkhIx3WazwePxwG63y/Ir+1Ktr4VzcwmDwYCFCxeipqYGu3btwqpVq/C//tf/on6MgoaGBvT29mLNmjXQ6XTQ6XQ4cOAAfv7zn0On06GgoID6cppYrVYsXrwYV69epe9kFBQWFmLZsmWytKVLl4ruu0SOOSklaAwGA2pqarBv3z4xjed57Nu3D3V1dUlsWepQWVkJm80m60On04ljx46JfVhXVwe73Y6GhgYxz0cffQSe51FbW5vwNicLxhiefPJJvP322/joo49QWVkpO19TUwO9Xi/ry8uXL6OtrU3Wl+fOnZP9Y927dy/MZnPIQ2CuwfM83G439WMUbNq0CefOncPp06fF19q1a7F9+3bxM/Xl9HC5XGhubkZhYSF9J6Ngw4YNIctZNDU1oby8HECCx5zoY5qTy+uvv86MRiN7+eWX2cWLF9m3v/1tZrVaZZHmc53h4WF26tQpdurUKQaA/fSnP2WnTp1i169fZ4wFptBZrVb2u9/9jp09e5Y9+OCDqlPobrnlFnbs2DF2+PBhtmjRojk3bfu73/0us1gsbP/+/bKpnaOjo2Ke73znO6ysrIx99NFH7OTJk6yuro7V1dWJ54Wpnffccw87ffo027NnD8vLy5tzUzt/9KMfsQMHDrCWlhZ29uxZ9qMf/YhxHMf+8Ic/MMaoH2eCdJYTY9SXkfKnf/qnbP/+/aylpYUdOXKE1dfXs9zcXNbb28sYo36MlOPHjzOdTsf+7u/+jl25coX96le/Yunp6ezVV18V8yRqzEk5QcMYY7/4xS9YWVkZMxgMbN26dezTTz9NdpNuKj7++GMGIOT1+OOPM8YC0+j++q//mhUUFDCj0cg2bdrELl++LKtjYGCAPfrooywzM5OZzWb2zW9+kw0PDyfhbpKHWh8CYC+99JKYZ2xsjP3X//pf2bx581h6ejr74he/yLq6umT1tLa2sq1btzKTycRyc3PZn/7pnzKv15vgu0kuf/RHf8TKy8uZwWBgeXl5bNOmTaKYYYz6cSYoBQ31ZWQ88sgjrLCwkBkMBlZcXMweeeQR2dop1I+R895777Hly5czo9HIqqqq2P/+3/9bdj5RYw7HGGNRWpgIgiAIgiBuKlIqhoYgCIIgCEINEjQEQRAEQaQ8JGgIgiAIgkh5SNAQBEEQBJHykKAhCIIgCCLlIUFDEARBEETKQ4KGIAiCIIiUhwQNQRAEQRApDwkagiAIgiBSHhI0BEEQBEGkPCRoCIIgCIJIeUjQEARBEASR8vz/4/rQeL1Bjn0AAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "try:\n", " import requests\n", " import imageio.v2 as imageio\n", " from io import BytesIO\n", "\n", " response = requests.get(\"https://www.python.org/static/community_logos/python-logo-master-v3-TM.png\")\n", " img = imageio.imread(BytesIO(response.content)).astype(np.double)\n", " img /= img.max()\n", " plt.imshow(img);\n", "except ImportError:\n", " print(\"No requests or imageio installed\")\n", " img = np.random.random((82, 290, 4))" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "filtered_image = np.zeros_like(img[..., 0])\n", "# here we call the compiled stencil function\n", "compiled_kernel(img=img, dst=filtered_image, w_2=0.5)\n", "plt.imshow(filtered_image, cmap='gray');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Digging into *pystencils*\n", "\n", "On our way we have created an ``ast``-object. We can inspect this, to see what *pystencils* actually does." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "139920670537616\n", "\n", "Func: kernel (dst,img,w_2)\n", "\n", "\n", "\n", "139920670664720\n", "\n", "Block\n", "\n", "\n", "\n", "139920670537616->139920670664720\n", "\n", "\n", "\n", "\n", "\n", "139920666169168\n", "\n", "_data_img_22\n", "\n", "\n", "\n", "139920670656400\n", "\n", "Loop over dim 0\n", "\n", "\n", "\n", "139920657663504\n", "\n", "Block\n", "\n", "\n", "\n", "139920670656400->139920657663504\n", "\n", "\n", "\n", "\n", "\n", "139920670665808\n", "\n", "_data_dst_00\n", "\n", "\n", "\n", "139920670698640\n", "\n", "_data_img_22_01\n", "\n", "\n", "\n", "139920661915920\n", "\n", "_data_img_22_0m1\n", "\n", "\n", "\n", "139920657676048\n", "\n", "Loop over dim 1\n", "\n", "\n", "\n", "139920657567760\n", "\n", "Block\n", "\n", "\n", "\n", "139920657676048->139920657567760\n", "\n", "\n", "\n", "\n", "\n", "139920662243472\n", "\n", "_data_dst_00[_stride_dst_1*ctr_1]\n", "\n", "\n", "\n", "139920657567760->139920662243472\n", "\n", "\n", "\n", "\n", "\n", "139920657663504->139920670665808\n", "\n", "\n", "\n", "\n", "\n", "139920657663504->139920670698640\n", "\n", "\n", "\n", "\n", "\n", "139920657663504->139920661915920\n", "\n", "\n", "\n", "\n", "\n", "139920657663504->139920657676048\n", "\n", "\n", "\n", "\n", "\n", "139920670664720->139920666169168\n", "\n", "\n", "\n", "\n", "\n", "139920670664720->139920670656400\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ps.to_dot(ast, graph_style={'size': \"9.5,12.5\"})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*pystencils* also builds a tree structure of the program, where each `Assignment` node internally again has a *sympy* AST which is not printed here. Out of this representation *C* code can be generated:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
FUNC_PREFIX void kernel(double * RESTRICT  _data_dst, double * RESTRICT const _data_img, int64_t const _size_dst_0, int64_t const _size_dst_1, int64_t const _stride_dst_0, int64_t const _stride_dst_1, int64_t const _stride_img_0, int64_t const _stride_img_1, int64_t const _stride_img_2, double w_2)\n",
       "{\n",
       "   double * RESTRICT _data_img_22 = _data_img + 2*_stride_img_2;\n",
       "   for (int64_t ctr_0 = 1; ctr_0 < _size_dst_0 - 1; ctr_0 += 1)\n",
       "   {\n",
       "      double * RESTRICT  _data_dst_00 = _data_dst + _stride_dst_0*ctr_0;\n",
       "      double * RESTRICT _data_img_22_01 = _stride_img_0*ctr_0 + _stride_img_0 + _data_img_22;\n",
       "      double * RESTRICT _data_img_22_0m1 = _stride_img_0*ctr_0 - _stride_img_0 + _data_img_22;\n",
       "      for (int64_t ctr_1 = 1; ctr_1 < _size_dst_1 - 1; ctr_1 += 1)\n",
       "      {\n",
       "         _data_dst_00[_stride_dst_1*ctr_1] = (w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1])*(w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1]);\n",
       "      }\n",
       "   }\n",
       "}\n",
       "
\n" ], "text/plain": [ "FUNC_PREFIX void kernel(double * RESTRICT _data_dst, double * RESTRICT const _data_img, int64_t const _size_dst_0, int64_t const _size_dst_1, int64_t const _stride_dst_0, int64_t const _stride_dst_1, int64_t const _stride_img_0, int64_t const _stride_img_1, int64_t const _stride_img_2, double w_2)\n", "{\n", " double * RESTRICT _data_img_22 = _data_img + 2*_stride_img_2;\n", " for (int64_t ctr_0 = 1; ctr_0 < _size_dst_0 - 1; ctr_0 += 1)\n", " {\n", " double * RESTRICT _data_dst_00 = _data_dst + _stride_dst_0*ctr_0;\n", " double * RESTRICT _data_img_22_01 = _stride_img_0*ctr_0 + _stride_img_0 + _data_img_22;\n", " double * RESTRICT _data_img_22_0m1 = _stride_img_0*ctr_0 - _stride_img_0 + _data_img_22;\n", " for (int64_t ctr_1 = 1; ctr_1 < _size_dst_1 - 1; ctr_1 += 1)\n", " {\n", " _data_dst_00[_stride_dst_1*ctr_1] = (w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1])*(w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1]);\n", " }\n", " }\n", "}" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ps.show_code(ast)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Behind the scenes this code is compiled into a shared library and made available as a Python function. Before compiling this function we can modify the AST object, for example to parallelize it with OpenMP." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
FUNC_PREFIX void kernel(double * RESTRICT  _data_dst, double * RESTRICT const _data_img, int64_t const _size_dst_0, int64_t const _size_dst_1, int64_t const _stride_dst_0, int64_t const _stride_dst_1, int64_t const _stride_img_0, int64_t const _stride_img_1, int64_t const _stride_img_2, double w_2)\n",
       "{\n",
       "   #pragma omp parallel num_threads(2)\n",
       "   {\n",
       "      double * RESTRICT _data_img_22 = _data_img + 2*_stride_img_2;\n",
       "      #pragma omp for schedule(static)\n",
       "      for (int64_t ctr_0 = 1; ctr_0 < _size_dst_0 - 1; ctr_0 += 1)\n",
       "      {\n",
       "         double * RESTRICT  _data_dst_00 = _data_dst + _stride_dst_0*ctr_0;\n",
       "         double * RESTRICT _data_img_22_01 = _stride_img_0*ctr_0 + _stride_img_0 + _data_img_22;\n",
       "         double * RESTRICT _data_img_22_0m1 = _stride_img_0*ctr_0 - _stride_img_0 + _data_img_22;\n",
       "         for (int64_t ctr_1 = 1; ctr_1 < _size_dst_1 - 1; ctr_1 += 1)\n",
       "         {\n",
       "            _data_dst_00[_stride_dst_1*ctr_1] = (w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1])*(w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1]);\n",
       "         }\n",
       "      }\n",
       "   }\n",
       "}\n",
       "
\n" ], "text/plain": [ "FUNC_PREFIX void kernel(double * RESTRICT _data_dst, double * RESTRICT const _data_img, int64_t const _size_dst_0, int64_t const _size_dst_1, int64_t const _stride_dst_0, int64_t const _stride_dst_1, int64_t const _stride_img_0, int64_t const _stride_img_1, int64_t const _stride_img_2, double w_2)\n", "{\n", " #pragma omp parallel num_threads(2)\n", " {\n", " double * RESTRICT _data_img_22 = _data_img + 2*_stride_img_2;\n", " #pragma omp for schedule(static)\n", " for (int64_t ctr_0 = 1; ctr_0 < _size_dst_0 - 1; ctr_0 += 1)\n", " {\n", " double * RESTRICT _data_dst_00 = _data_dst + _stride_dst_0*ctr_0;\n", " double * RESTRICT _data_img_22_01 = _stride_img_0*ctr_0 + _stride_img_0 + _data_img_22;\n", " double * RESTRICT _data_img_22_0m1 = _stride_img_0*ctr_0 - _stride_img_0 + _data_img_22;\n", " for (int64_t ctr_1 = 1; ctr_1 < _size_dst_1 - 1; ctr_1 += 1)\n", " {\n", " _data_dst_00[_stride_dst_1*ctr_1] = (w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1])*(w_2*-1.0*_data_img_22_0m1[_stride_img_1*ctr_1] + w_2*_data_img_22_01[_stride_img_1*ctr_1] - 0.5*_data_img_22_01[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 + _stride_img_1] - 0.5*_data_img_22_0m1[_stride_img_1*ctr_1 - _stride_img_1] + 0.5*_data_img_22_01[_stride_img_1*ctr_1 - _stride_img_1]);\n", " }\n", " }\n", " }\n", "}" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ast = ps.create_kernel(update_rule)\n", "ps.cpu.add_openmp(ast, num_threads=2)\n", "ps.show_code(ast)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fixed array sizes\n", "\n", "Since we already know the arrays to which the kernel should be applied, we can \n", "create *Field* objects with fixed size, based on a numpy array:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
FUNC_PREFIX void kernel(double * RESTRICT const _data_I, double * RESTRICT  _data_dst)\n",
       "{\n",
       "   double * RESTRICT _data_I_21 = _data_I + 1;\n",
       "   for (int64_t ctr_0 = 1; ctr_0 < 202; ctr_0 += 1)\n",
       "   {\n",
       "      double * RESTRICT  _data_dst_00 = _data_dst + 601*ctr_0;\n",
       "      double * RESTRICT _data_I_21_01 = _data_I_21 + 2404*ctr_0 + 2404;\n",
       "      double * RESTRICT _data_I_21_0m1 = _data_I_21 + 2404*ctr_0 - 2404;\n",
       "      for (int64_t ctr_1 = 1; ctr_1 < 600; ctr_1 += 1)\n",
       "      {\n",
       "         _data_dst_00[ctr_1] = -1.0*_data_I_21_01[4*ctr_1 + 4] - 1.0*_data_I_21_0m1[4*ctr_1 + 4] - 1.0*_data_I_21_0m1[4*ctr_1 - 4] - 2.0*_data_I_21_0m1[4*ctr_1] + 2.0*_data_I_21_01[4*ctr_1] + _data_I_21_01[4*ctr_1 - 4];\n",
       "      }\n",
       "   }\n",
       "}\n",
       "
\n" ], "text/plain": [ "FUNC_PREFIX void kernel(double * RESTRICT const _data_I, double * RESTRICT _data_dst)\n", "{\n", " double * RESTRICT _data_I_21 = _data_I + 1;\n", " for (int64_t ctr_0 = 1; ctr_0 < 202; ctr_0 += 1)\n", " {\n", " double * RESTRICT _data_dst_00 = _data_dst + 601*ctr_0;\n", " double * RESTRICT _data_I_21_01 = _data_I_21 + 2404*ctr_0 + 2404;\n", " double * RESTRICT _data_I_21_0m1 = _data_I_21 + 2404*ctr_0 - 2404;\n", " for (int64_t ctr_1 = 1; ctr_1 < 600; ctr_1 += 1)\n", " {\n", " _data_dst_00[ctr_1] = -1.0*_data_I_21_01[4*ctr_1 + 4] - 1.0*_data_I_21_0m1[4*ctr_1 + 4] - 1.0*_data_I_21_0m1[4*ctr_1 - 4] - 2.0*_data_I_21_0m1[4*ctr_1] + 2.0*_data_I_21_01[4*ctr_1] + _data_I_21_01[4*ctr_1 - 4];\n", " }\n", " }\n", "}" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "img_field, dst_field = ps.fields(\"I(4), dst : [2D]\", I=img.astype(np.double), dst=filtered_image)\n", "\n", "sobel_x = -2 * img_field[-1,0](1) - img_field[-1,-1](1) - img_field[-1, +1](1) \\\n", " +2 * img_field[+1,0](1) + img_field[+1,-1](1) - img_field[+1, +1](1)\n", "update_rule = ps.Assignment(dst_field[0,0], sobel_x)\n", "\n", "ast = create_kernel(update_rule)\n", "ps.show_code(ast)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compare this code to the version above. In this code the loop bounds and array offsets are constants, which usually leads to faster kernels." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Running on GPU\n", "\n", "If you have a GPU and [cupy](https://cupy.dev/) installed, *pystencils* can run your kernel on the GPU as well. You can find more details about this in the GPU tutorial." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
FUNC_PREFIX __launch_bounds__(256) void kernel(double * RESTRICT const _data_I, double * RESTRICT  _data_dst)\n",
       "{\n",
       "   if (blockDim.x*blockIdx.x + threadIdx.x + 1 < 202 && blockDim.y*blockIdx.y + threadIdx.y + 1 < 600)\n",
       "   {\n",
       "      const int64_t ctr_0 = blockDim.x*blockIdx.x + threadIdx.x + 1;\n",
       "      const int64_t ctr_1 = blockDim.y*blockIdx.y + threadIdx.y + 1;\n",
       "      double * RESTRICT  _data_dst_10 = _data_dst + ctr_1;\n",
       "      double * RESTRICT _data_I_11_21 = _data_I + 4*ctr_1 + 5;\n",
       "      double * RESTRICT _data_I_1m1_21 = _data_I + 4*ctr_1 - 3;\n",
       "      double * RESTRICT _data_I_10_21 = _data_I + 4*ctr_1 + 1;\n",
       "      _data_dst_10[601*ctr_0] = -1.0*_data_I_11_21[2404*ctr_0 + 2404] - 1.0*_data_I_11_21[2404*ctr_0 - 2404] - 1.0*_data_I_1m1_21[2404*ctr_0 - 2404] - 2.0*_data_I_10_21[2404*ctr_0 - 2404] + 2.0*_data_I_10_21[2404*ctr_0 + 2404] + _data_I_1m1_21[2404*ctr_0 + 2404];\n",
       "   } \n",
       "}\n",
       "
\n" ], "text/plain": [ "FUNC_PREFIX __launch_bounds__(256) void kernel(double * RESTRICT const _data_I, double * RESTRICT _data_dst)\n", "{\n", " if (blockDim.x*blockIdx.x + threadIdx.x + 1 < 202 && blockDim.y*blockIdx.y + threadIdx.y + 1 < 600)\n", " {\n", " const int64_t ctr_0 = blockDim.x*blockIdx.x + threadIdx.x + 1;\n", " const int64_t ctr_1 = blockDim.y*blockIdx.y + threadIdx.y + 1;\n", " double * RESTRICT _data_dst_10 = _data_dst + ctr_1;\n", " double * RESTRICT _data_I_11_21 = _data_I + 4*ctr_1 + 5;\n", " double * RESTRICT _data_I_1m1_21 = _data_I + 4*ctr_1 - 3;\n", " double * RESTRICT _data_I_10_21 = _data_I + 4*ctr_1 + 1;\n", " _data_dst_10[601*ctr_0] = -1.0*_data_I_11_21[2404*ctr_0 + 2404] - 1.0*_data_I_11_21[2404*ctr_0 - 2404] - 1.0*_data_I_1m1_21[2404*ctr_0 - 2404] - 2.0*_data_I_10_21[2404*ctr_0 - 2404] + 2.0*_data_I_10_21[2404*ctr_0 + 2404] + _data_I_1m1_21[2404*ctr_0 + 2404];\n", " } \n", "}" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "try:\n", " import cupy\n", " from pystencils.gpu import BlockIndexing\n", "\n", " gpu_ast = create_kernel(update_rule, target=ps.Target.GPU,\n", " gpu_indexing=BlockIndexing,\n", " gpu_indexing_params={'blockSize': (64, 1, 1)})\n", "\n", " ps.show_code(gpu_ast)\n", "except ImportError:\n", " print(\"Please install cupy for GPU support\")" ] } ], "metadata": { "anaconda-cloud": {}, "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.11.0rc1" } }, "nbformat": 4, "nbformat_minor": 4 }