272 lines
150 KiB
Plaintext
272 lines
150 KiB
Plaintext
{
|
||||
|
|
"cells": [
|
|||
|
|
{
|
|||
|
|
"cell_type": "markdown",
|
|||
|
|
"id": "1c737c8c",
|
|||
|
|
"metadata": {},
|
|||
|
|
"source": [
|
|||
|
|
"# 12 — Stochastic control"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 1,
|
|||
|
|
"id": "2538d7df",
|
|||
|
|
"metadata": {
|
|||
|
|
"execution": {
|
|||
|
|
"iopub.execute_input": "2026-05-12T10:16:06.244193Z",
|
|||
|
|
"iopub.status.busy": "2026-05-12T10:16:06.243690Z",
|
|||
|
|
"iopub.status.idle": "2026-05-12T10:16:07.005639Z",
|
|||
|
|
"shell.execute_reply": "2026-05-12T10:16:07.003895Z"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"outputs": [],
|
|||
|
|
"source": [
|
|||
|
|
"import numpy as np\n",
|
|||
|
|
"import matplotlib.pyplot as plt\n",
|
|||
|
|
"from optimizr import _core as opt\n",
|
|||
|
|
"plt.rcParams['figure.figsize'] = (7, 4)\n",
|
|||
|
|
"plt.rcParams['figure.dpi'] = 110\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "markdown",
|
|||
|
|
"id": "24eca7d5",
|
|||
|
|
"metadata": {},
|
|||
|
|
"source": [
|
|||
|
|
"## Optimal switching (Snell envelope)\n",
|
|||
|
|
"\n",
|
|||
|
|
"Two modes; only mode 1 pays a unit reward. Free switching should give `V_0(0) = N - 1` and `V_0(1) = N`."
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 2,
|
|||
|
|
"id": "7add7040",
|
|||
|
|
"metadata": {
|
|||
|
|
"execution": {
|
|||
|
|
"iopub.execute_input": "2026-05-12T10:16:07.009088Z",
|
|||
|
|
"iopub.status.busy": "2026-05-12T10:16:07.008716Z",
|
|||
|
|
"iopub.status.idle": "2026-05-12T10:16:07.018949Z",
|
|||
|
|
"shell.execute_reply": "2026-05-12T10:16:07.015673Z"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"name": "stdout",
|
|||
|
|
"output_type": "stream",
|
|||
|
|
"text": [
|
|||
|
|
"V_0 = [4. 5.]\n",
|
|||
|
|
"Optimal next mode at each (k, i):\n",
|
|||
|
|
"[[1 1]\n",
|
|||
|
|
" [1 1]\n",
|
|||
|
|
" [1 1]\n",
|
|||
|
|
" [1 1]\n",
|
|||
|
|
" [0 0]\n",
|
|||
|
|
" [0 1]]\n"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"n_steps, n_modes = 5, 2\n",
|
|||
|
|
"stage = np.zeros((n_steps, n_modes)); stage[:, 1] = 1.0\n",
|
|||
|
|
"cost = [0.0] * (n_modes * n_modes)\n",
|
|||
|
|
"res = opt.optimal_switching_dp(stage.flatten().tolist(),\n",
|
|||
|
|
" [0.0] * n_modes, cost,\n",
|
|||
|
|
" n_modes, n_steps)\n",
|
|||
|
|
"value = np.array(res['value']).reshape(n_steps + 1, n_modes)\n",
|
|||
|
|
"policy = np.array(res['policy']).reshape(n_steps + 1, n_modes)\n",
|
|||
|
|
"print('V_0 =', value[0])\n",
|
|||
|
|
"print('Optimal next mode at each (k, i):'); print(policy)\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 3,
|
|||
|
|
"id": "1bf3fb78",
|
|||
|
|
"metadata": {
|
|||
|
|
"execution": {
|
|||
|
|
"iopub.execute_input": "2026-05-12T10:16:07.022668Z",
|
|||
|
|
"iopub.status.busy": "2026-05-12T10:16:07.022382Z",
|
|||
|
|
"iopub.status.idle": "2026-05-12T10:16:07.321635Z",
|
|||
|
|
"shell.execute_reply": "2026-05-12T10:16:07.320449Z"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"data": {
|
|||
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAvYAAAGtCAYAAAB9QDCJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AABJPUlEQVR4nO3deXhU5d3/8c/MZA+EhCRgWMOOimxCEJWyCIJllU2FyuLDVhSUqIiiBQRRH5sIxQWrFbTIUhDBqiBCiUKVVrQoIFRRAgEjJCELSxLIzPn9wS/zMCaEJIQ5w5n367q8ZO6zfc/ck8knZ+5zj80wDEMAAAAArmp2swsAAAAAcPkI9gAAAIAFEOwBAAAACyDYAwAAABZAsAcAAAAsgGAPAAAAWADBHgAAALAAgj0AAABgAQR7AAAAwAII9gAAAIAFEOwBmKpbt26Kj4/3aBszZoxsNps5BXmB1c+vsjIzMzVq1CjVqVNHNptN3bp1M7skU5T2M1EVli5dKpvNppSUlHKtz+sUuPoEmF0AAHMcP35cycnJ+vDDD3Xo0CEZhqHY2Fi1adNG/fr10//8z/+YXSL8zMMPP6xVq1Zp5syZaty4sWrXrm12ST5j3bp12rVrl2bPnm12KQB8GMEe8EOHDx9WQkKCMjMzNXToUI0fP15BQUH66aeftH37di1YsIBgD6/75JNP1Lt3b/3hD38wuxRTbdq0SYZheLStW7dOb731lleD/euvv67Fixd77XgALh/BHvBDL7zwgo4dO6YFCxbowQcfLLH8l19+MaEq+LtffvlFNWvWLNe6eXl5ioiIuMIVmSMoKMjsEiRJgYGBCgwMNLsMABXAGHvAD/3www+SpNtuu63U5ddcc43H4+Ixv7/88ovuvfdeRUdHKzQ0VL/5zW+0c+fOUvfx7rvvqmvXroqIiFBoaKjatWunN954o2pP5P/78ccfNWbMGNWpU0dBQUGqV6+eJk+erMzMTI/1Zs+eLZvNpu+//15/+MMf1LBhQwUHB+vaa6/VO++847Hurbfeqho1aig/P7/E8dLS0uRwOHTvvfd6tG/dulV33HGHoqKi3Pt9/vnn5XQ6y3Ue+/fv1913363atWsrODhYjRs31iOPPKK8vDyP9YrHSm/evFnz5s1To0aNFBwcrBYtWmjRokWX9RyZoXgst2EYeuutt2Sz2WSz2bR06VJJks1m05gxY5SSkqJu3bopIiJCbdq0cW9fkXM7efKkZs6cqRYtWig4OFg1a9bUoEGD9O2335ar1sLCQs2dO1fXXXedwsPDFRERoRYtWui+++5zv1YmTJiggIAA5eTkuLc7fvy47Ha7bDab0tLS3O2nT59WUFCQ7rvvPnfbr8fYx8fH66233nI/F79+fiTp1KlTmj17tlq1aqXQ0FBFRUWpY8eOeumll0qcg2EYWrBggZo3b67g4GA1atRIycnJJdYrbYx9cVteXp6mTJmiuLg4BQcHq3379vr4449LPdaLL76oZs2aKTg4WE2aNNGzzz6rLVu2lDgHAJePK/aAH2rSpIkkacmSJXr++ecVEHDpt4LTp0+rS5cuuvHGGzV37lwdO3ZML774ou644w799NNPql69unvdWbNm6emnn1b37t01a9YshYaG6uOPP9b48eN14MABPffcc1V2Lrt27VK3bt0UFham++67Tw0bNtQPP/ygV199VVu2bNG///1v1ahRw2Ob0aNHy2azaerUqbLb7XrllVf0u9/9Tk2aNNFNN90k6XyAGT9+vNauXauRI0d6bP/WW2/J5XJpzJgx7rY333xT48aNU7t27TRjxgxFRkbqn//8px5//HH95z//0cqVKy95Hr/5zW9UVFSkyZMnq3Hjxtq+fbuSkpK0ZcsW/fOf/1RYWJjHNjNmzFBubq7Gjx+v4OBgrVixQlOnTtWxY8c0b968y3qOvGnixInq2bOn7r33XnXp0kUTJkyQJN18883udXbu3Kk1a9bovvvu04gRI3Ty5ElJFTu3vLw83XrrrTpw4IBGjx6tNm3aKDs7W6+//ro6d+6sbdu2qX379mXW+sADD+iNN97QyJEjNXXqVEnSwYMH9cEHH+j06dMKDQ3Vbbfdptdff11bt27VnXfeKUnavHmzDMOQ3W7X5s2bNXbsWEnSp59+qnPnzqlnz54XPeaCBQuUnJysbdu26a9//au7vfj5yc3NVZcuXbR79271799f9913nwIDA7V7926tXbtWDzzwgMf+nnjiCeXl5Wns2LGqVq2a3n77bT388MOqU6eO7r777kt3mKTevXsrMjJSjz/+uM6cOaMFCxZowIAB+uGHH9SgQQP3etOnT9cf//hHJSQk6Pe//70KCwu1ZMkSvffee+U6DoAKMgD4nR9//NGoUaOGIcmoVauWMWTIEOP55583tm/fbjidzhLrd+3a1ZBkzJ8/36N9xYoVhiTjtddec7d9/fXXhs1mM6ZOnVpiPw888IBht9uNH3/80WPfDRs29Fhv9OjRRnnfntq2bWs0atTIyMrK8mj/17/+ZTgcDmP27NnutlmzZhmSjDvuuMPjPA8fPmwEBgYa99xzj7stNzfXCAsLM3r27FnimE2bNjUaNmxouFwuwzAMIz093QgJCTEGDRrkbiv2xz/+0ZBkpKSklHl+Xbp0MWw2m7F9+3aP9jlz5hiSjLlz57rblixZYkgy6tWrZ2RnZ7vbCwoKjISEBMNutxsHDhyo1HNkJknG6NGjS22XZGzYsKHEsoqc20MPPWQEBgYaO3bs8Fg3OzvbqFevntGtW7dL1hgVFWX06dOnzHWOHz9u2Gw2Y/Lkye62sWPHGtdff73Rvn17Y8SIEe72adOmGZKM9PR0d1tFfybuv/9+Q5KRlJRUYtmFr/Pi103r1q2NgoICd/upU6eM6Ohoo3Pnzpc8ZnHbhAkTPNq/+OILQ5Lx+OOPu9v++9//GjabzbjllluMs2fPuttzcnKM+vXrG5KMJUuWlHpOACqHoTiAH2rcuLG++eYbTZ06VeHh4Xr33Xf12GOP6dZbb1XTpk21adOmEtvY7XZNmzbNo61Xr16SpO+//97d9s4778gwDP3P//yPMjMzPf4bMGCAXC6XNm/eXCXnsWfPHu3atUt33323XC6Xx7EaN26spk2bljo8YNq0abLb/+/tr379+mrRooXHeURERGjw4MH6xz/+4TF0Yvv27Tpw4IBGjRrlHqawZs0aFRQUaNy4ccrKyvKoo1+/fpJUah3FMjIytG3bNvXq1Uu33HKLx7JHHnnE3Ue/NnnyZEVGRrofBwcH6+GHH5bL5dK6desu6znyNW3atFGfPn082ipyboZhaNmyZercubOaNGnisW5RUZFuv/12bdu2rdShVxeKjIzU3r179c0331x0ndjYWLVu3Vpbtmxxt23ZskU9e/ZUz5499Y9//MOj/frrry8x/K28XC6Xli9frsaNG+uhhx4qsfzC13mxBx54QMHBwe7H4eHh6ty5s8fr/1IeeeQRj8c33XSTqlWr5rGPdevWyTAMTZs2zWOsfo0aNfT73/++3McCUH4MxQH8VMOGDbVw4UItXLhQx48f1xdffKG//e1vWrFihe6880598803atq0qXv9OnXqKCQkxGMf0dHRkqSsrCx32759+yTJYwz0rx07dqxKzqH4WM8++6yeffbZUtdp3Lhxudqio6N16NAhj7axY8dq2bJlevvttzVz5kxJ/ze+ffTo0SXqKA7xpSnrnH/66SdJ0g033FBiWVhYmJo0aaIff/yxxLLrrrvuom0HDhzwqK2iz9GF8vPzlZubW+Y6l1KzZs3Luim0efPmJdoqcm7FIf6zzz5TbGzsRY+TmZmp+vXrX3T5woULde+996pt27Zq0KCBunTpot69e2vYsGEePx89e/ZUUlKSjhw5ovz8fB0+fFg9e/ZUcHCw/vd//1d79uxRrVq1tHv3bk2ZMqVcz8HF6s3Ozla3bt1KDfGludjr/8Kf46rYR/HrumXLliXWvfbaa8t9LADlR7AHoFq1amngwIEaOHCgGjRooOeee04rV67Uk08+6V7H4XBcdHvjgqn5XC6XJOmDDz7
|
|||
|
|
"text/plain": [
|
|||
|
|
"<Figure size 770x440 with 1 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"fig, ax = plt.subplots()\n",
|
|||
|
|
"ax.step(range(n_steps + 1), value[:, 0], where='post', label='V_k(mode 0)')\n",
|
|||
|
|
"ax.step(range(n_steps + 1), value[:, 1], where='post', label='V_k(mode 1)')\n",
|
|||
|
|
"ax.set_xlabel('k'); ax.set_ylabel('value'); ax.legend(); ax.grid(alpha=0.3)\n",
|
|||
|
|
"ax.set_title('Snell envelope — free switching')\n",
|
|||
|
|
"fig.tight_layout(); plt.show()\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "markdown",
|
|||
|
|
"id": "b439b794",
|
|||
|
|
"metadata": {},
|
|||
|
|
"source": [
|
|||
|
|
"## Pontryagin 1-D LQR\n",
|
|||
|
|
"\n",
|
|||
|
|
"Closed-form Riccati for $a=q=0$, $b=r=s_T=1$, $T=1$ is $P(t) = 1/(1 + (T - t))$, hence $P(0) = 0.5$."
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 4,
|
|||
|
|
"id": "6d27157f",
|
|||
|
|
"metadata": {
|
|||
|
|
"execution": {
|
|||
|
|
"iopub.execute_input": "2026-05-12T10:16:07.324766Z",
|
|||
|
|
"iopub.status.busy": "2026-05-12T10:16:07.324457Z",
|
|||
|
|
"iopub.status.idle": "2026-05-12T10:16:07.333897Z",
|
|||
|
|
"shell.execute_reply": "2026-05-12T10:16:07.331408Z"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"name": "stdout",
|
|||
|
|
"output_type": "stream",
|
|||
|
|
"text": [
|
|||
|
|
"P(0) = 0.499913334323088 analytic = 0.5\n",
|
|||
|
|
"cost = 0.5000000041705193\n"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"res = opt.pontryagin_lqr(a=0.0, b=1.0, q=0.0, r=1.0,\n",
|
|||
|
|
" s_terminal=1.0, x0=1.0,\n",
|
|||
|
|
" t_horizon=1.0, n_steps=2000)\n",
|
|||
|
|
"tg = np.array(res['time_grid'])\n",
|
|||
|
|
"P = np.array(res['riccati'])\n",
|
|||
|
|
"x = np.array(res['state']); u = np.array(res['control'])\n",
|
|||
|
|
"P_an = 1.0 / (1.0 + (1.0 - tg))\n",
|
|||
|
|
"print('P(0) =', P[0], ' analytic =', P_an[0])\n",
|
|||
|
|
"print('cost =', res['cost'])\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 5,
|
|||
|
|
"id": "0dc4a80a",
|
|||
|
|
"metadata": {
|
|||
|
|
"execution": {
|
|||
|
|
"iopub.execute_input": "2026-05-12T10:16:07.337718Z",
|
|||
|
|
"iopub.status.busy": "2026-05-12T10:16:07.337376Z",
|
|||
|
|
"iopub.status.idle": "2026-05-12T10:16:08.271548Z",
|
|||
|
|
"shell.execute_reply": "2026-05-12T10:16:08.269696Z"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"data": {
|
|||
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABYoAAAGcCAYAAAB3KJQUAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AADrz0lEQVR4nOzdd3gU1dvG8e+mF1JJ6ITeu/QmPTTpHaSjRJqCIGADRREUsYMKCAKiSEdAQBCUjlRpCb23EAKhpe68f/Bmf8RsIAmBDcn9ua5cmtkzZ595suzZeXbmHJNhGAYiIiIiIiIiIiIikmnZ2ToAEREREREREREREbEtFYpFREREREREREREMjkVikVEREREREREREQyORWKRURERERERERERDI5FYpFREREREREREREMjkVikVEREREREREREQyORWKRURERERERERERDI5FYpFREREREREREREMjkVikVEREREREREREQyORWKRURERERERERERDI5FYpFkmns2LGYTCZOnz5t61CSpVevXphMpifW/+zZs3F2dubMmTPJan/y5EmcnJz4+eefn1hMIiIiGV2DBg1o0qRJstuPGzeO7NmzExER8QSjEhFJH65du0aPHj3IlSsXJpOJunXr2iyWunXrkj9//gTb8ufPb9OYbBnD+++/j7+/P7du3UpW+02bNmEymdi2bdsTjuzxxcTEULx4cYKCghJsN5lM9OrV64k+95w5c3BxcXnidYrw8HD8/PyYMGFCsvdJ6WeW1MrIry1bUKFYMqWNGzdiMpkS/Li5uVGmTBnef/997t27Z+sQk+Xzzz9n1qxZj91PfFE5/sfe3h4/Pz8aN27M77//nqj9nTt3GD16NP379ydfvnyW7fv27WPs2LFWB6mCBQvSp08fRo0a9czkV0REnj2zZs3i888/f+x+Tp8+zdixY9m3b99j95VWlixZwoYNG/jwww8TbH/YMb/22muYzWbGjRv3FCIUEbGt119/nfnz5xMUFMScOXN46623bB2SAOfPn2fixImMHj0aDw8Py/aNGzcyduxYbty4kWif2rVr07hxY1577TUMw3iK0abc119/zZkzZ3jnnXfSpL9x48bh4uKSrMJnt27dKFSoECNGjEh2/9bqIe7u7pQtW5YPPviAyMjIRPuMHTsWR0dHhgwZkmj70qVLrT7Phx9+yJo1a1i5cmWyY0upjP7asglDJBPasGGDARjt27c35syZY8yZM8eYPHmyUa1aNQMwGjdunGifmJgY4969e4bZbLZBxNbly5fPqFOnjtXHoqOjjXv37iWrn549exqA8eWXXxpz5swxfvjhB2PkyJGGj4+PARjz5s1L0P7TTz81TCaTcerUqQTbZ86caQDGhg0brD7P0aNHDcD46quvkhWXiIhIStWpU8fIly/fY/cT/1lh5syZj91XWilbtqxRr169RNsfdcxvvvmm4ezsbFy7du0JRiciYns5c+Y0WrRoYeswDMOw/t78sPO3p8UWMQwePNjIkiWLcfv27QTbx4wZYwCJzivjrV271gCM33777SlEmTqRkZGGv7+/0bt370SPAUbPnj1T3Ge5cuWM5s2bJ7v9999/bwDGgQMHktXeWj1k0qRJRqVKlQzAaNKkSYL2ly5dMhwdHY333nsvUV+POsYaNWoYFStWTPaxpFRGfm3Ziq4olkytXLlyvPjii7z44osMHTqUzZs389xzz7FmzRp2796doK2DgwMuLi5PdDqHtOTo6IiLi0uK9mnTpg0vvvgivXv3ZsKECaxZswaADz74wNLGMAymTp1q9VaqRylSpAjVq1dn6tSpKdpPREQks9u0aRP//vtvqm5h7dmzJ1FRUcyYMSPtAxMRSUcuX76Mr6+vrcOQB9y+fZsff/yRDh064O7unqJ9GzRoQJ48eZgyZcoTiu7x/frrr4SGhqbZFBMnT55k//79tG3b9qHtHpxSqmPHjri4uKQ4Tw/WQ15//XW2bt1KuXLlWL16Nf/884+l3bRp04iNjaVnz54pOxjufwbZvXs3O3fuTPG+j5LRX1u2okKxyAPs7e2pV68eAMeOHUvwWFJzFN++fZuxY8dSunRpXF1d8fHxoXLlynz99dcJ2sXExPDZZ59RsWJF3N3d8fDwoGzZsowZM8bSxmw2M378eOrWrUvOnDlxcnIid+7c9OzZk7Nnz1ranT59GpPJxJkzZ/jrr78S3DISH19azFFcuXJlsmbNyvHjxy3bdu/ezfHjx2nevHmCtr169aJ3794A1KtXzxLPfwfM5s2bc/jwYQ4cOPBYsYmISOYSFRXFuHHjKFmyJO7u7nh6elKsWDH69OljmdLIZDLx119/cebMmQRj48aNGwEIDg5m4MCBlC5dGi8vL1xdXSlTpgyTJk0iLi7O8lxjx461fB7o3bu3pZ//zum4aNEi6tSpg6enJ66urlSoUIHp06cn63imT5+OyWRi6NChCbbfu3ePsmXL4uXlxYkTJyzb58+fD5Bo/H3UMQMULVqUwoULW/oQEclo4s99DMPgxx9/tLwXPjhN34YNG2jatCk+Pj44OztTokQJJk6cmOD9P96JEyfo1asXuXLlwsnJiTx58jBgwACuXbuWqO3x48dp06YNXl5eeHh4EBgYyP79+x8a7/79+wkMDMTDwwMvLy/atm2b4D0fkn9u+KBNmzbRqlUr/P39cXZ2JiAggK5duybq+78uX75MxYoV8fLyYt26dQ9t+7C1e6xdTLR69WoiIiISjV9169blvffeA6BAgQKWv9nYsWMtbezs7GjSpAlr1qyxOoVAejB//nw8PT2pVatWkm02bNhAzZo1cXd3x8/Pj169enH16lWrbRctWoS9vT2tWrUC/nfuP3bsWBYtWkSVKlVwc3OjZcuWln28vLyoVasWCxYseKypFBwdHWnYsCFAghrA/PnzKVWqVIJpJ+OnrwAS/Jv7bw0i/u+enM8gBw4cwM3NjapVqxITE5PgsX79+mEymViwYIFlW0Z/bdmKg60DEElv4gfRrFmzPrLtzZs3qV27NgcOHKBFixb06dMHR0dHDhw4wOLFixk0aBBwv0jctGlT1q9fT506dXj33Xfx9PTkyJEjLFiwwPImFh0dzcSJE2nbti3NmzfHy8uLf//9lx9++IH169fz77//4uvri7+/P3PmzGHo0KH4+fklmHvL398/zXIRGhpKeHg4OXPmtGzbsGEDANWqVUvQtn///jg7O/P999/z5ptvUqJECQAKFSqUoF2NGjUs/ZQpUybNYhURkYxt0KBBTJ8+nW7dulnmxzt16hQrVqzgzp07uLq6MmfOHD788EOuXbvGZ599Ztk3fkzauHEjGzZs4IUXXqBAgQJERkayatUqRowYwcmTJy1XlbRt25aYmBjGjx/Pyy+/TO3atQHInj27pc8xY8bw/vvvU69ePcaMGYOrqytr1qzhpZde4vjx449c7KVfv35s3LiRzz//nHr16llO+AYPHsyBAwf49ddfE4yhGzZsoHDhwok+nzzqmOPVqFGDuXPncuPGDby9vZOVcxGRZ0X//v1p2LAh3bt3p3bt2rz88svA/849fvjhB/r160eFChUYNWoU3t7ebNmyhdGjR7N3715++eUXS1/79u2jbt26uLm50adPH/Lly8exY8eYOnUq69evZ+fOnXh5eQFw7tw5atSowY0bNwgKCqJEiRJs376dunXrJnk+ef78ecv7/scff8yRI0f49ttv2bp1K7t37yZ37txA8s8N402fPp3+/fvj7+9Pv379KFCgAJcvX2b16tUcPHgw0XlZvMOHD9OsWTNiY2PZtGkTZcuWffw/yAOSOn9866238PX1ZcmSJXz22Wf4+fkBJHr+GjVqMH36dP7+++8ExdGkhIeHWy3+W+Pi4kKWLFmS1daauLg4Nm3aRJUqVbCzs34d5t69e1m4cCG9e/fmxRdfZOfOnfz444/s2LGDf/75J9HzL168mDp16iR6/SxbtozPP/+coKAgXnrppUQF4Ro1arBu3ToOHDjwWH/Do0ePAv+rK4SGhnLo0CH69euXoF2JEiWYM2dOon9z/5U7d24CAgI
|
|||
|
|
"text/plain": [
|
|||
|
|
"<Figure size 1430x440 with 3 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"fig, axes = plt.subplots(1, 3, figsize=(13, 4))\n",
|
|||
|
|
"axes[0].plot(tg, P, label='numeric'); axes[0].plot(tg, P_an, '--', label='analytic')\n",
|
|||
|
|
"axes[0].set_title('Riccati P(t)'); axes[0].set_xlabel('t'); axes[0].legend(); axes[0].grid(alpha=0.3)\n",
|
|||
|
|
"axes[1].plot(tg, x); axes[1].set_title('state x(t)'); axes[1].set_xlabel('t'); axes[1].grid(alpha=0.3)\n",
|
|||
|
|
"axes[2].plot(tg[:-1], u); axes[2].set_title('feedback u(t) = -(b/r) P(t) x(t)'); axes[2].set_xlabel('t'); axes[2].grid(alpha=0.3)\n",
|
|||
|
|
"fig.tight_layout(); plt.show()\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "markdown",
|
|||
|
|
"id": "6c10852e",
|
|||
|
|
"metadata": {},
|
|||
|
|
"source": [
|
|||
|
|
"## Two-sided intensity control\n",
|
|||
|
|
"\n",
|
|||
|
|
"Affine premium $δ_±(λ) = α_± + κ_± λ$. First-order condition: $\\lambda^*_\\pm = \\max(0, (α_\\pm - ΔV_\\pm) / (2 κ_\\pm))$."
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 6,
|
|||
|
|
"id": "61395265",
|
|||
|
|
"metadata": {
|
|||
|
|
"execution": {
|
|||
|
|
"iopub.execute_input": "2026-05-12T10:16:08.275395Z",
|
|||
|
|
"iopub.status.busy": "2026-05-12T10:16:08.274998Z",
|
|||
|
|
"iopub.status.idle": "2026-05-12T10:16:08.557818Z",
|
|||
|
|
"shell.execute_reply": "2026-05-12T10:16:08.555785Z"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"data": {
|
|||
|
|
"image/png": "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
|
|||
|
|
"text/plain": [
|
|||
|
|
"<Figure size 770x440 with 1 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"deltas = np.linspace(-2.0, 2.0, 41)\n",
|
|||
|
|
"lam_plus = []\n",
|
|||
|
|
"for dv in deltas:\n",
|
|||
|
|
" r = opt.two_sided_intensities(1.0, 1.0, 0.5, 0.5, dv, -dv)\n",
|
|||
|
|
" lam_plus.append(r['lambda_plus'])\n",
|
|||
|
|
"lam_plus = np.array(lam_plus)\n",
|
|||
|
|
"fig, ax = plt.subplots()\n",
|
|||
|
|
"ax.plot(deltas, lam_plus, lw=2)\n",
|
|||
|
|
"ax.set_xlabel('ΔV_+'); ax.set_ylabel('λ*_+')\n",
|
|||
|
|
"ax.set_title('Optimal upward intensity vs value-function gradient')\n",
|
|||
|
|
"ax.grid(alpha=0.3); fig.tight_layout(); plt.show()\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "markdown",
|
|||
|
|
"id": "09d2a422",
|
|||
|
|
"metadata": {},
|
|||
|
|
"source": [
|
|||
|
|
"**Verified:** switching `V_0` matches analytic recursion exactly; Pontryagin `P(0) = 0.4999` against analytic `0.5`."
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"metadata": {
|
|||
|
|
"kernelspec": {
|
|||
|
|
"display_name": "Python 3 (rhftlab)",
|
|||
|
|
"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.11.13"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"nbformat": 4,
|
|||
|
|
"nbformat_minor": 5
|
|||
|
|
}
|