\documentclass[11pt,twoside]{scrartcl}

%opening
\newcommand{\lecid}{15-414}
\newcommand{\leccourse}{Bug Catching: Automated Program Verification}
\newcommand{\lecdate}{} %e.g. {October 21, 2013}
\newcommand{\lecnum}{7}
\newcommand{\lectitle}{Programs with Arrays}
\newcommand{\lecturer}{Matt Fredrikson}

\usepackage{lecnotes}

\usepackage[irlabel]{bugcatch}
\usepackage{xcolor}
\usepackage{listings}

\definecolor{mygray}{rgb}{0.5,0.5,0.5}
\definecolor{backgray}{gray}{0.95}
\lstdefinestyle{customjava}{
  belowcaptionskip=1\baselineskip,
  breaklines=true,
  language=Java,
  showstringspaces=false,
  numbers=left,
  xleftmargin=2em,
  framexleftmargin=1.5em,
  numbersep=5pt,
  numberstyle=\tiny\color{mygray},
  basicstyle=\footnotesize\ttfamily,
  keywordstyle=\color{blue},
  commentstyle=\itshape\color{purple!40!black},
  tabsize=2,
  backgroundcolor=\color{backgray},
  escapechar=\%
}


\begin{document}

\maketitle
\thispagestyle{empty}

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

\section{Introduction}

The previous lecture focused on loops, starting with axioms and leading to a derived rule that allows us to simplify reasoning about loops to reasoning about the behavior of a single iteration of their bodies. We worked an example involving a program that uses loops to compute the square of a number, and found that much of the difficulty in reasoning about loops lies in finding a suitable invariant.

Loops are frequently used to compute over a sort of programming element that we haven't introduced yet, namely arrays. Arrays are an important data structure in imperative programming, enabling us to implement things that we couldn't otherwise. However, they introduce significant complexity into programs' behavior, so sound reasoning about their use is crucial. Today we will introduce arrays into our language, and use our semantics to argue the correctness of the binary search code we discussed in the first lecture. Along the way, we will build more experience in finding suitable loop invariants for increasingly complex code, and in all likelihood, learn to appreciate the benefits of using an SMT solver to complete our proofs.


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

\section{Recall: Loop Invariants}

Last lecture we derived the loop rule using the dynamic logic sequent calculus. We started from the unwind and unfold axioms,

\begin{calculus}
\cinferenceRule[whileiterateb|$\dibox{\text{unwind}}$]{unfold while loop}
{\linferenceRule[equiv]
  {\dbox{\pifs{\ivr}{\plgroup\ausprg;\pwhile{\ivr}{\ausprg}\prgroup}}{\ausfml}}
  {\axkey{\dbox{\pwhile{\ivr}{\ausprg}}{\ausfml}}}
}{}%
\cinferenceRule[unfold|$\dibox{\text{unfold}}$]{unfold while loop}
{\linferenceRule[equiv]
  {(\ivr\limply\dbox{\ausprg}{\dbox{\pwhile{\ivr}{\ausprg}}{\ausfml}}) \land (\lnot\ivr\limply\ausfml)}
  {\axkey{\dbox{\pwhile{\ivr}{\ausprg}}{\ausfml}}}
}{}%
\end{calculus}

The issue with using these axioms to reason about a program is that they do not make our work easier: in either case, they reduce reasoning about a while loop to a bit of logic, and then more reasoning about exactly the same while loop. In principle, as long as we knew how many times a loop would execute in advance, we could use these axioms to unfold the loop body until the condition implies termination. But we will not in general know this information, so we needed different ways of reasoning about loop behavior.

This led to our derivation of the loop rule, which relies on an \emph{invariant} $\inv$. 
\[
\cinferenceRule[whileloop|loop]{while loop invariant}
{\linferenceRule[sequent]
  {\lsequent[L]{}{\inv}
  &
  \lsequent[G]{\inv,\ivr}{\dbox{\ausprg}{\inv}}
  &
  \lsequent[G]{\inv,\lnot\ivr}{\ausfml}}
  {\lsequent[L]{}{
    \dbox{\pwhile{\ivr}{\ausprg}}{\ausfml}}}
}{}
\]
This rule requires us to prove three things in order to show that $P$ holds after the loop executes.
\begin{enumerate}
\item $\lsequent[L]{}{\inv}$: The loop invariant $\inv$ is true when the loop begins its execution.
\item $\lsequent[G]{\inv,\ivr}{\dbox{\ausprg}{\inv}}$: Assuming that both the loop invariant $\inv$ and the guard $Q$ are true, then executing the body of the loop $\alpha$ one time results in the loop invariant remaining true afterwards.
\item $\lsequent[G]{\inv,\lnot\ivr}{\ausfml}$: Assuming that both the loop invariant $\inv$ and the negation of the guard $\lnot Q$ are true, the postcondition $P$ must also be true.
\end{enumerate}
Certainly, this is an improvement over reasoning from the axioms, becuase we are able to deduce correctness by considering the behavior of just a single execution of the loop body. However, it leaves open the question of how we should go about finding a suitable loop invariant, and this can be nontrivial. Today we'll take a closer look at this question, using a familiar program as an example.

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

\section{Back to Binary Search}

Let's go back to an example that we briefly touched on in the first lecture. Recall that we looked at a buggy version of binary search, shown below. 
\begin{verbatim}
int binarySearch(int key, int[] a, int n) {
   int low = 0;
   int high = n;

   while (low < high) {
       int mid = (low + high) / 2;

       if(a[mid] == key) return mid; // key found
       else if(a[mid] < key) {
           low = mid + 1;
       } else {
            high = mid;
       }
   }
   return -1;  // key not found.
}
\end{verbatim}

However, the bug in this case was a bit subtle. If we as the programmer assume that variables assigned type \texttt{int} take values from $\mathbb{Z}$, then there is no bug. Actual computers, being nothing more than glorified finite-state machines, of course do not use integers from $\mathbb{Z}$ but rather machine integers of a fixed size. So when the computer executes \texttt{low + high} on line 6 in the process of determining the midpoint, the resulting sum may be too large to fit into a machine integer and thus overflow, ultimately causing a negative number to be stored in \texttt{mid}.

The simple imperative language that we have studied since does not use machine integers, and we've instead given ourselves the liberty of assuming that all values are integers from $\mathbb{Z}$. This means that if we translate the code from earlier into our language, then we should be able to rigorously prove its correctness using the axioms of Dynamic Logic. However, before we can do so, we will of course need to extend our language to handle arrays.

\paragraph{Basic Arrays: Syntax.}
Most of us are probably familiar with the square bracket notation for expressing arrays, e.g., \texttt{x[i]} to refer to the $i^{th}$ element of the array variable $x$. Because we are already making heavy use of square brackets in dynamic logic formulas, we will instead opt to use parenthesis for array syntax. We must first add a new term $a(\astrm)$ to the syntax, which corresponds to referencing an array $a$ at the index given by $\astrm$. Note that for now we allow arbitrary terms to specify the index, so the following are acceptable terms in the new syntax: $a(0), a(1+x), a(a(x)\cdot 3)$. 

However, we do need to be careful about distinguishing between variable symbols and array symbols. We don't want to allow certain sets of terms into the language, like $a + a$ and $a \le a$. To do this, we need to assume that all variable symbols are already defined as either corresponding to arrays or variables.
\[
\begin{array}{llll}
%
\text{term syntax}
&
  \astrm,\bstrm ~\bebecomes&
  x &(\text{where}~x~\text{is a variable symbol}) \\
  && \alternative 
  c &(\text{where}~c~\text{is a constant literal}) \\
  && \alternative
  a(\astrm) &(\text{where}~a~\text{is an array symbol}) \\
  && \alternative
  \astrm+\bstrm & \\
  && \alternative
  \astrm\cdot\bstrm &
\end{array}
\]
Note that this definition also prohibits terms like $a(1)(2)$. In a language like C, when looking at this term we might expect that $a$ is an array of arrays, so $a(1)(2)$ obtains the second array stored in $a$, and then looks up its third value. As we will see when we define the semantics, our language does not have arrays of arrays, so we shouldn't allow terms like this into our language. The above syntax definition accomplishes this.

Recall that our syntax for programs specified assignments as taking the form $\pupdate{\pumod{x}{\astrm}}$. This means that in order for programs to update arrays, we must also update program syntax with a new alternative for array update. The term $\pupdate{\pumod{a(\astrm)}}{\bstrm}$ does exactly this.
\[
\begin{array}{llll}
%
\text{program syntax}
&
  \asprg,\bsprg ~\bebecomes&
  \pupdate{\pumod{x}{\astrm}}&(\text{where}~x~\text{is a variable symbol}) \\
  && \alternative
  \pupdate{\pumod{a(\astrm)}}{\bstrm}&(\text{where}~a~\text{is an array symbol}) \\
  && \alternative
  \ptest{\ivr} & \\
  && \alternative
  \pif{\ivr}{\asprg}{\bsprg} & \\
  && \alternative
  \asprg;\bsprg & \\
  && \alternative
  \pwhile{\ivr}{\asprg}
\end{array}
\]
Now that there are arrays in our language, we can write the binary search program in it. We don't have procedures and \texttt{return} statements in our language, but we can change things a bit to make it work.
\[
\begin{array}{l}
l := 0;
h := n; \\
m := (l+h) / 2; \\
\pwhile{l < h \land a(m) \ne k}{\{\\
\ \ \ \ \pif{a(m) < k}{\\
\ \ \ \ \ \ \ \ l := m + 1; \\
\ \ \ \ }{\\
\ \ \ \ \ \ \ \ h := m; \\}
\ \ \ \ m := (l + h) / 2; \\
\}
} \\
\pif{l < h}{r := m}{r := -1}
\end{array}
\]

\paragraph{Basic Arrays: Term Semantics.}
Now that we've defined array syntax, we need to give them semantics so that we can reason about their use in programs. Intuitively, we think of arrays as functions from the integers to the type of element stored in the array. We can generalize this idea further by observing that multi-dimensional arrays are nothing more than functions with arity greater than one, from the integers to the element type. This way of modeling generalized array values even extends nicely to constant values, because we can view them as functions of arity zero.

Making this more precise, we will update our semantics for terms and programs to account for all values (i.e., both arrays and integers) as functions of the appropriate arity. We won't worry about multi-dimensional arrays for now (this could return as an exercise), so all of the values we work with will be functions of arity 0 (i.e., integer constants) or 1 (i.e., arrays storing integers). To make this change, we start with redefining the set of all states $\mathcal{S}$. We had previously defined $\mathcal{S}$ as a function that assigns an integer value in $\mathbb{Z}$ to every variable in $V$, the set of all variables. Now we will define $\mathcal{S}$ to be a function that maps $V$ to the set of functions over $\mathbb{Z}$ of arity at most 1:
\[
\mathcal{S} = (\mathbb{Z}^{0} \mapsto \mathbb{Z}) \cup (\mathbb{Z}^{1} \mapsto \mathbb{Z}) = \mathbb{Z} \cup \mathbb{Z} \mapsto \mathbb{Z}
\]
Note that with this change, we can still view the semantics of terms $\omega\llbracket\astrm\rrbracket$ as a subset of $\mathcal{S}$, and that of programs $\llbracket\asprg\rrbracket$ as a subset of $\mathcal{S}\times\mathcal{S}$.

We can now define the semantics of terms with arrays as we have done previously, by inductively distinguishing the shape of a given term $\astrm$ against a state $\omega \in \mathcal{S}$. However, it might first be a good idea to consider how we would like arrays to be used in the language. For instance, do we want to assign meaning to programs that contain constructs like $a_1 + a_2$? Probably not, so we will need to be careful in how we define the semantics to avoid such cases. Intuitively, we will do so by only assigning semantics to terms involving arrays that evaluate to integer constants. While this will prohibit some cases that we might see as useful, such as $a_1 := a_2$ (i.e., copy an entire array) and $a_1 = a_2$ (i.e., compare all elements of two arrays), we can implement such functionality in other ways.

\begin{definition}[Semantics of terms with basic arrays] The semantics of a term $\astrm$ in a state $\omega \in \mathcal{S}$ is its value $\omega\llbracket\astrm\rrbracket$, defined inductively as follows.
\begin{itemize}
\item $\omega\llbracket c \rrbracket = c$ for number literals $c$
\item $\omega\llbracket x \rrbracket = \omega(x)$
\item $\omega\llbracket a(\astrm) \rrbracket = \omega(a)(\omega\llbracket\astrm\rrbracket)$
\item $\omega\llbracket \astrm + \bstrm \rrbracket = \omega\llbracket \astrm\rrbracket + \omega\llbracket\bstrm \rrbracket$
\item $\omega\llbracket \astrm \cdot \bstrm \rrbracket = \omega\llbracket \astrm\rrbracket \cdot \omega\llbracket\bstrm \rrbracket$
\end{itemize}
\end{definition}
The only change is the addition of the term $a(\astrm)$, which corresponds to array lookup. The semantics defines this by looking up $a$ in $\omega$, evaluating $\astrm$ in $\omega$, and then applying the results.

\section{Proving Binary Search}
Now that we have semantics for terms that mention arrays, we should be able to reason about the correctness of the binary search code from earlier. But what should the specification be? 

\paragraph{Specification.}
As for a precondition, there is one big assumption that this code must make, namely that the array $a$ is already sorted. There are several ways to specify sortedness of arrays, and they all involve quantifiers. Perhaps the most obvious specification would simply state that for all valid positions in the array $0 \le i < n$, every pair $a(i-1)$ and $a(i)$ are in order:
\[
\forall i . 0 < i < n \rightarrow a(i-1) \le a(i)
\]
This specification is fine, but we should think about how we might want to use it later on. In a sequent calculus proof, this precondition will eventually give us $0 < i < n \rightarrow a(i-1) \le a(i)$ (for some $i$) in the antecedent. This will let us conclude things directly about adjacent elements in the array, but if we want to reason about elements that are arbitrarily far away, e.g., to prove that $a(0) \le a(n-1)$, then we will have to do a bit more work as this fact is not immediate from the precondition. We would need to prove a lemma:
\[
\vdash (\forall i . 0 < i < n \rightarrow a(i-1) \le a(i)) \rightarrow (\forall i_1, i_2 . 0 \le i_1 \le i_2 < n \rightarrow a(i_1) \le a(i_2))
\]
However, because we are free to place whatever we would like in the precondition (within reason), we can simply use the formula on the right as our precondition for sortedness.
\[
\keywordfont{sorted}(a,n) \equiv \forall i_1, i_2 . 0 \le i_1 \le i_2 < n \rightarrow a(i_1) \le a(i_2)
\]
Implicit in this is another precondition, namely that $0 < n$. If we did not have this, then someone could initialize $n$ to be negative, which would cause $\keywordfont{sorted}$ to evaluate to true but result in meaningless program behavior. So, our precondition is:
\[
\keywordfont{pre}(a,n) \equiv 0 < n \land \keywordfont{sorted}(a,n)
\]
Now we need a postcondition. Looking at the program text, the variable $r$ is used to store the result on the last line. If $l < h$, which means that the loop ended early after finding an element with value $k$, then $r$ takes the position of the element. Otherwise, $r$ takes the value $-1$. This suggests a postcondition with two cases:
\[
\begin{array}{ll}
0 \le r \rightarrow a(r) = k & k~\text{found at position}~r \\
r < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k & k~\text{not found}
\end{array}
\]
The antecedents are mutually exclusive, so we can simply conjoin these cases to arrive at our postcondition:
\[
\keywordfont{post}(a,r,k,n) \equiv (0 \le r \rightarrow a(r) = k) \land (r < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k)
\]
The dynamic logic formula that we would then like to prove is:
\[
\begin{array}{rl}
%
\keywordfont{pre}(a,n) \rightarrow [& \\
&
\left.
\begin{array}{l}
%
\left.
\begin{array}{l}
%
l := 0; \\
h := n; \\
m := (l+h) / 2; 
\end{array}
\ \ \ \ \ \ \ \ \right\} \gamma
\\
\pwhile{l < h \land a(m) \ne k}{\{\\ 
\left.
\begin{array}{rl}
%
& \ \ \ \ \pif{a(m) < k}{\\
& \ \ \ \ \ \ \ \ l := m + 1; \\
& \ \ \ \ }{\\
& \ \ \ \ \ \ \ \ h := m; \\}
& \ \ \ \ m := (l + h) / 2;
\end{array} \right\} \beta
\\
\}} \\
\pif{l < h}{r := m}{r := -1}
\end{array} 
\right\} \alpha
\\
] & \keywordfont{post}(a,r,k,n)
\end{array}
\]
Notice that we will use $\alpha$ as shorthand for the entire program, and $\beta$ for the portion within the loop, and $\gamma$ for the first three assignments.

\paragraph{Finding a loop invariant.}
Before we can begin proving this formula valid, we need to think about a loop invariant. What should it be? It isn't immediately clear from inspection, so perhaps we can find a somewhat systematic way to nudge us in the right direction. One approach that often works is to start writing the proof with a placeholder for the loop invariant, so that we can see what is needed of the loop invariant to make the proof work.
\begin{sequentdeduction}[array]
\linfer[implyr] {
  \linfer[composeb] {
    \linfer[ifb] {
      \lsequent{\mathtt{pre}(a,n)} {{}[\gamma][\pwhile{l<h \land a(m) \ne k}{\beta}]P}
    } {
      \lsequent{\mathtt{pre}(a,n)} {{}[\gamma][\pwhile{l<h \land a(m) \ne k}{\beta}][\pif{l<h}{r:=m}{r:=-1}]\mathtt{post}(a,r,k,n)}
    }
  } {
    \lsequent{\mathtt{pre}(a,n)} {{}[\alpha] \mathtt{post}(a,r,k,n)}
  }
} {
  \lsequent{} {{}\mathtt{pre}(a,n) \rightarrow [\alpha] \mathtt{post}(a,r,k,n)}
}
\end{sequentdeduction}
In the preceeding, we completed the proof up to the $\keywordfont{while}$ statement. The condition $P$, which is too large to fit on the page with the surrounding formula, is:
\[
P \equiv \underbrace{l<h \rightarrow [r:=m]\mathtt{post}(a,r,k,n)}_{P_T} \land \underbrace{l\ge h \rightarrow [r:=-1]\mathtt{post}(a,r,k,n)}_{P_F}
\]
However, we haven't gotten to the loop yet, so we need to continue by applying the loop rule.
\begin{sequentdeduction}[array]
\linfer[loop] {
  \lsequent{\mathtt{pre}(a,n)} {{}[\gamma]\inv}
  !\lsequent{\inv,l<h \land a(m) \ne k} {{}[\beta]\inv}
  !\lsequent{\inv,\lnot(l<h \land a(m) \ne k)} {{}P}
} {
  \lsequent{\mathtt{pre}(a,n)} {{}[\gamma][\pwhile{l<h \land a(m) \ne k}{\beta}]P}
}
\end{sequentdeduction}
Of the three things that we must prove, the last corresponding to the invariant implying the postcondition will probably be the most helpful in learning more about a suitable loop invariant. We'll proceed with proving this obligation, and see what we can learn from the attempt.
\begin{sequentdeduction}
\linfer[andr] {
  \linfer[implyr] {
    \textcircled{1}
  } {
    \lsequent{\inv,\lnot(l<h \land a(m) \ne k)} {{}P_T}
  }
  &\linfer[implyr] {
    \textcircled{2}
  } {
    &\lsequent{\inv,\lnot(l<h \land a(m) \ne k)} {{}P_F}
  }
} {
  \lsequent{\inv,\lnot(l<h \land a(m) \ne k)} {{}P_T \land P_F}
}
\end{sequentdeduction}
We'll continue with the proof $\textcircled{1}$ of $\lsequent{\inv,\lnot(l<h \land a(m) \ne k)} {{}P_T}$ first.
\begin{sequentdeduction}
\linfer[assignb] {
  \linfer[notl] {
    \linfer[andr] {
      \linfer[id] {
        \lclose
      } {
        \lsequent{\inv,l<h} {{}l<h,\texttt{post}(a,m,k,n)} 
      }
      &\linfer[notr] {\textcircled{3}}{\lsequent{\inv,l<h} {{}a(m) \ne k,\texttt{post}(a,m,k,n)}}
    } {
      \lsequent{\inv,l<h} {{}l<h \land a(m) \ne k,\texttt{post}(a,m,k,n)} 
    }
  } {
    \lsequent{\inv,\lnot(l<h \land a(m) \ne k),l<h} {{}\texttt{post}(a,m,k,n)} 
  }
} {
  \lsequent{\inv,\lnot(l<h \land a(m) \ne k),l<h} {{}[r:=m]\texttt{post}(a,r,k,n)}
}
\end{sequentdeduction}
And continuing with \textcircled{3} from above, let $Q \equiv J,l<h, a(m)=k$:
\begin{sequentdeduction}
\linfer[andr] {
  \linfer[implyr] {
    \linfer[id] {\lclose} {\lsequent{Q,0 \le m} {{}a(m) = k}}
  } {
    \lsequent{Q} {{}0 \le m \rightarrow a(m) = k}
  }
  &\linfer[implyr] {
    \linfer[notl] {
      \linfer {\vdots} {\lsequent{Q} {{}0 \le m, \forall i . 0 \le i < n \rightarrow a(i) \ne k}}
    } {
      \lsequent{Q,m < 0} {{}\forall i . 0 \le i < n \rightarrow a(i) \ne k}
    }
  } {
    \lsequent{Q} {{}m < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k}
  }
} {
  \lsequent{Q} {{}(0 \le m \rightarrow a(m) = k) \land (m < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k)}
}
\end{sequentdeduction}
It doesn't seem that we can go any further. From our loop invariant $\inv$, in addition to $l < h \land a(m) = k$, we must prove that either $0 \le m$ or that none of the elements of $a$ from 0 to $n-1$ match $k$. It shouldn't take too much thought to realize which of these goals will make a more reasonable addition to the loop invariant, because we expect that $m$ should stay within the bounds $[0,n]$ throughout the execution of the loop. 

In fact, a bit of thought says that we can strengthen this a bit if we're careful, and stronger invariants make for more direct proofs. In particular, it seems that most of the time, $0 \le m < n$ (note, not just $0 \le m \le n$). Scanning through the code, the only situation that we might get into trouble with this invariant is if $l = h = n$. But we can account for this by observing that as long as $l < h$, then $0 \le m < n$. We'll add this to our invariant.
\[
\inv \equiv l < h \rightarrow 0 \le m < n
\]
Let's finish the proof of $\textcircled{3}$, from where we left off at the vertical dots. Let $\Delta \equiv \forall i . 0 \le i < n \rightarrow a(i) \ne k$.
\begin{sequentdeduction}
\linfer[implyl] {
  \linfer[id] {\lclose} {\lsequent{l < h, a(m) = k} {{}l < h, 0 \le m, \Delta}}
  &\linfer[qear] {\lclose} {\lsequent{0 \le m < n, l < h, a(m) = k} {{}0 \le m, \Delta}}
} {
  \lsequent{l < h \rightarrow 0 \le m < n, l < h, a(m) = k} {{}0 \le m, \Delta}
}
\end{sequentdeduction}
At this point, we have shown that the loop invariant $\inv \equiv l < h \rightarrow 0 \le m < n$ implies the postcondition when the program takes the true branch on the final conditional statement (e.g., $l < h$ when the loop terminates). We still need to think about what will happen when it takes the other branch, when $l \ge h$. Let's proceed with the proof, corresponding to the subtree $\textcircled{2}$ from above.
\begin{sequentdeduction}
\linfer[assignb] {
  \linfer[notl] {
    \linfer[andr] { 
      \linfer { 
        \textcircled{4}
      } {
        \lsequent{\inv,l \ge h} {{}l<h,\mathtt{post}(a,-1,k,n)}
      }
      &\linfer{
        \textcircled{5}
      } {
        \lsequent{\inv,l \ge h} {{}a(m) \ne k,\mathtt{post}(a,-1,k,n)}
      }
    } {
      \lsequent{\inv,l \ge h} {{}l<h \land a(m) \ne k,\mathtt{post}(a,-1,k,n)}
    }
  } {
    \lsequent{\inv,\lnot(l<h \land a(m) \ne k),l \ge h} {{}\mathtt{post}(a,-1,k,n)}
  }
} {
  \lsequent{\inv,\lnot(l<h \land a(m) \ne k),l \ge h} {{}[r := -1]\mathtt{post}(a,r,k,n)}
}
\end{sequentdeduction}
In subtree $\textcircled{4}$, we will need to prove that the postcondition holds for $r=-1$ given $J$ and $l \ge h$, because we cannot prove that $l<h$ given these assumptions. In subtree $\textcircled{5}$, we must prove that either the postcondition holds at $r=-1$, or that $a(m) \ne k$. Let's start with $\textcircled{4}$.
\begin{sequentdeduction}
\linfer[andr] {
  \linfer[implyr] {
    \linfer[notl] {
      \linfer[qear] {
        \lclose
      } {
        \lsequent{\inv,l \ge h} {{}-1 < 0,l<h,a(-1) = k}
      }
    } {
      \lsequent{\inv,l \ge h,0\le-1} {{}l<h,a(-1) = k}
    }
  } {
    \lsequent{\inv,l \ge h} {{}l<h,0 \le -1 \rightarrow a(-1) = k}
  }
  &\linfer[implyr]{
    \linfer[allr] {
      \linfer[implyr] {
        \linfer {
          \vdots
        } {
          \lsequent{\inv,l \ge h,0 \le i < n} {{}l<h,a(i) \ne k}
        }
      } {
        \lsequent{\inv,l \ge h} {{}l<h,0 \le i < n \rightarrow a(i) \ne k}
      }
    } {
      \lsequent{\inv,l \ge h} {{}l<h,\forall i . 0 \le i < n \rightarrow a(i) \ne k}
    }
  } {
    \lsequent{\inv,l \ge h} {{}l<h,-1 < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k}
  }
} {
  \lsequent{\inv,l \ge h} {{}l<h,(0 \le -1 \rightarrow a(-1) = k) \land (-1 < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k)}
}
\end{sequentdeduction}
We can't go any further at this point. Our invariant will need to give us enough to prove that when $l \ge h$ and $0 \le i < n$, then $a(i) \ne k$. With this in mind, we might go back to our binary search code and think about how the main loop body works. It begins with pointers to the beginning and end of the array, and works its way inwards. At each iteration of the loop, it checks the midpoint of these pointers, and moves one of the pointers to the current midpoint (or its immediate successor) depending on the outcome of the test $a(m) < k$. After each move, we know that because the array is sorted, none of the positions outside the pointers will contain a match for $k$.

\begin{figure}
\fbox{
\begin{minipage}{\textwidth}
\textbf{Aside}: Rules for quantifiers
\\[1ex]
Our proof of \textcircled{4} used a rule that we have not seen before: $\forall\rightrule$. The rule allows us to remove the quantifier, replacing the bound variable with a new variable that does not appear anywhere else in the sequent. This is equivalent to saying that if we can prove that $F(y)$ holds on some $y$ for which we make no prior assumptions, then we can conclude that it holds universally. The corresponding left rule ($\forall\leftrule$) says that if we can prove something assuming $F$ holds for a particular term, say $\astrm$, then we can prove it assuming that $F$ holds universally. Intuitively, we've only made our assumptions stronger by assuming that $F$ holds universally.
\[
\cinferenceRule[allr|$\forall$\leftrule]{forall left}
{\linferenceRule[sequent]
  {\lsequent{\Gamma,F(\astrm)}{{}\Delta}}
  {\lsequent{\Gamma,\forall x.F(x)}{{}\Delta}}
}{}
\quad
\cinferenceRule[allr|$\forall$\rightrule]{forall right}
{\linferenceRule[sequent]
  {\lsequent{\Gamma}{{}F(y),\Delta}}
  {\lsequent{\Gamma}{{}\forall x.F(x),\Delta}}
}{\text{$y$ new}}
\]
The rules for existential quantifiers are similar, but in this case, it is the left rule in which we need to be careful about renaming. Similarly to the $\forall\rightrule$, if we can use the fact that $F(y)$ holds to prove $\Delta$, and nothing in our assumptions or $\Delta$ mentions specific things about $y$, then we can conclude that the details of $y$ don't matter for the conclusion, and the only important fact is that some value establishing $F(y)$ exists. The $\exists\rightrule$ simply says that if we can prove that $F$ holds for term $\astrm$, then we can conclude that it must hold for some value, even if we leave the value unspecified.
\[
\cinferenceRule[existsr|$\exists$\leftrule]{exists left}
{\linferenceRule[sequent]
  {\lsequent{\Gamma,F(y)}{{}\Delta}}
  {\lsequent{\Gamma,\exists x.F(x)}{{}\Delta}}
}{\text{$y$ new}}
\quad
\cinferenceRule[existsr|$\exists$\rightrule]{exists right}
{\linferenceRule[sequent]
  {\lsequent{\Gamma}{{}F(\astrm),\Delta}}
  {\lsequent{\Gamma}{{}\exists x.F(x),\Delta}}
}{}
\]
\end{minipage}
}
\end{figure}

Taking stock of this, our loop invariant might state that for all $0 \le i < n$, whenever $a(i) = k$ then it is the case that $l \le i < h$. We can formalize this, and add it to our invariant.
\[
\begin{array}{lll}
%
\texttt{mbound} &\equiv& l < h \rightarrow 0 \le m < n \\
\texttt{notfound} &\equiv& \forall i . 0 \le i < n \rightarrow a(i) = k \rightarrow l \le i < h \\
\inv &\equiv& \texttt{mbound} \land \texttt{notfound}
\end{array}
\]
Now let's see if we can finish off the proof of $\textcircled{4}$.
\begin{sequentdeduction}
\linfer[alll] {
  \linfer[implyl] {
    \textcircled{6}\ \ 
    &\linfer[implyl] {
      \textcircled{7}\ \ 
      &\linfer[notr] {
        \linfer[qear] {
          \linfer[notl] {
            \linfer[id] {
              \lclose
            } {
              \lsequent{\texttt{mbound},l \ge h,0 \le i < n} {{}l \ge h,a(i) \ne k}
            }
          } {
            \lsequent{\texttt{mbound},l < h,l \ge h,0 \le i < n} {{}a(i) \ne k}
          }
        } {
          \lsequent{\texttt{mbound},l \le i < h,l \ge h,0 \le i < n} {{}a(i) \ne k}
        }
      } {
        \lsequent{\texttt{mbound},l \le i < h,l \ge h,0 \le i < n} {{}l<h,a(i) \ne k}
      }
    } {
      \lsequent{\texttt{mbound},a(i) = k \rightarrow l \le i < h,l \ge h,0 \le i < n} {{}l<h,a(i) \ne k}
    }
  } {
    \lsequent{\texttt{mbound},0 \le i < n \rightarrow a(i) = k \rightarrow l \le i < h,l \ge h,0 \le i < n} {{}l<h,a(i) \ne k}
  }
} {
  \lsequent{\texttt{mbound},\texttt{notfound},l \ge h,0 \le i < n} {{}l<h,a(i) \ne k}
}
\end{sequentdeduction}
The proof of $\textcircled{6}$ is direct from id:
\begin{sequentdeduction}
\linfer[id] {
  \lclose
} {
  \lsequent{\texttt{mbound},l \ge h,0 \le i < n} {{}0 \le i < n,l<h,a(i) \ne k}
}
\end{sequentdeduction}
The proof of $\textcircled{7}$ is nearly as straightforward:
\begin{sequentdeduction}
\linfer[notr] {
  \linfer[id] {
    \lclose 
  } {
    \lsequent{\texttt{mbound},l \ge h,0 \le i < n,a(i) \ne k} {{}a(i) = k,l<h,a(i) \ne k}
  }
} {
  \lsequent{\texttt{mbound},l \ge h,0 \le i < n} {{}a(i) = k,l<h,a(i) \ne k}
}
\end{sequentdeduction}
So we have now shown that the loop invariant $\texttt{mbound} \land \texttt{notfound}$ implies the postcondition in the case where the loop terminated because $\lnot(l < h)$. We still have to show the case where the loop terminated because $a(m) = k$, which corresponds to subtree \texttt{5} from above.
\begin{sequentdeduction}
\linfer[andr] {
  \textcircled{8}\ \ \ \ 
  &\linfer[implyr]{
    \linfer[allr] {
      \linfer[implyr] {
        \linfer[alll] {
          \linfer[implyl] {
            \textcircled{9}\ \ 
            &\linfer[implyl] {
              \textcircled{a}\ \ 
              &\linfer[qear] {
                \linfer[notl] {
                  \linfer[id] {
                    \lclose
                  } {
                    \lsequent{\texttt{mbound},l \ge h,0 \le i < n} {{}l \ge h,a(m)\ne k,a(i) \ne k}
                  }
                } {
                  \lsequent{\texttt{mbound},l < h,l \ge h,0 \le i < n} {{}a(m)\ne k,a(i) \ne k}
                }
              } {
                \lsequent{\texttt{mbound},l \le i < h,l \ge h,0 \le i < n} {{}a(m)\ne k,a(i) \ne k}
              }
            } {
              \lsequent{\texttt{mbound},a(i) = k \rightarrow l \le i < h,l \ge h,0 \le i < n} {{}a(m)\ne k,a(i) \ne k}
            }
          } {
            \lsequent{\texttt{mbound},0 \le i < n \rightarrow a(i) = k \rightarrow l \le i < h,l \ge h,0 \le i < n} {{}a(m)\ne k,a(i) \ne k}
          }
        } {
          \lsequent{\texttt{mbound},\texttt{notfound},l \ge h,0 \le i < n} {{}a(m)\ne k,a(i) \ne k}
        }
      } {
        \lsequent{\inv,l \ge h} {{}a(m)\ne k,0 \le i < n \rightarrow a(i) \ne k}
      }
    } {
      \lsequent{\inv,l \ge h} {{}a(m)\ne k,\forall i . 0 \le i < n \rightarrow a(i) \ne k}
    }
  } {
    \lsequent{\inv,l \ge h} {{}a(m)\ne k,-1 < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k}
  }
} {
  \lsequent{\inv,l \ge h} {{}a(m)\ne k,(0 \le -1 \rightarrow a(-1) = k) \land (-1 < 0 \rightarrow \forall i . 0 \le i < n \rightarrow a(i) \ne k)}
}
\end{sequentdeduction}
The proof of $\textcircled{8}$ is identical to the corresponding case in our proof of $\textcircled{4}$ above, so we won't list it here. The proof of $\textcircled{9}$ is identical to that of $\textcircled{6}$ above. Likewise, the proof of $\textcircled{a}$ is idntical to that of $\textcircled{7}$, so we'll omit it here. Finally, to finish off this proof, we used identical reasoning as in the proof of $\textcircled{4}$. We ended up with conflicting facts about the natural numbers in our assumptions (i.e., $0 \le i < h$ and $l \ge h$), so applying $\lnot$L closed the proof. 

We conclude that this loop invariant is strong enough to give us the desired postcondition, but we still need to prove two things:
\begin{enumerate}
\item $\lsequent{\mathtt{pre}(a,n)} {{}[\gamma]\inv}$. That the loop invariant holds when we begin executing the loop.
\item $\lsequent{\inv,l<h \land a(m) \ne k} {{}[\beta]\inv}$. That each iteration of the loop preserves the invariant.
\end{enumerate}
A quick glance at the first three statements should give us some confidence that the (1) will hold, so we'll hold off on the formal proof until the end. However, (2) is a bit more involved, and may even require strengthening the invariant more.
\begin{sequentdeduction}
\linfer[assignb] {
  \linfer[ifb] {
    \textcircled{b}
    &\textcircled{c}
  } {
    \lsequent{\inv,l<h \land a(m) \ne k} {{}[\pif{a(m)<k}{l:=m+1}{h:=m}]\inv(a,l,h,(l+h)/2,n)}
  }
} {
  \lsequent{\inv,l<h \land a(m) \ne k} {{}[\beta]\inv(a,l,h,m,n)}
}
\end{sequentdeduction}
We'll attempt to prove each branch now, corresponding to $\textcircled{b}$ and $\textcircled{c}$. To aid in variable substitutions, we'll use the following placeholders with variables for $\mathtt{mbound}$ and $\mathtt{notfound}$:
\begin{align*}
\mathtt{mbound}(l,h,m,n) \\
\mathtt{notfound}(a,n,k,l,h) \\
\end{align*}
The beginning of the proof for $\textcircled{b}$ is:
\begin{sequentdeduction}
\linfer[assignb] {
  \linfer[implyr] {
    \linfer[andr] {
      \textcircled{d}
      &\textcircled{e}
    } {
      \lsequent{\inv,l<h \land a(m) \ne k,a(m)<k} {{}\mathtt{mbound}(m+1,h,(l+h)/2,n) \land \mathtt{notfound}(a,n,k,m+1,h)}
    }
  } {
    \lsequent{\inv,l<h \land a(m) \ne k} {{}a(m)<k \rightarrow \inv(a,m+1,h,(l+h)/2,n)}
  }
} {
  \lsequent{\inv,l<h \land a(m) \ne k} {{}a(m)<k \rightarrow [l:=m+1]\inv(a,l,h,(l+h)/2,n)}
}
\end{sequentdeduction}
It shouldn't be surprising that we need to prove each conjunct in our invariant separately. In fact, it is conventional to split loop invariants along conjuncts into a set of loop invariants, as it facilitates readability. We'll start with $\textcircled{d}$, which says that $m$ remains bounded between $0$ and $n$ (non-inclusive) as long as $l < h$.
\begin{sequentdeduction}
\linfer[andl] {
  \linfer[implyr] {
    \linfer[implyl] {
      \textcircled{f}
      &\linfer{
        \vdots
      } {
        \lsequent{0\le m<n,\mathtt{notfound},l<h,a(m) \ne k,a(m)<k,m+1 < h} {{}0 \le (l+h)/2 < n}
      }
    } {
      \lsequent{l<h \rightarrow 0\le m<n,\mathtt{notfound},l<h,a(m) \ne k,a(m)<k,m+1 < h} {{}0 \le (l+h)/2 < n} 
    }
  } {
    \lsequent{\inv,l<h,a(m) \ne k,a(m)<k} {{}m+1 < h \rightarrow 0 \le (l+h)/2 < n} 
  }
} {
  \lsequent{\inv,l<h \land a(m) \ne k,a(m)<k} {{}m+1 < h \rightarrow 0 \le (l+h)/2 < n}
}
\end{sequentdeduction}
Subtree \textcircled{f} follows immediately from the fact that $l<h$ is in our assumptions. We can't complete the proof without the assumption that $h \le n$, which we have implicitly assumed by adding $l < h \rightarrow 0 \le m < n$ to our invariant. We'll add the fact to our invariant, giving us
\[
\inv \equiv \mathtt{mbound} \land \mathtt{notfound} \land h \le n
\]
Although the addition of this fact won't invalidate our earlier proofs that the invariant implies the postcondition, we will need to prove that it is preserved by the loop, and holds when the loop is first entered. We'll get back to these obligations later, but for now let's return to the proof of \textcircled{b}, picking up where we left off at the vertical dots with this new fact in hand. The rest of the proof follows from arithmetic, because $l < h \le n$, so $(l+h)/2 < n$.
\begin{sequentdeduction}
\linfer[qear]{
  \lclose
} {
  \lsequent{h \le n,0\le m<n,\mathtt{notfound},l<h,a(m) \ne k,a(m)<k,m+1 < h} {{}0 \le (l+h)/2 < n}
}
\end{sequentdeduction}
Now we need to prove \textcircled{e}, which is preservation of $\mathtt{notfound}(a,n,k,m+1,h)$. To reduce clutter, we'll remove the assumption $a(m) \ne k$, as it implied by $a(m) < k$.
\begin{sequentdeduction}
\linfer[andl] {
  \linfer[allr] {
    \linfer[implyr] {
      \linfer[implyr] {
        \linfer[implyl] {
          \textcircled{g}\ \ 
          &\linfer[andr] {
              \textcircled{h}
              &\textcircled{i}
          } {
            \lsequent{0\le m<n,\mathtt{notfound},h\le n,l<h,a(m)<k,0\le i<n,a(i)=k} {{}m+1 \le i < h}
          }
        } {
          \lsequent{l<h\rightarrow 0\le m<n,\mathtt{notfound},h\le n,l<h,a(m)<k,0\le i<n,a(i)=k} {{}m+1 \le i < h}
        }
      } {
        \lsequent{\inv,l<h,a(m)<k,0\le i<n} {{}a(i) = k \rightarrow m+1 \le i < h}
      }
    } {
      \lsequent{\inv,l<h,a(m)<k} {{}0 \le i < n \rightarrow a(i) = k \rightarrow m+1 \le i < h}
    }
  } {
    \lsequent{\inv,l<h,a(m)<k} {{}\forall i . 0 \le i < n \rightarrow a(i) = k \rightarrow m+1 \le i < h} 
  }
} {
  \lsequent{\inv,l<h \land a(m) \ne k,a(m)<k} {{}\forall i . 0 \le i < n \rightarrow a(i) = k \rightarrow m+1 \le i < h}
}
\end{sequentdeduction}
Subtree \textcircled{g} follows immediately from the fact that $l<h$ is already in our assumptions. The last step splits $m+1 \le i < h$ into $m+1 \le i \land i < h$, and applies $\land$R. However, we can't proceed further, even though our intuition suggests that we should be able to. What's going on? We need to prove that $m+1 \le i < h$, and we know that $a(m) < k$ and that $a(i) = k$. We know that this implies that $m+1 \le i$, but why? Because $a$ is sorted! But we cannot use that fact at the moment, because it isn't in our loop invariant. In retrospect, it should have been obvious that we would need to use this fact in our invariant, because we required it in the precondition. Adding it gives us the following:
\[
\inv \equiv \mathtt{mbound} \land \mathtt{notfound} \land h \le n \land \mathtt{sorted}
\]
Let's finish the proof of \textcircled{h}, but first we might think about de-cluttering our assumptions to make the reasoning more clear. From our informal discussion in the previous paragraph, we only seem to need the fact that the loop is sorted, the two comparisons involving $a(m)$ and $a(i)$, and facts about the bounds of $m$. Let's try to finish the proof with just these.
\begin{sequentdeduction}
\linfer[qear] {
  \linfer[alll] {
    \linfer[notl] {
      \linfer[notr] {
        \linfer[implyl] {
          \textcircled{j}\ \ \ 
          &\linfer[applyeql] {
            \linfer[id] {
              \lclose
            } {
              \lsequent{a(i) \le a(m),0\le m<n,0\le i<n,a(i)=k,m\ge i} {{}a(m) \ge a(i)}
            }
          } {
            \lsequent{a(i) \le a(m),0\le m<n,0\le i<n,a(i)=k,m\ge i} {{}a(m) \ge k}
          }
        } {
          \lsequent{0 \le i \le m < n \rightarrow a(i) \le a(m),0\le m<n,0\le i<n,a(i)=k,m\ge i} {{}a(m) \ge k}
        }
      } {
        \lsequent{0 \le i \le m < n \rightarrow a(i) \le a(m),0\le m<n,0\le i<n,a(i)=k} {{}a(m) \ge k,m<i}
      }
    } {
      \lsequent{0 \le i \le m < n \rightarrow a(i) \le a(m),0\le m<n,0\le i<n,a(m)<k,a(i)=k} {{}m < i}
    }
  } {
    \lsequent{\forall i_1, i_2 . 0 \le i_1 \le i_2 < n \rightarrow a(i_1) \le a(i_2),0\le m<n,0\le i<n,a(m)<k,a(i)=k} {{}m < i}  
  }
} {
  \lsequent{\forall i_1, i_2 . 0 \le i_1 \le i_2 < n \rightarrow a(i_1) \le a(i_2),0\le m<n,0\le i<n,a(m)<k,a(i)=k} {{}m+1 \le i}
}
\end{sequentdeduction}
Subtree \textcircled{j} follows directly from the assumptions available in our context. We must now prove the other side of the inequality, $i < h$, corresponding to subtree \textcircled{i}.
\begin{sequentdeduction}
\linfer[alll] {
  \linfer[implyl] {
    \textcircled{k}\ \ \ 
    &\linfer[implyl] {
      \textcircled{l}
      &\linfer[qear] {
        \lclose
      } {
        \lsequent{0\le i<n,l \le i < h,h\le n,l<h,a(i)=k} {{}i < h}
      }
    } {
      \lsequent{0\le i<n,a(i) = k \rightarrow l \le i < h,h\le n,l<h,a(i)=k} {{}i < h}
    }
  } {
    \lsequent{0\le i<n,0 \le i < n \rightarrow a(i) = k \rightarrow l \le i < h,h\le n,l<h,a(i)=k} {{}i < h}
  }
} {
  \lsequent{0\le m<n,\forall i . 0 \le i < n \rightarrow a(i) = k \rightarrow l \le i < h,h\le n,l<h,a(m)\ne k} {{}i < h}
}
\end{sequentdeduction}
Subtrees \textcircled{k} and \textcircled{l} follow immediately from the assumptions in our context. We've now closed out subtree \textcircled{d}, which means that we've proved preservation of $\mathtt{mbound}$. 

We still need to prove preservation of the $\mathtt{mbound}$ and $\mathtt{notfound}$ on the other branch of the conditional (subtree \textcircled{c}), in addition to the parts of the loop invariant that we added while proving $\mathtt{mbound}$: $h \le n$ and $\mathtt{sorted}$.
Let's first look at the preservation proof for $\mathtt{sorted}$. The only free variables in this portion of the invariant are $a$ and $n$, neither of which are updated by the code. Intuitively, this suggests that the proof should be trivial, which we see from the proof.
\begin{sequentdeduction}
\linfer[assignb] {
  \linfer[id] {
    \lclose
  } {
    \lsequent{\mathtt{sorted},\mathtt{mbound},\mathtt{notfound},h\le n,l<h,a(m)\ne k} {{}\mathtt{sorted}}
  }
} {
  \lsequent{\mathtt{sorted},\mathtt{mbound},\mathtt{notfound},h\le n,l<h,a(m)\ne k} {{}[l:=m+1]\mathtt{sorted}}
}
\end{sequentdeduction}
Similarly for $h \le n$, because $h$ isn't updated on this branch of the conditional, we get preservation by assuming $h \le n$ as part of the invariant.

Let's continue for now with subtree \textcircled{e}.
\begin{sequentdeduction}
\linfer[assignb] {
  \linfer[andr] {
    \textcircled{m}
    &\textcircled{n}
    &\textcircled{o}
    &\textcircled{p}
  } {
    \lsequent{\inv,l<h,a(m)>k} {{}\mathtt{mbound}(l,m,(l+m)/2,n) \land \mathtt{notfound}(a,n,k,l,m) \land m \le n \land \mathtt{sorted}}
  }
} {
  \lsequent{\inv,l<h,a(m)>k} {{}[h := m]\inv(a,l,m,(l+h)/2,n)}
}
\end{sequentdeduction}
As in the previous branch, because $a$ and $n$ aren't updated, the proof of \textcircled{p} will be similarly trivial. The proof of \textcircled{o}, which is preservation of $h \le n$, is not as trivial this time. Because $h$ was updated to $m$, we need to show that:
\[
\begin{array}{ll}
%
& l < h \rightarrow 0 \le m < n,
\forall i . 0 \le i < n \rightarrow a(i) = k \rightarrow l \le i < h, \\
& h \le n,
\forall i_1, i_2 . 0 \le i_1 \le i_2 < n \rightarrow a(i_1) \le a(i_2), \\
& l < h,
a(m) > k
\end{array}
\vdash
m \le n
\]
This follows from applying $\rightarrow$L on $l < h \rightarrow 0 \le m < n$, and the fact that $l < h$ is one of the premises. However, it should be clear that manually proving all of the obligations is a very laborious undertaking! Once the correct loop invariant has been identified, then the rest of the work is difficult only insofar as it requires a lot of work. This is exactly why we use SMT solvers to prove straightforward formulas like the one we just discussed, and verification condition generators to apply the axioms of dynamic logic to eliminate box and diamond terms.

The remaining obligations \textcircled{m} and \textcircled{n} are left as an exercise, as is the proof that the first three statements in $\gamma$ establish the invariant, $\lsequent{\mathtt{pre}(a,n)}{{}[\gamma] J}$.

\end{document}