1
Fork 0
solar-conflux/python/denoising/Main.ipynb

430 lines
753 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "9785abf6",
"metadata": {},
"source": [
"We have:\n",
"$$\n",
"\\nabla f = (I + \\beta A) \\vec s - \\vec y\n",
" = \\vec s - \\vec y + \\beta A \\vec s\n",
"$$ \n",
"We can substitute in the partial derivatives:\n",
"$$ \n",
"\\left(\\begin{matrix}\n",
"s_0 - y_0 + \\beta (s_0 - s_1) \\\\\n",
"s_1 - y_1 + \\beta (2s_1 - s_0 - s_2) \\\\\n",
"\\vdots \\\\\n",
"s_N - y_N + \\beta (s_N - s_{N - 1})\n",
"\\end{matrix}\\right) \n",
"= \\vec s - \\vec y + \\beta A \\vec s\n",
"$$\n",
"\n",
"Adding $\\vec y - \\vec s$ to both sides and then dividing by $\\beta$ yields:\n",
"$$ \n",
"\\left(\\begin{matrix}\n",
"s_0 - s_1 \\\\\n",
"2s_1 - s_0 - s_2 \\\\\n",
"\\vdots \\\\\n",
"s_N - s_{N - 1}\n",
"\\end{matrix}\\right) \n",
"= A \\vec s\n",
"$$\n",
"\n",
"Solving for $A$ is trivial and yields:\n",
"$$ \n",
"A = \\left(\\begin{matrix}\n",
"1 & -1 & & & & & \\\\\n",
"-1 & 2 & -1 & & & & \\\\\n",
" & -1 & 2 & -1 & & & \\\\\n",
" & & \\ddots & \\ddots & \\ddots & & \\\\\n",
" & & & -1 & 2 & -1 & \\\\\n",
" & & & & -1 & 2 & -1\\\\\n",
" & & & & & -1 & 1\n",
"\\end{matrix}\\right) \n",
"$$"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e7d69a97",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import modules.tridiagonal as td\n",
"\n",
"def sample_with_noise(points, vectorized_g):\n",
" \"\"\"\n",
" Sample a function at a given set of points,\n",
" introducing noise in the process.\n",
"\n",
" Returns a tuple of the real values of the function\n",
" and the values with introduced noise.\n",
"\n",
" For performance reasons, this function takes in\n",
" a vectorized version of `g`. If you want to pass\n",
" a non vectorized function use `np.vectorize`. Eg:\n",
" ```py\n",
" sample_with_noise(points, np.vectorize(g))\n",
" ```\n",
" \"\"\"\n",
" gs = vectorized_g(points)\n",
" ys = gs + 0.05 * np.random.randn(len(gs))\n",
"\n",
" return (gs, ys)\n",
"\n",
"def p5(x):\n",
" \"\"\"\n",
" Vectorized version of the function\n",
" described in the pdf.\n",
" \"\"\"\n",
" n = 5\n",
" result = 0\n",
"\n",
" for k in range(n + 1):\n",
" result += x**k\n",
"\n",
" return result/(n + 1)\n",
"\n",
"def create_A(n):\n",
" \"\"\"\n",
" Creates the (n+1) x (n + 1) A matrix.\n",
" (see the latex above for it's definition)\n",
" \"\"\"\n",
" a = 2*np.ones(n + 1)\n",
" a[0] = 1\n",
" a[-1] = 1\n",
"\n",
" c = -np.ones(n)\n",
" e = -np.ones(n)\n",
"\n",
" return td.create(a, c, e)\n",
"\n",
"def smooth(data, beta):\n",
" \"\"\"\n",
" Smooth noisy data (y) by means of solving \n",
" a minimization problem.\n",
"\n",
" Args:\n",
" data (numpy.array): noisy data to be smoothed (y)\n",
" beta (float): parameter >= 0 that balances fit and smoothing\n",
"\n",
" Returns:\n",
" numpy array of smoothed data (s)\n",
" \"\"\"\n",
" # We need to solve for s in:\n",
" # (I + βA)s - y = 0\n",
" # <=> (I + βA)s = y\n",
" #\n",
" # Because A and I are tridiagonal, I + βA\n",
" # is tridiagonal as well (linear combinations\n",
" # of tridiagonal matrices are themselves\n",
" # tridiagonal)\n",
" #\n",
" # This means we can use our existing implementation\n",
" # of tridiagonal linear equation system solving.\n",
" # \n",
" # We start by constructing \n",
" # iba := I + βA\n",
" n = len(data) - 1\n",
"\n",
" # I + βA\n",
" iba = td.add(\n",
" td.identity(n + 1),\n",
" td.scale(beta, create_A(n))\n",
" )\n",
"\n",
" data_smoothed = td.solve(*iba, data)\n",
"\n",
" return data_smoothed"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2b9f2b79",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHgCAYAAABZ+0ykAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAACOi0lEQVR4nOzdd3hTZfvA8W+S7pXuBR3sVXYZZRVEtshSQHwZiv5ERJbKEBTwVYYTUYYLcCCgsnyVvbfsWTaFMgqlBbpncn5/lEZCd2mbNtyf68pVcvKcc+5zEpq7z1QpiqIghBBCCGEm1KYOQAghhBCiOElyI4QQQgizIsmNEEIIIcyKJDdCCCGEMCuS3AghhBDCrEhyI4QQQgizIsmNEEIIIcyKJDdCCCGEMCuS3AghhBDCrEhyI8RjWrx4MSqVyvCwsbHB29ubdu3aMWPGDKKioop87LCwMKZOncqVK1eKL+A8bN++HZVKxfbt20vlfPlp27Ytbdu2LdQ+Ze0aTCmne7F27VqmTp1qspiEKA2S3AhRTBYtWsS+ffvYtGkTc+fOpUGDBsyaNYtatWqxefPmIh0zLCyMadOmlVpyU9bMmzePefPmFWqfRo0asW/fPho1alRCUZVva9euZdq0aaYOQ4gSZWHqAIQwF0FBQQQHBxue9+nThzFjxtCqVSt69+7NhQsX8PLyMmGE5U/t2rULvY+TkxPNmzcvgWiKJjk5GRsbG1QqlalDEeKJITU3QpQgf39/PvvsM+Lj4/nmm28M2w8dOkT//v0JDAzE1taWwMBAXnjhBa5evWoos3jxYp5//nkA2rVrZ2j2Wrx4MQCbNm2iR48eVKxYERsbG6pWrcprr71GdHR0gWI7e/YsnTt3xs7ODnd3d4YNG0Z8fHyOZTdv3kz79u1xcnLCzs6Oli1bsmXLFqMyU6dORaVScfr0aV544QW0Wi1eXl68/PLLxMbGGpVNSUlh4sSJVKpUCSsrKypUqMAbb7zB/fv3jcrl1Cw1f/586tevj4ODA46OjtSsWZN3333X8HpOTTFDhgzBwcGBixcv0rVrVxwcHPDz8+Ott94iNTXV6PjXr1/nueeew9HREWdnZ1588UUOHjxodO9zk9VEuXHjRl5++WU8PDyws7MznGP58uWEhIRgb2+Pg4MDnTp14ujRo0bHuHz5Mv3798fX1xdra2u8vLxo3749x44dM5RRqVQ5Ni0FBgYyZMiQXOMbMmQIc+fONRwj65FVM/j777/TrFkztFotdnZ2VK5cmZdffjnPaxaiLJKaGyFKWNeuXdFoNOzcudOw7cqVK9SoUYP+/fvj6upKZGQk8+fPp0mTJoSFheHu7k63bt2YPn067777LnPnzjU0s1SpUgWAS5cuERISwiuvvIJWq+XKlSt8/vnntGrVipMnT2JpaZlrTLdv3yY0NBRLS0vmzZuHl5cXS5YsYcSIEdnK/vLLLwwaNIgePXrw448/YmlpyTfffEOnTp3YsGED7du3Nyrfp08f+vXrx9ChQzl58iQTJ04EYOHChQAoikLPnj3ZsmULEydOpHXr1pw4cYIpU6awb98+9u3bh7W1dY5xL1u2jOHDh/Pmm2/y6aefolaruXjxImFhYfm+D+np6Tz77LMMHTqUt956i507d/Lf//4XrVbL+++/D0BiYiLt2rXj7t27zJo1i6pVq7J+/Xr69euX7/Ef9vLLL9OtWzd+/vlnEhMTsbS0ZPr06UyePJmXXnqJyZMnk5aWxieffELr1q05cOCAoZaqa9eu6HQ6Pv74Y/z9/YmOjmbv3r3ZEr+ieO+990hMTOSPP/5g3759hu0+Pj7s27ePfv360a9fP6ZOnYqNjQ1Xr15l69atj31eIUqdIoR4LIsWLVIA5eDBg7mW8fLyUmrVqpXr6xkZGUpCQoJib2+vfPnll4btv//+uwIo27ZtyzMGvV6vpKenK1evXlUAZc2aNXmWHz9+vKJSqZRjx44Zbe/QoYPR+RITExVXV1ele/fuRuV0Op1Sv359pWnTpoZtU6ZMUQDl448/Nio7fPhwxcbGRtHr9YqiKMr69etzLLd8+XIFUL799lvDttDQUCU0NNTwfMSIEYqzs3Oe17Zt27Zs92zw4MEKoPz2229GZbt27arUqFHD8Hzu3LkKoKxbt86o3GuvvaYAyqJFi/I8d9ZnYdCgQUbbIyIiFAsLC+XNN9802h4fH694e3srffv2VRRFUaKjoxVAmT17dp7nAZQpU6Zk2x4QEKAMHjzY8Dyne/HGG28oOf3q//TTTxVAuX//fp7nFqI8kGYpIUqBoihGzxMSEhg/fjxVq1bFwsICCwsLHBwcSExM5MyZMwU6ZlRUFMOGDcPPzw8LCwssLS0JCAgAyPcY27Zto06dOtSvX99o+4ABA4ye7927l7t37zJ48GAyMjIMD71eT+fOnTl48CCJiYlG+zz77LNGz+vVq0dKSoph1FhWTcCjzSfPP/889vb22Zq7Hta0aVPu37/PCy+8wJo1awrcBAeZzTDdu3fPFtvDTYE7duzA0dGRzp07G5V74YUXCnweyKy9etiGDRvIyMhg0KBBRvfRxsaG0NBQQxOaq6srVapU4ZNPPuHzzz/n6NGj6PX6Qp27qJo0aQJA3759+e2337hx40apnFeIkiDJjRAlLDExkZiYGHx9fQ3bBgwYwNdff80rr7zChg0bOHDgAAcPHsTDw4Pk5OR8j6nX6+nYsSMrV65k3LhxbNmyhQMHDrB//36AfI8RExODt7d3tu2Pbrt9+zYAzz33HJaWlkaPWbNmoSgKd+/eNdrHzc3N6HlWE1NWTDExMVhYWODh4WFUTqVS4e3tTUxMTK5xDxw4kIULF3L16lX69OmDp6cnzZo1Y9OmTXleL4CdnR02NjbZYktJSTE8j4mJybHTd2E7gvv4+Bg9z7qPTZo0yXYfly9fbkjSVCoVW7ZsoVOnTnz88cc0atQIDw8PRo4cmWt/qOLSpk0bVq9ebUjCKlasSFBQEEuXLi3R8wpREqTPjRAl7O+//0an0xk6xsbGxvLXX38xZcoUJkyYYCiXmpqaLVHIzalTpzh+/DiLFy9m8ODBhu0XL14s0P5ubm7cunUr2/ZHt7m7uwPw1Vdf5ToCqbBf/G5ubmRkZHDnzh2jBEdRFG7dumWoQcjNSy+9xEsvvURiYiI7d+5kypQpPPPMM5w/f95Qc1VUbm5uHDhwINv2nO5VXh4dGZV1H//44498YwwICOCHH34A4Pz58/z2229MnTqVtLQ0FixYAGQmZY92hAbyTAwLokePHvTo0YPU1FT279/PjBkzGDBgAIGBgYSEhDzWsYUoTVJzI0QJioiI4O2330ar1fLaa68BmV98iqJk6zT7/fffo9PpjLY9WuuRJevL89FjPDwiKy/t2rXj9OnTHD9+3Gj7r7/+avS8ZcuWODs7ExYWRnBwcI4PKyurAp0zS1YH5F9++cVo+4oVK0hMTMzWQTk39vb2dOnShUmTJpGWlsbp06cLFUdOQkNDiY+PZ926dUbbly1b9ljH7dSpExYWFly6dCnX+5iT6tWrM3nyZOrWrcuRI0cM2wMDAzlx4oRR2a1bt5KQkJBvLLl9ph4tExoayqxZswCyjegSoqyTmhshismpU6cMfSmioqLYtWsXixYtQqPRsGrVKkMthZOTE23atOGTTz7B3d2dwMBAduzYwQ8//ICzs7PRMYOCggD49ttvcXR0xMbGhkqVKlGzZk2qVKnChAkTUBQFV1dX/ve//xWoeQZg9OjRLFy4kG7duvHhhx8aRkudPXvWqJyDgwNfffUVgwcP5u7duzz33HN4enpy584djh8/zp07d5g/f36h7lOHDh3o1KkT48ePJy4ujpYtWxpGSzVs2JCBAwfmuu+rr76Kra0tLVu2xMfHh1u3bjFjxgy0Wm2+NT4FMXjwYL744gv+85//8OGHH1K1alXWrVvHhg0bAFCri/b3YGBgIB988AGTJk3i8uXLdO7cGRcXF27fvs2BAwewt7dn2rRpnDhxghEjRvD8889TrVo1rKys2Lp1KydOnDCq5Rs4cCDvvfce77//PqGhoYS
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plot\n",
"\n",
"# Reusable function to plot the data\n",
"def plot_denoise_results(beta, N, ax):\n",
" # Create the data\n",
" x = np.linspace(-1, 1, num=N+1)\n",
" g, y = sample_with_noise(x, p5)\n",
" s = smooth(y, beta)\n",
"\n",
" ax.set_title(f\"β={beta} and {N=}\")\n",
" ax.set_xlabel(\"x\")\n",
" ax.set_ylabel(\"y\")\n",
" ax.plot(x, y, \".\", label=\"raw data points\")\n",
" ax.plot(x, g, \"-\", label=\"original function\")\n",
" ax.plot(x, s, \"-\", label=\"denoised values\")\n",
" ax.legend()\n",
"\n",
"\n",
"N = 100\n",
"beta = 10\n",
"\n",
"fig, ax = plot.subplots()\n",
"fig.suptitle(f\"Data denoising results\")\n",
"\n",
"# Plot the data\n",
"plot_denoise_results(beta, N, ax)"
]
},
{
"cell_type": "markdown",
"id": "f2e5c860",
"metadata": {},
"source": [
"We notice that finding a point where $\\nabla f(s) = 0$ is the same as finding a point where $\\frac 1 \\beta \\nabla f(s) = \\nabla \\frac 1 \\beta f(s) = 0$. We can use this to our advantage, by defining $h(s) = \\frac 1 \\beta f(s)$ and finding the points where $\\nabla h(s) = 0$ instead. When $\\beta \\to \\infty$, we have:\n",
"$$\n",
"\\lim_{\\beta \\to \\inf} h _\\beta (s) =\n",
"\\lim_{\\beta \\to \\inf} \n",
" \\frac 1 {2\\beta} \\sum_{k=0}^n (y_k - s_k)^2\n",
" +\\frac 1 2 \\sum_{k=1}^n (s_k - s_{k - 1})^2\n",
" = \\frac 1 2 \\sum_{k=1}^n (s_k - s_{k - 1})^2\n",
"$$\n",
"\n",
"It now becomes trivial to verify that $\\nabla f(s) = As$. Looking at the value of $A$ we notice that any vector with all values equal to some constant are solutions for this equation. This means the result will be a constant function (which might differ based on the linear equation solver we are using)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d7f7dbdf",
"metadata": {
"scrolled": false
},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1280x960 with 9 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axs = plot.subplots(\n",
" 3,\n",
" 3,\n",
" layout=\"constrained\",\n",
" figsize=(12.8, 9.6)\n",
")\n",
"\n",
"fig.suptitle(r\"Data denoising results as $\\beta \\to \\infty$\")\n",
"\n",
"N = 100\n",
"\n",
"for i in range(len(axs.flat)):\n",
" ax = axs.flat[i]\n",
" plot_denoise_results(4**i, N, ax)"
]
},
{
"cell_type": "markdown",
"id": "3c9f50cc",
"metadata": {},
"source": [
"When $\\beta \\to 0$ we have:\n",
"$$\n",
"\\lim_{\\beta \\to 0} \\nabla f(s) =\n",
"\\lim_{\\beta \\to 0} (I + \\betaA)s - y = s - y\n",
"$$\n",
"\n",
"This means when solving for $\\nabla f(s) = 0$, we are solving the equation $s - y = 0$, which has the solution $s = y$. The resulting function will then be one equal to the data containing the noise."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "dfc485d5",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABQsAAAPLCAYAAAD4+99NAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3gU5drH8e9m0ztJIAVIoaOAdKkCIiBiAxXUc0QUz6uiIiCg2NsRUFTsehQEFQEL2EAQkSK9K80CJIQSCKGk1915/1iyEtIpmZTf57pySWafmblnWPdm7n2KxTAMAxEREREREREREanxXMwOQERERERERERERCoHFQtFREREREREREQEULFQRERERERERERETlOxUERERERERERERAAVC0VEREREREREROQ0FQtFREREREREREQEULFQRERERERERERETlOxUERERERERERERAAVC0VEREREREREROQ0FQtFREREREREREQEULFQRERERERERERETlOxUERERIo0Y8YMLBaL88fT05OwsDB69erFxIkTSUxMPOdjr1mzhmeffZZTp05duIBL8Oyzz2KxWCrkXKXJv69xcXEXdZ/qrKj7UdHvqQslIyOD5557jmbNmuHp6UlwcDC33XYbJ0+evCDHT0tLY9SoUURERODp6Unr1q2ZM2fOBTm2iIiIVE8qFoqIiEiJPv74Y9auXcuSJUt45513aN26NZMnT6Z58+b8/PPP53TMNWvW8Nxzz1W5ws6FMGDAANauXUt4ePhF3aemqYrvKcMwuO2225g6dSr33nsvP/74I08//TRz587l1VdfvSDnGDRoEDNnzuSZZ57hxx9/pEOHDtx22218/vnnF+T4IiIiUv24mh2AiIiIVG4tWrSgffv2zt9vuukmRo8eTbdu3Rg0aBB///03oaGhJkZYtdSuXZvatWtf9H0utoyMDLy9vc0Oo0pbsWIF3333HV9++SU333wzgLPnbnp6+nkff+HChSxZsoTPP/+c2267zXn8/fv3M27cOIYMGYLVaj3v84iIiEj1op6FIiIiUm6RkZG8+uqrpKam8sEHHwCwZ88e7rrrLho3boy3tzd169bluuuuY/v27QX2ffbZZxk3bhwAMTExzmHOy5cvL/MxSrJgwQJat26Nh4cHMTExTJkypch2f//9N7fffjt16tTBw8OD5s2b88477xSK1WKxsHPnTm677TYCAgIIDQ3l7rvvJjk5udAxV61aRe/evfHz88Pb25suXbqwYMGCAm2KGkJ77Ngx/u///o/69evj4eFB7dq16dq1q7PnZlH7lDe2b7/9llatWuHh4UGDBg144403yjw8O7/dli1buPnmm6lVqxYNGzYs830syzUOGzaM6OjoYs9dWnzFvadKO29xyvpePNfjA3z55ZfUqlWLgQMHOretXLmSo0ePcuWVV5a6f77XX3+db775ptD2+fPn4+vryy233FJg+1133cXhw4dZv359mc8hIiIiNYd6FoqIiMg5ueaaa7BaraxcuRKAw4cPExwczKRJk6hduzYnTpxg5syZXH755WzdupWmTZsCcM8993DixAneeust5s2b5xxae8kll7Bt27YyHaM4S5cu5YYbbqBz587MmTMHm83Gyy+/zNGjRwu027VrF126dHEWPcPCwli8eDEjR44kKSmJZ555pkD7m266iSFDhjB8+HC2b9/OhAkTAJg+fbqzzYoVK+jTpw+tWrVi2rRpeHh48O6773Ldddcxe/ZshgwZUmzcd9xxB1u2bOG///0vTZo04dSpU2zZsoXjx4+X+vdQltgWLVrEoEGDuOKKK5g7dy55eXlMmTKl0H0pzaBBg7j11lu57777SE9PL9d9PJ9rLE1J76nBgwef03nL+n4+n+tas2YNl19+ufN8ixcvZvz48fTu3Ztrr722zNe/adMmHn30Ub744gtuvPFG5/YdO3bQvHlzXF0L/pO/VatWzte7dOlS5vOIiIhIDWGIiIiIFOHjjz82AGPjxo3FtgkNDTWaN29e5Gt5eXlGTk6O0bhxY2P06NEFXnvllVcMwIiNjS0xhpKOUZTLL7/ciIiIMDIzM53bUlJSjKCgIOPMf/b069fPqFevnpGcnFxg/wcffNDw9PQ0Tpw4YRiGYTzzzDMGYLz88ssF2o0YMcLw9PQ07Ha7c1unTp2MOnXqGKmpqQXib9GihVGvXj1n2/z7eua1+/r6GqNGjSr2uorapzyxdejQwahfv76RnZ3t3JaammoEBwcbZfnnYP65nn766QLby3ofy3KNd955pxEVFVXsuc9U1P0o7j1V2nnLqrj34rkePzMz03B1dTWeeeYZ4/nnnzcAAzAiIyONAwcOlDu222+/3XBzczPmz5/v3N64cWOjX79+hdofPnzYAIyXXnqp3HGLiIhI9adhyCIiInLODMNw/jkvL4+XXnqJSy65BHd3d1xdXXF3d+fvv/9m9+7dZTre+RwjPT2djRs3MmjQIDw9PZ3b/fz8uO6665y/Z2VlsXTpUgYOHIi3tzd5eXnOn2uuuYasrCzWrVtX4NjXX399gd9btWpFVlaWc0Xo9PR01q9fz80334yvr6+zndVq5Y477uDgwYP8+eefxcbesWNHZsyYwYsvvsi6devIzc0t/WaVI7ZNmzZx44034u7u7mzn6+tb4L6UxU033eT8c3nv4/lc4/k41/OW9b14rsffsmULeXl5dOzYkX/9618sXryY5557jtTUVK644grS0tIASEpKKrAqeVE/rq6ufP755+Tm5jJ48OACPUZLGsJdWVYIFxERkcpFw5BFRETknKSnp3P8+HFatmwJwJgxY3jnnXd49NFH6dGjB7Vq1cLFxYV77rmHzMzMMh3zfI5x8uRJ7HY7YWFhhV47c9vx48fJy8vjrbfe4q233iryWElJSQV+Dw4OLvC7h4cHgDOmkydPYhhGkasVR0REOM9bnLlz5/Liiy/y0Ucf8dRTT+Hr68vAgQN5+eWXi7yec4mtqEVoyrswzZnXV977eD7XeD7O9bxlfS+e6/E3bNgAOIqNISEhNGjQgL59+9KkSRNuu+021q1bx1VXXYWfnx8ffvhhqde5aNEivv76a2644QbneyI4OLjI992JEycACAoKKvW4IiIiUvOoWCgiIiLnZMGCBdhsNnr27AnAZ599xtChQ3nppZcKtEtKSiIwMLBMxzyfY9SqVQuLxcKRI0cKvXbmtlq1ajl7/D3wwANFHismJqZM8Z55TBcXFxISEgq9dvjwYQBCQkKK3T8kJISpU6cydepU4uPj+e6773jsscdITExk0aJF5YqlqNgsFkuR8xMWda9KcmZPtPLex9Ku0dPTk+zs7ELHOLtwW17nem/L+l481+Nv2LCBBg0aFPu+yC8ye3h4cM8995R4jQsWLOCHH37g5ptvZvbs2c45Clu2bMns2bPJy8srMG9h/iItLVq0KPG4IiIiUjNpGLKIiIiUW3x8PGPHjiUgIIB7770XcBSS8nu15VuwYAGHDh0qtP/Zvd/ylecYZ/Px8aFjx47MmzePrKws5/bU1FS+//575+/e3t706tWLrVu30qpVK9q3b1/o5+zeemU59+WXX868efMKXJPdbuezzz6jXr16NGnSpEzHioyM5MEHH6RPnz5s2bKlXHEUF1v79u355ptvyMnJcW5PS0vjhx9+OOfjns99LOoao6OjSUxMLFDUzMnJYfHixWWKp7j3VGnnLc65vBfLc/wNGzYU6olqGAYfffQRLVq04JJLLilx/zO98sorzoV0ziwKDhw4kLS0NL7++usC7WfOnElERIRzcRURERGRM6lnoYiIiJRox44dzrnoEhMT+fXXX/n444+xWq3Mnz+f2rVrA3DttdcyY8YMmjVrRqtWrdi8eTOvvPIK9erVK3TM/KHLb7zxBnfeeSdubm40bdq0XMcoygsvvMDVV19Nnz59eOSRR7DZbEyePBkfHx/n0Mv883br1o3u3btz//33Ex0dTWpqKnv27OH777/nl19+Kfd9mjhxIn369KFXr16MHTsWd3d33n33XXbs2MHs2bOLnR8uOTmZXr16cfv
"text/plain": [
"<Figure size 1280x960 with 6 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axs = plot.subplots(\n",
" 2,\n",
" 3,\n",
" layout=\"constrained\",\n",
" figsize=(12.8, 9.6)\n",
")\n",
"\n",
"fig.suptitle(r\"Data denoising results as $\\beta \\to 0$\")\n",
"\n",
"N = 100\n",
"\n",
"for i in range(len(axs.flat)):\n",
" ax = axs.flat[i]\n",
" plot_denoise_results(2**-i, N, ax)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "cef74c06",
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1280x960 with 6 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plot\n",
"import modules.tridiagonal as td\n",
"\n",
"def smooth_iterative(data, beta, num_iterations):\n",
" \"\"\"\n",
" Smooth noisy data (y) by means of solving a minimization problem\n",
" iteratively with the gradient descent method.\n",
"\n",
" Args:\n",
" data (numpy.array): of the noisy data to be smoothed (y)\n",
" beta (float): parameter >= 0 that balances fit and smoothing\n",
" num_iterations (int): number of gradient descent iterations (>0)\n",
"\n",
" Returns:\n",
" numpy array of smoothed data (s)\n",
" \"\"\"\n",
"\n",
" n = len(data) - 1\n",
"\n",
" A = create_A(n)\n",
"\n",
" # Our initial eigenvalue guess\n",
" initial_guess = np.zeros(n + 1)\n",
" initial_guess[0] = 1\n",
"\n",
" max_eigenvalue = td.largest_eigenvalue(\n",
" *A, \n",
" initial_guess,\n",
" 30\n",
" )\n",
"\n",
" alpha = 1.5 / (1 + beta * max_eigenvalue)\n",
" x = np.zeros(n + 1)\n",
"\n",
" for _ in range(num_iterations):\n",
" gradient = td.multiply_vector(\n",
" *td.add(\n",
" td.identity(n + 1), \n",
" td.scale(beta, A)\n",
" ),\n",
" x\n",
" ) - data\n",
" # print(f\"{alpha=}\")\n",
" # print(f\"{gradient=}\")\n",
" # print(f\"{x=}\")\n",
" x = x - alpha * gradient\n",
"\n",
" return x\n",
"\n",
"fig, axs = plot.subplots(\n",
" 3,\n",
" 2,\n",
" # 1,\n",
" # 1,\n",
" layout=\"constrained\",\n",
" figsize=(12.8, 9.6)\n",
")\n",
"\n",
"N = 100\n",
"beta = 10\n",
"\n",
"fig.suptitle(f\"Iterative vs direct denoising comparison for β={beta} and {N=}\")\n",
"\n",
"for i in range(len(axs.flat)):\n",
"# for _ in range(1):\n",
" ax = axs.flat[i]\n",
" iterations = 3**(i + 1)\n",
" # ax = axs\n",
" # iterations = 3 ** 5\n",
"\n",
" # Create the data\n",
" x = np.linspace(-1, 1, num=N+1)\n",
" g, y = sample_with_noise(x, p5)\n",
" sd = smooth(y, beta) # Indirect\n",
" si = smooth_iterative(y, beta, iterations) # Direct\n",
"\n",
" # Plot the data\n",
" ax.set_title(f\"{iterations=}\")\n",
" ax.set_xlabel(\"x\")\n",
" ax.set_ylabel(\"y\")\n",
" ax.plot(x, y, \".\", label=\"raw data points\")\n",
" ax.plot(x, g, \"-\", label=\"original function\")\n",
" ax.plot(x, sd, \"-\", label=\"directly denoised values\")\n",
" ax.plot(x, si, \"-\", label=\"iteratively denoised values\")\n",
" ax.legend()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d48e32df",
"metadata": {},
"outputs": [],
"source": []
}
],
"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.10.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}