From 59acdc0f6e83bcbd83716c6e3217e2cacdb41075 Mon Sep 17 00:00:00 2001 From: Anton Kovsharov Date: Tue, 27 Oct 2026 01:09:57 +0000 Subject: [PATCH 1/6] 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 a721be0d68..90839e29d1 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 0000000000..042654afdf --- /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 0000000000..20af9edff5 --- /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 2adad2a0dd3deb619e5a5bf2f6c07338cb9902fa Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 01:15:34 +0000 Subject: [PATCH 2/6] 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 a721be0d68..5a4099a757 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 0000000000..9eaf86f95f --- /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 fd4d1d6fae..606596edb3 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 0000000000..74c56c4c1e --- /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 0000000000..8f82fcc9a7 --- /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 0000000000..b4041e3106 --- /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 c48bccdcc8..cd7b9e3e24 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 74bdac9678..9e1a3cd24e 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 0000000000..9e8ca74af8 --- /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 3/6] 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 538186db1a..26257d1b84 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 042654afdf..9938a5c538 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 20af9edff5..f6738e7b7d 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 bff2d5cd4ab0995180b0bd855ba1049440e7c49d Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 02:32:41 +0000 Subject: [PATCH 4/6] 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 9eaf86f95f..9fa0c30738 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 74c56c4c1e..b16783da0f 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 8f82fcc9a7..bcf9a74bf4 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 5/6] 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 5a4099a757..3931213b2b 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 0000000000..a73a91be72 --- /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 0000000000..9e4db331b9 --- /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 0000000000..1a12400cfa --- /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 712d9dc98169da0465c6f6b7d3acd90d4d9c2cca Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Tue, 27 Oct 2026 03:07:12 +0000 Subject: [PATCH 6/6] 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 a73a91be72..bc712e30a9 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 9e4db331b9..7c5c6629cb 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