{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Typing basics\n",
    "\n",
    "This demo assumes you are familiar with the basics of running an Intrepydd, which is covered in the [\"Hello, world!\" demo](./001-hello-world.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As a toy example, consider the function, `increment_elements(xs, value)`, defined below. Given a three-dimensional Numpy array, `xs`, and a floating-point value, `value`, it adds `value` to every element of `xs`, modifying `xs` in-place. This function only uses native Python and Numpy constructs.\n",
    "\n",
    "> The example is strictly to help show the similarity between Intrepydd code and basic Python. In particular, it uses explicit nested loops to iterate over the elements as opposed to more idiomatic Numpy methods, such as `xs += value`. A key feature of Intrepydd is that it can optimize many types of explicit loop code, and future versions will support mixing of \"native\" Numpy and loop-based code. In this way, Intrepydd takes inspiration from [Numba](https://numba.pydata.org/), although our planned extensions will go further."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def increment_elements(xs, value):\n",
    "    '''                                                                                                                                                                                                     \n",
    "    Increment every element in array `xs` by `value`.                                                                                                                                                       \n",
    "    Assume the array is 3d.                                                                                                                                                                                 \n",
    "    '''\n",
    "    assert len(xs.shape) == 3\n",
    "    for i in range(xs.shape[0]):\n",
    "        for j in range(xs.shape[1]):\n",
    "            for k in range(xs.shape[2]):\n",
    "                xs[i, j, k] += value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a function to test this function. We'll reuse this function later to test an Intrepydd version."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test_code(increment_elements_function=increment_elements):\n",
    "    from numpy import arange\n",
    "    xs = arange(12).reshape(2, 2, 3).astype('double')\n",
    "    print('=== before ===')\n",
    "    print(xs)\n",
    "    increment_elements(xs, 3.0)\n",
    "    print('\\n=== after ===')\n",
    "    print(xs)\n",
    "    \n",
    "test_code()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Types ###\n",
    "\n",
    "The first technique for attaining performance using Intrepydd is to use type specialization. For example, if you know that your code's Python object is an array of floating-point values, Intrepydd can use this information to generate specialized code that is presumably faster and more energy-efficient.\n",
    "\n",
    "For instance, suppose we know that the array only contains 64-bit floating-point values, or `double` values, and that the `value` increment is also a `double`. Then we can take the original Python function and simply modify the function signature (`def` line) to declare this fact. That is,\n",
    "```python\n",
    "    def increment_elements(xs, value):\n",
    "```\n",
    "becomes\n",
    "```python\n",
    "    def increment_elements_pydd(xs: Array(float64), value: double):\n",
    "```\n",
    "Here is a complete implementation, which we will write to `demo2.pydd`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile demo2.pydd\n",
    "def increment_elements_pydd(xs: Array(float64), value: double): # Add types\n",
    "    '''                                                                                                                                                                                                     \n",
    "    Increment every element in array `xs` by `value`.                                                                                                                                                       \n",
    "    Assume the array is 3d.                                                                                                                                                                                 \n",
    "    '''\n",
    "    for i in range(shape(xs, 0)):\n",
    "        for j in range(shape(xs, 1)):\n",
    "            for k in range(shape(xs, 2)):\n",
    "                xs[i, j, k] += value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are some additional differences between certain operations involving Intrepydd arrays and Numpy arrays. In Intrepydd v0.1, field objects (e.g., `xs.shape[0]`) have function counterparts (e.g., `shape(xs, 0)`)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Compile and run ###\n",
    "\n",
    "Let's go ahead and compile this new version:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "!pyddc demo2.pydd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's test the correctness of the Intrepydd version by importing the new module and running the test code against it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!ls -al"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import demo2\n",
    "test_code(demo2.increment_elements_pydd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If everything went well, you should see the same numerical output as with the original version."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Summary and next steps ###\n",
    "\n",
    "A key first step in enabling higher performance is type specialization. The first way you do that in Intrepydd is by modifying the signatures of your function definitions to include annotations."
   ]
  }
 ],
 "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.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}