{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "56889fcd-f08a-4475-9928-af817748f5c8",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-5e2b58ff19f0997c",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Numerical Analysis - Fall semester 2025\n",
    "# Serie 09 - Splines, numerical differentiation and integration"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d0c899b-58da-49e8-ace0-828c8d5b60ca",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-e56f5ef08590e711",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "First, we will need to import some of the usual packages. You will have to run this cell every time you restart your notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d640d3e-d688-47ad-95b6-96eed7152684",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-6bbe893666203fcf",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import scipy as sp"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7199b23b-ac3a-4087-9c30-83d8249316d2",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3141cab342a74bd8",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<hr style=\"clear:both\">\n",
    "\n",
    "### Interpolation of the Runge function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "041c94fb-2730-4dfc-951c-7a333a16fcff",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-cf07ec01b52c57c1",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "As in the previous exercise sheet, we are given the data $(x_i,y_i)$, $i=0,1,\\ldots,n$, where the $x_i$ are $n+1$ nodes in the interval $[a,b]=[-5,5]$ and\n",
    "the values $y_i$ come from evaluation of the *Runge* function\n",
    "$$\n",
    "  g(x)= \\frac{1}{1+x^2}\n",
    "$$\n",
    "without any measurement error, namely $y_i = g(x_i)$, and want to analyze the accuracy of different polynomial interpolation strategies of $g$ for the data $(x_i,y_i)$. To measure the accuracy, we compute the maximum error the interpolant makes on a set of points `x_eval` specified by the user. The below two functions do this for the interpolants with uniform and Chebyshev nodes based on last week's implementations.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "be0932b8-4fa7-4edf-8ed7-12aee1ad56db",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-0ccc0945dcf855d4",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def uniform_interpolation_error(g, a, b, n, x_eval):\n",
    "    x_unif = a + np.arange(n + 1) / n * (b - a)\n",
    "    y_unif = g(x_unif)\n",
    "    a_unif = np.polyfit(x_unif, y_unif, n)\n",
    "    y_eval = np.polyval(a_unif, x_eval)\n",
    "    error = np.max(np.abs(y_eval - g(x_eval)))\n",
    "    return error\n",
    "\n",
    "def chebyshev_interpolation_error(g, a, b, n, x_eval):\n",
    "    x_cheb = (a + b) / 2 - (b - a) / 2 * np.cos(np.pi * (np.arange(n + 1) + 0.5) / (n + 1)) \n",
    "    y_cheb = g(x_cheb)\n",
    "    a_cheb = np.polyfit(x_cheb, y_cheb, n)\n",
    "    y_eval = np.polyval(a_cheb, x_eval)\n",
    "    error = np.max(np.abs(y_eval - g(x_eval)))\n",
    "    return error"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1807a8b7-bc33-4a8c-9ede-a33e99073484",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-5b489d6cc8e222e5",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "**Exercise 1:** Create an equivalent function for cubic spline interpolation.\n",
    "\n",
    "*Hint:* As last week, you can use the SciPy function `spline = sp.interpolate.CubicSpline(x, y)` to interpolate some data `x` and `y` with a cubic spline, and return a function `spline`, which can be called to evaluate the spline at some points. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "530ada3b-f8cb-472e-a212-72994545bc70",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-966694aa6c140d06",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def spline_interpolation_error(g, a, b, n, x_eval):\n",
    "    ### BEGIN SOLUTION\n",
    "    x_unif = a + np.arange(n + 1) / n * (b - a)\n",
    "    y_unif = g(x_unif)\n",
    "    spline = sp.interpolate.CubicSpline(x_unif, y_unif)\n",
    "    y_eval = spline(x_eval)\n",
    "    error = np.max(np.abs(y_eval - g(x_eval)))\n",
    "    ### END SOLUTION\n",
    "    return error"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ad574c7-4a26-4453-968b-94a2cb0a9817",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3713f2f8c72dc60b",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "**Exercise 2:** For $n=1, 3, 5, ..., 25$, compute the errors of the three methods (uniform, Chebyshev, spline) and visualize them in the same plot with a logarithmic $y$-axis. Choose `x_eval` as 1'000 uniformly spaced values in $[-5, 5]$. What do you observe?\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a4403392-5586-4756-ba1f-310ebd20a19d",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-7ef3e42056d8af80",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def g(x):\n",
    "    return 1 / (1 + x ** 2)\n",
    "\n",
    "a = -5\n",
    "b = 5\n",
    "x_eval = np.linspace(-5, 5, 1000)\n",
    "\n",
    "### BEGIN SOLUTION\n",
    "n_list = np.arange(1, 25, 2)\n",
    "uniform_interpolant_errors = []\n",
    "chebyshev_interpolant_errors = []\n",
    "spline_interpolant_errors = []\n",
    "\n",
    "for n in n_list:\n",
    "    uniform_interpolant_errors.append(uniform_interpolation_error(g, a, b, n, x_eval))\n",
    "    chebyshev_interpolant_errors.append(chebyshev_interpolation_error(g, a, b, n, x_eval))\n",
    "    spline_interpolant_errors.append(spline_interpolation_error(g, a, b, n, x_eval))\n",
    "\n",
    "plt.semilogy(n_list, uniform_interpolant_errors, label=r\"uniform interpolant\")\n",
    "plt.semilogy(n_list, chebyshev_interpolant_errors, label=r\"Chebyshev interpolant\")\n",
    "plt.semilogy(n_list, spline_interpolant_errors, label=r\"spline interpolant\")\n",
    "plt.grid()\n",
    "plt.ylabel(r\"error\")\n",
    "plt.xlabel(r\"interpolation nodes $n$\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# Spline interpolation has a smaller error than interpolation at Chebyshev nodes.\n",
    "\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6483f48d-323a-42cb-8ee9-83da5c2ef338",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-4ed297a599d137b1",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<hr style=\"clear:both\">\n",
    "\n",
    "### Forward and central finite differences"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86016eb9-2439-4dd4-bd48-5810584540b2",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-36f727029cd3481f",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "**Exercise 3:** Implement the functions `delta_h_backward` and `delta_h_central` which, given a function $f$, a point $\\overline{x}$, and a value $h$, compute the backward finite differences approximation \n",
    "\\begin{equation}\n",
    "\\delta_h^{-}f(\\overline{x}) = \\frac{f(\\overline{x}) - f(\\overline{x} - h)}{h}\n",
    "\\end{equation}\n",
    "and the central finite difference approximation \n",
    "\\begin{equation}\n",
    "\\delta_h^{c}f(\\overline{x}) = \\frac{f(\\overline{x} + h) - f(\\overline{x} - h)}{2h}\n",
    "\\end{equation}\n",
    "respectively.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36db09e5-d835-4c12-abd2-9c0da7cae88a",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3bae7b0b57da42c1",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def delta_h_backward(f, x_bar, h):\n",
    "    ### BEGIN SOLUTION\n",
    "    return (f(x_bar) - f(x_bar - h)) / h\n",
    "    ### END SOLUTION\n",
    "\n",
    "def delta_h_central(f, x_bar, h):\n",
    "    ### BEGIN SOLUTION\n",
    "    return (f(x_bar + h) - f(x_bar - h)) / (2 * h)\n",
    "    ### END SOLUTION"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5839fe26-5f85-4d0c-8af4-bdb796c7ad2f",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-9d11fe8c9f9f17c4",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "Let us run a few simple checks on your implementation, to see if there are no issues."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d394e023-0442-494a-970e-556f5195d1d3",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3f4849577575e280",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert np.isclose(D := delta_h_backward(lambda x: x, 0, 0.1), 1), f\"'delta_h_backward(f, 0, 0.1)' for 'f(x) = x' should return 'f'(0) ≈ 1', but got {D}\";assert np.isclose(D := delta_h_central(lambda x: x, 0, 0.1), 1), f\"'delta_h_central(f, 0, 0.1)' for 'f(x) = x' should return 'f'(0) ≈ 1', but got {D}\";assert np.isclose(D := delta_h_backward(lambda x: x**2, 0.5, 1e-10), 1), f\"'delta_h_backward(f, 0, 0.1)' for 'f(x) = x^2' should return 'f'(1/2) ≈ 1', but got {D}\";assert np.isclose(D := delta_h_central(lambda x: x**2, 0.5, 1e-10), 1), f\"'delta_h_central(f, 0, 0.1)' for 'f(x) = x^2' should return 'f'(1/2) ≈ 1', but got {D}\";print(\"Nice! Your function worked well on the simple examples.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0613dd97-3b02-4b78-9e67-4ad7e3fba93e",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-d3b2430fda49eada",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "We now consider the function\n",
    "\n",
    "\\begin{equation}\n",
    "f(x)=x\\log(x)-\\sin^2(x)\n",
    "\\end{equation}\n",
    "\n",
    "for $x>0$ for which we want to approximate the first derivative in\n",
    "$\\bar{x}=1.$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76b80441-c2fd-4aa6-b171-e644a68fba3d",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-2a8bcca3c6359c15",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "**Exercise 4:** Approximate the derivative of $f$ using the two methods you have implemented in the previous exercises for $h = 0.1 \\cdot (\\frac{1}{2})^{i}$ for $i=0,1,2,3,4,5$, and compute for each $h$ the error $\\varepsilon_h=\\vert f^{'}(\\bar{x})-\\delta_h  (\\bar{x})\\vert$, where $\\delta_h$ is either the forward finite differences or the central finite differences operator. Plot the errors $\\varepsilon_h$ in terms of $h$ on a graph in logarithmic scale. Comment on the obtained results.\n",
    "\n",
    "*Hint:* Use `plt.loglog` and compare the graphs of $\\varepsilon_h$ with the graphs defined by the curves $(h,h)$ and $(h,h^2)$.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6d9ae130-d571-42ed-a051-48fd44e41070",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-4025f88f67eb6a6a",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    ### BEGIN SOLUTION\n",
    "    return x * np.log(x) - np.sin(x) ** 2\n",
    "    ### END SOLUTION\n",
    "\n",
    "def df(x):\n",
    "    ### BEGIN SOLUTION\n",
    "    return 1 + np.log(x) - 2 * np.sin(x) * np.cos(x)\n",
    "    ### END SOLUTION\n",
    "\n",
    "### BEGIN SOLUTION\n",
    "x_bar = 1\n",
    "errors_backward = []\n",
    "errors_central = []\n",
    "\n",
    "h_list = 0.1 * 0.5 ** np.arange(6)\n",
    "for h in h_list:\n",
    "    errors_backward.append(abs(df(x_bar) - delta_h_backward(f, x_bar, h)))\n",
    "    errors_central.append(abs(df(x_bar) - delta_h_central(f, x_bar, h)))\n",
    "\n",
    "plt.loglog(h_list, errors_backward, label=r\"$\\varepsilon_h$ backward\")\n",
    "plt.loglog(h_list, errors_central, label=r\"$\\varepsilon_h$ central\")\n",
    "plt.loglog(h_list, h_list, linestyle=\"--\", c=\"tab:blue\", label=r\"$(h, h)$\")\n",
    "plt.loglog(h_list, h_list ** 2, linestyle=\"--\", c=\"tab:orange\", label=r\"$(h, h^2)$\")\n",
    "plt.xlabel(r\"$h$\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# We notice that for the backward finite differences method, the error decreases\n",
    "# with a slope (h, h) in logarithmic scale, showing that the method is of first\n",
    "# order. For the central finite differences method, the error decreases with\n",
    "# slope (h, h^2), which means it is of second order.\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c15b4d35-60b1-4245-bfad-184aec6ce9bc",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-73274d942c17aede",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "**Exercise 5:** Repeat the previous exercise for $i = 0, 1,\\ldots, 30$.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a974f3f0-0f76-4bf2-b43e-93392e7fbecf",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-149a97ac6376015a",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "### BEGIN SOLUTION\n",
    "x_bar = 1\n",
    "errors_forward = []\n",
    "errors_central = []\n",
    "\n",
    "h_list = 0.1 * 0.5 ** np.arange(31)\n",
    "for h in h_list:\n",
    "    errors_forward.append(abs(df(x_bar) - delta_h_backward(f, x_bar, h)))\n",
    "    errors_central.append(abs(df(x_bar) - delta_h_central(f, x_bar, h)))\n",
    "\n",
    "plt.loglog(h_list, errors_forward, label=r\"$\\varepsilon_h$ backward\")\n",
    "plt.loglog(h_list, errors_central, label=r\"$\\varepsilon_h$ central\")\n",
    "plt.loglog(h_list, h_list, linestyle=\"--\", c=\"tab:blue\", label=r\"$(h, h)$\")\n",
    "plt.loglog(h_list, h_list ** 2, linestyle=\"--\", c=\"tab:orange\", label=r\"$(h, h^2)$\")\n",
    "plt.xlabel(r\"$h$\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# We notice that for the backward finite differences method, the errors decreases\n",
    "# initially with slope (h, h) in logarithmic scale and then, for h ≈ 1e-8,\n",
    "# it increases with slope (h, 1/h). This is due to round-off errors. Similarly,\n",
    "# for the central finite differences method, the error decreases initially with\n",
    "# slope (h, h^2) and then, for h ≈ 1e-5, it increases with slope (h, 1/h)\n",
    "# because of the round-off errors.\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8aa303c9-d65e-49f9-a569-bf54a6883f7a",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-384ca392df10ebfa",
     "locked": true,
     "points": 0,
     "schema_version": 3,
     "solution": false,
     "task": true
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "**Exercise 6 (Theoretical):** Similarly like you have seen in the lecture, justify why in the previous exercise the error is minimized for a particular value of $h$. \n",
    "</div>\n",
    "\n",
    "=== BEGIN MARK SCHEME ===\n",
    "As in the lecture, we balance the approximation error with the rounding error.\n",
    "\n",
    "For the backward method, the approximation error is $C h$ for a constant $C$ and the rounding error is $\\varepsilon_{\\text{machine}} |f(\\bar{x})| / h$. \n",
    "Since $|f(\\bar{x})| = |f(1)| = \\sin(1)^2 \\approx 0.7$ and $C$ are constant, we will omit them. Setting the two errors equal allows us to deduce  \n",
    "$$\n",
    "  h \\approx \\varepsilon_{\\text{machine}} / h \\quad \\implies \\quad h \\approx \\sqrt{\\varepsilon_{\\text{machine}}} \\approx 10^{-8}\n",
    "$$\n",
    "\n",
    "For the central method, the approximation error is $C h^2$ for a constant $C$ and the rounding error is again $\\varepsilon_{\\text{machine}} |f(\\bar{x})| / h$. \n",
    "Hence, we similarly deduce  \n",
    "$$\n",
    "  h^2 \\approx \\varepsilon_{\\text{machine}} / h \\quad \\implies \\quad h \\approx \\sqrt[3]{\\varepsilon_{\\text{machine}}} \\approx 10^{-5}\n",
    "$$\n",
    "\n",
    "The two estimated optimal values for $h$ correspond to what we see in the plot from the previous exercise.\n",
    "\n",
    "=== END MARK SCHEME ==="
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e06c1508-431f-417a-a736-482d984265e2",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-9abeb0ed6a9a88e3",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<hr style=\"clear:both\">\n",
    "\n",
    "### Mysterious finite differences formula"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a443a391-6fbb-4869-972b-8c683202e88c",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-743b7a3e7bc0a719",
     "locked": true,
     "points": 0,
     "schema_version": 3,
     "solution": false,
     "task": true
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "**Exercise 7 (Theoretical):** Consider the general finite differences formula\n",
    "\n",
    "$$\n",
    "D_hf({x})=\\frac{\\frac{1}{3} f({x}+h)+\\beta\\, f({x})- (\\frac{1}{2}+\\beta)\\,f(x-h)+\n",
    "\\frac{1}{6}f(x-2h)}{h}\n",
    "$$\n",
    "\n",
    "to approximate $f'(x)$ with $\\beta\\, \\in \\mathbb{R}$ independent of $h$. If we suppose that the function $f$ is regular enough, which of the following statements is true? [Only one answer is possible]\n",
    "\n",
    "1. If $\\beta = \\frac{1}{2}$, the formula $D_h f$ is of second order but not of third order.\n",
    "\n",
    "2. If $\\beta \\neq \\frac{1}{2}$, the formula does not approximate $f'(x)$ when $h\\rightarrow 0$.\n",
    "\n",
    "3. If $\\beta = -\\frac{5}{6}$, the formula $D_h f$ is of first order.\n",
    "\n",
    "4. The formula $D_h f$ converges to $f'(x)$ for all $\\beta \\in \\mathbb{R}$ when $h\\rightarrow 0$.\n",
    "\n",
    "5. If $\\beta = -\\frac{1}{6}$, the formula $D_h f$ is of first order.\n",
    "\n",
    "*Hint:* Insert the Taylor series expansions of $f(x + h)$, $f(x - h)$, and $f(x - 2h)$ into the formula and group all terms of the same derivative order ($f(x),f'(x),f''(x), \\dots$) together to see how it behaves depending on $\\beta$ and $h$.\n",
    "</div>\n",
    "\n",
    "=== BEGIN MARK SCHEME ===\n",
    "\n",
    "The answer 2 is correct. Expanding $f(x + h)$, $f(x - h)$, and $f(x - 2h)$ in Taylor series of first order around $x$, we get\n",
    "\n",
    "\\begin{align*}\n",
    "D_h f(x) = \\frac{1}{h} \n",
    "\t\\Bigg[&\\frac{1}{3} \\bigg(f(x) + hf'(x) + r_2(x) \\bigg) \\\\\n",
    "           &+ \\beta f(x) \\\\\n",
    "           &-\\left( \\frac{1}{2} + \\beta \\right) \\bigg( f(x) - h f'(x) + r_2(x)  \\bigg) \\\\\n",
    "       &\\frac{1}{6} \\bigg( f(x) - 2 h f'(x) +  r_2(x)  \\bigg)\n",
    "\t\\Bigg]\n",
    "\\end{align*}\n",
    "where $r_2(x)$ is the remainder term which involves terms in $h^2, h^3, \\dots$.\n",
    "\n",
    "Regrouping the terms yields \n",
    "\n",
    "\\begin{align*}\n",
    "D_h f(x) &= f(x) \\frac{1}{h} \\underbrace{\\bigg( \\frac{1}{3} + \\beta - \\frac{1}{2} - \\beta + \\frac{1}{6} \\bigg)}_{=0} + f'(x) \\underbrace{\\bigg( \\frac{1}{3} + \\frac{1}{2} + \\beta - \\frac{1}{3} \\bigg)}_{= \\frac{1}{2} + \\beta} + r_1(x) \\\\\n",
    "&= f'(x)\\bigg(\\frac{1}{2} + \\beta\\bigg) + r_1(x)\n",
    "\\end{align*}\n",
    "where $r_1(x)$ is the remainder term which involves terms in $h, h^2, \\dots$.\n",
    "\n",
    "\n",
    "Therefore, unless $\\beta = \\frac{1}{2}$, $D_h f(x)$ will not approximate $f'(x)$ when $h \\to 0$.\n",
    "\n",
    "=== END MARK SCHEME ==="
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d80f1e35-4574-4fd1-a540-c41347806321",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-6d7f999d0bbb6be0",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<hr style=\"clear:both\">\n",
    "\n",
    "### Mysterious finite differences formula"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70c95a59-1acb-4249-b216-c837da0ab4a5",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-aa5d4fd1d4f799a0",
     "locked": true,
     "points": 0,
     "schema_version": 3,
     "solution": false,
     "task": true
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "**Exercise 8 (Theoretical):** Compute the approximation errors for the trapezoidal rule and Simpson rule applied to the integrals:\n",
    "$$\n",
    "    \\int_0^1 x^4\\,\\mathrm{d}x \\quad\\text{and}\\quad \\int_{0}^1 x^5\\,\\mathrm{d}x\n",
    "$$\n",
    "</div>\n",
    "\n",
    "=== BEGIN MARK SCHEME ===\n",
    "\n",
    "Let $Q_{[0,1]}^T[f]$ and $Q_{[0,1]}^S[f]$ be the approximate integral of any function $f$ defined on $[0,1]$ obtained, respectively, with the trapezoidal rule and Simpson rule. Then\n",
    "\t\\begin{align*}\n",
    "\t\\int_{0}^1 x^4 \\text{d}x - Q_{[0,1]}^T\\left[x^4\\right] &= \\frac{1}{5} - \\frac{1}{2} = - \\frac{3}{10}; \\\\\n",
    "\t\\int_{0}^1 x^5 \\text{d}x  - Q_{[0,1]}^T\\left[x^5\\right] &= \\frac{1}{6} - \\frac{1}{2} = -\\frac{1}{3}; \\\\\n",
    "\t \\int_{0}^1 x^4 \\text{d}x - Q_{[0,1]}^S\\left[x^4\\right] &= \\frac{1}{5} - \\frac{5}{24} = -\\frac{1}{120}; \\\\\n",
    "\t\\int_{0}^1 x^5 \\text{d}x  - Q_{[0,1]}^S\\left[x^5\\right] &= \\frac{1}{6} - \\frac{9}{48} = -\\frac{1}{48}.\n",
    "\t\\end{align*}\n",
    "    The errors are then the absolute values of each expression.\n",
    "\n",
    "=== END MARK SCHEME ==="
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9697b5ac-be57-41a6-b36e-08a66e112a68",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-169300f28af196fc",
     "locked": true,
     "points": 0,
     "schema_version": 3,
     "solution": false,
     "task": true
    }
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "**Exercise 9 (Theoretical):** Find $C \\in \\mathbb{R}$ such that the trapezoidal rule gives the exact result for the integral\n",
    "$$\n",
    "    \\int_0^1 x^5 - C x^4\\,\\mathrm{d}x .\n",
    "$$\n",
    "</div>\n",
    "\n",
    "=== BEGIN MARK SCHEME ===\n",
    "\n",
    "Using the results in the previous question and since both the integral and the quadrature rules are linear, one can verify that \n",
    "\t\\begin{align*}\n",
    "    \\int_0^1 \\left( x^5 - C x^4 \\right) \\text{d}x - Q_{[0,1]}^T\\left[x^5 -Cx^4\\right] = - \\frac{1}{3} + \\frac{3}{10}C. \n",
    "\t\\end{align*}\n",
    "\tTherefore, the trapezoidal rule is exact if $\\displaystyle\\frac{3}{10}C - \\frac{1}{3}=0$, that is, when $C = \\displaystyle\\frac{10}{9}$. \n",
    "    \n",
    "=== END MARK SCHEME ==="
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ba42156-1c02-4a00-b8b1-fcc789dd6435",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-5b39b5b957674ceb",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "<hr style=\"clear:both\">\n",
    "\n",
    "## The end\n",
    "\n",
    "Splendid! You have reached the end of the ninth exercise notebook."
   ]
  }
 ],
 "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.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
