{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Numpy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See also \n",
    "* [Slides by Bryan Van de Ven](https://www.slideshare.net/slideshow/introduction-to-numpy/18938104)\n",
    "* [Tutorial for beginners](https://numpy.org/doc/1.25/user/absolute_beginners.html)\n",
    "* [100 numpy exercises](https://github.com/rougier/numpy-100) with hints and answers, from easy to hard ones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We start with an illustration -- numerical computation of $\\int_0^\\pi \\sin(x)$. \n",
    "First in Sage/Python. \n",
    "If you run this in Python, use the import below. In Sage you may go betwen the two versions of sin\n",
    "and compare. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 191,
   "metadata": {},
   "outputs": [],
   "source": [
    "from math import sin"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 193,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sage.all import sin"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 194,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 3.65 s, sys: 2.79 ms, total: 3.65 s\n",
      "Wall time: 3.67 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1.999999999835479"
      ]
     },
     "execution_count": 194,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time\n",
    "N=10**5\n",
    "float(sum([ float(sin(pi*x/N)) for x in range(N)])*pi/N)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the same in numpy. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 195,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 196,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.83 ms, sys: 0 ns, total: 1.83 ms\n",
      "Wall time: 1.32 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1.9999999998355071"
      ]
     },
     "execution_count": 196,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time\n",
    "N=10**5\n",
    "x = np.linspace(0,np.pi, N+1)\n",
    "np.sum( np.sin(x) )*np.pi/N"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, if you want precise answer, you need to use Sage (or sympy or ...). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 16.9 ms, sys: 3.72 ms, total: 20.6 ms\n",
      "Wall time: 19.8 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = var('x')\n",
    "%time integral(sin(x), x, 0, pi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Basic numpy datatype -- efficient n-dimensional array\n",
    "\n",
    "* All elements of the same type\n",
    "* Stored so that efficient looping (in C) is possible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 199,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[  21    5 2024]\n",
      "<class 'numpy.ndarray'>\n",
      "int64\n",
      "(3,)\n"
     ]
    }
   ],
   "source": [
    "x = np.array([21,5,2024])  # compare with x = np.array([21,5,2024.])\n",
    "print(x)\n",
    "print(type(x))\n",
    "print(x.dtype)\n",
    "print(x.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 197,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<class 'list'>"
      ]
     },
     "execution_count": 197,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type([21,5,2024])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 200,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 2],\n",
       "       [3, 4]])"
      ]
     },
     "execution_count": 200,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "z = np.array([[1,2],[3,4]])\n",
    "z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "\n",
      "[[[0. 0.]\n",
      "  [0. 0.]\n",
      "  [0. 0.]]\n",
      "\n",
      " [[0. 0.]\n",
      "  [0. 0.]\n",
      "  [0. 0.]]]\n",
      "\n",
      "[[1. 0. 0. 0. 0.]\n",
      " [0. 1. 0. 0. 0.]\n",
      " [0. 0. 1. 0. 0.]\n",
      " [0. 0. 0. 1. 0.]\n",
      " [0. 0. 0. 0. 1.]]\n",
      "\n",
      "[[0. 1. 0. 0. 0. 0. 0. 0.]\n",
      " [0. 0. 1. 0. 0. 0. 0. 0.]\n",
      " [0. 0. 0. 1. 0. 0. 0. 0.]\n",
      " [0. 0. 0. 0. 1. 0. 0. 0.]\n",
      " [0. 0. 0. 0. 0. 1. 0. 0.]]\n"
     ]
    }
   ],
   "source": [
    "print(np.ones((2,3)))\n",
    "print()\n",
    "print(np.zeros((2,3,2)))\n",
    "print()\n",
    "print(np.identity(5))\n",
    "print()\n",
    "print(np.eye(5,8,1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 0, 0],\n",
       "       [0, 3, 0],\n",
       "       [0, 0, 9]])"
      ]
     },
     "execution_count": 102,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.diag([1,3,9])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 201,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[   0,    0,    0,    0],\n",
       "       [  21,    0,    0,    0],\n",
       "       [   0,    5,    0,    0],\n",
       "       [   0,    0, 2024,    0]])"
      ]
     },
     "execution_count": 201,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.diag(x, -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Type of the variables is guessed: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 202,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 202,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.arange(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 203,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0., 1., 2., 3., 4.])"
      ]
     },
     "execution_count": 203,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.arange(5.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But can also be specified."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0., 1., 2., 3., 4.])"
      ]
     },
     "execution_count": 104,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.arange(5, dtype=np.float64)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 0, 0, 0, 0])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.zeros(5, dtype=\"int64\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0., 0., 0., 0., 0.])"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.zeros(5, dtype=\"float64\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Operations are entrywise and fast\n",
    "* and built-in functions as well\n",
    "* custom functions can be \"vectorized\"\n",
    "* when two arguments: they must be of same, or compatible size (\"broadcasting\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 209,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 1 2 3 4 5]\n",
      "[ 0  1  4  9 16 25]\n",
      "[ 1  3  5  7  9 11]\n"
     ]
    }
   ],
   "source": [
    "x = np.arange(6)\n",
    "print(x)\n",
    "print(x**2)  ## if you are using Sage + numpy, you can use print(x^2) as well\n",
    "print(2*x+1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 219,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  2,  4,  6,  8, 10])"
      ]
     },
     "execution_count": 219,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "2*x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 220,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  2,  4,  6,  8, 10])"
      ]
     },
     "execution_count": 220,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.array([2,2,2,2,2,2])*x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 210,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "operands could not be broadcast together with shapes (6,) (4,) ",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[210], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m y \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marange(Integer(\u001b[38;5;241m4\u001b[39m))\n\u001b[0;32m----> 2\u001b[0m \u001b[43mx\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43my\u001b[49m\n",
      "\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (6,) (4,) "
     ]
    }
   ],
   "source": [
    "y = np.arange(4)\n",
    "x+y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 217,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  2,  4,  6,  8, 10])"
      ]
     },
     "execution_count": 217,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = np.arange(6)\n",
    "x+y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 213,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0],\n",
       "       [1],\n",
       "       [2]])"
      ]
     },
     "execution_count": 213,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = np.arange(3).reshape(3,1)\n",
    "y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 214,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2, 3, 4, 5],\n",
       "       [1, 2, 3, 4, 5, 6],\n",
       "       [2, 3, 4, 5, 6, 7]])"
      ]
     },
     "execution_count": 214,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = x+y\n",
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 221,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.63 µs ± 64.5 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n"
     ]
    }
   ],
   "source": [
    "x = np.arange(10**4)\n",
    "%timeit y = x**2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compare this to python list comprehension: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "977 µs ± 11.2 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit ly = [t**2 for t in range(10**4)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are many \"fast functions\" -- officially called [universal functions](https://numpy.org/doc/stable/reference/ufuncs.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 16\n",
      "[1 1 2 3 3]\n"
     ]
    }
   ],
   "source": [
    "print(x.min(), (x^2).max())\n",
    "print(x.clip(min=1,max=3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 0.          0.84147098  0.90929743  0.14112001 -0.7568025 ]\n",
      "[ 1.          2.71828183  7.3890561  20.08553692 54.59815003]\n"
     ]
    }
   ],
   "source": [
    "print(np.sin(x))\n",
    "print(np.exp(x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can define new ones!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 224,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.  , 0.25, 0.5 , 0.75, 1.  ])"
      ]
     },
     "execution_count": 224,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "l = np.linspace(0,1,5); l"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 227,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "def myfunc(a):\n",
    "    if a>0.5:\n",
    "        return a+1\n",
    "    else:\n",
    "        return a\n",
    "    \n",
    "#myfunc(l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 226,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.  , 0.25, 0.5 , 1.75, 2.  ])"
      ]
     },
     "execution_count": 226,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vfunc = np.vectorize(myfunc)\n",
    "vfunc(l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 228,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[0, 1, 2, 3, 4, 5],\n",
       "        [1, 2, 3, 4, 5, 6],\n",
       "        [2, 3, 4, 5, 6, 7]]),\n",
       " (3, 6))"
      ]
     },
     "execution_count": 228,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s, s.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 229,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(63, 63)"
      ]
     },
     "execution_count": 229,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s.sum(), np.sum(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 3,  6,  9, 12, 15])"
      ]
     },
     "execution_count": 141,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s.sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 142,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([10, 15, 20])"
      ]
     },
     "execution_count": 142,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s.sum(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 230,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[[ 0.,  0.,  0.,  0.,  0.,  0.,  0.],\n",
       "         [ 0.,  1.,  2.,  3.,  4.,  5.,  6.],\n",
       "         [ 0.,  2.,  4.,  6.,  8., 10., 12.],\n",
       "         [ 0.,  3.,  6.,  9., 12., 15., 18.],\n",
       "         [ 0.,  4.,  8., 12., 16., 20., 24.]],\n",
       " \n",
       "        [[ 1.,  1.,  1.,  1.,  1.,  1.,  1.],\n",
       "         [ 1.,  2.,  3.,  4.,  5.,  6.,  7.],\n",
       "         [ 1.,  3.,  5.,  7.,  9., 11., 13.],\n",
       "         [ 1.,  4.,  7., 10., 13., 16., 19.],\n",
       "         [ 1.,  5.,  9., 13., 17., 21., 25.]]]),\n",
       " (2, 5, 7))"
      ]
     },
     "execution_count": 230,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b, b.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.,  1.,  1.,  1.,  1.,  1.,  1.],\n",
       "       [ 1.,  3.,  5.,  7.,  9., 11., 13.],\n",
       "       [ 1.,  5.,  9., 13., 17., 21., 25.],\n",
       "       [ 1.,  7., 13., 19., 25., 31., 37.],\n",
       "       [ 1.,  9., 17., 25., 33., 41., 49.]])"
      ]
     },
     "execution_count": 144,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b.sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 148,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([  7.,  49.,  91., 133., 175.])"
      ]
     },
     "execution_count": 148,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b.sum(axis=(0,2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Concatenation vs addition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2, 3, 4, 5, 6]"
      ]
     },
     "execution_count": 121,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "L = [1,2,3]; M = [4,5,6]\n",
    "L + M"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 231,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([5, 7, 9])"
      ]
     },
     "execution_count": 231,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "l = np.array(L); m = np.array(M)\n",
    "l + m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 232,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 2, 3, 4, 5, 6])"
      ]
     },
     "execution_count": 232,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.concatenate([l,m])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Subarray by properties"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 233,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = np.arange(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 234,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([False, False, False, False, False, False,  True,  True,  True,\n",
       "        True])"
      ]
     },
     "execution_count": 234,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x > 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 156,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6, 7, 8, 9])"
      ]
     },
     "execution_count": 156,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x[x>5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 235,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 235,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum(x>5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Not to confuse with the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 158,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6, 6, 6, 6, 6, 6, 6, 7, 8, 9])"
      ]
     },
     "execution_count": 158,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x.clip(min=6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Indexing of a subarray"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 236,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(2, 5, 7)\n",
      "[[[ 0.  0.  0.  0.  0.  0.  0.]\n",
      "  [ 0.  1.  2.  3.  4.  5.  6.]\n",
      "  [ 0.  2.  4.  6.  8. 10. 12.]\n",
      "  [ 0.  3.  6.  9. 12. 15. 18.]\n",
      "  [ 0.  4.  8. 12. 16. 20. 24.]]\n",
      "\n",
      " [[ 1.  1.  1.  1.  1.  1.  1.]\n",
      "  [ 1.  2.  3.  4.  5.  6.  7.]\n",
      "  [ 1.  3.  5.  7.  9. 11. 13.]\n",
      "  [ 1.  4.  7. 10. 13. 16. 19.]\n",
      "  [ 1.  5.  9. 13. 17. 21. 25.]]]\n"
     ]
    }
   ],
   "source": [
    "b = np.fromfunction(lambda i,j,k: i+j*k, (2,5,7))\n",
    "print(b.shape)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 242,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0\n",
      "\n",
      "[0. 1. 2. 3. 4. 5. 6.]\n",
      "\n",
      "[2. 4.]\n",
      "\n",
      "[0. 4. 8.]\n",
      "\n",
      "[[ 1.  1.  1.  1.  1.  1.  1.]\n",
      " [ 1.  2.  3.  4.  5.  6.  7.]\n",
      " [ 1.  3.  5.  7.  9. 11. 13.]\n",
      " [ 1.  4.  7. 10. 13. 16. 19.]\n",
      " [ 1.  5.  9. 13. 17. 21. 25.]]\n"
     ]
    }
   ],
   "source": [
    "print(b[0,1,2])\n",
    "print()\n",
    "print(b[0,1])\n",
    "print()\n",
    "print(b[0,1:3,2])\n",
    "print()\n",
    "print(b[0,0::2,2])\n",
    "print()\n",
    "print(b[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[ 0.,  0.,  0.,  0.,  0.,  0.,  0.],\n",
       "        [ 0.,  0.,  0.,  0.,  0.,  0.,  0.],\n",
       "        [ 0.,  2.,  4.,  6.,  8., 10., 12.],\n",
       "        [ 0.,  3.,  6.,  9., 12., 15., 18.],\n",
       "        [ 0.,  4.,  8., 12., 16., 20., 24.]],\n",
       "\n",
       "       [[ 1.,  1.,  1.,  1.,  1.,  1.,  1.],\n",
       "        [ 1.,  2.,  3.,  4.,  5.,  6.,  7.],\n",
       "        [ 1.,  3.,  5.,  7.,  9., 11., 13.],\n",
       "        [ 1.,  4.,  7., 10., 13., 16., 19.],\n",
       "        [ 1.,  5.,  9., 13., 17., 21., 25.]]])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0.  0.  0.]\n",
      " [ 1.  3.  5.]\n",
      " [ 2.  6. 10.]]\n",
      "\n",
      "[[ 0.  0.  0.]\n",
      " [ 1.  3.  5.]\n",
      " [ 2.  6. 10.]]\n",
      "\n",
      "[[ 0.  0.  0.]\n",
      " [ 5.  3.  1.]\n",
      " [10.  6.  2.]]\n",
      "\n",
      "[[[ 0.  0.  0.  0.  0.  0.  0.]\n",
      "  [ 1.  1.  1.  1.  1.  1.  1.]\n",
      "  [ 0.  2.  4.  6.  8. 10. 12.]\n",
      "  [ 0.  3.  6.  9. 12. 15. 18.]\n",
      "  [ 0.  4.  8. 12. 16. 20. 24.]]\n",
      "\n",
      " [[ 1.  1.  1.  1.  1.  1.  1.]\n",
      "  [ 1.  2.  3.  4.  5.  6.  7.]\n",
      "  [ 1.  3.  5.  7.  9. 11. 13.]\n",
      "  [ 1.  4.  7. 10. 13. 16. 19.]\n",
      "  [ 1.  5.  9. 13. 17. 21. 25.]]]\n",
      "\n",
      "[[[ 0.  0.  0.  0.  0.  0.  0.]\n",
      "  [ 0.  0.  0.  0.  0.  0.  0.]\n",
      "  [ 0.  2.  4.  6.  8. 10. 12.]\n",
      "  [ 0.  3.  6.  9. 12. 15. 18.]\n",
      "  [ 0.  4.  8. 12. 16. 20. 24.]]\n",
      "\n",
      " [[ 1.  1.  1.  1.  1.  1.  1.]\n",
      "  [ 1.  2.  3.  4.  5.  6.  7.]\n",
      "  [ 1.  3.  5.  7.  9. 11. 13.]\n",
      "  [ 1.  4.  7. 10. 13. 16. 19.]\n",
      "  [ 1.  5.  9. 13. 17. 21. 25.]]]\n"
     ]
    }
   ],
   "source": [
    "print(b[0,0:3,1:6:2])\n",
    "print()\n",
    "print(b[0,:3,1::2])\n",
    "print()\n",
    "print(b[0,:-2,5::-2])\n",
    "print()\n",
    "b[0,1] = np.ones(7)\n",
    "print(b)\n",
    "print()\n",
    "b[0,1] = 0\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Concept of view vs. copy\n",
    "\n",
    "In the following observe which variables are affected by ``y[0] = 1``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 245,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 1 2 3 4 5]\n"
     ]
    }
   ],
   "source": [
    "x = np.arange(6)\n",
    "print(x)\n",
    "\n",
    "y = x\n",
    "z = x.copy()\n",
    "w = x[::2]\n",
    "v = x.reshape((2,3))\n",
    "x[0] = 1\n",
    "z[0] = 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 244,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 1 2 3 4 5]\n",
      "\n",
      "[1 1 2 3 4 5]\n",
      "\n",
      "[2 1 2 3 4 5]\n",
      "\n",
      "[1 2 4]\n",
      "\n",
      "[[1 1 2]\n",
      " [3 4 5]]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(x, end=\"\\n\\n\")\n",
    "print(y, end=\"\\n\\n\")\n",
    "print(z, end=\"\\n\\n\")\n",
    "print(w, end=\"\\n\\n\")\n",
    "print(v, end=\"\\n\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 151,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 2]\n",
      " [3 4]]\n"
     ]
    }
   ],
   "source": [
    "x = np.array([1,2,3,4])\n",
    "w = x.reshape((2,2))\n",
    "x[0] = 0\n",
    "print(w)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 247,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]\n",
      " [12 13 14 15]]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 6,  7,  8,  9, 10, 11, 12, 13, 14, 15])"
      ]
     },
     "execution_count": 247,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = np.arange(16).reshape(4,4)\n",
    "print(x)\n",
    "x[x>5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Several ways how to assign a constant to a complicated array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 248,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "\n",
      "[[2. 2. 2.]\n",
      " [2. 2. 2.]]\n",
      "\n",
      "[[3. 2. 3.]\n",
      " [2. 2. 2.]]\n",
      "\n",
      "4\n"
     ]
    }
   ],
   "source": [
    "x = np.ones(6)\n",
    "v = x.reshape((2,3))\n",
    "print(v, end=\"\\n\\n\")\n",
    "v.fill(2)\n",
    "print(v, end=\"\\n\\n\")\n",
    "v[0,::2] = 3\n",
    "print(v, end=\"\\n\\n\")\n",
    "v = 4\n",
    "print(v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Difference numpy vs. python lists!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0, 2, 3, 4, 5], [0, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5])"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "L = [1,2,3,4,5]\n",
    "M = L\n",
    "N = L[:]\n",
    "O = L.copy()\n",
    "L[0] = 0\n",
    "L, M, N, O"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([13,  1,  2,  3,  4]),\n",
       " array([13,  1,  2,  3,  4]),\n",
       " array([13,  1,  2,  3,  4]),\n",
       " array([0, 1, 2, 3, 4]))"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = np.arange(5)\n",
    "x2 = x\n",
    "y = x[:]\n",
    "z = x.copy()\n",
    "x[0] = 13\n",
    "x, x2, y, z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Further functions to create useful arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.   0.25 0.5  0.75 1.  ]\n",
      "[0.   0.25 0.5  0.75 1.  ]\n",
      "[1.         1.18920712 1.41421356 1.68179283 2.        ]\n",
      "[1.         1.18920712 1.41421356 1.68179283 2.        ]\n"
     ]
    }
   ],
   "source": [
    "l = np.linspace(0,1,5)\n",
    "print(np.arange(5.)/4)\n",
    "print(l)\n",
    "print(np.logspace(0,1,5, base=2))\n",
    "print(np.exp2(l))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.         0.24740396 0.47942554 0.68163876 0.84147098]\n",
      "[1.         0.96891242 0.87758256 0.73168887 0.54030231]\n",
      "[1. 1. 1. 1. 1.]\n"
     ]
    }
   ],
   "source": [
    "print(np.sin(l))\n",
    "print(np.cos(l))\n",
    "print(np.sin(l)^2 + np.cos(l)^2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Multiple ways to estimate area of a circle.\n",
    "* First the Sage way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.145552\n",
      "CPU times: user 1.07 s, sys: 64 ms, total: 1.14 s\n",
      "Wall time: 1.14 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "N=10**3\n",
    "s = 0\n",
    "for x,y in mrange([N+1,N+1]):\n",
    "    if x**2 + y**2 <= N**2:\n",
    "        s += 1\n",
    "        \n",
    "print(float(s/N**2*4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Next python -- it does not have mrange, we must use two for cycles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.145552\n",
      "CPU times: user 781 ms, sys: 3.29 ms, total: 785 ms\n",
      "Wall time: 787 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "N=10**3\n",
    "s = 0\n",
    "for x in range(N+1):\n",
    "    for y in range(N+1):\n",
    "        if x**2 + y**2 <= N**2:    \n",
    "            s += 1\n",
    "        \n",
    "print(float(s/N**2*4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Next python -- or we can import ``itertools``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 165,
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import product"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.145552\n",
      "CPU times: user 845 ms, sys: 0 ns, total: 845 ms\n",
      "Wall time: 849 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "N=10**3\n",
    "s = 0\n",
    "for x,y in product(range(N+1), range(N+1)):\n",
    "    if x**2 + y**2 <= N**2:\n",
    "        s += 1\n",
    "        \n",
    "print(float(s/N**2*4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Finally, numpy. \n",
    "\n",
    "We learn new functions ``linspace`` (partition of an interval) and ``meshgrid`` that creates a tuple of two-dim variables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 249,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 7.47 ms, sys: 239 µs, total: 7.71 ms\n",
      "Wall time: 7.06 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3.145552"
      ]
     },
     "execution_count": 249,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time\n",
    "N = 10**3\n",
    "interval = np.linspace(0,1,N+1)\n",
    "X,Y = np.meshgrid(interval, interval)\n",
    "\n",
    "np.sum(X**2 + Y**2 <= 1)*4./N**2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 172,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5.58 ms ± 220 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%%timeit\n",
    "N = 10**3\n",
    "interval = np.linspace(0,1,N+1)\n",
    "X,Y = np.meshgrid(interval, interval)\n",
    "\n",
    "np.sum(X**2 + Y**2 <= 1)*4./N**2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6.01 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%%timeit\n",
    "N = 10**3\n",
    "interval = np.linspace(0,1,N+1)\n",
    "X,Y = np.meshgrid(interval, interval)\n",
    "\n",
    "np.sum(X*X + Y*Y <= 1)*4./N**2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Explanation of ``meshgrid``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 251,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.  0.5 1. ]\n",
      "\n",
      "x = \n",
      "[[0.  0.5 1. ]\n",
      " [0.  0.5 1. ]\n",
      " [0.  0.5 1. ]]\n",
      "\n",
      "y = \n",
      "[[0.  0.  0. ]\n",
      " [0.5 0.5 0.5]\n",
      " [1.  1.  1. ]]\n",
      "\n",
      "[[0.   0.25 1.  ]\n",
      " [0.25 0.5  1.25]\n",
      " [1.   1.25 2.  ]]\n",
      "\n",
      "[[ True  True  True]\n",
      " [ True  True False]\n",
      " [ True False False]]\n",
      "\n",
      "6\n"
     ]
    }
   ],
   "source": [
    "partition = np.linspace(0,1,3)\n",
    "x,y = np.meshgrid(partition, partition)\n",
    "\n",
    "print(partition)\n",
    "print()\n",
    "\n",
    "print(f\"x = \\n{x}\")\n",
    "print()\n",
    "\n",
    "print(f\"y = \\n{y}\")\n",
    "print()\n",
    "\n",
    "print(x**2+y**2)\n",
    "print()\n",
    "print(x**2+y**2 <= 1)\n",
    "print()\n",
    "print(np.sum(x**2+y**2<=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 252,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 252,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "True + True + True == 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercises\n",
    "\n",
    "Below are some fun exercises. \n",
    "For a list of thorough exercise see [100 numpy exercises](https://github.com/rougier/numpy-100). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://projecteuler.net/problem=1\n",
    "\n",
    "If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.\n",
    "\n",
    "Find the sum of all the multiples of 3 or 5 below 1000."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://projecteuler.net/problem=6\n",
    "\n",
    "The difference between the sum of the squares of the first ten natural numbers and the square of the sum is 2640. Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://projecteuler.net/problem=8\n",
    "\n",
    "The four adjacent digits in the 1000-digit number that have the greatest product are\n",
    "$9 \\times 9 \\times 8 \\times 9 = 5832$.\n",
    "\n",
    "73167176531330624919225119674426574742355349194934\n",
    "96983520312774506326239578318016984801869478851843\n",
    "85861560789112949495459501737958331952853208805511\n",
    "12540698747158523863050715693290963295227443043557\n",
    "66896648950445244523161731856403098711121722383113\n",
    "62229893423380308135336276614282806444486645238749\n",
    "30358907296290491560440772390713810515859307960866\n",
    "70172427121883998797908792274921901699720888093776\n",
    "65727333001053367881220235421809751254540594752243\n",
    "52584907711670556013604839586446706324415722155397\n",
    "53697817977846174064955149290862569321978468622482\n",
    "83972241375657056057490261407972968652414535100474\n",
    "82166370484403199890008895243450658541227588666881\n",
    "16427171479924442928230863465674813919123162824586\n",
    "17866458359124566529476545682848912883142607690042\n",
    "24219022671055626321111109370544217506941658960408\n",
    "07198403850962455444362981230987879927244284909188\n",
    "84580156166097919133875499200524063689912560717606\n",
    "05886116467109405077541002256983155200055935729725\n",
    "71636269561882670428252483600823257530420752963450\n",
    "\n",
    "Find the thirteen adjacent digits in the\n",
    "1000-digit number that have the greatest product. What is the value of this product?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "SageMath 10.2",
   "language": "sage",
   "name": "sagemath"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.7"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
