diff --git a/Complexity.lean b/Complexity.lean index 3bb8bfd771..111b827d45 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 @@ -1179,9 +1182,12 @@ 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 +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 public import Complexity.Computability.Complexity.Classes.Hierarchy.Space @@ -1239,6 +1245,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 @@ -1246,6 +1253,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 @@ -1257,6 +1265,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 @@ -1282,6 +1291,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 @@ -1347,7 +1357,9 @@ 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 public import Complexity.Computability.Complexity.Classes.Toda.Counting public import Complexity.Computability.Complexity.Classes.Toda.Hierarchy @@ -1436,6 +1448,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 @@ -1606,6 +1619,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 +1630,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 @@ -2022,6 +2037,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 @@ -2344,6 +2360,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 +2390,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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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. -/ 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/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/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/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/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/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)` -/ 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/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/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/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 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/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 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 diff --git a/ROADMAP.md b/ROADMAP.md index 966929f763..5a1fb33263 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -178,12 +178,14 @@ 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`, 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 - `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. + degrees `1 ≤ a < b`, in deterministic and in nondeterministic time, 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`). For + nondeterministic time, padding amplifies the coarse lazy-diagonal hierarchy, which 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. - 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 36330860a1..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}, @@ -2362,6 +2371,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 +2781,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}, @@ -4163,6 +4193,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}, @@ -5293,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}, 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