\documentclass[10pt,a4paper]{article}
\usepackage[dutch]{babel}
\usepackage[utf8]{inputenc}
\usepackage{booktabs,graphicx,float,hyperref,amsmath,listings}

% Paragraph indentation
\setlength{\parindent}{0pt}
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}

\title{Modelleren, Simuleren \& Contin\"ue Wiskunde \\
       Opdracht 4: Parallel programming \\
       Deel 2: Trillende snaar}
\author{Tadde\"us Kroes (6054129)}

\begin{document}

\maketitle
\tableofcontents
\pagebreak

\section{Inleiding}

Het tweede deel van practicumopdracht 4 omhelst het simuleren van een
trillende snaar in een parallel programma. Hierbij wordt ook een sequëntieel
programma ontwikkeld, zodat de stijging in performance door parallellisatie kan
worden bepaald.

\section{Theorie}

\subsection{Simulatie}

De uitwijking van de snaar wordt gegeven als een functie van positie en tijd.
De bijbehordende differentiaalvergelijking is
$$\frac{\delta^2 y}{\delta t^2} = c^2 \frac{\delta^2 y}{\delta x^2}$$
De oplossing van deze vergelijking, $y(x, t)$, is de uitwijking van het punt
van de snaar op positie $x$ op een bepaald moment in de tijd $t$. Om een
computersimulatie te kunnen uitvoeren moet deze oplossing worden
gediscretiseerd. De formule hiervoor is gegeven in de opdracht:
$$y(x_i, t_j + \Delta t) = 2y(x_i, t_j) - y(x_i, t_j - \Delta t)
    + \tau^2(y(x_i - \Delta x, t_j) - 2y(x_i, t_j) + y(x_i + \Delta x, t_j))$$

Met:
$$\tau = \frac{c \Delta t}{\Delta x}$$
$\tau$ geeft de stabiliteit van de oplossing. In het document dat is
meegeleverd met de opdracht is bewezen dat de oplossing stabiel is als, en
alleen dan als $\tau \leq 1$.

\subsubsection*{Initialisatie}
Voor de begintoestand ($t = 0$) van de snaar zijn er twee toestanden
geïmplementeerd:
\begin{itemize}
    \item Een sinusvorm: $y(x) = sin(n \pi \frac{x}{l})$. Hierbij is $n$ een
        constante en $l$ is de lengte van de snaar.
    \item Een snaar vastgegegrepen op positie $x_p$:
        $$y(x) = \left\{ \begin{array}{ll}
            \frac{x}{x_p}         & (x < x_p) \\
            \frac{l - x}{l - x_p} & (x > x_p) \\
        \end{array} \right.$$
\end{itemize}

De gegeven formule berekent de toestand van een punt $x_i$ op de snaar op het
tijdstip $t_j + \Delta t$. Hiervoor zijn de vorige twee tijdstoestanden nodig
(op tijdstip $t_j$ en $t_j - \Delta t$). Bovenstaande initialisatiemethoden
berekenen alleen de toestand van de snaar op $t = 0$. Om de toestand op $t = 1$
te berekenen zijn volgens de gegeven formule de tooestand op $t = 0$ en op $t =
-1$ nodig. Omdat de toestand op $t = -1$ onbekend is, moet $y(x_i, 1)$ op een
andere manier worden berekend. Voor dit uitzonderingsgeval wordt gebruik
gemaakt van het feit dat de snaar op tijdstip $t = 0$ in rusttoestand is. Dit
betekent dat de toestand op tijdstip $t = -1$ gelijk is aan de toestand op
tijdstip $t = 1$. Met dit gegeven kan de formule als volgt worden beschreven
voor $t = 1$:

\begin{table}[H]
\begin{tabular}{rl}
$y(x_i, t_1)$ & $= 2y(x_i, t_0) - y(x_i, t_{-1})
+ \tau^2(y(x_i - \Delta x, t_j) - 2y(x_i, t_j) + y(x_i + \Delta x, t_j))$ \\
\vspace{4mm}
& $= 2y(x_i, t_0) - y(x_i, t_1)
+ \tau^2(y(x_i - \Delta x, t_j) - 2y(x_i, t_j) + y(x_i + \Delta x, t_j))$ \\
\vspace{4mm}
$ 2y(x_i, t_1)$ & $= 2y(x_i, t_0)
+ \tau^2(y(x_i - \Delta x, t_j) - 2y(x_i, t_j) + y(x_i + \Delta x, t_j)) $ \\
$y(x_i, t_1)$ & $= y(x_i, t_0) + \frac{1}{2}
\tau^2(y(x_i - \Delta x, t_j) - 2y(x_i, t_j) + y(x_i + \Delta x, t_j))$ \\
\end{tabular}
\end{table}

Deze formule heeft alleen de toestand op $t = 0$ nodig. In de implementatie
moet (alleen) voor het tijdstip $t = 1$ dus deze omgeschreven formule worden
gebruikt, in plaats van de gegeven formule.

\subsection{Parallellisatie}

Omdat voor de toestand van een punt op de snaar de vorige tijdtoestand nodig
is, zijn er weinig mogelijkheden om te parallelliseren in de tijddimensie.
Omdat deze afhankelijkheid er in de afstandsdimensie niet is (om de uitwijking
op een punt van de snaar te berekenen zijn immers alleen waarden uit de vorige
tijdstippen nodig), is ervoor gekozen om hierin te parallelliseren. Dit gebeurt
door het verdelen van de snaar in stukken van een grootte die proportioneel is
aan de verwerkingskracht van de bijbehorende ``node''. Omdat de computers die
in dit geval worden gebruikt hetzelfde zijn, kan de snaar in gelijke stukken
worden verdeeld. Elke node berekent voor zijn eigen snaargedeelte steeds de
volgende tijdstpestand voor elke positie op het gedeelte, waarbij tussen de
tijdstappen de meest linker- en rechter waardes worden opgehaald van de
``omliggende'' nodes.

Omdat slechts voor de twee randwaardes telkens hoeft te worden gecommuniceerd
tussen de nodes, zal de parallellisatie waarschijnlijk weinig overhead
opleveren. Vooral bij een kleine $\Delta x$ verwacht ik dat het parallelle
programma een merkbare performancewinst zal laten zien. Merk op dat tijdens de
communicatie moet worden gewacht op de randwaardes van de omliggende nodes, wat
ervoor kan zorgen dat een node korte tijd ``idle'' is.

\subsubsection*{Tijdstappen parallelliseren}

Bij wijze van gedachtenexperiment ben ik nagegaan of er in de tijdsdimensie ook
te parallelliseren is, en het viel mij op dat een vorm van ``pipelining'' te
implementeren zou zijn:

Op het moment dat de toestand op het punt $i$ tijdstip $j$ ($y(x_i, t_j)$) is
berekend, kan de volgende toestand ($y(x_i, t_{j + 1})$) pas worden berekend
wanneer $y(x_{i - 1}, t_j)$ en $y(x_{i + 1}, t_j)$ zijn berekend. De rest van
de posities zijn irrelevant, en hoeven dus ook niet te worden berekend voordat
$y(x_i, t_{j + 1})$ kan worden berekend. In berekeningsstappen is dit als volgt
uit te beelden:
\begin{table}[H]
\begin{tabular}{rc}
    & $x \rightarrow$ \\
    & \\
    $\underset{\downarrow}{t}$ &
    $\left. \begin{array}{ccccc}
    1      & 2      & 3      & 4      & \ldots \\
    4      & 5      & 6      & \ldots & \ldots \\
    6      & 7      & \ddots & \ldots & \ldots \\
    8      & \vdots & \vdots & \ddots & \ldots \\
    \vdots & \vdots & \vdots & \vdots & \ddots \\
    \end{array} \right.$ \\
\end{tabular}
\end{table}

We zien dat op het moment dat van de eerste toestand de derde waarde is
berekend, van de tweede toestand de eerste waarde kan worden berekend, etc..
Deze methode is moeilijk te implementeren en zal veel communicatie vergen
tussen de verschillende processen. Het is daarom niet verstandig om eerst in
de tijddimensie te parallelliseren. De genoemde eigenschappen zouden echter
wellicht wel kunnen worden gebruikt als uitbreiding op het verdelen van de
snaar in delen, bijvoorbeeld zou bij het wacthen op de randwaardes van
omliggende nodes al in het midden van het snaargedeelte kunnen worden begonnen
met doorrekenen (merk op dat het maximum aantal door te rekenen tijdsstappen in
dat geval een maximum heeft dat proportioneel is aan het aantal afstandsstappen
van het snaargedeelte).

\section{Implementatie}

\subsection{Sequentiëel}

Het sequentiële programma heeft een simpele opzet: de \texttt{main}-methode
interpreteert de parameters en alloceert het benodigde geheugen. vervolgend
wordt een initialisatiemethode aangeroepen, en daarna de berekeningsmethode.
tot slot wordt het gealloceerde geheugen weer vrijgegeven.
de initialisatie van de snaar wordt gedaan in een aparte functie die eigen is
aan de initialisatiemethode (in dit geval alleen ``sinus'' en ``plucked'')
zodat eventueel op eenvoudige wijze een nieuwe initialisatiemethode kan worden
toegevoegd.

De eigenschappen van de snaar worden aan het programma meegegeven
als parameters in de volgende volgorde:
\begin{enumerate}
    \item \texttt{INIT\_METHOD} \\
        De te gebruiken initialisatiemethode: ``sinus'' of ``plucked''.
    \item \texttt{CALCULATION\_STEPS} \\
        Het aantal te berekenen tijdstappen.
    \item \texttt{LENGTH} \\
        De lengte van de snaar in meters.
    \item \texttt{DX} \\
        De stapgrootte voor de afstandsdimensie in meters.
    \item \texttt{TAU} \\
        De waarde van $\tau$.
    \item \texttt{N|XP} \\
        Een constante die eigen is aan de initialisatiemethode: $n$ voor
        ``sinus'', $x_p$ voor ``plucked''.
\end{enumerate}

\subsubsection*{Cyclische buffer}

Om te voorkomen dat bij een groot aantal tijdstappen een enorme hoeveelheid
geheugen wordt gealloceerd, worden op elk moment alleen de uitwijkingen in
laatste twee tijdstappen opgeslagen. Om dit te realiseren implementeert het
programma een cyclische buffer: de indices van de gealloceerde lijsten
verschuiven elke tijdstap zodat telkens de oudste waardes worden overschreven
met de nieuwe. Dit verschuiven gebeurt op regel 101-103:
\begin{verbatim}
prev = current;
current = next;
next = (next + 1) % 3;
\end{verbatim}

Door het gebruik van deze buffer is het geheugengebruik van het programma
proportioneel aan het aantal afstandsstappen (ofwel
$\mathcal{O}(\frac{\texttt{LENGTH}}{\texttt{DX}})$), en onafhankelijk van het
aantal tijdstappen.

\subsubsection*{Verbositeit}

De uitvoer van het programme is alleen interessant wanneer deze ook
daadwerkelijk wordt gebruikt, bijvoorbeeld in een grafiek. Echter, de
mogelijkheid moet bestaan om de uitvoer uit de programmacode te filteren, zodat
alleen de tijd het berekenen kan worden gemeten. Hiervoor heeft het prrogramma
een definitie:
\begin{verbatim}
#define VERBOSE
\end{verbatim}
Wanneer deze definitie in commentaar wordt geplaatst zullen alle print
statements worden genegeerd tijdens de compilatie (ook de functie
\texttt{print} zelf). Dit gebeurt op deze manier, alvorens te compileren,
zodat bij de uitvoer van een benchmark geen enkele statement verloren gaat aan
uitvoer van het programma.

\subsection{Parallel}

Het parallelle programma is een uitbreiding van het sequentiële programma, en
onderscheidt zich vooral door de volgende verschillen:

\begin{itemize}
    \item De \texttt{main}-methode alloceert nu alleen het geheugen dat nodig
        is voor het gedeelte van de snaar dat bij het uitgevoerde proces hoort.
    \item De functie \texttt{interact} is toegevoegd. Deze functie wordt
        uitgevoerd na het berekenen van alle uitwijkingen op het snaargedeelte
        in een bepaalde tijdstap, en dient voor de communicatie van randwaardes
        naar en vanuit de omliggende processen.
    \item De uitvoer van het programma wordt niet meer geschreven naar
        \texttt{stdout}, maar naar elk proces schrijft naar een eigen bestand
        (uiteraard alleen wanneer \texttt{VERBOSE} is gedefiniëeerd).  Deze
        bestanden kunnen later weer worden gecombineerd tot een enkele reeks
        waarden m.b.v. \emph{combine.py}.
\end{itemize}

\subsection{Scripts}
\label{sec:scripts}

Voor het uitvoeren van het parallelle programma zijn verschillende
shell- en python-scripts aanwezig:

\begin{itemize}
    \item \emph{par.sh} \\
        Voert het parallelle programma uit door \texttt{mpirun} aan te roepen.
        De eerste parameter is het aantal te starten processen, de rest van de
        parameters wordt meegegeven aan het parallelle programma.
    \item \emph{diff.sh} \\
        Voert zowel het sequentiële als het parallelle proces uit en vergelijk
        de uitvoer van beide programma's. Het verschil wordt geprint m.b.v. het
        commando \texttt{diff}. Als alles goed gaat is de uitvoer van dit
        script dus leeg. De parameters van dt programma zijn gelijk aan de
        parameters voor \emph{par.sh}.
    \item \emph{benchmark.sh} \\
        Voert een aantal tijdmetingen uit voor een verschillend aantal nodes
        meegegeven voor parallelle programma. De parameter geeft het aantal
        gewenste afstandsstappen (gelijk aan $\frac{1}{\texttt{DX}}$).
    \item \emph{compare.sh} \\
        Vergelijkt de runtime van het sequentiële en parallelle programma voor
        een vast aantal parallelle nodes, maar een variabel aantal
        afstandsstappen.
        Voert een aantal tijdmetingen uit voor een vast aantal parallel nodes
        meegegeven voor parallelle programma. De parameter geeft het aantal te
        starten parallelle processen.
    \item \emph{combine.py} \\
        Combineert de uitvoer van de verschillende parallelle processen tot een
        enkele reeks (zoals de uitvoer van het sequentiële programma zou zijn).
        De enige parameter voor dit script is het aantal processen dat is
        uitgevoerd (dat gelijk is aan het aantal bestanden met uitvoer).
    \item \emph{draw.py} \\
        Toont een animatie van een reeks gegenereerde data. De parameter van
        dit programma is de printresolutie. Als hier bijvoorbeeld 10 voor wordt
        opgegegeven, wordt van de gegeven invoer (gelezen van \texttt{stdin})
        elke 10 regels een regel geprint. Als tweede parameter kan de tijd
        tussen twee frames worden opgegeven in seconden (standaard $0.10s$).
    \item \emph{plot.py} \\
        Zelfde doeleinde als \emph{draw.py}, maar produceert een statische
        serie grafieken (met name voor gebruik in het verslag). Als optionele
        parameter kan een bestand worden opgegeven om de grafieken in op te
        slaan.
    \item \emph{plot\_benchmark.py} en \emph{plot\_compare.py} \\
        Produceren grafieken van de uitvoer van \emph{benchmark.sh} en
        \emph{compare.sh} (met name voor gebruik in het verslag). Als optionele
        parameter kan een bestand worden opgegeven om de grafieken in op te
        slaan.
\end{itemize}

\subsection{Gebruik}

Hier volgen ter verduidelijking enkele voorbeelden van het (bedoelde) gebruik
van bovenstaande scripts:

\begin{verbatim}
# Sequentiëel programma
$ ./seq sinus 1000 1 .01 1 2

# Parallel programma (4 processen)
$ ./par.sh 4 sinus 1000 1 .01 1 2

# Animatie sequentiële programma
$ ./seq sinus 1000 1 .01 1 2 | ./draw.py

# Animatie parallelle programma
$ ./par.sh 4 sinus 1000 1 .01 1 2; ./combine.py 4 | ./draw.py

# Verschillen in programma's (lege uitvoer)
$ ./diff.sh 4 sinus 1000 1 .01 1 2
$ ...

# Benchmark voor 2, 4, 6 en 8 nodes met 1000 afstandsstappen (DX = .01)
$ ./benchmark.sh 8 2 1000

# Vergelijking voor sequentiëel vs. 4 nodes parallel
$ ./compare.sh 4
\end{verbatim}

Indien een verkeerd aantal parameters wordt meegegeven zal elk programma en
script ook afzonderlijk een melding printen over het correcte gebruik \\
(``Usage: ...'').

\section{Resultaten}

In dit hoofdstuk zal eerst worden gecontroleerd of beide programma's correcte
uitvoer leveren. Vervolgens zal de performance worden vergeleken.

\subsection{Trillende snaar}

De uitvoer van het sequentiële programma wordt gecontroleerd door enkele
verschillende parameters mee te geven en te controleren of de vorm van de snaar
overeenkomt met de verwachting.

\subsubsection*{Test 1: stabiele sinusvorm}

De eerste test is een sinusvorm met $n = 1$, verdeeld over 1000
afstandsstappen ($l = 1.0$ en $dx = 0.001$). Voor maximale stabiliteit kiezen
we $\tau = 1$. De uitvoer van deze test is te vinden in bijlage
\ref{app:test-1}.

De grafiek heeft een enkel maximum ($n = 1$) en ziet heeft een sinusvorm, dus
kan worden geconcludeerd dat de uitvoer voor deze parameters correct is.

\subsubsection*{Test 2: instabiele sinusvorm}

In de tweede test wordt voor $\tau$ waarde 1.2 meegegeven, om te controleren of
instabiliteit optreedt. De uitvoer staat in bijlage \ref{app:test-2}.

We zien dat de sinusvorm na een aantal tijdstappen ``opblaast'': de cumulatieve
fout in de berekening is te groot geworden.

\subsubsection*{Test 3: ``plucked'' stabiel}

In deze test wordt een ``plucked'' snaar getekend, waarbij de snaar wordt
vastgegrepen op $x = 0.3$. Verder zijn de parameters hetzelfde als in de eerste
test. De uitvoer is terug te vinden in bijlage \ref{app:test-3}.

De snaar is inderdaad vastgegrepen op het punt 0.3, en de vorm ontwikkelt zich
zoals de bedoeling is.

\subsubsection*{Parallellisatie}

Omdat de uitvoer van het sequentiële programma correct is bevonden, dient deze
als maatstaf voor het parallelle programma. Als dit dezelfde uitvoer heeft als
het sequentiële programma, is de implementatie dus ook correct. Om dit te
testen wordt het script \emph{diff.sh} gebruikt. In alle tests worden 4
processen gestart:

\begin{verbatim}
$ ./diff.sh 4 sinus 2000 1 .001 1 1
$ ./diff.sh 4 sinus 50 1 .01 1.2 2
$ ./diff.sh 4 plucked 2000 1 .001 1 .3
$ ...
\end{verbatim}

De uitvoer van alle tests is leeg, wat betekent dat de uitvoer van het
parallelle en het sequentiële programma gelijk zijn. Hieruit kan worden
geconcludeerd dat ook het parallelle programma correct is geïmplementeerd..

\subsection{Performance}

\subsubsection*{Testomgeving}

Alle tijdmetingen worden uitgevoerd door het script \emph{benchmark.sh}. Omdat
het programma is geparallelliseerd in de afstandsdimensie, is de enige
variabele het aantal te berekenen afstandsstappen (\texttt{STRING\_STEPS}). We
kiezen $l = 1.0$, zodat $dx = \frac{1.0}{\texttt{STRING\_STEPS}}$. De rest van
de parameters zijn als volgt gekozen: $tijdstappen = 10000, \tau = 1$ en
$n = 2$.

Ondanks de aanwezigheid van dualcore-machines wordt elk proces op een
afzonderlijke machine uitgevoerd. Hierdoor is de communicatie altijd tussen
aparte machines, en niet binnen dezelfde machine (wat de latency zou verlagen).

Elke tijdmeting wordt vier maal uitgevoerd, waarna de laagste waarde wordt
gebruikt. De laagste waarde is interessanter dan de gemiddelde waarde, omdat
bij de laagste waarde de ``idle time'' van een proces het kleinst is We zijn
immers alleen geïnteresseerd in de tijd die een proces daadwerkelijjk aan het
rekenen is.

\subsubsection*{Speedup}

De benchmark wordt uitgevoerd met een vast aantal afstandsstappen. Indien dit
aantal te klein is zal de communicatie in het parallelle programma veel
overhead opleveren. Figuur \ref{fig:benchmark} toont het resultaat voor 50000
stappen\footnote{Zie bijlage \ref{app:benchmark} voor de precieze metingen},
waarbij een zichtbare speedup is in het parallelle programma. Een Least Squares
fit laat een minimum zien bij 9-10 processen. Dit betekent dat het programma
het meest efficiënt wordt uitgevoerd met 9 tot 10 processen. De kleinste meting
is gedaan bij 9 processen, de speedup bij dit aantal is $\frac{9.67}{2.57} =
3.76$ ten opzichte van het sequentële programma.

\begin{figure}[H]
    \label{fig:benchmark}
    \includegraphics[width=12.1cm]{benchmark.pdf}
    \caption{Runtime met parameters \texttt{sinus 10000 1 .00002 1 2}. De
             blauwe punten zijn de metingen van het parallelle programma, de
             groene lijn is een Least Squares datafit van die metingen. Het
             rode punt linksboven is het resultaat van het sequentiële
             programma.}
\end{figure}

\subsubsection*{Overhead communicatie}

Zoals al eerder gezegd is het waarschijnlijk dat bij een klein aantal
afstandsstappen het parallelle programma langzamer zal zijn dan het
sequentiële programma, door overhead van de communicatie. Omdat het parallelle
programma bij de benchmark het snelst is met 9 processen, zullen we vergelijken
wanneer het sequentiële programma voordeel heeft t.o.v. 9 parallelle processen.
Voor dit experiment wordt het script \emph{compare.sh} gebruikt, de resultaten
hiervan staan in figuur \ref{fig:compare}.

We zien dat tot ongeveer 8500 afstandsstappen het sequentiële programma sneller
is dan het parallelle programma, daarna wordt het parallelle programma sneller.
De groene pieken rechts kunnen worden afgedaan als uitschieters, deze kwamen in
een andere uitvoer van hetzelfde programma niet naar boven. Deze zijn ook
minder interessant dan de kruising van de blauwe en groene lijn.

Merk op dat deze meting alleen met 9 processen is gedaan, met minder processen
is het omslagpunt wellicht al bij minder afstandsstappen (een 3D grafiek zou
hier wellicht een beter licht op werpen).

\begin{figure}[H]
    \label{fig:compare}
    \includegraphics[width=13cm]{compare.pdf}
    \caption{Runtime van sequentiëel (blauw) vs. parallel (groen) met
             parameters \texttt{sinus 10000 1} \emph{dx} \texttt{1 2}.}
\end{figure}

\pagebreak
\appendix

\section{Uitvoer tests}

\subsection{Test 1}
\label{app:test-1}

\begin{figure}[H]
    \includegraphics[width=15cm]{sinus-stable.pdf}
    \caption{Resultaten met parameters \texttt{sinus 2000 1 .001 1 1}.}
\end{figure}

\subsection{Test 2}
\label{app:test-2}

\begin{figure}[H]
    \includegraphics[width=15cm]{sinus-unstable.pdf}
    \caption{Resultaten met parameters \texttt{sinus 50 1 .01 1.2 2}.}
\end{figure}

\subsection{Test 3}
\label{app:test-3}

\begin{figure}[H]
    \includegraphics[width=15cm]{plucked-stable.pdf}
    \caption{Resultaten met parameters \texttt{plucked 2000 1 .001 1 .3}.}
\end{figure}

\section{Uitvoer benchmarkscript}
\label{app:benchmark}

\lstinputlisting{bench.txt}

\end{document}
