{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# L-curve criterion\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This page shows how to use the L-curve criterion to find the regularization parameter in the case\n",
"of the example ill-posed problem.\n",
"\n",
"The basic theory of the L-curve criterion is described in [this page](../../user/theory/lcurve.ipynb).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Definition of example inverse problem\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"As a famouse ill-posed linear equation, Fredholm integral equation is often used:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\int_a^b K(s, t)\\ x(t)\\ \\mathrm{d}t = b(s), \\quad c\\leq s\\leq d.\n",
"\\label{eq:fredholm}\n",
"\\end{equation}\n",
"$$\n",
"\n",
"Here we think of the following situation as the above equation form:\n",
"\n",
"$$\n",
"\\begin{align}\n",
"& K(s, t) \\equiv (\\cos(s) + \\cos(t))\\left(\\frac{\\sin(u)}{u}\\right)^2,\\quad u \\equiv \\pi\\left(\\sin(s) + \\sin(t) \\right),\\\\\n",
"& [a, b] = [c, d] \\equiv \\left[-\\frac{\\pi}{2}, \\frac{\\pi}{2} \\right].\n",
"\\end{align}\n",
"$$\n",
"\n",
"And, the true solution $x_\\text{true}(t)$ is assumed as follows:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"x_\\text{true}(t) = 2.0 \\exp\\left[-6(t-0.8)^2 \\right] + \\exp\\left[-2(t+0.5)^2 \\right].\n",
"\\end{equation}\n",
"$$\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from matplotlib import pyplot as plt\n",
"from scipy.sparse import diags\n",
"\n",
"from cherab.inversion import Lcurve, compute_svd\n",
"from cherab.inversion.tools import parse_scientific_notation\n",
"\n",
"plt.rcParams[\"figure.dpi\"] = 150"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Firstly, let us code the $K(s, t)$ and $x_\\text{true}(t)$ as a function.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"def kernel(s: np.ndarray, t: np.ndarray) -> np.ndarray:\n",
" \"\"\"Kernel of Fredholm integral equation of the first kind.\"\"\"\n",
" u = np.pi * (np.sin(s) + np.sin(t))\n",
" if u == 0:\n",
" return np.cos(s) + np.cos(t)\n",
" else:\n",
" return (np.cos(s) + np.cos(t)) * (np.sin(u) / u) ** 2\n",
"\n",
"\n",
"def x_t_func(t: np.ndarray) -> np.ndarray:\n",
" \"\"\"Define the function x_true(t)\"\"\"\n",
" return 2.0 * np.exp(-6.0 * (t - 0.8) ** 2) + np.exp(-2.0 * (t + 0.5) ** 2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Discretization of the equation\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When discretizing the integral equation \\eqref{eq:fredholm} using the trapezoidal integral approximation,\n",
"the following linear equation is obtained:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\mathbf{K} \\mathbf{x} = \\mathbf{b},\n",
"\\end{equation}\n",
"$$\n",
"\n",
"where $\\mathbf{K}\\in\\mathbb{R}^{M\\times N}$ is the discretized kernel matrix,\n",
"$\\mathbf{x}\\in\\mathbb{R}^N$ is the discretized solution vector and\n",
"$\\mathbf{b}\\in\\mathbb{R}^M$ is the discretized data vector.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$N$ and $M$ are the number of discretization points of $t$ and $s$, respectively.\n",
"Here we set $N=M=64$ and generate points evenly spaced in $[-\\pi/2, \\pi/2]$.\n",
"$x_\\mathrm{true}(t)$ discretized on these points yields the true solution vector $\\mathbf{x}_\\mathrm{true}$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# discretize s, t\n",
"s = np.linspace(-np.pi * 0.5, np.pi * 0.5, num=64, endpoint=True)\n",
"t = np.linspace(-np.pi * 0.5, np.pi * 0.5, num=64, endpoint=True)\n",
"\n",
"# vectorize solution\n",
"x_t = x_t_func(t)\n",
"\n",
"# discretize kernel\n",
"k_mat = np.zeros((s.size, t.size))\n",
"k_mat = np.array([[kernel(i, j) for j in t] for i in s])\n",
"\n",
"# trapezoidal rule\n",
"k_mat[:, 0] *= 0.5\n",
"k_mat[:, -1] *= 0.5\n",
"k_mat *= t[1] - t[0]\n",
"\n",
"print(f\"{k_mat.shape = }\")\n",
"print(f\"{x_t.shape = }\")\n",
"print(f\"condition number of K is {np.linalg.cond(k_mat):.4g}\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The given data $\\mathbf{b}$ is generated by adding white noise $\\mathbf{e}$ to the true\n",
"data $\\bar{\\mathbf{b}} = \\mathbf{K}\\mathbf{x}_\\mathrm{true}$, that is,\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\mathbf{b} = \\bar{\\mathbf{b}} + \\mathbf{e}.\n",
"\\end{equation}\n",
"$$\n",
"\n",
"The noise variance is set to $10^{-4}$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b_bar = k_mat @ x_t\n",
"rng = np.random.default_rng()\n",
"noise = rng.normal(0, 1.0e-4, b_bar.size)\n",
"b = b_bar + noise"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solve the inverse problem\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The solution of the ill-posed linear equation is obtained with the regularization procedure:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\mathbf{x}_\\lambda = \\left(\\mathbf{K}^\\mathsf{T}\\mathbf{K} + \\lambda\\mathbf{H}\\right)^{-1}\\mathbf{K}^\\mathsf{T}\\mathbf{b},\n",
"\\end{equation}\n",
"$$\n",
"\n",
"where $\\mathbf{H}$ is the regularization matrix.\n",
"Here we set $\\mathbf{H} = \\mathbf{D_2}^\\mathsf{T}\\mathbf{D_2}$, where $\\mathbf{D_2}$ is the second-order difference matrix.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dmat = diags([1, -2, 1], [-1, 0, 1], shape=(t.size, t.size)).tocsr()\n",
"print(f\"{dmat.shape = }\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we create lcurve solver object after calculating the singular value decomposition according\n",
"to the [series expansion of solution](../../user/theory/inversion.ipynb#Series-expansion-of-the-solution).\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"s, u, basis = compute_svd(k_mat, dmat.T @ dmat)\n",
"lcurve = Lcurve(s, u, basis, data=b)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us solve the inverse problem.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sol, status = lcurve.solve()\n",
"print(status)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Evaluate the L-curve criterion\n",
"\n",
"Next we evaluate the solution obtained by the L-curve criterion\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Plot L-curve\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"nbsphinx-thumbnail"
]
},
"outputs": [],
"source": [
"fig, ax = lcurve.plot_L_curve(scatter_plot=7)\n",
"ax.autoscale(axis=\"both\", tight=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The L-curve shown above is limited to the range of $\\lambda$ from $\\sigma_0^2$ to $\\sigma_{r}^2$ and\n",
"it is enough to find the corner of the L-curve in this range.\n",
"\n",
"The below plot shows why it is enough by plotting points of $\\lambda = \\sigma_i^2$ on the L-curve,\n",
"where $\\sigma_i$ is the $i$-th singular value and $i$ is indicated by the annotation."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = lcurve.plot_L_curve(plot_lambda_opt=False)\n",
"\n",
"indices = list(range(0, 20)) + [lcurve.s.size - 1]\n",
"sigmas = lcurve.s[indices]\n",
"residuals = [lcurve.residual_norm(beta) for beta in sigmas**2]\n",
"regularizations = [lcurve.regularization_norm(beta) for beta in sigmas**2]\n",
"ax.scatter(residuals, regularizations, color=\"red\", marker=\".\")\n",
"ax.legend([\"L-curve\", \"Points at $\\\\lambda = \\\\sigma_i^2$\"])\n",
"for i, ind in enumerate(indices):\n",
" ax.annotate(f\"{ind}\", (residuals[i], regularizations[i]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"sigma_7^2 : {sigmas[7]**2:.4e}\")\n",
"print(f\"sigma_8^2 : {sigmas[8]**2:.4e}\")\n",
"print(f\"sigma_9^2 : {sigmas[9]**2:.4e}\")\n",
"print(f\"lambda_opt: {lcurve.lambda_opt:.4e}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Plot L-curve's curvature\n",
"\n",
"$\\lambda_\\mathrm{opt}$ is the regularization parameter that maximizes the curvature of the L-curve.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"_, ax = lcurve.plot_curvature()\n",
"lambda_opt_text = parse_scientific_notation(f\"{lcurve.lambda_opt:.3e}\")\n",
"ax.set_title(f\"$\\\\lambda_\\\\mathrm{{opt}} = {lambda_opt_text}$\")\n",
"ax.tick_params(axis=\"both\", which=\"both\", direction=\"in\", top=True, right=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Compare $\\mathbf{x}_\\lambda$ with $\\mathbf{x}_\\mathrm{true}$\n",
"\n",
"Let us compare solutions at different regularization parameters $\\lambda=10^{-9}$,\n",
"$\\lambda_\\text{opt}$, $10^3$ with the true solution $\\mathbf{x}_\\mathrm{true}$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"lambdas = [1.0e-9, lcurve.lambda_opt, 1.0e3]\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(10, 4), sharey=True, layout=\"constrained\")\n",
"\n",
"for ax, beta in zip(axes, lambdas, strict=False):\n",
" ax.plot(t, x_t, \"--\", label=\"$\\\\mathbf{x}_\\\\mathrm{true}$\")\n",
" ax.plot(t, lcurve.solution(beta=beta), label=\"$\\\\mathbf{x}_\\\\lambda$\")\n",
"\n",
" ax.set_xlim(t.min(), t.max())\n",
" ax.set_ylim(0, x_t.max() * 1.1)\n",
" ax.set_xlabel(\"$t$\")\n",
" parsed_lambda = parse_scientific_notation(f\"{beta:.2e}\")\n",
" ax.set_title(f\"$\\\\lambda = {parsed_lambda}$\")\n",
" ax.tick_params(direction=\"in\", labelsize=10, which=\"both\", top=True, right=True)\n",
" ax.legend(loc=\"upper left\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that the solution at $\\lambda < \\lambda_\\mathrm{opt}$ is perturbed by noise, while\n",
"the solution at $\\lambda > \\lambda_\\mathrm{opt}$ is smoothed too much.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Plot norms and curvature as a function of $\\lambda$\n",
"\n",
"Let us plot the residual norm $\\sqrt{\\rho}$ and the regularization norm $\\sqrt{\\eta}$ as a function of $\\lambda$.\n",
"Additionally, we plot the curvature of the L-curve as a function of $\\lambda$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax1 = plt.subplots()\n",
"fig.subplots_adjust(right=0.85)\n",
"\n",
"ax2 = ax1.twinx()\n",
"ax3 = ax1.twinx()\n",
"\n",
"ax3.spines.right.set_position((\"axes\", 1.2))\n",
"\n",
"# calculation of the values\n",
"lambdas = np.logspace(-10, 0, num=500)\n",
"rhos = [lcurve.residual_norm(beta) for beta in lambdas]\n",
"etas = [lcurve.regularization_norm(beta) for beta in lambdas]\n",
"kappa = [lcurve.curvature(beta) for beta in lambdas]\n",
"\n",
"# plot lines\n",
"(p1,) = ax1.loglog(lambdas, rhos, color=\"C0\")\n",
"(p2,) = ax2.loglog(lambdas, etas, color=\"C1\")\n",
"(p3,) = ax3.semilogx(lambdas, kappa, color=\"C2\")\n",
"\n",
"# set axes properties\n",
"ax1.set(\n",
" xlim=(lambdas[0], lambdas[-1]),\n",
" xlabel=\"Regularization parameter $\\\\lambda$\",\n",
" ylabel=\"Residual norm $\\\\sqrt{\\\\rho}$\",\n",
")\n",
"ax2.set(ylabel=\"Regularization norm $\\\\sqrt{\\\\eta}$\")\n",
"ax3.set(ylabel=\"curvature of L-curve\")\n",
"\n",
"ax1.yaxis.label.set_color(p1.get_color())\n",
"ax2.yaxis.label.set_color(p2.get_color())\n",
"ax3.yaxis.label.set_color(p3.get_color())\n",
"\n",
"ax1.tick_params(axis=\"x\", which=\"both\", direction=\"in\", top=True)\n",
"ax1.tick_params(axis=\"y\", which=\"both\", direction=\"in\", colors=p1.get_color())\n",
"ax2.tick_params(axis=\"y\", which=\"both\", direction=\"in\", colors=p2.get_color())\n",
"ax3.tick_params(axis=\"y\", which=\"both\", direction=\"in\", colors=p3.get_color())\n",
"\n",
"ax3.spines[\"left\"].set_color(p1.get_color())\n",
"ax2.spines[\"right\"].set_color(p2.get_color())\n",
"ax3.spines[\"right\"].set_color(p3.get_color())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$\\sqrt{\\rho}$ is monotonically increasing with $\\lambda$, while $\\sqrt{\\eta}$ is monotonically decreasing with $\\lambda$.\n",
"This behavior is consistent with the theory of the L-curve criterion.\n",
"\n",
"The curvature of the L-curve is maximized at the center region where both are flat.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Check the relative error\n",
"\n",
"The relative error between the solution $\\mathbf{x}_\\lambda$ and the true solution\n",
"$\\mathbf{x}_\\mathrm{true}$ is defined as follows:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\epsilon_\\mathrm{rel} = \\frac{\\|\\mathbf{x}_\\lambda - \\mathbf{x}_\\mathrm{true}\\|_2}{\\|\\mathbf{x}_\\mathrm{true}\\|_2}.\n",
"\\end{equation}\n",
"$$\n",
"\n",
"Let us seek the minimum $\\epsilon_\\mathrm{rel}$ as a function of $\\lambda$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from scipy.optimize import minimize_scalar\n",
"\n",
"X_T_NORM = np.linalg.norm(x_t, axis=0)\n",
"\n",
"\n",
"def relative_error(\n",
" log_lambda: float, x_t: np.ndarray = x_t, x_t_norm: float = X_T_NORM, lcurve: Lcurve = lcurve\n",
") -> float:\n",
" \"\"\"Calculate relative error.\"\"\"\n",
" beta = 10**log_lambda\n",
" sol = lcurve.solution(beta=beta)\n",
" return np.linalg.norm(x_t - sol, axis=0) / x_t_norm\n",
"\n",
"\n",
"# minimize relative error\n",
"bounds = -10, -1\n",
"res = minimize_scalar(\n",
" relative_error,\n",
" bounds=bounds,\n",
" method=\"bounded\",\n",
" args=(x_t, X_T_NORM, lcurve),\n",
" options={\"xatol\": 1.0e-10, \"maxiter\": 1000},\n",
")\n",
"\n",
"# obtain minimum relative error and lambda\n",
"error_min = res.fun\n",
"lambda_min = 10**res.x\n",
"\n",
"print(f\"minimum relative error: {error_min:.2%} at lambda = {lambda_min:.4g}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us plot the relative error and curvature as a function of $\\lambda$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"# set regularization parameters\n",
"num = 500\n",
"lambdas = np.logspace(*bounds, num=num)\n",
"\n",
"# calculate errors and curvatures\n",
"errors = np.asarray([relative_error(log_lambda) for log_lambda in np.linspace(*bounds, num=num)])\n",
"kappa = np.asarray([lcurve.curvature(beta) for beta in lambdas])\n",
"\n",
"# create figure\n",
"fig, ax1 = plt.subplots()\n",
"ax2 = ax1.twinx()\n",
"\n",
"# plot errors and curvatures\n",
"(p1,) = ax1.loglog(lambdas, errors, color=\"C0\")\n",
"(p2,) = ax2.semilogx(lambdas, kappa, color=\"C1\")\n",
"\n",
"# plot minimum error vertical line and point\n",
"ax1.axvline(lambda_min, color=\"r\", linestyle=\"--\", linewidth=0.75)\n",
"ax1.scatter(lambda_min, error_min, color=\"r\", marker=\"o\", s=10, zorder=2)\n",
"ax1.text(\n",
" lambda_min,\n",
" 1.5e-2,\n",
" \"$\\\\lambda_\\\\mathrm{min}$\",\n",
" color=\"r\",\n",
" horizontalalignment=\"left\",\n",
" verticalalignment=\"center\",\n",
")\n",
"\n",
"# plot maximum curvature vertical line and point\n",
"assert lcurve.lambda_opt is not None\n",
"ax1.axvline(lcurve.lambda_opt, color=\"g\", linestyle=\"--\", linewidth=0.75)\n",
"ax2.scatter(\n",
" lcurve.lambda_opt, lcurve.curvature(lcurve.lambda_opt), color=\"g\", marker=\"o\", s=10, zorder=2\n",
")\n",
"ax1.text(\n",
" lcurve.lambda_opt,\n",
" 1.5e-2,\n",
" \"$\\\\lambda_\\\\mathrm{opt}$\",\n",
" color=\"g\",\n",
" horizontalalignment=\"left\",\n",
" verticalalignment=\"center\",\n",
")\n",
"\n",
"# set axes\n",
"ax1.set(\n",
" xlim=(lambdas[0], lambdas[-1]),\n",
" ylim=(0.01, 1),\n",
" xlabel=\"$\\\\lambda$\",\n",
" ylabel=\"Relative error $\\\\epsilon_\\\\mathrm{rel}$\",\n",
")\n",
"ax2.set(ylabel=\"curvature of L-curve\")\n",
"\n",
"ax1.yaxis.label.set_color(p1.get_color())\n",
"ax2.yaxis.label.set_color(p2.get_color())\n",
"\n",
"ax1.tick_params(axis=\"x\", which=\"both\", direction=\"in\", top=True)\n",
"ax1.tick_params(axis=\"y\", which=\"both\", direction=\"in\", colors=p1.get_color())\n",
"ax2.tick_params(axis=\"y\", which=\"both\", direction=\"in\", colors=p2.get_color())\n",
"\n",
"ax2.spines[\"left\"].set_color(p1.get_color())\n",
"ax2.spines[\"right\"].set_color(p2.get_color())\n",
"\n",
"error_opt = relative_error(np.log10(lcurve.lambda_opt))\n",
"ax1.set_title(\n",
" f\"$\\\\epsilon_\\\\mathrm{{rel}}(\\\\lambda_\\\\mathrm{{opt}})$ = {error_opt:.2%}, \"\n",
" + f\"$\\\\epsilon_\\\\mathrm{{rel}}(\\\\lambda_\\\\mathrm{{min}}) = ${error_min:.2%}\"\n",
");"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Compare the solution at $\\lambda_\\mathrm{opt}$ with $\\lambda_\\mathrm{min}$\n",
"\n",
"$\\lambda_\\mathrm{min}$ is the regularization parameter that minimizes the relative error.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = plt.subplots()\n",
"ax.plot(t, x_t, \"k--\", label=\"$\\\\mathbf{x}_\\\\mathrm{true}$\", linewidth=1.0)\n",
"ax.plot(t, lcurve.solution(lambda_min), label=\"$\\\\lambda_\\\\mathrm{min}$\")\n",
"ax.plot(t, sol, label=\"$\\\\lambda_\\\\mathrm{opt}$\")\n",
"ax.set_xlabel(\"$t$\")\n",
"ax.set_xlim(t.min(), t.max())\n",
"ax.set_ylim(0, x_t.max() * 1.1)\n",
"ax.legend()\n",
"ax.tick_params(axis=\"both\", which=\"both\", direction=\"in\", top=True, right=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Discrete Picard Plot\n",
"\n",
"**Discrete Picard Condition**\n",
"\n",
"> The data vector $\\mathbf{b}$ satisfies the **discrete Picard condition (DPC)** if the data space\n",
"> coefficients $|\\mathbf{u}_i^\\mathsf{T}\\mathbf{b}|$ decay faster than the singular values $\\sigma_i$.\n",
"\n",
"In ill-posed problems, we find that the DPC holds initially and then fails at some point $i_\\mathrm{DPC}$,\n",
"where the data become dominated by errors (noise).\n",
"If this is the case, and if the regularization parameter is accurately selected,\n",
"then the regularized solution should provide a valid solution.\n",
"Examining $i_\\mathrm{DPC}$ provides a method of characterizing the ill-posedness of the problem.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ub = np.abs(lcurve.U.T @ b)\n",
"\n",
"fig, ax1 = plt.subplots()\n",
"ax2 = ax1.twinx()\n",
"\n",
"# DP plot\n",
"ax1.semilogy(lcurve.s, \".-\", label=\"$\\\\sigma_i$\")\n",
"ax1.semilogy(ub, \"o\", label=\"$|\\\\mathbf{u}_i^\\\\mathsf{T}\\\\mathbf{b}|$\")\n",
"ax1.semilogy(ub / lcurve.s, \"s\", label=\"$|\\\\mathbf{u}_i^\\\\mathsf{T}\\\\mathbf{b}|/\\\\sigma_i$\")\n",
"ax1.axhline(1e-4, color=\"k\", linestyle=\"--\", zorder=0)\n",
"ax1.axvline(10, color=\"k\", linestyle=\"--\", zorder=0)\n",
"ax1.set_xlabel(\"$i$\")\n",
"ax1.legend(loc=\"lower left\")\n",
"ax1.tick_params(axis=\"both\", which=\"both\", direction=\"in\", top=True)\n",
"\n",
"# filter plot\n",
"assert lcurve.lambda_opt is not None\n",
"(p2,) = ax2.semilogy(\n",
" lcurve.filter(lcurve.lambda_opt), \".-\", color=\"C4\", label=\"$\\\\mathbf{f}_\\\\mathrm{opt}$\"\n",
")\n",
"ax2.set_ylabel(\"$f_{\\\\lambda, i}$\", color=\"C4\")\n",
"\n",
"# color axes2\n",
"ax2.yaxis.label.set_color(p2.get_color())\n",
"ax2.tick_params(axis=\"y\", which=\"both\", direction=\"in\", colors=p2.get_color())\n",
"ax2.spines[\"right\"].set_color(p2.get_color())\n",
"\n",
"ax1.set_xlim(0, 25);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The vertical dashed line in the above figure marks the biginning of $|\\mathbf{u}_i^\\mathsf{T}\\mathbf{b}| < \\sigma_i$,\n",
"and the horizontal dashed line represents the noise level.\n",
"DPC is satisfied for $i < i_\\mathrm{DPC} \\simeq 10$.
\n",
"We confirm that the filter factor $f_{\\lambda_\\mathrm{opt}, i}$, the $\\lambda_\\mathrm{opt}$ of which\n",
"is selected by the L-curve criterion, starts to decrease around $i_\\mathrm{DPC}$. This behavior works\n",
"as a filter to suppress the noise component and yields a physically meaningful solution.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## References\n"
]
},
{
"cell_type": "raw",
"metadata": {
"raw_mimetype": "text/restructuredtext"
},
"source": [
".. footbibliography::"
]
}
],
"metadata": {
"celltoolbar": "Raw Cell Format",
"kernelspec": {
"display_name": "Python 3.10.8 ('cherab-phix-dev')",
"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"
},
"vscode": {
"interpreter": {
"hash": "2725905a4c02db19e04df9b8fdbbe5ec65a73ea52bebaf9474aa1cc98819834c"
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}