mathlib documentation

computability.turing_machine

Turing machines

This file defines a sequence of simple machine languages, starting with Turing machines and working up to more complex languages based on Wang B-machines.

Naming conventions

Each model of computation in this file shares a naming convention for the elements of a model of computation. These are the parameters for the language:

All of these variables denote "essentially finite" types, but for technical reasons it is convenient to allow them to be infinite anyway. When using an infinite type, we will be interested to prove that only finitely many values of the type are ever interacted with.

Given these parameters, there are a few common structures for the model that arise:

def turing.blank_extends {Γ : Type u_1} [inhabited Γ] :
list Γlist Γ → Prop

The blank_extends partial order holds of l₁ and l₂ if l₂ is obtained by adding blanks (default Γ) to the end of l₁.

Equations
theorem turing.blank_extends.refl {Γ : Type u_1} [inhabited Γ] (l : list Γ) :

theorem turing.blank_extends.trans {Γ : Type u_1} [inhabited Γ] {l₁ l₂ l₃ : list Γ} :
turing.blank_extends l₁ l₂turing.blank_extends l₂ l₃turing.blank_extends l₁ l₃

theorem turing.blank_extends.below_of_le {Γ : Type u_1} [inhabited Γ] {l l₁ l₂ : list Γ} :
turing.blank_extends l l₁turing.blank_extends l l₂l₁.length l₂.lengthturing.blank_extends l₁ l₂

def turing.blank_extends.above {Γ : Type u_1} [inhabited Γ] {l l₁ l₂ : list Γ} :

Any two extensions by blank l₁,l₂ of l have a common join (which can be taken to be the longer of l₁ and l₂).

Equations
theorem turing.blank_extends.above_of_le {Γ : Type u_1} [inhabited Γ] {l l₁ l₂ : list Γ} :
turing.blank_extends l₁ lturing.blank_extends l₂ ll₁.length l₂.lengthturing.blank_extends l₁ l₂

def turing.blank_rel {Γ : Type u_1} [inhabited Γ] :
list Γlist Γ → Prop

blank_rel is the symmetric closure of blank_extends, turning it into an equivalence relation. Two lists are related by blank_rel if one extends the other by blanks.

Equations
theorem turing.blank_rel.refl {Γ : Type u_1} [inhabited Γ] (l : list Γ) :

theorem turing.blank_rel.symm {Γ : Type u_1} [inhabited Γ] {l₁ l₂ : list Γ} :
turing.blank_rel l₁ l₂turing.blank_rel l₂ l₁

theorem turing.blank_rel.trans {Γ : Type u_1} [inhabited Γ] {l₁ l₂ l₃ : list Γ} :
turing.blank_rel l₁ l₂turing.blank_rel l₂ l₃turing.blank_rel l₁ l₃

def turing.blank_rel.above {Γ : Type u_1} [inhabited Γ] {l₁ l₂ : list Γ} :

Given two blank_rel lists, there exists (constructively) a common join.

Equations
def turing.blank_rel.below {Γ : Type u_1} [inhabited Γ] {l₁ l₂ : list Γ} :

Given two blank_rel lists, there exists (constructively) a common meet.

Equations
def turing.blank_rel.setoid (Γ : Type u_1) [inhabited Γ] :

Construct a setoid instance for blank_rel.

Equations
def turing.list_blank (Γ : Type u_1) [inhabited Γ] :
Type u_1

A list_blank Γ is a quotient of list Γ by extension by blanks at the end. This is used to represent half-tapes of a Turing machine, so that we can pretend that the list continues infinitely with blanks.

Equations
def turing.list_blank.lift_on {Γ : Type u_1} [inhabited Γ] {α : Sort u_2} (l : turing.list_blank Γ) (f : list Γ → α) :
(∀ (a b : list Γ), turing.blank_extends a bf a = f b) → α

A modified version of quotient.lift_on' specialized for list_blank, with the stronger precondition blank_extends instead of blank_rel.

Equations
def turing.list_blank.mk {Γ : Type u_1} [inhabited Γ] :

The quotient map turning a list into a list_blank.

Equations
theorem turing.list_blank.induction_on {Γ : Type u_1} [inhabited Γ] {p : turing.list_blank Γ → Prop} (q : turing.list_blank Γ) :
(∀ (a : list Γ), p (turing.list_blank.mk a))p q

def turing.list_blank.head {Γ : Type u_1} [inhabited Γ] :

The head of a list_blank is well defined.

Equations
@[simp]
theorem turing.list_blank.head_mk {Γ : Type u_1} [inhabited Γ] (l : list Γ) :

The tail of a list_blank is well defined (up to the tail of blanks).

Equations
@[simp]

def turing.list_blank.cons {Γ : Type u_1} [inhabited Γ] :

We can cons an element onto a list_blank.

Equations
@[simp]
theorem turing.list_blank.cons_mk {Γ : Type u_1} [inhabited Γ] (a : Γ) (l : list Γ) :

@[simp]
theorem turing.list_blank.head_cons {Γ : Type u_1} [inhabited Γ] (a : Γ) (l : turing.list_blank Γ) :

@[simp]
theorem turing.list_blank.tail_cons {Γ : Type u_1} [inhabited Γ] (a : Γ) (l : turing.list_blank Γ) :

@[simp]

The cons and head/tail functions are mutually inverse, unlike in the case of list where this only holds for nonempty lists.

theorem turing.list_blank.exists_cons {Γ : Type u_1} [inhabited Γ] (l : turing.list_blank Γ) :
∃ (a : Γ) (l' : turing.list_blank Γ), l = turing.list_blank.cons a l'

The cons and head/tail functions are mutually inverse, unlike in the case of list where this only holds for nonempty lists.

def turing.list_blank.nth {Γ : Type u_1} [inhabited Γ] :
turing.list_blank Γ → Γ

The n-th element of a list_blank is well defined for all n : ℕ, unlike in a list.

Equations
@[simp]
theorem turing.list_blank.nth_mk {Γ : Type u_1} [inhabited Γ] (l : list Γ) (n : ) :

@[simp]
theorem turing.list_blank.nth_zero {Γ : Type u_1} [inhabited Γ] (l : turing.list_blank Γ) :
l.nth 0 = l.head

@[simp]
theorem turing.list_blank.nth_succ {Γ : Type u_1} [inhabited Γ] (l : turing.list_blank Γ) (n : ) :
l.nth (n + 1) = l.tail.nth n

@[ext]
theorem turing.list_blank.ext {Γ : Type u_1} [inhabited Γ] {L₁ L₂ : turing.list_blank Γ} :
(∀ (i : ), L₁.nth i = L₂.nth i)L₁ = L₂

@[simp]
def turing.list_blank.modify_nth {Γ : Type u_1} [inhabited Γ] :
(Γ → Γ)turing.list_blank Γturing.list_blank Γ

Apply a function to a value stored at the nth position of the list.

Equations
theorem turing.list_blank.nth_modify_nth {Γ : Type u_1} [inhabited Γ] (f : Γ → Γ) (n i : ) (L : turing.list_blank Γ) :
(turing.list_blank.modify_nth f n L).nth i = ite (i = n) (f (L.nth i)) (L.nth i)

structure turing.pointed_map (Γ : Type u) (Γ' : Type v) [inhabited Γ] [inhabited Γ'] :
Type (max u v)

A pointed map of inhabited types is a map that sends one default value to the other.

@[instance]
def turing.inhabited {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] :

Equations
@[instance]
def turing.has_coe_to_fun {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] :

Equations
@[simp]
theorem turing.pointed_map.mk_val {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : Γ → Γ') (pt : f (default Γ) = default Γ') :
{f := f, map_pt' := pt} = f

@[simp]
theorem turing.pointed_map.map_pt {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') :
f (default Γ) = default Γ'

@[simp]
theorem turing.pointed_map.head_map {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : list Γ) :

def turing.list_blank.map {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] :

The map function on lists is well defined on list_blanks provided that the map is pointed.

Equations
@[simp]
theorem turing.list_blank.map_mk {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : list Γ) :

@[simp]
theorem turing.list_blank.head_map {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : turing.list_blank Γ) :

@[simp]
theorem turing.list_blank.tail_map {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : turing.list_blank Γ) :

@[simp]
theorem turing.list_blank.map_cons {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : turing.list_blank Γ) (a : Γ) :

@[simp]
theorem turing.list_blank.nth_map {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : turing.list_blank Γ) (n : ) :

def turing.proj {ι : Type u_1} {Γ : ι → Type u_2} [Π (i : ι), inhabited (Γ i)] (i : ι) :
turing.pointed_map (Π (i : ι), Γ i) (Γ i)

The i-th projection as a pointed map.

Equations
theorem turing.proj_map_nth {ι : Type u_1} {Γ : ι → Type u_2} [Π (i : ι), inhabited (Γ i)] (i : ι) (L : turing.list_blank (Π (i : ι), Γ i)) (n : ) :

theorem turing.list_blank.map_modify_nth {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (F : turing.pointed_map Γ Γ') (f : Γ → Γ) (f' : Γ' → Γ') (H : ∀ (x : Γ), F (f x) = f' (F x)) (n : ) (L : turing.list_blank Γ) :

@[simp]
def turing.list_blank.append {Γ : Type u_1} [inhabited Γ] :

Append a list on the left side of a list_blank.

Equations
@[simp]
theorem turing.list_blank.append_mk {Γ : Type u_1} [inhabited Γ] (l₁ l₂ : list Γ) :

theorem turing.list_blank.append_assoc {Γ : Type u_1} [inhabited Γ] (l₁ l₂ : list Γ) (l₃ : turing.list_blank Γ) :

def turing.list_blank.bind {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (l : turing.list_blank Γ) (f : Γ → list Γ') :
(∃ (n : ), f (default Γ) = list.repeat (default Γ') n)turing.list_blank Γ'

The bind function on lists is well defined on list_blanks provided that the default element is sent to a sequence of default elements.

Equations
@[simp]
theorem turing.list_blank.bind_mk {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (l : list Γ) (f : Γ → list Γ') (hf : ∃ (n : ), f (default Γ) = list.repeat (default Γ') n) :

@[simp]
theorem turing.list_blank.cons_bind {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (a : Γ) (l : turing.list_blank Γ) (f : Γ → list Γ') (hf : ∃ (n : ), f (default Γ) = list.repeat (default Γ') n) :

structure turing.tape (Γ : Type u_1) [inhabited Γ] :
Type u_1

The tape of a Turing machine is composed of a head element (which we imagine to be the current position of the head), together with two list_blanks denoting the portions of the tape going off to the left and right. When the Turing machine moves right, an element is pulled from the right side and becomes the new head, while the head element is consed onto the left side.

inductive turing.dir  :
Type

A direction for the turing machine move command, either left or right.

def turing.tape.left₀ {Γ : Type u_1} [inhabited Γ] :

The "inclusive" left side of the tape, including both left and head.

Equations
def turing.tape.right₀ {Γ : Type u_1} [inhabited Γ] :

The "inclusive" right side of the tape, including both right and head.

Equations
def turing.tape.move {Γ : Type u_1} [inhabited Γ] :

Move the tape in response to a motion of the Turing machine. Note that T.move dir.left makes T.left smaller; the Turing machine is moving left and the tape is moving right.

Equations
def turing.tape.mk' {Γ : Type u_1} [inhabited Γ] :

Construct a tape from a left side and an inclusive right side.

Equations
@[simp]
theorem turing.tape.mk'_left {Γ : Type u_1} [inhabited Γ] (L R : turing.list_blank Γ) :

@[simp]
theorem turing.tape.mk'_head {Γ : Type u_1} [inhabited Γ] (L R : turing.list_blank Γ) :

@[simp]
theorem turing.tape.mk'_right {Γ : Type u_1} [inhabited Γ] (L R : turing.list_blank Γ) :

@[simp]
theorem turing.tape.mk'_right₀ {Γ : Type u_1} [inhabited Γ] (L R : turing.list_blank Γ) :

@[simp]
theorem turing.tape.mk'_left_right₀ {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) :

theorem turing.tape.exists_mk' {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) :
∃ (L R : turing.list_blank Γ), T = turing.tape.mk' L R

def turing.tape.mk₂ {Γ : Type u_1} [inhabited Γ] :
list Γlist Γturing.tape Γ

Construct a tape from a left side and an inclusive right side.

Equations
def turing.tape.mk₁ {Γ : Type u_1} [inhabited Γ] :
list Γturing.tape Γ

Construct a tape from a list, with the head of the list at the TM head and the rest going to the right.

Equations
def turing.tape.nth {Γ : Type u_1} [inhabited Γ] :
turing.tape Γ → Γ

The nth function of a tape is integer-valued, with index 0 being the head, negative indexes on the left and positive indexes on the right. (Picture a number line.)

Equations
@[simp]
theorem turing.tape.nth_zero {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) :
T.nth 0 = T.head

theorem turing.tape.right₀_nth {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) (n : ) :

@[simp]
theorem turing.tape.mk'_nth_nat {Γ : Type u_1} [inhabited Γ] (L R : turing.list_blank Γ) (n : ) :

@[simp]
theorem turing.tape.move_left_nth {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) (i : ) :

@[simp]
theorem turing.tape.move_right_nth {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) (i : ) :

@[simp]
theorem turing.tape.move_right_n_head {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) (i : ) :

def turing.tape.write {Γ : Type u_1} [inhabited Γ] :
Γ → turing.tape Γturing.tape Γ

Replace the current value of the head on the tape.

Equations
@[simp]
theorem turing.tape.write_self {Γ : Type u_1} [inhabited Γ] (T : turing.tape Γ) :

@[simp]
theorem turing.tape.write_nth {Γ : Type u_1} [inhabited Γ] (b : Γ) (T : turing.tape Γ) {i : } :
(turing.tape.write b T).nth i = ite (i = 0) b (T.nth i)

def turing.tape.map {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] :

Apply a pointed map to a tape to change the alphabet.

Equations
@[simp]
theorem turing.tape.map_fst {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (T : turing.tape Γ) :

@[simp]
theorem turing.tape.map_write {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (b : Γ) (T : turing.tape Γ) :

theorem turing.tape.map_move {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (T : turing.tape Γ) (d : turing.dir) :

theorem turing.tape.map_mk' {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (L R : turing.list_blank Γ) :

theorem turing.tape.map_mk₂ {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (L R : list Γ) :

theorem turing.tape.map_mk₁ {Γ : Type u_1} {Γ' : Type u_2} [inhabited Γ] [inhabited Γ'] (f : turing.pointed_map Γ Γ') (l : list Γ) :

def turing.eval {σ : Type u_1} :
(σ → option σ)σ → roption σ

Run a state transition function σ → option σ "to completion". The return value is the last state returned before a none result. If the state transition function always returns some, then the computation diverges, returning roption.none.

Equations
def turing.reaches {σ : Type u_1} :
(σ → option σ)σ → σ → Prop

The reflexive transitive closure of a state transition function. reaches f a b means there is a finite sequence of steps f a = some a₁, f a₁ = some a₂, ... such that aₙ = b. This relation permits zero steps of the state transition function.

Equations
def turing.reaches₁ {σ : Type u_1} :
(σ → option σ)σ → σ → Prop

The transitive closure of a state transition function. reaches₁ f a b means there is a nonempty finite sequence of steps f a = some a₁, f a₁ = some a₂, ... such that aₙ = b. This relation does not permit zero steps of the state transition function.

Equations
theorem turing.reaches₁_eq {σ : Type u_1} {f : σ → option σ} {a b c : σ} :
f a = f b(turing.reaches₁ f a c turing.reaches₁ f b c)

theorem turing.reaches_total {σ : Type u_1} {f : σ → option σ} {a b c : σ} :

theorem turing.reaches₁_fwd {σ : Type u_1} {f : σ → option σ} {a b c : σ} :
turing.reaches₁ f a cb f aturing.reaches f b c

def turing.reaches₀ {σ : Type u_1} :
(σ → option σ)σ → σ → Prop

A variation on reaches. reaches₀ f a b holds if whenever reaches₁ f b c then reaches₁ f a c. This is a weaker property than reaches and is useful for replacing states with equivalent states without taking a step.

Equations
theorem turing.reaches₀.trans {σ : Type u_1} {f : σ → option σ} {a b c : σ} :

theorem turing.reaches₀.refl {σ : Type u_1} {f : σ → option σ} (a : σ) :

theorem turing.reaches₀.single {σ : Type u_1} {f : σ → option σ} {a b : σ} :
b f aturing.reaches₀ f a b

theorem turing.reaches₀.head {σ : Type u_1} {f : σ → option σ} {a b c : σ} :
b f aturing.reaches₀ f b cturing.reaches₀ f a c

theorem turing.reaches₀.tail {σ : Type u_1} {f : σ → option σ} {a b c : σ} :
turing.reaches₀ f a bc f bturing.reaches₀ f a c

theorem turing.reaches₀_eq {σ : Type u_1} {f : σ → option σ} {a b : σ} :
f a = f bturing.reaches₀ f a b

theorem turing.reaches₁.to₀ {σ : Type u_1} {f : σ → option σ} {a b : σ} :

theorem turing.reaches.to₀ {σ : Type u_1} {f : σ → option σ} {a b : σ} :

theorem turing.reaches₀.tail' {σ : Type u_1} {f : σ → option σ} {a b c : σ} :
turing.reaches₀ f a bc f bturing.reaches₁ f a c

def turing.eval_induction {σ : Type u_1} {f : σ → option σ} {b : σ} {C : σ → Sort u_2} {a : σ} :
b turing.eval f a(Π (a : σ), b turing.eval f a(Π (a' : σ), b turing.eval f a'f a = some a'C a')C a)C a

(co-)Induction principle for eval. If a property C holds of any point a evaluating to b which is either terminal (meaning a = b) or where the next point also satisfies C, then it holds of any point where eval f a evaluates to b. This formalizes the notion that if eval f a evaluates to b then it reaches terminal state b in finitely many steps.

Equations
theorem turing.mem_eval {σ : Type u_1} {f : σ → option σ} {a b : σ} :

theorem turing.eval_maximal₁ {σ : Type u_1} {f : σ → option σ} {a b : σ} (h : b turing.eval f a) (c : σ) :

theorem turing.eval_maximal {σ : Type u_1} {f : σ → option σ} {a b : σ} (h : b turing.eval f a) {c : σ} :
turing.reaches f b c c = b

theorem turing.reaches_eval {σ : Type u_1} {f : σ → option σ} {a b : σ} :

def turing.respects {σ₁ : Type u_1} {σ₂ : Type u_2} :
(σ₁ → option σ₁)(σ₂ → option σ₂)(σ₁ → σ₂ → Prop) → Prop

Given a relation tr : σ₁ → σ₂ → Prop between state spaces, and state transition functions f₁ : σ₁ → option σ₁ and f₂ : σ₂ → option σ₂, respects f₁ f₂ tr means that if tr a₁ a₂ holds initially and f₁ takes a step to a₂ then f₂ will take one or more steps before reaching a state b₂ satisfying tr a₂ b₂, and if f₁ a₁ terminates then f₂ a₂ also terminates. Such a relation tr is also known as a refinement.

Equations
  • turing.respects f₁ f₂ tr = ∀ ⦃a₁ : σ₁⦄ ⦃a₂ : σ₂⦄, tr a₁ a₂turing.respects._match_1 f₂ tr (f₁ a₁)
  • turing.respects._match_1 f₂ tr (some b₁) = ∃ (b₂ : σ₂), tr b₁ b₂ turing.reaches₁ f₂ a₂ b₂
  • turing.respects._match_1 f₂ tr none = (f₂ a₂ = none)
theorem turing.tr_reaches₁ {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂ → Prop} (H : turing.respects f₁ f₂ tr) {a₁ : σ₁} {a₂ : σ₂} (aa : tr a₁ a₂) {b₁ : σ₁} :
turing.reaches₁ f₁ a₁ b₁(∃ (b₂ : σ₂), tr b₁ b₂ turing.reaches₁ f₂ a₂ b₂)

theorem turing.tr_reaches {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂ → Prop} (H : turing.respects f₁ f₂ tr) {a₁ : σ₁} {a₂ : σ₂} (aa : tr a₁ a₂) {b₁ : σ₁} :
turing.reaches f₁ a₁ b₁(∃ (b₂ : σ₂), tr b₁ b₂ turing.reaches f₂ a₂ b₂)

theorem turing.tr_reaches_rev {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂ → Prop} (H : turing.respects f₁ f₂ tr) {a₁ : σ₁} {a₂ : σ₂} (aa : tr a₁ a₂) {b₂ : σ₂} :
turing.reaches f₂ a₂ b₂(∃ (c₁ : σ₁) (c₂ : σ₂), turing.reaches f₂ b₂ c₂ tr c₁ c₂ turing.reaches f₁ a₁ c₁)

theorem turing.tr_eval {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂ → Prop} (H : turing.respects f₁ f₂ tr) {a₁ b₁ : σ₁} {a₂ : σ₂} :
tr a₁ a₂b₁ turing.eval f₁ a₁(∃ (b₂ : σ₂), tr b₁ b₂ b₂ turing.eval f₂ a₂)

theorem turing.tr_eval_rev {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂ → Prop} (H : turing.respects f₁ f₂ tr) {a₁ : σ₁} {b₂ a₂ : σ₂} :
tr a₁ a₂b₂ turing.eval f₂ a₂(∃ (b₁ : σ₁), tr b₁ b₂ b₁ turing.eval f₁ a₁)

theorem turing.tr_eval_dom {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂ → Prop} (H : turing.respects f₁ f₂ tr) {a₁ : σ₁} {a₂ : σ₂} :
tr a₁ a₂((turing.eval f₂ a₂).dom (turing.eval f₁ a₁).dom)

def turing.frespects {σ₁ : Type u_1} {σ₂ : Type u_2} :
(σ₂ → option σ₂)(σ₁ → σ₂)σ₂ → option σ₁ → Prop

A simpler version of respects when the state transition relation tr is a function.

Equations
theorem turing.frespects_eq {σ₁ : Type u_1} {σ₂ : Type u_2} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂} {a₂ b₂ : σ₂} (h : f₂ a₂ = f₂ b₂) {b₁ : option σ₁} :
turing.frespects f₂ tr a₂ b₁ turing.frespects f₂ tr b₂ b₁

theorem turing.fun_respects {σ₁ : Type u_1} {σ₂ : Type u_2} {f₁ : σ₁ → option σ₁} {f₂ : σ₂ → option σ₂} {tr : σ₁ → σ₂} :
turing.respects f₁ f₂ (λ (a : σ₁) (b : σ₂), tr a = b) ∀ ⦃a₁ : σ₁⦄, turing.frespects f₂ tr (tr a₁) (f₁ a₁)

theorem turing.tr_eval' {σ₁ σ₂ : Type u_1} (f₁ : σ₁ → option σ₁) (f₂ : σ₂ → option σ₂) (tr : σ₁ → σ₂) (H : turing.respects f₁ f₂ (λ (a : σ₁) (b : σ₂), tr a = b)) (a₁ : σ₁) :
turing.eval f₂ (tr a₁) = tr <$> turing.eval f₁ a₁

The TM0 model

A TM0 turing machine is essentially a Post-Turing machine, adapted for type theory.

A Post-Turing machine with symbol type Γ and label type Λ is a function Λ → Γ → option (Λ × stmt), where a stmt can be either move left, move right or write a for a : Γ. The machine works over a "tape", a doubly-infinite sequence of elements of Γ, and an instantaneous configuration, cfg, is a label q : Λ indicating the current internal state of the machine, and a tape Γ (which is essentially ℤ →₀ Γ). The evolution is described by the step function:

The initial state takes a list Γ and produces a tape Γ where the head of the list is the head of the tape and the rest of the list extends to the right, with the left side all blank. The final state takes the entire right side of the tape right or equal to the current position of the machine. (This is actually a list_blank Γ, not a list Γ, because we don't know, at this level of generality, where the output ends. If equality to default Γ is decidable we can trim the list to remove the infinite tail of blanks.)

inductive turing.TM0.stmt (Γ : Type u_1) [inhabited Γ] :
Type u_1

A Turing machine "statement" is just a command to either move left or right, or write a symbol on the tape.

@[nolint]
def turing.TM0.machine (Γ : Type u_1) [inhabited Γ] (Λ : Type u_2) [inhabited Λ] :
Type (max u_2 u_1)

A Post-Turing machine with symbol type Γ and label type Λ is a function which, given the current state q : Λ and the tape head a : Γ, either halts (returns none) or returns a new state q' : Λ and a stmt describing what to do, either a move left or right, or a write command.

Both Λ and Γ are required to be inhabited; the default value for Γ is the "blank" tape value, and the default value of Λ is the initial state.

Equations
@[instance]
def turing.TM0.machine.inhabited (Γ : Type u_1) [inhabited Γ] (Λ : Type u_2) [inhabited Λ] :

Equations
structure turing.TM0.cfg (Γ : Type u_1) [inhabited Γ] (Λ : Type u_2) [inhabited Λ] :
Type (max u_1 u_2)

The configuration state of a Turing machine during operation consists of a label (machine state), and a tape, represented in the form (a, L, R) meaning the tape looks like L.rev ++ [a] ++ R with the machine currently reading the a. The lists are automatically extended with blanks as the machine moves around.

@[instance]
def turing.TM0.cfg.inhabited (Γ : Type u_1) [inhabited Γ] (Λ : Type u_2) [inhabited Λ] :

Equations
def turing.TM0.step {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :

Execution semantics of the Turing machine.

Equations
def turing.TM0.reaches {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :
turing.TM0.machine Γ Λturing.TM0.cfg Γ Λturing.TM0.cfg Γ Λ → Prop

The statement reaches M s₁ s₂ means that s₂ is obtained starting from s₁ after a finite number of steps from s₂.

Equations
def turing.TM0.init {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :
list Γturing.TM0.cfg Γ Λ

The initial configuration.

Equations
def turing.TM0.eval {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :

Evaluate a Turing machine on initial input to a final state, if it terminates.

Equations
def turing.TM0.supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :
turing.TM0.machine Γ Λset Λ → Prop

The raw definition of a Turing machine does not require that Γ and Λ are finite, and in practice we will be interested in the infinite Λ case. We recover instead a notion of "effectively finite" Turing machines, which only make use of a finite subset of their states. We say that a set S ⊆ Λ supports a Turing machine M if S is closed under the transition function and contains the initial state.

Equations
theorem turing.TM0.step_supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] (M : turing.TM0.machine Γ Λ) {S : set Λ} (ss : turing.TM0.supports M S) {c c' : turing.TM0.cfg Γ Λ} :
c' turing.TM0.step M cc.q Sc'.q S

theorem turing.TM0.univ_supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] (M : turing.TM0.machine Γ Λ) :

def turing.TM0.stmt.map {Γ : Type u_1} [inhabited Γ] {Γ' : Type u_2} [inhabited Γ'] :

Map a TM statement across a function. This does nothing to move statements and maps the write values.

Equations
def turing.TM0.cfg.map {Γ : Type u_1} [inhabited Γ] {Γ' : Type u_2} [inhabited Γ'] {Λ : Type u_3} [inhabited Λ] {Λ' : Type u_4} [inhabited Λ'] :
turing.pointed_map Γ Γ'(Λ → Λ')turing.TM0.cfg Γ Λturing.TM0.cfg Γ' Λ'

Map a configuration across a function, given f : Γ → Γ' a map of the alphabets and g : Λ → Λ' a map of the machine states.

Equations
def turing.TM0.machine.map {Γ : Type u_1} [inhabited Γ] {Γ' : Type u_2} [inhabited Γ'] {Λ : Type u_3} [inhabited Λ] {Λ' : Type u_4} [inhabited Λ'] :
turing.TM0.machine Γ Λturing.pointed_map Γ Γ'turing.pointed_map Γ' Γ(Λ → Λ')(Λ' → Λ)turing.TM0.machine Γ' Λ'

Because the state transition function uses the alphabet and machine states in both the input and output, to map a machine from one alphabet and machine state space to another we need functions in both directions, essentially an equiv without the laws.

Equations
theorem turing.TM0.machine.map_step {Γ : Type u_1} [inhabited Γ] {Γ' : Type u_2} [inhabited Γ'] {Λ : Type u_3} [inhabited Λ] {Λ' : Type u_4} [inhabited Λ'] (M : turing.TM0.machine Γ Λ) (f₁ : turing.pointed_map Γ Γ') (f₂ : turing.pointed_map Γ' Γ) (g₁ : Λ → Λ') (g₂ : Λ' → Λ) {S : set Λ} (f₂₁ : function.right_inverse f₁ f₂) (g₂₁ : ∀ (q : Λ), q Sg₂ (g₁ q) = q) (c : turing.TM0.cfg Γ Λ) :
c.q Soption.map (turing.TM0.cfg.map f₁ g₁) (turing.TM0.step M c) = turing.TM0.step (M.map f₁ f₂ g₁ g₂) (turing.TM0.cfg.map f₁ g₁ c)

theorem turing.TM0.map_init {Γ : Type u_1} [inhabited Γ] {Γ' : Type u_2} [inhabited Γ'] {Λ : Type u_3} [inhabited Λ] {Λ' : Type u_4} [inhabited Λ'] (f₁ : turing.pointed_map Γ Γ') (g₁ : turing.pointed_map Λ Λ') (l : list Γ) :

theorem turing.TM0.machine.map_respects {Γ : Type u_1} [inhabited Γ] {Γ' : Type u_2} [inhabited Γ'] {Λ : Type u_3} [inhabited Λ] {Λ' : Type u_4} [inhabited Λ'] (M : turing.TM0.machine Γ Λ) (f₁ : turing.pointed_map Γ Γ') (f₂ : turing.pointed_map Γ' Γ) (g₁ : turing.pointed_map Λ Λ') (g₂ : Λ' → Λ) {S : set Λ} :
turing.TM0.supports M Sfunction.right_inverse f₁ f₂(∀ (q : Λ), q Sg₂ (g₁ q) = q)turing.respects (turing.TM0.step M) (turing.TM0.step (M.map f₁ f₂ g₁ g₂)) (λ (a : turing.TM0.cfg Γ Λ) (b : turing.TM0.cfg Γ' Λ'), a.q S turing.TM0.cfg.map f₁ g₁ a = b)

The TM1 model

The TM1 model is a simplification and extension of TM0 (Post-Turing model) in the direction of Wang B-machines. The machine's internal state is extended with a (finite) store σ of variables that may be accessed and updated at any time.

A machine is given by a Λ indexed set of procedures or functions. Each function has a body which is a stmt. Most of the regular commands are allowed to use the current value a of the local variables and the value T.head on the tape to calculate what to write or how to change local state, but the statements themselves have a fixed structure. The stmts can be as follows:

Note that here most statements do not have labels; goto commands can only go to a new function. Only the goto and halt statements actually take a step; the rest is done by recursion on statements and so take 0 steps. (There is a uniform bound on many statements can be executed before the next goto, so this is an O(1) speedup with the constant depending on the machine.)

The halt command has a one step stutter before actually halting so that any changes made before the halt have a chance to be "committed", since the eval relation uses the final configuration before the halt as the output, and move and write etc. take 0 steps in this model.

inductive turing.TM1.stmt (Γ : Type u_1) [inhabited Γ] :
Type u_2Type u_3Type (max u_1 u_2 u_3)

The TM1 model is a simplification and extension of TM0 (Post-Turing model) in the direction of Wang B-machines. The machine's internal state is extended with a (finite) store σ of variables that may be accessed and updated at any time. A machine is given by a Λ indexed set of procedures or functions. Each function has a body which is a stmt, which can either be a move or write command, a branch (if statement based on the current tape value), a load (set the variable value), a goto (call another function), or halt. Note that here most statements do not have labels; goto commands can only go to a new function. All commands have access to the variable value and current tape value.

@[instance]
def turing.TM1.stmt.inhabited (Γ : Type u_1) [inhabited Γ] (Λ : Type u_2) (σ : Type u_3) :

Equations
structure turing.TM1.cfg (Γ : Type u_1) [inhabited Γ] :
Type u_2Type u_3Type (max u_1 u_2 u_3)

The configuration of a TM1 machine is given by the currently evaluating statement, the variable store value, and the tape.

@[instance]
def turing.TM1.cfg.inhabited (Γ : Type u_1) [inhabited Γ] (Λ : Type u_2) (σ : Type u_3) [inhabited σ] :

Equations
def turing.TM1.step_aux {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} :
turing.TM1.stmt Γ Λ σσ → turing.tape Γturing.TM1.cfg Γ Λ σ

The semantics of TM1 evaluation.

Equations
def turing.TM1.step {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} :
(Λ → turing.TM1.stmt Γ Λ σ)turing.TM1.cfg Γ Λ σoption (turing.TM1.cfg Γ Λ σ)

The state transition function.

Equations
def turing.TM1.supports_stmt {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} :
finset Λturing.TM1.stmt Γ Λ σ → Prop

A set S of labels supports the statement q if all the goto statements in q refer only to other functions in S.

Equations
theorem turing.TM1.stmts₁_self {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} {q : turing.TM1.stmt Γ Λ σ} :

theorem turing.TM1.stmts₁_trans {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} {q₁ q₂ : turing.TM1.stmt Γ Λ σ} :

theorem turing.TM1.stmts₁_supports_stmt_mono {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} {S : finset Λ} {q₁ q₂ : turing.TM1.stmt Γ Λ σ} :

def turing.TM1.stmts {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} :
(Λ → turing.TM1.stmt Γ Λ σ)finset Λfinset (option (turing.TM1.stmt Γ Λ σ))

The set of all statements in a turing machine, plus one extra value none representing the halt state. This is used in the TM1 to TM0 reduction.

Equations
theorem turing.TM1.stmts_trans {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} {M : Λ → turing.TM1.stmt Γ Λ σ} {S : finset Λ} {q₁ q₂ : turing.TM1.stmt Γ Λ σ} :

def turing.TM1.supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} [inhabited Λ] :
(Λ → turing.TM1.stmt Γ Λ σ)finset Λ → Prop

A set S of labels supports machine M if all the goto statements in the functions in S refer only to other functions in S.

Equations
theorem turing.TM1.stmts_supports_stmt {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} [inhabited Λ] {M : Λ → turing.TM1.stmt Γ Λ σ} {S : finset Λ} {q : turing.TM1.stmt Γ Λ σ} :

theorem turing.TM1.step_supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} [inhabited Λ] (M : Λ → turing.TM1.stmt Γ Λ σ) {S : finset Λ} (ss : turing.TM1.supports M S) {c c' : turing.TM1.cfg Γ Λ σ} :

def turing.TM1.init {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} [inhabited Λ] [inhabited σ] :
list Γturing.TM1.cfg Γ Λ σ

The initial state, given a finite input that is placed on the tape starting at the TM head and going to the right.

Equations
def turing.TM1.eval {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} {σ : Type u_3} [inhabited Λ] [inhabited σ] :
(Λ → turing.TM1.stmt Γ Λ σ)list Γroption (turing.list_blank Γ)

Evaluate a TM to completion, resulting in an output list on the tape (with an indeterminate number of blanks on the end).

Equations

TM1 emulator in TM0

To prove that TM1 computable functions are TM0 computable, we need to reduce each TM1 program to a TM0 program. So suppose a TM1 program is given. We take the following:

Even though stmt₁ contains a statement called halt, we must separate it from none (some halt steps to none and none actually halts) because there is a one step stutter in the TM1 semantics.

@[nolint]
def turing.TM1to0.Λ' {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] :
(Λ → turing.TM1.stmt Γ Λ σ)Type (max (max u_1 u_2 u_3) u_3)

The base machine state space is a pair of an option stmt₁ representing the current program to be executed, or none for the halt state, and a σ which is the local state (stored in the TM, not the tape). Because there are an infinite number of programs, this state space is infinite, but for a finitely supported TM1 machine and a finite type σ, only finitely many of these states are reachable.

Equations
@[instance]
def turing.TM1to0.inhabited {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) :

Equations
def turing.TM1to0.tr_aux {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) :
Γ → turing.TM1.stmt Γ Λ σσ → turing.TM1to0.Λ' M × turing.TM0.stmt Γ

The core TM1 → TM0 translation function. Here s is the current value on the tape, and the stmt₁ is the TM1 statement to translate, with local state v : σ. We evaluate all regular instructions recursively until we reach either a move or write command, or a goto; in the latter case we emit a dummy write s step and transition to the new target location.

Equations
def turing.TM1to0.tr {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) :

The translated TM0 machine (given the TM1 machine input).

Equations
def turing.TM1to0.tr_cfg {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) :

Translate configurations from TM1 to TM0.

Equations
theorem turing.TM1to0.tr_respects {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) :

theorem turing.TM1to0.tr_eval {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) (l : list Γ) :

def turing.TM1to0.tr_stmts {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) [fintype σ] :

Given a finite set of accessible Λ machine states, there is a finite set of accessible machine states in the target (even though the type Λ' is infinite).

Equations
theorem turing.TM1to0.tr_supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) [fintype σ] {S : finset Λ} :

TM1(Γ) emulator in TM1(bool)

The most parsimonious Turing machine model that is still Turing complete is TM0 with Γ = bool. Because our construction in the previous section reducing TM1 to TM0 doesn't change the alphabet, we can do the alphabet reduction on TM1 instead of TM0 directly.

The basic idea is to use a bijection between Γ and a subset of vector bool n, where n is a fixed constant. Each tape element is represented as a block of n bools. Whenever the machine wants to read a symbol from the tape, it traverses over the block, performing n branch instructions to each any of the 2^n results.

For the write instruction, we have to use a goto because we need to follow a different code path depending on the local state, which is not available in the TM1 model, so instead we jump to a label computed using the read value and the local state, which performs the writing and returns to normal execution.

Emulation overhead is O(1). If not for the above write behavior it would be 1-1 because we are exploiting the 0-step behavior of regular commands to avoid taking steps, but there are nevertheless a bounded number of write calls between goto statements because TM1 statements are finitely long.

theorem turing.TM1to1.exists_enc_dec {Γ : Type u_1} [inhabited Γ] [fintype Γ] :
∃ (n : ) (enc : Γ → vector bool n) (dec : vector bool n → Γ), enc (default Γ) = vector.repeat ff n ∀ (a : Γ), dec (enc a) = a

inductive turing.TM1to1.Λ' {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] :
Type (max u_1 u_2 u_3)

The configuration state of the TM.

@[instance]
def turing.TM1to1.inhabited {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] :

Equations
def turing.TM1to1.read_aux {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (n : ) :

Read a vector of length n from the tape.

Equations
def turing.TM1to1.move {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] :

A move left or right corresponds to n moves across the super-cell.

Equations
def turing.TM1to1.read {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } :

To read a symbol from the tape, we use read_aux to traverse the symbol, then return to the original position with n moves to the left.

Equations
def turing.TM1to1.write {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] :

Write a list of bools on the tape.

Equations
def turing.TM1to1.tr_normal {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } :

Translate a normal instruction. For the write command, we use a goto indirection so that we can access the current value of the tape.

Equations
theorem turing.TM1to1.step_aux_move {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } (d : turing.dir) (q : turing.TM1.stmt bool turing.TM1to1.Λ' σ) (v : σ) (T : turing.tape bool) :

theorem turing.TM1to1.supports_stmt_read {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } (dec : vector bool n → Γ) {S : finset turing.TM1to1.Λ'} {f : Γ → turing.TM1.stmt bool turing.TM1to1.Λ' σ} :

def turing.TM1to1.tr_tape' {Γ : Type u_1} [inhabited Γ] {n : } {enc : Γ → vector bool n} :

The low level tape corresponding to the given tape over alphabet Γ.

Equations
def turing.TM1to1.tr_tape {Γ : Type u_1} [inhabited Γ] {n : } {enc : Γ → vector bool n} :

The low level tape corresponding to the given tape over alphabet Γ.

Equations
theorem turing.TM1to1.tr_tape_mk' {Γ : Type u_1} [inhabited Γ] {n : } {enc : Γ → vector bool n} (enc0 : enc (default Γ) = vector.repeat ff n) (L R : turing.list_blank Γ) :

def turing.TM1to1.tr {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } :
(Γ → vector bool n)(vector bool n → Γ)(Λ → turing.TM1.stmt Γ Λ σ)turing.TM1to1.Λ'turing.TM1.stmt bool turing.TM1to1.Λ' σ

The top level program.

Equations
def turing.TM1to1.tr_cfg {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } {enc : Γ → vector bool n} :

The machine configuration translation.

Equations
theorem turing.TM1to1.step_aux_write {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } {enc : Γ → vector bool n} (enc0 : enc (default Γ) = vector.repeat ff n) (q : turing.TM1.stmt bool turing.TM1to1.Λ' σ) (v : σ) (a b : Γ) (L R : turing.list_blank Γ) :

theorem turing.TM1to1.step_aux_read {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } {enc : Γ → vector bool n} (dec : vector bool n → Γ) (enc0 : enc (default Γ) = vector.repeat ff n) (encdec : ∀ (a : Γ), dec (enc a) = a) (f : Γ → turing.TM1.stmt bool turing.TM1to1.Λ' σ) (v : σ) (L R : turing.list_blank Γ) :

theorem turing.TM1to1.tr_respects {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } {enc : Γ → vector bool n} (dec : vector bool n → Γ) (enc0 : enc (default Γ) = vector.repeat ff n) (M : Λ → turing.TM1.stmt Γ Λ σ) :
(∀ (a : Γ), dec (enc a) = a)turing.respects (turing.TM1.step M) (turing.TM1.step (turing.TM1to1.tr enc dec M)) (λ (c₁ : turing.TM1.cfg Γ Λ σ) (c₂ : turing.TM1.cfg bool turing.TM1to1.Λ' σ), turing.TM1to1.tr_cfg enc0 c₁ = c₂)

def turing.TM1to1.tr_supp {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] (M : Λ → turing.TM1.stmt Γ Λ σ) [fintype Γ] :

The set of accessible machine states, assuming that the input machine is supported on S, are the normal states embedded from S, plus all write states accessible from these states.

Equations
theorem turing.TM1to1.tr_supports {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] {σ : Type u_3} [inhabited σ] {n : } {enc : Γ → vector bool n} (dec : vector bool n → Γ) (M : Λ → turing.TM1.stmt Γ Λ σ) [fintype Γ] {S : finset Λ} :

TM0 emulator in TM1

To establish that TM0 and TM1 are equivalent computational models, we must also have a TM0 emulator in TM1. The main complication here is that TM0 allows an action to depend on the value at the head and local state, while TM1 doesn't (in order to have more programming language-like semantics). So we use a computed goto to go to a state that performes the desired action and then returns to normal execution.

One issue with this is that the halt instruction is supposed to halt immediately, not take a step to a halting state. To resolve this we do a check for halt first, then goto (with an unreachable branch).

inductive turing.TM0to1.Λ' {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :
Type (max u_1 u_2)

The machine states for a TM1 emulating a TM0 machine. States of the TM0 machine are embedded as normal q states, but the actual operation is split into two parts, a jump to act s q followed by the action and a jump to the next normal state.

@[instance]
def turing.TM0to1.inhabited {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :

Equations
def turing.TM0to1.tr {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :

The program.

Equations
def turing.TM0to1.tr_cfg {Γ : Type u_1} [inhabited Γ] {Λ : Type u_2} [inhabited Λ] :

The configuration translation.

Equations

The TM2 model

The TM2 model removes the tape entirely from the TM1 model, replacing it with an arbitrary (finite) collection of stacks, each with elements of different types (the alphabet of stack k : K is Γ k). The statements are:

The configuration is a tuple (l, var, stk) where l : option Λ is the current label to run or none for the halting state, var : σ is the (finite) internal state, and stk : ∀ k, list (Γ k) is the collection of stacks. (Note that unlike the TM0 and TM1 models, these are not list_blanks, they have definite ends that can be detected by the pop command.)

Given a designated stack k and a value L : list (Γ k), the initial configuration has all the stacks empty except the designated "input" stack; in eval this designated stack also functions as the output stack.

inductive turing.TM2.stmt {K : Type u_1} [decidable_eq K] :
(K → Type u_2)Type u_3Type u_4Type (max u_1 u_2 u_3 u_4)

The TM2 model removes the tape entirely from the TM1 model, replacing it with an arbitrary (finite) collection of stacks. The operation push puts an element on one of the stacks, and pop removes an element from a stack (and modifying the internal state based on the result). peek modifies the internal state but does not remove an element.

@[instance]
def turing.TM2.stmt.inhabited {K : Type u_1} [decidable_eq K] (Γ : K → Type u_2) (Λ : Type u_3) (σ : Type u_4) :

Equations
structure turing.TM2.cfg {K : Type u_1} [decidable_eq K] :
(K → Type u_2)Type u_3Type u_4Type (max u_1 u_2 u_3 u_4)
  • l : option Λ
  • var : σ
  • stk : Π (k : K), list (Γ k)

A configuration in the TM2 model is a label (or none for the halt state), the state of local variables, and the stacks. (Note that the stacks are not list_blanks, they have a definite size.)

@[instance]
def turing.TM2.cfg.inhabited {K : Type u_1} [decidable_eq K] (Γ : K → Type u_2) (Λ : Type u_3) (σ : Type u_4) [inhabited σ] :

Equations
@[simp]
def turing.TM2.step_aux {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} :
turing.TM2.stmt Γ Λ σσ → (Π (k : K), list (Γ k))turing.TM2.cfg Γ Λ σ

The step function for the TM2 model.

Equations
@[simp]
def turing.TM2.step {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} :
(Λ → turing.TM2.stmt Γ Λ σ)turing.TM2.cfg Γ Λ σoption (turing.TM2.cfg Γ Λ σ)

The step function for the TM2 model.

Equations
def turing.TM2.reaches {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} :
(Λ → turing.TM2.stmt Γ Λ σ)turing.TM2.cfg Γ Λ σturing.TM2.cfg Γ Λ σ → Prop

The (reflexive) reachability relation for the TM2 model.

Equations
theorem turing.TM2.stmts₁_self {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} {q : turing.TM2.stmt Γ Λ σ} :

theorem turing.TM2.stmts₁_trans {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} {q₁ q₂ : turing.TM2.stmt Γ Λ σ} :

theorem turing.TM2.stmts₁_supports_stmt_mono {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} {S : finset Λ} {q₁ q₂ : turing.TM2.stmt Γ Λ σ} :

def turing.TM2.stmts {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} :
(Λ → turing.TM2.stmt Γ Λ σ)finset Λfinset (option (turing.TM2.stmt Γ Λ σ))

The set of statements accessible from initial set S of labels.

Equations
theorem turing.TM2.stmts_trans {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} {M : Λ → turing.TM2.stmt Γ Λ σ} {S : finset Λ} {q₁ q₂ : turing.TM2.stmt Γ Λ σ} :

def turing.TM2.supports {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} [inhabited Λ] :
(Λ → turing.TM2.stmt Γ Λ σ)finset Λ → Prop

Given a TM2 machine M and a set S of states, supports M S means that all states in S jump only to other states in S.

Equations
theorem turing.TM2.stmts_supports_stmt {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} [inhabited Λ] {M : Λ → turing.TM2.stmt Γ Λ σ} {S : finset Λ} {q : turing.TM2.stmt Γ Λ σ} :

theorem turing.TM2.step_supports {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} [inhabited Λ] (M : Λ → turing.TM2.stmt Γ Λ σ) {S : finset Λ} (ss : turing.TM2.supports M S) {c c' : turing.TM2.cfg Γ Λ σ} :

def turing.TM2.init {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} [inhabited Λ] [inhabited σ] (k : K) :
list (Γ k)turing.TM2.cfg Γ Λ σ

The initial state of the TM2 model. The input is provided on a designated stack.

Equations
def turing.TM2.eval {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} [inhabited Λ] [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) (k : K) :
list (Γ k)roption (list (Γ k))

Evaluates a TM2 program to completion, with the output on the same stack as the input.

Equations

TM2 emulator in TM1

To prove that TM2 computable functions are TM1 computable, we need to reduce each TM2 program to a TM1 program. So suppose a TM2 program is given. This program has to maintain a whole collection of stacks, but we have only one tape, so we must "multiplex" them all together. Pictorially, if stack 1 contains [a, b] and stack 2 contains [c, d, e, f] then the tape looks like this:

 bottom:  ... | _ | T | _ | _ | _ | _ | ...
 stack 1: ... | _ | b | a | _ | _ | _ | ...
 stack 2: ... | _ | f | e | d | c | _ | ...

where a tape element is a vertical slice through the diagram. Here the alphabet is Γ' := bool × ∀ k, option (Γ k), where:

In "resting" position, the TM is sitting at the position marked bottom. For non-stack actions, it operates in place, but for the stack actions push, peek, and pop, it must shuttle to the end of the appropriate stack, make its changes, and then return to the bottom. So the states are:

Because of the shuttling, emulation overhead is O(n), where n is the current maximum of the length of all stacks. Therefore a program that takes k steps to run in TM2 takes O((m+k)k) steps to run when emulated in TM1, where m is the length of the input.

theorem turing.TM2to1.stk_nth_val {K : Type u_1} {Γ : K → Type u_2} {L : turing.list_blank (Π (k : K), option (Γ k))} {k : K} {S : list (Γ k)} (n : ) :

@[nolint]
def turing.TM2to1.Γ' {K : Type u_1} [decidable_eq K] :
Π {Γ : K → Type u_2}, Type (max u_1 u_2)

The alphabet of the TM2 simulator on TM1 is a marker for the stack bottom, plus a vector of stack elements for each stack, or none if the stack does not extend this far.

Equations
@[instance]
def turing.TM2to1.Γ'.inhabited {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} :

Equations
@[instance]
def turing.TM2to1.Γ'.fintype {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} [fintype K] [Π (k : K), fintype (Γ k)] :

Equations
def turing.TM2to1.add_bottom {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} :

The bottom marker is fixed throughout the calculation, so we use the add_bottom function to express the program state in terms of a tape with only the stacks themselves.

Equations
theorem turing.TM2to1.add_bottom_map {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} (L : turing.list_blank (Π (k : K), option (Γ k))) :

theorem turing.TM2to1.add_bottom_modify_nth {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} (f : (Π (k : K), option (Γ k))Π (k : K), option (Γ k)) (L : turing.list_blank (Π (k : K), option (Γ k))) (n : ) :

theorem turing.TM2to1.add_bottom_nth_snd {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} (L : turing.list_blank (Π (k : K), option (Γ k))) (n : ) :

theorem turing.TM2to1.add_bottom_nth_succ_fst {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} (L : turing.list_blank (Π (k : K), option (Γ k))) (n : ) :

theorem turing.TM2to1.add_bottom_head_fst {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} (L : turing.list_blank (Π (k : K), option (Γ k))) :

inductive turing.TM2to1.st_act {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {σ : Type u_4} [inhabited σ] :
K → Type (max u_2 u_4)

A stack action is a command that interacts with the top of a stack. Our default position is at the bottom of all the stacks, so we have to hold on to this action while going to the end to modify the stack.

@[instance]
def turing.TM2to1.st_act.inhabited {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {σ : Type u_4} [inhabited σ] {k : K} :

Equations
@[nolint]
def turing.TM2to1.st_run {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {k : K} :

The TM2 statement corresponding to a stack action.

Equations
def turing.TM2to1.st_var {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {σ : Type u_4} [inhabited σ] {k : K} :
σ → list (Γ k)turing.TM2to1.st_act k → σ

The effect of a stack action on the local variables, given the value of the stack.

Equations
def turing.TM2to1.st_write {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {σ : Type u_4} [inhabited σ] {k : K} :
σ → list (Γ k)turing.TM2to1.st_act klist (Γ k)

The effect of a stack action on the stack.

Equations
def turing.TM2to1.stmt_st_rec {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {C : turing.TM2.stmt Γ Λ σSort l} (H₁ : Π (k : K) (s : turing.TM2to1.st_act k) (q : turing.TM2.stmt Γ Λ σ), C qC (turing.TM2to1.st_run s q)) (H₂ : Π (a : σ → σ) (q : turing.TM2.stmt Γ Λ σ), C qC (turing.TM2.stmt.load a q)) (H₃ : Π (p : σ → bool) (q₁ q₂ : turing.TM2.stmt Γ Λ σ), C q₁C q₂C (turing.TM2.stmt.branch p q₁ q₂)) (H₄ : Π (l : σ → Λ), C (turing.TM2.stmt.goto l)) (H₅ : C turing.TM2.stmt.halt) (n : turing.TM2.stmt Γ Λ σ) :
C n

We have partitioned the TM2 statements into "stack actions", which require going to the end of the stack, and all other actions, which do not. This is a modified recursor which lumps the stack actions into one.

Equations
theorem turing.TM2to1.supports_run {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (S : finset Λ) {k : K} (s : turing.TM2to1.st_act k) (q : turing.TM2.stmt Γ Λ σ) :

inductive turing.TM2to1.Λ' {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] :
Type (max u_1 u_2 u_3 u_4)

The machine states of the TM2 emulator. We can either be in a normal state when waiting for the next TM2 action, or we can be in the "go" and "return" states to go to the top of the stack and return to the bottom, respectively.

@[instance]
def turing.TM2to1.Λ'.inhabited {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] :

Equations
def turing.TM2to1.tr_st_act {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {k : K} :

The program corresponding to state transitions at the end of a stack. Here we start out just after the top of the stack, and should end just after the new top of the stack.

Equations
def turing.TM2to1.tr_init {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} (k : K) :

The initial state for the TM2 emulator, given an initial TM2 state. All stacks start out empty except for the input stack, and the stack bottom mark is set at the head.

Equations
theorem turing.TM2to1.step_run {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {k : K} (q : turing.TM2.stmt Γ Λ σ) (v : σ) (S : Π (k : K), list (Γ k)) (s : turing.TM2to1.st_act k) :

theorem turing.TM2to1.tr_normal_run {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {k : K} (s : turing.TM2to1.st_act k) (q : turing.TM2.stmt Γ Λ σ) :

theorem turing.TM2to1.tr_stmts₁_run {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {k : K} {s : turing.TM2to1.st_act k} {q : turing.TM2.stmt Γ Λ σ} :

theorem turing.TM2to1.tr_respects_aux₂ {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] {k : K} {q : turing.TM1.stmt turing.TM2to1.Γ' turing.TM2to1.Λ' σ} {v : σ} {S : Π (k : K), list (Γ k)} {L : turing.list_blank (Π (k : K), option (Γ k))} (hL : ∀ (k : K), turing.list_blank.map (turing.proj k) L = turing.list_blank.mk (list.map some (S k)).reverse) (o : turing.TM2to1.st_act k) :

def turing.TM2to1.tr {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] :

The TM2 emulator machine states written as a TM1 program. This handles the go and ret states, which shuttle to and from a stack top.

Equations
inductive turing.TM2to1.tr_cfg {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] :

The relation between TM2 configurations and TM1 configurations of the TM2 emulator.

theorem turing.TM2to1.tr_respects_aux₁ {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) {k : K} (o : turing.TM2to1.st_act k) (q : turing.TM2.stmt Γ Λ σ) (v : σ) {S : list (Γ k)} {L : turing.list_blank (Π (k : K), option (Γ k))} (hL : turing.list_blank.map (turing.proj k) L = turing.list_blank.mk (list.map some S).reverse) (n : ) :

theorem turing.TM2to1.tr_respects_aux₃ {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) {q : turing.TM2.stmt Γ Λ σ} {v : σ} {L : turing.list_blank (Π (k : K), option (Γ k))} (n : ) :

theorem turing.TM2to1.tr_respects {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) :

theorem turing.TM2to1.tr_cfg_init {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) (k : K) (L : list (Γ k)) :

theorem turing.TM2to1.tr_eval_dom {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) (k : K) (L : list (Γ k)) :

theorem turing.TM2to1.tr_eval {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) (k : K) (L : list (Γ k)) {L₁ : turing.list_blank turing.TM2to1.Γ'} {L₂ : list (Γ k)} :
L₁ turing.TM1.eval (turing.TM2to1.tr M) (turing.TM2to1.tr_init k L)L₂ turing.TM2.eval M k L(∃ (S : Π (k : K), list (Γ k)) (L' : turing.list_blank (Π (k : K), option (Γ k))), turing.TM2to1.add_bottom L' = L₁ (∀ (k : K), turing.list_blank.map (turing.proj k) L' = turing.list_blank.mk (list.map some (S k)).reverse) S k = L₂)

def turing.TM2to1.tr_supp {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] :
(Λ → turing.TM2.stmt Γ Λ σ)finset Λfinset turing.TM2to1.Λ'

The support of a set of TM2 states in the TM2 emulator.

Equations
theorem turing.TM2to1.tr_supports {K : Type u_1} [decidable_eq K] {Γ : K → Type u_2} {Λ : Type u_3} [inhabited Λ] {σ : Type u_4} [inhabited σ] (M : Λ → turing.TM2.stmt Γ Λ σ) {S : finset Λ} :