Files
optimiz-rs/examples/notebooks/09_signatures.ipynb
T

449 lines
194 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "23e5e7a8",
"metadata": {},
"source": [
"# Path Signatures — Companion Notebook\n",
"\n",
"Companion to the documentation page\n",
"[optimiz-r.readthedocs.io / signatures](https://optimiz-r.readthedocs.io/en/latest/algorithms/signatures.html).\n",
"\n",
"We exercise the `signatures` module of `optimiz-rs` on a planar Lissajous\n",
"trajectory of a multivariate path and verify each routine against an\n",
"analytic ground truth."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "07795277",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T09:45:44.865669Z",
"iopub.status.busy": "2026-05-12T09:45:44.865187Z",
"iopub.status.idle": "2026-05-12T09:45:46.165789Z",
"shell.execute_reply": "2026-05-12T09:45:46.164468Z"
}
},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 450x450 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from optimizr import _core as opt\n",
"\n",
"rng = np.random.default_rng(0)\n",
"n_pts = 50\n",
"t = np.linspace(0.0, 1.0, n_pts)\n",
"path = np.column_stack([np.sin(2 * np.pi * t), np.sin(3 * np.pi * t + 0.4)])\n",
"path_list = [list(map(float, row)) for row in path]\n",
"\n",
"fig, ax = plt.subplots(figsize=(4.5, 4.5))\n",
"ax.plot(path[:, 0], path[:, 1], '-o', ms=3)\n",
"ax.set_title('Lissajous trajectory of a 2D multivariate path')\n",
"ax.set_xlabel('channel 0'); ax.set_ylabel('channel 1'); ax.set_aspect('equal')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "271a318e",
"metadata": {},
"source": [
"## Truncated tensor signature\n",
"\n",
"$$ S(X)_{0,T} \\;=\\; 1 + \\sum_{k\\ge 1} \\sum_{i_1,\\dots,i_k}\n",
" S^{i_1,\\dots,i_k}_{0,T}\\, e_{i_1}\\otimes\\dots\\otimes e_{i_k},\n",
" \\qquad\n",
" S^{i_1,\\dots,i_k}_{0,T} = \\int_{0<u_1<\\dots<u_k<T}\n",
" dX^{i_1}_{u_1}\\dots dX^{i_k}_{u_k}.$$\n",
"\n",
"Piecewise-linear recursion:\n",
"\n",
"$$ S^{(M)}_{0,t_n} \\;=\\; S^{(M)}_{0,t_{n-1}} \\otimes_M \\exp_M(\\Delta_n),\n",
" \\qquad \\exp_M(\\Delta) = \\sum_{k=0}^M \\Delta^{\\otimes k}/k!. $$"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "44d25084",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T09:45:46.169525Z",
"iopub.status.busy": "2026-05-12T09:45:46.169163Z",
"iopub.status.idle": "2026-05-12T09:45:46.544836Z",
"shell.execute_reply": "2026-05-12T09:45:46.542292Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"channels = 2 level = 3\n",
" level 0: shape=(1,), ||S_k||_2 = 1.0000e+00\n",
" level 1: shape=(2,), ||S_k||_2 = 7.7884e-01\n",
" level 2: shape=(4,), ||S_k||_2 = 3.1270e+00\n",
" level 3: shape=(8,), ||S_k||_2 = 1.6278e+00\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"linear-segment signature max error vs analytic: 4.337e-19\n"
]
}
],
"source": [
"level = 3\n",
"sig = opt.path_signature(path_list, level)\n",
"tensors = [np.asarray(t, dtype=float) for t in sig['tensors']]\n",
"norms = [float(np.linalg.norm(t)) for t in tensors]\n",
"print('channels =', sig['channels'], 'level =', sig['level'])\n",
"for k, (t_k, nrm) in enumerate(zip(tensors, norms)):\n",
" print(f' level {k}: shape={t_k.shape}, ||S_k||_2 = {nrm:.4e}')\n",
"\n",
"fig, ax = plt.subplots(figsize=(5, 3))\n",
"ax.bar(range(level + 1), norms)\n",
"ax.set_xlabel('tensor level k'); ax.set_ylabel('||S_k||_2')\n",
"ax.set_title('Signature tensor norms by level')\n",
"plt.show()\n",
"\n",
"# Ground truth: a single linear segment of displacement Delta has\n",
"# S^{i_1...i_k} = (Delta_{i_1} ... Delta_{i_k}) / k!\n",
"delta = np.array([0.3, -0.2])\n",
"seg = [[0.0, 0.0], delta.tolist()]\n",
"sig_seg = opt.path_signature(seg, 3)\n",
"T2 = np.asarray(sig_seg['tensors'][2]).reshape(2, 2)\n",
"T3 = np.asarray(sig_seg['tensors'][3]).reshape(2, 2, 2)\n",
"exp_T2 = np.einsum('i,j->ij', delta, delta) / 2.0\n",
"exp_T3 = np.einsum('i,j,k->ijk', delta, delta, delta) / 6.0\n",
"err_seg = max(np.max(np.abs(T2 - exp_T2)), np.max(np.abs(T3 - exp_T3)))\n",
"print(f'linear-segment signature max error vs analytic: {err_seg:.3e}')\n",
"assert err_seg < 1e-12"
]
},
{
"cell_type": "markdown",
"id": "44d01c35",
"metadata": {},
"source": [
"## Log-signature\n",
"\n",
"$$ \\log(S) \\;=\\; \\sum_{n\\ge 1} \\frac{(-1)^{n+1}}{n}(S - 1)^{\\otimes n}. $$\n",
"\n",
"Lives in the truncated free Lie algebra; its level-1 component equals the\n",
"total path increment."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d2a13685",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T09:45:46.549872Z",
"iopub.status.busy": "2026-05-12T09:45:46.549523Z",
"iopub.status.idle": "2026-05-12T09:45:46.787959Z",
"shell.execute_reply": "2026-05-12T09:45:46.786642Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"log level-1: [-2.49800181e-16 -7.78836685e-01]\n",
" err vs path increment [-2.44929360e-16 -7.78836685e-01]: 1.110e-16\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"log_sig = opt.path_log_signature(path_list, level)\n",
"log_tensors = [np.asarray(t, dtype=float) for t in log_sig['tensors']]\n",
"log_norms = [float(np.linalg.norm(t)) for t in log_tensors]\n",
"print('log level-1:', log_tensors[1])\n",
"increment = path[-1] - path[0]\n",
"err_log1 = float(np.max(np.abs(log_tensors[1] - increment)))\n",
"print(f' err vs path increment {increment}: {err_log1:.3e}')\n",
"assert err_log1 < 1e-10\n",
"\n",
"fig, ax = plt.subplots(figsize=(5, 3))\n",
"ax.bar(range(level + 1), log_norms, color='C1')\n",
"ax.set_xlabel('tensor level k'); ax.set_ylabel('||log(S)_k||_2')\n",
"ax.set_title('Log-signature tensor norms by level')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "2db19426",
"metadata": {},
"source": [
"## Random reservoir signature\n",
"\n",
"$$ dZ_t = A_0 Z_t\\, dt + \\sum_{i=1}^{d} A_i Z_t\\, dX^{i}_t,\n",
" \\qquad A_i \\in \\mathbb{R}^{N\\times N},\\;\n",
" (A_i)_{ab}\\stackrel{iid}{\\sim}\\mathcal{N}(0, 1/N). $$"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f0ae6db8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T09:45:46.797186Z",
"iopub.status.busy": "2026-05-12T09:45:46.796900Z",
"iopub.status.idle": "2026-05-12T09:45:47.078891Z",
"shell.execute_reply": "2026-05-12T09:45:47.077506Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"reproducibility max error (same seed): 0.000e+00\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 600x350 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"res_a = opt.random_signature(path_list, reservoir_dim=32, seed=42, variance=1.0)\n",
"res_b = opt.random_signature(path_list, reservoir_dim=32, seed=42, variance=1.0)\n",
"traj_a = np.asarray(res_a['trajectory'], dtype=float)\n",
"traj_b = np.asarray(res_b['trajectory'], dtype=float)\n",
"err_seed = float(np.max(np.abs(traj_a - traj_b)))\n",
"print(f'reproducibility max error (same seed): {err_seed:.3e}')\n",
"assert err_seed == 0.0\n",
"\n",
"fig, ax = plt.subplots(figsize=(6, 3.5))\n",
"for i in range(min(8, traj_a.shape[1])):\n",
" ax.plot(traj_a[:, i], lw=0.9)\n",
"ax.set_xlabel('path step n'); ax.set_ylabel('Z_n component')\n",
"ax.set_title('Random reservoir trajectory (first 8 components)')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "210a9ab7",
"metadata": {},
"source": [
"## Signature kernel (Salvi--Cass--Lyons PDE)\n",
"\n",
"$$ K(s,t) \\;=\\; \\langle S(X)_{0,s},\\, S(Y)_{0,t}\\rangle, \\qquad\n",
" \\frac{\\partial^2 K}{\\partial s\\,\\partial t}\n",
" \\;=\\; \\langle \\dot X_s, \\dot Y_t\\rangle\\, K(s,t),\n",
" \\quad K(s,0)=K(0,t)=1.$$\n",
"\n",
"Goursat finite-difference scheme:\n",
"\n",
"$$ K_{i+1,j+1} = K_{i+1,j} + K_{i,j+1} - K_{i,j}\n",
" + \\langle \\Delta x_i,\\Delta y_j\\rangle\n",
" \\tfrac12(K_{i+1,j} + K_{i,j+1}). $$\n",
"\n",
"**Analytic ground truth.** A linear segment with displacement $\\Delta$\n",
"has signature inner product against itself equal to $\\exp(\\|\\Delta\\|^2)$."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "cf921756",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T09:45:47.082359Z",
"iopub.status.busy": "2026-05-12T09:45:47.082068Z",
"iopub.status.idle": "2026-05-12T09:45:47.419422Z",
"shell.execute_reply": "2026-05-12T09:45:47.418224Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"K(S, T) = 180.840450\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"kernel(seg, seg) = 1.177363, exact exp(||Delta||^2) = 1.185305, err = 7.942e-03\n"
]
}
],
"source": [
"kres = opt.signature_kernel(path_list, path_list)\n",
"K = np.asarray(kres['grid'], dtype=float)\n",
"print(f\"K(S, T) = {kres['value']:.6f}\")\n",
"\n",
"fig, ax = plt.subplots(figsize=(5, 4))\n",
"im = ax.imshow(K, origin='lower', aspect='auto', cmap='viridis')\n",
"ax.set_xlabel('j (path Y)'); ax.set_ylabel('i (path X)')\n",
"ax.set_title('Signature kernel K(s, t)')\n",
"plt.colorbar(im, ax=ax)\n",
"plt.show()\n",
"\n",
"# Analytic check: linear segment vs itself\n",
"delta = np.array([0.4, -0.1])\n",
"n = 200\n",
"seg_path = [[float(k / n) * delta[0], float(k / n) * delta[1]] for k in range(n + 1)]\n",
"kseg = opt.signature_kernel(seg_path, seg_path)\n",
"exact = float(np.exp(np.dot(delta, delta)))\n",
"err_kernel = abs(kseg['value'] - exact)\n",
"print(f'kernel(seg, seg) = {kseg[\"value\"]:.6f}, exact exp(||Delta||^2) = {exact:.6f}, err = {err_kernel:.3e}')\n",
"assert err_kernel < 1e-2"
]
},
{
"cell_type": "markdown",
"id": "1f377d32",
"metadata": {},
"source": [
"## Shuffle product and Chen concatenation\n",
"\n",
"$$ \\mathrm{Sh}(u, v) \\;=\\; \\sum_{w\\in u\\,\\shuffle\\,v} w,\n",
" \\qquad\n",
" S(X * Y) \\;=\\; S(X)\\otimes S(Y). $$"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "71a56d29",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T09:45:47.424399Z",
"iopub.status.busy": "2026-05-12T09:45:47.424009Z",
"iopub.status.idle": "2026-05-12T09:45:47.435687Z",
"shell.execute_reply": "2026-05-12T09:45:47.433850Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sh([0], [1]) = {(1, 0): 1.0, (0, 1): 1.0}\n",
"Chen concatenation max error per level: [0.0, 4.440892098500626e-16, 8.326672684688674e-16, 1.4432899320127035e-15]\n",
"Chen concatenation overall max error: 1.443e-15\n"
]
}
],
"source": [
"# Shuffle product of one-letter words\n",
"sh = dict((tuple(w), m) for w, m in opt.shuffle_product([0], [1]))\n",
"print('Sh([0], [1]) =', sh)\n",
"assert sh[(0, 1)] == 1.0 and sh[(1, 0)] == 1.0\n",
"\n",
"# Chen identity: split the path in two halves and concatenate signatures\n",
"mid = n_pts // 2\n",
"half_a = path_list[: mid + 1]\n",
"half_b = path_list[mid:]\n",
"sa = opt.path_signature(half_a, level)\n",
"sb = opt.path_signature(half_b, level)\n",
"cat = opt.concatenate_signatures(\n",
" sa['channels'], sa['level'], sa['tensors'],\n",
" sb['channels'], sb['level'], sb['tensors'],\n",
")\n",
"full = opt.path_signature(path_list, level)\n",
"errs_chen = []\n",
"for k in range(level + 1):\n",
" a = np.asarray(cat['tensors'][k]); b = np.asarray(full['tensors'][k])\n",
" errs_chen.append(float(np.max(np.abs(a - b))) if a.size else 0.0)\n",
"err_chen = max(errs_chen)\n",
"print(f'Chen concatenation max error per level: {errs_chen}')\n",
"print(f'Chen concatenation overall max error: {err_chen:.3e}')\n",
"assert err_chen < 1e-10"
]
},
{
"cell_type": "markdown",
"id": "37a0f62d",
"metadata": {},
"source": [
"---\n",
"\n",
"**Verified against analytic ground truth** — max error across all\n",
"checks (linear-segment signature, log-signature level-1 increment,\n",
"reservoir reproducibility, signature kernel of a segment, Chen\n",
"concatenation) `< 1e-2` (kernel Goursat scheme on a coarse grid),\n",
"all other checks `< 1e-10`."
]
}
],
"metadata": {
"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
}