# Deduction example: array reversal

This is an example of using tactics in Ivy to keep verification conditions decidable.

The problem we will tackle is reversal of an array. The difficulty
from the point of view of decidability is that, to specify an array
reversal procedure, we need arithmetic in some form. For example,
we might say that array `a`

is the reverse of array `b`

if they have
the same length `n`

, and if, for all `0 <= i < n`

, we have `b[i] = a[n-i-1]`

.

The difficulty with this specification is that it applies the
arithmetic operator `+`

to the universally quantifier variable `n`

,
which means it is not in the `almost uninterpreted`

fragment.

We can see this if we try to verify the following program:

```
# include collections
#
# type t
# interpret t -> int
# type u
# interpret u -> int
#
# instance arr : array(t,u)
#
# action rev(a:arr) returns(b:arr) = {
# var idx : t := 0;
# b := b.resize(a.end,0);
# while idx < a.end
# invariant a.end = b.end & 0 <= idx & idx <= a.end
# invariant forall I. 0 <= I & I < idx -> b.value(((a.end)-I)-1) = a.value(I)
# {
# b := b.set((a.end) - idx - 1,a.value(idx));
# idx := idx + 1;
# };
# ensure b.end = a.end & forall I. 0 <= I & I < a.end
# -> b.value((a.end)-I-2) = a.value(I)
# }
#
# export rev
```

Because the loop invariant is outside the decidable fragment, Ivy gives this error message:

```
# error: The verification condition is not in the fragment FAU.
#
# An interpreted symbol is applied to a universally quantified variable:
# array_rev1.ivy: arr.end(__fml:a) - I
```

If we try to force Ivy to verify the program with the `complete=fo`

option,
it gets stuck producing a counter-model for the `ensure`

condition (notice
there is an error there!).

To avoid the use of arithmetic in defining array reversal, we can
define *relationally* what it means for one array to be the
reverse of another. That is, we will define a relation `rev(n,i,j)`

that
holds for two indices `i,j`

of an array of length `n`

, provide `i`

is
the reverse position of `j`

, that is, `i + j + 1 = n`

.

We will then prove some useful properties of `rev`

in an isolate, and
also provide a procedure that, given `n`

and `i`

, returns the `j`

such that `rev(n,i,j)`

. The proof of this isolate can be done in the
decidable fragment. For one property, however, we will need to
apply a bit of tactical theorem proving to achieve this.

The isolate hides the theory of arithmetic, allowing us to do the remainder of our reasoning without any theories. We will implement a simple array reversal procedure and prove it is correct. Then we will show that applying reversal twice to an array yields the original array.

## References

We will need the `order`

and `collections`

libraries for arrays, and
the `deduction`

library for deduction rules of first-order logic.

```
include order
include collections
include deduction
```

## Reversal permutations

To express the definition of array reversal, we start with a more
primitive notion of reversing a range of numbers. We construct an
unbounded sequence `idx`

and then define a relation `rev(U,X,Y)`

describing a
permutation on the range `[0,U)`

. That is, `rev(U,X,Y)`

holds when
`X + Y + 1 = U `

.

The definition of `rev`

and its associated properties are provided
by an isolate `rt`

(for “reversal theory”). This isolate hides the
definition of `rev`

but gives us some useful properties that will
allow us to reason about array reversal without using arithmetic.

```
instance idx : unbounded_sequence
isolate rt = {
```

The reversal permutation as a relation

```
relation rev(U : idx, X : idx, Y : idx)
```

An action that computes the reverse of an index position.

```
action reverse(x:idx,u:idx) returns (y:idx)
specification {
```

We need the following properties of the `rev`

relation:

- It is a partial function (for a given bound
`U`

) - It is monotonically decreasing over the range
`[0,U)`

- It maps
`[0..)`

onto`[0..U)`

- It is symmetric

```
property rev(U,X,Y) & rev(U,X,Y1) -> Y = Y1
property rev(U,X,X1) & rev(U,Y,Y1) & X < Y -> Y1 < X1
property rev(U,X,X1) -> 0 <= X1 & X1 < U
property rev(U,X,X1) -> rev(U,X1,X)
```

The reverse procedure returns a value that is `rev`

of the input.

```
around reverse {
require 0 <= x & x < u;
...
ensure rev(u,x,y)
}
```

Later, when we reason about double reversal, we will need one
further property, to guarantee the relation is a
permutation. We need to know that every number in the range
`[0,U)`

is related to *some* number in that range (in other
words, reversal is a bijection). We don’t want to state this
as a property, however, since it contains an unstratified
quantifier alternation (in effect, a function from `idx`

to
`idx`

). Instead, we state the property as a theorem. The
difference is that this property will not be used by
default. Rather, we will use it explicitly and specialize it
to particular values of `X`

, so that the unstratified
quantifier alternation is eliminated.

```
theorem [into] {
property 0 <= X & X < U -> exists Y. rev(U,X,Y)
}
```

The proof of this theorem requires the definition of `rev`

given below. If we try to prove it directly with Z3, however, we
get following error message:

```
# error: The verification condition is not in the fragment FAU.
#
# An interpreted symbol is applied to a universally quantified variable:
# list_reverse2.ivy: line 217: X:idx + Y
# list_reverse2.ivy: line 149: The quantified variable is Y:idx
```

In the negated VC, a property to prove occurs
negatively. Thus, the existential quantifier above becomes a
universal. When unfolding the definition of `rev`

below, we
get the interpreted arithmetic operator `+`

applied to the
universal variable `Y`

which puts us outside the decidable
fragment. In fact, if we tried to force Ivy to check this
with `complete=fo`

, we would find that Z3 produces an
inconclusive result. To avoid, this, we will apply some
tactics.

```
proof
apply introImp;
apply introE with witness = U - X - 1
```

The first step is to apply the “implication introduction” rule,
which is defined in the deduction library. This is a standard rule
of the natural deduction system which says, to prove `p -> q`

,
you need to prove `q`

from the assumption of `p`

. This converts
the proof goal `0 <= X & X < U -> exists Y. rev(U,X,Y)`

to:

```
# {
# property 0 <= X & X < U
# #----------------------
# property exists Y. rev(U,X,Y)
# }
```

To prove the existentially quantified goal, we apply the “existential
introduction” rule, giving a witness for `Y`

. In this case, the `Y`

that corresponds to `X`

under reversal is `U - X -1`

(we can get this
by solving for `Y`

in the definition of `rev`

below. This gives us the
following goal:

```
# {
# property 0 <= X & X < U
# #-----------------------
# property rev(U,X,U - X - 1)
# }
```

With the quantifier removed, expanding the definition of
`rev`

no longer gives us arithmetic over quantified
variables. This means we can leave the rest of the proof to
the default tactic using Z3. The general approach is to do
just enough tactical reasoning to eliminate the offending
quantifiers or unstratified functions and then let Z3 do the
rest of the work.

```
} # end specification section
```

The definition of `rev`

is given in the implementation section so that
the arithmetic will be hidden from users of this isolate.

```
implementation {
definition rev(U,X,Y) = (X + Y + 1 = U)
implement reverse {
y := u - x - 1
}
}
```

We say `with idx.impl`

to use the implementation of `idx`

using
the natural number theory, which is needed to prove the above
properties. Note it’s very important *not* to say `with idx`

here, since the specification of `idx`

(see order.ivy) contains
various universally quantified properties that, when mixed with
arithmetic, would put us outside the decidable fragment.

```
} with idx.impl
```

## Defining array reversal

Now that we have proved some properties of reversal permutations we
will use this notion to define reversal of an array. First, we
create a type `t`

for the elements of the array, and create an array
type indexed by type `idx`

.

```
type t
instance arr : array(idx,t)
```

The relation `arr_rev`

is true of a pair of arrays when one is the
revere of the other. This means that the have the same length and
elements that correspond according to `rev`

have equal
values. Notice that there is a quantifier alternation here, but it
is benign, since it is from `arr`

and `idx`

to `t`

.

```
relation arr_rev(A1:arr,A2:arr)
definition [def_arr_rev]
arr_rev(A1:arr,A2:arr) =
arr.end(A1) = arr.end(A2) &
forall X,Y. rt.rev(arr.end(A1),X,Y) -> arr.value(A1,X) = arr.value(A2,Y)
```

From the symmetry of `rev`

, Z3 can prove that `arr_rev`

is also symmetric:

```
property [arr_rev_symm] arr_rev(A1,A2) -> arr_rev(A2,A1)
```

## Implementing array reversal

Now that we have defined the notion of array reversal, let’s write a procedure that reverses an array a and prove that it returns the correct result.

```
isolate my_rev = {
```

The interface of our array reversal isolate has just one action
`reverse`

that returns the reverse of the array.

```
action reverse(a:arr) returns (b:arr)
```

The specification says that the output is the reverse of the input.

```
specification {
after reverse {
ensure arr_rev(old a,b);
}
}
```

The implementation just copies the array backward.

```
implement reverse {
var i : idx := 0;
b := b.resize(a.end,0);
while i < a.end
invariant a.end = b.end & 0 <= i & i <= a.end
invariant forall I. 0 <= I & I < i & rt.rev(a.end,I,J)
-> b.value(J) = a.value(I)
{
b := b.set(rt.reverse(i,a.end),a.value(i));
i := i.next;
};
}
```

The isolate needs to uses the properties of the index type and arrays, as well as the reversal permutation theory and the definition of array reversal. Probably it would have been better to organize all of these in a single object for easy reference.

```
} with idx, rt, arr, def_arr_rev
```

## Double array reversal

Now we would like to show that reversing an array twice gives the
original array. By symmetry, this is the same as saying the two
arrays `B`

and `C`

that are reversals of the same array `A`

are
equal. We will prove this in two different ways.

The first way is to start with a lemma. We’ll do this in an isolate so we won’t give unwanted help to the second proof below.

```
isolate double_reverse1 = {
```

Our lemma says that corresponding elements of `B`

and `C`

are equal.

```
property arr_rev(A,B) & arr_rev(A,C) & 0 <= X & X < arr.end(B)
-> arr.value(B,X) = arr.value(C,X)
```

To show this, we need theorem `into`

, which tells us that every
index in `B`

and `C`

corresponds to *some* index in `A`

.
The elements of `B`

and `C`

are both equal by definition to this
element of `A`

, so by transitivity they are equal to each
other. We use the `assume`

tactic to bring in theorem `into`

as
an assumption in our proof goal, plugging in the specific value
of `U`

that we need.

```
proof
assume rt.into with U = arr.end(B)
```

Here is the resulting proof goal:

```
# {
# property 0 <= X & X < arr.end(B) -> exists Y. rt.rev(arr.end(B),X,Y)
# property (arr_rev(A,B) & arr_rev(A,C) & 0:idx <= X & X < arr.end(B))
# -> arr.value(B,X) = arr.value(C,X)
# }
```

We can allow the default tactic to prove this. In this proof,
`X`

is a constant, so there is no unstratified quantifier
alternation. Notice that we cleverly stated our lemma using the
same free variable `X`

that was used in the statement of theorem
`into`

. If we had used, say, `Z`

instead, we would have had to
say `with X = Z`

to get the needed instance of `into`

.

Now with our lemma, we can prove that `B`

and `C`

are equal:

```
property arr_rev(A,B) & arr_rev(A,C) -> B = C
```

To prove this, we need the “extensionality” property of
arrays. That is, we need to know that two arrays with equal
elements are equal. This property isn’t exposed by default
because it has a quantifier alternation. We bring it in
explicitly with the `assume`

tactic. Since the statement of this
property uses array variables `X`

and `Y`

(see collections.ivy)
we need to do some substitution.

```
proof
assume arr.spec.extensionality with X = B, Y = C
```

Here is the proof goal we get:

```
# {
# {
# property arr.end(B) = arr.end(C)
# & forall I. (0:idx <= I & I < arr.end(B)) -> arr.value(B,I) = arr.value(C,I))
# property B:arr = C
# }
# property (arr_rev(A,B) & arr_rev(A,C)) -> B = C
# }
```

Our lemma says the corresponding elements of `B`

and `C`

are
equal, so by extensionality, `B`

and `C`

are equal. There is a
quantifier alternation from `arr`

to `idx`

in the extensionality
property, but it doesn’t break stratification, so the default
tactic can handle this goal.

Since this isolate needs all of the previously establish theory, we
say `with this`

, which means “use the properties exposed in the global
scope”. Notice, though that we do not use any arithmetic, since the
definition of `rev`

is hidden by isolate `rt`

.

```
} with this
```

### A different proof

Now we will prove the same property a second time, but using more tactics and less Z3.

```
isolate double_reverse2 = {
property arr_rev(A,B) & arr_rev(A,C) -> B = C
```

The proof begins by applying “implication introduction” to move
`arr_rev(A,B) & arr_rev(A,C)`

into the assumptions [1]. Then we can
apply array extensionality, since its conclusion `X=Y`

matches our
conclusion `B = C`

. Ivy finds this match automatically, so we don’t
need a `with`

clause [2].

```
proof
apply introImp; # [1]
apply arr.spec.extensionality; # [2]
```

We now have the following proof goal:

```
# theorem [arr.spec.prop64] {
# property [prop183] (arr_rev(A,B) & arr_rev(A,C))
# property arr.end(B) = arr.end(C)
# & forall I. (0:idx <= I & I < arr.end(B)) -> arr.value(B,I) = arr.value(C,I)
# }
```

That is, we need to prove a conjunction of two facts: the array lengths are equal and
the array contents are equal. We apply the “and introduction” rule to break this into
two separate goals [3], and then we use `defergoal`

to defer the first case, since the
default tactic can handle it [4].

```
apply introAnd; # [3]
defergoal; # [4]
```

Now we need to hand the universal quantifier. We apply the “forall introduction” rule,
which requires us to prove the quantified fact for a generic `x`

[5]. This particular index
`x`

in `B`

and `C`

corresponds to some index in `A`

, which we can establish by assuming
the `into`

theorem for `x`

[6].

```
apply introA; # [5]
assume rt.into with U = arr.end(B), X = x # [6]
```

This leaves us with the following goals (the second being the one we deferred):

```
# {
# property arr_rev(A,B) & arr_rev(A,C)
# individual x : idx
# property (0:idx <= x & x < arr.end(B)) -> exists Y. rev(arr.end(B),x,Y)
# property (0:idx <= x & x < arr.end(B)) -> arr.value(B,x) = arr.value(C,x)
# }
# {
# property [prop183] (arr_rev(A,B) & arr_rev(A,C))
# property arr.end(B) = arr.end(C)
# }
```

The default tactic can handle these, since there are no unstratified quantifier alternations.

```
} with this
```

## Implementing double array reversal

Now let’s implement double array reversal and use our theorem to show that it in fact returns its argument.

```
isolate my_double_rev = {
action reverse2(x:arr) returns (x:arr)
```

The specification of reverse2 just says that its output equals its input.

```
specification {
after reverse2 {
ensure x = old x
}
}
```

The implementation just calls `rev`

twice.

```
implementation {
implement reverse2 {
x := my_rev.reverse(x);
x := my_rev.reverse(x);
}
}
```

For the verification we need to use the spec of `my_rev`

, the
symmetry of array reversal and the double reverse property. The
rest of the forgoing theory is unnecessary.

```
} with my_rev, arr_rev_symm, double_reverse1
```

## A more efficient reversal

This version of array reversal reverses the array in place, by swapping elements.

```
isolate my_rev2 = {
```

The interface of our array reversal isolate has just one action
`reverse`

that returns the reverse of the array, but notice the
input and output are both variable `a`

, meaning the compiler
can optimize this action to modify `a`

in place.

```
action reverse(a:arr) returns (a:arr)
```

The specification says that the output is the reverse of the input.

```
specification {
after reverse {
ensure arr_rev(old a,a);
}
}
```

We swap elements until we reach the middle of the array.
The variables `i`

and `j`

hold the indices that we are about to swap.
The invariant of the loop says that the elements in the range `[i,j]`

are unchanged, while the elements outside this range are reversed.

```
implement reverse {
if a.end > 0 {
var i : idx := 0;
var j := rt.reverse(i,a.end);
while i < j
invariant 0 <= i & i <= a.end & a.end = (old a).end & rt.rev(a.end,i,j)
invariant forall I. (0 <= I & I < i | j < I & I < a.end)
& rt.rev(a.end,I,J)-> a.value(J) = (old a).value(I)
invariant i <= I & I <= j -> a.value(I) = (old a).value(I)
{
var tmp := a.value(j);
a := a.set(j,a.value(i));
a := a.set(i,tmp);
i := i.next;
j := rt.reverse(i,a.end);
};
}
}
} with idx, rt, arr, def_arr_rev
```

Finally, to actually verify the executable code, we export actions to the environment.

```
export my_rev.reverse
export my_double_rev.reverse2
export my_rev2.reverse
```