From 70dee0bb4c2aa3c876986da2eab382c7b5fa9324 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 21:24:12 -0400 Subject: [PATCH 01/24] feat: bound marked occurrences in counting paths --- .../Sparsification/CountingOccurrences.lean | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingOccurrences.lean diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingOccurrences.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingOccurrences.lean new file mode 100644 index 000000000..d89f6c4b9 --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingOccurrences.lean @@ -0,0 +1,175 @@ +/- +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.ClauseSet.Sparsification.CountingUnits + +/-! +# Marked clause occurrences in counting sparsification + +Appendix A.2, Lemma A.2 [OpenAI2026ETH] bounds the marked clauses still present at a node. A +clause present in the root is excluded. This is distinct from the sum of insertion events in +`CountingUnits`: a higher-width clause may later be deleted. + +The minimum-width and maximum-heart priorities of `Choice` control the old clauses and the new +petals, respectively. This follows the existing proof of `Path.card_newContaining_lt` +[Calabro, Lemma 5.9][Calabro2009], applied directly to counting steps. The B-child's additional +negated-heart units have width one and cannot enter the filter considered here, whose width is at +least two. No decision-path `Refines` or tree-size theorem is transferred. + +This present-clause upper bound requires positive thresholds and valid choices, but not root +reducedness or nontautology. Those stronger conditions remain relevant to later immediate-survival +and deletion-charge arguments. No higher-width insertion-event, depth, leaf or runtime bound is +asserted here. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.card_lt_of_mem_countingIntro`: payload widths decrease. +* `Cslib.Logic.PL.ClauseSet.Sparsification.card_counting_newContaining_lt`: marked-present bound. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-- Every counting payload clause is smaller than the selected width, including negated units. -/ +theorem card_lt_of_mem_countingIntro {θ : ℕ → ℕ} {ψ : ClauseSet V} {c : ℕ} + {H : Finset (Literal V)} (h : Heavy θ ψ c H) {b : Bool} {C : Finset (Literal V)} + (hC : C ∈ countingIntro ψ c H b) : C.card < c := by + cases b with + | false => + have hCH : C = H := by simpa only [countingIntro, mem_singleton] using hC + simpa only [hCH] using h.2.1 + | true => + rcases mem_union.mp hC with hC | hC + · rw [card_of_mem_petals hC] + have := h.1 + have := h.2.1 + omega + · obtain ⟨l, -, rfl⟩ := mem_image.mp hC + simp only [card_singleton] + have := h.1 + have := h.2.1 + omega + +private theorem mem_petals_of_mem_countingIntro_true {ψ : ClauseSet V} {c : ℕ} + {H C : Finset (Literal V)} (hC : C ∈ countingIntro ψ c H true) (hwidth : 2 ≤ C.card) : + C ∈ petals ψ c H := by + rcases mem_union.mp hC with hC | hC + · exact hC + · obtain ⟨l, -, hCl⟩ := mem_image.mp hC + have hcard : C.card = 1 := by rw [← hCl]; exact card_singleton _ + omega + +/-- At every prefix of an actual counting path, fewer than `2 * θ (j - |S|)` root-absent clauses +of width `j` containing a fixed nonempty proper literal set `S` are present. + +The selected choices retain both priority conditions. The additional B-units are explicitly +excluded using `j ≥ 2`. Root clauses remain excluded, and no ambient finite variable type or +resource assumption is needed. This counts clauses currently present, not all insertion events. +-/ +theorem card_counting_newContaining_lt {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice θ (ψ i) (c i) (H i)) + {t j : ℕ} (ht : t ≤ N) {S : Finset (Literal V)} (hS : 1 ≤ S.card) (hSj : S.card < j) : + ((ψ t).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ S ⊆ C).card < 2 * θ (j - S.card) := by + set γ := (ψ t).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ S ⊆ C + by_contra hge + push Not at hge + have hγ : ∀ C ∈ γ, C ∉ ψ 0 ∧ C.card = j ∧ S ⊆ C := fun C hC => (mem_filter.mp hC).2 + have hex : ∃ m, γ ⊆ ψ m := ⟨t, filter_subset _ _⟩ + set x := Nat.find hex + have hxsub : γ ⊆ ψ x := Nat.find_spec hex + have hxle : x ≤ t := Nat.find_min' hex (filter_subset _ _) + have hγne : γ.Nonempty := by rw [← card_pos]; have := hθ (j - S.card); omega + have hx0 : x ≠ 0 := by + intro hx + obtain ⟨C, hC⟩ := hγne + exact (hγ C hC).1 (hx ▸ hxsub hC) + obtain ⟨m, hxm⟩ : ∃ m, x = m + 1 := ⟨x - 1, by omega⟩ + have hm : m < N := by omega + have hnotsub : ¬ γ ⊆ ψ m := Nat.find_min hex (by omega) + have hnext : ψ (m + 1) ⊆ ψ m ∪ countingIntro (ψ m) (c m) (H m) (b m) := by + rw [hstep m hm, countingChild_eq_reduce_union] + exact reduce_subset _ + obtain ⟨C₀, hC₀γ, hC₀old⟩ := not_subset.mp hnotsub + have hC₀intro : C₀ ∈ countingIntro (ψ m) (c m) (H m) (b m) := by + exact (mem_union.mp (hnext (hxm ▸ hxsub hC₀γ))).resolve_left hC₀old + have hjc : j < c m := by + have := card_lt_of_mem_countingIntro (hchoice m hm).1 hC₀intro + rwa [(hγ C₀ hC₀γ).2.1] at this + have hlight : (flower (ψ m) j S).card < θ (j - S.card) := by + by_contra hheavy + push Not at hheavy + have := (hchoice m hm).2.1 j S ⟨hS, hSj, hheavy⟩ + omega + have hold : (γ.filter (· ∈ ψ m)).card < θ (j - S.card) := by + refine lt_of_le_of_lt (card_le_card fun C hC => ?_) hlight + obtain ⟨hCγ, hCold⟩ := mem_filter.mp hC + exact mem_flower.mpr ⟨hCold, (hγ C hCγ).2.1, (hγ C hCγ).2.2⟩ + set Q := γ.filter (· ∉ ψ m) + have hsplit : (γ.filter (· ∈ ψ m)).card + Q.card = γ.card := + card_filter_add_card_filter_not (s := γ) (· ∈ ψ m) + have hQcard : θ (j - S.card) + 1 ≤ Q.card := by omega + have hQintro : Q ⊆ countingIntro (ψ m) (c m) (H m) (b m) := by + intro C hC + obtain ⟨hCγ, hCold⟩ := mem_filter.mp hC + exact (mem_union.mp (hnext (hxm ▸ hxsub hCγ))).resolve_left hCold + cases hdir : b m with + | false => + have hQone : Q.card ≤ 1 := by + have := card_le_card hQintro + simpa only [hdir, countingIntro, card_singleton] using this + have := hθ (j - S.card) + omega + | true => + have hQpetal : ∀ q ∈ Q, q ∈ petals (ψ m) (c m) (H m) := by + intro q hq + have hqintro := hQintro hq + rw [hdir] at hqintro + apply mem_petals_of_mem_countingIntro_true hqintro + have := (hγ q (mem_filter.mp hq).1).2.1 + omega + obtain ⟨q₀, hq₀⟩ : Q.Nonempty := by rw [← card_pos]; omega + obtain ⟨D₀, hD₀, hD₀q⟩ := mem_petals.mp (hQpetal q₀ hq₀) + have hSq₀ : S ⊆ q₀ := (hγ q₀ (mem_filter.mp hq₀).1).2.2 + have hdisj : Disjoint (H m) S := by + rw [Finset.disjoint_right] + intro l hlS hlH + have := hSq₀ hlS + rw [← hD₀q] at this + exact (mem_sdiff.mp this).2 hlH + have hcardHS : (H m ∪ S).card = (H m).card + S.card := card_union_of_disjoint hdisj + have hj : j = c m - (H m).card := by + rw [← (hγ q₀ (mem_filter.mp hq₀).1).2.1] + exact card_of_mem_petals (hQpetal q₀ hq₀) + have hHc : (H m).card < c m := (hchoice m hm).1.2.1 + have hQle : Q.card ≤ (flower (ψ m) (c m) (H m ∪ S)).card := by + have hsub : Q ⊆ ((flower (ψ m) (c m) (H m)).filter (S ⊆ ·)).image (· \ H m) := by + intro q hq + obtain ⟨D, hD, rfl⟩ := mem_petals.mp (hQpetal q hq) + have hSD : S ⊆ D \ H m := (hγ _ (mem_filter.mp hq).1).2.2 + exact mem_image.mpr ⟨D, mem_filter.mpr ⟨hD, hSD.trans sdiff_subset⟩, rfl⟩ + refine (card_le_card hsub).trans (card_image_le.trans (card_le_card fun D hD => ?_)) + obtain ⟨hDf, hSD⟩ := mem_filter.mp hD + obtain ⟨hDψ, hDcard, hHD⟩ := mem_flower.mp hDf + exact mem_flower.mpr ⟨hDψ, hDcard, union_subset hHD hSD⟩ + have hheavy : Heavy θ (ψ m) (c m) (H m ∪ S) := by + refine ⟨by omega, by omega, ?_⟩ + have heq : c m - (H m ∪ S).card = j - S.card := by omega + rw [heq] + omega + have := (hchoice m hm).2.2 (H m ∪ S) hheavy + omega + +end Cslib.Logic.PL.ClauseSet.Sparsification From 59acdc0f6e83bcbd83716c6e3217e2cacdb41075 Mon Sep 17 00:00:00 2001 From: Anton Kovsharov Date: Tue, 27 Oct 2026 01:09:57 +0000 Subject: [PATCH 02/24] feat(ClauseSet): recursive counting-sparsification tree and leaf model-count conservation --- Complexity.lean | 1 + .../Sparsification/CountingTree.lean | 748 ++++++++++++++++++ .../SparsificationCountingTree.lean | 263 ++++++ 3 files changed, 1012 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean create mode 100644 ComplexityTests/SparsificationCountingTree.lean diff --git a/Complexity.lean b/Complexity.lean index a721be0d6..90839e29d 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2169,6 +2169,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Basic public import Complexity.Logics.Propositional.ClauseSet.Sparsification public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean new file mode 100644 index 000000000..042654afd --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean @@ -0,0 +1,748 @@ +/- +Copyright (c) 2026 Anton Kovsharov. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anton Kovsharov +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Tree +public import Mathlib.Data.Finset.Powerset +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.Group.Action.Defs + +/-! +# Recursive counting-sparsification tree + +The decision sparsification tree (`BranchingRule` in `Sparsification/Tree.lean`) fixes its step to +`child`, whose branches cover the parent's satisfying assignments but may overlap. For exact model +counting (`Counting.lean`), `countingChild` refines the petal branch (`true`) by adjoining the unit +clauses `negatedHeartUnits H` requiring every literal of `H` to be false, matching equation (A.2) +in Appendix A.1 of [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH] (pp. 44–45). + +Adding `negatedHeartUnits H` contributes up to `2 |H|` to the clause-width potential +`∑_{C ∈ ψ} 2 ^ |C|`. A single width-two clause (`z = 1`, `c = 2`, `|H| = 1`) can therefore be +replaced by one petal unit and one negated-heart unit of equal total potential `2 + 2 = 4`. +Whenever the selected flower has at least two clauses (`2 ≤ (flower ψ c H).card`), however, +removing the flower subtracts `z · 2 ^ c` while the petals and negated-heart units add at most +`z · 2 ^ (c - |H|) + 2 |H| < z · 2 ^ c` (`potential_countingChild_lt_of_two_le_card_flower`). Any +threshold schedule satisfying `2 ≤ θ j` for `1 ≤ j`—in particular the geometric schedule +`θ j = M ^ j` with `2 ≤ M` in Appendix A.1—guarantees `2 ≤ (flower ψ c H).card` at every heavy +heart (`potential_countingChild_lt`, `potential_countingChild_lt_pow`). + +Using this strict potential decrease, `countingLeaves` recursively enumerates the finite list of +leaves obtained from `chooseBranch` and `countingChild`. Along the recursion we establish: +* leaf sparsity (`sparse_of_mem_countingLeaves`) whenever `2 ≤ θ j` for `1 ≤ j`; +* preservation of declared variable support (`support_of_mem_countingLeaves`), clause width + (`card_le_of_mem_countingLeaves`), and reducedness (`reduced_of_mem_countingLeaves`); +* exact assignment partition: every satisfying assignment of the root satisfies a unique leaf + occurrence (`countP_sat_countingLeaves`, `sat_iff_existsUnique_fin_countingLeaves`, + `sat_iff_existsUnique_mem_countingLeaves`), while unsatisfying assignments satisfy none; +* exact additive conservation of `CNF.modelCountOn` on a single common declared variable set + (`sum_modelCountOn_countingLeaves`, `sum_modelCountOn_countingLeaves_toClauseSet`), including + unused declared variables, empty formulas, unit clauses, and contradictory formulas. + +Quantitative insertion, path-length, and `2 ^ (σ n)` leaf-count bounds (Appendix A.2–A.3, +Lemmas A.1–A.3) are separate results not claimed in this module. + +## Main definitions + +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingLeaves`: well-founded recursive leaf enumeration + for disjoint counting sparsification. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.potential_countingChild_lt_of_two_le_card_flower`: + strict potential decrease when the flower has at least two clauses. +* `Cslib.Logic.PL.ClauseSet.Sparsification.potential_countingChild_lt`: strict potential decrease + under `2 ≤ θ j` for `1 ≤ j`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.potential_countingChild_lt_pow`: strict potential + decrease for the geometric schedule `θ j = M ^ j` with `2 ≤ M`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.sparse_of_mem_countingLeaves`: every leaf is sparse when + `2 ≤ θ j` for `1 ≤ j`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.support_of_mem_countingLeaves`: declared-variable + support is preserved at every leaf. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countP_sat_countingLeaves`: exact `0`/`1` + leaf-occurrence multiplicity for every assignment. +* `Cslib.Logic.PL.ClauseSet.Sparsification.sum_modelCountOn_countingLeaves`: additive conservation + of `CNF.modelCountOn` across all leaves on a common declared variable set. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.1, pp. 44–45][OpenAI2026ETH] +* [C. Calabro, R. Impagliazzo, R. Paturi, *A duality between clause width and clause density for + SAT*][CalabroImpagliazzoPaturi2006] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-! ### Negated-heart units and counting-child potential -/ + +private theorem mem_negatedHeartUnits {H : Finset (Literal V)} {C : Finset (Literal V)} : + C ∈ negatedHeartUnits H ↔ ∃ l ∈ H, {Std.Sat.Literal.negate l} = C := + mem_image + +private theorem card_of_mem_negatedHeartUnits {H : Finset (Literal V)} {C : Finset (Literal V)} + (hC : C ∈ negatedHeartUnits H) : C.card = 1 := by + obtain ⟨l, -, rfl⟩ := mem_negatedHeartUnits.mp hC + exact card_singleton _ + +private theorem card_negatedHeartUnits (H : Finset (Literal V)) : + (negatedHeartUnits H).card = H.card := by + refine card_image_of_injective H fun a b hab => ?_ + have hneg : Std.Sat.Literal.negate a = Std.Sat.Literal.negate b := singleton_inj.mp hab + rcases a with ⟨va, ba⟩ + rcases b with ⟨vb, bb⟩ + simp only [Std.Sat.Literal.negate, Prod.mk.injEq] at hneg + exact Prod.ext hneg.1 (Bool.not_inj_iff.mp hneg.2) + +private theorem potential_negatedHeartUnits (H : Finset (Literal V)) : + potential (negatedHeartUnits H) = 2 * H.card := by + unfold potential + calc ∑ C ∈ negatedHeartUnits H, 2 ^ C.card + = ∑ _C ∈ negatedHeartUnits H, 2 ^ 1 := + sum_congr rfl fun C hC => by rw [card_of_mem_negatedHeartUnits hC] + _ = 2 * H.card := by + rw [sum_const, card_negatedHeartUnits, smul_eq_mul, pow_one, Nat.mul_comm] + +private theorem reduced_countingChild (ψ : ClauseSet V) (c : ℕ) (H : Finset (Literal V)) + (b : Bool) : Reduced (countingChild ψ c H b) := by + cases b <;> exact reduced_reduce _ + +private theorem disjoint_flower_countingChild (ψ : ClauseSet V) {c : ℕ} {H : Finset (Literal V)} + (h1 : 1 ≤ H.card) (hlt : H.card < c) (b : Bool) : + Disjoint (flower ψ c H) (countingChild ψ c H b) := by + rw [Finset.disjoint_left] + intro C hC hCchild + obtain ⟨-, hcard, hHC⟩ := mem_flower.mp hC + cases b with + | false => + have hmin := (mem_reduce.mp hCchild).2 + refine hmin H (mem_union_right _ (by simp only [intro, mem_singleton])) + (ssubset_of_subset_of_ne hHC ?_) + intro heq + rw [heq, hcard] at hlt + exact lt_irrefl _ hlt + | true => + have hmin := (mem_reduce.mp hCchild).2 + refine hmin (C \ H) ?_ (sdiff_ssubset hHC (card_pos.mp h1)) + exact mem_union_left _ (mem_union_right _ (mem_petals.mpr ⟨C, hC, rfl⟩)) + +private theorem card_le_of_mem_countingChild {ψ : ClauseSet V} {c k : ℕ} {H : Finset (Literal V)} + (h1 : 1 ≤ H.card) (hlt : H.card < c) (hF : (flower ψ c H).Nonempty) + (hk : ∀ C ∈ ψ, C.card ≤ k) {b : Bool} {C : Finset (Literal V)} + (hC : C ∈ countingChild ψ c H b) : C.card ≤ k := by + obtain ⟨D₀, hD₀⟩ := hF + have hck : c ≤ k := (mem_flower.mp hD₀).2.1 ▸ hk D₀ (flower_subset ψ c H hD₀) + cases b with + | false => + rcases mem_union.mp (child_subset ψ c H false hC) with hCψ | hCH + · exact hk C hCψ + · simp only [intro, mem_singleton] at hCH + subst hCH + omega + | true => + rcases mem_union.mp (reduce_subset _ hC) with hC' | hCneg + · rcases mem_union.mp hC' with hCψ | hCpet + · exact hk C hCψ + · have hp := card_of_mem_petals hCpet + omega + · have hu := card_of_mem_negatedHeartUnits hCneg + omega + +omit [DecidableEq V] in +private theorem potential_mono {α β : ClauseSet V} (h : α ⊆ β) : + potential α ≤ potential β := by + classical + unfold potential + have := sum_sdiff h (f := fun C : Finset (Literal V) => 2 ^ C.card) + omega + +private theorem potential_union_le (α β : ClauseSet V) : + potential (α ∪ β) ≤ potential α + potential β := by + unfold potential + have := sum_union_inter (s₁ := α) (s₂ := β) (f := fun C : Finset (Literal V) => 2 ^ C.card) + omega + +private theorem petal_unit_potential_bound {c h z : ℕ} (h1 : 1 ≤ h) (hlt : h < c) (hz : 2 ≤ z) : + z * 2 ^ (c - h) + 2 * h < z * 2 ^ c := by + have hj_le : c - h ≤ c - 1 := by omega + have hpow_j : 2 ^ (c - h) ≤ 2 ^ (c - 1) := Nat.pow_le_pow_right (by omega) hj_le + have hh_lt : h < 2 ^ (c - 1) := lt_of_le_of_lt (by omega : h ≤ c - 1) Nat.lt_two_pow_self + have hc_pow : 2 ^ c = 2 ^ (c - 1) + 2 ^ (c - 1) := by + conv_lhs => rw [show c = (c - 1) + 1 by omega, Nat.pow_succ] + omega + calc z * 2 ^ (c - h) + 2 * h + ≤ z * 2 ^ (c - 1) + 2 * h := + Nat.add_le_add_right (Nat.mul_le_mul_left z hpow_j) _ + _ < z * 2 ^ (c - 1) + 2 * 2 ^ (c - 1) := + Nat.add_lt_add_left (Nat.mul_lt_mul_of_pos_left hh_lt (by omega)) _ + _ ≤ z * 2 ^ (c - 1) + z * 2 ^ (c - 1) := + Nat.add_le_add_left (Nat.mul_le_mul_right _ hz) _ + _ = z * 2 ^ c := by rw [hc_pow, Nat.mul_add] + +/-- **Strict potential decrease for `countingChild`.** When `1 ≤ |H| < c` and `flower ψ c H` has at +least two clauses, both counting branches strictly decrease `potential`. On the petal branch +(`true`), removing `z ≥ 2` clauses of width `c` subtracts `z · 2 ^ c` while adding at most `z` +petals of width `c - |H|` and `|H|` negated units of width `1`, and +`z · 2 ^ (c - |H|) + 2 |H| < z · 2 ^ c`. -/ +theorem potential_countingChild_lt_of_two_le_card_flower + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (h1 : 1 ≤ H.card) (hlt : H.card < c) (hz : 2 ≤ (flower ψ c H).card) (b : Bool) : + potential (countingChild ψ c H b) < potential ψ := by + set F := flower ψ c H + have hFsplit : potential ψ = potential (ψ \ F) + potential F := by + unfold potential + rw [← sum_sdiff (flower_subset ψ c H)] + have hF : potential F = F.card * 2 ^ c := by + unfold potential + rw [sum_congr rfl fun C hC => by rw [(mem_flower.mp hC).2.1], sum_const, smul_eq_mul] + have hdisj := disjoint_flower_countingChild ψ h1 hlt b + cases b with + | false => + have hsub : countingChild ψ c H false ⊆ (ψ \ F) ∪ {H} := by + intro C hC + rcases mem_union.mp (child_subset ψ c H false hC) with hCψ | hCH + · exact mem_union_left _ (mem_sdiff.mpr ⟨hCψ, fun hCF => disjoint_left.mp hdisj hCF hC⟩) + · simp only [intro] at hCH + exact mem_union_right _ hCH + have hHlt : potential ({H} : ClauseSet V) < F.card * 2 ^ c := by + unfold potential + rw [sum_singleton] + calc 2 ^ H.card < 2 ^ c := Nat.pow_lt_pow_right (by omega) hlt + _ ≤ F.card * 2 ^ c := Nat.le_mul_of_pos_left _ (by omega) + calc potential (countingChild ψ c H false) + ≤ potential ((ψ \ F) ∪ {H}) := potential_mono hsub + _ ≤ potential (ψ \ F) + potential ({H} : ClauseSet V) := potential_union_le _ _ + _ < potential (ψ \ F) + potential F := by rw [hF]; exact Nat.add_lt_add_left hHlt _ + _ = potential ψ := hFsplit.symm + | true => + have hsub : countingChild ψ c H true ⊆ (ψ \ F) ∪ (petals ψ c H ∪ negatedHeartUnits H) := by + intro C hC + rcases mem_union.mp (reduce_subset _ hC) with hC' | hCneg + · rcases mem_union.mp hC' with hCψ | hCpet + · exact mem_union_left _ (mem_sdiff.mpr ⟨hCψ, fun hCF => disjoint_left.mp hdisj hCF hC⟩) + · exact mem_union_right _ (mem_union_left _ hCpet) + · exact mem_union_right _ (mem_union_right _ hCneg) + have hpet : potential (petals ψ c H) = F.card * 2 ^ (c - H.card) := by + unfold potential + calc ∑ C ∈ petals ψ c H, 2 ^ C.card + = ∑ _C ∈ petals ψ c H, 2 ^ (c - H.card) := + sum_congr rfl fun C hC => by rw [card_of_mem_petals hC] + _ = F.card * 2 ^ (c - H.card) := by rw [sum_const, card_petals, smul_eq_mul] + have hnew : potential (petals ψ c H ∪ negatedHeartUnits H) < F.card * 2 ^ c := by + calc potential (petals ψ c H ∪ negatedHeartUnits H) + ≤ potential (petals ψ c H) + potential (negatedHeartUnits H) := potential_union_le _ _ + _ = F.card * 2 ^ (c - H.card) + 2 * H.card := by rw [hpet, potential_negatedHeartUnits] + _ < F.card * 2 ^ c := petal_unit_potential_bound h1 hlt hz + calc potential (countingChild ψ c H true) + ≤ potential ((ψ \ F) ∪ (petals ψ c H ∪ negatedHeartUnits H)) := potential_mono hsub + _ ≤ potential (ψ \ F) + potential (petals ψ c H ∪ negatedHeartUnits H) := + potential_union_le _ _ + _ < potential (ψ \ F) + potential F := by rw [hF]; exact Nat.add_lt_add_left hnew _ + _ = potential ψ := hFsplit.symm + +/-- Any threshold schedule satisfying `2 ≤ θ j` for `1 ≤ j` guarantees at least two clauses in the +flower of every heavy heart. -/ +theorem two_le_card_flower_of_heavy {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (h : Heavy θ ψ c H) : + 2 ≤ (flower ψ c H).card := + (hθ (c - H.card) (by have := h.2.1; omega)).trans h.2.2 + +/-- **Termination under `2 ≤ θ j` (`1 ≤ j`).** Every heavy heart has a flower of size at least `2`, +so both counting children strictly decrease `potential`. -/ +theorem potential_countingChild_lt {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (h : Heavy θ ψ c H) (b : Bool) : + potential (countingChild ψ c H b) < potential ψ := + potential_countingChild_lt_of_two_le_card_flower h.1 h.2.1 + (two_le_card_flower_of_heavy hθ h) b + +/-- **Termination for geometric thresholds `θ j = M ^ j` (`2 ≤ M`)** (Appendix A.1, p. 44). -/ +theorem potential_countingChild_lt_pow {M : ℕ} (hM : 2 ≤ M) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (h : Heavy (M ^ ·) ψ c H) (b : Bool) : + potential (countingChild ψ c H b) < potential ψ := + potential_countingChild_lt + (fun _ hj => hM.trans + (by simpa only [pow_one] using Nat.pow_le_pow_right (by omega : 0 < M) hj)) + h b + +/-! ### Finite characterization of sparsity and unique choices -/ + +/-- When `1 ≤ θ j` for `1 ≤ j`, sparsity depends only on nonempty proper subsets of clauses present +in `ψ`, giving a finite criterion on any variable type with decidable equality. -/ +theorem sparse_iff_forall_mem_powerset {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ j → 1 ≤ θ j) + (ψ : ClauseSet V) : + Sparse θ ψ ↔ ∀ C ∈ ψ, ∀ H ∈ C.powerset, ¬ Heavy θ ψ C.card H := by + refine ⟨fun hs C _ H _ => hs C.card H, fun h c H hheavy => ?_⟩ + have hpos : 0 < (flower ψ c H).card := + lt_of_lt_of_le (hθ (c - H.card) (by have := hheavy.2.1; omega)) hheavy.2.2 + obtain ⟨C, hC⟩ := card_pos.mp hpos + obtain ⟨hCψ, hCcard, hHC⟩ := mem_flower.mp hC + exact h C hCψ H (mem_powerset.mpr hHC) (hCcard ▸ hheavy) + +/-- If `(c, H)` is a valid `Choice` and no other heavy heart at width `c` has size at least `|H|`, +`chooseBranch` must select `(c, H)`. -/ +theorem chooseBranch_eq_of_choice_unique {θ : ℕ → ℕ} {ψ : ClauseSet V} {c : ℕ} + {H : Finset (Literal V)} (hch : Choice θ ψ c H) + (huniq : ∀ H', Heavy θ ψ c H' → H.card ≤ H'.card → H' = H) : + chooseBranch θ ψ = (c, H) := by + have hns : ¬ Sparse θ ψ := fun hs => hs c H hch.1 + have hcb : Choice θ ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2 := choice_chooseBranch hns + have hc_eq : (chooseBranch θ ψ).1 = c := + le_antisymm (hcb.2.1 c H hch.1) (hch.2.1 _ _ hcb.1) + have hH_heavy : Heavy θ ψ c (chooseBranch θ ψ).2 := hc_eq ▸ hcb.1 + have hH_le : H.card ≤ (chooseBranch θ ψ).2.card := (hc_eq ▸ hcb.2.2) H hch.1 + exact Prod.ext hc_eq (huniq _ hH_heavy hH_le) + +/-! ### Recursive leaf enumeration -/ + +/-- Recursively enumerate the leaves of the disjoint counting-sparsification tree of `ψ` using +`chooseBranch θ` and `countingChild`. At any non-sparse state whose chosen flower has at least two +clauses (which holds automatically whenever `2 ≤ θ j` for `1 ≤ j`), `potential` strictly decreases +in both children. -/ +noncomputable def countingLeaves (θ : ℕ → ℕ) (ψ : ClauseSet V) : List (ClauseSet V) := + open Classical in + if ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card then + let ch := chooseBranch θ ψ + countingLeaves θ (countingChild ψ ch.1 ch.2 false) ++ + countingLeaves θ (countingChild ψ ch.1 ch.2 true) + else + [ψ] +termination_by potential ψ +decreasing_by + · rename_i h + exact potential_countingChild_lt_of_two_le_card_flower + (choice_chooseBranch h.1).1.1 (choice_chooseBranch h.1).1.2.1 h.2 false + · rename_i h + exact potential_countingChild_lt_of_two_le_card_flower + (choice_chooseBranch h.1).1.1 (choice_chooseBranch h.1).1.2.1 h.2 true + +/-- At a sparse clause set, `countingLeaves` terminates immediately with `[ψ]`. -/ +theorem countingLeaves_of_sparse {θ : ℕ → ℕ} {ψ : ClauseSet V} (h : Sparse θ ψ) : + countingLeaves θ ψ = [ψ] := by + rw [countingLeaves] + exact ite_eq_right fun h' => h'.1 h + +/-- When `2 ≤ θ j` for `1 ≤ j` and `ψ` is not sparse, `countingLeaves` branches into the heart +(`false`) and petal-plus-negated-units (`true`) subtrees for `chooseBranch θ ψ`. -/ +theorem countingLeaves_of_not_sparse {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) + {ψ : ClauseSet V} (h : ¬ Sparse θ ψ) : + countingLeaves θ ψ = + countingLeaves θ (countingChild ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2 false) ++ + countingLeaves θ (countingChild ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2 true) := by + have hz : 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card := + two_le_card_flower_of_heavy hθ (choice_chooseBranch h).1 + rw [countingLeaves] + exact ite_eq_left ⟨h, hz⟩ + +/-- Unfolding `countingLeaves` when the valid `Choice` at `ψ` is uniquely `(c, H)` and +`flower ψ c H` has at least two clauses. -/ +theorem countingLeaves_of_choice_unique {θ : ℕ → ℕ} {ψ : ClauseSet V} {c : ℕ} + {H : Finset (Literal V)} (hch : Choice θ ψ c H) (hz : 2 ≤ (flower ψ c H).card) + (huniq : ∀ H', Heavy θ ψ c H' → H.card ≤ H'.card → H' = H) : + countingLeaves θ ψ = + countingLeaves θ (countingChild ψ c H false) ++ + countingLeaves θ (countingChild ψ c H true) := by + have hns : ¬ Sparse θ ψ := fun hs => hs c H hch.1 + have hcb := chooseBranch_eq_of_choice_unique hch huniq + have hcond : ¬ Sparse θ ψ ∧ + 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card := by + rw [hcb] + exact ⟨hns, hz⟩ + rw [countingLeaves, ite_eq_left hcond, hcb] + +/-- Finite criterion for unfolding `countingLeaves` at a unique valid choice `(c, H)` when +`1 ≤ θ j` for `1 ≤ j`, inspecting only clauses `C ∈ ψ` and subsets `H' ∈ C.powerset`. -/ +theorem countingLeaves_of_mem_powerset_unique {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ j → 1 ≤ θ j) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (hheavy : Heavy θ ψ c H) (hz : 2 ≤ (flower ψ c H).card) + (hmin : ∀ C ∈ ψ, C.card < c → ∀ H' ∈ C.powerset, ¬ Heavy θ ψ C.card H') + (huniq : ∀ C ∈ ψ, C.card = c → ∀ H' ∈ C.powerset, + Heavy θ ψ c H' → H.card ≤ H'.card → H' = H) : + countingLeaves θ ψ = + countingLeaves θ (countingChild ψ c H false) ++ + countingLeaves θ (countingChild ψ c H true) := by + have hex_clause : ∀ {c' : ℕ} {H' : Finset (Literal V)}, Heavy θ ψ c' H' → + ∃ C ∈ ψ, C.card = c' ∧ H' ∈ C.powerset := by + intro c' H' hh + have hpos : 0 < (flower ψ c' H').card := + lt_of_lt_of_le (hθ (c' - H'.card) (by have := hh.2.1; omega)) hh.2.2 + obtain ⟨C, hC⟩ := card_pos.mp hpos + obtain ⟨hCψ, hCcard, hHC⟩ := mem_flower.mp hC + exact ⟨C, hCψ, hCcard, mem_powerset.mpr hHC⟩ + have huniq' : ∀ H', Heavy θ ψ c H' → H.card ≤ H'.card → H' = H := by + intro H' hh hle + obtain ⟨C, hCψ, hCcard, hH'pow⟩ := hex_clause hh + exact huniq C hCψ hCcard H' hH'pow hh hle + have hch : Choice θ ψ c H := by + refine ⟨hheavy, fun c' H' hh => ?_, fun H' hh => ?_⟩ + · by_contra hlt + obtain ⟨C, hCψ, hCcard, hH'pow⟩ := hex_clause hh + exact hmin C hCψ (by omega) H' hH'pow (hCcard ▸ hh) + · by_contra hlt + have heq := huniq' H' hh (by omega) + rw [heq] at hlt + exact hlt le_rfl + exact countingLeaves_of_choice_unique hch hz huniq' + +/-- `countingLeaves` never returns the empty list. -/ +theorem countingLeaves_ne_nil (θ : ℕ → ℕ) (ψ : ClauseSet V) : + countingLeaves θ ψ ≠ [] := by + induction hp : potential ψ using Nat.strong_induction_on generalizing ψ with + | _ n ih => + rw [countingLeaves] + by_cases h : ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card + · rw [ite_eq_left h] + have hlt := potential_countingChild_lt_of_two_le_card_flower + (choice_chooseBranch h.1).1.1 (choice_chooseBranch h.1).1.2.1 h.2 false + exact List.append_ne_nil_of_left_ne_nil (ih _ (hp ▸ hlt) _ rfl) _ + · rw [ite_eq_right h] + exact List.cons_ne_nil _ _ + +/-! ### Structural invariants along the recursion -/ + +/-- **Leaf sparsity.** Whenever `2 ≤ θ j` for `1 ≤ j`, every clause set in `countingLeaves θ ψ` is +`θ`-sparse. -/ +theorem sparse_of_mem_countingLeaves {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) + {ψ L : ClauseSet V} (hL : L ∈ countingLeaves θ ψ) : Sparse θ L := by + suffices ∀ n (ψ : ClauseSet V), potential ψ = n → L ∈ countingLeaves θ ψ → Sparse θ L from + this (potential ψ) ψ rfl hL + intro n + induction n using Nat.strong_induction_on with + | _ n ih => + intro ψ hp hL + by_cases hs : Sparse θ ψ + · rw [countingLeaves_of_sparse hs, List.mem_singleton] at hL + exact hL ▸ hs + · rw [countingLeaves_of_not_sparse hθ hs, List.mem_append] at hL + have hch := (choice_chooseBranch hs).1 + rcases hL with hL | hL + · exact ih _ (hp ▸ potential_countingChild_lt hθ hch false) _ rfl hL + · exact ih _ (hp ▸ potential_countingChild_lt hθ hch true) _ rfl hL + +/-- **Leaf sparsity for geometric thresholds `θ j = M ^ j` (`2 ≤ M`).** -/ +theorem sparse_of_mem_countingLeaves_pow {M : ℕ} (hM : 2 ≤ M) + {ψ L : ClauseSet V} (hL : L ∈ countingLeaves (M ^ ·) ψ) : Sparse (M ^ ·) L := + sparse_of_mem_countingLeaves + (fun _ hj => hM.trans + (by simpa only [pow_one] using Nat.pow_le_pow_right (by omega : 0 < M) hj)) + hL + +/-- **Variable-support preservation.** If all variables occurring in `ψ` belong to `declared`, then +all variables occurring in every leaf `L ∈ countingLeaves θ ψ` belong to `declared`. -/ +theorem support_of_mem_countingLeaves {θ : ℕ → ℕ} {ψ L : ClauseSet V} {declared : Finset V} + (hψ : ∀ C ∈ ψ, ∀ l ∈ C, l.1 ∈ declared) (hL : L ∈ countingLeaves θ ψ) : + ∀ C ∈ L, ∀ l ∈ C, l.1 ∈ declared := by + suffices ∀ n (ψ : ClauseSet V), potential ψ = n → + (∀ C ∈ ψ, ∀ l ∈ C, l.1 ∈ declared) → L ∈ countingLeaves θ ψ → + ∀ C ∈ L, ∀ l ∈ C, l.1 ∈ declared from + this (potential ψ) ψ rfl hψ hL + intro n + induction n using Nat.strong_induction_on with + | _ n ih => + intro ψ hp hψ hL + rw [countingLeaves] at hL + by_cases h : ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card + · rw [ite_eq_left h, List.mem_append] at hL + set ch := chooseBranch θ ψ + have hch := (choice_chooseBranch h.1).1 + obtain ⟨D, hD⟩ := card_pos.mp (lt_of_lt_of_le (by omega : 0 < 2) h.2) + obtain ⟨hDψ, -, hHD⟩ := mem_flower.mp hD + have hH : ∀ l ∈ ch.2, l.1 ∈ declared := fun l hl => hψ D hDψ l (hHD hl) + rcases hL with hL | hL + · have hlt := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 false + exact ih _ (hp ▸ hlt) _ rfl (support_countingChild ψ ch.1 ch.2 declared hψ hH false) hL + · have hlt := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 true + exact ih _ (hp ▸ hlt) _ rfl (support_countingChild ψ ch.1 ch.2 declared hψ hH true) hL + · rw [ite_eq_right h, List.mem_singleton] at hL + exact hL ▸ hψ + +/-- **Clause-width preservation.** If every clause of `ψ` has width at most `k`, then every clause +of every leaf `L ∈ countingLeaves θ ψ` has width at most `k`. -/ +theorem card_le_of_mem_countingLeaves {θ : ℕ → ℕ} {ψ L : ClauseSet V} {k : ℕ} + (hk : ∀ C ∈ ψ, C.card ≤ k) (hL : L ∈ countingLeaves θ ψ) : + ∀ C ∈ L, C.card ≤ k := by + suffices ∀ n (ψ : ClauseSet V), potential ψ = n → + (∀ C ∈ ψ, C.card ≤ k) → L ∈ countingLeaves θ ψ → ∀ C ∈ L, C.card ≤ k from + this (potential ψ) ψ rfl hk hL + intro n + induction n using Nat.strong_induction_on with + | _ n ih => + intro ψ hp hk hL + rw [countingLeaves] at hL + by_cases h : ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card + · rw [ite_eq_left h, List.mem_append] at hL + set ch := chooseBranch θ ψ + have hch := (choice_chooseBranch h.1).1 + have hne : (flower ψ ch.1 ch.2).Nonempty := + card_pos.mp (lt_of_lt_of_le (by omega : 0 < 2) h.2) + rcases hL with hL | hL + · have hlt := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 false + exact ih _ (hp ▸ hlt) _ rfl + (fun C hC => card_le_of_mem_countingChild hch.1 hch.2.1 hne hk hC) hL + · have hlt := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 true + exact ih _ (hp ▸ hlt) _ rfl + (fun C hC => card_le_of_mem_countingChild hch.1 hch.2.1 hne hk hC) hL + · rw [ite_eq_right h, List.mem_singleton] at hL + exact hL ▸ hk + +/-- **Reducedness preservation.** From a reduced root `ψ`, every leaf in `countingLeaves θ ψ` is +reduced. -/ +theorem reduced_of_mem_countingLeaves {θ : ℕ → ℕ} {ψ L : ClauseSet V} + (hred : Reduced ψ) (hL : L ∈ countingLeaves θ ψ) : Reduced L := by + suffices ∀ n (ψ : ClauseSet V), potential ψ = n → + Reduced ψ → L ∈ countingLeaves θ ψ → Reduced L from + this (potential ψ) ψ rfl hred hL + intro n + induction n using Nat.strong_induction_on with + | _ n ih => + intro ψ hp hred hL + rw [countingLeaves] at hL + by_cases h : ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card + · rw [ite_eq_left h, List.mem_append] at hL + set ch := chooseBranch θ ψ + have hch := (choice_chooseBranch h.1).1 + rcases hL with hL | hL + · have hlt := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 false + exact ih _ (hp ▸ hlt) _ rfl (reduced_countingChild ψ ch.1 ch.2 false) hL + · have hlt := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 true + exact ih _ (hp ▸ hlt) _ rfl (reduced_countingChild ψ ch.1 ch.2 true) hL + · rw [ite_eq_right h, List.mem_singleton] at hL + exact hL ▸ hred + +/-! ### Exact assignment partition and model-count conservation -/ + +/-- **Exact assignment partition (multiplicity form).** For every assignment `x : V → Bool`, the +number of leaf occurrences in `countingLeaves θ ψ` satisfied by `x` is `1` when `ψ.Sat x` holds and +`0` otherwise. -/ +theorem countP_sat_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseSet V) (x : V → Bool) : + (countingLeaves θ ψ).countP (fun L => decide (L.Sat x)) = + if ψ.Sat x then 1 else 0 := by + induction hp : potential ψ using Nat.strong_induction_on generalizing ψ with + | _ n ih => + rw [countingLeaves] + by_cases h : ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card + · rw [ite_eq_left h] + set ch := chooseBranch θ ψ + have hch := (choice_chooseBranch h.1).1 + have hlt₀ := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 false + have hlt₁ := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 true + rw [List.countP_append, ih _ (hp ▸ hlt₀) _ rfl, ih _ (hp ▸ hlt₁) _ rfl] + by_cases hsat : ψ.Sat x + · by_cases hH : ∃ l ∈ ch.2, x l.1 = l.2 + · have h₀ : (countingChild ψ ch.1 ch.2 false).Sat x := + (sat_countingChild_false_iff ψ ch.1 ch.2 x).mpr ⟨hsat, hH⟩ + have h₁ : ¬ (countingChild ψ ch.1 ch.2 true).Sat x := fun ht => + not_sat_both_countingChild ψ ch.1 ch.2 x ⟨h₀, ht⟩ + rw [ite_eq_left hsat, ite_eq_left h₀, ite_eq_right h₁] + · have h₁ : (countingChild ψ ch.1 ch.2 true).Sat x := + (sat_countingChild_true_iff ψ ch.1 ch.2 x).mpr ⟨hsat, hH⟩ + have h₀ : ¬ (countingChild ψ ch.1 ch.2 false).Sat x := fun hf => + not_sat_both_countingChild ψ ch.1 ch.2 x ⟨hf, h₁⟩ + rw [ite_eq_left hsat, ite_eq_right h₀, ite_eq_left h₁] + · have h₀ : ¬ (countingChild ψ ch.1 ch.2 false).Sat x := fun hf => + hsat ((sat_countingChild_false_iff ψ ch.1 ch.2 x).mp hf).1 + have h₁ : ¬ (countingChild ψ ch.1 ch.2 true).Sat x := fun ht => + hsat ((sat_countingChild_true_iff ψ ch.1 ch.2 x).mp ht).1 + rw [ite_eq_right hsat, ite_eq_right h₀, ite_eq_right h₁] + · rw [ite_eq_right h] + let p : ClauseSet V → Bool := fun L => decide (L.Sat x) + change [ψ].countP p = if ψ.Sat x then 1 else 0 + by_cases hsat : ψ.Sat x + · rw [List.countP_cons_of_pos (p := p) (decide_eq_true hsat), List.countP_nil, + ite_eq_left hsat] + · rw [List.countP_cons_of_neg (p := p) (by simpa only [p, decide_eq_true_eq] using hsat), + List.countP_nil, ite_eq_right hsat] + +private theorem existsUnique_fin_of_countP_eq_one {α : Type*} : + ∀ {l : List α} {p : α → Bool}, l.countP p = 1 → ∃! i : Fin l.length, p l[i] = true + | [], _, h => by + rw [List.countP_nil] at h + omega + | a :: tl, p, h => by + cases hpa : p a with + | true => + rw [List.countP_cons_of_pos hpa] at h + have htl : tl.countP p = 0 := by omega + refine Exists.intro (⟨0, Nat.succ_pos _⟩ : Fin (a :: tl).length) + (And.intro ?_ fun i hi => ?_) + · simpa only [Fin.getElem_fin, List.getElem_cons_zero] using hpa + · rcases i with ⟨_ | k, hk⟩ + · rfl + · simp only [Fin.getElem_fin, List.getElem_cons_succ] at hi + have hk' : k < tl.length := by simp only [List.length_cons] at hk; omega + exact absurd hi ((List.countP_eq_zero.mp htl) tl[k] (List.getElem_mem hk')) + | false => + rw [List.countP_cons_of_neg (by rw [hpa]; decide)] at h + obtain ⟨j, hj, hjuniq⟩ := existsUnique_fin_of_countP_eq_one h + refine Exists.intro j.succ (And.intro ?_ fun i hi => ?_) + · simpa only [Fin.getElem_fin, Fin.val_succ, List.getElem_cons_succ] using hj + · rcases i with ⟨_ | k, hk⟩ + · simp only [Fin.getElem_fin, List.getElem_cons_zero] at hi + exact absurd hi (by rw [hpa]; decide) + · simp only [Fin.getElem_fin, List.getElem_cons_succ] at hi + have hk' : k < tl.length := by simp only [List.length_cons] at hk; omega + have heq := Fin.ext_iff.mp (hjuniq ⟨k, hk'⟩ (by simpa only [Fin.getElem_fin] using hi)) + exact Fin.ext (by simp only [Fin.val_succ]; exact congrArg (· + 1) heq) + +private theorem existsUnique_mem_of_countP_eq_one {α : Type*} : + ∀ {l : List α} {p : α → Bool}, l.countP p = 1 → ∃! a, a ∈ l ∧ p a = true + | [], _, h => by + rw [List.countP_nil] at h + omega + | a :: tl, p, h => by + cases hpa : p a with + | true => + rw [List.countP_cons_of_pos hpa] at h + have htl : tl.countP p = 0 := by omega + refine Exists.intro a (And.intro ⟨List.mem_cons_self, hpa⟩ fun b ⟨hb, hpb⟩ => ?_) + rcases List.mem_cons.mp hb with rfl | hbtl + · rfl + · exact absurd hpb ((List.countP_eq_zero.mp htl) b hbtl) + | false => + rw [List.countP_cons_of_neg (by rw [hpa]; decide)] at h + obtain ⟨b, ⟨hbtl, hpb⟩, hbuniq⟩ := existsUnique_mem_of_countP_eq_one h + refine Exists.intro b (And.intro ⟨List.mem_cons_of_mem a hbtl, hpb⟩ fun c ⟨hc, hpc⟩ => ?_) + rcases List.mem_cons.mp hc with rfl | hctl + · exact absurd hpc (by rw [hpa]; decide) + · exact hbuniq c ⟨hctl, hpc⟩ + +/-- **Exact assignment partition (indexed leaf occurrence form).** An assignment `x` satisfies `ψ` +if and only if there is a unique index `i` into `countingLeaves θ ψ` whose leaf is satisfied by +`x`. -/ +theorem sat_iff_existsUnique_fin_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseSet V) (x : V → Bool) : + ψ.Sat x ↔ ∃! i : Fin (countingLeaves θ ψ).length, ((countingLeaves θ ψ)[i]).Sat x := by + have hc := countP_sat_countingLeaves θ ψ x + constructor + · intro hsat + rw [ite_eq_left hsat] at hc + obtain ⟨i, hi, huniq⟩ := existsUnique_fin_of_countP_eq_one hc + exact Exists.intro i (And.intro (of_decide_eq_true hi) fun j hj => huniq j (decide_eq_true hj)) + · rintro ⟨i, hi, -⟩ + by_contra hnsat + rw [ite_eq_right hnsat, List.countP_eq_zero] at hc + exact hc ((countingLeaves θ ψ)[i]) (List.getElem_mem i.isLt) (decide_eq_true hi) + +/-- **Exact assignment partition (leaf membership form).** Because any satisfiable leaf occurs at +most once in `countingLeaves θ ψ`, `x` satisfies `ψ` if and only if there is a unique clause set +`L` satisfying `L ∈ countingLeaves θ ψ ∧ L.Sat x`. -/ +theorem sat_iff_existsUnique_mem_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseSet V) (x : V → Bool) : + ψ.Sat x ↔ ∃! L, L ∈ countingLeaves θ ψ ∧ L.Sat x := by + have hc := countP_sat_countingLeaves θ ψ x + constructor + · intro hsat + rw [ite_eq_left hsat] at hc + obtain ⟨L, ⟨hLm, hLs⟩, huniq⟩ := existsUnique_mem_of_countP_eq_one hc + exact Exists.intro L (And.intro ⟨hLm, of_decide_eq_true hLs⟩ fun L' ⟨hL'm, hL's⟩ => + huniq L' ⟨hL'm, decide_eq_true hL's⟩) + · rintro ⟨L, ⟨hLm, hLs⟩, -⟩ + by_contra hnsat + rw [ite_eq_right hnsat, List.countP_eq_zero] at hc + exact hc L hLm (decide_eq_true hLs) + +/-- **Additive model-count conservation.** Summing `CNF.modelCountOn declared` over all leaves in +`countingLeaves θ ψ` recovers `ψ.toCNF.modelCountOn declared` on the same declared variable set, +preserving free choices for unused declared variables and handling empty or contradictory clause +sets without extra premises. -/ +theorem sum_modelCountOn_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseSet V) (declared : Finset V) : + ((countingLeaves θ ψ).map fun L => L.toCNF.modelCountOn declared).sum = + ψ.toCNF.modelCountOn declared := by + induction hp : potential ψ using Nat.strong_induction_on generalizing ψ with + | _ n ih => + rw [countingLeaves] + by_cases h : ¬ Sparse θ ψ ∧ 2 ≤ (flower ψ (chooseBranch θ ψ).1 (chooseBranch θ ψ).2).card + · rw [ite_eq_left h] + set ch := chooseBranch θ ψ + have hch := (choice_chooseBranch h.1).1 + have hlt₀ := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 false + have hlt₁ := potential_countingChild_lt_of_two_le_card_flower hch.1 hch.2.1 h.2 true + rw [List.map_append, List.sum_append, ih _ (hp ▸ hlt₀) _ rfl, ih _ (hp ▸ hlt₁) _ rfl] + exact (modelCountOn_countingChild ψ ch.1 ch.2 declared).symm + · rw [ite_eq_right h] + simp only [List.map_cons, List.map_nil, List.sum_cons, List.sum_nil, Nat.add_zero] + +/-! ### Bridges to `CNF` formulas and `CNF.modelCountOn` -/ + +/-- Round-tripping `φ : CNF V` through `toClauseSet` and `toCNF` preserves `modelCountOn` on any +declared variable set. -/ +@[simp] +theorem modelCountOn_toCNF_toClauseSet (φ : CNF V) (declared : Finset V) : + φ.toClauseSet.toCNF.modelCountOn declared = φ.modelCountOn declared := by + unfold CNF.modelCountOn + congr 1 + ext σ + simp only [mem_filter, mem_univ, true_and, ClauseSet.eval_toCNF_eq_true_iff, + CNF.sat_toClauseSet_iff] + +/-- Clause-set reduction `reduce` preserves `modelCountOn` on any declared variable set. -/ +@[simp] +theorem modelCountOn_toCNF_reduce (ψ : ClauseSet V) (declared : Finset V) : + (reduce ψ).toCNF.modelCountOn declared = ψ.toCNF.modelCountOn declared := by + unfold CNF.modelCountOn + congr 1 + ext σ + simp only [mem_filter, mem_univ, true_and, ClauseSet.eval_toCNF_eq_true_iff, sat_reduce_iff] + +/-- **CNF assignment partition.** Evaluating `L.toCNF` across `countingLeaves θ φ.toClauseSet` +accepts each assignment `x` at `1` leaf occurrence when `φ.eval x = true` and `0` otherwise. -/ +theorem countP_eval_toCNF_countingLeaves (θ : ℕ → ℕ) (φ : CNF V) (x : V → Bool) : + ((countingLeaves θ φ.toClauseSet).map ClauseSet.toCNF).countP (fun F => F.eval x) = + if φ.eval x then 1 else 0 := by + rw [List.countP_map] + have hcongr : (Function.comp (fun F : CNF V => F.eval x) ClauseSet.toCNF) = + fun L : ClauseSet V => decide (L.Sat x) := by + funext L + exact Bool.eq_iff_iff.mpr + ((ClauseSet.eval_toCNF_eq_true_iff L x).trans decide_eq_true_iff.symm) + rw [hcongr, countP_sat_countingLeaves] + by_cases hev : φ.eval x = true + · rw [ite_eq_left ((CNF.sat_toClauseSet_iff φ x).mpr hev), ite_eq_left hev] + · have hnsat : ¬ φ.toClauseSet.Sat x := fun hs => hev ((CNF.sat_toClauseSet_iff φ x).mp hs) + rw [ite_eq_right hnsat, ite_eq_right hev] + +/-- **CNF declared-variable support preservation.** If `φ.vars.toFinset ⊆ declared`, then every +leaf formula `L.toCNF` produced from `φ.toClauseSet` has its occurring variables contained in +`declared`. -/ +theorem vars_toCNF_subset_of_mem_countingLeaves {θ : ℕ → ℕ} {φ : CNF V} {declared : Finset V} + (hvars : φ.vars.toFinset ⊆ declared) {L : ClauseSet V} + (hL : L ∈ countingLeaves θ φ.toClauseSet) : + L.toCNF.vars.toFinset ⊆ declared := by + have hroot : ∀ C ∈ φ.toClauseSet, ∀ l ∈ C, l.1 ∈ declared := by + intro C hC l hl + simp only [CNF.toClauseSet, List.mem_toFinset, List.mem_map] at hC + obtain ⟨c, hc, rfl⟩ := hC + exact hvars (List.mem_toFinset.mpr (CNF.mem_vars.mpr ⟨c, hc, l.2, List.mem_toFinset.mp hl⟩)) + have hleaf := support_of_mem_countingLeaves hroot hL + intro v hv + obtain ⟨c, hc, b, hvb⟩ := CNF.mem_vars.mp (List.mem_toFinset.mp hv) + simp only [ClauseSet.toCNF, List.mem_map, Finset.mem_toList] at hc + obtain ⟨C, hC, rfl⟩ := hc + exact hleaf C hC (v, b) (Finset.mem_toList.mp hvb) + +/-- **CNF additive model-count conservation.** Summing `modelCountOn declared` over the leaf +formulas of `countingLeaves θ φ.toClauseSet` recovers `φ.modelCountOn declared`. -/ +theorem sum_modelCountOn_countingLeaves_toClauseSet (θ : ℕ → ℕ) (φ : CNF V) + (declared : Finset V) : + ((countingLeaves θ φ.toClauseSet).map fun L => L.toCNF.modelCountOn declared).sum = + φ.modelCountOn declared := by + rw [sum_modelCountOn_countingLeaves, modelCountOn_toCNF_toClauseSet] + +/-- **Reduced CNF additive model-count conservation.** Normalizing the root clause set with +`reduce` before enumerating `countingLeaves` likewise preserves `φ.modelCountOn declared`. -/ +theorem sum_modelCountOn_countingLeaves_reduce_toClauseSet (θ : ℕ → ℕ) (φ : CNF V) + (declared : Finset V) : + ((countingLeaves θ (reduce φ.toClauseSet)).map fun L => L.toCNF.modelCountOn declared).sum = + φ.modelCountOn declared := by + rw [sum_modelCountOn_countingLeaves, modelCountOn_toCNF_reduce, modelCountOn_toCNF_toClauseSet] + +end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/SparsificationCountingTree.lean b/ComplexityTests/SparsificationCountingTree.lean new file mode 100644 index 000000000..20af9edff --- /dev/null +++ b/ComplexityTests/SparsificationCountingTree.lean @@ -0,0 +1,263 @@ +/- +Copyright (c) 2026 Anton Kovsharov. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anton Kovsharov +-/ + +module + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree + +public section + +open Cslib.Logic.PL +open ClauseSet.Sparsification + +namespace ComplexityTests.SparsificationCountingTree + +private theorem count_eq_card {V : Type*} [DecidableEq V] + (ψ : ClauseSet V) (s : Finset V) : + ψ.toCNF.modelCountOn s = (Finset.univ.filter fun σ : s → Bool => + ψ.Sat (fun v => if h : v ∈ s then σ ⟨v, h⟩ else false)).card := by + unfold CNF.modelCountOn + congr 1 + ext σ + simp only [Finset.mem_filter, Finset.mem_univ, true_and, ClauseSet.eval_toCNF_eq_true_iff] + +private theorem one_le_two_pow : ∀ j, 1 ≤ j → 1 ≤ 2 ^ j := + fun _ _ => Nat.one_le_two_pow + +/-! ### 1. Sharpness of `2 ≤ (flower ψ c H).card` for potential decrease -/ + +-- A single width-2 clause (`z = 1`, `c = 2`, `|H| = 1`) has potential `4`, and its petal-plus-unit +-- child `{{(1, true)}, {(0, false)}}` also has potential `2 + 2 = 4`: `z ≥ 2` cannot be dropped. +private def singleFlower : ClauseSet (Fin 2) := + {{(0, true), (1, true)}} + +example : potential singleFlower = 4 := by decide + +example : (flower singleFlower 2 {(0, true)}).card = 1 := by decide + +example : potential (countingChild singleFlower 2 {(0, true)} true) = 4 := by decide + +-- With two clauses (`z = 2`), both branches strictly drop below potential `8` (`2 < 8`, `6 < 8`). +private def twoFlower : ClauseSet (Fin 3) := + {{(0, true), (1, true)}, {(0, true), (2, true)}} + +example : potential twoFlower = 8 := by decide + +example : potential (countingChild twoFlower 2 {(0, true)} false) = 2 := by decide + +example : potential (countingChild twoFlower 2 {(0, true)} true) = 6 := by decide + +/-! ### 2. Multi-step recursion (`depth = 2`, `3` sparse leaves) under `θ j = 2 ^ j` -/ + +-- At the root: +-- `C₁ = {(0, true), (1, true), (2, true)}`, `C₂ = {(0, true), (1, true), (3, true)}` (width 3), +-- `E₁ = {(1, true), (4, true)}` (width 2). +-- Step 1 uniquely selects `(3, {(0, true), (1, true)})`. +-- Its `false` (heart) child `{{(0, true), (1, true)}, {(1, true), (4, true)}}` creates a new +-- width-2 flower around `{(1, true)}` and branches a second time into `leaf₀₀` and `leaf₀₁`, while +-- its `true` (petal + negated-heart-units) child `leaf₁` is already `(2 ^ ·)`-sparse. +private def multiRoot : ClauseSet (Fin 5) := + {{(0, true), (1, true), (2, true)}, + {(0, true), (1, true), (3, true)}, + {(1, true), (4, true)}} + +private def child₀ : ClauseSet (Fin 5) := + {{(0, true), (1, true)}, {(1, true), (4, true)}} + +private def leaf₀₀ : ClauseSet (Fin 5) := + {{(1, true)}} + +private def leaf₀₁ : ClauseSet (Fin 5) := + {{(1, false)}, {(0, true)}, {(4, true)}} + +private def leaf₁ : ClauseSet (Fin 5) := + {{(0, false)}, {(1, false)}, {(2, true)}, {(3, true)}, {(1, true), (4, true)}} + +-- Potentials strictly decrease along every branch: `20 → 8 → 2`, `20 → 8 → 6`, and `20 → 12`. +example : potential multiRoot = 20 ∧ potential child₀ = 8 ∧ + potential leaf₀₀ = 2 ∧ potential leaf₀₁ = 6 ∧ potential leaf₁ = 12 := by + decide + +private theorem child₀_eq : + countingChild multiRoot 3 {(0, true), (1, true)} false = child₀ := by decide + +private theorem leaf₁_eq : + countingChild multiRoot 3 {(0, true), (1, true)} true = leaf₁ := by decide + +private theorem leaf₀₀_eq : + countingChild child₀ 2 {(1, true)} false = leaf₀₀ := by decide + +private theorem leaf₀₁_eq : + countingChild child₀ 2 {(1, true)} true = leaf₀₁ := by decide + +private theorem sparse_leaf₀₀ : Sparse (2 ^ ·) leaf₀₀ := + (sparse_iff_forall_mem_powerset one_le_two_pow leaf₀₀).mpr (by unfold Heavy; decide) + +private theorem sparse_leaf₀₁ : Sparse (2 ^ ·) leaf₀₁ := + (sparse_iff_forall_mem_powerset one_le_two_pow leaf₀₁).mpr (by unfold Heavy; decide) + +private theorem sparse_leaf₁ : Sparse (2 ^ ·) leaf₁ := + (sparse_iff_forall_mem_powerset one_le_two_pow leaf₁).mpr (by unfold Heavy; decide) + +private theorem countingLeaves_multiRoot : + countingLeaves (2 ^ ·) multiRoot = [leaf₀₀, leaf₀₁, leaf₁] := by + have hstep1 : countingLeaves (2 ^ ·) multiRoot = + countingLeaves (2 ^ ·) child₀ ++ countingLeaves (2 ^ ·) leaf₁ := by + rw [countingLeaves_of_mem_powerset_unique one_le_two_pow (c := 3) + (H := {(0, true), (1, true)}) + (by unfold Heavy; decide) (by decide) (by unfold Heavy; decide) (by unfold Heavy; decide), + child₀_eq, leaf₁_eq] + have hstep2 : countingLeaves (2 ^ ·) child₀ = + countingLeaves (2 ^ ·) leaf₀₀ ++ countingLeaves (2 ^ ·) leaf₀₁ := by + rw [countingLeaves_of_mem_powerset_unique one_le_two_pow (c := 2) + (H := {(1, true)}) + (by unfold Heavy; decide) (by decide) (by unfold Heavy; decide) (by unfold Heavy; decide), + leaf₀₀_eq, leaf₀₁_eq] + rw [hstep1, hstep2, countingLeaves_of_sparse sparse_leaf₀₀, + countingLeaves_of_sparse sparse_leaf₀₁, countingLeaves_of_sparse sparse_leaf₁] + rfl + +-- Individual leaf counts are `16`, `4`, and `1`, summing to `21 = multiRoot`'s model count. +example : leaf₀₀.toCNF.modelCountOn Finset.univ = 16 := by rw [count_eq_card]; decide + +example : leaf₀₁.toCNF.modelCountOn Finset.univ = 4 := by rw [count_eq_card]; decide + +example : leaf₁.toCNF.modelCountOn Finset.univ = 1 := by rw [count_eq_card]; decide + +example : multiRoot.toCNF.modelCountOn Finset.univ = 21 := by rw [count_eq_card]; decide + +example : ((countingLeaves (2 ^ ·) multiRoot).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 21 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +-- Concrete assignment check: the all-true assignment satisfies `leaf₀₀` and neither `leaf₀₁` nor +-- `leaf₁`, while `witnessAssign` (`x₀ = x₁ = false`, others `true`) satisfies `leaf₁` alone. +private def witnessAssign : Fin 5 → Bool + | 0 => false + | 1 => false + | _ => true + +example : (countingLeaves (2 ^ ·) multiRoot).countP + (fun L => decide (L.Sat (fun _ => true))) = 1 := by + rw [countingLeaves_multiRoot] + decide + +example : leaf₁.Sat witnessAssign ∧ + ¬ leaf₀₀.Sat witnessAssign ∧ + ¬ leaf₀₁.Sat witnessAssign := by + decide + +/-! ### 3. Unused declared variables and support preservation -/ + +-- Embedding `multiRoot` into `Fin 6` leaves variable `5` unused: every leaf stays supported on +-- `{0, 1, 2, 3, 4}`, while `modelCountOn Finset.univ` doubles from `21` to `42`. +private def multiRootSix : ClauseSet (Fin 6) := + {{(0, true), (1, true), (2, true)}, + {(0, true), (1, true), (3, true)}, + {(1, true), (4, true)}} + +example : ∀ L ∈ countingLeaves (2 ^ ·) multiRootSix, + ∀ C ∈ L, ∀ l ∈ C, l.1 ∈ ({0, 1, 2, 3, 4} : Finset (Fin 6)) := + fun _ hL => support_of_mem_countingLeaves (by decide) hL + +example : ∀ L ∈ countingLeaves (2 ^ ·) multiRootSix, ∀ C ∈ L, C.card ≤ 3 := + fun _ hL => card_le_of_mem_countingLeaves (by decide) hL + +example : ∀ L ∈ countingLeaves (2 ^ ·) multiRootSix, Reduced L := + fun _ hL => reduced_of_mem_countingLeaves (by unfold Reduced; decide) hL + +example : ((countingLeaves (2 ^ ·) multiRootSix).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 42 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +/-! ### 4. Empty formula, unit clauses, contradictory formulas, and `CNF` round-trip -/ + +-- Empty clause set on 3 declared variables has 1 leaf `∅` with `2^3 = 8` models; on `Empty`, `1`. +example : countingLeaves (2 ^ ·) (∅ : ClauseSet (Fin 3)) = [∅] := + countingLeaves_of_sparse + ((sparse_iff_forall_mem_powerset one_le_two_pow ∅).mpr (by unfold Heavy; decide)) + +example : ((countingLeaves (2 ^ ·) (∅ : ClauseSet (Fin 3))).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 8 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +theorem empty_leaves_count : + ((countingLeaves (2 ^ ·) (∅ : ClauseSet Empty)).map + fun L => L.toCNF.modelCountOn ∅).sum = 1 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +-- Pure unit clauses are already sparse and retain their exact count (`2` models on `Fin 3`). +private def unitClauseSet : ClauseSet (Fin 3) := + {{(0, true)}, {(1, false)}} + +example : countingLeaves (2 ^ ·) unitClauseSet = [unitClauseSet] := + countingLeaves_of_sparse + ((sparse_iff_forall_mem_powerset one_le_two_pow unitClauseSet).mpr (by unfold Heavy; decide)) + +example : ((countingLeaves (2 ^ ·) unitClauseSet).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 2 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +-- Contradictory clause set containing the empty clause `{∅}`: 1 leaf, `0` models. +example : countingLeaves (2 ^ ·) ({∅} : ClauseSet (Fin 2)) = [{∅}] := + countingLeaves_of_sparse + ((sparse_iff_forall_mem_powerset one_le_two_pow {∅}).mpr (by unfold Heavy; decide)) + +example : ((countingLeaves (2 ^ ·) ({∅} : ClauseSet (Fin 2))).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 0 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +-- Contradictory unit pair `{x₀} ∧ {¬x₀}` combined with a heavy width-2 flower: `countingLeaves` +-- still branches cleanly into 2 sparse leaves, both with `0` models (`0 + 0 = 0`). +private def unsatHeavy : ClauseSet (Fin 4) := + {{(0, true)}, {(0, false)}, {(1, true), (2, true)}, {(1, true), (3, true)}} + +example : (countingLeaves (2 ^ ·) unsatHeavy).length = 2 := by + have hstep : countingLeaves (2 ^ ·) unsatHeavy = + countingLeaves (2 ^ ·) (countingChild unsatHeavy 2 {(1, true)} false) ++ + countingLeaves (2 ^ ·) (countingChild unsatHeavy 2 {(1, true)} true) := + countingLeaves_of_mem_powerset_unique one_le_two_pow (c := 2) (H := {(1, true)}) + (by unfold Heavy; decide) (by decide) (by unfold Heavy; decide) (by unfold Heavy; decide) + have hs0 : Sparse (2 ^ ·) (countingChild unsatHeavy 2 {(1, true)} false) := + (sparse_iff_forall_mem_powerset one_le_two_pow _).mpr (by unfold Heavy; decide) + have hs1 : Sparse (2 ^ ·) (countingChild unsatHeavy 2 {(1, true)} true) := + (sparse_iff_forall_mem_powerset one_le_two_pow _).mpr (by unfold Heavy; decide) + rw [hstep, countingLeaves_of_sparse hs0, countingLeaves_of_sparse hs1] + rfl + +example : ((countingLeaves (2 ^ ·) unsatHeavy).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 0 := by + rw [sum_modelCountOn_countingLeaves, count_eq_card] + decide + +-- `CNF` formula with duplicate and subsumed clauses: both `toClauseSet` and `reduce ∘ toClauseSet` +-- conserve `CNF.modelCountOn Finset.univ = 21`. +private def multiCNF : CNF (Fin 5) := + [[(0, true), (1, true), (2, true)], + [(0, true), (1, true), (2, true)], + [(0, true), (1, true), (3, true)], + [(1, true), (4, true)], + [(1, true), (2, false), (4, true)]] + +example : ((countingLeaves (2 ^ ·) multiCNF.toClauseSet).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 21 := by + rw [sum_modelCountOn_countingLeaves_toClauseSet] + unfold CNF.modelCountOn + decide + +example : ((countingLeaves (2 ^ ·) (reduce multiCNF.toClauseSet)).map + fun L => L.toCNF.modelCountOn Finset.univ).sum = 21 := by + rw [sum_modelCountOn_countingLeaves_reduce_toClauseSet] + unfold CNF.modelCountOn + decide + +end ComplexityTests.SparsificationCountingTree From 98fe7a3ed3c5aa865552cbd966ae24057ecb8d40 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 21:31:13 -0400 Subject: [PATCH 03/24] test: exercise counting occurrence bounds and priority premises --- Complexity.lean | 1 + .../SparsificationCountingOccurrences.lean | 158 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 ComplexityTests/SparsificationCountingOccurrences.lean diff --git a/Complexity.lean b/Complexity.lean index a721be0d6..43bf364b1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2169,6 +2169,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Basic public import Complexity.Logics.Propositional.ClauseSet.Sparsification public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/ComplexityTests/SparsificationCountingOccurrences.lean b/ComplexityTests/SparsificationCountingOccurrences.lean new file mode 100644 index 000000000..edf1f4cda --- /dev/null +++ b/ComplexityTests/SparsificationCountingOccurrences.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences + +public section + +open Cslib.Logic.PL +open ClauseSet.Sparsification Finset + +namespace ComplexityTests.SparsificationCountingOccurrences + +-- The added unit is real payload data, rather than silently omitted in the occurrence proof. +theorem negated_unit_remains_in_payload : {(0, false)} ∈ countingIntro + ({{(0, true), (1, true), (2, true)}} : ClauseSet (Fin 3)) 3 {(0, true)} true := by decide + +private def threshold (j : ℕ) := 2 ^ j + +private theorem threshold_pos (j : ℕ) : 1 ≤ threshold j := by + induction j with + | zero => decide + | succ j ih => + change 1 ≤ 2 ^ (j + 1) + rw [pow_succ] + change 1 ≤ 2 ^ j at ih + omega + +private theorem threshold_two {j : ℕ} (hj : 0 < j) : 2 ≤ threshold j := by + cases j with + | zero => omega + | succ j => + change 2 ≤ 2 ^ (j + 1) + rw [pow_succ] + have := threshold_pos j + change 1 ≤ 2 ^ j at this + omega + +-- A checked uniform-width family supplies the minimum-width priority. Intersections control +-- the maximum heart: any heavy flower has two different clauses containing that heart. +private theorem choice_of_uniform_intersections {V : Type*} [DecidableEq V] + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (hheavy : Heavy threshold ψ c H) (hwidth : ∀ C ∈ ψ, C.card = c) + (hinter : ∀ C ∈ ψ, ∀ D ∈ ψ, C ≠ D → (C ∩ D).card ≤ H.card) : + Choice threshold ψ c H := by + refine ⟨hheavy, ?_, ?_⟩ + · intro c' H' hh + obtain ⟨C, hC⟩ := hh.flower_nonempty threshold_pos + have hCc := (mem_flower.mp hC).2.1 + have := hwidth C (flower_subset _ _ _ hC) + omega + · intro H' hh + have htwo : 2 ≤ (flower ψ c H').card := + (threshold_two (Nat.sub_pos_of_lt hh.2.1)).trans hh.2.2 + obtain ⟨C, hC, D, hD, hCD⟩ := one_lt_card.mp (by omega : 1 < (flower ψ c H').card) + calc + H'.card ≤ (C ∩ D).card := card_le_card + (subset_inter (mem_flower.mp hC).2.2 (mem_flower.mp hD).2.2) + _ ≤ H.card := hinter C (flower_subset _ _ _ hC) D (flower_subset _ _ _ hD) hCD + +private def fan : ClauseSet (Fin 9) := + {{(0, true), (1, true), (2, true)}, {(0, true), (3, true), (4, true)}, + {(0, true), (5, true), (6, true)}, {(0, true), (7, true), (8, true)}} + +private def fanHeart : Finset (Literal (Fin 9)) := {(0, true)} + +private theorem fan_choice : Choice threshold fan 3 fanHeart := by + apply choice_of_uniform_intersections + · unfold Heavy + decide + · decide + · decide + +private def fanNodes (i : ℕ) := if i = 0 then fan else countingChild fan 3 fanHeart true + +-- A genuine maximum-heart B step introduces four width-two petals plus a negated unit. +example : ((fanNodes 1).filter fun C => C.card = 1).card = 1 := by decide + +example : ((fanNodes 1).filter fun C => C ∉ fan ∧ C.card = 2 ∧ {(1, true)} ⊆ C).card = 1 := by + decide + +example : ((fanNodes 1).filter fun C => C ∉ fan ∧ C.card = 2 ∧ {(1, true)} ⊆ C).card < 4 := by + have hstep : ∀ i < 1, fanNodes (i + 1) = countingChild (fanNodes i) 3 fanHeart true := by + intro i hi + have : i = 0 := by omega + subst i + rfl + have hchoice : ∀ i < 1, Choice threshold (fanNodes i) 3 fanHeart := by + intro i hi + have : i = 0 := by omega + subst i + exact fan_choice + exact card_counting_newContaining_lt threshold_pos 1 fanNodes (fun _ => 3) + (fun _ => fanHeart) (fun _ => true) hstep hchoice (by omega) (by decide) (by decide) + +-- All root clauses are unmarked, even if the initial root itself has high occurrence. +private def rootPairs : ClauseSet (Fin 5) := + {{(0, true), (1, true)}, {(0, true), (2, true)}, + {(0, true), (3, true)}, {(0, true), (4, true)}} + +example : (rootPairs.filter fun C => C ∉ rootPairs ∧ C.card = 2 ∧ {(0, true)} ⊆ C).card = 0 := by + decide + +example : ¬ (rootPairs.filter fun C => C.card = 2 ∧ {(0, true)} ⊆ C).card < 4 := by decide + +example : (rootPairs.filter fun C => C ∉ rootPairs ∧ C.card = 2 ∧ {(0, true)} ⊆ C).card < 4 := by + exact card_counting_newContaining_lt threshold_pos 0 (fun _ => rootPairs) (fun _ => 0) + (fun _ => ∅) (fun _ => false) (by intro i hi; omega) (by intro i hi; omega) + (t := 0) (by omega) (by decide) (by decide) + +-- Dropping maximum-heart priority really breaks the proposed bound, even in one step. +private def star : ClauseSet (Fin 6) := + {{(0, true), (1, true), (2, true)}, {(0, true), (1, true), (3, true)}, + {(0, true), (1, true), (4, true)}, {(0, true), (1, true), (5, true)}} + +example : Heavy threshold star 3 {(0, true)} := by unfold Heavy; decide + +example : ¬ Choice threshold star 3 {(0, true)} := by + intro h + have hh : Heavy threshold star 3 {(0, true), (1, true)} := by unfold Heavy; decide + have hbad := h.2.2 {(0, true), (1, true)} hh + change 2 ≤ 1 at hbad + omega + +example : ¬ ((countingChild star 3 {(0, true)} true).filter fun C => + C ∉ star ∧ C.card = 2 ∧ {(1, true)} ⊆ C).card < 4 := by decide + +-- Dropping minimum-width priority also breaks the bound: a second A step ignores an existing +-- width-two heavy clause. Both selected hearts still have maximal possible proper size. +private def minRoot : ClauseSet (Fin 5) := + {{(0, true), (1, true), (2, true)}, {(0, true), (3, true), (4, true)}} + +private def minFirst := countingChild minRoot 3 {(0, true), (1, true)} false +private def minSecond := countingChild minFirst 3 {(0, true), (3, true)} false + +example : Heavy (fun _ => 1) minFirst 3 {(0, true), (3, true)} := by unfold Heavy; decide + +example : ∀ H : Finset (Literal (Fin 5)), Heavy (fun _ => 1) minFirst 3 H → + H.card ≤ ({(0, true), (3, true)} : Finset (Literal (Fin 5))).card := by + intro H hh + have := hh.2.1 + change H.card ≤ 2 + omega + +example : ¬ Choice (fun _ => 1) minFirst 3 {(0, true), (3, true)} := by + intro h + have hh : Heavy (fun _ => 1) minFirst 2 {(0, true)} := by unfold Heavy; decide + have hbad := h.2.1 2 {(0, true)} hh + omega + +example : ¬ (minSecond.filter fun C => C ∉ minRoot ∧ C.card = 2 ∧ {(0, true)} ⊆ C).card < 2 := by + decide + +end ComplexityTests.SparsificationCountingOccurrences From 2adad2a0dd3deb619e5a5bf2f6c07338cb9902fa Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 01:15:34 +0000 Subject: [PATCH 04/24] feat(FineGrained,Sparsification): list-level counting sparsification, dense relabeling, and ETH endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the list-level counting-sparsification step, dense CNF variable relabeling, and fine-grained subexponential-time endpoints for #1226, following the unrefereed manuscript "The Exponential Time Hypothesis is False" (OpenAI, revision 7ef5c0cc, [OpenAI2026ETH]), §2 (Proposition 2.1) and §6.3 (Theorem 1.1 and Proposition 6.4): - `Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean`: defines `Cslib.Logic.PL.CNF.denseRelabel φ := φ.relabel φ.distinctVars.idxOf` (contrasted with `CNF.compact`, which uses `φ.vars.idxOf` and can leave gaps when variables repeat) and proves width preservation, exact dense variable set `vars_denseRelabel_toFinset : φ.denseRelabel.vars.toFinset = Finset.range φ.numVars`, `modelCountOn_denseRelabel : φ.denseRelabel.modelCountOn (Finset.range φ.numVars) = φ.modelCount`, and polynomial-time computability (`SAT.computableInPolyTime_denseRelabel`). - `Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean`: defines `Clause.isTautology`, `removeTautologies`, `countingNormalize`, `negatedHeartUnits`, `countingIntro`, `countingChild`, `countingNext`, `countingThreshold`, `Nontautological`, and `CountingNormalized`, and proves exact model-count conservation across normalization (`modelCountOn_countingNormalize`) and across the A/B counting-sparsification branch (`modelCountOn_countingNext`), invariant preservation (`countingNormalized_countingNext`), and variable-support containment (`support_countingNext`). - `Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPolyTime.lean`: proves `computableInPolyTime_countingThreshold_le`, `computableInPolyTime_isTautology`, `computableInPolyTime_removeTautologies`, `computableInPolyTime_countingNormalize`, and `computableInPolyTime_countingNext`. - `Complexity/Computability/Complexity/FineGrained/CountingScale.lean` and `CountingEndpoint.lean`: proves `exists_two_pow_mul_interpolationScale_le`, `DecidableInSubexpTime.of_interpolationScale`, `DecidableInSubexpTime.of_computableInO_interpolationScale`, `DecidableInSubexpTime.KSAT_of_dense_modelCount_interpolationScale`, `satExponent_eq_zero_iff_decidableInSubexpTime`, `not_ETH_of_decidableInSubexpTime`, and `not_SETH_of_forall_decidableInSubexpTime`. - `ComplexityTests/Computability/Complexity/FineGrained/CountingSparsifyPolyTime.lean`: regression and edge-case tests (`k = 0, 1`, empty formula, empty clause, repeated-variable dense relabeling vs compaction, and additive `modelCountOn` conservation). Refs #1226 AI assistance: written by worker-jetski-58251ede (Jetski agent). --- Complexity.lean | 4 + .../FineGrained/CountingEndpoint.lean | 298 +++++++++++++ .../Complexity/FineGrained/CountingScale.lean | 29 ++ .../CNF/ModelCount/DenseRelabel.lean | 204 +++++++++ .../Sparsification/CountingList.lean | 395 ++++++++++++++++++ .../Sparsification/CountingPolyTime.lean | 100 +++++ .../ClauseSet/Sparsification/List.lean | 17 +- .../ClauseSet/Sparsification/PolyTime.lean | 63 +-- .../FineGrained/CountingSparsifyPolyTime.lean | 116 +++++ 9 files changed, 1196 insertions(+), 30 deletions(-) create mode 100644 Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean create mode 100644 Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPolyTime.lean create mode 100644 ComplexityTests/Computability/Complexity/FineGrained/CountingSparsifyPolyTime.lean diff --git a/Complexity.lean b/Complexity.lean index a721be0d6..5a4099a75 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -761,6 +761,7 @@ public import Complexity.Computability.Complexity.Fagin.Verifier public import Complexity.Computability.Complexity.FineGrained public import Complexity.Computability.Complexity.FineGrained.Assignment public import Complexity.Computability.Complexity.FineGrained.BruteForce +public import Complexity.Computability.Complexity.FineGrained.CountingEndpoint public import Complexity.Computability.Complexity.FineGrained.CountingScale public import Complexity.Computability.Complexity.FineGrained.ExhaustiveSearch public import Complexity.Computability.Complexity.FineGrained.Exponents @@ -2148,6 +2149,7 @@ public import Complexity.Logics.Propositional.CNF.ImplicationChain public import Complexity.Logics.Propositional.CNF.ImplicationCycle public import Complexity.Logics.Propositional.CNF.ModelCount public import Complexity.Logics.Propositional.CNF.ModelCount.Declared +public import Complexity.Logics.Propositional.CNF.ModelCount.DenseRelabel public import Complexity.Logics.Propositional.CNF.ModelCount.Relabel public import Complexity.Logics.Propositional.CNF.Occurrence public import Complexity.Logics.Propositional.CNF.Parity @@ -2169,6 +2171,8 @@ public import Complexity.Logics.Propositional.ClauseSet.Basic public import Complexity.Logics.Propositional.ClauseSet.Sparsification public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean b/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean new file mode 100644 index 000000000..9eaf86f95 --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean @@ -0,0 +1,298 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.CountingScale +public import Complexity.Computability.Complexity.FineGrained.Reduction +public import Complexity.Logics.Propositional.CNF.ModelCount.DenseRelabel +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary + +/-! +# From sublinear interpolation-scale counting to `satExponent k = 0`, `¬ETH`, and `¬SETH` + +Bridges the sublinear exponent `Cslib.Complexity.SubexponentialSAT.interpolationScale` of +Section 6.4, Proposition 6.4 (pp. 43–44) [OpenAI2026ETH] and dense variable relabeling +(`Cslib.Logic.PL.CNF.denseRelabel`) to the fine-grained satisfiability endpoints +(`DecidableInSubexpTime (KSAT k) encCNF CNF.numVars`, `satExponent k = 0`, `¬ETH`, and `¬SETH`). + +### Source relation and library adaptations + +* **Proposition 6.4 (§6.4, pp. 43–44) vs. Theorem 1.1:** Proposition 6.4 establishes deterministic + exact model counting for $k$-CNF formulas on $n$ declared variables in time bounded using the + squared-$\log\log$ interpolation scale $\rho(n) = n (\log\log n)^2 / \sqrt{\log n}$ + (formalized with the $+16$ domain shift as `interpolationScale n`), whereas Theorem 1.1 states + the sharper single-$\log\log$ headline bound. This module uses Proposition 6.4's + `interpolationScale`, which is already $o(n)$ (`isLittleO_interpolationScale`). +* **Library adaptations:** First-occurrence dense variable numbering + (`CNF.denseRelabel φ = φ.relabel φ.distinctVars.idxOf`, mapping occurring variables onto + `Finset.range φ.numVars` while preserving `φ.modelCount` as + `φ.denseRelabel.modelCountOn (Finset.range φ.numVars)`) and the conditional complexity-class + reduction wrappers below are library adaptations connecting a dense-variable model counter to + `Cslib.Complexity`'s existing `KSAT`, `DecidableInSubexpTime`, `satExponent`, `ETH`, and `SETH` + definitions. This module assumes such a dense counter as a hypothesis and does not construct it. + +Specifically: +1. Any problem decidable in time `O(2 ^ (A₀ * interpolationScale (μ a)) * (|enc a| + 1) ^ d)` is + `DecidableInSubexpTime` with respect to `μ` (`DecidableInSubexpTime.of_interpolationScale`, + `DecidableInSubexpTime.of_computableInO_interpolationScale`). +2. Any model-counting routine `count : CNF ℕ → ℕ` that computes + `ψ.modelCountOn (Finset.range ψ.numVars)` on dense width-`≤ k` formulas (`ψ.vars.toFinset = + Finset.range ψ.numVars`) within time `O(2 ^ (A₀ * interpolationScale ψ.numVars) * (|ψ| + 1) ^ e)` + yields `DecidableInSubexpTime (KSAT k) encCNF CNF.numVars` via polynomial-time dense relabeling + and clause-width checking (`DecidableInSubexpTime.KSAT_of_dense_modelCount_interpolationScale`). +3. Subexponential-time decidability of `KSAT k` is equivalent to `satExponent k = 0` + (`satExponent_eq_zero_iff_decidableInSubexpTime`), refutes `ETH` at `k = 3` + (`not_ETH_of_decidableInSubexpTime`), and refutes `SETH` when established for all `k` + (`not_SETH_of_forall_decidableInSubexpTime`). + +## Main results + +* `Cslib.Logic.PL.CNF.denseRelabel_mem_KSAT_iff`: dense relabeling preserves `KSAT k`. +* `Cslib.Complexity.DecidableInSubexpTime.of_interpolationScale`, + `Cslib.Complexity.DecidableInSubexpTime.of_computableInO_interpolationScale`: subexponential + time from `interpolationScale` runtime bounds. +* `Cslib.Complexity.DecidableInSubexpTime.KSAT_of_dense_modelCount_interpolationScale`: + `DecidableInSubexpTime (KSAT k)` from a dense-variable model counter at `interpolationScale`. +* `Cslib.Complexity.satExponent_eq_zero_of_decidableInSubexpTime`, + `Cslib.Complexity.satExponent_eq_zero_iff_decidableInSubexpTime`: `satExponent k = 0`. +* `Cslib.Complexity.not_ETH_of_decidableInSubexpTime`, + `Cslib.Complexity.not_SETH_of_forall_decidableInSubexpTime`: refutation of `ETH` and `SETH`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Section 6.4, + Proposition 6.4, pp. 43–44. +* [R. Impagliazzo, R. Paturi, *On the complexity of k-SAT*][ImpagliazzoPaturi2001] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF + +open Cslib.Complexity + +/-- Dense variable relabeling preserves membership in `KSAT k` for every `k` (including `k = 0, 1`, +empty formulas, and formulas with repeated literals or gaps in variable numbering). -/ +@[simp] +theorem denseRelabel_mem_KSAT_iff {k : ℕ} {φ : CNF ℕ} : + φ.denseRelabel ∈ KSAT k ↔ φ ∈ KSAT k := by + simp only [KSAT, Set.mem_ofPred_eq, all_length_le_denseRelabel, satisfiable_denseRelabel] + +end Cslib.Logic.PL.CNF + +namespace Cslib.Complexity + +open Cslib.Logic.PL Turing.MultiTapeTM SubexponentialSAT + +/-! ### From `interpolationScale` bounds to `DecidableInSubexpTime` -/ + +/-- Any problem decidable in time `O(2 ^ (A₀ * interpolationScale (μ a)) * (|enc a| + 1) ^ d)` is +decidable in subexponential time in `μ`. -/ +theorem DecidableInSubexpTime.of_interpolationScale {α : Type} {enc : α ↪ List Bool} + {A : Set α} {μ : α → ℕ} {A₀ : ℝ} {d : ℕ} + (h : DecidableInTimeO A enc (fun a => + (2 : ℝ) ^ (A₀ * interpolationScale (μ a)) * (((enc a).length : ℝ) + 1) ^ d)) : + DecidableInSubexpTime A enc μ := by + intro δ hδ + obtain ⟨K, _, hK⟩ := exists_two_pow_mul_interpolationScale_le A₀ hδ + refine ⟨d, h.mono ⌈K⌉₊ (fun a => ?_) fun a => by positivity⟩ + have hceil : K ≤ ⌈K⌉₊ := Nat.le_ceil K + have h1 : (2 : ℝ) ^ (A₀ * interpolationScale (μ a)) ≤ ⌈K⌉₊ * (2 : ℝ) ^ (δ * μ a) := + (hK (μ a)).trans (mul_le_mul_of_nonneg_right hceil (by positivity)) + have h2 := mul_le_mul_of_nonneg_right h1 + (by positivity : (0 : ℝ) ≤ (((enc a).length : ℝ) + 1) ^ d) + linarith + +/-- If `f : α → Bool` decides `A` in `ComputableInO` time bounded by +`c * (2 ^ (A₀ * interpolationScale (μ a)) * (|enc a| + 1) ^ e) + c`, then `A` is decidable in +subexponential time in `μ`. -/ +theorem DecidableInSubexpTime.of_computableInO_interpolationScale {α : Type} {enc : α ↪ List Bool} + {A : Set α} {μ : α → ℕ} {f : α → Bool} {t : α → ℕ} + (hf : ComputableInO f enc encBool t t) (hfA : ∀ a, a ∈ A ↔ f a = true) + {A₀ : ℝ} {c e : ℕ} + (ht : ∀ a, (t a : ℝ) ≤ + c * ((2 : ℝ) ^ (A₀ * interpolationScale (μ a)) * (((enc a).length : ℝ) + 1) ^ e) + c) : + DecidableInSubexpTime A enc μ := by + intro δ hδ + obtain ⟨K, _, hK⟩ := exists_two_pow_mul_interpolationScale_le A₀ hδ + refine DecidableInExpTime.of_computableInO hf hfA (c * ⌈K⌉₊ + c) e fun a => ?_ + refine (ht a).trans ?_ + have hceil : K ≤ ⌈K⌉₊ := Nat.le_ceil K + have hX1 : (1 : ℝ) ≤ (2 : ℝ) ^ (δ * μ a) := Real.one_le_rpow (by norm_num) (by positivity) + have hP1 : (1 : ℝ) ≤ (((enc a).length : ℝ) + 1) ^ e := + one_le_pow₀ (by linarith [(Nat.cast_nonneg (enc a).length : (0 : ℝ) ≤ _)]) + have hXP1 : (1 : ℝ) ≤ (2 : ℝ) ^ (δ * μ a) * (((enc a).length : ℝ) + 1) ^ e := + one_le_mul_of_one_le_of_one_le hX1 hP1 + have hcXP : (c : ℝ) ≤ (c : ℝ) * ((2 : ℝ) ^ (δ * μ a) * (((enc a).length : ℝ) + 1) ^ e) := + le_mul_of_one_le_right (Nat.cast_nonneg c) hXP1 + have h1 : (2 : ℝ) ^ (A₀ * interpolationScale (μ a)) ≤ ⌈K⌉₊ * (2 : ℝ) ^ (δ * μ a) := + (hK (μ a)).trans (mul_le_mul_of_nonneg_right hceil (by positivity)) + have h2 := mul_le_mul_of_nonneg_right h1 + (by positivity : (0 : ℝ) ≤ (((enc a).length : ℝ) + 1) ^ e) + rw [mul_assoc] at h2 + have h3 := mul_le_mul_of_nonneg_left h2 (Nat.cast_nonneg c) + push_cast + linarith + +/-! ### From dense-variable model counting to `DecidableInSubexpTime (KSAT k)` -/ + +open ComputableTidilyInPolyTime in +private theorem computableInPolyTime_pos_nat : + ComputableInPolyTime (fun n : ℕ => decide (0 < n)) encNat encBool := by + have heq : (fun n : ℕ => decide (0 < n)) = fun n => !(n == 0) := by + funext n + cases n <;> rfl + rw [heq] + refine .of_tidy ?_ + fun_prop + +/-- **From dense-variable model counting at `interpolationScale` to subexponential `k`-SAT.** +Suppose `count : CNF ℕ → ℕ` runs in time +`O(2 ^ (A₀ * interpolationScale ψ.numVars) * (|encCNF ψ| + 1) ^ e)` and computes the +declared-variable model count `ψ.modelCountOn (Finset.range ψ.numVars)` on every width-`≤ k` +formula `ψ` whose occurring variables are `Finset.range ψ.numVars`. Then `KSAT k` is decidable in +subexponential time in the number of occurring variables (`CNF.numVars`). -/ +theorem DecidableInSubexpTime.KSAT_of_dense_modelCount_interpolationScale {k : ℕ} + {count : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} + (hcount : ComputableInO count encCNF encNat t t) + (hspec : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + ψ.vars.toFinset = Finset.range ψ.numVars → + count ψ = ψ.modelCountOn (Finset.range ψ.numVars)) + {A₀ : ℝ} {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ + c * ((2 : ℝ) ^ (A₀ * interpolationScale ψ.numVars) * (((encCNF ψ).length : ℝ) + 1) ^ e) + c) : + DecidableInSubexpTime (KSAT k) encCNF CNF.numVars := by + have hcount_tidy := hcount.tidy + obtain ⟨dpos, _, hpos⟩ := computableInPolyTime_pos_nat.tidy.exists_degree + have hposO := hpos.inO + obtain ⟨l, hl⟩ := hcount_tidy.length_le_time + have hcomp : ComputableInO (fun ψ : CNF ℕ => decide (0 < count ψ)) encCNF encBool _ _ := + .of_tidy (hposO.comp hcount_tidy) + let B : Set (CNF ℕ) := {ψ | decide (0 < count ψ) = true} + have hB_subexp : DecidableInSubexpTime B encCNF CNF.numVars := by + set A₁ : ℝ := max A₀ 0 * (dpos + 1) + set E : ℕ := e * (dpos + 1) + set C₀ : ℕ := (2 * c + 1) + (2 * l + 1) ^ dpos * (2 * c + 2) ^ dpos + refine DecidableInSubexpTime.of_computableInO_interpolationScale hcomp (fun _ => Iff.rfl) + (A₀ := A₁) (c := C₀) (e := E) fun ψ => ?_ + set L : ℝ := ((encCNF ψ).length : ℝ) + set X₀ : ℝ := (2 : ℝ) ^ (max A₀ 0 * interpolationScale ψ.numVars) + set P₀ : ℝ := (L + 1) ^ e + have hX₀1 : 1 ≤ X₀ := Real.one_le_rpow (by norm_num) + (mul_nonneg (le_max_right _ _) (interpolationScale_nonneg _)) + have hP₀1 : 1 ≤ P₀ := + one_le_pow₀ (by linarith [(Nat.cast_nonneg (encCNF ψ).length : (0 : ℝ) ≤ L)]) + have hXP1 : 1 ≤ X₀ * P₀ := one_le_mul_of_one_le_of_one_le hX₀1 hP₀1 + have hX_mono : (2 : ℝ) ^ (A₀ * interpolationScale ψ.numVars) ≤ X₀ := + Real.rpow_le_rpow_of_exponent_le (by norm_num) + (mul_le_mul_of_nonneg_right (le_max_left _ _) (interpolationScale_nonneg _)) + have ht_le : (t ψ : ℝ) ≤ (2 * c + 1) * (X₀ * P₀) := by + have h1 := ht ψ + have h2 : (2 : ℝ) ^ (A₀ * interpolationScale ψ.numVars) * P₀ ≤ X₀ * P₀ := + mul_le_mul_of_nonneg_right hX_mono (by positivity) + have h3 := mul_le_mul_of_nonneg_left h2 (Nat.cast_nonneg c) + have h4 : (c : ℝ) ≤ c * (X₀ * P₀) := le_mul_of_one_le_right (Nat.cast_nonneg c) hXP1 + linarith + have hlen_le : ((encNat (count ψ)).length : ℝ) + 1 ≤ + (2 * l + 1) * (2 * c + 2) * (X₀ * P₀) := by + have h1 : ((encNat (count ψ)).length : ℝ) ≤ l * ((t ψ : ℝ) + 1) := by + exact_mod_cast hl ψ + have h2 : (t ψ : ℝ) + 1 ≤ (2 * c + 2) * (X₀ * P₀) := by linarith + have h3 := mul_le_mul_of_nonneg_left h2 (Nat.cast_nonneg l) + nlinarith + have hpow_le : (((encNat (count ψ)).length : ℝ) + 1) ^ dpos ≤ + ((2 * l + 1) ^ dpos * (2 * c + 2) ^ dpos) * (X₀ * P₀) ^ dpos := by + calc (((encNat (count ψ)).length : ℝ) + 1) ^ dpos + ≤ ((2 * l + 1) * (2 * c + 2) * (X₀ * P₀)) ^ dpos := + pow_le_pow_left₀ (by positivity) hlen_le dpos + _ = ((2 * l + 1) ^ dpos * (2 * c + 2) ^ dpos) * (X₀ * P₀) ^ dpos := by + rw [mul_pow, mul_pow] + have hXP_dpos : (X₀ * P₀) ^ dpos ≤ (X₀ * P₀) ^ (dpos + 1) := + pow_le_pow_right₀ hXP1 (Nat.le_succ dpos) + have hXP_one : X₀ * P₀ ≤ (X₀ * P₀) ^ (dpos + 1) := + le_self_pow₀ hXP1 (Nat.succ_ne_zero dpos) + have hXP_Rewrite : (X₀ * P₀) ^ (dpos + 1) = + (2 : ℝ) ^ (A₁ * interpolationScale ψ.numVars) * (L + 1) ^ E := by + rw [mul_pow] + congr 1 + · simp only [X₀, A₁, ← Real.rpow_natCast] + rw [← Real.rpow_mul (by norm_num)] + congr 1 + push_cast + ring + · simp only [P₀, E, ← pow_mul] + have htot : (t ψ : ℝ) + (((encNat (count ψ)).length : ℝ) + 1) ^ dpos ≤ + C₀ * ((X₀ * P₀) ^ (dpos + 1)) := by + have ht_le' : (t ψ : ℝ) ≤ (c + 1 + c) * (X₀ * P₀) ^ (dpos + 1) := by + have := mul_le_mul_of_nonneg_left hXP_one (by positivity : (0 : ℝ) ≤ 2 * c + 1) + linarith + have hpow_le' : (((encNat (count ψ)).length : ℝ) + 1) ^ dpos ≤ + ((2 * l + 1) ^ dpos * (2 * c + 2) ^ dpos) * (X₀ * P₀) ^ (dpos + 1) := by + refine hpow_le.trans (mul_le_mul_of_nonneg_left hXP_dpos (by positivity)) + simp only [C₀] + push_cast + linarith + push_cast at htot ⊢ + rw [← hXP_Rewrite] + have hC₀ : (0 : ℝ) ≤ C₀ := Nat.cast_nonneg C₀ + linarith + have hB'_subexp : DecidableInSubexpTime {φ : CNF ℕ | φ.denseRelabel ∈ B} encCNF CNF.numVars := + hB_subexp.of_reduction SAT.computableInPolyTime_denseRelabel (fun _ => Iff.rfl) (c := 1) + fun φ => by rw [CNF.numVars_denseRelabel]; omega + intro δ hδ + have hKSAT_eq : KSAT k = {φ : CNF ℕ | (φ.all fun c => decide (c.length ≤ k)) = true} ∩ + {φ : CNF ℕ | φ.denseRelabel ∈ B} := by + ext φ + simp only [KSAT, B, Set.mem_inter_iff, Set.mem_ofPred_eq, List.all_eq_true, decide_eq_true_eq] + refine and_congr_right fun hφ => ?_ + have hvars : φ.denseRelabel.vars.toFinset = Finset.range φ.denseRelabel.numVars := by + rw [CNF.numVars_denseRelabel, CNF.vars_denseRelabel_toFinset] + rw [hspec φ.denseRelabel ((CNF.all_length_le_denseRelabel φ).mpr hφ) hvars, + CNF.numVars_denseRelabel, CNF.modelCountOn_denseRelabel, CNF.modelCount_pos_iff] + rw [hKSAT_eq] + exact (hB'_subexp δ hδ).inter (computableInPolyTime_all_length_le k) hδ.le + +/-! ### Exponent and ETH/SETH consequences -/ + +/-- If `KSAT k` is decidable in subexponential time in the number of variables, then +`satExponent k = 0`. -/ +theorem satExponent_eq_zero_of_decidableInSubexpTime {k : ℕ} + (h : DecidableInSubexpTime (KSAT k) encCNF CNF.numVars) : + satExponent k = 0 := by + refine le_antisymm (le_of_not_gt fun hpos => ?_) (satExponent_nonneg k) + exact not_decidableInExpTime_of_lt_satExponent (half_pos hpos).le (half_lt_self hpos) + (h (satExponent k / 2) (half_pos hpos)) + +/-- `satExponent k = 0` if and only if `KSAT k` is decidable in subexponential time in the number +of variables. -/ +theorem satExponent_eq_zero_iff_decidableInSubexpTime {k : ℕ} : + satExponent k = 0 ↔ DecidableInSubexpTime (KSAT k) encCNF CNF.numVars := by + refine ⟨fun h δ hδ => ?_, satExponent_eq_zero_of_decidableInSubexpTime⟩ + by_contra hndec + have hle := le_satExponent_of_not_decidableInExpTime hndec + linarith + +/-- Subexponential-time decidability of 3-SAT refutes the Exponential Time Hypothesis (`¬ETH`). -/ +theorem not_ETH_of_decidableInSubexpTime + (h : DecidableInSubexpTime (KSAT 3) encCNF CNF.numVars) : ¬ETH := by + rw [ETH_iff_satExponent_pos, satExponent_eq_zero_of_decidableInSubexpTime h] + exact lt_irrefl 0 + +/-- Subexponential-time decidability of `k`-SAT for all `k` refutes the Strong Exponential Time +Hypothesis (`¬SETH`). -/ +theorem not_SETH_of_forall_decidableInSubexpTime + (h : ∀ k, DecidableInSubexpTime (KSAT k) encCNF CNF.numVars) : ¬SETH := by + rintro hSETH + obtain ⟨k, hk⟩ := hSETH (1 / 2) (by norm_num) + have h12 : (1 : ℝ) - 1 / 2 = 1 / 2 := by norm_num + rw [h12] at hk + exact hk (h k (1 / 2) (by norm_num)) + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/FineGrained/CountingScale.lean b/Complexity/Computability/Complexity/FineGrained/CountingScale.lean index fd4d1d6fa..606596edb 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingScale.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingScale.lean @@ -79,4 +79,33 @@ theorem eventually_mul_interpolationScale_le (A : ℝ) {ε : ℝ} (hε : 0 < ε) have h := mul_le_mul_of_nonneg_right hn.le (Nat.cast_nonneg n : (0 : ℝ) ≤ n) simpa only [interpolationScale, mul_div_assoc, mul_assoc, mul_left_comm, mul_comm] using h +/-- The interpolation scale is nonnegative at every natural input. -/ +theorem interpolationScale_nonneg (n : ℕ) : 0 ≤ interpolationScale n := by + unfold interpolationScale + positivity + +/-- For any multiplier `A` and any `ε > 0`, `2 ^ (A * interpolationScale n)` is globally bounded +by a constant multiple of `2 ^ (ε * n)`. -/ +theorem exists_two_pow_mul_interpolationScale_le (A : ℝ) {ε : ℝ} (hε : 0 < ε) : + ∃ K : ℝ, 1 ≤ K ∧ ∀ n : ℕ, (2 : ℝ) ^ (A * interpolationScale n) ≤ K * (2 : ℝ) ^ (ε * n) := by + obtain ⟨N₀, hN₀⟩ := eventually_atTop.mp (eventually_mul_interpolationScale_le A hε) + let K : ℝ := 1 + ∑ m ∈ Finset.range N₀, (2 : ℝ) ^ (A * interpolationScale m) + have hsum_nonneg : 0 ≤ ∑ m ∈ Finset.range N₀, (2 : ℝ) ^ (A * interpolationScale m) := + Finset.sum_nonneg fun m _ => by positivity + have hK1 : 1 ≤ K := by dsimp [K]; linarith + refine ⟨K, hK1, fun n => ?_⟩ + have h2ε : (1 : ℝ) ≤ (2 : ℝ) ^ (ε * n) := Real.one_le_rpow (by norm_num) (by positivity) + by_cases hn : n < N₀ + · have hmem : n ∈ Finset.range N₀ := Finset.mem_range.mpr hn + have hle_sum : (2 : ℝ) ^ (A * interpolationScale n) ≤ + ∑ m ∈ Finset.range N₀, (2 : ℝ) ^ (A * interpolationScale m) := + Finset.single_le_sum (f := fun m => (2 : ℝ) ^ (A * interpolationScale m)) + (fun m _ => by positivity) hmem + have hle_K : (2 : ℝ) ^ (A * interpolationScale n) ≤ K := by dsimp [K]; linarith + exact hle_K.trans (le_mul_of_one_le_right (by positivity) h2ε) + · have hle : A * interpolationScale n ≤ ε * n := hN₀ n (not_lt.mp hn) + have hrpow : (2 : ℝ) ^ (A * interpolationScale n) ≤ (2 : ℝ) ^ (ε * n) := + Real.rpow_le_rpow_of_exponent_le (by norm_num) hle + exact hrpow.trans (le_mul_of_one_le_left (by positivity) hK1) + end Cslib.Complexity.SubexponentialSAT diff --git a/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean b/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean new file mode 100644 index 000000000..74c56c4c1 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean @@ -0,0 +1,204 @@ +/- +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.CNF.ModelCount.Declared +public import Complexity.Logics.Propositional.CNF.Compact.Relabel +public import Complexity.Computability.Complexity.Problems.SAT.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + +/-! +# Dense variable numbering for CNF formulas + +`CNF.denseRelabel φ` renames each variable of a CNF formula `φ` by its index in `φ.distinctVars`. +Unlike `CNF.compact` (which uses first positions in `φ.vars` and can leave gaps when variables +repeat), `CNF.denseRelabel φ` maps the occurring variables bijectively onto `0, …, φ.numVars - 1` +in order of first occurrence [OpenAI2026ETH, Section 2]. + +The renaming preserves clause lengths, clause count, `numVars`, `Satisfiable`, `modelCount`, and +gives `φ.denseRelabel.modelCountOn (Finset.range φ.numVars) = φ.modelCount`. It is computable in +polynomial time for any variable encoding, both into unary variable indices and into binary +natural-number indices (`encCNF`). + +## Main definitions + +* `Cslib.Logic.PL.CNF.denseRelabel`: rename variables by their positions in `distinctVars`. + +## Main results + +* `Cslib.Logic.PL.CNF.vars_denseRelabel_toFinset`, + `Cslib.Logic.PL.CNF.numVars_denseRelabel`, + `Cslib.Logic.PL.CNF.distinctVars_denseRelabel`: the occurring variables of `φ.denseRelabel` are + `0, …, φ.numVars - 1` in order. +* `Cslib.Logic.PL.CNF.satisfiable_denseRelabel`, + `Cslib.Logic.PL.CNF.modelCount_denseRelabel`, + `Cslib.Logic.PL.CNF.modelCountOn_denseRelabel`: satisfiability and model counts are preserved. +* `Cslib.Logic.PL.CNF.computableInPolyTime_denseRelabel_unary`, + `Cslib.Logic.PL.CNF.computableInPolyTime_denseRelabel`, + `Cslib.Complexity.SAT.computableInPolyTime_denseRelabel`: dense relabeling is computable in + polynomial time. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF + +open Turing.MultiTapeTM ComputableTidilyInPolyTime + +variable {V : Type*} [DecidableEq V] + +/-- Rename each variable of `φ` by its index in `φ.distinctVars`, producing dense variable indices +`0, …, φ.numVars - 1`. -/ +def denseRelabel (φ : CNF V) : CNF ℕ := + φ.relabel φ.distinctVars.idxOf + +/-- Dense relabeling preserves all clause lengths in order. -/ +@[simp] +theorem map_length_denseRelabel (φ : CNF V) : + φ.denseRelabel.map List.length = φ.map List.length := by + simp [denseRelabel, CNF.relabel, Clause.relabel, List.map_map, Function.comp_def] + +/-- Dense relabeling preserves the number of clauses. -/ +@[simp] +theorem length_denseRelabel (φ : CNF V) : + φ.denseRelabel.length = φ.length := by + simp [denseRelabel, CNF.relabel] + +/-- Dense relabeling preserves any uniform upper bound on clause lengths. -/ +theorem all_length_le_denseRelabel {k : ℕ} (φ : CNF V) : + (∀ c ∈ φ.denseRelabel, c.length ≤ k) ↔ ∀ c ∈ φ, c.length ≤ k := by + simp [denseRelabel, CNF.relabel] + +/-- The occurring variables of `φ.denseRelabel` are `Finset.range φ.numVars`. -/ +@[simp] +theorem vars_denseRelabel_toFinset (φ : CNF V) : + φ.denseRelabel.vars.toFinset = Finset.range φ.numVars := + vars_dense_relabel φ + +/-- Every variable occurring in `φ.denseRelabel` is strictly below `φ.numVars`. -/ +theorem mem_vars_denseRelabel_lt (φ : CNF V) {v : ℕ} (hv : v ∈ φ.denseRelabel.vars) : + v < φ.numVars := by + have hv' : v ∈ φ.denseRelabel.vars.toFinset := List.mem_toFinset.mpr hv + rwa [vars_denseRelabel_toFinset, Finset.mem_range] at hv' + +/-- Dense relabeling preserves the number of distinct occurring variables. -/ +@[simp] +theorem numVars_denseRelabel (φ : CNF V) : + φ.denseRelabel.numVars = φ.numVars := by + rw [numVars, vars_denseRelabel_toFinset, Finset.card_range] + +private theorem map_idxOf_of_nodup {α : Type*} [DecidableEq α] (l : List α) (hl : l.Nodup) : + l.map l.idxOf = List.range l.length := by + refine List.ext_getElem (by simp) fun i h₁ h₂ => ?_ + simp only [List.getElem_map, List.getElem_range] + exact List.Nodup.idxOf_getElem hl i _ + +/-- The distinct variables of `φ.denseRelabel` in order of first occurrence are +`0, …, φ.numVars - 1`. -/ +@[simp] +theorem distinctVars_denseRelabel (φ : CNF V) : + φ.denseRelabel.distinctVars = List.range φ.numVars := by + have hinj : Set.InjOn φ.distinctVars.idxOf {v | v ∈ φ.vars} := + fun _ ha _ _ h => (List.idxOf_inj (mem_distinctVars.mpr ha)).mp h + rw [denseRelabel, distinctVars_relabel_of_injOn φ φ.distinctVars.idxOf hinj, + map_idxOf_of_nodup φ.distinctVars φ.nodup_distinctVars, length_distinctVars] + +/-- Reading original variable values at their `distinctVars` indices recovers the evaluation of +`φ`. -/ +theorem eval_denseRelabel (φ : CNF V) (σ : V → Bool) : + φ.denseRelabel.eval (fun i => φ.distinctVars[i]?.elim false σ) = φ.eval σ := by + rw [denseRelabel, CNF.eval_relabel] + apply CNF.eval_congr + intro v hv + have hv' : v ∈ φ.distinctVars := mem_distinctVars.mpr hv + simp [List.getElem?_idxOf hv'] + +/-- Dense relabeling preserves satisfiability. -/ +@[simp] +theorem satisfiable_denseRelabel (φ : CNF V) : + φ.denseRelabel.Satisfiable ↔ φ.Satisfiable := by + constructor + · rintro ⟨σ, hσ⟩ + exact ⟨σ ∘ φ.distinctVars.idxOf, by simpa only [denseRelabel, CNF.eval_relabel] using hσ⟩ + · rintro ⟨σ, hσ⟩ + exact ⟨fun i => φ.distinctVars[i]?.elim false σ, (eval_denseRelabel φ σ).trans hσ⟩ + +/-- Dense relabeling preserves the occurring-variable model count. -/ +@[simp] +theorem modelCount_denseRelabel (φ : CNF V) : + φ.denseRelabel.modelCount = φ.modelCount := + modelCount_dense_relabel φ + +/-- Dense relabeling preserves the declared-variable model count on `Finset.range φ.numVars`. -/ +theorem modelCountOn_denseRelabel (φ : CNF V) : + φ.denseRelabel.modelCountOn (Finset.range φ.numVars) = φ.modelCount := + modelCountOn_dense_relabel φ + +section PolyTime + +variable {W : Type} [DecidableEq W] + +attribute [local fun_prop] ComputableInPolyTime.tidy in +/-- Dense relabeling is polynomial-time into unary variable indices for any variable encoding. -/ +@[fun_prop] +theorem computableInPolyTime_denseRelabel_unary (enc : W ↪ List Bool) : + ComputableInPolyTime (CNF.denseRelabel (V := W)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair encUnary encBool))) := by + have hd : ComputableTidilyInPolyTime (CNF.distinctVars (V := W)) + (encList (encList (encPair enc encBool))) (encList enc) := + (CNF.computableInPolyTime_distinctVars enc).tidy + refine .of_tidy ?_ + unfold CNF.denseRelabel CNF.relabel Clause.relabel Prod.map + fun_prop + +attribute [local fun_prop] ComputableInPolyTime.tidy in +/-- Dense relabeling is polynomial-time into binary natural-number variable indices (`encCNF`) for +any variable encoding. -/ +@[fun_prop] +theorem computableInPolyTime_denseRelabel (enc : W ↪ List Bool) : + ComputableInPolyTime (CNF.denseRelabel (V := W)) + (encList (encList (encPair enc encBool))) + Cslib.Complexity.encCNF := by + have h1 : ComputableTidilyInPolyTime (CNF.denseRelabel (V := W)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair encUnary encBool))) := + (computableInPolyTime_denseRelabel_unary enc).tidy + have h2 : ComputableTidilyInPolyTime (CNF.relabel (id : ℕ → ℕ)) + (encList (encList (encPair encUnary encBool))) + (encList (encList (encPair encNat encBool))) := + (CNF.computableInPolyTime_relabel (encV := encUnary) (encW := encNat) + (.of_tidy unary_to_binary)).tidy + have heq : (CNF.denseRelabel (V := W)) = fun φ => (CNF.denseRelabel φ).relabel id := by + funext φ + exact (CNF.relabel_id _).symm + rw [heq] + exact .of_tidy (h2.comp h1) + +end PolyTime + +end Cslib.Logic.PL.CNF + +namespace Cslib.Complexity.SAT + +open Cslib.Logic.PL Turing.MultiTapeTM + +/-- Dense variable relabeling on `CNF ℕ` is computable in polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_denseRelabel : + ComputableInPolyTime (CNF.denseRelabel (V := ℕ)) encCNF encCNF := + CNF.computableInPolyTime_denseRelabel encNat + +end Cslib.Complexity.SAT diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean new file mode 100644 index 000000000..8f82fcc9a --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean @@ -0,0 +1,395 @@ +/- +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.ClauseSet.Sparsification.Counting +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List + +/-! +# Counting sparsification on CNF formulas + +List-level operations for the counting sparsification refinement of Appendix A.1 [OpenAI2026ETH]: +tautology removal (`removeTautologies`), nontautological normalization (`countingNormalize`), +singleton negated-heart unit clauses (`negatedHeartUnits`), the disjoint counting children +(`countingChild`), and one deterministic counting-sparsification step (`countingNext`). + +At each branching step on a heart `H`, the `false` (A) branch adds `H` while the `true` (B) branch +adds both the petals `petals ψ c H` and the singleton negated-heart units `negatedHeartUnits H` +[OpenAI2026ETH, Appendix A.1, equation (A.2)]. Applying `dedupClauses` before `reduce` ensures that +the list-level normal form (`CountingNormalized`) is preserved unconditionally across steps while +computing the exact clause-set operation `ClauseSet.Sparsification.countingChild` +(`toClauseSet_countingChild`) and conserving the declared-variable model count +(`modelCountOn_countingChild`). + +## Main definitions + +* `Cslib.Logic.PL.CNF.Sparsification.Clause.isTautology`, + `Cslib.Logic.PL.CNF.Sparsification.removeTautologies`, + `Cslib.Logic.PL.CNF.Sparsification.countingNormalize`: tautology removal and normalization. +* `Cslib.Logic.PL.CNF.Sparsification.negatedHeartUnits`, + `Cslib.Logic.PL.CNF.Sparsification.countingIntro`, + `Cslib.Logic.PL.CNF.Sparsification.countingChild`, + `Cslib.Logic.PL.CNF.Sparsification.countingNext`: the list-level counting step. +* `Cslib.Logic.PL.CNF.Sparsification.countingThreshold`: the counting thresholds `θ j = M ^ j`. +* `Cslib.Logic.PL.CNF.Sparsification.Nontautological`, + `Cslib.Logic.PL.CNF.Sparsification.CountingNormalized`: the nontautological normal form. + +## Main results + +* `Cslib.Logic.PL.CNF.Sparsification.eval_countingNormalize`, + `Cslib.Logic.PL.CNF.Sparsification.modelCountOn_countingNormalize`, + `Cslib.Logic.PL.CNF.Sparsification.countingNormalized_countingNormalize`: nontautological + normalization preserves evaluation and declared-variable model counts. +* `Cslib.Logic.PL.CNF.Sparsification.toClauseSet_countingChild`, + `Cslib.Logic.PL.CNF.Sparsification.toClauseSet_countingNext`: the list-level counting children + compute the clause-set counting children. +* `Cslib.Logic.PL.CNF.Sparsification.modelCountOn_countingChild`, + `Cslib.Logic.PL.CNF.Sparsification.modelCountOn_countingNext`: additive conservation of + `CNF.modelCountOn` across both branches. +* `Cslib.Logic.PL.CNF.Sparsification.countingNormalized_countingChild`, + `Cslib.Logic.PL.CNF.Sparsification.countingNormalized_countingNext`: the counting step preserves + the nontautological normal form. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.1][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.Sparsification + +open Finset ClauseSet.Sparsification +open scoped List + +variable {V : Type*} [DecidableEq V] + +/-! ### Tautology removal and counting normalization -/ + +/-- Whether a clause contains both a literal and its negation. -/ +def Clause.isTautology (C : Clause V) : Bool := + C.any fun l => C.any (· == Std.Sat.Literal.negate l) + +/-- Remove tautological clauses from a CNF formula. -/ +def removeTautologies (φ : CNF V) : CNF V := + φ.filter fun C => !Clause.isTautology C + +/-- Bring a formula into nontautological normal form: remove tautological clauses, deduplicate +literals and clauses, and reduce by strict subset containment. -/ +def countingNormalize (φ : CNF V) : CNF V := + normalize (removeTautologies φ) + +/-- Singleton clauses `[-l]` for each literal `l` of a heart `H`. -/ +def negatedHeartUnits (H : Clause V) : CNF V := + H.map fun l => [Std.Sat.Literal.negate l] + +/-- The clauses introduced by a counting-sparsification branch: the heart (`false`) or the petals +together with the singleton negated-heart units (`true`). -/ +def countingIntro (ψ : CNF V) (c : ℕ) (H : Clause V) (b : Bool) : CNF V := + if b then petals ψ c H ++ negatedHeartUnits H else [H] + +/-- A counting child of a branching node: append the introduced clauses, deduplicate clauses with +identical literal sets, and reduce by strict subset containment. -/ +def countingChild (ψ : CNF V) (c : ℕ) (H : Clause V) (b : Bool) : CNF V := + reduce (dedupClauses (ψ ++ countingIntro ψ c H b)) + +/-- One deterministic counting-sparsification step using the computed choice `choice θ k ψ`. -/ +def countingNext (θ : ℕ → ℕ) (k : ℕ) (ψ : CNF V) (b : Bool) : CNF V := + countingChild ψ (choice θ k ψ).1 (choice θ k ψ).2 b + +/-- The counting-sparsification threshold schedule `θ j = M ^ j` [OpenAI2026ETH, Appendix A.1, +equation (A.1)]. -/ +def countingThreshold (M j : ℕ) : ℕ := M ^ j + +/-- For `1 ≤ M`, every counting threshold is positive. -/ +theorem one_le_countingThreshold {M : ℕ} (hM : 1 ≤ M) (j : ℕ) : + 1 ≤ countingThreshold M j := + Nat.one_le_pow j M hM + +/-! ### Nontautological normal form -/ + +/-- A CNF formula is *nontautological* if no clause contains both a literal and its negation. -/ +def Nontautological (ψ : CNF V) : Prop := + ∀ C ∈ ψ, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C + +/-- The normal form for list-level counting sparsification at width `k`: `Normalized k ψ` together +with `Nontautological ψ`. -/ +structure CountingNormalized (k : ℕ) (ψ : CNF V) : Prop extends Normalized k ψ where + nontautological : Nontautological ψ + +theorem Clause.isTautology_eq_true_iff {C : Clause V} : + Clause.isTautology C = true ↔ ∃ l ∈ C, Std.Sat.Literal.negate l ∈ C := by + simp [Clause.isTautology] + +theorem Clause.isTautology_eq_false_iff {C : Clause V} : + Clause.isTautology C = false ↔ ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + rw [← Bool.not_eq_true, Clause.isTautology_eq_true_iff] + push Not + rfl + +/-- Any tautological clause evaluates to `true` under every assignment. -/ +theorem eval_eq_true_of_isTautology {C : Clause V} (h : Clause.isTautology C = true) + (σ : V → Bool) : C.eval σ = true := by + obtain ⟨⟨v, b⟩, hl, hneg⟩ := Clause.isTautology_eq_true_iff.mp h + rw [Clause.eval_eq_true_iff] + by_cases hσ : σ v = b + · exact ⟨(v, b), hl, hσ⟩ + · refine ⟨Std.Sat.Literal.negate (v, b), hneg, ?_⟩ + cases b <;> cases hσ' : σ v <;> simp_all [Std.Sat.Literal.negate] + +/-- `removeTautologies φ` is a sublist of `φ`. -/ +theorem removeTautologies_sublist (φ : CNF V) : removeTautologies φ <+ φ := + List.filter_sublist + +/-- Removing tautological clauses preserves evaluation under every assignment. -/ +@[simp] +theorem eval_removeTautologies (φ : CNF V) (σ : V → Bool) : + (removeTautologies φ).eval σ = φ.eval σ := by + rw [Bool.eq_iff_iff, CNF.eval_eq_true_iff, CNF.eval_eq_true_iff] + constructor + · intro h C hC + rcases Bool.eq_false_or_eq_true (Clause.isTautology C) with htrue | hfalse + · exact eval_eq_true_of_isTautology htrue σ + · exact h C (List.mem_filter.mpr ⟨hC, by simp [hfalse]⟩) + · intro h C hC + exact h C (List.mem_filter.mp hC).1 + +/-- Removing tautological clauses preserves the declared-variable model count. -/ +@[simp] +theorem modelCountOn_removeTautologies (φ : CNF V) (declared : Finset V) : + (removeTautologies φ).modelCountOn declared = φ.modelCountOn declared := by + simp only [CNF.modelCountOn, eval_removeTautologies] + +/-- `removeTautologies φ` is nontautological. -/ +theorem nontautological_removeTautologies (φ : CNF V) : + Nontautological (removeTautologies φ) := by + intro C hC + have h := (List.mem_filter.mp hC).2 + rw [Bool.not_eq_true'] at h + exact Clause.isTautology_eq_false_iff.mp h + +/-- The clause set of `countingNormalize φ` has the same satisfying assignments as `φ`. -/ +theorem sat_toClauseSet_countingNormalize_iff (φ : CNF V) (σ : V → Bool) : + (countingNormalize φ).toClauseSet.Sat σ ↔ φ.eval σ = true := by + rw [countingNormalize, sat_toClauseSet_normalize_iff, eval_removeTautologies] + +/-- `countingNormalize φ` evaluates identically to `φ` under every assignment. -/ +@[simp] +theorem eval_countingNormalize (φ : CNF V) (σ : V → Bool) : + (countingNormalize φ).eval σ = φ.eval σ := by + rw [Bool.eq_iff_iff, ← CNF.sat_toClauseSet_iff, sat_toClauseSet_countingNormalize_iff] + +/-- `countingNormalize φ` preserves the declared-variable model count. -/ +@[simp] +theorem modelCountOn_countingNormalize (φ : CNF V) (declared : Finset V) : + (countingNormalize φ).modelCountOn declared = φ.modelCountOn declared := by + simp only [CNF.modelCountOn, eval_countingNormalize] + +/-- Every clause of `countingNormalize φ` has the literals of a nontautological clause of `φ`. -/ +theorem exists_of_mem_countingNormalize {φ : CNF V} {C : Clause V} + (hC : C ∈ countingNormalize φ) : + ∃ D ∈ removeTautologies φ, C.toFinset = D.toFinset := by + have hsub : countingNormalize φ <+ (removeTautologies φ).map dedup := + (reduce_sublist _).trans (dedupClauses_sublist _) + obtain ⟨D, hD, rfl⟩ := List.mem_map.mp (hsub.subset hC) + exact ⟨D, hD, toFinset_dedup D⟩ + +/-- The number of clauses of `countingNormalize φ` is at most that of `φ`. -/ +theorem length_countingNormalize_le (φ : CNF V) : + (countingNormalize φ).length ≤ φ.length := + ((reduce_sublist _).trans (dedupClauses_sublist _)).length_le.trans + ((List.length_map _).le.trans (removeTautologies_sublist φ).length_le) + +/-- Every variable occurring in `countingNormalize φ` occurs in `φ`. -/ +theorem mem_vars_of_mem_countingNormalize {φ : CNF V} : + ∀ C ∈ (countingNormalize φ).toClauseSet, ∀ l ∈ C, l.1 ∈ φ.vars.toFinset := by + intro C hC l hl + obtain ⟨C', hC', rfl⟩ := mem_toClauseSet.mp hC + obtain ⟨D, hD, hDeq⟩ := exists_of_mem_countingNormalize hC' + rw [hDeq, List.mem_toFinset] at hl + have hDφ := (removeTautologies_sublist φ).subset hD + exact List.mem_toFinset.mpr (List.mem_map.mpr ⟨l, List.mem_flatten.mpr ⟨D, hDφ, hl⟩, rfl⟩) + +/-- Every formula of width `k` is brought into nontautological normal form by +`countingNormalize`. -/ +theorem countingNormalized_countingNormalize {k : ℕ} {φ : CNF V} + (hφ : ∀ C ∈ φ, C.length ≤ k) : CountingNormalized k (countingNormalize φ) := by + refine ⟨normalized_normalize fun C hC => hφ C ((removeTautologies_sublist φ).subset hC), + fun C hC l hl hneg => ?_⟩ + obtain ⟨D, hD, hCD⟩ := exists_of_mem_countingNormalize hC + have hlD : l ∈ D := List.mem_toFinset.mp (hCD ▸ List.mem_toFinset.mpr hl) + have hnegD : Std.Sat.Literal.negate l ∈ D := + List.mem_toFinset.mp (hCD ▸ List.mem_toFinset.mpr hneg) + exact nontautological_removeTautologies φ D hD l hlD hnegD + +/-! ### Agreement with clause-set counting operations -/ + +/-- The clause set of `negatedHeartUnits H` is `ClauseSet.Sparsification.negatedHeartUnits`. -/ +theorem toClauseSet_negatedHeartUnits (H : Clause V) : + (negatedHeartUnits H).toClauseSet = + ClauseSet.Sparsification.negatedHeartUnits H.toFinset := by + ext C + simp [negatedHeartUnits, ClauseSet.Sparsification.negatedHeartUnits, CNF.toClauseSet] + +/-- The list-level counting children compute the clause-set counting children. -/ +theorem toClauseSet_countingChild {ψ : CNF V} (hnd : ∀ C ∈ ψ, C.Nodup) (c : ℕ) (H : Clause V) + (b : Bool) : + (countingChild ψ c H b).toClauseSet = + ClauseSet.Sparsification.countingChild ψ.toClauseSet c H.toFinset b := by + rw [countingChild, toClauseSet_reduce, toClauseSet_dedupClauses, toClauseSet_append] + cases b + · simp [countingIntro, ClauseSet.Sparsification.countingChild, + ClauseSet.Sparsification.child, ClauseSet.Sparsification.intro, CNF.toClauseSet] + · simp only [countingIntro, ite_true, toClauseSet_append, toClauseSet_petals hnd, + toClauseSet_negatedHeartUnits, ClauseSet.Sparsification.countingChild, Finset.union_assoc] + +/-- The clause set of `countingNext θ k ψ b` equals the clause-set counting child for the computed +choice. -/ +theorem toClauseSet_countingNext {θ : ℕ → ℕ} {k : ℕ} {ψ : CNF V} (h : Normalized k ψ) (b : Bool) : + (countingNext θ k ψ b).toClauseSet = ClauseSet.Sparsification.countingChild ψ.toClauseSet + (choice θ k ψ).1 (choice θ k ψ).2.toFinset b := + toClauseSet_countingChild h.nodup _ _ b + +/-- A formula and the canonical CNF of its clause set have the same declared-variable model +count. -/ +theorem modelCountOn_toCNF_toClauseSet (ψ : CNF V) (declared : Finset V) : + ψ.toClauseSet.toCNF.modelCountOn declared = ψ.modelCountOn declared := by + simp only [CNF.modelCountOn] + congr 1 + ext σ + simp only [Finset.mem_filter, Finset.mem_univ, true_and, ClauseSet.eval_toCNF_eq_true_iff, + CNF.sat_toClauseSet_iff] + +/-- **Additive model-count conservation for `countingChild`.** On any formula `ψ` without repeated +literals in its clauses, the declared-variable model count equals the sum of the counts of its two +counting children. -/ +theorem modelCountOn_countingChild {ψ : CNF V} (hnd : ∀ C ∈ ψ, C.Nodup) (c : ℕ) (H : Clause V) + (declared : Finset V) : + ψ.modelCountOn declared = + (countingChild ψ c H false).modelCountOn declared + + (countingChild ψ c H true).modelCountOn declared := by + rw [← modelCountOn_toCNF_toClauseSet ψ, + ← modelCountOn_toCNF_toClauseSet (countingChild ψ c H false), + ← modelCountOn_toCNF_toClauseSet (countingChild ψ c H true), + toClauseSet_countingChild hnd c H false, + toClauseSet_countingChild hnd c H true] + exact ClauseSet.Sparsification.modelCountOn_countingChild ψ.toClauseSet c H.toFinset declared + +/-- **Additive model-count conservation for `countingNext`.** On any `k`-normalized formula `ψ`, +one deterministic counting-sparsification step splits the declared-variable model count additively +across its `false` and `true` branches. -/ +theorem modelCountOn_countingNext {θ : ℕ → ℕ} {k : ℕ} {ψ : CNF V} (h : Normalized k ψ) + (declared : Finset V) : + ψ.modelCountOn declared = + (countingNext θ k ψ false).modelCountOn declared + + (countingNext θ k ψ true).modelCountOn declared := + modelCountOn_countingChild h.nodup _ _ declared + +/-! ### Preservation of normal form under the counting step -/ + +omit [DecidableEq V] in +private theorem literal_negate_ne_self (l : Literal V) : Std.Sat.Literal.negate l ≠ l := by + rcases l with ⟨v, b⟩ + cases b <;> simp [Std.Sat.Literal.negate] + +/-- The counting children preserve `CountingNormalized k` whenever `1 ≤ k` and the heart `H` is a +duplicate-free nontautological clause of width at most `k`. -/ +theorem countingNormalized_countingChild {k : ℕ} {ψ : CNF V} (hk : 1 ≤ k) + (h : CountingNormalized k ψ) {c : ℕ} {H : Clause V} (hH_nodup : H.Nodup) + (hH_len : H.length ≤ k) (hH_nontaut : ∀ l ∈ H, Std.Sat.Literal.negate l ∉ H) (b : Bool) : + CountingNormalized k (countingChild ψ c H b) := by + have hsub : countingChild ψ c H b <+ ψ ++ countingIntro ψ c H b := + (reduce_sublist _).trans (dedupClauses_sublist _) + have hintro : ∀ C ∈ countingIntro ψ c H b, + C.Nodup ∧ C.length ≤ k ∧ ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + intro C hC + cases b + · simp only [countingIntro, Bool.false_eq_true, ite_false, List.mem_singleton] at hC + subst hC + exact ⟨hH_nodup, hH_len, hH_nontaut⟩ + · simp only [countingIntro, ite_true, List.mem_append, petals, negatedHeartUnits, + List.mem_map] at hC + rcases hC with ⟨D, hD, rfl⟩ | ⟨l₀, _, rfl⟩ + · have hDψ : D ∈ ψ := List.filter_sublist.subset hD + refine ⟨(h.nodup D hDψ).filter _, ?_, fun l hl hneg => ?_⟩ + · exact (List.length_filter_le _ D).trans (h.length_le D hDψ) + · exact h.nontautological D hDψ l (List.mem_filter.mp hl).1 (List.mem_filter.mp hneg).1 + · refine ⟨List.nodup_singleton _, by simpa using hk, fun l hl hneg => ?_⟩ + rw [List.mem_singleton] at hl hneg + subst hl + exact literal_negate_ne_self _ hneg + refine ⟨⟨fun C hC => ?_, fun C hC => ?_, ?_, ?_⟩, fun C hC => ?_⟩ + · rcases List.mem_append.mp (hsub.subset hC) with hC | hC + · exact h.nodup C hC + · exact (hintro C hC).1 + · rcases List.mem_append.mp (hsub.subset hC) with hC | hC + · exact h.length_le C hC + · exact (hintro C hC).2.1 + · exact ((reduce_sublist _).map _).nodup (nodup_toFinset_dedupClauses _) + · rw [countingChild, toClauseSet_reduce] + exact reduced_reduce _ + · rcases List.mem_append.mp (hsub.subset hC) with hC | hC + · exact h.nontautological C hC + · exact (hintro C hC).2.2 + +private theorem choice_snd_sublist_or_nil (θ : ℕ → ℕ) (k : ℕ) (ψ : CNF V) : + (choice θ k ψ).2 = [] ∨ ∃ C ∈ ψ, (choice θ k ψ).2 <+ C := by + unfold choice + rcases hhead : ((List.range ((k + 1) * (k + 1) + 1)).flatMap fun r => + ((candidates k ψ).filter (heavy θ ψ)).filter fun x => rank k x == r).head? with _ | x + · exact Or.inl rfl + · refine Or.inr ?_ + have hx := List.mem_of_mem_head? hhead + obtain ⟨r, -, hxr⟩ := List.mem_flatMap.mp hx + have hxcand := (List.mem_filter.mp (List.mem_filter.mp hxr).1).1 + obtain ⟨C, hC, hxH⟩ := List.mem_flatMap.mp hxcand + obtain ⟨H, hH, rfl⟩ := List.mem_map.mp hxH + exact ⟨C, hC, mem_hearts hH⟩ + +/-- **One counting-sparsification step preserves `CountingNormalized k`.** -/ +theorem countingNormalized_countingNext (θ : ℕ → ℕ) {k : ℕ} (hk : 1 ≤ k) {ψ : CNF V} + (h : CountingNormalized k ψ) (b : Bool) : + CountingNormalized k (countingNext θ k ψ b) := by + rcases choice_snd_sublist_or_nil θ k ψ with hnil | ⟨C, hC, hsub⟩ + · refine countingNormalized_countingChild hk h ?_ ?_ ?_ b <;> simp [hnil] + · refine countingNormalized_countingChild hk h (hsub.nodup (h.nodup C hC)) + (hsub.length_le.trans (h.length_le C hC)) + (fun l hl hneg => h.nontautological C hC l (hsub.subset hl) (hsub.subset hneg)) b + +/-- Every literal occurring in `countingChild ψ c H b` occurs in `ψ` or has its variable in `H`. -/ +theorem support_countingChild {ψ : CNF V} {c : ℕ} {H : Clause V} {declared : Finset V} + (hψ : ∀ C ∈ ψ, ∀ l ∈ C, l.1 ∈ declared) (hH : ∀ l ∈ H, l.1 ∈ declared) (b : Bool) : + ∀ C ∈ countingChild ψ c H b, ∀ l ∈ C, l.1 ∈ declared := by + have hsub : countingChild ψ c H b <+ ψ ++ countingIntro ψ c H b := + (reduce_sublist _).trans (dedupClauses_sublist _) + intro C hC l hl + rcases List.mem_append.mp (hsub.subset hC) with hC | hC + · exact hψ C hC l hl + · cases b + · simp only [countingIntro, Bool.false_eq_true, ite_false, List.mem_singleton] at hC + subst hC + exact hH l hl + · simp only [countingIntro, ite_true, List.mem_append, petals, negatedHeartUnits, + List.mem_map] at hC + rcases hC with ⟨D, hD, rfl⟩ | ⟨l₀, hl₀, rfl⟩ + · exact hψ D (List.filter_sublist.subset hD) l (List.mem_filter.mp hl).1 + · rw [List.mem_singleton] at hl + subst hl + exact hH l₀ hl₀ + +/-- Every literal occurring in `countingNext θ k ψ b` has its variable in `declared` whenever `ψ` +does. -/ +theorem support_countingNext (θ : ℕ → ℕ) (k : ℕ) {ψ : CNF V} {declared : Finset V} + (hψ : ∀ C ∈ ψ, ∀ l ∈ C, l.1 ∈ declared) (b : Bool) : + ∀ C ∈ countingNext θ k ψ b, ∀ l ∈ C, l.1 ∈ declared := by + refine support_countingChild hψ (fun l hl => ?_) b + rcases choice_snd_sublist_or_nil θ k ψ with hnil | ⟨C, hC, hsub⟩ + · simp [hnil] at hl + · exact hψ C hC l (hsub.subset hl) + +end Cslib.Logic.PL.CNF.Sparsification diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPolyTime.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPolyTime.lean new file mode 100644 index 000000000..b4041e310 --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPolyTime.lean @@ -0,0 +1,100 @@ +/- +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.ClauseSet.Sparsification.CountingList +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + +/-! +# Counting sparsification -- the step is polynomial-time + +The list-level counting-sparsification step (`Cslib.Logic.PL.CNF.Sparsification.countingNext`), +tautology removal (`Cslib.Logic.PL.CNF.Sparsification.removeTautologies`), nontautological +normalization (`Cslib.Logic.PL.CNF.Sparsification.countingNormalize`), and comparison with the +counting thresholds `countingThreshold M j = M ^ j` +(`Cslib.Logic.PL.CNF.Sparsification.computableInPolyTime_countingThreshold_le`) are polynomial-time +on the framed encoding of formulas, for any encoding of the variables [OpenAI2026ETH, Appendix A.1]. + +## Main results + +* `Cslib.Logic.PL.CNF.Sparsification.computableInPolyTime_countingThreshold_le`: comparisons with + `countingThreshold M`. +* `Cslib.Logic.PL.CNF.Sparsification.computableInPolyTime_removeTautologies`, + `Cslib.Logic.PL.CNF.Sparsification.computableInPolyTime_countingNormalize`: tautology removal and + nontautological normalization. +* `Cslib.Logic.PL.CNF.Sparsification.computableInPolyTime_countingNext`: the list-level counting + step. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.1][OpenAI2026ETH] +-/ + +@[expose] public section + +open Turing.MultiTapeTM ComputableTidilyInPolyTime + +namespace Cslib.Logic.PL.CNF.Sparsification + +/-- Comparing the counting thresholds `countingThreshold M j = M ^ j` with a number is +polynomial-time in unary. -/ +theorem computableInPolyTime_countingThreshold_le (M : ℕ) : + ComputableInPolyTime (fun p : ℕ × ℕ => decide (countingThreshold M p.1 ≤ p.2)) + (encPair encUnary encUnary) encBool := by + unfold countingThreshold + fun_prop + +variable {V : Type} [DecidableEq V] (encV : V ↪ List Bool) + +/-- The framed encoding of clauses. -/ +local notation "encC" => encList (encPair encV encBool) + +/-- The framed encoding of formulas. -/ +local notation "encF" => encList (encList (encPair encV encBool)) + +/-- Tautology testing on a clause is polynomial-time. -/ +theorem computableInPolyTime_isTautology : + ComputableInPolyTime (Clause.isTautology (V := V)) encC encBool := by + refine .of_tidy ?_ + unfold Clause.isTautology Std.Sat.Literal.negate + fun_prop + +/-- Removing tautological clauses from a formula is polynomial-time. -/ +theorem computableInPolyTime_removeTautologies : + ComputableInPolyTime (removeTautologies (V := V)) encF encF := by + refine .of_tidy ?_ + have htaut := (computableInPolyTime_isTautology encV).tidy + unfold removeTautologies + fun_prop + +/-- Nontautological normalization (`countingNormalize`) is polynomial-time. -/ +theorem computableInPolyTime_countingNormalize : + ComputableInPolyTime (countingNormalize (V := V)) encF encF := by + have hrem := (computableInPolyTime_removeTautologies encV).tidy + have hnorm := (computableInPolyTime_normalize encV).tidy + exact .of_tidy (hnorm.comp hrem) + +/-- **The list-level counting-sparsification step is polynomial-time.** -/ +theorem computableInPolyTime_countingNext {θ : ℕ → ℕ} + (hθ : ComputableInPolyTime (fun p : ℕ × ℕ => decide (θ p.1 ≤ p.2)) + (encPair encUnary encUnary) encBool) (k : ℕ) : + ComputableInPolyTime (fun p : CNF V × Bool => countingNext θ k p.1 p.2) + (encPair encF encBool) encF := by + refine .of_tidy ?_ + have := (computableInPolyTime_choice encV hθ k).tidy + have := (computableInPolyTime_reduce encV).tidy + have := (computableInPolyTime_dedupClauses encV).tidy + have := (computableInPolyTime_flower encV).tidy + unfold countingNext countingChild countingIntro petals negatedHeartUnits Std.Sat.Literal.negate + fun_prop + +end Cslib.Logic.PL.CNF.Sparsification diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/List.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/List.lean index c48bccdcc..cd7b9e3e2 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/List.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/List.lean @@ -178,7 +178,7 @@ private theorem not_and_not_subclause_iff {D C : Clause V} : cases subclause D C <;> cases subclause C D <;> simp /-- The list-level reduction computes the reduction of clause sets. -/ -private theorem toClauseSet_reduce (ψ : CNF V) : +theorem toClauseSet_reduce (ψ : CNF V) : (reduce ψ).toClauseSet = ClauseSet.Sparsification.reduce ψ.toClauseSet := by have hkeep (C : Clause V) : (ψ.all fun D => !(subclause D C && !subclause C D)) = true ↔ ∀ D' ∈ ψ.toClauseSet, ¬ D' ⊂ C.toFinset := by @@ -199,7 +199,8 @@ private theorem toClauseSet_reduce (ψ : CNF V) : obtain ⟨C, hC, rfl⟩ := mem_toClauseSet.mp hC' exact ⟨C, List.mem_filter.mpr ⟨hC, (hkeep C).mpr hk⟩, rfl⟩ -private theorem toClauseSet_flower {ψ : CNF V} (hnd : ∀ C ∈ ψ, C.Nodup) (c : ℕ) (H : Clause V) : +/-- The list-level flower computes the clause-set flower when clauses have no repeated literals. -/ +theorem toClauseSet_flower {ψ : CNF V} (hnd : ∀ C ∈ ψ, C.Nodup) (c : ℕ) (H : Clause V) : (flower ψ c H).toClauseSet = ClauseSet.Sparsification.flower ψ.toClauseSet c H.toFinset := by ext C' rw [mem_toClauseSet, ClauseSet.Sparsification.mem_flower, mem_toClauseSet] @@ -221,7 +222,8 @@ theorem toFinset_filter_not_mem (C H : Clause V) : rw [Bool.not_eq_true', List.any_eq_false] exact ⟨fun h hl => h l hl (beq_self_eq_true l), fun h l' hl' heq => h (beq_iff_eq.mp heq ▸ hl')⟩ -private theorem toClauseSet_petals {ψ : CNF V} (hnd : ∀ C ∈ ψ, C.Nodup) (c : ℕ) (H : Clause V) : +/-- The list-level petals compute the clause-set petals when clauses have no repeated literals. -/ +theorem toClauseSet_petals {ψ : CNF V} (hnd : ∀ C ∈ ψ, C.Nodup) (c : ℕ) (H : Clause V) : (petals ψ c H).toClauseSet = ClauseSet.Sparsification.petals ψ.toClauseSet c H.toFinset := by ext C' rw [mem_toClauseSet, ClauseSet.Sparsification.mem_petals, ← toClauseSet_flower hnd] @@ -289,7 +291,8 @@ private theorem heavy_eq_true_iff {θ : ℕ → ℕ} {k : ℕ} {ψ : CNF V} (h : /-! ### Candidates -/ omit [DecidableEq V] in -private theorem mem_hearts {k : ℕ} {C H : Clause V} (hH : H ∈ hearts k C) : H <+ C := by +/-- Every candidate heart of `C` is a sublist of `C`. -/ +theorem mem_hearts {k : ℕ} {C H : Clause V} (hH : H ∈ hearts k C) : H <+ C := by obtain ⟨S, -, rfl⟩ := List.mem_map.mp hH conv_rhs => rw [← List.zipIdx_map_fst 0 C] exact List.filter_sublist.map _ @@ -606,7 +609,8 @@ private theorem pairwise_zipIdx (ψ : CNF V) : simp only [List.getElem_zipIdx, Nat.zero_add] exact hij -private theorem nodup_toFinset_dedupClauses (ψ : CNF V) : +/-- Deduplicating clauses produces clauses with pairwise distinct literal sets. -/ +theorem nodup_toFinset_dedupClauses (ψ : CNF V) : ((dedupClauses ψ).map List.toFinset).Nodup := by rw [dedupClauses, List.map_map, List.nodup_iff_pairwise_ne, List.pairwise_map] refine ((pairwise_zipIdx ψ).filter _).imp_of_mem fun {p q} _ hq hpq heq => ?_ @@ -618,7 +622,8 @@ private theorem nodup_toFinset_dedupClauses (ψ : CNF V) : have h₂ : subclause q.1 p.1 = true := subclause_eq_true_iff.mpr heq.ge simp [hpq, h₁, h₂] at this -private theorem toClauseSet_dedupClauses (ψ : CNF V) : +/-- Deduplicating clauses preserves the clause set. -/ +theorem toClauseSet_dedupClauses (ψ : CNF V) : (dedupClauses ψ).toClauseSet = ψ.toClauseSet := by refine Finset.Subset.antisymm (toClauseSet_mono (dedupClauses_sublist ψ).subset) ?_ intro C' hC' diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/PolyTime.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/PolyTime.lean index 74bdac967..9e1a3cd24 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/PolyTime.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/PolyTime.lean @@ -74,23 +74,36 @@ local notation "encC" => encList (encPair encV encBool) local notation "encF" => encList (encList (encPair encV encBool)) /-- The list-level reduction is polynomial-time. -/ -private theorem computableTidilyInPolyTime_reduce : - ComputableTidilyInPolyTime (reduce (V := V)) encF encF := by +theorem computableInPolyTime_reduce : + ComputableInPolyTime (reduce (V := V)) encF encF := by + refine .of_tidy ?_ unfold reduce subclause fun_prop -private theorem computableTidilyInPolyTime_flower : - ComputableTidilyInPolyTime (fun p : CNF V × (ℕ × Clause V) => flower p.1 p.2.1 p.2.2) +/-- Removing duplicate clauses with identical literal sets is polynomial-time. -/ +theorem computableInPolyTime_dedupClauses : + ComputableInPolyTime (dedupClauses (V := V)) encF encF := by + refine .of_tidy ?_ + unfold dedupClauses subclause + fun_prop + +/-- Computing the flower of a clause size and heart is polynomial-time. -/ +theorem computableInPolyTime_flower : + ComputableInPolyTime (fun p : CNF V × (ℕ × Clause V) => flower p.1 p.2.1 p.2.2) (encPair encF (encPair encUnary encC)) encF := by + refine .of_tidy ?_ unfold flower subclause fun_prop -private theorem computableTidilyInPolyTime_heavy {θ : ℕ → ℕ} - (hθ : ComputableTidilyInPolyTime (fun p : ℕ × ℕ => decide (θ p.1 ≤ p.2)) +/-- Testing whether a candidate `(c, H)` is heavy is polynomial-time. -/ +theorem computableInPolyTime_heavy {θ : ℕ → ℕ} + (hθ : ComputableInPolyTime (fun p : ℕ × ℕ => decide (θ p.1 ≤ p.2)) (encPair encUnary encUnary) encBool) : - ComputableTidilyInPolyTime (fun p : CNF V × (ℕ × Clause V) => heavy θ p.1 p.2) + ComputableInPolyTime (fun p : CNF V × (ℕ × Clause V) => heavy θ p.1 p.2) (encPair encF (encPair encUnary encC)) encBool := by - have := computableTidilyInPolyTime_flower encV + have hθ := hθ.tidy + refine .of_tidy ?_ + have := (computableInPolyTime_flower encV).tidy have h₁ : ComputableTidilyInPolyTime (fun p : CNF V × (ℕ × Clause V) => decide (1 ≤ p.2.2.length) && decide (p.2.2.length < p.2.1)) (encPair encF (encPair encUnary encC)) encBool := by @@ -102,8 +115,10 @@ private theorem computableTidilyInPolyTime_heavy {θ : ℕ → ℕ} exact bool_and.comp (h₁.prod (hθ.comp h₂)) omit [DecidableEq V] in -private theorem computableTidilyInPolyTime_candidates (k : ℕ) : - ComputableTidilyInPolyTime (candidates (V := V) k) encF (encList (encPair encUnary encC)) := by +/-- Listing the candidate pairs `(c, H)` of a formula is polynomial-time. -/ +theorem computableInPolyTime_candidates (k : ℕ) : + ComputableInPolyTime (candidates (V := V) k) encF (encList (encPair encUnary encC)) := by + refine .of_tidy ?_ have : ComputableTidilyInPolyTime (hearts (V := V) k) encC (encList encC) := by unfold hearts fun_prop @@ -111,14 +126,15 @@ private theorem computableTidilyInPolyTime_candidates (k : ℕ) : fun_prop /-- The computed choice is polynomial-time. -/ -private theorem computableTidilyInPolyTime_choice {θ : ℕ → ℕ} - (hθ : ComputableTidilyInPolyTime (fun p : ℕ × ℕ => decide (θ p.1 ≤ p.2)) +theorem computableInPolyTime_choice {θ : ℕ → ℕ} + (hθ : ComputableInPolyTime (fun p : ℕ × ℕ => decide (θ p.1 ≤ p.2)) (encPair encUnary encUnary) encBool) (k : ℕ) : - ComputableTidilyInPolyTime (choice (V := V) θ k) encF (encPair encUnary encC) := by + ComputableInPolyTime (choice (V := V) θ k) encF (encPair encUnary encC) := by + refine .of_tidy ?_ have hc : ComputableTidilyInPolyTime (fun ψ : CNF V => (candidates k ψ).filter (heavy θ ψ)) encF (encList (encPair encUnary encC)) := - filterWithInput (computableTidilyInPolyTime_candidates encV k) - (computableTidilyInPolyTime_heavy encV hθ) + filterWithInput (computableInPolyTime_candidates encV k).tidy + (computableInPolyTime_heavy encV hθ).tidy have hr : ComputableTidilyInPolyTime (fun x : ℕ × Clause V => rank k x) (encPair encUnary encC) encUnary := by unfold rank @@ -136,11 +152,10 @@ theorem computableInPolyTime_isSparse {θ : ℕ → ℕ} (hθ : ComputableInPolyTime (fun p : ℕ × ℕ => decide (θ p.1 ≤ p.2)) (encPair encUnary encUnary) encBool) (k : ℕ) : ComputableInPolyTime (isSparse (V := V) θ k) encF encBool := by - have hθ := hθ.tidy refine .of_tidy ?_ unfold isSparse - exact bool_not.comp (anyWithInput (computableTidilyInPolyTime_candidates encV k) - (computableTidilyInPolyTime_heavy encV hθ)) + exact bool_not.comp (anyWithInput (computableInPolyTime_candidates encV k).tidy + (computableInPolyTime_heavy encV hθ).tidy) /-- **The list-level step is polynomial-time.** -/ theorem computableInPolyTime_next {θ : ℕ → ℕ} @@ -148,11 +163,10 @@ theorem computableInPolyTime_next {θ : ℕ → ℕ} (encPair encUnary encUnary) encBool) (k : ℕ) : ComputableInPolyTime (fun p : CNF V × Bool => next θ k p.1 p.2) (encPair encF encBool) encF := by - have hθ := hθ.tidy refine .of_tidy ?_ - have := computableTidilyInPolyTime_choice encV hθ k - have := computableTidilyInPolyTime_reduce encV - have := computableTidilyInPolyTime_flower encV + have := (computableInPolyTime_choice encV hθ k).tidy + have := (computableInPolyTime_reduce encV).tidy + have := (computableInPolyTime_flower encV).tidy unfold next child intro petals fun_prop @@ -160,11 +174,12 @@ theorem computableInPolyTime_next {θ : ℕ → ℕ} theorem computableInPolyTime_normalize : ComputableInPolyTime (normalize (V := V)) encF encF := by refine .of_tidy ?_ - have := computableTidilyInPolyTime_reduce encV + have := (computableInPolyTime_reduce encV).tidy + have := (computableInPolyTime_dedupClauses encV).tidy have hdedup : ComputableTidilyInPolyTime (dedup (V := V)) encC encC := by unfold dedup fun_prop - unfold normalize dedupClauses subclause + unfold normalize fun_prop end Cslib.Logic.PL.CNF.Sparsification diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingSparsifyPolyTime.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingSparsifyPolyTime.lean new file mode 100644 index 000000000..9e8ca74af --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingSparsifyPolyTime.lean @@ -0,0 +1,116 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.CountingEndpoint +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime + +/-! +# Tests for dense CNF relabeling, list-level counting sparsification, and fine-grained endpoints + +Concrete regression tests and edge-case checks for: +* `Cslib.Logic.PL.CNF.denseRelabel` vs `CNF.compact` on gapped/repeated-literal formulas, empty + formulas, and width-`0`/`1` `KSAT` instances; +* `Cslib.Logic.PL.CNF.Sparsification.removeTautologies`, `countingNormalize`, `countingChild`, and + `countingNext`, including additive `modelCountOn` conservation and `CountingNormalized` + preservation; +* Polynomial-time certificates and fine-grained endpoint reductions in + `Complexity.Computability.Complexity.FineGrained.CountingEndpoint`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.Tests + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification Turing.MultiTapeTM + +/-! ### 1. Concrete dense-relabeling vs compaction checks -/ + +/-- A concrete formula whose variable occurrences `[10, 10, 20, 5]` have a duplicate before `20` and +non-monotone variable numbers. -/ +def gappedFormula : CNF ℕ := + [[(10, true), (10, true)], [(20, false), (5, true)]] + +/-- `CNF.compact` uses first occurrence positions in `vars`, leaving a gap at `1` (`[0, 2, 3]`). -/ +example : gappedFormula.compact = [[(0, true), (0, true)], [(2, false), (3, true)]] := by + decide + +/-- `CNF.denseRelabel` uses indices in `distinctVars`, producing dense indices `[0, 1, 2]`. -/ +example : gappedFormula.denseRelabel = [[(0, true), (0, true)], [(1, false), (2, true)]] := by + decide + +example : gappedFormula.denseRelabel.distinctVars = List.range gappedFormula.numVars := + CNF.distinctVars_denseRelabel gappedFormula + +example : gappedFormula.denseRelabel.modelCountOn (Finset.range gappedFormula.numVars) = + gappedFormula.modelCount := + CNF.modelCountOn_denseRelabel gappedFormula + +/-- Edge cases: empty formula and empty-clause formula at `k = 0` and `k = 1`. -/ +example : CNF.denseRelabel ([] : CNF ℕ) = [] := rfl +example : CNF.denseRelabel ([[]] : CNF ℕ) = [[]] := rfl +example : CNF.denseRelabel ([] : CNF ℕ) ∈ KSAT 0 ↔ ([] : CNF ℕ) ∈ KSAT 0 := + CNF.denseRelabel_mem_KSAT_iff +example : CNF.denseRelabel ([[]] : CNF ℕ) ∈ KSAT 1 ↔ ([[]] : CNF ℕ) ∈ KSAT 1 := + CNF.denseRelabel_mem_KSAT_iff + +/-! ### 2. Concrete list-level counting sparsification checks -/ + +/-- A 2-CNF formula with a tautological clause `[(0, true), (0, false)]` and a size-2 flower of +2-clauses sharing the heart `[(0, true)]`. -/ +def flowerWithTautology : CNF ℕ := + [[(0, true), (0, false)], [(0, true), (1, true)], [(0, true), (2, false)]] + +/-- `removeTautologies` drops the tautological clause and keeps the two nontautological clauses. -/ +example : removeTautologies flowerWithTautology = + [[(0, true), (1, true)], [(0, true), (2, false)]] := by + decide + +/-- `countingNormalize` produces a `CountingNormalized 2` formula with the same 3-variable model +count. -/ +example : CountingNormalized 2 (countingNormalize flowerWithTautology) := + countingNormalized_countingNormalize (by decide) + +example : (countingNormalize flowerWithTautology).modelCountOn (Finset.range 3) = + flowerWithTautology.modelCountOn (Finset.range 3) := + modelCountOn_countingNormalize flowerWithTautology (Finset.range 3) + +/-- At threshold `M = 2` (`θ 1 = 2`), `choice (countingThreshold 2) 2` selects the heavy heart +`[(0, true)]` at clause size `2`. -/ +example : choice (countingThreshold 2) 2 (countingNormalize flowerWithTautology) = + (2, [(0, true)]) := by + decide + +/-- The `false` (A) branch reduces to the unit heart `[[(0, true)]]`, while the `true` (B) branch +introduces the petals `[[(1, true)], [(2, false)]]` and the negated-heart unit `[[(0, false)]]`. -/ +example : countingNext (countingThreshold 2) 2 (countingNormalize flowerWithTautology) false = + [[(0, true)]] := by + decide + +example : countingNext (countingThreshold 2) 2 (countingNormalize flowerWithTautology) true = + [[(1, true)], [(2, false)], [(0, false)]] := by + decide + +/-- Both counting children preserve `CountingNormalized 2` and partition the 5 satisfying +assignments on `{0, 1, 2}` into `4 + 1 = 5`. -/ +example : CountingNormalized 2 + (countingNext (countingThreshold 2) 2 (countingNormalize flowerWithTautology) true) := + countingNormalized_countingNext (countingThreshold 2) (by decide) + (countingNormalized_countingNormalize (by decide)) true + +example : (countingNormalize flowerWithTautology).modelCountOn (Finset.range 3) = + CNF.modelCountOn + (countingNext (countingThreshold 2) 2 (countingNormalize flowerWithTautology) false) + (Finset.range 3) + + CNF.modelCountOn + (countingNext (countingThreshold 2) 2 (countingNormalize flowerWithTautology) true) + (Finset.range 3) := + modelCountOn_countingNext + (countingNormalized_countingNormalize (k := 2) (by decide)).toNormalized (Finset.range 3) + +end Cslib.Complexity.Tests From 48adefe6167b373fe2038299327057c75e0c4e70 Mon Sep 17 00:00:00 2001 From: Anton Kovsharov Date: Tue, 27 Oct 2026 01:54:02 +0000 Subject: [PATCH 05/24] refactor(Foundations,ClauseSet): move List.countP uniqueness lemmas to List/Counting --- .../Foundations/Data/List/Counting.lean | 61 ++++++++++++++++++- .../Sparsification/CountingTree.lean | 58 +----------------- .../SparsificationCountingTree.lean | 23 +++++++ 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/Complexity/Foundations/Data/List/Counting.lean b/Complexity/Foundations/Data/List/Counting.lean index 538186db1..26257d1b8 100644 --- a/Complexity/Foundations/Data/List/Counting.lean +++ b/Complexity/Foundations/Data/List/Counting.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 Christian Reitwiessner, Samuel Schlesinger. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Christian Reitwiessner, Samuel Schlesinger +Authors: Christian Reitwiessner, Samuel Schlesinger, Anton Kovsharov -/ module @@ -19,6 +19,8 @@ public import Mathlib.Data.List.Basic * `List.decide_mem_eq_any`: membership as a scan of the list. * `List.countP_and_not_add_countP_and`: members satisfying one predicate split by whether they satisfy another. +* `List.existsUnique_fin_of_countP_eq_one`, `List.existsUnique_mem_of_countP_eq_one`: a Boolean + predicate with `countP` equal to `1` holds at a unique list index and at a unique member. -/ @[expose] public section @@ -53,3 +55,60 @@ theorem List.countP_and_not_add_countP_and {α : Type*} (p q : α → Prop) [Dec by_cases hp : p a <;> by_cases hq : q a <;> simp only [hp, hq, not_true_eq_false, not_false_eq_true, and_true, and_false, decide_true, decide_false, Bool.false_eq_true, ↓reduceIte] <;> omega + +/-- If `l.countP p = 1`, there is a unique index `i : Fin l.length` with `p l[i] = true`. -/ +theorem List.existsUnique_fin_of_countP_eq_one {α : Type*} {l : List α} {p : α → Bool} + (h : l.countP p = 1) : ∃! i : Fin l.length, p l[i] = true := by + induction l with + | nil => + rw [List.countP_nil] at h + omega + | cons a tl ih => + cases hpa : p a with + | true => + rw [List.countP_cons_of_pos hpa] at h + have htl : tl.countP p = 0 := by omega + refine Exists.intro (⟨0, Nat.succ_pos _⟩ : Fin (a :: tl).length) + (And.intro ?_ fun i hi => ?_) + · simpa only [Fin.getElem_fin, List.getElem_cons_zero] using hpa + · rcases i with ⟨_ | k, hk⟩ + · rfl + · simp only [Fin.getElem_fin, List.getElem_cons_succ] at hi + have hk' : k < tl.length := by simp only [List.length_cons] at hk; omega + exact absurd hi ((List.countP_eq_zero.mp htl) tl[k] (List.getElem_mem hk')) + | false => + rw [List.countP_cons_of_neg (by rw [hpa]; decide)] at h + obtain ⟨j, hj, hjuniq⟩ := ih h + refine Exists.intro j.succ (And.intro ?_ fun i hi => ?_) + · simpa only [Fin.getElem_fin, Fin.val_succ, List.getElem_cons_succ] using hj + · rcases i with ⟨_ | k, hk⟩ + · simp only [Fin.getElem_fin, List.getElem_cons_zero] at hi + exact absurd hi (by rw [hpa]; decide) + · simp only [Fin.getElem_fin, List.getElem_cons_succ] at hi + have hk' : k < tl.length := by simp only [List.length_cons] at hk; omega + have heq := Fin.ext_iff.mp (hjuniq ⟨k, hk'⟩ (by simpa only [Fin.getElem_fin] using hi)) + exact Fin.ext (by simp only [Fin.val_succ]; exact congrArg (· + 1) heq) + +/-- If `l.countP p = 1`, there is a unique element `a` satisfying `a ∈ l ∧ p a = true`. -/ +theorem List.existsUnique_mem_of_countP_eq_one {α : Type*} {l : List α} {p : α → Bool} + (h : l.countP p = 1) : ∃! a, a ∈ l ∧ p a = true := by + induction l with + | nil => + rw [List.countP_nil] at h + omega + | cons a tl ih => + cases hpa : p a with + | true => + rw [List.countP_cons_of_pos hpa] at h + have htl : tl.countP p = 0 := by omega + refine Exists.intro a (And.intro ⟨List.mem_cons_self, hpa⟩ fun b ⟨hb, hpb⟩ => ?_) + rcases List.mem_cons.mp hb with rfl | hbtl + · rfl + · exact absurd hpb ((List.countP_eq_zero.mp htl) b hbtl) + | false => + rw [List.countP_cons_of_neg (by rw [hpa]; decide)] at h + obtain ⟨b, ⟨hbtl, hpb⟩, hbuniq⟩ := ih h + refine Exists.intro b (And.intro ⟨List.mem_cons_of_mem a hbtl, hpb⟩ fun c ⟨hc, hpc⟩ => ?_) + rcases List.mem_cons.mp hc with rfl | hctl + · exact absurd hpc (by rw [hpa]; decide) + · exact hbuniq c ⟨hctl, hpc⟩ diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean index 042654afd..9938a5c53 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingTree.lean @@ -11,6 +11,7 @@ public import Complexity.Init public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Tree public import Mathlib.Data.Finset.Powerset +import Complexity.Foundations.Data.List.Counting import Mathlib.Algebra.BigOperators.Group.Finset.Basic import Mathlib.Algebra.Group.Action.Defs @@ -564,59 +565,6 @@ theorem countP_sat_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseSet V) (x : V · rw [List.countP_cons_of_neg (p := p) (by simpa only [p, decide_eq_true_eq] using hsat), List.countP_nil, ite_eq_right hsat] -private theorem existsUnique_fin_of_countP_eq_one {α : Type*} : - ∀ {l : List α} {p : α → Bool}, l.countP p = 1 → ∃! i : Fin l.length, p l[i] = true - | [], _, h => by - rw [List.countP_nil] at h - omega - | a :: tl, p, h => by - cases hpa : p a with - | true => - rw [List.countP_cons_of_pos hpa] at h - have htl : tl.countP p = 0 := by omega - refine Exists.intro (⟨0, Nat.succ_pos _⟩ : Fin (a :: tl).length) - (And.intro ?_ fun i hi => ?_) - · simpa only [Fin.getElem_fin, List.getElem_cons_zero] using hpa - · rcases i with ⟨_ | k, hk⟩ - · rfl - · simp only [Fin.getElem_fin, List.getElem_cons_succ] at hi - have hk' : k < tl.length := by simp only [List.length_cons] at hk; omega - exact absurd hi ((List.countP_eq_zero.mp htl) tl[k] (List.getElem_mem hk')) - | false => - rw [List.countP_cons_of_neg (by rw [hpa]; decide)] at h - obtain ⟨j, hj, hjuniq⟩ := existsUnique_fin_of_countP_eq_one h - refine Exists.intro j.succ (And.intro ?_ fun i hi => ?_) - · simpa only [Fin.getElem_fin, Fin.val_succ, List.getElem_cons_succ] using hj - · rcases i with ⟨_ | k, hk⟩ - · simp only [Fin.getElem_fin, List.getElem_cons_zero] at hi - exact absurd hi (by rw [hpa]; decide) - · simp only [Fin.getElem_fin, List.getElem_cons_succ] at hi - have hk' : k < tl.length := by simp only [List.length_cons] at hk; omega - have heq := Fin.ext_iff.mp (hjuniq ⟨k, hk'⟩ (by simpa only [Fin.getElem_fin] using hi)) - exact Fin.ext (by simp only [Fin.val_succ]; exact congrArg (· + 1) heq) - -private theorem existsUnique_mem_of_countP_eq_one {α : Type*} : - ∀ {l : List α} {p : α → Bool}, l.countP p = 1 → ∃! a, a ∈ l ∧ p a = true - | [], _, h => by - rw [List.countP_nil] at h - omega - | a :: tl, p, h => by - cases hpa : p a with - | true => - rw [List.countP_cons_of_pos hpa] at h - have htl : tl.countP p = 0 := by omega - refine Exists.intro a (And.intro ⟨List.mem_cons_self, hpa⟩ fun b ⟨hb, hpb⟩ => ?_) - rcases List.mem_cons.mp hb with rfl | hbtl - · rfl - · exact absurd hpb ((List.countP_eq_zero.mp htl) b hbtl) - | false => - rw [List.countP_cons_of_neg (by rw [hpa]; decide)] at h - obtain ⟨b, ⟨hbtl, hpb⟩, hbuniq⟩ := existsUnique_mem_of_countP_eq_one h - refine Exists.intro b (And.intro ⟨List.mem_cons_of_mem a hbtl, hpb⟩ fun c ⟨hc, hpc⟩ => ?_) - rcases List.mem_cons.mp hc with rfl | hctl - · exact absurd hpc (by rw [hpa]; decide) - · exact hbuniq c ⟨hctl, hpc⟩ - /-- **Exact assignment partition (indexed leaf occurrence form).** An assignment `x` satisfies `ψ` if and only if there is a unique index `i` into `countingLeaves θ ψ` whose leaf is satisfied by `x`. -/ @@ -626,7 +574,7 @@ theorem sat_iff_existsUnique_fin_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseS constructor · intro hsat rw [ite_eq_left hsat] at hc - obtain ⟨i, hi, huniq⟩ := existsUnique_fin_of_countP_eq_one hc + obtain ⟨i, hi, huniq⟩ := List.existsUnique_fin_of_countP_eq_one hc exact Exists.intro i (And.intro (of_decide_eq_true hi) fun j hj => huniq j (decide_eq_true hj)) · rintro ⟨i, hi, -⟩ by_contra hnsat @@ -642,7 +590,7 @@ theorem sat_iff_existsUnique_mem_countingLeaves (θ : ℕ → ℕ) (ψ : ClauseS constructor · intro hsat rw [ite_eq_left hsat] at hc - obtain ⟨L, ⟨hLm, hLs⟩, huniq⟩ := existsUnique_mem_of_countP_eq_one hc + obtain ⟨L, ⟨hLm, hLs⟩, huniq⟩ := List.existsUnique_mem_of_countP_eq_one hc exact Exists.intro L (And.intro ⟨hLm, of_decide_eq_true hLs⟩ fun L' ⟨hL'm, hL's⟩ => huniq L' ⟨hL'm, decide_eq_true hL's⟩) · rintro ⟨L, ⟨hLm, hLs⟩, -⟩ diff --git a/ComplexityTests/SparsificationCountingTree.lean b/ComplexityTests/SparsificationCountingTree.lean index 20af9edff..f6738e7b7 100644 --- a/ComplexityTests/SparsificationCountingTree.lean +++ b/ComplexityTests/SparsificationCountingTree.lean @@ -6,6 +6,7 @@ Authors: Anton Kovsharov module +public import Complexity.Foundations.Data.List.Counting public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree public section @@ -260,4 +261,26 @@ example : ((countingLeaves (2 ^ ·) (reduce multiCNF.toClauseSet)).map unfold CNF.modelCountOn decide +/-! ### 5. Unique-index and unique-member extraction from `List.countP = 1` -/ + +-- Even when non-matching elements repeat (`3` appears twice), `countP = 1` yields both a unique +-- index (`⟨1, _⟩`) and a unique list member (`4`). +example : ∃! i : Fin [3, 4, 3, 5].length, ([3, 4, 3, 5][i] == 4) = true := + List.existsUnique_fin_of_countP_eq_one (l := [3, 4, 3, 5]) (p := (· == 4)) (by decide) + +example : ∃! a, a ∈ [3, 4, 3, 5] ∧ (a == 4) = true := + List.existsUnique_mem_of_countP_eq_one (l := [3, 4, 3, 5]) (p := (· == 4)) (by decide) + +-- Singleton list at index `0`. +example : ∃! i : Fin [true].length, [true][i] = true := + List.existsUnique_fin_of_countP_eq_one (l := [true]) (p := id) (by decide) + +-- End-to-end unique-leaf wrappers on `multiRoot` and `witnessAssign`. +example : ∃! i : Fin (countingLeaves (2 ^ ·) multiRoot).length, + ((countingLeaves (2 ^ ·) multiRoot)[i]).Sat witnessAssign := + (sat_iff_existsUnique_fin_countingLeaves (2 ^ ·) multiRoot witnessAssign).mp (by decide) + +example : ∃! L, L ∈ countingLeaves (2 ^ ·) multiRoot ∧ L.Sat witnessAssign := + (sat_iff_existsUnique_mem_countingLeaves (2 ^ ·) multiRoot witnessAssign).mp (by decide) + end ComplexityTests.SparsificationCountingTree From b49a87856a3d631afc10b730c66869b02c0a5b7c Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 22:10:42 -0400 Subject: [PATCH 06/24] feat: charge deleted marked clauses to counting insertions --- .../Sparsification/CountingDeletion.lean | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean new file mode 100644 index 000000000..8fbe7ed2b --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean @@ -0,0 +1,138 @@ +/- +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.ClauseSet.Sparsification.CountingOccurrences +public import Mathlib.Order.Interval.Finset.Nat +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.Order.BigOperators.Group.Finset +import Mathlib.Algebra.Group.Action.Defs + +/-! +# Charging deleted marked clauses to actual counting insertions + +The deletion-charge argument of Appendix A.3, Lemma A.3 and (A.3) [OpenAI2026ETH] assigns +a removed marked width-`j` clause to a new strict subclause inserted in that step. +The present-clause bound supplies at most `2 * θ (j - i)` recipients for a width-`i` insertion. + +Here the donor is proved absent from both the current node and the root. Every root clause has a +current subclause, directly from the actual reduction steps; a root donor would contradict current +reducedness. This is not the decision-path `Refines` property. The actual counting payload includes +the B-child's negated-heart units. + +This bounds deletion of clauses present before one step. It does not assert immediate survival of +new higher-width payload clauses, nor the complete insertion recurrence or a depth/leaf/runtime +bound. Nontautology is not needed for this one-step old-clause charge; its role in immediate +survival and the later insertion-event balance remains separate. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.exists_mem_counting_node_subset`: root witnesses persist. +* `Cslib.Logic.PL.ClauseSet.Sparsification.card_counting_deleted_marked_le`: deletion charge. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-- Every root clause contains a clause of each later node of an actual counting path. +This direction of containment holds for reduction after additions, including negated units. -/ +theorem exists_mem_counting_node_subset + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + {t : ℕ} (ht : t ≤ N) {C : Finset (Literal V)} (hC : C ∈ ψ 0) : + ∃ D ∈ ψ t, D ⊆ C := by + suffices ∀ t, t ≤ N → ∃ D ∈ ψ t, D ⊆ C from this t ht + intro t + induction t with + | zero => exact fun _ => ⟨C, hC, subset_rfl⟩ + | succ t ih => + intro ht + obtain ⟨D, hD, hDC⟩ := ih (by omega) + rw [hstep t (by omega), countingChild_eq_reduce_union] + obtain ⟨E, hE, hED⟩ := exists_mem_reduce_subset + (ψ := ψ t ∪ countingIntro (ψ t) (c t) (H t) (b t)) (mem_union_left _ hD) + exact ⟨E, hE, hED.trans hDC⟩ + +/-- The width-`j` marked clauses deleted at one actual counting step can be charged to that step's +new marked insertion events of smaller width, with the source's `2 * θ (j - i)` charge bound. + +The occurrence bound is derived from the actual path and both `Choice` priorities, not supplied +as a hypothesis. The insertion batch excludes clauses in the current node OR the root. No finite +ambient variable type, model count or native resource bound is assumed. -/ +theorem card_counting_deleted_marked_le {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + {m : ℕ} (hm : m < N) (j : ℕ) : + ((ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ C ∉ ψ (m + 1)).card ≤ + ∑ i ∈ Ico 1 j, 2 * θ (j - i) * + (((countingIntro (ψ m) (c m) (H m) (b m)) \ (ψ m ∪ ψ 0)).filter + fun D => D.card = i).card := by + let I := countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0) + set E := (ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ C ∉ ψ (m + 1) + change E.card ≤ ∑ i ∈ Ico 1 j, 2 * θ (j - i) * (I.filter fun D => D.card = i).card + have hredm : Reduced (ψ m) := by + cases m with + | zero => exact hred + | succ m => + rw [hstep m (by omega), countingChild_eq_reduce_union] + exact reduced_reduce _ + have hcover : E ⊆ (I.filter (·.card < j)).biUnion fun D => + (ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ D ⊆ C := by + intro C hC + obtain ⟨hCψ, hCroot, hCj, hCgone⟩ := mem_filter.mp hC + rw [hstep m hm, countingChild_eq_reduce_union] at hCgone + obtain ⟨D, hD, hDC⟩ := exists_ssubset_of_notMem_reduce hredm hCψ hCgone + have hDψ : D ∉ ψ m := fun h => hredm C hCψ D h hDC + have hDroot : D ∉ ψ 0 := by + intro hD0 + obtain ⟨F, hF, hFD⟩ := exists_mem_counting_node_subset N ψ c H b hstep hm.le hD0 + exact hredm C hCψ F hF (Finset.ssubset_of_subset_of_ssubset hFD hDC) + have hDI : D ∈ I := by + refine mem_sdiff.mpr ⟨hD, ?_⟩ + exact fun h => (mem_union.mp h).elim hDψ hDroot + refine mem_biUnion.mpr ⟨D, mem_filter.mpr ⟨hDI, ?_⟩, ?_⟩ + · exact hCj ▸ card_lt_card hDC + · exact mem_filter.mpr ⟨hCψ, hCroot, hCj, hDC.subset⟩ + have hmaps : ∀ D ∈ I.filter (·.card < j), D.card ∈ Ico 1 j := by + intro D hD + obtain ⟨hDI, hDj⟩ := mem_filter.mp hD + have hDpos := countingIntro_nonempty (hchoice m hm).1.1 (hchoice m hm).1.2.1 + (mem_sdiff.mp hDI).1 + rw [← card_pos] at hDpos + exact mem_Ico.mpr ⟨by omega, hDj⟩ + have hsum : (∑ D ∈ I.filter (·.card < j), + ((ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ D ⊆ C).card) ≤ + ∑ i ∈ Ico 1 j, 2 * θ (j - i) * (I.filter fun D => D.card = i).card := by + calc + _ ≤ ∑ D ∈ I.filter (·.card < j), 2 * θ (j - D.card) := by + refine sum_le_sum fun D hD => ?_ + exact (card_counting_newContaining_lt hθ N ψ c H b hstep hchoice hm.le + (mem_Ico.mp (hmaps D hD)).1 (mem_filter.mp hD).2).le + _ = ∑ i ∈ Ico 1 j, ∑ D ∈ (I.filter (·.card < j)).filter (·.card = i), + 2 * θ (j - i) := (sum_fiberwise_of_maps_to' hmaps fun i => 2 * θ (j - i)).symm + _ = ∑ i ∈ Ico 1 j, 2 * θ (j - i) * (I.filter fun D => D.card = i).card := by + refine sum_congr rfl fun i hi => ?_ + have hij := (mem_Ico.mp hi).2 + rw [sum_const, smul_eq_mul, mul_comm] + congr 2 + ext D + simp only [mem_filter] + constructor + · rintro ⟨⟨hDI, -⟩, hDi⟩ + exact ⟨hDI, hDi⟩ + · rintro ⟨hDI, hDi⟩ + exact ⟨⟨hDI, hDi ▸ hij⟩, hDi⟩ + exact (card_le_card hcover).trans (card_biUnion_le.trans hsum) + +end Cslib.Logic.PL.ClauseSet.Sparsification From 9889cf3dc5735b5ac0425c6f9d656e13c89e2bae Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 22:14:53 -0400 Subject: [PATCH 07/24] test: cover deletion charges and immediate-survival boundary --- Complexity.lean | 1 + .../SparsificationCountingDeletion.lean | 169 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 ComplexityTests/SparsificationCountingDeletion.lean diff --git a/Complexity.lean b/Complexity.lean index 43bf364b1..4dff02f59 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2169,6 +2169,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Basic public import Complexity.Logics.Propositional.ClauseSet.Sparsification public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List diff --git a/ComplexityTests/SparsificationCountingDeletion.lean b/ComplexityTests/SparsificationCountingDeletion.lean new file mode 100644 index 000000000..fe0a1f2d2 --- /dev/null +++ b/ComplexityTests/SparsificationCountingDeletion.lean @@ -0,0 +1,169 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion + +public section + +open Cslib.Logic.PL +open ClauseSet.Sparsification Finset + +namespace ComplexityTests.SparsificationCountingDeletion + +-- A negated-heart unit deletes a clause outside the selected flower. +theorem negated_unit_deletes_unselected_pair : {(0, false), (1, true)} ∉ countingChild + ({{(0, false), (1, true)}, {(0, true), (2, true)}, {(0, true), (3, true)}} : + ClauseSet (Fin 4)) 2 {(0, true)} true := by decide + +private def θ (j : ℕ) := 2 ^ j + +private theorem θpos (j : ℕ) : 1 ≤ θ j := by + induction j with + | zero => decide + | succ j ih => + change 1 ≤ 2 ^ (j + 1) + rw [pow_succ] + change 1 ≤ 2 ^ j at ih + omega + +private theorem θtwo {j : ℕ} (hj : 0 < j) : 2 ≤ θ j := by + cases j with + | zero => omega + | succ j => + change 2 ≤ 2 ^ (j + 1) + rw [pow_succ] + have := θpos j + change 1 ≤ 2 ^ j at this + omega + +-- A heavy flower contains two different clauses. Their common width establishes the minimum +-- priority, and the intersection cap establishes the maximum-heart priority. +private theorem choice_of_pairs {V : Type*} [DecidableEq V] + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (hh : Heavy θ ψ c H) + (hw : ∀ C ∈ ψ, ∀ D ∈ ψ, C ≠ D → C.card = D.card → C.card = c) + (hinter : ∀ C ∈ ψ, ∀ D ∈ ψ, C ≠ D → (C ∩ D).card ≤ H.card) : Choice θ ψ c H := by + refine ⟨hh, ?_, ?_⟩ + · intro c' H' hh' + have htwo : 2 ≤ (flower ψ c' H').card := (θtwo (Nat.sub_pos_of_lt hh'.2.1)).trans hh'.2.2 + obtain ⟨C, hC, D, hD, hCD⟩ := one_lt_card.mp (by omega : 1 < (flower ψ c' H').card) + have hCc := (mem_flower.mp hC).2.1 + have hDc := (mem_flower.mp hD).2.1 + have := hw C (flower_subset _ _ _ hC) D (flower_subset _ _ _ hD) hCD (by omega) + omega + · intro H' hh' + have htwo : 2 ≤ (flower ψ c H').card := (θtwo (Nat.sub_pos_of_lt hh'.2.1)).trans hh'.2.2 + obtain ⟨C, hC, D, hD, hCD⟩ := one_lt_card.mp (by omega : 1 < (flower ψ c H').card) + exact (card_le_card (subset_inter (mem_flower.mp hC).2.2 (mem_flower.mp hD).2.2)).trans + (hinter C (flower_subset _ _ _ hC) D (flower_subset _ _ _ hD) hCD) + +private def root : ClauseSet (Fin 9) := + {{(1, true), (2, true)}, {(0, true), (1, false), (3, true)}, + {(0, true), (1, true), (4, true)}, {(0, true), (5, true), (6, true)}, + {(0, true), (7, true), (8, true)}} + +private def first := countingChild root 3 {(0, true)} true +private def nodes (tail : Bool) (i : ℕ) := + if i = 0 then root else if i = 1 then first else countingChild first 2 {(1, true)} tail +private def sizes (i : ℕ) := if i = 0 then 3 else 2 +private def hearts (i : ℕ) : Finset (Literal (Fin 9)) := + if i = 0 then {(0, true)} else {(1, true)} +private def dirs (tail : Bool) (i : ℕ) := if i = 0 then true else tail + +private theorem firstChoice : Choice θ root 3 {(0, true)} := by + apply choice_of_pairs + · unfold Heavy; decide + · decide + · decide + +private theorem secondChoice : Choice θ first 2 {(1, true)} := by + refine ⟨?_, ?_, ?_⟩ + · unfold Heavy; decide + · intro c H hh + have := hh.1 + have := hh.2.1 + omega + · intro H hh + have := hh.2.1 + change H.card ≤ 1 + omega + +private theorem steps (tail : Bool) : ∀ i < 2, + nodes tail (i + 1) = countingChild (nodes tail i) (sizes i) (hearts i) (dirs tail i) := by + intro i hi + have : i = 0 ∨ i = 1 := by omega + rcases this with rfl | rfl <;> rfl + +private theorem choices (tail : Bool) : ∀ i < 2, + Choice θ (nodes tail i) (sizes i) (hearts i) := by + intro i hi + have : i = 0 ∨ i = 1 := by omega + rcases this with rfl | rfl + · exact firstChoice + · exact secondChoice + +private theorem rootReduced : Reduced root := by unfold Reduced; decide + +-- Three width-two clauses disappear, but the initial root pair must not be counted as marked. +example : (first.filter fun C => C.card = 2 ∧ C ∉ nodes true 2).card = 3 := by decide +example : (first.filter fun C => C ∉ root ∧ C.card = 2 ∧ C ∉ nodes true 2).card = 2 := by decide + +-- The unselected marked pair has NO petal donor. The added negated-heart unit is indispensable. +example : {(1, false), (3, true)} ∉ flower first 2 {(1, true)} := by decide +example : ¬ ∃ D ∈ petals first 2 {(1, true)}, D ⊆ ({(1, false), (3, true)} : + Finset (Literal (Fin 9))) := by decide +example : {(1, false)} ∈ countingIntro first 2 {(1, true)} true \ (first ∪ root) ∧ + ({(1, false)} : Finset (Literal (Fin 9))) ⊂ {(1, false), (3, true)} := by decide + +example : ((countingIntro first 2 {(1, true)} true \ (first ∪ root)).filter + fun D => D.card = 1).card = 3 := by decide + +example : (first.filter fun C => C ∉ root ∧ C.card = 2 ∧ C ∉ nodes true 2).card ≤ 12 := by + exact card_counting_deleted_marked_le θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (choices true) rootReduced (m := 1) (by omega) 2 + +example : (first.filter fun C => C ∉ root ∧ C.card = 2 ∧ C ∉ nodes false 2).card = 1 := by decide + +example : (first.filter fun C => C ∉ root ∧ C.card = 2 ∧ C ∉ nodes false 2).card ≤ 4 := by + exact card_counting_deleted_marked_le θpos 2 (nodes false) sizes hearts (dirs false) + (steps false) (choices false) rootReduced (m := 1) (by omega) 2 + +-- Unit deletions have zero charge: Ico 1 1 is empty. +example : (first.filter fun C => C ∉ root ∧ C.card = 1 ∧ C ∉ nodes true 2).card ≤ 0 := by + exact card_counting_deleted_marked_le θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (choices true) rootReduced (m := 1) (by omega) 1 + +example : ∃ D ∈ nodes true 2, D ⊆ ({(0, true), (1, false), (3, true)} : + Finset (Literal (Fin 9))) := + exists_mem_counting_node_subset 2 (nodes true) sizes hearts (dirs true) (steps true) + (t := 2) le_rfl (by decide) + +-- Empty variables and an empty root clause need no inhabitance at a zero-length prefix. +example : ∃ D ∈ ({∅} : ClauseSet Empty), D ⊆ (∅ : Finset (Literal Empty)) := + exists_mem_counting_node_subset 0 (fun _ => ({∅} : ClauseSet Empty)) id + (fun _ => ∅) (fun _ => false) (by intro i hi; omega) (t := 0) le_rfl (by decide) + +-- The FULL insertion balance still needs nontautology. In this reduced, legal-choice root, +-- one newly introduced width-two petal is immediately removed by the added negated unit. +private def tautRoot : ClauseSet (Fin 8) := + {{(0, true), (0, false), (1, true)}, {(0, true), (2, true), (3, true)}, + {(0, true), (4, true), (5, true)}, {(0, true), (6, true), (7, true)}} + +example : Reduced tautRoot := by unfold Reduced; decide +example : Choice θ tautRoot 3 {(0, true)} := by + apply choice_of_pairs + · unfold Heavy; decide + · decide + · decide + +example : ((countingIntro tautRoot 3 {(0, true)} true \ tautRoot).filter + fun C => C.card = 2).card = 4 := by decide +example : ((countingChild tautRoot 3 {(0, true)} true).filter + fun C => C ∉ tautRoot ∧ C.card = 2).card = 3 := by decide +example : ∃ C ∈ tautRoot, ∃ l ∈ C, Std.Sat.Literal.negate l ∈ C := by decide + +end ComplexityTests.SparsificationCountingDeletion From 7957fa8a302dc86f202abc52dcd0c1594342d9d7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 22:23:52 -0400 Subject: [PATCH 08/24] doc: correct deletion lemma subsection locator --- .../ClauseSet/Sparsification/CountingDeletion.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean index 8fbe7ed2b..62acd241e 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingDeletion.lean @@ -16,7 +16,7 @@ import Mathlib.Algebra.Group.Action.Defs /-! # Charging deleted marked clauses to actual counting insertions -The deletion-charge argument of Appendix A.3, Lemma A.3 and (A.3) [OpenAI2026ETH] assigns +The deletion-charge argument of Appendix A.2, Lemma A.3 and (A.3) [OpenAI2026ETH] assigns a removed marked width-`j` clause to a new strict subclause inserted in that step. The present-clause bound supplies at most `2 * θ (j - i)` recipients for a width-`i` insertion. From bff2d5cd4ab0995180b0bd855ba1049440e7c49d Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 02:32:41 +0000 Subject: [PATCH 09/24] docs: refine Appendix A.1, Proposition 6.4, and DenseRelabel source records (#1255) --- .../FineGrained/CountingEndpoint.lean | 14 +++++++----- .../CNF/ModelCount/DenseRelabel.lean | 3 ++- .../Sparsification/CountingList.lean | 22 +++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean b/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean index 9eaf86f95..9fa0c3073 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingEndpoint.lean @@ -24,12 +24,14 @@ Section 6.4, Proposition 6.4 (pp. 43–44) [OpenAI2026ETH] and dense variable re ### Source relation and library adaptations -* **Proposition 6.4 (§6.4, pp. 43–44) vs. Theorem 1.1:** Proposition 6.4 establishes deterministic - exact model counting for $k$-CNF formulas on $n$ declared variables in time bounded using the - squared-$\log\log$ interpolation scale $\rho(n) = n (\log\log n)^2 / \sqrt{\log n}$ - (formalized with the $+16$ domain shift as `interpolationScale n`), whereas Theorem 1.1 states - the sharper single-$\log\log$ headline bound. This module uses Proposition 6.4's - `interpolationScale`, which is already $o(n)$ (`isLittleO_interpolationScale`). +* **Proposition 6.4 (§6.4, pp. 43–44) vs. Theorem 1.1:** Proposition 6.4 states that for every fixed + integer $k \ge 2$, there is a deterministic algorithm that, given a $k$-CNF formula $F$ on + $n \ge 20$ variables with $m$ clauses, outputs the exact number of satisfying assignments of $F$ + in $\{0, 1\}^n$ in time $(n + m)^{O_k(1)} \cdot 2^{O_k(\rho(n))}$, where + $\rho(n) = n (\log\log n)^2 / \sqrt{\log n}$ (formalized for all $n \in \mathbb{N}$ with the + $+16$ domain shift as `interpolationScale n`), whereas Theorem 1.1 states the sharper + single-$\log\log$ headline bound. This module uses Proposition 6.4's `interpolationScale`, which + is already $o(n)$ (`isLittleO_interpolationScale`). * **Library adaptations:** First-occurrence dense variable numbering (`CNF.denseRelabel φ = φ.relabel φ.distinctVars.idxOf`, mapping occurring variables onto `Finset.range φ.numVars` while preserving `φ.modelCount` as diff --git a/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean b/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean index 74c56c4c1..b16783da0 100644 --- a/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean +++ b/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean @@ -21,7 +21,8 @@ import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Pl `CNF.denseRelabel φ` renames each variable of a CNF formula `φ` by its index in `φ.distinctVars`. Unlike `CNF.compact` (which uses first positions in `φ.vars` and can leave gaps when variables repeat), `CNF.denseRelabel φ` maps the occurring variables bijectively onto `0, …, φ.numVars - 1` -in order of first occurrence [OpenAI2026ETH, Section 2]. +in order of first occurrence (a library adaptation to the declared-variable model of +[OpenAI2026ETH]). The renaming preserves clause lengths, clause count, `numVars`, `Satisfiable`, `modelCount`, and gives `φ.denseRelabel.modelCountOn (Finset.range φ.numVars) = φ.modelCount`. It is computable in diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean index 8f82fcc9a..bcf9a74bf 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingList.lean @@ -18,13 +18,16 @@ tautology removal (`removeTautologies`), nontautological normalization (`countin singleton negated-heart unit clauses (`negatedHeartUnits`), the disjoint counting children (`countingChild`), and one deterministic counting-sparsification step (`countingNext`). -At each branching step on a heart `H`, the `false` (A) branch adds `H` while the `true` (B) branch -adds both the petals `petals ψ c H` and the singleton negated-heart units `negatedHeartUnits H` -[OpenAI2026ETH, Appendix A.1, equation (A.2)]. Applying `dedupClauses` before `reduce` ensures that -the list-level normal form (`CountingNormalized`) is preserved unconditionally across steps while -computing the exact clause-set operation `ClauseSet.Sparsification.countingChild` -(`toClauseSet_countingChild`) and conserving the declared-variable model count -(`modelCountOn_countingChild`). +Initial preprocessing (`countingNormalize φ = normalize (removeTautologies φ)`) removes tautologies, +deduplicates literals and clauses, and reduces to a subset-minimal antichain. At each branching step +on a heavy heart `H` satisfying `deg_c(H) ≥ θ_{c - |H|}` (equation (A.1), p. 44, with the +unnumbered threshold schedule `θ j = M ^ j`), the `false` (A) branch adds `H` while the `true` (B) +branch adds both the petals `petals ψ c H` and the singleton negated-heart units +`negatedHeartUnits H` [OpenAI2026ETH, Appendix A.1, equation (A.2), pp. 44–45]. Applying +`dedupClauses` before `reduce` restores the duplicate-free antichain (`CountingNormalized`) across +steps without re-running tautology removal, while computing the exact clause-set operation +`ClauseSet.Sparsification.countingChild` (`toClauseSet_countingChild`) and conserving the +declared-variable model count (`modelCountOn_countingChild`). ## Main definitions @@ -102,8 +105,9 @@ def countingChild (ψ : CNF V) (c : ℕ) (H : Clause V) (b : Bool) : CNF V := def countingNext (θ : ℕ → ℕ) (k : ℕ) (ψ : CNF V) (b : Bool) : CNF V := countingChild ψ (choice θ k ψ).1 (choice θ k ψ).2 b -/-- The counting-sparsification threshold schedule `θ j = M ^ j` [OpenAI2026ETH, Appendix A.1, -equation (A.1)]. -/ +/-- The counting-sparsification threshold schedule `θ j = M ^ j` (defined on p. 44 of +[OpenAI2026ETH, Appendix A.1] for `1 ≤ j ≤ k` and extended by `M ^ 0 = 1`, used in the heavy-heart +branching inequality `deg_c(H) ≥ θ_{c - |H|}` of equation (A.1)). -/ def countingThreshold (M j : ℕ) : ℕ := M ^ j /-- For `1 ≤ M`, every counting threshold is positive. -/ From a76c007e48a1c9a4e65283096728d8def0f693c7 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 02:39:20 +0000 Subject: [PATCH 10/24] feat(FineGrained,Sparsification): depth-first counting-sparsification search and ComputableInO certificate (#1226) --- Complexity.lean | 2 + .../FineGrained/CountingSparsification.lean | 920 ++++++++++++++++++ .../Sparsification/CountingSearch.lean | 654 +++++++++++++ .../FineGrained/CountingSparsification.lean | 122 +++ 4 files changed, 1698 insertions(+) create mode 100644 Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean create mode 100644 ComplexityTests/Computability/Complexity/FineGrained/CountingSparsification.lean diff --git a/Complexity.lean b/Complexity.lean index 5a4099a75..3931213b2 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -763,6 +763,7 @@ public import Complexity.Computability.Complexity.FineGrained.Assignment public import Complexity.Computability.Complexity.FineGrained.BruteForce public import Complexity.Computability.Complexity.FineGrained.CountingEndpoint public import Complexity.Computability.Complexity.FineGrained.CountingScale +public import Complexity.Computability.Complexity.FineGrained.CountingSparsification public import Complexity.Computability.Complexity.FineGrained.ExhaustiveSearch public import Complexity.Computability.Complexity.FineGrained.Exponents public import Complexity.Computability.Complexity.FineGrained.GraphProblems @@ -2173,6 +2174,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean new file mode 100644 index 000000000..a73a91be7 --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean @@ -0,0 +1,920 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.Exponents +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime +import Complexity.Computability.Complexity.Problems.SAT.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.NatPower +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Div +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain + +/-! +# Algorithmic counting sparsification on native machines + +Implements the depth-first counting-sparsification search +(`Cslib.Complexity.CountingSparsify.search`) of Appendix A and Section 6.4, Proposition 6.4 +[OpenAI2026ETH] on the native multi-tape Turing machines, parallel to the decision sparsifier +`Cslib.Complexity.Sparsify.search` +(`Complexity/Computability/Complexity/FineGrained/Sparsification.lean`). + +Given a formula `φ : CNF ℕ` with `n = φ.numVars` occurring variables, the search maintains a state +`(n, (stk, acc))` (`CountingSparsify.State`, with `n` in unary and `acc` in binary capped at +`2 ^ n`): +* Starting from `init k φ = (φ.distinctVars.length, ([root k φ], 0))` (where `root k φ` is + `countingNormalize φ` if `φ` has clause width at most `k` and `[[]]` otherwise), each step + (`CountingSparsify.step`) inspects the top formula `ψ` of `stk`. +* If `ψ` is sparse (`isSparse (countingThreshold M) K ψ = true`), it pops `ψ`, runs a leaf model + counter `g : CNF ℕ → ℕ` on `ψ`, weights the result by `leafWeight n ψ (g ψ) = + 2 ^ (n - ψ.distinctVars.length) * g ψ` to account for the `n - ψ.numVars` declared variables of + `φ` not occurring in `ψ` (`CNF.modelCountOn_eq_mul_modelCount`), and adds the weighted count into + `acc` capped at `2 ^ n`. +* If `ψ` is not sparse (using the heavy-heart branching condition of Appendix A.1, equation (A.1), + p. 44, with the unnumbered threshold schedule `countingThreshold M j = M ^ j`), it pushes the two + disjoint counting children `countingNext (countingThreshold M) K ψ false` and + `countingNext (countingThreshold M) K ψ true` (Appendix A.1, equation (A.2), pp. 44–45). + +Under the quantitative tree depth and leaf-count bounds of Appendix A.2–A.3 (`CountingTreeBound`), +running `step M K g` for `rounds B φ = 2 ^ (n / B + 2)` steps visits every leaf of the +counting-sparsification tree and returns `φ.modelCount` (`CountingSparsify.search_eq_modelCount`), +within time `O(2 ^ ((1 / B + occBound K M * δ) * n) * (|encCNF φ| + 1) ^ E)` whenever `g` counts +sparse width-`≤ k` formulas in time `O(2 ^ (δ * m) * (|encCNF ψ| + 1) ^ e)` +(`CountingSparsify.exists_computableInO_search`). + +## Main definitions + +* `Cslib.Complexity.CountingSparsify.State`, `Cslib.Complexity.CountingSparsify.leafWeight`, + `Cslib.Complexity.CountingSparsify.leafUpdate`, `Cslib.Complexity.CountingSparsify.step`, + `Cslib.Complexity.CountingSparsify.root`, `Cslib.Complexity.CountingSparsify.init`, + `Cslib.Complexity.CountingSparsify.rounds`, `Cslib.Complexity.CountingSparsify.search`: the + counting-sparsification search. +* `Cslib.Complexity.CountingSparsify.occBound`: the sparse leaf clause-density bound + `2 * ∑ j ∈ Finset.range K, countingThreshold M j`. +* `Cslib.Complexity.CountingSparsify.CountingTreeBound`: the Appendix A.2–A.3 depth and leaf-count + hypotheses for `countingRule`. + +## Main results + +* `Cslib.Complexity.CountingSparsify.search_eq_modelCount`, + `Cslib.Complexity.CountingSparsify.search_eq_ite_modelCount`: the search computes `φ.modelCount` + on formulas of width `≤ k` (and `0` otherwise). +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search`: native `ComputableInO` time and + space certificate for `CountingSparsify.search`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Section 6.4, + Proposition 6.4 (pp. 43–44) and Appendix A (pp. 44–47). +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification Turing.MultiTapeTM +open scoped List + +namespace CountingSparsify + +open ClauseSet.Sparsification (CountingBranchingRule) + +/-! ### Auxiliary model-count and binary-encoding bounds -/ + +/-- The number of satisfying assignments of `ψ` on its occurring variables is at most +`2 ^ ψ.numVars`. -/ +theorem modelCount_le_two_pow_numVars {V : Type*} [DecidableEq V] (ψ : CNF V) : + ψ.modelCount ≤ 2 ^ ψ.numVars := by + rw [CNF.modelCount, CNF.numVars] + refine (Finset.card_filter_le _ _).trans ?_ + simp only [Finset.card_univ, Fintype.card_fun, Fintype.card_bool, Fintype.card_coe, le_rfl] + +private theorem length_encNat_le_of_le_two_pow {acc n : ℕ} (h : acc ≤ 2 ^ n) : + (encNat acc).length ≤ n + 1 := by + refine (ComputableTidilyInO.length_encNat_mono h).trans ?_ + rw [encNat_two_pow, List.length_append, List.length_replicate, List.length_singleton] + +/-! ### The counting-sparsification search -/ + +/-- The states of the counting-sparsification search: the declared variable count `n` (in unary), +a stack of formulas, and a binary accumulator capped at `2 ^ n`. -/ +abbrev State : Type := ℕ × (List (CNF ℕ) × ℕ) + +/-- States encoded as framed tuples, with the declared variable count in unary and the accumulator +in binary. -/ +private abbrev encState : State ↪ List Bool := + encPair encUnary (encPair (encList encCNF) encNat) + +/-- Weight a leaf's occurring-variable model count `c` by `2 ^ (n - ψ.numVars)` for the unused +declared variables. -/ +def leafWeight (n : ℕ) (ψ : CNF ℕ) (c : ℕ) : ℕ := + 2 ^ (n - ψ.distinctVars.length) * c + +/-- Update the search state when the top of the stack is a sparse leaf with occurring-variable +count `c`, popping the leaf and adding its weighted count (capped at `2 ^ s.1`). -/ +def leafUpdate (s : State) (c : ℕ) : State := + (s.1, (s.2.1.tail, min (2 ^ s.1) (s.2.2 + leafWeight s.1 (s.2.1.headD []) c))) + +/-- One step of the depth-first search of the counting-sparsification tree, for threshold base `M` +and width `K`: a sparse formula on top of the stack is popped and weighted by `leafWeight` applied +to the leaf counter `g`; any other formula on top is replaced by its two counting children +(`countingNext`). -/ +def step (M K : ℕ) (g : CNF ℕ → ℕ) (s : State) : State := + if s.2.1.isEmpty then s + else if isSparse (countingThreshold M) K (s.2.1.headD []) then + leafUpdate s (g (s.2.1.headD [])) + else + (s.1, (countingNext (countingThreshold M) K (s.2.1.headD []) false :: + countingNext (countingThreshold M) K (s.2.1.headD []) true :: s.2.1.tail, s.2.2)) + +/-- The root of the counting-sparsification search: the nontautological normal form +(`countingNormalize`) of a formula of width `k`, and the unsatisfiable formula `[[]]` otherwise. -/ +def root (k : ℕ) (φ : CNF ℕ) : CNF ℕ := + if φ.all fun c => decide (c.length ≤ k) then countingNormalize φ else [[]] + +/-- The initial state of the counting-sparsification search on `φ`. -/ +def init (k : ℕ) (φ : CNF ℕ) : State := + (φ.distinctVars.length, ([root k φ], 0)) + +/-- The number of rounds of the search, `2 ^ (n / B + 2)` on formulas with `n` occurring +variables. -/ +def rounds (B : ℕ) (φ : CNF ℕ) : ℕ := 2 ^ (φ.distinctVars.length / B + 2) + +/-- **The counting-sparsification search**: run `step M K g` for `rounds B φ` rounds from +`init k φ`, and report the accumulated model count. -/ +def search (M K k B : ℕ) (g : CNF ℕ → ℕ) (φ : CNF ℕ) : ℕ := + ((step M K g)^[rounds B φ] (init k φ)).2.2 + +/-! ### Agreement with the depth-first tree search -/ + +variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) + +/-- The list-level counting branching rule for width `K` and thresholds `countingThreshold M`. -/ +abbrev rule : CountingBranchingRule (countingThreshold M) ℕ {ψ : CNF ℕ // CountingNormalized K ψ} := + CNF.Sparsification.countingRule (one_le_countingThreshold hM) hK + +include hM hK in +private theorem step_map (g : CNF ℕ → ℕ) (n : ℕ) + (stk : List {ψ : CNF ℕ // CountingNormalized K ψ}) (acc : ℕ) : + step M K g (n, (stk.map Subtype.val, acc)) = + (n, Prod.map (List.map Subtype.val) id + ((rule hM hK).searchStep (2 ^ n) (fun t => leafWeight n t.1 (g t.1)) (stk, acc))) := by + cases stk with + | nil => rfl + | cons s rest => + have hiff := isSparse_eq_true_iff (one_le_countingThreshold hM) s.2.toNormalized + by_cases hs : ClauseSet.Sparsification.Sparse (countingThreshold M) s.1.toClauseSet + · have hs' : isSparse (countingThreshold M) K s.1 = true := hiff.mpr hs + simp [step, leafUpdate, CountingBranchingRule.searchStep, hs, hs', rule, + CNF.Sparsification.countingRule] + · have hs' : isSparse (countingThreshold M) K s.1 = false := by + simpa using fun h => hs (hiff.mp h) + simp [step, CountingBranchingRule.searchStep, hs, hs', rule, + CNF.Sparsification.countingRule] + +include hM hK in +private theorem iterate_step_map (g : CNF ℕ → ℕ) (r n : ℕ) + (stk : List {ψ : CNF ℕ // CountingNormalized K ψ}) (acc : ℕ) : + (step M K g)^[r] (n, (stk.map Subtype.val, acc)) = + (n, Prod.map (List.map Subtype.val) id + (((rule hM hK).searchStep (2 ^ n) + (fun t => leafWeight n t.1 (g t.1)))^[r] (stk, acc))) := by + induction r generalizing stk acc with + | zero => rfl + | succ r ih => + rw [Function.iterate_succ_apply, Function.iterate_succ_apply, step_map hM hK] + obtain ⟨stk', acc'⟩ := + (rule hM hK).searchStep (2 ^ n) (fun t => leafWeight n t.1 (g t.1)) (stk, acc) + exact ih stk' acc' + +private theorem step_nil (g : CNF ℕ → ℕ) (n acc : ℕ) : + step M K g (n, ([], acc)) = (n, ([], acc)) := rfl + +private theorem iterate_step_nil (g : CNF ℕ → ℕ) (r n acc : ℕ) : + (step M K g)^[r] (n, ([], acc)) = (n, ([], acc)) := + Function.iterate_fixed (step_nil g n acc) r + +/-! ### The root -/ + +theorem countingNormalized_root {k : ℕ} (hkK : k ≤ K) (φ : CNF ℕ) : + CountingNormalized K (root k φ) := by + unfold root + split_ifs with h + · simp only [List.all_eq_true, decide_eq_true_eq] at h + exact countingNormalized_countingNormalize fun C hC => (h C hC).trans hkK + · exact ⟨⟨by simp, by simp, by simp, + by simp [CNF.toClauseSet, ClauseSet.Sparsification.Reduced]⟩, + by simp [Nontautological]⟩ + +private theorem length_le_of_mem_root (k : ℕ) (φ : CNF ℕ) {C : Clause ℕ} (hC : C ∈ root k φ) : + C.length ≤ k := by + unfold root at hC + split_ifs at hC with h + · simp only [List.all_eq_true, decide_eq_true_eq] at h + exact (countingNormalized_countingNormalize h).length_le C hC + · simp only [List.mem_singleton] at hC + subst hC + exact Nat.zero_le k + +private theorem length_root_le (k : ℕ) (φ : CNF ℕ) : (root k φ).length ≤ φ.length + 1 := by + unfold root + split_ifs + · exact (length_countingNormalize_le φ).trans (Nat.le_succ _) + · simp + +/-- The root as a state of the list-level counting branching rule. -/ +def rootState {k : ℕ} (hkK : k ≤ K) (φ : CNF ℕ) : {ψ : CNF ℕ // CountingNormalized K ψ} := + ⟨root k φ, countingNormalized_root hkK φ⟩ + +private theorem mem_vars_of_mem_root {k : ℕ} {φ : CNF ℕ} : + ∀ C ∈ root k φ, ∀ l ∈ C, l.1 ∈ φ.vars.toFinset := by + intro C hC l hl + unfold root at hC + split_ifs at hC with h + · exact mem_vars_of_mem_countingNormalize C.toFinset (mem_toClauseSet.mpr ⟨C, hC, rfl⟩) + l (List.mem_toFinset.mpr hl) + · simp only [List.mem_singleton] at hC + subst hC + simp at hl + +/-! ### The states of the tree -/ + +/-- The bound `2 ∑_{j < K} countingThreshold M j` on the occurrences of a variable in a sparse +formula. -/ +def occBound (K M : ℕ) : ℕ := 2 * ∑ j ∈ Finset.range K, countingThreshold M j + +section Tree + +variable {k : ℕ} (hkK : k ≤ K) {φ : CNF ℕ} {w : List Bool} +include hM hK hkK + +private theorem mem_vars_of_mem_walk (w : List Bool) : + ∀ C ∈ ((rule hM hK).walk (rootState hkK φ) w).1, ∀ l ∈ C, l.1 ∈ φ.vars.toFinset := + support_walk_countingRule (one_le_countingThreshold hM) hK mem_vars_of_mem_root w + +private theorem vars_walk_subset (w : List Bool) : + ((rule hM hK).walk (rootState hkK φ) w).1.vars.toFinset ⊆ φ.vars.toFinset := by + intro v hv + obtain ⟨l, hl, rfl⟩ := List.mem_map.mp (List.mem_toFinset.mp hv) + obtain ⟨C, hC, hlC⟩ := List.mem_flatten.mp hl + exact mem_vars_of_mem_walk hM hK hkK w C hC l hlC + +private theorem numVars_walk_le (w : List Bool) : + ((rule hM hK).walk (rootState hkK φ) w).1.numVars ≤ φ.numVars := + Finset.card_le_card (vars_walk_subset hM hK hkK w) + +/-- Every state reached along a node word of length at most `h` has at most `φ.length + 1 + K * h` +clauses. -/ +private theorem length_walk_le (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) + {h : ℕ} (hwlen : w.length ≤ h) : + ((rule hM hK).walk (rootState hkK φ) w).1.length ≤ φ.length + 1 + K * h := by + have hcard : (((rule hM hK).walk (rootState hkK φ) w).1.toClauseSet).card ≤ + (rootState hkK φ).1.toClauseSet.card + K * w.length := + CountingBranchingRule.IsNodeWord.card_walk_le (one_le_countingThreshold hM) hK + (rootState hkK φ).2.card_le hw + rw [((rule hM hK).walk (rootState hkK φ) w).2.card_toClauseSet, + (rootState hkK φ).2.card_toClauseSet] at hcard + exact hcard.trans (Nat.add_le_add (length_root_le k φ) (Nat.mul_le_mul_left K hwlen)) + +/-- **Sparse leaves are small.** The sparse states of the counting-sparsification tree have at most +`occBound K M * φ.numVars + 1` clauses. -/ +private theorem length_walk_le_of_isSparse + (hs : isSparse (countingThreshold M) K ((rule hM hK).walk (rootState hkK φ) w).1 = true) : + ((rule hM hK).walk (rootState hkK φ) w).1.length ≤ occBound K M * φ.numVars + 1 := by + set ψ := (rule hM hK).walk (rootState hkK φ) w + have hsparse := (isSparse_eq_true_iff (one_le_countingThreshold hM) ψ.2.toNormalized).mp hs + rw [← ψ.2.card_toClauseSet] + refine ClauseSet.Sparsification.card_le_of_sparse (one_le_countingThreshold hM) hsparse + ψ.2.card_le φ.vars.toFinset fun C' hC' l hl => ?_ + obtain ⟨C, hC, rfl⟩ := mem_toClauseSet.mp hC' + exact mem_vars_of_mem_walk hM hK hkK w C hC l (List.mem_toFinset.mp hl) + +/-- Every state of the tree has clause width at most `k` (for every `φ : CNF ℕ` and every `k : ℕ`, +including `k = 0`). -/ +private theorem length_le_of_mem_walk (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) + {C : Clause ℕ} (hC : C ∈ ((rule hM hK).walk (rootState hkK φ) w).1) : C.length ≤ k := by + set ψ := (rule hM hK).walk (rootState hkK φ) w + rcases Nat.eq_zero_or_pos k with rfl | hk + · have hsparse : ClauseSet.Sparsification.Sparse (countingThreshold M) + (rootState hkK φ).1.toClauseSet := by + intro c H hH + obtain ⟨D', hD'⟩ := hH.flower_nonempty (one_le_countingThreshold hM) + obtain ⟨hD'root, rfl, -⟩ := ClauseSet.Sparsification.mem_flower.mp hD' + obtain ⟨D, hD, rfl⟩ := mem_toClauseSet.mp hD'root + have hD0 : D.toFinset.card ≤ 0 := + (List.toFinset_card_le D).trans (length_le_of_mem_root 0 φ hD) + have := hH.1 + have := hH.2.1 + omega + cases w with + | nil => exact length_le_of_mem_root 0 φ hC + | cons b u => exact absurd hsparse hw.1 + · have hcard : C.toFinset.card ≤ k := by + refine CountingBranchingRule.IsNodeWord.card_le_of_mem_walk + (R := rule hM hK) (one_le_countingThreshold hM) hk (fun D' hD' => ?_) hw C.toFinset + (mem_toClauseSet.mpr ⟨C, hC, rfl⟩) + obtain ⟨D, hD, rfl⟩ := mem_toClauseSet.mp hD' + exact (List.toFinset_card_le D).trans (length_le_of_mem_root k φ hD) + rwa [List.toFinset_card_of_nodup (ψ.2.nodup C hC)] at hcard + +end Tree + +/-! ### The run of the search -/ + +/-- The quantitative depth and leaf-count bounds for the counting-sparsification tree +(Appendix A.2–A.3 [OpenAI2026ETH]): from any `CountingNormalized K` root formula `ψ` whose +occurring variables lie in a finite set `declared` of size `N = declared.card`, every leaf word of +`rule hM hK` has length at most `L_coeff * N`, and any finset of leaf words has cardinality at +most `2 ^ (N / B + 1)`. -/ +def CountingTreeBound (M K B L_coeff : ℕ) (hM : 1 ≤ M) (hK : 1 ≤ K) : Prop := + ∀ (ψ : {ψ : CNF ℕ // CountingNormalized K ψ}) (declared : Finset ℕ), + (∀ C ∈ ψ.1, ∀ l ∈ C, l.1 ∈ declared) → + (∀ w, (rule hM hK).IsLeafWord ψ w → w.length ≤ L_coeff * declared.card) ∧ + ∀ W : Finset (List Bool), (∀ w ∈ W, (rule hM hK).IsLeafWord ψ w) → + W.card ≤ 2 ^ (declared.card / B + 1) + +open Classical in +/-- **The run of the counting-sparsification search.** Under `CountingTreeBound`, after +`rounds B φ` rounds the stack is empty and the accumulator records the capped sum of `leafWeight` +over the leaf words of `rootState hkK φ`. At every round `i`, the stack holds at most +`L_coeff * φ.numVars + 1` nodes of depth at most `L_coeff * φ.numVars`, and the accumulator is at +most `2 ^ φ.numVars`. -/ +private theorem iterate_step (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) (g : CNF ℕ → ℕ) (φ : CNF ℕ) : + (∃ W : Finset (List Bool), + (∀ w, w ∈ W ↔ (rule hM hK).IsLeafWord (rootState hkK φ) w) ∧ + (step M K g)^[rounds B φ] (init k φ) = + (φ.distinctVars.length, ([], min (2 ^ φ.distinctVars.length) + (∑ w ∈ W, leafWeight φ.distinctVars.length + ((rule hM hK).walk (rootState hkK φ) w).1 + (g ((rule hM hK).walk (rootState hkK φ) w).1))))) ∧ + ∀ i, ∃ (stk : List {ψ : CNF ℕ // CountingNormalized K ψ}) (acc : ℕ), + (step M K g)^[i] (init k φ) = (φ.distinctVars.length, (stk.map Subtype.val, acc)) ∧ + stk.length ≤ L_coeff * φ.numVars + 1 ∧ + acc ≤ 2 ^ φ.numVars ∧ + ∀ t ∈ stk, ∃ w, (rule hM hK).IsNodeWord (rootState hkK φ) w ∧ + w.length ≤ L_coeff * φ.numVars ∧ (rule hM hK).walk (rootState hkK φ) w = t := by + set R := rule hM hK + set s₀ := rootState hkK φ + set n := φ.distinctVars.length + have hn_eq : n = φ.numVars := CNF.length_distinctVars φ + obtain ⟨hlen, hWbound⟩ := hTB s₀ φ.vars.toFinset mem_vars_of_mem_root + obtain ⟨W, hW, N, hN, h⟩ := CountingBranchingRule.exists_iterate_searchStep (R := R) + (one_le_countingThreshold hM) (fun _ => two_le_countingThreshold hM2) + (2 ^ n) (fun t => leafWeight n t.1 (g t.1)) s₀ hlen + have hWcard : W.card ≤ 2 ^ (φ.numVars / B + 1) := + hWbound W fun w hw => (hW w).mp hw + have hNr : N ≤ rounds B φ := by + rw [rounds, CNF.length_distinctVars, pow_succ] + omega + have hinit : init k φ = (n, ([s₀].map Subtype.val, 0)) := rfl + obtain ⟨hfin, hmid⟩ := h [] 0 (Nat.zero_le _) + have hN' : (step M K g)^[N] (init k φ) = + (n, ([], min (2 ^ n) (∑ w ∈ W, leafWeight n (R.walk s₀ w).1 (g (R.walk s₀ w).1)))) := by + rw [hinit, iterate_step_map hM hK, hfin] + simp + refine ⟨⟨W, hW, ?_⟩, fun i => ?_⟩ + · rw [show rounds B φ = (rounds B φ - N) + N by omega, Function.iterate_add_apply, hN', + iterate_step_nil] + · by_cases hi : i ≤ N + · obtain ⟨stk, acc', heq, hstk, hacc', hmem⟩ := hmid i hi + refine ⟨stk, acc', ?_, hstk, hn_eq ▸ hacc', hmem⟩ + rw [hinit, iterate_step_map hM hK, heq] + simp + · refine ⟨[], min (2 ^ n) (∑ w ∈ W, leafWeight n (R.walk s₀ w).1 (g (R.walk s₀ w).1)), + ?_, by simp, hn_eq ▸ Nat.min_le_left _ _, by simp⟩ + rw [show i = (i - N) + N by omega, Function.iterate_add_apply, hN', iterate_step_nil] + rfl + +include hM hK in +/-- **The search computes the model count.** If `g` computes `ψ.modelCount` on sparse formulas `ψ` +of width at most `k`, then `search M K k B g φ` returns `φ.modelCount` on formulas of width at most +`k` and `0` otherwise. -/ +theorem search_eq_ite_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) + (φ : CNF ℕ) : + search M K k B g φ = + if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0 := by + classical + set R := rule hM hK + set s₀ := rootState hkK φ + obtain ⟨⟨W, hW, hfin⟩, -⟩ := iterate_step hM hK hM2 hkK hTB g φ + rw [search, hfin] + dsimp only + have hleaf (w : List Bool) (hw : R.IsLeafWord s₀ w) : + leafWeight φ.distinctVars.length (R.walk s₀ w).1 (g (R.walk s₀ w).1) = + (R.walk s₀ w).1.modelCountOn φ.vars.toFinset := by + set ψ := R.walk s₀ w + have hsparse : isSparse (countingThreshold M) K ψ.1 = true := + (isSparse_eq_true_iff (one_le_countingThreshold hM) ψ.2.toNormalized).mpr hw.sparse_walk + have hwidth : ∀ c ∈ ψ.1, c.length ≤ k := + fun c hc => length_le_of_mem_walk hM hK hkK hw.isNodeWord hc + rw [leafWeight, hg ψ.1 hwidth hsparse, CNF.length_distinctVars, CNF.length_distinctVars] + exact (CNF.modelCountOn_eq_mul_modelCount ψ.1 φ.vars.toFinset + (vars_walk_subset hM hK hkK w)).symm + have hsum : ∑ w ∈ W, leafWeight φ.distinctVars.length (R.walk s₀ w).1 (g (R.walk s₀ w).1) = + (root k φ).modelCountOn φ.vars.toFinset := + sum_leafWords_countingRule_modelCountOn (one_le_countingThreshold hM) + (fun _ => two_le_countingThreshold hM2) hK φ.vars.toFinset + (f := fun t => leafWeight φ.distinctVars.length t.1 (g t.1)) s₀ hleaf hW + rw [hsum, CNF.length_distinctVars] + unfold root + split_ifs with hφ + · rw [modelCountOn_countingNormalize, CNF.modelCountOn_vars] + exact Nat.min_eq_right (modelCount_le_two_pow_numVars φ) + · rw [CNF.modelCountOn_eq_zero_of_nil_mem (List.mem_singleton_self [])] + exact Nat.min_eq_right (Nat.zero_le _) + +include hM hK in +/-- **The search computes `φ.modelCount` on formulas of width at most `k`.** -/ +theorem search_eq_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) + {φ : CNF ℕ} (hφ : ∀ c ∈ φ, c.length ≤ k) : + search M K k B g φ = φ.modelCount := by + rw [search_eq_ite_modelCount hM hK hM2 hkK hTB hg φ, + ite_eq_left_iff.mpr fun h => + absurd (List.all_eq_true.mpr fun c hc => decide_eq_true (hφ c hc)) h] + +/-! ### Polynomial-time parts -/ + +section PolyTime + +open ComputableTidilyInPolyTime + +private theorem computableTidilyInPolyTime_isSparse_head (M K : ℕ) : + ComputableTidilyInPolyTime + (fun s : State => isSparse (countingThreshold M) K (s.2.1.headD [])) + encState encBool := by + have h := (computableInPolyTime_isSparse encNat + (computableInPolyTime_countingThreshold_le M) K).tidy + have hH : ComputableTidilyInPolyTime (fun s : State => s.2.1.headD []) encState encCNF := by + unfold encState + fun_prop + exact h.comp hH + +private theorem computableTidilyInPolyTime_inner (M K : ℕ) : + ComputableTidilyInPolyTime (fun s : State => + (s.1, (countingNext (countingThreshold M) K (s.2.1.headD []) false :: + countingNext (countingThreshold M) K (s.2.1.headD []) true :: s.2.1.tail, s.2.2))) + encState encState := by + have h := (computableInPolyTime_countingNext encNat + (computableInPolyTime_countingThreshold_le M) K).tidy + unfold encState encCNF + fun_prop + +private theorem computableTidilyInPolyTime_leafUpdate : + ComputableTidilyInPolyTime (fun p : State × ℕ => leafUpdate p.1 p.2) + (encPair encState encNat) encState := by + have hvars := SAT.computableInPolyTime_distinctVars.tidy + have heq : (fun p : State × ℕ => leafUpdate p.1 p.2) = fun p => + (p.1.1, (p.1.2.1.tail, + if decide (2 ^ p.1.1 ≤ p.1.2.2 + + 2 ^ (p.1.1 - (p.1.2.1.headD []).distinctVars.length) * p.2) then + 2 ^ p.1.1 + else + p.1.2.2 + 2 ^ (p.1.1 - (p.1.2.1.headD []).distinctVars.length) * p.2)) := by + funext p + simp [leafUpdate, leafWeight, min_def] + rw [heq] + unfold encState encCNF + fun_prop + +private theorem computableTidilyInPolyTime_init (k : ℕ) : + ComputableTidilyInPolyTime (init k) encCNF encState := by + have hvars := SAT.computableInPolyTime_distinctVars.tidy + have hnorm := (computableInPolyTime_countingNormalize encNat).tidy + unfold init root encState encCNF + fun_prop + +attribute [local fun_prop] ComputableInPolyTime.tidy in +private theorem computableTidilyInPolyTime_rounds (B : ℕ) : + ComputableTidilyInPolyTime (rounds B) encCNF encNat := by + have hvars := SAT.computableInPolyTime_distinctVars.tidy + have h := unary_div_const B + unfold rounds + fun_prop + +/-- **The time of a counting-sparsification step**: a polynomial in the length of the encoded state, +plus the time of the leaf counter `g` and a polynomial in the state and counter output lengths when +`g` runs on a sparse head formula. -/ +private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} + (hg : ComputableTidilyInO g encCNF encNat t t) : + ∃ d, ComputableTidilyInO (step M K g) encState encState + (fun s => ((encState s).length + 1) ^ d + + if s.2.1.isEmpty = false ∧ + isSparse (countingThreshold M) K (s.2.1.headD []) = true then + t (s.2.1.headD []) + + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d + else 0) + (fun s => ((encState s).length + 1) ^ d + + if s.2.1.isEmpty = false ∧ + isSparse (countingThreshold M) K (s.2.1.headD []) = true then + t (s.2.1.headD []) + + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d + else 0) := by + obtain ⟨dE, eE, hE⟩ := (show ComputableTidilyInPolyTime (fun s : State => s.2.1.isEmpty) encState + encBool by unfold encState; fun_prop).exists_degree + obtain ⟨dI, eI, hI⟩ := (id' (encIn := encState)).exists_degree + obtain ⟨dS, eS, hS⟩ := (computableTidilyInPolyTime_isSparse_head M K).exists_degree + obtain ⟨dH, eH, hH⟩ := (show ComputableTidilyInPolyTime (fun s : State => s.2.1.headD []) encState + encCNF by unfold encState; fun_prop).exists_degree + obtain ⟨dN, eN, hN⟩ := (computableTidilyInPolyTime_inner M K).exists_degree + obtain ⟨dU, eU, hU⟩ := computableTidilyInPolyTime_leafUpdate.exists_degree + set D := dE + dI + dS + dH + dN + dU + 1 + have hE' := (hE.mono (d' := D) (e' := eE) (by omega) le_rfl).inO + have hI' := (hI.mono (d' := D) (e' := eI) (by omega) le_rfl).inO + have hS' := (hS.mono (d' := D) (e' := eS) (by omega) le_rfl).inO + have hH' := (hH.mono (d' := D) (e' := eH) (by omega) le_rfl).inO + have hN' := (hN.mono (d' := D) (e' := eN) (by omega) le_rfl).inO + have hU' := (hU.mono (d' := D) (e' := eU) (by omega) le_rfl).inO + have hleaf := hU'.comp (hI'.prod (hg.comp hH')) + have h := hE'.ite hI' (hS'.ite hleaf hN') + have hpair_le (s : State) (c : ℕ) : + ((encPair encState encNat (s, c)).length + 1) ^ D ≤ + 4 ^ D * ((encState s).length + (encNat c).length + 1) ^ D := by + rw [ ← mul_pow] + refine Nat.pow_le_pow_left ?_ D + simp only [length_encPair] + omega + refine ⟨D, h.mono (4 ^ D + 5) (fun s => ?_) (fun s => ?_)⟩ <;> + · simp only [Function.comp_apply] + have hp := hpair_le s (g (s.2.1.headD [])) + have h4D : 1 ≤ 4 ^ D := Nat.one_le_pow _ _ (by norm_num) + generalize 4 ^ D = F at hp h4D ⊢ + generalize ((encState s).length + 1) ^ D = P + generalize t (s.2.1.headD []) = T + generalize ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ D = Q at hp ⊢ + generalize ((encPair encState encNat (s, g (s.2.1.headD []))).length + 1) ^ D = R at hp ⊢ + cases s.2.1.isEmpty <;> + cases isSparse (countingThreshold M) K (s.2.1.headD []) <;> + simp only [Bool.false_eq_true, ite_false, ite_true, and_true, and_false, + Bool.true_eq_false] <;> nlinarith + +end PolyTime + +/-! ### Encoded state sizes -/ + +/-- A bound on the length of the encoded formulas in the counting-sparsification tree. -/ +private def nodeBound (K L_coeff : ℕ) (φ : CNF ℕ) : ℕ := + (φ.length + 1 + K * (L_coeff * φ.numVars)) * (2 * (K * (2 * (encCNF φ).length + 1)) + 1) + +/-- A bound on the length of the encoded states of the counting-sparsification search. -/ +private def stateBound (K L_coeff : ℕ) (φ : CNF ℕ) : ℕ := + 4 * φ.numVars + 4 * ((L_coeff * φ.numVars + 1) * (2 * nodeBound K L_coeff φ + 1)) + 10 + +private theorem length_encLiteral_le_of_mem_vars {φ : CNF ℕ} {l : Literal ℕ} + (hl : l.1 ∈ φ.vars.toFinset) : (encPair encNat encBool l).length ≤ (encCNF φ).length := by + obtain ⟨l', hl'F, hl'eq⟩ := List.mem_map.mp (List.mem_toFinset.mp hl) + obtain ⟨D, hD, hl'D⟩ := List.mem_flatten.mp hl'F + have hlen_eq : (encPair encNat encBool l).length = (encPair encNat encBool l').length := by + simp only [length_encPair, encBool_apply, List.length_singleton, hl'eq] + rw [hlen_eq] + exact (length_le_encList_of_mem _ hl'D).trans (length_le_encList_of_mem _ hD) + +section Sizes + +variable {k L_coeff : ℕ} (hkK : k ≤ K) {φ : CNF ℕ} +include hM hK hkK + +private theorem length_encCNF_walk_le {w : List Bool} + (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) (hwlen : w.length ≤ L_coeff * φ.numVars) : + (encCNF ((rule hM hK).walk (rootState hkK φ) w).1).length ≤ nodeBound K L_coeff φ := by + set ψ := (rule hM hK).walk (rootState hkK φ) w + refine (length_encList_le _ ψ.1 (b := K * (2 * (encCNF φ).length + 1)) fun C hC => ?_).trans + (Nat.mul_le_mul_right _ (length_walk_le hM hK hkK hw hwlen)) + refine (length_encList_le _ C (b := (encCNF φ).length) fun l hl => ?_).trans + (Nat.mul_le_mul_right _ (ψ.2.length_le C hC)) + exact length_encLiteral_le_of_mem_vars (mem_vars_of_mem_walk hM hK hkK w C hC l hl) + +private theorem length_encState_le {stk : List {ψ : CNF ℕ // CountingNormalized K ψ}} {acc : ℕ} + (hstk : stk.length ≤ L_coeff * φ.numVars + 1) (hacc : acc ≤ 2 ^ φ.numVars) + (hmem : ∀ t ∈ stk, ∃ w, (rule hM hK).IsNodeWord (rootState hkK φ) w ∧ + w.length ≤ L_coeff * φ.numVars ∧ (rule hM hK).walk (rootState hkK φ) w = t) : + (encState (φ.distinctVars.length, (stk.map Subtype.val, acc))).length ≤ + stateBound K L_coeff φ := by + have hlist := length_encList_le encCNF (stk.map Subtype.val) + (b := nodeBound K L_coeff φ) fun ψ hψ => by + obtain ⟨t, ht, rfl⟩ := List.mem_map.mp hψ + obtain ⟨w, hw, hwlen, rfl⟩ := hmem t ht + exact length_encCNF_walk_le hM hK hkK hw hwlen + rw [List.length_map] at hlist + have hlist' := hlist.trans (Nat.mul_le_mul_right _ hstk) + have hacc' := length_encNat_le_of_le_two_pow hacc + have hn := CNF.length_distinctVars φ + simp only [encState, length_encPair, encUnary_apply, List.length_replicate, hn, stateBound] + omega + +end Sizes + +/-! ### The time of the counting-sparsification search -/ + +/-- The time bound of the leaf counter on the sparse leaves of the tree. -/ +private noncomputable def leafTime (K M L_coeff : ℕ) (δ : ℝ) (c e : ℕ) (φ : CNF ℕ) : ℕ := + ⌈(c : ℝ) * (2 ^ (δ * ((occBound K M * φ.numVars + 1 : ℕ) : ℝ)) * + ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c⌉₊ + +include hM hK in +/-- **The time of the rounds.** Under `CountingTreeBound`, the counting-sparsification search is +computed within `rounds B φ + 1` times the bound on a round: a polynomial in `stateBound`, plus the +time bound of the leaf counter on the sparse leaves (`leafTime`), plus the cost of the binary round +counter; and within polynomial space for a round. -/ +private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableTidilyInO g encCNF encNat t t) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + ∃ d dn di, ComputableTidilyInO (search M K k B g) encCNF encNat + (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2)) + (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ) + + stateBound K L_coeff φ + (encNat (rounds B φ)).length) := by + obtain ⟨d, hstep⟩ := exists_computableTidilyInO_step M K hg + obtain ⟨dn, en, hn⟩ := (computableTidilyInPolyTime_rounds B).exists_degree + obtain ⟨di, ei, hi⟩ := (computableTidilyInPolyTime_init k).exists_degree + have hround : ∀ φ, ∀ i ≤ rounds B φ, + (fun s : State => ((encState s).length + 1) ^ d + + if s.2.1.isEmpty = false ∧ + isSparse (countingThreshold M) K (s.2.1.headD []) = true then + t (s.2.1.headD []) + + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d + else 0) ((step M K g)^[i] (init k φ)) ≤ + 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ := by + intro φ i _ + obtain ⟨stk, acc, heq, hstk, hacc, hmem⟩ := (iterate_step hM hK hM2 hkK hTB g φ).2 i + rw [heq] + dsimp only + have hS := length_encState_le hM hK hkK hstk hacc hmem + have hpow1 : ((encState (φ.distinctVars.length, (stk.map Subtype.val, acc))).length + 1) ^ d ≤ + (2 * stateBound K L_coeff φ + 1) ^ d := + Nat.pow_le_pow_left (by omega) d + split_ifs with hc + · cases stk with + | nil => simp at hc + | cons s rest => + obtain ⟨w, hw, hwlen, rfl⟩ := hmem s List.mem_cons_self + simp only [List.map_cons, List.headD_cons] at hc hS hpow1 ⊢ + set ψ := ((rule hM hK).walk (rootState hkK φ) w).1 + have hlen := length_walk_le_of_isSparse hM hK hkK hc.2 + have henc := length_encCNF_walk_le hM hK hkK hw hwlen + have hwidth : ∀ c ∈ ψ, c.length ≤ k := + fun c hC => length_le_of_mem_walk hM hK hkK hw hC + have hg_bound : g ψ ≤ 2 ^ φ.numVars := + (hg_le ψ hwidth hc.2).trans (Nat.pow_le_pow_right (by omega) + (numVars_walk_le hM hK hkK w)) + have hglen : (encNat (g ψ)).length ≤ φ.numVars + 1 := + length_encNat_le_of_le_two_pow hg_bound + have hN_le_sb : φ.numVars + 1 ≤ stateBound K L_coeff φ := by + unfold stateBound + omega + have hpow2 : + ((encState (φ.distinctVars.length, (ψ :: rest.map Subtype.val, acc))).length + + (encNat (g ψ)).length + 1) ^ d ≤ (2 * stateBound K L_coeff φ + 1) ^ d := + Nat.pow_le_pow_left (by omega) d + have h₁ : (2 : ℝ) ^ (δ * ψ.length) ≤ + 2 ^ (δ * ((occBound K M * φ.numVars + 1 : ℕ) : ℝ)) := + Real.rpow_le_rpow_of_exponent_le (by norm_num) + (mul_le_mul_of_nonneg_left (by exact_mod_cast hlen) hδ) + have h₂ : (((encCNF ψ).length : ℝ) + 1) ^ e ≤ ((nodeBound K L_coeff φ : ℝ) + 1) ^ e := + pow_le_pow_left₀ (by positivity) (by exact_mod_cast Nat.add_le_add_right henc 1) e + have h₃ := mul_le_mul h₁ h₂ (by positivity) (by positivity) + have h₄ := mul_le_mul_of_nonneg_left h₃ (Nat.cast_nonneg c : (0 : ℝ) ≤ c) + have h₅ := (ht ψ).trans (add_le_add h₄ le_rfl) + have ht_le : t ψ ≤ leafTime K M L_coeff δ c e φ := + Nat.cast_le.mp (h₅.trans (Nat.le_ceil _)) + omega + · omega + have hstate : ∀ φ, ∀ i ≤ rounds B φ, + (encState ((step M K g)^[i] (init k φ))).length ≤ stateBound K L_coeff φ := by + intro φ i _ + obtain ⟨stk, acc, heq, hstk, hacc, hmem⟩ := (iterate_step hM hK hM2 hkK hTB g φ).2 i + rw [heq] + exact length_encState_le hM hK hkK hstk hacc hmem + exact ⟨d, dn, di, + (ComputableTidilyInO.iterate hn.inO hi.inO hstep hround hround hstate).snd'.snd'⟩ + +include hM hK in +/-- **The time of the counting-sparsification search.** With a leaf model counter `g` running +within time `O(2 ^ (δ * m) * (|encCNF ψ| + 1) ^ e)` on formulas with `m` clauses and bounded by +`2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` is computable within time +`O(2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ +theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B g) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, dn, di, h⟩ := + exists_computableTidilyInO_search hM hK hM2 hkK hTB hg.tidy hg_le hδ ht + set Kx := K * L_coeff + set c₁ := (2 + Kx) * (6 * K + 1) + set c₂ := 4 + 4 * (L_coeff + 1) * (2 * c₁ + 1) + 10 + set C₁ := 2 * (2 * c₂ + 1) ^ d + c₂ + 16 + set E := dn + di + 3 * d + 2 * e + 3 + set C₂ : ℝ := c * 2 ^ δ * ((c₁ : ℝ) + 1) ^ e + c + 1 + set C₀ : ℝ := 2 + 5 * (C₁ + C₂) + set T : CNF ℕ → ℕ := fun φ => + ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) + have h_mono : ComputableTidilyInO (search M K k B g) encCNF encNat T T := + h.mono_le (fun _ => le_rfl) fun φ => by + dsimp only [T] + have h1 : 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + (encNat (rounds B φ)).length ≤ + 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2 := by + have : (encNat (rounds B φ)).length ≤ ((encNat (rounds B φ)).length + 1) ^ 2 := + (Nat.le_succ _).trans (Nat.le_self_pow two_ne_zero _) + omega + have h2 := Nat.le_mul_of_pos_left + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) + (Nat.succ_pos (rounds B φ)) + simp only [Nat.succ_eq_add_one] at h2 + omega + refine ⟨T, ⌈C₀⌉₊, E, .of_tidy h_mono, fun φ => ?_⟩ + set L := (encCNF φ).length + set N := φ.numVars + set P := (L + 1) ^ E + have hNL : N ≤ L := φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hmL : φ.length ≤ L := length_le_length_encList _ φ + have hP (a : ℕ) (ha : a ≤ E) : (L + 1) ^ a ≤ P := Nat.pow_le_pow_right (Nat.succ_pos L) ha + have hnb : nodeBound K L_coeff φ ≤ c₁ * (L + 1) ^ 2 := by + have h₁ : φ.length + 1 + K * (L_coeff * N) ≤ (2 + Kx) * (L + 1) := by + have : K * (L_coeff * N) = Kx * N := by ring + nlinarith + have h₂ : 2 * (K * (2 * L + 1)) + 1 ≤ (6 * K + 1) * (L + 1) := by nlinarith + calc nodeBound K L_coeff φ ≤ ((2 + Kx) * (L + 1)) * ((6 * K + 1) * (L + 1)) := + Nat.mul_le_mul h₁ h₂ + _ = c₁ * (L + 1) ^ 2 := by ring + have hsb : stateBound K L_coeff φ ≤ c₂ * (L + 1) ^ 3 := by + have h₁ : L_coeff * N + 1 ≤ (L_coeff + 1) * (L + 1) := by nlinarith + have h₂ : 2 * nodeBound K L_coeff φ + 1 ≤ (2 * c₁ + 1) * (L + 1) ^ 2 := by + have : 1 ≤ (L + 1) ^ 2 := Nat.one_le_pow _ _ (Nat.succ_pos L) + nlinarith + have h₃ : 1 ≤ (L + 1) ^ 3 := Nat.one_le_pow _ _ (Nat.succ_pos L) + have h₃' : L + 1 ≤ (L + 1) ^ 3 := by + calc L + 1 = (L + 1) ^ 1 := (pow_one _).symm + _ ≤ (L + 1) ^ 3 := Nat.pow_le_pow_right (Nat.succ_pos L) (by omega) + have h₄ := Nat.mul_le_mul h₁ h₂ + have hdef : stateBound K L_coeff φ = + 4 * N + 4 * ((L_coeff * N + 1) * (2 * nodeBound K L_coeff φ + 1)) + 10 := rfl + have e₁ : 4 * (L + 1) ^ 3 + + 4 * (((L_coeff + 1) * (L + 1)) * ((2 * c₁ + 1) * (L + 1) ^ 2)) + 10 * (L + 1) ^ 3 = + c₂ * (L + 1) ^ 3 := by simp only [c₂]; ring + omega + have henc : (encNat (rounds B φ)).length = N / B + 3 := by + rw [rounds, CNF.length_distinctVars, encNat_two_pow, List.length_append, + List.length_replicate, List.length_singleton] + have hr : rounds B φ + 1 ≤ 5 * 2 ^ (N / B) := by + have : rounds B φ = 4 * 2 ^ (N / B) := by + rw [rounds, CNF.length_distinctVars, pow_add] + ring + have : 1 ≤ 2 ^ (N / B) := Nat.one_le_two_pow + omega + have hpoly : (L + 1) ^ dn + (L + 1) ^ di ≤ 2 * P := by + have := hP dn (by omega) + have := hP di (by omega) + omega + have hrnd : 2 * (2 * stateBound K L_coeff φ + 1) ^ d + stateBound K L_coeff φ + + ((encNat (rounds B φ)).length + 1) ^ 2 ≤ C₁ * P := by + have h₁ : (2 * stateBound K L_coeff φ + 1) ^ d ≤ (2 * c₂ + 1) ^ d * (L + 1) ^ (3 * d) := by + rw [pow_mul, ← mul_pow] + refine Nat.pow_le_pow_left ?_ d + have : 1 ≤ (L + 1) ^ 3 := Nat.one_le_pow _ _ (Nat.succ_pos L) + have e₁ : (2 * c₂ + 1) * (L + 1) ^ 3 = 2 * (c₂ * (L + 1) ^ 3) + (L + 1) ^ 3 := by ring + omega + have h₂ : ((encNat (rounds B φ)).length + 1) ^ 2 ≤ 16 * (L + 1) ^ 2 := by + rw [henc] + have : N / B + 3 + 1 ≤ 4 * (L + 1) := by + have := (Nat.div_le_self N B).trans hNL + omega + calc (N / B + 3 + 1) ^ 2 ≤ (4 * (L + 1)) ^ 2 := Nat.pow_le_pow_left this 2 + _ = 16 * (L + 1) ^ 2 := by ring + have h₃ := hP (3 * d) (by omega) + have h₄ := hP 3 (by omega) + have h₅ := hP 2 (by omega) + have h₆ := Nat.mul_le_mul_left ((2 * c₂ + 1) ^ d) h₃ + calc 2 * (2 * stateBound K L_coeff φ + 1) ^ d + stateBound K L_coeff φ + + ((encNat (rounds B φ)).length + 1) ^ 2 + ≤ 2 * ((2 * c₂ + 1) ^ d * P) + c₂ * P + 16 * P := by + have := hsb.trans (Nat.mul_le_mul_left c₂ h₄) + have := h₂.trans (Nat.mul_le_mul_left 16 h₅) + omega + _ = C₁ * P := by ring + set Y : ℝ := 2 ^ ((occBound K M * δ) * N) + have hY : 1 ≤ Y := Real.one_le_rpow (by norm_num) (by positivity) + have hP1 : (1 : ℝ) ≤ P := by exact_mod_cast Nat.one_le_pow _ _ (Nat.succ_pos L) + have hleaf : (leafTime K M L_coeff δ c e φ : ℝ) ≤ C₂ * Y * P := by + have h₁ : (2 : ℝ) ^ (δ * ((occBound K M * N + 1 : ℕ) : ℝ)) = 2 ^ δ * Y := by + rw [← Real.rpow_add (by norm_num)] + congr 1 + push_cast + ring + have h₂ : ((nodeBound K L_coeff φ : ℝ) + 1) ^ e ≤ ((c₁ : ℝ) + 1) ^ e * P := by + have : ((nodeBound K L_coeff φ : ℝ) + 1) ≤ ((c₁ : ℝ) + 1) * ((L : ℝ) + 1) ^ 2 := by + have hnb' : (nodeBound K L_coeff φ : ℝ) ≤ c₁ * ((L : ℝ) + 1) ^ 2 := by exact_mod_cast hnb + have hL2 : (1 : ℝ) ≤ ((L : ℝ) + 1) ^ 2 := + one_le_pow₀ (by linarith [(L.cast_nonneg : (0 : ℝ) ≤ L)]) + have e₁ : ((c₁ : ℝ) + 1) * ((L : ℝ) + 1) ^ 2 = c₁ * ((L : ℝ) + 1) ^ 2 + + ((L : ℝ) + 1) ^ 2 := by ring + linarith only [hnb', hL2, e₁] + calc ((nodeBound K L_coeff φ : ℝ) + 1) ^ e ≤ (((c₁ : ℝ) + 1) * ((L : ℝ) + 1) ^ 2) ^ e := + pow_le_pow_left₀ (by positivity) this e + _ = ((c₁ : ℝ) + 1) ^ e * ((L : ℝ) + 1) ^ (2 * e) := by rw [mul_pow, ← pow_mul] + _ ≤ ((c₁ : ℝ) + 1) ^ e * P := by + refine mul_le_mul_of_nonneg_left ?_ (by positivity) + exact_mod_cast hP (2 * e) (by omega) + have h₃ := Nat.ceil_lt_add_one (show (0 : ℝ) ≤ + (c : ℝ) * (2 ^ (δ * ((occBound K M * N + 1 : ℕ) : ℝ)) * + ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c by positivity) + unfold leafTime + rw [h₁] at h₃ ⊢ + have h₄ : (c : ℝ) * (2 ^ δ * Y * ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) ≤ + c * 2 ^ δ * ((c₁ : ℝ) + 1) ^ e * Y * P := by + have := mul_le_mul_of_nonneg_left h₂ (by positivity : (0 : ℝ) ≤ c * 2 ^ δ * Y) + linarith only [this] + have h₅ : (c : ℝ) + 1 ≤ ((c : ℝ) + 1) * Y * P := by + rw [mul_assoc] + exact le_mul_of_one_le_right (by positivity) + (one_le_mul_of_one_le_of_one_le hY hP1) + calc (⌈(c : ℝ) * (2 ^ δ * Y * ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c⌉₊ : ℝ) + ≤ (c : ℝ) * (2 ^ δ * Y * ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c + 1 := h₃.le + _ ≤ C₂ * Y * P := by simp only [C₂]; linarith only [h₄, h₅] + set X : ℝ := 2 ^ ((1 / B : ℝ) * N) + have hX : ((2 ^ (N / B) : ℕ) : ℝ) ≤ X := by + rw [Nat.cast_pow, Nat.cast_ofNat, ← Real.rpow_natCast] + refine Real.rpow_le_rpow_of_exponent_le (by norm_num) ?_ + rw [one_div_mul_eq_div] + exact Nat.cast_div_le + have hX1 : 1 ≤ X := Real.one_le_rpow (by norm_num) (by positivity) + have hXY : X * Y = 2 ^ ((1 / B + occBound K M * δ) * N) := by + rw [← Real.rpow_add (by norm_num)] + ring_nf + have hT : (T φ : ℝ) ≤ 2 * P + 5 * X * (C₁ * P + C₂ * Y * P) := by + dsimp only [T] + have h₁ : ((L + 1) ^ dn + (L + 1) ^ di : ℝ) ≤ 2 * P := by exact_mod_cast hpoly + have h₂ : ((rounds B φ + 1 : ℕ) : ℝ) ≤ 5 * X := by + have : ((rounds B φ + 1 : ℕ) : ℝ) ≤ ((5 * 2 ^ (N / B) : ℕ) : ℝ) := by exact_mod_cast hr + rw [Nat.cast_mul] at this + push_cast at this hX ⊢ + linarith only [this, hX] + have h₃ : (((2 * (2 * stateBound K L_coeff φ + 1) ^ d + stateBound K L_coeff φ + + ((encNat (rounds B φ)).length + 1) ^ 2 : ℕ)) : ℝ) ≤ C₁ * P := by exact_mod_cast hrnd + have h₄ : (((2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2 : ℕ)) : ℝ) ≤ + C₁ * P + C₂ * Y * P := by + push_cast at h₃ ⊢ + linarith only [h₃, hleaf] + have h₅ := mul_le_mul h₂ h₄ (by positivity) (by positivity) + push_cast at h₁ h₅ ⊢ + linarith only [h₁, h₅] + have h_prod : (T φ : ℝ) ≤ + ⌈C₀⌉₊ * (2 ^ ((1 / B + occBound K M * δ) * N) * ((L : ℝ) + 1) ^ E) := by + calc (T φ : ℝ) ≤ 2 * P + 5 * X * (C₁ * P + C₂ * Y * P) := hT + _ ≤ C₀ * (X * Y * P) := by + have hXY1 : 1 ≤ X * Y := one_le_mul_of_one_le_of_one_le hX1 hY + have hC₁ : (0 : ℝ) ≤ C₁ := by positivity + have hP0 : (0 : ℝ) ≤ P := by positivity + have a₁ : (P : ℝ) ≤ X * Y * P := le_mul_of_one_le_left hP0 hXY1 + have a₂ : X * P ≤ X * Y * P := by + have := mul_le_mul_of_nonneg_left hY (by positivity : (0 : ℝ) ≤ X * P) + linarith only [this, show X * P * Y = X * Y * P by ring] + have a₃ := mul_le_mul_of_nonneg_left a₂ hC₁ + simp only [C₀] + linarith only [a₁, a₃] + _ ≤ ⌈C₀⌉₊ * (2 ^ ((1 / B + occBound K M * δ) * N) * ((L : ℝ) + 1) ^ E) := by + rw [← hXY] + push_cast [P] + exact mul_le_mul_of_nonneg_right (Nat.le_ceil C₀) (by positivity) + linarith [h_prod, (Nat.cast_nonneg ⌈C₀⌉₊ : (0 : ℝ) ≤ ⌈C₀⌉₊)] + +end CountingSparsify + +end Cslib.Complexity diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean new file mode 100644 index 000000000..9e4db331b --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean @@ -0,0 +1,654 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.Group.Action.Defs +import Mathlib.Tactic.Ring +import Mathlib.Tactic.Positivity + +/-! +# Counting sparsification -- branching rules and depth-first tree search + +A *counting branching rule* (`CountingBranchingRule`) is the counting-sparsification analogue of +`BranchingRule` (`Complexity/Logics/Propositional/ClauseSet/Sparsification/Tree.lean`): a +deterministic step `next : σ → Bool → σ` on states `σ` representing clause sets via `toClauseSet`, +which at every state that is not sparse takes the heart (`false`) or petal-plus-negated-heart-units +(`true`) child (`ClauseSet.Sparsification.countingChild`) for some valid choice `Choice θ`. + +When all thresholds for `j ≥ 1` are at least `2` (in particular for `countingThreshold M` with +`M ≥ 2`), every non-sparse step strictly decreases the clause-set potential +`potential ψ = ∑ C ∈ ψ, 2 ^ C.card` (`potential_countingChild_lt`, +`CountingBranchingRule.potential_next_lt`): removing a flower of `|F| ≥ 2` clauses of width `c` +decreases the potential by `|F| · 2 ^ c`, while adding the `|F|` petals of width `c - |H| ≤ c - 1` +and `|H| ≤ c - 1` unit clauses adds at most `|F| · 2 ^ (c - 1) + 2 (c - 1) < |F| · 2 ^ c`. +Furthermore, because `reduce` eliminates every clause of the flower `F` on both branches, each +step increases the total clause count by at most `K` (`card_countingChild_le`, +`CountingBranchingRule.IsNodeWord.card_walk_le`). + +A depth-first search of the counting branching tree (`CountingBranchingRule.searchStep`) keeps a +stack of states and a `cap`-bounded `ℕ` accumulator. Started from `(s :: rest, acc)`, after `N` +steps (with `N + 1 ≤ 2 * W.card` for the finset `W` of leaf words of `s`) the stack is back to +`rest` and the accumulator records `min cap (acc + ∑ w ∈ W, f (R.walk s w))` +(`CountingBranchingRule.exists_iterate_searchStep`). When `f` is additive across non-sparse +branches (in particular when `f` computes `modelCountOn declared` at the leaves), the leaf sum +equals the root value (`CountingBranchingRule.sum_leafWords_eq`, +`CountingBranchingRule.sum_leafWords_modelCountOn`). + +At the list level, `Cslib.Logic.PL.CNF.Sparsification.countingRule` instantiates +`CountingBranchingRule` on `{ψ : CNF V // CountingNormalized k ψ}` using `countingNext θ k`. + +## Main definitions + +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule`: a deterministic + counting-sparsification branching rule. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.walk`, `.IsNodeWord`, + `.IsLeafWord`: nodes and leaves of the counting branching tree. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.searchStep`: one step of the + depth-first counting search with a capped `ℕ` accumulator. +* `Cslib.Logic.PL.CNF.Sparsification.countingRule`: the list-level counting branching rule on + `{ψ : CNF V // CountingNormalized k ψ}`. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.potential_countingChild_lt`, + `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.potential_next_lt`: strict + potential decrease on both counting branches when `∀ j, 1 ≤ j → 2 ≤ θ j`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.card_countingChild_le`, + `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsNodeWord.card_walk_le`: + each counting step increases the number of clauses by at most the width `K`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.exists_iterate_searchStep`: + depth-first counting search visits every leaf within `2 * W.card` steps with stack height + at most `h + 1` and accumulator bounded by `max acc cap`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.sum_leafWords_modelCountOn`: + the sum of `modelCountOn declared` over all leaf words equals the root `modelCountOn declared`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Section 6.4, + Proposition 6.4 (pp. 43–44) and Appendix A (pp. 44–47). +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] {θ : ℕ → ℕ} + +/-! ### Clause-set properties of `countingChild` -/ + +/-- The flower `flower ψ c H` is disjoint from both counting children `countingChild ψ c H b`: +on `false` every flower clause strictly contains `H`, and on `true` every flower clause `C` +strictly contains its petal `C \ H`. -/ +theorem disjoint_flower_countingChild {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (h : Heavy θ ψ c H) (b : Bool) : + Disjoint (flower ψ c H) (countingChild ψ c H b) := by + rw [Finset.disjoint_left] + intro C hC hCchild + obtain ⟨-, hcard, hH⟩ := mem_flower.mp hC + cases b with + | false => + have hmin := (mem_reduce.mp hCchild).2 + refine hmin H (mem_union_right _ (by simp [intro])) (ssubset_of_subset_of_ne hH ?_) + intro heq + have := h.2.1 + rw [heq, hcard] at this + exact lt_irrefl _ this + | true => + have hmin := (mem_reduce.mp hCchild).2 + refine hmin (C \ H) + (mem_union_left _ (mem_union_right _ (mem_petals.mpr ⟨C, hC, rfl⟩))) + (sdiff_ssubset hH ?_) + rw [← card_pos] + exact h.1 + +/-- Every clause of `countingChild ψ c H true` belongs to `(ψ \ flower ψ c H) ∪ petals ψ c H ∪ +negatedHeartUnits H`. -/ +private theorem countingChild_true_subset {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (h : Heavy θ ψ c H) : + countingChild ψ c H true ⊆ (ψ \ flower ψ c H) ∪ petals ψ c H ∪ negatedHeartUnits H := by + intro C hC + rcases mem_union.mp (reduce_subset _ hC) with hC' | hCu + · rcases mem_union.mp hC' with hCψ | hCp + · refine mem_union_left _ (mem_union_left _ (mem_sdiff.mpr ⟨hCψ, fun hF => ?_⟩)) + exact disjoint_left.mp (disjoint_flower_countingChild h true) hF hC + · exact mem_union_left _ (mem_union_right _ hCp) + · exact mem_union_right _ hCu + +/-- Every clause of `countingChild ψ c H false` belongs to `(ψ \ flower ψ c H) ∪ {H}`. -/ +private theorem countingChild_false_subset {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (h : Heavy θ ψ c H) : + countingChild ψ c H false ⊆ (ψ \ flower ψ c H) ∪ {H} := by + intro C hC + rcases mem_union.mp (reduce_subset _ hC) with hCψ | hCH + · refine mem_union_left _ (mem_sdiff.mpr ⟨hCψ, fun hF => ?_⟩) + exact disjoint_left.mp (disjoint_flower_countingChild h false) hF hC + · exact mem_union_right _ hCH + +/-- Each singleton clause in `negatedHeartUnits H` has cardinality `1`. -/ +private theorem card_of_mem_negatedHeartUnits {H : Finset (Literal V)} {C : Finset (Literal V)} + (hC : C ∈ negatedHeartUnits H) : C.card = 1 := by + obtain ⟨l, -, rfl⟩ := mem_image.mp hC + exact card_singleton _ + +/-- The potential of `negatedHeartUnits H` is at most `2 * H.card`. -/ +private theorem potential_negatedHeartUnits_le (H : Finset (Literal V)) : + potential (negatedHeartUnits H) ≤ H.card * 2 := by + unfold potential + rw [sum_congr rfl fun C hC => by rw [card_of_mem_negatedHeartUnits hC], sum_const, smul_eq_mul, + pow_one] + exact Nat.mul_le_mul_right 2 card_image_le + +/-- **Termination of counting sparsification.** When `∀ j, 1 ≤ j → 2 ≤ θ j`, each counting +branching step on a heavy heart strictly decreases the potential `∑ C ∈ ψ, 2 ^ C.card`. -/ +theorem potential_countingChild_lt (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (h : Heavy θ ψ c H) (b : Bool) : + potential (countingChild ψ c H b) < potential ψ := by + cases b with + | false => exact potential_child_lt hθ1 h false + | true => + set F := flower ψ c H + have hFsplit : potential ψ = potential (ψ \ F) + potential F := by + unfold potential + rw [← sum_sdiff (flower_subset ψ c H)] + have hF : potential F = F.card * 2 ^ c := by + unfold potential + rw [sum_congr rfl fun C hC => by rw [(mem_flower.mp hC).2.1], sum_const, smul_eq_mul] + have hF2 : 2 ≤ F.card := (hθ2 (c - H.card) (by have := h.2.1; omega)).trans h.2.2 + have hpetals : potential (petals ψ c H) = F.card * 2 ^ (c - H.card) := by + unfold potential + rw [sum_congr rfl fun C hC => by rw [card_of_mem_petals hC], sum_const, card_petals, + smul_eq_mul] + have hunits := potential_negatedHeartUnits_le H + have hc1 : c = (c - 1) + 1 := by have := h.1; have := h.2.1; omega + have hpow_le : 2 ^ (c - H.card) ≤ 2 ^ (c - 1) := + Nat.pow_le_pow_right (by omega) (by have := h.1; omega) + have hH_lt : H.card < 2 ^ (c - 1) := + (show H.card ≤ c - 1 by have := h.2.1; omega).trans_lt Nat.lt_two_pow_self + have hsum_lt : potential (petals ψ c H) + potential (negatedHeartUnits H) < F.card * 2 ^ c := by + have h1 : potential (petals ψ c H) ≤ F.card * 2 ^ (c - 1) := by + rw [hpetals] + exact Nat.mul_le_mul_left F.card hpow_le + have h2 : potential (negatedHeartUnits H) < 2 * 2 ^ (c - 1) := by omega + have h3 : 2 * 2 ^ (c - 1) ≤ F.card * 2 ^ (c - 1) := + Nat.mul_le_mul_right _ hF2 + have h4 : F.card * 2 ^ c = F.card * 2 ^ (c - 1) + F.card * 2 ^ (c - 1) := by + conv_lhs => rw [hc1, pow_succ] + ring + omega + calc potential (countingChild ψ c H true) + ≤ potential ((ψ \ F) ∪ petals ψ c H ∪ negatedHeartUnits H) := + sum_le_sum_of_subset (countingChild_true_subset h) + _ ≤ potential ((ψ \ F) ∪ petals ψ c H) + potential (negatedHeartUnits H) := by + unfold potential + have := sum_union_inter (s₁ := (ψ \ F) ∪ petals ψ c H) (s₂ := negatedHeartUnits H) + (f := fun C : Finset (Literal V) => 2 ^ C.card) + omega + _ ≤ potential (ψ \ F) + potential (petals ψ c H) + potential (negatedHeartUnits H) := by + have : potential ((ψ \ F) ∪ petals ψ c H) ≤ + potential (ψ \ F) + potential (petals ψ c H) := by + unfold potential + have := sum_union_inter (s₁ := ψ \ F) (s₂ := petals ψ c H) + (f := fun C : Finset (Literal V) => 2 ^ C.card) + omega + omega + _ < potential (ψ \ F) + potential F := by rw [hF]; omega + _ = potential ψ := hFsplit.symm + +/-- Counting children keep the clause width `≤ k` when `1 ≤ k`. -/ +theorem card_le_of_mem_countingChild (hθ : ∀ j, 1 ≤ θ j) {ψ : ClauseSet V} {c k : ℕ} + (hk1 : 1 ≤ k) {H : Finset (Literal V)} (h : Heavy θ ψ c H) (hk : ∀ C ∈ ψ, C.card ≤ k) + {b : Bool} {C : Finset (Literal V)} (hC : C ∈ countingChild ψ c H b) : C.card ≤ k := by + cases b with + | false => exact card_le_of_mem_child hθ h hk hC + | true => + rcases mem_union.mp (countingChild_true_subset h hC) with hC' | hCu + · rcases mem_union.mp hC' with hCψ | hCp + · exact hk C (mem_sdiff.mp hCψ).1 + · obtain ⟨D, hD, rfl⟩ := mem_petals.mp hCp + exact (card_le_card sdiff_subset).trans (hk D (flower_subset ψ c H hD)) + · rw [card_of_mem_negatedHeartUnits hCu] + exact hk1 + +/-- Each counting child increases the number of clauses by at most `k`: the flower `F` is removed +from `ψ` while at most `|F| + |H| ≤ |F| + k` clauses are added. -/ +theorem card_countingChild_le (hθ : ∀ j, 1 ≤ θ j) {ψ : ClauseSet V} {c k : ℕ} + {H : Finset (Literal V)} (h : Heavy θ ψ c H) (hk : ∀ C ∈ ψ, C.card ≤ k) (b : Bool) : + (countingChild ψ c H b).card ≤ ψ.card + k := by + set F := flower ψ c H + have hFsub : F ⊆ ψ := flower_subset ψ c H + have hFcard : (ψ \ F).card + F.card = ψ.card := card_sdiff_add_card_eq_card hFsub + obtain ⟨D, hD⟩ := h.flower_nonempty hθ + have hFpos : 1 ≤ F.card := card_pos.mpr ⟨D, hD⟩ + have hck : c ≤ k := (mem_flower.mp hD).2.1 ▸ hk D (hFsub hD) + cases b with + | false => + calc (countingChild ψ c H false).card + ≤ ((ψ \ F) ∪ {H}).card := card_le_card (countingChild_false_subset h) + _ ≤ (ψ \ F).card + ({H} : Finset (Finset (Literal V))).card := card_union_le _ _ + _ = (ψ \ F).card + 1 := by rw [card_singleton] + _ ≤ ψ.card + k := by omega + | true => + have hpet : (petals ψ c H).card = F.card := card_petals ψ c H + have huni : (negatedHeartUnits H).card ≤ H.card := card_image_le + have hHc : H.card < c := h.2.1 + calc (countingChild ψ c H true).card + ≤ ((ψ \ F) ∪ petals ψ c H ∪ negatedHeartUnits H).card := + card_le_card (countingChild_true_subset h) + _ ≤ ((ψ \ F) ∪ petals ψ c H).card + (negatedHeartUnits H).card := card_union_le _ _ + _ ≤ (ψ \ F).card + (petals ψ c H).card + (negatedHeartUnits H).card := + Nat.add_le_add_right (card_union_le _ _) _ + _ ≤ ψ.card + k := by omega + +/-! ### Counting branching rules -/ + +/-- A *counting branching rule* for the thresholds `θ`: a deterministic step `next` on states `σ`, +each representing the clause set `toClauseSet s`, which at every state that is not sparse takes the +heart (`false`) or petal + negated-heart-units (`true`) child `countingChild` for one valid choice. +-/ +structure CountingBranchingRule (θ : ℕ → ℕ) (V : Type*) [DecidableEq V] (σ : Type*) where + /-- The clause set a state represents. -/ + toClauseSet : σ → ClauseSet V + /-- The heart (`false`) or petal + negated-heart-units (`true`) child of a state. -/ + next : σ → Bool → σ + /-- At a state that is not sparse, both children are taken for one valid choice. -/ + exists_choice_next : ∀ s, ¬ Sparse θ (toClauseSet s) → + ∃ c H, Choice θ (toClauseSet s) c H ∧ + ∀ b, toClauseSet (next s b) = countingChild (toClauseSet s) c H b + +namespace CountingBranchingRule + +variable {σ : Type*} (R : CountingBranchingRule θ V σ) + +/-- The state reached along a word of branches. -/ +def walk : σ → List Bool → σ + | s, [] => s + | s, b :: w => walk (R.next s b) w + +/-- A *node word*: its proper prefixes reach states that are not sparse. -/ +def IsNodeWord : σ → List Bool → Prop + | _, [] => True + | s, b :: w => ¬ Sparse θ (R.toClauseSet s) ∧ IsNodeWord (R.next s b) w + +/-- A *leaf word*: its proper prefixes reach states that are not sparse and it reaches a sparse +state. -/ +def IsLeafWord : σ → List Bool → Prop + | s, [] => Sparse θ (R.toClauseSet s) + | s, b :: w => ¬ Sparse θ (R.toClauseSet s) ∧ IsLeafWord (R.next s b) w + +/-- One step of the depth-first counting search of the branching tree, on a stack of states and a +`cap`-bounded `ℕ` accumulator: a sparse state on top is popped and `f s` is added to the +accumulator (capped at `cap`); any other state on top is replaced by its `false` and `true` +counting children. -/ +noncomputable def searchStep (cap : ℕ) (f : σ → ℕ) : List σ × ℕ → List σ × ℕ + | ([], acc) => ([], acc) + | (s :: rest, acc) => + open Classical in + if Sparse θ (R.toClauseSet s) then (rest, min cap (acc + f s)) + else (R.next s false :: R.next s true :: rest, acc) + +variable {R} + +/-- **Termination.** When `∀ j, 1 ≤ j → 2 ≤ θ j`, each counting branching step strictly decreases +the potential. -/ +theorem potential_next_lt (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) {s : σ} + (hs : ¬ Sparse θ (R.toClauseSet s)) (b : Bool) : + potential (R.toClauseSet (R.next s b)) < potential (R.toClauseSet s) := by + obtain ⟨c, H, hch, hnext⟩ := R.exists_choice_next s hs + rw [hnext] + exact potential_countingChild_lt hθ1 hθ2 hch.1 b + +/-- **Additive model-count conservation.** At any non-sparse state, the declared-variable model +count equals the sum of the model counts of its two children. -/ +theorem modelCountOn_next {s : σ} (hs : ¬ Sparse θ (R.toClauseSet s)) (declared : Finset V) : + (R.toClauseSet s).toCNF.modelCountOn declared = + (R.toClauseSet (R.next s false)).toCNF.modelCountOn declared + + (R.toClauseSet (R.next s true)).toCNF.modelCountOn declared := by + obtain ⟨c, H, -, hnext⟩ := R.exists_choice_next s hs + rw [hnext false, hnext true] + exact modelCountOn_countingChild (R.toClauseSet s) c H declared + +/-- Every leaf word is a node word. -/ +theorem IsLeafWord.isNodeWord {s : σ} {w : List Bool} (h : R.IsLeafWord s w) : + R.IsNodeWord s w := by + induction w generalizing s with + | nil => trivial + | cons b w ih => exact ⟨h.1, ih h.2⟩ + +/-- Walking along a leaf word reaches a sparse clause set. -/ +theorem IsLeafWord.sparse_walk {s : σ} {w : List Bool} (h : R.IsLeafWord s w) : + Sparse θ (R.toClauseSet (R.walk s w)) := by + induction w generalizing s with + | nil => exact h + | cons b w ih => exact ih h.2 + +/-- The only leaf word of a sparse state is the empty word. -/ +private theorem IsLeafWord.eq_nil {s : σ} (hs : Sparse θ (R.toClauseSet s)) {w : List Bool} + (hw : R.IsLeafWord s w) : w = [] := by + cases w with + | nil => rfl + | cons b w => exact absurd hs hw.1 + +/-- Every state has a leaf word when `∀ j, 1 ≤ θ j` and `∀ j, 1 ≤ j → 2 ≤ θ j`. -/ +theorem exists_isLeafWord (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) (s : σ) : + ∃ w, R.IsLeafWord s w := by + induction h : potential (R.toClauseSet s) using Nat.strong_induction_on generalizing s with + | _ n ih => + by_cases hs : Sparse θ (R.toClauseSet s) + · exact ⟨[], hs⟩ + · obtain ⟨w, hw⟩ := ih _ (h ▸ potential_next_lt hθ1 hθ2 hs false) (R.next s false) rfl + exact ⟨false :: w, hs, hw⟩ + +/-- Node words preserve clause width `≤ k` when `1 ≤ k`. -/ +theorem IsNodeWord.card_le_of_mem_walk (hθ : ∀ j, 1 ≤ θ j) {k : ℕ} (hk1 : 1 ≤ k) {s : σ} + (hk : ∀ C ∈ R.toClauseSet s, C.card ≤ k) {w : List Bool} (hw : R.IsNodeWord s w) : + ∀ C ∈ R.toClauseSet (R.walk s w), C.card ≤ k := by + induction w generalizing s with + | nil => exact hk + | cons b w ih => + obtain ⟨c, H, hch, hnext⟩ := R.exists_choice_next s hw.1 + refine ih (fun C hC => ?_) hw.2 + rw [hnext b] at hC + exact card_le_of_mem_countingChild hθ hk1 hch.1 hk hC + +/-- Along a node word `w` from a state of width `≤ k` (`1 ≤ k`), the number of clauses increases by +at most `k * w.length`. -/ +theorem IsNodeWord.card_walk_le (hθ : ∀ j, 1 ≤ θ j) {k : ℕ} (hk1 : 1 ≤ k) {s : σ} + (hk : ∀ C ∈ R.toClauseSet s, C.card ≤ k) {w : List Bool} (hw : R.IsNodeWord s w) : + (R.toClauseSet (R.walk s w)).card ≤ (R.toClauseSet s).card + k * w.length := by + induction w generalizing s with + | nil => simp [walk] + | cons b w ih => + obtain ⟨c, H, hch, hnext⟩ := R.exists_choice_next s hw.1 + have hk_next : ∀ C ∈ R.toClauseSet (R.next s b), C.card ≤ k := by + intro C hC + rw [hnext b] at hC + exact card_le_of_mem_countingChild hθ hk1 hch.1 hk hC + have hcard_next : (R.toClauseSet (R.next s b)).card ≤ (R.toClauseSet s).card + k := by + rw [hnext b] + exact card_countingChild_le hθ hch.1 hk b + have hih := ih hk_next hw.2 + simp only [walk, List.length_cons] + calc (R.toClauseSet (R.walk (R.next s b) w)).card + ≤ (R.toClauseSet (R.next s b)).card + k * w.length := hih + _ ≤ ((R.toClauseSet s).card + k) + k * w.length := Nat.add_le_add_right hcard_next _ + _ = (R.toClauseSet s).card + k * (w.length + 1) := by ring + +/-- Node words preserve declared-variable support containment. -/ +theorem IsNodeWord.support_walk (hθ : ∀ j, 1 ≤ θ j) {s : σ} {declared : Finset V} + (hU : ∀ C ∈ R.toClauseSet s, ∀ l ∈ C, l.1 ∈ declared) {w : List Bool} + (hw : R.IsNodeWord s w) : + ∀ C ∈ R.toClauseSet (R.walk s w), ∀ l ∈ C, l.1 ∈ declared := by + induction w generalizing s with + | nil => exact hU + | cons b w ih => + obtain ⟨c, H, hch, hnext⟩ := R.exists_choice_next s hw.1 + refine ih (fun C hC l hl => ?_) hw.2 + rw [hnext b] at hC + obtain ⟨D, hD⟩ := hch.1.flower_nonempty hθ + have hHD : H ⊆ D := (mem_flower.mp hD).2.2 + have hDψ : D ∈ R.toClauseSet s := flower_subset _ _ _ hD + exact support_countingChild (R.toClauseSet s) c H declared hU + (fun a ha => hU D hDψ a (hHD ha)) b C hC l hl + +private theorem min_add_assoc (cap a b c : ℕ) : + min cap (min cap (a + b) + c) = min cap (a + (b + c)) := by + omega + +open Classical in +/-- **Depth-first counting search.** Started from a state `s` above `rest` with accumulator +`acc ≤ cap`, the search takes `N` steps (`N + 1 ≤ 2 * W.card` for the finset `W` of all leaf words +of `s`) to return to `rest`, with accumulator `min cap (acc + ∑ w ∈ W, f (R.walk s w))`. If all +leaf words of `s` have length at most `h`, the stack above `rest` has height at most `h + 1` at +every intermediate step `i ≤ N` and the accumulator remains at most `cap`. -/ +theorem exists_iterate_searchStep (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) + (cap : ℕ) (f : σ → ℕ) (s : σ) {h : ℕ} (hh : ∀ w, R.IsLeafWord s w → w.length ≤ h) : + ∃ W : Finset (List Bool), (∀ w, w ∈ W ↔ R.IsLeafWord s w) ∧ ∃ N, N + 1 ≤ 2 * W.card ∧ + ∀ rest acc, acc ≤ cap → + (R.searchStep cap f)^[N] (s :: rest, acc) = + (rest, min cap (acc + ∑ w ∈ W, f (R.walk s w))) ∧ + ∀ i ≤ N, ∃ stk acc', (R.searchStep cap f)^[i] (s :: rest, acc) = (stk ++ rest, acc') ∧ + stk.length ≤ h + 1 ∧ acc' ≤ cap ∧ + ∀ t ∈ stk, ∃ w, R.IsNodeWord s w ∧ w.length ≤ h ∧ R.walk s w = t := by + classical + induction hp : potential (R.toClauseSet s) using Nat.strong_induction_on generalizing s h with + | _ n ih => + by_cases hs : Sparse θ (R.toClauseSet s) + · refine ⟨{[]}, fun w => ?_, 1, by simp, fun rest acc hacc => ⟨?_, fun i hi => ?_⟩⟩ + · simp only [mem_singleton] + exact ⟨fun h => h ▸ hs, fun hw => hw.eq_nil hs⟩ + · simp only [Function.iterate_one, searchStep, hs, ↓reduceIte, sum_singleton, walk] + · rcases Nat.le_one_iff_eq_zero_or_eq_one.mp hi with rfl | rfl + · exact ⟨[s], acc, rfl, by simp, hacc, + fun t ht => ⟨[], trivial, Nat.zero_le h, (List.mem_singleton.mp ht).symm⟩⟩ + · refine ⟨[], min cap (acc + f s), ?_, by simp, Nat.min_le_left _ _, by simp⟩ + simp only [Function.iterate_one, searchStep, hs, ↓reduceIte, List.nil_append] + · have hlt (b : Bool) : potential (R.toClauseSet (R.next s b)) < n := + hp ▸ potential_next_lt hθ1 hθ2 hs b + have hh' (b : Bool) : ∀ w, R.IsLeafWord (R.next s b) w → w.length ≤ h - 1 := fun w hw => by + have := hh (b :: w) ⟨hs, hw⟩ + simp only [List.length_cons] at this + omega + obtain ⟨u, hu⟩ := exists_isLeafWord (R := R) hθ1 hθ2 s + have hh1 : 1 ≤ h := by + cases u with + | nil => exact absurd hu hs + | cons b u => have := hh _ hu; simp only [List.length_cons] at this; omega + obtain ⟨W₀, hW₀, N₀, hN₀, h₀⟩ := ih _ (hlt false) (R.next s false) (hh' false) rfl + obtain ⟨W₁, hW₁, N₁, hN₁, h₁⟩ := ih _ (hlt true) (R.next s true) (hh' true) rfl + have hstep (rest : List σ) (acc : ℕ) : R.searchStep cap f (s :: rest, acc) = + (R.next s false :: R.next s true :: rest, acc) := by + simp only [searchStep, hs, ↓reduceIte] + have hdisj : Disjoint (W₀.image (false :: ·)) (W₁.image (true :: ·)) := by + rw [Finset.disjoint_left] + intro w hw₀ hw₁ + obtain ⟨u₀, -, rfl⟩ := mem_image.mp hw₀ + obtain ⟨u₁, -, hu₁⟩ := mem_image.mp hw₁ + exact Bool.false_ne_true (List.head_eq_of_cons_eq hu₁).symm + set W := W₀.image (false :: ·) ∪ W₁.image (true :: ·) + have hW_sum : ∑ w ∈ W, f (R.walk s w) = + (∑ u ∈ W₀, f (R.walk (R.next s false) u)) + + (∑ u ∈ W₁, f (R.walk (R.next s true) u)) := by + rw [sum_union hdisj, sum_image (fun _ _ _ _ h => List.cons_injective h), + sum_image (fun _ _ _ _ h => List.cons_injective h)] + rfl + refine ⟨W, fun w => ?_, N₁ + (N₀ + 1), ?_, + fun rest acc hacc => ⟨?_, fun i hi => ?_⟩⟩ + · constructor + · intro hw + rcases mem_union.mp hw with hw | hw + · obtain ⟨u, hu, rfl⟩ := mem_image.mp hw + exact ⟨hs, (hW₀ u).mp hu⟩ + · obtain ⟨u, hu, rfl⟩ := mem_image.mp hw + exact ⟨hs, (hW₁ u).mp hu⟩ + · intro hw + cases w with + | nil => exact absurd hw hs + | cons b u => + cases b + · exact mem_union_left _ (mem_image.mpr ⟨u, (hW₀ u).mpr hw.2, rfl⟩) + · exact mem_union_right _ (mem_image.mpr ⟨u, (hW₁ u).mpr hw.2, rfl⟩) + · rw [card_union_of_disjoint hdisj, card_image_of_injective _ List.cons_injective, + card_image_of_injective _ List.cons_injective] + omega + · have h₀_step := (h₀ (R.next s true :: rest) acc hacc).1 + have h₁_step := (h₁ rest (min cap (acc + ∑ w ∈ W₀, f (R.walk (R.next s false) w))) + (Nat.min_le_left _ _)).1 + rw [Function.iterate_add_apply, Function.iterate_succ_apply, hstep, h₀_step, h₁_step, + min_add_assoc, hW_sum] + · rcases Nat.eq_zero_or_pos i with rfl | hi0 + · exact ⟨[s], acc, rfl, by simp, hacc, + fun t ht => ⟨[], trivial, Nat.zero_le h, (List.mem_singleton.mp ht).symm⟩⟩ + by_cases hi₀ : i ≤ N₀ + 1 + · obtain ⟨j, rfl⟩ : ∃ j, i = j + 1 := ⟨i - 1, by omega⟩ + obtain ⟨stk, acc', heq, hlen, hacc', hmem⟩ := + (h₀ (R.next s true :: rest) acc hacc).2 j (by omega) + refine ⟨stk ++ [R.next s true], acc', ?_, ?_, hacc', fun t ht => ?_⟩ + · rw [Function.iterate_succ_apply, hstep, heq, List.append_assoc, List.singleton_append] + · simp only [List.length_append, List.length_singleton] + omega + · rcases List.mem_append.mp ht with ht | ht + · obtain ⟨w, hw, hwlen, rfl⟩ := hmem t ht + exact ⟨false :: w, ⟨hs, hw⟩, by simp only [List.length_cons]; omega, rfl⟩ + · exact ⟨[true], ⟨hs, trivial⟩, by simp only [List.length_singleton]; omega, + (List.mem_singleton.mp ht).symm⟩ + · obtain ⟨j, rfl⟩ : ∃ j, i = j + (N₀ + 1) := ⟨i - (N₀ + 1), by omega⟩ + obtain ⟨stk, acc', heq, hlen, hacc', hmem⟩ := + (h₁ rest (min cap (acc + ∑ w ∈ W₀, f (R.walk (R.next s false) w))) + (Nat.min_le_left _ _)).2 j (by omega) + refine ⟨stk, acc', ?_, by omega, hacc', fun t ht => ?_⟩ + · rw [Function.iterate_add_apply, Function.iterate_succ_apply, hstep, + (h₀ _ acc hacc).1, heq] + · obtain ⟨w, hw, hwlen, rfl⟩ := hmem t ht + exact ⟨true :: w, ⟨hs, hw⟩, by simp only [List.length_cons]; omega, rfl⟩ + +/-- If `M : σ → ℕ` is additive across non-sparse steps +(`M t = M (R.next t false) + M (R.next t true)`) and `f` agrees with `M` on the leaves of `s`, then +the sum of `f (R.walk s w)` over any finset `W` containing exactly the leaf words of `s` equals +`M s`. -/ +theorem sum_leafWords_eq (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) + {M f : σ → ℕ} + (hM : ∀ t, ¬ Sparse θ (R.toClauseSet t) → M t = M (R.next t false) + M (R.next t true)) + (s : σ) (hf : ∀ w, R.IsLeafWord s w → f (R.walk s w) = M (R.walk s w)) + {W : Finset (List Bool)} (hW : ∀ w, w ∈ W ↔ R.IsLeafWord s w) : + ∑ w ∈ W, f (R.walk s w) = M s := by + classical + induction hp : potential (R.toClauseSet s) using Nat.strong_induction_on generalizing s W with + | _ n ih => + by_cases hs : Sparse θ (R.toClauseSet s) + · have hWeq : W = {[]} := by + ext w + rw [hW, mem_singleton] + exact ⟨fun hw => hw.eq_nil hs, fun h => h ▸ hs⟩ + rw [hWeq, sum_singleton] + exact hf [] hs + · have hlt (b : Bool) : potential (R.toClauseSet (R.next s b)) < n := + hp ▸ potential_next_lt hθ1 hθ2 hs b + let W₀ := (W.filter fun w => w.head? = some false).image List.tail + let W₁ := (W.filter fun w => w.head? = some true).image List.tail + have hW₀ (u : List Bool) : u ∈ W₀ ↔ R.IsLeafWord (R.next s false) u := by + simp only [W₀, mem_image, mem_filter] + constructor + · rintro ⟨w, ⟨hwW, hwhead⟩, rfl⟩ + cases w with + | nil => simp at hwhead + | cons b u' => + simp only [List.head?_cons, Option.some.injEq] at hwhead + subst hwhead + exact ((hW (false :: u')).mp hwW).2 + · intro hu + exact ⟨false :: u, ⟨(hW (false :: u)).mpr ⟨hs, hu⟩, rfl⟩, rfl⟩ + have hW₁ (u : List Bool) : u ∈ W₁ ↔ R.IsLeafWord (R.next s true) u := by + simp only [W₁, mem_image, mem_filter] + constructor + · rintro ⟨w, ⟨hwW, hwhead⟩, rfl⟩ + cases w with + | nil => simp at hwhead + | cons b u' => + simp only [List.head?_cons, Option.some.injEq] at hwhead + subst hwhead + exact ((hW (true :: u')).mp hwW).2 + · intro hu + exact ⟨true :: u, ⟨(hW (true :: u)).mpr ⟨hs, hu⟩, rfl⟩, rfl⟩ + have hdisj : Disjoint (W₀.image (false :: ·)) (W₁.image (true :: ·)) := by + rw [Finset.disjoint_left] + intro w hw₀ hw₁ + obtain ⟨u₀, -, rfl⟩ := mem_image.mp hw₀ + obtain ⟨u₁, -, hu₁⟩ := mem_image.mp hw₁ + exact Bool.false_ne_true (List.head_eq_of_cons_eq hu₁).symm + have hWeq : W = W₀.image (false :: ·) ∪ W₁.image (true :: ·) := by + ext w + rw [mem_union, mem_image, mem_image, hW] + constructor + · intro hw + cases w with + | nil => exact absurd hw hs + | cons b u => + cases b + · exact Or.inl ⟨u, (hW₀ u).mpr hw.2, rfl⟩ + · exact Or.inr ⟨u, (hW₁ u).mpr hw.2, rfl⟩ + · rintro (⟨u, hu, rfl⟩ | ⟨u, hu, rfl⟩) + · exact ⟨hs, (hW₀ u).mp hu⟩ + · exact ⟨hs, (hW₁ u).mp hu⟩ + rw [hWeq, sum_union hdisj, sum_image (fun _ _ _ _ h => List.cons_injective h), + sum_image (fun _ _ _ _ h => List.cons_injective h)] + have ih₀ := ih _ (hlt false) (R.next s false) (fun u hu => hf (false :: u) ⟨hs, hu⟩) hW₀ rfl + have ih₁ := ih _ (hlt true) (R.next s true) (fun u hu => hf (true :: u) ⟨hs, hu⟩) hW₁ rfl + change (∑ x ∈ W₀, f (R.walk (R.next s false) x)) + + (∑ x ∈ W₁, f (R.walk (R.next s true) x)) = M s + rw [ih₀, ih₁, ← hM s hs] + +/-- **Leaf model-count partition.** If `f` computes `(R.toClauseSet ·).toCNF.modelCountOn declared` +on every leaf of `s`, then the sum of `f (R.walk s w)` over the leaf words of `s` equals +`(R.toClauseSet s).toCNF.modelCountOn declared`. -/ +theorem sum_leafWords_modelCountOn (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) + (declared : Finset V) {f : σ → ℕ} (s : σ) + (hf : ∀ w, R.IsLeafWord s w → + f (R.walk s w) = (R.toClauseSet (R.walk s w)).toCNF.modelCountOn declared) + {W : Finset (List Bool)} (hW : ∀ w, w ∈ W ↔ R.IsLeafWord s w) : + ∑ w ∈ W, f (R.walk s w) = (R.toClauseSet s).toCNF.modelCountOn declared := + sum_leafWords_eq hθ1 hθ2 (fun _ ht => modelCountOn_next ht declared) s hf hW + +end CountingBranchingRule + +end Cslib.Logic.PL.ClauseSet.Sparsification + +namespace Cslib.Logic.PL.CNF.Sparsification + +open ClauseSet.Sparsification + +variable {V : Type*} [DecidableEq V] + +/-- For `2 ≤ M` and `1 ≤ j`, `2 ≤ countingThreshold M j`. -/ +theorem two_le_countingThreshold {M : ℕ} (hM : 2 ≤ M) {j : ℕ} (hj : 1 ≤ j) : + 2 ≤ countingThreshold M j := by + unfold countingThreshold + calc 2 = 2 ^ 1 := rfl + _ ≤ M ^ 1 := Nat.pow_le_pow_left hM 1 + _ ≤ M ^ j := Nat.pow_le_pow_right (by omega) hj + +/-- **The list-level counting branching rule**, on formulas in nontautological normal form +(`CountingNormalized k`): uses `countingNext θ k`. -/ +def countingRule {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ θ j) {k : ℕ} (hk : 1 ≤ k) : + CountingBranchingRule θ V {ψ : CNF V // CountingNormalized k ψ} where + toClauseSet ψ := ψ.1.toClauseSet + next ψ b := ⟨countingNext θ k ψ.1 b, countingNormalized_countingNext θ hk ψ.2 b⟩ + exists_choice_next ψ hns := + ⟨_, _, (choice_spec hθ ψ.2.toNormalized hns).1, + fun b => toClauseSet_countingNext ψ.2.toNormalized b⟩ + +/-- Walking along `countingRule` preserves variable support containment for any word `w`. -/ +theorem support_walk_countingRule {θ : ℕ → ℕ} (hθ : ∀ j, 1 ≤ θ j) {k : ℕ} (hk : 1 ≤ k) + {s : {ψ : CNF V // CountingNormalized k ψ}} {declared : Finset V} + (hs : ∀ C ∈ s.1, ∀ l ∈ C, l.1 ∈ declared) (w : List Bool) : + ∀ C ∈ ((countingRule hθ hk).walk s w).1, ∀ l ∈ C, l.1 ∈ declared := by + induction w generalizing s with + | nil => exact hs + | cons b w ih => exact ih (support_countingNext θ k hs b) + +/-- **List-level leaf model-count partition.** If `f` computes `CNF.modelCountOn · declared` on +every leaf of `s` under `countingRule`, then the sum of `f` over the leaf words of `s` equals +`s.1.modelCountOn declared`. -/ +theorem sum_leafWords_countingRule_modelCountOn {θ : ℕ → ℕ} (hθ1 : ∀ j, 1 ≤ θ j) + (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) {k : ℕ} (hk : 1 ≤ k) (declared : Finset V) + {f : {ψ : CNF V // CountingNormalized k ψ} → ℕ} + (s : {ψ : CNF V // CountingNormalized k ψ}) + (hf : ∀ w, (countingRule hθ1 hk).IsLeafWord s w → + f ((countingRule hθ1 hk).walk s w) = + ((countingRule hθ1 hk).walk s w).1.modelCountOn declared) + {W : Finset (List Bool)} (hW : ∀ w, w ∈ W ↔ (countingRule hθ1 hk).IsLeafWord s w) : + ∑ w ∈ W, f ((countingRule hθ1 hk).walk s w) = s.1.modelCountOn declared := + CountingBranchingRule.sum_leafWords_eq (R := countingRule hθ1 hk) + (M := fun t => t.1.modelCountOn declared) hθ1 hθ2 + (fun t _ => modelCountOn_countingNext t.2.toNormalized declared) s hf hW + +end Cslib.Logic.PL.CNF.Sparsification diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingSparsification.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingSparsification.lean new file mode 100644 index 000000000..1a12400cf --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingSparsification.lean @@ -0,0 +1,122 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.CountingSparsification +public import ComplexityTests.Computability.Complexity.FineGrained.CountingSparsifyPolyTime + +/-! +# Tests for depth-first counting-sparsification search + +Concrete execution trace and edge-case tests for `Cslib.Complexity.CountingSparsify` and +`Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule`: +* Step-by-step stack and accumulator trace of `CountingSparsify.step` on `flowerWithTautology` + (showing the `2 ^ (n - ψ.numVars)` unused-variable weighting on the unit heart leaf + `[[(0, true)]]` contributing `2 ^ (3 - 1) * 1 = 4` and the 3-unit petal/negated-heart leaf + contributing `2 ^ (3 - 3) * 1 = 1` for a total of `5 = flowerWithTautology.modelCount`); +* Full `CountingSparsify.search` evaluation on `flowerWithTautology`, the empty formula `[]` (at + `k = 0` and `k = 2`), the empty-clause formula `[[]]`, and formulas exceeding width `k`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.Tests + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification + +/-! ### 1. Step-by-step trace of `CountingSparsify.step` on `flowerWithTautology` -/ + +/-- Initial state on `flowerWithTautology`: `n = 3` declared variables, stack containing the +nontautological normal form of the 2-clause flower, and accumulator `0`. -/ +example : CountingSparsify.init 2 flowerWithTautology = + (3, ([[[(0, true), (1, true)], [(0, true), (2, false)]]], 0)) := by + decide + +/-- Step 1: the non-sparse flower is popped and replaced by its `false` child `[[(0, true)]]` and +`true` child `[[(1, true)], [(2, false)], [(0, false)]]`. -/ +example : CountingSparsify.step 2 2 CNF.modelCount (CountingSparsify.init 2 flowerWithTautology) = + (3, ([[[(0, true)]], [[(1, true)], [(2, false)], [(0, false)]]], 0)) := by + decide + +/-- Step 2: the sparse `false` child `[[(0, true)]]` (which mentions only variable `0`, so +`distinctVars.length = 1` and `modelCount = 1`) is popped and weighted by +`2 ^ (3 - 1) * 1 = 4`. -/ +example : (CountingSparsify.step 2 2 CNF.modelCount)^[2] + (CountingSparsify.init 2 flowerWithTautology) = + (3, ([[[(1, true)], [(2, false)], [(0, false)]]], 4)) := by + decide + +/-- Step 3: the sparse `true` child `[[(1, true)], [(2, false)], [(0, false)]]` (which mentions all +3 variables `0, 1, 2`, with `modelCount = 1`) is popped and weighted by `2 ^ (3 - 3) * 1 = 1`, +emptying the stack with final count `4 + 1 = 5`. -/ +example : (CountingSparsify.step 2 2 CNF.modelCount)^[3] + (CountingSparsify.init 2 flowerWithTautology) = + (3, ([], 5)) := by + decide + +/-! ### 2. Full `CountingSparsify.search` evaluation and edge cases -/ + +/-- `CountingSparsify.search` recovers all `5` satisfying assignments of `flowerWithTautology` on +its `3` occurring variables. -/ +example : CountingSparsify.search 2 2 2 2 CNF.modelCount flowerWithTautology = 5 := by + decide + +example : CountingSparsify.search 2 2 2 2 CNF.modelCount flowerWithTautology = + flowerWithTautology.modelCount := by + decide + +/-- Edge cases: empty formula at `k = 0` and `k = 2`, unsatisfiable empty-clause formula `[[]]`, +and a 3-CNF clause rejected by the `k = 2` width guard. -/ +example : CountingSparsify.search 2 2 0 1 CNF.modelCount ([] : CNF ℕ) = 1 := by + decide + +example : CountingSparsify.search 2 2 2 1 CNF.modelCount ([] : CNF ℕ) = 1 := by + decide + +example : CountingSparsify.search 2 2 2 1 CNF.modelCount ([[]] : CNF ℕ) = 0 := by + decide + +example : + CountingSparsify.search 2 2 2 1 CNF.modelCount [[(0, true), (1, true), (2, true)]] = 0 := by + decide + +/-! ### 3. Non-vacuity witness for `CountingTreeBound` at width `K = 1` -/ + +/-- At width `K = 1`, every `CountingNormalized 1` formula is already sparse because no clause of +width `≤ 1` can strictly contain a nonempty heart (`1 ≤ H.card < c ≤ 1`). Consequently the only leaf +word is `[]`, witnessing `CountingTreeBound M 1 B L_coeff` unconditionally. -/ +theorem countingTreeBound_one {M : ℕ} (hM : 1 ≤ M) (B L_coeff : ℕ) : + CountingSparsify.CountingTreeBound M 1 B L_coeff hM le_rfl := by + intro ψ declared _ + have hsparse : ClauseSet.Sparsification.Sparse (countingThreshold M) ψ.1.toClauseSet := by + intro c H hH + obtain ⟨D, hD⟩ := hH.flower_nonempty (one_le_countingThreshold hM) + have hDcard : D.card ≤ 1 := ψ.2.card_le D (ClauseSet.Sparsification.flower_subset _ _ _ hD) + have hDc : D.card = c := (ClauseSet.Sparsification.mem_flower.mp hD).2.1 + have := hH.1 + have := hH.2.1 + omega + refine ⟨fun w hw => ?_, fun W hW => ?_⟩ + · cases w with + | nil => simp + | cons b u => exact absurd hsparse hw.1 + · have hsub : W ⊆ {[]} := fun w hw => by + rw [Finset.mem_singleton] + cases w with + | nil => rfl + | cons b u => exact absurd hsparse (hW _ hw).1 + calc W.card ≤ ({[]} : Finset (List Bool)).card := Finset.card_le_card hsub + _ = 1 := Finset.card_singleton [] + _ ≤ 2 ^ (declared.card / B + 1) := Nat.one_le_two_pow + +example (φ : CNF ℕ) (hφ : ∀ c ∈ φ, c.length ≤ 1) : + CountingSparsify.search 2 1 1 1 CNF.modelCount φ = φ.modelCount := + CountingSparsify.search_eq_modelCount (by omega) (by omega) (by omega) le_rfl + (countingTreeBound_one (by omega) 1 1) (fun _ _ _ => rfl) hφ + +end Cslib.Complexity.Tests From f4cf04501b9f2775028ccfe257336cb822d994c4 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 22:51:28 -0400 Subject: [PATCH 11/24] feat: prove immediate counting payload survival --- Complexity.lean | 1 + .../Sparsification/CountingSurvival.lean | 178 ++++++++++++++++++ .../SparsificationCountingSurvival.lean | 146 ++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSurvival.lean create mode 100644 ComplexityTests/SparsificationCountingSurvival.lean diff --git a/Complexity.lean b/Complexity.lean index 4dff02f59..2740c3a89 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2171,6 +2171,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSurvival.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSurvival.lean new file mode 100644 index 000000000..d10d9deae --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSurvival.lean @@ -0,0 +1,178 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion + +/-! +# Immediate survival in counting sparsification + +Appendix A.1, Lemma A.1 [OpenAI2026ETH] shows that the heart and petals are new and survive +immediate reduction. For the B-child, nontautology prevents its additional negated-heart units +from subsuming a petal. This module proves that fact for the actual `countingChild`, and preserves +the pointwise absence of complementary literals along actual counting paths. + +The local result needs only a heavy heart, positive thresholds and a reduced nontautological +parent; neither priority in `Choice` is needed. At width at least two, the full counting payload +consists of the heart or petals, so all its clauses survive and are absent from the current node. +The root-subclause invariant additionally excludes the root along a path. The resulting equality +identifies these insertion events with new clauses present in the next node. It does not sum +events or prove the complete Appendix A.2 recurrence, depth, leaf count or native runtime. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.nontautological_countingChild`: preservation at one step. +* `Cslib.Logic.PL.ClauseSet.Sparsification.nontautological_counting_node`: path preservation. +* `Cslib.Logic.PL.ClauseSet.Sparsification.intro_subset_countingChild`: heart and petal survival. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingIntro_filter_eq_node_sdiff`: higher-width + insertion events are exactly the new marked clauses present in the next node. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.1, Lemma A.1][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-- A proper heavy-heart step preserves the absence of complementary literals within each +clause. This concerns clauses individually, not consistency between different unit clauses. -/ +theorem nontautological_countingChild {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} + (hnt : ∀ C ∈ ψ, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (h : Heavy θ ψ c H) (b : Bool) : + ∀ C ∈ countingChild ψ c H b, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + intro C hC l hl hneg + rw [countingChild_eq_reduce_union] at hC + rcases mem_union.mp (reduce_subset _ hC) with hC | hC + · exact hnt C hC l hl hneg + · have hcore : C ∈ intro ψ c H b ∨ C ∈ negatedHeartUnits H := by + cases b with + | false => exact Or.inl hC + | true => exact mem_union.mp hC + rcases hcore with hcore | hunit + · obtain ⟨D, hD, hCD⟩ := exists_flower_ssuperset hθ h hcore + exact hnt D (flower_subset _ _ _ hD) l (hCD.subset hl) (hCD.subset hneg) + · obtain ⟨a, -, rfl⟩ := mem_image.mp hunit + rw [mem_singleton] at hl hneg + subst l + rcases a with ⟨v, p⟩ + cases p <;> simp [Std.Sat.Literal.negate] at hneg + +/-- Nontautology at the root is preserved along actual counting children. There is no finite +ambient variable type or satisfiability premise, and heaviness suffices in place of `Choice`. -/ +theorem nontautological_counting_node {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + {t : ℕ} (ht : t ≤ N) : + ∀ C ∈ ψ t, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + induction t with + | zero => exact hnt + | succ t ih => + rw [hstep t (by omega)] + exact nontautological_countingChild hθ (ih (by omega)) (hheavy t (by omega)) (b t) + +/-- Every heart or petal survives immediate counting reduction. This includes unit petals but +does not refer to the B-child's additional negated-heart units. Nontautology prevents those units +from subsuming any petal. -/ +theorem intro_subset_countingChild {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (hred : Reduced ψ) + (hnt : ∀ C ∈ ψ, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (h : Heavy θ ψ c H) (b : Bool) : intro ψ c H b ⊆ countingChild ψ c H b := by + intro a ha + rw [countingChild_eq_reduce_union, mem_reduce] + have haI : a ∈ countingIntro ψ c H b := by + cases b with + | false => exact ha + | true => exact mem_union_left _ ha + refine ⟨mem_union_right _ haI, fun D hD hDa => ?_⟩ + obtain ⟨C, hC, haC⟩ := exists_flower_ssuperset hθ h ha + rcases mem_union.mp hD with hD | hD + · exact hred C (flower_subset _ _ _ hC) D hD (hDa.trans haC) + · cases b with + | false => + have haH : a = H := mem_singleton.mp ha + have hDH : D = H := mem_singleton.mp hD + exact (ne_of_ssubset hDa) (hDH.trans haH.symm) + | true => + rcases mem_union.mp hD with hD | hD + · have hlt := card_lt_card hDa + rw [card_of_mem_petals hD, card_of_mem_petals ha] at hlt + exact lt_irrefl _ hlt + · obtain ⟨l, hl, rfl⟩ := mem_image.mp hD + exact hnt C (flower_subset _ _ _ hC) l ((mem_flower.mp hC).2.2 hl) + (haC.subset (hDa.subset (mem_singleton_self _))) + +/-- Every heart or petal is absent from a reduced parent. This newness fact does not require +nontautology; the strict flower superset already rules out membership in the old antichain. -/ +theorem notMem_of_mem_intro {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (hred : Reduced ψ) + (h : Heavy θ ψ c H) {b : Bool} {a : Finset (Literal V)} + (ha : a ∈ intro ψ c H b) : a ∉ ψ := by + obtain ⟨C, hC, haC⟩ := exists_flower_ssuperset hθ h ha + exact fun haψ => hred C (flower_subset _ _ _ hC) a haψ haC + +/-- At width at least two, the counting payload contains only the heart or petals. +The B-child's negated-heart units have width one and are excluded by this hypothesis. -/ +theorem mem_intro_of_mem_countingIntro {ψ : ClauseSet V} {c : ℕ} + {H : Finset (Literal V)} {b : Bool} {a : Finset (Literal V)} + (ha : a ∈ countingIntro ψ c H b) (ha2 : 2 ≤ a.card) : a ∈ intro ψ c H b := by + cases b with + | false => exact ha + | true => + rcases mem_union.mp ha with ha | ha + · exact ha + · obtain ⟨l, -, rfl⟩ := mem_image.mp ha + simp only [card_singleton] at ha2 + omega + +/-- Along an actual counting path, every width-`j ≥ 2` payload clause survives and is absent +from both the current node and the root. Thus its insertion event is exactly a new marked clause +present in the next node. No survival or insertion-balance premise is assumed. -/ +theorem countingIntro_filter_eq_node_sdiff {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + {m : ℕ} (hm : m < N) {j : ℕ} (hj : 2 ≤ j) : + (countingIntro (ψ m) (c m) (H m) (b m)).filter (·.card = j) = + (ψ (m + 1) \ (ψ m ∪ ψ 0)).filter (·.card = j) := by + have hredm : Reduced (ψ m) := by + cases m with + | zero => exact hred + | succ m => + rw [hstep m (by omega), countingChild_eq_reduce_union] + exact reduced_reduce _ + have hntm := nontautological_counting_node hθ N ψ c H b hstep hheavy hnt hm.le + ext a + simp only [mem_filter, mem_sdiff, mem_union] + constructor + · rintro ⟨ha, haj⟩ + have hai := mem_intro_of_mem_countingIntro ha (haj ▸ hj) + have haψ := notMem_of_mem_intro hθ hredm (hheavy m hm) hai + have haroot : a ∉ ψ 0 := by + intro ha0 + obtain ⟨D, hD, hDa⟩ := exists_mem_counting_node_subset N ψ c H b hstep hm.le ha0 + obtain ⟨C, hC, haC⟩ := exists_flower_ssuperset hθ (hheavy m hm) hai + exact hredm C (flower_subset _ _ _ hC) D hD + (Finset.ssubset_of_subset_of_ssubset hDa haC) + refine ⟨⟨?_, fun h => h.elim haψ haroot⟩, haj⟩ + rw [hstep m hm] + exact intro_subset_countingChild hθ hredm hntm (hheavy m hm) (b m) hai + · rintro ⟨⟨ha, hnot⟩, haj⟩ + rw [hstep m hm, countingChild_eq_reduce_union] at ha + exact ⟨(mem_union.mp (reduce_subset _ ha)).resolve_left (fun h => hnot (Or.inl h)), haj⟩ + +end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/SparsificationCountingSurvival.lean b/ComplexityTests/SparsificationCountingSurvival.lean new file mode 100644 index 000000000..701de92e4 --- /dev/null +++ b/ComplexityTests/SparsificationCountingSurvival.lean @@ -0,0 +1,146 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival + +public section + +open Cslib.Logic.PL +open ClauseSet.Sparsification Finset + +namespace ComplexityTests.SparsificationCountingSurvival + +private def θ (j : ℕ) := 2 ^ j + +private theorem θpos (j : ℕ) : 1 ≤ θ j := Nat.one_le_pow j 2 (by decide) + +-- Mixed polarities, a two-literal heart, and four disjoint two-literal petals. One of the +-- negated-heart units is already in the root, so payload is not the same as new unit events. +private def root : ClauseSet (Fin 10) := + {{(0, false)}, {(0, true), (1, false), (2, true), (3, false)}, + {(0, true), (1, false), (4, true), (5, false)}, + {(0, true), (1, false), (6, true), (7, false)}, + {(0, true), (1, false), (8, true), (9, false)}} + +private def heart : Finset (Literal (Fin 10)) := {(0, true), (1, false)} + +private theorem rootReduced : Reduced root := by unfold Reduced; decide + +private theorem rootNontautological : + ∀ C ∈ root, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by decide + +private theorem rootHeavy : Heavy θ root 4 heart := by unfold Heavy; decide + +private def nodes (b : Bool) (i : ℕ) := if i = 0 then root else countingChild root 4 heart b + +private theorem steps (b : Bool) : ∀ i < 1, + nodes b (i + 1) = countingChild (nodes b i) 4 heart b := by + intro i hi + have : i = 0 := by omega + subst i + rfl + +private theorem heavies (b : Bool) : ∀ i < 1, Heavy θ (nodes b i) 4 heart := by + intro i hi + have : i = 0 := by omega + subst i + exact rootHeavy + +-- Apply the path theorem, then count its concrete insertion batch: A inserts one width-two +-- heart; B inserts four width-two petals. This exercises the equality in both directions. +example : ((nodes false 1 \ (nodes false 0 ∪ nodes false 0)).filter (·.card = 2)).card = 1 := by + rw [← countingIntro_filter_eq_node_sdiff θpos 1 (nodes false) (fun _ => 4) + (fun _ => heart) (fun _ => false) (steps false) (heavies false) + rootReduced rootNontautological (m := 0) (by decide) (by decide)] + decide + +example : ((nodes true 1 \ (nodes true 0 ∪ nodes true 0)).filter (·.card = 2)).card = 4 := by + rw [← countingIntro_filter_eq_node_sdiff θpos 1 (nodes true) (fun _ => 4) + (fun _ => heart) (fun _ => true) (steps true) (heavies true) + rootReduced rootNontautological (m := 0) (by decide) (by decide)] + decide + +-- The property is clausewise: preservation does not require a satisfying assignment. +example : ∀ C ∈ nodes false 1, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + exact nontautological_counting_node θpos 1 (nodes false) (fun _ => 4) + (fun _ => heart) (fun _ => false) (steps false) (heavies false) + rootNontautological (t := 1) le_rfl + +example : ∀ C ∈ nodes true 1, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + exact nontautological_countingChild θpos rootNontautological rootHeavy true + +-- Existing negated units belong to the raw payload but are not new insertions. Extending the +-- event equality from widths at least two to width one would fail in this same legal example. +example : ((countingIntro root 4 heart true).filter (·.card = 1)).card = 2 := by decide +example : ((nodes true 1 \ root).filter (·.card = 1)).card = 1 := by decide +example : {(0, false)} ∈ nodes true 1 ∧ {(0, false)} ∈ root := by decide + +-- A one-literal petal is also covered by core survival, even though the event equality above +-- deliberately separates width-one accounting. +theorem unit_petal_survives : {(2, true)} ∈ countingChild + ({{(0, true), (2, true)}, {(0, true), (3, false)}} : ClauseSet (Fin 4)) + 2 {(0, true)} true := by + apply intro_subset_countingChild θpos (by unfold Reduced; decide) + (by decide) (by unfold Heavy; decide) true + decide + +-- A later step has a current node different from the root. It removes the remaining old +-- width-three clause and introduces a width-two heart, while the earlier negated unit persists. +private def chainRoot : ClauseSet ℕ := {{(0, true), (1, true), (2, true), (3, true)}} + +private def chainFirst := countingChild chainRoot 4 {(0, true)} true + +private def chainNodes (i : ℕ) := + if i = 0 then chainRoot else if i = 1 then chainFirst + else countingChild chainFirst 3 {(1, true), (2, true)} false + +private def chainSizes (i : ℕ) := if i = 0 then 4 else 3 +private def chainHearts (i : ℕ) : Finset (Literal ℕ) := + if i = 0 then {(0, true)} else {(1, true), (2, true)} +private def chainDirs (i : ℕ) := i == 0 + +private theorem chainSteps : ∀ i < 2, chainNodes (i + 1) = + countingChild (chainNodes i) (chainSizes i) (chainHearts i) (chainDirs i) := by + intro i hi + have : i = 0 ∨ i = 1 := by omega + rcases this with rfl | rfl <;> rfl + +private theorem chainHeavies : ∀ i < 2, + Heavy (fun _ => 1) (chainNodes i) (chainSizes i) (chainHearts i) := by + intro i hi + have : i = 0 ∨ i = 1 := by omega + rcases this with rfl | rfl <;> unfold Heavy <;> decide + +example : ((chainNodes 2 \ (chainNodes 1 ∪ chainNodes 0)).filter (·.card = 2)).card = 1 := by + rw [← countingIntro_filter_eq_node_sdiff (fun _ => le_rfl) 2 chainNodes chainSizes + chainHearts chainDirs chainSteps chainHeavies (by unfold Reduced; decide) + (by decide) (m := 1) (by decide) (by decide)] + decide + +example : {(0, false)} ∈ chainNodes 2 ∧ {(1, true), (2, true)} ∈ chainNodes 2 := by decide + +-- Reducedness is essential: a pre-existing unit immediately subsumes the new width-two petal. +private def unreduced : ClauseSet (Fin 3) := + {{(1, true)}, {(0, true), (1, true), (2, true)}} + +example : ∀ C ∈ unreduced, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by decide +example : Heavy (fun _ => 1) unreduced 3 {(0, true)} := by unfold Heavy; decide +example : ¬ Reduced unreduced := by unfold Reduced; decide +example : {(1, true), (2, true)} ∈ intro unreduced 3 {(0, true)} true ∧ + {(1, true), (2, true)} ∉ countingChild unreduced 3 {(0, true)} true := by decide + +-- The independent tautological full-Choice 4-payload/3-survivor counterexample remains in +-- SparsificationCountingDeletion. It is unchanged and checked alongside this module. + +-- No ambient variable or nonempty-formula hypothesis at a zero-length prefix. +example : ∀ C ∈ ({∅} : ClauseSet Empty), ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + exact nontautological_counting_node θpos 0 (fun _ => ({∅} : ClauseSet Empty)) + (fun _ => 0) (fun _ => ∅) (fun _ => false) (by intro i hi; omega) + (by intro i hi; omega) (by simp) (t := 0) le_rfl + +end ComplexityTests.SparsificationCountingSurvival From 712d9dc98169da0465c6f6b7d3acd90d4d9c2cca Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 03:07:12 +0000 Subject: [PATCH 12/24] fix(ClauseSet,Complexity): reuse CountingTree termination and clarify resource/source scope --- .../FineGrained/CountingSparsification.lean | 68 ++++++++----- .../Sparsification/CountingSearch.lean | 98 +++---------------- 2 files changed, 60 insertions(+), 106 deletions(-) diff --git a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean index a73a91be7..bc712e30a 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean @@ -24,10 +24,13 @@ import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain # Algorithmic counting sparsification on native machines Implements the depth-first counting-sparsification search -(`Cslib.Complexity.CountingSparsify.search`) of Appendix A and Section 6.4, Proposition 6.4 +(`Cslib.Complexity.CountingSparsify.search`) for Lemma 6.3, Proposition 6.4, and Appendix A [OpenAI2026ETH] on the native multi-tape Turing machines, parallel to the decision sparsifier `Cslib.Complexity.Sparsify.search` -(`Complexity/Computability/Complexity/FineGrained/Sparsification.lean`). +(`Complexity/Computability/Complexity/FineGrained/Sparsification.lean`). In Lemma 6.3 (Section 6.4, +printed p. 43), *"The input count is the sum of the leaf counts"*, obtained in Appendix A (printed +p. 44) by *"repeatedly splitting a formula into two cases. Both children use the same n variables, +and every satisfying assignment of the parent satisfies exactly one child."* Given a formula `φ : CNF ℕ` with `n = φ.numVars` occurring variables, the search maintains a state `(n, (stk, acc))` (`CountingSparsify.State`, with `n` in unary and `acc` in binary capped at @@ -45,12 +48,21 @@ Given a formula `φ : CNF ℕ` with `n = φ.numVars` occurring variables, the se disjoint counting children `countingNext (countingThreshold M) K ψ false` and `countingNext (countingThreshold M) K ψ true` (Appendix A.1, equation (A.2), pp. 44–45). -Under the quantitative tree depth and leaf-count bounds of Appendix A.2–A.3 (`CountingTreeBound`), -running `step M K g` for `rounds B φ = 2 ^ (n / B + 2)` steps visits every leaf of the -counting-sparsification tree and returns `φ.modelCount` (`CountingSparsify.search_eq_modelCount`), -within time `O(2 ^ ((1 / B + occBound K M * δ) * n) * (|encCNF φ| + 1) ^ E)` whenever `g` counts -sparse width-`≤ k` formulas in time `O(2 ^ (δ * m) * (|encCNF ψ| + 1) ^ e)` -(`CountingSparsify.exists_computableInO_search`). +To mirror `Sparsify`, this module packages the quantitative tree depth (`w.length ≤ L_coeff * N`) +and fixed-parameter leaf-count (`W.card ≤ 2 ^ (N / B + 1)`) hypotheses into `CountingTreeBound`, a +library adaptation of Appendix A.2–A.3 (Lemmas A.2–A.3, pp. 46–47) that remains an open +quantitative obligation for `K ≥ 2` (unit insertion accounting and finite recursive leaf partition +are prerequisites, with higher-width occurrence, deletion, survival, and leaf-recurrence bounds +handled separately). Under `CountingTreeBound`, running `step M K g` for +`rounds B φ = 2 ^ (n / B + 2)` steps visits every leaf of the counting-sparsification tree and +returns `φ.modelCount` (`CountingSparsify.search_eq_modelCount`). Given a total leaf routine `g` +with `hg : ComputableInO g encCNF encNat t t` and a global bound `ht` holding for every +`ψ : CNF ℕ` (`(t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e) + c`), +`CountingSparsify.exists_computableInO_search` yields a combined time-and-space certificate +`ComputableInO (search M K k B g) encCNF encNat T T` with the fixed-parameter library-adaptation +transfer bound `T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * n) * (|encCNF φ| + 1) ^ E) + C₀`. +Because `hg` bounds both time and space of `g` by `t` (which may be exponential) and the step space +bound includes `leafTime`, no separate polynomial leaf-workspace certificate is claimed here. ## Main definitions @@ -61,21 +73,21 @@ sparse width-`≤ k` formulas in time `O(2 ^ (δ * m) * (|encCNF ψ| + 1) ^ e)` counting-sparsification search. * `Cslib.Complexity.CountingSparsify.occBound`: the sparse leaf clause-density bound `2 * ∑ j ∈ Finset.range K, countingThreshold M j`. -* `Cslib.Complexity.CountingSparsify.CountingTreeBound`: the Appendix A.2–A.3 depth and leaf-count - hypotheses for `countingRule`. +* `Cslib.Complexity.CountingSparsify.CountingTreeBound`: the fixed-parameter depth and leaf-count + hypotheses for `countingRule` (a library adaptation of Appendix A.2–A.3). ## Main results * `Cslib.Complexity.CountingSparsify.search_eq_modelCount`, `Cslib.Complexity.CountingSparsify.search_eq_ite_modelCount`: the search computes `φ.modelCount` on formulas of width `≤ k` (and `0` otherwise). -* `Cslib.Complexity.CountingSparsify.exists_computableInO_search`: native `ComputableInO` time and - space certificate for `CountingSparsify.search`. +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search`: native `ComputableInO ... T T` + time-and-space transfer certificate for `CountingSparsify.search`. ## References * [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Section 6.4, - Proposition 6.4 (pp. 43–44) and Appendix A (pp. 44–47). + Lemma 6.3 and Proposition 6.4 (pp. 43–44) and Appendix A (pp. 44–47). -/ @[expose] public section @@ -331,8 +343,9 @@ end Tree /-! ### The run of the search -/ -/-- The quantitative depth and leaf-count bounds for the counting-sparsification tree -(Appendix A.2–A.3 [OpenAI2026ETH]): from any `CountingNormalized K` root formula `ψ` whose +/-- The fixed-parameter quantitative depth and leaf-count hypotheses for the +counting-sparsification tree (a library adaptation of Appendix A.2–A.3, Lemmas A.2–A.3, pp. 46–47 +[OpenAI2026ETH], open for `K ≥ 2`): from any `CountingNormalized K` root formula `ψ` whose occurring variables lie in a finite set `declared` of size `N = declared.card`, every leaf word of `rule hM hK` has length at most `L_coeff * N`, and any finset of leaf words has cardinality at most `2 ^ (N / B + 1)`. -/ @@ -370,7 +383,7 @@ private theorem iterate_step (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) have hn_eq : n = φ.numVars := CNF.length_distinctVars φ obtain ⟨hlen, hWbound⟩ := hTB s₀ φ.vars.toFinset mem_vars_of_mem_root obtain ⟨W, hW, N, hN, h⟩ := CountingBranchingRule.exists_iterate_searchStep (R := R) - (one_le_countingThreshold hM) (fun _ => two_le_countingThreshold hM2) + (fun _ => two_le_countingThreshold hM2) (2 ^ n) (fun t => leafWeight n t.1 (g t.1)) s₀ hlen have hWcard : W.card ≤ 2 ^ (φ.numVars / B + 1) := hWbound W fun w hw => (hW w).mp hw @@ -628,10 +641,12 @@ private noncomputable def leafTime (K M L_coeff : ℕ) (δ : ℝ) (c e : ℕ) ( ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c⌉₊ include hM hK in -/-- **The time of the rounds.** Under `CountingTreeBound`, the counting-sparsification search is -computed within `rounds B φ + 1` times the bound on a round: a polynomial in `stateBound`, plus the -time bound of the leaf counter on the sparse leaves (`leafTime`), plus the cost of the binary round -counter; and within polynomial space for a round. -/ +/-- **The time and space of the rounds.** Under `CountingTreeBound`, given a total leaf routine `g` +with `hg : ComputableTidilyInO g encCNF encNat t t` and global bound `ht` holding for every +`ψ : CNF ℕ`, `search M K k B g` runs within time equal to initialization plus `rounds B φ + 1` +times the per-round bound (a polynomial in `stateBound`, plus `leafTime`, plus the binary +round-counter step cost), and within space equal to initialization/round-count space plus the +per-round bound (including `leafTime`, since `hg` bounds both time and space of `g` by `t`). -/ private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableTidilyInO g encCNF encNat t t) @@ -712,10 +727,15 @@ private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : (ComputableTidilyInO.iterate hn.inO hi.inO hstep hround hround hstate).snd'.snd'⟩ include hM hK in -/-- **The time of the counting-sparsification search.** With a leaf model counter `g` running -within time `O(2 ^ (δ * m) * (|encCNF ψ| + 1) ^ e)` on formulas with `m` clauses and bounded by -`2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` is computable within time -`O(2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ +/-- **The time and space of the counting-sparsification search.** Given a total leaf routine `g` +with `hg : ComputableInO g encCNF encNat t t` whose time and space `t ψ` satisfy the global bound +`ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : CNF ℕ`, and whose output is at +most `2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` has a combined +time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the +fixed-parameter transfer bound +`T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀` (a library +adaptation of the leaf-to-root runtime assembly, without an independent polynomial-space +certificate). -/ theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean index 9e4db331b..7c5c6629c 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingSearch.lean @@ -8,11 +8,10 @@ module -- upstream: candidate public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds import Mathlib.Algebra.BigOperators.Group.Finset.Basic -import Mathlib.Algebra.Group.Action.Defs import Mathlib.Tactic.Ring -import Mathlib.Tactic.Positivity /-! # Counting sparsification -- branching rules and depth-first tree search @@ -25,7 +24,7 @@ which at every state that is not sparse takes the heart (`false`) or petal-plus- When all thresholds for `j ≥ 1` are at least `2` (in particular for `countingThreshold M` with `M ≥ 2`), every non-sparse step strictly decreases the clause-set potential -`potential ψ = ∑ C ∈ ψ, 2 ^ C.card` (`potential_countingChild_lt`, +`potential ψ = ∑ C ∈ ψ, 2 ^ C.card` (`potential_countingChild_lt` from `CountingTree.lean`, `CountingBranchingRule.potential_next_lt`): removing a flower of `|F| ≥ 2` clauses of width `c` decreases the potential by `|F| · 2 ^ c`, while adding the `|F|` petals of width `c - |H| ≤ c - 1` and `|H| ≤ c - 1` unit clauses adds at most `|F| · 2 ^ (c - 1) + 2 (c - 1) < |F| · 2 ^ c`. @@ -58,8 +57,7 @@ At the list level, `Cslib.Logic.PL.CNF.Sparsification.countingRule` instantiates ## Main results -* `Cslib.Logic.PL.ClauseSet.Sparsification.potential_countingChild_lt`, - `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.potential_next_lt`: strict +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.potential_next_lt`: strict potential decrease on both counting branches when `∀ j, 1 ≤ j → 2 ≤ θ j`. * `Cslib.Logic.PL.ClauseSet.Sparsification.card_countingChild_le`, `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsNodeWord.card_walk_le`: @@ -140,70 +138,6 @@ private theorem card_of_mem_negatedHeartUnits {H : Finset (Literal V)} {C : Fins obtain ⟨l, -, rfl⟩ := mem_image.mp hC exact card_singleton _ -/-- The potential of `negatedHeartUnits H` is at most `2 * H.card`. -/ -private theorem potential_negatedHeartUnits_le (H : Finset (Literal V)) : - potential (negatedHeartUnits H) ≤ H.card * 2 := by - unfold potential - rw [sum_congr rfl fun C hC => by rw [card_of_mem_negatedHeartUnits hC], sum_const, smul_eq_mul, - pow_one] - exact Nat.mul_le_mul_right 2 card_image_le - -/-- **Termination of counting sparsification.** When `∀ j, 1 ≤ j → 2 ≤ θ j`, each counting -branching step on a heavy heart strictly decreases the potential `∑ C ∈ ψ, 2 ^ C.card`. -/ -theorem potential_countingChild_lt (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) - {ψ : ClauseSet V} {c : ℕ} {H : Finset (Literal V)} (h : Heavy θ ψ c H) (b : Bool) : - potential (countingChild ψ c H b) < potential ψ := by - cases b with - | false => exact potential_child_lt hθ1 h false - | true => - set F := flower ψ c H - have hFsplit : potential ψ = potential (ψ \ F) + potential F := by - unfold potential - rw [← sum_sdiff (flower_subset ψ c H)] - have hF : potential F = F.card * 2 ^ c := by - unfold potential - rw [sum_congr rfl fun C hC => by rw [(mem_flower.mp hC).2.1], sum_const, smul_eq_mul] - have hF2 : 2 ≤ F.card := (hθ2 (c - H.card) (by have := h.2.1; omega)).trans h.2.2 - have hpetals : potential (petals ψ c H) = F.card * 2 ^ (c - H.card) := by - unfold potential - rw [sum_congr rfl fun C hC => by rw [card_of_mem_petals hC], sum_const, card_petals, - smul_eq_mul] - have hunits := potential_negatedHeartUnits_le H - have hc1 : c = (c - 1) + 1 := by have := h.1; have := h.2.1; omega - have hpow_le : 2 ^ (c - H.card) ≤ 2 ^ (c - 1) := - Nat.pow_le_pow_right (by omega) (by have := h.1; omega) - have hH_lt : H.card < 2 ^ (c - 1) := - (show H.card ≤ c - 1 by have := h.2.1; omega).trans_lt Nat.lt_two_pow_self - have hsum_lt : potential (petals ψ c H) + potential (negatedHeartUnits H) < F.card * 2 ^ c := by - have h1 : potential (petals ψ c H) ≤ F.card * 2 ^ (c - 1) := by - rw [hpetals] - exact Nat.mul_le_mul_left F.card hpow_le - have h2 : potential (negatedHeartUnits H) < 2 * 2 ^ (c - 1) := by omega - have h3 : 2 * 2 ^ (c - 1) ≤ F.card * 2 ^ (c - 1) := - Nat.mul_le_mul_right _ hF2 - have h4 : F.card * 2 ^ c = F.card * 2 ^ (c - 1) + F.card * 2 ^ (c - 1) := by - conv_lhs => rw [hc1, pow_succ] - ring - omega - calc potential (countingChild ψ c H true) - ≤ potential ((ψ \ F) ∪ petals ψ c H ∪ negatedHeartUnits H) := - sum_le_sum_of_subset (countingChild_true_subset h) - _ ≤ potential ((ψ \ F) ∪ petals ψ c H) + potential (negatedHeartUnits H) := by - unfold potential - have := sum_union_inter (s₁ := (ψ \ F) ∪ petals ψ c H) (s₂ := negatedHeartUnits H) - (f := fun C : Finset (Literal V) => 2 ^ C.card) - omega - _ ≤ potential (ψ \ F) + potential (petals ψ c H) + potential (negatedHeartUnits H) := by - have : potential ((ψ \ F) ∪ petals ψ c H) ≤ - potential (ψ \ F) + potential (petals ψ c H) := by - unfold potential - have := sum_union_inter (s₁ := ψ \ F) (s₂ := petals ψ c H) - (f := fun C : Finset (Literal V) => 2 ^ C.card) - omega - omega - _ < potential (ψ \ F) + potential F := by rw [hF]; omega - _ = potential ψ := hFsplit.symm - /-- Counting children keep the clause width `≤ k` when `1 ≤ k`. -/ theorem card_le_of_mem_countingChild (hθ : ∀ j, 1 ≤ θ j) {ψ : ClauseSet V} {c k : ℕ} (hk1 : 1 ≤ k) {H : Finset (Literal V)} (h : Heavy θ ψ c H) (hk : ∀ C ∈ ψ, C.card ≤ k) @@ -300,12 +234,12 @@ variable {R} /-- **Termination.** When `∀ j, 1 ≤ j → 2 ≤ θ j`, each counting branching step strictly decreases the potential. -/ -theorem potential_next_lt (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) {s : σ} +theorem potential_next_lt (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) {s : σ} (hs : ¬ Sparse θ (R.toClauseSet s)) (b : Bool) : potential (R.toClauseSet (R.next s b)) < potential (R.toClauseSet s) := by obtain ⟨c, H, hch, hnext⟩ := R.exists_choice_next s hs rw [hnext] - exact potential_countingChild_lt hθ1 hθ2 hch.1 b + exact potential_countingChild_lt hθ hch.1 b /-- **Additive model-count conservation.** At any non-sparse state, the declared-variable model count equals the sum of the model counts of its two children. -/ @@ -338,14 +272,14 @@ private theorem IsLeafWord.eq_nil {s : σ} (hs : Sparse θ (R.toClauseSet s)) {w | nil => rfl | cons b w => exact absurd hs hw.1 -/-- Every state has a leaf word when `∀ j, 1 ≤ θ j` and `∀ j, 1 ≤ j → 2 ≤ θ j`. -/ -theorem exists_isLeafWord (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) (s : σ) : +/-- Every state has a leaf word when `∀ j, 1 ≤ j → 2 ≤ θ j`. -/ +theorem exists_isLeafWord (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) (s : σ) : ∃ w, R.IsLeafWord s w := by induction h : potential (R.toClauseSet s) using Nat.strong_induction_on generalizing s with | _ n ih => by_cases hs : Sparse θ (R.toClauseSet s) · exact ⟨[], hs⟩ - · obtain ⟨w, hw⟩ := ih _ (h ▸ potential_next_lt hθ1 hθ2 hs false) (R.next s false) rfl + · obtain ⟨w, hw⟩ := ih _ (h ▸ potential_next_lt hθ hs false) (R.next s false) rfl exact ⟨false :: w, hs, hw⟩ /-- Node words preserve clause width `≤ k` when `1 ≤ k`. -/ @@ -410,7 +344,7 @@ open Classical in of `s`) to return to `rest`, with accumulator `min cap (acc + ∑ w ∈ W, f (R.walk s w))`. If all leaf words of `s` have length at most `h`, the stack above `rest` has height at most `h + 1` at every intermediate step `i ≤ N` and the accumulator remains at most `cap`. -/ -theorem exists_iterate_searchStep (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) +theorem exists_iterate_searchStep (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) (cap : ℕ) (f : σ → ℕ) (s : σ) {h : ℕ} (hh : ∀ w, R.IsLeafWord s w → w.length ≤ h) : ∃ W : Finset (List Bool), (∀ w, w ∈ W ↔ R.IsLeafWord s w) ∧ ∃ N, N + 1 ≤ 2 * W.card ∧ ∀ rest acc, acc ≤ cap → @@ -433,12 +367,12 @@ theorem exists_iterate_searchStep (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 · refine ⟨[], min cap (acc + f s), ?_, by simp, Nat.min_le_left _ _, by simp⟩ simp only [Function.iterate_one, searchStep, hs, ↓reduceIte, List.nil_append] · have hlt (b : Bool) : potential (R.toClauseSet (R.next s b)) < n := - hp ▸ potential_next_lt hθ1 hθ2 hs b + hp ▸ potential_next_lt hθ hs b have hh' (b : Bool) : ∀ w, R.IsLeafWord (R.next s b) w → w.length ≤ h - 1 := fun w hw => by have := hh (b :: w) ⟨hs, hw⟩ simp only [List.length_cons] at this omega - obtain ⟨u, hu⟩ := exists_isLeafWord (R := R) hθ1 hθ2 s + obtain ⟨u, hu⟩ := exists_isLeafWord (R := R) hθ s have hh1 : 1 ≤ h := by cases u with | nil => exact absurd hu hs @@ -515,7 +449,7 @@ theorem exists_iterate_searchStep (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 (`M t = M (R.next t false) + M (R.next t true)`) and `f` agrees with `M` on the leaves of `s`, then the sum of `f (R.walk s w)` over any finset `W` containing exactly the leaf words of `s` equals `M s`. -/ -theorem sum_leafWords_eq (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) +theorem sum_leafWords_eq (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) {M f : σ → ℕ} (hM : ∀ t, ¬ Sparse θ (R.toClauseSet t) → M t = M (R.next t false) + M (R.next t true)) (s : σ) (hf : ∀ w, R.IsLeafWord s w → f (R.walk s w) = M (R.walk s w)) @@ -532,7 +466,7 @@ theorem sum_leafWords_eq (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 rw [hWeq, sum_singleton] exact hf [] hs · have hlt (b : Bool) : potential (R.toClauseSet (R.next s b)) < n := - hp ▸ potential_next_lt hθ1 hθ2 hs b + hp ▸ potential_next_lt hθ hs b let W₀ := (W.filter fun w => w.head? = some false).image List.tail let W₁ := (W.filter fun w => w.head? = some true).image List.tail have hW₀ (u : List Bool) : u ∈ W₀ ↔ R.IsLeafWord (R.next s false) u := by @@ -590,13 +524,13 @@ theorem sum_leafWords_eq (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 /-- **Leaf model-count partition.** If `f` computes `(R.toClauseSet ·).toCNF.modelCountOn declared` on every leaf of `s`, then the sum of `f (R.walk s w)` over the leaf words of `s` equals `(R.toClauseSet s).toCNF.modelCountOn declared`. -/ -theorem sum_leafWords_modelCountOn (hθ1 : ∀ j, 1 ≤ θ j) (hθ2 : ∀ j, 1 ≤ j → 2 ≤ θ j) +theorem sum_leafWords_modelCountOn (hθ : ∀ j, 1 ≤ j → 2 ≤ θ j) (declared : Finset V) {f : σ → ℕ} (s : σ) (hf : ∀ w, R.IsLeafWord s w → f (R.walk s w) = (R.toClauseSet (R.walk s w)).toCNF.modelCountOn declared) {W : Finset (List Bool)} (hW : ∀ w, w ∈ W ↔ R.IsLeafWord s w) : ∑ w ∈ W, f (R.walk s w) = (R.toClauseSet s).toCNF.modelCountOn declared := - sum_leafWords_eq hθ1 hθ2 (fun _ ht => modelCountOn_next ht declared) s hf hW + sum_leafWords_eq hθ (fun _ ht => modelCountOn_next ht declared) s hf hW end CountingBranchingRule @@ -648,7 +582,7 @@ theorem sum_leafWords_countingRule_modelCountOn {θ : ℕ → ℕ} (hθ1 : ∀ j {W : Finset (List Bool)} (hW : ∀ w, w ∈ W ↔ (countingRule hθ1 hk).IsLeafWord s w) : ∑ w ∈ W, f ((countingRule hθ1 hk).walk s w) = s.1.modelCountOn declared := CountingBranchingRule.sum_leafWords_eq (R := countingRule hθ1 hk) - (M := fun t => t.1.modelCountOn declared) hθ1 hθ2 + (M := fun t => t.1.modelCountOn declared) hθ2 (fun t _ => modelCountOn_countingNext t.2.toNormalized declared) s hf hW end Cslib.Logic.PL.CNF.Sparsification From bcf211fdb07941bf909668fb5d7a7a109c7fc46d Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 23:15:50 -0400 Subject: [PATCH 13/24] feat(sparsification): bound cumulative counting insertions --- Complexity.lean | 1 + .../Sparsification/CountingInsertions.lean | 288 ++++++++++++++++++ .../SparsificationCountingInsertions.lean | 173 +++++++++++ 3 files changed, 462 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingInsertions.lean create mode 100644 ComplexityTests/SparsificationCountingInsertions.lean diff --git a/Complexity.lean b/Complexity.lean index 2740c3a89..943286563 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2170,6 +2170,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingInsertions public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingInsertions.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingInsertions.lean new file mode 100644 index 000000000..b5f481443 --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingInsertions.lean @@ -0,0 +1,288 @@ +/- +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.ClauseSet.Sparsification.CountingSurvival +import Mathlib.Algebra.Group.Action.Defs +import Mathlib.Algebra.Order.BigOperators.Group.Finset +import Mathlib.Algebra.Ring.GeomSum +import Mathlib.Tactic.Ring + +/-! +# Cumulative counting-sparsification insertions + +Appendix A.2, Lemma A.3 and equation (A.3) [OpenAI2026ETH] bound actual insertion events along +every path prefix. `countingInsertionCount` sums the new payload at each step, excluding clauses +already in the current node or the root. It does not deduplicate events across steps or discard +insertions whose clauses are later deleted. + +For widths at least two, immediate survival gives the exact balance between cumulative insertions, +present marked clauses and cumulative deletions. Summing the marked-occurrence bound over the two +literals per declared variable bounds present clauses. Summing the one-step deletion charge then +gives (A.3). Units are handled by the existing unit-event budget, including B-negated-heart units. +No tree, potential, search algorithm, depth, leaf-count or native-resource result is asserted. + +## Main definitions + +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingInsertionCount`: cumulative insertion events. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingInsertionCount_eq_present_add_deleted`: + exact higher-width event balance. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingInsertionCount_one_le`: unit-event budget. +* `Cslib.Logic.PL.ClauseSet.Sparsification.card_counting_marked_le`: present-clause bound. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingInsertionCount_le_rec`: recurrence (A.3). +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingInsertionCount_le_pow`: Lemma A.3's closed bound. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.2, Lemma A.3][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-- The number of width-`j` insertion events in the first `N` steps. Each summand excludes both +the current node and the initial root; a later deletion does not erase the earlier event. -/ +def countingInsertionCount (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) + (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) (j : ℕ) : ℕ := + ∑ m ∈ range N, ((countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0)).filter + fun C => C.card = j).card + +/-- The empty path prefix has no insertion events, regardless of its root. -/ +@[simp] +theorem countingInsertionCount_zero (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) + (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) (j : ℕ) : + countingInsertionCount 0 ψ c H b j = 0 := by simp [countingInsertionCount] + +/-- Along actual heavy-heart counting steps, every literal remains on a declared root variable. +This does not require a finite ambient variable type, reducedness or nontautology. -/ +theorem support_counting_node {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (declared : Finset V) + (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) {t : ℕ} (ht : t ≤ N) : + ∀ C ∈ ψ t, ∀ l ∈ C, l.1 ∈ declared := by + induction t with + | zero => exact hsupport + | succ t ih => + have hprev := ih (by omega) + obtain ⟨D, hD⟩ := (hheavy t (by omega)).flower_nonempty hθ + have hH : ∀ l ∈ H t, l.1 ∈ declared := + fun l hl => hprev D (flower_subset _ _ _ hD) l ((mem_flower.mp hD).2.2 hl) + rw [hstep t (by omega)] + exact support_countingChild (ψ t) (c t) (H t) declared hprev hH (b t) + +/-- Unit events, including the negated-heart units, have total at most two per declared variable. +The root-excluding count is bounded by the existing current-excluding unit-event API. -/ +theorem countingInsertionCount_one_le {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (declared : Finset V) (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) : + countingInsertionCount N ψ c H b 1 ≤ 2 * declared.card := by + refine le_trans ?_ (sum_countingUnitInsertions_le hθ N ψ c H b hstep hheavy + hred declared hsupport) + unfold countingInsertionCount + refine sum_le_sum fun m hm => card_le_card fun C hC => ?_ + obtain ⟨hCI, hC1⟩ := mem_filter.mp hC + obtain ⟨hCI, hCold⟩ := mem_sdiff.mp hCI + exact mem_filter.mpr ⟨mem_sdiff.mpr ⟨hCI, fun h => hCold (mem_union_left _ h)⟩, hC1⟩ + +/-- At any counting node, the present marked width-`j ≥ 2` clauses number at most +`4 * declared.card * θ (j - 1)`. Both `Choice` priorities are used by the occurrence supplier. -/ +theorem card_counting_marked_le {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice θ (ψ i) (c i) (H i)) (declared : Finset V) + (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) {t j : ℕ} (ht : t ≤ N) + (hj : 2 ≤ j) : + ((ψ t).filter fun C => C ∉ ψ 0 ∧ C.card = j).card ≤ + 4 * declared.card * θ (j - 1) := by + let literals := declared ×ˢ (univ : Finset Bool) + have hs := support_counting_node hθ N ψ c H b hstep (fun i hi => (hchoice i hi).1) + declared hsupport ht + have hcover : (ψ t).filter (fun C => C ∉ ψ 0 ∧ C.card = j) ⊆ + literals.biUnion fun l => (ψ t).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ {l} ⊆ C := by + intro C hC + obtain ⟨hCψ, hCroot, hCj⟩ := mem_filter.mp hC + obtain ⟨l, hl⟩ : C.Nonempty := card_pos.mp (by omega) + exact mem_biUnion.mpr ⟨l, mem_product.mpr ⟨hs C hCψ l hl, mem_univ _⟩, + mem_filter.mpr ⟨hCψ, hCroot, hCj, singleton_subset_iff.mpr hl⟩⟩ + calc + _ ≤ (literals.biUnion fun l => + (ψ t).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ {l} ⊆ C).card := card_le_card hcover + _ ≤ ∑ l ∈ literals, + ((ψ t).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ {l} ⊆ C).card := card_biUnion_le + _ ≤ ∑ _l ∈ literals, 2 * θ (j - 1) := by + refine sum_le_sum fun l hl => ?_ + have hbound := card_counting_newContaining_lt hθ N ψ c H b hstep hchoice (j := j) ht + (S := {l}) (by simp) (by simp only [card_singleton]; omega) + simpa only [card_singleton] using hbound.le + _ = 4 * declared.card * θ (j - 1) := by + simp only [sum_const, smul_eq_mul, literals, card_product, card_univ, Fintype.card_bool] + ring + +/-- For widths at least two, insertion events telescope exactly into currently present marked +clauses plus deletion events. Events are counted at their steps, without a no-reinsertion premise. +Immediate survival and root exclusion are derived from the actual counting path. -/ +theorem countingInsertionCount_eq_present_add_deleted {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) {j : ℕ} (hj : 2 ≤ j) : + countingInsertionCount N ψ c H b j = + ((ψ N).filter fun C => C ∉ ψ 0 ∧ C.card = j).card + + ∑ m ∈ range N, ((ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ C ∉ ψ (m + 1)).card := by + let S (m : ℕ) := (ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j + let I (m : ℕ) := (countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0)).filter + fun C => C.card = j + let D (m : ℕ) := (ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ C ∉ ψ (m + 1) + have hinc (m : ℕ) (hm : m < N) : (I m).card + (S m).card = + (D m).card + (S (m + 1)).card := by + have hsurv := countingIntro_filter_eq_node_sdiff hθ N ψ c H b hstep hheavy hred hnt hm hj + have hI : I m = S (m + 1) \ S m := by + ext C + have hsurvC := congrArg (C ∈ ·) hsurv + simp only [mem_filter, mem_sdiff, mem_union] at hsurvC + simp only [I, S, mem_filter, mem_sdiff, mem_union] + tauto + have hD : D m = S m \ S (m + 1) := by + ext C + simp only [D, S, mem_filter, mem_sdiff] + tauto + rw [hI, hD, card_sdiff_add_card, card_sdiff_add_card, union_comm] + suffices ∀ n, n ≤ N → (∑ m ∈ range n, (I m).card) = + (S n).card + ∑ m ∈ range n, (D m).card from this N le_rfl + intro n + induction n with + | zero => + have hS0 : S 0 = ∅ := by + apply filter_eq_empty_iff.mpr + intro C hC h + exact h.1 hC + simp [hS0] + | succ n ih => + intro hn + have hprev := ih (by omega) + have hstep := hinc n (by omega) + rw [sum_range_succ, sum_range_succ] + omega + +/-- Summing the actual one-step deletion charges gives the deletion term of (A.3). +This part requires both `Choice` priorities and reducedness, but not nontautology. -/ +theorem sum_counting_deleted_marked_le {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) (j : ℕ) : + (∑ m ∈ range N, ((ψ m).filter fun C => C ∉ ψ 0 ∧ C.card = j ∧ C ∉ ψ (m + 1)).card) ≤ + 2 * ∑ i ∈ Ico 1 j, θ (j - i) * countingInsertionCount N ψ c H b i := by + calc + _ ≤ ∑ m ∈ range N, ∑ i ∈ Ico 1 j, 2 * θ (j - i) * + ((countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0)).filter + fun C => C.card = i).card := by + refine sum_le_sum fun m hm => ?_ + exact card_counting_deleted_marked_le hθ N ψ c H b hstep hchoice hred (mem_range.mp hm) j + _ = 2 * ∑ i ∈ Ico 1 j, θ (j - i) * countingInsertionCount N ψ c H b i := by + rw [sum_comm] + simp only [countingInsertionCount, mul_sum, mul_assoc] + +/-- Equation (A.3) for actual cumulative insertion events along any legal counting-path prefix. +The hypotheses assert actual steps, choices and root invariants; neither the balance nor its +target bounds are assumptions. Units have the sharper bound `countingInsertionCount_one_le`. +-/ +theorem countingInsertionCount_le_rec {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (declared : Finset V) (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) + {j : ℕ} (hj : 2 ≤ j) : + countingInsertionCount N ψ c H b j ≤ 4 * declared.card * θ (j - 1) + + 2 * ∑ i ∈ Ico 1 j, θ (j - i) * countingInsertionCount N ψ c H b i := by + rw [countingInsertionCount_eq_present_add_deleted hθ N ψ c H b hstep + (fun i hi => (hchoice i hi).1) hred hnt hj] + exact add_le_add (card_counting_marked_le hθ N ψ c H b hstep hchoice declared hsupport le_rfl hj) + (sum_counting_deleted_marked_le hθ N ψ c H b hstep hchoice hred j) + +/-- Lemma A.3: for thresholds `θ r = M ^ r`, every path prefix has at most +`4 * (3 * M) ^ (j - 1) * declared.card` width-`j` insertion events. This holds for all `j ≥ 1`, +in particular the manuscript's range `1 ≤ j < k` for width-`k` roots. The proof does not divide +by the declared-variable count, so it includes zero variables and zero-length prefixes. -/ +theorem countingInsertionCount_le_pow {M : ℕ} (hM : 2 ≤ M) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun r => M ^ r) (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (declared : Finset V) (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) + {j : ℕ} (hj : 1 ≤ j) : + countingInsertionCount N ψ c H b j ≤ 4 * (3 * M) ^ (j - 1) * declared.card := by + have hθ : ∀ r, 1 ≤ M ^ r := fun r => Nat.one_le_pow r M (by omega) + induction j using Nat.strong_induction_on with + | _ j ih => + by_cases hj1 : j = 1 + · subst j + have hunit := countingInsertionCount_one_le hθ N ψ c H b hstep + (fun i hi => (hchoice i hi).1) hred declared hsupport + simp only [Nat.sub_self, pow_zero, mul_one] + omega + have hj2 : 2 ≤ j := by omega + have hrec := countingInsertionCount_le_rec hθ N ψ c H b hstep hchoice hred hnt + declared hsupport hj2 + have hterm (i : ℕ) (hi : i ∈ Ico 1 j) : + M ^ (j - i) * countingInsertionCount N ψ c H b i ≤ + (4 * declared.card * M ^ (j - 1)) * 3 ^ (i - 1) := by + obtain ⟨hi1, hij⟩ := mem_Ico.mp hi + have hpow : M ^ (j - i) * M ^ (i - 1) = M ^ (j - 1) := by + rw [← pow_add] + congr 1 + omega + calc + _ ≤ M ^ (j - i) * (4 * (3 * M) ^ (i - 1) * declared.card) := + Nat.mul_le_mul_left _ (ih i hij hi1) + _ = (4 * declared.card * (M ^ (j - i) * M ^ (i - 1))) * 3 ^ (i - 1) := by + rw [mul_pow] + ring + _ = _ := by rw [hpow] + have hgeom : 1 + 2 * (∑ i ∈ Ico 1 j, (3 : ℕ) ^ (i - 1)) = 3 ^ (j - 1) := by + rw [sum_Ico_eq_sum_range] + simpa only [Nat.add_sub_cancel_left, Nat.add_sub_cancel, Nat.reduceAdd, mul_comm, add_comm] + using geom_sum_mul_add (2 : ℕ) (j - 1) + calc + _ ≤ 4 * declared.card * M ^ (j - 1) + + 2 * ∑ i ∈ Ico 1 j, M ^ (j - i) * countingInsertionCount N ψ c H b i := hrec + _ ≤ 4 * declared.card * M ^ (j - 1) + + 2 * ∑ i ∈ Ico 1 j, (4 * declared.card * M ^ (j - 1)) * 3 ^ (i - 1) := + Nat.add_le_add_left (Nat.mul_le_mul_left 2 (sum_le_sum hterm)) _ + _ = (4 * declared.card * M ^ (j - 1)) * + (1 + 2 * ∑ i ∈ Ico 1 j, 3 ^ (i - 1)) := by + rw [← mul_sum] + ring + _ = (4 * declared.card * M ^ (j - 1)) * 3 ^ (j - 1) := by rw [hgeom] + _ = _ := by rw [mul_pow]; ring + +/-- The closed insertion bound also holds for any declared-variable budget above the finite +support's cardinality. No inhabitance or positivity of that budget is required. -/ +theorem countingInsertionCount_le_pow_of_card_le {M : ℕ} (hM : 2 ≤ M) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun r => M ^ r) (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (declared : Finset V) (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) + {n : ℕ} (hn : declared.card ≤ n) {j : ℕ} (hj : 1 ≤ j) : + countingInsertionCount N ψ c H b j ≤ 4 * (3 * M) ^ (j - 1) * n := + (countingInsertionCount_le_pow hM N ψ c H b hstep hchoice hred hnt declared hsupport hj).trans + (Nat.mul_le_mul_left _ hn) + +end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/SparsificationCountingInsertions.lean b/ComplexityTests/SparsificationCountingInsertions.lean new file mode 100644 index 000000000..14177b7f3 --- /dev/null +++ b/ComplexityTests/SparsificationCountingInsertions.lean @@ -0,0 +1,173 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingInsertions + +public section + +open Cslib.Logic.PL +open ClauseSet.Sparsification Finset + +namespace ComplexityTests.SparsificationCountingInsertions + +/-- A two-petal B-step counts both petal units and the negated-heart unit. -/ +theorem binary_flower_inserts_three_units : + let root : ClauseSet (Fin 3) := {{(0, true), (1, true)}, {(0, true), (2, true)}} + countingInsertionCount 1 + (fun i => if i = 0 then root else countingChild root 2 {(0, true)} true) + (fun _ => 2) (fun _ => {(0, true)}) (fun _ => true) 1 = 3 := by decide + +private def θ (j : ℕ) := 2 ^ j +private theorem θpos (j : ℕ) : 1 ≤ θ j := Nat.one_le_pow j 2 (by decide) + +private theorem θtwo {j : ℕ} (hj : 1 ≤ j) : 2 ≤ θ j := by + change 2 ^ 1 ≤ 2 ^ j + exact Nat.pow_le_pow_right (by decide) hj + +-- The first B-step inserts four pairs. The second B-step deletes two marked pairs, including +-- an unselected pair killed by the negated-heart unit. The root pair is deleted but unmarked. +private def root : ClauseSet (Fin 9) := + {{(1, true), (2, true)}, {(0, true), (1, false), (3, true)}, + {(0, true), (1, true), (4, true)}, {(0, true), (5, true), (6, true)}, + {(0, true), (7, true), (8, true)}} + +private def first := countingChild root 3 {(0, true)} true +private def nodes (tail : Bool) (i : ℕ) := + if i = 0 then root else if i = 1 then first else countingChild first 2 {(1, true)} tail +private def sizes (i : ℕ) := if i = 0 then 3 else 2 +private def hearts (i : ℕ) : Finset (Literal (Fin 9)) := + if i = 0 then {(0, true)} else {(1, true)} +private def dirs (tail : Bool) (i : ℕ) := if i = 0 then true else tail + +private theorem firstChoice : Choice θ root 3 {(0, true)} := by + have hpairs : ∀ C ∈ root, ∀ D ∈ root, C ≠ D → C.card = D.card → C.card = 3 := by decide + have hinter : ∀ C ∈ root, ∀ D ∈ root, C ≠ D → (C ∩ D).card ≤ 1 := by decide + refine ⟨by unfold Heavy; decide, ?_, ?_⟩ + · intro c H hh + have htwo : 2 ≤ (flower root c H).card := + (θtwo (by have := hh.2.1; omega)).trans hh.2.2 + obtain ⟨C, hC, D, hD, hCD⟩ := one_lt_card.mp (by omega : 1 < (flower root c H).card) + have hCc := (mem_flower.mp hC).2.1 + have hDc := (mem_flower.mp hD).2.1 + have := hpairs C (flower_subset _ _ _ hC) D (flower_subset _ _ _ hD) hCD (by omega) + omega + · intro H hh + have htwo : 2 ≤ (flower root 3 H).card := + (θtwo (by have := hh.2.1; omega)).trans hh.2.2 + obtain ⟨C, hC, D, hD, hCD⟩ := one_lt_card.mp (by omega : 1 < (flower root 3 H).card) + have hH : H ⊆ C ∩ D := subset_inter (mem_flower.mp hC).2.2 (mem_flower.mp hD).2.2 + exact (card_le_card hH).trans (hinter C (flower_subset _ _ _ hC) D (flower_subset _ _ _ hD) hCD) + +private theorem secondChoice : Choice θ first 2 {(1, true)} := by + refine ⟨by unfold Heavy; decide, ?_, ?_⟩ + · intro c H hh + have := hh.1 + have := hh.2.1 + omega + · intro H hh + have := hh.2.1 + change H.card ≤ 1 + omega + +private theorem steps (tail : Bool) : ∀ i < 2, + nodes tail (i + 1) = countingChild (nodes tail i) (sizes i) (hearts i) (dirs tail i) := by + intro i hi + have : i = 0 ∨ i = 1 := by omega + rcases this with rfl | rfl <;> rfl + +private theorem choices (tail : Bool) : ∀ i < 2, + Choice θ (nodes tail i) (sizes i) (hearts i) := by + intro i hi + have : i = 0 ∨ i = 1 := by omega + rcases this with rfl | rfl + · exact firstChoice + · exact secondChoice + +private theorem rootReduced : Reduced root := by unfold Reduced; decide +private theorem rootNontautological : + ∀ C ∈ root, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by decide + +-- Compute the event count THROUGH the proved balance, with both a nonzero final population and +-- nonzero deletions. Looking only at the final node would lose half of the insertions. +private theorem four_pair_insertion_events : + countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 = 4 := by + have hbalance := countingInsertionCount_eq_present_add_deleted θpos 2 (nodes true) + sizes hearts (dirs true) (steps true) (fun i hi => (choices true i hi).1) + rootReduced rootNontautological (j := 2) (by decide) + have hfinal : ((nodes true 2).filter fun C => C ∉ nodes true 0 ∧ C.card = 2).card = 2 := by decide + have hdeleted : (∑ m ∈ range 2, ((nodes true m).filter fun C => + C ∉ nodes true 0 ∧ C.card = 2 ∧ C ∉ nodes true (m + 1)).card) = 2 := by decide + rw [hfinal, hdeleted] at hbalance + exact hbalance + +example : countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 ≠ + ((nodes true 2).filter fun C => C ∉ root ∧ C.card = 2).card := by + rw [four_pair_insertion_events] + decide + +example : countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 1 = 4 := by decide +example : countingInsertionCount 1 (nodes true) sizes hearts (dirs true) 1 = 1 := by decide + +-- The A-tail deletes one marked pair; the B-tail deletes two. Both prefixes still count the +-- same four earlier pair insertions, and root clauses never enter the event count. +example : countingInsertionCount 2 (nodes false) sizes hearts (dirs false) 2 = 4 := by decide +example : ((nodes false 2).filter fun C => C ∉ root ∧ C.card = 2).card = 3 := by decide +example : ((nodes true 1).filter fun C => C.card = 2 ∧ C ∉ nodes true 2).card = 3 := by decide + +-- Actual full-Choice clients of the recurrence and the source's unchanged closed constant. +example : countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 ≤ 88 := by + exact countingInsertionCount_le_rec θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (choices true) rootReduced rootNontautological univ + (by intro C hC l hl; exact mem_univ _) (by decide) + +example : countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 ≤ 216 := by + exact countingInsertionCount_le_pow (M := 2) (by decide) 2 (nodes true) sizes hearts (dirs true) + (steps true) (choices true) rootReduced rootNontautological univ + (by intro C hC l hl; exact mem_univ _) (by decide) + +example : countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 ≤ 240 := by + exact countingInsertionCount_le_pow_of_card_le (M := 2) (by decide) 2 (nodes true) + sizes hearts (dirs true) (steps true) (choices true) rootReduced rootNontautological univ + (by intro C hC l hl; exact mem_univ _) (n := 10) (by decide) (by decide) + +-- Empty variables with an empty root clause do not require division by n or an inhabited type. +example : countingInsertionCount 0 (fun _ => ({∅} : ClauseSet Empty)) (fun _ => 0) + (fun _ => ∅) (fun _ => false) 2 ≤ 0 := by + exact countingInsertionCount_le_pow_of_card_le (M := 2) (by decide) 0 + (fun _ => ({∅} : ClauseSet Empty)) (fun _ => 0) (fun _ => ∅) (fun _ => false) + (by intro i hi; omega) (by intro i hi; omega) (by unfold Reduced; decide) + (by simp) ∅ (by simp) (n := 0) (by simp) (by decide) + +-- Contradictory root units on one variable are individually nontautological. Neither is an +-- insertion, and no unit propagation is assumed by the bound. +private def oneVariable : ClauseSet Unit := {{((), true)}, {((), false)}} + +example : countingInsertionCount 0 (fun _ => oneVariable) (fun _ => 0) + (fun _ => ∅) (fun _ => false) 1 = 0 := by decide +example : countingInsertionCount 0 (fun _ => oneVariable) (fun _ => 0) + (fun _ => ∅) (fun _ => false) 1 ≤ 4 := by + exact countingInsertionCount_le_pow (M := 2) (by decide) 0 (fun _ => oneVariable) + (fun _ => 0) (fun _ => ∅) (fun _ => false) (by intro i hi; omega) + (by intro i hi; omega) (by unfold Reduced; decide) (by decide) {()} + (by intro C hC l hl; exact mem_singleton.mpr (Subsingleton.elim _ _)) (by decide) + +-- Without nontautology, even the one-step event balance can fail before any OLD marked clause +-- is deleted. This is the full-Choice counterexample retained in SparsificationCountingDeletion. +private def tautRoot : ClauseSet (Fin 8) := + {{(0, true), (0, false), (1, true)}, {(0, true), (2, true), (3, true)}, + {(0, true), (4, true), (5, true)}, {(0, true), (6, true), (7, true)}} +private def tautNodes (i : ℕ) := + if i = 0 then tautRoot else countingChild tautRoot 3 {(0, true)} true + +example : countingInsertionCount 1 tautNodes (fun _ => 3) (fun _ => {(0, true)}) + (fun _ => true) 2 = 4 := by decide +example : ((tautNodes 1).filter fun C => C ∉ tautNodes 0 ∧ C.card = 2).card = 3 := by decide +example : (∑ m ∈ range 1, ((tautNodes m).filter fun C => + C ∉ tautNodes 0 ∧ C.card = 2 ∧ C ∉ tautNodes (m + 1)).card) = 0 := by decide + +end ComplexityTests.SparsificationCountingInsertions From 1a973266f6cd27325fb7bcdddeb742069b498fe4 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 23:38:23 -0400 Subject: [PATCH 14/24] feat(sparsification): bound actual counting path budgets --- Complexity.lean | 1 + .../Sparsification/CountingPathBounds.lean | 299 ++++++++++++++++++ .../SparsificationCountingPathBounds.lean | 122 +++++++ 3 files changed, 422 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPathBounds.lean create mode 100644 ComplexityTests/SparsificationCountingPathBounds.lean diff --git a/Complexity.lean b/Complexity.lean index 943286563..26a44086d 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2172,6 +2172,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingInsertions public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPathBounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPathBounds.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPathBounds.lean new file mode 100644 index 000000000..43dffada4 --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingPathBounds.lean @@ -0,0 +1,299 @@ +/- +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.ClauseSet.Sparsification.CountingInsertions +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.BigOperators.Intervals +import Mathlib.Algebra.BigOperators.Ring.Finset +import Mathlib.Algebra.Group.Action.Defs +import Mathlib.Algebra.Order.BigOperators.Group.Finset +import Mathlib.Tactic.Ring + +/-! +# Depth and B-choice budgets for actual counting paths + +Appendix A.3 [OpenAI2026ETH], printed p.46, obtains depth `C * n` and at most `Γ * n / M` +B-choices from actual insertion events, where `C = 4 * ∑ r < k - 1, (3 * M) ^ r` and +`Γ = 4 * ∑ r < k - 1, 3 ^ r`. Here the B-budget is stated as `M * #B ≤ Γ * n`, without division. + +Every step inserts a genuinely new heart or petal of width between one and `k - 1`; every B-step +of petal width `j` inserts at least `M ^ j` such clauses. Summing these lower bounds against the +cumulative insertion bounds proves both budgets. There is no replacement by distinct-clause counts +and no assumed path-length bound. The empty sums at `k ≤ 1` force zero steps. No ambient finite +variable type or positivity of the declared-variable count is needed. + +The input is the existing Nat-indexed actual counting path. Tree/search clients can obtain it from +successive word prefixes and their branching rule's choice witnesses. Node/leaf-word counting, +effective parameter selection and native resource bounds remain separate consumer obligations. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.card_le_counting_node`: root width is preserved. +* `Cslib.Logic.PL.ClauseSet.Sparsification.counting_new_nonempty`: every step inserts a new clause. +* `Cslib.Logic.PL.ClauseSet.Sparsification.counting_length_le_sum_insertions`: depth lower charge. +* `Cslib.Logic.PL.ClauseSet.Sparsification.mul_card_counting_petalSteps_le`: B-step lower charge. +* `Cslib.Logic.PL.ClauseSet.Sparsification.counting_length_le`: the `C * n` depth bound. +* `Cslib.Logic.PL.ClauseSet.Sparsification.mul_card_counting_B_le`: the `Γ * n` weighted B-budget. +* `Cslib.Logic.PL.ClauseSet.Sparsification.counting_path_budgets_of_card_le`: declared-budget form. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.3][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-- All clauses along an actual heavy-heart counting path retain the root width bound. +The payload-width theorem includes the additional negated-heart units. -/ +theorem card_le_counting_node {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) {t : ℕ} (ht : t ≤ N) : ∀ C ∈ ψ t, C.card ≤ k := by + induction t with + | zero => exact hk + | succ t ih => + have hprev := ih (by omega) + obtain ⟨D, hD⟩ := (hheavy t (by omega)).flower_nonempty hθ + have hck : c t ≤ k := (mem_flower.mp hD).2.1 ▸ hprev D (flower_subset _ _ _ hD) + intro C hC + rw [hstep t (by omega), countingChild_eq_reduce_union] at hC + rcases mem_union.mp (reduce_subset _ hC) with hC | hC + · exact hprev C hC + · exact (card_lt_of_mem_countingIntro (hheavy t (by omega)) hC).le.trans hck + +/-- Every selected width is at most the root width bound. -/ +theorem counting_selected_width_le {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) {m : ℕ} (hm : m < N) : c m ≤ k := by + obtain ⟨C, hC⟩ := (hheavy m hm).flower_nonempty hθ + exact (mem_flower.mp hC).2.1 ▸ + card_le_counting_node hθ N ψ c H b hstep hheavy hk hm.le C (flower_subset _ _ _ hC) + +/-- Every heart/petal is genuinely new, including width-one petals. This excludes the current +node and root using reducedness and root-subclause witnesses, without nontautology. -/ +theorem intro_subset_counting_new {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + {m : ℕ} (hm : m < N) : + intro (ψ m) (c m) (H m) (b m) ⊆ + countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0) := by + have hredm : Reduced (ψ m) := by + cases m with + | zero => exact hred + | succ m => + rw [hstep m (by omega), countingChild_eq_reduce_union] + exact reduced_reduce _ + intro C hC + have hCI : C ∈ countingIntro (ψ m) (c m) (H m) (b m) := by + cases hb : b m with + | false => simpa only [hb, intro, countingIntro] using hC + | true => + rw [countingIntro] + exact mem_union_left _ (by simpa only [hb, intro] using hC) + refine mem_sdiff.mpr ⟨hCI, ?_⟩ + intro hCold + rcases mem_union.mp hCold with hCold | hCold + · exact notMem_of_mem_intro hθ hredm (hheavy m hm) hC hCold + · obtain ⟨D, hD, hDC⟩ := exists_mem_counting_node_subset N ψ c H b hstep hm.le hCold + obtain ⟨E, hE, hCE⟩ := exists_flower_ssuperset hθ (hheavy m hm) hC + exact hredm E (flower_subset _ _ _ hE) D hD + (Finset.ssubset_of_subset_of_ssubset hDC hCE) + +/-- Every legal counting step has at least one insertion absent from current and root. +The witness is a heart or petal, so already-present B-negated units do not affect this fact. -/ +theorem counting_new_nonempty {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + {m : ℕ} (hm : m < N) : + (countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0)).Nonempty := + (intro_nonempty hθ (hheavy m hm) (b m)).mono + (intro_subset_counting_new hθ N ψ c H b hstep hheavy hred hm) + +/-- Every actual payload width lies in `[1,k)`, where `k` bounds the root widths. -/ +theorem card_mem_Ico_of_mem_countingIntro {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) {m : ℕ} (hm : m < N) {C : Finset (Literal V)} + (hC : C ∈ countingIntro (ψ m) (c m) (H m) (b m)) : C.card ∈ Ico 1 k := by + have hpos := card_pos.mpr + (countingIntro_nonempty (hheavy m hm).1 (hheavy m hm).2.1 hC) + exact mem_Ico.mpr ⟨hpos, (card_lt_of_mem_countingIntro (hheavy m hm) hC).trans_le + (counting_selected_width_le hθ N ψ c H b hstep hheavy hk hm)⟩ + +/-- Charging each step to its nonempty insertion batch bounds path length by the sum of +width-indexed event counts. No deduplication across steps or terminal-sparsity premise is used. -/ +theorem counting_length_le_sum_insertions {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) : N ≤ ∑ j ∈ Ico 1 k, countingInsertionCount N ψ c H b j := by + let I (m : ℕ) := countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0) + calc + N = ∑ _m ∈ range N, 1 := by simp + _ ≤ ∑ m ∈ range N, (I m).card := by + refine sum_le_sum fun m hm => ?_ + exact card_pos.mpr (counting_new_nonempty hθ N ψ c H b hstep hheavy hred (mem_range.mp hm)) + _ = ∑ m ∈ range N, ∑ j ∈ Ico 1 k, ((I m).filter (·.card = j)).card := by + refine sum_congr rfl fun m hm => card_eq_sum_card_fiberwise fun C hC => ?_ + exact card_mem_Ico_of_mem_countingIntro hθ N ψ c H b hstep hheavy hk + (mem_range.mp hm) (mem_sdiff.mp hC).1 + _ = ∑ j ∈ Ico 1 k, countingInsertionCount N ψ c H b j := by + rw [sum_comm] + rfl + +/-- A B-step of petal width `j` inserts at least `θ j` new width-`j` clauses. Summing these +per-step contributions gives a lower bound on actual cumulative insertion events, including `j=1`. +-/ +theorem mul_card_counting_petalSteps_le {θ : ℕ → ℕ} (hθ : ∀ r, 1 ≤ θ r) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hheavy : ∀ i < N, Heavy θ (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) (j : ℕ) : + θ j * ((range N).filter fun m => b m = true ∧ c m - (H m).card = j).card ≤ + countingInsertionCount N ψ c H b j := by + let S := (range N).filter fun m => b m = true ∧ c m - (H m).card = j + let I (m : ℕ) := (countingIntro (ψ m) (c m) (H m) (b m) \ (ψ m ∪ ψ 0)).filter + fun C => C.card = j + calc + _ = ∑ _m ∈ S, θ j := by rw [sum_const, smul_eq_mul, mul_comm] + _ ≤ ∑ m ∈ S, (I m).card := by + refine sum_le_sum fun m hm => ?_ + obtain ⟨hmN, hb, hj⟩ := mem_filter.mp hm + have hm' := mem_range.mp hmN + have hnew := intro_subset_counting_new hθ N ψ c H b hstep hheavy hred hm' + have hsub : petals (ψ m) (c m) (H m) ⊆ I m := by + intro C hC + refine mem_filter.mpr ⟨hnew ?_, (card_of_mem_petals hC).trans hj⟩ + simpa only [hb, intro] using hC + calc + θ j ≤ (flower (ψ m) (c m) (H m)).card := hj ▸ (hheavy m hm').2.2 + _ = (petals (ψ m) (c m) (H m)).card := (card_petals _ _ _).symm + _ ≤ (I m).card := card_le_card hsub + _ ≤ ∑ m ∈ range N, (I m).card := sum_le_sum_of_subset (filter_subset _ _) + _ = countingInsertionCount N ψ c H b j := rfl + +/-- Actual counting paths have depth at most `C * declared.card`, with the source's +`C = 4 * ∑ r < k - 1, (3 * M) ^ r`. At `k ≤ 1` the empty sum forces zero steps. -/ +theorem counting_length_le {M : ℕ} (hM : 2 ≤ M) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun r => M ^ r) (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) (declared : Finset V) + (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) : + N ≤ (4 * ∑ r ∈ range (k - 1), (3 * M) ^ r) * declared.card := by + have hθ : ∀ r, 1 ≤ M ^ r := fun r => Nat.one_le_pow r M (by omega) + calc + N ≤ ∑ j ∈ Ico 1 k, countingInsertionCount N ψ c H b j := + counting_length_le_sum_insertions hθ N ψ c H b hstep (fun i hi => (hchoice i hi).1) hred hk + _ ≤ ∑ j ∈ Ico 1 k, 4 * (3 * M) ^ (j - 1) * declared.card := by + refine sum_le_sum fun j hj => ?_ + exact countingInsertionCount_le_pow hM N ψ c H b hstep hchoice hred hnt declared hsupport + (mem_Ico.mp hj).1 + _ = _ := by + rw [sum_Ico_eq_sum_range] + simp only [Nat.add_sub_cancel_left, mul_sum, sum_mul, mul_assoc] + +/-- At each fixed petal width, the B-step budget keeps one factor of `M` after canceling a +positive power of `M`. There is no division by the number of declared variables. -/ +theorem mul_card_counting_petalSteps_le_pow {M : ℕ} (hM : 2 ≤ M) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun r => M ^ r) (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (declared : Finset V) (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) + {j : ℕ} (hj : 1 ≤ j) : + M * ((range N).filter fun m => b m = true ∧ c m - (H m).card = j).card ≤ + 4 * 3 ^ (j - 1) * declared.card := by + have hθ : ∀ r, 1 ≤ M ^ r := fun r => Nat.one_le_pow r M (by omega) + have hcharge := mul_card_counting_petalSteps_le hθ N ψ c H b hstep + (fun i hi => (hchoice i hi).1) hred j + have hbound := countingInsertionCount_le_pow hM N ψ c H b hstep hchoice hred hnt + declared hsupport hj + have hpow : M ^ j = M ^ (j - 1) * M := by + rw [← pow_succ] + congr 1 + omega + apply Nat.le_of_mul_le_mul_left (c := M ^ (j - 1)) _ (pow_pos (by omega : 0 < M) _) + calc + _ = M ^ j * ((range N).filter fun m => b m = true ∧ c m - (H m).card = j).card := by + rw [hpow, mul_assoc] + _ ≤ countingInsertionCount N ψ c H b j := hcharge + _ ≤ 4 * (3 * M) ^ (j - 1) * declared.card := hbound + _ = _ := by rw [mul_pow]; ring + +/-- The source's weighted B-choice budget: `M * #B ≤ Γ * declared.card`, where +`Γ = 4 * ∑ r < k - 1, 3 ^ r`. This includes `k ≤ 1` and zero declared variables. -/ +theorem mul_card_counting_B_le {M : ℕ} (hM : 2 ≤ M) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun r => M ^ r) (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) (declared : Finset V) + (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) : + M * ((range N).filter fun m => b m = true).card ≤ + (4 * ∑ r ∈ range (k - 1), 3 ^ r) * declared.card := by + have hθ : ∀ r, 1 ≤ M ^ r := fun r => Nat.one_le_pow r M (by omega) + have hmaps : ∀ m ∈ (range N).filter (fun m => b m = true), c m - (H m).card ∈ Ico 1 k := by + intro m hm + have hm' := mem_range.mp (mem_filter.mp hm).1 + have hheavy := (hchoice m hm').1 + have hck := counting_selected_width_le hθ N ψ c H b hstep + (fun i hi => (hchoice i hi).1) hk hm' + apply mem_Ico.mpr + have := hheavy.1 + have := hheavy.2.1 + omega + have hfiber : ((range N).filter (fun m => b m = true)).card = + ∑ j ∈ Ico 1 k, ((range N).filter fun m => b m = true ∧ c m - (H m).card = j).card := by + rw [card_eq_sum_card_fiberwise hmaps] + refine sum_congr rfl fun j hj => ?_ + congr 1 + ext m + simp only [mem_filter] + tauto + rw [hfiber, mul_sum] + calc + _ ≤ ∑ j ∈ Ico 1 k, 4 * 3 ^ (j - 1) * declared.card := by + refine sum_le_sum fun j hj => ?_ + exact mul_card_counting_petalSteps_le_pow hM N ψ c H b hstep hchoice hred hnt + declared hsupport (mem_Ico.mp hj).1 + _ = _ := by + rw [sum_Ico_eq_sum_range] + simp only [Nat.add_sub_cancel_left, mul_sum, sum_mul, mul_assoc] + +/-- Both actual path budgets hold for any declared-variable budget above the finite support's +cardinality, including zero. The B-budget retains the source's exact coefficient `Γ`. -/ +theorem counting_path_budgets_of_card_le {M : ℕ} (hM : 2 ≤ M) + (N : ℕ) (ψ : ℕ → ClauseSet V) (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)) (b : ℕ → Bool) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun r => M ^ r) (ψ i) (c i) (H i)) (hred : Reduced (ψ 0)) + (hnt : ∀ C ∈ ψ 0, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) {k : ℕ} + (hk : ∀ C ∈ ψ 0, C.card ≤ k) (declared : Finset V) + (hsupport : ∀ C ∈ ψ 0, ∀ l ∈ C, l.1 ∈ declared) {n : ℕ} (hn : declared.card ≤ n) : + N ≤ (4 * ∑ r ∈ range (k - 1), (3 * M) ^ r) * n ∧ + M * ((range N).filter fun m => b m = true).card ≤ + (4 * ∑ r ∈ range (k - 1), 3 ^ r) * n := + ⟨(counting_length_le hM N ψ c H b hstep hchoice hred hnt hk declared hsupport).trans + (Nat.mul_le_mul_left _ hn), + (mul_card_counting_B_le hM N ψ c H b hstep hchoice hred hnt hk declared hsupport).trans + (Nat.mul_le_mul_left _ hn)⟩ + +end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/SparsificationCountingPathBounds.lean b/ComplexityTests/SparsificationCountingPathBounds.lean new file mode 100644 index 000000000..1481a9aee --- /dev/null +++ b/ComplexityTests/SparsificationCountingPathBounds.lean @@ -0,0 +1,122 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module + +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPathBounds +import all ComplexityTests.SparsificationCountingInsertions + +public section + +open Cslib.Logic.PL +open ClauseSet.Sparsification Finset + +namespace ComplexityTests.SparsificationCountingPathBounds + +-- Reuse the full-Choice, two-step concrete fixture from the insertion-count tests. Its first +-- B-step inserts four pairs; its second step is selectable as A or B. The source/tests stay frozen. +open SparsificationCountingInsertions + +example : ((range 2).filter fun m => dirs true m = true).card = 2 := by decide +example : ((range 2).filter fun m => dirs false m = true).card = 1 := by decide + +example : ((range 2).filter fun m => + dirs true m = true ∧ sizes m - (hearts m).card = 2).card = 1 := by decide +example : ((range 2).filter fun m => + dirs true m = true ∧ sizes m - (hearts m).card = 1).card = 1 := by decide +example : ((range 2).filter fun m => + dirs false m = true ∧ sizes m - (hearts m).card = 1).card = 0 := by decide + +-- The first B-step saturates its petal charge: four width-two petals for theta(2)=4. +example : 4 ≤ countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 := by + exact mul_card_counting_petalSteps_le θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (fun i hi => (choices true i hi).1) rootReduced 2 + +example : countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 2 = 4 := by decide + +-- Unit-petal charges are valid too: the second B-step inserts two unit petals as well as its +-- negated-heart unit. Unit events from the earlier step remain part of the total. +example : 2 ≤ countingInsertionCount 2 (nodes true) sizes hearts (dirs true) 1 := by + exact mul_card_counting_petalSteps_le θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (fun i hi => (choices true i hi).1) rootReduced 1 + +-- Obtain a positive new-clause witness from the actual second step, then prove it has width one. +example : ∃ C ∈ countingIntro (nodes true 1) (sizes 1) (hearts 1) (dirs true 1) \ + (nodes true 1 ∪ nodes true 0), C.card = 1 := by + obtain ⟨C, hC⟩ := counting_new_nonempty θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (fun i hi => (choices true i hi).1) rootReduced (m := 1) (by decide) + have hCI := (mem_sdiff.mp hC).1 + have hpos := card_pos.mpr (countingIntro_nonempty (choices true 1 (by decide)).1.1 + (choices true 1 (by decide)).1.2.1 hCI) + have hlt := card_lt_of_mem_countingIntro (choices true 1 (by decide)).1 hCI + change C.card < 2 at hlt + exact ⟨C, hC, by omega⟩ + +example : 2 ≤ ∑ j ∈ Ico 1 3, countingInsertionCount 2 (nodes true) sizes hearts (dirs true) j := by + exact counting_length_le_sum_insertions θpos 2 (nodes true) sizes hearts (dirs true) + (steps true) (fun i hi => (choices true i hi).1) rootReduced (by decide) + +example : + (∑ j ∈ Ico 1 3, countingInsertionCount 2 (nodes true) sizes hearts (dirs true) j) = 8 := by + decide + +-- The preserved width is three, not two; this root-width premise is checked on the fixture. +example : ¬ ∀ C ∈ root, C.card ≤ 2 := by decide +example : ∀ C ∈ nodes true 2, C.card ≤ 3 := by + exact card_le_counting_node θpos 2 (nodes true) sizes hearts (dirs true) (steps true) + (fun i hi => (choices true i hi).1) (by decide) le_rfl + +-- For k=3 and M=2 the source constants are C=28 and Gamma=16, rather than a coarser rate. +example : 2 ≤ 252 := by + exact counting_length_le (M := 2) (by decide) 2 (nodes true) sizes hearts (dirs true) + (steps true) (choices true) rootReduced rootNontautological (k := 3) (by decide) univ + (by intro C hC l hl; exact mem_univ _) + +example : 2 * ((range 2).filter fun m => dirs true m = true).card ≤ 144 := by + exact mul_card_counting_B_le (M := 2) (by decide) 2 (nodes true) sizes hearts (dirs true) + (steps true) (choices true) rootReduced rootNontautological (k := 3) (by decide) univ + (by intro C hC l hl; exact mem_univ _) + +example : 2 * ((range 2).filter fun m => dirs false m = true).card ≤ 144 := by + exact mul_card_counting_B_le (M := 2) (by decide) 2 (nodes false) sizes hearts (dirs false) + (steps false) (choices false) rootReduced rootNontautological (k := 3) (by decide) univ + (by intro C hC l hl; exact mem_univ _) + +example : 2 ≤ 280 ∧ 2 * ((range 2).filter fun m => dirs true m = true).card ≤ 160 := by + exact counting_path_budgets_of_card_le (M := 2) (by decide) 2 (nodes true) sizes hearts + (dirs true) (steps true) (choices true) rootReduced rootNontautological (k := 3) + (by decide) univ (by intro C hC l hl; exact mem_univ _) (n := 10) (by decide) + +/-- A root of two contradictory unit clauses cannot make even one legal branching step. +This derives zero length from the width-one bound; zero length is not a premise. -/ +theorem unit_root_cannot_branch (N : ℕ) (ψ : ℕ → ClauseSet Unit) (c : ℕ → ℕ) + (H : ℕ → Finset (Literal Unit)) (b : ℕ → Bool) + (hroot : ψ 0 = {{((), true)}, {((), false)}}) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun j => 2 ^ j) (ψ i) (c i) (H i)) : N = 0 := by + have hbound := counting_length_le (M := 2) (by decide) N ψ c H b hstep hchoice + (by rw [hroot]; unfold Reduced; decide) (by rw [hroot]; decide) + (k := 1) (by rw [hroot]; decide) {()} + (by intro C hC l hl; exact mem_singleton.mpr (Subsingleton.elim _ _)) + simpa using Nat.eq_zero_of_le_zero hbound + +example : ∃ (ψ : ℕ → ClauseSet Unit), ψ 0 = {{((), true)}, {((), false)}} ∧ + (∀ i < 0, ψ (i + 1) = countingChild (ψ i) 2 {((), true)} true) ∧ + (∀ i < 0, Choice (fun j => 2 ^ j) (ψ i) 2 {((), true)}) := + ⟨fun _ => {{((), true)}, {((), false)}}, rfl, by simp, by simp⟩ + +-- At zero width and zero variables the same theorem forces zero steps, even with an empty +-- root clause. No ambient inhabitance or division by the declared-variable count is used. +example (N : ℕ) (ψ : ℕ → ClauseSet Empty) (c : ℕ → ℕ) + (H : ℕ → Finset (Literal Empty)) (b : ℕ → Bool) (hroot : ψ 0 = {∅}) + (hstep : ∀ i < N, ψ (i + 1) = countingChild (ψ i) (c i) (H i) (b i)) + (hchoice : ∀ i < N, Choice (fun j => 2 ^ j) (ψ i) (c i) (H i)) : N = 0 := by + have hbound := counting_path_budgets_of_card_le (M := 2) (by decide) N ψ c H b hstep hchoice + (by rw [hroot]; unfold Reduced; decide) (by rw [hroot]; simp) + (k := 0) (by rw [hroot]; simp) ∅ (by rw [hroot]; simp) (n := 0) (by simp) + simpa using Nat.eq_zero_of_le_zero hbound.1 + +end ComplexityTests.SparsificationCountingPathBounds From 74db37bb005be90f350bcae5f4e7910e50f159e5 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 03:49:33 +0000 Subject: [PATCH 15/24] feat(FineGrained): add dense occurrence-3 compiler and CountingSparsify leaf reduction --- Complexity.lean | 1 + .../FineGrained/ThreeOccurrenceReduction.lean | 524 ++++++++++++++++++ .../FineGrained/ThreeOccurrenceReduction.lean | 97 ++++ 3 files changed, 622 insertions(+) create mode 100644 Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean create mode 100644 ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean diff --git a/Complexity.lean b/Complexity.lean index a1bada3ff..7387844b4 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -773,6 +773,7 @@ public import Complexity.Computability.Complexity.FineGrained.PermutationIndepen public import Complexity.Computability.Complexity.FineGrained.Reduction public import Complexity.Computability.Complexity.FineGrained.Schoening public import Complexity.Computability.Complexity.FineGrained.Sparsification +public import Complexity.Computability.Complexity.FineGrained.ThreeOccurrenceReduction public import Complexity.Computability.Complexity.HardnessEncoding.PolyTime public import Complexity.Computability.Complexity.HardnessEncoding.Program public import Complexity.Computability.Complexity.Parameterized diff --git a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean new file mode 100644 index 000000000..66ccab2b5 --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean @@ -0,0 +1,524 @@ +/- +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.CountingSparsification +public import Complexity.Computability.Complexity.FineGrained.Reduction +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Counting +public import Complexity.Logics.Propositional.CNF.ModelCount.DenseRelabel +import Complexity.Logics.Propositional.CNF.ThreeOccurrence.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain + +/-! +# Bounded-occurrence reductions for fine-grained decision and model counting + +Connects the occurrence-splitting compiler (`CNF.toThreeOccurrence`, `toThreeSATThree`, +[Papadimitriou, Proposition 9.3][Papadimitriou94]) with dense variable numbering +(`CNF.denseRelabel`), fine-grained exponential/subexponential reductions (`FineGrained/Reduction`), +and the depth-first counting-sparsification search (`CountingSparsify.search`, +[OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Section 6.4 and Appendix A, +pp. 43–47). + +## Main definitions + +* `Cslib.Complexity.toThreeOccurrenceDense`: split variable occurrences via `CNF.toThreeOccurrence` + and renumber the resulting copies densely onto `0, …, φ.vars.length - 1` via `CNF.denseRelabel`. +* `Cslib.Complexity.guardOccurrenceCounter`: guard a model counter `f : CNF ℕ → ℕ` by the + clause-width check `∀ c ∈ ψ, c.length ≤ k` and precompose with `toThreeOccurrenceDense`. + +## Main results + +* `Cslib.Logic.PL.CNF.numVars_relabel_of_injOn`, `Cslib.Logic.PL.CNF.numVars_relabel`: + injective variable relabelings preserve `CNF.numVars`. +* `Cslib.Logic.PL.CNF.count_vars_denseRelabel`, + `Cslib.Logic.PL.CNF.count_vars_denseRelabel_le`: `CNF.denseRelabel` preserves per-variable + literal-occurrence counts and upper bounds. +* `Cslib.Complexity.computableInPolyTime_toThreeOccurrenceDense`, + `Cslib.Complexity.modelCount_toThreeOccurrenceDense`, + `Cslib.Complexity.vars_toThreeOccurrenceDense_toFinset`, + `Cslib.Complexity.width_toThreeOccurrenceDense`, + `Cslib.Complexity.count_vars_toThreeOccurrenceDense_le`, + `Cslib.Complexity.numVars_toThreeOccurrenceDense_le_mul_length`: `toThreeOccurrenceDense` runs in + polynomial time, preserves `Satisfiable`, `modelCount`, and clause width `≤ k` (for `2 ≤ k`), and + produces a dense formula on `Finset.range φ.vars.length` (`numVars ≤ k * φ.length`) in which + every variable occurs at most three times. +* `Cslib.Complexity.DecidableInExpTime.threeSAT_length_of_threeSATThree_numVars`, + `Cslib.Complexity.DecidableInSubexpTime.threeSAT_of_threeSATThree_numVars`, + `Cslib.Complexity.ETH.not_decidableInSubexpTime_threeSATThree_numVars`: fine-grained exponential + and subexponential reductions from `ThreeSAT` to `ThreeSATThree` in both `CNF.numVars` and + `List.length`. +* `Cslib.Complexity.exists_computableInO_guardOccurrenceCounter`, + `Cslib.Complexity.CountingSparsify.exists_computableInO_search_of_threeOccurrence_counter`: a + total model counter `f` running in time and space `O(2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e)` + yields a clause-exponential leaf counter in time and space + `O(2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E)`, and hence (under `CountingTreeBound`, a + library adaptation of Appendix A.2–A.3) a general width-`≤ k` model counter via + `CountingSparsify.search` in time and space + `O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E')`. + +## References + +* [C. H. Papadimitriou, *Computational Complexity*, Proposition 9.3][Papadimitriou94] +* [R. Impagliazzo, R. Paturi, F. Zane, *Which problems have strongly exponential + complexity?*][ImpagliazzoPaturiZane2001] +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Section 2 (pp. 7–14), + Section 6.4 (pp. 43–44), and Appendix A (pp. 44–47). +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF + +variable {V W : Type*} + +/-- Relabeling injectively on the occurring variables preserves `numVars`. -/ +theorem numVars_relabel_of_injOn [DecidableEq V] [DecidableEq W] (φ : CNF V) (f : V → W) + (hf : Set.InjOn f {v | v ∈ φ.vars}) : + (φ.relabel f).numVars = φ.numVars := by + rw [ ← length_distinctVars, ← length_distinctVars, distinctVars_relabel_of_injOn φ f hf, + List.length_map] + +/-- Relabeling along an injective map preserves `numVars`. -/ +@[simp] +theorem numVars_relabel [DecidableEq V] [DecidableEq W] {f : V → W} (hf : Function.Injective f) + (φ : CNF V) : (φ.relabel f).numVars = φ.numVars := + numVars_relabel_of_injOn φ f fun _ _ _ _ h => hf h + +/-- Dense variable relabeling preserves the exact literal-occurrence count of every occurring +variable. -/ +theorem count_vars_denseRelabel [DecidableEq V] (φ : CNF V) {v : V} (hv : v ∈ φ.vars) : + φ.denseRelabel.vars.count (φ.distinctVars.idxOf v) = φ.vars.count v := by + rw [denseRelabel, vars_relabel] + simp only [List.count_eq_countP, List.countP_map, Function.comp_def] + apply List.countP_congr + intro w _ + simp only [beq_iff_eq] + rw [eq_comm, List.idxOf_inj (mem_distinctVars.mpr hv), eq_comm] + +/-- Any upper bound on original literal-occurrence counts is preserved by `CNF.denseRelabel`. -/ +theorem count_vars_denseRelabel_le [DecidableEq V] (φ : CNF V) {B : ℕ} + (h : ∀ v, φ.vars.count v ≤ B) (i : ℕ) : + φ.denseRelabel.vars.count i ≤ B := by + by_cases hi : i ∈ φ.denseRelabel.vars + · rw [denseRelabel, vars_relabel] at hi + obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hi + rw [count_vars_denseRelabel φ hv] + exact h v + · rw [List.count_eq_zero.mpr hi] + exact Nat.zero_le _ + +end Cslib.Logic.PL.CNF + +namespace Cslib.Complexity + +open Cslib.Logic.PL Turing.MultiTapeTM + +/-! ### 1. Dense occurrence-3 compiler (`toThreeOccurrenceDense`) -/ + +/-- Split literal occurrences via `CNF.toThreeOccurrence` and renumber the resulting `(ℕ × ℕ × ℕ)` +copy variables densely onto `0, …, φ.vars.length - 1` via `CNF.denseRelabel` (a library adaptation +combining [Papadimitriou, Proposition 9.3][Papadimitriou94] with first-occurrence dense variable +numbering). -/ +def toThreeOccurrenceDense (φ : CNF ℕ) : CNF ℕ := + φ.toThreeOccurrence.denseRelabel + +/-- `toThreeOccurrenceDense` preserves satisfiability. -/ +@[simp] +theorem satisfiable_toThreeOccurrenceDense (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).Satisfiable ↔ φ.Satisfiable := by + rw [toThreeOccurrenceDense, CNF.satisfiable_denseRelabel, CNF.satisfiable_toThreeOccurrence] + +/-- `toThreeOccurrenceDense` preserves the occurring-variable model count. -/ +@[simp] +theorem modelCount_toThreeOccurrenceDense (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).modelCount = φ.modelCount := by + rw [toThreeOccurrenceDense, CNF.modelCount_denseRelabel, CNF.modelCount_toThreeOccurrence] + +/-- The number of distinct occurring variables of `toThreeOccurrenceDense φ` equals the number of +literal occurrences `φ.vars.length` of `φ`. -/ +@[simp] +theorem numVars_toThreeOccurrenceDense (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).numVars = φ.vars.length := by + rw [toThreeOccurrenceDense, CNF.numVars_denseRelabel, CNF.numVars_toThreeOccurrence] + +/-- The occurring variables of `toThreeOccurrenceDense φ` are `Finset.range φ.vars.length`. -/ +@[simp] +theorem vars_toThreeOccurrenceDense_toFinset (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).vars.toFinset = Finset.range φ.vars.length := by + rw [toThreeOccurrenceDense, CNF.vars_denseRelabel_toFinset, CNF.numVars_toThreeOccurrence] + +/-- The occurring variables of `toThreeOccurrenceDense φ` are `Finset.range` of its own +`numVars`. -/ +theorem vars_toThreeOccurrenceDense_eq_range_numVars (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).vars.toFinset = + Finset.range (toThreeOccurrenceDense φ).numVars := by + rw [vars_toThreeOccurrenceDense_toFinset, numVars_toThreeOccurrenceDense] + +/-- The declared-variable model count of `toThreeOccurrenceDense φ` on +`Finset.range (toThreeOccurrenceDense φ).numVars` equals `φ.modelCount`. -/ +theorem modelCountOn_toThreeOccurrenceDense (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).modelCountOn (Finset.range (toThreeOccurrenceDense φ).numVars) = + φ.modelCount := by + rw [toThreeOccurrenceDense, CNF.numVars_denseRelabel, CNF.modelCountOn_denseRelabel, + CNF.modelCount_toThreeOccurrence] + +/-- `toThreeOccurrenceDense φ` has `φ.length + φ.vars.length` clauses. -/ +@[simp] +theorem length_toThreeOccurrenceDense (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).length = φ.length + φ.vars.length := by + rw [toThreeOccurrenceDense, CNF.length_denseRelabel, CNF.length_toThreeOccurrence] + +/-- `toThreeOccurrenceDense φ` has `3 * φ.vars.length` total literal occurrences. -/ +@[simp] +theorem length_vars_toThreeOccurrenceDense (φ : CNF ℕ) : + (toThreeOccurrenceDense φ).vars.length = 3 * φ.vars.length := by + rw [toThreeOccurrenceDense, CNF.denseRelabel, CNF.vars_relabel, List.length_map, + CNF.length_vars_toThreeOccurrence] + +/-- For `2 ≤ k`, `toThreeOccurrenceDense` preserves clause width `≤ k`. -/ +theorem width_toThreeOccurrenceDense {k : ℕ} (hk : 2 ≤ k) {φ : CNF ℕ} + (hφ : ∀ c ∈ φ, c.length ≤ k) : + ∀ c ∈ toThreeOccurrenceDense φ, c.length ≤ k := + (CNF.all_length_le_denseRelabel φ.toThreeOccurrence).mpr (CNF.width_toThreeOccurrence hk hφ) + +/-- Every variable occurs at most three times in `toThreeOccurrenceDense φ`. -/ +theorem count_vars_toThreeOccurrenceDense_le (φ : CNF ℕ) (v : ℕ) : + (toThreeOccurrenceDense φ).vars.count v ≤ 3 := + CNF.count_vars_denseRelabel_le φ.toThreeOccurrence + (CNF.count_vars_toThreeOccurrence_le φ) v + +/-- On a formula `φ` of clause width at most `k`, `toThreeOccurrenceDense φ` has at most +`k * φ.length` distinct variables. -/ +theorem numVars_toThreeOccurrenceDense_le_mul_length {k : ℕ} {φ : CNF ℕ} + (hφ : ∀ c ∈ φ, c.length ≤ k) : + (toThreeOccurrenceDense φ).numVars ≤ k * φ.length := by + rw [numVars_toThreeOccurrenceDense] + exact CNF.length_vars_le_mul_length hφ + +/-- On a formula `φ` of clause width at most `k`, `toThreeOccurrenceDense φ` has at most +`(k + 1) * φ.length` clauses. -/ +theorem length_toThreeOccurrenceDense_le_mul_length {k : ℕ} {φ : CNF ℕ} + (hφ : ∀ c ∈ φ, c.length ≤ k) : + (toThreeOccurrenceDense φ).length ≤ (k + 1) * φ.length := by + rw [length_toThreeOccurrenceDense] + have hvars := CNF.length_vars_le_mul_length hφ + calc φ.length + φ.vars.length + ≤ φ.length + k * φ.length := Nat.add_le_add_left hvars _ + _ = (k + 1) * φ.length := by ring + +/-- `toThreeOccurrenceDense` is computable in polynomial time under `encCNF`. -/ +@[fun_prop] +theorem computableInPolyTime_toThreeOccurrenceDense : + ComputableInPolyTime toThreeOccurrenceDense encCNF encCNF := + (CNF.computableInPolyTime_denseRelabel (encPair encNat (encPair encUnary encUnary))).comp + (CNF.computableInPolyTime_toThreeOccurrence encNat) + +/-! ### 2. Fine-grained `ThreeSATThree` reductions and `ETH` consequences -/ + +/-- For every `φ : CNF ℕ`, `toThreeSATThree φ` has at most `3 * φ.length` distinct variables. -/ +theorem numVars_toThreeSATThree_le_mul_length (φ : CNF ℕ) : + (toThreeSATThree φ).numVars ≤ 3 * φ.length := by + unfold toThreeSATThree + split_ifs with hw + · have hw' : ∀ c ∈ φ, c.length ≤ 3 := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hw + rw [CNF.numVars_relabel ThreeSATThree.numberCopy_injective, CNF.numVars_toThreeOccurrence] + exact CNF.length_vars_le_mul_length hw' + · simp [CNF.numVars, CNF.vars] + +/-- For every `φ : CNF ℕ`, `toThreeSATThree φ` has at most `4 * φ.length + 1` clauses. -/ +theorem length_toThreeSATThree_le_mul_length (φ : CNF ℕ) : + (toThreeSATThree φ).length ≤ 4 * φ.length + 1 := by + by_cases hw : ∀ c ∈ φ, c.length ≤ 3 + · rw [length_toThreeSATThree φ hw] + have hvars := CNF.length_vars_le_mul_length hw + omega + · have hg : φ.all (fun c => decide (c.length ≤ 3)) = false := by simpa using hw + simp only [toThreeSATThree, hg, Bool.false_eq_true, ite_false, List.length_singleton] + omega + +/-- **Exponential-time reduction from `ThreeSAT` (`KSAT 3`) in clauses to `ThreeSATThree` in +variables.** Deciding `ThreeSATThree` in time `O(2 ^ (δ * numVars) * poly)` decides `ThreeSAT` in +time `O(2 ^ ((3 * δ) * length) * poly)`. -/ +theorem DecidableInExpTime.threeSAT_length_of_threeSATThree_numVars {δ : ℝ} (hδ : 0 ≤ δ) + (h : DecidableInExpTime ThreeSATThree encCNF CNF.numVars δ) : + DecidableInExpTime ThreeSAT encCNF List.length (3 * δ) := + DecidableInExpTime.of_reduction computableInPolyTime_toThreeSATThree + (fun φ => (mem_ThreeSATThree_toThreeSATThree_iff φ).symm) + (c := 3) (fun φ => (numVars_toThreeSATThree_le_mul_length φ).trans (Nat.le_add_right _ 3)) hδ h + +/-- **Exponential-time reduction from `ThreeSAT` (`KSAT 3`) in clauses to `ThreeSATThree` in +clauses.** Deciding `ThreeSATThree` in time `O(2 ^ (δ * length) * poly)` decides `ThreeSAT` in +time `O(2 ^ ((4 * δ) * length) * poly)`. -/ +theorem DecidableInExpTime.threeSAT_length_of_threeSATThree_length {δ : ℝ} (hδ : 0 ≤ δ) + (h : DecidableInExpTime ThreeSATThree encCNF List.length δ) : + DecidableInExpTime ThreeSAT encCNF List.length (4 * δ) := + DecidableInExpTime.of_reduction computableInPolyTime_toThreeSATThree + (fun φ => (mem_ThreeSATThree_toThreeSATThree_iff φ).symm) + (c := 4) (fun φ => (length_toThreeSATThree_le_mul_length φ).trans (by omega)) hδ h + +/-- **Subexponential-time reduction from `ThreeSAT` (`KSAT 3`) to `ThreeSATThree` in variables.** +If `ThreeSATThree` is decidable in subexponential time in `CNF.numVars`, then `ThreeSAT` is +decidable in subexponential time in `CNF.numVars` (via the Sparsification Lemma). -/ +theorem DecidableInSubexpTime.threeSAT_of_threeSATThree_numVars + (h : DecidableInSubexpTime ThreeSATThree encCNF CNF.numVars) : + DecidableInSubexpTime ThreeSAT encCNF CNF.numVars := by + rw [← KSAT_three] + refine DecidableInSubexpTime.numVars_of_length ?_ + rw [KSAT_three] + exact DecidableInSubexpTime.of_reduction computableInPolyTime_toThreeSATThree + (fun φ => (mem_ThreeSATThree_toThreeSATThree_iff φ).symm) + (c := 3) (fun φ => (numVars_toThreeSATThree_le_mul_length φ).trans (Nat.le_add_right _ 3)) h + +/-- **Subexponential-time reduction from `ThreeSAT` (`KSAT 3`) to `ThreeSATThree` in clauses.** -/ +theorem DecidableInSubexpTime.threeSAT_of_threeSATThree_length + (h : DecidableInSubexpTime ThreeSATThree encCNF List.length) : + DecidableInSubexpTime ThreeSAT encCNF CNF.numVars := by + rw [← KSAT_three] + refine DecidableInSubexpTime.numVars_of_length ?_ + rw [KSAT_three] + exact DecidableInSubexpTime.of_reduction computableInPolyTime_toThreeSATThree + (fun φ => (mem_ThreeSATThree_toThreeSATThree_iff φ).symm) + (c := 4) (fun φ => (length_toThreeSATThree_le_mul_length φ).trans (by omega)) h + +/-- **Under `ETH`, `ThreeSATThree` is not decidable in subexponential time in the number of +variables.** -/ +theorem ETH.not_decidableInSubexpTime_threeSATThree_numVars (h : ETH) : + ¬ DecidableInSubexpTime ThreeSATThree encCNF CNF.numVars := + fun h' => ETH_iff_not_decidableInSubexpTime.mp h h'.threeSAT_of_threeSATThree_numVars + +/-- **Under `ETH`, `ThreeSATThree` is not decidable in subexponential time in the number of +clauses.** -/ +theorem ETH.not_decidableInSubexpTime_threeSATThree_length (h : ETH) : + ¬ DecidableInSubexpTime ThreeSATThree encCNF List.length := + fun h' => ETH_iff_not_decidableInSubexpTime.mp h h'.threeSAT_of_threeSATThree_length + +/-- Deciding `ThreeSATThree` in subexponential time in `CNF.numVars` refutes `ETH`. -/ +theorem not_ETH_of_decidableInSubexpTime_threeSATThree_numVars + (h : DecidableInSubexpTime ThreeSATThree encCNF CNF.numVars) : ¬ ETH := + fun heth => heth.not_decidableInSubexpTime_threeSATThree_numVars h + +/-- Deciding `ThreeSATThree` in subexponential time in `List.length` refutes `ETH`. -/ +theorem not_ETH_of_decidableInSubexpTime_threeSATThree_length + (h : DecidableInSubexpTime ThreeSATThree encCNF List.length) : ¬ ETH := + fun heth => heth.not_decidableInSubexpTime_threeSATThree_length h + +/-! ### 3. Guarded leaf model-counter reduction for `CountingSparsify.search` -/ + +/-- Guard a model counter `f : CNF ℕ → ℕ` by the clause-width check `∀ c ∈ ψ, c.length ≤ k` and +apply `f` to the dense 3-occurrence formula `toThreeOccurrenceDense ψ`. -/ +def guardOccurrenceCounter (k : ℕ) (f : CNF ℕ → ℕ) (ψ : CNF ℕ) : ℕ := + if ψ.all fun c => decide (c.length ≤ k) then f (toThreeOccurrenceDense ψ) else 0 + +/-- If `f` computes `χ.modelCount` on every dense formula `χ : CNF ℕ` of width `≤ k` (`2 ≤ k`) and +at most `3` occurrences per variable (`∀ v, χ.vars.count v ≤ 3` and +`χ.vars.toFinset = Finset.range χ.numVars`), then `guardOccurrenceCounter k f ψ = ψ.modelCount` on +every formula `ψ : CNF ℕ` of width `≤ k` (and `0` otherwise). -/ +theorem guardOccurrenceCounter_eq_ite_modelCount {k : ℕ} (hk : 2 ≤ k) {f : CNF ℕ → ℕ} + (hf : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + (ψ : CNF ℕ) : + guardOccurrenceCounter k f ψ = + if (ψ.all fun c => decide (c.length ≤ k)) then ψ.modelCount else 0 := by + unfold guardOccurrenceCounter + split_ifs with hψ + · have hψ' : ∀ c ∈ ψ, c.length ≤ k := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + rw [hf (toThreeOccurrenceDense ψ) (width_toThreeOccurrenceDense hk hψ') + (count_vars_toThreeOccurrenceDense_le ψ) (vars_toThreeOccurrenceDense_eq_range_numVars ψ), + modelCount_toThreeOccurrenceDense] + · rfl + +/-- On formulas `ψ` of width `≤ k` (`2 ≤ k`), `guardOccurrenceCounter k f ψ = ψ.modelCount` +whenever `f` counts dense width-`≤ k`, occurrence-`≤ 3` formulas. -/ +theorem guardOccurrenceCounter_eq_modelCount {k : ℕ} (hk : 2 ≤ k) {f : CNF ℕ → ℕ} + (hf : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {ψ : CNF ℕ} (hψ : ∀ c ∈ ψ, c.length ≤ k) : + guardOccurrenceCounter k f ψ = ψ.modelCount := by + have hg : (ψ.all fun c => decide (c.length ≤ k)) = true := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + simp only [guardOccurrenceCounter_eq_ite_modelCount hk hf ψ, hg, ↓reduceIte] + +/-- When `f` counts dense width-`≤ k`, occurrence-`≤ 3` formulas (`2 ≤ k`), +`guardOccurrenceCounter k f ψ ≤ 2 ^ ψ.numVars` for all `ψ : CNF ℕ`. -/ +theorem guardOccurrenceCounter_le_two_pow_numVars {k : ℕ} (hk : 2 ≤ k) {f : CNF ℕ → ℕ} + (hf : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + (ψ : CNF ℕ) : + guardOccurrenceCounter k f ψ ≤ 2 ^ ψ.numVars := by + rw [guardOccurrenceCounter_eq_ite_modelCount hk hf ψ] + split_ifs + · exact CountingSparsify.modelCount_le_two_pow_numVars ψ + · exact Nat.zero_le _ + +/-- When `f` counts dense width-`≤ k`, occurrence-`≤ 3` formulas (`2 ≤ k`), +`0 < guardOccurrenceCounter k f ψ` holds if and only if `ψ ∈ KSAT k`. -/ +theorem guardOccurrenceCounter_pos_iff_mem_kSAT {k : ℕ} (hk : 2 ≤ k) {f : CNF ℕ → ℕ} + (hf : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + (ψ : CNF ℕ) : + 0 < guardOccurrenceCounter k f ψ ↔ ψ ∈ KSAT k := by + rw [guardOccurrenceCounter_eq_ite_modelCount hk hf ψ] + by_cases hψ : (ψ.all fun c => decide (c.length ≤ k)) = true + · have hψ' : ∀ c ∈ ψ, c.length ≤ k := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + simp only [hψ, ↓reduceIte, CNF.modelCount_pos_iff, KSAT, Set.mem_ofPred_eq] + exact ⟨fun hs => ⟨hψ', hs⟩, fun h => h.2⟩ + · have hψ' : ¬ ∀ c ∈ ψ, c.length ≤ k := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + simp only [hψ, Bool.false_eq_true, ite_false, lt_irrefl, KSAT, Set.mem_ofPred_eq, hψ', + false_and] + +/-- **From variable-exponential occurrence-3 counting to clause-exponential leaf counting.** +If `f : CNF ℕ → ℕ` has a combined time-and-space certificate `ComputableInO f encCNF encNat tf tf` +with `(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c` for all `χ : CNF ℕ`, then +`guardOccurrenceCounter k f` has a combined time-and-space certificate +`ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T` with +`(T ψ : ℝ) ≤ C₀ * (2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E) + C₀` for all `ψ : CNF ℕ`. -/ +theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} + (hf : ComputableInO f encCNF encNat tf tf) {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨dc, ec, hc⟩ := (computableInPolyTime_all_length_le k).tidy.exists_degree + obtain ⟨dr, er, hr⟩ := computableInPolyTime_toThreeOccurrenceDense.tidy.exists_degree + obtain ⟨dz, ez, hz⟩ := + (show ComputableTidilyInPolyTime (fun _ : CNF ℕ => (0 : ℕ)) encCNF encNat + by fun_prop).exists_degree + set d := dc + dr + dz + 1 + have hcO := (hc.mono (d' := d) (e' := ec) (by omega) le_rfl).inO + have hrO := (hr.mono (d' := d) (e' := er) (by omega) le_rfl).inO + have hzO := (hz.mono (d' := d) (e' := ez) (by omega) le_rfl).inO + obtain ⟨l, hl⟩ := hrO.length_le + have hcomp := hf.tidy.comp hrO + have hite := hcO.ite hcomp hzO + set T : CNF ℕ → ℕ := fun ψ => + ((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) + else + ((encCNF ψ).length + 1) ^ d + set E := d * e + d + set K : ℝ := 2 + c * (2 * l + 1) ^ e + c + refine ⟨T, ⌈K⌉₊, E, .of_tidy hite, fun ψ => ?_⟩ + set P : ℝ := (((encCNF ψ).length : ℝ) + 1) ^ d + set Q : ℝ := (((encCNF ψ).length : ℝ) + 1) ^ E + set X : ℝ := (2 : ℝ) ^ ((k * δ) * ψ.length) + have hP1 : 1 ≤ P := one_le_pow₀ (by linarith [(Nat.cast_nonneg (encCNF ψ).length : (0 : ℝ) ≤ _)]) + have hQ1 : 1 ≤ Q := one_le_pow₀ (by linarith [(Nat.cast_nonneg (encCNF ψ).length : (0 : ℝ) ≤ _)]) + have hX1 : 1 ≤ X := Real.one_le_rpow (by norm_num) (by positivity) + have hPQ : P ≤ Q := by + refine pow_le_pow_right₀ (by linarith [(Nat.cast_nonneg (encCNF ψ).length : (0 : ℝ) ≤ _)]) ?_ + omega + have hPeQ : P ^ e ≤ Q := by + rw [← pow_mul] + refine pow_le_pow_right₀ (by linarith [(Nat.cast_nonneg (encCNF ψ).length : (0 : ℝ) ≤ _)]) ?_ + omega + have hQXQ : Q ≤ X * Q := le_mul_of_one_le_left (by positivity) hX1 + have hPXQ : P ≤ X * Q := hPQ.trans hQXQ + have hPeXQ : P ^ e ≤ X * Q := hPeQ.trans hQXQ + have h1XQ : (1 : ℝ) ≤ X * Q := one_le_mul_of_one_le_of_one_le hX1 hQ1 + dsimp only [T] + split_ifs with hψ + · have hψ' : ∀ c ∈ ψ, c.length ≤ k := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + have hnv : (toThreeOccurrenceDense ψ).numVars ≤ k * ψ.length := + numVars_toThreeOccurrenceDense_le_mul_length hψ' + have hexp : (2 : ℝ) ^ (δ * (toThreeOccurrenceDense ψ).numVars) ≤ X := by + refine Real.rpow_le_rpow_of_exponent_le (by norm_num) ?_ + have hnvR : ((toThreeOccurrenceDense ψ).numVars : ℝ) ≤ (k : ℝ) * ψ.length := by + exact_mod_cast hnv + have hmul := mul_le_mul_of_nonneg_left hnvR hδ + linarith + have hlen : ((encCNF (toThreeOccurrenceDense ψ)).length : ℝ) + 1 ≤ (2 * l + 1) * P := by + have hl' : ((encCNF (toThreeOccurrenceDense ψ)).length : ℝ) ≤ l * (P + 1) := by + have := hl ψ + simp only [P] + exact_mod_cast this + have hl0 : (0 : ℝ) ≤ l := Nat.cast_nonneg l + nlinarith + have hpow : (((encCNF (toThreeOccurrenceDense ψ)).length : ℝ) + 1) ^ e ≤ + (2 * l + 1) ^ e * P ^ e := by + rw [← mul_pow] + exact pow_le_pow_left₀ (by positivity) hlen e + have htf : (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ + c * (2 * l + 1) ^ e * X * Q + c * (X * Q) := by + have htψ := ht (toThreeOccurrenceDense ψ) + have hprod := mul_le_mul hexp hpow (by positivity) (by positivity) + have hprod' : (2 : ℝ) ^ (δ * (toThreeOccurrenceDense ψ).numVars) * + (((encCNF (toThreeOccurrenceDense ψ)).length : ℝ) + 1) ^ e ≤ + (2 * l + 1) ^ e * (X * Q) := by + have hPeX : X * ((2 * l + 1) ^ e * P ^ e) ≤ (2 * l + 1) ^ e * (X * Q) := by + have := mul_le_mul_of_nonneg_left hPeQ (by positivity : (0 : ℝ) ≤ (2 * l + 1) ^ e * X) + linarith + exact hprod.trans hPeX + have hcprod := mul_le_mul_of_nonneg_left hprod' (Nat.cast_nonneg c : (0 : ℝ) ≤ c) + have hc1 := mul_le_mul_of_nonneg_left h1XQ (Nat.cast_nonneg c : (0 : ℝ) ≤ c) + linarith + have hsum : P + (P + (tf (toThreeOccurrenceDense ψ) : ℝ)) ≤ K * (X * Q) := by + simp only [K] + linarith + have hceil : K * (X * Q) ≤ ⌈K⌉₊ * (X * Q) := + mul_le_mul_of_nonneg_right (Nat.le_ceil K) (by positivity) + have hC0 : (0 : ℝ) ≤ ⌈K⌉₊ := Nat.cast_nonneg _ + push_cast [P] at hsum ⊢ + linarith + · have hsum : P + P ≤ K * (X * Q) := by + have hc0 : (0 : ℝ) ≤ c * (2 * l + 1) ^ e + c := by positivity + have := mul_le_mul_of_nonneg_right hc0 (by positivity : (0 : ℝ) ≤ X * Q) + simp only [K] + linarith + have hceil : K * (X * Q) ≤ ⌈K⌉₊ * (X * Q) := + mul_le_mul_of_nonneg_right (Nat.le_ceil K) (by positivity) + have hC0 : (0 : ℝ) ≤ ⌈K⌉₊ := Nat.cast_nonneg _ + push_cast [P] at hsum ⊢ + linarith + +namespace CountingSparsify + +variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) + +include hM hK in +/-- **Counting sparsification from a dense 3-occurrence model counter.** +Under `CountingTreeBound`, given `2 ≤ k ≤ K` and a total model counter `f : CNF ℕ → ℕ` that +computes `χ.modelCount` on every dense formula `χ` of width `≤ k` and variable occurrence `≤ 3` and +has a combined time-and-space certificate `ComputableInO f encCNF encNat tf tf` with global bound +`(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c`, the search +`search M K k B (guardOccurrenceCounter k f)` computes `φ.modelCount` on every formula `φ` of width +`≤ k` and has a combined time-and-space certificate with fixed-parameter transfer bound +`O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ +theorem exists_computableInO_search_of_threeOccurrence_counter (hM2 : 2 ≤ M) {k B L_coeff : ℕ} + (hk2 : 2 ≤ k) (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → + search M K k B (guardOccurrenceCounter k f) φ = φ.modelCount) ∧ + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + refine ⟨fun φ hφ => search_eq_modelCount hM hK hM2 hkK hTB + (fun ψ hψ _ => guardOccurrenceCounter_eq_modelCount hk2 hf_eq hψ) hφ, ?_⟩ + obtain ⟨Tg, Cg, Eg, hgO, hgt⟩ := exists_computableInO_guardOccurrenceCounter k hf hδ ht + exact exists_computableInO_search hM hK hM2 hkK hTB hgO + (fun ψ _ _ => guardOccurrenceCounter_le_two_pow_numVars hk2 hf_eq ψ) + (by positivity) hgt + +end CountingSparsify + +end Cslib.Complexity diff --git a/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean b/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean new file mode 100644 index 000000000..f3e9defd4 --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.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.Complexity.FineGrained.ThreeOccurrenceReduction + +/-! +# Tests for bounded-occurrence reductions and counting-sparsification leaf reductions + +Concrete execution and non-vacuity checks for `toThreeOccurrenceDense`, `guardOccurrenceCounter`, +the `ThreeSATThree` exponential/subexponential reductions, and +`CountingSparsify.exists_computableInO_search_of_threeOccurrence_counter`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.Tests.ThreeOccurrenceReduction + +open Cslib.Logic.PL + +/-- A nontrivial formula with non-contiguous variable labels (`2, 5, 9`) and repeated variable +occurrences (`2` occurs 3 times, `5` occurs 2 times, `9` occurs once; 6 literal occurrences in +total). -/ +def sampleThreeCNF : CNF ℕ := + [[(2, true), (5, false)], [(2, false), (9, true)], [(2, true), (5, true)]] + +example : (toThreeOccurrenceDense ([] : CNF ℕ)).numVars = 0 := by decide +example : (toThreeOccurrenceDense ([] : CNF ℕ)).length = 0 := by decide +example : (toThreeOccurrenceDense ([] : CNF ℕ)).modelCount = 1 := by decide + +example : (toThreeOccurrenceDense ([[]] : CNF ℕ)).numVars = 0 := by decide +example : (toThreeOccurrenceDense ([[]] : CNF ℕ)).length = 1 := by decide +example : (toThreeOccurrenceDense ([[]] : CNF ℕ)).modelCount = 0 := by decide + +example : sampleThreeCNF.vars.length = 6 := by decide +example : (toThreeOccurrenceDense sampleThreeCNF).numVars = 6 := by decide +example : (toThreeOccurrenceDense sampleThreeCNF).vars.toFinset = Finset.range 6 := by decide +example : (toThreeOccurrenceDense sampleThreeCNF).length = 9 := by decide +example : (toThreeOccurrenceDense sampleThreeCNF).vars.length = 18 := by decide +example : + (toThreeOccurrenceDense sampleThreeCNF).all (fun c => decide (c.length ≤ 3)) = true := by + decide +example : + ((List.range 6).all fun v => + decide ((toThreeOccurrenceDense sampleThreeCNF).vars.count v ≤ 3)) = true := by + decide +example : + (toThreeOccurrenceDense sampleThreeCNF).modelCount = sampleThreeCNF.modelCount := + modelCount_toThreeOccurrenceDense sampleThreeCNF + +example : + guardOccurrenceCounter 3 CNF.modelCount sampleThreeCNF = sampleThreeCNF.modelCount := by + refine guardOccurrenceCounter_eq_modelCount (by decide : 2 ≤ 3) (fun _ _ _ _ => rfl) ?_ + decide + +example : + guardOccurrenceCounter 3 CNF.modelCount + [[(0, true), (1, true), (2, true), (3, true)]] = 0 := by + decide + +example : + CountingSparsify.search 2 3 3 2 (guardOccurrenceCounter 3 CNF.modelCount) sampleThreeCNF = + sampleThreeCNF.modelCount := by + decide + +example : Turing.MultiTapeTM.ComputableInPolyTime toThreeOccurrenceDense encCNF encCNF := + computableInPolyTime_toThreeOccurrenceDense + +example (h : DecidableInSubexpTime ThreeSATThree encCNF CNF.numVars) : ¬ ETH := + not_ETH_of_decidableInSubexpTime_threeSATThree_numVars h + +example (h : DecidableInSubexpTime ThreeSATThree encCNF List.length) : ¬ ETH := + not_ETH_of_decidableInSubexpTime_threeSATThree_length h + +example (ψ : CNF ℕ) : 0 < guardOccurrenceCounter 3 CNF.modelCount ψ ↔ ψ ∈ KSAT 3 := + guardOccurrenceCounter_pos_iff_mem_kSAT (by decide : 2 ≤ 3) (fun _ _ _ _ => rfl) ψ + +example {B L_coeff : ℕ} + (hTB : CountingSparsify.CountingTreeBound 2 3 B L_coeff (by decide) (by decide)) + {f tf : CNF ℕ → ℕ} + (hf : Turing.MultiTapeTM.ComputableInO f encCNF Turing.MultiTapeTM.encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ 3) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + ∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ 3) → + CountingSparsify.search 2 3 3 B (guardOccurrenceCounter 3 f) φ = φ.modelCount := + (CountingSparsify.exists_computableInO_search_of_threeOccurrence_counter + (by decide) (by decide) (by decide) (by decide) le_rfl hTB hf hf_eq hδ ht).1 + +end Cslib.Complexity.Tests.ThreeOccurrenceReduction From edae064b82c54842a2de908716fd256ceb9361eb Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 23:44:28 -0400 Subject: [PATCH 16/24] feat(Choose): bound geometric binomial tails by an integer budget --- .../Foundations/Data/Nat/Choose/Real.lean | 91 ++++++++++++++++++- .../Data/Nat/Choose/Geometric.lean | 56 ++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 ComplexityTests/Foundations/Data/Nat/Choose/Geometric.lean diff --git a/Complexity/Foundations/Data/Nat/Choose/Real.lean b/Complexity/Foundations/Data/Nat/Choose/Real.lean index 4fd05452f..8265f9eab 100644 --- a/Complexity/Foundations/Data/Nat/Choose/Real.lean +++ b/Complexity/Foundations/Data/Nat/Choose/Real.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 Samuel Schlesinger. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Samuel Schlesinger +Authors: Samuel Schlesinger, OpenAI -/ module @@ -10,6 +10,7 @@ module public import Complexity.Init public import Mathlib.Data.Nat.Choose.Sum public import Mathlib.Analysis.SpecialFunctions.Log.Base +import Mathlib.Analysis.Complex.ExponentialBounds import Mathlib.Tactic.Linarith import Mathlib.Tactic.Ring @@ -24,10 +25,17 @@ The generating-function proof is shared with the sparsification lemma. The logar consequences turn an exponential lower bound on the square of a partial binomial sum into size-activity bounds; they include the zero-radius case using the usual total real operations. +An exact integer logarithm test also bounds the tail of dimension `C * N` and radius +`Γ * N / M` by `2 ^ (N / B)` for every `N`. This is an original specialization of the +generating-function bound, motivated by the geometric thresholds in Appendix A.3 of +[OpenAI2026ETH]; it does not construct the parameters or bound the cost of selecting them. + ## Main results * `Cslib.sum_choose_le_exp_mul_pow`: a real-parameter generating-function bound. * `Cslib.sum_choose_le_exp_one_mul_div_pow`: the bound with parameter `q / e`. +* `Cslib.sum_choose_le_two_rpow_div`: the geometric tail under an exact integer budget test. +* `Cslib.sum_choose_le_two_pow_div_add_one`: its natural-number rounded form. * `Cslib.le_mul_logb_of_two_pow_le_sum_choose_sq`: the logarithmic consequence. * `Cslib.mul_rpow_le_of_two_pow_le_sum_choose_sq`: the rearranged size lower bound. @@ -37,6 +45,7 @@ size-activity bounds; they include the zero-radius case using the usual total re [ThresholdCircuitRepresentations2026] * [C. Calabro, R. Impagliazzo, R. Paturi, *A duality between clause width and clause density for SAT*][CalabroImpagliazzoPaturi2006] +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.3, pp. 46–47][OpenAI2026ETH] -/ @[expose] public section @@ -104,6 +113,86 @@ theorem sum_choose_le_exp_one_mul_div_pow {q e : ℕ} (heq : e ≤ q) : rw [mul_div_assoc, mul_pow, ← Real.exp_nat_mul] simp +/-- For any input size `N`, the tail with dimension `C * N` and radius `Γ * N / M` is at most +`2 ^ (N / B)` when the exact integer test `B * Γ * ⌈log₂ (4 * C * M)⌉ ≤ M` holds. +This is an original corollary of `sum_choose_le_exp_mul_pow`, motivated by Appendix A.3 of +[OpenAI2026ETH], rather than a verbatim result from that source. Zero input and zero radius are +included. The parameters are supplied; no parameter-selection or complexity bound is asserted. -/ +theorem sum_choose_le_two_rpow_div {C Γ M B : ℕ} (hC : 4 ≤ C) (hΓ : 4 ≤ Γ) + (hM : Γ ≤ M) (hB : 1 ≤ B) (hbudget : B * Γ * Nat.clog 2 (4 * C * M) ≤ M) + (N : ℕ) : + (∑ j ∈ range (Γ * N / M + 1), ((C * N).choose j : ℝ)) ≤ (2 : ℝ) ^ ((N : ℝ) / B) := by + have hC0 : (0 : ℝ) < C := by exact_mod_cast (show 0 < C by omega) + have hΓ0 : (0 : ℝ) < Γ := by exact_mod_cast (show 0 < Γ by omega) + have hM0 : (0 : ℝ) < M := by exact_mod_cast (show 0 < M by omega) + have hB0 : (0 : ℝ) < B := by exact_mod_cast (show 0 < B by omega) + let T : ℝ := C * M / Γ + let a : ℝ := Γ * N / M + let h := Nat.clog 2 (4 * C * M) + have hT : 1 ≤ T := by + apply (le_div_iff₀ hΓ0).mpr + have hCM : (1 : ℝ) ≤ C := by exact_mod_cast (show 1 ≤ C by omega) + have hΓM : (Γ : ℝ) ≤ M := by exact_mod_cast hM + nlinarith + have hT0 : 0 < T := lt_of_lt_of_le zero_lt_one hT + have ha : 0 ≤ a := by dsimp only [a]; positivity + have hradius : ((Γ * N / M : ℕ) : ℝ) ≤ a := by + apply (le_div_iff₀ hM0).mpr + exact_mod_cast Nat.div_mul_le_self (Γ * N) M + have hratio : ((C * N : ℕ) : ℝ) / T = a := by + dsimp only [T, a] + push_cast + field_simp + have hexp : Real.exp 1 ≤ (Γ : ℝ) := + Real.exp_one_lt_three.le.trans (by exact_mod_cast (show 3 ≤ Γ by omega)) + have hbase : Real.exp 1 * T ≤ (4 * C * M : ℕ) := by + calc Real.exp 1 * T ≤ Γ * T := mul_le_mul_of_nonneg_right hexp hT0.le + _ = C * M := by dsimp only [T]; field_simp + _ ≤ (4 * C * M : ℕ) := by push_cast; nlinarith + have hpow : ((4 * C * M : ℕ) : ℝ) ≤ (2 : ℝ) ^ h := by + exact_mod_cast Nat.le_pow_clog (by decide : 1 < 2) (4 * C * M) + have hexponent : (h : ℝ) * a ≤ (N : ℝ) / B := by + have hb : (B : ℝ) * Γ * h ≤ M := by exact_mod_cast hbudget + dsimp only [a] + rw [← mul_div_assoc, div_le_div_iff₀ hM0 hB0] + nlinarith [mul_le_mul_of_nonneg_right hb (Nat.cast_nonneg N)] + calc (∑ j ∈ range (Γ * N / M + 1), ((C * N).choose j : ℝ)) + ≤ Real.exp (((C * N : ℕ) : ℝ) / T) * T ^ (Γ * N / M) := + sum_choose_le_exp_mul_pow (C * N) (Γ * N / M) T hT + _ = Real.exp a * T ^ (Γ * N / M) := by rw [hratio] + _ ≤ Real.exp a * T ^ a := by + apply mul_le_mul_of_nonneg_left _ (Real.exp_pos a).le + rw [← Real.rpow_natCast] + exact Real.rpow_le_rpow_of_exponent_le hT hradius + _ = (Real.exp 1 * T) ^ a := by + rw [Real.mul_rpow (Real.exp_pos 1).le hT0.le, ← Real.exp_mul, one_mul] + _ ≤ ((4 * C * M : ℕ) : ℝ) ^ a := + Real.rpow_le_rpow (by positivity) hbase ha + _ ≤ ((2 : ℝ) ^ h) ^ a := Real.rpow_le_rpow (by positivity) hpow ha + _ = (2 : ℝ) ^ ((h : ℝ) * a) := by + rw [← Real.rpow_natCast, ← Real.rpow_mul (by norm_num)] + _ ≤ (2 : ℝ) ^ ((N : ℝ) / B) := + Real.rpow_le_rpow_of_exponent_le (by norm_num) hexponent + +/-- The natural-number rounding of `sum_choose_le_two_rpow_div`, suitable for a discrete +leaf-count or round budget. The division in the conclusion is natural-number division. -/ +theorem sum_choose_le_two_pow_div_add_one {C Γ M B : ℕ} (hC : 4 ≤ C) (hΓ : 4 ≤ Γ) + (hM : Γ ≤ M) (hB : 1 ≤ B) (hbudget : B * Γ * Nat.clog 2 (4 * C * M) ≤ M) + (N : ℕ) : + ∑ j ∈ range (Γ * N / M + 1), (C * N).choose j ≤ 2 ^ (N / B + 1) := by + have hB0 : (0 : ℝ) < B := by exact_mod_cast (show 0 < B by omega) + have hround : (N : ℝ) / B ≤ ((N / B + 1 : ℕ) : ℝ) := by + apply (div_le_iff₀ hB0).mpr + have hn : N < B * (N / B + 1) := by + have := Nat.mod_lt N (show 0 < B by omega) + have := Nat.div_add_mod N B + nlinarith + exact_mod_cast (show N ≤ (N / B + 1) * B by simpa only [Nat.mul_comm] using hn.le) + have h := (sum_choose_le_two_rpow_div hC hΓ hM hB hbudget N).trans + (Real.rpow_le_rpow_of_exponent_le (by norm_num) hround) + rw [Real.rpow_natCast] at h + exact_mod_cast h + /-- If the square of the radius-`e` binomial sum is at least `2 ^ m`, then `m / 2 ≤ e * log₂ (exp 1 * q / e)`. The statement includes `e = 0`, which forces `m = 0`. -/ theorem le_mul_logb_of_two_pow_le_sum_choose_sq {m q e : ℕ} (heq : e ≤ q) diff --git a/ComplexityTests/Foundations/Data/Nat/Choose/Geometric.lean b/ComplexityTests/Foundations/Data/Nat/Choose/Geometric.lean new file mode 100644 index 000000000..eade3a9ca --- /dev/null +++ b/ComplexityTests/Foundations/Data/Nat/Choose/Geometric.lean @@ -0,0 +1,56 @@ +import Complexity.Foundations.Data.Nat.Choose.Real + +/-! +Concrete controls for the geometric binomial-tail budget. These include a positive radius, a +nonintegral real exponent, zero input/radius, and cases where an omitted premise would fail. +-/ + +open Finset Cslib + +-- The accepted budget is 40 ≤ 64, the radius is one, and the tail counts 65 subsets: +-- one empty subset and 64 singletons. +example : + (∑ j ∈ range (4 * 16 / 64 + 1), ((4 * 16).choose j : ℝ)) = 65 ∧ + (∑ j ∈ range (4 * 16 / 64 + 1), ((4 * 16).choose j : ℝ)) ≤ (2 : ℝ) ^ (16 : ℕ) := by + constructor + · norm_num [sum_range_succ] + · simpa using sum_choose_le_two_rpow_div (C := 4) (Γ := 4) (M := 64) (B := 1) + (by decide) (by decide) (by decide) (by decide) (by decide) 16 + +-- Odd input size and B = 2 exercise both real division and the natural rounded consumer bound. +example : + (∑ j ∈ range (4 * 33 / 128 + 1), ((4 * 33).choose j : ℝ)) ≤ (2 : ℝ) ^ (33 / 2 : ℝ) ∧ + (∑ j ∈ range (4 * 33 / 128 + 1), (4 * 33).choose j) ≤ 2 ^ 17 := by + constructor + · exact sum_choose_le_two_rpow_div (C := 4) (Γ := 4) (M := 128) (B := 2) + (by decide) (by decide) (by decide) (by decide) (by decide) 33 + · exact sum_choose_le_two_pow_div_add_one (C := 4) (Γ := 4) (M := 128) (B := 2) + (by decide) (by decide) (by decide) (by decide) (by decide) 33 + +-- N = 0 has one empty subset, and its real upper bound is exactly one. +example : (∑ j ∈ range (4 * 0 / 64 + 1), ((4 * 0).choose j : ℝ)) ≤ 1 := by + exact (sum_choose_le_two_rpow_div (C := 4) (Γ := 4) (M := 64) (B := 1) + (by decide) (by decide) (by decide) (by decide) (by decide) 0).trans (by norm_num) + +-- Positive input need not give positive radius: 4 * 15 / 64 = 0. +example : + (∑ j ∈ range (4 * 15 / 64 + 1), ((4 * 15).choose j : ℝ)) = 1 ∧ + (∑ j ∈ range (4 * 15 / 64 + 1), (4 * 15).choose j) ≤ 2 ^ 16 := by + constructor + · norm_num + · exact sum_choose_le_two_pow_div_add_one (C := 4) (Γ := 4) (M := 64) (B := 1) + (by decide) (by decide) (by decide) (by decide) (by decide) 15 + +-- M ≥ Γ alone is insufficient: this rejected budget has a tail of 5, above 2^(N/B) = 2. +example : ¬ (1 * 4 * Nat.clog 2 (4 * 4 * 4) ≤ 4) ∧ + ¬ ((∑ j ∈ range (4 * 1 / 4 + 1), ((4 * 1).choose j : ℝ)) ≤ (2 : ℝ) ^ (1 / 1 : ℝ)) := by + constructor + · decide + · norm_num [sum_range_succ] + +-- Positivity of B matters even when the multiplied budget test itself holds. +example : 0 * 4 * Nat.clog 2 (4 * 4 * 64) ≤ 64 ∧ + ¬ ((∑ j ∈ range (4 * 16 / 64 + 1), ((4 * 16).choose j : ℝ)) ≤ (2 : ℝ) ^ (16 / 0 : ℝ)) := by + constructor + · decide + · norm_num [sum_range_succ] From be22d34c1340bb86783c4c948911243048af51e0 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 04:12:42 +0000 Subject: [PATCH 17/24] feat(FineGrained): discharge CountingTreeBound from counting path and binomial budgets --- Complexity.lean | 10 +- .../FineGrained/CountingTreeBound.lean | 171 ++++++++++ .../Sparsification/CountingBounds.lean | 304 ++++++++++++++++++ .../FineGrained/CountingTreeBound.lean | 78 +++++ 4 files changed, 559 insertions(+), 4 deletions(-) create mode 100644 Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean create mode 100644 ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean diff --git a/Complexity.lean b/Complexity.lean index f8b825cdf..7d6440fb1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -764,6 +764,7 @@ public import Complexity.Computability.Complexity.FineGrained.BruteForce public import Complexity.Computability.Complexity.FineGrained.CountingEndpoint public import Complexity.Computability.Complexity.FineGrained.CountingScale public import Complexity.Computability.Complexity.FineGrained.CountingSparsification +public import Complexity.Computability.Complexity.FineGrained.CountingTreeBound public import Complexity.Computability.Complexity.FineGrained.ExhaustiveSearch public import Complexity.Computability.Complexity.FineGrained.Exponents public import Complexity.Computability.Complexity.FineGrained.GraphProblems @@ -2173,15 +2174,16 @@ public import Complexity.Logics.Propositional.ClauseSet.Basic public import Complexity.Logics.Propositional.ClauseSet.Sparsification public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Bounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Counting -public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList -public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime -public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch -public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingBounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingDeletion public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingInsertions +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPathBounds +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean new file mode 100644 index 000000000..394bb063e --- /dev/null +++ b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -0,0 +1,171 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Complexity.Computability.Complexity.FineGrained.CountingSparsification +public import Complexity.Computability.Complexity.FineGrained.ThreeOccurrenceReduction +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingBounds + +/-! +# Discharging `CountingTreeBound` for Counting Sparsification + +We discharge the quantitative depth and leaf-count hypothesis `CountingSparsify.CountingTreeBound` +of `CountingSparsification` from the actual path budgets and binomial leaf-word bounds in +`CountingBounds` (Appendix A.2–A.3, Lemmas A.2–A.3, printed pp. 46–47 of [OpenAI2026ETH]). + +Specifically, for any parameters `M, K, B : ℕ` with `2 ≤ M`, `1 ≤ K`, and `1 ≤ B` satisfying the +integer parameter budget +* `countingBStepCoeff K ≤ M`, and +* `B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M` +whenever `2 ≤ K`, `CountingSparsify.countingTreeBound_of_budget` proves +`CountingSparsify.CountingTreeBound M K B (countingDepthCoeff K M) hM hK`. + +We then specialize the correctness and native `ComputableInO` transfer theorems of +`CountingSparsification` and `ThreeOccurrenceReduction` so that downstream clients need only supply +the integer parameter budget on `(M, K, B)` rather than an external `CountingTreeBound` witness. + +## Main results + +* `Cslib.Complexity.CountingSparsify.countingTreeBound_of_budget`: discharges `CountingTreeBound` + with linear depth coefficient `countingDepthCoeff K M` from the integer parameter budget. +* `Cslib.Complexity.CountingSparsify.search_eq_ite_modelCount_of_budget`, + `Cslib.Complexity.CountingSparsify.search_eq_modelCount_of_budget`: model-count correctness of + `CountingSparsify.search` under the integer parameter budget. +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_of_budget`: native + `ComputableInO ... T T` time-and-space transfer certificate for `CountingSparsify.search` under + the integer parameter budget. +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_of_budget`: + end-to-end counting-sparsification reduction from a dense occurrence-`≤ 3` model counter under + the integer parameter budget. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Section 6.4 and + Appendix A.2–A.3][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification Cslib.Logic.PL.ClauseSet.Sparsification +open Turing.MultiTapeTM + +namespace CountingSparsify + +variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) + +include hM hK in +/-- **Discharging `CountingTreeBound` from the integer parameter budget.** +For `2 ≤ M`, `1 ≤ K`, and `1 ≤ B`, if `2 ≤ K` implies `countingBStepCoeff K ≤ M` and +`B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M`, then +`CountingTreeBound M K B (countingDepthCoeff K M) hM hK` holds (Appendix A.3, Lemma A.3, printed +p. 47 [OpenAI2026ETH]). -/ +theorem countingTreeBound_of_budget (hM2 : 2 ≤ M) {B : ℕ} (hB : 1 ≤ B) + (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ + B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) : + CountingTreeBound M K B (countingDepthCoeff K M) hM hK := by + intro ψ declared hsupport + have hnt : ∀ C ∈ ψ.1.toClauseSet, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by + intro C' hC' l hl hneg + obtain ⟨C, hC, rfl⟩ := mem_toClauseSet.mp hC' + exact ψ.2.nontautological C hC l (List.mem_toFinset.mp hl) (List.mem_toFinset.mp hneg) + have hsup : ∀ C ∈ ψ.1.toClauseSet, ∀ l ∈ C, l.1 ∈ declared := by + intro C' hC' l hl + obtain ⟨C, hC, rfl⟩ := mem_toClauseSet.mp hC' + exact hsupport C hC l (List.mem_toFinset.mp hl) + refine ⟨fun w hw => ?_, fun W hW => ?_⟩ + · exact CountingBranchingRule.IsLeafWord.length_le (R := rule hM hK) hM2 ψ.2.reduced + hnt ψ.2.card_le declared hsup hw + · exact CountingBranchingRule.card_le_two_pow_of_isLeafWord (R := rule hM hK) hM2 + ψ.2.reduced hnt ψ.2.card_le declared hsup hB hbudget W hW + +include hM hK in +/-- **Model-count correctness of `search` under the integer parameter budget.** +If `g` computes `ψ.modelCount` on sparse formulas `ψ` of width at most `k`, then +`search M K k B g φ` returns `φ.modelCount` on formulas of width at most `k` and `0` otherwise. -/ +theorem search_eq_ite_modelCount_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) + (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ + B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) + (φ : CNF ℕ) : + search M K k B g φ = + if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0 := + search_eq_ite_modelCount hM hK hM2 hkK (countingTreeBound_of_budget hM hK hM2 hB hbudget) hg φ + +include hM hK in +/-- **Model-count correctness of `search` on width-`≤ k` formulas under the integer parameter +budget.** -/ +theorem search_eq_modelCount_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) + (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ + B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) + {φ : CNF ℕ} (hφ : ∀ c ∈ φ, c.length ≤ k) : + search M K k B g φ = φ.modelCount := + search_eq_modelCount hM hK hM2 hkK (countingTreeBound_of_budget hM hK hM2 hB hbudget) hg hφ + +include hM hK in +/-- **Time and space of `search` under the integer parameter budget.** +Given a total leaf routine `g` with `hg : ComputableInO g encCNF encNat t t` satisfying the global +bound `ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : CNF ℕ` and bounded by +`2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` has a combined time-and-space +certificate `ComputableInO (search M K k B g) encCNF encNat T T` with fixed-parameter transfer +bound `T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀`. -/ +theorem exists_computableInO_search_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) + (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ + B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B g) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search hM hK hM2 hkK (countingTreeBound_of_budget hM hK hM2 hB hbudget) + hg hg_le hδ ht + +include hM hK in +/-- **End-to-end counting sparsification from a dense 3-occurrence model counter under the integer +parameter budget.** +Given `2 ≤ k ≤ K` and a total model counter `f : CNF ℕ → ℕ` that computes `χ.modelCount` on every +dense formula `χ` of width `≤ k` and variable occurrence `≤ 3` and has a combined time-and-space +certificate `ComputableInO f encCNF encNat tf tf` with global bound +`(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c`, the search +`search M K k B (guardOccurrenceCounter k f)` computes `φ.modelCount` on every formula `φ` of width +`≤ k` and has a combined time-and-space certificate with fixed-parameter transfer bound +`O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ +theorem exists_computableInO_search_threeOccurrence_of_budget + (hM2 : 2 ≤ M) {k B : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) + (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ + B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → + search M K k B (guardOccurrenceCounter k f) φ = φ.modelCount) ∧ + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_of_threeOccurrence_counter hM hK hM2 hk2 hkK + (countingTreeBound_of_budget hM hK hM2 hB hbudget) hf hf_eq hδ ht + +end CountingSparsify + +end Cslib.Complexity diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean new file mode 100644 index 000000000..b1b56cc3d --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean @@ -0,0 +1,304 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Complexity.Foundations.Data.Nat.Choose.Real +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPathBounds +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch + +/-! +# Depth and Leaf-Count Bounds for Counting Sparsification + +We connect the actual counting-path insertion and petal-step bounds of `CountingPathBounds` +(Appendix A.2–A.3, Lemma A.2 and the path budgets of Lemma A.3, printed pp. 46–47 of +[OpenAI2026ETH]) to the prefix words of a `CountingBranchingRule`, and combine the leaf-word +petal-step injection with the geometric binomial-tail bound +`Cslib.sum_choose_le_two_pow_div_add_one` to bound the number of leaf words of any +counting-sparsification tree. + +For a `CountingBranchingRule (fun r => M ^ r) V σ` starting from a state `s` whose clause set +`R.toClauseSet s` is `Reduced`, nontautological, of width at most `k`, and supported on a finite +variable set `declared` of size `N = declared.card`: +* every node word `w` has length at most `countingDepthCoeff k M * N`, where + `countingDepthCoeff k M = 4 * ∑ r < k - 1, (3 * M) ^ r`; +* every node word `w` has `M * #{i < w.length | w[i] = true} ≤ countingBStepCoeff k * N`, where + `countingBStepCoeff k = 4 * ∑ r < k - 1, 3 ^ r`; +* because two leaf words with `true` steps at the same positions are equal + (`CountingBranchingRule.IsLeafWord.eq_of_getD_eq`), any finset `W` of leaf words injects into the + subsets of `range (countingDepthCoeff k M * N)` of cardinality at most + `countingBStepCoeff k * N / M`, giving + `W.card ≤ ∑ j ≤ countingBStepCoeff k * N / M, (countingDepthCoeff k M * N).choose j` + (`CountingBranchingRule.card_le_of_isLeafWord`); +* under the integer parameter budget `countingBStepCoeff k ≤ M` and + `B * countingBStepCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M` (for `2 ≤ k`, with + `1 ≤ B`), `W.card ≤ 2 ^ (N / B + 1)` (`CountingBranchingRule.card_le_two_pow_of_isLeafWord`). + +## Main definitions + +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingDepthCoeff`: the linear depth coefficient + `4 * ∑ r ∈ range (k - 1), (3 * M) ^ r`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingBStepCoeff`: the linear `M`-scaled petal-step + coefficient `4 * ∑ r ∈ range (k - 1), 3 ^ r`. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsLeafWord.eq_of_getD_eq`: two + leaf words with `true` steps at the same positions are equal. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsNodeWord.length_le`, + `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsLeafWord.length_le`: linear depth + bound along node and leaf words. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsNodeWord.mul_card_le`, + `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.IsLeafWord.mul_card_le`: `M`-scaled + petal-step budget along node and leaf words. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.card_le_of_isLeafWord`: + binomial-sum bound on any finset of leaf words. +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.card_le_two_pow_of_isLeafWord`: + exponential bound `2 ^ (declared.card / B + 1)` on any finset of leaf words under the integer + parameter budget. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.3, Lemma A.3][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +variable {V : Type*} [DecidableEq V] + +/-! ### Linear depth and petal-step coefficients -/ + +/-- The linear depth coefficient `4 * ∑_{r < k - 1} (3 * M) ^ r` bounding the length of a counting +branching path of width at most `k` (Appendix A.3, Lemma A.3, printed p. 47 [OpenAI2026ETH]). -/ +def countingDepthCoeff (k M : ℕ) : ℕ := + 4 * ∑ r ∈ range (k - 1), (3 * M) ^ r + +/-- The linear petal-step coefficient `4 * ∑_{r < k - 1} 3 ^ r` in the `M`-scaled petal-step budget +`M * #B ≤ countingBStepCoeff k * N` (Appendix A.3, Lemma A.3, printed p. 47 [OpenAI2026ETH]). -/ +def countingBStepCoeff (k : ℕ) : ℕ := + 4 * ∑ r ∈ range (k - 1), 3 ^ r + +theorem countingDepthCoeff_eq_zero {k : ℕ} (hk : k ≤ 1) (M : ℕ) : + countingDepthCoeff k M = 0 := by + have hk0 : k - 1 = 0 := by omega + simp [countingDepthCoeff, hk0] + +theorem countingBStepCoeff_eq_zero {k : ℕ} (hk : k ≤ 1) : + countingBStepCoeff k = 0 := by + have hk0 : k - 1 = 0 := by omega + simp [countingBStepCoeff, hk0] + +theorem four_le_countingDepthCoeff {k : ℕ} (hk : 2 ≤ k) (M : ℕ) : + 4 ≤ countingDepthCoeff k M := by + have h0 : 0 ∈ range (k - 1) := mem_range.mpr (by omega) + have hsum := single_le_sum (f := fun r => (3 * M) ^ r) (fun _ _ => Nat.zero_le _) h0 + rw [pow_zero] at hsum + unfold countingDepthCoeff + omega + +theorem four_le_countingBStepCoeff {k : ℕ} (hk : 2 ≤ k) : + 4 ≤ countingBStepCoeff k := by + have h0 : 0 ∈ range (k - 1) := mem_range.mpr (by omega) + have hsum := single_le_sum (f := fun r => 3 ^ r) (fun _ _ => Nat.zero_le _) h0 + rw [pow_zero] at hsum + unfold countingBStepCoeff + omega + +/-! ### Prefix paths and leaf-count bounds for a counting branching rule -/ + +namespace CountingBranchingRule + +variable {θ : ℕ → ℕ} {σ : Type*} (R : CountingBranchingRule θ V σ) + +private theorem walk_append (s : σ) (u v : List Bool) : + R.walk s (u ++ v) = R.walk (R.walk s u) v := by + induction u generalizing s with + | nil => rfl + | cons a u ih => exact ih (R.next s a) + +private theorem walk_take_succ (s : σ) (w : List Bool) {i : ℕ} (hi : i < w.length) : + R.walk s (w.take (i + 1)) = R.next (R.walk s (w.take i)) (w.getD i false) := by + rw [List.take_add_one, List.getElem?_eq_getElem hi, Option.toList_some, walk_append, + List.getD_eq_getElem?_getD, List.getElem?_eq_getElem hi, Option.getD_some] + rfl + +variable {R} + +private theorem IsNodeWord.not_sparse_take {s : σ} {w : List Bool} (h : R.IsNodeWord s w) : + ∀ i < w.length, ¬ Sparse θ (R.toClauseSet (R.walk s (w.take i))) := by + induction w generalizing s with + | nil => intro i hi; simp at hi + | cons b w ih => + intro i hi + cases i with + | zero => exact h.1 + | succ i => + rw [List.take_succ_cons, walk] + exact ih h.2 i (by simpa using hi) + +/-- **Leaf words are determined by their petal steps.** Two leaf words with `true` steps at the +same positions are equal. -/ +theorem IsLeafWord.eq_of_getD_eq {s : σ} {w w' : List Bool} (hw : R.IsLeafWord s w) + (hw' : R.IsLeafWord s w') (h : ∀ i, w.getD i false = w'.getD i false) : w = w' := by + induction w generalizing s w' with + | nil => + cases w' with + | nil => rfl + | cons b' w' => exact absurd hw hw'.1 + | cons b w ih => + cases w' with + | nil => exact absurd hw' hw.1 + | cons b' w' => + have hb : b = b' := by simpa using h 0 + subst hb + rw [ih hw.2 hw'.2 fun i => by simpa using h (i + 1)] + +/-- Every node word `w` of a counting branching rule induces a sequence of valid choices +`(c i, H i)` along its prefixes `w.take i`. -/ +theorem exists_choice_seq_of_isNodeWord (s : σ) {w : List Bool} (hw : R.IsNodeWord s w) : + ∃ (c : ℕ → ℕ) (H : ℕ → Finset (Literal V)), + (∀ i < w.length, + R.toClauseSet (R.walk s (w.take (i + 1))) = + countingChild (R.toClauseSet (R.walk s (w.take i))) (c i) (H i) (w.getD i false)) ∧ + (∀ i < w.length, Choice θ (R.toClauseSet (R.walk s (w.take i))) (c i) (H i)) := by + classical + have hns := hw.not_sparse_take + have hch (i : ℕ) (hi : i < w.length) := R.exists_choice_next _ (hns i hi) + let c (i : ℕ) : ℕ := if hi : i < w.length then (hch i hi).choose else 0 + let H (i : ℕ) : Finset (Literal V) := + if hi : i < w.length then (hch i hi).choose_spec.choose else ∅ + have hspec (i : ℕ) (hi : i < w.length) : + Choice θ (R.toClauseSet (R.walk s (w.take i))) (c i) (H i) ∧ + ∀ b, R.toClauseSet (R.next (R.walk s (w.take i)) b) = + countingChild (R.toClauseSet (R.walk s (w.take i))) (c i) (H i) b := by + simp only [c, H, dite_eq_left hi] + exact (hch i hi).choose_spec.choose_spec + refine ⟨c, H, fun i hi => ?_, fun i hi => (hspec i hi).1⟩ + rw [walk_take_succ R s w hi] + exact (hspec i hi).2 _ + +section PathBounds + +variable {M k : ℕ} (hM : 2 ≤ M) + {R : CountingBranchingRule (fun r => M ^ r) V σ} {s : σ} + (hred : Reduced (R.toClauseSet s)) + (hnt : ∀ C ∈ R.toClauseSet s, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (hk : ∀ C ∈ R.toClauseSet s, C.card ≤ k) + (declared : Finset V) + (hsupport : ∀ C ∈ R.toClauseSet s, ∀ l ∈ C, l.1 ∈ declared) +include hM hred hnt hk hsupport + +/-- **Node words are short.** From a reduced, nontautological clause set of width `≤ k` over +`declared`, every node word has length at most `countingDepthCoeff k M * declared.card`. -/ +theorem IsNodeWord.length_le {w : List Bool} (hw : R.IsNodeWord s w) : + w.length ≤ countingDepthCoeff k M * declared.card := by + obtain ⟨c, H, hstep, hchoice⟩ := exists_choice_seq_of_isNodeWord s hw + exact counting_length_le hM w.length (fun i => R.toClauseSet (R.walk s (w.take i))) + c H (fun i => w.getD i false) hstep hchoice hred hnt hk declared hsupport + +/-- **Node words have few petal steps.** From a reduced, nontautological clause set of width `≤ k` +over `declared`, every node word satisfies +`M * #{i < w.length | w.getD i false = true} ≤ countingBStepCoeff k * declared.card`. -/ +theorem IsNodeWord.mul_card_le {w : List Bool} (hw : R.IsNodeWord s w) : + M * ((range w.length).filter fun i => w.getD i false = true).card ≤ + countingBStepCoeff k * declared.card := by + obtain ⟨c, H, hstep, hchoice⟩ := exists_choice_seq_of_isNodeWord s hw + exact mul_card_counting_B_le hM w.length (fun i => R.toClauseSet (R.walk s (w.take i))) + c H (fun i => w.getD i false) hstep hchoice hred hnt hk declared hsupport + +/-- **Leaf words are short.** From a reduced, nontautological clause set of width `≤ k` over +`declared`, every leaf word has length at most `countingDepthCoeff k M * declared.card`. -/ +theorem IsLeafWord.length_le {w : List Bool} (hw : R.IsLeafWord s w) : + w.length ≤ countingDepthCoeff k M * declared.card := + hw.isNodeWord.length_le hM hred hnt hk declared hsupport + +/-- **Leaf words have few petal steps.** From a reduced, nontautological clause set of width `≤ k` +over `declared`, every leaf word satisfies +`M * #{i < w.length | w.getD i false = true} ≤ countingBStepCoeff k * declared.card`. -/ +theorem IsLeafWord.mul_card_le {w : List Bool} (hw : R.IsLeafWord s w) : + M * ((range w.length).filter fun i => w.getD i false = true).card ≤ + countingBStepCoeff k * declared.card := + hw.isNodeWord.mul_card_le hM hred hnt hk declared hsupport + +/-- **Binomial-sum leaf-word bound.** From a reduced, nontautological clause set of width `≤ k` over +`declared`, any finset `W` of leaf words has cardinality at most +`∑_{j ≤ countingBStepCoeff k * declared.card / M} + (countingDepthCoeff k M * declared.card).choose j`. -/ +theorem card_le_of_isLeafWord (W : Finset (List Bool)) (hW : ∀ w ∈ W, R.IsLeafWord s w) : + W.card ≤ ∑ j ∈ range (countingBStepCoeff k * declared.card / M + 1), + (countingDepthCoeff k M * declared.card).choose j := by + classical + set Λ := countingDepthCoeff k M * declared.card + set ρ := countingBStepCoeff k * declared.card / M + let pos (w : List Bool) : Finset ℕ := (range Λ).filter fun i => w.getD i false = true + have hpos : ∀ w ∈ W, pos w ∈ (range Λ).powerset.filter (·.card ≤ ρ) := by + intro w hw + have hlen := (hW w hw).length_le hM hred hnt hk declared hsupport + refine mem_filter.mpr ⟨mem_powerset.mpr (filter_subset _ _), ?_⟩ + refine (Nat.le_div_iff_mul_le (by omega)).mpr ((Nat.mul_comm _ _).trans_le + ((Nat.mul_le_mul_left M (card_le_card fun i hi => ?_)).trans + ((hW w hw).mul_card_le hM hred hnt hk declared hsupport))) + obtain ⟨-, hi⟩ := mem_filter.mp hi + refine mem_filter.mpr ⟨mem_range.mpr ?_, hi⟩ + by_contra hge + rw [List.getD_eq_getElem?_getD, List.getElem?_eq_none (by omega)] at hi + exact Bool.false_ne_true hi + have hinj : Set.InjOn pos W := by + intro w hw w' hw' h + refine (hW w hw).eq_of_getD_eq (hW w' hw') fun i => ?_ + by_cases hi : i < Λ + · have := congrArg (i ∈ ·) h + simp only [pos, mem_filter, mem_range, hi, true_and, eq_iff_iff] at this + cases hwi : w.getD i false <;> cases hw'i : w'.getD i false <;> simp_all + · have hl := (hW w hw).length_le hM hred hnt hk declared hsupport + have hl' := (hW w' hw').length_le hM hred hnt hk declared hsupport + rw [List.getD_eq_getElem?_getD, List.getElem?_eq_none (by omega), + List.getD_eq_getElem?_getD, List.getElem?_eq_none (by omega)] + calc W.card = (W.image pos).card := (card_image_of_injOn hinj).symm + _ ≤ ((range Λ).powerset.filter (·.card ≤ ρ)).card := + card_le_card fun t ht => by + obtain ⟨w, hw, rfl⟩ := mem_image.mp ht + exact hpos w hw + _ ≤ ((range (ρ + 1)).biUnion fun j => (range Λ).powersetCard j).card := by + refine card_le_card fun t ht => ?_ + obtain ⟨htΛ, htρ⟩ := mem_filter.mp ht + exact mem_biUnion.mpr ⟨t.card, mem_range.mpr (by omega), + mem_powersetCard.mpr ⟨mem_powerset.mp htΛ, rfl⟩⟩ + _ ≤ ∑ j ∈ range (ρ + 1), ((range Λ).powersetCard j).card := card_biUnion_le + _ = ∑ j ∈ range (ρ + 1), Λ.choose j := by + refine sum_congr rfl fun j _ => ?_ + rw [card_powersetCard, card_range] + +/-- **Exponential leaf-word bound.** Under the integer parameter budget `countingBStepCoeff k ≤ M` +and `B * countingBStepCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M` (for `2 ≤ k`, with +`1 ≤ B`), any finset `W` of leaf words has cardinality at most `2 ^ (declared.card / B + 1)`. -/ +theorem card_le_two_pow_of_isLeafWord {B : ℕ} (hB : 1 ≤ B) + (hbudget : 2 ≤ k → countingBStepCoeff k ≤ M ∧ + B * countingBStepCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M) + (W : Finset (List Bool)) (hW : ∀ w ∈ W, R.IsLeafWord s w) : + W.card ≤ 2 ^ (declared.card / B + 1) := by + refine (card_le_of_isLeafWord hM hred hnt hk declared hsupport W hW).trans ?_ + by_cases hk2 : 2 ≤ k + · obtain ⟨hΓM, hbud⟩ := hbudget hk2 + exact Cslib.sum_choose_le_two_pow_div_add_one + (four_le_countingDepthCoeff hk2 M) (four_le_countingBStepCoeff hk2) + hΓM hB hbud declared.card + · have hk1 : k ≤ 1 := by omega + rw [countingDepthCoeff_eq_zero hk1 M, countingBStepCoeff_eq_zero hk1] + simp only [zero_mul, Nat.zero_div, zero_add, range_one, sum_singleton, Nat.choose_zero_right] + exact Nat.one_le_two_pow + +end PathBounds + +end CountingBranchingRule + +end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean new file mode 100644 index 000000000..1ce1d2a77 --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -0,0 +1,78 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Complexity.Computability.Complexity.FineGrained.CountingTreeBound + +/-! +# Tests for `CountingBounds` and `CountingTreeBound` +-/ + +@[expose] public section + +namespace Cslib.Complexity.CountingTreeBoundTests + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification Cslib.Logic.PL.ClauseSet.Sparsification +open Cslib.Complexity.CountingSparsify Turing.MultiTapeTM + +example : countingDepthCoeff 1 2 = 0 := by decide + +example : countingBStepCoeff 1 = 0 := by decide + +example : countingDepthCoeff 2 2 = 4 := by decide + +example : countingBStepCoeff 2 = 4 := by decide + +example : countingDepthCoeff 3 2 = 28 := by decide + +example : countingBStepCoeff 3 = 16 := by decide + +/-- Unconditional `CountingTreeBound` witness for `K = 1, B = 1, M = 2`. -/ +example : CountingTreeBound 2 1 1 (countingDepthCoeff 1 2) (by decide) (by decide) := + countingTreeBound_of_budget (by decide) (by decide) (by decide) (by decide) (by decide) + +/-- Unconditional `CountingTreeBound` witness for 2-CNF (`K = 2, B = 1, M = 64`), verified by +`decide` on the integer parameter budget `4 * Nat.clog 2 (4 * 4 * 64) = 40 ≤ 64`. -/ +example : CountingTreeBound 64 2 1 (countingDepthCoeff 2 64) (by decide) (by decide) := + countingTreeBound_of_budget (by decide) (by decide) (by decide) (by decide) (by decide) + +/-- Unconditional `CountingTreeBound` witness for 3-CNF (`K = 3, B = 1, M = 512`), verified by +`decide` on the integer parameter budget `16 * Nat.clog 2 (4 * 6148 * 512) = 384 ≤ 512`. -/ +example : CountingTreeBound 512 3 1 (countingDepthCoeff 3 512) (by decide) (by decide) := + countingTreeBound_of_budget (by decide) (by decide) (by decide) (by decide) (by decide) + +/-- Model-count correctness of `CountingSparsify.search` on 3-CNF at `(M, K, k, B) = (512, 3, 3, 1)` +without an external `CountingTreeBound` hypothesis. -/ +example {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ 3) → + isSparse (countingThreshold 512) 3 ψ = true → g ψ = ψ.modelCount) + {φ : CNF ℕ} (hφ : ∀ c ∈ φ, c.length ≤ 3) : + search 512 3 3 1 g φ = φ.modelCount := + search_eq_modelCount_of_budget (by decide) (by decide) (by decide) (by decide) (by decide) + (by decide) hg hφ + +/-- End-to-end 3-CNF counting-sparsification reduction from a dense 3-occurrence model counter at +`(M, K, k, B) = (512, 3, 3, 1)` without an external `CountingTreeBound` hypothesis. -/ +example {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ 3) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ 3) → + search 512 3 3 1 (guardOccurrenceCounter 3 f) φ = φ.modelCount) ∧ + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search 512 3 3 1 (guardOccurrenceCounter 3 f)) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / (1 : ℕ) + occBound 3 512 * (3 * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_threeOccurrence_of_budget + (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) + hf hf_eq hδ ht + +end Cslib.Complexity.CountingTreeBoundTests From 12c91ebe67244a7eb1b3fd44a1f87257eaf87630 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 04:20:27 +0000 Subject: [PATCH 18/24] refactor(FineGrained): extract general CNF relabel/count lemmas and add promised-runtime frontends --- .../FineGrained/ThreeOccurrenceReduction.lean | 158 ++++++++++-------- .../Propositional/CNF/Compact/Relabel.lean | 18 +- .../CNF/ModelCount/DenseRelabel.lean | 29 +++- .../FineGrained/ThreeOccurrenceReduction.lean | 15 ++ 4 files changed, 148 insertions(+), 72 deletions(-) diff --git a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean index 66ccab2b5..7da861057 100644 --- a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean +++ b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean @@ -36,11 +36,6 @@ pp. 43–47). ## Main results -* `Cslib.Logic.PL.CNF.numVars_relabel_of_injOn`, `Cslib.Logic.PL.CNF.numVars_relabel`: - injective variable relabelings preserve `CNF.numVars`. -* `Cslib.Logic.PL.CNF.count_vars_denseRelabel`, - `Cslib.Logic.PL.CNF.count_vars_denseRelabel_le`: `CNF.denseRelabel` preserves per-variable - literal-occurrence counts and upper bounds. * `Cslib.Complexity.computableInPolyTime_toThreeOccurrenceDense`, `Cslib.Complexity.modelCount_toThreeOccurrenceDense`, `Cslib.Complexity.vars_toThreeOccurrenceDense_toFinset`, @@ -55,13 +50,15 @@ pp. 43–47). `Cslib.Complexity.ETH.not_decidableInSubexpTime_threeSATThree_numVars`: fine-grained exponential and subexponential reductions from `ThreeSAT` to `ThreeSATThree` in both `CNF.numVars` and `List.length`. -* `Cslib.Complexity.exists_computableInO_guardOccurrenceCounter`, +* `Cslib.Complexity.exists_computableInO_guardOccurrenceCounter_of_promise`, + `Cslib.Complexity.exists_computableInO_guardOccurrenceCounter`, + `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_of_promise`, `Cslib.Complexity.CountingSparsify.exists_computableInO_search_of_threeOccurrence_counter`: a total model counter `f` running in time and space `O(2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e)` - yields a clause-exponential leaf counter in time and space - `O(2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E)`, and hence (under `CountingTreeBound`, a - library adaptation of Appendix A.2–A.3) a general width-`≤ k` model counter via - `CountingSparsify.search` in time and space + (either globally or on dense width-`≤ k`, occurrence-`≤ 3` formulas) yields a clause-exponential + leaf counter in time and space `O(2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E)`, and hence + (under `CountingTreeBound`, a library adaptation of Appendix A.2–A.3) a general width-`≤ k` model + counter via `CountingSparsify.search` in time and space `O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E')`. ## References @@ -75,48 +72,6 @@ pp. 43–47). @[expose] public section -namespace Cslib.Logic.PL.CNF - -variable {V W : Type*} - -/-- Relabeling injectively on the occurring variables preserves `numVars`. -/ -theorem numVars_relabel_of_injOn [DecidableEq V] [DecidableEq W] (φ : CNF V) (f : V → W) - (hf : Set.InjOn f {v | v ∈ φ.vars}) : - (φ.relabel f).numVars = φ.numVars := by - rw [ ← length_distinctVars, ← length_distinctVars, distinctVars_relabel_of_injOn φ f hf, - List.length_map] - -/-- Relabeling along an injective map preserves `numVars`. -/ -@[simp] -theorem numVars_relabel [DecidableEq V] [DecidableEq W] {f : V → W} (hf : Function.Injective f) - (φ : CNF V) : (φ.relabel f).numVars = φ.numVars := - numVars_relabel_of_injOn φ f fun _ _ _ _ h => hf h - -/-- Dense variable relabeling preserves the exact literal-occurrence count of every occurring -variable. -/ -theorem count_vars_denseRelabel [DecidableEq V] (φ : CNF V) {v : V} (hv : v ∈ φ.vars) : - φ.denseRelabel.vars.count (φ.distinctVars.idxOf v) = φ.vars.count v := by - rw [denseRelabel, vars_relabel] - simp only [List.count_eq_countP, List.countP_map, Function.comp_def] - apply List.countP_congr - intro w _ - simp only [beq_iff_eq] - rw [eq_comm, List.idxOf_inj (mem_distinctVars.mpr hv), eq_comm] - -/-- Any upper bound on original literal-occurrence counts is preserved by `CNF.denseRelabel`. -/ -theorem count_vars_denseRelabel_le [DecidableEq V] (φ : CNF V) {B : ℕ} - (h : ∀ v, φ.vars.count v ≤ B) (i : ℕ) : - φ.denseRelabel.vars.count i ≤ B := by - by_cases hi : i ∈ φ.denseRelabel.vars - · rw [denseRelabel, vars_relabel] at hi - obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hi - rw [count_vars_denseRelabel φ hv] - exact h v - · rw [List.count_eq_zero.mpr hi] - exact Nat.zero_le _ - -end Cslib.Logic.PL.CNF - namespace Cslib.Complexity open Cslib.Logic.PL Turing.MultiTapeTM @@ -378,15 +333,13 @@ theorem guardOccurrenceCounter_pos_iff_mem_kSAT {k : ℕ} (hk : 2 ≤ k) {f : CN simp only [hψ, Bool.false_eq_true, ite_false, lt_irrefl, KSAT, Set.mem_ofPred_eq, hψ', false_and] -/-- **From variable-exponential occurrence-3 counting to clause-exponential leaf counting.** -If `f : CNF ℕ → ℕ` has a combined time-and-space certificate `ComputableInO f encCNF encNat tf tf` -with `(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c` for all `χ : CNF ℕ`, then -`guardOccurrenceCounter k f` has a combined time-and-space certificate -`ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T` with -`(T ψ : ℝ) ≤ C₀ * (2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E) + C₀` for all `ψ : CNF ℕ`. -/ -theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} - (hf : ComputableInO f encCNF encNat tf tf) {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} - (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : +private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → + (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ + c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * + ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ @@ -454,7 +407,7 @@ theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → exact pow_le_pow_left₀ (by positivity) hlen e have htf : (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ c * (2 * l + 1) ^ e * X * Q + c * (X * Q) := by - have htψ := ht (toThreeOccurrenceDense ψ) + have htψ := ht ψ hψ' have hprod := mul_le_mul hexp hpow (by positivity) (by positivity) have hprod' : (2 : ℝ) ^ (δ * (toThreeOccurrenceDense ψ).numVars) * (((encCNF (toThreeOccurrenceDense ψ)).length : ℝ) + 1) ^ e ≤ @@ -485,10 +438,81 @@ theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → push_cast [P] at hsum ⊢ linarith +/-- **From variable-exponential occurrence-3 counting to clause-exponential leaf counting (promised +runtime).** +If `2 ≤ k` and `f : CNF ℕ → ℕ` has a combined time-and-space certificate +`ComputableInO f encCNF encNat tf tf` whose time and space `tf χ` satisfy +`(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c` on every dense formula `χ` of +width `≤ k` and variable occurrence `≤ 3`, then `guardOccurrenceCounter k f` has a combined +time-and-space certificate `ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T` with +`(T ψ : ℝ) ≤ C₀ * (2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E) + C₀` for all `ψ : CNF ℕ`. -/ +theorem exists_computableInO_guardOccurrenceCounter_of_promise {k : ℕ} (hk : 2 ≤ k) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ d ∈ χ, d.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_guardOccurrenceCounter_aux k hf hδ fun ψ hψ => + ht (toThreeOccurrenceDense ψ) (width_toThreeOccurrenceDense hk hψ) + (count_vars_toThreeOccurrenceDense_le ψ) (vars_toThreeOccurrenceDense_eq_range_numVars ψ) + +/-- **From variable-exponential occurrence-3 counting to clause-exponential leaf counting.** +If `f : CNF ℕ → ℕ` has a combined time-and-space certificate `ComputableInO f encCNF encNat tf tf` +with `(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c` for all `χ : CNF ℕ`, then +`guardOccurrenceCounter k f` has a combined time-and-space certificate +`ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T` with +`(T ψ : ℝ) ≤ C₀ * (2 ^ ((k * δ) * ψ.length) * (|encCNF ψ| + 1) ^ E) + C₀` for all `ψ : CNF ℕ`. -/ +theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} + (hf : ComputableInO f encCNF encNat tf tf) {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_guardOccurrenceCounter_aux k hf hδ fun ψ _ => ht (toThreeOccurrenceDense ψ) + namespace CountingSparsify variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) +include hM hK in +/-- **Counting sparsification from a dense 3-occurrence model counter (promised runtime).** +Under `CountingTreeBound`, given `2 ≤ k ≤ K` and a total model counter `f : CNF ℕ → ℕ` with a +combined time-and-space certificate `ComputableInO f encCNF encNat tf tf` that on every dense +formula `χ` of width `≤ k` and variable occurrence `≤ 3` computes `χ.modelCount` and satisfies +`(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c`, the search +`search M K k B (guardOccurrenceCounter k f)` computes `φ.modelCount` on every formula `φ` of width +`≤ k` and has a combined time-and-space certificate with fixed-parameter transfer bound +`O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ +theorem exists_computableInO_search_threeOccurrence_of_promise + (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → + search M K k B (guardOccurrenceCounter k f) φ = φ.modelCount) ∧ + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + refine ⟨fun φ hφ => search_eq_modelCount hM hK hM2 hkK hTB + (fun ψ hψ _ => guardOccurrenceCounter_eq_modelCount hk2 hf_eq hψ) hφ, ?_⟩ + obtain ⟨Tg, Cg, Eg, hgO, hgt⟩ := + exists_computableInO_guardOccurrenceCounter_of_promise hk2 hf hδ ht + exact exists_computableInO_search hM hK hM2 hkK hTB hgO + (fun ψ _ _ => guardOccurrenceCounter_le_two_pow_numVars hk2 hf_eq ψ) + (by positivity) hgt + include hM hK in /-- **Counting sparsification from a dense 3-occurrence model counter.** Under `CountingTreeBound`, given `2 ≤ k ≤ K` and a total model counter `f : CNF ℕ → ℕ` that @@ -511,13 +535,9 @@ theorem exists_computableInO_search_of_threeOccurrence_counter (hM2 : 2 ≤ M) { ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ ∀ φ : CNF ℕ, (T φ : ℝ) ≤ C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * - (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by - refine ⟨fun φ hφ => search_eq_modelCount hM hK hM2 hkK hTB - (fun ψ hψ _ => guardOccurrenceCounter_eq_modelCount hk2 hf_eq hψ) hφ, ?_⟩ - obtain ⟨Tg, Cg, Eg, hgO, hgt⟩ := exists_computableInO_guardOccurrenceCounter k hf hδ ht - exact exists_computableInO_search hM hK hM2 hkK hTB hgO - (fun ψ _ _ => guardOccurrenceCounter_le_two_pow_numVars hk2 hf_eq ψ) - (by positivity) hgt + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_threeOccurrence_of_promise hM hK hM2 hk2 hkK hTB hf hf_eq + hδ fun χ _ _ _ => ht χ end CountingSparsify diff --git a/Complexity/Logics/Propositional/CNF/Compact/Relabel.lean b/Complexity/Logics/Propositional/CNF/Compact/Relabel.lean index 3e669508b..cc66b99ec 100755 --- a/Complexity/Logics/Propositional/CNF/Compact/Relabel.lean +++ b/Complexity/Logics/Propositional/CNF/Compact/Relabel.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 Erik Demaine. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Erik Demaine +Authors: Erik Demaine, Samuel Schlesinger -/ module @@ -20,6 +20,9 @@ variables keeps its order under this renaming, even though compact names may hav ## Main results +* `Cslib.Logic.PL.CNF.distinctVars_relabel_of_injOn`, + `Cslib.Logic.PL.CNF.numVars_relabel_of_injOn`, `Cslib.Logic.PL.CNF.numVars_relabel`: injective + variable relabelings preserve `distinctVars` order and `numVars`. * `Cslib.Logic.PL.CNF.distinctVars_compact`: exact ordered list of compact variable names. * `Cslib.Logic.PL.CNF.idxOf_distinctVars_compact`: graph vertex indices are preserved. @@ -67,6 +70,19 @@ theorem distinctVars_relabel_of_injOn (φ : CNF V) (f : V → W) simp only [distinctVars, vars_relabel, List.length_map] exact selectDistinct_map_injOn _ _ _ hf +/-- Relabeling injectively on the occurring variables preserves `numVars`. -/ +theorem numVars_relabel_of_injOn (φ : CNF V) (f : V → W) + (hf : Set.InjOn f {v | v ∈ φ.vars}) : + (φ.relabel f).numVars = φ.numVars := by + rw [← length_distinctVars, ← length_distinctVars, distinctVars_relabel_of_injOn φ f hf, + List.length_map] + +/-- Relabeling along an injective map preserves `numVars`. -/ +@[simp] +theorem numVars_relabel {f : V → W} (hf : Function.Injective f) (φ : CNF V) : + (φ.relabel f).numVars = φ.numVars := + numVars_relabel_of_injOn φ f fun _ _ _ _ h => hf h + variable [BEq V] [LawfulBEq V] /-- Compaction preserves the exact order of the distinct variables. Any lawful Boolean diff --git a/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean b/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean index b16783da0..73addc879 100644 --- a/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean +++ b/Complexity/Logics/Propositional/CNF/ModelCount/DenseRelabel.lean @@ -37,8 +37,10 @@ natural-number indices (`encCNF`). * `Cslib.Logic.PL.CNF.vars_denseRelabel_toFinset`, `Cslib.Logic.PL.CNF.numVars_denseRelabel`, - `Cslib.Logic.PL.CNF.distinctVars_denseRelabel`: the occurring variables of `φ.denseRelabel` are - `0, …, φ.numVars - 1` in order. + `Cslib.Logic.PL.CNF.distinctVars_denseRelabel`, + `Cslib.Logic.PL.CNF.count_vars_denseRelabel`, + `Cslib.Logic.PL.CNF.count_vars_denseRelabel_le`: the occurring variables of `φ.denseRelabel` are + `0, …, φ.numVars - 1` in order, with preserved literal-occurrence counts. * `Cslib.Logic.PL.CNF.satisfiable_denseRelabel`, `Cslib.Logic.PL.CNF.modelCount_denseRelabel`, `Cslib.Logic.PL.CNF.modelCountOn_denseRelabel`: satisfiability and model counts are preserved. @@ -116,6 +118,29 @@ theorem distinctVars_denseRelabel (φ : CNF V) : rw [denseRelabel, distinctVars_relabel_of_injOn φ φ.distinctVars.idxOf hinj, map_idxOf_of_nodup φ.distinctVars φ.nodup_distinctVars, length_distinctVars] +/-- Dense variable relabeling preserves the exact literal-occurrence count of every occurring +variable. -/ +theorem count_vars_denseRelabel (φ : CNF V) {v : V} (hv : v ∈ φ.vars) : + φ.denseRelabel.vars.count (φ.distinctVars.idxOf v) = φ.vars.count v := by + rw [denseRelabel, vars_relabel] + simp only [List.count_eq_countP, List.countP_map, Function.comp_def] + apply List.countP_congr + intro w _ + simp only [beq_iff_eq] + rw [eq_comm, List.idxOf_inj (mem_distinctVars.mpr hv), eq_comm] + +/-- Any upper bound on original literal-occurrence counts is preserved by `CNF.denseRelabel`. -/ +theorem count_vars_denseRelabel_le (φ : CNF V) {B : ℕ} + (h : ∀ v, φ.vars.count v ≤ B) (i : ℕ) : + φ.denseRelabel.vars.count i ≤ B := by + by_cases hi : i ∈ φ.denseRelabel.vars + · rw [denseRelabel, vars_relabel] at hi + obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hi + rw [count_vars_denseRelabel φ hv] + exact h v + · rw [List.count_eq_zero.mpr hi] + exact Nat.zero_le _ + /-- Reading original variable values at their `distinctVars` indices recovers the evaluation of `φ`. -/ theorem eval_denseRelabel (φ : CNF V) (σ : V → Bool) : diff --git a/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean b/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean index f3e9defd4..383b09403 100644 --- a/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean +++ b/ComplexityTests/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean @@ -94,4 +94,19 @@ example {B L_coeff : ℕ} (CountingSparsify.exists_computableInO_search_of_threeOccurrence_counter (by decide) (by decide) (by decide) (by decide) le_rfl hTB hf hf_eq hδ ht).1 +example {B L_coeff : ℕ} + (hTB : CountingSparsify.CountingTreeBound 2 3 B L_coeff (by decide) (by decide)) + {f tf : CNF ℕ → ℕ} + (hf : Turing.MultiTapeTM.ComputableInO f encCNF Turing.MultiTapeTM.encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ 3) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ d ∈ χ, d.length ≤ 3) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + ∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ 3) → + CountingSparsify.search 2 3 3 B (guardOccurrenceCounter 3 f) φ = φ.modelCount := + (CountingSparsify.exists_computableInO_search_threeOccurrence_of_promise + (by decide) (by decide) (by decide) (by decide) le_rfl hTB hf hf_eq hδ ht).1 + end Cslib.Complexity.Tests.ThreeOccurrenceReduction From cabd99debf58c726fcb9ec05b833bd6858cd078e Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 04:24:05 +0000 Subject: [PATCH 19/24] feat(FineGrained): add promised-runtime budget-discharged counting search frontend --- .../FineGrained/CountingTreeBound.lean | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean index 394bb063e..8d05537a9 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -40,7 +40,8 @@ the integer parameter budget on `(M, K, B)` rather than an external `CountingTre * `Cslib.Complexity.CountingSparsify.exists_computableInO_search_of_budget`: native `ComputableInO ... T T` time-and-space transfer certificate for `CountingSparsify.search` under the integer parameter budget. -* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_of_budget`: +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_promise_of_budget`, + `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_of_budget`: end-to-end counting-sparsification reduction from a dense occurrence-`≤ 3` model counter under the integer parameter budget. @@ -137,6 +138,37 @@ theorem exists_computableInO_search_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : exists_computableInO_search hM hK hM2 hkK (countingTreeBound_of_budget hM hK hM2 hB hbudget) hg hg_le hδ ht +include hM hK in +/-- **End-to-end counting sparsification from a dense 3-occurrence model counter under the integer +parameter budget (promised runtime).** +Given `2 ≤ k ≤ K` and a total model counter `f : CNF ℕ → ℕ` with a combined time-and-space +certificate `ComputableInO f encCNF encNat tf tf` that on every dense formula `χ` of width `≤ k` +and variable occurrence `≤ 3` computes `χ.modelCount` and satisfies +`(tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * (|encCNF χ| + 1) ^ e) + c`, the search +`search M K k B (guardOccurrenceCounter k f)` computes `φ.modelCount` on every formula `φ` of width +`≤ k` and has a combined time-and-space certificate with fixed-parameter transfer bound +`O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ +theorem exists_computableInO_search_threeOccurrence_promise_of_budget + (hM2 : 2 ≤ M) {k B : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) + (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ + B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → + search M K k B (guardOccurrenceCounter k f) φ = φ.modelCount) ∧ + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_threeOccurrence_of_promise hM hK hM2 hk2 hkK + (countingTreeBound_of_budget hM hK hM2 hB hbudget) hf hf_eq hδ ht + include hM hK in /-- **End-to-end counting sparsification from a dense 3-occurrence model counter under the integer parameter budget.** @@ -163,8 +195,8 @@ theorem exists_computableInO_search_threeOccurrence_of_budget ∀ φ : CNF ℕ, (T φ : ℝ) ≤ C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := - exists_computableInO_search_of_threeOccurrence_counter hM hK hM2 hk2 hkK - (countingTreeBound_of_budget hM hK hM2 hB hbudget) hf hf_eq hδ ht + exists_computableInO_search_threeOccurrence_promise_of_budget hM hK hM2 hk2 hkK hB hbudget + hf hf_eq hδ fun χ _ _ _ => ht χ end CountingSparsify From 4f161cc188a754d1f0c65b742f8fdb6f8fd3583c Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Tue, 27 Oct 2026 00:05:37 -0400 Subject: [PATCH 20/24] feat(Sparsification): select sharp first-success counting thresholds --- Complexity.lean | 1 + .../Sparsification/CountingParameters.lean | 300 ++++++++++++++++++ ComplexityTests/CountingParameters.lean | 59 ++++ 3 files changed, 360 insertions(+) create mode 100644 Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingParameters.lean create mode 100644 ComplexityTests/CountingParameters.lean diff --git a/Complexity.lean b/Complexity.lean index 7d6440fb1..41323f499 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2184,6 +2184,7 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingP public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingParameters public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingParameters.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingParameters.lean new file mode 100644 index 000000000..7a3ea6a09 --- /dev/null +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingParameters.lean @@ -0,0 +1,300 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Nat.Choose.Real +public import Complexity.Foundations.Data.Nat.Log +import Mathlib.Data.Nat.Find + +/-! +# First-success geometric thresholds for counting sparsification + +For fixed width `k ≥ 2` and inverse error budget `B ≥ 1`, Appendix A.3 of [OpenAI2026ETH] +tests dyadic threshold bases `M` in increasing order. The exact test is `Γ ≤ M` and +`B * Γ * ⌈log₂ (4 * C * M)⌉ ≤ M`, where `C = 4 ∑ r < k - 1, (3 * M) ^ r` and +`Γ = 4 ∑ r < k - 1, 3 ^ r`. `countingThresholdBase` implements this first-success search. +The parameters depend only on `k` and `B`, before any input formula or variable budget. + +An explicit successful candidate proves termination and the sharp bound +`M = O_k(B log(2B))`; that candidate does not replace the first-success output. Widths zero +and one have empty coefficient sums and use the separate default `M = 2`. The same total +default is used at `B = 0`, which is outside the positive-error-budget specification. + +This module proves numerical selection and size bounds, not a native bit-cost certificate, +tree-word counting, or the complete counting sparsification theorem. + +## Main definitions + +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingDepthCoeff`: the source coefficient `C`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingBChoiceCoeff`: the source coefficient `Γ`. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingThresholdBase`: the first successful dyadic base. + +## Main results + +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingThresholdBase_spec`: the exact integer test. +* `Cslib.Logic.PL.ClauseSet.Sparsification.countingThresholdBase_first`: dyadic minimality. +* `Cslib.Logic.PL.ClauseSet.Sparsification.exists_countingThresholdBase_le_mul_clog`: + the sharp size dependence on the inverse error budget. +* `Cslib.Logic.PL.ClauseSet.Sparsification.isBigO_countingThresholdBase`: + the real logarithmic bound. +* `Cslib.Logic.PL.ClauseSet.Sparsification.exists_clog_countingThresholdBase_le`: + a logarithmic bound on the number of dyadic candidates tested. +* `Cslib.Logic.PL.ClauseSet.Sparsification.sum_choose_le_countingThresholdBase`: + the all-input-size numerical binomial-tail bound for the selected parameter. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*, Appendix A.3, pp. 46–47][OpenAI2026ETH] +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.ClauseSet.Sparsification + +open Finset + +/-- The linear path-depth coefficient `C` in Appendix A.3. -/ +def countingDepthCoeff (k M : ℕ) : ℕ := 4 * ∑ r ∈ range (k - 1), (3 * M) ^ r + +/-- The coefficient `Γ` in the weighted B-choice bound of Appendix A.3. -/ +def countingBChoiceCoeff (k : ℕ) : ℕ := 4 * ∑ r ∈ range (k - 1), 3 ^ r + +/-- The depth coefficient is at least four once width two is allowed. -/ +theorem four_le_countingDepthCoeff {k M : ℕ} (hk : 2 ≤ k) : 4 ≤ countingDepthCoeff k M := by + have h := single_le_sum (f := fun r => (3 * M) ^ r) + (fun r _ => Nat.zero_le _) (show 0 ∈ range (k - 1) by simp only [mem_range]; omega) + simp only [pow_zero] at h + exact Nat.mul_le_mul_left 4 h + +/-- The B-choice coefficient is at least four once width two is allowed. -/ +theorem four_le_countingBChoiceCoeff {k : ℕ} (hk : 2 ≤ k) : 4 ≤ countingBChoiceCoeff k := by + simpa only [countingDepthCoeff, countingBChoiceCoeff, mul_one] using + (four_le_countingDepthCoeff (M := 1) hk) + +private theorem depthCoeff_le {k M : ℕ} (hM : 1 ≤ M) : + countingDepthCoeff k M ≤ countingBChoiceCoeff k * M ^ (k - 2) := by + calc countingDepthCoeff k M + ≤ 4 * ∑ r ∈ range (k - 1), 3 ^ r * M ^ (k - 2) := by + apply Nat.mul_le_mul_left 4 + apply sum_le_sum + intro r hr + rw [Nat.mul_pow] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by omega) (by + have := mem_range.mp hr + omega)) + _ = countingBChoiceCoeff k * M ^ (k - 2) := by + rw [← sum_mul] + exact (Nat.mul_assoc _ _ _).symm + +private abbrev Accepts (k B m : ℕ) : Prop := + countingBChoiceCoeff k ≤ 2 ^ m ∧ + B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k (2 ^ m) * 2 ^ m) ≤ + 2 ^ m + +private def offset (k : ℕ) : ℕ := + 2 * (countingBChoiceCoeff k * (Nat.clog 2 (4 * countingBChoiceCoeff k) + 3 * (k - 1) + 1)) + +private def candidateExponent (k B : ℕ) : ℕ := + offset k + Nat.clog 2 B + Nat.clog 2 (Nat.clog 2 B + 1) + +private theorem clog_depthCoeff_le {k : ℕ} (hk : 2 ≤ k) (m : ℕ) : + Nat.clog 2 (4 * countingDepthCoeff k (2 ^ m) * 2 ^ m) ≤ + Nat.clog 2 (4 * countingBChoiceCoeff k) + (k - 1) * m := by + apply Nat.clog_le_of_le_pow + calc 4 * countingDepthCoeff k (2 ^ m) * 2 ^ m + ≤ 4 * (countingBChoiceCoeff k * (2 ^ m) ^ (k - 2)) * 2 ^ m := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (depthCoeff_le Nat.one_le_two_pow)) + _ = (4 * countingBChoiceCoeff k) * (2 ^ m) ^ (k - 1) := by + rw [show k - 1 = (k - 2) + 1 by omega, pow_succ] + ring + _ ≤ 2 ^ Nat.clog 2 (4 * countingBChoiceCoeff k) * (2 ^ m) ^ (k - 1) := + Nat.mul_le_mul_right _ (Nat.le_pow_clog (by decide) _) + _ = 2 ^ (Nat.clog 2 (4 * countingBChoiceCoeff k) + (k - 1) * m) := by + rw [← pow_mul, ← pow_add, Nat.mul_comm m] + +private theorem offset_budget {k : ℕ} (hk : 2 ≤ k) : + countingBChoiceCoeff k * (Nat.clog 2 (4 * countingBChoiceCoeff k) + + (k - 1) * offset k + 2 * (k - 1)) ≤ 2 ^ offset k := by + let g := countingBChoiceCoeff k + let a := Nat.clog 2 (4 * g) + let s := k - 1 + let t := g * (a + 3 * s + 1) + have hg : 1 ≤ g := (by have := four_le_countingBChoiceCoeff hk; omega) + have hga : g * a + 2 * g * s ≤ t := by dsimp only [t]; nlinarith + have hgs : g * s + 1 ≤ t := by dsimp only [t]; nlinarith + have hpoly : g * (a + s * (2 * t) + 2 * s) ≤ 2 * t ^ 2 + 1 := by + nlinarith [Nat.mul_le_mul_right (2 * t) hgs] + exact hpoly.trans (Nat.two_mul_sq_add_one_le_two_pow_two_mul t) + +private theorem gamma_le_two_pow_offset {k : ℕ} (hk : 2 ≤ k) : + countingBChoiceCoeff k ≤ 2 ^ offset k := by + have hg : 1 ≤ countingBChoiceCoeff k := (by have := four_le_countingBChoiceCoeff hk; omega) + have hgo : countingBChoiceCoeff k ≤ offset k := by unfold offset; nlinarith + exact hgo.trans Nat.lt_two_pow_self.le + +private theorem candidate_accepts {k B : ℕ} (hk : 2 ≤ k) : + Accepts k B (candidateExponent k B) := by + let L := Nat.clog 2 B + have hL : Nat.clog 2 (L + 1) ≤ L := Nat.clog_le_of_le_pow Nat.lt_two_pow_self + have hm : candidateExponent k B ≤ offset k + 2 * L := by + dsimp only [candidateExponent, L] at * + omega + have hbudget := offset_budget hk + have hcoeff : countingBChoiceCoeff k * + (Nat.clog 2 (4 * countingBChoiceCoeff k) + (k - 1) * candidateExponent k B) ≤ + 2 ^ offset k * (L + 1) := by + have h1 := Nat.mul_le_mul_left (countingBChoiceCoeff k * (k - 1)) hm + have h2 := Nat.mul_le_mul_right (L + 1) hbudget + nlinarith [Nat.zero_le (countingBChoiceCoeff k * + (Nat.clog 2 (4 * countingBChoiceCoeff k) + (k - 1) * offset k) * L)] + refine ⟨(gamma_le_two_pow_offset hk).trans (Nat.pow_le_pow_right (by decide) (by + unfold candidateExponent; omega)), ?_⟩ + calc B * countingBChoiceCoeff k * + Nat.clog 2 (4 * countingDepthCoeff k (2 ^ candidateExponent k B) * + 2 ^ candidateExponent k B) + ≤ B * (countingBChoiceCoeff k * + (Nat.clog 2 (4 * countingBChoiceCoeff k) + (k - 1) * candidateExponent k B)) := by + have := Nat.mul_le_mul_left (B * countingBChoiceCoeff k) + (clog_depthCoeff_le hk (candidateExponent k B)) + simpa only [Nat.mul_assoc] using this + _ ≤ B * (2 ^ offset k * (L + 1)) := Nat.mul_le_mul_left B hcoeff + _ ≤ 2 ^ L * (2 ^ offset k * 2 ^ Nat.clog 2 (L + 1)) := + Nat.mul_le_mul (Nat.le_pow_clog (by decide) B) + (Nat.mul_le_mul_left _ (Nat.le_pow_clog (by decide) (L + 1))) + _ = 2 ^ candidateExponent k B := by + unfold candidateExponent + rw [pow_add, pow_add] + ring + +private theorem exists_accepts {k : ℕ} (hk : 2 ≤ k) (B : ℕ) : + ∃ m, Accepts k B m := ⟨candidateExponent k B, candidate_accepts hk⟩ + +/-- The first dyadic `M ≥ Γ` passing Appendix A.3's exact test for error budget `σ = 1/B`. +Widths at most one and the out-of-domain input `B = 0` use the total default `2`. The existence +proof only bounds the search: `Nat.find` executes the decidable tests from exponent zero upward. -/ +def countingThresholdBase (k B : ℕ) : ℕ := + if h : 2 ≤ k ∧ 1 ≤ B then + 2 ^ Nat.find (p := fun m => countingBChoiceCoeff k ≤ 2 ^ m ∧ + B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k (2 ^ m) * 2 ^ m) ≤ + 2 ^ m) (by exact exists_accepts h.1 B) + else 2 + +/-- The selected dyadic base passes both exact integer tests. -/ +theorem countingThresholdBase_spec {k B : ℕ} (hk : 2 ≤ k) (hB : 1 ≤ B) : + countingBChoiceCoeff k ≤ countingThresholdBase k B ∧ + B * countingBChoiceCoeff k * + Nat.clog 2 (4 * countingDepthCoeff k (countingThresholdBase k B) * + countingThresholdBase k B) ≤ countingThresholdBase k B := by + simp only [countingThresholdBase, hk, hB, and_self, ↓reduceDIte] + exact Nat.find_spec (exists_accepts hk B) + +/-- The returned base is dyadic and every earlier power of two fails the exact test. -/ +theorem countingThresholdBase_first {k B : ℕ} (hk : 2 ≤ k) (hB : 1 ≤ B) : + ∃ m, countingThresholdBase k B = 2 ^ m ∧ ∀ j < m, + ¬ (countingBChoiceCoeff k ≤ 2 ^ j ∧ + B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k (2 ^ j) * 2 ^ j) ≤ + 2 ^ j) := by + refine ⟨Nat.find (exists_accepts hk B), ?_, fun j hj => Nat.find_min _ hj⟩ + simp only [countingThresholdBase, hk, hB, and_self, ↓reduceDIte] + +/-- A passing candidate with no earlier success computes the selected threshold base. This +permits finite evaluation proofs without unfolding the termination argument of `Nat.find`. -/ +theorem countingThresholdBase_eq_two_pow {k B m : ℕ} (hk : 2 ≤ k) (hB : 1 ≤ B) + (hpass : countingBChoiceCoeff k ≤ 2 ^ m ∧ + B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k (2 ^ m) * 2 ^ m) ≤ + 2 ^ m) + (hfirst : ∀ j < m, ¬ (countingBChoiceCoeff k ≤ 2 ^ j ∧ + B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k (2 ^ j) * 2 ^ j) ≤ + 2 ^ j)) : countingThresholdBase k B = 2 ^ m := by + simp only [countingThresholdBase, hk, hB, and_self, ↓reduceDIte] + congr 1 + exact (Nat.find_eq_iff _).mpr ⟨hpass, hfirst⟩ + +/-- Widths zero and one use `M = 2` separately from the geometric search. -/ +theorem countingThresholdBase_of_lt_two {k : ℕ} (hk : k < 2) (B : ℕ) : + countingThresholdBase k B = 2 := by simp [countingThresholdBase, show ¬ 2 ≤ k by omega] + +/-- The inverse error budget zero is outside the specification and uses the total default. -/ +theorem countingThresholdBase_zero (k : ℕ) : countingThresholdBase k 0 = 2 := by + simp [countingThresholdBase] + +private theorem thresholdBase_le_candidate {k B : ℕ} (hk : 2 ≤ k) (hB : 1 ≤ B) : + countingThresholdBase k B ≤ 2 ^ candidateExponent k B := by + simp only [countingThresholdBase, hk, hB, and_self, ↓reduceDIte] + exact Nat.pow_le_pow_right (by decide) (Nat.find_min' _ (candidate_accepts hk)) + +/-- The first-success base has the sharp `B log(2B)` dependence. The constant depends only on +the fixed width; the proof supplies the explicit constant `2 ^ (offset k + 2)`. -/ +theorem exists_countingThresholdBase_le_mul_clog {k : ℕ} (hk : 2 ≤ k) : + ∃ A : ℕ, ∀ B : ℕ, 1 ≤ B → + countingThresholdBase k B ≤ A * B * (Nat.clog 2 B + 1) := by + refine ⟨2 ^ (offset k + 2), fun B hB => (thresholdBase_le_candidate hk hB).trans ?_⟩ + calc 2 ^ candidateExponent k B + = 2 ^ offset k * 2 ^ Nat.clog 2 B * 2 ^ Nat.clog 2 (Nat.clog 2 B + 1) := by + simp only [candidateExponent, pow_add] + _ ≤ 2 ^ offset k * (2 * B) * (2 * (Nat.clog 2 B + 1)) := + Nat.mul_le_mul (Nat.mul_le_mul_left _ (Nat.pow_clog_le_mul (by decide) (by omega))) + (Nat.pow_clog_le_mul (by decide) (by omega)) + _ = 2 ^ (offset k + 2) * B * (Nat.clog 2 B + 1) := by rw [pow_add]; ring + +/-- For fixed width the first-success parameter is `O(B log(2B))` as `B` tends to infinity. -/ +theorem isBigO_countingThresholdBase {k : ℕ} (hk : 2 ≤ k) : + (fun B : ℕ => (countingThresholdBase k B : ℝ)) =O[Filter.atTop] + (fun B : ℕ => (B : ℝ) * Real.log (2 * B)) := by + obtain ⟨A, hA⟩ := exists_countingThresholdBase_le_mul_clog hk + refine Asymptotics.IsBigO.of_bound (2 * A / Real.log 2) ?_ + filter_upwards [Filter.eventually_ge_atTop 1] with B hB + have hBR : (1 : ℝ) ≤ B := by exact_mod_cast hB + have hlog : 0 ≤ Real.logb 2 (B : ℝ) := Real.logb_nonneg (by norm_num) hBR + have hclog : (Nat.clog 2 B : ℝ) ≤ Real.logb 2 (B : ℝ) + 1 := by + rw [← Real.natCeil_logb_natCast] + exact (Nat.ceil_lt_add_one hlog).le + have hlogmul : Real.logb 2 (2 * (B : ℝ)) = 1 + Real.logb 2 (B : ℝ) := by + rw [Real.logb_mul (by norm_num) (by positivity), Real.logb_self_eq_one (by norm_num)] + have hclog' : (Nat.clog 2 B : ℝ) + 1 ≤ 2 * Real.logb 2 (2 * (B : ℝ)) := by + rw [hlogmul] + linarith + have hlog0 : 0 ≤ Real.log (2 * (B : ℝ)) := Real.log_nonneg (by linarith) + have hM0 : (0 : ℝ) ≤ countingThresholdBase k B := Nat.cast_nonneg _ + simp only [Real.norm_eq_abs, abs_of_nonneg hM0, + abs_of_nonneg (mul_nonneg (Nat.cast_nonneg B) hlog0)] + calc (countingThresholdBase k B : ℝ) + ≤ A * B * ((Nat.clog 2 B : ℝ) + 1) := by exact_mod_cast hA B hB + _ ≤ A * B * (2 * Real.logb 2 (2 * (B : ℝ))) := + mul_le_mul_of_nonneg_left hclog' (by positivity) + _ = (2 * A / Real.log 2) * ((B : ℝ) * Real.log (2 * B)) := by rw [Real.logb]; ring + +/-- The selected exponent is at most `D + 2 * ⌈log₂ B⌉`, for a width-dependent constant `D`. +Thus testing exponents from zero through the first success uses at most this bound plus one +tests. This is a count of candidates, not a native bit-operation bound. -/ +theorem exists_clog_countingThresholdBase_le {k : ℕ} (hk : 2 ≤ k) : + ∃ D : ℕ, ∀ B : ℕ, 1 ≤ B → + Nat.clog 2 (countingThresholdBase k B) ≤ D + 2 * Nat.clog 2 B := by + refine ⟨offset k, fun B hB => (Nat.clog_le_of_le_pow (thresholdBase_le_candidate hk hB)).trans ?_⟩ + have h : Nat.clog 2 (Nat.clog 2 B + 1) ≤ Nat.clog 2 B := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self + unfold candidateExponent + omega + +/-- The selected geometric parameter gives the rounded binomial-tail bound for every variable +budget `N`, including zero. This is only a numerical bound; counting tree words is separate. -/ +theorem sum_choose_le_countingThresholdBase {k B : ℕ} (hk : 2 ≤ k) (hB : 1 ≤ B) (N : ℕ) : + ∑ j ∈ range (countingBChoiceCoeff k * N / countingThresholdBase k B + 1), + (countingDepthCoeff k (countingThresholdBase k B) * N).choose j ≤ 2 ^ (N / B + 1) := by + obtain ⟨hM, hbudget⟩ := countingThresholdBase_spec hk hB + exact Cslib.sum_choose_le_two_pow_div_add_one (four_le_countingDepthCoeff hk) + (four_le_countingBChoiceCoeff hk) hM hB hbudget N + +/-- Every selected base, including the total defaults, is at least two. -/ +theorem two_le_countingThresholdBase (k B : ℕ) : 2 ≤ countingThresholdBase k B := by + by_cases h : 2 ≤ k ∧ 1 ≤ B + · exact (by decide : 2 ≤ 4).trans + ((four_le_countingBChoiceCoeff h.1).trans (countingThresholdBase_spec h.1 h.2).1) + · simp [countingThresholdBase, h] + +end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/CountingParameters.lean b/ComplexityTests/CountingParameters.lean new file mode 100644 index 000000000..79b424aa9 --- /dev/null +++ b/ComplexityTests/CountingParameters.lean @@ -0,0 +1,59 @@ +import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingParameters + +/-! Actual first-success outputs, exact source coefficients, rejected candidates and boundaries. +The evaluation control runs the executable `Nat.find` selector. Equality proofs separately use +its minimum specification and decidable tests of all smaller candidates. -/ + +open Cslib.Logic.PL.ClauseSet.Sparsification Finset + +/-- info: [64, 128, 512, 1024, 2, 2, 2] -/ +#guard_msgs in +#eval [(2, 1), (2, 2), (3, 1), (3, 2), (1, 1), (0, 1), (3, 0)].map + fun (k, B) => countingThresholdBase k B + +example : countingDepthCoeff 3 2 = 28 ∧ countingBChoiceCoeff 3 = 16 := by decide + +private theorem width_two_budget_one : countingThresholdBase 2 1 = 64 := + countingThresholdBase_eq_two_pow (m := 6) (by decide) (by decide) (by decide) (by decide) + +example : countingThresholdBase 2 2 = 128 := + countingThresholdBase_eq_two_pow (m := 7) (by decide) (by decide) (by decide) (by decide) + +example : countingThresholdBase 3 1 = 512 := + countingThresholdBase_eq_two_pow (m := 9) (by decide) (by decide) (by decide) (by decide) + +example : countingThresholdBase 3 2 = 1024 := + countingThresholdBase_eq_two_pow (m := 10) (by decide) (by decide) (by decide) (by decide) + +-- M = 32 is large enough for Γ but fails the budget; the selector must continue to 64. +example : countingBChoiceCoeff 2 ≤ 32 ∧ + ¬ (1 * countingBChoiceCoeff 2 * Nat.clog 2 (4 * countingDepthCoeff 2 32 * 32) ≤ 32) := by + decide + +-- No branching remains at width zero or one. The numerical coefficients vanish, while the +-- separate total choice M = 2 remains compatible with consumers requiring a base at least two. +example : countingDepthCoeff 0 2 = 0 ∧ countingBChoiceCoeff 0 = 0 ∧ + countingThresholdBase 0 3 = 2 ∧ countingDepthCoeff 1 2 = 0 ∧ countingBChoiceCoeff 1 = 0 ∧ + countingThresholdBase 1 3 = 2 := by + norm_num [countingDepthCoeff, countingBChoiceCoeff, + countingThresholdBase_of_lt_two (k := 0) (by decide), + countingThresholdBase_of_lt_two (k := 1) (by decide)] + +example : countingThresholdBase 3 0 = 2 := countingThresholdBase_zero 3 + +-- The selected value feeds the numerical consumer, with positive radius one and tail 65. +example : + (∑ j ∈ range (countingBChoiceCoeff 2 * 16 / countingThresholdBase 2 1 + 1), + (countingDepthCoeff 2 (countingThresholdBase 2 1) * 16).choose j) = 65 ∧ + (∑ j ∈ range (countingBChoiceCoeff 2 * 16 / countingThresholdBase 2 1 + 1), + (countingDepthCoeff 2 (countingThresholdBase 2 1) * 16).choose j) ≤ 2 ^ 17 := by + constructor + · rw [width_two_budget_one] + norm_num [countingBChoiceCoeff, countingDepthCoeff, sum_range_succ] + · exact sum_choose_le_countingThresholdBase (by decide) (by decide) 16 + +-- Variable budget N = 0 is still permitted by the selected-parameter tail theorem. +example : + (∑ j ∈ range (countingBChoiceCoeff 3 * 0 / countingThresholdBase 3 2 + 1), + (countingDepthCoeff 3 (countingThresholdBase 3 2) * 0).choose j) ≤ 2 := by + exact sum_choose_le_countingThresholdBase (by decide) (by decide) 0 From 3d9527c23e82f0567ac3ca1254305ba7de342c9a Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 04:32:51 +0000 Subject: [PATCH 21/24] feat(FineGrained): reuse CountingParameters coefficients and prove unconditional CountingTreeBound at countingThresholdBase --- Complexity.lean | 2 +- .../FineGrained/CountingTreeBound.lean | 138 +++++++++++++----- .../Sparsification/CountingBounds.lean | 112 +++++++------- .../FineGrained/CountingTreeBound.lean | 42 ++++-- 4 files changed, 182 insertions(+), 112 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 41323f499..5acba7ffd 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2179,12 +2179,12 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingD public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingInsertions public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingList public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingOccurrences +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingParameters public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPathBounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPolyTime public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSurvival public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingTree -public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingParameters public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingUnits public import Complexity.Logics.Propositional.ClauseSet.Sparsification.List public import Complexity.Logics.Propositional.ClauseSet.Sparsification.Path diff --git a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean index 8d05537a9..b4417994e 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -17,33 +17,40 @@ public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingB We discharge the quantitative depth and leaf-count hypothesis `CountingSparsify.CountingTreeBound` of `CountingSparsification` from the actual path budgets and binomial leaf-word bounds in -`CountingBounds` (Appendix A.2–A.3, Lemmas A.2–A.3, printed pp. 46–47 of [OpenAI2026ETH]). +`CountingBounds` and the first-success threshold base `countingThresholdBase` in +`CountingParameters` (Appendix A.2–A.3, Lemmas A.2–A.3, printed pp. 46–47 of [OpenAI2026ETH]). -Specifically, for any parameters `M, K, B : ℕ` with `2 ≤ M`, `1 ≤ K`, and `1 ≤ B` satisfying the -integer parameter budget -* `countingBStepCoeff K ≤ M`, and -* `B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M` -whenever `2 ≤ K`, `CountingSparsify.countingTreeBound_of_budget` proves -`CountingSparsify.CountingTreeBound M K B (countingDepthCoeff K M) hM hK`. +Specifically: +* For any parameters `M, K, B : ℕ` with `2 ≤ M`, `1 ≤ K`, and `1 ≤ B` satisfying the integer + parameter budget `countingBChoiceCoeff K ≤ M` and + `B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M` whenever `2 ≤ K`, + `CountingSparsify.countingTreeBound_of_budget` proves + `CountingSparsify.CountingTreeBound M K B (countingDepthCoeff K M) hM hK`. +* Instantiating `M = countingThresholdBase K B` via `countingThresholdBase_spec`, + `CountingSparsify.countingTreeBound_countingThresholdBase` proves `CountingTreeBound` + unconditionally for every `1 ≤ K` and `1 ≤ B`. We then specialize the correctness and native `ComputableInO` transfer theorems of -`CountingSparsification` and `ThreeOccurrenceReduction` so that downstream clients need only supply -the integer parameter budget on `(M, K, B)` rather than an external `CountingTreeBound` witness. +`CountingSparsification` and `ThreeOccurrenceReduction` both for any budget-satisfying `M` and +unconditionally at `M = countingThresholdBase K B`. ## Main results -* `Cslib.Complexity.CountingSparsify.countingTreeBound_of_budget`: discharges `CountingTreeBound` - with linear depth coefficient `countingDepthCoeff K M` from the integer parameter budget. +* `Cslib.Complexity.CountingSparsify.countingTreeBound_of_budget`, + `Cslib.Complexity.CountingSparsify.countingTreeBound_countingThresholdBase`: discharge + `CountingTreeBound` with linear depth coefficient `countingDepthCoeff K M` from the integer + parameter budget and unconditionally at `M = countingThresholdBase K B`. * `Cslib.Complexity.CountingSparsify.search_eq_ite_modelCount_of_budget`, - `Cslib.Complexity.CountingSparsify.search_eq_modelCount_of_budget`: model-count correctness of - `CountingSparsify.search` under the integer parameter budget. -* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_of_budget`: native - `ComputableInO ... T T` time-and-space transfer certificate for `CountingSparsify.search` under - the integer parameter budget. + `Cslib.Complexity.CountingSparsify.search_eq_modelCount_of_budget`, + `Cslib.Complexity.CountingSparsify.search_eq_modelCount_countingThresholdBase`: model-count + correctness of `CountingSparsify.search` without an external `CountingTreeBound` witness. +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_of_budget`, + `Cslib.Complexity.CountingSparsify.exists_computableInO_search_countingThresholdBase`: native + `ComputableInO ... T T` time-and-space transfer certificates for `CountingSparsify.search`. * `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_promise_of_budget`, - `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_of_budget`: - end-to-end counting-sparsification reduction from a dense occurrence-`≤ 3` model counter under - the integer parameter budget. + `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_of_budget`, + `Cslib.Complexity.CountingSparsify.exists_computableInO_search_threeOccurrence_thresholdBase`: + end-to-end counting-sparsification reduction from a dense occurrence-`≤ 3` model counter. ## References @@ -64,13 +71,13 @@ variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) include hM hK in /-- **Discharging `CountingTreeBound` from the integer parameter budget.** -For `2 ≤ M`, `1 ≤ K`, and `1 ≤ B`, if `2 ≤ K` implies `countingBStepCoeff K ≤ M` and -`B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M`, then +For `2 ≤ M`, `1 ≤ K`, and `1 ≤ B`, if `2 ≤ K` implies `countingBChoiceCoeff K ≤ M` and +`B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M`, then `CountingTreeBound M K B (countingDepthCoeff K M) hM hK` holds (Appendix A.3, Lemma A.3, printed p. 47 [OpenAI2026ETH]). -/ theorem countingTreeBound_of_budget (hM2 : 2 ≤ M) {B : ℕ} (hB : 1 ≤ B) - (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ - B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) : + (hbudget : 2 ≤ K → countingBChoiceCoeff K ≤ M ∧ + B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) : CountingTreeBound M K B (countingDepthCoeff K M) hM hK := by intro ψ declared hsupport have hnt : ∀ C ∈ ψ.1.toClauseSet, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C := by @@ -87,13 +94,25 @@ theorem countingTreeBound_of_budget (hM2 : 2 ≤ M) {B : ℕ} (hB : 1 ≤ B) · exact CountingBranchingRule.card_le_two_pow_of_isLeafWord (R := rule hM hK) hM2 ψ.2.reduced hnt ψ.2.card_le declared hsup hB hbudget W hW +include hK in +/-- **Unconditional `CountingTreeBound` at `countingThresholdBase K B`.** +For any `1 ≤ K` and `1 ≤ B`, the first-success dyadic base `countingThresholdBase K B` satisfies +`CountingTreeBound` with linear depth coefficient +`countingDepthCoeff K (countingThresholdBase K B)`. -/ +theorem countingTreeBound_countingThresholdBase {B : ℕ} (hB : 1 ≤ B) : + CountingTreeBound (countingThresholdBase K B) K B + (countingDepthCoeff K (countingThresholdBase K B)) + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K B)) hK := + countingTreeBound_of_budget _ hK (two_le_countingThresholdBase K B) hB + fun hk2 => countingThresholdBase_spec hk2 hB + include hM hK in /-- **Model-count correctness of `search` under the integer parameter budget.** If `g` computes `ψ.modelCount` on sparse formulas `ψ` of width at most `k`, then `search M K k B g φ` returns `φ.modelCount` on formulas of width at most `k` and `0` otherwise. -/ theorem search_eq_ite_modelCount_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) - (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ - B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + (hbudget : 2 ≤ K → countingBChoiceCoeff K ≤ M ∧ + B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) {g : CNF ℕ → ℕ} (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) @@ -106,8 +125,8 @@ include hM hK in /-- **Model-count correctness of `search` on width-`≤ k` formulas under the integer parameter budget.** -/ theorem search_eq_modelCount_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) - (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ - B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + (hbudget : 2 ≤ K → countingBChoiceCoeff K ≤ M ∧ + B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) {g : CNF ℕ → ℕ} (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) @@ -115,6 +134,17 @@ theorem search_eq_modelCount_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ search M K k B g φ = φ.modelCount := search_eq_modelCount hM hK hM2 hkK (countingTreeBound_of_budget hM hK hM2 hB hbudget) hg hφ +include hK in +/-- **Unconditional model-count correctness of `search` at `countingThresholdBase K B`.** -/ +theorem search_eq_modelCount_countingThresholdBase {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) + {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K B)) K ψ = true → g ψ = ψ.modelCount) + {φ : CNF ℕ} (hφ : ∀ c ∈ φ, c.length ≤ k) : + search (countingThresholdBase K B) K k B g φ = φ.modelCount := + search_eq_modelCount _ hK (two_le_countingThresholdBase K B) hkK + (countingTreeBound_countingThresholdBase hK hB) hg hφ + include hM hK in /-- **Time and space of `search` under the integer parameter budget.** Given a total leaf routine `g` with `hg : ComputableInO g encCNF encNat t t` satisfying the global @@ -123,8 +153,8 @@ bound `ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : C certificate `ComputableInO (search M K k B g) encCNF encNat T T` with fixed-parameter transfer bound `T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀`. -/ theorem exists_computableInO_search_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) - (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ - B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + (hbudget : 2 ≤ K → countingBChoiceCoeff K ≤ M ∧ + B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) @@ -138,6 +168,22 @@ theorem exists_computableInO_search_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hkK : exists_computableInO_search hM hK hM2 hkK (countingTreeBound_of_budget hM hK hM2 hB hbudget) hg hg_le hδ ht +include hK in +/-- **Unconditional time and space of `search` at `countingThresholdBase K B`.** -/ +theorem exists_computableInO_search_countingThresholdBase {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K B)) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search (countingThresholdBase K B) K k B g) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K (countingThresholdBase K B) * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search _ hK (two_le_countingThresholdBase K B) hkK + (countingTreeBound_countingThresholdBase hK hB) hg hg_le hδ ht + include hM hK in /-- **End-to-end counting sparsification from a dense 3-occurrence model counter under the integer parameter budget (promised runtime).** @@ -150,8 +196,8 @@ and variable occurrence `≤ 3` computes `χ.modelCount` and satisfies `O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ theorem exists_computableInO_search_threeOccurrence_promise_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) - (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ - B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + (hbudget : 2 ≤ K → countingBChoiceCoeff K ≤ M ∧ + B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) @@ -181,8 +227,8 @@ certificate `ComputableInO f encCNF encNat tf tf` with global bound `O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E)`. -/ theorem exists_computableInO_search_threeOccurrence_of_budget (hM2 : 2 ≤ M) {k B : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) - (hbudget : 2 ≤ K → countingBStepCoeff K ≤ M ∧ - B * countingBStepCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) + (hbudget : 2 ≤ K → countingBChoiceCoeff K ≤ M ∧ + B * countingBChoiceCoeff K * Nat.clog 2 (4 * countingDepthCoeff K M * M) ≤ M) {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) @@ -198,6 +244,32 @@ theorem exists_computableInO_search_threeOccurrence_of_budget exists_computableInO_search_threeOccurrence_promise_of_budget hM hK hM2 hk2 hkK hB hbudget hf hf_eq hδ fun χ _ _ _ => ht χ +include hK in +/-- **Unconditional end-to-end counting sparsification from a dense 3-occurrence model counter at +`countingThresholdBase K B` (promised runtime).** -/ +theorem exists_computableInO_search_threeOccurrence_thresholdBase + {k B : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → + search (countingThresholdBase K B) K k B (guardOccurrenceCounter k f) φ = φ.modelCount) ∧ + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO + (search (countingThresholdBase K B) K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ + ((1 / B + occBound K (countingThresholdBase K B) * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_threeOccurrence_promise_of_budget + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K B)) hK + (two_le_countingThresholdBase K B) hk2 hkK hB (fun hk2' => countingThresholdBase_spec hk2' hB) + hf hf_eq hδ ht + end CountingSparsify end Cslib.Complexity diff --git a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean index b1b56cc3d..ad7e24dcf 100644 --- a/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean +++ b/Complexity/Logics/Propositional/ClauseSet/Sparsification/CountingBounds.lean @@ -8,7 +8,7 @@ module -- upstream: candidate public import Complexity.Init -public import Complexity.Foundations.Data.Nat.Choose.Real +public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingParameters public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingPathBounds public import Complexity.Logics.Propositional.ClauseSet.Sparsification.CountingSearch @@ -19,7 +19,8 @@ We connect the actual counting-path insertion and petal-step bounds of `Counting (Appendix A.2–A.3, Lemma A.2 and the path budgets of Lemma A.3, printed pp. 46–47 of [OpenAI2026ETH]) to the prefix words of a `CountingBranchingRule`, and combine the leaf-word petal-step injection with the geometric binomial-tail bound -`Cslib.sum_choose_le_two_pow_div_add_one` to bound the number of leaf words of any +`Cslib.sum_choose_le_two_pow_div_add_one` and the first-success threshold base +`countingThresholdBase` of `CountingParameters` to bound the number of leaf words of any counting-sparsification tree. For a `CountingBranchingRule (fun r => M ^ r) V σ` starting from a state `s` whose clause set @@ -27,24 +28,19 @@ For a `CountingBranchingRule (fun r => M ^ r) V σ` starting from a state `s` wh variable set `declared` of size `N = declared.card`: * every node word `w` has length at most `countingDepthCoeff k M * N`, where `countingDepthCoeff k M = 4 * ∑ r < k - 1, (3 * M) ^ r`; -* every node word `w` has `M * #{i < w.length | w[i] = true} ≤ countingBStepCoeff k * N`, where - `countingBStepCoeff k = 4 * ∑ r < k - 1, 3 ^ r`; +* every node word `w` has `M * #{i < w.length | w[i] = true} ≤ countingBChoiceCoeff k * N`, where + `countingBChoiceCoeff k = 4 * ∑ r < k - 1, 3 ^ r`; * because two leaf words with `true` steps at the same positions are equal (`CountingBranchingRule.IsLeafWord.eq_of_getD_eq`), any finset `W` of leaf words injects into the subsets of `range (countingDepthCoeff k M * N)` of cardinality at most - `countingBStepCoeff k * N / M`, giving - `W.card ≤ ∑ j ≤ countingBStepCoeff k * N / M, (countingDepthCoeff k M * N).choose j` + `countingBChoiceCoeff k * N / M`, giving + `W.card ≤ ∑ j ≤ countingBChoiceCoeff k * N / M, (countingDepthCoeff k M * N).choose j` (`CountingBranchingRule.card_le_of_isLeafWord`); -* under the integer parameter budget `countingBStepCoeff k ≤ M` and - `B * countingBStepCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M` (for `2 ≤ k`, with - `1 ≤ B`), `W.card ≤ 2 ^ (N / B + 1)` (`CountingBranchingRule.card_le_two_pow_of_isLeafWord`). - -## Main definitions - -* `Cslib.Logic.PL.ClauseSet.Sparsification.countingDepthCoeff`: the linear depth coefficient - `4 * ∑ r ∈ range (k - 1), (3 * M) ^ r`. -* `Cslib.Logic.PL.ClauseSet.Sparsification.countingBStepCoeff`: the linear `M`-scaled petal-step - coefficient `4 * ∑ r ∈ range (k - 1), 3 ^ r`. +* under the integer parameter budget `countingBChoiceCoeff k ≤ M` and + `B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M` (for `2 ≤ k`, with + `1 ≤ B`), and in particular unconditionally at `M = countingThresholdBase k B`, + `W.card ≤ 2 ^ (N / B + 1)` (`CountingBranchingRule.card_le_two_pow_of_isLeafWord`, + `CountingBranchingRule.card_le_two_pow_thresholdBase`). ## Main results @@ -58,9 +54,10 @@ variable set `declared` of size `N = declared.card`: petal-step budget along node and leaf words. * `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.card_le_of_isLeafWord`: binomial-sum bound on any finset of leaf words. -* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.card_le_two_pow_of_isLeafWord`: +* `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.card_le_two_pow_of_isLeafWord`, + `Cslib.Logic.PL.ClauseSet.Sparsification.CountingBranchingRule.card_le_two_pow_thresholdBase`: exponential bound `2 ^ (declared.card / B + 1)` on any finset of leaf words under the integer - parameter budget. + parameter budget and at `countingThresholdBase k B`. ## References @@ -75,43 +72,15 @@ open Finset variable {V : Type*} [DecidableEq V] -/-! ### Linear depth and petal-step coefficients -/ - -/-- The linear depth coefficient `4 * ∑_{r < k - 1} (3 * M) ^ r` bounding the length of a counting -branching path of width at most `k` (Appendix A.3, Lemma A.3, printed p. 47 [OpenAI2026ETH]). -/ -def countingDepthCoeff (k M : ℕ) : ℕ := - 4 * ∑ r ∈ range (k - 1), (3 * M) ^ r - -/-- The linear petal-step coefficient `4 * ∑_{r < k - 1} 3 ^ r` in the `M`-scaled petal-step budget -`M * #B ≤ countingBStepCoeff k * N` (Appendix A.3, Lemma A.3, printed p. 47 [OpenAI2026ETH]). -/ -def countingBStepCoeff (k : ℕ) : ℕ := - 4 * ∑ r ∈ range (k - 1), 3 ^ r - theorem countingDepthCoeff_eq_zero {k : ℕ} (hk : k ≤ 1) (M : ℕ) : countingDepthCoeff k M = 0 := by have hk0 : k - 1 = 0 := by omega simp [countingDepthCoeff, hk0] -theorem countingBStepCoeff_eq_zero {k : ℕ} (hk : k ≤ 1) : - countingBStepCoeff k = 0 := by +theorem countingBChoiceCoeff_eq_zero {k : ℕ} (hk : k ≤ 1) : + countingBChoiceCoeff k = 0 := by have hk0 : k - 1 = 0 := by omega - simp [countingBStepCoeff, hk0] - -theorem four_le_countingDepthCoeff {k : ℕ} (hk : 2 ≤ k) (M : ℕ) : - 4 ≤ countingDepthCoeff k M := by - have h0 : 0 ∈ range (k - 1) := mem_range.mpr (by omega) - have hsum := single_le_sum (f := fun r => (3 * M) ^ r) (fun _ _ => Nat.zero_le _) h0 - rw [pow_zero] at hsum - unfold countingDepthCoeff - omega - -theorem four_le_countingBStepCoeff {k : ℕ} (hk : 2 ≤ k) : - 4 ≤ countingBStepCoeff k := by - have h0 : 0 ∈ range (k - 1) := mem_range.mpr (by omega) - have hsum := single_le_sum (f := fun r => 3 ^ r) (fun _ _ => Nat.zero_le _) h0 - rw [pow_zero] at hsum - unfold countingBStepCoeff - omega + simp [countingBChoiceCoeff, hk0] /-! ### Prefix paths and leaf-count bounds for a counting branching rule -/ @@ -207,10 +176,10 @@ theorem IsNodeWord.length_le {w : List Bool} (hw : R.IsNodeWord s w) : /-- **Node words have few petal steps.** From a reduced, nontautological clause set of width `≤ k` over `declared`, every node word satisfies -`M * #{i < w.length | w.getD i false = true} ≤ countingBStepCoeff k * declared.card`. -/ +`M * #{i < w.length | w.getD i false = true} ≤ countingBChoiceCoeff k * declared.card`. -/ theorem IsNodeWord.mul_card_le {w : List Bool} (hw : R.IsNodeWord s w) : M * ((range w.length).filter fun i => w.getD i false = true).card ≤ - countingBStepCoeff k * declared.card := by + countingBChoiceCoeff k * declared.card := by obtain ⟨c, H, hstep, hchoice⟩ := exists_choice_seq_of_isNodeWord s hw exact mul_card_counting_B_le hM w.length (fun i => R.toClauseSet (R.walk s (w.take i))) c H (fun i => w.getD i false) hstep hchoice hred hnt hk declared hsupport @@ -223,22 +192,22 @@ theorem IsLeafWord.length_le {w : List Bool} (hw : R.IsLeafWord s w) : /-- **Leaf words have few petal steps.** From a reduced, nontautological clause set of width `≤ k` over `declared`, every leaf word satisfies -`M * #{i < w.length | w.getD i false = true} ≤ countingBStepCoeff k * declared.card`. -/ +`M * #{i < w.length | w.getD i false = true} ≤ countingBChoiceCoeff k * declared.card`. -/ theorem IsLeafWord.mul_card_le {w : List Bool} (hw : R.IsLeafWord s w) : M * ((range w.length).filter fun i => w.getD i false = true).card ≤ - countingBStepCoeff k * declared.card := + countingBChoiceCoeff k * declared.card := hw.isNodeWord.mul_card_le hM hred hnt hk declared hsupport /-- **Binomial-sum leaf-word bound.** From a reduced, nontautological clause set of width `≤ k` over `declared`, any finset `W` of leaf words has cardinality at most -`∑_{j ≤ countingBStepCoeff k * declared.card / M} +`∑_{j ≤ countingBChoiceCoeff k * declared.card / M} (countingDepthCoeff k M * declared.card).choose j`. -/ theorem card_le_of_isLeafWord (W : Finset (List Bool)) (hW : ∀ w ∈ W, R.IsLeafWord s w) : - W.card ≤ ∑ j ∈ range (countingBStepCoeff k * declared.card / M + 1), + W.card ≤ ∑ j ∈ range (countingBChoiceCoeff k * declared.card / M + 1), (countingDepthCoeff k M * declared.card).choose j := by classical set Λ := countingDepthCoeff k M * declared.card - set ρ := countingBStepCoeff k * declared.card / M + set ρ := countingBChoiceCoeff k * declared.card / M let pos (w : List Bool) : Finset ℕ := (range Λ).filter fun i => w.getD i false = true have hpos : ∀ w ∈ W, pos w ∈ (range Λ).powerset.filter (·.card ≤ ρ) := by intro w hw @@ -278,27 +247,44 @@ theorem card_le_of_isLeafWord (W : Finset (List Bool)) (hW : ∀ w ∈ W, R.IsLe refine sum_congr rfl fun j _ => ?_ rw [card_powersetCard, card_range] -/-- **Exponential leaf-word bound.** Under the integer parameter budget `countingBStepCoeff k ≤ M` -and `B * countingBStepCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M` (for `2 ≤ k`, with -`1 ≤ B`), any finset `W` of leaf words has cardinality at most `2 ^ (declared.card / B + 1)`. -/ +/-- **Exponential leaf-word bound.** Under the integer parameter budget `countingBChoiceCoeff k ≤ M` +and `B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M` (for `2 ≤ k`, +with `1 ≤ B`), any finset `W` of leaf words has cardinality at most `2 ^ (declared.card / B + 1)`. +-/ theorem card_le_two_pow_of_isLeafWord {B : ℕ} (hB : 1 ≤ B) - (hbudget : 2 ≤ k → countingBStepCoeff k ≤ M ∧ - B * countingBStepCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M) + (hbudget : 2 ≤ k → countingBChoiceCoeff k ≤ M ∧ + B * countingBChoiceCoeff k * Nat.clog 2 (4 * countingDepthCoeff k M * M) ≤ M) (W : Finset (List Bool)) (hW : ∀ w ∈ W, R.IsLeafWord s w) : W.card ≤ 2 ^ (declared.card / B + 1) := by refine (card_le_of_isLeafWord hM hred hnt hk declared hsupport W hW).trans ?_ by_cases hk2 : 2 ≤ k · obtain ⟨hΓM, hbud⟩ := hbudget hk2 exact Cslib.sum_choose_le_two_pow_div_add_one - (four_le_countingDepthCoeff hk2 M) (four_le_countingBStepCoeff hk2) + (four_le_countingDepthCoeff hk2) (four_le_countingBChoiceCoeff hk2) hΓM hB hbud declared.card · have hk1 : k ≤ 1 := by omega - rw [countingDepthCoeff_eq_zero hk1 M, countingBStepCoeff_eq_zero hk1] + rw [countingDepthCoeff_eq_zero hk1 M, countingBChoiceCoeff_eq_zero hk1] simp only [zero_mul, Nat.zero_div, zero_add, range_one, sum_singleton, Nat.choose_zero_right] exact Nat.one_le_two_pow end PathBounds +/-- **Unconditional exponential leaf-word bound at `countingThresholdBase k B`.** +For any `1 ≤ B` and any counting branching rule `R` at `M = countingThresholdBase k B`, any finset +`W` of leaf words from a reduced, nontautological clause set of width `≤ k` over `declared` has +cardinality at most `2 ^ (declared.card / B + 1)`. -/ +theorem card_le_two_pow_thresholdBase {k B : ℕ} (hB : 1 ≤ B) + {R : CountingBranchingRule (fun r => (countingThresholdBase k B) ^ r) V σ} {s : σ} + (hred : Reduced (R.toClauseSet s)) + (hnt : ∀ C ∈ R.toClauseSet s, ∀ l ∈ C, Std.Sat.Literal.negate l ∉ C) + (hk : ∀ C ∈ R.toClauseSet s, C.card ≤ k) + (declared : Finset V) + (hsupport : ∀ C ∈ R.toClauseSet s, ∀ l ∈ C, l.1 ∈ declared) + (W : Finset (List Bool)) (hW : ∀ w ∈ W, R.IsLeafWord s w) : + W.card ≤ 2 ^ (declared.card / B + 1) := + card_le_two_pow_of_isLeafWord (two_le_countingThresholdBase k B) hred hnt hk declared hsupport hB + (fun hk2 => countingThresholdBase_spec hk2 hB) W hW + end CountingBranchingRule end Cslib.Logic.PL.ClauseSet.Sparsification diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean index 1ce1d2a77..27b9383c5 100644 --- a/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -23,15 +23,15 @@ open Cslib.Complexity.CountingSparsify Turing.MultiTapeTM example : countingDepthCoeff 1 2 = 0 := by decide -example : countingBStepCoeff 1 = 0 := by decide +example : countingBChoiceCoeff 1 = 0 := by decide example : countingDepthCoeff 2 2 = 4 := by decide -example : countingBStepCoeff 2 = 4 := by decide +example : countingBChoiceCoeff 2 = 4 := by decide example : countingDepthCoeff 3 2 = 28 := by decide -example : countingBStepCoeff 3 = 16 := by decide +example : countingBChoiceCoeff 3 = 16 := by decide /-- Unconditional `CountingTreeBound` witness for `K = 1, B = 1, M = 2`. -/ example : CountingTreeBound 2 1 1 (countingDepthCoeff 1 2) (by decide) (by decide) := @@ -47,6 +47,14 @@ example : CountingTreeBound 64 2 1 (countingDepthCoeff 2 64) (by decide) (by dec example : CountingTreeBound 512 3 1 (countingDepthCoeff 3 512) (by decide) (by decide) := countingTreeBound_of_budget (by decide) (by decide) (by decide) (by decide) (by decide) +/-- Unconditional `CountingTreeBound` witness for arbitrary `1 ≤ K` and `1 ≤ B` at +`countingThresholdBase K B`. -/ +example {K B : ℕ} (hK : 1 ≤ K) (hB : 1 ≤ B) : + CountingTreeBound (countingThresholdBase K B) K B + (countingDepthCoeff K (countingThresholdBase K B)) + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K B)) hK := + countingTreeBound_countingThresholdBase hK hB + /-- Model-count correctness of `CountingSparsify.search` on 3-CNF at `(M, K, k, B) = (512, 3, 3, 1)` without an external `CountingTreeBound` hypothesis. -/ example {g : CNF ℕ → ℕ} @@ -57,22 +65,26 @@ example {g : CNF ℕ → ℕ} search_eq_modelCount_of_budget (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) hg hφ -/-- End-to-end 3-CNF counting-sparsification reduction from a dense 3-occurrence model counter at -`(M, K, k, B) = (512, 3, 3, 1)` without an external `CountingTreeBound` hypothesis. -/ -example {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) - (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ 3) → (∀ v, χ.vars.count v ≤ 3) → +/-- Unconditional end-to-end counting-sparsification reduction from a dense 3-occurrence model +counter for arbitrary `2 ≤ k ≤ K` and `1 ≤ B` at `countingThresholdBase K B`. -/ +example {K k B : ℕ} (hK : 1 ≤ K) (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} - (ht : ∀ χ : CNF ℕ, (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : - (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ 3) → - search 512 3 3 1 (guardOccurrenceCounter 3 f) φ = φ.modelCount) ∧ + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) : + (∀ φ : CNF ℕ, (∀ c ∈ φ, c.length ≤ k) → + search (countingThresholdBase K B) K k B (guardOccurrenceCounter k f) φ = φ.modelCount) ∧ ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, - ComputableInO (search 512 3 3 1 (guardOccurrenceCounter 3 f)) encCNF encNat T T ∧ + ComputableInO + (search (countingThresholdBase K B) K k B (guardOccurrenceCounter k f)) encCNF encNat T T ∧ ∀ φ : CNF ℕ, (T φ : ℝ) ≤ - C₀ * ((2 : ℝ) ^ ((1 / (1 : ℕ) + occBound 3 512 * (3 * δ)) * φ.numVars) * + C₀ * ((2 : ℝ) ^ + ((1 / B + occBound K (countingThresholdBase K B) * (k * δ)) * φ.numVars) * (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := - exists_computableInO_search_threeOccurrence_of_budget - (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) - hf hf_eq hδ ht + exists_computableInO_search_threeOccurrence_thresholdBase + hK hk2 hkK hB hf hf_eq hδ ht end Cslib.Complexity.CountingTreeBoundTests From 91ff927e9c62c89ab19927ad8afc06f3bccd8c49 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Tue, 27 Oct 2026 00:44:13 -0400 Subject: [PATCH 22/24] feat(counting): preserve independent sparsification workspace --- .../FineGrained/CountingSparsification.lean | 292 +++++++++++++----- .../FineGrained/ThreeOccurrenceReduction.lean | 145 +++++++-- .../FineGrained/CountingWorkspace.lean | 119 +++++++ 3 files changed, 462 insertions(+), 94 deletions(-) create mode 100644 ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean diff --git a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean index bc712e30a..ffb89e22f 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean @@ -50,10 +50,9 @@ Given a formula `φ : CNF ℕ` with `n = φ.numVars` occurring variables, the se To mirror `Sparsify`, this module packages the quantitative tree depth (`w.length ≤ L_coeff * N`) and fixed-parameter leaf-count (`W.card ≤ 2 ^ (N / B + 1)`) hypotheses into `CountingTreeBound`, a -library adaptation of Appendix A.2–A.3 (Lemmas A.2–A.3, pp. 46–47) that remains an open -quantitative obligation for `K ≥ 2` (unit insertion accounting and finite recursive leaf partition -are prerequisites, with higher-width occurrence, deletion, survival, and leaf-recurrence bounds -handled separately). Under `CountingTreeBound`, running `step M K g` for +library adaptation of Appendix A.2–A.3 (Lemmas A.2–A.3, pp. 46–47). The module +`Complexity.Computability.Complexity.FineGrained.CountingTreeBound` supplies this hypothesis from +an explicit integer parameter budget. Under `CountingTreeBound`, running `step M K g` for `rounds B φ = 2 ^ (n / B + 2)` steps visits every leaf of the counting-sparsification tree and returns `φ.modelCount` (`CountingSparsify.search_eq_modelCount`). Given a total leaf routine `g` with `hg : ComputableInO g encCNF encNat t t` and a global bound `ht` holding for every @@ -61,8 +60,13 @@ with `hg : ComputableInO g encCNF encNat t t` and a global bound `ht` holding fo `CountingSparsify.exists_computableInO_search` yields a combined time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the fixed-parameter library-adaptation transfer bound `T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * n) * (|encCNF φ| + 1) ^ E) + C₀`. -Because `hg` bounds both time and space of `g` by `t` (which may be exponential) and the step space -bound includes `leafTime`, no separate polynomial leaf-workspace certificate is claimed here. +The independent-space extension `CountingSparsify.exists_computableInO_search_polySpace` instead +starts from one total certificate `ComputableInO g encCNF encNat t s`, with separate quantitative +promises for sparse width-`≤ k` leaf inputs. It preserves this exponential time envelope and gives +polynomial workspace in the full `encCNF` length. Initialization, every traversal prefix including +the terminal state, the binary clock, retained declared variables, and the final output are charged. +All parameters `M`, `K`, `k`, `B`, and `L_coeff` are fixed before the machine is chosen; runtime +parameter uniformity and the native subexponential counting endpoint remain separate obligations. ## Main definitions @@ -83,6 +87,8 @@ bound includes `leafTime`, no separate polynomial leaf-workspace certificate is on formulas of width `≤ k` (and `0` otherwise). * `Cslib.Complexity.CountingSparsify.exists_computableInO_search`: native `ComputableInO ... T T` time-and-space transfer certificate for `CountingSparsify.search`. +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_polySpace`: the same time envelope + with independent polynomial workspace, from separately promised leaf time and workspace. ## References @@ -521,11 +527,10 @@ private theorem computableTidilyInPolyTime_rounds (B : ℕ) : unfold rounds fun_prop -/-- **The time of a counting-sparsification step**: a polynomial in the length of the encoded state, -plus the time of the leaf counter `g` and a polynomial in the state and counter output lengths when -`g` runs on a sparse head formula. -/ -private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} - (hg : ComputableTidilyInO g encCNF encNat t t) : +/-- Separate step time and workspace: a polynomial in the encoded state and counter-output +lengths, plus the corresponding resource of `g` when it runs on a sparse head formula. -/ +private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ} {t u : CNF ℕ → ℕ} + (hg : ComputableTidilyInO g encCNF encNat t u) : ∃ d, ComputableTidilyInO (step M K g) encState encState (fun s => ((encState s).length + 1) ^ d + if s.2.1.isEmpty = false ∧ @@ -536,7 +541,7 @@ private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ (fun s => ((encState s).length + 1) ^ d + if s.2.1.isEmpty = false ∧ isSparse (countingThreshold M) K (s.2.1.headD []) = true then - t (s.2.1.headD []) + + u (s.2.1.headD []) + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d else 0) := by obtain ⟨dE, eE, hE⟩ := (show ComputableTidilyInPolyTime (fun s : State => s.2.1.isEmpty) encState @@ -570,6 +575,7 @@ private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ generalize 4 ^ D = F at hp h4D ⊢ generalize ((encState s).length + 1) ^ D = P generalize t (s.2.1.headD []) = T + generalize u (s.2.1.headD []) = U generalize ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ D = Q at hp ⊢ generalize ((encPair encState encNat (s, g (s.2.1.headD []))).length + 1) ^ D = R at hp ⊢ cases s.2.1.isEmpty <;> @@ -633,7 +639,7 @@ private theorem length_encState_le {stk : List {ψ : CNF ℕ // CountingNormaliz end Sizes -/-! ### The time of the counting-sparsification search -/ +/-! ### Independent time and workspace of the counting-sparsification search -/ /-- The time bound of the leaf counter on the sparse leaves of the tree. -/ private noncomputable def leafTime (K M L_coeff : ℕ) (δ : ℝ) (c e : ℕ) (φ : CNF ℕ) : ℕ := @@ -641,38 +647,46 @@ private noncomputable def leafTime (K M L_coeff : ℕ) (δ : ℝ) (c e : ℕ) ( ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c⌉₊ include hM hK in -/-- **The time and space of the rounds.** Under `CountingTreeBound`, given a total leaf routine `g` -with `hg : ComputableTidilyInO g encCNF encNat t t` and global bound `ht` holding for every -`ψ : CNF ℕ`, `search M K k B g` runs within time equal to initialization plus `rounds B φ + 1` -times the per-round bound (a polynomial in `stateBound`, plus `leafTime`, plus the binary -round-counter step cost), and within space equal to initialization/round-count space plus the -per-round bound (including `leafTime`, since `hg` bounds both time and space of `g` by `t`). -/ -private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) +/-- The same prefix invariant bounds step time and workspace separately, including the terminal +empty stack, capped accumulator, retained declared-variable count, and binary loop clock. -/ +private theorem exists_computableTidilyInO_search_resources (hM2 : 2 ≤ M) + {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) - {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableTidilyInO g encCNF encNat t t) + {g : CNF ℕ → ℕ} {t u R Q : CNF ℕ → ℕ} + (hg : ComputableTidilyInO g encCNF encNat t u) (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) - {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} - (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + (ht : ∀ φ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + ψ.length ≤ occBound K M * φ.numVars + 1 → + (encCNF ψ).length ≤ nodeBound K L_coeff φ → t ψ ≤ R φ) + (hu : ∀ φ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + ψ.length ≤ occBound K M * φ.numVars + 1 → + (encCNF ψ).length ≤ nodeBound K L_coeff φ → u ψ ≤ Q φ) : ∃ d dn di, ComputableTidilyInO (search M K k B g) encCNF encNat (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + - leafTime K M L_coeff δ c e φ + + R φ + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2)) (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + - (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ) + + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + Q φ) + stateBound K L_coeff φ + (encNat (rounds B φ)).length) := by obtain ⟨d, hstep⟩ := exists_computableTidilyInO_step M K hg obtain ⟨dn, en, hn⟩ := (computableTidilyInPolyTime_rounds B).exists_degree obtain ⟨di, ei, hi⟩ := (computableTidilyInPolyTime_init k).exists_degree - have hround : ∀ φ, ∀ i ≤ rounds B φ, + have hround (v V : CNF ℕ → ℕ) + (hv : ∀ φ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + ψ.length ≤ occBound K M * φ.numVars + 1 → + (encCNF ψ).length ≤ nodeBound K L_coeff φ → v ψ ≤ V φ) : ∀ φ, ∀ i ≤ rounds B φ, (fun s : State => ((encState s).length + 1) ^ d + if s.2.1.isEmpty = false ∧ isSparse (countingThreshold M) K (s.2.1.headD []) = true then - t (s.2.1.headD []) + + v (s.2.1.headD []) + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d else 0) ((step M K g)^[i] (init k φ)) ≤ - 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ := by + 2 * (2 * stateBound K L_coeff φ + 1) ^ d + V φ := by intro φ i _ obtain ⟨stk, acc, heq, hstk, hacc, hmem⟩ := (iterate_step hM hK hM2 hkK hTB g φ).2 i rw [heq] @@ -704,17 +718,7 @@ private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ((encState (φ.distinctVars.length, (ψ :: rest.map Subtype.val, acc))).length + (encNat (g ψ)).length + 1) ^ d ≤ (2 * stateBound K L_coeff φ + 1) ^ d := Nat.pow_le_pow_left (by omega) d - have h₁ : (2 : ℝ) ^ (δ * ψ.length) ≤ - 2 ^ (δ * ((occBound K M * φ.numVars + 1 : ℕ) : ℝ)) := - Real.rpow_le_rpow_of_exponent_le (by norm_num) - (mul_le_mul_of_nonneg_left (by exact_mod_cast hlen) hδ) - have h₂ : (((encCNF ψ).length : ℝ) + 1) ^ e ≤ ((nodeBound K L_coeff φ : ℝ) + 1) ^ e := - pow_le_pow_left₀ (by positivity) (by exact_mod_cast Nat.add_le_add_right henc 1) e - have h₃ := mul_le_mul h₁ h₂ (by positivity) (by positivity) - have h₄ := mul_le_mul_of_nonneg_left h₃ (Nat.cast_nonneg c : (0 : ℝ) ≤ c) - have h₅ := (ht ψ).trans (add_le_add h₄ le_rfl) - have ht_le : t ψ ≤ leafTime K M L_coeff δ c e φ := - Nat.cast_le.mp (h₅.trans (Nat.le_ceil _)) + have hv_le := hv φ ψ hwidth hc.2 hlen henc omega · omega have hstate : ∀ φ, ∀ i ≤ rounds B φ, @@ -724,32 +728,60 @@ private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : rw [heq] exact length_encState_le hM hK hkK hstk hacc hmem exact ⟨d, dn, di, - (ComputableTidilyInO.iterate hn.inO hi.inO hstep hround hround hstate).snd'.snd'⟩ + (ComputableTidilyInO.iterate hn.inO hi.inO hstep + (hround t R ht) (hround u Q hu) hstate).snd'.snd'⟩ + + +private theorem le_leafTime {K M L_coeff : ℕ} {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + {t : CNF ℕ → ℕ} {φ ψ : CNF ℕ} + (ht : (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) + (hlen : ψ.length ≤ occBound K M * φ.numVars + 1) + (henc : (encCNF ψ).length ≤ nodeBound K L_coeff φ) : + t ψ ≤ leafTime K M L_coeff δ c e φ := by + have h₁ : (2 : ℝ) ^ (δ * ψ.length) ≤ + 2 ^ (δ * ((occBound K M * φ.numVars + 1 : ℕ) : ℝ)) := + Real.rpow_le_rpow_of_exponent_le (by norm_num) + (mul_le_mul_of_nonneg_left (by exact_mod_cast hlen) hδ) + have h₂ : (((encCNF ψ).length : ℝ) + 1) ^ e ≤ ((nodeBound K L_coeff φ : ℝ) + 1) ^ e := + pow_le_pow_left₀ (by positivity) (by exact_mod_cast Nat.add_le_add_right henc 1) e + have h₃ := mul_le_mul h₁ h₂ (by positivity) (by positivity) + have h₄ := mul_le_mul_of_nonneg_left h₃ (Nat.cast_nonneg c : (0 : ℝ) ≤ c) + exact Nat.cast_le.mp ((ht.trans (add_le_add h₄ le_rfl)).trans (Nat.le_ceil _)) include hM hK in -/-- **The time and space of the counting-sparsification search.** Given a total leaf routine `g` -with `hg : ComputableInO g encCNF encNat t t` whose time and space `t ψ` satisfy the global bound -`ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : CNF ℕ`, and whose output is at -most `2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` has a combined -time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the -fixed-parameter transfer bound -`T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀` (a library -adaptation of the leaf-to-root runtime assembly, without an independent polynomial-space -certificate). -/ -theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) +/-- **The time and space of the rounds.** Under `CountingTreeBound`, given a total leaf routine `g` +with `hg : ComputableTidilyInO g encCNF encNat t t` and global bound `ht` holding for every +`ψ : CNF ℕ`, `search M K k B g` runs within time equal to initialization plus `rounds B φ + 1` +times the per-round bound (a polynomial in `stateBound`, plus `leafTime`, plus the binary +round-counter step cost), and within space equal to initialization/round-count space plus the +per-round bound (including `leafTime`, since `hg` bounds both time and space of `g` by `t`). -/ +private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) - {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableTidilyInO g encCNF encNat t t) (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : - ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, - ComputableInO (search M K k B g) encCNF encNat T T ∧ - ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + ∃ d dn di, ComputableTidilyInO (search M K k B g) encCNF encNat + (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2)) + (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ) + + stateBound K L_coeff φ + (encNat (rounds B φ)).length) := by + apply exists_computableTidilyInO_search_resources hM hK hM2 hkK hTB hg hg_le <;> + intro φ ψ _ _ hlen henc <;> exact le_leafTime hδ (ht ψ) hlen henc + +private theorem search_time_bound (K M B L_coeff d dn di : ℕ) + {δ : ℝ} (hδ : 0 ≤ δ) (c e : ℕ) : + ∃ C₀ E : ℕ, ∀ φ : CNF ℕ, + (((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + stateBound K L_coeff φ + + ((encNat (rounds B φ)).length + 1) ^ 2) : ℕ) ≤ C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by - obtain ⟨d, dn, di, h⟩ := - exists_computableTidilyInO_search hM hK hM2 hkK hTB hg.tidy hg_le hδ ht set Kx := K * L_coeff set c₁ := (2 + Kx) * (6 * K + 1) set c₂ := 4 + 4 * (L_coeff + 1) * (2 * c₁ + 1) + 10 @@ -762,23 +794,7 @@ theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) - have h_mono : ComputableTidilyInO (search M K k B g) encCNF encNat T T := - h.mono_le (fun _ => le_rfl) fun φ => by - dsimp only [T] - have h1 : 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + - stateBound K L_coeff φ + (encNat (rounds B φ)).length ≤ - 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + - stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2 := by - have : (encNat (rounds B φ)).length ≤ ((encNat (rounds B φ)).length + 1) ^ 2 := - (Nat.le_succ _).trans (Nat.le_self_pow two_ne_zero _) - omega - have h2 := Nat.le_mul_of_pos_left - (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + - stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) - (Nat.succ_pos (rounds B φ)) - simp only [Nat.succ_eq_add_one] at h2 - omega - refine ⟨T, ⌈C₀⌉₊, E, .of_tidy h_mono, fun φ => ?_⟩ + refine ⟨⌈C₀⌉₊, E, fun φ => ?_⟩ set L := (encCNF φ).length set N := φ.numVars set P := (L + 1) ^ E @@ -935,6 +951,136 @@ theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k exact mul_le_mul_of_nonneg_right (Nat.le_ceil C₀) (by positivity) linarith [h_prod, (Nat.cast_nonneg ⌈C₀⌉₊ : (0 : ℝ) ≤ ⌈C₀⌉₊)] +include hM hK in +/-- **The time and space of the counting-sparsification search.** Given a total leaf routine `g` +with `hg : ComputableInO g encCNF encNat t t` whose time and space `t ψ` satisfy the global bound +`ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : CNF ℕ`, and whose output is at +most `2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` has a combined +time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the +fixed-parameter transfer bound +`T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀` (a library +adaptation of the leaf-to-root runtime assembly, without an independent polynomial-space +certificate). -/ +theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B g) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, dn, di, h⟩ := + exists_computableTidilyInO_search hM hK hM2 hkK hTB hg.tidy hg_le hδ ht + obtain ⟨C₀, E, hT⟩ := search_time_bound K M B L_coeff d dn di hδ c e + set T : CNF ℕ → ℕ := fun φ => + ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) + have h_mono : ComputableTidilyInO (search M K k B g) encCNF encNat T T := + h.mono_le (fun _ => le_rfl) fun φ => by + dsimp only [T] + have h1 : 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + (encNat (rounds B φ)).length ≤ + 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2 := by + have : (encNat (rounds B φ)).length ≤ ((encNat (rounds B φ)).length + 1) ^ 2 := + (Nat.le_succ _).trans (Nat.le_self_pow two_ne_zero _) + omega + have h2 := Nat.le_mul_of_pos_left + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) + (Nat.succ_pos (rounds B φ)) + simp only [Nat.succ_eq_add_one] at h2 + omega + exact ⟨T, C₀, E, .of_tidy h_mono, hT⟩ + +private theorem polyBounded_nodeBound (K L_coeff : ℕ) : + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) (nodeBound K L_coeff) := by + have hn : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) CNF.numVars := + PolyBounded.self.mono fun φ => φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hm : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) List.length := + PolyBounded.self.mono fun φ => length_le_length_encList _ φ + have hclauses := (hm.add (PolyBounded.const 1)).add + ((PolyBounded.const K).mul ((PolyBounded.const L_coeff).mul hn)) + have hliteral := ((PolyBounded.const 2).mul + ((PolyBounded.const K).mul (((PolyBounded.const 2).mul + (PolyBounded.self (size := fun φ : CNF ℕ => (encCNF φ).length))).add (.const 1)))).add + (PolyBounded.const 1) + exact hclauses.mul hliteral + +private theorem polyBounded_stateBound (K L_coeff : ℕ) : + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) (stateBound K L_coeff) := by + have hn : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) CNF.numVars := + PolyBounded.self.mono fun φ => φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hstack := (((PolyBounded.const L_coeff).mul hn).add (.const 1)).mul + (((PolyBounded.const 2).mul (polyBounded_nodeBound K L_coeff)).add (.const 1)) + exact (((PolyBounded.const 4).mul hn).add ((PolyBounded.const 4).mul hstack)).add (.const 10) + +private theorem polyBounded_pow {α : Type*} {size f : α → ℕ} + (hf : PolyBounded size f) (d : ℕ) : PolyBounded size (fun a => f a ^ d) := by + obtain ⟨c, e, h⟩ := hf.exists_le + refine .of_forall_le (c ^ d) (e * d) fun a => ?_ + calc f a ^ d ≤ (c * (size a + 1) ^ e) ^ d := Nat.pow_le_pow_left (h a) d + _ = c ^ d * (size a + 1) ^ (e * d) := by rw [mul_pow, ← pow_mul] + +include hM hK in +/-- **Independent polynomial workspace for counting sparsification.** A single total leaf machine +with separate time `t` and workspace `s` gives a single total machine for the existing `search`. +Only sparse width-`≤ k` inputs need satisfy the quantitative leaf promises `ht`, `hs`, and `hg_le`. +The workspace includes all traversal prefixes and the terminal state, the full encoded formulas, +initialization, binary loop clock, retained declared-variable count, and output accumulator. + +The parameters `M`, `K`, `k`, `B`, and `L_coeff`, and all leaf-bound constants are fixed before the +machine is chosen. Polynomial workspace is measured in the full `encCNF` length, independently +of the exponential number of rounds and leaf time. This resource transfer does not construct a +leaf counter or provide a uniform machine for runtime choices of these parameters. -/ +theorem exists_computableInO_search_polySpace (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {g : CNF ℕ → ℕ} {t s : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t s) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ C ∈ ψ, C.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) + (hs : ∀ ψ : CNF ℕ, (∀ C ∈ ψ, C.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → s ψ ≤ a * ((encCNF ψ).length + 1) ^ b) : + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B g) encCNF encNat T S ∧ + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) S ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, dn, di, h⟩ := exists_computableTidilyInO_search_resources hM hK hM2 hkK hTB + hg.tidy hg_le (R := leafTime K M L_coeff δ c e) + (Q := fun φ => a * (nodeBound K L_coeff φ + 1) ^ b) + (fun _ ψ hw hsp hlen henc => le_leafTime hδ (ht ψ hw hsp) hlen henc) + (fun _ ψ hw hsp _ henc => (hs ψ hw hsp).trans + (Nat.mul_le_mul_left a (Nat.pow_le_pow_left (Nat.add_le_add_right henc 1) b))) + obtain ⟨C₀, E, hT⟩ := search_time_bound K M B L_coeff d dn di hδ c e + refine ⟨_, _, C₀, E, .of_tidy h, ?_, hT⟩ + have hclock : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) + (fun φ => (encNat (rounds B φ)).length) := by + refine (PolyBounded.self.add (PolyBounded.const 3)).mono fun φ => ?_ + rw [rounds, CNF.length_distinctVars, encNat_two_pow, List.length_append, + List.length_replicate, List.length_singleton] + have hn := φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hdiv := Nat.div_le_self φ.numVars B + omega + have hinput := (PolyBounded.self (size := fun φ : CNF ℕ => (encCNF φ).length)).add + (PolyBounded.const 1) + have hinit := (polyBounded_pow hinput dn).add (polyBounded_pow hinput di) + have hstep := (PolyBounded.const 2).mul (polyBounded_pow + (((PolyBounded.const 2).mul (polyBounded_stateBound K L_coeff)).add (.const 1)) d) + have hleaf := (PolyBounded.const a).mul + (polyBounded_pow ((polyBounded_nodeBound K L_coeff).add (.const 1)) b) + exact ((hinit.add (hstep.add hleaf)).add (polyBounded_stateBound K L_coeff)).add hclock + end CountingSparsify end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean index 7da861057..9ab8f1b1a 100644 --- a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean +++ b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean @@ -61,6 +61,13 @@ pp. 43–47). counter via `CountingSparsify.search` in time and space `O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E')`. +* `Cslib.Complexity.exists_computableInO_guardOccurrenceCounter_polySpace_of_promise`, + `CountingSparsify.exists_computableInO_search_threeOccurrence_polySpace_of_promise`: + the same guard, compiler, and search with separate leaf time and polynomial workspace promises, + yielding a total machine with the same time envelope and independent polynomial workspace in the + full input encoding. Parameters are fixed; no leaf counter or runtime parameter uniformity is + supplied by these transfer results. + ## References * [C. H. Papadimitriou, *Computational Complexity*, Proposition 9.3][Papadimitriou94] @@ -333,17 +340,19 @@ theorem guardOccurrenceCounter_pos_iff_mem_kSAT {k : ℕ} (hk : 2 ≤ k) {f : CN simp only [hψ, Bool.false_eq_true, ite_false, lt_irrefl, KSAT, Set.mem_ofPred_eq, hψ', false_and] -private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) - {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) - {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} - (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → - (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ - c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * - ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : - ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, - ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ - ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ - C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by +private theorem exists_computableInO_guardOccurrenceCounter_resources (k : ℕ) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) : + ∃ d l : ℕ, (∀ ψ : CNF ℕ, (encCNF (toThreeOccurrenceDense ψ)).length ≤ + l * (((encCNF ψ).length + 1) ^ d + 1)) ∧ + ComputableInO (guardOccurrenceCounter k f) encCNF encNat + (fun ψ => ((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) + else ((encCNF ψ).length + 1) ^ d) + (fun ψ => ((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + sf (toThreeOccurrenceDense ψ) + else ((encCNF ψ).length + 1) ^ d) := by obtain ⟨dc, ec, hc⟩ := (computableInPolyTime_all_length_le k).tidy.exists_degree obtain ⟨dr, er, hr⟩ := computableInPolyTime_toThreeOccurrenceDense.tidy.exists_degree obtain ⟨dz, ez, hz⟩ := @@ -354,17 +363,25 @@ private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) have hrO := (hr.mono (d' := d) (e' := er) (by omega) le_rfl).inO have hzO := (hz.mono (d' := d) (e' := ez) (by omega) le_rfl).inO obtain ⟨l, hl⟩ := hrO.length_le - have hcomp := hf.tidy.comp hrO - have hite := hcO.ite hcomp hzO - set T : CNF ℕ → ℕ := fun ψ => - ((encCNF ψ).length + 1) ^ d + - if (ψ.all fun c => decide (c.length ≤ k)) then - ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) - else - ((encCNF ψ).length + 1) ^ d + exact ⟨d, l, hl, .of_tidy (hcO.ite (hf.tidy.comp hrO) hzO)⟩ + +private theorem guardOccurrenceCounter_time_bound (k d l : ℕ) {tf : CNF ℕ → ℕ} + (hl : ∀ ψ : CNF ℕ, (encCNF (toThreeOccurrenceDense ψ)).length ≤ + l * (((encCNF ψ).length + 1) ^ d + 1)) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → + (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ + c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * + ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : + ∃ C₀ E : ℕ, ∀ ψ : CNF ℕ, + ((((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) + else ((encCNF ψ).length + 1) ^ d : ℕ) : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by set E := d * e + d set K : ℝ := 2 + c * (2 * l + 1) ^ e + c - refine ⟨T, ⌈K⌉₊, E, .of_tidy hite, fun ψ => ?_⟩ + refine ⟨⌈K⌉₊, E, fun ψ => ?_⟩ set P : ℝ := (((encCNF ψ).length : ℝ) + 1) ^ d set Q : ℝ := (((encCNF ψ).length : ℝ) + 1) ^ E set X : ℝ := (2 : ℝ) ^ ((k * δ) * ψ.length) @@ -382,7 +399,6 @@ private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) have hPXQ : P ≤ X * Q := hPQ.trans hQXQ have hPeXQ : P ^ e ≤ X * Q := hPeQ.trans hQXQ have h1XQ : (1 : ℝ) ≤ X * Q := one_le_mul_of_one_le_of_one_le hX1 hQ1 - dsimp only [T] split_ifs with hψ · have hψ' : ∀ c ∈ ψ, c.length ≤ k := by simpa only [List.all_eq_true, decide_eq_true_eq] using hψ @@ -438,6 +454,21 @@ private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) push_cast [P] at hsum ⊢ linarith +private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → + (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ + c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * + ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, l, hl, h⟩ := exists_computableInO_guardOccurrenceCounter_resources k hf + obtain ⟨C₀, E, hT⟩ := guardOccurrenceCounter_time_bound k d l hl hδ ht + exact ⟨_, C₀, E, h, hT⟩ + /-- **From variable-exponential occurrence-3 counting to clause-exponential leaf counting (promised runtime).** If `2 ≤ k` and `f : CNF ℕ → ℕ` has a combined time-and-space certificate @@ -475,6 +506,42 @@ theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := exists_computableInO_guardOccurrenceCounter_aux k hf hδ fun ψ _ => ht (toThreeOccurrenceDense ψ) +/-- The existing width guard and dense occurrence-3 compiler preserve independent polynomial +workspace. `hf` is one total machine; `ht` and `hs` need hold only on the dense width-`≤ k`, +occurrence-`≤ 3` promise. The result is total, including the zero output on rejected widths. +The compiler output, its retained input, and the leaf output are charged by the native composition +rules. All parameters and polynomial constants are fixed before choosing the resulting machine. -/ +theorem exists_computableInO_guardOccurrenceCounter_polySpace_of_promise {k : ℕ} (hk : 2 ≤ k) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ d ∈ χ, d.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) + (hs : ∀ χ : CNF ℕ, (∀ d ∈ χ, d.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → sf χ ≤ a * ((encCNF χ).length + 1) ^ b) : + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T S ∧ + PolyBounded (fun ψ : CNF ℕ => (encCNF ψ).length) S ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, l, hl, h⟩ := exists_computableInO_guardOccurrenceCounter_resources k hf + obtain ⟨C₀, E, hT⟩ := guardOccurrenceCounter_time_bound k d l hl hδ fun ψ hψ => + ht (toThreeOccurrenceDense ψ) (width_toThreeOccurrenceDense hk hψ) + (count_vars_toThreeOccurrenceDense_le ψ) (vars_toThreeOccurrenceDense_eq_range_numVars ψ) + refine ⟨_, _, C₀, E, h, ?_, hT⟩ + refine ⟨fun n => 2 * (n + 1) ^ d + a * (l * ((n + 1) ^ d + 1) + 1) ^ b, + by fun_prop, fun ψ => ?_⟩ + dsimp only + split_ifs with hψ + · have hwidth : ∀ c ∈ ψ, c.length ≤ k := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + have hsψ := hs (toThreeOccurrenceDense ψ) (width_toThreeOccurrenceDense hk hwidth) + (count_vars_toThreeOccurrenceDense_le ψ) (vars_toThreeOccurrenceDense_eq_range_numVars ψ) + have hpow := Nat.mul_le_mul_left a + (Nat.pow_le_pow_left (Nat.add_le_add_right (hl ψ) 1) b) + omega + · omega + namespace CountingSparsify variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) @@ -539,6 +606,42 @@ theorem exists_computableInO_search_of_threeOccurrence_counter (hM2 : 2 ≤ M) { exists_computableInO_search_threeOccurrence_of_promise hM hK hM2 hk2 hkK hTB hf hf_eq hδ fun χ _ _ _ => ht χ +include hM hK in +/-- Counting sparsification with independent polynomial workspace from one total dense +occurrence-3 counter whose correctness, time, and space guarantees are promised only on dense +width-`≤ k`, occurrence-`≤ 3` formulas. The unchanged search returns the model count on width +`≤ k` inputs and zero otherwise, in the original exponential time envelope and polynomial space +in the full input encoding. `M`, `K`, `k`, `B`, and `L_coeff` are fixed parameters; this does not +supply a counter or establish uniformity for runtime parameter choices. -/ +theorem exists_computableInO_search_threeOccurrence_polySpace_of_promise + (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) + (hs : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → sf χ ≤ a * ((encCNF χ).length + 1) ^ b) : + (∀ φ : CNF ℕ, search M K k B (guardOccurrenceCounter k f) φ = + if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0) ∧ + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T S ∧ + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) S ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + refine ⟨search_eq_ite_modelCount hM hK hM2 hkK hTB + (fun ψ hψ _ => guardOccurrenceCounter_eq_modelCount hk2 hf_eq hψ), ?_⟩ + obtain ⟨Tg, Sg, Cg, Eg, hgO, hgS, hgt⟩ := + exists_computableInO_guardOccurrenceCounter_polySpace_of_promise hk2 hf hδ ht hs + obtain ⟨a', b', hgs⟩ := hgS.exists_le + exact exists_computableInO_search_polySpace hM hK hM2 hkK hTB hgO + (fun ψ _ _ => guardOccurrenceCounter_le_two_pow_numVars hk2 hf_eq ψ) + (by positivity) (fun ψ _ _ => hgt ψ) (fun ψ _ _ => hgs ψ) + end CountingSparsify end Cslib.Complexity diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean new file mode 100644 index 000000000..76763234e --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean @@ -0,0 +1,119 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.CountingTreeBound +public import ComplexityTests.Computability.Complexity.FineGrained.CountingSparsification +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain + +/-! +# Independent counting-sparsification workspace controls + +A concrete native polynomial-time leaf routine is given an exponential time allowance while its +workspace stays polynomial. The new transfer is instantiated with an unconditional tree bound. +The routine is only a diagnostic: it counts the particular leaves below, not arbitrary CNFs. +The branching trace checks the declared-variable correction and terminal stack separately from +the resource theorem, without adding a second search or counting algorithm. +-/ + +@[expose] public section + +namespace Cslib.Complexity.Tests.CountingWorkspace + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification Turing.MultiTapeTM +open CountingSparsify + +/-- Diagnostic leaf output: reject an empty clause, otherwise return one. -/ +def leafProbe (ψ : CNF ℕ) : ℕ := if ψ.any List.isEmpty then 0 else 1 + +private theorem probe_certificate (q : CNF ℕ → ℕ) : + ∃ d, ComputableInO leafProbe encCNF encNat + (fun ψ => 2 ^ q ψ * ((encCNF ψ).length + 1) ^ d) + (fun ψ => ((encCNF ψ).length + 1) ^ d) := by + have hp : ComputableTidilyInPolyTime leafProbe encCNF encNat := by + unfold leafProbe encCNF + fun_prop + obtain ⟨d, e, h⟩ := hp.exists_degree + exact ⟨d, .of_tidy (h.inO.mono_le + (fun ψ => Nat.le_mul_of_pos_left _ (Nat.two_pow_pos (q ψ))) (fun _ => le_rfl))⟩ + +/-- No tree or machine premise is assumed: the width-one tree bound and the concrete leaf +certificate provide a single native search certificate with polynomial workspace. The supplied +leaf time allowance is `2 ^ ψ.length * (|encCNF ψ| + 1) ^ d`. -/ +example : ∃ T S : CNF ℕ → ℕ, + ComputableInO (search 2 1 1 1 leafProbe) encCNF encNat T S ∧ + PolyBounded (fun ψ : CNF ℕ => (encCNF ψ).length) S := by + obtain ⟨d, h⟩ := probe_certificate List.length + have ht (ψ : CNF ℕ) : + ((2 ^ ψ.length * ((encCNF ψ).length + 1) ^ d : ℕ) : ℝ) ≤ + 1 * (2 ^ ((1 : ℝ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ d) + 1 := by + simp only [one_mul, Real.rpow_natCast, Nat.cast_mul, Nat.cast_pow, Nat.cast_ofNat, + Nat.cast_add, Nat.cast_one] + linarith + obtain ⟨T, S, _, _, hTS, hS, _⟩ := exists_computableInO_search_polySpace + (by decide : 1 ≤ 2) (by decide : 1 ≤ 1) (by decide : 2 ≤ 2) (k := 1) le_rfl + (countingTreeBound_one (by decide) 1 1) h + (fun ψ _ _ => by unfold leafProbe; split_ifs <;> simp [Nat.one_le_two_pow]) + (by norm_num : (0 : ℝ) ≤ 1) (c := 1) (e := d) + (fun ψ _ _ => by simpa only [Nat.cast_one] using ht ψ) + (a := 1) (b := d) (fun _ _ _ => by simp) + exact ⟨T, S, hTS, hS⟩ + +/-- The same native diagnostic passes through the existing dense compiler and width guard with +an exponential allowance in the number of variables and a separate polynomial workspace. -/ +example : ∃ T S : CNF ℕ → ℕ, + ComputableInO (guardOccurrenceCounter 2 leafProbe) encCNF encNat T S ∧ + PolyBounded (fun ψ : CNF ℕ => (encCNF ψ).length) S := by + obtain ⟨d, h⟩ := probe_certificate CNF.numVars + have ht (ψ : CNF ℕ) : + ((2 ^ ψ.numVars * ((encCNF ψ).length + 1) ^ d : ℕ) : ℝ) ≤ + 1 * (2 ^ ((1 : ℝ) * ψ.numVars) * (((encCNF ψ).length : ℝ) + 1) ^ d) + 1 := by + simp only [one_mul, Real.rpow_natCast, Nat.cast_mul, Nat.cast_pow, Nat.cast_ofNat, + Nat.cast_add, Nat.cast_one] + linarith + obtain ⟨T, S, _, _, hTS, hS, _⟩ := + exists_computableInO_guardOccurrenceCounter_polySpace_of_promise (by decide : 2 ≤ 2) + h (by norm_num : (0 : ℝ) ≤ 1) (c := 1) (e := d) + (fun ψ _ _ _ => by simpa only [Nat.cast_one] using ht ψ) + (a := 1) (b := d) (fun _ _ _ _ => by simp) + exact ⟨T, S, hTS, hS⟩ + +/-- The width-one guard rejects the tautological two-literal clause. At width two, its variable +remains declared after normalization, so the remaining unit leaf contributes two. -/ +example : search 2 1 1 1 leafProbe [[(7, true)], [(99, true), (99, false)]] = 0 := by + decide + +example : search 2 2 2 1 leafProbe [[(7, true)], [(99, true), (99, false)]] = 2 := by + decide + +/-- The two leaf calls contribute `4 * 1` and `1 * 1`; the terminal empty stack retains count 5. -/ +example : (step 2 2 leafProbe)^[3] (init 2 flowerWithTautology) = (3, ([], 5)) := by + decide + +example : (step 2 2 leafProbe)^[4] (init 2 flowerWithTautology) = (3, ([], 5)) := by + decide + +example : search 2 2 2 2 leafProbe flowerWithTautology = 5 := by decide + +example : search 2 1 0 1 leafProbe ([] : CNF ℕ) = 1 := by decide +example : search 2 1 0 1 leafProbe ([[]] : CNF ℕ) = 0 := by decide +example : search 2 1 0 1 leafProbe [[(1000, true)]] = 0 := by decide + +/-- Equal variable cardinality does not mean equal encoded input size: label bits are charged. -/ +example : CNF.numVars [[(0, true)]] = CNF.numVars [[(1000, true)]] ∧ + (encCNF [[(0, true)]]).length < (encCNF [[(1000, true)]]).length := by + decide + +/-- A resource certificate alone does not assert correct model counting. -/ +example : leafProbe [[(0, true), (1, true)]] = 1 ∧ + CNF.modelCount [[(0, true), (1, true)]] = 3 := by decide + +end Cslib.Complexity.Tests.CountingWorkspace From 0f58c8a958604b9e24b62fc0dba2f190747ef6b0 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 05:46:46 +0000 Subject: [PATCH 23/24] feat(FineGrained): export prefix bounds, runtime-context stepWith, and declared-N searchDeclaredWith --- .../FineGrained/CountingSparsification.lean | 308 ++++++++++++++++-- .../FineGrained/CountingTreeBound.lean | 129 ++++++++ .../FineGrained/CountingTreeBound.lean | 64 ++++ 3 files changed, 476 insertions(+), 25 deletions(-) diff --git a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean index ffb89e22f..784418440 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean @@ -117,7 +117,7 @@ theorem modelCount_le_two_pow_numVars {V : Type*} [DecidableEq V] (ψ : CNF V) : refine (Finset.card_filter_le _ _).trans ?_ simp only [Finset.card_univ, Fintype.card_fun, Fintype.card_bool, Fintype.card_coe, le_rfl] -private theorem length_encNat_le_of_le_two_pow {acc n : ℕ} (h : acc ≤ 2 ^ n) : +theorem length_encNat_le_of_le_two_pow {acc n : ℕ} (h : acc ≤ 2 ^ n) : (encNat acc).length ≤ n + 1 := by refine (ComputableTidilyInO.length_encNat_mono h).trans ?_ rw [encNat_two_pow, List.length_append, List.length_replicate, List.length_singleton] @@ -130,7 +130,7 @@ abbrev State : Type := ℕ × (List (CNF ℕ) × ℕ) /-- States encoded as framed tuples, with the declared variable count in unary and the accumulator in binary. -/ -private abbrev encState : State ↪ List Bool := +abbrev encState : State ↪ List Bool := encPair encUnary (encPair (encList encCNF) encNat) /-- Weight a leaf's occurring-variable model count `c` by `2 ^ (n - ψ.numVars)` for the unused @@ -280,20 +280,20 @@ private theorem mem_vars_of_mem_walk (w : List Bool) : ∀ C ∈ ((rule hM hK).walk (rootState hkK φ) w).1, ∀ l ∈ C, l.1 ∈ φ.vars.toFinset := support_walk_countingRule (one_le_countingThreshold hM) hK mem_vars_of_mem_root w -private theorem vars_walk_subset (w : List Bool) : +theorem vars_walk_subset (w : List Bool) : ((rule hM hK).walk (rootState hkK φ) w).1.vars.toFinset ⊆ φ.vars.toFinset := by intro v hv obtain ⟨l, hl, rfl⟩ := List.mem_map.mp (List.mem_toFinset.mp hv) obtain ⟨C, hC, hlC⟩ := List.mem_flatten.mp hl exact mem_vars_of_mem_walk hM hK hkK w C hC l hlC -private theorem numVars_walk_le (w : List Bool) : +theorem numVars_walk_le (w : List Bool) : ((rule hM hK).walk (rootState hkK φ) w).1.numVars ≤ φ.numVars := Finset.card_le_card (vars_walk_subset hM hK hkK w) /-- Every state reached along a node word of length at most `h` has at most `φ.length + 1 + K * h` clauses. -/ -private theorem length_walk_le (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) +theorem length_walk_le (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) {h : ℕ} (hwlen : w.length ≤ h) : ((rule hM hK).walk (rootState hkK φ) w).1.length ≤ φ.length + 1 + K * h := by have hcard : (((rule hM hK).walk (rootState hkK φ) w).1.toClauseSet).card ≤ @@ -306,7 +306,7 @@ private theorem length_walk_le (hw : (rule hM hK).IsNodeWord (rootState hkK φ) /-- **Sparse leaves are small.** The sparse states of the counting-sparsification tree have at most `occBound K M * φ.numVars + 1` clauses. -/ -private theorem length_walk_le_of_isSparse +theorem length_walk_le_of_isSparse (hs : isSparse (countingThreshold M) K ((rule hM hK).walk (rootState hkK φ) w).1 = true) : ((rule hM hK).walk (rootState hkK φ) w).1.length ≤ occBound K M * φ.numVars + 1 := by set ψ := (rule hM hK).walk (rootState hkK φ) w @@ -319,7 +319,7 @@ private theorem length_walk_le_of_isSparse /-- Every state of the tree has clause width at most `k` (for every `φ : CNF ℕ` and every `k : ℕ`, including `k = 0`). -/ -private theorem length_le_of_mem_walk (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) +theorem length_le_of_mem_walk (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) {C : Clause ℕ} (hC : C ∈ ((rule hM hK).walk (rootState hkK φ) w).1) : C.length ≤ k := by set ψ := (rule hM hK).walk (rootState hkK φ) w rcases Nat.eq_zero_or_pos k with rfl | hk @@ -368,7 +368,7 @@ open Classical in over the leaf words of `rootState hkK φ`. At every round `i`, the stack holds at most `L_coeff * φ.numVars + 1` nodes of depth at most `L_coeff * φ.numVars`, and the accumulator is at most `2 ^ φ.numVars`. -/ -private theorem iterate_step (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) +theorem iterate_step (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) (g : CNF ℕ → ℕ) (φ : CNF ℕ) : (∃ W : Finset (List Bool), (∀ w, w ∈ W ↔ (rule hM hK).IsLeafWord (rootState hkK φ) w) ∧ @@ -416,14 +416,15 @@ private theorem iterate_step (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) rfl include hM hK in -/-- **The search computes the model count.** If `g` computes `ψ.modelCount` on sparse formulas `ψ` -of width at most `k`, then `search M K k B g φ` returns `φ.modelCount` on formulas of width at most -`k` and `0` otherwise. -/ -theorem search_eq_ite_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) - (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} +/-- **The search computes the model count (occurring-variable support promise).** If `g` computes +`ψ.modelCount` on sparse formulas `ψ` of width at most `k` whose occurring variables lie in +`φ.vars.toFinset`, then `search M K k B g φ` returns `φ.modelCount` on formulas of width at most `k` +and `0` otherwise. -/ +theorem search_eq_ite_modelCount_of_subset (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} {φ : CNF ℕ} (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → - isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) - (φ : CNF ℕ) : + isSparse (countingThreshold M) K ψ = true → + ψ.vars.toFinset ⊆ φ.vars.toFinset → g ψ = ψ.modelCount) : search M K k B g φ = if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0 := by classical @@ -440,9 +441,10 @@ theorem search_eq_ite_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k (isSparse_eq_true_iff (one_le_countingThreshold hM) ψ.2.toNormalized).mpr hw.sparse_walk have hwidth : ∀ c ∈ ψ.1, c.length ≤ k := fun c hc => length_le_of_mem_walk hM hK hkK hw.isNodeWord hc - rw [leafWeight, hg ψ.1 hwidth hsparse, CNF.length_distinctVars, CNF.length_distinctVars] - exact (CNF.modelCountOn_eq_mul_modelCount ψ.1 φ.vars.toFinset - (vars_walk_subset hM hK hkK w)).symm + have hsub : ψ.1.vars.toFinset ⊆ φ.vars.toFinset := + vars_walk_subset hM hK hkK w + rw [leafWeight, hg ψ.1 hwidth hsparse hsub, CNF.length_distinctVars, CNF.length_distinctVars] + exact (CNF.modelCountOn_eq_mul_modelCount ψ.1 φ.vars.toFinset hsub).symm have hsum : ∑ w ∈ W, leafWeight φ.distinctVars.length (R.walk s₀ w).1 (g (R.walk s₀ w).1) = (root k φ).modelCountOn φ.vars.toFinset := sum_leafWords_countingRule_modelCountOn (one_le_countingThreshold hM) @@ -456,6 +458,19 @@ theorem search_eq_ite_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k · rw [CNF.modelCountOn_eq_zero_of_nil_mem (List.mem_singleton_self [])] exact Nat.min_eq_right (Nat.zero_le _) +include hM hK in +/-- **The search computes the model count.** If `g` computes `ψ.modelCount` on sparse formulas `ψ` +of width at most `k`, then `search M K k B g φ` returns `φ.modelCount` on formulas of width at most +`k` and `0` otherwise. -/ +theorem search_eq_ite_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) {g : CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ = ψ.modelCount) + (φ : CNF ℕ) : + search M K k B g φ = + if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0 := + search_eq_ite_modelCount_of_subset hM hK hM2 hkK hTB fun ψ hw hsp _ => hg ψ hw hsp + include hM hK in /-- **The search computes `φ.modelCount` on formulas of width at most `k`.** -/ theorem search_eq_modelCount (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) @@ -588,11 +603,11 @@ end PolyTime /-! ### Encoded state sizes -/ /-- A bound on the length of the encoded formulas in the counting-sparsification tree. -/ -private def nodeBound (K L_coeff : ℕ) (φ : CNF ℕ) : ℕ := +def nodeBound (K L_coeff : ℕ) (φ : CNF ℕ) : ℕ := (φ.length + 1 + K * (L_coeff * φ.numVars)) * (2 * (K * (2 * (encCNF φ).length + 1)) + 1) /-- A bound on the length of the encoded states of the counting-sparsification search. -/ -private def stateBound (K L_coeff : ℕ) (φ : CNF ℕ) : ℕ := +def stateBound (K L_coeff : ℕ) (φ : CNF ℕ) : ℕ := 4 * φ.numVars + 4 * ((L_coeff * φ.numVars + 1) * (2 * nodeBound K L_coeff φ + 1)) + 10 private theorem length_encLiteral_le_of_mem_vars {φ : CNF ℕ} {l : Literal ℕ} @@ -609,7 +624,7 @@ section Sizes variable {k L_coeff : ℕ} (hkK : k ≤ K) {φ : CNF ℕ} include hM hK hkK -private theorem length_encCNF_walk_le {w : List Bool} +theorem length_encCNF_walk_le {w : List Bool} (hw : (rule hM hK).IsNodeWord (rootState hkK φ) w) (hwlen : w.length ≤ L_coeff * φ.numVars) : (encCNF ((rule hM hK).walk (rootState hkK φ) w).1).length ≤ nodeBound K L_coeff φ := by set ψ := (rule hM hK).walk (rootState hkK φ) w @@ -619,7 +634,7 @@ private theorem length_encCNF_walk_le {w : List Bool} (Nat.mul_le_mul_right _ (ψ.2.length_le C hC)) exact length_encLiteral_le_of_mem_vars (mem_vars_of_mem_walk hM hK hkK w C hC l hl) -private theorem length_encState_le {stk : List {ψ : CNF ℕ // CountingNormalized K ψ}} {acc : ℕ} +theorem length_encState_le {stk : List {ψ : CNF ℕ // CountingNormalized K ψ}} {acc : ℕ} (hstk : stk.length ≤ L_coeff * φ.numVars + 1) (hacc : acc ≤ 2 ^ φ.numVars) (hmem : ∀ t ∈ stk, ∃ w, (rule hM hK).IsNodeWord (rootState hkK φ) w ∧ w.length ≤ L_coeff * φ.numVars ∧ (rule hM hK).walk (rootState hkK φ) w = t) : @@ -639,6 +654,44 @@ private theorem length_encState_le {stk : List {ψ : CNF ℕ // CountingNormaliz end Sizes +include hM hK in +/-- **Packaged all-prefix invariants of `CountingSparsify.step`.** +Under `CountingTreeBound M K B L_coeff hM hK`, for every round `i : ℕ`: +1. the retained variable count `.1` is `φ.distinctVars.length` and the encoded state has length at + most `stateBound K L_coeff φ` (with `φ.numVars + 1 ≤ stateBound K L_coeff φ`); +2. whenever the stack is nonempty and its top formula `ψ` is `(countingThreshold M, K)`-sparse, + `ψ` has clause width `≤ k`, variable support contained in `φ.vars.toFinset`, + `ψ.numVars ≤ φ.numVars`, `ψ.length ≤ occBound K M * φ.numVars + 1`, and + `(encCNF ψ).length ≤ nodeBound K L_coeff φ`. -/ +theorem iterate_step_prefix_bounds (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) (g : CNF ℕ → ℕ) (φ : CNF ℕ) (i : ℕ) : + let s := (step M K g)^[i] (init k φ) + s.1 = φ.distinctVars.length ∧ + (encState s).length ≤ stateBound K L_coeff φ ∧ + φ.numVars + 1 ≤ stateBound K L_coeff φ ∧ + (s.2.1.isEmpty = false ∧ isSparse (countingThreshold M) K (s.2.1.headD []) = true → + let ψ := s.2.1.headD [] + (∀ c ∈ ψ, c.length ≤ k) ∧ + ψ.vars.toFinset ⊆ φ.vars.toFinset ∧ + ψ.numVars ≤ φ.numVars ∧ + ψ.length ≤ occBound K M * φ.numVars + 1 ∧ + (encCNF ψ).length ≤ nodeBound K L_coeff φ) := by + dsimp only + obtain ⟨stk, acc, heq, hstk, hacc, hmem⟩ := (iterate_step hM hK hM2 hkK hTB g φ).2 i + rw [heq] + refine ⟨rfl, length_encState_le hM hK hkK hstk hacc hmem, by unfold stateBound; omega, ?_⟩ + intro hc + cases stk with + | nil => simp at hc + | cons s rest => + obtain ⟨w, hw, hwlen, rfl⟩ := hmem s List.mem_cons_self + simp only [List.map_cons, List.headD_cons] at hc ⊢ + exact ⟨fun c hC => length_le_of_mem_walk hM hK hkK hw hC, + vars_walk_subset hM hK hkK w, + numVars_walk_le hM hK hkK w, + length_walk_le_of_isSparse hM hK hkK hc.2, + length_encCNF_walk_le hM hK hkK hw hwlen⟩ + /-! ### Independent time and workspace of the counting-sparsification search -/ /-- The time bound of the leaf counter on the sparse leaves of the tree. -/ @@ -999,7 +1052,7 @@ theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k omega exact ⟨T, C₀, E, .of_tidy h_mono, hT⟩ -private theorem polyBounded_nodeBound (K L_coeff : ℕ) : +theorem polyBounded_nodeBound (K L_coeff : ℕ) : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) (nodeBound K L_coeff) := by have hn : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) CNF.numVars := PolyBounded.self.mono fun φ => φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) @@ -1013,7 +1066,7 @@ private theorem polyBounded_nodeBound (K L_coeff : ℕ) : (PolyBounded.const 1) exact hclauses.mul hliteral -private theorem polyBounded_stateBound (K L_coeff : ℕ) : +theorem polyBounded_stateBound (K L_coeff : ℕ) : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) (stateBound K L_coeff) := by have hn : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) CNF.numVars := PolyBounded.self.mono fun φ => φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) @@ -1021,7 +1074,7 @@ private theorem polyBounded_stateBound (K L_coeff : ℕ) : (((PolyBounded.const 2).mul (polyBounded_nodeBound K L_coeff)).add (.const 1)) exact (((PolyBounded.const 4).mul hn).add ((PolyBounded.const 4).mul hstack)).add (.const 10) -private theorem polyBounded_pow {α : Type*} {size f : α → ℕ} +theorem polyBounded_pow {α : Type*} {size f : α → ℕ} (hf : PolyBounded size f) (d : ℕ) : PolyBounded size (fun a => f a ^ d) := by obtain ⟨c, e, h⟩ := hf.exists_le refine .of_forall_le (c ^ d) (e * d) fun a => ?_ @@ -1081,6 +1134,211 @@ theorem exists_computableInO_search_polySpace (hM2 : 2 ≤ M) {k B L_coeff : ℕ (polyBounded_pow ((polyBounded_nodeBound K L_coeff).add (.const 1)) b) exact ((hinit.add (hstep.add hleaf)).add (polyBounded_stateBound K L_coeff)).add hclock +/-! ### Captured runtime context and declared-variable wrapper -/ + +section ContextWrapper + +variable {P : Type*} + +/-- One step of the depth-first search of the counting-sparsification tree carrying an unchanged +runtime context `p : P`, using threshold family `θ p` and context-aware leaf counter `g` +(returning the occurring-variable count `ψ.modelCount` on sparse leaves). -/ +def stepWith (θ : P → ℕ → ℕ) (K : ℕ) (g : P × CNF ℕ → ℕ) (ps : P × State) : P × State := + (ps.1, + if ps.2.2.1.isEmpty then ps.2 + else if isSparse (θ ps.1) K (ps.2.2.1.headD []) then + leafUpdate ps.2 (g (ps.1, ps.2.2.1.headD [])) + else + (ps.2.1, (countingNext (θ ps.1) K (ps.2.2.1.headD []) false :: + countingNext (θ ps.1) K (ps.2.2.1.headD []) true :: ps.2.2.1.tail, ps.2.2.2))) + +@[simp] +theorem stepWith_fst (θ : P → ℕ → ℕ) (K : ℕ) (g : P × CNF ℕ → ℕ) (ps : P × State) : + (stepWith θ K g ps).1 = ps.1 := rfl + +@[simp] +theorem iterate_stepWith_fst (θ : P → ℕ → ℕ) (K : ℕ) (g : P × CNF ℕ → ℕ) (r : ℕ) + (ps : P × State) : ((stepWith θ K g)^[r] ps).1 = ps.1 := by + induction r generalizing ps with + | zero => rfl + | succ r ih => rw [Function.iterate_succ_apply, ih, stepWith_fst] + +@[simp] +theorem stepWith_countingThreshold (MOf : P → ℕ) (K : ℕ) (g : P × CNF ℕ → ℕ) + (p : P) (s : State) : + stepWith (fun q => countingThreshold (MOf q)) K g (p, s) = + (p, step (MOf p) K (fun ψ => g (p, ψ)) s) := rfl + +@[simp] +theorem iterate_stepWith_countingThreshold (MOf : P → ℕ) (K : ℕ) (g : P × CNF ℕ → ℕ) + (r : ℕ) (p : P) (s : State) : + (stepWith (fun q => countingThreshold (MOf q)) K g)^[r] (p, s) = + (p, (step (MOf p) K (fun ψ => g (p, ψ)))^[r] s) := by + induction r generalizing s with + | zero => rfl + | succ r ih => rw [Function.iterate_succ_apply, stepWith_countingThreshold, ih, + Function.iterate_succ_apply] + +/-- Exact right-shift normalization converting a declared-range leaf count +`ψ.modelCountOn (Finset.range (NOf p))` into the occurring-variable count `ψ.modelCount` +(`= h (p, ψ) >>> (NOf p - ψ.distinctVars.length)`). -/ +def normalizeDeclaredLeaf (NOf : P → ℕ) (h : P × CNF ℕ → ℕ) (x : P × CNF ℕ) : ℕ := + h x >>> (NOf x.1 - x.2.distinctVars.length) + +theorem normalizeDeclaredLeaf_eq_div (NOf : P → ℕ) (h : P × CNF ℕ → ℕ) (x : P × CNF ℕ) : + normalizeDeclaredLeaf NOf h x = h x / 2 ^ (NOf x.1 - x.2.numVars) := by + rw [normalizeDeclaredLeaf, Nat.shiftRight_eq_div_pow, CNF.length_distinctVars] + +/-- When `ψ`'s occurring variables lie in `Finset.range (NOf p)` and `h (p, ψ)` equals the +declared-range model count `ψ.modelCountOn (Finset.range (NOf p))`, `normalizeDeclaredLeaf` +recovers the exact occurring-variable count `ψ.modelCount`. -/ +theorem normalizeDeclaredLeaf_eq_modelCount {NOf : P → ℕ} {h : P × CNF ℕ → ℕ} + {p : P} {ψ : CNF ℕ} (hsub : ψ.vars.toFinset ⊆ Finset.range (NOf p)) + (hh : h (p, ψ) = ψ.modelCountOn (Finset.range (NOf p))) : + normalizeDeclaredLeaf NOf h (p, ψ) = ψ.modelCount := by + rw [normalizeDeclaredLeaf_eq_div, hh, + CNF.modelCountOn_eq_mul_modelCount ψ (Finset.range (NOf p)) hsub, Finset.card_range, + Nat.mul_div_right _ (by positivity)] + +/-- When `ψ`'s occurring variables lie in `Finset.range (NOf p)` and `h (p, ψ) ≤ 2 ^ NOf p`, +`normalizeDeclaredLeaf NOf h (p, ψ)` is at most `2 ^ ψ.numVars`. -/ +theorem normalizeDeclaredLeaf_le_two_pow_numVars {NOf : P → ℕ} {h : P × CNF ℕ → ℕ} + {p : P} {ψ : CNF ℕ} (hsub : ψ.vars.toFinset ⊆ Finset.range (NOf p)) + (hh : h (p, ψ) ≤ 2 ^ NOf p) : + normalizeDeclaredLeaf NOf h (p, ψ) ≤ 2 ^ ψ.numVars := by + rw [normalizeDeclaredLeaf_eq_div] + have hle : ψ.numVars ≤ NOf p := by + simpa only [CNF.numVars, Finset.card_range] using Finset.card_le_card hsub + refine Nat.div_le_of_le_mul (hh.trans ?_) + rw [← pow_add, Nat.sub_add_cancel hle] + +/-- Guard checking that every clause of `φ` has width at most `k` and every occurring variable of +`φ` lies in `0, ..., N - 1`. -/ +def validDeclaredInput (k N : ℕ) (φ : CNF ℕ) : Bool := + (φ.all fun c => decide (c.length ≤ k)) && (φ.vars.all fun v => decide (v < N)) + +theorem validDeclaredInput_eq_true_iff {k N : ℕ} {φ : CNF ℕ} : + validDeclaredInput k N φ = true ↔ + (∀ c ∈ φ, c.length ≤ k) ∧ φ.vars.toFinset ⊆ Finset.range N := by + simp only [validDeclaredInput, Bool.and_eq_true, List.all_eq_true, decide_eq_true_eq] + refine and_congr_right' ⟨fun h v hv => ?_, fun h v hv => ?_⟩ + · exact Finset.mem_range.mpr (h v (List.mem_toFinset.mp hv)) + · exact Finset.mem_range.mp (h (List.mem_toFinset.mpr hv)) + +/-- **Runtime-context counting-sparsification search** (occurring-variable count): run +`stepWith (fun q => countingThreshold (MOf q)) K g` for `rounds (BOf p) φ` steps from +`(p, init k φ)` and return the accumulated occurring-variable count. -/ +def searchWith (MOf BOf : P → ℕ) (K k : ℕ) (g : P × CNF ℕ → ℕ) (p : P) (φ : CNF ℕ) : ℕ := + (((stepWith (fun q => countingThreshold (MOf q)) K g)^[rounds (BOf p) φ] + (p, init k φ)).2).2.2 + +@[simp] +theorem searchWith_eq_search (MOf BOf : P → ℕ) (K k : ℕ) (g : P × CNF ℕ → ℕ) + (p : P) (φ : CNF ℕ) : + searchWith MOf BOf K k g p φ = + search (MOf p) K k (BOf p) (fun ψ => g (p, ψ)) φ := by + rw [searchWith, iterate_stepWith_countingThreshold, search] + +/-- **Declared-variable counting-sparsification wrapper**: guards width `≤ k` and variable support +`⊆ Finset.range (NOf p)`, runs `searchWith MOf BOf K k g p φ` (retaining `State.1 = φ.numVars`), +and shifts left by `NOf p - φ.distinctVars.length` (`= * 2 ^ (NOf p - φ.numVars)`). Returns +`φ.modelCountOn (Finset.range (NOf p))` on valid inputs (including `2 ^ NOf p` on the empty formula +`[]`) and `0` otherwise. -/ +def searchDeclaredWith (NOf MOf BOf : P → ℕ) (K k : ℕ) (g : P × CNF ℕ → ℕ) + (p : P) (φ : CNF ℕ) : ℕ := + if validDeclaredInput k (NOf p) φ then + searchWith MOf BOf K k g p φ <<< (NOf p - φ.distinctVars.length) + else 0 + +theorem searchDeclaredWith_eq_ite_mul (NOf MOf BOf : P → ℕ) (K k : ℕ) (g : P × CNF ℕ → ℕ) + (p : P) (φ : CNF ℕ) : + searchDeclaredWith NOf MOf BOf K k g p φ = + if validDeclaredInput k (NOf p) φ then + 2 ^ (NOf p - φ.numVars) * search (MOf p) K k (BOf p) (fun ψ => g (p, ψ)) φ + else 0 := by + rw [searchDeclaredWith, searchWith_eq_search, Nat.shiftLeft_eq, CNF.length_distinctVars, + Nat.mul_comm] + +/-- **Correctness of `searchDeclaredWith` under `CountingTreeBound`.** +If `g (p, ψ) = ψ.modelCount` on sparse formulas `ψ` of width `≤ k` whose occurring variables lie in +`Finset.range (NOf p)`, then `searchDeclaredWith` returns `φ.modelCountOn (Finset.range (NOf p))` +when `validDeclaredInput k (NOf p) φ = true` and `0` otherwise. -/ +theorem searchDeclaredWith_eq_ite_modelCountOn {NOf MOf BOf : P → ℕ} {K k L_coeff : ℕ} + {p : P} (hM : 1 ≤ MOf p) (hK : 1 ≤ K) (hM2 : 2 ≤ MOf p) (hkK : k ≤ K) + (hTB : CountingTreeBound (MOf p) K (BOf p) L_coeff hM hK) + {g : P × CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (MOf p)) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → g (p, ψ) = ψ.modelCount) + (φ : CNF ℕ) : + searchDeclaredWith NOf MOf BOf K k g p φ = + if validDeclaredInput k (NOf p) φ then φ.modelCountOn (Finset.range (NOf p)) else 0 := by + rw [searchDeclaredWith_eq_ite_mul] + split_ifs with hvalid + · obtain ⟨hw, hsub⟩ := validDeclaredInput_eq_true_iff.mp hvalid + have hsearch : search (MOf p) K k (BOf p) (fun ψ => g (p, ψ)) φ = φ.modelCount := by + rw [search_eq_ite_modelCount_of_subset hM hK hM2 hkK hTB + (fun ψ hψw hsp hψsub => hg ψ hψw hsp (hψsub.trans hsub)), + ite_eq_left_iff.mpr fun h => + absurd (List.all_eq_true.mpr fun c hc => decide_eq_true (hw c hc)) h] + rw [hsearch, CNF.modelCountOn_eq_mul_modelCount φ (Finset.range (NOf p)) hsub, + Finset.card_range] + · rfl + +/-- **Correctness of `searchDeclaredWith` on valid inputs under `CountingTreeBound`.** -/ +theorem searchDeclaredWith_eq_modelCountOn {NOf MOf BOf : P → ℕ} {K k L_coeff : ℕ} + {p : P} (hM : 1 ≤ MOf p) (hK : 1 ≤ K) (hM2 : 2 ≤ MOf p) (hkK : k ≤ K) + (hTB : CountingTreeBound (MOf p) K (BOf p) L_coeff hM hK) + {g : P × CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (MOf p)) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → g (p, ψ) = ψ.modelCount) + {φ : CNF ℕ} (hw : ∀ c ∈ φ, c.length ≤ k) (hsub : φ.vars.toFinset ⊆ Finset.range (NOf p)) : + searchDeclaredWith NOf MOf BOf K k g p φ = φ.modelCountOn (Finset.range (NOf p)) := by + rw [searchDeclaredWith_eq_ite_modelCountOn hM hK hM2 hkK hTB hg φ, + ite_eq_left_iff.mpr fun h => absurd (validDeclaredInput_eq_true_iff.mpr ⟨hw, hsub⟩) h] + +/-- **Correctness of `searchDeclaredWith` composed with `normalizeDeclaredLeaf`.** +When a leaf provider `h : P × CNF ℕ → ℕ` returns the declared-range count +`ψ.modelCountOn (Finset.range (NOf p))` on sparse width-`≤ k` leaves with +`ψ.vars.toFinset ⊆ Finset.range (NOf p)`, normalizing `h` via `normalizeDeclaredLeaf NOf h` makes +`searchDeclaredWith` compute `φ.modelCountOn (Finset.range (NOf p))` on valid inputs and `0` +otherwise. -/ +theorem searchDeclaredWith_normalizeDeclaredLeaf_eq_ite_modelCountOn + {NOf MOf BOf : P → ℕ} {K k L_coeff : ℕ} + {p : P} (hM : 1 ≤ MOf p) (hK : 1 ≤ K) (hM2 : 2 ≤ MOf p) (hkK : k ≤ K) + (hTB : CountingTreeBound (MOf p) K (BOf p) L_coeff hM hK) + {h : P × CNF ℕ → ℕ} + (hh : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (MOf p)) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → + h (p, ψ) = ψ.modelCountOn (Finset.range (NOf p))) + (φ : CNF ℕ) : + searchDeclaredWith NOf MOf BOf K k (normalizeDeclaredLeaf NOf h) p φ = + if validDeclaredInput k (NOf p) φ then φ.modelCountOn (Finset.range (NOf p)) else 0 := + searchDeclaredWith_eq_ite_modelCountOn hM hK hM2 hkK hTB + (fun ψ hw hsp hsub => normalizeDeclaredLeaf_eq_modelCount hsub (hh ψ hw hsp hsub)) φ + +/-- **Correctness of `searchDeclaredWith` composed with `normalizeDeclaredLeaf` on valid +inputs.** -/ +theorem searchDeclaredWith_normalizeDeclaredLeaf_eq_modelCountOn + {NOf MOf BOf : P → ℕ} {K k L_coeff : ℕ} + {p : P} (hM : 1 ≤ MOf p) (hK : 1 ≤ K) (hM2 : 2 ≤ MOf p) (hkK : k ≤ K) + (hTB : CountingTreeBound (MOf p) K (BOf p) L_coeff hM hK) + {h : P × CNF ℕ → ℕ} + (hh : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (MOf p)) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → + h (p, ψ) = ψ.modelCountOn (Finset.range (NOf p))) + {φ : CNF ℕ} (hw : ∀ c ∈ φ, c.length ≤ k) (hsub : φ.vars.toFinset ⊆ Finset.range (NOf p)) : + searchDeclaredWith NOf MOf BOf K k (normalizeDeclaredLeaf NOf h) p φ = + φ.modelCountOn (Finset.range (NOf p)) := + searchDeclaredWith_eq_modelCountOn hM hK hM2 hkK hTB + (fun ψ hψw hsp hψsub => normalizeDeclaredLeaf_eq_modelCount hψsub (hh ψ hψw hsp hψsub)) + hw hsub + +end ContextWrapper + end CountingSparsify end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean index b4417994e..3e2e80916 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -270,6 +270,135 @@ theorem exists_computableInO_search_threeOccurrence_thresholdBase (two_le_countingThresholdBase K B) hk2 hkK hB (fun hk2' => countingThresholdBase_spec hk2' hB) hf hf_eq hδ ht +include hK in +/-- **Unconditional polynomial-workspace counting sparsification at +`countingThresholdBase K B`.** -/ +theorem exists_computableInO_search_polySpace_countingThresholdBase + {k B : ℕ} (hkK : k ≤ K) (hB : 1 ≤ B) + {g : CNF ℕ → ℕ} {t s : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t s) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K B)) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ C ∈ ψ, C.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K B)) K ψ = true → + (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) + (hs : ∀ ψ : CNF ℕ, (∀ C ∈ ψ, C.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K B)) K ψ = true → + s ψ ≤ a * ((encCNF ψ).length + 1) ^ b) : + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search (countingThresholdBase K B) K k B g) encCNF encNat T S ∧ + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) S ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K (countingThresholdBase K B) * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_polySpace _ hK (two_le_countingThresholdBase K B) hkK + (countingTreeBound_countingThresholdBase hK hB) hg hg_le hδ ht hs + +include hK in +/-- **Unconditional polynomial-workspace counting sparsification from a dense 3-occurrence counter +at `countingThresholdBase K B`.** -/ +theorem exists_computableInO_search_threeOccurrence_polySpace_thresholdBase + {k B : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ B) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) + (hs : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → sf χ ≤ a * ((encCNF χ).length + 1) ^ b) : + (∀ φ : CNF ℕ, search (countingThresholdBase K B) K k B (guardOccurrenceCounter k f) φ = + if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0) ∧ + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search (countingThresholdBase K B) K k B (guardOccurrenceCounter k f)) + encCNF encNat T S ∧ + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) S ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ + ((1 / B + occBound K (countingThresholdBase K B) * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := + exists_computableInO_search_threeOccurrence_polySpace_of_promise + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K B)) hK + (two_le_countingThresholdBase K B) hk2 hkK + (countingTreeBound_countingThresholdBase hK hB) hf hf_eq hδ ht hs + +include hK in +/-- **Unconditional declared-variable correctness of `searchDeclaredWith` at +`countingThresholdBase K (BOf p)`.** -/ +theorem searchDeclaredWith_eq_ite_modelCountOn_countingThresholdBase {P : Type*} + {NOf BOf : P → ℕ} {k : ℕ} (hkK : k ≤ K) {p : P} (hB : 1 ≤ BOf p) + {g : P × CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K (BOf p))) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → g (p, ψ) = ψ.modelCount) + (φ : CNF ℕ) : + searchDeclaredWith NOf (fun q => countingThresholdBase K (BOf q)) BOf K k g p φ = + if validDeclaredInput k (NOf p) φ then φ.modelCountOn (Finset.range (NOf p)) else 0 := + searchDeclaredWith_eq_ite_modelCountOn + (NOf := NOf) (MOf := fun q => countingThresholdBase K (BOf q)) (BOf := BOf) + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K (BOf p))) hK + (two_le_countingThresholdBase K (BOf p)) hkK + (countingTreeBound_countingThresholdBase hK hB) hg φ + +include hK in +/-- **Unconditional declared-variable correctness of `searchDeclaredWith` on valid inputs at +`countingThresholdBase K (BOf p)`.** -/ +theorem searchDeclaredWith_eq_modelCountOn_countingThresholdBase {P : Type*} + {NOf BOf : P → ℕ} {k : ℕ} (hkK : k ≤ K) {p : P} (hB : 1 ≤ BOf p) + {g : P × CNF ℕ → ℕ} + (hg : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K (BOf p))) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → g (p, ψ) = ψ.modelCount) + {φ : CNF ℕ} (hw : ∀ c ∈ φ, c.length ≤ k) (hsub : φ.vars.toFinset ⊆ Finset.range (NOf p)) : + searchDeclaredWith NOf (fun q => countingThresholdBase K (BOf q)) BOf K k g p φ = + φ.modelCountOn (Finset.range (NOf p)) := + searchDeclaredWith_eq_modelCountOn + (NOf := NOf) (MOf := fun q => countingThresholdBase K (BOf q)) (BOf := BOf) + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K (BOf p))) hK + (two_le_countingThresholdBase K (BOf p)) hkK + (countingTreeBound_countingThresholdBase hK hB) hg hw hsub + +include hK in +/-- **Unconditional declared-variable correctness of `searchDeclaredWith` composed with +`normalizeDeclaredLeaf` at `countingThresholdBase K (BOf p)`.** -/ +theorem searchDeclaredWith_normalizeDeclaredLeaf_eq_ite_modelCountOn_countingThresholdBase + {P : Type*} {NOf BOf : P → ℕ} {k : ℕ} (hkK : k ≤ K) {p : P} (hB : 1 ≤ BOf p) + {h : P × CNF ℕ → ℕ} + (hh : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K (BOf p))) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → + h (p, ψ) = ψ.modelCountOn (Finset.range (NOf p))) + (φ : CNF ℕ) : + searchDeclaredWith NOf (fun q => countingThresholdBase K (BOf q)) BOf K k + (normalizeDeclaredLeaf NOf h) p φ = + if validDeclaredInput k (NOf p) φ then φ.modelCountOn (Finset.range (NOf p)) else 0 := + searchDeclaredWith_normalizeDeclaredLeaf_eq_ite_modelCountOn + (NOf := NOf) (MOf := fun q => countingThresholdBase K (BOf q)) (BOf := BOf) + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K (BOf p))) hK + (two_le_countingThresholdBase K (BOf p)) hkK + (countingTreeBound_countingThresholdBase hK hB) hh φ + +include hK in +/-- **Unconditional declared-variable correctness of `searchDeclaredWith` composed with +`normalizeDeclaredLeaf` on valid inputs at `countingThresholdBase K (BOf p)`.** -/ +theorem searchDeclaredWith_normalizeDeclaredLeaf_eq_modelCountOn_countingThresholdBase + {P : Type*} {NOf BOf : P → ℕ} {k : ℕ} (hkK : k ≤ K) {p : P} (hB : 1 ≤ BOf p) + {h : P × CNF ℕ → ℕ} + (hh : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold (countingThresholdBase K (BOf p))) K ψ = true → + ψ.vars.toFinset ⊆ Finset.range (NOf p) → + h (p, ψ) = ψ.modelCountOn (Finset.range (NOf p))) + {φ : CNF ℕ} (hw : ∀ c ∈ φ, c.length ≤ k) (hsub : φ.vars.toFinset ⊆ Finset.range (NOf p)) : + searchDeclaredWith NOf (fun q => countingThresholdBase K (BOf q)) BOf K k + (normalizeDeclaredLeaf NOf h) p φ = + φ.modelCountOn (Finset.range (NOf p)) := + searchDeclaredWith_normalizeDeclaredLeaf_eq_modelCountOn + (NOf := NOf) (MOf := fun q => countingThresholdBase K (BOf q)) (BOf := BOf) + ((by decide : 1 ≤ 2).trans (two_le_countingThresholdBase K (BOf p))) hK + (two_le_countingThresholdBase K (BOf p)) hkK + (countingTreeBound_countingThresholdBase hK hB) hh hw hsub + end CountingSparsify end Cslib.Complexity diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean index 27b9383c5..c7ebbd2ae 100644 --- a/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingTreeBound.lean @@ -87,4 +87,68 @@ example {K k B : ℕ} (hK : 1 ≤ K) (hk2 : 2 ≤ k) (hkK : k ≤ K) (hB : 1 ≤ exists_computableInO_search_threeOccurrence_thresholdBase hK hk2 hkK hB hf hf_eq hδ ht +/-! ### Acceptance controls for `searchDeclaredWith` and `normalizeDeclaredLeaf` -/ + +/-- Exact occurring-variable leaf counter for concrete test evaluation. -/ +def occurringLeafCounter : ℕ × CNF ℕ → ℕ := + fun x => x.2.modelCount + +/-- Exact declared-range (`Finset.range N`) leaf counter for concrete test evaluation. -/ +def declaredLeafCounter : ℕ × CNF ℕ → ℕ := + fun x => x.2.modelCountOn (Finset.range x.1) + +/-- `N = 0` with the empty formula `[]` returns `1`. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 0 [] = 1 := by + decide + +/-- `N = 3` with the empty formula `[]` returns `2 ^ 3 = 8`. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 3 [] = 8 := by + decide + +/-- The unit formula `[[(0, true)]]` gives `1` at `N = 1` and `4` at `N = 3`. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 1 [[(0, true)]] = 1 ∧ + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 3 [[(0, true)]] = 4 := by + decide + +/-- Distinguishing occurring-variable leaf counts from declared-`N` leaf counts on `[[(0, true)]]` +at `N = 3`: feeding the unnormalized declared-`N` count `4` directly into `searchDeclaredWith` +hits the internal cap `2 ^ 1 = 2` and scales by `2 ^ (3 - 1) = 4` to yield `8`, whereas +normalizing via `normalizeDeclaredLeaf` yields `1` internally and the exact declared count `4`. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + declaredLeafCounter 3 [[(0, true)]] = 8 ∧ + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 3 [[(0, true)]] = 4 ∧ + CNF.modelCountOn [[(0, true)]] (Finset.range 3) = 4 := by + decide + +/-- Empty clauses return `0` even when `N = 3`. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 3 [[]] = 0 := by + decide + +/-- A tautological clause `[[(0, true), (0, false)]]` disappearing during normalization retains its +declared-variable contribution at `N = 3` and returns `8`. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + (normalizeDeclaredLeaf id declaredLeafCounter) 3 [[(0, true), (0, false)]] = 8 := by + decide + +/-- A variable label outside `Finset.range 3` (`[[(3, true)]]`) and an excessive-width clause +(`k = 2` on width `3`) take the guard's zero branch. -/ +example : + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + occurringLeafCounter 3 [[(3, true)]] = 0 ∧ + searchDeclaredWith id (fun _ => 2) (fun _ => 1) 2 2 + occurringLeafCounter 3 [[(0, true), (1, true), (2, true)]] = 0 := by + decide + end Cslib.Complexity.CountingTreeBoundTests From 34db3c45ecbede780ad3c5c54c4573f3b44d6591 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Tue, 27 Oct 2026 00:44:13 -0400 Subject: [PATCH 24/24] feat(counting): preserve independent sparsification workspace --- .../FineGrained/CountingSparsification.lean | 292 +++++++++++++----- .../FineGrained/ThreeOccurrenceReduction.lean | 145 +++++++-- .../FineGrained/CountingWorkspace.lean | 119 +++++++ 3 files changed, 462 insertions(+), 94 deletions(-) create mode 100644 ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean diff --git a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean index bc712e30a..ffb89e22f 100644 --- a/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean +++ b/Complexity/Computability/Complexity/FineGrained/CountingSparsification.lean @@ -50,10 +50,9 @@ Given a formula `φ : CNF ℕ` with `n = φ.numVars` occurring variables, the se To mirror `Sparsify`, this module packages the quantitative tree depth (`w.length ≤ L_coeff * N`) and fixed-parameter leaf-count (`W.card ≤ 2 ^ (N / B + 1)`) hypotheses into `CountingTreeBound`, a -library adaptation of Appendix A.2–A.3 (Lemmas A.2–A.3, pp. 46–47) that remains an open -quantitative obligation for `K ≥ 2` (unit insertion accounting and finite recursive leaf partition -are prerequisites, with higher-width occurrence, deletion, survival, and leaf-recurrence bounds -handled separately). Under `CountingTreeBound`, running `step M K g` for +library adaptation of Appendix A.2–A.3 (Lemmas A.2–A.3, pp. 46–47). The module +`Complexity.Computability.Complexity.FineGrained.CountingTreeBound` supplies this hypothesis from +an explicit integer parameter budget. Under `CountingTreeBound`, running `step M K g` for `rounds B φ = 2 ^ (n / B + 2)` steps visits every leaf of the counting-sparsification tree and returns `φ.modelCount` (`CountingSparsify.search_eq_modelCount`). Given a total leaf routine `g` with `hg : ComputableInO g encCNF encNat t t` and a global bound `ht` holding for every @@ -61,8 +60,13 @@ with `hg : ComputableInO g encCNF encNat t t` and a global bound `ht` holding fo `CountingSparsify.exists_computableInO_search` yields a combined time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the fixed-parameter library-adaptation transfer bound `T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * n) * (|encCNF φ| + 1) ^ E) + C₀`. -Because `hg` bounds both time and space of `g` by `t` (which may be exponential) and the step space -bound includes `leafTime`, no separate polynomial leaf-workspace certificate is claimed here. +The independent-space extension `CountingSparsify.exists_computableInO_search_polySpace` instead +starts from one total certificate `ComputableInO g encCNF encNat t s`, with separate quantitative +promises for sparse width-`≤ k` leaf inputs. It preserves this exponential time envelope and gives +polynomial workspace in the full `encCNF` length. Initialization, every traversal prefix including +the terminal state, the binary clock, retained declared variables, and the final output are charged. +All parameters `M`, `K`, `k`, `B`, and `L_coeff` are fixed before the machine is chosen; runtime +parameter uniformity and the native subexponential counting endpoint remain separate obligations. ## Main definitions @@ -83,6 +87,8 @@ bound includes `leafTime`, no separate polynomial leaf-workspace certificate is on formulas of width `≤ k` (and `0` otherwise). * `Cslib.Complexity.CountingSparsify.exists_computableInO_search`: native `ComputableInO ... T T` time-and-space transfer certificate for `CountingSparsify.search`. +* `Cslib.Complexity.CountingSparsify.exists_computableInO_search_polySpace`: the same time envelope + with independent polynomial workspace, from separately promised leaf time and workspace. ## References @@ -521,11 +527,10 @@ private theorem computableTidilyInPolyTime_rounds (B : ℕ) : unfold rounds fun_prop -/-- **The time of a counting-sparsification step**: a polynomial in the length of the encoded state, -plus the time of the leaf counter `g` and a polynomial in the state and counter output lengths when -`g` runs on a sparse head formula. -/ -private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} - (hg : ComputableTidilyInO g encCNF encNat t t) : +/-- Separate step time and workspace: a polynomial in the encoded state and counter-output +lengths, plus the corresponding resource of `g` when it runs on a sparse head formula. -/ +private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ} {t u : CNF ℕ → ℕ} + (hg : ComputableTidilyInO g encCNF encNat t u) : ∃ d, ComputableTidilyInO (step M K g) encState encState (fun s => ((encState s).length + 1) ^ d + if s.2.1.isEmpty = false ∧ @@ -536,7 +541,7 @@ private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ (fun s => ((encState s).length + 1) ^ d + if s.2.1.isEmpty = false ∧ isSparse (countingThreshold M) K (s.2.1.headD []) = true then - t (s.2.1.headD []) + + u (s.2.1.headD []) + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d else 0) := by obtain ⟨dE, eE, hE⟩ := (show ComputableTidilyInPolyTime (fun s : State => s.2.1.isEmpty) encState @@ -570,6 +575,7 @@ private theorem exists_computableTidilyInO_step (M K : ℕ) {g : CNF ℕ → ℕ generalize 4 ^ D = F at hp h4D ⊢ generalize ((encState s).length + 1) ^ D = P generalize t (s.2.1.headD []) = T + generalize u (s.2.1.headD []) = U generalize ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ D = Q at hp ⊢ generalize ((encPair encState encNat (s, g (s.2.1.headD []))).length + 1) ^ D = R at hp ⊢ cases s.2.1.isEmpty <;> @@ -633,7 +639,7 @@ private theorem length_encState_le {stk : List {ψ : CNF ℕ // CountingNormaliz end Sizes -/-! ### The time of the counting-sparsification search -/ +/-! ### Independent time and workspace of the counting-sparsification search -/ /-- The time bound of the leaf counter on the sparse leaves of the tree. -/ private noncomputable def leafTime (K M L_coeff : ℕ) (δ : ℝ) (c e : ℕ) (φ : CNF ℕ) : ℕ := @@ -641,38 +647,46 @@ private noncomputable def leafTime (K M L_coeff : ℕ) (δ : ℝ) (c e : ℕ) ( ((nodeBound K L_coeff φ : ℝ) + 1) ^ e) + c⌉₊ include hM hK in -/-- **The time and space of the rounds.** Under `CountingTreeBound`, given a total leaf routine `g` -with `hg : ComputableTidilyInO g encCNF encNat t t` and global bound `ht` holding for every -`ψ : CNF ℕ`, `search M K k B g` runs within time equal to initialization plus `rounds B φ + 1` -times the per-round bound (a polynomial in `stateBound`, plus `leafTime`, plus the binary -round-counter step cost), and within space equal to initialization/round-count space plus the -per-round bound (including `leafTime`, since `hg` bounds both time and space of `g` by `t`). -/ -private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) +/-- The same prefix invariant bounds step time and workspace separately, including the terminal +empty stack, capped accumulator, retained declared-variable count, and binary loop clock. -/ +private theorem exists_computableTidilyInO_search_resources (hM2 : 2 ≤ M) + {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) - {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableTidilyInO g encCNF encNat t t) + {g : CNF ℕ → ℕ} {t u R Q : CNF ℕ → ℕ} + (hg : ComputableTidilyInO g encCNF encNat t u) (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) - {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} - (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + (ht : ∀ φ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + ψ.length ≤ occBound K M * φ.numVars + 1 → + (encCNF ψ).length ≤ nodeBound K L_coeff φ → t ψ ≤ R φ) + (hu : ∀ φ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + ψ.length ≤ occBound K M * φ.numVars + 1 → + (encCNF ψ).length ≤ nodeBound K L_coeff φ → u ψ ≤ Q φ) : ∃ d dn di, ComputableTidilyInO (search M K k B g) encCNF encNat (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + - leafTime K M L_coeff δ c e φ + + R φ + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2)) (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + - (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ) + + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + Q φ) + stateBound K L_coeff φ + (encNat (rounds B φ)).length) := by obtain ⟨d, hstep⟩ := exists_computableTidilyInO_step M K hg obtain ⟨dn, en, hn⟩ := (computableTidilyInPolyTime_rounds B).exists_degree obtain ⟨di, ei, hi⟩ := (computableTidilyInPolyTime_init k).exists_degree - have hround : ∀ φ, ∀ i ≤ rounds B φ, + have hround (v V : CNF ℕ → ℕ) + (hv : ∀ φ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + ψ.length ≤ occBound K M * φ.numVars + 1 → + (encCNF ψ).length ≤ nodeBound K L_coeff φ → v ψ ≤ V φ) : ∀ φ, ∀ i ≤ rounds B φ, (fun s : State => ((encState s).length + 1) ^ d + if s.2.1.isEmpty = false ∧ isSparse (countingThreshold M) K (s.2.1.headD []) = true then - t (s.2.1.headD []) + + v (s.2.1.headD []) + ((encState s).length + (encNat (g (s.2.1.headD []))).length + 1) ^ d else 0) ((step M K g)^[i] (init k φ)) ≤ - 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ := by + 2 * (2 * stateBound K L_coeff φ + 1) ^ d + V φ := by intro φ i _ obtain ⟨stk, acc, heq, hstk, hacc, hmem⟩ := (iterate_step hM hK hM2 hkK hTB g φ).2 i rw [heq] @@ -704,17 +718,7 @@ private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ((encState (φ.distinctVars.length, (ψ :: rest.map Subtype.val, acc))).length + (encNat (g ψ)).length + 1) ^ d ≤ (2 * stateBound K L_coeff φ + 1) ^ d := Nat.pow_le_pow_left (by omega) d - have h₁ : (2 : ℝ) ^ (δ * ψ.length) ≤ - 2 ^ (δ * ((occBound K M * φ.numVars + 1 : ℕ) : ℝ)) := - Real.rpow_le_rpow_of_exponent_le (by norm_num) - (mul_le_mul_of_nonneg_left (by exact_mod_cast hlen) hδ) - have h₂ : (((encCNF ψ).length : ℝ) + 1) ^ e ≤ ((nodeBound K L_coeff φ : ℝ) + 1) ^ e := - pow_le_pow_left₀ (by positivity) (by exact_mod_cast Nat.add_le_add_right henc 1) e - have h₃ := mul_le_mul h₁ h₂ (by positivity) (by positivity) - have h₄ := mul_le_mul_of_nonneg_left h₃ (Nat.cast_nonneg c : (0 : ℝ) ≤ c) - have h₅ := (ht ψ).trans (add_le_add h₄ le_rfl) - have ht_le : t ψ ≤ leafTime K M L_coeff δ c e φ := - Nat.cast_le.mp (h₅.trans (Nat.le_ceil _)) + have hv_le := hv φ ψ hwidth hc.2 hlen henc omega · omega have hstate : ∀ φ, ∀ i ≤ rounds B φ, @@ -724,32 +728,60 @@ private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : rw [heq] exact length_encState_le hM hK hkK hstk hacc hmem exact ⟨d, dn, di, - (ComputableTidilyInO.iterate hn.inO hi.inO hstep hround hround hstate).snd'.snd'⟩ + (ComputableTidilyInO.iterate hn.inO hi.inO hstep + (hround t R ht) (hround u Q hu) hstate).snd'.snd'⟩ + + +private theorem le_leafTime {K M L_coeff : ℕ} {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + {t : CNF ℕ → ℕ} {φ ψ : CNF ℕ} + (ht : (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) + (hlen : ψ.length ≤ occBound K M * φ.numVars + 1) + (henc : (encCNF ψ).length ≤ nodeBound K L_coeff φ) : + t ψ ≤ leafTime K M L_coeff δ c e φ := by + have h₁ : (2 : ℝ) ^ (δ * ψ.length) ≤ + 2 ^ (δ * ((occBound K M * φ.numVars + 1 : ℕ) : ℝ)) := + Real.rpow_le_rpow_of_exponent_le (by norm_num) + (mul_le_mul_of_nonneg_left (by exact_mod_cast hlen) hδ) + have h₂ : (((encCNF ψ).length : ℝ) + 1) ^ e ≤ ((nodeBound K L_coeff φ : ℝ) + 1) ^ e := + pow_le_pow_left₀ (by positivity) (by exact_mod_cast Nat.add_le_add_right henc 1) e + have h₃ := mul_le_mul h₁ h₂ (by positivity) (by positivity) + have h₄ := mul_le_mul_of_nonneg_left h₃ (Nat.cast_nonneg c : (0 : ℝ) ≤ c) + exact Nat.cast_le.mp ((ht.trans (add_le_add h₄ le_rfl)).trans (Nat.le_ceil _)) include hM hK in -/-- **The time and space of the counting-sparsification search.** Given a total leaf routine `g` -with `hg : ComputableInO g encCNF encNat t t` whose time and space `t ψ` satisfy the global bound -`ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : CNF ℕ`, and whose output is at -most `2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` has a combined -time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the -fixed-parameter transfer bound -`T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀` (a library -adaptation of the leaf-to-root runtime assembly, without an independent polynomial-space -certificate). -/ -theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) +/-- **The time and space of the rounds.** Under `CountingTreeBound`, given a total leaf routine `g` +with `hg : ComputableTidilyInO g encCNF encNat t t` and global bound `ht` holding for every +`ψ : CNF ℕ`, `search M K k B g` runs within time equal to initialization plus `rounds B φ + 1` +times the per-round bound (a polynomial in `stateBound`, plus `leafTime`, plus the binary +round-counter step cost), and within space equal to initialization/round-count space plus the +per-round bound (including `leafTime`, since `hg` bounds both time and space of `g` by `t`). -/ +private theorem exists_computableTidilyInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) (hTB : CountingTreeBound M K B L_coeff hM hK) - {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableTidilyInO g encCNF encNat t t) (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : - ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, - ComputableInO (search M K k B g) encCNF encNat T T ∧ - ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + ∃ d dn di, ComputableTidilyInO (search M K k B g) encCNF encNat + (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2)) + (fun φ => ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ) + + stateBound K L_coeff φ + (encNat (rounds B φ)).length) := by + apply exists_computableTidilyInO_search_resources hM hK hM2 hkK hTB hg hg_le <;> + intro φ ψ _ _ hlen henc <;> exact le_leafTime hδ (ht ψ) hlen henc + +private theorem search_time_bound (K M B L_coeff d dn di : ℕ) + {δ : ℝ} (hδ : 0 ≤ δ) (c e : ℕ) : + ∃ C₀ E : ℕ, ∀ φ : CNF ℕ, + (((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + stateBound K L_coeff φ + + ((encNat (rounds B φ)).length + 1) ^ 2) : ℕ) ≤ C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by - obtain ⟨d, dn, di, h⟩ := - exists_computableTidilyInO_search hM hK hM2 hkK hTB hg.tidy hg_le hδ ht set Kx := K * L_coeff set c₁ := (2 + Kx) * (6 * K + 1) set c₂ := 4 + 4 * (L_coeff + 1) * (2 * c₁ + 1) + 10 @@ -762,23 +794,7 @@ theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) - have h_mono : ComputableTidilyInO (search M K k B g) encCNF encNat T T := - h.mono_le (fun _ => le_rfl) fun φ => by - dsimp only [T] - have h1 : 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + - stateBound K L_coeff φ + (encNat (rounds B φ)).length ≤ - 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + - stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2 := by - have : (encNat (rounds B φ)).length ≤ ((encNat (rounds B φ)).length + 1) ^ 2 := - (Nat.le_succ _).trans (Nat.le_self_pow two_ne_zero _) - omega - have h2 := Nat.le_mul_of_pos_left - (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + - stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) - (Nat.succ_pos (rounds B φ)) - simp only [Nat.succ_eq_add_one] at h2 - omega - refine ⟨T, ⌈C₀⌉₊, E, .of_tidy h_mono, fun φ => ?_⟩ + refine ⟨⌈C₀⌉₊, E, fun φ => ?_⟩ set L := (encCNF φ).length set N := φ.numVars set P := (L + 1) ^ E @@ -935,6 +951,136 @@ theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k exact mul_le_mul_of_nonneg_right (Nat.le_ceil C₀) (by positivity) linarith [h_prod, (Nat.cast_nonneg ⌈C₀⌉₊ : (0 : ℝ) ≤ ⌈C₀⌉₊)] +include hM hK in +/-- **The time and space of the counting-sparsification search.** Given a total leaf routine `g` +with `hg : ComputableInO g encCNF encNat t t` whose time and space `t ψ` satisfy the global bound +`ht` (`O(2 ^ (δ * ψ.length) * (|encCNF ψ| + 1) ^ e)`) for every `ψ : CNF ℕ`, and whose output is at +most `2 ^ ψ.numVars` on sparse width-`≤ k` formulas, `search M K k B g` has a combined +time-and-space certificate `ComputableInO (search M K k B g) encCNF encNat T T` with the +fixed-parameter transfer bound +`T φ ≤ C₀ * (2 ^ ((1 / B + occBound K M * δ) * φ.numVars) * (|encCNF φ| + 1) ^ E) + C₀` (a library +adaptation of the leaf-to-root runtime assembly, without an independent polynomial-space +certificate). -/ +theorem exists_computableInO_search (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {g : CNF ℕ → ℕ} {t : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t t) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B g) encCNF encNat T T ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, dn, di, h⟩ := + exists_computableTidilyInO_search hM hK hM2 hkK hTB hg.tidy hg_le hδ ht + obtain ⟨C₀, E, hT⟩ := search_time_bound K M B L_coeff d dn di hδ c e + set T : CNF ℕ → ℕ := fun φ => + ((encCNF φ).length + 1) ^ dn + ((encCNF φ).length + 1) ^ di + + (rounds B φ + 1) * (2 * (2 * stateBound K L_coeff φ + 1) ^ d + + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) + have h_mono : ComputableTidilyInO (search M K k B g) encCNF encNat T T := + h.mono_le (fun _ => le_rfl) fun φ => by + dsimp only [T] + have h1 : 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + (encNat (rounds B φ)).length ≤ + 2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2 := by + have : (encNat (rounds B φ)).length ≤ ((encNat (rounds B φ)).length + 1) ^ 2 := + (Nat.le_succ _).trans (Nat.le_self_pow two_ne_zero _) + omega + have h2 := Nat.le_mul_of_pos_left + (2 * (2 * stateBound K L_coeff φ + 1) ^ d + leafTime K M L_coeff δ c e φ + + stateBound K L_coeff φ + ((encNat (rounds B φ)).length + 1) ^ 2) + (Nat.succ_pos (rounds B φ)) + simp only [Nat.succ_eq_add_one] at h2 + omega + exact ⟨T, C₀, E, .of_tidy h_mono, hT⟩ + +private theorem polyBounded_nodeBound (K L_coeff : ℕ) : + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) (nodeBound K L_coeff) := by + have hn : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) CNF.numVars := + PolyBounded.self.mono fun φ => φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hm : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) List.length := + PolyBounded.self.mono fun φ => length_le_length_encList _ φ + have hclauses := (hm.add (PolyBounded.const 1)).add + ((PolyBounded.const K).mul ((PolyBounded.const L_coeff).mul hn)) + have hliteral := ((PolyBounded.const 2).mul + ((PolyBounded.const K).mul (((PolyBounded.const 2).mul + (PolyBounded.self (size := fun φ : CNF ℕ => (encCNF φ).length))).add (.const 1)))).add + (PolyBounded.const 1) + exact hclauses.mul hliteral + +private theorem polyBounded_stateBound (K L_coeff : ℕ) : + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) (stateBound K L_coeff) := by + have hn : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) CNF.numVars := + PolyBounded.self.mono fun φ => φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hstack := (((PolyBounded.const L_coeff).mul hn).add (.const 1)).mul + (((PolyBounded.const 2).mul (polyBounded_nodeBound K L_coeff)).add (.const 1)) + exact (((PolyBounded.const 4).mul hn).add ((PolyBounded.const 4).mul hstack)).add (.const 10) + +private theorem polyBounded_pow {α : Type*} {size f : α → ℕ} + (hf : PolyBounded size f) (d : ℕ) : PolyBounded size (fun a => f a ^ d) := by + obtain ⟨c, e, h⟩ := hf.exists_le + refine .of_forall_le (c ^ d) (e * d) fun a => ?_ + calc f a ^ d ≤ (c * (size a + 1) ^ e) ^ d := Nat.pow_le_pow_left (h a) d + _ = c ^ d * (size a + 1) ^ (e * d) := by rw [mul_pow, ← pow_mul] + +include hM hK in +/-- **Independent polynomial workspace for counting sparsification.** A single total leaf machine +with separate time `t` and workspace `s` gives a single total machine for the existing `search`. +Only sparse width-`≤ k` inputs need satisfy the quantitative leaf promises `ht`, `hs`, and `hg_le`. +The workspace includes all traversal prefixes and the terminal state, the full encoded formulas, +initialization, binary loop clock, retained declared-variable count, and output accumulator. + +The parameters `M`, `K`, `k`, `B`, and `L_coeff`, and all leaf-bound constants are fixed before the +machine is chosen. Polynomial workspace is measured in the full `encCNF` length, independently +of the exponential number of rounds and leaf time. This resource transfer does not construct a +leaf counter or provide a uniform machine for runtime choices of these parameters. -/ +theorem exists_computableInO_search_polySpace (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {g : CNF ℕ → ℕ} {t s : CNF ℕ → ℕ} (hg : ComputableInO g encCNF encNat t s) + (hg_le : ∀ ψ : CNF ℕ, (∀ c ∈ ψ, c.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → g ψ ≤ 2 ^ ψ.numVars) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ C ∈ ψ, C.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → + (t ψ : ℝ) ≤ c * (2 ^ (δ * ψ.length) * ((encCNF ψ).length + 1) ^ e) + c) + (hs : ∀ ψ : CNF ℕ, (∀ C ∈ ψ, C.length ≤ k) → + isSparse (countingThreshold M) K ψ = true → s ψ ≤ a * ((encCNF ψ).length + 1) ^ b) : + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B g) encCNF encNat T S ∧ + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) S ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * δ) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, dn, di, h⟩ := exists_computableTidilyInO_search_resources hM hK hM2 hkK hTB + hg.tidy hg_le (R := leafTime K M L_coeff δ c e) + (Q := fun φ => a * (nodeBound K L_coeff φ + 1) ^ b) + (fun _ ψ hw hsp hlen henc => le_leafTime hδ (ht ψ hw hsp) hlen henc) + (fun _ ψ hw hsp _ henc => (hs ψ hw hsp).trans + (Nat.mul_le_mul_left a (Nat.pow_le_pow_left (Nat.add_le_add_right henc 1) b))) + obtain ⟨C₀, E, hT⟩ := search_time_bound K M B L_coeff d dn di hδ c e + refine ⟨_, _, C₀, E, .of_tidy h, ?_, hT⟩ + have hclock : PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) + (fun φ => (encNat (rounds B φ)).length) := by + refine (PolyBounded.self.add (PolyBounded.const 3)).mono fun φ => ?_ + rw [rounds, CNF.length_distinctVars, encNat_two_pow, List.length_append, + List.length_replicate, List.length_singleton] + have hn := φ.numVars_le_length_vars.trans (SAT.length_vars_le φ) + have hdiv := Nat.div_le_self φ.numVars B + omega + have hinput := (PolyBounded.self (size := fun φ : CNF ℕ => (encCNF φ).length)).add + (PolyBounded.const 1) + have hinit := (polyBounded_pow hinput dn).add (polyBounded_pow hinput di) + have hstep := (PolyBounded.const 2).mul (polyBounded_pow + (((PolyBounded.const 2).mul (polyBounded_stateBound K L_coeff)).add (.const 1)) d) + have hleaf := (PolyBounded.const a).mul + (polyBounded_pow ((polyBounded_nodeBound K L_coeff).add (.const 1)) b) + exact ((hinit.add (hstep.add hleaf)).add (polyBounded_stateBound K L_coeff)).add hclock + end CountingSparsify end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean index 7da861057..9ab8f1b1a 100644 --- a/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean +++ b/Complexity/Computability/Complexity/FineGrained/ThreeOccurrenceReduction.lean @@ -61,6 +61,13 @@ pp. 43–47). counter via `CountingSparsify.search` in time and space `O(2 ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * (|encCNF φ| + 1) ^ E')`. +* `Cslib.Complexity.exists_computableInO_guardOccurrenceCounter_polySpace_of_promise`, + `CountingSparsify.exists_computableInO_search_threeOccurrence_polySpace_of_promise`: + the same guard, compiler, and search with separate leaf time and polynomial workspace promises, + yielding a total machine with the same time envelope and independent polynomial workspace in the + full input encoding. Parameters are fixed; no leaf counter or runtime parameter uniformity is + supplied by these transfer results. + ## References * [C. H. Papadimitriou, *Computational Complexity*, Proposition 9.3][Papadimitriou94] @@ -333,17 +340,19 @@ theorem guardOccurrenceCounter_pos_iff_mem_kSAT {k : ℕ} (hk : 2 ≤ k) {f : CN simp only [hψ, Bool.false_eq_true, ite_false, lt_irrefl, KSAT, Set.mem_ofPred_eq, hψ', false_and] -private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) - {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) - {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} - (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → - (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ - c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * - ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : - ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, - ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ - ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ - C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by +private theorem exists_computableInO_guardOccurrenceCounter_resources (k : ℕ) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) : + ∃ d l : ℕ, (∀ ψ : CNF ℕ, (encCNF (toThreeOccurrenceDense ψ)).length ≤ + l * (((encCNF ψ).length + 1) ^ d + 1)) ∧ + ComputableInO (guardOccurrenceCounter k f) encCNF encNat + (fun ψ => ((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) + else ((encCNF ψ).length + 1) ^ d) + (fun ψ => ((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + sf (toThreeOccurrenceDense ψ) + else ((encCNF ψ).length + 1) ^ d) := by obtain ⟨dc, ec, hc⟩ := (computableInPolyTime_all_length_le k).tidy.exists_degree obtain ⟨dr, er, hr⟩ := computableInPolyTime_toThreeOccurrenceDense.tidy.exists_degree obtain ⟨dz, ez, hz⟩ := @@ -354,17 +363,25 @@ private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) have hrO := (hr.mono (d' := d) (e' := er) (by omega) le_rfl).inO have hzO := (hz.mono (d' := d) (e' := ez) (by omega) le_rfl).inO obtain ⟨l, hl⟩ := hrO.length_le - have hcomp := hf.tidy.comp hrO - have hite := hcO.ite hcomp hzO - set T : CNF ℕ → ℕ := fun ψ => - ((encCNF ψ).length + 1) ^ d + - if (ψ.all fun c => decide (c.length ≤ k)) then - ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) - else - ((encCNF ψ).length + 1) ^ d + exact ⟨d, l, hl, .of_tidy (hcO.ite (hf.tidy.comp hrO) hzO)⟩ + +private theorem guardOccurrenceCounter_time_bound (k d l : ℕ) {tf : CNF ℕ → ℕ} + (hl : ∀ ψ : CNF ℕ, (encCNF (toThreeOccurrenceDense ψ)).length ≤ + l * (((encCNF ψ).length + 1) ^ d + 1)) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → + (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ + c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * + ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : + ∃ C₀ E : ℕ, ∀ ψ : CNF ℕ, + ((((encCNF ψ).length + 1) ^ d + + if (ψ.all fun c => decide (c.length ≤ k)) then + ((encCNF ψ).length + 1) ^ d + tf (toThreeOccurrenceDense ψ) + else ((encCNF ψ).length + 1) ^ d : ℕ) : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by set E := d * e + d set K : ℝ := 2 + c * (2 * l + 1) ^ e + c - refine ⟨T, ⌈K⌉₊, E, .of_tidy hite, fun ψ => ?_⟩ + refine ⟨⌈K⌉₊, E, fun ψ => ?_⟩ set P : ℝ := (((encCNF ψ).length : ℝ) + 1) ^ d set Q : ℝ := (((encCNF ψ).length : ℝ) + 1) ^ E set X : ℝ := (2 : ℝ) ^ ((k * δ) * ψ.length) @@ -382,7 +399,6 @@ private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) have hPXQ : P ≤ X * Q := hPQ.trans hQXQ have hPeXQ : P ^ e ≤ X * Q := hPeQ.trans hQXQ have h1XQ : (1 : ℝ) ≤ X * Q := one_le_mul_of_one_le_of_one_le hX1 hQ1 - dsimp only [T] split_ifs with hψ · have hψ' : ∀ c ∈ ψ, c.length ≤ k := by simpa only [List.all_eq_true, decide_eq_true_eq] using hψ @@ -438,6 +454,21 @@ private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) push_cast [P] at hsum ⊢ linarith +private theorem exists_computableInO_guardOccurrenceCounter_aux (k : ℕ) + {f : CNF ℕ → ℕ} {tf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf tf) + {δ : ℝ} (hδ : 0 ≤ δ) {c e : ℕ} + (ht : ∀ ψ : CNF ℕ, (∀ d ∈ ψ, d.length ≤ k) → + (tf (toThreeOccurrenceDense ψ) : ℝ) ≤ + c * (2 ^ (δ * (toThreeOccurrenceDense ψ).numVars) * + ((encCNF (toThreeOccurrenceDense ψ)).length + 1) ^ e) + c) : + ∃ T : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T T ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, l, hl, h⟩ := exists_computableInO_guardOccurrenceCounter_resources k hf + obtain ⟨C₀, E, hT⟩ := guardOccurrenceCounter_time_bound k d l hl hδ ht + exact ⟨_, C₀, E, h, hT⟩ + /-- **From variable-exponential occurrence-3 counting to clause-exponential leaf counting (promised runtime).** If `2 ≤ k` and `f : CNF ℕ → ℕ` has a combined time-and-space certificate @@ -475,6 +506,42 @@ theorem exists_computableInO_guardOccurrenceCounter (k : ℕ) {f : CNF ℕ → C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := exists_computableInO_guardOccurrenceCounter_aux k hf hδ fun ψ _ => ht (toThreeOccurrenceDense ψ) +/-- The existing width guard and dense occurrence-3 compiler preserve independent polynomial +workspace. `hf` is one total machine; `ht` and `hs` need hold only on the dense width-`≤ k`, +occurrence-`≤ 3` promise. The result is total, including the zero output on rejected widths. +The compiler output, its retained input, and the leaf output are charged by the native composition +rules. All parameters and polynomial constants are fixed before choosing the resulting machine. -/ +theorem exists_computableInO_guardOccurrenceCounter_polySpace_of_promise {k : ℕ} (hk : 2 ≤ k) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ d ∈ χ, d.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) + (hs : ∀ χ : CNF ℕ, (∀ d ∈ χ, d.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → sf χ ≤ a * ((encCNF χ).length + 1) ^ b) : + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (guardOccurrenceCounter k f) encCNF encNat T S ∧ + PolyBounded (fun ψ : CNF ℕ => (encCNF ψ).length) S ∧ + ∀ ψ : CNF ℕ, (T ψ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((k * δ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ E) + C₀ := by + obtain ⟨d, l, hl, h⟩ := exists_computableInO_guardOccurrenceCounter_resources k hf + obtain ⟨C₀, E, hT⟩ := guardOccurrenceCounter_time_bound k d l hl hδ fun ψ hψ => + ht (toThreeOccurrenceDense ψ) (width_toThreeOccurrenceDense hk hψ) + (count_vars_toThreeOccurrenceDense_le ψ) (vars_toThreeOccurrenceDense_eq_range_numVars ψ) + refine ⟨_, _, C₀, E, h, ?_, hT⟩ + refine ⟨fun n => 2 * (n + 1) ^ d + a * (l * ((n + 1) ^ d + 1) + 1) ^ b, + by fun_prop, fun ψ => ?_⟩ + dsimp only + split_ifs with hψ + · have hwidth : ∀ c ∈ ψ, c.length ≤ k := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hψ + have hsψ := hs (toThreeOccurrenceDense ψ) (width_toThreeOccurrenceDense hk hwidth) + (count_vars_toThreeOccurrenceDense_le ψ) (vars_toThreeOccurrenceDense_eq_range_numVars ψ) + have hpow := Nat.mul_le_mul_left a + (Nat.pow_le_pow_left (Nat.add_le_add_right (hl ψ) 1) b) + omega + · omega + namespace CountingSparsify variable {M K : ℕ} (hM : 1 ≤ M) (hK : 1 ≤ K) @@ -539,6 +606,42 @@ theorem exists_computableInO_search_of_threeOccurrence_counter (hM2 : 2 ≤ M) { exists_computableInO_search_threeOccurrence_of_promise hM hK hM2 hk2 hkK hTB hf hf_eq hδ fun χ _ _ _ => ht χ +include hM hK in +/-- Counting sparsification with independent polynomial workspace from one total dense +occurrence-3 counter whose correctness, time, and space guarantees are promised only on dense +width-`≤ k`, occurrence-`≤ 3` formulas. The unchanged search returns the model count on width +`≤ k` inputs and zero otherwise, in the original exponential time envelope and polynomial space +in the full input encoding. `M`, `K`, `k`, `B`, and `L_coeff` are fixed parameters; this does not +supply a counter or establish uniformity for runtime parameter choices. -/ +theorem exists_computableInO_search_threeOccurrence_polySpace_of_promise + (hM2 : 2 ≤ M) {k B L_coeff : ℕ} (hk2 : 2 ≤ k) (hkK : k ≤ K) + (hTB : CountingTreeBound M K B L_coeff hM hK) + {f : CNF ℕ → ℕ} {tf sf : CNF ℕ → ℕ} (hf : ComputableInO f encCNF encNat tf sf) + (hf_eq : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → f χ = χ.modelCount) + {δ : ℝ} (hδ : 0 ≤ δ) {c e a b : ℕ} + (ht : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → + (tf χ : ℝ) ≤ c * (2 ^ (δ * χ.numVars) * ((encCNF χ).length + 1) ^ e) + c) + (hs : ∀ χ : CNF ℕ, (∀ c ∈ χ, c.length ≤ k) → (∀ v, χ.vars.count v ≤ 3) → + χ.vars.toFinset = Finset.range χ.numVars → sf χ ≤ a * ((encCNF χ).length + 1) ^ b) : + (∀ φ : CNF ℕ, search M K k B (guardOccurrenceCounter k f) φ = + if (φ.all fun c => decide (c.length ≤ k)) then φ.modelCount else 0) ∧ + ∃ T S : CNF ℕ → ℕ, ∃ C₀ E : ℕ, + ComputableInO (search M K k B (guardOccurrenceCounter k f)) encCNF encNat T S ∧ + PolyBounded (fun φ : CNF ℕ => (encCNF φ).length) S ∧ + ∀ φ : CNF ℕ, (T φ : ℝ) ≤ + C₀ * ((2 : ℝ) ^ ((1 / B + occBound K M * (k * δ)) * φ.numVars) * + (((encCNF φ).length : ℝ) + 1) ^ E) + C₀ := by + refine ⟨search_eq_ite_modelCount hM hK hM2 hkK hTB + (fun ψ hψ _ => guardOccurrenceCounter_eq_modelCount hk2 hf_eq hψ), ?_⟩ + obtain ⟨Tg, Sg, Cg, Eg, hgO, hgS, hgt⟩ := + exists_computableInO_guardOccurrenceCounter_polySpace_of_promise hk2 hf hδ ht hs + obtain ⟨a', b', hgs⟩ := hgS.exists_le + exact exists_computableInO_search_polySpace hM hK hM2 hkK hTB hgO + (fun ψ _ _ => guardOccurrenceCounter_le_two_pow_numVars hk2 hf_eq ψ) + (by positivity) (fun ψ _ _ => hgt ψ) (fun ψ _ _ => hgs ψ) + end CountingSparsify end Cslib.Complexity diff --git a/ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean b/ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean new file mode 100644 index 000000000..76763234e --- /dev/null +++ b/ComplexityTests/Computability/Complexity/FineGrained/CountingWorkspace.lean @@ -0,0 +1,119 @@ +/- +Copyright (c) 2026 OpenAI, Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: OpenAI, Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.FineGrained.CountingTreeBound +public import ComplexityTests.Computability.Complexity.FineGrained.CountingSparsification +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Basic +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Plain + +/-! +# Independent counting-sparsification workspace controls + +A concrete native polynomial-time leaf routine is given an exponential time allowance while its +workspace stays polynomial. The new transfer is instantiated with an unconditional tree bound. +The routine is only a diagnostic: it counts the particular leaves below, not arbitrary CNFs. +The branching trace checks the declared-variable correction and terminal stack separately from +the resource theorem, without adding a second search or counting algorithm. +-/ + +@[expose] public section + +namespace Cslib.Complexity.Tests.CountingWorkspace + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Sparsification Turing.MultiTapeTM +open CountingSparsify + +/-- Diagnostic leaf output: reject an empty clause, otherwise return one. -/ +def leafProbe (ψ : CNF ℕ) : ℕ := if ψ.any List.isEmpty then 0 else 1 + +private theorem probe_certificate (q : CNF ℕ → ℕ) : + ∃ d, ComputableInO leafProbe encCNF encNat + (fun ψ => 2 ^ q ψ * ((encCNF ψ).length + 1) ^ d) + (fun ψ => ((encCNF ψ).length + 1) ^ d) := by + have hp : ComputableTidilyInPolyTime leafProbe encCNF encNat := by + unfold leafProbe encCNF + fun_prop + obtain ⟨d, e, h⟩ := hp.exists_degree + exact ⟨d, .of_tidy (h.inO.mono_le + (fun ψ => Nat.le_mul_of_pos_left _ (Nat.two_pow_pos (q ψ))) (fun _ => le_rfl))⟩ + +/-- No tree or machine premise is assumed: the width-one tree bound and the concrete leaf +certificate provide a single native search certificate with polynomial workspace. The supplied +leaf time allowance is `2 ^ ψ.length * (|encCNF ψ| + 1) ^ d`. -/ +example : ∃ T S : CNF ℕ → ℕ, + ComputableInO (search 2 1 1 1 leafProbe) encCNF encNat T S ∧ + PolyBounded (fun ψ : CNF ℕ => (encCNF ψ).length) S := by + obtain ⟨d, h⟩ := probe_certificate List.length + have ht (ψ : CNF ℕ) : + ((2 ^ ψ.length * ((encCNF ψ).length + 1) ^ d : ℕ) : ℝ) ≤ + 1 * (2 ^ ((1 : ℝ) * ψ.length) * (((encCNF ψ).length : ℝ) + 1) ^ d) + 1 := by + simp only [one_mul, Real.rpow_natCast, Nat.cast_mul, Nat.cast_pow, Nat.cast_ofNat, + Nat.cast_add, Nat.cast_one] + linarith + obtain ⟨T, S, _, _, hTS, hS, _⟩ := exists_computableInO_search_polySpace + (by decide : 1 ≤ 2) (by decide : 1 ≤ 1) (by decide : 2 ≤ 2) (k := 1) le_rfl + (countingTreeBound_one (by decide) 1 1) h + (fun ψ _ _ => by unfold leafProbe; split_ifs <;> simp [Nat.one_le_two_pow]) + (by norm_num : (0 : ℝ) ≤ 1) (c := 1) (e := d) + (fun ψ _ _ => by simpa only [Nat.cast_one] using ht ψ) + (a := 1) (b := d) (fun _ _ _ => by simp) + exact ⟨T, S, hTS, hS⟩ + +/-- The same native diagnostic passes through the existing dense compiler and width guard with +an exponential allowance in the number of variables and a separate polynomial workspace. -/ +example : ∃ T S : CNF ℕ → ℕ, + ComputableInO (guardOccurrenceCounter 2 leafProbe) encCNF encNat T S ∧ + PolyBounded (fun ψ : CNF ℕ => (encCNF ψ).length) S := by + obtain ⟨d, h⟩ := probe_certificate CNF.numVars + have ht (ψ : CNF ℕ) : + ((2 ^ ψ.numVars * ((encCNF ψ).length + 1) ^ d : ℕ) : ℝ) ≤ + 1 * (2 ^ ((1 : ℝ) * ψ.numVars) * (((encCNF ψ).length : ℝ) + 1) ^ d) + 1 := by + simp only [one_mul, Real.rpow_natCast, Nat.cast_mul, Nat.cast_pow, Nat.cast_ofNat, + Nat.cast_add, Nat.cast_one] + linarith + obtain ⟨T, S, _, _, hTS, hS, _⟩ := + exists_computableInO_guardOccurrenceCounter_polySpace_of_promise (by decide : 2 ≤ 2) + h (by norm_num : (0 : ℝ) ≤ 1) (c := 1) (e := d) + (fun ψ _ _ _ => by simpa only [Nat.cast_one] using ht ψ) + (a := 1) (b := d) (fun _ _ _ _ => by simp) + exact ⟨T, S, hTS, hS⟩ + +/-- The width-one guard rejects the tautological two-literal clause. At width two, its variable +remains declared after normalization, so the remaining unit leaf contributes two. -/ +example : search 2 1 1 1 leafProbe [[(7, true)], [(99, true), (99, false)]] = 0 := by + decide + +example : search 2 2 2 1 leafProbe [[(7, true)], [(99, true), (99, false)]] = 2 := by + decide + +/-- The two leaf calls contribute `4 * 1` and `1 * 1`; the terminal empty stack retains count 5. -/ +example : (step 2 2 leafProbe)^[3] (init 2 flowerWithTautology) = (3, ([], 5)) := by + decide + +example : (step 2 2 leafProbe)^[4] (init 2 flowerWithTautology) = (3, ([], 5)) := by + decide + +example : search 2 2 2 2 leafProbe flowerWithTautology = 5 := by decide + +example : search 2 1 0 1 leafProbe ([] : CNF ℕ) = 1 := by decide +example : search 2 1 0 1 leafProbe ([[]] : CNF ℕ) = 0 := by decide +example : search 2 1 0 1 leafProbe [[(1000, true)]] = 0 := by decide + +/-- Equal variable cardinality does not mean equal encoded input size: label bits are charged. -/ +example : CNF.numVars [[(0, true)]] = CNF.numVars [[(1000, true)]] ∧ + (encCNF [[(0, true)]]).length < (encCNF [[(1000, true)]]).length := by + decide + +/-- A resource certificate alone does not assert correct model counting. -/ +example : leafProbe [[(0, true), (1, true)]] = 1 ∧ + CNF.modelCount [[(0, true), (1, true)]] = 3 := by decide + +end Cslib.Complexity.Tests.CountingWorkspace