Topics and format.
You’ve solved similar problems on homeworks and in sections.
Do the easy parts of all the problems first. Those are worth most points. So, if you don’t get to the hard parts, e.g., establishing $P(k+1)$ after invoking the hypothesis, you’ll still get a lot of credit!
A brief review of Lecture 17.
Induction$\rule{P(0); \forall k. P(k)\rightarrow P(k+1)}{\forall n. P(n)}$
Domain: natural numbers ($\N$).
How do we get $P(3)$ from $P(0)$ and $\forall k. P(k)\rightarrow P(k+1)$?
1. | First, we have $P(0)$. | $P(0)$ |
2. | Since $P(k)\rightarrow P(k+1)$ for all $k$, we have $P(0)\rightarrow P(1)$. | $\ \Downarrow_{\ P(0)\rightarrow P(1)}$ |
3. | Applying Modus Ponens to 1 and 2, we get $P(1)$. | $P(1)$ |
4. | Since $P(k)\rightarrow P(k+1)$ for all $k$, we have $P(1)\rightarrow P(2)$. | $\ \Downarrow_{\ P(1)\rightarrow P(2)}$ |
5. | Applying Modus Ponens to 3 and 4, we get $P(2)$. | $P(2)$ |
6. | Since $P(k)\rightarrow P(k+1)$ for all $k$, we have $P(2)\rightarrow P(3)$. | $\ \Downarrow_{\ P(2)\rightarrow P(3)}$ |
7. | Applying Modus Ponens to 6 and 7, we get $P(3)$. | $P(3)$ |
Note that we have $P(0), \ldots, P(k)$ when proving $k+1$.
So we can safely assume $P(0) \wedge \ldots \wedge P(k)$, rather than just $P(k)$.
$\rule{P(b); \forall k. (P(b)\wedge P(b+1)\wedge\ldots\wedge P(k))\rightarrow P(k+1)}{\forall n \geq b. P(n)}$
We need to reason about procedures that given an input $k$ invoke themselves recursively on an input different from $k-1$.
// Assumes a >= b >= 0.
public static int gcd(int a, int b) {
if (b == 0)
return a; // GCD(a, 0) = a
else
return gcd(b, a % b); // GCD(a, b) = GCD(b, a mod b)
}
We use strong induction to reason about this algorithm and other functions with recursive definitions.
Recursive function definitions and example proofs.
When the recursive case refers only to $f(n)$, as in these examples, we can prove properties of $f(n)$ easily using ordinary induction.
Prove $n! \leq n^n$ for $n\in\N$ with Dafny:
// x^y where 0^0 = 1
function expt(x : nat, y: nat) : nat {
if y == 0 then 1 else x * expt(x, y-1)
}
// n!
function fact(n : nat) : nat {
if n == 0 then 1 else n * fact(n-1)
}
// n! <= n^n for all natural numbers
lemma factLemma(n : nat)
ensures fact(n) <= expt(n, n)
{ }
Dafny can’t prove this theorem because the proof involves several steps that are too difficult for Dafny to discover on its own.
Really prove $n! \leq n^n$ for $n\in\N$ with Dafny:
// x^y where 0^0 = 1
function expt(x : nat, y: nat) : nat {
if y == 0 then 1 else x * expt(x, y-1)
}
// n!
function fact(n : nat) : nat {
if n == 0 then 1 else n * fact(n-1)
}
// n! <= n^n for all natural numbers
lemma factLemma(n : nat)
ensures fact(n) <= expt(n, n)
{
if (n == 0) { // Base case
assert fact(0) <= expt(0, 0);
} else { // Inductive step
factLemma(n-1); // Inductive hypothesis
exptLemma(n-1, n-1); // (n-1)^(n-1) <= n^(n-1)
assert fact(n) == n * fact(n-1); // by fact defn
assert n * fact(n-1) <= n * expt(n-1, n-1); // by IH
assert n * expt(n-1, n-1) <= n * expt(n, n-1); // by exptLemma
assert fact(n) <= expt(n, n); // qed.
}
}
// x^y <= (x+1)^y for all natural numbers.
lemma exptLemma(x: nat, y: nat)
ensures expt(x, y) <= expt(x + 1, y)
{}
When the recursive function has multiple base cases, we use strong induction to prove its properties. And we also extend the strong induction proof template to account for the additional base cases.
Prove $f_n < 2^n$ for $n\geq 0$ with Dafny:
// 2^n
function pow2(n : nat) : nat {
if n == 0 then 1 else 2 * pow2(n-1)
}
// Fibonacci function f_n
function fib(n: nat): nat {
if n == 0 then 0
else if n == 1 then 1
else fib(n-2) + fib(n-1)
}
// f_n < 2^n
lemma fibLemma(n : nat)
ensures fib(n) < pow2(n)
{ }
Yes, Dafny can prove this theorem automatically!
Recursive definitions of sets.
The exclusion rule is assumed, so no need to state it explicitly.