\floatname{algorithm}{Listing} \newcommand\inflFP{\mathsf{infl^{FP}}} \newcommand\depFP{\mathsf{dep^{FP}}} \newcommand\stableFP{\mathsf{stable^{FP}}} \newcommand\touchedFP{\mathsf{touched^{FP}}} \newcommand\inflSI{\mathsf{infl^{SI}}} \newcommand\depSI{\mathsf{dep^{SI}}} \newcommand\stableSI{\mathsf{stable^{SI}}} \newcommand\stable{\mathsf{stable}} \newcommand\system{\mathcal{E}} %\mathsf{system}} \newcommand\parent{\mathsf{parent}} \newcommand\parents{\mathsf{parents}} \newcommand\val{\mathsf{val}} \newcommand\old{\mathsf{old}} \newcommand\best{\mathsf{best}} \newcommand\all{\mathsf{all}} \newcommand\fix{\mathsf{fix}} \newcommand\solnFP{\mathsf{soln^{FP}}} \newcommand\RFP{\mathsf{R^{FP}}} \newcommand\solnSI{\mathsf{soln^{SI}}} \newcommand\RSI{\mathsf{R^{SI}}} \newcommand\post{\mathsf{post}} \newcommand\pre{\mathsf{pre}} \newcommand\last{\mathsf{last}} \newcommand\Operators{\mathcal{O}} \newcommand\Systems{\mathbb{E}} \newcommand\improve{\mathsf{\textsc{improve}}} \newcommand\init{\mathsf{\textsc{init}}} \newcommand\eval{\mathsf{\textsc{eval}}} \newcommand\stabilise{\mathsf{\textsc{stabilise}}} \newcommand\solve{\mathsf{\textsc{solve}}} \newcommand\invalidate{\mathsf{\textsc{invalidate}}} \newcommand\fixpoint{\mathsf{\textsc{fixpoint}}} \newcommand\strategy{\mathsf{\textsc{strategy}}} \algblockx[Globals]{Globals}{EndGlobals}{\textbf{Globals:\\}}{} \algblockx[Assume]{Assumptions}{EndAssumptions}{\textbf{Assume:\\}}{} \algblockdefx[ReturnLambdaFn]{ReturnLambdaFn}{EndReturnLambdaFn}[1]{\textbf{return function} (#1)}{\textbf{end function}} \chapter{Demand-driven strategy improvement} \label{chap:contribution} In this chapter the main theoretical contribution of this thesis is presented: an extension on a $\max$-strategy iteration algorithm for solving fixpoint equations over the integers with monotonic operators\cite{Gawlitza:2007:PFC:1762174.1762203} (see Section \ref{sec:gawlitza-algorithm}). We devise an algorithm which runs in linear time in the best case. In this chapter we will begin by presenting the Work-list Depth First Search (W-DFS) fixpoint algorithm developed by Seidl et al.\cite{DBLP:tr/trier/MI96-11}. We will then discuss a modification of the algorithm to allow it to perform $\max$-strategy iteration. The chapter will then conclude with our Local Demand-driven Strategy Improvement (LDSI) algorithm to perform efficient strategy iteration and fixpoint iteration. % TODO: fix me, or something The existing algorithm as presented in Section \ref{section:basic-algorithm} consists of two iterative operations: fixpoint iteration and max-strategy iteration. Each of these operations consists of naively ``evaluating'' the system repeatedly until a further evaluation results in no change. Gawlitza et al.~proved that these iterations converge in a finite number of steps\cite{Gawlitza:2007:PFC:1762174.1762203} , but in practice this naive approach performs a large number of re-calculations of results which are already known. % TODO: add something here about variable dependencies \section{Preliminaries} In order to aid our explanation of these algorithms we will now define a few terms and notations. We will assume a fixed, finite set $X$ of variables. We will also define $\CZ = \Z \cup \{-\infty, \infty\}$. \begin{definition} A \textbf{variable assignment} is a partial function, $\rho: X \to \CZ$ that maps variables to values. The domain of a variable assignment, $\rho$, is represented by $\mathsf{domain}(\rho) \subseteq X$. An underlined value, $\underline{a}$, indicates a variable assignment $\{ x \mapsto a \mid \forall x \in X \}$. Variable assignments $\rho : X \to \CZ$ and $\varrho : X \to \CZ$ may be composed with the $\oplus$ operator in the following way: \begin{align*} (\rho \oplus \varrho)(x) = \left\{\begin{array}{lc} \varrho(x) & x \in \mathsf{domain}(\varrho) \\ \rho(x) & \mbox{otherwise} \end{array}\right., \forall x \in X \end{align*} \end{definition} \begin{definition} \textbf{Expressions:} The class of expressions, $E$, is defined inductively: \begin{itemize} \item If $z \in \CZ$ then $z \in E$ \item If $x \in X$ then $x \in E$ \item If $f : \CZ^k \to \CZ$ and $e_1,...,e_k \in E$ then $f(e_1,...,e_k) \in E$ \item Nothing else is in E. \end{itemize} We then define the semantics of these expressions by: \begin{align*} \llb z \rrb &= \lambda \rho . z & \forall z \in \CZ \\ \llb x \rrb &= \lambda \rho . \rho(x) & \forall x \in X \\ \llb f(e_1,...,e_k) \rrb &= \lambda \rho . f(\llb e_1 \rrb(\rho),...,\llb e_k \rrb(\rho)) \end{align*} By abusing our notation we will consider expressions, $e \in E$, as $e : (X \to \CZ) \to \CZ$ defined by $e = \llb e \rrb$. The subset of expressions of the form $\max\{ e_1, e_2, ... e_k \}$, with $e_1, e_2, ..., e_k \in E$ are referred to as \emph{$\max$-expressions}, denoted by $E_{\max} \subset E$. We define a function $\parents: E \to 2^E$, taking an expression, $e$, to the set of expressions containing $e$, $\{ e_\parent \mid e_\parent = f\{ ..., e, ... \}, f: \CZ^k \to \CZ \}$. $e$ is then said to be a \emph{subexpression} of $e_{\parent}$. \end{definition} \begin{definition} \textbf{Equation System:} An equation system is a mapping from variables to expressions. $\system : X \to E$. We define $E_\system \subset E$ to be the preimage of system, that is $E_\system = \{ e \in E \mid system(x) = e, \forall x \in X \}$. As each element of $E$ is also a function $(X \to \CZ) \to \CZ$, so an equation system is considered as a function $\system : X \to ((X \to \CZ) \to \CZ)$ (which is equivalent to the above). Alternatively an equation system can be represented as a function $\system : (X \to \CZ) \to (X \to \CZ)$, $\system(\rho)[x] = e_x(\rho)$, taking a variable assignment $\rho$ and returning the result of each $e_x$'s evaluation in $\rho$. We use this second form as a convenience for when we are evaluating the entire equation system in the context of a variable assignment: we get a new value for each variable and thus a new variable assignment as a result. These two functional forms are equivalent and correspond to changing the argument order in the following way: $\system(\rho)[x] = \system[x](\rho)$. We use both forms as a convenience for when we only wish to use one argument. The representations are differentiated by context (as the arguments are of differing types) as well as by whether parentheses or brackets are used. The expressions in $E_\system$ are referred to as ``right hand sides'' of the equation system $\system$. We denote the set of all equation systems by $\Systems$. We define an inverse mapping, $\system^{-1} : E \to 2^X$, taking an expression, $e$, to the set of variables, $x$ for which it is a transitive subexpression of $\system[x]$. This is captured in the following definition: \begin{align*} \system^{-1}(e) &= \{ x \mid system(x) = e \} \cup \{ \system^{-1}(e_\parent) \mid e_\parent \in parents(e) \} \end{align*} \end{definition} \begin{definition} \textbf{Dependencies:} A variable or expression $x$ is said to \emph{depend on} $y$ if $x(\rho) \ne x(\varrho)$ and \begin{align*} \rho(z) &= \varrho(z) & \forall z \in X \backslash \{y\} \\ \rho(y) &\ne \varrho(y) \end{align*} If $x$ depends on $y$ then $y$ is said to \emph{influence} $x$. \end{definition} \section{Fixpoint Iteration} \subsection{Kleene Iteration} A simple approach to fixpoint iteration over monotonic equations is to simply iterate over the system repeatedly until a reevaluation results in no change to the values of any variables. This approach will always reach the least/greatest solution if there is one to be found, but it will often perform many more evaluations than are necessary. This algorithm is presented in Listing \ref{algo:kleene}. \begin{algorithm}[H] \begin{algorithmic} \Function {solvefixpoint} {$\system \in \Systems$} \State $k = 0$ \State $\rho_0 = \underline{\infty}$ \Repeat \State $\rho_{k+1} = \system( \rho_{k} )$ \State $k = k + 1$ \Until {$\rho_{k-1} = \rho_k$} \State \Return $\rho_k$ \EndFunction \end{algorithmic} \caption{The Kleene iteration algorithm for solving fixpoint equations for their greatest solutions.} \label{algo:kleene} \end{algorithm} For each iteration the entire system is evaluated, resulting in a considerable inefficiency in practice, requiring the repeated evaluation of many right-hand-sides for the same value. Thus an approach which evaluates smaller portions of the system in each iteration would be a significant improvement. An additional deficiency of Kleene iteration is that it is not guaranteed to terminate for all fixpoints. An example system is presented in Figure \ref{fig:kleene-infinite}, where our first iteration will give $\{ x \mapsto 0, y \mapsto 0 \}$, leading to a second iteration resulting in $\{ x \mapsto -1, y \mapsto 0\}$. The iteration will continue without bound, tending towards $\{ x \mapsto -\infty, y \mapsto 0 \}$ rather than the greatest fixpoint of $\{ x \mapsto -\infty, y \mapsto -\infty \}$. \begin{figure}[H] \begin{align*} x &= \min(0, x - 1) \\ y &= \min(0, \infty \oplus x) & \text{where } \infty \oplus -\infty &= -\infty \end{align*} \caption{An example equation system for which Kleene iteration will not terminate} \label{fig:kleene-infinite} \end{figure} It has been shown by Gawlitza et al.\cite{Gawlitza:2007:PFC:1762174.1762203} that Kleene iteration is at most restricted to $|X|$ iterations in the particular case of the $\max$-strategy iteration algorithm considered in this thesis. Kleene iteration is therefore restricted to $\Theta(|X|^3)$ right hand side evaluations at most in our application. We seek to improve on this performance in the next section. \subsection{W-DFS algorithm} \label{sec:wdfs} The W-DFS algorithm of Seidl et al.~takes into account dependencies between variables as it solves the system. By taking into account dependencies we can leave portions of the system unevaluated when we are certain that those values have not changed. The algorithm is presented in Listing \ref{algo:wdfs}. \begin{algorithm} \begin{algorithmic} \Globals \begin{tabularx}{0.9\textwidth}{rX} $D$ & $: X \to \CZ$, a mapping from variables to their current values \\ $\inflFP$ & $: X \to 2^X$, a mapping from a variable to the variables it \emph{may} influence \\ $\stableFP$ & $\subseteq X$, a set of ``stable'' variables \\ $\system$ & $\in \Systems$, an equation system \\ \end{tabularx} \EndGlobals \end{algorithmic} \begin{algorithmic} \Function {init} {$s \in \Systems$} \State $D = \underline{\infty}$ \State $\inflFP = \{x \mapsto \emptyset \mid \forall x \in X\}$ \State $\stableFP = \emptyset$ \State $\system = s$ \State \Return $\lambda x . (\solve(x); D[x])$ \EndFunction \label{algo:wdfs:init} \end{algorithmic} \begin{algorithmic} \Function {eval} {$x \in X$, $y \in X$} \Comment{Evaluate $y$, track $x$ depends on $y$} \State $\solve(y)$ \State $\inflFP[y] = \inflFP[y] \cup \{x\}$ \State \Return $D[y]$ \EndFunction \end{algorithmic} \begin{algorithmic} \Procedure {solve} {$x \in X$} \Comment{Solve a $x$ and place its value in $D$} \If {$x \not \in \stableFP$} \State $e_x = \system[x]$ \State $\stableFP = \stableFP \cup \{x\}$ \State $v = e_x( \lambda y . \eval(x, y) )$ \If {$v \ne D[x]$} \State $D = \{ x \mapsto v \} \oplus D$ \State $W = \inflFP[x]$ \State $\inflFP[x] = \emptyset$ \State $\stableFP = \stableFP \backslash W$ \For {$v \in W$} \State $\solve(v)$ \EndFor \EndIf \EndIf \EndProcedure \end{algorithmic} \caption{The W-DFS mentioned in \cite{DBLP:tr/trier/MI96-11} and presented in \cite{fixpoint-slides}, modified to find greatest-fixpoints of monotonic fixpoint equations} \label{algo:wdfs} \end{algorithm} The function $\init: \Systems \to (X \to D)$ in Listing \ref{algo:wdfs:init} takes an equation system as its argument and returns a function to query the greatest fixpoint. Each fixpoint value is solved on demand and will only evaluate the subset of the equation system which is required to ensure the correct solution is found. The algorithm performs no evaluation of any variables unless their value is requested, whether externally or internally. Once a value is requested the right-hand-side is evaluated, which may in turn request the values of other variables through the $\eval$ function. In the case of mutually-recursive variables this would result in infinite recursion, as each time the variable's value is calculated we first calculate the variable's value. In order to prevent this a $\stable$ set is maintained of variables whose values are either currently being computed or whose values have already been computed. This set provides a ``base'' for the recursion to terminate, at which point the value is simply looked up in $D$. Whenever a variable's value changes it will ``destabilise'' and re-evaluate any variables which \emph{may} depend on it for their values, as they may change their value if re-evaluated (and hence are not certainly stable). If these values change then they, too, will destabilise their dependent variables, and so on. The W-DFS algorithm provides us with a \emph{local}, \emph{demand-driven} solver for greatest fixpoints of monotonic fixpoint problems. \begin{figure}[H] \begin{tikzpicture} \node[draw] (full) at (0,-2) { \begin{minipage}{0.3\textwidth} \begin{align*} x &= \min(0, y) \\ y &= x \\ a &= \min(0, b) \\ b &= c \\ c &= d \\ d &= e \\ e &= a \end{align*} \end{minipage} }; \node[draw] (first) at (10, 1) { \begin{minipage}{0.3\textwidth} \begin{align*} x &= \min(0, y) \\ y &= x \end{align*} \end{minipage} }; \node[draw] (second) at (10,-4) { \begin{minipage}{0.3\textwidth} \begin{align*} a &= \min(0, b) \\ b &= c \\ c &= d \\ d &= e \\ e &= a \end{align*} \end{minipage} }; \draw (full) -- (first.west); \draw (full) -- (second.west); \end{tikzpicture} \caption{Example of an equation system which can be separated into two independent sub-systems} \label{fig:wdfs-example} \end{figure} Because each value is only computed when requested, the solver avoids calculating results which are irrelevant to the final outcome. To provide a more concrete example, consider the equation system presented in Figure \ref{fig:wdfs-example}. This equation system can be separated into two independent equation systems, one consisting of $\{x, y\}$ and the other of $\{a, b, c, d, e\}$. In order to find the value of a variable in either of these subsystems it is necessary to evaluate the entire subsystem, but it is not necessary to evaluate the other subsystem. To compute the value of $x$ in the greatest fixpoint it is unnecessary to compute the value of the variable $d$, but it is necessary to compute the value of $y$. The W-DFS algorithm, will only evaluate the necessary subsystem for a requested variable, leaving the other system untouched. In order to capture this property we will define the notion of a \emph{local} solver. \begin{definition} \textbf{Local:} A solver is said be \emph{local} if, for some equation system, $\system$, and some $e \in E_\system$, it will solve for $e_x$ without evaluating every element of $E_\system$. \end{definition} W-DFS provides us with a \emph{local}, \emph{demand-driven} solver for greatest-fixpoint problems. \section{$\max$-strategy Iteration} The problem of $\max$-strategy iteration is trying to find a mapping $\sigma: E_{\max} \to E$ from each $\max$-expression to its greatest subexpression. We will use $\sigma: \Systems \to \Systems$ as a shorthand to denote the system obtained by replacing each $\max$-expression, $x$, in $\system$ with $\sigma(x)$. Similarly $\sigma_{\all}: E \to E$ will denote the expression obtained by replacing each $\max$-subexpression in $e$ with $\sigma(e)$. In the particular case of \emph{monotonic}, \emph{expansive} operators it has been shown\cite{Gawlitza:2007:PFC:1762174.1762203} that this process will take a finite number of iterations if we ensure at each iteration that we make a \emph{strict} improvement on our strategy. This means that each time we improve our strategy we make it greater in at least one $\max$-expression, and no worse in the others. That is, we must satisfy $\sigma_{n+1}(\rho) > \sigma_n(\rho)$, for the $\rho$ in which we are improving. A new function, $\improve: ((E_{\max} \to E) \times (X \to D)) \to (E_{\max} \to E)$, is used below as a \emph{strategy improvement operator}. $\improve$ takes a $\max$-strategy and a variable assignment and returns a new $\max$-strategy which constitutes an `improvement' of the strategy in the variable assignment $\rho$. If have a $\max$-strategy $\sigma$, a variable assignment $\rho$ and $\varsigma = \improve(\sigma, \rho)$. Then \begin{align*} \sigma(\system)(\rho) \le \varsigma(\system)(\rho) \end{align*} If there is a possible improvement to be made on $\sigma$ then $\improve$ will return an improvement, otherwise it will return $\sigma$ unchanged. One possible implementation of $\improve$ is presented in Listing \ref{algo:pmax}. This implementation will try every option of subexpressions for this $\max$-expression and will then return the best. The use of an anonymous function as the returned strategy is so the evaluation of each strategy is only done when requested. \begin{algorithm} \begin{algorithmic} \Function {improve} {$\sigma: E_{\max} \to E$, $\rho: X \to D$} \ReturnLambdaFn {$e \in E_{\max}$} \State $e_{\best} = \sigma(e)$ \State $\val_{\best} = \sigma_{\all}(e_{\old})(\rho)$ \For {$e_i \in e$} \If {$\sigma_{\all}(e_i)(\rho) > \val_{\best}$} \State $e_{\best} = e_i$ \State $\val_{\best} = \sigma(e_i)(\rho)$ \EndIf \EndFor \State \Return $e_{\best}$ \EndReturnLambdaFn \EndFunction \end{algorithmic} \caption{The variation of $\improve$ used in our algorithm} \label{algo:pmax} \end{algorithm} \subsection{Naive approach} The problem of $\max$-strategy iteration is one of constantly improving until no further improvement can be made, so a simple approach is simply to perform a straightforward loop, improving the strategy at each step, until an equilibrium point is reached. This is the approach presented in Listing \ref{algo:naive-strategy}. \begin{algorithm}[H] \begin{algorithmic} \Assumptions \begin{tabularx}{0.9\textwidth}{rX} $\sigma$ & $: E_{\max} \to E$, a $\max$ strategy \\ $\system$ & $\in \Systems$, an equation system \\ $\rho$ & $: X \to D$, a variable assignment \\ $\improve$ & $: ((E_{\max} \to E) \times (X \to \CZ)) \to (E_{\max} \to E)$, a $\max$-strategy improvement operator \\ \end{tabularx} \EndAssumptions \Function {solvestrategy} {$\system \in \Systems$} \State $k = 0$ \State $\sigma_0 = \{ x \mapsto -\infty \mid \forall x \in X \}$ \State $\rho_0 = \underline{-\infty}$ \Repeat \State $\sigma_{k+1} = \improve(\sigma_k, \rho_k)$ \State $\rho_{k+1} = \solve \fixpoint(\sigma_{k+1}(\system))$ \State $k = k + 1$ \Until {$\sigma_{k-1} = \sigma_k$} \State \Return $\sigma_k$ \EndFunction \end{algorithmic} \caption{The naive approach to strategy iteration} \label{algo:naive-strategy} \end{algorithm} This approach is quite similar to that of Kleene iteration, and from the results of Gawlitza et al.\cite{Gawlitza:2007:PFC:1762174.1762203} it is known that this iteration is guaranteed to terminate. This naive approach is inefficient, however, in the same way as Kleene iteration. A large amount of time will be spent attempting to improve portions of the strategy for which no improvement can be made. \subsection{Adapted W-DFS algorithm} \label{sec:adapted-wdfs} The $\max$-strategy iteration can be viewed as an accelerated fixpoint problem. We are attempting to find a strategy, $\sigma: E_{\max} \to E$ that will result in the greatest value for each $e \in E_{\max}$. Therefore if we consider our ``variables'' to be $\max$-expressions and our ``values'' to be their subexpressions then we can solve for the best $\max$-strategy using a similar approach to the W-DFS algorithm presented above in Section \ref{sec:wdfs}. Listing \ref{algo:adapted-wdfs} presents one variation on W-DFS to allow it to solve $\max$-strategy iteration problems. \begin{algorithm}[H] \begin{algorithmic} \Globals \begin{tabularx}{0.9\textwidth}{rX} $\sigma$ & $: (E_{\max} \to E)$, a mapping from $\max$-expressions to a subexpression \\ $\inflSI$ & $: (E_{\max} \to 2^{E_{\max}}$, a mapping from a $\max$-expression to the subexpressions it influences \\ $\stableSI$ & $\subseteq E_{\max}$, the set of all $\max$-expressions whose strategies are stable \\ $\rho$ & $: X \to \CZ$, a variable assignment \\ $\system$ & $\in \Systems$, an equation system \\ $\improve$ & $: ((E_{\max} \to E) \times (X \to \CZ)) \to (E_{\max} \to E)$, a $\max$-strategy improvement operator \\ \end{tabularx} \EndGlobals \end{algorithmic} \begin{algorithmic} \Function {init} {$s \in \Systems$} \State $\sigma = \underline{-\infty}$ \State $\inflSI = \{x \mapsto \emptyset \mid \forall x \in E_{\max}\}$ \State $\stableSI = \emptyset$ \State $\system = s$ \State \Return $\lambda x . (\solve(x); \sigma[x])$ \EndFunction \label{algo:adapted-wdfs:init} \end{algorithmic} \begin{algorithmic} \Function {eval} {$x \in E_{\max}$, $y \in E_{\max}$} \State $\solve(y)$ \State $\inflSI[y] = \inflSI[y] \cup \{x\}$ \State \Return $\sigma[y]$ \EndFunction \end{algorithmic} \begin{algorithmic} \Procedure {solve} {$x \in E_{\max}$} \If {$x \not \in \stableSI$} \State $\stableSI = \stableSI \cup \{x\}$ \State $v = \improve(\lambda y . \eval(x, y), \rho)[x]$ \If {$v \ne \sigma[x]$} \State $\sigma = \{ x \mapsto v\} \oplus \sigma$ \State $W = \inflSI[x]$ \State $\inflSI[x] = \emptyset$ \State $\stableSI = \stableSI \backslash W$ \For {$v \in W$} \State $\solve(v)$ \EndFor \EndIf \State $\rho_{\last} = \rho$ \State $\rho = \solve \fixpoint(\sigma(\system))$ \For {$y \in X$} \If {$\rho_{\last}[y] = \rho[y]$} \State $\stableSI = \stableSI \backslash \{\system[y]\}$ \State $\solve(\system[y])$ \EndIf \EndFor \EndIf \EndProcedure \end{algorithmic} \caption{W-DFS, this time modified to find the best $\max$-strategy.} \label{algo:adapted-wdfs} \end{algorithm} This approach retains the benefits of the W-DFS algorithm for solving fixpoints, but applies them to $\max$-strategy iteration problems. Dependencies between $\max$-expressions are considered and an improvement on the strategy at an expression $e$ is only attempted if some of the $\max$-expressions which influence $e$ themselves improve strategy. We also destabilise $\max$-expressions which have their values changed in the fixpoint iteration step. For this particular algorithm to work, however, we must assume another property of $\improve$. In Listing \ref{algo:adapted-wdfs} we take the value of $\improve(\lambda y . \eval(x, y), \rho)[x]$; we improve the strategy, then use only the strategy for $x$. In order for this algorithm to be correct $\improve$ must always improve the strategy at $x$ if it is possible to do so. If $\improve$ did not improve the strategy at $x$, while a permissible improvement existed, then we would consider $x$ to be ``stable'' and would not attempt to improve our strategy at $x$ until the improvement of another portion of our strategy invalidated $x$. It is not guaranteed that another strategy improvement will invalidate the strategy at $x$, however, so the strategy at $x$ may still be able to be improved when the algorithm terminates. If the strategy at $x$ can still be improved then we have not reached the solution of our $\max$-strategy iteration. The particular $\improve$ that we are using (see Listing \ref{algo:pmax}) satisfies this assumption, as it attempts to improve the strategy at the point of individual $\max$-expressions. Additionally, in order to remain efficient, $\improve$ should not attempt to improve the strategy for any $\max$-expressions until that expression is requested. Whether or not $\improve$ is lazy in this way will not affect the correctness of the algorithm, as it will only result in the $\max$-strategy improvement at each step being more expensive. The extra work to improve other portions of the strategy will be discarded with only $\improve(...)[x]$ being used. The particular $\improve$ that we are using (see Listing \ref{algo:pmax}) satisfies this property using an anonymous function to delay the computation. \section{Local Demand-driven Strategy Improvement (LDSI)} W-DFS speeds up both the $\max$-strategy iteration and the fixpoint iteration as two independent processes, but each time we seek to improve our strategy we compute at least a subset of the greatest fixpoint from a base of no information. We would like to be able to provide some channel for communication between the two W-DFS instances to reduce the amount of work that must be done at each stage. By supplying each other with information about which parts of the system have changed we can jump in partway through the process of finding a fixpoint, thereby avoiding repeating work that we have already done when it is unnecessary to do so. The new \emph{Local Demand-driven Strategy Improvement} algorithm, \emph{LDSI}, seeks to do this. By adding an ``invalidate'' procedure to the fixpoint iteration we provide a method for the $\max$-strategy to invalidate fixpoint variables, and we can modify the fixpoint iteration algorithm to report which variables it has modified with each fixpoint iteration step. We then have a $\max$-strategy iteration which, at each strategy-improvement step, invalidates a portion of the current fixpoint iteration which may depend on the changed strategy. The fixpoint iteration then re-stabilises each variable as it is requested, tracking which values have changed since the last time they were stabilised. The $\max$-strategy iteration then uses this list of changed variables to determine which portions of the current strategy must be re-stabilised once more. This process continues until the $\max$-strategy stabilises (at which point the fixpoint will also stabilise). Both portions of the LDSI algorithm are \emph{demand driven}, and so any necessary evaluation of strategies or fixpoint values is delayed until the point when it is required. This means that the solver will be \emph{local} (see Section \ref{sec:wdfs}). This algorithm is presented in three parts. \begin{itemize} \item Section \ref{sec:ldsi:top-level} presents the global state and entry point to the algorithm. \item Section \ref{sec:ldsi:fixpoint} presents the fixpoint portion of the algorithm. This portion bears many similarities to the W-DFS algorithm presented in Section \ref{sec:wdfs}, with a few modifications to track changes and allow external invalidations of parts of the solution. \item Section \ref{sec:ldsi:max-strategy} presents the $\max$-strategy portion of the algorithm. This portion is quite similar to the Adapted W-DFS algorithm presented in Section \ref{sec:adapted-wdfs}, with some modifications to allow for communication with the fixpoint portion of the algorithm. \item Section \ref{sec:ldsi:correctness} argues the correctness of this approach for finding the least fixpoints of monotonic, expansive equation systems involving $\max$-expressions. \end{itemize} \subsection{Top level} \label{sec:ldsi:top-level} \begin{algorithm}[H] \begin{algorithmic} \Globals \begin{tabularx}{0.9\textwidth}{rX} $D$ & $: X \to \CZ$, a mapping from variables to their current value \\ $D_\old$ & $: X \to \CZ$, a mapping from variables to their last stable value \\ $\inflFP$ & $: X \to 2^X$, a mapping from a variable to the variables it \emph{may} influence \\ $\stableFP$ & $\subseteq X$, a set of ``stable'' variables \\ $\touchedFP$ & $\subseteq X$, a set of variables which have been ``touched'' by the fixpoint iteration, but not yet acknowledged by the $\max$-strategy iteration \\ \\ $\sigma$ & $: E_{\max} \to E$, a mapping from $\max$-expressions to a subexpression \\ $\inflSI$ & $: E_{\max} \to 2^{E_{\max}}$, a mapping from a $\max$-expression to the subexpressions it influences \\ $\stableSI$ & $\subseteq E_{\max}$, the set of all $\max$-expressions whose strategies are stable \\ $\improve$ & $: ((E_{\max} \to E) \times (X \to \CZ)) \to (E_{\max} \to E)$, a $\max$-strategy improvement operator \\ \\ $\system$ & $\in \Systems$, an equation system \\ \end{tabularx} \EndGlobals \end{algorithmic} \caption{Global variables used by the LDSI algorithm} \label{algo:ldsi:globals} \end{algorithm} These variables are mostly just a combination of the variables found in Sections \ref{sec:wdfs} and \ref{sec:adapted-wdfs}. The only exception is $\touchedFP$, which is a newly introduced variable to track variables which are touched by the fixpoint iteration steps. \begin{algorithm}[H] \begin{algorithmic} \Function {init} {$s \in \Systems$} \State $D = \underline{-\infty}$ \State $D_\old = \underline{-\infty}$ \State $\inflFP = \{x \mapsto \emptyset \mid \forall x \in X\}$ \State $\stableFP = X$ \State $\touchedFP = \emptyset$ \State $\sigma = \underline{-\infty}$ \State $\inflSI = \{x \mapsto \emptyset \mid \forall x \in E_{\max}\}$ \State $\stableSI = \emptyset$ \State $\system = s$ \State \Return $\lambda x . (\solve \strategy (x); \solve \fixpoint (x) ; D[x])$ \EndFunction \end{algorithmic} \caption{LSDI init function and entry point} \label{algo:ldsi:init} \end{algorithm} The $\init$ function is, similarly, just a combination of the $\init$ functions presented in Sections \ref{sec:wdfs} and \ref{sec:adapted-wdfs}. The $\touchedFP$ and $D_\old$ variables are also initialised, as they was not present in either of the previous $\init$ functions. $D$ and $D_\old$ are both initialised to \underline{$-\infty$} and $\stableFP$ is initially the entire set of variables. The values of $D$, $D_\old$ and $\stableFP$ are set to their result after a complete fixpoint iteration over each variable with the initial $\max$-strategy. The type of this function is different to either of the original $\init$ functions. $\init : \Systems \to (X \to \CZ)$. The function returned by $\init$ performs the entire process of $\max$-strategy iteration and fixpoint iteration and calculates the final value of each variable for the least fixpoint, returning a value in $\CZ$. \subsection{Fixpoint iteration} \label{sec:ldsi:fixpoint} The process of fixpoint iteration is similar to the fixpoint iteration algorithm presented in Section \ref{sec:wdfs}. The only difference is that we have an $\invalidate \fixpoint$ procedure for the $\max$-strategy iteration to invalidate a portion of the system. By invalidating only a portion of the system we reuse the values which have already been computed to avoid performing additional work. The last stable value is stored in this procedure to identify \emph{changed} values, rather than only \emph{unstable} ones. \begin{algorithm}[H] \begin{algorithmic} \Function {evalfixpoint} {$x \in X$, $y \in X$} \State $\solve \fixpoint(y)$ \State $\inflFP[y] = \inflFP[y] \cup \{x\}$ \State \Return $D[y]$ \EndFunction \end{algorithmic} \caption{Utility function used to track fixpoint variable dependencies. This function is very similar to the $\eval$ function presented in Listing \ref{algo:wdfs}} \label{algo:ldsi:fixpoint:eval} \end{algorithm} This procedure is similar to the equivalent method in the W-DFS algorithm, except for the fact that $\solve$ has been renamed to $\solve \fixpoint$. $\eval \fixpoint$ performs the same function as the $\eval$ function in Listing \ref{algo:wdfs}. \begin{algorithm}[H] \begin{algorithmic} \Procedure {invalidatefixpoint} {$x \in X$} \Comment{Invalidate a fixpoint variable} \If {$x \in \stableFP$} \State $\stableFP = \stableFP \backslash \{x\}$ \State $\D_\old[x] = D[x]$ \State $D[x] = \infty$ \State $W = \inflFP[x]$ \State $\inflFP[x] = \emptyset$ \State $\touchedFP = \touchedFP \cup \{ x \}$ \For {$v \in W$} \State $\invalidate \fixpoint(v)$ \EndFor \EndIf \EndProcedure \end{algorithmic} \caption{Fixpoint subroutine called from the $\max$-strategy iteration portion to invalidate fixpoint variables} \label{algo:ldsi:fixpoint:invalidate} \end{algorithm} This procedure is not called in the fixpoint iteration process, but is rather the method by which the $\max$-strategy iteration can communicate with the fixpoint-iteration. A call to $\invalidate \fixpoint$ indicates to the fixpoint-iteration that a variable's value \emph{may} have changed as a result of a $\max$-strategy improvement, and that it will require re-calculation. The invalidation provided by this procedure permits us to only re-evaluate a partial system (the solving of which is delayed until requested by the $\solve \fixpoint$ procedure). While invalidating variables we store their $\old$ value, and mark them as ``touched''. As the $\old$ value is only set in this procedure, and only when $x$ is stable, it gives us the ability to see whether a variable has stabilised to a new value, or whether it has merely re-stabilised to the same value as last time. This refines the set of changed variables for the $\max$-strategy iteration phase. \begin{algorithm}[H] \begin{algorithmic} \Procedure {solvefixpoint} {$x \in X$} \If {$x \not \in \stableFP$} \State $\stableFP = \stableFP \cup \{ x \}$ \State $v = \sigma(\system[x])( \lambda y . \eval \fixpoint(x, y) )$ \If {$v \ne D[x]$} \State $D = \{ x \mapsto v \} \oplus D$ \State $W = \inflFP[x]$ \State $\inflFP[x] = \emptyset$ \State $\stableFP = \stableFP \backslash W$ \For {$v \in W$} \State $\solve \fixpoint(v)$ \EndFor \EndIf \EndIf \EndProcedure \end{algorithmic} \caption{The subroutine of the fixpoint iteration responsible for solving for each variable. This is very similar to the $\solve$ procedure in Listing \ref{algo:wdfs}} \label{algo:ldsi:fixpoint:solve} \end{algorithm} The $\solve \fixpoint$ procedure is similar to the $\solve$ procedure presented in Listing \ref{algo:wdfs}. There are two main differences: the self-recursive call has been renamed to reflect the change in the function's name, and instead of using $\system[x]$ we must use $\sigma(\system[x])$. We use $\sigma$ to provide the fixpoint-iteration with the current $\max$-strategy. $\sigma$ is stable for the duration of the $\solve \fixpoint$ execution, so it will result in fixpoint iteration being performed over a stable system. After an evaluation of the $\solve \fixpoint$ procedure, the variable $x$ will have its value in the current $\max$-strategy's greatest fixpoint stabilised, and that value will be stored in $D[x]$. Stabilising this may result in other related variables also being stabilised. \subsection{$\max$-strategy iteration} \label{sec:ldsi:max-strategy} \begin{algorithm}[H] \begin{algorithmic} \Function {evalstrategy} {$x \in E_{\max}$, $y \in E_{\max}$} \State $\solve \strategy(y)$ \State $\inflSI[y] = \inflSI[y] \cup \{x\}$ \State \Return $\sigma[y]$ \EndFunction \Function {evalstrategyfixpoint} {$x \in E_{\max}$, $y \in X$} \State $\solve \fixpoint(y)$ \State $\inflSI[y] = \inflSI[\system[y]] \cup \{ x \}$ \For {$v \in \touchedFP$} \If {$v \in \stableFP$ and $D[x] \ne D_\old[x]$} \For {$w \in \inflSI[\strategy(\system[v])]$} \State $\invalidate \strategy(\system[v])$ \EndFor \State $\touchedFP = \touchedFP \backslash \{v\}$ \EndIf \EndFor \State \Return $D[y]$ \EndFunction \end{algorithmic} \label{algo:ldsi:strategy:eval} \caption{Functions which evaluate a portion of the $\max$-strategy/fixpoint and note a dependency} \end{algorithm} The $\eval \strategy$ function is essentially the same as the $\eval$ function in Listing \ref{algo:adapted-wdfs}, except that it calls the $\solve \strategy$ procedure. The $\solve \fixpoint$ calls in $\eval \strategy \fixpoint$ are top-level fixpoint-iteration calls, meaning that upon their return we know that $D$ contains the value it would take in the greatest fixpoint of the current strategy $\sigma$. This function, therefore, acts as a simple intermediate layer between the fixpoint-iteration and the $\max$-strategy iteration to allow for dependencies to be tracked. Upon the conclusion of the $\solve \fixpoint$ we also must inspect the $\touchedFP$ set to determine which variables have values which may have changed. If their values have stabilised since being invalidated, and if they stabilised to a different value to their previous stabilisation, then we will invalidate all strategies which depend on them. We do not have to invalidate the variable's right hand side directly, but if it is dependent on its own value (as in $\{ x = x + 1 \}$, for example), then it will be destabilised by the transitivity of $\invalidate \strategy$. \begin{algorithm}[H] \begin{algorithmic} \Procedure {invalidatestrategy} {$x \in E_{\max}$} \If {$x \in \stableSI$} \State $\stableSI = \stableSI \backslash \{x\}$ \For {$v \in \inflSI$} \State $\invalidate \strategy (v)$ \EndFor \EndIf \EndProcedure \end{algorithmic} \label{algo:ldsi:strategy:invalidate} \caption{Evaluate a portion of the $\max$-strategy and note a dependency} \end{algorithm} Invalidating the $\max$-strategy iteration is essentially the same as the invalidation in the fixpoint-iteration stage, except that we do not need to keep track of the last stable values, nor which $\max$-expressions we have invalidated. All we need to do is remove them from the $\stableSI$ set, thereby indicating that they need to be re-stabilised by a $\solve \strategy$ call. All $\max$-expressions which (transitively) depend on $x$ are themselves destabilised as well. \begin{algorithm}[H] \begin{algorithmic} \Procedure {solvestrategy} {$x \in E_{\max}$} \If {$x \not \in \stableSI$} \State $\stableSI = \stableSI_{\max} \cup \{x\}$ \State $e = \improve(\lambda y . \eval \strategy(x,y), \lambda z . \eval \strategy \fixpoint(x, z))[x]$ \If {$e \ne \sigma[x]$} \State $\sigma = \{ x \mapsto e \} \oplus \sigma$ \For {$v \in \system^{-1}(x)$} \State $\invalidate \fixpoint(\system^{-1}(x))$ \EndFor \State $\stableSI = \stableSI \backslash I[x]$ \For {$v \in \inflSI[x]$} \State $\solve \strategy(v)$ \EndFor \EndIf \EndIf \EndProcedure \end{algorithmic} \caption{The $\max$-strategy portion of the Combined W-DFS.} \label{algo:ldsi:solve} \end{algorithm} $\solve \strategy$ is very similar to the other $\solve$ calls we have already seen. The procedure attempts a strategy improvement for the $\max$-expression $x$ and, if any improvement is possible, it improves the strategy $\sigma$ before invalidating and re-evaluating any other $\max$-expressions which may depend on $x$. We here assume that $\improve$ has the same properties as those which are assumed in Section \ref{sec:adapted-wdfs}. \subsection{Correctness} \label{sec:ldsi:correctness} To argue the correctness of our LDSI algorithm we will first argue the correctness of the fixpoint iteration portion of the algorithm. We will then argue the correctness of the $\max$-strategy iteration portion, including the communication between the fixpoint iteration and $\max$-strategy iteration. The fixpoint iteration portion of the algorithm is concerned with finding the greatest fixpoint, $\solnFP(s) : X \to D$ for the system $s = \sigma(\system)$. If we consider all valid fixpoints, $\fix(s) = \{ \rho \mid \rho = s(\rho) \}$, then we find that $\solnFP(s) \in \fix(s)$ and $\solnFP(s) \ge \rho ~ \forall \rho \in \fix(s)$. Our general approach will be to consider our variable assignment $D \ge \solnFP(s)$ and show that it decreases until $D \in \fix(s)$ and thus $D = \solnFP(s)$. We begin by stating three invariants of the LDSI algorithm. We use $\depFP^{+}(x)$ to be the set of all variables which $x$ may depend on. $\depFP^{+}$ is defined as the transitive closure of $\depFP$, $\depFP[x] = \{ y \in X \mid x \in \inflFP[y] \}$. $\RFP \subseteq X$ is defined as the variables currently on our recursion stack. \begin{align} D &\ge \solnFP(s) \label{eqn:fp-inv1} \\ % x &\in \inflFP[y] & \forall x \in \stableFP, y \in \depFP[x] \label{eqn:fp-inv3} \\ % D &= \solnFP(s)[x] & \mbox{if } x \not \in \RFP, \depFP^{+}[x] \cap \RFP = \emptyset, x \in \stableFP \label{eqn:fp-inv2} \end{align} (\ref{eqn:fp-inv1}) follows from monotonicity. We begin our iteration at some variable assignment $D \ge \solnFP(s)$ and monotonically descend from there. We know that $s(D)[x] \ge soln(s)[x] ~\forall D \ge \solnFP(s), x \in X$, thus each evaluation of $s[x]$ in the context of $D$ will result in a new value for $D[x]$ which is either closer to or equal to $\solnFP(s)[x]$. As we only ever evaluate in the context of variables taken from $D$, and $D[x]$ is set to $\infty$ when $x$ is invalidated, we know that $D \ge \solnFP(s)$ holds. (\ref{eqn:fp-inv3}) follows from the use of the $\eval \fixpoint$ function. Each variable lookup results in a direct insertion into $\inflFP$, so during the evaluation of $x$ all relevant $\inflFP$ sets are updated to reflect the current dependencies. For (\ref{eqn:fp-inv2}) we will examine what has happened when each of the conditions is false: \begin{itemize} \item If $x \in \RFP$ then we are currently in the context of an earlier evaluation of $x$, so the value of $D[x]$ will be changed again by that earlier call. In addition, we conclude that $x \in \depFP^{+}[x]$ and thus, from the following condition, that $x$ will be re-evaluated at some point after this one. \item If $\depFP^{+}[x] \cap \RFP \ne \emptyset$ then a variable which $x$ depends on is in $\RFP$ at the moment. This will result in the re-evaluation of $x$ if the value of any of the variables in $\depFP^{+}[x] \cap \RFP$ have changed. \item If $x \not \in \stableFP$ then a re-evaluation of $x$ will occur with the next call to $\solve \fixpoint(x)$. This may result in a change to the value of $D[x]$, or it may leave $D[x]$ stable for the moment. If we denote the variable assignment before the evaluation of $x$ by $D_{\pre}$, and the variable assignment after the evaluation of $x$ by $D_{\post}$ then we find that $D_{\post} \le D_{\pre}$, due to the monotonicity of $s$. After each change of $D[x]$, everything which \emph{may} have depended on $D[x]$ is re-evaluated (see the previous two items), leading to a final point of stability when $D[x] = \solnFP(s)[x]$, by the definition of $\solnFP(s)[x]$ and (\ref{eqn:fp-inv1}). \end{itemize} After an evaluation of $\solve \fixpoint(x)$ we know that $x \in \stableFP$. If it is also the case that $\RFP = \emptyset$, as is the case for top-level calls to $\solve \fixpoint$, then we know that $D[x] = \solnFP(s)[x]$. This means that the function $\lambda x . (\solve \fixpoint(x); D[x])$ will act as a variable assignment solving for the greatest fixpoint of $s = \sigma(\system)$, as is required by our $\max$-strategy iteration. As the $\max$-strategy iteration changes $\sigma$ it also induces a change in $s = \sigma(\system)$, so in order to maintain the correctness of this algorithm we must show that our above invariants are maintained by this process. When the $\max$-strategy iteration changes the current $\max$-strategy $\sigma$ at the variable $x$ it changes the equation system $s = \sigma(\system)$ that the fixpoint iteration uses. The $\max$-strategy iteration then invalidates the affected portion of the fixpoint iteration by doing two things: it removes the variable $x$ from the $\stableFP$ set, and it sets $D[x] = \infty$. The invalidation of variables is then propagated to each variable which transitively depends on $x$: $\{ y \in X \mid x \in \depFP^{+}[y] \} = \{ y \in X \mid y \in \inflFP^{+}[x] \}$, where $\inflFP^{+}$ is the transitive closure of $\inflFP$. We know from (\ref{eqn:fp-inv3}) that this transitive closure of $\inflFP$ will identify the entire subsystem which may depend on the value of $x$. The invalidation of transitive dependencies ensures that (\ref{eqn:fp-inv1}) holds for the changed subsystem, as $\infty \ge z ~ \forall z \in \CZ$. From (\ref{eqn:fp-inv1}) we can then conclude that (\ref{eqn:fp-inv2}) holds as well, as the removal of $x$ from $\stableFP$ combined with (\ref{eqn:fp-inv1}) leads to (\ref{eqn:fp-inv2}). These invariants now hold for the affected sub-system and are still true for the unaffected sub-system. Thus our invariants hold for our entire system and our fixpoint iteration will continue to be correct in the presence of invalidation by the $\max$-strategy iteration. We move now to the $\max$-strategy iteration. We will use $\rho: X \to \CZ$ as $\rho = \lambda x . (\solve \fixpoint(x); D[x])$, a variable assignment which will always calculate the greatest fixpoint of $\sigma(\system)$ for the current strategy $\sigma$. Each time $\rho$ is queried for a variable's value it will also record which variables have had their values changed, whether or not those changed values are final, in the set $\touchedFP$. We can establish similar invariants for the $\max$-strategy iteration as we had for the fixpoint iteration. We denote the optimal strategy by $\solnSI(\system)$ and we define $\RSI \subseteq E_{\max}$ to be the set of $\max$-expressions on our recursion stack. \begin{align} e &\in \inflSI[y] & \forall e \in \stableSI, y \in \depSI[e] \label{eqn:si-inv1} \\ % \sigma &= \solnSI(\system)[e] & \mbox{if } e \not \in \RSI, \depSI^{+}[e] \cap \RSI = \emptyset, e \in \stableSI \label{eqn:si-inv2} \end{align} (\ref{eqn:si-inv1}) follows from the same logic as (\ref{eqn:fp-inv3}) for the fixpoint iteration. The $\eval \strategy$ directly updates the $\inflSI$ sets whenever they are used. This ensures our dependency information is correct. (\ref{eqn:si-inv2}) follows a similar argument to (\ref{eqn:fp-inv2}) for the fixpoint iteration. At each step we must make a strategy improvement, such that $\sigma_{n+1}(\system)(\rho) > \sigma_n(\system)(\rho)$. Every step must be an improvement and, by the assumptions made in Section \ref{sec:adapted-wdfs}, we know that we will make an improvement for each individual variable if it is possible to do so. The invalidation for a changed strategy is identical to that which is performed for variables in the fixpoint iteration, so it will not be re-considered here. An additional complexity is that $\rho$ is not a constant mapping during $\max$-strategy iteration. In order to maintain (\ref{eqn:si-inv2}) we ensure that on changes in $\rho$ we invalidate some of our $\max$-expressions. The set $\touchedFP$ over-approximates the set of changed variables, so we are certain that all changes will be found by enumerating $\touchedFP$. We only invalidate those $\max$-expressions which are the right-hand side of a stable, changed variable. If a variable has not stabilised then it's value is has not been used directly (see (\ref{eqn:fp-inv2})) and thus cannot directly influence the $\max$-strategy. If the variable has stabilised to the same value as it had previously then it has not changed and thus has no effect on the $\max$-strategy. If both the $\max$-strategy iteration and the fixpoint iteration correctly solve their respective portions of the problem, and the communication between them is correct, then we know that the overall algorithm is correct. \subsection{Implementation} \label{sec:ldsi:implementation}