From d5a8e214597eb703100df208b8665689ea99b61f Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:14:32 -0400 Subject: [PATCH 01/13] feat(Quantum): the acceptance gap of a circuit as a tensor contraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build an explicit Boolean tensor network from a list of Hadamard-Toffoli gate codes and its mirror copy, and prove that its contraction is `acceptGap`: over any commutative ring with Hadamard sign -1 (`contraction_network`), and over ℕ with sign 2^P - 1 modulo 2^P once P exceeds the number of Hadamard gates (`contraction_network_mod`). The network has 4N edges for N <= 3L qubit occurrences of L gates, degree at most six and full tables. General lemmas: equality vertices on edge pairs (`contraction_append_pairs`) and maps of tables (`map_contraction`). `length_foldl_pathStep` becomes public and `acceptGap_nonneg_and_le` bounds the gap by 2^h. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Circuit/Quantum/PathNetwork.lean | 1129 +++++++++++++++++ .../Circuit/Quantum/PathSum.lean | 19 +- ComplexityTests/QuantumPathNetwork.lean | 66 + 4 files changed, 1214 insertions(+), 1 deletion(-) create mode 100644 Complexity/Computability/Circuit/Quantum/PathNetwork.lean create mode 100644 ComplexityTests/QuantumPathNetwork.lean diff --git a/Complexity.lean b/Complexity.lean index 6d336ab1d..1b5ff0934 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1031,6 +1031,7 @@ public import Complexity.Computability.Circuit.Quantum.Classical public import Complexity.Computability.Circuit.Quantum.Code public import Complexity.Computability.Circuit.Quantum.Counter public import Complexity.Computability.Circuit.Quantum.Parallel +public import Complexity.Computability.Circuit.Quantum.PathNetwork public import Complexity.Computability.Circuit.Quantum.PathSum public import Complexity.Computability.Circuit.Quantum.Reversible public import Complexity.Computability.Circuit.Quantum.Uniform diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean new file mode 100644 index 000000000..6d51c5c73 --- /dev/null +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -0,0 +1,1129 @@ +/- +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.Quantum.PathSum +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +import Mathlib.Data.List.GetD +import Mathlib.Data.List.OfFn +import Mathlib.Logic.Equiv.Fin.Basic + +/-! +# The acceptance gap of a circuit as a tensor contraction + +The acceptance gap of a list of gate codes (`Cslib.Quantum.acceptGap`) is the contraction of an +explicit Boolean tensor network (`Cslib.Quantum.PathNetwork.network`) built from the circuit and a +mirror copy of it (`Cslib.Quantum.PathNetwork.contraction_network`). This is the form in which the +contraction algorithm of [OpenAI2026ETH], Theorem D.1, simulates quantum circuits exactly. + +**Occurrences.** The *qubits* of a gate code are its target and then its controls +(`Cslib.Quantum.PathNetwork.qubits`). Listing them gate by gate numbers the *occurrences* +`0, …, N - 1` of qubits in gates (`Cslib.Quantum.PathNetwork.occurrences`); gate `i` owns the +occurrences from `base gs i` on. The last occurrence of qubit `j` before position `p` +(`Cslib.Quantum.PathNetwork.lastOcc`) is where the value of `j` entering position `p` was written. + +**The network.** It has two *copies*, `c = 0` and `c = 1`, of the circuit. In copy `c`, occurrence +`u` has the *segment* `c N + u`, with an *out* edge `2 (c N + u)` and a *link* edge +`2 (c N + u) + 1`, so there are `4 N` edges. + +* **Gate vertices (rows).** In each copy, gate `i` has a vertex with the out edges of its + occurrences and, for each of its qubits written by an earlier gate, the link edge of that earlier + occurrence. A qubit not written before is read from the input register instead + (`Cslib.Quantum.PathNetwork.inPort`). The tensor (`Cslib.Quantum.PathNetwork.gateEntry`) is, for + the Hadamard gate, `neg` when the input and output bits of the target are both one and `1` + otherwise; for a classical gate it is the indicator that the outputs are the gate applied to the + inputs. +* **Glue vertices (rows).** The link edges of the last occurrence of each qubit in the two copies + meet at a glue vertex with the equality tensor, or, for qubit `0`, the tensor that requires both + bits to be one. A *scalar* vertex without ports requires the input bit of qubit `0` to be one when + no gate touches qubit `0`. +* **Segment vertices (columns).** Each segment joins its out and link edges with the equality + tensor. + +So every edge joins a row to a column, every vertex has at most six ports, and untouched qubits +cost nothing. With `neg = -1`, an assignment of nonzero weight is a pair of paths of the path sum +(`Cslib.Quantum.pathRun`) ending in the same register with qubit `0` equal to one, and its weight +is the product of their signs, so the contraction is `acceptGap` +(`Cslib.Quantum.PathNetwork.contraction_network`). The network is defined for any value `neg` of +the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` modulo `2 ^ P` +(`Cslib.Quantum.PathNetwork.contraction_network_mod`). + +## Main definitions + +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.eqTable`, + `Cslib.Circuits.Frontier.ExplicitTensorNetwork.mapTables`: the equality table on two ports and + maps of the table entries. +* `Cslib.Quantum.PathNetwork.qubits`, `Cslib.Quantum.PathNetwork.occurrences`, + `Cslib.Quantum.PathNetwork.base`, `Cslib.Quantum.PathNetwork.lastOcc`: occurrences of qubits. +* `Cslib.Quantum.PathNetwork.gateEntry`, `Cslib.Quantum.PathNetwork.table`: the tensor of a gate + and the tables of the vertices. +* `Cslib.Quantum.PathNetwork.inPort`, `Cslib.Quantum.PathNetwork.gatePorts`, + `Cslib.Quantum.PathNetwork.inBit`, `Cslib.Quantum.PathNetwork.gateVertex`, + `Cslib.Quantum.PathNetwork.acceptTable`, `Cslib.Quantum.PathNetwork.glueVertex`, + `Cslib.Quantum.PathNetwork.scalarVertex`: the vertices. +* `Cslib.Quantum.PathNetwork.network`: the network of a circuit, with its number of rows. + +## Main results + +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.contraction_append_pairs`: equality vertices on + consecutive pairs of edges reduce the contraction to a sum over one bit per pair. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.map_contraction`: the contraction commutes with + maps of semirings applied to the tables. +* `Cslib.Quantum.PathNetwork.lastOcc_succ`, `Cslib.Quantum.PathNetwork.lastOcc_eq_some`, + `Cslib.Quantum.PathNetwork.lastOcc_eq_none`: last occurrences. +* `Cslib.Quantum.PathNetwork.contraction_network`: with the Hadamard sign `-1`, the contraction + is the acceptance gap. +* `Cslib.Quantum.PathNetwork.network_mapTables`, + `Cslib.Quantum.PathNetwork.contraction_network_mod`: over `ℕ`, with the Hadamard sign + `2 ^ P - 1`, the contraction modulo `2 ^ P` is the acceptance gap once `P` exceeds the number + of Hadamard gates. +* `Cslib.Quantum.PathNetwork.numEdges_network`, + `Cslib.Quantum.PathNetwork.length_vertices_network`, + `Cslib.Quantum.PathNetwork.maxDegree_network_le`, + `Cslib.Quantum.PathNetwork.length_table_network`, + `Cslib.Quantum.PathNetwork.length_occurrences_le`: `4 N` edges for `N ≤ 3 L` occurrences of the + `L` gates, degree at most six and full tables. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Theorem D.1 (p. 53). +-/ + +public section + +namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork + +/-- A product of indicators is the indicator of the conjunction. -/ +theorem prod_map_boole {R : Type*} [CommSemiring R] {α : Type*} (l : List α) (p : α → Prop) + [DecidablePred p] : + (l.map fun a => if p a then (1 : R) else 0).prod = if ∀ a ∈ l, p a then 1 else 0 := by + induction l with + | nil => simp + | cons a l ih => + rw [List.map_cons, List.prod_cons, ih] + by_cases ha : p a <;> simp [ha] + +/-- The equality table on two ports. -/ +@[expose] def eqTable {R : Type*} [Zero R] [One R] : List R := [1, 0, 0, 1] + +theorem entry_eqTable {R : Type*} [Zero R] [One R] (a b : Bool) : + entry (eqTable : List R) [a, b] = if a = b then 1 else 0 := by + cases a <;> cases b <;> rfl + +/-- **Equality vertices on pairs of edges.** If the last vertices of a network with `2 W` edges are +`W` equality vertices, vertex `w` joining the edges `2 w` and `2 w + 1`, then the contraction is the +sum over one bit `y w` per pair of the product of the other entries, where edge `e` carries +`y (e / 2)`. -/ +theorem contraction_append_pairs {R : Type*} [CommSemiring R] (W : ℕ) + (rows : List (List ℕ × List R)) : + contraction (⟨2 * W, rows ++ (List.range W).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩ : + ExplicitTensorNetwork R) = + ∑ y : Fin W → Bool, (rows.map fun v => entry v.2 + (v.1.map fun e => if h : e / 2 < W then y ⟨e / 2, h⟩ else false)).prod := by + classical + set G : ExplicitTensorNetwork R := + ⟨2 * W, rows ++ (List.range W).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩ + -- an assignment that repeats each bit of `y` on the two edges of its pair + let dup : (Fin W → Bool) → Fin (2 * W) → Bool := fun y e => y ⟨e / 2, by omega⟩ + have hdup : Function.Injective dup := by + intro y y' h + funext w + have := congrFun h ⟨2 * w, by omega⟩ + simpa [dup, show 2 * (w : ℕ) / 2 = w by omega] using this + have hpb (x : Fin (2 * W) → Bool) (ps : List ℕ) : + G.portBits x ps = ps.map fun i => if h : i < 2 * W then x ⟨i, h⟩ else false := rfl + -- the weight of an assignment + let F : (Fin (2 * W) → Bool) → R := fun x => + (G.vertices.map fun v => entry v.2 (G.portBits x v.1)).prod + have hcol (x : Fin (2 * W) → Bool) : + (((List.range W).map fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List R)).map + fun v => entry v.2 (G.portBits x v.1)).prod = + if ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ then 1 else 0 := by + rw [List.map_map] + have h1 : ∀ w ∈ List.range W, ((fun v => entry v.2 (G.portBits x v.1)) ∘ + fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List R)) w = + if (∀ h : w < W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩) then 1 else 0 := by + intro w hw + have hw' : w < W := List.mem_range.mp hw + simp only [Function.comp_apply, hpb, List.map_cons, List.map_nil, + show 2 * w < 2 * W by omega, show 2 * w + 1 < 2 * W by omega, dite_true, entry_eqTable] + simp [hw'] + rw [List.map_congr_left h1] + have key := prod_map_boole (R := R) (List.range W) + (fun w => ∀ h : w < W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩) + rw [key] + congr 1 + exact propext ⟨fun h w => h w (List.mem_range.mpr w.2) w.2, + fun h w _ hw => h ⟨w, hw⟩⟩ + have hF (x : Fin (2 * W) → Bool) : F x = + (rows.map fun v => entry v.2 (G.portBits x v.1)).prod * + if ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ then 1 else 0 := by + simp only [F, G, List.map_append, List.prod_append] + rw [← hcol] + -- assignments off the image of `dup` have weight zero + have hzero (x : Fin (2 * W) → Bool) (hx : x ∉ Finset.univ.image dup) : F x = 0 := by + rw [hF] + have : ¬ ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ := by + intro h + refine hx (Finset.mem_image.mpr ⟨fun w => x ⟨2 * w, by omega⟩, Finset.mem_univ _, ?_⟩) + funext e + obtain ⟨e, he⟩ := e + simp only [dup] + rcases Nat.even_or_odd' e with ⟨w, rfl | rfl⟩ + · exact congrArg x (Fin.ext (by change 2 * (2 * w / 2) = 2 * w; omega)) + · refine (congrArg x (Fin.ext ?_)).trans (h ⟨w, by omega⟩) + change 2 * ((2 * w + 1) / 2) = 2 * w + omega + rw [ite_eq_right this, mul_zero] + change ∑ x, F x = _ + rw [← Finset.sum_subset (Finset.subset_univ (Finset.univ.image dup)) fun x _ hx => hzero x hx, + Finset.sum_image fun y _ y' _ h => hdup h] + refine Finset.sum_congr rfl fun y _ => ?_ + rw [hF, ite_eq_left fun w => by simp [dup, show 2 * (w : ℕ) / 2 = w by omega, + show (2 * (w : ℕ) + 1) / 2 = w by omega], mul_one] + congr 1 + refine List.map_congr_left fun v _ => ?_ + rw [hpb] + congr 1 + refine List.map_congr_left fun e _ => ?_ + by_cases he : e < 2 * W + · simp [he, dup, show e / 2 < W by omega] + · simp [he, show ¬ e / 2 < W by omega] + +/-- The network with every table entry mapped by `ψ`. -/ +@[expose] def mapTables {R S : Type*} (ψ : R → S) (d : ExplicitTensorNetwork R) : + ExplicitTensorNetwork S := + ⟨d.numEdges, d.vertices.map fun v => (v.1, v.2.map ψ)⟩ + +/-- **The contraction commutes with maps of semirings** applied to the tables. -/ +theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) + (d : ExplicitTensorNetwork R) : ψ d.contraction = (d.mapTables ψ).contraction := by + rw [contraction, map_sum] + refine Finset.sum_congr rfl fun x _ => ?_ + rw [map_list_prod, List.map_map] + simp only [mapTables, List.map_map] + congr 1 + refine List.map_congr_left fun v _ => ?_ + simp only [Function.comp_apply, entry] + rw [← map_zero ψ, List.getD_map] + rfl + +end Cslib.Circuits.Frontier.ExplicitTensorNetwork + +namespace Cslib.Quantum.PathNetwork + +open Cslib.Circuits.Frontier ExplicitTensorNetwork + +/-! ### Occurrences -/ + +/-- The qubits of a gate code: its target, then its controls. -/ +@[expose] def qubits (g : GateCode) : List ℕ := g.2.1 :: g.2.2 + +/-- The occurrences of qubits in a list of gate codes, gate by gate. -/ +@[expose] def occurrences (gs : List GateCode) : List ℕ := gs.flatMap qubits + +/-- The number of the first occurrence of gate `i`. -/ +@[expose] def base (gs : List GateCode) (i : ℕ) : ℕ := (occurrences (gs.take i)).length + +/-- The last occurrence of the qubit `j` before position `p` in the occurrence list `qs`. -/ +@[expose] def lastOcc (qs : List ℕ) (j p : ℕ) : Option ℕ := + (List.range p).reverse.find? fun w => decide (qs.getD w 0 = j) + +/-! ### Tensors and vertices -/ + +section Vertices + +variable {R : Type*} [Zero R] [One R] + +/-- The tensor of the gate code `g` at the bits `ins` entering and `outs` leaving its qubits. For +the Hadamard gate it is `neg` when the target enters and leaves as one, and `1` otherwise; for a +classical gate it is `1` when the outputs are the gate applied to the inputs, and `0` otherwise. -/ +@[expose] def gateEntry (neg : R) (g : GateCode) (ins outs : List Bool) : R := + if g.1 then (if ins.headD false && outs.headD false then neg else 1) + else if outs = (ins.headD false ^^ ins.tail.all id) :: ins.tail then 1 else 0 + +/-- The table of the values `f 0, …, f (2 ^ w - 1)` for `w ≤ 6`, listed by keeping the positions +below `2 ^ w` among the first `2 ^ 6`. -/ +@[expose] def table (w : ℕ) (f : ℕ → R) : List R := + ((List.range (2 ^ 6)).filter fun idx => idx < 2 ^ w).map f + +/-- The port at which a gate with first occurrence `b` reads the qubit `j` in copy `c`: the link +edge of the last earlier occurrence of `j`, or none when the gate reads `j` from the input. -/ +@[expose] def inPort (gs : List GateCode) (c b j : ℕ) : Option ℕ := + (lastOcc (occurrences gs) j b).map fun w => 2 * (c * (occurrences gs).length + w) + 1 + +/-- The ports of gate `i` in copy `c`: the out edges of its occurrences, then its input ports. -/ +@[expose] def gatePorts (gs : List GateCode) (c i : ℕ) : List ℕ := + (List.range (qubits (gs.getD i default)).length).map + (fun k => 2 * (c * (occurrences gs).length + base gs i + k)) ++ + (qubits (gs.getD i default)).filterMap (inPort gs c (base gs i)) + +/-- The bit entering qubit `j` of gate `i` in copy `c` at the table position `idx`: the input bit +of `j`, or the bit of its input port. -/ +@[expose] def inBit (X : List Bool) (gs : List GateCode) (c i idx j : ℕ) : Bool := + match inPort gs c (base gs i) j with + | none => X.getD j false + | some e => idx.testBit ((gatePorts gs c i).idxOf e) + +/-- The vertex of gate `i` in copy `c`, for the input register `X`. -/ +@[expose] def gateVertex (neg : R) (X : List Bool) (gs : List GateCode) (c i : ℕ) : + List ℕ × List R := + (gatePorts gs c i, table (gatePorts gs c i).length fun idx => + gateEntry neg (gs.getD i default) ((qubits (gs.getD i default)).map (inBit X gs c i idx)) + ((List.range (qubits (gs.getD i default)).length).map idx.testBit)) + +/-- The table on two ports that requires both bits to be one. -/ +@[expose] def acceptTable : List R := [0, 0, 0, 1] + +/-- The glue vertex of occurrence `w`: when `w` is the last occurrence of its qubit, it joins the +link edges of `w` in the two copies, requiring equal bits, and both bits one for qubit `0`. -/ +@[expose] def glueVertex (gs : List GateCode) (w : ℕ) : List ℕ × List R := + if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w then + ([2 * w + 1, 2 * ((occurrences gs).length + w) + 1], + if (occurrences gs).getD w 0 = 0 then acceptTable else eqTable) + else ([], [1]) + +/-- The scalar vertex: when no gate touches qubit `0`, it requires the input bit of qubit `0` to be +one. -/ +@[expose] def scalarVertex (X : List Bool) (gs : List GateCode) : List ℕ × List R := + ([], [if 0 ∈ occurrences gs ∨ X.getD 0 false = true then 1 else 0]) + +/-- **The network of a circuit** with the input register `X` and the Hadamard sign `neg`, with its +number of rows: the gate vertices of the two copies, the glue vertices and the scalar vertex, then +the segment vertices. -/ +@[expose] def network (neg : R) (X : List Bool) (gs : List GateCode) : + ℕ × ExplicitTensorNetwork R := + (2 * gs.length + (occurrences gs).length + 1, ⟨2 * (2 * (occurrences gs).length), + (List.range gs.length).map (gateVertex neg X gs 0) ++ + (List.range gs.length).map (gateVertex neg X gs 1) ++ + (List.range (occurrences gs).length).map (glueVertex gs) ++ [scalarVertex X gs] ++ + (List.range (2 * (occurrences gs).length)).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩) + +end Vertices + +/-! ### Runs along the occurrences -/ + +/-- The sign `±1` of a Boolean. -/ +private def sgn {R : Type*} [CommRing R] (σ : Bool) : R := if σ then -1 else 1 + +private theorem sgn_xor {R : Type*} [CommRing R] (σ τ : Bool) : + (sgn (σ ^^ τ) : R) = sgn σ * sgn τ := by + cases σ <;> cases τ <;> simp [sgn] + +/-- The register `r` with the qubits `js` set to the bits `bs`, in order. -/ +private def setQubits (r : ℕ → Bool) (js : List ℕ) (bs : List Bool) : ℕ → Bool := + (js.zip bs).foldl (fun r p => Function.update r p.1 p.2) r + +private theorem setQubits_cons (r : ℕ → Bool) (j : ℕ) (js : List ℕ) (b : Bool) (bs : List Bool) : + setQubits r (j :: js) (b :: bs) = setQubits (Function.update r j b) js bs := rfl + +/-- Setting control qubits to their current bits changes nothing after the target. -/ +private theorem setQubits_controls (r : ℕ → Bool) (t : ℕ) (b : Bool) (cs : List ℕ) + (ht : t ∉ cs) : + setQubits r (t :: cs) (b :: cs.map r) = Function.update r t b := by + rw [setQubits_cons] + suffices h : ∀ r' : ℕ → Bool, (∀ c ∈ cs, r' c = r c) → + setQubits r' cs (cs.map r) = r' by + exact h _ fun c hc => Function.update_of_ne (by rintro rfl; exact ht hc) _ _ + intro r' hr' + induction cs generalizing r' with + | nil => rfl + | cons c cs ih => + rw [List.map_cons, setQubits_cons, ← hr' c List.mem_cons_self, Function.update_eq_self] + exact ih (fun h => ht (List.mem_cons_of_mem _ h)) r' fun c' hc' => + hr' c' (List.mem_cons_of_mem _ hc') + +/-- The weight of a run of the gates `gs` from the register `r` that writes the bits `ys` to the +qubits of the gates in turn, times `Φ` of the final register. -/ +private def trajWeight {R : Type*} [Zero R] [One R] [Mul R] (neg : R) : + List GateCode → (ℕ → Bool) → List Bool → ((ℕ → Bool) → R) → R + | [], r, _, Φ => Φ r + | g :: gs, r, ys, Φ => gateEntry neg g ((qubits g).map r) (ys.take (qubits g).length) * + trajWeight neg gs (setQubits r (qubits g) (ys.take (qubits g).length)) + (ys.drop (qubits g).length) Φ + +/-- The weight of a run is linear in the final function. -/ +private theorem trajWeight_sum {R : Type*} [CommSemiring R] (neg : R) {ι : Type*} (I : Finset ι) + (gs : List GateCode) (r : ℕ → Bool) (ys : List Bool) (f : ι → (ℕ → Bool) → R) : + trajWeight neg gs r ys (fun r' => ∑ i ∈ I, f i r') = ∑ i ∈ I, trajWeight neg gs r ys (f i) := by + induction gs generalizing r ys with + | nil => rfl + | cons g gs ih => + simp only [trajWeight] + rw [ih, Finset.mul_sum] + +private theorem occurrences_nil : occurrences [] = [] := rfl + +private theorem occurrences_cons (g : GateCode) (gs : List GateCode) : + occurrences (g :: gs) = qubits g ++ occurrences gs := List.flatMap_cons + +/-- A sum over the bit strings of length `a + b` splits into the two parts. -/ +private theorem sum_ofFn_append {R : Type*} [AddCommMonoid R] {n a b : ℕ} (h : n = a + b) + (f : List Bool → R) : + ∑ ys : Fin n → Bool, f (List.ofFn ys) = + ∑ ys : Fin a → Bool, ∑ zs : Fin b → Bool, f (List.ofFn ys ++ List.ofFn zs) := by + subst h + rw [← (Fin.appendEquiv a b).sum_comp, Fintype.sum_prod_type] + refine Finset.sum_congr rfl fun ys _ => Finset.sum_congr rfl fun zs _ => ?_ + simp [Fin.appendEquiv] + +/-- A sum over the bit strings of length `k` of an indicator of one string `v` of length `k`. -/ +private theorem sum_ofFn_ite_eq {R : Type*} [AddCommMonoid R] {k : ℕ} (v : List Bool) + (hv : v.length = k) (F : List Bool → R) : + ∑ ys : Fin k → Bool, (if List.ofFn ys = v then F (List.ofFn ys) else 0) = F v := by + subst hv + rw [Fintype.sum_eq_single (fun i => v[i])] + · have hv : (List.ofFn fun i : Fin v.length => v[i]) = v := List.ofFn_getElem + rw [hv, ite_eq_left rfl] + · intro ys hys + refine ite_eq_right fun h => hys (List.ofFn_injective (h.trans List.ofFn_getElem.symm)) + +private theorem getD_set_eq_update (s : List Bool) {t : ℕ} (ht : t < s.length) (b : Bool) : + (fun j => (s.set t b).getD j false) = Function.update (fun j => s.getD j false) t b := by + funext j + by_cases hj : j = t + · subst hj + simp [List.getD_eq_getElem?_getD, ht] + · rw [Function.update_of_ne hj] + simp [List.getD_eq_getElem?_getD, Ne.symm hj] + +/-- **The runs along the occurrences sum to the path sum** (with an initial sign `σ`): summing the +weight of the runs over all bit strings written to the occurrences gives the sum over the paths +of the path simulation of their signs times `Φ` of their final registers. -/ +private theorem sgn_mul_sum_trajWeight {R : Type*} [CommRing R] (gs : List GateCode) + (s : List Bool) (hgs : ∀ g ∈ gs, g.WellFormed s.length) (σ : Bool) (Φ : (ℕ → Bool) → R) : + sgn σ * ∑ ys : Fin (occurrences gs).length → Bool, + trajWeight (-1) gs (fun j => s.getD j false) (List.ofFn ys) Φ = + ∑ π : Fin (hadamardCount gs) → Bool, + sgn (gs.foldl pathStep (s, List.ofFn π, σ)).2.2 * + Φ (fun j => (gs.foldl pathStep (s, List.ofFn π, σ)).1.getD j false) := by + induction gs generalizing s σ with + | nil => + change sgn σ * ∑ ys : Fin 0 → Bool, Φ (fun j => s.getD j false) = + ∑ π : Fin 0 → Bool, sgn σ * Φ (fun j => s.getD j false) + simp + | cons g gs ih => + have hgs' : ∀ g ∈ gs, g.WellFormed s.length := fun g' hg' => hgs g' (List.mem_cons_of_mem _ hg') + have hwf := hgs g List.mem_cons_self + rw [sum_ofFn_append ((congrArg List.length (occurrences_cons g gs)).trans + (List.length_append ..)) fun ys => trajWeight (-1) (g :: gs) (fun j => s.getD j false) ys Φ] + simp only [trajWeight, List.take_left' (List.length_ofFn), List.drop_left' (List.length_ofFn)] + obtain ⟨_ | _, t, cs⟩ := g + · -- a classical gate: one bit string has nonzero weight + obtain ⟨hlen, hnd, hlt⟩ := hwf + have ht : t < s.length := hlt t List.mem_cons_self + have htcs : t ∉ cs := (List.nodup_cons.mp hnd).1 + have hcs : ∀ c ∈ cs, (fun j => s.getD j false) c = s.getD c false := fun _ _ => rfl + set b := s.getD t false ^^ cs.all fun c => s.getD c false + have hentry (ys : List Bool) : + gateEntry (-1 : R) (false, t, cs) ((qubits (false, t, cs)).map fun j => s.getD j false) + ys = if ys = b :: cs.map (fun j => s.getD j false) then 1 else 0 := by + simp only [gateEntry, qubits, List.map_cons, List.headD_cons, List.tail_cons, + Bool.false_eq_true, ite_false, List.all_map, Function.comp_def, id, b] + simp only [hentry, ite_mul, one_mul, zero_mul] + rw [Finset.sum_comm, Finset.mul_sum] + have hset : setQubits (fun j => s.getD j false) (qubits (false, t, cs)) + (b :: cs.map fun j => s.getD j false) = fun j => (s.set t b).getD j false := by + rw [qubits, setQubits_controls _ _ _ _ htcs, getD_set_eq_update s ht] + have hlen' : (qubits (false, t, cs)).length = + (b :: cs.map fun j => s.getD j false).length := by + simp [qubits] + simp_rw [fun zs => sum_ofFn_ite_eq (b :: cs.map fun j => s.getD j false) hlen'.symm + (fun ys => trajWeight (-1 : R) gs (setQubits (fun j => s.getD j false) + (qubits (false, t, cs)) ys) (List.ofFn zs) Φ), hset] + rw [← Finset.mul_sum] + have h := ih (s.set t b) (by simpa using hgs') σ + rw [h] + rfl + · -- a Hadamard gate: sum over the bit written to the target + obtain ⟨ht, rfl⟩ := hwf + have hq : qubits (true, t, []) = [t] := rfl + have hsum1 (f : List Bool → R) : ∑ ys : Fin (qubits (true, t, [])).length → Bool, + f (List.ofFn ys) = ∑ c : Bool, f [c] := by + change ∑ ys : Fin 1 → Bool, f (List.ofFn ys) = _ + rw [← (Equiv.funUnique (Fin 1) Bool).symm.sum_comp] + rfl + rw [hsum1 fun ys => ∑ zs : Fin (occurrences gs).length → Bool, + gateEntry (-1 : R) (true, t, []) ((qubits (true, t, [])).map fun j => s.getD j false) ys * + trajWeight (-1) gs (setQubits (fun j => s.getD j false) (qubits (true, t, [])) ys) + (List.ofFn zs) Φ, Finset.mul_sum] + rw [hadamardCount_cons_true, ← (Fin.consEquiv fun _ => Bool).sum_comp, + Fintype.sum_prod_type] + refine Finset.sum_congr rfl fun c _ => ?_ + simp only [Fin.consEquiv_apply, List.ofFn_succ, Fin.cons_zero, Fin.cons_succ, + List.foldl_cons, pathStep, List.headD_cons, List.tail_cons] + have hentry : gateEntry (-1 : R) (true, t, []) ((qubits (true, t, [])).map + fun j => s.getD j false) [c] = sgn (s.getD t false && c) := by + simp [gateEntry, qubits, sgn] + have hset : setQubits (fun j => s.getD j false) (qubits (true, t, [])) [c] = + fun j => (s.set t c).getD j false := by + rw [getD_set_eq_update s ht c] + rfl + rw [← Finset.mul_sum, hentry, hset, ← mul_assoc, ← sgn_xor] + exact ih (s.set t c) (by simpa using hgs') (σ ^^ (s.getD t false && c)) + +/-! ### Last occurrences -/ + +theorem lastOcc_zero (qs : List ℕ) (j : ℕ) : lastOcc qs j 0 = none := rfl + +theorem lastOcc_succ (qs : List ℕ) (j p : ℕ) : + lastOcc qs j (p + 1) = if qs.getD p 0 = j then some p else lastOcc qs j p := by + unfold lastOcc + rw [List.range_succ, List.reverse_append, List.reverse_singleton, List.singleton_append, + List.find?_cons] + by_cases h : qs.getD p 0 = j + · simp only [h, decide_true, ↓reduceIte] + · simp only [h, decide_false, ↓reduceIte] + +/-- The last occurrence of `j` before `p` is an occurrence of `j` before `p`, and no later +occurrence before `p` is one of `j`. -/ +theorem lastOcc_eq_some {qs : List ℕ} {j p w : ℕ} (h : lastOcc qs j p = some w) : + w < p ∧ qs.getD w 0 = j ∧ ∀ w', w < w' → w' < p → qs.getD w' 0 ≠ j := by + induction p with + | zero => simp [lastOcc_zero] at h + | succ p ih => + rw [lastOcc_succ] at h + split_ifs at h with hp + · cases h + exact ⟨by omega, hp, fun w' h1 h2 => by omega⟩ + · obtain ⟨h1, h2, h3⟩ := ih h + refine ⟨by omega, h2, fun w' hw1 hw2 => ?_⟩ + rcases Nat.lt_succ_iff_lt_or_eq.mp hw2 with hw | rfl + · exact h3 w' hw1 hw + · exact hp + +/-- There is no occurrence of `j` before `p` exactly when no position before `p` holds `j`. -/ +theorem lastOcc_eq_none {qs : List ℕ} {j p : ℕ} : + lastOcc qs j p = none ↔ ∀ w < p, qs.getD w 0 ≠ j := by + induction p with + | zero => simp [lastOcc_zero] + | succ p ih => + rw [lastOcc_succ] + by_cases hp : qs.getD p 0 = j + · simp only [hp, ite_true, reduceCtorEq, false_iff, not_forall] + exact ⟨p, by omega, fun h => h hp⟩ + · simp only [hp, ite_false, ih] + constructor + · intro h w hw + rcases Nat.lt_succ_iff_lt_or_eq.mp hw with hw | rfl + · exact h w hw + · exact hp + · exact fun h w hw => h w (by omega) + +/-! ### Occurrences of gates -/ + +private theorem occurrences_append (gs hs : List GateCode) : + occurrences (gs ++ hs) = occurrences gs ++ occurrences hs := List.flatMap_append + +private theorem take_succ_getD {gs : List GateCode} {i : ℕ} (hi : i < gs.length) : + gs.take (i + 1) = gs.take i ++ [gs.getD i default] := by + rw [List.take_add_one, List.getD_eq_getElem _ _ hi, List.getElem?_eq_getElem hi] + rfl + +theorem base_succ {gs : List GateCode} {i : ℕ} (hi : i < gs.length) : + base gs (i + 1) = base gs i + (qubits (gs.getD i default)).length := by + simp [base, take_succ_getD hi, occurrences] + +theorem base_length (gs : List GateCode) : base gs gs.length = (occurrences gs).length := by + simp [base] + +theorem base_le (gs : List GateCode) (i : ℕ) : base gs i ≤ (occurrences gs).length := by + conv_rhs => rw [← List.take_append_drop i gs, occurrences_append, List.length_append] + exact Nat.le_add_right _ _ + +theorem base_add_le {gs : List GateCode} {i : ℕ} (hi : i < gs.length) : + base gs i + (qubits (gs.getD i default)).length ≤ (occurrences gs).length := + base_succ hi ▸ base_le gs (i + 1) + +/-- The occurrence `k` of gate `i` is its qubit `k`. -/ +theorem getD_occurrences {gs : List GateCode} {i k : ℕ} (hi : i < gs.length) + (hk : k < (qubits (gs.getD i default)).length) : + (occurrences gs).getD (base gs i + k) 0 = (qubits (gs.getD i default)).getD k 0 := by + have h1 : (occurrences [gs.getD i default]) = qubits (gs.getD i default) := by + simp [occurrences] + have hsplit : occurrences gs = occurrences (gs.take i) ++ + (qubits (gs.getD i default) ++ occurrences (gs.drop (i + 1))) := by + conv_lhs => rw [← List.take_append_drop (i + 1) gs] + rw [occurrences_append, take_succ_getD hi, occurrences_append, h1, List.append_assoc] + rw [hsplit, List.getD_append_right _ _ _ _ (Nat.le_add_right (base gs i) k), + show base gs i + k - (occurrences (gs.take i)).length = k by simp [base], + List.getD_append _ _ _ _ hk] + +/-! ### Registers along the occurrences -/ + +/-- The register entering position `p`: each qubit holds the bit `y w` of its last occurrence `w` +before `p`, or its input bit `X j`. -/ +private def reg (qs : List ℕ) (X y : ℕ → Bool) (p j : ℕ) : Bool := + match lastOcc qs j p with + | none => X j + | some w => y w + +private theorem reg_zero (qs : List ℕ) (X y : ℕ → Bool) : reg qs X y 0 = X := rfl + +private theorem reg_succ (qs : List ℕ) (X y : ℕ → Bool) (p : ℕ) : + reg qs X y (p + 1) = Function.update (reg qs X y p) (qs.getD p 0) (y p) := by + funext j + by_cases h : qs.getD p 0 = j + · subst h + simp [reg, lastOcc_succ] + · rw [Function.update_of_ne (Ne.symm h)] + simp only [reg, lastOcc_succ, h, ↓reduceIte] + +/-- Running through occurrences that hold the qubits `l` writes their bits to `l`. -/ +private theorem reg_add (qs : List ℕ) (X y : ℕ → Bool) (l : List ℕ) (b : ℕ) + (hl : ∀ k < l.length, qs.getD (b + k) 0 = l.getD k 0) : + reg qs X y (b + l.length) = + setQubits (reg qs X y b) l ((List.range l.length).map fun k => y (b + k)) := by + induction l generalizing b with + | nil => rfl + | cons j l ih => + have h0 : qs.getD b 0 = j := by simpa using hl 0 (by simp) + have ih' := ih (b + 1) fun k hk => by + simpa [Nat.add_assoc, Nat.add_comm 1 k] using hl (k + 1) (by simp; omega) + rw [List.length_cons, ← Nat.add_assoc, Nat.add_right_comm, ih', reg_succ, h0, + List.range_succ_eq_map, List.map_cons, List.map_map, Nat.add_zero, setQubits_cons] + congr 2 + funext k + simp only [Function.comp_apply, Nat.succ_eq_add_one] + congr 1 + omega + +/-- **The gates read the registers of the run.** The product of the gate entries over the global +occurrence numbering, with the inputs of each gate read from the register entering it, times `Φ` +of the final register, is the weight of the run. -/ +private theorem prod_gateEntry_mul {R : Type*} [CommMonoidWithZero R] (neg : R) + (gs : List GateCode) (X y : ℕ → Bool) (Φ : (ℕ → Bool) → R) : + ((List.range gs.length).map fun i => gateEntry neg (gs.getD i default) + ((qubits (gs.getD i default)).map (reg (occurrences gs) X y (base gs i))) + ((List.range (qubits (gs.getD i default)).length).map fun k => y (base gs i + k))).prod * + Φ (reg (occurrences gs) X y (occurrences gs).length) = + trajWeight neg gs X ((List.range (occurrences gs).length).map y) Φ := by + set F : ℕ → R := fun i => gateEntry neg (gs.getD i default) + ((qubits (gs.getD i default)).map (reg (occurrences gs) X y (base gs i))) + ((List.range (qubits (gs.getD i default)).length).map fun k => y (base gs i + k)) + set ys := (List.range (occurrences gs).length).map y + suffices h : ∀ n i, i + n = gs.length → ((List.range' i n).map F).prod * + Φ (reg (occurrences gs) X y (occurrences gs).length) = + trajWeight neg (gs.drop i) (reg (occurrences gs) X y (base gs i)) (ys.drop (base gs i)) Φ by + have := h gs.length 0 (by simp) + simpa [List.range_eq_range', base, reg_zero, occurrences_nil] using this + intro n + induction n with + | zero => + intro i hi + subst hi + simp [trajWeight, base_length] + | succ n ih => + intro i hi + have hil : i < gs.length := by omega + rw [List.range'_succ, List.map_cons, List.prod_cons, mul_assoc, ih (i + 1) (by omega), + List.drop_eq_getElem_cons hil, ← List.getD_eq_getElem _ default hil, trajWeight] + have hk := base_add_le hil + have htake : (ys.drop (base gs i)).take (qubits (gs.getD i default)).length = + (List.range (qubits (gs.getD i default)).length).map fun k => y (base gs i + k) := by + refine List.ext_getElem (by simp only [ys, List.length_take, List.length_drop, + List.length_map, List.length_range]; omega) fun k h₁ h₂ => ?_ + simp [ys] + rw [htake, List.drop_drop, ← base_succ hil, ← reg_add _ _ _ _ _ fun k hk' => + getD_occurrences hil hk', ← base_succ hil] + +/-! ### Entries of the vertices -/ + +section Entries + +variable {R : Type*} [Zero R] [One R] + +omit [Zero R] [One R] in +/-- For `w ≤ 6`, the table lists `f` at the first `2 ^ w` positions. -/ +theorem table_eq {w : ℕ} (f : ℕ → R) (hw : w ≤ 6) : table w f = (List.range (2 ^ w)).map f := by + have hle : 2 ^ w ≤ 2 ^ 6 := Nat.pow_le_pow_right (by norm_num) hw + unfold table + congr 1 + obtain ⟨c, hc⟩ := Nat.exists_eq_add_of_le hle + rw [hc, List.range_add, List.filter_append, List.filter_eq_self.mpr (by simp), + List.filter_eq_nil_iff.mpr (by simp), List.append_nil] + +omit [One R] in +/-- Looking up a table at a word of length `w ≤ 6` evaluates its function at the value of the +word. -/ +theorem entry_table {w : ℕ} (f : ℕ → R) (hw : w ≤ 6) {bs : List Bool} (h : bs.length = w) : + entry (table w f) bs = f (BitString.lsbValue bs) := by + subst h + rw [entry, table_eq f hw, + List.getD_eq_getElem _ _ (by simpa using BitString.lsbValue_lt bs)] + simp + +/-- A gate vertex has at most twice as many ports as its gate has qubits. -/ +theorem length_gatePorts_le (gs : List GateCode) (c i : ℕ) : + (gatePorts gs c i).length ≤ 2 * (qubits (gs.getD i default)).length := by + have := List.length_filterMap_le (inPort gs c (base gs i)) (qubits (gs.getD i default)) + simp only [gatePorts, List.length_append, List.length_map, List.length_range] + omega + +/-- **The entry of a gate vertex** when edge `e` carries `Z (e / 2)`: the tensor of the gate with +the bits of the segments `c N + u` as the bits of the occurrences `u`, the inputs read from the +register entering the gate. -/ +private theorem entry_gateVertex (neg : R) (X : List Bool) (gs : List GateCode) (c i : ℕ) + (hi : (qubits (gs.getD i default)).length ≤ 3) (Z : ℕ → Bool) : + entry (gateVertex neg X gs c i).2 ((gateVertex neg X gs c i).1.map fun e => Z (e / 2)) = + gateEntry neg (gs.getD i default) + ((qubits (gs.getD i default)).map (reg (occurrences gs) (fun j => X.getD j false) + (fun u => Z (c * (occurrences gs).length + u)) (base gs i))) + ((List.range (qubits (gs.getD i default)).length).map + fun k => Z (c * (occurrences gs).length + (base gs i + k))) := by + have hps : (gatePorts gs c i).length ≤ 6 := (length_gatePorts_le gs c i).trans (by omega) + simp only [gateVertex] + rw [entry_table _ hps (List.length_map _)] + congr 1 + · refine List.map_congr_left fun j hj => ?_ + simp only [inBit, reg, inPort] + cases hl : lastOcc (occurrences gs) j (base gs i) with + | none => rfl + | some w => + simp only [Option.map_some, BitString.testBit_lsbValue] + have hmem : 2 * (c * (occurrences gs).length + w) + 1 ∈ gatePorts gs c i := + List.mem_append_right _ (List.mem_filterMap.mpr ⟨j, hj, by simp [inPort, hl]⟩) + have hlt := List.idxOf_lt_length_of_mem hmem + rw [List.getD_eq_getElem _ _ (by simpa using hlt), List.getElem_map, + List.getElem_idxOf hlt] + congr 1 + omega + · refine List.map_congr_left fun k hk => ?_ + have hk' : k < (qubits (gs.getD i default)).length := List.mem_range.mp hk + rw [BitString.testBit_lsbValue, List.getD_eq_getElem _ _ (by + simp only [List.length_map, gatePorts, List.length_append, List.length_range]; omega), + List.getElem_map] + simp only [gatePorts] + rw [List.getElem_append_left (by simpa using hk')] + simp only [List.getElem_map, List.getElem_range] + congr 1 + omega + +/-- The glue condition on the final bits `a` and `b` of the qubit `j` in the two copies: they are +equal, and one for qubit `0`. -/ +private def GlueOK (a b : Bool) (j : ℕ) : Prop := a = b ∧ (j = 0 → a = true) + +private instance (a b : Bool) (j : ℕ) : Decidable (GlueOK a b j) := by + unfold GlueOK + infer_instance + +/-- **The entry of a glue vertex** when edge `e` carries `Z (e / 2)`. -/ +private theorem entry_glueVertex (gs : List GateCode) (w : ℕ) (Z : ℕ → Bool) : + entry (glueVertex gs w : List ℕ × List R).2 + ((glueVertex gs w : List ℕ × List R).1.map fun e => Z (e / 2)) = + if (lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w → + GlueOK (Z w) (Z ((occurrences gs).length + w)) ((occurrences gs).getD w 0)) + then (1 : R) else 0 := by + unfold glueVertex + by_cases hl : lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = + some w + · simp only [hl, ↓reduceIte, List.map_cons, List.map_nil, true_implies, + show (2 * w + 1) / 2 = w by omega, + show (2 * ((occurrences gs).length + w) + 1) / 2 = (occurrences gs).length + w by omega] + by_cases h0 : (occurrences gs).getD w 0 = 0 + · simp only [h0, ↓reduceIte, GlueOK, true_implies] + cases Z w <;> cases Z ((occurrences gs).length + w) <;> rfl + · simp only [h0, ↓reduceIte, GlueOK, entry_eqTable, false_implies, and_true] + · simp only [hl, ↓reduceIte, false_implies] + rfl + +private theorem entry_scalarVertex (X : List Bool) (gs : List GateCode) : + entry (scalarVertex X gs : List ℕ × List R).2 ((scalarVertex X gs : List ℕ × List R).1.map + fun _ => false) = + if 0 ∈ occurrences gs ∨ X.getD 0 false = true then 1 else 0 := rfl + +end Entries + +/-! ### The final registers -/ + +/-- A run of the path simulation never changes a qubit that no gate touches. -/ +private theorem getD_foldl_pathStep_of_not_mem (gs : List GateCode) {j : ℕ} + (hj : j ∉ occurrences gs) (s π : List Bool) (σ : Bool) : + (gs.foldl pathStep (s, π, σ)).1.getD j false = s.getD j false := by + induction gs generalizing s π σ with + | nil => rfl + | cons g gs ih => + rw [occurrences_cons, List.mem_append, not_or] at hj + have hjt : g.2.1 ≠ j := fun h => hj.1 (h ▸ List.mem_cons_self) + obtain ⟨_ | _, t, cs⟩ := g <;> + simp only [List.foldl_cons, pathStep] <;> rw [ih hj.2] <;> + simp [List.getD_eq_getElem?_getD, hjt] + +/-- The condition of the glue and scalar vertices on the final registers `r` and `r'` of the two +copies. -/ +private def FinalOK (X : List Bool) (gs : List GateCode) (r r' : ℕ → Bool) : Prop := + (∀ w < (occurrences gs).length, + lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w → + GlueOK (r ((occurrences gs).getD w 0)) (r' ((occurrences gs).getD w 0)) + ((occurrences gs).getD w 0)) ∧ + (0 ∈ occurrences gs ∨ X.getD 0 false = true) + +private instance (X : List Bool) (gs : List GateCode) (r r' : ℕ → Bool) : + Decidable (FinalOK X gs r r') := by + unfold FinalOK + infer_instance + +private theorem mem_iff_getD (qs : List ℕ) (j : ℕ) : + j ∈ qs ↔ ∃ w < qs.length, qs.getD w 0 = j := by + rw [List.mem_iff_getElem] + constructor + · rintro ⟨w, hw, rfl⟩ + exact ⟨w, hw, List.getD_eq_getElem _ _ hw⟩ + · rintro ⟨w, hw, rfl⟩ + exact ⟨w, hw, (List.getD_eq_getElem _ _ hw).symm⟩ + +/-- **The glue condition on two final registers** that agree with the input register off the +touched qubits: the registers are equal and qubit `0` is one. -/ +private theorem finalOK_iff (X : List Bool) (gs : List GateCode) {s s' : List Bool} + (hs : s.length = X.length) (hs' : s'.length = X.length) + (hu : ∀ j ∉ occurrences gs, s.getD j false = X.getD j false) + (hu' : ∀ j ∉ occurrences gs, s'.getD j false = X.getD j false) : + FinalOK X gs (fun j => s.getD j false) (fun j => s'.getD j false) ↔ + s = s' ∧ s.headD false = true := by + set qs := occurrences gs + -- a touched qubit has a last occurrence + have hlast (j : ℕ) (hj : j ∈ qs) : ∃ w < qs.length, qs.getD w 0 = j ∧ + lastOcc qs (qs.getD w 0) qs.length = some w := by + cases hl : lastOcc qs j qs.length with + | none => + obtain ⟨w, hw, hwj⟩ := (mem_iff_getD qs j).mp hj + exact absurd hwj (lastOcc_eq_none.mp hl w hw) + | some w => + obtain ⟨hw, hwj, -⟩ := lastOcc_eq_some hl + exact ⟨w, hw, hwj, hwj ▸ hl⟩ + have hhead : s.headD false = s.getD 0 false := by cases s <;> rfl + constructor + · rintro ⟨hglue, h0⟩ + have heq (j : ℕ) : s.getD j false = s'.getD j false ∧ (j = 0 → j ∈ qs → + s.getD 0 false = true) := by + by_cases hj : j ∈ qs + · obtain ⟨w, hw, rfl, hl⟩ := hlast j hj + obtain ⟨h1, h2⟩ := hglue w hw hl + exact ⟨h1, fun h _ => h ▸ h2 h⟩ + · exact ⟨(hu j hj).trans (hu' j hj).symm, fun _ h => absurd h hj⟩ + refine ⟨List.ext_getElem (hs.trans hs'.symm) fun j h₁ h₂ => ?_, ?_⟩ + · have := (heq j).1 + rwa [List.getD_eq_getElem _ _ h₁, List.getD_eq_getElem _ _ h₂] at this + · rw [hhead] + by_cases h : 0 ∈ qs + · exact (heq 0).2 rfl h + · rw [hu 0 h] + exact h0.resolve_left h + · rintro ⟨rfl, hs0⟩ + refine ⟨fun w _ _ => ⟨rfl, fun h => ?_⟩, ?_⟩ + · change s.getD (qs.getD w 0) false = true + rw [h, ← hhead] + exact hs0 + by_cases h : 0 ∈ qs + · exact Or.inl h + · exact Or.inr ((hu 0 h).symm.trans (hhead ▸ hs0)) + +/-! ### The contraction -/ + +/-- The bits of two strings of length `N`, one after the other. -/ +private def pairFun {N : ℕ} (a b : Fin N → Bool) (i : ℕ) : Bool := + if h : i < N then a ⟨i, h⟩ else if h' : i < 2 * N then b ⟨i - N, by omega⟩ else false + +/-- A sum over the bit strings of length `2 N` splits into its two halves. -/ +private theorem sum_pair {M : Type*} [AddCommMonoid M] (N : ℕ) (G : (ℕ → Bool) → M) : + ∑ y : Fin (2 * N) → Bool, G (fun i => if h : i < 2 * N then y ⟨i, h⟩ else false) = + ∑ a : Fin N → Bool, ∑ b : Fin N → Bool, G (pairFun a b) := by + let e : (Fin N → Bool) × (Fin N → Bool) ≃ (Fin (2 * N) → Bool) := + (Fin.appendEquiv N N).trans (Equiv.arrowCongr (finCongr (two_mul N).symm) (Equiv.refl _)) + rw [← e.sum_comp, Fintype.sum_prod_type] + refine Finset.sum_congr rfl fun a _ => Finset.sum_congr rfl fun b _ => congrArg G ?_ + funext i + simp only [e, Equiv.trans_apply, Fin.appendEquiv_apply, Equiv.arrowCongr_apply, + Equiv.refl_apply, Function.comp_apply, finCongr_symm, finCongr_apply, pairFun] + by_cases h1 : i < N + · simp only [show i < 2 * N by omega, dite_true, h1] + exact Fin.append_left a b ⟨i, h1⟩ + · by_cases h2 : i < 2 * N + · simp only [h2, dite_true, h1, dite_false] + have := Fin.append_right a b ⟨i - N, by omega⟩ + convert this using 2 + ext + simp + omega + · simp only [h2, dite_false, h1] + +private theorem map_pairFun_left {N : ℕ} (a b : Fin N → Bool) : + (List.range N).map (fun u => pairFun a b (0 * N + u)) = List.ofFn a := by + refine List.ext_getElem (by simp) fun u h₁ h₂ => ?_ + simp only [List.getElem_map, List.getElem_range, List.getElem_ofFn, Nat.zero_mul, + Nat.zero_add, pairFun] + simp at h₁ + simp [h₁] + +private theorem map_pairFun_right {N : ℕ} (a b : Fin N → Bool) : + (List.range N).map (fun u => pairFun a b (1 * N + u)) = List.ofFn b := by + refine List.ext_getElem (by simp) fun u h₁ h₂ => ?_ + simp only [List.getElem_map, List.getElem_range, List.getElem_ofFn, Nat.one_mul, pairFun] + simp at h₁ + simp [show N + u < 2 * N by omega] + +/-- **The contraction of the network is the acceptance gap.** With `-1` as the Hadamard sign, over +any commutative ring, the contraction of the network of well-formed gate codes on the input +register `X` is `acceptGap gs X`. -/ +theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List GateCode) + (hgs : ∀ g ∈ gs, g.WellFormed X.length) : + (network (-1 : R) X gs).2.contraction = (acceptGap gs X : R) := by + set qs := occurrences gs with hqs + set N := qs.length with hN + set Xf : ℕ → Bool := fun j => X.getD j false + -- every gate has at most three qubits + have hq3 (i : ℕ) (hi : i < gs.length) : (qubits (gs.getD i default)).length ≤ 3 := by + have hwf := hgs _ (List.getD_eq_getElem _ default hi ▸ List.getElem_mem hi) + generalize gs.getD i default = g at hwf ⊢ + obtain ⟨_ | _, t, cs⟩ := g + · simp only [qubits, List.length_cons] + have := hwf.1 + omega + · simp only [qubits, hwf.2, List.length_cons, List.length_nil] + omega + -- the products of the gate vertices of a copy + let P : (ℕ → Bool) → R := fun y => ((List.range gs.length).map fun i => + gateEntry (-1 : R) (gs.getD i default) + ((qubits (gs.getD i default)).map (reg qs Xf y (base gs i))) + ((List.range (qubits (gs.getD i default)).length).map fun k => y (base gs i + k))).prod + have hP (c : ℕ) (Z : ℕ → Bool) : ((List.range gs.length).map (gateVertex (-1 : R) X gs c)).map + (fun v => entry v.2 (v.1.map fun e => Z (e / 2))) = + (List.range gs.length).map fun i => gateEntry (-1 : R) (gs.getD i default) + ((qubits (gs.getD i default)).map (reg qs Xf (fun u => Z (c * N + u)) (base gs i))) + ((List.range (qubits (gs.getD i default)).length).map + fun k => (fun u => Z (c * N + u)) (base gs i + k)) := by + rw [List.map_map] + exact List.map_congr_left fun i hi => + entry_gateVertex _ X gs c i (hq3 i (List.mem_range.mp hi)) Z + -- the glue and scalar vertices + have hG (Z : ℕ → Bool) : ((List.range N).map (glueVertex gs)).map + (fun v => entry (v : List ℕ × List R).2 (v.1.map fun e => Z (e / 2))) = + (List.range N).map fun w => if (lastOcc qs (qs.getD w 0) N = some w → + GlueOK (reg qs Xf (fun u => Z (0 * N + u)) N (qs.getD w 0)) + (reg qs Xf (fun u => Z (1 * N + u)) N (qs.getD w 0)) (qs.getD w 0)) then 1 else 0 := by + rw [List.map_map] + refine List.map_congr_left fun w _ => ?_ + rw [Function.comp_apply, entry_glueVertex, ← hqs, ← hN] + by_cases hl : lastOcc qs (qs.getD w 0) N = some w + · simp only [reg, hl, Nat.zero_mul, Nat.zero_add, Nat.one_mul] + · simp only [hl, false_implies] + have hrows (Z : ℕ → Bool) : + (((List.range gs.length).map (gateVertex (-1 : R) X gs 0) ++ + (List.range gs.length).map (gateVertex (-1 : R) X gs 1) ++ + (List.range N).map (glueVertex gs) ++ [scalarVertex X gs]).map + fun v : List ℕ × List R => entry v.2 (v.1.map fun e => Z (e / 2))).prod = + P (fun u => Z (0 * N + u)) * (P (fun u => Z (1 * N + u)) * + if FinalOK X gs (reg qs Xf (fun u => Z (0 * N + u)) N) + (reg qs Xf (fun u => Z (1 * N + u)) N) then 1 else 0) := by + simp only [List.map_append, List.prod_append, hP, hG, prod_map_boole, List.map_cons, + List.map_nil, List.prod_cons, List.prod_nil, mul_one] + rw [show entry (scalarVertex X gs : List ℕ × List R).2 ((scalarVertex X gs).1.map + fun e => Z (e / 2)) = if 0 ∈ qs ∨ X.getD 0 false = true then 1 else 0 from rfl] + simp only [P, FinalOK, ← hqs, ← hN, ite_and, List.mem_range] + split_ifs <;> ring + -- the pair reduction and the two halves + change contraction (⟨2 * (2 * N), _ ++ (List.range (2 * N)).map fun w => + ([2 * w, 2 * w + 1], eqTable)⟩ : ExplicitTensorNetwork R) = _ + rw [contraction_append_pairs] + refine (sum_pair N fun Z => (((List.range gs.length).map (gateVertex (-1 : R) X gs 0) ++ + (List.range gs.length).map (gateVertex (-1 : R) X gs 1) ++ + (List.range N).map (glueVertex gs) ++ [scalarVertex X gs]).map + fun v : List ℕ × List R => entry v.2 (v.1.map fun e => Z (e / 2))).prod).trans ?_ + simp only [hrows] + -- each copy is a run along the occurrences + have hB (y : ℕ → Bool) (Φ : (ℕ → Bool) → R) : + P y * Φ (reg qs Xf y N) = trajWeight (-1) gs Xf ((List.range N).map y) Φ := + prod_gateEntry_mul (-1 : R) gs Xf y Φ + have hstep (a b : Fin N → Bool) : + P (fun u => pairFun a b (0 * N + u)) * (P (fun u => pairFun a b (1 * N + u)) * + if FinalOK X gs (reg qs Xf (fun u => pairFun a b (0 * N + u)) N) + (reg qs Xf (fun u => pairFun a b (1 * N + u)) N) then 1 else 0) = + trajWeight (-1) gs Xf (List.ofFn a) fun r0 => + trajWeight (-1) gs Xf (List.ofFn b) fun r1 => if FinalOK X gs r0 r1 then 1 else 0 := by + have h1 := hB (fun u => pairFun a b (1 * N + u)) fun r1 => + if FinalOK X gs (reg qs Xf (fun u => pairFun a b (0 * N + u)) N) r1 then (1 : R) else 0 + have h0 := hB (fun u => pairFun a b (0 * N + u)) fun r0 => + trajWeight (-1) gs Xf (List.ofFn b) fun r1 => if FinalOK X gs r0 r1 then (1 : R) else 0 + simp only [map_pairFun_left, map_pairFun_right] at h0 h1 + rw [h1] + exact h0 + -- the path sums + have hC (Φ : (ℕ → Bool) → R) : ∑ b : Fin N → Bool, trajWeight (-1) gs Xf (List.ofFn b) Φ = + ∑ π : Fin (hadamardCount gs) → Bool, sgn (gs.foldl pathStep (X, List.ofFn π, false)).2.2 * + Φ (fun j => (gs.foldl pathStep (X, List.ofFn π, false)).1.getD j false) := by + have := sgn_mul_sum_trajWeight gs X hgs false Φ + rwa [sgn, ite_eq_right Bool.false_ne_true, one_mul] at this + have hinner (a : Fin N → Bool) : ∑ b : Fin N → Bool, (trajWeight (-1) gs Xf (List.ofFn a) + fun r0 => trajWeight (-1) gs Xf (List.ofFn b) fun r1 => + if FinalOK X gs r0 r1 then (1 : R) else 0) = + trajWeight (-1) gs Xf (List.ofFn a) fun r0 => + ∑ π' : Fin (hadamardCount gs) → Bool, sgn (gs.foldl pathStep (X, List.ofFn π', false)).2.2 * + if FinalOK X gs r0 (fun j => (gs.foldl pathStep (X, List.ofFn π', false)).1.getD j false) + then 1 else 0 := by + rw [← trajWeight_sum] + congr 1 + funext r0 + exact hC _ + simp only [hstep, hinner] + rw [hC, acceptGap, Int.cast_sum] + refine Finset.sum_congr rfl fun π _ => ?_ + rw [Int.cast_sum, Finset.mul_sum] + refine Finset.sum_congr rfl fun π' _ => ?_ + set f := gs.foldl pathStep (X, List.ofFn π, false) + set f' := gs.foldl pathStep (X, List.ofFn π', false) + have hok := finalOK_iff X gs (s := f.1) (s' := f'.1) (length_foldl_pathStep _ _ _ _) + (length_foldl_pathStep _ _ _ _) (fun j hj => getD_foldl_pathStep_of_not_mem gs hj _ _ _) + (fun j hj => getD_foldl_pathStep_of_not_mem gs hj _ _ _) + have hterm : acceptTerm gs X (List.ofFn π) (List.ofFn π') = + if f.1 = f'.1 ∧ f.1.headD false = true then (if f.2.2 ^^ f'.2.2 then -1 else 1) else 0 := + rfl + rw [hterm] + by_cases hc : f.1 = f'.1 ∧ f.1.headD false = true + · rw [ite_eq_left (hok.mpr hc), ite_eq_left hc] + cases f.2.2 <;> cases f'.2.2 <;> simp [sgn] + · rw [ite_eq_right (mt hok.mp hc), ite_eq_right hc] + simp + +/-! ### Residues of the contraction over `ℕ` -/ + +/-- **Mapping the tables maps the Hadamard sign.** -/ +theorem network_mapTables {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) (neg : R) + (X : List Bool) (gs : List GateCode) : + (network neg X gs).2.mapTables ψ = (network (ψ neg) X gs).2 := by + have hgate (c i : ℕ) : (fun v : List ℕ × List R => (v.1, v.2.map ψ)) (gateVertex neg X gs c i) = + gateVertex (ψ neg) X gs c i := by + simp only [gateVertex, table, List.map_map] + congr 2 + funext idx + simp only [Function.comp_apply, gateEntry] + split_ifs <;> simp + have hglue (w : ℕ) : (fun v : List ℕ × List R => (v.1, v.2.map ψ)) (glueVertex gs w) = + glueVertex gs w := by + simp only [glueVertex] + split_ifs <;> simp [acceptTable, eqTable] + simp only [network, ExplicitTensorNetwork.mapTables, List.map_append, List.map_map, + Function.comp_def, hgate, hglue, List.map_cons, List.map_nil, scalarVertex, eqTable, map_one, + map_zero] + congr 3 + split_ifs <;> simp + +/-- **The residue of the contraction over `ℕ` is the acceptance gap.** With `2 ^ P - 1`, which is +`-1` modulo `2 ^ P`, as the Hadamard sign, and `P` larger than the number of Hadamard gates, the +contraction of the network of well-formed gate codes on a nonempty input register `X`, reduced +modulo `2 ^ P`, is the acceptance gap. -/ +theorem contraction_network_mod (X : List Bool) (hX : 0 < X.length) (gs : List GateCode) + (hgs : ∀ g ∈ gs, g.WellFormed X.length) {P : ℕ} (hP : hadamardCount gs < P) : + (network (2 ^ P - 1 : ℕ) X gs).2.contraction % 2 ^ P = (acceptGap gs X).toNat := by + obtain ⟨h0, h1⟩ := acceptGap_nonneg_and_le hX gs hgs + have hlt : (acceptGap gs X).toNat < 2 ^ P := by + have : (acceptGap gs X).toNat ≤ 2 ^ hadamardCount gs := Int.toNat_le.mpr (by exact_mod_cast h1) + exact this.trans_lt (Nat.pow_lt_pow_right (by norm_num) hP) + have hcast : ((network (2 ^ P - 1 : ℕ) X gs).2.contraction : ZMod (2 ^ P)) = + ((acceptGap gs X).toNat : ZMod (2 ^ P)) := by + have hneg : ((2 ^ P - 1 : ℕ) : ZMod (2 ^ P)) = -1 := by + rw [Nat.cast_sub Nat.one_le_two_pow, ZMod.natCast_self, Nat.cast_one, zero_sub] + rw [← Nat.coe_castRingHom, ExplicitTensorNetwork.map_contraction, network_mapTables, + Nat.coe_castRingHom, hneg, contraction_network X gs hgs, ← Int.toNat_of_nonneg h0] + simp + rw [ZMod.natCast_eq_natCast_iff'] at hcast + rw [hcast, Nat.mod_eq_of_lt hlt] + +/-! ### Sizes -/ + +/-- A well-formed gate code has at most three qubits. -/ +theorem length_qubits_le_three {m : ℕ} {g : GateCode} (h : g.WellFormed m) : + (qubits g).length ≤ 3 := by + obtain ⟨_ | _, t, cs⟩ := g + · simp only [qubits, List.length_cons] + have := h.1 + omega + · simp only [qubits, h.2, List.length_cons, List.length_nil] + omega + +/-- Every gate has at least one and at most three occurrences. -/ +theorem length_le_length_occurrences (gs : List GateCode) : + gs.length ≤ (occurrences gs).length := by + induction gs with + | nil => simp [occurrences] + | cons g gs ih => + rw [occurrences_cons, List.length_append, List.length_cons] + simp only [qubits, List.length_cons] + omega + +theorem length_occurrences_le {m : ℕ} {gs : List GateCode} (hgs : ∀ g ∈ gs, g.WellFormed m) : + (occurrences gs).length ≤ 3 * gs.length := by + induction gs with + | nil => simp [occurrences] + | cons g gs ih => + rw [occurrences_cons, List.length_append, List.length_cons] + have h1 := length_qubits_le_three (hgs g List.mem_cons_self) + have h2 := ih fun g' hg' => hgs g' (List.mem_cons_of_mem _ hg') + omega + +variable {R : Type*} [Zero R] [One R] + +@[simp] theorem fst_network (neg : R) (X : List Bool) (gs : List GateCode) : + (network neg X gs).1 = 2 * gs.length + (occurrences gs).length + 1 := rfl + +/-- **Four edges per occurrence.** -/ +@[simp] theorem numEdges_network (neg : R) (X : List Bool) (gs : List GateCode) : + (network neg X gs).2.numEdges = 2 * (2 * (occurrences gs).length) := rfl + +theorem vertices_network (neg : R) (X : List Bool) (gs : List GateCode) : + (network neg X gs).2.vertices = + (List.range gs.length).map (gateVertex neg X gs 0) ++ + (List.range gs.length).map (gateVertex neg X gs 1) ++ + (List.range (occurrences gs).length).map (glueVertex gs) ++ [scalarVertex X gs] ++ + (List.range (2 * (occurrences gs).length)).map fun w => ([2 * w, 2 * w + 1], eqTable) := rfl + +@[simp] theorem length_vertices_network (neg : R) (X : List Bool) (gs : List GateCode) : + (network neg X gs).2.vertices.length = + 2 * gs.length + 3 * (occurrences gs).length + 1 := by + simp only [vertices_network, List.length_append, List.length_map, List.length_range, + List.length_singleton] + omega + +/-- **Degree at most six**: a gate vertex has at most two ports per qubit, and the other vertices +at most two ports. -/ +theorem maxDegree_network_le (neg : R) (X : List Bool) {gs : List GateCode} + (hgs : ∀ g ∈ gs, (qubits g).length ≤ 3) : (network neg X gs).2.maxDegree ≤ 6 := by + refine (ExplicitTensorNetwork.maxDegree_le_iff _).mpr fun v hv => ?_ + simp only [vertices_network, List.mem_append, List.mem_map, List.mem_range, + List.mem_singleton] at hv + rcases hv with (((⟨i, hi, rfl⟩ | ⟨i, hi, rfl⟩) | ⟨w, -, rfl⟩) | rfl) | ⟨w, -, rfl⟩ + · have := hgs _ (List.getD_eq_getElem _ default hi ▸ List.getElem_mem hi) + exact (length_gatePorts_le gs 0 i).trans (by omega) + · have := hgs _ (List.getD_eq_getElem _ default hi ▸ List.getElem_mem hi) + exact (length_gatePorts_le gs 1 i).trans (by omega) + · unfold glueVertex + split_ifs <;> simp + · simp [scalarVertex] + · simp + +/-- **Full tables**: every table lists `2 ^ (number of ports)` entries. -/ +theorem length_table_network (neg : R) (X : List Bool) {gs : List GateCode} + (hgs : ∀ g ∈ gs, (qubits g).length ≤ 3) : + ∀ v ∈ (network neg X gs).2.vertices, v.2.length = 2 ^ v.1.length := by + intro v hv + simp only [vertices_network, List.mem_append, List.mem_map, List.mem_range, + List.mem_singleton] at hv + have hgate (c i : ℕ) (hi : i < gs.length) : + (gateVertex neg X gs c i).2.length = 2 ^ (gateVertex neg X gs c i).1.length := by + have := hgs _ (List.getD_eq_getElem _ default hi ▸ List.getElem_mem hi) + simp only [gateVertex] + rw [table_eq _ ((length_gatePorts_le gs c i).trans (by omega)), List.length_map, + List.length_range] + rcases hv with (((⟨i, hi, rfl⟩ | ⟨i, hi, rfl⟩) | ⟨w, -, rfl⟩) | rfl) | ⟨w, -, rfl⟩ + · exact hgate 0 i hi + · exact hgate 1 i hi + · unfold glueVertex + split_ifs <;> rfl + · rfl + · rfl + +end Cslib.Quantum.PathNetwork diff --git a/Complexity/Computability/Circuit/Quantum/PathSum.lean b/Complexity/Computability/Circuit/Quantum/PathSum.lean index f06efa471..f523b4564 100644 --- a/Complexity/Computability/Circuit/Quantum/PathSum.lean +++ b/Complexity/Computability/Circuit/Quantum/PathSum.lean @@ -43,6 +43,7 @@ simulations, which places `BQP` in the counting classes * `Cslib.Quantum.ofFn_inputState`: the input basis state is the input word padded with zeros. * `Cslib.Quantum.run_ofCodes_apply`: amplitudes are path sums. * `Cslib.Quantum.acceptProb_eq_acceptGap`: the acceptance probability is `acceptGap / 2^h`. +* `Cslib.Quantum.acceptGap_nonneg_and_le`: so the acceptance gap lies between `0` and `2^h`. ## References @@ -223,7 +224,7 @@ theorem run_ofCodes_apply (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed rfl /-- The simulation keeps the register length. -/ -private theorem length_foldl_pathStep (gs : List GateCode) (s π : List Bool) (σ : Bool) : +theorem length_foldl_pathStep (gs : List GateCode) (s π : List Bool) (σ : Bool) : (gs.foldl pathStep (s, π, σ)).1.length = s.length := by induction gs generalizing s π σ with | nil => rfl @@ -306,4 +307,20 @@ theorem acceptProb_eq_acceptGap (hm : 0 < m) (gs : List GateCode) congr 1 exact_mod_cast sum_accepting_pathGap_sq hm gs _ (by simp) +/-- **The acceptance gap is a count between `0` and `2 ^ h`** for well-formed codes on a nonempty +register `X`, since it is `2 ^ h` times a probability. -/ +theorem acceptGap_nonneg_and_le {X : List Bool} (hX : 0 < X.length) (gs : List GateCode) + (hgs : ∀ g ∈ gs, g.WellFormed X.length) : + 0 ≤ acceptGap gs X ∧ acceptGap gs X ≤ 2 ^ hadamardCount gs := by + have h := acceptProb_eq_acceptGap hX gs hgs X + rw [ofFn_inputState le_rfl, Nat.sub_self, List.replicate_zero, List.append_nil] at h + have h0 := acceptProb_nonneg X.length gs X + have h1 := acceptProb_le_one X.length gs X + have hpos : (0 : ℝ) < 2 ^ hadamardCount gs := by positivity + rw [h] at h0 h1 + constructor + · exact_mod_cast (div_nonneg_iff.mp h0).elim (fun h => h.1) fun h => absurd h.2 (not_le.mpr hpos) + · rw [div_le_one hpos] at h1 + exact_mod_cast h1 + end Cslib.Quantum diff --git a/ComplexityTests/QuantumPathNetwork.lean b/ComplexityTests/QuantumPathNetwork.lean new file mode 100644 index 000000000..ebddc4fc6 --- /dev/null +++ b/ComplexityTests/QuantumPathNetwork.lean @@ -0,0 +1,66 @@ +/- +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.Quantum.PathNetwork + +/-! The tensor network of a circuit and its mirror copy: concrete contractions that equal the +acceptance gaps of small circuits, computed directly and through `contraction_network`. -/ + +namespace ComplexityTests.QuantumPathNetwork + +open Cslib Cslib.Quantum Cslib.Quantum.PathNetwork Cslib.Circuits.Frontier + +/-! ### Direct contractions -/ + +-- One Hadamard gate on `|0⟩`: one of the four pairs of paths accepts, so the gap is `1`. The +-- network has one occurrence, so four edges: sixteen assignments. +example : (network (-1 : ℤ) [false] [(true, 0, [])]).2.contraction = 1 := by decide +example : acceptGap [(true, 0, [])] [false] = 1 := by decide + +-- A NOT gate on `|0⟩` accepts with certainty and has no Hadamard gate. +example : (network (-1 : ℤ) [false] [(false, 0, [])]).2.contraction = 1 := by decide + +-- A NOT gate on `|1⟩` rejects with certainty. +example : (network (-1 : ℤ) [true] [(false, 0, [])]).2.contraction = 0 := by decide + +-- Without gates the network is the scalar vertex alone: it reads the input bit of qubit `0`. +example : (network (-1 : ℤ) [true] []).2.contraction = 1 := by decide +example : (network (-1 : ℤ) [false] []).2.contraction = 0 := by decide + +-- An untouched qubit `0` set to one: the gate on qubit `1` does not matter, the gap is `2 ^ 1`. +example : (network (-1 : ℤ) [true, false] [(true, 1, [])]).2.contraction = 2 := by decide + +/-! ### Through the identity -/ + +-- Two Hadamard gates interfere; the signed entries `-1` cancel the four accepting pairs from +-- `|0⟩` and add them up from `|1⟩`. +example : (network (-1 : ℤ) [false] [(true, 0, []), (true, 0, [])]).2.contraction = 0 := by + rw [contraction_network _ _ (by decide)] + decide +example : (network (-1 : ℤ) [true] [(true, 0, []), (true, 0, [])]).2.contraction = 4 := by + rw [contraction_network _ _ (by decide)] + decide + +-- A Hadamard gate copied by a CNOT into qubit `0`: probability `1 / 2`, gap `1`. +example : (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).2.contraction = 1 := by + rw [contraction_network _ _ (by decide)] + decide + +/-! ### Residues over `ℕ` -/ + +-- Over `ℕ` the Hadamard sign is `2 ^ P - 1`; modulo `2 ^ P` the contraction is the gap. +example : (network (2 ^ 3 - 1 : ℕ) [true] [(true, 0, []), (true, 0, [])]).2.contraction % 2 ^ 3 = + 4 := by + rw [contraction_network_mod _ (by decide) _ (by decide) (by decide)] + decide + +/-! ### Sizes -/ + +-- Four edges per occurrence: the Toffoli gate has three occurrences. +example : (network (-1 : ℤ) [false, false, false] [(false, 2, [0, 1])]).2.numEdges = 12 := by + decide + +end ComplexityTests.QuantumPathNetwork From 52b8bdb0cd96a303c72b57e32764e9efe02f3900 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:19:28 -0400 Subject: [PATCH 02/13] wip(Quantum): gates of occurrences and readers of link edges Toward `isRowsFirstBipartite_network`: `lastOcc_eq_some_of`, `base_mono`, `exists_base_le_lt` and `eq_of_base_le_lt` (every occurrence lies in exactly one gate), `mem_qubits_iff`, and `sum_readers`: the link edge of an occurrence is read by exactly one gate, or by its glue vertex when it is the last occurrence of its qubit. The per-row port counts that turn this into `IsRowsFirstBipartite` are not written yet. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Quantum/PathNetwork.lean | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index 6d51c5c73..d056e2cbc 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -1126,4 +1126,167 @@ theorem length_table_network (neg : R) (X : List Bool) {gs : List GateCode} · rfl · rfl +/-! ### The bipartition -/ + +/-- The converse of `lastOcc_eq_some`. -/ +theorem lastOcc_eq_some_of {qs : List ℕ} {j p w : ℕ} (hw : w < p) (hwj : qs.getD w 0 = j) + (hnext : ∀ w', w < w' → w' < p → qs.getD w' 0 ≠ j) : lastOcc qs j p = some w := by + induction p with + | zero => omega + | succ p ih => + rw [lastOcc_succ] + rcases Nat.lt_succ_iff_lt_or_eq.mp hw with hw' | rfl + · rw [ite_eq_right (hnext p hw' (by omega))] + exact ih hw' fun w' h1 h2 => hnext w' h1 (by omega) + · rw [ite_eq_left hwj] + +theorem base_mono (gs : List GateCode) {i i' : ℕ} (h : i ≤ i') : base gs i ≤ base gs i' := by + unfold base + rw [← List.take_append_drop i (gs.take i'), List.take_take, Nat.min_eq_left h, + occurrences_append, List.length_append] + exact Nat.le_add_right _ _ + +/-- **The gate of an occurrence**: every occurrence lies in one gate. -/ +theorem exists_base_le_lt {gs : List GateCode} {p : ℕ} (hp : p < (occurrences gs).length) : + ∃ i < gs.length, base gs i ≤ p ∧ p < base gs (i + 1) := by + classical + have hex : ∃ i, p < base gs (i + 1) ∧ i < gs.length := by + refine ⟨gs.length - 1, ?_, ?_⟩ + · rcases Nat.eq_zero_or_pos gs.length with h | h + · simp [List.length_eq_zero_iff.mp h, occurrences] at hp + · rw [Nat.sub_add_cancel h, base_length] + exact hp + · rcases Nat.eq_zero_or_pos gs.length with h | h + · simp [List.length_eq_zero_iff.mp h, occurrences] at hp + · omega + refine ⟨Nat.find hex, (Nat.find_spec hex).2, ?_, (Nat.find_spec hex).1⟩ + rcases Nat.eq_zero_or_pos (Nat.find hex) with h | h + · rw [h] + simp [base, occurrences] + · have hmin := Nat.find_min hex (show Nat.find hex - 1 < Nat.find hex by omega) + rw [Nat.sub_add_cancel h] at hmin + have : Nat.find hex - 1 < gs.length := by have := (Nat.find_spec hex).2; omega + simp only [this, and_true, not_lt] at hmin + exact hmin + +theorem eq_of_base_le_lt {gs : List GateCode} {p i i' : ℕ} (h1 : base gs i ≤ p) + (h2 : p < base gs (i + 1)) (h1' : base gs i' ≤ p) (h2' : p < base gs (i' + 1)) : i = i' := by + rcases lt_trichotomy i i' with h | h | h + · have := base_mono gs (show i + 1 ≤ i' by omega) + omega + · exact h + · have := base_mono gs (show i' + 1 ≤ i by omega) + omega + +/-- Gate `i` holds the qubit `q` exactly when one of its occurrences does. -/ +theorem mem_qubits_iff {gs : List GateCode} {i : ℕ} (hi : i < gs.length) (q : ℕ) : + q ∈ qubits (gs.getD i default) ↔ + ∃ k < (qubits (gs.getD i default)).length, (occurrences gs).getD (base gs i + k) 0 = q := by + rw [List.mem_iff_getElem] + constructor + · rintro ⟨k, hk, rfl⟩ + exact ⟨k, hk, (getD_occurrences hi hk).trans (List.getD_eq_getElem _ _ hk)⟩ + · rintro ⟨k, hk, hq⟩ + exact ⟨k, hk, ((List.getD_eq_getElem _ _ hk).symm.trans (getD_occurrences hi hk).symm).trans hq⟩ + +private theorem sum_range_ite_eq (L i₀ : ℕ) : + ((List.range L).map fun i => if i = i₀ then (1 : ℕ) else 0).sum = if i₀ < L then 1 else 0 := by + induction L with + | zero => simp + | succ L ih => + rw [List.range_succ, List.map_append, List.sum_append, ih] + rcases lt_trichotomy i₀ L with h | rfl | h + · simp [h, Nat.lt_succ_of_lt h, h.ne'] + · simp + · simp [show ¬ i₀ < L by omega, show ¬ i₀ < L + 1 by omega, show L ≠ i₀ by omega] + +/-- **The reader of a link edge.** Let `u` be an occurrence of the qubit `q`. If `u` is the last +occurrence of `q`, no gate reads `q` from `u`; otherwise exactly one gate does, the gate of the +next occurrence of `q`. -/ +theorem sum_readers {gs : List GateCode} (hnd : ∀ g ∈ gs, (qubits g).Nodup) {u : ℕ} + (hu : u < (occurrences gs).length) : + ((List.range gs.length).map fun i => + if (occurrences gs).getD u 0 ∈ qubits (gs.getD i default) ∧ + lastOcc (occurrences gs) ((occurrences gs).getD u 0) (base gs i) = some u + then (1 : ℕ) else 0).sum + + (if lastOcc (occurrences gs) ((occurrences gs).getD u 0) (occurrences gs).length = some u + then 1 else 0) = 1 := by + classical + set qs := occurrences gs with hqs + set q := qs.getD u 0 + have hgate (i : ℕ) (hi : i < gs.length) : + (qubits (gs.getD i default)).Nodup := + hnd _ (List.getD_eq_getElem _ default hi ▸ List.getElem_mem hi) + by_cases hlast : lastOcc qs q qs.length = some u + · -- the last occurrence: no gate reads it + rw [ite_eq_left hlast] + suffices h : ∀ i ∈ List.range gs.length, ¬ (q ∈ qubits (gs.getD i default) ∧ + lastOcc qs q (base gs i) = some u) by + rw [List.map_congr_left fun i hi => ite_eq_right (h i hi)] + simp + rintro i hi ⟨hq, hl⟩ + have hi' := List.mem_range.mp hi + obtain ⟨k, hk, hkq⟩ := (mem_qubits_iff hi' q).mp hq + have h1 := (lastOcc_eq_some hl).1 + have h2 := base_add_le hi' + rw [← hqs] at h2 + exact (lastOcc_eq_some hlast).2.2 (base gs i + k) (by omega) (by omega) hkq + · rw [ite_eq_right hlast, Nat.add_zero] + -- the next occurrence of `q` after `u` + obtain ⟨w', hw'⟩ : ∃ w', lastOcc qs q qs.length = some w' := by + cases h : lastOcc qs q qs.length with + | none => exact absurd rfl (lastOcc_eq_none.mp h u hu) + | some w' => exact ⟨w', rfl⟩ + obtain ⟨hw'N, hw'q, hw'next⟩ := lastOcc_eq_some hw' + have huw' : u < w' := by + rcases lt_trichotomy u w' with h | rfl | h + · exact h + · exact absurd hw' hlast + · exact absurd rfl (hw'next u h hu) + have hex : ∃ p, u < p ∧ p < qs.length ∧ qs.getD p 0 = q := ⟨w', huw', hw'N, hw'q⟩ + set p₀ := Nat.find hex + obtain ⟨hup, hpN, hpq⟩ := Nat.find_spec hex + have hmin : ∀ p, u < p → p < p₀ → qs.getD p 0 ≠ q := fun p h1 h2 h3 => + Nat.find_min hex h2 ⟨h1, by omega, h3⟩ + obtain ⟨i₀, hi₀, hb₀, hb₀'⟩ := exists_base_le_lt hpN + rw [base_succ hi₀] at hb₀' + -- gate `i₀` reads `q` from `u`, and no other gate does + have hiff : ∀ i < gs.length, (q ∈ qubits (gs.getD i default) ∧ + lastOcc qs q (base gs i) = some u) ↔ i = i₀ := by + intro i hi + constructor + · rintro ⟨hq, hl⟩ + obtain ⟨k, hk, hkq⟩ := (mem_qubits_iff hi q).mp hq + obtain ⟨hub, -, hnext⟩ := lastOcc_eq_some hl + have hp₀ : p₀ ≤ base gs i + k := Nat.find_min' hex ⟨by omega, by + have := base_add_le hi; rw [← hqs] at this; omega, hkq⟩ + have hbi := base_succ hi + rcases lt_trichotomy i i₀ with h | h | h + · have := base_mono gs (show i + 1 ≤ i₀ by omega) + omega + · exact h + · have := base_mono gs (show i₀ + 1 ≤ i by omega) + rw [base_succ hi₀] at this + exact absurd hpq (hnext p₀ hup (by omega)) + · intro h + rw [h] + have hk : p₀ - base gs i₀ < (qubits (gs.getD i₀ default)).length := by omega + refine ⟨(mem_qubits_iff hi₀ q).mpr ⟨p₀ - base gs i₀, hk, by + rw [Nat.add_sub_cancel' hb₀]; exact hpq⟩, lastOcc_eq_some_of ?_ rfl ?_⟩ + · -- `u` lies before gate `i₀`, whose qubits are distinct + by_contra hub + replace hub := Nat.le_of_not_lt hub + have hku : u - base gs i₀ < (qubits (gs.getD i₀ default)).length := by omega + have e1 := getD_occurrences hi₀ hku + have e2 := getD_occurrences hi₀ hk + rw [Nat.add_sub_cancel' hub] at e1 + rw [Nat.add_sub_cancel' hb₀, hpq] at e2 + rw [List.getD_eq_getElem _ _ hku] at e1 + rw [List.getD_eq_getElem _ _ hk] at e2 + have := (List.Nodup.getElem_inj_iff (hgate i₀ hi₀)).mp (e1.symm.trans e2) + omega + · exact fun w h1 h2 => hmin w h1 (by omega) + rw [List.map_congr_left fun i hi => if_congr (hiff i (List.mem_range.mp hi)) rfl rfl, + sum_range_ite_eq, ite_eq_left hi₀] + end Cslib.Quantum.PathNetwork From 692c545e08d186d1b858d0ea5275474a1ffc8cb7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:22:02 -0400 Subject: [PATCH 03/13] fix(Quantum): use simp only for non-terminal simp calls Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity/Computability/Circuit/Quantum/PathNetwork.lean | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index 6d51c5c73..27d9b8060 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -848,7 +848,7 @@ private theorem sum_pair {M : Type*} [AddCommMonoid M] (N : ℕ) (G : (ℕ → B have := Fin.append_right a b ⟨i - N, by omega⟩ convert this using 2 ext - simp + simp only [Fin.cast_mk, Fin.natAdd_eq_addNat, Fin.addNat_mk] omega · simp only [h2, dite_false, h1] @@ -857,14 +857,14 @@ private theorem map_pairFun_left {N : ℕ} (a b : Fin N → Bool) : refine List.ext_getElem (by simp) fun u h₁ h₂ => ?_ simp only [List.getElem_map, List.getElem_range, List.getElem_ofFn, Nat.zero_mul, Nat.zero_add, pairFun] - simp at h₁ + simp only [List.length_map, List.length_range] at h₁ simp [h₁] private theorem map_pairFun_right {N : ℕ} (a b : Fin N → Bool) : (List.range N).map (fun u => pairFun a b (1 * N + u)) = List.ofFn b := by refine List.ext_getElem (by simp) fun u h₁ h₂ => ?_ simp only [List.getElem_map, List.getElem_range, List.getElem_ofFn, Nat.one_mul, pairFun] - simp at h₁ + simp only [List.length_map, List.length_range] at h₁ simp [show N + u < 2 * N by omega] /-- **The contraction of the network is the acceptance gap.** With `-1` as the Hadamard sign, over From c6b574f0efe4e0177104c310d345b393103fd55b Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:37:39 -0400 Subject: [PATCH 04/13] feat(Quantum): bipartition and polynomial-time construction of the path network Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Quantum/PathNetwork.lean | 195 +++++++++++++ .../Circuit/Quantum/PathNetworkPolyTime.lean | 270 ++++++++++++++++++ 2 files changed, 465 insertions(+) create mode 100644 Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index 6287f2581..c15439986 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -1289,4 +1289,199 @@ theorem sum_readers {gs : List GateCode} (hnd : ∀ g ∈ gs, (qubits g).Nodup) rw [List.map_congr_left fun i hi => if_congr (hiff i (List.mem_range.mp hi)) rfl rfl, sum_range_ite_eq, ite_eq_left hi₀] +/-- A gate whose qubits are distinct has distinct ports: the out edges are distinct and even, and +the input ports are distinct and odd, since distinct qubits have distinct last occurrences. -/ +theorem nodup_gatePorts (gs : List GateCode) (c i : ℕ) (hnd : (qubits (gs.getD i default)).Nodup) : + (gatePorts gs c i).Nodup := by + unfold gatePorts + rw [List.nodup_append] + refine ⟨List.nodup_range.map fun a b h => by omega, hnd.filterMap ?_, ?_⟩ + · intro j j' e hj hj' + simp only [inPort, Option.mem_def, Option.map_eq_some_iff] at hj hj' + obtain ⟨w, hw, rfl⟩ := hj + obtain ⟨w', hw', he⟩ := hj' + obtain rfl : w' = w := by omega + exact (lastOcc_eq_some hw).2.1.symm.trans (lastOcc_eq_some hw').2.1 + · intro a ha b hb + simp only [List.mem_map, List.mem_range, List.mem_filterMap, inPort, + Option.map_eq_some_iff] at ha hb + obtain ⟨k, -, rfl⟩ := ha + obtain ⟨j, -, w, -, rfl⟩ := hb + omega + +private theorem mem_gatePorts {gs : List GateCode} {c i e : ℕ} : + e ∈ gatePorts gs c i ↔ + (∃ k < (qubits (gs.getD i default)).length, + 2 * (c * (occurrences gs).length + base gs i + k) = e) ∨ + ∃ j ∈ qubits (gs.getD i default), ∃ w, lastOcc (occurrences gs) j (base gs i) = some w ∧ + 2 * (c * (occurrences gs).length + w) + 1 = e := by + simp only [gatePorts, List.mem_append, List.mem_map, List.mem_range, List.mem_filterMap, inPort, + Option.map_eq_some_iff] + +/-- The out edge of occurrence `u` in copy `c` is a port of gate `i` in copy `c'` exactly when +`c' = c` and `u` is an occurrence of gate `i`. -/ +private theorem count_gatePorts_out {gs : List GateCode} {i : ℕ} (hi : i < gs.length) + (hnd : (qubits (gs.getD i default)).Nodup) {c c' u : ℕ} (hc : c < 2) (hc' : c' < 2) + (hu : u < (occurrences gs).length) : + (gatePorts gs c' i).count (2 * (c * (occurrences gs).length + u)) = + if c' = c ∧ base gs i ≤ u ∧ u < base gs (i + 1) then 1 else 0 := by + rw [List.Nodup.count (nodup_gatePorts gs c' i hnd)] + refine if_congr ?_ rfl rfl + have hk := base_add_le hi + rw [mem_gatePorts, base_succ hi] + constructor + · rintro (⟨k, hk', he⟩ | ⟨j, -, w, -, he⟩) + · interval_cases c <;> interval_cases c' <;> + simp only [Nat.zero_mul, Nat.one_mul, true_and] at he ⊢ <;> omega + · omega + · rintro ⟨rfl, h1, h2⟩ + exact Or.inl ⟨u - base gs i, by omega, by omega⟩ + +/-- The link edge of occurrence `u` in copy `c` is a port of gate `i` in copy `c'` exactly when +`c' = c` and gate `i` reads the qubit of `u` from `u`. -/ +private theorem count_gatePorts_link {gs : List GateCode} (i : ℕ) + (hnd : (qubits (gs.getD i default)).Nodup) {c c' u : ℕ} (hc : c < 2) (hc' : c' < 2) + (hu : u < (occurrences gs).length) : + (gatePorts gs c' i).count (2 * (c * (occurrences gs).length + u) + 1) = + if c' = c ∧ ((occurrences gs).getD u 0 ∈ qubits (gs.getD i default) ∧ + lastOcc (occurrences gs) ((occurrences gs).getD u 0) (base gs i) = some u) + then 1 else 0 := by + rw [List.Nodup.count (nodup_gatePorts gs c' i hnd)] + refine if_congr ?_ rfl rfl + have hb := base_le gs i + rw [mem_gatePorts] + constructor + · rintro (⟨k, -, he⟩ | ⟨j, hj, w, hw, he⟩) + · omega + · have hw' := lastOcc_eq_some hw + obtain ⟨rfl, rfl⟩ : c' = c ∧ w = u := by + interval_cases c <;> interval_cases c' <;> + simp only [Nat.zero_mul, Nat.one_mul, true_and] at he ⊢ <;> omega + rw [hw'.2.1] + exact ⟨rfl, hj, hw⟩ + · rintro ⟨rfl, hj, hw⟩ + exact Or.inr ⟨_, hj, u, hw, rfl⟩ + +/-- The glue vertices have only link edges among their ports. -/ +private theorem count_glueVertex_out (gs : List GateCode) (w v : ℕ) : + (glueVertex gs w : List ℕ × List R).1.count (2 * v) = 0 := by + unfold glueVertex + split_ifs <;> simp only [List.count_cons, List.count_nil, beq_iff_eq] <;> split_ifs <;> omega + +/-- The link edge of occurrence `u` in copy `c` is a port of the glue vertex of `w` exactly when +`w = u` is the last occurrence of its qubit. -/ +private theorem count_glueVertex_link (gs : List GateCode) {w c u : ℕ} + (hw : w < (occurrences gs).length) (hc : c < 2) (hu : u < (occurrences gs).length) : + (glueVertex gs w : List ℕ × List R).1.count (2 * (c * (occurrences gs).length + u) + 1) = + if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w ∧ + w = u then 1 else 0 := by + unfold glueVertex + by_cases hl : lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = + some w + · simp only [hl, true_and, ↓reduceIte, List.count_cons, List.count_nil, beq_iff_eq] + interval_cases c <;> simp only [Nat.zero_mul, Nat.one_mul, Nat.zero_add] <;> split_ifs <;> + omega + · simp only [hl, ↓reduceIte, false_and, List.count_nil] + +private theorem sum_range_ite_and (L : ℕ) (P : Prop) [Decidable P] (Q : ℕ → Prop) + [DecidablePred Q] : + ((List.range L).map fun i => if P ∧ Q i then (1 : ℕ) else 0).sum = + if P then ((List.range L).map fun i => if Q i then (1 : ℕ) else 0).sum else 0 := by + by_cases hP : P <;> simp [hP] + +/-- Every occurrence lies in exactly one gate. -/ +private theorem sum_range_base (gs : List GateCode) {u : ℕ} (hu : u < (occurrences gs).length) : + ((List.range gs.length).map fun i => + if base gs i ≤ u ∧ u < base gs (i + 1) then (1 : ℕ) else 0).sum = 1 := by + obtain ⟨i₀, hi₀, h1, h2⟩ := exists_base_le_lt hu + rw [List.map_congr_left fun i _ => if_congr (show base gs i ≤ u ∧ u < base gs (i + 1) ↔ + i = i₀ from ⟨fun h => eq_of_base_le_lt h.1 h.2 h1 h2, fun h => h ▸ ⟨h1, h2⟩⟩) rfl rfl, + sum_range_ite_eq, ite_eq_left hi₀] + +/-- **The network is rows-first bipartite** with the gate, glue and scalar vertices as rows, when +the qubits of every gate are distinct. -/ +theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCode} + (hnd : ∀ g ∈ gs, (qubits g).Nodup) : + (network neg X gs).2.IsRowsFirstBipartite (network neg X gs).1 := by + have hnd' (i : ℕ) (hi : i ∈ List.range gs.length) : (qubits (gs.getD i default)).Nodup := + hnd _ (List.getD_eq_getElem _ default (List.mem_range.mp hi) ▸ + List.getElem_mem (List.mem_range.mp hi)) + refine ⟨?_, fun e he => ⟨?_, ?_⟩⟩ + · -- every port names an edge + intro v hv p hp + simp only [vertices_network, List.mem_append, List.mem_map, List.mem_range, + List.mem_singleton] at hv + simp only [numEdges_network] + rcases hv with (((⟨i, hi, rfl⟩ | ⟨i, hi, rfl⟩) | ⟨w, hw, rfl⟩) | rfl) | ⟨w, hw, rfl⟩ + · rcases mem_gatePorts.mp hp with ⟨k, hk, rfl⟩ | ⟨j, -, w, hw, rfl⟩ + · have := base_add_le hi + omega + · have := (lastOcc_eq_some hw).1 + have := base_le gs i + omega + · rcases mem_gatePorts.mp hp with ⟨k, hk, rfl⟩ | ⟨j, -, w, hw, rfl⟩ + · have := base_add_le hi + omega + · have := (lastOcc_eq_some hw).1 + have := base_le gs i + omega + · unfold glueVertex at hp + split_ifs at hp <;> simp only [List.mem_cons, List.not_mem_nil, or_false] at hp <;> omega + · simp [scalarVertex] at hp + · simp only [List.mem_cons, List.not_mem_nil, or_false] at hp + omega + · -- the rows: write `e = 2 (c N + u) + r` + simp only [numEdges_network] at he + obtain ⟨c, u, r, hc, hu, hr, rfl⟩ : ∃ c u r, c < 2 ∧ u < (occurrences gs).length ∧ r < 2 ∧ + e = 2 * (c * (occurrences gs).length + u) + r := by + by_cases h : e / 2 < (occurrences gs).length + · exact ⟨0, e / 2, e % 2, by omega, h, by omega, by omega⟩ + · exact ⟨1, e / 2 - (occurrences gs).length, e % 2, by omega, by omega, by omega, by omega⟩ + rw [fst_network, vertices_network, List.take_left' (by simp; omega)] + simp only [List.map_append, List.sum_append, List.map_map, List.map_cons, List.map_nil, + List.sum_cons, List.sum_nil, Function.comp_def, gateVertex, scalarVertex, List.count_nil, + Nat.add_zero] + interval_cases r + · -- an out edge belongs to the gate of its occurrence in its copy + simp only [Nat.add_zero, count_glueVertex_out, List.map_const', List.sum_replicate, + smul_eq_mul, Nat.mul_zero] + rw [List.map_congr_left fun i hi => count_gatePorts_out (List.mem_range.mp hi) (hnd' i hi) + hc (show 0 < 2 by omega) hu, + List.map_congr_left fun i hi => count_gatePorts_out (List.mem_range.mp hi) (hnd' i hi) + hc (show 1 < 2 by omega) hu, + sum_range_ite_and, sum_range_ite_and, sum_range_base gs hu] + interval_cases c <;> simp + · -- a link edge belongs to its reader or its glue vertex + rw [List.map_congr_left fun i hi => count_gatePorts_link i (hnd' i hi) hc + (show 0 < 2 by omega) hu, + List.map_congr_left fun i hi => count_gatePorts_link i (hnd' i hi) hc + (show 1 < 2 by omega) hu, + List.map_congr_left fun w hw => count_glueVertex_link gs (List.mem_range.mp hw) hc hu, + sum_range_ite_and, sum_range_ite_and] + have hglue : ((List.range (occurrences gs).length).map fun w => + if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = + some w ∧ w = u then (1 : ℕ) else 0).sum = + if lastOcc (occurrences gs) ((occurrences gs).getD u 0) (occurrences gs).length = + some u then 1 else 0 := by + rw [List.map_congr_left fun w _ => if_congr (show (lastOcc (occurrences gs) + ((occurrences gs).getD w 0) (occurrences gs).length = some w ∧ w = u) ↔ + (lastOcc (occurrences gs) ((occurrences gs).getD u 0) (occurrences gs).length = + some u ∧ w = u) from + ⟨fun ⟨h1, h2⟩ => ⟨h2 ▸ h1, h2⟩, fun ⟨h1, h2⟩ => ⟨h2 ▸ h1, h2⟩⟩) rfl rfl, + sum_range_ite_and] + split_ifs + · rw [sum_range_ite_eq, ite_eq_left hu] + · rfl + rw [hglue] + have := sum_readers hnd hu + interval_cases c <;> simpa using this + · -- the columns: the segment vertex `e / 2` + simp only [numEdges_network] at he + rw [fst_network, vertices_network, List.drop_left' (by simp; omega)] + have hcount (w : ℕ) : ([2 * w, 2 * w + 1] : List ℕ).count e = if w = e / 2 then 1 else 0 := by + simp only [List.count_cons, List.count_nil, beq_iff_eq] + split_ifs <;> omega + simp only [List.map_map, Function.comp_def] + rw [List.map_congr_left fun w _ => hcount w, sum_range_ite_eq, ite_eq_left (by omega)] + end Cslib.Quantum.PathNetwork diff --git a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean new file mode 100644 index 000000000..10eb26120 --- /dev/null +++ b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean @@ -0,0 +1,270 @@ +/- +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.Quantum.PathNetwork +public import Complexity.Computability.Circuit.Quantum.Uniform +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.List +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Nat +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Option +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Unary +public import Complexity.Computability.Machines.Turing.MultiTape.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Combinators.TakeDropUnary +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.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListMap +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives + +/-! +# The network of a circuit in polynomial time + +The network of a list of gate codes (`Cslib.Quantum.PathNetwork.network`) is computable in +polynomial time from the gate codes, the input register and the Hadamard sign, with its number of +rows in binary and the network in the encoding of the contraction algorithm +(`ExplicitTensorNetwork.encExplicitNetwork`). Every port and last occurrence is a search or a map +over the occurrences of qubits, written in unary, and every table is a map over the first `2 ^ 6` +numbers, a constant list. + +## Main results + +* `Cslib.Quantum.PathNetwork.computableInPolyTime_network`: the network is polynomial-time. +-/ + +public section + +namespace Cslib.Quantum.PathNetwork + +open Turing.MultiTapeTM ComputableInPolyTime Cslib.Circuits.Frontier ExplicitTensorNetwork + +variable {α : Type} {inp : α ↪ List Bool} + +/-! ### Reformulations for the polynomial-time combinators -/ + +private theorem filterMap_eq {β : Type} (l : List β) (f : β → Option ℕ) : + l.filterMap f = (l.filter fun b => (f b).isSome).map fun b => (f b).getD 0 := by + induction l with + | nil => rfl + | cons b l ih => + cases h : f b <;> simp [h, ih] + +private theorem gatePorts_eq (gs : List GateCode) (c i : ℕ) : + gatePorts gs c i = + (List.range (qubits (gs.getD i default)).length).map + (fun k => 2 * (c * (occurrences gs).length + base gs i + k)) ++ + ((qubits (gs.getD i default)).filter + fun j => (lastOcc (occurrences gs) j (base gs i)).isSome).map + fun j => 2 * (c * (occurrences gs).length + + (lastOcc (occurrences gs) j (base gs i)).getD 0) + 1 := by + rw [gatePorts, filterMap_eq] + congr 1 + simp only [inPort, Option.isSome_map] + refine List.map_congr_left fun j hj => ?_ + obtain ⟨w, hw⟩ := Option.isSome_iff_exists.mp (List.mem_filter.mp hj).2 + rw [hw] + rfl + +private theorem inBit_eq (X : List Bool) (gs : List GateCode) (c i idx : ℕ) : + inBit X gs c i idx = fun j => (lastOcc (occurrences gs) j (base gs i)).elim (X.getD j false) + fun w => idx.testBit ((gatePorts gs c i).idxOf + (2 * (c * (occurrences gs).length + w) + 1)) := by + funext j + unfold inBit inPort + cases lastOcc (occurrences gs) j (base gs i) <;> rfl + +private theorem scalarVertex_eq (X : List Bool) (gs : List GateCode) : + (scalarVertex X gs : List ℕ × List ℕ) = + ([], [if ((occurrences gs).any fun q => decide (q = 0)) || X.getD 0 false then 1 else 0]) := by + simp only [scalarVertex, List.any_eq_true, decide_eq_true_eq, exists_eq_right, Bool.or_eq_true] + +/-- The encoding of networks is the encoding of their number of edges and vertices. -/ +private theorem computableInPolyTime_equivProd_symm : + ComputableInPolyTime (DescribedNetwork.equivProd (T := List ℕ)).symm + (encPair encUnary (encList (encPair (encList encNat) (encList encNat)))) + ExplicitTensorNetwork.encExplicitNetwork := + ComputableInPolyTime.of_eq fun _ => rfl + +private theorem binaryPorts {ps : α → List ℕ} + (hps : ComputableInPolyTime ps inp (encList encUnary)) : + ComputableInPolyTime ps inp (encList encNat) := by + simpa only [Function.comp_def, List.map_id'] using (map unary_to_binary).comp hps + +/-! ### Occurrences -/ + +section Data + +variable {gs : α → List GateCode} (hgs : ComputableInPolyTime gs inp (encList encGateCode)) +include hgs + +theorem computableInPolyTime_qubits_getD {i : α → ℕ} (hi : ComputableInPolyTime i inp encUnary) : + ComputableInPolyTime (fun a => qubits ((gs a).getD (i a) default)) inp + (encList encUnary) := by + have hg := listGetDWithInput hgs hi default + unfold qubits + exact cons' ((fst _ _).comp ((snd _ _).comp hg)) ((snd _ _).comp ((snd _ _).comp hg)) + +theorem computableInPolyTime_occurrences : + ComputableInPolyTime (fun a => occurrences (gs a)) inp (encList encUnary) := by + unfold occurrences qubits + exact flatMapWithInput hgs (cons' ((fst _ _).comp ((snd _ _).comp (snd _ _))) + ((snd _ _).comp ((snd _ _).comp (snd _ _)))) + +theorem computableInPolyTime_base {i : α → ℕ} (hi : ComputableInPolyTime i inp encUnary) : + ComputableInPolyTime (fun a => base (gs a) (i a)) inp encUnary := + (length_list _).comp (computableInPolyTime_occurrences + ((ComputableInDegree.takeUnary encGateCode).computableInPolyTime.comp (hgs.prod hi))) + +end Data + +theorem computableInPolyTime_lastOcc {qs : α → List ℕ} {j p : α → ℕ} + (hqs : ComputableInPolyTime qs inp (encList encUnary)) + (hj : ComputableInPolyTime j inp encUnary) (hp : ComputableInPolyTime p inp encUnary) : + ComputableInPolyTime (fun a => lastOcc (qs a) (j a) (p a)) inp (encOption encUnary) := by + unfold lastOcc + exact findWithInput (listReverseWithInput (range.comp hp)) + (decide_eq' (listGetDWithInput (hqs.comp (fst _ _)) (snd _ _) 0) (hj.comp (fst _ _))) + +/-- The table of a vertex with `w ≤ 6` ports whose entries are polynomial-time at every fixed +position. -/ +theorem computableInPolyTime_table {w : α → ℕ} {f : α → ℕ → ℕ} + (hw : ComputableInPolyTime w inp encUnary) + (hf : ∀ idx, ComputableInPolyTime (fun a => f a idx) inp encNat) : + ComputableInPolyTime (fun a => table (w a) (f a)) inp (encList encNat) := by + unfold table + exact mapFilterConst _ (fun idx _ => binary_lt' (const idx) (binary_pow' (const 2) hw)) + fun idx _ => hf idx + +/-! ### Vertices -/ + +section Vertices + +variable {gs : α → List GateCode} (hgs : ComputableInPolyTime gs inp (encList encGateCode)) +include hgs + +/-- The ports of a gate vertex, in unary. -/ +theorem computableInPolyTime_gatePorts {c i : α → ℕ} (hc : ComputableInPolyTime c inp encUnary) + (hi : ComputableInPolyTime i inp encUnary) : + ComputableInPolyTime (fun a => gatePorts (gs a) (c a) (i a)) inp (encList encUnary) := by + have hq := computableInPolyTime_qubits_getD hgs hi + have hN := (length_list _).comp (computableInPolyTime_occurrences hgs) + have hb := computableInPolyTime_base hgs hi + simp only [gatePorts_eq] + refine listAppend' (mapWithInput (range.comp ((length_list _).comp hq)) + (unary_mul' (const 2) (unary_add' (unary_add' (unary_mul' (hc.comp (fst _ _)) + (hN.comp (fst _ _))) (hb.comp (fst _ _))) (snd _ _)))) ?_ + have hl : ComputableInPolyTime (fun p : α × ℕ => lastOcc (occurrences (gs p.1)) p.2 + (base (gs p.1) (i p.1))) (encPair inp encUnary) (encOption encUnary) := + computableInPolyTime_lastOcc (computableInPolyTime_occurrences (hgs.comp (fst _ _))) + (snd _ _) (hb.comp (fst _ _)) + exact mapWithInput (filterWithInput hq (optionIsSome hl)) + (unary_add' (unary_mul' (const 2) (unary_add' (unary_mul' (hc.comp (fst _ _)) + (hN.comp (fst _ _))) (optionGetDWithInput hl (const 0)))) (const 1)) + +/-- **Gate vertices are polynomial-time.** -/ +theorem computableInPolyTime_gateVertex {neg : α → ℕ} {X : α → List Bool} {c i : α → ℕ} + (hneg : ComputableInPolyTime neg inp encNat) (hX : ComputableInPolyTime X inp encBits) + (hc : ComputableInPolyTime c inp encUnary) (hi : ComputableInPolyTime i inp encUnary) : + ComputableInPolyTime (fun a => gateVertex (neg a) (X a) (gs a) (c a) (i a)) inp + (encPair (encList encNat) (encList encNat)) := by + have hps := computableInPolyTime_gatePorts hgs hc hi + have hq := computableInPolyTime_qubits_getD hgs hi + have hg := listGetDWithInput hgs hi default + have hN := (length_list _).comp (computableInPolyTime_occurrences hgs) + have hb := computableInPolyTime_base hgs hi + refine (binaryPorts hps).prod (computableInPolyTime_table ((length_list _).comp hps) + fun idx => ?_) + -- the bits entering the qubits of the gate + have hins : ComputableInPolyTime (fun a => (qubits ((gs a).getD (i a) default)).map + (inBit (X a) (gs a) (c a) (i a) idx)) inp (encList encBool) := by + simp only [inBit_eq] + refine mapWithInput hq (optionElimWithInput + (computableInPolyTime_lastOcc (computableInPolyTime_occurrences (hgs.comp (fst _ _))) + (snd _ _) (hb.comp (fst _ _))) + (bitsGetDWithInput (hX.comp (fst _ _)) (snd _ _) false) + (binary_testBit (const idx) (idxOfWithInput (hps.comp ((fst _ _).comp (fst _ _))) + (unary_add' (unary_mul' (const 2) (unary_add' (unary_mul' + (hc.comp ((fst _ _).comp (fst _ _))) (hN.comp ((fst _ _).comp (fst _ _)))) + (snd _ _))) (const 1))))) + -- the bits leaving them + have houts : ComputableInPolyTime (fun a => (List.range (qubits ((gs a).getD (i a) + default)).length).map idx.testBit) inp (encList encBool) := + mapWithInput (range.comp ((length_list _).comp hq)) (binary_testBit (const idx) (snd _ _)) + unfold gateEntry + refine ite_decide (decide_eq' ((fst _ _).comp hg) (const true)) ?_ ?_ + · exact ite_decide (decide_eq' (bool_and' ((headD false).comp hins) ((headD false).comp houts)) + (const true)) hneg (const 1) + · exact ite_decide (decide_eq' houts (cons' (bool_xor.comp (((headD false).comp hins).prod + (allWithInput ((tail (encA := encBool)).comp hins) (snd _ _)))) ((tail (encA := encBool)).comp hins))) (const 1) (const 0) + +/-- **Glue vertices are polynomial-time.** -/ +theorem computableInPolyTime_glueVertex {w : α → ℕ} (hw : ComputableInPolyTime w inp encUnary) : + ComputableInPolyTime (fun a => (glueVertex (gs a) (w a) : List ℕ × List ℕ)) inp + (encPair (encList encNat) (encList encNat)) := by + have hqs := computableInPolyTime_occurrences hgs + have hN := (length_list _).comp hqs + have hq := listGetDWithInput hqs hw 0 + unfold glueVertex + refine ite_decide (decide_eq' (computableInPolyTime_lastOcc hqs hq hN) (optionSome _ |>.comp hw)) + ((binaryPorts (cons' (unary_add' (unary_mul' (const 2) hw) (const 1)) + (cons' (unary_add' (unary_mul' (const 2) (unary_add' hN hw)) (const 1)) (const [])))).prod + (ite_decide (decide_eq' hq (const 0)) (const _) (const _))) (const _) + +end Vertices + +/-- **The scalar vertex is polynomial-time.** -/ +theorem computableInPolyTime_scalarVertex {X : α → List Bool} {gs : α → List GateCode} + (hX : ComputableInPolyTime X inp encBits) + (hgs : ComputableInPolyTime gs inp (encList encGateCode)) : + ComputableInPolyTime (fun a => (scalarVertex (X a) (gs a) : List ℕ × List ℕ)) inp + (encPair (encList encNat) (encList encNat)) := by + simp only [scalarVertex_eq] + refine (const []).prod (cons' (ite_decide (decide_eq' (bool_or.comp + ((anyWithInput (computableInPolyTime_occurrences hgs) (decide_eq' (snd _ _) (const 0))).prod + (bitsGetDWithInput hX (const 0) false))) (const true)) (const 1) (const 0)) (const [])) + +/-- **The network of a circuit is polynomial-time** in the gate codes, the input register and the +Hadamard sign, with its number of rows in binary. -/ +theorem computableInPolyTime_network {neg : α → ℕ} {X : α → List Bool} {gs : α → List GateCode} + (hneg : ComputableInPolyTime neg inp encNat) (hX : ComputableInPolyTime X inp encBits) + (hgs : ComputableInPolyTime gs inp (encList encGateCode)) : + ComputableInPolyTime (fun a => network (neg a) (X a) (gs a)) inp + (encPair encNat ExplicitTensorNetwork.encExplicitNetwork) := by + have hL := (length_list _).comp hgs + have hN := (length_list _).comp (computableInPolyTime_occurrences hgs) + have hgate (c : ℕ) : ComputableInPolyTime (fun a => (List.range (gs a).length).map + (gateVertex (neg a) (X a) (gs a) c)) inp + (encList (encPair (encList encNat) (encList encNat))) := + mapWithInput (range.comp hL) (computableInPolyTime_gateVertex (hgs.comp (fst _ _)) + (hneg.comp (fst _ _)) (hX.comp (fst _ _)) (const c) (snd _ _)) + have hverts : ComputableInPolyTime (fun a => + (List.range (gs a).length).map (gateVertex (neg a) (X a) (gs a) 0) ++ + (List.range (gs a).length).map (gateVertex (neg a) (X a) (gs a) 1) ++ + (List.range (occurrences (gs a)).length).map (glueVertex (gs a)) ++ + [scalarVertex (X a) (gs a)] ++ + (List.range (2 * (occurrences (gs a)).length)).map fun w => + (([2 * w, 2 * w + 1], eqTable) : List ℕ × List ℕ)) inp + (encList (encPair (encList encNat) (encList encNat))) := + listAppend' (listAppend' (listAppend' (listAppend' (hgate 0) (hgate 1)) + (mapWithInput (range.comp hN) (computableInPolyTime_glueVertex (hgs.comp (fst _ _)) + (snd _ _)))) + (cons' (computableInPolyTime_scalarVertex hX hgs) (const []))) + (mapWithInput (range.comp (unary_mul' (const 2) hN)) + ((binaryPorts (cons' (unary_mul' (const 2) (snd _ _)) + (cons' (unary_add' (unary_mul' (const 2) (snd _ _)) (const 1)) (const [])))).prod + (const _))) + unfold network + exact (unary_to_binary.comp (unary_add' (unary_add' (unary_mul' (const 2) hL) hN) + (const 1))).prod (computableInPolyTime_equivProd_symm.comp + ((unary_mul' (const 2) (unary_mul' (const 2) hN)).prod hverts)) + +end Cslib.Quantum.PathNetwork From 8da556b43a9b70bb6c571aa765d3f185d5cd681a Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:43:29 -0400 Subject: [PATCH 05/13] wip(Quantum): BQTIME and the contraction simulator Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Classes/BQP/TensorContraction.lean | 362 ++++++++++++++++++ .../Complexity/Classes/BQP/Time.lean | 92 +++++ 2 files changed, 454 insertions(+) create mode 100644 Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean create mode 100644 Complexity/Computability/Complexity/Classes/BQP/Time.lean diff --git a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean new file mode 100644 index 000000000..b7603688c --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean @@ -0,0 +1,362 @@ +/- +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.BQP.Time +public import Complexity.Computability.Circuit.Quantum.PathNetworkPolyTime +public import Complexity.Computability.Complexity.Classes.CountingSimulation.FastLeaves +public import Complexity.Computability.Complexity.Classes.Time +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain +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 +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Foundations.Data.Nat.SqrtLog + +/-! +# Quantum circuits by tensor contraction: `BQTIME[T] ⊆ DTIME[2 ^ O(T / √log T)]` + +The acceptance probability of a circuit of `L` gates over the Hadamard, NOT, CNOT and Toffoli gates +is `acceptGap / 2 ^ h`, where `h` is the number of Hadamard gates +(`Cslib.Quantum.acceptProb_eq_acceptGap`). The acceptance gap is the contraction of the network of +the circuit and its mirror copy (`Cslib.Quantum.PathNetwork.contraction_network`), and over `ℕ`, +with the Hadamard sign `2 ^ (h + 1) - 1`, its residue modulo `2 ^ (h + 1)` +(`Cslib.Quantum.PathNetwork.contraction_network_mod`). The network has `s = 4 N` edges for +`N ≤ 3 L` occurrences of qubits in gates, degree at most six and a bipartition, so it is admissible +for the contraction algorithm of Theorem D.1 of [OpenAI2026ETH] +(`CountingSimulation.fastContractionAlgorithm`) at size `s`, which contracts it in time +`2 ^ O(s / √⌊log₂ s⌋)` times a polynomial and in polynomial space. The width of the circuit does +not enter the exponent: qubits that no gate touches cost nothing. + +So the acceptance gap of polynomial-time computed circuits is computed by one machine in time +`2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the input length, and in polynomial space +(`QuantumContraction.exists_computableInO_gapValue`). For a uniform family with `O(T)` gates this +decides the problem of the family: `BQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]` for +`max n 2 ≤ T n`, with a joint polynomial space bound `T ^ d` +(`QuantumContraction.BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`, +`QuantumContraction.exists_decidableInTimeAndSpace_of_mem_BQTIME`). No time-constructibility of +`T` is needed, since the machine never computes `T`. The novelty of this bound has not been +checked against the literature. + +## Main definitions + +* `Cslib.Complexity.QuantumContraction.gapValue`: the acceptance gap as the simulator computes + it. + +## Main results + +* `Cslib.Quantum.PathNetwork.explicitAdmissible_network`: the network is admissible. +* `Cslib.Complexity.QuantumContraction.gapValue_eq`: the simulator computes the acceptance gap. +* `Cslib.Complexity.QuantumContraction.exists_computableInO_rule`, + `Cslib.Complexity.QuantumContraction.exists_computableInO_gapValue`: the simulator and its + resources. +* `Cslib.Complexity.QuantumContraction.exists_decidableInTimeAndSpace_of_mem_BQTIME`, + `Cslib.Complexity.QuantumContraction.BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`: the + class containments. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Theorem D.1 (p. 53). +-/ + +public section + +namespace Cslib.Quantum.PathNetwork + +open Cslib.Circuits.Frontier Cslib.Complexity.CountingSimulation + +/-- The qubits of a well-formed gate code are distinct. -/ +theorem nodup_qubits {m : ℕ} {g : GateCode} (h : g.WellFormed m) : (qubits g).Nodup := by + obtain ⟨_ | _, t, cs⟩ := g + · exact h.2.1 + · simp [qubits, h.2] + +/-- **The network is admissible** at size `4 N`, its number of edges, with constant `6`, for a +nonempty list of well-formed gate codes. -/ +theorem explicitAdmissible_network (neg : ℕ) (X : List Bool) {gs : List GateCode} + (hgs : ∀ g ∈ gs, g.WellFormed X.length) (hne : gs ≠ []) : + ExplicitAdmissible 6 (4 * (occurrences gs).length) (network neg X gs) := by + have h3 : ∀ g ∈ gs, (qubits g).length ≤ 3 := fun g hg => length_qubits_le_three (hgs g hg) + have hL : 1 ≤ gs.length := List.length_pos_iff.mpr hne + have hLN := length_le_length_occurrences gs + have hlog : 1 ≤ Nat.log 2 (4 * (occurrences gs).length) := Nat.log_pos (by norm_num) (by omega) + refine ⟨isRowsFirstBipartite_network neg X fun g hg => nodup_qubits (hgs g hg), ?_, ?_, ?_, + length_table_network neg X h3, ?_⟩ + · rw [numEdges_network] + omega + · exact (maxDegree_network_le neg X h3).trans (by omega) + · rw [length_vertices_network] + omega + · rw [numEdges_network] + omega + +end Cslib.Quantum.PathNetwork + +namespace Cslib.Complexity.QuantumContraction + +open Quantum Quantum.PathNetwork Cslib.Circuits.Frontier Turing.MultiTapeTM Cslib.GenericRAM + CountingSimulation ComputableInPolyTime + +/-! ### The simulator -/ + +/-- The circuits handed to the contraction algorithm: those with at least one gate. -/ +@[expose] def guard (gs : List GateCode) : Bool := !gs.isEmpty + +/-- The input of the contraction algorithm: the precision `h + 1`, for `h` Hadamard gates, and the +network with Hadamard sign `2 ^ (h + 1) - 1` on the input register `X`. -/ +@[expose] def contractionInput (gs : List GateCode) (X : List Bool) : + ℕ × ℕ × ExplicitTensorNetwork ℕ := + (hadamardCount gs + 1, network (2 ^ (hadamardCount gs + 1) - 1 : ℕ) X gs) + +/-- The answer for a circuit without gates: the bit of qubit `0`. -/ +@[expose] def fallback (X : List Bool) : ℕ := if X.getD 0 false then 1 else 0 + +/-- **The acceptance gap as the simulator computes it**: the residue of the contraction of the +network modulo `2 ^ (h + 1)`, or the bit of qubit `0` for a circuit without gates. -/ +@[expose] def gapValue (gs : List GateCode) (X : List Bool) : ℕ := + if guard gs then (contractionInput gs X).2.2.contraction % 2 ^ (contractionInput gs X).1 + else fallback X + +private theorem acceptGap_nil (X : List Bool) : + acceptGap [] X = if X.headD false then 1 else 0 := by + simp only [acceptGap, hadamardCount_nil, acceptTerm, + pathRun, List.ofFn_zero, List.foldl_nil, true_and, Bool.xor_self, Bool.false_eq_true, + ↓reduceIte] + cases X.headD false <;> rfl + +/-- **The simulator computes the acceptance gap** of well-formed gate codes on a nonempty input +register. -/ +theorem gapValue_eq {X : List Bool} (hX : 0 < X.length) {gs : List GateCode} + (hgs : ∀ g ∈ gs, g.WellFormed X.length) : gapValue gs X = (acceptGap gs X).toNat := by + unfold gapValue + split + · exact contraction_network_mod X hX gs hgs (Nat.lt_succ_self _) + · next hg => + obtain rfl : gs = [] := by simpa [guard] using hg + rw [acceptGap_nil, fallback] + cases X with + | nil => rfl + | cons b X => cases b <;> rfl + +/-! ### Resources -/ + +/-- The exponent of the contraction is at most `12 K (L + 1) / √⌊log₂ (L + 2)⌋` for `L` gates. -/ +private theorem exponent_le (K : ℕ) {gs : List GateCode} {m : ℕ} + (hgs : ∀ g ∈ gs, g.WellFormed m) (hne : gs ≠ []) : + K * (4 * (occurrences gs).length) / Nat.sqrt (Nat.log 2 (4 * (occurrences gs).length)) ≤ + 12 * K * (gs.length + 1) / Nat.sqrt (Nat.log 2 (gs.length + 2)) := by + have hL : 1 ≤ gs.length := List.length_pos_iff.mpr hne + have hLN := length_le_length_occurrences gs + have hN3 := length_occurrences_le hgs + refine Nat.div_le_div ?_ (Nat.sqrt_le_sqrt (Nat.log_mono_right (by omega))) ?_ + · have : K * (4 * (occurrences gs).length) ≤ K * (12 * (gs.length + 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' + +section Machine + +variable {α : Type} {enc : α ↪ List Bool} {gs : α → List GateCode} {X : α → List Bool} + +/-- **The simulator with polynomial-time postprocessing.** For polynomial-time computed gate codes +`gs a`, well formed on the polynomial-time computed register `X a`, one machine computes +`rule (h, gapValue (gs a) (X a))`, for `h` the number of Hadamard gates and a polynomial-time +`rule`, in time `2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the input length and in +polynomial space, where `L` is the number of gates. -/ +theorem exists_computableInO_rule (hgs : ComputableInPolyTime gs enc (encList encGateCode)) + (hX : ComputableInPolyTime X enc encBits) (hwf : ∀ a, ∀ g ∈ gs a, g.WellFormed (X a).length) + {β : Type} {encβ : β ↪ List Bool} {rule : ℕ × ℕ → β} + (hrule : ComputableInPolyTime rule (encPair encUnary encNat) encβ) : + ∃ A e : ℕ, ComputableInO (fun a => rule (hadamardCount (gs a), gapValue (gs a) (X a))) enc + encβ + (fun a => 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * + ((enc a).length + 1) ^ e) + (fun a => ((enc a).length + 1) ^ e) := by + obtain ⟨K, hK⟩ := fastContractionAlgorithm.bound 6 1 + -- the polynomial-time parts + have hh : ComputableInPolyTime (fun a => hadamardCount (gs a)) enc encUnary := by + simp only [hadamardCount_eq_countP] + fun_prop + have hguard : ComputableInPolyTime (fun a => guard (gs a)) enc encBool := by + unfold guard + fun_prop + have hinput : ComputableInPolyTime (fun a => contractionInput (gs a) (X a)) enc + (encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) := + (unary_to_binary.comp (unary_add' hh (const 1))).prod (computableInPolyTime_network + (binary_sub' (binary_pow' (const 2) (unary_add' hh (const 1))) + (const 1)) hX hgs) + have hfb : ComputableInPolyTime (fun a => fallback (X a)) enc encNat := + ite_decide (decide_eq' (bitsGetDWithInput hX (const 0) false) (const true)) (const 1) (const 0) + -- the machine + let cv : NativeCertificate (fun a => gapValue (gs a) (X a)) enc encNat := + (NativeCertificate.branch (NativeCertificate.ofPolyTime hguard) + (fastContractionAlgorithm.certificate.comp (NativeCertificate.ofPolyTime hinput)) + (NativeCertificate.ofPolyTime hfb)).congr fun _ => rfl + let cpair := (NativeCertificate.ofPolyTime hh).prod cv + let ctotal := (NativeCertificate.ofPolyTime hrule).comp cpair + -- polynomial bounds + have hpg := NativeCertificate.ofPolyTime_isPolynomial hguard + have hpi := NativeCertificate.ofPolyTime_isPolynomial hinput + have hpf := NativeCertificate.ofPolyTime_isPolynomial hfb + have hph := NativeCertificate.ofPolyTime_isPolynomial hh + have hpr := NativeCertificate.ofPolyTime_isPolynomial hrule + obtain ⟨cg, dg, htg⟩ := hpg.1.exists_le + obtain ⟨cg', dg', hsg⟩ := hpg.2.exists_le + obtain ⟨ci, di, hti⟩ := hpi.1.exists_le + obtain ⟨ci', di', hsi⟩ := hpi.2.exists_le + obtain ⟨cf, df, htf⟩ := hpf.1.exists_le + obtain ⟨cf', df', hsf⟩ := hpf.2.exists_le + obtain ⟨ch, dh, hth⟩ := hph.1.exists_le + obtain ⟨ch', dh', hsh⟩ := hph.2.exists_le + obtain ⟨cr, dr, htr⟩ := hpr.1.exists_le + obtain ⟨cr', dr', hsr⟩ := hpr.2.exists_le + obtain ⟨p, q, hlen⟩ := hinput.polyBounded_length.exists_le + -- the contraction on guarded inputs + have hfast (a : α) (hga : guard (gs a) = true) : + fastContractionAlgorithm.certificate.time (contractionInput (gs a) (X a)) ≤ + K * (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) + (contractionInput (gs a) (X a))).length + 1) ^ K * + 2 ^ (12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) ∧ + fastContractionAlgorithm.certificate.space (contractionInput (gs a) (X a)) ≤ + K * (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) + (contractionInput (gs a) (X a))).length + 1) ^ K := by + have hne : gs a ≠ [] := by simpa [guard] using hga + have hL : 1 ≤ (gs a).length := List.length_pos_iff.mpr hne + have hLN := length_le_length_occurrences (gs a) + have hhL : hadamardCount (gs a) ≤ (gs a).length := by + rw [hadamardCount_eq_countP] + exact List.countP_le_length + have hb := hK (4 * (occurrences (gs a)).length) (hadamardCount (gs a) + 1) + (network (2 ^ (hadamardCount (gs a) + 1) - 1 : ℕ) (X a) (gs a)) (by omega) (by omega) + (by omega) (explicitAdmissible_network _ (X a) (hwf a) hne) + exact ⟨hb.1.trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by norm_num) + (exponent_le K (hwf a) hne))), hb.2⟩ + -- the space of the pair, and so the length of its output + set ds := dg' + di' + df' + dh' + q * K + set CS := cg' + ci' + cf' + ch' + K * (p + 1) ^ K + have hY {a : α} {d d' : ℕ} (h : d ≤ d') : + ((enc a).length + 1) ^ d ≤ ((enc a).length + 1) ^ d' := + Nat.pow_le_pow_right (Nat.le_add_left 1 _) h + have hinlen (a : α) : (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) + (contractionInput (gs a) (X a))).length + 1) ^ K ≤ + (p + 1) ^ K * ((enc a).length + 1) ^ (q * K) := by + have h1 := hlen a + have h2 : 1 ≤ ((enc a).length + 1) ^ q := Nat.one_le_pow _ _ (Nat.le_add_left 1 _) + calc _ ≤ ((p + 1) * ((enc a).length + 1) ^ q) ^ K := Nat.pow_le_pow_left (by nlinarith) K + _ = _ := by rw [mul_pow, pow_mul] + have hspace_pair (a : α) : cpair.space a ≤ CS * ((enc a).length + 1) ^ ds := by + have e1 := (hsh a).trans (Nat.mul_le_mul_left ch' (hY (a := a) (show dh' ≤ ds by omega))) + have e2 := (hsg a).trans (Nat.mul_le_mul_left cg' (hY (a := a) (show dg' ≤ ds by omega))) + have e3 := (hsi a).trans (Nat.mul_le_mul_left ci' (hY (a := a) (show di' ≤ ds by omega))) + have e4 := (hsf a).trans (Nat.mul_le_mul_left cf' (hY (a := a) (show df' ≤ ds by omega))) + change (NativeCertificate.ofPolyTime hh).space a + + ((NativeCertificate.ofPolyTime hguard).space a + if guard (gs a) then + (NativeCertificate.ofPolyTime hinput).space a + + fastContractionAlgorithm.certificate.space (contractionInput (gs a) (X a)) + else (NativeCertificate.ofPolyTime hfb).space a) ≤ _ + split + · next hga => + have e5 := ((hfast a hga).2.trans (Nat.mul_le_mul_left K (hinlen a))).trans + (show K * ((p + 1) ^ K * ((enc a).length + 1) ^ (q * K)) ≤ + K * (p + 1) ^ K * ((enc a).length + 1) ^ ds by + rw [← Nat.mul_assoc]; exact Nat.mul_le_mul_left _ (hY (by omega))) + calc _ ≤ ch' * ((enc a).length + 1) ^ ds + (cg' * ((enc a).length + 1) ^ ds + + (ci' * ((enc a).length + 1) ^ ds + K * (p + 1) ^ K * ((enc a).length + 1) ^ ds)) := + Nat.add_le_add e1 (Nat.add_le_add e2 (Nat.add_le_add e3 e5)) + _ = (ch' + cg' + ci' + K * (p + 1) ^ K) * ((enc a).length + 1) ^ ds := by ring + _ ≤ _ := Nat.mul_le_mul_right _ (by simp only [CS]; omega) + · calc _ ≤ ch' * ((enc a).length + 1) ^ ds + (cg' * ((enc a).length + 1) ^ ds + + cf' * ((enc a).length + 1) ^ ds) := Nat.add_le_add e1 (Nat.add_le_add e2 e4) + _ = (ch' + cg' + cf') * ((enc a).length + 1) ^ ds := by ring + _ ≤ _ := Nat.mul_le_mul_right _ (by simp only [CS]; omega) + obtain ⟨K₀, -, hK₀⟩ := cpair.computes + -- the postprocessing + set CR := K₀ * CS + K₀ + 1 + have hpairlen (a : α) : (encPair encUnary encNat + (hadamardCount (gs a), gapValue (gs a) (X a))).length + 1 ≤ + CR * ((enc a).length + 1) ^ ds := by + have h1 := hK₀ a + have h2 := hspace_pair a + have h3 : 1 ≤ ((enc a).length + 1) ^ ds := Nat.one_le_pow _ _ (Nat.le_add_left 1 _) + change _ ≤ K₀ * (cpair.space a + 1) at h1 + simp only [CR] + nlinarith + have hrule_time (a : α) : (NativeCertificate.ofPolyTime hrule).time + (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ + cr * CR ^ dr * ((enc a).length + 1) ^ (ds * dr) := by + refine (htr _).trans ?_ + calc _ ≤ cr * (CR * ((enc a).length + 1) ^ ds) ^ dr := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hpairlen a) dr) + _ = _ := by rw [mul_pow, ← pow_mul]; ring + have hrule_space (a : α) : (NativeCertificate.ofPolyTime hrule).space + (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ + cr' * CR ^ dr' * ((enc a).length + 1) ^ (ds * dr') := by + refine (hsr _).trans ?_ + calc _ ≤ cr' * (CR * ((enc a).length + 1) ^ ds) ^ dr' := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hpairlen a) dr') + _ = _ := by rw [mul_pow, ← pow_mul]; ring + -- the totals + set e := dg + di + df + dh + q * K + ds * dr + ds * dr' + ds + set k := cg + ci + cf + ch + K * (p + 1) ^ K + cr * CR ^ dr + CS + cr' * CR ^ dr' + 1 + refine ⟨12 * K, e, ctotal.computes.mono k (fun a => ?_) (fun a => ?_)⟩ + · -- time + set E := 12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2)) + set Z := 2 ^ E * ((enc a).length + 1) ^ e + have hZ {d : ℕ} (h : d ≤ e) : ((enc a).length + 1) ^ d ≤ Z := + (hY h).trans (Nat.le_mul_of_pos_left _ Nat.one_le_two_pow) + have e1 := (hth a).trans (Nat.mul_le_mul_left ch (hZ (show dh ≤ e by omega))) + have e2 := (htg a).trans (Nat.mul_le_mul_left cg (hZ (show dg ≤ e by omega))) + have e3 := (hti a).trans (Nat.mul_le_mul_left ci (hZ (show di ≤ e by omega))) + have e4 := (htf a).trans (Nat.mul_le_mul_left cf (hZ (show df ≤ e by omega))) + have e6 := (hrule_time a).trans (Nat.mul_le_mul_left _ (hZ (show ds * dr ≤ e by omega))) + change (NativeCertificate.ofPolyTime hh).time a + + ((NativeCertificate.ofPolyTime hguard).time a + if guard (gs a) then + (NativeCertificate.ofPolyTime hinput).time a + + fastContractionAlgorithm.certificate.time (contractionInput (gs a) (X a)) + else (NativeCertificate.ofPolyTime hfb).time a) + + (NativeCertificate.ofPolyTime hrule).time + (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ k * (Z + 1) + have hk : ∀ c, c ≤ k → c * Z ≤ k * (Z + 1) := fun c hc => + (Nat.mul_le_mul_right _ hc).trans (Nat.mul_le_mul_left _ (Nat.le_succ _)) + split + · next hga => + have e5 : fastContractionAlgorithm.certificate.time (contractionInput (gs a) (X a)) ≤ + K * (p + 1) ^ K * Z := by + refine (hfast a hga).1.trans ?_ + calc _ ≤ K * ((p + 1) ^ K * ((enc a).length + 1) ^ (q * K)) * 2 ^ E := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (hinlen a)) + _ = K * (p + 1) ^ K * (2 ^ E * ((enc a).length + 1) ^ (q * K)) := by ring + _ ≤ K * (p + 1) ^ K * Z := + Nat.mul_le_mul_left _ (Nat.mul_le_mul_left _ (hY (by omega))) + calc _ ≤ ch * Z + (cg * Z + (ci * Z + K * (p + 1) ^ K * Z)) + cr * CR ^ dr * Z := + Nat.add_le_add (Nat.add_le_add e1 (Nat.add_le_add e2 (Nat.add_le_add e3 e5))) e6 + _ = (ch + cg + ci + K * (p + 1) ^ K + cr * CR ^ dr) * Z := by ring + _ ≤ _ := hk _ (by simp only [k]; omega) + · calc _ ≤ ch * Z + (cg * Z + cf * Z) + cr * CR ^ dr * Z := + Nat.add_le_add (Nat.add_le_add e1 (Nat.add_le_add e2 e4)) e6 + _ = (ch + cg + cf + cr * CR ^ dr) * Z := by ring + _ ≤ _ := hk _ (by simp only [k]; omega) + · -- space + have e1 := (hspace_pair a).trans (Nat.mul_le_mul_left CS (hY (a := a) (show ds ≤ e by omega))) + have e2 := (hrule_space a).trans + (Nat.mul_le_mul_left _ (hY (a := a) (show ds * dr' ≤ e by omega))) + change cpair.space a + (NativeCertificate.ofPolyTime hrule).space + (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ k * (((enc a).length + 1) ^ e + 1) + calc _ ≤ CS * ((enc a).length + 1) ^ e + cr' * CR ^ dr' * ((enc a).length + 1) ^ e := + Nat.add_le_add e1 e2 + _ = (CS + cr' * CR ^ dr') * ((enc a).length + 1) ^ e := by ring + _ ≤ k * ((enc a).length + 1) ^ e := + Nat.mul_le_mul_right _ (by simp only [k]; omega) + _ ≤ _ := Nat.mul_le_mul_left _ (Nat.le_succ _) + +end Machine + +end Cslib.Complexity.QuantumContraction diff --git a/Complexity/Computability/Complexity/Classes/BQP/Time.lean b/Complexity/Computability/Complexity/Classes/BQP/Time.lean new file mode 100644 index 000000000..c4dd3b7da --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/BQP/Time.lean @@ -0,0 +1,92 @@ +/- +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.BQP +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimePrimitives + +/-! +# Bounded-error quantum time + +`BQTIME enc t` (`Cslib.Complexity.BQTIME`) is the class of problems decided with bounded error by +the polynomial-time uniform families of quantum circuits of `Cslib.Complexity.BQP` whose circuits +for inputs of length `n` have at most `c₁ t n + c₂` gates, for constants `c₁` and `c₂`. The gate +count plays the role of running time. + +The families are polynomial-time uniform, so every circuit has polynomially many gates anyway, and +`BQTIME enc t` is only meaningful for polynomially bounded `t`: it always lies in `BQP` +(`Cslib.Complexity.BQTIME_subset_BQP`), and the gate bound no longer constrains the family once `t` +dominates every polynomial. `BQP` is the union of the classes `BQTIME[n ^ k]` +(`Cslib.Complexity.BQP_eq_iUnion_BQTIME`). + +## Main definitions + +* `Cslib.Complexity.BQTIME`. + +## Main results + +* `Cslib.Complexity.BQTIME_subset_BQP`: every `BQTIME` class lies in `BQP`. +* `Cslib.Complexity.BQP_eq_iUnion_BQTIME`: `BQP = ⋃ k, BQTIME[n ^ k]`. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Quantum Turing.MultiTapeTM + +variable {α : Type} + +/-- **Bounded-error quantum time** `t`: the problems decided with bounded error, with respect to +the encoding `enc` of instances, by a polynomial-time uniform family of quantum circuits over the +Hadamard, NOT, CNOT and Toffoli gates (`Cslib.Quantum.UniformCircuitFamily`) whose circuit for +inputs of length `n` has at most `c₁ t n + c₂` gates. Members are accepted with probability at +least `2 / 3` and other instances with probability at most `1 / 3`. + +Since the families are polynomial-time uniform, their circuits have polynomially many gates in any +case, so the class is only meaningful for polynomially bounded `t`. -/ +def BQTIME (enc : α ↪ List Bool) (t : ℕ → ℕ) : Set (Set α) := + {A | ∃ (F : UniformCircuitFamily) (c₁ c₂ : ℕ), (∀ n, (F.gates n).length ≤ c₁ * t n + c₂) ∧ + ∀ a, (a ∈ A → 2 / 3 ≤ F.acceptProb (enc a)) ∧ (a ∉ A → F.acceptProb (enc a) ≤ 1 / 3)} + +variable {enc : α ↪ List Bool} + +/-- Every `BQTIME` class lies in `BQP`. -/ +theorem BQTIME_subset_BQP (t : ℕ → ℕ) : BQTIME enc t ⊆ BQP enc := + fun _ ⟨F, _, _, _, hF⟩ => ⟨F, hF⟩ + +private theorem length_le_length_encList {β : Type} (e : β ↪ List Bool) (l : List β) : + l.length ≤ (encList e l).length := by + rw [length_encList] + induction l with + | nil => simp + | cons b l ih => simp only [List.length_cons, List.map_cons, List.sum_cons]; omega + +/-- **`BQP` is the union of the polynomial quantum time classes** `BQTIME[n ^ k]`: the circuits of +a polynomial-time uniform family have polynomially many gates. -/ +theorem BQP_eq_iUnion_BQTIME : BQP enc = ⋃ k : ℕ, BQTIME enc fun n => n ^ k := by + refine Set.Subset.antisymm (fun A ⟨F, hF⟩ => ?_) + (Set.iUnion_subset fun k => BQTIME_subset_BQP _) + obtain ⟨c, d, hc⟩ := F.computableInPolyTime_gates.polyBounded_length.exists_le + refine Set.mem_iUnion.mpr ⟨d, F, c * 2 ^ d, c, fun n => ?_, hF⟩ + have h1 := (length_le_length_encList encGateCode (F.gates n)).trans (hc n) + rw [encUnary_apply, List.length_replicate] at h1 + have h2 : (n + 1) ^ d ≤ 2 ^ d * n ^ d + 1 := by + rcases Nat.eq_zero_or_pos n with rfl | hn + · rcases Nat.eq_zero_or_pos d with rfl | hd + · simp + · simp [Nat.zero_pow hd] + · calc (n + 1) ^ d ≤ (2 * n) ^ d := Nat.pow_le_pow_left (by omega) d + _ = 2 ^ d * n ^ d := mul_pow 2 n d + _ ≤ _ := Nat.le_succ _ + calc (F.gates n).length ≤ c * (n + 1) ^ d := h1 + _ ≤ c * (2 ^ d * n ^ d + 1) := Nat.mul_le_mul_left _ h2 + _ = c * 2 ^ d * n ^ d + c := by ring + +end Cslib.Complexity From c602fa78e920e394426902d63d467287394487ee Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:45:21 -0400 Subject: [PATCH 06/13] feat(Quantum): BQTIME[T] in DTIME[2^O(T/sqrt log T)] by tensor contraction Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Classes/BQP/TensorContraction.lean | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean index b7603688c..0c38eca17 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean @@ -13,6 +13,7 @@ public import Complexity.Computability.Complexity.Classes.CountingSimulation.Fas public import Complexity.Computability.Complexity.Classes.Time public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +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.PolyTime.Nat.Binary @@ -357,6 +358,173 @@ theorem exists_computableInO_rule (hgs : ComputableInPolyTime gs enc (encList en Nat.mul_le_mul_right _ (by simp only [k]; omega) _ ≤ _ := Nat.mul_le_mul_left _ (Nat.le_succ _) +/-- **The simulator.** For polynomial-time computed gate codes `gs a`, well formed on the +polynomial-time computed register `X a`, one machine computes `gapValue (gs a) (X a)`, the +acceptance gap when `X a` is nonempty (`QuantumContraction.gapValue_eq`), in time +`2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the input length and in polynomial +space, where `L` is the number of gates. -/ +theorem exists_computableInO_gapValue (hgs : ComputableInPolyTime gs enc (encList encGateCode)) + (hX : ComputableInPolyTime X enc encBits) + (hwf : ∀ a, ∀ g ∈ gs a, g.WellFormed (X a).length) : + ∃ A e : ℕ, ComputableInO (fun a => gapValue (gs a) (X a)) enc encNat + (fun a => 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * + ((enc a).length + 1) ^ e) + (fun a => ((enc a).length + 1) ^ e) := + exists_computableInO_rule hgs hX hwf (rule := Prod.snd) (snd _ _) + end Machine +/-- **The acceptance probability from the simulator**: for well-formed codes on `m > 0` qubits and +an input `x` of length at most `m`, the acceptance probability is `gapValue / 2 ^ h` on the padded +register. -/ +theorem acceptProb_eq_gapValue {m : ℕ} (hm : 0 < m) {gs : List GateCode} + (hgs : ∀ g ∈ gs, g.WellFormed m) {x : List Bool} (hx : x.length ≤ m) : + acceptProb m gs x = + (gapValue gs (x ++ List.replicate (m - x.length) false) : ℝ) / 2 ^ hadamardCount gs := by + have hlen : (x ++ List.replicate (m - x.length) false).length = m := by simp; omega + have hgs' : ∀ g ∈ gs, g.WellFormed (x ++ List.replicate (m - x.length) false).length := by + rw [hlen] + exact hgs + have hm' : 0 < (x ++ List.replicate (m - x.length) false).length := by + rw [hlen] + exact hm + rw [acceptProb_eq_acceptGap hm gs hgs x, ofFn_inputState hx, gapValue_eq hm' hgs'] + congr 1 + have := Int.toNat_of_nonneg (acceptGap_nonneg_and_le hm' gs hgs').1 + exact_mod_cast this.symm + +/-! ### The class containments -/ + +/-- The exponent in terms of a bound `c₁ T + c₂` on the number of gates `L`. -/ +private theorem exponent_le_of_le (A c₁ c₂ : ℕ) {T L : ℕ} (hT : 2 ≤ T) (hL : L ≤ c₁ * T + c₂) : + A * (L + 1) / Nat.sqrt (Nat.log 2 (L + 2)) ≤ + 2 * A * (c₁ + c₂ + 2) * (T / Nat.sqrt (Nat.log 2 T)) + (2 * A * (c₁ + c₂ + 2) + 3 * A) := by + have hM : L + 2 ≤ (c₁ + c₂ + 2) * T := by nlinarith + calc A * (L + 1) / Nat.sqrt (Nat.log 2 (L + 2)) + ≤ A * (L + 2) / Nat.sqrt (Nat.log 2 (L + 2)) := + Nat.div_le_div_right (Nat.mul_le_mul_left _ (by omega)) + _ ≤ A * ((L + 2) / Nat.sqrt (Nat.log 2 (L + 2))) + A := Nat.mul_div_le_mul_div_add _ _ _ + _ ≤ A * (2 * ((c₁ + c₂ + 2) * T / Nat.sqrt (Nat.log 2 ((c₁ + c₂ + 2) * T))) + 2) + A := by + gcongr + exact Nat.div_sqrt_log_two_le_of_le hM + _ ≤ A * (2 * ((c₁ + c₂ + 2) * (T / Nat.sqrt (Nat.log 2 T)) + (c₁ + c₂ + 2)) + 2) + A := by + gcongr + exact Nat.mul_div_sqrt_log_two_le _ T + _ = _ := by ring + +/-- A polynomial in `n + 1 ≤ T + 1` is at most `2 ^ (2 e T / √⌊log₂ T⌋)`. -/ +private theorem add_one_pow_le {n T : ℕ} (e : ℕ) (hT : 2 ≤ T) (hn : n ≤ T) : + (n + 1) ^ e ≤ 2 ^ (2 * e * (T / Nat.sqrt (Nat.log 2 T))) := by + have h1 := Nat.one_le_div_sqrt_log_two hT + have h2 := Nat.log_two_le_div_sqrt_log_two T + calc (n + 1) ^ e ≤ (2 ^ (Nat.log 2 T + 1)) ^ e := + Nat.pow_le_pow_left ((Nat.succ_le_succ hn).trans (Nat.lt_pow_succ_log_self one_lt_two T)) e + _ = 2 ^ (e * (Nat.log 2 T + 1)) := by rw [← pow_mul, mul_comm] + _ ≤ _ := Nat.pow_le_pow_right (by norm_num) (by nlinarith) + +variable {α : Type} {enc : α ↪ List Bool} {T : ℕ → ℕ} + +/-- **Bounded-error quantum time `T` in time `2 ^ O(T / √log T)` and space `T ^ O(1)`.** For `T` +with `max n 2 ≤ T n`, every problem in `BQTIME[T]` is decided by one deterministic machine within +time `2 ^ (C T n / √⌊log₂ (T n)⌋)` and workspace `T n ^ d`: the machine computes the acceptance gap +of the circuit by tensor contraction and compares it with half of `2 ^ h`. -/ +theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T n) {L : Set α} + (hL : L ∈ BQTIME enc T) : + ∃ C d : ℕ, DecidableInTimeAndSpace L enc + (fun a => 2 ^ (C * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)))) + (fun a => T (enc a).length ^ d) := by + obtain ⟨F, c₁, c₂, hgates, hF⟩ := hL + have hgates' := F.computableInPolyTime_gates + have hwidth := F.computableInPolyTime_width + let gs : α → List GateCode := fun a => F.gates (enc a).length + let X : α → List Bool := fun a => + enc a ++ List.replicate (F.width (enc a).length - (enc a).length) false + have hgs : ComputableInPolyTime gs enc (encList encGateCode) := by dsimp only [gs]; fun_prop + have henc : ComputableInPolyTime (fun a => enc a) enc encBits := of_eq fun _ => rfl + have hX : ComputableInPolyTime X enc encBits := by dsimp only [X]; fun_prop + have hwf (a : α) : ∀ g ∈ gs a, g.WellFormed (X a).length := by + have : (X a).length = F.width (enc a).length := by + have := F.le_width (enc a).length + simp only [X, List.length_append, List.length_replicate] + omega + rw [this] + exact F.wellFormed _ + have hrule : ComputableInPolyTime (fun p : ℕ × ℕ => decide (2 ^ p.1 < 2 * p.2)) + (encPair encUnary encNat) encBool := by fun_prop + obtain ⟨A, e, hc⟩ := exists_computableInO_rule hgs hX hwf hrule + obtain ⟨K, hK, -⟩ := hc + -- correctness + have hcorrect (a : α) : a ∈ L ↔ + decide (2 ^ hadamardCount (gs a) < 2 * gapValue (gs a) (X a)) = true := by + have hp := acceptProb_eq_gapValue (F.width_pos (enc a).length) + (F.wellFormed (enc a).length) (F.le_width (enc a).length) + change F.acceptProb (enc a) = _ at hp + set g := gapValue (gs a) (X a) + set r := hadamardCount (gs a) + have hpos : (0 : ℝ) < 2 ^ r := by positivity + rw [decide_eq_true_iff] + by_cases ha : a ∈ L + · have := (hF a).1 ha + rw [hp, le_div_iff₀ hpos] at this + have h1 : (2 : ℝ) * 2 ^ r ≤ 3 * g := by linarith + have h2 : 2 * 2 ^ r ≤ 3 * g := by exact_mod_cast h1 + have h3 : 1 ≤ 2 ^ r := Nat.one_le_two_pow + exact ⟨fun _ => by omega, fun _ => ha⟩ + · have := (hF a).2 ha + rw [hp, div_le_iff₀ hpos] at this + have h1 : (3 : ℝ) * g ≤ 2 ^ r := by linarith + have h2 : 3 * g ≤ 2 ^ r := by exact_mod_cast h1 + have h3 : 1 ≤ 2 ^ r := Nat.one_le_two_pow + exact ⟨fun h => absurd h ha, fun h => by omega⟩ + -- the bounds + set C₀ := 2 * A * (c₁ + c₂ + 2) + 2 * e + set D₀ := 2 * A * (c₁ + c₂ + 2) + 3 * A + refine ⟨(K * 2 ^ D₀ + K + 1) * (C₀ + 1), K + K + 2 * e, + decidableInTimeAndSpace_iff.mpr ⟨_, hK.mono (fun a => ?_) (fun a => ?_), hcorrect⟩⟩ + all_goals + have hT2 : 2 ≤ T (enc a).length := (le_max_right _ _).trans (hTn _) + have hnT : (enc a).length ≤ T (enc a).length := (le_max_left _ _).trans (hTn _) + · -- time + set S := T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) + have hE := exponent_le_of_le A c₁ c₂ hT2 (hgates (enc a).length) + have hY := add_one_pow_le e hT2 hnT + have hZ : 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * + ((enc a).length + 1) ^ e ≤ 2 ^ D₀ * 2 ^ (C₀ * S) := by + calc _ ≤ 2 ^ (2 * A * (c₁ + c₂ + 2) * S + D₀) * 2 ^ (2 * e * S) := + Nat.mul_le_mul (Nat.pow_le_pow_right (by norm_num) hE) hY + _ = 2 ^ D₀ * 2 ^ (C₀ * S) := by + rw [← pow_add, ← pow_add] + congr 1 + simp only [C₀] + ring + have hCS : C₀ * S ≤ C₀ * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) := + Nat.mul_div_le_mul_div_assoc _ _ _ + have hfinal := Nat.mul_two_pow_div_sqrt_log_two_add_le (a := K * 2 ^ D₀) (b := K) (C := C₀) + hT2 + calc K * (2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * + ((enc a).length + 1) ^ e + 1) + ≤ K * (2 ^ D₀ * 2 ^ (C₀ * T (enc a).length / + Nat.sqrt (Nat.log 2 (T (enc a).length))) + 1) := + Nat.mul_le_mul_left _ (Nat.add_le_add_right (hZ.trans (Nat.mul_le_mul_left _ + (Nat.pow_le_pow_right (by norm_num) hCS))) _) + _ = K * 2 ^ D₀ * 2 ^ (C₀ * T (enc a).length / + Nat.sqrt (Nat.log 2 (T (enc a).length))) + K := by ring + _ ≤ _ := hfinal + · -- space + have hpow : ((enc a).length + 1) ^ e ≤ (T (enc a).length + 1) ^ e := + Nat.pow_le_pow_left (by omega) e + have := Nat.mul_add_one_pow_add_le_pow (a := K) (b := K) (d := e) hT2 + calc K * (((enc a).length + 1) ^ e + 1) ≤ K * (T (enc a).length + 1) ^ e + K := by + rw [Nat.mul_add, Nat.mul_one] + exact Nat.add_le_add_right (Nat.mul_le_mul_left _ hpow) _ + _ ≤ _ := this + +/-- **`BQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]`** for `T` with `max n 2 ≤ T n`. -/ +theorem BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (hTn : ∀ n, max n 2 ≤ T n) : + BQTIME enc T ⊆ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := by + intro L hL + obtain ⟨C, d, h⟩ := exists_decidableInTimeAndSpace_of_mem_BQTIME hTn hL + exact Set.mem_iUnion.mpr ⟨C, 1, 0, fun n => T n ^ d, + ComputableInTimeAndSpace.mono h (fun a => by rw [Nat.one_mul, Nat.add_zero]) fun _ => le_rfl⟩ + end Cslib.Complexity.QuantumContraction From 64e6c91ee2dfce8d72026c812453961bfcf05c60 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:47:35 -0400 Subject: [PATCH 07/13] test(Quantum): BQTIME and the contraction simulator Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 + .../Circuit/Quantum/PathNetworkPolyTime.lean | 8 +- ComplexityTests/BQPTensorContraction.lean | 109 ++++++++++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 ComplexityTests/BQPTensorContraction.lean diff --git a/Complexity.lean b/Complexity.lean index 239c5ca16..4a3a29c29 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1034,6 +1034,7 @@ public import Complexity.Computability.Circuit.Quantum.Code public import Complexity.Computability.Circuit.Quantum.Counter public import Complexity.Computability.Circuit.Quantum.Parallel public import Complexity.Computability.Circuit.Quantum.PathNetwork +public import Complexity.Computability.Circuit.Quantum.PathNetworkPolyTime public import Complexity.Computability.Circuit.Quantum.PathSum public import Complexity.Computability.Circuit.Quantum.Reversible public import Complexity.Computability.Circuit.Quantum.Uniform @@ -1148,6 +1149,8 @@ public import Complexity.Computability.Complexity.Classes.BQP public import Complexity.Computability.Complexity.Classes.BQP.Amplification public import Complexity.Computability.Complexity.Classes.BQP.Classical public import Complexity.Computability.Complexity.Classes.BQP.Counting +public import Complexity.Computability.Complexity.Classes.BQP.TensorContraction +public import Complexity.Computability.Complexity.Classes.BQP.Time public import Complexity.Computability.Complexity.Classes.BooleanHierarchy public import Complexity.Computability.Complexity.Classes.BooleanHierarchy.Closure public import Complexity.Computability.Complexity.Classes.BooleanHierarchy.Kadin diff --git a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean index 10eb26120..861b088b3 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean @@ -84,7 +84,8 @@ private theorem inBit_eq (X : List Bool) (gs : List GateCode) (c i idx : ℕ) : private theorem scalarVertex_eq (X : List Bool) (gs : List GateCode) : (scalarVertex X gs : List ℕ × List ℕ) = - ([], [if ((occurrences gs).any fun q => decide (q = 0)) || X.getD 0 false then 1 else 0]) := by + ([], [if ((occurrences gs).any fun q => decide (q = 0)) || X.getD 0 false then 1 + else 0]) := by simp only [scalarVertex, List.any_eq_true, decide_eq_true_eq, exists_eq_right, Bool.or_eq_true] /-- The encoding of networks is the encoding of their number of edges and vertices. -/ @@ -203,8 +204,9 @@ theorem computableInPolyTime_gateVertex {neg : α → ℕ} {X : α → List Bool refine ite_decide (decide_eq' ((fst _ _).comp hg) (const true)) ?_ ?_ · exact ite_decide (decide_eq' (bool_and' ((headD false).comp hins) ((headD false).comp houts)) (const true)) hneg (const 1) - · exact ite_decide (decide_eq' houts (cons' (bool_xor.comp (((headD false).comp hins).prod - (allWithInput ((tail (encA := encBool)).comp hins) (snd _ _)))) ((tail (encA := encBool)).comp hins))) (const 1) (const 0) + · have htl := (tail (encA := encBool)).comp hins + exact ite_decide (decide_eq' houts (cons' (bool_xor.comp (((headD false).comp hins).prod + (allWithInput htl (snd _ _)))) htl)) (const 1) (const 0) /-- **Glue vertices are polynomial-time.** -/ theorem computableInPolyTime_glueVertex {w : α → ℕ} (hw : ComputableInPolyTime w inp encUnary) : diff --git a/ComplexityTests/BQPTensorContraction.lean b/ComplexityTests/BQPTensorContraction.lean new file mode 100644 index 000000000..dd90bb0c7 --- /dev/null +++ b/ComplexityTests/BQPTensorContraction.lean @@ -0,0 +1,109 @@ +/- +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.BQP.TensorContraction + +/-! Quantum circuits by tensor contraction: the bipartition and admissibility of the network of a +circuit, the value the simulator computes on small circuits, a member of `BQTIME`, and the class +containment applied to it. -/ + +namespace ComplexityTests.BQPTensorContraction + +open Cslib Cslib.Quantum Cslib.Quantum.PathNetwork Cslib.Complexity + Cslib.Complexity.QuantumContraction Turing.MultiTapeTM + +/-! ### The bipartition -/ + +-- A Hadamard gate copied by a CNOT: every edge joins a row to a column. +example : (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).2.IsRowsFirstBipartite + (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).1 := by decide + +example : (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).2.IsRowsFirstBipartite + (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).1 := + isRowsFirstBipartite_network _ _ (by decide) + +-- The hypothesis that the qubits of a gate are distinct is needed: a CNOT whose control is its +-- target reads the same link edge twice. +example : ¬(network (-1 : ℤ) [false, false] [(true, 0, []), (false, 0, [0])]).2.IsRowsFirstBipartite + (network (-1 : ℤ) [false, false] [(true, 0, []), (false, 0, [0])]).1 := by decide + +-- A Toffoli gate: three occurrences, so twelve edges, admissible at size twelve. +example : CountingSimulation.ExplicitAdmissible 6 12 + (network 7 [false, false, false] [(false, 2, [0, 1])]) := + explicitAdmissible_network 7 [false, false, false] (by decide) (by decide) + +/-! ### The simulator -/ + +-- One Hadamard gate on `|0⟩`: the gap is `1`, so the acceptance probability is `1 / 2`. +example : gapValue [(true, 0, [])] [false] = 1 := by decide + +example : acceptProb 1 [(true, 0, [])] [false] = 1 / 2 := by + rw [acceptProb_eq_gapValue (by decide) (by decide) (by decide)] + rw [show gapValue [(true, 0, [])] ([false] ++ List.replicate (1 - [false].length) false) = 1 by + decide] + norm_num [hadamardCount] + +-- Without gates, the simulator reads qubit `0`. +example : gapValue [] [true, false] = 1 := by decide +example : gapValue [] [false, true] = 0 := by decide + +-- Two Hadamard gates on `|1⟩` interfere: the gap is `2 ^ 2`. +example : gapValue [(true, 0, []), (true, 0, [])] [true] = 4 := by + rw [gapValue_eq (by decide) (by decide)] + decide + +/-! ### A member of `BQTIME` -/ + +/-- The family with one qubit more than the input and no gates: it measures the first input bit. -/ +def readFirst : UniformCircuitFamily where + width n := n + 1 + gates _ := [] + computableInPolyTime_width := by fun_prop + computableInPolyTime_gates := ComputableInPolyTime.const _ + le_width n := Nat.le_succ n + width_pos n := Nat.succ_pos n + wellFormed _ _ h := by simp at h + +theorem acceptProb_readFirst (x : List Bool) : + readFirst.acceptProb x = if x.headD false then 1 else 0 := by + change measureProb (Circuit.run (Circuit.ofCodes (x.length + 1) []) (inputState _ x)) + (accepting _) = _ + simp only [Circuit.ofCodes_nil, Circuit.run, Circuit.eval_nil, measureProb_ket, + mem_accepting (Nat.succ_pos _), inputState] + cases x <;> rfl + +/-- The words whose first bit is one. -/ +def FirstBit : Set (List Bool) := {x | x.headD false = true} + +/-- Deciding the first bit takes no gates. -/ +theorem firstBit_mem_BQTIME (t : ℕ → ℕ) : FirstBit ∈ BQTIME encBits t := by + refine ⟨readFirst, 0, 0, fun n => by simp [readFirst], fun x => ⟨fun hx => ?_, fun hx => ?_⟩⟩ + · rw [encBits_apply, acceptProb_readFirst] + simp only [show x.headD false = true from hx, ite_true] + norm_num + · rw [encBits_apply, acceptProb_readFirst] + simp only [show x.headD false = false from Bool.eq_false_iff.mpr hx, Bool.false_eq_true, + ite_false] + norm_num + +example : FirstBit ∈ BQP encBits := BQTIME_subset_BQP _ (firstBit_mem_BQTIME fun _ => 0) + +example : FirstBit ∈ BQP encBits := + BQP_eq_iUnion_BQTIME ▸ Set.mem_iUnion.mpr ⟨1, firstBit_mem_BQTIME _⟩ + +-- The containment applies with the clock `T n = n + 2`, which satisfies `max n 2 ≤ T n`. +example : FirstBit ∈ ⋃ C, DTIME encBits fun n => + 2 ^ (C * (n + 2) / Nat.sqrt (Nat.log 2 (n + 2))) := + BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (T := fun n => n + 2) + (fun n => max_le (by omega) (by omega)) (firstBit_mem_BQTIME _) + +example : ∃ C d : ℕ, DecidableInTimeAndSpace FirstBit encBits + (fun x => 2 ^ (C * ((encBits x).length + 2) / Nat.sqrt (Nat.log 2 ((encBits x).length + 2)))) + (fun x => ((encBits x).length + 2) ^ d) := + exists_decidableInTimeAndSpace_of_mem_BQTIME (T := fun n => n + 2) + (fun n => max_le (by omega) (by omega)) (firstBit_mem_BQTIME _) + +end ComplexityTests.BQPTensorContraction From 43bf8d30cb646e8af6880fcd532593c47d1150d8 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 11:49:27 -0400 Subject: [PATCH 08/13] doc(Quantum): list the bipartition results of the path network Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity/Computability/Circuit/Quantum/PathNetwork.lean | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index c15439986..526a4f814 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -88,6 +88,10 @@ the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` mo `Cslib.Quantum.PathNetwork.length_table_network`, `Cslib.Quantum.PathNetwork.length_occurrences_le`: `4 N` edges for `N ≤ 3 L` occurrences of the `L` gates, degree at most six and full tables. +* `Cslib.Quantum.PathNetwork.exists_base_le_lt`, `Cslib.Quantum.PathNetwork.sum_readers`, + `Cslib.Quantum.PathNetwork.isRowsFirstBipartite_network`: every occurrence lies in one gate and + every link edge is read by one gate or its glue vertex, so the network is rows-first bipartite + when the qubits of every gate are distinct. ## References From 2b7fa1251324b7292f6172d6bfa7b46914d21007 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 12:58:53 -0400 Subject: [PATCH 09/13] refactor(TensorNetwork): share explicit-network tables and the indicator product Address review on #2058. - New `Frontier/TensorNetwork/ExplicitTables.lean`: `eqTable`, `mapTables`, `map_contraction` and `contraction_append_pairs` move there from `Quantum/PathNetwork.lean`, and `ClauseNetwork.boundedTable` with its lemmas moves there as `ExplicitTensorNetwork.boundedTable`, generalized over the entry type. `PathNetwork.table`, `table_eq` and `entry_table` are replaced by `boundedTable 6`. - `List.prod_map_boole` (new `Foundations/Data/List/Boole.lean`) replaces `ExplicitTensorNetwork.prod_map_boole` and the private copy in `ClauseNetwork`. - `PathNetwork.qubits` binds the gate components by name. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../Frontier/TensorNetwork/ClauseNetwork.lean | 48 +---- .../TensorNetwork/ClauseNetworkPolyTime.lean | 1 + .../TensorNetwork/ExplicitTables.lean | 195 ++++++++++++++++++ .../Circuit/Quantum/PathNetwork.lean | 175 ++-------------- Complexity/Foundations/Data/List/Boole.lean | 40 ++++ 6 files changed, 260 insertions(+), 201 deletions(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTables.lean create mode 100644 Complexity/Foundations/Data/List/Boole.lean diff --git a/Complexity.lean b/Complexity.lean index f8e96746a..09adf2098 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -938,6 +938,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedC public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedLift public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTables public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionDerivation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionLeaf @@ -2881,6 +2882,7 @@ public import Complexity.Foundations.Data.Int.Residue public import Complexity.Foundations.Data.List.Append public import Complexity.Foundations.Data.List.BalancedFold public import Complexity.Foundations.Data.List.Basic +public import Complexity.Foundations.Data.List.Boole public import Complexity.Foundations.Data.List.Counting public import Complexity.Foundations.Data.List.Equiv public import Complexity.Foundations.Data.List.EraseDups diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetwork.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetwork.lean index 4b8ceb844..8d1203092 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetwork.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetwork.lean @@ -7,8 +7,9 @@ Authors: Samuel Schlesinger module -- upstream: candidate -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTables public import Complexity.Logics.Propositional.CNF.ModelCount +import Complexity.Foundations.Data.List.Boole import Mathlib.Data.List.GetD /-! @@ -40,9 +41,9 @@ equality vertices have degree at most four (`ClauseNetwork.length_qPorts_le`), w Theorem D.1 needs, and the subdivision is built in. It does not merge repeated variables of a clause; their edges are joined by the chain. -The tables are listed from the first `2 ^ B` numbers (`ClauseNetwork.boundedTable`), for a bound -`B` on the degrees, so that the network is computed from constant-length lists; for the clause -network of width `k` formulas, `B = max k 4` (`ClauseNetwork.clauseNetwork`). +The tables are listed from the first `2 ^ B` numbers (`ExplicitTensorNetwork.boundedTable`), for +a bound `B` on the degrees, so that the network is computed from constant-length lists; for the +clause network of width `k` formulas, `B = max k 4` (`ClauseNetwork.clauseNetwork`). ## Main definitions @@ -74,6 +75,8 @@ public section namespace Cslib.Circuits.Frontier.ClauseNetwork +open ExplicitTensorNetwork (boundedTable length_boundedTable entry_boundedTable) + open Cslib.Logic.PL /-! ### Occurrences -/ @@ -129,11 +132,6 @@ end. -/ /-! ### The network -/ -/-- A table of the `2 ^ w` values `f 0, …, f (2 ^ w - 1)` when `w ≤ B`, listed by keeping the -numbers below `2 ^ w` among the first `2 ^ B`. -/ -@[expose] def boundedTable (B w : ℕ) (f : ℕ → ℕ) : List ℕ := - ((List.range (2 ^ B)).filter fun idx => idx < 2 ^ w).map f - /-- The equality tensor on `w` ports: one at the all-`false` and the all-`true` words. -/ @[expose] def eqEntry (w idx : ℕ) : ℕ := if idx = 0 ∨ idx + 1 = 2 ^ w then 1 else 0 @@ -250,28 +248,6 @@ theorem eq_of_prev_eq {w u u' : ℕ} (hu : prev os u = w) (hu' : prev os u' = w) /-! ### Tables -/ -/-- For `w ≤ B`, the bounded table is the table of `f` on `0, …, 2 ^ w - 1`. -/ -theorem boundedTable_eq {B w : ℕ} (f : ℕ → ℕ) (h : w ≤ B) : - boundedTable B w f = (List.range (2 ^ w)).map f := by - have hle : 2 ^ w ≤ 2 ^ B := Nat.pow_le_pow_right (by norm_num) h - unfold boundedTable - congr 1 - obtain ⟨c, hc⟩ := Nat.exists_eq_add_of_le hle - rw [hc, List.range_add, List.filter_append, List.filter_eq_self.mpr (by simp), - List.filter_eq_nil_iff.mpr (by simp), List.append_nil] - -theorem length_boundedTable {B w : ℕ} (f : ℕ → ℕ) (h : w ≤ B) : - (boundedTable B w f).length = 2 ^ w := by - simp [boundedTable_eq f h] - -/-- Looking up a bounded table at a word of length at most `B` evaluates its function at the -value of the word. -/ -theorem entry_boundedTable {B : ℕ} (f : ℕ → ℕ) {bs : List Bool} (h : bs.length ≤ B) : - ExplicitTensorNetwork.entry (boundedTable B bs.length f) bs = f (BitString.lsbValue bs) := by - rw [ExplicitTensorNetwork.entry, boundedTable_eq f h, - List.getD_eq_getElem _ _ (by simpa using BitString.lsbValue_lt bs)] - simp - private theorem eq_replicate_or_iff (bs : List Bool) : (bs = List.replicate bs.length false ∨ bs = List.replicate bs.length true) ↔ ∀ a ∈ bs, ∀ b ∈ bs, a = b := by @@ -530,14 +506,6 @@ theorem length_table_network (B m : ℕ) (hB : ∀ i, (os.map Prod.fst).count i /-! ### The contraction -/ -private theorem prod_map_boole {α : Type*} (l : List α) (p : α → Prop) [DecidablePred p] : - (l.map fun a => if p a then (1 : ℕ) else 0).prod = if ∀ a ∈ l, p a then 1 else 0 := by - induction l with - | nil => simp - | cons a l ih => - rw [List.map_cons, List.prod_cons, ih] - by_cases ha : p a <;> simp [ha] - /-- The bits on the edges, read as a function of the edge numbers. -/ private def bitAt {E : ℕ} (x : Fin E → Bool) (j : ℕ) : Bool := if h : j < E then x ⟨j, h⟩ else false @@ -604,7 +572,7 @@ private theorem contraction_network_eq (B m : ℕ) (hB : ∀ i, (os.map Prod.fst if ∀ a ∈ qPorts os w, ∀ b ∈ qPorts os w, bitAt x a = bitAt x b then 1 else 0 := List.map_congr_left fun w _ => entry_qVertex os (bitAt x) ((length_qPorts_le w).trans (le_max_right _ _)) - rw [h1, h2, h3, prod_map_boole, prod_map_boole, prod_map_boole] + rw [h1, h2, h3, List.prod_map_boole, List.prod_map_boole, List.prod_map_boole] clear h1 h2 h3 hpb simp only [List.mem_range, Good] split_ifs <;> first | rfl | (exfalso; tauto) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean index 200113f80..95607eaba 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean @@ -48,6 +48,7 @@ namespace Cslib.Circuits.Frontier.ClauseNetwork open Turing.MultiTapeTM ComputableInPolyTime Cslib.Logic.PL open Cslib.Complexity (encCNF) +open ExplicitTensorNetwork (boundedTable) variable {α : Type} {inp : α ↪ List Bool} diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTables.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTables.lean new file mode 100644 index 000000000..e9c39295e --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTables.lean @@ -0,0 +1,195 @@ +/- +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.Frontier.TensorNetwork.Explicit +import Complexity.Foundations.Data.List.Boole +import Mathlib.Data.List.GetD + +/-! +# Tables of explicit tensor networks + +Tables for the vertices of an `ExplicitTensorNetwork`, and two contraction identities for +networks assembled from them. + +A *bounded table* lists the values `f 0, …, f (2 ^ w - 1)` of a function on the words of length +`w`, by keeping the numbers below `2 ^ w` among the first `2 ^ B` for a bound `w ≤ B` +(`ExplicitTensorNetwork.boundedTable`), so that a network with degrees at most `B` is computed +from constant-length lists. Looking it up at a word evaluates `f` at the value of the word +(`ExplicitTensorNetwork.entry_boundedTable`). The equality table on two ports +(`ExplicitTensorNetwork.eqTable`) joins two edges into one bit: equality vertices on consecutive +pairs of edges reduce the contraction to a sum over one bit per pair +(`ExplicitTensorNetwork.contraction_append_pairs`). Mapping every table entry by a semiring +homomorphism maps the contraction (`ExplicitTensorNetwork.map_contraction`). + +## Main definitions + +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.boundedTable`: the table of a function on the + words of length `w`, for a bound `w ≤ B`. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.eqTable`: the equality table on two ports. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.mapTables`: maps of the table entries. + +## Main results + +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.boundedTable_eq`, + `Cslib.Circuits.Frontier.ExplicitTensorNetwork.length_boundedTable`, + `Cslib.Circuits.Frontier.ExplicitTensorNetwork.entry_boundedTable`: bounded tables. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.contraction_append_pairs`: equality vertices on + consecutive pairs of edges reduce the contraction to a sum over one bit per pair. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.map_contraction`: the contraction commutes with + maps of semirings applied to the tables. +-/ + +public section + +namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork + +/-! ### Bounded tables -/ + +section BoundedTable + +variable {R : Type*} + +/-- A table of the `2 ^ w` values `f 0, …, f (2 ^ w - 1)` when `w ≤ B`, listed by keeping the +numbers below `2 ^ w` among the first `2 ^ B`. -/ +@[expose] def boundedTable (B w : ℕ) (f : ℕ → R) : List R := + ((List.range (2 ^ B)).filter fun idx => idx < 2 ^ w).map f + +/-- For `w ≤ B`, the bounded table is the table of `f` on `0, …, 2 ^ w - 1`. -/ +theorem boundedTable_eq {B w : ℕ} (f : ℕ → R) (h : w ≤ B) : + boundedTable B w f = (List.range (2 ^ w)).map f := by + have hle : 2 ^ w ≤ 2 ^ B := Nat.pow_le_pow_right (by norm_num) h + unfold boundedTable + congr 1 + obtain ⟨c, hc⟩ := Nat.exists_eq_add_of_le hle + rw [hc, List.range_add, List.filter_append, List.filter_eq_self.mpr (by simp), + List.filter_eq_nil_iff.mpr (by simp), List.append_nil] + +theorem length_boundedTable {B w : ℕ} (f : ℕ → R) (h : w ≤ B) : + (boundedTable B w f).length = 2 ^ w := by + simp [boundedTable_eq f h] + +/-- Looking up a bounded table at a word of length at most `B` evaluates its function at the +value of the word. -/ +theorem entry_boundedTable [Zero R] {B : ℕ} (f : ℕ → R) {bs : List Bool} (h : bs.length ≤ B) : + entry (boundedTable B bs.length f) bs = f (BitString.lsbValue bs) := by + rw [entry, boundedTable_eq f h, + List.getD_eq_getElem _ _ (by simpa using BitString.lsbValue_lt bs)] + simp + +end BoundedTable + +/-! ### Equality vertices and maps of tables -/ + +/-- The equality table on two ports. -/ +@[expose] def eqTable {R : Type*} [Zero R] [One R] : List R := [1, 0, 0, 1] + +theorem entry_eqTable {R : Type*} [Zero R] [One R] (a b : Bool) : + entry (eqTable : List R) [a, b] = if a = b then 1 else 0 := by + cases a <;> cases b <;> rfl + +/-- **Equality vertices on pairs of edges.** If the last vertices of a network with `2 W` edges are +`W` equality vertices, vertex `w` joining the edges `2 w` and `2 w + 1`, then the contraction is the +sum over one bit `y w` per pair of the product of the other entries, where edge `e` carries +`y (e / 2)`. -/ +theorem contraction_append_pairs {R : Type*} [CommSemiring R] (W : ℕ) + (rows : List (List ℕ × List R)) : + contraction (⟨2 * W, rows ++ (List.range W).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩ : + ExplicitTensorNetwork R) = + ∑ y : Fin W → Bool, (rows.map fun v => entry v.2 + (v.1.map fun e => if h : e / 2 < W then y ⟨e / 2, h⟩ else false)).prod := by + classical + set G : ExplicitTensorNetwork R := + ⟨2 * W, rows ++ (List.range W).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩ + -- an assignment that repeats each bit of `y` on the two edges of its pair + let dup : (Fin W → Bool) → Fin (2 * W) → Bool := fun y e => y ⟨e / 2, by omega⟩ + have hdup : Function.Injective dup := by + intro y y' h + funext w + have := congrFun h ⟨2 * w, by omega⟩ + simpa [dup, show 2 * (w : ℕ) / 2 = w by omega] using this + have hpb (x : Fin (2 * W) → Bool) (ps : List ℕ) : + G.portBits x ps = ps.map fun i => if h : i < 2 * W then x ⟨i, h⟩ else false := rfl + -- the weight of an assignment + let F : (Fin (2 * W) → Bool) → R := fun x => + (G.vertices.map fun v => entry v.2 (G.portBits x v.1)).prod + have hcol (x : Fin (2 * W) → Bool) : + (((List.range W).map fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List R)).map + fun v => entry v.2 (G.portBits x v.1)).prod = + if ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ then 1 else 0 := by + rw [List.map_map] + have h1 : ∀ w ∈ List.range W, ((fun v => entry v.2 (G.portBits x v.1)) ∘ + fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List R)) w = + if (∀ h : w < W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩) then 1 else 0 := by + intro w hw + have hw' : w < W := List.mem_range.mp hw + simp only [Function.comp_apply, hpb, List.map_cons, List.map_nil, + show 2 * w < 2 * W by omega, show 2 * w + 1 < 2 * W by omega, dite_true, entry_eqTable] + simp [hw'] + rw [List.map_congr_left h1] + have key := List.prod_map_boole (M₀ := R) (List.range W) + (fun w => ∀ h : w < W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩) + rw [key] + congr 1 + exact propext ⟨fun h w => h w (List.mem_range.mpr w.2) w.2, + fun h w _ hw => h ⟨w, hw⟩⟩ + have hF (x : Fin (2 * W) → Bool) : F x = + (rows.map fun v => entry v.2 (G.portBits x v.1)).prod * + if ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ then 1 else 0 := by + simp only [F, G, List.map_append, List.prod_append] + rw [← hcol] + -- assignments off the image of `dup` have weight zero + have hzero (x : Fin (2 * W) → Bool) (hx : x ∉ Finset.univ.image dup) : F x = 0 := by + rw [hF] + have : ¬ ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ := by + intro h + refine hx (Finset.mem_image.mpr ⟨fun w => x ⟨2 * w, by omega⟩, Finset.mem_univ _, ?_⟩) + funext e + obtain ⟨e, he⟩ := e + simp only [dup] + rcases Nat.even_or_odd' e with ⟨w, rfl | rfl⟩ + · exact congrArg x (Fin.ext (by change 2 * (2 * w / 2) = 2 * w; omega)) + · refine (congrArg x (Fin.ext ?_)).trans (h ⟨w, by omega⟩) + change 2 * ((2 * w + 1) / 2) = 2 * w + omega + rw [ite_eq_right this, mul_zero] + change ∑ x, F x = _ + rw [← Finset.sum_subset (Finset.subset_univ (Finset.univ.image dup)) fun x _ hx => hzero x hx, + Finset.sum_image fun y _ y' _ h => hdup h] + refine Finset.sum_congr rfl fun y _ => ?_ + rw [hF, ite_eq_left fun w => by simp [dup, show 2 * (w : ℕ) / 2 = w by omega, + show (2 * (w : ℕ) + 1) / 2 = w by omega], mul_one] + congr 1 + refine List.map_congr_left fun v _ => ?_ + rw [hpb] + congr 1 + refine List.map_congr_left fun e _ => ?_ + by_cases he : e < 2 * W + · simp [he, dup, show e / 2 < W by omega] + · simp [he, show ¬ e / 2 < W by omega] + +/-- The network with every table entry mapped by `ψ`. -/ +@[expose] def mapTables {R S : Type*} (ψ : R → S) (d : ExplicitTensorNetwork R) : + ExplicitTensorNetwork S := + ⟨d.numEdges, d.vertices.map fun v => (v.1, v.2.map ψ)⟩ + +/-- **The contraction commutes with maps of semirings** applied to the tables. -/ +theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) + (d : ExplicitTensorNetwork R) : ψ d.contraction = (d.mapTables ψ).contraction := by + rw [contraction, map_sum] + refine Finset.sum_congr rfl fun x _ => ?_ + rw [map_list_prod, List.map_map] + simp only [mapTables, List.map_map] + congr 1 + refine List.map_congr_left fun v _ => ?_ + simp only [Function.comp_apply, entry] + rw [← map_zero ψ, List.getD_map] + rfl + +end Cslib.Circuits.Frontier.ExplicitTensorNetwork + diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index 27d9b8060..9cb35319d 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -8,7 +8,8 @@ module -- upstream: candidate public import Complexity.Computability.Circuit.Quantum.PathSum -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTables +import Complexity.Foundations.Data.List.Boole import Mathlib.Data.List.GetD import Mathlib.Data.List.OfFn import Mathlib.Logic.Equiv.Fin.Basic @@ -46,7 +47,9 @@ occurrences from `base gs i` on. The last occurrence of qubit `j` before positio tensor. So every edge joins a row to a column, every vertex has at most six ports, and untouched qubits -cost nothing. With `neg = -1`, an assignment of nonzero weight is a pair of paths of the path sum +cost nothing. The gate tables are bounded tables for the degree bound six +(`Cslib.Circuits.Frontier.ExplicitTensorNetwork.boundedTable`). With `neg = -1`, an assignment of +nonzero weight is a pair of paths of the path sum (`Cslib.Quantum.pathRun`) ending in the same register with qubit `0` equal to one, and its weight is the product of their signs, so the contraction is `acceptGap` (`Cslib.Quantum.PathNetwork.contraction_network`). The network is defined for any value `neg` of @@ -55,13 +58,9 @@ the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` mo ## Main definitions -* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.eqTable`, - `Cslib.Circuits.Frontier.ExplicitTensorNetwork.mapTables`: the equality table on two ports and - maps of the table entries. * `Cslib.Quantum.PathNetwork.qubits`, `Cslib.Quantum.PathNetwork.occurrences`, `Cslib.Quantum.PathNetwork.base`, `Cslib.Quantum.PathNetwork.lastOcc`: occurrences of qubits. -* `Cslib.Quantum.PathNetwork.gateEntry`, `Cslib.Quantum.PathNetwork.table`: the tensor of a gate - and the tables of the vertices. +* `Cslib.Quantum.PathNetwork.gateEntry`: the tensor of a gate. * `Cslib.Quantum.PathNetwork.inPort`, `Cslib.Quantum.PathNetwork.gatePorts`, `Cslib.Quantum.PathNetwork.inBit`, `Cslib.Quantum.PathNetwork.gateVertex`, `Cslib.Quantum.PathNetwork.acceptTable`, `Cslib.Quantum.PathNetwork.glueVertex`, @@ -70,10 +69,6 @@ the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` mo ## Main results -* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.contraction_append_pairs`: equality vertices on - consecutive pairs of edges reduce the contraction to a sum over one bit per pair. -* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.map_contraction`: the contraction commutes with - maps of semirings applied to the tables. * `Cslib.Quantum.PathNetwork.lastOcc_succ`, `Cslib.Quantum.PathNetwork.lastOcc_eq_some`, `Cslib.Quantum.PathNetwork.lastOcc_eq_none`: last occurrences. * `Cslib.Quantum.PathNetwork.contraction_network`: with the Hadamard sign `-1`, the contraction @@ -96,125 +91,6 @@ the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` mo public section -namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork - -/-- A product of indicators is the indicator of the conjunction. -/ -theorem prod_map_boole {R : Type*} [CommSemiring R] {α : Type*} (l : List α) (p : α → Prop) - [DecidablePred p] : - (l.map fun a => if p a then (1 : R) else 0).prod = if ∀ a ∈ l, p a then 1 else 0 := by - induction l with - | nil => simp - | cons a l ih => - rw [List.map_cons, List.prod_cons, ih] - by_cases ha : p a <;> simp [ha] - -/-- The equality table on two ports. -/ -@[expose] def eqTable {R : Type*} [Zero R] [One R] : List R := [1, 0, 0, 1] - -theorem entry_eqTable {R : Type*} [Zero R] [One R] (a b : Bool) : - entry (eqTable : List R) [a, b] = if a = b then 1 else 0 := by - cases a <;> cases b <;> rfl - -/-- **Equality vertices on pairs of edges.** If the last vertices of a network with `2 W` edges are -`W` equality vertices, vertex `w` joining the edges `2 w` and `2 w + 1`, then the contraction is the -sum over one bit `y w` per pair of the product of the other entries, where edge `e` carries -`y (e / 2)`. -/ -theorem contraction_append_pairs {R : Type*} [CommSemiring R] (W : ℕ) - (rows : List (List ℕ × List R)) : - contraction (⟨2 * W, rows ++ (List.range W).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩ : - ExplicitTensorNetwork R) = - ∑ y : Fin W → Bool, (rows.map fun v => entry v.2 - (v.1.map fun e => if h : e / 2 < W then y ⟨e / 2, h⟩ else false)).prod := by - classical - set G : ExplicitTensorNetwork R := - ⟨2 * W, rows ++ (List.range W).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩ - -- an assignment that repeats each bit of `y` on the two edges of its pair - let dup : (Fin W → Bool) → Fin (2 * W) → Bool := fun y e => y ⟨e / 2, by omega⟩ - have hdup : Function.Injective dup := by - intro y y' h - funext w - have := congrFun h ⟨2 * w, by omega⟩ - simpa [dup, show 2 * (w : ℕ) / 2 = w by omega] using this - have hpb (x : Fin (2 * W) → Bool) (ps : List ℕ) : - G.portBits x ps = ps.map fun i => if h : i < 2 * W then x ⟨i, h⟩ else false := rfl - -- the weight of an assignment - let F : (Fin (2 * W) → Bool) → R := fun x => - (G.vertices.map fun v => entry v.2 (G.portBits x v.1)).prod - have hcol (x : Fin (2 * W) → Bool) : - (((List.range W).map fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List R)).map - fun v => entry v.2 (G.portBits x v.1)).prod = - if ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ then 1 else 0 := by - rw [List.map_map] - have h1 : ∀ w ∈ List.range W, ((fun v => entry v.2 (G.portBits x v.1)) ∘ - fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List R)) w = - if (∀ h : w < W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩) then 1 else 0 := by - intro w hw - have hw' : w < W := List.mem_range.mp hw - simp only [Function.comp_apply, hpb, List.map_cons, List.map_nil, - show 2 * w < 2 * W by omega, show 2 * w + 1 < 2 * W by omega, dite_true, entry_eqTable] - simp [hw'] - rw [List.map_congr_left h1] - have key := prod_map_boole (R := R) (List.range W) - (fun w => ∀ h : w < W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩) - rw [key] - congr 1 - exact propext ⟨fun h w => h w (List.mem_range.mpr w.2) w.2, - fun h w _ hw => h ⟨w, hw⟩⟩ - have hF (x : Fin (2 * W) → Bool) : F x = - (rows.map fun v => entry v.2 (G.portBits x v.1)).prod * - if ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ then 1 else 0 := by - simp only [F, G, List.map_append, List.prod_append] - rw [← hcol] - -- assignments off the image of `dup` have weight zero - have hzero (x : Fin (2 * W) → Bool) (hx : x ∉ Finset.univ.image dup) : F x = 0 := by - rw [hF] - have : ¬ ∀ w : Fin W, x ⟨2 * w, by omega⟩ = x ⟨2 * w + 1, by omega⟩ := by - intro h - refine hx (Finset.mem_image.mpr ⟨fun w => x ⟨2 * w, by omega⟩, Finset.mem_univ _, ?_⟩) - funext e - obtain ⟨e, he⟩ := e - simp only [dup] - rcases Nat.even_or_odd' e with ⟨w, rfl | rfl⟩ - · exact congrArg x (Fin.ext (by change 2 * (2 * w / 2) = 2 * w; omega)) - · refine (congrArg x (Fin.ext ?_)).trans (h ⟨w, by omega⟩) - change 2 * ((2 * w + 1) / 2) = 2 * w - omega - rw [ite_eq_right this, mul_zero] - change ∑ x, F x = _ - rw [← Finset.sum_subset (Finset.subset_univ (Finset.univ.image dup)) fun x _ hx => hzero x hx, - Finset.sum_image fun y _ y' _ h => hdup h] - refine Finset.sum_congr rfl fun y _ => ?_ - rw [hF, ite_eq_left fun w => by simp [dup, show 2 * (w : ℕ) / 2 = w by omega, - show (2 * (w : ℕ) + 1) / 2 = w by omega], mul_one] - congr 1 - refine List.map_congr_left fun v _ => ?_ - rw [hpb] - congr 1 - refine List.map_congr_left fun e _ => ?_ - by_cases he : e < 2 * W - · simp [he, dup, show e / 2 < W by omega] - · simp [he, show ¬ e / 2 < W by omega] - -/-- The network with every table entry mapped by `ψ`. -/ -@[expose] def mapTables {R S : Type*} (ψ : R → S) (d : ExplicitTensorNetwork R) : - ExplicitTensorNetwork S := - ⟨d.numEdges, d.vertices.map fun v => (v.1, v.2.map ψ)⟩ - -/-- **The contraction commutes with maps of semirings** applied to the tables. -/ -theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) - (d : ExplicitTensorNetwork R) : ψ d.contraction = (d.mapTables ψ).contraction := by - rw [contraction, map_sum] - refine Finset.sum_congr rfl fun x _ => ?_ - rw [map_list_prod, List.map_map] - simp only [mapTables, List.map_map] - congr 1 - refine List.map_congr_left fun v _ => ?_ - simp only [Function.comp_apply, entry] - rw [← map_zero ψ, List.getD_map] - rfl - -end Cslib.Circuits.Frontier.ExplicitTensorNetwork - namespace Cslib.Quantum.PathNetwork open Cslib.Circuits.Frontier ExplicitTensorNetwork @@ -222,7 +98,8 @@ open Cslib.Circuits.Frontier ExplicitTensorNetwork /-! ### Occurrences -/ /-- The qubits of a gate code: its target, then its controls. -/ -@[expose] def qubits (g : GateCode) : List ℕ := g.2.1 :: g.2.2 +@[expose] def qubits : GateCode → List ℕ + | (_, target, controls) => target :: controls /-- The occurrences of qubits in a list of gate codes, gate by gate. -/ @[expose] def occurrences (gs : List GateCode) : List ℕ := gs.flatMap qubits @@ -247,11 +124,6 @@ classical gate it is `1` when the outputs are the gate applied to the inputs, an if g.1 then (if ins.headD false && outs.headD false then neg else 1) else if outs = (ins.headD false ^^ ins.tail.all id) :: ins.tail then 1 else 0 -/-- The table of the values `f 0, …, f (2 ^ w - 1)` for `w ≤ 6`, listed by keeping the positions -below `2 ^ w` among the first `2 ^ 6`. -/ -@[expose] def table (w : ℕ) (f : ℕ → R) : List R := - ((List.range (2 ^ 6)).filter fun idx => idx < 2 ^ w).map f - /-- The port at which a gate with first occurrence `b` reads the qubit `j` in copy `c`: the link edge of the last earlier occurrence of `j`, or none when the gate reads `j` from the input. -/ @[expose] def inPort (gs : List GateCode) (c b j : ℕ) : Option ℕ := @@ -273,7 +145,7 @@ of `j`, or the bit of its input port. -/ /-- The vertex of gate `i` in copy `c`, for the input register `X`. -/ @[expose] def gateVertex (neg : R) (X : List Bool) (gs : List GateCode) (c i : ℕ) : List ℕ × List R := - (gatePorts gs c i, table (gatePorts gs c i).length fun idx => + (gatePorts gs c i, boundedTable 6 (gatePorts gs c i).length fun idx => gateEntry neg (gs.getD i default) ((qubits (gs.getD i default)).map (inBit X gs c i idx)) ((List.range (qubits (gs.getD i default)).length).map idx.testBit)) @@ -638,26 +510,6 @@ section Entries variable {R : Type*} [Zero R] [One R] -omit [Zero R] [One R] in -/-- For `w ≤ 6`, the table lists `f` at the first `2 ^ w` positions. -/ -theorem table_eq {w : ℕ} (f : ℕ → R) (hw : w ≤ 6) : table w f = (List.range (2 ^ w)).map f := by - have hle : 2 ^ w ≤ 2 ^ 6 := Nat.pow_le_pow_right (by norm_num) hw - unfold table - congr 1 - obtain ⟨c, hc⟩ := Nat.exists_eq_add_of_le hle - rw [hc, List.range_add, List.filter_append, List.filter_eq_self.mpr (by simp), - List.filter_eq_nil_iff.mpr (by simp), List.append_nil] - -omit [One R] in -/-- Looking up a table at a word of length `w ≤ 6` evaluates its function at the value of the -word. -/ -theorem entry_table {w : ℕ} (f : ℕ → R) (hw : w ≤ 6) {bs : List Bool} (h : bs.length = w) : - entry (table w f) bs = f (BitString.lsbValue bs) := by - subst h - rw [entry, table_eq f hw, - List.getD_eq_getElem _ _ (by simpa using BitString.lsbValue_lt bs)] - simp - /-- A gate vertex has at most twice as many ports as its gate has qubits. -/ theorem length_gatePorts_le (gs : List GateCode) (c i : ℕ) : (gatePorts gs c i).length ≤ 2 * (qubits (gs.getD i default)).length := by @@ -678,7 +530,8 @@ private theorem entry_gateVertex (neg : R) (X : List Bool) (gs : List GateCode) fun k => Z (c * (occurrences gs).length + (base gs i + k))) := by have hps : (gatePorts gs c i).length ≤ 6 := (length_gatePorts_le gs c i).trans (by omega) simp only [gateVertex] - rw [entry_table _ hps (List.length_map _)] + rw [show (gatePorts gs c i).length = ((gatePorts gs c i).map fun e => Z (e / 2)).length by simp, + entry_boundedTable _ (by simpa using hps)] congr 1 · refine List.map_congr_left fun j hj => ?_ simp only [inBit, reg, inPort] @@ -920,7 +773,7 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List P (fun u => Z (0 * N + u)) * (P (fun u => Z (1 * N + u)) * if FinalOK X gs (reg qs Xf (fun u => Z (0 * N + u)) N) (reg qs Xf (fun u => Z (1 * N + u)) N) then 1 else 0) := by - simp only [List.map_append, List.prod_append, hP, hG, prod_map_boole, List.map_cons, + simp only [List.map_append, List.prod_append, hP, hG, List.prod_map_boole, List.map_cons, List.map_nil, List.prod_cons, List.prod_nil, mul_one] rw [show entry (scalarVertex X gs : List ℕ × List R).2 ((scalarVertex X gs).1.map fun e => Z (e / 2)) = if 0 ∈ qs ∨ X.getD 0 false = true then 1 else 0 from rfl] @@ -997,7 +850,7 @@ theorem network_mapTables {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : (network neg X gs).2.mapTables ψ = (network (ψ neg) X gs).2 := by have hgate (c i : ℕ) : (fun v : List ℕ × List R => (v.1, v.2.map ψ)) (gateVertex neg X gs c i) = gateVertex (ψ neg) X gs c i := by - simp only [gateVertex, table, List.map_map] + simp only [gateVertex, boundedTable, List.map_map] congr 2 funext idx simp only [Function.comp_apply, gateEntry] @@ -1116,7 +969,7 @@ theorem length_table_network (neg : R) (X : List Bool) {gs : List GateCode} (gateVertex neg X gs c i).2.length = 2 ^ (gateVertex neg X gs c i).1.length := by have := hgs _ (List.getD_eq_getElem _ default hi ▸ List.getElem_mem hi) simp only [gateVertex] - rw [table_eq _ ((length_gatePorts_le gs c i).trans (by omega)), List.length_map, + rw [boundedTable_eq _ ((length_gatePorts_le gs c i).trans (by omega)), List.length_map, List.length_range] rcases hv with (((⟨i, hi, rfl⟩ | ⟨i, hi, rfl⟩) | ⟨w, -, rfl⟩) | rfl) | ⟨w, -, rfl⟩ · exact hgate 0 i hi diff --git a/Complexity/Foundations/Data/List/Boole.lean b/Complexity/Foundations/Data/List/Boole.lean new file mode 100644 index 000000000..cdda03a8d --- /dev/null +++ b/Complexity/Foundations/Data/List/Boole.lean @@ -0,0 +1,40 @@ +/- +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.Init +public import Mathlib.Algebra.BigOperators.Group.List.Basic +public import Mathlib.Algebra.GroupWithZero.Defs + +/-! +# Products of indicators over a list + +In a monoid with zero, the product over a list of the indicators `if p a then 1 else 0` is the +indicator that `p` holds at every element of the list (`List.prod_map_boole`). This is the list +form of `Finset.prod_boole`. + +## Main results + +* `List.prod_map_boole`: a product of indicators is the indicator of the conjunction. +-/ + +public section + +namespace List + +/-- **A product of indicators is the indicator of the conjunction.** -/ +theorem prod_map_boole {M₀ α : Type*} [MonoidWithZero M₀] (l : List α) (p : α → Prop) + [DecidablePred p] : + (l.map fun a => if p a then (1 : M₀) else 0).prod = if ∀ a ∈ l, p a then 1 else 0 := by + induction l with + | nil => simp + | cons a l ih => + rw [List.map_cons, List.prod_cons, ih] + by_cases ha : p a <;> simp [ha] + +end List From bb23fd1b0fd93b91dfa232ec5f7bfa802d808619 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 13:12:41 -0400 Subject: [PATCH 10/13] refactor(BQP): share tables, list sums and scale estimates; name gapValue components Address review on #2072 (and carry #2058's fixes, merged at 2b7fa1251). - `gapValue` destructures the contraction input as `(precision, _, network)`. - `BQP/Time.lean` uses `Turing.MultiTapeTM.length_le_length_encList` instead of a private copy. - New `Frontier/TensorNetwork/ExplicitTablesPolyTime.lean`: `ExplicitTensorNetwork.computableInPolyTime_boundedTable` (moved from `ClauseNetworkPolyTime`) and `ExplicitTensorNetwork.ports_unary_to_binary`, shared by `ClauseNetworkPolyTime` and `PathNetworkPolyTime`. `PathNetwork.computableInPolyTime_table` and both private `binaryPorts` helpers are removed. - New `Foundations/Data/List/SumIndicator.lean` (`List.sum_map_range_ite_eq`, `List.sum_map_ite_and`) and `Foundations/Data/List/FilterMap.lean` (`List.filterMap_eq_map_filter`) replace private copies in `PathNetwork` and `PathNetworkPolyTime`. - New `Foundations/Data/Nat/SqrtLog/Linear.lean` (`Nat.mul_succ_div_sqrt_log_two_le_of_le`, `Nat.add_one_pow_le_two_pow_div_sqrt_log_two`) replaces the private estimates in `BQP/TensorContraction.lean`. - `Quantum.acceptGap_nil` is public in `Quantum/PathSum.lean`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 4 ++ .../TensorNetwork/ClauseNetworkPolyTime.lean | 25 ++------ .../TensorNetwork/ExplicitTablesPolyTime.lean | 59 +++++++++++++++++ .../Circuit/Quantum/PathNetwork.lean | 32 +++------- .../Circuit/Quantum/PathNetworkPolyTime.lean | 41 ++++-------- .../Circuit/Quantum/PathSum.lean | 8 +++ .../Classes/BQP/TensorContraction.lean | 44 ++----------- .../Complexity/Classes/BQP/Time.lean | 9 +-- .../Foundations/Data/List/FilterMap.lean | 38 +++++++++++ .../Foundations/Data/List/SumIndicator.lean | 49 +++++++++++++++ .../Foundations/Data/Nat/SqrtLog/Linear.lean | 63 +++++++++++++++++++ 11 files changed, 255 insertions(+), 117 deletions(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTablesPolyTime.lean create mode 100644 Complexity/Foundations/Data/List/FilterMap.lean create mode 100644 Complexity/Foundations/Data/List/SumIndicator.lean create mode 100644 Complexity/Foundations/Data/Nat/SqrtLog/Linear.lean diff --git a/Complexity.lean b/Complexity.lean index daa8f6c81..d6c92071e 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -939,6 +939,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedL public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTables +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTablesPolyTime public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionDerivation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionLeaf @@ -2893,6 +2894,7 @@ public import Complexity.Foundations.Data.List.Equiv public import Complexity.Foundations.Data.List.EraseDups public import Complexity.Foundations.Data.List.EraseDupsBy public import Complexity.Foundations.Data.List.Filter +public import Complexity.Foundations.Data.List.FilterMap public import Complexity.Foundations.Data.List.Fold public import Complexity.Foundations.Data.List.Last public import Complexity.Foundations.Data.List.Lex @@ -2901,6 +2903,7 @@ public import Complexity.Foundations.Data.List.OfFn public import Complexity.Foundations.Data.List.Range public import Complexity.Foundations.Data.List.RowMajor public import Complexity.Foundations.Data.List.SelectDistinct +public import Complexity.Foundations.Data.List.SumIndicator public import Complexity.Foundations.Data.List.TwoBitTable public import Complexity.Foundations.Data.Matrix.Binary public import Complexity.Foundations.Data.Matrix.IntegerBits @@ -2952,6 +2955,7 @@ public import Complexity.Foundations.Data.Nat.Radix.Measurement public import Complexity.Foundations.Data.Nat.RecursionTree public import Complexity.Foundations.Data.Nat.ResidueSum public import Complexity.Foundations.Data.Nat.SqrtLog +public import Complexity.Foundations.Data.Nat.SqrtLog.Linear public import Complexity.Foundations.Data.Nat.SqrtRatio public import Complexity.Foundations.Data.Nat.Subexponential public import Complexity.Foundations.Data.Option diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean index 95607eaba..2c73c8e78 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ClauseNetworkPolyTime.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ClauseNetwork +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTablesPolyTime public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction public import Complexity.Computability.Complexity.Problems.SAT import Complexity.Computability.Complexity.Problems.SAT.PolyTime @@ -48,7 +49,7 @@ namespace Cslib.Circuits.Frontier.ClauseNetwork open Turing.MultiTapeTM ComputableInPolyTime Cslib.Logic.PL open Cslib.Complexity (encCNF) -open ExplicitTensorNetwork (boundedTable) +open ExplicitTensorNetwork (computableInPolyTime_boundedTable ports_unary_to_binary) variable {α : Type} {inp : α ↪ List Bool} @@ -128,20 +129,6 @@ theorem computableInPolyTime_falsifier {os : α → List (Occurrence ℕ)} have hnum := (ComputableInPolyTime.of_tidy ComputableTidilyInPolyTime.bitsToNat).comp hbits simpa only [falsifier, lsbValue_eq_ofBits, Function.comp_def] using hnum -/-- A bounded table whose entries are polynomial-time at every fixed position. -/ -theorem computableInPolyTime_boundedTable (B : ℕ) {w : α → ℕ} {f : α → ℕ → ℕ} - (hw : ComputableInPolyTime w inp encUnary) - (hf : ∀ idx, ComputableInPolyTime (fun a => f a idx) inp encNat) : - ComputableInPolyTime (fun a => boundedTable B (w a) (f a)) inp (encList encNat) := by - unfold boundedTable - exact mapFilterConst _ (fun idx _ => binary_lt' (const idx) (binary_pow' (const 2) hw)) - fun idx _ => hf idx - -private theorem binaryPorts {ps : α → List ℕ} - (hps : ComputableInPolyTime ps inp (encList encUnary)) : - ComputableInPolyTime ps inp (encList encNat) := by - simpa only [Function.comp_def, List.map_id'] using (map unary_to_binary).comp hps - /-- **Clause vertices are polynomial-time.** -/ theorem computableInPolyTime_clauseVertex (B : ℕ) {os : α → List (Occurrence ℕ)} (hos : ComputableInPolyTime os inp encOs) {i : α → ℕ} @@ -151,8 +138,8 @@ theorem computableInPolyTime_clauseVertex (B : ℕ) {os : α → List (Occurrenc have hps := computableInPolyTime_clausePorts hos hi have hz := computableInPolyTime_falsifier hos hps unfold clauseVertex - refine (binaryPorts hps).prod (computableInPolyTime_boundedTable B ((length_list _).comp hps) - fun idx => ?_) + refine (ports_unary_to_binary hps).prod + (computableInPolyTime_boundedTable B ((length_list _).comp hps) fun idx => ?_) unfold clauseEntry exact ite_decide (decide_eq' (const idx) hz) (const 0) (const 1) @@ -165,7 +152,7 @@ theorem computableInPolyTime_qVertex (B : ℕ) {os : α → List (Occurrence ℕ have hps := computableInPolyTime_qPorts hos hw have hlen := (length_list _).comp hps unfold qVertex - refine (binaryPorts hps).prod (computableInPolyTime_boundedTable B hlen fun idx => ?_) + refine (ports_unary_to_binary hps).prod (computableInPolyTime_boundedTable B hlen fun idx => ?_) unfold eqEntry exact ite_decide (decide_or (decide_eq' (const (encOut := encNat) idx) (const 0)) (decide_eq' (const (idx + 1)) (binary_pow' (const 2) hlen))) (const 1) (const 0) @@ -175,7 +162,7 @@ theorem computableInPolyTime_linkVertex {u : α → ℕ} (hu : ComputableInPolyT ComputableInPolyTime (fun a => linkVertex (u a)) inp (encPair (encList encNat) (encList encNat)) := by unfold linkVertex - refine (binaryPorts (cons' (unary_add' (unary_mul' (const 3) hu) (const 1)) + refine (ports_unary_to_binary (cons' (unary_add' (unary_mul' (const 3) hu) (const 1)) (cons' (unary_add' (unary_mul' (const 3) hu) (const 2)) (const [])))).prod (const _) /-- **The network of an occurrence list is polynomial-time**, with its number of rows in binary. -/ diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTablesPolyTime.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTablesPolyTime.lean new file mode 100644 index 000000000..9b2c2add8 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ExplicitTablesPolyTime.lean @@ -0,0 +1,59 @@ +/- +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.Frontier.TensorNetwork.ExplicitTables +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.List +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Nat +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Unary +public import Complexity.Computability.Machines.Turing.MultiTape.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends + +/-! +# Tables of explicit tensor networks in polynomial time + +The vertices of an explicit network written by a polynomial-time construction list their ports in +binary and their tables as bounded tables. Ports computed in unary are rewritten in binary +(`ExplicitTensorNetwork.ports_unary_to_binary`), and a bounded table is polynomial-time whenever +each of its entries is, since it is a map over the first `2 ^ B` numbers, a constant list +(`ExplicitTensorNetwork.computableInPolyTime_boundedTable`). + +## Main results + +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.ports_unary_to_binary`: a list of numbers in unary + can be rewritten in binary. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.computableInPolyTime_boundedTable`: bounded tables + with polynomial-time entries. +-/ + +public section + +namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork + +open Turing.MultiTapeTM ComputableInPolyTime + +variable {α : Type} {inp : α ↪ List Bool} + +/-- A list of numbers computed in unary, such as the ports of a vertex, is computed in binary. -/ +theorem ports_unary_to_binary {ps : α → List ℕ} + (hps : ComputableInPolyTime ps inp (encList encUnary)) : + ComputableInPolyTime ps inp (encList encNat) := by + simpa only [Function.comp_def, List.map_id'] using (map unary_to_binary).comp hps + +/-- A bounded table whose entries are polynomial-time at every fixed position. -/ +theorem computableInPolyTime_boundedTable (B : ℕ) {w : α → ℕ} {f : α → ℕ → ℕ} + (hw : ComputableInPolyTime w inp encUnary) + (hf : ∀ idx, ComputableInPolyTime (fun a => f a idx) inp encNat) : + ComputableInPolyTime (fun a => boundedTable B (w a) (f a)) inp (encList encNat) := by + unfold boundedTable + exact mapFilterConst _ (fun idx _ => binary_lt' (const idx) (binary_pow' (const 2) hw)) + fun idx _ => hf idx + +end Cslib.Circuits.Frontier.ExplicitTensorNetwork diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index f0dd69264..82a2a6ddd 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -10,6 +10,7 @@ module public import Complexity.Computability.Circuit.Quantum.PathSum public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTables import Complexity.Foundations.Data.List.Boole +import Complexity.Foundations.Data.List.SumIndicator import Mathlib.Data.List.GetD import Mathlib.Data.List.OfFn import Mathlib.Logic.Equiv.Fin.Basic @@ -1046,17 +1047,6 @@ theorem mem_qubits_iff {gs : List GateCode} {i : ℕ} (hi : i < gs.length) (q : · rintro ⟨k, hk, hq⟩ exact ⟨k, hk, ((List.getD_eq_getElem _ _ hk).symm.trans (getD_occurrences hi hk).symm).trans hq⟩ -private theorem sum_range_ite_eq (L i₀ : ℕ) : - ((List.range L).map fun i => if i = i₀ then (1 : ℕ) else 0).sum = if i₀ < L then 1 else 0 := by - induction L with - | zero => simp - | succ L ih => - rw [List.range_succ, List.map_append, List.sum_append, ih] - rcases lt_trichotomy i₀ L with h | rfl | h - · simp [h, Nat.lt_succ_of_lt h, h.ne'] - · simp - · simp [show ¬ i₀ < L by omega, show ¬ i₀ < L + 1 by omega, show L ≠ i₀ by omega] - /-- **The reader of a link edge.** Let `u` be an occurrence of the qubit `q`. If `u` is the last occurrence of `q`, no gate reads `q` from `u`; otherwise exactly one gate does, the gate of the next occurrence of `q`. -/ @@ -1144,7 +1134,7 @@ theorem sum_readers {gs : List GateCode} (hnd : ∀ g ∈ gs, (qubits g).Nodup) omega · exact fun w h1 h2 => hmin w h1 (by omega) rw [List.map_congr_left fun i hi => if_congr (hiff i (List.mem_range.mp hi)) rfl rfl, - sum_range_ite_eq, ite_eq_left hi₀] + List.sum_map_range_ite_eq, ite_eq_left hi₀] /-- A gate whose qubits are distinct has distinct ports: the out edges are distinct and even, and the input ports are distinct and odd, since distinct qubits have distinct last occurrences. -/ @@ -1240,12 +1230,6 @@ private theorem count_glueVertex_link (gs : List GateCode) {w c u : ℕ} omega · simp only [hl, ↓reduceIte, false_and, List.count_nil] -private theorem sum_range_ite_and (L : ℕ) (P : Prop) [Decidable P] (Q : ℕ → Prop) - [DecidablePred Q] : - ((List.range L).map fun i => if P ∧ Q i then (1 : ℕ) else 0).sum = - if P then ((List.range L).map fun i => if Q i then (1 : ℕ) else 0).sum else 0 := by - by_cases hP : P <;> simp [hP] - /-- Every occurrence lies in exactly one gate. -/ private theorem sum_range_base (gs : List GateCode) {u : ℕ} (hu : u < (occurrences gs).length) : ((List.range gs.length).map fun i => @@ -1253,7 +1237,7 @@ private theorem sum_range_base (gs : List GateCode) {u : ℕ} (hu : u < (occurre obtain ⟨i₀, hi₀, h1, h2⟩ := exists_base_le_lt hu rw [List.map_congr_left fun i _ => if_congr (show base gs i ≤ u ∧ u < base gs (i + 1) ↔ i = i₀ from ⟨fun h => eq_of_base_le_lt h.1 h.2 h1 h2, fun h => h ▸ ⟨h1, h2⟩⟩) rfl rfl, - sum_range_ite_eq, ite_eq_left hi₀] + List.sum_map_range_ite_eq, ite_eq_left hi₀] /-- **The network is rows-first bipartite** with the gate, glue and scalar vertices as rows, when the qubits of every gate are distinct. -/ @@ -1306,7 +1290,7 @@ theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCo hc (show 0 < 2 by omega) hu, List.map_congr_left fun i hi => count_gatePorts_out (List.mem_range.mp hi) (hnd' i hi) hc (show 1 < 2 by omega) hu, - sum_range_ite_and, sum_range_ite_and, sum_range_base gs hu] + List.sum_map_ite_and, List.sum_map_ite_and, sum_range_base gs hu] interval_cases c <;> simp · -- a link edge belongs to its reader or its glue vertex rw [List.map_congr_left fun i hi => count_gatePorts_link i (hnd' i hi) hc @@ -1314,7 +1298,7 @@ theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCo List.map_congr_left fun i hi => count_gatePorts_link i (hnd' i hi) hc (show 1 < 2 by omega) hu, List.map_congr_left fun w hw => count_glueVertex_link gs (List.mem_range.mp hw) hc hu, - sum_range_ite_and, sum_range_ite_and] + List.sum_map_ite_and, List.sum_map_ite_and] have hglue : ((List.range (occurrences gs).length).map fun w => if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w ∧ w = u then (1 : ℕ) else 0).sum = @@ -1325,9 +1309,9 @@ theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCo (lastOcc (occurrences gs) ((occurrences gs).getD u 0) (occurrences gs).length = some u ∧ w = u) from ⟨fun ⟨h1, h2⟩ => ⟨h2 ▸ h1, h2⟩, fun ⟨h1, h2⟩ => ⟨h2 ▸ h1, h2⟩⟩) rfl rfl, - sum_range_ite_and] + List.sum_map_ite_and] split_ifs - · rw [sum_range_ite_eq, ite_eq_left hu] + · rw [List.sum_map_range_ite_eq, ite_eq_left hu] · rfl rw [hglue] have := sum_readers hnd hu @@ -1339,6 +1323,6 @@ theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCo simp only [List.count_cons, List.count_nil, beq_iff_eq] split_ifs <;> omega simp only [List.map_map, Function.comp_def] - rw [List.map_congr_left fun w _ => hcount w, sum_range_ite_eq, ite_eq_left (by omega)] + rw [List.map_congr_left fun w _ => hcount w, List.sum_map_range_ite_eq, ite_eq_left (by omega)] end Cslib.Quantum.PathNetwork diff --git a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean index 861b088b3..8c1d0379d 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Computability.Circuit.Quantum.PathNetwork +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ExplicitTablesPolyTime public import Complexity.Computability.Circuit.Quantum.Uniform public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.List @@ -25,6 +26,7 @@ import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListM import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives +import Complexity.Foundations.Data.List.FilterMap /-! # The network of a circuit in polynomial time @@ -51,12 +53,9 @@ variable {α : Type} {inp : α ↪ List Bool} /-! ### Reformulations for the polynomial-time combinators -/ -private theorem filterMap_eq {β : Type} (l : List β) (f : β → Option ℕ) : - l.filterMap f = (l.filter fun b => (f b).isSome).map fun b => (f b).getD 0 := by - induction l with - | nil => rfl - | cons b l ih => - cases h : f b <;> simp [h, ih] +/-- The qubits of a gate code, read by projections, as the pair combinators compute them. -/ +private theorem qubits_eq_proj : qubits = fun g : GateCode => g.2.1 :: g.2.2 := + funext fun ⟨_, _, _⟩ => rfl private theorem gatePorts_eq (gs : List GateCode) (c i : ℕ) : gatePorts gs c i = @@ -66,7 +65,7 @@ private theorem gatePorts_eq (gs : List GateCode) (c i : ℕ) : fun j => (lastOcc (occurrences gs) j (base gs i)).isSome).map fun j => 2 * (c * (occurrences gs).length + (lastOcc (occurrences gs) j (base gs i)).getD 0) + 1 := by - rw [gatePorts, filterMap_eq] + rw [gatePorts, List.filterMap_eq_map_filter _ _ 0] congr 1 simp only [inPort, Option.isSome_map] refine List.map_congr_left fun j hj => ?_ @@ -95,11 +94,6 @@ private theorem computableInPolyTime_equivProd_symm : ExplicitTensorNetwork.encExplicitNetwork := ComputableInPolyTime.of_eq fun _ => rfl -private theorem binaryPorts {ps : α → List ℕ} - (hps : ComputableInPolyTime ps inp (encList encUnary)) : - ComputableInPolyTime ps inp (encList encNat) := by - simpa only [Function.comp_def, List.map_id'] using (map unary_to_binary).comp hps - /-! ### Occurrences -/ section Data @@ -111,12 +105,13 @@ theorem computableInPolyTime_qubits_getD {i : α → ℕ} (hi : ComputableInPoly ComputableInPolyTime (fun a => qubits ((gs a).getD (i a) default)) inp (encList encUnary) := by have hg := listGetDWithInput hgs hi default - unfold qubits + rw [qubits_eq_proj] exact cons' ((fst _ _).comp ((snd _ _).comp hg)) ((snd _ _).comp ((snd _ _).comp hg)) theorem computableInPolyTime_occurrences : ComputableInPolyTime (fun a => occurrences (gs a)) inp (encList encUnary) := by - unfold occurrences qubits + unfold occurrences + rw [qubits_eq_proj] exact flatMapWithInput hgs (cons' ((fst _ _).comp ((snd _ _).comp (snd _ _))) ((snd _ _).comp ((snd _ _).comp (snd _ _)))) @@ -135,16 +130,6 @@ theorem computableInPolyTime_lastOcc {qs : α → List ℕ} {j p : α → ℕ} exact findWithInput (listReverseWithInput (range.comp hp)) (decide_eq' (listGetDWithInput (hqs.comp (fst _ _)) (snd _ _) 0) (hj.comp (fst _ _))) -/-- The table of a vertex with `w ≤ 6` ports whose entries are polynomial-time at every fixed -position. -/ -theorem computableInPolyTime_table {w : α → ℕ} {f : α → ℕ → ℕ} - (hw : ComputableInPolyTime w inp encUnary) - (hf : ∀ idx, ComputableInPolyTime (fun a => f a idx) inp encNat) : - ComputableInPolyTime (fun a => table (w a) (f a)) inp (encList encNat) := by - unfold table - exact mapFilterConst _ (fun idx _ => binary_lt' (const idx) (binary_pow' (const 2) hw)) - fun idx _ => hf idx - /-! ### Vertices -/ section Vertices @@ -182,8 +167,8 @@ theorem computableInPolyTime_gateVertex {neg : α → ℕ} {X : α → List Bool have hg := listGetDWithInput hgs hi default have hN := (length_list _).comp (computableInPolyTime_occurrences hgs) have hb := computableInPolyTime_base hgs hi - refine (binaryPorts hps).prod (computableInPolyTime_table ((length_list _).comp hps) - fun idx => ?_) + refine (ports_unary_to_binary hps).prod + (computableInPolyTime_boundedTable 6 ((length_list _).comp hps) fun idx => ?_) -- the bits entering the qubits of the gate have hins : ComputableInPolyTime (fun a => (qubits ((gs a).getD (i a) default)).map (inBit (X a) (gs a) (c a) (i a) idx)) inp (encList encBool) := by @@ -217,7 +202,7 @@ theorem computableInPolyTime_glueVertex {w : α → ℕ} (hw : ComputableInPolyT have hq := listGetDWithInput hqs hw 0 unfold glueVertex refine ite_decide (decide_eq' (computableInPolyTime_lastOcc hqs hq hN) (optionSome _ |>.comp hw)) - ((binaryPorts (cons' (unary_add' (unary_mul' (const 2) hw) (const 1)) + ((ports_unary_to_binary (cons' (unary_add' (unary_mul' (const 2) hw) (const 1)) (cons' (unary_add' (unary_mul' (const 2) (unary_add' hN hw)) (const 1)) (const [])))).prod (ite_decide (decide_eq' hq (const 0)) (const _) (const _))) (const _) @@ -261,7 +246,7 @@ theorem computableInPolyTime_network {neg : α → ℕ} {X : α → List Bool} { (snd _ _)))) (cons' (computableInPolyTime_scalarVertex hX hgs) (const []))) (mapWithInput (range.comp (unary_mul' (const 2) hN)) - ((binaryPorts (cons' (unary_mul' (const 2) (snd _ _)) + ((ports_unary_to_binary (cons' (unary_mul' (const 2) (snd _ _)) (cons' (unary_add' (unary_mul' (const 2) (snd _ _)) (const 1)) (const [])))).prod (const _))) unfold network diff --git a/Complexity/Computability/Circuit/Quantum/PathSum.lean b/Complexity/Computability/Circuit/Quantum/PathSum.lean index f523b4564..d2c2c2e73 100644 --- a/Complexity/Computability/Circuit/Quantum/PathSum.lean +++ b/Complexity/Computability/Circuit/Quantum/PathSum.lean @@ -44,6 +44,7 @@ simulations, which places `BQP` in the counting classes * `Cslib.Quantum.run_ofCodes_apply`: amplitudes are path sums. * `Cslib.Quantum.acceptProb_eq_acceptGap`: the acceptance probability is `acceptGap / 2^h`. * `Cslib.Quantum.acceptGap_nonneg_and_le`: so the acceptance gap lies between `0` and `2^h`. +* `Cslib.Quantum.acceptGap_nil`: the acceptance gap of the empty circuit is the bit of qubit `0`. ## References @@ -128,6 +129,13 @@ def acceptGap (gs : List GateCode) (x : List Bool) : ℤ := ∑ π : Fin (hadamardCount gs) → Bool, ∑ π' : Fin (hadamardCount gs) → Bool, acceptTerm gs x (List.ofFn π) (List.ofFn π') +/-- The acceptance gap of the empty circuit is the bit of qubit `0`. -/ +theorem acceptGap_nil (x : List Bool) : acceptGap [] x = if x.headD false then 1 else 0 := by + simp only [acceptGap, hadamardCount_nil, acceptTerm, + pathRun, List.ofFn_zero, List.foldl_nil, true_and, Bool.xor_self, Bool.false_eq_true, + ↓reduceIte] + cases x.headD false <;> rfl + /-- The sign `±1` of a Boolean, in `ℂ`. -/ private abbrev sgn (σ : Bool) : ℂ := if σ then -1 else 1 diff --git a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean index 0c38eca17..8af90ccfd 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean @@ -20,7 +20,7 @@ import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Na import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends -import Complexity.Foundations.Data.Nat.SqrtLog +import Complexity.Foundations.Data.Nat.SqrtLog.Linear /-! # Quantum circuits by tensor contraction: `BQTIME[T] ⊆ DTIME[2 ^ O(T / √log T)]` @@ -123,16 +123,11 @@ network with Hadamard sign `2 ^ (h + 1) - 1` on the input register `X`. -/ /-- **The acceptance gap as the simulator computes it**: the residue of the contraction of the network modulo `2 ^ (h + 1)`, or the bit of qubit `0` for a circuit without gates. -/ @[expose] def gapValue (gs : List GateCode) (X : List Bool) : ℕ := - if guard gs then (contractionInput gs X).2.2.contraction % 2 ^ (contractionInput gs X).1 + if guard gs then + let (precision, _, network) := contractionInput gs X + network.contraction % 2 ^ precision else fallback X -private theorem acceptGap_nil (X : List Bool) : - acceptGap [] X = if X.headD false then 1 else 0 := by - simp only [acceptGap, hadamardCount_nil, acceptTerm, - pathRun, List.ofFn_zero, List.foldl_nil, true_and, Bool.xor_self, Bool.false_eq_true, - ↓reduceIte] - cases X.headD false <;> rfl - /-- **The simulator computes the acceptance gap** of well-formed gate codes on a nonempty input register. -/ theorem gapValue_eq {X : List Bool} (hX : 0 < X.length) {gs : List GateCode} @@ -395,33 +390,6 @@ theorem acceptProb_eq_gapValue {m : ℕ} (hm : 0 < m) {gs : List GateCode} /-! ### The class containments -/ -/-- The exponent in terms of a bound `c₁ T + c₂` on the number of gates `L`. -/ -private theorem exponent_le_of_le (A c₁ c₂ : ℕ) {T L : ℕ} (hT : 2 ≤ T) (hL : L ≤ c₁ * T + c₂) : - A * (L + 1) / Nat.sqrt (Nat.log 2 (L + 2)) ≤ - 2 * A * (c₁ + c₂ + 2) * (T / Nat.sqrt (Nat.log 2 T)) + (2 * A * (c₁ + c₂ + 2) + 3 * A) := by - have hM : L + 2 ≤ (c₁ + c₂ + 2) * T := by nlinarith - calc A * (L + 1) / Nat.sqrt (Nat.log 2 (L + 2)) - ≤ A * (L + 2) / Nat.sqrt (Nat.log 2 (L + 2)) := - Nat.div_le_div_right (Nat.mul_le_mul_left _ (by omega)) - _ ≤ A * ((L + 2) / Nat.sqrt (Nat.log 2 (L + 2))) + A := Nat.mul_div_le_mul_div_add _ _ _ - _ ≤ A * (2 * ((c₁ + c₂ + 2) * T / Nat.sqrt (Nat.log 2 ((c₁ + c₂ + 2) * T))) + 2) + A := by - gcongr - exact Nat.div_sqrt_log_two_le_of_le hM - _ ≤ A * (2 * ((c₁ + c₂ + 2) * (T / Nat.sqrt (Nat.log 2 T)) + (c₁ + c₂ + 2)) + 2) + A := by - gcongr - exact Nat.mul_div_sqrt_log_two_le _ T - _ = _ := by ring - -/-- A polynomial in `n + 1 ≤ T + 1` is at most `2 ^ (2 e T / √⌊log₂ T⌋)`. -/ -private theorem add_one_pow_le {n T : ℕ} (e : ℕ) (hT : 2 ≤ T) (hn : n ≤ T) : - (n + 1) ^ e ≤ 2 ^ (2 * e * (T / Nat.sqrt (Nat.log 2 T))) := by - have h1 := Nat.one_le_div_sqrt_log_two hT - have h2 := Nat.log_two_le_div_sqrt_log_two T - calc (n + 1) ^ e ≤ (2 ^ (Nat.log 2 T + 1)) ^ e := - Nat.pow_le_pow_left ((Nat.succ_le_succ hn).trans (Nat.lt_pow_succ_log_self one_lt_two T)) e - _ = 2 ^ (e * (Nat.log 2 T + 1)) := by rw [← pow_mul, mul_comm] - _ ≤ _ := Nat.pow_le_pow_right (by norm_num) (by nlinarith) - variable {α : Type} {enc : α ↪ List Bool} {T : ℕ → ℕ} /-- **Bounded-error quantum time `T` in time `2 ^ O(T / √log T)` and space `T ^ O(1)`.** For `T` @@ -486,8 +454,8 @@ theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T have hnT : (enc a).length ≤ T (enc a).length := (le_max_left _ _).trans (hTn _) · -- time set S := T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) - have hE := exponent_le_of_le A c₁ c₂ hT2 (hgates (enc a).length) - have hY := add_one_pow_le e hT2 hnT + have hE := Nat.mul_succ_div_sqrt_log_two_le_of_le A c₁ c₂ hT2 (hgates (enc a).length) + have hY := Nat.add_one_pow_le_two_pow_div_sqrt_log_two e hT2 hnT have hZ : 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * ((enc a).length + 1) ^ e ≤ 2 ^ D₀ * 2 ^ (C₀ * S) := by calc _ ≤ 2 ^ (2 * A * (c₁ + c₂ + 2) * S + D₀) * 2 ^ (2 * e * S) := diff --git a/Complexity/Computability/Complexity/Classes/BQP/Time.lean b/Complexity/Computability/Complexity/Classes/BQP/Time.lean index c4dd3b7da..17a790f1d 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/Time.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/Time.lean @@ -61,13 +61,6 @@ variable {enc : α ↪ List Bool} theorem BQTIME_subset_BQP (t : ℕ → ℕ) : BQTIME enc t ⊆ BQP enc := fun _ ⟨F, _, _, _, hF⟩ => ⟨F, hF⟩ -private theorem length_le_length_encList {β : Type} (e : β ↪ List Bool) (l : List β) : - l.length ≤ (encList e l).length := by - rw [length_encList] - induction l with - | nil => simp - | cons b l ih => simp only [List.length_cons, List.map_cons, List.sum_cons]; omega - /-- **`BQP` is the union of the polynomial quantum time classes** `BQTIME[n ^ k]`: the circuits of a polynomial-time uniform family have polynomially many gates. -/ theorem BQP_eq_iUnion_BQTIME : BQP enc = ⋃ k : ℕ, BQTIME enc fun n => n ^ k := by @@ -75,7 +68,7 @@ theorem BQP_eq_iUnion_BQTIME : BQP enc = ⋃ k : ℕ, BQTIME enc fun n => n ^ k (Set.iUnion_subset fun k => BQTIME_subset_BQP _) obtain ⟨c, d, hc⟩ := F.computableInPolyTime_gates.polyBounded_length.exists_le refine Set.mem_iUnion.mpr ⟨d, F, c * 2 ^ d, c, fun n => ?_, hF⟩ - have h1 := (length_le_length_encList encGateCode (F.gates n)).trans (hc n) + have h1 := (Turing.MultiTapeTM.length_le_length_encList encGateCode (F.gates n)).trans (hc n) rw [encUnary_apply, List.length_replicate] at h1 have h2 : (n + 1) ^ d ≤ 2 ^ d * n ^ d + 1 := by rcases Nat.eq_zero_or_pos n with rfl | hn diff --git a/Complexity/Foundations/Data/List/FilterMap.lean b/Complexity/Foundations/Data/List/FilterMap.lean new file mode 100644 index 000000000..34e5c70bc --- /dev/null +++ b/Complexity/Foundations/Data/List/FilterMap.lean @@ -0,0 +1,38 @@ +/- +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.Init +public import Mathlib.Data.List.Basic + +/-! +# `filterMap` as a filter followed by a map + +`l.filterMap f` keeps the elements on which `f` is defined and maps them by `f`, read with any +default (`List.filterMap_eq_map_filter`). This is the form that list combinators which filter and +map separately compute. + +## Main results + +* `List.filterMap_eq_map_filter`: `l.filterMap f` is `l.filter (f · |>.isSome)` mapped by + `(f · |>.getD d)`. +-/ + +public section + +namespace List + +/-- `filterMap` is a filter by definedness followed by a map, read with any default. -/ +theorem filterMap_eq_map_filter {α β : Type*} (l : List α) (f : α → Option β) (d : β) : + l.filterMap f = (l.filter fun a => (f a).isSome).map fun a => (f a).getD d := by + induction l with + | nil => rfl + | cons a l ih => + cases h : f a <;> simp [h, ih] + +end List diff --git a/Complexity/Foundations/Data/List/SumIndicator.lean b/Complexity/Foundations/Data/List/SumIndicator.lean new file mode 100644 index 000000000..809f8a4c6 --- /dev/null +++ b/Complexity/Foundations/Data/List/SumIndicator.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.Foundations.Data.List.OfFn +import Mathlib.Algebra.BigOperators.Group.Finset.Piecewise + +/-! +# Sums of indicators over lists + +Indicator sums over `List.range n` reduce to the finite-sum indicator API through +`List.sum_map_range`: the indicator of a single index sums to whether the index is in range +(`List.sum_map_range_ite_eq`). A condition that does not depend on the element comes out of the +sum (`List.sum_map_ite_and`). + +## Main results + +* `List.sum_map_range_ite_eq`: the indicator of `i₀` sums to `b` over `List.range n` when + `i₀ < n`, and to `0` otherwise. +* `List.sum_map_ite_and`: a constant conjunct of an indicator comes out of the sum. +-/ + +public section + +namespace List + +variable {M : Type*} [AddCommMonoid M] + +/-- The indicator of a single index sums over `List.range n` to `b` when the index is below `n`, +and to `0` otherwise. -/ +theorem sum_map_range_ite_eq (n i₀ : ℕ) (b : M) : + ((List.range n).map fun i => if i = i₀ then b else 0).sum = if i₀ < n then b else 0 := by + rw [List.sum_map_range, ← Finset.sum_range (fun i => if i = i₀ then b else 0), + Finset.sum_ite_eq'] + simp only [Finset.mem_range] + +/-- A conjunct of an indicator that does not depend on the element comes out of the sum. -/ +theorem sum_map_ite_and {α : Type*} (l : List α) (P : Prop) [Decidable P] (Q : α → Prop) + [DecidablePred Q] (b : M) : + (l.map fun a => if P ∧ Q a then b else 0).sum = + if P then (l.map fun a => if Q a then b else 0).sum else 0 := by + by_cases hP : P <;> simp [hP] + +end List diff --git a/Complexity/Foundations/Data/Nat/SqrtLog/Linear.lean b/Complexity/Foundations/Data/Nat/SqrtLog/Linear.lean new file mode 100644 index 000000000..1e97cfa2f --- /dev/null +++ b/Complexity/Foundations/Data/Nat/SqrtLog/Linear.lean @@ -0,0 +1,63 @@ +/- +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.Foundations.Data.Nat.SqrtLog +import Mathlib.Tactic.GCongr +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# The scale `n / √(log n)` under linear bounds + +Two estimates that transfer bounds in a derived size `L ≤ c₁ T + c₂`, such as the number of gates +of a circuit, to the scale `T / √⌊log₂ T⌋` of a time bound `T ≥ 2`: the shifted scale +`A (L + 1) / √⌊log₂ (L + 2)⌋` is at most a constant times `T / √⌊log₂ T⌋` plus a constant +(`Nat.mul_succ_div_sqrt_log_two_le_of_le`), and a polynomial `(n + 1) ^ e` in `n ≤ T` is at most +`2 ^ (2 e T / √⌊log₂ T⌋)` (`Nat.add_one_pow_le_two_pow_div_sqrt_log_two`). + +## Main results + +* `Nat.mul_succ_div_sqrt_log_two_le_of_le`: the shifted scale under a linear bound. +* `Nat.add_one_pow_le_two_pow_div_sqrt_log_two`: polynomials are absorbed by the exponential scale. +-/ + +public section + +namespace Nat + +/-- **The shifted scale under a linear bound.** If `L ≤ c₁ T + c₂` and `2 ≤ T`, then +`A (L + 1) / √⌊log₂ (L + 2)⌋` is at most a constant times `T / √⌊log₂ T⌋`, plus a constant. -/ +theorem mul_succ_div_sqrt_log_two_le_of_le (A c₁ c₂ : ℕ) {T L : ℕ} (hT : 2 ≤ T) + (hL : L ≤ c₁ * T + c₂) : + A * (L + 1) / Nat.sqrt (Nat.log 2 (L + 2)) ≤ + 2 * A * (c₁ + c₂ + 2) * (T / Nat.sqrt (Nat.log 2 T)) + (2 * A * (c₁ + c₂ + 2) + 3 * A) := by + have hM : L + 2 ≤ (c₁ + c₂ + 2) * T := by nlinarith + calc A * (L + 1) / Nat.sqrt (Nat.log 2 (L + 2)) + ≤ A * (L + 2) / Nat.sqrt (Nat.log 2 (L + 2)) := + Nat.div_le_div_right (Nat.mul_le_mul_left _ (by omega)) + _ ≤ A * ((L + 2) / Nat.sqrt (Nat.log 2 (L + 2))) + A := Nat.mul_div_le_mul_div_add _ _ _ + _ ≤ A * (2 * ((c₁ + c₂ + 2) * T / Nat.sqrt (Nat.log 2 ((c₁ + c₂ + 2) * T))) + 2) + A := by + gcongr + exact Nat.div_sqrt_log_two_le_of_le hM + _ ≤ A * (2 * ((c₁ + c₂ + 2) * (T / Nat.sqrt (Nat.log 2 T)) + (c₁ + c₂ + 2)) + 2) + A := by + gcongr + exact Nat.mul_div_sqrt_log_two_le _ T + _ = _ := by ring + +/-- A polynomial in `n + 1 ≤ T + 1` is at most `2 ^ (2 e T / √⌊log₂ T⌋)`. -/ +theorem add_one_pow_le_two_pow_div_sqrt_log_two {n T : ℕ} (e : ℕ) (hT : 2 ≤ T) (hn : n ≤ T) : + (n + 1) ^ e ≤ 2 ^ (2 * e * (T / Nat.sqrt (Nat.log 2 T))) := by + have h1 := Nat.one_le_div_sqrt_log_two hT + have h2 := Nat.log_two_le_div_sqrt_log_two T + calc (n + 1) ^ e ≤ (2 ^ (Nat.log 2 T + 1)) ^ e := + Nat.pow_le_pow_left ((Nat.succ_le_succ hn).trans (Nat.lt_pow_succ_log_self one_lt_two T)) e + _ = 2 ^ (e * (Nat.log 2 T + 1)) := by rw [← pow_mul, mul_comm] + _ ≤ _ := Nat.pow_le_pow_right (by norm_num) (by nlinarith) + +end Nat From d87e4f2305d634fe2b62205778e29974b40848c6 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 17:00:48 -0400 Subject: [PATCH 11/13] feat(Quantum): NQTIME, EQTIME and PostBQTIME in DTIME[2^O(T/sqrt log T)] by projected gaps Generalize the final projector of the path sum and the doubled network from qubit 0 to a list of measured qubits (onesProb, onesGap), give the simulator two projected gaps and a polynomial-time rule, and derive the containments of BQTIME, NQTIME, EQTIME and PostBQTIME from one class-level lemma. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Computability/Circuit/Quantum/Code.lean | 41 +- .../Circuit/Quantum/PathNetwork.lean | 243 +++---- .../Circuit/Quantum/PathNetworkPolyTime.lean | 54 +- .../Circuit/Quantum/PathSum.lean | 131 ++-- .../Circuit/Quantum/Uniform.lean | 26 + .../Complexity/Classes/BQP/Counting.lean | 3 +- .../Classes/BQP/TensorContraction.lean | 601 ++++++++++-------- .../Complexity/Classes/BQP/Time.lean | 86 ++- .../GenericRAM/Native/ExpTimePolySpace.lean | 164 +++++ ComplexityTests/BQPCounting.lean | 2 +- ComplexityTests/BQPTensorContraction.lean | 45 +- ComplexityTests/QuantumPathNetwork.lean | 58 +- ComplexityTests/QuantumTimeClasses.lean | 183 ++++++ references.bib | 31 + 15 files changed, 1172 insertions(+), 497 deletions(-) create mode 100644 Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean create mode 100644 ComplexityTests/QuantumTimeClasses.lean diff --git a/Complexity.lean b/Complexity.lean index afaf3c0aa..7a98df615 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1652,6 +1652,7 @@ 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.ExpTimePolySpace public import Complexity.Computability.GenericRAM.Native.HeavyBranchFold public import Complexity.Computability.GenericRAM.Native.IndexFold public import Complexity.Computability.GenericRAM.Native.MeasurementFold diff --git a/Complexity/Computability/Circuit/Quantum/Code.lean b/Complexity/Computability/Circuit/Quantum/Code.lean index 0ee18bbd6..33559fcd8 100644 --- a/Complexity/Computability/Circuit/Quantum/Code.lean +++ b/Complexity/Computability/Circuit/Quantum/Code.lean @@ -29,7 +29,9 @@ definition of uniform circuit families. A run starts in the basis state `|x 0^{m - n}⟩` holding the input word `x` of length `n` on the first qubits (`Cslib.Quantum.inputState`), and accepts when the final measurement finds qubit `0` in state `1` (`Cslib.Quantum.accepting`, `Cslib.Quantum.acceptProb`), as in -[Arora, Barak][AroraBarak09], Chapter 10. +[Arora, Barak][AroraBarak09], Chapter 10. More generally, `Cslib.Quantum.onesProb` is the +probability that the final measurement finds every qubit of a list `S` in state `1`, the events on +which postselected classes condition. Classical reversible gates permute basis states, updating the bit of their target (`Cslib.Quantum.GateCode.eval_toPlacedGate_ket`). Appending a NOT gate on qubit `0` exchanges @@ -42,6 +44,8 @@ acceptance and rejection (`Cslib.Quantum.acceptProb_append_not`). * `Cslib.Quantum.Circuit.ofCodes`: the circuit denoted by a list of gate codes. * `Cslib.Quantum.inputState`, `Cslib.Quantum.accepting`, `Cslib.Quantum.acceptProb`: the input basis state, the accepting outcomes and the acceptance probability. +* `Cslib.Quantum.onesAt`, `Cslib.Quantum.onesProb`: the outcomes in which every qubit of a list + reads `1`, and their probability; acceptance is the case of qubit `0`. ## Main results @@ -249,9 +253,12 @@ theorem Circuit.ofCodes_append (gs hs : List GateCode) : first qubits (truncated when `x` is longer than `m`). -/ def inputState (m : ℕ) (x : List Bool) : Fin m → Bool := fun i => x.getD i false +/-- The outcomes in which every qubit of `S` reads `1`. -/ +def onesAt (m : ℕ) (S : List ℕ) : Finset (Fin m → Bool) := + univ.filter fun y => ∀ j ∈ S, ∃ h : j < m, y ⟨j, h⟩ = true + /-- The accepting outcomes: qubit `0` reads `1`. -/ -def accepting (m : ℕ) : Finset (Fin m → Bool) := - univ.filter fun y => ∃ h : 0 < m, y ⟨0, h⟩ = true +def accepting (m : ℕ) : Finset (Fin m → Bool) := onesAt m [0] /-- The **acceptance probability** of the circuit with gate codes `gs` on `m` qubits on input `x`: started in `|x 0^{m - n}⟩`, the final measurement finds qubit `0` in state `1`. -/ @@ -270,7 +277,33 @@ theorem acceptProb_le_one (m : ℕ) (gs : List GateCode) (x : List Bool) : theorem mem_accepting (hm : 0 < m) {y : Fin m → Bool} : y ∈ accepting m ↔ y ⟨0, hm⟩ = true := by - simp [accepting, hm] + simp [accepting, onesAt, hm] + +/-- The probability that the final measurement of the circuit with gate codes `gs` on `m` qubits, +started in `|x 0^{m - n}⟩`, finds every qubit of `S` in state `1`. The acceptance probability is the +case `S = [0]` (`Cslib.Quantum.acceptProb_eq_onesProb`). -/ +noncomputable def onesProb (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : ℝ := + measureProb (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (onesAt m S) + +theorem acceptProb_eq_onesProb (m : ℕ) (gs : List GateCode) (x : List Bool) : + acceptProb m gs x = onesProb m gs [0] x := + rfl + +theorem onesProb_nonneg (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : + 0 ≤ onesProb m gs S x := + measureProb_nonneg _ _ + +/-- The event of no condition is certain. -/ +@[simp] theorem onesProb_nil (m : ℕ) (gs : List GateCode) (x : List Bool) : + onesProb m gs [] x = 1 := by + rw [onesProb, show onesAt m [] = univ by simp [onesAt], measureProb_univ, Circuit.norm_run, + one_pow] + +theorem onesProb_le_one (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : + onesProb m gs S x ≤ 1 := by + have h := measureProb_le_measureProb_univ + (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (onesAt m S) + rwa [measureProb_univ, Circuit.norm_run, one_pow] at h /-- The probability of the rejecting outcomes is one minus the acceptance probability. -/ private theorem measureProb_compl_accepting (ψ : Qubits m) (hψ : ‖ψ‖ = 1) : diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index 82a2a6ddd..0a1ae707f 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -20,7 +20,9 @@ import Mathlib.Logic.Equiv.Fin.Basic The acceptance gap of a list of gate codes (`Cslib.Quantum.acceptGap`) is the contraction of an explicit Boolean tensor network (`Cslib.Quantum.PathNetwork.network`) built from the circuit and a -mirror copy of it (`Cslib.Quantum.PathNetwork.contraction_network`). This is the form in which the +mirror copy of it (`Cslib.Quantum.PathNetwork.contraction_network`). More generally, for a list `S` +of *measured* qubits, the network computes the projected gap `Cslib.Quantum.onesGap S` of the event +that every qubit of `S` reads `1`; acceptance is the case `S = [0]`. This is the form in which the contraction algorithm of [OpenAI2026ETH], Theorem D.1, simulates quantum circuits exactly. **Occurrences.** The *qubits* of a gate code are its target and then its controls @@ -41,9 +43,9 @@ occurrences from `base gs i` on. The last occurrence of qubit `j` before positio otherwise; for a classical gate it is the indicator that the outputs are the gate applied to the inputs. * **Glue vertices (rows).** The link edges of the last occurrence of each qubit in the two copies - meet at a glue vertex with the equality tensor, or, for qubit `0`, the tensor that requires both - bits to be one. A *scalar* vertex without ports requires the input bit of qubit `0` to be one when - no gate touches qubit `0`. + meet at a glue vertex with the equality tensor, or, for a measured qubit, the tensor that requires + both bits to be one. A *scalar* vertex without ports requires the input bit of every measured + qubit that no gate touches to be one. * **Segment vertices (columns).** Each segment joins its out and link edges with the equality tensor. @@ -51,8 +53,8 @@ So every edge joins a row to a column, every vertex has at most six ports, and u cost nothing. The gate tables are bounded tables for the degree bound six (`Cslib.Circuits.Frontier.ExplicitTensorNetwork.boundedTable`). With `neg = -1`, an assignment of nonzero weight is a pair of paths of the path sum -(`Cslib.Quantum.pathRun`) ending in the same register with qubit `0` equal to one, and its weight -is the product of their signs, so the contraction is `acceptGap` +(`Cslib.Quantum.pathRun`) ending in the same register with every measured qubit equal to one, and +its weight is the product of their signs, so the contraction is `onesGap S` (`Cslib.Quantum.PathNetwork.contraction_network`). The network is defined for any value `neg` of the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` modulo `2 ^ P` (`Cslib.Quantum.PathNetwork.contraction_network_mod`). @@ -73,10 +75,10 @@ the Hadamard sign, so that over `ℕ` it can carry `2 ^ P - 1`, which is `-1` mo * `Cslib.Quantum.PathNetwork.lastOcc_succ`, `Cslib.Quantum.PathNetwork.lastOcc_eq_some`, `Cslib.Quantum.PathNetwork.lastOcc_eq_none`: last occurrences. * `Cslib.Quantum.PathNetwork.contraction_network`: with the Hadamard sign `-1`, the contraction - is the acceptance gap. + is the projected gap. * `Cslib.Quantum.PathNetwork.network_mapTables`, `Cslib.Quantum.PathNetwork.contraction_network_mod`: over `ℕ`, with the Hadamard sign - `2 ^ P - 1`, the contraction modulo `2 ^ P` is the acceptance gap once `P` exceeds the number + `2 ^ P - 1`, the contraction modulo `2 ^ P` is the projected gap once `P` exceeds the number of Hadamard gates. * `Cslib.Quantum.PathNetwork.numEdges_network`, `Cslib.Quantum.PathNetwork.length_vertices_network`, @@ -157,28 +159,29 @@ of `j`, or the bit of its input port. -/ /-- The table on two ports that requires both bits to be one. -/ @[expose] def acceptTable : List R := [0, 0, 0, 1] -/-- The glue vertex of occurrence `w`: when `w` is the last occurrence of its qubit, it joins the -link edges of `w` in the two copies, requiring equal bits, and both bits one for qubit `0`. -/ -@[expose] def glueVertex (gs : List GateCode) (w : ℕ) : List ℕ × List R := +/-- The glue vertex of occurrence `w`, for the list `S` of measured qubits: when `w` is the last +occurrence of its qubit, it joins the link edges of `w` in the two copies, requiring equal bits, +and both bits one for a qubit of `S`. -/ +@[expose] def glueVertex (S : List ℕ) (gs : List GateCode) (w : ℕ) : List ℕ × List R := if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w then ([2 * w + 1, 2 * ((occurrences gs).length + w) + 1], - if (occurrences gs).getD w 0 = 0 then acceptTable else eqTable) + if (occurrences gs).getD w 0 ∈ S then acceptTable else eqTable) else ([], [1]) -/-- The scalar vertex: when no gate touches qubit `0`, it requires the input bit of qubit `0` to be +/-- The scalar vertex: it requires the input bit of every qubit of `S` that no gate touches to be one. -/ -@[expose] def scalarVertex (X : List Bool) (gs : List GateCode) : List ℕ × List R := - ([], [if 0 ∈ occurrences gs ∨ X.getD 0 false = true then 1 else 0]) +@[expose] def scalarVertex (S : List ℕ) (X : List Bool) (gs : List GateCode) : List ℕ × List R := + ([], [if ∀ j ∈ S, j ∈ occurrences gs ∨ X.getD j false = true then 1 else 0]) -/-- **The network of a circuit** with the input register `X` and the Hadamard sign `neg`, with its -number of rows: the gate vertices of the two copies, the glue vertices and the scalar vertex, then -the segment vertices. -/ -@[expose] def network (neg : R) (X : List Bool) (gs : List GateCode) : +/-- **The network of a circuit** with the measured qubits `S`, the input register `X` and the +Hadamard sign `neg`, with its number of rows: the gate vertices of the two copies, the glue +vertices and the scalar vertex, then the segment vertices. -/ +@[expose] def network (neg : R) (S : List ℕ) (X : List Bool) (gs : List GateCode) : ℕ × ExplicitTensorNetwork R := (2 * gs.length + (occurrences gs).length + 1, ⟨2 * (2 * (occurrences gs).length), (List.range gs.length).map (gateVertex neg X gs 0) ++ (List.range gs.length).map (gateVertex neg X gs 1) ++ - (List.range (occurrences gs).length).map (glueVertex gs) ++ [scalarVertex X gs] ++ + (List.range (occurrences gs).length).map (glueVertex S gs) ++ [scalarVertex S X gs] ++ (List.range (2 * (occurrences gs).length)).map fun w => ([2 * w, 2 * w + 1], eqTable)⟩) end Vertices @@ -563,19 +566,19 @@ private theorem entry_gateVertex (neg : R) (X : List Bool) (gs : List GateCode) omega /-- The glue condition on the final bits `a` and `b` of the qubit `j` in the two copies: they are -equal, and one for qubit `0`. -/ -private def GlueOK (a b : Bool) (j : ℕ) : Prop := a = b ∧ (j = 0 → a = true) +equal, and one for a qubit of `S`. -/ +private def GlueOK (S : List ℕ) (a b : Bool) (j : ℕ) : Prop := a = b ∧ (j ∈ S → a = true) -private instance (a b : Bool) (j : ℕ) : Decidable (GlueOK a b j) := by +private instance (S : List ℕ) (a b : Bool) (j : ℕ) : Decidable (GlueOK S a b j) := by unfold GlueOK infer_instance /-- **The entry of a glue vertex** when edge `e` carries `Z (e / 2)`. -/ -private theorem entry_glueVertex (gs : List GateCode) (w : ℕ) (Z : ℕ → Bool) : - entry (glueVertex gs w : List ℕ × List R).2 - ((glueVertex gs w : List ℕ × List R).1.map fun e => Z (e / 2)) = +private theorem entry_glueVertex (S : List ℕ) (gs : List GateCode) (w : ℕ) (Z : ℕ → Bool) : + entry (glueVertex S gs w : List ℕ × List R).2 + ((glueVertex S gs w : List ℕ × List R).1.map fun e => Z (e / 2)) = if (lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w → - GlueOK (Z w) (Z ((occurrences gs).length + w)) ((occurrences gs).getD w 0)) + GlueOK S (Z w) (Z ((occurrences gs).length + w)) ((occurrences gs).getD w 0)) then (1 : R) else 0 := by unfold glueVertex by_cases hl : lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = @@ -583,17 +586,17 @@ private theorem entry_glueVertex (gs : List GateCode) (w : ℕ) (Z : ℕ → Boo · simp only [hl, ↓reduceIte, List.map_cons, List.map_nil, true_implies, show (2 * w + 1) / 2 = w by omega, show (2 * ((occurrences gs).length + w) + 1) / 2 = (occurrences gs).length + w by omega] - by_cases h0 : (occurrences gs).getD w 0 = 0 + by_cases h0 : (occurrences gs).getD w 0 ∈ S · simp only [h0, ↓reduceIte, GlueOK, true_implies] cases Z w <;> cases Z ((occurrences gs).length + w) <;> rfl · simp only [h0, ↓reduceIte, GlueOK, entry_eqTable, false_implies, and_true] · simp only [hl, ↓reduceIte, false_implies] rfl -private theorem entry_scalarVertex (X : List Bool) (gs : List GateCode) : - entry (scalarVertex X gs : List ℕ × List R).2 ((scalarVertex X gs : List ℕ × List R).1.map - fun _ => false) = - if 0 ∈ occurrences gs ∨ X.getD 0 false = true then 1 else 0 := rfl +private theorem entry_scalarVertex (S : List ℕ) (X : List Bool) (gs : List GateCode) : + entry (scalarVertex S X gs : List ℕ × List R).2 + ((scalarVertex S X gs : List ℕ × List R).1.map fun _ => false) = + if ∀ j ∈ S, j ∈ occurrences gs ∨ X.getD j false = true then 1 else 0 := rfl end Entries @@ -614,15 +617,15 @@ private theorem getD_foldl_pathStep_of_not_mem (gs : List GateCode) {j : ℕ} /-- The condition of the glue and scalar vertices on the final registers `r` and `r'` of the two copies. -/ -private def FinalOK (X : List Bool) (gs : List GateCode) (r r' : ℕ → Bool) : Prop := +private def FinalOK (S : List ℕ) (X : List Bool) (gs : List GateCode) (r r' : ℕ → Bool) : Prop := (∀ w < (occurrences gs).length, lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w → - GlueOK (r ((occurrences gs).getD w 0)) (r' ((occurrences gs).getD w 0)) + GlueOK S (r ((occurrences gs).getD w 0)) (r' ((occurrences gs).getD w 0)) ((occurrences gs).getD w 0)) ∧ - (0 ∈ occurrences gs ∨ X.getD 0 false = true) + ∀ j ∈ S, j ∈ occurrences gs ∨ X.getD j false = true -private instance (X : List Bool) (gs : List GateCode) (r r' : ℕ → Bool) : - Decidable (FinalOK X gs r r') := by +private instance (S : List ℕ) (X : List Bool) (gs : List GateCode) (r r' : ℕ → Bool) : + Decidable (FinalOK S X gs r r') := by unfold FinalOK infer_instance @@ -636,13 +639,13 @@ private theorem mem_iff_getD (qs : List ℕ) (j : ℕ) : exact ⟨w, hw, (List.getD_eq_getElem _ _ hw).symm⟩ /-- **The glue condition on two final registers** that agree with the input register off the -touched qubits: the registers are equal and qubit `0` is one. -/ -private theorem finalOK_iff (X : List Bool) (gs : List GateCode) {s s' : List Bool} +touched qubits: the registers are equal and every qubit of `S` is one. -/ +private theorem finalOK_iff (S : List ℕ) (X : List Bool) (gs : List GateCode) {s s' : List Bool} (hs : s.length = X.length) (hs' : s'.length = X.length) (hu : ∀ j ∉ occurrences gs, s.getD j false = X.getD j false) (hu' : ∀ j ∉ occurrences gs, s'.getD j false = X.getD j false) : - FinalOK X gs (fun j => s.getD j false) (fun j => s'.getD j false) ↔ - s = s' ∧ s.headD false = true := by + FinalOK S X gs (fun j => s.getD j false) (fun j => s'.getD j false) ↔ + s = s' ∧ ∀ j ∈ S, s.getD j false = true := by set qs := occurrences gs -- a touched qubit has a last occurrence have hlast (j : ℕ) (hj : j ∈ qs) : ∃ w < qs.length, qs.getD w 0 = j ∧ @@ -654,32 +657,26 @@ private theorem finalOK_iff (X : List Bool) (gs : List GateCode) {s s' : List Bo | some w => obtain ⟨hw, hwj, -⟩ := lastOcc_eq_some hl exact ⟨w, hw, hwj, hwj ▸ hl⟩ - have hhead : s.headD false = s.getD 0 false := by cases s <;> rfl constructor - · rintro ⟨hglue, h0⟩ - have heq (j : ℕ) : s.getD j false = s'.getD j false ∧ (j = 0 → j ∈ qs → - s.getD 0 false = true) := by + · rintro ⟨hglue, hS⟩ + have heq (j : ℕ) : s.getD j false = s'.getD j false ∧ (j ∈ S → j ∈ qs → + s.getD j false = true) := by by_cases hj : j ∈ qs · obtain ⟨w, hw, rfl, hl⟩ := hlast j hj - obtain ⟨h1, h2⟩ := hglue w hw hl - exact ⟨h1, fun h _ => h ▸ h2 h⟩ + exact ⟨(hglue w hw hl).1, fun h _ => (hglue w hw hl).2 h⟩ · exact ⟨(hu j hj).trans (hu' j hj).symm, fun _ h => absurd h hj⟩ - refine ⟨List.ext_getElem (hs.trans hs'.symm) fun j h₁ h₂ => ?_, ?_⟩ + refine ⟨List.ext_getElem (hs.trans hs'.symm) fun j h₁ h₂ => ?_, fun j hj => ?_⟩ · have := (heq j).1 rwa [List.getD_eq_getElem _ _ h₁, List.getD_eq_getElem _ _ h₂] at this - · rw [hhead] - by_cases h : 0 ∈ qs - · exact (heq 0).2 rfl h - · rw [hu 0 h] - exact h0.resolve_left h - · rintro ⟨rfl, hs0⟩ - refine ⟨fun w _ _ => ⟨rfl, fun h => ?_⟩, ?_⟩ - · change s.getD (qs.getD w 0) false = true - rw [h, ← hhead] - exact hs0 - by_cases h : 0 ∈ qs + · by_cases h : j ∈ qs + · exact (heq j).2 hj h + · rw [hu j h] + exact (hS j hj).resolve_left h + · rintro ⟨rfl, hs1⟩ + refine ⟨fun w _ _ => ⟨rfl, hs1 _⟩, fun j hj => ?_⟩ + by_cases h : j ∈ qs · exact Or.inl h - · exact Or.inr ((hu 0 h).symm.trans (hhead ▸ hs0)) + · exact Or.inr ((hu j h).symm.trans (hs1 j hj)) /-! ### The contraction -/ @@ -725,12 +722,13 @@ private theorem map_pairFun_right {N : ℕ} (a b : Fin N → Bool) : simp only [List.length_map, List.length_range] at h₁ simp [show N + u < 2 * N by omega] -/-- **The contraction of the network is the acceptance gap.** With `-1` as the Hadamard sign, over -any commutative ring, the contraction of the network of well-formed gate codes on the input -register `X` is `acceptGap gs X`. -/ -theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List GateCode) - (hgs : ∀ g ∈ gs, g.WellFormed X.length) : - (network (-1 : R) X gs).2.contraction = (acceptGap gs X : R) := by +/-- **The contraction of the network is the projected gap.** With `-1` as the Hadamard sign, over +any commutative ring, the contraction of the network of well-formed gate codes with the measured +qubits `S` on the input register `X` is `onesGap S gs X`; for `S = [0]` it is the acceptance gap +(`Cslib.Quantum.acceptGap_eq_onesGap`). -/ +theorem contraction_network {R : Type*} [CommRing R] (S : List ℕ) (X : List Bool) + (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed X.length) : + (network (-1 : R) S X gs).2.contraction = (onesGap S gs X : R) := by set qs := occurrences gs with hqs set N := qs.length with hN set Xf : ℕ → Bool := fun j => X.getD j false @@ -759,10 +757,10 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List exact List.map_congr_left fun i hi => entry_gateVertex _ X gs c i (hq3 i (List.mem_range.mp hi)) Z -- the glue and scalar vertices - have hG (Z : ℕ → Bool) : ((List.range N).map (glueVertex gs)).map + have hG (Z : ℕ → Bool) : ((List.range N).map (glueVertex S gs)).map (fun v => entry (v : List ℕ × List R).2 (v.1.map fun e => Z (e / 2))) = (List.range N).map fun w => if (lastOcc qs (qs.getD w 0) N = some w → - GlueOK (reg qs Xf (fun u => Z (0 * N + u)) N (qs.getD w 0)) + GlueOK S (reg qs Xf (fun u => Z (0 * N + u)) N (qs.getD w 0)) (reg qs Xf (fun u => Z (1 * N + u)) N (qs.getD w 0)) (qs.getD w 0)) then 1 else 0 := by rw [List.map_map] refine List.map_congr_left fun w _ => ?_ @@ -773,15 +771,15 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List have hrows (Z : ℕ → Bool) : (((List.range gs.length).map (gateVertex (-1 : R) X gs 0) ++ (List.range gs.length).map (gateVertex (-1 : R) X gs 1) ++ - (List.range N).map (glueVertex gs) ++ [scalarVertex X gs]).map + (List.range N).map (glueVertex S gs) ++ [scalarVertex S X gs]).map fun v : List ℕ × List R => entry v.2 (v.1.map fun e => Z (e / 2))).prod = P (fun u => Z (0 * N + u)) * (P (fun u => Z (1 * N + u)) * - if FinalOK X gs (reg qs Xf (fun u => Z (0 * N + u)) N) + if FinalOK S X gs (reg qs Xf (fun u => Z (0 * N + u)) N) (reg qs Xf (fun u => Z (1 * N + u)) N) then 1 else 0) := by simp only [List.map_append, List.prod_append, hP, hG, List.prod_map_boole, List.map_cons, List.map_nil, List.prod_cons, List.prod_nil, mul_one] - rw [show entry (scalarVertex X gs : List ℕ × List R).2 ((scalarVertex X gs).1.map - fun e => Z (e / 2)) = if 0 ∈ qs ∨ X.getD 0 false = true then 1 else 0 from rfl] + rw [show entry (scalarVertex S X gs : List ℕ × List R).2 ((scalarVertex S X gs).1.map + fun e => Z (e / 2)) = if ∀ j ∈ S, j ∈ qs ∨ X.getD j false = true then 1 else 0 from rfl] simp only [P, FinalOK, ← hqs, ← hN, ite_and, List.mem_range] split_ifs <;> ring -- the pair reduction and the two halves @@ -790,7 +788,7 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List rw [contraction_append_pairs] refine (sum_pair N fun Z => (((List.range gs.length).map (gateVertex (-1 : R) X gs 0) ++ (List.range gs.length).map (gateVertex (-1 : R) X gs 1) ++ - (List.range N).map (glueVertex gs) ++ [scalarVertex X gs]).map + (List.range N).map (glueVertex S gs) ++ [scalarVertex S X gs]).map fun v : List ℕ × List R => entry v.2 (v.1.map fun e => Z (e / 2))).prod).trans ?_ simp only [hrows] -- each copy is a run along the occurrences @@ -799,14 +797,14 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List prod_gateEntry_mul (-1 : R) gs Xf y Φ have hstep (a b : Fin N → Bool) : P (fun u => pairFun a b (0 * N + u)) * (P (fun u => pairFun a b (1 * N + u)) * - if FinalOK X gs (reg qs Xf (fun u => pairFun a b (0 * N + u)) N) + if FinalOK S X gs (reg qs Xf (fun u => pairFun a b (0 * N + u)) N) (reg qs Xf (fun u => pairFun a b (1 * N + u)) N) then 1 else 0) = trajWeight (-1) gs Xf (List.ofFn a) fun r0 => - trajWeight (-1) gs Xf (List.ofFn b) fun r1 => if FinalOK X gs r0 r1 then 1 else 0 := by + trajWeight (-1) gs Xf (List.ofFn b) fun r1 => if FinalOK S X gs r0 r1 then 1 else 0 := by have h1 := hB (fun u => pairFun a b (1 * N + u)) fun r1 => - if FinalOK X gs (reg qs Xf (fun u => pairFun a b (0 * N + u)) N) r1 then (1 : R) else 0 + if FinalOK S X gs (reg qs Xf (fun u => pairFun a b (0 * N + u)) N) r1 then (1 : R) else 0 have h0 := hB (fun u => pairFun a b (0 * N + u)) fun r0 => - trajWeight (-1) gs Xf (List.ofFn b) fun r1 => if FinalOK X gs r0 r1 then (1 : R) else 0 + trajWeight (-1) gs Xf (List.ofFn b) fun r1 => if FinalOK S X gs r0 r1 then (1 : R) else 0 simp only [map_pairFun_left, map_pairFun_right] at h0 h1 rw [h1] exact h0 @@ -818,30 +816,33 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List rwa [sgn, ite_eq_right Bool.false_ne_true, one_mul] at this have hinner (a : Fin N → Bool) : ∑ b : Fin N → Bool, (trajWeight (-1) gs Xf (List.ofFn a) fun r0 => trajWeight (-1) gs Xf (List.ofFn b) fun r1 => - if FinalOK X gs r0 r1 then (1 : R) else 0) = + if FinalOK S X gs r0 r1 then (1 : R) else 0) = trajWeight (-1) gs Xf (List.ofFn a) fun r0 => ∑ π' : Fin (hadamardCount gs) → Bool, sgn (gs.foldl pathStep (X, List.ofFn π', false)).2.2 * - if FinalOK X gs r0 (fun j => (gs.foldl pathStep (X, List.ofFn π', false)).1.getD j false) + if FinalOK S X gs r0 + (fun j => (gs.foldl pathStep (X, List.ofFn π', false)).1.getD j false) then 1 else 0 := by rw [← trajWeight_sum] congr 1 funext r0 exact hC _ simp only [hstep, hinner] - rw [hC, acceptGap, Int.cast_sum] + rw [hC, onesGap, Int.cast_sum] refine Finset.sum_congr rfl fun π _ => ?_ rw [Int.cast_sum, Finset.mul_sum] refine Finset.sum_congr rfl fun π' _ => ?_ set f := gs.foldl pathStep (X, List.ofFn π, false) set f' := gs.foldl pathStep (X, List.ofFn π', false) - have hok := finalOK_iff X gs (s := f.1) (s' := f'.1) (length_foldl_pathStep _ _ _ _) + have hok := finalOK_iff S X gs (s := f.1) (s' := f'.1) (length_foldl_pathStep _ _ _ _) (length_foldl_pathStep _ _ _ _) (fun j hj => getD_foldl_pathStep_of_not_mem gs hj _ _ _) (fun j hj => getD_foldl_pathStep_of_not_mem gs hj _ _ _) - have hterm : acceptTerm gs X (List.ofFn π) (List.ofFn π') = - if f.1 = f'.1 ∧ f.1.headD false = true then (if f.2.2 ^^ f'.2.2 then -1 else 1) else 0 := + have hterm : onesTerm S gs X (List.ofFn π) (List.ofFn π') = + if f.1 = f'.1 ∧ ∀ j ∈ S, f.1.getD j false = true then + (if f.2.2 ^^ f'.2.2 then -1 else 1) else 0 := by + simp only [onesTerm, pathRun, List.all_eq_true] rfl rw [hterm] - by_cases hc : f.1 = f'.1 ∧ f.1.headD false = true + by_cases hc : f.1 = f'.1 ∧ ∀ j ∈ S, f.1.getD j false = true · rw [ite_eq_left (hok.mpr hc), ite_eq_left hc] cases f.2.2 <;> cases f'.2.2 <;> simp [sgn] · rw [ite_eq_right (mt hok.mp hc), ite_eq_right hc] @@ -850,9 +851,9 @@ theorem contraction_network {R : Type*} [CommRing R] (X : List Bool) (gs : List /-! ### Residues of the contraction over `ℕ` -/ /-- **Mapping the tables maps the Hadamard sign.** -/ -theorem network_mapTables {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) (neg : R) - (X : List Bool) (gs : List GateCode) : - (network neg X gs).2.mapTables ψ = (network (ψ neg) X gs).2 := by +theorem network_mapTables {R R' : Type*} [CommSemiring R] [CommSemiring R'] (ψ : R →+* R') + (neg : R) (S : List ℕ) (X : List Bool) (gs : List GateCode) : + (network neg S X gs).2.mapTables ψ = (network (ψ neg) S X gs).2 := by have hgate (c i : ℕ) : (fun v : List ℕ × List R => (v.1, v.2.map ψ)) (gateVertex neg X gs c i) = gateVertex (ψ neg) X gs c i := by simp only [gateVertex, boundedTable, List.map_map] @@ -860,8 +861,8 @@ theorem network_mapTables {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : funext idx simp only [Function.comp_apply, gateEntry] split_ifs <;> simp - have hglue (w : ℕ) : (fun v : List ℕ × List R => (v.1, v.2.map ψ)) (glueVertex gs w) = - glueVertex gs w := by + have hglue (w : ℕ) : (fun v : List ℕ × List R => (v.1, v.2.map ψ)) (glueVertex S gs w) = + glueVertex S gs w := by simp only [glueVertex] split_ifs <;> simp [acceptTable, eqTable] simp only [network, ExplicitTensorNetwork.mapTables, List.map_append, List.map_map, @@ -870,23 +871,24 @@ theorem network_mapTables {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : congr 3 split_ifs <;> simp -/-- **The residue of the contraction over `ℕ` is the acceptance gap.** With `2 ^ P - 1`, which is +/-- **The residue of the contraction over `ℕ` is the projected gap.** With `2 ^ P - 1`, which is `-1` modulo `2 ^ P`, as the Hadamard sign, and `P` larger than the number of Hadamard gates, the -contraction of the network of well-formed gate codes on a nonempty input register `X`, reduced -modulo `2 ^ P`, is the acceptance gap. -/ -theorem contraction_network_mod (X : List Bool) (hX : 0 < X.length) (gs : List GateCode) +contraction of the network of well-formed gate codes with the measured qubits `S` on the input +register `X`, reduced modulo `2 ^ P`, is the projected gap. -/ +theorem contraction_network_mod (S : List ℕ) (X : List Bool) (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed X.length) {P : ℕ} (hP : hadamardCount gs < P) : - (network (2 ^ P - 1 : ℕ) X gs).2.contraction % 2 ^ P = (acceptGap gs X).toNat := by - obtain ⟨h0, h1⟩ := acceptGap_nonneg_and_le hX gs hgs - have hlt : (acceptGap gs X).toNat < 2 ^ P := by - have : (acceptGap gs X).toNat ≤ 2 ^ hadamardCount gs := Int.toNat_le.mpr (by exact_mod_cast h1) + (network (2 ^ P - 1 : ℕ) S X gs).2.contraction % 2 ^ P = (onesGap S gs X).toNat := by + obtain ⟨h0, h1⟩ := onesGap_nonneg_and_le S gs hgs + have hlt : (onesGap S gs X).toNat < 2 ^ P := by + have : (onesGap S gs X).toNat ≤ 2 ^ hadamardCount gs := + Int.toNat_le.mpr (by exact_mod_cast h1) exact this.trans_lt (Nat.pow_lt_pow_right (by norm_num) hP) - have hcast : ((network (2 ^ P - 1 : ℕ) X gs).2.contraction : ZMod (2 ^ P)) = - ((acceptGap gs X).toNat : ZMod (2 ^ P)) := by + have hcast : ((network (2 ^ P - 1 : ℕ) S X gs).2.contraction : ZMod (2 ^ P)) = + ((onesGap S gs X).toNat : ZMod (2 ^ P)) := by have hneg : ((2 ^ P - 1 : ℕ) : ZMod (2 ^ P)) = -1 := by rw [Nat.cast_sub Nat.one_le_two_pow, ZMod.natCast_self, Nat.cast_one, zero_sub] rw [← Nat.coe_castRingHom, ExplicitTensorNetwork.map_contraction, network_mapTables, - Nat.coe_castRingHom, hneg, contraction_network X gs hgs, ← Int.toNat_of_nonneg h0] + Nat.coe_castRingHom, hneg, contraction_network S X gs hgs, ← Int.toNat_of_nonneg h0] simp rw [ZMod.natCast_eq_natCast_iff'] at hcast rw [hcast, Nat.mod_eq_of_lt hlt] @@ -925,22 +927,23 @@ theorem length_occurrences_le {m : ℕ} {gs : List GateCode} (hgs : ∀ g ∈ gs variable {R : Type*} [Zero R] [One R] -@[simp] theorem fst_network (neg : R) (X : List Bool) (gs : List GateCode) : - (network neg X gs).1 = 2 * gs.length + (occurrences gs).length + 1 := rfl +@[simp] theorem fst_network (neg : R) (S : List ℕ) (X : List Bool) (gs : List GateCode) : + (network neg S X gs).1 = 2 * gs.length + (occurrences gs).length + 1 := rfl /-- **Four edges per occurrence.** -/ -@[simp] theorem numEdges_network (neg : R) (X : List Bool) (gs : List GateCode) : - (network neg X gs).2.numEdges = 2 * (2 * (occurrences gs).length) := rfl +@[simp] theorem numEdges_network (neg : R) (S : List ℕ) (X : List Bool) (gs : List GateCode) : + (network neg S X gs).2.numEdges = 2 * (2 * (occurrences gs).length) := rfl -theorem vertices_network (neg : R) (X : List Bool) (gs : List GateCode) : - (network neg X gs).2.vertices = +theorem vertices_network (neg : R) (S : List ℕ) (X : List Bool) (gs : List GateCode) : + (network neg S X gs).2.vertices = (List.range gs.length).map (gateVertex neg X gs 0) ++ (List.range gs.length).map (gateVertex neg X gs 1) ++ - (List.range (occurrences gs).length).map (glueVertex gs) ++ [scalarVertex X gs] ++ + (List.range (occurrences gs).length).map (glueVertex S gs) ++ [scalarVertex S X gs] ++ (List.range (2 * (occurrences gs).length)).map fun w => ([2 * w, 2 * w + 1], eqTable) := rfl -@[simp] theorem length_vertices_network (neg : R) (X : List Bool) (gs : List GateCode) : - (network neg X gs).2.vertices.length = +@[simp] theorem length_vertices_network (neg : R) (S : List ℕ) (X : List Bool) + (gs : List GateCode) : + (network neg S X gs).2.vertices.length = 2 * gs.length + 3 * (occurrences gs).length + 1 := by simp only [vertices_network, List.length_append, List.length_map, List.length_range, List.length_singleton] @@ -948,8 +951,8 @@ theorem vertices_network (neg : R) (X : List Bool) (gs : List GateCode) : /-- **Degree at most six**: a gate vertex has at most two ports per qubit, and the other vertices at most two ports. -/ -theorem maxDegree_network_le (neg : R) (X : List Bool) {gs : List GateCode} - (hgs : ∀ g ∈ gs, (qubits g).length ≤ 3) : (network neg X gs).2.maxDegree ≤ 6 := by +theorem maxDegree_network_le (neg : R) (S : List ℕ) (X : List Bool) {gs : List GateCode} + (hgs : ∀ g ∈ gs, (qubits g).length ≤ 3) : (network neg S X gs).2.maxDegree ≤ 6 := by refine (ExplicitTensorNetwork.maxDegree_le_iff _).mpr fun v hv => ?_ simp only [vertices_network, List.mem_append, List.mem_map, List.mem_range, List.mem_singleton] at hv @@ -964,9 +967,9 @@ theorem maxDegree_network_le (neg : R) (X : List Bool) {gs : List GateCode} · simp /-- **Full tables**: every table lists `2 ^ (number of ports)` entries. -/ -theorem length_table_network (neg : R) (X : List Bool) {gs : List GateCode} +theorem length_table_network (neg : R) (S : List ℕ) (X : List Bool) {gs : List GateCode} (hgs : ∀ g ∈ gs, (qubits g).length ≤ 3) : - ∀ v ∈ (network neg X gs).2.vertices, v.2.length = 2 ^ v.1.length := by + ∀ v ∈ (network neg S X gs).2.vertices, v.2.length = 2 ^ v.1.length := by intro v hv simp only [vertices_network, List.mem_append, List.mem_map, List.mem_range, List.mem_singleton] at hv @@ -1210,16 +1213,16 @@ private theorem count_gatePorts_link {gs : List GateCode} (i : ℕ) exact Or.inr ⟨_, hj, u, hw, rfl⟩ /-- The glue vertices have only link edges among their ports. -/ -private theorem count_glueVertex_out (gs : List GateCode) (w v : ℕ) : - (glueVertex gs w : List ℕ × List R).1.count (2 * v) = 0 := by +private theorem count_glueVertex_out (S : List ℕ) (gs : List GateCode) (w v : ℕ) : + (glueVertex S gs w : List ℕ × List R).1.count (2 * v) = 0 := by unfold glueVertex split_ifs <;> simp only [List.count_cons, List.count_nil, beq_iff_eq] <;> split_ifs <;> omega /-- The link edge of occurrence `u` in copy `c` is a port of the glue vertex of `w` exactly when `w = u` is the last occurrence of its qubit. -/ -private theorem count_glueVertex_link (gs : List GateCode) {w c u : ℕ} +private theorem count_glueVertex_link (S : List ℕ) (gs : List GateCode) {w c u : ℕ} (hw : w < (occurrences gs).length) (hc : c < 2) (hu : u < (occurrences gs).length) : - (glueVertex gs w : List ℕ × List R).1.count (2 * (c * (occurrences gs).length + u) + 1) = + (glueVertex S gs w : List ℕ × List R).1.count (2 * (c * (occurrences gs).length + u) + 1) = if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = some w ∧ w = u then 1 else 0 := by unfold glueVertex @@ -1241,9 +1244,9 @@ private theorem sum_range_base (gs : List GateCode) {u : ℕ} (hu : u < (occurre /-- **The network is rows-first bipartite** with the gate, glue and scalar vertices as rows, when the qubits of every gate are distinct. -/ -theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCode} +theorem isRowsFirstBipartite_network (neg : R) (S : List ℕ) (X : List Bool) {gs : List GateCode} (hnd : ∀ g ∈ gs, (qubits g).Nodup) : - (network neg X gs).2.IsRowsFirstBipartite (network neg X gs).1 := by + (network neg S X gs).2.IsRowsFirstBipartite (network neg S X gs).1 := by have hnd' (i : ℕ) (hi : i ∈ List.range gs.length) : (qubits (gs.getD i default)).Nodup := hnd _ (List.getD_eq_getElem _ default (List.mem_range.mp hi) ▸ List.getElem_mem (List.mem_range.mp hi)) @@ -1297,7 +1300,7 @@ theorem isRowsFirstBipartite_network (neg : R) (X : List Bool) {gs : List GateCo (show 0 < 2 by omega) hu, List.map_congr_left fun i hi => count_gatePorts_link i (hnd' i hi) hc (show 1 < 2 by omega) hu, - List.map_congr_left fun w hw => count_glueVertex_link gs (List.mem_range.mp hw) hc hu, + List.map_congr_left fun w hw => count_glueVertex_link S gs (List.mem_range.mp hw) hc hu, List.sum_map_ite_and, List.sum_map_ite_and] have hglue : ((List.range (occurrences gs).length).map fun w => if lastOcc (occurrences gs) ((occurrences gs).getD w 0) (occurrences gs).length = diff --git a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean index 8c1d0379d..737167d55 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetworkPolyTime.lean @@ -18,6 +18,7 @@ public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Unary public import Complexity.Computability.Machines.Turing.MultiTape.PolyTime import Complexity.Computability.Machines.Turing.MultiTape.Combinators.TakeDropUnary import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Membership import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain @@ -32,8 +33,8 @@ import Complexity.Foundations.Data.List.FilterMap # The network of a circuit in polynomial time The network of a list of gate codes (`Cslib.Quantum.PathNetwork.network`) is computable in -polynomial time from the gate codes, the input register and the Hadamard sign, with its number of -rows in binary and the network in the encoding of the contraction algorithm +polynomial time from the gate codes, the measured qubits, the input register and the Hadamard sign, +with its number of rows in binary and the network in the encoding of the contraction algorithm (`ExplicitTensorNetwork.encExplicitNetwork`). Every port and last occurrence is a search or a map over the occurrences of qubits, written in unary, and every table is a map over the first `2 ^ 6` numbers, a constant list. @@ -81,11 +82,11 @@ private theorem inBit_eq (X : List Bool) (gs : List GateCode) (c i idx : ℕ) : unfold inBit inPort cases lastOcc (occurrences gs) j (base gs i) <;> rfl -private theorem scalarVertex_eq (X : List Bool) (gs : List GateCode) : - (scalarVertex X gs : List ℕ × List ℕ) = - ([], [if ((occurrences gs).any fun q => decide (q = 0)) || X.getD 0 false then 1 +private theorem scalarVertex_eq (S : List ℕ) (X : List Bool) (gs : List GateCode) : + (scalarVertex S X gs : List ℕ × List ℕ) = + ([], [if (S.all fun j => decide (j ∈ occurrences gs) || X.getD j false) then 1 else 0]) := by - simp only [scalarVertex, List.any_eq_true, decide_eq_true_eq, exists_eq_right, Bool.or_eq_true] + simp only [scalarVertex, List.all_eq_true, Bool.or_eq_true, decide_eq_true_eq] /-- The encoding of networks is the encoding of their number of edges and vertices. -/ private theorem computableInPolyTime_equivProd_symm : @@ -194,8 +195,10 @@ theorem computableInPolyTime_gateVertex {neg : α → ℕ} {X : α → List Bool (allWithInput htl (snd _ _)))) htl)) (const 1) (const 0) /-- **Glue vertices are polynomial-time.** -/ -theorem computableInPolyTime_glueVertex {w : α → ℕ} (hw : ComputableInPolyTime w inp encUnary) : - ComputableInPolyTime (fun a => (glueVertex (gs a) (w a) : List ℕ × List ℕ)) inp +theorem computableInPolyTime_glueVertex {S : α → List ℕ} + (hS : ComputableInPolyTime S inp (encList encUnary)) {w : α → ℕ} + (hw : ComputableInPolyTime w inp encUnary) : + ComputableInPolyTime (fun a => (glueVertex (S a) (gs a) (w a) : List ℕ × List ℕ)) inp (encPair (encList encNat) (encList encNat)) := by have hqs := computableInPolyTime_occurrences hgs have hN := (length_list _).comp hqs @@ -204,27 +207,30 @@ theorem computableInPolyTime_glueVertex {w : α → ℕ} (hw : ComputableInPolyT refine ite_decide (decide_eq' (computableInPolyTime_lastOcc hqs hq hN) (optionSome _ |>.comp hw)) ((ports_unary_to_binary (cons' (unary_add' (unary_mul' (const 2) hw) (const 1)) (cons' (unary_add' (unary_mul' (const 2) (unary_add' hN hw)) (const 1)) (const [])))).prod - (ite_decide (decide_eq' hq (const 0)) (const _) (const _))) (const _) + (ite_decide (decide_mem hS hq) (const _) (const _))) (const _) end Vertices /-- **The scalar vertex is polynomial-time.** -/ -theorem computableInPolyTime_scalarVertex {X : α → List Bool} {gs : α → List GateCode} +theorem computableInPolyTime_scalarVertex {S : α → List ℕ} {X : α → List Bool} + {gs : α → List GateCode} (hS : ComputableInPolyTime S inp (encList encUnary)) (hX : ComputableInPolyTime X inp encBits) (hgs : ComputableInPolyTime gs inp (encList encGateCode)) : - ComputableInPolyTime (fun a => (scalarVertex (X a) (gs a) : List ℕ × List ℕ)) inp + ComputableInPolyTime (fun a => (scalarVertex (S a) (X a) (gs a) : List ℕ × List ℕ)) inp (encPair (encList encNat) (encList encNat)) := by simp only [scalarVertex_eq] - refine (const []).prod (cons' (ite_decide (decide_eq' (bool_or.comp - ((anyWithInput (computableInPolyTime_occurrences hgs) (decide_eq' (snd _ _) (const 0))).prod - (bitsGetDWithInput hX (const 0) false))) (const true)) (const 1) (const 0)) (const [])) - -/-- **The network of a circuit is polynomial-time** in the gate codes, the input register and the -Hadamard sign, with its number of rows in binary. -/ -theorem computableInPolyTime_network {neg : α → ℕ} {X : α → List Bool} {gs : α → List GateCode} - (hneg : ComputableInPolyTime neg inp encNat) (hX : ComputableInPolyTime X inp encBits) + refine (const []).prod (cons' (ite_decide (decide_eq' (allWithInput hS (bool_or.comp + ((decide_mem (computableInPolyTime_occurrences (hgs.comp (fst _ _))) (snd _ _)).prod + (bitsGetDWithInput (hX.comp (fst _ _)) (snd _ _) false)))) (const true)) (const 1) (const 0)) + (const [])) + +/-- **The network of a circuit is polynomial-time** in the gate codes, the measured qubits, the +input register and the Hadamard sign, with its number of rows in binary. -/ +theorem computableInPolyTime_network {neg : α → ℕ} {S : α → List ℕ} {X : α → List Bool} + {gs : α → List GateCode} (hneg : ComputableInPolyTime neg inp encNat) + (hS : ComputableInPolyTime S inp (encList encUnary)) (hX : ComputableInPolyTime X inp encBits) (hgs : ComputableInPolyTime gs inp (encList encGateCode)) : - ComputableInPolyTime (fun a => network (neg a) (X a) (gs a)) inp + ComputableInPolyTime (fun a => network (neg a) (S a) (X a) (gs a)) inp (encPair encNat ExplicitTensorNetwork.encExplicitNetwork) := by have hL := (length_list _).comp hgs have hN := (length_list _).comp (computableInPolyTime_occurrences hgs) @@ -236,15 +242,15 @@ theorem computableInPolyTime_network {neg : α → ℕ} {X : α → List Bool} { have hverts : ComputableInPolyTime (fun a => (List.range (gs a).length).map (gateVertex (neg a) (X a) (gs a) 0) ++ (List.range (gs a).length).map (gateVertex (neg a) (X a) (gs a) 1) ++ - (List.range (occurrences (gs a)).length).map (glueVertex (gs a)) ++ - [scalarVertex (X a) (gs a)] ++ + (List.range (occurrences (gs a)).length).map (glueVertex (S a) (gs a)) ++ + [scalarVertex (S a) (X a) (gs a)] ++ (List.range (2 * (occurrences (gs a)).length)).map fun w => (([2 * w, 2 * w + 1], eqTable) : List ℕ × List ℕ)) inp (encList (encPair (encList encNat) (encList encNat))) := listAppend' (listAppend' (listAppend' (listAppend' (hgate 0) (hgate 1)) (mapWithInput (range.comp hN) (computableInPolyTime_glueVertex (hgs.comp (fst _ _)) - (snd _ _)))) - (cons' (computableInPolyTime_scalarVertex hX hgs) (const []))) + (hS.comp (fst _ _)) (snd _ _)))) + (cons' (computableInPolyTime_scalarVertex hS hX hgs) (const []))) (mapWithInput (range.comp (unary_mul' (const 2) hN)) ((ports_unary_to_binary (cons' (unary_mul' (const 2) (snd _ _)) (cons' (unary_add' (unary_mul' (const 2) (snd _ _)) (const 1)) (const [])))).prod diff --git a/Complexity/Computability/Circuit/Quantum/PathSum.lean b/Complexity/Computability/Circuit/Quantum/PathSum.lean index d2c2c2e73..33b813547 100644 --- a/Complexity/Computability/Circuit/Quantum/PathSum.lean +++ b/Complexity/Computability/Circuit/Quantum/PathSum.lean @@ -28,7 +28,10 @@ Squaring and summing over the accepting outcomes, the acceptance probability is *pairs* of paths that end in the same accepting register, divided by `2^h` (`Cslib.Quantum.acceptProb_eq_acceptGap`). Both counts are integers computed from polynomial-time simulations, which places `BQP` in the counting classes -(`Complexity/Computability/Complexity/Classes/BQP/Counting.lean`). +(`Complexity/Computability/Complexity/Classes/BQP/Counting.lean`). The same holds for the +probability that every qubit of a list `S` reads `1`, with the pairs of paths ending in the same +register in which every qubit of `S` is one (`Cslib.Quantum.onesProb_eq_onesGap`); acceptance is +the case `S = [0]` (`Cslib.Quantum.acceptGap_eq_onesGap`). ## Main definitions @@ -37,13 +40,17 @@ simulations, which places `BQP` in the counting classes * `Cslib.Quantum.pathTerm`, `Cslib.Quantum.pathGap`: the signed path count of an amplitude. * `Cslib.Quantum.acceptTerm`, `Cslib.Quantum.acceptGap`: the signed count of pairs of paths of the acceptance probability. +* `Cslib.Quantum.onesTerm`, `Cslib.Quantum.onesGap`: the same for the event that every qubit of a + list reads `1`. ## Main results * `Cslib.Quantum.ofFn_inputState`: the input basis state is the input word padded with zeros. * `Cslib.Quantum.run_ofCodes_apply`: amplitudes are path sums. -* `Cslib.Quantum.acceptProb_eq_acceptGap`: the acceptance probability is `acceptGap / 2^h`. -* `Cslib.Quantum.acceptGap_nonneg_and_le`: so the acceptance gap lies between `0` and `2^h`. +* `Cslib.Quantum.onesProb_eq_onesGap`, `Cslib.Quantum.acceptProb_eq_acceptGap`: probabilities are + projected gaps over `2^h`. +* `Cslib.Quantum.onesGap_nonneg_and_le`, `Cslib.Quantum.onesGap_nil_eq_two_pow`: so projected + gaps lie between `0` and `2^h`, with `2^h` for the empty list. * `Cslib.Quantum.acceptGap_nil`: the acceptance gap of the empty circuit is the bit of qubit `0`. ## References @@ -129,6 +136,28 @@ def acceptGap (gs : List GateCode) (x : List Bool) : ℤ := ∑ π : Fin (hadamardCount gs) → Bool, ∑ π' : Fin (hadamardCount gs) → Bool, acceptTerm gs x (List.ofFn π) (List.ofFn π') +/-- The signed contribution of a pair of paths to the probability that every qubit of `S` reads +`1`: the product of their signs when both end in the same register with every qubit of `S` equal +to one, and `0` otherwise. -/ +def onesTerm (S : List ℕ) (gs : List GateCode) (x π π' : List Bool) : ℤ := + match pathRun gs x π, pathRun gs x π' with + | (s, σ), (s', σ') => + if s = s' ∧ (S.all fun j => s.getD j false) = true then (if σ ^^ σ' then -1 else 1) else 0 + +/-- The **projected gap** of `S`: the sum of `onesTerm S` over all pairs of paths. The acceptance +gap is the case `S = [0]` (`Cslib.Quantum.acceptGap_eq_onesGap`). -/ +def onesGap (S : List ℕ) (gs : List GateCode) (x : List Bool) : ℤ := + ∑ π : Fin (hadamardCount gs) → Bool, ∑ π' : Fin (hadamardCount gs) → Bool, + onesTerm S gs x (List.ofFn π) (List.ofFn π') + +theorem acceptGap_eq_onesGap (gs : List GateCode) (x : List Bool) : + acceptGap gs x = onesGap [0] gs x := by + refine Finset.sum_congr rfl fun π _ => Finset.sum_congr rfl fun π' _ => ?_ + simp only [acceptTerm, onesTerm, List.all_cons, List.all_nil, Bool.and_true] + generalize pathRun gs x (List.ofFn π) = r + obtain ⟨s, σ⟩ := r + cases s <;> rfl + /-- The acceptance gap of the empty circuit is the bit of qubit `0`. -/ theorem acceptGap_nil (x : List Bool) : acceptGap [] x = if x.headD false then 1 else 0 := by simp only [acceptGap, hadamardCount_nil, acceptTerm, @@ -244,20 +273,21 @@ private theorem pathTerm_eq (gs : List GateCode) (x y π : List Bool) : if (pathRun gs x π).1 = y then (if (pathRun gs x π).2 then -1 else 1) else 0 := rfl -private theorem acceptTerm_eq (gs : List GateCode) (x π π' : List Bool) : - acceptTerm gs x π π' = - if (pathRun gs x π).1 = (pathRun gs x π').1 ∧ (pathRun gs x π).1.headD false = true then +private theorem onesTerm_eq (S : List ℕ) (gs : List GateCode) (x π π' : List Bool) : + onesTerm S gs x π π' = + if (pathRun gs x π).1 = (pathRun gs x π').1 ∧ + (S.all fun j => (pathRun gs x π).1.getD j false) = true then (if (pathRun gs x π).2 ^^ (pathRun gs x π').2 then -1 else 1) else 0 := rfl -/-- Summing the products of two path contributions over the accepting outcomes gives the -contribution of the pair. -/ -private theorem sum_accepting_pathTerm_mul (hm : 0 < m) (gs : List GateCode) (x π π' : List Bool) +/-- Summing the products of two path contributions over the outcomes in which every qubit of `S` +reads `1` gives the contribution of the pair. -/ +private theorem sum_onesAt_pathTerm_mul (S : List ℕ) (gs : List GateCode) (x π π' : List Bool) (hx : x.length = m) : - ∑ y ∈ accepting m, pathTerm gs x (List.ofFn y) π * pathTerm gs x (List.ofFn y) π' = - acceptTerm gs x π π' := by + ∑ y ∈ onesAt m S, pathTerm gs x (List.ofFn y) π * pathTerm gs x (List.ofFn y) π' = + onesTerm S gs x π π' := by have hs : (pathRun gs x π).1.length = m := (length_foldl_pathStep gs x π false).trans hx - simp only [pathTerm_eq, acceptTerm_eq] + simp only [pathTerm_eq, onesTerm_eq] generalize pathRun gs x π = r at hs ⊢ generalize pathRun gs x π' = r' obtain ⟨s, σ⟩ := r @@ -271,14 +301,13 @@ private theorem sum_accepting_pathTerm_mul (hm : 0 < m) (gs : List GateCode) (x have hiff (y : Fin m → Bool) : s = List.ofFn y ↔ y = y₀ := by rw [hs₀, List.ofFn_inj, eq_comm] simp only [hiff, ite_mul, zero_mul] - rw [Finset.sum_ite_eq' (accepting m) y₀] - have hacc : y₀ ∈ accepting m ↔ s.headD false = true := by - rw [mem_accepting hm] - simp only [y₀] - cases s with - | nil => simp at hs; omega - | cons b s => rfl - by_cases h₀ : y₀ ∈ accepting m + rw [Finset.sum_ite_eq' (onesAt m S) y₀] + have hacc : y₀ ∈ onesAt m S ↔ (S.all fun j => s.getD j false) = true := by + simp only [onesAt, Finset.mem_filter, Finset.mem_univ, true_and, List.all_eq_true, y₀] + refine forall₂_congr fun j _ => ⟨fun ⟨_, h⟩ => h, fun h => ⟨?_, h⟩⟩ + by_contra hj + simp [List.getD_eq_getElem?_getD, List.getElem?_eq_none (show s.length ≤ j by omega)] at h + by_cases h₀ : y₀ ∈ onesAt m S · simp only [h₀, ite_true, ← hs₀, hacc.mp h₀, and_true] by_cases hss : s = s' · subst hss @@ -286,23 +315,24 @@ private theorem sum_accepting_pathTerm_mul (hm : 0 < m) (gs : List GateCode) (x · simp [hss, Ne.symm hss] · simp only [h₀, ite_false, mt hacc.mpr h₀, Bool.false_eq_true, and_false] -/-- The squared amplitude gaps of the accepting outcomes sum to the acceptance gap. -/ -private theorem sum_accepting_pathGap_sq (hm : 0 < m) (gs : List GateCode) (x : List Bool) +/-- The squared amplitude gaps of the outcomes in which every qubit of `S` reads `1` sum to the +projected gap of `S`. -/ +private theorem sum_onesAt_pathGap_sq (S : List ℕ) (gs : List GateCode) (x : List Bool) (hx : x.length = m) : - ∑ y ∈ accepting m, pathGap gs x (List.ofFn y) ^ 2 = acceptGap gs x := by - simp only [pathGap, sq, Finset.sum_mul_sum, acceptGap] + ∑ y ∈ onesAt m S, pathGap gs x (List.ofFn y) ^ 2 = onesGap S gs x := by + simp only [pathGap, sq, Finset.sum_mul_sum, onesGap] rw [Finset.sum_comm] refine Finset.sum_congr rfl fun π _ => ?_ rw [Finset.sum_comm] - exact Finset.sum_congr rfl fun π' _ => sum_accepting_pathTerm_mul hm gs x _ _ hx - -/-- **The acceptance probability is a gap over `2^h`**: for well-formed codes on `m > 0` qubits, -the acceptance probability is `acceptGap` divided by `2^h`, where `h` counts the Hadamard -gates. -/ -theorem acceptProb_eq_acceptGap (hm : 0 < m) (gs : List GateCode) - (hgs : ∀ g ∈ gs, g.WellFormed m) (x : List Bool) : - acceptProb m gs x = - (acceptGap gs (List.ofFn (inputState m x)) : ℝ) / 2 ^ hadamardCount gs := by + exact Finset.sum_congr rfl fun π' _ => sum_onesAt_pathTerm_mul S gs x _ _ hx + +/-- **The probability of an event of ones is a projected gap over `2^h`**: for well-formed codes, +the probability that every qubit of `S` reads `1` is `onesGap S` divided by `2^h`, where `h` counts +the Hadamard gates. -/ +theorem onesProb_eq_onesGap (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed m) (S : List ℕ) + (x : List Bool) : + onesProb m gs S x = + (onesGap S gs (List.ofFn (inputState m x)) : ℝ) / 2 ^ hadamardCount gs := by have hnorm (g : ℤ) : ‖(g : ℂ) * (((Real.sqrt 2)⁻¹ : ℝ) : ℂ) ^ hadamardCount gs‖ ^ 2 = (g : ℝ) ^ 2 / 2 ^ hadamardCount gs := by @@ -310,20 +340,28 @@ theorem acceptProb_eq_acceptGap (hm : 0 < m) (gs : List GateCode) abs_of_nonneg (Real.sqrt_nonneg 2), Complex.norm_intCast, mul_pow, sq_abs, ← pow_mul, mul_comm (hadamardCount gs) 2, pow_mul, inv_pow, Real.sq_sqrt (by norm_num), inv_pow, div_eq_mul_inv] - rw [acceptProb, measureProb] + rw [onesProb, measureProb] simp only [run_ofCodes_apply gs hgs, hnorm, ← Finset.sum_div] congr 1 - exact_mod_cast sum_accepting_pathGap_sq hm gs _ (by simp) + exact_mod_cast sum_onesAt_pathGap_sq S gs _ (by simp) -/-- **The acceptance gap is a count between `0` and `2 ^ h`** for well-formed codes on a nonempty -register `X`, since it is `2 ^ h` times a probability. -/ -theorem acceptGap_nonneg_and_le {X : List Bool} (hX : 0 < X.length) (gs : List GateCode) +/-- **The acceptance probability is a gap over `2^h`**: for well-formed codes on `m` qubits, the +acceptance probability is `acceptGap` divided by `2^h`, where `h` counts the Hadamard gates. -/ +theorem acceptProb_eq_acceptGap (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed m) + (x : List Bool) : + acceptProb m gs x = + (acceptGap gs (List.ofFn (inputState m x)) : ℝ) / 2 ^ hadamardCount gs := by + rw [acceptGap_eq_onesGap, acceptProb_eq_onesProb, onesProb_eq_onesGap gs hgs] + +/-- **A projected gap is a count between `0` and `2 ^ h`** for well-formed codes on the register +`X`, since it is `2 ^ h` times a probability. -/ +theorem onesGap_nonneg_and_le (S : List ℕ) {X : List Bool} (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed X.length) : - 0 ≤ acceptGap gs X ∧ acceptGap gs X ≤ 2 ^ hadamardCount gs := by - have h := acceptProb_eq_acceptGap hX gs hgs X + 0 ≤ onesGap S gs X ∧ onesGap S gs X ≤ 2 ^ hadamardCount gs := by + have h := onesProb_eq_onesGap gs hgs S X rw [ofFn_inputState le_rfl, Nat.sub_self, List.replicate_zero, List.append_nil] at h - have h0 := acceptProb_nonneg X.length gs X - have h1 := acceptProb_le_one X.length gs X + have h0 := onesProb_nonneg X.length gs S X + have h1 := onesProb_le_one X.length gs S X have hpos : (0 : ℝ) < 2 ^ hadamardCount gs := by positivity rw [h] at h0 h1 constructor @@ -331,4 +369,13 @@ theorem acceptGap_nonneg_and_le {X : List Bool} (hX : 0 < X.length) (gs : List G · rw [div_le_one hpos] at h1 exact_mod_cast h1 +/-- **The projected gap of no condition is `2 ^ h`**, the squared norm of the final state. -/ +theorem onesGap_nil_eq_two_pow {X : List Bool} (gs : List GateCode) + (hgs : ∀ g ∈ gs, g.WellFormed X.length) : + onesGap [] gs X = 2 ^ hadamardCount gs := by + have h := onesProb_eq_onesGap gs hgs [] X + rw [ofFn_inputState le_rfl, Nat.sub_self, List.replicate_zero, List.append_nil, + onesProb_nil, eq_div_iff (by positivity), one_mul] at h + exact_mod_cast h.symm + end Cslib.Quantum diff --git a/Complexity/Computability/Circuit/Quantum/Uniform.lean b/Complexity/Computability/Circuit/Quantum/Uniform.lean index b612a5435..b1e274f3c 100644 --- a/Complexity/Computability/Circuit/Quantum/Uniform.lean +++ b/Complexity/Computability/Circuit/Quantum/Uniform.lean @@ -32,6 +32,11 @@ polynomially bounded. On an input word `x` the family runs its circuit for `n = `|x 0^{m - n}⟩` and accepts when the final measurement finds qubit `0` in state `1` (`Cslib.Quantum.UniformCircuitFamily.acceptProb`). +Postselected classes condition on a second measured qubit: the postselected acceptance probability +is the probability that qubit `0` reads `1` given that qubit `1` does +(`Cslib.Quantum.UniformCircuitFamily.postselectedAcceptProb`), as in +[Aaronson][Aaronson2005Postselection], Definition 1, with the two qubits exchanged. + Appending a NOT gate on qubit `0` gives a family with complementary acceptance probability (`Cslib.Quantum.UniformCircuitFamily.appendNot`). @@ -40,6 +45,9 @@ Appending a NOT gate on qubit `0` gives a family with complementary acceptance p * `Cslib.Quantum.encGateCode`: the encoding of gate codes. * `Cslib.Quantum.UniformCircuitFamily`: polynomial-time uniform families of circuits. * `Cslib.Quantum.UniformCircuitFamily.acceptProb`: the acceptance probability on an input word. +* `Cslib.Quantum.UniformCircuitFamily.onesProb`, + `Cslib.Quantum.UniformCircuitFamily.postselectedAcceptProb`: the probability that measured qubits + read `1`, and the acceptance probability postselected on qubit `1`. * `Cslib.Quantum.UniformCircuitFamily.appendNot`: the family with a final NOT on qubit `0`. ## Main results @@ -50,6 +58,8 @@ Appending a NOT gate on qubit `0` gives a family with complementary acceptance p ## References * [S. Arora, B. Barak, *Computational Complexity: A Modern Approach*][AroraBarak09] +* [S. Aaronson, *Quantum computing, postselection, and probabilistic + polynomial-time*][Aaronson2005Postselection] -/ @[expose] public section @@ -90,6 +100,22 @@ theorem acceptProb_nonneg (F : UniformCircuitFamily) (x : List Bool) : 0 ≤ F.a theorem acceptProb_le_one (F : UniformCircuitFamily) (x : List Bool) : F.acceptProb x ≤ 1 := Quantum.acceptProb_le_one _ _ _ +/-- The probability that the final measurement of the family's circuit on the input word `x` finds +every qubit of `S` in state `1`. -/ +noncomputable def onesProb (F : UniformCircuitFamily) (S : List ℕ) (x : List Bool) : ℝ := + Quantum.onesProb (F.width x.length) (F.gates x.length) S x + +theorem acceptProb_eq_onesProb (F : UniformCircuitFamily) (x : List Bool) : + F.acceptProb x = F.onesProb [0] x := + rfl + +/-- The **postselected acceptance probability** of the family on the input word `x`: the +probability that qubit `0` reads `1` conditioned on qubit `1` reading `1`, that is, the probability +that both read `1` divided by the probability that qubit `1` does. It is `0` when qubit `1` never +reads `1`. -/ +noncomputable def postselectedAcceptProb (F : UniformCircuitFamily) (x : List Bool) : ℝ := + F.onesProb [0, 1] x / F.onesProb [1] x + /-- The family with a final NOT gate on qubit `0`. -/ def appendNot (F : UniformCircuitFamily) : UniformCircuitFamily where width := F.width diff --git a/Complexity/Computability/Complexity/Classes/BQP/Counting.lean b/Complexity/Computability/Complexity/Classes/BQP/Counting.lean index c931057a4..4fada9b86 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/Counting.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/Counting.lean @@ -154,8 +154,7 @@ theorem BQP_subset_AWPP : BQP enc ⊆ AWPP enc := by have := F.computableInPolyTime_gates simp only [hadamardCount_eq_countP] fun_prop) (fun a => ?_) - have hp := acceptProb_eq_acceptGap (F.width_pos (enc a).length) _ - (F.wellFormed (enc a).length) (enc a) + have hp := acceptProb_eq_acceptGap _ (F.wellFormed (enc a).length) (enc a) set g := acceptGap (F.gates (enc a).length) (List.ofFn (inputState (F.width (enc a).length) (enc a))) set r := hadamardCount (F.gates (enc a).length) diff --git a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean index 8af90ccfd..021d3df69 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/TensorContraction.lean @@ -12,6 +12,7 @@ public import Complexity.Computability.Circuit.Quantum.PathNetworkPolyTime public import Complexity.Computability.Complexity.Classes.CountingSimulation.FastLeaves public import Complexity.Computability.Complexity.Classes.Time public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain +import Complexity.Computability.GenericRAM.Native.ExpTimePolySpace import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List @@ -23,13 +24,14 @@ import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFr import Complexity.Foundations.Data.Nat.SqrtLog.Linear /-! -# Quantum circuits by tensor contraction: `BQTIME[T] ⊆ DTIME[2 ^ O(T / √log T)]` - -The acceptance probability of a circuit of `L` gates over the Hadamard, NOT, CNOT and Toffoli gates -is `acceptGap / 2 ^ h`, where `h` is the number of Hadamard gates -(`Cslib.Quantum.acceptProb_eq_acceptGap`). The acceptance gap is the contraction of the network of -the circuit and its mirror copy (`Cslib.Quantum.PathNetwork.contraction_network`), and over `ℕ`, -with the Hadamard sign `2 ^ (h + 1) - 1`, its residue modulo `2 ^ (h + 1)` +# Quantum circuits by tensor contraction: quantum time `T` in `DTIME[2 ^ O(T / √log T)]` + +The probability that the final measurement of a circuit of `L` gates over the Hadamard, NOT, CNOT +and Toffoli gates finds every qubit of a list `S` in state `1` is `onesGap S / 2 ^ h`, where `h` is +the number of Hadamard gates (`Cslib.Quantum.onesProb_eq_onesGap`); acceptance is the case +`S = [0]`. The projected gap is the contraction of the network of the circuit and its mirror copy +(`Cslib.Quantum.PathNetwork.contraction_network`), and over `ℕ`, with the Hadamard sign +`2 ^ (h + 1) - 1`, its residue modulo `2 ^ (h + 1)` (`Cslib.Quantum.PathNetwork.contraction_network_mod`). The network has `s = 4 N` edges for `N ≤ 3 L` occurrences of qubits in gates, degree at most six and a bipartition, so it is admissible for the contraction algorithm of Theorem D.1 of [OpenAI2026ETH] @@ -37,35 +39,52 @@ for the contraction algorithm of Theorem D.1 of [OpenAI2026ETH] `2 ^ O(s / √⌊log₂ s⌋)` times a polynomial and in polynomial space. The width of the circuit does not enter the exponent: qubits that no gate touches cost nothing. -So the acceptance gap of polynomial-time computed circuits is computed by one machine in time +So the projected gaps of polynomial-time computed circuits are computed by one machine in time `2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the input length, and in polynomial space -(`QuantumContraction.exists_computableInO_gapValue`). For a uniform family with `O(T)` gates this -decides the problem of the family: `BQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]` for -`max n 2 ≤ T n`, with a joint polynomial space bound `T ^ d` -(`QuantumContraction.BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`, -`QuantumContraction.exists_decidableInTimeAndSpace_of_mem_BQTIME`). No time-constructibility of -`T` is needed, since the machine never computes `T`. The novelty of this bound has not been -checked against the literature. +(`QuantumContraction.exists_computableInO_gapValue`), and so is any polynomial-time rule applied to +two of them (`QuantumContraction.exists_computableInO_rule`). The gaps are exact, so a rule can +test a probability for zero or compare two probabilities. For a uniform family with `O(T)` gates +and `max n 2 ≤ T n`, a problem decided by such a rule lies in `⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]`, +with a joint polynomial space bound `T ^ d` +(`QuantumContraction.exists_decidableInTimeAndSpace_of_onesProb`): + +* `BQTIME[T]`: the acceptance gap `g` against the gap `2 ^ h` of no condition, `2 ^ h < 2 g`; +* `NQTIME[T]`: the acceptance gap is positive; +* `EQTIME[T]`, which lies in `NQTIME[T]`; +* `PostBQTIME[T]`: the gap `g` of qubits `0` and `1` against the gap `g'` of qubit `1`, `g' < 2 g`. + +No time-constructibility of `T` is needed, since the machine never computes `T`. The textbook +characterizations `NQP = coC₌P` [FennerGreenHomerPruim1999] and `PostBQP = PP` +[Aaronson2005Postselection] would lose a polynomial in `T`. The novelty of these bounds has not +been established; a limited search found no direct simulation of these classes in this time. ## Main definitions -* `Cslib.Complexity.QuantumContraction.gapValue`: the acceptance gap as the simulator computes - it. +* `Cslib.Complexity.QuantumContraction.gapValue`: the projected gap as the simulator computes it. ## Main results * `Cslib.Quantum.PathNetwork.explicitAdmissible_network`: the network is admissible. -* `Cslib.Complexity.QuantumContraction.gapValue_eq`: the simulator computes the acceptance gap. -* `Cslib.Complexity.QuantumContraction.exists_computableInO_rule`, - `Cslib.Complexity.QuantumContraction.exists_computableInO_gapValue`: the simulator and its +* `Cslib.Complexity.QuantumContraction.gapValue_eq`: the simulator computes the projected gap. +* `Cslib.Complexity.QuantumContraction.exists_computableInO_gapValue`, + `Cslib.Complexity.QuantumContraction.exists_computableInO_rule`: the simulator and its resources. -* `Cslib.Complexity.QuantumContraction.exists_decidableInTimeAndSpace_of_mem_BQTIME`, - `Cslib.Complexity.QuantumContraction.BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`: the - class containments. +* `Cslib.Complexity.QuantumContraction.exists_decidableInTimeAndSpace_of_onesProb`: deciding by two + projected gaps. +* `Cslib.Complexity.QuantumContraction.BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`, + `Cslib.Complexity.QuantumContraction.NQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`, + `Cslib.Complexity.QuantumContraction.EQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`, + `Cslib.Complexity.QuantumContraction.PostBQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two`, + with the joint forms `exists_decidableInTimeAndSpace_of_mem_BQTIME`, `…_NQTIME` and + `…_PostBQTIME`: the class containments. ## References * [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Theorem D.1 (p. 53). +* [S. Fenner, F. Green, S. Homer, R. Pruim, *Determining acceptance possibility for a quantum + computation is hard for the polynomial hierarchy*][FennerGreenHomerPruim1999] +* [S. Aaronson, *Quantum computing, postselection, and probabilistic + polynomial-time*][Aaronson2005Postselection] -/ public section @@ -81,19 +100,19 @@ theorem nodup_qubits {m : ℕ} {g : GateCode} (h : g.WellFormed m) : (qubits g). · simp [qubits, h.2] /-- **The network is admissible** at size `4 N`, its number of edges, with constant `6`, for a -nonempty list of well-formed gate codes. -/ -theorem explicitAdmissible_network (neg : ℕ) (X : List Bool) {gs : List GateCode} +nonempty list of well-formed gate codes and any measured qubits. -/ +theorem explicitAdmissible_network (neg : ℕ) (S : List ℕ) (X : List Bool) {gs : List GateCode} (hgs : ∀ g ∈ gs, g.WellFormed X.length) (hne : gs ≠ []) : - ExplicitAdmissible 6 (4 * (occurrences gs).length) (network neg X gs) := by + ExplicitAdmissible 6 (4 * (occurrences gs).length) (network neg S X gs) := by have h3 : ∀ g ∈ gs, (qubits g).length ≤ 3 := fun g hg => length_qubits_le_three (hgs g hg) have hL : 1 ≤ gs.length := List.length_pos_iff.mpr hne have hLN := length_le_length_occurrences gs have hlog : 1 ≤ Nat.log 2 (4 * (occurrences gs).length) := Nat.log_pos (by norm_num) (by omega) - refine ⟨isRowsFirstBipartite_network neg X fun g hg => nodup_qubits (hgs g hg), ?_, ?_, ?_, - length_table_network neg X h3, ?_⟩ + refine ⟨isRowsFirstBipartite_network neg S X fun g hg => nodup_qubits (hgs g hg), ?_, ?_, ?_, + length_table_network neg S X h3, ?_⟩ · rw [numEdges_network] omega - · exact (maxDegree_network_le neg X h3).trans (by omega) + · exact (maxDegree_network_le neg S X h3).trans (by omega) · rw [length_vertices_network] omega · rw [numEdges_network] @@ -112,35 +131,35 @@ open Quantum Quantum.PathNetwork Cslib.Circuits.Frontier Turing.MultiTapeTM Csli @[expose] def guard (gs : List GateCode) : Bool := !gs.isEmpty /-- The input of the contraction algorithm: the precision `h + 1`, for `h` Hadamard gates, and the -network with Hadamard sign `2 ^ (h + 1) - 1` on the input register `X`. -/ -@[expose] def contractionInput (gs : List GateCode) (X : List Bool) : +network with the measured qubits `S` and Hadamard sign `2 ^ (h + 1) - 1` on the input register +`X`. -/ +@[expose] def contractionInput (S : List ℕ) (gs : List GateCode) (X : List Bool) : ℕ × ℕ × ExplicitTensorNetwork ℕ := - (hadamardCount gs + 1, network (2 ^ (hadamardCount gs + 1) - 1 : ℕ) X gs) + (hadamardCount gs + 1, network (2 ^ (hadamardCount gs + 1) - 1 : ℕ) S X gs) -/-- The answer for a circuit without gates: the bit of qubit `0`. -/ -@[expose] def fallback (X : List Bool) : ℕ := if X.getD 0 false then 1 else 0 +/-- The answer for a circuit without gates: whether every qubit of `S` reads one in `X`. -/ +@[expose] def fallback (S : List ℕ) (X : List Bool) : ℕ := + if S.all fun j => X.getD j false then 1 else 0 -/-- **The acceptance gap as the simulator computes it**: the residue of the contraction of the -network modulo `2 ^ (h + 1)`, or the bit of qubit `0` for a circuit without gates. -/ -@[expose] def gapValue (gs : List GateCode) (X : List Bool) : ℕ := +/-- **The projected gap as the simulator computes it**: the residue of the contraction of the +network modulo `2 ^ (h + 1)`, or the bits of the qubits of `S` for a circuit without gates. -/ +@[expose] def gapValue (S : List ℕ) (gs : List GateCode) (X : List Bool) : ℕ := if guard gs then - let (precision, _, network) := contractionInput gs X + let (precision, _, network) := contractionInput S gs X network.contraction % 2 ^ precision - else fallback X + else fallback S X -/-- **The simulator computes the acceptance gap** of well-formed gate codes on a nonempty input -register. -/ -theorem gapValue_eq {X : List Bool} (hX : 0 < X.length) {gs : List GateCode} - (hgs : ∀ g ∈ gs, g.WellFormed X.length) : gapValue gs X = (acceptGap gs X).toNat := by +/-- **The simulator computes the projected gap** of well-formed gate codes on the register `X`. -/ +theorem gapValue_eq (S : List ℕ) {X : List Bool} {gs : List GateCode} + (hgs : ∀ g ∈ gs, g.WellFormed X.length) : gapValue S gs X = (onesGap S gs X).toNat := by unfold gapValue split - · exact contraction_network_mod X hX gs hgs (Nat.lt_succ_self _) + · exact contraction_network_mod S X gs hgs (Nat.lt_succ_self _) · next hg => obtain rfl : gs = [] := by simpa [guard] using hg - rw [acceptGap_nil, fallback] - cases X with - | nil => rfl - | cons b X => cases b <;> rfl + simp only [fallback, onesGap, hadamardCount_nil, onesTerm, pathRun, List.ofFn_zero, + List.foldl_nil, true_and, Bool.xor_self, Bool.false_eq_true, ↓reduceIte] + split <;> rfl /-! ### Resources -/ @@ -162,20 +181,17 @@ section Machine variable {α : Type} {enc : α ↪ List Bool} {gs : α → List GateCode} {X : α → List Bool} -/-- **The simulator with polynomial-time postprocessing.** For polynomial-time computed gate codes -`gs a`, well formed on the polynomial-time computed register `X a`, one machine computes -`rule (h, gapValue (gs a) (X a))`, for `h` the number of Hadamard gates and a polynomial-time -`rule`, in time `2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the input length and in -polynomial space, where `L` is the number of gates. -/ -theorem exists_computableInO_rule (hgs : ComputableInPolyTime gs enc (encList encGateCode)) - (hX : ComputableInPolyTime X enc encBits) (hwf : ∀ a, ∀ g ∈ gs a, g.WellFormed (X a).length) - {β : Type} {encβ : β ↪ List Bool} {rule : ℕ × ℕ → β} - (hrule : ComputableInPolyTime rule (encPair encUnary encNat) encβ) : - ∃ A e : ℕ, ComputableInO (fun a => rule (hadamardCount (gs a), gapValue (gs a) (X a))) enc - encβ - (fun a => 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * - ((enc a).length + 1) ^ e) - (fun a => ((enc a).length + 1) ^ e) := by +/-- **The simulator as a certificate**: for polynomial-time computed measured qubits `S a` and +gate codes `gs a`, well formed on the polynomial-time computed register `X a`, the projected gap +is computed in time `2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial and in polynomial +space. -/ +private theorem exists_expTimePolySpace_gapValue {S : α → List ℕ} + (hS : ComputableInPolyTime S enc (encList encUnary)) + (hgs : ComputableInPolyTime gs enc (encList encGateCode)) + (hX : ComputableInPolyTime X enc encBits) (hwf : ∀ a, ∀ g ∈ gs a, g.WellFormed (X a).length) : + ∃ (A : ℕ) (c : NativeCertificate (fun a => gapValue (S a) (gs a) (X a)) enc encNat), + c.ExpTimePolySpace fun a => + A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2)) := by obtain ⟨K, hK⟩ := fastContractionAlgorithm.bound 6 1 -- the polynomial-time parts have hh : ComputableInPolyTime (fun a => hadamardCount (gs a)) enc encUnary := by @@ -184,46 +200,45 @@ theorem exists_computableInO_rule (hgs : ComputableInPolyTime gs enc (encList en have hguard : ComputableInPolyTime (fun a => guard (gs a)) enc encBool := by unfold guard fun_prop - have hinput : ComputableInPolyTime (fun a => contractionInput (gs a) (X a)) enc + have hinput : ComputableInPolyTime (fun a => contractionInput (S a) (gs a) (X a)) enc (encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) := (unary_to_binary.comp (unary_add' hh (const 1))).prod (computableInPolyTime_network - (binary_sub' (binary_pow' (const 2) (unary_add' hh (const 1))) - (const 1)) hX hgs) - have hfb : ComputableInPolyTime (fun a => fallback (X a)) enc encNat := - ite_decide (decide_eq' (bitsGetDWithInput hX (const 0) false) (const true)) (const 1) (const 0) + (binary_sub' (binary_pow' (const 2) (unary_add' hh (const 1))) (const 1)) hS hX hgs) + have hfb : ComputableInPolyTime (fun a => fallback (S a) (X a)) enc encNat := + ite_decide (decide_eq' (allWithInput hS (bitsGetDWithInput (hX.comp (fst _ _)) (snd _ _) + false)) (const true)) (const 1) (const 0) -- the machine - let cv : NativeCertificate (fun a => gapValue (gs a) (X a)) enc encNat := + let cv : NativeCertificate (fun a => gapValue (S a) (gs a) (X a)) enc encNat := (NativeCertificate.branch (NativeCertificate.ofPolyTime hguard) (fastContractionAlgorithm.certificate.comp (NativeCertificate.ofPolyTime hinput)) (NativeCertificate.ofPolyTime hfb)).congr fun _ => rfl - let cpair := (NativeCertificate.ofPolyTime hh).prod cv - let ctotal := (NativeCertificate.ofPolyTime hrule).comp cpair + refine ⟨12 * K, cv, ?_⟩ -- polynomial bounds have hpg := NativeCertificate.ofPolyTime_isPolynomial hguard have hpi := NativeCertificate.ofPolyTime_isPolynomial hinput have hpf := NativeCertificate.ofPolyTime_isPolynomial hfb - have hph := NativeCertificate.ofPolyTime_isPolynomial hh - have hpr := NativeCertificate.ofPolyTime_isPolynomial hrule obtain ⟨cg, dg, htg⟩ := hpg.1.exists_le obtain ⟨cg', dg', hsg⟩ := hpg.2.exists_le obtain ⟨ci, di, hti⟩ := hpi.1.exists_le obtain ⟨ci', di', hsi⟩ := hpi.2.exists_le obtain ⟨cf, df, htf⟩ := hpf.1.exists_le obtain ⟨cf', df', hsf⟩ := hpf.2.exists_le - obtain ⟨ch, dh, hth⟩ := hph.1.exists_le - obtain ⟨ch', dh', hsh⟩ := hph.2.exists_le - obtain ⟨cr, dr, htr⟩ := hpr.1.exists_le - obtain ⟨cr', dr', hsr⟩ := hpr.2.exists_le obtain ⟨p, q, hlen⟩ := hinput.polyBounded_length.exists_le -- the contraction on guarded inputs + have hinlen (a : α) : (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) + (contractionInput (S a) (gs a) (X a))).length + 1) ^ K ≤ + (p + 1) ^ K * ((enc a).length + 1) ^ (q * K) := by + have h1 := hlen a + have h2 : 1 ≤ ((enc a).length + 1) ^ q := Nat.one_le_pow _ _ (Nat.le_add_left 1 _) + calc _ ≤ ((p + 1) * ((enc a).length + 1) ^ q) ^ K := Nat.pow_le_pow_left (by nlinarith) K + _ = _ := by rw [mul_pow, pow_mul] have hfast (a : α) (hga : guard (gs a) = true) : - fastContractionAlgorithm.certificate.time (contractionInput (gs a) (X a)) ≤ - K * (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) - (contractionInput (gs a) (X a))).length + 1) ^ K * - 2 ^ (12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) ∧ - fastContractionAlgorithm.certificate.space (contractionInput (gs a) (X a)) ≤ - K * (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) - (contractionInput (gs a) (X a))).length + 1) ^ K := by + fastContractionAlgorithm.certificate.time (contractionInput (S a) (gs a) (X a)) ≤ + K * (p + 1) ^ K * + (2 ^ (12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * + ((enc a).length + 1) ^ (q * K)) ∧ + fastContractionAlgorithm.certificate.space (contractionInput (S a) (gs a) (X a)) ≤ + K * (p + 1) ^ K * ((enc a).length + 1) ^ (q * K) := by have hne : gs a ≠ [] := by simpa [guard] using hga have hL : 1 ≤ (gs a).length := List.length_pos_iff.mpr hne have hLN := length_le_length_occurrences (gs a) @@ -231,177 +246,148 @@ theorem exists_computableInO_rule (hgs : ComputableInPolyTime gs enc (encList en rw [hadamardCount_eq_countP] exact List.countP_le_length have hb := hK (4 * (occurrences (gs a)).length) (hadamardCount (gs a) + 1) - (network (2 ^ (hadamardCount (gs a) + 1) - 1 : ℕ) (X a) (gs a)) (by omega) (by omega) - (by omega) (explicitAdmissible_network _ (X a) (hwf a) hne) - exact ⟨hb.1.trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by norm_num) - (exponent_le K (hwf a) hne))), hb.2⟩ - -- the space of the pair, and so the length of its output - set ds := dg' + di' + df' + dh' + q * K - set CS := cg' + ci' + cf' + ch' + K * (p + 1) ^ K - have hY {a : α} {d d' : ℕ} (h : d ≤ d') : - ((enc a).length + 1) ^ d ≤ ((enc a).length + 1) ^ d' := - Nat.pow_le_pow_right (Nat.le_add_left 1 _) h - have hinlen (a : α) : (((encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) - (contractionInput (gs a) (X a))).length + 1) ^ K ≤ - (p + 1) ^ K * ((enc a).length + 1) ^ (q * K) := by - have h1 := hlen a - have h2 : 1 ≤ ((enc a).length + 1) ^ q := Nat.one_le_pow _ _ (Nat.le_add_left 1 _) - calc _ ≤ ((p + 1) * ((enc a).length + 1) ^ q) ^ K := Nat.pow_le_pow_left (by nlinarith) K - _ = _ := by rw [mul_pow, pow_mul] - have hspace_pair (a : α) : cpair.space a ≤ CS * ((enc a).length + 1) ^ ds := by - have e1 := (hsh a).trans (Nat.mul_le_mul_left ch' (hY (a := a) (show dh' ≤ ds by omega))) - have e2 := (hsg a).trans (Nat.mul_le_mul_left cg' (hY (a := a) (show dg' ≤ ds by omega))) - have e3 := (hsi a).trans (Nat.mul_le_mul_left ci' (hY (a := a) (show di' ≤ ds by omega))) - have e4 := (hsf a).trans (Nat.mul_le_mul_left cf' (hY (a := a) (show df' ≤ ds by omega))) - change (NativeCertificate.ofPolyTime hh).space a + - ((NativeCertificate.ofPolyTime hguard).space a + if guard (gs a) then - (NativeCertificate.ofPolyTime hinput).space a + - fastContractionAlgorithm.certificate.space (contractionInput (gs a) (X a)) - else (NativeCertificate.ofPolyTime hfb).space a) ≤ _ - split - · next hga => - have e5 := ((hfast a hga).2.trans (Nat.mul_le_mul_left K (hinlen a))).trans - (show K * ((p + 1) ^ K * ((enc a).length + 1) ^ (q * K)) ≤ - K * (p + 1) ^ K * ((enc a).length + 1) ^ ds by - rw [← Nat.mul_assoc]; exact Nat.mul_le_mul_left _ (hY (by omega))) - calc _ ≤ ch' * ((enc a).length + 1) ^ ds + (cg' * ((enc a).length + 1) ^ ds + - (ci' * ((enc a).length + 1) ^ ds + K * (p + 1) ^ K * ((enc a).length + 1) ^ ds)) := - Nat.add_le_add e1 (Nat.add_le_add e2 (Nat.add_le_add e3 e5)) - _ = (ch' + cg' + ci' + K * (p + 1) ^ K) * ((enc a).length + 1) ^ ds := by ring - _ ≤ _ := Nat.mul_le_mul_right _ (by simp only [CS]; omega) - · calc _ ≤ ch' * ((enc a).length + 1) ^ ds + (cg' * ((enc a).length + 1) ^ ds + - cf' * ((enc a).length + 1) ^ ds) := Nat.add_le_add e1 (Nat.add_le_add e2 e4) - _ = (ch' + cg' + cf') * ((enc a).length + 1) ^ ds := by ring - _ ≤ _ := Nat.mul_le_mul_right _ (by simp only [CS]; omega) - obtain ⟨K₀, -, hK₀⟩ := cpair.computes - -- the postprocessing - set CR := K₀ * CS + K₀ + 1 - have hpairlen (a : α) : (encPair encUnary encNat - (hadamardCount (gs a), gapValue (gs a) (X a))).length + 1 ≤ - CR * ((enc a).length + 1) ^ ds := by - have h1 := hK₀ a - have h2 := hspace_pair a - have h3 : 1 ≤ ((enc a).length + 1) ^ ds := Nat.one_le_pow _ _ (Nat.le_add_left 1 _) - change _ ≤ K₀ * (cpair.space a + 1) at h1 - simp only [CR] - nlinarith - have hrule_time (a : α) : (NativeCertificate.ofPolyTime hrule).time - (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ - cr * CR ^ dr * ((enc a).length + 1) ^ (ds * dr) := by - refine (htr _).trans ?_ - calc _ ≤ cr * (CR * ((enc a).length + 1) ^ ds) ^ dr := - Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hpairlen a) dr) - _ = _ := by rw [mul_pow, ← pow_mul]; ring - have hrule_space (a : α) : (NativeCertificate.ofPolyTime hrule).space - (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ - cr' * CR ^ dr' * ((enc a).length + 1) ^ (ds * dr') := by - refine (hsr _).trans ?_ - calc _ ≤ cr' * (CR * ((enc a).length + 1) ^ ds) ^ dr' := - Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hpairlen a) dr') - _ = _ := by rw [mul_pow, ← pow_mul]; ring + (network (2 ^ (hadamardCount (gs a) + 1) - 1 : ℕ) (S a) (X a) (gs a)) (by omega) (by omega) + (by omega) (explicitAdmissible_network _ (S a) (X a) (hwf a) hne) + constructor + · refine hb.1.trans ?_ + calc _ ≤ K * ((p + 1) ^ K * ((enc a).length + 1) ^ (q * K)) * + 2 ^ (12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) := + Nat.mul_le_mul (Nat.mul_le_mul_left _ (hinlen a)) + (Nat.pow_le_pow_right (by norm_num) (exponent_le K (hwf a) hne)) + _ = _ := by ring + · exact hb.2.trans ((Nat.mul_le_mul_left K (hinlen a)).trans (Nat.mul_assoc _ _ _).ge) -- the totals - set e := dg + di + df + dh + q * K + ds * dr + ds * dr' + ds - set k := cg + ci + cf + ch + K * (p + 1) ^ K + cr * CR ^ dr + CS + cr' * CR ^ dr' + 1 - refine ⟨12 * K, e, ctotal.computes.mono k (fun a => ?_) (fun a => ?_)⟩ - · -- time - set E := 12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2)) - set Z := 2 ^ E * ((enc a).length + 1) ^ e - have hZ {d : ℕ} (h : d ≤ e) : ((enc a).length + 1) ^ d ≤ Z := - (hY h).trans (Nat.le_mul_of_pos_left _ Nat.one_le_two_pow) - have e1 := (hth a).trans (Nat.mul_le_mul_left ch (hZ (show dh ≤ e by omega))) - have e2 := (htg a).trans (Nat.mul_le_mul_left cg (hZ (show dg ≤ e by omega))) - have e3 := (hti a).trans (Nat.mul_le_mul_left ci (hZ (show di ≤ e by omega))) - have e4 := (htf a).trans (Nat.mul_le_mul_left cf (hZ (show df ≤ e by omega))) - have e6 := (hrule_time a).trans (Nat.mul_le_mul_left _ (hZ (show ds * dr ≤ e by omega))) - change (NativeCertificate.ofPolyTime hh).time a + - ((NativeCertificate.ofPolyTime hguard).time a + if guard (gs a) then - (NativeCertificate.ofPolyTime hinput).time a + - fastContractionAlgorithm.certificate.time (contractionInput (gs a) (X a)) - else (NativeCertificate.ofPolyTime hfb).time a) + - (NativeCertificate.ofPolyTime hrule).time - (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ k * (Z + 1) - have hk : ∀ c, c ≤ k → c * Z ≤ k * (Z + 1) := fun c hc => - (Nat.mul_le_mul_right _ hc).trans (Nat.mul_le_mul_left _ (Nat.le_succ _)) - split - · next hga => - have e5 : fastContractionAlgorithm.certificate.time (contractionInput (gs a) (X a)) ≤ - K * (p + 1) ^ K * Z := by - refine (hfast a hga).1.trans ?_ - calc _ ≤ K * ((p + 1) ^ K * ((enc a).length + 1) ^ (q * K)) * 2 ^ E := - Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (hinlen a)) - _ = K * (p + 1) ^ K * (2 ^ E * ((enc a).length + 1) ^ (q * K)) := by ring - _ ≤ K * (p + 1) ^ K * Z := - Nat.mul_le_mul_left _ (Nat.mul_le_mul_left _ (hY (by omega))) - calc _ ≤ ch * Z + (cg * Z + (ci * Z + K * (p + 1) ^ K * Z)) + cr * CR ^ dr * Z := - Nat.add_le_add (Nat.add_le_add e1 (Nat.add_le_add e2 (Nat.add_le_add e3 e5))) e6 - _ = (ch + cg + ci + K * (p + 1) ^ K + cr * CR ^ dr) * Z := by ring - _ ≤ _ := hk _ (by simp only [k]; omega) - · calc _ ≤ ch * Z + (cg * Z + cf * Z) + cr * CR ^ dr * Z := - Nat.add_le_add (Nat.add_le_add e1 (Nat.add_le_add e2 e4)) e6 - _ = (ch + cg + cf + cr * CR ^ dr) * Z := by ring - _ ≤ _ := hk _ (by simp only [k]; omega) - · -- space - have e1 := (hspace_pair a).trans (Nat.mul_le_mul_left CS (hY (a := a) (show ds ≤ e by omega))) - have e2 := (hrule_space a).trans - (Nat.mul_le_mul_left _ (hY (a := a) (show ds * dr' ≤ e by omega))) - change cpair.space a + (NativeCertificate.ofPolyTime hrule).space - (hadamardCount (gs a), gapValue (gs a) (X a)) ≤ k * (((enc a).length + 1) ^ e + 1) - calc _ ≤ CS * ((enc a).length + 1) ^ e + cr' * CR ^ dr' * ((enc a).length + 1) ^ e := - Nat.add_le_add e1 e2 - _ = (CS + cr' * CR ^ dr') * ((enc a).length + 1) ^ e := by ring - _ ≤ k * ((enc a).length + 1) ^ e := - Nat.mul_le_mul_right _ (by simp only [k]; omega) - _ ≤ _ := Nat.mul_le_mul_left _ (Nat.le_succ _) - -/-- **The simulator.** For polynomial-time computed gate codes `gs a`, well formed on the -polynomial-time computed register `X a`, one machine computes `gapValue (gs a) (X a)`, the -acceptance gap when `X a` is nonempty (`QuantumContraction.gapValue_eq`), in time + set d := dg + di + df + dg' + di' + df' + q * K + refine ⟨cg + ci + cf + cg' + ci' + cf' + K * (p + 1) ^ K, d, fun a => ?_⟩ + set Y := ((enc a).length + 1) ^ d + set Z := 2 ^ (12 * K * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) + have hY {e : ℕ} (he : e ≤ d) : ((enc a).length + 1) ^ e ≤ Y := + Nat.pow_le_pow_right (Nat.le_add_left 1 _) he + have hZ : Y ≤ Z * Y := Nat.le_mul_of_pos_left Y (Nat.two_pow_pos _) + change (NativeCertificate.ofPolyTime hguard).time a + (if guard (gs a) then + (NativeCertificate.ofPolyTime hinput).time a + + fastContractionAlgorithm.certificate.time (contractionInput (S a) (gs a) (X a)) + else (NativeCertificate.ofPolyTime hfb).time a) ≤ _ ∧ + (NativeCertificate.ofPolyTime hguard).space a + (if guard (gs a) then + (NativeCertificate.ofPolyTime hinput).space a + + fastContractionAlgorithm.certificate.space (contractionInput (S a) (gs a) (X a)) + else (NativeCertificate.ofPolyTime hfb).space a) ≤ _ + have e1 := (htg a).trans (Nat.mul_le_mul_left cg ((hY (e := dg) (by omega)).trans hZ)) + have e2 := (hti a).trans (Nat.mul_le_mul_left ci ((hY (e := di) (by omega)).trans hZ)) + have e3 := (htf a).trans (Nat.mul_le_mul_left cf ((hY (e := df) (by omega)).trans hZ)) + have e1' := (hsg a).trans (Nat.mul_le_mul_left cg' (hY (e := dg') (by omega))) + have e2' := (hsi a).trans (Nat.mul_le_mul_left ci' (hY (e := di') (by omega))) + have e3' := (hsf a).trans (Nat.mul_le_mul_left cf' (hY (e := df') (by omega))) + have hqK : ((enc a).length + 1) ^ (q * K) ≤ Y := hY (by omega) + split + · next hga => + have e4 := (hfast a hga).1.trans (Nat.mul_le_mul_left _ (Nat.mul_le_mul_left Z hqK)) + have e4' := (hfast a hga).2.trans (Nat.mul_le_mul_left _ hqK) + constructor + · calc _ ≤ cg * (Z * Y) + (ci * (Z * Y) + K * (p + 1) ^ K * (Z * Y)) := + Nat.add_le_add e1 (Nat.add_le_add e2 e4) + _ ≤ _ := by + rw [show cg * (Z * Y) + (ci * (Z * Y) + K * (p + 1) ^ K * (Z * Y)) = + (cg + ci + K * (p + 1) ^ K) * Z * Y by ring] + exact Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by omega)) + · calc _ ≤ cg' * Y + (ci' * Y + K * (p + 1) ^ K * Y) := + Nat.add_le_add e1' (Nat.add_le_add e2' e4') + _ ≤ _ := by + rw [show cg' * Y + (ci' * Y + K * (p + 1) ^ K * Y) = + (cg' + ci' + K * (p + 1) ^ K) * Y by ring] + exact Nat.mul_le_mul_right _ (by omega) + · constructor + · calc _ ≤ cg * (Z * Y) + cf * (Z * Y) := Nat.add_le_add e1 e3 + _ ≤ _ := by + rw [show cg * (Z * Y) + cf * (Z * Y) = (cg + cf) * Z * Y by ring] + exact Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by omega)) + · calc _ ≤ cg' * Y + cf' * Y := Nat.add_le_add e1' e3' + _ ≤ _ := by + rw [show cg' * Y + cf' * Y = (cg' + cf') * Y by ring] + exact Nat.mul_le_mul_right _ (by omega) + +/-- **The simulator.** For polynomial-time computed measured qubits `S a` and gate codes `gs a`, +well formed on the polynomial-time computed register `X a`, one machine computes +`gapValue (S a) (gs a) (X a)`, the projected gap (`QuantumContraction.gapValue_eq`), in time `2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the input length and in polynomial space, where `L` is the number of gates. -/ -theorem exists_computableInO_gapValue (hgs : ComputableInPolyTime gs enc (encList encGateCode)) +theorem exists_computableInO_gapValue {S : α → List ℕ} + (hS : ComputableInPolyTime S enc (encList encUnary)) + (hgs : ComputableInPolyTime gs enc (encList encGateCode)) (hX : ComputableInPolyTime X enc encBits) (hwf : ∀ a, ∀ g ∈ gs a, g.WellFormed (X a).length) : - ∃ A e : ℕ, ComputableInO (fun a => gapValue (gs a) (X a)) enc encNat + ∃ A e : ℕ, ComputableInO (fun a => gapValue (S a) (gs a) (X a)) enc encNat (fun a => 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * ((enc a).length + 1) ^ e) - (fun a => ((enc a).length + 1) ^ e) := - exists_computableInO_rule hgs hX hwf (rule := Prod.snd) (snd _ _) + (fun a => ((enc a).length + 1) ^ e) := by + obtain ⟨A, c, hc⟩ := exists_expTimePolySpace_gapValue hS hgs hX hwf + obtain ⟨e, he⟩ := hc.computableInO + exact ⟨A, e, he⟩ + +/-- **Two projected gaps with polynomial-time postprocessing.** For polynomial-time computed lists +`S a` and `P a` of measured qubits and gate codes `gs a`, well formed on the polynomial-time +computed register `X a`, one machine computes `rule (gapValue (S a) …, gapValue (P a) …)`, for a +polynomial-time `rule`, in time `2 ^ (A (L + 1) / √⌊log₂ (L + 2)⌋)` times a polynomial in the +input length and in polynomial space, where `L` is the number of gates. With `P a = []`, whose +projected gap is `2 ^ h` (`Cslib.Quantum.onesGap_nil_eq_two_pow`), the rule sees a probability +and its denominator; with `P a` a postselected event, a conditional probability. -/ +theorem exists_computableInO_rule {S P : α → List ℕ} + (hS : ComputableInPolyTime S enc (encList encUnary)) + (hP : ComputableInPolyTime P enc (encList encUnary)) + (hgs : ComputableInPolyTime gs enc (encList encGateCode)) + (hX : ComputableInPolyTime X enc encBits) (hwf : ∀ a, ∀ g ∈ gs a, g.WellFormed (X a).length) + {β : Type} {encβ : β ↪ List Bool} {rule : ℕ × ℕ → β} + (hrule : ComputableInPolyTime rule (encPair encNat encNat) encβ) : + ∃ A e : ℕ, ComputableInO + (fun a => rule (gapValue (S a) (gs a) (X a), gapValue (P a) (gs a) (X a))) enc encβ + (fun a => 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * + ((enc a).length + 1) ^ e) + (fun a => ((enc a).length + 1) ^ e) := by + obtain ⟨A, c, hc⟩ := exists_expTimePolySpace_gapValue hS hgs hX hwf + obtain ⟨A', c', hc'⟩ := exists_expTimePolySpace_gapValue hP hgs hX hwf + have hcc := ((hc.prod hc').mono (E' := fun a => + (A + A') * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) fun a => + Nat.div_add_div_le_add_div.trans (Nat.div_le_div_right (by rw [Nat.add_mul]))).comp_ofPolyTime + hrule + obtain ⟨e, he⟩ := hcc.computableInO + exact ⟨A + A', e, he⟩ end Machine -/-- **The acceptance probability from the simulator**: for well-formed codes on `m > 0` qubits and -an input `x` of length at most `m`, the acceptance probability is `gapValue / 2 ^ h` on the padded -register. -/ -theorem acceptProb_eq_gapValue {m : ℕ} (hm : 0 < m) {gs : List GateCode} - (hgs : ∀ g ∈ gs, g.WellFormed m) {x : List Bool} (hx : x.length ≤ m) : - acceptProb m gs x = - (gapValue gs (x ++ List.replicate (m - x.length) false) : ℝ) / 2 ^ hadamardCount gs := by +/-- **Probabilities from the simulator**: for well-formed codes on `m` qubits and an input `x` of +length at most `m`, the probability that every qubit of `S` reads `1` is `gapValue S / 2 ^ h` on the +padded register. -/ +theorem onesProb_eq_gapValue {m : ℕ} {gs : List GateCode} (hgs : ∀ g ∈ gs, g.WellFormed m) + (S : List ℕ) {x : List Bool} (hx : x.length ≤ m) : + onesProb m gs S x = + (gapValue S gs (x ++ List.replicate (m - x.length) false) : ℝ) / 2 ^ hadamardCount gs := by have hlen : (x ++ List.replicate (m - x.length) false).length = m := by simp; omega have hgs' : ∀ g ∈ gs, g.WellFormed (x ++ List.replicate (m - x.length) false).length := by rw [hlen] exact hgs - have hm' : 0 < (x ++ List.replicate (m - x.length) false).length := by - rw [hlen] - exact hm - rw [acceptProb_eq_acceptGap hm gs hgs x, ofFn_inputState hx, gapValue_eq hm' hgs'] + rw [onesProb_eq_onesGap gs hgs, ofFn_inputState hx, gapValue_eq S hgs'] congr 1 - have := Int.toNat_of_nonneg (acceptGap_nonneg_and_le hm' gs hgs').1 + have := Int.toNat_of_nonneg (onesGap_nonneg_and_le S gs hgs').1 exact_mod_cast this.symm /-! ### The class containments -/ variable {α : Type} {enc : α ↪ List Bool} {T : ℕ → ℕ} -/-- **Bounded-error quantum time `T` in time `2 ^ O(T / √log T)` and space `T ^ O(1)`.** For `T` -with `max n 2 ≤ T n`, every problem in `BQTIME[T]` is decided by one deterministic machine within -time `2 ^ (C T n / √⌊log₂ (T n)⌋)` and workspace `T n ^ d`: the machine computes the acceptance gap -of the circuit by tensor contraction and compares it with half of `2 ^ h`. -/ -theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T n) {L : Set α} - (hL : L ∈ BQTIME enc T) : +/-- **Deciding by two projected gaps in time `2 ^ O(T / √log T)` and space `T ^ O(1)`.** For `T` +with `max n 2 ≤ T n`, a uniform family with `O(T)` gates and lists `S` and `P` of measured qubits, +a problem is decided by one deterministic machine within time `2 ^ (C T n / √⌊log₂ (T n)⌋)` and +workspace `T n ^ d` when a polynomial-time `rule` decides it from any two numerators `g` and `g'` +over a common power of two of the probabilities that the qubits of `S` and of `P` read `1`. -/ +theorem exists_decidableInTimeAndSpace_of_onesProb (hTn : ∀ n, max n 2 ≤ T n) + {F : UniformCircuitFamily} {c₁ c₂ : ℕ} (hgates : ∀ n, (F.gates n).length ≤ c₁ * T n + c₂) + (S P : List ℕ) {rule : ℕ × ℕ → Bool} + (hrule : ComputableInPolyTime rule (encPair encNat encNat) encBool) {L : Set α} + (hL : ∀ a (g g' r : ℕ), F.onesProb S (enc a) = g / 2 ^ r → + F.onesProb P (enc a) = g' / 2 ^ r → (a ∈ L ↔ rule (g, g') = true)) : ∃ C d : ℕ, DecidableInTimeAndSpace L enc (fun a => 2 ^ (C * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)))) (fun a => T (enc a).length ^ d) := by - obtain ⟨F, c₁, c₂, hgates, hF⟩ := hL have hgates' := F.computableInPolyTime_gates have hwidth := F.computableInPolyTime_width let gs : α → List GateCode := fun a => F.gates (enc a).length @@ -417,33 +403,13 @@ theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T omega rw [this] exact F.wellFormed _ - have hrule : ComputableInPolyTime (fun p : ℕ × ℕ => decide (2 ^ p.1 < 2 * p.2)) - (encPair encUnary encNat) encBool := by fun_prop - obtain ⟨A, e, hc⟩ := exists_computableInO_rule hgs hX hwf hrule + obtain ⟨A, e, hc⟩ := exists_computableInO_rule (const S) (const P) hgs hX hwf hrule obtain ⟨K, hK, -⟩ := hc -- correctness - have hcorrect (a : α) : a ∈ L ↔ - decide (2 ^ hadamardCount (gs a) < 2 * gapValue (gs a) (X a)) = true := by - have hp := acceptProb_eq_gapValue (F.width_pos (enc a).length) - (F.wellFormed (enc a).length) (F.le_width (enc a).length) - change F.acceptProb (enc a) = _ at hp - set g := gapValue (gs a) (X a) - set r := hadamardCount (gs a) - have hpos : (0 : ℝ) < 2 ^ r := by positivity - rw [decide_eq_true_iff] - by_cases ha : a ∈ L - · have := (hF a).1 ha - rw [hp, le_div_iff₀ hpos] at this - have h1 : (2 : ℝ) * 2 ^ r ≤ 3 * g := by linarith - have h2 : 2 * 2 ^ r ≤ 3 * g := by exact_mod_cast h1 - have h3 : 1 ≤ 2 ^ r := Nat.one_le_two_pow - exact ⟨fun _ => by omega, fun _ => ha⟩ - · have := (hF a).2 ha - rw [hp, div_le_iff₀ hpos] at this - have h1 : (3 : ℝ) * g ≤ 2 ^ r := by linarith - have h2 : 3 * g ≤ 2 ^ r := by exact_mod_cast h1 - have h3 : 1 ≤ 2 ^ r := Nat.one_le_two_pow - exact ⟨fun h => absurd h ha, fun h => by omega⟩ + have hcorrect (a : α) : + a ∈ L ↔ rule (gapValue S (gs a) (X a), gapValue P (gs a) (X a)) = true := + hL a _ _ _ (onesProb_eq_gapValue (F.wellFormed _) S (F.le_width _)) + (onesProb_eq_gapValue (F.wellFormed _) P (F.le_width _)) -- the bounds set C₀ := 2 * A * (c₁ + c₂ + 2) + 2 * e set D₀ := 2 * A * (c₁ + c₂ + 2) + 3 * A @@ -453,19 +419,19 @@ theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T have hT2 : 2 ≤ T (enc a).length := (le_max_right _ _).trans (hTn _) have hnT : (enc a).length ≤ T (enc a).length := (le_max_left _ _).trans (hTn _) · -- time - set S := T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) + set Q := T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) have hE := Nat.mul_succ_div_sqrt_log_two_le_of_le A c₁ c₂ hT2 (hgates (enc a).length) have hY := Nat.add_one_pow_le_two_pow_div_sqrt_log_two e hT2 hnT have hZ : 2 ^ (A * ((gs a).length + 1) / Nat.sqrt (Nat.log 2 ((gs a).length + 2))) * - ((enc a).length + 1) ^ e ≤ 2 ^ D₀ * 2 ^ (C₀ * S) := by - calc _ ≤ 2 ^ (2 * A * (c₁ + c₂ + 2) * S + D₀) * 2 ^ (2 * e * S) := + ((enc a).length + 1) ^ e ≤ 2 ^ D₀ * 2 ^ (C₀ * Q) := by + calc _ ≤ 2 ^ (2 * A * (c₁ + c₂ + 2) * Q + D₀) * 2 ^ (2 * e * Q) := Nat.mul_le_mul (Nat.pow_le_pow_right (by norm_num) hE) hY - _ = 2 ^ D₀ * 2 ^ (C₀ * S) := by + _ = 2 ^ D₀ * 2 ^ (C₀ * Q) := by rw [← pow_add, ← pow_add] congr 1 simp only [C₀] ring - have hCS : C₀ * S ≤ C₀ * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) := + have hCQ : C₀ * Q ≤ C₀ * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)) := Nat.mul_div_le_mul_div_assoc _ _ _ have hfinal := Nat.mul_two_pow_div_sqrt_log_two_add_le (a := K * 2 ^ D₀) (b := K) (C := C₀) hT2 @@ -474,7 +440,7 @@ theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T ≤ K * (2 ^ D₀ * 2 ^ (C₀ * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length))) + 1) := Nat.mul_le_mul_left _ (Nat.add_le_add_right (hZ.trans (Nat.mul_le_mul_left _ - (Nat.pow_le_pow_right (by norm_num) hCS))) _) + (Nat.pow_le_pow_right (by norm_num) hCQ))) _) _ = K * 2 ^ D₀ * 2 ^ (C₀ * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length))) + K := by ring _ ≤ _ := hfinal @@ -487,12 +453,115 @@ theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T exact Nat.add_le_add_right (Nat.mul_le_mul_left _ hpow) _ _ ≤ _ := this -/-- **`BQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]`** for `T` with `max n 2 ≤ T n`. -/ -theorem BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (hTn : ∀ n, max n 2 ≤ T n) : - BQTIME enc T ⊆ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := by - intro L hL - obtain ⟨C, d, h⟩ := exists_decidableInTimeAndSpace_of_mem_BQTIME hTn hL +/-- A problem decided within time `2 ^ (C T / √⌊log₂ T⌋)` lies in the union of these classes. -/ +private theorem mem_iUnion_DTIME {L : Set α} + (h : ∃ C d : ℕ, DecidableInTimeAndSpace L enc + (fun a => 2 ^ (C * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)))) + (fun a => T (enc a).length ^ d)) : + L ∈ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := by + obtain ⟨C, d, h⟩ := h exact Set.mem_iUnion.mpr ⟨C, 1, 0, fun n => T n ^ d, ComputableInTimeAndSpace.mono h (fun a => by rw [Nat.one_mul, Nat.add_zero]) fun _ => le_rfl⟩ +/-- **The bounded-error rule**: when `g / g'` is at least `2 / 3` on members and at most `1 / 3` +otherwise, for `g' > 0`, membership is `g' < 2 g`. -/ +private theorem iff_lt_two_mul {L : Set α} {a : α} {g g' : ℕ} (hg' : 0 < g') + (h₁ : a ∈ L → (2 : ℝ) / 3 ≤ g / g') (h₂ : a ∉ L → (g : ℝ) / g' ≤ 1 / 3) : + a ∈ L ↔ decide (g' < 2 * g) = true := by + have hpos : (0 : ℝ) < g' := by exact_mod_cast hg' + rw [decide_eq_true_iff] + by_cases ha : a ∈ L + · have := h₁ ha + rw [le_div_iff₀ hpos] at this + have : 2 * g' ≤ 3 * g := by exact_mod_cast (by linarith : (2 : ℝ) * g' ≤ 3 * g) + exact ⟨fun _ => by omega, fun _ => ha⟩ + · have := h₂ ha + rw [div_le_iff₀ hpos] at this + have : 3 * g ≤ g' := by exact_mod_cast (by linarith : (3 : ℝ) * g ≤ g') + exact ⟨fun h => absurd h ha, fun h => by omega⟩ + +/-- **Bounded-error quantum time `T` in time `2 ^ O(T / √log T)` and space `T ^ O(1)`.** For `T` +with `max n 2 ≤ T n`, every problem in `BQTIME[T]` is decided by one deterministic machine within +time `2 ^ (C T n / √⌊log₂ (T n)⌋)` and workspace `T n ^ d`: the machine computes the acceptance gap +`g` of the circuit and the projected gap `2 ^ h` of no condition by tensor contraction and accepts +when `2 ^ h < 2 g`. -/ +theorem exists_decidableInTimeAndSpace_of_mem_BQTIME (hTn : ∀ n, max n 2 ≤ T n) {L : Set α} + (hL : L ∈ BQTIME enc T) : + ∃ C d : ℕ, DecidableInTimeAndSpace L enc + (fun a => 2 ^ (C * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)))) + (fun a => T (enc a).length ^ d) := by + obtain ⟨F, c₁, c₂, hgates, hF⟩ := hL + refine exists_decidableInTimeAndSpace_of_onesProb hTn hgates [0] [] + (rule := fun p => decide (p.2 < 2 * p.1)) (by fun_prop) fun a g g' r h h' => ?_ + rw [UniformCircuitFamily.onesProb, onesProb_nil] at h' + have hg' : (g' : ℝ) = 2 ^ r := by + rw [eq_div_iff (by positivity), one_mul] at h' + exact h'.symm + have hp : F.acceptProb (enc a) = g / g' := by rw [hg']; exact h + exact iff_lt_two_mul (by exact_mod_cast hg' ▸ (by positivity : (0 : ℝ) < 2 ^ r)) + (fun ha => hp ▸ (hF a).1 ha) fun ha => hp ▸ (hF a).2 ha + +/-- **`BQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]`** for `T` with `max n 2 ≤ T n`. -/ +theorem BQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (hTn : ∀ n, max n 2 ≤ T n) : + BQTIME enc T ⊆ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := + fun _ hL => mem_iUnion_DTIME (exists_decidableInTimeAndSpace_of_mem_BQTIME hTn hL) + +/-- **Nondeterministic quantum time `T` in time `2 ^ O(T / √log T)` and space `T ^ O(1)`.** For +`T` with `max n 2 ≤ T n`, every problem in `NQTIME[T]` is decided by one deterministic machine +within time `2 ^ (C T n / √⌊log₂ (T n)⌋)` and workspace `T n ^ d`: the machine computes the +acceptance gap of the circuit by tensor contraction and accepts when it is positive. -/ +theorem exists_decidableInTimeAndSpace_of_mem_NQTIME (hTn : ∀ n, max n 2 ≤ T n) {L : Set α} + (hL : L ∈ NQTIME enc T) : + ∃ C d : ℕ, DecidableInTimeAndSpace L enc + (fun a => 2 ^ (C * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)))) + (fun a => T (enc a).length ^ d) := by + obtain ⟨F, c₁, c₂, hgates, hF⟩ := hL + refine exists_decidableInTimeAndSpace_of_onesProb hTn hgates [0] [] + (rule := fun p => decide (0 < p.1)) (by fun_prop) fun a g g' r h _ => ?_ + rw [hF a, decide_eq_true_iff, UniformCircuitFamily.acceptProb_eq_onesProb, h] + constructor + · intro hpos + exact_mod_cast (div_pos_iff_of_pos_right (by positivity : (0 : ℝ) < 2 ^ r)).mp hpos + · intro hg + exact div_pos (by exact_mod_cast hg) (by positivity) + +/-- **`NQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]`** for `T` with `max n 2 ≤ T n`. -/ +theorem NQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (hTn : ∀ n, max n 2 ≤ T n) : + NQTIME enc T ⊆ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := + fun _ hL => mem_iUnion_DTIME (exists_decidableInTimeAndSpace_of_mem_NQTIME hTn hL) + +/-- **Exact quantum time `T` in time `2 ^ O(T / √log T)`**: exact quantum time lies in +nondeterministic quantum time (`Cslib.Complexity.EQTIME_subset_NQTIME`). -/ +theorem EQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (hTn : ∀ n, max n 2 ≤ T n) : + EQTIME enc T ⊆ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := + (EQTIME_subset_NQTIME T).trans (NQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two hTn) + +/-- **Postselected bounded-error quantum time `T` in time `2 ^ O(T / √log T)` and space +`T ^ O(1)`.** For `T` with `max n 2 ≤ T n`, every problem in `PostBQTIME[T]` is decided by one +deterministic machine within time `2 ^ (C T n / √⌊log₂ (T n)⌋)` and workspace `T n ^ d`: the +machine computes the projected gaps `g` of qubits `0` and `1` and `g'` of qubit `1` by tensor +contraction and accepts when `g' < 2 g`, that is, when the postselected acceptance probability +exceeds `1 / 2`. -/ +theorem exists_decidableInTimeAndSpace_of_mem_PostBQTIME (hTn : ∀ n, max n 2 ≤ T n) {L : Set α} + (hL : L ∈ PostBQTIME enc T) : + ∃ C d : ℕ, DecidableInTimeAndSpace L enc + (fun a => 2 ^ (C * T (enc a).length / Nat.sqrt (Nat.log 2 (T (enc a).length)))) + (fun a => T (enc a).length ^ d) := by + obtain ⟨F, c₁, c₂, hgates, hF⟩ := hL + refine exists_decidableInTimeAndSpace_of_onesProb hTn hgates [0, 1] [1] + (rule := fun p => decide (p.2 < 2 * p.1)) (by fun_prop) fun a g g' r h h' => ?_ + obtain ⟨hpost, h₁, h₂⟩ := hF a + have hr : (0 : ℝ) < 2 ^ r := by positivity + have hg' : 0 < g' := by + rw [h'] at hpost + exact_mod_cast (div_pos_iff_of_pos_right hr).mp hpost + have hp : F.postselectedAcceptProb (enc a) = g / g' := by + rw [UniformCircuitFamily.postselectedAcceptProb, h, h', div_div_div_cancel_right₀ hr.ne'] + exact iff_lt_two_mul hg' (fun ha => hp ▸ h₁ ha) fun ha => hp ▸ h₂ ha + +/-- **`PostBQTIME[T] ⊆ ⋃ C, DTIME[2 ^ (C T / √⌊log₂ T⌋)]`** for `T` with `max n 2 ≤ T n`. -/ +theorem PostBQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (hTn : ∀ n, max n 2 ≤ T n) : + PostBQTIME enc T ⊆ ⋃ C, DTIME enc fun n => 2 ^ (C * T n / Nat.sqrt (Nat.log 2 (T n))) := + fun _ hL => mem_iUnion_DTIME (exists_decidableInTimeAndSpace_of_mem_PostBQTIME hTn hL) + end Cslib.Complexity.QuantumContraction diff --git a/Complexity/Computability/Complexity/Classes/BQP/Time.lean b/Complexity/Computability/Complexity/Classes/BQP/Time.lean index 17a790f1d..68a1b2cc1 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/Time.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/Time.lean @@ -12,7 +12,7 @@ import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimePrimitives /-! -# Bounded-error quantum time +# Quantum time classes `BQTIME enc t` (`Cslib.Complexity.BQTIME`) is the class of problems decided with bounded error by the polynomial-time uniform families of quantum circuits of `Cslib.Complexity.BQP` whose circuits @@ -25,16 +25,47 @@ The families are polynomial-time uniform, so every circuit has polynomially many dominates every polynomial. `BQP` is the union of the classes `BQTIME[n ^ k]` (`Cslib.Complexity.BQP_eq_iUnion_BQTIME`). +The same families with the same gate bounds define three more classes, by other acceptance +conditions: + +* **Nondeterministic quantum time** `NQTIME enc t` (`Cslib.Complexity.NQTIME`): members are the + instances accepted with nonzero probability. With polynomial `t` this is the class `NQP` of + Adleman, DeMarrais and Huang, as in [Fenner, Green, Homer, + Pruim][FennerGreenHomerPruim1999], Definition 2.1, who show `NQP = coC₌P` (Theorem 1.1). +* **Exact quantum time** `EQTIME enc t` (`Cslib.Complexity.EQTIME`): members are accepted with + probability `1` and other instances with probability `0`, as `EQTime(T(n))` in + [Bernstein, Vazirani][BernsteinVazirani1997], Definition 8.1, for quantum Turing machines. + Exactness depends on the gate set: approximating one gate set by another preserves bounded error + but not zero error, so this is exact quantum time over the Hadamard, NOT, CNOT and Toffoli gates, + and it is not claimed to equal exact quantum time over other gate sets or quantum Turing + machines. It lies in `NQTIME` and in `BQTIME` (`Cslib.Complexity.EQTIME_subset_NQTIME`, + `Cslib.Complexity.EQTIME_subset_BQTIME`). +* **Postselected bounded-error quantum time** `PostBQTIME enc t` (`Cslib.Complexity.PostBQTIME`): + qubit `1` reads `1` with nonzero probability, and conditioned on that, qubit `0` reads `1` with + probability at least `2 / 3` on members and at most `1 / 3` otherwise. With polynomial `t` this + is `PostBQP`, [Aaronson][Aaronson2005Postselection], Definition 1, with his first (postselected) + and second (output) qubits exchanged; he shows `PostBQP = PP` (Theorem 4). + ## Main definitions -* `Cslib.Complexity.BQTIME`. +* `Cslib.Complexity.BQTIME`, `Cslib.Complexity.NQTIME`, `Cslib.Complexity.EQTIME`, + `Cslib.Complexity.PostBQTIME`. ## Main results * `Cslib.Complexity.BQTIME_subset_BQP`: every `BQTIME` class lies in `BQP`. * `Cslib.Complexity.BQP_eq_iUnion_BQTIME`: `BQP = ⋃ k, BQTIME[n ^ k]`. --/ +* `Cslib.Complexity.EQTIME_subset_NQTIME`, `Cslib.Complexity.EQTIME_subset_BQTIME`: exact quantum + time is nondeterministic and bounded-error quantum time. + +## References +* [E. Bernstein, U. Vazirani, *Quantum complexity theory*][BernsteinVazirani1997] +* [S. Fenner, F. Green, S. Homer, R. Pruim, *Determining acceptance possibility for a quantum + computation is hard for the polynomial hierarchy*][FennerGreenHomerPruim1999] +* [S. Aaronson, *Quantum computing, postselection, and probabilistic + polynomial-time*][Aaronson2005Postselection] +-/ @[expose] public section namespace Cslib.Complexity @@ -55,8 +86,57 @@ def BQTIME (enc : α ↪ List Bool) (t : ℕ → ℕ) : Set (Set α) := {A | ∃ (F : UniformCircuitFamily) (c₁ c₂ : ℕ), (∀ n, (F.gates n).length ≤ c₁ * t n + c₂) ∧ ∀ a, (a ∈ A → 2 / 3 ≤ F.acceptProb (enc a)) ∧ (a ∉ A → F.acceptProb (enc a) ≤ 1 / 3)} +/-- **Nondeterministic quantum time** `t`: the problems whose members, with respect to the encoding +`enc` of instances, are exactly the instances accepted with nonzero probability by a +polynomial-time uniform family of quantum circuits over the Hadamard, NOT, CNOT and Toffoli gates +(`Cslib.Quantum.UniformCircuitFamily`) whose circuit for inputs of length `n` has at most +`c₁ t n + c₂` gates. -/ +def NQTIME (enc : α ↪ List Bool) (t : ℕ → ℕ) : Set (Set α) := + {A | ∃ (F : UniformCircuitFamily) (c₁ c₂ : ℕ), (∀ n, (F.gates n).length ≤ c₁ * t n + c₂) ∧ + ∀ a, a ∈ A ↔ 0 < F.acceptProb (enc a)} + +/-- **Exact quantum time** `t` over the Hadamard, NOT, CNOT and Toffoli gates: the problems decided +without error, with respect to the encoding `enc` of instances, by a polynomial-time uniform family +of quantum circuits whose circuit for inputs of length `n` has at most `c₁ t n + c₂` gates. +Members are accepted with probability `1` and other instances with probability `0`. -/ +def EQTIME (enc : α ↪ List Bool) (t : ℕ → ℕ) : Set (Set α) := + {A | ∃ (F : UniformCircuitFamily) (c₁ c₂ : ℕ), (∀ n, (F.gates n).length ≤ c₁ * t n + c₂) ∧ + ∀ a, (a ∈ A → F.acceptProb (enc a) = 1) ∧ (a ∉ A → F.acceptProb (enc a) = 0)} + +/-- **Postselected bounded-error quantum time** `t`: the problems decided with bounded error after +postselection, with respect to the encoding `enc` of instances, by a polynomial-time uniform family +of quantum circuits over the Hadamard, NOT, CNOT and Toffoli gates whose circuit for inputs of +length `n` has at most `c₁ t n + c₂` gates. On every instance qubit `1` reads `1` with nonzero +probability, and conditioned on that (`Cslib.Quantum.UniformCircuitFamily.postselectedAcceptProb`) +members are accepted with probability at least `2 / 3` and other instances with probability at +most `1 / 3`. -/ +def PostBQTIME (enc : α ↪ List Bool) (t : ℕ → ℕ) : Set (Set α) := + {A | ∃ (F : UniformCircuitFamily) (c₁ c₂ : ℕ), (∀ n, (F.gates n).length ≤ c₁ * t n + c₂) ∧ + ∀ a, 0 < F.onesProb [1] (enc a) ∧ (a ∈ A → 2 / 3 ≤ F.postselectedAcceptProb (enc a)) ∧ + (a ∉ A → F.postselectedAcceptProb (enc a) ≤ 1 / 3)} + variable {enc : α ↪ List Bool} +/-- **Exact quantum time is nondeterministic quantum time**: a probability that is `0` or `1` is +positive exactly when it is `1`. -/ +theorem EQTIME_subset_NQTIME (t : ℕ → ℕ) : EQTIME enc t ⊆ NQTIME enc t := by + rintro A ⟨F, c₁, c₂, hgates, hF⟩ + refine ⟨F, c₁, c₂, hgates, fun a => ⟨fun ha => ?_, fun hp => ?_⟩⟩ + · rw [(hF a).1 ha] + exact one_pos + · by_contra ha + rw [(hF a).2 ha] at hp + exact lt_irrefl 0 hp + +/-- **Exact quantum time is bounded-error quantum time.** -/ +theorem EQTIME_subset_BQTIME (t : ℕ → ℕ) : EQTIME enc t ⊆ BQTIME enc t := by + rintro A ⟨F, c₁, c₂, hgates, hF⟩ + refine ⟨F, c₁, c₂, hgates, fun a => ⟨fun ha => ?_, fun ha => ?_⟩⟩ + · rw [(hF a).1 ha] + norm_num + · rw [(hF a).2 ha] + norm_num + /-- Every `BQTIME` class lies in `BQP`. -/ theorem BQTIME_subset_BQP (t : ℕ → ℕ) : BQTIME enc t ⊆ BQP enc := fun _ ⟨F, _, _, _, hF⟩ => ⟨F, hF⟩ diff --git a/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean b/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean new file mode 100644 index 000000000..4b77aebb1 --- /dev/null +++ b/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean @@ -0,0 +1,164 @@ +/- +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.Combinators + +/-! +# Certificates in time `2 ^ E` times a polynomial and polynomial space + +A native certificate runs *in time `2 ^ E` times a polynomial and in polynomial space* +(`Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace`) when, for constants `C` and `d`, its time is +at most `C 2 ^ E a (n + 1) ^ d` and its space at most `C (n + 1) ^ d` on inputs `a` of encoded +length `n`. This shape survives pairing, which adds the exponents +(`Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace.prod`), and polynomial-time postprocessing, +since a polynomial space bound also bounds the encoded intermediate value +(`Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace.comp_ofPolyTime`). Simulations that run a few +exponential-time subroutines and combine their answers in polynomial time are assembled this way. + +## Main definitions + +* `Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace`. + +## Main results + +* `Cslib.GenericRAM.NativeCertificate.expTimePolySpace_ofPolyTime`: polynomial-time certificates. +* `Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace.prod`, + `Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace.comp_ofPolyTime`: pairs and + polynomial-time postprocessing. +* `Cslib.GenericRAM.NativeCertificate.ExpTimePolySpace.computableInO`: the resulting machine bounds. +-/ + +public section + +namespace Cslib.GenericRAM.NativeCertificate + +open Turing.MultiTapeTM + +variable {α β γ : Type} {enc : α ↪ List Bool} {eβ : β ↪ List Bool} {eγ : γ ↪ List Bool} + {f : α → β} {g : α → γ} + +/-- A certificate **runs in time `2 ^ E` times a polynomial and in polynomial space**: for constants +`C` and `d`, its time is at most `C 2 ^ E a (n + 1) ^ d` and its space at most `C (n + 1) ^ d` on +inputs `a` of encoded length `n`. -/ +@[expose] def ExpTimePolySpace (c : NativeCertificate f enc eβ) (E : α → ℕ) : Prop := + ∃ C d : ℕ, ∀ a, c.time a ≤ C * 2 ^ E a * ((enc a).length + 1) ^ d ∧ + c.space a ≤ C * ((enc a).length + 1) ^ d + +private theorem pow_le_pow_of_le {a d d' : ℕ} (h : d ≤ d') : (a + 1) ^ d ≤ (a + 1) ^ d' := + Nat.pow_le_pow_right (Nat.succ_pos a) h + +namespace ExpTimePolySpace + +variable {c : NativeCertificate f enc eβ} {E E' : α → ℕ} + +theorem mono (h : c.ExpTimePolySpace E) (hE : ∀ a, E a ≤ E' a) : c.ExpTimePolySpace E' := by + obtain ⟨C, d, h⟩ := h + exact ⟨C, d, fun a => ⟨(h a).1.trans (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ + (Nat.pow_le_pow_right (by norm_num) (hE a)))), (h a).2⟩⟩ + +/-- **Pairs add the exponents.** -/ +theorem prod {c' : NativeCertificate g enc eγ} (h : c.ExpTimePolySpace E) + (h' : c'.ExpTimePolySpace E') : (c.prod c').ExpTimePolySpace fun a => E a + E' a := by + obtain ⟨C, d, h⟩ := h + obtain ⟨C', d', h'⟩ := h' + refine ⟨C + C', d + d', fun a => ?_⟩ + have hd := pow_le_pow_of_le (a := (enc a).length) (Nat.le_add_right d d') + have hd' := pow_le_pow_of_le (a := (enc a).length) (Nat.le_add_left d' d) + have hE := Nat.le_mul_of_pos_right (2 ^ E a) (Nat.two_pow_pos (E' a)) + have hE' := Nat.le_mul_of_pos_left (2 ^ E' a) (Nat.two_pow_pos (E a)) + rw [prod_time, prod_space, pow_add 2 (E a) (E' a)] + constructor + · calc c.time a + c'.time a + ≤ C * 2 ^ E a * ((enc a).length + 1) ^ d + C' * 2 ^ E' a * ((enc a).length + 1) ^ d' := + Nat.add_le_add (h a).1 (h' a).1 + _ ≤ C * (2 ^ E a * 2 ^ E' a) * ((enc a).length + 1) ^ (d + d') + + C' * (2 ^ E a * 2 ^ E' a) * ((enc a).length + 1) ^ (d + d') := by + gcongr + _ = _ := by ring + · calc c.space a + c'.space a + ≤ C * ((enc a).length + 1) ^ d + C' * ((enc a).length + 1) ^ d' := + Nat.add_le_add (h a).2 (h' a).2 + _ ≤ C * ((enc a).length + 1) ^ (d + d') + C' * ((enc a).length + 1) ^ (d + d') := by + gcongr + _ = _ := by ring + +/-- **Polynomial-time postprocessing keeps the shape**: the polynomial space bound also bounds the +encoded intermediate value, on which the postprocessing runs. -/ +theorem comp_ofPolyTime (h : c.ExpTimePolySpace E) {r : β → γ} + (hr : ComputableInPolyTime r eβ eγ) : ((ofPolyTime hr).comp c).ExpTimePolySpace E := by + obtain ⟨C, d, h⟩ := h + obtain ⟨K₀, -, hK₀⟩ := c.computes + have hp := ofPolyTime_isPolynomial hr + obtain ⟨ct, dt, ht⟩ := hp.1.exists_le + obtain ⟨cs, ds, hs⟩ := hp.2.exists_le + set D := K₀ * C + K₀ + 1 + -- the encoded intermediate value + have hlen (a : α) : (eβ (f a)).length + 1 ≤ D * ((enc a).length + 1) ^ d := by + have h1 := hK₀ a + have h2 := (h a).2 + have h3 : 1 ≤ ((enc a).length + 1) ^ d := Nat.one_le_pow _ _ (Nat.succ_pos _) + simp only [D] + nlinarith + have hpoly {c₀ d₀ : ℕ} {u : β → ℕ} (hu : ∀ b, u b ≤ c₀ * ((eβ b).length + 1) ^ d₀) (a : α) : + u (f a) ≤ c₀ * D ^ d₀ * ((enc a).length + 1) ^ (d * d₀) := by + refine (hu (f a)).trans ?_ + calc c₀ * ((eβ (f a)).length + 1) ^ d₀ ≤ c₀ * (D * ((enc a).length + 1) ^ d) ^ d₀ := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (hlen a) d₀) + _ = _ := by rw [mul_pow, ← pow_mul]; ring + refine ⟨C + ct * D ^ dt + cs * D ^ ds, d + d * dt + d * ds, fun a => ?_⟩ + set Y := ((enc a).length + 1) ^ (d + d * dt + d * ds) + have hY {e : ℕ} (he : e ≤ d + d * dt + d * ds) : ((enc a).length + 1) ^ e ≤ Y := + pow_le_pow_of_le he + have hZ : Y ≤ 2 ^ E a * Y := Nat.le_mul_of_pos_left Y (Nat.two_pow_pos (E a)) + rw [comp_time, comp_space] + constructor + · calc c.time a + (ofPolyTime hr).time (f a) + ≤ C * 2 ^ E a * ((enc a).length + 1) ^ d + + ct * D ^ dt * ((enc a).length + 1) ^ (d * dt) := + Nat.add_le_add (h a).1 (hpoly ht a) + _ ≤ C * 2 ^ E a * Y + ct * D ^ dt * (2 ^ E a * Y) := + Nat.add_le_add (Nat.mul_le_mul_left _ (hY (e := d) (by omega))) + (Nat.mul_le_mul_left _ ((hY (e := d * dt) (by omega)).trans hZ)) + _ = (C + ct * D ^ dt) * 2 ^ E a * Y := by ring + _ ≤ _ := Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by omega)) + · calc c.space a + (ofPolyTime hr).space (f a) + ≤ C * ((enc a).length + 1) ^ d + cs * D ^ ds * ((enc a).length + 1) ^ (d * ds) := + Nat.add_le_add (h a).2 (hpoly hs a) + _ ≤ C * Y + cs * D ^ ds * Y := + Nat.add_le_add (Nat.mul_le_mul_left _ (hY (e := d) (by omega))) + (Nat.mul_le_mul_left _ (hY (e := d * ds) (by omega))) + _ = (C + cs * D ^ ds) * Y := by ring + _ ≤ _ := Nat.mul_le_mul_right _ (by omega) + +/-- **The machine bounds** of a certificate in time `2 ^ E` times a polynomial and polynomial +space. -/ +theorem computableInO (h : c.ExpTimePolySpace E) : ∃ e : ℕ, + ComputableInO f enc eβ (fun a => 2 ^ E a * ((enc a).length + 1) ^ e) + (fun a => ((enc a).length + 1) ^ e) := by + obtain ⟨C, d, h⟩ := h + refine ⟨d, c.computes.mono C (fun a => (h a).1.trans ?_) (fun a => (h a).2.trans ?_)⟩ + · rw [Nat.mul_assoc] + exact Nat.mul_le_mul_left _ (Nat.le_succ _) + · exact Nat.mul_le_mul_left _ (Nat.le_succ _) + +end ExpTimePolySpace + +/-- **Polynomial-time certificates** run in time `2 ^ E` times a polynomial for every `E`. -/ +theorem expTimePolySpace_ofPolyTime (hf : ComputableInPolyTime f enc eβ) (E : α → ℕ) : + (ofPolyTime hf).ExpTimePolySpace E := by + have hp := ofPolyTime_isPolynomial hf + obtain ⟨ct, dt, ht⟩ := hp.1.exists_le + obtain ⟨cs, ds, hs⟩ := hp.2.exists_le + refine ⟨ct + cs, dt + ds, fun a => ⟨(ht a).trans ?_, (hs a).trans ?_⟩⟩ + · rw [Nat.mul_assoc] + exact Nat.mul_le_mul (by omega) ((pow_le_pow_of_le (Nat.le_add_right _ _)).trans + (Nat.le_mul_of_pos_left _ (Nat.two_pow_pos _))) + · exact Nat.mul_le_mul (by omega) (pow_le_pow_of_le (Nat.le_add_left _ _)) + +end Cslib.GenericRAM.NativeCertificate diff --git a/ComplexityTests/BQPCounting.lean b/ComplexityTests/BQPCounting.lean index 38706a002..d3a65076d 100644 --- a/ComplexityTests/BQPCounting.lean +++ b/ComplexityTests/BQPCounting.lean @@ -42,7 +42,7 @@ example : acceptGap [(true, 0, []), (true, 0, [])] [true] = 4 := by decide -- The count agrees with the quantum acceptance probability of `hadamardOnce`. example : acceptProb 1 [(true, 0, [])] [] = 1 / 2 := by - rw [acceptProb_eq_acceptGap (by norm_num) _ (by decide)] + rw [acceptProb_eq_acceptGap _ (by decide)] have : List.ofFn (inputState 1 []) = [false] := by decide rw [this, show acceptGap [(true, 0, [])] [false] = 1 by decide] norm_num diff --git a/ComplexityTests/BQPTensorContraction.lean b/ComplexityTests/BQPTensorContraction.lean index dd90bb0c7..6b9912d81 100644 --- a/ComplexityTests/BQPTensorContraction.lean +++ b/ComplexityTests/BQPTensorContraction.lean @@ -18,41 +18,50 @@ open Cslib Cslib.Quantum Cslib.Quantum.PathNetwork Cslib.Complexity /-! ### The bipartition -/ -- A Hadamard gate copied by a CNOT: every edge joins a row to a column. -example : (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).2.IsRowsFirstBipartite - (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).1 := by decide +example : + (network (-1 : ℤ) [0] [false, false] [(true, 1, []), (false, 0, [1])]).2.IsRowsFirstBipartite + (network (-1 : ℤ) [0] [false, false] [(true, 1, []), (false, 0, [1])]).1 := by decide -example : (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).2.IsRowsFirstBipartite - (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).1 := - isRowsFirstBipartite_network _ _ (by decide) +example : + (network (-1 : ℤ) [0] [false, false] [(true, 1, []), (false, 0, [1])]).2.IsRowsFirstBipartite + (network (-1 : ℤ) [0] [false, false] [(true, 1, []), (false, 0, [1])]).1 := + isRowsFirstBipartite_network _ _ _ (by decide) -- The hypothesis that the qubits of a gate are distinct is needed: a CNOT whose control is its -- target reads the same link edge twice. -example : ¬(network (-1 : ℤ) [false, false] [(true, 0, []), (false, 0, [0])]).2.IsRowsFirstBipartite - (network (-1 : ℤ) [false, false] [(true, 0, []), (false, 0, [0])]).1 := by decide +example : + ¬(network (-1 : ℤ) [0] [false, false] [(true, 0, []), (false, 0, [0])]).2.IsRowsFirstBipartite + (network (-1 : ℤ) [0] [false, false] [(true, 0, []), (false, 0, [0])]).1 := by decide -- A Toffoli gate: three occurrences, so twelve edges, admissible at size twelve. example : CountingSimulation.ExplicitAdmissible 6 12 - (network 7 [false, false, false] [(false, 2, [0, 1])]) := - explicitAdmissible_network 7 [false, false, false] (by decide) (by decide) + (network 7 [0] [false, false, false] [(false, 2, [0, 1])]) := + explicitAdmissible_network 7 [0] [false, false, false] (by decide) (by decide) /-! ### The simulator -/ -- One Hadamard gate on `|0⟩`: the gap is `1`, so the acceptance probability is `1 / 2`. -example : gapValue [(true, 0, [])] [false] = 1 := by decide +example : gapValue [0] [(true, 0, [])] [false] = 1 := by decide example : acceptProb 1 [(true, 0, [])] [false] = 1 / 2 := by - rw [acceptProb_eq_gapValue (by decide) (by decide) (by decide)] - rw [show gapValue [(true, 0, [])] ([false] ++ List.replicate (1 - [false].length) false) = 1 by - decide] + rw [acceptProb_eq_onesProb, onesProb_eq_gapValue (by decide) [0] (by decide)] + rw [show gapValue [0] [(true, 0, [])] ([false] ++ List.replicate (1 - [false].length) false) = 1 + by decide] norm_num [hadamardCount] --- Without gates, the simulator reads qubit `0`. -example : gapValue [] [true, false] = 1 := by decide -example : gapValue [] [false, true] = 0 := by decide +-- Without gates, the simulator reads the measured qubits. +example : gapValue [0] [] [true, false] = 1 := by decide +example : gapValue [0] [] [false, true] = 0 := by decide +example : gapValue [0, 1] [] [true, true] = 1 := by decide -- Two Hadamard gates on `|1⟩` interfere: the gap is `2 ^ 2`. -example : gapValue [(true, 0, []), (true, 0, [])] [true] = 4 := by - rw [gapValue_eq (by decide) (by decide)] +example : gapValue [0] [(true, 0, []), (true, 0, [])] [true] = 4 := by + rw [gapValue_eq _ (by decide)] + decide + +-- Measuring no qubit gives `2 ^ h`, the denominator of the bounded-error rule. +example : gapValue [] [(true, 0, []), (true, 0, [])] [true] = 4 := by + rw [gapValue_eq _ (by decide)] decide /-! ### A member of `BQTIME` -/ diff --git a/ComplexityTests/QuantumPathNetwork.lean b/ComplexityTests/QuantumPathNetwork.lean index ebddc4fc6..285c07914 100644 --- a/ComplexityTests/QuantumPathNetwork.lean +++ b/ComplexityTests/QuantumPathNetwork.lean @@ -7,7 +7,8 @@ Authors: Samuel Schlesinger import Complexity.Computability.Circuit.Quantum.PathNetwork /-! The tensor network of a circuit and its mirror copy: concrete contractions that equal the -acceptance gaps of small circuits, computed directly and through `contraction_network`. -/ +acceptance gaps and other projected gaps of small circuits, computed directly and through +`contraction_network`. -/ namespace ComplexityTests.QuantumPathNetwork @@ -17,50 +18,73 @@ open Cslib Cslib.Quantum Cslib.Quantum.PathNetwork Cslib.Circuits.Frontier -- One Hadamard gate on `|0⟩`: one of the four pairs of paths accepts, so the gap is `1`. The -- network has one occurrence, so four edges: sixteen assignments. -example : (network (-1 : ℤ) [false] [(true, 0, [])]).2.contraction = 1 := by decide +example : (network (-1 : ℤ) [0] [false] [(true, 0, [])]).2.contraction = 1 := by decide example : acceptGap [(true, 0, [])] [false] = 1 := by decide -- A NOT gate on `|0⟩` accepts with certainty and has no Hadamard gate. -example : (network (-1 : ℤ) [false] [(false, 0, [])]).2.contraction = 1 := by decide +example : (network (-1 : ℤ) [0] [false] [(false, 0, [])]).2.contraction = 1 := by decide -- A NOT gate on `|1⟩` rejects with certainty. -example : (network (-1 : ℤ) [true] [(false, 0, [])]).2.contraction = 0 := by decide +example : (network (-1 : ℤ) [0] [true] [(false, 0, [])]).2.contraction = 0 := by decide -- Without gates the network is the scalar vertex alone: it reads the input bit of qubit `0`. -example : (network (-1 : ℤ) [true] []).2.contraction = 1 := by decide -example : (network (-1 : ℤ) [false] []).2.contraction = 0 := by decide +example : (network (-1 : ℤ) [0] [true] []).2.contraction = 1 := by decide +example : (network (-1 : ℤ) [0] [false] []).2.contraction = 0 := by decide -- An untouched qubit `0` set to one: the gate on qubit `1` does not matter, the gap is `2 ^ 1`. -example : (network (-1 : ℤ) [true, false] [(true, 1, [])]).2.contraction = 2 := by decide +example : (network (-1 : ℤ) [0] [true, false] [(true, 1, [])]).2.contraction = 2 := by decide /-! ### Through the identity -/ -- Two Hadamard gates interfere; the signed entries `-1` cancel the four accepting pairs from -- `|0⟩` and add them up from `|1⟩`. -example : (network (-1 : ℤ) [false] [(true, 0, []), (true, 0, [])]).2.contraction = 0 := by - rw [contraction_network _ _ (by decide)] +example : (network (-1 : ℤ) [0] [false] [(true, 0, []), (true, 0, [])]).2.contraction = 0 := by + rw [contraction_network _ _ _ (by decide)] decide -example : (network (-1 : ℤ) [true] [(true, 0, []), (true, 0, [])]).2.contraction = 4 := by - rw [contraction_network _ _ (by decide)] +example : (network (-1 : ℤ) [0] [true] [(true, 0, []), (true, 0, [])]).2.contraction = 4 := by + rw [contraction_network _ _ _ (by decide)] decide -- A Hadamard gate copied by a CNOT into qubit `0`: probability `1 / 2`, gap `1`. -example : (network (-1 : ℤ) [false, false] [(true, 1, []), (false, 0, [1])]).2.contraction = 1 := by - rw [contraction_network _ _ (by decide)] +example : + (network (-1 : ℤ) [0] [false, false] [(true, 1, []), (false, 0, [1])]).2.contraction = 1 := by + rw [contraction_network _ _ _ (by decide)] + decide + +/-! ### Other measured qubits -/ + +-- Measuring no qubit gives the squared norm `2 ^ h`. +example : (network (-1 : ℤ) [] [false] [(true, 0, [])]).2.contraction = 2 := by decide + +-- An untouched measured qubit is read from the input register by the scalar vertex. +example : (network (-1 : ℤ) [1] [false, true] [(true, 0, [])]).2.contraction = 2 := by decide +example : (network (-1 : ℤ) [1] [false, false] [(true, 0, [])]).2.contraction = 0 := by decide + +-- The Hadamard gate copied into qubit `0` leaves qubits `0` and `1` equal: both read `1` with +-- probability `1 / 2`, the same as qubit `1` alone. +example : + (network (-1 : ℤ) [0, 1] [false, false] [(true, 1, []), (false, 0, [1])]).2.contraction = + 1 := by + rw [contraction_network _ _ _ (by decide)] + decide +example : + (network (-1 : ℤ) [1] [false, false] [(true, 1, []), (false, 0, [1])]).2.contraction = 1 := by + rw [contraction_network _ _ _ (by decide)] decide /-! ### Residues over `ℕ` -/ -- Over `ℕ` the Hadamard sign is `2 ^ P - 1`; modulo `2 ^ P` the contraction is the gap. -example : (network (2 ^ 3 - 1 : ℕ) [true] [(true, 0, []), (true, 0, [])]).2.contraction % 2 ^ 3 = - 4 := by - rw [contraction_network_mod _ (by decide) _ (by decide) (by decide)] +example : + (network (2 ^ 3 - 1 : ℕ) [0] [true] [(true, 0, []), (true, 0, [])]).2.contraction % 2 ^ 3 = + 4 := by + rw [contraction_network_mod _ _ _ (by decide) (by decide)] decide /-! ### Sizes -/ -- Four edges per occurrence: the Toffoli gate has three occurrences. -example : (network (-1 : ℤ) [false, false, false] [(false, 2, [0, 1])]).2.numEdges = 12 := by +example : (network (-1 : ℤ) [0] [false, false, false] [(false, 2, [0, 1])]).2.numEdges = 12 := by decide end ComplexityTests.QuantumPathNetwork diff --git a/ComplexityTests/QuantumTimeClasses.lean b/ComplexityTests/QuantumTimeClasses.lean new file mode 100644 index 000000000..f43fe7ac2 --- /dev/null +++ b/ComplexityTests/QuantumTimeClasses.lean @@ -0,0 +1,183 @@ +/- +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.BQP.TensorContraction +import ComplexityTests.BQPTensorContraction + +/-! Nondeterministic, exact and postselected quantum time: circuits accepting with probability +strictly between `0` and `1`, exact circuits whose paths interfere, a postselected circuit that +accepts only after postselection, the classes they witness, and the containments applied to them. +-/ + +namespace ComplexityTests.QuantumTimeClasses + +open Cslib Cslib.Quantum Cslib.Complexity Cslib.Complexity.QuantumContraction Turing.MultiTapeTM + ComplexityTests.BQPTensorContraction + +/-- The encoding of the single instance of `Unit` as the empty word. -/ +def encUnit : Unit ↪ List Bool := ⟨fun _ => [], fun _ _ _ => rfl⟩ + +/-- The family running the fixed gate codes `gs` on `n + k` qubits for inputs of length `n`. -/ +def constFamily (k : ℕ) (hk : 0 < k) (gs : List GateCode) + (hgs : ∀ g ∈ gs, ∀ n, g.WellFormed (n + k)) : UniformCircuitFamily where + width n := n + k + gates _ := gs + computableInPolyTime_width := by fun_prop + computableInPolyTime_gates := ComputableInPolyTime.const _ + le_width n := Nat.le_add_right n k + width_pos n := by omega + wellFormed n g hg := hgs g hg n + +/-! ### Nonzero but not exact -/ + +/-- One Hadamard gate on qubit `0`. -/ +def coin : UniformCircuitFamily := + constFamily 1 (by norm_num) [(true, 0, [])] fun g hg n => by + obtain rfl := List.mem_singleton.mp hg + simp [GateCode.WellFormed] + +-- The coin accepts with probability `1 / 2`: positive, but neither `0` nor `1`. +theorem acceptProb_coin : coin.acceptProb [] = 1 / 2 := by + change acceptProb 1 [(true, 0, [])] [] = 1 / 2 + rw [acceptProb_eq_acceptGap _ (by decide), show List.ofFn (inputState 1 []) = [false] by decide, + show acceptGap [(true, 0, [])] [false] = 1 by decide] + norm_num [hadamardCount] + +example : 0 < coin.acceptProb [] ∧ coin.acceptProb [] ≠ 1 := by + rw [acceptProb_coin] + norm_num + +/-- The coin puts the instance into `NQTIME`. -/ +theorem univ_mem_NQTIME : (Set.univ : Set Unit) ∈ NQTIME encUnit fun _ => 1 := + ⟨coin, 0, 1, fun _ => le_rfl, fun _ => by + change _ ↔ 0 < coin.acceptProb [] + rw [acceptProb_coin] + norm_num⟩ + +example : (Set.univ : Set Unit) ∈ ⋃ C, DTIME encUnit fun n => + 2 ^ (C * (n + 2) / Nat.sqrt (Nat.log 2 (n + 2))) := + NQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (T := fun n => n + 2) + (fun n => max_le (by omega) (by omega)) + (show (Set.univ : Set Unit) ∈ NQTIME encUnit fun n => n + 2 from + ⟨coin, 0, 1, fun _ => by simp [coin, constFamily], fun _ => by + change _ ↔ 0 < coin.acceptProb [] + rw [acceptProb_coin] + norm_num⟩) + +/-! ### Exact -/ + +/-- Two Hadamard gates on qubit `0`, after a NOT gate when `flip` holds. Their paths interfere, so +the circuit accepts exactly when `flip` holds. -/ +def twice (flip : Bool) : UniformCircuitFamily := + constFamily 1 (by norm_num) + ((if flip then [(false, 0, [])] else []) ++ [(true, 0, []), (true, 0, [])]) fun g hg n => by + cases flip <;> simp only [Bool.false_eq_true, ite_false, ite_true, List.nil_append, + List.cons_append, List.mem_cons, List.not_mem_nil, or_false] at hg <;> + rcases hg with rfl | rfl | rfl <;> simp [GateCode.WellFormed] + +-- From `|0⟩` the four accepting pairs of paths cancel; after the NOT gate they add up to `2 ^ 2`. +theorem acceptProb_twice_false : (twice false).acceptProb [] = 0 := by + change acceptProb 1 [(true, 0, []), (true, 0, [])] [] = 0 + rw [acceptProb_eq_acceptGap _ (by decide), show List.ofFn (inputState 1 []) = [false] by decide, + show acceptGap [(true, 0, []), (true, 0, [])] [false] = 0 by decide] + norm_num + +theorem acceptProb_twice_true : (twice true).acceptProb [] = 1 := by + change acceptProb 1 [(false, 0, []), (true, 0, []), (true, 0, [])] [] = 1 + rw [acceptProb_eq_acceptGap _ (by decide), show List.ofFn (inputState 1 []) = [false] by decide, + show acceptGap [(false, 0, []), (true, 0, []), (true, 0, [])] [false] = 4 by decide] + norm_num [hadamardCount] + +example : (∅ : Set Unit) ∈ EQTIME encUnit fun _ => 1 := + ⟨twice false, 0, 2, fun _ => le_rfl, fun _ => + ⟨fun h => absurd h (Set.notMem_empty _), fun _ => acceptProb_twice_false⟩⟩ + +example : (Set.univ : Set Unit) ∈ EQTIME encUnit fun _ => 1 := + ⟨twice true, 0, 3, fun _ => le_rfl, fun _ => + ⟨fun _ => acceptProb_twice_true, fun h => absurd (Set.mem_univ _) h⟩⟩ + +/-- Deciding the first bit takes no gates, exactly. -/ +theorem firstBit_mem_EQTIME (t : ℕ → ℕ) : FirstBit ∈ EQTIME encBits t := by + refine ⟨readFirst, 0, 0, fun n => by simp [readFirst], fun x => ⟨fun hx => ?_, fun hx => ?_⟩⟩ + · rw [encBits_apply, acceptProb_readFirst] + simp only [show x.headD false = true from hx, ite_true] + · rw [encBits_apply, acceptProb_readFirst] + simp only [show x.headD false = false from Bool.eq_false_iff.mpr hx, Bool.false_eq_true, + ite_false] + +example : FirstBit ∈ NQTIME encBits fun _ => 0 := + EQTIME_subset_NQTIME _ (firstBit_mem_EQTIME _) + +example : FirstBit ∈ ⋃ C, DTIME encBits fun n => + 2 ^ (C * (n + 2) / Nat.sqrt (Nat.log 2 (n + 2))) := + EQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (T := fun n => n + 2) + (fun n => max_le (by omega) (by omega)) (firstBit_mem_EQTIME _) + +/-! ### Postselection -/ + +/-- The gate codes of `postselect`: two fair coins on qubits `2` and `3`, and their conjunction +written into qubits `0` and `1`. -/ +def postselectCodes : List GateCode := + [(true, 2, []), (true, 3, []), (false, 0, [2, 3]), (false, 1, [2, 3])] + +/-- Two fair coins whose conjunction is written into qubits `0` and `1`: qubit `0` reads `1` with +probability `1 / 4`, but always once qubit `1` reads `1`. -/ +def postselect : UniformCircuitFamily := + constFamily 4 (by norm_num) postselectCodes fun g hg n => by + simp only [postselectCodes, List.mem_cons, List.not_mem_nil, or_false] at hg + rcases hg with rfl | rfl | rfl | rfl <;> simp [GateCode.WellFormed] + +theorem onesProb_postselect (S : List ℕ) : + postselect.onesProb S [] = + (onesGap S postselectCodes [false, false, false, false] : ℝ) / 4 := by + change onesProb 4 postselectCodes S [] = _ + rw [onesProb_eq_onesGap _ (by decide), + show List.ofFn (inputState 4 []) = [false, false, false, false] by decide, + show hadamardCount postselectCodes = 2 by decide] + norm_num + +-- Without postselection the circuit rejects: it accepts with probability `1 / 4`. +example : postselect.acceptProb [] = 1 / 4 := by + rw [UniformCircuitFamily.acceptProb_eq_onesProb, onesProb_postselect, + show onesGap [0] postselectCodes [false, false, false, false] = 1 by decide] + norm_num + +-- Qubit `1` reads `1` with probability `1 / 4`, and then so does qubit `0`. +theorem onesProb_postselect_one : postselect.onesProb [1] [] = 1 / 4 := by + rw [onesProb_postselect, show onesGap [1] postselectCodes [false, false, false, false] = 1 by + decide] + norm_num + +theorem postselectedAcceptProb_postselect : postselect.postselectedAcceptProb [] = 1 := by + rw [UniformCircuitFamily.postselectedAcceptProb, onesProb_postselect_one, onesProb_postselect, + show onesGap [0, 1] postselectCodes [false, false, false, false] = 1 by decide] + norm_num + +-- The simulator computes both projected gaps. +example : gapValue [0, 1] postselectCodes [false, false, false, false] = 1 := by + rw [gapValue_eq _ (by decide)] + decide +example : gapValue [1] postselectCodes [false, false, false, false] = 1 := by + rw [gapValue_eq _ (by decide)] + decide + +/-- Postselection puts the instance into `PostBQTIME`. -/ +theorem univ_mem_PostBQTIME (t : ℕ → ℕ) : (Set.univ : Set Unit) ∈ PostBQTIME encUnit t := by + refine ⟨postselect, 0, 4, fun _ => by simp [postselect, constFamily, postselectCodes], + fun _ => ⟨?_, fun _ => ?_, fun h => absurd (Set.mem_univ _) h⟩⟩ + · change 0 < postselect.onesProb [1] [] + rw [onesProb_postselect_one] + norm_num + · change 2 / 3 ≤ postselect.postselectedAcceptProb [] + rw [postselectedAcceptProb_postselect] + norm_num + +example : (Set.univ : Set Unit) ∈ ⋃ C, DTIME encUnit fun n => + 2 ^ (C * (n + 2) / Nat.sqrt (Nat.log 2 (n + 2))) := + PostBQTIME_subset_iUnion_DTIME_two_pow_div_sqrt_log_two (T := fun n => n + 2) + (fun n => max_le (by omega) (by omega)) (univ_mem_PostBQTIME _) + +end ComplexityTests.QuantumTimeClasses diff --git a/references.bib b/references.bib index 925a848de..33404174c 100644 --- a/references.bib +++ b/references.bib @@ -1,3 +1,18 @@ +@article{Aaronson2005Postselection, + author = {Aaronson, Scott}, + title = {Quantum Computing, Postselection, and Probabilistic Polynomial-Time}, + journal = {Proceedings of the Royal Society A: Mathematical, Physical and Engineering + Sciences}, + volume = {461}, + number = {2063}, + pages = {3473--3482}, + year = {2005}, + doi = {10.1098/rspa.2005.1546}, + eprint = {quant-ph/0412187}, + archivePrefix = {arXiv}, + note = {Locators follow arXiv:quant-ph/0412187v1} +} + @inproceedings{AaronsonBenDavidKothariRaoTal2021, author = {Aaronson, Scott and Ben-David, Shalev and Kothari, Robin and Rao, Shravas and Tal, Avishay}, @@ -1692,6 +1707,22 @@ @article{FennerFortnowKurtz1994 doi = {10.1016/S0022-0000(05)80024-8} } +@article{FennerGreenHomerPruim1999, + author = {Fenner, Stephen and Green, Frederic and Homer, Steven and Pruim, Randall}, + title = {Determining Acceptance Possibility for a Quantum Computation is Hard for the + Polynomial Hierarchy}, + journal = {Proceedings of the Royal Society of London. Series A: Mathematical, Physical and + Engineering Sciences}, + volume = {455}, + number = {1991}, + pages = {3953--3966}, + year = {1999}, + doi = {10.1098/rspa.1999.0485}, + eprint = {quant-ph/9812056}, + archivePrefix = {arXiv}, + note = {Locators follow arXiv:quant-ph/9812056v1} +} + @article{Fenwick1994, author = {Fenwick, Peter M.}, title = {A New Data Structure for Cumulative Frequency Tables}, From c858bc4112325bd5d742a80590552c68e3376bf7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 17:03:44 -0400 Subject: [PATCH 12/13] refactor(Quantum): derive acceptance bounds from onesProb; drop a private power helper Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Computability/Circuit/Quantum/Code.lean | 60 +++++++++---------- .../Circuit/Quantum/PathSum.lean | 6 +- .../Complexity/Classes/BQP/Time.lean | 13 ++-- .../GenericRAM/Native/ExpTimePolySpace.lean | 16 +++-- 4 files changed, 46 insertions(+), 49 deletions(-) diff --git a/Complexity/Computability/Circuit/Quantum/Code.lean b/Complexity/Computability/Circuit/Quantum/Code.lean index 33559fcd8..2694fb099 100644 --- a/Complexity/Computability/Circuit/Quantum/Code.lean +++ b/Complexity/Computability/Circuit/Quantum/Code.lean @@ -257,53 +257,51 @@ def inputState (m : ℕ) (x : List Bool) : Fin m → Bool := fun i => x.getD i f def onesAt (m : ℕ) (S : List ℕ) : Finset (Fin m → Bool) := univ.filter fun y => ∀ j ∈ S, ∃ h : j < m, y ⟨j, h⟩ = true -/-- The accepting outcomes: qubit `0` reads `1`. -/ -def accepting (m : ℕ) : Finset (Fin m → Bool) := onesAt m [0] - -/-- The **acceptance probability** of the circuit with gate codes `gs` on `m` qubits on input `x`: -started in `|x 0^{m - n}⟩`, the final measurement finds qubit `0` in state `1`. -/ -noncomputable def acceptProb (m : ℕ) (gs : List GateCode) (x : List Bool) : ℝ := - measureProb (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (accepting m) - -theorem acceptProb_nonneg (m : ℕ) (gs : List GateCode) (x : List Bool) : - 0 ≤ acceptProb m gs x := - measureProb_nonneg _ _ - -theorem acceptProb_le_one (m : ℕ) (gs : List GateCode) (x : List Bool) : - acceptProb m gs x ≤ 1 := by - have h := measureProb_le_measureProb_univ - (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (accepting m) - rwa [measureProb_univ, Circuit.norm_run, one_pow] at h - -theorem mem_accepting (hm : 0 < m) {y : Fin m → Bool} : - y ∈ accepting m ↔ y ⟨0, hm⟩ = true := by - simp [accepting, onesAt, hm] - /-- The probability that the final measurement of the circuit with gate codes `gs` on `m` qubits, started in `|x 0^{m - n}⟩`, finds every qubit of `S` in state `1`. The acceptance probability is the case `S = [0]` (`Cslib.Quantum.acceptProb_eq_onesProb`). -/ noncomputable def onesProb (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : ℝ := measureProb (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (onesAt m S) -theorem acceptProb_eq_onesProb (m : ℕ) (gs : List GateCode) (x : List Bool) : - acceptProb m gs x = onesProb m gs [0] x := - rfl - theorem onesProb_nonneg (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : 0 ≤ onesProb m gs S x := measureProb_nonneg _ _ +theorem onesProb_le_one (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : + onesProb m gs S x ≤ 1 := by + have h := measureProb_le_measureProb_univ + (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (onesAt m S) + rwa [measureProb_univ, Circuit.norm_run, one_pow] at h + /-- The event of no condition is certain. -/ @[simp] theorem onesProb_nil (m : ℕ) (gs : List GateCode) (x : List Bool) : onesProb m gs [] x = 1 := by rw [onesProb, show onesAt m [] = univ by simp [onesAt], measureProb_univ, Circuit.norm_run, one_pow] -theorem onesProb_le_one (m : ℕ) (gs : List GateCode) (S : List ℕ) (x : List Bool) : - onesProb m gs S x ≤ 1 := by - have h := measureProb_le_measureProb_univ - (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (onesAt m S) - rwa [measureProb_univ, Circuit.norm_run, one_pow] at h +/-- The accepting outcomes: qubit `0` reads `1`. -/ +def accepting (m : ℕ) : Finset (Fin m → Bool) := onesAt m [0] + +/-- The **acceptance probability** of the circuit with gate codes `gs` on `m` qubits on input `x`: +started in `|x 0^{m - n}⟩`, the final measurement finds qubit `0` in state `1`. -/ +noncomputable def acceptProb (m : ℕ) (gs : List GateCode) (x : List Bool) : ℝ := + measureProb (Circuit.run (Circuit.ofCodes m gs) (inputState m x)) (accepting m) + +theorem acceptProb_eq_onesProb (m : ℕ) (gs : List GateCode) (x : List Bool) : + acceptProb m gs x = onesProb m gs [0] x := + rfl + +theorem acceptProb_nonneg (m : ℕ) (gs : List GateCode) (x : List Bool) : + 0 ≤ acceptProb m gs x := + onesProb_nonneg m gs [0] x + +theorem acceptProb_le_one (m : ℕ) (gs : List GateCode) (x : List Bool) : + acceptProb m gs x ≤ 1 := + onesProb_le_one m gs [0] x + +theorem mem_accepting (hm : 0 < m) {y : Fin m → Bool} : + y ∈ accepting m ↔ y ⟨0, hm⟩ = true := by + simp [accepting, onesAt, hm] /-- The probability of the rejecting outcomes is one minus the acceptance probability. -/ private theorem measureProb_compl_accepting (ψ : Qubits m) (hψ : ‖ψ‖ = 1) : diff --git a/Complexity/Computability/Circuit/Quantum/PathSum.lean b/Complexity/Computability/Circuit/Quantum/PathSum.lean index 33b813547..94aa3e357 100644 --- a/Complexity/Computability/Circuit/Quantum/PathSum.lean +++ b/Complexity/Computability/Circuit/Quantum/PathSum.lean @@ -326,9 +326,9 @@ private theorem sum_onesAt_pathGap_sq (S : List ℕ) (gs : List GateCode) (x : L rw [Finset.sum_comm] exact Finset.sum_congr rfl fun π' _ => sum_onesAt_pathTerm_mul S gs x _ _ hx -/-- **The probability of an event of ones is a projected gap over `2^h`**: for well-formed codes, -the probability that every qubit of `S` reads `1` is `onesGap S` divided by `2^h`, where `h` counts -the Hadamard gates. -/ +/-- **Probabilities are projected gaps over `2^h`**: for well-formed codes, the probability that +every qubit of `S` reads `1` is `onesGap S` divided by `2^h`, where `h` counts the Hadamard +gates. -/ theorem onesProb_eq_onesGap (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed m) (S : List ℕ) (x : List Bool) : onesProb m gs S x = diff --git a/Complexity/Computability/Complexity/Classes/BQP/Time.lean b/Complexity/Computability/Complexity/Classes/BQP/Time.lean index 68a1b2cc1..803ed496b 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/Time.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/Time.lean @@ -29,9 +29,10 @@ The same families with the same gate bounds define three more classes, by other conditions: * **Nondeterministic quantum time** `NQTIME enc t` (`Cslib.Complexity.NQTIME`): members are the - instances accepted with nonzero probability. With polynomial `t` this is the class `NQP` of - Adleman, DeMarrais and Huang, as in [Fenner, Green, Homer, - Pruim][FennerGreenHomerPruim1999], Definition 2.1, who show `NQP = coC₌P` (Theorem 1.1). + instances accepted with nonzero probability. It is the time-`t` circuit analogue of the class + `NQP` of Adleman, DeMarrais and Huang, defined for quantum Turing machines in [Fenner, Green, + Homer, Pruim][FennerGreenHomerPruim1999], Definition 2.1, who show `NQP = coC₌P` (Theorem 1.1), + and in Section 4 that `coC₌P` is already in `NQP` with amplitudes in `{0, ±1/√2, ±1}`. * **Exact quantum time** `EQTIME enc t` (`Cslib.Complexity.EQTIME`): members are accepted with probability `1` and other instances with probability `0`, as `EQTime(T(n))` in [Bernstein, Vazirani][BernsteinVazirani1997], Definition 8.1, for quantum Turing machines. @@ -42,9 +43,9 @@ conditions: `Cslib.Complexity.EQTIME_subset_BQTIME`). * **Postselected bounded-error quantum time** `PostBQTIME enc t` (`Cslib.Complexity.PostBQTIME`): qubit `1` reads `1` with nonzero probability, and conditioned on that, qubit `0` reads `1` with - probability at least `2 / 3` on members and at most `1 / 3` otherwise. With polynomial `t` this - is `PostBQP`, [Aaronson][Aaronson2005Postselection], Definition 1, with his first (postselected) - and second (output) qubits exchanged; he shows `PostBQP = PP` (Theorem 4). + probability at least `2 / 3` on members and at most `1 / 3` otherwise. It is the time-`t` + analogue of `PostBQP`, [Aaronson][Aaronson2005Postselection], Definition 1, with his first + (postselected) and second (output) qubits exchanged; he shows `PostBQP = PP` (Theorem 4). ## Main definitions diff --git a/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean b/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean index 4b77aebb1..0cbc7efe9 100644 --- a/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean +++ b/Complexity/Computability/GenericRAM/Native/ExpTimePolySpace.lean @@ -50,9 +50,6 @@ inputs `a` of encoded length `n`. -/ ∃ C d : ℕ, ∀ a, c.time a ≤ C * 2 ^ E a * ((enc a).length + 1) ^ d ∧ c.space a ≤ C * ((enc a).length + 1) ^ d -private theorem pow_le_pow_of_le {a d d' : ℕ} (h : d ≤ d') : (a + 1) ^ d ≤ (a + 1) ^ d' := - Nat.pow_le_pow_right (Nat.succ_pos a) h - namespace ExpTimePolySpace variable {c : NativeCertificate f enc eβ} {E E' : α → ℕ} @@ -68,8 +65,8 @@ theorem prod {c' : NativeCertificate g enc eγ} (h : c.ExpTimePolySpace E) obtain ⟨C, d, h⟩ := h obtain ⟨C', d', h'⟩ := h' refine ⟨C + C', d + d', fun a => ?_⟩ - have hd := pow_le_pow_of_le (a := (enc a).length) (Nat.le_add_right d d') - have hd' := pow_le_pow_of_le (a := (enc a).length) (Nat.le_add_left d' d) + have hd := Nat.pow_le_pow_right (Nat.succ_pos (enc a).length) (Nat.le_add_right d d') + have hd' := Nat.pow_le_pow_right (Nat.succ_pos (enc a).length) (Nat.le_add_left d' d) have hE := Nat.le_mul_of_pos_right (2 ^ E a) (Nat.two_pow_pos (E' a)) have hE' := Nat.le_mul_of_pos_left (2 ^ E' a) (Nat.two_pow_pos (E a)) rw [prod_time, prod_space, pow_add 2 (E a) (E' a)] @@ -114,7 +111,7 @@ theorem comp_ofPolyTime (h : c.ExpTimePolySpace E) {r : β → γ} refine ⟨C + ct * D ^ dt + cs * D ^ ds, d + d * dt + d * ds, fun a => ?_⟩ set Y := ((enc a).length + 1) ^ (d + d * dt + d * ds) have hY {e : ℕ} (he : e ≤ d + d * dt + d * ds) : ((enc a).length + 1) ^ e ≤ Y := - pow_le_pow_of_le he + Nat.pow_le_pow_right (Nat.succ_pos _) he have hZ : Y ≤ 2 ^ E a * Y := Nat.le_mul_of_pos_left Y (Nat.two_pow_pos (E a)) rw [comp_time, comp_space] constructor @@ -157,8 +154,9 @@ theorem expTimePolySpace_ofPolyTime (hf : ComputableInPolyTime f enc eβ) (E : obtain ⟨cs, ds, hs⟩ := hp.2.exists_le refine ⟨ct + cs, dt + ds, fun a => ⟨(ht a).trans ?_, (hs a).trans ?_⟩⟩ · rw [Nat.mul_assoc] - exact Nat.mul_le_mul (by omega) ((pow_le_pow_of_le (Nat.le_add_right _ _)).trans - (Nat.le_mul_of_pos_left _ (Nat.two_pow_pos _))) - · exact Nat.mul_le_mul (by omega) (pow_le_pow_of_le (Nat.le_add_left _ _)) + exact Nat.mul_le_mul (by omega) + ((Nat.pow_le_pow_right (Nat.succ_pos _) (Nat.le_add_right _ _)).trans + (Nat.le_mul_of_pos_left _ (Nat.two_pow_pos _))) + · exact Nat.mul_le_mul (by omega) (Nat.pow_le_pow_right (Nat.succ_pos _) (Nat.le_add_left _ _)) end Cslib.GenericRAM.NativeCertificate From bc8c06e85e1bacd37280b7434ff546a456f2a6e0 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 17:54:56 -0400 Subject: [PATCH 13/13] refactor(Quantum): define acceptGap as onesGap [0]; drop acceptTerm Address review: the acceptance gap is now the projected gap of qubit 0, with no parallel implementation. acceptGap_mem_GapP uses the specialization onesTerm_zero. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Quantum/PathNetwork.lean | 2 +- .../Circuit/Quantum/PathSum.lean | 46 ++++++++----------- .../Complexity/Classes/BQP/Counting.lean | 4 +- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean index 0a1ae707f..6f96a84ab 100644 --- a/Complexity/Computability/Circuit/Quantum/PathNetwork.lean +++ b/Complexity/Computability/Circuit/Quantum/PathNetwork.lean @@ -725,7 +725,7 @@ private theorem map_pairFun_right {N : ℕ} (a b : Fin N → Bool) : /-- **The contraction of the network is the projected gap.** With `-1` as the Hadamard sign, over any commutative ring, the contraction of the network of well-formed gate codes with the measured qubits `S` on the input register `X` is `onesGap S gs X`; for `S = [0]` it is the acceptance gap -(`Cslib.Quantum.acceptGap_eq_onesGap`). -/ +`Cslib.Quantum.acceptGap`. -/ theorem contraction_network {R : Type*} [CommRing R] (S : List ℕ) (X : List Bool) (gs : List GateCode) (hgs : ∀ g ∈ gs, g.WellFormed X.length) : (network (-1 : R) S X gs).2.contraction = (onesGap S gs X : R) := by diff --git a/Complexity/Computability/Circuit/Quantum/PathSum.lean b/Complexity/Computability/Circuit/Quantum/PathSum.lean index 94aa3e357..ead1a98fb 100644 --- a/Complexity/Computability/Circuit/Quantum/PathSum.lean +++ b/Complexity/Computability/Circuit/Quantum/PathSum.lean @@ -30,18 +30,17 @@ Squaring and summing over the accepting outcomes, the acceptance probability is simulations, which places `BQP` in the counting classes (`Complexity/Computability/Complexity/Classes/BQP/Counting.lean`). The same holds for the probability that every qubit of a list `S` reads `1`, with the pairs of paths ending in the same -register in which every qubit of `S` is one (`Cslib.Quantum.onesProb_eq_onesGap`); acceptance is -the case `S = [0]` (`Cslib.Quantum.acceptGap_eq_onesGap`). +register in which every qubit of `S` is one (`Cslib.Quantum.onesProb_eq_onesGap`); the acceptance +gap is the case `S = [0]`. ## Main definitions * `Cslib.Quantum.pathStep`, `Cslib.Quantum.pathRun`: the path simulation. * `Cslib.Quantum.hadamardCount`: the number of Hadamard gates. * `Cslib.Quantum.pathTerm`, `Cslib.Quantum.pathGap`: the signed path count of an amplitude. -* `Cslib.Quantum.acceptTerm`, `Cslib.Quantum.acceptGap`: the signed count of pairs of paths of the - acceptance probability. -* `Cslib.Quantum.onesTerm`, `Cslib.Quantum.onesGap`: the same for the event that every qubit of a - list reads `1`. +* `Cslib.Quantum.onesTerm`, `Cslib.Quantum.onesGap`: the signed count of pairs of paths of the + probability that every qubit of a list reads `1`. +* `Cslib.Quantum.acceptGap`: its case of qubit `0`, for the acceptance probability. ## Main results @@ -124,18 +123,6 @@ def pathTerm (gs : List GateCode) (x y π : List Bool) : ℤ := def pathGap (gs : List GateCode) (x y : List Bool) : ℤ := ∑ π : Fin (hadamardCount gs) → Bool, pathTerm gs x y (List.ofFn π) -/-- The signed contribution of a pair of paths to the acceptance probability: the product of -their signs when both end in the same register with qubit `0` equal to one, and `0` otherwise. -/ -def acceptTerm (gs : List GateCode) (x π π' : List Bool) : ℤ := - match pathRun gs x π, pathRun gs x π' with - | (s, σ), (s', σ') => - if s = s' ∧ s.headD false = true then (if σ ^^ σ' then -1 else 1) else 0 - -/-- The acceptance gap: the sum of `acceptTerm` over all pairs of paths. -/ -def acceptGap (gs : List GateCode) (x : List Bool) : ℤ := - ∑ π : Fin (hadamardCount gs) → Bool, ∑ π' : Fin (hadamardCount gs) → Bool, - acceptTerm gs x (List.ofFn π) (List.ofFn π') - /-- The signed contribution of a pair of paths to the probability that every qubit of `S` reads `1`: the product of their signs when both end in the same register with every qubit of `S` equal to one, and `0` otherwise. -/ @@ -144,23 +131,28 @@ def onesTerm (S : List ℕ) (gs : List GateCode) (x π π' : List Bool) : ℤ := | (s, σ), (s', σ') => if s = s' ∧ (S.all fun j => s.getD j false) = true then (if σ ^^ σ' then -1 else 1) else 0 -/-- The **projected gap** of `S`: the sum of `onesTerm S` over all pairs of paths. The acceptance -gap is the case `S = [0]` (`Cslib.Quantum.acceptGap_eq_onesGap`). -/ +/-- The **projected gap** of `S`: the sum of `onesTerm S` over all pairs of paths. -/ def onesGap (S : List ℕ) (gs : List GateCode) (x : List Bool) : ℤ := ∑ π : Fin (hadamardCount gs) → Bool, ∑ π' : Fin (hadamardCount gs) → Bool, onesTerm S gs x (List.ofFn π) (List.ofFn π') -theorem acceptGap_eq_onesGap (gs : List GateCode) (x : List Bool) : - acceptGap gs x = onesGap [0] gs x := by - refine Finset.sum_congr rfl fun π _ => Finset.sum_congr rfl fun π' _ => ?_ - simp only [acceptTerm, onesTerm, List.all_cons, List.all_nil, Bool.and_true] - generalize pathRun gs x (List.ofFn π) = r +/-- The acceptance gap: the projected gap of qubit `0`. -/ +def acceptGap (gs : List GateCode) (x : List Bool) : ℤ := onesGap [0] gs x + +/-- The contribution of a pair of paths to the acceptance gap: the product of their signs when both +end in the same register with qubit `0` equal to one, and `0` otherwise. -/ +theorem onesTerm_zero (gs : List GateCode) (x π π' : List Bool) : + onesTerm [0] gs x π π' = + if (pathRun gs x π).1 = (pathRun gs x π').1 ∧ (pathRun gs x π).1.headD false = true then + (if (pathRun gs x π).2 ^^ (pathRun gs x π').2 then -1 else 1) else 0 := by + simp only [onesTerm, List.all_cons, List.all_nil, Bool.and_true] + generalize pathRun gs x π = r obtain ⟨s, σ⟩ := r cases s <;> rfl /-- The acceptance gap of the empty circuit is the bit of qubit `0`. -/ theorem acceptGap_nil (x : List Bool) : acceptGap [] x = if x.headD false then 1 else 0 := by - simp only [acceptGap, hadamardCount_nil, acceptTerm, + simp only [acceptGap, onesGap, hadamardCount_nil, onesTerm_zero, pathRun, List.ofFn_zero, List.foldl_nil, true_and, Bool.xor_self, Bool.false_eq_true, ↓reduceIte] cases x.headD false <;> rfl @@ -351,7 +343,7 @@ theorem acceptProb_eq_acceptGap (gs : List GateCode) (hgs : ∀ g ∈ gs, g.Well (x : List Bool) : acceptProb m gs x = (acceptGap gs (List.ofFn (inputState m x)) : ℝ) / 2 ^ hadamardCount gs := by - rw [acceptGap_eq_onesGap, acceptProb_eq_onesProb, onesProb_eq_onesGap gs hgs] + rw [acceptGap, acceptProb_eq_onesProb, onesProb_eq_onesGap gs hgs] /-- **A projected gap is a count between `0` and `2 ^ h`** for well-formed codes on the register `X`, since it is `2 ^ h` times a probability. -/ diff --git a/Complexity/Computability/Complexity/Classes/BQP/Counting.lean b/Complexity/Computability/Complexity/Classes/BQP/Counting.lean index 4fada9b86..338ee4128 100644 --- a/Complexity/Computability/Complexity/Classes/BQP/Counting.lean +++ b/Complexity/Computability/Complexity/Classes/BQP/Counting.lean @@ -141,8 +141,8 @@ theorem acceptGap_mem_GapP (F : UniformCircuitFamily) : have hinner := GapP.sum_bits_mem hT (r := fun p : α × List Bool => h p.1) (by fun_prop) have houter := GapP.sum_bits_mem hinner hh convert houter using 2 with a - rw [ofFn_inputState (F.le_width _)] - rfl + rw [ofFn_inputState (F.le_width _), acceptGap, onesGap] + exact Finset.sum_congr rfl fun π _ => Finset.sum_congr rfl fun π' _ => onesTerm_zero _ _ _ _ /-- **`BQP ⊆ AWPP`** [Fortnow, Rogers][FortnowRogers1999]: the acceptance probability of a uniform family is a `GapP` function divided by `2^h`, with `h` computed in polynomial time. -/