python_reference/tutorials/running_cython.ipynb
2014-07-06 22:48:40 -04:00

1002 lines
96 KiB
Plaintext

{
"metadata": {
"name": "",
"signature": "sha256:c53d1aaf41825ecf8aae8e9e7691d07f984de379cd765b3cabd973cfb29cc420"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Sebastian Raschka](http://sebastianraschka.com) \n",
"\n",
"[Link to this IPython notebook on GitHub](https://github.com/rasbt/python_reference/blob/master/tutorials/running_cython.ipynb)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%load_ext watermark"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%watermark -d -m -v -p cython"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"06/07/2014 \n",
"\n",
"CPython 3.4.1\n",
"IPython 2.1.0\n",
"\n",
"cython 0.20.2\n",
"\n",
"compiler : GCC 4.2.1 (Apple Inc. build 5577)\n",
"system : Darwin\n",
"release : 13.2.0\n",
"machine : x86_64\n",
"processor : i386\n",
"CPU cores : 2\n",
"interpreter: 64bit\n"
]
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<font size=\"1.5em\">[More information](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/ipython_magic/watermark.ipynb) about the `watermark` magic command extension.</font>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<hr>\n",
"I would be happy to hear your comments and suggestions. \n",
"Please feel free to drop me a note via\n",
"[twitter](https://twitter.com/rasbt), [email](mailto:bluewoodtree@gmail.com), or [google+](https://plus.google.com/118404394130788869227).\n",
"<hr>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"sections\"></a>\n",
"<br>\n",
"<br>\n"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Using Cython with and without IPython magic"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"introduction\"></a>\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Sections"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- [Bubblesort in regular (C)Python](#bubblesort-cpython)\n",
"- [Bubblesort implemented in Cython](#Bubblesort-implemented-in-Cython)\n",
" - [Naive Cython implementation - auto-guessing types](#Naive-Cython-implementation---auto-guessing-types)\n",
" - [Cython with explicit type-declarations](#Cython-with-explicit-type-declarations)\n",
"- [Speed comparison](#Speed-comparison)\n",
"- [How to use Cython without the IPython magic](#How-to-use-Cython-without-the-IPython-magic)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"introduction\"></a>\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id='bubblesort-cpython'></a>"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Bubblesort in regular (C)Python"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#Sections)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we will write a simple implementation of the bubble sort algorithm in regular (C)Python"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def python_bubblesort(a_list):\n",
" \"\"\" Bubblesort in Python for list objects. \"\"\"\n",
" length = len(a_list)\n",
" swapped = 1\n",
" for i in range(0, length):\n",
" if swapped: \n",
" swapped = 0\n",
" for ele in range(0, length-i-1):\n",
" if a_list[ele] > a_list[ele + 1]:\n",
" temp = a_list[ele + 1]\n",
" a_list[ele + 1] = a_list[ele]\n",
" a_list[ele] = temp\n",
" swapped = 1\n",
" return a_list"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 3
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"python_bubblesort([6,3,1,5,6])"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 4,
"text": [
"[1, 3, 5, 6, 6]"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Bubblesort implemented in Cython"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#Sections)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we will speed things up a little bit via [Cython's C-extensions for Python](http://cython.org). Cython is basically a hybrid between C and Python and can be pictured as compiled Python code with type declarations. \n",
"Since we are working in an IPython notebook here, we can make use of the very convenient ***IPython magic***: It will take care of the conversion to C code, the compilation, and eventually the loading of the function. "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%load_ext cythonmagic"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we will take the initial Python code as is and use Cython for the compilation. Cython is capable of auto-guessing types, however, we can make our code way more efficient by adding static types."
]
},
{
"cell_type": "heading",
"level": 4,
"metadata": {},
"source": [
"Naive Cython implementation - auto-guessing types"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#Sections)]"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%cython\n",
"def cython_bubblesort_untyped(a_list):\n",
" \"\"\" Bubblesort in Python for list objects. \"\"\"\n",
" length = len(a_list)\n",
" swapped = 1\n",
" for i in range(0, length):\n",
" if swapped: \n",
" swapped = 0\n",
" for ele in range(0, length-i-1):\n",
" if a_list[ele] > a_list[ele + 1]:\n",
" temp = a_list[ele + 1]\n",
" a_list[ele + 1] = a_list[ele]\n",
" a_list[ele] = temp\n",
" swapped = 1\n",
" return a_list"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 6
},
{
"cell_type": "heading",
"level": 4,
"metadata": {},
"source": [
"Cython with explicit type-declarations"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#Sections)]"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%cython\n",
"import numpy as np\n",
"cimport numpy as np\n",
"cimport cython\n",
"@cython.boundscheck(False) \n",
"@cython.wraparound(False)\n",
"cpdef cython_bubblesort_typed(np.ndarray[long, ndim=1] inp_ary):\n",
" \"\"\" The Cython implementation of Bubblesort with NumPy memoryview.\"\"\"\n",
" cdef unsigned long length, i, swapped, ele, temp\n",
" cdef long[:] np_ary = inp_ary\n",
" length = np_ary.shape[0]\n",
" swapped = 1\n",
" for i in xrange(0, length):\n",
" if swapped: \n",
" swapped = 0\n",
" for ele in xrange(0, length-i-1):\n",
" if np_ary[ele] > np_ary[ele + 1]:\n",
" temp = np_ary[ele + 1]\n",
" np_ary[ele + 1] = np_ary[ele]\n",
" np_ary[ele] = temp\n",
" swapped = 1\n",
" return inp_ary"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Speed comparison"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#Sections)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Below, we will do a quick speed comparison of our 3 implementations of the bubble sort algorithm by sorting a list (or numpy array) of 1000 random digits. Here, we have to make copies of the lists/numpy arrays, since our bubble sort implementation is sorting in place."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import random\n",
"import numpy as np\n",
"import copy\n",
"\n",
"list_a = [random.randint(0,1000) for num in range(1000)]\n",
"list_b = copy.deepcopy(list_a)\n",
"\n",
"ary_a = np.asarray(list_a)\n",
"ary_b = copy.deepcopy(ary_a)\n",
"ary_c = copy.deepcopy(ary_a)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 8
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import timeit\n",
"\n",
"times = []\n",
"\n",
"times.append(min(timeit.Timer('python_bubblesort(list_a)', \n",
" 'from __main__ import python_bubblesort, list_a').repeat(repeat=3, number=1000)))\n",
"\n",
"times.append(min(timeit.Timer('python_bubblesort(ary_a)', \n",
" 'from __main__ import python_bubblesort, ary_a').repeat(repeat=3, number=1000)))\n",
"\n",
"times.append(min(timeit.Timer('cython_bubblesort_untyped(list_b)', \n",
" 'from __main__ import cython_bubblesort_untyped, list_b').repeat(repeat=3, number=1000)))\n",
"\n",
"times.append(min(timeit.Timer('cython_bubblesort_untyped(ary_b)', \n",
" 'from __main__ import cython_bubblesort_untyped, ary_b').repeat(repeat=3, number=1000)))\n",
"\n",
"times.append(min(timeit.Timer('cython_bubblesort_typed(ary_c)', \n",
" 'from __main__ import cython_bubblesort_typed, ary_c').repeat(repeat=3, number=1000)))\n",
"\n"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%matplotlib inline"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 10
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from matplotlib import pyplot as plt\n",
"import numpy as np\n",
"\n",
"bar_labels = ('(C)Python on list', \n",
" '(C)Python on numpy array', \n",
" 'untyped Cython on list', \n",
" 'untyped Cython on numpy array', \n",
" 'typed Cython with memoryview on numpy array')\n",
"\n",
"fig = plt.figure(figsize=(10,8))\n",
"\n",
"# plot bars\n",
"y_pos = np.arange(len(times))\n",
"plt.yticks(y_pos, bar_labels, fontsize=14)\n",
"bars = plt.barh(y_pos, times,\n",
" align='center', alpha=0.4, color='g')\n",
"\n",
"# annotation and labels\n",
"\n",
"for b,d in zip(bars, times):\n",
" plt.text(max(times)+0.1, b.get_y() + b.get_height()/2.5, \n",
" '{:.2} ms'.format(d),\n",
" ha='center', va='bottom', fontsize=12)\n",
"\n",
"t = plt.title('Bubblesort on 1000 random integers', fontsize=18)\n",
"plt.ylim([-1,len(times)+0.5])\n",
"plt.vlines(min(times), -1, len(times)+0.5, linestyles='dashed')\n",
"plt.grid()\n",
"\n",
"plt.show()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAA9sAAAHtCAYAAAAJNW1FAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XmcHGWd+PHPAHIIgkREiAIRZSN4gBoRECGC+HM1KIgu\nCgIRZAOIgCeKRyZqVJBlvd0YhHgArgcIiYu7HBkximJQAQGDIAkg900MASHz++NbRdfUVHfP9HSm\nZvJ83q9XvzJdXf30862q7tT3OapAkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ\nkiRJkiRJGtf6gJuHuO5UYBVw2BDXn5etL40XvcQxu3XN9agyleF9/yQlYK26KyBJ0jg2lTjBLj4e\nBW4CzgBe1IXP6F+N6w+37DrtB8ysuxJtTAZOBS4FHiSOh3Z1PhT4I7ACuBOYC2zWZN2JwPeAe7L1\nfw+8vUtla+T6Gdl36gRM1iVJkiSgkWz/ADgoexwBfJVIcB5iZL1wfcDfhlmXQ4e4/jzGV8/2PMZ+\nfacDTwJLgIuJ+n66xfofyNa5FHgvMAt4BPgz8PTSuhOIY+Fhoof3vcDC7P3TR1j2eNHL2O3Z7gHW\nZWQdWUuJ/SVJkiQlbypx8v/Bitfen712wgjK78Nke6Ps33lEIjuWbQpsnP39Slon25sB/wB+SyRq\nuWnZ+z5eWv+UbPmbC8vWAn4H3AtsOIKyh+MZI3jvSPUydpPtbljK+Eu2exh47EkqcBi5JEmrxx3Z\nv48Vlk2l+bzOeTRPfp8PnE8MTX4IODdbVqWHSPRvIIa0LwGOHXq12RL4FnBLVve/A3OAZ5fWmwD8\nJzFk/lEi4VsMfLi03jrAicB1hfXOBV5SWm8SjWHXBwJXEqMDvkb04B6axVYcsj+UhoX9gF8Dy4me\n3UXAWyrWW5p9zouAnxM9yA8CPwaeM4TPAXggex8MTHKb1WsDIr7i0OMFRAPLu0vrHwTcmNUttyp7\n/wTgTSMou5lVwJnA3sR2ewS4IHttIvAfwJ+A+4l9ey3wUQafX07PynodcXzcBKwkjs2qfbgW0SBw\nc1buNUT8zbwMOA+4r1CPj1TUY15WjwnENI97iP11PnHcA8wArs/KuZ7qY6XKVAZ/t4vL3pPVayVx\nrH2k9P68ESF/zyoGNyxMyeK8JyvnL8BJwNoV9TkAuCqLYxnR6PP6ijoCrJeVc222/gPEft6pRYzv\no/Gdzr/zLya+L3/P6ncH0XjwJqRErVN3BSRJWgNsSGMu7AZEIjmbOCn+acX6zeZ1Vi3fiOjh/i3w\nMeBfgGOAXYCXA3eV1n8/sAXwX0RydBAxrH0C8Jk2cWwNXE6cH3yHSIq2A44mEqUpNJLJHwOvJRLz\nq4m4dwD2JOYt584C3gH8H/ANIql5X/Y5ryWStaL9snp8M3s8TJz8r5WtX0wUf9MmnmOArxNJ0ywi\nAZ4O/IxIquYW1u0Hnksk3OcSCdhO2XobA/+vzWcN16uyfy+veO13wDuJ4d4riG02kZiuULUuxL75\ncQdltzOFSNy+TSTeuZcB+xPb6ibgacC/Al8EtgWOqijr88D6xDHzOHFczSMaEYr78jTgOOCXREL/\nHOLYqRrlMSVb77FsnTuJBPlkYEeqGxZ+AdwKfIo4vo8jksvziOPj9Ky844CfEN+5pRXlVKn6Dh+V\nxXA60YBzSFa/24BzsnUOIRqv7iF+O3L3Zv++mdjWNxDfr/uB3Yjv9E7AvxXec2BW7l+J0QBPEgny\nvhV1fBqxPXYlrgfwVeCZwJFEI9UeRMNX0QnAs4hj4k5iWz6LSKxXEb89y4gGuinAzsD/VGwXSZIk\nqampDL5AWv74M3GSXrV+VW/ePAb3bPdly04rLd8vW/6tirIfIhKz3NOIBOtxIpls9XnnEyfPE0vL\nXwn8k8bFvjbJ3vv1ijiK9snWO6e0/GVZeZcVlk3K1n2MuNBYWVV9W9mU6M2+gcZQdIhh0DcSSfwm\nheVLs/LLFxz7era8vC/bmULrYeTziSRovYrX8iHjL8ye50PSv1Cx7tNpXDegk7JbWZWVs1fFa+s3\nec/3gCeIBp/c9KysKxnY0TOR6AE9u7BscrbuRQwcHfDyQn2Kvb2/Jo7t8kiJ/87WL9Z9Ho3RAEX/\nkS1fxsBj5aXZ8s/T3lQGf7fzZbcxcPj9BsDdDG4sWkr1MPL1ie9lH4N760/IPmPP7Pk6RM/yHQw8\nvjckGkXKdczn9u9TKvcZxPZYWBHPvQy+0N5bqP7+SElzGLkkSSM3hxii+XpiXuyJxMnohYx8fmk/\n0VtY9DMiidyvYv2zgNsLz/9J9JitQ6Nnq8omRN0vIJKXzQqPZcSJ+huydR8lkuJdgG1alLl/9u/s\n0vKriYRwd6JHrOjnxPDikdqHSES/SiTduUeyZRsR+6vo70RPZlGebAwlOR2O/CJlj1W8trK0znDW\n7WT9Vq6iOgFcWfh7XWLkxGbECIa1iAaCsm8SiXjuduI4Lm7bt2b/nsbAHtg/ZmUXE/DNiR7ZC4jG\nraL8mNufwb5cer4o+/e7DDxWriEaZUa6788kjrvco0QD2HZDfP8+RKzzaGzn/HFhtk7+3XwlMRJi\nHtHwlvsH0eNc9m5i5McfSuWuR1zkb3cGN9p8j0aPe+7B7N83Ue+8fmlMcRi5JEkj91cGJiT/Qwxt\n/S0xXPRdIyj7QaIXrOx6IjHZgDh5Ly6vWheaz/OG6FHsIa5c/d4m69yU/fs40aP2FWJe7XVE/D9j\n4HZ4PtETWVWn64jGgucTc21zN7So43DksV7b5LOL6+SqhinndSs3CoxUPoR7PQYnxeuX1imuW1Ze\nd7hlt9Nsf6xDTGs4FHgBg+eob1rxnqrtez+wVeH5ttm/f6lY93oaSSW03sd/IZL1qmO+XI8Hsn+r\n7mn/ICPf982Oq6GWu3327xlNXu8nknFoxFvVYFW1L7cnjol7WpS9GdEQ1aqcy4gkfDpwMHFbuouJ\nEQZV338pCSbbkiStHlcQvWKvKyxrdQ/eddq8XqWb98nOk6XvEz18VYpJ/Rxi2PmbiSGsbycuxPbf\njKxxYahJ4OrQ6mrn7S54Nly3Z2U+l8HJ2HOJIbm3F9bNl5fly4rJ0HDKbqfZ/jiN2N8/BD5LNAj9\nk+hZPZnq0ZPNtm+3t207zb43q6t+I72Kfv75H2bwNQ5yQ92fVWVfTfUdFXLlXuxmx8R04EvE3P3X\nAh8CPkE0zH2jw/pJ45rJtiRJq886DBxSeX/274SKdbetWAbRQ/gcBl8IbXsiwXm0tHyHijLyZa1u\nI3YjkYSsx9BvP3QncSG17xDJ1feJRPtUYn7u34grJe9ADMkt16mf6t7EKv0Mr3Eh74V/CQPnneaf\nDUO/rdrqcAVxEardKuqxC9EzmSc1dxDJ9K4V5eyS/bu4w7I7dQgxeqN8lfDhzm0vy/fb9gw+NsrH\ndv56eb42xFXle6h3Hw9Xs+M770leQfvvZr5NXlTxWtW1EG4gesUXtvj84bg2e5xKTE35HTENxmRb\nSXLOtiRJq0c+Z7h4Jd+biTmr5YsR7UYjaarysdLz/Ymk5mcV6x7MwB7QdYmLID1B3PqpqHhyfR8x\n/P1twKsryu1h4BXXy3N+V9FIqPPGhPOyf8v3dX4JcUGlRQwcQt7K8qwOVcOTq1xEzFN9P4MvkPZ+\nYg7tRUMsa3U4n2goOZaB52P7EkOBzyqtfw4xXHtaYdnaRCwPMPBqz8MtuxNPMPg8ckPiWBuJC4jj\n8oOl8l9BzLEvHrP5Rcb2JW47leuhccydx0DdHA0yUuW6LKd6aPn/ErF+jOrjfwMax/hionFmOnFV\n8dxGVF8h/nvExeya9WwP9bZ3mzL4eHiIuOjbBlRPgZDWePZsS5I0cq+kcYuh9YgT/38n5jZ/srDe\ncuLCRe8lrsD8S+IiSdOJC1HtWFH2vUQCPLGw/jFEr3Jvxfo3EL1J/5V93kHElbE/w8ChxjB4eOzR\nRAKcz7/8E3ECvS2RHH83K2dyVpdziV6sB4ieyKOInsRfZeVdDPyIuNXUpsTFz7Ygbv21gri10lBd\nnr3vm0Ri+U9iTvzSJus/RNzz+RvE9phH49Zf2xK39HqkyXs7tTGNmPIruu9J4xg4n0aDxL3EradO\nJbbTD4lGkg8Rc1zLF/H6InELtbOJIdy3E6MIXkkcT/8orDvcsjvxE2Ib/hC4hEjK3sPQG0+Kisfh\nEmKfHUv04p5L9Ly+jzgeX1567/HEsfir7H13EQ0SbyAaFcqjGkZ7yHor5bpcDhxBfMf+QjRgXUB8\nVw4lGteWEHO3byKS6RcRjW/7Ed/bJ4nh5mcRIxy+ky2bTuybSQxM8r9CNP59ibhy+0Ji+svWxP3V\nH6X6avRlhxENLfmt4P5JHPtvIKaWVF2sT5IkSWpqTxq3I8pv+fUEkQj/hOorMm9I3N/5XiJB+iXR\nq30mg+d2LiSS10nEifZD2eM8Bg87n5q9/1Cit/MG4orRS7LnZVWfB9Gzdkr2vkeJRPoq4orm+dDU\nCUTC98fs9RXZ553G4J6wtYmk97qsPvcSJ+QvLq03ida3yuohEoJbiW2cx9rOfsTtoZZnj0VEw0HZ\nzVQP0Z06jM+axMDbvz1J49hoVsZhRBL5KHHcnM7g2yrlJhKNIPdk6y8mEvBmhlN2lVU0vyjXBsRx\nsjQrfwmxn/di8O2lphPx71FRTn6MF/UAJ2VlryTmFL+LuPVc+dZfELeSO49IJlcSDUAfZnAy2+yY\nn0rz/dPsuBhKGa3KrarLs4nfjftoHDvFWF9MTNW4jUhe7ySO508wuMf77cT3diVxN4FeGrcMLN+e\nKx8hcQWN78mS7LOKV+xvFc+ORIPWX7P3P0T8PnyAuP2gJEmSJElrpA8RyfbOdVdEkiRJkqTx5mlE\nb3XRRsQIgrtxGqk0avyySZIkSWuOFwAXEhfVWwpsSUwp2Ia4LsMTtdVMkiRJkqRxagJxIb1lxHz6\nR4jrFpTnaktazcbSFRklKRk77rhj/1VXXVV3NSRJkobiKmCnuisx3nifbUmqwVVXXUV/fz/9/XEH\nlvzvNf0xc+bM2utg3MZt3MZt3MZt3MN7UH1rSrVhsi1JGjVLly6tuwq1MO60GHdajDstqcatzphs\nS1LNZs6cWXcVJEmS1GXl2wJIkkZHb29vLwBTp06ttSKj6ZnPfCaTJk2quxqjzrjTYtxpMe60pBr3\nrFmzAGbVXY/xxgukSVI9+rM5UJIkSWNaT08PmDsOm8PIJUmjpq+vr+4q1MK402LcaTHutKQatzpj\nsi1JkiRJUpc5FECS6uEwckmSNC44jLwz9mxLUs3yC6VJkiRpzWGyLUk1y67wmYRU57oZd1qMOy3G\nnZZU41ZnTLYlSZIkSeoyx91LUj2emrPd09OD87clSdJY5ZztztizLUmSJElSl5lsS5JGTapz3Yw7\nLcadFuNOS6pxqzMm25JUs5kzZ9ZdBUmSJHWZ4+4lqR7eZ1uSJI0LztnujD3bkiRJkiR1mcm2JGnU\npDrXzbjTYtxpMe60pBq3OmOyLUmSJElSlznuXpLq4ZxtSZI0LjhnuzP2bEtSzXp7e+uugiRJkrrM\nZFuSajZr1qy6qzBqUp3rZtxpMe60GHdaUo1bnTHZliRJkiSpyxx3L0n1eGrOdk9PD87fliRJY5Vz\ntjtjz7YkSZIkSV1msi1JGjWpznUz7rQYd1qMOy2pxq3OmGxLUs1mzpxZdxUkSZLUZY67l6R6eJ9t\nSZI0LjhnuzP2bEuSJEmS1GUm25KkUZPqXDfjTotxp8W405Jq3OqMybYkSZIkSV3muHtJqodztiVJ\n0rjgnO3O2LMtSTXr7e2tuwqSJEnqMpNtSarZrFmz6q7CqEl1rptxp8W402LcaUk1bnXGZFuSJEmS\npC5z3L0k1eOpOds9PT04f1uSJI1VztnujD3bkiRJkiR1mcm2JGnUpDrXzbjTYtxpMe60pBq3OmOy\nLUk1mzlzZt1VkCRJUpc57l6S6uF9tiVJ0rjgnO3O2LMtSZIkSVKXmWxLkkZNqnPdjDstxp0W405L\nqnGrMybbkiRJkiR1mePuJakeztmWJEnjgnO2O2PPtiTVrLe3t+4qSJIkqctMtiWpZrNmzaq7CqMm\n1bluxp0W406Lcacl1bjVGZNtSZIkSZK6zHH3klSPp+Zs9/T04PxtSZI0VjlnuzP2bEuSJEmS1GUm\n25KkUZPqXDfjTotxp8W405Jq3OqMybYk1WzmzJl1V0GSJEld5rh7SaqH99mWJEnjgnO2O2PPtiRJ\nkiRJXWayLUkaNanOdTPutBh3Wow7LanGrc6YbEuSJEmS1GWOu5ekejhnW5IkjQvO2e6MPduSVLPe\n3t66qyBJkqQuM9mWpJrNmjWr7iqMmlTnuhl3Wow7LcadllTjVmdMtiVJkiRJ6jLH3UtSPZ6as93T\n04PztyVJ0ljlnO3O2LMtSZIkSVKXmWxLkkZNqnPdjDstxp0W405LqnGrMybbklSzmTNn1l0FSZIk\ndZnj7iWpHt5nW5IkjQvO2e6MPduSJEmSJHWZybYkadSkOtfNuNNi3Gkx7rSkGrc6Y7ItSZIkSVKX\nOe5ekurhnG1JkjQuOGe7M/ZsS1LNent7666CJEmSusxkW5JqNmvWrLqrMGpSnetm3Gkx7rQYd1pS\njVudMdmWJEmSJKnLHHcvSfV4as52T08Pzt+WJEljlXO2O2PPtiRJkiRJXWayLUkaNanOdTPutBh3\nWow7LanGrc6YbEtSzWbOnFl3FSRJktRljruXpHr0n3TySQBsvvHmHH/U8TVXR5IkqZpztjuzTt0V\nkKRUbbP3NgAsu2RZzTWRJElStzmMXJI0alKd62bcaTHutBh3WlKNW50x2ZYkSZIkqcscdy9J9eif\ns3gOEMPIZ390ds3VkSRJquac7c7Ysy1JNbvsosvqroIkSZK6zGRbkmq26OJFdVdh1KQ6182402Lc\naTHutKQatzpjsi1JkiRJUpc57l6S6vHUnO0ZU2bQ399fc3UkSZKqOWe7M/ZsS5IkSZLUZSbbkqRR\nk+pcN+NOi3GnxbjTkmrc6ozJtiTVbPfX7153FSRJktRljruXpHp4n21JkjQuOGe7M/ZsS5IkSZLU\nZSbbkqRRk+pcN+NOi3GnxbjTkmrc6ozJtiRJkiRJXea4e0mqh3O2JUnSuOCc7c7Ysy1JNbvsosvq\nroIkSZK6zGRbkmq26OJFdVdh1KQ6182402LcaTHutKQatzpjsi1JkiRJUpc57l6S6vHUnO0ZU2bQ\n399fc3UkSZKqOWe7M+OxZ/vDwM01fv48YH6Nnz9UU4FVwIQ26y0FPrS6K7MG6wO+WnclJEmSJI0t\n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\nfmLOS76dP0Mkx+eW3tdHDO3po/39EucQvyFnE6MFts4+o119abLsc1k5FxLfv0eovoPAcuI3bSXD\nv4CbJEnSePUA0cG0EdEhUZwjfQvR0XRbYdl/Eh0UmxCdOMVr6LQq64dEwr0R0Qk1ncZ1niCuw7RZ\n9nnbEx0kzSwlzsGLvdNbEbcfzh1CYwj7iUSDwTOIDuKWd5wZaU/Xq4lhks9n4IaDqPDbGXyyPNYc\nSCShWzLwystKh8dA/W4meufbNaZtQPzWHEs0HI1VFxL/qcxosU7/nMVxXbwZU2YQ06EkSZLGnp6e\nHujeyNdkdHo18nWJKwl/lkimy4n2eLABkVydRFxYzCQrPR4D40cPMVLleOJicmO1x3hT4nYV+zD4\n/vGSJElKyFCGkVc5iOhyn8DgK1mPFycSw2LvpXH/a6XFY2D82Ia45eC7iaH6T9Zbnab+SFzw4+OM\n/IKIa6RU57oZd1qMOy3GnZZU41ZnOu3Znpc9Wuk0kR8tvTS/X7LS0IvHwFhRvlVe2VLG/m8KVF8w\nra3dX1++mL4kSZLGO8fdS1I9npqzveySZcz+6OyaqyNJklTNOdudGQ89RZIkSZIkjSsm25KkUZPq\nXDfjTotxp8W405Jq3OqMybYkSZIkSV3muHtJqodztiVJ0rjgnO3O2LMtSTW77KLL6q6CJEmSusxk\nW5JqtujiRXVXYdSkOtfNuNNi3Gkx7rSkGrc6Y7ItSZIkSVKXOe5ekurx1JztGVNm0N/fX3N1JEmS\nqjlnuzP2bEuSJEmS1GUm25KkUZPqXDfjTotxp8W405Jq3OqMybYk1Wz31+9edxUkSZLUZY67l6R6\neJ9tSZI0LjhnuzP2bEuSJEmS1GUm25KkUZPqXDfjTotxp8W405Jq3OqMybYkSZIkSV3muHtJqodz\ntiVJ0rjgnO3O2LMtSTW77KLL6q6CJEmSusxkW5JqtujiRXVXYdSkOtfNuNNi3Gkx7rSkGrc6s07d\nFZCkVC27ZFndVZAkSdJq4rh7SapHf39/PxDzoPK/JUmSxhrnbHfGYeSSJEmSJHWZybYkadSkOtfN\nuNNi3Gkx7rSkGrc6Y7ItSTWbOXNm3VWQJElSlznuXpLq0e88bUmSNB44Z7sz9mxLkiRJktRlJtuS\npFGT6lw3406LcafFuNOSatzqjMm2JEmSJEld5rh7SaqHc7YlSdK44JztztizLUk16+3trbsKkiRJ\n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\nuNNi3Gkxbqk9k21JkiRJkrrMcfeSVI+n5mzPmDID52+PTc7ZliTJOdudsmdbkiRJkqQuM9mWJI0a\n52ynxbjTYtxpMW6pPZNtSarZtCOn1V0FSZIkdZnj7iWpHv0nnXxS3XVQG5tvvDnHH3V83dWQJKlW\nztnujBtMkurR39/fX3cdJEmS2jLZ7ozDyCVJoybVuW7GnRbjTotxpyXVuNUZk21JkiRJkrrMoQCS\nVA+HkUuSpHHBYeSdsWdbkmrW29tbdxUkSZLUZSbbklSzWbNm1V2FUZPqXDfjTotxp8W405Jq3OqM\nybYkSZIkSV3muHtJqsdTc7Z7enpw/rYkSRqrnLPdGXu2JUmSJEnqMpNtSdKoSXWum3GnxbjTYtxp\nSTVudcZkW5JqNnPmzLqrIEmSpC5z3L0k1cP7bEuSpHHBOdudsWdbkiRJkqQuW6fuCkhSqj5xyifq\nrsKoW3bTMrZ5wTZ1V2PUGXdajDstxp2WZnFvvvHmHH/U8TXUSGOZybYk1WSbvdM7SVm5yUq2mWLc\nqTDutBh3Wox7oGWXLKuhNhrrHHcvSfXon7N4Tt11kCRJXbDskmXM/ujsuqux2jhnuzPO2Zakms2f\nM7/uKkiSJKnLTLYlqWYL5i6ouwqjZsniJXVXoRbGnRbjTotxpyXVuNUZk21JkiRJkrrMZFuSNGom\nT5lcdxVqYdxpMe60GHdaUo1bnTHZliRJkiSpy0y2JUmjJtW5bsadFuNOi3GnJdW41RmTbUmq2bQj\np9VdBUmSJHWZ90qTpHp4n21JktYQ3mdbVezZliRJkiSpy0y2JUmjJtW5bsadFuNOi3GnJdW41RmT\nbUmSJEmSusxx95JUD+dsS5K0hnDOtqrYsy1JNZs/Z37dVZAkSVKXmWxLUs0WzF1QdxVGTapz3Yw7\nLcadFuNOS6pxqzMm25IkSZIkdZnJtiRp1EyeMrnuKtTCuNNi3Gkx7rSkGrc6Y7ItSZIkSVKXmWxL\nkkZNqnPdjDstxp0W405LqnGrMybbGms2Ae4Eth3m+44Dzut+dZpaCnxoFD9Pa7BpR06ruwqSJEnq\nMpNtjTUfAS4G/lZa/jbgUuABYDlwNfA54NnZ698GdgNeVXrfqsLjYeD3wP7DqM904JGK5f3ZQxqx\nfWfsW3cVRk2qc92MOy3GnRbjTkuqcXfBBKJjbDnRafWuNut/CrgVeBBYCOxQeO1YYDGwEjiz2xXt\nJpNtjSXrAkcy+EszG/gR8AfgzcD2wPHA84Gjs3VWAj8G3ldR7nuBLYhE/KpsvVd3ue5aM6zTZPnT\nRrUWkiRJa5ZvEOfrmwMHA99iYAJd9BbgKOC1RJJ+OfD9wut/Bz4LnLG6KtstJtsaS14PbED0YOd2\nBj4OfDh7/IZo5VpIfFG/Ulj3fOAAYO1SuQ8CdwNLgBnEF31f4gv8OPCc0vqziaR8T+JLvCGN3vFP\nF9bbAJgDPJTV6cOlcrYmWvAezh4/BZ5beL0XuAZ4J3BTts55wLNo7aVE7/8K4D6icWLjwuvzgPlE\ng8RtwP1ZHBu0KHNqFt9ewO+AfxCjAF5eWGc6g3v58/dNKK3zRuAvWTnnZ/U7ELiB2B/zgPUK5fQR\nP7pfyep7P3AK0JO9/mliW5X9moHHQNkXs3qsAG4GTi59bm9W7nRiHzxKY38fA5xLtMDOJn4vv0OM\nuliRxfKRQh33oPXxJNKd62bcaTHutBh3WlKNe4Q2JEapfoo4h/o1cX54SJP1XwwsInrAVwFnMTAx\nPy97/31D+Ozp2eedRoyQvZEYDfse4BbgLuDQwvpvAq4lzstvY4TTRk22NZbsQfReF4dnH0wkO19r\n8p6HCn8vJr7M5aHkRU9mj/WAXxEJVvELtlb2/HQisT+B+FHYInucmq3XA3yASKJeTiRxpwC7FMo5\nnxjmPhV4HTAR+FmpPpOAdwBvBd6QlTW7Rf03hP/f3v3HyVXWhx7/bBOQSEgUY6SUXwoYgYoUA0UE\nuxq9SBtEpbZeEEU0DVqFXkGgomaDIN7Scqml2hh6+SU/XqJVb7b+aED2agWlQX4pGIEmIRQsWCGA\nwOXX3j++Z7JnZ2d2zk5mztmZ5/N+vfaVOWfOzDzfndmT832e5/sM3yVOAAcQU+IPZmLP3qHESWkR\nkeS+g0i+W/kscCqwP3ECu7zAY+q9APgYMT1oEbCQSFqPIU60byd6LD9U97hjsn8PIjpF/oz4/UMk\nua9i/Hu7AHgd8V418zhxMn0VkTy/Gzij7piXZ/uPAl5DdMYALAOGgd8lemN/izjpvit7vjOAT2TP\nD/B9Jv88SZIkpeiVwLNEoltzK5FUN3ItcY23JzG78H3AtxscN9BgXyMHZq+3PXAlMWN2f2B34D3A\nBcALs2P/kbgGnZO173v1TzYVzaZMSlXYk+hhqt93D5Egt/IwMaq6J/Cj3P7aH+ILiERyO2JkGCIJ\n+gBwbrZ9GJEgfxl4hkhqR4mR8XrfBb6Q3b6AWKRtUfbai4gR6FfkYjqaOMm8ibE/3JmMHzH+EmPJ\nWyNHEyeDY4lRY4gTwnXZa9Vq3TcR029GiRH9q7M2fW6S54bocfy/2e0ziV7FHYH7WzwubyYxnf+u\nbPsKomNiPjFiDdERsQg4P/e4+xnrEPgFcWL+GPC/iOlC3wGOJ0bcyW6vofGId81Zudv3AucQPZT5\nGQpbE7/Ph+oeexUTOzGW1T3fa4lOhdpxk32eRLq1bsadFuNOi3GnJdW4t9Bs4po67zHimryRG4FL\niGvY54hrrkUNjiu6ftK67PkgEu0ziOvcZ4DVxMzEPYg1oZ4mkuzbievpmwu+RkMm25pOtiOmcuQN\nULzXCuIPeW7dvsuIacuziCnMJxOJMsClxEjyQUSSfDwxNeXhFq8zSvxB5t3P2IJte2Xb+c6Dddm+\nvRlLtjcwfmr2A0RS2sxeRM/cb3L7biCm2OzNWLJ9B+NPQA9QrE49H9MD2b/zmVqy/f8YS7QhOip+\nyViiXduXnw40yvgOErLtzxAn6MeBlcSJ8i+I3tFjgeUt2vLH2fG7Z88zg4kzeu5jYqINkcjXO4FY\nA2AX4vO0FTHFqWZKn6eLll3EvB3nsfamtez3B/ux84KdN/8nXpum5rbbbrvttttuT//tbdgGgJGR\nEQAGBwd7ert2e/369XTA44wveYS4Xm+0CDHEAmiLgJ2Ia8hjiWvnfYiSv5qiOUI+v6g9/qG6fbOz\n20cBnyQGqG4DTmfiNWphU0lipG67mpjCm6/fOJ8YKdye6H1qZRMxqlobSXye+IP9DpGI/6rBY76S\nPe50YgR1MWMj38cRU9jre97WZfvPy+27jugFOzH7OYVIyvI2En+8f0/UCx9FjIDXNHu9mvOIadlv\nyO3bmpjqfiTwz0THwkuIuvSaRq+VN0icxOYxlhTvRiTvC4np/e8lRvJn5x73FqLjova4Ru0/hXhP\nXp7b9zniJFqbFn4d0THxvtwxi4jexjnESXom0TlxKvFeXkVM7W92oj6IKBUYIt7/R4jf0V8zlnAP\n0fj38jyRqP9Tbt+fEr/bk4kSg0eJz9Y76mKb7POUN7pizQoAli5cSu12v1u7Zm2SowLGnRbjTotx\np6VZ3Buu3cDZp05WCdjbBgYGoP3ccVviOnEfxqaSX0ZcF3+iwfHDxPVlvoz0YeLa8Ce5fZ8hEvLJ\nZoUeR+QSh2bbexAzKPODLxuJ67zrc/tmAB8lZlnWX88XZs22ppO7mfhhvoL4A/1Ik8fkR7FfTCR5\nd9Ud80siaWyUaEOMmP4JUSf8AOMTo6eZuOBaEXcS0693ze17Rbbvjjaer+YOIjHMJ7wHE3/Ld+b2\ndeNryR4iprDnE+n9OvTcA0wceT+ISFYfz7afJZLd44mT6tdonmgDvD57/NnATUQ5wm5b0MZDiMXj\nvgDcQnym9mDi73qyz5MkSVJqfkMMYJxJXEseQgwKXdbk+NuIa6n5xDXuscSgSy1RnwFsk+2bQZSK\ntnO9Xm8rYg2hucT09ccoVsralMm2ppMfEAuE5XvNbiQWHjsX+BsigdqVGIm9jPGLfh1IjPDeNMXX\nXU0sBvZpIpnLW0/8Mb+ZGL2dbEXv/JT31cSJ4nKirndhdvsmYhS3XZcTMV5KLNz1BmJF9K8x/rvJ\nuzFrpbZK+TlEknkUsehYp+xIzGRYQIwqn0LUa+ddSLz3i4kFLCazllj9/Wiio+NDxEJo7VpLLKbx\nVmJdgE8xfoZBzWSfp+SlOAoCxp0a406Lcacl1bg74MPEdfSDxAzUExgbKNqFSGx3yrbPIq67biNG\ntE8irjtrdd+1Vc1PIxY4e5KJC+DWjDJxYGSyQan3EDNYNxHrIh0zybEtmWxrOrmGmEZevwDC6USS\ntD8xTfpnxLSSB4F/yB33NuCrxAjoVF1M9GbVf8f39dlrXJm93scneY76P+YjidHg64gp2vcTK3E3\nOz6/v5kniUW35hAdEd8gvs7g+BbP2+y1Wr1uft+viRPOW4iT3weJmpZWJ7Ai7RklTrwziLqYLxGJ\ndX2yvY5YwG0DYwu5NTNMdNKcT9S5LyIS4PrXLToLYAUxRfwK4ne/C9EB1MjFNP48SZIkpehhovRu\nNjHT8KrcffcSMyfvy7afIK4zdyBGmRcC/5I7fojIY/M/ZzZ53UsYPzhyNxNHwXcmrvmfAQ4nylfn\nErMur2cLWLOt6eYsov51qr1Is4hR6COIRGiqvkiMfh7WxmO15fL17q3cQcxqOKerLdoyRT5P1mwn\nxLjTYtxpMe60WLOtqXA1ck035xLTRvJfY1XEEmKEd6qJ9lxiVexjie9PVjWKrDr/UmJ6+S7EKPN0\n1NbnafGSxV1rkCRJkqph74RSN0KsiH0h4+u/Va4iI9vPE9PyP0bUrk9HIxT/PI2mMpotSVK/c2Rb\njTiyrdQNVt0AAfDGAsf0whoTg1U3QJIkSdNDL1y8SpL6xNo1a6tuQiWMOy3GnRbjTkuqcas9JtuS\nJEmSJHWY8+4lqRrWbEuS1Ces2VYjjmxLUsVWrVhVdRMkSZLUYSbbklSx4ZXDVTehNKnWuhl3Wow7\nLcadllTjVntMtiVJkiRJ6jCTbUlSaRYsXFB1Eyph3Gkx7rQYd1pSjVvtMdmWJEmSJKnDTLYlSaVJ\ntdbNuNNi3Gkx7rSkGrfaY7ItSRVbvGRx1U2QJElSh/ldaZJUDb9nW5KkPuH3bKsRR7YlSZIkSeow\nk21JUmlSrXUz7rQYd1qMOy2pxq32mGxLkiRJktRhzruXpGpYsy1JUp+wZluNOLItSRVbtWJV1U2Q\nJElSh5lsS1LFhlcOV92E0qRa62bcaTHutBh3WlKNW+0x2ZYkSZIkqcNMtiVJpVmwcEHVTaiEcafF\nuNNi3GlJNW61Z2bVDZCkVG24dkPD25IkqbfMnzO/6iZoGnJFOUmqxujo6CgQK3zWbve7kZERBgcH\nq25G6Yw7LcadFuNOS6pxuxp5e5xGLkkVW7ZsWdVNkCRJUofZOyFJ1RhNZTRbkiT1Nke22+PItiRJ\nkiRJHWayLUkqzcjISNVNqIRxp8W402LcaUk1brXHZFuSJEmSpA5z3r0kVcOabUmS1BOs2W6PI9uS\nVLGhoaGqmyBJkqQOM9mWpIotX7686iaUJtVaN+NOi3GnxbjTkmrcao/JtiRJkiRJHea8e0mqxuaa\n7YGBAazfliRJ05U12+1xZFuSJEmSpA4z2ZYklSbVWjfjTotxp8W405Jq3GrPzKobIEmpOuOvzgDg\nkDcfsvl2v9twzwZW37i66maUzrjD/DnzOemEkypskSRJ5XHevSRVY3TFmhVVt0Eq1YZrN3D2qWdX\n3QxJ0hRZs90ep5FLkiRJktRhJtuSpNKsXbO26iZUwrjTkmpNp3Gnxbil1ky2JUmSJEnqMOfdS1I1\nrNlWcqzZlqTeZM12exzZlqSKrVqxquomSJIkqcNMtiWpYsMrh6tuQmlSreE17rSkWtNp3Gkxbqk1\nk21JkiRJkjrMefeSVI3NNdtLFy7F+m2lwJptSepN1my3x5FtSZIkSZI6zGRbklSaVGt4jTstqdZ0\nGndajFtqzWRbkiq2eMniqpsgSZKkDnPevSRVw+/ZVnKs2Zak3mTNdnsc2ZYkSZIkqcNMtiVJpUm1\nhte405JqTadxp8W4pdZMtiVJkiRJ6jDn3UtSNazZVnKs2Zak3mTNdnsc2Zakiq1asarqJkiSJKnD\nTLYlqWLDK4erbkJpUq3hNe60pFrTadxpMW6pNZNtSZIkSZI6zGRbmv7mAr8EXjHFx50IfL3zzWlq\nPXByia/XbbsBzwP7N9lWGxYsXFB1Eyph3GkZHBysugmVMO60GLemaHviuvRx4prxv09y7LuBnwOb\ngF8B/wTsmLv/I8Aa4Cngoi60tWNMtqXp7+PANcC/1+1/J/A94GHixHUbcBbw0uz+LwEHAwfUPe75\n3M+jwL8B75hCe44DHmuwfzT76Vf3AjsAtxY8fj391fkgSZLUrr8nkuP5wDHAF4G9mxz7Q+ANxIDT\nrsATwHm5+/8D+Azwv7vV2E4x2Zamt62BJUzstTsb+ArwE+CPgL2Ak4CXAx/KjnkKuBr48wbP+0Ei\ncTyASB6vBn6/w23vN88DDwLPFTy+nzse2pZqDa9xpyXVmk7jTotxawq2JQaJPkUkzj8Evgkc2+T4\njcQ1F8QK6M8BD+Tu/3r2+P8q8NrHZa93HjFAdTcxGPV+YiDlP4H35o7/Q+BnxIDUfWzhwInJtjS9\nvRmYRYxg1xwI/CVwSvZzPXFSuo7oKfzb3LHfBI4CZtQ97yPESWwtsJRIzI8ADgWeBl5Wd/zZRFL+\nB0Qv4raMjY5/OnfcLGAFMe1nY9a+vF2IE+Sj2c/XgN/J3T8E3E5MH7onO+brwEuY3KuJ0f8niBPv\nRcCc3P0XA6uIDon7gF9nccxq8bx5uzF+GvlWwOeJ3tWniBP2Odl9I0RP7LnZYyZN0BcvWTyFZkiS\nJPWUVwLPEoluza3APpM85hDievVR4vrxtAbHFP0qsgOz19seuJIYsNof2B14D3AB8MLs2H8E/oy4\njtyH8dfgU2ayLU1vbyBGr/OjpMcQ08b/rsljNuVuryES4/qp5HnPZT8vAH5AJLn5Hr7fyrYvJBL7\nvyCS2h2yn7/OjhsA/gdxMvs94H8CfwUclHuebxLT3AeBNxL1N9+oa89uwLuAI4H/lj3XZF/Muy3w\nXeJkfAAxJf5gJk4tOpSYrrQI+NPsuJMmed5WTgTenj3XHtm/P8/ueweR1C8nfke/PdkTHbH0iC1o\nRm9JtYbXuNOSak2ncafFuDUFs4nrtLzHgO0mecy/Ai8CdgKeIQYw6hWdRbgOuCQ7/ivE9eeZ2fOu\nJgaa9siOfZpIsucQ19Q3F3yNhky2peltT2LEtH7fPRSbzvwwcTLbs25/rSfwBcSUnu2IkWGIpPr9\nuWMPIxLkLxMnpUeJk9WD2c8TuWO/C3yBqC+/gOjBXJTdt4gYgT6a6EC4Kbu9P/Cm3HPMJKb8/BT4\nEVF7vojmjiZ6I48lpv18n+iRfCfjF5XbBJxAjOavJqbOT/a8rewC/IL4z+A+4AbiRA7xe3+O+N3X\nfk+SJEkpepzxMw4h6rEbrQFU737iWvW9De4rOrL9n7nbT2b/PlS3b3Z2+yhiKvl6YqbiQWyBmVvy\nYEldtx3jTxAQJ5aiJxeI5Hhu3b7LiKnVs4gpOicTiTLApcRI8kFEsns8MZX74RavM0os0pZ3P2ML\ntu2Vbec7D9Zl+/ZmbJrOBsaffB8gFtNoZi9iNP03uX03ENO392ZsYbk7GN8D+gBbVqd+MZG0/wL4\nF+BbwLeZQq32RcsuYt6O8wCYNXsWOy/YefNIYK3Wtd+2a/umS3vK2r7m8muSeH9bvd/bsA0wVvNY\nGyHqt+3zzz+f/fbbb9q0p6zt2r7p0h7f7+5u1/ZNl/b4fnd2u3Z7/fr1dMAviLxzD8amkr+GGFgp\nYivGD+7UdGN9nDXEzMUZwEeJkfBd2n2yqVywSyrf1UQ9cH4BifOBDxB1J88UeI5NxCJpX862nye+\nMuE7RCL+qwaP+Ur2uNOJmuTFjI18H0dMYa+f+rMu259fLfI6ogb7xOznFCaesDYCnyNWqRwiehRf\nnbu/2evVnAcsJKbc12xNnJSPBP6ZSIxfQtSl1zR6rbzdiER9ITESX78N0Qt6GDFC/i4i6X8LcfJv\n9PvIG12xZkWTu/rX2jVrk5xabNxhw7UbOPvUyapC+sPIyMjmC9eUGHdajDstAwMDsGW545XE9dEH\niVmNw8DrgDsbHHs0Udq4kVgD51LiGuvE7P4ZRAK+jFj7ZwlRE95o1udxxHXzodn2HkTyn5/hvZEo\nB/w34E+ytm3KHvdJYgHitjiNXJre7mZicnoFUaf8kSaPyY9iv5hIUu+qO+aXROLYKNEGWEmcbJYS\nI8DX5O57mokLrhVxJ1Ejs2tu3yuyfXe08Xw1dxAJ8+zcvoOJ81v+BN6N3s/HiUXePkysCv8mYrEN\naP/31NdSTDjBuFOT4oU4GHdqjFtT9GFiRuWDxADQCYxdp+1CzGrcKdvem1gn6HFiKvcNwKm556qt\nan4ascDZk8AZTV630VfTTnZN+B5iwGQTUZZ4zKRRtWCyLU1vPyAWCMv3JN5ILDx2LvA3wOuJBHaQ\nmB6eX/TrQOJkdNMUX3c1sar3p4lR4bz1wDbESunzmHxF7/yU99XENPPLgdcSI8SXZ227borty7uc\niPFS4HeJEe4VRBKc/27yTs/k+RixavpeRC/pMcSJ+b7s/vVZW3Ykfk9NrVqxqsNNkyRJmlYeJhaQ\nnU3MFrwqd9+9xOBQ7Rrqk8DO2bEvJ2ZaPpU7fojIY/M/ZzZ53UsYP/vxbiYOhuxMJPfPAIcTs0fn\nEuWG1xeKrgmTbWl6u4Y4udQv5HU6kejtT0yT/hkxZflB4B9yx70N+CoxtWaqLiam6NR/x/f12Wtc\nmb3exyd5jvrexCOJBSmuI2q07yfqYpodn9/fzJPEVO45REfEN4jvUzy+xfM2e63JXje//SgR+4+J\nDoN9iRN07T+DTxMn73uYWHc/zvDK4RbN6B+pfu+ycaclX/OYEuNOi3FLrblAmjS9PU2sxv1+xk/l\nhkiivzrJY2cBf8z4OmUo3sn229lr1q+GDjEV6MN1+xrVs7yxbnsj0avZzPLsJ+9iJo6u1/spMdLe\nzPsb7Gv0WnnrGd/zWb99YfbTzI+B/Sa5X5IkSX3MBdKk6W8u8XVVBzN+WnQrJxJTy9/ZxuvtTUz7\nfhexwrY6b/MCaUsXLiXFxdKUnlQWSJOkftOBBdKS5Mi2NP1tAnZo43Gfz36m6pvAAcSorYm2JEmS\n1AZrtiXVGyRWOz+pxXHSlKVaw2vcaUm1ptO402LcUmsm25JUscVLFlfdBEmSJHWY8+4lqRqj1mkr\nNdZsS1Jvsma7PY5sS5IkSZLUYSbbkqTSpFrDa9xpSbWm07jTYtxSaybbkiRJkiR1mPPuJaka1mwr\nOdZsS1Jvsma7PY5sS1LFVq1YVXUTJEmS1GEm25JUseGVw1U3oTSp1vAad1pSrek07rQYt9SaybYk\nSZIkSR3mvHtJqsbmmu2lC5di/bZSYM22JPUma7bb48i2JEmSJEkdZrItSSpNqjW8xp2WVGs6jTst\nxi21ZrItSRVbvGRx1U2QJElShznvXpKq4fdsKznWbEtSb7Jmuz2ObEuSJEmS1GEm25Kk0qRaw2vc\naUm1ptO402LcUmsm25IkSZIkdZjz7iWpGtZsKznWbEtSb7Jmuz2ObEtSxVatWFV1EyRJktRhM6tu\ngCSlasO1GwAYXjnMvnvsW3FryrHhng3suvuuVTejdMYd5s+ZX2FryjMyMsLg4GDVzSidcafFj+0c\ndgAAB3pJREFUuKXWTLYlqSK16bSfPe2zyUytTfUixbglSUqP8+4lqRqjo6OjQNRB1W5LkiRNN9Zs\nt8eabUmSJEmSOsxkW5JUmlS/n9S402LcaTHutKQat9pjsi1JFVu2bFnVTZAkSVKHOe9ekqoxap22\nJEnqBdZst8eRbUmSJEmSOsxkW5JUmlRr3Yw7LcadFuNOS6pxqz0m25IkSZIkdZjz7iWpGtZsS5Kk\nnmDNdnsc2Zakig0NDVXdBEmSJHWYybYkVWz58uVVN6E0qda6GXdajDstxp2WVONWe0y2JUmSJEnq\nMOfdS1I1NtdsDwwMYP22JEmarqzZbo8j25IkSZIkdZjJtiSpNKnWuhl3Wow7LcadllTjVntMtiWp\nYsuWLau6CZIkSeow591LUjX8nm1JktQTrNlujyPbkiRJkiR1mMm2JKk0qda6GXdajDstxp2WVONW\ne0y2JUmSJEnqMOfdS1I1rNmWJEk9wZrt9jiyLUkVGxoaqroJkiRJ6jCTbUmq2PLly6tuQmlSrXUz\n7rQYd1qMOy2pxq32mGxLkiRJktRhzruXpGpsrtkeGBjA+m1JkjRdWbPdHke2JUmSJEnqMJNtSVJp\nUq11M+60GHdajDstqcat9phsS1LFli1bVnUTJEmS1GHOu5ekavg925IkqSdYs90eR7YlSZIkSeow\nk21JUmlSrXUz7rQYd1qMOy2pxq32mGxLkiRJktRhzruXpGpYsy1JknqCNdvtcWRbkio2NDRUdRMk\nSZLUYSbbklSx5cuXV92E0qRa62bcaTHutBh3WlKNW+0x2ZYkleaWW26pugmVMO60GHdajDstqcat\n9phsS5JK88gjj1TdhEoYd1qMOy3GnZZU41Z7TLYlSZIkSeowk21JUmnWr19fdRMqYdxpMe60GHda\nUo1b7XH5dkmqxi3Aa6puhCRJUgG3AvtV3QhJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJnfFW4OfA\nXcBpTY75fHb/rcDvldSubmsV96uAG4CngJNLbFe3tYr7GOJ9vg34IbBveU3rqlZxH0nEfTNwE/Cm\n8prWVUX+vgEOAJ4F3llGo0rQKu5BYBPxft8MfLK0lnVXkfd7kIj5p8BIKa3qvlZxn8LYe3078Vl/\nUWmt655Wcc8DvkMsePpT4LjSWtZdreJ+MfB14pz+Y2Cf8pomSdKYGcDdwG7AVsR/yHvVHfOHwLey\n278P/KisxnVRkbhfCiwEzqJ/ku0icb8OmJvdfivpvN/b5m6/Oju+1xWJu3bc94Bh4KiyGtdFReIe\nBP5Pqa3qviJxvwj4GbBTtj2vrMZ1UdHPec1i4JruN6vrisQ9BJyT3Z4H/Bcws5zmdU2RuM8FPpXd\nXkB/vN9d4/dsS1L3HEj8p7UeeAa4ihjhy3sbcEl2+8fExdrLSmpftxSJ+yFgTXZ/vygS9w3EiB/E\n+70Tva9I3L/J3Z4N/KqUlnVXkbgBPgp8lfjM94Oicffb18sWifto4GvAfdl2Sp/zmqOBK7vfrK4r\nEvcDwJzs9hwi2X62pPZ1S5G49wKuy26vJRLzl5bTvN5jsi1J3fM7wMbc9n3ZvlbH9HoCViTufjTV\nuD/A2KyGXlY07rcDdwLfBk4soV3dVvTv+0jgi9n2aAnt6rYicY8CBxPTTL8F7F1O07qqSNx7AtsT\nicga4NhymtZVUzmvvRA4jOhw6HVF4l5JTKG+n/isn1RO07qqSNy3MlYScyCwK71/3dI1vT7VQZKm\ns6IX1vUjQL1+Qd7r7W/XVOJ+I3A88PoutaVMReP+RvZzKHAZMf2wlxWJ+3zg9OzYAfpjtLdI3D8B\ndgaeAA4n3vdXdrNRJSgS91bA/sAiIvG8gSgVuauL7eq2qZzXjgD+FXikS20pU5G4P0FMsx4EdgdW\nA68BHutes7quSNyfA/6WsRr9m4HnutmoXmayLUnd8x/EBWfNzoxNL2x2zE7Zvl5WJO5+VDTufYkR\nkbcCD5fQrm6b6vv9A+L64yXEtMteVSTu1xLTMCFqOg8npmb2cj1zkbjzyca3gS8QI76/7m7TuqpI\n3BuJqeNPZj/fJ5KvXk62p/L3/W76Ywo5FIv7YODs7PY9wDqiE3FN11vXPUX/vo/Pba8D/r3L7ZIk\naYKZxH/AuwFb03qBtIPojwWzisRdM0T/LJBWJO5diHq4g0ptWXcViXt3xkZ198+O73VT+ZwDXER/\nrEZeJO6XMfZ+H0jUf/a6InG/ilgsagYxsn07vT+FvujnfC7ReTartJZ1V5G4zwOWZbdfRiSl25fU\nvm4pEvfc7D6AJcDFJbVNkqQJDicWELkb+Mts39Lsp+aC7P5biUSkH7SKewdiFGgTMbp7L7FwVq9r\nFfeFxAVp7Wtybiy7gV3SKu5Tia/GuZkY2T6g7AZ2SZG/75p+Sbahddx/TrzftwDX0z+dS0Xe71OI\nFclvpz/WJoBicb8PuKLkdnVbq7jnAauI/7tvJxaH6wet4n5ddv/PicUf59Y/gSRJkiRJkiRJkiRJ\nkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkqRq/X+gw5RTr4p5twAAAABJRU5ErkJg\ngg==\n",
"text": [
"<matplotlib.figure.Figure at 0x105dd0978>"
]
}
],
"prompt_number": 15
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>\n",
"As we can see from the results above, we are already able to make our Python code run almost as twice as fast if we compile it via Cython (Python on list: 332 \u00b5s, untyped Cython on list: 183 \u00b5s). \n",
"However, although it is more \"work\" to adjust the Python code, the \"typed Cython with memoryview on numpy array\" is significantly as expected."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"cython_bonus\"></a>\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"How to use Cython without the IPython magic"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#sections)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"IPython's notebook is really great for explanatory analysis and documentation, but what if we want to compile our Python code via Cython without letting IPython's magic doing all the work? \n",
"These are the steps you would need."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1. Creating a .pyx file containing the the desired code or function."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%file cython_bubblesort_nomagic.pyx\n",
"\n",
"cimport numpy as np\n",
"cimport cython\n",
"@cython.boundscheck(False) \n",
"@cython.wraparound(False)\n",
"cpdef cython_bubblesort_nomagic(np.ndarray[long, ndim=1] inp_ary):\n",
" \"\"\" The Cython implementation of Bubblesort with NumPy memoryview.\"\"\"\n",
" cdef unsigned long length, i, swapped, ele, temp\n",
" cdef long[:] np_ary = inp_ary\n",
" length = np_ary.shape[0]\n",
" swapped = 1\n",
" for i in xrange(0, length):\n",
" if swapped: \n",
" swapped = 0\n",
" for ele in xrange(0, length-i-1):\n",
" if np_ary[ele] > np_ary[ele + 1]:\n",
" temp = np_ary[ele + 1]\n",
" np_ary[ele + 1] = np_ary[ele]\n",
" np_ary[ele] = temp\n",
" swapped = 1\n",
" return inp_ary"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Writing cython_bubblesort_nomagic.pyx\n"
]
}
],
"prompt_number": 16
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2. Creating a simple setup file"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%file setup.py\n",
"\n",
"import numpy as np\n",
"from distutils.core import setup\n",
"from distutils.extension import Extension\n",
"from Cython.Distutils import build_ext\n",
"\n",
"setup(\n",
" cmdclass = {'build_ext': build_ext},\n",
" ext_modules = [\n",
" Extension(\"cython_bubblesort_nomagic\",\n",
" [\"cython_bubblesort_nomagic.pyx\"],\n",
" include_dirs=[np.get_include()])\n",
" ]\n",
")"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Writing setup.py\n"
]
}
],
"prompt_number": 17
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"####3. Building and Compiling"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"!python3 setup.py build_ext --inplace"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"running build_ext\r\n"
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"cythoning cython_bubblesort_nomagic.pyx to cython_bubblesort_nomagic.c\r\n"
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"building 'cython_bubblesort_nomagic' extension\r\n",
"/usr/bin/clang -fno-strict-aliasing -Werror=declaration-after-statement -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/Users/sebastian/miniconda3/envs/py34/include -arch x86_64 -I/Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include -I/Users/sebastian/miniconda3/envs/py34/include/python3.4m -c cython_bubblesort_nomagic.c -o build/temp.macosx-10.5-x86_64-3.4/cython_bubblesort_nomagic.o\r\n"
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"In file included from cython_bubblesort_nomagic.c:352:\r\n",
"In file included from /Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/arrayobject.h:4:\r\n",
"In file included from /Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/ndarrayobject.h:17:\r\n",
"In file included from /Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/ndarraytypes.h:1761:\r\n",
"\u001b[1m/Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:15:2: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1m\r\n",
" \"Using deprecated NumPy API, disable it by \" \"#defining\r\n",
" NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-W#warnings]\u001b[0m\r\n",
"#warning \"Using deprecated NumPy API, disable it by \" \\\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m"
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"In file included from cython_bubblesort_nomagic.c:352:\r\n",
"In file included from /Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/arrayobject.h:4:\r\n",
"In file included from /Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/ndarrayobject.h:26:\r\n",
"\u001b[1m/Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/__multiarray_api.h:1629:1: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1m\r\n",
" unused function '_import_array' [-Wunused-function]\u001b[0m\r\n",
"_import_array(void)\r\n",
"\u001b[0;1;32m^\r\n",
"\u001b[0mIn file included from cython_bubblesort_nomagic.c:353:\r\n",
"In file included from /Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/ufuncobject.h:327:\r\n",
"\u001b[1m/Users/sebastian/miniconda3/envs/py34/lib/python3.4/site-packages/numpy/core/include/numpy/__ufunc_api.h:241:1: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1m\r\n",
" unused function '_import_umath' [-Wunused-function]\u001b[0m\r\n",
"_import_umath(void)\r\n",
"\u001b[0;1;32m^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:18981:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_PyUnicode_FromString' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char* c_str) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:19132:33: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_PyInt_FromSize_t' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t ival) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:16538:1: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_add_acquisition_count_locked' [-Wunused-function]\u001b[0m\r\n",
"__pyx_add_acquisition_count_locked(__pyx_atomic_int *acquisition_count,\r\n",
"\u001b[0;1;32m^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:16548:1: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_sub_acquisition_count_locked' [-Wunused-function]\u001b[0m\r\n",
"__pyx_sub_acquisition_count_locked(__pyx_atomic_int *acquisition_count,\r\n",
"\u001b[0;1;32m^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17017:26: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_PyBytes_Equals' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE int __Pyx_PyBytes_Equals(PyObject* s1, PyObject* s2...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17298:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_GetItemInt_List_Fast' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, P...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17312:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_GetItemInt_Tuple_Fast' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, ...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17491:38: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_PyInt_From_unsigned_long' [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE PyObject* __Pyx_PyInt_From_unsigned_long(unsi...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17538:36: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1mfunction\r\n",
" '__Pyx_PyInt_As_unsigned_long' is not needed and will not be emitted\r\n",
" [-Wunneeded-internal-declaration]\u001b[0m\r\n",
"static CYTHON_INLINE unsigned long __Pyx_PyInt_As_unsigned_long(PyObject *x) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17644:48: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_t_float_complex_from_parts' [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_fro...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17654:30: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_eqf'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE int __Pyx_c_eqf(__pyx_t_float_complex a, __pyx_...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17657:48: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_sumf'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_sumf(__pyx_t_floa...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17663:48: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_difff'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_difff(__pyx_t_flo...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17675:48: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_quotf'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quotf(__pyx_t_flo...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17682:48: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_negf'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_negf(__pyx_t_floa...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17688:30: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__Pyx_c_is_zerof' [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE int __Pyx_c_is_zerof(__pyx_t_float_complex a) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17691:48: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_conjf'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_conjf(__pyx_t_flo...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17705:52: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_powf'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_powf(__pyx_t_...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17764:49: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_t_double_complex_from_parts' [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_f...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17774:30: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_eq'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE int __Pyx_c_eq(__pyx_t_double_complex a, __pyx_...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17777:49: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_sum'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_sum(__pyx_t_doub...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17783:49: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_diff'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_diff(__pyx_t_dou...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17795:49: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_quot'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot(__pyx_t_dou...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17802:49: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_neg'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_neg(__pyx_t_doub...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17808:30: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_is_zero'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE int __Pyx_c_is_zero(__pyx_t_double_complex a) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17811:49: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_conj'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_conj(__pyx_t_dou...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:17825:53: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function '__Pyx_c_pow'\r\n",
" [-Wunused-function]\u001b[0m\r\n",
" static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_pow(__pyx_t_...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:18247:27: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1mfunction '__Pyx_PyInt_As_char' is\r\n",
" not needed and will not be emitted [-Wunneeded-internal-declaration]\u001b[0m\r\n",
"static CYTHON_INLINE char __Pyx_PyInt_As_char(PyObject *x) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:18347:27: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1mfunction '__Pyx_PyInt_As_long' is\r\n",
" not needed and will not be emitted [-Wunneeded-internal-declaration]\u001b[0m\r\n",
"static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) {\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:2955:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_PyArray_MultiIterNew1' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyOb...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:3005:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_PyArray_MultiIterNew2' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyOb...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:3055:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_PyArray_MultiIterNew3' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyOb...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:3105:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_PyArray_MultiIterNew4' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyOb...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:3155:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_PyArray_MultiIterNew5' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyOb...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:3909:27: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_set_array_base' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *_...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m\u001b[1mcython_bubblesort_nomagic.c:3997:32: \u001b[0m\u001b[0;1;35mwarning: \u001b[0m\u001b[1munused function\r\n",
" '__pyx_f_5numpy_get_array_base' [-Wunused-function]\u001b[0m\r\n",
"static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObje...\r\n",
"\u001b[0;1;32m ^\r\n",
"\u001b[0m"
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"39 warnings generated.\r\n",
"/usr/bin/clang -bundle -undefined dynamic_lookup -L/Users/sebastian/miniconda3/envs/py34/lib -arch x86_64 build/temp.macosx-10.5-x86_64-3.4/cython_bubblesort_nomagic.o -L/Users/sebastian/miniconda3/envs/py34/lib -o /Users/sebastian/github/python_reference/tutorials/cython_bubblesort_nomagic.so\r\n"
]
}
],
"prompt_number": 18
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 4. Importing and running the code"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import cython_bubblesort_nomagic\n",
"\n",
"cython_bubblesort_nomagic.cython_bubblesort_nomagic(np.array([4,6,2,1,6]))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 19,
"text": [
"array([1, 2, 4, 6, 6])"
]
}
],
"prompt_number": 19
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Speed comparison"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#Sections)]"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from cython_bubblesort_nomagic import cython_bubblesort_nomagic\n",
"\n",
"list_a = [random.randint(0,100000) for num in range(100000)]\n",
"\n",
"ary_a = np.asarray(list_a)\n",
"ary_b = np.asarray(list_a)\n",
"\n",
"times = []\n",
"\n",
"times.append(min(timeit.Timer('cython_bubblesort_typed(ary_a)', \n",
" 'from __main__ import cython_bubblesort_typed, ary_a').repeat(repeat=3, number=1000)))\n",
"\n",
"times.append(min(timeit.Timer('cython_bubblesort_nomagic(ary_b)', \n",
" 'from __main__ import cython_bubblesort_nomagic, ary_b').repeat(repeat=3, number=1000)))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 20
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from matplotlib import pyplot as plt\n",
"import numpy as np\n",
"\n",
"bar_labels = ('Cython with IPython magic', \n",
" 'Cython without IPython magic')\n",
"\n",
"fig = plt.figure(figsize=(10,8))\n",
"\n",
"# plot bars\n",
"y_pos = np.arange(len(times))\n",
"plt.yticks(y_pos, bar_labels, fontsize=14)\n",
"bars = plt.barh(y_pos, times,\n",
" align='center', alpha=0.4, color='g')\n",
"\n",
"# annotation and labels\n",
"\n",
"for b,d in zip(bars, times):\n",
" plt.text(max(times)+0.02, b.get_y() + b.get_height()/2.5, \n",
" '{:.2} ms'.format(d),\n",
" ha='center', va='bottom', fontsize=12)\n",
"\n",
"t = plt.title('Bubblesort on 100,000 random integers', fontsize=18)\n",
"plt.ylim([-1,len(times)+0.5])\n",
"plt.vlines(min(times), -1, len(times)+0.5, linestyles='dashed')\n",
"plt.grid()\n",
"\n",
"plt.show()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"png": "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\nPYSOZ0+HlIY9HZIkKQv2dEiSJEka90w6JKkE65LzZvzyZvzyZexk0iFJkiSprezpkNKwp0OSJGXB\nng5JkiRpBL29vamH0PFMOiSpBOuS82b88mb88pU6dn19fUn3L5MOSZIkSW1mT4eUhj0dkiSNkUql\ngn93R8+eDkmSJEnjnkmHJJWQui5ZrTF+eTN++TJ2MumQJEnShNbT05N6CB3Png4pDXs6JElSFuzp\nkCRJkjTumXRIUgnWJefN+OXN+OXL2MmkQ5IkSVJb2dMhpWFPhyRJyoI9HZIkSdIIent7Uw+h45l0\nSFIJ1iXnzfjlzfjlK3Xs+vr6ku5fJh2SJEmS2syeDikNezokSRojlUoF/+6Onj0dkiRJksY9kw5J\nKiF1XbJaY/zyZvzyZexk0iFJkqQJraenJ/UQOp49HVIa9nRIkqQs2NMhSZIkadwz6ZCkEqxLzpvx\ny5vxy5exk0mHJEmSpLayp0NKw54OSZKUBXs6JEmSpBH09vamHkLHM+mQpBKsS86b8cub8ctX6tj1\n9fUl3b9MOiRJkiS1mT0dUhr2dEiSNEYqlQr+3R09ezokSZIkjXsmHZJUQuq6ZLXG+OXN+OXL2Mmk\nQ5IkSRNaT09P6iF0PHs6pDTs6ZAkSVmwp0OSJEnSuGfSIUklWJecN+OXN+OXL2Mnkw5JkiRJbWVP\nh5SGPR2SJCkL9nRIkiRJI+jt7U09hI5n0iFJJViXnDfjlzfjl6/Usevr60u6f5l0SJIkSWozezqk\nNOzpkCRpjFQqFfy7O3r2dEiSJEka90w6JKmE1HXJao3xy5vxy5exk0mHJEmSJrSenp7UQ+h49nRI\nadjTIUmSsmBPhyRJkqRxz6RDkkqwLjlvxi9vxi9fxk4mHZIkSZLayp4OKQ17OiRJUhbs6ZAkSZJG\n0Nvbm3oIHc+kQ5JKsC45b8Yvb8YvX6lj19fXl3T/MumQJEmS1Gb2dEhp2NMhSdIYqVQq+Hd39Ozp\nkCRJkjTumXRIUgmp65LVGuOXN+OXL2Mnkw5JkiRNaD09PamH0PHs6ZDSsKdDkiRlwZ4OSZIkSeOe\nSYcklWBdct6MX96MX76MnUw6JEmSJLWVPR1SGvZ0SJKkLNjTIUmSJI2gt7c39RA6nkmHJJVgXXLe\njF/ejF++Useur68v6f5l0iFJpXz/+99PPQS1wPjlzfjly9jJpEOSSnj00UdTD0EtMH55M375MnYy\n6ZAkSZLUViYdklTCypUrUw9BLTB+eTN++TJ28pK5UhrfB2amHoQkSVIT7gJemnoQkiRJkiRJkiRJ\nkgteCbgAAAKlSURBVCRJkiRJkiRJkpTS64CfAj8DzhhinS8U998FHFDysWqvVuK3EvgBcCdwW/uG\nqGGMFL99gO8CTwKnlXys2quV2K3Ez15qI8XvWOJ35g+A7wAvKfFYtVcrsVuJnz0pia2Ae4AZwNbE\nFar2rVvnb4Alxf9fAdxa4rFqr1biB3AfMK29Q9QwmonfTsAs4CwGT1z9/KXVSuzAz15qzcTvz4Gp\nxf9fh3/7xotWYgclP3t+T4e05byc+PCuBJ4G/g14U906hwGXFP//HvBsYNcmH6v2Gm38dqm538uQ\np9NM/H4D3F7cX/axap9WYlflZy+dZuL3XWB18f/vAX9S4rFqn1ZiV9X0Z8+kQ9pyngc8UHP7wWJZ\nM+vs1sRj1V6txA9gE3A9MTE6qU1j1NCaiV87HqvWtfr6+9lLq2z8TmTgjLGfvbRaiR2U/OxNGsUA\nJTW2qcn1PCI3PrUav9nAQ0QZyHVEjewtW2Bcak6z8dvSj1XrWn39Xw38Ej97qZSJ32uAE4iYlX2s\ntrxWYgclP3ue6ZC2nF8A02tuTyeOGgy3zp8U6zTzWLXXaOP3i+L/DxU/fwN8izhtrbHTymfIz19a\nrb7+vyx++tlLo9n4vQRYSJSp/r7kY9UercQO/OxJyUwC7iUasrZh5EbkVzLQkNXMY9VercTvWUBX\n8f8diCt8/HUbx6rNlfkM9TK4GdnPX1qtxM7PXnrNxO/5RO/AK0fxWLVPK7Hzsycl9npgBfEB/Uix\n7F3Fv6rzi/vvAl42wmM1tkYbvxcQv6y/D/wI45fKSPHblahfXk0crbsfmDzMYzV2Rhs7P3vjw0jx\n+yrwO+LSqvWXV/Wzl9ZoY+dnT5IkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSWrg\n/wPnZgT7CL7wcwAAAABJRU5ErkJggg==\n",
"text": [
"<matplotlib.figure.Figure at 0x105844390>"
]
}
],
"prompt_number": 21
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"numba\"></a>\n",
"<br>\n",
"<br>"
]
}
],
"metadata": {}
}
]
}