Files
optimiz-rs/examples/notebooks/11_pde.ipynb
T

349 lines
321 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "d78c4190",
"metadata": {},
"source": [
"# 11 — PDE solvers (FokkerPlanck, Poisson)\n",
"\n",
"CPU-only finite-difference solvers. Doc page:\n",
"[pde.rst](../../docs/source/algorithms/pde.rst).\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e2943612",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:20.836605Z",
"iopub.status.busy": "2026-05-12T14:05:20.836332Z",
"iopub.status.idle": "2026-05-12T14:05:21.554858Z",
"shell.execute_reply": "2026-05-12T14:05:21.553141Z"
}
},
"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": "defeff96",
"metadata": {},
"source": [
"## Cellule 1 — Diffusion d'une gaussienne (FokkerPlanck 1-D)\n",
"\n",
"**Théorème (FokkerPlanck / Kolmogorov forward).** Pour la diffusion\n",
"$dX_t = \\mu\\,dt + \\sigma\\,dW_t$, la densité $p(t, x)$ vérifie\n",
"$$\\partial_t p = -\\mu\\,\\partial_x p + \\tfrac{1}{2}\\sigma^2\\,\\partial_{xx} p.$$\n",
"\n",
"**Équation pivot (densité gaussienne).**\n",
"$$p(t, x) = \\frac{1}{\\sqrt{2\\pi(\\sigma^2 t + s_0^2)}}\n",
" \\exp\\!\\left(-\\frac{(x-\\mu t)^2}{2(\\sigma^2 t + s_0^2)}\\right).$$\n",
"\n",
"**Ce que la cellule vérifie.** Le primitive\n",
"`fokker_planck_constant(mu, sigma_sq, init_sigma, ...)` reproduit la\n",
"gaussienne analytique pour $\\mu = 0.1$, $\\sigma^2 = 0.16$,\n",
"$s_0 = 0.2$, $T = 1$.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "211170d1",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:21.558941Z",
"iopub.status.busy": "2026-05-12T14:05:21.558558Z",
"iopub.status.idle": "2026-05-12T14:05:22.231875Z",
"shell.execute_reply": "2026-05-12T14:05:22.230545Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Erreur sup |p_num - p_ana| = 6.454e-04\n",
"Masse finale (≈ 1) = 1.0000\n"
]
},
{
"data": {
"image/png": "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rqhPHfHG/SghxPvoTwWEEIuPPvoIADB58mTF5/z666/o3LkzDAYDpkyZgtq1a2Pfvn1YsWIFvv32W/zwww8ICwtzu01XrlxB165dMWDAAPzzn//EiRMn8NZbb2H//v04fPgwkpOTLWXHjx+P999/HwMHDsTo0aMBmEa/Pfzww3jzzTctz+u7775Dv3790LhxYzzzzDOIj4/HX3/9hf379+PIkSPo1KmT2+0dO3YsOI7D9OnTwfM8Vq1ahTFjxqBOnTpo3769w/Nee+01rFy5Et9//z3Wr19vOd6hQweH51y9ehXvvPMOBg8ejEceeQQhISE4ePAg3n77bezbtw8///yzy2k67777LiZOnIhWrVrh+eefR0xMDH744QfMmTMHR44cUfzhaPfu3cjOzsaQIUOcllu/fj2+//57vPPOO3jhhRfQqFEjRfW//fbbuHr1KiZOnIjExERs3boVixcvRlRUFJ5//nlLub1792LPnj3o168fatWqhcLCQuzYsQPPPPMMzp07h1WrVrm8ltL3ESGEkOCQn59vF84AplH6sbGxAEwhy6RJk7B161bLz3az9957D6IoSqaezps3DwsXLkSzZs2Qnp4Oxhg2bNiAkSNH4ty5cy7X71WqUaNGWL9+PR599FF06tQJTzzxhKLzvvzySwwYMABRUVGYMmUKqlatih07dmDevHnYv38/vvjiC/C8dHyIu/cogPp7p8ceewyMMcycORNFRUVYt24dRo4ciby8PEycONFSbt26dbh+/TrGjBmDlJQU3Lx5E++99x4GDBiADz/8EI888ggA4MUXX0RcXBy2bduGV199FQkJCQCA5s2bO2yz0WhE3759sWfPHvTt2xdTp07FX3/9hTfffBMdO3bEzp070bVrV6fPOzExEevXr8eMGTOQkJCAF198UfIYYLoXTktLQ1hYGCZMmICaNWvi9OnTePPNN/Htt9/i4MGDiI6OtnstQkJC8Oyzz+Lu3btYvnw5evbsifXr12PcuHGYOHEiRowYgZ07d+LVV19FYmIi5syZY9e+3r17IyoqCnPnzsXt27fx9ttv48EHH8Tnn3+OBx980FJO7fs5Ly8PnTp1Qps2bbBgwQLcuXMHERERyM3NRceOHXHmzBmMHTsWLVq0QFZWFlavXo37778f33//PVq3bu20T+V4636UEOIDjBBCSsXHx7PIyEhV53Tq1IlxHMf27dsnOb5gwQIGgC1cuNByLD09nQFg58+ft6unS5curGbNmpJjNWvWZADYsmXLJMe3bt3KALCxY8dajn366acMAFu5cqVd3f3792dRUVEsNzeXMcbYjBkzGAD2119/qXquzp6H+VifPn2Y0Wi0HL906RLTarVs5MiRLp/v2LFjmaMfy2vXrmUA2J49eyzHioqKWHFxsV3Z1atXMwDso48+khy37bOMjAwWEhLCBg0axERRlJRdvnw5A8D27t0r2x5bkyZNYlWqVJE8d0fkngtjjO3Zs4cBYGvXrrU7VrVqVXb79m3LcaPRyBo1asSSkpIkdeTl5clec9SoUUwQBJaRkeH0emreR4QQQgLL/HPc0Zf179mcnBwWFhbGevToYVdP3bp1Wc2aNS2/C0+dOsV4nmctWrRgd+/etZTLy8tjTZs2ZYIgSO4B5H5/y/2eN7P9fezomKO6DAYDS01
"text/plain": [
"<Figure size 1320x440 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"mu, sigma_sq, init_sigma = 0.1, 0.16, 0.2\n",
"T, n_t = 1.0, 200\n",
"n_x = 201\n",
"res = opt.fokker_planck_constant(mu, sigma_sq, init_sigma,\n",
" -3.0, 3.0, n_x, T, n_t)\n",
"xs = np.array(res['x_grid'])\n",
"ts = np.array(res['time_grid'])\n",
"density = np.array(res['density']).reshape(n_t + 1, n_x)\n",
"p_final = density[-1]\n",
"\n",
"# ground truth gaussienne\n",
"sig_eff = np.sqrt(sigma_sq * T + init_sigma ** 2)\n",
"analytic = np.exp(-(xs - mu * T) ** 2 / (2 * sig_eff ** 2))\n",
"analytic /= np.trapezoid(analytic, xs)\n",
"\n",
"err = float(np.max(np.abs(p_final - analytic)))\n",
"mass = float(np.trapezoid(p_final, xs))\n",
"print(f\"Erreur sup |p_num - p_ana| = {err:.3e}\")\n",
"print(f\"Masse finale (≈ 1) = {mass:.4f}\")\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
"axes[0].plot(xs, density[0], ':', label='p(0, x)', alpha=0.6)\n",
"axes[0].plot(xs, p_final, lw=2, label=f'p({T}, x) — schéma')\n",
"axes[0].plot(xs, analytic, '--', lw=1.5, label='gaussienne analytique')\n",
"axes[0].set_xlabel('x'); axes[0].set_ylabel('densité')\n",
"axes[0].set_title(\"Coupes initiale / finale\")\n",
"axes[0].legend()\n",
"im = axes[1].imshow(density.T, aspect='auto', origin='lower',\n",
" extent=[0, T, xs.min(), xs.max()],\n",
" cmap='inferno')\n",
"axes[1].set_xlabel('t'); axes[1].set_ylabel('x')\n",
"axes[1].set_title(\"Évolution spatio-temporelle\")\n",
"plt.colorbar(im, ax=axes[1], label='p(t, x)')\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "ae66c1a1",
"metadata": {},
"source": [
"**Résultat attendu.** Erreur sup faible et masse $\\approx 1$.\n",
"\n",
"**Lecture du graphique.** Gauche : pic initial fin (pointillé) qui\n",
"s'étale en gaussienne décalée vers la droite (drift $\\mu T = 0.1$).\n",
"Droite : carte chaleur de $p(t, x)$, qui s'élargit dans le temps.\n",
"\n",
"**Conclusion.** Le solveur FokkerPlanck est validé sur le cas\n",
"gaussien.\n"
]
},
{
"cell_type": "markdown",
"id": "d7b22c65",
"metadata": {},
"source": [
"## Cellule 2 — Poisson 2D sur un carré (équation harmonique)\n",
"\n",
"**Théorème (Poisson Dirichlet).** Pour $f$ régulière et conditions\n",
"nulles sur $\\partial\\Omega = [0,1]^2$, $-\\Delta u = f$ admet une\n",
"unique solution dans $H^1_0$.\n",
"\n",
"**Équation pivot.** Si $f = 2\\pi^2 \\sin(\\pi x)\\sin(\\pi y)$ alors\n",
"$$u(x, y) = \\sin(\\pi x)\\sin(\\pi y).$$\n",
"\n",
"**Démonstration.** Calcul direct : $-\\Delta u = 2\\pi^2 \\sin(\\pi x)\n",
"\\sin(\\pi y) = f$. $\\square$\n",
"\n",
"**Ce que la cellule vérifie.** Le solveur SOR `poisson_2d_zero_boundary`\n",
"résout l'équation et l'erreur tend vers zéro.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "576f1eb5",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:22.235032Z",
"iopub.status.busy": "2026-05-12T14:05:22.234767Z",
"iopub.status.idle": "2026-05-12T14:05:22.746208Z",
"shell.execute_reply": "2026-05-12T14:05:22.744955Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Itérations SOR : 290\n",
"Résidu final : 9.737e-07\n",
"Erreur sup : 4.896e-04\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABFEAAAGtCAYAAADecetiAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AACk9UlEQVR4nOzdeZhcVZk/8O+599bWW7o7BtIsEUGWqCw6GgRlZAsZQTFsAZwZRYyPkRGURTCMQBiGAE5+6LhgZBkcFUaGGFCRTTJBh2EYUGfUYdLsi0LYkk46vdRy7zm/Pypd6equuu/bXUWnq/L9PE8/kLrvPffWrXPPPXXqLMY550BERERERERERLG87X0CRERERERERESNgI0oREREREREREQKbEQhIiIiIiIiIlJgIwoRERERERERkQIbUYiIiIiIiIiIFNiIQkRERERERESkwEYUIiIiIiIiIiIFNqIQERERERERESmwEYWIiIiIiIiISIGNKERERERERERECmxEIZqmbrjhBvzDP/wDCoXC9j4VIiIiIqriqaeewrJly/Dwww9v71MhoinARhSiaejb3/42zjnnHLznPe9BIpGY0L7GGJxxxhlvzokRERERTSOHH3449thjj+12/L6+PnzkIx/Br3/9a7z3ve/dbudBRFOHjShE08wvfvELXHTRRbjjjjtw1FFHbe/TISIiIqIKwjDEKaecgj322AM//vGPkUwmt/cpEdEUMM45t71PgoiKnHP4xje+gXe9612TbkDJZrPwfX/CPViIiIiIGk0+n4dzDqlUasqP/d///d+499578cUvfhGZTGbKj09E2wcbUYiawPDwMBKJBIIg2N6nQkRERNNMf38/Ojo6JrztzbS9jktEVCsO56Fpa9myZTDG4Pnnnx+3bSLjX0fmCHn00Udx5JFHoq2tDZ2dnTjttNPw2muv1XTMPfbYA4cffjj+93//FwsWLEBHRwdmzpyJxYsXY3BwENZafPWrX8Xb3/52pFIpvPOd78TPf/7ziue5du1afPjDH0ZXVxdSqRTmzp2La665BlEUVTyPF154Aaeddhre8pa3oKWlBX/605/K3u9Y3/jGN7DvvvsilUrhbW97G6644go88MADMMbge9/73qSvAVD8Jebkk0/GTjvthGQyiT333BNf/vKXMTQ0VPG9EhERkV4+n8dXv/pVHHDAAchkMujo6MDRRx+NX/3qV2Vxzz//PIwxWLZsGX784x9j3rx5aGlpwfHHHw9gW73l97//PY477jh0dXVhxowZpf1fffVVnH322dhjjz2QTCax884746/+6q/G1QkmW1+qdtxKcrkcrrjiCrzjHe9Aa2srOjo6sO++++LMM8/E8PBw7PEA4N5778XBBx+MTCaDnXbaCZ/5zGewcePGcfWk0dfsnnvuwfvf/35kMhnMmjULn/3sZzE4ODgu7S1btuBv//ZvS/Wq7u5uLFy4EL///e/HxTrncMMNN2DevHlobW1Fa2srDj30UNx5552x75+Ipi/+bE07hN/97nf48Ic/jE984hM49dRT8Zvf/AY33ngjNm3ahHvvvbemtF966SUceeSROPnkk3HCCSfgP//zP3HTTTdheHgYXV1deOihh/DZz34Wvu/jH//xH3HiiSfiySefxFvf+tZSGv/0T/+ExYsX493vfje+/OUvo7OzE//xH/+BpUuX4r//+7/xox/9qOyYAwMDOOyww/C+970Pl19+ObZs2YK2traq5/jlL38Z11xzDf7sz/4My5cvRy6Xw0033YSf/OQnNb13oFhJWbhwIXbffXecffbZ2HnnnfG73/0O1157Lf7jP/4Da9euZQ8ZIiKiSQrDEMceeyx++ctf4vTTT8eSJUswNDSEH/7whzjyyCNx55134iMf+UjZPj/5yU/w9a9/HUuWLMFnPvMZjO54/sc//hEf+tCHcMIJJ+Cqq67CK6+8Unr90EMPxcDAAD796U9jn332wUsvvYTvfOc7uP/++/HrX/8ac+bMmfT7qHbcaj7/+c/jxhtvxF/+5V/inHPOAQA899xzuOuuuzA4OBg7fOanP/0pTjjhBMyePRtf/vKX0dXVhZ/85Cf4i7/4i6r73HPPPfjWt76Fz372szjjjDOwZs0aXH/99TDGYOXKlaW4/v5+fPCDH8TTTz+NT37ykzjwwAPR19eHG264AYcccgj+/d//He95z3tK8Z/61Kfw/e9/Hx/72Mfwl3/5lwCA1atX44QTTsB3vvMdLFmyRHX9iGgacUTT1GWXXeYAuOeee27ctg996EPurW99qyodAM4Y4/7jP/6j7PXPfvazDoB74oknJn3Mt771rQ6A+5d/+Zey1z/2sY85Y4w76KCDXC6XK73+3//93w6AW7p0aem19evXu3Q67RYuXOistWXprFixwgFwDz74YNl5AHAXXXRR1ff7yU9+svTvp556ynme5+bNm+ey2Wzp9Q0bNrienh4HwN18882TugbDw8Nu9uzZ49J2zrlVq1Y5AO573/texfMkIiIi2de//nUHwK1evbrs9Xw+79797ne7t73tbaXXnnvuOQfABUHg/vCHP4xLa6Te8p3vfGfctoULF7quri73zDPPlL3+3HPPuba2NnfGGWeUXptsfanScavp6upyf/EXfyHGjT1eGIZuzpw5bsaMGe7ll18uvW6tdR/72MfG1ZNGrlkmkxn33hcsWOASiYQbGBgovfbFL37RJRIJ98gjj5TF9vX1ud12280dfvjhpdfuvPNOB8Bde+214877ox/9qOvo6HD9/f3ieySi6YXDeWiHcMghh+DQQw8te23+/PkAgCeffLKmtHfZZRecdtppZa996EMfgnMOZ511VtlM7QcddBA6OjrKjrlq1Spks1ksXrwYGzZswBtvvFH6G/ll6b777ht33Isuukh1fnfccQestbjgggvKJl3r7u7G3/zN30zovY71wAMP4JVXXsEZZ5yBLVu2lJ37n//5n6OlpaXiuRMREZHOD37wA+yxxx447LDDyp6zmzdvxvHHH4/nnntuXF3muOOOw7ve9a6K6XV3d+Mzn/lM2WubN2/GT3/6Uxx77LHo6OgoO05bWxve//731/w8r3TcOJ2dnXj88cfxu9/9bkLH+c1vfoMXX3wRf/3Xf42enp7S68aY2LrTCSecgD333LPstfnz56NQKOC5554DUBya88Mf/hCHHHII9tprr7LrFIYhjjnmGPz7v/97abjRD37wA2QyGZx66qllsW+88QYWLlyI/v5+/Od//ueE3h8RbX/sY087hLEPRQCYOXMmAGDDhg11T7urqyt22+hjrlu3DgDGdcUd7dVXXy3796xZs0rHkDzzzDMAgHe84x3jtr3zne9UpVHNyLmfddZZOOussyrGjD13IiIi0lu3bh2GhoYwa9asqjGvvvoq9tlnn9K/R///WHvttRd83y977cknn4S1FrfccgtuueWWivt5Xm2/vVY6bpx//Md/xF//9V/joIMOwpw5c3DYYYdhwYIFOOWUU5BOp6vu9+yzzwIA9ttvv3Hb5s6dW3U/TV1xpAHkV7/6Vezn8cYbb2D33XfHunXrMDw8jF133bVqLOtJRI2HjSg0bRljqm4Lw3BCacU9tN2occKTOWZc2tW2jT6mtRYAcOONN5bNkzLaLrvsUvbvlpaWqses1USuwci5X3nllZg3b17FfbSNPURERDSetRb77rsvvvWtb1WNGdvrJK6eUGnbyPN80aJFqt4ik6kvTbTu8tGPfhTPP/887rvvPjz44IN48MEHccstt+Dyyy/Hf/7nf8Y2YkyGpq44cp3+/M//HJdccknV+JFzs9ZixowZWLVqVdXYWn/QIqKpx0YUmra6u7sBABs3bhw36/qzzz5bNkymkY858mtRV1cXjj766Lqnv9deewEA/u///m/cg/rxxx8fFz+RazBy7ul0+k05dyIioh3dPvvsgz/+8Y84/PDD37SJ2t/+9rfD8zwMDw+rnudTVV/q7OzEqaeeilNPPRUAsHLlSnzuc5/Dt7/9bSxbtqziPiM9Snp7e8dtG+lBO1mzZs1CZ2cn+vr6VNdpn332QW9
"text/plain": [
"<Figure size 1210x440 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"nx_grid = 41\n",
"xs = ys = np.linspace(0, 1, nx_grid)\n",
"X, Y = np.meshgrid(xs, ys, indexing='ij')\n",
"f = 2 * np.pi ** 2 * np.sin(np.pi * X) * np.sin(np.pi * Y)\n",
"res = opt.poisson_2d_zero_boundary(\n",
" f.flatten().tolist(), nx_grid, nx_grid,\n",
" 0.0, 1.0, 0.0, 1.0,\n",
")\n",
"u = np.array(res['u']).reshape(nx_grid, nx_grid)\n",
"\n",
"u_exact = np.sin(np.pi * X) * np.sin(np.pi * Y)\n",
"err = float(np.max(np.abs(u - u_exact)))\n",
"print(f\"Itérations SOR : {res['iterations']}\")\n",
"print(f\"Résidu final : {res['residual']:.3e}\")\n",
"print(f\"Erreur sup : {err:.3e}\")\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(11, 4))\n",
"im0 = axes[0].imshow(u, origin='lower', extent=[0, 1, 0, 1], cmap='viridis')\n",
"axes[0].set_title('u numérique'); plt.colorbar(im0, ax=axes[0])\n",
"im1 = axes[1].imshow(u - u_exact, origin='lower',\n",
" extent=[0, 1, 0, 1], cmap='RdBu_r')\n",
"axes[1].set_title('erreur signée'); plt.colorbar(im1, ax=axes[1])\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "ce3f9fdf",
"metadata": {},
"source": [
"**Résultat attendu.** Erreur sup faible (< $10^{-2}$) ; résidu\n",
"SOR petit.\n",
"\n",
"**Lecture du graphique.** Cloche centrée à hauteur $1$ ;\n",
"carte d'erreur quasi nulle, symétrique autour de zéro.\n",
"\n",
"**Conclusion.** Le solveur elliptique est validé.\n"
]
},
{
"cell_type": "markdown",
"id": "7d53c3c5",
"metadata": {},
"source": [
"## Cellule 3 — Exemple concret : diffusion thermique sur une plaque\n",
"\n",
"**Modèle physique.** Plaque carrée $[0,1]^2$ chauffée par une source\n",
"gaussienne au centre, conditions de Dirichlet nulles sur le bord.\n",
"Équilibre régi par\n",
"$$-\\Delta u = q(x, y),\\qquad u\\big|_{\\partial\\Omega} = 0.$$\n",
"\n",
"**Ce que la cellule vérifie.** Profil radial décroissant de température\n",
"sous source ponctuelle.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "adc915d4",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:05:22.749447Z",
"iopub.status.busy": "2026-05-12T14:05:22.749181Z",
"iopub.status.idle": "2026-05-12T14:05:23.199414Z",
"shell.execute_reply": "2026-05-12T14:05:23.198332Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Température max au centre : 0.290\n",
"Température max au bord : 0.00e+00\n",
"Itérations SOR : 820\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAoMAAAIcCAYAAACNXqQYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOy9eZwU1b3+/1T17PsMM8AMOwww7IvKLiKbEY3iGlEjxGgu10TjNSZG8zOamOj1xqg30Wi+iXHPdQVjXABBQGSRTfYZhp1hGBhm37eu8/ujuqpOVZ1au3qmB+rxNdJddc6p01XVVe9+PudziiOEEPjy5cuXL1++fPm6IMV3dQd8+fLly5cvX758dZ18GPTly5cvX758+bqA5cOgL1++fPny5cvXBSwfBn358uXLly9fvi5g+TDoy5cvX758+fJ1AcuHQV++fPny5cuXrwtYPgz68uXLly9fvnxdwPJh0JcvX758+fLl6wKWD4O+fPny5cuXL18XsHwY9NWtdejQITz++OPYtGlTV3fFly9f56H8a4yvC0EXBAyuW7cOHMfhtdde6+qudLmWLFkCjuO6uhsAgKamJtx3333o378/AoEABg4cKK/74IMPMG7cOCQmJoLjOKxbt05Xv7q6GldffTW2b9+Oiy++uPM67suXL0/FcRyWLFmiWjZw4EDMmjUrYtt84403EB8fjxMnThiW6cprjN19MmvWLNW102hZZ+iJJ55Ar169UFdX1+nb9hWeui0MSoBH/yUnJ2Ps2LH43e9+h5aWlq7uoi8LPf300/jzn/+M733ve3jttdfw/PPPAwCKi4uxaNEipKen44UXXsCbb76JESNGqOp2dHTgpptuwsCBA/Hhhx8iLi6uCz6BL1/nlx5//HF89NFHXd2NiKuxsREPP/ww/uM//gMDBgxglvGvMc51//33QxAEPPHEE13dFV8OFdPVHQhXN954I6699loAwNmzZ/HOO+/g0UcfxcaNG/H55593ce98memLL77AmDFj8Ic//EG1fN26dejo6MDzzz+PiRMnMuvu3bsXc+bMwf3334/4+PjO6K4vX+e9fvOb32Dx4sVYuHBhV3clovrrX/+KsrIyPPDAA4Zluvoa09zcjEAgYFlu1apVIIR0Qo+slZqaih/96Ef44x//iF/+8pfo0aNHV3fJl011W2dQ0rhx43D77bfj9ttv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
"text/plain": [
"<Figure size 660x550 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"nx_grid = 81\n",
"xs = ys = np.linspace(0, 1, nx_grid)\n",
"X, Y = np.meshgrid(xs, ys, indexing='ij')\n",
"q = 50.0 * np.exp(-((X - 0.5) ** 2 + (Y - 0.5) ** 2) / (2 * 0.05 ** 2))\n",
"res = opt.poisson_2d_zero_boundary(\n",
" q.flatten().tolist(), nx_grid, nx_grid,\n",
" 0.0, 1.0, 0.0, 1.0,\n",
")\n",
"u = np.array(res['u']).reshape(nx_grid, nx_grid)\n",
"\n",
"print(f\"Température max au centre : {u.max():.3f}\")\n",
"print(f\"Température max au bord : {u[0, :].max():.2e}\")\n",
"print(f\"Itérations SOR : {res['iterations']}\")\n",
"\n",
"fig, ax = plt.subplots(figsize=(6, 5))\n",
"im = ax.imshow(u, origin='lower', extent=[0, 1, 0, 1],\n",
" cmap='inferno', interpolation='bilinear')\n",
"ax.contour(X, Y, u, levels=8, colors='white', linewidths=0.5, alpha=0.6)\n",
"ax.set_title(\"Plaque chauffée par une source ponctuelle (équilibre)\")\n",
"ax.set_xlabel('x'); ax.set_ylabel('y')\n",
"plt.colorbar(im, ax=ax, label='température')\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "7419240e",
"metadata": {},
"source": [
"**Résultat attendu.** Température décroissante du centre vers le\n",
"bord (Dirichlet), profil radial visible.\n",
"\n",
"**Lecture du graphique.** Iso-températures (lignes blanches) circulaires\n",
"autour de la source ; température nulle sur les bords.\n",
"\n",
"**Conclusion.** Le même solveur Poisson 2D résout des problèmes\n",
"mathématiques académiques *et* des problèmes physiques concrets\n",
"(transfert thermique, électrostatique).\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
}