diff --git a/.github/workflows/tests+pypi.yml b/.github/workflows/tests+pypi.yml index afa57efe..c7ce45f2 100644 --- a/.github/workflows/tests+pypi.yml +++ b/.github/workflows/tests+pypi.yml @@ -236,6 +236,7 @@ jobs: run: | mkdir -p /home/runner/work/_temp/_github_home/figures mv /tmp/pytest-of-runner/pytest-current/test_run_notebooks_examples_*/fig_4.svg /home/runner/work/_temp/_github_home/figures + mv /tmp/pytest-of-runner/pytest-current/test_run_notebooks_examples_*/advection_diffusion.gif /home/runner/work/_temp/_github_home/figures - if: ${{ github.ref == 'refs/heads/main' && matrix.platform == 'ubuntu-latest' && matrix.python-version == '3.11'}} uses: eine/tip@master diff --git a/examples/PyMPDATA_examples/__init__.py b/examples/PyMPDATA_examples/__init__.py index 68e7940f..4e88a725 100644 --- a/examples/PyMPDATA_examples/__init__.py +++ b/examples/PyMPDATA_examples/__init__.py @@ -1,6 +1,8 @@ """ PyMPDATA_examples package includes common Python modules used in PyMPDATA smoke tests -and in example notebooks (but the package wheels do not include the notebooks) +and in example notebooks (but the package wheels do not include the notebooks). +![adv_diff](https://github.com/open-atmos/PyMPDATA/releases/download/tip/advection_diffusion.gif) + """ from importlib.metadata import PackageNotFoundError, version diff --git a/examples/PyMPDATA_examples/advection_diffusion_2d/__init__.py b/examples/PyMPDATA_examples/advection_diffusion_2d/__init__.py new file mode 100644 index 00000000..8412e802 --- /dev/null +++ b/examples/PyMPDATA_examples/advection_diffusion_2d/__init__.py @@ -0,0 +1,3 @@ +""" +PyMPDATA 2D advection-diffusion example with gif creation. +""" diff --git a/examples/PyMPDATA_examples/advection_diffusion_2d/advection-diffusion-2d.ipynb b/examples/PyMPDATA_examples/advection_diffusion_2d/advection-diffusion-2d.ipynb new file mode 100644 index 00000000..67cc62dd --- /dev/null +++ b/examples/PyMPDATA_examples/advection_diffusion_2d/advection-diffusion-2d.ipynb @@ -0,0 +1,418 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e333839d", + "metadata": {}, + "source": [ + "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/open-atmos/PyMPDATA.git/main?urlpath=lab/tree/examples/PyMPDATA_examples/advection_diffusion_2d/advection-diffusion-2d.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/open-atmos/examples/blob/main/examples/PyMPDATA_examples/advection_diffusion_2d/advection-diffusion-2d.ipynb)" + ] + }, + { + "cell_type": "code", + "id": "43d2893d-f472-43ac-ad5b-bf342a3783b9", + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:26.426280Z", + "start_time": "2024-09-06T09:33:25.835433Z" + } + }, + "source": [ + "from open_atmos_jupyter_utils import show_plot" + ], + "outputs": [], + "execution_count": 1 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:26.710248Z", + "start_time": "2024-09-06T09:33:26.426280Z" + } + }, + "cell_type": "code", + "source": [ + "import os\n", + "import numpy as np\n", + "import imageio\n", + "from IPython.display import display\n", + "from ipywidgets import FloatProgress\n", + "import matplotlib.pyplot as plt\n", + "from PyMPDATA import Solver, ScalarField, VectorField, Stepper, Options\n", + "from PyMPDATA.boundary_conditions import Periodic" + ], + "id": "9aaadc4a5234804a", + "outputs": [], + "execution_count": 2 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:26.714536Z", + "start_time": "2024-09-06T09:33:26.710248Z" + } + }, + "cell_type": "code", + "source": [ + "mu = 0.0005 # diffusion coefficient\n", + "dt = 0.025\n", + "tmax = 5.0\n", + "nt = int(tmax / dt)\n", + "\n", + "nx = 32\n", + "ny = 32\n", + "ux = 0.25\n", + "uy = 0.25\n", + "\n", + "omega = np.pi\n", + "\n", + "min_x, min_y = -1, -1\n", + "max_x, max_y = 1, 1\n", + "dx = (max_x - min_x) / nx\n", + "dy = (max_y - min_y) / ny\n", + "Cx = ux * dt / dx\n", + "Cy = uy * dt / dy" + ], + "id": "a563a769a256feba", + "outputs": [], + "execution_count": 3 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:27.142393Z", + "start_time": "2024-09-06T09:33:27.139022Z" + } + }, + "cell_type": "code", + "source": [ + "opt = Options(n_iters=3, non_zero_mu_coeff=True)\n", + "boundary_conditions = (Periodic(), Periodic())" + ], + "id": "9a0f60b51e32ce3e", + "outputs": [], + "execution_count": 4 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:27.463192Z", + "start_time": "2024-09-06T09:33:27.459668Z" + } + }, + "cell_type": "code", + "source": [ + "def analytic_solution(x, y, t):\n", + " return np.sin(omega*(x-ux*t+y-uy*t))*np.exp(-2*mu*t*omega**2) + 1" + ], + "id": "cab790be5c425ea5", + "outputs": [], + "execution_count": 5 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:27.696725Z", + "start_time": "2024-09-06T09:33:27.687964Z" + } + }, + "cell_type": "code", + "source": [ + "def z(t):\n", + " return np.array(\n", + " [\n", + " analytic_solution(x, y, t=t) for x in np.linspace(min_x, max_x, nx)\n", + " for y in np.linspace(min_y, max_y, ny)\n", + " ],\n", + " dtype=float\n", + ").reshape((nx, ny))\n", + "\n", + "advectee = ScalarField(data=z(t=0), halo=opt.n_halo, boundary_conditions=boundary_conditions)" + ], + "id": "b454a74473b8f900", + "outputs": [], + "execution_count": 6 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:28.441977Z", + "start_time": "2024-09-06T09:33:28.437960Z" + } + }, + "cell_type": "code", + "source": [ + "field_x = np.full((nx+1, ny), Cx, dtype=opt.dtype)\n", + "field_y = np.full((nx, ny+1), Cy, dtype=opt.dtype)\n", + "\n", + "advector = VectorField(\n", + " data=(field_x, field_y),\n", + " halo=opt.n_halo,\n", + " boundary_conditions=(boundary_conditions[0], Periodic())\n", + ")" + ], + "id": "fb28f958a4920cd", + "outputs": [], + "execution_count": 7 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:29.248347Z", + "start_time": "2024-09-06T09:33:28.925906Z" + } + }, + "cell_type": "code", + "source": [ + "stepper = Stepper(options=opt, n_dims=2)\n", + "solver = Solver(stepper=stepper, advector=advector, advectee=advectee)" + ], + "id": "45c8ea60d8490cd4", + "outputs": [], + "execution_count": 8 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:29.252473Z", + "start_time": "2024-09-06T09:33:29.249364Z" + } + }, + "cell_type": "code", + "source": [ + "vmin = np.min(solver.advectee.get())\n", + "vmax = np.max(solver.advectee.get())" + ], + "id": "bd4722c47297508c", + "outputs": [], + "execution_count": 9 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:33:30.257084Z", + "start_time": "2024-09-06T09:33:29.523763Z" + } + }, + "cell_type": "code", + "source": [ + "plt.imshow(solver.advectee.get().copy(), cmap='viridis', vmin=vmin, vmax=vmax)\n", + "plt.colorbar()\n", + "plt.xlabel('x')\n", + "plt.ylabel('y')\n", + "plt.title('Initial condition')\n", + "show_plot()" + ], + "id": "d7a5cd43651621e6", + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/svg+xml": "\n\n\n \n \n \n \n 2024-09-06T11:33:30.205644\n image/svg+xml\n \n \n Matplotlib v3.9.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "HBox(children=(HTML(value=\".\\\\tmpticf40e0.pdf
\"), HTML(val…" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "136af6436ac24094b27677f8ddff7aad" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 10 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:34:10.460749Z", + "start_time": "2024-09-06T09:33:30.258075Z" + } + }, + "cell_type": "code", + "source": [ + "progbar = FloatProgress(value=0, min=0, max=1)\n", + "display(progbar)\n", + "\n", + "states_history = [solver.advectee.get().copy()]\n", + "for i in range(nt//10):\n", + " solver.advance(n_steps=10, mu_coeff=(mu, mu))\n", + " states_history.append(solver.advectee.get().copy())\n", + " progbar.value = (i + 1.) / (nt//10)" + ], + "id": "2b279996b00be430", + "outputs": [ + { + "data": { + "text/plain": [ + "FloatProgress(value=0.0, max=1.0)" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "379bd59874384a0ebacd89b564e39953" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:34:10.906429Z", + "start_time": "2024-09-06T09:34:10.461745Z" + } + }, + "cell_type": "code", + "source": [ + "plt.imshow(solver.advectee.get().copy(), cmap='viridis', vmin=vmin, vmax=vmax)\n", + "plt.colorbar()\n", + "plt.xlabel('x')\n", + "plt.ylabel('y')\n", + "plt.title('Final condition')\n", + "show_plot()" + ], + "id": "2576fcdbfa20c032", + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/svg+xml": "\n\n\n \n \n \n \n 2024-09-06T11:34:10.858857\n image/svg+xml\n \n \n Matplotlib v3.9.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "HBox(children=(HTML(value=\".\\\\tmp28jhi9uc.pdf
\"), HTML(val…" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "f0b819655ca540aca67b724163621470" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 12 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:34:45.486389Z", + "start_time": "2024-09-06T09:34:45.479218Z" + } + }, + "cell_type": "code", + "source": "assert np.allclose(solver.advectee.get(), z(t=nt*dt), atol=0.25)", + "id": "556684f5a2e2483d", + "outputs": [], + "execution_count": 19 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:34:18.324878Z", + "start_time": "2024-09-06T09:34:16.129557Z" + } + }, + "cell_type": "code", + "source": [ + "os.makedirs(\"animation\", exist_ok=True)\n", + "for i, state in enumerate(states_history):\n", + " state = np.flipud(state)\n", + " plt.imshow(state, cmap='viridis')\n", + " plt.axis('off')\n", + " plt.tight_layout()\n", + " plt.colorbar()\n", + " plt.savefig(f\"animation/frame_{i:03d}.png\")\n", + " plt.close()" + ], + "id": "cc07e0075e5b9857", + "outputs": [], + "execution_count": 14 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:34:19.600400Z", + "start_time": "2024-09-06T09:34:19.595140Z" + } + }, + "cell_type": "code", + "source": [ + "def merge_images_into_gif(image_folder, gif_name, duration=0.01):\n", + " with imageio.get_writer(gif_name, mode='I', duration=duration) as writer:\n", + " for filename in sorted(os.listdir(image_folder)):\n", + " image = imageio.v3.imread(os.path.join(image_folder, filename))\n", + " writer.append_data(image)" + ], + "id": "1888a38e39bf2395", + "outputs": [], + "execution_count": 15 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-06T09:34:20.915947Z", + "start_time": "2024-09-06T09:34:20.179615Z" + } + }, + "cell_type": "code", + "source": [ + "merge_images_into_gif(\"animation\", \"advection_diffusion.gif\", duration=0.01)" + ], + "id": "52bc22d60805baa6", + "outputs": [], + "execution_count": 16 + }, + { + "metadata": {}, + "cell_type": "code", + "source": "", + "id": "5403105d000206da", + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/setup.py b/examples/setup.py index 602c2cd3..9b8678d7 100644 --- a/examples/setup.py +++ b/examples/setup.py @@ -34,6 +34,7 @@ def get_long_description(): "pint", "joblib", "sympy", + "imageio", ], author="https://github.com/open-atmos/PyMPDATA/graphs/contributors", license="GPL-3.0",