Files
optimiz-rs/examples/notebooks/15_agent_based.ipynb
T

170 lines
139 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "96cf5e36",
"metadata": {},
"source": [
"# 15 — Agent-based dynamics"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "748b67d0",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T10:16:16.689328Z",
"iopub.status.busy": "2026-05-12T10:16:16.688638Z",
"iopub.status.idle": "2026-05-12T10:16:17.486313Z",
"shell.execute_reply": "2026-05-12T10:16:17.481693Z"
}
},
"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": "code",
"execution_count": 2,
"id": "f0a8728c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T10:16:17.493354Z",
"iopub.status.busy": "2026-05-12T10:16:17.492373Z",
"iopub.status.idle": "2026-05-12T10:16:17.580527Z",
"shell.execute_reply": "2026-05-12T10:16:17.578574Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"initial mean = 19.5\n",
"final mean = 19.44376167152161\n",
"final std = 0.14305254923151872\n"
]
}
],
"source": [
"init = np.arange(40.0).tolist()\n",
"init_mean = float(np.mean(init))\n",
"res = opt.consensus_dynamics(init, alpha=0.3, noise_sigma=0.1,\n",
" n_steps=80, seed=0)\n",
"n_t = res['n_steps']; n_a = res['n_agents']\n",
"S = np.array(res['states_flat']).reshape(n_t, n_a)\n",
"mean_traj = np.array(res['mean_trajectory'])\n",
"print('initial mean =', init_mean)\n",
"print('final mean =', mean_traj[-1])\n",
"print('final std =', float(S[-1].std()))\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e223948c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T10:16:17.587271Z",
"iopub.status.busy": "2026-05-12T10:16:17.586746Z",
"iopub.status.idle": "2026-05-12T10:16:18.114774Z",
"shell.execute_reply": "2026-05-12T10:16:18.113245Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAvYAAAGtCAYAAAB9QDCJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AADGH0lEQVR4nOzdd5xcZb348c9p08v2zSabQhpJgNBLEEjggkiRXkSRoiCIoD/BdkEpghUJ6hVBL5ZLuyBIFKQIV0LT0JGakLZJNslm++7s9FOe3x8zO+xuNiG7hMwC3/frta9kvnPmnOd8p33PM895jqaUUgghhBBCCCE+1PRyN0AIIYQQQgjx/klhL4QQQgghxEeAFPZCCCGEEEJ8BEhhL4QQQgghxEeAFPZCCCGEEEJ8BEhhL4QQQgghxEeAFPZCCCGEEEJ8BEhhL4QQQgghxEeAFPZCCCGEEEJ8BEhhL4QQQgghxEeAFPZCiO1iwYIFTJky5QNZ95QpU1iwYMEHsu4PSkdHB2eddRbjx49H07RS+0eyL2vWrEHTNK6++uoPrJ1CCCE+OqSwF2IHe/LJJ9E0bdCfZVlMmjSJ4447jieeeKLcTRTbwWWXXcY999zDhRdeyO23384VV1xR7iYJ8ZH29NNPc9hhhxGNRolGoxx22GE888wz2/z4F154gVNPPZUZM2YQi8UIh8PMnj2bSy+9lI0bN36ALRdi+zHL3QAhPq5OOeUUjj/+eAByuRzLli3j1ltv5W9/+xuLFi0q3Sc+nB5//HGOPPJIrrzyykHxd955B03TytQqIT6a/v73v3PssccyYcIErrrqKvx+P7/97W857LDDeOSRRzj88MPfcx0rVqygr6+PM844g4aGBnRd5/XXX+c3v/kNd911F6+88grjx4/fAXsjxOhJYS9Emey+++6ceeaZg2IHH3wwxx9/PH/4wx+ksP+Q27RpE1VVVZvF/X5/GVojxEeX67pceOGF+P1+nn76aSZNmgTAWWedxS677MKXv/xl3nnnHXR964MUPve5z/G5z31us/j8+fM5/fTTufXWWzc7UBdirJGhOEKMIf29QT6fb7P7Hn30UQ499FBisRjBYJA99tiDm266CaXUoOW2NtZd0zTOOeec0u2BY7gfeeQRDjjgAILBILW1tVxwwQWkUqnN1rFy5UpOPPFE4vE40WiUT37yk7z22mtb3KdXX32VU045hbq6Onw+H1OnTuU73/kO6XR6s2VffvllDj/8cMLhMJWVlZx88smsWbNmi+veEtu2ufHGG9l7770Jh8NEo1Hmzp3LVVddNWi5bDbLNddcw6xZswgEAlRVVfHpT3+al156abN19ufuhRde4LDDDiMSiVBRUcFnPvMZ2traSsudc845aJqGUor/+Z//KQ23+uMf/whseYz9//7v/7L77rsTCASYMGECl1566bA5AlBK8d///d/st99+hMNhwuEwBx54IH/5y19G3e6R5m4kbdiaBx54gMMPP5zKykoCgQBTp07lvPPOo6OjY9Byd955J/vvv39pWwcccAB33333Zuvrf/1v2rSJz3/+81RXVxMMBjnkkEOGfV7vvPNO5s2bR1VVFcFgkEmTJnHSSSfx9ttvD1qutbWVSy65hClTpuDz+aivr+fMM8/c7PX5xz/+EU3TWLx4MT//+c+ZOXMmfr+fnXbaiYULF262/aVLl3LGGWcwceJE/H4/dXV1HHjggdx6662brfPJJ5/c7PH9r7eRrnM0XnjhBT75yU8SiUSorq7mpJNOYu3atfT19REMBvnRj370vtY/Ws888wxr1qzh1FNPLRX1APF4nPPOO4+VK1fyr3/9a9Tr7/887enpeZ8tFeKDJz32QpRJOp0uFS+5XI4VK1bwn//5n1iWxQUXXDBo2d/97necf/75TJo0iW9+85tEIhHuu+8+Lr74Yl577TV++9vfvq+2PPLII/zqV7/iggsu4JxzzuEf//gHv/3tb9E0jVtuuaW0XHNzMwceeCA9PT1ceOGFzJ49m+eee44FCxZQXV292XofffRRTjjhBCZOnMgll1xCfX09r732GgsXLuSf//wnixcvxjQLH0OvvPIKhxxyCIZhcMkllzBx4kQee+wxFixYMOwBxpbYts1RRx3FP/7xD+bPn8+VV15JLBZj6dKl3HvvvVxzzTVAoZfv6KOPZvHixRx99NFcfPHFbNq0iZtvvpmDDjqIRx55hEMPPXTQul977TWOOuoozjrrLE4//XRefvllbr31Vnp6enj00UcBuOCCCzj88MP5/Oc/z8EHH8yXvvQlAA488MAttvmWW27hy1/+MjNmzODKK6/E5/Nx55138vTTTw+7/Lnnnsttt93G8ccfX+phvP/++znxxBO5+eabufDCC0fc7pHkbjRtGM6VV17Jtddey7Rp07jkkktobGxk3bp1PPjgg6xfv56amppBy+22225cddVVKKW44447OOOMM1i9ejWXX375oPWmUikOPvhg9t57b6699lpaW1u58cYbOeqoo1i9ejXRaBQoFPVnnnkmn/jEJ7jqqquIRCJs2LCBJ554gnfeeYc5c+YA777uk8kkX/ziF5k5cyYbNmzg5ptv5rHHHuOll14aVFACXH755SQSCc4991wikQi33XYbl112GePHj+czn/kMAJ2dnRx66KF4nscFF1zATjvtRHd3N2+88QZPPfUU55133nvmcKgPYp0Aixcv5lOf+lRpqEsul+P666/n2GOP5bLLLiObzXLaaae953ps26a3t3ebtxuPx7Esa6vLPP/888Dw77H+2AsvvMBBBx20TdtMp9Ok02kymQxvv/023/nOdwA45phjtrndQpSNEkLsUIsXL1bAsH8TJkxQTzzxxKDle3p6VCQSUQ0NDaq9vb0Ut21bHXHEEQpQzzzzTCk+f/58NXny5GG3Daizzz67dLupqUkBKhgMqlWrVg1a9sgjj1SWZalkMlmKff7zn1eAWrRo0aBlf/SjHylg0HYzmYwaN26c2m+//VQ2mx20/H333acA9cc//rEUO/jgg5Wu6+qll14atOwFF1ygADV//vxh92mo66+/XgHqq1/9qvI8b9B9ruuW/v+73/1OAer8888ftMw777yj/H6/mjFjxqDlAaVpmvrnP/85bPveeeedQfGhue43efLkQfvS//xOmjRJ9fT0lOLpdFrtscceClBXXXVVKf6Xv/xFAWrhwoWbrfvTn/60isViKpFIjKrd25q7kbZhOC+88IIC1AEHHDDoNTZ0e8uXL1e6rqvdd99dpVKp0v3JZFLtuuuuyjAM1dTUVIrPnz9fAeqHP/zhoPX97//+rwLUb37zm1LsxBNPVNFoVOXz+a229YQTTlCVlZWbvUeamppUJBJR55xzTin2hz/8QQFq7ty5g173yWRSVVdXq3nz5pVif/3rXxWg7r777q1uv3+dixcv3uy+s88+Ww38Kt/WdY7UbrvtpgzDGJSD3//+96XPrb322mub1rO1z7/h/obb56EuvvhiBaiHH354s/veeustBaivf/3r27yvV1111aA27LTTTuq2227b5scLUU4yFEeIMjnnnHN4/PHHefzxx3nkkUf49a9/TUVFBSeccMKgntrHHnuMZDLJJZdcUurBBDBNk+9+97sA/PnPf35fbTnxxBOZOnXqoNgRRxyBbds0NTUB4Hkef/nLX5gzZw4nnHDCoGX/3//7f0QikUGx//u//2PTpk2cc8459PX10dHRUfo75JBDCIVC/P3vfwegvb2dZ555hk996lPsvffeg9bzve99b0T7cscddxAOh/nhD3+42RCFgWNs+3M2sBcaYObMmXz2s59lxYoVvPHGG4Pumzdv3ma9gkcccQQAy5cvH1E7+/U/vxdffDHxeLwUDwaDfOMb39hs+dtvv51gMMjpp58+KKcdHR2ccMIJJBIJlixZMqp2b2vuRtOGoe68804AfvSjHxEOhze7v397f/nLX/A8j29/+9uEQqHS/eFwmG9+85u4rstf//rXzR779a9//T33t6KignQ6zYMPPojnecO2s7e3lwceeICjjz6aWCw2aF8jkQgHHHBA6XU
"text/plain": [
"<Figure size 770x440 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"for i in range(n_a):\n",
" ax.plot(S[:, i], color='tab:blue', alpha=0.3, lw=0.6)\n",
"ax.plot(mean_traj, color='red', lw=2, label='empirical mean')\n",
"ax.axhline(init_mean, color='k', ls=':', label='initial mean')\n",
"ax.set_xlabel('step k'); ax.set_ylabel('s^k_i'); ax.legend(); ax.grid(alpha=0.3)\n",
"ax.set_title('Bounded-confidence consensus, α = 0.3')\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4f6911f2",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T10:16:18.119625Z",
"iopub.status.busy": "2026-05-12T10:16:18.119259Z",
"iopub.status.idle": "2026-05-12T10:16:18.719675Z",
"shell.execute_reply": "2026-05-12T10:16:18.717353Z"
}
},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 770x440 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"for alpha in [0.05, 0.1, 0.3, 0.6, 1.0]:\n",
" r = opt.consensus_dynamics(init, alpha=alpha, noise_sigma=0.0, n_steps=60, seed=0)\n",
" S = np.array(r['states_flat']).reshape(r['n_steps'], r['n_agents'])\n",
" spread = S.max(axis=1) - S.min(axis=1)\n",
" ax.semilogy(spread, label=f'α = {alpha:g}')\n",
"ax.set_xlabel('step k'); ax.set_ylabel('max_i s min_i s')\n",
"ax.set_title('Convergence rate vs averaging weight α'); ax.legend(); ax.grid(alpha=0.3)\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "2c5abb26",
"metadata": {},
"source": [
"**Verified:** without noise, the empirical mean is exactly preserved and the spread decays geometrically."
]
}
],
"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
}