diff --git a/Complexity.lean b/Complexity.lean index 2e1c61c92a..a22a7cece1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -764,6 +764,9 @@ public import Complexity.Computability.Circuit.Boolean.PPoly public import Complexity.Computability.Circuit.Boolean.PPolyHard public import Complexity.Computability.Circuit.Boolean.Parity public import Complexity.Computability.Circuit.Boolean.PolynomialTime +public import Complexity.Computability.Circuit.Boolean.PopulationCount +public import Complexity.Computability.Circuit.Boolean.PopulationCount.PolyTime +public import Complexity.Computability.Circuit.Boolean.PopulationCount.ThresholdPolyTime public import Complexity.Computability.Circuit.Boolean.Propagation public import Complexity.Computability.Circuit.Boolean.ReadOnce public import Complexity.Computability.Circuit.Boolean.Routing @@ -905,6 +908,11 @@ public import Complexity.Computability.Circuit.Frontier.Sweep public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ActiveCall public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Binarize +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryDigits +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryDigitsPolyTime +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryWeightBounds +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryWeights +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CSPBinaryWeights public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CSPNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CSPNetworkPolyTime public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes @@ -938,6 +946,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCo public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProduct public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModulusGrowth public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeCall public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChild public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChildBounds @@ -958,6 +967,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Truncated public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedAlgorithm public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedEntry public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedPolyTime +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedPolynomialEntries public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedResources public import Complexity.Computability.Circuit.Frontier.TensorNetwork.VertexDesc public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness @@ -1186,6 +1196,7 @@ public import Complexity.Computability.Complexity.Classes.CountingSimulation.Gua public import Complexity.Computability.Complexity.Classes.CountingSimulation.Interfaces public import Complexity.Computability.Complexity.Classes.CountingSimulation.Promise public import Complexity.Computability.Complexity.Classes.CountingSimulation.Separation +public import Complexity.Computability.Complexity.Classes.CountingSimulation.TruncatedBoundedDegree public import Complexity.Computability.Complexity.Classes.CountingSimulation.TruncatedEntry public import Complexity.Computability.Complexity.Classes.DP public import Complexity.Computability.Complexity.Classes.EXP @@ -1428,6 +1439,7 @@ public import Complexity.Computability.Complexity.Fagin.Verifier public import Complexity.Computability.Complexity.Fagin.Witness public import Complexity.Computability.Complexity.FineGrained public import Complexity.Computability.Complexity.FineGrained.Assignment +public import Complexity.Computability.Complexity.FineGrained.BinaryWeightedCounting public import Complexity.Computability.Complexity.FineGrained.BruteForce public import Complexity.Computability.Complexity.FineGrained.CircuitCounting public import Complexity.Computability.Complexity.FineGrained.CircuitLowerBoundBudget @@ -1453,6 +1465,9 @@ public import Complexity.Computability.Complexity.FineGrained.Exponents public import Complexity.Computability.Complexity.FineGrained.GeneralCNFBranchSchedule public import Complexity.Computability.Complexity.FineGrained.GraphProblems public import Complexity.Computability.Complexity.FineGrained.LocalMapCounting +public import Complexity.Computability.Complexity.FineGrained.MaxSAT +public import Complexity.Computability.Complexity.FineGrained.MaxSAT.Histogram +public import Complexity.Computability.Complexity.FineGrained.MaxSAT.PolyTime public import Complexity.Computability.Complexity.FineGrained.OrthogonalVectors public import Complexity.Computability.Complexity.FineGrained.PPSZ.AssignSL public import Complexity.Computability.Complexity.FineGrained.PPSZ.Basic @@ -1659,6 +1674,7 @@ public import Complexity.Computability.GenericRAM.Native.HeavyBranchFold public import Complexity.Computability.GenericRAM.Native.IndexFold public import Complexity.Computability.GenericRAM.Native.MeasurementFold public import Complexity.Computability.GenericRAM.Native.Nat +public import Complexity.Computability.GenericRAM.Native.PolynomialOverhead public import Complexity.Computability.GenericRAM.Native.StackRecursion public import Complexity.Computability.GenericRAM.Native.StackRecursionTree public import Complexity.Computability.GenericRAM.Native.SubsetPairFold @@ -1847,6 +1863,7 @@ public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolyTime public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolynomialPadding +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolynomialQueries public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Preprocess public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Sum public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.TwoPow @@ -1957,6 +1974,7 @@ public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Poly public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Option public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Pi public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Polynomial public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.RotationMap public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.RotationMap.Family public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.RotationMap.Tower @@ -3313,6 +3331,7 @@ public import Complexity.Foundations.Data.Nat.SqrtRatio public import Complexity.Foundations.Data.Nat.Subexponential public import Complexity.Foundations.Data.Nat.SummationByParts public import Complexity.Foundations.Data.Option +public import Complexity.Foundations.Data.Polynomial.BinaryDigits public import Complexity.Foundations.Data.Polynomial.BivariateCoeffNorm1 public import Complexity.Foundations.Data.Polynomial.CoeffList public import Complexity.Foundations.Data.Polynomial.CoeffNorm1 @@ -3606,6 +3625,7 @@ public import Complexity.Logics.Propositional.CNF.ThreeOccurrence.PolyTime public import Complexity.Logics.Propositional.CNF.TwoCNF public import Complexity.Logics.Propositional.CNF.UnsatVal public import Complexity.Logics.Propositional.CSP +public import Complexity.Logics.Propositional.CSP.BinaryDigits public import Complexity.Logics.Propositional.CSP.ExactlyOne public import Complexity.Logics.Propositional.CSP.HardCore public import Complexity.Logics.Propositional.CSP.PartitionFunction diff --git a/Complexity/Computability/Circuit/Boolean/PopulationCount.lean b/Complexity/Computability/Circuit/Boolean/PopulationCount.lean new file mode 100644 index 0000000000..08a51dde5e --- /dev/null +++ b/Complexity/Computability/Circuit/Boolean/PopulationCount.lean @@ -0,0 +1,220 @@ +/- +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.Boolean.CarryLookahead +public import Complexity.Computability.Circuit.Boolean.ThresholdNC1 +import Complexity.Foundations.Data.Nat.Log +import Mathlib.Tactic.GCongr +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Linear-size population counting and thresholds + +The word width at a node of a balanced addition tree is the height of that node plus one. +Consequently, the existing quadratic-size adder suffices for a linear-size population counter: +there are exponentially fewer nodes at increasing heights. All gates, including constants, +are charged. The input is padded with zeros to a power of two, so the construction also handles +an empty input. + +## Main definitions + +* `Cslib.Circuits.Boolean.PopulationCount.tree`: the counter on a power-of-two input block. +* `Cslib.Circuits.Boolean.populationCountCircuit`: the counter on an arbitrary input block. +* `Cslib.Circuits.Boolean.linearThresholdCircuit`: a threshold circuit of linear size. + +## Main results + +* `Cslib.Circuits.Boolean.val_eval_populationCountCircuit`: exact binary population count. +* `Cslib.Circuits.Boolean.size_populationCountCircuit_le`: a linear gate bound. +* `Cslib.Circuits.Boolean.eval_linearThresholdCircuit`: exact threshold semantics. +* `Cslib.Circuits.Boolean.size_linearThresholdCircuit_le`: a linear gate bound. + +## References + +Binary addition and threshold circuits are standard; see [Vollmer, Chapter 1][Vollmer1999]. +The concrete adders and their source attribution are in `Boolean.CarryLookahead`. +-/ + +public section + +namespace Cslib.Circuits.Boolean + +open BitString + +namespace PopulationCount + +/-- Select a bit from the first half of the next tree level. -/ +@[expose] def leftWire (h : ℕ) (i : Fin (2 ^ h)) : Fin (2 ^ (h + 1)) := + ⟨i.val, by have := i.isLt; rw [pow_succ, Nat.mul_two]; omega⟩ + +/-- Select a bit from the second half of the next tree level. -/ +@[expose] def rightWire (h : ℕ) (i : Fin (2 ^ h)) : Fin (2 ^ (h + 1)) := + ⟨2 ^ h + i.val, by have := i.isLt; rw [pow_succ, Nat.mul_two]; omega⟩ + +/-- Add the counts of the two half blocks, retaining the final carry. A height-`h` tree has +`2^h` inputs and `h+1` output bits. -/ +@[expose] def tree : (h : ℕ) → Circuit signature (2 ^ h) (h + 1) + | 0 => Circuit.id signature 1 + | h + 1 => + (carryLookahead (h + 1)).comp + ((((tree h).comp (Circuit.wiring signature (leftWire h))).append + ((tree h).comp (Circuit.wiring signature (rightWire h)))).append + (Circuit.oneGate (.const false) Fin.elim0)) + +private theorem weight_halves (h : ℕ) (x : BitString (2 ^ (h + 1))) : + weight (x ∘ leftWire h) + weight (x ∘ rightWire h) = weight x := by + have he : 2 ^ h + 2 ^ h = 2 ^ (h + 1) := by rw [pow_succ, Nat.mul_two] + rw [weight_eq_sum_toNat, weight_eq_sum_toNat, weight_eq_sum_toNat] + calc + _ = ∑ i : Fin (2 ^ h + 2 ^ h), (x (Fin.cast he i)).toNat := by + rw [Fin.sum_univ_add] + rfl + _ = ∑ i : Fin (2 ^ (h + 1)), (x i).toNat := + Fintype.sum_equiv (finCongr he) _ _ (fun _ => rfl) + +/-- The output word is the exact number of true inputs, including the full carry. -/ +theorem val_eval_tree (h : ℕ) (x : BitString (2 ^ h)) : + (BitString.equivFin (h + 1) ((tree h).eval interpretation x) : ℕ) = weight x := by + induction h with + | zero => + change (equivFin 1 x : ℕ) = weight x + rw [val_equivFin_eq_sum, weight_eq_sum_toNat] + simp + | succ h ih => + simp only [tree, Circuit.eval_comp, Circuit.eval_append, Circuit.eval_wiring] + change (BitString.equivFin (h + 1 + 1) + ((carryLookahead (h + 1)).eval interpretation + (Fin.append + (Fin.append ((tree h).eval interpretation (x ∘ leftWire h)) + ((tree h).eval interpretation (x ∘ rightWire h))) (fun _ => false))) : ℕ) = _ + rw [val_eval_carryLookahead, ih, ih, Bool.toNat_false, Nat.add_zero, weight_halves] + +private theorem size_tree_succ (h : ℕ) : + (tree (h + 1)).size = 2 * (tree h).size + (carryLookahead (h + 1)).size + 1 := by + simp [tree, Nat.two_mul, Nat.add_comm, Nat.add_left_comm] + +private theorem size_tree_with_slack (h : ℕ) : + (tree h).size + 21 * (h ^ 2 + 6 * h + 11) ≤ 231 * 2 ^ h := by + induction h with + | zero => simp [tree] + | succ h ih => + have hadd := size_carryLookahead_le (h + 1) + rw [size_tree_succ, pow_succ 2 h] + nlinarith + +/-- The increasing word widths cost only a constant per leaf in total. -/ +theorem size_tree_le (h : ℕ) : (tree h).size ≤ 231 * 2 ^ h := by + have := size_tree_with_slack h + omega + +/-- One shared false gate extends a word with zeros. -/ +@[expose] def pad (n m : ℕ) : Circuit signature n m := + (Circuit.wiring signature (fun i : Fin m => + if hi : i.val < n then Fin.castAdd 1 ⟨i.val, hi⟩ else Fin.natAdd n 0)).comp + ((Circuit.id signature n).append (Circuit.oneGate (.const false) Fin.elim0)) + +/-- Padding agrees with the standard zero extension of a bit string. -/ +@[simp] theorem eval_pad (n m : ℕ) (x : BitString n) : + (pad n m).eval interpretation x = zeroExtend x := by + funext i + simp only [pad, Circuit.eval_comp, Circuit.eval_wiring, Circuit.eval_append, + Function.comp_apply, zeroExtend] + split_ifs with hi + · change Fin.append x (fun _ => false) (Fin.castAdd 1 ⟨i.val, hi⟩) = _ + exact Fin.append_left _ _ _ + · change Fin.append x (fun _ => false) (Fin.natAdd n 0) = false + exact Fin.append_right _ _ _ + +/-- Zero padding charges exactly one gate. -/ +@[simp] theorem size_pad (n m : ℕ) : (pad n m).size = 1 := by + simp [pad] + +private theorem weight_zeroExtend {n m : ℕ} (h : n ≤ m) (x : BitString n) : + weight (zeroExtend (d' := m) x) = weight x := by + rw [weight, weight] + have hlt (i : Fin m) (hi : i ∈ Finset.univ.filter (fun j => zeroExtend x j = true)) : + i.val < n := by + have ht := (Finset.mem_filter.mp hi).2 + by_contra hn + simp [zeroExtend, hn] at ht + refine Finset.card_bij (fun i hi => (⟨i.val, hlt i hi⟩ : Fin n)) ?_ ?_ ?_ + · intro i hi + exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, by + simpa only [zeroExtend, dite_eq_left (hlt i hi)] using (Finset.mem_filter.mp hi).2⟩ + · intro i hi j hj hij + exact Fin.ext (congrArg (fun a : Fin n => a.val) hij) + · intro i hi + refine ⟨⟨i.val, i.isLt.trans_le h⟩, ?_, rfl⟩ + exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, by + simpa [zeroExtend, i.isLt] using (Finset.mem_filter.mp hi).2⟩ + +end PopulationCount + +/-- A population count on `n` inputs. Padding to the next power of two uses one shared false +gate, and the balanced tree returns `⌈log₂(n+1)⌉+1` bits. -/ +@[expose] def populationCountCircuit (n : ℕ) : + Circuit signature n (Nat.clog 2 (n + 1) + 1) := + (PopulationCount.tree (Nat.clog 2 (n + 1))).comp + (PopulationCount.pad n (2 ^ Nat.clog 2 (n + 1))) + +/-- The circuit computes the exact binary numeral of the input's Hamming weight. -/ +theorem val_eval_populationCountCircuit (n : ℕ) (x : BitString n) : + (BitString.equivFin (Nat.clog 2 (n + 1) + 1) + ((populationCountCircuit n).eval interpretation x) : ℕ) = weight x := by + rw [populationCountCircuit, Circuit.eval_comp, PopulationCount.val_eval_tree, + PopulationCount.eval_pad] + exact PopulationCount.weight_zeroExtend + ((Nat.le_succ n).trans (Nat.le_pow_clog Nat.one_lt_two _)) x + +/-- A uniform linear gate bound, with constants and padding included. -/ +theorem size_populationCountCircuit_le (n : ℕ) : + (populationCountCircuit n).size ≤ 463 * (n + 1) := by + have hs := PopulationCount.size_tree_le (Nat.clog 2 (n + 1)) + have hp := Nat.pow_clog_le_mul Nat.one_lt_two (Nat.succ_ne_zero n) + rw [populationCountCircuit, Circuit.size_comp, PopulationCount.size_pad] + nlinarith + +/-- Compare the population count with a cutoff. An impossible cutoff returns false. -/ +@[expose] def linearThresholdCircuit (n q : ℕ) : Circuit signature n 1 := + if hq : q ≤ n then + let w := Nat.clog 2 (n + 1) + 1 + have hb : q < 2 ^ w := + (Nat.lt_succ_of_le hq).trans_le ((Nat.le_pow_clog Nat.one_lt_two (n + 1)).trans + (Nat.pow_le_pow_right (by omega : 1 ≤ (2 : ℕ)) (Nat.le_succ _))) + (leCircuit w).comp + ((wordConstCircuit n ((BitString.equivFin w).symm ⟨q, hb⟩)).append + (populationCountCircuit n)) + else Circuit.oneGate (.const false) Fin.elim0 + +/-- The linear-size threshold circuit accepts precisely when at least `q` inputs are true. -/ +@[simp] theorem eval_linearThresholdCircuit (n q : ℕ) (x : BitString n) : + (linearThresholdCircuit n q).eval interpretation x = single (threshold n q) x := by + funext o + dsimp [linearThresholdCircuit] + split_ifs with hq + · rw [Circuit.eval_comp, Circuit.eval_append, eval_wordConstCircuit, eval_leCircuit] + simp only [threshold, Equiv.apply_symm_apply, Fin.le_iff_val_le_val, + val_eval_populationCountCircuit] + · simp only [Circuit.eval_oneGate, interpretation, threshold_eq_false_of_lt (not_le.mp hq)] + +/-- Linear population counting followed by comparison remains linear in the input count. -/ +theorem size_linearThresholdCircuit_le (n q : ℕ) : + (linearThresholdCircuit n q).size ≤ 476 * (n + 1) := by + simp only [linearThresholdCircuit] + split_ifs with hq + · rw [Circuit.size_comp, Circuit.size_append, size_leCircuit, size_wordConstCircuit] + have hs := size_populationCountCircuit_le n + have hl : Nat.clog 2 (n + 1) ≤ n + 1 := + Nat.clog_le_of_le_pow (Nat.lt_two_pow_self.le) + omega + · simp only [Circuit.size_oneGate] + omega + +end Cslib.Circuits.Boolean diff --git a/Complexity/Computability/Circuit/Boolean/PopulationCount/PolyTime.lean b/Complexity/Computability/Circuit/Boolean/PopulationCount/PolyTime.lean new file mode 100644 index 0000000000..6b1b5e47db --- /dev/null +++ b/Complexity/Computability/Circuit/Boolean/PopulationCount/PolyTime.lean @@ -0,0 +1,200 @@ +/- +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.Boolean.PopulationCount +public import Complexity.Computability.Circuit.Boolean.CarryLookahead.PolyTime +import Complexity.Computability.Circuit.Boolean.EncodingSize +import Complexity.Foundations.Data.BitString.Binary +import Complexity.Foundations.Data.Nat.Log +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Iteration +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.Complexity.PolyTime.Nat.Log +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + +/-! +# Native generation of linear-size threshold circuits + +The population tree is generated one height at a time. Its state includes the current number +of leaves in unary as well as the complete circuit code. Only logarithmically many heights are +visited, and each intermediate tree has at most twice the requested number of leaves. Thus all +wire numbers, framing bits, and intermediate circuit descriptions have polynomial total length. + +The resulting polynomial-time certificate concerns the actual gate-and-output encoding used +by the native circuit counter. +-/ + +@[expose] public section + +namespace Cslib.Circuits.Boolean + +open Turing.MultiTapeTM ComputableInPolyTime + +namespace PopulationCount + +/-- Combine two copies of a counter on `p` inputs, using an exact `w`-bit adder. -/ +def treeStepCode (p w : ℕ) (c : List (Op × List ℕ) × List ℕ) : + List (Op × List ℕ) × List ℕ := + compCode (p + p) (carryLookahead w).code + (appendCode (p + p) + (appendCode (p + p) + (compCode (p + p) c ([], List.range p)) + (compCode (p + p) c ([], (List.range p).map (fun i => p + i)))) + ([(.const false, [])], [p + p])) + +private theorem treeStepCode_eq (h : ℕ) : + treeStepCode (2 ^ h) (h + 1) (tree h).code = (tree (h + 1)).code := by + have hl : (Circuit.wiring signature (leftWire h)).code = ([], List.range (2 ^ h)) := by + simpa using Circuit.code_wiring_eq_map (σ := signature) (f := leftWire h) + (g := id) (fun _ => rfl) + have hr : (Circuit.wiring signature (rightWire h)).code = + ([], (List.range (2 ^ h)).map (fun i => 2 ^ h + i)) := + Circuit.code_wiring_eq_map (σ := signature) (f := rightWire h) + (g := fun i => 2 ^ h + i) (fun _ => rfl) + rw [tree, ← compCode_code, ← appendCode_code, ← appendCode_code, + ← compCode_code, ← compCode_code, hl, hr] + have hz : (Circuit.oneGate (σ := signature) (n := 2 ^ (h + 1)) + (.const false) Fin.elim0).code = ([(.const false, [])], [2 ^ (h + 1)]) := rfl + rw [hz] + simp only [treeStepCode, pow_succ, Nat.mul_two] + +private theorem treeStepCode_polyTime {α : Type} {enc : α ↪ List Bool} + {p w : α → ℕ} {c : α → List (Op × List ℕ) × List ℕ} + (hp : ComputableInPolyTime p enc encUnary) + (hw : ComputableInPolyTime w enc encUnary) + (hc : ComputableInPolyTime c enc encCode) : + ComputableInPolyTime (fun a => treeStepCode (p a) (w a) (c a)) enc encCode := by + have hp' := unary_to_binary.comp hp + have hn : ComputableInPolyTime (fun a => p a + p a) enc encNat := by fun_prop + have hl : ComputableInPolyTime (fun a => ([], List.range (p a))) enc encCode := by + have h := unary_to_binary.map.comp (range.comp hp) + simpa only [Function.comp_def, List.map_id_fun', id_eq] using + (const (encOut := encList encGate) []).prod h + have hr : ComputableInPolyTime + (fun a => ([], (List.range (p a)).map (fun i => p a + i))) enc encCode := by + have hi : ComputableInPolyTime (fun a : α × ℕ => p a.1 + a.2) + (encPair enc encUnary) encNat := by + have hleft := hp'.comp (fst enc encUnary) + have hright := unary_to_binary.comp (snd enc encUnary) + fun_prop + exact (const (encOut := encList encGate) []).prod (mapWithInput (range.comp hp) hi) + have hz : ComputableInPolyTime + (fun a => ([(Op.const false, [])], [p a + p a])) enc encCode := by fun_prop + exact computableInPolyTime_compCode hn (computableInPolyTime_carryLookahead.comp hw) + (computableInPolyTime_appendCode hn + (computableInPolyTime_appendCode hn + (computableInPolyTime_compCode hn hc hl) + (computableInPolyTime_compCode hn hc hr)) hz) + +/-- Generate the power-of-two tree selected by an arbitrary unary input count. -/ +theorem computableInPolyTime_tree : + ComputableInPolyTime (fun n => (tree (Nat.clog 2 (n + 1))).code) encUnary encCode := by + let stateEnc := encPair (encPair encUnary encUnary) encCode + let init : (ℕ × ℕ) × (List (Op × List ℕ) × List ℕ) := ((0, 1), ([], [0])) + let next (state : (ℕ × ℕ) × (List (Op × List ℕ) × List ℕ)) := + ((state.1.1 + 1, state.1.2 + state.1.2), + treeStepCode state.1.2 (state.1.1 + 1) state.2) + have hn : ComputableInPolyTime next stateEnc stateEnc := by + have hh := (fst (encPair encUnary encUnary) encCode).fst' + have hp := (fst (encPair encUnary encUnary) encCode).snd' + have hc := snd (encPair encUnary encUnary) encCode + have hw : ComputableInPolyTime + (fun a : (ℕ × ℕ) × (List (Op × List ℕ) × List ℕ) => a.1.1 + 1) + stateEnc encUnary := by fun_prop + have hp2 : ComputableInPolyTime + (fun a : (ℕ × ℕ) × (List (Op × List ℕ) × List ℕ) => a.1.2 + a.1.2) + stateEnc encUnary := by fun_prop + exact (hw.prod hp2).prod (treeStepCode_polyTime hp hw hc) + have heq (i : ℕ) : next^[i] init = ((i, 2 ^ i), (tree i).code) := by + induction i with + | zero => rfl + | succ i ih => + rw [Function.iterate_succ_apply', ih] + simp only [next, treeStepCode_eq, pow_succ, Nat.mul_two] + have hcount : ComputableInPolyTime (fun n : ℕ => Nat.clog 2 (n + 1)) + encUnary encUnary := by + exact (unary_clog_const 2).comp (by fun_prop) + let bound (n : ℕ) := 5000000 * (n + 2) ^ 2 + have hb : PolyBounded (fun n => (encUnary n).length) bound := + polyBounded (by unfold bound; fun_prop) + have h := iterateWith (encPar := encUnary) (par := fun _ : ℕ => 0) + (count := fun n => Nat.clog 2 (n + 1)) (init := fun _ => init) + (step := fun _ => next) (const 0) hcount (const init) + (hn.comp (snd encUnary stateEnc)) hb (fun n i hi => by + rw [heq] + have hi' : i ≤ n + 1 := hi.trans (Nat.clog_le_of_le_pow (Nat.lt_two_pow_self.le)) + have hp : 2 ^ i ≤ 2 * (n + 1) := + (Nat.pow_le_pow_right (by omega : 1 ≤ (2 : ℕ)) hi).trans + (Nat.pow_clog_le_mul Nat.one_lt_two (Nat.succ_ne_zero n)) + have hs : (tree i).size ≤ 462 * (n + 1) := + (size_tree_le i).trans (by nlinarith) + have hiout : i + 1 ≤ n + 2 := by omega + have hcode := length_encCode_le (tree i) + have hbound : (encCode (tree i).code).length ≤ + 2 * (462 * (n + 1)) * (23 + 8 * (2 * (n + 1) + 462 * (n + 1))) + 1 + + (n + 2) * (2 * (2 * (n + 1) + 462 * (n + 1)) + 1) := + hcode.trans (by gcongr) + change (encPair (encPair encUnary encUnary) encCode + ((i, 2 ^ i), (tree i).code)).length ≤ _ + rw [length_encPair, length_encPair] + simp only [encUnary_apply, List.length_replicate] + dsimp only [bound] + nlinarith) + simpa only [Function.comp_def, heq] using h.snd' + +private theorem pad_code (n m : ℕ) : + (pad n m).code = ([(.const false, [])], + (List.range m).map (fun i => if i < n then i else n)) := by + have hw (i : Fin m) : ((pad n m).outputs i).toNat = + if i.val < n then i.val else n := by + dsimp only [pad, Circuit.comp, Circuit.wiring, Function.comp_apply, + Program.appendedWire] + split_ifs with hi + · rw [Circuit.outputs_append_castAdd] + rfl + · rw [Circuit.outputs_append_natAdd] + rfl + apply Prod.ext + · rfl + · apply List.ext_getElem (by simp [pad, Circuit.code]) + intro i hi hj + simp only [Circuit.code, List.getElem_ofFn, hw, List.getElem_map, List.getElem_range] + +/-- Generate padding from unary input and output counts. -/ +theorem computableInPolyTime_pad : + ComputableInPolyTime (fun p : ℕ × ℕ => (pad p.1 p.2).code) + (encPair encUnary encUnary) encCode := by + have hwire : ComputableInPolyTime + (fun p : (ℕ × ℕ) × ℕ => if p.2 < p.1.1 then p.2 else p.1.1) + (encPair (encPair encUnary encUnary) encUnary) encNat := by + have hn := unary_to_binary.comp ((fst (encPair encUnary encUnary) encUnary).fst') + have hi := unary_to_binary.comp (snd (encPair encUnary encUnary) encUnary) + fun_prop + have h := (const (encOut := encList encGate) [(Op.const false, [])]).prod + (mapWithInput (f := fun p i => if i < p.1 then i else p.1) + (range.comp (snd encUnary encUnary)) hwire) + simpa only [pad_code, Function.comp_def] using h + +end PopulationCount + +/-- The complete linear-size population-count circuit is generated in polynomial time from +its unary number of inputs. -/ +theorem computableInPolyTime_populationCountCircuit : + ComputableInPolyTime (fun n => (populationCountCircuit n).code) encUnary encCode := by + have hn := unary_to_binary + have hp : ComputableInPolyTime (fun n : ℕ => 2 ^ Nat.clog 2 (n + 1)) + encUnary encUnary := by + apply unary_pow_clog_const + fun_prop + have hpad := PopulationCount.computableInPolyTime_pad.comp + ((ComputableInPolyTime.id' (encIn := encUnary)).prod hp) + have h := computableInPolyTime_compCode hn PopulationCount.computableInPolyTime_tree hpad + simpa only [Function.comp_def, compCode_code, populationCountCircuit] using h + +end Cslib.Circuits.Boolean diff --git a/Complexity/Computability/Circuit/Boolean/PopulationCount/ThresholdPolyTime.lean b/Complexity/Computability/Circuit/Boolean/PopulationCount/ThresholdPolyTime.lean new file mode 100644 index 0000000000..fa475e6794 --- /dev/null +++ b/Complexity/Computability/Circuit/Boolean/PopulationCount/ThresholdPolyTime.lean @@ -0,0 +1,202 @@ +/- +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.Boolean.PopulationCount.PolyTime +import Complexity.Computability.Circuit.Boolean.EncodingSize +import Complexity.Foundations.Data.BitString.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Iteration +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.Complexity.PolyTime.Nat.Log +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + +/-! +# Native generation of the threshold comparison + +A unary-width loop generates the existing linear-size comparator, and binary digit selection +generates its constant threshold word. Together with the population-count generator these give +a native polynomial-time certificate for the full linear-size threshold circuit. +-/ + +@[expose] public section + +namespace Cslib.Circuits.Boolean + +open Turing.MultiTapeTM ComputableInPolyTime + +namespace PopulationCount + +private def comparisonStepCode (w : ℕ) (c : List (Op × List ℕ) × List ℕ) : + List (Op × List ℕ) × List ℕ := + compCode (w + 1 + (w + 1)) leStep.code + (appendCode (w + 1 + (w + 1)) + (compCode (w + 1 + (w + 1)) c + ([], (List.range (w + w)).map (fun i => if i < w then i else i + 1))) + ([], [w, w + 1 + w])) + +private theorem comparisonStepCode_eq (w : ℕ) : + comparisonStepCode w (leCircuit w).code = (leCircuit (w + 1)).code := by + have hl (i : Fin (w + w)) : + (lowBits w i).val = if i.val < w then i.val else i.val + 1 := by + refine Fin.addCases (fun i => ?_) (fun i => ?_) i + · simp only [lowBits, Fin.append_left, Fin.val_castAdd, Fin.val_castSucc] + simp [i.isLt] + · simp only [lowBits, Fin.append_right, Fin.val_natAdd, Fin.val_castSucc] + rw [ite_eq_right (by omega)] + omega + have hcode := Circuit.code_wiring_eq_map (σ := signature) (f := lowBits w) + (g := fun i => if i < w then i else i + 1) hl + rw [leCircuit, ← compCode_code, ← appendCode_code, ← compCode_code, hcode] + have ht : (Circuit.wiring signature (topBits w)).code = ([], [w, w + 1 + w]) := by + simp [Circuit.code_wiring, topBits] + rw [ht] + rfl + +private theorem comparisonStepCode_polyTime : + ComputableInPolyTime + (fun p : ℕ × (List (Op × List ℕ) × List ℕ) => comparisonStepCode p.1 p.2) + (encPair encUnary encCode) encCode := by + have hw := fst encUnary encCode + have hw' := unary_to_binary.comp hw + have hn : ComputableInPolyTime + (fun p : ℕ × (List (Op × List ℕ) × List ℕ) => p.1 + 1 + (p.1 + 1)) + (encPair encUnary encCode) encNat := by fun_prop + have hl : ComputableInPolyTime + (fun p : ℕ × (List (Op × List ℕ) × List ℕ) => + ([], (List.range (p.1 + p.1)).map (fun i => if i < p.1 then i else i + 1))) + (encPair encUnary encCode) encCode := by + have hi : ComputableInPolyTime + (fun p : (ℕ × (List (Op × List ℕ) × List ℕ)) × ℕ => + if p.2 < p.1.1 then p.2 else p.2 + 1) + (encPair (encPair encUnary encCode) encUnary) encNat := by + have hw'' := hw'.comp (fst (encPair encUnary encCode) encUnary) + have hi' := unary_to_binary.comp (snd (encPair encUnary encCode) encUnary) + fun_prop + have hr : ComputableInPolyTime + (fun p : ℕ × (List (Op × List ℕ) × List ℕ) => List.range (p.1 + p.1)) + (encPair encUnary encCode) (encList encUnary) := by fun_prop + exact (const (encOut := encList encGate) []).prod + (mapWithInput (f := fun p i => if i < p.1 then i else i + 1) hr hi) + have ht : ComputableInPolyTime + (fun p : ℕ × (List (Op × List ℕ) × List ℕ) => ([], [p.1, p.1 + 1 + p.1])) + (encPair encUnary encCode) encCode := by fun_prop + exact computableInPolyTime_compCode hn (const leStep.code) + (computableInPolyTime_appendCode hn + (computableInPolyTime_compCode hn (snd encUnary encCode) hl) ht) + +end PopulationCount + +/-- Generate the existing comparator's complete circuit code from unary word width. -/ +theorem computableInPolyTime_leCircuit : + ComputableInPolyTime (fun w => (leCircuit w).code) encUnary encCode := by + let stateEnc := encPair encUnary encCode + let init : ℕ × (List (Op × List ℕ) × List ℕ) := (0, ([(.const true, [])], [0])) + let next (state : ℕ × (List (Op × List ℕ) × List ℕ)) := + (state.1 + 1, PopulationCount.comparisonStepCode state.1 state.2) + have hn : ComputableInPolyTime next stateEnc stateEnc := by + have hh : ComputableInPolyTime + (fun p : ℕ × (List (Op × List ℕ) × List ℕ) => p.1 + 1) + stateEnc encUnary := by fun_prop + exact hh.prod PopulationCount.comparisonStepCode_polyTime + have heq (i : ℕ) : next^[i] init = (i, (leCircuit i).code) := by + induction i with + | zero => rfl + | succ i ih => + rw [Function.iterate_succ_apply', ih] + simp only [next, PopulationCount.comparisonStepCode_eq] + let bound (w : ℕ) := 1000 * (w + 1) ^ 2 + have hb : PolyBounded (fun w => (encUnary w).length) bound := + polyBounded (by unfold bound; fun_prop) + have h := iterateWith (encPar := encUnary) (par := fun _ : ℕ => 0) + (count := fun w => w) (init := fun _ => init) (step := fun _ => next) + (const 0) id' (const init) (hn.comp (snd encUnary stateEnc)) hb (fun w i hi => by + rw [heq] + have hc := length_encCode_le (leCircuit i) + rw [size_leCircuit] at hc + change (encPair encUnary encCode (i, (leCircuit i).code)).length ≤ _ + rw [length_encPair] + simp only [encUnary_apply, List.length_replicate] + dsimp only [bound] + nlinarith [Nat.pow_le_pow_left hi 2]) + simpa only [Function.comp_def, heq] using h.snd' + +namespace PopulationCount + +/-- The two constant gates and their selected output wires for a binary numeral. -/ +def numeralCode (n w q : ℕ) : List (Op × List ℕ) × List ℕ := + ([(.const false, []), (.const true, [])], + (List.range w).map (fun i => if q.testBit i then n + 1 else n)) + +private theorem numeralCode_eq (n w : ℕ) (q : Fin (2 ^ w)) : + numeralCode n w q.val = (wordConstCircuit n ((BitString.equivFin w).symm q)).code := by + have hw (i : Fin w) : + ((wordConstCircuit n ((BitString.equivFin w).symm q)).outputs i).toNat = + if q.val.testBit i.val then n + 1 else n := by + dsimp only [wordConstCircuit, Circuit.comp, Circuit.wiring, Function.comp_apply, + Program.appendedWire] + rw [BitString.equivFin_symm_apply] + cases q.val.testBit i.val <;> rfl + apply Prod.ext + · rfl + · apply List.ext_getElem (by simp [numeralCode, Circuit.code]) + intro i hi hj + simp only [numeralCode, List.getElem_map, List.getElem_range, Circuit.code, + List.getElem_ofFn, hw] + +private theorem numeralCode_polyTime {α : Type} {enc : α ↪ List Bool} + {n w q : α → ℕ} (hn : ComputableInPolyTime n enc encNat) + (hw : ComputableInPolyTime w enc encUnary) (hq : ComputableInPolyTime q enc encNat) : + ComputableInPolyTime (fun a => numeralCode (n a) (w a) (q a)) enc encCode := by + have hi : ComputableInPolyTime + (fun p : α × ℕ => if (q p.1).testBit p.2 then n p.1 + 1 else n p.1) + (encPair enc encUnary) encNat := by + have hn' := (hn.comp (fst enc encUnary)).tidy + have hq' := (hq.comp (fst enc encUnary)).tidy + have hi' := (snd enc encUnary).tidy + have ht := ComputableTidilyInPolyTime.binary_testBit hq' hi' + exact .of_tidy (ht.cond + (ComputableTidilyInPolyTime.binary_add.comp + (hn'.prod (ComputableTidilyInPolyTime.const 1))) hn') + exact (const (encOut := encList encGate) [(Op.const false, []), (Op.const true, [])]).prod + (mapWithInput (range.comp hw) hi) + +end PopulationCount + +/-- A native polynomial-time compiler for the whole linear-size threshold circuit. The number +of inputs and the cutoff are supplied in unary; all output circuit references are binary. -/ +theorem computableInPolyTime_linearThresholdCircuit : + ComputableInPolyTime (fun p : ℕ × ℕ => (linearThresholdCircuit p.1 p.2).code) + (encPair encUnary encUnary) encCode := by + have hn := fst encUnary encUnary + have hq := snd encUnary encUnary + have hn' := unary_to_binary.comp hn + have hq' := unary_to_binary.comp hq + have hw : ComputableInPolyTime (fun p : ℕ × ℕ => Nat.clog 2 (p.1 + 1) + 1) + (encPair encUnary encUnary) encUnary := by + have hc := (unary_clog_const 2).comp + (show ComputableInPolyTime (fun p : ℕ × ℕ => p.1 + 1) + (encPair encUnary encUnary) encUnary from by fun_prop) + fun_prop + have hp := computableInPolyTime_compCode hn' (computableInPolyTime_leCircuit.comp hw) + (computableInPolyTime_appendCode hn' + (PopulationCount.numeralCode_polyTime hn' hw hq') + (computableInPolyTime_populationCountCircuit.comp hn)) + have hz : ComputableInPolyTime (fun p : ℕ × ℕ => ([(Op.const false, [])], [p.1])) + (encPair encUnary encUnary) encCode := by fun_prop + have ht : ComputableInPolyTime (fun p : ℕ × ℕ => decide (p.2 ≤ p.1)) + (encPair encUnary encUnary) encBool := by fun_prop + have h := ite_decide ht hp hz + apply h.of_enc_eq + intro p + dsimp only [linearThresholdCircuit, Function.comp_apply] + split_ifs with hq + · rw [← compCode_code, ← appendCode_code, ← PopulationCount.numeralCode_eq] + · rfl + +end Cslib.Circuits.Boolean diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryDigits.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryDigits.lean new file mode 100644 index 0000000000..0c50a8f117 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryDigits.lean @@ -0,0 +1,143 @@ +/- +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.Truncated +public import Complexity.Foundations.Data.Polynomial.BinaryDigits +import Mathlib.Algebra.Order.BigOperators.Group.List +import Mathlib.Data.Fintype.BigOperators +import Mathlib.Data.List.GetD + +/-! +# Recovering natural tensor contractions from binary-digit polynomials + +Replace each natural table entry by the polynomial of its binary digits. Its value at `2` is +the original entry, so evaluating the polynomial contraction at `2` recovers the natural +contraction. The coefficients of a local polynomial sum to at most the bit length of its entry. + +If every entry has at most `B` bits, the polynomial contraction has degree at most +`(B - 1) * G.vertices.length` and coefficient sum at most +`2 ^ G.numEdges * B ^ G.vertices.length`. Thus precision +`G.numEdges + G.vertices.length * B.size + 1` suffices to recover every coefficient exactly. +The bounds apply to arbitrary explicit networks, including empty tables and isolated edges. + +## Main definitions + +* `ExplicitTensorNetwork.binaryEntry`: a table lookup returned as binary-digit coefficients. +* `ExplicitTensorNetwork.binaryDegreeBound`: the total degree bound. +* `ExplicitTensorNetwork.binaryPrecision`: a sufficient coefficient precision. + +## Main results + +* `ExplicitTensorNetwork.eval_two_polyContraction_binaryEntry`: evaluation recovers contraction. +* `ExplicitTensorNetwork.natDegree_polyContraction_binaryEntry_le`: the degree bound. +* `ExplicitTensorNetwork.eval_one_polyContraction_binaryEntry_le`: the coefficient-sum bound. +* `ExplicitTensorNetwork.evalCoeffList_truncContraction_binaryEntry`: exact recovery from the + truncated contraction at the derived degree and precision. +-/ + +public section + +namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork + +open Polynomial DescribedNetwork + +/-- The binary digits of a table entry, least significant first, as natural coefficients. -/ +@[expose] def binaryEntry (table : List ℕ) (bs : List Bool) : List ℕ := + (entry table bs).bits.map Bool.toNat + +/-- The coefficient list has exactly the bit length of the entry. -/ +@[simp] theorem length_binaryEntry (table : List ℕ) (bs : List Bool) : + (binaryEntry table bs).length = (entry table bs).size := by + simp only [binaryEntry, List.length_map, Nat.size_eq_bits_len] + +/-- The degree bound when every table entry has at most `B` bits. -/ +@[expose] def binaryDegreeBound (B : ℕ) (G : ExplicitTensorNetwork ℕ) : ℕ := + (B - 1) * G.vertices.length + +/-- A coefficient precision for a network whose entries have at most `B` bits. -/ +@[expose] def binaryPrecision (B : ℕ) (G : ExplicitTensorNetwork ℕ) : ℕ := + G.numEdges + G.vertices.length * B.size + 1 + +/-- A table entry satisfies any bit-length bound satisfied by every stored table value. -/ +theorem size_entry_le {table : List ℕ} {B : ℕ} + (h : ∀ n ∈ table, n.size ≤ B) (bs : List Bool) : (entry table bs).size ≤ B := by + unfold entry + rcases lt_or_ge (BitString.lsbValue bs) table.length with hi | hi + · rw [List.getD_eq_getElem _ _ hi] + exact h _ (List.getElem_mem hi) + · rw [List.getD_eq_default _ _ hi, Nat.size_zero] + exact Nat.zero_le _ + +/-- Evaluation of the binary-digit contraction at `2` is the original natural contraction. -/ +@[simp] theorem eval_two_polyContraction_binaryEntry (G : ExplicitTensorNetwork ℕ) : + (G.polyContraction binaryEntry).eval 2 = G.contraction := by + change (evalRingHom 2) (DescribedNetwork.contraction + (fun t bs => binaryDigits (entry t bs)) G) = _ + rw [map_contraction] + simpa only [coe_evalRingHom, eval_two_binaryDigits] using contraction_entry G + +variable {G : ExplicitTensorNetwork ℕ} {B : ℕ} + +/-- The degree of the binary-digit contraction is bounded by the entry bit lengths. -/ +theorem natDegree_polyContraction_binaryEntry_le + (h : ∀ v ∈ G.vertices, ∀ n ∈ v.2, n.size ≤ B) : + (G.polyContraction binaryEntry).natDegree ≤ G.binaryDegreeBound B := by + refine natDegree_sum_le_of_forall_le _ _ fun x _ => (natDegree_list_prod_le _).trans ?_ + rw [List.map_map] + refine (List.sum_le_length_nsmul _ (B - 1) fun d hd => ?_).trans ?_ + · obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hd + exact (natDegree_binaryDigits_le _).trans + (Nat.sub_le_sub_right (size_entry_le (h v hv) _) 1) + · simp only [List.length_map, nsmul_eq_mul, Nat.cast_id, binaryDegreeBound, mul_comm, le_refl] + +/-- The coefficient sum is at most the number of edge assignments times `B` per vertex. -/ +theorem eval_one_polyContraction_binaryEntry_le + (h : ∀ v ∈ G.vertices, ∀ n ∈ v.2, n.size ≤ B) : + (G.polyContraction binaryEntry).eval 1 ≤ 2 ^ G.numEdges * B ^ G.vertices.length := by + change (evalRingHom 1) (DescribedNetwork.contraction + (fun t bs => binaryDigits (entry t bs)) G) ≤ _ + rw [map_contraction, DescribedNetwork.contraction] + refine (Finset.sum_le_card_nsmul _ _ (B ^ G.vertices.length) fun x _ => ?_).trans_eq ?_ + · refine (List.prod_le_pow_length _ B fun n hn => ?_).trans_eq ?_ + · obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hn + exact (eval_one_binaryDigits_le _).trans (size_entry_le (h v hv) _) + · simp only [List.length_map] + · simp only [Finset.card_univ, Fintype.card_fun, Fintype.card_bool, Fintype.card_fin, + nsmul_eq_mul, Nat.cast_id] + +/-- The derived precision strictly exceeds every coefficient of the polynomial contraction. -/ +theorem eval_one_polyContraction_binaryEntry_lt_two_pow + (h : ∀ v ∈ G.vertices, ∀ n ∈ v.2, n.size ≤ B) : + (G.polyContraction binaryEntry).eval 1 < 2 ^ G.binaryPrecision B := by + calc (G.polyContraction binaryEntry).eval 1 + ≤ 2 ^ G.numEdges * B ^ G.vertices.length := eval_one_polyContraction_binaryEntry_le h + _ ≤ 2 ^ G.numEdges * (2 ^ B.size) ^ G.vertices.length := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (Nat.lt_size_self B).le _) + _ = 2 ^ (G.numEdges + G.vertices.length * B.size) := by + rw [← pow_mul, mul_comm B.size, ← pow_add] + _ < 2 ^ G.binaryPrecision B := Nat.pow_lt_pow_right (by decide) (Nat.lt_succ_self _) + +/-- The truncated contraction at the derived parameters recovers the whole polynomial. -/ +theorem ofCoeffList_truncContraction_binaryEntry + (h : ∀ v ∈ G.vertices, ∀ n ∈ v.2, n.size ≤ B) : + ofCoeffList (G.truncContraction binaryEntry (G.binaryPrecision B) + (G.binaryDegreeBound B + 1)) = G.polyContraction binaryEntry := + ofCoeffList_coeffsBelow_mod + (Nat.lt_succ_of_le (natDegree_polyContraction_binaryEntry_le h)) + (eval_one_polyContraction_binaryEntry_lt_two_pow h) + +/-- Exact natural contraction by Horner evaluation of the recovered coefficient residues. -/ +theorem evalCoeffList_truncContraction_binaryEntry + (h : ∀ v ∈ G.vertices, ∀ n ∈ v.2, n.size ≤ B) : + evalCoeffList 2 (G.truncContraction binaryEntry (G.binaryPrecision B) + (G.binaryDegreeBound B + 1)) = G.contraction := by + rw [← eval_ofCoeffList, ofCoeffList_truncContraction_binaryEntry h, + eval_two_polyContraction_binaryEntry] + +end Cslib.Circuits.Frontier.ExplicitTensorNetwork diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryDigitsPolyTime.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryDigitsPolyTime.lean new file mode 100644 index 0000000000..f544948e30 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryDigitsPolyTime.lean @@ -0,0 +1,105 @@ +/- +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.BinaryDigits +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedContraction +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Polynomial +import Mathlib.Algebra.Order.BigOperators.Group.List + +/-! +# Computing binary-digit tensor entries in polynomial time + +The evaluator reads a natural table entry at the little-endian index of its port word and +returns its binary digits as natural coefficients. The table values and the index are in binary; +out-of-range indices return zero. Together with polynomial-time Horner evaluation at `2`, this +supplies the input and output maps for exact natural counting through truncated contraction. + +## Main results + +* `ExplicitTensorNetwork.binaryEntry_polyTime`: the uniform evaluator certificate. +* `ExplicitTensorNetwork.binaryTableBound`: a positive bit-length bound read from all tables. +* `ExplicitTensorNetwork.size_le_binaryTableBound`: every stored entry satisfies that bound. +* `ExplicitTensorNetwork.binaryTableBound_polyTime`: computing the bound in unary. +* `ExplicitTensorNetwork.binaryTableBound_le`: bounding it from table and entry lengths. +-/ + +public section + +namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork + +open Turing.MultiTapeTM + +/-- The binary-digit evaluator for an arbitrary natural table is polynomial-time. -/ +theorem binaryEntry_polyTime : + ComputableInPolyTime (fun q : List ℕ × List Bool => binaryEntry q.1 q.2) + (encPair (encList encNat) encBits) (encList encNat) := by + have hindex := ComputableInPolyTime.bitsBinaryValue.comp + (ComputableInPolyTime.bitsReverse.comp (ComputableInPolyTime.snd (encList encNat) encBits)) + have hentry := ComputableInPolyTime.listGetD_binary (encA := encNat) + (encL := encList encNat) 0 (ComputableInPolyTime.fst (encList encNat) encBits) hindex + simpa only [binaryEntry, entry, BitString.lsbValue_eq_binaryValue_reverse, + Function.comp_def] using + ComputableInPolyTime.binary_digitCoeffs.comp hentry + +/-- One more than the encoded length of all table entries. It is a positive uniform bound on +every entry's bit length, and can be computed from the explicit input. -/ +@[expose] def binaryTableBound (G : ExplicitTensorNetwork ℕ) : ℕ := + (encList encNat (G.vertices.flatMap Prod.snd)).length + 1 + +/-- The automatic table bound is positive, including when there are no entries. -/ +theorem binaryTableBound_pos (G : ExplicitTensorNetwork ℕ) : 0 < G.binaryTableBound := + Nat.zero_lt_succ _ + +/-- Every stored natural has bit length at most the automatic bound. -/ +theorem size_le_binaryTableBound (G : ExplicitTensorNetwork ℕ) + {v : List ℕ × List ℕ} (hv : v ∈ G.vertices) {n : ℕ} (hn : n ∈ v.2) : + n.size ≤ G.binaryTableBound := by + have h := length_le_encList_of_mem encNat (List.mem_flatMap.mpr ⟨v, hv, hn⟩) + simp only [encNat_apply, Nat.size_eq_bits_len] at h + exact h.trans (Nat.le_succ _) + +/-- A table-length bound and an entry-bit bound control the entire encoded table list. -/ +theorem binaryTableBound_le {G : ExplicitTensorNetwork ℕ} {M W : ℕ} + (hM : ∀ v ∈ G.vertices, v.2.length ≤ M) + (hW : ∀ v ∈ G.vertices, ∀ n ∈ v.2, n.size ≤ W) : + G.binaryTableBound ≤ G.vertices.length * M * (2 * W + 1) + 1 := by + have hlen : (G.vertices.flatMap Prod.snd).length ≤ G.vertices.length * M := by + rw [List.length_flatMap] + refine (List.sum_le_length_nsmul _ M fun n hn => ?_).trans_eq ?_ + · obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hn + exact hM v hv + · simp only [List.length_map, nsmul_eq_mul, Nat.cast_id] + have henc := length_encList_le encNat (G.vertices.flatMap Prod.snd) (b := W) (by + intro n hn + obtain ⟨v, hv, hn⟩ := List.mem_flatMap.mp hn + simpa only [encNat_apply, Nat.size_eq_bits_len] using hW v hv n hn) + exact Nat.add_le_add_right + (henc.trans (Nat.mul_le_mul_right _ hlen)) 1 + +/-- The automatic bound is computable in unary in polynomial time. -/ +theorem binaryTableBound_polyTime : + ComputableInPolyTime binaryTableBound (DescribedNetwork.encDescribed (encList encNat)) + encUnary := by + have htables : ComputableInPolyTime + (fun G : ExplicitTensorNetwork ℕ => G.vertices.flatMap Prod.snd) + (DescribedNetwork.encDescribed (encList encNat)) (encList encNat) := + ComputableInPolyTime.flatMapWithInput DescribedNetwork.vertices_polyTime + ((ComputableInPolyTime.snd (encList encNat) (encList encNat)).comp + (ComputableInPolyTime.snd _ _)) + exact ComputableInPolyTime.unary_succ.comp + ((ComputableInPolyTime.encoding_length (encList encNat)).comp htables) + +/-- Using the automatic bit bound makes the exact recovery statement unconditional. -/ +theorem evalCoeffList_truncContraction_binaryTableBound (G : ExplicitTensorNetwork ℕ) : + Polynomial.evalCoeffList 2 + (G.truncContraction binaryEntry (G.binaryPrecision G.binaryTableBound) + (G.binaryDegreeBound G.binaryTableBound + 1)) = G.contraction := + evalCoeffList_truncContraction_binaryEntry fun _ hv _ hn => G.size_le_binaryTableBound hv hn + +end Cslib.Circuits.Frontier.ExplicitTensorNetwork diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryWeightBounds.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryWeightBounds.lean new file mode 100644 index 0000000000..dae1653ff2 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryWeightBounds.lean @@ -0,0 +1,69 @@ +/- +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.BinarySize +public import Mathlib.Data.Nat.Log +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Precision and truncation bounds for binary weights + +If the number of vertices is linear in `s` and local weights have a fixed polynomial number of +bits, binary-digit contraction needs precision `O(s log s)` and a fixed polynomial truncation +order. These elementary estimates keep the constants before the instance parameters. + +## Main results + +* `binaryWeight_size_le`: a polynomially bounded bit-length has logarithmic binary length. +* `binaryWeight_parameters_le`: the precision and truncation fit the strengthened contraction + interface with explicit constants. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +/-- The binary length of a number bounded by `D (s + 1)^d` is at most a fixed multiple of +`⌊log₂ s⌋ + 1`. -/ +theorem binaryWeight_size_le {B D d s : ℕ} (hB : B ≤ D * (s + 1) ^ d) : + B.size ≤ (D.size + 2 * d + 1) * (Nat.log 2 s + 1) := by + have hs : s + 1 ≤ 2 ^ (Nat.log 2 s + 1) := Nat.lt_pow_succ_log_self one_lt_two s + have hsize : (s + 1).size ≤ Nat.log 2 s + 2 := by + simpa only [Nat.size_pow, Nat.add_assoc] using Nat.size_le_size hs + have h1 := Nat.size_mul_le D ((s + 1) ^ d) + have h2 := Nat.size_pow_le (s + 1) d + have h3 := Nat.size_le_size hB + have h4 := Nat.mul_le_mul_left d hsize + nlinarith + +/-- For at most `C s` vertices, a polynomial bound on local bit-length gives logarithmic-linear +precision and polynomial truncation. The displayed constant depends only on `C`, `D` and `d`. -/ +theorem binaryWeight_parameters_le {B C D d s v : ℕ} (hs : 1 ≤ s) + (hv : v ≤ C * s) (hB : B ≤ D * (s + 1) ^ d) : + s + v * B.size + 1 ≤ (C * (D.size + 2 * d + 1) + 2) * s * (Nat.log 2 s + 1) ∧ + (B - 1) * v + 1 ≤ (C * D + 1) * (s + 1) ^ (d + 1) := by + constructor + · set E := D.size + 2 * d + 1 + set L := Nat.log 2 s + 1 + have hL : 1 ≤ L := by omega + have h1 : v * B.size ≤ C * s * (E * L) := + Nat.mul_le_mul hv (binaryWeight_size_le hB) + have h2 : s ≤ s * L := Nat.le_mul_of_pos_right _ hL + have h3 : 1 ≤ s * L := hs.trans h2 + calc s + v * B.size + 1 ≤ s * L + C * s * (E * L) + s * L := by omega + _ = (C * E + 2) * s * L := by ring + · have h1 : (B - 1) * v ≤ C * D * (s + 1) ^ (d + 1) := by + calc (B - 1) * v ≤ (D * (s + 1) ^ d) * (C * (s + 1)) := + Nat.mul_le_mul (by omega) (hv.trans (Nat.mul_le_mul_left C (Nat.le_succ s))) + _ = C * D * (s + 1) ^ (d + 1) := by rw [pow_succ]; ring + have h2 : 1 ≤ (s + 1) ^ (d + 1) := Nat.one_le_pow _ _ (by omega) + nlinarith + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryWeights.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryWeights.lean new file mode 100644 index 0000000000..36c943a3f9 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/BinaryWeights.lean @@ -0,0 +1,126 @@ +/- +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.BinaryDigitsPolyTime +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryWeightBounds +public import Complexity.Computability.Complexity.Classes.CountingSimulation.TruncatedBoundedDegree +public import Complexity.Computability.GenericRAM.Native.PolynomialOverhead + +/-! +# Exact native contraction of large natural weights + +The binary-digit reduction, polynomial truncation, and logarithmic-linear precision give a +native exact contraction of natural-number tensor tables. The certificate is correct on every +network. On rows-first networks of fixed degree, with linearly many vertices and a polynomial +total table bit length, it uses time `poly(L) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial space, where +`s` is the number of edges and `L` the complete encoded input length. + +The input bound is on the binary lengths of weights; the weights' numeric magnitudes may be +exponential in a fixed power of `s`. All constants and polynomial exponents are selected before +the network. Polynomial preprocessing and Horner decoding preserve the exponential rate because +the contraction's space bound charges its encoded output. + +## Main definitions + +* `BinaryWeights.input`: derived precision, truncation order, and original table network. +* `BinaryWeights.certificate`: the native exact natural contraction. + +## Main results + +* `BinaryWeights.certificate_bound`: the subexponential time and polynomial space bound. +-/ + +public section + +namespace Cslib.Circuits.Frontier.BinaryWeights + +open Cslib.GenericRAM Turing.MultiTapeTM Cslib.Complexity.CountingSimulation +open ExplicitTensorNetwork + +/-- The native input encoding: a binary row count and the existing explicit natural network. -/ +abbrev encInput : ℕ × ExplicitTensorNetwork ℕ ↪ List Bool := + encPair encNat ExplicitTensorNetwork.encExplicitNetwork + +/-- Derived precision and truncation order from the table bit bound. -/ +@[expose] def input (x : ℕ × ExplicitTensorNetwork ℕ) : + ℕ × ℕ × ℕ × DescribedNetwork (List ℕ) := + (x.2.binaryPrecision x.2.binaryTableBound, x.2.binaryDegreeBound x.2.binaryTableBound + 1, + x.1, x.2) + +theorem input_polyTime : + ComputableInPolyTime input encInput (encTrunc (encList encNat)) := by + have hG : ComputableInPolyTime (fun x : ℕ × ExplicitTensorNetwork ℕ => x.2) + encInput (DescribedNetwork.encDescribed (encList encNat)) := ComputableInPolyTime.snd _ _ + have hB := binaryTableBound_polyTime.comp hG + have hM := DescribedNetwork.numEdges_polyTime.comp hG + have hV := (ComputableInPolyTime.length_list _).comp + (DescribedNetwork.vertices_polyTime.comp hG) + have hS := ComputableInPolyTime.binary_size.comp (ComputableInPolyTime.unary_to_binary.comp hB) + have hP := ComputableInPolyTime.unary_add' + (ComputableInPolyTime.unary_add' hM (ComputableInPolyTime.unary_mul' hV hS)) + (ComputableInPolyTime.const 1) + have hL := ComputableInPolyTime.unary_add' + (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.unary_sub' hB (ComputableInPolyTime.const 1)) hV) + (ComputableInPolyTime.const 1) + exact hP.prod (hL.prod ComputableInPolyTime.id') + +/-- The fixed polynomial-time binary-digit entry evaluator. -/ +@[expose] noncomputable def entryCertificate : + NativeCertificate (fun q : List ℕ × List Bool => binaryEntry q.1 q.2) + (encPair (encList encNat) encBits) (encList encNat) := + NativeCertificate.ofPolyTime binaryEntry_polyTime + +/-- Exact natural contraction. The degree parameter selects a finite-size fallback only; +correctness holds for every input network and every row count. -/ +@[expose] noncomputable def certificate (k : ℕ) : + NativeCertificate (fun x : ℕ × ExplicitTensorNetwork ℕ => x.2.contraction) encInput encNat := + ((NativeCertificate.ofPolyTime ComputableInPolyTime.evalCoeffList_two).comp + ((truncBoundedDegreeCertificate entryCertificate k).comp + (NativeCertificate.ofPolyTime input_polyTime))).congr fun x => + evalCoeffList_truncContraction_binaryTableBound x.2 + +/-- Native exact contraction of fixed-degree natural networks with a polynomial number of +weight bits. The encoded output is included in the polynomial space bound. -/ +theorem certificate_bound (k C D d : ℕ) : + ∃ K : ℕ, ∀ (r : ℕ) (G : ExplicitTensorNetwork ℕ), 2 ≤ G.numEdges → + G.IsRowsFirstBipartite r → G.vertices.length ≤ C * G.numEdges → G.maxDegree ≤ k → + G.binaryTableBound ≤ D * (G.numEdges + 1) ^ d → + (certificate k).time (r, G) ≤ K * ((encInput (r, G)).length + 1) ^ K * + 2 ^ (K * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges)) ∧ + (certificate k).space (r, G) ≤ K * ((encInput (r, G)).length + 1) ^ K := by + let D' := C * (D.size + 2 * d + 1) + 2 + (C * D + 1) + obtain ⟨K₀, hK₀⟩ := truncBoundedDegreeCertificate_bound entryCertificate + (NativeCertificate.ofPolyTime_isPolynomial binaryEntry_polyTime) k C D' (d + 1) + obtain ⟨K₁, hK₁⟩ := NativeCertificate.exists_polynomial_overhead + (truncBoundedDegreeCertificate entryCertificate k) input_polyTime + ComputableInPolyTime.evalCoeffList_two K₀ + refine ⟨K₀ + K₁, fun r G hs hrows hv hk hB => ?_⟩ + obtain ⟨hP, hℓ⟩ := binaryWeight_parameters_le (by omega : 1 ≤ G.numEdges) hv hB + have hPD : G.binaryPrecision G.binaryTableBound ≤ + D' * G.numEdges * (Nat.log 2 G.numEdges + 1) := + hP.trans (Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by dsimp only [D']; omega))) + have hℓD : G.binaryDegreeBound G.binaryTableBound + 1 ≤ D' * (G.numEdges + 1) ^ (d + 1) := + hℓ.trans (Nat.mul_le_mul_right _ (by dsimp only [D']; omega)) + obtain ⟨ht, hs'⟩ := hK₀ (G.binaryPrecision G.binaryTableBound) + (G.binaryDegreeBound G.binaryTableBound + 1) r G hs hrows hv + (G.maxDegree_le_iff.mp hk) (by simp only [binaryPrecision]; omega) hPD + (by omega) hℓD + obtain ⟨hT, hS⟩ := hK₁ (r, G) + (2 ^ (K₀ * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges))) Nat.one_le_two_pow ht hs' + have hpoly : K₁ * ((encInput (r, G)).length + 1) ^ K₁ ≤ + (K₀ + K₁) * ((encInput (r, G)).length + 1) ^ (K₀ + K₁) := + Nat.mul_le_mul (by omega) (Nat.pow_le_pow_right (by omega) (by omega)) + have hpow : 2 ^ (K₀ * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges)) ≤ + 2 ^ ((K₀ + K₁) * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges)) := + Nat.pow_le_pow_right (by norm_num) + (Nat.div_le_div_right (Nat.mul_le_mul_right _ (by omega))) + exact ⟨hT.trans (Nat.mul_le_mul hpoly hpow), hS.trans hpoly⟩ + +end Cslib.Circuits.Frontier.BinaryWeights diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/CSPBinaryWeights.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CSPBinaryWeights.lean new file mode 100644 index 0000000000..c8aa5e6f70 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CSPBinaryWeights.lean @@ -0,0 +1,120 @@ +/- +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.CSPNetwork +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryDigitsPolyTime +import Complexity.Logics.Propositional.CSP.Vars +import Mathlib.Data.List.GetD + +/-! +# From local weight bit lengths to the constraint network's bit budget + +A constraint network has one row tensor per constraint and equality tensors with entries zero or +one. Its arity bound controls every table length. Consequently a polynomial bound on the binary +length of each local weight gives a polynomial bound on the total encoded table length, with +explicit constants. This supplies the bit-budget premise of native binary-weight counting. + +## Main results + +* `ClauseNetwork.size_entry_cspNetwork_le`: local weight bit bounds hold throughout the network. +* `ClauseNetwork.binaryTableBound_cspNetwork_le`: local polynomial bit bounds imply the explicit + total table-bit budget. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Turing.MultiTapeTM Cslib.Logic.PL + +namespace ClauseNetwork + +variable {Rel : Type*} {k W : ℕ} {wt : Rel → List Bool → ℕ} {F : CSP Rel ℕ} + +private theorem size_getD_le {l : List ℕ} {W : ℕ} + (h : ∀ n ∈ l, n.size ≤ W) (i : ℕ) : (l.getD i 0).size ≤ W := by + rcases lt_or_ge i l.length with hi | hi + · rw [List.getD_eq_getElem _ _ hi] + exact h _ (List.getElem_mem hi) + · rw [List.getD_eq_default _ _ hi, Nat.size_zero] + exact Nat.zero_le _ + +/-- Every table entry of the constraint network satisfies the local weight-bit bound, provided +that the bound also covers the equality tensors' unit entries. -/ +theorem size_entry_cspNetwork_le (hW : 1 ≤ W) + (hk : ∀ c ∈ F, c.2.length ≤ k) + (hwt : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → (wt c.1 w).size ≤ W) : + ∀ v ∈ (cspNetwork k wt F).2.vertices, ∀ n ∈ v.2, n.size ≤ W := by + have hrow (i j : ℕ) : (cspRow k wt F i j).size ≤ W := by + apply size_getD_le + intro n hn + obtain ⟨c, hc, rfl⟩ := List.mem_map.mp hn + rw [cspEntry, List.getD_eq_getElem _ _ (by + simpa only [List.length_map, List.length_range] using Nat.lt_succ_of_le (hk c hc)), + List.getElem_map, List.getElem_range] + exact hwt c hc _ (BitString.length_lowBits ..) + intro v hv n hn + simp only [cspNetwork, vertices_network, List.mem_append, List.mem_map, List.mem_range] at hv + rcases hv with (⟨i, _, rfl⟩ | ⟨u, _, rfl⟩) | ⟨w, _, rfl⟩ + · obtain ⟨j, _, rfl⟩ := List.mem_map.mp hn + exact hrow i j + · have hn' : n = 1 ∨ n = 0 := by + have h : n = 1 ∨ n = 0 ∨ n = 1 := by simpa [linkVertex] using hn + tauto + rcases hn' with rfl | rfl + · simpa only [Nat.size_one] using hW + · exact Nat.zero_le _ + · obtain ⟨j, _, rfl⟩ := List.mem_map.mp hn + unfold eqEntry + split + · simpa only [Nat.size_one] using hW + · exact Nat.zero_le _ + +/-- If each local weight has at most `(N + 1) ^ d` bits, the automatic network table-bit bound is +at most `(3 * 2 ^ (max k 4) + 1) * (s + 1) ^ (d + 1)`, where `s = 3 N` is its edge count. -/ +theorem binaryTableBound_cspNetwork_le (d : ℕ) + (hk : ∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) + (hwt : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → + (wt c.1 w).size ≤ (F.vars.length + 1) ^ d) : + (cspNetwork k wt F).2.binaryTableBound ≤ + (3 * 2 ^ (max k 4) + 1) * ((cspNetwork k wt F).2.numEdges + 1) ^ (d + 1) := by + let G := (cspNetwork k wt F).2 + let s := G.numEdges + let M := 2 ^ (max k 4) + have hfull := length_table_cspNetwork (wt := wt) (fun c hc => (hk c hc).2) + have hdeg := maxDegree_cspNetwork_le (wt := wt) (fun c hc => (hk c hc).2) + have hsize := size_entry_cspNetwork_le (wt := wt) + (Nat.one_le_pow _ _ (by omega : 1 ≤ F.vars.length + 1)) (fun c hc => (hk c hc).2) hwt + have htable := ExplicitTensorNetwork.binaryTableBound_le (G := G) (M := M) + (W := (F.vars.length + 1) ^ d) (fun v hv => by + rw [hfull v hv] + exact Nat.pow_le_pow_right (by decide) ((G.length_le_maxDegree hv).trans hdeg)) hsize + have hv : G.vertices.length ≤ s := by + dsimp only [G, s] + rw [length_vertices_cspNetwork, numEdges_cspNetwork] + have hm := CSP.length_le_length_vars fun c hc => (hk c hc).1 + omega + have hN : F.vars.length + 1 ≤ s + 1 := by + dsimp only [s, G] + rw [numEdges_cspNetwork] + omega + have hpow : (F.vars.length + 1) ^ d ≤ (s + 1) ^ d := Nat.pow_le_pow_left hN d + have hpos : 1 ≤ (s + 1) ^ d := Nat.one_le_pow _ _ (by omega) + have hpos' : 1 ≤ (s + 1) ^ (d + 1) := Nat.one_le_pow _ _ (by omega) + calc G.binaryTableBound ≤ G.vertices.length * M * (2 * (F.vars.length + 1) ^ d + 1) + 1 := + htable + _ ≤ s * M * (3 * (s + 1) ^ d) + 1 := Nat.add_le_add_right + (Nat.mul_le_mul (Nat.mul_le_mul_right _ hv) (by omega)) 1 + _ ≤ (s + 1) * M * (3 * (s + 1) ^ d) + 1 := Nat.add_le_add_right + (Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (Nat.le_succ s))) 1 + _ = 3 * M * (s + 1) ^ (d + 1) + 1 := by rw [pow_succ]; ring + _ ≤ (3 * M + 1) * (s + 1) ^ (d + 1) := by nlinarith + +end ClauseNetwork +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContraction.lean index eeeb0d4397..4b50873e8b 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContraction.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContraction.lean @@ -51,8 +51,9 @@ encoded length bounds it. `(P, fl, r, G)`, with the precision `P` in unary and the coefficients `fl` of a modulus, to a coefficient list whose value in `A_P` at the modulus `FastContraction.modulus m`, for `m` the number of edges, is the contraction of the entries' values, on every input. On admissible inputs -at that modulus it runs in time `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space `K (n + 1) ^ K`, -the bound shape of `Cslib.Complexity.CountingSimulation.ContractionAlgorithm`. The input format is +at that modulus and precision `P ≤ D s (⌊log₂ s⌋ + 1)`, for each fixed `D`, it runs in time +`K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space `K (n + 1) ^ K`. The original linear-precision +bound is a corollary (`EntryContractionAlgorithm.bound`). The input format is that of the truncation-order reduction's contractions of specialized networks. **Direct summation** (`EntryContraction.directCore`). Summing over all edge assignments with the @@ -217,12 +218,13 @@ for `L` the encoded length of `G`. -/ ce.space (v.2, bs) ≤ C * (s + (DescribedNetwork.encDescribed encT G.2).length + 1) ^ C variable (encT entry) in -/-- **The contraction interface for entry procedures** (Theorem D.1 with `ℓ = 1`, entries in -`A_P`). A native certificate maps `(P, fl, r, G)` to a coefficient list `core (P, fl, r, G)`. At +/-- **The contraction interface for entry procedures**, extending Theorem D.1 at `ℓ = 1` to +logarithmic root precision, with entries in `A_P`. A native certificate maps `(P, fl, r, G)` to a +coefficient list `core (P, fl, r, G)`. At the modulus `fl = FastContraction.modulus m`, for `m` the number of edges of `G`, its value in `A_P` is the contraction of the values of the entries, on every input. For all constants `C` and `D` there is `K` such that on every network admissible at a size `s ≥ 2` with constant `C`, and -every precision `1 ≤ P ≤ D s`, at that modulus the certificate takes time at most +every precision `1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)`, at that modulus the certificate takes time at most `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space at most `K (n + 1) ^ K`, for `n` the encoded length of the input. -/ structure EntryContractionAlgorithm (Admissible : ℕ → ℕ → ℕ × DescribedNetwork T → Prop) where @@ -235,9 +237,10 @@ structure EntryContractionAlgorithm (Admissible : ℕ → ℕ → ℕ × Describ val P (FastContraction.modulus G.numEdges) (core (P, FastContraction.modulus G.numEdges, r, G)) = G.contraction fun t bs => val P (FastContraction.modulus G.numEdges) (entry t bs) - /-- Its time and space on admissible inputs at the modulus of the number of edges. -/ - bound : ∀ C D : ℕ, ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → - P ≤ D * s → Admissible C s (r, G) → + /-- The same machine and rate allow a logarithmic factor in the root precision. This extension + of Theorem D.1 follows from the inherited-precision estimate (D.11). -/ + bound_logPrecision : ∀ C D : ℕ, ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → + 1 ≤ P → P ≤ D * s * (Nat.log 2 s + 1) → Admissible C s (r, G) → certificate.time (P, FastContraction.modulus G.numEdges, r, G) ≤ K * ((EntryContraction.encInput encT (P, FastContraction.modulus G.numEdges, r, G)).length + 1) ^ K * @@ -246,6 +249,23 @@ structure EntryContractionAlgorithm (Admissible : ℕ → ℕ → ℕ × Describ K * ((EntryContraction.encInput encT (P, FastContraction.modulus G.numEdges, r, G)).length + 1) ^ K +/-- The original linear-precision bound follows from the logarithmic-precision bound of the +same algorithm. -/ +theorem EntryContractionAlgorithm.bound {Admissible : ℕ → ℕ → ℕ × DescribedNetwork T → Prop} + (alg : EntryContractionAlgorithm encT entry Admissible) (C D : ℕ) : + ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s → + Admissible C s (r, G) → + alg.certificate.time (P, FastContraction.modulus G.numEdges, r, G) ≤ + K * ((EntryContraction.encInput encT + (P, FastContraction.modulus G.numEdges, r, G)).length + 1) ^ K * + 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ + alg.certificate.space (P, FastContraction.modulus G.numEdges, r, G) ≤ + K * ((EntryContraction.encInput encT + (P, FastContraction.modulus G.numEdges, r, G)).length + 1) ^ K := by + obtain ⟨K, hK⟩ := alg.bound_logPrecision C D + exact ⟨K, fun s P r G hs hP hPD hA => hK s P r G hs hP + (hPD.trans (Nat.le_mul_of_pos_right _ (Nat.succ_pos _))) hA⟩ + /-! ### Direct summation -/ namespace EntryContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContractionMachine.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContractionMachine.lean index c23f6e016b..cc71fa26b6 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContractionMachine.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/EntryContractionMachine.lean @@ -14,6 +14,8 @@ import Complexity.Computability.Circuit.Frontier.TensorNetwork.RowsFirstPolyTime 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.Nat.Div +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Log import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.SqrtLog import Complexity.Computability.Machines.Turing.MultiTape.Internal.EqualityPrimitives import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPrimitives @@ -44,7 +46,9 @@ passes it (`EntryContraction.guard_of_entryAdmissible`), since **The root and the clock.** The root call (`EntryContraction.rootCall`) has scale `m`, depth `0`, edge bound `m + 1`, precision `P`, modulus `fl`, `r` rows and the network `FastContraction.rootNet G`. The clock (`EntryContraction.clock`) is -`2 ^ (K₀ (m + P) / √⌊log₂ (m + P)⌋)`, as for explicit networks. On guarded inputs the root is +`2 ^ (K₀ (m + q) / √⌊log₂ (m + q)⌋)`, where `q = P / (⌊log₂ m⌋ + 1)`. +The logarithmic normalization uses the schedule's larger precision allowance without charging +an extra logarithm in the exponential clock. On guarded inputs the root is well-formed (`EntryContraction.wf_rootCall`), and it has no packets (`EntryContraction.packetShape_rootCall`). @@ -175,9 +179,16 @@ precision `P`, modulus `fl`, `r` rows and the descriptors of `G`. -/ @[expose] def rootCall : ℕ × List ℕ × ℕ × DescribedNetwork T → Call T | (P, fl, r, G) => ((G.numEdges, 0, G.numEdges + 1, P, fl, r), rootNet G) -/-- **The clock** of an input `(P, fl, r, G)`: `2 ^ (K₀ (m + P) / √⌊log₂ (m + P)⌋)`. -/ +/-- The precision charged to the clock: the schedule absorbs `⌊log₂ m⌋ + 1` precision bits +for each unit of its root bound. -/ +@[expose] def normalizedPrecision (m P : ℕ) : ℕ := P / (Nat.log 2 m + 1) + +/-- **The clock** of an input `(P, fl, r, G)`: `2 ^ (K₀ (m + q) / √⌊log₂ (m + q)⌋)`, where +`q = normalizedPrecision m P`. -/ @[expose] def clock : ℕ × List ℕ × ℕ × DescribedNetwork T → ℕ - | (P, _, _, G) => 2 ^ (fuelConst * (G.numEdges + P) / Nat.sqrt (Nat.log 2 (G.numEdges + P))) + | (P, _, _, G) => + 2 ^ (fuelConst * (G.numEdges + normalizedPrecision G.numEdges P) / + Nat.sqrt (Nat.log 2 (G.numEdges + normalizedPrecision G.numEdges P))) /-- **The root call is polynomial-time.** -/ theorem rootCall_polyTime : @@ -196,9 +207,15 @@ theorem rootCall_polyTime : ((FastContraction.rootNet_polyTime encT).comp network_polyTime) /-- **The clock is polynomial-time.** -/ -theorem clock_polyTime : ComputableInPolyTime (clock (T := T)) (encInput encT) encNat := - (ComputableInPolyTime.unary_two_pow_div_sqrt_log_two fuelConst).comp - (ComputableInPolyTime.unary_add' numEdges_polyTime (ComputableInPolyTime.fst _ _)) +theorem clock_polyTime : ComputableInPolyTime (clock (T := T)) (encInput encT) encNat := by + have hm := numEdges_polyTime (encT := encT) + have hlog := ComputableInPolyTime.binary_log_two.comp + (ComputableInPolyTime.unary_to_binary.comp hm) + have hq := ComputableInPolyTime.unary_div.comp + ((ComputableInPolyTime.fst _ _).prod + (ComputableInPolyTime.unary_add' hlog (ComputableInPolyTime.const 1))) + exact (ComputableInPolyTime.unary_two_pow_div_sqrt_log_two fuelConst).comp + (ComputableInPolyTime.unary_add' hm hq) /-- **Guarded roots are well-formed**: the modulus is irreducible modulo `2`, and the descriptors of `G` keep its rows-first ports. -/ diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSchedule.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSchedule.lean index 388e67f5eb..77d3cd32f2 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSchedule.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionSchedule.lean @@ -212,17 +212,27 @@ theorem Reach.onSchedule (hB : ∀ d M p, Active C m d M p → ActiveBounds C m /-- **The root is on the schedule**: a well-formed root with `m` edges, at scale `m` with the modulus of `m`, of maximum degree at most `degreeBound m`, with edge bound `m + 1` and precision at -most `C m` for `1 ≤ C`. -/ -theorem onSchedule_root {x₀ : Call T} (hC : 1 ≤ C) (hs : x₀.scale = m) (he : x₀.edges = m) +most `C m (⌊log₂ m⌋ + 1)` for `1 ≤ C`. -/ +theorem onSchedule_root_logPrecision {x₀ : Call T} (hC : 1 ≤ C) + (hs : x₀.scale = m) (he : x₀.edges = m) (hfl : x₀.fl = modulus m) (hwf : WF x₀) (hdeg : ∀ v ∈ x₀.net.vertices, v.1.length ≤ degreeBound m) - (hb : x₀.bound = m + 1) (hd : x₀.depth = 0) (hp : x₀.prec ≤ C * m) : OnSchedule C m x₀ := by + (hb : x₀.bound = m + 1) (hd : x₀.depth = 0) (hp : x₀.prec ≤ C * m * (Nat.log 2 m + 1)) : + OnSchedule C m x₀ := by refine ⟨hs, hfl, hwf, hdeg, by omega, ?_⟩ by_cases hl : x₀.edges ≤ leafSize m · exact Or.inl hl · refine Or.inr ?_ rw [hd, he] - exact Active.root (Nat.le_mul_of_pos_left m hC) (by omega) hp + exact Active.root_logPrecision (Nat.le_mul_of_pos_left m hC) (by omega) hp + +/-- A root of linear precision is on the same schedule. -/ +theorem onSchedule_root {x₀ : Call T} (hC : 1 ≤ C) (hs : x₀.scale = m) (he : x₀.edges = m) + (hfl : x₀.fl = modulus m) (hwf : WF x₀) + (hdeg : ∀ v ∈ x₀.net.vertices, v.1.length ≤ degreeBound m) + (hb : x₀.bound = m + 1) (hd : x₀.depth = 0) (hp : x₀.prec ≤ C * m) : OnSchedule C m x₀ := + onSchedule_root_logPrecision hC hs he hfl hwf hdeg hb hd + (hp.trans (Nat.le_mul_of_pos_right _ (Nat.succ_pos _))) /-! ### The number of children -/ diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ModulusGrowth.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ModulusGrowth.lean new file mode 100644 index 0000000000..42c1f2072c --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ModulusGrowth.lean @@ -0,0 +1,74 @@ +/- +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.FastContractionSchedule + +/-! +# Polynomially many residues in the contraction modulus + +The modulus already used by the contraction machine has degree +`2 * 3 ^ ⌊log₂ (4 ⌊log₂ m⌋ + 2)⌋`. For every fixed `d`, its residue field eventually has at least +`(m + 1) ^ d` elements. The explicit sufficient threshold `2 ^ (2 ^ d)` is intentionally coarse: +it is used only to bound a finite fallback branch by a constant depending on `d`. + +This observation lets the same contraction machine interpolate every fixed polynomial +truncation order. It does not change the modulus or require a larger extension field. + +## Main results + +* `FastContraction.mul_log_add_one_le_length_modulus`: eventually the degree exceeds any fixed + multiple of `⌊log₂ m⌋ + 1`. +* `FastContraction.pow_le_card_modulus`: eventually every fixed power of `m + 1` fits in the + residue field. +-/ + +public section + +namespace Cslib.Circuits.Frontier.FastContraction + +private theorem mul_two_pow_le_two_mul_three_pow (t : ℕ) : + (t + 2) * 2 ^ t ≤ 2 * 3 ^ t := by + induction t with + | zero => norm_num + | succ t ih => + have h : 2 * (t + 3) ≤ 3 * (t + 2) := by omega + calc (t + 1 + 2) * 2 ^ (t + 1) + = (2 * (t + 3)) * 2 ^ t := by rw [pow_succ]; ring + _ ≤ (3 * (t + 2)) * 2 ^ t := Nat.mul_le_mul_right _ h + _ = 3 * ((t + 2) * 2 ^ t) := by ring + _ ≤ 3 * (2 * 3 ^ t) := Nat.mul_le_mul_left _ ih + _ = 2 * 3 ^ (t + 1) := by rw [pow_succ]; ring + +/-- The existing modulus eventually has degree at least any fixed multiple of +`⌊log₂ m⌋ + 1`. The threshold depends only on that multiple. -/ +theorem mul_log_add_one_le_length_modulus {d m : ℕ} (hm : 2 ^ (2 ^ d) ≤ m) : + d * (Nat.log 2 m + 1) ≤ (modulus m).length := by + have hd : 2 ^ d ≤ Nat.log 2 m := Nat.le_log_of_pow_le (by norm_num) hm + have ht : d ≤ modulusExp m := + Nat.le_log_of_pow_le (by norm_num) (show 2 ^ d ≤ 4 * Nat.log 2 m + 2 by omega) + have hlog := Nat.lt_pow_succ_log_self one_lt_two (4 * Nat.log 2 m + 2) + change 4 * Nat.log 2 m + 2 < 2 ^ (modulusExp m + 1) at hlog + rw [pow_succ] at hlog + have hpow : Nat.log 2 m + 1 ≤ 2 ^ modulusExp m := by omega + rw [length_modulus] + exact (Nat.mul_le_mul (by omega : d ≤ modulusExp m + 2) hpow).trans + (mul_two_pow_le_two_mul_three_pow (modulusExp m)) + +/-- For every fixed power, the residue field of the existing modulus eventually contains at +least that many points. -/ +theorem pow_le_card_modulus {d m : ℕ} (hm : 2 ^ (2 ^ d) ≤ m) : + (m + 1) ^ d ≤ 2 ^ (modulus m).length := by + have hm' : m + 1 ≤ 2 ^ (Nat.log 2 m + 1) := + Nat.lt_pow_succ_log_self one_lt_two m + calc (m + 1) ^ d ≤ (2 ^ (Nat.log 2 m + 1)) ^ d := Nat.pow_le_pow_left hm' d + _ = 2 ^ (d * (Nat.log 2 m + 1)) := by rw [← pow_mul, Nat.mul_comm] + _ ≤ 2 ^ (modulus m).length := + Nat.pow_le_pow_right (by norm_num) (mul_log_add_one_le_length_modulus hm) + +end Cslib.Circuits.Frontier.FastContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean index 4bde8f16fc..482a85a314 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean @@ -37,12 +37,13 @@ p. 64). * The node `t` of valuation `i` is recovered from its children at precision `p_t = p + 8 h 2 ^ (a - i) + 8 M (a + 1)` (`nodePrecision`), as in Lemma D.5, where `D_t = L / 2 ^ i`. -* Precisions stay below `precisionBound C s d`, which starts at `C s` and grows by +* Precisions stay below `precisionBound C s d`, which starts at `C s (⌊log₂ s⌋ + 1)` and grows by `8 C s (4 · 2 ^ a_d + a_d + 1)` per level. **Active calls.** `Active C s d M p` says that a call at depth `d` has `M` edges, with `16 ^ d M ≤ C s` and `H < M`, and precision `p ≤ precisionBound C s d`. The root is active when -it has more than `H` edges and edges and precision are at most `C s` (`Active.root`), and an +it has more than `H` edges, at most `C s` edges and precision at most +`C s (⌊log₂ s⌋ + 1)` (`Active.root_logPrecision`), and an active call's children are active when they have more than `H` edges (`Active.child`): Lemma D.3 gives them at most `M / 16` edges. @@ -69,6 +70,10 @@ consumer interface of #1833, where the manuscript has `⌈log₂ s⌉` and `⌈s `u_d ≥ ⌊√λ⌋ / (2 ^ 17 C)` instead of `u_d ≥ λ ^ (4/5)`, which suffices for (D.11). The manuscript's precision bound `p ≤ s λ^{O(1)} 2 ^ (a_{d-1})` is `precisionBound_le`. +The logarithmic factor in the root precision extends the manuscript's `O(s)` allowance. +It changes only the polynomial factor in the inherited-precision estimate (D.11); the taper +absorbs it, so the mode and call-count bounds are unchanged. + ## Main definitions * `leafSize`, `taper`, `prefixBits`, `slotBits`, `degreeBound`, `nodePrecision`, @@ -82,7 +87,7 @@ precision bound `p ≤ s λ^{O(1)} 2 ^ (a_{d-1})` is `precisionBound_le`. * `card_modeBudget_le`, `ActiveBounds.card_modeBudget_le`: the mode count (D.15) at an active call. * `sum_branchExponent_le`: the excess exponents sum to `O(s / √⌊log₂ s⌋)`. -* `precisionBound_le`: the precision bound `p ≤ C s 2 ^ (a_{d-1}) (1 + d (8 λ + 40))`. +* `precisionBound_le`: the precision bound `p ≤ C s 2 ^ (a_{d-1}) (λ + 1 + d (8 λ + 40))`. ## References @@ -120,10 +125,10 @@ slots. -/ is recovered by Lemma D.5, for input precision `p`, `h = ⌈3 M / u⌉` and `2 ^ a` slots. -/ @[expose] def nodePrecision (p h a M i : ℕ) : ℕ := p + 8 * h * 2 ^ (a - i) + 8 * M * (a + 1) -/-- The precision schedule: `C s` at the root, growing by `8 C s (4 · 2 ^ a_d + a_d + 1)` from -depth `d` to `d + 1`. -/ +/-- The precision schedule: `C s (⌊log₂ s⌋ + 1)` at the root, growing by +`8 C s (4 · 2 ^ a_d + a_d + 1)` from depth `d` to `d + 1`. -/ @[expose] def precisionBound (C s : ℕ) : ℕ → ℕ - | 0 => C * s + | 0 => C * s * (Nat.log 2 s + 1) | d + 1 => precisionBound C s d + 8 * C * s * (4 * 2 ^ slotBits s d + slotBits s d + 1) /-- The excess exponent `e_d = 4 ⌊15360 C s / (⌊√⌊log₂ s⌋⌋ 8 ^ d)⌋ + 4` of the mode count at @@ -145,6 +150,12 @@ structure Active (C s d M p : ℕ) : Prop where /-- The root is active when it is not a leaf and its edges and precision are at most `C s`. -/ theorem Active.root {C s M p : ℕ} (hM : M ≤ C * s) (hH : leafSize s < M) (hp : p ≤ C * s) : Active C s 0 M p := + ⟨by simpa using hM, hH, hp.trans (Nat.le_mul_of_pos_right _ (Nat.succ_pos _))⟩ + +/-- The same schedule allows root precision `C s (⌊log₂ s⌋ + 1)`, with unchanged branching +bounds. The additional logarithm is absorbed by the taper in (D.11). -/ +theorem Active.root_logPrecision {C s M p : ℕ} (hM : M ≤ C * s) (hH : leafSize s < M) + (hp : p ≤ C * s * (Nat.log 2 s + 1)) : Active C s 0 M p := ⟨by simpa using hM, hH, hp⟩ /-- A child of an active call at depth `d` with at most a sixteenth of its edges, precision @@ -215,9 +226,10 @@ theorem degreeBound_mono {a b : ℕ} (h : a ≤ b) : degreeBound a ≤ degreeBou Nat.pow_le_pow_right (by norm_num) (by have := Nat.log_mono_right (b := 2) h; omega) /-- **The precision bound** of (D.11), `p ≤ s λ^{O(1)} 2 ^ (a_{d - 1})`: -`precisionBound C s d ≤ C s 2 ^ (a_{d - 1}) (1 + d (8 λ + 40))`. -/ +`precisionBound C s d ≤ C s 2 ^ (a_{d - 1}) (λ + 1 + d (8 λ + 40))`. -/ theorem precisionBound_le (C s d : ℕ) : - precisionBound C s d ≤ C * s * 2 ^ prefixBits s d * (1 + d * (8 * Nat.log 2 s + 40)) := by + precisionBound C s d ≤ + C * s * 2 ^ prefixBits s d * (Nat.log 2 s + 1 + d * (8 * Nat.log 2 s + 40)) := by induction d with | zero => simp [precisionBound, prefixBits] | succ d ih => @@ -230,14 +242,14 @@ theorem precisionBound_le (C s d : ℕ) : have hstep : 8 * (4 * 2 ^ a + a + 1) ≤ 2 ^ a * (8 * n + 40) := by have : a + 1 ≤ 2 ^ a * (n + 1) := by nlinarith nlinarith - have hP : precisionBound C s d ≤ C * s * 2 ^ a * (1 + d * (8 * n + 40)) := + have hP : precisionBound C s d ≤ C * s * 2 ^ a * (n + 1 + d * (8 * n + 40)) := ih.trans (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ hpow)) calc precisionBound C s (d + 1) = precisionBound C s d + C * s * (8 * (4 * 2 ^ a + a + 1)) := by rw [precisionBound]; ring - _ ≤ C * s * 2 ^ a * (1 + d * (8 * n + 40)) + C * s * (2 ^ a * (8 * n + 40)) := + _ ≤ C * s * 2 ^ a * (n + 1 + d * (8 * n + 40)) + C * s * (2 ^ a * (8 * n + 40)) := Nat.add_le_add hP (Nat.mul_le_mul_left _ hstep) - _ = C * s * 2 ^ prefixBits s (d + 1) * (1 + (d + 1) * (8 * n + 40)) := by + _ = C * s * 2 ^ prefixBits s (d + 1) * (n + 1 + (d + 1) * (8 * n + 40)) := by rw [show prefixBits s (d + 1) = a from rfl]; ring /-! ### Active calls -/ @@ -351,10 +363,11 @@ structure ActiveBounds (C s d M p : ℕ) : Prop where /-! #### Arithmetic lemmas -/ -private theorem one_add_mul_le_sq (n : ℕ) : 1 + n * (8 * n + 40) ≤ 49 * (n + 1) ^ 2 := by +private theorem one_add_mul_le_sq (n : ℕ) : n + 1 + n * (8 * n + 40) ≤ 50 * (n + 1) ^ 2 := by nlinarith -private theorem one_add_mul_le_sq' {n : ℕ} (hn : 1 ≤ n) : 1 + n * (8 * n + 40) ≤ 49 * n ^ 2 := by +private theorem one_add_mul_le_sq' {n : ℕ} (hn : 1 ≤ n) : + n + 1 + n * (8 * n + 40) ≤ 50 * n ^ 2 := by nlinarith /-- The children's precisions are within the schedule. -/ @@ -374,34 +387,35 @@ private theorem nodePrecision_le_of {p P h u M C s a i : ℕ} (hp : p ≤ P) /-- The inherited precision is absorbed by the taper: `p u ≤ M 2 ^ (a_{d-1} + u)`. -/ private theorem mul_taper_le_of {p C s A d n σ u M : ℕ} - (hp : p ≤ C * s * 2 ^ A * (1 + d * (8 * n + 40))) (hsM : s ≤ M * σ) (hσ : σ ≤ n) - (hd : d ≤ n) (hu : u ≤ n) (hn : 1 ≤ n) (hpow : 49 * C * n ^ 4 ≤ 2 ^ u) : + (hp : p ≤ C * s * 2 ^ A * (n + 1 + d * (8 * n + 40))) (hsM : s ≤ M * σ) (hσ : σ ≤ n) + (hd : d ≤ n) (hu : u ≤ n) (hn : 1 ≤ n) (hpow : 50 * C * n ^ 4 ≤ 2 ^ u) : p * u ≤ M * 2 ^ (A + u) := by have h0 : d * (8 * n + 40) ≤ n * (8 * n + 40) := Nat.mul_le_mul_right _ hd - have h1 : 1 + d * (8 * n + 40) ≤ 49 * n ^ 2 := (Nat.add_le_add_left h0 1).trans - (one_add_mul_le_sq' hn) + have h1 : n + 1 + d * (8 * n + 40) ≤ 50 * n ^ 2 := + (Nat.add_le_add_left h0 (n + 1)).trans (one_add_mul_le_sq' hn) have h2 : σ * u ≤ n ^ 2 := by rw [sq]; exact Nat.mul_le_mul hσ hu - have hfac : C * σ * (1 + d * (8 * n + 40)) * u ≤ 49 * C * n ^ 4 := - calc C * σ * (1 + d * (8 * n + 40)) * u = C * (σ * u) * (1 + d * (8 * n + 40)) := by ring - _ ≤ C * n ^ 2 * (49 * n ^ 2) := Nat.mul_le_mul (Nat.mul_le_mul_left _ h2) h1 - _ = 49 * C * n ^ 4 := by ring - calc p * u ≤ C * s * 2 ^ A * (1 + d * (8 * n + 40)) * u := Nat.mul_le_mul_right _ hp - _ ≤ C * (M * σ) * 2 ^ A * (1 + d * (8 * n + 40)) * u := + have hfac : C * σ * (n + 1 + d * (8 * n + 40)) * u ≤ 50 * C * n ^ 4 := + calc C * σ * (n + 1 + d * (8 * n + 40)) * u + = C * (σ * u) * (n + 1 + d * (8 * n + 40)) := by ring + _ ≤ C * n ^ 2 * (50 * n ^ 2) := Nat.mul_le_mul (Nat.mul_le_mul_left _ h2) h1 + _ = 50 * C * n ^ 4 := by ring + calc p * u ≤ C * s * 2 ^ A * (n + 1 + d * (8 * n + 40)) * u := Nat.mul_le_mul_right _ hp + _ ≤ C * (M * σ) * 2 ^ A * (n + 1 + d * (8 * n + 40)) * u := Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left C hsM))) - _ = M * 2 ^ A * (C * σ * (1 + d * (8 * n + 40)) * u) := by ring + _ = M * 2 ^ A * (C * σ * (n + 1 + d * (8 * n + 40)) * u) := by ring _ ≤ M * 2 ^ A * 2 ^ u := Nat.mul_le_mul_left _ (hfac.trans hpow) _ = M * 2 ^ (A + u) := by rw [mul_assoc, ← pow_add] /-- The evaluation loss is absorbed by the taper: `8 (a + 1) u ≤ 2 ^ u`. -/ private theorem eight_mul_le_two_pow {a u n C : ℕ} (hC : 1 ≤ C) (hn : 1 ≤ n) (ha : a ≤ n) - (hu : u ≤ n) (hpow : 49 * C * n ^ 4 ≤ 2 ^ u) : 8 * (a + 1) * u ≤ 2 ^ u := by + (hu : u ≤ n) (hpow : 50 * C * n ^ 4 ≤ 2 ^ u) : 8 * (a + 1) * u ≤ 2 ^ u := by have h1 : 8 * (a + 1) * u ≤ 16 * n ^ 2 := by calc 8 * (a + 1) * u ≤ 8 * (n + n) * n := Nat.mul_le_mul (Nat.mul_le_mul_left 8 (Nat.add_le_add ha hn)) hu _ = 16 * n ^ 2 := by ring have h2 : n ^ 2 ≤ n ^ 4 := Nat.pow_le_pow_right hn (by norm_num) - have h3 : 16 * n ^ 4 ≤ 49 * C * n ^ 4 := + have h3 : 16 * n ^ 4 ≤ 50 * C * n ^ 4 := Nat.mul_le_mul_right _ (by omega) omega @@ -456,39 +470,39 @@ private theorem points_le_of {M a C s n : ℕ} (hM1 : 1 ≤ M) (hM : M ≤ C * s /-- The asymptotic inequalities behind the certificate, in `n = ⌊log₂ s⌋`. -/ private theorem eventually_large (C : ℕ) (hC : 1 ≤ C) : ∀ᶠ n : ℕ in atTop, - 200 ≤ n ∧ 49 * C * (Nat.sqrt n + 1) ^ 8 ≤ 2 ^ (Nat.sqrt n / (2 ^ 17 * C)) ∧ + 200 ≤ n ∧ 50 * C * (Nat.sqrt n + 1) ^ 8 ≤ 2 ^ (Nat.sqrt n / (2 ^ 17 * C)) ∧ C * (n + 1) ^ 1 ≤ 2 ^ (n / 8) ∧ 8 * (n + 1) ^ 1 ≤ 2 ^ (n / 20) ∧ - 49 * C * (n + 1) ^ 2 ≤ 2 ^ (n / 2) := by + 50 * C * (n + 1) ^ 2 ≤ 2 ^ (n / 2) := by have hsqrt : Tendsto Nat.sqrt atTop atTop := tendsto_atTop_atTop.2 fun b => ⟨b * b, fun n hn => Nat.le_sqrt.2 hn⟩ filter_upwards [eventually_ge_atTop 200, - hsqrt.eventually (Nat.eventually_mul_add_one_pow_le_pow_div (49 * C) 8 (2 ^ 17 * C) + hsqrt.eventually (Nat.eventually_mul_add_one_pow_le_pow_div (50 * C) 8 (2 ^ 17 * C) one_lt_two (by positivity)), Nat.eventually_mul_add_one_pow_le_pow_div C 1 8 one_lt_two (by norm_num), Nat.eventually_mul_add_one_pow_le_pow_div 8 1 20 one_lt_two (by norm_num), - Nat.eventually_mul_add_one_pow_le_pow_div (49 * C) 2 2 one_lt_two (by norm_num)] + Nat.eventually_mul_add_one_pow_le_pow_div (50 * C) 2 2 one_lt_two (by norm_num)] with n h1 h2 h3 h4 h5 exact ⟨h1, h2, h3, h4, h5⟩ /-! #### The certificate -/ -/-- Precisions up to depth `⌊log₂ s⌋` are at most `s ^ 2` once `49 C (λ + 1)² ≤ 2 ^ (λ / 2)`. -/ +/-- Precisions up to depth `⌊log₂ s⌋` are at most `s ^ 2` once `50 C (λ + 1)² ≤ 2 ^ (λ / 2)`. -/ private theorem precisionBound_le_sq_of {C s d : ℕ} (hd : d ≤ Nat.log 2 s) (hs : s ≠ 0) - (h : 49 * C * (Nat.log 2 s + 1) ^ 2 ≤ 2 ^ (Nat.log 2 s / 2)) : + (h : 50 * C * (Nat.log 2 s + 1) ^ 2 ≤ 2 ^ (Nat.log 2 s / 2)) : precisionBound C s d ≤ s ^ 2 := by set n := Nat.log 2 s have h2n : 2 ^ n ≤ s := Nat.pow_log_le_self 2 hs have hpow : 2 ^ prefixBits s d ≤ 2 ^ (n / 32768) := Nat.pow_le_pow_right two_pos (prefixBits_le s d) - have hfac : C * (1 + d * (8 * n + 40)) ≤ 49 * C * (n + 1) ^ 2 := by + have hfac : C * (n + 1 + d * (8 * n + 40)) ≤ 50 * C * (n + 1) ^ 2 := by have h1 : d * (8 * n + 40) ≤ n * (8 * n + 40) := Nat.mul_le_mul_right _ hd have h2 := one_add_mul_le_sq n - calc C * (1 + d * (8 * n + 40)) ≤ C * (49 * (n + 1) ^ 2) := + calc C * (n + 1 + d * (8 * n + 40)) ≤ C * (50 * (n + 1) ^ 2) := Nat.mul_le_mul_left _ (by omega) - _ = 49 * C * (n + 1) ^ 2 := by ring - calc precisionBound C s d ≤ C * s * 2 ^ prefixBits s d * (1 + d * (8 * n + 40)) := + _ = 50 * C * (n + 1) ^ 2 := by ring + calc precisionBound C s d ≤ C * s * 2 ^ prefixBits s d * (n + 1 + d * (8 * n + 40)) := precisionBound_le C s d - _ = s * (C * (1 + d * (8 * n + 40))) * 2 ^ prefixBits s d := by ring + _ = s * (C * (n + 1 + d * (8 * n + 40))) * 2 ^ prefixBits s d := by ring _ ≤ s * 2 ^ (n / 2) * 2 ^ (n / 32768) := Nat.mul_le_mul (Nat.mul_le_mul_left _ (hfac.trans h)) hpow _ = s * 2 ^ (n / 2 + n / 32768) := by rw [mul_assoc, ← pow_add] @@ -499,7 +513,7 @@ private theorem precisionBound_le_sq_of {C s d : ℕ} (hd : d ≤ Nat.log 2 s) ( /-- The certificate at one active call, from the asymptotic inequalities at `λ = ⌊log₂ s⌋`. -/ private theorem activeBounds_of {C s d M p : ℕ} (hA : Active C s d M p) (h200 : 200 ≤ Nat.log 2 s) - (hA1 : 49 * C * (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 ≤ + (hA1 : 50 * C * (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 ≤ 2 ^ (Nat.sqrt (Nat.log 2 s) / (2 ^ 17 * C))) (hA2 : C * (Nat.log 2 s + 1) ≤ 2 ^ (Nat.log 2 s / 8)) (hA3 : 8 * (Nat.log 2 s + 1) ≤ 2 ^ (Nat.log 2 s / 20)) : @@ -532,12 +546,12 @@ private theorem activeBounds_of {C s d M p : ℕ} (hA : Active C s d M p) have := (Nat.pow_lt_pow_iff_right one_lt_two).1 h1 omega -- The taper. - have hpowu : 49 * C * Nat.log 2 s ^ 4 ≤ 2 ^ taper s d := by + have hpowu : 50 * C * Nat.log 2 s ^ 4 ≤ 2 ^ taper s d := by have hn4 : Nat.log 2 s ^ 4 ≤ (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := by calc Nat.log 2 s ^ 4 ≤ ((Nat.sqrt (Nat.log 2 s) + 1) ^ 2) ^ 4 := Nat.pow_le_pow_left hnσ.le 4 _ = (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := by rw [← pow_mul] - calc 49 * C * Nat.log 2 s ^ 4 ≤ 49 * C * (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := + calc 50 * C * Nat.log 2 s ^ 4 ≤ 50 * C * (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := Nat.mul_le_mul_left _ hn4 _ ≤ 2 ^ (Nat.sqrt (Nat.log 2 s) / (2 ^ 17 * C)) := hA1 _ ≤ 2 ^ taper s d := Nat.pow_le_pow_right two_pos (hA.le_taper hs2) @@ -546,9 +560,9 @@ private theorem activeBounds_of {C s d M p : ℕ} (hA : Active C s d M p) have h0 : taper s d = 0 := by omega rw [h0, pow_zero] at hpowu have : 1 ≤ Nat.log 2 s ^ 4 := Nat.one_le_pow _ _ (by omega) - have : 49 ≤ 49 * C * Nat.log 2 s ^ 4 := - calc 49 = 49 * 1 * 1 := rfl - _ ≤ 49 * C * Nat.log 2 s ^ 4 := Nat.mul_le_mul (Nat.mul_le_mul_left _ hC) this + have : 50 ≤ 50 * C * Nat.log 2 s ^ 4 := + calc 50 = 50 * 1 * 1 := rfl + _ ≤ 50 * C * Nat.log 2 s ^ 4 := Nat.mul_le_mul (Nat.mul_le_mul_left _ hC) this omega have hun : taper s d ≤ Nat.log 2 s := Nat.div_le_self _ _ have huM : taper s d ≤ M := by diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedAlgorithm.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedAlgorithm.lean index bee65d2372..107c7a6fcb 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedAlgorithm.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedAlgorithm.lean @@ -8,13 +8,14 @@ module -- upstream: candidate public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedResources +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModulusGrowth import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionPolyTime import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary /-! -# Theorem D.1 at truncation order `ℓ`, from a contraction over `A_P` +# Polynomial truncation order from a contraction over `A_P` Theorem D.1 of [OpenAI2026ETH] (p. 53) contracts networks whose entries lie in `B_{P,ℓ} = (ℤ / 2 ^ P)[V] / (V ^ ℓ)`. This file states its interface for described networks @@ -25,8 +26,9 @@ Theorem D.1 of [OpenAI2026ETH] (p. 53) contracts networks whose entries lie in **The interface.** One native certificate computes the truncated contraction (`DescribedNetwork.truncContraction`) from `(P, ℓ, r, G)`, with `P` and `ℓ` in unary. For all -constants `C` and `D` there is `K` such that, on every network admissible at a size `s ≥ 2` with -constant `C`, and every `1 ≤ P, ℓ ≤ D s`, it takes time at most +constants `C`, `D` and `d` there is `K` such that, on every network admissible at a size `s ≥ 2` +with constant `C`, precision `1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)` and truncation order +`1 ≤ ℓ ≤ D (s + 1) ^ d`, it takes time at most `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space at most `K (n + 1) ^ K`, for the encoded length `n` of the input. This is the shape of `Cslib.Complexity.CountingSimulation.ContractionAlgorithm`. @@ -37,9 +39,15 @@ of the input. This is the shape of `Cslib.Complexity.CountingSimulation.Contract (`Cslib.Circuits.Frontier.fastTruncCertificate`); * otherwise it sums directly (`truncDirectCertificate`). -On admissible inputs with `k ≤ C s` vertices, `s ≤ m` and `ℓ ≤ D s`, the first branch is taken once -`s ≥ 4 (C D + 1)` (`Cslib.Circuits.Frontier.truncGuard_of_le`), since `deg f ≥ 4 ⌊log₂ m⌋`. Below -that size, direct summation costs a constant factor. +On admissible inputs with at most `C s` vertices, at least `s` edges and polynomial `ℓ`, the first +branch is eventually taken. The modulus already has degree +`2 * 3 ^ ⌊log₂ (4 ⌊log₂ m⌋ + 2)⌋`, which eventually exceeds every fixed multiple of `log₂ m`. +The explicit sufficient threshold is `max (C D) (2 ^ (2 ^ (d + 2)))` +(`Cslib.Circuits.Frontier.truncGuard_of_polynomial_le`). Below it, direct summation costs a +constant factor. The older and smaller linear-order threshold is also retained. + +The logarithmic factor in precision and the polynomial truncation range strengthen the stated +range of Theorem D.1. These extensions are proved here; they are not attributed to its statement. **The hypotheses on the contraction over `A_P`** are those of the interface planned in #1538 for milestone E1: it is correct on every network at the modulus of its number of edges, and meets the @@ -78,8 +86,9 @@ variable {T : Type} {encT : T ↪ List Bool} /-- **The interface of Theorem D.1 at truncation order `ℓ`** for described networks. One native certificate computes the truncated contraction from `(P, ℓ, r, G)`, with `P` and `ℓ` in unary. For -all constants `C` and `D` there is `K` such that, on every network admissible at a size `s ≥ 2` -with constant `C`, and every `1 ≤ P, ℓ ≤ D s`, the certificate's time is at most +all constants `C`, `D`, `d` there is `K` such that, on every network admissible at a size `s ≥ 2` +with constant `C`, precision `1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)` and order `1 ≤ ℓ ≤ D (s + 1) ^ d`, its +time is at most `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and its space at most `K (n + 1) ^ K`, where `n` is the encoded length of the input. Outside admissible inputs the certificate need only be correct. -/ structure TruncatedContractionAlgorithm (encT : T ↪ List Bool) (entry : T → List Bool → List ℕ) @@ -88,13 +97,30 @@ structure TruncatedContractionAlgorithm (encT : T ↪ List Bool) (entry : T → certificate : NativeCertificate (fun ((P, ℓ, _, G) : ℕ × ℕ × ℕ × DescribedNetwork T) => G.truncContraction entry P ℓ) (encTrunc encT) (encList encNat) - /-- Its time and space on admissible inputs. -/ - bound : ∀ C D : ℕ, ∃ K : ℕ, ∀ (s P ℓ r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → - P ≤ D * s → 1 ≤ ℓ → ℓ ≤ D * s → Admissible C s (r, G) → + /-- Its time and space for logarithmically larger precision and any fixed polynomial + truncation order. All constants are chosen before the instance. -/ + bound_polynomial : ∀ C D d : ℕ, ∃ K : ℕ, ∀ (s P ℓ r : ℕ) (G : DescribedNetwork T), + 2 ≤ s → 1 ≤ P → P ≤ D * s * (Nat.log 2 s + 1) → + 1 ≤ ℓ → ℓ ≤ D * (s + 1) ^ d → Admissible C s (r, G) → certificate.time (P, ℓ, r, G) ≤ K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K * 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ certificate.space (P, ℓ, r, G) ≤ K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K +/-- The linear precision and truncation range of Theorem D.1 is a special case of the enlarged +range. -/ +theorem TruncatedContractionAlgorithm.bound {entry : T → List Bool → List ℕ} + {Admissible : ℕ → ℕ → ℕ × DescribedNetwork T → Prop} + (A : TruncatedContractionAlgorithm encT entry Admissible) (C D : ℕ) : + ∃ K : ℕ, ∀ (s P ℓ r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s → + 1 ≤ ℓ → ℓ ≤ D * s → Admissible C s (r, G) → + A.certificate.time (P, ℓ, r, G) ≤ K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K * + 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ + A.certificate.space (P, ℓ, r, G) ≤ K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K := by + obtain ⟨K, hK⟩ := A.bound_polynomial C D 1 + refine ⟨K, fun s P ℓ r G hs hP hPD hℓ hℓD hA => hK s P ℓ r G hs hP ?_ hℓ ?_ hA⟩ + · exact hPD.trans (Nat.le_mul_of_pos_right _ (by omega)) + · simpa only [pow_one] using hℓD.trans (Nat.mul_le_mul_left D (Nat.le_succ s)) + /-! ### The guard -/ /-- **The guard** at an input `(P, ℓ, r, G)`: the modulus `FastContraction.modulus m` of the network @@ -161,6 +187,29 @@ theorem truncGuard_of_le {C D s P ℓ r : ℕ} {G : DescribedNetwork T} (hs : 4 nlinarith omega +/-- Every fixed polynomial truncation order eventually passes the same interpolation guard. +The threshold is independent of the network, its precision and its encoding length. -/ +theorem truncGuard_of_polynomial_le {C D d s P ℓ r : ℕ} {G : DescribedNetwork T} + (hs : max (C * D) (2 ^ (2 ^ (d + 2))) ≤ s) + (hℓ : ℓ ≤ D * (s + 1) ^ d) (hk : G.vertices.length ≤ C * s) (hm : s ≤ G.numEdges) : + truncGuard (P, ℓ, r, G) = true := by + simp only [truncGuard, decide_eq_true_eq] + have hCD : C * D ≤ s := (le_max_left _ _).trans hs + have hpow : 1 ≤ (s + 1) ^ (d + 1) := Nat.one_le_pow _ _ (by omega) + have hprod : (ℓ - 1) * G.vertices.length ≤ C * D * (s + 1) ^ (d + 1) := by + calc (ℓ - 1) * G.vertices.length ≤ (D * (s + 1) ^ d) * (C * (s + 1)) := + Nat.mul_le_mul (by omega) (hk.trans (Nat.mul_le_mul_left C (Nat.le_succ s))) + _ = C * D * (s + 1) ^ (d + 1) := by rw [pow_succ]; ring + calc DescribedNetwork.pointCount ℓ G + ≤ (C * D + 1) * (s + 1) ^ (d + 1) := by + simp only [DescribedNetwork.pointCount] + nlinarith + _ ≤ (s + 1) * (s + 1) ^ (d + 1) := Nat.mul_le_mul_right _ (by omega) + _ = (s + 1) ^ (d + 2) := by rw [show d + 2 = (d + 1) + 1 by omega, pow_succ]; ring + _ ≤ (G.numEdges + 1) ^ (d + 2) := Nat.pow_le_pow_left (by omega) _ + _ ≤ 2 ^ (FastContraction.modulus G.numEdges).length := + FastContraction.pow_le_card_modulus (((le_max_right _ _).trans hs).trans hm) + /-! ### The fast branch -/ /-- The input of the reduction at the modulus of the network. -/ @@ -225,14 +274,15 @@ noncomputable def TruncatedContractionAlgorithm.ofCore {entry : T → List Bool G'.contraction fun d bs => val P (FastContraction.modulus G'.numEdges) (specEntry entry d bs)) (hcore : ∀ C D : ℕ, ∃ K : ℕ, ∀ (s P r : ℕ) (G' : DescribedNetwork (SpecDesc T)), 2 ≤ s → - 1 ≤ P → P ≤ D * s → CoreAdm C s (r, G') → + 1 ≤ P → P ≤ D * s * (Nat.log 2 s + 1) → CoreAdm C s (r, G') → cc.time (P, FastContraction.modulus G'.numEdges, r, G') ≤ K * ((encCore encT (P, FastContraction.modulus G'.numEdges, r, G')).length + 1) ^ K * 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ cc.space (P, FastContraction.modulus G'.numEdges, r, G') ≤ K * ((encCore encT (P, FastContraction.modulus G'.numEdges, r, G')).length + 1) ^ K) - (htransfer : ∀ C D : ℕ, ∃ C' : ℕ, ∀ (s P ℓ r k : ℕ) (G : DescribedNetwork T), 2 ≤ s → - 1 ≤ P → P ≤ D * s → 1 ≤ ℓ → ℓ ≤ D * s → Adm C s (r, G) → + (htransfer : ∀ C D d : ℕ, ∃ C' : ℕ, ∀ (s P ℓ r k : ℕ) (G : DescribedNetwork T), 2 ≤ s → + 1 ≤ P → P ≤ D * s * (Nat.log 2 s + 1) → + 1 ≤ ℓ → ℓ ≤ D * (s + 1) ^ d → Adm C s (r, G) → CoreAdm C' s (r, DescribedNetwork.specNet P ℓ (FastContraction.modulus G.numEdges) (FastContraction.digits (FastContraction.modulus G.numEdges).length k) G)) (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) @@ -251,8 +301,8 @@ noncomputable def TruncatedContractionAlgorithm.ofCore {entry : T → List Bool split · next h => exact fastTruncCertificate_correct hval P ℓ r G h · rfl - bound C D := by - obtain ⟨C', hC'⟩ := htransfer C D + bound_polynomial C D dℓ := by + obtain ⟨C', hC'⟩ := htransfer C D dℓ obtain ⟨K₀, hK₀⟩ := hcore C' D obtain ⟨Ke, hKe⟩ := hentry C obtain ⟨Kd, hKd⟩ := truncDirectCertificate_le entry ce @@ -261,7 +311,7 @@ noncomputable def TruncatedContractionAlgorithm.ofCore {entry : T → List Bool obtain ⟨pg, hpg, mg, bg⟩ := exists_mono_poly_cert (truncGuard_polyTime (encT := encT)) obtain ⟨pM, hpM, mM, bM⟩ := exists_mono_poly_cert (modulusEdges_polyTime (encT := encT)) obtain ⟨cY, dY, hY⟩ := (modulusEdges_polyTime (encT := encT)).polyBounded_length.exists_le - set s₀ := 4 * (C * D + 1) with hs₀ + set s₀ := max (C * D) (2 ^ (2 ^ (dℓ + 2))) with hs₀ -- The overheads, as polynomials in the length `n` of the input. let Yf : ℕ → ℕ := fun n => cY * (n + 1) ^ dY let Qf : ℕ → ℕ := fun n => K₀ * (9 * (Yf n + 1) ^ 2 + 1) ^ K₀ @@ -385,7 +435,8 @@ noncomputable def TruncatedContractionAlgorithm.ofCore {entry : T → List Bool intro hg have hss : s < s₀ := by by_contra h - have := truncGuard_of_le (P := P) (r := r) (show 4 * (C * D + 1) ≤ s by omega) hℓD hkC hsm + have := truncGuard_of_polynomial_le (P := P) (r := r) + (show max (C * D) (2 ^ (2 ^ (dℓ + 2))) ≤ s by omega) hℓD hkC hsm rw [this] at hg exact Bool.noConfusion hg have hL : (DescribedNetwork.encDescribed encT G).length ≤ n := by diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedEntry.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedEntry.lean index c869278161..55109bb449 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedEntry.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedEntry.lean @@ -24,15 +24,16 @@ algorithm for the original entries. It instantiates the assembly **Admissible networks** (`Cslib.Circuits.Frontier.TruncEntryAdmissible`). The hypotheses of Theorem D.1 as in `Cslib.Circuits.Frontier.EntryAdmissible`, plus **at most `C s` vertices**. The -precision and the truncation order are bounded by `P, ℓ ≤ D s` in the interface itself. +precision is bounded by `D s (⌊log₂ s⌋ + 1)` and the truncation order by `D (s + 1) ^ d`, for +fixed `D` and `d`, in the interface itself. *Narrowing against Theorem D.1.* The theorem asks only that the number of vertices be bounded by a fixed polynomial in `s`. The bound `k ≤ C s` is what the assembly needs: the reduction of truncation order `ℓ` to `A_P` interpolates at `(ℓ - 1) k + 1` points, which must fit in the -residues of the modulus `FastContraction.modulus m`, more than `m ^ 4 / 16` of them for `m` edges. -With `k ≤ C s` and `ℓ ≤ D s` they fit once `s ≥ 4 (C D + 1)` -(`Cslib.Circuits.Frontier.truncGuard_of_le`), and below that size direct summation costs a constant -factor. The applications satisfy the narrower bound: the network of Lemma D.2 (p. 56) has +residues of the modulus `FastContraction.modulus m`. These eventually outnumber every fixed +polynomial in `m` (`FastContraction.pow_le_card_modulus`), so polynomial truncation passes the +guard beyond a constant size. Below that size direct summation costs a constant factor. +The applications satisfy the linear vertex bound: the network of Lemma D.2 (p. 56) has `2 J = 2 ⌈n ^ (1/4)⌉` vertices at size `s = n ν`, and that of the fixed-density bound (§D.2, p. 54) has `O_k(n + m)` vertices at size `s = n + m`. @@ -265,15 +266,16 @@ entries (`specEntry entry`, with the certificate `specEntryCertificate entry ce` admissible for them. Then the assembly `TruncatedContractionAlgorithm.ofCore` around `A` is a truncated contraction algorithm for the networks admissible for `ce` with at most `C s` vertices (`TruncEntryAdmissible ce`): it computes the contraction in `B_{P,ℓ}` in time -`poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial space, for `1 ≤ P, ℓ ≤ D s`. -/ +`poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial space, for +`1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)` and `1 ≤ ℓ ≤ D (s + 1) ^ d`, for fixed `D`, `d`. -/ noncomputable def TruncatedContractionAlgorithm.ofEntry (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) (encList encNat)) (A : EntryContractionAlgorithm (encSpecDesc encT) (specEntry entry) (EntryAdmissible (specEntryCertificate entry ce))) : TruncatedContractionAlgorithm encT entry (TruncEntryAdmissible ce) := - ofCore (core := A.core) A.certificate A.val_core A.bound - (fun C _ => by + ofCore (core := A.core) A.certificate A.val_core A.bound_logPrecision + (fun C _ _ => by obtain ⟨C', hC'⟩ := EntryContraction.exists_entryAdmissible_specNet ce C exact ⟨C', fun s P ℓ r _ G _ _ _ _ _ hA => hC' s P ℓ r _ _ G hA.1⟩) ce diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedPolynomialEntries.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedPolynomialEntries.lean new file mode 100644 index 0000000000..d168fe0ba3 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TruncatedPolynomialEntries.lean @@ -0,0 +1,101 @@ +/- +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.TruncatedEntry + +/-! +# Direct truncated contraction with polynomial-time entries + +For a fixed polynomial-time entry procedure, direct truncated contraction costs a polynomial in +the encoded input times `2 ^ m`, where `m` is the number of edges, and polynomial space. This is +the finite-size fallback for bounded-degree weighted counting. + +## Main results + +* `truncDirectCertificate_le_of_isPolynomial`: uniform direct-contraction bounds without a + degree or bipartition hypothesis. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Cslib.GenericRAM Turing.MultiTapeTM + +variable {T : Type} {encT : T ↪ List Bool} {entry : T → List Bool → List ℕ} + +/-- The encoded argument of an entry on a vertex is at most three times the encoded network +length plus a constant. -/ +theorem DescribedNetwork.length_entryInput_le (G : DescribedNetwork T) + {v : List ℕ × T} (hv : v ∈ G.vertices) {bs : List Bool} (hbs : bs.length = v.1.length) : + (encPair encT encBits (v.2, bs)).length + 1 ≤ + 3 * ((DescribedNetwork.encDescribed encT G).length + 1) := by + have h1 := length_le_encList_of_mem (encPair (encList encNat) encT) hv + have h2 : (encList (encPair (encList encNat) encT) G.vertices).length ≤ + (DescribedNetwork.encDescribed encT G).length := by + change _ ≤ (encPair encUnary (encList (encPair (encList encNat) encT)) + (G.numEdges, G.vertices)).length + rw [length_encPair] + dsimp only + omega + have h3 := length_le_length_encList encNat v.1 + simp only [length_encPair, encBits_apply] at h1 ⊢ + omega + +/-- Direct truncated contraction with a polynomial entry procedure has only the `2 ^ m` +enumeration factor. The result includes its encoded output in the space bound. -/ +theorem truncDirectCertificate_le_of_isPolynomial + (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) + (encList encNat)) (hce : ce.IsPolynomial) : + ∃ K : ℕ, ∀ (P ℓ r : ℕ) (G : DescribedNetwork T), + (truncDirectCertificate entry ce).time (P, ℓ, r, G) ≤ + K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K * 2 ^ G.numEdges ∧ + (truncDirectCertificate entry ce).space (P, ℓ, r, G) ≤ + K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K := by + obtain ⟨a, d, ha⟩ := (hce.1.add hce.2).exists_le + obtain ⟨Kd, hKd⟩ := truncDirectCertificate_le entry ce + let E : ℕ → ℕ := fun n => a * 3 ^ d * (n + 1) ^ d + let F : ℕ → ℕ := fun n => Kd * (n + E n + 1) ^ Kd * (E n + 1) + have hEp : PolyBounded id E := + (PolyBounded.const (a * 3 ^ d)).mul ((PolyBounded.self.add (PolyBounded.const 1)).pow d) + have hFp : PolyBounded id F := + ((PolyBounded.const Kd).mul (((PolyBounded.self.add hEp).add + (PolyBounded.const 1)).pow Kd)).mul (hEp.add (PolyBounded.const 1)) + obtain ⟨aF, dF, hF⟩ := hFp.exists_le + refine ⟨aF + dF, fun P ℓ r G => ?_⟩ + set n := (encTrunc encT (P, ℓ, r, G)).length + have hL : (DescribedNetwork.encDescribed encT G).length ≤ n := by + simp only [n, length_encPair] + omega + have he : ∀ v ∈ G.vertices, ∀ bs : List Bool, bs.length = v.1.length → + ce.time (v.2, bs) ≤ E n ∧ ce.space (v.2, bs) ≤ E n := by + intro v hv bs hbs + have h1 := G.length_entryInput_le (encT := encT) hv hbs + have h2 := ha (v.2, bs) + have h3 : a * ((encPair encT encBits (v.2, bs)).length + 1) ^ d ≤ E n := by + calc _ ≤ a * (3 * (n + 1)) ^ d := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) d) + _ = E n := by simp only [E, mul_pow]; ring + exact ⟨by omega, by omega⟩ + obtain ⟨hT, hS⟩ := hKd P ℓ r G (E n) (E n) he + have hbound : F n ≤ (aF + dF) * (n + 1) ^ (aF + dF) := + (hF n).trans (Nat.mul_le_mul (by omega) + (Nat.pow_le_pow_right (by omega) (by omega))) + refine ⟨?_, ?_⟩ + · calc (truncDirectCertificate entry ce).time (P, ℓ, r, G) + ≤ Kd * (n + E n + 1) ^ Kd * 2 ^ G.numEdges * (E n + 1) := hT + _ = F n * 2 ^ G.numEdges := by simp only [F]; ring + _ ≤ (aF + dF) * (n + 1) ^ (aF + dF) * 2 ^ G.numEdges := + Nat.mul_le_mul_right _ hbound + · calc (truncDirectCertificate entry ce).space (P, ℓ, r, G) + ≤ Kd * (n + E n + 1) ^ Kd := hS + _ ≤ F n := Nat.le_mul_of_pos_right _ (by omega) + _ ≤ (aF + dF) * (n + 1) ^ (aF + dF) := hbound + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/EntryAnalysis.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/EntryAnalysis.lean index 436cee0775..d7fcb149b7 100644 --- a/Complexity/Computability/Complexity/Classes/CountingSimulation/EntryAnalysis.lean +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/EntryAnalysis.lean @@ -19,8 +19,10 @@ The instance of the contraction interface for entry procedures `EntryContraction.fastCore` (milestone E1.3 of #1538): for every native certificate `ce` of an entry procedure, `CountingSimulation.entryContractionAlgorithm ce` contracts the networks admissible for `ce` (`Cslib.Circuits.Frontier.EntryAdmissible`) in time `poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and -polynomial space. This is Theorem D.1 of [OpenAI2026ETH] (p. 53) at truncation order `1`, with -the entries given by procedures. +polynomial space. This includes Theorem D.1 of [OpenAI2026ETH] (p. 53) at truncation order `1`, +with the entries given by procedures, and extends its root precision to +`P ≤ D s (⌊log₂ s⌋ + 1)` for each fixed `D`. The schedule absorbs the extra logarithm in +(D.11), while the clock charges `P / (⌊log₂ m⌋ + 1)` at `m` edges. The analysis follows the one for explicit networks (`CountingSimulation.FastAnalysis`, `CountingSimulation.fastSizes`, `CountingSimulation.fastLeaves`), with the root call of @@ -80,28 +82,59 @@ theorem length_parts_le_encInput (P : ℕ) (fl : List ℕ) (r : ℕ) (G : Descri omega /-- **Guarded roots are on the schedule**, for every constant `C ≥ 1` with precision at most -`C m`. -/ -theorem onSchedule_rootCall {P C r : ℕ} {G : DescribedNetwork T} +`C m (⌊log₂ m⌋ + 1)`. -/ +theorem onSchedule_rootCall_logPrecision {P C r : ℕ} {G : DescribedNetwork T} (hg : EntryContraction.guard (P, modulus G.numEdges, r, G) = true) (hC : 1 ≤ C) - (hp : P ≤ C * G.numEdges) : + (hp : P ≤ C * G.numEdges * (Nat.log 2 G.numEdges + 1)) : OnSchedule C G.numEdges (rootCall (P, modulus G.numEdges, r, G)) := by have hdeg : ∀ v ∈ G.vertices, v.1.length ≤ degreeBound G.numEdges := by simp only [EntryContraction.guard, decide_eq_true_eq] at hg exact hg.2.2 - refine FastContraction.onSchedule_root hC rfl rfl rfl (wf_rootCall hg) (fun v hv => ?_) rfl rfl - hp + refine FastContraction.onSchedule_root_logPrecision hC rfl rfl rfl (wf_rootCall hg) + (fun v hv => ?_) rfl rfl hp simp only [rootCall, FastContraction.rootNet, List.mem_map] at hv obtain ⟨w, hw, rfl⟩ := hv exact hdeg w hw +/-- Linear root precision is also allowed. -/ +theorem onSchedule_rootCall {P C r : ℕ} {G : DescribedNetwork T} + (hg : EntryContraction.guard (P, modulus G.numEdges, r, G) = true) (hC : 1 ≤ C) + (hp : P ≤ C * G.numEdges) : + OnSchedule C G.numEdges (rootCall (P, modulus G.numEdges, r, G)) := + onSchedule_rootCall_logPrecision hg hC + (hp.trans (Nat.le_mul_of_pos_right _ (Nat.succ_pos _))) + +/-- Dividing the precision by the logarithmic allowance restores a linear root bound. -/ +theorem normalizedPrecision_le {m s P D : ℕ} (hsm : s ≤ m) + (hP : P ≤ D * s * (Nat.log 2 s + 1)) : normalizedPrecision m P ≤ D * s := by + have hlog : Nat.log 2 s + 1 ≤ Nat.log 2 m + 1 := Nat.add_le_add_right + (Nat.log_mono_right hsm) 1 + have hp := hP.trans (Nat.mul_le_mul_left (D * s) hlog) + have h := Nat.div_le_div_right (c := Nat.log 2 m + 1) hp + rw [Nat.mul_div_cancel _ (Nat.succ_pos _)] at h + exact h + +/-- The integer constant read from normalized precision bounds the original precision. -/ +theorem le_normalizedPrecision_root {m P : ℕ} (hm : 0 < m) : + P ≤ (normalizedPrecision m P / m + 1) * m * (Nat.log 2 m + 1) := by + have hp : P ≤ (normalizedPrecision m P + 1) * (Nat.log 2 m + 1) := by + have h := (Nat.lt_div_mul_add (a := P) (b := Nat.log 2 m + 1) (Nat.succ_pos _)).le + simpa only [normalizedPrecision, add_mul, one_mul] using h + have hq : normalizedPrecision m P + 1 ≤ (normalizedPrecision m P / m + 1) * m := by + have h := Nat.lt_div_mul_add (a := normalizedPrecision m P) (b := m) hm + rw [add_mul, one_mul] + omega + exact hp.trans (Nat.mul_le_mul_right _ hq) + /-- **The clock is subexponential on admissible inputs**: with `s ≤ m ≤ C s` edges and precision -`P ≤ D s`, it is at most `2 ^ (K₀ (C + D) s / √⌊log₂ s⌋)`. -/ +`P ≤ D s (⌊log₂ s⌋ + 1)`, it is at most `2 ^ (K₀ (C + D) s / √⌊log₂ s⌋)`. -/ theorem clock_le {C D s P r : ℕ} {fl : List ℕ} {G : DescribedNetwork T} (hs : 2 ≤ s) - (hsm : s ≤ G.numEdges) (hmC : G.numEdges ≤ C * s) (hP : P ≤ D * s) : + (hsm : s ≤ G.numEdges) (hmC : G.numEdges ≤ C * s) (hP : P ≤ D * s * (Nat.log 2 s + 1)) : clock (P, fl, r, G) ≤ 2 ^ (fuelConst * (C + D) * s / Nat.sqrt (Nat.log 2 s)) := by simp only [clock] refine Nat.pow_le_pow_right (by norm_num) ?_ - set y := G.numEdges + P + set y := G.numEdges + normalizedPrecision G.numEdges P + have hqP := normalizedPrecision_le hsm hP have hy : y ≤ (C + D) * s := by rw [add_mul]; omega have hsy : s ≤ y := by omega have hq : 1 ≤ Nat.sqrt (Nat.log 2 s) := Nat.one_le_sqrt_log_two hs @@ -209,10 +242,11 @@ theorem entry_length_le_of_reach {x : ℕ × List ℕ × ℕ × DescribedNetwork /-- **The facts at the reached calls that the leaf bounds use.** For all constants `C` and `D` there is `s₀` such that on every guarded input at the modulus with `s ≤ m ≤ C s` edges and -precision `P ≤ D s`, every reached call has scale `m`, at most `2 m` edges, and, from `s₀` on, +precision `P ≤ D s (⌊log₂ s⌋ + 1)`, every reached call has scale `m`, at most `2 m` edges and, +from `s₀` on, precision at most `m ^ 4`, and at most `leafSize m` edges if the machine treats it as a leaf. -/ theorem entry_reach_facts (C D : ℕ) : ∃ s₀, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → - P ≤ D * s → s ≤ G.numEdges → G.numEdges ≤ C * s → + P ≤ D * s * (Nat.log 2 s + 1) → s ≤ G.numEdges → G.numEdges ≤ C * s → EntryContraction.guard (P, modulus G.numEdges, r, G) = true → ∀ y, Reach nativeMk (rootCall (P, modulus G.numEdges, r, G)) y → y.scale = G.numEdges ∧ y.edges ≤ 2 * y.scale ∧ @@ -220,7 +254,11 @@ theorem entry_reach_facts (C D : ℕ) : ∃ s₀, ∀ (s P r : ℕ) (G : Describ obtain ⟨sA, hsA⟩ := exists_activeBounds_le D refine ⟨max sA (D + 81), fun s P r G hs hPD hsm hmC hg y hy => ?_⟩ set m := G.numEdges with hm - have hp₀ : P ≤ D * m := hPD.trans (Nat.mul_le_mul_left _ hsm) + have hp₀ : P ≤ D * m * (Nat.log 2 m + 1) := + hPD.trans (Nat.mul_le_mul (Nat.mul_le_mul_left _ hsm) + (Nat.add_le_add_right (Nat.log_mono_right hsm) 1)) + have hq₀ : normalizedPrecision m P ≤ D * m := + (normalizedPrecision_le hsm hPD).trans (Nat.mul_le_mul_left _ hsm) have hsh := FastContraction.Reach.shape (x₀ := rootCall (P, modulus m, r, G)) rfl rfl (wf_rootCall hg) hy have hsc : y.scale = m := hsh.scale_eq @@ -229,9 +267,15 @@ theorem entry_reach_facts (C D : ℕ) : ∃ s₀, ∀ (s P r : ℕ) (G : Describ change y.bound + y.depth ≤ (m + 1) + 0 at hbd have hprec : y.prec ≤ P + y.depth * (40 * m ^ 2) := hsh.prec_le have : y.depth * (40 * m ^ 2) ≤ (m + 1) * (40 * m ^ 2) := Nat.mul_le_mul_right _ (by omega) - have h4 : D * m + (m + 1) * (40 * m ^ 2) ≤ m ^ 4 := by + have hlog : Nat.log 2 m + 1 ≤ m := Nat.log_lt_self 2 (by omega) + have hp₂ : P ≤ D * m ^ 2 := hp₀.trans (by + calc D * m * (Nat.log 2 m + 1) ≤ D * m * m := Nat.mul_le_mul_left _ hlog + _ = D * m ^ 2 := by ring) + have h4 : D * m ^ 2 + (m + 1) * (40 * m ^ 2) ≤ m ^ 4 := by have hm81 : D + 81 ≤ m := by omega - have h1 : D * m ≤ m * m := Nat.mul_le_mul_right _ (by omega) + have h1 : D * m ^ 2 ≤ m ^ 3 := by + calc D * m ^ 2 ≤ m * m ^ 2 := Nat.mul_le_mul_right _ (by omega) + _ = m ^ 3 := by ring have h2 : 81 * m ^ 3 ≤ m ^ 4 := by rw [pow_succ m 3, mul_comm (m ^ 3) m] exact Nat.mul_le_mul_right _ (by omega) @@ -243,14 +287,13 @@ theorem entry_reach_facts (C D : ℕ) : ∃ s₀, ∀ (s P r : ℕ) (G : Describ rw [hsc] omega · have hm2 : D + 2 ≤ m := by omega - have hC1 : P / m + 1 ≤ D + 1 := by - have := Nat.div_le_div_right (c := m) hp₀ + have hC1 : normalizedPrecision m P / m + 1 ≤ D + 1 := by + have := Nat.div_le_div_right (c := m) hq₀ rw [Nat.mul_div_cancel _ (by omega)] at this omega - have hB := hsA m (by omega) (P / m + 1) hC1 - have hpC : P ≤ (P / m + 1) * m := - (Nat.lt_div_mul_add (a := P) (b := m) (by omega)).le.trans (by rw [add_mul, one_mul]) - have h := hy.onSchedule hB (onSchedule_rootCall hg (Nat.le_add_left 1 _) hpC) + have hB := hsA m (by omega) (normalizedPrecision m P / m + 1) hC1 + have hpC := le_normalizedPrecision_root (P := P) (by omega : 0 < m) + have h := hy.onSchedule hB (onSchedule_rootCall_logPrecision hg (Nat.le_add_left 1 _) hpC) rw [hsc] exact h.edges_le_leafSize hB hl @@ -269,7 +312,8 @@ private theorem two_pow_mul_ceilNineTenths_le {C s₁ : ℕ} /-- **The leaf bounds on admissible inputs**: for a native certificate `ce` of the entry procedure and all constants `C` and `D` there is `K` such that on every input at the modulus admissible at -size `s ≥ 2` with constant `C` and with precision `1 ≤ P ≤ D s`, direct summation at every reached +size `s ≥ 2` with constant `C` and precision `1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)`, direct summation +at every reached call that the machine treats as a leaf takes time at most `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space at most `K (n + 1) ^ K`. The root entries take time at most `C (s + L + 1) ^ C 2 ^ (C ⌈s ^ (9/10)⌉)` at every reached leaf @@ -278,7 +322,7 @@ space at most `K (n + 1) ^ K`. The root entries take time at most theorem exists_entryLeaves_le (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) (encList encNat)) (C D : ℕ) : - ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s → + ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s * (Nat.log 2 s + 1) → EntryAdmissible ce C s (r, G) → ∀ y, Reach nativeMk (rootCall (P, modulus G.numEdges, r, G)) y → active y = false → (leafCertificate ce nativeMk).time y ≤ @@ -377,7 +421,8 @@ theorem exists_entryLeaves_le /-- **The resource analysis of the recursion on admissible inputs.** For a native certificate `ce` of the entry procedure and all constants `C` and `D` there are `K` and `s₀` such that on every input -at the modulus admissible at size `s ≥ 2` with constant `C` and with precision `1 ≤ P ≤ D s`, the +at the modulus admissible at size `s ≥ 2` with constant `C` and precision +`1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)`, the root call has a derivation whose nodes, frames and values are at most `F` long, whose leaves take time at most `TL` and space at most `SL`, with `H` open calls, such that `H + F + SL ≤ K (n + 1) ^ K` and `TL ≤ K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` for the input length @@ -385,7 +430,8 @@ time at most `TL` and space at most `SL`, with `H` open calls, such that theorem exists_entryAnalysis (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) (encList encNat)) (C D : ℕ) : - ∃ K s₀ : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s → + ∃ K s₀ : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → + P ≤ D * s * (Nat.log 2 s + 1) → EntryAdmissible ce C s (r, G) → ∃ N H F TL SL : ℕ, (scheme entry nativeMk).Eval @@ -420,23 +466,23 @@ theorem exists_entryAnalysis have hmv := DescribedNetwork.length_numEdges_vertices_le (encT := encT) G rw [← hx, ← hn] at hparts have hmn : m ≤ n := by omega - have hp₀ : P ≤ D * m := hPD.trans (Nat.mul_le_mul_left _ hsm) + have hq₀ : normalizedPrecision m P ≤ D * m := + (normalizedPrecision_le hsm hPD).trans (Nat.mul_le_mul_left _ hsm) -- The derivation. obtain ⟨N, H, he, hN, hH⟩ : ∃ N H, (scheme entry nativeMk).Eval (reachInv entry nativeMk x₀) x₀ (0, value entry nativeMk x₀) N H ∧ (max sA (D + 2) ≤ s → 2 * N - 1 ≤ clock x) ∧ H ≤ m + 1 := by by_cases hs₀ : max sA (D + 2) ≤ s · have hm2 : D + 2 ≤ m := by omega - have hC1 : P / m + 1 ≤ D + 1 := by - have := Nat.div_le_div_right (c := m) hp₀ + have hC1 : normalizedPrecision m P / m + 1 ≤ D + 1 := by + have := Nat.div_le_div_right (c := m) hq₀ rw [Nat.mul_div_cancel _ (by omega)] at this omega - have hB := hsA m (by omega) (P / m + 1) hC1 - have hpC : P ≤ (P / m + 1) * m := - (Nat.lt_div_mul_add (a := P) (b := m) (by omega)).le.trans (by rw [add_mul, one_mul]) + have hB := hsA m (by omega) (normalizedPrecision m P / m + 1) hC1 + have hpC := le_normalizedPrecision_root (P := P) (by omega : 0 < m) obtain ⟨N, H, he, hN, hH⟩ := FastContraction.exists_eval_onSchedule entry hB - (onSchedule_rootCall hg (Nat.le_add_left 1 _) hpC) rfl (by omega) - exact ⟨N, H, he, fun _ => two_mul_sub_one_le_fuel hm2 hp₀ hN, + (onSchedule_rootCall_logPrecision hg (Nat.le_add_left 1 _) hpC) rfl (by omega) + exact ⟨N, H, he, fun _ => two_mul_sub_one_le_fuel hm2 hq₀ hN, hH.trans ((Nat.log_lt_self 2 (by omega)).le.trans (Nat.le_succ _))⟩ · obtain ⟨N, H, he, hH⟩ := FastContraction.exists_eval_reachInv (ev := entry) (mk := nativeMk) (x₀ := x₀) rfl @@ -470,14 +516,15 @@ theorem exists_entryAnalysis /-- **The machine within the interface's bounds.** For a native certificate `ce` of the entry procedure and all constants `C` and `D` there is `K` such that on every input at the modulus -admissible at size `s ≥ 2` with constant `C` and with precision `1 ≤ P ≤ D s`, the machine +admissible at size `s ≥ 2` with constant `C` and precision `1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)`, +the machine `EntryContraction.fastCore ce` takes time at most `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space at most `K (n + 1) ^ K`. Admissible inputs pass the guard; from some size on the clocked run returns the root's value, and below it direct summation costs a constant factor. -/ theorem exists_fastCore_le (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) (encList encNat)) (C D : ℕ) : - ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s → + ∃ K : ℕ, ∀ (s P r : ℕ) (G : DescribedNetwork T), 2 ≤ s → 1 ≤ P → P ≤ D * s * (Nat.log 2 s + 1) → EntryAdmissible ce C s (r, G) → (fastCore ce).time (P, modulus G.numEdges, r, G) ≤ K * ((encInput encT (P, modulus G.numEdges, r, G)).length + 1) ^ K * @@ -562,7 +609,8 @@ theorem exists_fastCore_le /-! ### The contraction algorithm -/ -/-- **Theorem D.1 for entry procedures** ([OpenAI2026ETH], p. 53, at truncation order `1`): for +/-- **Theorem D.1 for entry procedures with logarithmic root precision.** Extending +[OpenAI2026ETH], p. 53, at truncation order `1`: for every native certificate `ce` of an entry procedure, the guarded native recursion with direct summation as the fallback (`EntryContraction.fastCore ce`) is a contraction algorithm for the networks admissible for `ce`, in time `poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial space. -/ @@ -573,6 +621,6 @@ noncomputable def entryContractionAlgorithm core := coreValue entry certificate := fastCore ce val_core := val_coreValue - bound C D := exists_fastCore_le ce C D + bound_logPrecision C D := exists_fastCore_le ce C D end Cslib.Complexity.CountingSimulation diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedBoundedDegree.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedBoundedDegree.lean new file mode 100644 index 0000000000..5c5e6b6734 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedBoundedDegree.lean @@ -0,0 +1,155 @@ +/- +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.CountingSimulation.TruncatedEntry +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TruncatedPolynomialEntries + +/-! +# Polynomial truncation on bounded-degree networks + +For a fixed polynomial-time entry procedure, bounded-degree rows-first networks with linearly +many vertices admit the enlarged truncation bounds at every size at least two. Direct summation +handles the finitely many sizes where the fixed degree exceeds the schedule's degree bound. + +## Main definitions + +* `truncBoundedDegreeCertificate`: a native truncated contraction, correct on every input. + +## Main results + +* `truncBoundedDegreeCertificate_bound`: time `poly(L) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial + space, for precision `O(s log s)` and every fixed polynomial truncation order. +-/ + +public section + +namespace Cslib.Complexity.CountingSimulation + +open Cslib.Circuits.Frontier Cslib.GenericRAM Turing.MultiTapeTM + +variable {T : Type} {encT : T ↪ List Bool} {entry : T → List Bool → List ℕ} + +/-- The fixed finite range in which direct contraction handles a possibly large degree. -/ +@[expose] def truncSmallSize (k : ℕ) : ℕ × ℕ × ℕ × DescribedNetwork T → Bool + | (_, _, _, G) => decide (G.numEdges < (k + 1) ^ 10 + 2) + +theorem truncSmallSize_polyTime (k : ℕ) : + ComputableInPolyTime (truncSmallSize (T := T) k) (encTrunc encT) encBool := by + have hnet : ComputableInPolyTime (fun x : ℕ × ℕ × ℕ × DescribedNetwork T => x.2.2.2) + (encTrunc encT) (DescribedNetwork.encDescribed encT) := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp + (ComputableInPolyTime.snd _ _)) + exact ComputableInPolyTime.unary_lt.comp + ((DescribedNetwork.numEdges_polyTime.comp hnet).prod + (ComputableInPolyTime.const ((k + 1) ^ 10 + 2))) + +/-- A native truncated contraction for bounded-degree applications. Both branches compute the +same contraction on every input; the degree hypothesis is used only for the resource bound. -/ +@[expose] noncomputable def truncBoundedDegreeCertificate + (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) + (encList encNat)) (k : ℕ) : + NativeCertificate + (fun ((P, ℓ, _, G) : ℕ × ℕ × ℕ × DescribedNetwork T) => G.truncContraction entry P ℓ) + (encTrunc encT) (encList encNat) := + (NativeCertificate.branch (NativeCertificate.ofPolyTime (truncSmallSize_polyTime k)) + (truncDirectCertificate entry ce) (truncEntryContractionAlgorithm ce).certificate).congr + fun _ => by split <;> rfl + +/-- Fixed-degree networks with polynomial-time entries satisfy the enlarged truncation bound. +The degree, density and polynomial parameters are fixed before the instance. -/ +theorem truncBoundedDegreeCertificate_bound + (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) + (encList encNat)) (hce : ce.IsPolynomial) (k C D d : ℕ) : + ∃ K : ℕ, ∀ (P ℓ r : ℕ) (G : DescribedNetwork T), 2 ≤ G.numEdges → + G.IsRowsFirstBipartite r → G.vertices.length ≤ C * G.numEdges → + (∀ v ∈ G.vertices, v.1.length ≤ k) → + 1 ≤ P → P ≤ D * G.numEdges * (Nat.log 2 G.numEdges + 1) → + 1 ≤ ℓ → ℓ ≤ D * (G.numEdges + 1) ^ d → + (truncBoundedDegreeCertificate ce k).time (P, ℓ, r, G) ≤ + K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K * + 2 ^ (K * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges)) ∧ + (truncBoundedDegreeCertificate ce k).space (P, ℓ, r, G) ≤ + K * ((encTrunc encT (P, ℓ, r, G)).length + 1) ^ K := by + obtain ⟨C₀, hC₀⟩ := EntryContraction.exists_entryAdmissible_of_isPolynomial hce + obtain ⟨KT, hKT⟩ := (truncEntryContractionAlgorithm ce).bound_polynomial (C + C₀ + 1) D d + obtain ⟨KD, hKD⟩ := truncDirectCertificate_le_of_isPolynomial ce hce + obtain ⟨pg, hpg, -, hgle⟩ := exists_mono_poly_cert (truncSmallSize_polyTime (encT := encT) k) + let M := (k + 1) ^ 10 + 2 + let F : ℕ → ℕ := fun n => pg n + KT * (n + 1) ^ KT + KD * 2 ^ M * (n + 1) ^ KD + have hFp : PolyBounded id F := + ((PolyBounded.of_polynomiallyBounded id hpg).add + ((PolyBounded.const KT).mul ((PolyBounded.self.add (PolyBounded.const 1)).pow KT))).add + ((PolyBounded.const (KD * 2 ^ M)).mul + ((PolyBounded.self.add (PolyBounded.const 1)).pow KD)) + obtain ⟨aF, dF, hF⟩ := hFp.exists_le + refine ⟨aF + dF + KT, fun P ℓ r G hs hrows hv hk hP hPD hℓ hℓD => ?_⟩ + set n := (encTrunc encT (P, ℓ, r, G)).length + set X := 2 ^ (KT * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges)) + have hX : 1 ≤ X := Nat.one_le_two_pow + have hFp' : F n ≤ (aF + dF + KT) * (n + 1) ^ (aF + dF + KT) := + (hF n).trans (Nat.mul_le_mul (by omega) + (Nat.pow_le_pow_right (by omega) (by omega))) + have hX' : X ≤ 2 ^ ((aF + dF + KT) * G.numEdges / Nat.sqrt (Nat.log 2 G.numEdges)) := + Nat.pow_le_pow_right (by norm_num) + (Nat.div_le_div_right (Nat.mul_le_mul_right _ (by omega))) + have hgt : (NativeCertificate.ofPolyTime (truncSmallSize_polyTime k)).time (P, ℓ, r, G) ≤ + pg n := (hgle _).1 + have hgs : (NativeCertificate.ofPolyTime (truncSmallSize_polyTime k)).space (P, ℓ, r, G) ≤ + pg n := (hgle _).2 + have hfast (hg : truncSmallSize k (P, ℓ, r, G) = false) : + (truncEntryContractionAlgorithm ce).certificate.time (P, ℓ, r, G) ≤ + KT * (n + 1) ^ KT * X ∧ + (truncEntryContractionAlgorithm ce).certificate.space (P, ℓ, r, G) ≤ + KT * (n + 1) ^ KT := by + have hM : (k + 1) ^ 10 + 2 ≤ G.numEdges := by simpa [truncSmallSize] using hg + have hdegree : k ≤ ceilNineTenths G.numEdges := by + apply (le_ceilNineTenths_iff (by omega)).2 + have h1 : (k - 1) ^ 10 ≤ (k + 1) ^ 10 := Nat.pow_le_pow_left (by omega) _ + have h2 : G.numEdges ≤ G.numEdges ^ 9 := by + calc G.numEdges = G.numEdges ^ 1 := (pow_one _).symm + _ ≤ G.numEdges ^ 9 := Nat.pow_le_pow_right (by omega) (by norm_num) + omega + have hA : TruncEntryAdmissible ce (C + C₀ + 1) G.numEdges (r, G) := + ⟨hC₀ (C + C₀ + 1) G.numEdges r G (by omega) hrows le_rfl + (Nat.le_mul_of_pos_left _ (by omega)) (fun v hmem => (hk v hmem).trans hdegree), + hv.trans (Nat.mul_le_mul_right _ (by omega))⟩ + exact hKT G.numEdges P ℓ r G hs hP hPD hℓ hℓD hA + have hsmall (hg : truncSmallSize k (P, ℓ, r, G) = true) : + (truncDirectCertificate entry ce).time (P, ℓ, r, G) ≤ KD * 2 ^ M * (n + 1) ^ KD ∧ + (truncDirectCertificate entry ce).space (P, ℓ, r, G) ≤ KD * 2 ^ M * (n + 1) ^ KD := by + have hM : G.numEdges < M := by simpa [truncSmallSize, M] using hg + have hpow : 2 ^ G.numEdges ≤ 2 ^ M := Nat.pow_le_pow_right (by norm_num) hM.le + obtain ⟨hT, hS⟩ := hKD P ℓ r G + constructor + · calc _ ≤ KD * (n + 1) ^ KD * 2 ^ M := hT.trans (Nat.mul_le_mul_left _ hpow) + _ = _ := by ring + · calc _ ≤ KD * (n + 1) ^ KD := hS + _ ≤ KD * (n + 1) ^ KD * 2 ^ M := Nat.le_mul_of_pos_right _ Nat.one_le_two_pow + _ = _ := by ring + have htime : (truncBoundedDegreeCertificate ce k).time (P, ℓ, r, G) ≤ F n * X := by + simp only [truncBoundedDegreeCertificate, NativeCertificate.congr, + NativeCertificate.branch_time] + have hpgX : pg n ≤ pg n * X := Nat.le_mul_of_pos_right _ hX + have hsX : KD * 2 ^ M * (n + 1) ^ KD ≤ KD * 2 ^ M * (n + 1) ^ KD * X := + Nat.le_mul_of_pos_right _ hX + have hsum : F n * X = pg n * X + KT * (n + 1) ^ KT * X + + KD * 2 ^ M * (n + 1) ^ KD * X := by simp only [F]; ring + split + · next hg => have := (hsmall hg).1; omega + · next hg => have := (hfast (by simpa using hg)).1; omega + have hspace : (truncBoundedDegreeCertificate ce k).space (P, ℓ, r, G) ≤ F n := by + simp only [truncBoundedDegreeCertificate, NativeCertificate.congr, + NativeCertificate.branch_space] + dsimp only [F] + split + · next hg => have := (hsmall hg).2; omega + · next hg => have := (hfast (by simpa using hg)).2; omega + exact ⟨htime.trans (Nat.mul_le_mul hFp' hX'), hspace.trans hFp'⟩ + +end Cslib.Complexity.CountingSimulation diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedEntry.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedEntry.lean index 6a849014bc..b2d01e8f57 100644 --- a/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedEntry.lean +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/TruncatedEntry.lean @@ -18,7 +18,8 @@ The instance of the interface of Theorem D.1 at truncation order `ℓ` computed by procedures: for every native certificate `ce` of an entry procedure, `CountingSimulation.truncEntryContractionAlgorithm ce` computes the contraction in `B_{P,ℓ} = (ℤ / 2 ^ P)[V] / (V ^ ℓ)` of the networks admissible for `ce` with at most `C s` vertices -(`Cslib.Circuits.Frontier.TruncEntryAdmissible`), for `1 ≤ P, ℓ ≤ D s`, in time +(`Cslib.Circuits.Frontier.TruncEntryAdmissible`), for +`1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)` and `1 ≤ ℓ ≤ D (s + 1) ^ d`, for fixed `D`, `d`, in time `poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial space. It combines the reduction of truncation order `ℓ` to `A_P = (ℤ / 2 ^ P)[U] / (f)` @@ -26,7 +27,8 @@ It combines the reduction of truncation order `ℓ` to `A_P = (ℤ / 2 ^ P)[U] / for entry procedures (`CountingSimulation.entryContractionAlgorithm`), applied to the specialized entries. -This is Theorem D.1 of [OpenAI2026ETH] (p. 53) with one narrowing: the theorem asks for at most +This includes the linear precision and truncation range of Theorem D.1 of [OpenAI2026ETH] +(p. 53), and proves the stated larger ranges. There is one narrowing: the theorem asks for at most polynomially many vertices in `s`, and admissibility here asks for at most `C s`. The networks of Lemma D.2 (p. 56) and of the fixed-density bound (§D.2, p. 54) have that many. As for all the interfaces of this development, admissibility also asks for at least `s` edges, which a caller @@ -55,7 +57,8 @@ networks with at most `C s` vertices): for every native certificate `ce` of an e the reduction of truncation order `ℓ` to `A_P` around the contraction algorithm for the specialized entries is a truncated contraction algorithm for the networks admissible for `ce` with at most `C s` vertices. It computes the contraction in `B_{P,ℓ}` from `(P, ℓ, r, G)` on every -input, and on admissible inputs with `1 ≤ P, ℓ ≤ D s` in time `poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and +input, and on admissible inputs with `1 ≤ P ≤ D s (⌊log₂ s⌋ + 1)` and +`1 ≤ ℓ ≤ D (s + 1) ^ d`, for fixed `D`, `d`, in time `poly(n) 2 ^ O(s / √⌊log₂ s⌋)` and polynomial space. -/ noncomputable def truncEntryContractionAlgorithm (ce : NativeCertificate (fun q : T × List Bool => entry q.1 q.2) (encPair encT encBits) diff --git a/Complexity/Computability/Complexity/FineGrained/BinaryWeightedCounting.lean b/Complexity/Computability/Complexity/FineGrained/BinaryWeightedCounting.lean new file mode 100644 index 0000000000..8b069e96eb --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/BinaryWeightedCounting.lean @@ -0,0 +1,302 @@ +/- +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.BinaryWeights +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CSPBinaryWeights +public import Complexity.Computability.Complexity.FineGrained.ContractionPartitionFunction +import Complexity.Logics.Propositional.CSP.Vars +import Mathlib.Data.Fintype.BigOperators + +/-! +# Native weighted counting with polynomially many weight bits + +For fixed arity and polynomial bit-budget parameters, one native machine computes the partition +function of a Boolean CSP with input-dependent natural weights. The reduction constructs its +existing constraint network and applies exact binary-weight tensor contraction. The weight +magnitudes have no constant bound: only the total encoded length of the network's tables must +be polynomial in the number of occurrences. + +The counter checks the arity, excludes empty scopes, and checks this bit budget before invoking +contraction. It returns one on the empty instance and zero on other rejected inputs. Thus its +resource certificate holds on every encoded input. On the promised inputs the answer is the +partition function, in polynomial space and time +`poly(L) * 2 ^ O(N / √log(N + 2))`, where `N` counts occurrences, `n` counts variables, and `L` +is the complete encoded input length. Bounded occurrence gives the corresponding variable bound. + +## Main definitions + +* `BinaryWeightedCounting.guard`: the scope and polynomial table-bit checks. +* `BinaryWeightedCounting.count`: the total counting function. +* `BinaryWeightedCounting.certificate`: its ordinary native machine certificate. + +## Main results + +* `BinaryWeightedCounting.count_eq`: correctness on the promised inputs. +* `BinaryWeightedCounting.exists_partitionFunctionCounter`: the uniform occurrence bound. +* `BinaryWeightedCounting.exists_partitionFunctionCounter_boundedDegree`: the variable bound. +* `BinaryWeightedCounting.exists_partitionFunctionCounter_of_weightBits`: the same counter under + a polynomial bit-length bound on each relevant local weight, with the network budget derived. +-/ + +public section + +namespace Cslib.Complexity.BinaryWeightedCounting + +open Cslib.Logic.PL Cslib.Circuits.Frontier Cslib.Circuits.Frontier.ClauseNetwork + Turing.MultiTapeTM Cslib.GenericRAM ComputableInPolyTime + +variable {Rel : Type} + +/-- The existing scope check together with the polynomial bound on all table bits. -/ +@[expose] def guard (k D d : ℕ) (wt : Rel → List Bool → ℕ) (F : CSP Rel ℕ) : Bool := + ContractionPartitionFunction.guard k F && + decide ((cspNetwork k wt F).2.binaryTableBound ≤ + D * ((cspNetwork k wt F).2.numEdges + 1) ^ d) + +/-- Exact contraction on the guarded instances, with one on the empty instance and zero on +other rejected instances. -/ +@[expose] def count (k D d : ℕ) (wt : Rel → List Bool → ℕ) (F : CSP Rel ℕ) : ℕ := + if guard k D d wt F then (cspNetwork k wt F).2.contraction + else ContractionPartitionFunction.fallback F + +theorem guard_eq_true_iff {k D d : ℕ} {wt : Rel → List Bool → ℕ} {F : CSP Rel ℕ} : + guard k D d wt F = true ↔ F ≠ [] ∧ + (∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) ∧ + (cspNetwork k wt F).2.binaryTableBound ≤ + D * ((cspNetwork k wt F).2.numEdges + 1) ^ d := by + simp only [guard, Bool.and_eq_true, decide_eq_true_eq, + ContractionPartitionFunction.guard_eq_true_iff, and_assoc] + +/-- The empty input always has answer one, independently of the bit budget. -/ +@[simp] theorem count_nil (k D d : ℕ) (wt : Rel → List Bool → ℕ) : + count k D d wt [] = 1 := by + simp [count, guard, ContractionPartitionFunction.guard, ContractionPartitionFunction.fallback] + +/-- The counter computes the partition function whenever its arity and bit-budget promises hold. -/ +theorem count_eq {k D d : ℕ} {wt : Rel → List Bool → ℕ} {F : CSP Rel ℕ} + (hk : ∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) + (hB : (cspNetwork k wt F).2.binaryTableBound ≤ + D * ((cspNetwork k wt F).2.numEdges + 1) ^ d) : + count k D d wt F = F.partitionFunction wt := by + by_cases hF : F = [] + · subst F + rw [count_nil, CSP.partitionFunction] + change 1 = ∑ _ : (CSP.vars ([] : CSP Rel ℕ)).toFinset → Bool, (1 : ℕ) + rw [Finset.sum_const, Finset.card_univ] + have hc : Fintype.card ((CSP.vars ([] : CSP Rel ℕ)).toFinset → Bool) = 1 := by + rw [Fintype.card_fun, Fintype.card_coe] + simp [CSP.vars] + rw [hc, one_nsmul] + · rw [count, ite_eq_left (guard_eq_true_iff.mpr ⟨hF, hk, hB⟩), + contraction_cspNetwork (fun c hc => (hk c hc).2)] + +variable (encRel : Rel ↪ List Bool) (k D d : ℕ) (wt : Rel → List Bool → ℕ) + (hwt : ∀ w : List Bool, w.length ≤ k → + ComputableInPolyTime (fun R : Rel => wt R w) encRel encNat) + +include hwt + +/-- The scope and bit-budget checks are polynomial-time for the fixed parameters. -/ +theorem guard_polyTime : + ComputableInPolyTime (guard k D d wt) (encCSP encRel encNat) encBool := by + have hnet := computableInPolyTime_cspNetwork encRel k wt hwt + have hG := hnet.snd' + have hB := ExplicitTensorNetwork.binaryTableBound_polyTime.comp hG + have hM := DescribedNetwork.numEdges_polyTime.comp hG + have hbudget := unary_mul' (const D) + ((unary_pow d).comp (unary_add' hM (const 1))) + exact bool_and.comp ((ContractionPartitionFunction.computableInPolyTime_guard encRel k).prod + (binary_le' (unary_to_binary.comp hB) (unary_to_binary.comp hbudget))) + +/-- Native construction of the constraint network followed by exact binary-weight contraction. -/ +noncomputable def coreCertificate : + NativeCertificate (fun F : CSP Rel ℕ => (cspNetwork k wt F).2.contraction) + (encCSP encRel encNat) encNat := + (BinaryWeights.certificate (max k 4)).comp + (NativeCertificate.ofPolyTime (computableInPolyTime_cspNetwork encRel k wt hwt)) + +/-- The guarded native counter. Both the guard and the rejected-input answer have polynomial +time certificates in the original input encoding. -/ +noncomputable def certificate : + NativeCertificate (count k D d wt) (encCSP encRel encNat) encNat := + (NativeCertificate.branch (NativeCertificate.ofPolyTime (guard_polyTime encRel k D d wt hwt)) + (coreCertificate encRel k wt hwt) + (NativeCertificate.ofPolyTime + (ContractionPartitionFunction.computableInPolyTime_fallback encRel))).congr fun _ => rfl + +private theorem coreCertificate_bound : + ∃ A H : ℕ, ∀ F : CSP Rel ℕ, guard k D d wt F = true → + (coreCertificate encRel k wt hwt).time F ≤ + H * ((encCSP encRel encNat F).length + 1) ^ H * + 2 ^ (A * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2))) ∧ + (coreCertificate encRel k wt hwt).space F ≤ + H * ((encCSP encRel encNat F).length + 1) ^ H := by + obtain ⟨K, hK⟩ := BinaryWeights.certificate_bound (max k 4) 1 D d + have hnet := computableInPolyTime_cspNetwork encRel k wt hwt + obtain ⟨H, hH⟩ := NativeCertificate.exists_polynomial_overhead + (BinaryWeights.certificate (max k 4)) hnet (id' (encIn := encNat)) K + refine ⟨3 * K, H, fun F hg => ?_⟩ + obtain ⟨hne, hcl, hB⟩ := guard_eq_true_iff.mp hg + have hm := CSP.length_le_length_vars fun c hc => (hcl c hc).1 + have h1 : 1 ≤ F.length := List.length_pos_iff.mpr hne + have hN : 1 ≤ F.vars.length := h1.trans hm + have hs : 2 ≤ (cspNetwork k wt F).2.numEdges := by rw [numEdges_cspNetwork]; omega + have hv : (cspNetwork k wt F).2.vertices.length ≤ 1 * (cspNetwork k wt F).2.numEdges := by + rw [length_vertices_cspNetwork, numEdges_cspNetwork] + omega + obtain ⟨ht, hspace⟩ := hK (cspNetwork k wt F).1 (cspNetwork k wt F).2 hs + (isRowsFirstBipartite_cspNetwork k wt F) hv + (maxDegree_cspNetwork_le fun c hc => (hcl c hc).2) hB + have hexp := ContractionCounting.exponent_le_occurrences (K := K) hN + rw [numEdges_cspNetwork] at ht + have htime := ht.trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by decide) hexp)) + obtain ⟨hT, hS⟩ := hH F + (2 ^ (3 * K * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2)))) + Nat.one_le_two_pow htime hspace + exact ⟨(Nat.le_add_right _ _).trans hT, (Nat.le_add_right _ _).trans hS⟩ + +/-- One fixed occurrence exponent and polynomial bound control the total guarded machine, +including inputs rejected by either check. -/ +theorem certificate_bound : + ∃ A C e : ℕ, ∀ F : CSP Rel ℕ, + (certificate encRel k D d wt hwt).time F ≤ + C * ((encCSP encRel encNat F).length + 1) ^ e * + 2 ^ (A * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2))) ∧ + (certificate encRel k D d wt hwt).space F ≤ + C * ((encCSP encRel encNat F).length + 1) ^ e := by + obtain ⟨A, H, hcore⟩ := coreCertificate_bound encRel k D d wt hwt + obtain ⟨p, hp, -, hguard⟩ := exists_mono_poly_cert (guard_polyTime encRel k D d wt hwt) + obtain ⟨q, hq, -, hfallback⟩ := exists_mono_poly_cert + (ContractionPartitionFunction.computableInPolyTime_fallback encRel) + let Q : ℕ → ℕ := fun n => p n + q n + H * (n + 1) ^ H + have hQ : PolyBounded id Q := + ((PolyBounded.of_polynomiallyBounded id hp).add + (PolyBounded.of_polynomiallyBounded id hq)).add + ((PolyBounded.const H).mul ((PolyBounded.self.add (PolyBounded.const 1)).pow H)) + obtain ⟨C, e, hCe⟩ := hQ.exists_le + refine ⟨A, C, e, fun F => ?_⟩ + let n := (encCSP encRel encNat F).length + let X := 2 ^ (A * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2))) + have hX : 1 ≤ X := Nat.one_le_two_pow + have hpX : p n ≤ p n * X := Nat.le_mul_of_pos_right _ hX + have hqX : q n ≤ q n * X := Nat.le_mul_of_pos_right _ hX + have htime : (certificate encRel k D d wt hwt).time F ≤ Q n * X := by + change (NativeCertificate.ofPolyTime (guard_polyTime encRel k D d wt hwt)).time F + + (if guard k D d wt F then (coreCertificate encRel k wt hwt).time F else + (NativeCertificate.ofPolyTime + (ContractionPartitionFunction.computableInPolyTime_fallback encRel)).time F) ≤ _ + split + · next hg => + have hc := (hcore F hg).1 + calc _ ≤ p n * X + (H * (n + 1) ^ H) * X := + Nat.add_le_add ((hguard F).1.trans hpX) hc + _ = (p n + H * (n + 1) ^ H) * X := by ring + _ ≤ Q n * X := Nat.mul_le_mul_right _ (by dsimp only [Q]; omega) + · calc _ ≤ p n * X + q n * X := + Nat.add_le_add ((hguard F).1.trans hpX) ((hfallback F).1.trans hqX) + _ = (p n + q n) * X := by ring + _ ≤ Q n * X := Nat.mul_le_mul_right _ (by dsimp only [Q]; omega) + have hspace : (certificate encRel k D d wt hwt).space F ≤ Q n := by + change (NativeCertificate.ofPolyTime (guard_polyTime encRel k D d wt hwt)).space F + + (if guard k D d wt F then (coreCertificate encRel k wt hwt).space F else + (NativeCertificate.ofPolyTime + (ContractionPartitionFunction.computableInPolyTime_fallback encRel)).space F) ≤ _ + split + · next hg => + calc _ ≤ p n + H * (n + 1) ^ H := Nat.add_le_add (hguard F).2 (hcore F hg).2 + _ ≤ Q n := by dsimp only [Q]; omega + · calc _ ≤ p n + q n := Nat.add_le_add (hguard F).2 (hfallback F).2 + _ ≤ Q n := by dsimp only [Q]; omega + exact ⟨htime.trans (Nat.mul_le_mul_right _ (hCe n)), hspace.trans (hCe n)⟩ + +/-- Exact natural weighted counting with a fixed polynomial table-bit budget. The relation +symbol, including its potentially large numeric weights, is part of the input encoding. -/ +theorem exists_partitionFunctionCounter : + ∃ (counter : CSP Rel ℕ → ℕ) (A e b : ℕ), + (∀ F : CSP Rel ℕ, (∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) → + (cspNetwork k wt F).2.binaryTableBound ≤ + D * ((cspNetwork k wt F).2.numEdges + 1) ^ d → + counter F = F.partitionFunction wt) ∧ + ComputableInO counter (encCSP encRel encNat) encNat + (fun F => 2 ^ (A * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2))) * + ((encCSP encRel encNat F).length + 1) ^ e) + (fun F => ((encCSP encRel encNat F).length + 1) ^ b) := by + obtain ⟨A, C, e, h⟩ := certificate_bound encRel k D d wt hwt + refine ⟨count k D d wt, A, e, e, fun F hk hB => count_eq hk hB, + (certificate encRel k D d wt hwt).computes.mono C (fun F => ?_) (fun F => ?_)⟩ + · have hT := (h F).1 + calc _ ≤ C * (2 ^ (A * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2))) * + ((encCSP encRel encNat F).length + 1) ^ e) := by + simpa only [mul_assoc, mul_comm, mul_left_comm] using hT + _ ≤ _ := Nat.mul_le_mul_left _ (Nat.le_succ _) + · exact (h F).2.trans (Nat.mul_le_mul_left _ (Nat.le_succ _)) + +/-- Bounded variable occurrence converts the occurrence numerator to a variable numerator. +The machine and polynomial exponents are selected before the occurrence bound. -/ +theorem exists_partitionFunctionCounter_boundedDegree : + ∃ (counter : CSP Rel ℕ → ℕ) (T S : CSP Rel ℕ → ℕ) (e b : ℕ), + (∀ F : CSP Rel ℕ, (∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) → + (cspNetwork k wt F).2.binaryTableBound ≤ + D * ((cspNetwork k wt F).2.numEdges + 1) ^ d → + counter F = F.partitionFunction wt) ∧ + ComputableInO counter (encCSP encRel encNat) encNat T S ∧ + (∀ F, S F = ((encCSP encRel encNat F).length + 1) ^ b) ∧ + ∀ Δ : ℕ, ∃ A : ℕ, ∀ F : CSP Rel ℕ, (∀ v, F.vars.count v ≤ Δ) → + T F ≤ 2 ^ (A * (F.numVars + 1) / Nat.sqrt (Nat.log 2 (F.numVars + 2))) * + ((encCSP encRel encNat F).length + 1) ^ e := by + obtain ⟨counter, A, e, b, hcorrect, hc⟩ := + exists_partitionFunctionCounter encRel k D d wt hwt + refine ⟨counter, _, _, e, b, hcorrect, hc, fun _ => rfl, + fun Δ => ⟨A * (Δ + 1), fun F hF => ?_⟩⟩ + have hn : F.numVars ≤ F.vars.length := List.toFinset_card_le _ + refine Nat.mul_le_mul_right _ (Nat.pow_le_pow_right (by decide) + (Nat.div_le_div ?_ (Nat.sqrt_le_sqrt (Nat.log_mono_right (by omega))) ?_)) + · have h := CSP.length_vars_le_mul_numVars hF + have hN : F.vars.length + 1 ≤ (Δ + 1) * (F.numVars + 1) := by nlinarith + calc A * (F.vars.length + 1) ≤ A * ((Δ + 1) * (F.numVars + 1)) := Nat.mul_le_mul_left _ hN + _ = A * (Δ + 1) * (F.numVars + 1) := by ring + · exact Nat.sqrt_eq_zero.not.mpr (Nat.log_pos (by decide) (by omega)).ne' + +/-- Polynomially many bits per local weight suffice: the total network bit budget follows with +the explicit coefficient `3 * 2 ^ (max k 4) + 1` and exponent `d + 1`. The natural weight +magnitudes may be exponential in `(N + 1) ^ d`. -/ +theorem exists_partitionFunctionCounter_of_weightBits : + ∃ (counter : CSP Rel ℕ → ℕ) (A e b : ℕ), + (∀ F : CSP Rel ℕ, (∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) → + (∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → + (wt c.1 w).size ≤ (F.vars.length + 1) ^ d) → + counter F = F.partitionFunction wt) ∧ + ComputableInO counter (encCSP encRel encNat) encNat + (fun F => 2 ^ (A * (F.vars.length + 1) / Nat.sqrt (Nat.log 2 (F.vars.length + 2))) * + ((encCSP encRel encNat F).length + 1) ^ e) + (fun F => ((encCSP encRel encNat F).length + 1) ^ b) := by + obtain ⟨counter, A, e, b, hcorrect, hc⟩ := exists_partitionFunctionCounter encRel k + (3 * 2 ^ (max k 4) + 1) (d + 1) wt hwt + exact ⟨counter, A, e, b, fun F hk hw => + hcorrect F hk (binaryTableBound_cspNetwork_le d hk hw), hc⟩ + +/-- The polynomial-bit counter has the variable bound on bounded-occurrence inputs. Its machine +and polynomial exponents are independent of the occurrence bound `Δ`. -/ +theorem exists_partitionFunctionCounter_boundedDegree_of_weightBits : + ∃ (counter : CSP Rel ℕ → ℕ) (T S : CSP Rel ℕ → ℕ) (e b : ℕ), + (∀ F : CSP Rel ℕ, (∀ c ∈ F, 1 ≤ c.2.length ∧ c.2.length ≤ k) → + (∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → + (wt c.1 w).size ≤ (F.vars.length + 1) ^ d) → + counter F = F.partitionFunction wt) ∧ + ComputableInO counter (encCSP encRel encNat) encNat T S ∧ + (∀ F, S F = ((encCSP encRel encNat F).length + 1) ^ b) ∧ + ∀ Δ : ℕ, ∃ A : ℕ, ∀ F : CSP Rel ℕ, (∀ v, F.vars.count v ≤ Δ) → + T F ≤ 2 ^ (A * (F.numVars + 1) / Nat.sqrt (Nat.log 2 (F.numVars + 2))) * + ((encCSP encRel encNat F).length + 1) ^ e := by + obtain ⟨counter, T, S, e, b, hcorrect, hc, hS, hT⟩ := + exists_partitionFunctionCounter_boundedDegree encRel k (3 * 2 ^ (max k 4) + 1) (d + 1) wt hwt + exact ⟨counter, T, S, e, b, fun F hk hw => + hcorrect F hk (binaryTableBound_cspNetwork_le d hk hw), hc, hS, hT⟩ + +end Cslib.Complexity.BinaryWeightedCounting diff --git a/Complexity/Computability/Complexity/FineGrained/MaxSAT.lean b/Complexity/Computability/Complexity/FineGrained/MaxSAT.lean new file mode 100644 index 0000000000..7dfc33637f --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/MaxSAT.lean @@ -0,0 +1,216 @@ +/- +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.Boolean.PopulationCount +public import Complexity.Computability.Circuit.Balanced +public import Complexity.Computability.Circuit.SatCount +public import Complexity.Computability.Complexity.Problems.SAT +import Complexity.Foundations.Data.BitString.Weight +import Mathlib.Algebra.BigOperators.Fin + +/-! +# Exact MAX-SAT threshold counts + +An instance declares its number of Boolean variables and lists its clauses. Its score is the +number of satisfied clauses, counting repeated clauses with their multiplicity. The compiled +circuit has precisely those declared inputs, so every assignment contributes exactly once. +No fresh guessed bits or auxiliary-assignment multiplicities enter the threshold count. + +The circuit evaluates every clause and compares their population count with the requested +cutoff. It has at most `478 * (literal occurrences + clauses + 1)` gates. This bound depends on +the formula's actual occurrence count: a variable-count bound requires a density hypothesis. + +Variable references outside the declared range read false. This convention makes the compiler +total; for well-formed instances all references are below the declared variable count. + +## Main results + +* `MaxSAT.eval_thresholdCircuit`: the compiler accepts exactly the assignments meeting the cutoff. +* `MaxSAT.satCount_thresholdCircuit`: exact preservation of the assignment count. +* `MaxSAT.size_thresholdCircuit_le`: an explicit linear gate bound. + +## References + +* [OpenAI2026ETH], proof of Corollary 1.2: exact MAX-SAT on sparse sampled instances via a + linear-size threshold circuit. This module makes the count-preserving reduction explicit. +-/ + +@[expose] public section + +namespace Cslib.Complexity.MaxSAT + +open Cslib.Logic.PL Cslib.Circuits Cslib.Circuits.Boolean Turing.MultiTapeTM + +/-- The declared variable count and the list of clauses. -/ +abbrev Instance := ℕ × CNF ℕ + +/-- Unary declared input count followed by the usual binary-indexed formula encoding. -/ +def encInstance : Instance ↪ List Bool := encPair encUnary encCNF + +/-- A threshold query supplies the instance and a unary score cutoff. -/ +abbrev Query := Instance × ℕ + +/-- Encoding of a complete threshold-count query. -/ +def encQuery : Query ↪ List Bool := encPair encInstance encUnary + +/-- Read an assignment on declared variables; undeclared references default to false. -/ +def assignment (n : ℕ) (x : BitString n) (v : ℕ) : Bool := + if hv : v < n then x ⟨v, hv⟩ else false + +/-- Number of satisfied clauses, with the original list multiplicities. -/ +def score (F : Instance) (x : BitString F.1) : ℕ := + F.2.countP (Clause.eval (assignment F.1 x)) + +/-- Number of assignments satisfying at least the cutoff many clauses. -/ +def thresholdCount (F : Instance) (q : ℕ) : ℕ := + (Finset.univ.filter (fun x : BitString F.1 => q ≤ score F x)).card + +/-- Structural formula size used in the exponent: literal occurrences plus clauses. -/ +def workSize (F : Instance) : ℕ := F.2.vars.length + F.2.length + +/-- The score never exceeds the number of clauses. -/ +theorem score_le (F : Instance) (x : BitString F.1) : score F x ≤ F.2.length := + List.countP_le_length + +/-- A positive threshold count is precisely the existence of a qualifying assignment. -/ +theorem thresholdCount_pos_iff (F : Instance) (q : ℕ) : + 0 < thresholdCount F q ↔ ∃ x : BitString F.1, q ≤ score F x := by + simp [thresholdCount, Finset.card_pos, Finset.filter_nonempty_iff] + +/-- A declared literal needs at most one gate, including the default for invalid references. -/ +def literalCircuit (n : ℕ) (l : Literal ℕ) : Circuit signature n 1 := + if h : l.1 < n then + if l.2 then Circuit.wiring signature (fun _ => ⟨l.1, h⟩) + else Circuit.oneGate .not (fun _ => ⟨l.1, h⟩) + else Circuit.oneGate (.const (!l.2)) Fin.elim0 + +/-- Literal compilation respects polarity and the declared-variable convention. -/ +@[simp] theorem eval_literalCircuit (n : ℕ) (l : Literal ℕ) (x : BitString n) : + (literalCircuit n l).eval interpretation x 0 = Literal.eval (assignment n x) l := by + rcases l with ⟨v, b⟩ + unfold literalCircuit Literal.eval assignment + split_ifs <;> cases b <;> simp_all [interpretation] + +/-- A single literal costs at most one gate. -/ +theorem size_literalCircuit_le (n : ℕ) (l : Literal ℕ) : (literalCircuit n l).size ≤ 1 := by + unfold literalCircuit + split_ifs <;> simp + +/-- A two-input disjunction used to combine clause literals. -/ +def clauseJoin : Circuit signature (1 + 1) 1 := Circuit.oneGate .or id + +/-- Evaluate one clause by a balanced disjunction of its literal circuits. -/ +def clauseCircuit (n : ℕ) (c : Clause ℕ) : Circuit signature n 1 := + Circuit.balanced clauseJoin (Circuit.oneGate (.const false) Fin.elim0) + (c.map (literalCircuit n)) + +/-- The clause compiler returns its disjunction, including false for an empty clause. -/ +@[simp] theorem eval_clauseCircuit (n : ℕ) (c : Clause ℕ) (x : BitString n) : + (clauseCircuit n c).eval interpretation x 0 = Clause.eval (assignment n x) c := by + have h := List.balancedFold_hom (fun C : Circuit signature n 1 => C.eval interpretation x 0) + (op' := (· || ·)) (op := fun C D => clauseJoin.comp (C.append D)) + (e := Circuit.oneGate (.const false) Fin.elim0) (f := id) + (fun C D => by + simp only [clauseJoin, Circuit.eval_comp, Circuit.eval_append, Circuit.eval_oneGate, + interpretation] + rfl) + (c.map (literalCircuit n)) + change (clauseCircuit n c).eval interpretation x 0 = _ at h + rw [h] + change (c.map (literalCircuit n)).balancedFold (· || ·) false + (fun C => C.eval interpretation x 0) = _ + rw [List.balancedFold_eq_foldr] + simp only [List.map_map, Function.comp_def, eval_literalCircuit] + clear h + induction c with + | nil => rfl + | cons l c ih => + simpa only [List.map_cons, List.foldr_cons, Clause.eval_cons] using + congrArg (fun b => Literal.eval (assignment n x) l || b) ih + +/-- A balanced clause uses at most two gates per literal and one empty-clause fallback. -/ +theorem size_clauseCircuit_le (n : ℕ) (c : Clause ℕ) : + (clauseCircuit n c).size ≤ 2 * c.length + 1 := by + have hsum : ((c.map (literalCircuit n)).map Circuit.size).sum ≤ c.length := by + calc _ ≤ (c.map (fun _ => 1)).sum := by + rw [List.map_map] + exact List.sum_le_sum (fun l _ => size_literalCircuit_le n l) + _ = c.length := by simp + have h := Circuit.size_balanced_le clauseJoin + (Circuit.oneGate (σ := signature) (n := n) (.const false) Fin.elim0) + (c.map (literalCircuit n)) + simp only [clauseJoin, Circuit.size_oneGate, List.length_map, Nat.mul_one] at h + exact h.trans (by omega) + +/-- Compute all clause values on the original declared inputs. -/ +def scoreCircuit (F : Instance) : Circuit signature F.1 F.2.length := + Circuit.pi (fun i => clauseCircuit F.1 F.2[i]) + +/-- The clause-value word has exactly the formula's score as its Hamming weight. -/ +theorem weight_eval_scoreCircuit (F : Instance) (x : BitString F.1) : + BitString.weight ((scoreCircuit F).eval interpretation x) = score F x := by + rw [← BitString.count_true_ofFn] + simp only [scoreCircuit, Circuit.eval_pi, eval_clauseCircuit] + have he : List.ofFn (fun i : Fin F.2.length => Clause.eval (assignment F.1 x) F.2[i]) = + F.2.map (Clause.eval (assignment F.1 x)) := by + apply List.ext_getElem (by simp) + intro i hi hj + simp + rw [he, List.count_eq_countP] + simp [score, List.countP_map, Function.comp_def] + +/-- All clause values together have linear gate count. -/ +theorem size_scoreCircuit_le (F : Instance) : + (scoreCircuit F).size ≤ 2 * F.2.vars.length + F.2.length := by + rw [scoreCircuit, Circuit.size_pi] + calc _ ≤ ∑ i : Fin F.2.length, (2 * F.2[i].length + 1) := + Finset.sum_le_sum (fun _ _ => size_clauseCircuit_le _ _) + _ = 2 * F.2.vars.length + F.2.length := by + have he : (∑ i : Fin F.2.length, F.2[i].length) = (F.2.map List.length).sum := by + rw [← List.sum_ofFn] + congr 1 + apply List.ext_getElem (by simp) + intro i hi hj + simp + simp only [CNF.length_vars, Finset.sum_add_distrib, ← Finset.mul_sum, he, + Finset.sum_const, Finset.card_univ, Fintype.card_fin, smul_eq_mul, Nat.mul_one] + +/-- The full threshold circuit, with no fresh guessed inputs. -/ +def thresholdCircuit (F : Instance) (q : ℕ) : Circuit signature F.1 1 := + (linearThresholdCircuit F.2.length q).comp (scoreCircuit F) + +/-- Threshold compilation accepts exactly the assignments whose score meets the cutoff. -/ +@[simp] theorem eval_thresholdCircuit (F : Instance) (q : ℕ) (x : BitString F.1) : + (thresholdCircuit F q).eval interpretation x 0 = decide (q ≤ score F x) := by + rw [thresholdCircuit, Circuit.eval_comp, eval_linearThresholdCircuit] + change decide (q ≤ BitString.weight ((scoreCircuit F).eval interpretation x)) = _ + rw [weight_eval_scoreCircuit] + +/-- Exact assignment-count preservation; every declared input assignment contributes once. -/ +theorem satCount_thresholdCircuit (F : Instance) (q : ℕ) : + (thresholdCircuit F q).satCount interpretation = thresholdCount F q := by + simp only [Circuit.satCount, eval_thresholdCircuit, decide_eq_true_eq, thresholdCount] + +/-- All gates, including shared constants and empty-clause defaults, satisfy a linear bound. -/ +theorem size_thresholdCircuit_le (F : Instance) (q : ℕ) : + (thresholdCircuit F q).size ≤ 478 * (workSize F + 1) := by + rw [thresholdCircuit, Circuit.size_comp] + have hc := size_scoreCircuit_le F + have ht := size_linearThresholdCircuit_le F.2.length q + dsimp only [workSize] + omega + +/-- On width-`k` formulas, the structural size is at most `(k+1)` times the clause count. -/ +theorem workSize_le_of_width (k : ℕ) (F : Instance) + (h : ∀ c ∈ F.2, c.length ≤ k) : workSize F ≤ (k + 1) * F.2.length := by + have := CNF.length_vars_le_mul_length h + dsimp only [workSize] + nlinarith + +end Cslib.Complexity.MaxSAT diff --git a/Complexity/Computability/Complexity/FineGrained/MaxSAT/Histogram.lean b/Complexity/Computability/Complexity/FineGrained/MaxSAT/Histogram.lean new file mode 100644 index 0000000000..7f30369894 --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/MaxSAT/Histogram.lean @@ -0,0 +1,278 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.MaxSAT.PolyTime +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.PolynomialQueries +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.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Foundations.Data.Nat.SqrtLog.Shifted +import Mathlib.Data.Finset.Lattice.Fold + +/-! +# Exact MAX-SAT score distributions and optimum + +The threshold counter gives the number of assignments satisfying at least `q` clauses. Querying +all cutoffs from `0` through `m + 1`, for `m` clauses, gives the exact score distribution by +adjacent differences. The number of positive threshold counts, minus one, is the exact optimum. +Repeated and empty clauses retain their original contribution to the score. + +Both outputs have native algorithms using +`2 ^ O(N / √log N) poly(L)` time and polynomial space, where `N` is literal occurrences plus +clauses and `L` is the complete encoded input length. The generic native query and postprocessing +theorems preserve the threshold counter's exponential factor. A bound in the declared variable +count requires a density hypothesis; this module does not discard clauses or multiplicities. + +## Main results + +* `MaxSAT.histogramFromCounts_thresholdCounts`: adjacent differences recover every exact count. +* `MaxSAT.optimumFromCounts_thresholdCounts`: positive threshold counts recover the optimum. +* `MaxSAT.exists_histogram_algorithm`, `MaxSAT.exists_optimum_algorithm`: native resource bounds. +* `MaxSAT.exists_sparse_histogram_algorithm`, `MaxSAT.exists_sparse_optimum_algorithm`: + the rate in declared variables under fixed width and density bounds. +-/ + +@[expose] public section + +namespace Cslib.Complexity.MaxSAT + +open Cslib.Logic.PL Turing.MultiTapeTM + +/-- Number of assignments satisfying exactly `q` clauses. -/ +def exactCount (F : Instance) (q : ℕ) : ℕ := + (Finset.univ.filter (fun x : BitString F.1 => score F x = q)).card + +/-- The complete score distribution, including the zero-score coefficient. -/ +def histogram (F : Instance) : List ℕ := + (List.range (F.2.length + 1)).map (exactCount F) + +/-- Maximum number of simultaneously satisfied clauses. -/ +def optimum (F : Instance) : ℕ := Finset.univ.sup (score F) + +/-- Threshold counts with one trailing zero at cutoff `m + 1`. -/ +def thresholdCounts (F : Instance) : List ℕ := + (List.range (F.2.length + 2)).map (thresholdCount F) + +/-- Adjacent differences of a threshold-count list. -/ +def histogramFromCounts (counts : List ℕ) : List ℕ := + (counts.zip counts.tail).map (fun p => p.1 - p.2) + +/-- The number of feasible score cutoffs, minus the always-feasible zero cutoff. -/ +def optimumFromCounts (counts : List ℕ) : ℕ := + counts.countP (fun n => decide (0 < n)) - 1 + +/-- Differences of consecutive tail counts give the exact score count. -/ +theorem thresholdCount_sub (F : Instance) (q : ℕ) : + thresholdCount F q - thresholdCount F (q + 1) = exactCount F q := by + have hsub : (Finset.univ.filter (fun x : BitString F.1 => q + 1 ≤ score F x)) ⊆ + Finset.univ.filter (fun x : BitString F.1 => q ≤ score F x) := by + intro x hx + simp only [Finset.mem_filter, Finset.mem_univ, true_and] at hx ⊢ + omega + have heq : (Finset.univ.filter (fun x : BitString F.1 => q ≤ score F x)) \ + (Finset.univ.filter (fun x : BitString F.1 => q + 1 ≤ score F x)) = + Finset.univ.filter (fun x : BitString F.1 => score F x = q) := by + ext x + simp only [Finset.mem_sdiff, Finset.mem_filter, Finset.mem_univ, true_and] + omega + simpa only [thresholdCount, exactCount, heq] using + (Finset.card_sdiff_of_subset hsub).symm + +/-- Adjacent differences preserve the increasing order of score indices. -/ +theorem histogramFromCounts_range (f : ℕ → ℕ) (m : ℕ) : + histogramFromCounts ((List.range (m + 1)).map f) = + (List.range m).map (fun q => f q - f (q + 1)) := by + apply List.ext_getElem + · simp [histogramFromCounts] + · intro i hi hj + simp [histogramFromCounts] + +/-- The returned histogram contains every exact assignment count. -/ +theorem histogramFromCounts_thresholdCounts (F : Instance) : + histogramFromCounts (thresholdCounts F) = histogram F := by + simpa only [thresholdCounts, histogram, Nat.add_assoc, thresholdCount_sub] using + histogramFromCounts_range (thresholdCount F) (F.2.length + 1) + +/-- Every score is at most the optimum. -/ +theorem score_le_optimum (F : Instance) (x : BitString F.1) : score F x ≤ optimum F := + Finset.le_sup (Finset.mem_univ x) + +/-- The optimum is attained by an assignment, including when there are no variables. -/ +theorem exists_score_eq_optimum (F : Instance) : ∃ x : BitString F.1, score F x = optimum F := by + obtain ⟨x, _, hx⟩ := Finset.exists_mem_eq_sup (Finset.univ : Finset (BitString F.1)) + Finset.univ_nonempty (score F) + exact ⟨x, hx.symm⟩ + +/-- The optimum is at most the number of clauses. -/ +theorem optimum_le (F : Instance) : optimum F ≤ F.2.length := + Finset.sup_le (fun x _ => score_le F x) + +/-- The feasible thresholds are exactly the integers from zero through the optimum. -/ +theorem thresholdCount_pos_iff_le_optimum (F : Instance) (q : ℕ) : + 0 < thresholdCount F q ↔ q ≤ optimum F := by + rw [thresholdCount_pos_iff] + constructor + · rintro ⟨x, hx⟩ + exact hx.trans (score_le_optimum F x) + · intro hq + obtain ⟨x, hx⟩ := exists_score_eq_optimum F + exact ⟨x, hx ▸ hq⟩ + +private theorem countP_range_le (m q : ℕ) : + (List.range m).countP (fun j => decide (j ≤ q)) = min m (q + 1) := by + induction m with + | zero => simp + | succ m ih => + rw [List.range_succ, List.countP_append, ih] + by_cases h : m ≤ q <;> simp [h] <;> omega + +/-- Counting positive threshold counts returns the exact maximum score. -/ +theorem optimumFromCounts_thresholdCounts (F : Instance) : + optimumFromCounts (thresholdCounts F) = optimum F := by + simp only [optimumFromCounts, thresholdCounts, List.countP_map, Function.comp_def, + thresholdCount_pos_iff_le_optimum] + rw [countP_range_le, Nat.min_eq_right (by have := optimum_le F; omega)] + omega + +/-- Differences are polynomial-time on binary counts. -/ +theorem histogramFromCounts_polyTime : + ComputableInPolyTime histogramFromCounts (encList encNat) (encList encNat) := by + have hsub := ComputableInPolyTime.binary_sub' + (ComputableInPolyTime.fst encNat encNat) (ComputableInPolyTime.snd encNat encNat) + exact (ComputableInPolyTime.map hsub).comp + ((ComputableInPolyTime.zip encNat encNat).comp + (ComputableInPolyTime.id'.prod (ComputableInPolyTime.tail (encA := encNat)))) + +/-- Recovering the optimum is polynomial-time in the list of binary counts. -/ +theorem optimumFromCounts_polyTime : + ComputableInPolyTime optimumFromCounts (encList encNat) encNat := by + have hp : ComputableInPolyTime (fun p : List ℕ × ℕ => decide (0 < p.2)) + (encPair (encList encNat) encNat) encBool := + ComputableInPolyTime.binary_lt' (ComputableInPolyTime.const 0) + (ComputableInPolyTime.snd _ _) + have hn : ComputableInPolyTime + (fun counts : List ℕ => counts.countP (fun n => decide (0 < n))) + (encList encNat) encUnary := + ComputableInPolyTime.countPWithInput (encA := encNat) ComputableInPolyTime.id' hp + exact ComputableInPolyTime.unary_to_binary.comp + (ComputableInPolyTime.unary_sub' hn (ComputableInPolyTime.const 1)) + +/-! ### Native runtime bounds -/ + +/-- The complete threshold list is computed at the same contraction rate. The number of queries +and the materialized answer list contribute only polynomial factors. -/ +theorem exists_thresholdCounts_algorithm : + ∃ A d : ℕ, ComputableInO thresholdCounts encInstance (encList encNat) + (fun F => 2 ^ (A * (workSize F + 1) / Nat.sqrt (Nat.log 2 (workSize F + 2))) * + ((encInstance F).length + 1) ^ d) + (fun F => ((encInstance F).length + 1) ^ d) := by + obtain ⟨count, A, e, b, hcount, hc⟩ := exists_thresholdCounter + have he : count = fun p => thresholdCount p.1 p.2 := funext hcount + rw [he] at hc + have hm : ComputableInPolyTime (fun F : Instance => F.2.length) encInstance encUnary := + (ComputableInPolyTime.length_list (encList (encPair encNat encBool))).comp + (ComputableInPolyTime.snd encUnary encCNF) + have hq := ComputableInPolyTime.range.comp + (ComputableInPolyTime.unary_add' hm (ComputableInPolyTime.const 2)) + obtain ⟨d, hd⟩ := ComputableInO.polynomial_queries + (T := fun F => 2 ^ (A * (workSize F + 1) / Nat.sqrt (Nat.log 2 (workSize F + 2)))) + (e := e) (b := b) hc hq (fun _ => Nat.one_le_two_pow) + exact ⟨A, d, hd⟩ + +/-- A native algorithm returns all exact score counts, in increasing order from `0` through `m`, +within the threshold counter's exponential rate and polynomial space. -/ +theorem exists_histogram_algorithm : + ∃ A d : ℕ, ComputableInO histogram encInstance (encList encNat) + (fun F => 2 ^ (A * (workSize F + 1) / Nat.sqrt (Nat.log 2 (workSize F + 2))) * + ((encInstance F).length + 1) ^ d) + (fun F => ((encInstance F).length + 1) ^ d) := by + obtain ⟨A, d, hd⟩ := exists_thresholdCounts_algorithm + obtain ⟨e, he⟩ := hd.polynomial_postprocessing histogramFromCounts_polyTime + (fun _ => Nat.one_le_two_pow) + refine ⟨A, e, he.of_enc_eq fun F => ?_⟩ + rw [Function.comp_apply, histogramFromCounts_thresholdCounts] + +/-- A native algorithm returns the exact MAX-SAT optimum at the contraction rate, with +polynomial space. This is uniform in clause width and in the input's declared variable count. -/ +theorem exists_optimum_algorithm : + ∃ A d : ℕ, ComputableInO optimum encInstance encNat + (fun F => 2 ^ (A * (workSize F + 1) / Nat.sqrt (Nat.log 2 (workSize F + 2))) * + ((encInstance F).length + 1) ^ d) + (fun F => ((encInstance F).length + 1) ^ d) := by + obtain ⟨A, d, hd⟩ := exists_thresholdCounts_algorithm + obtain ⟨e, he⟩ := hd.polynomial_postprocessing optimumFromCounts_polyTime + (fun _ => Nat.one_le_two_pow) + refine ⟨A, e, he.of_enc_eq fun F => ?_⟩ + rw [Function.comp_apply, optimumFromCounts_thresholdCounts] + +/-! ### Sparse fixed-width instances -/ + +/-- Width at most `k` and at most `c (n+1)` clauses on `n` declared variables. References retain +the total evaluation convention of `Instance`; well-formed instances have all references below +`n`. Both resource parameters are fixed before the input. -/ +abbrev SparseInstance (k c : ℕ) := + {F : Instance // (∀ clause ∈ F.2, clause.length ≤ k) ∧ F.2.length ≤ c * (F.1 + 1)} + +/-- The declared variable count of a sparse instance. -/ +abbrev SparseInstance.numVars {k c : ℕ} : SparseInstance k c → ℕ + | ⟨(numVars, _), _⟩ => numVars + +/-- Sparse instances use the same complete encoding as their underlying formulas. -/ +def encSparseInstance (k c : ℕ) : SparseInstance k c ↪ List Bool := + (Function.Embedding.subtype _).trans encInstance + +/-- Width and density give an explicit linear bound on the parameter in the exponent. -/ +theorem sparse_workSize_le {k c : ℕ} (F : SparseInstance k c) : + workSize F.1 ≤ (k + 1) * c * F.numVars + (k + 1) * c := by + calc workSize F.1 ≤ (k + 1) * F.1.2.length := workSize_le_of_width k F.1 F.2.1 + _ ≤ (k + 1) * (c * (F.1.1 + 1)) := Nat.mul_le_mul_left _ F.2.2 + _ = (k + 1) * c * F.1.1 + (k + 1) * c := by ring + +private theorem sparse_algorithm {β : Type} {enc : β ↪ List Bool} {f : Instance → β} + {A d : ℕ} (k c : ℕ) + (hf : ComputableInO f encInstance enc + (fun F => 2 ^ (A * (workSize F + 1) / Nat.sqrt (Nat.log 2 (workSize F + 2))) * + ((encInstance F).length + 1) ^ d) + (fun F => ((encInstance F).length + 1) ^ d)) : + ComputableInO (fun F : SparseInstance k c => f F.1) (encSparseInstance k c) enc + (fun F => 2 ^ (A * (8 * ((k + 1) * c) + 14) * (F.numVars + 1) / + Nat.sqrt (Nat.log 2 (F.numVars + 2))) * ((encSparseInstance k c F).length + 1) ^ d) + (fun F => ((encSparseInstance k c F).length + 1) ^ d) := by + have h := hf.of_encIn_eq (encD := encSparseInstance k c) Subtype.val (fun _ => rfl) + refine h.mono_le (fun F => ?_) (fun _ => le_rfl) + apply Nat.mul_le_mul_right + apply Nat.pow_le_pow_right (by norm_num : 0 < (2 : ℕ)) + exact Nat.mul_succ_div_sqrt_log_two_le (sparse_workSize_le F) + +/-- Exact score distributions for sparse MAX-`k`-SAT in `2 ^ O(n / √log n) poly(L)` time and +polynomial space, for fixed width `k` and density `c`. Every one of the `2^n` canonical declared +assignments is counted, including assignments to unused variables. -/ +theorem exists_sparse_histogram_algorithm (k c : ℕ) : + ∃ A d : ℕ, ComputableInO (fun F : SparseInstance k c => histogram F.1) + (encSparseInstance k c) (encList encNat) + (fun F => 2 ^ (A * (F.numVars + 1) / Nat.sqrt (Nat.log 2 (F.numVars + 2))) * + ((encSparseInstance k c F).length + 1) ^ d) + (fun F => ((encSparseInstance k c F).length + 1) ^ d) := by + obtain ⟨A, d, h⟩ := exists_histogram_algorithm + exact ⟨A * (8 * ((k + 1) * c) + 14), d, sparse_algorithm k c h⟩ + +/-- The exact ordinary clause-count optimum for sparse MAX-`k`-SAT has the same native rate and +polynomial workspace. The input may contain duplicate and empty clauses. -/ +theorem exists_sparse_optimum_algorithm (k c : ℕ) : + ∃ A d : ℕ, ComputableInO (fun F : SparseInstance k c => optimum F.1) + (encSparseInstance k c) encNat + (fun F => 2 ^ (A * (F.numVars + 1) / Nat.sqrt (Nat.log 2 (F.numVars + 2))) * + ((encSparseInstance k c F).length + 1) ^ d) + (fun F => ((encSparseInstance k c F).length + 1) ^ d) := by + obtain ⟨A, d, h⟩ := exists_optimum_algorithm + exact ⟨A * (8 * ((k + 1) * c) + 14), d, sparse_algorithm k c h⟩ + +end Cslib.Complexity.MaxSAT diff --git a/Complexity/Computability/Complexity/FineGrained/MaxSAT/PolyTime.lean b/Complexity/Computability/Complexity/FineGrained/MaxSAT/PolyTime.lean new file mode 100644 index 0000000000..95ffc72440 --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/MaxSAT/PolyTime.lean @@ -0,0 +1,198 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.MaxSAT +public import Complexity.Computability.Complexity.FineGrained.ContractionCircuitCounting +public import Complexity.Computability.Circuit.Boolean.PopulationCount.ThresholdPolyTime +import Complexity.Computability.Circuit.Boolean.BalancedCode +import Complexity.Computability.Circuit.FanIn +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.Complexity.PolyTime.Plain + +/-! +# Native exact MAX-SAT threshold counting + +The full threshold circuit is generated by native polynomial-time operations on its complete +encoding. Tensor contraction then counts its accepted assignments in +`2 ^ O((N+1)/sqrt(log(N+2)))` times a polynomial in the input encoding length, using polynomial +space, where `N` is literal occurrences plus clauses. The theorem is uniform over all clause +widths and cutoffs. In particular, sparse fixed-width instances have the stated rate in their +number of variables. + +The runtime certificate includes clause generation, the linear-size population counter, +binary comparison, circuit serialization, and the count-preserving frontier compilation. +-/ + +public section + +namespace Cslib.Complexity.MaxSAT + +open Cslib.Logic.PL Cslib.Circuits Cslib.Circuits.Boolean Cslib.Circuits.Frontier + Turing.MultiTapeTM ComputableInPolyTime + +private abbrev literalEncoding := encPair encNat encBool +private abbrev clauseEncoding := encList literalEncoding + +private def literalCode (n : ℕ) (l : Literal ℕ) : List (Op × List ℕ) × List ℕ := + if l.1 < n then + if l.2 then ([], [l.1]) else ([(.not, [l.1])], [n]) + else ([(.const (!l.2), [])], [n]) + +private theorem literalCode_eq (n : ℕ) (l : Literal ℕ) : + literalCode n l = (literalCircuit n l).code := by + unfold literalCode literalCircuit + split_ifs <;> rfl + +private theorem literalCode_polyTime {α : Type} {enc : α ↪ List Bool} + {n : α → ℕ} {l : α → Literal ℕ} (hn : ComputableInPolyTime n enc encNat) + (hl : ComputableInPolyTime l enc literalEncoding) : + ComputableInPolyTime (fun a => literalCode (n a) (l a)) enc encCode := by + have hv := hl.fst' + have hs := hl.snd' + have hop : ComputableInPolyTime (fun b : Bool => Op.const (!b)) encBool encOp := + .of_tidy (ComputableTidilyInPolyTime.of_finite _) + have htag := hop.comp hs + unfold literalCode + fun_prop + +private def clauseCode (n : ℕ) (c : Clause ℕ) : List (Op × List ℕ) × List ℕ := + balancedCode n clauseJoin.code ([(.const false, [])], [n]) (c.map (literalCode n)) + +private theorem clauseCode_eq (n : ℕ) (c : Clause ℕ) : + clauseCode n c = (clauseCircuit n c).code := by + have he : c.map (literalCode n) = (c.map (literalCircuit n)).map Circuit.code := by + simp only [List.map_map, Function.comp_def] + exact List.map_congr_left (fun l _ => literalCode_eq n l) + rw [clauseCode, he] + exact balancedCode_eq_code clauseJoin (Circuit.oneGate (.const false) Fin.elim0) + (c.map (literalCircuit n)) + +private theorem clauseCode_polyTime : + ComputableInPolyTime (fun p : ℕ × Clause ℕ => clauseCode p.1 p.2) + (encPair encUnary clauseEncoding) encCode := by + have hn := unary_to_binary.comp (fst encUnary clauseEncoding) + have hl := literalCode_polyTime + (hn.comp (fst (encPair encUnary clauseEncoding) literalEncoding)) + (snd (encPair encUnary clauseEncoding) literalEncoding) + have hs := mapWithInput (f := fun p l => literalCode p.1 l) + (snd encUnary clauseEncoding) hl + have hz : ComputableInPolyTime + (fun p : ℕ × Clause ℕ => ([(Op.const false, [])], [p.1])) + (encPair encUnary clauseEncoding) encCode := by fun_prop + exact computableInPolyTime_balancedCode hn (const clauseJoin.code) hz hs + +private theorem scoreCircuit_code (F : Instance) : + (scoreCircuit F).code = appendCodes F.1 (F.2.map (clauseCode F.1)) := by + rw [scoreCircuit, ← appendCodes_code] + congr 1 + apply List.ext_getElem (by simp) + intro i hi hj + simp [clauseCode_eq] + +/-- The complete code of the clause-value circuit is generated in native polynomial time. -/ +theorem computableInPolyTime_scoreCircuit : + ComputableInPolyTime (fun F => (scoreCircuit F).code) encInstance encCode := by + have hn : ComputableInPolyTime (fun F : Instance => F.1) encInstance encUnary := + fst encUnary encCNF + have hc : ComputableInPolyTime (fun F : Instance => F.2) encInstance encCNF := + snd encUnary encCNF + have hb : ComputableInPolyTime + (fun p : Instance × Clause ℕ => clauseCode p.1.1 p.2) + (encPair encInstance clauseEncoding) encCode := + clauseCode_polyTime.comp + ((hn.comp (fst encInstance clauseEncoding)).prod (snd encInstance clauseEncoding)) + have h := computableInPolyTime_appendCodes (unary_to_binary.comp hn) + (mapWithInput (f := fun F c => clauseCode F.1 c) hc hb) + simpa only [scoreCircuit_code, Function.comp_def] using h + +/-- Native compilation of a threshold query to the complete typed circuit encoding. -/ +theorem computableInPolyTime_thresholdCircuit : + ComputableInPolyTime + (fun ((numVars, clauses), cutoff) => + (⟨numVars, thresholdCircuit (numVars, clauses) cutoff⟩ : Σ n, Circuit signature n 1)) + encQuery (Boolean.encCircuit 1) := by + have hF : ComputableInPolyTime (fun p : Query => p.1) encQuery encInstance := + fst encInstance encUnary + have hn := hF.fst' + have hm := (length_list clauseEncoding).comp hF.snd' + have hq : ComputableInPolyTime (fun p : Query => p.2) encQuery encUnary := + snd encInstance encUnary + have hcode := computableInPolyTime_compCode (unary_to_binary.comp hn) + (computableInPolyTime_linearThresholdCircuit.comp (hm.prod hq)) + (computableInPolyTime_scoreCircuit.comp hF) + have h := hn.prod hcode + apply h.of_enc_eq + intro p + rw [Boolean.encCircuit_apply] + congr 1 + simp only [Function.comp_apply, compCode_code, thresholdCircuit] + +private def compiledFormula (p : Query) : CNF ℕ := + Compiler.toCNF (thresholdCircuit p.1 p.2) interpretation (fun _ b => b) + +private def countShift (p : Query) : ℕ := + Compiler.freeShift (thresholdCircuit p.1 p.2) interpretation (fun _ b => b) + +private theorem compiledFormula_polyTime : + ComputableInPolyTime compiledFormula encQuery encCNF := + (CircuitCounting.computableInPolyTime_toCNF (σ := Boolean.signature) Boolean.encOp interpretation + (fun (_ : Fin 1) b => b)).comp computableInPolyTime_thresholdCircuit + +private theorem countShift_polyTime : + ComputableInPolyTime countShift encQuery encUnary := by + have h := (CircuitCounting.computableInPolyTime_freeShift (σ := Boolean.signature) + Boolean.encOp interpretation (fun (_ : Fin 1) b => b)).comp + computableInPolyTime_thresholdCircuit + apply h.of_enc_eq + intro p + rfl + +private theorem thresholdCircuit_fanIn (p : Query) : + (thresholdCircuit p.1 p.2).FanInAtMost 2 := + Circuit.fanInAtMost_of_arity_le Boolean.arity_le_two _ + +private theorem compiledFormula_width (p : Query) (cl : Clause ℕ) + (hcl : cl ∈ compiledFormula p) : cl.length ≤ 3 := + Compiler.length_le_of_mem_toCNF interpretation (fun _ b => b) + (thresholdCircuit_fanIn p) hcl + +private theorem compiledFormula_density (p : Query) : + (compiledFormula p).length ≤ 8 * ((compiledFormula p).numVars + 1) := + Compiler.length_toCNF_le_numVars interpretation (fun _ b => b) (thresholdCircuit_fanIn p) + +private theorem compiledFormula_vars (p : Query) : + (compiledFormula p).numVars ≤ 957 * workSize p.1 + 957 := by + apply (Compiler.numVars_toCNF_le interpretation (fun _ b => b) + (thresholdCircuit_fanIn p)).trans + have hs := size_thresholdCircuit_le p.1 p.2 + omega + +/-- One native machine computes the exact threshold count in the contraction rate, with an +explicit structural parameter and polynomial space. Its constants are independent of clause +width, density, cutoff, and declared unused variables. -/ +theorem exists_thresholdCounter : + ∃ (count : Query → ℕ) (A e b : ℕ), + (∀ p, count p = thresholdCount p.1 p.2) ∧ + ComputableInO count encQuery encNat + (fun p => 2 ^ (A * (workSize p.1 + 1) / Nat.sqrt (Nat.log 2 (workSize p.1 + 2))) * + ((encQuery p).length + 1) ^ e) + (fun p => ((encQuery p).length + 1) ^ b) := by + obtain ⟨count, A, e, b, hspec, hinO⟩ := + ContractionCounting.exists_shiftedCounter_of_polyTime_toCNF 3 8 957 + compiledFormula_polyTime countShift_polyTime (fun p => workSize p.1) + compiledFormula_width compiledFormula_density compiledFormula_vars + refine ⟨count, A, e, b, fun p => ?_, hinO⟩ + rw [hspec] + change 2 ^ Compiler.freeShift (thresholdCircuit p.1 p.2) interpretation (fun _ b => b) * + (Compiler.toCNF (thresholdCircuit p.1 p.2) interpretation (fun _ b => b)).modelCount = _ + rw [← Compiler.acceptCount_eq_two_pow_freeShift_mul_modelCount, + ← Circuit.satCount_eq_acceptCount, satCount_thresholdCircuit] + +end Cslib.Complexity.MaxSAT diff --git a/Complexity/Computability/GenericRAM/Native/PolynomialOverhead.lean b/Complexity/Computability/GenericRAM/Native/PolynomialOverhead.lean new file mode 100644 index 0000000000..f61fc0d279 --- /dev/null +++ b/Complexity/Computability/GenericRAM/Native/PolynomialOverhead.lean @@ -0,0 +1,97 @@ +/- +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.Certificate + +/-! +# Polynomial preprocessing and decoding of native algorithms + +A polynomial-time preprocessing and a polynomial-time decoder preserve a common running-time +factor when the intermediate native algorithm uses polynomial space. The space hypothesis also +bounds the encoded intermediate output, so the decoder does not multiply the exponential factor. + +## Main results + +* `NativeCertificate.exists_polynomial_overhead`: uniform polynomial overhead for a native + algorithm between two polynomial-time transformations, with an arbitrary factor at least one. +-/ + +public section + +namespace Cslib.GenericRAM.NativeCertificate + +open Turing.MultiTapeTM + +variable {α β γ δ : Type} {ei : α ↪ List Bool} {eb : β ↪ List Bool} + {ec : γ ↪ List Bool} {eo : δ ↪ List Bool} {prepare : α → β} {f : β → γ} {decode : γ → δ} + +/-- Polynomial-time preprocessing and decoding preserve an arbitrary time factor `X ≥ 1`, +provided the intermediate output is charged to polynomial space. The constant is selected before +the input and before `X`. -/ +theorem exists_polynomial_overhead (c : NativeCertificate f eb ec) + (hp : ComputableInPolyTime prepare ei eb) (hd : ComputableInPolyTime decode ec eo) (K : ℕ) : + ∃ K' : ℕ, ∀ (a : α) (X : ℕ), 1 ≤ X → + c.time (prepare a) ≤ K * ((eb (prepare a)).length + 1) ^ K * X → + c.space (prepare a) ≤ K * ((eb (prepare a)).length + 1) ^ K → + ((ofPolyTime hd).comp (c.comp (ofPolyTime hp))).time a ≤ + K' * ((ei a).length + 1) ^ K' * X ∧ + ((ofPolyTime hd).comp (c.comp (ofPolyTime hp))).space a ≤ + K' * ((ei a).length + 1) ^ K' := by + obtain ⟨p, hpp, -, hple⟩ := exists_mono_poly_cert hp + obtain ⟨q, hqp, hqm, hqle⟩ := exists_mono_poly_cert hd + obtain ⟨aY, dY, hY⟩ := hp.polyBounded_length.exists_le + obtain ⟨cO, -, hO⟩ := c.computes + let Y : ℕ → ℕ := fun n => aY * (n + 1) ^ dY + let Q : ℕ → ℕ := fun n => K * (Y n + 1) ^ K + let F : ℕ → ℕ := fun n => p n + Q n + q (cO * (Q n + 1)) + have hYp : PolyBounded id Y := + (PolyBounded.const aY).mul ((PolyBounded.self.add (PolyBounded.const 1)).pow dY) + have hQp : PolyBounded id Q := + (PolyBounded.const K).mul ((hYp.add (PolyBounded.const 1)).pow K) + have hFp : PolyBounded id F := + ((PolyBounded.of_polynomiallyBounded id hpp).add hQp).add + ((PolyBounded.of_polynomiallyBounded id hqp).comp + ((PolyBounded.const cO).mul (hQp.add (PolyBounded.const 1)))) + obtain ⟨aF, dF, hF⟩ := hFp.exists_le + refine ⟨aF + dF, fun a X hX ht hs => ?_⟩ + set n := (ei a).length + have hQ : K * ((eb (prepare a)).length + 1) ^ K ≤ Q n := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (Nat.add_le_add_right (hY a) 1) K) + have ht' : c.time (prepare a) ≤ Q n * X := ht.trans (Nat.mul_le_mul_right X hQ) + have hs' : c.space (prepare a) ≤ Q n := hs.trans hQ + have hlen : (ec (f (prepare a))).length ≤ cO * (Q n + 1) := + (hO (prepare a)).trans (Nat.mul_le_mul_left _ (Nat.add_le_add_right hs' 1)) + have hdt : (ofPolyTime hd).time (f (prepare a)) ≤ q (cO * (Q n + 1)) := + (hqle (f (prepare a))).1.trans (hqm hlen) + have hds : (ofPolyTime hd).space (f (prepare a)) ≤ q (cO * (Q n + 1)) := + (hqle (f (prepare a))).2.trans (hqm hlen) + have hbound : F n ≤ (aF + dF) * (n + 1) ^ (aF + dF) := + (hF n).trans (Nat.mul_le_mul (by omega) + (Nat.pow_le_pow_right (by omega) (by omega))) + have hpt : (ofPolyTime hp).time a ≤ p n := (hple a).1 + have hps : (ofPolyTime hp).space a ≤ p n := (hple a).2 + change (ofPolyTime hp).time a + c.time (prepare a) + + (ofPolyTime hd).time (f (prepare a)) ≤ _ ∧ + (ofPolyTime hp).space a + c.space (prepare a) + + (ofPolyTime hd).space (f (prepare a)) ≤ _ + refine ⟨?_, ?_⟩ + · have hpX : p n ≤ p n * X := Nat.le_mul_of_pos_right _ hX + have hqX : q (cO * (Q n + 1)) ≤ q (cO * (Q n + 1)) * X := + Nat.le_mul_of_pos_right _ hX + calc (ofPolyTime hp).time a + c.time (prepare a) + + (ofPolyTime hd).time (f (prepare a)) + ≤ p n * X + Q n * X + q (cO * (Q n + 1)) * X := by omega + _ = F n * X := by simp only [F]; ring + _ ≤ (aF + dF) * (n + 1) ^ (aF + dF) * X := Nat.mul_le_mul_right _ hbound + · calc (ofPolyTime hp).space a + c.space (prepare a) + + (ofPolyTime hd).space (f (prepare a)) + ≤ F n := by dsimp only [F]; omega + _ ≤ (aF + dF) * (n + 1) ^ (aF + dF) := hbound + +end Cslib.GenericRAM.NativeCertificate diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialQueries.lean b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialQueries.lean new file mode 100644 index 0000000000..58cd2b78cf --- /dev/null +++ b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/BigO/PolynomialQueries.lean @@ -0,0 +1,160 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Lists +import Complexity.Computability.GenericRAM.Native.Certificate +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length + +/-! +# Polynomially many queries with a common running-time factor + +A polynomial-time procedure produces a list of queries. The answer procedure may take +superpolynomial time, but its time on each query is a common factor `T x` times a polynomial in +the encoded query, and its workspace is polynomial. Computing all answers costs only a +polynomial factor beyond `T x` and still uses polynomial space, including the full answer list. + +The theorem uses the plain native list-mapping machine. In particular, it does not assume an +oracle for the answers or charge a superpolynomial answer procedure as a polynomial-time step. + +## Main results + +* `Turing.MultiTapeTM.ComputableInO.polynomial_queries`: polynomially many queries preserve a + common running-time factor and polynomial workspace. +* `Turing.MultiTapeTM.ComputableInO.polynomial_postprocessing`: polynomial postprocessing has + the same preservation property when the producer uses polynomial space. +-/ + +public section + +namespace Turing.MultiTapeTM.ComputableInO + +open Cslib.GenericRAM + +variable {α β γ : Type} {ei : α ↪ List Bool} {eq : β ↪ List Bool} {eo : γ ↪ List Bool} + +/-- Compute a polynomial-size list of answers while preserving the common running-time factor. +The constants `e`, `b`, and the resulting `d` are fixed before the input `x`. -/ +theorem polynomial_queries {answer : α × β → γ} {queries : α → List β} + {T : α → ℕ} {e b : ℕ} + (ha : ComputableInO answer (encPair ei eq) eo + (fun p => T p.1 * ((encPair ei eq p).length + 1) ^ e) + (fun p => ((encPair ei eq p).length + 1) ^ b)) + (hq : ComputableInPolyTime queries ei (encList eq)) (hT : ∀ x, 1 ≤ T x) : + ∃ d : ℕ, ComputableInO (fun x => (queries x).map fun q => answer (x, q)) + ei (encList eo) (fun x => T x * ((ei x).length + 1) ^ d) + (fun x => ((ei x).length + 1) ^ d) := by + let cq := NativeCertificate.ofPolyTime hq + have hcq := NativeCertificate.ofPolyTime_isPolynomial hq + let L := fun x => (ei x).length + let Q := fun x => (encList eq (queries x)).length + let B := fun x => 2 * L x + Q x + 2 + let O := fun x => (encList eo ((queries x).map fun q => answer (x, q))).length + have hL : PolyBounded L L := .self + have hQ : PolyBounded L Q := hq.polyBounded_length + have hB : PolyBounded L B := + (((PolyBounded.const 2).mul hL).add hQ).add (PolyBounded.const 2) + have hquery (x : α) (q : β) (h : q ∈ queries x) : + (encPair ei eq (x, q)).length + 1 ≤ B x := by + have := length_le_encList_of_mem eq h + simp only [length_encPair] + dsimp [B, L, Q] + omega + obtain ⟨c, hc⟩ := ha.length_le + have hO : PolyBounded L O := by + refine (hQ.mul (((PolyBounded.const 2).mul ((PolyBounded.const c).mul + ((hB.pow b).add (PolyBounded.const 1)))).add (PolyBounded.const 1))).mono fun x => ?_ + have hlen := length_encList_le eo ((queries x).map fun q => answer (x, q)) + (b := c * (B x ^ b + 1)) fun a ha => by + obtain ⟨q, hq, rfl⟩ := List.mem_map.mp ha + exact (hc (x, q)).trans (Nat.mul_le_mul_left c (Nat.add_le_add_right + (Nat.pow_le_pow_left (hquery x q hq) b) 1)) + rw [List.length_map] at hlen + exact hlen.trans (Nat.mul_le_mul_right _ (length_le_length_encList eq (queries x))) + let U := fun x => L x + L x + cq.time x + + (Q x + 1) * (L x + Q x + O x + B x ^ e) + let V := fun x => L x + L x + cq.space x + L x + Q x + O x + B x ^ b + have hU : PolyBounded L U := + ((hL.add hL).add hcq.1).add + ((hQ.add (PolyBounded.const 1)).mul (((hL.add hQ).add hO).add (hB.pow e))) + have hV : PolyBounded L V := + (((((hL.add hL).add hcq.2).add hL).add hQ).add hO).add (hB.pow b) + obtain ⟨k, d, hk⟩ := (hU.add hV).exists_le + have hmap := ha.mapWith + (ComputableInO.of_eq (encIn := ei) (encMid := ei) fun _ => rfl) cq.computes + refine ⟨d, hmap.mono k (fun x => ?_) (fun x => ?_)⟩ + · have hmax : listMax (fun q => T x * ((encPair ei eq (x, q)).length + 1) ^ e) + (queries x) ≤ T x * B x ^ e := + listMax_le fun q hq => Nat.mul_le_mul_left _ + (Nat.pow_le_pow_left (hquery x q hq) e) + have hlen : (queries x).length ≤ Q x := length_le_length_encList eq (queries x) + have hfirst : L x + L x + cq.time x ≤ T x * (L x + L x + cq.time x) := + Nat.le_mul_of_pos_left _ (hT x) + have hbody : L x + Q x + O x + T x * B x ^ e ≤ + T x * (L x + Q x + O x + B x ^ e) := by + have := Nat.le_mul_of_pos_left (L x + Q x + O x) (hT x) + nlinarith + have hbound : L x + L x + cq.time x + ((queries x).length + 1) * + (L x + Q x + O x + listMax + (fun q => T x * ((encPair ei eq (x, q)).length + 1) ^ e) (queries x)) ≤ + T x * U x := by + calc _ ≤ L x + L x + cq.time x + + (Q x + 1) * (L x + Q x + O x + T x * B x ^ e) := by + exact Nat.add_le_add_left (Nat.mul_le_mul (by omega) + (Nat.add_le_add_left hmax _)) _ + _ ≤ T x * (L x + L x + cq.time x) + + (Q x + 1) * (T x * (L x + Q x + O x + B x ^ e)) := + Nat.add_le_add hfirst (Nat.mul_le_mul_left _ hbody) + _ = T x * U x := by dsimp [U]; ring + have hpoly : U x ≤ k * (L x + 1) ^ d := (Nat.le_add_right _ _).trans (hk x) + have h := hbound.trans (Nat.mul_le_mul_left (T x) hpoly) + dsimp [L, Q, O] at h + nlinarith + · have hmax : listMax (fun q => ((encPair ei eq (x, q)).length + 1) ^ b) + (queries x) ≤ B x ^ b := + listMax_le fun q hq => Nat.pow_le_pow_left (hquery x q hq) b + have hpoly : V x ≤ k * (L x + 1) ^ d := (Nat.le_add_left _ _).trans (hk x) + dsimp [V, L, Q, O] at hpoly + nlinarith + +/-- Polynomial postprocessing of a polynomial-space result preserves the common time factor. +The producer itself need not run in polynomial time. -/ +theorem polynomial_postprocessing {f : α → β} {g : β → γ} {T : α → ℕ} {e b : ℕ} + (hf : ComputableInO f ei eq (fun x => T x * ((ei x).length + 1) ^ e) + (fun x => ((ei x).length + 1) ^ b)) + (hg : ComputableInPolyTime g eq eo) (hT : ∀ x, 1 ≤ T x) : + ∃ d : ℕ, ComputableInO (g ∘ f) ei eo (fun x => T x * ((ei x).length + 1) ^ d) + (fun x => ((ei x).length + 1) ^ d) := by + let cg := NativeCertificate.ofPolyTime hg + have hcg := NativeCertificate.ofPolyTime_isPolynomial hg + let L := fun x => (ei x).length + have hL : PolyBounded L L := .self + have hLp : PolyBounded L (fun x => (L x + 1)) := hL.add (PolyBounded.const 1) + have hlen : PolyBounded L (fun x => (eq (f x)).length) := by + obtain ⟨c, hc⟩ := hf.length_le + exact ((PolyBounded.const c).mul ((hLp.pow b).add (PolyBounded.const 1))).mono hc + have htime : PolyBounded L (fun x => cg.time (f x)) := hcg.1.comp hlen + have hspace : PolyBounded L (fun x => cg.space (f x)) := hcg.2.comp hlen + let P := fun x => (L x + 1) ^ e + cg.time (f x) + (L x + 1) ^ b + cg.space (f x) + have hP : PolyBounded L P := (((hLp.pow e).add htime).add (hLp.pow b)).add hspace + obtain ⟨k, d, hk⟩ := hP.exists_le + refine ⟨d, (cg.computes.comp hf).mono k (fun x => ?_) (fun x => ?_)⟩ + · have hscale := Nat.le_mul_of_pos_left (cg.time (f x)) (hT x) + have hp : (L x + 1) ^ e + cg.time (f x) ≤ P x := by dsimp [P]; omega + calc T x * (L x + 1) ^ e + cg.time (f x) + ≤ T x * ((L x + 1) ^ e + cg.time (f x)) := by + rw [Nat.mul_add] + exact Nat.add_le_add_left hscale _ + _ ≤ T x * P x := Nat.mul_le_mul_left _ hp + _ ≤ T x * (k * (L x + 1) ^ d) := Nat.mul_le_mul_left _ (hk x) + _ = k * (T x * (L x + 1) ^ d) := by ring + _ ≤ k * (T x * (L x + 1) ^ d + 1) := Nat.mul_le_mul_left _ (Nat.le_succ _) + · have hp : (L x + 1) ^ b + cg.space (f x) ≤ P x := by dsimp [P]; omega + exact (hp.trans (hk x)).trans (Nat.mul_le_mul_left _ (Nat.le_succ _)) + +end Turing.MultiTapeTM.ComputableInO diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Polynomial.lean b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Polynomial.lean new file mode 100644 index 0000000000..2adb302ccd --- /dev/null +++ b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Polynomial.lean @@ -0,0 +1,62 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Polynomial.CoeffList +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary + +/-! +# Binary-digit coefficients and natural Horner evaluation in polynomial time + +The binary digits of a binary natural can be returned as a framed list of natural coefficients. +Conversely, evaluating a list of arbitrary binary natural coefficients at `2` is polynomial-time: +Horner's step adds at most the coefficient's bit length plus three to the accumulator's length. +These certificates use the ordinary multitape polynomial-time predicate. + +## Main results + +* `Turing.MultiTapeTM.ComputableInPolyTime.binary_digitCoeffs`: binary-digit coefficients. +* `Turing.MultiTapeTM.ComputableInPolyTime.evalCoeffList_two`: Horner evaluation at `2`. +-/ + +public section + +namespace Turing.MultiTapeTM.ComputableInPolyTime + +/-- Return the binary digits of a binary natural as natural coefficients. -/ +@[fun_prop] theorem binary_digitCoeffs : + ComputableInPolyTime (fun n : ℕ => n.bits.map Bool.toNat) encNat (encList encNat) := by + have hbits : ComputableInPolyTime Nat.bits encNat encBits := of_eq fun _ => rfl + exact (map (of_finite (encIn := encBool) (encOut := encNat) Bool.toNat)).comp + (bitsToList.comp hbits) + +/-- Evaluate a list of binary natural coefficients at `2` by Horner's rule. -/ +@[fun_prop] theorem evalCoeffList_two : + ComputableInPolyTime (Polynomial.evalCoeffList 2) (encList encNat) encNat := by + have hq := fst (encPair (encList encNat) encNat) encNat + have hacc := hq.snd' + have hval := snd (encPair (encList encNat) encNat) encNat + have hstep := binary_add.comp (hval.prod + (binary_mul' (const 2) hacc)) + have h := foldlWith_of_bounded_growth (encA := encNat) (encL := encList encNat) + (encB := encNat) (par := fun l : List ℕ => l) (list := fun l => l.reverse) + (step := fun _ acc a => a + 2 * acc) id' (listReverseWithInput id') (const 0) hstep + (growth := fun l => (encList encNat l).length + 3) (.add .self (.const 3)) + (fun l acc a ha => by + have hlen := length_le_encList_of_mem encNat (List.mem_reverse.mp ha) + simp only [encNat_apply, Nat.size_eq_bits_len] at hlen ⊢ + have hadd := Nat.size_add_le a (2 * acc) + have hmul := Nat.size_mul_le 2 acc + have htwo : Nat.size 2 = 2 := rfl + omega) + change ComputableInPolyTime (fun l : List ℕ => l.foldr (fun a acc => a + 2 * acc) 0) + (encList encNat) encNat + simpa only [List.foldl_reverse] using h + +end Turing.MultiTapeTM.ComputableInPolyTime diff --git a/Complexity/Foundations/Data/Polynomial/BinaryDigits.lean b/Complexity/Foundations/Data/Polynomial/BinaryDigits.lean new file mode 100644 index 0000000000..a2867394d3 --- /dev/null +++ b/Complexity/Foundations/Data/Polynomial/BinaryDigits.lean @@ -0,0 +1,91 @@ +/- +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 (Mathlib `Algebra/Polynomial`) + +public import Complexity.Foundations.Data.Polynomial.CoeffList +public import Mathlib.Data.Nat.Size +import Complexity.Foundations.Data.Nat.Bits +import Mathlib.Algebra.Polynomial.Eval.Degree +import Mathlib.Algebra.Order.BigOperators.Group.Finset + +/-! +# Binary digits as polynomial coefficients + +`Polynomial.binaryDigits n` has the binary digits of `n` as its coefficients, starting with +the least significant bit. Evaluation at `2` recovers `n`; evaluation at `1` is at most the +binary length `n.size`. This separates a large integer's magnitude from the sizes of the +coefficients used in an exact polynomial computation. + +For a polynomial over `ℕ`, every coefficient is at most its value at `1`. Consequently a +degree bound and a bound on this value give exact recovery from a list of coefficient residues. + +## Main definitions + +* `Polynomial.binaryDigits`: the polynomial of the binary digits of a natural number. + +## Main results + +* `Polynomial.eval_two_binaryDigits`: evaluation at `2` recovers the natural number. +* `Polynomial.eval_one_binaryDigits_le`: the coefficient sum is at most the binary length. +* `Polynomial.natDegree_binaryDigits_le`: the degree is at most the binary length less one. +* `Polynomial.coeff_le_eval_one`: a coefficient of a natural polynomial is at most its value at `1`. +* `Polynomial.ofCoeffList_coeffsBelow_mod`: recovery from bounded coefficient residues. +-/ + +public section + +namespace Polynomial + +/-- The polynomial whose coefficients are the binary digits of `n`, least significant first. -/ +@[expose] noncomputable def binaryDigits (n : ℕ) : ℕ[X] := ofCoeffList (n.bits.map Bool.toNat) + +/-- Evaluating the binary-digit polynomial at `2` recovers the natural number. -/ +@[simp] theorem eval_two_binaryDigits (n : ℕ) : (binaryDigits n).eval 2 = n := by + have h (bs : List Bool) : evalCoeffList 2 (bs.map Bool.toNat) = bs.foldr Nat.bit 0 := by + induction bs with + | nil => rfl + | cons b bs ih => + rw [List.map_cons, evalCoeffList_cons, List.foldr_cons, ih, Nat.bit_val, Nat.add_comm] + rw [binaryDigits, eval_ofCoeffList, h, Nat.foldr_bit_bits] + +/-- The sum of the binary digits is at most the binary length. -/ +theorem eval_one_binaryDigits_le (n : ℕ) : (binaryDigits n).eval 1 ≤ n.size := by + have h (bs : List Bool) : evalCoeffList 1 (bs.map Bool.toNat) ≤ bs.length := by + induction bs with + | nil => exact Nat.zero_le _ + | cons b bs ih => + rw [List.map_cons, evalCoeffList_cons, one_mul, List.length_cons] + cases b <;> simp only [Bool.toNat_false, Bool.toNat_true] <;> omega + simpa only [binaryDigits, eval_ofCoeffList, Nat.size_eq_bits_len] using h n.bits + +/-- The binary-digit polynomial has degree at most the binary length less one. -/ +theorem natDegree_binaryDigits_le (n : ℕ) : (binaryDigits n).natDegree ≤ n.size - 1 := by + simpa only [binaryDigits, List.length_map, Nat.size_eq_bits_len] using + natDegree_ofCoeffList_le (n.bits.map Bool.toNat) + +/-- A coefficient of a polynomial over the natural numbers is at most the sum of its +coefficients, its value at `1`. -/ +theorem coeff_le_eval_one (p : ℕ[X]) (i : ℕ) : p.coeff i ≤ p.eval 1 := by + rw [eval_eq_sum_range' (Nat.lt_succ_self p.natDegree)] + simp only [one_pow, mul_one] + by_cases hi : i ≤ p.natDegree + · exact Finset.single_le_sum (fun _ _ => Nat.zero_le _) + (Finset.mem_range.mpr (Nat.lt_succ_of_le hi)) + · rw [coeff_eq_zero_of_natDegree_lt (Nat.lt_of_not_ge hi)] + exact Nat.zero_le _ + +/-- A polynomial over the natural numbers is recovered from the residues of sufficiently +many low coefficients when its coefficient sum is below the modulus. -/ +theorem ofCoeffList_coeffsBelow_mod {p : ℕ[X]} {ℓ M : ℕ} (hdeg : p.natDegree < ℓ) + (hM : p.eval 1 < M) : + ofCoeffList ((List.range ℓ).map fun i => p.coeff i % M) = p := by + have hlist : (List.range ℓ).map (fun i => p.coeff i % M) = p.coeffsBelow ℓ := by + exact List.map_congr_left fun i _ => Nat.mod_eq_of_lt ((coeff_le_eval_one p i).trans_lt hM) + rw [hlist, ofCoeffList_coeffsBelow hdeg] + +end Polynomial diff --git a/Complexity/Logics/Propositional/CSP/BinaryDigits.lean b/Complexity/Logics/Propositional/CSP/BinaryDigits.lean new file mode 100644 index 0000000000..e6d05f3542 --- /dev/null +++ b/Complexity/Logics/Propositional/CSP/BinaryDigits.lean @@ -0,0 +1,152 @@ +/- +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.Logics.Propositional.CSP.PartitionFunction +public import Complexity.Foundations.Data.Polynomial.BinaryDigits +public import Mathlib.Algebra.Polynomial.BigOperators +import Mathlib.Algebra.Order.BigOperators.Group.List +import Mathlib.Data.Fintype.BigOperators + +/-! +# Exact weighted counting through binary-digit polynomials + +Replace each natural local weight by its binary-digit polynomial and take the same partition +function over `ℕ[X]`. Evaluation at `2` gives the original weighted count. If the weights of +relation `r` have at most `b r` bits, the polynomial has degree at most +`∑ (r, _) ∈ F, (b r - 1)` and coefficient sum at most `2 ^ F.numVars * ∏ (r, _) ∈ F, b r`. + +The coefficient precision is therefore logarithmic in each bit-length bound, even when the +weights themselves are large. The explicit choice +`F.numVars + ∑ (r, _) ∈ F, (b r).size + 1` suffices for exact natural coefficient recovery. +This is an algebraic reduction; the tensor-contraction runtime is a separate theorem. + +## Main definitions + +* `Cslib.Logic.PL.CSP.binaryPartitionPolynomial`: the partition function of binary-digit weights. +* `Cslib.Logic.PL.CSP.binaryDegreeBound`: the sum of the local degree bounds. +* `Cslib.Logic.PL.CSP.binaryPrecision`: precision sufficient for exact coefficient recovery. + +## Main results + +* `Cslib.Logic.PL.CSP.eval_two_binaryPartitionPolynomial`: evaluation at `2` is the weighted count. +* `Cslib.Logic.PL.CSP.natDegree_binaryPartitionPolynomial_le`: the degree bound. +* `Cslib.Logic.PL.CSP.eval_one_binaryPartitionPolynomial_le`: the coefficient-sum bound. +* `Cslib.Logic.PL.CSP.eval_one_binaryPartitionPolynomial_lt_two_pow`: the precision suffices. +* `Cslib.Logic.PL.CSP.recover_binaryPartitionPolynomial`: exact recovery from coefficient residues. +* `Cslib.Logic.PL.CSP.evalCoeffList_binaryPartitionPolynomial_residues`: the decoded residues + evaluate to the original partition function. +-/ + +public section + +namespace Cslib.Logic.PL.CSP + +open Polynomial + +variable {Rel V D : Type*} [DecidableEq V] [Fintype D] [Inhabited D] + +/-- Evaluating polynomial weights commutes with the partition function. -/ +theorem eval_partitionFunction {R : Type*} [CommSemiring R] + (wt : Rel → List D → R[X]) (F : CSP Rel V) (z : R) : + (F.partitionFunction wt).eval z = F.partitionFunction (fun r w => (wt r w).eval z) := + map_partitionFunction (evalRingHom z) wt F + +/-- The degree of a polynomial partition function is at most the sum of the local degree +bounds. -/ +theorem natDegree_partitionFunction_le {R : Type*} [Semiring R] + {wt : Rel → List D → R[X]} {F : CSP Rel V} {d : Rel → ℕ} + (hd : ∀ c ∈ F, ∀ w : List D, w.length = c.2.length → (wt c.1 w).natDegree ≤ d c.1) : + (F.partitionFunction wt).natDegree ≤ (F.map fun c => d c.1).sum := by + rw [partitionFunction] + refine natDegree_sum_le_of_forall_le _ _ fun σ _ => ?_ + rw [weight] + refine (natDegree_list_prod_le _).trans ?_ + rw [List.map_map] + exact List.sum_le_sum fun c hc => hd c hc _ (List.length_map _) + +/-- The partition function with each local natural weight replaced by its binary-digit +polynomial. -/ +@[expose] noncomputable def binaryPartitionPolynomial (wt : Rel → List Bool → ℕ) + (F : CSP Rel V) : ℕ[X] := + F.partitionFunction fun r w => binaryDigits (wt r w) + +/-- The degree bound from a bit-length bound for each relation. -/ +@[expose] def binaryDegreeBound (b : Rel → ℕ) (F : CSP Rel V) : ℕ := + (F.map fun c => b c.1 - 1).sum + +/-- The precision for recovering the natural coefficients: the number of variables, plus the +sum of the binary lengths of the local bit-length bounds, plus one. -/ +@[expose] def binaryPrecision (b : Rel → ℕ) (F : CSP Rel V) : ℕ := + F.numVars + (F.map fun c => (b c.1).size).sum + 1 + +/-- Evaluating the binary-digit partition polynomial at `2` gives the original weighted +partition function. -/ +@[simp] theorem eval_two_binaryPartitionPolynomial (wt : Rel → List Bool → ℕ) + (F : CSP Rel V) : (F.binaryPartitionPolynomial wt).eval 2 = F.partitionFunction wt := by + simp only [binaryPartitionPolynomial, eval_partitionFunction, eval_two_binaryDigits] + +variable {wt : Rel → List Bool → ℕ} {F : CSP Rel V} {b : Rel → ℕ} + +/-- The polynomial's degree is at most the sum of the local bit-length bounds less one. -/ +theorem natDegree_binaryPartitionPolynomial_le + (hb : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → (wt c.1 w).size ≤ b c.1) : + (F.binaryPartitionPolynomial wt).natDegree ≤ F.binaryDegreeBound b := by + exact natDegree_partitionFunction_le + (wt := fun r w => binaryDigits (wt r w)) (d := fun r => b r - 1) fun c hc w hw => + (natDegree_binaryDigits_le _).trans (Nat.sub_le_sub_right (hb c hc w hw) 1) + +/-- The coefficient sum is at most the number of assignments times the product of the local +bit-length bounds. -/ +theorem eval_one_binaryPartitionPolynomial_le + (hb : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → (wt c.1 w).size ≤ b c.1) : + (F.binaryPartitionPolynomial wt).eval 1 ≤ + 2 ^ F.numVars * (F.map fun c => b c.1).prod := by + rw [binaryPartitionPolynomial, eval_partitionFunction] + simpa only [Fintype.card_bool] using partitionFunction_le_prod + (wt := fun r w => (binaryDigits (wt r w)).eval 1) + (fun c hc w hw => (eval_one_binaryDigits_le _).trans (hb c hc w hw)) + +/-- The explicit precision strictly exceeds the coefficient sum. -/ +theorem eval_one_binaryPartitionPolynomial_lt_two_pow + (hb : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → (wt c.1 w).size ≤ b c.1) : + (F.binaryPartitionPolynomial wt).eval 1 < 2 ^ F.binaryPrecision b := by + have hprod : (F.map fun c => b c.1).prod ≤ 2 ^ (F.map fun c => (b c.1).size).sum := by + clear hb + induction F with + | nil => simp + | cons c F ih => + simpa only [List.map_cons, List.prod_cons, List.sum_cons, pow_add] using + Nat.mul_le_mul (Nat.lt_size_self (b c.1)).le ih + calc (F.binaryPartitionPolynomial wt).eval 1 + ≤ 2 ^ F.numVars * (F.map fun c => b c.1).prod := + eval_one_binaryPartitionPolynomial_le hb + _ ≤ 2 ^ F.numVars * 2 ^ (F.map fun c => (b c.1).size).sum := + Nat.mul_le_mul_left _ hprod + _ = 2 ^ (F.numVars + (F.map fun c => (b c.1).size).sum) := (pow_add _ _ _).symm + _ < 2 ^ F.binaryPrecision b := Nat.pow_lt_pow_right (by decide) (Nat.lt_succ_self _) + +/-- Recovering enough coefficients modulo the explicit power of two gives the entire +binary-digit partition polynomial. -/ +theorem recover_binaryPartitionPolynomial + (hb : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → (wt c.1 w).size ≤ b c.1) : + ofCoeffList ((List.range (F.binaryDegreeBound b + 1)).map fun i => + (F.binaryPartitionPolynomial wt).coeff i % 2 ^ F.binaryPrecision b) = + F.binaryPartitionPolynomial wt := + ofCoeffList_coeffsBelow_mod (Nat.lt_succ_of_le (natDegree_binaryPartitionPolynomial_le hb)) + (eval_one_binaryPartitionPolynomial_lt_two_pow hb) + +/-- Evaluating the decoded coefficient residues at `2` recovers the weighted count. -/ +theorem evalCoeffList_binaryPartitionPolynomial_residues + (hb : ∀ c ∈ F, ∀ w : List Bool, w.length = c.2.length → (wt c.1 w).size ≤ b c.1) : + evalCoeffList 2 ((List.range (F.binaryDegreeBound b + 1)).map fun i => + (F.binaryPartitionPolynomial wt).coeff i % 2 ^ F.binaryPrecision b) = + F.partitionFunction wt := by + rw [← eval_ofCoeffList, recover_binaryPartitionPolynomial hb, eval_two_binaryPartitionPolynomial] + +end Cslib.Logic.PL.CSP diff --git a/Complexity/Logics/Propositional/CSP/PartitionFunction.lean b/Complexity/Logics/Propositional/CSP/PartitionFunction.lean index 3238e4eb70..7bf3f63c03 100644 --- a/Complexity/Logics/Propositional/CSP/PartitionFunction.lean +++ b/Complexity/Logics/Propositional/CSP/PartitionFunction.lean @@ -20,8 +20,8 @@ import Mathlib.Data.Fintype.BigOperators /-! # Partition functions of weighted constraint satisfaction problems -A weighting `wt : Rel → List D → ℕ` reads each relation symbol as a function from local -assignments to natural numbers. The partition function of a CSP instance `F : CSP Rel V` +A weighting `wt : Rel → List D → R` reads each relation symbol as a function from local +assignments to a semiring `R`. The partition function of a CSP instance `F : CSP Rel V` sums, over the assignments of the variables occurring in `F` to the finite domain `D`, the product of the weights of the constraints at their scopes: `F.partitionFunction wt = ∑ σ, ∏ (R, vs) ∈ F, wt R (vs.map σ)`. @@ -40,6 +40,10 @@ weightings. ## Main results * `Cslib.Logic.PL.CSP.partitionFunction_boole`: with `0/1` weights it is `CSP.modelCount`. +* `Cslib.Logic.PL.CSP.map_partitionFunction`: semiring homomorphisms commute with the partition + function, including evaluation of polynomial weights. +* `Cslib.Logic.PL.CSP.partitionFunction_le_prod`: a separate bound for each relation gives the + product of those bounds times the number of assignments. * `Cslib.Logic.PL.CSP.partitionFunction_le`: it is at most `|D| ^ n * W ^ m` when every weight is at most `W`, for `n` variables and `m` constraints. * `Cslib.Logic.PL.CSP.partitionFunction_eq_zero_of_mem`: a constraint of weight zero makes it zero. @@ -55,17 +59,32 @@ variable {Rel V D : Type*} /-- The weight of an assignment `σ`: the product over the constraints `(R, vs)` of `F` of `wt R (vs.map σ)`. -/ -@[expose] def weight (wt : Rel → List D → ℕ) (F : CSP Rel V) (σ : V → D) : ℕ := +@[expose] def weight {R : Type*} [Monoid R] (wt : Rel → List D → R) (F : CSP Rel V) + (σ : V → D) : R := (F.map fun c => wt c.1 (c.2.map σ)).prod +/-- Monoid homomorphisms commute with the weight of an assignment. -/ +theorem map_weight {R S : Type*} [Monoid R] [Monoid S] (f : R →* S) + (wt : Rel → List D → R) (F : CSP Rel V) (σ : V → D) : + f (F.weight wt σ) = F.weight (fun r w => f (wt r w)) σ := by + simp only [weight, map_list_prod, List.map_map, Function.comp_def] + variable [DecidableEq V] [Fintype D] [Inhabited D] /-- **The partition function** of a weighted CSP instance: the sum of `F.weight wt σ` over the assignments `σ` of the variables occurring in `F` (extended by `default` elsewhere). -/ -@[expose] def partitionFunction (wt : Rel → List D → ℕ) (F : CSP Rel V) : ℕ := +@[expose] def partitionFunction {R : Type*} [Semiring R] (wt : Rel → List D → R) + (F : CSP Rel V) : R := ∑ σ : F.vars.toFinset → D, F.weight wt fun v => if h : v ∈ F.vars.toFinset then σ ⟨v, h⟩ else default +/-- Semiring homomorphisms commute with the partition function. -/ +theorem map_partitionFunction {R S : Type*} [Semiring R] [Semiring S] (f : R →+* S) + (wt : Rel → List D → R) (F : CSP Rel V) : + f (F.partitionFunction wt) = F.partitionFunction (fun r w => f (wt r w)) := by + simp only [partitionFunction, map_sum] + exact Finset.sum_congr rfl fun σ _ => map_weight f.toMonoidHom wt F _ + /-- **With `0/1` weights the partition function is the model count** of the relation semantics. -/ theorem partitionFunction_boole (sem : Rel → List D → Bool) (F : CSP Rel V) : @@ -75,18 +94,24 @@ theorem partitionFunction_boole (sem : Rel → List D → Bool) (F : CSP Rel V) rw [weight, List.prod_map_boole] exact if_congr List.all_eq_true.symm rfl rfl +/-- The partition function is at most the number of assignments times the product of the +local bounds, which may vary with the relation symbol. -/ +theorem partitionFunction_le_prod {wt : Rel → List D → ℕ} {F : CSP Rel V} {b : Rel → ℕ} + (hb : ∀ c ∈ F, ∀ w : List D, w.length = c.2.length → wt c.1 w ≤ b c.1) : + F.partitionFunction wt ≤ Fintype.card D ^ F.numVars * (F.map fun c => b c.1).prod := by + rw [partitionFunction] + refine (Finset.sum_le_card_nsmul _ _ (F.map fun c => b c.1).prod fun σ _ => ?_).trans_eq ?_ + · rw [weight] + exact List.prod_le_prod fun c hc => hb c hc _ (List.length_map _) + · rw [Finset.card_univ, Fintype.card_fun, Fintype.card_coe, numVars, nsmul_eq_mul, Nat.cast_id] + /-- **The partition function is at most `|D| ^ n * W ^ m`** when every weight at a local assignment of the right length is at most `W`. -/ theorem partitionFunction_le {wt : Rel → List D → ℕ} {F : CSP Rel V} {W : ℕ} (hW : ∀ c ∈ F, ∀ w : List D, w.length = c.2.length → wt c.1 w ≤ W) : F.partitionFunction wt ≤ Fintype.card D ^ F.numVars * W ^ F.length := by - rw [partitionFunction] - refine (Finset.sum_le_card_nsmul _ _ (W ^ F.length) fun σ _ => ?_).trans ?_ - · rw [weight] - refine (List.prod_le_pow_length _ W fun x hx => ?_).trans_eq (by rw [List.length_map]) - obtain ⟨c, hc, rfl⟩ := List.mem_map.mp hx - exact hW c hc _ (List.length_map _) - · rw [Finset.card_univ, Fintype.card_fun, Fintype.card_coe, numVars, nsmul_eq_mul, Nat.cast_id] + simpa only [List.map_const', List.prod_replicate] using + partitionFunction_le_prod (b := fun _ => W) hW /-- **A constraint of weight zero makes the partition function zero**: if some constraint of `F` weighs zero at every local assignment of the right length, every assignment weighs zero. -/ diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkBinaryDigits.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkBinaryDigits.lean new file mode 100644 index 0000000000..52db2a7733 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkBinaryDigits.lean @@ -0,0 +1,54 @@ +/- +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.BinaryDigitsPolyTime + +/-! +# Exact binary-weight tensor recovery on finite examples +-/ + +public section + +namespace Cslib.Circuits.Frontier.ExplicitTensorNetwork.BinaryDigitsTests + +open Polynomial Turing.MultiTapeTM + +/-- Two unary tensors sharing an edge, with contraction `3 * 7 + 5 * 11`. -/ +@[expose] def weightedPair : ExplicitTensorNetwork ℕ := + ⟨1, [([0], [3, 5]), ([0], [7, 11])]⟩ + +example : binaryEntry [13] [] = [1, 0, 1, 1] ∧ binaryEntry [] [true] = [] := by decide + +example : weightedPair.contraction = 76 := by decide + +example : weightedPair.binaryDegreeBound 4 = 6 ∧ weightedPair.binaryPrecision 4 = 8 := by + decide + +example : (weightedPair.polyContraction binaryEntry).eval 2 = 76 := by + rw [eval_two_polyContraction_binaryEntry] + decide + +example : evalCoeffList 2 (weightedPair.truncContraction binaryEntry 8 7) = 76 := by + calc _ = weightedPair.contraction := + evalCoeffList_truncContraction_binaryEntry (B := 4) (by decide) + _ = 76 := by decide + +-- Automatic precision and truncation parameters need no separately supplied entry bound. +example (G : ExplicitTensorNetwork ℕ) : + evalCoeffList 2 (G.truncContraction binaryEntry (G.binaryPrecision G.binaryTableBound) + (G.binaryDegreeBound G.binaryTableBound + 1)) = G.contraction := + evalCoeffList_truncContraction_binaryTableBound G + +example : ComputableInPolyTime (fun q : List ℕ × List Bool => binaryEntry q.1 q.2) + (encPair (encList encNat) encBits) (encList encNat) := binaryEntry_polyTime + +example : ComputableInPolyTime binaryTableBound (DescribedNetwork.encDescribed (encList encNat)) + encUnary := binaryTableBound_polyTime + +end Cslib.Circuits.Frontier.ExplicitTensorNetwork.BinaryDigitsTests diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkBinaryWeights.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkBinaryWeights.lean new file mode 100644 index 0000000000..944275222b --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkBinaryWeights.lean @@ -0,0 +1,56 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: local-only (test driver) + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.BinaryWeights + +/-! +# Native weighted contraction regression tests + +The large natural table entries exceed a fixed small numeric weight bound, but their binary +lengths fit the polynomial promise. The native certificate computes the exact natural contraction +and its runtime theorem has an actual rows-first instance. The zero-edge case remains correct +even though the asymptotic resource theorem starts at two edges. +-/ + +public section + +namespace ComplexityTests.TensorNetworkBinaryWeights + +open Cslib.Circuits.Frontier Turing.MultiTapeTM + +def weighted : ExplicitTensorNetwork ℕ := + ⟨2, [([0, 1], [2 ^ 32, 0, 3, 5]), ([0, 1], [1, 1, 1, 1])]⟩ + +example : weighted.IsRowsFirstBipartite 1 := by decide + +example : weighted.binaryTableBound ≤ 100 * (weighted.numEdges + 1) ^ 2 := by decide + +-- A genuine native machine, with all assumptions of its rate theorem discharged. +example : ∃ K : ℕ, + (BinaryWeights.certificate 2).time (1, weighted) ≤ + K * ((BinaryWeights.encInput (1, weighted)).length + 1) ^ K * + 2 ^ (K * 2 / Nat.sqrt (Nat.log 2 2)) ∧ + (BinaryWeights.certificate 2).space (1, weighted) ≤ + K * ((BinaryWeights.encInput (1, weighted)).length + 1) ^ K := by + obtain ⟨K, hK⟩ := BinaryWeights.certificate_bound 2 1 100 2 + exact ⟨K, hK 1 weighted (by decide) (by decide) (by decide) (by decide) (by decide)⟩ + +example : IsComputable (fun x : ℕ × ExplicitTensorNetwork ℕ => x.2.contraction) + BinaryWeights.encInput encNat := (BinaryWeights.certificate 2).isComputable + +-- Empty networks retain exact correctness without any structural premise. +example : Polynomial.evalCoeffList 2 + ((⟨0, []⟩ : ExplicitTensorNetwork ℕ).truncContraction ExplicitTensorNetwork.binaryEntry + (ExplicitTensorNetwork.binaryPrecision 1 ⟨0, []⟩) + (ExplicitTensorNetwork.binaryDegreeBound 1 ⟨0, []⟩ + 1)) = 1 := by + rw [ExplicitTensorNetwork.evalCoeffList_truncContraction_binaryEntry + (by simp : ∀ v ∈ ([] : List (List ℕ × List ℕ)), ∀ n ∈ v.2, n.size ≤ 1)] + simp [ExplicitTensorNetwork.contraction] + +end ComplexityTests.TensorNetworkBinaryWeights diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkEntryAnalysis.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkEntryAnalysis.lean index 4afe8c6a21..5c9017b397 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkEntryAnalysis.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkEntryAnalysis.lean @@ -121,4 +121,22 @@ example : ∃ C K : ℕ, EntryAdmissible tableEntryCertificate C 2 (1, twoEdges) obtain ⟨K, hK⟩ := tableAlgorithm.bound C 1 exact ⟨C, K, hC, hK 2 1 1 twoEdges le_rfl le_rfl (by norm_num) hC⟩ +/-! ### Logarithmic root precision -/ + +-- The clock charges one unit per block of logarithmically many precision bits. +example : normalizedPrecision 1024 11264 = 1024 := by decide + +-- Precision four exceeds the linear allowance D*s at D=1 and s=2, but lies within +-- D*s*(log₂ s+1). The same concrete machine satisfies the subexponential resource bounds. +example : ∃ C K : ℕ, EntryAdmissible tableEntryCertificate C 2 (1, twoEdges) ∧ + tableAlgorithm.certificate.time (4, FastContraction.modulus 2, 1, twoEdges) ≤ + K * ((encInput (encList encNat) (4, FastContraction.modulus 2, 1, twoEdges)).length + + 1) ^ K * 2 ^ (K * 2 / Nat.sqrt (Nat.log 2 2)) ∧ + tableAlgorithm.certificate.space (4, FastContraction.modulus 2, 1, twoEdges) ≤ + K * ((encInput (encList encNat) (4, FastContraction.modulus 2, 1, twoEdges)).length + + 1) ^ K := by + obtain ⟨C, hC⟩ := exists_entryAdmissible_twoEdges + obtain ⟨K, hK⟩ := tableAlgorithm.bound_logPrecision C 1 + exact ⟨C, K, hC, hK 2 4 1 twoEdges le_rfl (by norm_num) (by decide) hC⟩ + end ComplexityTests.TensorNetworkEntryAnalysis diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean index ef6a4eedd9..b458186d66 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean @@ -42,13 +42,14 @@ example (d : ℕ) : taper 1024 d = 0 := by example : degreeBound 1024 = 2 ^ 11 := by rw [degreeBound, log_two_1024] -example : precisionBound 3 1024 0 = 3072 := rfl +example : precisionBound 3 1024 0 = 33792 := by + rw [precisionBound, log_two_1024] /-- One level adds `8 C s (4 · 2 ^ a_0 + a_0 + 1)` with `a_0 = 0`. -/ -example : precisionBound 3 1024 1 = 3072 + 8 * 3 * 1024 * 5 := by +example : precisionBound 3 1024 1 = 33792 + 8 * 3 * 1024 * 5 := by have h : slotBits 1024 0 = 0 := by simp [slotBits, prefixBits, taper, log_two_1024] - simp [precisionBound, h] + simp [precisionBound, log_two_1024, h] example : nodePrecision 5 2 3 7 1 = 5 + 8 * 2 * 4 + 8 * 7 * 4 := rfl @@ -63,6 +64,12 @@ example : Active 1 1024 0 1000 5 := Active.root (by norm_num) (by rw [show leafSize 1024 = 341 by rw [leafSize, log_two_1024, sqrt_10]]; norm_num) (by norm_num) +/-- The enlarged root allowance includes precision `11264`, exceeding the old linear bound. -/ +example : Active 1 1024 0 1000 11264 := + Active.root_logPrecision (by norm_num) + (by rw [leafSize, log_two_1024, sqrt_10]; norm_num) + (by rw [log_two_1024]) + /-- A child with `62` edges is not active at `s = 1024`: it is a leaf. -/ example : ¬ Active 1 1024 1 62 5 := fun h => by have := h.leafSize_lt diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedAlgorithm.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedAlgorithm.lean index 37c3f0529d..7c957257d4 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedAlgorithm.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedAlgorithm.lean @@ -41,7 +41,7 @@ networks with boundedly many edges. -/ noncomputable def boundedAlgorithm : TruncatedContractionAlgorithm (encList encNat) constEntry BoundedEdges where certificate := truncDirectCertificate constEntry constCert - bound C _ := by + bound_polynomial C _ _ := by obtain ⟨K, hK⟩ := truncDirectCertificate_le (encT := encList encNat) constEntry constCert obtain ⟨p, hp, hm, hb⟩ := exists_mono_poly_cert (ComputableInPolyTime.fst (encList encNat) encBits) @@ -105,4 +105,15 @@ example {C D s P ℓ r : ℕ} {G : DescribedNetwork (List ℕ)} (hs : 4 * (C * D truncGuard (P, ℓ, r, G) = true := truncGuard_of_le hs hℓ hk hm +-- The larger truncation range has constants fixed before the network. +example {C D d s P ℓ r : ℕ} {G : DescribedNetwork (List ℕ)} + (hs : max (C * D) (2 ^ (2 ^ (d + 2))) ≤ s) (hℓ : ℓ ≤ D * (s + 1) ^ d) + (hk : G.vertices.length ≤ C * s) (hm : s ≤ G.numEdges) : + truncGuard (P, ℓ, r, G) = true := + truncGuard_of_polynomial_le hs hℓ hk hm + +-- The fixed modulus has enough points for a fourth power beyond an explicit threshold. +example : (65536 + 1) ^ 4 ≤ 2 ^ (FastContraction.modulus 65536).length := + FastContraction.pow_le_card_modulus (by norm_num) + end ComplexityTests.TensorNetworkTruncatedAlgorithm diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedEntry.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedEntry.lean index b9ccd07f65..3b5efa9b5c 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedEntry.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTruncatedEntry.lean @@ -87,4 +87,18 @@ example (C D : ℕ) : ∃ K : ℕ, ∀ (s P ℓ r : ℕ) (G : DescribedNetwork ( K * ((encTrunc (encList encNat) (P, ℓ, r, G)).length + 1) ^ K := tableTruncAlgorithm.bound C D +-- The same machine's bound covers precision and order both beyond the old `D * s` range. +example : ∃ K : ℕ, + tableTruncAlgorithm.certificate.time (4, 8, 1, twoEdges) ≤ + K * ((encTrunc (encList encNat) (4, 8, 1, twoEdges)).length + 1) ^ K * + 2 ^ (K * 2 / Nat.sqrt (Nat.log 2 2)) ∧ + tableTruncAlgorithm.certificate.space (4, 8, 1, twoEdges) ≤ + K * ((encTrunc (encList encNat) (4, 8, 1, twoEdges)).length + 1) ^ K := by + obtain ⟨C, hC1, hC⟩ := exists_entryAdmissible_twoEdges + obtain ⟨K, hK⟩ := tableTruncAlgorithm.bound_polynomial C 1 3 + refine ⟨K, hK 2 4 8 1 twoEdges (by norm_num) (by norm_num) (by decide) + (by norm_num) (by norm_num) ⟨hC, ?_⟩⟩ + simp only [List.length_cons, List.length_nil] + omega + end ComplexityTests.TensorNetworkTruncatedEntry diff --git a/ComplexityTests/Computability/Complexity/FineGrained/BinaryWeightedCounting.lean b/ComplexityTests/Computability/Complexity/FineGrained/BinaryWeightedCounting.lean new file mode 100644 index 0000000000..ea30c43489 --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/BinaryWeightedCounting.lean @@ -0,0 +1,70 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.BinaryWeightedCounting + +/-! +# Native exact counting with input-supplied weight tables + +The relation symbol is itself a list of binary naturals. The checked unary example has weights +five and thirteen, accepts its adequate bit budget, and rejects an inadequate budget. The native +machine certificate uses ordinary polynomial-time table lookup for its local evaluator. +-/ + +public section + +namespace Cslib.Complexity.BinaryWeightedCountingTests + +open Cslib.Logic.PL Cslib.Circuits.Frontier Cslib.Circuits.Frontier.ClauseNetwork + Turing.MultiTapeTM Cslib.GenericRAM BinaryWeightedCounting + +/-- One unary relation whose two table entries are part of the input. -/ +@[expose] def weightedVariable : CSP (List ℕ) ℕ := [([5, 13], [0])] + +private theorem tableWeight_polyTime (w : List Bool) : + ComputableInPolyTime (fun table : List ℕ => ExplicitTensorNetwork.entry table w) + (encList encNat) encNat := + ComputableInPolyTime.listGetDWithInput ComputableInPolyTime.id' + (ComputableInPolyTime.const (BitString.lsbValue w)) 0 + +example : weightedVariable.partitionFunction ExplicitTensorNetwork.entry = 18 := by decide + +example : guard 1 16 1 ExplicitTensorNetwork.entry weightedVariable = true := by decide + +example : count 1 16 1 ExplicitTensorNetwork.entry weightedVariable = 18 := by + rw [count_eq (by decide) (by decide)] + decide + +example : count 1 0 1 ExplicitTensorNetwork.entry weightedVariable = 0 := by decide + +example : count 1 16 1 ExplicitTensorNetwork.entry ([] : CSP (List ℕ) ℕ) = 1 := count_nil .. + +example : count 1 16 1 ExplicitTensorNetwork.entry [([5, 13], [])] = 0 := by decide + +-- The supplied local weight certificate produces an ordinary native machine for the input tables. +example : ComputableInO (count 1 16 1 ExplicitTensorNetwork.entry) + (encCSP (encList encNat) encNat) encNat + (certificate (encList encNat) 1 16 1 ExplicitTensorNetwork.entry + (fun w _ => tableWeight_polyTime w)).time + (certificate (encList encNat) 1 16 1 ExplicitTensorNetwork.entry + (fun w _ => tableWeight_polyTime w)).space := + (certificate (encList encNat) 1 16 1 ExplicitTensorNetwork.entry + (fun w _ => tableWeight_polyTime w)).computes + +-- Per-entry four-bit bounds imply the network's total table-bit budget, with explicit constants. +example : (cspNetwork 1 ExplicitTensorNetwork.entry weightedVariable).2.binaryTableBound ≤ + 49 * ((cspNetwork 1 ExplicitTensorNetwork.entry weightedVariable).2.numEdges + 1) ^ 3 := by + apply binaryTableBound_cspNetwork_le 2 (by decide) + intro c hc w hw + have hc' : c = ([5, 13], [0]) := by simpa [weightedVariable] using hc + subst c + apply ExplicitTensorNetwork.size_entry_le + decide + +end Cslib.Complexity.BinaryWeightedCountingTests diff --git a/ComplexityTests/Computability/Complexity/FineGrained/MaxSATHistogram.lean b/ComplexityTests/Computability/Complexity/FineGrained/MaxSATHistogram.lean new file mode 100644 index 0000000000..7704afa062 --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/MaxSATHistogram.lean @@ -0,0 +1,44 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +import Complexity.Computability.Complexity.FineGrained.MaxSAT.Histogram + +/-! +# Exact MAX-SAT histogram edge cases + +The finite examples distinguish the canonical declared assignment count, clause multiplicities, +empty clauses, and the total convention for undeclared references. +-/ + +open Cslib.Complexity.MaxSAT + +example : histogram (0, []) = [1] := by decide +example : optimum (0, []) = 0 := by decide + +/-- Three unused variables still give eight assignments of score zero. -/ +example : histogram (3, []) = [8] := by decide +example : thresholdCounts (3, []) = [8, 0] := by decide + +/-- Repeated clauses retain their multiplicity: score one is impossible here. -/ +example : histogram (1, [[(0, true)], [(0, true)]]) = [1, 0, 1] := by decide +example : optimum (1, [[(0, true)], [(0, true)]]) = 2 := by decide + +/-- Opposite literals and an empty clause force score one; the second declared variable is free. -/ +example : histogram (2, [[(0, true)], [(0, false)], []]) = [0, 4, 0, 0] := by decide +example : optimum (2, [[(0, true)], [(0, false)], []]) = 1 := by decide + +/-- With no declared variables, the undeclared variable reads false. -/ +example : histogram (0, [[(0, true)], [(0, false)]]) = [0, 1, 0] := by decide + +/-- The runtime postprocessors agree with the exact semantics on a nontrivial example. -/ +example : histogramFromCounts (thresholdCounts (2, [[(0, true)], [(0, false)], []])) = + [0, 4, 0, 0] := by decide +example : optimumFromCounts (thresholdCounts (2, [[(0, true)], [(0, false)], []])) = 1 := by decide + +/-- Width and density permit the empty instance even at zero parameters. -/ +example : SparseInstance 0 0 := ⟨(0, []), by simp⟩ diff --git a/ComplexityTests/Foundations/Data/Polynomial/BinaryDigits.lean b/ComplexityTests/Foundations/Data/Polynomial/BinaryDigits.lean new file mode 100644 index 0000000000..42f8039710 --- /dev/null +++ b/ComplexityTests/Foundations/Data/Polynomial/BinaryDigits.lean @@ -0,0 +1,42 @@ +/- +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.Polynomial.BinaryDigits +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Polynomial + +/-! +# Checks for binary-digit polynomial encoding and exact recovery +-/ + +public section + +namespace Polynomial.BinaryDigitsTests + +example : (binaryDigits 13).coeff 0 = 1 ∧ (binaryDigits 13).coeff 1 = 0 ∧ + (binaryDigits 13).coeff 2 = 1 ∧ (binaryDigits 13).coeff 3 = 1 := by + simp only [binaryDigits, coeff_ofCoeffList] + decide + +example : (binaryDigits 0).eval 2 = 0 ∧ (binaryDigits 13).eval 2 = 13 := by simp + +example : (binaryDigits 13).eval 1 ≤ 4 ∧ (binaryDigits 13).natDegree ≤ 3 := + ⟨eval_one_binaryDigits_le 13, natDegree_binaryDigits_le 13⟩ + +-- The recovered coefficients need not themselves be bits. +example : evalCoeffList 2 [9, 5, 7, 3] = 71 := by decide + +example (p : ℕ[X]) (hdeg : p.natDegree < 4) (hsize : p.eval 1 < 32) : + ofCoeffList ((List.range 4).map fun i => p.coeff i % 32) = p := + ofCoeffList_coeffsBelow_mod hdeg hsize + +open Turing.MultiTapeTM in +example : ComputableInPolyTime (evalCoeffList 2) (encList encNat) encNat := + ComputableInPolyTime.evalCoeffList_two + +end Polynomial.BinaryDigitsTests diff --git a/ComplexityTests/Logics/Propositional/CSP/BinaryDigits.lean b/ComplexityTests/Logics/Propositional/CSP/BinaryDigits.lean new file mode 100644 index 0000000000..675a26f52b --- /dev/null +++ b/ComplexityTests/Logics/Propositional/CSP/BinaryDigits.lean @@ -0,0 +1,61 @@ +/- +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.Logics.Propositional.CSP.BinaryDigits +import Mathlib.Data.Fintype.BigOperators + +/-! +# Weighted CSP recovery through binary digits +-/ + +public section + +namespace Cslib.Logic.PL.CSP.BinaryDigitsTests + +open Polynomial + +/-- One unary constraint, so the two local weights are simply added. -/ +@[expose] def oneVariable : CSP Unit ℕ := [((), [0])] + +/-- Unequal four-bit and three-bit weights on the two assignments. -/ +@[expose] def localWeight (_ : Unit) (w : List Bool) : ℕ := if w.headD false then 13 else 5 + +private theorem weight_bits : + ∀ c ∈ oneVariable, ∀ w : List Bool, w.length = c.2.length → + (localWeight c.1 w).size ≤ 4 := by + intro c hc w hw + unfold localWeight + split <;> decide + +example : oneVariable.partitionFunction localWeight = 18 := by decide + +example : oneVariable.binaryDegreeBound (fun _ => 4) = 3 ∧ + oneVariable.binaryPrecision (fun _ => 4) = 5 := by decide + +example : (oneVariable.binaryPartitionPolynomial localWeight).eval 2 = 18 := by + rw [eval_two_binaryPartitionPolynomial] + decide + +-- Only four coefficients modulo 32 are needed although one weight already has four bits. +example : evalCoeffList 2 ((List.range 4).map fun i => + (oneVariable.binaryPartitionPolynomial localWeight).coeff i % 32) = 18 := by + calc _ = oneVariable.partitionFunction localWeight := + evalCoeffList_binaryPartitionPolynomial_residues (b := fun _ => 4) weight_bits + _ = 18 := by decide + +-- Empty CSPs retain their multiplicative empty-product value over arbitrary semirings. +example : partitionFunction (fun (_ : Unit) (_ : List Bool) => (1 : ℕ[X])) + ([] : CSP Unit ℕ) = 1 := by + rw [partitionFunction] + change (∑ _ : ((vars ([] : CSP Unit ℕ)).toFinset → Bool), (1 : ℕ[X])) = 1 + rw [Finset.sum_const, Finset.card_univ] + have h : Fintype.card ((vars ([] : CSP Unit ℕ)).toFinset → Bool) = 1 := by decide + rw [h, one_nsmul] + +end Cslib.Logic.PL.CSP.BinaryDigitsTests diff --git a/ComplexityTests/MaxSAT.lean b/ComplexityTests/MaxSAT.lean new file mode 100644 index 0000000000..4344695229 --- /dev/null +++ b/ComplexityTests/MaxSAT.lean @@ -0,0 +1,62 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.Complexity.FineGrained.MaxSAT.PolyTime + +/-! # Exact MAX-SAT compilation: checked examples + +The sample has opposing unit clauses, a tautological clause, an empty clause, and a nontrivial +last clause. It checks threshold multiplicities and free declared variables, including zero +clauses, zero variables, an impossible cutoff, and an invalid reference's documented default. +-/ + +namespace ComplexityTests.MaxSAT + +open Cslib Cslib.Circuits Cslib.Circuits.Boolean Cslib.Complexity Cslib.Complexity.MaxSAT + Turing.MultiTapeTM + +/-- Every assignment satisfies the first pair's exactly one clause and the tautology; the last +clause contributes one more unless `x₀` is false and `x₂` is true. -/ +def sample : Instance := + (3, [[(0, true)], [(0, false)], [(1, true), (1, false)], [], [(2, false), (0, true)]]) + +example : score sample ![false, false, true] = 2 ∧ + score sample ![true, false, true] = 3 := by decide + +example : thresholdCount sample 0 = 8 ∧ thresholdCount sample 2 = 8 ∧ + thresholdCount sample 3 = 6 ∧ thresholdCount sample 4 = 0 := by decide + +example : (thresholdCircuit sample 3).satCount interpretation = 6 := by + rw [satCount_thresholdCircuit] + decide + +-- An unused declared variable doubles every threshold count. +example : thresholdCount (4, sample.2) 3 = 12 := by decide + +-- Empty formulas still count all declared assignments at cutoff zero. +example : thresholdCount (2, []) 0 = 4 ∧ thresholdCount (2, []) 1 = 0 := by decide +example : thresholdCount (0, []) 0 = 1 ∧ thresholdCount (0, [[]]) 1 = 0 := by decide + +-- Repeated clauses carry multiplicity in the objective. +example : score (1, [[(0, true)], [(0, true)]]) ![true] = 2 ∧ + thresholdCount (1, [[(0, true)], [(0, true)]]) 2 = 1 := by decide + +-- A reference outside the declared range reads false, preserving the specified total semantics. +example : thresholdCount (1, [[(7, false)]]) 1 = 2 ∧ + thresholdCount (1, [[(7, true)]]) 1 = 0 := by decide + +-- The whole native counter, specialized to the checked nontrivial count. +example : ∃ (count : Query → ℕ) (A e b : ℕ), count (sample, 3) = 6 ∧ + ComputableInO count encQuery encNat + (fun p => 2 ^ (A * (workSize p.1 + 1) / Nat.sqrt (Nat.log 2 (workSize p.1 + 2))) * + ((encQuery p).length + 1) ^ e) + (fun p => ((encQuery p).length + 1) ^ b) := by + obtain ⟨count, A, e, b, hspec, hinO⟩ := exists_thresholdCounter + refine ⟨count, A, e, b, ?_, hinO⟩ + rw [hspec] + decide + +end ComplexityTests.MaxSAT diff --git a/ROADMAP.md b/ROADMAP.md index 11d44c8c06..1934915fd9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -698,8 +698,19 @@ and credited. `ContractionCounting.exists_modelCounter_fixedDensity` proves the fixed-density bound `2^{O_{k,D}(n / √log n)}` by tensor contraction (Theorem D.1 with `ℓ = 1`), and `DensityBranchCounting.exists_modelCounter` combines it with the interpolation-scale counter in - one machine. The arbitrary-density bound `2^{O_k(n log log n / √log n)}` still needs Theorem D.1 - with truncation order `ℓ = n + 1` and Lemma D.2. + one machine. `CountingSimulation.truncEntryContractionAlgorithm` now supplies polynomial + truncation order and precision `O(s log s)`, with a linear vertex bound. The arbitrary-density + bound `2^{O_k(n log log n / √log n)}` still needs the Lemma D.2 construction and its native + application to this interface; #1538 remains open. +- **Exact optimization and large weights:** `MaxSAT.exists_histogram_algorithm` and + `MaxSAT.exists_optimum_algorithm` compute the complete score distribution and optimum value in + contraction time measured by literal occurrences plus clauses. Their sparse fixed-width + specializations give `2^{O_{k,c}(n / √log n)}` time and polynomial space. + `BinaryWeights.certificate` contracts natural tensor tables exactly when their bit lengths are + polynomial in the structural size; `BinaryWeightedCounting.exists_partitionFunctionCounter` + supplies the native weighted-CSP application. Remaining extensions are native optimizing + assignment extraction and signed or rational weight interfaces. A variable-count rate for + dense exact MAX-SAT is not established by these results or by ordinary SAT sparsification. ## Documentation and repository