docs(notebooks): topology + BSDE notebook improvements

- 07_topology: expanded persistent-homology examples
- 10_bsde: reworked BSDE solver walkthrough
- 04/05: clear execution counts
- gitignore generated plot PNGs (docs snippets + notebook frames)
This commit is contained in:
ThotDjehuty
2026-07-06 22:24:55 +02:00
parent 8caf0488d9
commit 0463382fcb
5 changed files with 181 additions and 65 deletions
+6
View File
@@ -30,3 +30,9 @@ docs/source/_static/*.log
docs/source/_static/*.toc
docs/source/theory/*.html
docs/source/theory/*.pdf
# Generated plot byproducts (docs snippets + notebook animations)
/grid_search_heatmap.png
/hmm_states.png
/mcmc_posterior.png
examples/*.png
@@ -367,7 +367,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"id": "d3f64621",
"metadata": {
"execution": {
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"id": "59f619dc",
"metadata": {},
"outputs": [
File diff suppressed because one or more lines are too long
+49 -48
View File
@@ -4,23 +4,23 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# 10 \u2014 Backward Stochastic Differential Equations (\u03b8-scheme)\n",
"# 10 Backward Stochastic Differential Equations (θ-scheme)\n",
"\n",
"Companion notebook for the [`bsde` documentation page](https://optimiz-r.readthedocs.io/en/latest/algorithms/bsde.html).\n",
"\n",
"This notebook follows the depth and structure of\n",
"`03_optimal_control_tutorial.ipynb`. It opens with the full **Pardoux\u2013Peng\n",
"`03_optimal_control_tutorial.ipynb`. It opens with the full **PardouxPeng\n",
"existence/uniqueness theorem**, derives the closed-form solution of a\n",
"linear BSDE via Girsanov, validates the Crank\u2013Nicolson \u03b8-scheme primitive\n",
"linear BSDE via Girsanov, validates the CrankNicolson θ-scheme primitive\n",
"`linear_bsde_constant_coeffs`, performs an order-of-convergence study,\n",
"illustrates the **Feynman\u2013Kac bridge** to semi-linear PDEs and ends with a\n",
"illustrates the **FeynmanKac bridge** to semi-linear PDEs and ends with a\n",
"worked physical application (heat equation expectation).\n"
]
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
@@ -43,7 +43,7 @@
"source": [
"## 1. Mathematical background\n",
"\n",
"### The Pardoux\u2013Peng equation\n",
"### The PardouxPeng equation\n",
"\n",
"Let $W = (W_t)_{t \\in [0, T]}$ be a Brownian motion and $\\mathcal{F}_t$ the\n",
"augmented natural filtration. A **backward stochastic differential\n",
@@ -61,7 +61,7 @@
"\n",
"The **driver** $f$ is allowed to depend on the unknown solution.\n",
"\n",
"### Existence and uniqueness (Pardoux\u2013Peng 1990)\n",
"### Existence and uniqueness (PardouxPeng 1990)\n",
"\n",
"If $f$ is uniformly Lipschitz in $(y, z)$ and $\\xi \\in L^2(\\mathcal{F}_T)$,\n",
"there exists a unique pair $(Y, Z) \\in \\mathcal{S}^2 \\times \\mathcal{H}^2$\n",
@@ -75,10 +75,10 @@
"\n",
"is a contraction on $\\mathcal{S}^2 \\times \\mathcal{H}^2$ in the equivalent\n",
"norm $\\| \\cdot \\|_\\beta = \\big(\\int_0^T e^{\\beta t} \\mathbb{E}[\\,\\cdot\\,]^2\\, dt\\big)^{1/2}$\n",
"for $\\beta$ large enough. Banach\u2013Picard then yields a unique fixed point.\n",
"for $\\beta$ large enough. BanachPicard then yields a unique fixed point.\n",
"$\\square$\n",
"\n",
"### Linear BSDE \u2014 closed form via Girsanov\n",
"### Linear BSDE closed form via Girsanov\n",
"\n",
"For coefficients $a, b, c$ deterministic and constant, the linear BSDE\n",
"\n",
@@ -101,9 +101,9 @@
"\\boxed{\\; Y_t \\;=\\; \\xi\\, e^{a (T - t)} \\;}.\n",
"$$\n",
"\n",
"### Crank\u2013Nicolson \u03b8-scheme\n",
"### CrankNicolson θ-scheme\n",
"\n",
"For a uniform grid $t_n = n \\Delta t$ with $n = 0, \\dots, N$, the \u03b8-scheme\n",
"For a uniform grid $t_n = n \\Delta t$ with $n = 0, \\dots, N$, the θ-scheme\n",
"\n",
"$$\n",
"Y_n - Y_{n+1} \\;=\\; \\big[\\theta f(t_n, Y_n, Z_n) + (1 - \\theta) f(t_{n+1}, Y_{n+1}, Z_{n+1})\\big]\\, \\Delta t\n",
@@ -111,9 +111,9 @@
"$$\n",
"\n",
"is implicit in $Y_n$ for $\\theta > 0$. The choice $\\theta = 1/2$ yields the\n",
"Crank\u2013Nicolson rule, of order $\\mathcal{O}(\\Delta t^2)$ for ODE-like linear\n",
"CrankNicolson rule, of order $\\mathcal{O}(\\Delta t^2)$ for ODE-like linear\n",
"problems. For the discretisation of $Z$, the primitive uses the **discrete\n",
"Clark\u2013Ocone identity** $Z_n = \\mathbb{E}[Y_{n+1} \\Delta W_n / \\Delta t \\mid\n",
"ClarkOcone identity** $Z_n = \\mathbb{E}[Y_{n+1} \\Delta W_n / \\Delta t \\mid\n",
"\\mathcal{F}_n]$.\n"
]
},
@@ -121,7 +121,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Cell \u2014 verification against the analytic exponential\n",
"## 2. Cell verification against the analytic exponential\n",
"\n",
"We solve $- dY = a Y\\, dt - Z\\, dW$, $Y_T = 1$, with $a = -0.3$, $T = 1$,\n",
"$N = 200$, $\\theta = 1/2$. The analytic solution is $Y_t = e^{a (T - t)}$;\n",
@@ -130,8 +130,8 @@
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"rho, T, n = 0.3, 1.0, 200\n",
@@ -150,7 +150,7 @@
"axes[0].plot(tg, yg, lw=2, label=r'$\\theta$-scheme')\n",
"axes[0].plot(tg, analytic, '--', lw=1.5, label=r'$\\xi e^{a(T-t)}$')\n",
"axes[0].set_xlabel('t'); axes[0].set_ylabel(r'$Y_t$')\n",
"axes[0].set_title('Linear BSDE \u2014 Crank\u2013Nicolson vs analytic')\n",
"axes[0].set_title('Linear BSDE — CrankNicolson vs analytic')\n",
"axes[0].legend()\n",
"axes[1].semilogy(tg, np.abs(yg - analytic) + 1e-16)\n",
"axes[1].set_xlabel('t'); axes[1].set_ylabel('|error|')\n",
@@ -165,15 +165,15 @@
"source": [
"## 3. Convergence study\n",
"\n",
"For Crank\u2013Nicolson on the linear test problem the global error obeys\n",
"$|Y_0^{(N)} - e^{-\\rho T}| = \\mathcal{O}(\\Delta t^2)$, which on a $\\log$\u2013$\\log$\n",
"For CrankNicolson on the linear test problem the global error obeys\n",
"$|Y_0^{(N)} - e^{-\\rho T}| = \\mathcal{O}(\\Delta t^2)$, which on a $\\log$$\\log$\n",
"plot translates into a slope of $-2$ versus $N$.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ns = [25, 50, 100, 200, 400, 800]\n",
@@ -186,7 +186,7 @@
"ax.loglog(ns, errs, 'o-', lw=2, label='empirical max error')\n",
"ax.loglog(ns, [errs[0] * (ns[0] / n)**2 for n in ns], ':', label=r'reference slope $-2$')\n",
"ax.set_xlabel('number of steps $N$'); ax.set_ylabel(r'$|Y_0 - e^{-\\rho T}|$')\n",
"ax.set_title('Crank\u2013Nicolson convergence')\n",
"ax.set_title('CrankNicolson convergence')\n",
"ax.legend(); plt.tight_layout(); plt.show()\n",
"\n",
"slope = -np.polyfit(np.log(ns), np.log(errs), 1)[0]\n",
@@ -199,7 +199,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Feynman\u2013Kac bridge to a semi-linear PDE\n",
"## 4. FeynmanKac bridge to a semi-linear PDE\n",
"\n",
"For an SDE $dX_t = \\mu\\, dt + \\sigma\\, dW_t$, $X_0 = x$, define the value\n",
"function\n",
@@ -208,7 +208,7 @@
"u(t, x) \\;:=\\; \\mathbb{E}\\!\\left[ \\xi(X_T) + \\int_t^T f(s, u(s, X_s), \\sigma\\, \\partial_x u(s, X_s))\\, ds \\;\\middle|\\; X_t = x \\right].\n",
"$$\n",
"\n",
"The **non-linear Feynman\u2013Kac formula** of Pardoux\u2013Peng (1992) states that\n",
"The **non-linear FeynmanKac formula** of PardouxPeng (1992) states that\n",
"$u$ is the classical solution of the semi-linear parabolic PDE\n",
"\n",
"$$\n",
@@ -217,8 +217,8 @@
"\n",
"and the BSDE pair $(Y_t, Z_t) = (u(t, X_t), \\sigma\\, \\partial_x u(t, X_t))$\n",
"solves the corresponding equation. This bridge converts a non-linear PDE\n",
"problem into a stochastic one \u2014 the foundation of probabilistic numerics\n",
"and of deep BSDE methods (E\u2013Han\u2013Jentzen 2017).\n",
"problem into a stochastic one the foundation of probabilistic numerics\n",
"and of deep BSDE methods (EHanJentzen 2017).\n",
"\n",
"In the linear-deterministic special case $\\mu = 0$, $\\sigma \\equiv 1$,\n",
"$f(y) = -\\rho y$, $\\xi$ deterministic the BSDE collapses to the\n",
@@ -227,8 +227,8 @@
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Feynman--Kac sanity check: discount of a deterministic constant terminal.\n",
@@ -241,7 +241,7 @@
" ax.plot(tg, res['y'], lw=2, label=f'xi = {xi}')\n",
" ax.plot(tg, xi * np.exp(-rho * (T - tg)), '--', alpha=0.6)\n",
"ax.set_xlabel('t'); ax.set_ylabel(r'$Y_t = \\xi e^{-\\rho(T-t)}$')\n",
"ax.set_title('Linearity check \u2014 multiple terminal payoffs')\n",
"ax.set_title('Linearity check multiple terminal payoffs')\n",
"ax.legend(); plt.tight_layout(); plt.show()\n",
"print('All four trajectories overlay their analytical exponentials.')\n"
]
@@ -250,7 +250,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Concrete application \u2014 discounting a Brownian terminal\n",
"## 5. Concrete application discounting a Brownian terminal\n",
"\n",
"### Set-up\n",
"\n",
@@ -270,8 +270,8 @@
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"M = 10_000\n",
@@ -310,9 +310,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. Concrete application \u2014 heat-equation expectation\n",
"## 6. Concrete application heat-equation expectation\n",
"\n",
"Let $X_t = x + W_t$ and $\\xi(x) = x^2$. By Feynman\u2013Kac (linear case),\n",
"Let $X_t = x + W_t$ and $\\xi(x) = x^2$. By FeynmanKac (linear case),\n",
"\n",
"$$\n",
"u(t, x) \\;:=\\; \\mathbb{E}[\\xi(X_T) \\mid X_t = x] \\;=\\; x^2 + (T - t),\n",
@@ -333,8 +333,8 @@
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ts = np.linspace(0, T, 80)\n",
@@ -347,7 +347,7 @@
"ax.scatter([0], [T * np.exp(-rho * T)], color='red', zorder=5,\n",
" label=rf'$Y_0 = T e^{{-\\rho T}} = {T * np.exp(-rho*T):.3f}$')\n",
"ax.set_xlabel('t'); ax.set_ylabel('value at $x = 0$')\n",
"ax.set_title(r'Heat equation expectation $\\xi(x) = x^2$ \u2014 Feynman--Kac')\n",
"ax.set_title(r'Heat equation expectation $\\xi(x) = x^2$ Feynman--Kac')\n",
"ax.legend(); plt.tight_layout(); plt.show()\n",
"print('BSDE primitive matches the deterministic Feynman--Kac value.')\n"
]
@@ -356,26 +356,26 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary \u2014 verification against analytic ground truth\n",
"## Summary verification against analytic ground truth\n",
"\n",
"| Test | Expected | Observed |\n",
"|------|----------|----------|\n",
"| Linear BSDE (deterministic) | $Y_t = e^{a(T-t)}$ | max error $< 10^{-3}$ |\n",
"| Crank\u2013Nicolson order | slope $-2$ | $\\approx -2$ |\n",
"| Linearity in $\\xi$ | overlay of curves | \u2713 |\n",
"| CrankNicolson order | slope $-2$ | $\\approx -2$ |\n",
"| Linearity in $\\xi$ | overlay of curves | |\n",
"| Monte Carlo consistency | $Y_0 = T e^{-\\rho T}$ | < 1% rel. error |\n",
"| Feynman\u2013Kac heat equation | $u(t, 0) = T - t$ | \u2713 |\n",
"| FeynmanKac heat equation | $u(t, 0) = T - t$ | |\n",
"\n",
"The `linear_bsde_constant_coeffs` primitive is therefore validated as the\n",
"exact probabilistic discretisation of the linear semi-group governing the\n",
"heat equation with a constant linear forcing \u2014 the foundational case of\n",
"the Pardoux\u2013Peng theory.\n"
"heat equation with a constant linear forcing the foundational case of\n",
"the PardouxPeng theory.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('--- per-test residuals ---')\n",
@@ -387,20 +387,21 @@
],
"metadata": {
"kernelspec": {
"name": "rhftlab",
"display_name": "Python 3 (rhftlab)",
"language": "python"
"display_name": "rhftlab",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11",
"mimetype": "text/x-python",
"file_extension": ".py",
"pygments_lexer": "ipython3",
"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,