{ "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.65 ms ± 22.7 µ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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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "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": [ "951 µs ± 15 µ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": "iVBORw0KGgoAAAANSUhEUgAAAKoAAAAYCAYAAABqdGb8AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFRElEQVRoBe2b63HUMBDHDyYFAKmA0AGPDpIOklBBoAMYvuVbBjoIVACkg4QK8ugAOoC5DsL/ZySNzpb8kGXd3eCd0UnW8+/d1e5KTh7c398vhtLp6ekjjflgxu2Z/ET1y6Fzzf1nDlgOtOnVju00MP+oSd/aMSqfq3yr9MzWzfnMgQQORPXqYcJkDHkj5dz3xn5UeU91z726uThzYCgHonqVqqhY05uhKOr9pdg2bKg3zc8jOLDFfI3qVZKiihGflfx4lAV+qe6uL3/V9536zha4L8NMP/GtwTPVPVLyNz3eDf5uFQlzVK9SY1THAE0O4w6VXrjKjoLG0H9X+aeOrnNzkwM/xDcOs9YoUIYc/9V+pfRcCVf6+V/zdv2CX4idXiVZVPvKmoxdTHz6QmXfwtoujVz9YOwH5e8bjXNFHw78USd4jSDh5YVSg//iL0bgreG3ittDwtzQq2SLaiZ7r/wAFphn8l8dLEGxuSUoQsLDoQ9XuBGWJQOeO81x1JN58Bl+uxuanuNctwx43Vx9CloPJW3oVZJFNZPBhHOVcTHsbiwku72LjtW/pNJgdUibQsXwGD7D7zHvXxIvShrUq1SLyp0pL0DuSAxp3blqJ+bosrhuvrmQhQPw+1ippHFIBR7VK6eoZte9MSu8Un6ihIa/NnXX6kM8tFD+2NQNzQgTrmKDhmCIzZGz3sPDh4xbPTthm7Yvyvu64WzQtCZywlDsKiGjM9XZw5UeVwh+w3eHfaV14gfDp9F65bt+vgp8Ign7tdIXpX0949JhCrHOWHqpCX62TFICQ8vyjSYOffDjUqn+/lgpPERpQhbfjKyQDYlNRCweIviNMq+Lssi0UlS9JBrvC2KpZ4Rgd+GTWrsekwgmB+PYghh6ARce4m42LIRFquNu9Q7VqAl+hOtACflUpDKuHasZO6CCey2KKmzZ9Mq6/hvzwtXL64c7OU6XFUOU53JvKLxjsl3M5KMwCCOCClkV1lyoPRQ/t52g/Q8YWM8z5vGItep1rnkCPG7uQAFlxftxu1E/A/CMgWilifCOkqkPuFJUgazHNyHB+OOyl8di0PiQIi5Uj2dAgIM+Lqi/3aQoJIK23oU5sbbUtcXbWfFoLdYlBHmi3F3uU+9RSCHbjIMbqjmnwJtNryrX79CqIMBWMNXBiTbV8YkuxAR/aJ8ybqhznokx9MHp98GbOO9iGuDRUjjrgvDHTVEmxg/xz3qNEB76w/e10liZ7hgF/K63IOjFQrCzEILvQnJ9SWLORrxUGIMgDCLw+rxg8FriU63Lt3AOT3Vi48SsO0pcx18fn/05t0yxqLwk6Y+ZfGX3qY42e6hQcRSx47n6qlNJDPW1u55XhGz4AV7ccGniA8vKoUnP7wyI2DmiOm+UBqr1ssp0RxOyE4m/mHihF+f78KUSDOECFgV2YQB9RtBXjcV616kkhvraXc9YMO5LuRX5rWT/ODxmwbrmS24XBg54eD6rrFhLDMtT1S0jEyPXYPwZ6Z+rOqtMH6T8K8qYNxFDudc7Uo51nZy0TtJhKgZM86Gw+8pjB5rY0Ko+N562xbQWYQtGx26utu7BtpJ4gwBMJa6/NCHokjscSxOzNq3vLiFhvdxnYpU5mHA3eNY6sL0xGU/7tMFWvAH8HkMl8UZxFreoIJHAie8IMVbivyjKNTUIH9afuLC62lJO2IL7DR1o1oQyvKwwYk3BzsFv64kYdR1E4I/QN52JKCT3lhxYcJ8IvnhsqnVTiDi2pOdKwdh7zFosKugkcHb8ofJBF/G93+w/7iiesrEulG+0xxoior/cLWUUiDm0fAAAAABJRU5ErkJggg==\n", "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": "iVBORw0KGgoAAAANSUhEUgAAAIYAAAAXCAYAAADOQzd3AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEbUlEQVRoBeWZ7VHcMBCGD4YCSNIB6QBIBUAHkFQAdBCGf/xjoINABYR0AKmAjxJIBbm5DsjzGMsjzNnBn+eb2xmdZFnafbVa7a58S8/Pz6OqdHJyssacbcqYYvsL5ZT+R+pBExhXAXicghS7tE//5KW5OL9lulipqYYz5j3B+ML51N+pflM++DxwOgPvYcBI+wftB8rn0LdAdaEulmsq4ZR5V9HcT7T1HvNABxiD3i6QRr5G33roWKC6UBe1PAZKzIeMXZSpgueB9Bb38wC0B4yFuliqk2MEwBiIIeQb5Yr2eeifpxrcGvQu9SKGkldbFeuikWHIFWYmc9eUG9pzZRzgNXyIfYP2XCSf4Mx7Zg9l3oOzpGqU10Vjw1B8ytQETgU3BlltSfVGg9MbiYnnHu3BG0WKVyM+on3rqql9Xqdu5O2Y/0YXlXMMmOgh/lC2aAcjCIrdpD/00Xwfwcdk0AQwueW8b1b9UchRESp4Ry7ps/VTfa7lM+HddI0agd4hMYpUmnvRCDP8puqijmFMYDbOAXLRGsdPSh1ygZbOKVWEnkLDCDcRk7CjjoXXXiM4Te7FuhVjpD8x7LivSrtMF5UNIxW8R30M47/psx+45iVOG/LcJOuMWIvGMVQSmwdy0jLAQl1khoFQlXWQCnaj9ym6GW8d0h1jftmgNlxUDhnO7YIqYv/vR7iIn7H7gecsxKXvLqk9HH2RIdoPinoN98MDKbZr+uLQMuK5yj4W6mIZ5oH8CnZuoeOOcknZ5lkXq7B8NkzXYKht7HpD9XBDya/7K3269j5J/Uub7ofYKHoRDSOPpRVdJIYBcz1FrIAJzwoMJ+Vj7j2Pw6C2scPPU+nBkIzh5lMx2ffqlMYv226DJxiFt4+wH0GMOZ3eKxlD3do+hlByD9M4u91A4CN9GsiIuhW3CR+TPhPVPGl4ypkW58VRJr9t7LrsECb1Dqc5sOLP92VDmNvFGuUf70+QZ46gMRhqNNbWdJEYRqQIeCc0TSHhXe0aOdM2fkS/3snrqu67EjEnbGKY1wg7/MJh0AA8idkp5Z3exL5Cj8GYVtcoHgoik1uf9TQyFxwxrjVdxDlGIhDmQSFJopkKXKVfhQyaWsaul8q8ZrpwdeNG5Tega71oiGX6f+NNmupiJd1wP56YtAhAi3fxsTCTsa7v+YitRh1j9xTGOhBcr/lFpA3Dk3uUJ0O+e3Xbti70GJ4CyzhlPqadEX2+C8lY1j+QRpfYXxlFqgfleVPplZCt93bzswsCbT2IYdPPClKruliBoV7COCrjEQIPKf4hppWa3GgwWVhxzICoS+x6SDN+NyN8N3DpyuydwLEjFor7Ipmwx39LtKqLVv5ES2A2+GGxtZPPBmIrTXVTmLBNrfuuTPOwxnhRb5LP+GWP7QmyLIMgjYCit0yItm7ba2HhNfVlZOnvoNZYipSXhpKZE4qfiXsuWbgeLLhsh/kV+AKctUPqANfougppEKGkEN2MXrCJGoYxXE8x9T+JGUHrTew/A0Y1bcMwkZ8AAAAASUVORK5CYII=\n", "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": "iVBORw0KGgoAAAANSUhEUgAAAHoAAAAYCAYAAAA1Zem1AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEpklEQVRoBe2Z3VEjMQzHF4YCcpQQOuCjAkIHwFUA6QCGJ3hjuA6ACjjoIFwFfHQAHRyXDrj/b1l7nMVrYidr8rCaceSVLVkrWbK8WXp/fy9i4ezsrCeek4qvX+ED0cexsrr5eSywkrjMhZw6NLzqX6r/pLZmaB1eLAssJ6pzKOcOHN4L9fuirTu0rrtAFkh1NNH8OOt7aGOYtD+rqI7fsYDPrkmOlqArNfc8xvGvoj076wW7mnukCV0GCFopeZDsin0tLKUUY5ZbHQnEWbdqG+q7znenTfQ1b1eELeHjiYHuYWoLyHYcly7ciGYDTX0cPRa+YtJMjpYQUi+F2J760zq5p/l/NH9DuINIC1Q2J7CO1b+HXZjndeGJYljPFMjbwuPUqrsQM05msR1h8wx+5TkA7EQ2RxaQPhSNpLJyZ2dZNLDIHPTBqURv6eRqKYLHZ3fsjL2HSY7WIiaScbQ5Zzmnp0nF++KxVzPxtA0YgbYokKyP7MaRh7233ZcRvQw2l0ZfdGqpf2rHSY6WDFICCoMtSGDQgRpHUd/OszK6TtAC2JdzdxycNTmIvfeto8WM4w6rOVvCB2pE7s+K9qA5d/SFf1S0WMTOc1POBH+MDhOMLT04+nD2PenZpv9q7Fp4r6XlfWI3ReR2Q1Tjl79q6HYrWpNdoe8s68cAX7t+0UR4ULtWG+iZdMwmqFd5IkUDir4EuHLoEFj+09BJZY+RRurvvy8aGSon4AdgE7+gmxpRjqObdMHe/dLRmkQkuy9CaoDR7ODV2rgekwBF33ycGXXwLf+JJn2IGjY8QCaq6x3MTiXXHH+kj3Ey1bXxi1nhtzpkFzPH0MHo3Tep+1GT3LOTq8+zaDi8EJ5XemLDlDKRW4OZdJCOVJiDmkweWZN38NUPvGPTu7kfgIjec+Q4wFp1mh1uQR8j2/WToVErEaxkzHoKZ36vdLSUshdtEQHfi32MtPQ7qw7i9zmyEJ3MxPWKI2lq0HyzyXEokWKjSGNEO7S6UUX6AM2Zuz6SifCmQGGMmqoOZXC5Z3Q5QcLMi5WFF0TRerS6hIRn0siXclrWIVZtIt5mt4oZG1H91gMkVnbsfDZWyH6+aGf+20rlQC7hFEIIYifyEi4TRck0d2SxBgGZn3ZdZh2CCnoG0de1BVOyns+OThxP+KoOHLX4zJdhiOhXIprdSXurDE7UWRCNMVOUWHpihwjY8vDm1MGzfJA04eTKHuhLJZ4VtDZZ9l7YFs7qE7EctQcNypT11gqMapw/KF+Icag2UmP3cMizAWwaZ84McCNe347MqUOs+mQyKlqMa+6tyPBFD/RWQXrsoIsa/gGIWL5nNx0j+HU4058arBILUoh7HX+CNCkWKzI4X+skFWNNQiUPhw+Ek/6Umbc+TXpC11ocOwTtGqk7N2Aob0XakiJjyaVFgwxE5JDVSlCfNMk15vyDkvSbrE/CamQj7F0U/B+du52eno7U+rnXjV1POr6oHRk+9W/VLszzImPp2VcbGR05o78DuLJwVlO9LjIQEauK5CNhvilfqv8tZ3OCkTjDbebMfkYbhWUwzo9d4agPGYa/w80WqDbmnbC9MfwH+JA6Vr0VD58AAAAASUVORK5CYII=\n", "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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\n", "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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\n", "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": "iVBORw0KGgoAAAANSUhEUgAAAH8AAAAzCAYAAAC+J9cEAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEwElEQVR4Ae2d7VEUQRCGD8sAUDPADAAj4MhANAIgAyl+wT8LM0AiQMhAjQAkA8lAvAz0fZaZrblhl9u728+b7qpmZmd3Z7r7ne752ivWTk5O1kcFdHp6OikotqIBWkBYFmL8Qrr8Ev+N+GyAOprI5Ra4ivAF76s1ef5vZQ7VO36Uv2t3VskCwvpA+uy9XCWlVl0XgbYpHfHiLeWXHpYN/J73GIHMeH0hfhBvizfEtZCBX4sZm6vEefgeLSj/SQneXwsx4TNK1AIGfqLAo7aBb+AnbIGEVTfPN/ATtkDCqpvnG/gJWyBh1c3zDfyELTAs1d84cV/XIbZt79ZhxYbr0LYuhznQ+DEZXansXvnvSr+6srmTpMGX4eLvFi5Vdje3FRt+QTJle/t1N5Mk+DLmhgyJNx0pn33HoJRr+K04CUp1wgfIeHn4AQtHp4TSuUn1jMV8IDEoSs7zBdJ7IcSx6E6IlMp3w+s583QceFCUoucfCqGJwJ4MCqkGhG3c82VkPMKHxHfK74sZcz+KoRs9c/2YbeUvX8Pcq028Hxn+iBnnmUGHw4CKVpva8PwzGfULLFPeiPkkiTHySCkdI55xq6hR8uF5GxmcbEQDwGdISIYaBV/GxONDcAm1GNivTdmsCO/rsjmSPB74TeW9DL7Bb8pcBM/48pVNnw37zhA/pb03WhVD7Ok9v1a+VT6cQW+pgjuV0QlGSmeuX/UMbS8jA03FFMrk7/H7BTorw0Jh+Jcs57o3FseU7bjpPhEkJvStoue/+MU6rtX2Wlk9s8AHJABbiNSw7wT+/Q/KfPYXVVLVsZQMYRvUJaYo63zhvSDPfKSQ9G4RuCOVE802lDK0LUR6txSkhSqs8FKjYT9sX8rhMXhxPrlT2TocPtdCHq9+rs2iqNCCWO038aznLyOOA5XNFCZ8GByvwfNC4x7rmolfm0ToRq6YiHDIVxjy44dX4box8GUcPB1mRo2n8aODnFTGPWb/rZLavQZgMZ0y63hKkY8hiWXoYMjJfewE9sPVvsonVZRoEnw8iBk1II8kEL8H5BQKz2Ny9aB8PgTwTFukdnfFgI8sEBO2HV3Hc5TsZo//oEM+D3H6YNtK5xONgS9B6H25YBhQZYDdCeC0H5JkaXu4CZuvK38gPcLNKZbNlLGUndmRW5vw1aVtT+uho1cKtTXLj3PdLlpnY56/qEBDfE9e1skkUe3GG1V0BrauZ3o9djbPH2JvK5BZgHNWwX5D5X0ZA7/AkEMrEvDM9Bnv5/rdvoX9niEtINclUuVTUAc8y+nsewR3PVIa7qcUammeX2iWTgtZvlU6BXVAs1w9V54ZPqGfVczUnkqZNub5ZZbpoFzglZ2C+s0n9iPCU1DW9EQK0pxUz9QSO78RZQz8yCAdX851CiqQXy0jr4X9ZaxX87sCM16iseV8WXMzeXUGfm6KfmXUEcaSiJCe74iqrNZTUAv7PcEcYCVKq6eg5vk9AV9i4OkwB150hKkZu4sEtZ6CmufLyj2h1k9BDfyeIC/PnkiUqSWayho9BbWw3xPwuxDDwO/C6j1p08DvCRBdiOHHfD47ZoaZkxuD8mvLDNcCMbbSJMPaez6HA+E/XAj3j4ertUnuLcD+wRN8/wN7Ln/yKtUUoQAAAABJRU5ErkJggg==\n", "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": "iVBORw0KGgoAAAANSUhEUgAAAKoAAAAYCAYAAABqdGb8AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFRElEQVRoBe2b63HUMBDHDyYFAKmA0AGPDpIOklBBoAMYvuVbBjoIVACkg4QK8ugAOoC5DsL/ZySNzpb8kGXd3eCd0UnW8+/d1e5KTh7c398vhtLp6ekjjflgxu2Z/ET1y6Fzzf1nDlgOtOnVju00MP+oSd/aMSqfq3yr9MzWzfnMgQQORPXqYcJkDHkj5dz3xn5UeU91z726uThzYCgHonqVqqhY05uhKOr9pdg2bKg3zc8jOLDFfI3qVZKiihGflfx4lAV+qe6uL3/V9536zha4L8NMP/GtwTPVPVLyNz3eDf5uFQlzVK9SY1THAE0O4w6VXrjKjoLG0H9X+aeOrnNzkwM/xDcOs9YoUIYc/9V+pfRcCVf6+V/zdv2CX4idXiVZVPvKmoxdTHz6QmXfwtoujVz9YOwH5e8bjXNFHw78USd4jSDh5YVSg//iL0bgreG3ittDwtzQq2SLaiZ7r/wAFphn8l8dLEGxuSUoQsLDoQ9XuBGWJQOeO81x1JN58Bl+uxuanuNctwx43Vx9CloPJW3oVZJFNZPBhHOVcTHsbiwku72LjtW/pNJgdUibQsXwGD7D7zHvXxIvShrUq1SLyp0pL0DuSAxp3blqJ+bosrhuvrmQhQPw+1ippHFIBR7VK6eoZte9MSu8Un6ihIa/NnXX6kM8tFD+2NQNzQgTrmKDhmCIzZGz3sPDh4xbPTthm7Yvyvu64WzQtCZywlDsKiGjM9XZw5UeVwh+w3eHfaV14gfDp9F65bt+vgp8Ign7tdIXpX0949JhCrHOWHqpCX62TFICQ8vyjSYOffDjUqn+/lgpPERpQhbfjKyQDYlNRCweIviNMq+Lssi0UlS9JBrvC2KpZ4Rgd+GTWrsekwgmB+PYghh6ARce4m42LIRFquNu9Q7VqAl+hOtACflUpDKuHasZO6CCey2KKmzZ9Mq6/hvzwtXL64c7OU6XFUOU53JvKLxjsl3M5KMwCCOCClkV1lyoPRQ/t52g/Q8YWM8z5vGItep1rnkCPG7uQAFlxftxu1E/A/CMgWilifCOkqkPuFJUgazHNyHB+OOyl8di0PiQIi5Uj2dAgIM+Lqi/3aQoJIK23oU5sbbUtcXbWfFoLdYlBHmi3F3uU+9RSCHbjIMbqjmnwJtNryrX79CqIMBWMNXBiTbV8YkuxAR/aJ8ybqhznokx9MHp98GbOO9iGuDRUjjrgvDHTVEmxg/xz3qNEB76w/e10liZ7hgF/K63IOjFQrCzEILvQnJ9SWLORrxUGIMgDCLw+rxg8FriU63Lt3AOT3Vi48SsO0pcx18fn/05t0yxqLwk6Y+ZfGX3qY42e6hQcRSx47n6qlNJDPW1u55XhGz4AV7ccGniA8vKoUnP7wyI2DmiOm+UBqr1ssp0RxOyE4m/mHihF+f78KUSDOECFgV2YQB9RtBXjcV616kkhvraXc9YMO5LuRX5rWT/ODxmwbrmS24XBg54eD6rrFhLDMtT1S0jEyPXYPwZ6Z+rOqtMH6T8K8qYNxFDudc7Uo51nZy0TtJhKgZM86Gw+8pjB5rY0Ko+N562xbQWYQtGx26utu7BtpJ4gwBMJa6/NCHokjscSxOzNq3vLiFhvdxnYpU5mHA3eNY6sL0xGU/7tMFWvAH8HkMl8UZxFreoIJHAie8IMVbivyjKNTUIH9afuLC62lJO2IL7DR1o1oQyvKwwYk3BzsFv64kYdR1E4I/QN52JKCT3lhxYcJ8IvnhsqnVTiDi2pOdKwdh7zFosKugkcHb8ofJBF/G93+w/7iiesrEulG+0xxoior/cLWUUiDm0fAAAAABJRU5ErkJggg==\n", "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", "\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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\n", "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": "iVBORw0KGgoAAAANSUhEUgAAACkAAAAdCAYAAAA3i0VNAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACxklEQVRYCdWX7VEbMRCGwUMBHtKB00E+OrA7cOggoQOY/LL/MdAB6YCBDgwVJEMHcQd46MB5HvlOnGXd4bPHmOzMWqvV7urVrqSTD+fz+UFbGo/HXXxO4FPkz23929oftXUA1Cd8+oWfYHdOm4B8BNUjYIc7R1dM0HmribaZ578A2VhuSnpNBp6LLFzQL+VtEtPatzaTALok2jF8A/+Ay8OC+LZUCxIYAhOgQP/A9/BeKFtusmjWvF7uixLvDaBZyYJEP4CnuT2IrsfYKexCevTdFk+0V7Q7ocPqF4eJLLEABTCDvRNn6AW1PxJkyqPRaA6fpfp99VcODlmznNJe9+ECwuJ3BSRqv80HgLXU74JyIL+CbNqEjgUMKxlvMt1ojNhnVcfc6TaTtVkkgIfLkx8Xgux11erpho+3gvQEf6B/Hnr8IF/B13A4sLlMfsHud+lQbXESzIA27ldkFyVwx+RXCR/tvbbOYa+uCe0kcbxFFxayBBJlD0MnqsukTn7PI+Hjs82JYmbjYL1gnOpClfvF/MELudR1l0AyWh6aGCB4vPwYqG7sxapBKoCYiHRRz+jC/BX3ADQF6UV+VzGKYhHcQNuSAHM0Q3mcDLjtBh0m96TeFoPux5vEsOy6FQy0KxJgugCT0jOT7o8uQIMBbTaTRQCdtqW6GM6fboGQlBKkJ0uw3xoQGCAspMHm1SGSYByBWpmU0gOrzfQIp1+pZa6Pnac4FzhnHnX4uLCftPEepH8B+4gJoBhTvqNNM+l8f5deQSgaiSBm3P/aMRiygcqnm6fT6yg+3RhX9wB/R45bCdnKSV7mH2HvTDMciX6Yb+UF1PTS4WXUhy+bbOrG8BvWjeX02HfhiWOdCHsNgZV5b3nIWpUde8vZ9n4107WfxUa4TKijJW9ziHzBL5WyaRJs/Wz6WVxsq1yq19FZjnXsNrFJY/8DzKtH71g9xXgAAAAASUVORK5CYII=\n", "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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\n", "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": "iVBORw0KGgoAAAANSUhEUgAAAyUAAAArCAYAAABmdidyAAAACXBIWXMAAA7EAAAOxAGVKw4bAAATX0lEQVR4Ae2d77XctBbFh6wUEEIFkA4SXgWEDuBRwQ0dhJVP8C0LOkhSAYQOAhVA6IDXAeF2cN/++Vq+Ho/skTT22B5vreWRLOvP0T577HMsy/7o5uZm52AEjIARMAJGwAgYASNgBIyAEZgSgR9++OGB2n9R9/FZHV8p//r+lB27bSNgBIyAETACRsAIGAEjYASMQI3Aj3JAvg1oKP1K6ffaHt0LmY6NgBEwAkbACBgBI2AEjIARMAITIvBMjsjTVvs/Kv2Z8h7bKWmh4qQRMAJGwAgYASNgBIyAETACkyHALMmfsdY/8pqSGCzOMwJGwAgYASNgBIyAETACRmBKBDRDwkzJV4ofDa4pUQGe83qn+NcpBXLbRsAIGAEjYASMgBEwAkbACKwHAfkHLFr/XdsXSl/nSq46j1XnK21PqNv7+JYK4pA8VGyHBKQcjIARMAJGwAgYASNgBIyAEagQqB2RK+28VxoHJTmoPG/eYpbkSd3OLvr4lg4+U6FvFVeeS3IPLmgEjIARMAJGwAgYASNgBIzAZhCo/YYvFX+dMmiVwyH5TnH1Fq56/9Ap0QGmUpiK+VTp7KmYFGFcxggYASNgBIyAETACRsAIGIHLQEA+wzuN5JXiwSesdByHhKexvmuNHOfku9jjW2904LUdkhZUThoBI2AEjIARMAJGwAgYASPQhwCzJG/kPxx7jItvkvBKYOKw8Zrg673Ht5TBYpO32j62UyIUHIyAETACRsAIGAEjYASMgBE4ioB8h7AePekxrm6D3bdvseDkpzU4JJIRT+xFPSCmggjVZ+pvk/41AukImE/pWK29pHW9dg1OL785Mj3Ga+7B/Fiz9vJkt67z8FJp/Ii/hRsfQ/xfbu3GKVFl1pJg3L/MbWSm8r2fqZ9JHne7bgTMp3XrL0d66zoHrW2WNUe2qffUUZsfqUitv5x1naFDHBFtv6lKtUYko2pVtL2mhFmH39TYWha3936mPhcElzcCQsB82g4NrOvt6Lp0pOZIKXLbqGd+bEPPjNK6ztc1y0B4i292aDslYT1JdiMzVcALi36mfiZ53K0QkFPLjNteUN4DbeERu71jC9oxnwqUsVJ9W9cFui6pslJ+MFRzpEThBXVWyhHzo0DXK61iXecrjpkS7D78iqxQPb7VOinQ0NGg8s9V6JFilDVLUN+vOx0jC9NGf3XyvZuJgDDkmUDCP9oeaWP6MvXZwN9VlvU+QQ/hLQzRb96o7Oxckqw7ybFZPmnsm9L3lnUN13PD1vgBPuZIHku2xhHzI48fpaWF8+z2gXWdrz1hhi2OzfiltsHXA3dbDzMln3OgbqRbJraPA7CYx7wk995n6mMCOy8NAWHJ69n+UMxHbX5SmvdIv1M6dabjg8rDDXSCQwIhm691Kt0Ni+ISwmmsm+GTxrppfW9J190/Xsr+1vmxtfNBCie6ZbbOEZ9DuowYdX9R9oF1naVbJjkq3yKnVljojjcT7mwfrS/FcPd8EUGyYCxzp3fI8F2ErEsXQljyDCBTbo1nq/R1vc9r3uDJsfCXyie/Ck5lF8MlBiZ5NsMnjXXT+t6CrjXGp6I1b0HpzgQe+x/vVGfT/Nja+eAoISIFts4RjX8z14uI+ifPEr6LsQ+s62x1/60a2etKglPChSvp0a1ssSasUJOEO/qVsVzv7xSnPmo0oXSrbBpnIuac/qH858IVh2UxM2RjI6yxcYHZEp82q+8N6ZrZSraSsFl+ANaGOFLCjVBnsxwxPwIFLj+2rot0XNnhwu6xtphdGW00OCVctFZlyNck4e49RmRYXM1UX/uz9dFBO7MXAZzT2B3VwA2ON7Mova2s8MBG+bRJfW9U1yX/yk3yA6DMkWS6bJIj5kcyP1Zf0LouVmGwG7nZm+2U0CtTLb1BisFxYSqG6bT32m+MV6U5MeEY/EfblbaH2nAQWCjNLAZrEvgoI2W+0UYgzQLq6AyN8lm1T3u0QcAYfqr80C/PwiMTcRN0nH7PHtRvwOcTdc6HY4KcO6UZ67eKK9kUM7ZFPXImmZD/WECvR4Paqh77UEGwgJAvldeQsu5rSVxiTIviEwJNGTau703puoRHG+cHkJkjR4izcY6YH0f4UXp4gfbBqnXdwvPctmlwSpLsxsCX+xIYo5Hw4Tbq/X2hssxK4IDwDuLG6Fb6y/rYO6XfaKsWSiveKZ9y/yq+VsyK/GomQzGGOe18rG0v6BgGOw5I9cYmxcgIMf7UVvWrvIN6OjZnCPgwLjBo48M3YNpGP1gwps+1RZ0y5Z87BOIgW19oj2GozC/ST9VOrTucNDgSxhqwWgSXGIhkWxqf+vAdK3+z+t6grks4s1l+AJY5kkSZzXLE/EjiR2mhRdkHF6DrgOdZbVPhxnpkOJBiNzZcuZ9SQQ0/VjnWFRB4hjR4QDsdw7DEGSFgaON48NamEMKJi/UIwSjlGPkHwqoMwPEauMZIVB5tUjb0o+RygmRr48PMUNfBA6OXQWKV5yOVzBxUONZjw3EhgCHhSvmVYX+7u4hfPO3BIJmr9T2hkPbRHXrnUTteI93GylwKQC0ztr6XqZelSGV+LEUTy5XDHFmubhYnme2DcVXSwXMu2/ToOaA9apyS4DQMGcAYluHxm/+qTmNgK/2njuER4TRgUHcfn8IIJbRnDth/oi20yX4IzDL8SpshQ+nQxlLXM/TiU8sONm2HjKGBW3DueIytwU1pDHhmhgbfPKFytPu7NuLU8LXqxXD/MNBA4Mg/A2WGDjFOZr6C0xr6N5eGUGsdG1nXtGx9t/Bde1L84JzBzY9uqP67Ot6cX1oFht6UZ360gLqEpM8he1q8JPtjb2Dn3JmAU722lMZlWzNfub14SnfY1ZPYph0xc+zTHU5JuPj0VpTw13SimFkMyjUORjimvOqCqP2u8Y13xsWvakPpEDBIm3bIVJkA0s+hUB3TNo5PMOL3DisfuegjenyvcMGO2uXNU+3Zn71WdKwXHxUMsgdDPNRtr+F5pjbeagvY8fgaeYNvLaj75eR6cqAtbbSDfrsh5A3iq/rMZD1U3CcTs2VVG4oXySUGXss2G5+64NcywbE+XGNVBvM0RutbCE2t60El9ByUTIPnm1g11Yk5HWF8vBK49/zV0575UQMj7Ca9vsTwP5YnmUo44nPIHbCrsT/uRJ42tRROMcr6P4fd0diIyoO/hIu1NW+Hl/c7pLeAWQzPGkfO86Pbpnkj2C99b3/36B6PGFWzGBoIhGkHFrB3B8dxCBSMbfZ3qkse9bm7x35o63P2Fbrt4NjstVGV0o/qsmAaYBuDmfbI18ZsQ1JQWWYr2DjZ4xQ0Qfss0q9kbTLjiZgDdiC72sL5ao8Rg4L1MnMHMGY2oxuqu63KjOqgVRj9BV22sm9n4zTu9pgXxyUEloxj8amXg+ojlU9tDKdIb1rfMV2XgKx2enXd195QHfOjuR4E+Ga5ttC5dDHK+SAMJCc2Ryr8F33NiPEjR8eh7JCuQ5kx4qF+dGwp1yWGujj7YE5dq+9T7dM5bdP2DfijNMYpua5LBcMzWgky6wDGdDDOIU07xJwP6mDkdteCYIRXd6LVLse5a9EE5e05GDpA2902drVM7QXU5CEjFxL6ZjsaVIfy/yhmIT93FHlbWLc/ZjL2nJVIw4ylkb0+Hsv7Rm01Br7Sr7Vdt9oDn/a0W+vQpElePBAcw3ZH1VR3R8b28ZBmHLFHzva4oTLoZVFcYgC1XGPwKYWDKXwKuE4Vb1bfMV2XgKx2UnS913RiHfPjDrW98wfZtf4mu7a0+jj5fHA3jPSUObJ/M7PW96KuGbVMe/xI1/BdyURd31UoTCX2M/t5x7reV7DwGMM+jdmhsbwpbNO2bbs/uMjePeV9qPOPGfAMYCeAWKRNuvF+6n3qdx+7qpwN6lC3FSgb8nhVbpii+4UydXuh+Js6UR0LmXWMkxCcpCpLdXF2cCy6zkFdJRrRTpCH/kk/VVyNmRqtvCGcuKvTHFcdLqYPtbXbiTpYKlMF1eHEy+MCoz2qc9vy8V/1jR4+KKb/KijNeNDj1W1OhQV3hm+0dWeiXimvq4/ndT0WtYdQ4aGyS+ISso3CJ43rKAcZu/qDYw1fAjjnitX3lvV9oOsS3FN03W03pY7KmB8CTjhwruA/8nMHx6mvLXR3wJEU3XXkLNpN6UdlzBGhKxzm4sgBP0qUnaLrkna7dVL6UZnZOSW5l2gfzKlr+kYvVWjpqMKJzFZenz1xdttUMgX5go9xO4Ajv/dV8ZpN5WJ3uJvqKoOhxaNbeG07xcGRYJfOY3f2AahdjrIEQGbBNQZrY8RqH1mYZmKqird9faKNAdE2MnbDU+VHn6XuFuzbV31kR86uE0N/OAjt/OoPq7zognu1xUxLMMypz8LwT7W9IV8xeXvfMNF+E2pZwOaJ0pSdI+AMgX/4RgzxF9qH1FVANm3gsvfIGXnaqBt0ikOG/j5VXjMepZfIJcZ2Mp9oJCMM8imjnVOKblXf59Z1iY7Mj/muLejLHEljrc8haTitpdSs552F2geznAuExSj2qdqZwzZ9UBO+sf1S/gD360IYmAx+MGhg7bvdTVnlQ+IDp0b5zFgchLo8dQ5C95j2uRt/UFb5yJs12IPObjMCcN3DGNMY1e2Ao4TTFHVKKCi5Yk5SFDfKh1CPB+LQPu1U+lDcdopC8cli9QemsTHs9alyB/qmQC1vSv0oJqp/di7Vco/Fpz2cjuwc5dOR+icfFt6b07fGPIeuS3RlfmzrfGCODCCg/+1irhkrOocMINp7aAnnHev6Vj2j2afibMwui+LcZkbN9RLbtLJh1dbezet227F0cEqoVE2HxwqdI08DZ1bihbYrpTGUdnUe+c2jQ+TXgQHjOEwVcEi6hECuAPRo/WqctMnsAopnvAQIVH1ostrzTzICNYY5XKLtqfkUk38SPsU6uuS8An3PoesSFYzBD9pg22wo4AdYbYkjm+VGGHgBR9bCjzDEnHiM805Of2cteyG6Pot9Kqzgealt+h/V7XvKqVfnwSlhNoK3VbFWYK4LGEYk6y2qgCxKsJ4EQ715dOj2aPXL8TFk7WuD9ruzFFM5QeBPf8RN0Lhjnm1z3IleBHK5REPg38eF3o5OPDAVn04Ua3XVc/Ud1bX+b9wQ4PHJY4F1cN1zw7E6JcdP5ofkPJhlLhFk5XVy+cFwoxwpwWFiXp3MkZIxXWCdXI5E+TGxrhvYJ+7n0jm1Jl332STwr3sNmkJvp9imXE//akibmAhOCYvIX2nDKeh9NCmxzdJi9E/AOWItCaDjkPRdVFEIZU4Kaj94cniEXQC7+5TpEuGk/qksGZqv15/cmBsAgVwuUWcUPtFQRpiETxn9X0rRXH1Hda3/If/36vHJhQBjfoyjiFx+0GuUIyXiTMwrc6REKYd1cjkS5cfEum6knrifS+fUanQtPc9qn6r/U2xT/InsG+uVU6KOWbiM8T+4XqL5R0yQqPvvc0AOelR5Fkvz58kKqoMj80Jx+9Gol8oDwMoJ0bHKOVPMiacd6K9561j7gNPLQUB6g0fJXEJy1RmTT6lgmE+pSA2Uy9V3qa4HROg9pL5i55ve8p0D5kcHkJLdXH7QRylHTtR3yfDMkRLUOnVyOVLKj063SbvmVBJMyYVWqOvV2afCGBuakGWHUeEeP3V4q3jWdSVBkIwYL5KTchPY18YjGC+0VWnt85avECjPbEz7tbcsyP+EenVZFv/E1rHM5rQF4R1PisBYfDrGwTAI8ykgcf74QNclIuh8cUzXsfPNsTpBFPMjIDFPfMCREn2XiJ7QT2jWHAlInD8+4EeJCAm6PjiHTNRPaNacCkjcxbPpWvxYo30Kh7jR272xf4doX+rm5mbH9v333z/QdqPtachbeoys2n4skVP1vsqpp/Lg8y6njsvecmstOEi/5lN9PliLzkrlPEXXJX2qP59vVsatUziSq+9CTvmaNCOnTuFHob6zziGFfZhTEU4tXdeSb1F6kzz/aivi673grMijuVYajyxloWeoNmssmZkaYnE+dxKSg8oztZQ7rQQu2c/HJQvlgrMjYD7NroKzCVCq6xIBfb4pQW3+OqUcKdR3yYB9TSpBbaQ6pfwo6d6cKkFtvDor0PVizgXCqnoySXHR+vTGKUF9aoR1FjxaEF5LO55WJ2pJsuIo8DacnEXvjBEnLCmoLB+MfKs4fyoqqQcXWgoC0rH5tBRlTCxHoa5LpPL5pgS1BdQp5EiWvkuGKbl8TSoBbuQ6hfwokcKcKkFtxDpL1fUCzwUsnbgqhr47xacpl2fa3nfzl74vmR9MJeOUbU8ls9s97dGxKXU+ZdvWe77el6aPpcljTt0+3rwkHMyR/P/5lPq7BH1cwhim1HFoe2k4LUkeyfJc20n+w0cA3Q3yvHg38SvFr7vHvG8EjIARMAJGwAgYASNgBIyAEQAB+Qsso8B3eKJ08VNFe49v0XAdvlDMN0Ky1mqEyo6NgBEwAkbACBgBI2AEjIARuGwE5CuwfOKdtqtTHBJQijolapT1FrzSi3UUOWs1aNPBCBgBI2AEjIARMAJGwAgYgctHgE+K8EmNosXtbXiiTgkF1DjTL3yvw7MlAOJgBIyAETACRsAIGAEjYASMQIVAPXGBQzLKco//A3J+3F913zEPAAAAAElFTkSuQmCC\n", "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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\n", "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": [ { "name": "stdout", "output_type": "stream", "text": [ "No requests installed\n" ] } ], "source": [ "try:\n", " import requests\n", " import 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 installed\")\n", " img = np.random.random((82, 290, 4))" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "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", "\n", "\n", "\n", "140467585313440\n", "\n", "Func: kernel (dst,img,w_2)\n", "\n", "\n", "\n", "140467585884144\n", "\n", "Block\n", "\n", "\n", "\n", "140467585313440->140467585884144\n", "\n", "\n", "\n", "\n", "\n", "140467585881120\n", "\n", "_data_img_22\n", "\n", "\n", "\n", "140467585885152\n", "\n", "Loop over dim 0\n", "\n", "\n", "\n", "140467585885824\n", "\n", "Block\n", "\n", "\n", "\n", "140467585885152->140467585885824\n", "\n", "\n", "\n", "\n", "\n", "140467585883424\n", "\n", "_data_dst_00\n", "\n", "\n", "\n", "140467585879392\n", "\n", "_data_img_22_01\n", "\n", "\n", "\n", "140467585317616\n", "\n", "_data_img_22_0m1\n", "\n", "\n", "\n", "140467585884528\n", "\n", "Loop over dim 1\n", "\n", "\n", "\n", "140467585304800\n", "\n", "Block\n", "\n", "\n", "\n", "140467585884528->140467585304800\n", "\n", "\n", "\n", "\n", "\n", "140467585316992\n", "\n", "_data_dst_00[_stride_dst_1*ctr_1]\n", "\n", "\n", "\n", "140467585304800->140467585316992\n", "\n", "\n", "\n", "\n", "\n", "140467585885824->140467585883424\n", "\n", "\n", "\n", "\n", "\n", "140467585885824->140467585879392\n", "\n", "\n", "\n", "\n", "\n", "140467585885824->140467585317616\n", "\n", "\n", "\n", "\n", "\n", "140467585885824->140467585884528\n", "\n", "\n", "\n", "\n", "\n", "140467585884144->140467585881120\n", "\n", "\n", "\n", "\n", "\n", "140467585884144->140467585885152\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] = pow(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], 2);\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] = pow(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], 2);\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] = pow(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], 2);\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] = pow(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], 2);\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": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "loops = list(ast.atoms(ps.astnodes.LoopOverCoordinate))\n", "l1 = loops[0]\n", "l1.prefix_lines.append(\"#pragma something\")\n", "l1.is_outermost_loop" ] }, { "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": 36, "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 < 81; ctr_0 += 1)\n",
       "   {\n",
       "      double * RESTRICT  _data_dst_00 = _data_dst + 290*ctr_0;\n",
       "      double * RESTRICT _data_I_21_01 = _data_I_21 + 1160*ctr_0 + 1160;\n",
       "      double * RESTRICT _data_I_21_0m1 = _data_I_21 + 1160*ctr_0 - 1160;\n",
       "      for (int64_t ctr_1 = 1; ctr_1 < 289; 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 < 81; ctr_0 += 1)\n", " {\n", " double * RESTRICT _data_dst_00 = _data_dst + 290*ctr_0;\n", " double * RESTRICT _data_I_21_01 = _data_I_21 + 1160*ctr_0 + 1160;\n", " double * RESTRICT _data_I_21_0m1 = _data_I_21 + 1160*ctr_0 - 1160;\n", " for (int64_t ctr_1 = 1; ctr_1 < 289; 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 CUDA enabled graphics card and [pycuda](https://mathema.tician.de/software/pycuda/) 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": 37, "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 < 81; ctr_0 += 1)\n",
       "   {\n",
       "      double * RESTRICT  _data_dst_00 = _data_dst + 290*ctr_0;\n",
       "      double * RESTRICT _data_I_21_01 = _data_I_21 + 1160*ctr_0 + 1160;\n",
       "      double * RESTRICT _data_I_21_0m1 = _data_I_21 + 1160*ctr_0 - 1160;\n",
       "      for (int64_t ctr_1 = 1; ctr_1 < 289; 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 < 81; ctr_0 += 1)\n", " {\n", " double * RESTRICT _data_dst_00 = _data_dst + 290*ctr_0;\n", " double * RESTRICT _data_I_21_01 = _data_I_21 + 1160*ctr_0 + 1160;\n", " double * RESTRICT _data_I_21_0m1 = _data_I_21 + 1160*ctr_0 - 1160;\n", " for (int64_t ctr_1 = 1; ctr_1 < 289; 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": [ "try:\n", " import pycuda\n", " from pystencils.gpucuda 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(ast)\n", "except ImportError:\n", " print(\"Please install pycuda 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.10.2" } }, "nbformat": 4, "nbformat_minor": 4 }