{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercises Laboratorio di Calcolo: Practicing NumPy + Matplotlib\n",
    "\n",
    "By Hendrik Speleers, AY 2025-2026"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import all modules used in the exercises"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from matplotlib import pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 1\n",
    "\n",
    "Represent the following matrices in NumPy. Write for each of the matrices a Python function that generates a NumPy array in the right format. Avoid the use of explicit for-loops."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A matrix of size $(m, n)$ with checkerboard pattern"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def matrix_checkerboard(m, n):\n",
    "    matrix = np.zeros((m, n), dtype=int)\n",
    "    matrix[::2, ::2] = 1\n",
    "    matrix[1::2, 1::2] = 1\n",
    "    return matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 0, 1, 0, 1, 0],\n",
       "       [0, 1, 0, 1, 0, 1],\n",
       "       [1, 0, 1, 0, 1, 0],\n",
       "       [0, 1, 0, 1, 0, 1],\n",
       "       [1, 0, 1, 0, 1, 0]])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "matrix_checkerboard(5, 6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A matrix of size $(m, n)$ containing the integer numbers $1, 2, ..., m·n$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def matrix_enumeration(m, n):\n",
    "    matrix = np.arange(1, m*n+1, dtype=int).reshape((m, n))\n",
    "    return matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1,  2,  3,  4,  5],\n",
       "       [ 6,  7,  8,  9, 10],\n",
       "       [11, 12, 13, 14, 15],\n",
       "       [16, 17, 18, 19, 20],\n",
       "       [21, 22, 23, 24, 25]])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "matrix_enumeration(5, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A square matrix of size $(n, n)$ with the numbers $1, 2, ..., n$ on both the diagonal and the anti-diagonal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def matrix_cross(n):\n",
    "    vals = np.arange(1, n+1, dtype=int)\n",
    "    matrix = np.diag(vals)\n",
    "    matrix[vals-1, n-vals] = vals\n",
    "    return matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 0, 0, 0, 1],\n",
       "       [0, 2, 0, 2, 0],\n",
       "       [0, 0, 3, 0, 0],\n",
       "       [0, 4, 0, 4, 0],\n",
       "       [5, 0, 0, 0, 5]])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "matrix_cross(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2\n",
    "\n",
    "Write Python functions with the described utilities. Avoid the use of explicit for-loops."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A function that returns the closest value (to a given scalar) in a 1D array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_closest(vals, val):\n",
    "    index = np.abs(vals - val).argmin()\n",
    "    return vals[index]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vals = np.array([1, 2, 5, 9, 7])\n",
    "val = 4.7\n",
    "find_closest(vals, val)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A function that inserts 3 consecutive zeros between each element in a 1D array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def insert_zeros(vals, nz=3):\n",
    "    len_zeros = (len(vals)-1) * (nz+1) + 1\n",
    "    vals_zeros = np.zeros(len_zeros, dtype=vals.dtype)\n",
    "    vals_zeros[::nz+1] = vals\n",
    "    return vals_zeros"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 9, 0, 0, 0, 7])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vals = np.array([1, 2, 5, 9, 7])\n",
    "insert_zeros(vals, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A function that swaps two rows of a 2D array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def swap_rows(matrix, row1, row2):\n",
    "    matrix[[row1, row2]] = matrix[[row2, row1]]\n",
    "    return matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[6, 3, 8],\n",
       "       [4, 9, 7],\n",
       "       [1, 2, 5]])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "matrix = np.array([[1, 2, 5], [4, 9, 7], [6, 3, 8]])\n",
    "swap_rows(matrix, 0, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 3\n",
    "\n",
    "Write a Python program that visualizes a spiral (in red color) with bullets (in blue color) on top of it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_spiral(theta):\n",
    "    t_line = np.linspace(0, theta, int(10*theta))\n",
    "    x_line = t_line * np.cos(t_line)\n",
    "    y_line = t_line * np.sin(t_line)\n",
    "    plt.plot(x_line, y_line, 'r')\n",
    "    t_mark = np.linspace(0, theta, int(theta))\n",
    "    x_mark = t_mark * np.cos(t_mark)\n",
    "    y_mark = t_mark * np.sin(t_mark)\n",
    "    plt.plot(x_mark, y_mark, 'bo')\n",
    "    plt.axis('equal')\n",
    "    plt.axis('off')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 29688 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "theta = 20*np.pi\n",
    "plot_spiral(theta)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 4\n",
    "\n",
    "Write a Python program to visualize the first $n$ Legendre polynomials in the interval $[-1,1]$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_legendre(n):\n",
    "    x = np.linspace(-1, 1, 100)\n",
    "    for i in range(n):\n",
    "        Li = np.polynomial.Legendre.basis(i)\n",
    "        plt.plot(x, Li(x))\n",
    "    plt.legend(['$L_{%d}$' % i for i in range(n)])\n",
    "    plt.grid()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 48024 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = 6\n",
    "plot_legendre(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_legendre_alternative(n):\n",
    "    x = np.linspace(-1, 1, 100)\n",
    "    for i in range(n):\n",
    "        Li = np.polynomial.Legendre.basis(i)\n",
    "        plt.plot(x, Li(x), label='$L_{%d}$' % i)\n",
    "    plt.legend()\n",
    "    plt.grid()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 48024 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = 6\n",
    "plot_legendre_alternative(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_legendre_grid(n):\n",
    "    m1 = int(np.sqrt(n))\n",
    "    m2 = (n-1) // m1 + 1\n",
    "    x = np.linspace(-1, 1, 100)\n",
    "    for i in range(n):\n",
    "        Li = np.polynomial.Legendre.basis(i)\n",
    "        plt.subplot(m1, m2, i+1)\n",
    "        plt.plot(x, Li(x))\n",
    "        plt.axis([-1.2, 1.2, -1.2, 1.2])\n",
    "        plt.text(0, 0.6, '$L_{%d}$' % i)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 34748 characters]\n",
      "text/plain": [
       "<Figure size 720x432 with 6 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = 6\n",
    "plt.figure(figsize=(10, 6))\n",
    "plot_legendre_grid(n)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 5: the Mandelbrot set\n",
    "\n",
    "Write a Python function that returns a matrix containing for each complex point the minimal iteration step $i$ where the corresponding value $|z_{i+1}| > 2$ in the Mandelbrot recurrence relation.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mandelbrot_iter(nx, ny, max_it=20, x1=-2.0, x2=1.0, y1=-1.5, y2=1.5):\n",
    "    x = np.linspace(x1, x2, nx)\n",
    "    y = np.linspace(y1, y2, ny)\n",
    "    C = x[:,np.newaxis] + 1.0j*y[np.newaxis,:]\n",
    "    Z = np.zeros((nx, ny), dtype=complex)\n",
    "    M = np.ones((nx, ny), dtype=bool)\n",
    "    M_it = np.full((nx, ny), max_it, dtype=int)\n",
    "    for i in range(max_it):\n",
    "        Z[M] = Z[M]**2 + C[M]\n",
    "        Z_div = np.abs(Z) > 2\n",
    "        M_it[M & Z_div] = i\n",
    "        M[Z_div] = False\n",
    "    return M_it"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, visualize this matrix using plt.imshow and choose a high resolution (dpi) in order to fully appreciate the fractal structure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(-0.5, 2500.5, 2500.5, -0.5)"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 1983612 characters]\n",
      "text/plain": [
       "<Figure size 7200x4800 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "M_it = mandelbrot_iter(2501, 2501, 200)\n",
    "plt.figure(dpi=1200)\n",
    "plt.imshow(M_it.T, cmap='flag')\n",
    "plt.axis('off')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Enhance your function so that you can also specify the considered rectangle in the complex plane. This will allow for making nice zooms of the mandelbrot fractal.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(-0.5, 2500.5, 2500.5, -0.5)"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 8566396 characters]\n",
      "text/plain": [
       "<Figure size 7200x4800 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "M_it = mandelbrot_iter(2501, 2501, 1200, -0.7450, -0.7425, 0.1300, 0.1325)\n",
    "plt.figure(dpi=1200)\n",
    "plt.imshow(M_it.T, cmap='flag')\n",
    "plt.axis('off')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
