A brief review of Lecture 16.
Induction$\rule{P(0); \forall k. P(k)\rightarrow P(k+1)}{\forall n. P(n)}$
Induction with a stronger hypothesis.
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(5)$ 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)$ |
$\vdots$ | $\ \Downarrow_{\ P(k)\rightarrow P(k+1)}$ | |
11. | Applying Modus Ponens to 9 and 10, we get $P(5)$. | $P(5)$ |
Note that we have $P(0), \ldots, P(k)$ when proving $k+1$.
So we can safely assume all of them, rather than just $P(k)$.
Strong Induction$\rule{P(0); \forall k. (P(0)\wedge P(1)\wedge\ldots\wedge P(k))\rightarrow P(k+1)}{\forall n. P(n)}$
Domain: $\N$.
An example proof and when to use strong induction.
We use strong induction to prove that a factorization into primes exists (but not that it is unique).
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 will use strong induction to reason about this algorithm and other functions with recursive definitions.
Recursive function definitions and examples.
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!