{ "cells": [ { "cell_type": "markdown", "id": "27313f1e", "metadata": {}, "source": [ "# 2D heat transfer: hot water dipped into liquid nitrogen\n", "\n", "This notebook simulates heat transfer in a two-dimensional rectangular container.\n", "\n", "The initial conditions are:\n", "\n", "- water inside the container: **100 °C**\n", "- left, right, and bottom boundaries: **−196 °C** (liquid nitrogen)\n", "- top boundary: **22 °C** (room temperature)\n", "\n", "The temperature field is discretized on a rectangular grid. At each iteration, the\n", "temperature of every interior point is replaced by the average of itself and its four\n", "nearest neighbours.\n", "\n", "This is a simple finite-difference relaxation model. It is useful for illustrating\n", "diffusion-like heat flow, although it is not yet a quantitatively calibrated solution\n", "of the physical heat equation." ] }, { "cell_type": "code", "execution_count": null, "id": "480dea4a", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from matplotlib.animation import FuncAnimation\n", "from IPython.display import HTML" ] }, { "cell_type": "markdown", "id": "35ea1ad4", "metadata": {}, "source": [ "## The temperature grid\n", "\n", "The array contains an extra one-cell boundary around the simulated water:\n", "\n", "```text\n", " 22 °C\n", " ┌─────────────┐\n", "-196 │ │ -196\n", " °C │ water │ °C\n", " │ │\n", " └─────────────┘\n", " -196 °C\n", "```\n", "\n", "The array index order is `[y, x]`, which is also the natural order for displaying\n", "the field as an image." ] }, { "cell_type": "code", "execution_count": null, "id": "89e328ba", "metadata": {}, "outputs": [], "source": [ "class Heat2D:\n", " def __init__(self, height, width):\n", " # Index order is [y, x].\n", " # The extra two rows/columns contain the fixed boundary temperatures.\n", " self.heat_map = np.full((height + 2, width + 2), 100.0)\n", "\n", " # Liquid-nitrogen boundaries\n", " self.heat_map[:, 0] = -196.0\n", " self.heat_map[:, -1] = -196.0\n", " self.heat_map[-1, :] = -196.0\n", "\n", " # Top of the container remains at room temperature\n", " self.heat_map[0, :] = 22.0\n", "\n", " def step(self):\n", " # Perform one temperature-relaxation step.\n", " mid = self.heat_map[1:-1, 1:-1]\n", " above = self.heat_map[:-2, 1:-1]\n", " below = self.heat_map[2:, 1:-1]\n", " left = self.heat_map[1:-1, :-2]\n", " right = self.heat_map[1:-1, 2:]\n", "\n", " mid[:] = (mid + above + below + left + right) / 5.0\n", "\n", " return self.heat_map" ] }, { "cell_type": "markdown", "id": "5795102a", "metadata": {}, "source": [ "## Update rule\n", "\n", "For an interior grid point $(i,j)$, one iteration uses\n", "\n", "\\[\n", "T_{i,j}^{\\,\\mathrm{new}}\n", "=\n", "\\frac{\n", "T_{i,j}\n", "+T_{i-1,j}\n", "+T_{i+1,j}\n", "+T_{i,j-1}\n", "+T_{i,j+1}\n", "}{5}.\n", "\\]\n", "\n", "So each point moves toward the average temperature of its local neighbourhood.\n", "\n", "The NumPy implementation updates the whole interior grid without explicit Python\n", "loops." ] }, { "cell_type": "code", "execution_count": null, "id": "7b0ebdf6", "metadata": {}, "outputs": [], "source": [ "# Initialize a 100 x 100 element water container\n", "heat = Heat2D(100, 100)\n", "\n", "print(\"Array shape:\", heat.heat_map.shape)\n", "print(\"Initial interior temperature:\", heat.heat_map[50, 50], \"°C\")" ] }, { "cell_type": "markdown", "id": "673c9a2a", "metadata": {}, "source": [ "## Initial temperature field" ] }, { "cell_type": "code", "execution_count": null, "id": "03a4cea7", "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(6, 5))\n", "\n", "im = ax.imshow(\n", " heat.heat_map,\n", " origin=\"upper\",\n", " cmap=\"cool\",\n", " vmin=-200,\n", " vmax=100,\n", ")\n", "\n", "ax.set_title(\"Initial temperature\")\n", "ax.set_xlabel(\"x\")\n", "ax.set_ylabel(\"y\")\n", "\n", "cbar = fig.colorbar(im, ax=ax)\n", "cbar.set_label(\"Temperature (°C)\")\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "a1a3a764", "metadata": {}, "source": [ "## Take a few steps manually\n", "\n", "This is useful for seeing what one call to `step()` does before running an animation." ] }, { "cell_type": "code", "execution_count": null, "id": "8b69d850", "metadata": {}, "outputs": [], "source": [ "heat_test = Heat2D(100, 100)\n", "\n", "for _ in range(100):\n", " heat_test.step()\n", "\n", "fig, ax = plt.subplots(figsize=(6, 5))\n", "\n", "im = ax.imshow(\n", " heat_test.heat_map,\n", " origin=\"upper\",\n", " cmap=\"cool\",\n", " vmin=-200,\n", " vmax=100,\n", ")\n", "\n", "ax.set_title(\"Temperature after 100 iterations\")\n", "ax.set_xlabel(\"x\")\n", "ax.set_ylabel(\"y\")\n", "\n", "cbar = fig.colorbar(im, ax=ax)\n", "cbar.set_label(\"Temperature (°C)\")\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "6d6d7256", "metadata": {}, "source": [ "## Animation\n", "\n", "In a Jupyter notebook it is convenient to display the Matplotlib animation as\n", "embedded JavaScript/HTML rather than relying on a separate GUI window.\n", "\n", "The simulation below starts again from the initial state." ] }, { "cell_type": "code", "execution_count": null, "id": "67b3a138", "metadata": {}, "outputs": [], "source": [ "heat = Heat2D(100, 100)\n", "\n", "fig, ax = plt.subplots(figsize=(6, 5))\n", "\n", "im = ax.imshow(\n", " heat.heat_map,\n", " origin=\"upper\",\n", " cmap=\"cool\",\n", " vmin=-200,\n", " vmax=100,\n", ")\n", "\n", "ax.set_xlabel(\"x\")\n", "ax.set_ylabel(\"y\")\n", "\n", "cbar = fig.colorbar(im, ax=ax)\n", "cbar.set_label(\"Temperature (°C)\")\n", "\n", "\n", "def update(frame):\n", " heat.step()\n", " im.set_data(heat.heat_map)\n", " ax.set_title(f\"2D heat-transfer simulation — iteration {frame}\")\n", " return (im,)\n", "\n", "\n", "ani = FuncAnimation(\n", " fig,\n", " update,\n", " frames=500,\n", " interval=20,\n", " blit=False,\n", ")\n", "\n", "plt.close(fig)\n", "\n", "HTML(ani.to_jshtml())" ] }, { "cell_type": "markdown", "id": "8a264380", "metadata": {}, "source": [ "## A note about the physics\n", "\n", "This algorithm qualitatively resembles heat diffusion, but the iteration number is\n", "not yet a physical time.\n", "\n", "A standard finite-difference discretization of the heat equation\n", "\n", "\\[\n", "\\frac{\\partial T}{\\partial t}\n", "=\n", "\\alpha \\nabla^2 T\n", "\\]\n", "\n", "would normally contain the thermal diffusivity $\\alpha$, the spatial grid spacing\n", "$\\Delta x$, and the physical time step $\\Delta t$.\n", "\n", "Those can be added if the goal is to turn this visualization into a physically\n", "scaled heat-equation simulation." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3" } }, "nbformat": 4, "nbformat_minor": 5 }