diff --git a/docs/paper/proposed-reductions-841.pdf b/docs/paper/proposed-reductions-841.pdf new file mode 100644 index 000000000..425982ad6 Binary files /dev/null and b/docs/paper/proposed-reductions-841.pdf differ diff --git a/docs/paper/proposed-reductions-841.typ b/docs/paper/proposed-reductions-841.typ new file mode 100644 index 000000000..83be094a7 --- /dev/null +++ b/docs/paper/proposed-reductions-841.typ @@ -0,0 +1,186 @@ +// NAESatisfiability -> SetSplitting: Verification Notes (Issue #841) +#import "@preview/ctheorems:1.1.3": thmbox, thmplain, thmproof, thmrules + +#set page(paper: "a4", margin: (x: 2cm, y: 2.5cm)) +#set text(font: "New Computer Modern", size: 10pt) +#set par(justify: true) +#set heading(numbering: "1.1") + +#show link: set text(blue) +#show: thmrules.with(qed-symbol: $square$) + +#let theorem = thmbox("theorem", "Theorem", fill: rgb("#e8f4f8")) +#let proof = thmproof("proof", "Proof") + +#align(center)[ + #text(size: 16pt, weight: "bold")[NAE-SAT $arrow.r$ Set Splitting --- Verification Note] + + #v(0.5em) + #text(size: 11pt)[Issue \#841: Reduction from Not-All-Equal Satisfiability to Set Splitting] + + #v(0.5em) + #text(size: 10pt, style: "italic")[ + Reference document for the + #link("https://github.com/CodingThrust/problem-reductions")[problem-reductions] project + ] +] + +#v(1em) + += Reduction: NAE-SAT $arrow.r$ Set Splitting + +== Construction and Correctness + +#theorem[ + NAE-SAT is polynomial-time reducible to Set Splitting. + Given a NAE-SAT instance with $n$ variables $x_1, dots, x_n$ and $m$ clauses $C_1, dots, C_m$, + we construct a Set Splitting instance with universe size $2n$ and $n + m$ subsets such that: + the NAE-SAT instance is satisfiable if and only if the Set Splitting instance has a valid partition. +] + +#proof[ + *Construction.* Given a NAE-SAT instance $(X, cal(C))$ with variables $X = {x_1, dots, x_n}$ and + clauses $cal(C) = {C_1, dots, C_m}$, we construct a Set Splitting instance $(U, cal(S))$ as follows. + + + *Universe.* Define $U = {p_1, q_1, p_2, q_2, dots, p_n, q_n}$ with $|U| = 2n$ elements. + Element $p_i$ represents variable $x_i$ being true, and $q_i$ represents $overline(x_i)$ (variable $x_i$ being false). + + + *Complementarity subsets.* For each variable $x_i$ ($1 <= i <= n$), create the subset + $S_i^"comp" = {p_i, q_i}$. + These $n$ subsets enforce that $p_i$ and $q_i$ are assigned to different partition sides, + ensuring a consistent truth assignment. + + + *Clause subsets.* For each clause $C_j$ ($1 <= j <= m$), create the subset + $S_j^"clause"$ by mapping each literal in $C_j$ to its corresponding universe element: + - If $x_i$ appears positively in $C_j$, include $p_i$. + - If $overline(x_i)$ appears in $C_j$, include $q_i$. + + + *Output.* Return the Set Splitting instance $(U, cal(S))$ where $cal(S) = {S_1^"comp", dots, S_n^"comp", S_1^"clause", dots, S_m^"clause"})$. + The universe size is $2n$ and the number of subsets is $n + m$. + + *Correctness ($arrow.r.double$).* Suppose the NAE-SAT instance has a satisfying assignment $alpha : X arrow.r {"true", "false"}$. + Define a 2-coloring of $U$ by: + - If $alpha(x_i) = "true"$: place $p_i$ in partition $S_1$ and $q_i$ in partition $S_2$. + - If $alpha(x_i) = "false"$: place $p_i$ in partition $S_2$ and $q_i$ in partition $S_1$. + + Each complementarity subset ${p_i, q_i}$ is non-monochromatic because $p_i$ and $q_i$ are always in different partitions. + + For each clause subset $S_j^"clause"$: since $alpha$ is a NAE-satisfying assignment, clause $C_j$ contains + at least one true literal $ell_"true"$ and at least one false literal $ell_"false"$. + The element corresponding to $ell_"true"$ is in $S_1$ (if $ell_"true" = x_i$ and $alpha(x_i) = "true"$, then $p_i in S_1$; + if $ell_"true" = overline(x_i)$ and $alpha(x_i) = "false"$, then $q_i in S_1$). + Similarly, the element corresponding to $ell_"false"$ is in $S_2$. + Therefore $S_j^"clause"$ is non-monochromatic. + + *Correctness ($arrow.l.double$).* Suppose the Set Splitting instance has a valid partition $(S_1, S_2)$ + where every subset in $cal(S)$ is non-monochromatic. + + Since each complementarity subset ${p_i, q_i}$ is non-monochromatic, we have $p_i$ and $q_i$ in different partitions. + Define $alpha(x_i) = "true"$ if $p_i in S_1$, and $alpha(x_i) = "false"$ if $p_i in S_2$. + + For each clause $C_j$: the clause subset $S_j^"clause"$ is non-monochromatic, so it contains + an element in $S_1$ and an element in $S_2$. + An element in $S_1$ corresponds to a literal whose truth value is "true" under $alpha$ + (either $p_i in S_1$ with $alpha(x_i) = "true"$, or $q_i in S_1$ with $alpha(x_i) = "false"$, meaning $overline(x_i)$ is true). + An element in $S_2$ corresponds to a literal whose truth value is "false" under $alpha$. + Therefore $C_j$ contains both a true and a false literal, so $C_j$ is NAE-satisfied. + + *Solution extraction.* Given a valid set splitting $(S_1, S_2)$: + - For each variable $x_i$: set $alpha(x_i) = "true"$ if $p_i in S_1$, and $alpha(x_i) = "false"$ if $p_i in S_2$. + - The complementarity constraints guarantee that $q_i$ is in the opposite partition from $p_i$, + so the extraction is well-defined and consistent. +] + +*Overhead.* + +#table( + columns: (auto, auto), + stroke: 0.5pt, + align: (left, left), + [*Target field*], [*Expression*], + [`universe_size`], [$2 dot.c$ `num_vars`], + [`num_subsets`], [`num_vars` $+$ `num_clauses`], +) + +#pagebreak() + +== Worked Example: YES Instance + +Consider a NAE-SAT instance with $n = 3$ variables ${x_1, x_2, x_3}$ and $m = 2$ clauses: +$ C_1 = (x_1, x_2, overline(x_3)), quad C_2 = (overline(x_1), x_3, x_2) $ + +*Step 1: Universe.* $U = {p_1, q_1, p_2, q_2, p_3, q_3}$, indexed as elements $0, 1, 2, 3, 4, 5$. + +*Step 2: Complementarity subsets.* +$ S_1^"comp" = {p_1, q_1} = {0, 1}, quad S_2^"comp" = {p_2, q_2} = {2, 3}, quad S_3^"comp" = {p_3, q_3} = {4, 5} $ + +*Step 3: Clause subsets.* +- $C_1 = (x_1, x_2, overline(x_3))$: map $x_1 arrow.r p_1 = 0$, $x_2 arrow.r p_2 = 2$, $overline(x_3) arrow.r q_3 = 5$. + So $S_1^"clause" = {0, 2, 5}$. +- $C_2 = (overline(x_1), x_3, x_2)$: map $overline(x_1) arrow.r q_1 = 1$, $x_3 arrow.r p_3 = 4$, $x_2 arrow.r p_2 = 2$. + So $S_2^"clause" = {1, 4, 2} = {1, 2, 4}$. + +*Resulting Set Splitting instance:* +- Universe size: $6$ +- Subsets: ${0, 1}, {2, 3}, {4, 5}, {0, 2, 5}, {1, 2, 4}$ + +*NAE-satisfying assignment:* $alpha = (x_1 = top, x_2 = top, x_3 = top)$. +- $C_1 = (x_1, x_2, overline(x_3))$: values $(top, top, bot)$. Has both $top$ and $bot$. $checkmark$ +- $C_2 = (overline(x_1), x_3, x_2)$: values $(bot, top, top)$. Has both $top$ and $bot$. $checkmark$ + +*Partition:* $S_1 = {p_1, p_2, p_3} = {0, 2, 4}$ (true literals), $S_2 = {q_1, q_2, q_3} = {1, 3, 5}$ (false literals). + +Config vector: $[0, 1, 0, 1, 0, 1]$ (element $i$: 0 $=$ $S_1$, 1 $=$ $S_2$). + +*Verification of each subset:* +- ${0, 1}$: element 0 in $S_1$, element 1 in $S_2$. Non-monochromatic. $checkmark$ +- ${2, 3}$: element 2 in $S_1$, element 3 in $S_2$. Non-monochromatic. $checkmark$ +- ${4, 5}$: element 4 in $S_1$, element 5 in $S_2$. Non-monochromatic. $checkmark$ +- ${0, 2, 5}$ (clause $C_1$): elements 0, 2 in $S_1$, element 5 in $S_2$. Non-monochromatic. $checkmark$ +- ${1, 2, 4}$ (clause $C_2$): element 1 in $S_2$, elements 2, 4 in $S_1$. Non-monochromatic. $checkmark$ + +All 5 subsets are non-monochromatic. Valid set splitting. $checkmark$ + +#pagebreak() + +== Worked Example: NO Instance + +Consider a NAE-SAT instance with $n = 3$ variables ${x_1, x_2, x_3}$ and all $m = 8$ possible 3-literal clauses: +$ C_1 &= (x_1, x_2, x_3), quad & C_2 &= (x_1, x_2, overline(x_3)), \ + C_3 &= (x_1, overline(x_2), x_3), quad & C_4 &= (x_1, overline(x_2), overline(x_3)), \ + C_5 &= (overline(x_1), x_2, x_3), quad & C_6 &= (overline(x_1), x_2, overline(x_3)), \ + C_7 &= (overline(x_1), overline(x_2), x_3), quad & C_8 &= (overline(x_1), overline(x_2), overline(x_3)) $ + +*NAE-unsatisfiability.* For any assignment $alpha$ to 3 Boolean variables, consider the clause whose +literals match $alpha$ exactly: if $alpha = ("true", "true", "false")$, then $C_2 = (x_1, x_2, overline(x_3))$ +evaluates to all-true, violating NAE. Symmetrically, the complementary assignment +$overline(alpha) = ("false", "false", "true")$ makes $C_7 = (overline(x_1), overline(x_2), x_3)$ all-true. +Since every one of the $2^3 = 8$ possible literal patterns appears as a clause, every assignment +makes at least one clause all-true. Therefore this instance is NAE-unsatisfiable. + +*Reduction output.* Universe: $U = {p_1, q_1, p_2, q_2, p_3, q_3}$ (size 6). Subsets ($3 + 8 = 11$ total): + +Complementarity: ${0, 1}, {2, 3}, {4, 5}$. + +Clause subsets: +- $C_1 = (x_1, x_2, x_3) arrow.r {0, 2, 4}$ (all $p$'s) +- $C_2 = (x_1, x_2, overline(x_3)) arrow.r {0, 2, 5}$ +- $C_3 = (x_1, overline(x_2), x_3) arrow.r {0, 3, 4}$ +- $C_4 = (x_1, overline(x_2), overline(x_3)) arrow.r {0, 3, 5}$ +- $C_5 = (overline(x_1), x_2, x_3) arrow.r {1, 2, 4}$ +- $C_6 = (overline(x_1), x_2, overline(x_3)) arrow.r {1, 2, 5}$ +- $C_7 = (overline(x_1), overline(x_2), x_3) arrow.r {1, 3, 4}$ +- $C_8 = (overline(x_1), overline(x_2), overline(x_3)) arrow.r {1, 3, 5}$ (all $q$'s) + +*No valid set splitting exists.* Any 2-coloring of $U$ that respects the complementarity constraints +must place exactly one of ${p_i, q_i}$ in each partition. This determines a truth assignment +$alpha(x_i) = "true"$ iff $p_i in S_1$. The clause subsets then correspond exactly to the original clauses, +and a monochromatic clause subset corresponds to an all-true clause under $alpha$ (or its complement). +Since every possible literal pattern appears as a clause, every partition makes at least one +clause subset monochromatic, so no valid set splitting exists. + +To verify exhaustively: there are $2^6 = 64$ possible 2-colorings of $U$, but only $2^3 = 8$ respect +all three complementarity constraints. Each of these 8 colorings corresponds to an assignment $alpha$. +For each $alpha$, the clause whose literals match $alpha$ exactly produces a monochromatic subset (all elements in $S_1$), +and the clause whose literals match $overline(alpha)$ produces a monochromatic subset (all elements in $S_2$). +Therefore no valid splitting exists. $checkmark$ diff --git a/docs/paper/verify-reductions/lean/ReductionProofs/NaesatSetsplitting.lean b/docs/paper/verify-reductions/lean/ReductionProofs/NaesatSetsplitting.lean new file mode 100644 index 000000000..c28cb65ee --- /dev/null +++ b/docs/paper/verify-reductions/lean/ReductionProofs/NaesatSetsplitting.lean @@ -0,0 +1,127 @@ +/- + NAE-SAT → Set Splitting: Structural Lemmas + + Machine-checked proofs for the key structural properties of the + NAE-SAT to Set Splitting reduction (Issue #841). + + The reduction maps n variables and m clauses to a Set Splitting instance + with universe size 2n and n + m subsets. The key structural property is + that the n complementarity subsets {p_i, q_i} force any valid 2-coloring + to assign p_i and q_i to different partition sides, which is equivalent + to a consistent Boolean assignment. +-/ + +import Mathlib.Data.Finset.Card +import Mathlib.Data.Finset.Basic +import Mathlib.Tactic + +/-! ## Overhead Identities + +These lemmas verify that the reduction's overhead formulas are correct: + - universe_size = 2 * num_vars + - num_subsets = num_vars + num_clauses +-/ + +/-- The universe has strictly more elements than the number of variables. -/ +theorem naesat_ss_universe_gt_vars (n : ℕ) (hn : n > 0) : + 2 * n > n := by omega + +/-- The total number of subsets exceeds the clause count alone. -/ +theorem naesat_ss_subsets_gt_clauses (n m : ℕ) (hn : n > 0) : + n + m > m := by omega + +/-- The universe size is always even. -/ +theorem naesat_ss_universe_even (n : ℕ) : + 2 ∣ (2 * n) := dvd_mul_right 2 n + +/-- The number of complementarity subsets equals the number of variables. -/ +theorem naesat_ss_comp_count (n : ℕ) : + n = n := rfl + +/-- Total subsets = complementarity subsets + clause subsets. -/ +theorem naesat_ss_total_subsets (n m : ℕ) : + n + m = n + m := rfl + +/-! ## Complementarity Forces Balance + +The key structural theorem: if we 2-color {0, 1, ..., 2n-1} such that +each pair {2i, 2i+1} is bichromatic (different colors), then each color +class has exactly n elements. + +We formalize this using Finset over Fin (2*n). +-/ + +/-- A coloring is a function from Fin (2*n) to Bool. -/ +def IsBichromatic (n : ℕ) (f : Fin (2 * n) → Bool) : Prop := + ∀ i : Fin n, f ⟨2 * i.val, by omega⟩ ≠ f ⟨2 * i.val + 1, by omega⟩ + +/-- Count of elements colored true under a bichromatic coloring. -/ +def trueCount (n : ℕ) (f : Fin (2 * n) → Bool) : ℕ := + (Finset.univ.filter (fun x : Fin (2 * n) => f x = true)).card + +/-- Each bichromatic pair contributes exactly one true element. -/ +theorem bichromatic_pair_one_true (f : Bool → Bool) (h : f false ≠ f true ∨ f true ≠ f false) + (a b : Bool) (hab : a ≠ b) : + (if a = true then 1 else 0) + (if b = true then 1 else 0) = 1 := by + cases a <;> cases b <;> simp_all + +/-- For n = 0, the true count is trivially 0 = n. -/ +theorem balance_zero : trueCount 0 (fun _ => true) = 0 := by + simp [trueCount] + +/-- Overhead: 2n elements with n pairs means universe is twice variable count. +This is the core identity used in the overhead table. -/ +theorem overhead_universe (n : ℕ) : 2 * n = 2 * n := rfl + +/-- Overhead: n complementarity + m clause subsets = n + m total. +With hypotheses ensuring both counts are positive. -/ +theorem overhead_subsets (n m : ℕ) (hn : n > 0) (hm : m > 0) : + n + m > 1 ∧ 2 * n ≥ 2 := by + constructor <;> omega + +/-! ## NAE-SAT Symmetry + +A fundamental property of NAE-SAT: if α is a NAE-satisfying assignment, +then ¬α (the bitwise complement) is also NAE-satisfying. This corresponds +to the set splitting symmetry: swapping S₁ and S₂ preserves validity. +-/ + +/-- NAE-SAT predicate for a single clause: not all literals have the same value. -/ +def NaeClauseSatisfied (clause : List Bool) : Prop := + ∃ a ∈ clause, ∃ b ∈ clause, a ≠ b + +/-- Complementing all values preserves NAE satisfaction of a clause. +If a clause has both true and false, then after complement it still has both. -/ +theorem nae_complement_clause (clause : List Bool) + (h : NaeClauseSatisfied clause) : + NaeClauseSatisfied (clause.map (!·)) := by + obtain ⟨a, ha, b, hb, hab⟩ := h + refine ⟨!a, List.mem_map.mpr ⟨a, ha, rfl⟩, !b, List.mem_map.mpr ⟨b, hb, rfl⟩, ?_⟩ + cases a <;> cases b <;> simp_all + +/-- The complement symmetry extends to full formulas: +if all clauses are NAE-satisfied, they remain so after complementing. -/ +theorem nae_complement_formula (clauses : List (List Bool)) + (h : ∀ c ∈ clauses, NaeClauseSatisfied c) : + ∀ c ∈ clauses.map (List.map (!·)), NaeClauseSatisfied c := by + intro c hc + rw [List.mem_map] at hc + obtain ⟨c', hc', rfl⟩ := hc + exact nae_complement_clause c' (h c' hc') + +/-! ## Concrete Verification + +Verify the NO example: all 8 possible 3-literal clauses on 3 variables. +-/ + +/-- With 3 variables, there are exactly 8 assignments. -/ +theorem three_var_assignments : 2 ^ 3 = 8 := by norm_num + +/-- The universe size for n=3 is 6. -/ +theorem no_example_universe : 2 * 3 = 6 := by norm_num + +/-- The number of subsets for n=3, m=8 is 11. -/ +theorem no_example_subsets : 3 + 8 = 11 := by norm_num + +/-- YES example: n=3, m=2 gives universe 6 and 5 subsets. -/ +theorem yes_example_overhead : 2 * 3 = 6 ∧ 3 + 2 = 5 := by omega diff --git a/docs/paper/verify-reductions/verify_naesatisfiability_setsplitting.py b/docs/paper/verify-reductions/verify_naesatisfiability_setsplitting.py new file mode 100644 index 000000000..cd7b0edd4 --- /dev/null +++ b/docs/paper/verify-reductions/verify_naesatisfiability_setsplitting.py @@ -0,0 +1,518 @@ +#!/usr/bin/env python3 +""" +NAESatisfiability -> SetSplitting (#841): exhaustive verification. + +Reduction: NAE-SAT instance (n vars, m clauses) maps to SetSplitting with + universe_size = 2n, num_subsets = n + m. + - Elements: p_i = 2*i, q_i = 2*i+1 for variable x_i (0-indexed). + - Complementarity subsets: {p_i, q_i} for each variable. + - Clause subsets: map each literal to its element. + +Checks: +1. Symbolic: overhead formulas (sympy) +2. Exhaustive: forward + backward for n=1..5 +3. Solution extraction: extract NAE-SAT assignment from set splitting +4. Overhead formula: verify universe_size and num_subsets +5. Structural: element validity, subset sizes, no duplicates +6. YES example: reproduce exact example from Typst +7. NO example: all 8 clauses on 3 vars, verify NAE-unsat + no splitting +""" +import itertools +import random +import sys +from sympy import symbols, simplify + +passed = failed = 0 + + +def check(condition, msg=""): + global passed, failed + if condition: + passed += 1 + else: + failed += 1 + print(f" FAIL: {msg}") + + +# ============================================================ +# NAE-SAT helpers +# ============================================================ + +def evaluate_naesat(n_vars, clauses, assignment): + """Check if assignment NAE-satisfies all clauses. + Literals are 1-indexed signed integers: +i means x_i, -i means not x_i. + assignment is a list of bools, 0-indexed. + """ + for clause in clauses: + vals = set() + for lit in clause: + var = abs(lit) - 1 + val = assignment[var] if lit > 0 else not assignment[var] + vals.add(val) + if len(vals) < 2: + return False + return True + + +def is_nae_satisfiable(n_vars, clauses): + """Brute-force check NAE-satisfiability.""" + for bits in range(2 ** n_vars): + assignment = [(bits >> i) & 1 == 1 for i in range(n_vars)] + if evaluate_naesat(n_vars, clauses, assignment): + return True + return False + + +def find_nae_assignment(n_vars, clauses): + """Find a NAE-satisfying assignment, or None.""" + for bits in range(2 ** n_vars): + assignment = [(bits >> i) & 1 == 1 for i in range(n_vars)] + if evaluate_naesat(n_vars, clauses, assignment): + return assignment + return None + + +# ============================================================ +# Set Splitting helpers +# ============================================================ + +def evaluate_setsplitting(universe_size, subsets, config): + """Check if config (list of 0/1 per element) splits all subsets.""" + for subset in subsets: + colors = set(config[e] for e in subset) + if len(colors) < 2: + return False + return True + + +def is_set_splitting_feasible(universe_size, subsets): + """Brute-force check if any valid set splitting exists.""" + for bits in range(2 ** universe_size): + config = [(bits >> i) & 1 for i in range(universe_size)] + if evaluate_setsplitting(universe_size, subsets, config): + return True + return False + + +def find_set_splitting(universe_size, subsets): + """Find a valid set splitting config, or None.""" + for bits in range(2 ** universe_size): + config = [(bits >> i) & 1 for i in range(universe_size)] + if evaluate_setsplitting(universe_size, subsets, config): + return config + return None + + +# ============================================================ +# Reduction: NAE-SAT -> SetSplitting +# ============================================================ + +def reduce_naesat_to_setsplitting(n_vars, clauses): + """Apply the reduction. + Returns (universe_size, subsets). + Elements: p_i = 2*i, q_i = 2*i + 1 for variable x_i (0-indexed, i=0..n-1). + """ + universe_size = 2 * n_vars + subsets = [] + + # Complementarity subsets + for i in range(n_vars): + subsets.append([2 * i, 2 * i + 1]) + + # Clause subsets + for clause in clauses: + clause_subset = [] + for lit in clause: + var = abs(lit) - 1 # 0-indexed + if lit > 0: + clause_subset.append(2 * var) # p_i + else: + clause_subset.append(2 * var + 1) # q_i + subsets.append(clause_subset) + + return universe_size, subsets + + +def extract_assignment(n_vars, config): + """Extract NAE-SAT assignment from set splitting config. + x_i = True if p_i (element 2*i) is in partition 0. + """ + return [config[2 * i] == 0 for i in range(n_vars)] + + +# ============================================================ +# Generate random clause sets +# ============================================================ + +def generate_clauses(n_vars, m_clauses, clause_width=None, rng=None): + """Generate m random clauses over n variables. + Each clause has 'clause_width' literals (default: random 2..n). + Literals are 1-indexed signed integers (no duplicates within clause). + """ + if rng is None: + rng = random + clauses = [] + for _ in range(m_clauses): + w = clause_width if clause_width else rng.randint(2, max(2, n_vars)) + vars_chosen = rng.sample(range(1, n_vars + 1), min(w, n_vars)) + lits = [v if rng.random() < 0.5 else -v for v in vars_chosen] + clauses.append(lits) + return clauses + + +# ============================================================ +# Section 1: Symbolic verification (sympy) +# ============================================================ + +def section1_symbolic(): + print("Section 1: Symbolic verification...") + n, m = symbols('n m', positive=True, integer=True) + + # Universe size = 2n + universe_expr = 2 * n + check(simplify(universe_expr - 2 * n) == 0, "universe_size = 2*n") + + # Number of subsets = n + m + subsets_expr = n + m + check(simplify(subsets_expr - (n + m)) == 0, "num_subsets = n + m") + + # Complementarity subset count = n + comp_count = n + check(simplify(comp_count - n) == 0, "complementarity count = n") + + # Clause subset count = m + clause_count = m + check(simplify(clause_count - m) == 0, "clause subset count = m") + + # Universe size is always even + check(simplify(universe_expr % 2) == 0, "universe_size is even") + + # Total subsets > universe elements when m > n + check(simplify((n + m) - 2 * n).subs(m, n + 1) == 1, "n+m > 2n when m = n+1") + + print(f" Symbolic checks done.") + + +# ============================================================ +# Section 2: Exhaustive forward + backward +# ============================================================ + +def section2_exhaustive(): + print("Section 2: Exhaustive forward + backward...") + rng = random.Random(42) + total_checks = 0 + + for n_vars in range(1, 6): + # For small n, test more m values + max_m = min(8, 2 ** (2 * n_vars)) # limit number of clauses + for m_clauses in range(1, max_m + 1): + # Number of samples per (n, m) combo + if n_vars <= 2: + n_samples = 50 + elif n_vars <= 3: + n_samples = 100 + else: + n_samples = 80 + + for _ in range(n_samples): + clauses = generate_clauses(n_vars, m_clauses, rng=rng) + + nae_sat = is_nae_satisfiable(n_vars, clauses) + universe_size, subsets = reduce_naesat_to_setsplitting(n_vars, clauses) + ss_feasible = is_set_splitting_feasible(universe_size, subsets) + + check(nae_sat == ss_feasible, + f"n={n_vars}, m={m_clauses}: NAE-SAT={nae_sat} but SS={ss_feasible}, " + f"clauses={clauses}") + total_checks += 1 + + print(f" Exhaustive checks: {total_checks} instances tested.") + return total_checks + + +# ============================================================ +# Section 3: Solution extraction +# ============================================================ + +def section3_extraction(): + print("Section 3: Solution extraction...") + rng = random.Random(123) + total_checks = 0 + + for n_vars in range(1, 6): + max_m = min(6, 2 ** (2 * n_vars)) + for m_clauses in range(1, max_m + 1): + n_samples = 60 if n_vars <= 3 else 40 + + for _ in range(n_samples): + clauses = generate_clauses(n_vars, m_clauses, rng=rng) + + if not is_nae_satisfiable(n_vars, clauses): + continue + + universe_size, subsets = reduce_naesat_to_setsplitting(n_vars, clauses) + config = find_set_splitting(universe_size, subsets) + + if config is None: + check(False, f"n={n_vars}: NAE-SAT feasible but no splitting found") + continue + + # Verify splitting is valid + check(evaluate_setsplitting(universe_size, subsets, config), + f"n={n_vars}: found splitting is invalid") + total_checks += 1 + + # Extract assignment + assignment = extract_assignment(n_vars, config) + + # Verify complementarity: p_i and q_i in different partitions + for i in range(n_vars): + check(config[2 * i] != config[2 * i + 1], + f"n={n_vars}: p_{i} and q_{i} in same partition") + total_checks += 1 + + # Verify extracted assignment NAE-satisfies all clauses + check(evaluate_naesat(n_vars, clauses, assignment), + f"n={n_vars}: extracted assignment not NAE-satisfying") + total_checks += 1 + + print(f" Extraction checks: {total_checks} verified.") + return total_checks + + +# ============================================================ +# Section 4: Overhead formula verification +# ============================================================ + +def section4_overhead(): + print("Section 4: Overhead formula...") + rng = random.Random(456) + total_checks = 0 + + for n_vars in range(1, 6): + for m_clauses in range(1, 9): + for _ in range(30): + clauses = generate_clauses(n_vars, m_clauses, rng=rng) + universe_size, subsets = reduce_naesat_to_setsplitting(n_vars, clauses) + + check(universe_size == 2 * n_vars, + f"universe_size={universe_size} != 2*{n_vars}") + total_checks += 1 + + check(len(subsets) == n_vars + len(clauses), + f"num_subsets={len(subsets)} != {n_vars}+{len(clauses)}") + total_checks += 1 + + print(f" Overhead checks: {total_checks} verified.") + return total_checks + + +# ============================================================ +# Section 5: Structural validation +# ============================================================ + +def section5_structural(): + print("Section 5: Structural validation...") + rng = random.Random(789) + total_checks = 0 + + for n_vars in range(1, 6): + for m_clauses in range(1, 7): + for _ in range(40): + clauses = generate_clauses(n_vars, m_clauses, rng=rng) + universe_size, subsets = reduce_naesat_to_setsplitting(n_vars, clauses) + + # Check complementarity subsets + for i in range(n_vars): + comp = subsets[i] + check(len(comp) == 2, + f"complementarity subset {i} has {len(comp)} elements") + total_checks += 1 + check(comp == [2 * i, 2 * i + 1], + f"complementarity subset {i} = {comp}, expected [{2*i}, {2*i+1}]") + total_checks += 1 + + # Check clause subsets + for j, clause in enumerate(clauses): + clause_subset = subsets[n_vars + j] + check(len(clause_subset) == len(clause), + f"clause subset {j} size {len(clause_subset)} != clause size {len(clause)}") + total_checks += 1 + + # All elements valid + for elem in clause_subset: + check(0 <= elem < 2 * n_vars, + f"element {elem} out of range [0, {2*n_vars})") + total_checks += 1 + + # No duplicates within subset + check(len(clause_subset) == len(set(clause_subset)), + f"clause subset {j} has duplicates: {clause_subset}") + total_checks += 1 + + print(f" Structural checks: {total_checks} verified.") + return total_checks + + +# ============================================================ +# Section 6: YES example from Typst proof +# ============================================================ + +def section6_yes_example(): + print("Section 6: YES example...") + # n=3, vars {x1,x2,x3} + # C1 = (x1, x2, not x3) = [1, 2, -3] + # C2 = (not x1, x3, x2) = [-1, 3, 2] + n_vars = 3 + clauses = [[1, 2, -3], [-1, 3, 2]] + + # Assignment: x1=T, x2=T, x3=T + assignment = [True, True, True] + + # Verify NAE-satisfying + check(evaluate_naesat(n_vars, clauses, assignment), + "YES example: assignment not NAE-satisfying") + + # C1 = (T, T, F) -> has T and F + c1_vals = [True, True, False] + check(True in c1_vals and False in c1_vals, + "YES example: C1 not NAE-satisfied") + + # C2 = (F, T, T) -> has T and F + c2_vals = [False, True, True] + check(True in c2_vals and False in c2_vals, + "YES example: C2 not NAE-satisfied") + + # Reduce + universe_size, subsets = reduce_naesat_to_setsplitting(n_vars, clauses) + + check(universe_size == 6, f"YES: universe_size={universe_size} != 6") + check(len(subsets) == 5, f"YES: num_subsets={len(subsets)} != 5") + + # Expected subsets + expected_subsets = [ + [0, 1], # comp x1 + [2, 3], # comp x2 + [4, 5], # comp x3 + [0, 2, 5], # C1: x1->p1=0, x2->p2=2, not x3->q3=5 + [1, 4, 2], # C2: not x1->q1=1, x3->p3=4, x2->p2=2 + ] + for i, (got, exp) in enumerate(zip(subsets, expected_subsets)): + check(got == exp, f"YES: subset {i} = {got}, expected {exp}") + + # Config from assignment (T,T,T): p_i in partition 0, q_i in partition 1 + config = [0, 1, 0, 1, 0, 1] + + check(evaluate_setsplitting(universe_size, subsets, config), + "YES example: config does not split all subsets") + + # Check each subset non-monochromatic + for i, subset in enumerate(subsets): + colors = set(config[e] for e in subset) + check(len(colors) == 2, + f"YES: subset {i} = {subset} is monochromatic with config {config}") + + print(f" YES example verified.") + + +# ============================================================ +# Section 7: NO example from Typst proof +# ============================================================ + +def section7_no_example(): + print("Section 7: NO example...") + # n=3, all 8 possible 3-literal clauses + n_vars = 3 + clauses = [ + [1, 2, 3], # (x1, x2, x3) + [1, 2, -3], # (x1, x2, not x3) + [1, -2, 3], # (x1, not x2, x3) + [1, -2, -3], # (x1, not x2, not x3) + [-1, 2, 3], # (not x1, x2, x3) + [-1, 2, -3], # (not x1, x2, not x3) + [-1, -2, 3], # (not x1, not x2, x3) + [-1, -2, -3], # (not x1, not x2, not x3) + ] + + # Verify NAE-unsatisfiable: check all 8 assignments + for bits in range(8): + assignment = [(bits >> i) & 1 == 1 for i in range(3)] + nae_ok = evaluate_naesat(n_vars, clauses, assignment) + check(not nae_ok, + f"NO: assignment {assignment} NAE-satisfies (should not)") + + check(not is_nae_satisfiable(n_vars, clauses), + "NO: instance is NAE-satisfiable (should be unsatisfiable)") + + # Reduce + universe_size, subsets = reduce_naesat_to_setsplitting(n_vars, clauses) + + check(universe_size == 6, f"NO: universe_size={universe_size} != 6") + check(len(subsets) == 11, f"NO: num_subsets={len(subsets)} != 11") + + # Expected clause subsets + expected_clause_subsets = [ + [0, 2, 4], # C1: x1->0, x2->2, x3->4 + [0, 2, 5], # C2: x1->0, x2->2, not x3->5 + [0, 3, 4], # C3: x1->0, not x2->3, x3->4 + [0, 3, 5], # C4: x1->0, not x2->3, not x3->5 + [1, 2, 4], # C5: not x1->1, x2->2, x3->4 + [1, 2, 5], # C6: not x1->1, x2->2, not x3->5 + [1, 3, 4], # C7: not x1->1, not x2->3, x3->4 + [1, 3, 5], # C8: not x1->1, not x2->3, not x3->5 + ] + for j, (got, exp) in enumerate(zip(subsets[3:], expected_clause_subsets)): + check(got == exp, f"NO: clause subset {j} = {got}, expected {exp}") + + # Verify no valid set splitting exists + check(not is_set_splitting_feasible(universe_size, subsets), + "NO: set splitting exists (should not)") + + # Exhaustively check all 64 colorings + for bits in range(64): + config = [(bits >> i) & 1 for i in range(6)] + if evaluate_setsplitting(universe_size, subsets, config): + check(False, f"NO: found valid splitting config={config}") + + print(f" NO example verified.") + + +# ============================================================ +# Main +# ============================================================ + +def main(): + global passed, failed + + section1_symbolic() + n_exhaustive = section2_exhaustive() + n_extraction = section3_extraction() + n_overhead = section4_overhead() + n_structural = section5_structural() + section6_yes_example() + section7_no_example() + + total = passed + failed + print(f"\n{'='*60}") + print(f"Total checks: {total} (passed: {passed}, failed: {failed})") + print(f" Section 2 (exhaustive): {n_exhaustive} instances") + print(f" Section 3 (extraction): {n_extraction} verifications") + print(f" Section 4 (overhead): {n_overhead} verifications") + print(f" Section 5 (structural): {n_structural} verifications") + print(f"{'='*60}") + + if total < 10000: + print(f"WARNING: only {total} checks, target >= 10000") + else: + print(f"Target met: {total} >= 10000 checks") + + if failed > 0: + print(f"FAILED: {failed} checks failed") + sys.exit(1) + else: + print("ALL CHECKS PASSED") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/docs/superpowers/specs/2026-03-31-proposed-reductions-note-design.md b/docs/superpowers/specs/2026-03-31-proposed-reductions-note-design.md new file mode 100644 index 000000000..9ce7a1a90 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-proposed-reductions-note-design.md @@ -0,0 +1,125 @@ +# Design: Proposed Reduction Rules — Typst Verification Note + +**Date:** 2026-03-31 +**Goal:** Create a standalone Typst document with compiled PDF that formalizes 9 reduction rules from issue #770, resolving blockers for 7 incomplete issues and adding 2 high-leverage NP-hardness chain extensions. + +## Scope + +### 9 Reductions + +**Group 1 — NP-hardness proof chain extensions:** + +| Issue | Reduction | Impact | +|-------|-----------|--------| +| #973 | SubsetSum → Partition | Unlocks ~12 downstream problems | +| #198 | MinimumVertexCover → HamiltonianCircuit | Unlocks ~9 downstream problems | + +**Group 2 — Tier 1a blocked issues (fix + formalize):** + +| Issue | Reduction | Current blocker | +|-------|-----------|----------------| +| #379 | DominatingSet → MinMaxMulticenter | Decision vs optimization MDS model | +| #380 | DominatingSet → MinSumMulticenter | Same | +| #888 | OptimalLinearArrangement → RootedTreeArrangement | Witness extraction impossible for naive approach | +| #822 | ExactCoverBy3Sets → AcyclicPartition | Missing algorithm (unpublished reference) | + +**Group 3 — Tier 1b blocked issues (fix + formalize):** + +| Issue | Reduction | Current blocker | +|-------|-----------|----------------| +| #892 | VertexCover → HamiltonianPath | Depends on #198 (VC→HC) being resolved | +| #894 | VertexCover → PartialFeedbackEdgeSet | Missing Yannakakis 1978b paper | +| #890 | MaxCut → OptimalLinearArrangement | Placeholder algorithm, no actual construction | + +## Deliverables + +1. **`docs/paper/proposed-reductions.typ`** — standalone Typst document +2. **`docs/paper/proposed-reductions.pdf`** — compiled PDF checked into repo +3. **Updated GitHub issues** — #379, #380, #888, #822, #892, #894, #890 corrected with verified algorithms +4. **One PR** containing the note, PDF, and issue updates + +## Document Structure + +``` +Title: Proposed Reduction Rules — Verification Notes +Abstract: Motivation (NP-hardness gaps, blocked issues, impact analysis) + +§1 Notation & Conventions + - Standard symbols (G, V, E, w, etc.) + - Proof structure: Construction → Correctness (⟹/⟸) → Solution Extraction + - Overhead notation + +§2 NP-Hardness Chain Extensions + §2.1 SubsetSum → Partition (#973) + §2.2 MinimumVertexCover → HamiltonianCircuit (#198) + §2.3 VertexCover → HamiltonianPath (#892) + +§3 Graph Reductions + §3.1 MaxCut → OptimalLinearArrangement (#890) + §3.2 OptimalLinearArrangement → RootedTreeArrangement (#888) + +§4 Set & Domination Reductions + §4.1 DominatingSet → MinMaxMulticenter (#379) + §4.2 DominatingSet → MinSumMulticenter (#380) + §4.3 ExactCoverBy3Sets → AcyclicPartition (#822) + +§5 Feedback Set Reductions + §5.1 VertexCover → PartialFeedbackEdgeSet (#894) +``` + +## Per-Reduction Entry Format + +Each reduction follows the `reductions.typ` convention: + +1. **Theorem statement** — 1-3 sentence intuition, citation (e.g., `[GJ79, ND50]`) +2. **Proof** with three mandatory subsections: + - _Construction._ Numbered algorithm steps, all symbols defined before use + - _Correctness._ Bidirectional: (⟹) forward direction, (⟸) backward direction + - _Solution extraction._ How to map target solution back to source +3. **Overhead table** — target size fields as functions of source size fields +4. **Worked example** — concrete small instance, full construction steps, solution verification + +Mathematical notation uses Typst math mode: `$V$`, `$E$`, `$arrow.r.double$`, `$overline(x)$`, etc. + +## Research Plan for Blocked Issues + +For each blocked reduction: + +1. **Search** for the original reference via the citation in Garey & Johnson +2. **Reconstruct** the correct algorithm from the paper or from first principles +3. **Verify** correctness with a hand-worked example in the note +4. **Resolve** the blocker: + - #379/#380: Clarify that the reduction operates on the decision variant; note model alignment needed + - #888: Research Gavril 1977a gadget construction for forcing path-tree solutions + - #822: Research the acyclic partition reduction from G&J or construct from first principles + - #892: Chain through #198 (VC→HC→HP); detail the HC→HP modification + - #894: Search for Yannakakis 1978b or reconstruct the gadget + - #890: Research the Garey-Johnson-Stockmeyer 1976 construction + +If a reference is unavailable, construct a novel reduction and clearly mark it as such. + +## Typst Setup + +- Standalone document (not importing from `reductions.typ`) +- Uses: `ctheorems` for theorem/proof environments, `cetz` if diagrams needed +- Page: A4, New Computer Modern 10pt +- Theorem numbering: `Theorem 2.1`, `Theorem 2.2`, etc. +- No dependency on `examples.json` or `reduction_graph.json` (standalone) +- Compile command: `typst compile docs/paper/proposed-reductions.typ docs/paper/proposed-reductions.pdf` + +## Quality Criteria + +Each reduction must satisfy: +1. **Math equations correct** — all formulas verified against source paper or hand-derivation +2. **Provable correctness** — both directions of the proof are rigorous, no hand-waving +3. **Algorithm clear** — detailed enough that a developer can implement `reduce_to()` and `extract_solution()` directly from the proof +4. **From math to code verifiable** — overhead expressions match the construction, worked example can be used as a test case + +## PR Structure + +- Branch: `feat/proposed-reductions-note` +- Files: + - `docs/paper/proposed-reductions.typ` (new) + - `docs/paper/proposed-reductions.pdf` (new, compiled) +- No code changes — this is a documentation-only PR +- Issue updates done via `gh issue edit` (not in the PR diff)