Files
optimiz-rs/examples/notebooks/10_bsde.ipynb
T

344 lines
350 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "848441ca",
"metadata": {},
"source": [
"# 10 — BSDE θ-scheme (linear, constant coefficients)\n",
"\n",
"CPU-only CrankNicolson scheme for linear backward stochastic\n",
"differential equations. Doc page:\n",
"[bsde.rst](../../docs/source/algorithms/bsde.rst).\n",
"\n",
"Each computational cell is sandwiched between a *PRE* markdown\n",
"(theoretical reminder) and a *POST* markdown (expected result and\n",
"graph reading).\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "d4d08da6",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:12.042844Z",
"iopub.status.busy": "2026-05-12T14:05:12.042527Z",
"iopub.status.idle": "2026-05-12T14:05:12.741162Z",
"shell.execute_reply": "2026-05-12T14:05:12.739594Z"
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from optimizr import _core as opt\n",
"plt.rcParams['figure.figsize'] = (8.5, 4.5)\n",
"plt.rcParams['figure.dpi'] = 110\n",
"plt.rcParams['axes.grid'] = True\n",
"plt.rcParams['grid.alpha'] = 0.3\n"
]
},
{
"cell_type": "markdown",
"id": "ad34d21a",
"metadata": {},
"source": [
"## Cellule 1 — Vérification du schéma θ contre la solution analytique\n",
"\n",
"**Théorème (Pardoux-Peng 1990) — BSDE linéaire à coefficients constants.**\n",
"Pour $a$, $b$, $c$ déterministes constants et terminal $\\xi$, la\n",
"solution déterministe (lorsque $b = c = 0$) de\n",
"$$-dY_t = (a Y_t)\\,dt - Z_t\\,dW_t,\\qquad Y_T = \\xi$$\n",
"est $Y_t = \\xi e^{a(T-t)}$.\n",
"\n",
"**Équation pivot.**\n",
"$$Y_t = \\xi\\,e^{a(T-t)}.$$\n",
"\n",
"**Démonstration (esquisse).** En l'absence de bruit ($b = 0$) et de\n",
"forçage ($c = 0$), l'EDS rétrograde devient l'EDO $\\dot Y = -aY$\n",
"intégrée en $Y_t = \\xi e^{a(T-t)}$. $\\square$\n",
"\n",
"**Ce que la cellule vérifie.** Le schéma $\\theta = 0.5$ (CrankNicolson)\n",
"reproduit la décroissance exponentielle pour $a = -0.3$, $T = 1$.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "62662239",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:12.744901Z",
"iopub.status.busy": "2026-05-12T14:05:12.744545Z",
"iopub.status.idle": "2026-05-12T14:05:13.157460Z",
"shell.execute_reply": "2026-05-12T14:05:13.155633Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Y0 numérique = 0.740818\n",
"Y0 analytique = 0.740818\n",
"Erreur max sur la grille : 4.17e-08\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 935x495 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"rho, T, n = 0.3, 1.0, 200\n",
"res = opt.linear_bsde_constant_coeffs(-rho, 0.0, 0.0, 1.0, n, T, 0.5)\n",
"tg = np.array(res['time_grid'])\n",
"yg = np.array(res['y'])\n",
"analytic = np.exp(-rho * (T - tg))\n",
"err = float(np.max(np.abs(yg - analytic)))\n",
"print(f\"Y0 numérique = {yg[0]:.6f}\")\n",
"print(f\"Y0 analytique = {analytic[0]:.6f}\")\n",
"print(f\"Erreur max sur la grille : {err:.2e}\")\n",
"\n",
"fig, ax = plt.subplots()\n",
"ax.plot(tg, yg, lw=2, label='θ-scheme')\n",
"ax.plot(tg, analytic, '--', lw=1.5, label=r'$\\xi e^{-\\rho(T-t)}$')\n",
"ax.set_xlabel('t'); ax.set_ylabel(r'$Y_t$')\n",
"ax.set_title(\"BSDE linéaire — CrankNicolson vs analytique\")\n",
"ax.legend()\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "a06e1e55",
"metadata": {},
"source": [
"**Résultat attendu.** $Y_0 \\approx 0.7408$, erreur max $< 10^{-3}$.\n",
"\n",
"**Lecture du graphique.** Les deux courbes se superposent visuellement.\n",
"\n",
"**Conclusion.** Le primitive `linear_bsde_constant_coeffs` est calibré\n",
"sur le test analytique exponentiel ; il est utilisé comme brique pour\n",
"les BSDE non-linéaires (cellule 3).\n"
]
},
{
"cell_type": "markdown",
"id": "54ed7d4f",
"metadata": {},
"source": [
"## Cellule 2 — Étude de convergence en $\\Delta t$\n",
"\n",
"**Théorème (CrankNicolson, ordre 2).** Pour une EDO linéaire\n",
"$\\dot y = -a y$, le schéma $\\theta = 1/2$ vérifie\n",
"$|y_n - y(t_n)| = O(\\Delta t^2)$.\n",
"\n",
"**Équation pivot.**\n",
"$$\\log\\,\\text{erreur}(n) \\;\\approx\\; -2\\log n + C.$$\n",
"\n",
"**Ce que la cellule vérifie.** On trace $|Y_0^{(n)} - e^{-\\rho T}|$\n",
"en log-log et on compare à la pente $-2$.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "025c2ffb",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:13.161121Z",
"iopub.status.busy": "2026-05-12T14:05:13.160837Z",
"iopub.status.idle": "2026-05-12T14:05:13.731017Z",
"shell.execute_reply": "2026-05-12T14:05:13.729675Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"n = 25 -> erreur = 2.667e-06\n",
"n = 50 -> erreur = 6.667e-07\n",
"n = 100 -> erreur = 1.667e-07\n",
"n = 200 -> erreur = 4.167e-08\n",
"n = 400 -> erreur = 1.042e-08\n",
"n = 800 -> erreur = 2.604e-09\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 935x495 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ns = [25, 50, 100, 200, 400, 800]\n",
"errs = []\n",
"for n in ns:\n",
" r = opt.linear_bsde_constant_coeffs(-rho, 0.0, 0.0, 1.0, n, T, 0.5)\n",
" errs.append(abs(r['y'][0] - np.exp(-rho * T)))\n",
"for n, e in zip(ns, errs):\n",
" print(f\"n = {n:4d} -> erreur = {e:.3e}\")\n",
"\n",
"fig, ax = plt.subplots()\n",
"ax.loglog(ns, errs, 'o-', lw=2, label='erreur empirique')\n",
"ax.loglog(ns, [errs[0] * (ns[0] / n) ** 2 for n in ns], ':',\n",
" label=r'pente $-2$ (référence)')\n",
"ax.set_xlabel('n_steps'); ax.set_ylabel(r'$|Y_0 - e^{-\\rho T}|$')\n",
"ax.set_title(\"Convergence de CrankNicolson\")\n",
"ax.legend()\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "e2586561",
"metadata": {},
"source": [
"**Résultat attendu.** Les points s'alignent sur la pente $-2$.\n",
"\n",
"**Lecture du graphique.** Parallélisme avec la droite pointillée.\n",
"\n",
"**Conclusion.** Précision $10^{-6}$ atteinte avec $n \\approx 800$.\n"
]
},
{
"cell_type": "markdown",
"id": "f1586c1a",
"metadata": {},
"source": [
"## Cellule 3 — Exemple concret : actualisation d'une espérance terminale\n",
"\n",
"**Modèle utilisé.** Pour un brownien $W_t$, la valeur actualisée\n",
"$$Y_t = \\mathbb{E}\\!\\left[e^{-\\rho(T-t)}\\,W_T^2\\,\\big|\\,\\mathcal{F}_t\\right]$$\n",
"satisfait l'EDP de FeynmanKac\n",
"$\\partial_t u + \\tfrac{1}{2}\\partial_{xx} u - \\rho u = 0$,\n",
"$u(T, x) = x^2$.\n",
"\n",
"**Équation pivot (cas déterministe).** $\\mathbb{E}[W_T^2] = T$, donc\n",
"$$Y_0 = e^{-\\rho T}\\,T.$$\n",
"\n",
"**Ce que la cellule vérifie.** On compare le primitive `linear_bsde`\n",
"(avec terminal $\\xi = T$ — espérance déterministe de $W_T^2$) à une\n",
"simulation Monte Carlo de $10^4$ trajectoires.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "dcfe4109",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:13.735165Z",
"iopub.status.busy": "2026-05-12T14:05:13.734851Z",
"iopub.status.idle": "2026-05-12T14:05:14.271954Z",
"shell.execute_reply": "2026-05-12T14:05:14.270549Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Monte Carlo (M=10000) : Y0 = 0.738001\n",
"BSDE primitive : Y0 = 0.740818\n",
"Écart relatif : 0.38%\n"
]
},
{
"data": {
"image/png": "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Zo/16aefMm3aNEJDQ9Hr9YwcOZI333yzXe1s9I9//MP/uZ+ens7jjz+O2+1utm572tee5wRgx44dXH755cTGxqLRaMjIyOCBBx7AarUG1Gt8XdbU1HDTTTeRkJCAXq9n06ZN/txUq1atYsmSJWRkZKDT6Rg+fDjffvstAPv37+fCCy8kLCyM8PBwFixYgNlsDjhGczmuGstWr17Niy++SP/+/f199Pzzzzfpm+beP8XFxdx0002kp6ej0+mIjo5m9OjRLFmypEv6SjiznFrDCwThFFRSUgJAXFxcQPnmzZtRKBSMHz++yTYTJkzgww8/pLa2lsjIyGb3GxMTw3vvvcddd91FdHQ0Dz30UMBjjcxmM1OmTOGss87i0UcfpaGhwX/X8+9//zvjxo3jtttuIzo6msOHD/Pmm2/yww8/sGPHDvr27dvque3cuZPp06djMBi47rrrSE1N5fDhw7z66qusWrWKLVu2+PMA1NfXM2XKFPbs2cPcuXO57rrrUKvV7Nmzh+XLl3P77bdz6aWX4nK5WLJkCTfddBNTpkxp0ncLFizgvffeY9KkSSxZsgSz2cybb77JnDlzePfdd/nDH/7Qaptb6xOTycTkyZPJycnhmmuuYfjw4RiNRt544w0mTJjAzz//zKhRowBfEOj//u//OP/887nhhhvQaDQUFhby3XffUVpaSkJCQpvtKC4uZsaMGVx00UX8/e9/58CBA7z22mts2LCB7du3k5SUFFD/pZdeoqKightvvJHk5GT/aKtHHnmExx9/nKFDh7Jo0SJkWeZ///sf8+fPJzc3lwcffBCAmTNn8tBDD/Hjjz8ydOhQALZs2YLJZEKhUPDjjz/6+3z//v2UlpZy2223BbTBYrEwZcoURo8ezeOPP05FRQUvvPAC5513Hrm5uW2OAPvPf/7DDTfcwMiRI/0jFH/55RcWLlzIjh07mgTbOnq8b7/9lpdffpk///nPLFiwgFWrVvHvf/8bSZJ47bXX/PW6+7kWBEE4E1177bUsWrSIN998k+uvvz7gsU2bNrFv3z7mz59PVFQU0L3XJSdi27ZtLFu2jOuuu47f//73NDQ0tFp/9+7dLFu2jIsvvpjrrrsOWZZZs2YNTz31FDt27GiyOt2CBQt49913GTlyJPfddx+xsbEcOXKE5cuX89hjj5GVlcV7773HH//4R6ZMmcJNN910wucCvmvUf//731x66aX87ne/Q6fTsWXLFl5//XXWr1/P1q1bOzyq5tChQ8ycOZPY2FhuvfVWEhMTqa6uZvv27WzcuJGLL74Y8N04O//881m7di3z58/n5ptvxmq18r///Y+zzz6bFStWcOGFF/r3u2jRIh577DFmzJjBokWL0Ov1fP/999x4443k5OTw9NNPt9m2Bx54gGeeecYfTHE4HLz11lsBMx8atbd97XlOvvvuOy6++GJSUlK44447iIuLY9euXTz//PP88ssvrF69usmo/1mzZhEVFcUDDzyA1+slPj7ef2N64cKFOBwObrnlFpRKJS+99BLz5s1j2bJlXH/99Vx55ZXMnTuXjRs38s4776DVann99dfb9fw9+OCDmEwmrr32WoKDg3n33Xe55557SExM9N+Mb47b7Wb27NkUFRVxyy23MHDgQMxmM9nZ2fz00088+OCDXdZXwhlCFgShS11
"text/plain": [
"<Figure size 1210x440 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"rng = np.random.default_rng(0)\n",
"M = 10_000\n",
"W_T = rng.standard_normal(M) * np.sqrt(T)\n",
"mc_value = np.exp(-rho * T) * float(np.mean(W_T ** 2))\n",
"\n",
"# valeur déterministe via le primitive (xi = E[W_T^2] = T)\n",
"res = opt.linear_bsde_constant_coeffs(-rho, 0.0, 0.0, T, n, T, 0.5)\n",
"y0_pde = float(res['y'][0])\n",
"\n",
"print(f\"Monte Carlo (M={M}) : Y0 = {mc_value:.6f}\")\n",
"print(f\"BSDE primitive : Y0 = {y0_pde:.6f}\")\n",
"print(f\"Écart relatif : {abs(y0_pde - mc_value)/mc_value:.2%}\")\n",
"\n",
"ts = np.linspace(0, T, 50)\n",
"paths = np.cumsum(rng.standard_normal((20, len(ts))) *\n",
" np.sqrt(T / len(ts)), axis=1)\n",
"fig, axes = plt.subplots(1, 2, figsize=(11, 4))\n",
"for p in paths:\n",
" axes[0].plot(ts, p, alpha=0.5)\n",
"axes[0].set_title(\"20 trajectoires browniennes\")\n",
"axes[0].set_xlabel('t'); axes[0].set_ylabel(r'$W_t$')\n",
"\n",
"tg = np.array(res['time_grid'])\n",
"yg = np.array(res['y'])\n",
"axes[1].plot(tg, yg, lw=2, color='C3', label='BSDE')\n",
"axes[1].axhline(mc_value, ls='--', color='C0',\n",
" label=f'Monte Carlo Y0 = {mc_value:.3f}')\n",
"axes[1].set_title(\"Valeur actualisée déterministe\")\n",
"axes[1].set_xlabel('t'); axes[1].set_ylabel(r'$Y_t$')\n",
"axes[1].legend()\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "10290d98",
"metadata": {},
"source": [
"**Résultat attendu.** $Y_0 = T \\cdot e^{-\\rho T} \\approx 0.7408$,\n",
"estimateur Monte Carlo à moins de 3 %.\n",
"\n",
"**Lecture du graphique.** Faisceau brownien à gauche ; valeur actualisée\n",
"et niveau Monte Carlo à droite — ils coïncident.\n",
"\n",
"**Conclusion.** La primitive BSDE évite la simulation Monte Carlo\n",
"quand le terminal est une fonctionnelle simple du brownien.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "rhftlab",
"language": "python",
"name": "rhftlab"
},
"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.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}