mathlib3 documentation

computability.ackermann

Ackermann function #

THIS FILE IS SYNCHRONIZED WITH MATHLIB4. Any changes to this file require a corresponding PR to mathlib4.

In this file, we define the two-argument Ackermann function ack. Despite having a recursive definition, we show that this isn't a primitive recursive function.

Main results #

Proof approach #

We very broadly adapt the proof idea from https://www.planetmath.org/ackermannfunctionisnotprimitiverecursive. Namely, we prove that for any primitive recursive f : ℕ → ℕ, there exists m such that f n < ack m n for all n. This then implies that λ n, ack n n can't be primitive recursive, and so neither can ack. We aren't able to use the same bounds as in that proof though, since our approach of using pairing functions differs from their approach of using multivariate functions.

The important bounds we show during the main inductive proof (exists_lt_ack_of_primrec) are the following. Assuming ∀ n, f n < ack a n and ∀ n, g n < ack b n, we have:

The last one is evidently the hardest. Using nat.unpair_add_le, we reduce it to the more manageable

We then prove this by induction on n. Our proof crucially depends on ack_mkpair_lt, which is applied twice, giving us a constant of 4 + 4. The rest of the proof consists of simpler bounds which bump up our constant to 9.

def ack  :

The two-argument Ackermann function, defined so that

This is of interest as both a fast-growing function, and as an example of a recursive function that isn't primitive recursive.

Equations
@[simp]
theorem ack_zero (n : ) :
ack 0 n = n + 1
@[simp]
theorem ack_succ_zero (m : ) :
ack (m + 1) 0 = ack m 1
@[simp]
theorem ack_succ_succ (m n : ) :
ack (m + 1) (n + 1) = ack m (ack (m + 1) n)
@[simp]
theorem ack_one (n : ) :
ack 1 n = n + 2
@[simp]
theorem ack_two (n : ) :
ack 2 n = 2 * n + 3
@[simp]
theorem ack_three (n : ) :
ack 3 n = 2 ^ (n + 3) - 3
theorem ack_pos (m n : ) :
0 < ack m n
theorem one_lt_ack_succ_left (m n : ) :
1 < ack (m + 1) n
theorem one_lt_ack_succ_right (m n : ) :
1 < ack m (n + 1)
theorem ack_mono_right (m : ) :
@[simp]
theorem ack_lt_iff_right {m n₁ n₂ : } :
ack m n₁ < ack m n₂ n₁ < n₂
@[simp]
theorem ack_le_iff_right {m n₁ n₂ : } :
ack m n₁ ack m n₂ n₁ n₂
@[simp]
theorem ack_inj_right {m n₁ n₂ : } :
ack m n₁ = ack m n₂ n₁ = n₂
theorem max_ack_right (m n₁ n₂ : ) :
ack m (linear_order.max n₁ n₂) = linear_order.max (ack m n₁) (ack m n₂)
theorem add_lt_ack (m n : ) :
m + n < ack m n
theorem add_add_one_le_ack (m n : ) :
m + n + 1 ack m n
theorem lt_ack_left (m n : ) :
m < ack m n
theorem lt_ack_right (m n : ) :
n < ack m n
theorem ack_strict_mono_left (n : ) :
strict_mono (λ (m : ), ack m n)
theorem ack_mono_left (n : ) :
monotone (λ (m : ), ack m n)
theorem ack_injective_left (n : ) :
function.injective (λ (m : ), ack m n)
@[simp]
theorem ack_lt_iff_left {m₁ m₂ n : } :
ack m₁ n < ack m₂ n m₁ < m₂
@[simp]
theorem ack_le_iff_left {m₁ m₂ n : } :
ack m₁ n ack m₂ n m₁ m₂
@[simp]
theorem ack_inj_left {m₁ m₂ n : } :
ack m₁ n = ack m₂ n m₁ = m₂
theorem max_ack_left (m₁ m₂ n : ) :
ack (linear_order.max m₁ m₂) n = linear_order.max (ack m₁ n) (ack m₂ n)
theorem ack_le_ack {m₁ m₂ n₁ n₂ : } (hm : m₁ m₂) (hn : n₁ n₂) :
ack m₁ n₁ ack m₂ n₂
theorem ack_succ_right_le_ack_succ_left (m n : ) :
ack m (n + 1) ack (m + 1) n
theorem ack_add_one_sq_lt_ack_add_three (m n : ) :
(ack m n + 1) ^ 2 ack (m + 3) n
theorem ack_ack_lt_ack_max_add_two (m n k : ) :
ack m (ack n k) < ack (linear_order.max m n + 2) k
theorem ack_add_one_sq_lt_ack_add_four (m n : ) :
ack m ((n + 1) ^ 2) < ack (m + 4) n
theorem ack_mkpair_lt (m n k : ) :
ack m (nat.mkpair n k) < ack (m + 4) (linear_order.max n k)
theorem exists_lt_ack_of_nat_primrec {f : } (hf : nat.primrec f) :
(m : ), (n : ), f n < ack m n

If f is primitive recursive, there exists m such that f n < ack m n for all n.

theorem not_nat_primrec_ack_self  :
¬nat.primrec (λ (n : ), ack n n)
theorem not_primrec_ack_self  :
¬primrec (λ (n : ), ack n n)

The Ackermann function is not primitive recursive.