@@ -89,11 +89,15 @@ end SingleTapeTM
8989A single-tape Turing machine
9090over the alphabet of `Option Symbol` (where `none` is the blank `BiTape` symbol).
9191-/
92- structure SingleTapeTM Symbol [Inhabited Symbol] [Fintype Symbol] where
92+ structure SingleTapeTM Symbol where
93+ /-- Inhabited instance for the alphabet -/
94+ [SymbolInhabited : Inhabited Symbol]
95+ /-- Finiteness of the alphabet -/
96+ [SymbolFintype : Fintype Symbol]
9397 /-- type of state labels -/
9498 (State : Type )
9599 /-- finiteness of the state type -/
96- [stateFintype : Fintype State]
100+ [StateFintype : Fintype State]
97101 /-- Initial state -/
98102 (q₀ : State)
99103 /-- Transition function, mapping a state and a head symbol to a `Stmt` to invoke,
@@ -112,11 +116,11 @@ the step function that lets the machine transition from one configuration to the
112116and the intended initial and final configurations.
113117-/
114118
115- variable [Inhabited Symbol] [Fintype Symbol] (tm : SingleTapeTM Symbol)
119+ variable (tm : SingleTapeTM Symbol)
116120
117121instance : Inhabited tm.State := ⟨tm.q₀⟩
118122
119- instance : Fintype tm.State := tm.stateFintype
123+ instance : Fintype tm.State := tm.StateFintype
120124
121125instance inhabitedStmt : Inhabited (Stmt Symbol) := inferInstance
122126
@@ -186,8 +190,6 @@ end Cfg
186190
187191open Cfg
188192
189- variable [Inhabited Symbol] [Fintype Symbol]
190-
191193/--
192194The `TransitionRelation` corresponding to a `SingleTapeTM Symbol`
193195is defined by the `step` function,
@@ -196,6 +198,42 @@ which maps a configuration to its next configuration, if it exists.
196198@ [scoped grind =]
197199def TransitionRelation (tm : SingleTapeTM Symbol) (c₁ c₂ : tm.Cfg) : Prop := tm.step c₁ = some c₂
198200
201+ /-- The transition relation is deterministic: each configuration has at most
202+ one successor, since `step` is a function. -/
203+ lemma TransitionRelation_deterministic (tm : SingleTapeTM Symbol)
204+ (a b c : tm.Cfg) (hab : tm.TransitionRelation a b) (hac : tm.TransitionRelation a c) :
205+ b = c := by
206+ simp only [TransitionRelation] at hab hac
207+ rw [hab] at hac
208+ exact Option.some.inj hac
209+
210+ /-- No transitions from a halted configuration (state = none). -/
211+ lemma no_step_from_halt (tm : SingleTapeTM Symbol) (cfg cfg' : tm.Cfg)
212+ (h : cfg.state = none) : ¬tm.TransitionRelation cfg cfg' := by
213+ simp only [TransitionRelation, step]
214+ cases cfg with | mk state tape => subst h; simp
215+
216+ /-- In a deterministic relation where the endpoint has no successors,
217+ any chain starting from the same origin has length at most `n`. -/
218+ lemma reachable_steps_le_halting_steps (tm : SingleTapeTM Symbol)
219+ {a b : tm.Cfg} {n : ℕ} (hab : RelatesInSteps tm.TransitionRelation a b n)
220+ (hhalt : ∀ cfg', ¬tm.TransitionRelation b cfg')
221+ {c : tm.Cfg} {m : ℕ} (hac : RelatesInSteps tm.TransitionRelation a c m) :
222+ m ≤ n := by
223+ induction m generalizing a n with
224+ | zero => omega
225+ | succ k ih =>
226+ obtain ⟨a', ha_a', hac'⟩ := hac.succ'
227+ match n, hab with
228+ | 0 , hab =>
229+ have := hab.zero; subst this
230+ exact absurd ha_a' (hhalt a')
231+ | n'+1 , hab =>
232+ obtain ⟨a'', ha_a'', hab'⟩ := hab.succ'
233+ have := TransitionRelation_deterministic tm a a' a'' ha_a' ha_a''
234+ subst this
235+ exact Nat.succ_le_succ (ih hab' hac')
236+
199237/-- A proof of `tm` outputting `l'` on input `l`. -/
200238def Outputs (tm : SingleTapeTM Symbol) (l l' : List Symbol) : Prop :=
201239 ReflTransGen tm.TransitionRelation (initCfg tm l) (haltCfg tm l')
@@ -220,6 +258,8 @@ lemma output_length_le_input_length_add_time (tm : SingleTapeTM Symbol) (l l' :
220258
221259section Computers
222260
261+ variable [Inhabited Symbol] [Fintype Symbol]
262+
223263/-- A Turing machine computing the identity. -/
224264def idComputer : SingleTapeTM Symbol where
225265 State := PUnit
@@ -374,6 +414,38 @@ end compComputerLemmas
374414
375415end Computers
376416
417+ /-!
418+ ## Monotone Envelope
419+
420+ The running maximum of a function, used to convert arbitrary time bounds
421+ into monotone time bounds without changing the underlying Turing machine.
422+ -/
423+
424+ /-- The running maximum of `f`: `monotoneEnvelope f n = max (f 0) (f 1) ⋯ (f n)`. -/
425+ def monotoneEnvelope (f : ℕ → ℕ) : ℕ → ℕ
426+ | 0 => f 0
427+ | n + 1 => max (monotoneEnvelope f n) (f (n + 1 ))
428+
429+ theorem monotoneEnvelope_mono (f : ℕ → ℕ) : Monotone (monotoneEnvelope f) := by
430+ intro a b hab
431+ induction hab with
432+ | refl => exact le_refl _
433+ | step _ ih => exact le_trans ih (le_max_left _ _)
434+
435+ theorem le_monotoneEnvelope (f : ℕ → ℕ) (n : ℕ) : f n ≤ monotoneEnvelope f n := by
436+ cases n with
437+ | zero => exact le_refl _
438+ | succ n => exact le_max_right _ _
439+
440+ theorem monotoneEnvelope_le_of_le_monotone {f g : ℕ → ℕ}
441+ (hle : ∀ n, f n ≤ g n) (hg : Monotone g) (n : ℕ) :
442+ monotoneEnvelope f n ≤ g n := by
443+ induction n with
444+ | zero => exact hle 0
445+ | succ n ih =>
446+ simp only [monotoneEnvelope]
447+ exact max_le (le_trans ih (hg (Nat.le_succ n))) (hle (n + 1 ))
448+
377449/-!
378450## Time Computability
379451
@@ -401,6 +473,15 @@ def TimeComputable.id : TimeComputable (Symbol := Symbol) id where
401473 time_bound _ := 1
402474 outputsFunInTime _ := ⟨1 , le_rfl, RelatesInSteps.single rfl⟩
403475
476+ /-- Convert a `TimeComputable` to one with a monotone time bound,
477+ using the same TM but replacing the time bound with its monotone envelope. -/
478+ def TimeComputable.toMonotone {f : List Symbol → List Symbol}
479+ (hf : TimeComputable f) : TimeComputable f where
480+ tm := hf.tm
481+ time_bound := monotoneEnvelope hf.time_bound
482+ outputsFunInTime a := RelatesWithinSteps.of_le
483+ (hf.outputsFunInTime a) (le_monotoneEnvelope hf.time_bound a.length)
484+
404485/--
405486Time bounds for `compComputer`.
406487
@@ -410,46 +491,44 @@ The `compComputer` of two machines which have time bounds is bounded by
410491* added to the time taken by the second machine on the output size of the first machine
411492 (which is itself bounded by the time taken by the first machine)
412493
413- Note that we require the time function of the second machine to be monotone;
414- this is to ensure that if the first machine returns an output
415- which is shorter than the maximum possible length of output for that input size,
416- then the time bound for the second machine still holds for that shorter input to the second machine.
494+ The time bound of the second machine is automatically made monotone using
495+ `monotoneEnvelope`, so the caller does not need to supply a monotonicity proof.
417496-/
418497def TimeComputable.comp {f g : List Symbol → List Symbol}
419- (hf : TimeComputable f) (hg : TimeComputable g)
420- (h_mono : Monotone hg.time_bound) :
421- (TimeComputable (g ∘ f)) where
422- tm := compComputer hf.tm hg.tm
423- -- perhaps it would be good to track the blow up separately?
424- time_bound l := (hf.time_bound l) + hg.time_bound (max 1 l + hf.time_bound l)
425- outputsFunInTime a := by
426- have hf_outputsFun := hf.outputsFunInTime a
427- have hg_outputsFun := hg.outputsFunInTime (f a)
428- simp only [OutputsWithinTime, initCfg, compComputer_q₀_eq, Function.comp_apply,
429- haltCfg] at hg_outputsFun hf_outputsFun ⊢
430- -- The computer reduces a to f a in time hf.time_bound a.length
431- have h_a_reducesTo_f_a :
432- RelatesWithinSteps (compComputer hf.tm hg.tm).TransitionRelation
433- (initialCfg hf.tm hg.tm a)
434- (intermediateCfg hf.tm hg.tm (f a))
435- (hf.time_bound a.length) :=
436- comp_left_relatesWithinSteps hf.tm hg.tm a (f a)
437- (hf.time_bound a.length) hf_outputsFun
438- -- The computer reduces f a to g (f a) in time hg.time_bound (f a).length
439- have h_f_a_reducesTo_g_f_a :
440- RelatesWithinSteps (compComputer hf.tm hg.tm).TransitionRelation
441- (intermediateCfg hf.tm hg.tm (f a))
442- (finalCfg hf.tm hg.tm (g (f a)))
443- (hg.time_bound (f a).length) :=
444- comp_right_relatesWithinSteps hf.tm hg.tm (f a) (g (f a))
445- (hg.time_bound (f a).length) hg_outputsFun
446- -- Therefore, the computer reduces a to g (f a) in the sum of those times.
447- have h_a_reducesTo_g_f_a := RelatesWithinSteps.trans h_a_reducesTo_f_a h_f_a_reducesTo_g_f_a
448- apply RelatesWithinSteps.of_le h_a_reducesTo_g_f_a
449- refine Nat.add_le_add_left ?_ (hf.time_bound a.length)
450- · apply h_mono
451- -- Use the lemma about output length being bounded by input length + time
452- exact output_length_le_input_length_add_time hf.tm _ _ _ (hf.outputsFunInTime a)
498+ (hf : TimeComputable f) (hg : TimeComputable g) :
499+ (TimeComputable (g ∘ f)) :=
500+ let hg' := hg.toMonotone
501+ { tm := compComputer hf.tm hg' .tm
502+ -- perhaps it would be good to track the blow up separately?
503+ time_bound := fun l => (hf.time_bound l) + hg' .time_bound (max 1 l + hf.time_bound l)
504+ outputsFunInTime := fun a => by
505+ have hf_outputsFun := hf.outputsFunInTime a
506+ have hg_outputsFun := hg' .outputsFunInTime (f a)
507+ simp only [OutputsWithinTime, initCfg, compComputer_q₀_eq, Function.comp_apply,
508+ haltCfg] at hg_outputsFun hf_outputsFun ⊢
509+ -- The computer reduces a to f a in time hf.time_bound a.length
510+ have h_a_reducesTo_f_a :
511+ RelatesWithinSteps (compComputer hf.tm hg' .tm).TransitionRelation
512+ (initialCfg hf.tm hg' .tm a)
513+ (intermediateCfg hf.tm hg' .tm (f a))
514+ (hf.time_bound a.length) :=
515+ comp_left_relatesWithinSteps hf.tm hg' .tm a (f a)
516+ (hf.time_bound a.length) hf_outputsFun
517+ -- The computer reduces f a to g (f a) in time hg' .time_bound (f a).length
518+ have h_f_a_reducesTo_g_f_a :
519+ RelatesWithinSteps (compComputer hf.tm hg' .tm).TransitionRelation
520+ (intermediateCfg hf.tm hg' .tm (f a))
521+ (finalCfg hf.tm hg' .tm (g (f a)))
522+ (hg' .time_bound (f a).length) :=
523+ comp_right_relatesWithinSteps hf.tm hg' .tm (f a) (g (f a))
524+ (hg' .time_bound (f a).length) hg_outputsFun
525+ -- Therefore, the computer reduces a to g (f a) in the sum of those times.
526+ have h_a_reducesTo_g_f_a := RelatesWithinSteps.trans h_a_reducesTo_f_a h_f_a_reducesTo_g_f_a
527+ apply RelatesWithinSteps.of_le h_a_reducesTo_g_f_a
528+ refine Nat.add_le_add_left ?_ (hf.time_bound a.length)
529+ · apply monotoneEnvelope_mono
530+ -- Use the lemma about output length being bounded by input length + time
531+ exact output_length_le_input_length_add_time hf.tm _ _ _ (hf.outputsFunInTime a) }
453532
454533end TimeComputable
455534
@@ -468,6 +547,17 @@ section PolyTimeComputable
468547
469548open Polynomial
470549
550+ /-- Evaluation of a polynomial with natural number coefficients is monotone. -/
551+ private theorem poly_eval_nat_mono (p : Polynomial ℕ) : Monotone (fun n => p.eval n) := by
552+ intro a b hab
553+ induction p using Polynomial.induction_on' with
554+ | add p q ihp ihq =>
555+ simp only [eval_add]
556+ exact Nat.add_le_add (ihp) (ihq)
557+ | monomial n c =>
558+ simp only [eval_monomial]
559+ exact Nat.mul_le_mul_left c (pow_le_pow_left' hab n)
560+
471561variable [Inhabited Symbol] [Fintype Symbol]
472562
473563/-- A Turing machine + a polynomial time function +
@@ -479,27 +569,32 @@ structure PolyTimeComputable (f : List Symbol → List Symbol) extends TimeCompu
479569 bounds : ∀ n, time_bound n ≤ poly.eval n
480570
481571/-- A proof that the identity map on Symbol is computable in polytime. -/
482- noncomputable def PolyTimeComputable.id : PolyTimeComputable (Symbol := Symbol) id where
572+ noncomputable def PolyTimeComputable.id : @ PolyTimeComputable (Symbol := Symbol) id where
483573 toTimeComputable := TimeComputable.id
484574 poly := 1
485575 bounds _ := by simp [TimeComputable.id]
486576
487- -- TODO remove `h_mono` assumption
488- -- by developing function to convert PolyTimeComputable into one with monotone time bound
489577/--
490578A proof that the composition of two polytime computable functions is polytime computable.
579+
580+ The monotonicity of time bounds is handled internally via `monotoneEnvelope`,
581+ so no monotonicity assumption is needed from the caller.
491582-/
492583noncomputable def PolyTimeComputable.comp {f g : List Symbol → List Symbol}
493- (hf : PolyTimeComputable f) (hg : PolyTimeComputable g)
494- (h_mono : Monotone hg.time_bound) :
584+ (hf : PolyTimeComputable f) (hg : PolyTimeComputable g) :
495585 PolyTimeComputable (g ∘ f) where
496- toTimeComputable := TimeComputable.comp hf.toTimeComputable hg.toTimeComputable h_mono
586+ toTimeComputable := TimeComputable.comp hf.toTimeComputable hg.toTimeComputable
497587 poly := hf.poly + hg.poly.comp (1 + X + hf.poly)
498588 bounds n := by
499- simp only [TimeComputable.comp, eval_add, eval_comp, eval_X, eval_one]
589+ simp only [TimeComputable.comp, TimeComputable.toMonotone, eval_add, eval_comp, eval_X,
590+ eval_one]
500591 apply add_le_add
501592 · exact hf.bounds n
502- · exact (h_mono (add_le_add (by omega) (hf.bounds n))).trans (hg.bounds _)
593+ · calc monotoneEnvelope hg.time_bound (max 1 n + hf.time_bound n)
594+ _ ≤ hg.poly.eval (max 1 n + hf.time_bound n) :=
595+ monotoneEnvelope_le_of_le_monotone hg.bounds (poly_eval_nat_mono hg.poly) _
596+ _ ≤ hg.poly.eval (1 + n + hf.poly.eval n) :=
597+ poly_eval_nat_mono hg.poly (add_le_add (by omega) (hf.bounds n))
503598
504599end PolyTimeComputable
505600
0 commit comments