From 415d328669b3160ba61fec83eb334ada48a05af9 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 07:56:51 -0400 Subject: [PATCH 01/16] feat(Classes/Time): explicit-time verifier characterization of NTIME (#1572) Guess and verify within explicit time (`setOf_exists_mem_NTIME`), through a closed fair-coin composition with the new framed sampler `pairSampler`, and a linear-time verifier for every problem in `NTIME` (`exists_verifier_of_mem_NTIME`). Infrastructure toward the nondeterministic time hierarchy. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../Complexity/Classes/Time/Verifier.lean | 266 +++++++++++++++++ .../MultiTape/Probabilistic/PairSampler.lean | 268 ++++++++++++++++++ .../NondeterministicTimeVerifiers.lean | 85 ++++++ 4 files changed, 621 insertions(+) create mode 100644 Complexity/Computability/Complexity/Classes/Time/Verifier.lean create mode 100644 Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean create mode 100644 ComplexityTests/NondeterministicTimeVerifiers.lean diff --git a/Complexity.lean b/Complexity.lean index d8146fc83f..7eddad2ea2 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1327,6 +1327,7 @@ public import Complexity.Computability.Complexity.Classes.Tally public import Complexity.Computability.Complexity.Classes.Time public import Complexity.Computability.Complexity.Classes.Time.AcceptCount public import Complexity.Computability.Complexity.Classes.Time.Polynomial +public import Complexity.Computability.Complexity.Classes.Time.Verifier public import Complexity.Computability.Complexity.Classes.Toda public import Complexity.Computability.Complexity.Classes.Toda.Counting public import Complexity.Computability.Complexity.Classes.Toda.Hierarchy @@ -1990,6 +1991,7 @@ public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.O public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.Oracle public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.OutputPrefix public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.OutputToTape +public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PairSampler public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PolynomialClock public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.QueryBounds public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.QueryTake diff --git a/Complexity/Computability/Complexity/Classes/Time/Verifier.lean b/Complexity/Computability/Complexity/Classes/Time/Verifier.lean new file mode 100644 index 0000000000..89b4f594ab --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Time/Verifier.lean @@ -0,0 +1,266 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Time +import Complexity.Computability.Complexity.Classes.NP.Nondeterministic +import Complexity.Computability.Machines.Turing.MultiTape.Internal.EqualityPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.OCombinators +import Complexity.Computability.Machines.Turing.MultiTape.Internal.ResourceFacts +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.Composition +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PairSampler +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.ReplayInput +import Complexity.Computability.Machines.Turing.MultiTape.Run +import Complexity.Probability.BitString +import Complexity.Probability.PMF.Bind + +/-! +# Nondeterministic time through verifiers, with explicit bounds + +This module characterizes `Cslib.Complexity.NTIME enc t` by deterministic verifiers, keeping the +time bound up to constant factors. The polynomial characterization of `NP` +(`Cslib.Complexity.mem_NP_iff_decidableInPolyTime`) loses the degree in both directions. The +two directions here keep it. + +* **Guess and verify** (`Cslib.Complexity.setOf_exists_mem_NTIME`). Suppose a verifier `V` is + computable within time `T (a, w)` on encoded pairs, and a certificate length `ℓ a` is + computable in unary within time `Tℓ a`. Then the problem + `{a | ∃ w, w.length = ℓ a ∧ V (a, w) = true}` lies in `NTIME enc t` whenever + `(enc a).length + Tℓ a + T (a, w)` is within `O(t (enc a).length)` on certificates of that + length. The certificate length may depend on the instance, not only on its encoded length. + The machine is the nondeterministic reading of a closed fair-coin composition: + - a deterministic machine writes `frame (enc a)` followed by `ℓ` ones; + - the fixed sampler `Turing.MultiTapePTM.pairSampler` copies the framed instance and replaces + the ones by fair coins; + - a deterministic machine runs the verifier. + + Its computation paths are exactly the coin replays, so it accepts exactly when some + certificate is accepted, and every branch halts within the composed clock. +* **Verifiers from nondeterministic time** (`Cslib.Complexity.exists_verifier_of_mem_NTIME`). + A problem in `NTIME enc t` has a verifier that is computable in linear time on encoded pairs: + check that the certificate, read as coins of the choice table + (`Turing.MultiTapeNTM.checkChoices`), replays an accepting computation. Every certificate + length of at least `c * t (enc a).length + c` works, for a constant `c`. + +These are explicit-time forms of the guess-and-verify characterization of nondeterministic +polynomial time [Arora and Barak][AroraBarak09]: the choices of an accepting computation are a +certificate, and a certificate can be guessed and then checked. + +## Main results + +* `Cslib.Complexity.setOf_exists_mem_NTIME`: guess and verify within explicit time. +* `Cslib.Complexity.exists_verifier_of_mem_NTIME`: a linear-time verifier for a problem in + `NTIME`, with certificates of every sufficient length. + +## References + +* [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*][AroraBarak09] +-/ + +public section + +namespace Cslib.Complexity + +open _root_.Turing _root_.Turing.MultiTapeTM + +variable {α : Type} {enc : α ↪ List Bool} + +/-- **Guess and verify, within explicit time.** If a verifier `V` is computable within time +`T (a, w)` on encoded pairs, and the certificate length `ℓ a` is computable in unary within time +`Tℓ a`, then the problem of instances with an accepted certificate of that length is in +`NTIME enc t`, provided `(enc a).length + Tℓ a + T (a, w) ≤ C * t (enc a).length + C` for every +certificate `w` of that length. -/ +theorem setOf_exists_mem_NTIME {V : α × List Bool → Bool} {ℓ : α → ℕ} {t : ℕ → ℕ} + {Tℓ Sℓ : α → ℕ} {T S : α × List Bool → ℕ} {C : ℕ} + (hℓ : ComputableInO ℓ enc encUnary Tℓ Sℓ) + (hV : ComputableInO V (encPair enc encBits) encBool T S) + (ht : ∀ a w, w.length = ℓ a → (enc a).length + Tℓ a + T (a, w) ≤ C * t (enc a).length + C) : + {a | ∃ w : List Bool, w.length = ℓ a ∧ V (a, w) = true} ∈ NTIME enc t := by + -- the deterministic preparation and the verifier, as machines + obtain ⟨c₁, ⟨k₁, Q₁, _, tm₁, h₁⟩, -⟩ := + (ComputableInO.of_eq (f := fun a : α => a) (encIn := enc) (encMid := enc) fun _ => rfl).prod hℓ + obtain ⟨c₃, ⟨k₃, Q₃, _, tm₃, h₃⟩, -⟩ := hV + let L (a : α) : ℕ := ℓ a + let u (a : α) : List Bool := frame (enc a) ++ List.replicate (L a) true + let t₁ (a : α) : ℕ := c₁ * ((enc a).length + Tℓ a + 1) + let t₂ (a : α) : ℕ := (u a).length + 1 + let t₃ (a : α) : ℕ := c₃ * (C * t (enc a).length + C + 1) + have hrun₁ (a : α) := (h₁ a).runFrom_of_le (le_refl (t₁ a)) + have hlen₁ (a : α) : (u a).length ≤ t₁ a := (h₁ a).length_output_le + -- the first phase writes the framed instance and the unary certificate length + let M₁ : MultiTapePTM k₁ Bool Q₁ := MultiTapePTM.ofDeterministic tm₁ + have hfirst₁ (a : α) : ∀ final ∈ (ProbComp.eval + (M₁.runClosedConfigFrom (t₁ a) (M₁.initialConfig (enc a)))).support, + final.tapes.state = none := by + intro final hfinal + rw [MultiTapePTM.eval_runClosedConfigFrom_ofDeterministic, PMF.mem_support_pure_iff] at hfinal + subst hfinal + exact (hrun₁ a).1 + have hout₁ (a : α) : ProbComp.eval (M₁.runClosed (t₁ a) (enc a)) = PMF.pure (u a) := by + rw [MultiTapePTM.eval_runClosed_ofDeterministic, (hrun₁ a).2] + rfl + -- the sampler copies the framed instance and guesses the certificate + have hsecond₁ (a : α) : ∀ word ∈ (ProbComp.eval (M₁.runClosed (t₁ a) (enc a))).support, + ∀ final ∈ (ProbComp.eval (MultiTapePTM.pairSampler.runClosedConfigFrom (t₂ a) + (MultiTapePTM.pairSampler.initialConfig word))).support, final.tapes.state = none := by + intro word hword + rw [hout₁ a, PMF.mem_support_pure_iff] at hword + subst hword + exact MultiTapePTM.pairSampler_halted (enc a) (List.replicate (L a) true) + let P₁₂ := MultiTapePTM.pairSampler.comp M₁ false + let t₁₂ (a : α) : ℕ := t₁ a + (t₁ a + 4 + t₂ a) + have heval₁₂ (a : α) : ProbComp.eval (P₁₂.runClosed (t₁₂ a) (enc a)) = + (BitString.uniformList (L a)).map (frame (enc a) ++ ·) := by + rw [MultiTapePTM.eval_comp M₁ MultiTapePTM.pairSampler false (enc a) (t₁ a) (t₂ a) + (hfirst₁ a) (hsecond₁ a), hout₁ a, Probability.PMF.monad_bind_eq_bind, PMF.pure_bind] + simpa only [List.length_replicate, u] using + MultiTapePTM.eval_pairSampler (enc a) (List.replicate (L a) true) + have hhalt₁₂ (a : α) := MultiTapePTM.comp_halted M₁ MultiTapePTM.pairSampler false (enc a) + (t₁ a) (t₂ a) (hfirst₁ a) (hsecond₁ a) + -- the verifier runs on every guessed pair within the uniform clock `t₃` + let M₃ : MultiTapePTM k₃ Bool Q₃ := MultiTapePTM.ofDeterministic tm₃ + have hrun₃ (a : α) (w : List Bool) (hw : w.length = L a) := + (h₃ (a, w)).runFrom_of_le (J := t₃ a) (Nat.mul_le_mul_left c₃ (by + have := ht a w hw + omega)) + have hword (a : α) {word : List Bool} + (hword : word ∈ (ProbComp.eval (P₁₂.runClosed (t₁₂ a) (enc a))).support) : + ∃ w : List Bool, w.length = L a ∧ word = encPair enc encBits (a, w) := by + rw [heval₁₂ a] at hword + obtain ⟨w, hw, rfl⟩ := (PMF.mem_support_map_iff _ _ _).mp hword + exact ⟨w, BitString.mem_support_uniformList_iff.mp hw, rfl⟩ + have hsecond₂ (a : α) : ∀ word ∈ (ProbComp.eval (P₁₂.runClosed (t₁₂ a) (enc a))).support, + ∀ final ∈ (ProbComp.eval (M₃.runClosedConfigFrom (t₃ a) (M₃.initialConfig word))).support, + final.tapes.state = none := by + intro word hw final hfinal + obtain ⟨w, hwlen, rfl⟩ := hword a hw + rw [MultiTapePTM.eval_runClosedConfigFrom_ofDeterministic, PMF.mem_support_pure_iff] at hfinal + subst hfinal + exact (hrun₃ a w hwlen).1 + let P := M₃.comp P₁₂ false + let total (a : α) : ℕ := t₁₂ a + (t₁₂ a + 4 + t₃ a) + -- the nondeterministic reading decides the problem + have hdec : P.toNTM.DecidesInTime enc + {a | ∃ w : List Bool, w.length = ℓ a ∧ V (a, w) = true} total := by + intro a + have htime : P.toNTM.RunsInTime (enc a) (total a) := P.runsInTime_toNTM + (MultiTapePTM.comp_halted P₁₂ M₃ false (enc a) (t₁₂ a) (t₃ a) (hhalt₁₂ a) (hsecond₂ a)) + refine ⟨htime, ?_⟩ + rw [MultiTapeNTM.accepts_iff_exists_time_eq htime, + P.exists_computationPath_toNTM_iff (P := fun cfg => cfg.output = [true])] + have hiff : (∃ final ∈ (ProbComp.eval (P.runClosedConfigFrom (total a) + (P.initialConfig (enc a)))).support, final.tapes.output = [true]) ↔ + [true] ∈ (ProbComp.eval (P.runClosed (total a) (enc a))).support := by + rw [MultiTapePTM.runClosed_eq_map, ProbComp.eval_map, PMF.mem_support_map_iff] + rw [hiff, MultiTapePTM.eval_comp P₁₂ M₃ false (enc a) (t₁₂ a) (t₃ a) (hhalt₁₂ a) + (hsecond₂ a), Probability.PMF.monad_bind_eq_bind, PMF.mem_support_bind_iff] + constructor + · rintro ⟨w, hw, hV⟩ + refine ⟨encPair enc encBits (a, w), ?_, ?_⟩ + · rw [heval₁₂ a] + exact (PMF.mem_support_map_iff _ _ _).mpr + ⟨w, BitString.mem_support_uniformList_iff.mpr hw, rfl⟩ + · rw [MultiTapePTM.eval_runClosed_ofDeterministic, (hrun₃ a w hw).2, PMF.mem_support_pure_iff] + simp [hV] + · rintro ⟨word, hw, hacc⟩ + obtain ⟨w, hwlen, rfl⟩ := hword a hw + rw [MultiTapePTM.eval_runClosed_ofDeterministic, (hrun₃ a w hwlen).2, + PMF.mem_support_pure_iff] at hacc + exact ⟨w, hwlen, by simpa using hacc⟩ + -- the composed clock is within `O(t)` + refine ⟨(6 * c₁ + c₃) * C, (6 * c₁ + c₃) * (C + 1) + 14, _, _, inferInstance, P.toNTM, + hdec.mono fun a => ?_⟩ + set X := C * t (enc a).length + C + 1 + have hX₁ : t₁ a ≤ c₁ * X := Nat.mul_le_mul_left c₁ (by + have := ht a (List.replicate (L a) false) (List.length_replicate ..) + omega) + have htotal : total a ≤ 6 * t₁ a + 14 + t₃ a := by + have := hlen₁ a + simp only [total, t₁₂, t₂] + omega + have hX₃ : t₃ a = c₃ * X := rfl + calc total a ≤ 6 * (c₁ * X) + 14 + c₃ * X := by + have := Nat.mul_le_mul_left 6 hX₁ + rw [hX₃] at htotal + omega + _ = (6 * c₁ + c₃) * C * t (enc a).length + ((6 * c₁ + c₃) * (C + 1) + 14) := by + simp only [X] + ring + +/-- **A linear-time verifier for nondeterministic time.** A problem in `NTIME enc t` has a +verifier computable in linear time on encoded pairs. For a constant `c`, every certificate length +`L ≥ c * t (enc a).length + c` works: `a ∈ A` exactly when some certificate of length `L` is +accepted. The verifier checks that the certificate, read as coins of the choice table, replays an +accepting computation. -/ +theorem exists_verifier_of_mem_NTIME {A : Set α} {t : ℕ → ℕ} (hA : A ∈ NTIME enc t) : + ∃ (V : α × List Bool → Bool) (c : ℕ), + ComputableInO V (encPair enc encBits) encBool (fun p => (encPair enc encBits p).length) + (fun p => (encPair enc encBits p).length) ∧ + ∀ a L, c * t (enc a).length + c ≤ L → + (a ∈ A ↔ ∃ w : List Bool, w.length = L ∧ V (a, w) = true) := by + obtain ⟨c₁, c₂, k, State, hfinite, ntm, hdec⟩ := hA + set M := Nat.card (Turing.Action k Bool State) + refine ⟨ntm.checkChoices enc, M * (c₁ + c₂), ?_, fun a L hL => + hdec.mem_iff_exists_checkChoices ?_⟩ + · -- replay the certificate with the choice table, then compare the output with `[true]` + have hswap : ComputableInO (fun p : α × List Bool => (p.2, enc p.1)) (encPair enc encBits) + (encPair encBits encBits) + (fun p => (encPair enc encBits p).length + + ((encPair enc encBits p).length + (enc p.1).length)) + (fun p => (encPair enc encBits p).length + + ((encPair enc encBits p).length + (enc p.1).length)) := + (ComputableInO.snd enc encBits).prod + ((ComputableInO.of_eq (f := fun x : α => enc x) (encIn := enc) (encMid := encBits) + fun _ => rfl).comp (ComputableInO.fst enc encBits)) + have hrun := (ComputableInO.of_linear + (MultiTapePTM.computableInLinearTimeAndSpace_runClosedCoins ntm.choiceTable)).comp hswap + obtain ⟨cr, hcr⟩ := hrun.length_le_time + have heq := ComputableInO.eqWords.comp ((ComputableInO.const (encIn := encPair enc encBits) + (encMid := encBits) [true]).prod hrun) + have hfun : ((fun p : List Bool × List Bool => decide (p.1 = p.2)) ∘ + fun p : α × List Bool => ([true], ((fun q : List Bool × List Bool => + ntm.choiceTable.runClosedCoins q.1 q.2) ∘ fun p : α × List Bool => (p.2, enc p.1)) p)) = + ntm.checkChoices enc := by + funext p + simp [MultiTapeNTM.checkChoices, eq_comm] + rw [hfun] at heq + -- every part is linear in the encoded pair, including the replayed output + have hbound (p : α × List Bool) : + (encPair enc encBits p).length + ((encPair enc encBits p).length + + ((encPair enc encBits p).length + (enc p.1).length) + + (encPair encBits encBits (p.2, enc p.1)).length) + + 2 * ((ntm.choiceTable.runClosedCoins p.2 (enc p.1)).length + 2) ≤ + (9 + 10 * cr) * ((encPair enc encBits p).length + 1) := by + have hc := hcr p + simp only [Function.comp_apply, encBits_apply] at hc + have hle : (enc p.1).length ≤ (encPair enc encBits p).length := by + simp only [encPair_apply, List.length_append, length_frame, encBits_apply] + omega + have hsw : (encPair encBits encBits (p.2, enc p.1)).length ≤ + 2 * (encPair enc encBits p).length := by + simp only [encPair_apply, List.length_append, length_frame, encBits_apply] + omega + generalize (encPair enc encBits p).length = N at * + generalize (ntm.choiceTable.runClosedCoins p.2 (enc p.1)).length = R at * + generalize (encPair encBits encBits (p.2, enc p.1)).length = W at * + have hR : R ≤ cr * (5 * N + 1) := hc.trans (Nat.mul_le_mul_left cr (by omega)) + nlinarith + refine heq.mono (9 + 10 * cr) (fun p => ?_) fun p => ?_ + · have := hbound p + simp only [Function.comp_apply, List.length_singleton] at this ⊢ + nlinarith + · have := hbound p + simp only [Function.comp_apply, List.length_singleton] at this ⊢ + omega + · have hmul : M * (c₁ * t (enc a).length + c₂) ≤ + M * (c₁ + c₂) * t (enc a).length + M * (c₁ + c₂) := by + rw [Nat.mul_assoc, ← Nat.mul_add] + exact Nat.mul_le_mul_left M (by nlinarith) + exact hmul.trans hL + +end Cslib.Complexity diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean new file mode 100644 index 0000000000..7b6a8192b7 --- /dev/null +++ b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean @@ -0,0 +1,268 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.Sampling +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Frame.Basic + +/-! +# Sampling a certificate beside a framed word + +`Turing.MultiTapePTM.pairSampler` is one fixed fair-coin machine with three control states and no +work tapes. On an input `frame y ++ r` it copies `frame y` to the output, then emits one fresh +fair bit for each symbol of `r`, and halts at the right boundary. So it outputs `frame y ++ w`, +the pair encoding of `(y, w)`, for a uniform word `w` of length `r.length`. It takes exactly +`(frame y ++ r).length + 1` transitions. Each copying transition also draws a coin, and ignores +it. Read nondeterministically, the machine guesses a certificate for the framed instance. + +## Main definitions + +* `Turing.MultiTapePTM.pairSampler`: the framed copier and certificate sampler. + +## Main results + +* `Turing.MultiTapePTM.eval_pairSampler`: the output is a uniform certificate after the framed + word. +* `Turing.MultiTapePTM.pairSampler_halted`: every run has halted at the linear clock. +-/ + +@[expose] public section + +namespace Turing.MultiTapePTM + +open Cslib MultiTapeMachine MultiTapeTM + +namespace PairSampler + +/-- The control phases: before a frame tag, before a framed data bit, and in the sampled tail. -/ +inductive Phase + | tag + | data + | tail + deriving DecidableEq + +instance : Fintype Phase := + ⟨{.tag, .data, .tail}, fun q => by cases q <;> simp⟩ + +/-- The control state after reading a symbol. The right boundary halts. -/ +def next : Phase → Option Bool → Option Phase + | _, none => none + | .tag, some true => some .data + | .tag, some false => some .tail + | .data, some _ => some .tag + | .tail, some _ => some .tail + +/-- The emitted symbol. Framed symbols are copied, and tail symbols are replaced by the coin. -/ +def emit : Phase → Option Bool → Bool → Option Bool + | _, none, _ => none + | .tail, some _, bit => some bit + | .tag, some b, _ => some b + | .data, some b, _ => some b + +end PairSampler + +open PairSampler in +/-- Copy a framed word, then emit one fair bit per remaining input symbol, and halt at the right +boundary, without work tapes. -/ +def pairSampler : MultiTapePTM 0 Bool Phase where + initial := .tag + tr q symbol _ _ bit := .step + { inputTape := if symbol.isSome then 1 else 0 + workTapes := Fin.elim0 + output := emit q symbol bit + state := next q symbol } + Empty.elim Empty.elim + +namespace PairSampler + +/-- The sampler at an input position, with the output written so far. -/ +def config (input : List Bool) (state : Option Phase) (index : ℕ) (output : List Bool) + (h : index ≤ input.length) : Config 0 Bool Phase Empty input where + tapes := + { state := state + inputPos := ⟨index + 1, by lia⟩ + workTapes := Fin.elim0 + workTapePos := Fin.elim0 + output := output } + +/-- Reading a remaining symbol writes the emitted symbol and advances once. -/ +theorem step {input output : List Bool} {index : ℕ} (h : index < input.length) + (q : Phase) (o : Option Bool) (s : Option Phase) : + (config input (some q) index output (by lia)).step + { inputTape := 1, workTapes := Fin.elim0, output := o, state := s } + Empty.elim Empty.elim = + config input s (index + 1) (output ++ o.toList) h := by + refine Config.ext ?_ (funext fun oracle => oracle.elim) + refine Cfg.ext_zero_tapes rfl ?_ rfl + apply Fin.ext + change (moveInputPos _ .pos).val = index + 1 + 1 + rw [moveInputPos_pos_of_ne_right _ (by simp only [config]; lia)] + rfl + +/-- One live transition at a remaining symbol: draw a coin and take the selected step. -/ +theorem eval_run_succ {input output : List Bool} {index : ℕ} (h : index < input.length) + (q : Phase) (fuel : ℕ) : + ProbComp.eval (pairSampler.runClosedConfigFrom (fuel + 1) + (config input (some q) index output (by lia))) = + ProbComp.eval (ProbComp.uniform Bool >>= fun bit => + pairSampler.runClosedConfigFrom fuel + (config input (next q (some input[index])) (index + 1) + (output ++ (emit q (some input[index]) bit).toList) h)) := by + have hsym := inputSymbolInner (cfg := + (config input (some q) index output (by lia)).tapes) index (by simp [config]; lia) h + rw [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + simp only [show (config input (some q) index output (by lia)).tapes.state = some q from rfl, + hsym, pairSampler, Option.isSome_some, ↓reduceIte, bind_map_left, step h] + rfl + +/-- At the right boundary the sampler halts, drawing and discarding one coin. -/ +theorem eval_run_boundary (input output : List Bool) (q : Phase) (fuel : ℕ) : + ProbComp.eval (pairSampler.runClosedConfigFrom (fuel + 1) + (config input (some q) input.length output le_rfl)) = + PMF.pure (config input none input.length output le_rfl) := by + have hsym := inputSymbol_eq_none_of_boundary (cfg := + (config input (some q) input.length output le_rfl).tapes) + (Or.inr (by simp [config])) + rw [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + simp only [show (config input (some q) input.length output le_rfl).tapes.state = some q from rfl, + pairSampler, hsym, Option.isSome_none, Bool.false_eq_true, ↓reduceIte, bind_map_left] + have hcfg : ∀ bit : Bool, (config input (some q) input.length output le_rfl).step + { inputTape := 0, workTapes := Fin.elim0, output := emit q none bit, + state := next q none } Empty.elim Empty.elim = + config input none input.length output le_rfl := by + intro bit + refine Config.ext ?_ (funext fun oracle => oracle.elim) + refine Cfg.ext_zero_tapes ?_ (by simp [Config.step, config, Turing.Action.apply]) ?_ + · cases q <;> rfl + · cases q <;> simp [Config.step, config, Turing.Action.apply, emit] + simp only [hcfg] + rcases fuel with _ | fuel + · simp only [MultiTapeMachine.runConfigFrom, ProbComp.eval_bind, ProbComp.eval_pure, + PMF.bind_const] + · have hhalt : ∀ n, ProbComp.eval (pairSampler.runClosedConfigFrom n + (config input none input.length output le_rfl)) = + PMF.pure (config input none input.length output le_rfl) := by + intro n + cases n with + | zero => simp [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + | succ n => + rw [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + simp [config] + simp only [ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => ProbComp.eval + (pairSampler.runClosedConfigFrom (fuel + 1) + (config input none input.length output le_rfl))) = _ + rw [hhalt, PMF.bind_const] + +/-- The tail phase: one fresh fair bit per remaining symbol, then halting at the boundary. -/ +theorem eval_run_tail (input : List Bool) (remaining : ℕ) : + ∀ (index : ℕ) (output : List Bool) (h : index + remaining = input.length), + ProbComp.eval (pairSampler.runClosedConfigFrom (remaining + 1) + (config input (some .tail) index output (by lia))) = + (BitString.uniformList remaining).map + (fun tail => config input none input.length (output ++ tail) le_rfl) := by + induction remaining with + | zero => + intro index output h + have hi : index = input.length := by lia + subst index + rw [eval_run_boundary] + simp [BitString.uniformList_zero, PMF.pure_map] + | succ remaining ih => + intro index output h + have hi : index < input.length := by lia + rw [eval_run_succ hi] + simp only [next, emit, Option.toList_some] + simp only [ProbComp.eval_bind, ProbComp.eval_uniform, ih (index + 1) _ (by lia), + BitString.uniformList_succ, PMF.map_bind, PMF.map_comp, Function.comp_def, + List.append_assoc, List.singleton_append] + +/-- The copying phase: a framed word is copied verbatim, ignoring the coins drawn meanwhile. -/ +theorem eval_run_frame (input rest : List Bool) (fuel : ℕ) (y : List Bool) : + ∀ (pre output : List Bool) (hin : input = pre ++ frame y ++ rest), + ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y).length + fuel) + (config input (some .tag) pre.length output (by subst hin; simp))) = + ProbComp.eval (pairSampler.runClosedConfigFrom fuel + (config input (some .tail) (pre.length + (frame y).length) (output ++ frame y) + (by subst hin; simp))) := by + induction y with + | nil => + intro pre output hin + have hlt : pre.length < input.length := by subst hin; simp [frame] + have hget : input[pre.length] = false := by subst hin; simp [frame] + rw [show (frame []).length + fuel = fuel + 1 by simp [frame]; lia, eval_run_succ hlt] + simp only [hget, next, emit, Option.toList_some, ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => _) = _ + rw [PMF.bind_const] + rfl + | cons b y ih => + intro pre output hin + have hlt : pre.length < input.length := by subst hin; simp [frame] + have hlt' : pre.length + 1 < input.length := by subst hin; simp [frame] + have hget : input[pre.length] = true := by subst hin; simp [frame] + have hget' : input[pre.length + 1] = b := by + subst hin + simp only [frame, List.flatMap_cons, List.append_assoc] + rw [List.getElem_append_right (by simp)] + simp + have hlen : (frame (b :: y)).length + fuel = ((frame y).length + fuel + 1) + 1 := by + simp [frame]; lia + rw [hlen, eval_run_succ hlt] + simp only [hget, next, emit, Option.toList_some, ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => _) = _ + rw [PMF.bind_const, eval_run_succ hlt'] + simp only [hget', next, emit, Option.toList_some, ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => _) = _ + rw [PMF.bind_const] + have hpre : input = (pre ++ [true, b]) ++ frame y ++ rest := by + subst hin; simp [frame] + have := ih (pre ++ [true, b]) (output ++ [true] ++ [b]) hpre + simp only [List.length_append, List.length_cons, List.length_nil] at this + convert this using 3 + · congr 1 + · simp [frame]; lia + · simp + +end PairSampler + +open PairSampler + +/-- The final configuration retains the framed word and a uniform certificate, and has halted at +the right boundary. -/ +theorem eval_runClosedConfig_pairSampler (y r : List Bool) : + ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y ++ r).length + 1) + (pairSampler.initialConfig (frame y ++ r))) = + (BitString.uniformList r.length).map (fun w => + PairSampler.config (frame y ++ r) none (frame y ++ r).length (frame y ++ w) le_rfl) := by + have hcfg : pairSampler.initialConfig (frame y ++ r) = + PairSampler.config (frame y ++ r) (some .tag) ([] : List Bool).length [] (by simp) := by + refine Config.ext ?_ rfl + exact Cfg.ext_zero_tapes rfl rfl rfl + rw [hcfg, show (frame y ++ r).length + 1 = (frame y).length + (r.length + 1) by simp; lia, + eval_run_frame (frame y ++ r) r (r.length + 1) y [] [] (by simp)] + simpa only [List.length_nil, zero_add, List.nil_append] using + eval_run_tail (frame y ++ r) r.length (frame y).length (frame y) (by simp) + +/-- The sampler outputs a uniform certificate after the framed word, in a linear number of +transitions. -/ +theorem eval_pairSampler (y r : List Bool) : + ProbComp.eval (pairSampler.runClosed ((frame y ++ r).length + 1) (frame y ++ r)) = + (BitString.uniformList r.length).map (frame y ++ ·) := by + rw [runClosed_eq_map, ProbComp.eval_map, eval_runClosedConfig_pairSampler, PMF.map_comp] + rfl + +/-- Every supported configuration has actually halted by the linear clock. -/ +theorem pairSampler_halted (y r : List Bool) : + ∀ final ∈ (ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y ++ r).length + 1) + (pairSampler.initialConfig (frame y ++ r)))).support, final.tapes.state = none := by + intro final hfinal + rw [eval_runClosedConfig_pairSampler] at hfinal + obtain ⟨output, _, rfl⟩ := (PMF.mem_support_map_iff _ _ _).mp hfinal + rfl + +end Turing.MultiTapePTM diff --git a/ComplexityTests/NondeterministicTimeVerifiers.lean b/ComplexityTests/NondeterministicTimeVerifiers.lean new file mode 100644 index 0000000000..995a36357f --- /dev/null +++ b/ComplexityTests/NondeterministicTimeVerifiers.lean @@ -0,0 +1,85 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Computability.Complexity.Classes.Time.Verifier +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Quantitative +import Complexity.Computability.Machines.Turing.MultiTape.Internal.OCombinators +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PairSampler + +/-! The explicit-time verifier characterization of `NTIME` on concrete bounds. A deterministic +linear-time decider is a verifier with empty certificates, and a linear-time nondeterministic +problem survives the round trip through its linear-time verifier and guess and verify, at the +same linear bound. The sampler's output distribution is checked on a small input. -/ + +public section + +namespace ComplexityTests.NondeterministicTimeVerifiers + +open Cslib Cslib.Complexity Turing Turing.MultiTapeTM + +/-- `c * n + c` in unary, within linear time and space. -/ +private theorem computableInO_linear_unary (c : ℕ) : + ComputableInO (fun x : List Bool => c * x.length + c) encBits encUnary + (fun x => x.length) (fun x => x.length) := by + induction c with + | zero => + simpa using ComputableInO.const (encIn := encBits) (encMid := encUnary) 0 + | succ c ih => + have hstep := ComputableInO.lengthUnary.append + (ComputableInO.const (encIn := encBits) (encMid := encUnary) 1) + (g := fun x : List Bool => x.length + 1) (encOut := encUnary) + fun x => by simp [encUnary_apply, List.replicate_add] + have h := ih.append hstep (g := fun x : List Bool => (c + 1) * x.length + (c + 1)) + (encOut := encUnary) fun x => by + simp only [encUnary_apply, ← List.replicate_add] + congr 1 + ring + exact h.mono 3 (fun x => by simp only [encBits_apply]; omega) + (fun x => by simp only [encBits_apply]; omega) + +/-- Guess and verify with empty certificates recovers deterministic linear time inside +nondeterministic linear time. -/ +example {A : Set (List Bool)} (hA : A ∈ DTIME encBits fun n => n) : + A ∈ NTIME encBits fun n => n := by + have hV := (computableInO_indicator_of_mem_DTIME hA).comp (ComputableInO.fst encBits encBits) + have h := setOf_exists_mem_NTIME (ℓ := fun _ => 0) (t := fun n => n) (C := 6) + (ComputableInO.const (encIn := encBits) (encMid := encUnary) 0) hV fun x w hw => by + simp only [List.length_eq_zero_iff] at hw + subst hw + simp only [encBits_apply, encPair_apply, List.length_append, length_frame, + List.length_nil] + omega + have hset : {x : List Bool | ∃ w : List Bool, w.length = 0 ∧ + (indicator A ∘ Prod.fst) (x, w) = true} = A := by + ext x + simp [indicator] + rwa [hset] at h + +/-- **Round trip at linear time.** A problem in nondeterministic linear time has a linear-time +verifier, and guessing certificates of length `c * n + c` for it decides the problem in +nondeterministic linear time again. Both explicit-time hypotheses are satisfiable together. -/ +theorem mem_NTIME_linear_of_verifier_roundTrip {A : Set (List Bool)} + (hA : A ∈ NTIME encBits fun n => n) : A ∈ NTIME encBits fun n => n := by + obtain ⟨V, c, hV, hmem⟩ := exists_verifier_of_mem_NTIME hA + have h := setOf_exists_mem_NTIME (ℓ := fun x : List Bool => c * x.length + c) + (t := fun n => n) (C := c + 4) + (computableInO_linear_unary c) hV fun x w hw => by + simp only [encBits_apply, encPair_apply, List.length_append, length_frame, hw] + nlinarith + have hset : {x : List Bool | ∃ w : List Bool, w.length = c * x.length + c ∧ + V (x, w) = true} = A := by + ext x + exact (hmem x _ le_rfl).symm + rwa [hset] at h + +/-- The sampler copies the framed word `[true]` and guesses a two-bit certificate. -/ +example : ProbComp.eval (MultiTapePTM.pairSampler.runClosed 6 (frame [true] ++ [false, false])) = + (BitString.uniformList 2).map (frame [true] ++ ·) := by + simpa using MultiTapePTM.eval_pairSampler [true] [false, false] + +end ComplexityTests.NondeterministicTimeVerifiers From 4a2a973544622f80458d196f4924f6dfd2bf2c68 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 08:42:09 -0400 Subject: [PATCH 02/16] feat(Classes/Hierarchy): coarse nondeterministic time hierarchy by lazy diagonalization (#1572) For every degree d, NTIME((n+1)^d) is strictly contained in NTIME((n+1)^e) whenever 6d + 14 <= e, separated by the explicit lazy diagonal problem. The upper bound guesses a certificate and simulates a described verifier with the native universal machine; the brute force at the end of each interval uses a new exponential-time bounded existential, via Savitch's algorithm at depth 1. The lower bound diagonalizes against the linear-time verifier of #2025. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../Hierarchy/NondeterministicTime.lean | 791 ++++++++++++++++++ .../Classes/Time/BoundedExists.lean | 218 +++++ .../NondeterministicTimeHierarchy.lean | 44 + ROADMAP.md | 8 +- references.bib | 20 + 6 files changed, 1080 insertions(+), 3 deletions(-) create mode 100644 Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean create mode 100644 Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean create mode 100644 ComplexityTests/NondeterministicTimeHierarchy.lean diff --git a/Complexity.lean b/Complexity.lean index 689f8d2667..4a04db677a 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1172,6 +1172,7 @@ public import Complexity.Computability.Complexity.Classes.FunctionTime public import Complexity.Computability.Complexity.Classes.Hierarchy public import Complexity.Computability.Complexity.Classes.Hierarchy.LogClock public import Complexity.Computability.Complexity.Classes.Hierarchy.Nondeterministic +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicTime public import Complexity.Computability.Complexity.Classes.Hierarchy.Polynomial public import Complexity.Computability.Complexity.Classes.Hierarchy.ShortDesc public import Complexity.Computability.Complexity.Classes.Hierarchy.Space @@ -1334,6 +1335,7 @@ public import Complexity.Computability.Complexity.Classes.TFNP.Subclasses.Princi public import Complexity.Computability.Complexity.Classes.Tally public import Complexity.Computability.Complexity.Classes.Time public import Complexity.Computability.Complexity.Classes.Time.AcceptCount +public import Complexity.Computability.Complexity.Classes.Time.BoundedExists public import Complexity.Computability.Complexity.Classes.Time.Polynomial public import Complexity.Computability.Complexity.Classes.Time.Verifier public import Complexity.Computability.Complexity.Classes.Toda diff --git a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean new file mode 100644 index 0000000000..1f0e01fc6c --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean @@ -0,0 +1,791 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Hierarchy.Time +public import Complexity.Foundations.Data.BitString.Enumeration +import Complexity.Computability.Complexity.Classes.Time.BoundedExists +import Complexity.Computability.Complexity.Classes.Time.Verifier +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Constructible +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Foundations.Data.Nat.PolynomialBound.Exponential + +/-! +# A nondeterministic time hierarchy by lazy diagonalization + +For every degree `d`, nondeterministic time `O((n + 1) ^ d)` is strictly contained in +nondeterministic time `O((n + 1) ^ e)` on bit strings whenever `6 * d + 14 ≤ e` +(`Cslib.Complexity.NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le`). This is a coarse form of the +nondeterministic time hierarchy theorem [Arora and Barak, Theorem 3.3][AroraBarak07], proved by +lazy diagonalization [Žák][Zak1983]. The separating problem is explicit: the lazy diagonal +`Cslib.Complexity.lazyDiagonal d`. + +## The lazy diagonal + +A diagonal for nondeterministic time cannot simply negate the answer of the machine it +simulates: that answer is an existential over certificates. Lazy diagonalization negates it only +once per interval of lengths, where the input is long enough to try every certificate. + +An input `x` carries a word `w` framed at its start, and `w` carries the description +`unframe w` of a deterministic verifier on pairs framed at its start, followed by arbitrary +padding. The padding of `w` gives each description infinitely many words `w`. The interval of +`w` consists of the lengths from `P = 2 * w.length + 1`, the length of `frame w`, to +`2 ^ clock d P - 1`, where `clock d m = (m + 1) ^ (d + 3)`. On the input `x` of length `n` in +this interval, with a certificate `v` of length `certLen d (n + 1) = (n + 2) ^ (d + 2)`, the +verifier `Cslib.Complexity.LazyDiagonal.verifier d` + +* below the end of the interval, runs the described verifier on the one bit longer input + `x ++ [false]` and the certificate `v` for `clock d (n + 1)` steps with the universal function + (`Cslib.Complexity.LazyDiagonal.accepts`); +* at the end of the interval, accepts when the described verifier accepts no certificate of + length `certLen d P` on `frame w`, trying them all. + +The lazy diagonal is the problem of the inputs with an accepted certificate. + +* **Upper bound** (`Cslib.Complexity.lazyDiagonal_mem_NTIME`). Guess the certificate and run the + verifier (`Cslib.Complexity.setOf_exists_mem_NTIME`). The simulation takes time + `O((n + 1) ^ (6 * d + 14))`: polynomial in the input, the certificate and the clock, through the + native universal machine. The brute force takes time `2 ^ O((P + 1) ^ (d + 2))` + (`Cslib.Complexity.exists_computableInO_any_enumList`), which is `O(n)` at the end of the + interval, where `n + 1 = 2 ^ ((P + 1) ^ (d + 3))`. +* **Lower bound** (`Cslib.Complexity.lazyDiagonal_notMem_NTIME`). A nondeterministic machine + deciding the problem within time `O((n + 1) ^ d)` has a deterministic verifier on pairs within + linear time, accepting certificates of every length at least `c * (n + 1) ^ d + c` + (`Cslib.Complexity.exists_verifier_of_mem_NTIME`). On the interval of a word `w` describing that + verifier, with enough padding, the universal function runs it exactly. Then each input of the + interval is in the problem exactly when the next one is, and the last exactly when the first is + not. + +Arora and Barak enumerate nondeterministic machines and use one global sequence of intervals; this +construction gives each word `w` its own interval instead, and diagonalizes against verifiers. +The gap `6 * d + 14` is the polynomial overhead of the native universal machine and of the +certificate length, not the textbook `f (n + 1) = o (g n)`. + +## Main definitions + +* `Cslib.Complexity.LazyDiagonal.verifier`: the verifier of the lazy diagonal. +* `Cslib.Complexity.lazyDiagonal`: the lazy diagonal problem for degree `d`. + +## Main results + +* `Cslib.Complexity.lazyDiagonal_mem_NTIME`: the lazy diagonal is in + `NTIME ((n + 1) ^ (6 * d + 14))`. +* `Cslib.Complexity.lazyDiagonal_notMem_NTIME`: the lazy diagonal is not in `NTIME ((n + 1) ^ d)`. +* `Cslib.Complexity.NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le`: the hierarchy. + +## References + +* [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*, web draft of January + 2007][AroraBarak07] +* [S. Žák, *A Turing machine time hierarchy*][Zak1983] +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Turing Turing.MultiTapeTM Turing.MultiTapeTM.Universal + +namespace LazyDiagonal + +/-- The certificate length of the lazy diagonal on words of length `m`: `(m + 1) ^ (d + 2)`. -/ +def certLen (d m : ℕ) : ℕ := (m + 1) ^ (d + 2) + +/-- The clock of the lazy diagonal on words of length `m`: `(m + 1) ^ (d + 3)`. It bounds the +simulated steps on `m`, and the interval of lengths starting at `m` ends below `2 ^ clock d m`. -/ +def clock (d m : ℕ) : ℕ := (m + 1) ^ (d + 3) + +/-- The machine described by `c`, run as a verifier on the pair `(y, v)` (encoded as +`frame y ++ v`) for `clock d y.length` steps with the universal function, has halted with output +`[true]`. -/ +def accepts (d : ℕ) (c y v : List Bool) : Bool := + !Diagonal.verdict (universal (decodeDesc c, (frame y ++ v).map eBit, clock d y.length)) + +/-- **The verifier of the lazy diagonal**, on an input `x` and a certificate `v`. The input carries +a word `w` framed at its start, and `w` carries the description `unframe w` framed at its start. +When the description is short (`Cslib.Complexity.Diagonal.shortDesc`), the lengths from +`P = 2 * w.length + 1` to `2 ^ clock d P - 1` form an interval: + +* below its end, accept when the described verifier accepts `v` on the input one bit longer; +* at its end, accept when the described verifier accepts no certificate on `frame w`, the input of + length `P`, by trying them all; +* elsewhere, reject. -/ +def verifier (d : ℕ) (p : List Bool × List Bool) : Bool := + if Diagonal.shortDesc (unframe p.1) then + if Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1) then + accepts d (unframe (unframe p.1)) (p.1 ++ [false]) p.2 + else if Nat.size p.1.length ≤ clock d (2 * (unframe p.1).length + 1) then + !(Cslib.BitString.enumList (certLen d (2 * (unframe p.1).length + 1))).any + (accepts d (unframe (unframe p.1)) (frame (unframe p.1))) + else false + else false + +end LazyDiagonal + +open LazyDiagonal + +/-- **The lazy diagonal language** for degree `d`: the words `x` with a certificate of length +`(x.length + 2) ^ (d + 2)` accepted by `Cslib.Complexity.LazyDiagonal.verifier d`. -/ +def lazyDiagonal (d : ℕ) : Set (List Bool) := + {x | ∃ v : List Bool, v.length = certLen d (x.length + 1) ∧ verifier d (x, v) = true} + +/-! ### The lazy diagonal is not in `NTIME ((n + 1) ^ d)` -/ + +/-- The lengths at which certificates of length `certLen d m` and `clock d m` simulated steps +suffice for a verifier with constants `c` and `c₂`. -/ +private theorem exists_le_certLen_clock (d c c₂ : ℕ) : ∃ M, ∀ m, M ≤ m → + c * (m + 1) ^ d + c ≤ certLen d m ∧ c₂ * (2 * m + 1 + certLen d m + 1) ≤ clock d m := by + refine ⟨2 * c + 3 * c₂, fun m hm => ⟨?_, ?_⟩⟩ + · have hX : 1 ≤ (m + 1) ^ d := Nat.one_le_pow _ _ (by omega) + have e : certLen d m = (m + 1) ^ d * ((m + 1) * (m + 1)) := by + simp only [certLen]; ring + rw [e] + have : 2 * c ≤ (m + 1) * (m + 1) := by nlinarith + nlinarith + · have hX : 1 ≤ (m + 1) ^ d := Nat.one_le_pow _ _ (by omega) + have e₁ : certLen d m = (m + 1) ^ d * ((m + 1) * (m + 1)) := by + simp only [certLen]; ring + have e₂ : clock d m = (m + 1) ^ d * ((m + 1) * (m + 1)) * (m + 1) := by + simp only [clock]; ring + rw [e₁, e₂] + set Y := (m + 1) ^ d * ((m + 1) * (m + 1)) + have hY : 2 * m + 2 ≤ 2 * Y := by + have h₁ : m + 1 ≤ (m + 1) * (m + 1) := Nat.le_mul_of_pos_left _ (by omega) + have h₂ : (m + 1) * (m + 1) ≤ Y := Nat.le_mul_of_pos_left _ hX + omega + have : c₂ * (2 * m + 1 + Y + 1) ≤ c₂ * (3 * Y) := Nat.mul_le_mul_left _ (by omega) + nlinarith + +/-- **The lazy diagonal is not decidable in nondeterministic time `O((n + 1) ^ d)`.** A +nondeterministic machine deciding it has a verifier on pairs within linear time +(`Cslib.Complexity.exists_verifier_of_mem_NTIME`), described by a word `code`. On the interval of +lengths of `w = frame code ++ pad` with a long enough `pad`, the universal function runs that +verifier exactly. Each word of the interval is then in the language exactly when the next one is, +and the last exactly when the first is not. -/ +theorem lazyDiagonal_notMem_NTIME (d : ℕ) : + lazyDiagonal d ∉ NTIME encBits fun n => (n + 1) ^ d := by + intro hD + obtain ⟨V, c, hV, hmem⟩ := exists_verifier_of_mem_NTIME hD + obtain ⟨c₂, ⟨k, State, hfin, tm, htm⟩, -⟩ := hV + have := Fintype.ofFinite State + let eS : State ↪ ℕ := (Fintype.equivFin State).toEmbedding.trans Fin.valEmbedding + set code := eDesc (desc eS eBit tm) with hcode + obtain ⟨M, hM⟩ := exists_le_certLen_clock d c c₂ + -- the universal function runs the verifier exactly on long enough words + have hsim (y v : List Bool) (hy : M ≤ y.length) (hv : v.length = certLen d y.length) : + accepts d code y v = V (y, v) := by + have hclock : c₂ * ((encPair encBits encBits (y, v)).length + 1) ≤ clock d y.length := by + have := (hM y.length hy).2 + simp only [encPair_apply, encBits_apply, List.length_append, length_frame, hv] + omega + obtain ⟨hhalt, hout⟩ := (htm (y, v)).runFrom_of_le hclock + simp only [encPair_apply, encBits_apply, encBool_apply] at hhalt hout + rw [accepts, hcode, decodeDesc_eDesc, universal_desc, hout] + simp only [List.map_cons, List.map_nil, Cfg.Halted] at hhalt ⊢ + rw [hhalt] + cases V (y, v) <;> simp [Diagonal.verdict, eBit] + -- on long enough words, membership is acceptance by the simulated verifier + have hiff (y : List Bool) (hy : M ≤ y.length) : y ∈ lazyDiagonal d ↔ + ∃ v : List Bool, v.length = certLen d y.length ∧ accepts d code y v = true := by + have h := hmem y (certLen d y.length) (by simpa [encBits_apply] using (hM y.length hy).1) + rw [h] + exact exists_congr fun v => and_congr_right fun hv => by rw [hsim y v hy hv] + -- the interval of `w` + set w := frame code ++ List.replicate (2 ^ code.length + M) true with hw + set P := 2 * w.length + 1 with hP + set E := clock d P with hE + set x : ℕ → List Bool := fun m => frame w ++ List.replicate (m - P) false with hx + have hwlen : w.length = 2 * code.length + 1 + (2 ^ code.length + M) := by + simp [w] + have hMP : M ≤ P := by + have := hwlen + generalize 2 ^ code.length = e at this + omega + have hxlen (m : ℕ) (hm : P ≤ m) : (x m).length = m := by + simp only [x, List.length_append, length_frame, List.length_replicate] + omega + have hshort : Diagonal.shortDesc w = true := by + have hc : unframe w = code := unframe_frame_append _ _ + rw [Diagonal.shortDesc, decide_eq_true_eq, hc, hwlen] + omega + have hverifier (m : ℕ) (hm : P ≤ m) (v : List Bool) : verifier d (x m, v) = + if Nat.size (m + 1) ≤ E then accepts d code (x m ++ [false]) v + else if Nat.size m ≤ E then + !(Cslib.BitString.enumList (certLen d P)).any (accepts d code (frame w)) + else false := by + have hu : unframe (x m) = w := unframe_frame_append _ _ + have hc : unframe w = code := unframe_frame_append _ _ + simp only [verifier, hu, hc, hshort, hxlen m hm, ← hP, ← hE, ↓reduceIte] + have hPE : P + 1 < 2 ^ E := by + have : P + 1 ≤ E := by + simp only [hE, clock] + exact (Nat.le_self_pow (by omega) _) + exact this.trans_lt Nat.lt_two_pow_self + set U := 2 ^ E - 1 with hU + -- each word below the end of the interval is in the language exactly when the next one is + have hstep (m : ℕ) (hm : P ≤ m) (hmU : m < U) : + x m ∈ lazyDiagonal d ↔ x (m + 1) ∈ lazyDiagonal d := by + have hsize : Nat.size (m + 1) ≤ E := Nat.size_le.mpr (by omega) + have hnext : x m ++ [false] = x (m + 1) := by + simp only [x, List.append_assoc, ← List.replicate_succ'] + congr 2 + omega + rw [hiff (x (m + 1)) (by rw [hxlen _ (by omega)]; omega), lazyDiagonal, Set.mem_ofPred_eq, + hxlen (m + 1) (by omega), hxlen m hm] + refine exists_congr fun v => and_congr_right fun _ => ?_ + simp only [hverifier m hm v, hsize, ↓reduceIte, hnext] + -- the end of the interval is in the language exactly when its start is not + have hend : x U ∈ lazyDiagonal d ↔ x P ∉ lazyDiagonal d := by + have hUP : P ≤ U := by omega + have hsize₁ : ¬Nat.size (U + 1) ≤ E := by + rw [Nat.size_le, hU, Nat.sub_add_cancel Nat.one_le_two_pow] + exact lt_irrefl _ + have hsize₂ : Nat.size U ≤ E := Nat.size_le.mpr (by omega) + have hbase : x P = frame w := by simp [x] + rw [hiff (x P) (by rw [hxlen P le_rfl]; omega), hxlen P le_rfl, hbase, lazyDiagonal, + Set.mem_ofPred_eq] + simp only [hverifier U hUP, hsize₁, hsize₂, ↓reduceIte, Bool.not_eq_true', + List.any_eq_false, Cslib.BitString.mem_enumList, not_exists, not_and, Bool.not_eq_true] + constructor + · rintro ⟨-, -, h⟩ v hv + exact h v hv + · intro h + exact ⟨List.replicate _ false, List.length_replicate .., h⟩ + -- the chain from the start of the interval to its end + have hchain (j : ℕ) (hj : j ≤ U - P) : x (U - j) ∈ lazyDiagonal d ↔ x U ∈ lazyDiagonal d := by + induction j with + | zero => rfl + | succ j ih => + rw [← ih (by omega), hstep (U - (j + 1)) (by omega) (by omega)] + congr! 2 + omega + have h := hchain (U - P) le_rfl + rw [Nat.sub_sub_self (by omega), hend] at h + exact iff_not_self h + +/-! ### The lazy diagonal is in nondeterministic polynomial time -/ + +/-- The bound on the simulation of `Cslib.Complexity.LazyDiagonal.accepts`, for a decoder of +degree below `D`. -/ +private def accBound (d D : ℕ) (q : List Bool × List Bool × List Bool) : ℕ := + ((q.1.length + 1) ^ D + q.2.1.length + q.2.2.length + 1) ^ 4 * (q.2.1.length + 1) ^ (2 * d + 6) + +/-- The binary length of the clock is linear in the length of the input. -/ +private theorem length_encNat_clock_le (d y : ℕ) : + (encNat ((y + 1) ^ (d + 3))).length ≤ (d + 3) * (y + 1) := by + rw [encNat_apply, Nat.size_eq_bits_len, Nat.size_le, mul_comm, pow_mul] + exact Nat.pow_lt_pow_left Nat.lt_two_pow_self (by omega) + +/-- The numeric core of the resources of the simulation: for an encoded input of length `N`, a +decoding time `A`, a word `W = frame y ++ v`, a decoded description of encoded length `Dl`, encoded +symbols of length `Nl`, a clock `Y` of `Ll` binary digits and a result of encoded length `E`, +everything is within `K * X ^ 4 * Y ^ 2`. -/ +private theorem accBound_aux (d cD cO : ℕ) : ∃ K, ∀ {N A W Dl Nl Ll X Y E : ℕ}, + N ≤ 2 * X → A ≤ X → W ≤ 2 * X → Dl ≤ cD * (N + A + 1) → Nl ≤ 3 * W → + Ll ≤ (d + 4) * X → 1 ≤ X → 1 ≤ Y → + E ≤ cO * (N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) + 1) → + N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 4 * (Y + 1) ^ 2 + + (E + 1) ^ 2 + 1 ≤ K * (X ^ 4 * Y ^ 2) ∧ + N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) + E + 1 ≤ + K * (X ^ 4 * Y ^ 2) := by + set C₁ := 8 * cD + d + 19 + set C₂ := 11 + 2 * C₁ ^ 2 + refine ⟨10 + 4 * C₁ ^ 4 + (cO * C₂ + 1) ^ 2 + 1 + C₂ * (cO + 1), + fun {N A W Dl Nl Ll X Y E} hN hA hW hD hNl hLl hX hY hE => ?_⟩ + have hIn : 2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1 ≤ C₁ * X := by + have h₁ : cD * (N + A + 1) ≤ cD * (4 * X) := Nat.mul_le_mul_left _ (by omega) + have h₂ : (d + 4) * X = d * X + 4 * X := by ring + have h₃ : d * X ≤ d * X := le_rfl + have e : C₁ * X = 8 * (cD * X) + d * X + 19 * X := by simp only [C₁]; ring + have e' : cD * (4 * X) = 4 * (cD * X) := by ring + omega + have hXY : X * Y ≤ X ^ 2 * Y := Nat.mul_le_mul_right _ (Nat.le_self_pow (by norm_num) _) + have hX2Y : X ^ 2 * Y ≤ X ^ 4 * Y ^ 2 := + Nat.mul_le_mul (Nat.pow_le_pow_right hX (by norm_num)) (Nat.le_self_pow (by norm_num) _) + have hpre : N + A + (N + W + (N + Y)) ≤ 10 * (X * Y) := by + have h₁ : X ≤ X * Y := Nat.le_mul_of_pos_right _ hY + have h₂ : Y ≤ X * Y := Nat.le_mul_of_pos_left _ hX + omega + have hY1 : Y + 1 ≤ 2 * Y := by omega + have hU₂ : (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) ≤ 2 * C₁ ^ 2 * (X ^ 2 * Y) := + calc _ ≤ (C₁ * X) ^ 2 * (2 * Y) := Nat.mul_le_mul (Nat.pow_le_pow_left hIn 2) hY1 + _ = 2 * C₁ ^ 2 * (X ^ 2 * Y) := by ring + have hU₄ : (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 4 * (Y + 1) ^ 2 ≤ + 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) := + calc _ ≤ (C₁ * X) ^ 4 * (2 * Y) ^ 2 := + Nat.mul_le_mul (Nat.pow_le_pow_left hIn 4) (Nat.pow_le_pow_left hY1 2) + _ = 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) := by ring + have hS : N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) + 1 ≤ + C₂ * (X ^ 2 * Y) := by + have h₁ : 1 ≤ X ^ 2 * Y := Nat.mul_pos (Nat.pow_pos hX) hY + have e : C₂ * (X ^ 2 * Y) = 11 * (X ^ 2 * Y) + 2 * C₁ ^ 2 * (X ^ 2 * Y) := by + simp only [C₂]; ring + omega + have hE' : E ≤ cO * C₂ * (X ^ 2 * Y) := + hE.trans ((Nat.mul_le_mul_left cO hS).trans_eq (by ring)) + have hE2 : (E + 1) ^ 2 ≤ (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) := by + have h₁ : 1 ≤ X ^ 2 * Y := Nat.mul_pos (Nat.pow_pos hX) hY + have h₂ : E + 1 ≤ (cO * C₂ + 1) * (X ^ 2 * Y) := by + have e : (cO * C₂ + 1) * (X ^ 2 * Y) = cO * C₂ * (X ^ 2 * Y) + X ^ 2 * Y := by ring + omega + calc (E + 1) ^ 2 ≤ ((cO * C₂ + 1) * (X ^ 2 * Y)) ^ 2 := Nat.pow_le_pow_left h₂ 2 + _ = (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) := by ring + have h1 : 1 ≤ X ^ 4 * Y ^ 2 := Nat.mul_pos (Nat.pow_pos hX) (Nat.pow_pos hY) + have hXYW : X * Y ≤ X ^ 4 * Y ^ 2 := hXY.trans hX2Y + have hU₂' := hU₂.trans (Nat.mul_le_mul_left (2 * C₁ ^ 2) hX2Y) + constructor + · have e : (10 + 4 * C₁ ^ 4 + (cO * C₂ + 1) ^ 2 + 1 + C₂ * (cO + 1)) * (X ^ 4 * Y ^ 2) = + 10 * (X ^ 4 * Y ^ 2) + 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) + + (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) + X ^ 4 * Y ^ 2 + + C₂ * (cO + 1) * (X ^ 4 * Y ^ 2) := by + ring + have := Nat.mul_le_mul_left 10 hXYW + omega + · have e : (10 + 4 * C₁ ^ 4 + (cO * C₂ + 1) ^ 2 + 1 + C₂ * (cO + 1)) * (X ^ 4 * Y ^ 2) = + 10 * (X ^ 4 * Y ^ 2) + 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) + + (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) + X ^ 4 * Y ^ 2 + + (C₂ * (X ^ 4 * Y ^ 2) + cO * C₂ * (X ^ 4 * Y ^ 2)) := by + ring + have h₁ := Nat.mul_le_mul_left C₂ hX2Y + have h₂ := Nat.mul_le_mul_left (cO * C₂) hX2Y + omega + +/-- The simulation runs within time polynomial in the description, the input and the +certificate. -/ +private theorem exists_computableInO_accepts (d : ℕ) : ∃ D, ComputableInO + (fun q : List Bool × List Bool × List Bool => accepts d q.1 q.2.1 q.2.2) + (encPair encBits (encPair encBits encBits)) encBool (accBound d D) (accBound d D) := by + obtain ⟨dd, ed, hdec⟩ := exists_computableInDegree_decodeDesc + set enc := encPair encBits (encPair encBits encBits) + have hdesc := hdec.inO.comp (ComputableInO.fst encBits (encPair encBits encBits)) + have hword : ComputableInO (fun q : List Bool × List Bool × List Bool => frame q.2.1 ++ q.2.2) + enc encBits (fun q => (enc q).length) (fun q => (enc q).length) := + (ComputableInO.snd encBits (encPair encBits encBits)).of_enc_eq fun _ => rfl + have hsym := Universal.computableTidilyInDegree_map_eBit.inO_one.computableInO.comp hword + have hy := (ComputableInO.snd encBits (encPair encBits encBits)).fst' + have hclock := (timeConstructible_succ_pow (k := d + 3) (by omega)).computableInO.comp hy + have hU := computableInO_universal.comp (hdesc.prod (hsym.prod hclock)) + have hv := Diagonal.computableInO_verdict.comp hU + have hnot := (ComputableInDegree.of_finite (encIn := encBool) (encMid := encBool) + (fun b : Bool => !b)).inO_one.comp hv + obtain ⟨cD, hcD⟩ := hdesc.length_le + obtain ⟨cO, hcO⟩ := hU.length_le + obtain ⟨K, hK⟩ := accBound_aux d cD cO + have hbounds (q : List Bool × List Bool × List Bool) := by + have hD := hcD q + have hE := hcO q + have hNl : (encList encUnary ((frame q.2.1 ++ q.2.2).map eBit)).length ≤ + 3 * (frame q.2.1 ++ q.2.2).length := + (length_encList_le encUnary _ (b := 1) fun s hs => by + obtain ⟨b, -, rfl⟩ := List.mem_map.mp hs + cases b + · exact Nat.zero_le _ + · exact le_rfl).trans (by rw [List.length_map]; omega) + have hc : q.1.length + 1 ≤ (q.1.length + 1) ^ (dd + 1) := + Nat.le_self_pow (by omega) _ + have hA : (q.1.length + 1) ^ dd ≤ (q.1.length + 1) ^ (dd + 1) := + Nat.pow_le_pow_right (by omega) (by omega) + have hL := length_encNat_clock_le d q.2.1.length + simp only [Function.comp_apply, encBits_apply, enc, length_encPair, List.length_append, + length_frame] at hD hE hNl ⊢ + refine hK (X := (q.1.length + 1) ^ (dd + 1) + q.2.1.length + q.2.2.length + 1) + (by omega) (by omega) (by omega) hD hNl ?_ (by omega) (Nat.one_le_pow _ _ (by omega)) hE + have : (d + 3) * (q.2.1.length + 1) ≤ (d + 4) * (q.2.1.length + 1) := + Nat.mul_le_mul_right _ (by omega) + have : (d + 4) * (q.2.1.length + 1) ≤ + (d + 4) * ((q.1.length + 1) ^ (dd + 1) + q.2.1.length + q.2.2.length + 1) := + Nat.mul_le_mul_left _ (by omega) + omega + have e (q : List Bool × List Bool × List Bool) : accBound d (dd + 1) q = + ((q.1.length + 1) ^ (dd + 1) + q.2.1.length + q.2.2.length + 1) ^ 4 * + ((q.2.1.length + 1) ^ (d + 3)) ^ 2 := by + rw [accBound, ← pow_mul, show (d + 3) * 2 = 2 * d + 6 by ring] + have hb (b : Bool) : (encBool b).length = 1 := rfl + refine ⟨dd + 1, (hnot.mono K (fun q => ?_) fun q => ?_).of_enc_eq fun q => rfl⟩ + · refine le_trans ?_ (Nat.mul_le_mul_left K (Nat.le_succ _)) + rw [e] + have h := (hbounds q).1 + simp only [Function.comp_apply, encBits_apply, enc, length_encPair, List.length_append, + length_frame] at ⊢ + simp only [hb] at h ⊢ + omega + · refine le_trans ?_ (Nat.mul_le_mul_left K (Nat.le_succ _)) + rw [e] + have h := (hbounds q).2 + simp only [Function.comp_apply, encBits_apply, enc, length_encPair, List.length_append, + length_frame] at ⊢ + simp only [hb] at h ⊢ + omega + +attribute [local fun_prop] ComputableInPolyTime.fst' ComputableInPolyTime.snd' in +/-- The brute force at the end of an interval runs within time exponential in the description, +the input and the certificate length. -/ +private theorem exists_computableInO_brute (d : ℕ) : ∃ K, ComputableInO + (fun q : List Bool × List Bool => + (Cslib.BitString.enumList (certLen d q.2.length)).any (accepts d q.1 q.2)) + (encPair encBits encBits) encBool + (fun q => 2 ^ (K * ((encPair encBits encBits q).length + certLen d q.2.length))) + (fun q => 2 ^ (K * ((encPair encBits encBits q).length + certLen d q.2.length))) := by + obtain ⟨D, hacc⟩ := exists_computableInO_accepts d + set enc := encPair (encPair encBits encBits) encBits + have hre := ((ComputableInO.fst (encPair encBits encBits) encBits).fst').prod + (((ComputableInO.fst (encPair encBits encBits) encBits).snd').prod + (ComputableInO.snd (encPair encBits encBits) encBits)) + have hf := hacc.comp hre + have hsize (q : (List Bool × List Bool) × List Bool) : + q.1.1.length ≤ (enc q).length ∧ q.1.2.length ≤ (enc q).length ∧ + q.2.length ≤ (enc q).length := by + simp only [enc, length_encPair, encBits_apply] + omega + have hpoly : PolyBounded (fun q => (enc q).length) fun q => + (enc q).length + ((enc q).length + (enc q).length) + + accBound d D (q.1.1, q.1.2, q.2) := + ((PolyBounded.self.add (PolyBounded.self.add .self)).add + ((PolyBounded.of_polynomiallyBounded (fun q => (enc q).length) + (bound := fun n => ((n + 1) ^ D + n + n + 1) ^ 4 * (n + 1) ^ (2 * d + 6)) + (by fun_prop)).mono fun q => by + obtain ⟨h₁, h₂, h₃⟩ := hsize q + simp only [accBound] + gcongr)) + have hℓ : ComputableInPolyTime (fun q : List Bool × List Bool => certLen d q.2.length) + (encPair encBits encBits) encUnary := by + unfold certLen + fun_prop + exact exists_computableInO_any_enumList hf hpoly hpoly hℓ + +/-- Weaken only the time bound, keeping the space bound. -/ +private theorem _root_.Turing.MultiTapeTM.ComputableInO.mono_time {α β : Type*} {f : α → β} + {encIn : α ↪ List Bool} {encOut : β ↪ List Bool} {T S T' : α → ℕ} + (h : ComputableInO f encIn encOut T S) (k : ℕ) (hT : ∀ a, T a ≤ k * (T' a + 1)) : + ComputableInO f encIn encOut T' S := + h.mono (k + 1) (fun a => (hT a).trans (Nat.mul_le_mul_right _ (Nat.le_succ k))) + fun _ => (Nat.le_succ _).trans (Nat.le_mul_of_pos_left _ (Nat.succ_pos k)) + +/-- The cost of the tests of the verifier: quadratic in the encoded input, times a power of the +length of the input that pays for the clock in unary. -/ +private def cheap (d : ℕ) (p : List Bool × List Bool) : ℕ := + ((encPair encBits encBits p).length + 1) ^ 2 * ((p.1.length + 1) ^ (d + 3)) ^ 2 + +/-- The numeric facts behind `cheap`, for an encoded input of length `N`, an input of length `n` +and a clock `Z` of at most `G * Y`. -/ +private theorem cheap_aux (G : ℕ) {N n Z Y : ℕ} (hn : n ≤ N) (hY : 1 ≤ Y) (hZ : Z ≤ G * Y) : + N + 1 ≤ (N + 1) ^ 2 * Y ^ 2 ∧ (Z + 1) ^ 2 ≤ (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) ∧ + (2 * N + 4 + Z) ^ 2 ≤ (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) := by + have hY2 : 1 ≤ Y ^ 2 := Nat.one_le_pow _ _ hY + have hN2 : N + 1 ≤ (N + 1) ^ 2 := Nat.le_self_pow (by norm_num) _ + have hsq : (2 * N + 4 + Z) ^ 2 ≤ (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) := + calc (2 * N + 4 + Z) ^ 2 ≤ ((G + 4) * ((N + 1) * Y)) ^ 2 := by + apply Nat.pow_le_pow_left + have h₁ : N + 1 ≤ (N + 1) * Y := Nat.le_mul_of_pos_right _ hY + have h₂ : Y ≤ (N + 1) * Y := Nat.le_mul_of_pos_left _ (by omega) + have e : (G + 4) * ((N + 1) * Y) = G * ((N + 1) * Y) + 4 * ((N + 1) * Y) := by ring + have := Nat.mul_le_mul_left G h₂ + omega + _ = (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) := by ring + refine ⟨hN2.trans (Nat.le_mul_of_pos_right _ hY2), + (Nat.pow_le_pow_left (by omega) 2).trans hsq, hsq⟩ + +/-- **The verifier of the lazy diagonal is computable**, within the cost of its tests plus the +cost of the branch taken: the simulation, or the brute force at the end of an interval. -/ +private theorem exists_computableInO_verifier (d : ℕ) : + ∃ (D K : ℕ) (S : List Bool × List Bool → ℕ), + ComputableInO (verifier d) (encPair encBits encBits) encBool + (fun p => cheap d p + if Diagonal.shortDesc (unframe p.1) then + (cheap d p + if Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1) then + cheap d p + accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2) + else (cheap d p + if Nat.size p.1.length ≤ clock d (2 * (unframe p.1).length + 1) then + cheap d p + 2 ^ (K * ((encPair encBits encBits (unframe (unframe p.1), + frame (unframe p.1))).length + certLen d (2 * (unframe p.1).length + 1))) + else cheap d p)) + else cheap d p) S := by + obtain ⟨D, hacc⟩ := exists_computableInO_accepts d + obtain ⟨K, hbrute⟩ := exists_computableInO_brute d + have hx := ComputableInO.fst encBits encBits + have hun : ComputableInO unframe encBits encBits (fun z => z.length) (fun z => z.length) := + ComputableInO.of_linear (.of_unframe fun _ => rfl) + have hfr : ComputableInO frame encBits encBits (fun z => z.length) (fun z => z.length) := + ComputableInO.of_linear (.of_frame fun _ => rfl) + have hw := hun.comp hx + have hcode := hun.comp hw + have hbase := hfr.comp hw + have hsnoc : ComputableInO (fun p : List Bool × List Bool => p.1 ++ [false]) + (encPair encBits encBits) encBits + (fun p => (encPair encBits encBits p).length + (encPair encBits encBits p).length) + (fun p => (encPair encBits encBits p).length + (encPair encBits encBits p).length) := + hx.append (ComputableInO.const (encMid := encBits) [false]) fun _ => rfl + have hsz : ComputableInO (fun z : List Bool => Nat.size z.length) encBits encUnary + (fun z => (z.length + 1) ^ 2 + (encNat z.length).length) + (fun z => z.length + 1 + (encNat z.length).length) := + (ComputableInO.lengthUnary.comp (ComputableInO.lengthNat.of_enc_eq (encC := encBits) + (g := fun z : List Bool => encNat z.length) fun _ => rfl)).of_enc_eq fun z => by + simp [encNat_apply, Nat.size_eq_bits_len] + have hE := ComputableInO.natToUnary.comp + ((timeConstructible_succ_pow (k := d + 3) (by omega)).computableInO.comp hbase) + -- the numeric facts about the tests + set G := 2 ^ (d + 3) + have hfacts (p : List Bool × List Bool) : (unframe p.1).length ≤ p.1.length ∧ + p.1.length ≤ (encPair encBits encBits p).length ∧ + (encPair encBits encBits p).length + 1 ≤ cheap d p ∧ + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1) ^ 2 ≤ (G + 4) ^ 2 * cheap d p ∧ + (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 ≤ (G + 4) ^ 2 * cheap d p := by + have hu := length_unframe_le p.1 + have hn : p.1.length ≤ (encPair encBits encBits p).length := by + simp only [length_encPair, encBits_apply] + omega + have hZ : (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) ≤ + 2 ^ (d + 3) * (p.1.length + 1) ^ (d + 3) := by + rw [← mul_pow] + exact Nat.pow_le_pow_left (by omega) _ + exact ⟨hu, hn, cheap_aux G hn (Nat.one_le_pow _ _ (by omega)) hZ⟩ + have e (p : List Bool × List Bool) : (8 + 4 * (G + 4) ^ 2) * (cheap d p + 1) = + 8 * cheap d p + 4 * ((G + 4) ^ 2 * cheap d p) + (8 + 4 * (G + 4) ^ 2) := by ring + have hc₀ := (Diagonal.computableInO_shortDesc.comp hw).mono_time (T' := cheap d) + (8 + 4 * (G + 4) ^ 2) fun p => by + obtain ⟨hu, hn, h₁, -, h₃⟩ := hfacts p + have : ((unframe p.1).length + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + simp only [Function.comp_apply] at ⊢ + have := e p + omega + have hc₁ := (ComputableInO.unaryLe.comp ((hsz.comp hsnoc).prod hE)).mono_time + (T' := cheap d) (8 + 4 * (G + 4) ^ 2) fun p => by + obtain ⟨hu, hn, h₁, h₂, h₃⟩ := hfacts p + have hs := Nat.size_le.mpr (Nat.lt_two_pow_self (n := p.1.length + 1)) + have he := Universal.length_encNat_le (p.1.length + 1) + have hsq : (p.1.length + 1 + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + have hsq' : (2 * Nat.size (p.1.length + 1) + 1 + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := + Nat.pow_le_pow_left (by omega) 2 + have hZ : (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) ≤ + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1) ^ 2 := by + nlinarith [Nat.le_self_pow (n := 2) (by norm_num) + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1)] + simp only [Function.comp_apply, length_encPair (enc₁ := encUnary) (enc₂ := encUnary), + encUnary_apply, List.length_replicate, List.length_append, List.length_singleton, + length_frame] at ⊢ + have := e p + omega + have hc₂ := (ComputableInO.unaryLe.comp ((hsz.comp hx).prod hE)).mono_time + (T' := cheap d) (8 + 4 * (G + 4) ^ 2) fun p => by + obtain ⟨hu, hn, h₁, h₂, h₃⟩ := hfacts p + have hs := Nat.size_le.mpr (Nat.lt_two_pow_self (n := p.1.length)) + have he := Universal.length_encNat_le p.1.length + have hsq : (p.1.length + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + have hsq' : (2 * Nat.size p.1.length + 1 + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := + Nat.pow_le_pow_left (by omega) 2 + have hZ : (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) ≤ + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1) ^ 2 := by + nlinarith [Nat.le_self_pow (n := 2) (by norm_num) + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1)] + simp only [Function.comp_apply, length_encPair (enc₁ := encUnary) (enc₂ := encUnary), + encUnary_apply, List.length_replicate, length_frame] at ⊢ + have := e p + omega + have hc₁' := hc₁.of_enc_eq (encC := encBool) (g := fun p : List Bool × List Bool => + decide (Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1))) fun p => by + simp only [Function.comp_apply, clock, length_frame, List.length_append, + List.length_singleton] + rfl + have hc₂' := hc₂.of_enc_eq (encC := encBool) (g := fun p : List Bool × List Bool => + decide (Nat.size p.1.length ≤ clock d (2 * (unframe p.1).length + 1))) fun p => by + simp only [Function.comp_apply, clock, length_frame] + rfl + have hf₁ := (hacc.comp (hcode.prod (hsnoc.prod (ComputableInO.snd encBits encBits)))).mono_time + (T' := fun p => cheap d p + accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2)) 7 + fun p => by + obtain ⟨hu, hn, h₁, -⟩ := hfacts p + simp only [Function.comp_apply] at ⊢ + have e : 7 * (cheap d p + accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2) + 1) = + 7 * cheap d p + 7 * accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2) + 7 := by + ring + omega + have hf₂ := ((ComputableInDegree.of_finite (encIn := encBool) (encMid := encBool) + (fun b : Bool => !b)).inO_one.comp (hbrute.comp (hcode.prod hbase))).mono_time + (T' := fun p => cheap d p + 2 ^ (K * ((encPair encBits encBits (unframe (unframe p.1), + frame (unframe p.1))).length + certLen d (2 * (unframe p.1).length + 1)))) 7 + fun p => by + obtain ⟨hu, hn, h₁, -⟩ := hfacts p + have hb : (encBool ((Cslib.BitString.enumList (certLen d (frame (unframe p.1)).length)).any + (accepts d (unframe (unframe p.1)) (frame (unframe p.1))))).length = 1 := rfl + simp only [Function.comp_apply, hb] at ⊢ + rw [length_frame] + set B := 2 ^ (K * ((encPair encBits encBits (unframe (unframe p.1), + frame (unframe p.1))).length + certLen d (2 * (unframe p.1).length + 1))) + have e : 7 * (cheap d p + B + 1) = 7 * cheap d p + 7 * B + 7 := by ring + omega + have hfalse := (ComputableInO.const (encIn := encPair encBits encBits) (encMid := encBool) + false).mono_time (T' := cheap d) 1 fun p => by + obtain ⟨hu, hn, h₁, -⟩ := hfacts p + omega + have h := hc₀.ite (hc₁'.ite hf₁ (hc₂'.ite hf₂ hfalse)) hfalse + refine ⟨D, K, _, (h.of_enc_eq fun p => ?_).mono_le (fun p => le_of_eq ?_) fun p => le_rfl⟩ + · congr 1 + simp only [verifier, Function.comp_apply, decide_eq_true_eq, length_frame] + · simp only [Function.comp_apply, decide_eq_true_eq] + +/-- **The lazy diagonal is decidable in nondeterministic time `O((n + 1) ^ (6 * d + 14))`.** Guess +a certificate of length `(n + 2) ^ (d + 2)` and run the verifier +(`Cslib.Complexity.setOf_exists_mem_NTIME`). Below the end of an interval, the verifier simulates +a verifier on the input one bit longer with a certificate of that length, within time polynomial +in both. At the end of an interval of start `P`, it tries every certificate on the input of +length `P`, within time `2 ^ O((P + 1) ^ (d + 2))`, which is linear in the length `n` of the +input, since `n + 1 = 2 ^ ((P + 1) ^ (d + 3))` there. -/ +theorem lazyDiagonal_mem_NTIME (d : ℕ) : + lazyDiagonal d ∈ NTIME encBits fun n => (n + 1) ^ (6 * d + 14) := by + obtain ⟨D, K, S, hV⟩ := exists_computableInO_verifier d + have hsnoc : ComputableInO (fun x : List Bool => x ++ [false]) encBits encBits + (fun x => x.length + x.length) (fun x => x.length + x.length) := + (ComputableInO.of_eq (f := fun x : List Bool => x) (encIn := encBits) (encMid := encBits) + fun _ => rfl).append (ComputableInO.const (encMid := encBits) [false]) fun _ => rfl + have hℓ := (ComputableInO.natToUnary.comp + ((timeConstructible_succ_pow (k := d + 2) (by omega)).computableInO.comp hsnoc)).of_enc_eq + (encC := encUnary) (g := fun x : List Bool => certLen d (x.length + 1)) fun x => by + simp [certLen] + obtain ⟨CD, hCD⟩ := PolynomiallyBounded.exists_le_mul_two_pow (p := fun m => (m + 1) ^ D) + (by fun_prop) + set B₀ := 3 * K * (3 * K) ^ (d + 2) + set A₁ := 9 * 2 ^ (2 * d + 4) + set A₂ := (CD + 3) ^ 4 * 2 ^ (6 * d + 14) + set A₃ := 7 * 2 ^ (2 * d + 4) + refine setOf_exists_mem_NTIME hℓ hV (C := 2 + A₃ + 4 * A₁ + A₂ + 2 ^ B₀) fun x v hv => ?_ + simp only [encBits_apply] + set n := x.length with hn + have hv' : v.length = (n + 2) ^ (d + 2) := by + rw [hv] + rfl + set M := (n + 1) ^ (6 * d + 14) + have h2 (k : ℕ) : (n + 2) ^ k ≤ 2 ^ k * (n + 1) ^ k := by + rw [← mul_pow] + exact Nat.pow_le_pow_left (by omega) k + have hMk (k : ℕ) (hk : k ≤ 6 * d + 14) : (n + 1) ^ k ≤ M := Nat.pow_le_pow_right (by omega) hk + have hnM : n + 1 ≤ M := by simpa using hMk 1 (by omega) + -- the tests + have hcheap : cheap d (x, v) ≤ A₁ * M := by + have hN : (encPair encBits encBits (x, v)).length + 1 ≤ 3 * (n + 2) ^ (d + 2) := by + simp only [length_encPair, encBits_apply, hv'] + have : n + 2 ≤ (n + 2) ^ (d + 2) := Nat.le_self_pow (by omega) _ + omega + calc cheap d (x, v) = ((encPair encBits encBits (x, v)).length + 1) ^ 2 * + ((n + 1) ^ (d + 3)) ^ 2 := rfl + _ ≤ (3 * (n + 2) ^ (d + 2)) ^ 2 * ((n + 1) ^ (d + 3)) ^ 2 := + Nat.mul_le_mul_right _ (Nat.pow_le_pow_left hN 2) + _ = 9 * (n + 2) ^ (2 * d + 4) * (n + 1) ^ (2 * d + 6) := by ring + _ ≤ 9 * (2 ^ (2 * d + 4) * (n + 1) ^ (2 * d + 4)) * (n + 1) ^ (2 * d + 6) := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (h2 _)) + _ = A₁ * (n + 1) ^ (4 * d + 10) := by ring + _ ≤ A₁ * M := Nat.mul_le_mul_left _ (hMk _ (by omega)) + -- the simulation, when the description is short + have hacc (h₀ : Diagonal.shortDesc (unframe x) = true) : + accBound d D (unframe (unframe x), x ++ [false], v) ≤ A₂ * M := by + have hs : 2 ^ (unframe (unframe x)).length ≤ (unframe x).length := of_decide_eq_true h₀ + have hu := length_unframe_le x + have hD : ((unframe (unframe x)).length + 1) ^ D ≤ CD * n := + (hCD _).trans (Nat.mul_le_mul_left _ (hs.trans hu)) + have hb : ((unframe (unframe x)).length + 1) ^ D + (n + 1) + (n + 2) ^ (d + 2) + 1 ≤ + (CD + 3) * (n + 2) ^ (d + 2) := by + have h₁ : n + 2 ≤ (n + 2) ^ (d + 2) := Nat.le_self_pow (by omega) _ + have h₂ : CD * n ≤ CD * (n + 2) ^ (d + 2) := Nat.mul_le_mul_left _ (by omega) + have e : (CD + 3) * (n + 2) ^ (d + 2) = CD * (n + 2) ^ (d + 2) + 3 * (n + 2) ^ (d + 2) := by + ring + omega + calc accBound d D (unframe (unframe x), x ++ [false], v) = + (((unframe (unframe x)).length + 1) ^ D + (n + 1) + (n + 2) ^ (d + 2) + 1) ^ 4 * + (n + 2) ^ (2 * d + 6) := by + simp only [accBound, List.length_append, List.length_singleton, hv', ← hn, add_assoc, + Nat.reduceAdd] + _ ≤ ((CD + 3) * (n + 2) ^ (d + 2)) ^ 4 * (n + 2) ^ (2 * d + 6) := + Nat.mul_le_mul_right _ (Nat.pow_le_pow_left hb 4) + _ = (CD + 3) ^ 4 * (n + 2) ^ (6 * d + 14) := by ring + _ ≤ (CD + 3) ^ 4 * (2 ^ (6 * d + 14) * M) := Nat.mul_le_mul_left _ (h2 _) + _ = A₂ * M := by ring + -- the brute force, at the end of an interval + have hbrute (h₁ : ¬Nat.size (n + 1) ≤ clock d (2 * (unframe x).length + 1)) : + 2 ^ (K * ((encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d (2 * (unframe x).length + 1))) ≤ n + 1 + 2 ^ B₀ := by + set P := 2 * (unframe x).length + 1 with hP + have hE : 2 ^ clock d P ≤ n + 1 := by + rw [Nat.size_le, not_lt] at h₁ + exact h₁ + have hc := length_unframe_le (unframe x) + have hlen : (encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d P ≤ 3 * (P + 1) ^ (d + 2) := by + simp only [length_encPair, encBits_apply, length_frame, certLen] + have : P + 1 ≤ (P + 1) ^ (d + 2) := Nat.le_self_pow (by omega) _ + omega + rcases le_or_gt (3 * K) (P + 1) with hK | hK + · have hle : K * ((encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d P) ≤ clock d P := + calc _ ≤ K * (3 * (P + 1) ^ (d + 2)) := Nat.mul_le_mul_left _ hlen + _ = 3 * K * (P + 1) ^ (d + 2) := by ring + _ ≤ (P + 1) * (P + 1) ^ (d + 2) := Nat.mul_le_mul_right _ hK + _ = clock d P := by rw [clock]; ring + exact ((Nat.pow_le_pow_right (by norm_num) hle).trans hE).trans (Nat.le_add_right _ _) + · have hle : K * ((encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d P) ≤ B₀ := + calc _ ≤ K * (3 * (P + 1) ^ (d + 2)) := Nat.mul_le_mul_left _ hlen + _ = 3 * K * (P + 1) ^ (d + 2) := by ring + _ ≤ 3 * K * (3 * K) ^ (d + 2) := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hK.le _) + exact (Nat.pow_le_pow_right (by norm_num) hle).trans (Nat.le_add_left _ _) + -- the certificate length + have hTℓ : n + n + ((x ++ [false]).length + 1) ^ (d + 2) + + (((fun x : List Bool => (x.length + 1) ^ (d + 2)) ∘ fun x => x ++ [false]) x + 1) ^ 2 ≤ + A₃ * M := by + simp only [Function.comp_apply, List.length_append, List.length_singleton, add_assoc, + Nat.reduceAdd, ← hn] + have h₁ : n + 2 ≤ (n + 2) ^ (d + 2) := Nat.le_self_pow (by omega) _ + have h₂ : ((n + 2) ^ (d + 2) + 1) ^ 2 ≤ 4 * (n + 2) ^ (2 * d + 4) := by + calc _ ≤ (2 * (n + 2) ^ (d + 2)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ = 4 * (n + 2) ^ (2 * d + 4) := by ring + have h₃ : (n + 2) ^ (d + 2) ≤ (n + 2) ^ (2 * d + 4) := + Nat.pow_le_pow_right (by omega) (by omega) + have h₄ : 7 * (n + 2) ^ (2 * d + 4) ≤ A₃ * M := + calc _ ≤ 7 * (2 ^ (2 * d + 4) * (n + 1) ^ (2 * d + 4)) := Nat.mul_le_mul_left _ (h2 _) + _ = A₃ * (n + 1) ^ (2 * d + 4) := by ring + _ ≤ A₃ * M := Nat.mul_le_mul_left _ (hMk _ (by omega)) + omega + have e : (2 + A₃ + 4 * A₁ + A₂ + 2 ^ B₀) * M = + 2 * M + A₃ * M + 4 * (A₁ * M) + A₂ * M + 2 ^ B₀ * M := by ring + have hB : 2 ^ B₀ ≤ 2 ^ B₀ * M := Nat.le_mul_of_pos_right _ (by omega) + clear_value M A₁ A₂ A₃ B₀ + generalize 2 + A₃ + 4 * A₁ + A₂ + 2 ^ B₀ = C at * + generalize 2 ^ B₀ = P at * + split_ifs with h₀ h₁ h₂ + · have := hacc h₀ + omega + · have := hbrute h₁ + omega + · omega + · omega + +/-! ### The hierarchy -/ + +/-- **A nondeterministic time hierarchy for polynomial degrees.** For every degree `d`, +nondeterministic time `O((n + 1) ^ d)` is strictly contained in nondeterministic time +`O((n + 1) ^ e)` whenever `6 * d + 14 ≤ e`. The lazy diagonal `Cslib.Complexity.lazyDiagonal d` +separates them (`Cslib.Complexity.lazyDiagonal_mem_NTIME`, +`Cslib.Complexity.lazyDiagonal_notMem_NTIME`). This is a coarse form of the nondeterministic time +hierarchy theorem [Arora and Barak, Theorem 3.3][AroraBarak07]: the gap is the polynomial +overhead of the native universal machine and of the certificates, not `f (n + 1) = o (g n)`. -/ +theorem NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le {d e : ℕ} (h : 6 * d + 14 ≤ e) : + NTIME encBits (fun n => (n + 1) ^ d) ⊂ NTIME encBits fun n => (n + 1) ^ e := by + have hsub : ∀ {a b : ℕ}, a ≤ b → + NTIME encBits (fun n => (n + 1) ^ a) ⊆ NTIME encBits fun n => (n + 1) ^ b := + fun hab => NTIME_mono fun n => Nat.pow_le_pow_right (by omega) hab + exact (Set.ssubset_iff_of_subset (hsub (by omega))).mpr + ⟨lazyDiagonal d, hsub h (lazyDiagonal_mem_NTIME d), lazyDiagonal_notMem_NTIME d⟩ + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean b/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean new file mode 100644 index 0000000000..8ba1e0dd5a --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean @@ -0,0 +1,218 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Space.Reachability +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolyTime +import Complexity.Foundations.Data.Nat.PolynomialBound.Closure +import Complexity.Foundations.Data.Nat.PolynomialBound.Exponential + +/-! +# Bounded existential quantification in exponential time + +A test `f` that is computable in polynomial time can be tried on every word of length `ℓ x`, +for `ℓ` computable in polynomial time, within time `2 ^ O(|x| + ℓ x)` +(`Cslib.Complexity.exists_computableInO_any_enumList`). The candidates are enumerated in place: +the existential is reachability within two steps from the all-zero word to the all-one word of +width `ℓ x + 1`, along an edge to `true :: w` for each accepted `w` and from every word starting +with `true` to the all-one word (`Cslib.Complexity.boundedExistsEdge`). Savitch's algorithm +decides it at depth `1` (`Cslib.Reachability.computableInO_savitch`). Every polynomial in the +cost is absorbed by the exponential, so the exponent is linear in `|x| + ℓ x`, with a constant +that depends on the test. + +This brute force is the last step of lazy diagonalization for nondeterministic time, which +must decide deterministically whether some certificate is accepted. + +## Main definitions + +* `Cslib.Complexity.boundedExistsEdge`: the edges of the two-step reachability problem. + +## Main results + +* `Cslib.Complexity.savitch_boundedExistsEdge`: Savitch's algorithm at depth `1` on these edges + decides the bounded existential. +* `Cslib.Complexity.exists_computableInO_any_enumList`: the bounded existential within time and + space `2 ^ O(|x| + ℓ x)`. +-/ + +public section + +namespace Cslib.Complexity + +open _root_.Turing _root_.Turing.MultiTapeTM + +/-- The edges of the graph on words of width `n + 1` whose reachability within two steps from the +all-zero word to the all-one word is a bounded existential over `f`: an edge from the all-zero word +to `true :: w` when `f (x, w)`, and from every word starting with `true` to the all-one word. -/ +def boundedExistsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (u v : List Bool) : Bool := + if !(u.any fun b => b) && v.headD false then f (x, v.tail) + else u.headD false && !(v.any fun b => !b) + +/-- Savitch's algorithm at depth `1` on the edges of `Cslib.Complexity.boundedExistsEdge` decides +the bounded existential. -/ +theorem savitch_boundedExistsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (n : ℕ) : + Cslib.Reachability.savitch (boundedExistsEdge f x) (n + 1) 1 (List.replicate (n + 1) false) + (List.replicate (n + 1) true) = true ↔ ∃ w : List Bool, w.length = n ∧ f (x, w) = true := by + rw [Cslib.Reachability.savitch_eq] + simp only [Cslib.Reachability.reach, List.any_eq_true, Cslib.BitString.mem_enumList, + Bool.and_eq_true, Bool.or_eq_true, decide_eq_true_eq] + constructor + · rintro ⟨m, hm, h₁ | h₁, h₂ | h₂⟩ + · subst h₁ + exact absurd h₂ (by simp [List.replicate_succ]) + · subst h₁ + refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ + simpa [boundedExistsEdge, List.replicate_succ] using h₂ + · subst h₂ + refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ + simpa [boundedExistsEdge, List.replicate_succ] using h₁ + · obtain ⟨b, w, rfl⟩ := List.exists_cons_of_length_eq_add_one hm + refine ⟨w, by simpa using hm, ?_⟩ + cases b <;> simp_all [boundedExistsEdge, List.replicate_succ] + · rintro ⟨w, hw, hf⟩ + refine ⟨true :: w, by simp [hw], Or.inr ?_, Or.inr ?_⟩ + · simp [boundedExistsEdge, List.replicate_succ, hf] + · simp [boundedExistsEdge, List.replicate_succ] + +/-- A polynomially bounded resource function is within a constant multiple of `2 ^ size`. -/ +private theorem PolyBounded.exists_le_mul_two_pow {α : Type*} {size u : α → ℕ} + (h : PolyBounded size u) : ∃ C, ∀ a, u a ≤ C * 2 ^ size a := by + obtain ⟨c, d, hcd⟩ := h.exists_le + obtain ⟨C, hC⟩ := PolynomiallyBounded.exists_le_mul_two_pow + (p := fun n => c * (n + 1) ^ d) (by fun_prop) + exact ⟨C, fun a => (hcd a).trans (hC _)⟩ + +attribute [local fun_prop] ComputableInPolyTime.fst' ComputableInPolyTime.snd' + ComputableInPolyTime.bool_and' in +/-- **Bounded existential quantification in exponential time.** If a test `f` is computable within +time and space polynomial in its encoded input, and `ℓ` is computable in unary in polynomial time, +then whether some word of length `ℓ x` passes `f` with `x` is computable within time and space +`2 ^ (K * ((encB x).length + ℓ x))` for a constant `K`. Savitch's algorithm decides it at depth `1` +on the edges of `Cslib.Complexity.boundedExistsEdge`. -/ +theorem exists_computableInO_any_enumList {β : Type} {encB : β ↪ List Bool} + {f : β × List Bool → Bool} {Tf Sf : β × List Bool → ℕ} + (hf : ComputableInO f (encPair encB encBits) encBool Tf Sf) + (hTf : PolyBounded (fun q => (encPair encB encBits q).length) Tf) + (hSf : PolyBounded (fun q => (encPair encB encBits q).length) Sf) + {ℓ : β → ℕ} (hℓ : ComputableInPolyTime ℓ encB encUnary) : + ∃ K, ComputableInO (fun x => (Cslib.BitString.enumList (ℓ x)).any fun w => f (x, w)) encB + encBool (fun x => 2 ^ (K * ((encB x).length + ℓ x))) + (fun x => 2 ^ (K * ((encB x).length + ℓ x))) := by + set encQ := encPair encB (encPair encBits encBits) + -- the edge test: polynomial-time tests around one call of `f` + have hg : ComputableInPolyTime (fun q : β × List Bool × List Bool => (q.1, q.2.2.tail)) + encQ (encPair encB encBits) := by fun_prop + have hc : ComputableInPolyTime + (fun q : β × List Bool × List Bool => !(q.2.1.any fun b => b) && q.2.2.headD false) + encQ encBool := by fun_prop + have he : ComputableInPolyTime + (fun q : β × List Bool × List Bool => q.2.1.headD false && !(q.2.2.any fun b => !b)) + encQ encBool := by fun_prop + obtain ⟨Tg, Sg, hTg, hSg, hgO⟩ := hg.exists_computableInO + obtain ⟨Tc, Sc, hTc, hSc, hcO⟩ := hc.exists_computableInO + obtain ⟨Te, Se, hTe, hSe, heO⟩ := he.exists_computableInO + have hedge := hcO.ite (hf.comp hgO) heO + -- the arguments of Savitch's algorithm + have hin : ComputableInPolyTime + (fun x => (x, ℓ x + 1, 1, List.replicate (ℓ x + 1) false, List.replicate (ℓ x + 1) true)) + encB (Cslib.Reachability.encArgs encB) := by + unfold Cslib.Reachability.encArgs + fun_prop + obtain ⟨Ti, Si, hTi, hSi, hinO⟩ := hin.exists_computableInO + have h := Cslib.Reachability.computableInO_savitch (edge := boundedExistsEdge f) (par := id) + hedge hinO (fun _ => List.length_replicate ..) fun _ => List.length_replicate .. + -- every polynomial is within a constant multiple of an exponential + have hpoly (T₁ T₂ T₃ : β × List Bool × List Bool → ℕ) (T : β × List Bool → ℕ) + (h₁ : PolyBounded (fun q => (encQ q).length) T₁) + (h₂ : PolyBounded (fun q => (encQ q).length) T₂) + (h₃ : PolyBounded (fun q => (encQ q).length) T₃) + (hT : PolyBounded (fun q => (encPair encB encBits q).length) T) : + ∃ C, ∀ q, (T₁ q + if !(q.2.1.any fun b => b) && q.2.2.headD false then + T₂ q + T (q.1, q.2.2.tail) else T₃ q) ≤ C * 2 ^ (encQ q).length := by + have hcomp : PolyBounded (fun q => (encQ q).length) fun q => T (q.1, q.2.2.tail) := + hT.comp hg.polyBounded_length + refine PolyBounded.exists_le_mul_two_pow + ((h₁.add ((h₂.add hcomp).add h₃)).mono fun q => ?_) + split_ifs <;> omega + obtain ⟨CT, hCT⟩ := hpoly Tc Tg Te Tf hTc hTg hTe hTf + obtain ⟨CS, hCS⟩ := hpoly Sc Sg Se Sf hSc hSg hSe hSf + obtain ⟨CI, hCI⟩ := PolyBounded.exists_le_mul_two_pow hTi + obtain ⟨CJ, hCJ⟩ := PolyBounded.exists_le_mul_two_pow hSi + -- the edge queries on words of width `ℓ x + 1` + have hq (x : β) {u v : List Bool} (hu : u ∈ Cslib.BitString.enumList (ℓ x + 1)) + (hv : v ∈ Cslib.BitString.enumList (ℓ x + 1)) : + (encQ (x, u, v)).length = 2 * (encB x).length + 3 * ℓ x + 5 := by + simp only [encQ, length_encPair, encBits_apply, Cslib.BitString.mem_enumList.mp hu, + Cslib.BitString.mem_enumList.mp hv] + ring + refine ⟨12, (h.mono ((CI + CJ + CT + CS + 1) * 2 ^ 26) (fun x => ?_) fun x => ?_).of_enc_eq + fun x => ?_⟩ + · -- time + set n := (encB x).length + set L := ℓ x + have hmax := listMax_le (f := fun u => listMax (fun v => (Tc (x, u, v) + + if !(u.any fun b => b) && v.headD false then Tg (x, u, v) + Tf (x, v.tail) + else Te (x, u, v))) (Cslib.BitString.enumList (L + 1))) + (l := Cslib.BitString.enumList (L + 1)) (b := CT * 2 ^ (2 * n + 3 * L + 5)) + fun u hu => listMax_le fun v hv => (hCT (x, u, v)).trans_eq (by rw [hq x hu hv]) + change Ti x + 2 ^ ((L + 1 + 2) * (1 + 1)) * (((1 + 1) * (1 + (L + 1) + 1) + n + 1) ^ 2 + + Cslib.Reachability.edgeMax _ x (L + 1)) ≤ _ + unfold Cslib.Reachability.edgeMax + have hTi := hCI x + have hsq : ((1 + 1) * (1 + (L + 1) + 1) + n + 1) ^ 2 ≤ 2 ^ (2 * (2 * L + n + 7)) := + calc _ ≤ (2 * L + n + 7) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ ≤ (2 ^ (2 * L + n + 7)) ^ 2 := Nat.pow_le_pow_left Nat.lt_two_pow_self.le 2 + _ = _ := by rw [← pow_mul, mul_comm] + have hE : 2 ^ ((L + 1 + 2) * (1 + 1)) * (2 ^ (2 * (2 * L + n + 7)) + + CT * 2 ^ (2 * n + 3 * L + 5)) ≤ (CT + 1) * 2 ^ (12 * (n + L)) * 2 ^ 26 := by + have h₁ : 2 ^ ((L + 1 + 2) * (1 + 1)) * 2 ^ (2 * (2 * L + n + 7)) ≤ + 2 ^ (12 * (n + L)) * 2 ^ 26 := by + rw [← pow_add, ← pow_add] + exact Nat.pow_le_pow_right (by norm_num) (by ring_nf; omega) + have h₂ : 2 ^ ((L + 1 + 2) * (1 + 1)) * 2 ^ (2 * n + 3 * L + 5) ≤ + 2 ^ (12 * (n + L)) * 2 ^ 26 := by + rw [← pow_add, ← pow_add] + exact Nat.pow_le_pow_right (by norm_num) (by ring_nf; omega) + have := Nat.mul_le_mul_left CT h₂ + nlinarith + have hI : 2 ^ n ≤ 2 ^ (12 * (n + L)) := Nat.pow_le_pow_right (by norm_num) (by omega) + have hmono := Nat.mul_le_mul_left (2 ^ ((L + 1 + 2) * (1 + 1))) + (Nat.add_le_add hsq hmax) + have hX : 1 ≤ 2 ^ (12 * (n + L)) := Nat.one_le_two_pow + calc _ ≤ CI * 2 ^ n + (CT + 1) * 2 ^ (12 * (n + L)) * 2 ^ 26 := + Nat.add_le_add hTi (hmono.trans hE) + _ ≤ _ := by nlinarith + · -- space + set n := (encB x).length + set L := ℓ x + have hmax := listMax_le (f := fun u => listMax (fun v => (Sc (x, u, v) + + if !(u.any fun b => b) && v.headD false then Sg (x, u, v) + Sf (x, v.tail) + else Se (x, u, v))) (Cslib.BitString.enumList (L + 1))) + (l := Cslib.BitString.enumList (L + 1)) (b := CS * 2 ^ (2 * n + 3 * L + 5)) + fun u hu => listMax_le fun v hv => (hCS (x, u, v)).trans_eq (by rw [hq x hu hv]) + change Si x + (1 + 1) * (1 + (L + 1) + 1) + n + Cslib.Reachability.edgeMax _ x (L + 1) ≤ _ + unfold Cslib.Reachability.edgeMax + have hSi := hCJ x + have hX : 2 ^ (2 * n + 3 * L + 5) ≤ 2 ^ (12 * (n + L)) * 2 ^ 5 := by + rw [← pow_add] + exact Nat.pow_le_pow_right (by norm_num) (by omega) + have hI : 2 ^ n ≤ 2 ^ (12 * (n + L)) := Nat.pow_le_pow_right (by norm_num) (by omega) + have hL : (1 + 1) * (1 + (L + 1) + 1) + n ≤ 6 * 2 ^ (12 * (n + L)) := by + have : n + L < 2 ^ (n + L) := Nat.lt_two_pow_self + have : 2 ^ (n + L) ≤ 2 ^ (12 * (n + L)) := Nat.pow_le_pow_right (by norm_num) (by omega) + omega + have := Nat.mul_le_mul_left CS hX + nlinarith + · congr 1 + rw [Bool.eq_iff_iff, id, savitch_boundedExistsEdge f x (ℓ x)] + simp only [List.any_eq_true, Cslib.BitString.mem_enumList] + +end Cslib.Complexity diff --git a/ComplexityTests/NondeterministicTimeHierarchy.lean b/ComplexityTests/NondeterministicTimeHierarchy.lean new file mode 100644 index 0000000000..f688a9c506 --- /dev/null +++ b/ComplexityTests/NondeterministicTimeHierarchy.lean @@ -0,0 +1,44 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicTime + +/-! The coarse nondeterministic time hierarchy on concrete degrees. The lazy diagonal for degree +`1` is in nondeterministic time `O((n + 1) ^ 20)` and not in nondeterministic linear time, so the +two classes differ; degree `0` separates constant from `O((n + 1) ^ 14)` nondeterministic time. +The verifier rejects inputs without a short description, such as the empty input. -/ + +public section + +namespace ComplexityTests.NondeterministicTimeHierarchy + +open Cslib Cslib.Complexity Turing Turing.MultiTapeTM + +/-- The lazy diagonal for degree `1` separates nondeterministic linear time from nondeterministic +time `O((n + 1) ^ 20)`, at the separating problem itself. -/ +example : lazyDiagonal 1 ∈ NTIME encBits (fun n => (n + 1) ^ 20) ∧ + lazyDiagonal 1 ∉ NTIME encBits (fun n => (n + 1) ^ 1) := + ⟨lazyDiagonal_mem_NTIME 1, lazyDiagonal_notMem_NTIME 1⟩ + +/-- Nondeterministic linear time is strictly inside nondeterministic time `O((n + 1) ^ 20)`. -/ +example : NTIME encBits (fun n => n + 1) ⊂ NTIME encBits fun n => (n + 1) ^ 20 := by + simpa using NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le (d := 1) (e := 20) le_rfl + +/-- Nondeterministic constant time is strictly inside nondeterministic time `O((n + 1) ^ 14)`. -/ +example : NTIME encBits (fun _ => 1) ⊂ NTIME encBits fun n => (n + 1) ^ 14 := by + simpa using NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le (d := 0) (e := 14) le_rfl + +/-- The empty input carries no short description, so the verifier rejects it with every +certificate, and the empty input is not in the lazy diagonal. -/ +example (d : ℕ) (v : List Bool) : LazyDiagonal.verifier d ([], v) = false := by + simp [LazyDiagonal.verifier, Diagonal.shortDesc, unframe] + +example (d : ℕ) : [] ∉ lazyDiagonal d := by + simp [lazyDiagonal, LazyDiagonal.verifier, Diagonal.shortDesc, unframe] + +end ComplexityTests.NondeterministicTimeHierarchy diff --git a/ROADMAP.md b/ROADMAP.md index 0c5c4eba03..cda927d2c7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -180,9 +180,11 @@ index links to canonical definitions rather than keeping a second set. completeness, padding, self-reducibility, relativization and oracle separations, preserving each reduction's resource bound. - **Time hierarchies** ([#1572]): native polynomial padding separates every pair of integer - degrees `1 ≤ a < b`. Remaining targets include the general deterministic `f log f` hierarchy - with its simulation bound ([#800]), noninteger polynomial degrees and finer logarithmic - gaps, and sharp nondeterministic and alternating time hierarchies with certified simulators. + degrees `1 ≤ a < b`. For nondeterministic time, lazy diagonalization separates degree `d` + from degree `6 * d + 14`. Remaining targets include the general deterministic `f log f` + hierarchy with its simulation bound ([#800]), noninteger polynomial degrees and finer + logarithmic gaps, the nondeterministic hierarchy at every pair of integer degrees by padding, + and sharp nondeterministic and alternating time hierarchies with certified simulators. - The space hierarchy for `log n ≤ g n`, which needs a universal simulation that does not hold its input. - **Space:** Savitch bounds for general space-constructible functions. diff --git a/references.bib b/references.bib index 1b676dfaec..fc2d472320 100644 --- a/references.bib +++ b/references.bib @@ -280,6 +280,15 @@ @article{AngluinLaird1988 doi = {10.1007/BF00116829} } +@misc{AroraBarak07, + author = {Sanjeev Arora and + Boaz Barak}, + title = {Computational Complexity: {A} Modern Approach}, + howpublished = {Draft of a book, web draft dated 2007-01-08}, + year = {2007}, + url = {https://theory.cs.princeton.edu/complexity/book.pdf} +} + @book{AroraBarak09, author = {Sanjeev Arora and Boaz Barak}, @@ -5225,6 +5234,17 @@ @inproceedings{Yao1982 doi = {10.1109/SFCS.1982.45} } +@article{Zak1983, + author = {{\v{Z}}{\'a}k, Stanislav}, + title = {A {T}uring Machine Time Hierarchy}, + journal = {Theoretical Computer Science}, + volume = {26}, + number = {3}, + pages = {327--333}, + year = {1983}, + doi = {10.1016/0304-3975(83)90015-4} +} + @misc{Zhao2023, author = {Zhao, Yufei}, title = {Probabilistic Methods in Combinatorics}, From 123ef60b54a1d95d083f457280122e5303fff8d7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 08:44:37 -0400 Subject: [PATCH 03/16] refactor(Classes/Space): use the public bounded-existential edges in setOf_exists_mem_PSPACE The private existsEdge and savitch_existsEdge duplicated the public boundedExistsEdge and savitch_boundedExistsEdge; expose the definition so clients can match it against Savitch's edge test. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Complexity/Classes/Space/Closure.lean | 44 +++---------------- .../Classes/Time/BoundedExists.lean | 10 +++-- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/Complexity/Computability/Complexity/Classes/Space/Closure.lean b/Complexity/Computability/Complexity/Classes/Space/Closure.lean index 57946f366b..06a0adcb03 100644 --- a/Complexity/Computability/Complexity/Classes/Space/Closure.lean +++ b/Complexity/Computability/Complexity/Classes/Space/Closure.lean @@ -12,6 +12,7 @@ public import Complexity.Computability.Complexity.Reduction public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolySpace public import Complexity.Foundations.Order.BooleanSubalgebra import Complexity.Computability.Complexity.Classes.Space.Savitch +import Complexity.Computability.Complexity.Classes.Time.BoundedExists import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat @@ -146,47 +147,14 @@ theorem PolyTimeReducible.mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : exact mem_PSPACE_of_computableInPolySpace (hf.comp (.of_polyTime hg)) fun a => (hAB a).trans (hB (g a)) -/-- The edges of the graph on words of width `n + 1` whose reachability within two steps from the -all-zero word to the all-one word is a bounded existential over `f`: an edge from the all-zero word -to `true :: w` when `f (x, w)`, and from every word starting with `true` to the all-one word. -/ -private def existsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (u v : List Bool) : Bool := - if !(u.any fun b => b) && v.headD false then f (x, v.tail) - else u.headD false && !(v.any fun b => !b) - -/-- Savitch's algorithm at depth `1` on the edges of `existsEdge` decides the bounded -existential. -/ -private theorem savitch_existsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (n : ℕ) : - Cslib.Reachability.savitch (existsEdge f x) (n + 1) 1 (List.replicate (n + 1) false) - (List.replicate (n + 1) true) = true ↔ ∃ w : List Bool, w.length = n ∧ f (x, w) = true := by - rw [Cslib.Reachability.savitch_eq] - simp only [Cslib.Reachability.reach, List.any_eq_true, Cslib.BitString.mem_enumList, - Bool.and_eq_true, Bool.or_eq_true, decide_eq_true_eq] - constructor - · rintro ⟨m, hm, h₁ | h₁, h₂ | h₂⟩ - · subst h₁ - exact absurd h₂ (by simp [List.replicate_succ]) - · subst h₁ - refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ - simpa [existsEdge, List.replicate_succ] using h₂ - · subst h₂ - refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ - simpa [existsEdge, List.replicate_succ] using h₁ - · obtain ⟨b, w, rfl⟩ := List.exists_cons_of_length_eq_add_one hm - refine ⟨w, by simpa using hm, ?_⟩ - cases b <;> simp_all [existsEdge, List.replicate_succ] - · rintro ⟨w, hw, hf⟩ - refine ⟨true :: w, by simp [hw], Or.inr ?_, Or.inr ?_⟩ - · simp [existsEdge, List.replicate_succ, hf] - · simp [existsEdge, List.replicate_succ] - attribute [local fun_prop] ComputableInPolyTime.fst' ComputableInPolyTime.snd' ComputableInPolyTime.bool_and' in /-- **`PSPACE` is closed under polynomially bounded existential quantification**: if `B ⊆ β × List Bool` is in `PSPACE` and `ℓ` is polynomial-time, then so is the set of the `x` for which some word `w` of length `ℓ x` has `(x, w) ∈ B`. The candidates are enumerated in place: this is reachability -within two steps between two words of width `ℓ x + 1` (`existsEdge`), which Savitch's algorithm -decides at depth `1`, reusing the space of the decider of `B` for every candidate -(`Cslib.Reachability.computableInO_savitch`). -/ +within two steps between two words of width `ℓ x + 1` (`Cslib.Complexity.boundedExistsEdge`), +which Savitch's algorithm decides at depth `1`, reusing the space of the decider of `B` for every +candidate (`Cslib.Reachability.computableInO_savitch`). -/ theorem setOf_exists_mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : Set (β × List Bool)} {ℓ : β → ℕ} (hB : B ∈ PSPACE (encPair encB encBits)) (hℓ : ComputableInPolyTime ℓ encB encUnary) : @@ -213,7 +181,7 @@ theorem setOf_exists_mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : Set ( unfold Cslib.Reachability.encArgs fun_prop obtain ⟨Ti, Si, -, hSi, hinO⟩ := hin.exists_computableInO - have h := Cslib.Reachability.computableInO_savitch (edge := existsEdge f) (par := id) + have h := Cslib.Reachability.computableInO_savitch (edge := boundedExistsEdge f) (par := id) hedge hinO (fun _ => List.length_replicate ..) fun _ => List.length_replicate .. -- the space is polynomial have hSedge : PolyBounded (fun q => (encQ q).length) fun q => @@ -250,6 +218,6 @@ theorem setOf_exists_mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : Set ( Si x + 2 * (ℓ x + 3) + (encB x).length + cE * (2 * (encB x).length + 3 * ℓ x + 6) ^ dE omega · simp only [Set.mem_ofPred_eq, hB, id] - exact (savitch_existsEdge f x (ℓ x)).symm + exact (savitch_boundedExistsEdge f x (ℓ x)).symm end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean b/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean index 8ba1e0dd5a..eb926ceb84 100644 --- a/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean +++ b/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean @@ -28,8 +28,11 @@ decides it at depth `1` (`Cslib.Reachability.computableInO_savitch`). Every poly cost is absorbed by the exponential, so the exponent is linear in `|x| + ℓ x`, with a constant that depends on the test. -This brute force is the last step of lazy diagonalization for nondeterministic time, which -must decide deterministically whether some certificate is accepted. +The same edges give the closure of `PSPACE` under bounded existential quantification +(`Cslib.Complexity.setOf_exists_mem_PSPACE`), where the point is the space of the run. Here the +time is the point: this brute force is the last step of lazy diagonalization for +nondeterministic time, which must decide deterministically whether some certificate is +accepted. ## Main definitions @@ -52,7 +55,8 @@ open _root_.Turing _root_.Turing.MultiTapeTM /-- The edges of the graph on words of width `n + 1` whose reachability within two steps from the all-zero word to the all-one word is a bounded existential over `f`: an edge from the all-zero word to `true :: w` when `f (x, w)`, and from every word starting with `true` to the all-one word. -/ -def boundedExistsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (u v : List Bool) : Bool := +@[expose] def boundedExistsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (u v : List Bool) : + Bool := if !(u.any fun b => b) && v.headD false then f (x, v.tail) else u.headD false && !(v.any fun b => !b) From ba518a6f89f0837daa7e6a4c09a9ef989ad5432b Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 08:47:10 -0400 Subject: [PATCH 04/16] doc(Classes/Hierarchy): state the interval cases of the lazy diagonal verifier precisely Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Hierarchy/NondeterministicTime.lean | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean index 1f0e01fc6c..38d0ff53de 100644 --- a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean +++ b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean @@ -108,15 +108,16 @@ def clock (d m : ℕ) : ℕ := (m + 1) ^ (d + 3) def accepts (d : ℕ) (c y v : List Bool) : Bool := !Diagonal.verdict (universal (decodeDesc c, (frame y ++ v).map eBit, clock d y.length)) -/-- **The verifier of the lazy diagonal**, on an input `x` and a certificate `v`. The input carries -a word `w` framed at its start, and `w` carries the description `unframe w` framed at its start. -When the description is short (`Cslib.Complexity.Diagonal.shortDesc`), the lengths from -`P = 2 * w.length + 1` to `2 ^ clock d P - 1` form an interval: - -* below its end, accept when the described verifier accepts `v` on the input one bit longer; -* at its end, accept when the described verifier accepts no certificate on `frame w`, the input of - length `P`, by trying them all; -* elsewhere, reject. -/ +/-- **The verifier of the lazy diagonal**, on an input `x` of length `n` and a certificate `v`. +The input carries a word `w` framed at its start, and `w` carries the description `unframe w` +framed at its start. When the description is short (`Cslib.Complexity.Diagonal.shortDesc w`), +let `P = 2 * w.length + 1`; the lengths from `P` to `2 ^ clock d P - 1` form the interval of `w`. + +* If `n + 1 < 2 ^ clock d P`, below the end of the interval, accept when the described verifier + accepts `v` on the input one bit longer. +* If `n + 1 = 2 ^ clock d P`, at the end of the interval, accept when the described verifier + accepts no certificate on `frame w`, the input of length `P`, by trying them all. +* Otherwise, and when the description is not short, reject. -/ def verifier (d : ℕ) (p : List Bool × List Bool) : Bool := if Diagonal.shortDesc (unframe p.1) then if Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1) then From 430b810b232da98ee4f09b91616d4c493e71deb2 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 09:06:22 -0400 Subject: [PATCH 05/16] feat(BigO/PolynomialPadding): unary rounded powers and NTIME_succ_pow_eq `computableInO_unary_roundedPower` writes `2 ^ (k * Nat.size (n + 1) + s)` in unary from a binary natural within time `O((n + 1) ^ (k + 1))` and space `O((n + 1) ^ k)`. `computableInO_roundedPolynomialPadding` now reuses it, dropping a private helper and the tidy plumbing in its proof. `NTIME_succ_pow_eq` joins `DTIME_succ_pow_eq`, sharing a private bound. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Complexity/Classes/Time/Polynomial.lean | 43 ++++++--- .../Complexity/BigO/PolynomialPadding.lean | 89 ++++++++++--------- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/Complexity/Computability/Complexity/Classes/Time/Polynomial.lean b/Complexity/Computability/Complexity/Classes/Time/Polynomial.lean index 81634b741e..cdacbe45a2 100644 --- a/Complexity/Computability/Complexity/Classes/Time/Polynomial.lean +++ b/Complexity/Computability/Complexity/Classes/Time/Polynomial.lean @@ -10,34 +10,49 @@ module public import Complexity.Computability.Complexity.Classes.Time /-! -# Polynomial deterministic time bounds +# Polynomial time bounds Polynomial time bounds are often written with `n + 1` to avoid exceptional arithmetic at the -empty input. The additive and multiplicative constants in `DTIME` make this convention equivalent -to the usual `n ^ k`, including degree zero. +empty input. The additive and multiplicative constants in `DTIME` and `NTIME` make this convention +equivalent to the usual `n ^ k`, including degree zero. ## Main results * `Cslib.Complexity.DTIME_succ_pow_eq`: `DTIME ((n + 1) ^ k) = DTIME (n ^ k)`. +* `Cslib.Complexity.NTIME_succ_pow_eq`: `NTIME ((n + 1) ^ k) = NTIME (n ^ k)`. -/ public section namespace Cslib.Complexity +/-- The shifted power `(n + 1) ^ k` is within a constant factor and summand of `n ^ k`. -/ +private theorem succ_pow_le_two_pow_mul_add (k n : ℕ) : + (n + 1) ^ k ≤ 2 ^ k * n ^ k + 2 ^ k := by + rcases n with _ | n + · simp only [zero_add, one_pow] + exact (Nat.one_le_pow k 2 (by omega)).trans (Nat.le_add_left _ _) + · calc (n + 1 + 1) ^ k ≤ (2 * (n + 1)) ^ k := Nat.pow_le_pow_left (by omega) k + _ = 2 ^ k * (n + 1) ^ k := mul_pow _ _ _ + _ ≤ 2 ^ k * (n + 1) ^ k + 2 ^ k := Nat.le_add_right _ _ + /-- Replacing the input length `n` by `n + 1` does not change a polynomial deterministic time class, for any encoding and any natural degree. -/ theorem DTIME_succ_pow_eq {α : Type} (enc : α ↪ List Bool) (k : ℕ) : - DTIME enc (fun n => (n + 1) ^ k) = DTIME enc (fun n => n ^ k) := by - apply Set.Subset.antisymm - · apply DTIME_mono_of_le_mul_add (C := 2 ^ k) - intro n - rcases n with _ | n - · simp only [zero_add, one_pow] - exact (Nat.one_le_pow k 2 (by omega)).trans (Nat.le_add_left _ _) - · calc (n + 1 + 1) ^ k ≤ (2 * (n + 1)) ^ k := Nat.pow_le_pow_left (by omega) k - _ = 2 ^ k * (n + 1) ^ k := mul_pow _ _ _ - _ ≤ 2 ^ k * (n + 1) ^ k + 2 ^ k := Nat.le_add_right _ _ - · exact DTIME_mono fun n => Nat.pow_le_pow_left (Nat.le_succ n) k + DTIME enc (fun n => (n + 1) ^ k) = DTIME enc (fun n => n ^ k) := + Set.Subset.antisymm (DTIME_mono_of_le_mul_add (C := 2 ^ k) (succ_pow_le_two_pow_mul_add k)) + (DTIME_mono fun n => Nat.pow_le_pow_left (Nat.le_succ n) k) + +/-- Replacing the input length `n` by `n + 1` does not change a polynomial nondeterministic +time class, for any encoding and any natural degree. -/ +theorem NTIME_succ_pow_eq {α : Type} (enc : α ↪ List Bool) (k : ℕ) : + NTIME enc (fun n => (n + 1) ^ k) = NTIME enc (fun n => n ^ k) := by + refine Set.Subset.antisymm ?_ (NTIME_mono fun n => Nat.pow_le_pow_left (Nat.le_succ n) k) + rintro A ⟨c₁, c₂, k', State, hfinite, ntm, hA⟩ + refine ⟨c₁ * 2 ^ k, c₁ * 2 ^ k + c₂, k', State, hfinite, ntm, hA.mono fun a => ?_⟩ + have := Nat.mul_le_mul_left c₁ (succ_pow_le_two_pow_mul_add k (enc a).length) + rw [Nat.mul_add, ← Nat.mul_assoc] at this + beta_reduce + omega end Cslib.Complexity diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialPadding.lean b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialPadding.lean index 744cb7e4b4..3fdecd50c2 100644 --- a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialPadding.lean +++ b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialPadding.lean @@ -28,6 +28,8 @@ on inputs long enough to pay for it. * `Turing.MultiTapeTM.computableInO_polynomialPadding`: append `(n + 1) ^ k` bits to a framed word in time `O((n + 1) ^ (2 * k))` and space `O((n + 1) ^ k)`, for `k ≥ 1`. +* `Turing.MultiTapeTM.computableInO_unary_roundedPower`: write the rounded power + `2 ^ (k * Nat.size (n + 1) + s)` of a binary natural in unary in time `O((n + 1) ^ (k + 1))`. * `Turing.MultiTapeTM.computableInO_roundedPolynomialPadding`: round the padding length up to `2 ^ (k * Nat.size (n + 1))` and write it in time `O((n + 1) ^ (k + 1))`. * `Turing.MultiTapeTM.computableInO_polynomialPaddingGuard`: compare the `k`th power of the @@ -93,16 +95,41 @@ theorem polynomialPaddingPower_bounds (k n : ℕ) : · exact Nat.pow_le_pow_left (Nat.lt_size_self (n + 1)).le k · simpa only [mul_pow] using Nat.pow_le_pow_left hupper k -private theorem computableInO_length_succ_size_mul (k : ℕ) : - ComputableInO (fun x : List Bool => k * Nat.size (x.length + 1)) encBits encUnary - (fun x => (x.length + 1) ^ 2) (fun x => x.length + 1) := by - have hp : ComputableInPolyTime (fun n : ℕ => k * Nat.size (n + 1)) encNat encUnary := - ComputableInPolyTime.unary_mul' (.const k) - (ComputableInPolyTime.binary_size.comp (by fun_prop)) - have h := hp.nat_inO.tidy.comp ComputableInO.lengthNat.tidy - refine .of_tidy (h.mono 2 (fun x => ?_) fun x => ?_) - · nlinarith - · omega +/-- Write the rounded power `2 ^ (k * Nat.size (n + 1) + s)` of a binary natural `n` in unary. +It is at least `2 ^ s * (n + 1) ^ k` and at most `2 ^ (k + s) * (n + 1) ^ k` +(`Turing.MultiTapeTM.polynomialPaddingPower_bounds`). Repeated unary doubling gives time +`O((n + 1) ^ (k + 1))` and space `O((n + 1) ^ k)`, for `k ≥ 1`; the constants depend on `k` +and `s`. -/ +theorem computableInO_unary_roundedPower {k : ℕ} (hk : 1 ≤ k) (s : ℕ) : + ComputableInO (fun n : ℕ => 2 ^ (k * Nat.size (n + 1) + s)) encNat encUnary + (fun n => (n + 1) ^ (k + 1)) (fun n => (n + 1) ^ k) := by + have hp : ComputableInPolyTime (fun n : ℕ => k * Nat.size (n + 1) + s) encNat encUnary := + ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_mul' (.const k) + (ComputableInPolyTime.binary_size.comp (by fun_prop))) (.const s) + refine (ComputableInO.unary_two_pow.comp hp.nat_inO).mono ((k + s + 1) * 2 ^ (k + s) + 1) + (fun n => ?_) fun n => ?_ + all_goals + have hb := (polynomialPaddingPower_bounds k n).2 + have hp : n + 1 ≤ (n + 1) ^ k := Nat.le_self_pow (by omega) _ + have hN : 2 ^ (k * Nat.size (n + 1) + s) ≤ 2 ^ (k + s) * (n + 1) ^ k := by + rw [pow_add, pow_add] + nlinarith [Nat.one_le_two_pow (n := s)] + · have hsz : k * Nat.size (n + 1) + s + 1 ≤ (k + s + 1) * (n + 1) := by + have := Nat.size_le_self (n + 1) + nlinarith + calc + _ ≤ (n + 1) ^ (k + 1) + ((k + s + 1) * (n + 1)) * (2 ^ (k + s) * (n + 1) ^ k) := + Nat.add_le_add ((Nat.le_succ n).trans (Nat.le_self_pow (by omega) _)) + (Nat.mul_le_mul hsz hN) + _ = ((k + s + 1) * 2 ^ (k + s) + 1) * (n + 1) ^ (k + 1) := by ring + _ ≤ _ := Nat.mul_le_mul_left _ (Nat.le_succ _) + · calc + _ ≤ (n + 1) ^ k + 2 ^ (k + s) * (n + 1) ^ k := + Nat.add_le_add ((Nat.le_succ n).trans hp) hN + _ = (2 ^ (k + s) + 1) * (n + 1) ^ k := by ring + _ ≤ _ := by + apply Nat.mul_le_mul _ (Nat.le_succ _) + nlinarith [Nat.one_le_two_pow (n := k + s)] /-- Frame a word and append a rounded polynomial number of padding bits. Repeated unary doubling gives time `O((n + 1) ^ (k + 1))` while the space remains `O((n + 1) ^ k)`. -/ @@ -112,45 +139,21 @@ theorem computableInO_roundedPolynomialPadding {k : ℕ} (hk : 1 ≤ k) : List.replicate (2 ^ (k * Nat.size (x.length + 1))) true) encBits encBits (fun x => (x.length + 1) ^ (k + 1)) (fun x => (x.length + 1) ^ k) := by - have hu := ComputableInO.unary_two_pow.tidy.comp - (computableInO_length_succ_size_mul k).tidy - have h := (ComputableTidilyInO.of_linear - (.of_frame (f := id) (encOut := encFramed encBits) fun _ => rfl)).append hu + have h := (ComputableInO.of_linear + (.of_frame (f := id) (encMid := encFramed encBits) fun _ => rfl)).append + ((computableInO_unary_roundedPower hk 0).comp ComputableInO.lengthNat) (g := fun x : List Bool => frame x ++ List.replicate (2 ^ (k * Nat.size (x.length + 1))) true) - (encOut := encBits) fun _ => rfl - refine .of_tidy (h.mono ((k + 1) * 2 ^ k + 8) (fun x => ?_) fun x => ?_) + (encOut := encBits) fun _ => by simp + refine h.mono 3 (fun x => ?_) fun x => ?_ all_goals - have hb := (polynomialPaddingPower_bounds k x.length).2 have hp : x.length + 1 ≤ (x.length + 1) ^ k := Nat.le_self_pow (by omega) _ dsimp only [Function.comp_apply, id_eq, encBits_apply] - · have hc : (k * Nat.size (x.length + 1) + 1) * - 2 ^ (k * Nat.size (x.length + 1)) ≤ - ((k + 1) * 2 ^ k) * (x.length + 1) ^ (k + 1) := by - calc - _ ≤ ((k + 1) * (x.length + 1)) * (2 ^ k * (x.length + 1) ^ k) := - Nat.mul_le_mul (by have := Nat.size_le_self (x.length + 1); nlinarith) hb - _ = _ := by rw [pow_succ]; ring - have hs : (x.length + 1) ^ 2 ≤ (x.length + 1) ^ (k + 1) := + · have hs : (x.length + 1) ^ 2 ≤ (x.length + 1) ^ (k + 1) := Nat.pow_le_pow_right (by omega) (by omega) - have hl : x.length ≤ (x.length + 1) ^ (k + 1) := - (Nat.le_succ _).trans (Nat.le_self_pow (by omega) _) - calc - _ ≤ (x.length + 1) ^ (k + 1) + ((x.length + 1) ^ (k + 1) + - ((k + 1) * 2 ^ k) * (x.length + 1) ^ (k + 1)) := - Nat.add_le_add hl (Nat.add_le_add hs hc) - _ = ((k + 1) * 2 ^ k + 2) * (x.length + 1) ^ (k + 1) := by ring - _ ≤ _ := Nat.mul_le_mul (by omega) (Nat.le_succ _) - · have hcoeff : 2 ^ k + 2 ≤ (k + 1) * 2 ^ k + 8 := by - have hq : 2 ^ k ≤ (k + 1) * 2 ^ k := by - simpa only [one_mul] using - Nat.mul_le_mul_right (2 ^ k) (show 1 ≤ k + 1 by omega) - omega - calc - _ ≤ (x.length + 1) ^ k + ((x.length + 1) ^ k + 2 ^ k * (x.length + 1) ^ k) := - Nat.add_le_add ((Nat.le_succ _).trans hp) (Nat.add_le_add hp hb) - _ = (2 ^ k + 2) * (x.length + 1) ^ k := by ring - _ ≤ _ := Nat.mul_le_mul hcoeff (Nat.le_succ _) + have hl : x.length + 1 ≤ (x.length + 1) ^ (k + 1) := Nat.le_self_pow (by omega) _ + omega + · omega /-- Testing whether a framed word has polynomially sufficient padding takes quadratic time and linear space in the full input length, independently of the fixed exponent. -/ From 99d172460e5b5b511dd79a9330a135ebfb67d4f8 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 09:06:22 -0400 Subject: [PATCH 06/16] feat(Classes/Hierarchy): nondeterministic time hierarchy at every integer degree (#1572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `NTIME_pow_ssubset_NTIME_pow`: `NTIME (n ^ a) ⊂ NTIME (n ^ b)` on bit strings for integers `1 ≤ a < b`, with the `(n + 1)` form `NTIME_succ_pow_ssubset_NTIME_succ_pow`. Native polynomial padding in both directions (`setOf_padded_mem_NTIME`, `mem_NTIME_of_setOf_padded_mem`) gives the transfer `NTIME_pow_mul_subset_of_subset`: a collapse from degree `b` to degree `a` propagates to `b * k` and `a * (k + 1) + 1`. For `k ≥ 3 * a` this collapses every degree `a * j` to `a * (3 * a + 2)`, contradicting the coarse lazy-diagonal hierarchy `NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../Hierarchy/NondeterministicPolynomial.lean | 96 +++++++ .../Padding/NondeterministicPolynomial.lean | 263 ++++++++++++++++++ .../NondeterministicPolynomialHierarchy.lean | 45 +++ ROADMAP.md | 10 +- 5 files changed, 411 insertions(+), 5 deletions(-) create mode 100644 Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicPolynomial.lean create mode 100644 Complexity/Computability/Complexity/Classes/Padding/NondeterministicPolynomial.lean create mode 100644 ComplexityTests/NondeterministicPolynomialHierarchy.lean diff --git a/Complexity.lean b/Complexity.lean index 4a04db677a..d8874f8436 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1172,6 +1172,7 @@ public import Complexity.Computability.Complexity.Classes.FunctionTime public import Complexity.Computability.Complexity.Classes.Hierarchy public import Complexity.Computability.Complexity.Classes.Hierarchy.LogClock public import Complexity.Computability.Complexity.Classes.Hierarchy.Nondeterministic +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicPolynomial public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicTime public import Complexity.Computability.Complexity.Classes.Hierarchy.Polynomial public import Complexity.Computability.Complexity.Classes.Hierarchy.ShortDesc @@ -1270,6 +1271,7 @@ public import Complexity.Computability.Complexity.Classes.PP.Oracle public import Complexity.Computability.Complexity.Classes.PP.Time public import Complexity.Computability.Complexity.Classes.PP.TruthTable public import Complexity.Computability.Complexity.Classes.Padding +public import Complexity.Computability.Complexity.Classes.Padding.NondeterministicPolynomial public import Complexity.Computability.Complexity.Classes.Padding.Polynomial public import Complexity.Computability.Complexity.Classes.ParityP public import Complexity.Computability.Complexity.Classes.ParityP.Closure diff --git a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicPolynomial.lean b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicPolynomial.lean new file mode 100644 index 0000000000..8a0f196488 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicPolynomial.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicTime +public import Complexity.Computability.Complexity.Classes.Padding.NondeterministicPolynomial +public import Complexity.Computability.Complexity.Classes.Time.Polynomial +import Mathlib.Tactic.Linarith + +/-! +# The nondeterministic hierarchy at every integer polynomial degree + +For integers `1 ≤ a < b`, `NTIME (n ^ a)` is strictly contained in `NTIME (n ^ b)` on bit +strings (`Cslib.Complexity.NTIME_pow_ssubset_NTIME_pow`). The result has the conventional native +multitape-machine semantics of `NTIME`: finite-state nondeterministic machines with a binary +alphabet, every computation path halting within the bound. + +The proof amplifies a hypothetical collapse `NTIME ((n + 1) ^ b) ⊆ NTIME ((n + 1) ^ a)` by +polynomial padding (`Cslib.Complexity.NTIME_pow_mul_subset_of_subset`), which propagates it to +`NTIME ((n + 1) ^ (b * k)) ⊆ NTIME ((n + 1) ^ (a * (k + 1) + 1))` for every `k ≥ 1`. Once +`3 * a ≤ k`, the degree `a * (k + 3)` is at most `b * k`, and `a * (k + 1) + 1` is at most +`a * (k + 2)`. So each class at degree `a * (k + 3)` collapses to the class at degree +`a * (k + 2)`, and every class at a degree `a * j` with `j ≥ 3 * a + 2` collapses to the class at +degree `D = a * (3 * a + 2)`. This contradicts the coarse hierarchy obtained by lazy +diagonalization (`Cslib.Complexity.NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le`), which separates +degree `D` from degree `6 * D + 14`. Unlike the deterministic case +(`Cslib.Complexity.DTIME_pow_ssubset_DTIME_pow`), the linear degree `a = 1` needs no separate +argument: the transfer already loses a degree, and the amplification absorbs it. + +The hierarchy is a classical consequence of the nondeterministic time hierarchy theorem +[Arora and Barak, Theorem 3.3][AroraBarak07]. The proof combines native polynomial padding with the +library's coarse lazy diagonal; the amplification is proved here. Arbitrary real exponents and the +textbook gap `f (n + 1) = o (g n)` are not claimed by this theorem. + +## Main results + +* `Cslib.Complexity.NTIME_succ_pow_ssubset_NTIME_succ_pow`: strictness for `(n + 1)` powers. +* `Cslib.Complexity.NTIME_pow_ssubset_NTIME_pow`: strictness for the conventional `n` powers. + +## References + +* [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*, web draft of January + 2007][AroraBarak07] +-/ + +public section + +namespace Cslib.Complexity + +open Turing.MultiTapeTM + +/-- **Every two distinct positive integer polynomial degrees give distinct nondeterministic time +classes.** Padding propagates a collapse to overlapping intervals of exponents, so all classes +above degree `a * (3 * a + 2)` would collapse to it, contradicting the coarse lazy-diagonal +hierarchy. -/ +theorem NTIME_succ_pow_ssubset_NTIME_succ_pow {a b : ℕ} (ha : 1 ≤ a) (hab : a < b) : + NTIME encBits (fun n => (n + 1) ^ a) ⊂ NTIME encBits (fun n => (n + 1) ^ b) := by + have hsub {d e : ℕ} (h : d ≤ e) : + NTIME encBits (fun n => (n + 1) ^ d) ⊆ NTIME encBits (fun n => (n + 1) ^ e) := + NTIME_mono fun n => Nat.pow_le_pow_right (by omega) h + refine ⟨hsub hab.le, fun hcollapse => ?_⟩ + -- one step of the amplification, from degree `a * (k + 3)` down to `a * (k + 2)` + have hstep (k : ℕ) (hk : 3 * a ≤ k) : + NTIME encBits (fun n => (n + 1) ^ (a * (k + 3))) ⊆ + NTIME encBits (fun n => (n + 1) ^ (a * (k + 2))) := by + have hup : a * (k + 3) ≤ b * k := by nlinarith + have hdown : a * (k + 1) + 1 ≤ a * (k + 2) := by nlinarith + exact (hsub hup).trans ((NTIME_pow_mul_subset_of_subset ha (by omega) (by omega) + hcollapse).trans (hsub hdown)) + -- every degree `a * j` with `j ≥ 3 * a + 2` collapses to degree `a * (3 * a + 2)` + have htail (j : ℕ) (hj : 3 * a + 2 ≤ j) : + NTIME encBits (fun n => (n + 1) ^ (a * j)) ⊆ + NTIME encBits (fun n => (n + 1) ^ (a * (3 * a + 2))) := by + induction j, hj using Nat.le_induction with + | base => exact Set.Subset.refl _ + | succ j hj ih => + have h := hstep (j - 2) (by omega) + rw [show j - 2 + 3 = j + 1 by omega, show j - 2 + 2 = j by omega] at h + exact h.trans ih + exact (NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le (d := a * (3 * a + 2)) + (e := a * (18 * a + 26)) (by nlinarith)).2 (htail _ (by omega)) + +/-- **Strict nondeterministic polynomial time levels.** For integers `1 ≤ a < b`, some binary +language is decidable in nondeterministic time `O(n ^ b)` but not `O(n ^ a)`, using the native +finite-state, binary-alphabet nondeterministic multitape machines and the usual additive +constants in `NTIME`. -/ +theorem NTIME_pow_ssubset_NTIME_pow {a b : ℕ} (ha : 1 ≤ a) (hab : a < b) : + NTIME encBits (fun n => n ^ a) ⊂ NTIME encBits (fun n => n ^ b) := by + simpa only [NTIME_succ_pow_eq] using NTIME_succ_pow_ssubset_NTIME_succ_pow ha hab + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Padding/NondeterministicPolynomial.lean b/Complexity/Computability/Complexity/Classes/Padding/NondeterministicPolynomial.lean new file mode 100644 index 0000000000..c97ca2d563 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Padding/NondeterministicPolynomial.lean @@ -0,0 +1,263 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Time +import Complexity.Computability.Complexity.Classes.Time.Verifier +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Constructible +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolynomialPadding +import Complexity.Computability.Machines.Turing.MultiTape.Internal.OCombinators +import Mathlib.Tactic.Linarith + +/-! +# Polynomial padding for nondeterministic time + +A collapse `NTIME ((n + 1) ^ b) ⊆ NTIME ((n + 1) ^ a)`, with `1 ≤ a` and `2 ≤ b`, propagates +after multiplying the exponents by a positive integer `k`, up to one more degree on the right: +`NTIME ((n + 1) ^ (b * k)) ⊆ NTIME ((n + 1) ^ (a * (k + 1) + 1))` +(`Cslib.Complexity.NTIME_pow_mul_subset_of_subset`). + +The padded language of `A` consists of the words `x` carrying a word `y = unframe x` framed at +their start, with `(y.length + 1) ^ (k + 1) ≤ x.length` and `y ∈ A`. Both directions guess a +certificate and run a verifier (`Cslib.Complexity.setOf_exists_mem_NTIME`), with the linear-time +verifier of the other language (`Cslib.Complexity.exists_verifier_of_mem_NTIME`). + +* **Padding** (`Cslib.Complexity.setOf_padded_mem_NTIME`). If `A` is in + `NTIME ((n + 1) ^ (b * k))`, the padded language is in `NTIME ((n + 1) ^ b)`. Test the padding + in quadratic time, then guess a certificate for `y` of a length rounded to a power of two. The + padding exponent is `k + 1` rather than `k`: writing the rounded length in unary costs one + more factor `y.length + 1`, and the padding pays for it. +* **Unpadding** (`Cslib.Complexity.mem_NTIME_of_setOf_padded_mem`). If the padded language is in + `NTIME ((n + 1) ^ a)`, then `A` is in `NTIME ((n + 1) ^ (a * (k + 1) + 1))`. Write the padding + of `y`, rounded to a power of two, then guess a certificate for the padded word. Writing the + certificate length in unary costs the extra degree. + +The plain calculus has no linear-time unary writer for polynomial lengths, so neither direction +preserves the degree exactly, unlike the deterministic transfer +`Cslib.Complexity.DTIME_pow_mul_subset_of_subset`. The lost degree is harmless for hierarchy +amplification. This is the padding technique of [Arora and Barak, §2.6.2, Theorem 2.25 and +Remark 2.26][AroraBarak07], by which equalities between classes scale up, specialized to integer +powers with all native computation costs proved. No class-closure or simulation hypothesis is +hidden in the statements. + +## Main results + +* `Cslib.Complexity.setOf_padded_mem_NTIME`: padding into nondeterministic time. +* `Cslib.Complexity.mem_NTIME_of_setOf_padded_mem`: unpadding out of nondeterministic time. +* `Cslib.Complexity.NTIME_pow_mul_subset_of_subset`: a nondeterministic polynomial-time collapse + propagates after multiplying both exponents by the same positive integer, up to one degree. + +## References + +* [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*, web draft of January + 2007][AroraBarak07] +-/ + +public section + +namespace Cslib.Complexity + +open Turing.MultiTapeTM + +/-- Unframing is linear. -/ +private theorem computableInO_unframe : + ComputableInO unframe encBits encBits (fun x => x.length) (fun x => x.length) := + .of_linear (.of_unframe fun _ => rfl) + +/-- **Padding into nondeterministic time.** If `A` is in nondeterministic time +`O((n + 1) ^ (b * k))`, with `2 ≤ b` and `1 ≤ k`, then the words carrying a word `y` of `A` framed +at their start, padded to a length of at least `(y.length + 1) ^ (k + 1)`, form a problem in +nondeterministic time `O((n + 1) ^ b)`. -/ +theorem setOf_padded_mem_NTIME {A : Set (List Bool)} {b k : ℕ} (hb : 2 ≤ b) (hk : 1 ≤ k) + (hA : A ∈ NTIME encBits (fun n => (n + 1) ^ (b * k))) : + {x : List Bool | ((unframe x).length + 1) ^ (k + 1) ≤ x.length ∧ unframe x ∈ A} ∈ + NTIME encBits (fun n => (n + 1) ^ b) := by + classical + obtain ⟨V, c, hV, hc⟩ := exists_verifier_of_mem_NTIME hA + -- the certificate length for `y = unframe x`, rounded to a power of two, when padded enough + let guard (x : List Bool) : Bool := decide (((unframe x).length + 1) ^ (k + 1) ≤ x.length) + let L (x : List Bool) : ℕ := 2 ^ (b * k * Nat.size ((unframe x).length + 1) + (c + 1)) + let ℓ (x : List Bool) : ℕ := if guard x then L x else 0 + let V' (p : List Bool × List Bool) : Bool := if guard p.1 then V (unframe p.1, p.2) else false + have hL (x : List Bool) : + c * ((unframe x).length + 1) ^ (b * k) + c ≤ L x := by + have hp := (polynomialPaddingPower_bounds (b * k) (unframe x).length).1 + have hc' : 2 * c ≤ 2 ^ (c + 1) := by + have : c < 2 ^ c := Nat.lt_two_pow_self + rw [pow_succ] + omega + have h1 : 1 ≤ ((unframe x).length + 1) ^ (b * k) := Nat.one_le_pow _ _ (by omega) + calc + _ ≤ 2 * c * ((unframe x).length + 1) ^ (b * k) := by nlinarith + _ ≤ 2 ^ (c + 1) * 2 ^ (b * k * Nat.size ((unframe x).length + 1)) := Nat.mul_le_mul hc' hp + _ = L x := by rw [Nat.mul_comm, ← pow_add] + have hset : + {x : List Bool | ((unframe x).length + 1) ^ (k + 1) ≤ x.length ∧ unframe x ∈ A} = + {x | ∃ w : List Bool, w.length = ℓ x ∧ V' (x, w) = true} := by + ext x + simp only [Set.mem_ofPred_eq, ℓ, V', guard] + by_cases hg : ((unframe x).length + 1) ^ (k + 1) ≤ x.length + · simp only [hg, decide_true, ↓reduceIte, true_and] + exact hc (unframe x) (L x) (by simpa only [encBits_apply] using hL x) + · simp [hg] + rw [hset] + -- the certificate length and the verifier, as plain computations + have hlen := ComputableInO.lengthNat.comp computableInO_unframe + have hℓ := (computableInO_polynomialPaddingGuard (k + 1)).ite + ((computableInO_unary_roundedPower (k := b * k) (Nat.one_le_iff_ne_zero.mpr + (Nat.mul_ne_zero (by omega) (by omega))) (c + 1)).comp hlen) + (ComputableInO.const (encIn := encBits) (encMid := encUnary) 0) + have hpair := (computableInO_unframe.comp (ComputableInO.fst encBits encBits)).prod + (ComputableInO.snd encBits encBits) + have hV' := ((computableInO_polynomialPaddingGuard (k + 1)).comp + (ComputableInO.fst encBits encBits)).ite (hV.comp hpair) + (ComputableInO.const (encIn := encPair encBits encBits) (encMid := encBool) false) + refine setOf_exists_mem_NTIME (ℓ := ℓ) (V := V') hℓ hV' + (C := 40 + 8 * 2 ^ (b * k + (c + 1))) fun x w hw => ?_ + -- the bound + set n := x.length with hn + set m := (unframe x).length with hm + have hmn : m ≤ n := length_unframe_le x + have hsq : (n + 1) ^ 2 ≤ (n + 1) ^ b := Nat.pow_le_pow_right (by omega) hb + have hsq' : (m + 1) ^ 2 ≤ (n + 1) ^ b := + (Nat.pow_le_pow_left (by omega) 2).trans hsq + have hlin : n + 1 ≤ (n + 1) ^ b := + (Nat.le_self_pow (by omega) _).trans hsq + simp only [Function.comp_apply, encBits_apply, length_encPair, ← hn, ← hm] at hw ⊢ + by_cases hg : (m + 1) ^ (k + 1) ≤ n + · have hgx : guard x = true := decide_eq_true hg + simp only [ℓ, hgx, ↓reduceIte] at hw + simp only [hg, decide_true, ↓reduceIte] + -- the padding pays for the certificate and for writing its length + have hpad : (m + 1) ^ (b * k + 1) ≤ (n + 1) ^ b := by + calc + _ ≤ (m + 1) ^ (b * (k + 1)) := Nat.pow_le_pow_right (by omega) (by nlinarith) + _ = ((m + 1) ^ (k + 1)) ^ b := by rw [← pow_mul, Nat.mul_comm] + _ ≤ (n + 1) ^ b := Nat.pow_le_pow_left (by omega) b + have hpow : (m + 1) ^ (b * k) ≤ (m + 1) ^ (b * k + 1) := + Nat.pow_le_pow_right (by omega) (by omega) + have hW : w.length ≤ 2 ^ (b * k + (c + 1)) * (n + 1) ^ b := by + have hb' := (polynomialPaddingPower_bounds (b * k) m).2 + rw [hw] + simp only [L, ← hm] + calc + _ = 2 ^ (b * k * Nat.size (m + 1)) * 2 ^ (c + 1) := pow_add _ _ _ + _ ≤ 2 ^ (b * k) * (m + 1) ^ (b * k) * 2 ^ (c + 1) := Nat.mul_le_mul_right _ hb' + _ = 2 ^ (b * k + (c + 1)) * (m + 1) ^ (b * k) := by rw [pow_add]; ring + _ ≤ _ := Nat.mul_le_mul_left _ (hpow.trans hpad) + calc + _ ≤ 40 * (n + 1) ^ b + 8 * (2 ^ (b * k + (c + 1)) * (n + 1) ^ b) := by + have := hpow.trans hpad + omega + _ = (40 + 8 * 2 ^ (b * k + (c + 1))) * (n + 1) ^ b := by ring + _ ≤ _ := Nat.le_add_right _ _ + · have hgx : guard x = false := decide_eq_false hg + simp only [ℓ, hgx, Bool.false_eq_true, ↓reduceIte] at hw + simp only [hg, decide_false, Bool.false_eq_true, ↓reduceIte, hw] + calc + _ ≤ 40 * (n + 1) ^ b := by omega + _ ≤ (40 + 8 * 2 ^ (b * k + (c + 1))) * (n + 1) ^ b := Nat.mul_le_mul_right _ (by omega) + _ ≤ _ := Nat.le_add_right _ _ + +/-- **Unpadding out of nondeterministic time.** If the words carrying a word `y` of `A` framed at +their start, padded to a length of at least `(y.length + 1) ^ (k + 1)`, form a problem in +nondeterministic time `O((n + 1) ^ a)`, with `1 ≤ a`, then `A` is in nondeterministic time +`O((n + 1) ^ (a * (k + 1) + 1))`. -/ +theorem mem_NTIME_of_setOf_padded_mem {A : Set (List Bool)} {a k : ℕ} (ha : 1 ≤ a) + (hB : {x : List Bool | ((unframe x).length + 1) ^ (k + 1) ≤ x.length ∧ unframe x ∈ A} ∈ + NTIME encBits (fun n => (n + 1) ^ a)) : + A ∈ NTIME encBits (fun n => (n + 1) ^ (a * (k + 1) + 1)) := by + classical + obtain ⟨V, c, hV, hc⟩ := exists_verifier_of_mem_NTIME hB + -- pad `y` to a rounded length, and guess a certificate of a rounded length + let R (y : List Bool) : ℕ := 2 ^ ((k + 1) * Nat.size (y.length + 1)) + let pad (y : List Bool) : List Bool := frame y ++ List.replicate (R y) true + let s := 2 * c * 3 ^ a + let ℓ (y : List Bool) : ℕ := 2 ^ (a * (k + 1) * Nat.size (y.length + 1) + s) + let V' (p : List Bool × List Bool) : Bool := V (pad p.1, p.2) + have hR (y : List Bool) : (y.length + 1) ^ (k + 1) ≤ R y := + (polynomialPaddingPower_bounds (k + 1) y.length).1 + have hpadlen (y : List Bool) : (pad y).length = 2 * y.length + 1 + R y := by + simp only [pad, List.length_append, length_frame, List.length_replicate] + have hℓ' (y : List Bool) : c * ((pad y).length + 1) ^ a + c ≤ ℓ y := by + have h1 : y.length + 1 ≤ (y.length + 1) ^ (k + 1) := Nat.le_self_pow (by omega) _ + have hRy := hR y + have h3 : (pad y).length + 1 ≤ 3 * R y := by rw [hpadlen]; omega + have hpow : ((pad y).length + 1) ^ a ≤ 3 ^ a * R y ^ a := by + rw [← mul_pow] + exact Nat.pow_le_pow_left h3 a + have hRa : R y ^ a = 2 ^ (a * (k + 1) * Nat.size (y.length + 1)) := by + rw [← pow_mul] + ring + have h2 : 1 ≤ R y ^ a := Nat.one_le_pow _ _ (Nat.pos_of_ne_zero (by simp [R])) + have hs : s ≤ 2 ^ s := Nat.lt_two_pow_self.le + calc + _ ≤ c * (3 ^ a * R y ^ a) + c * (3 ^ a * R y ^ a) := + Nat.add_le_add (Nat.mul_le_mul_left _ hpow) + (Nat.le_mul_of_pos_right _ (Nat.mul_pos (Nat.pow_pos (by omega)) h2)) + _ = s * R y ^ a := by simp only [s]; ring + _ ≤ 2 ^ s * R y ^ a := Nat.mul_le_mul_right _ hs + _ = ℓ y := by rw [hRa, Nat.mul_comm, ← pow_add] + have hset : A = {y | ∃ w : List Bool, w.length = ℓ y ∧ V' (y, w) = true} := by + ext y + have hmem : y ∈ A ↔ pad y ∈ + {x : List Bool | ((unframe x).length + 1) ^ (k + 1) ≤ x.length ∧ unframe x ∈ A} := by + simp only [Set.mem_ofPred_eq, pad, unframe_frame_append] + have := hR y + have := hpadlen y + simp only [pad] at this + exact ⟨fun h => ⟨by omega, h⟩, fun h => h.2⟩ + rw [hmem, hc (pad y) (ℓ y) (by simpa only [encBits_apply] using hℓ' y)] + rfl + rw [hset] + -- the certificate length and the verifier, as plain computations + have hℓ := (computableInO_unary_roundedPower (k := a * (k + 1)) (Nat.one_le_iff_ne_zero.mpr + (Nat.mul_ne_zero (by omega) (by omega))) s).comp ComputableInO.lengthNat + have hpair := ((computableInO_roundedPolynomialPadding (k := k + 1) (by omega)).comp + (ComputableInO.fst encBits encBits)).prod (ComputableInO.snd encBits encBits) + have hV' := hV.comp hpair + refine setOf_exists_mem_NTIME (ℓ := ℓ) (V := V') hℓ hV' + (C := 20 + 4 * 2 ^ (k + 1) + 4 * 2 ^ (a * (k + 1) + s)) fun y w hw => ?_ + -- the bound + set m := y.length with hm + set X := (m + 1) ^ (a * (k + 1) + 1) + have hka : k + 2 ≤ a * (k + 1) + 1 := by nlinarith + have hX (e : ℕ) (he : e ≤ a * (k + 1) + 1) : (m + 1) ^ e ≤ X := + Nat.pow_le_pow_right (by omega) he + have hRb : R y ≤ 2 ^ (k + 1) * X := by + have := (polynomialPaddingPower_bounds (k + 1) m).2 + exact this.trans (Nat.mul_le_mul_left _ (hX _ (by omega))) + have hW : w.length ≤ 2 ^ (a * (k + 1) + s) * X := by + have hb' := (polynomialPaddingPower_bounds (a * (k + 1)) m).2 + rw [hw] + calc + _ = 2 ^ (a * (k + 1) * Nat.size (m + 1)) * 2 ^ s := pow_add _ _ _ + _ ≤ 2 ^ (a * (k + 1)) * (m + 1) ^ (a * (k + 1)) * 2 ^ s := Nat.mul_le_mul_right _ hb' + _ = 2 ^ (a * (k + 1) + s) * (m + 1) ^ (a * (k + 1)) := by rw [pow_add]; ring + _ ≤ _ := Nat.mul_le_mul_left _ (hX _ (by omega)) + have h1 := hX 1 (by omega) + have h2 := hX 2 (by omega) + have hk2 := hX (k + 1 + 1) hka + simp only [pow_one] at h1 + simp only [Function.comp_apply, encBits_apply, length_encPair, ← hm] at hw ⊢ + rw [hpadlen, ← hm] + calc + _ ≤ 20 * X + 4 * (2 ^ (k + 1) * X) + 4 * (2 ^ (a * (k + 1) + s) * X) := by omega + _ = (20 + 4 * 2 ^ (k + 1) + 4 * 2 ^ (a * (k + 1) + s)) * X := by ring + _ ≤ _ := Nat.le_add_right _ _ + +/-- **A nondeterministic polynomial-time collapse propagates under padding**, up to one degree. If +`NTIME ((n + 1) ^ b) ⊆ NTIME ((n + 1) ^ a)`, with `1 ≤ a` and `2 ≤ b`, then +`NTIME ((n + 1) ^ (b * k)) ⊆ NTIME ((n + 1) ^ (a * (k + 1) + 1))` for every `k ≥ 1`. -/ +theorem NTIME_pow_mul_subset_of_subset {a b k : ℕ} (ha : 1 ≤ a) (hb : 2 ≤ b) (hk : 1 ≤ k) + (h : NTIME encBits (fun n => (n + 1) ^ b) ⊆ NTIME encBits (fun n => (n + 1) ^ a)) : + NTIME encBits (fun n => (n + 1) ^ (b * k)) ⊆ + NTIME encBits (fun n => (n + 1) ^ (a * (k + 1) + 1)) := + fun _ hA => mem_NTIME_of_setOf_padded_mem ha (h (setOf_padded_mem_NTIME hb hk hA)) + +end Cslib.Complexity diff --git a/ComplexityTests/NondeterministicPolynomialHierarchy.lean b/ComplexityTests/NondeterministicPolynomialHierarchy.lean new file mode 100644 index 0000000000..c31e277da7 --- /dev/null +++ b/ComplexityTests/NondeterministicPolynomialHierarchy.lean @@ -0,0 +1,45 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicPolynomial + +/-! The nondeterministic hierarchy at concrete integer degrees. Linear and quadratic +nondeterministic time differ, so some language separates them; the padding transfer applies to a +satisfiable collapse; and the shifted and conventional length conventions can be mixed. -/ + +public section + +namespace ComplexityTests.NondeterministicPolynomialHierarchy + +open Cslib.Complexity Turing.MultiTapeTM + +/-- Nondeterministic linear time is strictly inside nondeterministic quadratic time, including +the linear lower boundary of the hierarchy. -/ +example : NTIME encBits (fun n => n) ⊂ NTIME encBits (fun n => n ^ 2) := by + simpa only [pow_one] using NTIME_pow_ssubset_NTIME_pow (a := 1) (b := 2) (by omega) (by omega) + +/-- A language decidable in nondeterministic quadratic time but not in nondeterministic linear +time exists: the strict inclusion is witnessed by an actual separating language. -/ +example : ∃ L : Set (List Bool), + L ∈ NTIME encBits (fun n => n ^ 2) ∧ L ∉ NTIME encBits (fun n => n) := by + obtain ⟨L, hL, hnot⟩ := Set.exists_of_ssubset + (NTIME_pow_ssubset_NTIME_pow (a := 1) (b := 2) (by omega) (by omega)) + exact ⟨L, hL, by simpa only [pow_one] using hnot⟩ + +/-- The padding transfer applies to the satisfiable collapse at equal degrees, where it gives +`NTIME ((n + 1) ^ 2) ⊆ NTIME ((n + 1) ^ 5)`. -/ +example : NTIME encBits (fun n => (n + 1) ^ 2) ⊆ NTIME encBits (fun n => (n + 1) ^ 5) := by + simpa using NTIME_pow_mul_subset_of_subset (a := 2) (b := 2) (k := 1) (by omega) le_rfl le_rfl + subset_rfl + +/-- Ordinary and shifted input-length conventions can be mixed in hierarchy statements. -/ +example : NTIME encBits (fun n => (n + 1) ^ 2) ⊂ NTIME encBits (fun n => n ^ 3) := by + rw [NTIME_succ_pow_eq] + exact NTIME_pow_ssubset_NTIME_pow (by omega) (by omega) + +end ComplexityTests.NondeterministicPolynomialHierarchy diff --git a/ROADMAP.md b/ROADMAP.md index cda927d2c7..2a3135bf36 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -180,11 +180,11 @@ index links to canonical definitions rather than keeping a second set. completeness, padding, self-reducibility, relativization and oracle separations, preserving each reduction's resource bound. - **Time hierarchies** ([#1572]): native polynomial padding separates every pair of integer - degrees `1 ≤ a < b`. For nondeterministic time, lazy diagonalization separates degree `d` - from degree `6 * d + 14`. Remaining targets include the general deterministic `f log f` - hierarchy with its simulation bound ([#800]), noninteger polynomial degrees and finer - logarithmic gaps, the nondeterministic hierarchy at every pair of integer degrees by padding, - and sharp nondeterministic and alternating time hierarchies with certified simulators. + degrees `1 ≤ a < b`, in deterministic and in nondeterministic time. For nondeterministic time, + padding amplifies the coarse lazy-diagonal hierarchy, which separates degree `d` from degree + `6 * d + 14`. Remaining targets include the general deterministic `f log f` hierarchy with its + simulation bound ([#800]), noninteger polynomial degrees and finer logarithmic gaps, and sharp + nondeterministic and alternating time hierarchies with certified simulators. - The space hierarchy for `log n ≤ g n`, which needs a universal simulation that does not hold its input. - **Space:** Savitch bounds for general space-constructible functions. From 48a6545c6091b21f6b1d19854e8be746217cbef9 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 12:07:01 -0400 Subject: [PATCH 07/16] refactor(Machines/PTM): make the pair sampler's proof configurations private The induction configurations `PairSampler.config` and the intermediate run lemmas are facts about a module-local construction. Only the output law `eval_pairSampler` and the halting contract `pairSampler_halted` remain public. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../MultiTape/Probabilistic/PairSampler.lean | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean index 7b6a8192b7..bce12d8550 100644 --- a/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean +++ b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean @@ -81,7 +81,7 @@ def pairSampler : MultiTapePTM 0 Bool Phase where namespace PairSampler /-- The sampler at an input position, with the output written so far. -/ -def config (input : List Bool) (state : Option Phase) (index : ℕ) (output : List Bool) +private def config (input : List Bool) (state : Option Phase) (index : ℕ) (output : List Bool) (h : index ≤ input.length) : Config 0 Bool Phase Empty input where tapes := { state := state @@ -91,7 +91,7 @@ def config (input : List Bool) (state : Option Phase) (index : ℕ) (output : Li output := output } /-- Reading a remaining symbol writes the emitted symbol and advances once. -/ -theorem step {input output : List Bool} {index : ℕ} (h : index < input.length) +private theorem step {input output : List Bool} {index : ℕ} (h : index < input.length) (q : Phase) (o : Option Bool) (s : Option Phase) : (config input (some q) index output (by lia)).step { inputTape := 1, workTapes := Fin.elim0, output := o, state := s } @@ -105,7 +105,7 @@ theorem step {input output : List Bool} {index : ℕ} (h : index < input.length) rfl /-- One live transition at a remaining symbol: draw a coin and take the selected step. -/ -theorem eval_run_succ {input output : List Bool} {index : ℕ} (h : index < input.length) +private theorem eval_run_succ {input output : List Bool} {index : ℕ} (h : index < input.length) (q : Phase) (fuel : ℕ) : ProbComp.eval (pairSampler.runClosedConfigFrom (fuel + 1) (config input (some q) index output (by lia))) = @@ -121,7 +121,7 @@ theorem eval_run_succ {input output : List Bool} {index : ℕ} (h : index < inpu rfl /-- At the right boundary the sampler halts, drawing and discarding one coin. -/ -theorem eval_run_boundary (input output : List Bool) (q : Phase) (fuel : ℕ) : +private theorem eval_run_boundary (input output : List Bool) (q : Phase) (fuel : ℕ) : ProbComp.eval (pairSampler.runClosedConfigFrom (fuel + 1) (config input (some q) input.length output le_rfl)) = PMF.pure (config input none input.length output le_rfl) := by @@ -160,7 +160,7 @@ theorem eval_run_boundary (input output : List Bool) (q : Phase) (fuel : ℕ) : rw [hhalt, PMF.bind_const] /-- The tail phase: one fresh fair bit per remaining symbol, then halting at the boundary. -/ -theorem eval_run_tail (input : List Bool) (remaining : ℕ) : +private theorem eval_run_tail (input : List Bool) (remaining : ℕ) : ∀ (index : ℕ) (output : List Bool) (h : index + remaining = input.length), ProbComp.eval (pairSampler.runClosedConfigFrom (remaining + 1) (config input (some .tail) index output (by lia))) = @@ -183,7 +183,7 @@ theorem eval_run_tail (input : List Bool) (remaining : ℕ) : List.append_assoc, List.singleton_append] /-- The copying phase: a framed word is copied verbatim, ignoring the coins drawn meanwhile. -/ -theorem eval_run_frame (input rest : List Bool) (fuel : ℕ) (y : List Bool) : +private theorem eval_run_frame (input rest : List Bool) (fuel : ℕ) (y : List Bool) : ∀ (pre output : List Bool) (hin : input = pre ++ frame y ++ rest), ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y).length + fuel) (config input (some .tag) pre.length output (by subst hin; simp))) = @@ -234,7 +234,7 @@ open PairSampler /-- The final configuration retains the framed word and a uniform certificate, and has halted at the right boundary. -/ -theorem eval_runClosedConfig_pairSampler (y r : List Bool) : +private theorem eval_runClosedConfig_pairSampler (y r : List Bool) : ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y ++ r).length + 1) (pairSampler.initialConfig (frame y ++ r))) = (BitString.uniformList r.length).map (fun w => From 4e1cc48bf9f846fc5091d0433192dfc60fe689cb Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 12:08:39 -0400 Subject: [PATCH 08/16] refactor(BigO/Bounds): make ComputableInO.mono_time public Weakening only the time bound of `ComputableInO`, keeping the space bound, is a general monotonicity law. It now sits beside `ComputableInO.mono` instead of as a private copy in the lazy-diagonal hierarchy. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Classes/Hierarchy/NondeterministicTime.lean | 8 -------- .../Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean index 38d0ff53de..4f2eb8644d 100644 --- a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean +++ b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean @@ -461,14 +461,6 @@ private theorem exists_computableInO_brute (d : ℕ) : ∃ K, ComputableInO fun_prop exact exists_computableInO_any_enumList hf hpoly hpoly hℓ -/-- Weaken only the time bound, keeping the space bound. -/ -private theorem _root_.Turing.MultiTapeTM.ComputableInO.mono_time {α β : Type*} {f : α → β} - {encIn : α ↪ List Bool} {encOut : β ↪ List Bool} {T S T' : α → ℕ} - (h : ComputableInO f encIn encOut T S) (k : ℕ) (hT : ∀ a, T a ≤ k * (T' a + 1)) : - ComputableInO f encIn encOut T' S := - h.mono (k + 1) (fun a => (hT a).trans (Nat.mul_le_mul_right _ (Nat.le_succ k))) - fun _ => (Nat.le_succ _).trans (Nat.le_mul_of_pos_left _ (Nat.succ_pos k)) - /-- The cost of the tests of the verifier: quadratic in the encoded input, times a power of the length of the input that pays for the clock in unary. -/ private def cheap (d : ℕ) (p : List Bool × List Bool) : ℕ := diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean index 048e3d7038..a38b07c63e 100644 --- a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean +++ b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean @@ -44,6 +44,12 @@ theorem mono {T' S' : α → ℕ} (h : ComputableInO f encIn encOut T S) (k : of_bounds hc (c * (k + 1)) (fun a => by nlinarith [hT a]) (fun a => by nlinarith [hS a]) (fun a => by nlinarith [hS a, hl a]) +/-- Weaken only the time bound up to a constant, keeping the space bound. -/ +theorem mono_time {T' : α → ℕ} (h : ComputableInO f encIn encOut T S) (k : ℕ) + (hT : ∀ a, T a ≤ k * (T' a + 1)) : ComputableInO f encIn encOut T' S := + h.mono (k + 1) (fun a => (hT a).trans (Nat.mul_le_mul_right _ (Nat.le_succ k))) + fun _ => (Nat.le_succ _).trans (Nat.le_mul_of_pos_left _ (Nat.succ_pos k)) + /-- Pointwise larger bounds retain the same encoded-output charge. -/ theorem mono_le {T' S' : α → ℕ} (h : ComputableInO f encIn encOut T S) (hT : ∀ a, T a ≤ T' a) (hS : ∀ a, S a ≤ S' a) : From 37584528d3a1da16f38812ffbacb148e3ec98921 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:23:52 +0000 Subject: [PATCH 09/16] feat(WordRAM): explicit-degree overhead for the Turing-machine simulation (#739) (#2046) * feat(WordRAM): explicit-degree overhead for the Turing-machine simulation (#739) Make the degree of the word-RAM => Turing-machine simulation explicit. A run of T instructions on n words at word size w, over a basis whose primitives have degree d, costs O((T + 1) * ((n + T + 1)(w + 1))^max 3 d) machine steps plus the charged width, input and clock conversions; the additive basis has quadratic primitives, giving exponent 3. Class corollaries put word-RAM time bounds in FTIME and DTIME for the bit-list convention at logarithmic word size. - NativeCertificate.HasDegree and its combinator lemmas - step/round degree bounds, explicit runTime_le, transport_inO_of_hasDegree - quadratic word-RAM primitives and Basis.additive.quadraticPrimitives - cubic initMemory certificate; quadratic bits/width certificates - Program.ComputesInTime.mem_FTIME, Program.DecidesInTime.mem_DTIME Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(WordRAM): named components and shared length lemmas in the overhead (#739) Address the review of #2046. - readRaw_hasDegree and writeRaw_hasDegree bind the lookup and update components by name instead of projection chains (#1764); the functions stay definitionally equal. - Overhead reuses Turing.MultiTapeTM.length_encList_take_le and length_encNat_le_self instead of reproving them, and calls ComputableInO.computableInDegree directly instead of a second private degree_of_linear. - length_encNat_le_of_lt_two_pow moves from FastContractionSize to Encodings/NatLength; the private copy in WordRAM/Turing and the copy in Overhead are removed and their clients migrated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --------- Co-authored-by: Claude Opus 5.5 --- Complexity.lean | 5 + .../TensorNetwork/FastContractionSize.lean | 6 +- .../Classes/FunctionTime/WordRAM.lean | 146 ++++++ .../GenericRAM/Native/Degree.lean | 280 +++++++++++ .../GenericRAM/Simulation/Degree.lean | 447 ++++++++++++++++++ .../Turing/MultiTape/Encodings/NatLength.lean | 14 +- .../Computability/WordRAM/NativeDegree.lean | 207 ++++++++ Complexity/Computability/WordRAM/Turing.lean | 15 +- .../WordRAM/Turing/Overhead.lean | 446 +++++++++++++++++ ComplexityTests/WordRAMTuringOverhead.lean | 99 ++++ 10 files changed, 1648 insertions(+), 17 deletions(-) create mode 100644 Complexity/Computability/Complexity/Classes/FunctionTime/WordRAM.lean create mode 100644 Complexity/Computability/GenericRAM/Native/Degree.lean create mode 100644 Complexity/Computability/GenericRAM/Simulation/Degree.lean create mode 100644 Complexity/Computability/WordRAM/NativeDegree.lean create mode 100644 Complexity/Computability/WordRAM/Turing/Overhead.lean create mode 100644 ComplexityTests/WordRAMTuringOverhead.lean diff --git a/Complexity.lean b/Complexity.lean index 3bb8bfd771..efc9a21a67 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1179,6 +1179,7 @@ public import Complexity.Computability.Complexity.Classes.FewP.Machine public import Complexity.Computability.Complexity.Classes.FunctionTime public import Complexity.Computability.Complexity.Classes.FunctionTime.Bounds public import Complexity.Computability.Complexity.Classes.FunctionTime.Decide +public import Complexity.Computability.Complexity.Classes.FunctionTime.WordRAM public import Complexity.Computability.Complexity.Classes.Hierarchy public import Complexity.Computability.Complexity.Classes.Hierarchy.LogClock public import Complexity.Computability.Complexity.Classes.Hierarchy.Nondeterministic @@ -1606,6 +1607,7 @@ public import Complexity.Computability.GenericRAM.Native.BooleanFold public import Complexity.Computability.GenericRAM.Native.BooleanSearch public import Complexity.Computability.GenericRAM.Native.Certificate public import Complexity.Computability.GenericRAM.Native.Combinators +public import Complexity.Computability.GenericRAM.Native.Degree public import Complexity.Computability.GenericRAM.Native.ExhaustiveCountFallback public import Complexity.Computability.GenericRAM.Native.HeavyBranchFold public import Complexity.Computability.GenericRAM.Native.IndexFold @@ -1616,6 +1618,7 @@ public import Complexity.Computability.GenericRAM.Native.StackRecursionTree public import Complexity.Computability.GenericRAM.Native.SubsetPairFold public import Complexity.Computability.GenericRAM.Signed public import Complexity.Computability.GenericRAM.Signed.Fits +public import Complexity.Computability.GenericRAM.Simulation.Degree public import Complexity.Computability.GenericRAM.Simulation.Finite public import Complexity.Computability.GenericRAM.Simulation.Run public import Complexity.Computability.GenericRAM.Simulation.Turing @@ -2344,6 +2347,7 @@ public import Complexity.Computability.WordRAM.Multiword.Sub public import Complexity.Computability.WordRAM.Multiword.Transfer public import Complexity.Computability.WordRAM.Multiword.Workspace public import Complexity.Computability.WordRAM.Native +public import Complexity.Computability.WordRAM.NativeDegree public import Complexity.Computability.WordRAM.Notation public import Complexity.Computability.WordRAM.OfTuring public import Complexity.Computability.WordRAM.PackedCount @@ -2373,6 +2377,7 @@ public import Complexity.Computability.WordRAM.Subroutine public import Complexity.Computability.WordRAM.Time public import Complexity.Computability.WordRAM.Trace public import Complexity.Computability.WordRAM.Turing +public import Complexity.Computability.WordRAM.Turing.Overhead public import Complexity.Computability.WordRAM.Width public import Complexity.Crypto.Computational.Basic public import Complexity.Crypto.Computational.CollisionResistant diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSize.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSize.lean index a4800c8085..471646d6cb 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSize.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSize.lean @@ -9,6 +9,7 @@ module public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionShape import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.NatLength import Mathlib.Data.List.GetD /-! @@ -49,11 +50,6 @@ theorem length_encNat_le {a b : ℕ} (h : a ≤ b) : (encNat a).length ≤ b := rw [encNat_apply, Nat.size_eq_bits_len] exact Nat.size_le.mpr (h.trans_lt Nat.lt_two_pow_self) -/-- A number below `2 ^ p` has at most `p` binary digits. -/ -theorem length_encNat_le_of_lt_two_pow {a p : ℕ} (h : a < 2 ^ p) : (encNat a).length ≤ p := by - rw [encNat_apply, Nat.size_eq_bits_len] - exact Nat.size_le.mpr h - /-- **A list of elements of encoded length at most `B`** has encoded length at most `k (2 B + 1)` for `k` elements. -/ theorem length_encList_le {α : Type*} (enc : α ↪ List Bool) {l : List α} {B : ℕ} diff --git a/Complexity/Computability/Complexity/Classes/FunctionTime/WordRAM.lean b/Complexity/Computability/Complexity/Classes/FunctionTime/WordRAM.lean new file mode 100644 index 0000000000..c7a919bd2d --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/FunctionTime/WordRAM.lean @@ -0,0 +1,146 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Turing.Overhead +public import Complexity.Computability.Complexity.Classes.FunctionTime +public import Complexity.Computability.Complexity.Classes.Time + +/-! +# Word-RAM time bounds give deterministic time bounds + +A fixed `Basis.additive` program that computes `f` within `T n` instructions on the bits of an +input of length `n`, at every word size of at least the logarithmic threshold `logWidth k n`, gives +`f ∈ FTIME encIn encNat (t + (n + T + 1) ^ 4 · (k (size n + 1) + 1) ^ 3)`, provided the time bound +`T n` itself is computable from the input in time `O(t n)`. For `T n ≥ n` this is +`O(t n + T(n) ^ 4 log³ n)`. The decision version gives the same bound in `DTIME`. + +The simulating machine runs at the word size `k (size n + 1) = Θ(k log n)`, and its overhead is the +explicit polynomial of `Program.ComputesInTime.computableInO_of_width_additive`. The clock +hypothesis on `T` is needed because the machine computes its own step budget. The exponent `4` +comes from the association-list memory of the simulator, whose scans are cubic in the encoded +state; it is not a property of the word-RAM model. + +The input convention is that of `Complexity.Computability.WordRAM.Invariance`: the words are the +bits of `encIn a`, and the size is `|encIn a|`. Together with the converse simulations of +`Complexity.Computability.WordRAM.OfTuring`, this transfers word-RAM running times to the time +classes beyond `P`. + +## Main results + +* `Cslib.WordRAM.Program.ComputesInTime.computableInO_bits`: the simulation as a + `ComputableInO` bound depending only on the input length. +* `Cslib.WordRAM.Program.ComputesInTime.mem_FTIME`: word-RAM time gives function time. +* `Cslib.WordRAM.Program.DecidesInTime.mem_DTIME`: word-RAM decision time gives `DTIME`. + +## References + +* [S. A. Cook and R. A. Reckhow, *Time bounded random access machines*][CookReckhow1973] +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +open Turing.MultiTapeTM Cslib.GenericRAM Cslib.Complexity + +variable {α : Type} {encIn : α ↪ List Bool} {r k : ℕ} + +/-- **The simulation of the bit-list convention at logarithmic word size**, with time +`O(t n + (n + T n + 1) ^ 4 · (k (size n + 1) + 1) ^ 3)` and a space bound depending only on the +input length `n`. -/ +theorem Program.ComputesInTime.computableInO_bits (hk : 1 ≤ k) {p : Program .additive r} + {T t : ℕ → ℕ} {f : α → ℕ} + (h : p.ComputesInTime (fun _ a => (encIn a).map Bool.toNat) (fun a _ => T (encIn a).length) + (fun a w => logWidth k (encIn a).length ≤ w) f) + (hT : (fun a => T (encIn a).length) ∈ FTIME encIn encNat t) : + ∃ S : ℕ → ℕ, Turing.MultiTapeTM.ComputableInO f encIn encNat + (fun a => t (encIn a).length + ((encIn a).length + T (encIn a).length + 1) ^ 4 * + (k * (Nat.size (encIn a).length + 1) + 1) ^ 3) + (fun a => S (encIn a).length) := by + obtain ⟨c₁, c₂, s, hTs⟩ := hT + have hwidth := ((logWidthBound_degree k).of_encIn_eq (encIn' := encIn) (fun a => encIn a) + fun _ => rfl).inO + have hencode := (bitsToNats_degree.of_encIn_eq (encIn' := encIn) (fun a => encIn a) + fun _ => rfl).inO + have hO := h.computableInO_of_width_additive + (width := fun a => k * (Nat.size (encIn a).length + 1)) hwidth + (fun a => logWidth_le_mul_size_succ k _) (fun a => inputFits_map_toNat hk _) hencode + (NativeCertificate.ofTimeAndSpace hTs).computes (fun _ => le_rfl) + refine ⟨fun n => n + 2 * (n + 1) ^ 2 + (s n + (c₁ * t n + c₂)) + + ((n + T n + 1) * (k * (Nat.size n + 1) + 1)) ^ 3, hO.mono (c₁ + c₂ + 5) (fun a => ?_) + (fun a => ?_)⟩ + · simp only [List.length_map, NativeCertificate.ofTimeAndSpace] + obtain ⟨X, hX⟩ : ∃ X, X = ((encIn a).length + T (encIn a).length + 1) ^ 4 * + (k * (Nat.size (encIn a).length + 1) + 1) ^ 3 := ⟨_, rfl⟩ + rw [← hX] + have hw1 : 1 ≤ (k * (Nat.size (encIn a).length + 1) + 1) ^ 3 := Nat.one_le_pow _ _ (by omega) + have hcube : (T (encIn a).length + 1) * (((encIn a).length + T (encIn a).length + 1) * + (k * (Nat.size (encIn a).length + 1) + 1)) ^ 3 ≤ X := by + rw [hX, Nat.mul_pow, ← Nat.mul_assoc] + refine Nat.mul_le_mul_right _ ?_ + calc (T (encIn a).length + 1) * ((encIn a).length + T (encIn a).length + 1) ^ 3 + ≤ ((encIn a).length + T (encIn a).length + 1) * + ((encIn a).length + T (encIn a).length + 1) ^ 3 := + Nat.mul_le_mul_right _ (by omega) + _ = ((encIn a).length + T (encIn a).length + 1) ^ 4 := by ring + have hsq : ((encIn a).length + 1) ^ 2 ≤ X := by + rw [hX] + calc ((encIn a).length + 1) ^ 2 ≤ ((encIn a).length + T (encIn a).length + 1) ^ 4 := + (Nat.pow_le_pow_left (by omega) 2).trans (Nat.pow_le_pow_right (by omega) (by omega)) + _ ≤ _ := Nat.le_mul_of_pos_right _ hw1 + have hn : (encIn a).length ≤ ((encIn a).length + 1) ^ 2 := by nlinarith + have h0 := Nat.zero_le (c₁ * X) + have h1 := Nat.zero_le (c₂ * t (encIn a).length) + have h2 := Nat.zero_le (c₂ * X) + nlinarith + · simp only [List.length_map, NativeCertificate.ofTimeAndSpace] + refine le_trans ?_ (Nat.le_mul_of_pos_left _ (by omega)) + omega + +/-- **Word-RAM time gives function time.** A `Basis.additive` program computing `f` within +`T n` instructions on the bits of an input of length `n`, at every word size of at least +`logWidth k n`, computes a function in `FTIME encIn encNat` with bound +`t n + (n + T n + 1) ^ 4 · (k (size n + 1) + 1) ^ 3`, when `T n` is computable from the input in +time `O(t n)`. -/ +theorem Program.ComputesInTime.mem_FTIME (hk : 1 ≤ k) {p : Program .additive r} + {T t : ℕ → ℕ} {f : α → ℕ} + (h : p.ComputesInTime (fun _ a => (encIn a).map Bool.toNat) (fun a _ => T (encIn a).length) + (fun a w => logWidth k (encIn a).length ≤ w) f) + (hT : (fun a => T (encIn a).length) ∈ FTIME encIn encNat t) : + f ∈ FTIME encIn encNat + (fun n => t n + (n + T n + 1) ^ 4 * (k * (Nat.size n + 1) + 1) ^ 3) := by + obtain ⟨S, c, hc, -⟩ := h.computableInO_bits hk hT + exact ⟨c, c, fun n => c * (S n + 1), hc.mono (fun a => by rw [Nat.mul_add, Nat.mul_one]) + (fun _ => le_rfl)⟩ + +/-- **Word-RAM decision time gives deterministic time.** A `Basis.additive` program deciding `A` +within `T n` instructions on the bits of an input of length `n`, at every word size of at least +`logWidth k n`, puts `A` in `DTIME encIn` with bound +`t n + (n + T n + 1) ^ 4 · (k (size n + 1) + 1) ^ 3`, when `T n` is computable from the input in +time `O(t n)`. -/ +theorem Program.DecidesInTime.mem_DTIME (hk : 1 ≤ k) {p : Program .additive r} + {T t : ℕ → ℕ} {A : Set α} + (h : p.DecidesInTime (fun _ a => (encIn a).map Bool.toNat) A (fun a _ => T (encIn a).length) + (fun a w => logWidth k (encIn a).length ≤ w)) + (hT : (fun a => T (encIn a).length) ∈ FTIME encIn encNat t) : + A ∈ DTIME encIn (fun n => t n + (n + T n + 1) ^ 4 * (k * (Nat.size n + 1) + 1) ^ 3) := by + classical + obtain ⟨S, hO⟩ := h.computesInTime.computableInO_bits hk hT + have hone : Turing.MultiTapeTM.ComputableInO (fun v : ℕ => decide (v = 1)) encNat encBool + (fun v => (encNat v).length) (fun v => (encNat v).length) := + ((ComputableInDegree.of_linear ComputableInLinearTimeAndSpace.natEq).comp + ((ComputableInDegree.of_eq (f := fun v : ℕ => v) (encIn := encNat) (encMid := encNat) + fun _ => rfl).prod (ComputableInDegree.const 1))).mono (d' := 1) (e' := 1) |>.inO_one + have h := setOf_mem_DTIME_of_computableInO (hone.comp_linear hO) + (t := fun n => t n + (n + T n + 1) ^ 4 * (k * (Nat.size n + 1) + 1) ^ 3) fun _ => le_rfl + convert h using 1 + ext a + by_cases ha : a ∈ A <;> simp [ha] + +end Cslib.WordRAM diff --git a/Complexity/Computability/GenericRAM/Native/Degree.lean b/Complexity/Computability/GenericRAM/Native/Degree.lean new file mode 100644 index 0000000000..4d7ce5ac04 --- /dev/null +++ b/Complexity/Computability/GenericRAM/Native/Degree.lean @@ -0,0 +1,280 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.GenericRAM.Native +public import Complexity.Computability.GenericRAM.Native.Combinators + +/-! +# Native certificates with explicit polynomial degrees + +`NativeCertificate.IsPolynomial` says that a certificate's time and space bounds are polynomial in +the encoded input length, without naming a degree. `NativeCertificate.HasDegree c d` names it: the +time and space bounds are at most a constant times `(n + 1) ^ d` on encoded inputs of length `n`, +and the encoded output has linear length. Linear output is what keeps the degree from growing under +composition: a degree-`d` bound evaluated at a linearly long intermediate value is again of degree +`d`. This is the discipline of `Turing.MultiTapeTM.ComputableInDegree` with output degree one, +applied to the input-dependent bounds that certificates carry. + +Every certificate combinator preserves `HasDegree`, so a simulation assembled from them inherits an +explicit degree from its primitives. `Model.NativePrimitives.HasDegree` asks the same of all +primitive certificates of a RAM model. + +## Main definitions + +* `Cslib.GenericRAM.NativeCertificate.HasDegree`: time and space bounds of degree `d`, encoded + output of linear length. +* `Cslib.GenericRAM.NativeCertificate.ofDegree`: the certificate of a degree-`d` computation with + linear output. +* `Cslib.GenericRAM.Model.NativePrimitives.HasDegree`: every primitive certificate has degree `d`. + +## Main results + +* `Cslib.GenericRAM.NativeCertificate.HasDegree.isPolynomial`: a degree bound is polynomial. +* `Cslib.GenericRAM.NativeCertificate.comp_hasDegree`, `.prod_hasDegree`, `.branch_hasDegree`, + `.option_hasDegree` and the other `*_hasDegree` lemmas: the combinators preserve the degree. +-/ + +@[expose] public section + +namespace Cslib.GenericRAM.NativeCertificate + +open Turing.MultiTapeTM + +variable {α β γ δ : Type} {ei : α ↪ List Bool} {em : β ↪ List Bool} + {eo : γ ↪ List Bool} {f : α → β} {g : α → γ} {d d' : ℕ} + +/-- Time and space bounds of degree `d`, and encoded output of linear length, in the encoded input +length. -/ +structure HasDegree (c : NativeCertificate f ei em) (d : ℕ) : Prop where + /-- The time bound has degree `d`. -/ + time : DegreeBounded (fun a => (ei a).length) c.time d + /-- The space bound has degree `d`. -/ + space : DegreeBounded (fun a => (ei a).length) c.space d + /-- The encoded output has linear length. -/ + length : DegreeBounded (fun a => (ei a).length) (fun a => (em (f a)).length) 1 + +namespace HasDegree + +variable {c : NativeCertificate f ei em} + +/-- Raising the degree keeps the bound. -/ +theorem mono (h : c.HasDegree d) (hd : d ≤ d') : c.HasDegree d' := + ⟨h.time.mono_deg hd, h.space.mono_deg hd, h.length⟩ + +/-- A certificate of some degree is polynomial. -/ +theorem isPolynomial (h : c.HasDegree d) : c.IsPolynomial := + ⟨h.time.polyBounded, h.space.polyBounded⟩ + +/-- Transfer a degree to a certificate with the same bounds for a pointwise equal function. This +sees through certificates defined by case analysis, whose casts block unification. -/ +theorem of_eq {f' : α → β} (c' : NativeCertificate f' ei em) (h : c.HasDegree d) + (ht : c'.time = c.time) (hs : c'.space = c.space) (hf : ∀ a, f a = f' a) : + c'.HasDegree d := + ⟨ht ▸ h.time, hs ▸ h.space, h.length.mono fun a => by rw [hf a]⟩ + +end HasDegree + +/-- A degree-`d` bound evaluated at an intermediate value of linear length has degree `d`. -/ +theorem degreeBounded_comp {size : α → ℕ} {u : β → ℕ} + (hu : DegreeBounded (fun b => (em b).length) u d) + (hf : DegreeBounded size (fun a => (em (f a)).length) 1) : + DegreeBounded size (fun a => u (f a)) d := by + simpa only [one_mul] using hu.comp hf + +/-- A linear-time, linear-space computation has encoded output of linear length. -/ +theorem length_of_linear {h : β → γ} + (hh : ComputableInO h em eo (fun b => (em b).length) (fun b => (em b).length)) : + DegreeBounded (fun b => (em b).length) (fun b => (eo (h b)).length) 1 := by + obtain ⟨k, -, hl⟩ := hh + exact ⟨k, fun b => by simpa only [pow_one] using hl b⟩ + +/-- A certificate with linear space has encoded output of linear length. -/ +theorem length_of_space (c : NativeCertificate f ei em) + (hs : DegreeBounded (fun a => (ei a).length) c.space 1) : + DegreeBounded (fun a => (ei a).length) (fun a => (em (f a)).length) 1 := by + obtain ⟨k, -, hl⟩ := c.computes + obtain ⟨C, hC⟩ := hs + refine ⟨k * (C + 1), fun a => (hl a).trans ?_⟩ + have := hC a + simp only [pow_one] at this ⊢ + nlinarith + +/-- A certificate whose time has degree `d ≥ 1` and whose space is linear has degree `d`. -/ +theorem hasDegree_of_space_linear (c : NativeCertificate f ei em) + (ht : DegreeBounded (fun a => (ei a).length) c.time d) + (hs : DegreeBounded (fun a => (ei a).length) c.space 1) (hd : 1 ≤ d) : c.HasDegree d := + ⟨ht, hs.mono_deg hd, c.length_of_space hs⟩ + +/-- The certificate of a computation in time and space degree `d` with linear output. -/ +def ofDegree (h : ComputableInDegree f ei em d 1) : NativeCertificate f ei em := + ⟨fun a => ((ei a).length + 1) ^ d, fun a => ((ei a).length + 1) ^ d, h.inO⟩ + +@[simp] theorem ofDegree_time (h : ComputableInDegree f ei em d 1) (a : α) : + (ofDegree h).time a = ((ei a).length + 1) ^ d := rfl + +@[simp] theorem ofDegree_space (h : ComputableInDegree f ei em d 1) (a : α) : + (ofDegree h).space a = ((ei a).length + 1) ^ d := rfl + +/-- `ofDegree` keeps the degree of the computation. -/ +theorem ofDegree_hasDegree (h : ComputableInDegree f ei em d 1) : (ofDegree h).HasDegree d := + ⟨⟨1, fun a => by simp⟩, ⟨1, fun a => by simp⟩, h.2⟩ + +/-! ## The combinators preserve the degree -/ + +theorem linear_hasDegree + (h : ComputableInO f ei em (fun a => (ei a).length) (fun a => (ei a).length)) (hd : 1 ≤ d) : + (linear h).HasDegree d := + ⟨DegreeBounded.self.mono_deg hd, DegreeBounded.self.mono_deg hd, length_of_linear h⟩ + +theorem const_hasDegree (b : β) (hd : 1 ≤ d) : (const (ei := ei) (em := em) b).HasDegree d := + linear_hasDegree _ hd + +theorem comp_hasDegree {h : β → γ} (c' : NativeCertificate h em eo) + (c : NativeCertificate f ei em) (hc' : c'.HasDegree d) (hc : c.HasDegree d) : + (c'.comp c).HasDegree d := + ⟨hc.time.add (degreeBounded_comp hc'.time hc.length), + hc.space.add (degreeBounded_comp hc'.space hc.length), + degreeBounded_comp (f := f) hc'.length hc.length⟩ + +theorem prod_hasDegree (c : NativeCertificate f ei em) (c' : NativeCertificate g ei eo) + (hc : c.HasDegree d) (hc' : c'.HasDegree d) : (prod c c').HasDegree d := + ⟨hc.time.add hc'.time, hc.space.add hc'.space, + ((hc.length.smul 2).add_const 1 |>.add hc'.length).mono fun a => by + simp only [length_encPair, le_refl]⟩ + +theorem post_hasDegree {h : β → γ} (c : NativeCertificate f ei em) + (hh : ComputableInO h em eo (fun b => (em b).length) (fun b => (em b).length)) + (hc : c.HasDegree d) : (post c hh).HasDegree d := + ⟨hc.time, hc.space, degreeBounded_comp (f := f) (length_of_linear hh) hc.length⟩ + +theorem fst_hasDegree {f : α → β × γ} (c : NativeCertificate f ei (encPair em eo)) + (hc : c.HasDegree d) : (fst c).HasDegree d := + post_hasDegree c _ hc + +theorem snd_hasDegree {f : α → β × γ} (c : NativeCertificate f ei (encPair em eo)) + (hc : c.HasDegree d) : (snd c).HasDegree d := + post_hasDegree c _ hc + +theorem some_hasDegree (c : NativeCertificate f ei em) (hc : c.HasDegree d) : + (some c).HasDegree d := + post_hasDegree c _ hc + +theorem inl_hasDegree (c : NativeCertificate f ei em) (right : γ ↪ List Bool) + (hc : c.HasDegree d) : (inl c right).HasDegree d := + post_hasDegree c _ hc + +theorem inr_hasDegree (left : γ ↪ List Bool) (c : NativeCertificate f ei em) + (hc : c.HasDegree d) : (inr left c).HasDegree d := + post_hasDegree c _ hc + +theorem congr_hasDegree {f' : α → β} (c : NativeCertificate f ei em) (h : ∀ a, f a = f' a) + (hc : c.HasDegree d) : (congr c h).HasDegree d := + ⟨hc.time, hc.space, hc.length.mono fun a => by rw [← h a]⟩ + +theorem pull_hasDegree {ed : δ ↪ List Bool} (c : NativeCertificate f ei em) + (hc : c.HasDegree d) (hd : 1 ≤ d) : (pull c ed).HasDegree d := + comp_hasDegree c _ hc (linear_hasDegree _ hd) + +theorem payload_hasDegree (input : α ↪ List Bool) (out : β ↪ List Bool) (hd : 1 ≤ d) : + (payload input out).HasDegree d := + linear_hasDegree _ hd + +theorem branch_hasDegree {condition : α → Bool} {f₁ f₂ : α → β} + (test : NativeCertificate condition ei encBool) + (yes : NativeCertificate f₁ ei em) (no : NativeCertificate f₂ ei em) + (ht : test.HasDegree d) (hy : yes.HasDegree d) (hn : no.HasDegree d) : + (branch test yes no).HasDegree d := by + refine ⟨(ht.time.add (hy.time.add hn.time)).mono fun a => ?_, + (ht.space.add (hy.space.add hn.space)).mono fun a => ?_, + (hy.length.add hn.length).mono fun a => ?_⟩ + · simp only [branch_time]; split <;> omega + · simp only [branch_space]; split <;> omega + · split <;> omega + +theorem option_hasDegree {value : α → Option β} {fallback : α → γ} {body : α × β → γ} + (c : NativeCertificate value ei (encOption em)) + (no : NativeCertificate fallback ei eo) + (yes : NativeCertificate body (encPair ei em) eo) + (hc : c.HasDegree d) (hno : no.HasDegree d) (hyes : yes.HasDegree d) (hd : 1 ≤ d) : + (option c no yes).HasDegree d := by + -- The continuation's input `(a, b)` is linearly long when `value a = some b`. + have hL : DegreeBounded (fun a => (ei a).length) + (fun a => 2 * (ei a).length + (encOption em (value a)).length) 1 := + (DegreeBounded.self.smul 2).add hc.length + have hpair (a : α) (b : β) (hv : value a = Option.some b) : + (encPair ei em (a, b)).length = 2 * (ei a).length + (encOption em (value a)).length := by + rw [hv]; simp; omega + -- A bound `C (|(a, b)| + 1) ^ k` on the continuation is of degree `k` in `|ei a|`. + have hsub {k C : ℕ} : DegreeBounded (fun a => (ei a).length) + (fun a => C * (2 * (ei a).length + (encOption em (value a)).length + 1) ^ k) k := by + simpa only [one_mul] using DegreeBounded.comp (size' := id) + (v := fun n => C * (n + 1) ^ k) ⟨C, fun _ => le_rfl⟩ hL + obtain ⟨ct, hyt⟩ := hyes.time + obtain ⟨cs, hys⟩ := hyes.space + obtain ⟨cl, hyl⟩ := hyes.length + refine ⟨((hc.time.smul 2).add (DegreeBounded.self.mono_deg hd) |>.add + (hno.time.add (hsub (C := ct)))).mono fun a => ?_, + ((hc.space.smul 2).add (DegreeBounded.self.mono_deg hd) |>.add + (hno.space.add (hsub (C := cs)))).mono fun a => ?_, + (hno.length.add (hsub (C := cl))).mono fun a => ?_⟩ + · simp only [option_time] + cases hv : value a with + | none => simp + | some b => + have := hyt (a, b) + dsimp only at this + rw [hpair a b hv, hv] at this + simp only [Option.elim_some] + omega + · simp only [option_space] + cases hv : value a with + | none => simp + | some b => + have := hys (a, b) + dsimp only at this + rw [hpair a b hv, hv] at this + simp only [Option.elim_some] + omega + · cases hv : value a with + | none => simp + | some b => + have := hyl (a, b) + dsimp only at this + rw [hpair a b hv, hv] at this + simp only [Option.elim_some, pow_one] at this ⊢ + omega + +end Cslib.GenericRAM.NativeCertificate + +namespace Cslib.GenericRAM.Model + +/-- Every primitive certificate of the bundle has degree `d`. -/ +structure NativePrimitives.HasDegree {M : Model.{0, 0, 0, 0}} {enc : M.Encoding} + (N : M.NativePrimitives enc) (d : ℕ) : Prop where + /-- Every literal symbol's certificate has degree `d`. -/ + constant : ∀ c, (N.constant c).HasDegree d + /-- Every operation symbol's certificate has degree `d`. -/ + operation : ∀ op, (N.operation op).HasDegree d + /-- Every test symbol's certificate has degree `d`. -/ + test : ∀ t, (N.test t).HasDegree d + /-- The address extraction certificate has degree `d`. -/ + address : N.address.HasDegree d + /-- The move validation certificate has degree `d`. -/ + validMove : N.validMove.HasDegree d + /-- The control-label validation certificate has degree `d`. -/ + validPC : N.validPC.HasDegree d + +/-- Raising the degree of a primitive bundle keeps the bound. -/ +theorem NativePrimitives.HasDegree.mono {M : Model.{0, 0, 0, 0}} {enc : M.Encoding} + {N : M.NativePrimitives enc} {d d' : ℕ} (h : N.HasDegree d) (hd : d ≤ d') : + N.HasDegree d' := + ⟨fun c => (h.constant c).mono hd, fun op => (h.operation op).mono hd, + fun t => (h.test t).mono hd, h.address.mono hd, h.validMove.mono hd, h.validPC.mono hd⟩ + +end Cslib.GenericRAM.Model diff --git a/Complexity/Computability/GenericRAM/Simulation/Degree.lean b/Complexity/Computability/GenericRAM/Simulation/Degree.lean new file mode 100644 index 0000000000..de54e3758d --- /dev/null +++ b/Complexity/Computability/GenericRAM/Simulation/Degree.lean @@ -0,0 +1,447 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.GenericRAM.Simulation.Run +public import Complexity.Computability.GenericRAM.Native.Degree + +/-! +# Explicit-degree overhead of the finite-state RAM simulation + +When every primitive certificate of a RAM model has degree `D ≥ 3` +(`Model.NativePrimitives.HasDegree`), one step of the native finite-state simulator has degree `D` +in the encoded runtime configuration and state. The `3` is the sparse-memory scan, which is cubic in +the encoded memory (`SparseMemory.scanTime_le_cube`). A clocked run of `n` steps through states of +encoded length at most `B` therefore costs `O(|input| + (n + 1) · ((B + 1) ^ D + (log n + 1) ^ 2))` +machine steps, and whole-run transport inherits this bound with every constant outside the degree. + +These are the degree analogues of the `*_isPolynomial` lemmas of +`Complexity.Computability.GenericRAM.Simulation.Run`, which give polynomial bounds of unnamed +degree. + +## Main results + +* `Cslib.GenericRAM.FiniteState.stepCertificate_hasDegree`, + `Cslib.GenericRAM.FiniteState.roundCertificate_hasDegree`: one simulated step has degree `D`. +* `Cslib.GenericRAM.FiniteState.runTime_le`, `Cslib.GenericRAM.FiniteState.runSpace_le`: explicit + bounds on a clocked run from per-round bounds and a bound on the visited states. +* `Cslib.GenericRAM.FiniteState.transport_inO_of_hasDegree`: whole-run transport with time + `O(… + (steps + 1) · ((B + 1) ^ D + (log steps + 1) ^ 2))`. + +## References + +* [S. A. Cook and R. A. Reckhow, *Time bounded random access machines*][CookReckhow1973] +-/ + +@[expose] public section + +namespace Cslib.GenericRAM.FiniteState + +open Turing.MultiTapeTM NativeCertificate + +variable {M : Model.{0, 0, 0, 0}} {r : ℕ} {α : Type} {ei : α ↪ List Bool} {D : ℕ} + +namespace Native + +variable {s : α → FiniteState M r} {k : α → M.Config} {v : α → M.Value} + +theorem fields_hasDegree (enc : M.Encoding) (c : NativeCertificate s ei (encoding enc)) + (hc : c.HasDegree D) : (fields enc c).HasDegree D := + post_hasDegree c _ hc + +theorem register_hasDegree (enc : M.Encoding) (c : NativeCertificate s ei (encoding enc)) + (i : Fin r) (hc : c.HasDegree D) : (register enc c i).HasDegree D := + post_hasDegree _ _ (fst_hasDegree _ (snd_hasDegree _ (fields_hasDegree enc c hc))) + +theorem operands_hasDegree (enc : M.Encoding) (c : NativeCertificate s ei (encoding enc)) + {n : ℕ} (args : Fin n → Fin r) (hc : c.HasDegree D) : + (operands enc c args).HasDegree D := + post_hasDegree _ _ (fst_hasDegree _ (snd_hasDegree _ (fields_hasDegree enc c hc))) + +theorem setCertificate_hasDegree (enc : M.Encoding) + (c : NativeCertificate s ei (encoding enc)) (d : Fin r) + (value : NativeCertificate v ei enc.value) (hc : c.HasDegree D) (hv : value.HasDegree D) : + (setCertificate enc c d value).HasDegree D := by + have hfs := fields_hasDegree enc c hc + exact post_hasDegree _ (pack enc).computes (prod_hasDegree _ _ (fst_hasDegree _ hfs) + (prod_hasDegree _ _ (post_hasDegree _ _ (prod_hasDegree _ _ + (fst_hasDegree _ (snd_hasDegree _ hfs)) hv)) (snd_hasDegree _ (snd_hasDegree _ hfs)))) + +theorem moveCertificate_hasDegree (enc : M.Encoding) (N : M.NativePrimitives enc) + (config : NativeCertificate k ei enc.config) (c : NativeCertificate s ei (encoding enc)) + (d : Fin r) (value : NativeCertificate v ei enc.value) (hD : 1 ≤ D) + (hN : N.HasDegree D) (hk : config.HasDegree D) (hc : c.HasDegree D) + (hv : value.HasDegree D) : + (moveCertificate enc N config c d value).HasDegree D := + branch_hasDegree _ _ _ + (comp_hasDegree N.validMove (prod config value) hN.validMove (prod_hasDegree _ _ hk hv)) + (some_hasDegree _ (setCertificate_hasDegree enc c d value hc hv)) + (const_hasDegree none hD) + +/-- The raw sparse-memory lookup is cubic in time and linear in space. -/ +theorem readRaw_hasDegree (enc : M.Encoding) (hD : 3 ≤ D) : + (⟨SparseMemory.readTime enc.address enc.value, SparseMemory.readSpace enc.address enc.value, + SparseMemory.read_inO enc.address enc.value⟩ : + NativeCertificate + (fun (((address, memory), defaultValue) : + (M.Address × SparseMemory M.Address M.Value) × M.Value) => + SparseMemory.read defaultValue address memory) _ _).HasDegree D := by + refine hasDegree_of_space_linear _ (DegreeBounded.mono_deg ⟨32, fun q => ?_⟩ hD) + ⟨32, fun q => ?_⟩ (by omega) + · have hq : (SparseMemory.queryEncoding enc.address enc.value q.1).length ≤ + (SparseMemory.readEncoding enc.address enc.value q).length := by + simp only [SparseMemory.readEncoding, length_encPair]; omega + have hp := Nat.pow_le_pow_left (Nat.add_le_add_right hq 1) 3 + have ht := SparseMemory.scanTime_le_cube enc.address enc.value q.1 + have he : (SparseMemory.readEncoding enc.address enc.value q).length ≤ + ((SparseMemory.readEncoding enc.address enc.value q).length + 1) ^ 3 := by + ring_nf; omega + unfold SparseMemory.readTime + nlinarith + · have hs := SparseMemory.scanSpace_le_linear enc.address enc.value q.1 + simp only [SparseMemory.readSpace, SparseMemory.readEncoding, + length_encPair, pow_one] at hs ⊢ + omega + +/-- The raw sparse-memory update is cubic in time and linear in space. -/ +theorem writeRaw_hasDegree (enc : M.Encoding) (hD : 3 ≤ D) : + (⟨SparseMemory.writeTime enc.address enc.value, SparseMemory.writeSpace enc.address enc.value, + SparseMemory.insert_inO enc.address enc.value⟩ : + NativeCertificate + (fun (((address, value), memory) : + (M.Address × M.Value) × SparseMemory M.Address M.Value) => + memory.insert address value) _ _).HasDegree D := by + refine hasDegree_of_space_linear _ (DegreeBounded.mono_deg ⟨33, fun q => ?_⟩ hD) + ⟨33, fun q => ?_⟩ (by omega) + · have hq : (SparseMemory.queryEncoding enc.address enc.value (q.1.1, q.2)).length ≤ + (SparseMemory.writeEncoding enc.address enc.value q).length := by + simp only [SparseMemory.queryEncoding, SparseMemory.writeEncoding, length_encPair] + omega + have hp := Nat.pow_le_pow_left (Nat.add_le_add_right hq 1) 3 + have ht := SparseMemory.scanTime_le_cube enc.address enc.value (q.1.1, q.2) + have he : (SparseMemory.writeEncoding enc.address enc.value q).length ≤ + ((SparseMemory.writeEncoding enc.address enc.value q).length + 1) ^ 3 := by + ring_nf; omega + unfold SparseMemory.writeTime + nlinarith + · have hs := SparseMemory.scanSpace_le_linear enc.address enc.value (q.1.1, q.2) + simp only [SparseMemory.writeSpace, SparseMemory.queryEncoding, + SparseMemory.writeEncoding, length_encPair, pow_one] at hs ⊢ + omega + +theorem readCertificate_hasDegree (enc : M.Encoding) + (c : NativeCertificate s ei (encoding enc)) + {a : α → M.Address} (address : NativeCertificate a ei enc.address) (hD : 3 ≤ D) + (hc : c.HasDegree D) (ha : address.HasDegree D) : + (readCertificate enc c address).HasDegree D := by + have hfs := fields_hasDegree enc c hc + exact comp_hasDegree _ _ (readRaw_hasDegree enc hD) + (prod_hasDegree _ _ (prod_hasDegree _ _ ha + (snd_hasDegree _ (snd_hasDegree _ (snd_hasDegree _ hfs)))) + (fst_hasDegree _ (snd_hasDegree _ (snd_hasDegree _ hfs)))) + +theorem writeCertificate_hasDegree (enc : M.Encoding) + (c : NativeCertificate s ei (encoding enc)) + {a : α → M.Address} (address : NativeCertificate a ei enc.address) + (value : NativeCertificate v ei enc.value) (hD : 3 ≤ D) + (hc : c.HasDegree D) (ha : address.HasDegree D) (hv : value.HasDegree D) : + (writeCertificate enc c address value).HasDegree D := by + have hfs := fields_hasDegree enc c hc + have hmem := comp_hasDegree _ _ (writeRaw_hasDegree enc hD) + (prod_hasDegree _ _ (prod_hasDegree _ _ ha hv) + (snd_hasDegree _ (snd_hasDegree _ (snd_hasDegree _ hfs)))) + exact post_hasDegree _ (pack enc).computes (prod_hasDegree _ _ (fst_hasDegree _ hfs) + (prod_hasDegree _ _ (fst_hasDegree _ (snd_hasDegree _ hfs)) + (prod_hasDegree _ _ (fst_hasDegree _ (snd_hasDegree _ (snd_hasDegree _ hfs))) hmem))) + +theorem evalOpCertificate_hasDegree (enc : M.Encoding) + (N : M.NativePrimitives enc) (config : NativeCertificate k ei enc.config) + (c : NativeCertificate s ei (encoding enc)) (o : Op M r) (hD : 3 ≤ D) + (hN : N.HasDegree D) (hk : config.HasDegree D) (hc : c.HasDegree D) : + (evalOpCertificate enc N config c o).HasDegree D := by + have h1 : 1 ≤ D := by omega + cases o with + | const d literal => + refine (option_hasDegree _ _ _ + (comp_hasDegree _ _ (hN.constant literal) hk) + (const_hasDegree none h1) + (some_hasDegree _ (setCertificate_hasDegree enc _ d _ + (pull_hasDegree c hc h1) (payload_hasDegree ei enc.value h1))) h1).of_eq _ rfl rfl + fun a => ?_ + simp only [Function.comp_apply, evalOp] + cases M.checkedConstant literal (k a) <;> rfl + | copy d src => + exact moveCertificate_hasDegree enc N config c d (register enc c src) h1 hN hk hc + (register_hasDegree enc c src hc) + | alu op d args => + refine (option_hasDegree _ _ _ + (comp_hasDegree _ _ (hN.operation op) (prod_hasDegree _ _ hk + (operands_hasDegree enc c args hc))) + (const_hasDegree none h1) + (some_hasDegree _ (setCertificate_hasDegree enc _ d _ + (pull_hasDegree c hc h1) (payload_hasDegree ei enc.value h1))) h1).of_eq _ rfl rfl + fun a => ?_ + simp only [Function.comp_apply, evalOp] + cases M.checkedOp op (k a, (s a).regs ∘ args) <;> rfl + | load d a => + have hpullC := pull_hasDegree (ed := enc.address) c hc h1 + have hpullK := pull_hasDegree (ed := enc.address) config hk h1 + have hpay := payload_hasDegree ei enc.address h1 + refine (option_hasDegree _ _ _ + (comp_hasDegree _ _ hN.address (prod_hasDegree _ _ hk (register_hasDegree enc c a hc))) + (const_hasDegree none h1) + (moveCertificate_hasDegree enc N _ _ d _ h1 hN hpullK hpullC + (readCertificate_hasDegree enc _ _ hD hpullC hpay)) h1).of_eq _ rfl rfl fun x => ?_ + simp only [Function.comp_apply, evalOp] + cases M.address (k x) ((s x).regs a) <;> rfl + | store a src => + have hpullC := pull_hasDegree (ed := enc.address) c hc h1 + have hpullK := pull_hasDegree (ed := enc.address) config hk h1 + have hpay := payload_hasDegree ei enc.address h1 + refine (option_hasDegree _ _ _ + (comp_hasDegree _ _ hN.address (prod_hasDegree _ _ hk (register_hasDegree enc c a hc))) + (const_hasDegree none h1) + (branch_hasDegree _ _ _ + (comp_hasDegree _ _ hN.validMove (prod_hasDegree _ _ hpullK + (register_hasDegree enc _ src hpullC))) + (some_hasDegree _ (writeCertificate_hasDegree enc _ _ _ hD hpullC hpay + (register_hasDegree enc _ src hpullC))) + (const_hasDegree none h1)) h1).of_eq _ rfl rfl fun x => ?_ + simp only [Function.comp_apply, evalOp] + cases M.address (k x) ((s x).regs a) <;> rfl + +theorem resumeCertificate_hasDegree (enc : M.Encoding) (N : M.NativePrimitives enc) + (config : NativeCertificate k ei enc.config) (c : NativeCertificate s ei (encoding enc)) + (pc : ℕ) (hD : 1 ≤ D) (hN : N.HasDegree D) (hk : config.HasDegree D) + (hc : c.HasDegree D) : + (resumeCertificate enc N config c pc).HasDegree D := + branch_hasDegree _ _ _ + (comp_hasDegree _ _ hN.validPC (prod_hasDegree _ _ hk (const_hasDegree pc hD))) + (some_hasDegree _ (inl_hasDegree _ _ (post_hasDegree _ (pack enc).computes (prod_hasDegree _ _ + (const_hasDegree pc hD) (snd_hasDegree _ (fields_hasDegree enc c hc)))))) + (const_hasDegree none hD) + +theorem evalInstrCertificate_hasDegree (enc : M.Encoding) + (N : M.NativePrimitives enc) (config : NativeCertificate k ei enc.config) + (c : NativeCertificate s ei (encoding enc)) (nextPC : ℕ) (i : Instr M r) (hD : 3 ≤ D) + (hN : N.HasDegree D) (hk : config.HasDegree D) (hc : c.HasDegree D) : + (evalInstrCertificate enc N config c nextPC i).HasDegree D := by + have h1 : 1 ≤ D := by omega + cases i with + | op o => + refine (option_hasDegree _ _ _ + (evalOpCertificate_hasDegree enc N config c o hD hN hk hc) + (const_hasDegree none h1) + (resumeCertificate_hasDegree enc N _ _ nextPC h1 hN + (pull_hasDegree config hk h1) (payload_hasDegree ei (encoding enc) h1)) h1).of_eq _ + rfl rfl fun a => ?_ + simp only [evalInstr] + cases evalOp (k a) o (s a) <;> rfl + | jump target => + exact resumeCertificate_hasDegree enc N config c target h1 hN hk hc + | branch test target => + cases test with + | test f args => + have hpullK := pull_hasDegree (ed := encBool) config hk h1 + have hpullC := pull_hasDegree (ed := encBool) c hc h1 + refine (option_hasDegree _ _ _ + (comp_hasDegree _ _ (hN.test f) (prod_hasDegree _ _ hk + (operands_hasDegree enc c args hc))) + (const_hasDegree none h1) + (branch_hasDegree _ _ _ + (payload_hasDegree ei encBool h1) + (resumeCertificate_hasDegree enc N _ _ target h1 hN hpullK hpullC) + (resumeCertificate_hasDegree enc N _ _ nextPC h1 hN hpullK hpullC)) h1).of_eq _ + rfl rfl fun a => ?_ + simp only [Function.comp_apply, evalInstr] + cases h : M.evalTest (k a) f ((s a).regs ∘ args) with + | none => rfl + | some b => cases b <;> rfl + | halt idx => + exact branch_hasDegree _ _ _ + (comp_hasDegree _ _ hN.validMove (prod_hasDegree _ _ hk (register_hasDegree enc c idx hc))) + (some_hasDegree _ (inr_hasDegree _ _ (register_hasDegree enc c idx hc))) + (const_hasDegree none h1) + +theorem pcEquals_hasDegree (enc : M.Encoding) (c : NativeCertificate s ei (encoding enc)) + (pc : ℕ) (hD : 2 ≤ D) (hc : c.HasDegree D) : + (pcEquals enc c pc).HasDegree D := by + have heq : (⟨fun p : ℕ × ℕ => ((encNat p.1).length + 1) * + ((encNat p.1).length + (encNat p.2).length + 1), + fun p : ℕ × ℕ => (encPair encNat encNat p).length, + ComputableInO.eq encNat⟩ : NativeCertificate (fun p : ℕ × ℕ => decide (p.1 = p.2)) + (encPair encNat encNat) encBool).HasDegree D := by + refine hasDegree_of_space_linear _ (DegreeBounded.mono_deg ⟨1, fun p => ?_⟩ hD) + ⟨1, fun p => by simp⟩ (by omega) + simp only [length_encPair] + nlinarith + exact comp_hasDegree _ _ heq (prod_hasDegree _ _ (fst_hasDegree _ (fields_hasDegree enc c hc)) + (const_hasDegree pc (by omega))) + +theorem dispatchCertificate_hasDegree (enc : M.Encoding) + (N : M.NativePrimitives enc) (config : NativeCertificate k ei enc.config) + (c : NativeCertificate s ei (encoding enc)) (p : Program M r) (pc : ℕ) (hD : 3 ≤ D) + (hN : N.HasDegree D) (hk : config.HasDegree D) (hc : c.HasDegree D) : + (dispatchCertificate enc N config c p pc).HasDegree D := by + induction p generalizing pc with + | nil => exact const_hasDegree none (by omega) + | cons i p ih => + refine congr_hasDegree _ _ ?_ + exact (branch_hasDegree _ _ _ + (pcEquals_hasDegree enc c pc (by omega) hc) + (evalInstrCertificate_hasDegree enc N config c (pc + 1) i hD hN hk hc) + (ih (pc + 1))) + +end Native + +/-- **One simulated step has degree `D`** when every primitive certificate has degree `D ≥ 3`. -/ +theorem stepCertificate_hasDegree (enc : M.Encoding) (N : M.NativePrimitives enc) + (p : Program M r) (hN : N.HasDegree D) (hD : 3 ≤ D) : + (stepCertificate enc N p).HasDegree D := by + have h1 : 1 ≤ D := by omega + have hcfg := linear_hasDegree (ComputableInO.fst enc.config (encoding (M := M) (r := r) enc)) h1 + have hst := linear_hasDegree (ComputableInO.snd enc.config (encoding (M := M) (r := r) enc)) h1 + exact branch_hasDegree _ _ _ + (comp_hasDegree _ _ hN.validPC + (prod_hasDegree _ _ hcfg (fst_hasDegree _ (Native.fields_hasDegree enc _ hst)))) + (Native.dispatchCertificate_hasDegree enc N _ _ p 0 hD hN hcfg hst) + (const_hasDegree none h1) + +/-- **One clocked round of the simulator has degree `D`** when every primitive certificate has +degree `D ≥ 3`. -/ +theorem roundCertificate_hasDegree (enc : M.Encoding) (N : M.NativePrimitives enc) + (p : Program M r) (hN : N.HasDegree D) (hD : 3 ≤ D) : + (roundCertificate enc N p).HasDegree D := by + have h1 : 1 ≤ D := by omega + have hcont : (continuingCertificate (M := M) (r := r) enc).HasDegree D := + hasDegree_of_space_linear _ (DegreeBounded.self.mono_deg h1) DegreeBounded.self h1 + have hres : (stepResultCertificate enc N p).HasDegree D := + (option_hasDegree _ _ _ + (comp_hasDegree _ _ hcont (snd_hasDegree _ (linear_hasDegree + (ComputableInO.of_eq (f := fun x : M.Config × Result M r => x) fun _ => rfl) h1))) + (snd_hasDegree _ (linear_hasDegree _ h1)) + (comp_hasDegree _ _ (stepCertificate_hasDegree enc N p hN hD) + (prod_hasDegree _ _ (fst_hasDegree _ (fst_hasDegree _ (linear_hasDegree + (ComputableInO.of_eq (f := fun x : (M.Config × Result M r) × FiniteState M r => x) + fun _ => rfl) h1))) + (snd_hasDegree _ (linear_hasDegree _ h1)))) h1).of_eq _ rfl rfl + fun q => (stepResult_eq q.1 p q.2).symm + exact prod_hasDegree _ _ (fst_hasDegree _ (linear_hasDegree _ h1)) hres + +/-! ## Clocked runs with explicit bounds -/ + +/-- The loop state after `j` rounds from a continuing state has the encoded length of the runtime +configuration and the `j`-step outcome. -/ +theorem length_loopEncoding_iterate_round (enc : M.Encoding) (p : Program M r) (j : ℕ) + (k : M.Config) (s : FiniteState M r) : + (loopEncoding enc ((round p)^[j] (k, Option.some (.inl s)))).length = + 2 * (enc.config k).length + 1 + (resultEncoding enc (run k p j s)).length := by + rw [iterate_round_inl, length_encPair] + +/-- **Explicit clocked run time.** If every round costs at most `C (ℓ + 1) ^ D` on a loop state of +encoded length `ℓ`, and the outcomes of the first `n` steps have encoded length at most `B`, then +`n` clocked steps cost at most `3 |input| + (n + 1) (C X ^ D + X + (|n| + 1) ^ 2)`, where +`X = 2 |k| + B + 2` bounds the encoded loop states plus one. -/ +theorem runTime_le (enc : M.Encoding) (N : M.NativePrimitives enc) (p : Program M r) {C : ℕ} + (hround : ∀ x, (roundCertificate enc N p).time x ≤ C * ((loopEncoding enc x).length + 1) ^ D) + (n : ℕ) (k : M.Config) (s : FiniteState M r) {B : ℕ} + (hB : ∀ j ≤ n, (resultEncoding enc (run k p j s)).length ≤ B) : + runTime enc N p (n, (k, s)) ≤ 3 * (runInputEncoding enc (n, (k, s))).length + + (n + 1) * (C * (2 * (enc.config k).length + B + 2) ^ D + + (2 * (enc.config k).length + B + 2) + ((encNat n).length + 1) ^ 2) := by + have hT := maxRoundTime_le (roundCertificate enc N p) n (k, Option.some (.inl s)) + (R := C * (2 * (enc.config k).length + B + 2) ^ D) fun j hj => by + refine (hround _).trans (Nat.mul_le_mul_left C (Nat.pow_le_pow_left ?_ D)) + have := hB j hj + rw [length_loopEncoding_iterate_round] + omega + have hL := maxStateLength_le (encPair enc.config (resultEncoding enc)) (round p) n + (k, Option.some (.inl s)) + (B := 2 * (enc.config k).length + B + 2) fun j hj => by + have := hB j hj + rw [length_loopEncoding_iterate_round] + omega + have hsum := Nat.mul_le_mul_left (n + 1) (Nat.add_le_add_right (Nat.add_le_add hT hL) + (((encNat n).length + 1) ^ 2)) + simp only [runTime, runCertificate, congr_time, snd_time, iterate_time, initLoopCertificate, + prod_time, fst_time, snd_time, some_time, inl_time, linear_time] + omega + +/-- **Explicit clocked run space.** The space analogue of `runTime_le`. -/ +theorem runSpace_le (enc : M.Encoding) (N : M.NativePrimitives enc) (p : Program M r) {C : ℕ} + (hround : ∀ x, (roundCertificate enc N p).space x ≤ C * ((loopEncoding enc x).length + 1) ^ D) + (n : ℕ) (k : M.Config) (s : FiniteState M r) {B : ℕ} + (hB : ∀ j ≤ n, (resultEncoding enc (run k p j s)).length ≤ B) : + runSpace enc N p (n, (k, s)) ≤ 3 * (runInputEncoding enc (n, (k, s))).length + + C * (2 * (enc.config k).length + B + 2) ^ D + (2 * (enc.config k).length + B + 2) + + (encNat n).length := by + have hS := maxRoundSpace_le (roundCertificate enc N p) n (k, Option.some (.inl s)) + (Q := C * (2 * (enc.config k).length + B + 2) ^ D) fun j hj => by + refine (hround _).trans (Nat.mul_le_mul_left C (Nat.pow_le_pow_left ?_ D)) + have := hB j hj + rw [length_loopEncoding_iterate_round] + omega + have hL := maxStateLength_le (encPair enc.config (resultEncoding enc)) (round p) n + (k, Option.some (.inl s)) + (B := 2 * (enc.config k).length + B + 2) fun j hj => by + have := hB j hj + rw [length_loopEncoding_iterate_round] + omega + simp only [runSpace, runCertificate, congr_space, snd_space, iterate_space, initLoopCertificate, + prod_space, fst_space, snd_space, some_space, inl_space, linear_space] + omega + +/-- **Whole-run transport with an explicit degree.** Suppose every primitive certificate of the +model has degree `D ≥ 3`, a halting run of `p` from `initState a` takes at most `steps a` +instructions, and the outcomes of its first `steps a` clocked steps have encoded length at most +`B a`. Then `f` is computable in time +`O(Tsteps + Tcfg + Tinit + |input| + (steps + 1) · (X ^ D + (|steps| + 1) ^ 2))`, where +`X = 2 |cfg| + B + 2` and `|input|` is the encoded length of `(steps, cfg, initState)`. Only the +constant factor is hidden. -/ +theorem transport_inO_of_hasDegree (enc : M.Encoding) (N : M.NativePrimitives enc) + (hN : N.HasDegree D) (hD : 3 ≤ D) (p : Program M r) + {f : α → M.Value} {cfg : α → M.Config} {initState : α → FiniteState M r} + {steps : α → ℕ} + (cSteps : NativeCertificate steps ei encNat) + (cCfg : NativeCertificate cfg ei enc.config) + (cInit : NativeCertificate initState ei (encoding enc)) + (hExec : ∀ a, ∃ m ≤ steps a, + GenericRAM.Executes (cfg a) p (initState a).denote (f a) m) + {B : α → ℕ} + (hOutLen : ∀ a, ∀ j ≤ steps a, + (resultEncoding enc (run (cfg a) p j (initState a))).length ≤ B a) : + ComputableInO f ei enc.value + (fun a => cSteps.time a + cCfg.time a + cInit.time a + + (runInputEncoding enc (steps a, (cfg a, initState a))).length + + (steps a + 1) * ((2 * (enc.config (cfg a)).length + B a + 2) ^ D + + ((encNat (steps a)).length + 1) ^ 2)) + (fun a => cSteps.space a + cCfg.space a + cInit.space a + + (runInputEncoding enc (steps a, (cfg a, initState a))).length + + (2 * (enc.config (cfg a)).length + B a + 2) ^ D + (encNat (steps a)).length) := by + have hround := roundCertificate_hasDegree enc N p hN hD + obtain ⟨Ct, hCt⟩ := hround.time + obtain ⟨Cs, hCs⟩ := hround.space + refine (transport_inO enc N p cSteps cCfg cInit hExec).mono (Ct + Cs + 4) (fun a => ?_) + (fun a => ?_) + · have hrun := runTime_le enc N p hCt (steps a) (cfg a) (initState a) (hOutLen a) + set X := 2 * (enc.config (cfg a)).length + B a + 2 + have hX : X ≤ X ^ D := Nat.le_self_pow (by omega) X + have hmul := Nat.mul_le_mul_left (steps a + 1) + (show Ct * X ^ D + X + ((encNat (steps a)).length + 1) ^ 2 ≤ + (Ct + 1) * (X ^ D + ((encNat (steps a)).length + 1) ^ 2) by nlinarith) + have hc : (steps a + 1) * ((Ct + 1) * (X ^ D + ((encNat (steps a)).length + 1) ^ 2)) ≤ + (Ct + Cs + 4) * ((steps a + 1) * (X ^ D + ((encNat (steps a)).length + 1) ^ 2)) := by + rw [Nat.mul_left_comm] + exact Nat.mul_le_mul_right _ (by omega) + nlinarith + · have hrun := runSpace_le enc N p hCs (steps a) (cfg a) (initState a) (hOutLen a) + set X := 2 * (enc.config (cfg a)).length + B a + 2 + have hX : X ≤ X ^ D := Nat.le_self_pow (by omega) X + nlinarith + +end Cslib.GenericRAM.FiniteState diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Encodings/NatLength.lean b/Complexity/Computability/Machines/Turing/MultiTape/Encodings/NatLength.lean index ef5dccbf5e..e238f7c935 100644 --- a/Complexity/Computability/Machines/Turing/MultiTape/Encodings/NatLength.lean +++ b/Complexity/Computability/Machines/Turing/MultiTape/Encodings/NatLength.lean @@ -15,12 +15,15 @@ import Mathlib.Tactic.NormNum /-! # Binary lengths of natural numbers -The binary encoding `encNat` of a number has `Nat.size` digits. Numbers at most `N` have at most -`log₂ N + 1` digits, and numbers at most `(N + 1) ^ d` at most `(d + 1) * (log₂ N + 1)`: counters -bounded by a polynomial in the length of the input are stored in logarithmic space. +The binary encoding `encNat` of a number has `Nat.size` digits, so numbers below `2 ^ p` have at +most `p` digits. Numbers at most `N` have at most `log₂ N + 1` digits, and numbers at most +`(N + 1) ^ d` at most `(d + 1) * (log₂ N + 1)`: counters bounded by a polynomial in the length of +the input are stored in logarithmic space. ## Main results +* `Turing.MultiTapeTM.length_encNat_le_of_lt_two_pow`: numbers below `2 ^ p` have at most `p` + digits. * `Turing.MultiTapeTM.length_encNat_le_log2`: numbers at most `N` have at most `log₂ N + 1` digits. * `Turing.MultiTapeTM.length_encNat_le_log2_add_two`: numbers at most `N + 1` have at most @@ -40,6 +43,11 @@ bounded by a polynomial in the length of the input are stored in logarithmic spa namespace Turing.MultiTapeTM +/-- A number below `2 ^ p` has at most `p` binary digits. -/ +theorem length_encNat_le_of_lt_two_pow {a p : ℕ} (h : a < 2 ^ p) : (encNat a).length ≤ p := by + rw [encNat_apply, Nat.size_eq_bits_len] + exact Nat.size_le.mpr h + /-- The binary length of a number at most `N` is at most `log₂ N + 1`. -/ theorem length_encNat_le_log2 {j N : ℕ} (h : j ≤ N) : (encNat j).length ≤ Nat.log2 N + 1 := by rw [encNat_apply, Nat.size_eq_bits_len, Nat.size_le] diff --git a/Complexity/Computability/WordRAM/NativeDegree.lean b/Complexity/Computability/WordRAM/NativeDegree.lean new file mode 100644 index 0000000000..fd459dc23d --- /dev/null +++ b/Complexity/Computability/WordRAM/NativeDegree.lean @@ -0,0 +1,207 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Native +public import Complexity.Computability.GenericRAM.Native.Degree +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Quantitative +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.LinearSpaceArith +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Pi + +/-! +# Word-RAM primitives with explicit degrees + +`WordRAM.Native` certifies each primitive of a word-RAM basis in polynomial time of unnamed degree. +Here the primitives get explicit degrees: on a unary word size and binary operands, the literal +check, the `eq`/`lt` tests, address extraction and the move and label checks all run in quadratic +time and space with output of linear length, and so do checked addition and truncated subtraction. +The fits test `v < 2 ^ w` compares the bit length of `v` with `w` in unary, without forming `2 ^ w`. + +`Basis.nativePrimitivesOfDegree` assembles a primitive bundle of degree `d ≥ 2` from degree-`d` +certificates for the operations of a basis; `Basis.additive.quadraticPrimitives` is the instance +with `d = 2`. The other bases need degree certificates for multiplication, division, +remainder, the bitwise operations and the shifts, which the machine library does not yet supply +except for multiplication. + +## Main definitions + +* `Cslib.WordRAM.Basis.nativePrimitivesOfDegree`: the primitive bundle of a basis from degree-`d` + certificates for its operations. +* `Cslib.WordRAM.Basis.additive.quadraticPrimitives`: the quadratic bundle of the additive basis. + +## Main results + +* `Cslib.WordRAM.fits_degree`: the fits test is quadratic. +* `Cslib.WordRAM.checkedAdd_degree`, `Cslib.WordRAM.checkedSub_degree`: checked addition and + subtraction are quadratic. +* `Cslib.WordRAM.Basis.nativePrimitivesOfDegree_hasDegree`, + `Cslib.WordRAM.Basis.additive.quadraticPrimitives_hasDegree`: the bundles have the stated + degree. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +open Cslib.GenericRAM Turing.MultiTapeTM ComputableInDegree + +/-- A linear-time, linear-space computation has cost and output degree one. -/ +private theorem degree_of_linear {α β : Type} {ei : α ↪ List Bool} {eo : β ↪ List Bool} + {f : α → β} (h : ComputableInO f ei eo (fun a => (ei a).length) (fun a => (ei a).length)) : + ComputableInDegree f ei eo 1 1 := + h.computableInDegree ⟨1, fun a => by simp⟩ ⟨1, fun a => by simp⟩ le_rfl + +/-- A computation in quadratic time and linear space has cost degree two and output degree one. -/ +private theorem degree_of_quadratic {α β : Type} {ei : α ↪ List Bool} {eo : β ↪ List Bool} + {f : α → β} + (h : ComputableInO f ei eo (fun a => ((ei a).length + 1) ^ 2) (fun a => (ei a).length + 1)) : + ComputableInDegree f ei eo 2 1 := + h.computableInDegree ⟨1, fun a => by simp⟩ ⟨1, fun a => by simp⟩ (by omega) + +/-- **The fits test `v < 2 ^ w` is quadratic** on a unary width and a binary value. It compares the +bit length of `v` with `w`. -/ +theorem fits_degree : + ComputableInDegree (fun p : ℕ × ℕ => decide (Fits p.1 p.2)) (encPair encUnary encNat) + encBool 2 1 := by + have hsize : ComputableInDegree (fun p : ℕ × ℕ => (encNat p.2).length) + (encPair encUnary encNat) encUnary 1 1 := + ((degree_of_linear (ComputableInO.lengthUnary.comp_enc encNat)).comp + (snd encUnary encNat)).mono + have hsub : ComputableInDegree (fun p : ℕ × ℕ => (encNat p.2).length - p.1) + (encPair encUnary encNat) encUnary 2 1 := + (unary_sub.comp (hsize.prod (fst encUnary encNat))).mono + have hzero : ComputableInDegree (fun p : ℕ × ℕ => decide ((encNat p.2).length - p.1 = 0)) + (encPair encUnary encNat) encBool 2 1 := + ((eq encUnary).comp (hsub.prod (const 0))).mono + convert hzero using 1 + funext p + refine decide_eq_decide.mpr ?_ + rw [fits_iff, encNat_apply, Nat.size_eq_bits_len, ← Nat.size_le] + omega + +/-- Checking that a quadratic word value fits the unary width is quadratic. -/ +private theorem checkFits_degree {k : ℕ} {val : ℕ × (Fin k → ℕ) → ℕ} + (hval : ComputableInDegree val (encPair encUnary (encPi encNat)) encNat 2 1) : + ComputableInDegree + (fun p : ℕ × (Fin k → ℕ) => if decide (Fits p.1 (val p)) then some (val p) else none) + (encPair encUnary (encPi encNat)) (encOption encNat) 2 1 := by + have htest : ComputableInDegree (fun p : ℕ × (Fin k → ℕ) => decide (Fits p.1 (val p))) + (encPair encUnary (encPi encNat)) encBool 2 1 := + (fits_degree.comp ((fst encUnary (encPi encNat (Index := Fin k))).prod hval)).mono + have hsome : ComputableInDegree (fun p : ℕ × (Fin k → ℕ) => some (val p)) + (encPair encUnary (encPi encNat)) (encOption encNat) 2 1 := + ((optionSome (enc₁ := encNat) (enc := encOption encNat)).comp hval).mono + exact ite_le htest hsome (const none) (by omega) (by omega) + +/-- An operand register of a binary operation, read in linear time. -/ +private theorem operand_degree (i : Fin 2) : + ComputableInDegree (fun p : ℕ × (Fin 2 → ℕ) => p.2 i) (encPair encUnary (encPi encNat)) + encNat 1 1 := + ((degree_of_linear (ComputableInO.pi_apply encNat i)).comp + (snd encUnary (encPi encNat))).mono + +/-- **Checked addition is quadratic** on a unary width and binary operands. -/ +theorem checkedAdd_degree : + ComputableInDegree (Basis.standard.genericModel.checkedOp (.bin .add)) + (encPair encUnary (encPi encNat)) (encOption encNat) 2 1 := + checkFits_degree (k := 2) (val := fun p : ℕ × (Fin 2 → ℕ) => p.2 0 + p.2 1) + (computableInDegree_add.comp ((operand_degree 0).prod (operand_degree 1))).mono + +/-- **Checked truncated subtraction is quadratic** on a unary width and binary operands. -/ +theorem checkedSub_degree : + ComputableInDegree (Basis.standard.genericModel.checkedOp (.bin .sub)) + (encPair encUnary (encPi encNat)) (encOption encNat) 2 1 := + checkFits_degree (k := 2) (val := fun p : ℕ × (Fin 2 → ℕ) => p.2 0 - p.2 1) + ((degree_of_quadratic ComputableInO.natSub).comp + ((operand_degree 1).prod (operand_degree 0))).mono + +/-- A literal is checked against the unary width in quadratic time. -/ +theorem checkedConstant_degree (B : Basis) (c : ℕ) : + ComputableInDegree (B.genericModel.checkedConstant c) encUnary (encOption encNat) 2 1 := by + have htest : ComputableInDegree (fun w : ℕ => decide (Fits w c)) encUnary encBool 2 1 := + (fits_degree.comp ((of_eq (f := fun w : ℕ => w) (encIn := encUnary) (encMid := encUnary) + fun _ => rfl).prod (const c))).mono + exact ite_le htest (const (some c)) (const none) (by omega) (by omega) + +/-- The word-RAM tests (`eq`, `lt` and the faulting test) are quadratic. -/ +theorem evalTest_degree (B : Basis) (t : Option Bool) : + ComputableInDegree (fun p => B.genericModel.evalTest p.1 t p.2) + (encPair encUnary (encPi encNat)) (encOption encBool) 2 1 := by + cases t with + | none => + change ComputableInDegree (fun _ : ℕ × (Fin 0 → ℕ) => (none : Option Bool)) + (encPair encUnary (encPi encNat (Index := Fin 0))) (encOption encBool) 2 1 + exact (const none).mono + | some b => + have hxy := (operand_degree 0).prod (operand_degree 1) + cases b + · change ComputableInDegree + (fun p : ℕ × (Fin 2 → ℕ) => some (decide (p.2 0 = p.2 1))) + (encPair encUnary (encPi encNat (Index := Fin 2))) (encOption encBool) 2 1 + exact ((optionSome (enc₁ := encBool) (enc := encOption encBool)).comp + ((of_linear ComputableInLinearTimeAndSpace.natEq).comp hxy)).mono + · change ComputableInDegree + (fun p : ℕ × (Fin 2 → ℕ) => some (decide (p.2 0 < p.2 1))) + (encPair encUnary (encPi encNat (Index := Fin 2))) (encOption encBool) 2 1 + exact ((optionSome (enc₁ := encBool) (enc := encOption encBool)).comp + ((degree_of_quadratic ComputableInO.natLt).comp hxy)).mono + +/-- Word-RAM address extraction is linear. -/ +theorem address_degree (B : Basis) : + ComputableInDegree (fun p => B.genericModel.address p.1 p.2) + (encPair encUnary encNat) (encOption encNat) 1 1 := + ((optionSome (enc₁ := encNat) (enc := encOption encNat)).comp (snd encUnary encNat)).mono + +/-- The primitive bundle of a basis from degree-`d` certificates for its checked operations, +`d ≥ 2`; the literal, test, address, move and label primitives are quadratic. -/ +noncomputable def Basis.nativePrimitivesOfDegree (B : Basis) {d : ℕ} (hd : 2 ≤ d) + (hop : ∀ op : B.Op, ComputableInDegree (B.genericModel.checkedOp op) + (encPair encUnary (encPi encNat)) (encOption encNat) d 1) : + B.genericModel.NativePrimitives B.encoding where + constant c := .ofDegree ((checkedConstant_degree B c).mono hd le_rfl) + operation op := .ofDegree (hop op) + test t := .ofDegree ((evalTest_degree B t).mono hd le_rfl) + address := .ofDegree ((address_degree B).mono (d' := d) (by omega) le_rfl) + validMove := .ofDegree ((const true).mono (d' := d) (e' := 1) (by omega) zero_le_one) + validPC := .ofDegree ((const true).mono (d' := d) (e' := 1) (by omega) zero_le_one) + +/-- `Basis.nativePrimitivesOfDegree` has degree `d`. -/ +theorem Basis.nativePrimitivesOfDegree_hasDegree (B : Basis) {d : ℕ} (hd : 2 ≤ d) + (hop : ∀ op : B.Op, ComputableInDegree (B.genericModel.checkedOp op) + (encPair encUnary (encPi encNat)) (encOption encNat) d 1) : + (B.nativePrimitivesOfDegree hd hop).HasDegree d := + ⟨fun c => NativeCertificate.ofDegree_hasDegree ((checkedConstant_degree B c).mono hd le_rfl), + fun op => NativeCertificate.ofDegree_hasDegree (hop op), + fun t => NativeCertificate.ofDegree_hasDegree ((evalTest_degree B t).mono hd le_rfl), + NativeCertificate.ofDegree_hasDegree ((address_degree B).mono (d' := d) (by omega) le_rfl), + NativeCertificate.ofDegree_hasDegree + ((const true).mono (d' := d) (e' := 1) (by omega) zero_le_one), + NativeCertificate.ofDegree_hasDegree + ((const true).mono (d' := d) (e' := 1) (by omega) zero_le_one)⟩ + +/-- Every checked operation of the additive basis is quadratic. -/ +theorem Basis.additive.checkedOp_degree (op : Basis.additive.Op) : + ComputableInDegree (Basis.additive.genericModel.checkedOp op) + (encPair encUnary (encPi encNat)) (encOption encNat) 2 1 := by + obtain ⟨f, hf⟩ := op + simp only [List.mem_cons, List.not_mem_nil, or_false] at hf + rcases hf with rfl | rfl + · exact checkedAdd_degree + · exact checkedSub_degree + +/-- The quadratic primitive bundle of the additive basis. -/ +noncomputable def Basis.additive.quadraticPrimitives : + Basis.additive.genericModel.NativePrimitives Basis.additive.encoding := + Basis.nativePrimitivesOfDegree Basis.additive le_rfl Basis.additive.checkedOp_degree + +/-- **Every primitive of the additive basis is quadratic.** -/ +theorem Basis.additive.quadraticPrimitives_hasDegree : + Basis.additive.quadraticPrimitives.HasDegree 2 := + Basis.nativePrimitivesOfDegree_hasDegree _ le_rfl Basis.additive.checkedOp_degree + +end Cslib.WordRAM diff --git a/Complexity/Computability/WordRAM/Turing.lean b/Complexity/Computability/WordRAM/Turing.lean index 2ee77b033e..6d6915285b 100644 --- a/Complexity/Computability/WordRAM/Turing.lean +++ b/Complexity/Computability/WordRAM/Turing.lean @@ -12,6 +12,7 @@ public import Complexity.Computability.WordRAM.Function public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Lists import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.NatLength import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat /-! @@ -312,10 +313,6 @@ private theorem listMax_le {α : Type*} {f : α → ℕ} {l : List α} {m : ℕ} simp only [listMax, List.map_cons, List.foldr_cons] at ih ⊢ exact max_le (h a (List.mem_cons_self ..)) (ih fun b hb => h b (List.mem_cons_of_mem _ hb)) -private theorem length_encNat_le_of_lt {v : ℕ} (hv : v < 2 ^ w) : (encNat v).length ≤ w := by - rw [encNat_apply, Nat.size_eq_bits_len] - exact Nat.size_le.mpr hv - /-- Encoded outcomes along a clocked run from a word-bounded state are bounded by `stateBound`, with at most one new stored cell per step. -/ theorem length_resultEncoding_run_le {p : GenericRAM.Program B.genericModel r} @@ -328,7 +325,7 @@ theorem length_resultEncoding_run_le {p : GenericRAM.Program B.genericModel r} | some u => cases u with | inr v => - have hv := length_encNat_le_of_lt (hinr v hrun) + have hv := length_encNat_le_of_lt_two_pow (hinr v hrun) simp only [resultEncoding, encOption_some, encSum_inr, List.length_cons, Basis.encoding, stateBound] omega @@ -336,16 +333,16 @@ theorem length_resultEncoding_run_le {p : GenericRAM.Program B.genericModel r} obtain ⟨ht, htpc⟩ := hinl t hrun have hE := length_entries_run_le hrun have hlen := FiniteState.length_encoding_le B.encoding t - (bound := w) fun i => length_encNat_le_of_lt (ht.regs_lt i) + (bound := w) fun i => length_encNat_le_of_lt_two_pow (ht.regs_lt i) have hpcl : (encNat t.pc).length ≤ (encNat (labelBound p)).length := by simp only [encNat_apply, Nat.size_eq_bits_len] exact Nat.size_le_size htpc have hdef : (B.encoding.value t.default).length ≤ w := - length_encNat_le_of_lt ht.default_lt + length_encNat_le_of_lt_two_pow ht.default_lt have hkey : SparseMemory.maxKeyLength B.encoding.address t.memory ≤ w := - listMax_le fun e he => length_encNat_le_of_lt (ht.entries_lt e he).1 + listMax_le fun e he => length_encNat_le_of_lt_two_pow (ht.entries_lt e he).1 have hval : SparseMemory.maxValueLength B.encoding.value t.memory ≤ w := - listMax_le fun e he => length_encNat_le_of_lt (ht.entries_lt e he).2 + listMax_le fun e he => length_encNat_le_of_lt_two_pow (ht.entries_lt e he).2 have hmul : (SparseMemory.entries t.memory).length * (4 * SparseMemory.maxKeyLength B.encoding.address t.memory + 2 * SparseMemory.maxValueLength B.encoding.value t.memory + 3) ≤ diff --git a/Complexity/Computability/WordRAM/Turing/Overhead.lean b/Complexity/Computability/WordRAM/Turing/Overhead.lean new file mode 100644 index 0000000000..11aa3e6c0a --- /dev/null +++ b/Complexity/Computability/WordRAM/Turing/Overhead.lean @@ -0,0 +1,446 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Turing +public import Complexity.Computability.WordRAM.NativeDegree +public import Complexity.Computability.GenericRAM.Simulation.Degree +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Lists +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.NatLength + +/-! +# Explicit overhead of the word-RAM simulation by Turing machines + +`Program.ComputesInTime.turingComputableInPolyTime_of_width` simulates a word-RAM program by a +multi-tape Turing machine in polynomial time of unnamed degree. Here the degree is explicit. If +every primitive of the basis has degree `d` (`Model.NativePrimitives.HasDegree`), a run of at most +`T` instructions on `n` input words at word size `w` costs +`O((T + 1) · ((n + T + 1)(w + 1)) ^ max 3 d)` machine steps, on top of computing the word size, +the input words and the step budget. Only the constant factor is hidden. For `Basis.additive`, +whose primitives are quadratic (`Basis.additive.quadraticPrimitives`), the exponent is `3`. The +cube comes from scanning the association-list memory of the simulator +(`SparseMemory.scanTime_le_cube`), not from the word-RAM model. + +The simulation is the same as in `Complexity.Computability.WordRAM.Turing`. The new ingredients are +the degree-tracking certificates of `Complexity.Computability.GenericRAM.Simulation.Degree`, a +cubic initialization certificate (`initMemory_degree`, a counted fold), and quadratic certificates +for the conventions of `Complexity.Computability.WordRAM.Invariance` with logarithmic word size: +the bit list of the input as words (`bitsToNats_degree`) and the word size `k (size n + 1)` +(`logWidthBound_degree`). + +## Main results + +* `Cslib.WordRAM.initMemory_degree`: building the initial memory is cubic. +* `Cslib.WordRAM.Program.ComputesInTime.computableInO_of_width`: the simulation at a supplied + width schedule, with time `O(|x| + Tw + Te + Ts + (T + 1) · ((n + T + 1)(w + 1)) ^ max 3 d)`. +* `Cslib.WordRAM.Program.ComputesInTime.computableInO_of_width_additive`: the case of + `Basis.additive`, with exponent `3`. +* `Cslib.WordRAM.bitsToNats_degree`, `Cslib.WordRAM.logWidthBound_degree`: quadratic input + conversion and word size for the bit-list convention. + +## References + +* [S. A. Cook and R. A. Reckhow, *Time bounded random access machines*][CookReckhow1973] +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +open Turing.MultiTapeTM Cslib.GenericRAM ComputableInDegree + +/-! ## Encoded lengths -/ + +section Lengths + +/-- A word list that fits `w`-bit words has encoded length at most `n (2 w + 1)`. -/ +theorem length_encList_le_of_inputFits {w : ℕ} {l : List ℕ} (h : InputFits w l) : + (encList encNat l).length ≤ l.length * (2 * w + 1) := by + rw [length_encList] + have hle : ∀ x ∈ l, 2 * (encNat x).length + 1 ≤ 2 * w + 1 := fun x hx => by + have := length_encNat_le_of_lt_two_pow (h.2 x hx) + omega + calc (l.map fun x => 2 * (encNat x).length + 1).sum + ≤ (l.map fun _ => 2 * w + 1).sum := List.sum_le_sum hle + _ = l.length * (2 * w + 1) := by simp + +/-- `stateBound` is linear in the number of stored cells and in the word size. -/ +theorem stateBound_le (r L w E : ℕ) : + stateBound r L w E ≤ (2 * (encNat L).length + 4 * r + 14) * ((E + 1) * (w + 1)) := by + unfold stateBound + have h1 : 1 ≤ (E + 1) * (w + 1) := Nat.one_le_iff_ne_zero.mpr (by positivity) + have h2 : w + 1 ≤ (E + 1) * (w + 1) := Nat.le_mul_of_pos_left _ (by omega) + have h3 : E + 1 ≤ (E + 1) * (w + 1) := Nat.le_mul_of_pos_right _ (by omega) + nlinarith + +end Lengths + +/-! ## Initialization with an explicit degree -/ + +section Init + +/-- One step of the counted fold that builds the stored cells of `State.init`. -/ +def initStep (q : ℕ × List (ℕ × ℕ)) (x : ℕ) : ℕ × List (ℕ × ℕ) := + (q.1 + 1, q.2 ++ [(q.1 + 1, x)]) + +theorem foldl_initStep (l : List ℕ) (k : ℕ) (acc : List (ℕ × ℕ)) : + l.foldl initStep (k, acc) = + (k + l.length, acc ++ (l.zipIdx k).map fun e => (e.2 + 1, e.1)) := by + induction l generalizing k acc with + | nil => simp + | cons x l ih => + rw [List.foldl_cons, initStep, ih, List.zipIdx_cons] + simp only [List.length_cons, List.map_cons, List.append_assoc, List.singleton_append, + Prod.mk.injEq, and_true] + omega + +/-- The stored cells of `State.init` as the result of the counted fold. -/ +theorem initEntries_eq_foldl (input : List ℕ) : + initEntries input = (0, (input.foldl initStep (0, [])).1) :: + (input.foldl initStep (0, [])).2 := by + simp [initEntries, foldl_initStep] + +private theorem length_encList_zipIdx_le (l : List ℕ) (k : ℕ) : + (encList (encPair encNat encNat) ((l.zipIdx k).map fun e => (e.2 + 1, e.1))).length ≤ + 2 * (encList encNat l).length + l.length * (4 * (k + l.length) + 3) := by + induction l generalizing k with + | nil => simp + | cons x l ih => + have h1 := length_encNat_le_self (k + 1) + have h2 := ih (k + 1) + simp only [List.zipIdx_cons, List.map_cons, length_encList, List.sum_cons, + List.length_cons, length_encPair] at h2 ⊢ + nlinarith + +/-- The counted fold is cubic, with quadratic output. -/ +theorem initFold_degree : + ComputableInDegree (fun l : List ℕ => l.foldl initStep (0, [])) (encList encNat) + (encPair encNat (encList (encPair encNat encNat))) 3 2 := by + have hk : ComputableInDegree (fun p : (ℕ × List (ℕ × ℕ)) × ℕ => p.1.1 + 1) + (encPair (encPair encNat (encList (encPair encNat encNat))) encNat) encNat 1 1 := + (natSucc.comp ((fst _ _).comp (fst _ _))).mono + have hacc : ComputableInDegree (fun p : (ℕ × List (ℕ × ℕ)) × ℕ => p.1.2) + (encPair (encPair encNat (encList (encPair encNat encNat))) encNat) + (encList (encPair encNat encNat)) 1 1 := + ((snd _ _).comp (fst _ _)).mono + have hnew : ComputableInDegree (fun p : (ℕ × List (ℕ × ℕ)) × ℕ => p.1.2 ++ [(p.1.1 + 1, p.2)]) + (encPair (encPair encNat (encList (encPair encNat encNat))) encNat) + (encList (encPair encNat encNat)) 1 1 := + ((snoc (encPair encNat encNat)).comp (hacc.prod (hk.prod (snd _ _)))).mono + have hstep : ComputableInDegree (fun p : (ℕ × List (ℕ × ℕ)) × ℕ => initStep p.1 p.2) + (encPair (encPair encNat (encList (encPair encNat encNat))) encNat) + (encPair encNat (encList (encPair encNat encNat))) 1 1 := + (hk.prod hnew).mono + have h := foldl' (encA := encNat) (encL := encList encNat) + (encB := encPair encNat (encList (encPair encNat encNat))) + (list := fun l => l) (init := fun _ => ((0 : ℕ), ([] : List (ℕ × ℕ)))) (step := initStep) + (of_eq fun _ => rfl) (const _) hstep + (B := fun l => 8 * ((encList encNat l).length + 1) ^ 2) ⟨8, fun _ => le_rfl⟩ + (fun l i hi => by + rw [foldl_initStep] + have hlen := length_le_length_encList encNat l + have htake := length_encList_take_le encNat l i + have hti : (l.take i).length ≤ l.length := List.length_take_le' i l + have hz := length_encList_zipIdx_le (l.take i) 0 + have hn := length_encNat_le_self (0 + (l.take i).length) + simp only [List.nil_append, length_encPair] at hz ⊢ + nlinarith) + exact h.mono + +/-- **Building the initial memory is cubic** in the encoded input words, with quadratic output. -/ +theorem initMemory_degree : + ComputableInDegree initMemory (encList encNat) (SparseMemory.encoding encNat encNat) 3 2 := by + have h : ComputableInDegree + (fun l : List ℕ => (0, (l.foldl initStep (0, [])).1) :: (l.foldl initStep (0, [])).2) + (encList encNat) (encList (encPair encNat encNat)) 3 2 := + ((cons (encPair encNat encNat)).comp ((((const 0).prod ((fst _ _).comp initFold_degree))).prod + ((snd _ _).comp initFold_degree))).mono + refine h.of_enc_eq fun l => ?_ + simp only [SparseMemory.encoding_apply, entries_initMemory, initEntries_eq_foldl] + +/-- The certificate of `initMemory`, cubic in the encoded input words. -/ +def initMemoryCertificate : + NativeCertificate initMemory (encList encNat) (SparseMemory.encoding encNat encNat) := + ⟨fun l => ((encList encNat l).length + 1) ^ 3, fun l => ((encList encNat l).length + 1) ^ 3, + initMemory_degree.inO⟩ + +end Init + +/-! ## The simulation with an explicit degree -/ + +section Main + +variable {B : Basis} {r : ℕ} {α : Type} {encIn : α ↪ List Bool} + +/-- **Word-RAM time gives Turing-machine time with an explicit polynomial overhead.** + +Let every primitive of the basis have degree `d` (`Model.NativePrimitives.HasDegree`), and let `p` +compute `f` (`Program.ComputesInTime`). Suppose a machine computes the word size `width a` in unary +in time `O(Tw a)`, the input words `encode (width a) a` in time `O(Te a)` and a step budget +`steps a ≥ time a (width a)` in binary in time `O(Ts a)`; the word size must be admitted and the +input words must fit it. Then `f` is computable with binary output in time +`O(|encIn a| + Tw a + Te a + Ts a + (steps a + 1) · R ^ max 3 d)`, where +`R = (n + steps a + 1) · (width a + 1)` and `n` is the number of input words. The degree is +explicit; only the constant factor is hidden. -/ +theorem Program.ComputesInTime.computableInO_of_width + {N : B.genericModel.NativePrimitives B.encoding} {d : ℕ} (hN : N.HasDegree d) + {p : Program B r} {encode : ℕ → α → List ℕ} {time : α → ℕ → ℕ} + {allowed : α → ℕ → Prop} {f : α → ℕ} + (h : p.ComputesInTime encode time allowed f) {width steps Tw Sw Te Se Ts Ss : α → ℕ} + (hwidth : Turing.MultiTapeTM.ComputableInO width encIn encUnary Tw Sw) + (hallowed : ∀ a, allowed a (width a)) + (hfits : ∀ a, InputFits (width a) (encode (width a) a)) + (hencode : Turing.MultiTapeTM.ComputableInO (fun a => encode (width a) a) encIn + (encList encNat) Te Se) + (hsteps : Turing.MultiTapeTM.ComputableInO steps encIn encNat Ts Ss) + (htime : ∀ a, time a (width a) ≤ steps a) : + Turing.MultiTapeTM.ComputableInO f encIn encNat + (fun a => (encIn a).length + Tw a + Te a + Ts a + (steps a + 1) * + (((encode (width a) a).length + steps a + 1) * (width a + 1)) ^ max 3 d) + (fun a => (encIn a).length + Sw a + Se a + Ss a + + (((encode (width a) a).length + steps a + 1) * (width a + 1)) ^ max 3 d) := by + have hD : 3 ≤ max 3 d := le_max_left _ _ + have hN' := hN.mono (le_max_right 3 d) + have hExec (a : α) : ∃ m ≤ steps a, GenericRAM.Executes (width a) p.toGeneric + (initState B r (encode (width a) a)).denote (f a) m := by + obtain ⟨m, hm, hle⟩ := h a (width a) (hallowed a) (hfits a) + refine ⟨m, hle.trans (htime a), ?_⟩ + rw [denote_initState] + exact executes_toGeneric_iff.mpr hm + -- Every simulated outcome is a word-bounded state with at most `n + 1 + steps` cells. + have hOutLen (a : α) (j : ℕ) (hj : j ≤ steps a) : + (FiniteState.resultEncoding B.encoding (FiniteState.run (width a) p.toGeneric j + (initState B r (encode (width a) a)))).length ≤ + stateBound r (labelBound p.toGeneric) (width a) + ((encode (width a) a).length + 1 + steps a) := by + have hrun := (wordBounded_initState (B := B) (r := r) (hfits a)).length_resultEncoding_run_le + (p := p.toGeneric) (by simp) j + rw [length_entries_initState] at hrun + refine hrun.trans ?_ + simp only [stateBound] + have := Nat.mul_le_mul_right (6 * width a + 3) + (show (encode (width a) a).length + 1 + j ≤ (encode (width a) a).length + 1 + steps a by + omega) + omega + have ht := FiniteState.transport_inO_of_hasDegree B.encoding N hN' hD p.toGeneric + (⟨Ts, Ss, hsteps⟩ : NativeCertificate steps encIn encNat) + (⟨Tw, Sw, hwidth⟩ : NativeCertificate width encIn encUnary) + (FiniteState.initConstCertificate (r := r) B.encoding (NativeCertificate.const 0) + (NativeCertificate.const 0) + (initMemoryCertificate.comp (⟨Te, Se, hencode⟩ : + NativeCertificate (fun a => encode (width a) a) encIn (encList encNat)))) + hExec hOutLen + -- The constants: the state-bound factor, and its `max 3 d`-th power. + set KL := 2 * (encNat (labelBound p.toGeneric)).length + 4 * r + 14 with hKL + set C := (2 * KL + 4) ^ max 3 d with hC + refine ht.mono (r + 2 * KL + C + 40) (fun a => ?_) (fun a => ?_) <;> + · -- Shared estimates, with `R = (n + steps + 1)(w + 1)` and `P = R ^ max 3 d`. + obtain ⟨R, hR⟩ : ∃ R, R = ((encode (width a) a).length + steps a + 1) * (width a + 1) := + ⟨_, rfl⟩ + have hR1 : 1 ≤ R := hR ▸ Nat.one_le_iff_ne_zero.mpr (by positivity) + have hwR : width a + 1 ≤ R := hR ▸ Nat.le_mul_of_pos_left _ (by omega) + have hTR : steps a + 1 ≤ R := hR ▸ (show steps a + 1 ≤ _ + steps a + 1 by omega).trans + (Nat.le_mul_of_pos_right _ (by omega)) + have hRP : R ≤ R ^ max 3 d := Nat.le_self_pow (by omega) R + have hBd : stateBound r (labelBound p.toGeneric) (width a) + ((encode (width a) a).length + 1 + steps a) ≤ KL * (2 * R) := by + refine (stateBound_le _ _ _ _).trans (Nat.mul_le_mul_left _ ?_) + rw [hR] + nlinarith + have hXD : (2 * width a + stateBound r (labelBound p.toGeneric) (width a) + ((encode (width a) a).length + 1 + steps a) + 2) ^ max 3 d ≤ C * R ^ max 3 d := by + rw [hC, ← Nat.mul_pow] + exact Nat.pow_le_pow_left (by nlinarith) _ + have hsq : ((encNat (steps a)).length + 1) ^ 2 ≤ R ^ max 3 d := by + have h1 := length_encNat_le_self (steps a) + calc ((encNat (steps a)).length + 1) ^ 2 ≤ R ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ ≤ R ^ max 3 d := Nat.pow_le_pow_right hR1 (by omega) + have hinit : ((encList encNat (encode (width a) a)).length + 1) ^ 3 ≤ 27 * R ^ max 3 d := by + have h1 := length_encList_le_of_inputFits (hfits a) + have h2 : (encList encNat (encode (width a) a)).length + 1 ≤ 3 * R := by + have : (encode (width a) a).length * (2 * width a + 1) ≤ 2 * R := by rw [hR]; nlinarith + omega + calc ((encList encNat (encode (width a) a)).length + 1) ^ 3 ≤ (3 * R) ^ 3 := + Nat.pow_le_pow_left h2 3 + _ = 27 * R ^ 3 := by ring + _ ≤ 27 * R ^ max 3 d := Nat.mul_le_mul_left 27 (Nat.pow_le_pow_right hR1 hD) + have hencS : (FiniteState.encoding B.encoding (initState B r (encode (width a) a))).length ≤ + stateBound r (labelBound p.toGeneric) (width a) + ((encode (width a) a).length + 1 + steps a) := by + have := hOutLen a 0 (Nat.zero_le _) + simp only [FiniteState.run_zero, FiniteState.resultEncoding, encOption_some, + encSum_inl, List.length_cons] at this + omega + have hQ := Nat.mul_le_mul_left (steps a + 1) (Nat.add_le_add hXD hsq) + have hPQ : R ^ max 3 d ≤ (steps a + 1) * R ^ max 3 d := Nat.le_mul_of_pos_left _ (by omega) + have hencT := length_encNat_le_self (steps a) + have hKLP : KL * (2 * R) ≤ 2 * (KL * R ^ max 3 d) := by + rw [Nat.mul_left_comm]; exact Nat.mul_le_mul_left _ (Nat.mul_le_mul_left _ hRP) + have hKLQ : KL * R ^ max 3 d ≤ KL * ((steps a + 1) * R ^ max 3 d) := + Nat.mul_le_mul_left _ hPQ + have hCQ : (steps a + 1) * (C * R ^ max 3 d + R ^ max 3 d) = + C * ((steps a + 1) * R ^ max 3 d) + (steps a + 1) * R ^ max 3 d := by ring + obtain ⟨K, hK⟩ : ∃ K, K = r + 2 * KL + C + 40 := ⟨_, rfl⟩ + have hK1 := Nat.mul_le_mul_right (encIn a).length (show r + 3 ≤ K by omega) + have hK2 := Nat.mul_le_mul_right ((steps a + 1) * R ^ max 3 d) + (show 2 * KL + C + 34 ≤ K by omega) + have hK3 := Nat.mul_le_mul_right (R ^ max 3 d) (show 2 * KL + C + 34 ≤ K by omega) + have hKpos : 1 ≤ K := by omega + have hKT (x : ℕ) : x ≤ K * x := Nat.le_mul_of_pos_left x hKpos + have hTw := hKT (Tw a) + have hTe := hKT (Te a) + have hTs := hKT (Ts a) + have hSw := hKT (Sw a) + have hSe := hKT (Se a) + have hSs := hKT (Ss a) + simp only [initState, FiniteState.initConstCertificate, FiniteState.initCertificate_time, + FiniteState.initCertificate_space, FiniteState.constRegsCertificate, + NativeCertificate.const_time, NativeCertificate.const_space, NativeCertificate.comp_time, + NativeCertificate.comp_space, initMemoryCertificate, Finset.sum_const, Finset.card_univ, + Fintype.card_fin, smul_eq_mul, FiniteState.runInputEncoding, FiniteState.inputEncoding, + length_encPair, Basis.encoding, encUnary_apply, List.length_replicate, Function.comp_apply, + ← hR, ← hK] at hencS ⊢ + linarith + +/-- **The additive word RAM with an explicit cubic overhead.** A run of `Basis.additive` within +`steps a` instructions on `n` input words at word size `w` costs +`O(|encIn a| + Tw a + Te a + Ts a + (steps a + 1) · ((n + steps a + 1)(w + 1)) ^ 3)` Turing-machine +steps; see `Program.ComputesInTime.computableInO_of_width`. -/ +theorem Program.ComputesInTime.computableInO_of_width_additive + {p : Program .additive r} {encode : ℕ → α → List ℕ} {time : α → ℕ → ℕ} + {allowed : α → ℕ → Prop} {f : α → ℕ} + (h : p.ComputesInTime encode time allowed f) {width steps Tw Sw Te Se Ts Ss : α → ℕ} + (hwidth : Turing.MultiTapeTM.ComputableInO width encIn encUnary Tw Sw) + (hallowed : ∀ a, allowed a (width a)) + (hfits : ∀ a, InputFits (width a) (encode (width a) a)) + (hencode : Turing.MultiTapeTM.ComputableInO (fun a => encode (width a) a) encIn + (encList encNat) Te Se) + (hsteps : Turing.MultiTapeTM.ComputableInO steps encIn encNat Ts Ss) + (htime : ∀ a, time a (width a) ≤ steps a) : + Turing.MultiTapeTM.ComputableInO f encIn encNat + (fun a => (encIn a).length + Tw a + Te a + Ts a + (steps a + 1) * + (((encode (width a) a).length + steps a + 1) * (width a + 1)) ^ 3) + (fun a => (encIn a).length + Sw a + Se a + Ss a + + (((encode (width a) a).length + steps a + 1) * (width a + 1)) ^ 3) := + h.computableInO_of_width Basis.additive.quadraticPrimitives_hasDegree hwidth hallowed hfits + hencode hsteps htime + +end Main + +/-! ## The bit-list convention with logarithmic word size -/ + +section Bits + +private theorem foldl_snoc_map {β γ : Type} (g : β → γ) (l : List β) (acc : List γ) : + l.foldl (fun acc b => acc ++ [g b]) acc = acc ++ l.map g := by + induction l generalizing acc with + | nil => simp + | cons b l ih => simp [ih] + +private theorem foldl_succ (l : List Bool) (m : ℕ) : + l.foldl (fun n _ => n + 1) m = m + l.length := by + induction l generalizing m with + | nil => simp + | cons b l ih => simp only [List.foldl_cons, ih, List.length_cons]; omega + +/-- **The bits of a list as words, in quadratic time.** -/ +theorem bitsToNats_degree : + ComputableInDegree (fun l : List Bool => l.map Bool.toNat) encBits (encList encNat) 2 1 := by + have hstep : ComputableInDegree (fun p : List ℕ × Bool => p.1 ++ [p.2.toNat]) + (encPair (encList encNat) encBool) (encList encNat) 1 1 := + ((snoc encNat).comp ((fst _ _).prod ((of_finite Bool.toNat).comp (snd _ _)))).mono + have h := foldl' (encA := encBool) (encL := encBits) (encB := encList encNat) + (list := fun l => l) (init := fun _ => ([] : List ℕ)) + (step := fun acc b => acc ++ [b.toNat]) (of_eq fun _ => rfl) (const _) hstep + (B := fun l => 3 * (encBits l).length) (DegreeBounded.self.smul 3) + (fun l i hi => by + rw [foldl_snoc_map, List.nil_append, length_encList] + have hle : ∀ x ∈ (l.take i).map Bool.toNat, 2 * (encNat x).length + 1 ≤ 3 := by + intro x hx + obtain ⟨b, -, rfl⟩ := List.mem_map.mp hx + have := length_encNat_le_self b.toNat + cases b <;> simp_all + calc (((l.take i).map Bool.toNat).map fun x => 2 * (encNat x).length + 1).sum + ≤ (((l.take i).map Bool.toNat).map fun _ => 3).sum := List.sum_le_sum hle + _ ≤ 3 * (encBits l).length := by + simp only [List.map_const', List.sum_replicate, List.length_map, smul_eq_mul, + encBits_apply] + have := List.length_take_le' i l + omega) + convert h.mono (d' := 2) (e' := 1) using 1 + funext l + rw [foldl_snoc_map, List.nil_append] + +/-- The length of a bit list in binary, in quadratic time. -/ +theorem lengthBinary_degree : + ComputableInDegree (fun l : List Bool => l.length) encBits encNat 2 1 := by + have h := foldl' (encA := encBool) (encL := encBits) (encB := encNat) + (list := fun l => l) (init := fun _ => (0 : ℕ)) (step := fun n _ => n + 1) + (of_eq fun _ => rfl) (const _) ((natSucc.comp (fst encNat encBool)).mono (d' := 1) (e' := 1)) + (B := fun l => (encBits l).length) DegreeBounded.self + (fun l i hi => by + rw [foldl_succ, Nat.zero_add] + have := length_encNat_le_self (l.take i).length + have := List.length_take_le' i l + simp only [encBits_apply] + omega) + convert h.mono (d' := 2) (e' := 1) using 1 + funext l + rw [foldl_succ, Nat.zero_add] + +/-- Multiplying a unary number by a constant is linear. -/ +theorem unaryMulConst_degree (k : ℕ) : + ComputableInDegree (fun m : ℕ => k * m) encUnary encUnary 1 1 := by + induction k with + | zero => + convert (const (encIn := encUnary) (encMid := encUnary) 0).mono (d' := 1) (e' := 1) + using 1 + funext m + simp + | succ k ih => + convert unaryAdd ih (of_eq (f := fun m : ℕ => m) fun _ => rfl) (d := 1) (e := 1) + (by omega) (by omega) using 1 + funext m + rw [Nat.succ_mul] + +/-- **The word size `k (size n + 1)` of an input of length `n`, in unary, in quadratic time.** It +is at least the logarithmic threshold `logWidth k n`. -/ +theorem logWidthBound_degree (k : ℕ) : + ComputableInDegree (fun l : List Bool => k * (Nat.size l.length + 1)) encBits encUnary 2 1 := by + have hsize : ComputableInDegree (fun l : List Bool => (encNat l.length).length) encBits + encUnary 2 1 := + (((ComputableInO.lengthUnary.comp_enc encNat).computableInDegree (d := 1) (e := 1) + ⟨1, fun _ => by simp⟩ ⟨1, fun _ => by simp⟩ le_rfl).comp lengthBinary_degree).mono + have h := ((unaryMulConst_degree k).comp (unary_succ.comp hsize)).mono (d' := 2) (e' := 1) + convert h using 1 + funext l + simp [encNat_apply, Nat.size_eq_bits_len] + +/-- The logarithmic threshold is at most the word size `k (size n + 1)`. -/ +theorem logWidth_le_mul_size_succ (k n : ℕ) : logWidth k n ≤ k * (Nat.size n + 1) := by + refine Nat.mul_le_mul_left k (Nat.add_le_add_right ?_ 1) + rcases Nat.eq_zero_or_pos n with rfl | hn + · simp + · exact (Nat.log_lt_of_lt_pow (by omega) (Nat.lt_size_self n)).le + +/-- The bits of an input of length `n` fit words of `k (size n + 1)` bits when `k ≥ 1`. -/ +theorem inputFits_map_toNat {k : ℕ} (hk : 1 ≤ k) (l : List Bool) : + InputFits (k * (Nat.size l.length + 1)) (l.map Bool.toNat) := by + have hw : Nat.size l.length + 1 ≤ k * (Nat.size l.length + 1) := Nat.le_mul_of_pos_left _ hk + refine ⟨?_, fun v hv => ?_⟩ + · rw [fits_iff, List.length_map] + exact (Nat.lt_size_self _).trans_le (Nat.pow_le_pow_right (by omega) (by omega)) + · obtain ⟨b, -, rfl⟩ := List.mem_map.mp hv + rw [fits_iff] + calc b.toNat < 2 ^ 1 := by cases b <;> simp + _ ≤ _ := Nat.pow_le_pow_right (by omega) (by omega) + +end Bits + +end Cslib.WordRAM diff --git a/ComplexityTests/WordRAMTuringOverhead.lean b/ComplexityTests/WordRAMTuringOverhead.lean new file mode 100644 index 0000000000..18e6f6d5ee --- /dev/null +++ b/ComplexityTests/WordRAMTuringOverhead.lean @@ -0,0 +1,99 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.Complexity.Classes.FunctionTime.WordRAM + +/-! +# Explicit-overhead word-RAM simulation clients + +The additive primitives are quadratic, and the counted initialization fold builds the memory of +`State.init`. A two-instruction additive program returning the input length, on the bits of the +input at logarithmic word size, puts the length function in an explicit `FTIME` class; a +two-branch additive decider puts emptiness in an explicit `DTIME` class. Both go through +`Program.ComputesInTime.computableInO_of_width_additive`, whose hypotheses these clients satisfy. +-/ + +namespace ComplexityTests.WordRAMTuringOverhead + +open Cslib.WordRAM Turing.MultiTapeTM Cslib.Complexity + +/-! ## Primitives and initialization -/ + +/-- The additive primitives are quadratic, so the simulator's exponent is `max 3 2 = 3`. -/ +example : Basis.additive.quadraticPrimitives.HasDegree 2 := + Basis.additive.quadraticPrimitives_hasDegree + +/-- The counted fold builds the stored cells of `State.init`. -/ +example : initEntries [5, 7] = [(0, 2), (1, 5), (2, 7)] := by + rw [initEntries_eq_foldl] + rfl + +/-- The fits test agrees with `v < 2 ^ w`: `5` fits three bits but not two. -/ +example : Fits 3 5 ∧ ¬Fits 2 5 := by decide + +/-! ## A constant clock -/ + +/-- A constant time bound is computable in linear time. -/ +theorem const_mem_FTIME (v : ℕ) : + (fun _ : List Bool => v) ∈ FTIME encBits encNat (fun n => n + 1) := by + obtain ⟨c, hc, -⟩ := (ComputableInDegree.const (encIn := encBits) (encMid := encNat) v).inO + exact ⟨c, c, fun n => c * ((n + 1) + 1), + hc.mono (fun l => by simp [Nat.mul_add]) (fun l => by simp)⟩ + +/-! ## A function: the input length -/ + +/-- Load the length from address `0` and return it. -/ +def lengthProgram : Program .additive 1 := [.op (.load 0 0), .halt 0] + +theorem lengthProgram_executes (w : ℕ) (l : List ℕ) : + Executes w lengthProgram (.init l) l.length 2 := by + refine Executes.head (t := ⟨1, (Data.init l).set 0 l.length⟩) ?_ (Executes.halt ?_) + · simp [Next, Cslib.WordRAM.step, lengthProgram, State.init] + · simp [Cslib.WordRAM.step, lengthProgram] + +theorem lengthProgram_computesInTime : + lengthProgram.ComputesInTime (fun _ (l : List Bool) => l.map Bool.toNat) (fun _ _ => 2) + (fun l w => logWidth 1 l.length ≤ w) List.length := + fun l w _ _ => ⟨2, by simpa only [List.length_map] using + lengthProgram_executes w (l.map Bool.toNat), le_rfl⟩ + +/-- **The length of a bit list is in an explicit function-time class**, through the two-instruction +additive program at logarithmic word size. -/ +example : List.length ∈ FTIME encBits encNat + (fun n => (n + 1) + (n + 2 + 1) ^ 4 * (1 * (Nat.size n + 1) + 1) ^ 3) := + lengthProgram_computesInTime.mem_FTIME (encIn := encBits) (T := fun _ => 2) le_rfl + (const_mem_FTIME 2) + +/-! ## A decision problem: emptiness -/ + +/-- Return `1` exactly when the input is empty, branching on its length. -/ +def emptyProgram : Program .additive 2 := + [.op (.load 0 0), .branch (.eq 0 1) 3, .halt 1, .op (.const 1 1), .halt 1] + +theorem emptyProgram_decidesInTime : + emptyProgram.DecidesInTime (fun _ (l : List Bool) => l.map Bool.toNat) {l | l = []} + (fun _ _ => 4) (fun l w => logWidth 1 l.length ≤ w) := by + intro l w hw _ + have hw1 : 1 ≤ w := le_trans (by simp [logWidth]) hw + rcases l with _ | ⟨x, l⟩ + · refine ⟨1, 4, ?_, le_rfl, le_rfl, by simp⟩ + refine .of_run (fuel := 4) ?_ + have h1 : Fits w 1 := (fits_iff.mpr (Nat.one_lt_two_pow (by omega))) + simp [run, Cslib.WordRAM.step, WordSizeDependentProgram.const, emptyProgram, Op.eval, + State.init, Data.put, h1] + · refine ⟨0, 3, ?_, by norm_num, by norm_num, by simp⟩ + refine .of_run (fuel := 3) ?_ + simp [run, Cslib.WordRAM.step, WordSizeDependentProgram.const, emptyProgram, Op.eval, + State.init] + +/-- **Emptiness of a bit list is in an explicit `DTIME` class**, through the additive decider at +logarithmic word size. -/ +example : {l : List Bool | l = []} ∈ DTIME encBits + (fun n => (n + 1) + (n + 4 + 1) ^ 4 * (1 * (Nat.size n + 1) + 1) ^ 3) := + emptyProgram_decidesInTime.mem_DTIME (encIn := encBits) (T := fun _ => 4) le_rfl + (const_mem_FTIME 4) + +end ComplexityTests.WordRAMTuringOverhead From bb6b1c5a8596448c2f6b9c996ea79e47f54eacb7 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:33:39 +0000 Subject: [PATCH 10/16] feat(Classes/Time): explicit-time verifier characterization of NTIME (#2025) * feat(Classes/Time): explicit-time verifier characterization of NTIME (#1572) Guess and verify within explicit time (`setOf_exists_mem_NTIME`), through a closed fair-coin composition with the new framed sampler `pairSampler`, and a linear-time verifier for every problem in `NTIME` (`exists_verifier_of_mem_NTIME`). Infrastructure toward the nondeterministic time hierarchy. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(Machines/PTM): make the pair sampler's proof configurations private The induction configurations `PairSampler.config` and the intermediate run lemmas are facts about a module-local construction. Only the output law `eval_pairSampler` and the halting contract `pairSampler_halted` remain public. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --------- Co-authored-by: Claude Opus 5.5 --- Complexity.lean | 2 + .../Complexity/Classes/Time/Verifier.lean | 266 +++++++++++++++++ .../MultiTape/Probabilistic/PairSampler.lean | 268 ++++++++++++++++++ .../NondeterministicTimeVerifiers.lean | 85 ++++++ 4 files changed, 621 insertions(+) create mode 100644 Complexity/Computability/Complexity/Classes/Time/Verifier.lean create mode 100644 Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean create mode 100644 ComplexityTests/NondeterministicTimeVerifiers.lean diff --git a/Complexity.lean b/Complexity.lean index efc9a21a67..85d501bae2 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1349,6 +1349,7 @@ public import Complexity.Computability.Complexity.Classes.Tally public import Complexity.Computability.Complexity.Classes.Time public import Complexity.Computability.Complexity.Classes.Time.AcceptCount public import Complexity.Computability.Complexity.Classes.Time.Polynomial +public import Complexity.Computability.Complexity.Classes.Time.Verifier public import Complexity.Computability.Complexity.Classes.Toda public import Complexity.Computability.Complexity.Classes.Toda.Counting public import Complexity.Computability.Complexity.Classes.Toda.Hierarchy @@ -2025,6 +2026,7 @@ public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.O public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.Oracle public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.OutputPrefix public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.OutputToTape +public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PairSampler public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PolynomialClock public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.QueryBounds public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.QueryTake diff --git a/Complexity/Computability/Complexity/Classes/Time/Verifier.lean b/Complexity/Computability/Complexity/Classes/Time/Verifier.lean new file mode 100644 index 0000000000..89b4f594ab --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Time/Verifier.lean @@ -0,0 +1,266 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Time +import Complexity.Computability.Complexity.Classes.NP.Nondeterministic +import Complexity.Computability.Machines.Turing.MultiTape.Internal.EqualityPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.OCombinators +import Complexity.Computability.Machines.Turing.MultiTape.Internal.ResourceFacts +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.Composition +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PairSampler +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.ReplayInput +import Complexity.Computability.Machines.Turing.MultiTape.Run +import Complexity.Probability.BitString +import Complexity.Probability.PMF.Bind + +/-! +# Nondeterministic time through verifiers, with explicit bounds + +This module characterizes `Cslib.Complexity.NTIME enc t` by deterministic verifiers, keeping the +time bound up to constant factors. The polynomial characterization of `NP` +(`Cslib.Complexity.mem_NP_iff_decidableInPolyTime`) loses the degree in both directions. The +two directions here keep it. + +* **Guess and verify** (`Cslib.Complexity.setOf_exists_mem_NTIME`). Suppose a verifier `V` is + computable within time `T (a, w)` on encoded pairs, and a certificate length `ℓ a` is + computable in unary within time `Tℓ a`. Then the problem + `{a | ∃ w, w.length = ℓ a ∧ V (a, w) = true}` lies in `NTIME enc t` whenever + `(enc a).length + Tℓ a + T (a, w)` is within `O(t (enc a).length)` on certificates of that + length. The certificate length may depend on the instance, not only on its encoded length. + The machine is the nondeterministic reading of a closed fair-coin composition: + - a deterministic machine writes `frame (enc a)` followed by `ℓ` ones; + - the fixed sampler `Turing.MultiTapePTM.pairSampler` copies the framed instance and replaces + the ones by fair coins; + - a deterministic machine runs the verifier. + + Its computation paths are exactly the coin replays, so it accepts exactly when some + certificate is accepted, and every branch halts within the composed clock. +* **Verifiers from nondeterministic time** (`Cslib.Complexity.exists_verifier_of_mem_NTIME`). + A problem in `NTIME enc t` has a verifier that is computable in linear time on encoded pairs: + check that the certificate, read as coins of the choice table + (`Turing.MultiTapeNTM.checkChoices`), replays an accepting computation. Every certificate + length of at least `c * t (enc a).length + c` works, for a constant `c`. + +These are explicit-time forms of the guess-and-verify characterization of nondeterministic +polynomial time [Arora and Barak][AroraBarak09]: the choices of an accepting computation are a +certificate, and a certificate can be guessed and then checked. + +## Main results + +* `Cslib.Complexity.setOf_exists_mem_NTIME`: guess and verify within explicit time. +* `Cslib.Complexity.exists_verifier_of_mem_NTIME`: a linear-time verifier for a problem in + `NTIME`, with certificates of every sufficient length. + +## References + +* [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*][AroraBarak09] +-/ + +public section + +namespace Cslib.Complexity + +open _root_.Turing _root_.Turing.MultiTapeTM + +variable {α : Type} {enc : α ↪ List Bool} + +/-- **Guess and verify, within explicit time.** If a verifier `V` is computable within time +`T (a, w)` on encoded pairs, and the certificate length `ℓ a` is computable in unary within time +`Tℓ a`, then the problem of instances with an accepted certificate of that length is in +`NTIME enc t`, provided `(enc a).length + Tℓ a + T (a, w) ≤ C * t (enc a).length + C` for every +certificate `w` of that length. -/ +theorem setOf_exists_mem_NTIME {V : α × List Bool → Bool} {ℓ : α → ℕ} {t : ℕ → ℕ} + {Tℓ Sℓ : α → ℕ} {T S : α × List Bool → ℕ} {C : ℕ} + (hℓ : ComputableInO ℓ enc encUnary Tℓ Sℓ) + (hV : ComputableInO V (encPair enc encBits) encBool T S) + (ht : ∀ a w, w.length = ℓ a → (enc a).length + Tℓ a + T (a, w) ≤ C * t (enc a).length + C) : + {a | ∃ w : List Bool, w.length = ℓ a ∧ V (a, w) = true} ∈ NTIME enc t := by + -- the deterministic preparation and the verifier, as machines + obtain ⟨c₁, ⟨k₁, Q₁, _, tm₁, h₁⟩, -⟩ := + (ComputableInO.of_eq (f := fun a : α => a) (encIn := enc) (encMid := enc) fun _ => rfl).prod hℓ + obtain ⟨c₃, ⟨k₃, Q₃, _, tm₃, h₃⟩, -⟩ := hV + let L (a : α) : ℕ := ℓ a + let u (a : α) : List Bool := frame (enc a) ++ List.replicate (L a) true + let t₁ (a : α) : ℕ := c₁ * ((enc a).length + Tℓ a + 1) + let t₂ (a : α) : ℕ := (u a).length + 1 + let t₃ (a : α) : ℕ := c₃ * (C * t (enc a).length + C + 1) + have hrun₁ (a : α) := (h₁ a).runFrom_of_le (le_refl (t₁ a)) + have hlen₁ (a : α) : (u a).length ≤ t₁ a := (h₁ a).length_output_le + -- the first phase writes the framed instance and the unary certificate length + let M₁ : MultiTapePTM k₁ Bool Q₁ := MultiTapePTM.ofDeterministic tm₁ + have hfirst₁ (a : α) : ∀ final ∈ (ProbComp.eval + (M₁.runClosedConfigFrom (t₁ a) (M₁.initialConfig (enc a)))).support, + final.tapes.state = none := by + intro final hfinal + rw [MultiTapePTM.eval_runClosedConfigFrom_ofDeterministic, PMF.mem_support_pure_iff] at hfinal + subst hfinal + exact (hrun₁ a).1 + have hout₁ (a : α) : ProbComp.eval (M₁.runClosed (t₁ a) (enc a)) = PMF.pure (u a) := by + rw [MultiTapePTM.eval_runClosed_ofDeterministic, (hrun₁ a).2] + rfl + -- the sampler copies the framed instance and guesses the certificate + have hsecond₁ (a : α) : ∀ word ∈ (ProbComp.eval (M₁.runClosed (t₁ a) (enc a))).support, + ∀ final ∈ (ProbComp.eval (MultiTapePTM.pairSampler.runClosedConfigFrom (t₂ a) + (MultiTapePTM.pairSampler.initialConfig word))).support, final.tapes.state = none := by + intro word hword + rw [hout₁ a, PMF.mem_support_pure_iff] at hword + subst hword + exact MultiTapePTM.pairSampler_halted (enc a) (List.replicate (L a) true) + let P₁₂ := MultiTapePTM.pairSampler.comp M₁ false + let t₁₂ (a : α) : ℕ := t₁ a + (t₁ a + 4 + t₂ a) + have heval₁₂ (a : α) : ProbComp.eval (P₁₂.runClosed (t₁₂ a) (enc a)) = + (BitString.uniformList (L a)).map (frame (enc a) ++ ·) := by + rw [MultiTapePTM.eval_comp M₁ MultiTapePTM.pairSampler false (enc a) (t₁ a) (t₂ a) + (hfirst₁ a) (hsecond₁ a), hout₁ a, Probability.PMF.monad_bind_eq_bind, PMF.pure_bind] + simpa only [List.length_replicate, u] using + MultiTapePTM.eval_pairSampler (enc a) (List.replicate (L a) true) + have hhalt₁₂ (a : α) := MultiTapePTM.comp_halted M₁ MultiTapePTM.pairSampler false (enc a) + (t₁ a) (t₂ a) (hfirst₁ a) (hsecond₁ a) + -- the verifier runs on every guessed pair within the uniform clock `t₃` + let M₃ : MultiTapePTM k₃ Bool Q₃ := MultiTapePTM.ofDeterministic tm₃ + have hrun₃ (a : α) (w : List Bool) (hw : w.length = L a) := + (h₃ (a, w)).runFrom_of_le (J := t₃ a) (Nat.mul_le_mul_left c₃ (by + have := ht a w hw + omega)) + have hword (a : α) {word : List Bool} + (hword : word ∈ (ProbComp.eval (P₁₂.runClosed (t₁₂ a) (enc a))).support) : + ∃ w : List Bool, w.length = L a ∧ word = encPair enc encBits (a, w) := by + rw [heval₁₂ a] at hword + obtain ⟨w, hw, rfl⟩ := (PMF.mem_support_map_iff _ _ _).mp hword + exact ⟨w, BitString.mem_support_uniformList_iff.mp hw, rfl⟩ + have hsecond₂ (a : α) : ∀ word ∈ (ProbComp.eval (P₁₂.runClosed (t₁₂ a) (enc a))).support, + ∀ final ∈ (ProbComp.eval (M₃.runClosedConfigFrom (t₃ a) (M₃.initialConfig word))).support, + final.tapes.state = none := by + intro word hw final hfinal + obtain ⟨w, hwlen, rfl⟩ := hword a hw + rw [MultiTapePTM.eval_runClosedConfigFrom_ofDeterministic, PMF.mem_support_pure_iff] at hfinal + subst hfinal + exact (hrun₃ a w hwlen).1 + let P := M₃.comp P₁₂ false + let total (a : α) : ℕ := t₁₂ a + (t₁₂ a + 4 + t₃ a) + -- the nondeterministic reading decides the problem + have hdec : P.toNTM.DecidesInTime enc + {a | ∃ w : List Bool, w.length = ℓ a ∧ V (a, w) = true} total := by + intro a + have htime : P.toNTM.RunsInTime (enc a) (total a) := P.runsInTime_toNTM + (MultiTapePTM.comp_halted P₁₂ M₃ false (enc a) (t₁₂ a) (t₃ a) (hhalt₁₂ a) (hsecond₂ a)) + refine ⟨htime, ?_⟩ + rw [MultiTapeNTM.accepts_iff_exists_time_eq htime, + P.exists_computationPath_toNTM_iff (P := fun cfg => cfg.output = [true])] + have hiff : (∃ final ∈ (ProbComp.eval (P.runClosedConfigFrom (total a) + (P.initialConfig (enc a)))).support, final.tapes.output = [true]) ↔ + [true] ∈ (ProbComp.eval (P.runClosed (total a) (enc a))).support := by + rw [MultiTapePTM.runClosed_eq_map, ProbComp.eval_map, PMF.mem_support_map_iff] + rw [hiff, MultiTapePTM.eval_comp P₁₂ M₃ false (enc a) (t₁₂ a) (t₃ a) (hhalt₁₂ a) + (hsecond₂ a), Probability.PMF.monad_bind_eq_bind, PMF.mem_support_bind_iff] + constructor + · rintro ⟨w, hw, hV⟩ + refine ⟨encPair enc encBits (a, w), ?_, ?_⟩ + · rw [heval₁₂ a] + exact (PMF.mem_support_map_iff _ _ _).mpr + ⟨w, BitString.mem_support_uniformList_iff.mpr hw, rfl⟩ + · rw [MultiTapePTM.eval_runClosed_ofDeterministic, (hrun₃ a w hw).2, PMF.mem_support_pure_iff] + simp [hV] + · rintro ⟨word, hw, hacc⟩ + obtain ⟨w, hwlen, rfl⟩ := hword a hw + rw [MultiTapePTM.eval_runClosed_ofDeterministic, (hrun₃ a w hwlen).2, + PMF.mem_support_pure_iff] at hacc + exact ⟨w, hwlen, by simpa using hacc⟩ + -- the composed clock is within `O(t)` + refine ⟨(6 * c₁ + c₃) * C, (6 * c₁ + c₃) * (C + 1) + 14, _, _, inferInstance, P.toNTM, + hdec.mono fun a => ?_⟩ + set X := C * t (enc a).length + C + 1 + have hX₁ : t₁ a ≤ c₁ * X := Nat.mul_le_mul_left c₁ (by + have := ht a (List.replicate (L a) false) (List.length_replicate ..) + omega) + have htotal : total a ≤ 6 * t₁ a + 14 + t₃ a := by + have := hlen₁ a + simp only [total, t₁₂, t₂] + omega + have hX₃ : t₃ a = c₃ * X := rfl + calc total a ≤ 6 * (c₁ * X) + 14 + c₃ * X := by + have := Nat.mul_le_mul_left 6 hX₁ + rw [hX₃] at htotal + omega + _ = (6 * c₁ + c₃) * C * t (enc a).length + ((6 * c₁ + c₃) * (C + 1) + 14) := by + simp only [X] + ring + +/-- **A linear-time verifier for nondeterministic time.** A problem in `NTIME enc t` has a +verifier computable in linear time on encoded pairs. For a constant `c`, every certificate length +`L ≥ c * t (enc a).length + c` works: `a ∈ A` exactly when some certificate of length `L` is +accepted. The verifier checks that the certificate, read as coins of the choice table, replays an +accepting computation. -/ +theorem exists_verifier_of_mem_NTIME {A : Set α} {t : ℕ → ℕ} (hA : A ∈ NTIME enc t) : + ∃ (V : α × List Bool → Bool) (c : ℕ), + ComputableInO V (encPair enc encBits) encBool (fun p => (encPair enc encBits p).length) + (fun p => (encPair enc encBits p).length) ∧ + ∀ a L, c * t (enc a).length + c ≤ L → + (a ∈ A ↔ ∃ w : List Bool, w.length = L ∧ V (a, w) = true) := by + obtain ⟨c₁, c₂, k, State, hfinite, ntm, hdec⟩ := hA + set M := Nat.card (Turing.Action k Bool State) + refine ⟨ntm.checkChoices enc, M * (c₁ + c₂), ?_, fun a L hL => + hdec.mem_iff_exists_checkChoices ?_⟩ + · -- replay the certificate with the choice table, then compare the output with `[true]` + have hswap : ComputableInO (fun p : α × List Bool => (p.2, enc p.1)) (encPair enc encBits) + (encPair encBits encBits) + (fun p => (encPair enc encBits p).length + + ((encPair enc encBits p).length + (enc p.1).length)) + (fun p => (encPair enc encBits p).length + + ((encPair enc encBits p).length + (enc p.1).length)) := + (ComputableInO.snd enc encBits).prod + ((ComputableInO.of_eq (f := fun x : α => enc x) (encIn := enc) (encMid := encBits) + fun _ => rfl).comp (ComputableInO.fst enc encBits)) + have hrun := (ComputableInO.of_linear + (MultiTapePTM.computableInLinearTimeAndSpace_runClosedCoins ntm.choiceTable)).comp hswap + obtain ⟨cr, hcr⟩ := hrun.length_le_time + have heq := ComputableInO.eqWords.comp ((ComputableInO.const (encIn := encPair enc encBits) + (encMid := encBits) [true]).prod hrun) + have hfun : ((fun p : List Bool × List Bool => decide (p.1 = p.2)) ∘ + fun p : α × List Bool => ([true], ((fun q : List Bool × List Bool => + ntm.choiceTable.runClosedCoins q.1 q.2) ∘ fun p : α × List Bool => (p.2, enc p.1)) p)) = + ntm.checkChoices enc := by + funext p + simp [MultiTapeNTM.checkChoices, eq_comm] + rw [hfun] at heq + -- every part is linear in the encoded pair, including the replayed output + have hbound (p : α × List Bool) : + (encPair enc encBits p).length + ((encPair enc encBits p).length + + ((encPair enc encBits p).length + (enc p.1).length) + + (encPair encBits encBits (p.2, enc p.1)).length) + + 2 * ((ntm.choiceTable.runClosedCoins p.2 (enc p.1)).length + 2) ≤ + (9 + 10 * cr) * ((encPair enc encBits p).length + 1) := by + have hc := hcr p + simp only [Function.comp_apply, encBits_apply] at hc + have hle : (enc p.1).length ≤ (encPair enc encBits p).length := by + simp only [encPair_apply, List.length_append, length_frame, encBits_apply] + omega + have hsw : (encPair encBits encBits (p.2, enc p.1)).length ≤ + 2 * (encPair enc encBits p).length := by + simp only [encPair_apply, List.length_append, length_frame, encBits_apply] + omega + generalize (encPair enc encBits p).length = N at * + generalize (ntm.choiceTable.runClosedCoins p.2 (enc p.1)).length = R at * + generalize (encPair encBits encBits (p.2, enc p.1)).length = W at * + have hR : R ≤ cr * (5 * N + 1) := hc.trans (Nat.mul_le_mul_left cr (by omega)) + nlinarith + refine heq.mono (9 + 10 * cr) (fun p => ?_) fun p => ?_ + · have := hbound p + simp only [Function.comp_apply, List.length_singleton] at this ⊢ + nlinarith + · have := hbound p + simp only [Function.comp_apply, List.length_singleton] at this ⊢ + omega + · have hmul : M * (c₁ * t (enc a).length + c₂) ≤ + M * (c₁ + c₂) * t (enc a).length + M * (c₁ + c₂) := by + rw [Nat.mul_assoc, ← Nat.mul_add] + exact Nat.mul_le_mul_left M (by nlinarith) + exact hmul.trans hL + +end Cslib.Complexity diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean new file mode 100644 index 0000000000..bce12d8550 --- /dev/null +++ b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/PairSampler.lean @@ -0,0 +1,268 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.Sampling +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Frame.Basic + +/-! +# Sampling a certificate beside a framed word + +`Turing.MultiTapePTM.pairSampler` is one fixed fair-coin machine with three control states and no +work tapes. On an input `frame y ++ r` it copies `frame y` to the output, then emits one fresh +fair bit for each symbol of `r`, and halts at the right boundary. So it outputs `frame y ++ w`, +the pair encoding of `(y, w)`, for a uniform word `w` of length `r.length`. It takes exactly +`(frame y ++ r).length + 1` transitions. Each copying transition also draws a coin, and ignores +it. Read nondeterministically, the machine guesses a certificate for the framed instance. + +## Main definitions + +* `Turing.MultiTapePTM.pairSampler`: the framed copier and certificate sampler. + +## Main results + +* `Turing.MultiTapePTM.eval_pairSampler`: the output is a uniform certificate after the framed + word. +* `Turing.MultiTapePTM.pairSampler_halted`: every run has halted at the linear clock. +-/ + +@[expose] public section + +namespace Turing.MultiTapePTM + +open Cslib MultiTapeMachine MultiTapeTM + +namespace PairSampler + +/-- The control phases: before a frame tag, before a framed data bit, and in the sampled tail. -/ +inductive Phase + | tag + | data + | tail + deriving DecidableEq + +instance : Fintype Phase := + ⟨{.tag, .data, .tail}, fun q => by cases q <;> simp⟩ + +/-- The control state after reading a symbol. The right boundary halts. -/ +def next : Phase → Option Bool → Option Phase + | _, none => none + | .tag, some true => some .data + | .tag, some false => some .tail + | .data, some _ => some .tag + | .tail, some _ => some .tail + +/-- The emitted symbol. Framed symbols are copied, and tail symbols are replaced by the coin. -/ +def emit : Phase → Option Bool → Bool → Option Bool + | _, none, _ => none + | .tail, some _, bit => some bit + | .tag, some b, _ => some b + | .data, some b, _ => some b + +end PairSampler + +open PairSampler in +/-- Copy a framed word, then emit one fair bit per remaining input symbol, and halt at the right +boundary, without work tapes. -/ +def pairSampler : MultiTapePTM 0 Bool Phase where + initial := .tag + tr q symbol _ _ bit := .step + { inputTape := if symbol.isSome then 1 else 0 + workTapes := Fin.elim0 + output := emit q symbol bit + state := next q symbol } + Empty.elim Empty.elim + +namespace PairSampler + +/-- The sampler at an input position, with the output written so far. -/ +private def config (input : List Bool) (state : Option Phase) (index : ℕ) (output : List Bool) + (h : index ≤ input.length) : Config 0 Bool Phase Empty input where + tapes := + { state := state + inputPos := ⟨index + 1, by lia⟩ + workTapes := Fin.elim0 + workTapePos := Fin.elim0 + output := output } + +/-- Reading a remaining symbol writes the emitted symbol and advances once. -/ +private theorem step {input output : List Bool} {index : ℕ} (h : index < input.length) + (q : Phase) (o : Option Bool) (s : Option Phase) : + (config input (some q) index output (by lia)).step + { inputTape := 1, workTapes := Fin.elim0, output := o, state := s } + Empty.elim Empty.elim = + config input s (index + 1) (output ++ o.toList) h := by + refine Config.ext ?_ (funext fun oracle => oracle.elim) + refine Cfg.ext_zero_tapes rfl ?_ rfl + apply Fin.ext + change (moveInputPos _ .pos).val = index + 1 + 1 + rw [moveInputPos_pos_of_ne_right _ (by simp only [config]; lia)] + rfl + +/-- One live transition at a remaining symbol: draw a coin and take the selected step. -/ +private theorem eval_run_succ {input output : List Bool} {index : ℕ} (h : index < input.length) + (q : Phase) (fuel : ℕ) : + ProbComp.eval (pairSampler.runClosedConfigFrom (fuel + 1) + (config input (some q) index output (by lia))) = + ProbComp.eval (ProbComp.uniform Bool >>= fun bit => + pairSampler.runClosedConfigFrom fuel + (config input (next q (some input[index])) (index + 1) + (output ++ (emit q (some input[index]) bit).toList) h)) := by + have hsym := inputSymbolInner (cfg := + (config input (some q) index output (by lia)).tapes) index (by simp [config]; lia) h + rw [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + simp only [show (config input (some q) index output (by lia)).tapes.state = some q from rfl, + hsym, pairSampler, Option.isSome_some, ↓reduceIte, bind_map_left, step h] + rfl + +/-- At the right boundary the sampler halts, drawing and discarding one coin. -/ +private theorem eval_run_boundary (input output : List Bool) (q : Phase) (fuel : ℕ) : + ProbComp.eval (pairSampler.runClosedConfigFrom (fuel + 1) + (config input (some q) input.length output le_rfl)) = + PMF.pure (config input none input.length output le_rfl) := by + have hsym := inputSymbol_eq_none_of_boundary (cfg := + (config input (some q) input.length output le_rfl).tapes) + (Or.inr (by simp [config])) + rw [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + simp only [show (config input (some q) input.length output le_rfl).tapes.state = some q from rfl, + pairSampler, hsym, Option.isSome_none, Bool.false_eq_true, ↓reduceIte, bind_map_left] + have hcfg : ∀ bit : Bool, (config input (some q) input.length output le_rfl).step + { inputTape := 0, workTapes := Fin.elim0, output := emit q none bit, + state := next q none } Empty.elim Empty.elim = + config input none input.length output le_rfl := by + intro bit + refine Config.ext ?_ (funext fun oracle => oracle.elim) + refine Cfg.ext_zero_tapes ?_ (by simp [Config.step, config, Turing.Action.apply]) ?_ + · cases q <;> rfl + · cases q <;> simp [Config.step, config, Turing.Action.apply, emit] + simp only [hcfg] + rcases fuel with _ | fuel + · simp only [MultiTapeMachine.runConfigFrom, ProbComp.eval_bind, ProbComp.eval_pure, + PMF.bind_const] + · have hhalt : ∀ n, ProbComp.eval (pairSampler.runClosedConfigFrom n + (config input none input.length output le_rfl)) = + PMF.pure (config input none input.length output le_rfl) := by + intro n + cases n with + | zero => simp [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + | succ n => + rw [runClosedConfigFrom, runConfigFrom, MultiTapeMachine.runConfigFrom] + simp [config] + simp only [ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => ProbComp.eval + (pairSampler.runClosedConfigFrom (fuel + 1) + (config input none input.length output le_rfl))) = _ + rw [hhalt, PMF.bind_const] + +/-- The tail phase: one fresh fair bit per remaining symbol, then halting at the boundary. -/ +private theorem eval_run_tail (input : List Bool) (remaining : ℕ) : + ∀ (index : ℕ) (output : List Bool) (h : index + remaining = input.length), + ProbComp.eval (pairSampler.runClosedConfigFrom (remaining + 1) + (config input (some .tail) index output (by lia))) = + (BitString.uniformList remaining).map + (fun tail => config input none input.length (output ++ tail) le_rfl) := by + induction remaining with + | zero => + intro index output h + have hi : index = input.length := by lia + subst index + rw [eval_run_boundary] + simp [BitString.uniformList_zero, PMF.pure_map] + | succ remaining ih => + intro index output h + have hi : index < input.length := by lia + rw [eval_run_succ hi] + simp only [next, emit, Option.toList_some] + simp only [ProbComp.eval_bind, ProbComp.eval_uniform, ih (index + 1) _ (by lia), + BitString.uniformList_succ, PMF.map_bind, PMF.map_comp, Function.comp_def, + List.append_assoc, List.singleton_append] + +/-- The copying phase: a framed word is copied verbatim, ignoring the coins drawn meanwhile. -/ +private theorem eval_run_frame (input rest : List Bool) (fuel : ℕ) (y : List Bool) : + ∀ (pre output : List Bool) (hin : input = pre ++ frame y ++ rest), + ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y).length + fuel) + (config input (some .tag) pre.length output (by subst hin; simp))) = + ProbComp.eval (pairSampler.runClosedConfigFrom fuel + (config input (some .tail) (pre.length + (frame y).length) (output ++ frame y) + (by subst hin; simp))) := by + induction y with + | nil => + intro pre output hin + have hlt : pre.length < input.length := by subst hin; simp [frame] + have hget : input[pre.length] = false := by subst hin; simp [frame] + rw [show (frame []).length + fuel = fuel + 1 by simp [frame]; lia, eval_run_succ hlt] + simp only [hget, next, emit, Option.toList_some, ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => _) = _ + rw [PMF.bind_const] + rfl + | cons b y ih => + intro pre output hin + have hlt : pre.length < input.length := by subst hin; simp [frame] + have hlt' : pre.length + 1 < input.length := by subst hin; simp [frame] + have hget : input[pre.length] = true := by subst hin; simp [frame] + have hget' : input[pre.length + 1] = b := by + subst hin + simp only [frame, List.flatMap_cons, List.append_assoc] + rw [List.getElem_append_right (by simp)] + simp + have hlen : (frame (b :: y)).length + fuel = ((frame y).length + fuel + 1) + 1 := by + simp [frame]; lia + rw [hlen, eval_run_succ hlt] + simp only [hget, next, emit, Option.toList_some, ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => _) = _ + rw [PMF.bind_const, eval_run_succ hlt'] + simp only [hget', next, emit, Option.toList_some, ProbComp.eval_bind] + change (ProbComp.eval (ProbComp.uniform Bool)).bind (fun _ => _) = _ + rw [PMF.bind_const] + have hpre : input = (pre ++ [true, b]) ++ frame y ++ rest := by + subst hin; simp [frame] + have := ih (pre ++ [true, b]) (output ++ [true] ++ [b]) hpre + simp only [List.length_append, List.length_cons, List.length_nil] at this + convert this using 3 + · congr 1 + · simp [frame]; lia + · simp + +end PairSampler + +open PairSampler + +/-- The final configuration retains the framed word and a uniform certificate, and has halted at +the right boundary. -/ +private theorem eval_runClosedConfig_pairSampler (y r : List Bool) : + ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y ++ r).length + 1) + (pairSampler.initialConfig (frame y ++ r))) = + (BitString.uniformList r.length).map (fun w => + PairSampler.config (frame y ++ r) none (frame y ++ r).length (frame y ++ w) le_rfl) := by + have hcfg : pairSampler.initialConfig (frame y ++ r) = + PairSampler.config (frame y ++ r) (some .tag) ([] : List Bool).length [] (by simp) := by + refine Config.ext ?_ rfl + exact Cfg.ext_zero_tapes rfl rfl rfl + rw [hcfg, show (frame y ++ r).length + 1 = (frame y).length + (r.length + 1) by simp; lia, + eval_run_frame (frame y ++ r) r (r.length + 1) y [] [] (by simp)] + simpa only [List.length_nil, zero_add, List.nil_append] using + eval_run_tail (frame y ++ r) r.length (frame y).length (frame y) (by simp) + +/-- The sampler outputs a uniform certificate after the framed word, in a linear number of +transitions. -/ +theorem eval_pairSampler (y r : List Bool) : + ProbComp.eval (pairSampler.runClosed ((frame y ++ r).length + 1) (frame y ++ r)) = + (BitString.uniformList r.length).map (frame y ++ ·) := by + rw [runClosed_eq_map, ProbComp.eval_map, eval_runClosedConfig_pairSampler, PMF.map_comp] + rfl + +/-- Every supported configuration has actually halted by the linear clock. -/ +theorem pairSampler_halted (y r : List Bool) : + ∀ final ∈ (ProbComp.eval (pairSampler.runClosedConfigFrom ((frame y ++ r).length + 1) + (pairSampler.initialConfig (frame y ++ r)))).support, final.tapes.state = none := by + intro final hfinal + rw [eval_runClosedConfig_pairSampler] at hfinal + obtain ⟨output, _, rfl⟩ := (PMF.mem_support_map_iff _ _ _).mp hfinal + rfl + +end Turing.MultiTapePTM diff --git a/ComplexityTests/NondeterministicTimeVerifiers.lean b/ComplexityTests/NondeterministicTimeVerifiers.lean new file mode 100644 index 0000000000..995a36357f --- /dev/null +++ b/ComplexityTests/NondeterministicTimeVerifiers.lean @@ -0,0 +1,85 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Computability.Complexity.Classes.Time.Verifier +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Quantitative +import Complexity.Computability.Machines.Turing.MultiTape.Internal.OCombinators +import Complexity.Computability.Machines.Turing.MultiTape.Probabilistic.PairSampler + +/-! The explicit-time verifier characterization of `NTIME` on concrete bounds. A deterministic +linear-time decider is a verifier with empty certificates, and a linear-time nondeterministic +problem survives the round trip through its linear-time verifier and guess and verify, at the +same linear bound. The sampler's output distribution is checked on a small input. -/ + +public section + +namespace ComplexityTests.NondeterministicTimeVerifiers + +open Cslib Cslib.Complexity Turing Turing.MultiTapeTM + +/-- `c * n + c` in unary, within linear time and space. -/ +private theorem computableInO_linear_unary (c : ℕ) : + ComputableInO (fun x : List Bool => c * x.length + c) encBits encUnary + (fun x => x.length) (fun x => x.length) := by + induction c with + | zero => + simpa using ComputableInO.const (encIn := encBits) (encMid := encUnary) 0 + | succ c ih => + have hstep := ComputableInO.lengthUnary.append + (ComputableInO.const (encIn := encBits) (encMid := encUnary) 1) + (g := fun x : List Bool => x.length + 1) (encOut := encUnary) + fun x => by simp [encUnary_apply, List.replicate_add] + have h := ih.append hstep (g := fun x : List Bool => (c + 1) * x.length + (c + 1)) + (encOut := encUnary) fun x => by + simp only [encUnary_apply, ← List.replicate_add] + congr 1 + ring + exact h.mono 3 (fun x => by simp only [encBits_apply]; omega) + (fun x => by simp only [encBits_apply]; omega) + +/-- Guess and verify with empty certificates recovers deterministic linear time inside +nondeterministic linear time. -/ +example {A : Set (List Bool)} (hA : A ∈ DTIME encBits fun n => n) : + A ∈ NTIME encBits fun n => n := by + have hV := (computableInO_indicator_of_mem_DTIME hA).comp (ComputableInO.fst encBits encBits) + have h := setOf_exists_mem_NTIME (ℓ := fun _ => 0) (t := fun n => n) (C := 6) + (ComputableInO.const (encIn := encBits) (encMid := encUnary) 0) hV fun x w hw => by + simp only [List.length_eq_zero_iff] at hw + subst hw + simp only [encBits_apply, encPair_apply, List.length_append, length_frame, + List.length_nil] + omega + have hset : {x : List Bool | ∃ w : List Bool, w.length = 0 ∧ + (indicator A ∘ Prod.fst) (x, w) = true} = A := by + ext x + simp [indicator] + rwa [hset] at h + +/-- **Round trip at linear time.** A problem in nondeterministic linear time has a linear-time +verifier, and guessing certificates of length `c * n + c` for it decides the problem in +nondeterministic linear time again. Both explicit-time hypotheses are satisfiable together. -/ +theorem mem_NTIME_linear_of_verifier_roundTrip {A : Set (List Bool)} + (hA : A ∈ NTIME encBits fun n => n) : A ∈ NTIME encBits fun n => n := by + obtain ⟨V, c, hV, hmem⟩ := exists_verifier_of_mem_NTIME hA + have h := setOf_exists_mem_NTIME (ℓ := fun x : List Bool => c * x.length + c) + (t := fun n => n) (C := c + 4) + (computableInO_linear_unary c) hV fun x w hw => by + simp only [encBits_apply, encPair_apply, List.length_append, length_frame, hw] + nlinarith + have hset : {x : List Bool | ∃ w : List Bool, w.length = c * x.length + c ∧ + V (x, w) = true} = A := by + ext x + exact (hmem x _ le_rfl).symm + rwa [hset] at h + +/-- The sampler copies the framed word `[true]` and guesses a two-bit certificate. -/ +example : ProbComp.eval (MultiTapePTM.pairSampler.runClosed 6 (frame [true] ++ [false, false])) = + (BitString.uniformList 2).map (frame [true] ++ ·) := by + simpa using MultiTapePTM.eval_pairSampler [true] [false, false] + +end ComplexityTests.NondeterministicTimeVerifiers From 4db81eeb7a4dc0c3dbf0f686adb8cb2f132f7178 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:33:40 +0000 Subject: [PATCH 11/16] feat(Frontier/TensorNetwork): leaf certificate and leaf bound for any entry procedure (#2047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(Frontier/TensorNetwork): make the contraction recursion generic in the root descriptor type `VertexDesc T := T ⊕ PacketDesc`, read through an entry procedure `ev : T → List Bool → List ℕ`, and `Call T`, `scheme ev mk`, `value ev mk`, `StepCorrect ev mk WF` and the polynomial-time and size modules over any `T`. Explicit tables are `T = List ℕ` with `tableEntry`; the leaf machine and `CountingSimulation.Fast*` stay at tables, so `fastContractionAlgorithm` and the #1995 containments keep their statements. First PR of #1538 milestone E1.2. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * feat(Frontier/TensorNetwork): leaf certificate and leaf bound for any entry procedure `VertexDesc.entryCertificate ce` evaluates every descriptor from a native certificate `ce` of the roots' entry procedure (option on `getLeft?`), and `entryCertificate_le` adds the root's time and space. `leafCertificate ce`, `leafCertificate_le ce` (time `K (n + R + ES + 1) ^ K 2 ^ M ((R + 2) ^ Λ + E)`) and `leafCertificate_le_of_packetShape ce` follow; tables are `tableEntryCertificate`, whose polynomial cost gives back the previous table bound. Second PR of #1538 milestone E1.2. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(Frontier/TensorNetwork): drop the table term from the leaf's reduced-list helper Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * fix(Frontier/TensorNetwork): name the components of the root entry input Address the review of #2047 at 21015c239: `rootInput_polyTime` and the body of `entryCertificate` bind `((_, bs), t)` by name instead of reaching into `p.1.2`. Both forms are definitionally equal, so the certificate, its `congr` proofs and the resource bound are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --------- Co-authored-by: Claude Opus 5.5 --- .../TensorNetwork/FastContractionLeaf.lean | 436 +++++++++++------- .../TensorNetwork/FastContractionMachine.lean | 60 ++- .../Frontier/TensorNetwork/VertexDesc.lean | 346 ++++++++------ .../CountingSimulation/FastAlgorithm.lean | 5 +- .../CountingSimulation/FastAnalysis.lean | 4 +- .../Frontier/TensorNetworkVertexDesc.lean | 28 +- 6 files changed, 529 insertions(+), 350 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionLeaf.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionLeaf.lean index 46dd54e3ec..4fba5a66f5 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionLeaf.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionLeaf.lean @@ -17,17 +17,22 @@ import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length # The leaves of the fast contraction algorithm The cost of the leaves of the native recursion of Theorem D.1 of [OpenAI2026ETH] -(`Cslib.Circuits.Frontier.FastContraction.leafCertificate`, over tables), the leaf clause of the -resource analysis `Cslib.Complexity.CountingSimulation.FastAnalysis`. A leaf sums directly over the -`2 ^ M` edge assignments of a call with `M` edges (§D.4.7, p. 64), and the entry of a packet vertex -costs `(m + 2) ^ L` for `m` target modes and `L` row ports, the source's `exp(O(L log s))` -(§D.4.6, p. 63). +(`Cslib.Circuits.Frontier.FastContraction.leafCertificate ce`, for a native certificate `ce` of the +entry procedure of the roots), the leaf clause of the resource analysis +`Cslib.Complexity.CountingSimulation.FastAnalysis`. A leaf sums directly over the `2 ^ M` edge +assignments of a call with `M` edges, with polynomially many entry evaluations each (§D.4.7, +p. 64). The entry of a packet vertex costs `(m + 2) ^ L` for `m` target modes and `L` row ports, +the source's `exp(O(L log s))` (§D.4.6, p. 63), and the entry of a root costs what `ce` costs. **The cost of a leaf** (`FastContraction.leafCertificate_le`): with at most `R` target modes and -`Λ` row ports per vertex, time `K (n + R + 1) ^ K 2 ^ M (R + 2) ^ Λ` and space `K (n + R + 1) ^ K` -on a call of encoded length `n`, from the bounds of direct summation +`Λ` row ports per vertex, and roots costing time at most `E` and space at most `ES` in `ce`, time +`K (n + R + ES + 1) ^ K 2 ^ M ((R + 2) ^ Λ + E)` and space `K (n + R + ES + 1) ^ K` on a call of +encoded length `n`, from the bounds of direct summation (`DescribedNetwork.contractionCertificate_le`) and of the evaluator of descriptors -(`VertexDesc.entryCertificate_le`). +(`VertexDesc.entryCertificate_le`). A root's output is at most a constant times `ES + 1` long, +since a native certificate charges its output to its space bound. For tables the root costs are +polynomial, which gives time `K (n + 1) ^ K 2 ^ M (A p + 2) ^ A` with the packet shape +(`FastContraction.leafCertificate_tableEntry_le_of_packetShape`). **The packet shape** (`FastContraction.PacketShape`). Every packet vertex of a call of the recursion has `i < a ≤ ⌊log₂ s⌋ / 32768`, `2 ^ (i + 1)` tiles, at most `p` exponents per tile, and @@ -54,8 +59,9 @@ but a polynomial in `n` is a constant. * `Cslib.Circuits.Frontier.FastContraction.packetShape_root`, `Cslib.Circuits.Frontier.FastContraction.packetShape_child`: the packet shape on the reached calls. -* `Cslib.Circuits.Frontier.FastContraction.leafCertificate_le_of_packetShape`: the cost of a leaf - with the packet shape. +* `Cslib.Circuits.Frontier.FastContraction.leafCertificate_le_of_packetShape`, + `Cslib.Circuits.Frontier.FastContraction.leafCertificate_tableEntry_le_of_packetShape`: the cost + of a leaf with the packet shape, and over tables. * `Cslib.Circuits.Frontier.FastContraction.exists_leafCertificate_le`: the leaf bound of the resource analysis. @@ -136,10 +142,9 @@ private theorem mul_pow_le_mul_pow {c W d E : ℕ} (hW : 1 ≤ W) (hd : d ≤ E) Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hW hd) /-- The reduced coefficient lists against `W`. -/ -private theorem rv_le {n W cf df cp dp ct dt : ℕ} (hnW : n + 1 ≤ W) : - n * (2 * n + 3) + cf * (n + 1) ^ df * (2 * (cp * (n + 1) ^ dp) + 3) + - ct * (3 * n + 2) ^ dt ≤ - (5 + cf * (2 * cp + 3) + ct * 3 ^ dt) * W ^ (2 + df + dp + dt) := by +private theorem rv_le {n W cf df cp dp : ℕ} (hnW : n + 1 ≤ W) : + n * (2 * n + 3) + cf * (n + 1) ^ df * (2 * (cp * (n + 1) ^ dp) + 3) ≤ + (5 + cf * (2 * cp + 3)) * W ^ (2 + df + dp) := by have hW1 : 1 ≤ W := by omega have h1 : n * (2 * n + 3) ≤ 5 * W ^ 2 := calc n * (2 * n + 3) ≤ W * (5 * W) := Nat.mul_le_mul (by omega) (by omega) @@ -155,16 +160,10 @@ private theorem rv_le {n W cf df cp dp ct dt : ℕ} (hnW : n + 1 ≤ W) : ≤ cf * W ^ df * ((2 * cp + 3) * W ^ dp) := Nat.mul_le_mul (Nat.mul_le_mul_left _ hb1) this _ = cf * (2 * cp + 3) * W ^ (df + dp) := by rw [pow_add]; ring - have h3 : ct * (3 * n + 2) ^ dt ≤ ct * 3 ^ dt * W ^ dt := - calc ct * (3 * n + 2) ^ dt ≤ ct * (3 * W) ^ dt := - Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _) - _ = ct * 3 ^ dt * W ^ dt := by rw [mul_pow]; ring - have l1 : 5 * W ^ 2 ≤ 5 * W ^ (2 + df + dp + dt) := mul_pow_le_mul_pow hW1 (by omega) - have l2 : cf * (2 * cp + 3) * W ^ (df + dp) ≤ cf * (2 * cp + 3) * W ^ (2 + df + dp + dt) := + have l1 : 5 * W ^ 2 ≤ 5 * W ^ (2 + df + dp) := mul_pow_le_mul_pow hW1 (by omega) + have l2 : cf * (2 * cp + 3) * W ^ (df + dp) ≤ cf * (2 * cp + 3) * W ^ (2 + df + dp) := mul_pow_le_mul_pow hW1 (by omega) - have l3 : ct * 3 ^ dt * W ^ dt ≤ ct * 3 ^ dt * W ^ (2 + df + dp + dt) := - mul_pow_le_mul_pow hW1 (by omega) - rw [add_mul, add_mul] + rw [add_mul] omega /-- The input of direct summation against `W`. -/ @@ -185,18 +184,21 @@ private theorem zb_le {W n R Rv cR E₀ : ℕ} (hW : 1 ≤ W) (hE : 1 ≤ E₀) omega /-- The arithmetic of the time of a leaf. -/ -private theorem time_assemble {Td Ti Ts cd dd ci di P X k W A Zb c₁ c₂ E₀ K₁ K₂ : ℕ} +private theorem time_assemble {Td Ti Ts cd dd ci di P X E k W A Zb c₁ c₂ E₀ K₁ K₂ : ℕ} (hW : 1 ≤ W) (hP : 1 ≤ P) (hX : 1 ≤ X) (hd : Td ≤ cd * W ^ dd) (hi : Ti ≤ ci * W ^ di) - (hs : Ts ≤ (P * (k + 1) + 1) * (K₁ * (A + 1) ^ K₁ + 2 * (K₂ * X * Zb ^ K₂))) + (hs : Ts ≤ (P * (k + 1) + 1) * (K₁ * (A + 1) ^ K₁ + 2 * (K₂ * X * Zb ^ K₂ + E))) (hk : k + 1 ≤ W) (hA : A + 1 ≤ c₁ * W ^ E₀) (hZ : Zb ≤ c₂ * W ^ E₀) : - Td + (Ti + Ts) ≤ (2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) + cd + ci) * - W ^ (1 + E₀ * (K₁ + K₂) + dd + di) * P * X := by + Td + (Ti + Ts) ≤ (2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) + cd + ci) * + W ^ (1 + E₀ * (K₁ + K₂) + dd + di) * P * (X + E) := by have hWF1 : W ^ (E₀ * K₁) ≤ W ^ (E₀ * (K₁ + K₂)) := Nat.pow_le_pow_right hW (by rw [Nat.mul_add]; omega) have hWF2 : W ^ (E₀ * K₂) ≤ W ^ (E₀ * (K₁ + K₂)) := Nat.pow_le_pow_right hW (by rw [Nat.mul_add]; omega) set F := E₀ * (K₁ + K₂) set S := W ^ (1 + F + dd + di) + set Y := X + E with hY + have hY1 : 1 ≤ Y := by omega + have hF1 : 1 ≤ W ^ F := Nat.one_le_pow _ _ (by omega) have hA' : K₁ * (A + 1) ^ K₁ ≤ K₁ * c₁ ^ K₁ * W ^ F := calc K₁ * (A + 1) ^ K₁ ≤ K₁ * (c₁ * W ^ E₀) ^ K₁ := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hA _) @@ -207,124 +209,138 @@ private theorem time_assemble {Td Ti Ts cd dd ci di P X k W A Zb c₁ c₂ E₀ Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hZ _) _ = K₂ * c₂ ^ K₂ * X * W ^ (E₀ * K₂) := by rw [mul_pow, ← pow_mul]; ring _ ≤ K₂ * c₂ ^ K₂ * X * W ^ F := Nat.mul_le_mul_left _ hWF2 - have hin : K₁ * (A + 1) ^ K₁ + 2 * (K₂ * X * Zb ^ K₂) ≤ - (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * X * W ^ F := by - have : K₁ * c₁ ^ K₁ * W ^ F ≤ K₁ * c₁ ^ K₁ * W ^ F * X := Nat.le_mul_of_pos_right _ hX - have e : (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * X * W ^ F = - K₁ * c₁ ^ K₁ * W ^ F * X + 2 * (K₂ * c₂ ^ K₂ * X * W ^ F) := by ring + have hin : K₁ * (A + 1) ^ K₁ + 2 * (K₂ * X * Zb ^ K₂ + E) ≤ + (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * Y * W ^ F := by + have h1 : K₁ * c₁ ^ K₁ * W ^ F ≤ K₁ * c₁ ^ K₁ * W ^ F * Y := Nat.le_mul_of_pos_right _ hY1 + have h2 : K₂ * c₂ ^ K₂ * X * W ^ F ≤ K₂ * c₂ ^ K₂ * Y * W ^ F := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (by omega)) + have h3 : E ≤ Y * W ^ F := (show E ≤ Y by omega).trans (Nat.le_mul_of_pos_right _ hF1) + have e : (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * Y * W ^ F = + K₁ * c₁ ^ K₁ * W ^ F * Y + 2 * (K₂ * c₂ ^ K₂ * Y * W ^ F) + 2 * (Y * W ^ F) := by ring omega have hPk : P * (k + 1) + 1 ≤ 2 * P * W := by have : P * (k + 1) ≤ P * W := Nat.mul_le_mul_left _ hk have : 1 ≤ P * W := Nat.one_le_iff_ne_zero.mpr (by positivity) rw [Nat.mul_assoc]; omega - have hs' : Ts ≤ 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * S * P * X := - calc Ts ≤ (2 * P * W) * ((K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * X * W ^ F) := + have hs' : Ts ≤ 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * S * P * Y := + calc Ts ≤ (2 * P * W) * ((K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * Y * W ^ F) := hs.trans (Nat.mul_le_mul hPk hin) - _ = 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * W ^ (1 + F) * P * X := by + _ = 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * W ^ (1 + F) * P * Y := by rw [pow_add, pow_one]; ring - _ ≤ 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * S * P * X := + _ ≤ 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * S * P * Y := Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hW (by omega)))) - have hPX : 1 ≤ P * X := Nat.one_le_iff_ne_zero.mpr (by positivity) - have hd' : Td ≤ cd * S * P * X := + have hPY : 1 ≤ P * Y := Nat.one_le_iff_ne_zero.mpr (by positivity) + have hd' : Td ≤ cd * S * P * Y := calc Td ≤ cd * S := hd.trans (mul_pow_le_mul_pow hW (by omega)) - _ ≤ cd * S * (P * X) := Nat.le_mul_of_pos_right _ hPX - _ = cd * S * P * X := by ring - have hi' : Ti ≤ ci * S * P * X := + _ ≤ cd * S * (P * Y) := Nat.le_mul_of_pos_right _ hPY + _ = cd * S * P * Y := by ring + have hi' : Ti ≤ ci * S * P * Y := calc Ti ≤ ci * S := hi.trans (mul_pow_le_mul_pow hW (by omega)) - _ ≤ ci * S * (P * X) := Nat.le_mul_of_pos_right _ hPX - _ = ci * S * P * X := by ring - have e : (2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) + cd + ci) * S * P * X = - 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) * S * P * X + cd * S * P * X + ci * S * P * X := by + _ ≤ ci * S * (P * Y) := Nat.le_mul_of_pos_right _ hPY + _ = ci * S * P * Y := by ring + have e : (2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) + cd + ci) * S * P * Y = + 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) * S * P * Y + cd * S * P * Y + + ci * S * P * Y := by ring omega /-- The arithmetic of the space of a leaf. -/ -private theorem space_assemble {Sd Si Ss cd dd ci di W A Zb c₁ c₂ E₀ K₁ K₂ ES : ℕ} +private theorem space_assemble {Sd Si Ss cd dd ci di W A Zb c₁ c₂ E₀ K₁ K₂ ES ES' : ℕ} (hW : 1 ≤ W) (hd : Sd ≤ cd * W ^ dd) (hi : Si ≤ ci * W ^ di) - (hs : Ss ≤ K₁ * (A + 1) ^ K₁ + ES) (hA : A + 1 ≤ c₁ * W ^ E₀) (hES : ES = K₂ * Zb ^ K₂) - (hZ : Zb ≤ c₂ * W ^ E₀) : - Sd + (Si + Ss) ≤ (K₁ * c₁ ^ K₁ + K₂ * c₂ ^ K₂ + cd + ci) * - W ^ (E₀ * (K₁ + K₂) + dd + di) := by - have hWF1 : W ^ (E₀ * K₁) ≤ W ^ (E₀ * (K₁ + K₂) + dd + di) := + (hs : Ss ≤ K₁ * (A + 1) ^ K₁ + ES') (hA : A + 1 ≤ c₁ * W ^ E₀) + (hES : ES' = K₂ * Zb ^ K₂ + ES) (hESW : ES ≤ W) (hZ : Zb ≤ c₂ * W ^ E₀) : + Sd + (Si + Ss) ≤ (K₁ * c₁ ^ K₁ + K₂ * c₂ ^ K₂ + cd + ci + 1) * + W ^ (1 + E₀ * (K₁ + K₂) + dd + di) := by + have hWF1 : W ^ (E₀ * K₁) ≤ W ^ (1 + E₀ * (K₁ + K₂) + dd + di) := Nat.pow_le_pow_right hW (by rw [Nat.mul_add]; omega) - have hWF2 : W ^ (E₀ * K₂) ≤ W ^ (E₀ * (K₁ + K₂) + dd + di) := + have hWF2 : W ^ (E₀ * K₂) ≤ W ^ (1 + E₀ * (K₁ + K₂) + dd + di) := Nat.pow_le_pow_right hW (by rw [Nat.mul_add]; omega) set F := E₀ * (K₁ + K₂) - set S := W ^ (F + dd + di) + set S := W ^ (1 + F + dd + di) have hA' : K₁ * (A + 1) ^ K₁ ≤ K₁ * c₁ ^ K₁ * S := calc K₁ * (A + 1) ^ K₁ ≤ K₁ * (c₁ * W ^ E₀) ^ K₁ := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hA _) _ = K₁ * c₁ ^ K₁ * W ^ (E₀ * K₁) := by rw [mul_pow, ← pow_mul, Nat.mul_assoc] _ ≤ K₁ * c₁ ^ K₁ * S := Nat.mul_le_mul_left _ hWF1 - have hZ' : ES ≤ K₂ * c₂ ^ K₂ * S := - calc ES = K₂ * Zb ^ K₂ := hES - _ ≤ K₂ * (c₂ * W ^ E₀) ^ K₂ := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hZ _) + have hZ' : K₂ * Zb ^ K₂ ≤ K₂ * c₂ ^ K₂ * S := + calc K₂ * Zb ^ K₂ ≤ K₂ * (c₂ * W ^ E₀) ^ K₂ := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hZ _) _ = K₂ * c₂ ^ K₂ * W ^ (E₀ * K₂) := by rw [mul_pow, ← pow_mul, Nat.mul_assoc] _ ≤ K₂ * c₂ ^ K₂ * S := Nat.mul_le_mul_left _ hWF2 + have hWS : ES ≤ S := + hESW.trans ((pow_one W).symm.le.trans (Nat.pow_le_pow_right hW (by omega))) have hd' : Sd ≤ cd * S := hd.trans (mul_pow_le_mul_pow hW (by omega)) have hi' : Si ≤ ci * S := hi.trans (mul_pow_le_mul_pow hW (by omega)) - have e : (K₁ * c₁ ^ K₁ + K₂ * c₂ ^ K₂ + cd + ci) * S = - K₁ * c₁ ^ K₁ * S + K₂ * c₂ ^ K₂ * S + cd * S + ci * S := by ring + have e : (K₁ * c₁ ^ K₁ + K₂ * c₂ ^ K₂ + cd + ci + 1) * S = + K₁ * c₁ ^ K₁ * S + K₂ * c₂ ^ K₂ * S + cd * S + ci * S + S := by ring omega -/-- **The cost of a leaf.** There is `K` such that, on a call of encoded length `n` with `M` edges -whose vertices have at most `R` target modes and at most `Λ` row ports on every bit list on their -ports, direct summation takes time at most `K (n + R + 1) ^ K 2 ^ M (R + 2) ^ Λ` and space at most -`K (n + R + 1) ^ K`, whatever the child builder. -/ -theorem leafCertificate_le : ∃ K : ℕ, - ∀ (mk : Call (List ℕ) → State → ℕ × DescribedNetwork (VertexDesc (List ℕ))) - (x : Call (List ℕ)) (R Λ : ℕ), - (∀ v ∈ x.net.vertices, v.2.modes ≤ R ∧ - ∀ bs : List Bool, bs.length = v.1.length → v.2.rowPorts bs ≤ Λ) → - (leafCertificate mk).time x ≤ - K * ((encCall (encList encNat) x).length + R + 1) ^ K * 2 ^ x.edges * (R + 2) ^ Λ ∧ - (leafCertificate mk).space x ≤ K * ((encCall (encList encNat) x).length + R + 1) ^ K := by +variable {encT : T ↪ List Bool} {ev : T → List Bool → List ℕ} + +/-- **The cost of a leaf.** For a native certificate `ce` of the entry procedure of the roots, there +is `K` such that, on a call of encoded length `n` with `M` edges whose vertices have at most `R` +target modes and at most `Λ` row ports on every bit list on their ports, and whose roots take time +at most `E` and space at most `ES` in `ce` on every bit list on their ports, direct summation takes +time at most `K (n + R + ES + 1) ^ K 2 ^ M ((R + 2) ^ Λ + E)` and space at most +`K (n + R + ES + 1) ^ K`, whatever the child builder. -/ +theorem leafCertificate_le + (ce : NativeCertificate (fun q : T × List Bool => ev q.1 q.2) (encPair encT encBits) + (encList encNat)) : + ∃ K : ℕ, ∀ (mk : Call T → State → ℕ × DescribedNetwork (VertexDesc T)) (x : Call T) + (R Λ E ES : ℕ), + (∀ v ∈ x.net.vertices, v.2.modes ≤ R ∧ + ∀ bs : List Bool, bs.length = v.1.length → v.2.rowPorts bs ≤ Λ) → + (∀ v ∈ x.net.vertices, ∀ t, v.2 = .inl t → ∀ bs : List Bool, bs.length = v.1.length → + ce.time (t, bs) ≤ E ∧ ce.space (t, bs) ≤ ES) → + (leafCertificate ce mk).time x ≤ + K * ((encCall encT x).length + R + ES + 1) ^ K * 2 ^ x.edges * ((R + 2) ^ Λ + E) ∧ + (leafCertificate ce mk).space x ≤ K * ((encCall encT x).length + R + ES + 1) ^ K := by obtain ⟨K₁, hK₁⟩ := DescribedNetwork.contractionCertificate_le (ops := SumOps.galois) - (eval := VertexDesc.entry tableEntry) (encQ := encPair encUnary (encList encNat)) - (encY := encList encNat) (encT := VertexDesc.enc (encList encNat)) SumOps.galois_polyTime - VertexDesc.entryCertificate - obtain ⟨K₂, hK₂⟩ := VertexDesc.entryCertificate_le - have hD := NativeCertificate.ofPolyTime_isPolynomial (depth_polyTime (encList encNat)) - have hI := NativeCertificate.ofPolyTime_isPolynomial (leafInput_polyTime (encList encNat)) + (eval := VertexDesc.entry ev) (encQ := encPair encUnary (encList encNat)) + (encY := encList encNat) (encT := VertexDesc.enc encT) SumOps.galois_polyTime + (VertexDesc.entryCertificate ce) + obtain ⟨K₂, hK₂⟩ := VertexDesc.entryCertificate_le ce + obtain ⟨cE, -, hcE⟩ := ce.computes + have hD := NativeCertificate.ofPolyTime_isPolynomial (depth_polyTime encT) + have hI := NativeCertificate.ofPolyTime_isPolynomial (leafInput_polyTime encT) obtain ⟨cd, dd, hdt⟩ := hD.1.exists_le obtain ⟨cd', dd', hds⟩ := hD.2.exists_le obtain ⟨ci, di, hit⟩ := hI.1.exists_le obtain ⟨ci', di', his⟩ := hI.2.exists_le - obtain ⟨cl, dl, hl⟩ := (leafInput_polyTime (encList encNat)).polyBounded_length.exists_le + obtain ⟨cl, dl, hl⟩ := (leafInput_polyTime encT).polyBounded_length.exists_le obtain ⟨cp, dp, hp⟩ := PacketCtx.p_polyTime.polyBounded_length.exists_le obtain ⟨cf, df, hf⟩ := PacketCtx.fl_polyTime.polyBounded_length.exists_le - obtain ⟨ct, dt, ht⟩ := VertexDesc.tableEntry_polyTime.polyBounded_length.exists_le -- The constants. - obtain ⟨e, he⟩ : ∃ e, e = 2 + df + dp + dt := ⟨_, rfl⟩ - obtain ⟨cR, hcR⟩ : ∃ cR, cR = 5 + cf * (2 * cp + 3) + ct * 3 ^ dt := ⟨_, rfl⟩ + obtain ⟨e, he⟩ : ∃ e, e = 2 + df + dp := ⟨_, rfl⟩ + obtain ⟨cR, hcR⟩ : ∃ cR, cR = 5 + cf * (2 * cp + 3) + cE := ⟨_, rfl⟩ obtain ⟨E₀, hE₀⟩ : ∃ E₀, E₀ = dl + e := ⟨_, rfl⟩ obtain ⟨c₁, hc₁⟩ : ∃ c₁, c₁ = cl + 2 * cR + 1 := ⟨_, rfl⟩ obtain ⟨c₂, hc₂⟩ : ∃ c₂, c₂ = 3 + cR := ⟨_, rfl⟩ - obtain ⟨G, hG⟩ : ∃ G, G = 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂)) + cd + ci + - (K₁ * c₁ ^ K₁ + K₂ * c₂ ^ K₂) + cd' + ci' := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = 2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) + cd + ci + + (K₁ * c₁ ^ K₁ + K₂ * c₂ ^ K₂ + cd' + ci' + 1) := ⟨_, rfl⟩ obtain ⟨Dg, hDg⟩ : ∃ Dg, Dg = 1 + E₀ * (K₁ + K₂) + dd + di + dd' + di' := ⟨_, rfl⟩ - refine ⟨G + Dg, fun mk x R Λ hRΛ => ?_⟩ - obtain ⟨n, hn⟩ : ∃ n, n = (encCall (encList encNat) x).length := ⟨_, rfl⟩ - obtain ⟨W, hW⟩ : ∃ W, W = n + R + 1 := ⟨_, rfl⟩ - rw [← hn, show n + R + 1 = W from hW.symm] + refine ⟨G + Dg, fun mk x R Λ E ES hRΛ hroot => ?_⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encCall encT x).length := ⟨_, rfl⟩ + obtain ⟨W, hW⟩ : ∃ W, W = n + R + ES + 1 := ⟨_, rfl⟩ + rw [← hn, show n + R + ES + 1 = W from hW.symm] have hW1 : 1 ≤ W := by omega have hnW : n + 1 ≤ W := by omega -- The precision and the modulus of the call. - have hpfl := scale_add_prec_add_length_fl_le (encList encNat) x + have hpfl := scale_add_prec_add_length_fl_le encT x rw [← hn] at hpfl -- Every vertex and bit list on its ports is at most `3 n + 1` long. - have hq (v : List ℕ × VertexDesc (List ℕ)) (hv : v ∈ x.net.vertices) (bs : List Bool) + have hq (v : List ℕ × VertexDesc T) (hv : v ∈ x.net.vertices) (bs : List Bool) (hbs : bs.length = v.1.length) : - (encPair (VertexDesc.enc (encList encNat)) encBits (v.2, bs)).length ≤ 3 * n + 1 := by - have h1 := length_vertex_le (encList encNat) hv + (encPair (VertexDesc.enc encT) encBits (v.2, bs)).length ≤ 3 * n + 1 := by + have h1 := length_vertex_le encT hv have h2 := length_le_length_encList encNat v.1 rw [length_encPair] at h1 ⊢ - change 2 * (VertexDesc.enc (encList encNat) v.2).length + 1 + bs.length ≤ 3 * n + 1 + change 2 * (VertexDesc.enc encT v.2).length + 1 + bs.length ≤ 3 * n + 1 omega - -- The reduced coefficient lists. + -- The reduced coefficient lists, and the outputs of the roots. obtain ⟨Rv, hRv⟩ : ∃ Rv, Rv = n * (2 * n + 3) + - cf * (n + 1) ^ df * (2 * (cp * (n + 1) ^ dp) + 3) + ct * (3 * n + 2) ^ dt := ⟨_, rfl⟩ + cf * (n + 1) ^ df * (2 * (cp * (n + 1) ^ dp) + 3) := ⟨_, rfl⟩ + obtain ⟨Ev, hEv⟩ : ∃ Ev, Ev = Rv + cE * (ES + 1) := ⟨_, rfl⟩ have hred : ∀ y, SumOps.galois.Reduced (x.prec, x.fl) y → (encList encNat y).length ≤ Rv := by intro y hy have h := SumOps.length_encList_le_of_galois_reduced hy @@ -332,13 +348,13 @@ theorem leafCertificate_le : ∃ K : ℕ, have : x.fl.length * (2 * x.prec + 3) ≤ n * (2 * n + 3) := Nat.mul_le_mul (by omega) (by omega) omega - have hpacket (v : List ℕ × VertexDesc (List ℕ)) (hv : v ∈ x.net.vertices) (d : PacketDesc) + have hpacket (v : List ℕ × VertexDesc T) (hv : v ∈ x.net.vertices) (d : PacketDesc) (hd : v.2 = .inr d) : ∀ y, SumOps.galois.Reduced (d.1.p, d.1.fl) y → (encList encNat y).length ≤ Rv := by intro y hy have h := SumOps.length_encList_le_of_galois_reduced hy simp only at h - have h1 := length_vertex_le (encList encNat) hv + have h1 := length_vertex_le encT hv rw [length_encPair, hd, length_encSum] at h1 simp only [Sum.elim_inr] at h1 have h2 : (PacketCtx.enc d.1).length ≤ n := by @@ -359,9 +375,9 @@ theorem leafCertificate_le : ∃ K : ℕ, obtain ⟨Zb, hZb⟩ : ∃ Zb, Zb = 3 * n + R + Rv + 3 := ⟨_, rfl⟩ have hentry : ∀ v ∈ ((x.prec, x.fl), x.net).2.vertices, ∀ bs : List Bool, bs.length = v.1.length → - VertexDesc.entryCertificate.time (v.2, bs) ≤ K₂ * (R + 2) ^ Λ * Zb ^ K₂ ∧ - VertexDesc.entryCertificate.space (v.2, bs) ≤ K₂ * Zb ^ K₂ ∧ - (encList encNat (VertexDesc.entry tableEntry v.2 bs)).length ≤ Rv := by + (VertexDesc.entryCertificate ce).time (v.2, bs) ≤ K₂ * (R + 2) ^ Λ * Zb ^ K₂ + E ∧ + (VertexDesc.entryCertificate ce).space (v.2, bs) ≤ K₂ * Zb ^ K₂ + ES ∧ + (encList encNat (VertexDesc.entry ev v.2 bs)).length ≤ Ev := by intro v hv bs hbs have hlen := hq v hv bs hbs obtain ⟨hmodes, hrows⟩ := hRΛ v hv @@ -370,45 +386,57 @@ theorem leafCertificate_le : ∃ K : ℕ, rcases hd : v.2 with t | d · trivial · exact hpacket v hv d hd - obtain ⟨hT, hS⟩ := hK₂ (v.2, bs) Rv hRLE + obtain ⟨hT, hS⟩ := hK₂ (v.2, bs) Rv E ES hRLE fun t ht => hroot v hv t ht bs hbs dsimp only at hT hS - have hZ : (encPair (VertexDesc.enc (encList encNat)) encBits (v.2, bs)).length + v.2.modes + - Rv + 2 ≤ Zb := by + have hZ : (encPair (VertexDesc.enc encT) encBits (v.2, bs)).length + v.2.modes + Rv + 2 ≤ + Zb := by omega - refine ⟨hT.trans ?_, hS.trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hZ _)), ?_⟩ + refine ⟨hT.trans (Nat.add_le_add_right ?_ _), + hS.trans (Nat.add_le_add_right (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hZ _)) _), ?_⟩ · refine Nat.mul_le_mul (Nat.mul_le_mul_left _ ?_) (Nat.pow_le_pow_left hZ _) exact (Nat.pow_le_pow_left (by omega) _).trans (Nat.pow_le_pow_right (by omega) hrows) · rcases hd : v.2 with t | d - · have := ht (v.2, bs) - rw [hd] at this - refine this.trans ?_ - have h3 : (encPair (VertexDesc.enc (encList encNat)) encBits (Sum.inl t, bs)).length + 1 ≤ - 3 * n + 2 := by - rw [← hd]; omega - calc ct * ((encPair (VertexDesc.enc (encList encNat)) encBits (Sum.inl t, bs)).length - + 1) ^ dt - ≤ ct * (3 * n + 2) ^ dt := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left h3 _) - _ ≤ Rv := by omega - · exact hpacket v hv d hd _ (VertexDesc.reduced_entry_inr tableEntry d bs) - obtain ⟨hsT, hsS⟩ := hK₁ ((x.prec, x.fl), x.net) (K₂ * (R + 2) ^ Λ * Zb ^ K₂) (K₂ * Zb ^ K₂) Rv - Rv hentry hred + · have h1 : (encList encNat (ev t bs)).length ≤ cE * (ce.space (t, bs) + 1) := hcE (t, bs) + have h2 : cE * (ce.space (t, bs) + 1) ≤ cE * (ES + 1) := + Nat.mul_le_mul_left _ (by have := (hroot v hv t hd bs hbs).2; omega) + simp only [VertexDesc.entry_inl] + omega + · have := hpacket v hv d hd _ (VertexDesc.reduced_entry_inr ev d bs) + omega + obtain ⟨hsT, hsS⟩ := hK₁ ((x.prec, x.fl), x.net) (K₂ * (R + 2) ^ Λ * Zb ^ K₂ + E) + (K₂ * Zb ^ K₂ + ES) Ev Rv hentry hred -- Polynomial pieces against `W`. - have hk : x.net.vertices.length + 1 ≤ W := by have := length_vertices_le (encList encNat) x; omega - have hRvW : Rv ≤ cR * W ^ e := by - rw [hRv, hcR, he]; exact rv_le hnW + have hk : x.net.vertices.length + 1 ≤ W := by have := length_vertices_le encT x; omega + have hRvW : Rv ≤ (cR - cE) * W ^ e := by + have h := rv_le (cf := cf) (df := df) (cp := cp) (dp := dp) hnW + rw [hRv, he] + refine h.trans (Nat.mul_le_mul_right _ ?_) + omega have hWE : W ^ e ≤ W ^ E₀ := Nat.pow_le_pow_right hW1 (by omega) + have hW1E : W ≤ W ^ E₀ := + (pow_one W).symm.le.trans (Nat.pow_le_pow_right hW1 (by omega)) + have hEvW : Ev ≤ cR * W ^ E₀ := by + have h1 : Rv ≤ (cR - cE) * W ^ E₀ := hRvW.trans (Nat.mul_le_mul_left _ hWE) + have h2 : cE * (ES + 1) ≤ cE * W ^ E₀ := Nat.mul_le_mul_left _ (by omega) + have e : cR * W ^ E₀ = (cR - cE) * W ^ E₀ + cE * W ^ E₀ := by + rw [← Nat.add_mul, Nat.sub_add_cancel (by omega)] + omega have hA1 : (DescribedNetwork.encInput (encPair encUnary (encList encNat)) - (VertexDesc.enc (encList encNat)) ((x.prec, x.fl), x.net)).length + Rv + Rv + 1 ≤ - c₁ * W ^ E₀ := by + (VertexDesc.enc encT) ((x.prec, x.fl), x.net)).length + Ev + Rv + 1 ≤ c₁ * W ^ E₀ := by have h1 : (DescribedNetwork.encInput (encPair encUnary (encList encNat)) - (VertexDesc.enc (encList encNat)) ((x.prec, x.fl), x.net)).length ≤ cl * W ^ dl := + (VertexDesc.enc encT) ((x.prec, x.fl), x.net)).length ≤ cl * W ^ dl := (hl x).trans (by rw [← hn]; exact mul_pow_le_of_le hnW) + have h2 : Rv ≤ Ev := by omega + have h3 := sum_le_of_le hW1 h1 (by omega) hEvW rw [hc₁] - exact sum_le_of_le hW1 h1 (by omega) (hRvW.trans (Nat.mul_le_mul_left _ hWE)) + omega have hZW : Zb ≤ c₂ * W ^ E₀ := by rw [hc₂, hZb] - exact zb_le hW1 (by omega) (hRvW.trans (Nat.mul_le_mul_left _ hWE)) (by omega) - have hnW' : (encCall (encList encNat) x).length + 1 ≤ W := by omega + have h1 : Rv ≤ cR * W ^ E₀ := by + have := hRvW.trans (Nat.mul_le_mul_left _ hWE) + exact this.trans (Nat.mul_le_mul_right _ (Nat.sub_le _ _)) + exact zb_le hW1 (by omega) h1 (by omega) + have hnW' : (encCall encT x).length + 1 ≤ W := by omega have hdt' := (hdt x).trans (mul_pow_le_of_le (c := cd) (d := dd) hnW') have hit' := (hit x).trans (mul_pow_le_of_le (c := ci) (d := di) hnW') have hds' := (hds x).trans (mul_pow_le_of_le (c := cd') (d := dd') hnW') @@ -417,11 +445,16 @@ theorem leafCertificate_le : ∃ K : ℕ, have hX : 1 ≤ (R + 2) ^ Λ := Nat.one_le_pow _ _ (by omega) refine ⟨?_, ?_⟩ · rw [leafCertificate_time] - refine (time_assemble hW1 hP hX hdt' hit' hsT hk hA1 hZW).trans ?_ - refine Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (Nat.mul_le_mul (by omega) - (Nat.pow_le_pow_right hW1 (by omega)))) + refine (time_assemble hW1 hP hX hdt' hit' hsT hk (by omega) hZW).trans ?_ + have hpow : W ^ (1 + E₀ * (K₁ + K₂) + dd + di) ≤ W ^ (G + Dg) := + Nat.pow_le_pow_right hW1 (by omega) + calc _ = (2 * (K₁ * c₁ ^ K₁ + 2 * (K₂ * c₂ ^ K₂) + 2) + cd + ci) * + W ^ (1 + E₀ * (K₁ + K₂) + dd + di) * (2 ^ x.edges * ((R + 2) ^ Λ + E)) := by ring + _ ≤ (G + Dg) * W ^ (G + Dg) * (2 ^ x.edges * ((R + 2) ^ Λ + E)) := + Nat.mul_le_mul_right _ (Nat.mul_le_mul (by omega) hpow) + _ = _ := by ring · rw [leafCertificate_space] - refine (space_assemble hW1 hds' his' hsS hA1 rfl hZW).trans ?_ + refine (space_assemble hW1 hds' his' hsS (by omega) rfl (by omega) hZW).trans ?_ exact Nat.mul_le_mul (by omega) (Nat.pow_le_pow_right hW1 (by omega)) /-! ### The shape of the packets -/ @@ -536,40 +569,118 @@ theorem rowPorts_le_of_packetShape {x : Call T} (hs : PacketShape x) {v : List rw [hbeq] exact hcard.trans (Nat.pow_le_pow_right (by norm_num) h2) -/-- **The cost of a leaf with the packet shape.** There is `K` such that, on a call of encoded -length `n` with `M` edges, scale `s` and precision `p` with the packet shape, writing -`A = 2 ^ (⌊log₂ s⌋ / 32768)`, direct summation takes time at most -`K (n + 1) ^ K 2 ^ M (A p + 2) ^ A` and space at most `K (n + 1) ^ K`. -/ -theorem leafCertificate_le_of_packetShape : ∃ K : ℕ, - ∀ (mk : Call (List ℕ) → State → ℕ × DescribedNetwork (VertexDesc (List ℕ))) - (x : Call (List ℕ)), PacketShape x → - (leafCertificate mk).time x ≤ K * ((encCall (encList encNat) x).length + 1) ^ K * - 2 ^ x.edges * - (2 ^ (Nat.log 2 x.scale / 32768) * x.prec + 2) ^ 2 ^ (Nat.log 2 x.scale / 32768) ∧ - (leafCertificate mk).space x ≤ K * ((encCall (encList encNat) x).length + 1) ^ K := by - obtain ⟨K, hK⟩ := leafCertificate_le - refine ⟨2 * K, fun mk x hs => ?_⟩ +/-- **The cost of a leaf with the packet shape.** For a native certificate `ce` of the entry +procedure of the roots, there is `K` such that, on a call of encoded length `n` with `M` edges, +scale `s` and precision `p` with the packet shape, whose roots take time at most `E` and space at +most `ES` in `ce` on every bit list on their ports, writing `A = 2 ^ (⌊log₂ s⌋ / 32768)`, direct +summation takes time at most `K (n + ES + 1) ^ K 2 ^ M ((A p + 2) ^ A + E)` and space at most +`K (n + ES + 1) ^ K`. -/ +theorem leafCertificate_le_of_packetShape + (ce : NativeCertificate (fun q : T × List Bool => ev q.1 q.2) (encPair encT encBits) + (encList encNat)) : + ∃ K : ℕ, ∀ (mk : Call T → State → ℕ × DescribedNetwork (VertexDesc T)) (x : Call T) + (E ES : ℕ), PacketShape x → + (∀ v ∈ x.net.vertices, ∀ t, v.2 = .inl t → ∀ bs : List Bool, bs.length = v.1.length → + ce.time (t, bs) ≤ E ∧ ce.space (t, bs) ≤ ES) → + (leafCertificate ce mk).time x ≤ K * ((encCall encT x).length + ES + 1) ^ K * 2 ^ x.edges * + ((2 ^ (Nat.log 2 x.scale / 32768) * x.prec + 2) ^ 2 ^ (Nat.log 2 x.scale / 32768) + E) ∧ + (leafCertificate ce mk).space x ≤ K * ((encCall encT x).length + ES + 1) ^ K := by + obtain ⟨K, hK⟩ := leafCertificate_le ce + refine ⟨2 * K, fun mk x E ES hs hroot => ?_⟩ obtain ⟨hT, hS⟩ := hK mk x (2 ^ (Nat.log 2 x.scale / 32768) * x.prec) - (2 ^ (Nat.log 2 x.scale / 32768)) fun v hv => - ⟨modes_le_of_packetShape hs hv, fun bs _ => rowPorts_le_of_packetShape hs hv bs⟩ - have hn := scale_add_prec_add_length_fl_le (encList encNat) x + (2 ^ (Nat.log 2 x.scale / 32768)) E ES (fun v hv => + ⟨modes_le_of_packetShape hs hv, fun bs _ => rowPorts_le_of_packetShape hs hv bs⟩) hroot + have hn := scale_add_prec_add_length_fl_le encT x have hA := two_pow_log_div_le x.scale - have hR : (encCall (encList encNat) x).length + 2 ^ (Nat.log 2 x.scale / 32768) * x.prec + 1 ≤ - ((encCall (encList encNat) x).length + 1) ^ 2 := by - have : 2 ^ (Nat.log 2 x.scale / 32768) * x.prec ≤ - ((encCall (encList encNat) x).length + 1) * (encCall (encList encNat) x).length := + set N := (encCall encT x).length + ES with hN + have hR : (encCall encT x).length + 2 ^ (Nat.log 2 x.scale / 32768) * x.prec + ES + 1 ≤ + (N + 1) ^ 2 := by + have : 2 ^ (Nat.log 2 x.scale / 32768) * x.prec ≤ (N + 1) * N := Nat.mul_le_mul (by omega) (by omega) nlinarith - have hpow : - K * ((encCall (encList encNat) x).length + 2 ^ (Nat.log 2 x.scale / 32768) * x.prec + 1) ^ K ≤ - 2 * K * ((encCall (encList encNat) x).length + 1) ^ (2 * K) := - calc _ ≤ K * (((encCall (encList encNat) x).length + 1) ^ 2) ^ K := - Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hR _) - _ = K * ((encCall (encList encNat) x).length + 1) ^ (2 * K) := by rw [← pow_mul] - _ ≤ 2 * K * ((encCall (encList encNat) x).length + 1) ^ (2 * K) := - Nat.mul_le_mul_right _ (by omega) + have hpow : K * ((encCall encT x).length + 2 ^ (Nat.log 2 x.scale / 32768) * x.prec + ES + + 1) ^ K ≤ 2 * K * (N + 1) ^ (2 * K) := + calc _ ≤ K * ((N + 1) ^ 2) ^ K := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hR _) + _ = K * (N + 1) ^ (2 * K) := by rw [← pow_mul] + _ ≤ 2 * K * (N + 1) ^ (2 * K) := Nat.mul_le_mul_right _ (by omega) exact ⟨hT.trans (Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ hpow)), hS.trans hpow⟩ +/-- **The cost of a leaf over tables with the packet shape.** The entry procedure of tables is +polynomial, so there is `K` such that, on a call over tables of encoded length `n` with `M` edges, +scale `s` and precision `p` with the packet shape, writing `A = 2 ^ (⌊log₂ s⌋ / 32768)`, direct +summation takes time at most `K (n + 1) ^ K 2 ^ M (A p + 2) ^ A` and space at most +`K (n + 1) ^ K`. -/ +theorem leafCertificate_tableEntry_le_of_packetShape : ∃ K : ℕ, + ∀ (mk : Call (List ℕ) → State → ℕ × DescribedNetwork (VertexDesc (List ℕ))) + (x : Call (List ℕ)), PacketShape x → + (leafCertificate tableEntryCertificate mk).time x ≤ + K * ((encCall (encList encNat) x).length + 1) ^ K * 2 ^ x.edges * + (2 ^ (Nat.log 2 x.scale / 32768) * x.prec + 2) ^ 2 ^ (Nat.log 2 x.scale / 32768) ∧ + (leafCertificate tableEntryCertificate mk).space x ≤ + K * ((encCall (encList encNat) x).length + 1) ^ K := by + obtain ⟨K, hK⟩ := leafCertificate_le_of_packetShape tableEntryCertificate + obtain ⟨ct, dt, ht⟩ := tableEntryCertificate_isPolynomial.1.exists_le + obtain ⟨cs, ds, hs⟩ := tableEntryCertificate_isPolynomial.2.exists_le + obtain ⟨c₁, hc₁⟩ : ∃ c₁, c₁ = 1 + cs * 3 ^ ds := ⟨_, rfl⟩ + obtain ⟨c₂, hc₂⟩ : ∃ c₂, c₂ = 1 + ct * 3 ^ dt := ⟨_, rfl⟩ + refine ⟨K * c₁ ^ K * c₂ + (ds + 1) * K + dt, fun mk x hshape => ?_⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encCall (encList encNat) x).length := ⟨_, rfl⟩ + rw [← hn] + -- A root and the bits on its ports are at most `3 n` long. + have hlen : ∀ v ∈ x.net.vertices, ∀ t, v.2 = .inl t → ∀ bs : List Bool, + bs.length = v.1.length → + (encPair (encList encNat) encBits (t, bs)).length + 1 ≤ 3 * (n + 1) := by + intro v hv t hvt bs hbs + have h1 := length_vertex_le (encList encNat) hv + have h2 := length_le_length_encList encNat v.1 + rw [← hn, length_encPair, hvt, length_encSum] at h1 + rw [length_encPair] + change 2 * (encList encNat t).length + 1 + bs.length + 1 ≤ 3 * (n + 1) + simp only [Sum.elim_inl] at h1 + omega + have hpow3 {c d : ℕ} : c * (3 * (n + 1)) ^ d = c * 3 ^ d * (n + 1) ^ d := by + rw [mul_pow]; ring + obtain ⟨hT, hS⟩ := hK mk x (ct * 3 ^ dt * (n + 1) ^ dt) (cs * 3 ^ ds * (n + 1) ^ ds) hshape + fun v hv t hvt bs hbs => ⟨(ht (t, bs)).trans (by + rw [← hpow3]; exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hlen v hv t hvt bs hbs) _)), + (hs (t, bs)).trans (by + rw [← hpow3]; exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hlen v hv t hvt bs hbs) _))⟩ + rw [← hn] at hT hS + set A := 2 ^ (Nat.log 2 x.scale / 32768) + set X := (A * x.prec + 2) ^ A + set B := n + 1 with hB + have hB1 : 1 ≤ B := by omega + have hX1 : 1 ≤ X := Nat.one_le_pow _ _ (by omega) + -- `n + ES + 1 ≤ c₁ B ^ (ds + 1)` and `X + E ≤ c₂ B ^ dt X`. + have hW : n + cs * 3 ^ ds * B ^ ds + 1 ≤ c₁ * B ^ (ds + 1) := by + have h1 : B ≤ B ^ (ds + 1) := (pow_one B).symm.le.trans (Nat.pow_le_pow_right hB1 (by omega)) + have h2 : cs * 3 ^ ds * B ^ ds ≤ cs * 3 ^ ds * B ^ (ds + 1) := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hB1 (by omega)) + rw [hc₁, add_mul, one_mul] + omega + have hXE : X + ct * 3 ^ dt * B ^ dt ≤ c₂ * B ^ dt * X := by + have h1 : 1 ≤ B ^ dt := Nat.one_le_pow _ _ (by omega) + have h2 : ct * 3 ^ dt * B ^ dt ≤ ct * 3 ^ dt * B ^ dt * X := Nat.le_mul_of_pos_right _ hX1 + have h3 : X ≤ B ^ dt * X := Nat.le_mul_of_pos_left _ h1 + have e : c₂ * B ^ dt * X = B ^ dt * X + ct * 3 ^ dt * B ^ dt * X := by rw [hc₂]; ring + omega + have hWK : K * (n + cs * 3 ^ ds * B ^ ds + 1) ^ K ≤ K * c₁ ^ K * B ^ ((ds + 1) * K) := by + calc _ ≤ K * (c₁ * B ^ (ds + 1)) ^ K := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hW _) + _ = K * c₁ ^ K * B ^ ((ds + 1) * K) := by rw [mul_pow, ← pow_mul]; ring + set K' := K * c₁ ^ K * c₂ + (ds + 1) * K + dt + have hc₂1 : 1 ≤ c₂ := by omega + refine ⟨hT.trans ?_, hS.trans ?_⟩ + · calc _ ≤ K * c₁ ^ K * B ^ ((ds + 1) * K) * 2 ^ x.edges * (c₂ * B ^ dt * X) := + Nat.mul_le_mul (Nat.mul_le_mul_right _ hWK) hXE + _ = K * c₁ ^ K * c₂ * B ^ ((ds + 1) * K + dt) * 2 ^ x.edges * X := by rw [pow_add]; ring + _ ≤ K' * B ^ K' * 2 ^ x.edges * X := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ + (Nat.mul_le_mul (by omega) (Nat.pow_le_pow_right hB1 (by omega)))) + · calc _ ≤ K * c₁ ^ K * B ^ ((ds + 1) * K) := hWK + _ ≤ K' * B ^ K' := Nat.mul_le_mul + ((Nat.le_mul_of_pos_right _ hc₂1).trans (by omega)) + (Nat.pow_le_pow_right hB1 (by omega)) + /-! ### The leaf bound of the analysis -/ /-- `(μ + 1) ^ 2 ≤ 16 · 2 ^ (μ / 2)`. -/ @@ -658,10 +769,11 @@ theorem exists_leafCertificate_le (C c s₀ : ℕ) : ∃ K : ℕ, (x : Call (List ℕ)), 2 ≤ s → s ≤ x.scale → x.scale ≤ C * s → PacketShape x → x.edges ≤ 2 * x.scale → (s₀ ≤ s → x.edges ≤ leafSize x.scale ∧ x.prec ≤ x.scale ^ c) → - (leafCertificate mk).time x ≤ + (leafCertificate tableEntryCertificate mk).time x ≤ K * ((encCall (encList encNat) x).length + 1) ^ K * 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ - (leafCertificate mk).space x ≤ K * ((encCall (encList encNat) x).length + 1) ^ K := by - obtain ⟨K₀, hK₀⟩ := leafCertificate_le_of_packetShape + (leafCertificate tableEntryCertificate mk).space x ≤ + K * ((encCall (encList encNat) x).length + 1) ^ K := by + obtain ⟨K₀, hK₀⟩ := leafCertificate_tableEntry_le_of_packetShape obtain ⟨K₁, hK₁⟩ := exists_packetFactor_le C c obtain ⟨Ks, hKs⟩ : ∃ Ks, Ks = K₀ * 2 ^ (2 * (C * s₀)) * (C * s₀ + 2) ^ (C * s₀) := ⟨_, rfl⟩ refine ⟨Ks + K₀ + C * s₀ + C + K₁, fun mk s x hs hsm hmC hshape hM hbig => ?_⟩ @@ -691,7 +803,8 @@ theorem exists_leafCertificate_le (C c s₀ : ℕ) : ∃ K : ℕ, _ ≤ ((C * s₀ + 2) * (n + 1)) ^ (C * s₀) := Nat.pow_le_pow_right (Nat.one_le_iff_ne_zero.mpr (by positivity)) (hAm.trans hmC') _ = (C * s₀ + 2) ^ (C * s₀) * (n + 1) ^ (C * s₀) := mul_pow _ _ _ - calc (leafCertificate mk).time x ≤ K₀ * (n + 1) ^ K₀ * 2 ^ x.edges * (A * x.prec + 2) ^ A := hT + calc (leafCertificate tableEntryCertificate mk).time x + ≤ K₀ * (n + 1) ^ K₀ * 2 ^ x.edges * (A * x.prec + 2) ^ A := hT _ ≤ K₀ * (n + 1) ^ K₀ * 2 ^ (2 * (C * s₀)) * ((C * s₀ + 2) ^ (C * s₀) * (n + 1) ^ (C * s₀)) := Nat.mul_le_mul (Nat.mul_le_mul_left _ h2M) hfac @@ -709,7 +822,8 @@ theorem exists_leafCertificate_le (C c s₀ : ℕ) : ∃ K : ℕ, have hCK : (C + K₁) * s ≤ K * s := Nat.mul_le_mul_right _ (by omega) have hexp : C * s / S + K₁ * s / S ≤ K * s / S := Nat.div_add_div_le_add_div.trans (Nat.div_le_div_right (by rw [← add_mul]; exact hCK)) - calc (leafCertificate mk).time x ≤ K₀ * (n + 1) ^ K₀ * 2 ^ x.edges * (A * x.prec + 2) ^ A := hT + calc (leafCertificate tableEntryCertificate mk).time x + ≤ K₀ * (n + 1) ^ K₀ * 2 ^ x.edges * (A * x.prec + 2) ^ A := hT _ ≤ K₀ * (n + 1) ^ K₀ * 2 ^ (C * s / S) * 2 ^ (K₁ * s / S) := Nat.mul_le_mul (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by norm_num) hMs)) hfac _ = K₀ * (n + 1) ^ K₀ * 2 ^ (C * s / S + K₁ * s / S) := by rw [pow_add]; ring diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean index 0f1c099277..5ab35e0d38 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean @@ -28,8 +28,9 @@ import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFr The stack recursion of `Cslib.Circuits.Frontier.FastContraction.scheme` as one native machine for the contraction modulo `2 ^ P` of an explicit network `(r, G)`, with the precision `P` in binary: the root descriptors are tables (`T = List ℕ`), read by the entry procedure `tableEntry`. The -encodings, the root network (`FastContraction.rootNet`) and its polynomial-time construction are -stated for every type of root descriptors. +encodings, the leaf certificate (`FastContraction.leafCertificate ce`, from a native certificate +`ce` of any entry procedure), the root network (`FastContraction.rootNet`) and its polynomial-time +construction are stated for every type of root descriptors. **The root.** The precision is first capped at the encoded length of the network (`ExplicitTensorNetwork.capPrecision`), which does not change the residue. The root call @@ -122,36 +123,46 @@ theorem leafInput_polyTime : (DescribedNetwork.encInput (encPair encUnary (encList encNat)) (VertexDesc.enc encT)) := by unfold Call.prec Call.fl Call.net; fun_prop -variable (mk : Call (List ℕ) → State → ℕ × DescribedNetwork (VertexDesc (List ℕ))) +section Leaf + +variable {encT} {ev : T → List Bool → List ℕ} + (ce : NativeCertificate (fun q : T × List Bool => ev q.1 q.2) (encPair encT encBits) + (encList encNat)) + (mk : Call T → State → ℕ × DescribedNetwork (VertexDesc T)) -/-- **The leaf certificate** over tables: direct summation over the coefficient lists of `A_p`, -with the native evaluator of the vertex descriptors, tagged with the depth. -/ +/-- **The leaf certificate**: direct summation over the coefficient lists of `A_p`, with the native +evaluator of the vertex descriptors built from a native certificate `ce` of the entry procedure of +the roots, tagged with the depth. -/ @[expose] noncomputable def leafCertificate : - NativeCertificate (scheme tableEntry mk).leaf (encCall (encList encNat)) encValue := - ((NativeCertificate.ofPolyTime (depth_polyTime (encList encNat))).prod - ((DescribedNetwork.contractionCertificate (VertexDesc.entry tableEntry) - SumOps.galois_polyTime VertexDesc.entryCertificate).comp - (NativeCertificate.ofPolyTime (leafInput_polyTime (encList encNat))))).congr + NativeCertificate (scheme ev mk).leaf (encCall encT) encValue := + ((NativeCertificate.ofPolyTime (depth_polyTime encT)).prod + ((DescribedNetwork.contractionCertificate (VertexDesc.entry ev) SumOps.galois_polyTime + (VertexDesc.entryCertificate ce)).comp + (NativeCertificate.ofPolyTime (leafInput_polyTime encT)))).congr fun _ => rfl /-- The time of a leaf: the depth, the input of direct summation, and direct summation. -/ -theorem leafCertificate_time (x : Call (List ℕ)) : - (leafCertificate mk).time x = - (NativeCertificate.ofPolyTime (depth_polyTime (encList encNat))).time x + - ((NativeCertificate.ofPolyTime (leafInput_polyTime (encList encNat))).time x + - (DescribedNetwork.contractionCertificate (VertexDesc.entry tableEntry) - SumOps.galois_polyTime VertexDesc.entryCertificate).time ((x.prec, x.fl), x.net)) := +theorem leafCertificate_time (x : Call T) : + (leafCertificate ce mk).time x = + (NativeCertificate.ofPolyTime (depth_polyTime encT)).time x + + ((NativeCertificate.ofPolyTime (leafInput_polyTime encT)).time x + + (DescribedNetwork.contractionCertificate (VertexDesc.entry ev) + SumOps.galois_polyTime (VertexDesc.entryCertificate ce)).time + ((x.prec, x.fl), x.net)) := rfl /-- The space of a leaf. -/ -theorem leafCertificate_space (x : Call (List ℕ)) : - (leafCertificate mk).space x = - (NativeCertificate.ofPolyTime (depth_polyTime (encList encNat))).space x + - ((NativeCertificate.ofPolyTime (leafInput_polyTime (encList encNat))).space x + - (DescribedNetwork.contractionCertificate (VertexDesc.entry tableEntry) - SumOps.galois_polyTime VertexDesc.entryCertificate).space ((x.prec, x.fl), x.net)) := +theorem leafCertificate_space (x : Call T) : + (leafCertificate ce mk).space x = + (NativeCertificate.ofPolyTime (depth_polyTime encT)).space x + + ((NativeCertificate.ofPolyTime (leafInput_polyTime encT)).space x + + (DescribedNetwork.contractionCertificate (VertexDesc.entry ev) + SumOps.galois_polyTime (VertexDesc.entryCertificate ce)).space + ((x.prec, x.fl), x.net)) := rfl +end Leaf + /-! ### The root -/ /-- The exponent `t` of the modulus, with `2 ^ (t + 1) > 4 ⌊log₂ m⌋ + 2`. -/ @@ -347,6 +358,8 @@ theorem fuel_polyTime : ComputableInPolyTime fuel ExplicitTensorNetwork.encContr /-! ### The result -/ +variable (mk : Call (List ℕ) → State → ℕ × DescribedNetwork (VertexDesc (List ℕ))) + /-- **The result of the machine** on an input `(P, r, G)`: with the capped precision `p`, the constant coordinate of a returned root value modulo `2 ^ p` if the run returns a value tagged `0`, and direct summation otherwise. -/ @@ -400,7 +413,8 @@ components of the scheme. -/ NativeCertificate ((scheme tableEntry mk).run fuel root) ExplicitTensorNetwork.encContractionInput (encOption encValue) := - Scheme.certificate hpoly (leafCertificate mk) (NativeCertificate.ofPolyTime fuel_polyTime) + Scheme.certificate hpoly (leafCertificate tableEntryCertificate mk) + (NativeCertificate.ofPolyTime fuel_polyTime) (NativeCertificate.ofPolyTime root_polyTime) /-- The constant coordinate of a value modulo `2 ^ p`, for the capped precision `p`. -/ diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean index 4f620b30fd..0abab83ad8 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean @@ -36,12 +36,13 @@ precision. A network of the recursion reads the entries at its own precision `(p its coefficient list: a retained packet vertex created at a lower precision is read at the higher one as the same element of `O = ℤ_(2)[U] / (f)`. Explicit tables are the root descriptors `T = List ℕ` with the entry procedure `tableEntry`, which returns the table entry as a constant. -For tables, one native machine evaluates every descriptor (`VertexDesc.entryCertificate`), with -the packet evaluator's bounds: time `K (m + 2) ^ L Z ^ K` and space `K Z ^ K`, for `m` target modes -and `L` row ports of a packet (none for a table) and `Z` polynomial in the input and the reduced -coefficient lists (`VertexDesc.entryCertificate_le`). It is the evaluator of direct summation over -networks of descriptors at coefficient lists (`DescribedNetwork.contractionCertificate` at -`SumOps.galois`). +From a native certificate `ce` of the entry procedure, one native machine evaluates every +descriptor (`VertexDesc.entryCertificate ce`): time `K (m + 2) ^ L Z ^ K + E` and space +`K Z ^ K + ES`, for `m` target modes and `L` row ports of a packet (none for a root), `Z` polynomial +in the input and the reduced coefficient lists, and `E` and `ES` the time and space of `ce` on a +root (`VertexDesc.entryCertificate_le`). It is the evaluator of direct summation over networks of +descriptors at coefficient lists (`DescribedNetwork.contractionCertificate` at `SumOps.galois`). +The entry procedure of tables has a polynomial native certificate (`tableEntryCertificate`). ## Main definitions @@ -49,7 +50,9 @@ networks of descriptors at coefficient lists (`DescribedNetwork.contractionCerti * `Cslib.Circuits.Frontier.VertexDesc`: a root descriptor or a packet descriptor. * `Cslib.Circuits.Frontier.VertexDesc.entry`: its entry at the bits on its ports. * `Cslib.Circuits.Frontier.VertexDesc.enc`: its encoding. -* `Cslib.Circuits.Frontier.VertexDesc.entryCertificate`: the native evaluator, for tables. +* `Cslib.Circuits.Frontier.VertexDesc.entryCertificate`: the native evaluator. +* `Cslib.Circuits.Frontier.tableEntryCertificate`: the native certificate of the entry procedure + of tables. ## Main results @@ -158,58 +161,55 @@ theorem reduced_entry_inr (ev : T → List Bool → List ℕ) (d : PacketDesc) ( SumOps.galois.Reduced (d.1.p, d.1.fl) (entry ev (.inr d) bs) := SumOps.galois.reduced_mul (d.1.p, d.1.fl) _ _ -/-! ### The native evaluator of tables -/ +/-! ### The native evaluator -/ -/-- The right summand of a descriptor and its bits. -/ -theorem getRight_polyTime : - ComputableInPolyTime (fun q : VertexDesc (List ℕ) × List Bool => q.1.getRight?) - (encPair (enc (encList encNat)) encBits) (encOption encPacketDesc) := - (ComputableInO.getRight?_deg _ _).computableInPolyTime.comp (ComputableInPolyTime.fst _ _) +section Evaluator -/-- The table entry of a descriptor at its bits, the empty table standing for a packet, as a -singleton list. -/ -theorem tableEntry_polyTime : - ComputableInPolyTime (fun q : VertexDesc (List ℕ) × List Bool => - [ExplicitTensorNetwork.entry (q.1.getLeft?.getD []) q.2]) - (encPair (enc (encList encNat)) encBits) (encList encNat) := by - have hleft : ComputableInPolyTime (fun q : VertexDesc (List ℕ) × List Bool => q.1.getLeft?) - (encPair (enc (encList encNat)) encBits) (encOption (encList encNat)) := - (ComputableInO.getLeft?_deg _ _).computableInPolyTime.comp (ComputableInPolyTime.fst _ _) - have htable : ComputableInPolyTime - (fun q : VertexDesc (List ℕ) × List Bool => q.1.getLeft?.getD []) - (encPair (enc (encList encNat)) encBits) (encList encNat) := - (ComputableInDegree.getD (enc₁ := encList encNat) (enc := encOption (encList encNat)) - []).computableInPolyTime.comp hleft - have hindex := ComputableInPolyTime.bitsBinaryValue.comp - (ComputableInPolyTime.bitsReverse.comp - (ComputableInPolyTime.snd (enc (encList encNat)) encBits)) - have hentry := ComputableInPolyTime.listGetD_binary (encA := encNat) (encL := encList encNat) 0 - htable hindex - exact (ComputableInPolyTime.cons' hentry (ComputableInPolyTime.const [])).of_enc_eq fun q => by - simp only [ExplicitTensorNetwork.entry, BitString.lsbValue_eq_binaryValue_reverse, - Function.comp_apply] +variable {encT : T ↪ List Bool} + +/-- The left summand of a descriptor and its bits. -/ +theorem getLeft_polyTime : + ComputableInPolyTime (fun q : VertexDesc T × List Bool => q.1.getLeft?) + (encPair (enc encT) encBits) (encOption encT) := + (ComputableInO.getLeft?_deg _ _).computableInPolyTime.comp (ComputableInPolyTime.fst _ _) -/-- The input of the packet evaluator from a descriptor's bits and its packet. -/ +/-- The input of the packet evaluator at a descriptor and its bits, the default packet standing for +a root. -/ theorem packetInput_polyTime : - ComputableInPolyTime (fun p : (VertexDesc (List ℕ) × List Bool) × PacketDesc => (p.2, p.1.2)) - (encPair (encPair (enc (encList encNat)) encBits) encPacketDesc) - (encPair encPacketDesc encBits) := + ComputableInPolyTime (fun q : VertexDesc T × List Bool => (q.1.getRight?.getD default, q.2)) + (encPair (enc encT) encBits) (encPair encPacketDesc encBits) := by + have hright : ComputableInPolyTime (fun q : VertexDesc T × List Bool => q.1.getRight?) + (encPair (enc encT) encBits) (encOption encPacketDesc) := + (ComputableInO.getRight?_deg _ _).computableInPolyTime.comp (ComputableInPolyTime.fst _ _) + exact ((ComputableInDegree.getD (enc₁ := encPacketDesc) (enc := encOption encPacketDesc) + default).computableInPolyTime.comp hright).prod (ComputableInPolyTime.snd _ _) + +/-- The input of the root's entry procedure from a descriptor's bits and its root descriptor. -/ +theorem rootInput_polyTime : + ComputableInPolyTime (fun (((_, bs), t) : (VertexDesc T × List Bool) × T) => (t, bs)) + (encPair (encPair (enc encT) encBits) encT) (encPair encT encBits) := (ComputableInPolyTime.snd _ _).prod - ((ComputableInPolyTime.snd (enc (encList encNat)) encBits).comp (ComputableInPolyTime.fst _ _)) - -/-- **One native machine for the entries of all descriptors over tables**: the table lookup for a -table, and the native packet evaluator (`NativePacket.vertexCertificate`) for a packet. -/ -noncomputable def entryCertificate : - NativeCertificate (fun q : VertexDesc (List ℕ) × List Bool => entry tableEntry q.1 q.2) - (encPair (enc (encList encNat)) encBits) (encList encNat) := - (NativeCertificate.option (value := fun q : VertexDesc (List ℕ) × List Bool => q.1.getRight?) - (fallback := fun q => [ExplicitTensorNetwork.entry (q.1.getLeft?.getD []) q.2]) - (body := fun p => packetVertexEntry p.2 p.1.2) - (NativeCertificate.ofPolyTime getRight_polyTime) - (NativeCertificate.ofPolyTime tableEntry_polyTime) + ((ComputableInPolyTime.snd (enc encT) encBits).comp (ComputableInPolyTime.fst _ _)) + +variable {ev : T → List Bool → List ℕ} + +/-- **One native machine for the entries of all descriptors**, from a native certificate `ce` of +the entry procedure `ev` of the root descriptors: `ce` for a root, and the native packet evaluator +(`NativePacket.vertexCertificate`) for a packet. -/ +noncomputable def entryCertificate + (ce : NativeCertificate (fun q : T × List Bool => ev q.1 q.2) (encPair encT encBits) + (encList encNat)) : + NativeCertificate (fun q : VertexDesc T × List Bool => entry ev q.1 q.2) + (encPair (enc encT) encBits) (encList encNat) := + (NativeCertificate.option (value := fun q : VertexDesc T × List Bool => q.1.getLeft?) + (fallback := fun q => packetVertexEntry (q.1.getRight?.getD default) q.2) + (body := fun ((_, bs), t) => ev t bs) + (NativeCertificate.ofPolyTime getLeft_polyTime) ((vertexCertificate.comp (NativeCertificate.ofPolyTime packetInput_polyTime)).congr - fun _ => rfl)).congr fun q => by - rcases q with ⟨d | d, bs⟩ <;> rfl + fun _ => rfl) + ((ce.comp (NativeCertificate.ofPolyTime rootInput_polyTime)).congr fun _ => rfl)).congr + fun q => by + rcases q with ⟨d | d, bs⟩ <;> rfl /-- `c (y + 1) ^ d ≤ c Z ^ D` when `y + 1 ≤ Z` and `d ≤ D`. -/ private theorem mul_pow_le_mul_pow {c y d Z D : ℕ} (hy : y + 1 ≤ Z) (hd : d ≤ D) : @@ -217,101 +217,116 @@ private theorem mul_pow_le_mul_pow {c y d Z D : ℕ} (hy : y + 1 ≤ Z) (hd : d Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left hy _).trans (Nat.pow_le_pow_right (by omega) hd)) -/-- **The resources of the native evaluator of descriptors.** There is `K` such that on a -descriptor and bits of total length `n`, with `m` target modes and `L` row ports (`0` and `0` for a -table), if the reduced coefficient lists of a packet's precision are at most `Rv` bits long, then -with `Z = n + m + Rv + 2` the time is at most `K (m + 2) ^ L Z ^ K` and the space at most -`K Z ^ K`. -/ -theorem entryCertificate_le : - ∃ K : ℕ, ∀ (q : VertexDesc (List ℕ) × List Bool) (Rv : ℕ), q.1.ReducedLE Rv → - entryCertificate.time q ≤ K * (q.1.modes + 2) ^ q.1.rowPorts q.2 * - ((encPair (enc (encList encNat)) encBits q).length + q.1.modes + Rv + 2) ^ K ∧ - entryCertificate.space q ≤ - K * ((encPair (enc (encList encNat)) encBits q).length + q.1.modes + Rv + 2) ^ K := by +/-- **The resources of the native evaluator of descriptors.** For a native certificate `ce` of +the entry procedure of the roots, there is `K` such that on a descriptor and bits of total length +`n`, with `m` target modes and `L` row ports (`0` and `0` for a root), if the reduced coefficient +lists of a packet's precision are at most `Rv` bits long and `ce` takes time at most `E` and space +at most `ES` on a root and the bits, then with `Z = n + m + Rv + 2` the time is at most +`K (m + 2) ^ L Z ^ K + E` and the space at most `K Z ^ K + ES`. -/ +theorem entryCertificate_le + (ce : NativeCertificate (fun q : T × List Bool => ev q.1 q.2) (encPair encT encBits) + (encList encNat)) : + ∃ K : ℕ, ∀ (q : VertexDesc T × List Bool) (Rv E ES : ℕ), q.1.ReducedLE Rv → + (∀ t, q.1 = .inl t → ce.time (t, q.2) ≤ E ∧ ce.space (t, q.2) ≤ ES) → + (entryCertificate ce).time q ≤ K * (q.1.modes + 2) ^ q.1.rowPorts q.2 * + ((encPair (enc encT) encBits q).length + q.1.modes + Rv + 2) ^ K + E ∧ + (entryCertificate ce).space q ≤ + K * ((encPair (enc encT) encBits q).length + q.1.modes + Rv + 2) ^ K + ES := by obtain ⟨K₀, hK₀⟩ := vertexCertificate_le - have hR := NativeCertificate.ofPolyTime_isPolynomial getRight_polyTime - have hT := NativeCertificate.ofPolyTime_isPolynomial tableEntry_polyTime - have hI := NativeCertificate.ofPolyTime_isPolynomial packetInput_polyTime - obtain ⟨cr, dr, hrt⟩ := hR.1.exists_le - obtain ⟨cr', dr', hrs⟩ := hR.2.exists_le - obtain ⟨ct, dt, htt⟩ := hT.1.exists_le - obtain ⟨ct', dt', hts⟩ := hT.2.exists_le + have hL := NativeCertificate.ofPolyTime_isPolynomial (getLeft_polyTime (encT := encT)) + have hP := NativeCertificate.ofPolyTime_isPolynomial (packetInput_polyTime (encT := encT)) + have hI := NativeCertificate.ofPolyTime_isPolynomial (rootInput_polyTime (encT := encT)) + obtain ⟨cr, dr, hrt⟩ := hL.1.exists_le + obtain ⟨cr', dr', hrs⟩ := hL.2.exists_le + obtain ⟨cp, dp, hpt⟩ := hP.1.exists_le + obtain ⟨cp', dp', hps⟩ := hP.2.exists_le obtain ⟨ci, di, hit⟩ := hI.1.exists_le obtain ⟨ci', di', his⟩ := hI.2.exists_le - obtain ⟨D, hD⟩ : ∃ D, D = K₀ + dr + dr' + dt + dt' + di + di' + 1 := ⟨_, rfl⟩ - obtain ⟨G, hG⟩ : ∃ G, G = K₀ + 2 * cr + 2 * cr' + ct + ct' + ci * 3 ^ di + ci' * 3 ^ di' + 1 := + obtain ⟨D, hD⟩ : ∃ D, D = K₀ + dr + dr' + dp + dp' + di + di' + 1 := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = K₀ + 2 * cr + 2 * cr' + cp + cp' + ci * 3 ^ di + ci' * 3 ^ di' + 1 := ⟨_, rfl⟩ - refine ⟨G + D, fun q Rv hRv => ?_⟩ - obtain ⟨n, hn⟩ : ∃ n, n = (encPair (enc (encList encNat)) encBits q).length := ⟨_, rfl⟩ + refine ⟨G + D, fun q Rv E ES hRv hE => ?_⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encPair (enc encT) encBits q).length := ⟨_, rfl⟩ obtain ⟨Z, hZ⟩ : ∃ Z, Z = n + q.1.modes + Rv + 2 := ⟨_, rfl⟩ obtain ⟨P, hP⟩ : ∃ P, P = (q.1.modes + 2) ^ q.1.rowPorts q.2 := ⟨_, rfl⟩ rw [← hn, ← hZ, ← hP] have hP1 : 1 ≤ P := hP ▸ Nat.one_le_pow _ _ (by omega) have hnZ : n + 1 ≤ Z := by omega have hZD : Z ^ D ≤ Z ^ (G + D) := Nat.pow_le_pow_right (by omega) (by omega) - have hnD : n ≤ Z ^ D := by - calc n ≤ Z := by omega - _ = Z ^ 1 := (pow_one Z).symm - _ ≤ Z ^ D := Nat.pow_le_pow_right (by omega) (by omega) - have hrt' : (NativeCertificate.ofPolyTime getRight_polyTime).time q ≤ cr * Z ^ D := - (hrt q).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) - have hrs' : (NativeCertificate.ofPolyTime getRight_polyTime).space q ≤ cr' * Z ^ D := - (hrs q).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) - rcases q with ⟨d | d, bs⟩ - · -- A table: the lookup alone. - have htt' : (NativeCertificate.ofPolyTime tableEntry_polyTime).time (.inl d, bs) ≤ - ct * Z ^ D := (htt _).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) - have hts' : (NativeCertificate.ofPolyTime tableEntry_polyTime).space (.inl d, bs) ≤ - ct' * Z ^ D := (hts _).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) - have htime : entryCertificate.time (.inl d, bs) = - 2 * (NativeCertificate.ofPolyTime getRight_polyTime).time (.inl d, bs) + n + - (NativeCertificate.ofPolyTime tableEntry_polyTime).time (.inl d, bs) := by + have hrt' : (NativeCertificate.ofPolyTime (getLeft_polyTime (encT := encT))).time q ≤ + cr * Z ^ D := (hrt q).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + have hrs' : (NativeCertificate.ofPolyTime (getLeft_polyTime (encT := encT))).space q ≤ + cr' * Z ^ D := (hrs q).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + rcases q with ⟨t | d, bs⟩ + · -- A root: the input of the entry procedure, then the entry procedure. + obtain ⟨hEt, hEs⟩ : ce.time (t, bs) ≤ E ∧ ce.space (t, bs) ≤ ES := hE t rfl + have hlenI : (encPair (encPair (enc encT) encBits) encT ((.inl t, bs), t)).length + 1 ≤ + 3 * Z := by + have h1 : (encPair (encPair (enc encT) encBits) encT ((.inl t, bs), t)).length = + 2 * (encPair (enc encT) encBits (.inl t, bs)).length + 1 + (encT t).length := + length_encPair _ + have h2 : (encPair (enc encT) encBits (Sum.inl t, bs)).length = + 2 * ((enc encT) (.inl t)).length + 1 + (encBits bs).length := length_encPair _ + have h3 : ((enc encT) (Sum.inl t : VertexDesc T)).length = (encT t).length + 1 := by + rw [length_encSum]; rfl + omega + have hpow3 {c e : ℕ} (he : e ≤ D) : c * (3 * Z) ^ e ≤ c * 3 ^ e * Z ^ D := by + rw [Nat.mul_pow, ← Nat.mul_assoc] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by omega) he) + have hit' : (NativeCertificate.ofPolyTime (rootInput_polyTime (encT := encT))).time + ((.inl t, bs), t) ≤ ci * 3 ^ di * Z ^ D := + (hit _).trans ((Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlenI _)).trans + (hpow3 (by omega))) + have his' : (NativeCertificate.ofPolyTime (rootInput_polyTime (encT := encT))).space + ((.inl t, bs), t) ≤ ci' * 3 ^ di' * Z ^ D := + (his _).trans ((Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlenI _)).trans + (hpow3 (by omega))) + have htime : (entryCertificate ce).time (.inl t, bs) = + 2 * (NativeCertificate.ofPolyTime (getLeft_polyTime (encT := encT))).time (.inl t, bs) + + n + ((NativeCertificate.ofPolyTime (rootInput_polyTime (encT := encT))).time + ((.inl t, bs), t) + ce.time (t, bs)) := by rw [hn]; rfl - have hspace : entryCertificate.space (.inl d, bs) = - 2 * (NativeCertificate.ofPolyTime getRight_polyTime).space (.inl d, bs) + n + - (NativeCertificate.ofPolyTime tableEntry_polyTime).space (.inl d, bs) := by + have hspace : (entryCertificate ce).space (.inl t, bs) = + 2 * (NativeCertificate.ofPolyTime (getLeft_polyTime (encT := encT))).space (.inl t, bs) + + n + ((NativeCertificate.ofPolyTime (rootInput_polyTime (encT := encT))).space + ((.inl t, bs), t) + ce.space (t, bs)) := by rw [hn]; rfl + have hnD : n ≤ Z ^ D := by + calc n ≤ Z := by omega + _ = Z ^ 1 := (pow_one Z).symm + _ ≤ Z ^ D := Nat.pow_le_pow_right (by omega) (by omega) constructor · rw [htime] - calc _ ≤ G * Z ^ D := by - have : (2 * cr + 1 + ct) * Z ^ D ≤ G * Z ^ D := Nat.mul_le_mul_right _ (by omega) - have e : (2 * cr + 1 + ct) * Z ^ D = 2 * (cr * Z ^ D) + Z ^ D + ct * Z ^ D := by ring - omega - _ ≤ (G + D) * P * Z ^ (G + D) := - Nat.mul_le_mul ((Nat.le_add_right G D).trans (Nat.le_mul_of_pos_right _ hP1)) hZD + have h1 : 2 * (cr * Z ^ D) + Z ^ D + ci * 3 ^ di * Z ^ D ≤ G * Z ^ D := by + have : (2 * cr + 1 + ci * 3 ^ di) * Z ^ D ≤ G * Z ^ D := Nat.mul_le_mul_right _ (by omega) + have e : (2 * cr + 1 + ci * 3 ^ di) * Z ^ D = + 2 * (cr * Z ^ D) + Z ^ D + ci * 3 ^ di * Z ^ D := by ring + omega + have h2 : G * Z ^ D ≤ (G + D) * P * Z ^ (G + D) := + Nat.mul_le_mul ((Nat.le_add_right G D).trans (Nat.le_mul_of_pos_right _ hP1)) hZD + omega · rw [hspace] - calc _ ≤ G * Z ^ D := by - have : (2 * cr' + 1 + ct') * Z ^ D ≤ G * Z ^ D := Nat.mul_le_mul_right _ (by omega) - have e : (2 * cr' + 1 + ct') * Z ^ D = 2 * (cr' * Z ^ D) + Z ^ D + ct' * Z ^ D := by - ring - omega - _ ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hZD + have h1 : 2 * (cr' * Z ^ D) + Z ^ D + ci' * 3 ^ di' * Z ^ D ≤ G * Z ^ D := by + have : (2 * cr' + 1 + ci' * 3 ^ di') * Z ^ D ≤ G * Z ^ D := + Nat.mul_le_mul_right _ (by omega) + have e : (2 * cr' + 1 + ci' * 3 ^ di') * Z ^ D = + 2 * (cr' * Z ^ D) + Z ^ D + ci' * 3 ^ di' * Z ^ D := by ring + omega + have h2 : G * Z ^ D ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hZD + omega · -- A packet: the input of the packet evaluator, then the evaluator. - have hn' : n = 2 * ((encPacketDesc d).length + 1) + 1 + (encBits bs).length := by - rw [hn]; exact length_encPair _ + have hpt' : (NativeCertificate.ofPolyTime (packetInput_polyTime (encT := encT))).time + (.inr d, bs) ≤ cp * Z ^ D := + (hpt _).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + have hps' : (NativeCertificate.ofPolyTime (packetInput_polyTime (encT := encT))).space + (.inr d, bs) ≤ cp' * Z ^ D := + (hps _).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) have hlen : (encPair encPacketDesc encBits (d, bs)).length ≤ n := by + have h1 : n = 2 * ((encPacketDesc d).length + 1) + 1 + (encBits bs).length := by + rw [hn]; exact length_encPair _ have h3 : (encPair encPacketDesc encBits (d, bs)).length = 2 * (encPacketDesc d).length + 1 + (encBits bs).length := length_encPair _ omega - have hlenI : (encPair (encPair (enc (encList encNat)) encBits) encPacketDesc - ((.inr d, bs), d)).length + 1 ≤ 3 * Z := by - have h1 : (encPair (encPair (enc (encList encNat)) encBits) encPacketDesc - ((.inr d, bs), d)).length = - 2 * (encPair (enc (encList encNat)) encBits (.inr d, bs)).length + 1 + - (encPacketDesc d).length := - length_encPair _ - omega - have hpow3 {c e : ℕ} (he : e ≤ D) : c * (3 * Z) ^ e ≤ c * 3 ^ e * Z ^ D := by - rw [Nat.mul_pow, ← Nat.mul_assoc] - exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by omega) he) - have hit' : (NativeCertificate.ofPolyTime packetInput_polyTime).time ((.inr d, bs), d) ≤ - ci * 3 ^ di * Z ^ D := - (hit _).trans ((Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlenI _)).trans - (hpow3 (by omega))) - have his' : (NativeCertificate.ofPolyTime packetInput_polyTime).space ((.inr d, bs), d) ≤ - ci' * 3 ^ di' * Z ^ D := - (his _).trans ((Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlenI _)).trans - (hpow3 (by omega))) obtain ⟨hvt, hvs⟩ := hK₀ (d, bs) Rv hRv have hZ' : (encPair encPacketDesc encBits (d, bs)).length + (targetModes d.1).length + Rv + 2 ≤ Z := by rw [hZ]; simp only [modes]; omega @@ -324,38 +339,67 @@ theorem entryCertificate_le : exact Nat.mul_le_mul_left _ hpowK have hvs' : vertexCertificate.space (d, bs) ≤ K₀ * Z ^ D := hvs.trans (Nat.mul_le_mul_left _ hpowK) - have htime : entryCertificate.time (.inr d, bs) = - 2 * (NativeCertificate.ofPolyTime getRight_polyTime).time (.inr d, bs) + n + - ((NativeCertificate.ofPolyTime packetInput_polyTime).time ((.inr d, bs), d) + - vertexCertificate.time (d, bs)) := by + have htime : (entryCertificate ce).time (.inr d, bs) = + 2 * (NativeCertificate.ofPolyTime (getLeft_polyTime (encT := encT))).time (.inr d, bs) + + n + ((NativeCertificate.ofPolyTime (packetInput_polyTime (encT := encT))).time + (.inr d, bs) + vertexCertificate.time (d, bs)) := by rw [hn]; rfl - have hspace : entryCertificate.space (.inr d, bs) = - 2 * (NativeCertificate.ofPolyTime getRight_polyTime).space (.inr d, bs) + n + - ((NativeCertificate.ofPolyTime packetInput_polyTime).space ((.inr d, bs), d) + - vertexCertificate.space (d, bs)) := by + have hspace : (entryCertificate ce).space (.inr d, bs) = + 2 * (NativeCertificate.ofPolyTime (getLeft_polyTime (encT := encT))).space (.inr d, bs) + + n + ((NativeCertificate.ofPolyTime (packetInput_polyTime (encT := encT))).space + (.inr d, bs) + vertexCertificate.space (d, bs)) := by rw [hn]; rfl + have hnD : n ≤ Z ^ D := by + calc n ≤ Z := by omega + _ = Z ^ 1 := (pow_one Z).symm + _ ≤ Z ^ D := Nat.pow_le_pow_right (by omega) (by omega) constructor · rw [htime] - have h1 : 2 * (cr * Z ^ D) + Z ^ D + ci * 3 ^ di * Z ^ D ≤ - P * ((2 * cr + 1 + ci * 3 ^ di) * Z ^ D) := by - calc _ = (2 * cr + 1 + ci * 3 ^ di) * Z ^ D := by ring + have h1 : 2 * (cr * Z ^ D) + Z ^ D + cp * Z ^ D ≤ P * ((2 * cr + 1 + cp) * Z ^ D) := by + calc _ = (2 * cr + 1 + cp) * Z ^ D := by ring _ ≤ _ := Nat.le_mul_of_pos_left _ hP1 - have h2 : P * ((2 * cr + 1 + ci * 3 ^ di) * Z ^ D) + K₀ * P * Z ^ D ≤ P * (G * Z ^ D) := by + have h2 : P * ((2 * cr + 1 + cp) * Z ^ D) + K₀ * P * Z ^ D ≤ P * (G * Z ^ D) := by rw [show K₀ * P * Z ^ D = P * (K₀ * Z ^ D) by ring, ← Nat.mul_add, ← Nat.add_mul] exact Nat.mul_le_mul_left _ (Nat.mul_le_mul_right _ (by omega)) - calc _ ≤ P * (G * Z ^ D) := by omega - _ ≤ (G + D) * P * Z ^ (G + D) := by - rw [show (G + D) * P * Z ^ (G + D) = P * ((G + D) * Z ^ (G + D)) by ring] - exact Nat.mul_le_mul_left _ (Nat.mul_le_mul (by omega) hZD) + have h3 : P * (G * Z ^ D) ≤ (G + D) * P * Z ^ (G + D) := by + rw [show (G + D) * P * Z ^ (G + D) = P * ((G + D) * Z ^ (G + D)) by ring] + exact Nat.mul_le_mul_left _ (Nat.mul_le_mul (by omega) hZD) + omega · rw [hspace] - calc _ ≤ G * Z ^ D := by - have : (2 * cr' + 1 + ci' * 3 ^ di' + K₀) * Z ^ D ≤ G * Z ^ D := - Nat.mul_le_mul_right _ (by omega) - have e : (2 * cr' + 1 + ci' * 3 ^ di' + K₀) * Z ^ D = - 2 * (cr' * Z ^ D) + Z ^ D + ci' * 3 ^ di' * Z ^ D + K₀ * Z ^ D := by ring - omega - _ ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hZD + have h1 : 2 * (cr' * Z ^ D) + Z ^ D + cp' * Z ^ D + K₀ * Z ^ D ≤ G * Z ^ D := by + have : (2 * cr' + 1 + cp' + K₀) * Z ^ D ≤ G * Z ^ D := Nat.mul_le_mul_right _ (by omega) + have e : (2 * cr' + 1 + cp' + K₀) * Z ^ D = + 2 * (cr' * Z ^ D) + Z ^ D + cp' * Z ^ D + K₀ * Z ^ D := by ring + omega + have h2 : G * Z ^ D ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hZD + omega + +end Evaluator end VertexDesc +/-! ### Tables -/ + +/-- The entry procedure of tables is polynomial-time. -/ +theorem tableEntry_polyTime : + ComputableInPolyTime (fun q : List ℕ × List Bool => tableEntry q.1 q.2) + (encPair (encList encNat) encBits) (encList encNat) := by + have hindex := ComputableInPolyTime.bitsBinaryValue.comp + (ComputableInPolyTime.bitsReverse.comp (ComputableInPolyTime.snd (encList encNat) encBits)) + have hentry := ComputableInPolyTime.listGetD_binary (encA := encNat) (encL := encList encNat) 0 + (ComputableInPolyTime.fst _ _) hindex + exact (ComputableInPolyTime.cons' hentry (ComputableInPolyTime.const [])).of_enc_eq fun q => by + simp only [tableEntry, ExplicitTensorNetwork.entry, + BitString.lsbValue_eq_binaryValue_reverse, Function.comp_apply] + +/-- **The native certificate of the entry procedure of tables.** -/ +noncomputable def tableEntryCertificate : + NativeCertificate (fun q : List ℕ × List Bool => tableEntry q.1 q.2) + (encPair (encList encNat) encBits) (encList encNat) := + NativeCertificate.ofPolyTime tableEntry_polyTime + +/-- The certificate of the entry procedure of tables is polynomial. -/ +theorem tableEntryCertificate_isPolynomial : tableEntryCertificate.IsPolynomial := + NativeCertificate.ofPolyTime_isPolynomial tableEntry_polyTime + end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean index 483fcbb679..0348e3bded 100644 --- a/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean @@ -316,7 +316,8 @@ whose leaves take time at most `TL` and space at most `SL`, with `H` open calls, (s₀ ≤ s → 2 * N - 1 ≤ fuel (ExplicitTensorNetwork.capPrecision (P, G))) ∧ (∀ x, I.node x → (encCall (encList encNat) x).length ≤ F ∧ ((scheme tableEntry mk).isLeaf x = true → - (leafCertificate mk).time x ≤ TL ∧ (leafCertificate mk).space x ≤ SL)) ∧ + (leafCertificate tableEntryCertificate mk).time x ≤ TL ∧ + (leafCertificate tableEntryCertificate mk).space x ≤ SL)) ∧ (∀ x a, I.frame x a → (encPair (encCall (encList encNat)) encFrame (x, a)).length ≤ F) ∧ (∀ v, I.value v → (encValue v).length ≤ F) ∧ H + F + SL ≤ K * ((FastContraction.encInput (P, G)).length + 1) ^ K ∧ @@ -374,7 +375,7 @@ theorem fastCertificate_le (fastCertificate hpoly).space (P, G) ≤ K * ((FastContraction.encInput (P, G)).length + 1) ^ K := by obtain ⟨Ka, s₀, hKa⟩ := hana C D - obtain ⟨K₁, hK₁⟩ := Scheme.certificate_le hpoly (leafCertificate mk) + obtain ⟨K₁, hK₁⟩ := Scheme.certificate_le hpoly (leafCertificate tableEntryCertificate mk) obtain ⟨K₂, hK₂⟩ := ExplicitTensorNetwork.exists_directContractionBinaryCertificate_le have hcapP := NativeCertificate.ofPolyTime_isPolynomial ExplicitTensorNetwork.capPrecision_polyTime diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAnalysis.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAnalysis.lean index ff5135680d..977c111927 100644 --- a/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAnalysis.lean +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAnalysis.lean @@ -195,10 +195,10 @@ summation at every reached call that the machine treats as a leaf takes time at ∀ C D : ℕ, ∃ K : ℕ, ∀ (s P : ℕ) (G : ℕ × ExplicitTensorNetwork ℕ), 2 ≤ s → 1 ≤ P → P ≤ D * s → ExplicitAdmissible C s G → fastGuard G = true → ∀ x, Reach nativeMk (root (ExplicitTensorNetwork.capPrecision (P, G))) x → active x = false → - (leafCertificate nativeMk).time x ≤ + (leafCertificate tableEntryCertificate nativeMk).time x ≤ K * ((FastContraction.encInput (P, G)).length + 1) ^ K * 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ - (leafCertificate nativeMk).space x ≤ + (leafCertificate tableEntryCertificate nativeMk).space x ≤ K * ((FastContraction.encInput (P, G)).length + 1) ^ K /-- `a B ^ a ≤ a B ^ K` for `1 ≤ B` and `a ≤ K`. -/ diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean index e84885bb7d..1d6bac0503 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean @@ -27,7 +27,8 @@ list come out in order. Reducing an element of `O` to `A_n` and mapping back alo namespace ComplexityTests.TensorNetworkVertexDesc open Cslib Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativePacket -open Turing.MultiTapeTM (encList encNat) +open Turing.MultiTapeTM (encList encNat encPair encBits) +open Cslib.GenericRAM (NativeCertificate) /-! ### Tables -/ @@ -86,16 +87,21 @@ example : sidePorts ([4, 1, 2].map fun c => (decide (c % 2 = 0), c)) ([4, 1, 2]. /-! ### Resources -/ -example : ∃ K : ℕ, ∀ (q : VertexDesc (List ℕ) × List Bool) (Rv : ℕ), q.1.ReducedLE Rv → - VertexDesc.entryCertificate.time q ≤ K * (q.1.modes + 2) ^ q.1.rowPorts q.2 * - ((Turing.MultiTapeTM.encPair (VertexDesc.enc (encList encNat)) Turing.MultiTapeTM.encBits - q).length + - q.1.modes + Rv + 2) ^ K ∧ - VertexDesc.entryCertificate.space q ≤ - K * ((Turing.MultiTapeTM.encPair (VertexDesc.enc (encList encNat)) - Turing.MultiTapeTM.encBits q).length + - q.1.modes + Rv + 2) ^ K := - VertexDesc.entryCertificate_le +-- The bounds of the evaluator, for every native certificate of the entry procedure of the roots: +-- the root's own time and space are added. +example {T : Type} {encT : T ↪ List Bool} {ev : T → List Bool → List ℕ} + (ce : NativeCertificate (fun q : T × List Bool => ev q.1 q.2) (encPair encT encBits) + (encList encNat)) : + ∃ K : ℕ, ∀ (q : VertexDesc T × List Bool) (Rv E ES : ℕ), q.1.ReducedLE Rv → + (∀ t, q.1 = .inl t → ce.time (t, q.2) ≤ E ∧ ce.space (t, q.2) ≤ ES) → + (VertexDesc.entryCertificate ce).time q ≤ K * (q.1.modes + 2) ^ q.1.rowPorts q.2 * + ((encPair (VertexDesc.enc encT) encBits q).length + q.1.modes + Rv + 2) ^ K + E ∧ + (VertexDesc.entryCertificate ce).space q ≤ + K * ((encPair (VertexDesc.enc encT) encBits q).length + q.1.modes + Rv + 2) ^ K + ES := + VertexDesc.entryCertificate_le ce + +-- The entry procedure of tables has a polynomial native certificate. +example : tableEntryCertificate.IsPolynomial := tableEntryCertificate_isPolynomial /-! ### Back from `A_n` to `O / (2 ^ n)` -/ From 95cbeeffba734456ca2775b22d9eef6b60f6fe8f Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:38:24 +0000 Subject: [PATCH 12/16] fix(check): keep quick mode available with on-demand comparator challenges (#2075) #1729 added the ComplexityComparatorChallenges library with `defaultFacets = []` and a gitignored, generated source directory. quick_check.py rejected the unknown library option, so every `scripts/check.sh --quick` fell back to all six gates. Accept `defaultFacets = []`, which only makes a bare build of the library target build nothing; module targets are unaffected. Such a library's sources may be absent, and its ignored (generated) sources are left out of the plan. Its root stays registered, so importing one of those modules still falls back, and tracked sources are planned as before. Other default facets still fall back. Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG Co-authored-by: Claude Opus 5.5 --- scripts/quick_check.py | 29 ++++++++++++++---- scripts/test_quick_check.py | 59 +++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/scripts/quick_check.py b/scripts/quick_check.py index 08b4a13f24..32c7403c0c 100644 --- a/scripts/quick_check.py +++ b/scripts/quick_check.py @@ -7,6 +7,7 @@ from collections import defaultdict, deque from dataclasses import dataclass import json +import os from pathlib import Path import re import subprocess @@ -60,7 +61,7 @@ def imports_from_lean(root: Path, paths: list[str]) -> dict[str, list[str]]: raise FullCheck(f"invalid native header-parser response: {error}") from error -def source_modules(root: Path) -> tuple[dict[str, str], set[str], set[str]]: +def source_modules(root: Path) -> tuple[dict[str, str], set[str], set[str], set[str]]: if (root / "lakefile.lean").exists(): raise FullCheck("dynamic Lake configuration is outside quick-check scope") try: @@ -76,10 +77,17 @@ def source_modules(root: Path) -> tuple[dict[str, str], set[str], set[str]]: physical_sources: set[Path] = set() aggregates: set[str] = set() roots: set[str] = set() - allowed = {"name", "srcDir", "leanOptions", "allowNonModules", "roots", "globs"} + on_demand: set[str] = set() + allowed = {"name", "srcDir", "leanOptions", "allowNonModules", "roots", "globs", + "defaultFacets"} for library in libraries: if not isinstance(library, dict) or set(library) - allowed: raise FullCheck("custom Lake library options need full checks") + # Lake reads `defaultFacets` only for a bare build of the library target; `[]` makes that + # build nothing. Module targets, which quick mode builds explicitly, are unaffected. + on_demand_library = "defaultFacets" in library + if on_demand_library and library["defaultFacets"] != []: + raise FullCheck("custom Lake library default facets need full checks") name = library.get("name", "") if not re.fullmatch(r"[A-Za-z_][A-Za-z_0-9]*(\.[A-Za-z_][A-Za-z_0-9]*)*", name): raise FullCheck("unsupported library name") @@ -96,6 +104,11 @@ def source_modules(root: Path) -> tuple[dict[str, str], set[str], set[str]]: raise FullCheck("custom Lake library roots/globs need full checks") if globbed and aggregator.exists(): raise FullCheck(f"glob library {name} also has an aggregator file") + if on_demand_library and globbed and not os.path.lexists(directory): + # Sources generated on demand (comparator challenges) may be absent. The root stays + # registered, so importing one of its modules is a missing local import below. + roots.add(name) + continue if (not globbed and not aggregator.is_file()) or not directory.is_dir(): raise FullCheck(f"missing library root {name}") # rglob does not descend through directory symlinks: reject them explicitly, @@ -118,7 +131,9 @@ def source_modules(root: Path) -> tuple[dict[str, str], set[str], set[str]]: raise FullCheck("overlapping library source paths need full checks") physical_sources.add(path.resolve()) modules[module] = path.relative_to(root).as_posix() - return modules, aggregates, roots + if on_demand_library: + on_demand.add(module) + return modules, aggregates, roots, on_demand def executable_sources(root: Path) -> list[Executable]: @@ -171,9 +186,13 @@ def make_plan(root: Path, base_ref: str = "origin/main", parser=None) -> Plan: raise FullCheck(f"deleted, renamed or unusual change: {path}") if not path.endswith(".lean"): raise FullCheck(f"non-Lean change: {path}") - modules, aggregates, roots = source_modules(root) - by_path = {path: module for module, path in modules.items()} + modules, aggregates, roots, on_demand = source_modules(root) visible = set(git(root, "ls-files", "-z").split("\0")) | {p for _, p in changes} + # Ignored sources of a library with empty default facets are generated inputs, such as the + # comparator challenges with their deliberate `sorry`. No gate builds them unless a module + # imports them, and such an import is a missing local import below. + modules = {m: p for m, p in modules.items() if m not in on_demand or p in visible} + by_path = {path: module for module, path in modules.items()} if set(by_path) - visible: raise FullCheck("ignored local Lean source has no reliable Git change record") changed: set[str] = set() diff --git a/scripts/test_quick_check.py b/scripts/test_quick_check.py index 79138b8e4b..42e6eed3bc 100644 --- a/scripts/test_quick_check.py +++ b/scripts/test_quick_check.py @@ -171,6 +171,55 @@ def test_unknown_library_options_fall_back(self): with self.assertRaises(quick.FullCheck): self.plan() + def add_on_demand_library(self, ignored=True): + """A library like the comparator challenges: empty default facets, generated sources.""" + with (self.root / "lakefile.toml").open("a") as f: + f.write('[[lean_lib]]\nname = "DemoChallenges"\nroots = []\n' + 'globs = ["DemoChallenges.+"]\ndefaultFacets = []\n') + if ignored: + self.write(".gitignore", ".lake/\n/DemoChallenges/\n") + self.commit() + self.git("update-ref", "refs/remotes/origin/main", "HEAD") + + def test_absent_on_demand_library_keeps_quick_plan(self): + self.add_on_demand_library() + self.write("Demo/Base.lean", "def value : Nat := 2\n") + self.assertEqual(self.plan().targets, + ["Demo.Base", "Demo.Middle", "DemoTests.AllClient", "DemoTests.Client"]) + + def test_ignored_on_demand_sources_are_not_planned(self): + self.add_on_demand_library() + self.write("DemoChallenges/Hole.lean", "import Demo.Base\ntheorem hole : value = 2 := sorry\n") + self.write("Demo/Base.lean", "def value : Nat := 2\n") + self.assertEqual(self.plan().targets, + ["Demo.Base", "Demo.Middle", "DemoTests.AllClient", "DemoTests.Client"]) + + def test_tracked_on_demand_sources_are_still_planned(self): + self.add_on_demand_library(ignored=False) + self.write("DemoChallenges/Client.lean", "import Demo.Base\nexample : Nat := value\n") + self.commit() + self.git("update-ref", "refs/remotes/origin/main", "HEAD") + self.write("Demo/Base.lean", "def value : Nat := 2\n") + self.assertIn("DemoChallenges.Client", self.plan().targets) + + def test_importing_absent_or_ignored_on_demand_source_falls_back(self): + self.add_on_demand_library() + self.write("DemoTests/Client.lean", "import DemoChallenges.Hole\n") + with self.assertRaisesRegex(quick.FullCheck, "missing local import"): + self.plan() + self.write("DemoChallenges/Hole.lean", "import Demo.Base\n") + with self.assertRaisesRegex(quick.FullCheck, "missing local import"): + self.plan() + + def test_nonempty_default_facets_fall_back(self): + with (self.root / "lakefile.toml").open("a") as f: + f.write('defaultFacets = ["shared"]\n') + self.commit() + self.git("update-ref", "refs/remotes/origin/main", "HEAD") + self.write("Demo/Base.lean", "def value : Nat := 2\n") + with self.assertRaisesRegex(quick.FullCheck, "default facets"): + self.plan() + def test_missing_local_import_falls_back(self): self.write("Demo/Middle.lean", "import Demo.Missing\n") with self.assertRaises(quick.FullCheck): @@ -318,6 +367,16 @@ def run(root, command, label): self.assertEqual(calls[0][1:4], ["build", "--wfail", "--iofail"]) +class RepositoryLayoutTests(unittest.TestCase): + def test_repository_libraries_are_in_quick_scope(self): + # An unsupported library layout in the real lakefile sends every quick check to all six + # gates; #1729's `defaultFacets = []` did so unnoticed. + modules, aggregates, roots, _ = quick.source_modules(REPO) + self.assertIn("Complexity", aggregates) + self.assertIn("ComplexityComparatorChallenges", roots) + self.assertIn("ComplexityTests.Axioms", modules) + + class NativeLeanTests(unittest.TestCase): write = QuickCheckTests.write git = QuickCheckTests.git From 02b0ffc2813629afd68cd1c84142e9bcb33ad5ec Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:53:22 +0000 Subject: [PATCH 13/16] feat(Communication): dag-like protocols give circuits (#2019 M1) (#2048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Communication): dag-like protocols give circuits (#2019 M1) Add Krajíček's dag-like protocols for search problems (Definition 2.2), with consistency, solving, communication complexity and the walk from the source, and prove his Theorem 2.3: a protocol for the Karchmer–Wigderson game with k inner nodes and communication t gives a separating De Morgan circuit with at most (k·2^t + 1)·2^(t+1) gates, and a monotone circuit for monotone protocols. Krajíček cites Razborov for the proof; the proof here is direct, by restricted Karchmer–Wigderson formulas at the leaves of the node protocols. Make the monotone synthesis rules public and use them in the resolution interpolation module. Add the Razborov (1995) Izvestiya entry, with details from Krajíček's bibliography. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(Communication): make Protocol.leafCount_pos public Move the private leaf-count positivity lemma from the dag-protocol circuit proof to Communication/Protocol.lean as public Protocol.leafCount_pos, next to Protocol.leafCount_le_two_pow_depth, and use it there. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --------- Co-authored-by: Claude Opus 5.5 --- Complexity.lean | 3 + .../Computability/Circuit/DagProtocol.lean | 371 ++++++++++++++++++ .../Circuit/Monotone/Synthesis.lean | 49 +++ .../Communication/DagProtocol.lean | 178 +++++++++ .../Computability/Communication/Protocol.lean | 13 +- .../Resolution/Interpolation.lean | 31 +- ComplexityTests/DagProtocol.lean | 128 ++++++ references.bib | 13 + 8 files changed, 759 insertions(+), 27 deletions(-) create mode 100644 Complexity/Computability/Circuit/DagProtocol.lean create mode 100644 Complexity/Computability/Circuit/Monotone/Synthesis.lean create mode 100644 Complexity/Computability/Communication/DagProtocol.lean create mode 100644 ComplexityTests/DagProtocol.lean diff --git a/Complexity.lean b/Complexity.lean index 85d501bae2..86464e834c 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -863,6 +863,7 @@ public import Complexity.Computability.Circuit.Continuity public import Complexity.Computability.Circuit.Counting public import Complexity.Computability.Circuit.Cyclic public import Complexity.Computability.Circuit.Cyclic.Feedback +public import Complexity.Computability.Circuit.DagProtocol public import Complexity.Computability.Circuit.Decomposable public import Complexity.Computability.Circuit.Depth public import Complexity.Computability.Circuit.Encoding.MapOp @@ -1024,6 +1025,7 @@ public import Complexity.Computability.Circuit.Monotone.Encoding public import Complexity.Computability.Circuit.Monotone.Formula public import Complexity.Computability.Circuit.Monotone.KarchmerWigderson public import Complexity.Computability.Circuit.Monotone.Set +public import Complexity.Computability.Circuit.Monotone.Synthesis public import Complexity.Computability.Circuit.Parallel public import Complexity.Computability.Circuit.Pi public import Complexity.Computability.Circuit.Pointwise @@ -1083,6 +1085,7 @@ public import Complexity.Computability.Circuit.Unfolding public import Complexity.Computability.Circuit.Wiring public import Complexity.Computability.Communication.Amplification public import Complexity.Computability.Communication.Corruption +public import Complexity.Computability.Communication.DagProtocol public import Complexity.Computability.Communication.Discrepancy public import Complexity.Computability.Communication.Disjointness public import Complexity.Computability.Communication.Distributional diff --git a/Complexity/Computability/Circuit/DagProtocol.lean b/Complexity/Computability/Circuit/DagProtocol.lean new file mode 100644 index 0000000000..d430c036b3 --- /dev/null +++ b/Complexity/Computability/Circuit/DagProtocol.lean @@ -0,0 +1,371 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Communication.DagProtocol +public import Complexity.Computability.Communication.KarchmerWigderson +public import Complexity.Computability.Circuit.Monotone.Synthesis +public import Cslib.Computability.Circuit.Boolean.Synthesis +import Complexity.Computability.Communication.Rectangle + +/-! +# Circuits from dag-like protocols + +A dag-like protocol for the Karchmer–Wigderson game on `U × V` (`Cslib.Communication.DagProtocol` +solving `Cslib.Communication.DiffersAt`) with `k` inner nodes and communication complexity `t` +gives a De Morgan circuit with at most `(k · 2 ^ t + 1) · 2 ^ (t + 1)` gates that is true on `U` +and false on `V` (`Cslib.Communication.DagProtocol.Solves.exists_circuit`). A monotone protocol, +one solving `Cslib.Communication.ExceedsAt`, gives a monotone circuit with as many gates +(`Cslib.Communication.DagProtocol.Solves.exists_monotone_circuit`). This is +[Krajíček, Theorem 2.3][Krajicek1997], with the explicit constant `2 ^ (2 t + 1)` for the factor +`2 ^ O(t)`. Krajíček credits the theorem to Razborov and proves it by citing Theorem 3.1 of +[Razborov1995b]; that locator is Krajíček's and was not checked against Razborov's paper. The proof +here is direct. + +The tree protocol at an inner node `j` partitions the inputs into at most `2 ^ t` rectangles +(`Cslib.Communication.Protocol.leafAlice`, `Cslib.Communication.Protocol.leafBob`). For each leaf +`ℓ` of it that names an inner node `i`, the circuit has a gate that is true on the inputs of `U` +and false on those of `V` in the rectangle of `ℓ`. That gate is the Karchmer–Wigderson formula of +the tree protocol at `i` restricted to this rectangle, as in +`Cslib.Communication.Protocol.kwFormula`: an OR at each of Alice's nodes and an AND at each of +Bob's. At a leaf of the tree protocol at `i` the formula takes the gate of that leaf if it names an +inner node, a literal if it names an answer, and a constant if it answers `none`: on the inputs of +the rectangle the node `i` is consistent, so the part of a `none` leaf inside it has an empty +side. Each such formula has at most `2 ^ (t + 1)` gates, and there are at most `k · 2 ^ t` of them, +built from the last inner node to the first; one more formula, at the source, separates `U` from +`V`. + +## Main results + +* `Cslib.Communication.DagProtocol.Solves.exists_circuit`: a dag-like protocol for the + Karchmer–Wigderson game gives a separating De Morgan circuit. +* `Cslib.Communication.DagProtocol.Solves.exists_monotone_circuit`: a dag-like protocol for the + monotone Karchmer–Wigderson game gives a separating monotone circuit. + +## References + +* [J. Krajíček, *Interpolation theorems, lower bounds for proof systems, and independence results + for bounded arithmetic*][Krajicek1997] +* [A. A. Razborov, *Unprovability of lower bounds on the circuit size in certain fragments of + bounded arithmetic*][Razborov1995b] +-/ + +public section + +namespace Cslib.Communication.DagProtocol + +open Cslib.Circuits + +variable {n k : ℕ} + +/-! ### Separating rectangles -/ + +/-- `f` is true on `A` and false on `B`. -/ +private def Sep (f : BitString n → Bool) (A B : Set (BitString n)) : Prop := + (∀ x ∈ A, f x = true) ∧ ∀ y ∈ B, f y = false + +private theorem Sep.mono {f : BitString n → Bool} {A B A' B' : Set (BitString n)} + (h : Sep f A B) (hA : A' ⊆ A) (hB : B' ⊆ B) : Sep f A' B' := + ⟨fun x hx => h.1 x (hA hx), fun y hy => h.2 y (hB hy)⟩ + +section Core + +variable {σ : Signature} {I : Interpretation σ Bool} + +/-- A rectangle containing no pair of inputs is separated by a constant. -/ +private theorem exists_sep_const {S : Set (BitString n → Bool)} + (hconst : ∀ b, Synthesis I S {fun _ => b} 1) {A B : Set (BitString n)} + (h : ∀ x ∈ A, ∀ y ∈ B, False) : ∃ f, Sep f A B ∧ Synthesis I S {f} 1 := by + by_cases hA : A.Nonempty + · obtain ⟨x, hx⟩ := hA + exact ⟨fun _ => true, ⟨fun _ _ => rfl, fun y hy => (h x hx y hy).elim⟩, hconst true⟩ + · exact ⟨fun _ => false, ⟨fun x hx => (hA ⟨x, hx⟩).elim, fun _ _ => rfl⟩, hconst false⟩ + +variable {S : Set (BitString n → Bool)} + +/-- **Separating through a tree protocol.** If every leaf rectangle of `p`, cut down to `A × B`, +is separated by a function synthesized from `S` with one gate, then `A` is separated from `B` by a +function synthesized with `2 · leaves - 1` gates: an OR at each of Alice's nodes and an AND at +each of Bob's. -/ +private theorem exists_sep_of_leaves + (hor : ∀ {f g : BitString n → Bool} {a b : ℕ}, Synthesis I S {f} a → Synthesis I S {g} b → + Synthesis I S {fun x => f x || g x} (a + b + 1)) + (hand : ∀ {f g : BitString n → Bool} {a b : ℕ}, Synthesis I S {f} a → Synthesis I S {g} b → + Synthesis I S {fun x => f x && g x} (a + b + 1)) + {Z : Type*} (p : Protocol (BitString n) (BitString n) Z) {A B : Set (BitString n)} + (hleaf : ∀ ℓ, ∃ f, Sep f (A ∩ p.leafAlice ℓ) (B ∩ p.leafBob ℓ) ∧ Synthesis I S {f} 1) : + ∃ f, Sep f A B ∧ Synthesis I S {f} (2 * p.leafCount - 1) := by + induction p generalizing A B with + | output z => + obtain ⟨f, hf, hs⟩ := hleaf ⟨0, Nat.zero_lt_one⟩ + exact ⟨f, hf.mono (fun x hx => ⟨hx, Set.mem_univ x⟩) fun y hy => ⟨hy, Set.mem_univ y⟩, hs⟩ + | alice send p₀ p₁ ih₀ ih₁ => + obtain ⟨f₀, hf₀, hs₀⟩ := ih₀ (A := {x ∈ A | send x = false}) (B := B) fun ℓ => by + obtain ⟨f, hf, hs⟩ := hleaf (Fin.castAdd _ ℓ) + refine ⟨f, hf.mono ?_ ?_, hs⟩ + · rintro x ⟨⟨hx, hsx⟩, hℓ⟩ + simp [hx, hsx, hℓ] + · simp + obtain ⟨f₁, hf₁, hs₁⟩ := ih₁ (A := {x ∈ A | send x = true}) (B := B) fun ℓ => by + obtain ⟨f, hf, hs⟩ := hleaf (Fin.natAdd _ ℓ) + refine ⟨f, hf.mono ?_ ?_, hs⟩ + · rintro x ⟨⟨hx, hsx⟩, hℓ⟩ + simp [hx, hsx, hℓ] + · simp + refine ⟨fun x => f₀ x || f₁ x, ⟨fun x hx => ?_, fun y hy => ?_⟩, + (hor hs₀ hs₁).mono Set.Subset.rfl Set.Subset.rfl ?_⟩ + · cases h : send x + · simp [hf₀.1 x ⟨hx, h⟩] + · simp [hf₁.1 x ⟨hx, h⟩] + · simp [hf₀.2 y hy, hf₁.2 y hy] + · have := p₀.leafCount_pos + have := p₁.leafCount_pos + simp only [Protocol.leafCount_alice] + omega + | bob send p₀ p₁ ih₀ ih₁ => + obtain ⟨f₀, hf₀, hs₀⟩ := ih₀ (A := A) (B := {y ∈ B | send y = false}) fun ℓ => by + obtain ⟨f, hf, hs⟩ := hleaf (Fin.castAdd _ ℓ) + refine ⟨f, hf.mono ?_ ?_, hs⟩ + · simp + · rintro y ⟨⟨hy, hsy⟩, hℓ⟩ + simp [hy, hsy, hℓ] + obtain ⟨f₁, hf₁, hs₁⟩ := ih₁ (A := A) (B := {y ∈ B | send y = true}) fun ℓ => by + obtain ⟨f, hf, hs⟩ := hleaf (Fin.natAdd _ ℓ) + refine ⟨f, hf.mono ?_ ?_, hs⟩ + · simp + · rintro y ⟨⟨hy, hsy⟩, hℓ⟩ + simp [hy, hsy, hℓ] + refine ⟨fun x => f₀ x && f₁ x, ⟨fun x hx => ?_, fun y hy => ?_⟩, + (hand hs₀ hs₁).mono Set.Subset.rfl Set.Subset.rfl ?_⟩ + · simp [hf₀.1 x hx, hf₁.1 x hx] + · cases h : send y + · simp [hf₀.2 y ⟨hy, h⟩] + · simp [hf₁.2 y ⟨hy, h⟩] + · have := p₀.leafCount_pos + have := p₁.leafCount_pos + simp only [Protocol.leafCount_bob] + omega + +end Core + +/-! ### Building the circuit -/ + +section Build + +variable {σ : Signature} {I : Interpretation σ Bool} + (P : DagProtocol (BitString n) (BitString n) (Fin n) k) + (R : BitString n → BitString n → Fin n → Prop) (U V : Set (BitString n)) + +/-- Every leaf of the tree protocol at inner node `j` that names an inner node has a function in +`T` that is true on the inputs of `U` and false on those of `V` in its rectangle. -/ +private def Gated (T : Set (BitString n → Bool)) (j : Fin k) : Prop := + ∀ ℓ (i : Fin j), (P.step j).leafOutput ℓ = some (.inl i) → + ∃ f ∈ T, Sep f (U ∩ (P.step j).leafAlice ℓ) (V ∩ (P.step j).leafBob ℓ) + +variable {P R U V} + +/-- The gate rules used to build the circuit: constants, a one-gate separator of every rectangle +on which an answer is valid, disjunction and conjunction. -/ +private structure Rules (I : Interpretation σ Bool) (R : BitString n → BitString n → Fin n → Prop) : + Prop where + const : ∀ b, Synthesis I (inputs n) {fun _ => b} 1 + rect : ∀ (A B : Set (BitString n)) (o : Fin n), (∀ x ∈ A, ∀ y ∈ B, R x y o) → + ∃ f, Sep f A B ∧ Synthesis I (inputs n) {f} 1 + or : ∀ {S : Set (BitString n → Bool)} {f g : BitString n → Bool} {a b : ℕ}, + Synthesis I S {f} a → Synthesis I S {g} b → Synthesis I S {fun x => f x || g x} (a + b + 1) + and : ∀ {S : Set (BitString n → Bool)} {f g : BitString n → Bool} {a b : ℕ}, + Synthesis I S {f} a → Synthesis I S {g} b → Synthesis I S {fun x => f x && g x} (a + b + 1) + +/-- **The formula at an inner node.** If the leaves of the tree protocol at `j` naming inner nodes +have gates in `T`, then any rectangle `A × B` inside `U × V` on which `j` is consistent is +separated by a function synthesized from the inputs and `T` with `2 · leaves - 1` gates. -/ +private theorem exists_sep_node (hP : P.Solves R U V) (rules : Rules I R) + {T : Set (BitString n → Bool)} {j : Fin k} (hT : Gated P U V T j) {A B : Set (BitString n)} + (hA : A ⊆ U) (hB : B ⊆ V) (hcons : ∀ x ∈ A, ∀ y ∈ B, (P.step j).run x y ≠ none) : + ∃ f, Sep f A B ∧ Synthesis I (inputs n ∪ T) {f} (2 * (P.step j).leafCount - 1) := by + refine exists_sep_of_leaves rules.or rules.and (P.step j) fun ℓ => ?_ + have hrun : ∀ x ∈ A ∩ (P.step j).leafAlice ℓ, ∀ y ∈ B ∩ (P.step j).leafBob ℓ, + (P.step j).run x y = (P.step j).leafOutput ℓ := fun x hx y hy => + Protocol.run_eq_leafOutput hx.2 hy.2 + rcases hout : (P.step j).leafOutput ℓ with _ | i | o + · exact exists_sep_const (A := A ∩ (P.step j).leafAlice ℓ) (B := B ∩ (P.step j).leafBob ℓ) + (fun b => (rules.const b).mono Set.subset_union_left Set.Subset.rfl le_rfl) + fun x hx y hy => hcons x hx.1 y hy.1 ((hrun x hx y hy).trans hout) + · obtain ⟨f, hfT, hf⟩ := hT ℓ i hout + exact ⟨f, hf.mono (Set.inter_subset_inter_left _ hA) (Set.inter_subset_inter_left _ hB), + (Synthesis.of_mem (Set.mem_union_right _ hfT)).mono Set.Subset.rfl Set.Subset.rfl + (Nat.zero_le 1)⟩ + · obtain ⟨f, hf, hs⟩ := rules.rect (A ∩ (P.step j).leafAlice ℓ) (B ∩ (P.step j).leafBob ℓ) o + fun x hx y hy => by + have := hP.2 j x (hA hx.1) y (hB hy.1) (.inr o) ((hrun x hx y hy).trans hout) + simpa using this + exact ⟨f, hf, hs.mono Set.subset_union_left Set.Subset.rfl le_rfl⟩ + +/-- A tree protocol at an inner node, of depth at most that of the dag-like protocol, gives a +formula with at most `2 ^ (P.depth + 1)` gates. -/ +private theorem two_mul_leafCount_sub_one_le (j : Fin k) : + 2 * (P.step j).leafCount - 1 ≤ 2 ^ (P.depth + 1) := by + have h₁ := (P.step j).leafCount_le_two_pow_depth + have h₂ := Nat.pow_le_pow_right (by decide : 0 < 2) (P.depth_step_le j) + rw [pow_succ] + omega + +/-- **The gates of the circuit**, for the leaves of the tree protocols at the inner nodes below +`m`, with at most `2 ^ P.depth` leaves per node and `2 ^ (P.depth + 1)` gates per leaf. -/ +private theorem exists_gates (hP : P.Solves R U V) (rules : Rules I R) : + ∀ m ≤ k, ∃ T, Synthesis I (inputs n) T (m * (2 ^ P.depth * 2 ^ (P.depth + 1))) ∧ + ∀ j : Fin k, j.val < m → Gated P U V T j := by + intro m + induction m with + | zero => + exact fun _ => ⟨∅, (Synthesis.of_subset (I := I) (Set.empty_subset _)).mono Set.Subset.rfl + Set.Subset.rfl (Nat.zero_le _), fun j hj => absurd hj (Nat.not_lt_zero _)⟩ + | succ m ih => + intro hm + obtain ⟨T, hT, hgated⟩ := ih (by omega) + obtain ⟨j, hj⟩ : ∃ j : Fin k, j.val = m := ⟨⟨m, hm⟩, rfl⟩ + have hleaf : ∀ ℓ : Fin (P.step j).leafCount, ∃ f, + (∀ i : Fin j, (P.step j).leafOutput ℓ = some (.inl i) → + Sep f (U ∩ (P.step j).leafAlice ℓ) (V ∩ (P.step j).leafBob ℓ)) ∧ + Synthesis I (inputs n ∪ T) {f} (2 ^ (P.depth + 1)) := by + intro ℓ + have hconst : Synthesis I (inputs n ∪ T) {fun _ => false} (2 ^ (P.depth + 1)) := + (rules.const false).mono Set.subset_union_left Set.Subset.rfl Nat.one_le_two_pow + rcases hout : (P.step j).leafOutput ℓ with _ | i | o + · exact ⟨fun _ => false, by simp, hconst⟩ + · obtain ⟨f, hf, hs⟩ := exists_sep_node hP rules (j := Fin.castLE j.isLt.le i) + (hgated _ (by simpa [hj] using i.isLt)) Set.inter_subset_left Set.inter_subset_left + fun x hx y hy => by + have := hP.2 j x hx.1 y hy.1 (.inl i) + ((Protocol.run_eq_leafOutput hx.2 hy.2).trans hout) + simpa using this + refine ⟨f, fun i' hi' => hf, hs.mono Set.Subset.rfl Set.Subset.rfl ?_⟩ + exact two_mul_leafCount_sub_one_le _ + · exact ⟨fun _ => false, by simp, hconst⟩ + choose g hg using hleaf + refine ⟨T ∪ Set.range g, ?_, fun j' hj' => ?_⟩ + · refine (hT.comp (Synthesis.family g (fun _ => 2 ^ (P.depth + 1)) fun ℓ => (hg ℓ).2)).mono + Set.Subset.rfl Set.Subset.rfl ?_ + simp only [Finset.sum_const, Finset.card_univ, Fintype.card_fin, smul_eq_mul] + have h₁ := (P.step j).leafCount_le_two_pow_depth + have h₂ := Nat.pow_le_pow_right (by decide : 0 < 2) (P.depth_step_le j) + have h₃ := Nat.mul_le_mul_right (2 ^ (P.depth + 1)) (h₁.trans h₂) + rw [Nat.succ_mul] + omega + · rcases Nat.lt_succ_iff_lt_or_eq.1 hj' with hlt | heq + · intro ℓ i h + obtain ⟨f, hf, hsep⟩ := hgated j' hlt ℓ i h + exact ⟨f, Or.inl hf, hsep⟩ + · obtain rfl : j' = j := Fin.ext (heq.trans hj.symm) + intro ℓ i h + exact ⟨g ℓ, Or.inr ⟨ℓ, rfl⟩, (hg ℓ).1 i h⟩ + +/-- **The separator.** A dag-like protocol solving `R` on `U × V` gives a function that is true on +`U` and false on `V`, synthesized with at most `(k · 2 ^ t + 1) · 2 ^ (t + 1)` gates, where `t` is +the communication complexity. -/ +private theorem exists_sep (hP : P.Solves R U V) (rules : Rules I R) : + ∃ f, Sep f U V ∧ + Synthesis I (inputs n) {f} ((k * 2 ^ P.depth + 1) * 2 ^ (P.depth + 1)) := by + obtain ⟨T, hT, hgated⟩ := exists_gates hP rules k le_rfl + have hroot := hP.1 + rcases hr : P.root with j | o + · obtain ⟨f, hf, hs⟩ := exists_sep_node hP rules (hgated j j.isLt) Set.Subset.rfl + Set.Subset.rfl fun x hx y hy => by simpa [hr] using hroot x hx y hy + refine ⟨f, hf, (hT.trans hs).mono Set.Subset.rfl Set.Subset.rfl ?_⟩ + have := two_mul_leafCount_sub_one_le (P := P) j + rw [Nat.add_mul, one_mul, Nat.mul_assoc] + omega + · obtain ⟨f, hf, hs⟩ := rules.rect U V o fun x hx y hy => by simpa [hr] using hroot x hx y hy + exact ⟨f, hf, hs.mono Set.Subset.rfl Set.Subset.rfl + (Nat.mul_pos (Nat.succ_pos _) (Nat.two_pow_pos _))⟩ + +end Build + +/-! ### The De Morgan and monotone circuits -/ + +/-- A rectangle on which every pair of inputs differs at `i` is separated by a constant, the +literal `xᵢ` or the literal `¬xᵢ`. -/ +private theorem exists_sep_differsAt (A B : Set (BitString n)) (i : Fin n) + (h : ∀ x ∈ A, ∀ y ∈ B, DiffersAt x y i) : + ∃ f, Sep f A B ∧ Synthesis Boolean.interpretation (inputs n) {f} 1 := by + by_cases hA : A.Nonempty + swap + · exact ⟨fun _ => false, ⟨fun x hx => (hA ⟨x, hx⟩).elim, fun _ _ => rfl⟩, Synthesis.const false⟩ + by_cases hB : B.Nonempty + swap + · exact ⟨fun _ => true, ⟨fun _ _ => rfl, fun y hy => (hB ⟨y, hy⟩).elim⟩, Synthesis.const true⟩ + obtain ⟨x₀, hx₀⟩ := hA + obtain ⟨y₀, hy₀⟩ := hB + have hinput : Synthesis Boolean.interpretation (inputs n) {fun x => x i} 0 := + Synthesis.of_mem ⟨i, rfl⟩ + cases hxi : x₀ i + · have hyi : y₀ i = true := by simpa [hxi] using (h x₀ hx₀ y₀ hy₀).symm + refine ⟨fun x => !x i, ⟨fun x hx => ?_, fun y hy => ?_⟩, hinput.not⟩ + · simpa [hyi] using h x hx y₀ hy₀ + · simpa [hxi] using (h x₀ hx₀ y hy).symm + · have hyi : y₀ i = false := by simpa [hxi] using (h x₀ hx₀ y₀ hy₀).symm + refine ⟨fun x => x i, ⟨fun x hx => ?_, fun y hy => ?_⟩, + hinput.mono Set.Subset.rfl Set.Subset.rfl (Nat.zero_le 1)⟩ + · simpa [hyi] using h x hx y₀ hy₀ + · simpa [hxi] using (h x₀ hx₀ y hy).symm + +/-- A rectangle on which every pair of inputs has `xᵢ = 1` and `yᵢ = 0` is separated by a +constant or the literal `xᵢ`. -/ +private theorem exists_sep_exceedsAt (A B : Set (BitString n)) (i : Fin n) + (h : ∀ x ∈ A, ∀ y ∈ B, ExceedsAt x y i) : + ∃ f, Sep f A B ∧ Synthesis Monotone.interpretation (inputs n) {f} 1 := by + by_cases hA : A.Nonempty + swap + · exact ⟨fun _ => false, ⟨fun x hx => (hA ⟨x, hx⟩).elim, fun _ _ => rfl⟩, + Synthesis.monotone_const false⟩ + by_cases hB : B.Nonempty + swap + · exact ⟨fun _ => true, ⟨fun _ _ => rfl, fun y hy => (hB ⟨y, hy⟩).elim⟩, + Synthesis.monotone_const true⟩ + obtain ⟨x₀, hx₀⟩ := hA + obtain ⟨y₀, hy₀⟩ := hB + have hinput : Synthesis Monotone.interpretation (inputs n) {fun x => x i} 0 := + Synthesis.of_mem ⟨i, rfl⟩ + exact ⟨fun x => x i, ⟨fun x hx => (h x hx y₀ hy₀).1, fun y hy => (h x₀ hx₀ y hy).2⟩, + hinput.mono Set.Subset.rfl Set.Subset.rfl (Nat.zero_le 1)⟩ + +variable {P : DagProtocol (BitString n) (BitString n) (Fin n) k} {U V : Set (BitString n)} + +/-- **Circuits from dag-like protocols** [Krajíček, Theorem 2.3][Krajicek1997], which Krajíček +credits to Razborov [Razborov1995b]. A dag-like protocol for the Karchmer–Wigderson game on +`U × V` with `k` inner nodes and communication complexity `t` gives a De Morgan circuit with at +most `(k · 2 ^ t + 1) · 2 ^ (t + 1)` gates that is true on `U` and false on `V`. -/ +theorem Solves.exists_circuit (hP : P.Solves DiffersAt U V) : + ∃ C : Circuit Boolean.signature n 1, + C.size ≤ (k * 2 ^ P.depth + 1) * 2 ^ (P.depth + 1) ∧ + (∀ u ∈ U, C.eval Boolean.interpretation u 0 = true) ∧ + ∀ v ∈ V, C.eval Boolean.interpretation v 0 = false := by + obtain ⟨f, hf, hs⟩ := exists_sep hP + ⟨Synthesis.const, exists_sep_differsAt, Synthesis.or, Synthesis.and⟩ + obtain ⟨C, hC, hsize⟩ := hs.exists_circuit + exact ⟨C, hsize, fun u hu => (congrFun (hC u) 0).trans (hf.1 u hu), + fun v hv => (congrFun (hC v) 0).trans (hf.2 v hv)⟩ + +/-- **Monotone circuits from monotone dag-like protocols** [Krajíček, Theorem 2.3][Krajicek1997], +which Krajíček credits to Razborov [Razborov1995b]. A dag-like protocol for the monotone +Karchmer–Wigderson game on `U × V` with `k` inner nodes and communication complexity `t` gives a +monotone circuit with at most `(k · 2 ^ t + 1) · 2 ^ (t + 1)` gates that is true on `U` and false +on `V`. -/ +theorem Solves.exists_monotone_circuit (hP : P.Solves ExceedsAt U V) : + ∃ C : Circuit Monotone.signature n 1, + C.size ≤ (k * 2 ^ P.depth + 1) * 2 ^ (P.depth + 1) ∧ + (∀ u ∈ U, C.eval Monotone.interpretation u 0 = true) ∧ + ∀ v ∈ V, C.eval Monotone.interpretation v 0 = false := by + obtain ⟨f, hf, hs⟩ := exists_sep hP + ⟨Synthesis.monotone_const, exists_sep_exceedsAt, Synthesis.monotone_or, + Synthesis.monotone_and⟩ + obtain ⟨C, hC, hsize⟩ := hs.exists_circuit + exact ⟨C, hsize, fun u hu => (congrFun (hC u) 0).trans (hf.1 u hu), + fun v hv => (congrFun (hC v) 0).trans (hf.2 v hv)⟩ + +end Cslib.Communication.DagProtocol diff --git a/Complexity/Computability/Circuit/Monotone/Synthesis.lean b/Complexity/Computability/Circuit/Monotone/Synthesis.lean new file mode 100644 index 0000000000..61cbe41368 --- /dev/null +++ b/Complexity/Computability/Circuit/Monotone/Synthesis.lean @@ -0,0 +1,49 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Monotone.Basic +public import Cslib.Computability.Circuit.Synthesis + +/-! +# Monotone synthesis + +The generic synthesis rules specialize to the monotone basis: constants, conjunction and +disjunction each cost one gate, as for the De Morgan basis (`Cslib.Circuits.Synthesis.const`, +`Cslib.Circuits.Synthesis.and`, `Cslib.Circuits.Synthesis.or`). + +## Main results + +* `Cslib.Circuits.Synthesis.monotone_const`, `Cslib.Circuits.Synthesis.monotone_and`, + `Cslib.Circuits.Synthesis.monotone_or`: the gates of the monotone basis. +-/ + +@[expose] public section + +namespace Cslib.Circuits.Synthesis + +variable {n : ℕ} {s : Set ((Fin n → Bool) → Bool)} {f g : (Fin n → Bool) → Bool} {a b : ℕ} + +/-- Monotone constants cost one gate. -/ +theorem monotone_const (value : Bool) : + Synthesis Monotone.interpretation s {fun _ => value} 1 := + nullary (I := Monotone.interpretation) (.const value) rfl + +/-- A monotone conjunction costs one gate beyond its arguments. -/ +theorem monotone_and (hf : Synthesis Monotone.interpretation s {f} a) + (hg : Synthesis Monotone.interpretation s {g} b) : + Synthesis Monotone.interpretation s {fun x => f x && g x} (a + b + 1) := by + simpa [Monotone.interpretation] using hf.binary hg .and + +/-- A monotone disjunction costs one gate beyond its arguments. -/ +theorem monotone_or (hf : Synthesis Monotone.interpretation s {f} a) + (hg : Synthesis Monotone.interpretation s {g} b) : + Synthesis Monotone.interpretation s {fun x => f x || g x} (a + b + 1) := by + simpa [Monotone.interpretation] using hf.binary hg .or + +end Cslib.Circuits.Synthesis diff --git a/Complexity/Computability/Communication/DagProtocol.lean b/Complexity/Computability/Communication/DagProtocol.lean new file mode 100644 index 0000000000..6df17b9b0c --- /dev/null +++ b/Complexity/Computability/Communication/DagProtocol.lean @@ -0,0 +1,178 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Communication.Protocol + +/-! +# Dag-like protocols + +A *dag-like protocol* for a search problem `R : X → Y → O → Prop` is a directed acyclic graph +through which Alice, holding `x : X`, and Bob, holding `y : Y`, walk from a source to a leaf +labelled by an answer. At each inner node the players run a deterministic tree protocol +(`Cslib.Communication.Protocol`) that decides whether the node is *consistent* with their inputs +and, if it is, names the next node. A protocol solves `R` on `A × B` when, for inputs from +`A × B`, the source is consistent and consistent nodes lead only to consistent nodes, where a leaf +is consistent when its answer is related to the inputs (`DagProtocol.Solves`). The walk from the +source then ends at a related answer (`DagProtocol.Solves.exists_run_eq`). Its communication +complexity is the largest depth of the tree protocols at the inner nodes (`DagProtocol.depth`). + +This is Krajíček's notion of a protocol for the Karchmer–Wigderson game +[Krajíček, Definition 2.2][Krajicek1997], for the relation `DiffersAt` (or `ExceedsAt` for +monotone protocols), which he describes as a variant of Razborov's formulation with PLS problems +[Razborov1995b]. Tree +protocols for this game correspond to formulas (`Cslib.Communication.KWProtocol`); dag-like +protocols, whose nodes can be reached along many paths, correspond to circuits: one with `k` inner +nodes and communication complexity `t` gives a separating circuit with `k · 2 ^ O(t)` gates +(`Complexity.Computability.Circuit.DagProtocol`). The representation differs from Definition 2.2 +in inessential ways: + +* The inner nodes are numbered `0, …, k - 1` so that every step goes to an earlier inner node or + to a leaf, which rules out cycles. Leaves are identified with their answers, so leaves with the + same label are merged and `k` counts only inner nodes. The source is any node. +* Krajíček's consistency condition `F(u, v)` and strategy `S(u, v, x)` are not separate data: the + tree protocol at node `j` answers `none` exactly when `j ∉ F`, and otherwise its answer is the + next node. The strategy matters only at consistent nodes, and closure of `F` under the paths of + the strategy reduces to closure under one step. +* Definition 2.2 bounds the bits needed to "decide whether `x ∈ F(u, v)` and compute `S(u, v, x)`"; + here one tree protocol does both, so if the two tasks are counted separately, the depth here is + at most their sum. + +## Main definitions + +* `Cslib.Communication.DagProtocol`: dag-like protocols. +* `Cslib.Communication.DagProtocol.Consistent`: consistency of a node with the inputs. +* `Cslib.Communication.DagProtocol.Solves`: a dag-like protocol solves a relation. +* `Cslib.Communication.DagProtocol.depth`: the communication complexity of a dag-like protocol. +* `Cslib.Communication.DagProtocol.run`: the answer at the end of the walk from the source. + +## Main results + +* `Cslib.Communication.DagProtocol.Solves.exists_run_eq`: a dag-like protocol solving `R` on + `A × B` answers every pair of inputs from `A × B` with a related answer. + +## References + +* [J. Krajíček, *Interpolation theorems, lower bounds for proof systems, and independence results + for bounded arithmetic*][Krajicek1997] +* [A. A. Razborov, *Unprovability of lower bounds on the circuit size in certain fragments of + bounded arithmetic*][Razborov1995b] +-/ + +@[expose] public section + +namespace Cslib.Communication + +/-- A **dag-like protocol** with `k` inner nodes between Alice, holding `x : X`, and Bob, holding +`y : Y`, with answers in `O` [Krajíček, Definition 2.2][Krajicek1997]. The inner nodes are +`0, …, k - 1` and the leaves are labelled by answers. At inner node `j` the players run the tree +protocol `step j`: it answers `none` if `j` is not consistent with the inputs, and otherwise names +the next node, an earlier inner node `i < j` or a leaf labelled by an answer. -/ +structure DagProtocol (X Y O : Type*) (k : ℕ) where + /-- The tree protocol at inner node `j`, which answers `none` if `j` is inconsistent with the + inputs and otherwise names the next node: an earlier inner node or an answer. -/ + step : (j : Fin k) → Protocol X Y (Option (Fin j ⊕ O)) + /-- The source: an inner node or an answer. -/ + root : Fin k ⊕ O + +namespace DagProtocol + +variable {X Y O : Type*} {k : ℕ} + +/-- The node named by an answer of the tree protocol at inner node `j`, among all nodes. -/ +def node (j : Fin k) : Fin j ⊕ O → Fin k ⊕ O := + Sum.map (Fin.castLE j.isLt.le) id + +@[simp] theorem node_inl (j : Fin k) (i : Fin j) : + node (O := O) j (.inl i) = .inl (Fin.castLE j.isLt.le i) := + rfl + +@[simp] theorem node_inr (j : Fin k) (o : O) : node (O := O) j (.inr o) = .inr o := + rfl + +variable (P : DagProtocol X Y O k) + +/-- Node `s` is **consistent** with Alice's input `x` and Bob's input `y` for the relation `R`: +an inner node when its tree protocol does not answer `none`, and a leaf when its answer is +related to `x` and `y`. This is Krajíček's consistency condition `F(x, y)`. -/ +def Consistent (R : X → Y → O → Prop) : Fin k ⊕ O → X → Y → Prop + | .inl j, x, y => (P.step j).run x y ≠ none + | .inr o, x, y => R x y o + +@[simp] theorem consistent_inl (R : X → Y → O → Prop) (j : Fin k) (x : X) (y : Y) : + P.Consistent R (.inl j) x y ↔ (P.step j).run x y ≠ none := + Iff.rfl + +@[simp] theorem consistent_inr (R : X → Y → O → Prop) (o : O) (x : X) (y : Y) : + P.Consistent R (.inr o) x y ↔ R x y o := + Iff.rfl + +/-- A dag-like protocol **solves** the relation `R` on `A × B` when, for every pair of inputs from +`A × B`, the source is consistent, and every node named by the tree protocol at an inner node is +consistent [Krajíček, Definition 2.2 (4)][Krajicek1997]. The tree protocol names a node only when +its own node is consistent, so consistency is closed under the steps of the walk, and the walk +from the source ends at a related answer (`DagProtocol.Solves.exists_run_eq`). -/ +def Solves (R : X → Y → O → Prop) (A : Set X) (B : Set Y) : Prop := + (∀ x ∈ A, ∀ y ∈ B, P.Consistent R P.root x y) ∧ + ∀ j, ∀ x ∈ A, ∀ y ∈ B, ∀ s, (P.step j).run x y = some s → P.Consistent R (node j s) x y + +/-- The **communication complexity** of a dag-like protocol: the largest depth of the tree +protocols at its inner nodes. -/ +def depth : ℕ := + Finset.univ.sup fun j => (P.step j).depth + +theorem depth_step_le (j : Fin k) : (P.step j).depth ≤ P.depth := + Finset.le_sup (f := fun j => (P.step j).depth) (Finset.mem_univ j) + +/-- The walk from node `s` on Alice's input `x` and Bob's input `y`: the answer at the leaf it +reaches, or `none` if it meets an inner node whose tree protocol answers `none`. -/ +def runFrom (x : X) (y : Y) : Fin k ⊕ O → Option O + | .inr o => some o + | .inl j => + match (P.step j).run x y with + | none => none + | some s => runFrom x y (node j s) +termination_by s => s.elim (fun j => j.val + 1) fun _ => 0 +decreasing_by + rcases s with i | _ + · simp only [node_inl, Sum.elim_inl, Fin.val_castLE] + exact Nat.succ_lt_succ i.isLt + · simp + +/-- The answer at the end of the walk from the source, or `none` if the walk meets an +inconsistent inner node. -/ +def run (x : X) (y : Y) : Option O := + P.runFrom x y P.root + +variable {P} {R : X → Y → O → Prop} {A : Set X} {B : Set Y} + +/-- From a consistent node, the walk of a protocol solving `R` ends at a related answer. -/ +theorem Solves.exists_runFrom_eq (hP : P.Solves R A B) {x : X} (hx : x ∈ A) {y : Y} (hy : y ∈ B) + (s : Fin k ⊕ O) (hs : P.Consistent R s x y) : ∃ o, P.runFrom x y s = some o ∧ R x y o := by + induction s using runFrom.induct P x y with + | case1 o => exact ⟨o, by rw [runFrom], hs⟩ + | case2 j h => exact absurd h hs + | case3 j s h ih => + rw [runFrom, h] + exact ih (hP.2 j x hx y hy s h) + +/-- **A dag-like protocol solving `R` on `A × B` solves the search problem**: on every pair of +inputs from `A × B`, the walk from the source ends at an answer related to them. -/ +theorem Solves.exists_run_eq (hP : P.Solves R A B) {x : X} (hx : x ∈ A) {y : Y} (hy : y ∈ B) : + ∃ o, P.run x y = some o ∧ R x y o := + hP.exists_runFrom_eq hx hy P.root (hP.1 x hx y hy) + +/-- A protocol solving a relation on `A × B` solves it on smaller sets. -/ +theorem Solves.mono (hP : P.Solves R A B) {A' : Set X} {B' : Set Y} (hA : A' ⊆ A) (hB : B' ⊆ B) : + P.Solves R A' B' := + ⟨fun x hx y hy => hP.1 x (hA hx) y (hB hy), + fun j x hx y hy => hP.2 j x (hA hx) y (hB hy)⟩ + +end DagProtocol + +end Cslib.Communication diff --git a/Complexity/Computability/Communication/Protocol.lean b/Complexity/Computability/Communication/Protocol.lean index 44e6df9262..17a368b4fc 100644 --- a/Complexity/Computability/Communication/Protocol.lean +++ b/Complexity/Computability/Communication/Protocol.lean @@ -45,8 +45,9 @@ announces the value (`Protocol.ecomplexityFun_le_succ`). ## Main results -* `Cslib.Communication.Protocol.leafCount_le_two_pow_depth`: a protocol has at most `2 ^ depth` - leaves. +* `Cslib.Communication.Protocol.leafCount_pos`, + `Cslib.Communication.Protocol.leafCount_le_two_pow_depth`: a protocol has at least one and at + most `2 ^ depth` leaves. * `Cslib.Communication.Protocol.ecomplexity_le_iff`, `Cslib.Communication.Protocol.le_ecomplexity_iff`: upper and lower bounds on communication complexity. @@ -161,6 +162,14 @@ theorem leafCount_le_two_pow_depth (p : Protocol X Y Z) : p.leafCount ≤ 2 ^ p. simp only [leafCount_alice, leafCount_bob, depth_alice, depth_bob, pow_succ] omega +/-- Every protocol has a leaf. -/ +theorem leafCount_pos (p : Protocol X Y Z) : 0 < p.leafCount := by + induction p with + | output z => simp + | alice send p₀ p₁ ih₀ ih₁ | bob send p₀ p₁ ih₀ ih₁ => + simp only [leafCount_alice, leafCount_bob] + omega + /-- A protocol solves the relation `R` on `A × B` when its output does: its output on every pair of inputs from `A × B` is related to them. `Communication.Solves.mono` and `Communication.Solves.of_imp` apply to it. -/ diff --git a/Complexity/Logics/Propositional/Resolution/Interpolation.lean b/Complexity/Logics/Propositional/Resolution/Interpolation.lean index a3c317c2a8..2866b10592 100644 --- a/Complexity/Logics/Propositional/Resolution/Interpolation.lean +++ b/Complexity/Logics/Propositional/Resolution/Interpolation.lean @@ -9,7 +9,7 @@ module public import Complexity.Logics.Propositional.Resolution.Proof public import Cslib.Computability.Circuit.Boolean.Synthesis -public import Complexity.Computability.Circuit.Monotone.Basic +public import Complexity.Computability.Circuit.Monotone.Synthesis /-! # Feasible interpolation for resolution @@ -689,25 +689,6 @@ theorem CNF.RefutableInSize.exists_interpolant {s : ℕ} (h : (A.appendShared B) /-! ### The monotone interpolant -/ -/-- Monotone constants cost one gate. -/ -private theorem synthesis_monotone_const {s : Set ((Fin n → Bool) → Bool)} (b : Bool) : - Synthesis Monotone.interpretation s {fun _ => b} 1 := - Synthesis.nullary (I := Monotone.interpretation) (.const b) rfl - -/-- A monotone conjunction costs one gate beyond its arguments. -/ -private theorem synthesis_monotone_and {s : Set ((Fin n → Bool) → Bool)} - {f g : (Fin n → Bool) → Bool} {a b : ℕ} (hf : Synthesis Monotone.interpretation s {f} a) - (hg : Synthesis Monotone.interpretation s {g} b) : - Synthesis Monotone.interpretation s {fun x => f x && g x} (a + b + 1) := by - simpa [Monotone.interpretation] using hf.binary hg .and - -/-- A monotone disjunction costs one gate beyond its arguments. -/ -private theorem synthesis_monotone_or {s : Set ((Fin n → Bool) → Bool)} - {f g : (Fin n → Bool) → Bool} {a b : ℕ} (hf : Synthesis Monotone.interpretation s {f} a) - (hg : Synthesis Monotone.interpretation s {g} b) : - Synthesis Monotone.interpretation s {fun x => f x || g x} (a + b + 1) := by - simpa [Monotone.interpretation] using hf.binary hg .or - /-- The monotone basis supplies constants, disjunctions and conjunctions within two gates. -/ private theorem monotone_gates : (∀ b, Synthesis Monotone.interpretation (inputs n) {fun _ => b} 2) ∧ @@ -715,10 +696,10 @@ private theorem monotone_gates : Synthesis Monotone.interpretation (inputs n ∪ {f₁, f₂}) {fun a => f₁ a || f₂ a} 2) ∧ ∀ f₁ f₂ : (Fin n → Bool) → Bool, Synthesis Monotone.interpretation (inputs n ∪ {f₁, f₂}) {fun a => f₁ a && f₂ a} 2 := - ⟨fun b => (synthesis_monotone_const b).mono Set.Subset.rfl Set.Subset.rfl (by omega), - fun f₁ f₂ => (synthesis_monotone_or (synthesis_fst f₁ f₂) (synthesis_snd f₁ f₂)).mono + ⟨fun b => (Synthesis.monotone_const b).mono Set.Subset.rfl Set.Subset.rfl (by omega), + fun f₁ f₂ => (Synthesis.monotone_or (synthesis_fst f₁ f₂) (synthesis_snd f₁ f₂)).mono Set.Subset.rfl Set.Subset.rfl (by omega), - fun f₁ f₂ => (synthesis_monotone_and (synthesis_fst f₁ f₂) (synthesis_snd f₁ f₂)).mono + fun f₁ f₂ => (Synthesis.monotone_and (synthesis_fst f₁ f₂) (synthesis_snd f₁ f₂)).mono Set.Subset.rfl Set.Subset.rfl (by omega)⟩ /-- **Monotone feasible interpolation for resolution**, when the shared variables occur only @@ -734,7 +715,7 @@ theorem CNF.IsProof.exists_monotone_interpolant_of_positive refine exists_circuit_of_good (ρA := leftPos) (ρB := rightPart) (fun _ _ _ => rfl) (fun _ _ _ => rfl) (fun d hd a _ l hl he => ?_) (fun _ _ _ _ l _ he => ?_) hconst hor hand (fun i _ _ f₁ f₂ h₁ h₂ => ⟨_, good_posSel i h₁ h₂, - (synthesis_monotone_and (synthesis_monotone_or (synthesis_input f₁ f₂ i) + (Synthesis.monotone_and (Synthesis.monotone_or (synthesis_input f₁ f₂ i) (synthesis_fst f₁ f₂)) (synthesis_snd f₁ f₂)).mono Set.Subset.rfl Set.Subset.rfl (by omega)⟩) h hnil · rcases l with ⟨i | _, b⟩ @@ -757,7 +738,7 @@ theorem CNF.IsProof.exists_monotone_interpolant_of_negative refine exists_circuit_of_good (ρA := leftPart) (ρB := rightNeg) (fun _ _ _ => rfl) (fun _ _ _ => rfl) (fun _ _ _ _ l _ he => ?_) (fun d hd a _ l hl he => ?_) hconst hor hand (fun i _ _ f₁ f₂ h₁ h₂ => ⟨_, good_negSel i h₁ h₂, - (synthesis_monotone_or (synthesis_monotone_and (synthesis_input f₁ f₂ i) + (Synthesis.monotone_or (Synthesis.monotone_and (synthesis_input f₁ f₂ i) (synthesis_snd f₁ f₂)) (synthesis_fst f₁ f₂)).mono Set.Subset.rfl Set.Subset.rfl (by omega)⟩) h hnil · rcases l with ⟨_ | _, b⟩ <;> simpa [leftPart] using he diff --git a/ComplexityTests/DagProtocol.lean b/ComplexityTests/DagProtocol.lean new file mode 100644 index 0000000000..9ee2666200 --- /dev/null +++ b/ComplexityTests/DagProtocol.lean @@ -0,0 +1,128 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.Circuit.DagProtocol +import Mathlib.Tactic.FinCases + +/-! +# Tests: dag-like protocols and their circuits + +Small dag-like protocols for Karchmer–Wigderson games on two bits, and the circuits that +`DagProtocol.Solves.exists_circuit` and `DagProtocol.Solves.exists_monotone_circuit` build from +them. + +* `andProtocol` solves the monotone game of `x₀ ∧ x₁` with two inner nodes, where the second + leads to the first: Bob says whether `y₀ = 0`; if so the answer is `0`, and otherwise the walk + goes to node `0`, where Bob checks consistency again (`y₀ = 1`) before answering `1`. The walk + answers `1` on `11` against `10`, and the extracted monotone circuit has at most `20` gates. +* The protocol of the one-gate circuit `x₀ ∧ x₁` (`gateProtocol`), as in the remark after + Krajíček's Theorem 2.3, solves the same game with one inner node and one bit. +* Answering `0` at once (`zeroProtocol`) does not solve the game: it fails on `11` against `10`. +* The protocol with no inner nodes whose source is the answer `0` solves the game of `¬x₀`, and + the extracted De Morgan circuit has at most two gates. +-/ + +namespace ComplexityTests.DagProtocol + +open Cslib Cslib.Communication Cslib.Circuits + +/-- The inputs where `x₀ ∧ x₁` holds. -/ +private def U : Set (BitString 2) := {x | x 0 = true ∧ x 1 = true} + +/-- The inputs where `x₀ ∧ x₁` fails. -/ +private def V : Set (BitString 2) := {y | y 0 = false ∨ y 1 = false} + +/-- The tree protocols of `andProtocol`. At node `1` Bob says whether `y₀ = 0`: if so the answer +is `0`, and otherwise the walk goes to node `0`. At node `0` Bob says whether `y₀ = 0` again: if +so the node is inconsistent, and otherwise the answer is `1`. -/ +private def andStep : (j : Fin 2) → Protocol (BitString 2) (BitString 2) (Option (Fin j ⊕ Fin 2)) + | 0 => .bob (fun y => y 0) (.output none) (.output (some (.inr 1))) + | 1 => .bob (fun y => y 0) (.output (some (.inr 0))) (.output (some (.inl ⟨0, Nat.zero_lt_one⟩))) + +/-- A dag-like protocol for the monotone Karchmer–Wigderson game of `x₀ ∧ x₁`, with two inner +nodes and source node `1`. -/ +private def andProtocol : DagProtocol (BitString 2) (BitString 2) (Fin 2) 2 := + ⟨andStep, .inl 1⟩ + +/-- Each tree protocol sends one bit. -/ +example : andProtocol.depth = 1 := by + decide + +/-- `andProtocol` solves the monotone Karchmer–Wigderson game of `x₀ ∧ x₁`. -/ +private theorem andProtocol_solves : andProtocol.Solves ExceedsAt U V := by + refine ⟨fun x _ y _ => ?_, fun j x hx y hy s hs => ?_⟩ + · change (andStep 1).run x y ≠ none + cases hy₀ : y 0 <;> simp [andStep, hy₀] + · obtain ⟨hx₀, hx₁⟩ := hx + fin_cases j <;> cases hy₀ : y 0 <;> + simp only [andProtocol, andStep, Protocol.run_bob, Protocol.run_output, hy₀, Bool.cond_false, + Bool.cond_true, reduceCtorEq, Option.some.injEq] at hs <;> subst hs + · exact ⟨hx₁, hy.resolve_left (by simp [hy₀])⟩ + · exact ⟨hx₀, hy₀⟩ + · change (andStep 0).run x y ≠ none + simp [andStep, hy₀] + +/-- The walk of `andProtocol` on `11` against `10` goes from node `1` to node `0` and answers `1`, +the coordinate where `11` is one and `10` is zero. -/ +example : andProtocol.run ![true, true] ![true, false] = some 1 := by + obtain ⟨o, ho, hR⟩ := andProtocol_solves.exists_run_eq (x := ![true, true]) (y := ![true, false]) + ⟨rfl, rfl⟩ (Or.inr rfl) + rw [ho] + fin_cases o + · simp at hR + · rfl + +/-- The monotone circuit extracted from `andProtocol` has at most `(2 · 2 + 1) · 4 = 20` gates and +separates the inputs of `x₀ ∧ x₁` from the others. -/ +example : ∃ C : Circuit Monotone.signature 2 1, C.size ≤ 20 ∧ + (∀ u ∈ U, C.eval Monotone.interpretation u 0 = true) ∧ + ∀ v ∈ V, C.eval Monotone.interpretation v 0 = false := by + have h : andProtocol.depth = 1 := by decide + simpa [h] using andProtocol_solves.exists_monotone_circuit + +/-- **The protocol of a circuit**, after the remark following [Krajíček, Theorem 2.3][Krajicek1997] +for the one-gate monotone circuit `x₀ ∧ x₁`: the inner node is the gate, consistent where its value +is one on Alice's input and zero on Bob's, which holds on all of `U × V`; there Bob names an +argument that is zero on his input, and so one on Alice's. -/ +private def gateProtocol : DagProtocol (BitString 2) (BitString 2) (Fin 2) 1 := + ⟨fun _ => .bob (fun y => y 0) (.output (some (.inr 0))) (.output (some (.inr 1))), .inl 0⟩ + +/-- The protocol of the one-gate circuit `x₀ ∧ x₁` solves its monotone Karchmer–Wigderson game with +one bit of communication. -/ +example : gateProtocol.Solves ExceedsAt U V ∧ gateProtocol.depth = 1 := by + refine ⟨⟨fun x _ y _ => ?_, fun j x hx y hy s hs => ?_⟩, by decide⟩ + · change (Protocol.bob _ _ _).run x y ≠ none + cases hy₀ : y 0 <;> simp [hy₀] + · obtain ⟨hx₀, hx₁⟩ := hx + change (Protocol.bob _ _ _).run x y = some s at hs + cases hy₀ : y 0 <;> + simp only [Protocol.run_bob, Protocol.run_output, hy₀, Bool.cond_false, Bool.cond_true, + Option.some.injEq] at hs <;> subst hs + · exact ⟨hx₀, hy₀⟩ + · exact ⟨hx₁, hy.resolve_left (by simp [hy₀])⟩ + +/-- A protocol answering `0` at once. -/ +private def zeroProtocol : DagProtocol (BitString 2) (BitString 2) (Fin 2) 0 := + ⟨fun j => j.elim0, .inr 0⟩ + +/-- Answering `0` at once does not solve the monotone game of `x₀ ∧ x₁`: `11` and `10` agree at +`0`. -/ +example : ¬ zeroProtocol.Solves ExceedsAt U V := by + intro h + have := h.1 ![true, true] ⟨rfl, rfl⟩ ![true, false] (Or.inr rfl) + simp [zeroProtocol] at this + +/-- The protocol with no inner nodes whose source is the answer `0` solves the Karchmer–Wigderson +game of `¬x₀`, and gives a De Morgan circuit with at most two gates separating `¬x₀` from `x₀`. -/ +example : ∃ C : Circuit Boolean.signature 2 1, C.size ≤ 2 ∧ + (∀ u ∈ {x : BitString 2 | x 0 = false}, C.eval Boolean.interpretation u 0 = true) ∧ + ∀ v ∈ {y : BitString 2 | y 0 = true}, C.eval Boolean.interpretation v 0 = false := by + have h : zeroProtocol.Solves DiffersAt {x | x 0 = false} {y | y 0 = true} := + ⟨fun x hx y hy => by simp_all [zeroProtocol], fun j => Fin.elim0 j⟩ + have hd : zeroProtocol.depth = 0 := by decide + simpa [hd] using h.exists_circuit + +end ComplexityTests.DagProtocol diff --git a/references.bib b/references.bib index 36330860a1..6911ba6068 100644 --- a/references.bib +++ b/references.bib @@ -4163,6 +4163,19 @@ @incollection{Razborov1995 doi = {10.1007/978-1-4612-2566-9_12} } +@article{Razborov1995b, + author = {Razborov, Alexander A.}, + title = {Unprovability of Lower Bounds on the Circuit Size in Certain Fragments of Bounded + Arithmetic}, + journal = {Izvestiya of the Russian Academy of Sciences}, + volume = {59}, + number = {1}, + pages = {201--224}, + year = {1995}, + note = {Details as listed in the bibliography of Kraj{\'i}{\v{c}}ek (1997), reference 43; not + checked against the paper} +} + @article{RazborovRudich1997, author = {Razborov, Alexander A. and Rudich, Steven}, title = {Natural Proofs}, From 51354ea2d2caeaaa705affeed9cb3077c542f21d Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:53:24 +0000 Subject: [PATCH 14/16] =?UTF-8?q?feat(FineGrained):=20count=20models=20of?= =?UTF-8?q?=20every=20CNF=20in=202^O(N/=E2=88=9Alog=20N)=20by=20tensor=20c?= =?UTF-8?q?ontraction=20(#2064)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One machine counts the satisfying assignments of every CNF formula, of any clause width, in time 2^(A(N+1)/√⌊log₂(N+2)⌋) times a polynomial and in polynomial space, where N is the number of literal occurrences (`ContractionCounting.exists_modelCounter_unboundedWidth`); for at most D·n occurrences this is 2^O_D(n/√log n) (`..._linearLength`). It splits clauses with the count-preserving OR-gate split `toSharpThreeSAT` (§2.8 of the manuscript) and runs #2029's width-3 contraction counter, now also stated by occurrences (`exists_modelCounter_occurrences`); the old `exists_modelCounter` keeps its statement and is derived from the same core. Closes #2061. Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG Co-authored-by: Claude Opus 5.5 --- Complexity.lean | 1 + .../FineGrained/ContractionCounting.lean | 84 ++++++++-- .../FineGrained/WideClauseCounting.lean | 157 ++++++++++++++++++ .../Complexity/Problems/SharpThreeSAT.lean | 31 +++- .../Propositional/CNF/ThreeCNF/Exact.lean | 11 +- ComplexityTests/WideClauseCounting.lean | 56 +++++++ 6 files changed, 315 insertions(+), 25 deletions(-) create mode 100644 Complexity/Computability/Complexity/FineGrained/WideClauseCounting.lean create mode 100644 ComplexityTests/WideClauseCounting.lean diff --git a/Complexity.lean b/Complexity.lean index 86464e834c..59277e67f4 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1441,6 +1441,7 @@ public import Complexity.Computability.Complexity.FineGrained.SparseCountingScal public import Complexity.Computability.Complexity.FineGrained.SparseParitySchedule public import Complexity.Computability.Complexity.FineGrained.Sparsification public import Complexity.Computability.Complexity.FineGrained.ThreeOccurrenceReduction +public import Complexity.Computability.Complexity.FineGrained.WideClauseCounting public import Complexity.Computability.Complexity.HardnessEncoding.PolyTime public import Complexity.Computability.Complexity.HardnessEncoding.Program public import Complexity.Computability.Complexity.Parameterized diff --git a/Complexity/Computability/Complexity/FineGrained/ContractionCounting.lean b/Complexity/Computability/Complexity/FineGrained/ContractionCounting.lean index abfbca7ba2..fef7336cff 100644 --- a/Complexity/Computability/Complexity/FineGrained/ContractionCounting.lean +++ b/Complexity/Computability/Complexity/FineGrained/ContractionCounting.lean @@ -28,7 +28,11 @@ time `2 ^ (A (m + 1) / √⌊log₂ (n + 2)⌋)` times a polynomial in the encod polynomial space, where `m` is the number of clauses and `n` the number of variables (`ContractionCounting.exists_modelCounter`). For formulas with at most `D n` clauses this is time `2 ^ O_{k,D}(n / √log n)` times a polynomial, the fixed-density bound of Theorem 1.1 of -[OpenAI2026ETH] (`ContractionCounting.exists_modelCounter_fixedDensity`). +[OpenAI2026ETH] (`ContractionCounting.exists_modelCounter_fixedDensity`). Measured by the number +`N` of literal occurrences instead, the time is `2 ^ (A (N + 1) / √⌊log₂ (N + 2)⌋)` times a +polynomial (`ContractionCounting.exists_modelCounter_occurrences`), with no logarithm of the number +of variables in the exponent; this form extends to formulas of unbounded clause width +(`Cslib.Complexity.ContractionCounting.exists_modelCounter_unboundedWidth`). The machine is the proof of Appendix D.2: it writes out the clause network of the formula (`Cslib.Circuits.Frontier.ClauseNetwork.clauseNetwork`), whose contraction is the model count, @@ -49,6 +53,8 @@ them. * `Cslib.Complexity.ContractionCounting.exists_modelCounter`: the counter, with exponent `A (m + 1) / √⌊log₂ (n + 2)⌋`. +* `Cslib.Complexity.ContractionCounting.exists_modelCounter_occurrences`: the counter, with exponent + `A (N + 1) / √⌊log₂ (N + 2)⌋` for `N` literal occurrences. * `Cslib.Complexity.ContractionCounting.exists_modelCounter_fixedDensity`: for each density bound `D`, exponent `A (n + 1) / √⌊log₂ (n + 2)⌋` on formulas with at most `D n` clauses. @@ -199,16 +205,24 @@ theorem exponent_le {k K N m n : ℕ} (hN : 1 ≤ N) (hNm : N ≤ k * m) (hnN : nlinarith · exact Nat.sqrt_eq_zero.not.mpr (Nat.log_pos (by norm_num) (by omega)).ne' -/-- **Model counting by tensor contraction.** For each clause width `k`, one machine computes -`φ.modelCount` on every formula `φ` whose clauses have at most `k` literals, in time -`2 ^ (A (m + 1) / √⌊log₂ (n + 2)⌋)` times a polynomial in the encoding length and in polynomial -space, where `m` is the number of clauses and `n` the number of variables. -/ -theorem exists_modelCounter (k : ℕ) : - ∃ (count : CNF ℕ → ℕ) (A e b : ℕ), - (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → count φ = φ.modelCount) ∧ - ComputableInO count encCNF encNat - (fun φ => 2 ^ (A * (φ.length + 1) / Nat.sqrt (Nat.log 2 (φ.numVars + 2))) * - ((encCNF φ).length + 1) ^ e) +/-- The exponent of the contraction is at most `3 K (N + 1) / √⌊log₂ (N + 2)⌋` for `N ≥ 1` literal +occurrences. -/ +theorem exponent_le_occurrences {K N : ℕ} (hN : 1 ≤ N) : + K * (3 * N) / Nat.sqrt (Nat.log 2 (3 * N)) ≤ + 3 * K * (N + 1) / Nat.sqrt (Nat.log 2 (N + 2)) := by + refine Nat.div_le_div ?_ (Nat.sqrt_le_sqrt (Nat.log_mono_right (by omega))) ?_ + · nlinarith + · exact Nat.sqrt_eq_zero.not.mpr (Nat.log_pos (by norm_num) (by omega)).ne' + +/-- The machine runs in polynomial space, and in time `2 ^ F φ` times a polynomial for every +exponent `F` that bounds the contraction's exponent `K s / √⌊log₂ s⌋` at the network size +`s = 3 N` on the formulas handed to the contraction algorithm. -/ +private theorem computableInO_count (k : ℕ) : + ∃ K e b : ℕ, ∀ F : CNF ℕ → ℕ, + (∀ φ, guard k φ = true → + K * (3 * φ.vars.length) / Nat.sqrt (Nat.log 2 (3 * φ.vars.length)) ≤ F φ) → + ComputableInO (count k) encCNF encNat + (fun φ => 2 ^ F φ * ((encCNF φ).length + 1) ^ e) (fun φ => ((encCNF φ).length + 1) ^ b) := by obtain ⟨K, hK⟩ := fastContractionAlgorithm.bound (k + 4) 1 have hg := NativeCertificate.ofPolyTime_isPolynomial (computableInPolyTime_guard k) @@ -223,7 +237,7 @@ theorem exists_modelCounter (k : ℕ) : obtain ⟨p, q, hlen⟩ := (computableInPolyTime_contractionInput k).polyBounded_length.exists_le set e := dg + dp + df + q * K set b := dg' + dp' + df' + q * K - refine ⟨count k, 3 * k * K, e, b, fun φ hk => count_eq hk, + refine ⟨K, e, b, fun F hF => (certificate k).computes.mono (cg + cp + cf + K * (p + 1) ^ K + cg' + cp' + cf' + 1) (fun φ => ?_) (fun φ => ?_)⟩ all_goals @@ -241,7 +255,7 @@ theorem exists_modelCounter (k : ℕ) : calc _ ≤ ((p + 1) * Y ^ q) ^ K := Nat.pow_le_pow_left hn' K _ = _ := by rw [mul_pow, pow_mul] · -- time - set E := 3 * k * K * (φ.length + 1) / Nat.sqrt (Nat.log 2 (φ.numVars + 2)) + set E := F φ have h2E : 1 ≤ 2 ^ E := Nat.one_le_two_pow set Z := 2 ^ E * Y ^ e have hZ {d : ℕ} (h : d ≤ e) : Y ^ d ≤ Z := @@ -256,14 +270,11 @@ theorem exists_modelCounter (k : ℕ) : ≤ K * (p + 1) ^ K * Z := by intro hgφ obtain ⟨hne, hcl⟩ := guard_eq_true_iff.mp hgφ - have hk : ∀ c ∈ φ, c.length ≤ k := fun c hc => (hcl c hc).2 have h1 : 1 ≤ φ.length := List.length_pos_iff.mpr hne have hm := length_le_length_vars fun c hc => (hcl c hc).1 - have hNm := CNF.length_vars_le_mul_length hk - have hnN := CNF.numVars_le_length_vars φ have hb := (hK (3 * φ.vars.length) (3 * φ.vars.length) (clauseNetwork k φ) (by omega) (by omega) (by omega) (explicitAdmissible_clauseNetwork hgφ)).1 - have hexp := exponent_le (K := K) (by omega) hNm hnN + have hexp := hF φ hgφ calc fastContractionAlgorithm.certificate.time (contractionInput k φ) ≤ K * ((p + 1) ^ K * Y ^ (q * K)) * 2 ^ E := by refine hb.trans ?_ @@ -323,6 +334,45 @@ theorem exists_modelCounter (k : ℕ) : _ = (cg' + cf') * Y ^ b := by ring _ ≤ _ := hC _ (by omega) +/-- **Model counting by tensor contraction.** For each clause width `k`, one machine computes +`φ.modelCount` on every formula `φ` whose clauses have at most `k` literals, in time +`2 ^ (A (m + 1) / √⌊log₂ (n + 2)⌋)` times a polynomial in the encoding length and in polynomial +space, where `m` is the number of clauses and `n` the number of variables. -/ +theorem exists_modelCounter (k : ℕ) : + ∃ (count : CNF ℕ → ℕ) (A e b : ℕ), + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → count φ = φ.modelCount) ∧ + ComputableInO count encCNF encNat + (fun φ => 2 ^ (A * (φ.length + 1) / Nat.sqrt (Nat.log 2 (φ.numVars + 2))) * + ((encCNF φ).length + 1) ^ e) + (fun φ => ((encCNF φ).length + 1) ^ b) := by + obtain ⟨K, e, b, h⟩ := computableInO_count k + refine ⟨count k, 3 * k * K, e, b, fun φ hk => count_eq hk, h _ fun φ hg => ?_⟩ + obtain ⟨hne, hcl⟩ := guard_eq_true_iff.mp hg + have h1 : 1 ≤ φ.length := List.length_pos_iff.mpr hne + have hm := length_le_length_vars fun c hc => (hcl c hc).1 + exact exponent_le (by omega) (CNF.length_vars_le_mul_length fun c hc => (hcl c hc).2) + (CNF.numVars_le_length_vars φ) + +/-- **Model counting by tensor contraction, measured by literal occurrences.** For each clause +width `k`, one machine computes `φ.modelCount` on every formula `φ` whose clauses have at most `k` +literals, in time `2 ^ (A (N + 1) / √⌊log₂ (N + 2)⌋)` times a polynomial in the encoding length +and in polynomial space, where `N` is the number of literal occurrences. Unlike +`exists_modelCounter`, the exponent does not divide by the logarithm of the number of variables, +so it stays `O(N / √log N)` for formulas with many clauses on few variables. -/ +theorem exists_modelCounter_occurrences (k : ℕ) : + ∃ (count : CNF ℕ → ℕ) (A e b : ℕ), + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → count φ = φ.modelCount) ∧ + ComputableInO count encCNF encNat + (fun φ => 2 ^ (A * (φ.vars.length + 1) / Nat.sqrt (Nat.log 2 (φ.vars.length + 2))) * + ((encCNF φ).length + 1) ^ e) + (fun φ => ((encCNF φ).length + 1) ^ b) := by + obtain ⟨K, e, b, h⟩ := computableInO_count k + refine ⟨count k, 3 * K, e, b, fun φ hk => count_eq hk, h _ fun φ hg => ?_⟩ + obtain ⟨hne, hcl⟩ := guard_eq_true_iff.mp hg + have h1 : 1 ≤ φ.length := List.length_pos_iff.mpr hne + have hm := length_le_length_vars fun c hc => (hcl c hc).1 + exact exponent_le_occurrences (by omega) + /-- **The fixed-density bound of Theorem 1.1.** For each clause width `k`, one machine computes `φ.modelCount` on every formula `φ` whose clauses have at most `k` literals, in polynomial space, and for each density `D` its time on formulas with at most `D n` clauses, for `n` variables, is diff --git a/Complexity/Computability/Complexity/FineGrained/WideClauseCounting.lean b/Complexity/Computability/Complexity/FineGrained/WideClauseCounting.lean new file mode 100644 index 0000000000..0b544e872e --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/WideClauseCounting.lean @@ -0,0 +1,157 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.ContractionCounting +import Complexity.Computability.Complexity.Problems.SharpThreeSAT +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.FunProp +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Bounds + +/-! +# Model counting by tensor contraction for unbounded clause width + +One native machine computes the number of satisfying assignments `CNF.modelCount φ` of **every** +formula `φ` in conjunctive normal form, with no bound on the width of its clauses, in time +`2 ^ (A (N + 1) / √⌊log₂ (N + 2)⌋)` times a polynomial in the encoding length and in polynomial +space, where `N` is the number of literal occurrences of `φ` (`φ.vars.length`) +(`ContractionCounting.exists_modelCounter_unboundedWidth`). For formulas with at most `D n` literal +occurrences on `n` variables this is `2 ^ O_D(n / √log n)` times a polynomial +(`ContractionCounting.exists_modelCounter_unboundedWidth_linearLength`), so sparse formulas are +counted in time `2 ^ o(n)` even when their clauses are long. + +**The machine.** It splits every clause into binary OR gates (`Cslib.Complexity.toSharpThreeSAT`, +the count-preserving split of §2.8 of [OpenAI2026ETH], p. 18). The result has clauses of at most +three literals, the same number of satisfying assignments, and between `N` and `7 N` literal +occurrences (`Cslib.Complexity.length_vars_toSharpThreeSAT`). Then it runs the contraction counter +for width three (`ContractionCounting.exists_modelCounter_occurrences`), whose exponent is +`O(N / √log N)` in the number of literal occurrences. + +After the split, every vertex of the contracted network has at most four ports and an explicit +table, so the form of Theorem D.1 with entries computed by procedures, which allows degree +`⌈s ^ (9/10)⌉`, is not needed; without a split it would not cover clauses wider than that bound +either. + +**Source relation.** Theorem 1.1 of [OpenAI2026ETH] (p. 2) is stated for `k`-CNF with fixed `k`, +in terms of the numbers of variables and clauses; its fixed-density case is proved in Appendix D.2 +(p. 54) from Theorem D.1 (p. 53). The bound here, for unbounded width and in terms of the number of +literal occurrences, combines that proof with the split of §2.8; the combination is a library +extension, not a statement of the manuscript. + +## Main results + +* `Cslib.Complexity.ContractionCounting.exists_modelCounter_unboundedWidth`: the counter for every + formula, with exponent `A (N + 1) / √⌊log₂ (N + 2)⌋`. +* `Cslib.Complexity.ContractionCounting.exists_modelCounter_unboundedWidth_linearLength`: for each + `D`, exponent `A (n + 1) / √⌊log₂ (n + 2)⌋` on formulas with at most `D n` literal occurrences. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Theorem 1.1 (p. 2), + §2.8 (p. 18), Theorem D.1 (p. 53) and Appendix D.2 (p. 54). +-/ + +public section + +namespace Cslib.Complexity.ContractionCounting + +open Cslib.Logic.PL Turing.MultiTapeTM + +/-- The exponent after splitting: between `N` and `7 N` literal occurrences, the exponent +`A (N' + 1) / √⌊log₂ (N' + 2)⌋` is at most `7 A (N + 1) / √⌊log₂ (N + 2)⌋`. -/ +private theorem exponent_le_of_le {A N N' : ℕ} (h₁ : N ≤ N') (h₂ : N' ≤ 7 * N) : + A * (N' + 1) / Nat.sqrt (Nat.log 2 (N' + 2)) ≤ + 7 * A * (N + 1) / Nat.sqrt (Nat.log 2 (N + 2)) := by + refine Nat.div_le_div ?_ (Nat.sqrt_le_sqrt (Nat.log_mono_right (by omega))) ?_ + · have : A * (N' + 1) ≤ A * (7 * (N + 1)) := Nat.mul_le_mul_left _ (by omega) + nlinarith + · exact Nat.sqrt_eq_zero.not.mpr (Nat.log_pos (by norm_num) (by omega)).ne' + +/-- **Model counting by tensor contraction for unbounded clause width.** One machine computes +`φ.modelCount` on every formula `φ`, in time `2 ^ (A (N + 1) / √⌊log₂ (N + 2)⌋)` times a +polynomial in the encoding length and in polynomial space, where `N` is the number of literal +occurrences of `φ`. -/ +theorem exists_modelCounter_unboundedWidth : + ∃ (count : CNF ℕ → ℕ) (A e b : ℕ), + (∀ φ : CNF ℕ, count φ = φ.modelCount) ∧ + ComputableInO count encCNF encNat + (fun φ => 2 ^ (A * (φ.vars.length + 1) / Nat.sqrt (Nat.log 2 (φ.vars.length + 2))) * + ((encCNF φ).length + 1) ^ e) + (fun φ => ((encCNF φ).length + 1) ^ b) := by + obtain ⟨count, A, e, b, hcount, hc⟩ := exists_modelCounter_occurrences 3 + obtain ⟨d, _, hdeg⟩ := computableInPolyTime_toSharpThreeSAT.exists_degree + obtain ⟨p, q, hlen⟩ := computableInPolyTime_toSharpThreeSAT.polyBounded_length.exists_le + refine ⟨fun φ => count (toSharpThreeSAT φ), 7 * A, d + q * e, d + q * b, + fun φ => (hcount _ fun _ hc => length_le_three_of_mem_toSharpThreeSAT hc).trans + (modelCount_toSharpThreeSAT φ), + (hc.comp' hdeg.inO).mono ((p + 1) ^ e + (p + 1) ^ b + 1) (fun φ => ?_) (fun φ => ?_)⟩ + all_goals + obtain ⟨Y, hY⟩ : ∃ Y, Y = (encCNF φ).length + 1 := ⟨_, rfl⟩ + have hY1 : 1 ≤ Y := by omega + have hψ : (encCNF (toSharpThreeSAT φ)).length + 1 ≤ (p + 1) * Y ^ q := by + have h1 : (encCNF (toSharpThreeSAT φ)).length ≤ p * Y ^ q := by rw [hY]; exact hlen φ + have h2 : 1 ≤ Y ^ q := Nat.one_le_pow _ _ hY1 + nlinarith + have hψpow (c : ℕ) : ((encCNF (toSharpThreeSAT φ)).length + 1) ^ c ≤ + (p + 1) ^ c * Y ^ (q * c) := by + calc _ ≤ ((p + 1) * Y ^ q) ^ c := Nat.pow_le_pow_left hψ c + _ = _ := by rw [mul_pow, pow_mul] + simp only [← hY] + · -- time + obtain ⟨E, hE⟩ : ∃ E, E = 7 * A * (φ.vars.length + 1) / Nat.sqrt (Nat.log 2 + (φ.vars.length + 2)) := ⟨_, rfl⟩ + rw [← hE] + obtain ⟨hN₁, hN₂⟩ := length_vars_toSharpThreeSAT φ + have hexp := hE ▸ exponent_le_of_le (A := A) hN₁ hN₂ + have h2E : 1 ≤ 2 ^ E := Nat.one_le_two_pow + have hd : Y ^ d ≤ 2 ^ E * Y ^ (d + q * e) := + (Nat.pow_le_pow_right hY1 (by omega)).trans (Nat.le_mul_of_pos_left _ h2E) + have hc' : 2 ^ (A * ((toSharpThreeSAT φ).vars.length + 1) / + Nat.sqrt (Nat.log 2 ((toSharpThreeSAT φ).vars.length + 2))) * + ((encCNF (toSharpThreeSAT φ)).length + 1) ^ e ≤ + (p + 1) ^ e * (2 ^ E * Y ^ (d + q * e)) := + calc _ ≤ 2 ^ E * ((p + 1) ^ e * Y ^ (q * e)) := + Nat.mul_le_mul (Nat.pow_le_pow_right (by norm_num) hexp) (hψpow e) + _ ≤ 2 ^ E * ((p + 1) ^ e * Y ^ (d + q * e)) := + Nat.mul_le_mul_left _ (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hY1 (by omega))) + _ = (p + 1) ^ e * (2 ^ E * Y ^ (d + q * e)) := by ring + calc _ ≤ 2 ^ E * Y ^ (d + q * e) + (p + 1) ^ e * (2 ^ E * Y ^ (d + q * e)) := + Nat.add_le_add hd hc' + _ = (1 + (p + 1) ^ e) * (2 ^ E * Y ^ (d + q * e)) := by ring + _ ≤ _ := Nat.mul_le_mul (by omega) (Nat.le_succ _) + · -- space + have hd : Y ^ d ≤ Y ^ (d + q * b) := Nat.pow_le_pow_right hY1 (by omega) + have hb : ((encCNF (toSharpThreeSAT φ)).length + 1) ^ b ≤ (p + 1) ^ b * Y ^ (d + q * b) := + (hψpow b).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hY1 (by omega))) + calc _ ≤ Y ^ (d + q * b) + (p + 1) ^ b * Y ^ (d + q * b) := Nat.add_le_add hd hb + _ = (1 + (p + 1) ^ b) * Y ^ (d + q * b) := by ring + _ ≤ _ := Nat.mul_le_mul (by omega) (Nat.le_succ _) + +/-- **Linearly many literal occurrences.** One machine computes `φ.modelCount` on every formula +`φ`, in polynomial space, and for each `D` its time on formulas with at most `D n` literal +occurrences, for `n` variables, is `2 ^ (A (n + 1) / √⌊log₂ (n + 2)⌋)` times a polynomial in the +encoding length. The machine, its time and space functions and the degree `e` of the polynomial +are fixed before `D`; only `A` depends on `D`. -/ +theorem exists_modelCounter_unboundedWidth_linearLength : + ∃ (count : CNF ℕ → ℕ) (T S : CNF ℕ → ℕ) (e b : ℕ), + (∀ φ : CNF ℕ, count φ = φ.modelCount) ∧ + ComputableInO count encCNF encNat T S ∧ + (∀ φ, S φ = ((encCNF φ).length + 1) ^ b) ∧ + ∀ D : ℕ, ∃ A : ℕ, ∀ φ : CNF ℕ, φ.vars.length ≤ D * φ.numVars → + T φ ≤ 2 ^ (A * (φ.numVars + 1) / Nat.sqrt (Nat.log 2 (φ.numVars + 2))) * + ((encCNF φ).length + 1) ^ e := by + obtain ⟨count, A, e, b, hcount, hc⟩ := exists_modelCounter_unboundedWidth + refine ⟨count, _, _, e, b, hcount, hc, fun _ => rfl, fun D => ⟨A * (D + 1), fun φ hφ => ?_⟩⟩ + refine Nat.mul_le_mul_right _ (Nat.pow_le_pow_right (by norm_num) ?_) + have hn := CNF.numVars_le_length_vars φ + refine Nat.div_le_div ?_ (Nat.sqrt_le_sqrt (Nat.log_mono_right (by omega))) + (Nat.sqrt_eq_zero.not.mpr (Nat.log_pos (by norm_num) (by omega)).ne') + have : φ.vars.length + 1 ≤ (D + 1) * (φ.numVars + 1) := by nlinarith + calc A * (φ.vars.length + 1) ≤ A * ((D + 1) * (φ.numVars + 1)) := Nat.mul_le_mul_left _ this + _ = A * (D + 1) * (φ.numVars + 1) := by ring + +end Cslib.Complexity.ContractionCounting diff --git a/Complexity/Computability/Complexity/Problems/SharpThreeSAT.lean b/Complexity/Computability/Complexity/Problems/SharpThreeSAT.lean index f8aa3372eb..c6a41a42c5 100644 --- a/Complexity/Computability/Complexity/Problems/SharpThreeSAT.lean +++ b/Complexity/Computability/Complexity/Problems/SharpThreeSAT.lean @@ -40,6 +40,10 @@ parsimonious reduction to `#SAT ∈ #P`. ## Main results +* `Cslib.Complexity.length_le_three_of_mem_toSharpThreeSAT`, + `Cslib.Complexity.modelCount_toSharpThreeSAT`, `Cslib.Complexity.length_vars_toSharpThreeSAT`: + the reduction has width three, preserves model counts and at most multiplies the number of + literal occurrences by seven. * `Cslib.Complexity.sharpThreeSAT_toSharpThreeSAT`: the reduction preserves counts. * `Cslib.Complexity.computableInPolyTime_toSharpThreeSAT`: it is polynomial-time. * `Cslib.Complexity.parsimoniousReducible_SharpSAT_SharpThreeSAT`: `#SAT` reduces @@ -63,16 +67,31 @@ open Cslib.Logic.PL Turing.MultiTapeTM number the original and gate variables injectively. -/ def toSharpThreeSAT (φ : CNF ℕ) : CNF ℕ := φ.toExact3CNF.relabel splitVarToNat +/-- Every clause of `toSharpThreeSAT φ` has at most three literals. -/ +theorem length_le_three_of_mem_toSharpThreeSAT {φ : CNF ℕ} {c : Clause ℕ} + (hc : c ∈ toSharpThreeSAT φ) : c.length ≤ 3 := by + obtain ⟨d, hd, rfl⟩ := List.mem_map.1 hc + simpa using CNF.length_le_three_of_mem_toExact3CNF hd + +/-- `toSharpThreeSAT` preserves the number of satisfying assignments. -/ +theorem modelCount_toSharpThreeSAT (φ : CNF ℕ) : + (toSharpThreeSAT φ).modelCount = φ.modelCount := by + rw [toSharpThreeSAT, CNF.modelCount_relabel splitVarToNat_injective, CNF.modelCount_toExact3CNF] + +/-- `toSharpThreeSAT φ` has between one and seven times as many literal occurrences as `φ`. -/ +theorem length_vars_toSharpThreeSAT (φ : CNF ℕ) : + φ.vars.length ≤ (toSharpThreeSAT φ).vars.length ∧ + (toSharpThreeSAT φ).vars.length ≤ 7 * φ.vars.length := by + rw [toSharpThreeSAT, CNF.vars_relabel, List.length_map] + exact ⟨CNF.length_vars_le_length_vars_toExact3CNF φ, CNF.length_vars_toExact3CNF_le φ⟩ + /-- `toSharpThreeSAT` has width at most three and the same number of satisfying assignments. -/ theorem sharpThreeSAT_toSharpThreeSAT (φ : CNF ℕ) : SharpThreeSAT (toSharpThreeSAT φ) = SharpSAT φ := by have hw : ((toSharpThreeSAT φ).all fun c => decide (c.length ≤ 3)) = true := by - simp only [List.all_eq_true, decide_eq_true_eq, toSharpThreeSAT, CNF.relabel, List.mem_map] - rintro _ ⟨c, hc, rfl⟩ - simpa using CNF.length_le_three_of_mem_toExact3CNF hc - simp only [SharpThreeSAT, hw, ↓reduceIte] - simp only [SharpSAT, toSharpThreeSAT, CNF.modelCount_relabel splitVarToNat_injective, - CNF.modelCount_toExact3CNF] + simp only [List.all_eq_true, decide_eq_true_eq] + exact fun c hc => length_le_three_of_mem_toSharpThreeSAT hc + simp only [SharpThreeSAT, hw, ↓reduceIte, SharpSAT, modelCount_toSharpThreeSAT] /-- The reduction `toSharpThreeSAT` is computable in polynomial time under `encCNF`. -/ @[fun_prop] diff --git a/Complexity/Logics/Propositional/CNF/ThreeCNF/Exact.lean b/Complexity/Logics/Propositional/CNF/ThreeCNF/Exact.lean index 4c4e5a3cd6..d657f6eb33 100644 --- a/Complexity/Logics/Propositional/CNF/ThreeCNF/Exact.lean +++ b/Complexity/Logics/Propositional/CNF/ThreeCNF/Exact.lean @@ -50,8 +50,9 @@ times. assignments. * `Cslib.Logic.PL.CNF.satisfiable_toExact3CNF`: hence it preserves satisfiability. * `Cslib.Logic.PL.CNF.length_toExact3CNF`, `Cslib.Logic.PL.CNF.length_vars_toExact3CNF`, - `Cslib.Logic.PL.CNF.length_vars_toExact3CNF_le`: the size, linear in the number of literal - occurrences. + `Cslib.Logic.PL.CNF.length_vars_toExact3CNF_le`, + `Cslib.Logic.PL.CNF.length_vars_le_length_vars_toExact3CNF`: the size, between one and seven + times the number of literal occurrences. ## References @@ -470,4 +471,10 @@ theorem CNF.length_vars_toExact3CNF_le (φ : CNF V) : rw [CNF.length_vars_toExact3CNF, CNF.length_vars, ← List.sum_map_mul_left] exact List.sum_le_sum fun c _ => by omega +/-- Splitting keeps every literal occurrence. -/ +theorem CNF.length_vars_le_length_vars_toExact3CNF (φ : CNF V) : + φ.vars.length ≤ φ.toExact3CNF.vars.length := by + rw [CNF.length_vars_toExact3CNF, CNF.length_vars] + exact List.sum_le_sum fun c _ => by omega + end Cslib.Logic.PL diff --git a/ComplexityTests/WideClauseCounting.lean b/ComplexityTests/WideClauseCounting.lean new file mode 100644 index 0000000000..d92a06ad0d --- /dev/null +++ b/ComplexityTests/WideClauseCounting.lean @@ -0,0 +1,56 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.Complexity.FineGrained.WideClauseCounting +import Complexity.Computability.Complexity.Problems.SharpThreeSAT + +/-! # Model counting for unbounded clause width: checked instances + +A formula with a clause of width six, its count-preserving split into width three, and the +counter of `ContractionCounting.exists_modelCounter_unboundedWidth` evaluated on it. -/ + +namespace ComplexityTests.WideClauseCounting + +open Cslib.Logic.PL Cslib.Complexity Cslib.Complexity.ContractionCounting + +/-- `(x₀ ∨ x₁ ∨ x₂ ∨ x₃ ∨ x₄ ∨ x₅) ∧ (¬x₀ ∨ x₆)`: a clause of width six, eight literal occurrences +and seven variables. -/ +def φ₁ : CNF ℕ := + [[(0, true), (1, true), (2, true), (3, true), (4, true), (5, true)], [(0, false), (6, true)]] + +/-- Of the `2 ^ 7` assignments, two falsify the wide clause (`x₀ = ⋯ = x₅ = false`) and `2 ^ 5` +falsify the second (`x₀ = true`, `x₆ = false`); no assignment falsifies both. -/ +theorem modelCount_φ₁ : φ₁.modelCount = 94 := by decide + +example : φ₁.vars.length = 8 ∧ φ₁.numVars = 7 := by decide + +-- The split: width three, `6 + 6 · 3 + 2 = 26 ≤ 7 · 8` literal occurrences, the same count. +example : (toSharpThreeSAT φ₁).all (fun c => decide (c.length ≤ 3)) = true := by decide + +example : (toSharpThreeSAT φ₁).vars.length = 26 := by decide + +example : (toSharpThreeSAT φ₁).modelCount = 94 := by + rw [modelCount_toSharpThreeSAT, modelCount_φ₁] + +-- The counter for every width counts `φ₁`. +example : ∃ (count : CNF ℕ → ℕ) (A e b : ℕ), count φ₁ = 94 ∧ + Turing.MultiTapeTM.ComputableInO count encCNF Turing.MultiTapeTM.encNat + (fun φ => 2 ^ (A * (φ.vars.length + 1) / Nat.sqrt (Nat.log 2 (φ.vars.length + 2))) * + ((encCNF φ).length + 1) ^ e) + (fun φ => ((encCNF φ).length + 1) ^ b) := by + obtain ⟨count, A, e, b, hcount, hc⟩ := exists_modelCounter_unboundedWidth + exact ⟨count, A, e, b, (hcount φ₁).trans modelCount_φ₁, hc⟩ + +-- `φ₁` meets the linear-length hypothesis `N ≤ D n` with `D = 2`, although its widest clause has +-- six literals; formulas with many occurrences of few variables do not. +example : φ₁.vars.length ≤ 2 * φ₁.numVars := by decide + +/-- `x₀ ∧ x₀ ∧ x₀`: three occurrences of one variable. -/ +def φ₂ : CNF ℕ := [[(0, true)], [(0, true)], [(0, true)]] + +example : ¬ φ₂.vars.length ≤ 2 * φ₂.numVars := by decide + +end ComplexityTests.WideClauseCounting From fe2f481df6820d65cc47caf51a33af019281f179 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 16:55:26 +0000 Subject: [PATCH 15/16] feat(Classes/Oracle): bounded-query oracle classes and P^{NP[O(log n)]} (#2062 M1) (#2065) * feat(Classes/Oracle): bounded-query oracle classes and P^{NP[O(log n)]} (#2062 M1) Define ComputableInPolyTimeWithOracleQueries, PWithOracleQueries and PNPLog, with the basic API: P, NP and coNP inside PNPLog (one request to SAT through Cook-Levin), closure under complement, and inclusion in P^NP. Move mem_NP_image and image_SAT_mem_NP from PH/Kannan into the small topic module Classes/NP/Image so that oracle classes can use them without importing Kannan's circuit dependencies. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * doc(Classes/Oracle): cite the page that defines P^{NP[O(log n)]} Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * feat(Classes/Oracle): name the one-request program queryBit Expose the single-request program with its interpretation, request list and oracle PPT certificate, so that oracle programs with several requests can count them. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(Classes/Oracle): share the one-request program between Closure and BoundedQuery Move `queryBit`, its interpretation and request equations and its strict oracle PPT certificate into the new module `Classes/Oracle/QueryBit`, imported by both `Oracle/Closure` and `Oracle/BoundedQuery`. `PolyTimeOracleProgram.of_query` is now built from `queryBit` instead of repeating the request/map/take construction and its proofs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --------- Co-authored-by: Claude Opus 5.5 --- Complexity.lean | 3 + .../Complexity/Classes/NP/Image.lean | 62 +++++ .../Classes/Oracle/BoundedQuery.lean | 235 ++++++++++++++++++ .../Complexity/Classes/Oracle/Closure.lean | 18 +- .../Complexity/Classes/Oracle/QueryBit.lean | 72 ++++++ .../Complexity/Classes/PH/Kannan.lean | 31 +-- ComplexityTests/OracleBoundedQuery.lean | 56 +++++ references.bib | 21 ++ 8 files changed, 455 insertions(+), 43 deletions(-) create mode 100644 Complexity/Computability/Complexity/Classes/NP/Image.lean create mode 100644 Complexity/Computability/Complexity/Classes/Oracle/BoundedQuery.lean create mode 100644 Complexity/Computability/Complexity/Classes/Oracle/QueryBit.lean create mode 100644 ComplexityTests/OracleBoundedQuery.lean diff --git a/Complexity.lean b/Complexity.lean index 59277e67f4..617a932b4f 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1243,6 +1243,7 @@ public import Complexity.Computability.Complexity.Classes.ModP.Time public import Complexity.Computability.Complexity.Classes.NP public import Complexity.Computability.Complexity.Classes.NP.Closure public import Complexity.Computability.Complexity.Classes.NP.GuessVerify +public import Complexity.Computability.Complexity.Classes.NP.Image public import Complexity.Computability.Complexity.Classes.NP.Nondeterministic public import Complexity.Computability.Complexity.Classes.NP.SelfReduction public import Complexity.Computability.Complexity.Classes.NP.TruthTable @@ -1250,6 +1251,7 @@ public import Complexity.Computability.Complexity.Classes.NP.Witness public import Complexity.Computability.Complexity.Classes.Operators public import Complexity.Computability.Complexity.Classes.Oracle public import Complexity.Computability.Complexity.Classes.Oracle.BakerGillSolovay +public import Complexity.Computability.Complexity.Classes.Oracle.BoundedQuery public import Complexity.Computability.Complexity.Classes.Oracle.Closure public import Complexity.Computability.Complexity.Classes.Oracle.Collapse public import Complexity.Computability.Complexity.Classes.Oracle.Counting @@ -1261,6 +1263,7 @@ public import Complexity.Computability.Complexity.Classes.Oracle.Lowness public import Complexity.Computability.Complexity.Classes.Oracle.Monotone public import Complexity.Computability.Complexity.Classes.Oracle.PP public import Complexity.Computability.Complexity.Classes.Oracle.PolyTime +public import Complexity.Computability.Complexity.Classes.Oracle.QueryBit public import Complexity.Computability.Complexity.Classes.Oracle.Space public import Complexity.Computability.Complexity.Classes.Oracle.TQBF public import Complexity.Computability.Complexity.Classes.P diff --git a/Complexity/Computability/Complexity/Classes/NP/Image.lean b/Complexity/Computability/Complexity/Classes/NP/Image.lean new file mode 100644 index 0000000000..15efd0c503 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/NP/Image.lean @@ -0,0 +1,62 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Decoder +import Complexity.Computability.Complexity.Problems.SAT.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List + +/-! +# Images of problems in `NP` + +The encodings of the members of a problem in `NP` form a problem in `NP` on words, when the +encoding has a decoder (`Cslib.Complexity.mem_NP_image`): the verifier decodes the word, checks +that it encodes what was decoded, and runs the verifier of the problem. In particular the +encodings of the satisfiable formulas form a problem in `NP` on words +(`Cslib.Complexity.image_SAT_mem_NP`), an oracle language that answers, through the Cook–Levin +reduction, every problem in `NP`. + +## Main results + +* `Cslib.Complexity.mem_NP_image`: images of problems in `NP` under encodings with decoders. +* `Cslib.Complexity.image_SAT_mem_NP`: the encodings of the satisfiable formulas. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Turing.MultiTapeTM + +/-- **Images of problems in `NP`.** The encodings of the members of a problem in `NP` form a +problem in `NP` on words, when the encoding has a decoder: the verifier decodes the word, checks +that it encodes what was decoded, and runs the verifier of the problem. -/ +theorem mem_NP_image {β : Type} {encB : β ↪ List Bool} (D : Decoder encB) {B : Set β} + (hB : B ∈ NP encB) : encB '' B ∈ NP encBits := by + obtain ⟨p, V, hV, hBV⟩ := mem_NP.mp hB + have hdec : ComputableInPolyTime (fun x : List Bool × List Bool => D.decode x.1) + (encPair encBits encBits) encB := D.computableInPolyTime.comp (.fst _ _) + have henc : ComputableInPolyTime (fun b => encB b) encB encBits := .of_eq fun _ => rfl + refine mem_NP.mpr + ⟨p, fun x => decide (encB (D.decode x.1) = x.1) && V (D.decode x.1, x.2), by fun_prop, + fun w => ?_⟩ + simp only [encBits_apply, Bool.and_eq_true, decide_eq_true_eq] + constructor + · rintro ⟨b, hb, rfl⟩ + obtain ⟨c, hc, hVc⟩ := (hBV b).1 hb + exact ⟨c, hc, by rw [D.decode_enc], by rw [D.decode_enc]; exact hVc⟩ + · rintro ⟨c, hc, hw, hVc⟩ + exact ⟨D.decode w, (hBV _).2 ⟨c, by rw [hw]; exact hc, hVc⟩, hw⟩ + +/-- The encodings of the satisfiable formulas form a problem in `NP` on words. -/ +theorem image_SAT_mem_NP : encCNF '' SAT ∈ NP encBits := + mem_NP_image (.list (.list (.pair .nat .bool))) SAT.mem_NP + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Oracle/BoundedQuery.lean b/Complexity/Computability/Complexity/Classes/Oracle/BoundedQuery.lean new file mode 100644 index 0000000000..9a3df0e025 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Oracle/BoundedQuery.lean @@ -0,0 +1,235 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Oracle.QueryBit +public import Complexity.Computability.Complexity.Problems.SAT +import Complexity.Computability.Complexity.Classes.NP.Image +import Complexity.Computability.Complexity.CookLevin.Completeness +import Complexity.Computability.Probabilistic.Oracle.Composition + +/-! +# Bounded-query oracle classes and `P^{NP[O(log n)]}` + +`f` is computable in polynomial time relative to `A` with `r` requests +(`Cslib.Complexity.ComputableInPolyTimeWithOracleQueries`) when it has the certificate of +`Cslib.Complexity.ComputableInPolyTimeWithOracle` and the program, run against the oracle +language `A`, makes at most `r n` requests on instances of encoded length `n` +(`Cslib.OracleComp.queriesWith`). The requests are counted against `A` itself, as in the usual +definition of a machine with oracle `A` making a bounded number of queries; the certificate's +clock bounds the requests against every oracle in any case +(`Cslib.Probability.IsOraclePPTOn.query_bounds`). + +`P^{NP[O(log n)]}` (`Cslib.Complexity.PNPLog`) is the class of problems decided in polynomial time +with at most `c log₂ n + c` requests to some oracle language in `NP`, for a constant `c` +[Hemaspaandra and Ogihara, p. 1][HemaspaandraOgihara2002] [Kadin][Kadin1989]. It contains +`NP` and `coNP` with one request to the encodings of the satisfiable formulas +(`Cslib.Complexity.NP_subset_PWithOracleQueries_image_SAT`), through the Cook–Levin reduction, and +is closed under complement. + +## Main definitions + +* `Cslib.Complexity.ComputableInPolyTimeWithOracleQueries`: polynomial time relative to an oracle + with a bounded number of requests. +* `Cslib.Complexity.PWithOracleQueries`: the problems so decided. +* `Cslib.Complexity.PNPLog`: `P^{NP[O(log n)]}`. + +## Main results + +* `Cslib.Complexity.ComputableInPolyTimeWithOracleQueries.computableInPolyTimeWithOracle`, + `.mono`, `.map`: forgetting and weakening the bound, and postprocessing. +* `Turing.MultiTapeTM.ComputableInPolyTime.computableInPolyTimeWithOracleQueries`: polynomial time + makes no request. +* `Cslib.Complexity.computableInPolyTimeWithOracleQueries_query`: one request + (`Cslib.Complexity.queryBit`) decides membership of a polynomial-time request word. +* `Cslib.Complexity.compl_mem_PWithOracleQueries`, `Cslib.Complexity.compl_mem_PNPLog`: closure + under complement. +* `Cslib.Complexity.NP_subset_PWithOracleQueries_image_SAT`: one request to `SAT` decides a + problem in `NP`. +* `Cslib.Complexity.P_subset_PNPLog`, `Cslib.Complexity.NP_subset_PNPLog`, + `Cslib.Complexity.coNP_subset_PNPLog`, `Cslib.Complexity.PNPLog_subset_PWithOracle`. + +## References + +* [L. A. Hemaspaandra, M. Ogihara, *The Complexity Theory Companion*][HemaspaandraOgihara2002] +* [J. Kadin, *P^{NP[O(log n)]} and sparse Turing-complete sets for NP*][Kadin1989] +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Probability Turing.MultiTapeTM Cslib.Logic.PL + +variable {α β γ : Type} + +/-- `f` is computable in polynomial time relative to the oracle language `A` with at most `r n` +requests on instances of encoded length `n`: one deterministic word-query program has the +certificate of `ComputableInPolyTimeWithOracle`, computes `f` against `A`, and makes at most +`r (input a).length` requests on `a` against `A`. -/ +def ComputableInPolyTimeWithOracleQueries (A : Set (List Bool)) (r : ℕ → ℕ) (f : α → β) + (input : α ↪ List Bool) (output : β ↪ List Bool) : Prop := + ∃ program : α → WordQueryComp Unit β, + IsOraclePPTOn input output (fun a => OracleComp.interpret ProbOracleComp.query (program a)) ∧ + ∀ a, OracleComp.interpret (m := Id) (languageOracle A) (program a) = f a ∧ + (OracleComp.queriesWith (languageOracle A) (program a)).length ≤ r (input a).length + +namespace ComputableInPolyTimeWithOracleQueries + +variable {A : Set (List Bool)} {r r' : ℕ → ℕ} {input : α ↪ List Bool} {middle : β ↪ List Bool} + {output : γ ↪ List Bool} + +/-- A bounded number of requests is in particular polynomial time relative to the oracle. -/ +theorem computableInPolyTimeWithOracle {f : α → β} + (h : ComputableInPolyTimeWithOracleQueries A r f input middle) : + ComputableInPolyTimeWithOracle A f input middle := + let ⟨program, hprogram, heq⟩ := h + ⟨program, hprogram, fun a => (heq a).1⟩ + +/-- Weakening the bound on the requests. -/ +theorem mono {f : α → β} (h : ComputableInPolyTimeWithOracleQueries A r f input middle) + (hr : ∀ n, r n ≤ r' n) : ComputableInPolyTimeWithOracleQueries A r' f input middle := + let ⟨program, hprogram, heq⟩ := h + ⟨program, hprogram, fun a => ⟨(heq a).1, (heq a).2.trans (hr _)⟩⟩ + +/-- Polynomial-time postprocessing of the result makes no further request. -/ +theorem map {f : α → β} {g : β → γ} (h : ComputableInPolyTimeWithOracleQueries A r f input middle) + (hg : ComputableInPolyTime g middle output) : + ComputableInPolyTimeWithOracleQueries A r (fun a => g (f a)) input output := by + obtain ⟨program, hprogram, heq⟩ := h + refine ⟨fun a => g <$> program a, ?_, fun a => ⟨?_, ?_⟩⟩ + · simpa only [OracleComp.interpret_map] using hprogram.map' hg + · rw [OracleComp.interpret_map, (heq a).1] + rfl + · rw [OracleComp.queriesWith_map] + exact (heq a).2 + +end ComputableInPolyTimeWithOracleQueries + +/-- A polynomial-time computation makes no request. -/ +theorem _root_.Turing.MultiTapeTM.ComputableInPolyTime.computableInPolyTimeWithOracleQueries + {f : α → β} {input : α ↪ List Bool} {output : β ↪ List Bool} + (h : ComputableInPolyTime f input output) (A : Set (List Bool)) (r : ℕ → ℕ) : + ComputableInPolyTimeWithOracleQueries A r f input output := + ⟨fun a => pure (f a), h.isOraclePPTOn, fun _ => ⟨rfl, by simp⟩⟩ + +open Classical in +/-- **One request** to `A` for the polynomial-time word `req a` decides whether `req a ∈ A`. -/ +theorem computableInPolyTimeWithOracleQueries_query {req : α → List Bool} + {input : α ↪ List Bool} (hreq : ComputableInPolyTime req input encBits) + (A : Set (List Bool)) : + ComputableInPolyTimeWithOracleQueries A (fun _ => 1) (fun a => decide (req a ∈ A)) input + encBool := + ⟨fun a => queryBit (req a), isOraclePPTOn_queryBit hreq, + fun a => ⟨interpret_queryBit A (req a), by simp⟩⟩ + +/-- The problems decided in polynomial time relative to the oracle language `A` with at most +`r n` requests on instances of encoded length `n`. -/ +def PWithOracleQueries (A : Set (List Bool)) (r : ℕ → ℕ) (enc : α ↪ List Bool) : Set (Set α) := + {L | ∃ f : α → Bool, ComputableInPolyTimeWithOracleQueries A r f enc encBool ∧ + ∀ a, a ∈ L ↔ f a = true} + +theorem mem_PWithOracleQueries {A : Set (List Bool)} {r : ℕ → ℕ} {enc : α ↪ List Bool} + {L : Set α} : + L ∈ PWithOracleQueries A r enc ↔ ∃ f : α → Bool, + ComputableInPolyTimeWithOracleQueries A r f enc encBool ∧ ∀ a, a ∈ L ↔ f a = true := + Iff.rfl + +/-- **`P^{NP[O(log n)]}`**: the problems decided in polynomial time with at most `c log₂ n + c` +requests, on instances of encoded length `n`, to some oracle language in `NP` +[Hemaspaandra and Ogihara, p. 1][HemaspaandraOgihara2002] [Kadin][Kadin1989]. -/ +def PNPLog (enc : α ↪ List Bool) : Set (Set α) := + ⋃ A ∈ NP encBits, ⋃ c : ℕ, PWithOracleQueries A (fun n => c * Nat.log 2 n + c) enc + +theorem mem_PNPLog {enc : α ↪ List Bool} {L : Set α} : + L ∈ PNPLog enc ↔ ∃ A ∈ NP encBits, ∃ c : ℕ, + L ∈ PWithOracleQueries A (fun n => c * Nat.log 2 n + c) enc := by + simp only [PNPLog, Set.mem_iUnion, exists_prop] + +section + +variable {A : Set (List Bool)} {r r' : ℕ → ℕ} {enc : α ↪ List Bool} {L : Set α} + +theorem PWithOracleQueries_subset_PWithOracle : + PWithOracleQueries A r enc ⊆ PWithOracle A enc := fun _ ⟨f, hf, hL⟩ => + ⟨f, hf.computableInPolyTimeWithOracle, hL⟩ + +theorem PWithOracleQueries_mono (hr : ∀ n, r n ≤ r' n) : + PWithOracleQueries A r enc ⊆ PWithOracleQueries A r' enc := fun _ ⟨f, hf, hL⟩ => + ⟨f, hf.mono hr, hL⟩ + +/-- A problem in `P` is decided with no request. -/ +theorem P_subset_PWithOracleQueries (A : Set (List Bool)) (r : ℕ → ℕ) (enc : α ↪ List Bool) : + P enc ⊆ PWithOracleQueries A r enc := fun _ ⟨f, hf, hL⟩ => + ⟨f, hf.computableInPolyTimeWithOracleQueries A r, hL⟩ + +/-- **Closure under complement**: negate the decider, with the same requests. -/ +theorem compl_mem_PWithOracleQueries (hL : L ∈ PWithOracleQueries A r enc) : + Lᶜ ∈ PWithOracleQueries A r enc := by + obtain ⟨f, hf, hL⟩ := hL + refine ⟨fun a => !f a, hf.map (by fun_prop), fun a => ?_⟩ + rw [Set.mem_compl_iff, hL a] + simp + +open Classical in +/-- A problem that reduces in polynomial time to the oracle language is decided with one +request. -/ +theorem mem_PWithOracleQueries_of_reduction {req : α → List Bool} + (hreq : ComputableInPolyTime req enc encBits) (h : ∀ a, a ∈ L ↔ req a ∈ A) : + L ∈ PWithOracleQueries A (fun _ => 1) enc := + ⟨_, computableInPolyTimeWithOracleQueries_query hreq A, fun a => by + rw [h a, decide_eq_true_iff]⟩ + +/-- **One request to `SAT` decides a problem in `NP`**: the request is the encoding of the +Cook–Levin reduction of the instance. -/ +theorem NP_subset_PWithOracleQueries_image_SAT (enc : α ↪ List Bool) : + NP enc ⊆ PWithOracleQueries (encCNF '' SAT) (fun _ => 1) enc := by + intro L hL + obtain ⟨f, hf, hfL⟩ := CookLevin.SAT_npHard enc L hL + have henc : ComputableInPolyTime (fun φ : CNF ℕ => encCNF φ) encCNF encBits := + .of_eq fun _ => rfl + refine mem_PWithOracleQueries_of_reduction (req := fun a => encCNF (f a)) (henc.comp hf) + fun a => ?_ + rw [hfL a] + exact ⟨fun h => ⟨f a, h, rfl⟩, fun ⟨_, h, he⟩ => encCNF.injective he ▸ h⟩ + +/-- A problem decided with `c log₂ n + c` requests to an oracle language in `NP` is in +`P^{NP[O(log n)]}`. -/ +theorem mem_PNPLog_of_mem_PWithOracleQueries (hA : A ∈ NP encBits) {c : ℕ} + (hL : L ∈ PWithOracleQueries A (fun n => c * Nat.log 2 n + c) enc) : L ∈ PNPLog enc := + mem_PNPLog.mpr ⟨A, hA, c, hL⟩ + +/-- **`P^{NP[O(log n)]}` is closed under complement.** -/ +theorem compl_mem_PNPLog (hL : L ∈ PNPLog enc) : Lᶜ ∈ PNPLog enc := by + obtain ⟨A, hA, c, hL⟩ := mem_PNPLog.mp hL + exact mem_PNPLog_of_mem_PWithOracleQueries hA (compl_mem_PWithOracleQueries hL) + +/-- `P ⊆ P^{NP[O(log n)]}`. -/ +theorem P_subset_PNPLog (enc : α ↪ List Bool) : P enc ⊆ PNPLog enc := fun _ hL => + mem_PNPLog_of_mem_PWithOracleQueries image_SAT_mem_NP + (P_subset_PWithOracleQueries _ (fun n => 0 * Nat.log 2 n + 0) enc hL) + +/-- **`NP ⊆ P^{NP[O(log n)]}`**, with one request to `SAT`. -/ +theorem NP_subset_PNPLog (enc : α ↪ List Bool) : NP enc ⊆ PNPLog enc := fun _ hL => + mem_PNPLog_of_mem_PWithOracleQueries (c := 1) image_SAT_mem_NP + (PWithOracleQueries_mono (fun n => by omega) (NP_subset_PWithOracleQueries_image_SAT enc hL)) + +/-- **`coNP ⊆ P^{NP[O(log n)]}`**. -/ +theorem coNP_subset_PNPLog (enc : α ↪ List Bool) : coNP enc ⊆ PNPLog enc := fun _ hL => by + simpa only [compl_compl] using compl_mem_PNPLog (NP_subset_PNPLog enc hL) + +/-- `P^{NP[O(log n)]} ⊆ P^{NP}`. -/ +theorem PNPLog_subset_PWithOracle (enc : α ↪ List Bool) : + PNPLog enc ⊆ ⋃ A ∈ NP encBits, PWithOracle A enc := by + intro L hL + obtain ⟨A, hA, c, hL⟩ := mem_PNPLog.mp hL + exact Set.mem_biUnion hA (PWithOracleQueries_subset_PWithOracle hL) + +end + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Oracle/Closure.lean b/Complexity/Computability/Complexity/Classes/Oracle/Closure.lean index ad11c518c5..79bc4cf1f4 100644 --- a/Complexity/Computability/Complexity/Classes/Oracle/Closure.lean +++ b/Complexity/Computability/Complexity/Classes/Oracle/Closure.lean @@ -10,6 +10,7 @@ module public import Complexity.Computability.Complexity.Classes.Oracle public import Complexity.Computability.Complexity.Reduction public import Complexity.Computability.Probabilistic.Oracle.Composition +import Complexity.Computability.Complexity.Classes.Oracle.QueryBit import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat @@ -23,7 +24,7 @@ Polynomial time relative to an oracle composes with polynomial-time pre- and pos `Cslib.Complexity.ComputableInPolyTimeWithOracle.map`), through the compositional rules of strict oracle PPT. One request decides the oracle language itself (`Cslib.Complexity.computableInPolyTimeWithOracle_mem`): the request is copied onto the query -buffer, and only the first bit of the reply is read. +buffer, and only the first bit of the reply is read (`Cslib.Complexity.queryBit`). Hence `P` relative to `A` contains `A` and lies inside `NP` relative to `A`, and both classes are closed under polynomial-time many-one reductions of the problem @@ -118,18 +119,9 @@ theorem PolyTimeOracleProgram.of_query {req : α → List Bool} {input : α ↪ {Admissible : Set (List Bool) → Prop} {Spec : (α → Bool) → Prop} (hreq : ComputableInPolyTime req input encBits) (hSpec : ∀ A : Set (List Bool), Admissible A → Spec (fun a => decide (req a ∈ A))) : - PolyTimeOracleProgram input encBool Admissible Spec := by - refine ⟨fun a => (fun reply => decide (reply = [true])) <$> - (List.take 1 <$> OracleComp.query ((), req a)), ?_, fun A hA => ?_⟩ - · simp only [OracleComp.interpret_map, OracleComp.interpret_query] - exact (IsOraclePPTOn.query_take' () hreq (limit := fun _ => 1) - (ComputableInPolyTime.const 1)).map' - (ComputableInPolyTime.id'.decide_eq' (ComputableInPolyTime.const [true])) - · convert hSpec A hA using 1 - funext a - change decide (List.take 1 (languageOracle A ((), req a)) = [true]) = decide (req a ∈ A) - rw [languageOracle_apply] - cases decide (req a ∈ A) <;> rfl + PolyTimeOracleProgram input encBool Admissible Spec := + ⟨fun a => queryBit (req a), isOraclePPTOn_queryBit hreq, fun A hA => by + simpa only [interpret_queryBit] using hSpec A hA⟩ open Classical in /-- **One request decides the oracle language.** The word is copied onto the query buffer and diff --git a/Complexity/Computability/Complexity/Classes/Oracle/QueryBit.lean b/Complexity/Computability/Complexity/Classes/Oracle/QueryBit.lean new file mode 100644 index 0000000000..701bbd70be --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Oracle/QueryBit.lean @@ -0,0 +1,72 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Oracle +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +import Complexity.Computability.Probabilistic.Oracle.Composition + +/-! +# One request to an oracle language + +`Cslib.Complexity.queryBit w` submits the word `w` and reads the first bit of the reply. Against +an oracle language `A` it returns `decide (w ∈ A)` and makes exactly the one request `w`; for a +polynomial-time word it is strict oracle PPT, since the word is written on the query buffer and +only one bit of the reply is read. The oracle closure rules and the bounded-query classes build +their one-request certificates from this program. + +## Main definitions + +* `Cslib.Complexity.queryBit`: one request, reading one bit of the reply. + +## Main results + +* `Cslib.Complexity.interpret_queryBit`: against `A`, the request answers membership in `A`. +* `Cslib.Complexity.queriesWith_queryBit`: the program makes exactly one request. +* `Cslib.Complexity.isOraclePPTOn_queryBit`: one request for a polynomial-time word is strict + oracle PPT. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Probability Turing.MultiTapeTM + +variable {α : Type} + +/-- **One request**: submit the word `w` and read the first bit of the reply. -/ +def queryBit (w : List Bool) : WordQueryComp Unit Bool := + (fun reply => decide (reply = [true])) <$> (List.take 1 <$> OracleComp.query ((), w)) + +open Classical in +/-- Against an oracle language, one request answers membership of its word. -/ +theorem interpret_queryBit (A : Set (List Bool)) (w : List Bool) : + OracleComp.interpret (m := Id) (languageOracle A) (queryBit w) = decide (w ∈ A) := by + change decide (List.take 1 (languageOracle A ((), w)) = [true]) = decide (w ∈ A) + rw [languageOracle_apply] + cases decide (w ∈ A) <;> rfl + +/-- One request makes one request. -/ +@[simp] +theorem queriesWith_queryBit (answer : Unit × List Bool → List Bool) (w : List Bool) : + OracleComp.queriesWith answer (queryBit w) = [((), w)] := by + simp [queryBit] + +/-- One request for a polynomial-time word is strict oracle PPT: the word is written on the query +buffer and one bit of the reply is read. -/ +theorem isOraclePPTOn_queryBit {req : α → List Bool} {input : α ↪ List Bool} + (hreq : ComputableInPolyTime req input encBits) : + IsOraclePPTOn input encBool + (fun a => OracleComp.interpret ProbOracleComp.query (queryBit (req a))) := by + simp only [queryBit, OracleComp.interpret_map, OracleComp.interpret_query] + exact (IsOraclePPTOn.query_take' () hreq (limit := fun _ => 1) + (ComputableInPolyTime.const 1)).map' + (ComputableInPolyTime.id'.decide_eq' (ComputableInPolyTime.const [true])) + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/PH/Kannan.lean b/Complexity/Computability/Complexity/Classes/PH/Kannan.lean index c85c3142bd..ca03474d3f 100644 --- a/Complexity/Computability/Complexity/Classes/PH/Kannan.lean +++ b/Complexity/Computability/Complexity/Classes/PH/Kannan.lean @@ -9,6 +9,7 @@ module public import Complexity.Computability.Circuit.Boolean.PPoly public import Complexity.Computability.Circuit.Boolean.Substitution +public import Complexity.Computability.Complexity.Classes.NP.Image public import Complexity.Computability.Complexity.Classes.PH public import Complexity.Computability.Complexity.Problems.SAT public import Complexity.Computability.MCSP.NP @@ -62,10 +63,6 @@ not say which of the two languages witnesses the theorem. ## Main results -* `Cslib.Complexity.mem_NP_image`: the encodings of the members of a problem in `NP` form a - problem in `NP` on words, when the encoding has a decoder. -* `Cslib.Complexity.image_SAT_mem_NP`: the encodings of the satisfiable formulas form a problem in - `NP` on words. * `Cslib.Complexity.Kannan.language_notMem_SIZE`: Kannan's language has no circuits of size `O(n ^ k)`. * `Cslib.Complexity.Kannan.language_mem_SigmaP_three`: Kannan's language is in `Σ₃`. @@ -87,32 +84,6 @@ namespace Cslib.Complexity open Circuits Circuits.Boolean Turing.MultiTapeTM Filter -/-! ### Images of problems in `NP` -/ - -/-- **Images of problems in `NP`.** The encodings of the members of a problem in `NP` form a -problem in `NP` on words, when the encoding has a decoder: the verifier decodes the word, checks -that it encodes what was decoded, and runs the verifier of the problem. -/ -theorem mem_NP_image {β : Type} {encB : β ↪ List Bool} (D : Decoder encB) {B : Set β} - (hB : B ∈ NP encB) : encB '' B ∈ NP encBits := by - obtain ⟨p, V, hV, hBV⟩ := mem_NP.mp hB - have hdec : ComputableInPolyTime (fun x : List Bool × List Bool => D.decode x.1) - (encPair encBits encBits) encB := D.computableInPolyTime.comp (.fst _ _) - have henc : ComputableInPolyTime (fun b => encB b) encB encBits := .of_eq fun _ => rfl - refine mem_NP.mpr - ⟨p, fun x => decide (encB (D.decode x.1) = x.1) && V (D.decode x.1, x.2), by fun_prop, - fun w => ?_⟩ - simp only [encBits_apply, Bool.and_eq_true, decide_eq_true_eq] - constructor - · rintro ⟨b, hb, rfl⟩ - obtain ⟨c, hc, hVc⟩ := (hBV b).1 hb - exact ⟨c, hc, by rw [D.decode_enc], by rw [D.decode_enc]; exact hVc⟩ - · rintro ⟨c, hc, hw, hVc⟩ - exact ⟨D.decode w, (hBV _).2 ⟨c, by rw [hw]; exact hc, hVc⟩, hw⟩ - -/-- The encodings of the satisfiable formulas form a problem in `NP` on words. -/ -theorem image_SAT_mem_NP : encCNF '' SAT ∈ NP encBits := - mem_NP_image (.list (.list (.pair .nat .bool))) SAT.mem_NP - namespace Kannan /-! ### Parameters -/ diff --git a/ComplexityTests/OracleBoundedQuery.lean b/ComplexityTests/OracleBoundedQuery.lean new file mode 100644 index 0000000000..3e4fd93776 --- /dev/null +++ b/ComplexityTests/OracleBoundedQuery.lean @@ -0,0 +1,56 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ +import Complexity.Computability.Complexity.Classes.NP.Image +import Complexity.Computability.Complexity.Classes.Oracle.BoundedQuery +import Complexity.Computability.Complexity.Problems.SAT.PolyTime + +/-! # Bounded-query oracle classes and `P^{NP[O(log n)]}` -/ + +namespace ComplexityTests.OracleBoundedQuery + +open Cslib Cslib.Complexity Cslib.Logic.PL Turing.MultiTapeTM + +-- One request decides the oracle language itself, with the bound `1` at every length. +open Classical in +example (A : Set (List Bool)) : + ComputableInPolyTimeWithOracleQueries A (fun _ => 1) (fun w => decide (w ∈ A)) encBits + encBool := + computableInPolyTimeWithOracleQueries_query ComputableInPolyTime.id' A + +-- The single-request program asks exactly the word it decides: one request, answered by `A`. +example (A : Set (List Bool)) (w : List Bool) : + OracleComp.queriesWith (languageOracle A) (queryBit w) = [((), w)] := by + simp + +open Classical in +example : OracleComp.interpret (m := Id) (languageOracle {[true]}) (queryBit [true]) = true := by + rw [interpret_queryBit] + simp + +-- `SAT` and its complement are in `P^{NP[O(log n)]}`. +example : SAT ∈ PNPLog encCNF := NP_subset_PNPLog encCNF SAT.mem_NP + +example : SATᶜ ∈ PNPLog encCNF := compl_mem_PNPLog (NP_subset_PNPLog encCNF SAT.mem_NP) + +-- `SAT` is decided by one request to the encodings of the satisfiable formulas, and so lies in +-- `P` relative to that oracle. +example : SAT ∈ PWithOracleQueries (encCNF '' SAT) (fun _ => 1) encCNF := + NP_subset_PWithOracleQueries_image_SAT encCNF SAT.mem_NP + +example : SAT ∈ PWithOracle (encCNF '' SAT) encCNF := + PWithOracleQueries_subset_PWithOracle (NP_subset_PWithOracleQueries_image_SAT encCNF SAT.mem_NP) + +-- The classes grow with the bound on the requests. +example : PWithOracleQueries (encCNF '' SAT) (fun _ => 1) encCNF ⊆ + PWithOracleQueries (encCNF '' SAT) (fun n => Nat.log 2 n + 1) encCNF := + PWithOracleQueries_mono fun _ => by omega + +-- A polynomial-time problem needs no request at all: the bound `0` suffices. +example : (Set.univ : Set (List Bool)) ∈ PWithOracleQueries ∅ (fun _ => 0) encBits := + P_subset_PWithOracleQueries ∅ _ encBits ⟨fun _ => true, ComputableInPolyTime.const true, + fun _ => by simp⟩ + +end ComplexityTests.OracleBoundedQuery diff --git a/references.bib b/references.bib index 6911ba6068..fdb300790e 100644 --- a/references.bib +++ b/references.bib @@ -2362,6 +2362,16 @@ @misc{HemaspaandraHemaspaandraHempel1999 abstract in SIGACT News 29, 10--22, 1998} } +@book{HemaspaandraOgihara2002, + author = {Hemaspaandra, Lane A. and Ogihara, Mitsunori}, + title = {The Complexity Theory Companion}, + series = {Texts in Theoretical Computer Science. An EATCS Series}, + publisher = {Springer}, + address = {Berlin, Heidelberg}, + year = {2002}, + doi = {10.1007/978-3-662-04880-1} +} + @article{ Hennessy1985, author = {Matthew Hennessy and Robin Milner}, @@ -2762,6 +2772,17 @@ @article{Kadin1988 doi = {10.1137/0217080} } +@article{Kadin1989, + author = {Kadin, Jim}, + title = {{$P^{NP[O(\log n)]}$} and Sparse {Turing}-Complete Sets for {NP}}, + journal = {Journal of Computer and System Sciences}, + volume = {39}, + number = {3}, + pages = {282--298}, + year = {1989}, + doi = {10.1016/0022-0000(89)90024-X} +} + @article{Kadin1991, author = {Kadin, Jim}, title = {Erratum: The Polynomial Time Hierarchy Collapses if the {Boolean} Hierarchy Collapses}, From f5834e419a7422e76cbcea9352e869d9e3084c5a Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 31 Oct 2026 17:02:12 +0000 Subject: [PATCH 16/16] feat(Classes/Hierarchy): coarse nondeterministic time hierarchy by lazy diagonalization (#2031) * feat(Classes/Time): explicit-time verifier characterization of NTIME (#1572) Guess and verify within explicit time (`setOf_exists_mem_NTIME`), through a closed fair-coin composition with the new framed sampler `pairSampler`, and a linear-time verifier for every problem in `NTIME` (`exists_verifier_of_mem_NTIME`). Infrastructure toward the nondeterministic time hierarchy. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * feat(Classes/Hierarchy): coarse nondeterministic time hierarchy by lazy diagonalization (#1572) For every degree d, NTIME((n+1)^d) is strictly contained in NTIME((n+1)^e) whenever 6d + 14 <= e, separated by the explicit lazy diagonal problem. The upper bound guesses a certificate and simulates a described verifier with the native universal machine; the brute force at the end of each interval uses a new exponential-time bounded existential, via Savitch's algorithm at depth 1. The lower bound diagonalizes against the linear-time verifier of #2025. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(Classes/Space): use the public bounded-existential edges in setOf_exists_mem_PSPACE The private existsEdge and savitch_existsEdge duplicated the public boundedExistsEdge and savitch_boundedExistsEdge; expose the definition so clients can match it against Savitch's edge test. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * doc(Classes/Hierarchy): state the interval cases of the lazy diagonal verifier precisely Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(Machines/PTM): make the pair sampler's proof configurations private The induction configurations `PairSampler.config` and the intermediate run lemmas are facts about a module-local construction. Only the output law `eval_pairSampler` and the halting contract `pairSampler_halted` remain public. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG * refactor(BigO/Bounds): make ComputableInO.mono_time public Weakening only the time bound of `ComputableInO`, keeping the space bound, is a general monotonicity law. It now sits beside `ComputableInO.mono` instead of as a private copy in the lazy-diagonal hierarchy. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --------- Co-authored-by: Claude Opus 5.5 --- Complexity.lean | 2 + .../Hierarchy/NondeterministicTime.lean | 784 ++++++++++++++++++ .../Complexity/Classes/Space/Closure.lean | 44 +- .../Classes/Time/BoundedExists.lean | 222 +++++ .../MultiTape/Complexity/BigO/Bounds.lean | 6 + .../NondeterministicTimeHierarchy.lean | 44 + ROADMAP.md | 8 +- references.bib | 20 + 8 files changed, 1089 insertions(+), 41 deletions(-) create mode 100644 Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean create mode 100644 Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean create mode 100644 ComplexityTests/NondeterministicTimeHierarchy.lean diff --git a/Complexity.lean b/Complexity.lean index 617a932b4f..a81f767ea6 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1186,6 +1186,7 @@ public import Complexity.Computability.Complexity.Classes.FunctionTime.WordRAM public import Complexity.Computability.Complexity.Classes.Hierarchy public import Complexity.Computability.Complexity.Classes.Hierarchy.LogClock public import Complexity.Computability.Complexity.Classes.Hierarchy.Nondeterministic +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicTime public import Complexity.Computability.Complexity.Classes.Hierarchy.Polynomial public import Complexity.Computability.Complexity.Classes.Hierarchy.ShortDesc public import Complexity.Computability.Complexity.Classes.Hierarchy.Space @@ -1354,6 +1355,7 @@ public import Complexity.Computability.Complexity.Classes.TFNP.Subclasses.Princi public import Complexity.Computability.Complexity.Classes.Tally public import Complexity.Computability.Complexity.Classes.Time public import Complexity.Computability.Complexity.Classes.Time.AcceptCount +public import Complexity.Computability.Complexity.Classes.Time.BoundedExists public import Complexity.Computability.Complexity.Classes.Time.Polynomial public import Complexity.Computability.Complexity.Classes.Time.Verifier public import Complexity.Computability.Complexity.Classes.Toda diff --git a/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean new file mode 100644 index 0000000000..4f2eb8644d --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Hierarchy/NondeterministicTime.lean @@ -0,0 +1,784 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.Hierarchy.Time +public import Complexity.Foundations.Data.BitString.Enumeration +import Complexity.Computability.Complexity.Classes.Time.BoundedExists +import Complexity.Computability.Complexity.Classes.Time.Verifier +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Constructible +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Foundations.Data.Nat.PolynomialBound.Exponential + +/-! +# A nondeterministic time hierarchy by lazy diagonalization + +For every degree `d`, nondeterministic time `O((n + 1) ^ d)` is strictly contained in +nondeterministic time `O((n + 1) ^ e)` on bit strings whenever `6 * d + 14 ≤ e` +(`Cslib.Complexity.NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le`). This is a coarse form of the +nondeterministic time hierarchy theorem [Arora and Barak, Theorem 3.3][AroraBarak07], proved by +lazy diagonalization [Žák][Zak1983]. The separating problem is explicit: the lazy diagonal +`Cslib.Complexity.lazyDiagonal d`. + +## The lazy diagonal + +A diagonal for nondeterministic time cannot simply negate the answer of the machine it +simulates: that answer is an existential over certificates. Lazy diagonalization negates it only +once per interval of lengths, where the input is long enough to try every certificate. + +An input `x` carries a word `w` framed at its start, and `w` carries the description +`unframe w` of a deterministic verifier on pairs framed at its start, followed by arbitrary +padding. The padding of `w` gives each description infinitely many words `w`. The interval of +`w` consists of the lengths from `P = 2 * w.length + 1`, the length of `frame w`, to +`2 ^ clock d P - 1`, where `clock d m = (m + 1) ^ (d + 3)`. On the input `x` of length `n` in +this interval, with a certificate `v` of length `certLen d (n + 1) = (n + 2) ^ (d + 2)`, the +verifier `Cslib.Complexity.LazyDiagonal.verifier d` + +* below the end of the interval, runs the described verifier on the one bit longer input + `x ++ [false]` and the certificate `v` for `clock d (n + 1)` steps with the universal function + (`Cslib.Complexity.LazyDiagonal.accepts`); +* at the end of the interval, accepts when the described verifier accepts no certificate of + length `certLen d P` on `frame w`, trying them all. + +The lazy diagonal is the problem of the inputs with an accepted certificate. + +* **Upper bound** (`Cslib.Complexity.lazyDiagonal_mem_NTIME`). Guess the certificate and run the + verifier (`Cslib.Complexity.setOf_exists_mem_NTIME`). The simulation takes time + `O((n + 1) ^ (6 * d + 14))`: polynomial in the input, the certificate and the clock, through the + native universal machine. The brute force takes time `2 ^ O((P + 1) ^ (d + 2))` + (`Cslib.Complexity.exists_computableInO_any_enumList`), which is `O(n)` at the end of the + interval, where `n + 1 = 2 ^ ((P + 1) ^ (d + 3))`. +* **Lower bound** (`Cslib.Complexity.lazyDiagonal_notMem_NTIME`). A nondeterministic machine + deciding the problem within time `O((n + 1) ^ d)` has a deterministic verifier on pairs within + linear time, accepting certificates of every length at least `c * (n + 1) ^ d + c` + (`Cslib.Complexity.exists_verifier_of_mem_NTIME`). On the interval of a word `w` describing that + verifier, with enough padding, the universal function runs it exactly. Then each input of the + interval is in the problem exactly when the next one is, and the last exactly when the first is + not. + +Arora and Barak enumerate nondeterministic machines and use one global sequence of intervals; this +construction gives each word `w` its own interval instead, and diagonalizes against verifiers. +The gap `6 * d + 14` is the polynomial overhead of the native universal machine and of the +certificate length, not the textbook `f (n + 1) = o (g n)`. + +## Main definitions + +* `Cslib.Complexity.LazyDiagonal.verifier`: the verifier of the lazy diagonal. +* `Cslib.Complexity.lazyDiagonal`: the lazy diagonal problem for degree `d`. + +## Main results + +* `Cslib.Complexity.lazyDiagonal_mem_NTIME`: the lazy diagonal is in + `NTIME ((n + 1) ^ (6 * d + 14))`. +* `Cslib.Complexity.lazyDiagonal_notMem_NTIME`: the lazy diagonal is not in `NTIME ((n + 1) ^ d)`. +* `Cslib.Complexity.NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le`: the hierarchy. + +## References + +* [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*, web draft of January + 2007][AroraBarak07] +* [S. Žák, *A Turing machine time hierarchy*][Zak1983] +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Turing Turing.MultiTapeTM Turing.MultiTapeTM.Universal + +namespace LazyDiagonal + +/-- The certificate length of the lazy diagonal on words of length `m`: `(m + 1) ^ (d + 2)`. -/ +def certLen (d m : ℕ) : ℕ := (m + 1) ^ (d + 2) + +/-- The clock of the lazy diagonal on words of length `m`: `(m + 1) ^ (d + 3)`. It bounds the +simulated steps on `m`, and the interval of lengths starting at `m` ends below `2 ^ clock d m`. -/ +def clock (d m : ℕ) : ℕ := (m + 1) ^ (d + 3) + +/-- The machine described by `c`, run as a verifier on the pair `(y, v)` (encoded as +`frame y ++ v`) for `clock d y.length` steps with the universal function, has halted with output +`[true]`. -/ +def accepts (d : ℕ) (c y v : List Bool) : Bool := + !Diagonal.verdict (universal (decodeDesc c, (frame y ++ v).map eBit, clock d y.length)) + +/-- **The verifier of the lazy diagonal**, on an input `x` of length `n` and a certificate `v`. +The input carries a word `w` framed at its start, and `w` carries the description `unframe w` +framed at its start. When the description is short (`Cslib.Complexity.Diagonal.shortDesc w`), +let `P = 2 * w.length + 1`; the lengths from `P` to `2 ^ clock d P - 1` form the interval of `w`. + +* If `n + 1 < 2 ^ clock d P`, below the end of the interval, accept when the described verifier + accepts `v` on the input one bit longer. +* If `n + 1 = 2 ^ clock d P`, at the end of the interval, accept when the described verifier + accepts no certificate on `frame w`, the input of length `P`, by trying them all. +* Otherwise, and when the description is not short, reject. -/ +def verifier (d : ℕ) (p : List Bool × List Bool) : Bool := + if Diagonal.shortDesc (unframe p.1) then + if Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1) then + accepts d (unframe (unframe p.1)) (p.1 ++ [false]) p.2 + else if Nat.size p.1.length ≤ clock d (2 * (unframe p.1).length + 1) then + !(Cslib.BitString.enumList (certLen d (2 * (unframe p.1).length + 1))).any + (accepts d (unframe (unframe p.1)) (frame (unframe p.1))) + else false + else false + +end LazyDiagonal + +open LazyDiagonal + +/-- **The lazy diagonal language** for degree `d`: the words `x` with a certificate of length +`(x.length + 2) ^ (d + 2)` accepted by `Cslib.Complexity.LazyDiagonal.verifier d`. -/ +def lazyDiagonal (d : ℕ) : Set (List Bool) := + {x | ∃ v : List Bool, v.length = certLen d (x.length + 1) ∧ verifier d (x, v) = true} + +/-! ### The lazy diagonal is not in `NTIME ((n + 1) ^ d)` -/ + +/-- The lengths at which certificates of length `certLen d m` and `clock d m` simulated steps +suffice for a verifier with constants `c` and `c₂`. -/ +private theorem exists_le_certLen_clock (d c c₂ : ℕ) : ∃ M, ∀ m, M ≤ m → + c * (m + 1) ^ d + c ≤ certLen d m ∧ c₂ * (2 * m + 1 + certLen d m + 1) ≤ clock d m := by + refine ⟨2 * c + 3 * c₂, fun m hm => ⟨?_, ?_⟩⟩ + · have hX : 1 ≤ (m + 1) ^ d := Nat.one_le_pow _ _ (by omega) + have e : certLen d m = (m + 1) ^ d * ((m + 1) * (m + 1)) := by + simp only [certLen]; ring + rw [e] + have : 2 * c ≤ (m + 1) * (m + 1) := by nlinarith + nlinarith + · have hX : 1 ≤ (m + 1) ^ d := Nat.one_le_pow _ _ (by omega) + have e₁ : certLen d m = (m + 1) ^ d * ((m + 1) * (m + 1)) := by + simp only [certLen]; ring + have e₂ : clock d m = (m + 1) ^ d * ((m + 1) * (m + 1)) * (m + 1) := by + simp only [clock]; ring + rw [e₁, e₂] + set Y := (m + 1) ^ d * ((m + 1) * (m + 1)) + have hY : 2 * m + 2 ≤ 2 * Y := by + have h₁ : m + 1 ≤ (m + 1) * (m + 1) := Nat.le_mul_of_pos_left _ (by omega) + have h₂ : (m + 1) * (m + 1) ≤ Y := Nat.le_mul_of_pos_left _ hX + omega + have : c₂ * (2 * m + 1 + Y + 1) ≤ c₂ * (3 * Y) := Nat.mul_le_mul_left _ (by omega) + nlinarith + +/-- **The lazy diagonal is not decidable in nondeterministic time `O((n + 1) ^ d)`.** A +nondeterministic machine deciding it has a verifier on pairs within linear time +(`Cslib.Complexity.exists_verifier_of_mem_NTIME`), described by a word `code`. On the interval of +lengths of `w = frame code ++ pad` with a long enough `pad`, the universal function runs that +verifier exactly. Each word of the interval is then in the language exactly when the next one is, +and the last exactly when the first is not. -/ +theorem lazyDiagonal_notMem_NTIME (d : ℕ) : + lazyDiagonal d ∉ NTIME encBits fun n => (n + 1) ^ d := by + intro hD + obtain ⟨V, c, hV, hmem⟩ := exists_verifier_of_mem_NTIME hD + obtain ⟨c₂, ⟨k, State, hfin, tm, htm⟩, -⟩ := hV + have := Fintype.ofFinite State + let eS : State ↪ ℕ := (Fintype.equivFin State).toEmbedding.trans Fin.valEmbedding + set code := eDesc (desc eS eBit tm) with hcode + obtain ⟨M, hM⟩ := exists_le_certLen_clock d c c₂ + -- the universal function runs the verifier exactly on long enough words + have hsim (y v : List Bool) (hy : M ≤ y.length) (hv : v.length = certLen d y.length) : + accepts d code y v = V (y, v) := by + have hclock : c₂ * ((encPair encBits encBits (y, v)).length + 1) ≤ clock d y.length := by + have := (hM y.length hy).2 + simp only [encPair_apply, encBits_apply, List.length_append, length_frame, hv] + omega + obtain ⟨hhalt, hout⟩ := (htm (y, v)).runFrom_of_le hclock + simp only [encPair_apply, encBits_apply, encBool_apply] at hhalt hout + rw [accepts, hcode, decodeDesc_eDesc, universal_desc, hout] + simp only [List.map_cons, List.map_nil, Cfg.Halted] at hhalt ⊢ + rw [hhalt] + cases V (y, v) <;> simp [Diagonal.verdict, eBit] + -- on long enough words, membership is acceptance by the simulated verifier + have hiff (y : List Bool) (hy : M ≤ y.length) : y ∈ lazyDiagonal d ↔ + ∃ v : List Bool, v.length = certLen d y.length ∧ accepts d code y v = true := by + have h := hmem y (certLen d y.length) (by simpa [encBits_apply] using (hM y.length hy).1) + rw [h] + exact exists_congr fun v => and_congr_right fun hv => by rw [hsim y v hy hv] + -- the interval of `w` + set w := frame code ++ List.replicate (2 ^ code.length + M) true with hw + set P := 2 * w.length + 1 with hP + set E := clock d P with hE + set x : ℕ → List Bool := fun m => frame w ++ List.replicate (m - P) false with hx + have hwlen : w.length = 2 * code.length + 1 + (2 ^ code.length + M) := by + simp [w] + have hMP : M ≤ P := by + have := hwlen + generalize 2 ^ code.length = e at this + omega + have hxlen (m : ℕ) (hm : P ≤ m) : (x m).length = m := by + simp only [x, List.length_append, length_frame, List.length_replicate] + omega + have hshort : Diagonal.shortDesc w = true := by + have hc : unframe w = code := unframe_frame_append _ _ + rw [Diagonal.shortDesc, decide_eq_true_eq, hc, hwlen] + omega + have hverifier (m : ℕ) (hm : P ≤ m) (v : List Bool) : verifier d (x m, v) = + if Nat.size (m + 1) ≤ E then accepts d code (x m ++ [false]) v + else if Nat.size m ≤ E then + !(Cslib.BitString.enumList (certLen d P)).any (accepts d code (frame w)) + else false := by + have hu : unframe (x m) = w := unframe_frame_append _ _ + have hc : unframe w = code := unframe_frame_append _ _ + simp only [verifier, hu, hc, hshort, hxlen m hm, ← hP, ← hE, ↓reduceIte] + have hPE : P + 1 < 2 ^ E := by + have : P + 1 ≤ E := by + simp only [hE, clock] + exact (Nat.le_self_pow (by omega) _) + exact this.trans_lt Nat.lt_two_pow_self + set U := 2 ^ E - 1 with hU + -- each word below the end of the interval is in the language exactly when the next one is + have hstep (m : ℕ) (hm : P ≤ m) (hmU : m < U) : + x m ∈ lazyDiagonal d ↔ x (m + 1) ∈ lazyDiagonal d := by + have hsize : Nat.size (m + 1) ≤ E := Nat.size_le.mpr (by omega) + have hnext : x m ++ [false] = x (m + 1) := by + simp only [x, List.append_assoc, ← List.replicate_succ'] + congr 2 + omega + rw [hiff (x (m + 1)) (by rw [hxlen _ (by omega)]; omega), lazyDiagonal, Set.mem_ofPred_eq, + hxlen (m + 1) (by omega), hxlen m hm] + refine exists_congr fun v => and_congr_right fun _ => ?_ + simp only [hverifier m hm v, hsize, ↓reduceIte, hnext] + -- the end of the interval is in the language exactly when its start is not + have hend : x U ∈ lazyDiagonal d ↔ x P ∉ lazyDiagonal d := by + have hUP : P ≤ U := by omega + have hsize₁ : ¬Nat.size (U + 1) ≤ E := by + rw [Nat.size_le, hU, Nat.sub_add_cancel Nat.one_le_two_pow] + exact lt_irrefl _ + have hsize₂ : Nat.size U ≤ E := Nat.size_le.mpr (by omega) + have hbase : x P = frame w := by simp [x] + rw [hiff (x P) (by rw [hxlen P le_rfl]; omega), hxlen P le_rfl, hbase, lazyDiagonal, + Set.mem_ofPred_eq] + simp only [hverifier U hUP, hsize₁, hsize₂, ↓reduceIte, Bool.not_eq_true', + List.any_eq_false, Cslib.BitString.mem_enumList, not_exists, not_and, Bool.not_eq_true] + constructor + · rintro ⟨-, -, h⟩ v hv + exact h v hv + · intro h + exact ⟨List.replicate _ false, List.length_replicate .., h⟩ + -- the chain from the start of the interval to its end + have hchain (j : ℕ) (hj : j ≤ U - P) : x (U - j) ∈ lazyDiagonal d ↔ x U ∈ lazyDiagonal d := by + induction j with + | zero => rfl + | succ j ih => + rw [← ih (by omega), hstep (U - (j + 1)) (by omega) (by omega)] + congr! 2 + omega + have h := hchain (U - P) le_rfl + rw [Nat.sub_sub_self (by omega), hend] at h + exact iff_not_self h + +/-! ### The lazy diagonal is in nondeterministic polynomial time -/ + +/-- The bound on the simulation of `Cslib.Complexity.LazyDiagonal.accepts`, for a decoder of +degree below `D`. -/ +private def accBound (d D : ℕ) (q : List Bool × List Bool × List Bool) : ℕ := + ((q.1.length + 1) ^ D + q.2.1.length + q.2.2.length + 1) ^ 4 * (q.2.1.length + 1) ^ (2 * d + 6) + +/-- The binary length of the clock is linear in the length of the input. -/ +private theorem length_encNat_clock_le (d y : ℕ) : + (encNat ((y + 1) ^ (d + 3))).length ≤ (d + 3) * (y + 1) := by + rw [encNat_apply, Nat.size_eq_bits_len, Nat.size_le, mul_comm, pow_mul] + exact Nat.pow_lt_pow_left Nat.lt_two_pow_self (by omega) + +/-- The numeric core of the resources of the simulation: for an encoded input of length `N`, a +decoding time `A`, a word `W = frame y ++ v`, a decoded description of encoded length `Dl`, encoded +symbols of length `Nl`, a clock `Y` of `Ll` binary digits and a result of encoded length `E`, +everything is within `K * X ^ 4 * Y ^ 2`. -/ +private theorem accBound_aux (d cD cO : ℕ) : ∃ K, ∀ {N A W Dl Nl Ll X Y E : ℕ}, + N ≤ 2 * X → A ≤ X → W ≤ 2 * X → Dl ≤ cD * (N + A + 1) → Nl ≤ 3 * W → + Ll ≤ (d + 4) * X → 1 ≤ X → 1 ≤ Y → + E ≤ cO * (N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) + 1) → + N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 4 * (Y + 1) ^ 2 + + (E + 1) ^ 2 + 1 ≤ K * (X ^ 4 * Y ^ 2) ∧ + N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) + E + 1 ≤ + K * (X ^ 4 * Y ^ 2) := by + set C₁ := 8 * cD + d + 19 + set C₂ := 11 + 2 * C₁ ^ 2 + refine ⟨10 + 4 * C₁ ^ 4 + (cO * C₂ + 1) ^ 2 + 1 + C₂ * (cO + 1), + fun {N A W Dl Nl Ll X Y E} hN hA hW hD hNl hLl hX hY hE => ?_⟩ + have hIn : 2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1 ≤ C₁ * X := by + have h₁ : cD * (N + A + 1) ≤ cD * (4 * X) := Nat.mul_le_mul_left _ (by omega) + have h₂ : (d + 4) * X = d * X + 4 * X := by ring + have h₃ : d * X ≤ d * X := le_rfl + have e : C₁ * X = 8 * (cD * X) + d * X + 19 * X := by simp only [C₁]; ring + have e' : cD * (4 * X) = 4 * (cD * X) := by ring + omega + have hXY : X * Y ≤ X ^ 2 * Y := Nat.mul_le_mul_right _ (Nat.le_self_pow (by norm_num) _) + have hX2Y : X ^ 2 * Y ≤ X ^ 4 * Y ^ 2 := + Nat.mul_le_mul (Nat.pow_le_pow_right hX (by norm_num)) (Nat.le_self_pow (by norm_num) _) + have hpre : N + A + (N + W + (N + Y)) ≤ 10 * (X * Y) := by + have h₁ : X ≤ X * Y := Nat.le_mul_of_pos_right _ hY + have h₂ : Y ≤ X * Y := Nat.le_mul_of_pos_left _ hX + omega + have hY1 : Y + 1 ≤ 2 * Y := by omega + have hU₂ : (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) ≤ 2 * C₁ ^ 2 * (X ^ 2 * Y) := + calc _ ≤ (C₁ * X) ^ 2 * (2 * Y) := Nat.mul_le_mul (Nat.pow_le_pow_left hIn 2) hY1 + _ = 2 * C₁ ^ 2 * (X ^ 2 * Y) := by ring + have hU₄ : (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 4 * (Y + 1) ^ 2 ≤ + 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) := + calc _ ≤ (C₁ * X) ^ 4 * (2 * Y) ^ 2 := + Nat.mul_le_mul (Nat.pow_le_pow_left hIn 4) (Nat.pow_le_pow_left hY1 2) + _ = 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) := by ring + have hS : N + A + (N + W + (N + Y)) + (2 * Dl + 1 + (2 * Nl + 1 + Ll) + 1) ^ 2 * (Y + 1) + 1 ≤ + C₂ * (X ^ 2 * Y) := by + have h₁ : 1 ≤ X ^ 2 * Y := Nat.mul_pos (Nat.pow_pos hX) hY + have e : C₂ * (X ^ 2 * Y) = 11 * (X ^ 2 * Y) + 2 * C₁ ^ 2 * (X ^ 2 * Y) := by + simp only [C₂]; ring + omega + have hE' : E ≤ cO * C₂ * (X ^ 2 * Y) := + hE.trans ((Nat.mul_le_mul_left cO hS).trans_eq (by ring)) + have hE2 : (E + 1) ^ 2 ≤ (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) := by + have h₁ : 1 ≤ X ^ 2 * Y := Nat.mul_pos (Nat.pow_pos hX) hY + have h₂ : E + 1 ≤ (cO * C₂ + 1) * (X ^ 2 * Y) := by + have e : (cO * C₂ + 1) * (X ^ 2 * Y) = cO * C₂ * (X ^ 2 * Y) + X ^ 2 * Y := by ring + omega + calc (E + 1) ^ 2 ≤ ((cO * C₂ + 1) * (X ^ 2 * Y)) ^ 2 := Nat.pow_le_pow_left h₂ 2 + _ = (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) := by ring + have h1 : 1 ≤ X ^ 4 * Y ^ 2 := Nat.mul_pos (Nat.pow_pos hX) (Nat.pow_pos hY) + have hXYW : X * Y ≤ X ^ 4 * Y ^ 2 := hXY.trans hX2Y + have hU₂' := hU₂.trans (Nat.mul_le_mul_left (2 * C₁ ^ 2) hX2Y) + constructor + · have e : (10 + 4 * C₁ ^ 4 + (cO * C₂ + 1) ^ 2 + 1 + C₂ * (cO + 1)) * (X ^ 4 * Y ^ 2) = + 10 * (X ^ 4 * Y ^ 2) + 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) + + (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) + X ^ 4 * Y ^ 2 + + C₂ * (cO + 1) * (X ^ 4 * Y ^ 2) := by + ring + have := Nat.mul_le_mul_left 10 hXYW + omega + · have e : (10 + 4 * C₁ ^ 4 + (cO * C₂ + 1) ^ 2 + 1 + C₂ * (cO + 1)) * (X ^ 4 * Y ^ 2) = + 10 * (X ^ 4 * Y ^ 2) + 4 * C₁ ^ 4 * (X ^ 4 * Y ^ 2) + + (cO * C₂ + 1) ^ 2 * (X ^ 4 * Y ^ 2) + X ^ 4 * Y ^ 2 + + (C₂ * (X ^ 4 * Y ^ 2) + cO * C₂ * (X ^ 4 * Y ^ 2)) := by + ring + have h₁ := Nat.mul_le_mul_left C₂ hX2Y + have h₂ := Nat.mul_le_mul_left (cO * C₂) hX2Y + omega + +/-- The simulation runs within time polynomial in the description, the input and the +certificate. -/ +private theorem exists_computableInO_accepts (d : ℕ) : ∃ D, ComputableInO + (fun q : List Bool × List Bool × List Bool => accepts d q.1 q.2.1 q.2.2) + (encPair encBits (encPair encBits encBits)) encBool (accBound d D) (accBound d D) := by + obtain ⟨dd, ed, hdec⟩ := exists_computableInDegree_decodeDesc + set enc := encPair encBits (encPair encBits encBits) + have hdesc := hdec.inO.comp (ComputableInO.fst encBits (encPair encBits encBits)) + have hword : ComputableInO (fun q : List Bool × List Bool × List Bool => frame q.2.1 ++ q.2.2) + enc encBits (fun q => (enc q).length) (fun q => (enc q).length) := + (ComputableInO.snd encBits (encPair encBits encBits)).of_enc_eq fun _ => rfl + have hsym := Universal.computableTidilyInDegree_map_eBit.inO_one.computableInO.comp hword + have hy := (ComputableInO.snd encBits (encPair encBits encBits)).fst' + have hclock := (timeConstructible_succ_pow (k := d + 3) (by omega)).computableInO.comp hy + have hU := computableInO_universal.comp (hdesc.prod (hsym.prod hclock)) + have hv := Diagonal.computableInO_verdict.comp hU + have hnot := (ComputableInDegree.of_finite (encIn := encBool) (encMid := encBool) + (fun b : Bool => !b)).inO_one.comp hv + obtain ⟨cD, hcD⟩ := hdesc.length_le + obtain ⟨cO, hcO⟩ := hU.length_le + obtain ⟨K, hK⟩ := accBound_aux d cD cO + have hbounds (q : List Bool × List Bool × List Bool) := by + have hD := hcD q + have hE := hcO q + have hNl : (encList encUnary ((frame q.2.1 ++ q.2.2).map eBit)).length ≤ + 3 * (frame q.2.1 ++ q.2.2).length := + (length_encList_le encUnary _ (b := 1) fun s hs => by + obtain ⟨b, -, rfl⟩ := List.mem_map.mp hs + cases b + · exact Nat.zero_le _ + · exact le_rfl).trans (by rw [List.length_map]; omega) + have hc : q.1.length + 1 ≤ (q.1.length + 1) ^ (dd + 1) := + Nat.le_self_pow (by omega) _ + have hA : (q.1.length + 1) ^ dd ≤ (q.1.length + 1) ^ (dd + 1) := + Nat.pow_le_pow_right (by omega) (by omega) + have hL := length_encNat_clock_le d q.2.1.length + simp only [Function.comp_apply, encBits_apply, enc, length_encPair, List.length_append, + length_frame] at hD hE hNl ⊢ + refine hK (X := (q.1.length + 1) ^ (dd + 1) + q.2.1.length + q.2.2.length + 1) + (by omega) (by omega) (by omega) hD hNl ?_ (by omega) (Nat.one_le_pow _ _ (by omega)) hE + have : (d + 3) * (q.2.1.length + 1) ≤ (d + 4) * (q.2.1.length + 1) := + Nat.mul_le_mul_right _ (by omega) + have : (d + 4) * (q.2.1.length + 1) ≤ + (d + 4) * ((q.1.length + 1) ^ (dd + 1) + q.2.1.length + q.2.2.length + 1) := + Nat.mul_le_mul_left _ (by omega) + omega + have e (q : List Bool × List Bool × List Bool) : accBound d (dd + 1) q = + ((q.1.length + 1) ^ (dd + 1) + q.2.1.length + q.2.2.length + 1) ^ 4 * + ((q.2.1.length + 1) ^ (d + 3)) ^ 2 := by + rw [accBound, ← pow_mul, show (d + 3) * 2 = 2 * d + 6 by ring] + have hb (b : Bool) : (encBool b).length = 1 := rfl + refine ⟨dd + 1, (hnot.mono K (fun q => ?_) fun q => ?_).of_enc_eq fun q => rfl⟩ + · refine le_trans ?_ (Nat.mul_le_mul_left K (Nat.le_succ _)) + rw [e] + have h := (hbounds q).1 + simp only [Function.comp_apply, encBits_apply, enc, length_encPair, List.length_append, + length_frame] at ⊢ + simp only [hb] at h ⊢ + omega + · refine le_trans ?_ (Nat.mul_le_mul_left K (Nat.le_succ _)) + rw [e] + have h := (hbounds q).2 + simp only [Function.comp_apply, encBits_apply, enc, length_encPair, List.length_append, + length_frame] at ⊢ + simp only [hb] at h ⊢ + omega + +attribute [local fun_prop] ComputableInPolyTime.fst' ComputableInPolyTime.snd' in +/-- The brute force at the end of an interval runs within time exponential in the description, +the input and the certificate length. -/ +private theorem exists_computableInO_brute (d : ℕ) : ∃ K, ComputableInO + (fun q : List Bool × List Bool => + (Cslib.BitString.enumList (certLen d q.2.length)).any (accepts d q.1 q.2)) + (encPair encBits encBits) encBool + (fun q => 2 ^ (K * ((encPair encBits encBits q).length + certLen d q.2.length))) + (fun q => 2 ^ (K * ((encPair encBits encBits q).length + certLen d q.2.length))) := by + obtain ⟨D, hacc⟩ := exists_computableInO_accepts d + set enc := encPair (encPair encBits encBits) encBits + have hre := ((ComputableInO.fst (encPair encBits encBits) encBits).fst').prod + (((ComputableInO.fst (encPair encBits encBits) encBits).snd').prod + (ComputableInO.snd (encPair encBits encBits) encBits)) + have hf := hacc.comp hre + have hsize (q : (List Bool × List Bool) × List Bool) : + q.1.1.length ≤ (enc q).length ∧ q.1.2.length ≤ (enc q).length ∧ + q.2.length ≤ (enc q).length := by + simp only [enc, length_encPair, encBits_apply] + omega + have hpoly : PolyBounded (fun q => (enc q).length) fun q => + (enc q).length + ((enc q).length + (enc q).length) + + accBound d D (q.1.1, q.1.2, q.2) := + ((PolyBounded.self.add (PolyBounded.self.add .self)).add + ((PolyBounded.of_polynomiallyBounded (fun q => (enc q).length) + (bound := fun n => ((n + 1) ^ D + n + n + 1) ^ 4 * (n + 1) ^ (2 * d + 6)) + (by fun_prop)).mono fun q => by + obtain ⟨h₁, h₂, h₃⟩ := hsize q + simp only [accBound] + gcongr)) + have hℓ : ComputableInPolyTime (fun q : List Bool × List Bool => certLen d q.2.length) + (encPair encBits encBits) encUnary := by + unfold certLen + fun_prop + exact exists_computableInO_any_enumList hf hpoly hpoly hℓ + +/-- The cost of the tests of the verifier: quadratic in the encoded input, times a power of the +length of the input that pays for the clock in unary. -/ +private def cheap (d : ℕ) (p : List Bool × List Bool) : ℕ := + ((encPair encBits encBits p).length + 1) ^ 2 * ((p.1.length + 1) ^ (d + 3)) ^ 2 + +/-- The numeric facts behind `cheap`, for an encoded input of length `N`, an input of length `n` +and a clock `Z` of at most `G * Y`. -/ +private theorem cheap_aux (G : ℕ) {N n Z Y : ℕ} (hn : n ≤ N) (hY : 1 ≤ Y) (hZ : Z ≤ G * Y) : + N + 1 ≤ (N + 1) ^ 2 * Y ^ 2 ∧ (Z + 1) ^ 2 ≤ (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) ∧ + (2 * N + 4 + Z) ^ 2 ≤ (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) := by + have hY2 : 1 ≤ Y ^ 2 := Nat.one_le_pow _ _ hY + have hN2 : N + 1 ≤ (N + 1) ^ 2 := Nat.le_self_pow (by norm_num) _ + have hsq : (2 * N + 4 + Z) ^ 2 ≤ (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) := + calc (2 * N + 4 + Z) ^ 2 ≤ ((G + 4) * ((N + 1) * Y)) ^ 2 := by + apply Nat.pow_le_pow_left + have h₁ : N + 1 ≤ (N + 1) * Y := Nat.le_mul_of_pos_right _ hY + have h₂ : Y ≤ (N + 1) * Y := Nat.le_mul_of_pos_left _ (by omega) + have e : (G + 4) * ((N + 1) * Y) = G * ((N + 1) * Y) + 4 * ((N + 1) * Y) := by ring + have := Nat.mul_le_mul_left G h₂ + omega + _ = (G + 4) ^ 2 * ((N + 1) ^ 2 * Y ^ 2) := by ring + refine ⟨hN2.trans (Nat.le_mul_of_pos_right _ hY2), + (Nat.pow_le_pow_left (by omega) 2).trans hsq, hsq⟩ + +/-- **The verifier of the lazy diagonal is computable**, within the cost of its tests plus the +cost of the branch taken: the simulation, or the brute force at the end of an interval. -/ +private theorem exists_computableInO_verifier (d : ℕ) : + ∃ (D K : ℕ) (S : List Bool × List Bool → ℕ), + ComputableInO (verifier d) (encPair encBits encBits) encBool + (fun p => cheap d p + if Diagonal.shortDesc (unframe p.1) then + (cheap d p + if Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1) then + cheap d p + accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2) + else (cheap d p + if Nat.size p.1.length ≤ clock d (2 * (unframe p.1).length + 1) then + cheap d p + 2 ^ (K * ((encPair encBits encBits (unframe (unframe p.1), + frame (unframe p.1))).length + certLen d (2 * (unframe p.1).length + 1))) + else cheap d p)) + else cheap d p) S := by + obtain ⟨D, hacc⟩ := exists_computableInO_accepts d + obtain ⟨K, hbrute⟩ := exists_computableInO_brute d + have hx := ComputableInO.fst encBits encBits + have hun : ComputableInO unframe encBits encBits (fun z => z.length) (fun z => z.length) := + ComputableInO.of_linear (.of_unframe fun _ => rfl) + have hfr : ComputableInO frame encBits encBits (fun z => z.length) (fun z => z.length) := + ComputableInO.of_linear (.of_frame fun _ => rfl) + have hw := hun.comp hx + have hcode := hun.comp hw + have hbase := hfr.comp hw + have hsnoc : ComputableInO (fun p : List Bool × List Bool => p.1 ++ [false]) + (encPair encBits encBits) encBits + (fun p => (encPair encBits encBits p).length + (encPair encBits encBits p).length) + (fun p => (encPair encBits encBits p).length + (encPair encBits encBits p).length) := + hx.append (ComputableInO.const (encMid := encBits) [false]) fun _ => rfl + have hsz : ComputableInO (fun z : List Bool => Nat.size z.length) encBits encUnary + (fun z => (z.length + 1) ^ 2 + (encNat z.length).length) + (fun z => z.length + 1 + (encNat z.length).length) := + (ComputableInO.lengthUnary.comp (ComputableInO.lengthNat.of_enc_eq (encC := encBits) + (g := fun z : List Bool => encNat z.length) fun _ => rfl)).of_enc_eq fun z => by + simp [encNat_apply, Nat.size_eq_bits_len] + have hE := ComputableInO.natToUnary.comp + ((timeConstructible_succ_pow (k := d + 3) (by omega)).computableInO.comp hbase) + -- the numeric facts about the tests + set G := 2 ^ (d + 3) + have hfacts (p : List Bool × List Bool) : (unframe p.1).length ≤ p.1.length ∧ + p.1.length ≤ (encPair encBits encBits p).length ∧ + (encPair encBits encBits p).length + 1 ≤ cheap d p ∧ + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1) ^ 2 ≤ (G + 4) ^ 2 * cheap d p ∧ + (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 ≤ (G + 4) ^ 2 * cheap d p := by + have hu := length_unframe_le p.1 + have hn : p.1.length ≤ (encPair encBits encBits p).length := by + simp only [length_encPair, encBits_apply] + omega + have hZ : (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) ≤ + 2 ^ (d + 3) * (p.1.length + 1) ^ (d + 3) := by + rw [← mul_pow] + exact Nat.pow_le_pow_left (by omega) _ + exact ⟨hu, hn, cheap_aux G hn (Nat.one_le_pow _ _ (by omega)) hZ⟩ + have e (p : List Bool × List Bool) : (8 + 4 * (G + 4) ^ 2) * (cheap d p + 1) = + 8 * cheap d p + 4 * ((G + 4) ^ 2 * cheap d p) + (8 + 4 * (G + 4) ^ 2) := by ring + have hc₀ := (Diagonal.computableInO_shortDesc.comp hw).mono_time (T' := cheap d) + (8 + 4 * (G + 4) ^ 2) fun p => by + obtain ⟨hu, hn, h₁, -, h₃⟩ := hfacts p + have : ((unframe p.1).length + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + simp only [Function.comp_apply] at ⊢ + have := e p + omega + have hc₁ := (ComputableInO.unaryLe.comp ((hsz.comp hsnoc).prod hE)).mono_time + (T' := cheap d) (8 + 4 * (G + 4) ^ 2) fun p => by + obtain ⟨hu, hn, h₁, h₂, h₃⟩ := hfacts p + have hs := Nat.size_le.mpr (Nat.lt_two_pow_self (n := p.1.length + 1)) + have he := Universal.length_encNat_le (p.1.length + 1) + have hsq : (p.1.length + 1 + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + have hsq' : (2 * Nat.size (p.1.length + 1) + 1 + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := + Nat.pow_le_pow_left (by omega) 2 + have hZ : (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) ≤ + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1) ^ 2 := by + nlinarith [Nat.le_self_pow (n := 2) (by norm_num) + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1)] + simp only [Function.comp_apply, length_encPair (enc₁ := encUnary) (enc₂ := encUnary), + encUnary_apply, List.length_replicate, List.length_append, List.length_singleton, + length_frame] at ⊢ + have := e p + omega + have hc₂ := (ComputableInO.unaryLe.comp ((hsz.comp hx).prod hE)).mono_time + (T' := cheap d) (8 + 4 * (G + 4) ^ 2) fun p => by + obtain ⟨hu, hn, h₁, h₂, h₃⟩ := hfacts p + have hs := Nat.size_le.mpr (Nat.lt_two_pow_self (n := p.1.length)) + have he := Universal.length_encNat_le p.1.length + have hsq : (p.1.length + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + have hsq' : (2 * Nat.size p.1.length + 1 + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + + 1) ^ 2 ≤ (2 * (encPair encBits encBits p).length + 4 + + (2 * (unframe p.1).length + 1 + 1) ^ (d + 3)) ^ 2 := + Nat.pow_le_pow_left (by omega) 2 + have hZ : (2 * (unframe p.1).length + 1 + 1) ^ (d + 3) ≤ + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1) ^ 2 := by + nlinarith [Nat.le_self_pow (n := 2) (by norm_num) + ((2 * (unframe p.1).length + 1 + 1) ^ (d + 3) + 1)] + simp only [Function.comp_apply, length_encPair (enc₁ := encUnary) (enc₂ := encUnary), + encUnary_apply, List.length_replicate, length_frame] at ⊢ + have := e p + omega + have hc₁' := hc₁.of_enc_eq (encC := encBool) (g := fun p : List Bool × List Bool => + decide (Nat.size (p.1.length + 1) ≤ clock d (2 * (unframe p.1).length + 1))) fun p => by + simp only [Function.comp_apply, clock, length_frame, List.length_append, + List.length_singleton] + rfl + have hc₂' := hc₂.of_enc_eq (encC := encBool) (g := fun p : List Bool × List Bool => + decide (Nat.size p.1.length ≤ clock d (2 * (unframe p.1).length + 1))) fun p => by + simp only [Function.comp_apply, clock, length_frame] + rfl + have hf₁ := (hacc.comp (hcode.prod (hsnoc.prod (ComputableInO.snd encBits encBits)))).mono_time + (T' := fun p => cheap d p + accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2)) 7 + fun p => by + obtain ⟨hu, hn, h₁, -⟩ := hfacts p + simp only [Function.comp_apply] at ⊢ + have e : 7 * (cheap d p + accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2) + 1) = + 7 * cheap d p + 7 * accBound d D (unframe (unframe p.1), p.1 ++ [false], p.2) + 7 := by + ring + omega + have hf₂ := ((ComputableInDegree.of_finite (encIn := encBool) (encMid := encBool) + (fun b : Bool => !b)).inO_one.comp (hbrute.comp (hcode.prod hbase))).mono_time + (T' := fun p => cheap d p + 2 ^ (K * ((encPair encBits encBits (unframe (unframe p.1), + frame (unframe p.1))).length + certLen d (2 * (unframe p.1).length + 1)))) 7 + fun p => by + obtain ⟨hu, hn, h₁, -⟩ := hfacts p + have hb : (encBool ((Cslib.BitString.enumList (certLen d (frame (unframe p.1)).length)).any + (accepts d (unframe (unframe p.1)) (frame (unframe p.1))))).length = 1 := rfl + simp only [Function.comp_apply, hb] at ⊢ + rw [length_frame] + set B := 2 ^ (K * ((encPair encBits encBits (unframe (unframe p.1), + frame (unframe p.1))).length + certLen d (2 * (unframe p.1).length + 1))) + have e : 7 * (cheap d p + B + 1) = 7 * cheap d p + 7 * B + 7 := by ring + omega + have hfalse := (ComputableInO.const (encIn := encPair encBits encBits) (encMid := encBool) + false).mono_time (T' := cheap d) 1 fun p => by + obtain ⟨hu, hn, h₁, -⟩ := hfacts p + omega + have h := hc₀.ite (hc₁'.ite hf₁ (hc₂'.ite hf₂ hfalse)) hfalse + refine ⟨D, K, _, (h.of_enc_eq fun p => ?_).mono_le (fun p => le_of_eq ?_) fun p => le_rfl⟩ + · congr 1 + simp only [verifier, Function.comp_apply, decide_eq_true_eq, length_frame] + · simp only [Function.comp_apply, decide_eq_true_eq] + +/-- **The lazy diagonal is decidable in nondeterministic time `O((n + 1) ^ (6 * d + 14))`.** Guess +a certificate of length `(n + 2) ^ (d + 2)` and run the verifier +(`Cslib.Complexity.setOf_exists_mem_NTIME`). Below the end of an interval, the verifier simulates +a verifier on the input one bit longer with a certificate of that length, within time polynomial +in both. At the end of an interval of start `P`, it tries every certificate on the input of +length `P`, within time `2 ^ O((P + 1) ^ (d + 2))`, which is linear in the length `n` of the +input, since `n + 1 = 2 ^ ((P + 1) ^ (d + 3))` there. -/ +theorem lazyDiagonal_mem_NTIME (d : ℕ) : + lazyDiagonal d ∈ NTIME encBits fun n => (n + 1) ^ (6 * d + 14) := by + obtain ⟨D, K, S, hV⟩ := exists_computableInO_verifier d + have hsnoc : ComputableInO (fun x : List Bool => x ++ [false]) encBits encBits + (fun x => x.length + x.length) (fun x => x.length + x.length) := + (ComputableInO.of_eq (f := fun x : List Bool => x) (encIn := encBits) (encMid := encBits) + fun _ => rfl).append (ComputableInO.const (encMid := encBits) [false]) fun _ => rfl + have hℓ := (ComputableInO.natToUnary.comp + ((timeConstructible_succ_pow (k := d + 2) (by omega)).computableInO.comp hsnoc)).of_enc_eq + (encC := encUnary) (g := fun x : List Bool => certLen d (x.length + 1)) fun x => by + simp [certLen] + obtain ⟨CD, hCD⟩ := PolynomiallyBounded.exists_le_mul_two_pow (p := fun m => (m + 1) ^ D) + (by fun_prop) + set B₀ := 3 * K * (3 * K) ^ (d + 2) + set A₁ := 9 * 2 ^ (2 * d + 4) + set A₂ := (CD + 3) ^ 4 * 2 ^ (6 * d + 14) + set A₃ := 7 * 2 ^ (2 * d + 4) + refine setOf_exists_mem_NTIME hℓ hV (C := 2 + A₃ + 4 * A₁ + A₂ + 2 ^ B₀) fun x v hv => ?_ + simp only [encBits_apply] + set n := x.length with hn + have hv' : v.length = (n + 2) ^ (d + 2) := by + rw [hv] + rfl + set M := (n + 1) ^ (6 * d + 14) + have h2 (k : ℕ) : (n + 2) ^ k ≤ 2 ^ k * (n + 1) ^ k := by + rw [← mul_pow] + exact Nat.pow_le_pow_left (by omega) k + have hMk (k : ℕ) (hk : k ≤ 6 * d + 14) : (n + 1) ^ k ≤ M := Nat.pow_le_pow_right (by omega) hk + have hnM : n + 1 ≤ M := by simpa using hMk 1 (by omega) + -- the tests + have hcheap : cheap d (x, v) ≤ A₁ * M := by + have hN : (encPair encBits encBits (x, v)).length + 1 ≤ 3 * (n + 2) ^ (d + 2) := by + simp only [length_encPair, encBits_apply, hv'] + have : n + 2 ≤ (n + 2) ^ (d + 2) := Nat.le_self_pow (by omega) _ + omega + calc cheap d (x, v) = ((encPair encBits encBits (x, v)).length + 1) ^ 2 * + ((n + 1) ^ (d + 3)) ^ 2 := rfl + _ ≤ (3 * (n + 2) ^ (d + 2)) ^ 2 * ((n + 1) ^ (d + 3)) ^ 2 := + Nat.mul_le_mul_right _ (Nat.pow_le_pow_left hN 2) + _ = 9 * (n + 2) ^ (2 * d + 4) * (n + 1) ^ (2 * d + 6) := by ring + _ ≤ 9 * (2 ^ (2 * d + 4) * (n + 1) ^ (2 * d + 4)) * (n + 1) ^ (2 * d + 6) := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (h2 _)) + _ = A₁ * (n + 1) ^ (4 * d + 10) := by ring + _ ≤ A₁ * M := Nat.mul_le_mul_left _ (hMk _ (by omega)) + -- the simulation, when the description is short + have hacc (h₀ : Diagonal.shortDesc (unframe x) = true) : + accBound d D (unframe (unframe x), x ++ [false], v) ≤ A₂ * M := by + have hs : 2 ^ (unframe (unframe x)).length ≤ (unframe x).length := of_decide_eq_true h₀ + have hu := length_unframe_le x + have hD : ((unframe (unframe x)).length + 1) ^ D ≤ CD * n := + (hCD _).trans (Nat.mul_le_mul_left _ (hs.trans hu)) + have hb : ((unframe (unframe x)).length + 1) ^ D + (n + 1) + (n + 2) ^ (d + 2) + 1 ≤ + (CD + 3) * (n + 2) ^ (d + 2) := by + have h₁ : n + 2 ≤ (n + 2) ^ (d + 2) := Nat.le_self_pow (by omega) _ + have h₂ : CD * n ≤ CD * (n + 2) ^ (d + 2) := Nat.mul_le_mul_left _ (by omega) + have e : (CD + 3) * (n + 2) ^ (d + 2) = CD * (n + 2) ^ (d + 2) + 3 * (n + 2) ^ (d + 2) := by + ring + omega + calc accBound d D (unframe (unframe x), x ++ [false], v) = + (((unframe (unframe x)).length + 1) ^ D + (n + 1) + (n + 2) ^ (d + 2) + 1) ^ 4 * + (n + 2) ^ (2 * d + 6) := by + simp only [accBound, List.length_append, List.length_singleton, hv', ← hn, add_assoc, + Nat.reduceAdd] + _ ≤ ((CD + 3) * (n + 2) ^ (d + 2)) ^ 4 * (n + 2) ^ (2 * d + 6) := + Nat.mul_le_mul_right _ (Nat.pow_le_pow_left hb 4) + _ = (CD + 3) ^ 4 * (n + 2) ^ (6 * d + 14) := by ring + _ ≤ (CD + 3) ^ 4 * (2 ^ (6 * d + 14) * M) := Nat.mul_le_mul_left _ (h2 _) + _ = A₂ * M := by ring + -- the brute force, at the end of an interval + have hbrute (h₁ : ¬Nat.size (n + 1) ≤ clock d (2 * (unframe x).length + 1)) : + 2 ^ (K * ((encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d (2 * (unframe x).length + 1))) ≤ n + 1 + 2 ^ B₀ := by + set P := 2 * (unframe x).length + 1 with hP + have hE : 2 ^ clock d P ≤ n + 1 := by + rw [Nat.size_le, not_lt] at h₁ + exact h₁ + have hc := length_unframe_le (unframe x) + have hlen : (encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d P ≤ 3 * (P + 1) ^ (d + 2) := by + simp only [length_encPair, encBits_apply, length_frame, certLen] + have : P + 1 ≤ (P + 1) ^ (d + 2) := Nat.le_self_pow (by omega) _ + omega + rcases le_or_gt (3 * K) (P + 1) with hK | hK + · have hle : K * ((encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d P) ≤ clock d P := + calc _ ≤ K * (3 * (P + 1) ^ (d + 2)) := Nat.mul_le_mul_left _ hlen + _ = 3 * K * (P + 1) ^ (d + 2) := by ring + _ ≤ (P + 1) * (P + 1) ^ (d + 2) := Nat.mul_le_mul_right _ hK + _ = clock d P := by rw [clock]; ring + exact ((Nat.pow_le_pow_right (by norm_num) hle).trans hE).trans (Nat.le_add_right _ _) + · have hle : K * ((encPair encBits encBits (unframe (unframe x), frame (unframe x))).length + + certLen d P) ≤ B₀ := + calc _ ≤ K * (3 * (P + 1) ^ (d + 2)) := Nat.mul_le_mul_left _ hlen + _ = 3 * K * (P + 1) ^ (d + 2) := by ring + _ ≤ 3 * K * (3 * K) ^ (d + 2) := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hK.le _) + exact (Nat.pow_le_pow_right (by norm_num) hle).trans (Nat.le_add_left _ _) + -- the certificate length + have hTℓ : n + n + ((x ++ [false]).length + 1) ^ (d + 2) + + (((fun x : List Bool => (x.length + 1) ^ (d + 2)) ∘ fun x => x ++ [false]) x + 1) ^ 2 ≤ + A₃ * M := by + simp only [Function.comp_apply, List.length_append, List.length_singleton, add_assoc, + Nat.reduceAdd, ← hn] + have h₁ : n + 2 ≤ (n + 2) ^ (d + 2) := Nat.le_self_pow (by omega) _ + have h₂ : ((n + 2) ^ (d + 2) + 1) ^ 2 ≤ 4 * (n + 2) ^ (2 * d + 4) := by + calc _ ≤ (2 * (n + 2) ^ (d + 2)) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ = 4 * (n + 2) ^ (2 * d + 4) := by ring + have h₃ : (n + 2) ^ (d + 2) ≤ (n + 2) ^ (2 * d + 4) := + Nat.pow_le_pow_right (by omega) (by omega) + have h₄ : 7 * (n + 2) ^ (2 * d + 4) ≤ A₃ * M := + calc _ ≤ 7 * (2 ^ (2 * d + 4) * (n + 1) ^ (2 * d + 4)) := Nat.mul_le_mul_left _ (h2 _) + _ = A₃ * (n + 1) ^ (2 * d + 4) := by ring + _ ≤ A₃ * M := Nat.mul_le_mul_left _ (hMk _ (by omega)) + omega + have e : (2 + A₃ + 4 * A₁ + A₂ + 2 ^ B₀) * M = + 2 * M + A₃ * M + 4 * (A₁ * M) + A₂ * M + 2 ^ B₀ * M := by ring + have hB : 2 ^ B₀ ≤ 2 ^ B₀ * M := Nat.le_mul_of_pos_right _ (by omega) + clear_value M A₁ A₂ A₃ B₀ + generalize 2 + A₃ + 4 * A₁ + A₂ + 2 ^ B₀ = C at * + generalize 2 ^ B₀ = P at * + split_ifs with h₀ h₁ h₂ + · have := hacc h₀ + omega + · have := hbrute h₁ + omega + · omega + · omega + +/-! ### The hierarchy -/ + +/-- **A nondeterministic time hierarchy for polynomial degrees.** For every degree `d`, +nondeterministic time `O((n + 1) ^ d)` is strictly contained in nondeterministic time +`O((n + 1) ^ e)` whenever `6 * d + 14 ≤ e`. The lazy diagonal `Cslib.Complexity.lazyDiagonal d` +separates them (`Cslib.Complexity.lazyDiagonal_mem_NTIME`, +`Cslib.Complexity.lazyDiagonal_notMem_NTIME`). This is a coarse form of the nondeterministic time +hierarchy theorem [Arora and Barak, Theorem 3.3][AroraBarak07]: the gap is the polynomial +overhead of the native universal machine and of the certificates, not `f (n + 1) = o (g n)`. -/ +theorem NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le {d e : ℕ} (h : 6 * d + 14 ≤ e) : + NTIME encBits (fun n => (n + 1) ^ d) ⊂ NTIME encBits fun n => (n + 1) ^ e := by + have hsub : ∀ {a b : ℕ}, a ≤ b → + NTIME encBits (fun n => (n + 1) ^ a) ⊆ NTIME encBits fun n => (n + 1) ^ b := + fun hab => NTIME_mono fun n => Nat.pow_le_pow_right (by omega) hab + exact (Set.ssubset_iff_of_subset (hsub (by omega))).mpr + ⟨lazyDiagonal d, hsub h (lazyDiagonal_mem_NTIME d), lazyDiagonal_notMem_NTIME d⟩ + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Space/Closure.lean b/Complexity/Computability/Complexity/Classes/Space/Closure.lean index 57946f366b..06a0adcb03 100644 --- a/Complexity/Computability/Complexity/Classes/Space/Closure.lean +++ b/Complexity/Computability/Complexity/Classes/Space/Closure.lean @@ -12,6 +12,7 @@ public import Complexity.Computability.Complexity.Reduction public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolySpace public import Complexity.Foundations.Order.BooleanSubalgebra import Complexity.Computability.Complexity.Classes.Space.Savitch +import Complexity.Computability.Complexity.Classes.Time.BoundedExists import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat @@ -146,47 +147,14 @@ theorem PolyTimeReducible.mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : exact mem_PSPACE_of_computableInPolySpace (hf.comp (.of_polyTime hg)) fun a => (hAB a).trans (hB (g a)) -/-- The edges of the graph on words of width `n + 1` whose reachability within two steps from the -all-zero word to the all-one word is a bounded existential over `f`: an edge from the all-zero word -to `true :: w` when `f (x, w)`, and from every word starting with `true` to the all-one word. -/ -private def existsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (u v : List Bool) : Bool := - if !(u.any fun b => b) && v.headD false then f (x, v.tail) - else u.headD false && !(v.any fun b => !b) - -/-- Savitch's algorithm at depth `1` on the edges of `existsEdge` decides the bounded -existential. -/ -private theorem savitch_existsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (n : ℕ) : - Cslib.Reachability.savitch (existsEdge f x) (n + 1) 1 (List.replicate (n + 1) false) - (List.replicate (n + 1) true) = true ↔ ∃ w : List Bool, w.length = n ∧ f (x, w) = true := by - rw [Cslib.Reachability.savitch_eq] - simp only [Cslib.Reachability.reach, List.any_eq_true, Cslib.BitString.mem_enumList, - Bool.and_eq_true, Bool.or_eq_true, decide_eq_true_eq] - constructor - · rintro ⟨m, hm, h₁ | h₁, h₂ | h₂⟩ - · subst h₁ - exact absurd h₂ (by simp [List.replicate_succ]) - · subst h₁ - refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ - simpa [existsEdge, List.replicate_succ] using h₂ - · subst h₂ - refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ - simpa [existsEdge, List.replicate_succ] using h₁ - · obtain ⟨b, w, rfl⟩ := List.exists_cons_of_length_eq_add_one hm - refine ⟨w, by simpa using hm, ?_⟩ - cases b <;> simp_all [existsEdge, List.replicate_succ] - · rintro ⟨w, hw, hf⟩ - refine ⟨true :: w, by simp [hw], Or.inr ?_, Or.inr ?_⟩ - · simp [existsEdge, List.replicate_succ, hf] - · simp [existsEdge, List.replicate_succ] - attribute [local fun_prop] ComputableInPolyTime.fst' ComputableInPolyTime.snd' ComputableInPolyTime.bool_and' in /-- **`PSPACE` is closed under polynomially bounded existential quantification**: if `B ⊆ β × List Bool` is in `PSPACE` and `ℓ` is polynomial-time, then so is the set of the `x` for which some word `w` of length `ℓ x` has `(x, w) ∈ B`. The candidates are enumerated in place: this is reachability -within two steps between two words of width `ℓ x + 1` (`existsEdge`), which Savitch's algorithm -decides at depth `1`, reusing the space of the decider of `B` for every candidate -(`Cslib.Reachability.computableInO_savitch`). -/ +within two steps between two words of width `ℓ x + 1` (`Cslib.Complexity.boundedExistsEdge`), +which Savitch's algorithm decides at depth `1`, reusing the space of the decider of `B` for every +candidate (`Cslib.Reachability.computableInO_savitch`). -/ theorem setOf_exists_mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : Set (β × List Bool)} {ℓ : β → ℕ} (hB : B ∈ PSPACE (encPair encB encBits)) (hℓ : ComputableInPolyTime ℓ encB encUnary) : @@ -213,7 +181,7 @@ theorem setOf_exists_mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : Set ( unfold Cslib.Reachability.encArgs fun_prop obtain ⟨Ti, Si, -, hSi, hinO⟩ := hin.exists_computableInO - have h := Cslib.Reachability.computableInO_savitch (edge := existsEdge f) (par := id) + have h := Cslib.Reachability.computableInO_savitch (edge := boundedExistsEdge f) (par := id) hedge hinO (fun _ => List.length_replicate ..) fun _ => List.length_replicate .. -- the space is polynomial have hSedge : PolyBounded (fun q => (encQ q).length) fun q => @@ -250,6 +218,6 @@ theorem setOf_exists_mem_PSPACE {β : Type} {encB : β ↪ List Bool} {B : Set ( Si x + 2 * (ℓ x + 3) + (encB x).length + cE * (2 * (encB x).length + 3 * ℓ x + 6) ^ dE omega · simp only [Set.mem_ofPred_eq, hB, id] - exact (savitch_existsEdge f x (ℓ x)).symm + exact (savitch_boundedExistsEdge f x (ℓ x)).symm end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean b/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean new file mode 100644 index 0000000000..eb926ceb84 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/Time/BoundedExists.lean @@ -0,0 +1,222 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Space.Reachability +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolyTime +import Complexity.Foundations.Data.Nat.PolynomialBound.Closure +import Complexity.Foundations.Data.Nat.PolynomialBound.Exponential + +/-! +# Bounded existential quantification in exponential time + +A test `f` that is computable in polynomial time can be tried on every word of length `ℓ x`, +for `ℓ` computable in polynomial time, within time `2 ^ O(|x| + ℓ x)` +(`Cslib.Complexity.exists_computableInO_any_enumList`). The candidates are enumerated in place: +the existential is reachability within two steps from the all-zero word to the all-one word of +width `ℓ x + 1`, along an edge to `true :: w` for each accepted `w` and from every word starting +with `true` to the all-one word (`Cslib.Complexity.boundedExistsEdge`). Savitch's algorithm +decides it at depth `1` (`Cslib.Reachability.computableInO_savitch`). Every polynomial in the +cost is absorbed by the exponential, so the exponent is linear in `|x| + ℓ x`, with a constant +that depends on the test. + +The same edges give the closure of `PSPACE` under bounded existential quantification +(`Cslib.Complexity.setOf_exists_mem_PSPACE`), where the point is the space of the run. Here the +time is the point: this brute force is the last step of lazy diagonalization for +nondeterministic time, which must decide deterministically whether some certificate is +accepted. + +## Main definitions + +* `Cslib.Complexity.boundedExistsEdge`: the edges of the two-step reachability problem. + +## Main results + +* `Cslib.Complexity.savitch_boundedExistsEdge`: Savitch's algorithm at depth `1` on these edges + decides the bounded existential. +* `Cslib.Complexity.exists_computableInO_any_enumList`: the bounded existential within time and + space `2 ^ O(|x| + ℓ x)`. +-/ + +public section + +namespace Cslib.Complexity + +open _root_.Turing _root_.Turing.MultiTapeTM + +/-- The edges of the graph on words of width `n + 1` whose reachability within two steps from the +all-zero word to the all-one word is a bounded existential over `f`: an edge from the all-zero word +to `true :: w` when `f (x, w)`, and from every word starting with `true` to the all-one word. -/ +@[expose] def boundedExistsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (u v : List Bool) : + Bool := + if !(u.any fun b => b) && v.headD false then f (x, v.tail) + else u.headD false && !(v.any fun b => !b) + +/-- Savitch's algorithm at depth `1` on the edges of `Cslib.Complexity.boundedExistsEdge` decides +the bounded existential. -/ +theorem savitch_boundedExistsEdge {β : Type} (f : β × List Bool → Bool) (x : β) (n : ℕ) : + Cslib.Reachability.savitch (boundedExistsEdge f x) (n + 1) 1 (List.replicate (n + 1) false) + (List.replicate (n + 1) true) = true ↔ ∃ w : List Bool, w.length = n ∧ f (x, w) = true := by + rw [Cslib.Reachability.savitch_eq] + simp only [Cslib.Reachability.reach, List.any_eq_true, Cslib.BitString.mem_enumList, + Bool.and_eq_true, Bool.or_eq_true, decide_eq_true_eq] + constructor + · rintro ⟨m, hm, h₁ | h₁, h₂ | h₂⟩ + · subst h₁ + exact absurd h₂ (by simp [List.replicate_succ]) + · subst h₁ + refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ + simpa [boundedExistsEdge, List.replicate_succ] using h₂ + · subst h₂ + refine ⟨List.replicate n true, List.length_replicate .., ?_⟩ + simpa [boundedExistsEdge, List.replicate_succ] using h₁ + · obtain ⟨b, w, rfl⟩ := List.exists_cons_of_length_eq_add_one hm + refine ⟨w, by simpa using hm, ?_⟩ + cases b <;> simp_all [boundedExistsEdge, List.replicate_succ] + · rintro ⟨w, hw, hf⟩ + refine ⟨true :: w, by simp [hw], Or.inr ?_, Or.inr ?_⟩ + · simp [boundedExistsEdge, List.replicate_succ, hf] + · simp [boundedExistsEdge, List.replicate_succ] + +/-- A polynomially bounded resource function is within a constant multiple of `2 ^ size`. -/ +private theorem PolyBounded.exists_le_mul_two_pow {α : Type*} {size u : α → ℕ} + (h : PolyBounded size u) : ∃ C, ∀ a, u a ≤ C * 2 ^ size a := by + obtain ⟨c, d, hcd⟩ := h.exists_le + obtain ⟨C, hC⟩ := PolynomiallyBounded.exists_le_mul_two_pow + (p := fun n => c * (n + 1) ^ d) (by fun_prop) + exact ⟨C, fun a => (hcd a).trans (hC _)⟩ + +attribute [local fun_prop] ComputableInPolyTime.fst' ComputableInPolyTime.snd' + ComputableInPolyTime.bool_and' in +/-- **Bounded existential quantification in exponential time.** If a test `f` is computable within +time and space polynomial in its encoded input, and `ℓ` is computable in unary in polynomial time, +then whether some word of length `ℓ x` passes `f` with `x` is computable within time and space +`2 ^ (K * ((encB x).length + ℓ x))` for a constant `K`. Savitch's algorithm decides it at depth `1` +on the edges of `Cslib.Complexity.boundedExistsEdge`. -/ +theorem exists_computableInO_any_enumList {β : Type} {encB : β ↪ List Bool} + {f : β × List Bool → Bool} {Tf Sf : β × List Bool → ℕ} + (hf : ComputableInO f (encPair encB encBits) encBool Tf Sf) + (hTf : PolyBounded (fun q => (encPair encB encBits q).length) Tf) + (hSf : PolyBounded (fun q => (encPair encB encBits q).length) Sf) + {ℓ : β → ℕ} (hℓ : ComputableInPolyTime ℓ encB encUnary) : + ∃ K, ComputableInO (fun x => (Cslib.BitString.enumList (ℓ x)).any fun w => f (x, w)) encB + encBool (fun x => 2 ^ (K * ((encB x).length + ℓ x))) + (fun x => 2 ^ (K * ((encB x).length + ℓ x))) := by + set encQ := encPair encB (encPair encBits encBits) + -- the edge test: polynomial-time tests around one call of `f` + have hg : ComputableInPolyTime (fun q : β × List Bool × List Bool => (q.1, q.2.2.tail)) + encQ (encPair encB encBits) := by fun_prop + have hc : ComputableInPolyTime + (fun q : β × List Bool × List Bool => !(q.2.1.any fun b => b) && q.2.2.headD false) + encQ encBool := by fun_prop + have he : ComputableInPolyTime + (fun q : β × List Bool × List Bool => q.2.1.headD false && !(q.2.2.any fun b => !b)) + encQ encBool := by fun_prop + obtain ⟨Tg, Sg, hTg, hSg, hgO⟩ := hg.exists_computableInO + obtain ⟨Tc, Sc, hTc, hSc, hcO⟩ := hc.exists_computableInO + obtain ⟨Te, Se, hTe, hSe, heO⟩ := he.exists_computableInO + have hedge := hcO.ite (hf.comp hgO) heO + -- the arguments of Savitch's algorithm + have hin : ComputableInPolyTime + (fun x => (x, ℓ x + 1, 1, List.replicate (ℓ x + 1) false, List.replicate (ℓ x + 1) true)) + encB (Cslib.Reachability.encArgs encB) := by + unfold Cslib.Reachability.encArgs + fun_prop + obtain ⟨Ti, Si, hTi, hSi, hinO⟩ := hin.exists_computableInO + have h := Cslib.Reachability.computableInO_savitch (edge := boundedExistsEdge f) (par := id) + hedge hinO (fun _ => List.length_replicate ..) fun _ => List.length_replicate .. + -- every polynomial is within a constant multiple of an exponential + have hpoly (T₁ T₂ T₃ : β × List Bool × List Bool → ℕ) (T : β × List Bool → ℕ) + (h₁ : PolyBounded (fun q => (encQ q).length) T₁) + (h₂ : PolyBounded (fun q => (encQ q).length) T₂) + (h₃ : PolyBounded (fun q => (encQ q).length) T₃) + (hT : PolyBounded (fun q => (encPair encB encBits q).length) T) : + ∃ C, ∀ q, (T₁ q + if !(q.2.1.any fun b => b) && q.2.2.headD false then + T₂ q + T (q.1, q.2.2.tail) else T₃ q) ≤ C * 2 ^ (encQ q).length := by + have hcomp : PolyBounded (fun q => (encQ q).length) fun q => T (q.1, q.2.2.tail) := + hT.comp hg.polyBounded_length + refine PolyBounded.exists_le_mul_two_pow + ((h₁.add ((h₂.add hcomp).add h₃)).mono fun q => ?_) + split_ifs <;> omega + obtain ⟨CT, hCT⟩ := hpoly Tc Tg Te Tf hTc hTg hTe hTf + obtain ⟨CS, hCS⟩ := hpoly Sc Sg Se Sf hSc hSg hSe hSf + obtain ⟨CI, hCI⟩ := PolyBounded.exists_le_mul_two_pow hTi + obtain ⟨CJ, hCJ⟩ := PolyBounded.exists_le_mul_two_pow hSi + -- the edge queries on words of width `ℓ x + 1` + have hq (x : β) {u v : List Bool} (hu : u ∈ Cslib.BitString.enumList (ℓ x + 1)) + (hv : v ∈ Cslib.BitString.enumList (ℓ x + 1)) : + (encQ (x, u, v)).length = 2 * (encB x).length + 3 * ℓ x + 5 := by + simp only [encQ, length_encPair, encBits_apply, Cslib.BitString.mem_enumList.mp hu, + Cslib.BitString.mem_enumList.mp hv] + ring + refine ⟨12, (h.mono ((CI + CJ + CT + CS + 1) * 2 ^ 26) (fun x => ?_) fun x => ?_).of_enc_eq + fun x => ?_⟩ + · -- time + set n := (encB x).length + set L := ℓ x + have hmax := listMax_le (f := fun u => listMax (fun v => (Tc (x, u, v) + + if !(u.any fun b => b) && v.headD false then Tg (x, u, v) + Tf (x, v.tail) + else Te (x, u, v))) (Cslib.BitString.enumList (L + 1))) + (l := Cslib.BitString.enumList (L + 1)) (b := CT * 2 ^ (2 * n + 3 * L + 5)) + fun u hu => listMax_le fun v hv => (hCT (x, u, v)).trans_eq (by rw [hq x hu hv]) + change Ti x + 2 ^ ((L + 1 + 2) * (1 + 1)) * (((1 + 1) * (1 + (L + 1) + 1) + n + 1) ^ 2 + + Cslib.Reachability.edgeMax _ x (L + 1)) ≤ _ + unfold Cslib.Reachability.edgeMax + have hTi := hCI x + have hsq : ((1 + 1) * (1 + (L + 1) + 1) + n + 1) ^ 2 ≤ 2 ^ (2 * (2 * L + n + 7)) := + calc _ ≤ (2 * L + n + 7) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ ≤ (2 ^ (2 * L + n + 7)) ^ 2 := Nat.pow_le_pow_left Nat.lt_two_pow_self.le 2 + _ = _ := by rw [← pow_mul, mul_comm] + have hE : 2 ^ ((L + 1 + 2) * (1 + 1)) * (2 ^ (2 * (2 * L + n + 7)) + + CT * 2 ^ (2 * n + 3 * L + 5)) ≤ (CT + 1) * 2 ^ (12 * (n + L)) * 2 ^ 26 := by + have h₁ : 2 ^ ((L + 1 + 2) * (1 + 1)) * 2 ^ (2 * (2 * L + n + 7)) ≤ + 2 ^ (12 * (n + L)) * 2 ^ 26 := by + rw [← pow_add, ← pow_add] + exact Nat.pow_le_pow_right (by norm_num) (by ring_nf; omega) + have h₂ : 2 ^ ((L + 1 + 2) * (1 + 1)) * 2 ^ (2 * n + 3 * L + 5) ≤ + 2 ^ (12 * (n + L)) * 2 ^ 26 := by + rw [← pow_add, ← pow_add] + exact Nat.pow_le_pow_right (by norm_num) (by ring_nf; omega) + have := Nat.mul_le_mul_left CT h₂ + nlinarith + have hI : 2 ^ n ≤ 2 ^ (12 * (n + L)) := Nat.pow_le_pow_right (by norm_num) (by omega) + have hmono := Nat.mul_le_mul_left (2 ^ ((L + 1 + 2) * (1 + 1))) + (Nat.add_le_add hsq hmax) + have hX : 1 ≤ 2 ^ (12 * (n + L)) := Nat.one_le_two_pow + calc _ ≤ CI * 2 ^ n + (CT + 1) * 2 ^ (12 * (n + L)) * 2 ^ 26 := + Nat.add_le_add hTi (hmono.trans hE) + _ ≤ _ := by nlinarith + · -- space + set n := (encB x).length + set L := ℓ x + have hmax := listMax_le (f := fun u => listMax (fun v => (Sc (x, u, v) + + if !(u.any fun b => b) && v.headD false then Sg (x, u, v) + Sf (x, v.tail) + else Se (x, u, v))) (Cslib.BitString.enumList (L + 1))) + (l := Cslib.BitString.enumList (L + 1)) (b := CS * 2 ^ (2 * n + 3 * L + 5)) + fun u hu => listMax_le fun v hv => (hCS (x, u, v)).trans_eq (by rw [hq x hu hv]) + change Si x + (1 + 1) * (1 + (L + 1) + 1) + n + Cslib.Reachability.edgeMax _ x (L + 1) ≤ _ + unfold Cslib.Reachability.edgeMax + have hSi := hCJ x + have hX : 2 ^ (2 * n + 3 * L + 5) ≤ 2 ^ (12 * (n + L)) * 2 ^ 5 := by + rw [← pow_add] + exact Nat.pow_le_pow_right (by norm_num) (by omega) + have hI : 2 ^ n ≤ 2 ^ (12 * (n + L)) := Nat.pow_le_pow_right (by norm_num) (by omega) + have hL : (1 + 1) * (1 + (L + 1) + 1) + n ≤ 6 * 2 ^ (12 * (n + L)) := by + have : n + L < 2 ^ (n + L) := Nat.lt_two_pow_self + have : 2 ^ (n + L) ≤ 2 ^ (12 * (n + L)) := Nat.pow_le_pow_right (by norm_num) (by omega) + omega + have := Nat.mul_le_mul_left CS hX + nlinarith + · congr 1 + rw [Bool.eq_iff_iff, id, savitch_boundedExistsEdge f x (ℓ x)] + simp only [List.any_eq_true, Cslib.BitString.mem_enumList] + +end Cslib.Complexity diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean index 048e3d7038..a38b07c63e 100644 --- a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean +++ b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/Bounds.lean @@ -44,6 +44,12 @@ theorem mono {T' S' : α → ℕ} (h : ComputableInO f encIn encOut T S) (k : of_bounds hc (c * (k + 1)) (fun a => by nlinarith [hT a]) (fun a => by nlinarith [hS a]) (fun a => by nlinarith [hS a, hl a]) +/-- Weaken only the time bound up to a constant, keeping the space bound. -/ +theorem mono_time {T' : α → ℕ} (h : ComputableInO f encIn encOut T S) (k : ℕ) + (hT : ∀ a, T a ≤ k * (T' a + 1)) : ComputableInO f encIn encOut T' S := + h.mono (k + 1) (fun a => (hT a).trans (Nat.mul_le_mul_right _ (Nat.le_succ k))) + fun _ => (Nat.le_succ _).trans (Nat.le_mul_of_pos_left _ (Nat.succ_pos k)) + /-- Pointwise larger bounds retain the same encoded-output charge. -/ theorem mono_le {T' S' : α → ℕ} (h : ComputableInO f encIn encOut T S) (hT : ∀ a, T a ≤ T' a) (hS : ∀ a, S a ≤ S' a) : diff --git a/ComplexityTests/NondeterministicTimeHierarchy.lean b/ComplexityTests/NondeterministicTimeHierarchy.lean new file mode 100644 index 0000000000..f688a9c506 --- /dev/null +++ b/ComplexityTests/NondeterministicTimeHierarchy.lean @@ -0,0 +1,44 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Computability.Complexity.Classes.Hierarchy.NondeterministicTime + +/-! The coarse nondeterministic time hierarchy on concrete degrees. The lazy diagonal for degree +`1` is in nondeterministic time `O((n + 1) ^ 20)` and not in nondeterministic linear time, so the +two classes differ; degree `0` separates constant from `O((n + 1) ^ 14)` nondeterministic time. +The verifier rejects inputs without a short description, such as the empty input. -/ + +public section + +namespace ComplexityTests.NondeterministicTimeHierarchy + +open Cslib Cslib.Complexity Turing Turing.MultiTapeTM + +/-- The lazy diagonal for degree `1` separates nondeterministic linear time from nondeterministic +time `O((n + 1) ^ 20)`, at the separating problem itself. -/ +example : lazyDiagonal 1 ∈ NTIME encBits (fun n => (n + 1) ^ 20) ∧ + lazyDiagonal 1 ∉ NTIME encBits (fun n => (n + 1) ^ 1) := + ⟨lazyDiagonal_mem_NTIME 1, lazyDiagonal_notMem_NTIME 1⟩ + +/-- Nondeterministic linear time is strictly inside nondeterministic time `O((n + 1) ^ 20)`. -/ +example : NTIME encBits (fun n => n + 1) ⊂ NTIME encBits fun n => (n + 1) ^ 20 := by + simpa using NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le (d := 1) (e := 20) le_rfl + +/-- Nondeterministic constant time is strictly inside nondeterministic time `O((n + 1) ^ 14)`. -/ +example : NTIME encBits (fun _ => 1) ⊂ NTIME encBits fun n => (n + 1) ^ 14 := by + simpa using NTIME_succ_pow_ssubset_NTIME_succ_pow_of_le (d := 0) (e := 14) le_rfl + +/-- The empty input carries no short description, so the verifier rejects it with every +certificate, and the empty input is not in the lazy diagonal. -/ +example (d : ℕ) (v : List Bool) : LazyDiagonal.verifier d ([], v) = false := by + simp [LazyDiagonal.verifier, Diagonal.shortDesc, unframe] + +example (d : ℕ) : [] ∉ lazyDiagonal d := by + simp [lazyDiagonal, LazyDiagonal.verifier, Diagonal.shortDesc, unframe] + +end ComplexityTests.NondeterministicTimeHierarchy diff --git a/ROADMAP.md b/ROADMAP.md index 966929f763..5b0f082155 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -180,10 +180,12 @@ index links to canonical definitions rather than keeping a second set. - **Time hierarchies** ([#1572]): native polynomial padding separates every pair of integer degrees `1 ≤ a < b`, and the `O(t log t)` universal simulation ([#800]) gives `DTIME f ⊂ DTIME g` for time-constructible `f ≥ n` once `(f + 1) * polylog f ≤ g`, with an - absolute exponent (`Hierarchy/TimePolylog`). Remaining targets include the textbook + absolute exponent (`Hierarchy/TimePolylog`). For nondeterministic time, lazy diagonalization + separates degree `d` from degree `6 * d + 14`. Remaining targets include the textbook `f log f = o(g)` hierarchy, which needs the diagonal to clock the universal machine's own - steps, noninteger polynomial degrees, and sharp nondeterministic and alternating time - hierarchies with certified simulators. + steps, noninteger polynomial degrees, the nondeterministic hierarchy at every pair of integer + degrees by padding, and sharp nondeterministic and alternating time hierarchies with certified + simulators. - The space hierarchy for `log n ≤ g n`, which needs a universal simulation that does not hold its input. - **Space:** Savitch bounds for general space-constructible functions. diff --git a/references.bib b/references.bib index fdb300790e..6a7474a26d 100644 --- a/references.bib +++ b/references.bib @@ -280,6 +280,15 @@ @article{AngluinLaird1988 doi = {10.1007/BF00116829} } +@misc{AroraBarak07, + author = {Sanjeev Arora and + Boaz Barak}, + title = {Computational Complexity: {A} Modern Approach}, + howpublished = {Draft of a book, web draft dated 2007-01-08}, + year = {2007}, + url = {https://theory.cs.princeton.edu/complexity/book.pdf} +} + @book{AroraBarak09, author = {Sanjeev Arora and Boaz Barak}, @@ -5327,6 +5336,17 @@ @article{Yap1983 doi = {10.1016/0304-3975(83)90020-8} } +@article{Zak1983, + author = {{\v{Z}}{\'a}k, Stanislav}, + title = {A {T}uring Machine Time Hierarchy}, + journal = {Theoretical Computer Science}, + volume = {26}, + number = {3}, + pages = {327--333}, + year = {1983}, + doi = {10.1016/0304-3975(83)90015-4} +} + @misc{Zhao2023, author = {Zhao, Yufei}, title = {Probabilistic Methods in Combinatorics},