\input{../../../utils/header_article.tex}

\title{Generalized Roy Model}
\subtitle{-- Problem Set 4 --}
\date{}

\begin{document}\maketitle\vspace{-2cm}

In this problem set, we are going to make use of the open-source Python package \href{https://github.com/OpenSourceEconomics/grmpy}{\texttt{grmpy}} to gain a practical understanding of the generalized Roy model and the economics behind it. We will employ \href{https://github.com/OpenSourceEconomics/grmpy}{\texttt{grmpy}} for both simulation and estimation purposes and, in particular, estimate \emph{marginal treatment effects} (MTE). To become familiar with the capabilities of \href{https://github.com/OpenSourceEconomics/grmpy}{\texttt{grmpy}}, we will explore the relationship between college education and income using a toy data set in Task A.
In Task B, we will then conduct a Monte Carlo analysis using our own simulated data. Auxiliary functions for Task B are provided in the file \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/sources/auxiliary.py}{\texttt{auxiliary.py}}.

\section*{Task A}

Suppose we want to estimate the effect of post-secondary education on income. We use a toy model where log earnings ($Y$) is a function of experience ($exp$), experience squared ($expsq$), and mother's education ($momsEdu$). The decision to enroll in college ($D$) is modeled as a function of mother's education and distance to college ($distCol$).

\begin{boenumerate}

  \item Open a \texttt{Jupyter Notebook} and import the data set \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/data/education_data_raw.pkl}{\texttt{education-data-raw.pkl}} --- we recommend using the software presented in class (such as \texttt{pandas} and \texttt{seaborn}).
  Before analyzing the data and estimating the marginal treatment effect of college attendance, you need to clean the data set. Drop missing values and plot the wage distribution. \emph{Are there any wage outliers?} If so, drop them.
  When you are done, save the data set as a new \texttt{pickle} file.

   \item Open the initialization file \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/sources/education.grmpy.yml}{\texttt{education.grmpy.yml}} and put the name of the cleaned data set you just saved in the blank after \texttt{["ESTIMATION"]["file"]:} (Hint: You do not need to do this in your Jupyter Notebook. You can use a simple Text Editor, such as \texttt{Sublime}, instead).

   \item You are now ready to estimate the marginal treatment effect of college attendance! \texttt{grmpy} offers two ways to do so. First, a parametric normal model. And second, the semiparametric method of \emph{local instrumental variables} (LIV) \citep{Heckman.2006d}.
   Using both models, estimate the MTE and plot it, while choosing different colors for the parametric and semiparametric plots, respectively. Note that for the semparametric model, the number of bootstrap iterations needs to be specified when plotting the MTE. Choose a suitable number.

\end{boenumerate}

\section*{Task B}

   To gain more insights into the economic implications behind \texttt{grmpy} and the realm of \emph{marginal treatment effects}, you will now perform a simulated Monte Carlo analysis.
   Simulated data allows us to explore additional objects of interest, for which we are not able to obtain reliable information using empirical data sets. With simulated data, we have information on the whole range of potential outcomes for each individual and can bypass the evaluation problem. Moreover, we can directly construct conventional treatment effect parameters on the population level (e.g. see chapters 2.3, 2.4, and 3.3 in \cite{Heckman.2007e}).

   \vspace{0.5cm}

   We will stick with our college example from above. For simplicity, however, assume now that log earnings ($Y$) only depend on mother's education ($momsEdu$), and the enrollment decision is solely driven by distance to college ($distCol$). To help you along with the analysis, we have prepared some auxiliary functions in  \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/sources/auxiliary.py}{\texttt{auxiliary.py}}.

\begin{boenumerate}
  \item Simulate a data set based on the intialization file \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/sources/sim.grmpy.yml}{\texttt{sim.grmpy.yml}}.
  \textit{What does the joint distribution of potential outcomes look like?}

  \item Compute the \emph{average treatment effect} (ATE), the \emph{average treatment effect on the treated} (TT), and the \emph{average treatment effect on the untreated} (TUT) by hand (Note: there are no auxiliary functions required so far). \emph{How does the ATE compare with the other treatment effect parameters?} \emph{What does this imply for the marginal treatment effect?}

  \item Plot the distribution of college-related wage benefits ($Y_1 - Y_0$). \textit{Hint:} You can verify your results in \textbf{2)} by plotting the effects along with the distribution of benefits.

\end{boenumerate}

  \noindent We now introduce essential heterogeneity to our simulated sample.

\begin{boenumerate}\setcounter{enumi}{3}

  \item Simulate a new sample based on \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/sources/sim-eh.grmpy.yml}{\texttt{sim-eh.grmpy.yml}}. \emph{How is essential heterogeneity reflected in the distribution of unobservables?} Compare the two simulated data sets.

  \item \emph{Do the conventional treatment effect parameters differ now?} Construct them again as you did in \textbf{(2)}.

  \item Investigate the shape of the MTE with and without essential heterogeneity. \emph{What does this imply for our toy model of the monetary effect of college attendance? Which individuals are more inclined to select into college?}

  \item Let us now begin with our Monte Carlo exploration! Simulate a new sample based on \href{https://github.com/HumanCapitalAnalysis/microeconometrics/blob/master/problem-sets/04-generalized-roy-model/sources/mc.grmpy.yml}{\texttt{mc.grmpy.yml}}. Note that the correlation between $U_1$ and $V$ (\texttt{["DIST"]["params"][2]}) is still zero, i.e. no essential heterogeneity is present so far. \emph{But how do the ATE and TT diverge when the (absolute) correlation between $U_1$ and $V$ is gradually increased?} Note that we consider the absolute value of the correlation coefficient. In the example here, the correlation becomes actually more negative.

  \item For three different estimation methods (assumption of random assignment, Ordinary Least Squares, \href{https://bashtage.github.io/linearmodels/doc/iv/introduction.html}{\texttt{Instrumental Variables}}) do the following:
  \begin{enumerate}
  	\item Compute the ATE without essential heterogheneity using the data you simulated in \textbf{7)}.
  	\item Plot the estimated ATE in comparison with the underyling population ATE for incremental positive selection into treatment.

  \end{enumerate}

  \textit{Note}:
  \begin{itemize}
  	\item Random assignment implies a simple comparison of average outcomes between treated and untreated individuals.
  	\item For the OLS setup, choose a simple regression of $Y$ on $D$ plus a constant. The coefficient on $D$ then captures the ATE.

  	\item In your \href{https://bashtage.github.io/linearmodels/doc/iv/introduction.html}{\texttt{IV/2SLS}} setting, use the full model. \emph{What is the endogeneous regressor? What is the instrument?}
  \end{itemize}

  Additionally, use \texttt{grmpy} to repeat step \textbf{b)} for both the paramteric and semiparametric MTE approach. Compare wih the estimation results above (for a discussion see chapter 3.1 in \cite{Heckman.2007f}).

\end{boenumerate}

\nocite{grmpy-1.0}
%\nocite{Heckman.2006d}
%\nocite{Heckman.2007e}
%\nocite{Heckman.2007f}

\bibliographystyle{apacite}
\bibliography{../../../submodules/bibliography/literature}

\end{document}


\section{Buffer abstraction}

As we have seen, some of the statements available to the programmer make use of a write buffer. Since depending on the memory model chosen, we may encounter different behaviours when running the same program using different memory models, we face the challenge of rewriting the program in a way that its behaviour will be conclusively defined, independent of the memory model chosen. We will do this by completely eliminating any buffer access by abstracting the buffers as sets of local variables. In order for us to be able to assume the lack of buffer operations, we will need to eliminate fence- and flush statements. As for remote stores and loads, we can replace them with code that simulates their buffer-related behaviours using non-WMM statements, retaining the use of store and load statements to represent assignment statements involving global variables without the involvement of the buffer. Since this intermediary code will never be run, but instead will be further abstracted in the predicate abstraction stage, we may safely ignore any regular WMM-behaviour such statements might have, as we will handle store- and load statements like local assignments for predicate abstraction purposes. The reason we refrain from using local assignment statements instead of store- or load statements in our abstracted program is our desire to preserve syntactic correctness even in our intermediary program, and to highlight the fact, that at certain points, load statements will actually perform a remote read, and that flush statements may at one point actually perform remote writes.\\

\subsection{Buffer size analysis}

For the purpose of determining the maximum buffer space needed by any global variable, we count the number of store statements in the control flow of the program within each process $i$. In case there are less store statements on a variable than the user-specified maximum buffer size $K$, we will only allocate the necessary number of buffer variables, which is the minimum of $K$ and the number of stores on that variable in the process $i$. We will refer to this number as $s_{x,i}$ for any global variable \lstinline$x$ when employing PSO, and $s_i$ for TSO.\\

In detail, we first take the AST representing the program, and cascade through it from the root to the leaves. Whenever we encounter a program point, we draw one control flow edge from that program point to any other program point that might directly follow in a possible sequential execution order. As shown in Figure 1, keywords such as \lstinline$else$ and \lstinline$endif$ are not program points, yet \lstinline$nop$ or unlabelled statements are.\\

Once we have the control flow graph, we start at the first program point in every process $i$ with a visitor having $s_i$ or $s_{x, i}$ for all global variables \lstinline$x$ set to $0$, and traverse the entire graph until we encounter a dead end, or an already visited node (which is the case for cyclic control flow graphs). Every time we visit a program point, we take the maxima of the visitor's $s_{x, i}$ or $s_i$, depending on the current store order, and the current node's corresponding values, and store them in both the node and the visitor. If the visited node is a store, we increment the corresponding values by 1 beforehand. Figure 2. shows the snapshot of the state of the program with a visitor just having visited the third node from the top.\\

Note that all buffer size entries are initialized to $0$. The visitor propagates the accumulated values to every subsequent node visited. If a visitor encounters a node with more than one outgoing edge, it spawns a copy of itself for every additional edge. These copies carry the same values as their predecessors, and they have the same record of visited nodes, i.e. if a visitor comes upon a node that had already been visited by any one of its ancestors, it will also regard that node as visited. Conversely, if a visitor encounters a node which had been visited by at least one other visitor, but none of its direct ancestors, it will not regard that node as visited, and it will continue down the control flow graph as usual.\\

If a visitor encounters a node it regards to be visited, it still increments the corresponding value as needed. At this point, if there is any value stored in the node that is smaller than its counterpart stored by the visitor, an arbitrary increase of that value is detected and that value is set to TOP (which is regarded as infinitely large for comparison reasons). The node communicates this change in the direction of the control flow, and every node that is situated downward of this current node also gets its corresponding value set to TOP. At this point, the visitor stops. Figure 3. shows the snapshot of the state of the program with a visitor just having revisited the third node from the top, Figure 4. shows the aftermath of the TOP values cascading along the control flow edges, and finally, Figure 5. shows the state after the last visitor reaches the bottom most node after having been spawned in the fourth node from the top.\\

Note that while the order of evaluating the path of multiple visitors is irrelevant, their affecting each other by node value editing creates a race condition on nodes that are potentially visited by more than one visitor. Thus, the buffer size analysis is not parallelizable on the same process. However, different processes may be examined in parallel.

\begin{figure}[h]

	\caption{Control flow example}
	\centering
	\includegraphics[width=5cm]{png/controlflow_01.png}

\end{figure}

\pagebreak

\begin{figure}[h]

	\caption{Visitor reached 3rd node}
	\centering
	\includegraphics[width=12cm]{png/controlflow_02.png}

\end{figure}

\begin{figure}[h]

	\caption{Visitor reached 3rd node again}
	\centering
	\includegraphics[width=12cm]{png/controlflow_03.png}

\end{figure}

\pagebreak

\begin{figure}[h]

	\caption{TOP values cascaded}
	\centering
	\includegraphics[width=12cm]{png/controlflow_04.png}

\end{figure}

\begin{figure}[h]

	\caption{Visitor reached last node}
	\centering
	\includegraphics[width=12cm]{png/controlflow_05.png}

\end{figure}

\FloatBarrier

\pagebreak

\subsection{PSO abstraction}

Using PSO, every global variable has a separate buffer per process of the size $s_{x,i}$. Therefore, for each process $i$, for each global variable \lstinline$x$, there are $s_{x,i}$ buffer variables of the form \lstinline$x_j_i$, where $j \in [1, s_{x,i}]$. Since flushes are non-deterministic, we cannot keep track of occupied buffer spaces. Therefore, we declare 2 additional auxiliary variables for each global variable in each process: \lstinline$x_cnt_i$, which is initialised to 0 and otherwise has the value of the last buffer index to which a value of \lstinline$x$ was written, and \lstinline$x_fst_i$, which is analogous to \lstinline$x_cnt_i$ with the difference that it points to the first such index. Figure 6. illustrates the buffer allocation abstraction we apply for PSO programs.\\

\begin{figure}[h]

	\caption{PSO buffer abstraction approach}
	\centering
	\includegraphics[width=13cm]{png/pso_buffer.png}

\end{figure}

\pagebreak

\subsection{TSO abstraction}

Using TSO, every process has a separate buffer of the size $K$, which all global variables share. The buffer holds all values in an order-preserving manner: A value $v_{1}$ that is stored in the buffer before another value $v_{2}$ will also be written to memory before $v_{2}$. Therefore, the store order of values must be preserved in the program.\\

An economic way to do this is to keep track of the last element inserted into the buffer, and store the next value in the following slot. Since we cannot know the actual run-time allocation of the buffer, we will only be able to analyse the maximum buffer size of the entire process (which we express by $s_{i}$), but not of the individual variables. For each buffer slot, we will declare two variables of the form \lstinline$buf_j_i$, and \lstinline$own_j_i$, where $j \in [1, s_{i}]$.\\

We will also statically build an allocator table, which will pair every global variable to a unique numeric value which will allow us to identify the values stored in the buffer as belonging to its owner variable at run-time. We will use the notation $\rho(v)$ to denote the numeric allocator key of the variable \lstinline$v$. Thus, for every buffer slot with the number $j \in [1, s_{i}]$ in every process $i$, we will have a variable \lstinline$buf_j_i$ (which we will call a buffer variable) containing the value currently held by that buffer slot, and a variable \lstinline$own_j_i$ (which we will call a buffer allocator) containing the allocator key of the variable whose value is stored there. If the allocator is set to $0$, the buffer is marked to be free, and if it is set to $-1$, it is marked as invalidated by a flush. Figure 7. illustrates the buffer allocation abstraction we apply for TSO programs.\\

\begin{figure}[h]

	\caption{TSO buffer abstraction approach}
	\centering
	\includegraphics[width=13cm]{png/tso_buffer_02.png}

\end{figure}

\FloatBarrier

\subsection{Replacement rules}

After we compute all $s_{x,i}$ or $s_{i}$ for each global variable \lstinline$x$ and for each process $i$, we have enough data to statically perform all necessary replacements in our program to allow predicate abstraction. We will define different replacement rules for each store order.

\subsubsection{Replacing store statements}

Assuming \lstinline$x$ is a global variable, \lstinline$r$ is an integer expression, and we encounter \lstinline$store x = r$ in a process $i$, we will replace this statement with the code blocks listed below. Note, that the variable symbol is appended to every corresponding if-else statement in the case of TSO. This is done in order to allow us a fast context analysis on buffer variable assignments within these if-else blocks, which we will use later on.

\begin{multicols}{2}

PSO approach:

\begin{lstlisting}[frame=single, mathescape]
if (x_cnt_$i$ = $s_{x,i}$)
	abort("overflow");
endif;

if (x_fst_$i$ = 0)
	x_fst_$i$ = 1;
	x_1_$i$ = r;
endif;

x_cnt_$i$ = x_cnt_$i$ + 1;

if (x_cnt_$i$ = 2)
	x_2_$i$ = r;
endif;
$...$
if (x_cnt_$i$ = $j$)
	x_$j$_$i$ = r;
endif;
$...$
if (x_cnt_$i$ = $s_{x,i}$)
	x_$s_{x,i}$_$i$ = r;
endif;
\end{lstlisting}

\columnbreak

TSO approach:

\begin{lstlisting}[frame=single, mathescape]
if (own_1_$i$ = 0)    // x
    buf_1_$i$ = r;
    own_1_$i$ = $\rho($x$)$;
    goto $SEL$;
endif;

if (own_2_$i$ = 0)    // x
    buf_2_$i$ = r;
    own_2_$i$ = $\rho($x$)$;
    goto $SEL$;
endif;
$...$
if (own_$j$_$i$ = 0)    // x
    buf_$j$_$i$ = r;
    own_$j$_$i$ = $\rho($x$)$;
    goto $SEL$;
endif;
$...$
if (own_$s_{i}$_$i$ = 0)    // x
    buf_$s_{i}$_$i$ = r;
    own_$s_{i}$_$i$ = $\rho($x$)$;
    goto $SEL$;
endif;
abort("overflow");
$SEL$: nop;
\end{lstlisting}

\end{multicols}

\pagebreak

\subsubsection{Replacing load statements}

Assuming \lstinline$x$ is a global variable, \lstinline$l$ is a local variable, and we encounter \lstinline$load l = x$ in a process $i$, we will replace this statement with:

\begin{multicols}{2}

PSO approach:

\begin{lstlisting}[frame=single, mathescape]
if (x_cnt_$i$ = 0)
	load l = x;
endif;

if (x_cnt_$i$ = $s_{x,i}$)
	l = x_$s_{x,i}$_$i$;
endif;
$...$
if (x_cnt_$i$ = $j$)
	l = x_$j$_$i$;
endif;
$...$
if (x_cnt_$i$ = 2)
	l = x_2_$i$;
endif;

if (x_cnt_$i$ = 1)
	l = x_1_$i$;
endif;
\end{lstlisting}

\columnbreak

TSO approach:

\begin{lstlisting}[frame=single, mathescape]
if (own_$s_{i}$_$i$ = $\rho($x$)$)	// x
    l = buf_$s_{i}$_$i$;
    goto $LEL$;
endif;
$...$
if (own_$j$_$i$ = $\rho($x$)$)	// x
    l = buf_$j$_$i$;
    goto $LEL$;
endif;
$...$
if (own_2_$i$ = $\rho($x$)$)	// x
    l = buf_2_$i$;
    goto $LEL$;
endif;

if (own_1_$i$ = $\rho($x$)$)	// x
    l = buf_1_$i$;
    goto $LEL$;
endif;
load l = V;
$LEL$: nop;
\end{lstlisting}

\end{multicols}

\subsubsection{Replacing fence statements}

Assuming we encounter \lstinline$fence;$ in a process $i$, we will replace this statement with the following block for every global variable \lstinline$x$:

\begin{multicols}{2}

PSO approach:

\begin{lstlisting}[frame=single, mathescape]
assume (x_cnt_$i$ = 0);
assume (x_fst_$i$ = 0);
\end{lstlisting}

\columnbreak

TSO approach:

\begin{lstlisting}[frame=single, mathescape]
assume (own_1_$i$ < 1);
assume (own_2_$i$ < 1);
$...$
assume (own_$j$_$i$ < 1);
$...$
assume (own_$s_{i}$_$i$ < 1);
own_1_$i$ = 0;
own_2_$i$ = 0;
$...$
own_$j$_$i$ = 0;
$...$
own_$s_{i}$_$i$ = 0;
\end{lstlisting}

\end{multicols}

\subsubsection{Replacing flush statements}

Let $\beta$ be the number of global variables in $P$ and $\rho^{-1}$ be the inverse function of $\rho$, i.e. it returns the variable symbol belonging to the input number. Assuming we encounter \lstinline$flush;$ in a process $i$, we will replace this statement with:

\begin{multicols}{2}

TSO approach:

\begin{lstlisting}[frame=single, mathescape]
if (*)
    goto $FEL$;
endif;
if (own_1_$i$ > 0)
    $[flush\:1]$
endif;
if (*)
    goto $FEL$;
endif;
if (own_2_$i$ > 0)
    $[flush\:2]$
endif;
$...$
if (*)
    goto $FEL$;
endif;
if (own_$j$_$i$ > 0)
    $[flush\:j]$
endif;
$...$
if (*)
    goto $FEL$;
endif;
if (own_$s_{i}$_$i$ > 0)
    $[flush\:s_{i}]$
endif;
$FEL$: nop;
\end{lstlisting}

\columnbreak

The placeholder $[flush\:\alpha]$ describes the following code block for a number $\alpha$:

\begin{lstlisting}[frame=single, mathescape]
if (own_$\alpha$_$i$ = 1)	// $\rho^{-1}(1)$
    store $\rho^{-1}(1)$ = buf_$\alpha$_$i$;
    goto $SubFEL$;
endif;
if (own_$\alpha$_$i$ = 2)	// $\rho^{-1}(2)$
    store $\rho^{-1}(2)$ = buf_$\alpha$_$i$;
    goto $SubFEL$;
endif;
$...$
if (own_$\alpha$_$i$ = $j$)	// $\rho^{-1}(j)$
    store $\rho^{-1}(j)$ = buf_$\alpha$_$i$;
    goto $SubFEL$;
endif;
$...$
if (own_$\alpha$_$i$ = $\beta$)	// $\rho^{-1}(\beta)$
    store $\rho^{-1}(\beta)$ = buf_$\alpha$_$i$;
    goto $SubFEL$;
endif;
$SubFEL$: own_$\alpha$_$i$ = -1;
\end{lstlisting}

\end{multicols}

\pagebreak

PSO approach:

\begin{lstlisting}[frame=single, mathescape]
p: if (*)
	$[global\:variable\:flushes]$
	goto p;
endif;
\end{lstlisting}

The placeholder $[global\:variable\:flushes]$ describes the following code block for each global variable \lstinline$x$:

\begin{lstlisting}[frame=single, mathescape]
if (x_cnt_$i$ > 0)
	if (*)
		if (x_fst_$i$ > 1)
			if (x_fst_$i$ > 2)
				$...$
				if (x_fst_$i$ > $j$)
					$...$
					if (x_fst_$i$ > $s_{x,i} - 1$)
						store x = x_$s_{x,i}$_$i$;
					else
						store x = x_$(s_{x,i} - 1)$_$i$;
					endif;
					$...$
				else
					store x = x_$j$_$i$;
				endif;
				$...$
			else
				store x = x_2_$i$;
			endif;
		else
			store x = x_1_$i$;
		endif;

		x_fst_$i$ = x_fst_$i$ + 1;
	endif;
endif;
\end{lstlisting}


\documentclass{article}
\usepackage{algorithm}
\usepackage{amsmath}
\usepackage{algpseudocode}
\usepackage[utf8]{inputenc}

% New definitions
\algnewcommand\algorithmicswitch{\textbf{switch}}
\algnewcommand\algorithmiccase{\textbf{case}}
\algnewcommand\algorithmicassert{\texttt{assert}}
\algnewcommand\Assert[1]{\State \algorithmicassert(#1)}%
% New "environments"
\algdef{SE}[SWITCH]{Switch}{EndSwitch}[1]{\algorithmicswitch\ #1\ \algorithmicdo}{\algorithmicend\ \algorithmicswitch}%
\algdef{SE}[CASE]{Case}{EndCase}[1]{\algorithmiccase\ #1}{\algorithmicend\ \algorithmiccase}%
\algtext*{EndSwitch}%
\algtext*{EndCase}%

\begin{document}

\section{Algorithms}

\subsection{weighted positions}
Three of adiff's strategies (dfs, bfs and uniform) are instances of the following
algorithm that is parameterized by a weight function.

\begin{algorithm}
  \caption{Weighted positions}
  \begin{algorithmic}
  \Procedure{weightedPositions}{weight function $w$, translation unit $tu$, budget $b$}
  \State $\textit{reads} \gets \Call{findAllReads}{tu}$
  \State $\textit{constants} \gets \Call{findAllConstants}{tu}$
  \State $\textit{weights} \gets \text{map $w$ $reads$} $
  \For {$b$ times}
      \State $r \gets \text{randomly draw from $reads$ with weights $weights$}$
      \State $constant \gets ite(\text{randomBool()}
      , \text{constants.lookup(r.type)}
      , \Call{randomValue}{r.type})$
      \State let $assertion = \texttt{r.variable != constant}$
      \State $tu' \gets$ insert(tu, $assertion$, r.position)
      \State $result \gets \text{verify}(tu')$
  \EndFor
  \EndProcedure
\end{algorithmic}
\end{algorithm}
\begin{itemize}
\item `dfs' is `weightedPositions' parametrized by the weight function
  \[
    w_{\text{dfs}}(r) = depth (r.position)
  \]
\item `bfs' is `weightedPositions' parametrized by
  \[
    w_{\text{bfs}}(r) =  1 / depth(r.position)
  \]
\item `uniform-random' is `weightedPositions' parametrized by
  \[
    w_{\text{uniform}}(r) = 1
  \]
\end{itemize}

\newpage
\subsection{Zipper-based algorithms}
In short: A zipper is basically a two-dimensional iterator that can also be used
to modify an object. Whereas a regular iterator only has methods to ask for the
current element and to move the iterator to the next element, our zipper has the
following directions: TODO.
Two of our strategies are based on those zippers.

\begin{algorithm}
\begin{algorithmic}
  \caption{Random Walk}
  \Procedure{RandomWalk}{translation unit $tu$, budget $b$}
  \State $\textit{constants} \gets \Call{findAllConstants}{tu}$
  \State $zipper \gets \Call{makeZipper}{tu}$
  \While {$ b > 0 $}
  \State \Call{MoveRandomDirection}{$zipper$}
  \State $reads \gets \Call{ReadsAt}{zipper}$
  \If {$reads.length == 0$}
  \State \textbf{Continue}
  \EndIf
  \State $r \gets \text{randomly choose from $reads$}$
  \State $constant <- ite(randomBool(), lookupConstants, randomConstant)$
  \State let $assertion = $ \texttt{r.variable != constant}
  \State $tu' \gets \text{zipper.insert($assertion$)}$
  \State $verify(tu')$
  \State $b \gets b - 1$
  \EndWhile
  \EndProcedure
\end{algorithmic}
\end{algorithm}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{algorithm}[ht]
  \caption{'Smart' algorithm}
  \begin{algorithmic}
  \State var $zipper$
  \State var $\textit{constants}$

  \Procedure{Smart}{budget $b$}
  \While {zipper.inCompound()}
    \State zipper.go(Down);
  \EndWhile
  \State $ children \gets \Call{ExploreLevel}{\ }$
  \For {$c \in children$}
      \State $b' \gets \text{a fraction of b, proportional to $c.rating$} $
      \State $\Call{ExplorePosition}{c.position, c.rating, b'}$
  \EndFor
  \EndProcedure
  \vspace{1em}
  \Procedure{ExplorePosition}{position p, rating r, budget b}
  \State {zipper.gotoPosition(p)}
  \If {zipper.currentPosition.isFunctionCall()}
    \State $\Call{ExploreStatement}{b / 4}$
    \State {zipper.followFunctionCall()}
    \State $\Call{Smart}{b / 4}$ \Comment{recurse}
  \Else
    \State $\Call{ExploreStatement}{b / 4}$
  \EndIf
  \If {zipper.go(Down)}
    \State $\Call{Smart}{b / 2}$ \Comment{recurse}
  \EndIf
  \EndProcedure
  \vspace{1em}
  \Procedure{ExploreStatement}{budget b}
  \State {reads $\gets$ zipper.readsAtCurrentPosition()}
  \For {$r \in reads$}
  \If{$b \leq 0$} \State{\textbf{Break}}\EndIf

  \State {\textbf{let} asrt = a  assertion with constant from constant pool, or if pool
    is empty a random constant}
  \State {tu' $\gets$ zipper.insertBefore(art)}
  \State {verify(tu')}
  \State {$b \gets b - 1$}
  \EndFor
  \EndProcedure
\end{algorithmic}
\end{algorithm}

Of special interest here is the function $\Call{ExploreLevel}$, as this one
control how much of the budget is alloted  to different parts of the program.

\begin{algorithm}[ht]
  \begin{algorithmic}
  \Procedure{ExploreLevel}{}
   \State {$scores = \emptyset$}
   \State {$usedBudget = 0$}

   \Loop
   \State {tu' $\gets$ zipper.insert(assertFalseStmt)}
   \State {(results, conclusion) $\gets$ verify(tu')}
   \State {usedBudget $\gets$ usedBudget + 1 }
   \State {updateMovingAverages(results)}
   \State {d $\gets$ disagreement(conclusion)}
   \State {t $\gets$ irregularity(results.times)}
   \State {scores.add(d + t)}

   \If{not(zipper.go(Down))} \State{\textbf{Break;}} \EndIf
   \EndLoop
   \State {\Return (scores, usedBudget)}
  \EndProcedure

   \vspace{1em}
   \Procedure{Disagreement}{conclusion c}
    \Switch{c}
    \Case{StrongAgreement}
    \Return 0.1
    \EndCase
    \Case{WeakAgreement}
    \Return 1.0
    \EndCase
    \Case{Unsoundness} \Return 100
    \EndCase
    \Case{Incompleteness}
    \Return 10
    \EndCase
    \Case{Disagreement}
    \Return 3
    \EndCase
    \EndSwitch
   \EndProcedure

   \vspace{1em}
   \Procedure{irregularity}{timings $ts$}
   \State {proportions = fractions of timing t with its average time }
   \State {$s \gets \Sigma \{ relativeError(t_1,t_2) \mid t_1,t_2 \in proportions \}$}
   \State {\Return {$s / n^2$}}
   \EndProcedure
\end{algorithmic}
\end{algorithm}

Relative Error:
\[
  relativeError(a,b) &=
  \begin{cases}
    0 & \text{if $a = b = 0$} \\
    \dfrac{| a - b |}{\max (|a|, |b|)}

  \end{cases}

\]

\end{document}


\chapter{Related Works}

\section{NumPy}

NumPy is an open source library that has become fundamental to scientific computing in Python. Its main contribution to the language is the \verb|ndarray| object representing a multi-dimensional array that can contain a variety of supported data-types. Since NumPy was developed in C, NumPy contains a range of useful C API tools that allow for other extension modules to take advantage of its functionality \cite{numpyCApi}. Today, NumPy is the backbone for countless scientific and mathematical libraries that build on \verb|ndarray|s and NumPy functionality.

\quad One of the biggest draws for NumPy is its speed and efficiency. Without NumPy, Python programmers are limited to the built-in Python list which grows dynamically and can support content of mixed data types. NumPy arrays on the other hand are able to achieve better performance through constraints that allow them to behave like arrays in languages like C. NumPy's \verb|ndarray| objects can only contain a consistent data type, and arrays must maintain a fixed shape and size \cite{arrayNumpy}. Using these constraints, operations can be optimized and parallelized to efficiently act on the sequence of data. NumPy uses the idea of vectorization for its operations which are built on pre-compiled C code \cite{numpyDocs}. These vectorized functions ``broadcast'' operations across the entire sequence in a notation that is similar to what is seen in mathematics \cite{numpyDocs}. This improves readability by removing the need for explicit loops and iteration within Python.

\section{SciPy and Scikits}

SciPy is a library that has become the standard for modeling and computing scientific problems in Python. SciPy is built on top of NumPy and the \verb|ndarray|, but it adds a variety of new data structures such as sparse matrices and k-dimensional trees \cite{scipy}. These, in combination with methods for manipulating and visualizing data, create an ecosystem for solving a wide variety of problems. Today, more than 100,000 different code repositories use SciPy as a dependency \cite{scipy}. For specialized tasks, SciPy introduced the concept of SciPy toolkits, or SciKits, that add on packages for SciPy. Each SciKit brings specialized functionality for a variety of use-cases. Among these are scikit-image for image processing and scikit-learn which has become the gold-standard for machine learning in Python \cite{scikits}.

\section{Numba}

Numba is a just-in-time (JIT) compiler for CPython that is written as an extension library so that the interpreter does not need to be modified or replaced. Numba specializes in accelerating functions that use \verb|ndarray|s, NumPy functions and operators, and loops. It includes function decorators such as \verb|@jit| that can be dropped in to existing code to make it easy to accelerate without needing to be rewritten. Internally, Numba uses the LLVM compiler infrastructure. Python bytecode is analyzed and turned into an intermediate representation called the Numba IR \cite{lessonsLearned}. From there, Numba will attempt to infer types and lower the code to LLVM so that it can be compiled into efficient machine code. This compilation happens during the runtime of an application when the decorated function is called, but it only has to happen once for each function written since the compiled version is stored in a cache. Because this code is compiled and does not incur the overhead of the Python interpreter, Numba is able to achieve speeds comparable to those of C programs.

\quad Numba acceleration is not just limited to the CPU. Numba is able to take advantage of GPU acceleration by leveraging CUDA's version of the LLVM library, NVVM. Numba is even able to support ROCm-compatible GPUs by compiling code into HSA kernels and device functions following the HSA execution model \cite{numbaRocm}. Numba is not perfect, however. Due to Numba's high level of abstraction, details and tuning are lost in translation and Numba lacks support for features such as dynamic parallelism and texture memory \cite{gpucomppy}. Python applications using Numba usually reach between 50\% and 85\% performance of the equivalent C/C++ CUDA implementations on compute heavy workloads. \cite{lessonsLearned}.

\section{CuPy}

CuPy is an open source Python library that aims to bring GPU acceleration to NumPy and SciPy. CuPy includes a wide variety of accelerated functions including linear algebra operations, sorting, sparse matrices, and more. In many ways, CuPy can function as a drop-in replacement for NumPy by providing functions of the same names. Originally, CuPy was designed to support CUDA-capable GPUs and leveraged popular CUDA-accelerated libraries such as cuBLAS, cuDNN, cuRAND, cuSOLVER, and cuSPARSE. Now, CuPy has expanded into experimental support for the ROCm ecosystem, and it and uses the equivalent ROCm-based computing libraries \cite{cupyRocm}.

\quad One of CuPy's most versatile features is the ability for users to create their own GPU kernels. Code snippets can be created in C++ syntax that are compiled into binaries that can be cached and used in subsequent runs. User defined kernels have the flexibility to either perform element-wise operations on all items in the data, or reduction kernels to fold data using binary operators \cite{cupy}. This allows for powerful flexibility and more control over GPU execution from Python at the expense of requiring the programmer to know kernel code design in C++ syntax. Even using the built-in functions, however, CuPy programmers can see tens to 100s of times speedup over NumPy when playing to CuPy's strengths.

\section{PyCuda and PyOpenCL}

PyCUDA and PyOpenCL are two open source toolkits built on CUDA and OpenCL respectively. They take the approach of using runtime code generation (RTCG) in order to take advantage of GPU parallelism from within Python code. RTCG works off of the idea of ``metaprogramming'' where code is tasked with interpreting and creating different sets of code to solve a problem \cite{pycuda}. In the case of PyCUDA, CUDA kernels can be written as strings within the Python code that are compiled at runtime using the NVCC compiler and run on the GPU as a binary. The compiled GPU code can be cached for future re-use which increases the efficiency of the application on subsequent runs. By extending support to OpenCL, PyOpenCL is able to work across a variety of platforms independent of the GPU manufacturer.

\quad PyCUDA and PyOpenCL use a variety of techniques to tune the generated code. Loop slicing helps preserve locality of data access and use cache memory efficiently. The code can also can also adapt based on the amount of on-chip user-managed memory as well. Finally, tuning is performed based on available DRAM bandwidth. Although PyCUDA and PyOpenCL reduce the overhead of writing applications in pure C, C++, and CUDA, the programmer still needs to be familiar with the creation of GPU kernels in order to write the code templates within the Python program. This is not unlike kernel generation mechanisms in other libraries such as CuPy.

\section{TensorFlow}

TensorFlow is one of the most prevalent libraries for developing and training machine learning models, specializing in deep neural networks. It is compatible with a variety of architectures allowing TensorFlow to be deployed on CPU, GPU and TPU-based environments \cite{tensorflow}. It is also extremely flexible when it comes to the scale of the system it is deployed on. TensorFlow can run on devices as small as mobile phones or embedded devices, and it can scale up to run on large scale distributed systems with thousands of compute devices \cite{largeScaleTensorflow}. This makes it a powerful tool for a variety of machine learning fields such as speech recognition, natural language processing, computer vision, and more. TensorFlow also fits in very well with Python's library ecosystem. It is built around a multi-dimensional array type called a tensor, which are comparable to NumPy's \verb|ndarray|s, and these tensors are even compatible with NumPy operations.

\section{Keras}

Keras is a high-level API for neural networks that is built on top of TensorFlow and other supported backends. Its interface is designed to be as simple and flexible as possible, allowing its users to quickly prototype and create deep learning models. Keras is built around two data structures called models and layers. At its most simple level, a sequential stack of layers are combined together to create a sequential model. More complicated models are built around arbitrary graph topologies that can handle a wide variety of machine learning problems \cite{keras}.