diff --git a/.patches/exercise-model/exercise-model.patch b/.patches/exercise-model/exercise-model.patch
index ce11c12f724aa4b5534adeb65f059da6eddf4f12..fcbdb5d606a439db4f1334d7d2c7621a26acbcde 100644
--- a/.patches/exercise-model/exercise-model.patch
+++ b/.patches/exercise-model/exercise-model.patch
@@ -1,16 +1,15 @@
 diff -ruN exercises/exercise-model/CMakeLists.txt exercises/solution/exercise-model/CMakeLists.txt
---- exercises/exercise-model/CMakeLists.txt	2024-05-21 14:15:07.153554813 +0200
-+++ exercises/solution/exercise-model/CMakeLists.txt	2024-05-21 14:15:07.213555107 +0200
+--- exercises/exercise-model/CMakeLists.txt	2024-07-12 09:11:32.256227192 +0200
++++ exercises/solution/exercise-model/CMakeLists.txt	2024-07-12 09:11:32.264227248 +0200
 @@ -1,4 +1,4 @@
--set(CMAKE_BUILD_TYPE Release)
  dune_symlink_to_source_files(FILES images params.input)
+ 
 -dumux_add_test(NAME exercise_nonlineardiffusion
-+
 +dumux_add_test(NAME exercise_nonlineardiffusion_sol
                 SOURCES main.cc)
 diff -ruN exercises/exercise-model/main.cc exercises/solution/exercise-model/main.cc
---- exercises/exercise-model/main.cc	2024-05-29 14:31:49.247625156 +0200
-+++ exercises/solution/exercise-model/main.cc	2024-05-21 14:15:07.213555107 +0200
+--- exercises/exercise-model/main.cc	2024-07-12 09:11:32.256227192 +0200
++++ exercises/solution/exercise-model/main.cc	2024-07-12 09:11:32.264227248 +0200
 @@ -41,6 +41,8 @@
  #include <dumux/nonlinear/newtonsolver.hh>
  #include <dumux/assembly/fvassembler.hh>
@@ -38,11 +37,51 @@ diff -ruN exercises/exercise-model/main.cc exercises/solution/exercise-model/mai
 -    // using Problem = GetPropType<TypeTag, Properties::Problem>;
 -    // using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
 -    // using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
--    //
+-
 -    // auto problem = std::make_shared<Problem>(gridGeometry);
 -    // auto sol = createInitialSolution<SolutionVector>(*gridGeometry, imageData);
 -    // auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry);
 -    // gridVariables->init(sol);
+-
+-    // // We initialize the VTK output module and write out the initial concentration field
+-    // VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
+-    // vtkWriter.addVolumeVariable([](const auto& vv){ return vv.priVar(0); }, "imageIntensity");
+-    // vtkWriter.write(0.0);
+-
+-    // // We instantiate time loop using start and end time as well as
+-    // // the time step size from the parameter tree (`params.input`)
+-    // auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(
+-    //     getParam<Scalar>("TimeLoop.TStart", 0.0),
+-    //     getParam<Scalar>("TimeLoop.Dt"),
+-    //     getParam<Scalar>("TimeLoop.TEnd")
+-    // );
+-
+-    // // Next, we choose the type of assembler, linear solver and nonlinear solver
+-    // // and construct instances of these classes.
+-    // using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
+-    // using LinearSolver = SSORCGIstlSolver<LinearSolverTraits<GridGeometry>, LinearAlgebraTraitsFromAssembler<Assembler>>;
+-    // using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;
+-
+-    // auto oldSol = sol; // copy the vector to store state of previous time step
+-    // auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables, timeLoop, oldSol);
+-    // auto linearSolver = std::make_shared<LinearSolver>(gridGeometry->gridView(), gridGeometry->dofMapper());
+-    // Solver solver(assembler, linearSolver);
+-
+-    // // time loop
+-    // timeLoop->start(); do
+-    // {
+-    //     // assemble & solve
+-    //     solver.solve(sol);
+-
+-    //     // make the new solution the old solution
+-    //     oldSol = sol;
+-    //     gridVariables->advanceTimeStep();
+-
+-    //     // advance to the time loop to the next step
+-    //     timeLoop->advanceTimeStep();
+-
+-    //     // write VTK output (writes out the concentration field)
+-    //     vtkWriter.write(timeLoop->time());
 +    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
 +    using Problem = GetPropType<TypeTag, Properties::Problem>;
 +    using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
@@ -52,33 +91,22 @@ diff -ruN exercises/exercise-model/main.cc exercises/solution/exercise-model/mai
 +    auto sol = createInitialSolution<SolutionVector>(*gridGeometry, imageData);
 +    auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry);
 +    gridVariables->init(sol);
- 
-     // We initialize the VTK output module and write out the initial concentration field
--    // VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
--    // vtkWriter.addVolumeVariable([](const auto& vv){ return vv.priVar(0); }, "imageIntensity");
--    // vtkWriter.write(0.0);
++
++    // We initialize the VTK output module and write out the initial concentration field
 +    VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
 +    vtkWriter.addVolumeVariable([](const auto& vv){ return vv.imageIntensity(); }, "imageIntensity");
 +    vtkWriter.write(0.0);
- 
-     // We instantiate time loop using start and end time as well as
-     // the time step size from the parameter tree (`params.input`)
--    // auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(
--    //     getParam<Scalar>("TimeLoop.TStart", 0.0),
--    //     getParam<Scalar>("TimeLoop.Dt"),
--    //     getParam<Scalar>("TimeLoop.TEnd")
--    // );
++
++    // We instantiate time loop using start and end time as well as
++    // the time step size from the parameter tree (`params.input`)
 +    auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(
 +        getParam<Scalar>("TimeLoop.TStart", 0.0),
 +        getParam<Scalar>("TimeLoop.Dt"),
 +        getParam<Scalar>("TimeLoop.TEnd")
 +    );
- 
-     // Next, we choose the type of assembler, linear solver and nonlinear solver
-     // and construct instances of these classes.
--    // using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
--    // using LinearSolver = SSORCGIstlSolver<LinearSolverTraits<GridGeometry>, LinearAlgebraTraitsFromAssembler<Assembler>>;
--    // using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;
++
++    // Next, we choose the type of assembler, linear solver and nonlinear solver
++    // and construct instances of these classes.
 +    using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
 +    using LinearSolver = SSORCGIstlSolver<LinearSolverTraits<GridGeometry>, LinearAlgebraTraitsFromAssembler<Assembler>>;
 +    using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;
@@ -87,37 +115,20 @@ diff -ruN exercises/exercise-model/main.cc exercises/solution/exercise-model/mai
 +    auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables, timeLoop, oldSol);
 +    auto linearSolver = std::make_shared<LinearSolver>(gridGeometry->gridView(), gridGeometry->dofMapper());
 +    Solver solver(assembler, linearSolver);
- 
--    // auto oldSol = sol; // copy the vector to store state of previous time step
--    // auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables, timeLoop, oldSol);
--    // auto linearSolver = std::make_shared<LinearSolver>(gridGeometry->gridView(), gridGeometry->dofMapper());
--    // Solver solver(assembler, linearSolver);
--
--    // // time loop
--    // timeLoop->start(); do
--    // {
--    //     // assemble & solve
--    //     solver.solve(sol);
++
 +    // time loop
 +    timeLoop->start(); do
 +    {
 +        // assemble & solve
 +        solver.solve(sol);
- 
--    //     // make the new solution the old solution
--    //     oldSol = sol;
--    //     gridVariables->advanceTimeStep();
++
 +        // make the new solution the old solution
 +        oldSol = sol;
 +        gridVariables->advanceTimeStep();
- 
--    //     // advance to the time loop to the next step
--    //     timeLoop->advanceTimeStep();
++
 +        // advance to the time loop to the next step
 +        timeLoop->advanceTimeStep();
- 
--    //     // write VTK output (writes out the concentration field)
--    //     vtkWriter.write(timeLoop->time());
++
 +        // write VTK output (writes out the concentration field)
 +        vtkWriter.write(timeLoop->time());
  
@@ -136,7 +147,7 @@ diff -ruN exercises/exercise-model/main.cc exercises/solution/exercise-model/mai
  }// end main
 diff -ruN exercises/exercise-model/model.hh exercises/solution/exercise-model/model.hh
 --- exercises/exercise-model/model.hh	1970-01-01 01:00:00.000000000 +0100
-+++ exercises/solution/exercise-model/model.hh	2024-05-21 14:15:07.213555107 +0200
++++ exercises/solution/exercise-model/model.hh	2024-07-12 09:11:32.264227248 +0200
 @@ -0,0 +1,222 @@
 +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 +// vi: set et ts=4 sw=4 sts=4:
@@ -361,9 +372,9 @@ diff -ruN exercises/exercise-model/model.hh exercises/solution/exercise-model/mo
 +
 +#endif
 diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/README.md
---- exercises/exercise-model/README.md	2024-05-21 14:15:07.153554813 +0200
+--- exercises/exercise-model/README.md	2024-07-12 09:11:32.256227192 +0200
 +++ exercises/solution/exercise-model/README.md	1970-01-01 01:00:00.000000000 +0100
-@@ -1,208 +0,0 @@
+@@ -1,195 +0,0 @@
 -# Exercise Model (DuMuX course)
 -The aim of this exercise is it to learn how to set up a new model (new system of equations).
 -As an example, we implement a nonlinear diffusion equation model and apply it for image denoising.
@@ -373,7 +384,7 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -
 -## Equation and Problem set-up
 -
--We consider a nonlinear diffusion equation on a domain $\Omega \subset \mathbb{R}^2$ and time interval $(0,T]$ with boundary $\partial\Omega$ given by
+-We consider a nonlinear diffusion equation on a domain $\Omega \subset \mathbb{R}^2$, with boundary $\partial\Omega$, and time interval $(0,T]$ given by
 -
 -```math
 -\begin{aligned}
@@ -382,25 +393,20 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -                                                        c &= c^0  && \mathrm{for}\; t = 0,
 -\end{aligned}
 -```
--where for a position $\mathbf{x} \in \Omega$ and time $t$, $c(\mathbf{x},t)$ denotes the unknown evolving concentration field and $D(c)$ the solution-dependent diffusion coefficient.
+-where for a position $\mathbf{x} \in \Omega$ and time $t$, $c(\mathbf{x},t)$ denotes the unknown evolving concentration field, $D(c)$ the solution-dependent diffusion coefficient, and $c^0$ the initial concentration field.
 -
--In this exercise we want to model image denoising such that the primary variable $c$ corresponds to the **image intensity** and $D$ is given (due to Perona & Malik), as
+-In this exercise we want to model image denoising such that the primary variable $c$ corresponds to the **image intensity**, $c^0$ to the noisy image data, and $D$ is given (due to Perona & Malik), as
 -```math
 -D(c) = \frac{1}{1 + \left(\frac{|| \nabla c ||}{K}\right)^2},
 -```
 -with conductance $K$.
 -
--<br><br>
 -## Task 1: Getting familiar with the diffusion example
--<hr>
 -
 -When implementing a new model it is advisable to built upon an already existing model that might be similar and only needs modifications. Since the above equation is a nonlinear diffusion equation, we can built upon the diffusion model implemented in the [diffusion example](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/tree/master/examples/diffusion).
 -The diffusion example also derives the discrete equations using the Box method as spatial discretization scheme.
--<hr>
 -
--<br><br>
 -## Task 2: Set up the model
--<hr>
 -
 -:arrow_right: Copy the `model.hh` file from the diffusion example into `dumux-course/exercises/exercise-model` and choose appropriate class names.
 -
@@ -428,9 +434,7 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -} // end namespace Dumux::Properties
 -```
 -
--<br><br>
 -## Task 3: Set up the test case
--<hr>
 -
 -:arrow_right: Open the file `main.cc`.
 -
@@ -451,7 +455,7 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -
 -:arrow_right: Include the header `model.hh` <br>
 -:arrow_right: To use the new model for this test case, make
--the test type tag inherit properties from the model type tag
+-the `NonlinearDiffusionTest` type tag inherit properties from the model type tag
 -
 -```c++
 -namespace Dumux::Properties::TTag {
@@ -487,11 +491,9 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -
 -:arrow_right: Fix any compiler errors and warnings.
 -
--<br><br>
 -## Task 4: Nonlinear diffusion
--<hr>
 -
--In the local residual class, instead of using a constant `1.0`, we now
+-In the local residual class, instead of using a constant diffusion coefficient of `1.0`, we now
 -want to implement the nonlinear diffusion function specified above. With a `Dune::FieldVector`
 -you can get the 2-norm of the vector via `v.two_norm()` and its square with `v.two_norm2()`. (The reason
 -for having two different interfaces is that the second one is more efficient.)
@@ -507,9 +509,7 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -the derivatives of your residual by hand and implement them. This greatly simplifies implementing
 -nonlinear equations.
 -
--<br><br>
 -## Task 5: Running the image denosing test case
--<hr>
 -
 -:arrow_right: Compile and run like above.
 -
@@ -525,11 +525,9 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -    </center>
 -</figure>
 -
--<br><br>
 -## Task 6: Custom volume variables
--<hr>
 -
--The volume variables represent variables of the control volume. So far we have used
+-The volume variables represent variables related to control volumes. So far we have used
 -the `BasicVolumeVariables` class that only stores the primary variables at control volumes.
 -We have used the interface `priVar` to access the primary variables. In more complex models,
 -we often want to
@@ -541,7 +539,7 @@ diff -ruN exercises/exercise-model/README.md exercises/solution/exercise-model/R
 -
 -:arrow_right: Implement your own `ImageDenoisingVolumeVariables` class (either within a new header file or directly in `model.hh`). As basis you can use the `BasicVolumeVariables`, see `dumux/common/volumevariables.hh`. Copy the class into `model.hh` and rename it.
 -
--<br> Add a function `imageIntensity()` that returns the first primary variable. <br>
+-:arrow_right: Add a function `imageIntensity()` that returns the first primary variable.
 -
 -:arrow_right: In order to use the new `ImageDenoisingVolumeVariables` class in your model, set in your model-specific properties (see `dumux-course/exercises/exercise-properties`):
 -```c++
diff --git a/exercises/exercise-model/CMakeLists.txt b/exercises/exercise-model/CMakeLists.txt
index 6dba1f0cda89446a4ed51963a86929f4a3cd0c94..0cade55c90e8e424516229c1e126bf149a4cc529 100644
--- a/exercises/exercise-model/CMakeLists.txt
+++ b/exercises/exercise-model/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(CMAKE_BUILD_TYPE Release)
 dune_symlink_to_source_files(FILES images params.input)
+
 dumux_add_test(NAME exercise_nonlineardiffusion
                SOURCES main.cc)
diff --git a/exercises/exercise-model/README.md b/exercises/exercise-model/README.md
index 265dfb4a6bd7d4536f2b853c188dbc005dd11f98..e9018ef5d7bbd9d0dcd33e50e680fefbb8dde5cd 100644
--- a/exercises/exercise-model/README.md
+++ b/exercises/exercise-model/README.md
@@ -7,7 +7,7 @@ and solve such a problem with DuMux. We also implement the model for a specific
 
 ## Equation and Problem set-up
 
-We consider a nonlinear diffusion equation on a domain $\Omega \subset \mathbb{R}^2$ and time interval $(0,T]$ with boundary $\partial\Omega$ given by
+We consider a nonlinear diffusion equation on a domain $\Omega \subset \mathbb{R}^2$, with boundary $\partial\Omega$, and time interval $(0,T]$ given by
 
 ```math
 \begin{aligned}
@@ -16,25 +16,20 @@ We consider a nonlinear diffusion equation on a domain $\Omega \subset \mathbb{R
                                                         c &= c^0  && \mathrm{for}\; t = 0,
 \end{aligned}
 ```
-where for a position $\mathbf{x} \in \Omega$ and time $t$, $c(\mathbf{x},t)$ denotes the unknown evolving concentration field and $D(c)$ the solution-dependent diffusion coefficient.
+where for a position $\mathbf{x} \in \Omega$ and time $t$, $c(\mathbf{x},t)$ denotes the unknown evolving concentration field, $D(c)$ the solution-dependent diffusion coefficient, and $c^0$ the initial concentration field.
 
-In this exercise we want to model image denoising such that the primary variable $c$ corresponds to the **image intensity** and $D$ is given (due to Perona & Malik), as
+In this exercise we want to model image denoising such that the primary variable $c$ corresponds to the **image intensity**, $c^0$ to the noisy image data, and $D$ is given (due to Perona & Malik), as
 ```math
 D(c) = \frac{1}{1 + \left(\frac{|| \nabla c ||}{K}\right)^2},
 ```
 with conductance $K$.
 
-<br><br>
 ## Task 1: Getting familiar with the diffusion example
-<hr>
 
 When implementing a new model it is advisable to built upon an already existing model that might be similar and only needs modifications. Since the above equation is a nonlinear diffusion equation, we can built upon the diffusion model implemented in the [diffusion example](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/tree/master/examples/diffusion).
 The diffusion example also derives the discrete equations using the Box method as spatial discretization scheme.
-<hr>
 
-<br><br>
 ## Task 2: Set up the model
-<hr>
 
 :arrow_right: Copy the `model.hh` file from the diffusion example into `dumux-course/exercises/exercise-model` and choose appropriate class names.
 
@@ -62,9 +57,7 @@ namespace Dumux::Properties {
 } // end namespace Dumux::Properties
 ```
 
-<br><br>
 ## Task 3: Set up the test case
-<hr>
 
 :arrow_right: Open the file `main.cc`.
 
@@ -85,7 +78,7 @@ const auto imageData = NetPBMReader::readPGM(imageFileName);
 
 :arrow_right: Include the header `model.hh` <br>
 :arrow_right: To use the new model for this test case, make
-the test type tag inherit properties from the model type tag
+the `NonlinearDiffusionTest` type tag inherit properties from the model type tag
 
 ```c++
 namespace Dumux::Properties::TTag {
@@ -121,11 +114,9 @@ make exercise_nonlineardiffusion
 
 :arrow_right: Fix any compiler errors and warnings.
 
-<br><br>
 ## Task 4: Nonlinear diffusion
-<hr>
 
-In the local residual class, instead of using a constant `1.0`, we now
+In the local residual class, instead of using a constant diffusion coefficient of `1.0`, we now
 want to implement the nonlinear diffusion function specified above. With a `Dune::FieldVector`
 you can get the 2-norm of the vector via `v.two_norm()` and its square with `v.two_norm2()`. (The reason
 for having two different interfaces is that the second one is more efficient.)
@@ -141,9 +132,7 @@ As the DuMux assembler uses numeric differentiation to approximate the Jacobian
 the derivatives of your residual by hand and implement them. This greatly simplifies implementing
 nonlinear equations.
 
-<br><br>
 ## Task 5: Running the image denosing test case
-<hr>
 
 :arrow_right: Compile and run like above.
 
@@ -159,11 +148,9 @@ __The final result should look like this:__
     </center>
 </figure>
 
-<br><br>
 ## Task 6: Custom volume variables
-<hr>
 
-The volume variables represent variables of the control volume. So far we have used
+The volume variables represent variables related to control volumes. So far we have used
 the `BasicVolumeVariables` class that only stores the primary variables at control volumes.
 We have used the interface `priVar` to access the primary variables. In more complex models,
 we often want to
@@ -175,7 +162,7 @@ In this task we will implement a custom volume variable class, while in this sim
 
 :arrow_right: Implement your own `ImageDenoisingVolumeVariables` class (either within a new header file or directly in `model.hh`). As basis you can use the `BasicVolumeVariables`, see `dumux/common/volumevariables.hh`. Copy the class into `model.hh` and rename it.
 
-<br> Add a function `imageIntensity()` that returns the first primary variable. <br>
+:arrow_right: Add a function `imageIntensity()` that returns the first primary variable.
 
 :arrow_right: In order to use the new `ImageDenoisingVolumeVariables` class in your model, set in your model-specific properties (see `dumux-course/exercises/exercise-properties`):
 ```c++
diff --git a/exercises/exercise-model/main.cc b/exercises/exercise-model/main.cc
index a036a6eb2e06a20534a69d371fc1ac61ca6bd325..8883fbb81b1bc680c1e99a8d000bc260d0c1b28c 100644
--- a/exercises/exercise-model/main.cc
+++ b/exercises/exercise-model/main.cc
@@ -153,27 +153,27 @@ int main(int argc, char** argv)
     // using Problem = GetPropType<TypeTag, Properties::Problem>;
     // using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
     // using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
-    //
+
     // auto problem = std::make_shared<Problem>(gridGeometry);
     // auto sol = createInitialSolution<SolutionVector>(*gridGeometry, imageData);
     // auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry);
     // gridVariables->init(sol);
 
-    // We initialize the VTK output module and write out the initial concentration field
+    // // We initialize the VTK output module and write out the initial concentration field
     // VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
     // vtkWriter.addVolumeVariable([](const auto& vv){ return vv.priVar(0); }, "imageIntensity");
     // vtkWriter.write(0.0);
 
-    // We instantiate time loop using start and end time as well as
-    // the time step size from the parameter tree (`params.input`)
+    // // We instantiate time loop using start and end time as well as
+    // // the time step size from the parameter tree (`params.input`)
     // auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(
     //     getParam<Scalar>("TimeLoop.TStart", 0.0),
     //     getParam<Scalar>("TimeLoop.Dt"),
     //     getParam<Scalar>("TimeLoop.TEnd")
     // );
 
-    // Next, we choose the type of assembler, linear solver and nonlinear solver
-    // and construct instances of these classes.
+    // // Next, we choose the type of assembler, linear solver and nonlinear solver
+    // // and construct instances of these classes.
     // using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
     // using LinearSolver = SSORCGIstlSolver<LinearSolverTraits<GridGeometry>, LinearAlgebraTraitsFromAssembler<Assembler>>;
     // using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;