\section{Up up and away! Adding More Layers to the Atmosphere}
Up until now we have neglected any vertical movement. This hampers the model, as the rising of warm air that then flows to the poles, cools down and flows back to the tropics is not possible
as the warm air cannot rise. So let us change that, let's add some vertical motion and some more layers to the atmosphere.

Remember \autoref{eq:atmos change}? We need this equation for every layer in the atmosphere. This also means that we have to adjust the main loop of the code, which is described  in 
\autoref{alg:temperature with density}. The $T_a$ needs to change, we need to either add a dimension (to indicate which layer of the atmosphere we are talking about) or we need to add different
matrices for each atmosphere layer. Let us define some useful variables in \autoref{alg:more layers}. We opt for adding a dimension as that costs less memory than defining new arrays 
\footnote{This has to do with pointers, creating a new object always costs a bit more space than adding a dimension as we need a pointer to the object and what type of object it is whereas with 
adding a dimension we do not need this additional information as it has already been defined}. So $T_a$, and all other matrices that have to do with the atmosphere (so not $T_p$ for instance) 
are no longer indexed by $lat, lon$ but are indexed by $lat, lon, layer$.

\begin{algorithm}
    $nlevels \leftarrow 4$ \;
    $heights \leftarrow \text{Array with } nlevels \text{ layers, each with a uniform thickness of } \frac{100 \cdot 10^3}{nlevels} m$ \;
    \caption{Definition of variables that are used throughout the code}
    \label{alg:more layers}
\end{algorithm}

We also need to change all the gradient functions (\autoref{alg:gradient x} and \autoref{alg:gradient y}) to incorporate the atmospheric layers. Additionally we need a new gradient function that 
calculates the gradient in the $z$ direction (vertical). Let us first change the existing gradient functions to take the atmospheric layer in effect. The changes can be found in 
\autoref{alg:gradient x layer} and \autoref{alg:gradient y layer}. Let us improve the gradient in the $y$ direction as well. Since we are using the central difference method (calculating the
gradient by taking the difference of the next grid cell and the previous grid cell) there is no gradient at the poles. What we can do instead of returning $0$ for those cases is forward 
differencing (calculating the gradient by taking the difference of the cell and the next/previous cell, multiplied by $2$ to keep it fair). A special change in both functions is checking whether
$k$ is equal to \texttt{NULL}. We do this as sometimes we want to use this function for matrices that does not have the layer dimension. Hence we define a default value for $k$ which is 
\texttt{NULL}. \texttt{NULL} is a special value in computer science. It represents nothing. This can be useful sometimes if you declare a variable to be something but it is referring to something
that has been deleted or it is returned when some function fails. It usually indicates that something special is going on. So here we use it in the special case where we do not want to consider
the layer part in the gradient.

\begin{algorithm}[hbt]
    \SetKwInOut{Input}{Input}
    \SetKwInOut{Output}{Output}
    \Input{Matrix (double array) $a$, first index $i$, second index $j$, third index $k$ with default value \texttt{NULL}}
    \Output{Gradient in the $x$ direction}
    \eIf{$k == \texttt{NULL}$}{
        $grad \leftarrow \frac{a[i, (j + 1)\text{ mod } nlon] - a[i, (j - 1) \text{ mod } nlon]}{\delta x[i]}$ \;
    }{
        $grad \leftarrow \frac{a[i, (j + 1)\text{ mod } nlon, k] - a[i, (j - 1) \text{ mod } nlon, k]}{\delta x[i]}$ \;
    }
    \Return{$grad$} \;
    \caption{Calculating the gradient in the $x$ direction}
    \label{alg:gradient x layer}
\end{algorithm}

\begin{algorithm}[hbt]
    \SetKwInOut{Input}{Input}
    \SetKwInOut{Output}{Output}
    \Input{Matrix (double array) $a$, first index $i$, second index $j$, third index $k$ with default value \texttt{NULL}}
    \Output{Gradient in the $y$ direction}
    \eIf{$k == \texttt{NULL}$}{
        \uIf{$i == 0$}{
            $grad \leftarrow 2 \frac{a[i + 1, j] - a[i, j]}{\delta y}$ \;
        }\uElseIf{$i == nlat - 1$}{
            $grad \leftarrow 2 \frac{a[i, j] - a[i - 1, j]}{\delta y}$ \;
        }\uElse{
            $grad \leftarrow \frac{a[i + 1, j] - a[i - 1 j]}{\delta y}$ \;
        }
    }{
        \uIf{$i == 0$}{
            $grad \leftarrow 2 \frac{a[i + 1, j, k] - a[i, j, k]}{\delta y}$ \;
        }\uElseIf{$i == nlat - 1$}{
            $grad \leftarrow 2 \frac{a[i, j, k] - a[i - 1, j, k]}{\delta y}$ \;
        }\uElse{
            $grad \leftarrow \frac{a[i + 1, j] - a[i - 1 j]}{\delta y}$ \;
        }
    }
    \Return $grad$ \;
    \caption{Calculating the gradient in the $y$ direction}
    \label{alg:gradient y layer}
\end{algorithm}

With those changes done, let us define the gradient in the $z$ direction. The function can be found in \autoref{alg:gradient z layer}. Here $a.dimensions$ is the attribute that tells us how 
deeply nested the array $a$ is. If the result is $1$ we have just a normal array, if it is $2$ we have a double array (an array at each index of the array) which is also called a matrix and if it 
is $3$ we have a triple array. We need this because we have a one-dimensional case, for when we do not use multiple layers and a three-dimensional case for when we do use multiple layers. This 
distinction is needed to avoid errors being thrown when running the model with one or multiple layers.

\begin{algorithm}[hbt]
    \SetKwInOut{Input}{Input}
    \SetKwInOut{Output}{Output}
    \Input{Matrix (double array) $a$, first index $i$, second index $j$, third index $k$}
    \Output{Gradient in the $z$ direction}
    \uIf{$a.dimensions == 1$}{
        \uIf{$k == 0$}{
            $grad \leftarrow \frac{a[k + 1] - a[k]}{\delta z[k]}$ \;
        }\uElseIf{$k == nlevels - 1$}{
            $grad \leftarrow \frac{a[k] - a[k - 1]}{\delta z[k]}$ \;
        }\uElse{
            $grad \leftarrow \frac{a[k + 1] - a[k - 1]}{2\delta z[k]}$ \;
        }
    } \uElse {
        \uIf{$k == 0$}{
            $grad \leftarrow \frac{a[i, j, k + 1] - a[i, j, k]}{\delta z[k]}$ \;
        }\uElseIf{$k == nlevels - 1$}{
            $grad \leftarrow \frac{a[i, j, k] - a[i, j, k - 1]}{\delta z[k]}$ \;
        }\uElse{
            $grad \leftarrow \frac{a[i, j, k + 1] - a[i, j, k - 1]}{2\delta z[k]}$ \;
        }
    }
    
    \Return $grad$ \;
    \caption{Calculating the gradient in the $z$ direction}
    \label{alg:gradient z layer}
\end{algorithm}

As you can see, we have used $\delta z$ however, we have not defined it yet. Let us do that in \autoref{alg:gradient z}.

\begin{algorithm}[hbt]
    \For{$k \in [0, nlevels - 1]$}{
        $\delta z[k] \leftarrow heights[k + 1] - heights[k]$ \;
    }
    $\delta z[nlevels - 1] \leftarrow \delta z [nlevels - 2]$ \;
    \caption{Defining $\delta z$ for later use throughout the code}
    \label{alg:gradient z}
\end{algorithm}

Let's incorporate the changes for the Laplacian operator (\autoref{alg:laplacian}) as well. The new code can be found in \autoref{alg:laplacian layer}.

\begin{algorithm}[hbt]
    \SetKwInOut{Input}{Input}
    \SetKwInOut{Output}{Output}
    \Input{A matrix (double array) a}
    \Output{A matrix (double array) with results for the laplacian operator for each element}
    \eIf{$a.dimensions == 2$}{
        \For{$lat \in [1, nlat - 1]$}{
            \For{$lon \in [0, nlon]$}{
                $output[lat, lon] \leftarrow \frac{\Delta_x(a, lat, (lon + 1) \text{ mod } nlon) - \Delta_x(a, lat, (lon - 1) \text{ mod } nlon)}{\delta x[lat]} + \frac{\Delta_y(a, lat + 1, lon) - 
                \Delta_y(a, lat - 1, lon)}{\delta y}$\;
            }
        }
    }{
        \For{$lat \in [1, nlat - 1]$}{
            \For{$lon \in [0, nlon]$}{
                \For{$k \in [0, nlevels - 1]$}{
                    $output[lat, lon, k] \leftarrow \frac{\Delta_x(a, lat, (lon + 1) \text{ mod } nlon, k) - \Delta_x(a, lat, (lon - 1) \text{ mod } nlon, k)}{\delta x[lat]} + \frac{\Delta_y(a, 
                    lat + 1, lon, k) - \Delta_y(a, lat - 1, lon, k)}{\delta y} + \frac{\Delta_z(a, lat, lon, k + 1) - \Delta_z(a, lat, lon, k + 1)}{2\delta z[k]}$\;
                }
            }
        }
    }
    
    \Return{$ouput$} \;
    \caption{Calculate the laplacian operator over a matrix a}
    \label{alg:laplacian layer}
\end{algorithm}

Of course we also need to incorporate the new layers in the divergence operator (\autoref{alg:divergence}). The new changes can be found in \autoref{alg:divergence layer}. Here we use $w$, the 
vertical wind velocity. We define $w$ in the same way as $u$ and $v$, it is all zeroes (in the beginning) and has the same dimensions as $u$ and $v$.

\begin{algorithm}[!hbt]
    \SetKwInOut{Input}{Input}
    \SetKwInOut{Output}{Output}
    \Input{A matrix (double array) $a$}
    \Output{A matrix (double array) containing the result of the divergence operator taken over that element}
    $dim_1 \leftarrow \text{ Length of } a \text{ in the first dimension}$ \;
    \For{$i \in [0, dim_1]$}{
        $dim_2 \leftarrow \text{ Length of } a \text{ in the second dimension (i.e. the length of the array stored at index } i)$ \;
        \For{$j \in [0, dim_2]$}{
            $dim_3 \leftarrow \text{ Length of } a \text{ in the third dimension}$ \;
            \For{$k \in [0, dim_3]$}{
                $output[i, j] \leftarrow \Delta_x(au, i, j, k) + \Delta_y(av, i, j, k) + \Delta_z(aw, i, j, k)$ \;
            }
        }
    }
    \Return{$output$} \;
    \caption{Calculate the result of the divergence operator on a vector}
    \label{alg:divergence layer}
\end{algorithm}

With all those changes in the functions done, let us incorporate the changes into the model itself. We now need to account for the temperature change throughout the layers. Let us look at the 
atmospheric temperature equation again (\autoref{eq:atmos change}). We need to account for one more thing, the absorbtion of energy from another layer. The new equation is shown in 
\autoref{eq:atmos change layer}. Here $k$ is the layer of the atmosphere, $k = -1$ means that you use $T_p$ and $k = nlevels$ means that $T_{a_{nlevels}} = 0$ as that is space. Also, let us
rewrite the equation a bit such that the variables that are repeated are only written once and stuff that is divided out is removed, which is done in \autoref{eq:atmos change layer improved}.
Let us also clean up the equation for the change in the surface temperature (\autoref{eq:surface change}) in \autoref{eq:surface change improved}.

\begin{subequations}
    \begin{equation}
        \Delta T_{a_k} = \frac{\delta t (\sigma \epsilon_{k - 1}T_{a_{k - 1}}^4 + \sigma \epsilon_{k + 1}T_{a_{k + 1}}^4 - 2\epsilon_k\sigma T_{a_k}^4)}{C_a}
        \label{eq:atmos change layer}
    \end{equation}
    \begin{equation}
        \Delta T_{a_k} = \frac{\delta t \sigma (\epsilon_{k - 1}T_{a_{k - 1}}^4 + \epsilon_{k + 1}T_{a_{k + 1}}^4 - 2\epsilon_kT_{a_k}^4)}{C_a}
        \label{eq:atmos change layer improved}
    \end{equation}
    \begin{equation}
        \Delta T_p = \frac{\delta t (S + \sigma(4\epsilon_pT_a^4 - 4T_p^4))}{4C_p}
        \label{eq:surface change improved}
    \end{equation}
\end{subequations}

With the changes made to the equation, we need to make those changes in the code as well. We need to add the new dimension to all matrices except $T_p$ and $a$ as they are unaffected (with 
regards to the storage of the values) by the addition of multiple atmospheric layers. Every other matrix is affected. The new code can be found in \autoref{alg:temperature layer}.

\begin{algorithm}[hbt]
    \SetAlgoLined

    \While{\texttt{TRUE}}{
        \For{$lat \in [-nlat, nlat]$}{
            \For{$lon \in [0, nlot]$}{
                \For{$layer \in [0, nlevels]$}{
                    $T_p[lat, lon] \leftarrow T_p[lat, lon] + \frac{\delta t ((1 - a[lat, lon])S + \sigma(4\epsilon[0](T_a[lat, lon, 0])^4 - 4(T_p[lat, lon])^4))}
                    {4C_p[lat, lon]}$ \;
                    \uIf{$layer == 0$}{
                        $T_a[lat, lon, layer] \leftarrow T_a[lat, lon, layer] + \frac{\delta t \sigma((T_p[lat, lon])^4 - 2\epsilon[layer](T_a[lat, lon, layer])^4)}
                        {\rho[lat, lon, layer]C_a\delta z[layer]}$ \;
                    }\uElseIf{$layer == nlevels - 1$}{
                        $T_a[lat, lon, layer] \leftarrow T_a[lat, lon, layer] + \frac{\delta t \sigma(\epsilon[layer - 1](T_a[lat, lon, layer - 1])^4 - 2\epsilon[layer](T_a[lat, lon, layer])^4)}
                        {\rho[lat, lon, layer]C_a\delta z[layer]}$ \;
                    }\uElse{
                        $T_a[lat, lon, layer] \leftarrow T_a[lat, lon, layer] + \frac{\delta t \sigma(\epsilon[layer - 1](T_a[lat, lon, layer - 1])^4 + \epsilon[layer + 1]T_a[lat, lon, layer + 1] 
                        - 2\epsilon[layer](T_a[lat, lon, layer])^4)}{\rho[lat, lon, layer]C_a\delta z[layer]}$ \;
                    }
                    $t \leftarrow t + \delta t$ \;
                }
            }
        }
    }
    \caption{The main loop of the temperature calculations}
    \label{alg:temperature layer}
\end{algorithm}

We also need to initialise the $\epsilon$ value for each layer. We do that in \autoref{alg:epsilon}.

\begin{algorithm}
    $\epsilon[0] \leftarrow 0.75$ \;
    \For{$i \in [1, nlevels]$}{
        $\epsilon[i] \leftarrow 0.5\epsilon[i - 1]$
    }
    \caption{Intialisation of the insulation of each layer (also known as $\epsilon$)}
    \label{alg:epsilon}
\end{algorithm}

Now we need to add vertical winds, or in other words add the $w$ component of the velocity vectors. We do that by editing \autoref{alg:stream3}. We change it to \autoref{alg:velocity}. Here we 
use gravity ($g$) instead of the coriolis force ($f$) and calculate the pressure gradient in the $z$ direction.

\begin{algorithm}
    $S_{xu} \leftarrow \texttt{gradient\_x}(u, lan, lon)$ \;
    $S_{yu} \leftarrow \texttt{gradient\_y}(u, lan, lon)$ \;
    $S_{xv} \leftarrow \texttt{gradient\_x}(v, lan, lon)$ \;
    $S_{yv} \leftarrow \texttt{gradient\_y}(v, lan, lon)$ \;
    $S_{px} \leftarrow \texttt{gradient\_x}(p, lan, lon)$ \;
    $S_{py} \leftarrow \texttt{gradient\_y}(p, lan, lon)$ \;
    $S_{pz} \leftarrow \texttt{gradient\_z}(p, lan, lon)$ \;
    \While{\texttt{TRUE}}{
        \For{$lat \in [1, nlat - 1]$}{
            \For{$lon \in [0, nlon]$}{
                \For{$layer \in [0, nlevels]$}{
                    $u[lan, lon, layer] \leftarrow u[lat, lon, layer] + \delta t \frac{-u[lat, lon, layer]S_{xu} - v[lat, lon, layer]S_{yu} + f[lat]v[lat, lon, layer] - S_{px}}{\rho}$ \;
                    $v[lan, lon, layer] \leftarrow v[lat, lon, layer] + \delta t \frac{-u[lat, lon, layer]S_{xv} - v[lat, lon, layer]S_{yv} - f[lat]u[lat, lon, layer] - S_{py}}{\rho}$ \;
                    $w[lan, lon, layer] \leftarrow w[lat, lon, layer] + \delta t (\frac{S_{pz}}{\rho[lat, lon, layer]} + g)$ \;
                }
            }
        }
    }
    \caption{Calculating the flow of the atmosphere (wind)}
    \label{alg:velocity}
\end{algorithm}

Lastly, we need to add the correct indices to the advection algorithm \autoref{alg:advectionfix}. Let us add it, with \autoref{alg:advection layer} as a result. Here the ':' means all indices 
of the 3 dimensional matrix.

\begin{algorithm}
    $\alpha_a \leftarrow 2 \cdot 10^{-5}$ \;
    $\alpha_p \leftarrow 1.5 \cdot 10^{-6}$ \;
    $boundary \leftarrow 7$ \;
    \While{\texttt{TRUE}}{
        $T_{add} \leftarrow T_a + \delta t \alpha_a \nabla^2(T_a) + \nabla(T_a)$ \;
        $T_a \leftarrow T_a - 0.5T_{add}[boundary:-boundary, :, :] \text{ //Only subtract } T_{add} \text{ to } T_a \text{ for indices in the interval } [-nlat + boundary, nlat - boundary]$. \;
        $\rho[boundary: -boundary, :, :] \leftarrow \rho - 0.5(\delta t \nabla \rho) \text{ //Only change the density for indices in the interval } [-nlat + boundary, nlat - boundary]$ \;
        $T_p \leftarrow T_p + \delta t \alpha_p \nabla^2(T_p)$ \;
    }
    \caption{The main loop for calculating the effects of advection}
    \label{alg:advection layer}
\end{algorithm}