diff --git a/notebooks/01--Introduction.ipynb b/notebooks/01--Introduction.ipynb
index 59aaaa7142e9ea945b5222986a752cbe9ca0c7fe..a2298c04bf3667c52ad1a937545e952e71c9d3ea 100644
--- a/notebooks/01--Introduction.ipynb
+++ b/notebooks/01--Introduction.ipynb
@@ -108,7 +108,7 @@
     "* Scientific Computing with Python: NumPy and SciPy\n",
     "* Performance: Cython, JIT (Numba, JAX), C/Fortran interfacing\n",
     "* Parallelism: multithreading, multiprocessing, GPU computing, Dask, mpi4py\n",
-    "* Software Engineering, Packaging, Profiling, Visualization"
+    "* Software Engineering, Packaging, Profiling, Testing"
    ]
   },
   {
@@ -141,6 +141,7 @@
     "\n",
     "* Practical hands-on approach with code examples\n",
     "* Presentation is based on Jupyter notebooks\n",
+    "* Exercises based on Jupyter notebooks complement the presentations\n",
     "* Course material available for download at  \n",
     "  https://gitlab.mpcdf.mpg.de/mpcdf/python-for-hpc-exercises"
    ]
@@ -220,13 +221,6 @@
     "$ conda activate pyhpc\n",
     "```"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
diff --git a/notebooks/04--BasicHPC.ipynb b/notebooks/04--BasicHPC.ipynb
index c41db26615304d0de808dcd489c30cd42683dd99..cbc7de15bf98a0ec579a67e148b7cc01b84deaa8 100644
--- a/notebooks/04--BasicHPC.ipynb
+++ b/notebooks/04--BasicHPC.ipynb
@@ -44,14 +44,14 @@
     "\n",
     "* High performance computing: \"computing at bottlenecks of the hardware\" (G. Hager, RRZE)\n",
     "* Write *efficient software* to use *maximum hardware potential*\n",
+    "* Needed:\n",
+    "    * Understand hardware\n",
+    "    * Understand how software is executed on hardware\n",
     "* Common bottlenecks:\n",
     "    * Floating point operations\n",
     "    * Memory access\n",
     "    * Input/Output\n",
     "    * Communication\n",
-    "* Needed:\n",
-    "    * Understand hardware\n",
-    "    * Understand how software is executed on hardware\n",
     "* Here: only very basics, for more in-depth information, see courses from HLRS, LRZ and RRZE"
    ]
   },
diff --git a/notebooks/08--Diffusion.ipynb b/notebooks/08--Diffusion.ipynb
index 588d876204579fe268cf6a923a38c2d8cca1b537..7a46efe3a039264acd642d83381a352e9c9123cf 100644
--- a/notebooks/08--Diffusion.ipynb
+++ b/notebooks/08--Diffusion.ipynb
@@ -142,12 +142,15 @@
    },
    "outputs": [],
    "source": [
-    "def init(val=0.5):\n",
+    "def init(val=0.5, with_ghosts=True):\n",
     "    \"\"\"Set up a 2d NumPy array with some initial value pattern.\"\"\"\n",
     "    x = np.linspace(0., 4.*np.pi, num=n_points+2)\n",
     "    y = np.linspace(0., 4.*np.pi, num=n_points+2)\n",
     "    grid = val * np.outer(np.sin(x)**4, np.sin(y)**4)\n",
-    "    return grid"
+    "    if with_ghosts:\n",
+    "        return grid\n",
+    "    else:\n",
+    "        return grid[1:-1,1:-1]"
    ]
   },
   {
@@ -289,7 +292,7 @@
     }
    ],
    "source": [
-    "from scipy.ndimage.filters import laplace as scipy_laplacian\n",
+    "from scipy.ndimage import laplace as scipy_laplacian\n",
     "\n",
     "def evolve_scipy(grid, grid_tmp, n_points, dt, D):\n",
     "    \"\"\"Time step based on the SciPy Laplacian.\"\"\"\n",
@@ -316,7 +319,7 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=False)\n",
     "solution_scipy = main_loop(evolve_scipy, grid)"
    ]
   },
@@ -357,7 +360,7 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()"
+    "grid = init(with_ghosts=False)"
    ]
   },
   {
@@ -380,7 +383,7 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=False)\n",
     "solution_scipy = main_loop(evolve_scipy, grid)"
    ]
   },
@@ -508,10 +511,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=False)\n",
     "solution_roll = main_loop(evolve_np_roll, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_roll))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 17,
@@ -533,7 +545,7 @@
     }
    ],
    "source": [
-    "plot_grids(init(), solution_roll)"
+    "plot_grids(init(with_ghosts=False), solution_roll)"
    ]
   },
   {
@@ -578,10 +590,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=False)\n",
     "solution_roll = main_loop(evolve_np_inplace_roll, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_roll))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 20,
@@ -603,7 +624,7 @@
     }
    ],
    "source": [
-    "plot_grids(init(), solution_roll)"
+    "plot_grids(init(with_ghosts=False), solution_roll)"
    ]
   },
   {
@@ -636,6 +657,15 @@
     "    grid[ :, 0] = grid[ :,-2]"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "grid = init(with_ghosts=True)"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 22,
@@ -659,6 +689,15 @@
     "apply_periodic_bc_python(grid, n_points)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "grid = init(with_ghosts=False)"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 23,
@@ -721,10 +760,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_np_slicing = main_loop(evolve_np_slicing, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_np_slicing[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 26,
@@ -791,10 +839,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_np_slicing = main_loop(evolve_np_inplace_slicing, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_np_slicing[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 29,
@@ -910,7 +967,7 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "#main_loop(evolve_cython, grid)\n",
     "\n",
     "# measured wall clock time during lunch break: 25 minutes!!!"
@@ -4706,9 +4763,9 @@
     "    cdef int i, j\n",
     "    for j in range(n_points + 2):\n",
     "        grid[         0, j] = grid[n_points, j]  # grid[ 0, j] = grid[-2, j]\n",
-    "        grid[n_points-1, j] = grid[       1, j]  # grid[-1, j] = grid[ 1, j]\n",
+    "        grid[n_points+1, j] = grid[       1, j]  # grid[-1, j] = grid[ 1, j]\n",
     "    for i in range(n_points + 2):\n",
-    "        grid[ i, n_points-1] = grid[ i,        1]  # grid[ i,-1] = grid[ i, 1]\n",
+    "        grid[ i, n_points+1] = grid[ i,        1]  # grid[ i,-1] = grid[ i, 1]\n",
     "        grid[ i,          0] = grid[ i, n_points]  # grid[ i, 0] = grid[ i,-2]\n",
     "\n",
     "@cython.boundscheck(False) # turn off bounds-checking\n",
@@ -4749,6 +4806,15 @@
     "solution_cython = main_loop(evolve_cython, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_cython[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 38,
@@ -4809,9 +4875,9 @@
     "    cdef int i, j\n",
     "    for j in range(n_points + 2):\n",
     "        grid[         0, j] = grid[n_points, j]  # grid[ 0, j] = grid[-2, j]\n",
-    "        grid[n_points-1, j] = grid[       1, j]  # grid[-1, j] = grid[ 1, j]\n",
+    "        grid[n_points+1, j] = grid[       1, j]  # grid[-1, j] = grid[ 1, j]\n",
     "    for i in range(n_points + 2):\n",
-    "        grid[ i, n_points-1] = grid[ i,        1]  # grid[ i,-1] = grid[ i, 1]\n",
+    "        grid[ i, n_points+1] = grid[ i,        1]  # grid[ i,-1] = grid[ i, 1]\n",
     "        grid[ i,          0] = grid[ i, n_points]  # grid[ i, 0] = grid[ i,-2]\n",
     "\n",
     "@cython.boundscheck(False) # turn off bounds-checking\n",
@@ -5023,9 +5089,9 @@
     "    cdef int i, j\n",
     "    for j in range(n_points + 2):\n",
     "        grid[         0, j] = grid[n_points, j]  # grid[ 0, j] = grid[-2, j]\n",
-    "        grid[n_points-1, j] = grid[       1, j]  # grid[-1, j] = grid[ 1, j]\n",
+    "        grid[n_points+1, j] = grid[       1, j]  # grid[-1, j] = grid[ 1, j]\n",
     "    for i in range(n_points + 2):\n",
-    "        grid[ i, n_points-1] = grid[ i,        1]  # grid[ i,-1] = grid[ i, 1]\n",
+    "        grid[ i, n_points+1] = grid[ i,        1]  # grid[ i,-1] = grid[ i, 1]\n",
     "        grid[ i,          0] = grid[ i, n_points]  # grid[ i, 0] = grid[ i,-2]\n",
     "\n",
     "@cython.boundscheck(False) # turn off bounds-checking\n",
@@ -5067,6 +5133,15 @@
     "solution_cython_parallel = main_loop(evolve_cython_parallel, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_cython_parallel[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 45,
@@ -5221,7 +5296,7 @@
    },
    "outputs": [],
    "source": [
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba = main_loop(evolve_python_numba, grid)"
    ]
   },
@@ -5246,10 +5321,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba = main_loop(evolve_python_numba, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_numba[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {
@@ -5303,7 +5387,7 @@
    },
    "outputs": [],
    "source": [
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba = main_loop(evolve_python_numba_parallel, grid)"
    ]
   },
@@ -5327,10 +5411,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba = main_loop(evolve_python_numba_parallel, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_numba[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 54,
@@ -5422,7 +5515,7 @@
    },
    "outputs": [],
    "source": [
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba = main_loop(evolve_np_slicing_numba_parallel, grid)"
    ]
   },
@@ -5446,10 +5539,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba = main_loop(evolve_np_slicing_numba_parallel, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_numba[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 59,
@@ -5524,7 +5626,7 @@
    },
    "outputs": [],
    "source": [
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba_stencil = main_loop(evolve_numba_stencil, grid)"
    ]
   },
@@ -5548,10 +5650,19 @@
    ],
    "source": [
     "%%time\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=True)\n",
     "solution_numba_stencil = main_loop(evolve_numba_stencil, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_numba_stencil[1:-1,1:-1]))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 63,
@@ -5661,7 +5772,7 @@
     "@jit\n",
     "def evolve_np_roll_jax(grid, n_points, dt, D):\n",
     "    \"\"\"Time step based on the NumPy-roll-Laplacian.\"\"\"\n",
-    "    return grid_tmp.at[:].set(grid + dt * D * laplacian_np_roll(grid))"
+    "    return grid.at[:].set(grid + dt * D * laplacian_np_roll(grid))"
    ]
   },
   {
@@ -5682,7 +5793,7 @@
     }
    ],
    "source": [
-    "grid = init()\n",
+    "grid = init(with_ghosts=False)\n",
     "solution_jax = main_loop_jax(evolve_np_roll_jax, grid)"
    ]
   },
@@ -5705,10 +5816,19 @@
    ],
    "source": [
     "%%timeit\n",
-    "grid = init()\n",
+    "grid = init(with_ghosts=False)\n",
     "solution_jax = main_loop_jax(evolve_np_roll_jax, grid)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert(np.allclose(solution_scipy, solution_jax))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 68,
diff --git a/notebooks/11--Software_Engineering.ipynb b/notebooks/11--Software_Engineering.ipynb
index 7241fa4d9a87d7d0edddf3fad19b2004e9a2fed7..e6408d3f9a60d4a1313d3101d9a418ba1412ae6a 100644
--- a/notebooks/11--Software_Engineering.ipynb
+++ b/notebooks/11--Software_Engineering.ipynb
@@ -535,7 +535,7 @@
     "## PyPI, the Python Package Index\n",
     "* PyPI is the online software repository for Python\n",
     "* https://pypi.python.org/pypi\n",
-    "* Contains nearly 330.000 packages (Oct 2021)\n",
+    "* Contains nearly 590.000 packages (Nov 2024)\n",
     "* Zip files, tar balls, wheel archives are accepted  \n",
     "  (typically generated locally by front-end, e.g., `python -m build`)\n",
     "* Anybody may upload packages after registration"