Skip to content
Snippets Groups Projects
demo_benchmark.ipynb 47.4 KiB
Newer Older
{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pystencils.session import *\n",
    "import timeit\n",
    "%load_ext Cython"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Demo: Benchmark numpy, Cython, pystencils\n",
    "In this notebook we compare and benchmark different ways of implementing a simple stencil kernel in Python.\n",
    "Our simple example computes the average of the four neighbors in 2D and stores it in a second array. To prevent out-of-bounds accesses, we skip the cells at the border and compute values only in the range `[1:-1, 1:-1]`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementations\n",
    "\n",
    "The first implementation is a pure Python implementation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def avg_pure_python(src, dst):       \n",
    "    for x in range(1, src.shape[0] - 1):\n",
    "        for y in range(1, src.shape[1] - 1):\n",
    "            dst[x, y] = (src[x + 1, y] + src[x - 1, y] +\n",
    "                         src[x, y + 1] + src[x, y - 1]) / 4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Obviously, this will be a rather slow version, since the loops are written directly in Python. \n",
    "\n",
    "Next, we use *numpy* functions to delegate the looping to numpy. The first version uses the `roll` function to shift the array by one element in each direction. This version has to allocate a new array for each accessed neighbor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def avg_numpy_roll(src, dst):\n",
    "    neighbors = [np.roll(src, axis=a, shift=s) for a in (0, 1) for s in (-1, 1)]\n",
    "    np.divide(sum(neighbors), 4, out=dst)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using views, we can get rid of the additional copies:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def avg_numpy_slice(src, dst):\n",
    "    dst[1:-1, 1:-1] = src[2:, 1:-1] + src[:-2, 1:-1] + \\\n",
    "                      src[1:-1, 2:] + src[1:-1, :-2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To further optimize the kernel we switch to Cython, to get a compiled C version."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%cython\n",
    "import cython\n",
    "\n",
    "@cython.boundscheck(False)\n",
    "@cython.wraparound(False)\n",
    "def avg_cython(object[double, ndim=2] src, object[double, ndim=2] dst):\n",
    "    cdef int xs, ys, x, y\n",
    "    xs, ys = src.shape\n",
    "    for x in range(1, xs - 1):\n",
    "        for y in range(1, ys - 1):\n",
    "            dst[x, y] = (src[x + 1, y] + src[x - 1, y] +\n",
    "                         src[x, y + 1] + src[x, y - 1]) / 4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If available, we also try the numba just-in-time compiler"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    from numba import jit\n",
    "\n",
    "    @jit(nopython=True)\n",
    "    def avg_numba(src, dst):\n",
    "        dst[1:-1, 1:-1] = src[2:, 1:-1] + src[:-2, 1:-1] + \\\n",
    "                          src[1:-1, 2:] + src[1:-1, :-2]\n",
    "except ImportError:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And finally we also create a *pystencils* version of the same stencil code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "src, dst = ps.fields(\"src, dst: [2D]\")\n",
    "\n",
    "update = ps.Assignment(dst[0,0], \n",
    "                       (src[1, 0] + src[-1, 0] + src[0, 1] + src[0, -1]) / 4)\n",
    "avg_pystencils = ps.create_kernel(update).compile()"
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_implementations = {\n",
    "    'pure Python': avg_pure_python,\n",
    "    'numpy roll': avg_numpy_roll,\n",
    "    'numpy slice': avg_numpy_slice,\n",
    "    'pystencils': avg_pystencils,\n",
    "}\n",
    "if 'avg_cython' in globals():\n",
    "    all_implementations['Cython'] = avg_cython\n",
    "if 'avg_numba' in globals():\n",
    "    all_implementations['numba'] = avg_numba"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Benchmark functions\n",
    "\n",
    "We implement a short function to get in- and output arrays of a given shape and to measure the runtime."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_arrays(shape):\n",
    "    in_arr = np.random.rand(*shape)\n",
    "    out_arr = np.empty_like(in_arr)\n",
    "    return in_arr, out_arr\n",
    "\n",
    "def do_benchmark(func, shape):\n",
    "    in_arr, out_arr = get_arrays(shape)\n",
    "    func(src=in_arr, dst=out_arr) # warmup\n",
    "    timer = timeit.Timer('f(src=src, dst=dst)', globals={'f': func, 'src': in_arr, 'dst': out_arr})\n",
    "    calls, time_taken = timer.autorange()\n",
    "    return time_taken / calls"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comparison"
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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IVG6GfiNpJeAnPIqZ/Fl0n44MDDt/BPANlT8rrC7gfqS5gIPAwMiYk05bYpMA/aXlD4r1Rd4qOS+I0mzKe5XZEmUkv5GAUMiYhGp6xuZtneqC/B8BWM0MzOzNMq8N7jukDSFVBJRkKEsH7g1IjJudt4EsdwOTIiIaklcJO0P/DYiTiuvbsPcdpHbv8KPaLItyMJhx9Z0CGZmVU7SzIjIL6/e5jCjtElIugJ4FLiyGof9E9CkGsfLAa6qxvHMzMzqlDr/wMmI6J7l/DBgWDXHspjUcl91jfdcdY1lZmZWF22xM0pmZmZm5XGiZGZmZpZFnV96s4rZZ8cWFHjTrpmZWTGeUTIzMzPLwomSmZmZWRZOlMzMzMyycKJkZmZmloU3cxsAcz9ZQd4VT9V0GHWSn2xtZrb58oySmZmZWRZOlMzMzMyycKJkZrXO448/Trdu3fjRj35E48aN2WWXXTjhhBN45pln1tcZM2YMkli4cGG1xjZo0CB+/vOfFzv3u9/9jiOPPJJWrVohiTFjxpRq9+mnn3LllVeSn59PixYtaN26NUcccQTTpk0rVXft2rXceuut7L333jRt2pTc3Fz69OnD66+/vkExf/XVVwwZMoTu3bvTvHlzJDFlypRS9d5++20uuugi9t13X7bZZhtyc3M5/vjjmTNnTqm63377LVdffTXt27encePG7Lzzzpx++uml/nv07t2b888/f4PiNqsNnCiZWa1yxx130KdPH9q1a8eoUaN46qmnGDp0KACTJk1aX+/YY49l+vTp5ObmVlts7733Hvfccw9XX311sfN//etfWbVqFccdd1zWtjNnzuThhx+md+/e/Otf/2LMmDE0atSI7t278+STTxare9VVVzFkyBBOOOEEJkyYwO233857773HYYcdxscff1zpuL/44gvuu+8+ttpqK3r27Jm13rPPPsvkyZPp378/EyZM4O6772bJkiV07tyZmTNnFqt71llncdNNN3H22WczceJErr/+eqZNm8YRRxzB119/vb7eNddcw7333svbb79d6bjNagNFRE3HYLVAw9x2kdv/tpoOo07yZu7KadOmDQceeCDjx48vVbZu3Trq1au53+8GDRrEjBkzePXVV4udL4rr3XffpV27dowePZoBAwYUq7N8+XK22WYbttrqh3toCgsL+clPfsKPf/zjYjNLO+ywA927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jz32WLnjvfbaa9SrVw9JrFlT8nur165dy2233UbHjh1p2rQpeXl5HHvsscyePXu9ru2f//wnp5xyCrvtthuNGzdmt91249xzz2XJkiVl6n777bdcfvnl5OXl0bhxY/bff38mT55cbv8PPfQQkthxxx3LnFuxYgWDBw+mXbt2NG7cmJ122onTTjutzPt79NFHc/7556/X9ZltTIqImo7BaoGGeW0jr//tNR2GrYdNZTP3HXfcwcUXX8wZZ5zBMcccQ9OmTfnggw+YMGEC7dq143e/+x0An332GR988AF77703DRs2rJbYPvjgA/bcc09ee+01unfvDsBll13GlClTOPnkk9lrr734+OOPufHGG1myZAkzZ85kp512Km5/9dVXM3z4cIYMGUK3bt14+OGHuffee3nqqac44ogjyoy3evVqunbtyueff84nn3zC6tWradDg+8/WXHXVVQwbNowrr7ySQw89lM8//5ybbrqJjz/+mFmzZmVMSMrzy1/+km+++Ybjjz+eXXfdlffee4/BgwfTsGFDZs+ezQ9+8IPiuieffDITJkzg1ltvZdddd+Wuu+7imWeeYcqUKXTp0qVM38uWLWOPPfZAEvXr12fRokUlzp900kk8/vjjXH/99XTv3p3//Oc/DB48mPr16zNr1qzisWfMmMG+++7L3LlzadeuXU7XZ7Y+JE2LiO4V1nOiZOBEaVO2qSRKO++8M926dWPcuHFlzq1bt4569WpugvvCCy9k6tSpvPnmm8Vln332Ga1atSpRb+HChbRp04ZrrrmGG264AYAlS5aw0047MWjQIK6//vriuocddhifffZZxlmgm2++mQcffJCjjz6am2++uUyitMMOO3DwwQfz4IMPFpe9/fbb7LnnnowYMYJf//rXOV1fpmuZPHkyBx10EH/9618544wzAJg1axZdunRh1KhRnH766QCsWbOGvfbai/bt2zN+/PgyfZ999tksXLiQvLw8XnzxxRKJ0sqVK2nWrBlXXHEFN998c3H5s88+y09/+lOeffZZDj/88OLyHj160L17d/785z/ndH1m66OyiZKX3jKQlC9pbnLcXdIdVdTv7ZJ6JccFkj6UNDP5KfNPNUldJE2RNE/SbEkn5Djew5LaVkXsZhtq6dKl/PCHP8x4Lj1JKr30NmDAACRl/Jk4cWJxu1mzZnHUUUex9dZb07hxYw488EBeeeWVCuNatWoVo0eP5qSTTipRXjqxANhll11o1aoVH3/8cXHZc889x3fffccpp5xSou4pp5zCnDlzyizVffDBBwwZMoQ///nPbLHFFhlj+u6772jevHmJsq222gpIJZW5ynQt++yzD0CJaxk/fjxbbLEFJ5zw/R81DRo04MQTT+S5555j1apVJfp49dVXGT16NHfddVfGcdesWcPatWsrfS0nnngiY8aMYeXKlTlcndnG5USpAhFRGBEXbWg/krYB9ouI9MX+yyOiS/IzM0OzFcBpEbEX0Be4XdJWOQx7N3DF+kdtVnV69OjB/fffz6233sq7775b6Xa//e1vmTJlSomfAw88sHjfEMD06dM54IADWLp0Kffeey+PPfYY2267Lb1792batGnl9j916lSWLVtGz549K4xl/vz5LFmyhD333LO4bN68eTRs2JDdd9+9RN299toLgLfeeqtE+bnnnstxxx1Hr169so5z3nnnMXr0aJ544gm++uor/v3vf3Peeeex4447lkhiNsSkSZMAylxLmzZtaNKkSZlr+e6773j//feLy1avXs3ZZ5/N5ZdfXubaizRr1oxTTz2VO+64g5dffplvvvmGefPmcfnll9O5c2cOO+ywEvV79erFV199xZQpU6rkGs2qQrUlSskszXxJ9yYzJM9Lapycmyipe3LcUtKC5HiApMclPZnMvlwg6TeSZkiamiQfRe1vl/SapLmSekiqJ+k9Sa2SOvUkvS+pZam4Dkqb1ZkhqVmp8wdLeio5/oGk+yTNSWZ4fpGU90lmfqZLGivpB5R1HPBshvKsIuLdiHgvOV4MLAFK/NNQUgNJb0o6OHl9i6QhyelXgN6S/GBRq3EjRoxg991354orrqB9+/a0bNmSfv368fzzz5fbbrfddmO//fYr/vnXv/7FlClTGDNmDLvuuisAl19+OTvvvDMvvfQSxx13HEcccQTjxo1j11135cYbbyy3/6lTpyKJH/3oR+XWW7NmDeeccw6tWrXizDPPLC5funQpW221FZJK1N9mm22KzxcZPXo0hYWF3HrrreWOdcMNN3DllVfy85//nBYtWrDbbrsxb948Jk6cWNzvhvj6668ZOHAge+65J8ccc0yJa9l6663L1M90LcOGDWPVqlVceeWV5Y513333ceyxx3LooYfSrFkzOnbsyOrVq3nhhRfYcsstS9Tt3Lkz9erVY+rUqRtyeWZVqrpnlNoCdyUzJMuAX1SiTUfgJKAHMARYERF7A1OA09LqNY2IA4DzgFERsQ4YDZycnO8NzIqIz0v1fxlwfkR0AXoC5c35/hZYHhGdIuJHwEtJ4nUN0DsiugKFwG8ytD0QKP1P2yFJwnWbpHJ3rUrqAWwJfJBeHhFrgAHA3ZJ+Qmrm6frk3DrgfaBzlj7PllQoqXDtiuXlDW+2wdq1a8eMGTOYNGkSV199NV26dGHcuHEcfvjh3HTTTZXq48knn+T//u//GDZsWPFf8CtXrmTSpEn88pe/pF69eqxZs4Y1a9YQEfTu3bvCT2wtXryY5s2bl/lLu7QLLriA1157jdGjR5dIJiKiTJJUVJ5u6dKlXHrppdx8881st9125Y519913c9NNN3HNNdfw8ssvM3bsWJo1a0afPn1YvHhxuW0rsmbNGvr168fHH3/Mww8/XGJvVGWv5f3332fIkCHceeedNGrUqNzxrrnmGkaPHs3w4cOZNGkSDzzwAF988QU//elP+d///lei7hZbbEGLFi02+BrNqlJ1zzR8mLbENA3Ir0SblyPia+BrScuBJ5PyOUD6PwEfAoiIyZKaJ0tUo4AngNuBM4D7MvT/KvAHSWOAf0TEokx/UCR6AycWvYiILyUdCXQAXk3abUkqiSstD/gs7fWVwCdJ/XuA/wNuyDSopDzgAaB/kvyUEBHzJD1A6nezf0R8l3Z6CbADZZM0IuKeZGwa5rX1rn7b6OrXr0+vXr2Kl50WL15M3759uf766zn//PMzzmYUmTVrFieddBJnnnkml112WXH50qVLWbt2LTfeeGPW2aPyNot/++23FX667sorr+See+7h/vvvp0+fPiXObbPNNnz55Zdlkowvv/yy+DykEobtt9+e448/nmXLlhWPDbB8+XIaNWpE06ZNWbp0KZdccgmXX355ic3hhx56KPn5+dx6663cdttt5cabzbp16+jfvz8vvvgiEyZMKDOLts022/Cf//ynTLvS13LRRRdx6KGHst9++xVfy3fffUdEsGzZMho2bEjjxo2ZN28eQ4cOZeTIkSVm4fbdd1/atWvHyJEjufjii0uM1bhxY+9RslqluhOl9J2Aa4HGyfEavp/dKv3Pk/Q269Jer6Nk/KX/oo+I+EjSp5IOBfbl+9ml9EpDJU0AjgCmSuoNfJslfmUYR8ALEdEvS5siK0m7toj4b3K4StJ9pGa2yg4oNQcmANdERHnz0Z1IzdJtX6q8EeXPkpnVmB122IFf/epXXHzxxbz33nv06NEjY71PP/2Uo446iv3226/MJ6K22mor6tWrx/nnn89pp52WsX15n6jbdtttixOBTIYMGcLQoUO54447OPXUU8uc32uvvVi1ahUffPBBib06RXuTOnToUPx6zpw5bLvttmX6aNmyJUcffTSPP/447777LqtWrSrebF1km222YbfddmP+/PlZY63IOeecwyOPPMKjjz5aZn9Q0bWMGzeOFStWlNin9NZbb7HlllsWX99bb73FwoULMya2W2+9NRdffDG33347c+bMAShzLW3btmWrrbbKeC1Lly6lZcuWZcrNakpt2buyAOgGvEFqL8/6OAF4WdKPSS2PFa0ljSS1BPdARKwt3UjSbhExB5gjaX9gDyDTxmqA54ELgIFJ262BqcBdknaPiPclNQF2jIjSu1XnA7sDE5O2eRHxX6X+CXoMMDdDbFsC44C/RcTYbBcu6efAtkAv4ClJPSJiWXK6HTAvW1uz6vLRRx+VePZQkbfffhsgrTe+NAAAFTtJREFU6yfivv32W44++miaNm3K2LFjSywVATRt2pSePXsya9YsunbtmvNjBvbYYw9Wr17NokWLyjyf6I477uCaa65hyJAhXHjhhRnb9+3bly233JIxY8YwePDg4vLRo0fTsWNH2rRpA8Dtt99ePPtSpKCggPvvv58XX3yR7bffvsTv4Y033uCoo44qrrt06VLef/99unbtmtP1Fbn00ksZOXIk999/f4l9SemOOuooBg8ezNixY+nfvz+QWqp75JFH6NOnT/HM28MPP1w8G1Zk6NChTJs2jbFjxxb/HtOvJX326t1332XZsmW0bt26RB+ffPIJ3377Le3bt1+vazTbGGpLojQc+LukU4GX1rOPLyW9BjQntcxWZDypJbdMy24AAyUdQmqG6y3gGVLLZJncRCopmpvUvz4i/iFpAPBQ2j6ja4DSidIE4NekEjeAMclGc5FKzM6B1OMIgHMi4lfA8aSSn22TMQAGpH9CLtkjNRQ4LJlBuxP4I9Bf0vbAyrTZK7Ma07FjRw455BCOPfZY2rRpw1dffcXTTz/NiBEjOP7444s/wVbawIEDmT59OgUFBcVJVZEOHTrQvHlz/vCHP9CrVy8OP/xwzjzzTPLy8vj888+ZPn06a9euZejQoVnjKloGfOONN0okSg8//DADBw6kb9++HHrooSU2GDdv3rx4pmi77bbjkksu4ZZbbqFZs2Z07dqVRx55hJdeeoknnniiuE2mhzUWPd7goIMOKk4A8/PzOfLII7n11lupV68eBx10EF988QW/+93vWLVqFeeee26J9occcgj33XcfAwYMyHqNw4YN4w9/+ANnnHEGbdu2LXEtrVq1YrfddiuO8YQTTmDgwIGsXr2aNm3acPfdd/Phhx8yZsyY4jb77bdfmTEKCgpo2LAhBx98cHFZz5496dy5M5deeilffvll8QMnb7rpJlq0aFGcjBV5/fXXAcr9RKBZdasTD5yUNBG4LCIKM5zrDtwWERV/9ncjk/Qv4Mi02Z6NPd4lwFcR8deK6vqBk5uuTeWBkyNGjODpp59m1qxZfPrpp9SvX5927drRr18/Bg4cWLyZuqCggNNPP50PP/yQ/Px8Dj744OKPspf28ssvF//FPH/+fK6//npeeuklli9fTqtWrejatSvnnHNOxqdjp9t3333p0KED9933/b+nBgwYwP3335+x/kEHHVTiGU5r167llltu4d577+WTTz6hffv2XHvttRx3XPkT5Ndddx3XX399mQdOrlixgt///vc89NBDLFy4kObNm9O1a1cGDx5cYnlywoQJHHnkkTzzzDP07ds36zjl/Q779+9PQUFB8euVK1dy9dVX8+CDD7Js2TI6d+7MsGHDSiRAmQwYMKDMAycBvvjiC26++WbGjx/PokWLaNmyJQcccAA33HBDmZmjs846ixkzZlBYWOaPcrMqp83pydzZEiVJg4BzgZMj4l81EVupePYlNcOzfl/YlPt4p5NaclxTUV0nSpuuTSVRqs0KCgq4+OKL+e9//1vmGUK12VVXXcX48eOZM2dOxk+rbUq+/fZb8vLyGD58eImN32YbS2UTpTrxwMmIODjTbFJEDI2IXWpDkgQQEa9XV5KUjHdfZZIks83dqaeeSuvWrTe5r86YNGkSV1111SafJAH85S9/YbvttiuzHGdW02rLHiUzsxpTv359Ro0axfTp02s6lJy8+uqrNR1ClWnYsCEFBQVlNuub1bQ6sfRmG85Lb5suL72ZmeWusktvTt0NgE6tW1Dov3DNzMxKqBN7lMzMzMw2BidK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\n",
      "text/plain": [
       "<Figure size 576x576 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_order = ['pystencils', 'Cython', 'numba', 'numpy slice', 'numpy roll', 'pure Python']\n",
    "plot_order = [p for p in plot_order if p in all_implementations]\n",
    "\n",
    "def bar_plot(*shape):\n",
    "    names = plot_order\n",
    "    runtimes = tuple(do_benchmark(all_implementations[name], shape) for name in names)\n",
    "    for runtime, name in zip(runtimes, names):\n",
    "        # assert that pystencils is the fastest\n",
    "        # if some change degrades performance of pystencils, we see this automatically in CI system\n",
    "        assert runtime >= runtimes[names.index('pystencils')], \"pystencils is slower than \" + name\n",
    "    speedups = tuple(runtime / min(runtimes) for runtime in runtimes)\n",
    "    y_pos = np.arange(len(names))\n",
    "    labels = tuple(f\"{name} ({round(speedup, 1)} x)\" for name, speedup in zip(names, speedups))\n",
    "    \n",
    "    plt.text(0.5, 0.5, f\"Size {shape}\", horizontalalignment='center', fontsize=16,\n",
    "             verticalalignment='center', transform=plt.gca().transAxes)\n",
    "    plt.barh(y_pos, runtimes, log=True)\n",
    "     \n",
    "    plt.yticks(y_pos, labels);\n",
    "    plt.xlabel('Runtime of single iteration')\n",
    "    \n",
    "plt.figure(figsize=(8, 8))\n",
    "plt.subplot(3, 1, 1)\n",
    "bar_plot(32, 32)\n",
    "\n",
    "plt.subplot(3, 1, 2)\n",
    "bar_plot(128, 128)\n",
    "\n",
    "plt.subplot(3, 1, 3)\n",
    "bar_plot(2048, 2048)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All runtimes are plotted logarithmically. Numbers next to the labels show how much slower the version is than the fastest one."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}