From c515a261161d80a219f1405d4f16f011e4c5ebab Mon Sep 17 00:00:00 2001
From: Timo Koch <timokoch@math.uio.no>
Date: Fri, 24 Mar 2023 17:42:11 +0100
Subject: [PATCH] [example][cahnhilliard] Further expand and improve example
 description

---
 examples/cahn_hilliard/.doc_config            |  16 +-
 examples/cahn_hilliard/CMakeLists.txt         |   1 +
 examples/cahn_hilliard/README.md              | 131 ++++--
 examples/cahn_hilliard/doc/_intro.md          | 127 ++++--
 .../doc/{mainfile_intro.md => _main.md}       |   0
 .../doc/{modelheader_intro.md => _model.md}   |   0
 .../doc/{mainfile.md => main.md}              | 374 +++++++----------
 .../doc/{modelheader.md => model.md}          | 311 +++++++--------
 examples/cahn_hilliard/main.cc                | 376 +++++++-----------
 examples/cahn_hilliard/model.hh               | 307 +++++++-------
 examples/diffusion/doc/main.md                |   4 +-
 examples/diffusion/main.cc                    |   4 +-
 12 files changed, 757 insertions(+), 894 deletions(-)
 rename examples/cahn_hilliard/doc/{mainfile_intro.md => _main.md} (100%)
 rename examples/cahn_hilliard/doc/{modelheader_intro.md => _model.md} (100%)
 rename examples/cahn_hilliard/doc/{mainfile.md => main.md} (53%)
 rename examples/cahn_hilliard/doc/{modelheader.md => model.md} (50%)

diff --git a/examples/cahn_hilliard/.doc_config b/examples/cahn_hilliard/.doc_config
index d1a87cc8a3..6df89661cd 100644
--- a/examples/cahn_hilliard/.doc_config
+++ b/examples/cahn_hilliard/.doc_config
@@ -3,21 +3,21 @@
         "doc/_intro.md"
     ],
 
-    "doc/mainfile.md" : [
-        "doc/mainfile_intro.md",
-        "main.cc"
+    "doc/model.md" : [
+        "doc/_model.md",
+        "model.hh"
     ],
 
-    "doc/modelheader.md" : [
-        "doc/modelheader_intro.md",
-        "model.hh"
+    "doc/main.md" : [
+        "doc/_main.md",
+        "main.cc"
     ],
 
     "navigation" : {
         "mainpage" : "README.md",
         "subpages" : [
-            "doc/modelheader.md",
-            "doc/mainfile.md"
+            "doc/model.md",
+            "doc/main.md"
         ],
         "subtitles" : [
             "Model implementation",
diff --git a/examples/cahn_hilliard/CMakeLists.txt b/examples/cahn_hilliard/CMakeLists.txt
index 73113b9863..f69a567fda 100644
--- a/examples/cahn_hilliard/CMakeLists.txt
+++ b/examples/cahn_hilliard/CMakeLists.txt
@@ -14,6 +14,7 @@ dumux_add_test(NAME example_cahn_hilliard
 dumux_add_test(NAME example_cahn_hilliard_parallel
                LABELS example
                TARGET example_cahn_hilliard
+               CMAKE_GUARD MPI_FOUND
                COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py
                CMD_ARGS --script fuzzy
                         --files ${CMAKE_SOURCE_DIR}/test/references/example_cahn_hilliard_parallel_p0-reference.vtu
diff --git a/examples/cahn_hilliard/README.md b/examples/cahn_hilliard/README.md
index a192a3346b..11552d3711 100644
--- a/examples/cahn_hilliard/README.md
+++ b/examples/cahn_hilliard/README.md
@@ -1,70 +1,127 @@
 <!-- Important: This file has been automatically generated by generate_example_docs.py. Do not edit this file directly! -->
 
-# Cahn-Hilliard model
+# Cahn-Hilliard equation
 
-A random initial distribution of two phases separating according to the Cahn-Hilliard model.
+In this example, we create an application solving the Cahn-Hilliard equation which describes
+a phase separation process.
+This example is implemented in three files, [`model.hh`](model.hh), [`params.input`](params.input), [`main.cc`](main.cc).
+The [`model.hh`](model.hh) header implements a simple self-contained Cahn-Hilliard equation model
+to be used with a control-volume finite element discretization like the Box method.
+The [`main.cc`](main.cc) source file contains the main program and [`params.input`](params.input) contains
+runtime parameters. The executable is configured in [`CMakeLists.txt`](CMakeLists.txt) and created with CMake.
+The source code will be discussed in Part 1 and Part 2 after an introduction to the equations and the problem description.
 
 __The main points illustrated in this example are__
-* A base setup for solving a nonlinear partial differential equation
 
-__Table of contents__. This description is structured as follows:
-
-[[_TOC_]]
+* setting up a model with two coupled nonlinear equations
+* creating a vector-valued random initial solution in parallel
+* solving a time-dependent nonlinear problem
+* using a Newton solver instance
+* enabling partial reassembly for the Newton method
+* using adaptive time stepping
 
-__Result__. The result will look like below.
-You see the concentration variable $c$ capturing the phase distribution after 1 second.
+__Results__. The result will look like below.
 
 <figure>
     <center>
-        <img src="img/results_phase_distribution.png" alt="Concentration field modeling phase distribution" width="50%"/>
+        <img src="img/animation.gif" alt="Cahn Hilliard result" width="400px"/>
+        <figcaption> <b> Fig.1 </b> - Evolution of an initially random concentration field according to the Cahn-Hilliard equation.</figcaption>
     </center>
 </figure>
 
-## Problem set-up
+__Table of contents__. This description is structured as follows:
 
-A square two-dimensional domain is initialized with a concentration `c`, randomly perturbed by
-noise following a uniform distribution between 0.41 and 0.43. 
-With time the concentration field evolves towards attaining mostly values near to 0 or 1 while
-conserving the total concentration, modeling the separation of two immiscible fluids.
+[TOC]
 
-## Model description
+## Equation and problem description
 
-The Cahn-Hilliard model uses a pair of second order nonlinear partial differential equations,
-for a concentration field $c$ and a chemical potential $\mu$. The former is conserved and
-evolves with a flux proportional to $\nabla \mu$, while the latter depends on the laplacian of
-the concentration $\nabla^2 c$ and a nonlinear function of $c$, a derivative of a free energy
-functional.
+The Cahn-Hilliard equation is a forth order partial differential equation (PDE) and reads
 
 ```math
-\frac{\partial c}{\partial t} = M \nabla^2 \mu \\
-\mu = E (4 c^3 - 6 c^2 +2 c) - \gamma \nabla^2 c
+\frac{\partial c}{\partial t} - \nabla\cdot \left( M \nabla\left[ \frac{\partial f}{\partial c} - \gamma \nabla^2 c \right]\right) = 0 \quad \text{in} \; \Omega \times (0, T]
 ```
 
-Here $M$ denotes the mobility of the concentration, while the energy scale $E$ and surface tension
-$\gamma$ balance the two contributions of the free energy functional.
+with the concentration $c(x,t)$, the mobility coefficient $M$, and surface tension $\gamma$.
+The domain $\Omega \subset \mathbb{R}^2$ is initialized with a concentration field
+$c(x,t=0) = 0.42 + \zeta$, randomly perturbed by
+noise $\zeta$ following a uniform distribution $\zeta \sim U(-0.02, 0.02)$.
+With time the concentration field evolves towards attaining mostly values near to $0$ or $1$ while
+conserving the total concentration. The model describes the separation of two immiscible fluids.
 
-## Implementation of a simple nonlinear PDE
+The fourth order PDE cannot be solved by a standard finite volume scheme. We therefore
+split the equation in two second order PDEs
 
-For this example the C++ code is contained in two files, `main.cc` and `model.hh`.
-The `model.hh` header contains the `ModelTraits` and properties for the model TypeTag
-`CahnHilliardModel`, as well as volumevariables `CahnHilliardModelVolumeVariables`
-and the basic local residual `CahnHilliardModelLocalResidual`.
-The residual's storage term is extended in the problem definition `CahnHilliardTestProblem`
-in the file `main.cc`, which also contains more specific properties of the problem setup (for
-type tag `CahnHilliardTest`) as well as the actual simulation loop usually found in a mainfile.
+```math
+\begin{aligned}
+\frac{\partial c}{\partial t} - \nabla\cdot \left( M \nabla \mu \right) &= 0\\
+- \gamma \nabla^2 c &= \mu - \frac{\partial f}{\partial c}.
+\end{aligned}
+```
+
+were $\mu$ is called chemical potential.
+Using the free energy functional $f = E c^2(1-c)^2$, where $E$ is a scaling parameter,
+we obtain
+
+```math
+\begin{aligned}
+\frac{\partial c}{\partial t} - \nabla\cdot \left( M \nabla \mu \right) &= 0\\
+- \gamma \nabla^2 c &= \mu - E (4 c^3 - 6 c^2 +2 c).
+\end{aligned}
+```
+
+The concentration field is conserved and
+evolves with a flux proportional to $\nabla \mu$, while the latter depends on the Laplacian of
+the concentration $\nabla^2 c$ and the free energy functional which is a nonlinear function of $c$. We therefore have a system of two coupled nonlinear PDEs. We will use homogeneous Neumann
+boundary conditions everywhere on $\partial \Omega$. This means that the total concentration in $\Omega$ stays constant.
+
+For the implementation, it is useful to write the conservation equation in the following abstract
+form in vector notation,
 
-More details are given in [main.cc](doc/mainfile.md) and [model.hh](doc/modelheader.md).
+```math
+\frac{\partial \boldsymbol{S}(\boldsymbol{U})}{\partial t} + \nabla\cdot \boldsymbol{F}(\boldsymbol{U}) = \boldsymbol{Q}(\boldsymbol{U})
+```
+
+where for the Cahn-Hillard equation, we have
+
+```math
+\boldsymbol{U} := \begin{bmatrix} c \\ \mu \end{bmatrix}, \quad
+\boldsymbol{S}(\boldsymbol{U}) := \begin{bmatrix} c \\ 0 \end{bmatrix}, \quad
+\boldsymbol{F}(\boldsymbol{U}) := \begin{bmatrix} -M \nabla \mu \\ -\gamma \nabla c \end{bmatrix}, \quad
+\boldsymbol{Q}(\boldsymbol{U}) := \begin{bmatrix} 0 \\ \mu - E (4 c^3 - 6 c^2 +2 c) \end{bmatrix}.
+```
 
-Additionally the folder contains `params.input` containing runtime parameters and the build
-system file `CMakeLists.txt` defining conditions for the accompanying test.
+### Model parameters
+
+* $\Omega = [0,1]\times[0,1]$ (unit square)
+* End time $T = 1$, initial time step size $\Delta t = 0.0025$, and maximum time step size $\Delta t = 0.01$
+* $100 \times 100$ cells (structured Cartesian grid)
+* $M = 0.0001$
+* $\gamma = 0.01$
+* $f = E c^2(1-c)^2$, with $E = 100$
+* $c^0_{h,B} = 0.42 + \zeta$, where $\zeta \sim U(-0.02,0.02)$
+* $\mu^0_{h,B} = 0.0$
+* homogeneous Neumann boundary conditions
+
+### Running the example in parallel
+
+See [Diffusion equation example](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/README.md).
+
+# Implementation
+
+For this example the C++ code is contained in two files, [`model.hh`](model.hh) and [`main.cc`](main.cc). The [`model.hh`](model.hh) header contains the `ModelTraits` and properties for the
+model type tag `CahnHilliardModel`, as well as the volume variables class
+`CahnHilliardModelVolumeVariables` and the local residual class `CahnHilliardModelLocalResidual`.
+The source term is extended in the problem definition `CahnHilliardTestProblem`
+in the file `main.cc`, which also contains more specific properties of the problem setup (for
+type tag `CahnHilliardTest`) as well as the actual simulation loop usually found in the main function.
 
 ## Part 1: Model implementation
 
-| [:arrow_right: Click to continue with part 1 of the documentation](doc/modelheader.md) |
+| [:arrow_right: Click to continue with part 1 of the documentation](doc/model.md) |
 |---:|
 
 
 ## Part 2: Main program
 
-| [:arrow_right: Click to continue with part 2 of the documentation](doc/mainfile.md) |
+| [:arrow_right: Click to continue with part 2 of the documentation](doc/main.md) |
 |---:|
diff --git a/examples/cahn_hilliard/doc/_intro.md b/examples/cahn_hilliard/doc/_intro.md
index 0c63dab293..42f94f6c00 100644
--- a/examples/cahn_hilliard/doc/_intro.md
+++ b/examples/cahn_hilliard/doc/_intro.md
@@ -1,57 +1,114 @@
-# Cahn-Hilliard model
+# Cahn-Hilliard equation
 
-A random initial distribution of two phases separating according to the Cahn-Hilliard model.
+In this example, we create an application solving the Cahn-Hilliard equation which describes
+a phase separation process.
+This example is implemented in three files, [`model.hh`](model.hh), [`params.input`](params.input), [`main.cc`](main.cc).
+The [`model.hh`](model.hh) header implements a simple self-contained Cahn-Hilliard equation model
+to be used with a control-volume finite element discretization like the Box method.
+The [`main.cc`](main.cc) source file contains the main program and [`params.input`](params.input) contains
+runtime parameters. The executable is configured in [`CMakeLists.txt`](CMakeLists.txt) and created with CMake.
+The source code will be discussed in Part 1 and Part 2 after an introduction to the equations and the problem description.
 
 __The main points illustrated in this example are__
-* A base setup for solving a nonlinear partial differential equation
 
-__Table of contents__. This description is structured as follows:
-
-[[_TOC_]]
+* setting up a model with two coupled nonlinear equations
+* creating a vector-valued random initial solution in parallel
+* solving a time-dependent nonlinear problem
+* using a Newton solver instance
+* enabling partial reassembly for the Newton method
+* using adaptive time stepping
 
-__Result__. The result will look like below.
-You see the concentration variable $c$ capturing the phase distribution after 1 second.
+__Results__. The result will look like below.
 
 <figure>
     <center>
-        <img src="img/results_phase_distribution.png" alt="Concentration field modeling phase distribution" width="50%"/>
+        <img src="img/animation.gif" alt="Cahn Hilliard result" width="400px"/>
+        <figcaption> <b> Fig.1 </b> - Evolution of an initially random concentration field according to the Cahn-Hilliard equation.</figcaption>
     </center>
 </figure>
 
-## Problem set-up
+__Table of contents__. This description is structured as follows:
 
-A square two-dimensional domain is initialized with a concentration `c`, randomly perturbed by
-noise following a uniform distribution between 0.41 and 0.43. 
-With time the concentration field evolves towards attaining mostly values near to 0 or 1 while
-conserving the total concentration, modeling the separation of two immiscible fluids.
+[TOC]
 
-## Model description
+## Equation and problem description
 
-The Cahn-Hilliard model uses a pair of second order nonlinear partial differential equations,
-for a concentration field $c$ and a chemical potential $\mu$. The former is conserved and
-evolves with a flux proportional to $\nabla \mu$, while the latter depends on the laplacian of
-the concentration $\nabla^2 c$ and a nonlinear function of $c$, a derivative of a free energy
-functional.
+The Cahn-Hilliard equation is a forth order partial differential equation (PDE) and reads
 
 ```math
-\frac{\partial c}{\partial t} = M \nabla^2 \mu \\
-\mu = E (4 c^3 - 6 c^2 +2 c) - \gamma \nabla^2 c
+\frac{\partial c}{\partial t} - \nabla\cdot \left( M \nabla\left[ \frac{\partial f}{\partial c} - \gamma \nabla^2 c \right]\right) = 0 \quad \text{in} \; \Omega \times (0, T]
 ```
 
-Here $M$ denotes the mobility of the concentration, while the energy scale $E$ and surface tension
-$\gamma$ balance the two contributions of the free energy functional.
+with the concentration $c(x,t)$, the mobility coefficient $M$, and surface tension $\gamma$.
+The domain $\Omega \subset \mathbb{R}^2$ is initialized with a concentration field
+$c(x,t=0) = 0.42 + \zeta$, randomly perturbed by
+noise $\zeta$ following a uniform distribution $\zeta \sim U(-0.02, 0.02)$.
+With time the concentration field evolves towards attaining mostly values near to $0$ or $1$ while
+conserving the total concentration. The model describes the separation of two immiscible fluids.
 
-## Implementation of a simple nonlinear PDE
+The fourth order PDE cannot be solved by a standard finite volume scheme. We therefore
+split the equation in two second order PDEs
 
-For this example the C++ code is contained in two files, `main.cc` and `model.hh`.
-The `model.hh` header contains the `ModelTraits` and properties for the model TypeTag
-`CahnHilliardModel`, as well as volumevariables `CahnHilliardModelVolumeVariables`
-and the basic local residual `CahnHilliardModelLocalResidual`.
-The residual's storage term is extended in the problem definition `CahnHilliardTestProblem`
-in the file `main.cc`, which also contains more specific properties of the problem setup (for
-type tag `CahnHilliardTest`) as well as the actual simulation loop usually found in a mainfile.
+```math
+\begin{aligned}
+\frac{\partial c}{\partial t} - \nabla\cdot \left( M \nabla \mu \right) &= 0\\
+- \gamma \nabla^2 c &= \mu - \frac{\partial f}{\partial c}.
+\end{aligned}
+```
+
+were $\mu$ is called chemical potential.
+Using the free energy functional $f = E c^2(1-c)^2$, where $E$ is a scaling parameter,
+we obtain
+
+```math
+\begin{aligned}
+\frac{\partial c}{\partial t} - \nabla\cdot \left( M \nabla \mu \right) &= 0\\
+- \gamma \nabla^2 c &= \mu - E (4 c^3 - 6 c^2 +2 c).
+\end{aligned}
+```
+
+The concentration field is conserved and
+evolves with a flux proportional to $\nabla \mu$, while the latter depends on the Laplacian of
+the concentration $\nabla^2 c$ and the free energy functional which is a nonlinear function of $c$. We therefore have a system of two coupled nonlinear PDEs. We will use homogeneous Neumann
+boundary conditions everywhere on $\partial \Omega$. This means that the total concentration in $\Omega$ stays constant.
+
+For the implementation, it is useful to write the conservation equation in the following abstract
+form in vector notation,
 
-More details are given in [main.cc](doc/mainfile.md) and [model.hh](doc/modelheader.md).
+```math
+\frac{\partial \boldsymbol{S}(\boldsymbol{U})}{\partial t} + \nabla\cdot \boldsymbol{F}(\boldsymbol{U}) = \boldsymbol{Q}(\boldsymbol{U})
+```
+
+where for the Cahn-Hillard equation, we have
+
+```math
+\boldsymbol{U} := \begin{bmatrix} c \\ \mu \end{bmatrix}, \quad
+\boldsymbol{S}(\boldsymbol{U}) := \begin{bmatrix} c \\ 0 \end{bmatrix}, \quad
+\boldsymbol{F}(\boldsymbol{U}) := \begin{bmatrix} -M \nabla \mu \\ -\gamma \nabla c \end{bmatrix}, \quad
+\boldsymbol{Q}(\boldsymbol{U}) := \begin{bmatrix} 0 \\ \mu - E (4 c^3 - 6 c^2 +2 c) \end{bmatrix}.
+```
 
-Additionally the folder contains `params.input` containing runtime parameters and the build
-system file `CMakeLists.txt` defining conditions for the accompanying test.
+### Model parameters
+
+* $\Omega = [0,1]\times[0,1]$ (unit square)
+* End time $T = 1$, initial time step size $\Delta t = 0.0025$, and maximum time step size $\Delta t = 0.01$
+* $100 \times 100$ cells (structured Cartesian grid)
+* $M = 0.0001$
+* $\gamma = 0.01$
+* $f = E c^2(1-c)^2$, with $E = 100$
+* $c^0_{h,B} = 0.42 + \zeta$, where $\zeta \sim U(-0.02,0.02)$
+* $\mu^0_{h,B} = 0.0$
+* homogeneous Neumann boundary conditions
+
+### Running the example in parallel
+
+See [Diffusion equation example](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/README.md).
+
+# Implementation
+
+For this example the C++ code is contained in two files, [`model.hh`](model.hh) and [`main.cc`](main.cc). The [`model.hh`](model.hh) header contains the `ModelTraits` and properties for the
+model type tag `CahnHilliardModel`, as well as the volume variables class
+`CahnHilliardModelVolumeVariables` and the local residual class `CahnHilliardModelLocalResidual`.
+The source term is extended in the problem definition `CahnHilliardTestProblem`
+in the file `main.cc`, which also contains more specific properties of the problem setup (for
+type tag `CahnHilliardTest`) as well as the actual simulation loop usually found in the main function.
diff --git a/examples/cahn_hilliard/doc/mainfile_intro.md b/examples/cahn_hilliard/doc/_main.md
similarity index 100%
rename from examples/cahn_hilliard/doc/mainfile_intro.md
rename to examples/cahn_hilliard/doc/_main.md
diff --git a/examples/cahn_hilliard/doc/modelheader_intro.md b/examples/cahn_hilliard/doc/_model.md
similarity index 100%
rename from examples/cahn_hilliard/doc/modelheader_intro.md
rename to examples/cahn_hilliard/doc/_model.md
diff --git a/examples/cahn_hilliard/doc/mainfile.md b/examples/cahn_hilliard/doc/main.md
similarity index 53%
rename from examples/cahn_hilliard/doc/mainfile.md
rename to examples/cahn_hilliard/doc/main.md
index ef55ebc606..778b01e761 100644
--- a/examples/cahn_hilliard/doc/mainfile.md
+++ b/examples/cahn_hilliard/doc/main.md
@@ -1,60 +1,78 @@
 <!-- Important: This file has been automatically generated by generate_example_docs.py. Do not edit this file directly! -->
 
 
-| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_left: Go back to part 1](modelheader.md) |
+| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_left: Go back to part 1](model.md) |
 |---|---:|
 
 
 
-# Problem, test properties/traits and main program flow (`main.cc`)
-In this example the file `main.cc` contains the problem class `CahnHilliardTestProblem`,
-properties and traits specific to the test case as well as the main program flow in the form of
-`main` function.
+# Cahn-Hilliard equation main program
 
+The file [`main.cc`](main.cc) contains the problem class `CahnHilliardTestProblem`,
+properties and traits specific to the test case as well as the `main` function.
+The setup consists of four steps:
+1. Define the problem setting boundary conditions and the diffusion coefficient
+2. Configure the property system reusing the model defined in Part 1
+3. Define a function for setting the random initial condition
+4. The main program defining all steps of the program
 
-```cpp
-#include <config.h>
-```
+__Table of contents__
 
-## Problem
-The __problem class__ defines boundary conditions and extends the storage term defined in the
-model's localresidual by the derivative of the free energy.
+[TOC]
 
+We start in `main.cc` with the necessary header includes:
+<details><summary> Click to show includes</summary>
 
-<details open>
-<summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../main.cc))</summary>
-
-### Include headers
+```cpp
 
+#include <config.h>
+#include <type_traits>
+#include <random>
 
-```cpp
-// use the property system and runtime parameters
+#include <dumux/common/initialize.hh>
 #include <dumux/common/properties.hh>
 #include <dumux/common/parameters.hh>
-// common DuMux vector for discretized equations
 #include <dumux/common/numeqvector.hh>
-// types of boundary conditions to use
 #include <dumux/common/boundarytypes.hh>
-// generic problem for finite volume simulations
 #include <dumux/common/fvproblem.hh>
+
+#include <dumux/discretization/box.hh>
+#include <dumux/linear/linearsolvertraits.hh>
+#include <dumux/linear/linearalgebratraits.hh>
+#include <dumux/linear/istlsolvers.hh>
+#include <dumux/nonlinear/newtonsolver.hh>
+#include <dumux/assembly/fvassembler.hh>
+
+#include <dune/grid/yaspgrid.hh>
+#include <dumux/io/vtkoutputmodule.hh>
+#include <dumux/io/grid/gridmanager_yasp.hh>
+
+#include "model.hh"
 ```
 
+</details>
+
+## 1. The problem class
 
-### The problem class `CahnHilliardTestProblem`
-In this abstract problem we inherit from the generic `FVProblem`.
+The problem class implements boundary conditions and the source term.
+It also provides an interface that is used by the local residual (see Part 1) to obtain
+the mobility and surface tension. The values are read from the parameter configuration tree.
+
+<details open>
+<summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../main.cc))</summary>
 
 
 ```cpp
 namespace Dumux {
-
 template<class TypeTag>
 class CahnHilliardTestProblem : public FVProblem<TypeTag>
 {
 ```
 
-<details><summary> Click to show alias definitions and local variables</summary>
+<details><summary> Click to show boilerplate code</summary>
 
 ```cpp
+
     using ParentType = FVProblem<TypeTag>;
     using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
     using FVElementGeometry = typename GridGeometry::LocalView;
@@ -71,6 +89,8 @@ class CahnHilliardTestProblem : public FVProblem<TypeTag>
 ```
 
 </details>
+In the constructor, we read the parameter constants from the
+parameter tree (which is initialized with the content of `params.input`).
 
 ```cpp
 public:
@@ -83,13 +103,9 @@ public:
     }
 ```
 
-
-### Problem source term
-
-Here we implement the derivative of the free energy, setting a source for the equation for
-the chemical potential. The `computeSource` function in the local residual adds the terms
-defined here.
-
+In the `source` function, we implement the derivative of the free energy.
+This demonstrates how parts of the local residual can be split into model specific
+parts and parts that might change from scenario to scenario.
 
 ```cpp
     template<class ElementVolumeVariables>
@@ -105,13 +121,9 @@ defined here.
     }
 ```
 
-
-### Boundary conditions
-
 For the boundary we choose boundary flux (or Neumann) conditions for all equations and on
 every part of the boundary, specifying zero flux everywhere for both equations.
 
-
 ```cpp
     BoundaryTypes boundaryTypesAtPos(const GlobalPosition& globalPos) const
     {
@@ -124,11 +136,9 @@ every part of the boundary, specifying zero flux everywhere for both equations.
     { return { 0.0, 0.0 }; }
 ```
 
-
-<details><summary> Click to show coefficients and private variables</summary>
-The problem class offers access to the mobility and surface tension coefficients as read from
-the parameter file by default `params.input`.
-
+The parameters interfaces are used in the local residual (see Part 1).
+We can name this interface however we want as long as we adapt the calling site
+in the `LocalResidual` class in `model.hh`.
 
 ```cpp
     Scalar mobility() const
@@ -142,136 +152,61 @@ private:
     Scalar surfaceTension_;
     Scalar energyScale_;
 };
-
 } // end namespace Dumux
 ```
 
-</details>
 
 </details>
 
-## Test case properties/traits
-Within the `Dumux::Properties` namespace we specialize properties and traits to the considered
-test case by using the test's TypeTag.
+## 2. Property tag and specializations
+
+We create a new tag `DiffusionTest` that inherits all properties
+specialized for the tag `DiffusionModel` (we created this in Part 1)
+and the tag `BoxModel` which provides types relevant for the spatial
+discretization scheme (see [dumux/discretization/box.hh](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/dumux/discretization/box.hh)).
 
 
 <details open>
 <summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../main.cc))</summary>
 
 
-### Include headers
-
-
-```cpp
-// Include the grid to be used
-#include <dune/grid/yaspgrid.hh>
-// The header for the box discretization scheme
-#include <dumux/discretization/box.hh>
-// The model header including the model traits and properties
-#include "model.hh"
-```
-
-
-### TypeTag `CahnHilliardTest`
-
-We define a type tag for the test case, allowing us to further specify properties and traits. To
-use those set for the Cahn-Hilliard model we inherit from its type tag.
-
-
 ```cpp
-namespace Dumux::Properties {
-
-// Inheriting properties of the Cahn-Hilliard model and the box finite volume discretization
-namespace TTag {
-struct CahnHilliardTest { using InheritsFrom = std::tuple<CahnHilliardModel, BoxModel>; };
-} // end namespace TTag
-```
-
-
-### Test properties
-
-We specify a grid to be used in the test, select our problem class and enable caching.
-
-
-```cpp
-// Set the grid type
-template<class TypeTag>
-struct Grid<TypeTag, TTag::CahnHilliardTest>
-{ using type = Dune::YaspGrid<2>; };
-
-// Select the problem class defined above
-template<class TypeTag>
-struct Problem<TypeTag, TTag::CahnHilliardTest>
-{ using type = CahnHilliardTestProblem<TypeTag>; };
+namespace Dumux::Properties::TTag {
+struct CahnHilliardTest
+{
+    using InheritsFrom = std::tuple<CahnHilliardModel, BoxModel>;
 
-// Enable caching
-template<class TypeTag>
-struct EnableGridVolumeVariablesCache<TypeTag, TTag::CahnHilliardTest>
-{ static constexpr bool value = true; };
+    using Grid = Dune::YaspGrid<2>;
 
-template<class TypeTag>
-struct EnableGridFluxVariablesCache<TypeTag, TTag::CahnHilliardTest>
-{ static constexpr bool value = true; };
-
-template<class TypeTag>
-struct EnableGridGeometryCache<TypeTag, TTag::CahnHilliardTest>
-{ static constexpr bool value = true; };
+    template<class TypeTag>
+    using Problem = CahnHilliardTestProblem<TypeTag>;
 
+    using EnableGridGeometryCache = std::true_type;
+    using EnableGridVolumeVariablesCache = std::true_type;
+    using EnableGridFluxVariablesCache = std::true_type;
+};
 } // end namespace Dumux::Properties
 ```
 
 
 </details>
 
-## The main program flow
-The main program flow in the `main` function is often the only content of `main.cc`. It sets up
-the simulation framework, initializes runtime parameters, creates the grid and storage vectors
-for the variables, primary and secondary. It specifies and constructs and assembler, which
-assembles the discretized residual and system matrix (Jacobian of the model residual), as well as
-linear and nonlinear solvers that solve the resulting linear system and handle the convergence of
-nonlinear iterations. The time loop controls the time stepping, with adaptive time step size in
-coordination with the nonlinear solver.
+## 3. Creating the initial solution
+
+To initialize with a random field in parallel, where each processor
+creates its own random number sequence, we need to communicate the
+resulting values on the processor border and overlap.
+See [Diffusion equation example](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/docs/main.md).
+for details. Here in addition, we need to provide a custom scatter operation
+that handles vector types. We only need to communicate the first entry (concentration).
 
 
 <details open>
 <summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../main.cc))</summary>
 
 
-### Include headers
-
-
-```cpp
-// standard header to generate random initial data
-#include <random>
-// common DuMux header for parallelization
-#include <dumux/common/initialize.hh>
-// headers to use the property system and runtime parameters
-#include <dumux/common/properties.hh>
-#include <dumux/common/parameters.hh>
-// module for VTK output, to write out fields of interest
-#include <dumux/io/vtkoutputmodule.hh>
-// gridmanager for the grid used in the test
-#include <dumux/io/grid/gridmanager_yasp.hh>
-// headers for linear and non-linear solvers as well as the assembler
-#include <dumux/linear/linearsolvertraits.hh>
-#include <dumux/linear/linearalgebratraits.hh>
-#include <dumux/linear/istlsolvers.hh>
-#include <dumux/nonlinear/newtonsolver.hh>
-#include <dumux/assembly/fvassembler.hh>
-```
-
-
-### Creating the initial solution
-
-We define a helper struct and function to handle communication for parallel runs.
-For our initial conditions we create a random field of values around a mean of 0.42.
-The random values are created with an offset based on the processor rank for communication
-purposes, which is removed afterwards. For more information see the description of the
-diffusion example
-[examples/diffusion/doc/main.md](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/doc/main.md)
-
-
 ```cpp
+// functor for data communication with MPI
 struct MinScatter
 {
     template<class A, class B>
@@ -279,6 +214,7 @@ struct MinScatter
     { a[0] = std::min(a[0], b[0]); }
 };
 
+// create the random initial solution
 template<class SolutionVector, class GridGeometry>
 SolutionVector createInitialSolution(const GridGeometry& gg)
 {
@@ -294,7 +230,8 @@ SolutionVector createInitialSolution(const GridGeometry& gg)
         sol[n][1] = 0.0;
     }
 
-    // Take the value of the processor with the minimum rank and subtract the rank offset
+    // We take the value of the processor with the minimum rank
+    // and subtract the rank offset
     if (gg.gridView().comm().size() > 1)
     {
         Dumux::VectorCommDataHandle<
@@ -314,143 +251,122 @@ SolutionVector createInitialSolution(const GridGeometry& gg)
 ```
 
 
-### The main function
+</details>
 
-The main function takes the command line arguments, optionally specifying an input file of
-parameters and/or individual key-value pairs of runtime parameters.
+## 4. The main program
+
+The `main` function sets up the simulation framework, initializes runtime parameters,
+creates the grid and storage vectors
+for the variables, primary and secondary. It specifies and constructs and assembler, which
+assembles the discretized residual and system matrix (Jacobian of the model residual), as well as
+a linear solver. A Newton method is used to solve the nonlinear equations where in each Newton iteration
+the Jacobian and the residual needs to be reassembled and the resulting linear system is solved.
+The time loop controls the time stepping. Here, we let the Newton solver suggest an adaption of
+the time step size based on the convergence history of the nonlinear solver.
+
+
+<details open>
+<summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../main.cc))</summary>
 
 
 ```cpp
 int main(int argc, char** argv)
 {
     using namespace Dumux;
-
-    // define the type tag for this problem
-    using TypeTag = Properties::TTag::CahnHilliardTest;
 ```
 
-
-We initialize parallelization backends as well as runtime parameters
-
+We initialize MPI and/or multithreading backend. When not running
+in parallel the MPI setup is skipped.
 
 ```cpp
-    // maybe initialize MPI and/or multithreading backend
     Dumux::initialize(argc, argv);
-
-    // initialize parameter tree
-    Parameters::init(argc, argv);
 ```
 
-
-### Grid setup
-
-Set up the grid as well as a grid geometry to access the (sub-)control-volumes and their
-faces
-
+Then we initialize parameter tree.
 
 ```cpp
-    // initialize the grid
-    GridManager<GetPropType<TypeTag, Properties::Grid>> gridManager;
-    gridManager.init();
-
-    // we compute on the leaf grid view
-    const auto& leafGridView = gridManager.grid().leafGridView();
-
-    // create the finite volume grid geometry
-    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
-    auto gridGeometry = std::make_shared<GridGeometry>(leafGridView);
+    Parameters::init(argc, argv);
 ```
 
-
-### Problem setup
-
-We instantiate also the problem class according to the test properties
-
+We create an alias for the type tag for this problem
+and extract type information through properties.
 
 ```cpp
-    // the problem (initial and boundary conditions)
+    using TypeTag = Properties::TTag::CahnHilliardTest;
+
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using Grid = GetPropType<TypeTag, Properties::Grid>;
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
     using Problem = GetPropType<TypeTag, Properties::Problem>;
-    auto problem = std::make_shared<Problem>(gridGeometry);
+    using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
+    using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
 ```
 
-
-### Applying initial conditions
-
-After writing the initial data to the storage for previous and current time-step, we
-initialize the grid variables, also computing secondary variables.
-
+We initialize the grid, grid geometry, problem, solution vector, and grid variables.
 
 ```cpp
-    // the solution vector
-    using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
-    auto sol = createInitialSolution<SolutionVector>(*gridGeometry);
-    // copy the vector to store state of previous time step
-    auto oldSol = sol;
+    GridManager<Grid> gridManager;
+    gridManager.init();
 
-    // the grid variables
-    using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
+    auto gridGeometry = std::make_shared<GridGeometry>(gridManager.grid().leafGridView());
+    auto problem = std::make_shared<Problem>(gridGeometry);
+    auto sol = createInitialSolution<SolutionVector>(*gridGeometry);
     auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry);
     gridVariables->init(sol);
 ```
 
-
-### Initialize VTK output
-
+We initialize the VTK output module and write out the initial concentration and chemical potential
 
 ```cpp
-    // initialize the vtk output module
     VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
     vtkWriter.addVolumeVariable([](const auto& vv){ return vv.concentration(); }, "c");
     vtkWriter.addVolumeVariable([](const auto& vv){ return vv.chemicalPotential(); }, "mu");
     vtkWriter.write(0.0);
 ```
 
-
-### Set up time loop
-
+We instantiate time loop using start and end time as well as
+the time step size from the parameter tree (`params.input`)
 
 ```cpp
-    // get some time loop parameters
-    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
-    const auto tEnd = getParam<Scalar>("TimeLoop.TEnd");
-    const auto dt = getParam<Scalar>("TimeLoop.InitialTimeStepSize");
-    const auto maxDt = getParam<Scalar>("TimeLoop.MaxTimeStepSize");
-
-    // instantiate time loop
-    auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(0.0, dt, tEnd);
-    timeLoop->setMaxTimeStepSize(maxDt);
+    auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(
+        getParam<Scalar>("TimeLoop.TStart", 0.0),
+        getParam<Scalar>("TimeLoop.InitialTimeStepSize"),
+        getParam<Scalar>("TimeLoop.TEnd")
+    );
 ```
 
+We set the maximum time step size allowed in the adaptive time stepping scheme.
 
-### Assembler, linear and nonlinear solver
+```cpp
+    timeLoop->setMaxTimeStepSize(getParam<Scalar>("TimeLoop.MaxTimeStepSize"));
+```
 
+Next, we choose the type of assembler, linear solver and PDE solver
+and construct instances of these classes.
 
 ```cpp
-    // the assembler with time loop for a transient problem
     using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
-    auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables, timeLoop, oldSol);
+    using LinearSolver = SSORBiCGSTABIstlSolver<LinearSolverTraits<GridGeometry>, LinearAlgebraTraitsFromAssembler<Assembler>>;
+    using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;
 
-    // the linear solver
-    using LinearSolver = SSORBiCGSTABIstlSolver<
-        LinearSolverTraits<GridGeometry>,
-        LinearAlgebraTraitsFromAssembler<Assembler>
-    >;
+    auto oldSol = sol;
+    auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables, timeLoop, oldSol);
     auto linearSolver = std::make_shared<LinearSolver>(gridGeometry->gridView(), gridGeometry->dofMapper());
-
-    // the solver
-    using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;
     Solver solver(assembler, linearSolver);
 ```
 
-
-### Time loop
+The time loop is where most of the actual computations happen.
+We assemble and solve the linear system of equations, update the solution,
+write the solution to a VTK file and continue until the we reach the
+final simulation time.
 
 
 ```cpp
-    // time loop
     timeLoop->start(); do
     {
-        // assemble & solve
+        // Assemble & solve
+        // Passing the time loop enables the solver to repeat the time step
+        // with a reduced time step size if the Newton solver does not converge.
         solver.solve(sol, *timeLoop);
 
         // make the new solution the old solution
@@ -460,7 +376,7 @@ initialize the grid variables, also computing secondary variables.
         // advance to the time loop to the next step
         timeLoop->advanceTimeStep();
 
-        // write VTK output
+        // write VTK output (concentration field and chemical potential)
         vtkWriter.write(timeLoop->time());
 
         // report statistics of this time step
@@ -470,15 +386,9 @@ initialize the grid variables, also computing secondary variables.
         timeLoop->setTimeStepSize(solver.suggestTimeStepSize(timeLoop->timeStepSize()));
 
     } while (!timeLoop->finished());
-```
-
-
-### Finalize
-
-
-```cpp
-    timeLoop->finalize(leafGridView.comm());
 
+    // print the final report
+    timeLoop->finalize(gridGeometry->gridView().comm());
     return 0;
 }
 ```
@@ -487,6 +397,6 @@ initialize the grid variables, also computing secondary variables.
 </details>
 
 
-| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_left: Go back to part 1](modelheader.md) |
+| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_left: Go back to part 1](model.md) |
 |---|---:|
 
diff --git a/examples/cahn_hilliard/doc/modelheader.md b/examples/cahn_hilliard/doc/model.md
similarity index 50%
rename from examples/cahn_hilliard/doc/modelheader.md
rename to examples/cahn_hilliard/doc/model.md
index 125ea472c8..b9fc2a3d73 100644
--- a/examples/cahn_hilliard/doc/modelheader.md
+++ b/examples/cahn_hilliard/doc/model.md
@@ -1,30 +1,81 @@
 <!-- Important: This file has been automatically generated by generate_example_docs.py. Do not edit this file directly! -->
 
 
-| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_right: Continue with part 2](mainfile.md) |
+| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_right: Continue with part 2](main.md) |
 |---|---:|
 
 
 
-# Volume variables, local residual and model traits (`model.hh`)
-In this example the file `model.hh` contains the classes `CahnHilliardModelVolumeVariables` and
-`CahnHilliardModelLocalResidual` as well as general model traits and properties.
+# Cahn-Hilliard equation model definition
 
-## VolumeVariables
+In the file `model.hh`, we define the model equations and
+set all default model properties. The setup consist of four steps:
+1. Create a model type tag (used to specialize properties)
+2. Define the volume variables class computing and storing variables for a control volume
+3. Define the local residual class implementing the discrete equation
+4. Specialize important properties of the model such that Dumux knows how to assemble the system matrix
 
-The volume variables store the local element volume variables, both primary and secondary.
+We implement the classes
+
+* `CahnHilliardModel` (step 1),
+* `CahnHilliardModelVolumeVariables` (step 2) and
+* `CahnHilliardModelLocalResidual` (step 3).
+
+__Table of contents__
+
+[TOC]
+
+We start in `model.hh` with the necessary header includes:
+<details><summary> Click to show includes</summary>
+
+```cpp
+
+#include <dumux/common/math.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/common/numeqvector.hh>
+```
+
+</details>
+
+## 1. Property Tag
+
+The property tag is simply an empty struct with the name `CahnHilliardModel`.
 
 
 <details open>
 <summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../model.hh))</summary>
 
 
-### The `CahnHilliardModelVolumeVariables` class
+```cpp
+namespace Dumux::Properties::TTag {
+struct CahnHilliardModel {};
+} // end namespace Dumux::Properties::TTag
+```
+
+
+</details>
+
+## 2. The volume variables
+
+The volume variables store the control volume variables, both primary and secondary.
+Let's have a look at the class implementation.
+
+
+<details open>
+<summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../model.hh))</summary>
+
+
+We write out a full volume variable class compatible with the Dumux assembly.
+We have to fulfill the same interface as the [`BasicVolumeVariables`](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/dumux/common/volumevariables.hh).
+To shorten the code, we could also inherit from `BasicVolumeVariables`. However,
+we want to show the entire interface here as a basis for the implementation of more complex variables.
+Note that in `update` we could, for example, compute some secondary variable that is a
+possibly nonlinear function of the primary variables. That secondary variable could be exposed
+via an interface and then used below in the `CahnHilliardModelLocalResidual` class implementation.
 
 
 ```cpp
 namespace Dumux {
-
 template <class Traits>
 class CahnHilliardModelVolumeVariables
 {
@@ -38,16 +89,13 @@ public:
 ```
 
 
-### Update variables
-
-The `update` function stores the local primary variables of the current solution and
-potentially recomputes secondary variables.
+**Update variables:** The `update` function stores the local primary variables of the current solution and
+potentially computes secondary variables. Secondary variables can be nonlinear functions
+of the primary variables.
 
 
 ```cpp
-    /*!
-     * \brief Update all quantities for a given control volume
-     */
+    //! Update all quantities for a given control volume
     template<class ElementSolution, class Problem, class Element, class SubControlVolume>
     void update(const ElementSolution& elemSol,
                 const Problem& problem,
@@ -59,10 +107,9 @@ potentially recomputes secondary variables.
 ```
 
 
-### Access functions
-
-Named and generic functions to access different primary variables
-
+**Access functions:** Named and generic functions to access different primary variables.
+The volumevariables are also expected to return an extrusion factor.
+Here we don't extrude our domain and therefore return $1.0$.
 
 ```cpp
     Scalar concentration() const
@@ -76,113 +123,53 @@ Named and generic functions to access different primary variables
 
     const PrimaryVariables& priVars() const
     { return priVars_; }
-```
-
-
-### Extrusion factor
-
-The volumevariables are also expected to return the extrusion factor
-
 
-```cpp
-    // for compatibility with more general models
     Scalar extrusionFactor() const
     { return 1.0; }
-```
-
-
-### Storage of local variables
 
-
-```cpp
 private:
     PrimaryVariables priVars_;
 };
-
 } // end namespace Dumux
 ```
 
 
 </details>
 
-## LocalResidual
+## 3. The local residual
 
-The local residual defines the discretized and integrated partial differential equation through
-terms for storage, fluxes and sources, with the residual given as
-d/dt storage + div(fluxes) - sources = 0
-The individual terms can be further adjusted or replaced in the more specific problem.
+The local residual assembles the contribution to the residual for
+all degrees of freedom associated with an element.
+See [examples/diffusion/doc/model.hh](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/doc/main.md)
+for a more detailed explanation for control-volume finite element method local residuals.
+Let's have a look at the class implementation.
 
 
 <details open>
 <summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../model.hh))</summary>
 
 
-### Include headers
-
-
-```cpp
-#include <dumux/common/math.hh>
-// use the property system
-#include <dumux/common/properties.hh>
-// common DuMux vector for discretized equations
-#include <dumux/common/numeqvector.hh>
-```
-
-
-### The local residual class `CahnHilliardModelLocalResidual` inherits from a base class set in
+The class `CahnHilliardModelLocalResidual` inherits from a base class set in
 the model properties, depending on the discretization scheme.
-
+See [examples/diffusion/doc/model.hh](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/doc/main.md)
+for details on the `BaseLocalResidual`.
 
 ```cpp
 namespace Dumux {
-
 template<class TypeTag>
 class CahnHilliardModelLocalResidual
 : public GetPropType<TypeTag, Properties::BaseLocalResidual>
 {
-    // the base local residual is selected depending on the chosen discretization scheme
-    using ParentType = GetPropType<TypeTag, Properties::BaseLocalResidual>;
 ```
 
 
-<details><summary> Click to show alias definitions</summary>
-
-```cpp
-    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
-    using Problem = GetPropType<TypeTag, Properties::Problem>;
-
-    using NumEqVector = Dumux::NumEqVector<GetPropType<TypeTag, Properties::PrimaryVariables>>;
-    using VolumeVariables = typename GetPropType<TypeTag, Properties::GridVolumeVariables>::VolumeVariables;
-
-    using ElementVolumeVariables = typename GetPropType<TypeTag, Properties::GridVolumeVariables>::LocalView;
-    using ElementFluxVariablesCache = typename GetPropType<TypeTag, Properties::GridFluxVariablesCache>::LocalView;
-    using FVElementGeometry = typename GetPropType<TypeTag, Properties::GridGeometry>::LocalView;
-
-    using SubControlVolume = typename FVElementGeometry::SubControlVolume;
-    using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace;
-    using GridView = typename GetPropType<TypeTag, Properties::GridGeometry>::GridView;
-    using Element = typename GridView::template Codim<0>::Entity;
-
-    using ModelTraits = GetPropType<TypeTag, Properties::ModelTraits>;
-    using Indices = typename ModelTraits::Indices;
-    static constexpr int dimWorld = GridView::dimensionworld;
-public:
-    using ParentType::ParentType;
-```
-
-</details>
-
-### The storage term
-The function `computeStorage` receives the volumevariables at the previous or current time
-step and computes the value of the storage terms.
+**Storage term:** The function `computeStorage` receives the volume variables
+at the previous or current time step and computes the value of the storage terms.
 In this case the mass balance equation is a conservation equation of the concentration and
 the equation for the chemical potential does not have a storage term.
 
 
 ```cpp
-    /*!
-     * \brief Evaluate the rate of change of all conserved quantities
-     */
     NumEqVector computeStorage(const Problem& problem,
                                const SubControlVolume& scv,
                                const VolumeVariables& volVars) const
@@ -194,20 +181,19 @@ the equation for the chemical potential does not have a storage term.
     }
 ```
 
-
-### The flux terms
-`computeFlux` computes the fluxes over a subcontrolvolumeface, including the integration over
+**Flux term:** The function `computeFlux` computes the fluxes
+over a sub control volume faces, including the integration over
 the area of the face.
 
+```math
+\begin{aligned}
+F_{K,\sigma,0} &= -M \sum_{B \in \mathcal{B}_K} \mu_{h,B} \nabla N_B \cdot\boldsymbol{n} \vert \sigma \vert \\
+F_{K,\sigma,1} &= -\gamma \sum_{B \in \mathcal{B}_K} c_{h,B} \nabla N_B \cdot\boldsymbol{n} \vert \sigma \vert
+\end{aligned}
+````
+
 
 ```cpp
-    /*!
-     * \brief Evaluate the fluxes over a face of a sub control volume
-     * Here we evaluate the flow rate, F1 = -M∇mu·n A, F2 = -gamma∇c·n A
-     *
-     * TODO: why is this called flux, if we expect it to be integrated already?
-     * computeFluxIntegral?
-     */
     NumEqVector computeFlux(const Problem& problem,
                             const Element& element,
                             const FVElementGeometry& fvGeometry,
@@ -215,40 +201,46 @@ the area of the face.
                             const SubControlVolumeFace& scvf,
                             const ElementFluxVariablesCache& elemFluxVarsCache) const
     {
+        static_assert(DiscretizationMethods::isCVFE<typename GridGeometry::DiscretizationMethod>,
+            "This local residual is hard-coded to control-volume finite element schemes");
+
         const auto& fluxVarCache = elemFluxVarsCache[scvf];
         Dune::FieldVector<Scalar, dimWorld> gradConcentration(0.0);
         Dune::FieldVector<Scalar, dimWorld> gradChemicalPotential(0.0);
         for (const auto& scv : scvs(fvGeometry))
         {
             const auto& volVars = elemVolVars[scv];
-            gradConcentration.axpy(volVars.concentration(), fluxVarCache.gradN(scv.indexInElement()));
-            gradChemicalPotential.axpy(volVars.chemicalPotential(), fluxVarCache.gradN(scv.indexInElement()));
+            // v.axpy(a, w) means v <- v + a*w
+            gradConcentration.axpy(
+                volVars.concentration(),
+                fluxVarCache.gradN(scv.indexInElement())
+            );
+            gradChemicalPotential.axpy(
+                volVars.chemicalPotential(),
+                fluxVarCache.gradN(scv.indexInElement())
+            );
         }
 
-        const auto M = problem.mobility();
-        const auto gamma = problem.surfaceTension();
-
         NumEqVector flux;
-        flux[Indices::massBalanceEqIdx] = -1.0*vtmv(scvf.unitOuterNormal(), M, gradChemicalPotential)*scvf.area();
-        flux[Indices::chemicalPotentialEqIdx] = -1.0*vtmv(scvf.unitOuterNormal(), gamma, gradConcentration)*scvf.area();
+        // Compute the flux with `vtmv` (vector transposed times matrix times vector).
+        // The mobility and surface tension coefficients comes from the `problem` (see Part 2 of the example).
+        flux[Indices::massBalanceEqIdx] = -1.0*vtmv(
+            scvf.unitOuterNormal(), problem.mobility(), gradChemicalPotential
+        )*scvf.area();
+        flux[Indices::chemicalPotentialEqIdx] = -1.0*vtmv(
+            scvf.unitOuterNormal(), problem.surfaceTension(), gradConcentration
+        )*scvf.area();
         return flux;
     }
 ```
 
-
-### Source terms
-
-`computeSource` defines the sources terms at a sub control volume.
+**Source term:** The function `computeSource` computes the sources terms for a sub control volume.
 We implement a model-specific source term for the chemical potential equation before
 deferring further implementation to the problem where we add the derivative of the free
-energy. The default implementation of this function also defers the calculation to the
-problem.
+energy.
 
 
 ```cpp
-    /*!
-     * \brief Calculate the source term of the equation
-     */
     NumEqVector computeSource(const Problem& problem,
                               const Element& element,
                               const FVElementGeometry& fvGeometry,
@@ -256,90 +248,59 @@ problem.
                               const SubControlVolume &scv) const
     {
         NumEqVector source(0.0);
-
+        source[Indices::massBalanceEqIdx] = 0.0;
         source[Indices::chemicalPotentialEqIdx] = elemVolVars[scv].chemicalPotential();
-
         // add contributions from problem (e.g. double well potential)
         source += problem.source(element, fvGeometry, elemVolVars, scv);
-
         return source;
     }
 };
-
 } // end namespace Dumux
 ```
 
 
 </details>
 
-## Model properties/traits
+## 4. The model properties/traits
 
-We set some general model traits and properties, using a TypeTag `CahnHilliardModel`.
-For this type tag we define a `ModelTraits` struct and set a number of properties by specifying
-further structs within the `Dumux::Properties` namespace.
+In the `Dumux::Properties` namespace, we specialize properties for
+the created type tag `CahnHilliardModel`.
 
 
 <details open>
 <summary><b>Click to hide/show the file documentation</b> (or inspect the [source code](../model.hh))</summary>
 
 
-### Include the header for the property system and common properties and switch to the
-`Properties` namespace.
-
-
 ```cpp
-#include <dumux/common/properties.hh>
-
 namespace Dumux::Properties {
 ```
 
-
-### Define the type tag
-
+The type of the local residual is the class defined above.
 
 ```cpp
-namespace TTag {
-struct CahnHilliardModel {};
-} // end namespace TTag
+template<class TypeTag>
+struct LocalResidual<TypeTag, TTag::CahnHilliardModel>
+{ using type = CahnHilliardModelLocalResidual<TypeTag>; };
 ```
 
-
-### Basic model properties
-
-Define some general properties to be used for this modedl such as a datatype for scalars and the
-default vector for the primary variables. Here we can also use the model traits we specify below.
-
+The default scalar type is double.
+We compute with double precision floating point numbers.
 
 ```cpp
-//! Set the default type of scalar values to double
 template<class TypeTag>
-struct Scalar<TypeTag, TTag:: CahnHilliardModel >
+struct Scalar<TypeTag, TTag::CahnHilliardModel>
 { using type = double; };
-
-//! Set the default primary variable vector to a vector of size of number of equations
-template<class TypeTag>
-struct PrimaryVariables<TypeTag, TTag:: CahnHilliardModel >
-{
-    using type = Dune::FieldVector<
-        GetPropType<TypeTag, Properties::Scalar>,
-        GetPropType<TypeTag, Properties::ModelTraits>::numEq()
-    >;
-};
 ```
 
-
-### Model traits
-
-We specify general traits of the implemented model, defining indices (often in `indices.hh`)
-and the number of equations in the model.
-
+The model traits specify some information about our equation system.
+Here we have two equations. The indices allow to access primary variables
+and equations with a named indices.
 
 ```cpp
-//! Set the model traits property
 template<class TypeTag>
 struct ModelTraits<TypeTag, TTag::CahnHilliardModel>
 {
-    struct Traits
+    struct type
     {
         struct Indices
         {
@@ -352,24 +313,26 @@ struct ModelTraits<TypeTag, TTag::CahnHilliardModel>
 
         static constexpr int numEq() { return 2; }
     };
-
-    using type = Traits;
 };
 ```
 
-
-### Further model properties
-
-Define further properties of the model, selecting the local residual and volumevariables defined
-above.
-
+The primary variable vector has entries of type `Scalar` and is
+as large as the number of equations (here 2) but we keep it general.
 
 ```cpp
 template<class TypeTag>
-struct LocalResidual<TypeTag, TTag::CahnHilliardModel>
-{ using type = CahnHilliardModelLocalResidual<TypeTag>; };
+struct PrimaryVariables<TypeTag, TTag::CahnHilliardModel>
+{
+    using type = Dune::FieldVector<
+        GetPropType<TypeTag, Properties::Scalar>,
+        GetPropType<TypeTag, Properties::ModelTraits>::numEq()
+    >;
+};
+```
+
+Finally, the type of the volume variables is the class defined above.
 
-//! Set the volume variables property
+```cpp
 template<class TypeTag>
 struct VolumeVariables<TypeTag, TTag::CahnHilliardModel>
 {
@@ -389,6 +352,6 @@ struct VolumeVariables<TypeTag, TTag::CahnHilliardModel>
 </details>
 
 
-| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_right: Continue with part 2](mainfile.md) |
+| [:arrow_left: Back to the main documentation](../README.md) | [:arrow_right: Continue with part 2](main.md) |
 |---|---:|
 
diff --git a/examples/cahn_hilliard/main.cc b/examples/cahn_hilliard/main.cc
index 4e18dcff99..b91a6d9acb 100644
--- a/examples/cahn_hilliard/main.cc
+++ b/examples/cahn_hilliard/main.cc
@@ -17,43 +17,59 @@
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
  *****************************************************************************/
 
-// # Problem, test properties/traits and main program flow (`main.cc`)
-// In this example the file `main.cc` contains the problem class `CahnHilliardTestProblem`,
-// properties and traits specific to the test case as well as the main program flow in the form of
-// `main` function.
+// # Cahn-Hilliard equation main program
 //
-#include <config.h>
-// ## Problem
-// The __problem class__ defines boundary conditions and extends the storage term defined in the
-// model's localresidual by the derivative of the free energy.
+// The file [`main.cc`](main.cc) contains the problem class `CahnHilliardTestProblem`,
+// properties and traits specific to the test case as well as the `main` function.
+// The setup consists of four steps:
+// 1. Define the problem setting boundary conditions and the diffusion coefficient
+// 2. Configure the property system reusing the model defined in Part 1
+// 3. Define a function for setting the random initial condition
+// 4. The main program defining all steps of the program
 //
-// [[content]]
-// ### Include headers
+// __Table of contents__
 //
-// [[codeblock]]
-// use the property system and runtime parameters
+// [TOC]
+//
+// We start in `main.cc` with the necessary header includes:
+// [[details]] includes
+#include <config.h>
+#include <type_traits>
+#include <random>
+
+#include <dumux/common/initialize.hh>
 #include <dumux/common/properties.hh>
 #include <dumux/common/parameters.hh>
-// common DuMux vector for discretized equations
 #include <dumux/common/numeqvector.hh>
-// types of boundary conditions to use
 #include <dumux/common/boundarytypes.hh>
-// generic problem for finite volume simulations
 #include <dumux/common/fvproblem.hh>
-// [[/codeblock]]
+
+#include <dumux/discretization/box.hh>
+#include <dumux/linear/linearsolvertraits.hh>
+#include <dumux/linear/linearalgebratraits.hh>
+#include <dumux/linear/istlsolvers.hh>
+#include <dumux/nonlinear/newtonsolver.hh>
+#include <dumux/assembly/fvassembler.hh>
+
+#include <dune/grid/yaspgrid.hh>
+#include <dumux/io/vtkoutputmodule.hh>
+#include <dumux/io/grid/gridmanager_yasp.hh>
+
+#include "model.hh"
+// [[/details]]
+
 //
-// ### The problem class `CahnHilliardTestProblem`
-// In this abstract problem we inherit from the generic `FVProblem`.
+// ## 1. The problem class
 //
-// [[codeblock]]
+// The problem class implements boundary conditions and the source term.
+// It also provides an interface that is used by the local residual (see Part 1) to obtain
+// the mobility and surface tension. The values are read from the parameter configuration tree.
+// [[content]]
 namespace Dumux {
-
 template<class TypeTag>
 class CahnHilliardTestProblem : public FVProblem<TypeTag>
 {
-// [[/codeblock]]
-// [[details]] alias definitions and local variables
-// [[codeblock]]
+    // [[details]] boilerplate code
     using ParentType = FVProblem<TypeTag>;
     using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
     using FVElementGeometry = typename GridGeometry::LocalView;
@@ -67,9 +83,9 @@ class CahnHilliardTestProblem : public FVProblem<TypeTag>
     using NumEqVector = Dumux::NumEqVector<PrimaryVariables>;
     using BoundaryTypes = Dumux::BoundaryTypes<GetPropType<TypeTag, Properties::ModelTraits>::numEq()>;
     using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices;
-// [[/codeblock]]
-// [[/details]]
-// [[codeblock]]
+    // [[/details]]
+    // In the constructor, we read the parameter constants from the
+    // parameter tree (which is initialized with the content of `params.input`).
 public:
     CahnHilliardTestProblem(std::shared_ptr<const GridGeometry> gridGeometry)
     : ParentType(gridGeometry)
@@ -78,15 +94,10 @@ public:
         surfaceTension_ = getParam<Scalar>("Problem.SurfaceTension");
         energyScale_ = getParam<Scalar>("Problem.EnergyScale");
     }
-    // [[/codeblock]]
-    //
-    // ### Problem source term
-    //
-    // Here we implement the derivative of the free energy, setting a source for the equation for
-    // the chemical potential. The `computeSource` function in the local residual adds the terms
-    // defined here.
-    //
-    // [[codeblock]]
+
+    // In the `source` function, we implement the derivative of the free energy.
+    // This demonstrates how parts of the local residual can be split into model specific
+    // parts and parts that might change from scenario to scenario.
     template<class ElementVolumeVariables>
     NumEqVector source(const Element &element,
                        const FVElementGeometry& fvGeometry,
@@ -98,13 +109,9 @@ public:
         values[Indices::chemicalPotentialEqIdx] = -energyScale_*2.0*c*(2.0*c*c - 3*c + 1);
         return values;
     }
-    // [[/codeblock]]
-    //
-    // ### Boundary conditions
-    //
+
     // For the boundary we choose boundary flux (or Neumann) conditions for all equations and on
     // every part of the boundary, specifying zero flux everywhere for both equations.
-    //
     // [[codeblock]]
     BoundaryTypes boundaryTypesAtPos(const GlobalPosition& globalPos) const
     {
@@ -116,11 +123,10 @@ public:
     NumEqVector neumannAtPos(const GlobalPosition& globalPos) const
     { return { 0.0, 0.0 }; }
     // [[/codeblock]]
-    //
-    // [[details]] coefficients and private variables
-    // The problem class offers access to the mobility and surface tension coefficients as read from
-    // the parameter file by default `params.input`.
-    //
+
+    // The parameters interfaces are used in the local residual (see Part 1).
+    // We can name this interface however we want as long as we adapt the calling site
+    // in the `LocalResidual` class in `model.hh`.
     // [[codeblock]]
     Scalar mobility() const
     { return mobility_; }
@@ -133,118 +139,48 @@ private:
     Scalar surfaceTension_;
     Scalar energyScale_;
 };
-
 } // end namespace Dumux
 // [[/codeblock]]
-// [[/details]]
 // [[/content]]
-//
-// ## Test case properties/traits
-// Within the `Dumux::Properties` namespace we specialize properties and traits to the considered
-// test case by using the test's TypeTag.
-//
-// [[content]]
-//
-// ### Include headers
-//
-// [[codeblock]]
-// Include the grid to be used
-#include <dune/grid/yaspgrid.hh>
-// The header for the box discretization scheme
-#include <dumux/discretization/box.hh>
-// The model header including the model traits and properties
-#include "model.hh"
-// [[/codeblock]]
-//
-// ### TypeTag `CahnHilliardTest`
-//
-// We define a type tag for the test case, allowing us to further specify properties and traits. To
-// use those set for the Cahn-Hilliard model we inherit from its type tag.
-//
-// [[codeblock]]
-namespace Dumux::Properties {
 
-// Inheriting properties of the Cahn-Hilliard model and the box finite volume discretization
-namespace TTag {
-struct CahnHilliardTest { using InheritsFrom = std::tuple<CahnHilliardModel, BoxModel>; };
-} // end namespace TTag
-// [[/codeblock]]
 //
-// ### Test properties
+// ## 2. Property tag and specializations
 //
-// We specify a grid to be used in the test, select our problem class and enable caching.
+// We create a new tag `DiffusionTest` that inherits all properties
+// specialized for the tag `DiffusionModel` (we created this in Part 1)
+// and the tag `BoxModel` which provides types relevant for the spatial
+// discretization scheme (see [dumux/discretization/box.hh](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/dumux/discretization/box.hh)).
 //
-// [[codeblock]]
-// Set the grid type
-template<class TypeTag>
-struct Grid<TypeTag, TTag::CahnHilliardTest>
-{ using type = Dune::YaspGrid<2>; };
-
-// Select the problem class defined above
-template<class TypeTag>
-struct Problem<TypeTag, TTag::CahnHilliardTest>
-{ using type = CahnHilliardTestProblem<TypeTag>; };
-
-// Enable caching
-template<class TypeTag>
-struct EnableGridVolumeVariablesCache<TypeTag, TTag::CahnHilliardTest>
-{ static constexpr bool value = true; };
+// [[content]]
+namespace Dumux::Properties::TTag {
+struct CahnHilliardTest
+{
+    using InheritsFrom = std::tuple<CahnHilliardModel, BoxModel>;
 
-template<class TypeTag>
-struct EnableGridFluxVariablesCache<TypeTag, TTag::CahnHilliardTest>
-{ static constexpr bool value = true; };
+    using Grid = Dune::YaspGrid<2>;
 
-template<class TypeTag>
-struct EnableGridGeometryCache<TypeTag, TTag::CahnHilliardTest>
-{ static constexpr bool value = true; };
+    template<class TypeTag>
+    using Problem = CahnHilliardTestProblem<TypeTag>;
 
+    using EnableGridGeometryCache = std::true_type;
+    using EnableGridVolumeVariablesCache = std::true_type;
+    using EnableGridFluxVariablesCache = std::true_type;
+};
 } // end namespace Dumux::Properties
-// [[/codeblock]]
 // [[/content]]
 //
-// ## The main program flow
-// The main program flow in the `main` function is often the only content of `main.cc`. It sets up
-// the simulation framework, initializes runtime parameters, creates the grid and storage vectors
-// for the variables, primary and secondary. It specifies and constructs and assembler, which
-// assembles the discretized residual and system matrix (Jacobian of the model residual), as well as
-// linear and nonlinear solvers that solve the resulting linear system and handle the convergence of
-// nonlinear iterations. The time loop controls the time stepping, with adaptive time step size in
-// coordination with the nonlinear solver.
-//
-// [[content]]
-//
-// ### Include headers
-//
-// [[codeblock]]
-// standard header to generate random initial data
-#include <random>
-// common DuMux header for parallelization
-#include <dumux/common/initialize.hh>
-// headers to use the property system and runtime parameters
-#include <dumux/common/properties.hh>
-#include <dumux/common/parameters.hh>
-// module for VTK output, to write out fields of interest
-#include <dumux/io/vtkoutputmodule.hh>
-// gridmanager for the grid used in the test
-#include <dumux/io/grid/gridmanager_yasp.hh>
-// headers for linear and non-linear solvers as well as the assembler
-#include <dumux/linear/linearsolvertraits.hh>
-#include <dumux/linear/linearalgebratraits.hh>
-#include <dumux/linear/istlsolvers.hh>
-#include <dumux/nonlinear/newtonsolver.hh>
-#include <dumux/assembly/fvassembler.hh>
-// [[/codeblock]]
-//
-// ### Creating the initial solution
+// ## 3. Creating the initial solution
 //
-// We define a helper struct and function to handle communication for parallel runs.
-// For our initial conditions we create a random field of values around a mean of 0.42.
-// The random values are created with an offset based on the processor rank for communication
-// purposes, which is removed afterwards. For more information see the description of the
-// diffusion example
-// [examples/diffusion/doc/main.md](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/doc/main.md)
+// To initialize with a random field in parallel, where each processor
+// creates its own random number sequence, we need to communicate the
+// resulting values on the processor border and overlap.
+// See [Diffusion equation example](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/docs/main.md).
+// for details. Here in addition, we need to provide a custom scatter operation
+// that handles vector types. We only need to communicate the first entry (concentration).
 //
+// [[content]]
 // [[codeblock]]
+// functor for data communication with MPI
 struct MinScatter
 {
     template<class A, class B>
@@ -252,6 +188,7 @@ struct MinScatter
     { a[0] = std::min(a[0], b[0]); }
 };
 
+// create the random initial solution
 template<class SolutionVector, class GridGeometry>
 SolutionVector createInitialSolution(const GridGeometry& gg)
 {
@@ -267,7 +204,8 @@ SolutionVector createInitialSolution(const GridGeometry& gg)
         sol[n][1] = 0.0;
     }
 
-    // Take the value of the processor with the minimum rank and subtract the rank offset
+    // We take the value of the processor with the minimum rank
+    // and subtract the rank offset
     if (gg.gridView().comm().size() > 1)
     {
         Dumux::VectorCommDataHandle<
@@ -285,127 +223,91 @@ SolutionVector createInitialSolution(const GridGeometry& gg)
     return sol;
 }
 // [[/codeblock]]
+// [[/content]]
 //
-// ### The main function
+// ## 4. The main program
 //
-// The main function takes the command line arguments, optionally specifying an input file of
-// parameters and/or individual key-value pairs of runtime parameters.
+// The `main` function sets up the simulation framework, initializes runtime parameters,
+// creates the grid and storage vectors
+// for the variables, primary and secondary. It specifies and constructs and assembler, which
+// assembles the discretized residual and system matrix (Jacobian of the model residual), as well as
+// a linear solver. A Newton method is used to solve the nonlinear equations where in each Newton iteration
+// the Jacobian and the residual needs to be reassembled and the resulting linear system is solved.
+// The time loop controls the time stepping. Here, we let the Newton solver suggest an adaption of
+// the time step size based on the convergence history of the nonlinear solver.
 //
-// [[codeblock]]
+// [[content]]
 int main(int argc, char** argv)
 {
     using namespace Dumux;
 
-    // define the type tag for this problem
-    using TypeTag = Properties::TTag::CahnHilliardTest;
-    // [[/codeblock]]
-    //
-    // We initialize parallelization backends as well as runtime parameters
-    //
-    // [[codeblock]]
-    // maybe initialize MPI and/or multithreading backend
+    // We initialize MPI and/or multithreading backend. When not running
+    // in parallel the MPI setup is skipped.
     Dumux::initialize(argc, argv);
 
-    // initialize parameter tree
+    // Then we initialize parameter tree.
     Parameters::init(argc, argv);
-    // [[/codeblock]]
-    //
-    // ### Grid setup
-    //
-    // Set up the grid as well as a grid geometry to access the (sub-)control-volumes and their
-    // faces
-    //
-    // [[codeblock]]
-    // initialize the grid
-    GridManager<GetPropType<TypeTag, Properties::Grid>> gridManager;
-    gridManager.init();
 
-    // we compute on the leaf grid view
-    const auto& leafGridView = gridManager.grid().leafGridView();
+    // We create an alias for the type tag for this problem
+    // and extract type information through properties.
+    using TypeTag = Properties::TTag::CahnHilliardTest;
 
-    // create the finite volume grid geometry
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using Grid = GetPropType<TypeTag, Properties::Grid>;
     using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
-    auto gridGeometry = std::make_shared<GridGeometry>(leafGridView);
-    // [[/codeblock]]
-    //
-    // ### Problem setup
-    //
-    // We instantiate also the problem class according to the test properties
-    //
-    // [[codeblock]]
-    // the problem (initial and boundary conditions)
     using Problem = GetPropType<TypeTag, Properties::Problem>;
-    auto problem = std::make_shared<Problem>(gridGeometry);
-    // [[/codeblock]]
-    //
-    // ### Applying initial conditions
-    //
-    // After writing the initial data to the storage for previous and current time-step, we
-    // initialize the grid variables, also computing secondary variables.
-    //
-    // [[codeblock]]
-    // the solution vector
     using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
-    auto sol = createInitialSolution<SolutionVector>(*gridGeometry);
-    // copy the vector to store state of previous time step
-    auto oldSol = sol;
-
-    // the grid variables
     using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
+
+    // We initialize the grid, grid geometry, problem, solution vector, and grid variables.
+    GridManager<Grid> gridManager;
+    gridManager.init();
+
+    auto gridGeometry = std::make_shared<GridGeometry>(gridManager.grid().leafGridView());
+    auto problem = std::make_shared<Problem>(gridGeometry);
+    auto sol = createInitialSolution<SolutionVector>(*gridGeometry);
     auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry);
     gridVariables->init(sol);
-    // [[/codeblock]]
-    //
-    // ### Initialize VTK output
-    //
-    // [[codeblock]]
-    // initialize the vtk output module
+
+    // We initialize the VTK output module and write out the initial concentration and chemical potential
     VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
     vtkWriter.addVolumeVariable([](const auto& vv){ return vv.concentration(); }, "c");
     vtkWriter.addVolumeVariable([](const auto& vv){ return vv.chemicalPotential(); }, "mu");
     vtkWriter.write(0.0);
-    // [[/codeblock]]
-    //
-    // ### Set up time loop
-    //
-    // [[codeblock]]
-    // get some time loop parameters
-    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
-    const auto tEnd = getParam<Scalar>("TimeLoop.TEnd");
-    const auto dt = getParam<Scalar>("TimeLoop.InitialTimeStepSize");
-    const auto maxDt = getParam<Scalar>("TimeLoop.MaxTimeStepSize");
 
-    // instantiate time loop
-    auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>(0.0, dt, tEnd);
-    timeLoop->setMaxTimeStepSize(maxDt);
-    // [[/codeblock]]
-    //
-    // ### Assembler, linear and nonlinear solver
-    //
-    // [[codeblock]]
-    // the assembler with time loop for a transient problem
-    using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
-    auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables, timeLoop, oldSol);
+    // 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.InitialTimeStepSize"),
+        getParam<Scalar>("TimeLoop.TEnd")
+    );
 
-    // the linear solver
-    using LinearSolver = SSORBiCGSTABIstlSolver<
-        LinearSolverTraits<GridGeometry>,
-        LinearAlgebraTraitsFromAssembler<Assembler>
-    >;
-    auto linearSolver = std::make_shared<LinearSolver>(gridGeometry->gridView(), gridGeometry->dofMapper());
+    // We set the maximum time step size allowed in the adaptive time stepping scheme.
+    timeLoop->setMaxTimeStepSize(getParam<Scalar>("TimeLoop.MaxTimeStepSize"));
 
-    // the solver
+    // Next, we choose the type of assembler, linear solver and PDE solver
+    // and construct instances of these classes.
+    using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
+    using LinearSolver = SSORBiCGSTABIstlSolver<LinearSolverTraits<GridGeometry>, LinearAlgebraTraitsFromAssembler<Assembler>>;
     using Solver = Dumux::NewtonSolver<Assembler, LinearSolver>;
+
+    auto oldSol = sol;
+    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);
-    // [[/codeblock]]
-    //
-    // ### Time loop
+
+    // The time loop is where most of the actual computations happen.
+    // We assemble and solve the linear system of equations, update the solution,
+    // write the solution to a VTK file and continue until the we reach the
+    // final simulation time.
     //
     // [[codeblock]]
-    // time loop
     timeLoop->start(); do
     {
-        // assemble & solve
+        // Assemble & solve
+        // Passing the time loop enables the solver to repeat the time step
+        // with a reduced time step size if the Newton solver does not converge.
         solver.solve(sol, *timeLoop);
 
         // make the new solution the old solution
@@ -415,7 +317,7 @@ int main(int argc, char** argv)
         // advance to the time loop to the next step
         timeLoop->advanceTimeStep();
 
-        // write VTK output
+        // write VTK output (concentration field and chemical potential)
         vtkWriter.write(timeLoop->time());
 
         // report statistics of this time step
@@ -425,13 +327,9 @@ int main(int argc, char** argv)
         timeLoop->setTimeStepSize(solver.suggestTimeStepSize(timeLoop->timeStepSize()));
 
     } while (!timeLoop->finished());
-    // [[/codeblock]]
-    //
-    // ### Finalize
-    //
-    // [[codeblock]]
-    timeLoop->finalize(leafGridView.comm());
 
+    // print the final report
+    timeLoop->finalize(gridGeometry->gridView().comm());
     return 0;
 }
 // [[/codeblock]]
diff --git a/examples/cahn_hilliard/model.hh b/examples/cahn_hilliard/model.hh
index 9f69f9dada..f77c3d4f3f 100644
--- a/examples/cahn_hilliard/model.hh
+++ b/examples/cahn_hilliard/model.hh
@@ -20,21 +20,61 @@
 #ifndef DUMUX_EXAMPLES_CAHN_HILLIARD_MODEL_HH
 #define DUMUX_EXAMPLES_CAHN_HILLIARD_MODEL_HH
 
-// # Volume variables, local residual and model traits (`model.hh`)
-// In this example the file `model.hh` contains the classes `CahnHilliardModelVolumeVariables` and
-// `CahnHilliardModelLocalResidual` as well as general model traits and properties.
+// # Cahn-Hilliard equation model definition
 //
-// ## VolumeVariables
+// In the file `model.hh`, we define the model equations and
+// set all default model properties. The setup consist of four steps:
+// 1. Create a model type tag (used to specialize properties)
+// 2. Define the volume variables class computing and storing variables for a control volume
+// 3. Define the local residual class implementing the discrete equation
+// 4. Specialize important properties of the model such that Dumux knows how to assemble the system matrix
 //
-// The volume variables store the local element volume variables, both primary and secondary.
+// We implement the classes
+//
+// * `CahnHilliardModel` (step 1),
+// * `CahnHilliardModelVolumeVariables` (step 2) and
+// * `CahnHilliardModelLocalResidual` (step 3).
+//
+// __Table of contents__
+//
+// [TOC]
+//
+// We start in `model.hh` with the necessary header includes:
+// [[details]] includes
+#include <dumux/common/math.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/common/numeqvector.hh>
+// [[/details]]
+//
+// ## 1. Property Tag
+//
+// The property tag is simply an empty struct with the name `CahnHilliardModel`.
+//
+// [[content]]
+// [[codeblock]]
+namespace Dumux::Properties::TTag {
+struct CahnHilliardModel {};
+} // end namespace Dumux::Properties::TTag
+// [[/codeblock]]
+// [[/content]]
+//
+// ## 2. The volume variables
+//
+// The volume variables store the control volume variables, both primary and secondary.
+// Let's have a look at the class implementation.
 //
 // [[content]]
 //
-// ### The `CahnHilliardModelVolumeVariables` class
+// We write out a full volume variable class compatible with the Dumux assembly.
+// We have to fulfill the same interface as the [`BasicVolumeVariables`](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/dumux/common/volumevariables.hh).
+// To shorten the code, we could also inherit from `BasicVolumeVariables`. However,
+// we want to show the entire interface here as a basis for the implementation of more complex variables.
+// Note that in `update` we could, for example, compute some secondary variable that is a
+// possibly nonlinear function of the primary variables. That secondary variable could be exposed
+// via an interface and then used below in the `CahnHilliardModelLocalResidual` class implementation.
 //
 // [[codeblock]]
 namespace Dumux {
-
 template <class Traits>
 class CahnHilliardModelVolumeVariables
 {
@@ -45,17 +85,14 @@ public:
     using PrimaryVariables = typename Traits::PrimaryVariables;
     //! export the indices type
     using Indices = typename Traits::ModelTraits::Indices;
-    // [[/codeblock]]
-    //
-    // ### Update variables
+// [[/codeblock]]
     //
-    // The `update` function stores the local primary variables of the current solution and
-    // potentially recomputes secondary variables.
+    // **Update variables:** The `update` function stores the local primary variables of the current solution and
+    // potentially computes secondary variables. Secondary variables can be nonlinear functions
+    // of the primary variables.
     //
     // [[codeblock]]
-    /*!
-     * \brief Update all quantities for a given control volume
-     */
+    //! Update all quantities for a given control volume
     template<class ElementSolution, class Problem, class Element, class SubControlVolume>
     void update(const ElementSolution& elemSol,
                 const Problem& problem,
@@ -66,10 +103,9 @@ public:
     }
     // [[/codeblock]]
     //
-    // ### Access functions
-    //
-    // Named and generic functions to access different primary variables
-    //
+    // **Access functions:** Named and generic functions to access different primary variables.
+    // The volumevariables are also expected to return an extrusion factor.
+    // Here we don't extrude our domain and therefore return $1.0$.
     // [[codeblock]]
     Scalar concentration() const
     { return priVars_[Indices::concentrationIdx]; }
@@ -82,77 +118,53 @@ public:
 
     const PrimaryVariables& priVars() const
     { return priVars_; }
-    // [[/codeblock]]
-    //
-    // ### Extrusion factor
-    //
-    // The volumevariables are also expected to return the extrusion factor
-    //
-    // [[codeblock]]
-    // for compatibility with more general models
+
     Scalar extrusionFactor() const
     { return 1.0; }
-    // [[/codeblock]]
-    //
-    // ### Storage of local variables
-    //
-    // [[codeblock]]
+
 private:
     PrimaryVariables priVars_;
 };
-
 } // end namespace Dumux
 // [[/codeblock]]
 // [[/content]]
 //
-// ## LocalResidual
+// ## 3. The local residual
 //
-// The local residual defines the discretized and integrated partial differential equation through
-// terms for storage, fluxes and sources, with the residual given as
-// d/dt storage + div(fluxes) - sources = 0
-// The individual terms can be further adjusted or replaced in the more specific problem.
+// The local residual assembles the contribution to the residual for
+// all degrees of freedom associated with an element.
+// See [examples/diffusion/doc/model.hh](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/doc/main.md)
+// for a more detailed explanation for control-volume finite element method local residuals.
+// Let's have a look at the class implementation.
 //
 // [[content]]
 //
-// ### Include headers
-//
-// [[codeblock]]
-#include <dumux/common/math.hh>
-// use the property system
-#include <dumux/common/properties.hh>
-// common DuMux vector for discretized equations
-#include <dumux/common/numeqvector.hh>
-// [[/codeblock]]
-//
-// ### The local residual class `CahnHilliardModelLocalResidual` inherits from a base class set in
+// The class `CahnHilliardModelLocalResidual` inherits from a base class set in
 // the model properties, depending on the discretization scheme.
-//
-// [[codeblock]]
+// See [examples/diffusion/doc/model.hh](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/examples/diffusion/doc/main.md)
+// for details on the `BaseLocalResidual`.
 namespace Dumux {
-
 template<class TypeTag>
 class CahnHilliardModelLocalResidual
 : public GetPropType<TypeTag, Properties::BaseLocalResidual>
 {
+    // [[exclude]]
     // the base local residual is selected depending on the chosen discretization scheme
     using ParentType = GetPropType<TypeTag, Properties::BaseLocalResidual>;
-    // [[/codeblock]]
-    //
-    // [[details]] alias definitions
-    // [[codeblock]]
     using Scalar = GetPropType<TypeTag, Properties::Scalar>;
     using Problem = GetPropType<TypeTag, Properties::Problem>;
-
     using NumEqVector = Dumux::NumEqVector<GetPropType<TypeTag, Properties::PrimaryVariables>>;
-    using VolumeVariables = typename GetPropType<TypeTag, Properties::GridVolumeVariables>::VolumeVariables;
 
-    using ElementVolumeVariables = typename GetPropType<TypeTag, Properties::GridVolumeVariables>::LocalView;
-    using ElementFluxVariablesCache = typename GetPropType<TypeTag, Properties::GridFluxVariablesCache>::LocalView;
-    using FVElementGeometry = typename GetPropType<TypeTag, Properties::GridGeometry>::LocalView;
+    using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
+    using VolumeVariables = typename GridVariables::GridVolumeVariables::VolumeVariables;
+    using ElementVolumeVariables = typename GridVariables::GridVolumeVariables::LocalView;
+    using ElementFluxVariablesCache = typename GridVariables::GridFluxVariablesCache::LocalView;
 
-    using SubControlVolume = typename FVElementGeometry::SubControlVolume;
-    using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace;
-    using GridView = typename GetPropType<TypeTag, Properties::GridGeometry>::GridView;
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+    using FVElementGeometry = typename GridGeometry::LocalView;
+    using SubControlVolume = typename GridGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace;
+    using GridView = typename GridGeometry::GridView;
     using Element = typename GridView::template Codim<0>::Entity;
 
     using ModelTraits = GetPropType<TypeTag, Properties::ModelTraits>;
@@ -160,19 +172,14 @@ class CahnHilliardModelLocalResidual
     static constexpr int dimWorld = GridView::dimensionworld;
 public:
     using ParentType::ParentType;
-    // [[/codeblock]]
-    // [[/details]]
+    // [[/exclude]]
     //
-    // ### The storage term
-    // The function `computeStorage` receives the volumevariables at the previous or current time
-    // step and computes the value of the storage terms.
+    // **Storage term:** The function `computeStorage` receives the volume variables
+    // at the previous or current time step and computes the value of the storage terms.
     // In this case the mass balance equation is a conservation equation of the concentration and
     // the equation for the chemical potential does not have a storage term.
     //
     // [[codeblock]]
-    /*!
-     * \brief Evaluate the rate of change of all conserved quantities
-     */
     NumEqVector computeStorage(const Problem& problem,
                                const SubControlVolume& scv,
                                const VolumeVariables& volVars) const
@@ -183,19 +190,19 @@ public:
         return storage;
     }
     // [[/codeblock]]
-    //
-    // ### The flux terms
-    // `computeFlux` computes the fluxes over a subcontrolvolumeface, including the integration over
+
+    // **Flux term:** The function `computeFlux` computes the fluxes
+    // over a sub control volume faces, including the integration over
     // the area of the face.
     //
+    // ```math
+    // \begin{aligned}
+    // F_{K,\sigma,0} &= -M \sum_{B \in \mathcal{B}_K} \mu_{h,B} \nabla N_B \cdot\boldsymbol{n} \vert \sigma \vert \\
+    // F_{K,\sigma,1} &= -\gamma \sum_{B \in \mathcal{B}_K} c_{h,B} \nabla N_B \cdot\boldsymbol{n} \vert \sigma \vert
+    // \end{aligned}
+    // ````
+    //
     // [[codeblock]]
-    /*!
-     * \brief Evaluate the fluxes over a face of a sub control volume
-     * Here we evaluate the flow rate, F1 = -M∇mu·n A, F2 = -gamma∇c·n A
-     *
-     * TODO: why is this called flux, if we expect it to be integrated already?
-     * computeFluxIntegral?
-     */
     NumEqVector computeFlux(const Problem& problem,
                             const Element& element,
                             const FVElementGeometry& fvGeometry,
@@ -203,38 +210,45 @@ public:
                             const SubControlVolumeFace& scvf,
                             const ElementFluxVariablesCache& elemFluxVarsCache) const
     {
+        static_assert(DiscretizationMethods::isCVFE<typename GridGeometry::DiscretizationMethod>,
+            "This local residual is hard-coded to control-volume finite element schemes");
+
         const auto& fluxVarCache = elemFluxVarsCache[scvf];
         Dune::FieldVector<Scalar, dimWorld> gradConcentration(0.0);
         Dune::FieldVector<Scalar, dimWorld> gradChemicalPotential(0.0);
         for (const auto& scv : scvs(fvGeometry))
         {
             const auto& volVars = elemVolVars[scv];
-            gradConcentration.axpy(volVars.concentration(), fluxVarCache.gradN(scv.indexInElement()));
-            gradChemicalPotential.axpy(volVars.chemicalPotential(), fluxVarCache.gradN(scv.indexInElement()));
+            // v.axpy(a, w) means v <- v + a*w
+            gradConcentration.axpy(
+                volVars.concentration(),
+                fluxVarCache.gradN(scv.indexInElement())
+            );
+            gradChemicalPotential.axpy(
+                volVars.chemicalPotential(),
+                fluxVarCache.gradN(scv.indexInElement())
+            );
         }
 
-        const auto M = problem.mobility();
-        const auto gamma = problem.surfaceTension();
-
         NumEqVector flux;
-        flux[Indices::massBalanceEqIdx] = -1.0*vtmv(scvf.unitOuterNormal(), M, gradChemicalPotential)*scvf.area();
-        flux[Indices::chemicalPotentialEqIdx] = -1.0*vtmv(scvf.unitOuterNormal(), gamma, gradConcentration)*scvf.area();
+        // Compute the flux with `vtmv` (vector transposed times matrix times vector).
+        // The mobility and surface tension coefficients comes from the `problem` (see Part 2 of the example).
+        flux[Indices::massBalanceEqIdx] = -1.0*vtmv(
+            scvf.unitOuterNormal(), problem.mobility(), gradChemicalPotential
+        )*scvf.area();
+        flux[Indices::chemicalPotentialEqIdx] = -1.0*vtmv(
+            scvf.unitOuterNormal(), problem.surfaceTension(), gradConcentration
+        )*scvf.area();
         return flux;
     }
     // [[/codeblock]]
-    //
-    // ### Source terms
-    //
-    // `computeSource` defines the sources terms at a sub control volume.
+
+    // **Source term:** The function `computeSource` computes the sources terms for a sub control volume.
     // We implement a model-specific source term for the chemical potential equation before
     // deferring further implementation to the problem where we add the derivative of the free
-    // energy. The default implementation of this function also defers the calculation to the
-    // problem.
+    // energy.
     //
     // [[codeblock]]
-    /*!
-     * \brief Calculate the source term of the equation
-     */
     NumEqVector computeSource(const Problem& problem,
                               const Element& element,
                               const FVElementGeometry& fvGeometry,
@@ -242,78 +256,43 @@ public:
                               const SubControlVolume &scv) const
     {
         NumEqVector source(0.0);
-
+        source[Indices::massBalanceEqIdx] = 0.0;
         source[Indices::chemicalPotentialEqIdx] = elemVolVars[scv].chemicalPotential();
-
         // add contributions from problem (e.g. double well potential)
         source += problem.source(element, fvGeometry, elemVolVars, scv);
-
         return source;
     }
 };
-
 } // end namespace Dumux
 // [[/codeblock]]
 // [[/content]]
 //
-// ## Model properties/traits
+// ## 4. The model properties/traits
 //
-// We set some general model traits and properties, using a TypeTag `CahnHilliardModel`.
-// For this type tag we define a `ModelTraits` struct and set a number of properties by specifying
-// further structs within the `Dumux::Properties` namespace.
+// In the `Dumux::Properties` namespace, we specialize properties for
+// the created type tag `CahnHilliardModel`.
 //
 // [[content]]
-//
-// ### Include the header for the property system and common properties and switch to the
-// `Properties` namespace.
-//
-// [[codeblock]]
-#include <dumux/common/properties.hh>
-
 namespace Dumux::Properties {
-// [[/codeblock]]
-//
-// ### Define the type tag
-//
-// [[codeblock]]
-namespace TTag {
-struct CahnHilliardModel {};
-} // end namespace TTag
-// [[/codeblock]]
-//
-// ### Basic model properties
-//
-// Define some general properties to be used for this modedl such as a datatype for scalars and the
-// default vector for the primary variables. Here we can also use the model traits we specify below.
-//
-// [[codeblock]]
-//! Set the default type of scalar values to double
+
+// The type of the local residual is the class defined above.
 template<class TypeTag>
-struct Scalar<TypeTag, TTag:: CahnHilliardModel >
-{ using type = double; };
+struct LocalResidual<TypeTag, TTag::CahnHilliardModel>
+{ using type = CahnHilliardModelLocalResidual<TypeTag>; };
 
-//! Set the default primary variable vector to a vector of size of number of equations
+// The default scalar type is double.
+// We compute with double precision floating point numbers.
 template<class TypeTag>
-struct PrimaryVariables<TypeTag, TTag:: CahnHilliardModel >
-{
-    using type = Dune::FieldVector<
-        GetPropType<TypeTag, Properties::Scalar>,
-        GetPropType<TypeTag, Properties::ModelTraits>::numEq()
-    >;
-};
-// [[/codeblock]]
-//
-// ### Model traits
-//
-// We specify general traits of the implemented model, defining indices (often in `indices.hh`)
-// and the number of equations in the model.
-//
-// [[codeblock]]
-//! Set the model traits property
+struct Scalar<TypeTag, TTag::CahnHilliardModel>
+{ using type = double; };
+
+// The model traits specify some information about our equation system.
+// Here we have two equations. The indices allow to access primary variables
+// and equations with a named indices.
 template<class TypeTag>
 struct ModelTraits<TypeTag, TTag::CahnHilliardModel>
 {
-    struct Traits
+    struct type
     {
         struct Indices
         {
@@ -326,22 +305,20 @@ struct ModelTraits<TypeTag, TTag::CahnHilliardModel>
 
         static constexpr int numEq() { return 2; }
     };
-
-    using type = Traits;
 };
-// [[/codeblock]]
-//
-// ### Further model properties
-//
-// Define further properties of the model, selecting the local residual and volumevariables defined
-// above.
-//
-// [[codeblock]]
+
+// The primary variable vector has entries of type `Scalar` and is
+// as large as the number of equations (here 2) but we keep it general.
 template<class TypeTag>
-struct LocalResidual<TypeTag, TTag::CahnHilliardModel>
-{ using type = CahnHilliardModelLocalResidual<TypeTag>; };
+struct PrimaryVariables<TypeTag, TTag::CahnHilliardModel>
+{
+    using type = Dune::FieldVector<
+        GetPropType<TypeTag, Properties::Scalar>,
+        GetPropType<TypeTag, Properties::ModelTraits>::numEq()
+    >;
+};
 
-//! Set the volume variables property
+// Finally, the type of the volume variables is the class defined above.
 template<class TypeTag>
 struct VolumeVariables<TypeTag, TTag::CahnHilliardModel>
 {
@@ -355,7 +332,7 @@ struct VolumeVariables<TypeTag, TTag::CahnHilliardModel>
 };
 
 } // end namespace Dumux::Properties
-// [[/codeblock]]
 // [[/content]]
-
+// [[exclude]]
 #endif
+// [[/exclude]]
diff --git a/examples/diffusion/doc/main.md b/examples/diffusion/doc/main.md
index 28d3bd539a..28791805ad 100644
--- a/examples/diffusion/doc/main.md
+++ b/examples/diffusion/doc/main.md
@@ -248,7 +248,7 @@ SolutionVector createInitialSolution(const GridGeometry& gg)
     for (int n = 0; n < sol.size(); ++n)
         sol[n] = dis(gen) + rank;
 
-    // We, take the value of the processor with the minimum rank
+    // We take the value of the processor with the minimum rank
     // and subtract the rank offset
     if (gg.gridView().comm().size() > 1)
     {
@@ -341,7 +341,7 @@ the problem for the boundary conditions, a solution vector and a grid variables
     gridVariables->init(sol);
 ```
 
-Ww 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
 
 ```cpp
     VtkOutputModule<GridVariables, SolutionVector> vtkWriter(*gridVariables, sol, problem->name());
diff --git a/examples/diffusion/main.cc b/examples/diffusion/main.cc
index fb80e86507..a3585e7eda 100644
--- a/examples/diffusion/main.cc
+++ b/examples/diffusion/main.cc
@@ -204,7 +204,7 @@ SolutionVector createInitialSolution(const GridGeometry& gg)
     for (int n = 0; n < sol.size(); ++n)
         sol[n] = dis(gen) + rank;
 
-    // We, take the value of the processor with the minimum rank
+    // We take the value of the processor with the minimum rank
     // and subtract the rank offset
     if (gg.gridView().comm().size() > 1)
     {
@@ -274,7 +274,7 @@ int main(int argc, char** argv)
     auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry);
     gridVariables->init(sol);
 
-    // Ww 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); }, "c");
     vtkWriter.write(0.0);
-- 
GitLab