Induction, Recursion, Iteration

From Specification to Implementation

Let’s review the material from last week, linking induction, recursion and iteration.

We have developed a correct implementation in three steps.

  1. Describe the mathematical concepts necessary to express required program properties
  2. Create a (recursive) specification that is easy to understand (and validate)
  3. Implement an efficient solution for the specification

We document clearly what is implemented by specifying it in mathematical terms. We document clearly why the implementation is correct by supplying a proof. Instead of a proof we also accept other evidence of correctness such as successful tests.

Today we make preparations for systematic testing of loops and recursive functions.

But first let’s refresh our memory of induction, recursion and iteration.

Example: Multiplication by Repeated Addition

We can express multiplication of two natural numbers by repeated addition. m * n = (n + n + ... + n) <- n times The repeated addition on the right-hand side can be expressed recursively.

0 * n = 0  
m' n = m * n + n

Using the recursive definition of multiplication we can calculate:

3 * 1 
= 2 * 1 + 1  
= 1 * 1 + 1 + 1
= 0 * 1 + 1 + 1 + 1  
= 0 + 1 + 1 + 1

Let’s specify it in Slang

Multiplication by Repeated Addition in Slang

We specify mult_spec using the inductive definition of the natural numbers.

1
2
3
4
@strictpure def mult_spec(m: Z, n: Z): Z = m match {
  case 0 => 0
  case k => mult_spec(k - 1, n) + n
}

where k - 1 denotes the predecessor of natural number k.

Induction rules for mult_spec:

Base case (m == 0):

1
2
3
4
5
@pure def mult_spec_0(n: Z) {
  Contract(
    Ensures(mult_spec(0, n) == 0)
  )
}

Inductive case (m > 0):

1
2
3
4
5
6
@pure def mult_spec_step(m: Z, n: Z) {
  Contract(
    Requires(m > 0),
    Ensures(mult_spec(m, n) == mult_spec(m - 1, n) + n)
  )
}

Logika applies these rules automatically.

Program Specification

We can write a specification for a multiplication function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@pure def mult_rec(m: Z, n: Z): Z = {
  Contract(
    Requires(m >= 0),
    Ensures(Res == mult_spec(m, n))
  )
  if (m == 0) {
    return 0
  } else {
    return mult_rec(m - 1, n) + n
  }
}

Of course, in this simple example the structure of the recursive specification resembles closely that of the mathematical definition. As a consequence, Logika proves the post-condition fully-automatically.

Program Implementation

We can implement the program using a while loop.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def mult_it(m: Z, n: Z): Z = {
  Contract(
    Requires(m >= 0),
    Ensures(Res == mult_rec(m, n))
  )
  var i: Z = m
  var k: Z = 0
  while (i > 0) {
    Invariant(
    	...
    )
    ...
  }
  return k
}

where variables i and k are modified until k contains the product.

Exercise 1

  1. Implement function mult_it
  2. Formulate an invariant
    • Hint: Use backward conjecture to find a candidate for the invariant
  3. Insert deductions that document why the program is correct
  4. Prove and document that the function terminates
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def mult_it(m: Z, n: Z): Z = {
  Contract(
    Requires(m >= 0),
    Ensures(Res == mult_rec(m, n))
  )
  var i: Z = m
  var k: Z = 0
  while (i > 0) {
    Invariant(
    	...
    )
    ...
  }
  return k
}

Recursion Unfolding

Example: Counting Down Recursively

We can specify counting down recursively as follows.

1
2
3
4
5
cd(k) =
    if k == 0
       k
    else
       cd(k-1)

We can calculate cd(2) observing the value of the parameter k at each invocation.

1
2
3
4
5
6
7
cd(2)
    { k == 2 and k != 0 }
= cd(2 - 1)
    { k == 2 - 1 and k != 0}
= cd(2 - 1 - 1)
    { k == 2 - 1 - 1 and k == 0 }
= 0

Let’s rename k at each invocation to clarify what’s going on.

We rename k into k0, k1, k2, ... counting upwards.

1
2
3
4
5
6
7
cd(2)
    { k0 == 2 and k0 !=0 }
= cd(2 - 1)
    { k1 == 2 - 1 and k1 != 0 }
= cd(2 - 1 - 1 - 1)
    { k2 == 2 - 1 - 1 and k2 == 0 }
= 0

Now, let’s replace sub-expressions by the names of the parameters holding those values.

Using k0, k1, k2 we get:

1
2
3
4
5
6
7
cd(k0)
    { k0 == 2 and k0 != 0 }
= cd(k0 - 1)
    { k1 == k0 - 1 and k1 != 0 }
= cd(k1 -1)
    { k2 == k1 - 1 and k2 == 0 }
= k2

Only focusing on the value of the parameter and ignoring the initial value 2, we observe:

1
2
3
k0 != 0
k1 == k0 - 1 and k1 != 0
k2 == k1 - 1 and k2 == 0

Recursively Unfolding Counting Down

The observation

1
2
3
k0 != 0
k1 == k0 - 1 and k1 != 0
k2 == k1 - 1 and k2 == 0

describes the computation starting with the call cd(2) in terms of the parameter values. Note, the final k2 == 0 which determines that the first branch is chosen and k2 is returned. We can read the function definition as an equation.

cd(k) == if(k == 0) k else cd(k - 1), (FP1)

Using lambda notation,

cd == λk * if(k == 0) k else cd(k - 1), (FP2)

Theses two equations are called a fix-point equations. Replacing the left-hand side by the right-hand side in either (FP1) or (FP2) is called unfolding. Let’s consider (FP2) first and then apply what we’ve learned to (FP1).

Recursively Unfolding Lambda Using (FP2)

Unfolding is a calculation that the function itself as a value.

cd  
= λk · if (k == 0) k else cd(k  1)  
= λk · if (k == 0) k else (λk · if (k == 0) k else cd(k  1))(k  1)    
= λk · if (k == 0) k else (λk · if (k == 0) k else (λk · if (k == 0) k else cd(k  1))(k  1))(k  1)  
  • Let’s colour the different k’s bound by the lambdas

cd2 = λk · if (k == 0) k else (λk · if (k == 0) k else (λk · if (k == 0) k else cd(k − 1))(k − 1))(k − 1)

  • Let’s call this function cd2
  • Now let k0, k1 == k0 − 1, k2 == k1 − 1 be given, and calculate
cd2(k0)  
= if (k0 == 0) k0 else (λk · if (k == 0) k else (λk · if (k == 0) k else cd(k − 1))(k − 1))(k0 − 1)  
= if (k0 == 0) k0 else (λk · if (k == 0) k else (λk · if (k == 0) k else cd(k − 1))(k − 1))(k1)  
= if (k0 == 0) k0 else if (k1 == 0) k1 else (λk · if (k == 0) k else cd(k − 1))(k1 − 1)  
= if (k0 == 0) k0 else if (k1 == 0) k1 else (λk · if (k == 0) k else cd(k − 1))(k2)  
= if (k0 == 0) k0 else if (k1 == 0) k1 else if (k2 == 0) k2 else cd(k2 − 1)  
  • Let’s compare this to our initial observation for the computation of cd(2)

Recursive Unfolding Vs Direct Calculation

Given k0, k1 == k0 − 1, k2 == k1 − 1, we have
if (k0 == 0) k0 else if (k1 == 0) k1 else if (k2 == 0) k2 else cd(k2 − 1) (1)
The observation

k0 != 0  
k1 == k0 − 1 and k1 != 0  
k2 == k1 − 1 and k2 == 0  

describes the situation where expression (1) returns k2
This is the case when k0 == 2
In other words, when cd(2) is called
Next let’s consider the fix-point equation cd(k) == if (k == 0) k else cd(k − 1)
We begin by unfolding it

Unfolding with Parameters using (FP1)

Using cd(k) == if (k == 0) k else cd(k − 1), we calculate

cd(k0)  
= if (k0 == 0) k0 else cd(k0 − 1)
  • Letting k1 == k0 − 1
= if (k0 == 0) k0 else cd(k1)  
= if (k0 == 0) k0 else if (k1 == 0) k1 else cd(k1 − 1)
  • Letting k2 == k1 − 1
= if (k0 == 0) k0 else if (k1 == 0) k1 else cd(k2)  
= if (k0 == 0) k0 else if (k1 == 0) k1 else if (k2 == 0) k2 else cd(k2 − 1)

Fix-point equation version (FP2) describes a function as its solution
This function can be used to observe computations via unfolding
Fix-point equation version (FP1) can be used directly for unfolding and observation
It hides the steps involving lambda abstraction and application
To keep track of consecutive parameter values we introduce new variables at each call

Unfolded Recursive Programs as Facts

Let’s state the expression if (k0 == 0) k0 else if (k1 == 0) k1 else if (k2 == 0) k2 else cd(k2 − 1) as a statement where the result value is assigned to a variable Res

if (k0 == 0)  
Res = k0        (k0 == 0 => Res == k0) &                                call cd(0)
else            (k0 != 0 => k1 == k0 − 1) &
if (k1 == 0)
Res = k1        (k0 != 0 & k1 == 0 => Res == k1) &                      call cd(1)
else            (k0 != 0 & k1 != 0 => k2 == k1 − 1) &
if (k2 == 0)
Res = k2        (k0 != 0 & k1 != 0 & k2 == 0 => Res = k2)
else            &
Res = cd(k2 − 1)(k0 != 0 & k1 != 0 & k2 != 0 => Res == cd(k2 − 1))

Within the fact for the unfolded function we also discover our original observation for cd(2) The two shorter cases deal with the calls cd(0) and cd(1)

Slang Examples: Counting Down and the Factorial Function

The count-down function in Slang:

1
2
3
4
5
6
7
@pure def count0(k: Z): Z = {
  if (k == 0) {
    return k
  } else {
    return count0(k - 1)
  }
}

With a separate function specifying its correctness:

1
2
3
4
5
6
@pure def count0_0(k: Z): Unit = {
  Contract(
    Requires(k >= 0),
    Ensures(count0(k) == 0)
  )
}

We can unfold function count0 in Slang. We do it within the body of the function.

The function itself:

1
2
3
4
5
6
7
@pure def count0(k0: Z): Z = {
  if (k0 == 0) {
    return k0
  } else {
    return count0(k0 - 1)
  }
}

First Unfolding:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@pure def count0(k0: Z): Z = {
  if (k0 == 0) {
    return k0
  } else {
    k1 = k0 - 1
    if (k1 == 0) {
      return k1
    } else {
      return count0(k1 - 1)
    }
  }
}

Second Unfolding:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@pure def count0(k0: Z): Z = {
  if (k0 == 0) {
    return k0
  } else {
    k1 = k0 - 1
    if (k1 == 0) {
      return k1
    } else {
      k2 = k1 - 1
      if (k2 == 0) {
        return k2
      } else {
        return count0(k2 - 1)
      }
    }
  }
}

We can see the effect of recursive unfolding in Slang. It occurs when inter-procedural check is chosen.

Recursive Counting Down and Unfolding in Logika

Let’s inter-procedurally check the post-condition count0(k) == 0.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@pure def count0(k: Z): Z = {
  if (k == 0) {
    return k
  } else {
    return count0(k - 1)
  }
}

@pure def count0_0(k: Z): Unit = {
  Contract(
    Requires(k >= 0),
    Ensures(count0(k) == 0)
  )
}

of function count0_0.

Rec-Interproc-1

Rec-Interproc-2

Rec-Interproc-3

Rec-Interproc-4

Rec-Interproc-5 Unfolded if-branch

Rec-Interproc-6 Unfolded else-branch

Exercise 2: Recursive Factorial Unfolding

Unfold function fac_rec two times. Write down the fact for the unfolded function. Inter-procedurally check the post-condition fac_rec(n) == fac_rec_spec(n).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@pure def fac_rec(n: Z): Z = {
  if (n == 0) {
    return 1
  } else {
    return n * fac_rec(n - 1)
  }
}

@pure def fac_rec_lemma(n: Z) {
  Contract(
    Requires(n >= 0),
    Ensures(fac_rec(n) == fac_rec_spec(n))
  )
}

of function fac_rec_lemma.

Iteration Unfolding

Example: Counting Down Iteratively

We can specify counting down recursively as follows.

cd(k) =
  m = k
  while m > 0
    m = m  1
  m

Where the tailing m is the returned result. We can calculate cd(2) observing the value of the local variable $m$ at each iteration.

cd(2)
  { m == 2 and m > 0 }
  { m == 2  1 and m > 0 }
  { m == 2  1  1 and m <= 0 }
= 0

It would be convenient if we could observe iterative programs similarly to recursive programs. Recall the similarity between tail-recursion and while-loops.

We rename k into m0, m1, m2, . . . counting upwards.

cd(2)
  { m0 == 2 and m0 > 0 }
  { m1 == 2  1 and m1 > 0 }
  { m2 == 2  1  1 and m2 <= 0 }
= 0

and replace sub-expressions by variable names.

cd(2)
  { m0 == 2 and m0 > 0 }
  { m1 == m0  1 and m1 > 0 }
  { m2 == m1  0 and m2 <= 0 }
= 0

This is exactly the same pattern we have observed for recursion. Let’s look for a fix-point equation.

We focus on the iterative part of the body of function cd.

m = k
while m > 0 
  m = m  1

To observe one step of the execution of the loop we consider the following. If the condition m > 0 is true, we execute the loop body and the execute the loop again. m = m − 1; while (m > 0) m = m − 1 If the condition is false, the loop is exited (and the statement following the loop may be executed). The above describes a conditional with an empty else-branch.
We have (in italics): while (m > 0) m = m − 1 == if (m > 0) { m = m − 1; while (m > 0) m = m − 1 } (FP3) The loop is a solution of fix-point equation (FP3). We can use it for unfolding while-loops.

Loop Unfolding using (FP3)

Using while (m > 0) m = m − 1 == if (m > 0) { m = m − 1; while (m > 0) m = m − 1 }, abbreviating while (m > 0) m = m − 1 with W, we calculate.

while (m > 0) m = m − 1  
= if (m > 0) { m = m − 1; W }  
= if (m > 0) { m = m − 1; if (m > 0) { m = m − 1; W } }  
= if (m > 0) { m = m − 1; if (m > 0) { m = m − 1; if (m > 0) { m = m − 1; W } } }  

More readable this is           . . . and as a fact  
if (m > 0)                      (m0 <= 0 => m == m0) &
  m = m − 1                     (m0 > 0 => m1 == m0 − 1) &  
    if (m > 0)                  (m0 > 0 & m1 <= 0 => m == m1) &  
      m = m − 1                 (m0 > 0 & m1 > 0 => m2 = m1 − 1) &  
        if (m > 0)              (m0 > 0 & m1 > 0 & m1 <= 0 => m == m2) &  
          m = m − 1             . . .
          W

The complete body of the loop unfolded twice:

m0 == k  
(m0 <= 0 => m == m0) &  
(m0 > 0 => m1 == m0 − 1) &  
(m0 > 0 & m1 <= 0 => m == m1) &  
(m0 > 0 & m1 > 0 => m2 = m1 − 1) &  
(m0 > 0 & m1 > 0 & m1 <= 0 => m == m2) &  
Res == m

Note the similarity of the structure of the formula. with respect to the variables m0, m1, m2 in the iterative case. and the variables k0, k1, k2 in the recursive case. In the iterative case the variables occur as a consequence of consecutive assignments. In the recursive case they occur as a consequence of consecutive parameter passing.

Unfolding the Iterative Slang Counting Down Function

The function itself:

1
2
3
4
5
6
7
@pure def while0(k: Z): Z = {
  var m: Z = k
  while (m > 0) {
    m = m - 1
  }
  return m
}

First Unfolding:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@pure def while0(k: Z): Z = {
  var m: Z = k
  if (m > 0) {
    m = m - 1
    while (m > 0) {
      m = m - 1
    }
  }
  return m
}

Second Unfolding:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@pure def while0(k: Z): Z = {
  var m: Z = k
  if (m > 0) {
    m = m - 1
    if (m > 0) {
      m = m - 1
      while (m > 0) {
        m = m - 1
      }
    }
  }
  return m
}

We can see the effect of recursive unfolding in Slang. It occurs when inter-procedural check is chosen.

Recursive Counting Down and Unfolding in Logika

Let’s inter-procedurally check the post-condition count0(k) == 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@pure def while0(k: Z): Z = {
  var m: Z = k
  while (m > 0) {
    m = m - 1
  }
  return m
}

@pure def while0_0(k: Z) {
  Contract(
    Requires(k >= 0),
    Ensures(while0(k) == 0)
  )
}

of function while0_0.

It-Interproc-1

It-Interproc-2

It-Interproc-3

It-Interproc-4

It-Interproc-5

It-Interproc-6 Second Unfolding

Exercise 3: Iterative Factorial Unfolding

  1. Unfold the loop of the function fac_it two times
  2. Write down the fact for the unfolded function
  3. Inter-procedurally check the post-condition fac_it(n) == fac_rec(n)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@pure def fac_it(n: Z): Z = {
  var x: Z = 1
  var m: Z = 0;
  while (m < n) {
    m = m + 1
    x = x * m
  }
  return x
}
  
@pure def fac_it_rec_lemma(n: Z) {
  Contract(
    Requires(n >= 0),
    Ensures(fac_it(n) == fac_rec(n))
  )
}

of function fac_it_rec_lemma.

Symbolic Execution with Unfolding

Symbolic Execution with Recursion

1
2
3
4
5
6
7
def count0(k: Z): Z = {
  if (k == 0) {
    return k // Res = k
  } else {
    return count0(k - 1)
  }
}
  • Executing count0(k) yields (k: K0), (PC: true)

  • Executing if (k == 0) { yields (k: K0), (PC: K0 == 0)

  • Executing return k yields (k: K0, Res: K0), (PC: K0 == 0)

  • Executing count0(k) yields (k: K0), (PC: true)

  • Executing } else { yields (k: K0), (PC: K0 != 0)

  • Executing return count0(k - 1) yields (k: K1), (PC: K0 != 0, K1 == K0 - 1)

  • Executing if (k == 0) { yields (k: K1), (PC: K0 != 0, K1 == K0 - 1, K1 == 0)

  • Executing return k yields (k: K1, Res: K1), (PC: K0 != 0, K1 == K0 - 1, K1 == 0)

Symbolic Execution with Iteratio

1
2
3
4
5
6
7
def while0(k: Z): Z = {
  var m: Z = k
  while (m > 0) {
    m = m - 1
  }
  return m // Res = m
}
  • Executing while0(k) yields (k: K), (PC: true)

  • Executing var m: Z = k yields (k: K, m: K), (PC: true)

  • Executing } yields (k: K, m: K), (PC: K <= 0)

  • Executing return m yields (k: K, m: K, Res: K), (PC: K <= 0)

  • Executing while0(k) yields (k: K), (PC: true)

  • Executing var m: Z = k yields (k: K, m: K), (PC: true)

  • Executing while (m > 0) { yields (k: K, m: K), (PC: K > 0)

  • Executing m = m - 1 yields (k: K, m: M1), (PC: K > 0, M1 > K - 1)

  • Executing } yields (k: K, m: M1), (PC: K > 0, M1 > K - 1, M1 <= 0)

  • Executing return m yields (k: K, m: M1, Res: M1), (PC: K > 0, M1 > K - 1, M1 <= 0)

Slang Examples

Count Int Loop Rec

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// #Sireum #Logika
import org.sireum._

@pure def while0(k: Z): Z = {
  var m: Z = k
  while (m > 0) {
    m = m - 1
  }
  return m
}

@pure def while0_0(k: Z) {
  Contract(
    Requires(k >= 0),
    Ensures(while0(k) == 0)
  )
}

@pure def count0(k: Z): Z = {
  if (k == 0) {
    return k
  } else {
    return count0(k - 1)
  }
}

@pure def count0_0(k: Z) {
  Contract(
    Requires(k >= 0),
    Ensures(count0(k) == 0)
  )
}

Fac Function Loop Rec Unfolding

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// #Sireum #Logika
import org.sireum._

  
  // mathematically oriented specification of the factorial function
  @strictpure def fac_rec_spec(n: Z): Z = n match {
    case 0 => 1
    case m => m * fac_rec_spec(m - 1)
  }

  // recursive implementation of the factorial function
  @pure def fac_rec(n: Z): Z = {
    // no contract: we will instead verify/test using a refinement approach
    if (n == 0) {
      return 1
    } else {
      return n * fac_rec(n - 1)
    }
  }

  // iterative/imperative implementation of the factorial function
  @pure def fac_it(n: Z): Z = {
    // no contract: we will instead verify/test using a refinement approach
    var x: Z = 1
    var m: Z = 0;
    while (m < n) {
      m = m + 1
      x = x * m
    }
    return x
  }

  // Refinement specification:
  //  For all n >= Z,
  //   fac_it(n) refines (conforms to) fac_rec_spec(n)
  //  i.e.,
  //    fac_it(n) == fac_rec_spec(n)
  @pure def fac_imp_spec_refinement(n: Z) {
    Contract(
      Requires(n >= 0)
    )
    assert(fac_it(n) == fac_rec_spec(n))
  }

  // Refinement specification:
  //  For all n >= Z,
  //   fac_it(n) refines (conforms to) fac_rec(n)
  //  i.e.,
  //    fac_it(n) == fac_rec(n)
  @pure def fac_imp_rec_refinement(n: Z) {
    Contract(
      Requires(n >= 0)
    )
    assert(fac_it(n) == fac_rec(n))
  }

  // Refinement specification:
  //  For all n >= Z,
  //   fac_rec(n) refines (conforms to) fac_rec_spec(n)
  //  i.e.,
  //    fac_it(n) == fac_rec(n)
  @pure def fac_rec_spec_refinement(n: Z) {
    Contract(
      Requires(n >= 0)
    )
    assert(fac_rec(n) == fac_rec_spec(n))
  }

Mult Add Rex It

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// #Sireum #Logika
import org.sireum._

// Inductive definition of multiplication

@strictpure def mult_spec(m: Z, n: Z): Z = m match {
  case 0 => 0
  case k => mult_spec(k - 1, n) + n
}

// Induction rules

@pure def mult_spec_0(n: Z) {
  Contract(
    Ensures(mult_spec(0, n) == 0)
  )
}

@pure def mult_spec_step(m: Z, n: Z) {
  Contract(
    Requires(m > 0),
    Ensures(mult_spec(m, n) == mult_spec(m - 1, n) + n)
  )
  Deduce(|- (m != 0))
}

@pure def mult_rec(m: Z, n: Z): Z = {
  Contract(
    Requires(m >= 0),
    Ensures(Res == mult_spec(m, n))
  )
  if (m == 0) {
    return 0
  } else {
    return mult_rec(m - 1, n) + n
  }
}

def mult_it(m: Z, n: Z): Z = {
  Contract(
    Requires(m >= 0),
    Ensures(Res == mult_rec(m, n))
  )
  var i: Z = m
  var k: Z = 0
  Deduce(|- (k == mult_rec(m - i, n)))
  while (i > 0) {
    Invariant(
      Modifies(k, i),
      (i >= 0),
      (k == mult_rec(m - i, n))
    )
    Deduce(|- (k == mult_rec(m - i, n)))
    Deduce(|- (k + n == mult_rec(m - i, n) + n))
    Deduce(|- (k + n == mult_rec(m - i + 1, n)))
    Deduce(|- (k + n == mult_rec(m - (i - 1), n)))
    k = k + n
    Deduce(|- (k == mult_rec(m - (i - 1), n)))
    i = i - 1
    Deduce(|- (k == mult_rec(m - i, n)))
  }
  Deduce(|- (i <= 0))
  Deduce(|- (i == 0))
  Deduce(|- (k == mult_rec(m - i, n)))
  Deduce(|- (k == mult_rec(m - 0, n)))
  Deduce(|- (k == mult_rec(m, n)))
  return k
}

def mult_it_term(m: Z, n: Z): Z = {
  Contract(
    Requires(m >= 0),
    Ensures(Res == mult_rec(m, n))
  )
  var i: Z = m
  var k: Z = 0
  while (i > 0) {
    // decreases i
    Invariant(
      Modifies(k, i),
      (i >= 0),
      (k == mult_rec(m - i, n))
    )
    val measure_i_pre = i
    Deduce(|- (measure_i_pre >= 0))
    k = k + n
    i = i - 1
    val measure_i_post = i
    Deduce(|- (measure_i_post < measure_i_pre))
  }
  return k
}

Summary

  • We have reviewed development and verification methodology for Slang programs
  • We have looked at unfolding of recursive functions
  • We have looked at unfolding of while-loops
  • We have considered fix-points that provide a justification for unfolding
  • We have looked at symbolic execution of recursive functions
  • We have looked at symbolic execution of while-loops