From 209dd3ccc7be1f309c171f01ecb2e3e9321952d1 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:26:47 -0400 Subject: [PATCH 1/9] doc(roadmap): record ASP framework follow-up --- ROADMAP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index e8aa6b0e1..2bdc7c2a8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -187,6 +187,8 @@ index links to canonical definitions rather than keeping a second set. ### Counting and modular classes +- The Another Solution Problem (ASP) framework: solution-preserving reductions with explicit + polynomial-time solution bijections, and applications to SAT and planar SAT. - Counting reductions and further oracle results preserving exact multiplicities; permanent #P-completeness; modular and counting hierarchies; the oracle-algorithm form of Stockmeyer's approximate counting. From 5b65c48394bf529138806b0cfe2d571b797998e8 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:26:47 -0400 Subject: [PATCH 2/9] feat(CNF): preserve model counts under occurrence splitting Part of #1144, split from the internally reviewed parsimonious planar SAT development. All six local validation gates and hygiene checks pass. No selected abstract changes. AI-assisted implementation and internal review with Codex gpt-6.1-sol; outside-fleet coordinator review remains required. --- Complexity.lean | 5 + .../Complexity/Problems/ThreeSATThree.lean | 9 ++ .../Problems/ThreeSATThree/Counting.lean | 75 ++++++++++ Complexity/Foundations/Data/List/Filter.lean | 136 ++++++++++++++++++ Complexity/Foundations/Data/List/Last.lean | 40 ++++++ Complexity/Foundations/Data/List/Range.lean | 36 +++++ .../Propositional/CNF/Compact/ModelCount.lean | 58 ++++++++ .../Logics/Propositional/CNF/Occurrence.lean | 6 + .../Propositional/CNF/ThreeOccurrence.lean | 91 ++++++++++-- ComplexityTests/ThreeSATThreeCounting.lean | 64 +++++++++ 10 files changed, 507 insertions(+), 13 deletions(-) create mode 100644 Complexity/Computability/Complexity/Problems/ThreeSATThree/Counting.lean create mode 100644 Complexity/Foundations/Data/List/Filter.lean create mode 100644 Complexity/Foundations/Data/List/Last.lean create mode 100644 Complexity/Foundations/Data/List/Range.lean create mode 100644 Complexity/Logics/Propositional/CNF/Compact/ModelCount.lean create mode 100644 ComplexityTests/ThreeSATThreeCounting.lean diff --git a/Complexity.lean b/Complexity.lean index e6c16467f..55ff7de36 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -828,6 +828,7 @@ public import Complexity.Computability.Complexity.Problems.TQBF.Closed public import Complexity.Computability.Complexity.Problems.TSP public import Complexity.Computability.Complexity.Problems.ThreeSAT public import Complexity.Computability.Complexity.Problems.ThreeSATThree +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Counting public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Size public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Syntax public import Complexity.Computability.Complexity.Problems.TwoSAT @@ -1886,10 +1887,13 @@ public import Complexity.Foundations.Data.List.BalancedFold public import Complexity.Foundations.Data.List.Basic public import Complexity.Foundations.Data.List.Counting public import Complexity.Foundations.Data.List.Equiv +public import Complexity.Foundations.Data.List.Filter public import Complexity.Foundations.Data.List.Fold +public import Complexity.Foundations.Data.List.Last public import Complexity.Foundations.Data.List.Lex public import Complexity.Foundations.Data.List.Lookup public import Complexity.Foundations.Data.List.OfFn +public import Complexity.Foundations.Data.List.Range public import Complexity.Foundations.Data.List.SelectDistinct public import Complexity.Foundations.Data.Matrix.Binary public import Complexity.Foundations.Data.Matrix.Permanent @@ -2080,6 +2084,7 @@ public import Complexity.Logics.FirstOrder.Tuple public import Complexity.Logics.Propositional.CNF public import Complexity.Logics.Propositional.CNF.Coding public import Complexity.Logics.Propositional.CNF.Compact +public import Complexity.Logics.Propositional.CNF.Compact.ModelCount public import Complexity.Logics.Propositional.CNF.Compact.Relabel public import Complexity.Logics.Propositional.CNF.ExactlyOne public import Complexity.Logics.Propositional.CNF.Gadgets diff --git a/Complexity/Computability/Complexity/Problems/ThreeSATThree.lean b/Complexity/Computability/Complexity/Problems/ThreeSATThree.lean index bfa06d5a0..b2cbc9f37 100644 --- a/Complexity/Computability/Complexity/Problems/ThreeSATThree.lean +++ b/Complexity/Computability/Complexity/Problems/ThreeSATThree.lean @@ -42,6 +42,7 @@ This endpoint imposes no planarity condition. ## Main results * `Cslib.Complexity.mem_ThreeSATThree_toThreeSATThree_iff`: correctness of the reduction. +* `Cslib.Complexity.modelCount_toThreeSATThree`: preservation of counts for width-three inputs. * `Cslib.Complexity.computableInPolyTime_toThreeSATThree`: native polynomial time under `encCNF`. * `Cslib.Complexity.ThreeSATThree_npComplete`: 3SAT-3 is NP-complete. @@ -149,6 +150,14 @@ theorem length_vars_toThreeSATThree (φ : CNF ℕ) (hφ : ∀ c ∈ φ, c.length have hg : φ.all (fun c => decide (c.length ≤ 3)) = true := by simpa using hφ simp [toThreeSATThree, hg, CNF.vars_relabel, CNF.length_vars_toThreeOccurrence] +/-- For a width-three input, occurrence splitting and injective numbering preserve the number of +satisfying assignments on occurring variables. -/ +theorem modelCount_toThreeSATThree (φ : CNF ℕ) (hφ : ∀ c ∈ φ, c.length ≤ 3) : + (toThreeSATThree φ).modelCount = φ.modelCount := by + have hg : φ.all (fun c => decide (c.length ≤ 3)) = true := by simpa using hφ + simp only [toThreeSATThree, hg, ↓reduceIte] + rw [CNF.modelCount_relabel ThreeSATThree.numberCopy_injective, CNF.modelCount_toThreeOccurrence] + /-- The total splitter is polynomial-time under binary `encCNF`. -/ @[fun_prop] theorem computableInPolyTime_toThreeSATThree : diff --git a/Complexity/Computability/Complexity/Problems/ThreeSATThree/Counting.lean b/Complexity/Computability/Complexity/Problems/ThreeSATThree/Counting.lean new file mode 100644 index 000000000..b6e9f2dbe --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/ThreeSATThree/Counting.lean @@ -0,0 +1,75 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.SharpP.Completeness +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Size +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Syntax + +/-! +# Parsimonious occurrence reduction for 3SAT + +The existing occurrence splitter preserves the number of satisfying assignments. Each source +variable has one value on every copy in its implication cycle, and numbering copies is injective. +The counting problems below count only occurring variables and return zero outside their syntax. +They reuse `CNF.modelCount` and the existing syntax guards rather than introducing new assignment +or satisfiability semantics. + +## Main definitions + +* `Cslib.Complexity.SharpThreeSAT`: model count for width-at-most-three CNF, zero otherwise. +* `Cslib.Complexity.SharpThreeSATThree`: model count with the additional three-occurrence bound. + +## Main results + +* `Cslib.Complexity.sharpThreeSATThree_toThreeSATThree`: exact counts for the total compiler. +* `Cslib.Complexity.parsimoniousReducible_SharpThreeSAT_SharpThreeSATThree`: the existing + polynomial-time compiler is a parsimonious reduction. + +## References + +The occurrence construction and its source credit are documented in `ThreeSATThree`. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Cslib.Logic.PL + +/-- Count satisfying assignments of width-at-most-three CNF; reject other syntax with count zero. +Only variables occurring in the formula are counted. -/ +def SharpThreeSAT (φ : CNF ℕ) : ℕ := + if φ.all (fun c => decide (c.length ≤ 3)) then φ.modelCount else 0 + +/-- Count satisfying assignments of width-three, occurrence-three CNF; reject other syntax with +count zero. Repetitions are literal positions, as in `ThreeSATThree.syntaxCheck`. -/ +def SharpThreeSATThree (φ : CNF ℕ) : ℕ := + if ThreeSATThree.syntaxCheck φ then φ.modelCount else 0 + +/-- The total occurrence compiler preserves the count, including rejected inputs. -/ +theorem sharpThreeSATThree_toThreeSATThree (φ : CNF ℕ) : + SharpThreeSATThree (toThreeSATThree φ) = SharpThreeSAT φ := by + have hout := (ThreeSATThree.syntaxCheck_iff (toThreeSATThree φ)).2 + ⟨width_toThreeSATThree φ, count_vars_toThreeSATThree_le φ⟩ + simp only [SharpThreeSATThree, hout, ↓reduceIte] + by_cases hw : φ.all (fun c => decide (c.length ≤ 3)) = true + · have hwidth : ∀ c ∈ φ, c.length ≤ 3 := by + simpa only [List.all_eq_true, decide_eq_true_eq] using hw + rw [modelCount_toThreeSATThree φ hwidth] + simp only [SharpThreeSAT, hw, ↓reduceIte] + · simp only [SharpThreeSAT, toThreeSATThree, hw, Bool.false_eq_true, ite_false] + decide + +/-- The already-certified polynomial-time 3SAT-to-3SAT-3 compiler is parsimonious. -/ +theorem parsimoniousReducible_SharpThreeSAT_SharpThreeSATThree : + ParsimoniousReducible encCNF encCNF SharpThreeSAT SharpThreeSATThree := + ⟨toThreeSATThree, computableInPolyTime_toThreeSATThree, + fun φ => (sharpThreeSATThree_toThreeSATThree φ).symm⟩ + +end Cslib.Complexity diff --git a/Complexity/Foundations/Data/List/Filter.lean b/Complexity/Foundations/Data/List/Filter.lean new file mode 100644 index 000000000..1af02195a --- /dev/null +++ b/Complexity/Foundations/Data/List/Filter.lean @@ -0,0 +1,136 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +import Mathlib.Data.List.GetD + +/-! +# Indexed multiplicity under list membership filtering + +These statements count positions, so repeated equal lists still contribute separately. +They are generalized from the canonical incidence-path occupancy proofs. + +## Main results + +* `List.exists_pair_indices_of_two_le_filter_contains`. +* `List.two_le_filter_contains_of_indices`. +* `List.exists_other_index_of_two_le_filter_contains`. +-/ + +@[expose] public section + +namespace List + +/-- Two filtered memberships occur at distinct indices, even for repeated lists. -/ +theorem exists_pair_indices_of_two_le_filter_contains {α : Type*} [DecidableEq α] + (rs : List (List α)) (p : α) + (hlen : 2 ≤ (rs.filter fun r => r.contains p).length) : + ∃ (i j : ℕ) (hi : i < rs.length) (hj : j < rs.length), + i ≠ j ∧ p ∈ rs[i] ∧ p ∈ rs[j] := by + induction rs with + | nil => simp at hlen + | cons r rs ih => + by_cases hp : p ∈ r + · have hne : (rs.filter fun r => r.contains p) ≠ [] := by + intro h + have hf : ((r :: rs).filter fun r => r.contains p) = [r] := by + have hpred : r.contains p = true := by simpa using hp + simp only [List.filter_cons, hpred, ↓reduceIte, h] + rw [hf] at hlen + simp at hlen + cases hf : rs.filter (fun r => r.contains p) with + | nil => exact False.elim (hne hf) + | cons t ts => + have ht : t ∈ rs.filter (fun r => r.contains p) := by rw [hf]; simp + obtain ⟨htmem, htp⟩ := List.mem_filter.mp ht + obtain ⟨j, hj, rfl⟩ := List.mem_iff_getElem.mp htmem + exact ⟨0, j + 1, by simp, by simpa using hj, by omega, hp, by simpa using htp⟩ + · have hf : ((r :: rs).filter fun r => r.contains p) = rs.filter (fun r => r.contains p) := by + simp [hp] + rw [hf] at hlen + obtain ⟨i, j, hi, hj, hij, hpi, hpj⟩ := ih hlen + exact ⟨i + 1, j + 1, by simpa using hi, by simpa using hj, by omega, hpi, hpj⟩ + +/-- Two distinct indices witnessing membership give at least two filtered entries. -/ +theorem two_le_filter_contains_of_indices {α : Type*} [DecidableEq α] + (rs : List (List α)) (p : α) {i j : ℕ} + (hi : i < rs.length) (hj : j < rs.length) (hij : i ≠ j) + (hpi : p ∈ rs[i]) (hpj : p ∈ rs[j]) : + 2 ≤ (rs.filter fun r => r.contains p).length := by + induction rs generalizing i j with + | nil => simp at hi + | cons r rs ih => + cases i with + | zero => + cases j with + | zero => exact False.elim (hij rfl) + | succ j => + have hj' : j < rs.length := by simpa using hj + have hm : rs[j] ∈ rs.filter (fun r => r.contains p) := + List.mem_filter.mpr ⟨List.getElem_mem (by simpa using hj), + List.contains_iff_mem.mpr hpj⟩ + obtain ⟨k, hk, _⟩ := List.mem_iff_getElem.mp hm + have hphead : p ∈ r := hpi + simp only [List.filter_cons, List.contains_iff_mem.mpr hphead, + ite_true, List.length_cons] + omega + | succ i => + cases j with + | zero => + have hi' : i < rs.length := by simpa using hi + have hm : rs[i] ∈ rs.filter (fun r => r.contains p) := + List.mem_filter.mpr ⟨List.getElem_mem (by simpa using hi), + List.contains_iff_mem.mpr hpi⟩ + obtain ⟨k, hk, _⟩ := List.mem_iff_getElem.mp hm + have hphead : p ∈ r := hpj + simp only [List.filter_cons, List.contains_iff_mem.mpr hphead, + ite_true, List.length_cons] + omega + | succ j => + have ht := ih (by simpa using hi) (by simpa using hj) (by omega) hpi hpj + rw [List.filter_cons] + split_ifs + · simp only [List.length_cons] + omega + · exact ht + +/-- At least two filtered memberships give an index different from any given index. -/ +theorem exists_other_index_of_two_le_filter_contains {α : Type*} [DecidableEq α] + (rs : List (List α)) (p : α) (i : ℕ) (hi : i < rs.length) + (hcount : 2 ≤ (rs.filter fun r => r.contains p).length) : + ∃ j, ∃ hj : j < rs.length, j ≠ i ∧ p ∈ rs[j] := by + induction rs generalizing i with + | nil => simp at hi + | cons r rs ih => + cases i with + | zero => + have hpos : 0 < (rs.filter fun r => r.contains p).length := by + rw [List.filter_cons] at hcount + split_ifs at hcount + · simp only [List.length_cons] at hcount + omega + · omega + obtain ⟨s, hs⟩ := List.exists_mem_of_length_pos hpos + have hm := List.mem_filter.mp hs + obtain ⟨j, hj, hsj⟩ := List.mem_iff_getElem.mp hm.1 + refine ⟨j + 1, by simp only [List.length_cons]; omega, by omega, ?_⟩ + simp only [List.getElem_cons_succ] + rw [hsj] + exact List.contains_iff_mem.mp hm.2 + | succ i => + by_cases hmem : r.contains p = true + · exact ⟨0, by simp, by omega, by simpa using List.contains_iff_mem.mp hmem⟩ + · have ht : 2 ≤ (rs.filter fun r => r.contains p).length := by + have hp : p ∉ r := fun hp => hmem (List.contains_iff_mem.mpr hp) + simpa [List.filter_cons, hp] using hcount + obtain ⟨j, hj, hji, hp⟩ := ih i (by simp only [List.length_cons] at hi; omega) ht + exact ⟨j + 1, by simp only [List.length_cons]; omega, by omega, by simpa using hp⟩ + + +end List diff --git a/Complexity/Foundations/Data/List/Last.lean b/Complexity/Foundations/Data/List/Last.lean new file mode 100644 index 000000000..b0781335c --- /dev/null +++ b/Complexity/Foundations/Data/List/Last.lean @@ -0,0 +1,40 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +import Mathlib.Data.List.GetD + +/-! +# Defaulted last-element lookup + +The default is unused for a nonempty list. The proof is moved from the path semantics. + +## Main results + +* `List.getLastD_eq_getElem`. +-/ + +@[expose] public section + +namespace List + +/-- A nonempty list's defaulted last lookup is its final indexed element. -/ +theorem getLastD_eq_getElem {α : Type*} (xs : List α) (d : α) (hne : xs ≠ []) : + xs.getLastD d = xs[xs.length - 1]'(by cases xs <;> simp_all) := by + induction xs generalizing d with + | nil => exact False.elim (hne rfl) + | cons a xs ih => + cases xs with + | nil => rfl + | cons b xs => + rw [List.getLastD_cons, ih a (List.cons_ne_nil _ _)] + simp only [List.length_cons, Nat.add_sub_cancel, List.getElem_cons_succ] + + +end List diff --git a/Complexity/Foundations/Data/List/Range.lean b/Complexity/Foundations/Data/List/Range.lean new file mode 100644 index 000000000..d2770bd73 --- /dev/null +++ b/Complexity/Foundations/Data/List/Range.lean @@ -0,0 +1,36 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# First indices in natural-number ranges + +This index identity is shared by source endpoint and path geometry certificates. + +## Main results + +* `List.idxOf_range_of_lt`. +-/ + +@[expose] public section + +namespace List + +/-- An in-range natural number occurs at its own position in `range`. -/ +theorem idxOf_range_of_lt {i n : ℕ} (hi : i < n) : (List.range n).idxOf i = i := by + have h := (List.nodup_range : (List.range n).Nodup).idxOf_getElem i + (by simpa only [List.length_range] using hi) + rw [List.getElem_range] at h + exact h + + +end List diff --git a/Complexity/Logics/Propositional/CNF/Compact/ModelCount.lean b/Complexity/Logics/Propositional/CNF/Compact/ModelCount.lean new file mode 100644 index 000000000..83e40eca3 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/Compact/ModelCount.lean @@ -0,0 +1,58 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.CNF.Compact +public import Complexity.Logics.Propositional.CNF.ModelCount +import Mathlib.Data.List.GetD + +/-! +# Model counts under compact variable numbering + +First-occurrence numbering preserves the number of satisfying assignments on the variables +occurring in a formula. The variable type may be infinite or empty. + +## Main results + +* `Cslib.Logic.PL.CNF.modelCount_compact`: compaction preserves the model count. + +## References + +This is an elementary counting property of the existing compaction construction. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF + +variable {V : Type*} [DecidableEq V] + +/-- First-occurrence numbering preserves the number of satisfying assignments, including for +empty formulas, empty clauses, and repeated literal occurrences. -/ +theorem modelCount_compact (φ : CNF V) : φ.compact.modelCount = φ.modelCount := by + refine modelCount_eq_card_filter (fun σ (v : φ.vars.toFinset) => σ (φ.vars.idxOf (v : V))) + (fun τ => φ.eval (φ.extendAssignment τ) = true) (fun σ τ _ _ => ?_) fun τ => ?_ + · rw [compact, vars_relabel, List.forall_mem_map, funext_iff, Subtype.forall] + simp only [List.mem_toFinset] + · constructor + · intro hτ + let σ : ℕ → Bool := fun i => φ.vars[i]?.elim false (φ.extendAssignment τ) + have he : ∀ v ∈ φ.vars, σ (φ.vars.idxOf v) = φ.extendAssignment τ v := by + intro v hv + simp [σ, List.getElem?_idxOf hv] + refine ⟨σ, ?_, funext fun v => ?_⟩ + · rw [compact, eval_relabel] + exact (eval_congr he).trans hτ + · exact (he v (List.mem_toFinset.1 v.2)).trans + (extendAssignment_of_mem _ (List.mem_toFinset.1 v.2)) + · rintro ⟨σ, hσ, rfl⟩ + rw [compact, eval_relabel] at hσ + rw [← hσ] + exact eval_extendAssignment_restrict φ (σ ∘ φ.vars.idxOf) + +end Cslib.Logic.PL.CNF diff --git a/Complexity/Logics/Propositional/CNF/Occurrence.lean b/Complexity/Logics/Propositional/CNF/Occurrence.lean index 8d299e11d..17ba7e0ba 100644 --- a/Complexity/Logics/Propositional/CNF/Occurrence.lean +++ b/Complexity/Logics/Propositional/CNF/Occurrence.lean @@ -24,6 +24,12 @@ The proof follows the indexed-copy uniqueness argument in `CNF.ThreeOccurrence`. namespace Cslib.Logic.PL +/-- The variable named by an indexed literal occurrence occurs in the formula. -/ +theorem variable_mem_of_mem_occurrences {V : Type*} {φ : CNF V} {o : Occurrence V} + (ho : o ∈ occurrences φ) : o.2.2.1 ∈ φ.vars := by + rw [← map_occurrences] + exact List.mem_map_of_mem ho + /-- Clause and literal indices distinguish every source occurrence, including repeated literals. -/ theorem occurrences_nodup {V : Type*} (φ : CNF V) : (occurrences φ).Nodup := by rw [occurrences, List.nodup_flatMap] diff --git a/Complexity/Logics/Propositional/CNF/ThreeOccurrence.lean b/Complexity/Logics/Propositional/CNF/ThreeOccurrence.lean index edd3bb974..9c714a4f0 100644 --- a/Complexity/Logics/Propositional/CNF/ThreeOccurrence.lean +++ b/Complexity/Logics/Propositional/CNF/ThreeOccurrence.lean @@ -35,7 +35,9 @@ This construction uses ordinary CNF formulas and Boolean assignments. ## Main results +* `Cslib.Logic.PL.CNF.eval_toThreeOccurrence_iff`: correspondence of satisfying assignments. * `Cslib.Logic.PL.CNF.satisfiable_toThreeOccurrence`: equisatisfiability. +* `Cslib.Logic.PL.CNF.modelCount_toThreeOccurrence`: preservation of satisfying-assignment counts. * `Cslib.Logic.PL.CNF.width_toThreeOccurrence`: preservation of width bounds of at least two. * `Cslib.Logic.PL.CNF.count_vars_toThreeOccurrence_le`: at most three literal occurrences. * `Cslib.Logic.PL.CNF.length_toThreeOccurrence`, @@ -114,11 +116,14 @@ theorem copies_agree [DecidableEq V] {φ : CNF V} {τ : V × ℕ × ℕ → Bool · simp [occurrenceCopies, hx] · simp [occurrenceCopies, hy, he] -/-- Occurrence splitting preserves satisfiability, including empty clauses and formulas. -/ -theorem satisfiable_toThreeOccurrence [DecidableEq V] (φ : CNF V) : - φ.toThreeOccurrence.Satisfiable ↔ φ.Satisfiable := by +/-- An assignment satisfies the split formula exactly when its occurring copies agree with a +satisfying assignment of the original formula. Together with `copies_agree`, this retains the +assignment correspondence, rather than just equisatisfiability. -/ +theorem eval_toThreeOccurrence_iff [DecidableEq V] (φ : CNF V) (τ : V × ℕ × ℕ → Bool) : + φ.toThreeOccurrence.eval τ = true ↔ + ∃ σ : V → Bool, φ.eval σ = true ∧ ∀ x ∈ φ.occurrenceClauses.vars, σ x.1 = τ x := by constructor - · rintro ⟨τ, hτ⟩ + · intro hτ classical let σ : V → Bool := fun v => if h : ∃ x ∈ φ.occurrenceClauses.vars, x.1 = v then τ (Classical.choose h) else false @@ -127,21 +132,33 @@ theorem satisfiable_toThreeOccurrence [DecidableEq V] (φ : CNF V) : have hex : ∃ y ∈ φ.occurrenceClauses.vars, y.1 = x.1 := ⟨x, hx, rfl⟩ simp only [σ, dite_eq_left hex] exact copies_agree hτ (Classical.choose_spec hex).1 hx (Classical.choose_spec hex).2 - refine ⟨σ, ?_⟩ + refine ⟨σ, ?_, he⟩ rw [← eval_occurrenceClauses] have ht := hτ simp only [toThreeOccurrence, eval_append, Bool.and_eq_true] at ht exact (eval_congr he).trans ht.1 - · rintro ⟨σ, hσ⟩ - refine ⟨σ ∘ Prod.fst, ?_⟩ - rw [toThreeOccurrence, eval_append, Bool.and_eq_true, eval_occurrenceClauses] - refine ⟨hσ, (eval_flatMap_eq_true_iff _ _ _).2 fun v _ => ?_⟩ + · rintro ⟨σ, hσ, he⟩ + rw [toThreeOccurrence, eval_append, Bool.and_eq_true] + refine ⟨(eval_congr he).symm.trans ((eval_occurrenceClauses φ σ).trans hσ), + (eval_flatMap_eq_true_iff _ _ _).2 fun v _ => ?_⟩ rw [eval_implicationCycle] intro x hx y hy - have hx := (List.mem_filter.1 hx).2 - have hy := (List.mem_filter.1 hy).2 - simp only [beq_iff_eq] at hx hy - simp [hx, hy] + obtain ⟨hx, hxv⟩ := List.mem_filter.1 hx + obtain ⟨hy, hyv⟩ := List.mem_filter.1 hy + simp only [beq_iff_eq] at hxv hyv + calc τ x = σ x.1 := (he x hx).symm + _ = σ y.1 := congrArg σ (hxv.trans hyv.symm) + _ = τ y := he y hy + +/-- Occurrence splitting preserves satisfiability, including empty clauses and formulas. -/ +theorem satisfiable_toThreeOccurrence [DecidableEq V] (φ : CNF V) : + φ.toThreeOccurrence.Satisfiable ↔ φ.Satisfiable := by + constructor + · rintro ⟨τ, hτ⟩ + obtain ⟨σ, hσ, _⟩ := (eval_toThreeOccurrence_iff φ τ).1 hτ + exact ⟨σ, hσ⟩ + · rintro ⟨σ, hσ⟩ + exact ⟨σ ∘ Prod.fst, (eval_toThreeOccurrence_iff φ _).2 ⟨σ, hσ, fun _ _ => rfl⟩⟩ /-- Cycles are binary, so a width bound of at least two is preserved. -/ theorem width_toThreeOccurrence [DecidableEq V] {φ : CNF V} {k : ℕ} (hk : 2 ≤ k) @@ -271,4 +288,52 @@ theorem numVars_toThreeOccurrence [DecidableEq V] (φ : CNF V) : rw [numVars, he, List.toFinset_card_of_nodup (nodup_vars_occurrenceClauses φ)] simpa using congrArg List.length (map_fst_vars_occurrenceClauses φ) +/-- Occurrence splitting preserves the number of satisfying assignments on occurring variables, +including empty formulas, empty clauses, and repeated literals. -/ +theorem modelCount_toThreeOccurrence [DecidableEq V] (φ : CNF V) : + φ.toThreeOccurrence.modelCount = φ.modelCount := by + classical + have hmem (x : V × ℕ × ℕ) : + x ∈ φ.toThreeOccurrence.vars ↔ x ∈ φ.occurrenceClauses.vars := by + simp only [← List.count_pos_iff, count_vars_toThreeOccurrence] + omega + have hfst {x : V × ℕ × ℕ} (hx : x ∈ φ.occurrenceClauses.vars) : x.1 ∈ φ.vars := by + rw [← map_fst_vars_occurrenceClauses] + exact List.mem_map.2 ⟨x, hx, rfl⟩ + have hex (v : φ.vars.toFinset) : ∃ x ∈ φ.occurrenceClauses.vars, x.1 = v := by + apply List.mem_map.1 + simpa only [map_fst_vars_occurrenceClauses] using List.mem_toFinset.1 v.2 + let copy (v : φ.vars.toFinset) : V × ℕ × ℕ := Classical.choose (hex v) + have hcopy (v : φ.vars.toFinset) : copy v ∈ φ.occurrenceClauses.vars ∧ (copy v).1 = v := + Classical.choose_spec (hex v) + let f (τ : V × ℕ × ℕ → Bool) (v : φ.vars.toFinset) : Bool := τ (copy v) + refine modelCount_eq_card_filter f (fun τ => φ.eval (φ.extendAssignment τ) = true) + (fun σ τ hσ hτ => ?_) fun τ => ?_ + · constructor + · intro he x hx + have hx := (hmem x).1 hx + let v : φ.vars.toFinset := ⟨x.1, List.mem_toFinset.2 (hfst hx)⟩ + calc σ x = σ (copy v) := copies_agree hσ hx (hcopy v).1 (hcopy v).2.symm + _ = τ (copy v) := congrFun he v + _ = τ x := copies_agree hτ (hcopy v).1 hx (hcopy v).2 + · intro he + exact funext fun v => he (copy v) ((hmem _).2 (hcopy v).1) + · constructor + · intro hτ + refine ⟨φ.extendAssignment τ ∘ Prod.fst, ?_, funext fun v => ?_⟩ + · exact (eval_toThreeOccurrence_iff φ _).2 ⟨φ.extendAssignment τ, hτ, fun _ _ => rfl⟩ + · change φ.extendAssignment τ (copy v).1 = τ v + rw [(hcopy v).2, extendAssignment_of_mem _ (List.mem_toFinset.1 v.2)] + · rintro ⟨σ, hσ, rfl⟩ + rw [← eval_occurrenceClauses] + have he : ∀ x ∈ φ.occurrenceClauses.vars, + (φ.extendAssignment (f σ) ∘ Prod.fst) x = σ x := by + intro x hx + change φ.extendAssignment (f σ) x.1 = σ x + rw [extendAssignment_of_mem _ (hfst hx)] + exact copies_agree hσ (hcopy _).1 hx (hcopy _).2 + have ht := hσ + simp only [toThreeOccurrence, eval_append, Bool.and_eq_true] at ht + exact (eval_congr he).trans ht.1 + end Cslib.Logic.PL.CNF diff --git a/ComplexityTests/ThreeSATThreeCounting.lean b/ComplexityTests/ThreeSATThreeCounting.lean new file mode 100644 index 000000000..270dafc50 --- /dev/null +++ b/ComplexityTests/ThreeSATThreeCounting.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Computability.Complexity.Problems.ThreeSATThree +import Complexity.Computability.Complexity.Problems.ThreeSATThree.Counting + +/-! # Exact counts under occurrence splitting and numbering -/ + +open Cslib.Logic.PL Cslib.Complexity + +/-- The empty formula has one assignment on its empty variable set. -/ +example : (toThreeSATThree []).modelCount = 1 := by + rw [modelCount_toThreeSATThree _ (by simp)] + decide + +/-- An empty clause has no satisfying assignment. -/ +example : (toThreeSATThree [[]]).modelCount = 0 := by + rw [modelCount_toThreeSATThree _ (by simp)] + decide + +/-- Repeated literals within a clause do not multiply the satisfying-assignment count. -/ +example : (toThreeSATThree [[(0, true), (0, true), (0, true)]]).modelCount = 1 := by + rw [modelCount_toThreeSATThree _ (by simp)] + decide + +/-- A singleton cycle introduces no additional choice for a forced variable. -/ +example : (toThreeSATThree [[(7, true)]]).modelCount = 1 := by + rw [modelCount_toThreeSATThree _ (by simp)] + decide + +/-- Several occurrences per variable preserve all three satisfying assignments of a disjunction. -/ +example : (toThreeSATThree + [[(0, true), (1, true)], [(0, true), (1, true)]]).modelCount = 3 := by + rw [modelCount_toThreeSATThree _ (by simp)] + decide + +/-- The unnumbered splitter preserves counts even beyond width three. -/ +example : (CNF.toThreeOccurrence + [[(0, true), (0, false), (0, true), (0, false)]]).modelCount = 2 := by + rw [CNF.modelCount_toThreeOccurrence] + decide + +/-- The numbered reduction rejects a width-four input, so its width hypothesis is necessary. -/ +example : (toThreeSATThree + [[(0, true), (0, false), (0, true), (0, false)]]).modelCount = 0 := by + decide + +/-- A satisfiable width-four formula has raw models but is rejected by the 3SAT count. -/ +example : SharpThreeSAT [[(0, true), (0, false), (0, true), (0, false)]] = 0 := by decide + +/-- The source count accepts width three even when the occurrence bound fails. -/ +example : SharpThreeSAT [[(0, true), (0, true), (0, true)], [(0, true)]] = 1 := by decide + +/-- The target count rejects the same formula for its fourth literal occurrence. -/ +example : SharpThreeSATThree [[(0, true), (0, true), (0, true)], [(0, true)]] = 0 := by decide + +/-- The actual compiler preserves that source count after repairing the occurrence bound. -/ +example : SharpThreeSATThree + (toThreeSATThree [[(0, true), (0, true), (0, true)], [(0, true)]]) = 1 := by + rw [sharpThreeSATThree_toThreeSATThree] + decide From 2ef95572326f53e35e84cae23f45f7db1f2bf726 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:29:46 -0400 Subject: [PATCH 3/9] doc(references): cite parsimonious planar SAT --- references.bib | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/references.bib b/references.bib index 422c12241..98a7ca2bd 100644 --- a/references.bib +++ b/references.bib @@ -2029,6 +2029,17 @@ @article{Huang2019 doi = {10.4007/annals.2019.190.3.6} } +@article{HuntMaratheRadhakrishnanStearns1998, + author = {Hunt III, Harry B. and Marathe, Madhav V. and Radhakrishnan, Venkatesh and Stearns, Richard E.}, + title = {The Complexity of Planar Counting Problems}, + journal = {SIAM Journal on Computing}, + volume = {27}, + number = {4}, + pages = {1142--1167}, + year = {1998}, + doi = {10.1137/S0097539793304601} +} + @inproceedings{Ilango2020, author = {Ilango, Rahul}, title = {Approaching {MCSP} from Above and Below: Hardness for a Conditional Variant and {$\mathsf{AC}^0[p]$}}, From 82860094aa1363005e1674471d2a342dc429dbca Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:29:46 -0400 Subject: [PATCH 4/9] feat(planar-SAT): certify paired wires and unique gadget extensions Part of #1144, split from the internally reviewed parsimonious planar SAT development. All six local validation gates and hygiene checks pass. No selected abstract changes. AI-assisted implementation and internal review with Codex gpt-6.1-sol; outside-fleet coordinator review remains required. --- Complexity.lean | 7 + .../Problems/SAT/Planar/OrdinaryRing.lean | 269 +++++++++++++++++ .../Geometry/Orthogonal/PairedBoundary.lean | 59 ++++ .../Geometry/Orthogonal/PairedPorts.lean | 153 ++++++++++ .../Geometry/Orthogonal/PairedSpokes.lean | 96 ++++++ .../CNF/Planar/CrossoverCounting.lean | 131 ++++++++ .../CNF/Planar/PairedCrossover.lean | 283 ++++++++++++++++++ .../Propositional/CNF/Planar/PairedWire.lean | 188 ++++++++++++ ComplexityTests/PairedCrossover.lean | 39 +++ ComplexityTests/PairedWire.lean | 56 ++++ ComplexityTests/PlanarCrossoverCounting.lean | 50 ++++ 11 files changed, 1331 insertions(+) create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/OrdinaryRing.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/PairedBoundary.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/PairedPorts.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/PairedSpokes.lean create mode 100644 Complexity/Logics/Propositional/CNF/Planar/CrossoverCounting.lean create mode 100644 Complexity/Logics/Propositional/CNF/Planar/PairedCrossover.lean create mode 100644 Complexity/Logics/Propositional/CNF/Planar/PairedWire.lean create mode 100644 ComplexityTests/PairedCrossover.lean create mode 100644 ComplexityTests/PairedWire.lean create mode 100644 ComplexityTests/PlanarCrossoverCounting.lean diff --git a/Complexity.lean b/Complexity.lean index 55ff7de36..719aee534 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -808,6 +808,7 @@ public import Complexity.Computability.Complexity.Problems.SAT.Planar.CrossoverL public import Complexity.Computability.Complexity.Problems.SAT.Planar.CrossoverLayoutData public import Complexity.Computability.Complexity.Problems.SAT.Planar.Geometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Occurrence +public import Complexity.Computability.Complexity.Problems.SAT.Planar.OrdinaryRing public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths.Occupancy public import Complexity.Computability.Complexity.Problems.SAT.Planar.PolyTime @@ -1951,6 +1952,9 @@ public import Complexity.Foundations.Geometry.Orthogonal.Endpoint public import Complexity.Foundations.Geometry.Orthogonal.Layout public import Complexity.Foundations.Geometry.Orthogonal.Macrocell public import Complexity.Foundations.Geometry.Orthogonal.Neighborhood +public import Complexity.Foundations.Geometry.Orthogonal.PairedBoundary +public import Complexity.Foundations.Geometry.Orthogonal.PairedPorts +public import Complexity.Foundations.Geometry.Orthogonal.PairedSpokes public import Complexity.Foundations.Geometry.Orthogonal.Separation public import Complexity.Foundations.Geometry.Orthogonal.Simple public import Complexity.Foundations.Geometry.Orthogonal.Subdivision @@ -2094,8 +2098,11 @@ public import Complexity.Logics.Propositional.CNF.ModelCount public import Complexity.Logics.Propositional.CNF.Occurrence public import Complexity.Logics.Propositional.CNF.Parity public import Complexity.Logics.Propositional.CNF.Planar.Crossover +public import Complexity.Logics.Propositional.CNF.Planar.CrossoverCounting public import Complexity.Logics.Propositional.CNF.Planar.CrossoverSplit public import Complexity.Logics.Propositional.CNF.Planar.DirectedWire +public import Complexity.Logics.Propositional.CNF.Planar.PairedCrossover +public import Complexity.Logics.Propositional.CNF.Planar.PairedWire public import Complexity.Logics.Propositional.CNF.PolyTime public import Complexity.Logics.Propositional.CNF.ThreeCNF public import Complexity.Logics.Propositional.CNF.ThreeCNF.PolyTime diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/OrdinaryRing.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/OrdinaryRing.lean new file mode 100644 index 000000000..7a496be62 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/OrdinaryRing.lean @@ -0,0 +1,269 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Geometry.Orthogonal.PairedPorts +public import Complexity.Logics.Propositional.CNF.ImplicationCycle +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum +import Mathlib.Tactic.SplitIfs + +/-! +# An eight-port ordinary signal ring + +Eight binary implications follow the perimeter of `[20,220]²`, forcing all eight +variables to agree. Every variable has two internal literal occurrences, leaving +one external occurrence for its paired access. Unused accesses introduce no freedom. +The sixteen incidence routes are straight and have total Manhattan length 800. + +## Main definitions + +* `Variable`, `cyclicOrder`, `formula`: the fixed implication ring. +* `variablePosition`, `clausePositions`, `incidenceRoutes`: its explicit drawing. + +## Main results + +* `eval_formula_iff`, `eval_constant`: all ports carry one freely chosen bit. +* `internal_occurrences`: exactly two internal literal occurrences per port. + +## References + +This fixed template is original and reuses `CNF.implicationCycle`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.OrdinaryRing + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- One variable for each side and lane of the common paired-port interface. -/ +abbrev Variable := Fin 4 × Fin 2 + +/-- Cyclic geometric order, traversing the inner square clockwise. -/ +def cyclicOrder : List Variable := + [(0, 0), (0, 1), (3, 0), (3, 1), (1, 1), (1, 0), (2, 1), (2, 0)] + +/-- The eight directed implications closing the signal ring. -/ +def formula : CNF Variable := implicationCycle cyclicOrder + +/-- The eight common inner access positions. -/ +def variablePosition (v : Variable) : Point := PairedPorts.variablePosition v.1 v.2 + +/-- Clause positions in precisely the order of the eight implications. -/ +def clausePositions : List Point := + [(20, 120), (20, 220), (120, 220), (220, 220), + (220, 120), (220, 20), (120, 20), (20, 20)] + +/-- Indexed clause position, with an irrelevant out-of-range default. -/ +def clausePosition (i : ℕ) : Point := clausePositions.getD i (0, 0) + +/-- The sixteen clause-to-variable incidences, in clause and literal order. -/ +def incidenceRoutes : List (ℕ × Variable × Route) := + [(0, (0, 0), [(20, 120), (20, 110)]), + (0, (0, 1), [(20, 120), (20, 130)]), + (1, (0, 1), [(20, 220), (20, 130)]), + (1, (3, 0), [(20, 220), (110, 220)]), + (2, (3, 0), [(120, 220), (110, 220)]), + (2, (3, 1), [(120, 220), (130, 220)]), + (3, (3, 1), [(220, 220), (130, 220)]), + (3, (1, 1), [(220, 220), (220, 130)]), + (4, (1, 1), [(220, 120), (220, 130)]), + (4, (1, 0), [(220, 120), (220, 110)]), + (5, (1, 0), [(220, 20), (220, 110)]), + (5, (2, 1), [(220, 20), (130, 20)]), + (6, (2, 1), [(120, 20), (130, 20)]), + (6, (2, 0), [(120, 20), (110, 20)]), + (7, (2, 0), [(20, 20), (110, 20)]), + (7, (0, 0), [(20, 20), (20, 110)])] + +/-- Common graph endpoints are the only permitted contacts between distinct incidences. -/ +def commonEndpoints (e f : ℕ × Variable × Route) : List Point := + (if e.1 = f.1 then [clausePosition e.1] else []) ++ + (if e.2.1 = f.2.1 then [variablePosition e.2.1] else []) + +/-- All eight port variables occur in the cycle. -/ +theorem mem_cyclicOrder : ∀ v : Variable, v ∈ cyclicOrder := by decide +kernel + +/-- The ring holds exactly when every port carries the value of the first port. -/ +theorem eval_formula_iff (σ : Variable → Bool) : + formula.eval σ = true ↔ ∀ v, σ v = σ (0, 0) := by + rw [formula, eval_implicationCycle] + constructor + · intro h v + exact h v (mem_cyclicOrder v) (0, 0) (mem_cyclicOrder (0, 0)) + · intro h x _ y _ + exact (h x).trans (h y).symm + +/-- Either constant assignment satisfies the ring. -/ +theorem eval_constant (b : Bool) : formula.eval (fun _ => b) = true := + (eval_formula_iff _).2 fun _ => rfl + +/-- Every port has exactly two internal literal occurrences. -/ +theorem internal_occurrences : ∀ v : Variable, formula.vars.count v = 2 := by decide +kernel + +/-- The formula has eight clauses, each of width two. -/ +theorem formula_sizes : formula.length = 8 ∧ ∀ c ∈ formula, c.length = 2 := by decide +kernel + +/-- Every variable is actually used, including variables with no external access in a client. -/ +theorem variables_used : ∀ v : Variable, v ∈ formula.vars := by decide +kernel + +private instance (r : Route) : Decidable (IsOrthogonal r) := + inferInstanceAs (Decidable (∀ e ∈ r.consecutivePairs, AxisAligned e.1 e.2)) + +/-- All literal incidences occur exactly once, in formula order. -/ +theorem incidenceRoutes_cover : + incidenceRoutes.map (fun e => (e.1, e.2.1)) = + formula.zipIdx.flatMap (fun ci => ci.1.map fun l => (ci.2, l.1)) := by decide +kernel + +/-- There are eight clause positions and sixteen incidence routes. -/ +theorem drawing_sizes : clausePositions.length = 8 ∧ incidenceRoutes.length = 16 := by + decide +kernel + +/-- Inner positions identify the eight variables. -/ +theorem variablePosition_injective : Function.Injective variablePosition := + PairedPorts.variablePosition_injective + +/-- Clause positions are distinct. -/ +theorem clausePositions_nodup : clausePositions.Nodup := by decide +kernel + +/-- Variables and clauses have different positions. -/ +theorem variablePosition_not_mem_clauses : + ∀ v : Variable, variablePosition v ∉ clausePositions := by decide +kernel + +/-- The sixteen labeled incidences are distinct. -/ +theorem incidenceEndpoints_nodup : + (incidenceRoutes.map fun e => (e.1, e.2.1)).Nodup := by decide +kernel + +/-- Every incidence has its prescribed clause and variable endpoints. -/ +theorem incidence_endpoints : ∀ e ∈ incidenceRoutes, + e.1 < 8 ∧ e.2.2.head? = some (clausePosition e.1) ∧ + e.2.2.getLast? = some (variablePosition e.2.1) := by decide +kernel + +/-- All sixteen incidence routes are straight segments. -/ +theorem incidence_pair : ∀ e ∈ incidenceRoutes, + e.2.2 = [clausePosition e.1, variablePosition e.2.1] := by decide +kernel + +/-- Every incidence is properly orthogonal. -/ +theorem incidence_orthogonal : ∀ e ∈ incidenceRoutes, IsOrthogonal e.2.2 := by decide +kernel + +/-- Listed incidence vertices are distinct within each route. -/ +theorem incidence_points_nodup : ∀ e ∈ incidenceRoutes, e.2.2.Nodup := by decide +kernel + +/-- Every incidence is continuously simple. -/ +theorem incidence_isSimple {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) : + IsSimple e.2.2 := by + refine ⟨incidence_points_nodup e he, ?_⟩ + intro a ha b hb hne + rw [incidence_pair e he] at ha hb + have ha' : a = (clausePosition e.1, variablePosition e.2.1) := by + simpa only [List.consecutivePairs, List.tail_cons, List.tail_nil, List.zip_cons_cons, + List.zip_nil_right, List.mem_singleton] using ha + have hb' : b = (clausePosition e.1, variablePosition e.2.1) := by + simpa only [List.consecutivePairs, List.tail_cons, List.tail_nil, List.zip_cons_cons, + List.zip_nil_right, List.mem_singleton] using hb + exact False.elim (hne (ha'.trans hb'.symm)) + +/-- All listed core points lie in `[20,220]²`. -/ +theorem core_bounds : ∀ e ∈ incidenceRoutes, ∀ p ∈ e.2.2, + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := by decide +kernel + +/-- Every variable lies in the inner core square. -/ +theorem variable_bounds : ∀ v : Variable, + 20 ≤ (variablePosition v).1 ∧ (variablePosition v).1 ≤ 220 ∧ + 20 ≤ (variablePosition v).2 ∧ (variablePosition v).2 ≤ 220 := by decide +kernel + +/-- Every clause lies in the inner core square. -/ +theorem clause_bounds : ∀ p ∈ clausePositions, + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := by decide +kernel + +/-- The full continuous core support lies strictly inside the outer cell. -/ +theorem core_support_interior {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) + {p : ℝ × ℝ} (hp : p ∈ support e.2.2) : + 0 < p.1 ∧ p.1 < 240 ∧ 0 < p.2 ∧ p.2 < 240 := by + have hsub : support e.2.2 ⊆ openCell 120 (0, 0) := by + apply support_subset_openCell + intro q hq + have hb := core_bounds e he q hq + have hq' : 0 < q.1 ∧ q.1 < 240 ∧ 0 < q.2 ∧ q.2 < 240 := by omega + have hreal : (0 : ℝ) < q.1 ∧ (q.1 : ℝ) < 240 ∧ + (0 : ℝ) < q.2 ∧ (q.2 : ℝ) < 240 := by exact_mod_cast hq' + simpa [openCell, realPoint] using hreal + simpa [openCell] using hsub hp + +/-- Finite certificate of all permitted contacts between distinct incidences. -/ +theorem routes_contact_check : ∀ e ∈ incidenceRoutes, ∀ f ∈ incidenceRoutes, e ≠ f → + routeContactCheck (commonEndpoints e f) e.2.2 f.2.2 = true := by decide +kernel + +/-- Continuous contacts between distinct incidences are shared graph endpoints. -/ +theorem routes_contact {e f : ℕ × Variable × Route} + (he : e ∈ incidenceRoutes) (hf : f ∈ incidenceRoutes) (hne : e ≠ f) + {p : ℝ × ℝ} (hp : p ∈ support e.2.2) (hq : p ∈ support f.2.2) : + ∃ q ∈ commonEndpoints e f, p = realPoint q := + routeContactCheck_sound (routes_contact_check e he f hf hne) hp hq + +/-- Finite certificate excluding nonincident variable vertices. -/ +theorem variable_avoidance_check : ∀ e ∈ incidenceRoutes, ∀ v : Variable, v ≠ e.2.1 → + routeContactCheck [] e.2.2 [variablePosition v] = true := by decide +kernel + +/-- Finite certificate excluding nonincident clause vertices. -/ +theorem clause_avoidance_check : ∀ e ∈ incidenceRoutes, ∀ i : Fin 8, i.val ≠ e.1 → + routeContactCheck [] e.2.2 [clausePosition i] = true := by decide +kernel + +/-- Incidences avoid every nonincident variable over their full real support. -/ +theorem variable_not_mem_support {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) + {v : Variable} (hv : v ≠ e.2.1) : realPoint (variablePosition v) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (variable_avoidance_check e he v hv) + +/-- Incidences avoid every nonincident clause over their full real support. -/ +theorem clause_not_mem_support {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) + {i : Fin 8} (hi : i.val ≠ e.1) : realPoint (clausePosition i) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (clause_avoidance_check e he i hi) + +/-- Finite certificate that access touches the core only at its own variable. -/ +theorem access_contact_check : ∀ s l, ∀ e ∈ incidenceRoutes, + routeContactCheck (if e.2.1 = (s, l) then [variablePosition (s, l)] else []) + (PairedPorts.accessRoute s l) e.2.2 = true := by decide +kernel + +/-- Every access/core contact is its designated variable endpoint. -/ +theorem access_contact {s : Fin 4} {l : Fin 2} {e : ℕ × Variable × Route} + (he : e ∈ incidenceRoutes) {p : ℝ × ℝ} + (hp : p ∈ support (PairedPorts.accessRoute s l)) (hq : p ∈ support e.2.2) : + e.2.1 = (s, l) ∧ p = realPoint (variablePosition (s, l)) := by + obtain ⟨q, hq, hpq⟩ := routeContactCheck_sound (access_contact_check s l e he) hp hq + split_ifs at hq with h + · have hq' : q = variablePosition (s, l) := List.mem_singleton.mp hq + exact ⟨h, hq' ▸ hpq⟩ + · exact False.elim (List.not_mem_nil hq) + +/-- Finite certificate excluding every other variable from an access. -/ +theorem access_variable_avoidance_check : ∀ s l, ∀ v : Variable, v ≠ (s, l) → + routeContactCheck [] (PairedPorts.accessRoute s l) [variablePosition v] = true := by + decide +kernel + +/-- Finite certificate excluding every clause from every access. -/ +theorem access_clause_avoidance_check : ∀ s l, ∀ p ∈ clausePositions, + routeContactCheck [] (PairedPorts.accessRoute s l) [p] = true := by decide +kernel + +/-- Access avoids every other variable over its full real support. -/ +theorem access_variable_not_mem {s : Fin 4} {l : Fin 2} {v : Variable} (hv : v ≠ (s, l)) : + realPoint (variablePosition v) ∉ support (PairedPorts.accessRoute s l) := + not_mem_support_of_routeContactCheck (access_variable_avoidance_check s l v hv) + +/-- Access avoids every clause over its full real support. -/ +theorem access_clause_not_mem {s : Fin 4} {l : Fin 2} {p : Point} (hp : p ∈ clausePositions) : + realPoint p ∉ support (PairedPorts.accessRoute s l) := + not_mem_support_of_routeContactCheck (access_clause_avoidance_check s l p hp) + +/-- The sixteen incidence segments have total Manhattan length 800. -/ +theorem route_sizes : + (incidenceRoutes.map fun e => e.2.2.consecutivePairs.length).sum = 16 ∧ + (incidenceRoutes.map fun e => wireLength e.2.2).sum = 800 := by decide +kernel + +end Cslib.Complexity.SAT.OrdinaryRing diff --git a/Complexity/Foundations/Geometry/Orthogonal/PairedBoundary.lean b/Complexity/Foundations/Geometry/Orthogonal/PairedBoundary.lean new file mode 100644 index 000000000..35b761b72 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/PairedBoundary.lean @@ -0,0 +1,59 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Geometry.Orthogonal.PairedPorts +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.NormNum + +/-! +# Matching paired boundaries of grid cells + +## Main results + +* `boundary_eq_cases`: equal boundary points in distinct cells determine adjacent opposite + sides and the same lane. +* `boundary_not_mem_openCell`: a paired boundary belongs to no open grid cell. + +## References + +These are original integer-coordinate facts about the fixed paired-port interface. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal.PairedPorts + +/-- Equal boundary points in distinct cells identify the lane and opposite adjacent sides. -/ +theorem boundary_eq_cases {c d : Point} {s t : Fin 4} {l k : Fin 2} + (hcd : c ≠ d) + (h : cellOrigin 120 c + boundaryPort s l = cellOrigin 120 d + boundaryPort t k) : + l = k ∧ d = c + neighbor s ∧ t = oppositeSide s := by + rcases c with ⟨cx, cy⟩ + rcases d with ⟨dx, dy⟩ + have hcd' : ¬ (cx = dx ∧ cy = dy) := fun h => hcd (Prod.ext h.1 h.2) + fin_cases s <;> fin_cases t <;> fin_cases l <;> fin_cases k <;> + norm_num [cellOrigin, boundaryPort, offset, neighbor, oppositeSide, Prod.ext_iff] at h ⊢ <;> + omega + +/-- Each translated paired boundary belongs to its own closed cell. -/ +theorem boundary_mem_closedCell (c : Point) (s : Fin 4) (l : Fin 2) : + realPoint (cellOrigin 120 c + boundaryPort s l) ∈ closedCell 120 c := by + rw [realPoint_add, add_comm, mem_closedCell_add_origin_iff] + fin_cases s <;> fin_cases l <;> norm_num [boundaryPort, offset, realPoint] + +/-- A translated paired boundary lies outside every open grid cell. -/ +theorem boundary_not_mem_openCell (c d : Point) (s : Fin 4) (l : Fin 2) : + realPoint (cellOrigin 120 c + boundaryPort s l) ∉ openCell 120 d := by + intro hp + have hdc := cell_eq_of_mem_open_closed (by decide) hp (boundary_mem_closedCell c s l) + subst d + rw [realPoint_add, add_comm, mem_openCell_add_origin_iff] at hp + fin_cases s <;> fin_cases l <;> norm_num [boundaryPort, offset, realPoint] at hp + +end Cslib.Orthogonal.PairedPorts diff --git a/Complexity/Foundations/Geometry/Orthogonal/PairedPorts.lean b/Complexity/Foundations/Geometry/Orthogonal/PairedPorts.lean new file mode 100644 index 000000000..b311f95a1 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/PairedPorts.lean @@ -0,0 +1,153 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Geometry.Orthogonal.Macrocell +public import Complexity.Foundations.Geometry.Orthogonal.Separation +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum +import Mathlib.Tactic.Ring + +/-! +# Paired ports of a fixed square cell + +Each side of `[0,240]²` has two ports, at offsets 110 and 130. Sides are ordered +left, right, bottom, top. Lane zero always has the smaller coordinate; translation +to an adjacent cell preserves the lane. Straight access routes end 20 units inside. + +## Main definitions + +* `boundaryPort`, `variablePosition`, `accessRoute`: the eight paired accesses. +* `oppositeSide`, `neighbor`: matching sides and adjacent cell displacements. + +## Main results + +* `boundaryPort_shared`: adjacent cells share the same port in the same lane. +* `access_disjoint`, `access_boundary_isolation`: continuous access separation. + +## References + +This fixed geometric interface is original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal.PairedPorts + +/-- A side and its lower- or higher-coordinate lane. -/ +abbrev Port := Fin 4 × Fin 2 + +/-- The two lane coordinates. -/ +def offset (l : Fin 2) : ℤ := 110 + 20 * (l : ℕ) + +/-- Paired boundary ports, in left/right/bottom/top side order. -/ +def boundaryPort (s : Fin 4) (l : Fin 2) : Point := + if s = 0 then (0, offset l) else if s = 1 then (240, offset l) + else if s = 2 then (offset l, 0) else (offset l, 240) + +/-- The corresponding inner positions on the perimeter of `[20,220]²`. -/ +def variablePosition (s : Fin 4) (l : Fin 2) : Point := + if s = 0 then (20, offset l) else if s = 1 then (220, offset l) + else if s = 2 then (offset l, 20) else (offset l, 220) + +/-- Straight access from one boundary port to its corresponding inner position. -/ +def accessRoute (s : Fin 4) (l : Fin 2) : Route := + [boundaryPort s l, variablePosition s l] + +/-- Opposite sides of neighboring cells. -/ +def oppositeSide (s : Fin 4) : Fin 4 := + if s = 0 then 1 else if s = 1 then 0 else if s = 2 then 3 else 2 + +/-- Displacement to the neighboring cell on the indicated side. -/ +def neighbor (s : Fin 4) : Point := + if s = 0 then (-1, 0) else if s = 1 then (1, 0) + else if s = 2 then (0, -1) else (0, 1) + +/-- Taking the opposite side twice recovers the original side. -/ +theorem oppositeSide_involutive : Function.Involutive oppositeSide := by + change ∀ s, oppositeSide (oppositeSide s) = s + decide +kernel + +/-- Adjacent cells match ports without reversing the lane. -/ +theorem boundaryPort_shared (p : Point) (s : Fin 4) (l : Fin 2) : + cellOrigin 120 p + boundaryPort s l = + cellOrigin 120 (p + neighbor s) + boundaryPort (oppositeSide s) l := by + fin_cases s <;> fin_cases l <;> ext <;> + simp [cellOrigin, boundaryPort, oppositeSide, neighbor, offset] <;> ring + +/-- All eight boundary positions are distinct. -/ +theorem boundaryPort_injective : Function.Injective (fun p : Port => boundaryPort p.1 p.2) := by + decide +kernel + +/-- All eight inner positions are distinct. -/ +theorem variablePosition_injective : + Function.Injective (fun p : Port => variablePosition p.1 p.2) := by decide +kernel + +/-- Every inner position lies in the smaller core square. -/ +theorem variable_bounds : ∀ s l, + 20 ≤ (variablePosition s l).1 ∧ (variablePosition s l).1 ≤ 220 ∧ + 20 ≤ (variablePosition s l).2 ∧ (variablePosition s l).2 ≤ 220 := by decide +kernel + +private instance (r : Route) : Decidable (IsOrthogonal r) := + inferInstanceAs (Decidable (∀ e ∈ r.consecutivePairs, AxisAligned e.1 e.2)) + +/-- Every access is orthogonal, with its prescribed boundary and inner endpoints. -/ +theorem access_endpoints : ∀ s l, IsOrthogonal (accessRoute s l) ∧ + (accessRoute s l).head? = some (boundaryPort s l) ∧ + (accessRoute s l).getLast? = some (variablePosition s l) := by decide +kernel + +/-- Every access is simple over its full real support. -/ +theorem access_isSimple (s : Fin 4) (l : Fin 2) : IsSimple (accessRoute s l) := by + constructor + · fin_cases s <;> fin_cases l <;> decide + · intro a ha b hb hne + have ht : (accessRoute s l).consecutivePairs = + [(boundaryPort s l, variablePosition s l)] := rfl + rw [ht, List.mem_singleton] at ha hb + exact False.elim (hne (ha.trans hb.symm)) + +/-- Each access has one segment and 20 units of Manhattan length. -/ +theorem access_route_sizes : ∀ s l, (accessRoute s l).consecutivePairs.length = 1 ∧ + wireLength (accessRoute s l) = 20 := by decide +kernel + +/-- Finite certificate separating every pair of distinct accesses. -/ +theorem access_disjoint_check : ∀ p q : Port, p ≠ q → + routeContactCheck [] (accessRoute p.1 p.2) (accessRoute q.1 q.2) = true := by decide +kernel + +/-- Distinct accesses have disjoint full real supports. -/ +theorem access_disjoint {p q : Port} (h : p ≠ q) : + Disjoint (support (accessRoute p.1 p.2)) (support (accessRoute q.1 q.2)) := + disjoint_support_of_routeContactCheck (access_disjoint_check p q h) + +private theorem mem_support_pair {a b : Point} {p : ℝ × ℝ} : + p ∈ support [a, b] ↔ p ∈ segment a b := by + rw [support_cons_cons] + change (p ∈ segment a b ∨ p ∈ support [b]) ↔ _ + have hright : realPoint b ∈ segment a b := _root_.right_mem_segment ℝ _ _ + have hs : p ∈ support [b] ↔ p = realPoint b := by simp [support, List.consecutivePairs] + rw [hs] + exact ⟨fun h => h.elim id (fun h => h ▸ hright), Or.inl⟩ + +/-- An access stays in the cell and touches its boundary only at the designated port. -/ +theorem access_boundary_isolation {s : Fin 4} {l : Fin 2} {p : ℝ × ℝ} + (hp : p ∈ support (accessRoute s l)) : + 0 ≤ p.1 ∧ p.1 ≤ 240 ∧ 0 ≤ p.2 ∧ p.2 ≤ 240 ∧ + (p.1 = 0 ∨ p.1 = 240 ∨ p.2 = 0 ∨ p.2 = 240 → p = realPoint (boundaryPort s l)) := by + unfold accessRoute at hp + rw [mem_support_pair] at hp + fin_cases s <;> fin_cases l <;> + simp only [boundaryPort, variablePosition, offset, Orthogonal.segment, realPoint] at hp ⊢ + all_goals + norm_num [mem_horizontal, mem_vertical, Set.mem_uIcc] at hp ⊢ + rcases hp with ⟨heq, hlo, hhi⟩ + refine ⟨by linarith, by linarith, by linarith, by linarith, ?_⟩ + intro h + rcases h with h | h | h | h <;> apply Prod.ext <;> norm_num <;> linarith + +end Cslib.Orthogonal.PairedPorts diff --git a/Complexity/Foundations/Geometry/Orthogonal/PairedSpokes.lean b/Complexity/Foundations/Geometry/Orthogonal/PairedSpokes.lean new file mode 100644 index 000000000..f80b2dc0f --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/PairedSpokes.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Geometry.Orthogonal.PairedPorts +import Mathlib.Tactic.FinCases + +/-! +# Four disjoint offset spokes + +A rotational choice of one of the paired ports on each side gives four proper orthogonal +spokes from the center of the square. Distinct spokes meet only at that center. These are +finite certificates about entire real route supports. + +## Main definitions + +* `Cslib.Orthogonal.PairedPorts.spokeLane`, `.spokeRoute`: selected ports and two-segment spokes. + +## Main results + +* `Cslib.Orthogonal.PairedPorts.spoke_isSimple`, `.spokes_contact`: simplicity and center-only + contacts. + +## References + +The finite offset star is original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal.PairedPorts + +/-- One selected lane on each side, in rotational order around the center. -/ +def spokeLane (s : Fin 4) : Fin 2 := if s = 1 ∨ s = 2 then 1 else 0 + +/-- A spoke first travels toward its bend and then toward its selected boundary point. -/ +def spokeElbow (s : Fin 4) : Point := + match s with + | 0 => (120, 110) + | 1 => (120, 130) + | 2 => (130, 120) + | _ => (110, 120) + +/-- A two-segment route from the center to the selected side port. -/ +def spokeRoute (s : Fin 4) : Route := + [(120, 120), spokeElbow s, boundaryPort s (spokeLane s)] + +private instance (r : Route) : Decidable (IsOrthogonal r) := + inferInstanceAs (Decidable (∀ e ∈ r.consecutivePairs, AxisAligned e.1 e.2)) + +/-- Every spoke is proper and orthogonal with its prescribed endpoints. -/ +theorem spoke_endpoints : ∀ s, IsOrthogonal (spokeRoute s) ∧ + (spokeRoute s).head? = some (120, 120) ∧ + (spokeRoute s).getLast? = some (boundaryPort s (spokeLane s)) := by decide +kernel + +/-- The two segments of a spoke meet only at their shared bend. -/ +theorem spoke_bend_check : ∀ s, + segmentContactCheck [spokeElbow s] (120, 120) (spokeElbow s) + (spokeElbow s) (boundaryPort s (spokeLane s)) = true := by decide +kernel + +/-- Each spoke is simple over its full real support. -/ +theorem spoke_isSimple (s : Fin 4) : IsSimple (spokeRoute s) := by + have hcontact : EndpointContacts (120, 120) (spokeElbow s) (spokeElbow s) + (boundaryPort s (spokeLane s)) := by + intro p hp hq + obtain ⟨q, hq, heq⟩ := segmentContactCheck_sound (spoke_bend_check s) hp hq + have hqb := List.mem_singleton.mp hq + exact ⟨Or.inr (hqb ▸ heq), Or.inl (hqb ▸ heq)⟩ + refine ⟨?_, ?_⟩ + · fin_cases s <;> decide + · intro e he f hf hne + simp only [spokeRoute, List.consecutivePairs, List.tail_cons, List.zip_cons_cons, + List.zip_nil_right, List.mem_cons, List.not_mem_nil, or_false] at he hf + rcases he with rfl | rfl <;> rcases hf with rfl | rfl + · exact False.elim (hne rfl) + · exact hcontact + · exact hcontact.symm + · exact False.elim (hne rfl) + +/-- Distinct spokes have only the center as an allowed continuous contact. -/ +theorem spokes_contact_check : ∀ s t : Fin 4, s ≠ t → + routeContactCheck [(120, 120)] (spokeRoute s) (spokeRoute t) = true := by decide +kernel + +/-- Every real intersection of distinct spokes is their common center. -/ +theorem spokes_contact {s t : Fin 4} (hst : s ≠ t) {p : ℝ × ℝ} + (hp : p ∈ support (spokeRoute s)) (hq : p ∈ support (spokeRoute t)) : + p = realPoint (120, 120) := by + obtain ⟨q, hq, hpq⟩ := routeContactCheck_sound (spokes_contact_check s t hst) hp hq + exact (List.mem_singleton.mp hq) ▸ hpq + +end Cslib.Orthogonal.PairedPorts diff --git a/Complexity/Logics/Propositional/CNF/Planar/CrossoverCounting.lean b/Complexity/Logics/Propositional/CNF/Planar/CrossoverCounting.lean new file mode 100644 index 000000000..b0371b7ae --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/Planar/CrossoverCounting.lean @@ -0,0 +1,131 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.CNF.Planar.CrossoverSplit + +/-! +# Unique extensions through the fixed crossover + +The fixed clause tables have exactly one satisfying full assignment for each pair of incoming +bits. The explicit Boolean extension and its uniqueness are preserved by the internal splitting +rings. Construction and source credit are documented in `Crossover` and `CrossoverSplit`. + +## Main definitions + +* `Cslib.Logic.PL.CNF.Crossover.canonicalExtension`: the original full assignment from two bits. +* `Cslib.Logic.PL.CNF.Crossover.canonicalSplitExtension`: its lift to all internal copies. + +## Main results + +* `Cslib.Logic.PL.CNF.Crossover.existsUnique_holds`: uniqueness before splitting. +* `Cslib.Logic.PL.CNF.Crossover.existsUnique_split_holds`: uniqueness after splitting. + +## References + +The construction adapts [Lichtenstein1982, Figs. 4–5] and [Tromino2025, Fig. 8(b)]. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.Crossover + +/-- The full satisfying assignment determined by the left and top input values. +The explicit Boolean expressions describe all nine internal values as well as the outputs. -/ +def canonicalExtension (a b : Bool) : Variable → Bool := + assignment a a b b a (!a || b) (!a || !b) b a b (a || b) (a || !b) a + +/-- The explicit extension satisfies all 26 clauses for every pair of inputs. -/ +theorem eval_canonicalExtension (a b : Bool) : + formula.eval (canonicalExtension a b) = true := by + cases a <;> cases b <;> decide +kernel + +-- Only the nine internal values are enumerated after the two inputs are fixed; the existing +-- boundary theorem has already identified the outputs with those inputs. +private theorem assignment_eq_canonicalExtension (a b : Bool) : + ∀ aInnerLeft upperLeft lowerLeft bInnerTop center bInnerBottom + upperRight lowerRight aInnerRight : Bool, + formula.eval (assignment a a b b aInnerLeft upperLeft lowerLeft bInnerTop center + bInnerBottom upperRight lowerRight aInnerRight) = true → + ∀ v, assignment a a b b aInnerLeft upperLeft lowerLeft bInnerTop center + bInnerBottom upperRight lowerRight aInnerRight v = canonicalExtension a b v := by + cases a <;> cases b <;> decide +kernel + +/-- Every satisfying full assignment is the explicit extension of its two incoming values. -/ +theorem eq_canonicalExtension_of_holds {σ : Variable → Bool} + (h : formula.eval σ = true) : + σ = canonicalExtension (σ .aLeft) (σ .bTop) := by + obtain ⟨horizontal, vertical⟩ := boundary_eq_of_holds h + have heq : assignment (σ .aLeft) (σ .aLeft) (σ .bTop) (σ .bTop) + (σ .aInnerLeft) (σ .upperLeft) (σ .lowerLeft) (σ .bInnerTop) (σ .center) + (σ .bInnerBottom) (σ .upperRight) (σ .lowerRight) (σ .aInnerRight) = σ := by + funext v + cases v <;> first | rfl | exact horizontal | exact vertical + have hfull := assignment_eq_canonicalExtension (σ .aLeft) (σ .bTop) + (σ .aInnerLeft) (σ .upperLeft) (σ .lowerLeft) (σ .bInnerTop) (σ .center) + (σ .bInnerBottom) (σ .upperRight) (σ .lowerRight) (σ .aInnerRight) + rw [heq] at hfull + exact funext (hfull h) + +/-- Satisfying assignments agreeing on the incoming ports agree on every variable. -/ +theorem ext_of_holds {σ τ : Variable → Bool} + (hσ : formula.eval σ = true) (hτ : formula.eval τ = true) + (ha : σ .aLeft = τ .aLeft) (hb : σ .bTop = τ .bTop) : σ = τ := by + rw [eq_canonicalExtension_of_holds hσ, eq_canonicalExtension_of_holds hτ, ha, hb] + +/-- Each pair of incoming values extends uniquely to a satisfying full assignment. -/ +theorem existsUnique_holds (a b : Bool) : + ∃! σ : Variable → Bool, + formula.eval σ = true ∧ σ .aLeft = a ∧ σ .bTop = b := by + refine ⟨canonicalExtension a b, ⟨eval_canonicalExtension a b, rfl, rfl⟩, ?_⟩ + rintro σ ⟨hσ, ha, hb⟩ + rw [eq_canonicalExtension_of_holds hσ, ha, hb] + +/-- The explicit full split assignment: every local copy receives its original variable's +canonical value, determined by the left and top inputs. -/ +def canonicalSplitExtension (a b : Bool) : SplitVariable → Bool := + canonicalExtension a b ∘ SplitVariable.origin + +/-- The canonical split extension satisfies all 74 clauses. -/ +theorem eval_canonicalSplitExtension (a b : Bool) : + splitFormula.eval (canonicalSplitExtension a b) = true := + eval_splitFormula_lift _ (eval_canonicalExtension a b) + +/-- Every satisfying split assignment is the explicit extension of its incoming values. +The implication rings identify the six copies before applying the original uniqueness result; +no enumeration of assignments to the 53 split variables is needed. -/ +theorem eq_canonicalSplitExtension_of_holds {σ : SplitVariable → Bool} + (h : splitFormula.eval σ = true) : + σ = canonicalSplitExtension (σ .aLeft) (σ .bTop) := by + obtain ⟨hbase, hagree⟩ := (eval_splitFormula_iff σ).1 h + have heq := eq_canonicalExtension_of_holds hbase + funext v + have hv := congrFun heq v.origin + cases v with + | aLeft | aRight | bTop | bBottom | center => exact hv + | copy v i => + have hzero : σ (.copy v 0) = canonicalSplitExtension (σ .aLeft) (σ .bTop) + (.copy v i) := by + cases v <;> exact hv + exact (hagree v i).trans hzero + +/-- Satisfying split assignments agreeing on the two incoming ports agree everywhere. -/ +theorem split_ext_of_holds {σ τ : SplitVariable → Bool} + (hσ : splitFormula.eval σ = true) (hτ : splitFormula.eval τ = true) + (ha : σ .aLeft = τ .aLeft) (hb : σ .bTop = τ .bTop) : σ = τ := by + rw [eq_canonicalSplitExtension_of_holds hσ, eq_canonicalSplitExtension_of_holds hτ, ha, hb] + +/-- Each pair of incoming values extends uniquely to a satisfying full split assignment. -/ +theorem existsUnique_split_holds (a b : Bool) : + ∃! σ : SplitVariable → Bool, + splitFormula.eval σ = true ∧ σ .aLeft = a ∧ σ .bTop = b := by + refine ⟨canonicalSplitExtension a b, ⟨eval_canonicalSplitExtension a b, rfl, rfl⟩, ?_⟩ + rintro σ ⟨hσ, ha, hb⟩ + rw [eq_canonicalSplitExtension_of_holds hσ, ha, hb] + +end Cslib.Logic.PL.CNF.Crossover diff --git a/Complexity/Logics/Propositional/CNF/Planar/PairedCrossover.lean b/Complexity/Logics/Propositional/CNF/Planar/PairedCrossover.lean new file mode 100644 index 000000000..05994f7e5 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/Planar/PairedCrossover.lean @@ -0,0 +1,283 @@ +/- +Copyright (c) 2026 lean-trominoes contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine, Stefan Langerman, GPT 5.6 +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.CNF.Planar.CrossoverCounting +import Mathlib.Tactic.FinCases + +/-! +# A crossover with two attachment ports per side + +Each old terminal is replaced by two ports and one implication between them. Together with +its two old incidences this forms an implication chain between two equal internal copies. +Consequently both ports have the old terminal's uniquely determined value. Each port has two +internal incidences and room for one external incidence. The unchanged internal core comes from +Lichtenstein's crossover [Lichtenstein1982, Figs. 4–5], as drawn in [Tromino2025, Fig. 8(b)]. + +## Main definitions + +* `PairedCrossover.formula`: the 57-variable, 78-clause paired-port template. +* `PairedCrossover.canonicalExtension`: its full assignment from two input bits. + +## Main results + +* `PairedCrossover.eq_canonicalExtension_of_holds`: uniqueness of the full extension. +* `PairedCrossover.terminal_occurrences`: two internal incidences at each of the eight ports. + +## References + +* David Lichtenstein, *Planar Formulae and Their Uses*, Figs. 4–5 [Lichtenstein1982]. +* MIT–ULB CompGeom Group, *Undecidability of Tiling with a Tromino*, Fig. 8(b) [Tromino2025]. + +The paired-terminal replacement and its uniqueness proof are original. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PairedCrossover + +/-- Eight boundary ports, the unsplit center, and the old 48 internal copies. -/ +inductive Variable + | port (side : Fin 4) (lane : Fin 2) + | center + | copy (v : Crossover.Internal) (i : Fin 6) + deriving DecidableEq, Repr + +instance : Fintype Variable where + elems := { .center } ∪ + Finset.univ.biUnion (fun s => Finset.univ.image (Variable.port s)) ∪ + Finset.univ.biUnion (fun v => Finset.univ.image (Variable.copy v)) + complete v := by cases v <;> simp + +/-- The eight attachment ports in left/right/bottom/top geometric order, with two lanes per +side. The source variables `.bTop` and `.bBottom` retain their historical names. -/ +def terminal : Fin 4 → Fin 2 → Variable := Variable.port + +/-- Forget the lane of a boundary port. -/ +def Variable.origin : Variable → Crossover.SplitVariable + | .port 0 _ => .aLeft + | .port 1 _ => .aRight + | .port 2 _ => .bTop + | .port 3 _ => .bBottom + | .center => .center + | .copy v i => .copy v i + +/-- Choose lane zero as the representative of each old terminal. -/ +def representative : Crossover.SplitVariable → Variable + | .aLeft => .port 0 0 + | .aRight => .port 1 0 + | .bTop => .port 2 0 + | .bBottom => .port 3 0 + | .center => .center + | .copy v i => .copy v i + +/-- Replace each old boundary literal by the port at that incidence's geometric lane. -/ +def mapLiteral : Literal Crossover.SplitVariable → Literal Variable + | (.aLeft, true) => (.port 0 0, true) + | (.aLeft, false) => (.port 0 1, false) + | (.aRight, false) => (.port 1 0, false) + | (.aRight, true) => (.port 1 1, true) + | (.bTop, false) => (.port 2 0, false) + | (.bTop, true) => (.port 2 1, true) + | (.bBottom, true) => (.port 3 0, true) + | (.bBottom, false) => (.port 3 1, false) + | (.center, b) => (.center, b) + | (.copy v i, b) => (.copy v i, b) + +/-- One directed implication joins the paired ports on each side. -/ +def bridges : CNF Variable := + [[(.port 0 0, false), (.port 0 1, true)], + [(.port 1 1, false), (.port 1 0, true)], + [(.port 2 1, false), (.port 2 0, true)], + [(.port 3 0, false), (.port 3 1, true)]] + +/-- Preserve the old 74 clauses with their terminal incidences separated, then add four bridges. -/ +def formula : CNF Variable := + Crossover.splitFormula.map (List.map mapLiteral) ++ bridges + +/-- The internal copy at the start of each terminal's implication chain. -/ +def chainStart : Fin 4 → Variable + | 0 => .copy .aInnerLeft 5 + | 1 => .copy .aInnerRight 3 + | 2 => .copy .bInnerTop 1 + | 3 => .copy .bInnerBottom 4 + +/-- The internal copy at the end of each terminal's implication chain. -/ +def chainEnd : Fin 4 → Variable + | 0 => .copy .aInnerLeft 4 + | 1 => .copy .aInnerRight 2 + | 2 => .copy .bInnerTop 0 + | 3 => .copy .bInnerBottom 3 + +/-- The first lane in each directed chain. -/ +def firstLane : Fin 4 → Fin 2 + | 0 => 0 + | 1 => 1 + | 2 => 1 + | 3 => 0 + +/-- Three existing clauses form a chain between two equal internal copies. -/ +def chain : Fin 4 → CNF Variable + | 0 => [[(.port 0 0, true), (.copy .aInnerLeft 5, false)], + [(.port 0 0, false), (.port 0 1, true)], + [(.port 0 1, false), (.copy .aInnerLeft 4, true)]] + | 1 => [[(.copy .aInnerRight 3, false), (.port 1 1, true)], + [(.port 1 1, false), (.port 1 0, true)], + [(.copy .aInnerRight 2, true), (.port 1 0, false)]] + | 2 => [[(.port 2 1, true), (.copy .bInnerTop 1, false)], + [(.port 2 1, false), (.port 2 0, true)], + [(.port 2 0, false), (.copy .bInnerTop 0, true)]] + | 3 => [[(.copy .bInnerBottom 4, false), (.port 3 0, true)], + [(.port 3 0, false), (.port 3 1, true)], + [(.copy .bInnerBottom 3, true), (.port 3 1, false)]] + +private theorem chain_mem : ∀ s, ∀ c ∈ chain s, c ∈ formula := by decide +kernel + +private theorem ring_mem : ∀ v, ∀ c ∈ (Crossover.ring v).relabel representative, + c ∈ formula := by decide +kernel + +private theorem eval_chain {σ : Variable → Bool} {s : Fin 4} + (h : (chain s).eval σ = true) (heq : σ (chainStart s) = σ (chainEnd s)) : + σ (terminal s 0) = σ (chainStart s) ∧ + σ (terminal s 1) = σ (chainStart s) := by + have cert : ∀ a p q : Bool, + ((!a || p) && (!p || q) && (!q || a)) = true → p = a ∧ q = a := by + decide +kernel + have h' : ((!σ (chainStart s) || σ (terminal s (firstLane s))) && + (!σ (terminal s (firstLane s)) || σ (terminal s (1 - firstLane s))) && + (!σ (terminal s (1 - firstLane s)) || σ (chainEnd s))) = true := by + fin_cases s <;> + simpa [chain, chainStart, chainEnd, terminal, firstLane, CNF.eval, Clause.eval, + Literal.eval, Bool.and_assoc, Bool.or_comm] using h + rw [← heq] at h' + have hp := cert _ _ _ h' + fin_cases s <;> simpa [firstLane, and_comm] using hp + +private theorem eval_mapped (σ : Variable → Bool) + (ha : ∀ s, σ (terminal s 0) = σ (terminal s 1)) : + CNF.eval σ (Crossover.splitFormula.map (List.map mapLiteral)) = + Crossover.splitFormula.eval (σ ∘ representative) := by + have hl : ∀ l, Literal.eval σ (mapLiteral l) = Literal.eval (σ ∘ representative) l := by + simp only [terminal] at ha + rintro ⟨v, b⟩ + cases v <;> cases b <;> + simp [mapLiteral, Literal.eval, representative, ← ha] + simp only [CNF.eval, List.all_map, Function.comp_def, Clause.eval, List.any_map, hl] + rfl + +/-- Exact semantics: the old split formula together with agreement of each paired terminal. -/ +theorem eval_formula_iff (σ : Variable → Bool) : + formula.eval σ = true ↔ + Crossover.splitFormula.eval (σ ∘ representative) = true ∧ + ∀ s, σ (terminal s 0) = σ (terminal s 1) := by + constructor + · intro h + have hc := CNF.eval_eq_true_iff σ formula |>.1 h + have hr : ∀ v i, σ (.copy v i) = σ (.copy v 0) := by + intro v + apply (Crossover.eval_ring_iff (σ ∘ representative) v).1 + rw [← CNF.eval_relabel] + exact (CNF.eval_eq_true_iff _ _).2 fun c hc' => hc c (ring_mem v c hc') + have hp : ∀ s, σ (terminal s 0) = σ (terminal s 1) := by + intro s + have hchain := (CNF.eval_eq_true_iff σ (chain s)).2 fun c hc' => hc c (chain_mem s c hc') + have heq : σ (chainStart s) = σ (chainEnd s) := by + fin_cases s <;> exact (hr _ _).trans (hr _ _).symm + exact (eval_chain hchain heq).1.trans (eval_chain hchain heq).2.symm + refine ⟨?_, hp⟩ + rw [formula, CNF.eval_append, Bool.and_eq_true] at h + rw [eval_mapped σ hp] at h + exact h.1 + · rintro ⟨h, hp⟩ + rw [formula, CNF.eval_append, Bool.and_eq_true, eval_mapped σ hp] + refine ⟨h, ?_⟩ + simp only [terminal] at hp + simp [bridges, CNF.eval, Clause.eval, Literal.eval, ← hp] + +/-- The explicit unique full assignment, including both lanes on every side. -/ +def canonicalExtension (a b : Bool) : Variable → Bool := + Crossover.canonicalSplitExtension a b ∘ Variable.origin + +/-- The explicit full assignment satisfies the paired-port formula. -/ +theorem eval_canonicalExtension (a b : Bool) : formula.eval (canonicalExtension a b) = true := by + apply (eval_formula_iff _).2 + constructor + · have heq : canonicalExtension a b ∘ representative = Crossover.canonicalSplitExtension a b := by + funext v + cases v <;> rfl + rw [heq] + exact Crossover.eval_canonicalSplitExtension a b + · intro s + fin_cases s <;> rfl + +/-- Both lanes on the left and right carry `a`; both lanes on the top and bottom carry `b`. -/ +theorem terminal_values_canonicalExtension (a b : Bool) : + ∀ s l, canonicalExtension a b (terminal s l) = if s.val < 2 then a else b := by + intro s l + fin_cases s <;> rfl + +/-- Every satisfying full assignment is determined by the left and top lane-zero inputs. -/ +theorem eq_canonicalExtension_of_holds {σ : Variable → Bool} (h : formula.eval σ = true) : + σ = canonicalExtension (σ (terminal 0 0)) (σ (terminal 2 0)) := by + obtain ⟨hb, hp⟩ := (eval_formula_iff σ).1 h + have heq := Crossover.eq_canonicalSplitExtension_of_holds hb + funext v + cases v with + | center => exact congrFun heq .center + | copy v i => exact congrFun heq (.copy v i) + | port s l => + have hs : σ (terminal s 0) = canonicalExtension (σ (terminal 0 0)) + (σ (terminal 2 0)) (terminal s l) := by + fin_cases s + · exact congrFun heq .aLeft + · exact congrFun heq .aRight + · exact congrFun heq .bTop + · exact congrFun heq .bBottom + fin_cases l + · exact hs + · exact (hp s).symm.trans hs + +/-- All satisfying terminal values are the horizontal or vertical input value. -/ +theorem terminal_value_of_holds {σ : Variable → Bool} (h : formula.eval σ = true) + (s : Fin 4) (l : Fin 2) : + σ (terminal s l) = if s.val < 2 then σ (terminal 0 0) else σ (terminal 2 0) := + (congrFun (eq_canonicalExtension_of_holds h) (terminal s l)).trans + (terminal_values_canonicalExtension _ _ s l) + +/-- Projecting a satisfying paired assignment recovers a satisfying old split assignment. -/ +theorem eval_representative_of_holds {σ : Variable → Bool} (h : formula.eval σ = true) : + Crossover.splitFormula.eval (σ ∘ representative) = true := + ((eval_formula_iff σ).1 h).1 + +/-- Every two-bit input has exactly one satisfying full assignment. -/ +theorem existsUnique_holds (a b : Bool) : + ∃! σ : Variable → Bool, + formula.eval σ = true ∧ σ (terminal 0 0) = a ∧ σ (terminal 2 0) = b := by + refine ⟨canonicalExtension a b, ⟨eval_canonicalExtension a b, rfl, rfl⟩, ?_⟩ + rintro σ ⟨h, ha, hb⟩ + rw [eq_canonicalExtension_of_holds h, ha, hb] + +/-- The template uses exactly 57 variables. -/ +theorem card_variable : Fintype.card Variable = 57 := by decide +kernel + +/-- The template has exactly 78 clauses. -/ +theorem length_formula : formula.length = 78 := by decide +kernel + +/-- All clauses have width at most three. -/ +theorem width_formula : ∀ c ∈ formula, c.length ≤ 3 := by decide +kernel + +/-- No clause repeats a variable. -/ +theorem distinct_variables : ∀ c ∈ formula, (c.map Prod.fst).Nodup := by decide +kernel + +/-- Every variable has at most three internal incidences. -/ +theorem occurrences_le_three : ∀ v, formula.occurrences v ≤ 3 := by decide +kernel + +/-- Each of the eight ports has exactly two internal incidences. -/ +theorem terminal_occurrences : ∀ s l, formula.occurrences (terminal s l) = 2 := by decide +kernel + +end Cslib.Logic.PL.CNF.PairedCrossover diff --git a/Complexity/Logics/Propositional/CNF/Planar/PairedWire.lean b/Complexity/Logics/Propositional/CNF/Planar/PairedWire.lean new file mode 100644 index 000000000..022d1f5e8 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/Planar/PairedWire.lean @@ -0,0 +1,188 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.CNF +public import Mathlib.Data.List.Defs +import Mathlib.Data.List.Basic +import Mathlib.Data.Bool.Basic + +/-! +# Paired implication wires + +Each cell has two lanes, each with an entry and an exit. Local constraints must equate all +four port values. Across a gap, lane zero implies the next entry from the previous exit; +lane one implies the previous exit from the next entry. These implications use separate +ports. Under the local equalities, they force the representative values of consecutive cells +to agree. Thus every port of a nonempty wire has the value of its first lane-zero entry. + +This module uses ordinary CNF semantics. It supplies no local cell constraints or geometry. +The two clauses and four literal positions per gap are counted with multiplicity, without +distinctness assumptions on the port variables. + +## Main definitions + +* `Cell`, `Cell.Agree`: two entry/exit pairs and equality of all four values. +* `gapClauses`, `links`: opposite implications across one gap and all consecutive gaps. + +## Main results + +* `eval_links_iff`: under local agreement, the links equate consecutive representatives. +* `agree_head`, `agree_head_of_ne_nil`: every entry and exit has the first entry's value. +* `eval_links_of_constant`: constant port values satisfy every link. +* `length_links`, `length_vars_links`, `length_of_mem_links`: exact sizes and binary width. + +## References + +The paired-port composition and equality argument are original. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PairedWire + +variable {V : Type*} + +/-- Two lanes of entry/exit variables. Lane zero's entry represents the cell's value. -/ +abbrev Cell (V : Type*) := Fin 2 → V × V + +/-- All four port values agree with the lane-zero entry. Local cell constraints must provide +this property; the wire clauses alone do not impose it. -/ +def Cell.Agree (cell : Cell V) (σ : V → Bool) : Prop := + ∀ lane, σ (cell lane).1 = σ (cell 0).1 ∧ σ (cell lane).2 = σ (cell 0).1 + +/-- Across one gap, lane zero points forward and lane one points backward. -/ +def gapClauses (previous next : Cell V) : CNF V := + [Clause.imp [(previous 0).2] (next 0).1, + Clause.imp [(next 1).1] (previous 1).2] + +/-- Two opposite implications across every consecutive pair of cells. -/ +def links (cells : List (Cell V)) : CNF V := + cells.consecutivePairs.flatMap fun p => gapClauses p.1 p.2 + +/-- With local agreement, the two implications equate the neighboring cell values. -/ +theorem eval_gapClauses_iff (σ : V → Bool) (previous next : Cell V) + (hp : previous.Agree σ) (hn : next.Agree σ) : + (gapClauses previous next).eval σ = true ↔ σ (previous 0).1 = σ (next 0).1 := by + simp only [gapClauses, eval_cons, eval_nil, Bool.and_true, Bool.and_eq_true, + Clause.eval_imp, List.forall_mem_singleton, (hp 0).2, (hp 1).2, (hn 1).1] + cases σ (previous 0).1 <;> cases σ (next 0).1 <;> simp + +/-- Under local agreement in every cell, the links hold exactly when consecutive lane-zero +entries agree. -/ +theorem eval_links_iff (σ : V → Bool) (cells : List (Cell V)) + (hcells : ∀ c ∈ cells, c.Agree σ) : + (links cells).eval σ = true ↔ + ∀ p ∈ cells.consecutivePairs, σ (p.1 0).1 = σ (p.2 0).1 := by + rw [links, eval_flatMap_eq_true_iff] + apply forall₂_congr fun p hp => ?_ + have hm := List.of_mem_zip hp + exact eval_gapClauses_iff σ p.1 p.2 (hcells _ hm.1) + (hcells _ (List.mem_of_mem_tail hm.2)) + +/-- Empty wires have no gap clauses. -/ +@[simp] theorem links_nil : links ([] : List (Cell V)) = [] := rfl + +/-- A single cell has no gap clauses. -/ +@[simp] theorem links_singleton (cell : Cell V) : links [cell] = [] := rfl + +/-- Remove the first gap, leaving the links of the nonempty tail. -/ +theorem links_cons_cons (first next : Cell V) (rest : List (Cell V)) : + links (first :: next :: rest) = gapClauses first next ++ links (next :: rest) := rfl + +/-- Under local agreement and satisfied links, all entries and exits have the value of the +first lane-zero entry, including for a singleton wire. -/ +theorem agree_head (σ : V → Bool) (first : Cell V) (rest : List (Cell V)) + (hcells : ∀ c ∈ first :: rest, c.Agree σ) + (hlinks : (links (first :: rest)).eval σ = true) : + ∀ c ∈ first :: rest, ∀ lane, + σ (c lane).1 = σ (first 0).1 ∧ σ (c lane).2 = σ (first 0).1 := by + induction rest generalizing first with + | nil => + intro c hc lane + rw [List.mem_singleton.1 hc] + exact hcells first List.mem_cons_self lane + | cons next rest ih => + rw [links_cons_cons, eval_append, Bool.and_eq_true] at hlinks + have he := (eval_gapClauses_iff σ first next (hcells _ List.mem_cons_self) + (hcells _ (List.mem_cons_of_mem _ List.mem_cons_self))).1 hlinks.1 + intro c hc lane + rcases List.mem_cons.1 hc with he | hc + · rw [he] + exact hcells first List.mem_cons_self lane + · have h := ih next (fun c hc => hcells c (List.mem_cons_of_mem _ hc)) hlinks.2 c hc lane + exact ⟨h.1.trans he.symm, h.2.trans he.symm⟩ + +/-- The head-agreement rule for a list with an explicit nonemptiness hypothesis. The fallback +only makes `headD` total and does not affect the result. -/ +theorem agree_head_of_ne_nil (σ : V → Bool) (fallback : Cell V) (cells : List (Cell V)) + (hne : cells ≠ []) (hcells : ∀ c ∈ cells, c.Agree σ) + (hlinks : (links cells).eval σ = true) : + ∀ c ∈ cells, ∀ lane, + σ (c lane).1 = σ (cells.headD fallback 0).1 ∧ + σ (c lane).2 = σ (cells.headD fallback 0).1 := by + cases cells with + | nil => exact False.elim (hne rfl) + | cons first rest => exact agree_head σ first rest hcells hlinks + +/-- A common value on every entry and exit satisfies all paired links. -/ +theorem eval_links_of_constant (σ : V → Bool) (value : Bool) (cells : List (Cell V)) + (h : ∀ c ∈ cells, ∀ lane, σ (c lane).1 = value ∧ σ (c lane).2 = value) : + (links cells).eval σ = true := by + have hcells : ∀ c ∈ cells, c.Agree σ := fun c hc lane => + ⟨(h c hc lane).1.trans (h c hc 0).1.symm, (h c hc lane).2.trans (h c hc 0).1.symm⟩ + apply (eval_links_iff σ cells hcells).2 + intro p hp + have hm := List.of_mem_zip hp + exact (h p.1 hm.1 0).1.trans (h p.2 (List.mem_of_mem_tail hm.2) 0).1.symm + +/-- There are exactly two clauses per gap. -/ +theorem length_gapClauses (previous next : Cell V) : + (gapClauses previous next).length = 2 := rfl + +/-- Each gap clause is binary, even if some port variables coincide. -/ +theorem length_of_mem_gapClauses (previous next : Cell V) {c : Clause V} + (hc : c ∈ gapClauses previous next) : c.length = 2 := by + simp only [gapClauses, List.mem_cons, List.not_mem_nil, or_false] at hc + rcases hc with rfl | rfl <;> rfl + +/-- A gap contributes four literal positions, counted with multiplicity. -/ +theorem length_vars_gapClauses (previous next : Cell V) : + (gapClauses previous next).vars.length = 4 := rfl + +/-- There are exactly two clauses for each consecutive pair of cells. -/ +theorem length_links_eq_gaps (cells : List (Cell V)) : + (links cells).length = 2 * cells.consecutivePairs.length := by + simp [links, List.length_flatMap, length_gapClauses, Nat.mul_comm] + +/-- A wire with `n` cells has `2 (n - 1)` clauses, with truncated subtraction for `n = 0`. -/ +theorem length_links (cells : List (Cell V)) : + (links cells).length = 2 * (cells.length - 1) := by + rw [length_links_eq_gaps] + simp [List.consecutivePairs] + +/-- Every clause of a paired wire has exactly two literal positions. -/ +theorem length_of_mem_links (cells : List (Cell V)) {c : Clause V} + (hc : c ∈ links cells) : c.length = 2 := by + obtain ⟨p, _, hc⟩ := List.mem_flatMap.1 hc + exact length_of_mem_gapClauses p.1 p.2 hc + +/-- Paired links have width at most two. -/ +theorem width_links (cells : List (Cell V)) : ∀ c ∈ links cells, c.length ≤ 2 := + fun _ hc => Nat.le_of_eq (length_of_mem_links cells hc) + +/-- There are exactly four literal positions per gap. -/ +theorem length_vars_links (cells : List (Cell V)) : + (links cells).vars.length = 4 * (cells.length - 1) := by + have h : (links cells).map List.length = List.replicate (links cells).length 2 := by + apply List.map_eq_replicate_iff.2 + exact fun c hc => length_of_mem_links cells hc + rw [CNF.length_vars, h] + simp [length_links, Nat.mul_right_comm] + +end Cslib.Logic.PL.CNF.PairedWire diff --git a/ComplexityTests/PairedCrossover.lean b/ComplexityTests/PairedCrossover.lean new file mode 100644 index 000000000..05277fd48 --- /dev/null +++ b/ComplexityTests/PairedCrossover.lean @@ -0,0 +1,39 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Logics.Propositional.CNF.Planar.PairedCrossover +import Mathlib.Data.Fintype.Pi +import Mathlib.Data.Fintype.Prod + +/-! The paired template has exactly four full models, including every internal copy and all +eight ports. Counting uses the canonical-extension bijection instead of enumerating 57 bits. -/ + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.PairedCrossover + +private def modelsEquivInputs : + {σ : Variable → Bool // formula.eval σ = true} ≃ Bool × Bool where + toFun σ := (σ.val (terminal 0 0), σ.val (terminal 2 0)) + invFun ab := ⟨canonicalExtension ab.1 ab.2, eval_canonicalExtension ab.1 ab.2⟩ + left_inv σ := Subtype.ext (eq_canonicalExtension_of_holds σ.property).symm + right_inv _ := rfl + +example : Fintype.card {σ : Variable → Bool // formula.eval σ = true} = 4 := by + rw [Fintype.card_congr modelsEquivInputs] + decide + +-- Incoming and outgoing values on distinct lanes still agree across the same wire. +example {σ : Variable → Bool} (h : formula.eval σ = true) : + σ (terminal 0 1) = σ (terminal 1 0) ∧ + σ (terminal 2 1) = σ (terminal 3 0) := by + constructor + · exact (terminal_value_of_holds h 0 1).trans (terminal_value_of_holds h 1 0).symm + · exact (terminal_value_of_holds h 2 1).trans (terminal_value_of_holds h 3 0).symm + +-- The two signals are independent, with all ports populated in this concrete model. +example : formula.eval (canonicalExtension true false) = true ∧ + canonicalExtension true false (terminal 1 1) = true ∧ + canonicalExtension true false (terminal 3 1) = false := + ⟨eval_canonicalExtension true false, rfl, rfl⟩ diff --git a/ComplexityTests/PairedWire.lean b/ComplexityTests/PairedWire.lean new file mode 100644 index 000000000..b72c10171 --- /dev/null +++ b/ComplexityTests/PairedWire.lean @@ -0,0 +1,56 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Logics.Propositional.CNF.Planar.PairedWire + +/-! # Paired-wire directions, propagation, and exact sizes -/ + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.PairedWire + +/-- Four distinct variables per cell, with two consecutive variables per lane. -/ +private def cell (index : ℕ) : Cell ℕ := + fun lane => (4 * index + 2 * lane.val, 4 * index + 2 * lane.val + 1) + +/-- The opposite clauses use separate lanes, with the prescribed entry/exit directions. -/ +example : gapClauses (cell 0) (cell 1) = + [[(1, false), (4, true)], [(6, false), (3, true)]] := rfl + +example : (links ([] : List (Cell ℕ))).length = 0 ∧ + (links ([] : List (Cell ℕ))).vars.length = 0 := by decide + +example : (links [cell 0]).length = 0 ∧ (links [cell 0]).vars.length = 0 := by decide + +example : (links [cell 0, cell 1, cell 2]).length = 4 ∧ + (links [cell 0, cell 1, cell 2]).vars.length = 8 := by decide + +/-- Counts retain multiplicity even when all port variables coincide. -/ +example : (links [fun _ => (0, 0), fun _ => (0, 0)]).length = 2 ∧ + (links [fun _ => (0, 0), fun _ => (0, 0)]).vars.length = 4 := by decide + +example (value : Bool) : (links [cell 0, cell 1, cell 2]).eval (fun _ => value) = true := + eval_links_of_constant _ value _ (fun _ _ _ => ⟨rfl, rfl⟩) + +/-- Local agreement is satisfiable when the two cells have different representative values. -/ +example : ∀ c ∈ [cell 0, cell 1], c.Agree (fun v => decide (v < 4)) := by + intro c hc + simp only [List.mem_cons, List.not_mem_nil, or_false] at hc + rcases hc with rfl | rfl <;> unfold Cell.Agree <;> decide + +/-- The forward implication excludes a drop from true to false between cells. -/ +example : (links [cell 0, cell 1]).eval (fun v => decide (v < 4)) = false := by decide + +/-- The backward implication excludes a rise from false to true between cells. -/ +example : (links [cell 0, cell 1]).eval (fun v => decide (4 ≤ v)) = false := by decide + +/-- Links alone allow different representatives when local agreement is absent. -/ +example : (links [cell 0, cell 1]).eval (fun v => v == 4) = true ∧ + ((cell 0 0).1 == 4) ≠ ((cell 1 0).1 == 4) := by decide + +/-- A compiler can recover any terminal port from local cell guarantees and satisfied links. -/ +example (σ : ℕ → Bool) (hcells : ∀ c ∈ [cell 0, cell 1, cell 2], c.Agree σ) + (hlinks : (links [cell 0, cell 1, cell 2]).eval σ = true) : + σ (cell 2 1).2 = σ (cell 0 0).1 := + (agree_head σ (cell 0) [cell 1, cell 2] hcells hlinks (cell 2) (by simp) 1).2 diff --git a/ComplexityTests/PlanarCrossoverCounting.lean b/ComplexityTests/PlanarCrossoverCounting.lean new file mode 100644 index 000000000..17167cb9a --- /dev/null +++ b/ComplexityTests/PlanarCrossoverCounting.lean @@ -0,0 +1,50 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Logics.Propositional.CNF.Planar.CrossoverCounting +import Mathlib.Data.Fintype.Pi +import Mathlib.Data.Fintype.Prod + +/-! Full satisfying assignments correspond bijectively to the two incoming bits, including +after splitting. The model counts below use these structural equivalences, not enumeration. -/ + +open Cslib.Logic.PL Cslib.Logic.PL.CNF.Crossover + +private def modelsEquivInputs : + {σ : Variable → Bool // formula.eval σ = true} ≃ Bool × Bool where + toFun σ := (σ.val .aLeft, σ.val .bTop) + invFun ab := ⟨canonicalExtension ab.1 ab.2, eval_canonicalExtension ab.1 ab.2⟩ + left_inv σ := Subtype.ext (eq_canonicalExtension_of_holds σ.property).symm + right_inv _ := rfl + +private def splitModelsEquivInputs : + {σ : SplitVariable → Bool // splitFormula.eval σ = true} ≃ Bool × Bool where + toFun σ := (σ.val .aLeft, σ.val .bTop) + invFun ab := ⟨canonicalSplitExtension ab.1 ab.2, eval_canonicalSplitExtension ab.1 ab.2⟩ + left_inv σ := Subtype.ext (eq_canonicalSplitExtension_of_holds σ.property).symm + right_inv _ := rfl + +example : Fintype.card {σ : Variable → Bool // formula.eval σ = true} = 4 := by + rw [Fintype.card_congr modelsEquivInputs] + decide + +example : Fintype.card {σ : SplitVariable → Bool // splitFormula.eval σ = true} = 4 := by + rw [Fintype.card_congr splitModelsEquivInputs] + decide + +-- The mixed input has a concrete internal value, common to all six copies. +example (i : Fin 6) : canonicalSplitExtension false true (.copy .upperLeft i) = true := rfl + +-- Changing a free-looking internal value cannot produce another satisfying full assignment. +example : splitFormula.eval (Function.update (canonicalSplitExtension false true) .center true) = + false := by + cases h : splitFormula.eval + (Function.update (canonicalSplitExtension false true) .center true) with + | false => rfl + | true => + have heq := eq_canonicalSplitExtension_of_holds h + have hcenter := congrFun heq .center + simp [canonicalSplitExtension, canonicalExtension, assignment, SplitVariable.origin] at hcenter From 55e49e5a9516a978ab2d33bdbb9537fd29fe8acd Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:32:59 -0400 Subject: [PATCH 5/9] feat(planar-SAT): certify the paired crossover drawing Part of #1144, split from the internally reviewed parsimonious planar SAT development. All six local validation gates and hygiene checks pass. No selected abstract changes. AI-assisted implementation and internal review with Codex gpt-6.1-sol; outside-fleet coordinator review remains required. --- Complexity.lean | 3 + .../SAT/Planar/PairedCrossoverLayout.lean | 203 +++++ .../Planar/PairedCrossoverLayoutChecks.lean | 841 ++++++++++++++++++ .../SAT/Planar/PairedCrossoverLayoutData.lean | 253 ++++++ 4 files changed, 1300 insertions(+) create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayout.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutChecks.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutData.lean diff --git a/Complexity.lean b/Complexity.lean index 719aee534..cfc6defa2 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -809,6 +809,9 @@ public import Complexity.Computability.Complexity.Problems.SAT.Planar.CrossoverL public import Complexity.Computability.Complexity.Problems.SAT.Planar.Geometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Occurrence public import Complexity.Computability.Complexity.Problems.SAT.Planar.OrdinaryRing +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayout +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutChecks +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutData public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths.Occupancy public import Complexity.Computability.Complexity.Problems.SAT.Planar.PolyTime diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayout.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayout.lean new file mode 100644 index 000000000..2b111bc70 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayout.lean @@ -0,0 +1,203 @@ +/- +Copyright (c) 2026 lean-trominoes contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine, Stefan Langerman, GPT 5.6 +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutChecks +public import Complexity.Foundations.Geometry.Orthogonal.Simple +import Mathlib.Data.List.Zip +import Mathlib.Tactic.Linarith + +/-! +# Certified continuous orthogonal paired-port crossover + +The finite checks for the fixed degree-three CNF template certify entire real supports. +Its 57 variable vertices and 78 clause vertices are distinct. Its 162 incidence routes are simple, +orthogonal, avoid nonincident vertices, and meet only at common graph endpoints. Their total +wire length is 2192. Eight mutually disjoint access routes connect degree-two terminals to the +boundary of `[0,240]²`, and meet the core only at the corresponding terminal. Each terminal +therefore accommodates exactly one external clause occurrence. + +## Main definitions + +This module uses the fixed table from `PairedCrossoverLayoutData` and the shared `Orthogonal` route +and continuous support semantics. + +## Main results + +* `Cslib.Logic.PL.CNF.PairedCrossover.incidence_simple`: continuous segment simplicity. +* `Cslib.Logic.PL.CNF.PairedCrossover.routes_contact`: intersections are common endpoints. +* `Cslib.Logic.PL.CNF.PairedCrossover.access_contact`: access meets the core only at its terminal. +* `Cslib.Logic.PL.CNF.PairedCrossover.access_boundary_isolation`: only ports touch the boundary. + +## References + +* David Lichtenstein, *Planar Formulae and Their Uses*, Figs. 4–5 [Lichtenstein1982]. +* MIT–ULB CompGeom Group, *Undecidability of Tiling with a Tromino*, Fig. 8(b) [Tromino2025]. + +The paired-terminal modification and its continuous orthogonal certificates are original. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PairedCrossover + +open Orthogonal + +private theorem support_bounds_of_points {r : Route} + (h : ∀ q ∈ r, 20 ≤ q.1 ∧ q.1 ≤ 220 ∧ 20 ≤ q.2 ∧ q.2 ≤ 220) + {p : ℝ × ℝ} (hp : p ∈ support r) : + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := by + have cast_bounds (q : Point) (hq : q ∈ r) : + (20 : ℝ) ≤ q.1 ∧ (q.1 : ℝ) ≤ 220 ∧ 20 ≤ (q.2 : ℝ) ∧ (q.2 : ℝ) ≤ 220 := by + exact_mod_cast h q hq + rcases hp with ⟨q, hq, rfl⟩ | ⟨e, he, hp⟩ + · exact cast_bounds q hq + · have he' := List.of_mem_zip he + have ha := cast_bounds e.1 he'.1 + have hb := cast_bounds e.2 (List.mem_of_mem_tail he'.2) + obtain ⟨hx, hy⟩ := mem_segment_bounds hp + rw [Set.mem_uIcc] at hx hy + have hx' : 20 ≤ p.1 ∧ p.1 ≤ 220 := by + rcases hx with hx | hx <;> constructor <;> linarith [ha.1, ha.2.1, hb.1, hb.2.1] + have hy' : 20 ≤ p.2 ∧ p.2 ≤ 220 := by + rcases hy with hy | hy <;> constructor <;> + linarith [ha.2.2.1, ha.2.2.2, hb.2.2.1, hb.2.2.2] + exact ⟨hx'.1, hx'.2, hy'.1, hy'.2⟩ + +/-- Every real point of the core drawing is strictly inside the outer 240-by-240 box. -/ +theorem core_support_interior {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) + {p : ℝ × ℝ} (hp : p ∈ support e.2.2) : + 0 < p.1 ∧ p.1 < 240 ∧ 0 < p.2 ∧ p.2 < 240 := by + have h := support_bounds_of_points (core_bounds e he) hp + constructor + · linarith [h.1] + constructor + · linarith [h.2.1] + constructor <;> linarith [h.2.2.1, h.2.2.2] + +/-- Distinct segments of a route intersect only at their consecutive shared bend. -/ +theorem incidence_simple {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) + {a b : Point × Point} (ha : a ∈ e.2.2.consecutivePairs) + (hb : b ∈ e.2.2.consecutivePairs) (hne : a ≠ b) + {p : ℝ × ℝ} (hp : p ∈ segment a.1 a.2) (hq : p ∈ segment b.1 b.2) : + ∃ q ∈ consecutiveContact a b, p = realPoint q := + segmentContactCheck_sound (incidence_simple_check e he a ha b hb hne) hp hq + +/-- Every incidence route is simple in the shared continuous polygonal-route semantics. -/ +theorem incidence_isSimple {e : ℕ × Variable × Route} (he : e ∈ incidenceRoutes) : + IsSimple e.2.2 := by + refine ⟨incidence_points_nodup e he, ?_⟩ + intro a ha b hb hne p hp hq + obtain ⟨q, hq, hpq⟩ := incidence_simple he ha hb hne hp hq + unfold consecutiveContact at hq + split_ifs at hq with h h' + · have hqa : q = a.2 := List.mem_singleton.mp hq + subst q + exact ⟨Or.inr hpq, Or.inl (hpq.trans (congrArg realPoint h))⟩ + · have hqa : q = a.1 := List.mem_singleton.mp hq + subst q + exact ⟨Or.inl hpq, Or.inr (hpq.trans (congrArg realPoint h'.symm))⟩ + · exact False.elim (List.not_mem_nil hq) + +/-- Every continuous intersection of distinct incidence routes is a shared graph endpoint. -/ +theorem routes_contact {e f : ℕ × Variable × Route} + (he : e ∈ incidenceRoutes) (hf : f ∈ incidenceRoutes) (hne : e ≠ f) + {p : ℝ × ℝ} (hp : p ∈ support e.2.2) (hq : p ∈ support f.2.2) : + ∃ q ∈ commonEndpoints e f, p = realPoint q := + routeContactCheck_sound (routes_contact_check e he f hf hne) hp hq + +/-- Continuous incidence routes avoid every nonincident variable vertex. -/ +theorem variable_not_mem_support {e : ℕ × Variable × Route} + (he : e ∈ incidenceRoutes) {v : Variable} (hv : v ≠ e.2.1) : + realPoint (variablePosition v) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (variable_avoidance_check e he v hv) + +/-- Continuous incidence routes avoid every nonincident clause vertex. -/ +theorem clause_not_mem_support {e : ℕ × Variable × Route} + (he : e ∈ incidenceRoutes) {i : Fin 78} (hi : i.val ≠ e.1) : + realPoint (clausePosition i) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (clause_avoidance_check e he i hi) + +/-- Every literal incidence appears exactly once in the route list. -/ +theorem incidenceEndpoints_nodup : + (incidenceRoutes.map fun e => (e.1, e.2.1)).Nodup := by decide +kernel + +/-- Every variable vertex lies in the core rectangle. -/ +theorem variable_bounds : ∀ v, 20 ≤ (variablePosition v).1 ∧ (variablePosition v).1 ≤ 220 ∧ + 20 ≤ (variablePosition v).2 ∧ (variablePosition v).2 ≤ 220 := by decide +kernel + +/-- Every clause vertex lies in the core rectangle. -/ +theorem clause_bounds : ∀ p ∈ clausePositions, + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := by decide +kernel + +/-- Each of the 57 variable names is genuinely used by the fixed formula. -/ +theorem variables_used : ∀ v, v ∈ formula.vars := by decide +kernel + +/-- No access route visits any other variable vertex. -/ +theorem access_variable_avoidance_check : ∀ s l v, v ≠ terminal s l → + routeContactCheck [] (accessRoute s l) [variablePosition v] = true := by decide +kernel + +/-- No access route visits a clause vertex. -/ +theorem access_clause_avoidance_check : ∀ s l, ∀ p ∈ clausePositions, + routeContactCheck [] (accessRoute s l) [p] = true := by decide +kernel + +/-- The full real access route avoids every other variable vertex. -/ +theorem access_variable_not_mem {s : Fin 4} {l : Fin 2} {v : Variable} + (hv : v ≠ terminal s l) : + realPoint (variablePosition v) ∉ support (accessRoute s l) := + not_mem_support_of_routeContactCheck (access_variable_avoidance_check s l v hv) + +/-- The full real access route avoids every clause vertex. -/ +theorem access_clause_not_mem {s : Fin 4} {l : Fin 2} {p : Point} + (hp : p ∈ clausePositions) : + realPoint p ∉ support (accessRoute s l) := + not_mem_support_of_routeContactCheck (access_clause_avoidance_check s l p hp) + +/-- Each access route has one segment and 20 units of wire length. -/ +theorem access_route_sizes : ∀ s l, (accessRoute s l).consecutivePairs.length = 1 ∧ + wireLength (accessRoute s l) = 20 := PairedPorts.access_route_sizes + +/-- Including all eight accesses gives 415 segments and total wire length 2352. -/ +theorem full_route_sizes : + (incidenceRoutes.map fun e => e.2.2.consecutivePairs.length).sum + + ((List.finRange 4).flatMap fun s => (List.finRange 2).map fun l => + (accessRoute s l).consecutivePairs.length).sum = 415 ∧ + (incidenceRoutes.map fun e => wireLength e.2.2).sum + + ((List.finRange 4).flatMap fun s => (List.finRange 2).map fun l => + wireLength (accessRoute s l)).sum = 2352 := by + rw [route_sizes.1, route_sizes.2] + decide +kernel + +/-- Each straight access is simple over its full real support. -/ +theorem access_isSimple (s : Fin 4) (l : Fin 2) : IsSimple (accessRoute s l) := + PairedPorts.access_isSimple s l + +/-- Distinct accesses have disjoint full real support. -/ +theorem access_disjoint {p q : PairedPorts.Port} (h : p ≠ q) : + Disjoint (support (accessRoute p.1 p.2)) (support (accessRoute q.1 q.2)) := + PairedPorts.access_disjoint h + +/-- Every continuous contact between an access and the core is its associated port. -/ +theorem access_contact {s : Fin 4} {l : Fin 2} {e : ℕ × Variable × Route} + (he : e ∈ incidenceRoutes) {p : ℝ × ℝ} + (hp : p ∈ support (accessRoute s l)) (hq : p ∈ support e.2.2) : + e.2.1 = terminal s l ∧ p = realPoint (variablePosition (terminal s l)) := by + obtain ⟨q, hq, hpq⟩ := routeContactCheck_sound (access_contact_check s l e he) hp hq + split_ifs at hq with h + · have hq' : q = variablePosition (terminal s l) := List.mem_singleton.mp hq + exact ⟨h, hq' ▸ hpq⟩ + · exact False.elim (List.not_mem_nil hq) + +/-- Only the designated boundary point of an access lies on the outer box boundary. -/ +theorem access_boundary_isolation {s : Fin 4} {l : Fin 2} {p : ℝ × ℝ} + (hp : p ∈ support (accessRoute s l)) : + 0 ≤ p.1 ∧ p.1 ≤ 240 ∧ 0 ≤ p.2 ∧ p.2 ≤ 240 ∧ + (p.1 = 0 ∨ p.1 = 240 ∨ p.2 = 0 ∨ p.2 = 240 → p = realPoint (boundaryPort s l)) := + PairedPorts.access_boundary_isolation hp + +end Cslib.Logic.PL.CNF.PairedCrossover diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutChecks.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutChecks.lean new file mode 100644 index 000000000..1f584ab93 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutChecks.lean @@ -0,0 +1,841 @@ +/- +Copyright (c) 2026 lean-trominoes contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine, Stefan Langerman, GPT 5.6 +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutData +public import Complexity.Foundations.Geometry.Orthogonal.Separation +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup +import Mathlib.Data.List.Zip +import Mathlib.Tactic.FinCases + +/-! +# Finite certificates for the paired-port orthogonal crossover + +Finite arithmetic certificates control the full real support of every route. The core has +57 variable vertices, 78 clause vertices and 162 incidences. Eight mutually disjoint access routes +connect the signal terminals to the boundary of `[0,240]²`; only the associated terminal may meet +an access route. An external clause uses this access and the terminal's remaining occurrence. + +## Main definitions + +* `Cslib.Logic.PL.CNF.PairedCrossover.clausePosition`: indexed positions of clause vertices. +* `Cslib.Logic.PL.CNF.PairedCrossover.commonEndpoints`: permitted contacts of incidence routes. + +## Main results + +* `Cslib.Logic.PL.CNF.PairedCrossover.incidenceRoutes_cover`: exact ordinary CNF literal incidences. +* `Cslib.Logic.PL.CNF.PairedCrossover.routes_contact_check`: finite permitted-contact certificate. +* `Cslib.Logic.PL.CNF.PairedCrossover.access_contact_check`: finite access isolation certificate. + +## References + +* David Lichtenstein, *Planar Formulae and Their Uses*, Figs. 4–5 [Lichtenstein1982]. +* MIT–ULB CompGeom Group, *Undecidability of Tiling with a Tromino*, Fig. 8(b) [Tromino2025]. + +The paired modification and its finite orthogonal certificates are original. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PairedCrossover + +open Orthogonal + +/-- The clause position at an index. All indices in `incidenceRoutes` are certified in range. -/ +def clausePosition (i : ℕ) : Point := clausePositions.getD i (0, 0) + +/-- Every variable, including both lanes on each side. -/ +def variableList : List Variable := + [.center] ++ (List.finRange 4).flatMap (fun s => + (List.finRange 2).map (Variable.port s)) ++ + Crossover.internalList.flatMap fun v => List.ofFn fun i : Fin 6 => Variable.copy v i + +/-- Every variable is included in the finite drawing vertex list. -/ +theorem mem_variableList : ∀ v, v ∈ variableList := by decide +kernel + +/-- The exact literal-incidence list of the ordinary CNF formula. -/ +theorem incidenceRoutes_cover : + incidenceRoutes.map (fun e => (e.1, e.2.1)) = + formula.zipIdx.flatMap (fun ci => ci.1.map fun l => (ci.2, l.1)) := by + decide +kernel + +/-- The fixed formula and drawing have 78 clauses and 162 incidences. -/ +theorem drawing_sizes : clausePositions.length = 78 ∧ incidenceRoutes.length = 162 := by + decide +kernel + +/-- Variable vertices have distinct positions. -/ +theorem variablePosition_injective : Function.Injective variablePosition := by + have h : (variableList.map variablePosition).Nodup := by decide +kernel + exact fun v w => List.inj_on_of_nodup_map h (mem_variableList v) (mem_variableList w) + +/-- Clause vertices are pairwise distinct. -/ +theorem clausePositions_nodup : clausePositions.Nodup := by decide +kernel + +/-- No clause vertex is a variable vertex. -/ +theorem variablePosition_not_mem_clauses : + ∀ v, variablePosition v ∉ clausePositions := by decide +kernel + +/-- The only allowed contacts between two distinct incidence routes are shared graph endpoints. -/ +def commonEndpoints (e f : ℕ × Variable × Route) : List Point := + (if e.1 = f.1 then [clausePosition e.1] else []) ++ + (if e.2.1 = f.2.1 then [variablePosition e.2.1] else []) + +/-- A pair of different segments may meet only at a consecutive shared bend. -/ +def consecutiveContact (a b : Point × Point) : List Point := + if a.2 = b.1 then [a.2] else if b.2 = a.1 then [a.1] else [] + +private instance (r : Route) : Decidable (IsOrthogonal r) := + inferInstanceAs (Decidable (∀ e ∈ r.consecutivePairs, AxisAligned e.1 e.2)) + +private def LocalSpec (e : ℕ × Variable × Route) : Prop := + (e.1 < 78 ∧ e.2.2.head? = some (clausePosition e.1) ∧ + e.2.2.getLast? = some (variablePosition e.2.1)) ∧ + IsOrthogonal e.2.2 ∧ + (∀ p ∈ e.2.2, 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220) ∧ + e.2.2.Nodup ∧ + ∀ a ∈ e.2.2.consecutivePairs, ∀ b ∈ e.2.2.consecutivePairs, a ≠ b → + segmentContactCheck (consecutiveContact a b) a.1 a.2 b.1 b.2 = true + +private instance (e : ℕ × Variable × Route) : Decidable (LocalSpec e) := + inferInstanceAs (Decidable (_ ∧ _)) + +private def rowCheck (e : ℕ × Variable × Route) : Bool := + decide (LocalSpec e) && + ((incidenceRoutes.all fun f => e == f || + routeContactCheck (commonEndpoints e f) e.2.2 f.2.2) && + ((variableList.all fun v => v == e.2.1 || + routeContactCheck [] e.2.2 [variablePosition v]) && + ((List.finRange 78).all fun i => i.val == e.1 || + routeContactCheck [] e.2.2 [clausePosition i]))) + +private def routeAt (i : Fin 162) : ℕ × Variable × Route := + incidenceRoutes.getD i (0, .center, []) + +-- Separate closed certificates keep kernel reduction memory bounded per incidence row. + +private theorem row_0 : rowCheck (routeAt 0) = true := by decide +kernel + +private theorem row_1 : rowCheck (routeAt 1) = true := by decide +kernel +private theorem row_2 : rowCheck (routeAt 2) = true := by decide +kernel +private theorem row_3 : rowCheck (routeAt 3) = true := by decide +kernel +private theorem row_4 : rowCheck (routeAt 4) = true := by decide +kernel +private theorem row_5 : rowCheck (routeAt 5) = true := by decide +kernel +private theorem row_6 : rowCheck (routeAt 6) = true := by decide +kernel +private theorem row_7 : rowCheck (routeAt 7) = true := by decide +kernel +private theorem row_8 : rowCheck (routeAt 8) = true := by decide +kernel +private theorem row_9 : rowCheck (routeAt 9) = true := by decide +kernel +private theorem row_10 : rowCheck (routeAt 10) = true := by decide +kernel + +private theorem row_11 : rowCheck (routeAt 11) = true := by decide +kernel +private theorem row_12 : rowCheck (routeAt 12) = true := by decide +kernel +private theorem row_13 : rowCheck (routeAt 13) = true := by decide +kernel +private theorem row_14 : rowCheck (routeAt 14) = true := by decide +kernel +private theorem row_15 : rowCheck (routeAt 15) = true := by decide +kernel +private theorem row_16 : rowCheck (routeAt 16) = true := by decide +kernel +private theorem row_17 : rowCheck (routeAt 17) = true := by decide +kernel +private theorem row_18 : rowCheck (routeAt 18) = true := by decide +kernel +private theorem row_19 : rowCheck (routeAt 19) = true := by decide +kernel +private theorem row_20 : rowCheck (routeAt 20) = true := by decide +kernel + +private theorem row_21 : rowCheck (routeAt 21) = true := by decide +kernel +private theorem row_22 : rowCheck (routeAt 22) = true := by decide +kernel +private theorem row_23 : rowCheck (routeAt 23) = true := by decide +kernel +private theorem row_24 : rowCheck (routeAt 24) = true := by decide +kernel +private theorem row_25 : rowCheck (routeAt 25) = true := by decide +kernel +private theorem row_26 : rowCheck (routeAt 26) = true := by decide +kernel +private theorem row_27 : rowCheck (routeAt 27) = true := by decide +kernel +private theorem row_28 : rowCheck (routeAt 28) = true := by decide +kernel +private theorem row_29 : rowCheck (routeAt 29) = true := by decide +kernel +private theorem row_30 : rowCheck (routeAt 30) = true := by decide +kernel + +private theorem row_31 : rowCheck (routeAt 31) = true := by decide +kernel +private theorem row_32 : rowCheck (routeAt 32) = true := by decide +kernel +private theorem row_33 : rowCheck (routeAt 33) = true := by decide +kernel +private theorem row_34 : rowCheck (routeAt 34) = true := by decide +kernel +private theorem row_35 : rowCheck (routeAt 35) = true := by decide +kernel +private theorem row_36 : rowCheck (routeAt 36) = true := by decide +kernel +private theorem row_37 : rowCheck (routeAt 37) = true := by decide +kernel +private theorem row_38 : rowCheck (routeAt 38) = true := by decide +kernel +private theorem row_39 : rowCheck (routeAt 39) = true := by decide +kernel +private theorem row_40 : rowCheck (routeAt 40) = true := by decide +kernel + +private theorem row_41 : rowCheck (routeAt 41) = true := by decide +kernel +private theorem row_42 : rowCheck (routeAt 42) = true := by decide +kernel +private theorem row_43 : rowCheck (routeAt 43) = true := by decide +kernel +private theorem row_44 : rowCheck (routeAt 44) = true := by decide +kernel +private theorem row_45 : rowCheck (routeAt 45) = true := by decide +kernel +private theorem row_46 : rowCheck (routeAt 46) = true := by decide +kernel +private theorem row_47 : rowCheck (routeAt 47) = true := by decide +kernel +private theorem row_48 : rowCheck (routeAt 48) = true := by decide +kernel +private theorem row_49 : rowCheck (routeAt 49) = true := by decide +kernel +private theorem row_50 : rowCheck (routeAt 50) = true := by decide +kernel + +private theorem row_51 : rowCheck (routeAt 51) = true := by decide +kernel +private theorem row_52 : rowCheck (routeAt 52) = true := by decide +kernel +private theorem row_53 : rowCheck (routeAt 53) = true := by decide +kernel +private theorem row_54 : rowCheck (routeAt 54) = true := by decide +kernel +private theorem row_55 : rowCheck (routeAt 55) = true := by decide +kernel +private theorem row_56 : rowCheck (routeAt 56) = true := by decide +kernel +private theorem row_57 : rowCheck (routeAt 57) = true := by decide +kernel +private theorem row_58 : rowCheck (routeAt 58) = true := by decide +kernel +private theorem row_59 : rowCheck (routeAt 59) = true := by decide +kernel +private theorem row_60 : rowCheck (routeAt 60) = true := by decide +kernel + +private theorem row_61 : rowCheck (routeAt 61) = true := by decide +kernel +private theorem row_62 : rowCheck (routeAt 62) = true := by decide +kernel +private theorem row_63 : rowCheck (routeAt 63) = true := by decide +kernel +private theorem row_64 : rowCheck (routeAt 64) = true := by decide +kernel +private theorem row_65 : rowCheck (routeAt 65) = true := by decide +kernel +private theorem row_66 : rowCheck (routeAt 66) = true := by decide +kernel +private theorem row_67 : rowCheck (routeAt 67) = true := by decide +kernel +private theorem row_68 : rowCheck (routeAt 68) = true := by decide +kernel +private theorem row_69 : rowCheck (routeAt 69) = true := by decide +kernel +private theorem row_70 : rowCheck (routeAt 70) = true := by decide +kernel + +private theorem row_71 : rowCheck (routeAt 71) = true := by decide +kernel +private theorem row_72 : rowCheck (routeAt 72) = true := by decide +kernel +private theorem row_73 : rowCheck (routeAt 73) = true := by decide +kernel +private theorem row_74 : rowCheck (routeAt 74) = true := by decide +kernel +private theorem row_75 : rowCheck (routeAt 75) = true := by decide +kernel +private theorem row_76 : rowCheck (routeAt 76) = true := by decide +kernel +private theorem row_77 : rowCheck (routeAt 77) = true := by decide +kernel +private theorem row_78 : rowCheck (routeAt 78) = true := by decide +kernel +private theorem row_79 : rowCheck (routeAt 79) = true := by decide +kernel +private theorem row_80 : rowCheck (routeAt 80) = true := by decide +kernel + +private theorem row_81 : rowCheck (routeAt 81) = true := by decide +kernel +private theorem row_82 : rowCheck (routeAt 82) = true := by decide +kernel +private theorem row_83 : rowCheck (routeAt 83) = true := by decide +kernel +private theorem row_84 : rowCheck (routeAt 84) = true := by decide +kernel +private theorem row_85 : rowCheck (routeAt 85) = true := by decide +kernel +private theorem row_86 : rowCheck (routeAt 86) = true := by decide +kernel +private theorem row_87 : rowCheck (routeAt 87) = true := by decide +kernel +private theorem row_88 : rowCheck (routeAt 88) = true := by decide +kernel +private theorem row_89 : rowCheck (routeAt 89) = true := by decide +kernel +private theorem row_90 : rowCheck (routeAt 90) = true := by decide +kernel + +private theorem row_91 : rowCheck (routeAt 91) = true := by decide +kernel +private theorem row_92 : rowCheck (routeAt 92) = true := by decide +kernel +private theorem row_93 : rowCheck (routeAt 93) = true := by decide +kernel +private theorem row_94 : rowCheck (routeAt 94) = true := by decide +kernel +private theorem row_95 : rowCheck (routeAt 95) = true := by decide +kernel +private theorem row_96 : rowCheck (routeAt 96) = true := by decide +kernel +private theorem row_97 : rowCheck (routeAt 97) = true := by decide +kernel +private theorem row_98 : rowCheck (routeAt 98) = true := by decide +kernel +private theorem row_99 : rowCheck (routeAt 99) = true := by decide +kernel +private theorem row_100 : rowCheck (routeAt 100) = true := by decide +kernel + +private theorem row_101 : rowCheck (routeAt 101) = true := by decide +kernel +private theorem row_102 : rowCheck (routeAt 102) = true := by decide +kernel +private theorem row_103 : rowCheck (routeAt 103) = true := by decide +kernel +private theorem row_104 : rowCheck (routeAt 104) = true := by decide +kernel +private theorem row_105 : rowCheck (routeAt 105) = true := by decide +kernel +private theorem row_106 : rowCheck (routeAt 106) = true := by decide +kernel +private theorem row_107 : rowCheck (routeAt 107) = true := by decide +kernel +private theorem row_108 : rowCheck (routeAt 108) = true := by decide +kernel +private theorem row_109 : rowCheck (routeAt 109) = true := by decide +kernel +private theorem row_110 : rowCheck (routeAt 110) = true := by decide +kernel + +private theorem row_111 : rowCheck (routeAt 111) = true := by decide +kernel +private theorem row_112 : rowCheck (routeAt 112) = true := by decide +kernel +private theorem row_113 : rowCheck (routeAt 113) = true := by decide +kernel +private theorem row_114 : rowCheck (routeAt 114) = true := by decide +kernel +private theorem row_115 : rowCheck (routeAt 115) = true := by decide +kernel +private theorem row_116 : rowCheck (routeAt 116) = true := by decide +kernel +private theorem row_117 : rowCheck (routeAt 117) = true := by decide +kernel +private theorem row_118 : rowCheck (routeAt 118) = true := by decide +kernel +private theorem row_119 : rowCheck (routeAt 119) = true := by decide +kernel +private theorem row_120 : rowCheck (routeAt 120) = true := by decide +kernel + +private theorem row_121 : rowCheck (routeAt 121) = true := by decide +kernel +private theorem row_122 : rowCheck (routeAt 122) = true := by decide +kernel +private theorem row_123 : rowCheck (routeAt 123) = true := by decide +kernel +private theorem row_124 : rowCheck (routeAt 124) = true := by decide +kernel +private theorem row_125 : rowCheck (routeAt 125) = true := by decide +kernel +private theorem row_126 : rowCheck (routeAt 126) = true := by decide +kernel +private theorem row_127 : rowCheck (routeAt 127) = true := by decide +kernel +private theorem row_128 : rowCheck (routeAt 128) = true := by decide +kernel +private theorem row_129 : rowCheck (routeAt 129) = true := by decide +kernel +private theorem row_130 : rowCheck (routeAt 130) = true := by decide +kernel + +private theorem row_131 : rowCheck (routeAt 131) = true := by decide +kernel +private theorem row_132 : rowCheck (routeAt 132) = true := by decide +kernel +private theorem row_133 : rowCheck (routeAt 133) = true := by decide +kernel +private theorem row_134 : rowCheck (routeAt 134) = true := by decide +kernel +private theorem row_135 : rowCheck (routeAt 135) = true := by decide +kernel +private theorem row_136 : rowCheck (routeAt 136) = true := by decide +kernel +private theorem row_137 : rowCheck (routeAt 137) = true := by decide +kernel +private theorem row_138 : rowCheck (routeAt 138) = true := by decide +kernel +private theorem row_139 : rowCheck (routeAt 139) = true := by decide +kernel +private theorem row_140 : rowCheck (routeAt 140) = true := by decide +kernel + +private theorem row_141 : rowCheck (routeAt 141) = true := by decide +kernel +private theorem row_142 : rowCheck (routeAt 142) = true := by decide +kernel +private theorem row_143 : rowCheck (routeAt 143) = true := by decide +kernel +private theorem row_144 : rowCheck (routeAt 144) = true := by decide +kernel +private theorem row_145 : rowCheck (routeAt 145) = true := by decide +kernel +private theorem row_146 : rowCheck (routeAt 146) = true := by decide +kernel +private theorem row_147 : rowCheck (routeAt 147) = true := by decide +kernel +private theorem row_148 : rowCheck (routeAt 148) = true := by decide +kernel +private theorem row_149 : rowCheck (routeAt 149) = true := by decide +kernel +private theorem row_150 : rowCheck (routeAt 150) = true := by decide +kernel + +private theorem row_151 : rowCheck (routeAt 151) = true := by decide +kernel +private theorem row_152 : rowCheck (routeAt 152) = true := by decide +kernel +private theorem row_153 : rowCheck (routeAt 153) = true := by decide +kernel + +private theorem row_154 : rowCheck (routeAt 154) = true := by decide +kernel +private theorem row_155 : rowCheck (routeAt 155) = true := by decide +kernel +private theorem row_156 : rowCheck (routeAt 156) = true := by decide +kernel +private theorem row_157 : rowCheck (routeAt 157) = true := by decide +kernel +private theorem row_158 : rowCheck (routeAt 158) = true := by decide +kernel +private theorem row_159 : rowCheck (routeAt 159) = true := by decide +kernel +private theorem row_160 : rowCheck (routeAt 160) = true := by decide +kernel +private theorem row_161 : rowCheck (routeAt 161) = true := by decide +kernel + +-- Keep dispatch proofs small as well as the individual kernel certificates. In particular, +-- no theorem elaborates the full 162-case split: six rows form a block, then 27 blocks cover all. +private def blockIndex (q : Fin 27) (r : Fin 6) : Fin 162 := + ⟨q.val * 6 + r.val, by omega⟩ + +private theorem block_0 (r : Fin 6) : rowCheck (routeAt (blockIndex 0 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (0 : Fin 162) + exact row_0 + · conv_lhs => arg 1; arg 1; change (1 : Fin 162) + exact row_1 + · conv_lhs => arg 1; arg 1; change (2 : Fin 162) + exact row_2 + · conv_lhs => arg 1; arg 1; change (3 : Fin 162) + exact row_3 + · conv_lhs => arg 1; arg 1; change (4 : Fin 162) + exact row_4 + · conv_lhs => arg 1; arg 1; change (5 : Fin 162) + exact row_5 + +private theorem block_1 (r : Fin 6) : rowCheck (routeAt (blockIndex 1 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (6 : Fin 162) + exact row_6 + · conv_lhs => arg 1; arg 1; change (7 : Fin 162) + exact row_7 + · conv_lhs => arg 1; arg 1; change (8 : Fin 162) + exact row_8 + · conv_lhs => arg 1; arg 1; change (9 : Fin 162) + exact row_9 + · conv_lhs => arg 1; arg 1; change (10 : Fin 162) + exact row_10 + · conv_lhs => arg 1; arg 1; change (11 : Fin 162) + exact row_11 + +private theorem block_2 (r : Fin 6) : rowCheck (routeAt (blockIndex 2 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (12 : Fin 162) + exact row_12 + · conv_lhs => arg 1; arg 1; change (13 : Fin 162) + exact row_13 + · conv_lhs => arg 1; arg 1; change (14 : Fin 162) + exact row_14 + · conv_lhs => arg 1; arg 1; change (15 : Fin 162) + exact row_15 + · conv_lhs => arg 1; arg 1; change (16 : Fin 162) + exact row_16 + · conv_lhs => arg 1; arg 1; change (17 : Fin 162) + exact row_17 + +private theorem block_3 (r : Fin 6) : rowCheck (routeAt (blockIndex 3 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (18 : Fin 162) + exact row_18 + · conv_lhs => arg 1; arg 1; change (19 : Fin 162) + exact row_19 + · conv_lhs => arg 1; arg 1; change (20 : Fin 162) + exact row_20 + · conv_lhs => arg 1; arg 1; change (21 : Fin 162) + exact row_21 + · conv_lhs => arg 1; arg 1; change (22 : Fin 162) + exact row_22 + · conv_lhs => arg 1; arg 1; change (23 : Fin 162) + exact row_23 + +private theorem block_4 (r : Fin 6) : rowCheck (routeAt (blockIndex 4 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (24 : Fin 162) + exact row_24 + · conv_lhs => arg 1; arg 1; change (25 : Fin 162) + exact row_25 + · conv_lhs => arg 1; arg 1; change (26 : Fin 162) + exact row_26 + · conv_lhs => arg 1; arg 1; change (27 : Fin 162) + exact row_27 + · conv_lhs => arg 1; arg 1; change (28 : Fin 162) + exact row_28 + · conv_lhs => arg 1; arg 1; change (29 : Fin 162) + exact row_29 + +private theorem block_5 (r : Fin 6) : rowCheck (routeAt (blockIndex 5 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (30 : Fin 162) + exact row_30 + · conv_lhs => arg 1; arg 1; change (31 : Fin 162) + exact row_31 + · conv_lhs => arg 1; arg 1; change (32 : Fin 162) + exact row_32 + · conv_lhs => arg 1; arg 1; change (33 : Fin 162) + exact row_33 + · conv_lhs => arg 1; arg 1; change (34 : Fin 162) + exact row_34 + · conv_lhs => arg 1; arg 1; change (35 : Fin 162) + exact row_35 + +private theorem block_6 (r : Fin 6) : rowCheck (routeAt (blockIndex 6 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (36 : Fin 162) + exact row_36 + · conv_lhs => arg 1; arg 1; change (37 : Fin 162) + exact row_37 + · conv_lhs => arg 1; arg 1; change (38 : Fin 162) + exact row_38 + · conv_lhs => arg 1; arg 1; change (39 : Fin 162) + exact row_39 + · conv_lhs => arg 1; arg 1; change (40 : Fin 162) + exact row_40 + · conv_lhs => arg 1; arg 1; change (41 : Fin 162) + exact row_41 + +private theorem block_7 (r : Fin 6) : rowCheck (routeAt (blockIndex 7 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (42 : Fin 162) + exact row_42 + · conv_lhs => arg 1; arg 1; change (43 : Fin 162) + exact row_43 + · conv_lhs => arg 1; arg 1; change (44 : Fin 162) + exact row_44 + · conv_lhs => arg 1; arg 1; change (45 : Fin 162) + exact row_45 + · conv_lhs => arg 1; arg 1; change (46 : Fin 162) + exact row_46 + · conv_lhs => arg 1; arg 1; change (47 : Fin 162) + exact row_47 + +private theorem block_8 (r : Fin 6) : rowCheck (routeAt (blockIndex 8 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (48 : Fin 162) + exact row_48 + · conv_lhs => arg 1; arg 1; change (49 : Fin 162) + exact row_49 + · conv_lhs => arg 1; arg 1; change (50 : Fin 162) + exact row_50 + · conv_lhs => arg 1; arg 1; change (51 : Fin 162) + exact row_51 + · conv_lhs => arg 1; arg 1; change (52 : Fin 162) + exact row_52 + · conv_lhs => arg 1; arg 1; change (53 : Fin 162) + exact row_53 + +private theorem block_9 (r : Fin 6) : rowCheck (routeAt (blockIndex 9 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (54 : Fin 162) + exact row_54 + · conv_lhs => arg 1; arg 1; change (55 : Fin 162) + exact row_55 + · conv_lhs => arg 1; arg 1; change (56 : Fin 162) + exact row_56 + · conv_lhs => arg 1; arg 1; change (57 : Fin 162) + exact row_57 + · conv_lhs => arg 1; arg 1; change (58 : Fin 162) + exact row_58 + · conv_lhs => arg 1; arg 1; change (59 : Fin 162) + exact row_59 + +private theorem block_10 (r : Fin 6) : rowCheck (routeAt (blockIndex 10 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (60 : Fin 162) + exact row_60 + · conv_lhs => arg 1; arg 1; change (61 : Fin 162) + exact row_61 + · conv_lhs => arg 1; arg 1; change (62 : Fin 162) + exact row_62 + · conv_lhs => arg 1; arg 1; change (63 : Fin 162) + exact row_63 + · conv_lhs => arg 1; arg 1; change (64 : Fin 162) + exact row_64 + · conv_lhs => arg 1; arg 1; change (65 : Fin 162) + exact row_65 + +private theorem block_11 (r : Fin 6) : rowCheck (routeAt (blockIndex 11 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (66 : Fin 162) + exact row_66 + · conv_lhs => arg 1; arg 1; change (67 : Fin 162) + exact row_67 + · conv_lhs => arg 1; arg 1; change (68 : Fin 162) + exact row_68 + · conv_lhs => arg 1; arg 1; change (69 : Fin 162) + exact row_69 + · conv_lhs => arg 1; arg 1; change (70 : Fin 162) + exact row_70 + · conv_lhs => arg 1; arg 1; change (71 : Fin 162) + exact row_71 + +private theorem block_12 (r : Fin 6) : rowCheck (routeAt (blockIndex 12 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (72 : Fin 162) + exact row_72 + · conv_lhs => arg 1; arg 1; change (73 : Fin 162) + exact row_73 + · conv_lhs => arg 1; arg 1; change (74 : Fin 162) + exact row_74 + · conv_lhs => arg 1; arg 1; change (75 : Fin 162) + exact row_75 + · conv_lhs => arg 1; arg 1; change (76 : Fin 162) + exact row_76 + · conv_lhs => arg 1; arg 1; change (77 : Fin 162) + exact row_77 + +private theorem block_13 (r : Fin 6) : rowCheck (routeAt (blockIndex 13 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (78 : Fin 162) + exact row_78 + · conv_lhs => arg 1; arg 1; change (79 : Fin 162) + exact row_79 + · conv_lhs => arg 1; arg 1; change (80 : Fin 162) + exact row_80 + · conv_lhs => arg 1; arg 1; change (81 : Fin 162) + exact row_81 + · conv_lhs => arg 1; arg 1; change (82 : Fin 162) + exact row_82 + · conv_lhs => arg 1; arg 1; change (83 : Fin 162) + exact row_83 + +private theorem block_14 (r : Fin 6) : rowCheck (routeAt (blockIndex 14 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (84 : Fin 162) + exact row_84 + · conv_lhs => arg 1; arg 1; change (85 : Fin 162) + exact row_85 + · conv_lhs => arg 1; arg 1; change (86 : Fin 162) + exact row_86 + · conv_lhs => arg 1; arg 1; change (87 : Fin 162) + exact row_87 + · conv_lhs => arg 1; arg 1; change (88 : Fin 162) + exact row_88 + · conv_lhs => arg 1; arg 1; change (89 : Fin 162) + exact row_89 + +private theorem block_15 (r : Fin 6) : rowCheck (routeAt (blockIndex 15 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (90 : Fin 162) + exact row_90 + · conv_lhs => arg 1; arg 1; change (91 : Fin 162) + exact row_91 + · conv_lhs => arg 1; arg 1; change (92 : Fin 162) + exact row_92 + · conv_lhs => arg 1; arg 1; change (93 : Fin 162) + exact row_93 + · conv_lhs => arg 1; arg 1; change (94 : Fin 162) + exact row_94 + · conv_lhs => arg 1; arg 1; change (95 : Fin 162) + exact row_95 + +private theorem block_16 (r : Fin 6) : rowCheck (routeAt (blockIndex 16 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (96 : Fin 162) + exact row_96 + · conv_lhs => arg 1; arg 1; change (97 : Fin 162) + exact row_97 + · conv_lhs => arg 1; arg 1; change (98 : Fin 162) + exact row_98 + · conv_lhs => arg 1; arg 1; change (99 : Fin 162) + exact row_99 + · conv_lhs => arg 1; arg 1; change (100 : Fin 162) + exact row_100 + · conv_lhs => arg 1; arg 1; change (101 : Fin 162) + exact row_101 + +private theorem block_17 (r : Fin 6) : rowCheck (routeAt (blockIndex 17 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (102 : Fin 162) + exact row_102 + · conv_lhs => arg 1; arg 1; change (103 : Fin 162) + exact row_103 + · conv_lhs => arg 1; arg 1; change (104 : Fin 162) + exact row_104 + · conv_lhs => arg 1; arg 1; change (105 : Fin 162) + exact row_105 + · conv_lhs => arg 1; arg 1; change (106 : Fin 162) + exact row_106 + · conv_lhs => arg 1; arg 1; change (107 : Fin 162) + exact row_107 + +private theorem block_18 (r : Fin 6) : rowCheck (routeAt (blockIndex 18 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (108 : Fin 162) + exact row_108 + · conv_lhs => arg 1; arg 1; change (109 : Fin 162) + exact row_109 + · conv_lhs => arg 1; arg 1; change (110 : Fin 162) + exact row_110 + · conv_lhs => arg 1; arg 1; change (111 : Fin 162) + exact row_111 + · conv_lhs => arg 1; arg 1; change (112 : Fin 162) + exact row_112 + · conv_lhs => arg 1; arg 1; change (113 : Fin 162) + exact row_113 + +private theorem block_19 (r : Fin 6) : rowCheck (routeAt (blockIndex 19 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (114 : Fin 162) + exact row_114 + · conv_lhs => arg 1; arg 1; change (115 : Fin 162) + exact row_115 + · conv_lhs => arg 1; arg 1; change (116 : Fin 162) + exact row_116 + · conv_lhs => arg 1; arg 1; change (117 : Fin 162) + exact row_117 + · conv_lhs => arg 1; arg 1; change (118 : Fin 162) + exact row_118 + · conv_lhs => arg 1; arg 1; change (119 : Fin 162) + exact row_119 + +private theorem block_20 (r : Fin 6) : rowCheck (routeAt (blockIndex 20 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (120 : Fin 162) + exact row_120 + · conv_lhs => arg 1; arg 1; change (121 : Fin 162) + exact row_121 + · conv_lhs => arg 1; arg 1; change (122 : Fin 162) + exact row_122 + · conv_lhs => arg 1; arg 1; change (123 : Fin 162) + exact row_123 + · conv_lhs => arg 1; arg 1; change (124 : Fin 162) + exact row_124 + · conv_lhs => arg 1; arg 1; change (125 : Fin 162) + exact row_125 + +private theorem block_21 (r : Fin 6) : rowCheck (routeAt (blockIndex 21 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (126 : Fin 162) + exact row_126 + · conv_lhs => arg 1; arg 1; change (127 : Fin 162) + exact row_127 + · conv_lhs => arg 1; arg 1; change (128 : Fin 162) + exact row_128 + · conv_lhs => arg 1; arg 1; change (129 : Fin 162) + exact row_129 + · conv_lhs => arg 1; arg 1; change (130 : Fin 162) + exact row_130 + · conv_lhs => arg 1; arg 1; change (131 : Fin 162) + exact row_131 + +private theorem block_22 (r : Fin 6) : rowCheck (routeAt (blockIndex 22 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (132 : Fin 162) + exact row_132 + · conv_lhs => arg 1; arg 1; change (133 : Fin 162) + exact row_133 + · conv_lhs => arg 1; arg 1; change (134 : Fin 162) + exact row_134 + · conv_lhs => arg 1; arg 1; change (135 : Fin 162) + exact row_135 + · conv_lhs => arg 1; arg 1; change (136 : Fin 162) + exact row_136 + · conv_lhs => arg 1; arg 1; change (137 : Fin 162) + exact row_137 + +private theorem block_23 (r : Fin 6) : rowCheck (routeAt (blockIndex 23 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (138 : Fin 162) + exact row_138 + · conv_lhs => arg 1; arg 1; change (139 : Fin 162) + exact row_139 + · conv_lhs => arg 1; arg 1; change (140 : Fin 162) + exact row_140 + · conv_lhs => arg 1; arg 1; change (141 : Fin 162) + exact row_141 + · conv_lhs => arg 1; arg 1; change (142 : Fin 162) + exact row_142 + · conv_lhs => arg 1; arg 1; change (143 : Fin 162) + exact row_143 + +private theorem block_24 (r : Fin 6) : rowCheck (routeAt (blockIndex 24 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (144 : Fin 162) + exact row_144 + · conv_lhs => arg 1; arg 1; change (145 : Fin 162) + exact row_145 + · conv_lhs => arg 1; arg 1; change (146 : Fin 162) + exact row_146 + · conv_lhs => arg 1; arg 1; change (147 : Fin 162) + exact row_147 + · conv_lhs => arg 1; arg 1; change (148 : Fin 162) + exact row_148 + · conv_lhs => arg 1; arg 1; change (149 : Fin 162) + exact row_149 + +private theorem block_25 (r : Fin 6) : rowCheck (routeAt (blockIndex 25 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (150 : Fin 162) + exact row_150 + · conv_lhs => arg 1; arg 1; change (151 : Fin 162) + exact row_151 + · conv_lhs => arg 1; arg 1; change (152 : Fin 162) + exact row_152 + · conv_lhs => arg 1; arg 1; change (153 : Fin 162) + exact row_153 + · conv_lhs => arg 1; arg 1; change (154 : Fin 162) + exact row_154 + · conv_lhs => arg 1; arg 1; change (155 : Fin 162) + exact row_155 + +private theorem block_26 (r : Fin 6) : rowCheck (routeAt (blockIndex 26 r)) = true := by + fin_cases r + · conv_lhs => arg 1; arg 1; change (156 : Fin 162) + exact row_156 + · conv_lhs => arg 1; arg 1; change (157 : Fin 162) + exact row_157 + · conv_lhs => arg 1; arg 1; change (158 : Fin 162) + exact row_158 + · conv_lhs => arg 1; arg 1; change (159 : Fin 162) + exact row_159 + · conv_lhs => arg 1; arg 1; change (160 : Fin 162) + exact row_160 + · conv_lhs => arg 1; arg 1; change (161 : Fin 162) + exact row_161 + +private theorem blocks_checked (q : Fin 27) (r : Fin 6) : + rowCheck (routeAt (blockIndex q r)) = true := by + fin_cases q + · exact block_0 r + · exact block_1 r + · exact block_2 r + · exact block_3 r + · exact block_4 r + · exact block_5 r + · exact block_6 r + · exact block_7 r + · exact block_8 r + · exact block_9 r + · exact block_10 r + · exact block_11 r + · exact block_12 r + · exact block_13 r + · exact block_14 r + · exact block_15 r + · exact block_16 r + · exact block_17 r + · exact block_18 r + · exact block_19 r + · exact block_20 r + · exact block_21 r + · exact block_22 r + · exact block_23 r + · exact block_24 r + · exact block_25 r + · exact block_26 r + +private theorem rows_checked (i : Fin 162) : rowCheck (routeAt i) = true := by + let q : Fin 27 := ⟨i.val / 6, by omega⟩ + let r : Fin 6 := ⟨i.val % 6, by omega⟩ + have heq : blockIndex q r = i := by + apply Fin.ext + simp only [blockIndex, q, r] + omega + have h := blocks_checked q r + rwa [heq] at h + +private theorem row_checked (e : ℕ × Variable × Route) (he : e ∈ incidenceRoutes) : + rowCheck e = true := by + obtain ⟨i, rfl⟩ := List.mem_iff_get.mp he + have hi : i.val < 162 := by simpa only [drawing_sizes.2] using i.isLt + have h := rows_checked ⟨i.val, hi⟩ + have heq : routeAt ⟨i.val, hi⟩ = incidenceRoutes.get i := + List.getD_eq_get incidenceRoutes (0, .center, []) i + rwa [heq] at h + +private theorem local_checked (e : ℕ × Variable × Route) (he : e ∈ incidenceRoutes) : + LocalSpec e := by + have h := row_checked e he + simp only [rowCheck, Bool.and_eq_true, decide_eq_true_eq] at h + exact h.1 + +/-- Every route starts at its clause and ends at its variable, with a valid clause index. -/ +theorem incidence_endpoints : ∀ e ∈ incidenceRoutes, + e.1 < 78 ∧ e.2.2.head? = some (clausePosition e.1) ∧ + e.2.2.getLast? = some (variablePosition e.2.1) := + fun e he => (local_checked e he).1 + +/-- All incidence routes have proper axis-aligned segments. -/ +theorem incidence_orthogonal : + ∀ e ∈ incidenceRoutes, IsOrthogonal e.2.2 := + fun e he => (local_checked e he).2.1 + +/-- All listed core points are inside the smaller rectangle `[20,220]²`. -/ +theorem core_bounds : ∀ e ∈ incidenceRoutes, ∀ p ∈ e.2.2, + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := + fun e he => (local_checked e he).2.2.1 + +/-- Exactly 407 proper segments and 2192 units of total Manhattan wire length. -/ +theorem route_sizes : + (incidenceRoutes.map fun e => e.2.2.consecutivePairs.length).sum = 407 ∧ + (incidenceRoutes.map fun e => wireLength e.2.2).sum = 2192 := by decide +kernel + +/-- Every incidence route lists each bend only once. -/ +theorem incidence_points_nodup : ∀ e ∈ incidenceRoutes, e.2.2.Nodup := + fun e he => (local_checked e he).2.2.2.1 + +/-- Finite simplicity certificate: distinct segments are disjoint except at a shared next bend. -/ +theorem incidence_simple_check : ∀ e ∈ incidenceRoutes, + ∀ a ∈ e.2.2.consecutivePairs, ∀ b ∈ e.2.2.consecutivePairs, a ≠ b → + segmentContactCheck (consecutiveContact a b) a.1 a.2 b.1 b.2 = true := + fun e he => (local_checked e he).2.2.2.2 + +/-- Exhaustive finite contact certificate for the fixed route table. -/ +theorem routes_contact_check : ∀ e ∈ incidenceRoutes, ∀ f ∈ incidenceRoutes, + e ≠ f → routeContactCheck (commonEndpoints e f) e.2.2 f.2.2 = true := by + intro e he f hf hne + have h := row_checked e he + simp only [rowCheck, Bool.and_eq_true] at h + have hf' := List.all_eq_true.mp h.2.1 f hf + simpa only [Bool.or_eq_true, beq_iff_eq, hne, false_or] using hf' + +/-- No route visits a nonincident variable vertex. -/ +theorem variable_avoidance_check : ∀ e ∈ incidenceRoutes, ∀ v, + v ≠ e.2.1 → routeContactCheck [] e.2.2 [variablePosition v] = true := by + intro e he v hv + have h := row_checked e he + simp only [rowCheck, Bool.and_eq_true] at h + have hv' := List.all_eq_true.mp h.2.2.1 v (mem_variableList v) + simpa only [Bool.or_eq_true, beq_iff_eq, hv, false_or] using hv' + +/-- No route visits a nonincident clause vertex. -/ +theorem clause_avoidance_check : ∀ e ∈ incidenceRoutes, ∀ i : Fin 78, + i.val ≠ e.1 → routeContactCheck [] e.2.2 [clausePosition i] = true := by + intro e he i hi + have h := row_checked e he + simp only [rowCheck, Bool.and_eq_true] at h + have hi' := List.all_eq_true.mp h.2.2.2 i (List.mem_finRange i) + simpa only [Bool.or_eq_true, beq_iff_eq, hi, false_or] using hi' + +/-- All eight access routes have their prescribed endpoints and proper orthogonal segments. -/ +theorem access_endpoints : ∀ s l, IsOrthogonal (accessRoute s l) ∧ + (accessRoute s l).head? = some (boundaryPort s l) ∧ + (accessRoute s l).getLast? = some (variablePosition (terminal s l)) := by decide +kernel + +/-- The eight access routes are mutually disjoint. -/ +theorem access_disjoint_check : ∀ p q : PairedPorts.Port, p ≠ q → + routeContactCheck [] (accessRoute p.1 p.2) (accessRoute q.1 q.2) = true := + PairedPorts.access_disjoint_check + +/-- Access to a port can meet an incidence route only at that port. -/ +theorem access_contact_check : ∀ s l, ∀ e ∈ incidenceRoutes, + routeContactCheck (if e.2.1 = terminal s l then [variablePosition (terminal s l)] else []) + (accessRoute s l) e.2.2 = true := by decide +kernel + +end Cslib.Logic.PL.CNF.PairedCrossover diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutData.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutData.lean new file mode 100644 index 000000000..f97d12560 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCrossoverLayoutData.lean @@ -0,0 +1,253 @@ +/- +Copyright (c) 2026 lean-trominoes contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine, Stefan Langerman, GPT 5.6 +-/ + +module +-- upstream: candidate + +public import Complexity.Logics.Propositional.CNF.Planar.PairedCrossover +public import Complexity.Foundations.Geometry.Orthogonal.PairedPorts +public import Complexity.Computability.Complexity.Problems.SAT.Planar.CrossoverLayoutData + +/-! +# Orthogonal drawing data for the paired-port crossover + +The old internal drawing is retained. Each old terminal is replaced by ports at offsets 110 +and 130, with the new implication clause at the old terminal position. Only the eight old +terminal incidence routes are rerouted. The shared paired accesses end at these eight ports. + +## Main definitions + +* `variablePosition`, `clausePositions`, `incidenceRoutes`: the 57-variable, 78-clause drawing. +* `accessRoute`: the eight accesses from `Orthogonal.PairedPorts`. + +## References + +The unchanged core comes from [Lichtenstein1982, Figs. 4–5] and [Tromino2025, Fig. 8(b)]. +The paired terminal modification is original. +Source credit for the core is in `CrossoverLayoutData`. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PairedCrossover + +open Orthogonal + +/-- The eight new ports use the shared paired interface; other vertices keep their positions. -/ +def variablePosition : Variable → Point + | .port s l => PairedPorts.variablePosition s l + | .center => Crossover.variablePosition .center + | .copy v i => Crossover.variablePosition (.copy v i) + +/-- The old clause positions followed by the four old terminal positions. -/ +def clausePositions : List Point := + Crossover.clausePositions ++ [(20, 120), (220, 120), (120, 20), (120, 220)] + +/-- Exact clause-to-variable incidence routes in clause and literal order. -/ +def incidenceRoutes : List (ℕ × Variable × Route) := + [ + (0, .port 0 0, [(40, 120), (40, 119), (21, 119), (21, 110), (20, 110)]), + (0, .copy .aInnerLeft 5, [(40, 120), (56, 120)]), + (1, .port 0 1, [(40, 140), (37, 140), (37, 135), (33, 135), (33, 131), (29, 131), (29, 130), + (20, 130)]), + (1, .copy .aInnerLeft 4, [(40, 140), (40, 137), (45, 137), (45, 133), (49, 133), (49, 129), + (53, 129), (53, 125), (56, 125), (56, 124)]), + (2, .copy .aInnerLeft 0, [(60, 60), (59, 60), (59, 115), (60, 115), (60, 116)]), + (2, .copy .upperLeft 0, [(60, 60), (60, 63), (65, 63), (65, 67), (69, 67), (69, 71), (73, 71), + (73, 75), (77, 75), (77, 79), (81, 79), (81, 83), (85, 83), (85, 87), (89, 87), (89, 91), + (93, 91), (93, 95), (96, 95), (96, 96)]), + (2, .copy .bInnerTop 5, [(60, 60), (116, 60)]), + (3, .copy .aInnerLeft 3, [(60, 180), (59, 180), (59, 125), (60, 125), (60, 124)]), + (3, .copy .lowerLeft 4, [(60, 180), (60, 177), (65, 177), (65, 173), (69, 173), (69, 169), (73, + 169), (73, 165), (77, 165), (77, 161), (81, 161), (81, 157), (85, 157), (85, 153), (89, 153), + (89, 149), (93, 149), (93, 145), (96, 145), (96, 144)]), + (3, .copy .bInnerBottom 5, [(60, 180), (116, 180)]), + (4, .copy .aInnerLeft 1, [(80, 100), (77, 100), (77, 105), (73, 105), (73, 109), (69, 109), + (69, 113), (65, 113), (65, 116), (64, 116)]), + (4, .copy .upperLeft 5, [(80, 100), (96, 100)]), + (5, .copy .upperLeft 4, [(80, 120), (80, 117), (85, 117), (85, 113), (89, 113), (89, 109), (93, + 109), (93, 105), (96, 105), (96, 104)]), + (5, .copy .lowerLeft 0, [(80, 120), (80, 123), (85, 123), (85, 127), (89, 127), (89, 131), (93, + 131), (93, 135), (96, 135), (96, 136)]), + (6, .copy .aInnerLeft 2, [(80, 140), (77, 140), (77, 135), (73, 135), (73, 131), (69, 131), + (69, 127), (65, 127), (65, 124), (64, 124)]), + (6, .copy .lowerLeft 5, [(80, 140), (96, 140)]), + (7, .port 2 0, [(100, 40), (100, 37), (105, 37), (105, 33), (109, 33), (109, 20), (110, 20)]), + (7, .copy .bInnerTop 0, [(100, 40), (100, 43), (105, 43), (105, 47), (109, 47), (109, 51), + (113, 51), (113, 55), (116, 55), (116, 56)]), + (8, .copy .upperLeft 1, [(100, 80), (100, 96)]), + (8, .copy .bInnerTop 4, [(100, 80), (100, 77), (105, 77), (105, 73), (109, 73), (109, 69), + (113, 69), (113, 65), (116, 65), (116, 64)]), + (9, .copy .upperLeft 3, [(100, 120), (100, 104)]), + (9, .copy .lowerLeft 1, [(100, 120), (100, 136)]), + (9, .center, [(100, 120), (120, 120)]), + (10, .copy .lowerLeft 3, [(100, 160), (100, 144)]), + (10, .copy .bInnerBottom 0, [(100, 160), (100, 163), (105, 163), (105, 167), (109, 167), (109, + 171), (113, 171), (113, 175), (116, 175), (116, 176)]), + (11, .port 2 1, [(120, 40), (121, 40), (121, 21), (130, 21), (130, 20)]), + (11, .copy .bInnerTop 1, [(120, 40), (120, 56)]), + (12, .copy .upperLeft 2, [(120, 100), (104, 100)]), + (12, .copy .upperRight 5, [(120, 100), (136, 100)]), + (13, .copy .lowerLeft 2, [(120, 140), (104, 140)]), + (13, .copy .lowerRight 5, [(120, 140), (136, 140)]), + (14, .copy .bInnerBottom 4, [(120, 200), (120, 184)]), + (14, .port 3 0, [(120, 200), (120, 201), (110, 201), (110, 220)]), + (15, .copy .bInnerTop 3, [(140, 80), (137, 80), (137, 75), (133, 75), (133, 71), (129, 71), + (129, 67), (125, 67), (125, 64), (124, 64)]), + (15, .copy .upperRight 0, [(140, 80), (140, 96)]), + (16, .center, [(140, 120), (120, 120)]), + (16, .copy .upperRight 4, [(140, 120), (140, 104)]), + (16, .copy .lowerRight 0, [(140, 120), (140, 136)]), + (17, .copy .bInnerBottom 1, [(140, 160), (137, 160), (137, 165), (133, 165), (133, 169), (129, + 169), (129, 173), (125, 173), (125, 176), (124, 176)]), + (17, .copy .lowerRight 4, [(140, 160), (140, 144)]), + (18, .copy .bInnerBottom 3, [(140, 200), (140, 197), (135, 197), (135, 193), (131, 193), (131, + 189), (127, 189), (127, 185), (124, 185), (124, 184)]), + (18, .port 3 1, [(140, 200), (137, 200), (137, 205), (133, 205), (133, 209), (133, 213), (130, + 213), (130, 220)]), + (19, .copy .upperRight 2, [(160, 100), (144, 100)]), + (19, .copy .aInnerRight 0, [(160, 100), (160, 103), (165, 103), (165, 107), (169, 107), (169, + 111), (173, 111), (173, 115), (176, 115), (176, 116)]), + (20, .copy .upperRight 3, [(160, 120), (157, 120), (157, 115), (153, 115), (153, 111), (149, + 111), (149, 107), (145, 107), (145, 104), (144, 104)]), + (20, .copy .lowerRight 1, [(160, 120), (160, 123), (155, 123), (155, 127), (151, 127), (151, + 131), (147, 131), (147, 135), (144, 135), (144, 136)]), + (21, .copy .lowerRight 2, [(160, 140), (144, 140)]), + (21, .copy .aInnerRight 5, [(160, 140), (160, 137), (165, 137), (165, 133), (169, 133), (169, + 129), (173, 129), (173, 125), (176, 125), (176, 124)]), + (22, .copy .bInnerTop 2, [(180, 60), (180, 59), (125, 59), (125, 60), (124, 60)]), + (22, .copy .upperRight 1, [(180, 60), (177, 60), (177, 65), (173, 65), (173, 69), (169, 69), + (169, 73), (165, 73), (165, 77), (161, 77), (161, 81), (157, 81), (157, 85), (153, 85), (153, + 89), (149, 89), (149, 93), (145, 93), (145, 96), (144, 96)]), + (22, .copy .aInnerRight 1, [(180, 60), (180, 116)]), + (23, .copy .bInnerBottom 2, [(180, 180), (180, 181), (125, 181), (125, 180), (124, 180)]), + (23, .copy .lowerRight 3, [(180, 180), (177, 180), (177, 175), (173, 175), (173, 171), (169, + 171), (169, 167), (165, 167), (165, 163), (161, 163), (161, 159), (157, 159), (157, 155), + (153, 155), (153, 151), (149, 151), (149, 147), (145, 147), (145, 144), (144, 144)]), + (23, .copy .aInnerRight 4, [(180, 180), (180, 124)]), + (24, .copy .aInnerRight 2, [(200, 100), (197, 100), (197, 105), (193, 105), (193, 109), (189, + 109), (189, 113), (185, 113), (185, 116), (184, 116)]), + (24, .port 1 0, [(200, 100), (200, 103), (205, 103), (205, 107), (209, 107), (209, 110), (220, + 110)]), + (25, .copy .aInnerRight 3, [(200, 120), (184, 120)]), + (25, .port 1 1, [(200, 120), (200, 121), (219, 121), (219, 130), (220, 130)]), + (26, .copy .aInnerLeft 0, [(62, 116), (60, 116)]), + (26, .copy .aInnerLeft 1, [(62, 116), (64, 116)]), + (27, .copy .aInnerLeft 1, [(64, 120), (64, 116)]), + (27, .copy .aInnerLeft 2, [(64, 120), (64, 124)]), + (28, .copy .aInnerLeft 2, [(62, 124), (64, 124)]), + (28, .copy .aInnerLeft 3, [(62, 124), (60, 124)]), + (29, .copy .aInnerLeft 3, [(58, 124), (60, 124)]), + (29, .copy .aInnerLeft 4, [(58, 124), (56, 124)]), + (30, .copy .aInnerLeft 4, [(56, 122), (56, 124)]), + (30, .copy .aInnerLeft 5, [(56, 122), (56, 120)]), + (31, .copy .aInnerLeft 5, [(56, 116), (56, 120)]), + (31, .copy .aInnerLeft 0, [(56, 116), (60, 116)]), + (32, .copy .upperLeft 0, [(98, 96), (96, 96)]), + (32, .copy .upperLeft 1, [(98, 96), (100, 96)]), + (33, .copy .upperLeft 1, [(104, 96), (100, 96)]), + (33, .copy .upperLeft 2, [(104, 96), (104, 100)]), + (34, .copy .upperLeft 2, [(104, 104), (104, 100)]), + (34, .copy .upperLeft 3, [(104, 104), (100, 104)]), + (35, .copy .upperLeft 3, [(98, 104), (100, 104)]), + (35, .copy .upperLeft 4, [(98, 104), (96, 104)]), + (36, .copy .upperLeft 4, [(96, 102), (96, 104)]), + (36, .copy .upperLeft 5, [(96, 102), (96, 100)]), + (37, .copy .upperLeft 5, [(96, 98), (96, 100)]), + (37, .copy .upperLeft 0, [(96, 98), (96, 96)]), + (38, .copy .lowerLeft 0, [(98, 136), (96, 136)]), + (38, .copy .lowerLeft 1, [(98, 136), (100, 136)]), + (39, .copy .lowerLeft 1, [(104, 136), (100, 136)]), + (39, .copy .lowerLeft 2, [(104, 136), (104, 140)]), + (40, .copy .lowerLeft 2, [(104, 144), (104, 140)]), + (40, .copy .lowerLeft 3, [(104, 144), (100, 144)]), + (41, .copy .lowerLeft 3, [(98, 144), (100, 144)]), + (41, .copy .lowerLeft 4, [(98, 144), (96, 144)]), + (42, .copy .lowerLeft 4, [(96, 142), (96, 144)]), + (42, .copy .lowerLeft 5, [(96, 142), (96, 140)]), + (43, .copy .lowerLeft 5, [(96, 138), (96, 140)]), + (43, .copy .lowerLeft 0, [(96, 138), (96, 136)]), + (44, .copy .bInnerTop 0, [(118, 56), (116, 56)]), + (44, .copy .bInnerTop 1, [(118, 56), (120, 56)]), + (45, .copy .bInnerTop 1, [(124, 56), (120, 56)]), + (45, .copy .bInnerTop 2, [(124, 56), (124, 60)]), + (46, .copy .bInnerTop 2, [(124, 62), (124, 60)]), + (46, .copy .bInnerTop 3, [(124, 62), (124, 64)]), + (47, .copy .bInnerTop 3, [(120, 64), (124, 64)]), + (47, .copy .bInnerTop 4, [(120, 64), (116, 64)]), + (48, .copy .bInnerTop 4, [(116, 62), (116, 64)]), + (48, .copy .bInnerTop 5, [(116, 62), (116, 60)]), + (49, .copy .bInnerTop 5, [(116, 58), (116, 60)]), + (49, .copy .bInnerTop 0, [(116, 58), (116, 56)]), + (50, .copy .bInnerBottom 0, [(120, 176), (116, 176)]), + (50, .copy .bInnerBottom 1, [(120, 176), (124, 176)]), + (51, .copy .bInnerBottom 1, [(124, 178), (124, 176)]), + (51, .copy .bInnerBottom 2, [(124, 178), (124, 180)]), + (52, .copy .bInnerBottom 2, [(124, 182), (124, 180)]), + (52, .copy .bInnerBottom 3, [(124, 182), (124, 184)]), + (53, .copy .bInnerBottom 3, [(122, 184), (124, 184)]), + (53, .copy .bInnerBottom 4, [(122, 184), (120, 184)]), + (54, .copy .bInnerBottom 4, [(116, 184), (120, 184)]), + (54, .copy .bInnerBottom 5, [(116, 184), (116, 180)]), + (55, .copy .bInnerBottom 5, [(116, 178), (116, 180)]), + (55, .copy .bInnerBottom 0, [(116, 178), (116, 176)]), + (56, .copy .upperRight 0, [(142, 96), (140, 96)]), + (56, .copy .upperRight 1, [(142, 96), (144, 96)]), + (57, .copy .upperRight 1, [(144, 98), (144, 96)]), + (57, .copy .upperRight 2, [(144, 98), (144, 100)]), + (58, .copy .upperRight 2, [(144, 102), (144, 100)]), + (58, .copy .upperRight 3, [(144, 102), (144, 104)]), + (59, .copy .upperRight 3, [(142, 104), (144, 104)]), + (59, .copy .upperRight 4, [(142, 104), (140, 104)]), + (60, .copy .upperRight 4, [(136, 104), (140, 104)]), + (60, .copy .upperRight 5, [(136, 104), (136, 100)]), + (61, .copy .upperRight 5, [(136, 96), (136, 100)]), + (61, .copy .upperRight 0, [(136, 96), (140, 96)]), + (62, .copy .lowerRight 0, [(142, 136), (140, 136)]), + (62, .copy .lowerRight 1, [(142, 136), (144, 136)]), + (63, .copy .lowerRight 1, [(144, 138), (144, 136)]), + (63, .copy .lowerRight 2, [(144, 138), (144, 140)]), + (64, .copy .lowerRight 2, [(144, 142), (144, 140)]), + (64, .copy .lowerRight 3, [(144, 142), (144, 144)]), + (65, .copy .lowerRight 3, [(142, 144), (144, 144)]), + (65, .copy .lowerRight 4, [(142, 144), (140, 144)]), + (66, .copy .lowerRight 4, [(136, 144), (140, 144)]), + (66, .copy .lowerRight 5, [(136, 144), (136, 140)]), + (67, .copy .lowerRight 5, [(136, 136), (136, 140)]), + (67, .copy .lowerRight 0, [(136, 136), (140, 136)]), + (68, .copy .aInnerRight 0, [(178, 116), (176, 116)]), + (68, .copy .aInnerRight 1, [(178, 116), (180, 116)]), + (69, .copy .aInnerRight 1, [(182, 116), (180, 116)]), + (69, .copy .aInnerRight 2, [(182, 116), (184, 116)]), + (70, .copy .aInnerRight 2, [(184, 118), (184, 116)]), + (70, .copy .aInnerRight 3, [(184, 118), (184, 120)]), + (71, .copy .aInnerRight 3, [(184, 124), (184, 120)]), + (71, .copy .aInnerRight 4, [(184, 124), (180, 124)]), + (72, .copy .aInnerRight 4, [(178, 124), (180, 124)]), + (72, .copy .aInnerRight 5, [(178, 124), (176, 124)]), + (73, .copy .aInnerRight 5, [(176, 120), (176, 124)]), + (73, .copy .aInnerRight 0, [(176, 120), (176, 116)]), + (74, .port 0 0, [(20, 120), (20, 110)]), + (74, .port 0 1, [(20, 120), (20, 130)]), + (75, .port 1 1, [(220, 120), (220, 130)]), + (75, .port 1 0, [(220, 120), (220, 110)]), + (76, .port 2 1, [(120, 20), (130, 20)]), + (76, .port 2 0, [(120, 20), (110, 20)]), + (77, .port 3 0, [(120, 220), (110, 220)]), + (77, .port 3 1, [(120, 220), (130, 220)]) + ] + +/-- Boundary access points agree definitionally with the shared paired interface. -/ +def boundaryPort : Fin 4 → Fin 2 → Point := PairedPorts.boundaryPort + +/-- Straight shared accesses to the eight degree-two terminals. -/ +def accessRoute : Fin 4 → Fin 2 → Route := PairedPorts.accessRoute + +/-- The terminal coordinates are exactly the shared paired-port positions. -/ +theorem terminal_position (s : Fin 4) (l : Fin 2) : + variablePosition (terminal s l) = PairedPorts.variablePosition s l := rfl + +end Cslib.Logic.PL.CNF.PairedCrossover From 8d9988a9719c15bef2177a8f3be75e0a8a5c3e94 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:36:37 -0400 Subject: [PATCH 6/9] refactor(planar-SAT): extract shared path certificates Part of #1144, split from the internally reviewed parsimonious planar SAT development. All six local validation gates and hygiene checks pass. No selected abstract changes. AI-assisted implementation and internal review with Codex gpt-6.1-sol; outside-fleet coordinator review remains required. --- Complexity.lean | 5 + .../Problems/SAT/Planar/Bounds.lean | 21 +- .../Problems/SAT/Planar/Compile.lean | 56 +-- .../Problems/SAT/Planar/Completeness.lean | 278 +------------ .../Problems/SAT/Planar/Correctness.lean | 114 +---- .../Problems/SAT/Planar/Geometry.lean | 335 +-------------- .../Problems/SAT/Planar/Occurrence.lean | 1 + .../Problems/SAT/Planar/PathAssignments.lean | 393 ++++++++++++++++++ .../Problems/SAT/Planar/PathBounds.lean | 113 +++++ .../Problems/SAT/Planar/PathData.lean | 83 ++++ .../Problems/SAT/Planar/PathGeometry.lean | 378 +++++++++++++++++ .../Problems/SAT/Planar/PathPolyTime.lean | 324 +++++++++++++++ .../Complexity/Problems/SAT/Planar/Paths.lean | 11 +- .../Problems/SAT/Planar/Paths/Occupancy.lean | 39 +- .../Problems/SAT/Planar/PolyTime.lean | 54 +-- .../Problems/SAT/Planar/Semantics.lean | 19 +- .../Complexity/Problems/SAT/Planar/Size.lean | 48 +-- 17 files changed, 1314 insertions(+), 958 deletions(-) create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PathAssignments.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PathBounds.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PathData.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PathGeometry.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PathPolyTime.lean diff --git a/Complexity.lean b/Complexity.lean index cfc6defa2..c7ad26cff 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -812,6 +812,11 @@ public import Complexity.Computability.Complexity.Problems.SAT.Planar.OrdinaryRi public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayout public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutChecks public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutData +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathGeometry +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathPolyTime public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths.Occupancy public import Complexity.Computability.Complexity.Problems.SAT.Planar.PolyTime diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Bounds.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Bounds.lean index 2aa32de78..53f6ab0f9 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Bounds.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Bounds.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds public import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalDrawing import Complexity.Computability.Complexity.Problems.SAT.Planar.Geometry @@ -50,12 +51,6 @@ These bounds concern the original finite compiler in `Compile` and its 240-squar namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal -/-- Horizontal side of the actual tiled drawing, including its last square. -/ -def gridWidth (φ : CNF ℕ) : ℕ := 240 * (8 * (SAT.incidenceGraph φ).1.length + 1) -/-- Vertical side of the actual tiled drawing, including its last square. -/ -def gridHeight (φ : CNF ℕ) : ℕ := - 240 * (8 * (SAT.incidenceGraph φ).1.length + 2 * (SAT.incidenceGraph φ).2.length + 1) - private def CellBound (φ : CNF ℕ) (p : Point) : Prop := 0 ≤ p.1 ∧ p.1 ≤ 8 * ((SAT.incidenceGraph φ).1.length : ℤ) ∧ 0 ≤ p.2 ∧ p.2 ≤ 8 * ((SAT.incidenceGraph φ).1.length : ℤ) + @@ -279,20 +274,6 @@ theorem drawing_fits (φ : CNF ℕ) : obtain ⟨pc, hpc, hr⟩ := List.mem_flatMap.mp hr exact (placedClauses_bounds φ pc hpc).2.2 r hr p hp -/-- The horizontal grid side is at most 1920 times the source incidence size plus one. -/ -theorem gridWidth_le (φ : CNF ℕ) : - gridWidth φ ≤ 1920 * (φ.numVars + φ.length + φ.vars.length + 1) := by - unfold gridWidth - rw [SAT.length_vertices_incidenceGraph] - nlinarith - -/-- The vertical grid side is at most 1920 times the source incidence size plus one. -/ -theorem gridHeight_le (φ : CNF ℕ) : - gridHeight φ ≤ 1920 * (φ.numVars + φ.length + φ.vars.length + 1) := by - unfold gridHeight - rw [SAT.length_vertices_incidenceGraph, SAT.length_edges_incidenceGraph] - nlinarith - private theorem access_cost (sites : List Point) (p q : Point) : wireLength (access sites p q) ≤ 240 ∧ (access sites p q).consecutivePairs.length ≤ 19 := by diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Compile.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Compile.lean index a14da4cc3..5b4c8d96e 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Compile.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Compile.lean @@ -7,6 +7,8 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Incidence public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalLayout public import Complexity.Foundations.Geometry.Orthogonal.Subdivision @@ -44,57 +46,9 @@ namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal -/-- A cell's ordinary signal, or a local variable of its crossover. -/ -abbrev Variable := Point × Option Crossover.SplitVariable - /-- A clause, its position, and one route per literal in literal order. -/ abbrev PlacedClause := Clause Variable × Point × List Route -/-- The explicit linear per-route expansion budget. -/ -def budget (φ : CNF ℕ) : ℕ := - 80 * (SAT.incidenceGraph φ).1.length + 10 * (SAT.incidenceGraph φ).2.length - -/-- Unit paths in the order of the original literal occurrences. -/ -def paths (φ : CNF ℕ) : List Route := - let G := SAT.incidenceGraph φ - (G.orthogonalRoutes G.canonicalPorts).map (subdivideWithin (budget φ)) - -/-- The path of a particular original literal occurrence. -/ -def occurrencePath (φ : CNF ℕ) (o : Occurrence ℕ) : Route := - (paths φ).getD ((Cslib.Logic.PL.occurrences φ).idxOf o) [] - -/-- Actual occupied crossing candidates, excluding every declared graph vertex center. -First-occurrence selection ensures exactly one local gadget per selected square. -/ -def crossingSites (φ : CNF ℕ) : List Point := - let G := SAT.incidenceGraph φ - let occupied := (G.crossingCandidates G.canonicalPorts).filter fun p => - !(G.orthogonalPositions.contains p) && - decide (2 ≤ ((paths φ).filter fun r => r.contains p).length) - Cslib.Complexity.selectDistinct occupied (List.replicate occupied.length true) - -/-- The selected cells are exactly occupied nonvertex candidates. -/ -theorem mem_crossingSites (φ : CNF ℕ) (p : Point) : - p ∈ crossingSites φ ↔ - p ∈ (SAT.incidenceGraph φ).crossingCandidates (SAT.incidenceGraph φ).canonicalPorts ∧ - p ∉ (SAT.incidenceGraph φ).orthogonalPositions ∧ - 2 ≤ ((paths φ).filter fun r => r.contains p).length := by - simp [crossingSites, mem_selectDistinct_replicate, Bool.and_eq_true] - -/-- Every selected square receives exactly one template. -/ -theorem crossingSites_nodup (φ : CNF ℕ) : (crossingSites φ).Nodup := nodup_selectDistinct _ _ - -/-- Side facing a neighboring square, in left/right/bottom/top order. -Only unit neighbors are used by the correctness theorem. -/ -def side (p q : Point) : Fin 4 := - if q.1 < p.1 then 0 else if p.1 < q.1 then 1 else if q.2 < p.2 then 2 else 3 - -/-- The center of a square of side 240 indexed by an integer grid point. -/ -def center (p : Point) : Point := cellOrigin 120 p + (120, 120) - -/-- The signal presented by a cell toward a neighboring cell. -/ -def signal (sites : List Point) (p q : Point) : Variable := - (p, if p ∈ sites then some (Crossover.splitTerminal (side p q)) else none) - /-- Geometric position of a structured output variable. -/ def variablePosition (v : Variable) : Point := cellOrigin 120 v.1 + v.2.elim (120, 120) Crossover.variablePosition @@ -104,12 +58,6 @@ def access (sites : List Point) (p q : Point) : Route := if p ∈ sites then translate (cellOrigin 120 p) (Crossover.accessRoute (side p q)) else [cellPort 120 p (side p q), center p] -/-- Every cell before the original clause vertex contributes an entry/exit signal pair. -At the source endpoint the unused entry defaults to the source's own ordinary signal. -/ -def wire (sites : List Point) (r : Route) : List (Variable × Variable) := - r.dropLast.zipIdx.map fun (p, i) => - (signal sites p (r.getD (i - 1) p), signal sites p (r.getD (i + 1) p)) - /-- One implication at the common side midpoint of adjacent occupied cells. Positive literals propagate toward the source, negative literals toward the clause. -/ def link (sites : List Point) (positive : Bool) (p q : Point) : PlacedClause := diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Completeness.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Completeness.lean index c61a182cb..3056a8b93 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Completeness.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Completeness.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Planar.Correctness import Mathlib.Data.List.GetD import Mathlib.Tactic.SplitIfs @@ -39,283 +40,6 @@ namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal -private theorem two_paths_filter_length (rs : List Route) (p : Point) {i j : ℕ} - (hi : i < rs.length) (hj : j < rs.length) (hij : i ≠ j) - (hpi : p ∈ rs[i]) (hpj : p ∈ rs[j]) : - 2 ≤ (rs.filter fun r => r.contains p).length := by - induction rs generalizing i j with - | nil => simp at hi - | cons r rs ih => - cases i with - | zero => - cases j with - | zero => exact False.elim (hij rfl) - | succ j => - have hj' : j < rs.length := by simpa using hj - have hm : rs[j] ∈ rs.filter (fun r => r.contains p) := - List.mem_filter.mpr ⟨List.getElem_mem (by simpa using hj), - List.contains_iff_mem.mpr hpj⟩ - obtain ⟨k, hk, _⟩ := List.mem_iff_getElem.mp hm - have hphead : p ∈ r := hpi - simp only [List.filter_cons, List.contains_iff_mem.mpr hphead, - ite_true, List.length_cons] - omega - | succ i => - cases j with - | zero => - have hi' : i < rs.length := by simpa using hi - have hm : rs[i] ∈ rs.filter (fun r => r.contains p) := - List.mem_filter.mpr ⟨List.getElem_mem (by simpa using hi), - List.contains_iff_mem.mpr hpi⟩ - obtain ⟨k, hk, _⟩ := List.mem_iff_getElem.mp hm - have hphead : p ∈ r := hpj - simp only [List.filter_cons, List.contains_iff_mem.mpr hphead, - ite_true, List.length_cons] - omega - | succ j => - have ht := ih (by simpa using hi) (by simpa using hj) (by omega) hpi hpj - rw [List.filter_cons] - split_ifs - · simp only [List.length_cons] - omega - · exact ht - -private theorem path_mem_paths_get {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} (hp : p ∈ occurrencePath φ o) : - p ∈ (paths φ)[(Cslib.Logic.PL.occurrences φ).idxOf o]'(by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ho) := by - unfold occurrencePath at hp - rwa [List.getD_eq_getElem _ _ (by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ho)] at hp - -/-- Two different occurrence paths sharing an ordinary cell meet at a declared vertex center. -/ -theorem shared_ordinary_cell_center {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) - {o t : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) - (ht : t ∈ Cslib.Logic.PL.occurrences φ) (hot : o ≠ t) {p : Point} - (hp : p ∉ crossingSites φ) (hpo : p ∈ occurrencePath φ o) - (hpt : p ∈ occurrencePath φ t) : p ∈ (SAT.incidenceGraph φ).orthogonalPositions := by - by_contra hc - have hij : (Cslib.Logic.PL.occurrences φ).idxOf o ≠ - (Cslib.Logic.PL.occurrences φ).idxOf t := fun he => hot ((List.idxOf_inj ho).1 he) - have hcandidate := Graph.unitPath_shared_mem_crossingCandidates (SAT.incidenceGraph φ) - (fun _ he => SAT.incidenceGraph_endpoints he) - (fun _ he => SAT.incidenceGraph_loopless he) (SAT.incidenceGraph_degree_le hw hv) - (occurrence_index_lt ho) (occurrence_index_lt ht) hij hc - (by simpa only [occurrencePath_eq_unitPath] using hpo) - (by simpa only [occurrencePath_eq_unitPath] using hpt) - apply hp - apply (mem_crossingSites φ p).2 - refine ⟨hcandidate, hc, ?_⟩ - exact two_paths_filter_length (paths φ) p - (by simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ho) - (by simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ht) - hij (path_mem_paths_get ho hpo) (path_mem_paths_get ht hpt) - -/-- No retained wire cell is the clause-center endpoint removed by `dropLast`. -/ -theorem clause_center_not_mem_dropLast {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) : - Layout.center (φ.distinctVars.length + o.1) ∉ (occurrencePath φ o).dropLast := by - intro hp - obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp hp - have hlen := occurrencePath_length_ge_two ho - have hk' : k < (occurrencePath φ o).length := by - simp only [List.length_dropLast] at hk - omega - have hlast := (occurrencePath_endpoints ho).2 - rw [List.getLast?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hlast - have heq' : (occurrencePath φ o)[k] = - (occurrencePath φ o)[(occurrencePath φ o).length - 1] := by - rw [← List.getElem_dropLast hk, heq, Option.some.inj hlast] - have hidx := (occurrencePath_nodup ho).getElem_inj_iff.1 heq' - simp only [List.length_dropLast] at hk - omega - -/-- A retained cell at a declared center is the source-variable center of its occurrence. -/ -theorem dropLast_center_eq_source {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} - (hp : p ∈ (occurrencePath φ o).dropLast) - (hc : p ∈ (SAT.incidenceGraph φ).orthogonalPositions) : - p = Layout.center (φ.distinctVars.idxOf o.2.2.1) := by - obtain ⟨w, hw, rfl⟩ := List.mem_map.mp hc - have hw' := List.mem_range.mp hw - have hwmem : w ∈ (SAT.incidenceGraph φ).1 := by - apply List.mem_range.mpr - simpa only [SAT.incidenceGraph, List.length_range] using hw' - have hi := List.idxOf_lt_length_of_mem hwmem - have hidx : (SAT.incidenceGraph φ).1.idxOf w = w := by - simpa only [SAT.incidenceGraph, List.getElem_range] using List.getElem_idxOf hi - have h := (Graph.unitPath_declared_center_iff (SAT.incidenceGraph φ) - (fun _ he => SAT.incidenceGraph_endpoints he) - (fun _ he => SAT.incidenceGraph_loopless he) (occurrence_index_lt ho) hwmem).1 - (by rw [hidx]; simpa only [occurrencePath_eq_unitPath] using List.dropLast_subset _ hp) - simp only [SAT.incidenceGraph, List.getElem_map, - List.getElem_idxOf (List.idxOf_lt_length_of_mem ho)] at h - rcases h with h | h - · rw [h] - · exact False.elim (clause_center_not_mem_dropLast ho (h ▸ hp)) - -/-- All occurrences using an ordinary retained cell have the same original variable. -/ -theorem ordinary_cell_source_eq {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) - {o t : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) - (ht : t ∈ Cslib.Logic.PL.occurrences φ) {p : Point} (hp : p ∉ crossingSites φ) - (hpo : p ∈ (occurrencePath φ o).dropLast) - (hpt : p ∈ (occurrencePath φ t).dropLast) : o.2.2.1 = t.2.2.1 := by - by_cases hot : o = t - · rw [hot] - have hc := shared_ordinary_cell_center hw hv ho ht hot hp - (List.dropLast_subset _ hpo) (List.dropLast_subset _ hpt) - have he := (dropLast_center_eq_source ho hpo hc).symm.trans - (dropLast_center_eq_source ht hpt hc) - have hx := congrArg Prod.fst he - simp only [Layout.center] at hx - have hi : φ.distinctVars.idxOf o.2.2.1 = φ.distinctVars.idxOf t.2.2.1 := by omega - apply (List.idxOf_inj ?_).1 hi - apply CNF.mem_distinctVars.mpr - rw [← Cslib.Logic.PL.map_occurrences] - exact List.mem_map_of_mem ho - -private theorem not_both_straight {r : Route} (hn : r.Nodup) {p : Point} (hp : p ∈ r) - (hh : StraightThrough r p (1, 0)) (hv : StraightThrough r p (0, 1)) : False := by - obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp hp - obtain ⟨_, _, hhn⟩ := hh.getD_neighbors hn hk heq - obtain ⟨_, _, hvn⟩ := hv.getD_neighbors hn hk heq - rcases hhn with ⟨hh, _⟩ | ⟨hh, _⟩ <;> rcases hvn with ⟨hv, _⟩ | ⟨hv, _⟩ - all_goals - have hx := congrArg Prod.fst (hh.symm.trans hv) - simp only [Prod.fst_sub, Prod.fst_add] at hx - omega - -/-- The occurrence carrying either coordinate-axis signal at a crossing is unique. -/ -theorem crossing_axis_occurrence_eq {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) - {o t : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) - (ht : t ∈ Cslib.Logic.PL.occurrences φ) {p d : Point} - (hp : p ∈ crossingSites φ) (hpo : p ∈ occurrencePath φ o) - (hpt : p ∈ occurrencePath φ t) (hd : d = (1, 0) ∨ d = (0, 1)) - (hs : StraightThrough (occurrencePath φ o) p d) - (hts : StraightThrough (occurrencePath φ t) p d) : o = t := by - by_contra hot - have hij : (Cslib.Logic.PL.occurrences φ).idxOf o ≠ - (Cslib.Logic.PL.occurrences φ).idxOf t := fun he => hot ((List.idxOf_inj ho).1 he) - have hn := Graph.unitPath_crossing_neighbors (SAT.incidenceGraph φ) - (fun _ he => SAT.incidenceGraph_endpoints he) - (fun _ he => SAT.incidenceGraph_loopless he) (SAT.incidenceGraph_degree_le hw hv) - (occurrence_index_lt ho) (occurrence_index_lt ht) hij - ((mem_crossingSites φ p).1 hp).2.1 - (by simpa only [occurrencePath_eq_unitPath] using hpo) - (by simpa only [occurrencePath_eq_unitPath] using hpt) - rw [← occurrencePath_eq_unitPath, ← occurrencePath_eq_unitPath] at hn - rcases hd with rfl | rfl - · rcases hn with ⟨_, htvert⟩ | ⟨hovert, _⟩ - · exact not_both_straight (occurrencePath_nodup ht) hpt hts htvert - · exact not_both_straight (occurrencePath_nodup ho) hpo hs hovert - · rcases hn with ⟨hohor, _⟩ | ⟨_, hthor⟩ - · exact not_both_straight (occurrencePath_nodup ho) hpo hohor hs - · exact not_both_straight (occurrencePath_nodup ht) hpt hthor hts - -open Classical in -private noncomputable def chosenValue (τ : ℕ → Bool) (P : Occurrence ℕ → Prop) : Bool := - if h : ∃ o, P o then τ (Classical.choose h).2.2.1 else false - -private theorem chosenValue_eq (τ : ℕ → Bool) (P : Occurrence ℕ → Prop) - {o : Occurrence ℕ} (ho : P o) (heq : ∀ t, P t → t.2.2.1 = o.2.2.1) : - chosenValue τ P = τ o.2.2.1 := by - classical - have h : ∃ t, P t := ⟨o, ho⟩ - simp only [chosenValue, dite_eq_left h] - rw [heq _ (Classical.choose_spec h)] - -/-- Every source assignment extends compatibly to all actual wire cells and crossover blocks. -The choice of auxiliary values is logical; this theorem makes no computational claim. -/ -theorem exists_compatible_assignment {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) (τ : ℕ → Bool) : - ∃ σ : Variable → Bool, - (∀ o ∈ Cslib.Logic.PL.occurrences φ, - ∀ c ∈ wire (crossingSites φ) (occurrencePath φ o), - σ c.1 = τ o.2.2.1 ∧ σ c.2 = τ o.2.2.1) ∧ - (∀ p ∈ crossingSites φ, Crossover.splitFormula.eval (fun v => σ (p, some v)) = true) := by - classical - let ordinary (p : Point) := chosenValue τ (fun o => - o ∈ Cslib.Logic.PL.occurrences φ ∧ p ∈ (occurrencePath φ o).dropLast) - let axis (p d : Point) := chosenValue τ (fun o => - o ∈ Cslib.Logic.PL.occurrences φ ∧ p ∈ occurrencePath φ o ∧ - StraightThrough (occurrencePath φ o) p d) - have hord {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} - (hp : p ∉ crossingSites φ) (hpo : p ∈ (occurrencePath φ o).dropLast) : - ordinary p = τ o.2.2.1 := by - apply chosenValue_eq τ _ ⟨ho, hpo⟩ - intro t ht - exact ordinary_cell_source_eq hw hv ht.1 ho hp ht.2 hpo - have haxis {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p d : Point} - (hp : p ∈ crossingSites φ) (hpo : p ∈ occurrencePath φ o) - (hd : d = (1, 0) ∨ d = (0, 1)) (hs : StraightThrough (occurrencePath φ o) p d) : - axis p d = τ o.2.2.1 := by - apply chosenValue_eq τ _ ⟨ho, hpo, hs⟩ - intro t ht - rw [crossing_axis_occurrence_eq hw hv ht.1 ho hp ht.2.1 hpo hd ht.2.2 hs] - let cross (p : Point) : Crossover.SplitVariable → Bool := - Classical.choose (Crossover.exists_split_holds (axis p (1, 0)) (axis p (0, 1))) - have hcross (p : Point) := - Classical.choose_spec (Crossover.exists_split_holds (axis p (1, 0)) (axis p (0, 1))) - let σ : Variable → Bool := fun v => match v.2 with - | none => ordinary v.1 - | some a => cross v.1 a - refine ⟨σ, ?_, fun p _ => (hcross p).1⟩ - intro o ho c hc - unfold wire at hc - obtain ⟨⟨p, k⟩, hpk, rfl⟩ := List.mem_map.mp hc - have hget := List.mem_zipIdx_iff_getElem?.mp hpk - obtain ⟨hk, heq⟩ := List.getElem?_eq_some_iff.mp hget - have hk' : k < (occurrencePath φ o).length := by - simp only [List.length_dropLast] at hk - omega - have heq' : (occurrencePath φ o)[k] = p := by - rw [← List.getElem_dropLast hk] - exact heq - have hpo : p ∈ occurrencePath φ o := heq' ▸ List.getElem_mem hk' - change (List.dropLast (occurrencePath φ o))[k] = p at heq - have hpod : p ∈ (occurrencePath φ o).dropLast := by - rw [← heq] - exact List.getElem_mem hk - by_cases hp : p ∈ crossingSites φ - · have hl : side p (p - (1, 0)) = 0 := by - rcases p with ⟨x, y⟩ - simp only [side, Prod.fst_sub, Prod.snd_sub] - split_ifs <;> first | rfl | omega - have hr : side p (p + (1, 0)) = 1 := by - rcases p with ⟨x, y⟩ - simp only [side, Prod.fst_add, Prod.snd_add] - split_ifs <;> first | rfl | omega - have ht : side p (p - (0, 1)) = 2 := by - rcases p with ⟨x, y⟩ - simp only [side, Prod.fst_sub, Prod.snd_sub] - split_ifs <;> first | rfl | omega - have hb : side p (p + (0, 1)) = 3 := by - rcases p with ⟨x, y⟩ - simp only [side, Prod.fst_add, Prod.snd_add] - split_ifs <;> first | rfl | omega - rcases occurrencePath_crossing_straight hw hv ho hp hpo with hs | hs - · have hav := haxis ho hp hpo (Or.inl rfl) hs - have hports := (hcross p).2 - obtain ⟨_, _, hnb⟩ := hs.getD_neighbors (occurrencePath_nodup ho) hk' heq' - rcases hnb with ⟨hprev, hnext⟩ | ⟨hprev, hnext⟩ - all_goals - simp only [hprev, hnext, signal, hp, ite_true, hl, hr, Crossover.splitTerminal] - exact ⟨by simpa only [σ, cross, hports.1, hports.2.1] using hav, - by simpa only [σ, cross, hports.1, hports.2.1] using hav⟩ - · have hav := haxis ho hp hpo (Or.inr rfl) hs - have hports := (hcross p).2.2.2 - obtain ⟨_, _, hnb⟩ := hs.getD_neighbors (occurrencePath_nodup ho) hk' heq' - rcases hnb with ⟨hprev, hnext⟩ | ⟨hprev, hnext⟩ - all_goals - simp only [hprev, hnext, signal, hp, ite_true, ht, hb, Crossover.splitTerminal] - exact ⟨by simpa only [σ, cross, hports.1, hports.2] using hav, - by simpa only [σ, cross, hports.1, hports.2] using hav⟩ - · simpa only [signal, hp, ite_false, σ] using - And.intro (hord ho hp hpod) (hord ho hp hpod) - /-- An actual raw compiler output has a satisfying extension of every source model. -/ theorem satisfiable_rawFormula_of_source {φ : CNF ℕ} (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Correctness.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Correctness.lean index df779538b..dc8fde83a 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Correctness.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Correctness.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Planar.Semantics public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths @@ -39,51 +40,6 @@ namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal -private theorem occurrence_var_mem {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) : o.2.2.1 ∈ φ.vars := by - rw [← Cslib.Logic.PL.map_occurrences] - exact List.mem_map_of_mem ho - -/-- The actual graph edge has the occurrence's original variable and clause labels. -/ -theorem occurrence_edgeEndpoint {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) (side : Bool) : - (SAT.incidenceGraph φ).edgeEndpoint ((Cslib.Logic.PL.occurrences φ).idxOf o) side = - if side then φ.distinctVars.length + o.1 else φ.distinctVars.idxOf o.2.2.1 := by - unfold Graph.edgeEndpoint - rw [List.getD_eq_getElem _ _ (occurrence_index_lt ho)] - simp only [SAT.incidenceGraph, List.getElem_map, - List.getElem_idxOf (List.idxOf_lt_length_of_mem ho)] - -private theorem other_path_index (rs : List Route) (p : Point) (i : ℕ) (hi : i < rs.length) - (hcount : 2 ≤ (rs.filter fun r => r.contains p).length) : - ∃ j, ∃ hj : j < rs.length, j ≠ i ∧ p ∈ rs[j] := by - induction rs generalizing i with - | nil => simp at hi - | cons r rs ih => - cases i with - | zero => - have hpos : 0 < (rs.filter fun r => r.contains p).length := by - rw [List.filter_cons] at hcount - split_ifs at hcount - · simp only [List.length_cons] at hcount - omega - · omega - obtain ⟨s, hs⟩ := List.exists_mem_of_length_pos hpos - have hm := List.mem_filter.mp hs - obtain ⟨j, hj, hsj⟩ := List.mem_iff_getElem.mp hm.1 - refine ⟨j + 1, by simp only [List.length_cons]; omega, by omega, ?_⟩ - simp only [List.getElem_cons_succ] - rw [hsj] - exact List.contains_iff_mem.mp hm.2 - | succ i => - by_cases hmem : r.contains p = true - · exact ⟨0, by simp, by omega, by simpa using List.contains_iff_mem.mp hmem⟩ - · have ht : 2 ≤ (rs.filter fun r => r.contains p).length := by - have hp : p ∉ r := fun hp => hmem (List.contains_iff_mem.mpr hp) - simpa [List.filter_cons, hp] using hcount - obtain ⟨j, hj, hji, hp⟩ := ih i (by simp only [List.length_cons] at hi; omega) ht - exact ⟨j + 1, by simp only [List.length_cons]; omega, by omega, by simpa using hp⟩ - /-- An actual crossover whose path goes straight through a cell equates the two signals read by the executable neighbor lookups, in either traversal direction. -/ theorem signals_eq_of_straight {φ : CNF ℕ} {σ : Variable → Bool} @@ -106,38 +62,6 @@ theorem signals_eq_of_straight {φ : CNF ℕ} {σ : Variable → Bool} · rw [hprev, hnext] exact (vertical_signal_eq hraw hp).symm -/-- A selected crossing is traversed straight along one coordinate axis by each occupying path. -/ -theorem occurrencePath_crossing_straight {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) - {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) - {p : Point} (hp : p ∈ crossingSites φ) (hpi : p ∈ occurrencePath φ o) : - StraightThrough (occurrencePath φ o) p (1, 0) ∨ - StraightThrough (occurrencePath φ o) p (0, 1) := by - have hsite := (mem_crossingSites φ p).1 hp - let G := SAT.incidenceGraph φ - have hG : ∀ e ∈ G.2, e.1 ∈ G.1 ∧ e.2 ∈ G.1 := - fun _ he => SAT.incidenceGraph_endpoints he - have hloop : ∀ e ∈ G.2, e.1 ≠ e.2 := fun _ he => SAT.incidenceGraph_loopless he - have hdeg : ∀ v, G.incidenceVertices.count v ≤ 3 := SAT.incidenceGraph_degree_le hw hv - have hi := occurrence_index_lt ho - obtain ⟨j, hj, hji, hpj⟩ := other_path_index (paths φ) p - ((Cslib.Logic.PL.occurrences φ).idxOf o) - (by simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi) hsite.2.2 - have hj' : j < G.2.length := by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hj - have hpi' : p ∈ G.unitPath ((Cslib.Logic.PL.occurrences φ).idxOf o) := by - simpa only [occurrencePath_eq_unitPath] using hpi - have hpj' : p ∈ G.unitPath j := by - unfold Graph.unitPath - change p ∈ (SAT.incidenceGraph φ).unitPaths.getD j [] - rw [← paths_eq_unitPaths, List.getD_eq_getElem _ _ hj] - exact hpj - have hnb := Graph.unitPath_crossing_neighbors G hG hloop hdeg hi hj' (Ne.symm hji) - hsite.2.1 hpi' hpj' - rcases hnb with ⟨h, _⟩ | ⟨h, _⟩ - · exact Or.inl (by simpa only [occurrencePath_eq_unitPath] using h) - · exact Or.inr (by simpa only [occurrencePath_eq_unitPath] using h) - /-- Actual cell entry/exit values agree: ordinary cells share one variable, while selected crossings use the certified opposite ports of the fixed crossover. -/ theorem wire_cells_agree {φ : CNF ℕ} @@ -164,42 +88,6 @@ theorem wire_cells_agree {φ : CNF ℕ} (occurrencePath_crossing_straight hw hv ho hp hpi) · simp only [signal, hp, ite_false] -/-- The initial source center is excluded from the actual selected crossing list. -/ -theorem source_center_not_crossing {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) : - Layout.center (φ.distinctVars.idxOf o.2.2.1) ∉ crossingSites φ := by - intro hp - have hvar := List.idxOf_lt_length_of_mem (CNF.mem_distinctVars.mpr (occurrence_var_mem ho)) - apply ((mem_crossingSites φ _).1 hp).2.1 - apply List.mem_map.mpr - refine ⟨φ.distinctVars.idxOf o.2.2.1, ?_, rfl⟩ - apply List.mem_range.mpr - simp only [SAT.incidenceGraph, List.length_range] - omega - -/-- The executable first entry is the original variable's ordinary cell signal. -/ -theorem wire_first_entry {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) (fallback : Variable) : - ((wire (crossingSites φ) (occurrencePath φ o)).headD (fallback, fallback)).1 = - (Layout.center (φ.distinctVars.idxOf o.2.2.1), none) := by - have hlen := occurrencePath_length_ge_two ho - have hwpos : 0 < (wire (crossingSites φ) (occurrencePath φ o)).length := by - rw [length_wire] - omega - have hhead : (wire (crossingSites φ) (occurrencePath φ o)).headD (fallback, fallback) = - (wire (crossingSites φ) (occurrencePath φ o))[0] := by - generalize hc : wire (crossingSites φ) (occurrencePath φ o) = xs at hwpos ⊢ - cases xs with - | nil => simp at hwpos - | cons a rest => rfl - rw [hhead, getElem_wire _ _ 0 (by omega)] - simp only [Nat.zero_sub] - rw [List.getD_eq_getElem _ _ (by omega)] - have hp := (occurrencePath_endpoints ho).1 - rw [List.head?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hp - rw [Option.some.inj hp] - simp only [signal, source_center_not_crossing ho, ite_false] - /-- Under the actual source width and literal-incidence bounds, raw output satisfaction implies source satisfaction. No local wire equality or endpoint assumption remains. -/ theorem eval_source_of_rawFormula {φ : CNF ℕ} diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Geometry.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Geometry.lean index d9971fc8e..e5ccff659 100755 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Geometry.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Geometry.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathGeometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalDrawing @@ -135,22 +136,6 @@ theorem crossingRoute_mem_openCell {e : ℕ × Crossover.SplitVariable × Route} rw [mem_openCell_add_origin_iff] simpa only [Int.reduceMul, Int.cast_ofNat] using Crossover.core_support_interior he hp -/-- Unit-adjacent integer cells differ by exactly one of the four cardinal displacements. -/ -theorem unit_neighbor_cases {p q : Point} (h : Orthogonal.length p q = 1) : - q = p + (-1, 0) ∨ q = p + (1, 0) ∨ q = p + (0, -1) ∨ q = p + (0, 1) := by - rcases p with ⟨px, py⟩ - rcases q with ⟨qx, qy⟩ - simp only [Orthogonal.length] at h - simp only [Prod.mk_add_mk, Prod.mk.injEq] - omega - -/-- Neighboring unit cells use exactly the same geometric midpoint for their shared port. -/ -theorem shared_cellPort {p q : Point} (h : Orthogonal.length p q = 1) : - cellPort 120 p (side p q) = cellPort 120 q (side q p) := by - rcases p with ⟨px, py⟩ - rcases unit_neighbor_cases h with rfl | rfl | rfl | rfl <;> - norm_num [side, cellPort, cellOrigin, Prod.ext_iff] <;> omega - private theorem mem_support_pair {a b : Point} {p : ℝ × ℝ} : p ∈ support [a, b] ↔ p ∈ segment a b := by rw [support_cons_cons] @@ -590,282 +575,6 @@ theorem crossingClause_mem_openCell {c : Point} {pc : PlacedClause} 0 < (Crossover.clausePositions.getD ci.2 (0, 0)).2 ∧ (Crossover.clausePositions.getD ci.2 (0, 0)).2 < 240 by omega) -private theorem consecutivePair_index {r : Route} {a b : Point} - (h : (a, b) ∈ r.consecutivePairs) : - ∃ (k : ℕ) (hk : k + 1 < r.length), r[k] = a ∧ r[k + 1] = b := by - obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp h - have hk' : k + 1 < r.length := by - simpa only [List.consecutivePairs, List.length_zip, List.length_tail, - min_eq_right (Nat.sub_le _ _), Nat.lt_sub_iff_add_lt] using hk - simp only [List.consecutivePairs, List.getElem_zip, List.getElem_tail] at heq - exact ⟨k, hk', congrArg Prod.fst heq, congrArg Prod.snd heq⟩ - -private theorem mem_of_adjacent {r : Route} {p q : Point} - (h : (p, q) ∈ r.consecutivePairs ∨ (q, p) ∈ r.consecutivePairs) : p ∈ r := by - rcases h with h | h - · exact (List.of_mem_zip h).1 - · exact List.mem_of_mem_tail (List.of_mem_zip h).2 - -private theorem neighbor_of_straight {r : Route} {p q d : Point} - (ht : StraightThrough r p d) (hn : r.Nodup) - (h : (p, q) ∈ r.consecutivePairs ∨ (q, p) ∈ r.consecutivePairs) : - q = p - d ∨ q = p + d := by - rcases h with h | h - · obtain ⟨k, hk, hp, hq⟩ := consecutivePair_index h - have hh := ht.getD_neighbors hn (by omega) hp - have hd : r.getD (k + 1) p = q := by rw [List.getD_eq_getElem _ _ hk]; exact hq - rcases hh.2.2 with ⟨_, hnext⟩ | ⟨_, hnext⟩ - · exact Or.inr (hd.symm.trans hnext) - · exact Or.inl (hd.symm.trans hnext) - · obtain ⟨k, hk, hq, hp⟩ := consecutivePair_index h - have hh := ht.getD_neighbors hn hk hp - have hd : r.getD (k + 1 - 1) p = q := by - rw [Nat.add_sub_cancel, List.getD_eq_getElem _ _ (by omega)] - exact hq - rcases hh.2.2 with ⟨hprev, _⟩ | ⟨hprev, _⟩ - · exact Or.inl (hd.symm.trans hprev) - · exact Or.inr (hd.symm.trans hprev) - -private theorem horizontal_vertical_neighbors {p q : Point} - (hx : q = p - (1, 0) ∨ q = p + (1, 0)) - (hy : q = p - (0, 1) ∨ q = p + (0, 1)) : False := by - rcases hx with hx | hx <;> rcases hy with hy | hy <;> - have h := hx.symm.trans hy <;> - have h' := congrArg Prod.fst h <;> simp at h' - -private theorem canonical_neighbor_unique (G : Graph) - (hG : ∀ e ∈ G.2, e.1 ∈ G.1 ∧ e.2 ∈ G.1) - (hloop : ∀ e ∈ G.2, e.1 ≠ e.2) - (hdeg : ∀ v, G.incidenceVertices.count v ≤ 3) {i j : ℕ} - (hi : i < G.2.length) (hj : j < G.2.length) (hij : i ≠ j) {p q : Point} - (hcenter : p ∉ G.orthogonalPositions) - (hiadj : (p, q) ∈ (G.unitPath i).consecutivePairs ∨ - (q, p) ∈ (G.unitPath i).consecutivePairs) - (hjadj : (p, q) ∈ (G.unitPath j).consecutivePairs ∨ - (q, p) ∈ (G.unitPath j).consecutivePairs) : False := by - have hn := Graph.unitPath_nodup G hG hloop hi - have hm := Graph.unitPath_nodup G hG hloop hj - rcases Graph.unitPath_crossing_neighbors G hG hloop hdeg hi hj hij hcenter - (mem_of_adjacent hiadj) (mem_of_adjacent hjadj) with ⟨hx, hy⟩ | ⟨hy, hx⟩ - · exact horizontal_vertical_neighbors (neighbor_of_straight hx hn hiadj) - (neighbor_of_straight hy hm hjadj) - · exact horizontal_vertical_neighbors (neighbor_of_straight hx hm hjadj) - (neighbor_of_straight hy hn hiadj) - -private theorem unit_not_both_centers (G : Graph) {p q : Point} - (hunit : Orthogonal.length p q = 1) (hp : p ∈ G.orthogonalPositions) - (hq : q ∈ G.orthogonalPositions) : False := by - obtain ⟨i, _, rfl⟩ := List.mem_map.mp hp - obtain ⟨j, _, rfl⟩ := List.mem_map.mp hq - simp only [Orthogonal.length, Layout.center] at hunit - omega - -/-- Two actual canonical paths cannot share a unit grid edge, even in opposite directions. -This count-free uniqueness statement also applies to the final step into a clause cell. -/ -theorem paths_shared_step_index {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) - {i j : ℕ} (hi : i < (paths φ).length) (hj : j < (paths φ).length) {p q : Point} - (he : (p, q) ∈ ((paths φ).getD i []).consecutivePairs) - (hf : (p, q) ∈ ((paths φ).getD j []).consecutivePairs ∨ - (q, p) ∈ ((paths φ).getD j []).consecutivePairs) : i = j := by - by_contra hij - have hi' : i < (SAT.incidenceGraph φ).2.length := by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi - have hj' : j < (SAT.incidenceGraph φ).2.length := by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hj - change (p, q) ∈ ((SAT.incidenceGraph φ).unitPath i).consecutivePairs at he - change (p, q) ∈ ((SAT.incidenceGraph φ).unitPath j).consecutivePairs ∨ - (q, p) ∈ ((SAT.incidenceGraph φ).unitPath j).consecutivePairs at hf - have hG : ∀ e ∈ (SAT.incidenceGraph φ).2, - e.1 ∈ (SAT.incidenceGraph φ).1 ∧ e.2 ∈ (SAT.incidenceGraph φ).1 := - fun _ => SAT.incidenceGraph_endpoints - have hloop : ∀ e ∈ (SAT.incidenceGraph φ).2, e.1 ≠ e.2 := - fun _ => SAT.incidenceGraph_loopless - have hdeg := SAT.incidenceGraph_degree_le hw hv - by_cases hp : p ∈ (SAT.incidenceGraph φ).orthogonalPositions - · have hq : q ∉ (SAT.incidenceGraph φ).orthogonalPositions := fun hq => - unit_not_both_centers _ (Graph.unitPath_unit _ hG hloop hi' he) hp hq - exact canonical_neighbor_unique _ hG hloop hdeg hi' hj' hij hq (Or.inr he) hf.symm - · exact canonical_neighbor_unique _ hG hloop hdeg hi' hj' hij hp (Or.inl he) hf - -/-- At a unit neighbor, the facing side determines the neighbor itself. -/ -theorem side_injective_on_unit_neighbors {p q t : Point} - (hq : Orthogonal.length p q = 1) (ht : Orthogonal.length p t = 1) - (hside : side p q = side p t) : q = t := by - rcases p with ⟨px, py⟩ - rcases unit_neighbor_cases hq with rfl | rfl | rfl | rfl <;> - rcases unit_neighbor_cases ht with rfl | rfl | rfl | rfl <;> - first | rfl | norm_num [side] at hside - -private def stepPorts (e : Point × Point) : List (Point × Fin 4) := - [(e.1, side e.1 e.2), (e.2, side e.2 e.1)] - -private theorem unit_reverse {p q : Point} (h : Orthogonal.length p q = 1) : - Orthogonal.length q p = 1 := by - simp only [Orthogonal.length] at h ⊢ - omega - -private theorem stepPorts_inter {e f : Point × Point} {x : Point × Fin 4} - (he : Orthogonal.length e.1 e.2 = 1) (hf : Orthogonal.length f.1 f.2 = 1) - (hx : x ∈ stepPorts e) (hy : x ∈ stepPorts f) : - e = f ∨ e = (f.2, f.1) := by - rcases e with ⟨a, b⟩ - rcases f with ⟨c, d⟩ - simp only [stepPorts, List.mem_cons, List.not_mem_nil, or_false] at hx hy - rcases hx with rfl | rfl <;> rcases hy with hy | hy - · obtain ⟨h, hs⟩ := Prod.mk.inj hy - subst c - have hb := side_injective_on_unit_neighbors he hf hs - exact Or.inl (Prod.ext rfl hb) - · obtain ⟨h, hs⟩ := Prod.mk.inj hy - subst d - have hb := side_injective_on_unit_neighbors he (unit_reverse hf) hs - exact Or.inr (Prod.ext rfl hb) - · obtain ⟨h, hs⟩ := Prod.mk.inj hy - subst c - have ha := side_injective_on_unit_neighbors (unit_reverse he) hf hs - exact Or.inr (Prod.ext ha rfl) - · obtain ⟨h, hs⟩ := Prod.mk.inj hy - subst d - have ha := side_injective_on_unit_neighbors (unit_reverse he) (unit_reverse hf) hs - exact Or.inl (Prod.ext ha rfl) - -private theorem routePorts_nodup {r : Route} (hn : r.Nodup) - (hu : ∀ e ∈ r.consecutivePairs, Orthogonal.length e.1 e.2 = 1) : - (r.consecutivePairs.flatMap stepPorts).Nodup := by - apply List.nodup_flatMap.mpr - constructor - · intro e he - have hne := (axisAligned_of_length_eq_one (hu e he)).2 - simp only [stepPorts, List.nodup_cons, List.not_mem_nil, List.nodup_nil, - List.mem_singleton, not_false_eq_true, and_true] - exact fun h => hne (congrArg Prod.fst h) - · rw [List.pairwise_iff_getElem] - intro i j hi hj hij x hx hy - have hi' : i + 1 < r.length := by - simp only [List.consecutivePairs, List.length_zip, List.length_tail] at hi - omega - have hj' : j + 1 < r.length := by - simp only [List.consecutivePairs, List.length_zip, List.length_tail] at hj - omega - rcases stepPorts_inter (hu _ (List.getElem_mem hi)) (hu _ (List.getElem_mem hj)) hx hy - with heq | heq - · have heq' : r[i] = r[j] := by - simpa only [List.consecutivePairs, List.getElem_zip, List.getElem_tail] using - congrArg Prod.fst heq - have := hn.eq_of_getElem_eq (by omega) (by omega) heq' - omega - · have heq' : r[i] = r[j + 1] := by - simpa only [List.consecutivePairs, List.getElem_zip, List.getElem_tail] using - congrArg Prod.fst heq - have := hn.eq_of_getElem_eq (by omega) hj' heq' - omega - -/-- All directed cell ports in the actual canonical paths are distinct. Both halves of every -unit step are included, even the final clause-directed half. No path witness is assumed. -/ -theorem pathPorts_nodup {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : - ((paths φ).flatMap fun r => r.consecutivePairs.flatMap fun e => - [(e.1, side e.1 e.2), (e.2, side e.2 e.1)]).Nodup := by - have hi_edge {i : ℕ} (hi : i < (paths φ).length) : - i < (SAT.incidenceGraph φ).2.length := by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi - have hn {i : ℕ} (hi : i < (paths φ).length) : ((paths φ)[i]).Nodup := by - have h := Graph.unitPath_nodup (SAT.incidenceGraph φ) - (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) (hi_edge hi) - change ((paths φ).getD i []).Nodup at h - rwa [List.getD_eq_getElem _ _ hi] at h - have hu {i : ℕ} (hi : i < (paths φ).length) {e : Point × Point} - (he : e ∈ ((paths φ)[i]).consecutivePairs) : Orthogonal.length e.1 e.2 = 1 := by - apply Graph.unitPath_unit (SAT.incidenceGraph φ) - (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) (hi_edge hi) - change e ∈ ((paths φ).getD i []).consecutivePairs - rwa [List.getD_eq_getElem _ _ hi] - change ((paths φ).flatMap fun r => r.consecutivePairs.flatMap stepPorts).Nodup - apply List.nodup_flatMap.mpr - constructor - · intro r hr - obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hr - exact routePorts_nodup (hn hi) (fun _ he => hu hi he) - · rw [List.pairwise_iff_getElem] - intro i j hi hj hij x hx hy - obtain ⟨e, he, hx⟩ := List.mem_flatMap.mp hx - obtain ⟨f, hf, hy⟩ := List.mem_flatMap.mp hy - have hi_step : e ∈ ((paths φ).getD i []).consecutivePairs := by - rwa [List.getD_eq_getElem _ _ hi] - have hj_step : (e.1, e.2) ∈ ((paths φ).getD j []).consecutivePairs ∨ - (e.2, e.1) ∈ ((paths φ).getD j []).consecutivePairs := by - rw [List.getD_eq_getElem _ _ hj] - rcases stepPorts_inter (hu hi he) (hu hj hf) hx hy with heq | heq - · exact Or.inl (heq.symm ▸ hf) - · exact Or.inr (by simpa only [heq] using hf) - have := paths_shared_step_index hw hv hi hj hi_step hj_step - omega - -/-- Equal boundary midpoints identify the same unordered unit grid edge. -/ -theorem cellPort_step_eq_cases {p q a b : Point} - (hpq : Orthogonal.length p q = 1) (hab : Orthogonal.length a b = 1) - (h : cellPort 120 p (side p q) = cellPort 120 a (side a b)) : - (p = a ∧ q = b) ∨ (p = b ∧ q = a) := by - rcases p with ⟨px, py⟩ - rcases a with ⟨ax, ay⟩ - rcases unit_neighbor_cases hpq with rfl | rfl | rfl | rfl <;> - rcases unit_neighbor_cases hab with rfl | rfl | rfl | rfl <;> - norm_num [side, cellPort, cellOrigin, Prod.ext_iff] at h ⊢ <;> omega - -/-- One geometric midpoint per actual unit step gives a duplicate-free list. -This includes final steps, so any prefix selection used for binary clauses remains distinct. -/ -theorem pathMidpoints_nodup {φ : CNF ℕ} - (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : - ((paths φ).flatMap fun r => r.consecutivePairs.map fun e => - cellPort 120 e.1 (side e.1 e.2)).Nodup := by - let steps := (paths φ).flatMap List.consecutivePairs - have hu {e : Point × Point} (he : e ∈ steps) : Orthogonal.length e.1 e.2 = 1 := by - obtain ⟨r, hr, he⟩ := List.mem_flatMap.mp he - obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hr - have hi' : i < (SAT.incidenceGraph φ).2.length := by - simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi - apply Graph.unitPath_unit (SAT.incidenceGraph φ) - (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) hi' - change e ∈ ((paths φ).getD i []).consecutivePairs - rwa [List.getD_eq_getElem _ _ hi] - have hp : (steps.flatMap stepPorts).Nodup := by - have h := pathPorts_nodup hw hv - change ((paths φ).flatMap fun r => r.consecutivePairs.flatMap stepPorts).Nodup at h - simpa only [steps, List.flatMap_assoc] using h - have hpair := (List.nodup_flatMap.mp hp).2 - have hmid : (steps.map fun e => cellPort 120 e.1 (side e.1 e.2)).Nodup := by - apply List.pairwise_map.mpr - apply hpair.imp_of_mem - intro e f he hf hd heq - have hx : (e.1, side e.1 e.2) ∈ stepPorts e := by simp [stepPorts] - rcases cellPort_step_eq_cases (hu he) (hu hf) heq with ⟨h₁, h₂⟩ | ⟨h₁, h₂⟩ - · have heq' : e = f := Prod.ext h₁ h₂ - exact hd hx (heq' ▸ hx) - · have heq' : e = (f.2, f.1) := Prod.ext h₁ h₂ - exact hd hx (by simp [stepPorts, heq']) - simpa only [steps, List.map_flatMap] using hmid - -/-- Distinct integer cells have distinct displayed centers. -/ -theorem center_injective : Function.Injective center := by - intro p q h - have hp := center_mem_openCell (r := 120) (by decide) p - have hq := center_mem_openCell (r := 120) (by decide) q - change realPoint (center p) ∈ openCell 120 p at hp - change realPoint (center q) ∈ openCell 120 q at hq - rw [← h] at hq - exact cell_eq_of_mem_open_closed (by decide) hp - ⟨hq.1.le, hq.2.1.le, hq.2.2.1.le, hq.2.2.2.le⟩ - -/-- Original clause cells are declared source centers and are never selected as crossings. -/ -theorem clause_cell_not_mem_crossingSites (φ : CNF ℕ) {i : ℕ} (hi : i < φ.length) : - Layout.center (φ.distinctVars.length + i) ∉ crossingSites φ := by - intro h - apply ((mem_crossingSites φ _).mp h).2.1 - refine List.mem_map.mpr ⟨φ.distinctVars.length + i, List.mem_range.mpr ?_, rfl⟩ - simp only [SAT.incidenceGraph, List.length_range] - omega - /-- Original clause positions are pairwise distinct, including repeated and empty clauses. -/ theorem originalPositions_nodup (φ : CNF ℕ) (sites : List Point) : ((originalClauses φ sites).map fun pc => pc.2.1).Nodup := by @@ -1049,48 +758,6 @@ theorem raw_variable_cell_membership (φ : CNF ℕ) {v : Variable} | exact Or.inr ⟨o, ho, (List.of_mem_zip he).1⟩ | exact Or.inr ⟨o, ho, List.mem_of_mem_tail (List.of_mem_zip he).2⟩ -private theorem occurrencePath_clause_center_iff {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) {i : ℕ} (hi : i < φ.length) : - Layout.center (φ.distinctVars.length + i) ∈ occurrencePath φ o ↔ i = o.1 := by - have hoidx := occurrence_index_lt ho - have h := Graph.unitPath_center_iff (SAT.incidenceGraph φ) - (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) hoidx - (w := φ.distinctVars.length + i) (by simp only [SAT.incidenceGraph, List.length_range]; omega) - have he : (SAT.incidenceGraph φ).2[(Cslib.Logic.PL.occurrences φ).idxOf o] = - (φ.distinctVars.idxOf o.2.2.1, φ.distinctVars.length + o.1) := by - simp only [SAT.incidenceGraph, List.getElem_map] - rw [List.getElem_idxOf (List.idxOf_lt_length_of_mem ho)] - have hb := SAT.incidenceGraph_bipartite (List.getElem_mem hoidx) - rw [he] at hb h - change Layout.center (φ.distinctVars.length + i) ∈ occurrencePath φ o ↔ - φ.distinctVars.length + i = (List.range _).idxOf (φ.distinctVars.idxOf o.2.2.1) ∨ - φ.distinctVars.length + i = (List.range _).idxOf (φ.distinctVars.length + o.1) at h - rw [idxOf_range_eq (by omega), idxOf_range_eq hb.2.2] at h - exact h.trans (by omega) - -/-- No source clause center is used as a signal cell before a path's final endpoint. -/ -theorem clause_cell_not_mem_path_dropLast {φ : CNF ℕ} {o : Occurrence ℕ} - (ho : o ∈ Cslib.Logic.PL.occurrences φ) {i : ℕ} (hi : i < φ.length) : - Layout.center (φ.distinctVars.length + i) ∉ (occurrencePath φ o).dropLast := by - intro hp - have hi' := (occurrencePath_clause_center_iff ho hi).mp - ((List.dropLast_sublist _).subset hp) - subst i - obtain ⟨k, hk, hget⟩ := List.mem_iff_getElem.mp hp - have hlen := occurrencePath_length_ge_two ho - have hend := (occurrencePath_endpoints ho).2 - rw [List.getLast?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hend - have hk' : k < (occurrencePath φ o).length := by - simp only [List.length_dropLast] at hk - omega - have heq : (occurrencePath φ o)[k] = - (occurrencePath φ o)[(occurrencePath φ o).length - 1] := by - rw [Option.some.inj hend] - simpa only [List.getElem_dropLast] using hget - have hidx := (occurrencePath_nodup ho).eq_of_getElem_eq hk' (by omega) heq - simp only [List.length_dropLast] at hk - omega - /-- Actual output variables never occupy a source clause cell. -/ theorem raw_variable_not_clause_cell {φ : CNF ℕ} {v : Variable} (hv : v ∈ (rawFormula φ).vars) {i : ℕ} (hi : i < φ.length) : diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Occurrence.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Occurrence.lean index 110a7990c..a24066603 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Occurrence.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Occurrence.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathGeometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Geometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths.Occupancy public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PathAssignments.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathAssignments.lean new file mode 100644 index 000000000..fb99e4398 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathAssignments.lean @@ -0,0 +1,393 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths +public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree +public import Complexity.Logics.Propositional.CNF.Planar.CrossoverSplit +import Complexity.Foundations.Data.List.Filter +import Mathlib.Data.List.GetD +import Mathlib.Tactic.SplitIfs + +/-! +# Compatible assignments on canonical paths + +The optional crossover tags are retained only as a logical assignment bridge for the +paired compiler. There is no second output formula or drawing. A compatible assignment +carries each source value along its path and satisfies each selected fixed crossover. + +## Main definitions + +* `Cslib.Complexity.SAT.PlanarCompile.Variable`, `.signal`, `.wire`: the logical path bridge. + +## Main results + +* `Cslib.Complexity.SAT.PlanarCompile.occurrencePath_crossing_straight`. +* `Cslib.Complexity.SAT.PlanarCompile.source_center_not_crossing`. +* `Cslib.Complexity.SAT.PlanarCompile.ordinary_cell_source_eq`. +* `Cslib.Complexity.SAT.PlanarCompile.crossing_axis_occurrence_eq`. +* `Cslib.Complexity.SAT.PlanarCompile.exists_compatible_assignment`. + +## References + +These are the shared canonical incidence-path constructions and certificates. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.PlanarCompile + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- A cell's ordinary signal, or a local variable of its crossover. -/ +abbrev Variable := Point × Option Crossover.SplitVariable + +/-- The signal presented by a cell toward a neighboring cell. -/ +def signal (sites : List Point) (p q : Point) : Variable := + (p, if p ∈ sites then some (Crossover.splitTerminal (side p q)) else none) + +/-- Every cell before the original clause vertex contributes an entry/exit signal pair. +At the source endpoint the unused entry defaults to the source's own ordinary signal. -/ +def wire (sites : List Point) (r : Route) : List (Variable × Variable) := + r.dropLast.zipIdx.map fun (p, i) => + (signal sites p (r.getD (i - 1) p), signal sites p (r.getD (i + 1) p)) + +/-- A path contributes one signal cell for each point before its final clause vertex. -/ +theorem length_wire (sites : List Point) (r : Route) : + (wire sites r).length = r.length - 1 := by simp [wire] + +/-- Every path of at least two points yields a nonempty cell wire. -/ +theorem wire_ne_nil (sites : List Point) (r : Route) (h : 2 ≤ r.length) : wire sites r ≠ [] := by + intro he + have hl := length_wire sites r + rw [he, List.length_nil] at hl + omega + +/-- Exact indexed cell signals in the executable wire list. -/ +theorem getElem_wire (sites : List Point) (r : Route) (i : ℕ) (hi : i < r.length - 1) : + (wire sites r)[i]'(by simpa [wire] using hi) = + (signal sites r[i] (r.getD (i - 1) r[i]), + signal sites r[i] (r.getD (i + 1) r[i])) := by + simp only [wire, List.getElem_map, List.getElem_zipIdx, Nat.zero_add, List.getElem_dropLast] + +/-- The actual graph edge has the occurrence's original variable and clause labels. -/ +theorem occurrence_edgeEndpoint {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) (side : Bool) : + (SAT.incidenceGraph φ).edgeEndpoint ((Cslib.Logic.PL.occurrences φ).idxOf o) side = + if side then φ.distinctVars.length + o.1 else φ.distinctVars.idxOf o.2.2.1 := by + unfold Graph.edgeEndpoint + rw [List.getD_eq_getElem _ _ (occurrence_index_lt ho)] + simp only [SAT.incidenceGraph, List.getElem_map, + List.getElem_idxOf (List.idxOf_lt_length_of_mem ho)] + +/-- A selected crossing is traversed straight along one coordinate axis by each occupying path. -/ +theorem occurrencePath_crossing_straight {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) + {p : Point} (hp : p ∈ crossingSites φ) (hpi : p ∈ occurrencePath φ o) : + StraightThrough (occurrencePath φ o) p (1, 0) ∨ + StraightThrough (occurrencePath φ o) p (0, 1) := by + have hsite := (mem_crossingSites φ p).1 hp + let G := SAT.incidenceGraph φ + have hG : ∀ e ∈ G.2, e.1 ∈ G.1 ∧ e.2 ∈ G.1 := + fun _ he => SAT.incidenceGraph_endpoints he + have hloop : ∀ e ∈ G.2, e.1 ≠ e.2 := fun _ he => SAT.incidenceGraph_loopless he + have hdeg : ∀ v, G.incidenceVertices.count v ≤ 3 := SAT.incidenceGraph_degree_le hw hv + have hi := occurrence_index_lt ho + obtain ⟨j, hj, hji, hpj⟩ := List.exists_other_index_of_two_le_filter_contains (paths φ) p + ((Cslib.Logic.PL.occurrences φ).idxOf o) + (by simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi) hsite.2.2 + have hj' : j < G.2.length := by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hj + have hpi' : p ∈ G.unitPath ((Cslib.Logic.PL.occurrences φ).idxOf o) := by + simpa only [occurrencePath_eq_unitPath] using hpi + have hpj' : p ∈ G.unitPath j := by + unfold Graph.unitPath + change p ∈ (SAT.incidenceGraph φ).unitPaths.getD j [] + rw [← paths_eq_unitPaths, List.getD_eq_getElem _ _ hj] + exact hpj + have hnb := Graph.unitPath_crossing_neighbors G hG hloop hdeg hi hj' (Ne.symm hji) + hsite.2.1 hpi' hpj' + rcases hnb with ⟨h, _⟩ | ⟨h, _⟩ + · exact Or.inl (by simpa only [occurrencePath_eq_unitPath] using h) + · exact Or.inr (by simpa only [occurrencePath_eq_unitPath] using h) + +/-- The initial source center is excluded from the actual selected crossing list. -/ +theorem source_center_not_crossing {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) : + Layout.center (φ.distinctVars.idxOf o.2.2.1) ∉ crossingSites φ := by + intro hp + have hvar := List.idxOf_lt_length_of_mem + (CNF.mem_distinctVars.mpr (variable_mem_of_mem_occurrences ho)) + apply ((mem_crossingSites φ _).1 hp).2.1 + apply List.mem_map.mpr + refine ⟨φ.distinctVars.idxOf o.2.2.1, ?_, rfl⟩ + apply List.mem_range.mpr + simp only [SAT.incidenceGraph, List.length_range] + omega + +/-- The executable first entry is the original variable's ordinary cell signal. -/ +theorem wire_first_entry {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) (fallback : Variable) : + ((wire (crossingSites φ) (occurrencePath φ o)).headD (fallback, fallback)).1 = + (Layout.center (φ.distinctVars.idxOf o.2.2.1), none) := by + have hlen := occurrencePath_length_ge_two ho + have hwpos : 0 < (wire (crossingSites φ) (occurrencePath φ o)).length := by + rw [length_wire] + omega + have hhead : (wire (crossingSites φ) (occurrencePath φ o)).headD (fallback, fallback) = + (wire (crossingSites φ) (occurrencePath φ o))[0] := by + generalize hc : wire (crossingSites φ) (occurrencePath φ o) = xs at hwpos ⊢ + cases xs with + | nil => simp at hwpos + | cons a rest => rfl + rw [hhead, getElem_wire _ _ 0 (by omega)] + simp only [Nat.zero_sub] + rw [List.getD_eq_getElem _ _ (by omega)] + have hp := (occurrencePath_endpoints ho).1 + rw [List.head?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hp + rw [Option.some.inj hp] + simp only [signal, source_center_not_crossing ho, ite_false] + +private theorem path_mem_paths_get {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} (hp : p ∈ occurrencePath φ o) : + p ∈ (paths φ)[(Cslib.Logic.PL.occurrences φ).idxOf o]'(by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ho) := by + unfold occurrencePath at hp + rwa [List.getD_eq_getElem _ _ (by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ho)] at hp + +/-- Two different occurrence paths sharing an ordinary cell meet at a declared vertex center. -/ +theorem shared_ordinary_cell_center {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {o t : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) + (ht : t ∈ Cslib.Logic.PL.occurrences φ) (hot : o ≠ t) {p : Point} + (hp : p ∉ crossingSites φ) (hpo : p ∈ occurrencePath φ o) + (hpt : p ∈ occurrencePath φ t) : p ∈ (SAT.incidenceGraph φ).orthogonalPositions := by + by_contra hc + have hij : (Cslib.Logic.PL.occurrences φ).idxOf o ≠ + (Cslib.Logic.PL.occurrences φ).idxOf t := fun he => hot ((List.idxOf_inj ho).1 he) + have hcandidate := Graph.unitPath_shared_mem_crossingCandidates (SAT.incidenceGraph φ) + (fun _ he => SAT.incidenceGraph_endpoints he) + (fun _ he => SAT.incidenceGraph_loopless he) (SAT.incidenceGraph_degree_le hw hv) + (occurrence_index_lt ho) (occurrence_index_lt ht) hij hc + (by simpa only [occurrencePath_eq_unitPath] using hpo) + (by simpa only [occurrencePath_eq_unitPath] using hpt) + apply hp + apply (mem_crossingSites φ p).2 + refine ⟨hcandidate, hc, ?_⟩ + exact List.two_le_filter_contains_of_indices (paths φ) p + (by simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ho) + (by simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using occurrence_index_lt ht) + hij (path_mem_paths_get ho hpo) (path_mem_paths_get ht hpt) + +/-- No retained wire cell is the clause-center endpoint removed by `dropLast`. -/ +theorem clause_center_not_mem_dropLast {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) : + Layout.center (φ.distinctVars.length + o.1) ∉ (occurrencePath φ o).dropLast := by + intro hp + obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp hp + have hlen := occurrencePath_length_ge_two ho + have hk' : k < (occurrencePath φ o).length := by + simp only [List.length_dropLast] at hk + omega + have hlast := (occurrencePath_endpoints ho).2 + rw [List.getLast?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hlast + have heq' : (occurrencePath φ o)[k] = + (occurrencePath φ o)[(occurrencePath φ o).length - 1] := by + rw [← List.getElem_dropLast hk, heq, Option.some.inj hlast] + have hidx := (occurrencePath_nodup ho).getElem_inj_iff.1 heq' + simp only [List.length_dropLast] at hk + omega + +/-- A retained cell at a declared center is the source-variable center of its occurrence. -/ +theorem dropLast_center_eq_source {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} + (hp : p ∈ (occurrencePath φ o).dropLast) + (hc : p ∈ (SAT.incidenceGraph φ).orthogonalPositions) : + p = Layout.center (φ.distinctVars.idxOf o.2.2.1) := by + obtain ⟨w, hw, rfl⟩ := List.mem_map.mp hc + have hw' := List.mem_range.mp hw + have hwmem : w ∈ (SAT.incidenceGraph φ).1 := by + apply List.mem_range.mpr + simpa only [SAT.incidenceGraph, List.length_range] using hw' + have hi := List.idxOf_lt_length_of_mem hwmem + have hidx : (SAT.incidenceGraph φ).1.idxOf w = w := by + simpa only [SAT.incidenceGraph, List.getElem_range] using List.getElem_idxOf hi + have h := (Graph.unitPath_declared_center_iff (SAT.incidenceGraph φ) + (fun _ he => SAT.incidenceGraph_endpoints he) + (fun _ he => SAT.incidenceGraph_loopless he) (occurrence_index_lt ho) hwmem).1 + (by rw [hidx]; simpa only [occurrencePath_eq_unitPath] using List.dropLast_subset _ hp) + simp only [SAT.incidenceGraph, List.getElem_map, + List.getElem_idxOf (List.idxOf_lt_length_of_mem ho)] at h + rcases h with h | h + · rw [h] + · exact False.elim (clause_center_not_mem_dropLast ho (h ▸ hp)) + +/-- All occurrences using an ordinary retained cell have the same original variable. -/ +theorem ordinary_cell_source_eq {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {o t : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) + (ht : t ∈ Cslib.Logic.PL.occurrences φ) {p : Point} (hp : p ∉ crossingSites φ) + (hpo : p ∈ (occurrencePath φ o).dropLast) + (hpt : p ∈ (occurrencePath φ t).dropLast) : o.2.2.1 = t.2.2.1 := by + by_cases hot : o = t + · rw [hot] + have hc := shared_ordinary_cell_center hw hv ho ht hot hp + (List.dropLast_subset _ hpo) (List.dropLast_subset _ hpt) + have he := (dropLast_center_eq_source ho hpo hc).symm.trans + (dropLast_center_eq_source ht hpt hc) + have hx := congrArg Prod.fst he + simp only [Layout.center] at hx + have hi : φ.distinctVars.idxOf o.2.2.1 = φ.distinctVars.idxOf t.2.2.1 := by omega + apply (List.idxOf_inj ?_).1 hi + apply CNF.mem_distinctVars.mpr + rw [← Cslib.Logic.PL.map_occurrences] + exact List.mem_map_of_mem ho + +private theorem not_both_straight {r : Route} (hn : r.Nodup) {p : Point} (hp : p ∈ r) + (hh : StraightThrough r p (1, 0)) (hv : StraightThrough r p (0, 1)) : False := by + obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp hp + obtain ⟨_, _, hhn⟩ := hh.getD_neighbors hn hk heq + obtain ⟨_, _, hvn⟩ := hv.getD_neighbors hn hk heq + rcases hhn with ⟨hh, _⟩ | ⟨hh, _⟩ <;> rcases hvn with ⟨hv, _⟩ | ⟨hv, _⟩ + all_goals + have hx := congrArg Prod.fst (hh.symm.trans hv) + simp only [Prod.fst_sub, Prod.fst_add] at hx + omega + +/-- The occurrence carrying either coordinate-axis signal at a crossing is unique. -/ +theorem crossing_axis_occurrence_eq {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {o t : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) + (ht : t ∈ Cslib.Logic.PL.occurrences φ) {p d : Point} + (hp : p ∈ crossingSites φ) (hpo : p ∈ occurrencePath φ o) + (hpt : p ∈ occurrencePath φ t) (hd : d = (1, 0) ∨ d = (0, 1)) + (hs : StraightThrough (occurrencePath φ o) p d) + (hts : StraightThrough (occurrencePath φ t) p d) : o = t := by + by_contra hot + have hij : (Cslib.Logic.PL.occurrences φ).idxOf o ≠ + (Cslib.Logic.PL.occurrences φ).idxOf t := fun he => hot ((List.idxOf_inj ho).1 he) + have hn := Graph.unitPath_crossing_neighbors (SAT.incidenceGraph φ) + (fun _ he => SAT.incidenceGraph_endpoints he) + (fun _ he => SAT.incidenceGraph_loopless he) (SAT.incidenceGraph_degree_le hw hv) + (occurrence_index_lt ho) (occurrence_index_lt ht) hij + ((mem_crossingSites φ p).1 hp).2.1 + (by simpa only [occurrencePath_eq_unitPath] using hpo) + (by simpa only [occurrencePath_eq_unitPath] using hpt) + rw [← occurrencePath_eq_unitPath, ← occurrencePath_eq_unitPath] at hn + rcases hd with rfl | rfl + · rcases hn with ⟨_, htvert⟩ | ⟨hovert, _⟩ + · exact not_both_straight (occurrencePath_nodup ht) hpt hts htvert + · exact not_both_straight (occurrencePath_nodup ho) hpo hs hovert + · rcases hn with ⟨hohor, _⟩ | ⟨_, hthor⟩ + · exact not_both_straight (occurrencePath_nodup ho) hpo hohor hs + · exact not_both_straight (occurrencePath_nodup ht) hpt hthor hts + +open Classical in +private noncomputable def chosenValue (τ : ℕ → Bool) (P : Occurrence ℕ → Prop) : Bool := + if h : ∃ o, P o then τ (Classical.choose h).2.2.1 else false + +private theorem chosenValue_eq (τ : ℕ → Bool) (P : Occurrence ℕ → Prop) + {o : Occurrence ℕ} (ho : P o) (heq : ∀ t, P t → t.2.2.1 = o.2.2.1) : + chosenValue τ P = τ o.2.2.1 := by + classical + have h : ∃ t, P t := ⟨o, ho⟩ + simp only [chosenValue, dite_eq_left h] + rw [heq _ (Classical.choose_spec h)] + +/-- Every source assignment extends compatibly to all actual wire cells and crossover blocks. +The choice of auxiliary values is logical; this theorem makes no computational claim. -/ +theorem exists_compatible_assignment {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) (τ : ℕ → Bool) : + ∃ σ : Variable → Bool, + (∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ c ∈ wire (crossingSites φ) (occurrencePath φ o), + σ c.1 = τ o.2.2.1 ∧ σ c.2 = τ o.2.2.1) ∧ + (∀ p ∈ crossingSites φ, Crossover.splitFormula.eval (fun v => σ (p, some v)) = true) := by + classical + let ordinary (p : Point) := chosenValue τ (fun o => + o ∈ Cslib.Logic.PL.occurrences φ ∧ p ∈ (occurrencePath φ o).dropLast) + let axis (p d : Point) := chosenValue τ (fun o => + o ∈ Cslib.Logic.PL.occurrences φ ∧ p ∈ occurrencePath φ o ∧ + StraightThrough (occurrencePath φ o) p d) + have hord {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} + (hp : p ∉ crossingSites φ) (hpo : p ∈ (occurrencePath φ o).dropLast) : + ordinary p = τ o.2.2.1 := by + apply chosenValue_eq τ _ ⟨ho, hpo⟩ + intro t ht + exact ordinary_cell_source_eq hw hv ht.1 ho hp ht.2 hpo + have haxis {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p d : Point} + (hp : p ∈ crossingSites φ) (hpo : p ∈ occurrencePath φ o) + (hd : d = (1, 0) ∨ d = (0, 1)) (hs : StraightThrough (occurrencePath φ o) p d) : + axis p d = τ o.2.2.1 := by + apply chosenValue_eq τ _ ⟨ho, hpo, hs⟩ + intro t ht + rw [crossing_axis_occurrence_eq hw hv ht.1 ho hp ht.2.1 hpo hd ht.2.2 hs] + let cross (p : Point) : Crossover.SplitVariable → Bool := + Classical.choose (Crossover.exists_split_holds (axis p (1, 0)) (axis p (0, 1))) + have hcross (p : Point) := + Classical.choose_spec (Crossover.exists_split_holds (axis p (1, 0)) (axis p (0, 1))) + let σ : Variable → Bool := fun v => match v.2 with + | none => ordinary v.1 + | some a => cross v.1 a + refine ⟨σ, ?_, fun p _ => (hcross p).1⟩ + intro o ho c hc + unfold wire at hc + obtain ⟨⟨p, k⟩, hpk, rfl⟩ := List.mem_map.mp hc + have hget := List.mem_zipIdx_iff_getElem?.mp hpk + obtain ⟨hk, heq⟩ := List.getElem?_eq_some_iff.mp hget + have hk' : k < (occurrencePath φ o).length := by + simp only [List.length_dropLast] at hk + omega + have heq' : (occurrencePath φ o)[k] = p := by + rw [← List.getElem_dropLast hk] + exact heq + have hpo : p ∈ occurrencePath φ o := heq' ▸ List.getElem_mem hk' + change (List.dropLast (occurrencePath φ o))[k] = p at heq + have hpod : p ∈ (occurrencePath φ o).dropLast := by + rw [← heq] + exact List.getElem_mem hk + by_cases hp : p ∈ crossingSites φ + · have hl : side p (p - (1, 0)) = 0 := by + rcases p with ⟨x, y⟩ + simp only [side, Prod.fst_sub, Prod.snd_sub] + split_ifs <;> first | rfl | omega + have hr : side p (p + (1, 0)) = 1 := by + rcases p with ⟨x, y⟩ + simp only [side, Prod.fst_add, Prod.snd_add] + split_ifs <;> first | rfl | omega + have ht : side p (p - (0, 1)) = 2 := by + rcases p with ⟨x, y⟩ + simp only [side, Prod.fst_sub, Prod.snd_sub] + split_ifs <;> first | rfl | omega + have hb : side p (p + (0, 1)) = 3 := by + rcases p with ⟨x, y⟩ + simp only [side, Prod.fst_add, Prod.snd_add] + split_ifs <;> first | rfl | omega + rcases occurrencePath_crossing_straight hw hv ho hp hpo with hs | hs + · have hav := haxis ho hp hpo (Or.inl rfl) hs + have hports := (hcross p).2 + obtain ⟨_, _, hnb⟩ := hs.getD_neighbors (occurrencePath_nodup ho) hk' heq' + rcases hnb with ⟨hprev, hnext⟩ | ⟨hprev, hnext⟩ + all_goals + simp only [hprev, hnext, signal, hp, ite_true, hl, hr, Crossover.splitTerminal] + exact ⟨by simpa only [σ, cross, hports.1, hports.2.1] using hav, + by simpa only [σ, cross, hports.1, hports.2.1] using hav⟩ + · have hav := haxis ho hp hpo (Or.inr rfl) hs + have hports := (hcross p).2.2.2 + obtain ⟨_, _, hnb⟩ := hs.getD_neighbors (occurrencePath_nodup ho) hk' heq' + rcases hnb with ⟨hprev, hnext⟩ | ⟨hprev, hnext⟩ + all_goals + simp only [hprev, hnext, signal, hp, ite_true, ht, hb, Crossover.splitTerminal] + exact ⟨by simpa only [σ, cross, hports.1, hports.2] using hav, + by simpa only [σ, cross, hports.1, hports.2] using hav⟩ + · simpa only [signal, hp, ite_false, σ] using + And.intro (hord ho hp hpod) (hord ho hp hpod) + +end Cslib.Complexity.SAT.PlanarCompile diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PathBounds.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathBounds.lean new file mode 100644 index 000000000..f5c04ed09 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathBounds.lean @@ -0,0 +1,113 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData +import Mathlib.Algebra.BigOperators.Ring.List +import Mathlib.Data.List.GetD +import Mathlib.Tactic.Linarith + +/-! +# Bounds for shared incidence-path data + +Canonical path budgets, crossing counts, retained-gap counts, and 240-macrocell grid +sides are independent of the emitted clause template. No output formula is defined here. + +## Main definitions + +* `Cslib.Complexity.SAT.PlanarCompile.linkCount`: retained path gaps. +* `Cslib.Complexity.SAT.PlanarCompile.gridWidth`, `.gridHeight`: shared macrocell grid sides. + +## Main results + +* `Cslib.Complexity.SAT.PlanarCompile.crossingSites_length_le`. +* `Cslib.Complexity.SAT.PlanarCompile.paths_length`. +* `Cslib.Complexity.SAT.PlanarCompile.path_length_le`. +* `Cslib.Complexity.SAT.PlanarCompile.linkCount_le`. +* `Cslib.Complexity.SAT.PlanarCompile.gridWidth_le`. +* `Cslib.Complexity.SAT.PlanarCompile.gridHeight_le`. + +## References + +These are the shared canonical incidence-path constructions and certificates. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.PlanarCompile + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- Number of retained path gaps before the final step into each source clause. -/ +def linkCount (φ : CNF ℕ) : ℕ := + ((Cslib.Logic.PL.occurrences φ).map fun o => (occurrencePath φ o).length - 2).sum + +/-- Selected crossing sites form a sublist of the quadratic canonical cover after deduplication. +The bound counts all source literal incidences, including repetitions. -/ +theorem crossingSites_length_le (φ : CNF ℕ) : + (crossingSites φ).length ≤ 2 * φ.vars.length ^ 2 := by + unfold crossingSites + rw [length_selectDistinct_replicate] + exact (List.toFinset_card_le _).trans ((List.length_filter_le _ _).trans (by + simp [Graph.crossingCandidates_length, SAT.length_edges_incidenceGraph])) + +/-- There is one unit path per original literal incidence. -/ +theorem paths_length (φ : CNF ℕ) : (paths φ).length = φ.vars.length := by + simp [paths, SAT.length_edges_incidenceGraph] + +/-- The bounded enumerator emits at most five segment budgets plus the final endpoint. +This unconditional bound also applies to inputs that fail the degree-three restriction. -/ +theorem path_length_le {φ : CNF ℕ} {r : Route} (hr : r ∈ paths φ) : + r.length ≤ 5 * budget φ + 1 := by + change r ∈ ((SAT.incidenceGraph φ).orthogonalRoutes + (SAT.incidenceGraph φ).canonicalPorts).map (subdivideWithin (budget φ)) at hr + obtain ⟨base, hbase, rfl⟩ := List.mem_map.mp hr + have hs : base.consecutivePairs.length = 5 := by + obtain ⟨⟨e, i⟩, _, rfl⟩ := List.mem_map.mp hbase + rfl + simpa only [hs, Nat.mul_comm] using subdivideWithin_length_le (budget φ) base + +/-- Indexed access to a path obeys the same bound, including the empty fallback. -/ +theorem occurrencePath_length_le (φ : CNF ℕ) (o : Occurrence ℕ) : + (occurrencePath φ o).length ≤ 5 * budget φ + 1 := by + unfold occurrencePath + by_cases h : (Cslib.Logic.PL.occurrences φ).idxOf o < (paths φ).length + · rw [List.getD_eq_getElem _ _ h] + exact path_length_le (List.getElem_mem h) + · rw [List.getD_eq_default _ _ (Nat.le_of_not_gt h)] + exact Nat.zero_le _ + +/-- There are quadratically many links, even before checking the source restrictions. -/ +theorem linkCount_le (φ : CNF ℕ) : + linkCount φ ≤ φ.vars.length * (5 * budget φ + 1) := by + calc + _ ≤ ((Cslib.Logic.PL.occurrences φ).map fun _ => 5 * budget φ + 1).sum := + List.sum_le_sum fun o _ => (Nat.sub_le _ _).trans (occurrencePath_length_le φ o) + _ = _ := by simp [Cslib.Logic.PL.length_occurrences, Nat.mul_comm] + +/-- Horizontal side of the actual tiled drawing, including its last square. -/ +def gridWidth (φ : CNF ℕ) : ℕ := 240 * (8 * (SAT.incidenceGraph φ).1.length + 1) +/-- Vertical side of the actual tiled drawing, including its last square. -/ +def gridHeight (φ : CNF ℕ) : ℕ := + 240 * (8 * (SAT.incidenceGraph φ).1.length + 2 * (SAT.incidenceGraph φ).2.length + 1) + +/-- The horizontal grid side is at most 1920 times the source incidence size plus one. -/ +theorem gridWidth_le (φ : CNF ℕ) : + gridWidth φ ≤ 1920 * (φ.numVars + φ.length + φ.vars.length + 1) := by + unfold gridWidth + rw [SAT.length_vertices_incidenceGraph] + nlinarith + +/-- The vertical grid side is at most 1920 times the source incidence size plus one. -/ +theorem gridHeight_le (φ : CNF ℕ) : + gridHeight φ ≤ 1920 * (φ.numVars + φ.length + φ.vars.length + 1) := by + unfold gridHeight + rw [SAT.length_vertices_incidenceGraph, SAT.length_edges_incidenceGraph] + nlinarith + +end Cslib.Complexity.SAT.PlanarCompile diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PathData.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathData.lean new file mode 100644 index 000000000..f08b7f279 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathData.lean @@ -0,0 +1,83 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Incidence +public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalLayout +public import Complexity.Foundations.Geometry.Orthogonal.Subdivision +public import Complexity.Foundations.Geometry.Orthogonal.Macrocell + +/-! +# Canonical incidence-path data + +The retained shared data consist of bounded unit paths, selected crossing cells, and the +common macrocell coordinates. No clauses, formula, or drawing are assembled here. + +## Main definitions + +* `Cslib.Complexity.SAT.PlanarCompile.budget`, `.paths`, `.occurrencePath`. +* `Cslib.Complexity.SAT.PlanarCompile.crossingSites`, `.side`, `.center`. + +## Main results + +* `Cslib.Complexity.SAT.PlanarCompile.mem_crossingSites`. +* `Cslib.Complexity.SAT.PlanarCompile.crossingSites_nodup`. + +## References + +These are the shared canonical incidence-path constructions and certificates. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.PlanarCompile + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- The explicit linear per-route expansion budget. -/ +def budget (φ : CNF ℕ) : ℕ := + 80 * (SAT.incidenceGraph φ).1.length + 10 * (SAT.incidenceGraph φ).2.length + +/-- Unit paths in the order of the original literal occurrences. -/ +def paths (φ : CNF ℕ) : List Route := + let G := SAT.incidenceGraph φ + (G.orthogonalRoutes G.canonicalPorts).map (subdivideWithin (budget φ)) + +/-- The path of a particular original literal occurrence. -/ +def occurrencePath (φ : CNF ℕ) (o : Occurrence ℕ) : Route := + (paths φ).getD ((Cslib.Logic.PL.occurrences φ).idxOf o) [] + +/-- Actual occupied crossing candidates, excluding every declared graph vertex center. +First-occurrence selection ensures exactly one local gadget per selected square. -/ +def crossingSites (φ : CNF ℕ) : List Point := + let G := SAT.incidenceGraph φ + let occupied := (G.crossingCandidates G.canonicalPorts).filter fun p => + !(G.orthogonalPositions.contains p) && + decide (2 ≤ ((paths φ).filter fun r => r.contains p).length) + Cslib.Complexity.selectDistinct occupied (List.replicate occupied.length true) + +/-- The selected cells are exactly occupied nonvertex candidates. -/ +theorem mem_crossingSites (φ : CNF ℕ) (p : Point) : + p ∈ crossingSites φ ↔ + p ∈ (SAT.incidenceGraph φ).crossingCandidates (SAT.incidenceGraph φ).canonicalPorts ∧ + p ∉ (SAT.incidenceGraph φ).orthogonalPositions ∧ + 2 ≤ ((paths φ).filter fun r => r.contains p).length := by + simp [crossingSites, mem_selectDistinct_replicate, Bool.and_eq_true] + +/-- Every selected square receives exactly one template. -/ +theorem crossingSites_nodup (φ : CNF ℕ) : (crossingSites φ).Nodup := nodup_selectDistinct _ _ + +/-- Side facing a neighboring square, in left/right/bottom/top order. +Only unit neighbors are used by the correctness theorem. -/ +def side (p q : Point) : Fin 4 := + if q.1 < p.1 then 0 else if p.1 < q.1 then 1 else if q.2 < p.2 then 2 else 3 + +/-- The center of a square of side 240 indexed by an integer grid point. -/ +def center (p : Point) : Point := cellOrigin 120 p + (120, 120) + +end Cslib.Complexity.SAT.PlanarCompile diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PathGeometry.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathGeometry.lean new file mode 100644 index 000000000..21e494e60 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathGeometry.lean @@ -0,0 +1,378 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths +public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths.Occupancy +public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree +import Complexity.Foundations.Data.List.Range +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup +import Mathlib.Data.List.Flatten +import Mathlib.Tactic.NormNum + +/-! +# Geometry of canonical incidence paths + +Shared unit-step, directed-port and midpoint uniqueness certificates support the paired +compiler without retaining the obsolete directed-output formula or its drawing. + +## Main results + +* `Cslib.Complexity.SAT.PlanarCompile.paths_shared_step_index`. +* `Cslib.Complexity.SAT.PlanarCompile.pathPorts_nodup`. +* `Cslib.Complexity.SAT.PlanarCompile.pathMidpoints_nodup`. +* `Cslib.Complexity.SAT.PlanarCompile.clause_cell_not_mem_crossingSites`. +* `Cslib.Complexity.SAT.PlanarCompile.clause_cell_not_mem_path_dropLast`. + +## References + +These are the shared canonical incidence-path constructions and certificates. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.PlanarCompile + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- Unit-adjacent integer cells differ by exactly one of the four cardinal displacements. -/ +theorem unit_neighbor_cases {p q : Point} (h : Orthogonal.length p q = 1) : + q = p + (-1, 0) ∨ q = p + (1, 0) ∨ q = p + (0, -1) ∨ q = p + (0, 1) := by + rcases p with ⟨px, py⟩ + rcases q with ⟨qx, qy⟩ + simp only [Orthogonal.length] at h + simp only [Prod.mk_add_mk, Prod.mk.injEq] + omega + +/-- Neighboring unit cells use exactly the same geometric midpoint for their shared port. -/ +theorem shared_cellPort {p q : Point} (h : Orthogonal.length p q = 1) : + cellPort 120 p (side p q) = cellPort 120 q (side q p) := by + rcases p with ⟨px, py⟩ + rcases unit_neighbor_cases h with rfl | rfl | rfl | rfl <;> + norm_num [side, cellPort, cellOrigin, Prod.ext_iff] <;> omega + +private theorem consecutivePair_index {r : Route} {a b : Point} + (h : (a, b) ∈ r.consecutivePairs) : + ∃ (k : ℕ) (hk : k + 1 < r.length), r[k] = a ∧ r[k + 1] = b := by + obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp h + have hk' : k + 1 < r.length := by + simpa only [List.consecutivePairs, List.length_zip, List.length_tail, + min_eq_right (Nat.sub_le _ _), Nat.lt_sub_iff_add_lt] using hk + simp only [List.consecutivePairs, List.getElem_zip, List.getElem_tail] at heq + exact ⟨k, hk', congrArg Prod.fst heq, congrArg Prod.snd heq⟩ + +private theorem mem_of_adjacent {r : Route} {p q : Point} + (h : (p, q) ∈ r.consecutivePairs ∨ (q, p) ∈ r.consecutivePairs) : p ∈ r := by + rcases h with h | h + · exact (List.of_mem_zip h).1 + · exact List.mem_of_mem_tail (List.of_mem_zip h).2 + +private theorem neighbor_of_straight {r : Route} {p q d : Point} + (ht : StraightThrough r p d) (hn : r.Nodup) + (h : (p, q) ∈ r.consecutivePairs ∨ (q, p) ∈ r.consecutivePairs) : + q = p - d ∨ q = p + d := by + rcases h with h | h + · obtain ⟨k, hk, hp, hq⟩ := consecutivePair_index h + have hh := ht.getD_neighbors hn (by omega) hp + have hd : r.getD (k + 1) p = q := by rw [List.getD_eq_getElem _ _ hk]; exact hq + rcases hh.2.2 with ⟨_, hnext⟩ | ⟨_, hnext⟩ + · exact Or.inr (hd.symm.trans hnext) + · exact Or.inl (hd.symm.trans hnext) + · obtain ⟨k, hk, hq, hp⟩ := consecutivePair_index h + have hh := ht.getD_neighbors hn hk hp + have hd : r.getD (k + 1 - 1) p = q := by + rw [Nat.add_sub_cancel, List.getD_eq_getElem _ _ (by omega)] + exact hq + rcases hh.2.2 with ⟨hprev, _⟩ | ⟨hprev, _⟩ + · exact Or.inl (hd.symm.trans hprev) + · exact Or.inr (hd.symm.trans hprev) + +private theorem horizontal_vertical_neighbors {p q : Point} + (hx : q = p - (1, 0) ∨ q = p + (1, 0)) + (hy : q = p - (0, 1) ∨ q = p + (0, 1)) : False := by + rcases hx with hx | hx <;> rcases hy with hy | hy <;> + have h := hx.symm.trans hy <;> + have h' := congrArg Prod.fst h <;> simp at h' + +private theorem canonical_neighbor_unique (G : Graph) + (hG : ∀ e ∈ G.2, e.1 ∈ G.1 ∧ e.2 ∈ G.1) + (hloop : ∀ e ∈ G.2, e.1 ≠ e.2) + (hdeg : ∀ v, G.incidenceVertices.count v ≤ 3) {i j : ℕ} + (hi : i < G.2.length) (hj : j < G.2.length) (hij : i ≠ j) {p q : Point} + (hcenter : p ∉ G.orthogonalPositions) + (hiadj : (p, q) ∈ (G.unitPath i).consecutivePairs ∨ + (q, p) ∈ (G.unitPath i).consecutivePairs) + (hjadj : (p, q) ∈ (G.unitPath j).consecutivePairs ∨ + (q, p) ∈ (G.unitPath j).consecutivePairs) : False := by + have hn := Graph.unitPath_nodup G hG hloop hi + have hm := Graph.unitPath_nodup G hG hloop hj + rcases Graph.unitPath_crossing_neighbors G hG hloop hdeg hi hj hij hcenter + (mem_of_adjacent hiadj) (mem_of_adjacent hjadj) with ⟨hx, hy⟩ | ⟨hy, hx⟩ + · exact horizontal_vertical_neighbors (neighbor_of_straight hx hn hiadj) + (neighbor_of_straight hy hm hjadj) + · exact horizontal_vertical_neighbors (neighbor_of_straight hx hm hjadj) + (neighbor_of_straight hy hn hiadj) + +private theorem unit_not_both_centers (G : Graph) {p q : Point} + (hunit : Orthogonal.length p q = 1) (hp : p ∈ G.orthogonalPositions) + (hq : q ∈ G.orthogonalPositions) : False := by + obtain ⟨i, _, rfl⟩ := List.mem_map.mp hp + obtain ⟨j, _, rfl⟩ := List.mem_map.mp hq + simp only [Orthogonal.length, Layout.center] at hunit + omega + +/-- Two actual canonical paths cannot share a unit grid edge, even in opposite directions. +This count-free uniqueness statement also applies to the final step into a clause cell. -/ +theorem paths_shared_step_index {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {i j : ℕ} (hi : i < (paths φ).length) (hj : j < (paths φ).length) {p q : Point} + (he : (p, q) ∈ ((paths φ).getD i []).consecutivePairs) + (hf : (p, q) ∈ ((paths φ).getD j []).consecutivePairs ∨ + (q, p) ∈ ((paths φ).getD j []).consecutivePairs) : i = j := by + by_contra hij + have hi' : i < (SAT.incidenceGraph φ).2.length := by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi + have hj' : j < (SAT.incidenceGraph φ).2.length := by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hj + change (p, q) ∈ ((SAT.incidenceGraph φ).unitPath i).consecutivePairs at he + change (p, q) ∈ ((SAT.incidenceGraph φ).unitPath j).consecutivePairs ∨ + (q, p) ∈ ((SAT.incidenceGraph φ).unitPath j).consecutivePairs at hf + have hG : ∀ e ∈ (SAT.incidenceGraph φ).2, + e.1 ∈ (SAT.incidenceGraph φ).1 ∧ e.2 ∈ (SAT.incidenceGraph φ).1 := + fun _ => SAT.incidenceGraph_endpoints + have hloop : ∀ e ∈ (SAT.incidenceGraph φ).2, e.1 ≠ e.2 := + fun _ => SAT.incidenceGraph_loopless + have hdeg := SAT.incidenceGraph_degree_le hw hv + by_cases hp : p ∈ (SAT.incidenceGraph φ).orthogonalPositions + · have hq : q ∉ (SAT.incidenceGraph φ).orthogonalPositions := fun hq => + unit_not_both_centers _ (Graph.unitPath_unit _ hG hloop hi' he) hp hq + exact canonical_neighbor_unique _ hG hloop hdeg hi' hj' hij hq (Or.inr he) hf.symm + · exact canonical_neighbor_unique _ hG hloop hdeg hi' hj' hij hp (Or.inl he) hf + +/-- At a unit neighbor, the facing side determines the neighbor itself. -/ +theorem side_injective_on_unit_neighbors {p q t : Point} + (hq : Orthogonal.length p q = 1) (ht : Orthogonal.length p t = 1) + (hside : side p q = side p t) : q = t := by + rcases p with ⟨px, py⟩ + rcases unit_neighbor_cases hq with rfl | rfl | rfl | rfl <;> + rcases unit_neighbor_cases ht with rfl | rfl | rfl | rfl <;> + first | rfl | norm_num [side] at hside + +private def stepPorts (e : Point × Point) : List (Point × Fin 4) := + [(e.1, side e.1 e.2), (e.2, side e.2 e.1)] + +private theorem unit_reverse {p q : Point} (h : Orthogonal.length p q = 1) : + Orthogonal.length q p = 1 := by + simp only [Orthogonal.length] at h ⊢ + omega + +private theorem stepPorts_inter {e f : Point × Point} {x : Point × Fin 4} + (he : Orthogonal.length e.1 e.2 = 1) (hf : Orthogonal.length f.1 f.2 = 1) + (hx : x ∈ stepPorts e) (hy : x ∈ stepPorts f) : + e = f ∨ e = (f.2, f.1) := by + rcases e with ⟨a, b⟩ + rcases f with ⟨c, d⟩ + simp only [stepPorts, List.mem_cons, List.not_mem_nil, or_false] at hx hy + rcases hx with rfl | rfl <;> rcases hy with hy | hy + · obtain ⟨h, hs⟩ := Prod.mk.inj hy + subst c + have hb := side_injective_on_unit_neighbors he hf hs + exact Or.inl (Prod.ext rfl hb) + · obtain ⟨h, hs⟩ := Prod.mk.inj hy + subst d + have hb := side_injective_on_unit_neighbors he (unit_reverse hf) hs + exact Or.inr (Prod.ext rfl hb) + · obtain ⟨h, hs⟩ := Prod.mk.inj hy + subst c + have ha := side_injective_on_unit_neighbors (unit_reverse he) hf hs + exact Or.inr (Prod.ext ha rfl) + · obtain ⟨h, hs⟩ := Prod.mk.inj hy + subst d + have ha := side_injective_on_unit_neighbors (unit_reverse he) (unit_reverse hf) hs + exact Or.inl (Prod.ext ha rfl) + +private theorem routePorts_nodup {r : Route} (hn : r.Nodup) + (hu : ∀ e ∈ r.consecutivePairs, Orthogonal.length e.1 e.2 = 1) : + (r.consecutivePairs.flatMap stepPorts).Nodup := by + apply List.nodup_flatMap.mpr + constructor + · intro e he + have hne := (axisAligned_of_length_eq_one (hu e he)).2 + simp only [stepPorts, List.nodup_cons, List.not_mem_nil, List.nodup_nil, + List.mem_singleton, not_false_eq_true, and_true] + exact fun h => hne (congrArg Prod.fst h) + · rw [List.pairwise_iff_getElem] + intro i j hi hj hij x hx hy + have hi' : i + 1 < r.length := by + simp only [List.consecutivePairs, List.length_zip, List.length_tail] at hi + omega + have hj' : j + 1 < r.length := by + simp only [List.consecutivePairs, List.length_zip, List.length_tail] at hj + omega + rcases stepPorts_inter (hu _ (List.getElem_mem hi)) (hu _ (List.getElem_mem hj)) hx hy + with heq | heq + · have heq' : r[i] = r[j] := by + simpa only [List.consecutivePairs, List.getElem_zip, List.getElem_tail] using + congrArg Prod.fst heq + have := hn.eq_of_getElem_eq (by omega) (by omega) heq' + omega + · have heq' : r[i] = r[j + 1] := by + simpa only [List.consecutivePairs, List.getElem_zip, List.getElem_tail] using + congrArg Prod.fst heq + have := hn.eq_of_getElem_eq (by omega) hj' heq' + omega + +/-- All directed cell ports in the actual canonical paths are distinct. Both halves of every +unit step are included, even the final clause-directed half. No path witness is assumed. -/ +theorem pathPorts_nodup {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + ((paths φ).flatMap fun r => r.consecutivePairs.flatMap fun e => + [(e.1, side e.1 e.2), (e.2, side e.2 e.1)]).Nodup := by + have hi_edge {i : ℕ} (hi : i < (paths φ).length) : + i < (SAT.incidenceGraph φ).2.length := by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi + have hn {i : ℕ} (hi : i < (paths φ).length) : ((paths φ)[i]).Nodup := by + have h := Graph.unitPath_nodup (SAT.incidenceGraph φ) + (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) (hi_edge hi) + change ((paths φ).getD i []).Nodup at h + rwa [List.getD_eq_getElem _ _ hi] at h + have hu {i : ℕ} (hi : i < (paths φ).length) {e : Point × Point} + (he : e ∈ ((paths φ)[i]).consecutivePairs) : Orthogonal.length e.1 e.2 = 1 := by + apply Graph.unitPath_unit (SAT.incidenceGraph φ) + (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) (hi_edge hi) + change e ∈ ((paths φ).getD i []).consecutivePairs + rwa [List.getD_eq_getElem _ _ hi] + change ((paths φ).flatMap fun r => r.consecutivePairs.flatMap stepPorts).Nodup + apply List.nodup_flatMap.mpr + constructor + · intro r hr + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hr + exact routePorts_nodup (hn hi) (fun _ he => hu hi he) + · rw [List.pairwise_iff_getElem] + intro i j hi hj hij x hx hy + obtain ⟨e, he, hx⟩ := List.mem_flatMap.mp hx + obtain ⟨f, hf, hy⟩ := List.mem_flatMap.mp hy + have hi_step : e ∈ ((paths φ).getD i []).consecutivePairs := by + rwa [List.getD_eq_getElem _ _ hi] + have hj_step : (e.1, e.2) ∈ ((paths φ).getD j []).consecutivePairs ∨ + (e.2, e.1) ∈ ((paths φ).getD j []).consecutivePairs := by + rw [List.getD_eq_getElem _ _ hj] + rcases stepPorts_inter (hu hi he) (hu hj hf) hx hy with heq | heq + · exact Or.inl (heq.symm ▸ hf) + · exact Or.inr (by simpa only [heq] using hf) + have := paths_shared_step_index hw hv hi hj hi_step hj_step + omega + +/-- Equal boundary midpoints identify the same unordered unit grid edge. -/ +theorem cellPort_step_eq_cases {p q a b : Point} + (hpq : Orthogonal.length p q = 1) (hab : Orthogonal.length a b = 1) + (h : cellPort 120 p (side p q) = cellPort 120 a (side a b)) : + (p = a ∧ q = b) ∨ (p = b ∧ q = a) := by + rcases p with ⟨px, py⟩ + rcases a with ⟨ax, ay⟩ + rcases unit_neighbor_cases hpq with rfl | rfl | rfl | rfl <;> + rcases unit_neighbor_cases hab with rfl | rfl | rfl | rfl <;> + norm_num [side, cellPort, cellOrigin, Prod.ext_iff] at h ⊢ <;> omega + +/-- One geometric midpoint per actual unit step gives a duplicate-free list. +This includes final steps, so any prefix selection used for binary clauses remains distinct. -/ +theorem pathMidpoints_nodup {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + ((paths φ).flatMap fun r => r.consecutivePairs.map fun e => + cellPort 120 e.1 (side e.1 e.2)).Nodup := by + let steps := (paths φ).flatMap List.consecutivePairs + have hu {e : Point × Point} (he : e ∈ steps) : Orthogonal.length e.1 e.2 = 1 := by + obtain ⟨r, hr, he⟩ := List.mem_flatMap.mp he + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hr + have hi' : i < (SAT.incidenceGraph φ).2.length := by + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi + apply Graph.unitPath_unit (SAT.incidenceGraph φ) + (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) hi' + change e ∈ ((paths φ).getD i []).consecutivePairs + rwa [List.getD_eq_getElem _ _ hi] + have hp : (steps.flatMap stepPorts).Nodup := by + have h := pathPorts_nodup hw hv + change ((paths φ).flatMap fun r => r.consecutivePairs.flatMap stepPorts).Nodup at h + simpa only [steps, List.flatMap_assoc] using h + have hpair := (List.nodup_flatMap.mp hp).2 + have hmid : (steps.map fun e => cellPort 120 e.1 (side e.1 e.2)).Nodup := by + apply List.pairwise_map.mpr + apply hpair.imp_of_mem + intro e f he hf hd heq + have hx : (e.1, side e.1 e.2) ∈ stepPorts e := by simp [stepPorts] + rcases cellPort_step_eq_cases (hu he) (hu hf) heq with ⟨h₁, h₂⟩ | ⟨h₁, h₂⟩ + · have heq' : e = f := Prod.ext h₁ h₂ + exact hd hx (heq' ▸ hx) + · have heq' : e = (f.2, f.1) := Prod.ext h₁ h₂ + exact hd hx (by simp [stepPorts, heq']) + simpa only [steps, List.map_flatMap] using hmid + +/-- Distinct integer cells have distinct displayed centers. -/ +theorem center_injective : Function.Injective center := by + intro p q h + have hp := center_mem_openCell (r := 120) (by decide) p + have hq := center_mem_openCell (r := 120) (by decide) q + change realPoint (center p) ∈ openCell 120 p at hp + change realPoint (center q) ∈ openCell 120 q at hq + rw [← h] at hq + exact cell_eq_of_mem_open_closed (by decide) hp + ⟨hq.1.le, hq.2.1.le, hq.2.2.1.le, hq.2.2.2.le⟩ + +/-- Original clause cells are declared source centers and are never selected as crossings. -/ +theorem clause_cell_not_mem_crossingSites (φ : CNF ℕ) {i : ℕ} (hi : i < φ.length) : + Layout.center (φ.distinctVars.length + i) ∉ crossingSites φ := by + intro h + apply ((mem_crossingSites φ _).mp h).2.1 + refine List.mem_map.mpr ⟨φ.distinctVars.length + i, List.mem_range.mpr ?_, rfl⟩ + simp only [SAT.incidenceGraph, List.length_range] + omega + +private theorem occurrencePath_clause_center_iff {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) {i : ℕ} (hi : i < φ.length) : + Layout.center (φ.distinctVars.length + i) ∈ occurrencePath φ o ↔ i = o.1 := by + have hoidx := occurrence_index_lt ho + have h := Graph.unitPath_center_iff (SAT.incidenceGraph φ) + (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) hoidx + (w := φ.distinctVars.length + i) (by simp only [SAT.incidenceGraph, List.length_range]; omega) + have he : (SAT.incidenceGraph φ).2[(Cslib.Logic.PL.occurrences φ).idxOf o] = + (φ.distinctVars.idxOf o.2.2.1, φ.distinctVars.length + o.1) := by + simp only [SAT.incidenceGraph, List.getElem_map] + rw [List.getElem_idxOf (List.idxOf_lt_length_of_mem ho)] + have hb := SAT.incidenceGraph_bipartite (List.getElem_mem hoidx) + rw [he] at hb h + change Layout.center (φ.distinctVars.length + i) ∈ occurrencePath φ o ↔ + φ.distinctVars.length + i = (List.range _).idxOf (φ.distinctVars.idxOf o.2.2.1) ∨ + φ.distinctVars.length + i = (List.range _).idxOf (φ.distinctVars.length + o.1) at h + rw [List.idxOf_range_of_lt (by omega), List.idxOf_range_of_lt hb.2.2] at h + exact h.trans (by omega) + +/-- No source clause center is used as a signal cell before a path's final endpoint. -/ +theorem clause_cell_not_mem_path_dropLast {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) {i : ℕ} (hi : i < φ.length) : + Layout.center (φ.distinctVars.length + i) ∉ (occurrencePath φ o).dropLast := by + intro hp + have hi' := (occurrencePath_clause_center_iff ho hi).mp + ((List.dropLast_sublist _).subset hp) + subst i + obtain ⟨k, hk, hget⟩ := List.mem_iff_getElem.mp hp + have hlen := occurrencePath_length_ge_two ho + have hend := (occurrencePath_endpoints ho).2 + rw [List.getLast?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hend + have hk' : k < (occurrencePath φ o).length := by + simp only [List.length_dropLast] at hk + omega + have heq : (occurrencePath φ o)[k] = + (occurrencePath φ o)[(occurrencePath φ o).length - 1] := by + rw [Option.some.inj hend] + simpa only [List.getElem_dropLast] using hget + have hidx := (occurrencePath_nodup ho).eq_of_getElem_eq hk' (by omega) heq + simp only [List.length_dropLast] at hk + omega + +end Cslib.Complexity.SAT.PlanarCompile diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PathPolyTime.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathPolyTime.lean new file mode 100644 index 000000000..7cc118ccd --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PathPolyTime.lean @@ -0,0 +1,324 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData +public import Complexity.Computability.Complexity.Problems.SAT +public import Complexity.Computability.Geometry.Orthogonal.Subdivision +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Finite +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Option +public import Complexity.Logics.Propositional.CNF.PolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Finite +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Int.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Membership +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Fin +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Option +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + + +/-! +# Native computation of canonical path data + +Input natural labels and coordinates use binary encodings; syntactic positions and the +subdivision budget use unary counters. These certificates compute only the shared paths, +crossing cells, and occurrence lookup, with no geometric validity assumption. + +## Main results + +* `Cslib.Complexity.SAT.PlanarCompile.computableInPolyTime_budget`. +* `Cslib.Complexity.SAT.PlanarCompile.computableInPolyTime_paths`. +* `Cslib.Complexity.SAT.PlanarCompile.computableInPolyTime_crossingSites`. +* `Cslib.Complexity.SAT.PlanarCompile.computableInPolyTime_occurrencePath`. + +## References + +These are the shared canonical incidence-path constructions and certificates. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.PlanarCompile + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal +open Turing.MultiTapeTM + +private theorem computable_natCast : + ComputableInPolyTime (fun n : ℕ => (n : ℤ)) encUnary encIntBin := + .of_tidy (ComputableTidilyInPolyTime.intBin_natCast.comp + ComputableTidilyInPolyTime.unary_to_binary) + +private theorem computable_finVal : + ComputableInPolyTime (fun s : Fin 3 => s.val) (encFinite (Fin 3)) encNat := + .of_tidy (ComputableTidilyInPolyTime.of_finite _) + +attribute [local fun_prop] computable_natCast computable_finVal ComputableInPolyTime.tidy + +private theorem computable_incidencePort : + ComputableInPolyTime (fun p : Graph × ℕ => p.1.incidencePort p.2) + (encPair encGraph encUnary) (encFinite (Fin 3)) := by + have hc : ComputableTidilyInPolyTime (fun p : Graph × ℕ => + (p.1.incidenceVertices.take p.2).count (p.1.incidenceVertices.getD p.2 0)) + (encPair encGraph encUnary) encUnary := by + unfold Graph.incidenceVertices encGraph + fun_prop + exact .of_tidy ((ComputableTidilyInPolyTime.unary_finMod (by decide) + (encFinite (Fin 3))).comp hc) + +attribute [local fun_prop] computable_incidencePort + +private theorem computable_canonicalPorts : + ComputableInPolyTime Graph.canonicalPorts encGraph + (encList (encPair (encFinite (Fin 3)) (encFinite (Fin 3)))) := by + refine .of_tidy ?_ + unfold Graph.canonicalPorts encGraph + fun_prop + +attribute [local fun_prop] computable_canonicalPorts + +private theorem computable_layoutCenter : + ComputableInPolyTime Layout.center encUnary encPoint := by + refine .of_tidy ?_ + unfold Layout.center encPoint + fun_prop + +private theorem computable_layoutColumn : + ComputableInPolyTime (fun p : ℕ × Fin 3 => Layout.column p.1 p.2) + (encPair encUnary (encFinite (Fin 3))) encIntBin := by + refine .of_tidy ?_ + unfold Layout.column + fun_prop + +private theorem computable_layoutTrack : + ComputableInPolyTime (fun p : ℕ × ℕ => Layout.track p.1 p.2) + (encPair encUnary encUnary) encIntBin := by + refine .of_tidy ?_ + unfold Layout.track + fun_prop + +attribute [local fun_prop] computable_layoutCenter computable_layoutColumn computable_layoutTrack + +private theorem computable_layoutElbow : + ComputableInPolyTime (fun p : ℕ × Fin 3 => Layout.elbow p.1 p.2) + (encPair encUnary (encFinite (Fin 3))) encPoint := by + refine .of_tidy ?_ + unfold Layout.elbow encPoint + fun_prop + +attribute [local fun_prop] computable_layoutElbow + +private theorem computable_layoutRoute : + ComputableInPolyTime (fun p : ℕ × ℕ × ℕ × ℕ × Fin 3 × Fin 3 => + Layout.route p.1 p.2.1 p.2.2.1 p.2.2.2.1 p.2.2.2.2.1 p.2.2.2.2.2) + (encPair encUnary (encPair encUnary (encPair encUnary + (encPair encUnary (encPair (encFinite (Fin 3)) (encFinite (Fin 3))))))) encRoute := by + refine .of_tidy ?_ + unfold Layout.route encRoute + fun_prop + +attribute [local fun_prop] computable_layoutRoute + +private theorem computable_orthogonalRoutes : + ComputableInPolyTime (fun G : Graph => G.orthogonalRoutes G.canonicalPorts) + encGraph (encList encRoute) := by + let edgeEncoding := encPair (encPair encNat encNat) encUnary + let routeInput := encPair encUnary (encPair encUnary (encPair encUnary + (encPair encUnary (encPair (encFinite (Fin 3)) (encFinite (Fin 3)))))) + have hcan := computable_canonicalPorts.tidy + have hp : ComputableTidilyInPolyTime (fun p : Graph × ((ℕ × ℕ) × ℕ) => + p.1.canonicalPorts.getD p.2.2 (0, 0)) + (encPair encGraph edgeEncoding) (encPair (encFinite (Fin 3)) (encFinite (Fin 3))) := by + unfold edgeEncoding encGraph + fun_prop + have hargs : ComputableInPolyTime (fun p : Graph × ((ℕ × ℕ) × ℕ) => + (p.1.1.length, p.2.2, p.1.1.idxOf p.2.1.1, p.1.1.idxOf p.2.1.2, + (p.1.canonicalPorts.getD p.2.2 (0, 0)).1, + (p.1.canonicalPorts.getD p.2.2 (0, 0)).2)) + (encPair encGraph edgeEncoding) routeInput := by + refine .of_tidy ?_ + unfold routeInput edgeEncoding encGraph at * + fun_prop + have hbody := computable_layoutRoute.comp hargs + have hlist : ComputableInPolyTime (fun G : Graph => G.2.zipIdx) encGraph + (encList edgeEncoding) := by + refine .of_tidy ?_ + unfold encGraph edgeEncoding + fun_prop + simpa only [Graph.orthogonalRoutes, Function.comp_def] using + ComputableInPolyTime.mapWithInput hlist hbody + +attribute [local fun_prop] computable_orthogonalRoutes + +/-- The budget is a linear expression in syntactic list lengths, computed as a unary counter. -/ +@[fun_prop] +theorem computableInPolyTime_budget : ComputableInPolyTime budget encCNF encUnary := by + refine .of_tidy ?_ + unfold budget encCNF + fun_prop + +/-- Canonical incidence routes have a native polynomial-time bounded unit expansion. +The input labels and output coordinates are binary; only the step budget is unary. -/ +@[fun_prop] +theorem computableInPolyTime_paths : + ComputableInPolyTime paths encCNF (encList encRoute) := by + change ComputableInPolyTime (fun φ => + ((SAT.incidenceGraph φ).orthogonalRoutes (SAT.incidenceGraph φ).canonicalPorts).map + (subdivideWithin (budget φ))) encCNF (encList encRoute) + have hraw := computable_orthogonalRoutes.comp (SAT.computableInPolyTime_incidenceGraph encNat) + have hargs : ComputableInPolyTime (fun p : CNF ℕ × Route => (budget p.1, p.2)) + (encPair encCNF encRoute) (encPair encUnary encRoute) := by fun_prop + have hsub := computableInPolyTime_subdivideWithin.comp hargs + simpa only [Function.comp_def, encCNF] using ComputableInPolyTime.mapWithInput hraw hsub + +private def endpointColumns (p : Graph × ℕ) : Point := + (Layout.column (p.1.1.idxOf (p.1.edgeEndpoint p.2 false)) + (Graph.portAt p.1.canonicalPorts p.2 false), + Layout.column (p.1.1.idxOf (p.1.edgeEndpoint p.2 true)) + (Graph.portAt p.1.canonicalPorts p.2 true)) + +private theorem computable_endpointColumns : + ComputableInPolyTime endpointColumns (encPair encGraph encUnary) encPoint := by + unfold endpointColumns + have hcan := computable_canonicalPorts.tidy + have hp : ComputableTidilyInPolyTime (fun p : Graph × ℕ => + p.1.canonicalPorts.getD p.2 (0, 0)) + (encPair encGraph encUnary) (encPair (encFinite (Fin 3)) (encFinite (Fin 3))) := by + unfold encGraph + fun_prop + have he : ComputableTidilyInPolyTime (fun p : Graph × ℕ => p.1.2.getD p.2 (0, 0)) + (encPair encGraph encUnary) (encPair encNat encNat) := by + unfold encGraph + fun_prop + refine .of_tidy ?_ + unfold Graph.edgeEndpoint Graph.portAt encPoint encGraph at * + simp only [Bool.false_eq_true, ↓reduceIte] + fun_prop + +attribute [local fun_prop] computable_endpointColumns + +private def candidatePair (p : Graph × (ℕ × ℕ)) : List Point := + [((endpointColumns (p.1, p.2.2)).1, Layout.track p.1.1.length p.2.1), + ((endpointColumns (p.1, p.2.2)).2, Layout.track p.1.1.length p.2.1)] + +private theorem computable_candidatePair : + ComputableInPolyTime candidatePair (encPair encGraph (encPair encUnary encUnary)) + (encList encPoint) := by + have hselect : ComputableTidilyInPolyTime + (fun p : Graph × (ℕ × ℕ) => (p.1, p.2.2)) + (encPair encGraph (encPair encUnary encUnary)) (encPair encGraph encUnary) := by fun_prop + have hcols := computable_endpointColumns.tidy.comp hselect + have hleft := hcols.fst' + have hright := hcols.snd' + have htrack : ComputableTidilyInPolyTime (fun p : Graph × (ℕ × ℕ) => + Layout.track p.1.1.length p.2.1) + (encPair encGraph (encPair encUnary encUnary)) encIntBin := by + unfold encGraph + fun_prop + refine .of_tidy ?_ + unfold candidatePair encPoint at * + fun_prop + +private def candidateRow (p : Graph × ℕ) : List Point := + (List.range p.1.2.length).flatMap fun j => candidatePair (p.1, p.2, j) + +private theorem computable_edgeRange : + ComputableInPolyTime (fun G : Graph => List.range G.2.length) encGraph + (encList encUnary) := by + refine .of_tidy ?_ + unfold encGraph + fun_prop + +private theorem computable_candidateRow : + ComputableInPolyTime candidateRow (encPair encGraph encUnary) (encList encPoint) := by + have hlist := computable_edgeRange.comp (ComputableInPolyTime.fst encGraph encUnary) + have harg : ComputableInPolyTime (fun p : (Graph × ℕ) × ℕ => (p.1.1, p.1.2, p.2)) + (encPair (encPair encGraph encUnary) encUnary) + (encPair encGraph (encPair encUnary encUnary)) := by fun_prop + have hbody := computable_candidatePair.comp harg + change ComputableInPolyTime (fun p : Graph × ℕ => + (List.range p.1.2.length).flatMap fun j => candidatePair (p.1, p.2, j)) _ _ + simpa only [Function.comp_def] using ComputableInPolyTime.flatMapWithInput hlist hbody + +private theorem computable_crossingCandidates : + ComputableInPolyTime (fun G : Graph => G.crossingCandidates G.canonicalPorts) + encGraph (encList encPoint) := by + have h := ComputableInPolyTime.flatMapWithInput + (f := fun G i => candidateRow (G, i)) (encB := encPoint) + computable_edgeRange computable_candidateRow + simpa only [candidateRow, candidatePair, endpointColumns, Graph.crossingCandidates] using h + +attribute [local fun_prop] computable_crossingCandidates + +private theorem computable_orthogonalPositions : + ComputableInPolyTime Graph.orthogonalPositions encGraph encRoute := by + refine .of_tidy ?_ + unfold Graph.orthogonalPositions encRoute encGraph + fun_prop + +attribute [local fun_prop] computable_orthogonalPositions + +private theorem computable_distinctPoints : + ComputableInPolyTime (fun l : Route => selectDistinct l (List.replicate l.length true)) + encRoute encRoute := by + have hrep : ComputableTidilyInPolyTime (fun n => List.replicate n true) encUnary encBits := + ComputableTidilyInPolyTime.of_linear (.of_eq fun _ => rfl) + refine .of_tidy ?_ + unfold selectDistinct encRoute + fun_prop + +private theorem computable_crossingPredicate : + ComputableInPolyTime (fun p : CNF ℕ × Point => + !((SAT.incidenceGraph p.1).orthogonalPositions.contains p.2) && + decide (2 ≤ ((paths p.1).filter fun r => r.contains p.2).length)) + (encPair encCNF encPoint) encBool := by + have hg := SAT.computableInPolyTime_incidenceGraph encNat + have hv : ComputableTidilyInPolyTime (fun p : CNF ℕ × Point => + (SAT.incidenceGraph p.1).orthogonalPositions) (encPair encCNF encPoint) encRoute := + (computable_orthogonalPositions.comp hg).tidy.comp (ComputableTidilyInPolyTime.fst _ _) + have hp : ComputableTidilyInPolyTime (fun p : CNF ℕ × Point => paths p.1) + (encPair encCNF encPoint) (encList encRoute) := + computableInPolyTime_paths.tidy.comp (ComputableTidilyInPolyTime.fst _ _) + refine .of_tidy ?_ + unfold encRoute at * + simp only [List.contains_eq_mem] + fun_prop + +/-- Occupied nonvertex crossing sites can be selected in native polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_crossingSites : + ComputableInPolyTime crossingSites encCNF encRoute := by + have hc := computable_crossingCandidates.comp (SAT.computableInPolyTime_incidenceGraph encNat) + have hf := ComputableInPolyTime.filterWithInput + (test := fun φ p => !((SAT.incidenceGraph φ).orthogonalPositions.contains p) && + decide (2 ≤ ((paths φ).filter fun r => r.contains p).length)) + hc computable_crossingPredicate + have h := computable_distinctPoints.comp hf + change ComputableInPolyTime (fun φ => + selectDistinct + (((SAT.incidenceGraph φ).crossingCandidates (SAT.incidenceGraph φ).canonicalPorts).filter + fun p => !((SAT.incidenceGraph φ).orthogonalPositions.contains p) && + decide (2 ≤ ((paths φ).filter fun r => r.contains p).length)) + (List.replicate + ((((SAT.incidenceGraph φ).crossingCandidates (SAT.incidenceGraph φ).canonicalPorts).filter + fun p => !((SAT.incidenceGraph φ).orthogonalPositions.contains p) && + decide (2 ≤ ((paths φ).filter fun r => r.contains p).length)).length) true)) _ _ + simpa only [Function.comp_def, encCNF] using h + +/-- Looking up the path for a syntactic literal occurrence is native polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_occurrencePath : + ComputableInPolyTime (fun p : CNF ℕ × Occurrence ℕ => occurrencePath p.1 p.2) + (encPair encCNF (Cslib.Logic.PL.encOccurrence encNat)) encRoute := by + have hp := computableInPolyTime_paths.tidy + have ho := (Cslib.Logic.PL.computableInPolyTime_occurrences encNat).tidy + refine .of_tidy ?_ + unfold occurrencePath encCNF Cslib.Logic.PL.encOccurrence at * + fun_prop + +end Cslib.Complexity.SAT.PlanarCompile diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths.lean index aa7f50a04..6e63918ec 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths.lean @@ -7,9 +7,10 @@ Authors: Erik Demaine module -- upstream: candidate -public import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalPath public import Complexity.Logics.Propositional.CNF.Occurrence +import Complexity.Foundations.Data.List.Range import Mathlib.Data.List.GetD /-! @@ -80,12 +81,6 @@ theorem occurrencePath_ne_nil {φ : CNF ℕ} {o : Occurrence ℕ} exact Graph.unitPath_ne_nil _ (fun _ => SAT.incidenceGraph_endpoints) (fun _ => SAT.incidenceGraph_loopless) (occurrence_index_lt ho) -private theorem idxOf_range_eq {i n : ℕ} (hi : i < n) : (List.range n).idxOf i = i := by - have h := (List.nodup_range : (List.range n).Nodup).idxOf_getElem i - (by simpa only [List.length_range] using hi) - rw [List.getElem_range] at h - exact h - /-- Actual occurrence paths start at the variable's source-grid center and end at their clause. -/ theorem occurrencePath_endpoints {φ : CNF ℕ} {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) : @@ -106,7 +101,7 @@ theorem occurrencePath_endpoints {φ : CNF ℕ} {o : Occurrence ℕ} some (Layout.center ((List.range _).idxOf (φ.distinctVars.idxOf o.2.2.1))) ∧ (occurrencePath φ o).getLast? = some (Layout.center ((List.range _).idxOf (φ.distinctVars.length + o.1))) at h - rw [idxOf_range_eq (by omega), idxOf_range_eq hb.2.2] at h + rw [List.idxOf_range_of_lt (by omega), List.idxOf_range_of_lt hb.2.2] at h exact h /-- Actual literal paths contain at least one unit step, so final-literal lookups are in range. -/ diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths/Occupancy.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths/Occupancy.lean index a3b374d48..70a78c5a8 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths/Occupancy.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Paths/Occupancy.lean @@ -10,6 +10,7 @@ module public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalPath public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree +import Complexity.Foundations.Data.List.Filter import Mathlib.Data.List.GetD /-! @@ -34,37 +35,6 @@ namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Orthogonal -private theorem canonical_paths (φ : CNF ℕ) : - paths φ = (SAT.incidenceGraph φ).unitPaths := rfl - -private theorem two_paths_of_filter_length (rs : List Route) (p : Point) - (hlen : 2 ≤ (rs.filter fun r => r.contains p).length) : - ∃ (i j : ℕ) (hi : i < rs.length) (hj : j < rs.length), - i ≠ j ∧ p ∈ rs[i] ∧ p ∈ rs[j] := by - induction rs with - | nil => simp at hlen - | cons r rs ih => - by_cases hp : p ∈ r - · have hne : (rs.filter fun r => r.contains p) ≠ [] := by - intro h - have hf : ((r :: rs).filter fun r => r.contains p) = [r] := by - have hpred : r.contains p = true := by simpa using hp - simp only [List.filter_cons, hpred, ↓reduceIte, h] - rw [hf] at hlen - simp at hlen - cases hf : rs.filter (fun r => r.contains p) with - | nil => exact False.elim (hne hf) - | cons t ts => - have ht : t ∈ rs.filter (fun r => r.contains p) := by rw [hf]; simp - obtain ⟨htmem, htp⟩ := List.mem_filter.mp ht - obtain ⟨j, hj, rfl⟩ := List.mem_iff_getElem.mp htmem - exact ⟨0, j + 1, by simp, by simpa using hj, by omega, hp, by simpa using htp⟩ - · have hf : ((r :: rs).filter fun r => r.contains p) = rs.filter (fun r => r.contains p) := by - simp [hp] - rw [hf] at hlen - obtain ⟨i, j, hi, hj, hij, hpi, hpj⟩ := ih hlen - exact ⟨i + 1, j + 1, by simpa using hi, by simpa using hj, by omega, hpi, hpj⟩ - /-- Away from source centers, an unselected ordinary cell belongs to at most one path. -/ theorem paths_filter_length_le_one {φ : CNF ℕ} (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) {p : Point} @@ -72,11 +42,12 @@ theorem paths_filter_length_le_one {φ : CNF ℕ} ((paths φ).filter fun r => r.contains p).length ≤ 1 := by by_contra h have ht : 2 ≤ ((paths φ).filter fun r => r.contains p).length := by omega - obtain ⟨i, j, hi, hj, hij, hpi, hpj⟩ := two_paths_of_filter_length (paths φ) p ht + obtain ⟨i, j, hi, hj, hij, hpi, hpj⟩ := + List.exists_pair_indices_of_two_le_filter_contains (paths φ) p ht have hi' : i < (SAT.incidenceGraph φ).2.length := by - simpa only [canonical_paths, Graph.unitPaths_length] using hi + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hi have hj' : j < (SAT.incidenceGraph φ).2.length := by - simpa only [canonical_paths, Graph.unitPaths_length] using hj + simpa only [paths_eq_unitPaths, Graph.unitPaths_length] using hj have hpi' : p ∈ (SAT.incidenceGraph φ).unitPath i := by change p ∈ (paths φ).getD i [] rwa [List.getD_eq_getElem _ _ hi] diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PolyTime.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PolyTime.lean index 693e90cee..48178aa3a 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/PolyTime.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PolyTime.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathPolyTime public import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile public import Complexity.Computability.Complexity.Problems.SAT public import Complexity.Computability.Geometry.Orthogonal.Subdivision @@ -166,27 +167,6 @@ private theorem computable_orthogonalRoutes : attribute [local fun_prop] computable_orthogonalRoutes -/-- The budget is a linear expression in syntactic list lengths, computed as a unary counter. -/ -@[fun_prop] -theorem computableInPolyTime_budget : ComputableInPolyTime budget encCNF encUnary := by - refine .of_tidy ?_ - unfold budget encCNF - fun_prop - -/-- Canonical incidence routes have a native polynomial-time bounded unit expansion. -The input labels and output coordinates are binary; only the step budget is unary. -/ -@[fun_prop] -theorem computableInPolyTime_paths : - ComputableInPolyTime paths encCNF (encList encRoute) := by - change ComputableInPolyTime (fun φ => - ((SAT.incidenceGraph φ).orthogonalRoutes (SAT.incidenceGraph φ).canonicalPorts).map - (subdivideWithin (budget φ))) encCNF (encList encRoute) - have hraw := computable_orthogonalRoutes.comp (SAT.computableInPolyTime_incidenceGraph encNat) - have hargs : ComputableInPolyTime (fun p : CNF ℕ × Route => (budget p.1, p.2)) - (encPair encCNF encRoute) (encPair encUnary encRoute) := by fun_prop - have hsub := computableInPolyTime_subdivideWithin.comp hargs - simpa only [Function.comp_def, encCNF] using ComputableInPolyTime.mapWithInput hraw hsub - private def endpointColumns (p : Graph × ℕ) : Point := (Layout.column (p.1.1.idxOf (p.1.edgeEndpoint p.2 false)) (Graph.portAt p.1.canonicalPorts p.2 false), @@ -300,38 +280,6 @@ private theorem computable_crossingPredicate : simp only [List.contains_eq_mem] fun_prop -/-- Occupied nonvertex crossing sites can be selected in native polynomial time. -/ -@[fun_prop] -theorem computableInPolyTime_crossingSites : - ComputableInPolyTime crossingSites encCNF encRoute := by - have hc := computable_crossingCandidates.comp (SAT.computableInPolyTime_incidenceGraph encNat) - have hf := ComputableInPolyTime.filterWithInput - (test := fun φ p => !((SAT.incidenceGraph φ).orthogonalPositions.contains p) && - decide (2 ≤ ((paths φ).filter fun r => r.contains p).length)) - hc computable_crossingPredicate - have h := computable_distinctPoints.comp hf - change ComputableInPolyTime (fun φ => - selectDistinct - (((SAT.incidenceGraph φ).crossingCandidates (SAT.incidenceGraph φ).canonicalPorts).filter - fun p => !((SAT.incidenceGraph φ).orthogonalPositions.contains p) && - decide (2 ≤ ((paths φ).filter fun r => r.contains p).length)) - (List.replicate - ((((SAT.incidenceGraph φ).crossingCandidates (SAT.incidenceGraph φ).canonicalPorts).filter - fun p => !((SAT.incidenceGraph φ).orthogonalPositions.contains p) && - decide (2 ≤ ((paths φ).filter fun r => r.contains p).length)).length) true)) _ _ - simpa only [Function.comp_def, encCNF] using h - -/-- Looking up the path for a syntactic literal occurrence is native polynomial-time. -/ -@[fun_prop] -theorem computableInPolyTime_occurrencePath : - ComputableInPolyTime (fun p : CNF ℕ × Occurrence ℕ => occurrencePath p.1 p.2) - (encPair encCNF (Cslib.Logic.PL.encOccurrence encNat)) encRoute := by - have hp := computableInPolyTime_paths.tidy - have ho := (Cslib.Logic.PL.computableInPolyTime_occurrences encNat).tidy - refine .of_tidy ?_ - unfold occurrencePath encCNF Cslib.Logic.PL.encOccurrence at * - fun_prop - private theorem computable_side : ComputableInPolyTime (fun p : Point × Point => side p.1 p.2) (encPair encPoint encPoint) (encFinite (Fin 4)) := by diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Semantics.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Semantics.lean index c4ff97a2f..30f5ee086 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Semantics.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Semantics.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile public import Complexity.Logics.Propositional.CNF.Planar.DirectedWire import Mathlib.Data.List.GetD @@ -39,17 +40,6 @@ namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal -/-- A path contributes one signal cell for each point before its final clause vertex. -/ -theorem length_wire (sites : List Point) (r : Route) : - (wire sites r).length = r.length - 1 := by simp [wire] - -/-- Every path of at least two points yields a nonempty cell wire. -/ -theorem wire_ne_nil (sites : List Point) (r : Route) (h : 2 ≤ r.length) : wire sites r ≠ [] := by - intro he - have hl := length_wire sites r - rw [he, List.length_nil] at hl - omega - /-- Forgetting geometric data gives exactly the renamed fixed crossover clauses. -/ theorem map_fst_crossingClauses (p : Point) : (crossingClauses p).map Prod.fst = @@ -91,13 +81,6 @@ theorem crossing_boundary_eq {φ : CNF ℕ} {σ : Variable → Bool} σ (p, some .bTop) = σ (p, some .bBottom) := Crossover.split_boundary_eq_of_holds (((eval_rawFormula_iff φ σ).1 h).2.1 p hp) -/-- Exact indexed cell signals in the executable wire list. -/ -theorem getElem_wire (sites : List Point) (r : Route) (i : ℕ) (hi : i < r.length - 1) : - (wire sites r)[i]'(by simpa [wire] using hi) = - (signal sites r[i] (r.getD (i - 1) r[i]), - signal sites r[i] (r.getD (i + 1) r[i])) := by - simp only [wire, List.getElem_map, List.getElem_zipIdx, Nat.zero_add, List.getElem_dropLast] - /-- The placed intercell clauses are exactly the generic polarity-directed wire clauses. -/ theorem map_fst_links (sites : List Point) (b : Bool) (r : Route) : (links sites b r).map Prod.fst = CNF.DirectedWire.links b (wire sites r) := by diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/Size.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/Size.lean index 592978fe7..078a60735 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/Planar/Size.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/Size.lean @@ -7,6 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds public import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile import Mathlib.Algebra.BigOperators.Ring.List import Mathlib.Data.List.GetD @@ -40,19 +41,6 @@ namespace Cslib.Complexity.SAT.PlanarCompile open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal -/-- Number of external implication clauses actually emitted by the compiler. -/ -def linkCount (φ : CNF ℕ) : ℕ := - ((Cslib.Logic.PL.occurrences φ).map fun o => (occurrencePath φ o).length - 2).sum - -/-- Selected crossing sites form a sublist of the quadratic canonical cover after deduplication. -The bound counts all source literal incidences, including repetitions. -/ -theorem crossingSites_length_le (φ : CNF ℕ) : - (crossingSites φ).length ≤ 2 * φ.vars.length ^ 2 := by - unfold crossingSites - rw [length_selectDistinct_replicate] - exact (List.toFinset_card_le _).trans ((List.length_filter_le _ _).trans (by - simp [Graph.crossingCandidates_length, SAT.length_edges_incidenceGraph])) - /-- Original clauses keep their count. -/ theorem length_originalClauses (φ : CNF ℕ) (sites : List Point) : (originalClauses φ sites).length = φ.length := by simp [originalClauses] @@ -126,40 +114,6 @@ theorem width_formula {φ : CNF ℕ} (h : ∀ c ∈ φ, c.length ≤ 3) : rw [← he] exact width_rawFormula h d hd -/-- There is one unit path per original literal incidence. -/ -theorem paths_length (φ : CNF ℕ) : (paths φ).length = φ.vars.length := by - simp [paths, SAT.length_edges_incidenceGraph] - -/-- The bounded enumerator emits at most five segment budgets plus the final endpoint. -This unconditional bound also applies to inputs that fail the degree-three restriction. -/ -theorem path_length_le {φ : CNF ℕ} {r : Route} (hr : r ∈ paths φ) : - r.length ≤ 5 * budget φ + 1 := by - change r ∈ ((SAT.incidenceGraph φ).orthogonalRoutes - (SAT.incidenceGraph φ).canonicalPorts).map (subdivideWithin (budget φ)) at hr - obtain ⟨base, hbase, rfl⟩ := List.mem_map.mp hr - have hs : base.consecutivePairs.length = 5 := by - obtain ⟨⟨e, i⟩, _, rfl⟩ := List.mem_map.mp hbase - rfl - simpa only [hs, Nat.mul_comm] using subdivideWithin_length_le (budget φ) base - -/-- Indexed access to a path obeys the same bound, including the empty fallback. -/ -theorem occurrencePath_length_le (φ : CNF ℕ) (o : Occurrence ℕ) : - (occurrencePath φ o).length ≤ 5 * budget φ + 1 := by - unfold occurrencePath - by_cases h : (Cslib.Logic.PL.occurrences φ).idxOf o < (paths φ).length - · rw [List.getD_eq_getElem _ _ h] - exact path_length_le (List.getElem_mem h) - · rw [List.getD_eq_default _ _ (Nat.le_of_not_gt h)] - exact Nat.zero_le _ - -/-- There are quadratically many links, even before checking the source restrictions. -/ -theorem linkCount_le (φ : CNF ℕ) : - linkCount φ ≤ φ.vars.length * (5 * budget φ + 1) := by - calc - _ ≤ ((Cslib.Logic.PL.occurrences φ).map fun _ => 5 * budget φ + 1).sum := - List.sum_le_sum fun o _ => (Nat.sub_le _ _).trans (occurrencePath_length_le φ o) - _ = _ := by simp [Cslib.Logic.PL.length_occurrences, Nat.mul_comm] - /-- An explicit quadratic clause bound in the source incidence graph's combinatorial size. -/ theorem length_formula_le (φ : CNF ℕ) : (formula φ).length ≤ 600 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by From de6e8d7ff633f23dc9695d63d97cbed049666cf6 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:40:59 -0400 Subject: [PATCH 7/9] feat(planar-SAT): prove paired compiler model-count preservation Part of #1144, split from the internally reviewed parsimonious planar SAT development. All six local validation gates and hygiene checks pass. No selected abstract changes. AI-assisted implementation and internal review with Codex gpt-6.1-sol; outside-fleet coordinator review remains required. --- Complexity.lean | 4 + .../Problems/SAT/Planar/PairedCompile.lean | 158 +++++++ .../SAT/Planar/PairedCorrectness.lean | 179 +++++++ .../Problems/SAT/Planar/PairedCounting.lean | 199 ++++++++ .../Problems/SAT/Planar/PairedSemantics.lean | 439 ++++++++++++++++++ ComplexityTests/PairedCounting.lean | 84 ++++ ComplexityTests/PlanarCompile.lean | 26 +- ComplexityTests/PlanarSemantics.lean | 44 +- 8 files changed, 1104 insertions(+), 29 deletions(-) create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCompile.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCorrectness.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCounting.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSemantics.lean create mode 100644 ComplexityTests/PairedCounting.lean diff --git a/Complexity.lean b/Complexity.lean index c7ad26cff..14fa3ef6e 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -809,9 +809,13 @@ public import Complexity.Computability.Complexity.Problems.SAT.Planar.CrossoverL public import Complexity.Computability.Complexity.Problems.SAT.Planar.Geometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Occurrence public import Complexity.Computability.Complexity.Problems.SAT.Planar.OrdinaryRing +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCorrectness +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCounting public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayout public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutChecks public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutData +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSemantics public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCompile.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCompile.lean new file mode 100644 index 000000000..84ab9effa --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCompile.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData +public import Complexity.Computability.Complexity.Problems.SAT.Planar.OrdinaryRing +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayout +public import Complexity.Foundations.Geometry.Orthogonal.PairedSpokes +public import Complexity.Logics.Propositional.CNF.Compact + +/-! +# A finite compiler with paired wire ports + +Canonical incidence paths and their crossing sites are reused. Each ordinary retained cell +contains a fixed implication ring whose eight ports carry the same value. A crossing cell +contains a crossover with two equal ports on each side. Each gap receives opposite implications +on its two separate ports. Consequently the adjacent signal values must agree. + +Original clause cells contain no auxiliary variables. Their final incidences use one of the two +ports facing the clause, with four disjoint offset spokes inside the clause cell. Unused ports +belong to their cell's implication ring and introduce no independent choices. + +## Main definitions + +* `Cslib.Complexity.SAT.ParsimoniousPlanar.placedClauses`: actual clauses and geometric data. +* `Cslib.Complexity.SAT.ParsimoniousPlanar.formula`: compact natural-number output labels. +* `Cslib.Complexity.SAT.ParsimoniousPlanar.positions`, `.routes`: finite drawing data. + +## References + +The paired assembly is original. The crossover's source credit is documented in `PairedCrossover`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- An ordinary ring port or a local crossover variable, together with its unit-cell index. -/ +abbrev Variable := Point × ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable) + +/-- A clause, its position, and one incidence route per literal, in literal order. -/ +abbrev PlacedClause := Clause Variable × Point × List Route + +/-- Reuse the already-certified canonical unit paths. -/ +abbrev paths := PlanarCompile.paths + +/-- Reuse the selected crossing cells. -/ +abbrev crossingSites := PlanarCompile.crossingSites + +/-- Reuse the occurrence-indexed canonical unit path. -/ +abbrev occurrencePath := PlanarCompile.occurrencePath + +/-- Side facing a unit neighbor, in left/right/bottom/top order. -/ +abbrev side := PlanarCompile.side + +/-- The center of a macrocell of side 240. -/ +abbrev center := PlanarCompile.center + +/-- All occupied retained ordinary cells, listed without repetitions. -/ +def ordinaryCells (φ : CNF ℕ) : List Point := + let occupied := ((paths φ).flatMap List.dropLast).filter fun p => decide (p ∉ crossingSites φ) + selectDistinct occupied (List.replicate occupied.length true) + +/-- The signal variable on a particular side and lane of a cell. -/ +def port (sites : List Point) (p : Point) (s : Fin 4) (lane : Fin 2) : Variable := + (p, if p ∈ sites then .inr (PairedCrossover.terminal s lane) else .inl (s, lane)) + +/-- Signal presented toward the neighboring cell on one lane. -/ +def signal (sites : List Point) (p q : Point) (lane : Fin 2) : Variable := + port sites p (side p q) lane + +/-- The actual displayed position of a structured variable. -/ +def variablePosition (v : Variable) : Point := + cellOrigin 120 v.1 + v.2.elim + (fun p => PairedPorts.variablePosition p.1 p.2) PairedCrossover.variablePosition + +/-- The shared side point for a specified lane. -/ +def boundary (p : Point) (s : Fin 4) (lane : Fin 2) : Point := + cellOrigin 120 p + PairedPorts.boundaryPort s lane + +/-- A straight route from a cell boundary to its presented signal. -/ +def access (sites : List Point) (p q : Point) (lane : Fin 2) : Route := + [boundary p (side p q) lane, variablePosition (signal sites p q lane)] + +/-- The clause on lane zero goes forward; the clause on lane one goes backward. -/ +def link (sites : List Point) (p q : Point) (lane : Fin 2) : PlacedClause := + let a := signal sites p q lane + let b := signal sites q p lane + let ra := access sites p q lane + let rb := access sites q p lane + (if lane = 0 then [(a, false), (b, true)] else [(b, false), (a, true)], + boundary p (side p q) lane, if lane = 0 then [ra, rb] else [rb, ra]) + +/-- Two implication clauses per gap, excluding the final step into the original clause. -/ +def links (sites : List Point) (r : Route) : List PlacedClause := + r.dropLast.consecutivePairs.flatMap fun e => [link sites e.1 e.2 0, link sites e.1 e.2 1] + +/-- Final literal and its incidence route from the original clause center. -/ +def lastLiteral (sites : List Point) (r : Route) (positive : Bool) : Literal Variable × Route := + let p := r.getD (r.length - 2) (0, 0) + let q := r.getLastD (0, 0) + let s := side q p + let lane := PairedPorts.spokeLane s + ((signal sites p q lane, positive), + [center q, cellOrigin 120 q + PairedPorts.spokeElbow s, boundary q s lane, + variablePosition (signal sites p q lane)]) + +/-- Original clauses keep their order, arity and literal signs. -/ +def originalClauses (φ : CNF ℕ) (sites : List Point) : List PlacedClause := + φ.zipIdx.map fun (c, i) => + let ls := c.zipIdx.map fun (l, j) => + lastLiteral sites (occurrencePath φ (i, j, l)) l.2 + (ls.map Prod.fst, center (Layout.center (φ.distinctVars.length + i)), ls.map Prod.snd) + +/-- The ordinary fixed implication ring in an occupied cell. -/ +def ordinaryClauses (p : Point) : List PlacedClause := + OrdinaryRing.formula.zipIdx.map fun (c, i) => + (c.relabel (fun v => (p, .inl v)), + cellOrigin 120 p + OrdinaryRing.clausePositions.getD i (0, 0), + (OrdinaryRing.incidenceRoutes.filter fun e => e.1 == i).map fun e => + translate (cellOrigin 120 p) e.2.2) + +/-- The paired-port crossover in a selected crossing cell. -/ +def crossingClauses (p : Point) : List PlacedClause := + PairedCrossover.formula.zipIdx.map fun (c, i) => + (c.relabel (fun v => (p, .inr v)), + cellOrigin 120 p + PairedCrossover.clausePositions.getD i (0, 0), + (PairedCrossover.incidenceRoutes.filter fun e => e.1 == i).map fun e => + translate (cellOrigin 120 p) e.2.2) + +/-- The actual finite output, with no clauses for unoccupied ordinary cells. -/ +def placedClauses (φ : CNF ℕ) : List PlacedClause := + let sites := crossingSites φ + originalClauses φ sites ++ (ordinaryCells φ).flatMap ordinaryClauses ++ + sites.flatMap crossingClauses ++ + (Cslib.Logic.PL.occurrences φ).flatMap fun o => links sites (occurrencePath φ o) + +/-- Output formula with structured variable names. -/ +def rawFormula (φ : CNF ℕ) : CNF Variable := (placedClauses φ).map Prod.fst + +/-- Compact natural-number labels for the actual formula. -/ +def formula (φ : CNF ℕ) : CNF ℕ := (rawFormula φ).compact + +/-- Positions ordered by the final formula's distinct variables, then its clauses. -/ +def positions (φ : CNF ℕ) : List Point := + (rawFormula φ).distinctVars.map variablePosition ++ (placedClauses φ).map fun c => c.2.1 + +/-- Incidence routes ordered by clauses and by literal positions. -/ +def routes (φ : CNF ℕ) : List Route := (placedClauses φ).flatMap fun c => c.2.2 + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCorrectness.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCorrectness.lean new file mode 100644 index 000000000..fbe3820c8 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCorrectness.lean @@ -0,0 +1,179 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSemantics +public import Complexity.Computability.Complexity.Problems.SAT.Planar.Paths +import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments +import Complexity.Foundations.Data.List.Filter +import Mathlib.Data.List.GetD + +/-! +# Unique assignments in the actual paired compiler + +Every occupied ordinary cell belongs to a retained occurrence path. Each selected crossing is +occupied by a horizontal and a vertical occurrence path. The constant-wire theorem therefore +determines both crossover inputs and every ordinary ring from the original assignment. + +## Main results + +* `Cslib.Complexity.SAT.ParsimoniousPlanar.crossing_axes_exist`: actual selected crossings carry + both independent signals. + +## References + +These are original composition proofs using the canonical path occupancy certificates. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- Every actual selected crossing is used by both a horizontal and a vertical occurrence. -/ +theorem crossing_axes_exist {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {p : Point} (hp : p ∈ crossingSites φ) : + ∃ o ∈ Cslib.Logic.PL.occurrences φ, ∃ t ∈ Cslib.Logic.PL.occurrences φ, + p ∈ occurrencePath φ o ∧ p ∈ occurrencePath φ t ∧ + StraightThrough (occurrencePath φ o) p (1, 0) ∧ + StraightThrough (occurrencePath φ t) p (0, 1) := by + have hc := ((PlanarCompile.mem_crossingSites φ p).1 hp).2.2 + rw [← PlanarCompile.occurrencePaths_eq_paths] at hc + obtain ⟨i, j, hi, hj, hij, hpi, hpj⟩ := List.exists_pair_indices_of_two_le_filter_contains _ p hc + have hi' : i < (Cslib.Logic.PL.occurrences φ).length := by + simpa only [List.length_map] using hi + have hj' : j < (Cslib.Logic.PL.occurrences φ).length := by + simpa only [List.length_map] using hj + simp only [List.getElem_map] at hpi hpj + have hoi := List.getElem_mem hi' + have hoj := List.getElem_mem hj' + have hot : (Cslib.Logic.PL.occurrences φ)[i] ≠ (Cslib.Logic.PL.occurrences φ)[j] := by + intro he + exact hij ((Cslib.Logic.PL.occurrences_nodup φ).getElem_inj_iff.1 he) + rcases PlanarCompile.occurrencePath_crossing_straight hw hv hoi hp hpi with hs | hs <;> + rcases PlanarCompile.occurrencePath_crossing_straight hw hv hoj hp hpj with ht | ht + · exact False.elim (hot (PlanarCompile.crossing_axis_occurrence_eq hw hv hoi hoj hp + hpi hpj (Or.inl rfl) hs ht)) + · exact ⟨_, hoi, _, hoj, hpi, hpj, hs, ht⟩ + · exact ⟨_, hoj, _, hoi, hpj, hpi, ht, hs⟩ + · exact False.elim (hot (PlanarCompile.crossing_axis_occurrence_eq hw hv hoi hoj hp + hpi hpj (Or.inr rfl) hs ht)) + +/-- Every retained ordinary cell lies on an actual source occurrence path. -/ +theorem ordinary_occurrence_exists {φ : CNF ℕ} {p : Point} (hp : p ∈ ordinaryCells φ) : + ∃ o ∈ Cslib.Logic.PL.occurrences φ, p ∈ (occurrencePath φ o).dropLast := by + obtain ⟨⟨r, hr, hpr⟩, _⟩ := (mem_ordinaryCells φ p).1 hp + change r ∈ PlanarCompile.paths φ at hr + rw [← PlanarCompile.occurrencePaths_eq_paths] at hr + obtain ⟨o, ho, rfl⟩ := List.mem_map.mp hr + exact ⟨o, ho, hpr⟩ + +/-- A satisfying ordinary ring's value is determined by any occurrence retaining its cell. -/ +theorem ordinary_value_source {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ : Variable → Bool} (h : (rawFormula φ).eval σ = true) + {p : Point} (hp : p ∈ ordinaryCells φ) {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) (hpo : p ∈ (occurrencePath φ o).dropLast) : + σ (p, Sum.inl (0, 0)) = sourceValue φ σ o.2.2.1 := by + obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp hpo + have hk' : k < (occurrencePath φ o).length - 1 := by + simpa only [List.length_dropLast] using hk + have hwi : k < (wire (crossingSites φ) (occurrencePath φ o)).length := by + rwa [length_wire] + have hc := (wire_values_source hw hv h ho _ (List.getElem_mem hwi) 0).1 + rw [getElem_wire _ _ _ hk'] at hc + rw [← List.getElem_dropLast hk, heq] at hc + simpa only [signal, ordinary_port_value h hp] using hc + +/-- Each crossover input is exactly the source value on its occupying axis. -/ +theorem crossing_axis_value {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ : Variable → Bool} (h : (rawFormula φ).eval σ = true) + {p : Point} (hp : p ∈ crossingSites φ) {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) (hpo : p ∈ occurrencePath φ o) + (axis : Bool) + (hs : StraightThrough (occurrencePath φ o) p (if axis then (0, 1) else (1, 0))) : + σ (p, Sum.inr (PairedCrossover.terminal (if axis then 2 else 0) 0)) = + sourceValue φ σ o.2.2.1 := by + obtain ⟨k, hk, heq⟩ := List.mem_iff_getElem.mp hpo + obtain ⟨_, hnext, hnb⟩ := hs.getD_neighbors (PlanarCompile.occurrencePath_nodup ho) hk heq + have hk' : k < (occurrencePath φ o).length - 1 := by omega + have hwi : k < (wire (crossingSites φ) (occurrencePath φ o)).length := by + rwa [length_wire] + have hc := (wire_values_source hw hv h ho _ (List.getElem_mem hwi) 0).1 + rw [getElem_wire _ _ _ hk', heq] at hc + cases axis with + | false => + have hl : side p (p - (1, 0)) = 0 := by + simpa [PairedPorts.neighbor, sub_eq_add_neg] using side_neighbor p 0 + have hr : side p (p + (1, 0)) = 1 := by + simpa [PairedPorts.neighbor] using side_neighbor p 1 + rcases hnb with ⟨hprev, _⟩ | ⟨hprev, _⟩ + all_goals + simp only [Bool.false_eq_true, ↓reduceIte] at hprev ⊢ + simp only [signal, hprev, hl, hr] at hc + rw [crossing_port_value h hp] at hc + simpa using hc + | true => + have hb : side p (p - (0, 1)) = 2 := by + simpa [PairedPorts.neighbor, sub_eq_add_neg] using side_neighbor p 2 + have ht : side p (p + (0, 1)) = 3 := by + simpa [PairedPorts.neighbor] using side_neighbor p 3 + rcases hnb with ⟨hprev, _⟩ | ⟨hprev, _⟩ + all_goals + simp only [↓reduceIte] at hprev ⊢ + simp only [signal, hprev, hb, ht] at hc + rw [crossing_port_value h hp] at hc + simpa using hc + +/-- Source agreement forces agreement on every variable of every emitted ordinary ring. -/ +theorem ordinary_assignments_agree {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ τ : Variable → Bool} (hσ : (rawFormula φ).eval σ = true) + (hτ : (rawFormula φ).eval τ = true) + (he : ∀ v ∈ φ.vars, sourceValue φ σ v = sourceValue φ τ v) + {p : Point} (hp : p ∈ ordinaryCells φ) (v : OrdinaryRing.Variable) : + σ (p, Sum.inl v) = τ (p, Sum.inl v) := by + obtain ⟨o, ho, hpo⟩ := ordinary_occurrence_exists hp + have hs := (OrdinaryRing.eval_formula_iff _).1 + (((eval_rawFormula_iff φ σ).1 hσ).2.1 p hp) v + have ht := (OrdinaryRing.eval_formula_iff _).1 + (((eval_rawFormula_iff φ τ).1 hτ).2.1 p hp) v + exact hs.trans ((ordinary_value_source hw hv hσ hp ho hpo).trans + ((he _ (variable_mem_of_mem_occurrences ho)).trans + ((ordinary_value_source hw hv hτ hp ho hpo).symm.trans ht.symm))) + +/-- Source agreement forces agreement on every variable of every emitted crossover. -/ +theorem crossing_assignments_agree {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ τ : Variable → Bool} (hσ : (rawFormula φ).eval σ = true) + (hτ : (rawFormula φ).eval τ = true) + (he : ∀ v ∈ φ.vars, sourceValue φ σ v = sourceValue φ τ v) + {p : Point} (hp : p ∈ crossingSites φ) (v : PairedCrossover.Variable) : + σ (p, Sum.inr v) = τ (p, Sum.inr v) := by + obtain ⟨o, ho, t, ht, hpo, hpt, hhor, hver⟩ := crossing_axes_exist hw hv hp + have ha : σ (p, Sum.inr (PairedCrossover.terminal 0 0)) = + τ (p, Sum.inr (PairedCrossover.terminal 0 0)) := + (crossing_axis_value hw hv hσ hp ho hpo false hhor).trans + ((he _ (variable_mem_of_mem_occurrences ho)).trans + (crossing_axis_value hw hv hτ hp ho hpo false hhor).symm) + have hb : σ (p, Sum.inr (PairedCrossover.terminal 2 0)) = + τ (p, Sum.inr (PairedCrossover.terminal 2 0)) := + (crossing_axis_value hw hv hσ hp ht hpt true hver).trans + ((he _ (variable_mem_of_mem_occurrences ht)).trans + (crossing_axis_value hw hv hτ hp ht hpt true hver).symm) + have hs := congrFun (PairedCrossover.eq_canonicalExtension_of_holds + (((eval_rawFormula_iff φ σ).1 hσ).2.2.1 p hp)) v + have ht := congrFun (PairedCrossover.eq_canonicalExtension_of_holds + (((eval_rawFormula_iff φ τ).1 hτ).2.2.1 p hp)) v + exact hs.trans (by rw [ha, hb]; exact ht.symm) + + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCounting.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCounting.lean new file mode 100644 index 000000000..416552976 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedCounting.lean @@ -0,0 +1,199 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCorrectness +public import Complexity.Logics.Propositional.CNF.Compact.ModelCount +import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments +import Mathlib.Data.List.GetD + +/-! +# Parsimony of the actual paired-port compiler + +For width-three inputs with at most three literal occurrences per variable, every satisfying +source assignment has a satisfying output extension. Its values on the occurring output +variables are uniquely determined by the source assignment. Conversely, reading the ordinary +source cells recovers the source assignment. Counts use only occurring variables on both sides. + +## Main results + +* `sourceVariable_mem_rawFormula`: every used source representative occurs in the actual output. +* `raw_assignments_agree_iff`: source and output agreement coincide for satisfying assignments. +* `exists_satisfying_extension`: every satisfying source assignment has an output extension. +* `modelCount_rawFormula`, `modelCount_formula`: the actual compiler preserves model counts. +* `satisfiable_formula_iff`: the corresponding satisfiability equivalence. + +## References + +These are original counting and assignment-correspondence proofs for `PairedCompile`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- A used source variable has an indexed literal occurrence, including repeated literals. -/ +private theorem exists_occurrence_of_mem_vars {φ : CNF ℕ} {v : ℕ} (hv : v ∈ φ.vars) : + ∃ o ∈ Cslib.Logic.PL.occurrences φ, o.2.2.1 = v := by + rw [← map_occurrences] at hv + exact List.mem_map.mp hv + +/-- Every used source cell is retained and receives an ordinary ring. -/ +theorem source_cell_mem_ordinary {φ : CNF ℕ} {v : ℕ} (hv : v ∈ φ.vars) : + Layout.center (φ.distinctVars.idxOf v) ∈ ordinaryCells φ := by + obtain ⟨o, ho, rfl⟩ := exists_occurrence_of_mem_vars hv + have hlen := PlanarCompile.occurrencePath_length_ge_two ho + change 2 ≤ (occurrencePath φ o).length at hlen + have hhead := (PlanarCompile.occurrencePath_endpoints ho).1 + change (occurrencePath φ o).head? = _ at hhead + rw [List.head?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hhead + have he := Option.some.inj hhead + have hret : Layout.center (φ.distinctVars.idxOf o.2.2.1) ∈ + (occurrencePath φ o).dropLast := by + rw [← he] + have hi : 0 < (occurrencePath φ o).dropLast.length := by + simp only [List.length_dropLast] + omega + rw [← List.getElem_dropLast hi] + exact List.getElem_mem _ + exact (mem_ordinaryCells φ _).2 + ⟨⟨_, occurrencePath_mem_paths ho, hret⟩, PlanarCompile.source_center_not_crossing ho⟩ + +/-- Every used source representative is an occurring variable of the actual raw output. -/ +theorem sourceVariable_mem_rawFormula {φ : CNF ℕ} {v : ℕ} (hv : v ∈ φ.vars) : + (Layout.center (φ.distinctVars.idxOf v), Sum.inl (0, 0)) ∈ (rawFormula φ).vars := by + have hp := source_cell_mem_ordinary hv + have hm : (Layout.center (φ.distinctVars.idxOf v), Sum.inl (0, 0)) ∈ + CNF.vars ((ordinaryClauses (Layout.center (φ.distinctVars.idxOf v))).map Prod.fst) := by + rw [map_fst_ordinaryClauses, CNF.vars_relabel] + exact List.mem_map_of_mem (OrdinaryRing.variables_used (0, 0)) + obtain ⟨c, hc, b, hvar⟩ := CNF.mem_vars.mp hm + obtain ⟨pc, hpc, rfl⟩ := List.mem_map.mp hc + apply CNF.mem_vars.mpr + refine ⟨pc.1, List.mem_map.mpr ⟨pc, ?_, rfl⟩, b, hvar⟩ + simp only [placedClauses, List.mem_append, List.mem_flatMap] + exact Or.inl (Or.inl (Or.inr ⟨_, hp, hpc⟩)) + +private theorem pairedLinks_variable_value {V : Type*} (σ : V → Bool) + (cells : List (PairedWire.Cell V)) (value : Bool) + (h : ∀ c ∈ cells, ∀ lane, σ (c lane).1 = value ∧ σ (c lane).2 = value) + {v : V} (hv : v ∈ (PairedWire.links cells).vars) : σ v = value := by + obtain ⟨c, hc, b, hl⟩ := CNF.mem_vars.mp hv + obtain ⟨p, hp, hc⟩ := List.mem_flatMap.mp hc + have hm := List.of_mem_zip hp + have hfirst := h p.1 hm.1 + have hnext := h p.2 (List.mem_of_mem_tail hm.2) + simp only [PairedWire.gapClauses, List.mem_cons, List.not_mem_nil, or_false] at hc + rcases hc with rfl | rfl + · rcases Clause.fst_mem_of_mem_imp hl with hv | hv + · exact (congrArg σ (List.mem_singleton.mp hv)).trans (hfirst 0).2 + · exact (congrArg σ hv).trans (hnext 0).1 + · rcases Clause.fst_mem_of_mem_imp hl with hv | hv + · exact (congrArg σ (List.mem_singleton.mp hv)).trans (hnext 1).1 + · exact (congrArg σ hv).trans (hfirst 1).2 + +/-- Source agreement determines every occurring raw output variable. Assignments outside the +formula's variable support are unrestricted. -/ +theorem raw_assignments_agree {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ τ : Variable → Bool} (hσ : (rawFormula φ).eval σ = true) + (hτ : (rawFormula φ).eval τ = true) + (he : ∀ v ∈ φ.vars, sourceValue φ σ v = sourceValue φ τ v) : + ∀ v ∈ (rawFormula φ).vars, σ v = τ v := by + intro v hvar + obtain ⟨c, hc, b, hl⟩ := CNF.mem_vars.mp hvar + obtain ⟨pc, hpc, rfl⟩ := List.mem_map.mp hc + simp only [placedClauses, List.mem_append, List.mem_flatMap] at hpc + rcases hpc with ((hpc | ⟨p, hp, hpc⟩) | ⟨p, hp, hpc⟩) | ⟨o, ho, hpc⟩ + · obtain ⟨ci, hci, rfl⟩ := List.mem_map.mp hpc + obtain ⟨lr, hlr, heq⟩ := List.mem_map.mp hl + obtain ⟨lj, hlj, rfl⟩ := List.mem_map.mp hlr + have ho : (ci.2, lj.2, lj.1) ∈ Cslib.Logic.PL.occurrences φ := + List.mem_flatMap.mpr ⟨ci, hci, List.mem_map.mpr ⟨lj, hlj, rfl⟩⟩ + have heq' : (lastLiteral (crossingSites φ) + (occurrencePath φ (ci.2, lj.2, lj.1)) lj.1.2).1.1 = v := congrArg Prod.fst heq + rw [← heq'] + exact (lastLiteral_value hw hv hσ ho).trans + ((he _ (variable_mem_of_mem_occurrences ho)).trans (lastLiteral_value hw hv hτ ho).symm) + · obtain ⟨ci, _, rfl⟩ := List.mem_map.mp hpc + obtain ⟨lit, _, heq⟩ := List.mem_map.mp hl + have heq' : (p, Sum.inl lit.1) = v := congrArg Prod.fst heq + rw [← heq'] + exact ordinary_assignments_agree hw hv hσ hτ he hp lit.1 + · obtain ⟨ci, _, rfl⟩ := List.mem_map.mp hpc + obtain ⟨lit, _, heq⟩ := List.mem_map.mp hl + have heq' : (p, Sum.inr lit.1) = v := congrArg Prod.fst heq + rw [← heq'] + exact crossing_assignments_agree hw hv hσ hτ he hp lit.1 + · have hm : v ∈ CNF.vars ((links (crossingSites φ) (occurrencePath φ o)).map Prod.fst) := + CNF.mem_vars.mpr ⟨pc.1, List.mem_map.mpr ⟨pc, hpc, rfl⟩, b, hl⟩ + rw [map_fst_links] at hm + exact (pairedLinks_variable_value σ _ _ (wire_values_source hw hv hσ ho) hm).trans + ((he _ (variable_mem_of_mem_occurrences ho)).trans + (pairedLinks_variable_value τ _ _ (wire_values_source hw hv hτ ho) hm).symm) + +/-- Among satisfying assignments, agreement on raw variables is equivalent to agreement on the +occurring source variables read at their fixed source representatives. -/ +theorem raw_assignments_agree_iff {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ τ : Variable → Bool} (hσ : (rawFormula φ).eval σ = true) + (hτ : (rawFormula φ).eval τ = true) : + (∀ v ∈ (rawFormula φ).vars, σ v = τ v) ↔ + ∀ v ∈ φ.vars, sourceValue φ σ v = sourceValue φ τ v := + ⟨fun he _ hm => he _ (sourceVariable_mem_rawFormula hm), + raw_assignments_agree hw hv hσ hτ⟩ + +/-- Every satisfying source assignment extends to a satisfying raw output assignment with the +same source values. Together with `raw_assignments_agree_iff`, its occurring values are unique. -/ +theorem exists_satisfying_extension {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + (τ : ℕ → Bool) (hτ : φ.eval τ = true) : + ∃ σ : Variable → Bool, (rawFormula φ).eval σ = true ∧ + ∀ v ∈ φ.vars, sourceValue φ σ v = τ v := by + obtain ⟨σ, hcompat, hord, hcross⟩ := exists_compatible_assignment hw hv τ + have hσ := eval_rawFormula_of_compatible τ σ hcompat hord hcross hτ + refine ⟨σ, hσ, fun v hmem => ?_⟩ + obtain ⟨o, ho, rfl⟩ := exists_occurrence_of_mem_vars hmem + obtain ⟨c, hc⟩ := List.exists_mem_of_ne_nil _ (wire_ne_nil ho) + exact (wire_values_source hw hv hσ ho c hc 0).1.symm.trans (hcompat o ho c hc 0).1 + +/-- The actual raw compiler preserves satisfying-assignment counts under the source width and +literal-occurrence restrictions. Both sides count only occurring variables. -/ +theorem modelCount_rawFormula (φ : CNF ℕ) + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + (rawFormula φ).modelCount = φ.modelCount := by + refine CNF.modelCount_eq_card_filter + (fun σ (v : φ.vars.toFinset) => sourceValue φ σ v) + (fun τ => φ.eval (φ.extendAssignment τ) = true) (fun σ τ hσ hτ => ?_) fun τ => ?_ + · rw [funext_iff, Subtype.forall] + simp only [List.mem_toFinset] + exact (raw_assignments_agree_iff hw hv hσ hτ).symm + · constructor + · intro hτ + obtain ⟨σ, hσ, he⟩ := exists_satisfying_extension hw hv (φ.extendAssignment τ) hτ + exact ⟨σ, hσ, funext fun v => (he v (List.mem_toFinset.mp v.2)).trans + (CNF.extendAssignment_of_mem _ (List.mem_toFinset.mp v.2))⟩ + · rintro ⟨σ, hσ, rfl⟩ + rw [CNF.eval_extendAssignment_restrict] + exact eval_source_of_rawFormula hw hv hσ + +/-- Compact natural-number labels preserve the raw compiler's exact model count. -/ +theorem modelCount_formula (φ : CNF ℕ) + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + (formula φ).modelCount = φ.modelCount := by + rw [formula, CNF.modelCount_compact, modelCount_rawFormula φ hw hv] + +/-- The actual compact compiler preserves satisfiability under the source restrictions. -/ +theorem satisfiable_formula_iff (φ : CNF ℕ) + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + (formula φ).Satisfiable ↔ φ.Satisfiable := by + rw [← CNF.modelCount_pos_iff, ← CNF.modelCount_pos_iff, modelCount_formula φ hw hv] + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSemantics.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSemantics.lean new file mode 100644 index 000000000..5b766bf8b --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSemantics.lean @@ -0,0 +1,439 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile +public import Complexity.Logics.Propositional.CNF.Planar.PairedWire +import Complexity.Logics.Propositional.CNF.Planar.CrossoverCounting +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments +import Complexity.Foundations.Data.List.Last +import Mathlib.Data.List.GetD +import Mathlib.Tactic.FinCases + +/-! +# Assignment semantics of the paired compiler + +The actual clause blocks use ordinary CNF evaluation. Local rings and crossovers identify the +four wire values within each cell; paired links identify consecutive cell values. The source +assignment then determines every used output variable. + +## Main definitions + +* `Cslib.Complexity.SAT.ParsimoniousPlanar.wire`: the actual two-lane entry and exit variables. + +## Main results + +* `Cslib.Complexity.SAT.ParsimoniousPlanar.eval_rawFormula_iff`: exact clause-block semantics. + +## References + +These are original composition proofs for the finite paired compiler. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- Every retained point contributes the entry and exit on each of its two lanes. -/ +def wire (sites : List Point) (r : Route) : List (PairedWire.Cell Variable) := + r.dropLast.zipIdx.map fun (p, i) lane => + (signal sites p (r.getD (i - 1) p) lane, signal sites p (r.getD (i + 1) p) lane) + +/-- A point receives an ordinary ring exactly when a retained path uses it outside a crossing. -/ +theorem mem_ordinaryCells (φ : CNF ℕ) (p : Point) : + p ∈ ordinaryCells φ ↔ + (∃ r ∈ paths φ, p ∈ r.dropLast) ∧ p ∉ crossingSites φ := by + simp [ordinaryCells, mem_selectDistinct_replicate, List.mem_flatMap] + +/-- No ordinary ring is emitted twice. -/ +theorem ordinaryCells_nodup (φ : CNF ℕ) : (ordinaryCells φ).Nodup := + nodup_selectDistinct _ _ + +/-- The two local clause tables retain their exact formulas under variable tagging. -/ +theorem map_fst_ordinaryClauses (p : Point) : + (ordinaryClauses p).map Prod.fst = + OrdinaryRing.formula.relabel (fun v => ((p, Sum.inl v) : Variable)) := by + change (OrdinaryRing.formula.zipIdx.map fun c => + c.1.relabel (fun v => ((p, Sum.inl v) : Variable))) = _ + calc + _ = (OrdinaryRing.formula.zipIdx.map Prod.fst).map + (Clause.relabel (fun v => ((p, Sum.inl v) : Variable))) := by rw [List.map_map]; rfl + _ = _ := by rw [List.zipIdx_map_fst]; rfl + +/-- The crossover clause table is exactly the renamed paired-port formula. -/ +theorem map_fst_crossingClauses (p : Point) : + (crossingClauses p).map Prod.fst = + PairedCrossover.formula.relabel (fun v => ((p, Sum.inr v) : Variable)) := by + change (PairedCrossover.formula.zipIdx.map fun c => + c.1.relabel (fun v => ((p, Sum.inr v) : Variable))) = _ + calc + _ = (PairedCrossover.formula.zipIdx.map Prod.fst).map + (Clause.relabel (fun v => ((p, Sum.inr v) : Variable))) := by rw [List.map_map]; rfl + _ = _ := by rw [List.zipIdx_map_fst]; rfl + +/-- The emitted formula evaluates precisely its original, ordinary, crossing, and link blocks. -/ +theorem eval_rawFormula_iff (φ : CNF ℕ) (σ : Variable → Bool) : + (rawFormula φ).eval σ = true ↔ + CNF.eval σ ((originalClauses φ (crossingSites φ)).map Prod.fst) = true ∧ + (∀ p ∈ ordinaryCells φ, + OrdinaryRing.formula.eval (fun v => σ (p, Sum.inl v)) = true) ∧ + (∀ p ∈ crossingSites φ, + PairedCrossover.formula.eval (fun v => σ (p, Sum.inr v)) = true) ∧ + ∀ o ∈ Cslib.Logic.PL.occurrences φ, + CNF.eval σ ((links (crossingSites φ) (occurrencePath φ o)).map Prod.fst) = true := by + simp only [rawFormula, placedClauses, List.map_append, List.map_flatMap] + simp_rw [map_fst_ordinaryClauses, map_fst_crossingClauses] + simp only [eval_append, Bool.and_eq_true, eval_flatMap_eq_true_iff, eval_relabel, + Function.comp_def, and_assoc] + +/-- Ordinary cell ports all carry the same value. -/ +theorem ordinary_port_value {φ : CNF ℕ} {σ : Variable → Bool} + (h : (rawFormula φ).eval σ = true) {p : Point} (hp : p ∈ ordinaryCells φ) + (s : Fin 4) (lane : Fin 2) : + σ (port (crossingSites φ) p s lane) = σ (p, Sum.inl (0, 0)) := by + have hn := ((mem_ordinaryCells φ p).1 hp).2 + have hr := ((eval_rawFormula_iff φ σ).1 h).2.1 p hp + simpa only [port, hn, ↓reduceIte] using (OrdinaryRing.eval_formula_iff _).1 hr (s, lane) + +/-- A crossing port carries the horizontal or vertical incoming value, independently of lane. -/ +theorem crossing_port_value {φ : CNF ℕ} {σ : Variable → Bool} + (h : (rawFormula φ).eval σ = true) {p : Point} (hp : p ∈ crossingSites φ) + (s : Fin 4) (lane : Fin 2) : + σ (port (crossingSites φ) p s lane) = + if s.val < 2 then σ (p, Sum.inr (PairedCrossover.terminal 0 0)) + else σ (p, Sum.inr (PairedCrossover.terminal 2 0)) := by + have hr := ((eval_rawFormula_iff φ σ).1 h).2.2.1 p hp + simpa only [port, hp, ↓reduceIte] using PairedCrossover.terminal_value_of_holds hr s lane + +/-- The paired wire has one cell for each retained path point. -/ +theorem length_wire (sites : List Point) (r : Route) : (wire sites r).length = r.length - 1 := by + simp [wire] + +/-- The actual entry and exit variables at a retained path index. -/ +theorem getElem_wire (sites : List Point) (r : Route) (i : ℕ) (hi : i < r.length - 1) : + (wire sites r)[i]'(by simpa [wire] using hi) = fun lane => + (signal sites r[i] (r.getD (i - 1) r[i]) lane, + signal sites r[i] (r.getD (i + 1) r[i]) lane) := by + simp only [wire, List.getElem_map, List.getElem_zipIdx, Nat.zero_add, List.getElem_dropLast] + +/-- An actual occurrence selects a member of the canonical path list. -/ +theorem occurrencePath_mem_paths {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) : occurrencePath φ o ∈ paths φ := by + change PlanarCompile.occurrencePath φ o ∈ PlanarCompile.paths φ + rw [← PlanarCompile.occurrencePaths_eq_paths] + exact List.mem_map_of_mem ho + +/-- The side lookup recovers each cardinal neighbor's side. -/ +theorem side_neighbor (p : Point) (s : Fin 4) : + side p (p + PairedPorts.neighbor s) = s := by + rcases p with ⟨x, y⟩ + fin_cases s <;> simp [side, PlanarCompile.side, PairedPorts.neighbor] + +/-- All four actual entry and exit values within each retained wire cell agree. -/ +theorem wire_cells_agree {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ : Variable → Bool} (h : (rawFormula φ).eval σ = true) : + ∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ c ∈ wire (crossingSites φ) (occurrencePath φ o), c.Agree σ := by + intro o ho c hc + unfold wire at hc + obtain ⟨⟨p, k⟩, hpk, rfl⟩ := List.mem_map.mp hc + obtain ⟨hk, heq⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hpk) + change (occurrencePath φ o).dropLast[k] = p at heq + have hk' : k < (occurrencePath φ o).length := by + simp only [List.length_dropLast] at hk + omega + have heq' : (occurrencePath φ o)[k] = p := by + rw [← List.getElem_dropLast hk] + exact heq + have hpo : p ∈ occurrencePath φ o := heq' ▸ List.getElem_mem hk' + have hpod : p ∈ (occurrencePath φ o).dropLast := by + rw [← heq] + exact List.getElem_mem hk + by_cases hp : p ∈ crossingSites φ + · have hl : side p (p - (1, 0)) = 0 := by + simpa [PairedPorts.neighbor, sub_eq_add_neg] using side_neighbor p 0 + have hr : side p (p + (1, 0)) = 1 := by + simpa [PairedPorts.neighbor, sub_eq_add_neg] using side_neighbor p 1 + have hb : side p (p - (0, 1)) = 2 := by + simpa [PairedPorts.neighbor, sub_eq_add_neg] using side_neighbor p 2 + have ht : side p (p + (0, 1)) = 3 := by + simpa [PairedPorts.neighbor, sub_eq_add_neg] using side_neighbor p 3 + rcases PlanarCompile.occurrencePath_crossing_straight hw hv ho hp hpo with hs | hs + · obtain ⟨_, _, hnb⟩ := hs.getD_neighbors (PlanarCompile.occurrencePath_nodup ho) hk' heq' + rcases hnb with ⟨hprev, hnext⟩ | ⟨hprev, hnext⟩ + all_goals + simp only [PairedWire.Cell.Agree, hprev, hnext, signal, hl, hr] + intro lane + rw [crossing_port_value h hp, crossing_port_value h hp, crossing_port_value h hp] + simp + · obtain ⟨_, _, hnb⟩ := hs.getD_neighbors (PlanarCompile.occurrencePath_nodup ho) hk' heq' + rcases hnb with ⟨hprev, hnext⟩ | ⟨hprev, hnext⟩ + all_goals + simp only [PairedWire.Cell.Agree, hprev, hnext, signal, hb, ht] + intro lane + rw [crossing_port_value h hp, crossing_port_value h hp, crossing_port_value h hp] + simp + · have hord : p ∈ ordinaryCells φ := + (mem_ordinaryCells φ p).2 ⟨⟨_, occurrencePath_mem_paths ho, hpod⟩, hp⟩ + intro lane + change σ (signal _ _ _ lane) = σ (signal _ _ _ 0) ∧ + σ (signal _ _ _ lane) = σ (signal _ _ _ 0) + simp [signal, ordinary_port_value h hord] + +/-- Forgetting geometric annotations gives the generic paired-wire links exactly. -/ +theorem map_fst_links (sites : List Point) (r : Route) : + (links sites r).map Prod.fst = PairedWire.links (wire sites r) := by + simp only [links, List.map_flatMap, List.map_cons, List.map_nil, PairedWire.links] + rw [List.flatMap_def, List.flatMap_def] + congr 1 + apply List.ext_getElem + · simp [wire, List.consecutivePairs] + · intro i hi hj + have hi' : i + 2 < r.length := by + simp only [List.length_map, List.consecutivePairs, List.length_zip, + List.length_tail, List.length_dropLast] at hi + omega + simp only [List.getElem_map, List.consecutivePairs, List.getElem_zip, List.getElem_tail] + rw [getElem_wire sites r i (by omega), getElem_wire sites r (i + 1) (by omega)] + simp only [List.getElem_dropLast] + have hn : r.getD (i + 1) r[i] = r[i + 1] := List.getD_eq_getElem _ _ (by omega) + have hp : r.getD (i + 1 - 1) r[i + 1] = r[i] := by + rw [Nat.add_sub_cancel, List.getD_eq_getElem _ _ (by omega)] + simp only [hn, hp] + rfl + +/-- The source value is read at one fixed port of its ordinary source cell. -/ +def sourceValue (φ : CNF ℕ) (σ : Variable → Bool) (v : ℕ) : Bool := + σ (Layout.center (φ.distinctVars.idxOf v), Sum.inl (0, 0)) + +/-- Actual occurrence wires are nonempty. -/ +theorem wire_ne_nil {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) : + wire (crossingSites φ) (occurrencePath φ o) ≠ [] := by + have hlen := PlanarCompile.occurrencePath_length_ge_two ho + change 2 ≤ (occurrencePath φ o).length at hlen + intro he + have hw := length_wire (crossingSites φ) (occurrencePath φ o) + rw [he, List.length_nil] at hw + omega + +/-- The actual first entry has the source cell's value. -/ +theorem wire_first_value {φ : CNF ℕ} {σ : Variable → Bool} + (h : (rawFormula φ).eval σ = true) {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) (fallback : PairedWire.Cell Variable) : + σ (((wire (crossingSites φ) (occurrencePath φ o)).headD fallback 0).1) = + sourceValue φ σ o.2.2.1 := by + have hlen := PlanarCompile.occurrencePath_length_ge_two ho + change 2 ≤ (occurrencePath φ o).length at hlen + have hwpos : 0 < (wire (crossingSites φ) (occurrencePath φ o)).length := by + rw [length_wire] + omega + have hhead : (wire (crossingSites φ) (occurrencePath φ o)).headD fallback = + (wire (crossingSites φ) (occurrencePath φ o))[0] := by + generalize hc : wire (crossingSites φ) (occurrencePath φ o) = xs at hwpos ⊢ + cases xs with + | nil => simp at hwpos + | cons a rest => rfl + have hp := (PlanarCompile.occurrencePath_endpoints ho).1 + change (occurrencePath φ o).head? = _ at hp + rw [List.head?_eq_getElem?, List.getElem?_eq_getElem (by omega)] at hp + have hp' : (occurrencePath φ o)[0] = Layout.center (φ.distinctVars.idxOf o.2.2.1) := + Option.some.inj hp + have hret : Layout.center (φ.distinctVars.idxOf o.2.2.1) ∈ + (occurrencePath φ o).dropLast := by + rw [← hp'] + have hdrop : 0 < (occurrencePath φ o).dropLast.length := by + simp only [List.length_dropLast] + omega + rw [← List.getElem_dropLast hdrop] + exact List.getElem_mem _ + have hord := (mem_ordinaryCells φ _).2 + ⟨⟨_, occurrencePath_mem_paths ho, hret⟩, PlanarCompile.source_center_not_crossing ho⟩ + rw [hhead, getElem_wire _ _ 0 (by omega)] + simp only [Nat.zero_sub] + rw [List.getD_eq_getElem _ _ (by omega), hp'] + exact ordinary_port_value h hord _ 0 + +/-- Every actual entry and exit along an occurrence wire has exactly its source value. -/ +theorem wire_values_source {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ : Variable → Bool} (h : (rawFormula φ).eval σ = true) + {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) : + ∀ c ∈ wire (crossingSites φ) (occurrencePath φ o), ∀ lane, + σ (c lane).1 = sourceValue φ σ o.2.2.1 ∧ + σ (c lane).2 = sourceValue φ σ o.2.2.1 := by + let fallback : PairedWire.Cell Variable := fun _ => (((0, 0), Sum.inl (0, 0)), + ((0, 0), Sum.inl (0, 0))) + have hl := ((eval_rawFormula_iff φ σ).1 h).2.2.2 o ho + rw [map_fst_links] at hl + have he := PairedWire.agree_head_of_ne_nil σ fallback _ (wire_ne_nil ho) + (wire_cells_agree hw hv h o ho) hl + rwa [wire_first_value h ho fallback] at he + +/-- Constant values along a nonempty retained wire determine its final literal. -/ +theorem lastLiteral_value_of_wire (sites : List Point) (r : Route) (positive value : Bool) + (σ : Variable → Bool) (hlen : 2 ≤ r.length) + (hcompat : ∀ c ∈ wire sites r, ∀ lane, + σ (c lane).1 = value ∧ σ (c lane).2 = value) : + σ (lastLiteral sites r positive).1.1 = value := by + have hi : r.length - 2 < r.length - 1 := by omega + have hwi : r.length - 2 < (wire sites r).length := by rwa [length_wire] + have hc := hcompat _ (List.getElem_mem hwi) + rw [getElem_wire _ _ _ hi] at hc + have hnext : r.length - 2 + 1 = r.length - 1 := by omega + have hget (d : Point) : r.getD (r.length - 1) d = r[r.length - 1] := + List.getD_eq_getElem _ _ (by omega) + simp only [hnext, hget] at hc + have hne : r ≠ [] := by intro he; rw [he] at hlen; simp at hlen + unfold lastLiteral + rw [List.getD_eq_getElem _ _ (by omega), List.getLastD_eq_getElem _ _ hne] + exact (hc _).2 + +/-- The literal emitted at an occurrence has precisely its source variable's value. -/ +theorem lastLiteral_value {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ : Variable → Bool} (h : (rawFormula φ).eval σ = true) + {o : Occurrence ℕ} (ho : o ∈ Cslib.Logic.PL.occurrences φ) : + σ (lastLiteral (crossingSites φ) (occurrencePath φ o) o.2.2.2).1.1 = + sourceValue φ σ o.2.2.1 := + lastLiteral_value_of_wire _ _ _ _ σ (PlanarCompile.occurrencePath_length_ge_two ho) + (wire_values_source hw hv h ho) + +/-- Replacing occurrence variables by equal-valued endpoint variables preserves evaluation. -/ +theorem eval_originalClauses_of_values (φ : CNF ℕ) (sites : List Point) + (σ : Variable → Bool) (τ : ℕ → Bool) + (hvalues : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + σ (lastLiteral sites (occurrencePath φ o) o.2.2.2).1.1 = τ o.2.2.1) : + CNF.eval σ ((originalClauses φ sites).map Prod.fst) = φ.eval τ := by + have hc (c : Clause ℕ × ℕ) (hc : c ∈ φ.zipIdx) : + Clause.eval σ (c.1.zipIdx.map fun l => + (lastLiteral sites (occurrencePath φ (c.2, l.2, l.1)) l.1.2).1) = + c.1.eval τ := by + have hm : + (c.1.zipIdx.map fun l => + (lastLiteral sites (occurrencePath φ (c.2, l.2, l.1)) l.1.2).1).map + (Literal.eval σ) = c.1.map (Literal.eval τ) := by + calc + _ = c.1.zipIdx.map (fun l => Literal.eval τ l.1) := by + rw [List.map_map] + apply List.map_congr_left + intro l hl + have ho : (c.2, l.2, l.1) ∈ Cslib.Logic.PL.occurrences φ := + List.mem_flatMap.mpr ⟨c, hc, List.mem_map.mpr ⟨l, hl, rfl⟩⟩ + simp only [Function.comp_def, Literal.eval] + rw [hvalues _ ho] + rfl + _ = (c.1.zipIdx.map Prod.fst).map (Literal.eval τ) := by rw [List.map_map]; rfl + _ = _ := by rw [List.zipIdx_map_fst] + have he := congrArg (fun bs : List Bool => bs.any id) hm + simpa only [List.any_map, Function.comp_def, id_eq, Clause.eval] using he + have hm : ((originalClauses φ sites).map Prod.fst).map (Clause.eval σ) = + φ.map (Clause.eval τ) := by + calc + _ = φ.zipIdx.map (fun c => Clause.eval τ c.1) := by + simp only [originalClauses, List.map_map, Function.comp_def] + apply List.map_congr_left + exact hc + _ = (φ.zipIdx.map Prod.fst).map (Clause.eval τ) := by rw [List.map_map]; rfl + _ = _ := by rw [List.zipIdx_map_fst] + have he := congrArg (fun bs : List Bool => bs.all id) hm + simpa only [List.all_map, Function.comp_def, id_eq, CNF.eval] using he + +/-- Every satisfying output assignment projects to a satisfying source assignment. -/ +theorem eval_source_of_rawFormula {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) + {σ : Variable → Bool} (h : (rawFormula φ).eval σ = true) : + φ.eval (sourceValue φ σ) = true := by + rw [← eval_originalClauses_of_values φ (crossingSites φ) σ (sourceValue φ σ) + (fun _ ho => lastLiteral_value hw hv h ho)] + exact ((eval_rawFormula_iff φ σ).1 h).1 + +/-- Lift compatible values on the original path cells to the new local templates. -/ +def liftAssignment (σ : PlanarCompile.Variable → Bool) : Variable → Bool + | (p, .inl _) => σ (p, none) + | (p, .inr v) => PairedCrossover.canonicalExtension + (σ (p, some .aLeft)) (σ (p, some .bTop)) v + +/-- The lifted paired signal is the original signal on either lane. -/ +theorem liftAssignment_signal (sites : List Point) (σ : PlanarCompile.Variable → Bool) + (hcross : ∀ p ∈ sites, Crossover.splitFormula.eval (fun v => σ (p, some v)) = true) + (p q : Point) (lane : Fin 2) : + liftAssignment σ (signal sites p q lane) = σ (PlanarCompile.signal sites p q) := by + by_cases hp : p ∈ sites + · have heq := congrFun (Crossover.eq_canonicalSplitExtension_of_holds (hcross p hp)) + (Crossover.splitTerminal (side p q)) + simp only [signal, port, hp, ↓reduceIte, liftAssignment, + PairedCrossover.terminal_values_canonicalExtension, PlanarCompile.signal] + change (if (side p q).val < 2 then σ (p, some .aLeft) else σ (p, some .bTop)) = + σ (p, some (Crossover.splitTerminal (side p q))) + generalize hs : side p q = s at heq ⊢ + fin_cases s + all_goals first + | rfl + | simpa [Crossover.splitTerminal, Crossover.canonicalSplitExtension, + Crossover.canonicalExtension, Crossover.assignment, Function.comp_def, + Crossover.SplitVariable.origin] using heq.symm + · simp [signal, port, hp, liftAssignment, PlanarCompile.signal] + +/-- Every source assignment has compatible values in every local template and on both lanes. -/ +theorem exists_compatible_assignment {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) (τ : ℕ → Bool) : + ∃ σ : Variable → Bool, + (∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ c ∈ wire (crossingSites φ) (occurrencePath φ o), ∀ lane, + σ (c lane).1 = τ o.2.2.1 ∧ σ (c lane).2 = τ o.2.2.1) ∧ + (∀ p ∈ ordinaryCells φ, + OrdinaryRing.formula.eval (fun v => σ (p, Sum.inl v)) = true) ∧ + (∀ p ∈ crossingSites φ, + PairedCrossover.formula.eval (fun v => σ (p, Sum.inr v)) = true) := by + obtain ⟨σ, hcompat, hcross⟩ := PlanarCompile.exists_compatible_assignment hw hv τ + refine ⟨liftAssignment σ, ?_, ?_, ?_⟩ + · intro o ho c hc lane + unfold wire at hc + obtain ⟨⟨p, k⟩, hpk, rfl⟩ := List.mem_map.mp hc + dsimp only + rw [liftAssignment_signal _ σ hcross, liftAssignment_signal _ σ hcross] + apply hcompat o ho + (PlanarCompile.signal (crossingSites φ) p ((occurrencePath φ o).getD (k - 1) p), + PlanarCompile.signal (crossingSites φ) p ((occurrencePath φ o).getD (k + 1) p)) + exact List.mem_map.mpr ⟨(p, k), hpk, rfl⟩ + · intro p _ + exact OrdinaryRing.eval_constant _ + · intro p _ + exact PairedCrossover.eval_canonicalExtension _ _ + +/-- Compatible local assignments satisfy the actual output exactly when the source holds. -/ +theorem eval_rawFormula_of_compatible {φ : CNF ℕ} (τ : ℕ → Bool) (σ : Variable → Bool) + (hcompat : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ c ∈ wire (crossingSites φ) (occurrencePath φ o), ∀ lane, + σ (c lane).1 = τ o.2.2.1 ∧ σ (c lane).2 = τ o.2.2.1) + (hord : ∀ p ∈ ordinaryCells φ, + OrdinaryRing.formula.eval (fun v => σ (p, Sum.inl v)) = true) + (hcross : ∀ p ∈ crossingSites φ, + PairedCrossover.formula.eval (fun v => σ (p, Sum.inr v)) = true) + (h : φ.eval τ = true) : (rawFormula φ).eval σ = true := by + apply (eval_rawFormula_iff φ σ).2 + refine ⟨?_, hord, hcross, ?_⟩ + · rw [eval_originalClauses_of_values φ (crossingSites φ) σ τ ?_] + · exact h + · intro o ho + exact lastLiteral_value_of_wire _ _ _ _ σ + (PlanarCompile.occurrencePath_length_ge_two ho) (hcompat o ho) + · intro o ho + rw [map_fst_links] + exact PairedWire.eval_links_of_constant σ (τ o.2.2.1) _ (hcompat o ho) + + + + + + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/ComplexityTests/PairedCounting.lean b/ComplexityTests/PairedCounting.lean new file mode 100644 index 000000000..2a2ad5158 --- /dev/null +++ b/ComplexityTests/PairedCounting.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCounting + +/-! # Exact counts and assignment correspondence of the actual paired compiler -/ + +open Cslib.Logic.PL Cslib.Complexity.SAT.ParsimoniousPlanar + +/-- The empty source has one assignment on its empty variable set. -/ +example : (formula []).modelCount = 1 := by + rw [modelCount_formula _ (by simp) (by simp [CNF.vars])] + decide + +/-- An empty clause remains unsatisfiable. -/ +example : (formula [[]]).modelCount = 0 := by + rw [modelCount_formula _ (by simp) (by simp [CNF.vars])] + decide + +/-- A singleton source variable acquires no auxiliary choices. -/ +example : (formula [[(7, true)]]).modelCount = 1 := by + rw [modelCount_formula _ (by simp) (fun _ => List.count_le_length.trans (by decide))] + decide + +/-- Repeated literal occurrences remain one forced source variable. -/ +example : (formula [[(7, true), (7, true), (7, true)]]).modelCount = 1 := by + rw [modelCount_formula _ (by simp) (fun _ => List.count_le_length.trans (by decide))] + decide + +/-- A tautological source variable retains both choices. -/ +example : (formula [[(7, true), (7, false)]]).modelCount = 2 := by + rw [modelCount_formula _ (by simp) (fun _ => List.count_le_length.trans (by decide))] + decide + +/-- Contradictory source unit clauses have no output models. -/ +example : (formula [[(0, true)], [(0, false)]]).modelCount = 0 := by + rw [modelCount_formula _ (by simp) (fun _ => List.count_le_length.trans (by decide))] + decide + +/-- Both source variables occur twice, on paths with actual selected crossings. -/ +private def crossedSource : CNF ℕ := + [[(0, true), (1, true)], [(1, false), (0, true)]] + +private theorem crossed_width : ∀ c ∈ crossedSource, c.length ≤ 3 := by decide + +private theorem crossed_count : ∀ v, crossedSource.vars.count v ≤ 3 := by + intro v + by_cases h0 : v = 0 + · subst v + decide + by_cases h1 : v = 1 + · subst v + decide + simp [crossedSource, CNF.vars, Ne.symm h0, Ne.symm h1] + +example : crossingSites crossedSource ≠ [] := by decide +kernel + +example : (formula crossedSource).modelCount = 2 := by + rw [modelCount_formula _ crossed_width crossed_count] + decide + +example : (rawFormula crossedSource).modelCount = 2 := by + rw [modelCount_rawFormula _ crossed_width crossed_count] + decide + +/-- Source values determine the actual raw models, including crossover internals. -/ +example (σ τ : Variable → Bool) (hσ : (rawFormula crossedSource).eval σ = true) + (hτ : (rawFormula crossedSource).eval τ = true) + (he : ∀ v ∈ crossedSource.vars, sourceValue crossedSource σ v = sourceValue crossedSource τ v) : + ∀ v ∈ (rawFormula crossedSource).vars, σ v = τ v := + raw_assignments_agree crossed_width crossed_count hσ hτ he + +/-- First-occurrence compaction works for an infinite variable type and sparse source names. -/ +example : (CNF.compact [[(7, true), (7, false)], [(1000000, true)]]).modelCount = 2 := by + rw [CNF.modelCount_compact] + decide + +/-- Compaction also works when the variable type is empty. -/ +example : (CNF.compact ([] : CNF Empty)).modelCount = 1 := by + rw [CNF.modelCount_compact] + decide diff --git a/ComplexityTests/PlanarCompile.lean b/ComplexityTests/PlanarCompile.lean index c6ee76142..18766bc64 100644 --- a/ComplexityTests/PlanarCompile.lean +++ b/ComplexityTests/PlanarCompile.lean @@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Erik Demaine -/ -import Complexity.Computability.Complexity.Problems.SAT.Planar.Compile +import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile import Complexity.Computability.Complexity.Problems.Graph.OrthogonalDrawing import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree import Complexity.Logics.Propositional.CNF.ImplicationChain @@ -12,7 +12,7 @@ import Complexity.Logics.Propositional.CNF.ImplicationChain namespace ComplexityTests.PlanarCompile open Cslib.Logic.PL Cslib.Orthogonal Cslib.Complexity -open SAT.PlanarCompile +open SAT.ParsimoniousPlanar -- The compiler preserves the two degenerate SAT instances, including the empty clause. example : formula [] = [] := by decide +kernel @@ -20,20 +20,20 @@ example : formula [[]] = [[]] := by decide +kernel -- A unit clause produces a nonempty wire, without inserting any crossing gadgets. example : crossingSites [[(7, true)]] = [] := by decide +kernel -example : (rawFormula [[(7, true)]]).length = 32 := by decide +kernel -example : (rawFormula [[(7, true)]]).vars.length = 63 := by decide +kernel +example : (rawFormula [[(7, true)]]).length = 319 := by decide +kernel +example : (rawFormula [[(7, true)]]).vars.length = 637 := by decide +kernel example : (rawFormula [[(7, true)]]).eval (fun _ => true) = true := by decide +kernel --- Crossing ports use the same left/right/bottom/top order as the macrocell API. -example : signal [(0, 0)] (0, 0) (-1, 0) = - ((0, 0), some CNF.Crossover.SplitVariable.aLeft) := by decide -example : access [] (0, 0) (1, 0) = [(240, 120), (120, 120)] := by decide +-- Crossing ports retain the side order and keep both lanes separate. +example : signal [(0, 0)] (0, 0) (-1, 0) 0 = + ((0, 0), Sum.inr (CNF.PairedCrossover.terminal 0 0)) := by decide +example : access [] (0, 0) (1, 0) 0 = [(240, 110), (220, 110)] := by decide --- Directed links need one binary clause, with the polarity-dependent orientation. -example : (link [] true (0, 0) (1, 0)).1 = - [(((1, 0), none), false), (((0, 0), none), true)] := by decide -example : (link [] false (0, 0) (1, 0)).1 = - [(((0, 0), none), false), (((1, 0), none), true)] := by decide +-- Separate forward and backward lanes close the slack between adjacent cells. +example : (link [] (0, 0) (1, 0) 0).1 = + [(((0, 0), Sum.inl (1, 0)), false), (((1, 0), Sum.inl (0, 0)), true)] := by decide +example : (link [] (0, 0) (1, 0) 1).1 = + [(((1, 0), Sum.inl (0, 1)), false), (((0, 0), Sum.inl (1, 1)), true)] := by decide -- Repeated literal incidences contribute separately to the actual graph degree. example : (SAT.incidenceGraph [[(5, true), (5, false)]]).incidenceVertices.count 0 = 2 := diff --git a/ComplexityTests/PlanarSemantics.lean b/ComplexityTests/PlanarSemantics.lean index d82724d1c..d2947cb38 100644 --- a/ComplexityTests/PlanarSemantics.lean +++ b/ComplexityTests/PlanarSemantics.lean @@ -3,9 +3,10 @@ Copyright (c) 2026 Erik Demaine. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Erik Demaine -/ -import Complexity.Computability.Complexity.Problems.SAT.Planar.Completeness +import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCounting +import Mathlib.Tactic.FinCases -open Cslib.Logic.PL Cslib.Logic.PL.CNF Cslib.Complexity.SAT.PlanarCompile +open Cslib.Logic.PL Cslib.Logic.PL.CNF Cslib.Complexity.SAT.ParsimoniousPlanar -- Concrete nonempty actual compiler outputs have satisfying assignments for both polarities. example : (rawFormula [[(0, true)]]).eval (fun _ => true) = true := by decide +kernel @@ -14,11 +15,12 @@ example : (rawFormula [[(0, false)]]).eval (fun _ => false) = true := by decide -- The actual compact output cannot manufacture a model of contradictory unit clauses. example : ¬ (formula [[(0, true)], [(0, false)]]).Satisfiable := by intro hs - obtain ⟨τ, hτ⟩ := satisfiable_source_of_formula + have hsource := (satisfiable_formula_iff [[(0, true)], [(0, false)]] (by decide : ∀ c ∈ ([[(0, true)], [(0, false)]] : CNF ℕ), c.length ≤ 3) (fun v => (List.count_le_length : (CNF.vars ([[(0, true)], [(0, false)]] : CNF ℕ)).count v ≤ - (CNF.vars ([[(0, true)], [(0, false)]] : CNF ℕ)).length).trans (by decide)) hs + (CNF.vars ([[(0, true)], [(0, false)]] : CNF ℕ)).length).trans (by decide))).1 hs + obtain ⟨τ, hτ⟩ := hsource cases hv : τ 0 <;> simp [CNF.eval, Clause.eval, Literal.eval, hv] at hτ private def crossedSource : CNF ℕ := @@ -40,29 +42,39 @@ private theorem crossed_count : ∀ v, crossedSource.vars.count v ≤ 3 := by simp [crossedSource, CNF.vars, Ne.symm h0, Ne.symm h1] example : (formula crossedSource).Satisfiable := by - apply (satisfiable_formula_iff crossed_width crossed_count).2 + apply (satisfiable_formula_iff crossedSource crossed_width crossed_count).2 exact ⟨fun v => v == 0, by decide⟩ example : (formula []).Satisfiable := by - apply (satisfiable_formula_iff (by simp) (by simp [CNF.vars])).2 + apply (satisfiable_formula_iff [] (by simp) (by simp [CNF.vars])).2 exact ⟨fun _ => false, rfl⟩ example : ¬ (formula [[]]).Satisfiable := by - rw [satisfiable_formula_iff (by simp) (by simp [CNF.vars])] + rw [satisfiable_formula_iff [[]] (by simp) (by simp [CNF.vars])] rintro ⟨τ, hτ⟩ simp [CNF.eval, Clause.eval] at hτ -- At this actual selected crossing, horizontal true and vertical false coexist in one model. example : ∃ σ : Variable → Bool, (rawFormula crossedSource).eval σ = true ∧ - σ ((10, 34), some Crossover.SplitVariable.aLeft) = true ∧ - σ ((10, 34), some Crossover.SplitVariable.bTop) = false := by + σ ((10, 34), Sum.inr (CNF.PairedCrossover.terminal 0 0)) = true ∧ + σ ((10, 34), Sum.inr (CNF.PairedCrossover.terminal 2 0)) = false := by let τ : ℕ → Bool := fun v => v == 0 - obtain ⟨σ, hcompat, hcross⟩ := exists_compatible_assignment crossed_width crossed_count τ + obtain ⟨σ, hcompat, hord, hcross⟩ := exists_compatible_assignment crossed_width crossed_count τ have hh := hcompat (0, 0, (0, true)) (by decide) - (((10, 34), some Crossover.SplitVariable.aLeft), - ((10, 34), some Crossover.SplitVariable.aRight)) (by decide +kernel) + (fun lane => (signal (crossingSites crossedSource) (10, 34) (9, 34) lane, + signal (crossingSites crossedSource) (10, 34) (11, 34) lane)) (by + apply List.mem_iff_getElem.mpr + refine ⟨40, by decide +kernel, ?_⟩ + funext lane + fin_cases lane <;> decide +kernel) 0 have hv := hcompat (0, 1, (1, true)) (by decide) - (((10, 34), some Crossover.SplitVariable.bTop), - ((10, 34), some Crossover.SplitVariable.bBottom)) (by decide +kernel) - exact ⟨σ, eval_rawFormula_complete_of_compatible crossedSource τ σ - (fun _ ho => occurrencePath_length_ge_two ho) hcompat hcross (by decide), hh.1, hv.1⟩ + (fun lane => (signal (crossingSites crossedSource) (10, 34) (10, 33) lane, + signal (crossingSites crossedSource) (10, 34) (10, 35) lane)) (by + apply List.mem_iff_getElem.mpr + refine ⟨24, by decide +kernel, ?_⟩ + funext lane + fin_cases lane <;> decide +kernel) 0 + have hp : (10, 34) ∈ crossingSites crossedSource := by decide +kernel + refine ⟨σ, eval_rawFormula_of_compatible τ σ hcompat hord hcross (by decide), ?_, ?_⟩ + · simpa [signal, port, hp, side, Cslib.Complexity.SAT.PlanarCompile.side, τ] using hh.1 + · simpa [signal, port, hp, side, Cslib.Complexity.SAT.PlanarCompile.side, τ] using hv.1 From 2ba82f8eacc12da628fed90bc6fcb02390961d7a Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 07:45:15 -0400 Subject: [PATCH 8/9] feat(planar-SAT): certify paired compiler occurrence and resource bounds Part of #1144, split from the internally reviewed parsimonious planar SAT development. All six local validation gates and hygiene checks pass. No selected abstract changes. AI-assisted implementation and internal review with Codex gpt-6.1-sol; outside-fleet coordinator review remains required. --- Complexity.lean | 4 + .../Problems/SAT/Planar/PairedBounds.lean | 541 ++++++++++++++++++ .../Problems/SAT/Planar/PairedOccurrence.lean | 331 +++++++++++ .../Problems/SAT/Planar/PairedPolyTime.lean | 375 ++++++++++++ .../Problems/SAT/Planar/PairedSize.lean | 225 ++++++++ ComplexityTests/PairedOccurrence.lean | 28 + ComplexityTests/PlanarBounds.lean | 14 +- ComplexityTests/PlanarOccurrence.lean | 47 +- 8 files changed, 1515 insertions(+), 50 deletions(-) create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedBounds.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedOccurrence.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedPolyTime.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSize.lean create mode 100644 ComplexityTests/PairedOccurrence.lean diff --git a/Complexity.lean b/Complexity.lean index 14fa3ef6e..ce53c88c7 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -809,13 +809,17 @@ public import Complexity.Computability.Complexity.Problems.SAT.Planar.CrossoverL public import Complexity.Computability.Complexity.Problems.SAT.Planar.Geometry public import Complexity.Computability.Complexity.Problems.SAT.Planar.Occurrence public import Complexity.Computability.Complexity.Problems.SAT.Planar.OrdinaryRing +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedBounds public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCorrectness public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCounting public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayout public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutChecks public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCrossoverLayoutData +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedOccurrence +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedPolyTime public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSemantics +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSize public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedBounds.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedBounds.lean new file mode 100644 index 000000000..3bda2d423 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedBounds.lean @@ -0,0 +1,541 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSize +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds +public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalDrawing +import Complexity.Computability.Complexity.Problems.SAT.Planar.PathGeometry +import Complexity.Computability.Complexity.Problems.Graph.OrthogonalPath +import Mathlib.Data.List.GetD +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum +import Mathlib.Tactic.SplitIfs + +/-! +# Quantitative bounds for the paired-port compiler + +The actual output fits the unchanged 240-macrocell grid, with linear grid sides and quadratic +clause, literal, route, segment, and Manhattan-wire costs. Bounds concern the emitted lists and +all their integer points; geometric separation is proved separately. + +## Main results + +* `drawing_fits`, `gridWidth_le`, `gridHeight_le`: the actual drawing fits a linear grid. +* `length_routes`, `drawingSegmentCount_eq`, `drawingWireLength_eq`: exact output costs. +* `routes_length_le`, `drawingSegmentCount_le`, `drawingWireLength_le`: quadratic bounds. + +## References + +The unchanged path and grid estimates are reused from `Planar.Size` and `Planar.Bounds`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- The unchanged horizontal side of the 240-macrocell drawing. -/ +abbrev gridWidth := PlanarCompile.gridWidth + +/-- The unchanged vertical side of the 240-macrocell drawing. -/ +abbrev gridHeight := PlanarCompile.gridHeight + +private def CellBound (φ : CNF ℕ) (p : Point) : Prop := + 0 ≤ p.1 ∧ p.1 ≤ 8 * ((SAT.incidenceGraph φ).1.length : ℤ) ∧ + 0 ≤ p.2 ∧ p.2 ≤ 8 * ((SAT.incidenceGraph φ).1.length : ℤ) + + 2 * (SAT.incidenceGraph φ).2.length + +private def OutputBound (φ : CNF ℕ) (p : Point) : Prop := + 0 ≤ p.1 ∧ p.1 ≤ gridWidth φ ∧ 0 ≤ p.2 ∧ p.2 ≤ gridHeight φ + +private theorem outputBound_add {φ : CNF ℕ} {p a : Point} (hp : CellBound φ p) + (ha : 0 ≤ a.1 ∧ a.1 ≤ 240 ∧ 0 ≤ a.2 ∧ a.2 ≤ 240) : + OutputBound φ (cellOrigin 120 p + a) := by + simp only [OutputBound, gridWidth, gridHeight, PlanarCompile.gridWidth, PlanarCompile.gridHeight, + Prod.fst_add, Prod.snd_add, cellOrigin, Nat.cast_mul, Nat.cast_add, Nat.cast_ofNat] + dsimp only [CellBound] at hp + norm_num at * + constructor + · nlinarith [hp.1, ha.1] + constructor + · nlinarith [hp.2.1, ha.2.1] + constructor <;> nlinarith [hp.2.2.1, hp.2.2.2, ha.2.2.1, ha.2.2.2] + +private theorem cellBound_path {φ : CNF ℕ} {r : Route} (hr : r ∈ paths φ) + {p : Point} (hp : p ∈ r) : CellBound φ p := by + change r ∈ (SAT.incidenceGraph φ).unitPaths at hr + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hr + have hi' : i < (SAT.incidenceGraph φ).2.length := by simpa using hi + have hp' : p ∈ (SAT.incidenceGraph φ).unitPath i := by + simpa only [Graph.unitPath, List.getD_eq_getElem _ _ hi] using hp + exact Graph.unitPath_bounds _ (fun _ he => SAT.incidenceGraph_endpoints he) + (fun _ he => SAT.incidenceGraph_loopless he) hi' hp' + +private theorem cellBound_occurrencePath {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) {p : Point} (hp : p ∈ occurrencePath φ o) : + CellBound φ p := by + have hi : (Cslib.Logic.PL.occurrences φ).idxOf o < (paths φ).length := by + rw [PlanarCompile.paths_length, ← Cslib.Logic.PL.length_occurrences] + exact List.idxOf_lt_length_of_mem ho + apply cellBound_path (r := occurrencePath φ o) _ hp + change (paths φ).getD ((Cslib.Logic.PL.occurrences φ).idxOf o) [] ∈ paths φ + rw [List.getD_eq_getElem _ _ hi] + exact List.getElem_mem hi + +private theorem cellBound_crossing {φ : CNF ℕ} {p : Point} (hp : p ∈ crossingSites φ) : + CellBound φ p := by + have hlen := (PlanarCompile.mem_crossingSites φ p).mp hp |>.2.2 + have hnonempty : ((paths φ).filter fun r => r.contains p) ≠ [] := by + intro he + rw [he] at hlen + simp at hlen + obtain ⟨r, hr⟩ := List.exists_mem_of_ne_nil _ hnonempty + obtain ⟨hr, hp⟩ := List.mem_filter.mp hr + exact cellBound_path hr (List.contains_iff_mem.mp hp) + +private theorem cellBound_clause {φ : CNF ℕ} {i : ℕ} (hi : i < φ.length) : + CellBound φ (Layout.center (φ.distinctVars.length + i)) := by + have hn : φ.distinctVars.length + i < (SAT.incidenceGraph φ).1.length := by + simp only [SAT.length_vertices_incidenceGraph, CNF.length_distinctVars] + omega + simp only [CellBound, Layout.center, Nat.cast_add] + omega + +private theorem crossover_variable_bounds : ∀ v, + 20 ≤ (PairedCrossover.variablePosition v).1 ∧ (PairedCrossover.variablePosition v).1 ≤ 220 ∧ + 20 ≤ (PairedCrossover.variablePosition v).2 ∧ (PairedCrossover.variablePosition v).2 ≤ 220 := + by decide +kernel + +private theorem crossover_clause_bounds : ∀ p ∈ PairedCrossover.clausePositions, + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := by decide +kernel + +private theorem crossover_core_bounds : ∀ e ∈ PairedCrossover.incidenceRoutes, ∀ p ∈ e.2.2, + 20 ≤ p.1 ∧ p.1 ≤ 220 ∧ 20 ≤ p.2 ∧ p.2 ≤ 220 := by decide +kernel + +private theorem outputBound_variable {φ : CNF ℕ} {v : Variable} (hv : CellBound φ v.1) : + OutputBound φ (variablePosition v) := by + apply outputBound_add hv + cases h : v.2 with + | inl w => + have hw := PairedPorts.variable_bounds w.1 w.2 + simp only [Sum.elim_inl] + omega + | inr w => + have hw := crossover_variable_bounds w + simp only [Sum.elim_inr] + omega + +private theorem outputBound_center {φ : CNF ℕ} {p : Point} (hp : CellBound φ p) : + OutputBound φ (center p) := outputBound_add hp (by norm_num) + +private theorem outputBound_boundary {φ : CNF ℕ} {p : Point} (hp : CellBound φ p) + (s : Fin 4) (lane : Fin 2) : OutputBound φ (boundary p s lane) := by + unfold boundary + apply outputBound_add hp + fin_cases s <;> fin_cases lane <;> decide + +private theorem outputBound_access {φ : CNF ℕ} (sites : List Point) {p q a : Point} + (hp : CellBound φ p) (lane : Fin 2) (ha : a ∈ access sites p q lane) : + OutputBound φ a := by + simp only [access, List.mem_cons, List.not_mem_nil, or_false] at ha + rcases ha with rfl | rfl + · exact outputBound_boundary hp _ _ + · exact outputBound_variable hp + +private theorem lastLiteral_bounds {φ : CNF ℕ} {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) (sites : List Point) : + OutputBound φ (variablePosition (lastLiteral sites (occurrencePath φ o) o.2.2.2).1.1) ∧ + ∀ a ∈ (lastLiteral sites (occurrencePath φ o) o.2.2.2).2, OutputBound φ a := by + let r := occurrencePath φ o + have hlen : 2 ≤ r.length := PlanarCompile.occurrencePath_length_ge_two ho + have hp : CellBound φ (r.getD (r.length - 2) (0, 0)) := by + apply cellBound_occurrencePath ho + rw [List.getD_eq_getElem _ _ (show r.length - 2 < r.length by omega)] + exact List.getElem_mem _ + have hq : CellBound φ (r.getLastD (0, 0)) := by + rw [List.getLastD_eq_getLast?, (PlanarCompile.occurrencePath_endpoints ho).2, + Option.getD_some] + exact cellBound_clause (Cslib.Logic.PL.mem_occurrences.mp ho).1 + constructor + · exact outputBound_variable hp + · intro a ha + simp only [lastLiteral, List.mem_cons, List.not_mem_nil, or_false] at ha + rcases ha with rfl | rfl | rfl | rfl + · exact outputBound_center hq + · apply outputBound_add hq + generalize side (r.getLastD (0, 0)) (r.getD (r.length - 2) (0, 0)) = s + fin_cases s <;> decide + · exact outputBound_boundary hq _ _ + · exact outputBound_variable hp + +private def ClauseBound (φ : CNF ℕ) (pc : PlacedClause) : Prop := + OutputBound φ pc.2.1 ∧ + (∀ l ∈ pc.1, OutputBound φ (variablePosition l.1)) ∧ + ∀ r ∈ pc.2.2, ∀ a ∈ r, OutputBound φ a + + +private theorem originalClauses_bounds (φ : CNF ℕ) (sites : List Point) : + ∀ pc ∈ originalClauses φ sites, ClauseBound φ pc := by + intro pc hpc + obtain ⟨ci, hci, rfl⟩ := List.mem_map.mp hpc + refine ⟨outputBound_center (cellBound_clause (List.mem_zipIdx' hci).1), ?_, ?_⟩ + · intro l hl + obtain ⟨lr, hlr, rfl⟩ := List.mem_map.mp hl + obtain ⟨lj, hlj, rfl⟩ := List.mem_map.mp hlr + have ho : (ci.2, lj.2, lj.1) ∈ Cslib.Logic.PL.occurrences φ := + List.mem_flatMap.mpr ⟨ci, hci, List.mem_map.mpr ⟨lj, hlj, rfl⟩⟩ + exact (lastLiteral_bounds ho sites).1 + · intro r hr a ha + obtain ⟨lr, hlr, rfl⟩ := List.mem_map.mp hr + obtain ⟨lj, hlj, rfl⟩ := List.mem_map.mp hlr + have ho : (ci.2, lj.2, lj.1) ∈ Cslib.Logic.PL.occurrences φ := + List.mem_flatMap.mpr ⟨ci, hci, List.mem_map.mpr ⟨lj, hlj, rfl⟩⟩ + exact (lastLiteral_bounds ho sites).2 a ha + +private theorem ordinaryClauses_bounds {φ : CNF ℕ} {p : Point} (hp : CellBound φ p) : + ∀ pc ∈ ordinaryClauses p, ClauseBound φ pc := by + intro pc hpc + obtain ⟨ci, hci, rfl⟩ := List.mem_map.mp hpc + refine ⟨?_, ?_, ?_⟩ + · apply outputBound_add hp + have hi : ci.2 < OrdinaryRing.clausePositions.length := by + rw [OrdinaryRing.drawing_sizes.1] + exact (OrdinaryRing.formula_sizes.1 ▸ (List.mem_zipIdx' hci).1) + have hb := OrdinaryRing.clause_bounds _ (List.getElem_mem hi) + rw [List.getD_eq_getElem _ _ hi] + omega + · intro l hl + obtain ⟨v, _, rfl⟩ := List.mem_map.mp hl + exact outputBound_variable hp + · intro r hr a ha + obtain ⟨e, he, rfl⟩ := List.mem_map.mp hr + obtain ⟨b, hb, rfl⟩ := List.mem_map.mp ha + rw [add_comm] + apply outputBound_add hp + have h := OrdinaryRing.core_bounds e (List.mem_filter.mp he).1 b hb + omega + +private theorem crossingClauses_bounds {φ : CNF ℕ} {p : Point} (hp : CellBound φ p) : + ∀ pc ∈ crossingClauses p, ClauseBound φ pc := by + intro pc hpc + obtain ⟨ci, hci, rfl⟩ := List.mem_map.mp hpc + refine ⟨?_, ?_, ?_⟩ + · apply outputBound_add hp + have hi : ci.2 < PairedCrossover.clausePositions.length := by + rw [show PairedCrossover.clausePositions.length = 78 by decide +kernel] + exact (PairedCrossover.length_formula ▸ (List.mem_zipIdx' hci).1) + have hb := crossover_clause_bounds _ (List.getElem_mem hi) + rw [List.getD_eq_getElem _ _ hi] + omega + · intro l hl + obtain ⟨v, _, rfl⟩ := List.mem_map.mp hl + exact outputBound_variable hp + · intro r hr a ha + obtain ⟨e, he, rfl⟩ := List.mem_map.mp hr + obtain ⟨b, hb, rfl⟩ := List.mem_map.mp ha + rw [add_comm] + apply outputBound_add hp + have h := crossover_core_bounds e (List.mem_filter.mp he).1 b hb + omega + + +private theorem link_bounds {φ : CNF ℕ} (sites : List Point) (lane : Fin 2) {p q : Point} + (hp : CellBound φ p) (hq : CellBound φ q) : ClauseBound φ (link sites p q lane) := by + have hvp := outputBound_variable (v := signal sites p q lane) hp + have hvq := outputBound_variable (v := signal sites q p lane) hq + refine ⟨outputBound_boundary hp _ _, ?_, ?_⟩ + · intro l hl + by_cases h : lane = 0 <;> + simp only [link, h, ite_true, ite_false, List.mem_cons, + List.not_mem_nil, or_false] at hl hvp hvq <;> + rcases hl with rfl | rfl <;> assumption + · intro r hr a ha + by_cases h : lane = 0 <;> + simp only [link, h, ite_true, ite_false, List.mem_cons, + List.not_mem_nil, or_false] at hr <;> + rcases hr with rfl | rfl + all_goals + first + | exact outputBound_access sites hp _ ha + | exact outputBound_access sites hq _ ha + +private theorem cellBound_ordinary {φ : CNF ℕ} {p : Point} (hp : p ∈ ordinaryCells φ) : + CellBound φ p := by + have hmem : p ∈ (paths φ).flatMap List.dropLast ∧ p ∉ crossingSites φ := by + simpa [ordinaryCells, mem_selectDistinct_replicate] using hp + obtain ⟨r, hr, hp⟩ := List.mem_flatMap.mp hmem.1 + exact cellBound_path hr ((List.dropLast_subset _) hp) + +private theorem placedClauses_bounds (φ : CNF ℕ) : + ∀ pc ∈ placedClauses φ, ClauseBound φ pc := by + intro pc hpc + simp only [placedClauses, List.mem_append, List.mem_flatMap] at hpc + rcases hpc with ((hpc | ⟨p, hp, hpc⟩) | ⟨p, hp, hpc⟩) | ⟨o, ho, hpc⟩ + · exact originalClauses_bounds φ _ pc hpc + · exact ordinaryClauses_bounds (cellBound_ordinary hp) pc hpc + · exact crossingClauses_bounds (cellBound_crossing hp) pc hpc + · obtain ⟨e, he, hpc⟩ := List.mem_flatMap.mp hpc + have he' := List.of_mem_zip he + have hp := cellBound_occurrencePath ho ((List.dropLast_subset _) he'.1) + have hq := cellBound_occurrencePath ho + ((List.dropLast_subset _) (List.mem_of_mem_tail he'.2)) + simp only [List.mem_cons, List.not_mem_nil, or_false] at hpc + rcases hpc with rfl | rfl <;> exact link_bounds _ _ hp hq + +/-- Every actual vertex and route point lies in the explicit nonnegative grid rectangle. +This quantitative statement does not assert geometric separation. -/ +theorem drawing_fits (φ : CNF ℕ) : + Graph.drawingFits (positions φ, routes φ) (gridWidth φ) (gridHeight φ) := by + constructor + · intro p hp + rcases List.mem_append.mp hp with hp | hp + · obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hp + obtain ⟨c, hc, b, hv⟩ := CNF.mem_vars.mp (CNF.mem_distinctVars.mp hv) + obtain ⟨pc, hpc, rfl⟩ := List.mem_map.mp hc + exact (placedClauses_bounds φ pc hpc).2.1 (v, b) hv + · obtain ⟨pc, hpc, rfl⟩ := List.mem_map.mp hp + exact (placedClauses_bounds φ pc hpc).1 + · intro r hr p hp + obtain ⟨pc, hpc, hr⟩ := List.mem_flatMap.mp hr + exact (placedClauses_bounds φ pc hpc).2.2 r hr p hp + +/-- The horizontal grid side is linear in source incidence size. -/ +theorem gridWidth_le (φ : CNF ℕ) : + gridWidth φ ≤ 1920 * (φ.numVars + φ.length + φ.vars.length + 1) := + PlanarCompile.gridWidth_le φ + +/-- The vertical grid side is linear in source incidence size. -/ +theorem gridHeight_le (φ : CNF ℕ) : + gridHeight φ ≤ 1920 * (φ.numVars + φ.length + φ.vars.length + 1) := + PlanarCompile.gridHeight_le φ + +/-- The position list contains the distinct structured variables followed by all output clauses. -/ +theorem length_positions (φ : CNF ℕ) : + (positions φ).length = (rawFormula φ).numVars + (formula φ).length := by + simp [positions, CNF.length_distinctVars, formula, rawFormula] + +/-- The total number of output vertex positions is explicitly quadratic. -/ +theorem positions_length_le (φ : CNF ℕ) : + (positions φ).length ≤ 15000 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by + have hv := (rawFormula φ).numVars_le_length_vars + have hf := length_formula_le φ + have hl := length_vars_formula_le φ + rw [length_positions] + rw [length_vars_rawFormula, ← length_vars_formula] at hv + omega + +private theorem cost_signal_position (sites : List Point) (p q : Point) (l : Fin 2) : + variablePosition (signal sites p q l) = + cellOrigin 120 p + PairedPorts.variablePosition (side p q) l := by + simp only [signal, port, variablePosition] + split_ifs <;> rfl + +/-- Actual accesses are translations of the fixed paired-port accesses. -/ +private theorem cost_access_eq_translate (sites : List Point) (p q : Point) (l : Fin 2) : + access sites p q l = translate (cellOrigin 120 p) (PairedPorts.accessRoute (side p q) l) := by + rw [access, cost_signal_position] + simp only [boundary, PairedPorts.accessRoute, translate, List.map_cons, List.map_nil] + simp only [add_comm] + +private def finalLocalRoute (s : Fin 4) : Route := + [(120, 120), PairedPorts.spokeElbow s, PairedPorts.boundaryPort s (PairedPorts.spokeLane s), + cellOrigin 120 (PairedPorts.neighbor s) + + PairedPorts.variablePosition (PairedPorts.oppositeSide s) (PairedPorts.spokeLane s)] + +private theorem finalRoute_eq_translate (sites : List Point) {p q : Point} + (h : Orthogonal.length p q = 1) : + [center q, cellOrigin 120 q + PairedPorts.spokeElbow (side q p), + boundary q (side q p) (PairedPorts.spokeLane (side q p)), + variablePosition (signal sites p q (PairedPorts.spokeLane (side q p)))] = + translate (cellOrigin 120 q) (finalLocalRoute (side q p)) := by + rw [cost_signal_position] + have hrev : Orthogonal.length q p = 1 := by + simp only [Orthogonal.length] at h ⊢ + omega + rcases q with ⟨x, y⟩ + rcases PlanarCompile.unit_neighbor_cases hrev with rfl | rfl | rfl | rfl <;> + simp [translate, finalLocalRoute, side, PlanarCompile.side, center, PlanarCompile.center, + boundary, cellOrigin, PairedPorts.neighbor, PairedPorts.oppositeSide, + PairedPorts.variablePosition, PairedPorts.boundaryPort, PairedPorts.spokeLane, + PairedPorts.spokeElbow, PairedPorts.offset, Prod.ext_iff] <;> omega + +private theorem original_route_count (φ : CNF ℕ) (sites : List Point) : + ((originalClauses φ sites).map fun c => c.2.2.length).sum = φ.vars.length := by + rw [CNF.length_vars] + simp only [originalClauses, List.map_map, Function.comp_def, List.length_map, + List.length_zipIdx] + change ((φ.zipIdx).map (List.length ∘ Prod.fst)).sum = (φ.map List.length).sum + rw [← List.map_map, List.zipIdx_map_fst] + +private theorem original_route_cost (φ : CNF ℕ) (sites : List Point) (cost : Route → ℕ) + (k : ℕ) (h : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + cost (lastLiteral sites (occurrencePath φ o) o.2.2.2).2 = k) : + ((originalClauses φ sites).map fun c => (c.2.2.map cost).sum).sum = k * φ.vars.length := by + have hm : (originalClauses φ sites).map (fun c => (c.2.2.map cost).sum) = + (originalClauses φ sites).map (fun c => k * c.2.2.length) := by + apply List.map_congr_left + intro c hc + have he : c.2.2.map cost = c.2.2.map (fun _ => k) := by + apply List.map_congr_left + intro r hr + obtain ⟨ci, hci, rfl⟩ := List.mem_map.mp hc + obtain ⟨lr, hlr, rfl⟩ := List.mem_map.mp hr + obtain ⟨lj, hlj, rfl⟩ := List.mem_map.mp hlr + exact h (ci.2, lj.2, lj.1) + (List.mem_flatMap.mpr ⟨ci, hci, List.mem_map.mpr ⟨lj, hlj, rfl⟩⟩) + rw [he] + simp [Nat.mul_comm] + rw [hm, List.sum_map_mul_left, original_route_count] + +private theorem total_route_cost (φ : CNF ℕ) (cost : Route → ℕ) + (source ordinary crossing seam : ℕ) + (hs : ((originalClauses φ (crossingSites φ)).map fun c => (c.2.2.map cost).sum).sum = + source * φ.vars.length) + (ho : ∀ p, ((ordinaryClauses p).map fun c => (c.2.2.map cost).sum).sum = ordinary) + (hc : ∀ p, ((crossingClauses p).map fun c => (c.2.2.map cost).sum).sum = crossing) + (hl : ∀ r, ((links (crossingSites φ) r).map fun c => (c.2.2.map cost).sum).sum = + seam * (r.length - 2)) : + ((routes φ).map cost).sum = source * φ.vars.length + + ordinary * (ordinaryCells φ).length + crossing * (crossingSites φ).length + + seam * PlanarCompile.linkCount φ := by + simp only [routes, List.flatMap, List.map_flatten, List.sum_flatten, List.map_map, + Function.comp_def] + simp only [placedClauses, List.map_append, List.sum_append, List.flatMap, + List.map_flatten, List.sum_flatten, List.map_map, Function.comp_def] + simp_rw [hs, ho, hc, hl] + simp [PlanarCompile.linkCount, List.sum_map_mul_left, Nat.mul_comm, Nat.add_assoc] + +private theorem links_stat (sites : List Point) (r : Route) (cost : PlacedClause → ℕ) + (k : ℕ) (h : ∀ p q lane, cost (link sites p q lane) = k) : + ((links sites r).map cost).sum = (2 * k) * (r.length - 2) := by + have hm : (links sites r).map cost = (links sites r).map (fun _ => k) := by + apply List.map_congr_left + intro c hc + obtain ⟨e, _, hc⟩ := List.mem_flatMap.mp hc + simp only [List.mem_cons, List.not_mem_nil, or_false] at hc + rcases hc with rfl | rfl <;> exact h _ _ _ + rw [hm] + have he : ((links sites r).map fun _ => k).sum = k * (links sites r).length := by + simp [Nat.mul_comm] + rw [he, length_links, ← Nat.mul_assoc, Nat.mul_comm k 2] + +private theorem segments_translate (p : Point) (r : Route) : + (translate p r).consecutivePairs.length = r.consecutivePairs.length := by + simp [translate, List.consecutivePairs] + +private theorem ordinary_route_costs (p : Point) : + ((ordinaryClauses p).map fun c => c.2.2.length).sum = 16 ∧ + ((ordinaryClauses p).map fun c => + (c.2.2.map fun r => r.consecutivePairs.length).sum).sum = 16 ∧ + ((ordinaryClauses p).map fun c => (c.2.2.map wireLength).sum).sum = 800 := by + simp only [ordinaryClauses, List.map_map, Function.comp_def, List.length_map, + segments_translate, wireLength_translate] + decide +kernel + +private theorem crossing_route_costs (p : Point) : + ((crossingClauses p).map fun c => c.2.2.length).sum = 162 ∧ + ((crossingClauses p).map fun c => + (c.2.2.map fun r => r.consecutivePairs.length).sum).sum = 407 ∧ + ((crossingClauses p).map fun c => (c.2.2.map wireLength).sum).sum = 2192 := by + simp only [crossingClauses, List.map_map, Function.comp_def, List.length_map, + segments_translate, wireLength_translate] + decide +kernel + +private theorem access_wire_cost (sites : List Point) (p q : Point) (lane : Fin 2) : + wireLength (access sites p q lane) = 20 := by + rw [cost_access_eq_translate, wireLength_translate] + exact (PairedPorts.access_route_sizes _ _).2 + +private theorem lastLiteral_wire_cost (sites : List Point) (r : Route) (b : Bool) + (h : Orthogonal.length (r.getD (r.length - 2) (0, 0)) (r.getLastD (0, 0)) = 1) : + wireLength (lastLiteral sites r b).2 = 150 := by + dsimp only [lastLiteral] + rw [finalRoute_eq_translate sites h, wireLength_translate] + generalize side (r.getLastD (0, 0)) (r.getD (r.length - 2) (0, 0)) = s + fin_cases s <;> decide + +/-- Exact route count, one per output literal position. -/ +theorem length_routes (φ : CNF ℕ) : + (routes φ).length = φ.vars.length + 16 * (ordinaryCells φ).length + + 162 * (crossingSites φ).length + 4 * PlanarCompile.linkCount φ := by + have hs := original_route_cost φ (crossingSites φ) (fun _ => 1) 1 (fun _ _ => rfl) + have ho (p : Point) := (ordinary_route_costs p).1 + have hc (p : Point) := (crossing_route_costs p).1 + have hl (r : Route) := links_stat (crossingSites φ) r (fun c => c.2.2.length) 2 + (by intro p q lane; by_cases h : lane = 0 <;> simp [link, h]) + have h := total_route_cost φ (fun _ => 1) 1 16 162 4 (by simpa using hs) + (fun p => by simpa using ho p) (fun p => by simpa using hc p) + (fun r => by simpa using hl r) + simpa using h + +/-- Exact number of listed incidence-route segments: three per source literal, sixteen per +ordinary ring, 407 per paired crossover, and four per retained path gap. -/ +theorem drawingSegmentCount_eq (φ : CNF ℕ) : + Graph.drawingSegmentCount (positions φ, routes φ) = + 3 * φ.vars.length + 16 * (ordinaryCells φ).length + + 407 * (crossingSites φ).length + 4 * PlanarCompile.linkCount φ := by + have hs := original_route_cost φ (crossingSites φ) (fun r => r.consecutivePairs.length) 3 + (fun _ _ => rfl) + have ho (p : Point) := (ordinary_route_costs p).2.1 + have hc (p : Point) := (crossing_route_costs p).2.1 + have hl (r : Route) := links_stat (crossingSites φ) r + (fun c => (c.2.2.map fun r => r.consecutivePairs.length).sum) 2 + (by intro p q lane; by_cases h : lane = 0 <;> simp [link, h, access, List.consecutivePairs]) + exact total_route_cost φ _ 3 16 407 4 hs ho hc hl + +/-- Exact Manhattan wire length of the actual drawing data. -/ +theorem drawingWireLength_eq (φ : CNF ℕ) : + Graph.drawingWireLength (positions φ, routes φ) = + 150 * φ.vars.length + 800 * (ordinaryCells φ).length + + 2192 * (crossingSites φ).length + 80 * PlanarCompile.linkCount φ := by + have hs := original_route_cost φ (crossingSites φ) wireLength 150 + (fun o ho => lastLiteral_wire_cost _ _ _ (PlanarCompile.occurrencePath_last_unit ho)) + have ho (p : Point) := (ordinary_route_costs p).2.2 + have hc (p : Point) := (crossing_route_costs p).2.2 + have hl (r : Route) := links_stat (crossingSites φ) r + (fun c => (c.2.2.map wireLength).sum) 40 + (by intro p q lane; by_cases h : lane = 0 <;> simp [link, h, access_wire_cost]) + exact total_route_cost φ _ 150 800 2192 80 hs ho hc hl + +/-- The actual route count is quadratically bounded in source incidence size. -/ +theorem routes_length_le (φ : CNF ℕ) : + (routes φ).length ≤ 10000 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by + rw [length_routes, ← length_vars_formula] + exact length_vars_formula_le φ + +/-- The actual segment count is quadratically bounded in source incidence size. -/ +theorem drawingSegmentCount_le (φ : CNF ℕ) : + Graph.drawingSegmentCount (positions φ, routes φ) ≤ + 12000 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by + have ho := ordinaryCells_length_le φ + have hc := crossingSites_length_le φ + have hl := PlanarCompile.linkCount_le φ + rw [drawingSegmentCount_eq] + have hb : PlanarCompile.budget φ = 80 * (φ.numVars + φ.length) + 10 * φ.vars.length := by + simp [PlanarCompile.budget, SAT.length_vertices_incidenceGraph, SAT.length_edges_incidenceGraph] + rw [hb] at ho hl + nlinarith + +/-- The actual Manhattan wire length is quadratically bounded in source incidence size. -/ +theorem drawingWireLength_le (φ : CNF ℕ) : + Graph.drawingWireLength (positions φ, routes φ) ≤ + 400000 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by + have ho := ordinaryCells_length_le φ + have hc := crossingSites_length_le φ + have hl := PlanarCompile.linkCount_le φ + rw [drawingWireLength_eq] + have hb : PlanarCompile.budget φ = 80 * (φ.numVars + φ.length) + 10 * φ.vars.length := by + simp [PlanarCompile.budget, SAT.length_vertices_incidenceGraph, SAT.length_edges_incidenceGraph] + rw [hb] at ho hl + nlinarith + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedOccurrence.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedOccurrence.lean new file mode 100644 index 000000000..d29dbf33f --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedOccurrence.lean @@ -0,0 +1,331 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathGeometry +public import Complexity.Logics.Propositional.CNF.Compact.Relabel +import Mathlib.Data.List.Nodup +import Mathlib.Algebra.BigOperators.Ring.List +import Mathlib.Tactic.FinCases + +/-! +# Literal occurrences in the paired-port compiler + +Each directed path half has its own port on each lane. These keys are distinct across all +canonical source paths. The emitted external literals form a submultiset of those keys, so a +port receives at most one external occurrence. Each emitted ring contributes two internal +occurrences per port; the paired crossover contributes two at its ports and at most three at +its internal variables. + +## Main results + +* `count_vars_rawFormula_le_three`: the actual structured output respects the occurrence bound. +* `count_vars_formula_le_three`: injective compaction preserves that bound. + +## References + +This literal-position accounting for the paired compiler is original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- The directed side used by each half of every unit step. -/ +def pathPortKeys (r : Route) : List (Point × Fin 4) := + r.consecutivePairs.flatMap fun e => [(e.1, side e.1 e.2), (e.2, side e.2 e.1)] + +/-- The signal attached to one directed half on one lane. -/ +def portKeySignal (sites : List Point) (k : (Point × Fin 4) × Fin 2) : Variable := + port sites k.1.1 k.1.2 k.2 + +/-- Both lanes of each directed path half, including the unused halves at the final clause. -/ +def pathPortSignals (sites : List Point) (r : Route) : List Variable := + ((pathPortKeys r).product [0, 1]).map (portKeySignal sites) + +private theorem portKeySignal_injective (sites : List Point) : + Function.Injective (portKeySignal sites) := by + rintro ⟨⟨p, s⟩, l⟩ ⟨⟨q, t⟩, k⟩ h + have hcell : p = q := congrArg Prod.fst h + subst q + by_cases hp : p ∈ sites + · have he : s = t ∧ l = k := by + simpa [portKeySignal, port, hp, PairedCrossover.terminal] using h + rcases he with ⟨rfl, rfl⟩ + rfl + · have he : s = t ∧ l = k := by + simpa [portKeySignal, port, hp] using h + rcases he with ⟨rfl, rfl⟩ + rfl + +private theorem pathPortSignals_cons_cons (sites : List Point) (a b : Point) (r : Route) : + pathPortSignals sites (a :: b :: r) = + [signal sites a b 0, signal sites a b 1, signal sites b a 0, signal sites b a 1] ++ + pathPortSignals sites (b :: r) := rfl + +private theorem links_cons_cons_cons (sites : List Point) (a b c : Point) (r : Route) : + links sites (a :: b :: c :: r) = + [link sites a b 0, link sites a b 1] ++ links sites (b :: c :: r) := rfl + +private theorem lastLiteral_cons_cons_cons (sites : List Point) (positive : Bool) + (a b c : Point) (r : Route) : + lastLiteral sites (a :: b :: c :: r) positive = + lastLiteral sites (b :: c :: r) positive := by + simp [lastLiteral] + +private theorem count_vars_append (φ ψ : CNF Variable) (v : Variable) : + (φ ++ ψ).vars.count v = φ.vars.count v + ψ.vars.count v := by + simp [CNF.vars] + +private theorem count_two_links (sites : List Point) (a b : Point) (v : Variable) : + (CNF.vars [(link sites a b 0).1, (link sites a b 1).1]).count v = + [signal sites a b 0, signal sites a b 1, signal sites b a 0, signal sites b a 1].count v := by + simp [link, CNF.vars, List.count_cons, Nat.add_comm, Nat.add_left_comm] + +private theorem count_last_pair_le (sites : List Point) (a b : Point) (l : Fin 2) (v : Variable) : + [signal sites a b l].count v ≤ (pathPortSignals sites [a, b]).count v := by + have hp : pathPortSignals sites [a, b] = + [signal sites a b 0, signal sites a b 1, signal sites b a 0, signal sites b a 1] := rfl + rw [hp] + have hl : l = 0 ∨ l = 1 := by fin_cases l <;> simp + rcases hl with rfl | rfl <;> simp only [List.count_cons, List.count_nil] <;> omega + +/-- External literals use only their actual path-half ports; unused clause halves overcount. -/ +theorem count_links_lastLiteral_le (sites : List Point) (positive : Bool) (r : Route) + (hlen : 2 ≤ r.length) (v : Variable) : + (CNF.vars ((links sites r).map Prod.fst)).count v + + [(lastLiteral sites r positive).1.1].count v ≤ (pathPortSignals sites r).count v := by + induction r with + | nil => simp at hlen + | cons a r ih => + cases r with + | nil => simp at hlen + | cons b r => + cases r with + | nil => + simpa [links, lastLiteral, List.consecutivePairs, CNF.vars] using + count_last_pair_le sites a b (PairedPorts.spokeLane (side b a)) v + | cons c r => + have ht := ih (by simp : 2 ≤ (b :: c :: r).length) + rw [links_cons_cons_cons, List.map_append, List.map_cons, List.map_cons, List.map_nil, + count_vars_append, lastLiteral_cons_cons_cons, + pathPortSignals_cons_cons, List.count_append, count_two_links] + omega + +/-- Both lanes of every directed port in all actual canonical paths. -/ +def allPathPortSignals (φ : CNF ℕ) : List Variable := + (paths φ).flatMap (pathPortSignals (crossingSites φ)) + +/-- Across all canonical paths each presented side/lane variable is distinct. -/ +theorem allPathPortSignals_nodup {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + (allPathPortSignals φ).Nodup := by + have hn : ((paths φ).flatMap pathPortKeys).Nodup := PlanarCompile.pathPorts_nodup hw hv + have heq : allPathPortSignals φ = + (((paths φ).flatMap pathPortKeys).product [0, 1]).map + (portKeySignal (crossingSites φ)) := by + change ((paths φ).flatMap fun r => + ((pathPortKeys r).product [0, 1]).map (portKeySignal (crossingSites φ))) = _ + simp [List.product, List.map_flatMap, List.flatMap_assoc] + rw [heq] + exact (hn.product (by decide)).map (portKeySignal_injective _) + +private theorem vars_originalClauses (φ : CNF ℕ) (sites : List Point) : + CNF.vars ((originalClauses φ sites).map Prod.fst) = + (Cslib.Logic.PL.occurrences φ).map + (fun o => (lastLiteral sites (occurrencePath φ o) o.2.2.2).1.1) := by + simp [originalClauses, Cslib.Logic.PL.occurrences, CNF.vars, List.map_flatten, + List.map_map, Function.comp_def, List.flatMap_def] + +private theorem count_external_blocks_le {φ : CNF ℕ} (sites : List Point) + (os : List (Occurrence ℕ)) + (hpaths : ∀ o ∈ os, 2 ≤ (occurrencePath φ o).length) (v : Variable) : + (CNF.vars ((os.flatMap fun o => links sites (occurrencePath φ o)).map Prod.fst)).count v + + (os.map (fun o => (lastLiteral sites (occurrencePath φ o) o.2.2.2).1.1)).count v ≤ + (os.flatMap fun o => pathPortSignals sites (occurrencePath φ o)).count v := by + induction os with + | nil => simp [CNF.vars] + | cons o os ih => + have ht := ih (fun a ha => hpaths a (List.mem_cons_of_mem _ ha)) + have ho := count_links_lastLiteral_le sites o.2.2.2 (occurrencePath φ o) + (hpaths o List.mem_cons_self) v + rw [List.flatMap_cons, List.map_append, count_vars_append, List.map_cons, + List.count_cons, List.flatMap_cons, List.count_append] + have hc : [(lastLiteral sites (occurrencePath φ o) o.2.2.2).1.1].count v = + if (lastLiteral sites (occurrencePath φ o) o.2.2.2).1.1 == v then 1 else 0 := by + simp [List.count_cons] + rw [hc] at ho + omega + +private theorem vars_append (φ ψ : CNF Variable) : (φ ++ ψ).vars = φ.vars ++ ψ.vars := by + simp [CNF.vars] + +private theorem vars_flatMap (ps : List Point) (f : Point → List PlacedClause) : + CNF.vars ((ps.flatMap f).map Prod.fst) = + ps.flatMap fun p => CNF.vars ((f p).map Prod.fst) := by + induction ps with + | nil => rfl + | cons p ps ih => rw [List.flatMap_cons, List.map_append, vars_append, ih]; rfl + +private theorem vars_ordinaryBlock (p : Point) : + CNF.vars ((ordinaryClauses p).map Prod.fst) = + OrdinaryRing.formula.vars.map (fun v => ((p, Sum.inl v) : Variable)) := by + have h : (ordinaryClauses p).map Prod.fst = + OrdinaryRing.formula.relabel (fun v => ((p, Sum.inl v) : Variable)) := by + simpa only [ordinaryClauses, CNF.relabel, List.map_map, Function.comp_def] using + congrArg (List.map (fun c : Clause OrdinaryRing.Variable => + c.relabel (fun v => ((p, Sum.inl v) : Variable)))) + (List.zipIdx_map_fst (l := OrdinaryRing.formula) 0) + rw [h, CNF.vars_relabel] + +private theorem vars_crossingBlock (p : Point) : + CNF.vars ((crossingClauses p).map Prod.fst) = + PairedCrossover.formula.vars.map (fun v => ((p, Sum.inr v) : Variable)) := by + have h : (crossingClauses p).map Prod.fst = + PairedCrossover.formula.relabel (fun v => ((p, Sum.inr v) : Variable)) := by + simpa only [crossingClauses, CNF.relabel, List.map_map, Function.comp_def] using + congrArg (List.map (fun c : Clause PairedCrossover.Variable => + c.relabel (fun v => ((p, Sum.inr v) : Variable)))) + (List.zipIdx_map_fst (l := PairedCrossover.formula) 0) + rw [h, CNF.vars_relabel] + +/-- Literal positions supplied by all emitted ordinary rings. -/ +def ordinarySignals (φ : CNF ℕ) : List Variable := + (ordinaryCells φ).flatMap fun p => + OrdinaryRing.formula.vars.map (fun v => ((p, Sum.inl v) : Variable)) + +/-- Literal positions supplied by all emitted paired crossovers. -/ +def crossingSignals (φ : CNF ℕ) : List Variable := + (crossingSites φ).flatMap fun p => + PairedCrossover.formula.vars.map (fun v => ((p, Sum.inr v) : Variable)) + +private theorem count_tagged {W : Type*} [DecidableEq W] (ls : List W) + (tag : W → ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) + (hinj : Function.Injective tag) (ps : List Point) (p : Point) (v : W) : + (ps.flatMap fun q => ls.map (fun w => ((q, tag w) : Variable))).count (p, tag v) = + ps.count p * ls.count v := by + have hone (q : Point) : + (ls.map (fun w => ((q, tag w) : Variable))).count (p, tag v) = + if q = p then ls.count v else 0 := by + by_cases h : q = p + · subst q + rw [ite_eq_left rfl] + exact List.count_map_of_injective ls (fun w => ((p, tag w) : Variable)) + (fun _ _ he => hinj (congrArg Prod.snd he)) v + · rw [ite_eq_right h, List.count_eq_zero] + rintro hm + obtain ⟨w, _, he⟩ := List.mem_map.mp hm + exact h (congrArg Prod.fst he) + induction ps with + | nil => simp + | cons q ps ih => + rw [List.flatMap_cons, List.count_append, hone, List.count_cons, ih] + by_cases h : q = p <;> simp [h, beq_iff_eq, Nat.add_mul, Nat.add_comm] + +private theorem count_ordinary_inl (φ : CNF ℕ) (p : Point) (v : Fin 4 × Fin 2) : + (ordinarySignals φ).count (p, Sum.inl v) = (ordinaryCells φ).count p * 2 := by + rw [ordinarySignals, count_tagged _ Sum.inl Sum.inl_injective, + OrdinaryRing.internal_occurrences] + +private theorem count_crossing_inr (φ : CNF ℕ) (p : Point) (v : PairedCrossover.Variable) : + (crossingSignals φ).count (p, Sum.inr v) = + (crossingSites φ).count p * PairedCrossover.formula.vars.count v := + count_tagged _ Sum.inr Sum.inr_injective _ _ _ + +private theorem count_ordinary_inr (φ : CNF ℕ) (p : Point) (v : PairedCrossover.Variable) : + (ordinarySignals φ).count (p, Sum.inr v) = 0 := by + rw [List.count_eq_zero] + simp [ordinarySignals] + +private theorem count_crossing_inl (φ : CNF ℕ) (p : Point) (v : Fin 4 × Fin 2) : + (crossingSignals φ).count (p, Sum.inl v) = 0 := by + rw [List.count_eq_zero] + simp [crossingSignals] + +/-- Count actual emitted literals by internal template literals and actual path-half ports. -/ +theorem count_vars_rawFormula_le (φ : CNF ℕ) (v : Variable) : + (rawFormula φ).vars.count v ≤ (ordinarySignals φ).count v + + (crossingSignals φ).count v + (allPathPortSignals φ).count v := by + have he := count_external_blocks_le (φ := φ) (crossingSites φ) + (Cslib.Logic.PL.occurrences φ) (fun _ => PlanarCompile.occurrencePath_length_ge_two) v + have heq : ((Cslib.Logic.PL.occurrences φ).flatMap fun o => + pathPortSignals (crossingSites φ) (occurrencePath φ o)) = allPathPortSignals φ := by + rw [allPathPortSignals, paths, ← PlanarCompile.occurrencePaths_eq_paths, List.flatMap_map] + rw [heq] at he + have ho : CNF.vars (((ordinaryCells φ).flatMap ordinaryClauses).map Prod.fst) = + ordinarySignals φ := by + rw [vars_flatMap] + simp_rw [vars_ordinaryBlock] + rfl + have hc : CNF.vars (((crossingSites φ).flatMap crossingClauses).map Prod.fst) = + crossingSignals φ := by + rw [vars_flatMap] + simp_rw [vars_crossingBlock] + rfl + simp only [rawFormula, placedClauses, List.map_append, count_vars_append, + vars_originalClauses, ho, hc] + omega + +private theorem count_allPathPortSignals_nonterminal {φ : CNF ℕ} (p : Point) + (v : PairedCrossover.Variable) (hv : ¬ ∃ s l, PairedCrossover.terminal s l = v) : + (allPathPortSignals φ).count (p, Sum.inr v) = 0 := by + rw [List.count_eq_zero] + intro hm + obtain ⟨r, _, hm⟩ := List.mem_flatMap.mp hm + obtain ⟨k, _, hk⟩ := List.mem_map.mp hm + have hs := congrArg Prod.snd hk + by_cases hp : k.1.1 ∈ crossingSites φ + · have ht : PairedCrossover.terminal k.1.2 k.2 = v := by + simpa [portKeySignal, port, hp] using hs + exact hv ⟨_, _, ht⟩ + · simp [portKeySignal, port, hp] at hs + +private theorem count_vars_pairedFormula : ∀ v, PairedCrossover.formula.vars.count v ≤ 3 := by + decide +kernel + +private theorem count_vars_pairedTerminal : + ∀ s l, PairedCrossover.formula.vars.count (PairedCrossover.terminal s l) = 2 := by + decide +kernel + +/-- Every actual output variable has at most three literal occurrences, including repetitions +inside a clause. No occurrence certificate is assumed for the output. -/ +theorem count_vars_rawFormula_le_three {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + ∀ v, (rawFormula φ).vars.count v ≤ 3 := by + rintro ⟨p, v⟩ + have hcompare := count_vars_rawFormula_le φ (p, v) + have hext := List.nodup_iff_count_le_one.mp (allPathPortSignals_nodup hw hv) (p, v) + cases v with + | inl v => + have hn : (ordinaryCells φ).count p ≤ 1 := List.nodup_iff_count_le_one.mp + (nodup_selectDistinct _ _ : (ordinaryCells φ).Nodup) p + rw [count_ordinary_inl, count_crossing_inl] at hcompare + omega + | inr v => + have hn : (crossingSites φ).count p ≤ 1 := + List.nodup_iff_count_le_one.mp (PlanarCompile.crossingSites_nodup φ) p + rw [count_ordinary_inr, count_crossing_inr] at hcompare + by_cases ht : ∃ s l, PairedCrossover.terminal s l = v + · obtain ⟨s, l, rfl⟩ := ht + rw [count_vars_pairedTerminal] at hcompare + omega + · rw [count_allPathPortSignals_nonterminal p v ht] at hcompare + have hlocal := count_vars_pairedFormula v + have hmul := Nat.mul_le_mul hn hlocal + omega + +/-- Injective compaction preserves the actual literal-occurrence bound. -/ +theorem count_vars_formula_le_three {φ : CNF ℕ} + (hw : ∀ c ∈ φ, c.length ≤ 3) (hv : ∀ v, φ.vars.count v ≤ 3) : + ∀ v, (formula φ).vars.count v ≤ 3 := + CNF.count_vars_compact_le (rawFormula φ) (count_vars_rawFormula_le_three hw hv) + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedPolyTime.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedPolyTime.lean new file mode 100644 index 000000000..f56c89a95 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedPolyTime.lean @@ -0,0 +1,375 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathPolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Finite +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Int.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Membership +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + +/-! +# Native polynomial-time computation of the paired-port compiler + +Original variable names and all integer coordinates use binary encodings. List positions and +compact ranks use unary counters. The paths, crossing-site selection, and occurrence lookup reuse +the original compiler's native polynomial-time implementations. Ordinary cells are selected from +the retained path points by filtering and first-occurrence selection. + +Only the local port tag has a fixed finite encoding: an ordinary side/lane pair or a paired +crossover variable. Template tables and all finite dispatches are fixed independently of the +input formula. Neither the input formulas nor their coordinate or variable domains are finite. + +## Main definitions + +* `encVariable`: binary cell coordinates and a fixed finite local tag. +* `encPlacedClause`: the emitted clause, position, and literal incidence routes. + +## Main results + +* `computableInPolyTime_ordinaryCells`: polynomial-time selection of occupied ordinary cells. +* `computableInPolyTime_placedClauses`, `computableInPolyTime_rawFormula`: actual compiler records. +* `computableInPolyTime_formula`: the compact formula under binary CNF encodings. +* `computableInPolyTime_positions`, `computableInPolyTime_routes`: its binary geometric data. + +## References + +The compiler is the construction defined in `PairedCompile`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal Turing.MultiTapeTM + +/-- Binary cell coordinates together with a fixed finite ordinary-port or crossover tag. -/ +noncomputable def encVariable : Variable ↪ List Bool := + encPair encPoint (encFinite ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) + +/-- A clause and its binary position and incidence-route data. -/ +noncomputable def encPlacedClause : PlacedClause ↪ List Bool := + encPair (encList (encPair encVariable encBool)) (encPair encPoint (encList encRoute)) + +private theorem computable_natCast : + ComputableInPolyTime (fun n : ℕ => (n : ℤ)) encUnary encIntBin := + .of_tidy (ComputableTidilyInPolyTime.intBin_natCast.comp + ComputableTidilyInPolyTime.unary_to_binary) + +attribute [local fun_prop] computable_natCast ComputableInPolyTime.tidy + +private theorem computable_layoutCenter : + ComputableInPolyTime Layout.center encUnary encPoint := by + refine .of_tidy ?_ + unfold Layout.center encPoint + fun_prop + +private theorem computable_side : + ComputableInPolyTime (fun p : Point × Point => side p.1 p.2) + (encPair encPoint encPoint) (encFinite (Fin 4)) := by + refine .of_tidy ?_ + unfold side PlanarCompile.side encPoint + fun_prop + +private theorem computable_cellOrigin : + ComputableInPolyTime (cellOrigin 120) encPoint encPoint := by + refine .of_tidy ?_ + unfold cellOrigin encPoint + fun_prop + +private theorem computable_center : ComputableInPolyTime center encPoint encPoint := by + refine .of_tidy ?_ + change ComputableTidilyInPolyTime (fun p : Point => + (240 * p.1 + 120, 240 * p.2 + 120)) encPoint encPoint + unfold encPoint + fun_prop + +private theorem computable_translate : + ComputableInPolyTime (fun p : Point × Route => translate p.1 p.2) + (encPair encPoint encRoute) encRoute := by + refine .of_tidy ?_ + change ComputableTidilyInPolyTime (fun p : Point × Route => + p.2.map fun q => (q.1 + p.1.1, q.2 + p.1.2)) _ _ + unfold encPoint encRoute + fun_prop + +private theorem computable_pointAdd : + ComputableInPolyTime (fun p : Point × Point => p.1 + p.2) + (encPair encPoint encPoint) encPoint := by + refine .of_tidy ?_ + change ComputableTidilyInPolyTime (fun p : Point × Point => + (p.1.1 + p.2.1, p.1.2 + p.2.2)) _ _ + unfold encPoint + fun_prop + +private theorem computable_dropLast : + ComputableInPolyTime (List.dropLast (α := Point)) encRoute encRoute := by + refine .of_tidy ?_ + change ComputableTidilyInPolyTime (fun r : Route => r.dropLast) encRoute encRoute + simp only [List.dropLast_eq_take] + unfold encRoute + fun_prop + +attribute [local fun_prop] computable_layoutCenter computable_side computable_cellOrigin + computable_center computable_translate computable_pointAdd computable_dropLast + +private theorem computable_distinctPoints : + ComputableInPolyTime (fun l : Route => selectDistinct l (List.replicate l.length true)) + encRoute encRoute := by + have hrep : ComputableTidilyInPolyTime (fun n => List.replicate n true) encUnary encBits := + ComputableTidilyInPolyTime.of_linear (.of_eq fun _ => rfl) + refine .of_tidy ?_ + unfold selectDistinct encRoute + fun_prop + +/-- Ordinary cells are selected from polynomial-size retained paths, without enumerating a +coordinate box or a domain depending on the numeric magnitude of an input label. -/ +@[fun_prop] +theorem computableInPolyTime_ordinaryCells : + ComputableInPolyTime ordinaryCells encCNF encRoute := by + have hp := PlanarCompile.computableInPolyTime_paths.tidy + have hs := PlanarCompile.computableInPolyTime_crossingSites.tidy + have hd := computable_dropLast.tidy.comp + (ComputableTidilyInPolyTime.snd encCNF encRoute) + have hl := ComputableTidilyInPolyTime.flatMapWithInput + (f := fun (_ : CNF ℕ) (r : Route) => r.dropLast) (encB := encPoint) hp hd + have ht : ComputableTidilyInPolyTime (fun p : CNF ℕ × Point => + decide (p.2 ∉ crossingSites p.1)) (encPair encCNF encPoint) encBool := + .decide_not (.decide_mem (hs.comp (.fst _ _)) (.snd _ _)) + have ho : ComputableTidilyInPolyTime (fun φ : CNF ℕ => + ((paths φ).flatMap List.dropLast).filter fun p => decide (p ∉ crossingSites φ)) + encCNF encRoute := .filterWithInput hl ht + change ComputableInPolyTime (fun φ => ordinaryCells φ) _ _ + simpa only [ordinaryCells, Function.comp_def] using + computable_distinctPoints.comp (.of_tidy ho) + +private theorem computable_port : + ComputableInPolyTime (fun p : List Point × Point × Fin 4 × Fin 2 => + port p.1 p.2.1 p.2.2.1 p.2.2.2) + (encPair encRoute (encPair encPoint + (encPair (encFinite (Fin 4)) (encFinite (Fin 2))))) encVariable := by + have hi : ComputableTidilyInPolyTime + (fun p : Fin 4 × Fin 2 => (Sum.inl p : (Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) + (encPair (encFinite (Fin 4)) (encFinite (Fin 2))) + (encFinite ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) := .of_finite _ + have ht : ComputableTidilyInPolyTime + (fun p : Fin 4 × Fin 2 => + (Sum.inr (PairedCrossover.terminal p.1 p.2) : (Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) + (encPair (encFinite (Fin 4)) (encFinite (Fin 2))) + (encFinite ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) := .of_finite _ + let input := encPair encRoute (encPair encPoint + (encPair (encFinite (Fin 4)) (encFinite (Fin 2)))) + have hs : ComputableTidilyInPolyTime + (fun p : List Point × Point × Fin 4 × Fin 2 => p.1) input encRoute := .fst _ _ + have hp : ComputableTidilyInPolyTime + (fun p : List Point × Point × Fin 4 × Fin 2 => p.2.1) input encPoint := by + unfold input + fun_prop + have ha : ComputableTidilyInPolyTime + (fun p : List Point × Point × Fin 4 × Fin 2 => (p.2.2.1, p.2.2.2)) input + (encPair (encFinite (Fin 4)) (encFinite (Fin 2))) := by + unfold input + fun_prop + have hm := ComputableTidilyInPolyTime.decide_mem hs hp + refine .of_tidy ?_ + exact hp.prod (hm.ite_decide (ht.comp ha) (hi.comp ha)) + +attribute [local fun_prop] computable_port + +private theorem computable_signal : + ComputableInPolyTime (fun p : List Point × Point × Point × Fin 2 => + signal p.1 p.2.1 p.2.2.1 p.2.2.2) + (encPair encRoute (encPair encPoint (encPair encPoint (encFinite (Fin 2))))) + encVariable := by + refine .of_tidy ?_ + unfold signal + fun_prop + +private theorem computable_variablePosition : + ComputableInPolyTime variablePosition encVariable encPoint := by + have hl : ComputableTidilyInPolyTime + (fun v : (Fin 4 × Fin 2) ⊕ PairedCrossover.Variable => + v.elim (fun p => PairedPorts.variablePosition p.1 p.2) PairedCrossover.variablePosition) + (encFinite ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) encPoint := .of_finite _ + refine .of_tidy ?_ + unfold variablePosition encVariable + fun_prop + +private theorem computable_boundary : + ComputableInPolyTime (fun p : Point × Fin 4 × Fin 2 => boundary p.1 p.2.1 p.2.2) + (encPair encPoint (encPair (encFinite (Fin 4)) (encFinite (Fin 2)))) encPoint := by + have hb : ComputableTidilyInPolyTime + (fun p : Fin 4 × Fin 2 => PairedPorts.boundaryPort p.1 p.2) + (encPair (encFinite (Fin 4)) (encFinite (Fin 2))) encPoint := .of_finite _ + refine .of_tidy ?_ + unfold boundary + fun_prop + +attribute [local fun_prop] computable_signal computable_variablePosition computable_boundary + +private theorem computable_access : + ComputableInPolyTime (fun p : List Point × Point × Point × Fin 2 => + access p.1 p.2.1 p.2.2.1 p.2.2.2) + (encPair encRoute (encPair encPoint (encPair encPoint (encFinite (Fin 2))))) encRoute := by + refine .of_tidy ?_ + unfold access encRoute + fun_prop + +attribute [local fun_prop] computable_access + +private theorem computable_link : + ComputableInPolyTime (fun p : List Point × Point × Point × Fin 2 => + link p.1 p.2.1 p.2.2.1 p.2.2.2) + (encPair encRoute (encPair encPoint (encPair encPoint (encFinite (Fin 2))))) + encPlacedClause := by + refine .of_tidy ?_ + dsimp only [link] + unfold encPlacedClause + fun_prop + +attribute [local fun_prop] computable_link + +private theorem computable_links : + ComputableInPolyTime (fun p : List Point × Route => links p.1 p.2) + (encPair encRoute encRoute) (encList encPlacedClause) := by + refine .of_tidy ?_ + unfold links List.consecutivePairs + fun_prop + +private theorem computable_lastLiteral : + ComputableInPolyTime (fun p : List Point × Route × Bool => lastLiteral p.1 p.2.1 p.2.2) + (encPair encRoute (encPair encRoute encBool)) + (encPair (encPair encVariable encBool) encRoute) := by + have hl : ComputableTidilyInPolyTime PairedPorts.spokeLane + (encFinite (Fin 4)) (encFinite (Fin 2)) := .of_finite _ + have he : ComputableTidilyInPolyTime PairedPorts.spokeElbow + (encFinite (Fin 4)) encPoint := .of_finite _ + refine .of_tidy ?_ + dsimp only [lastLiteral] + simp only [List.getLastD_eq_getLast?, List.getLast?_eq_head?_reverse] + unfold encRoute + fun_prop + +attribute [local fun_prop] computable_links computable_lastLiteral + +private theorem computable_originalClauses : + ComputableInPolyTime (fun p : CNF ℕ × List Point => originalClauses p.1 p.2) + (encPair encCNF encRoute) (encList encPlacedClause) := by + have hp := PlanarCompile.computableInPolyTime_occurrencePath.tidy + refine .of_tidy ?_ + dsimp only [originalClauses] + unfold encPlacedClause encCNF + fun_prop + +private theorem computable_templateClauses {A : Type} [Finite A] + (tag : A → (Fin 4 × Fin 2) ⊕ PairedCrossover.Variable) (ψ : CNF A) + (clausePositions : List Point) (incidenceRoutes : List (ℕ × A × Route)) : + ComputableInPolyTime (fun p : Point => ψ.zipIdx.map fun (c, i) => + (c.relabel (fun v => (p, tag v)), + cellOrigin 120 p + clausePositions.getD i (0, 0), + (incidenceRoutes.filter fun e => e.1 == i).map fun e => + translate (cellOrigin 120 p) e.2.2)) encPoint (encList encPlacedClause) := by + classical + let : Fintype A := Fintype.ofFinite A + let rowEncoding := encPair (encList (encPair (encFinite A) encBool)) encUnary + have htag : ComputableTidilyInPolyTime tag (encFinite A) + (encFinite ((Fin 4 × Fin 2) ⊕ PairedCrossover.Variable)) := .of_finite _ + have hbody : ComputableInPolyTime (fun p : Point × (Clause A × ℕ) => + (p.2.1.relabel (fun v => (p.1, tag v)), + cellOrigin 120 p.1 + clausePositions.getD p.2.2 (0, 0), + (incidenceRoutes.filter fun e => e.1 == p.2.2).map fun e => + translate (cellOrigin 120 p.1) e.2.2)) + (encPair encPoint rowEncoding) encPlacedClause := by + have hpositions : ComputableTidilyInPolyTime + (fun _ : Point × (Clause A × ℕ) => clausePositions) + (encPair encPoint rowEncoding) encRoute := .const _ + have hposition : ComputableTidilyInPolyTime + (fun p : Point × (Clause A × ℕ) => clausePositions.getD p.2.2 (0, 0)) + (encPair encPoint rowEncoding) encPoint := by + unfold rowEncoding encRoute at * + fun_prop + refine .of_tidy ?_ + unfold rowEncoding encPlacedClause Clause.relabel encVariable Prod.map at * + fun_prop + have hlist : ComputableInPolyTime (fun _ : Point => ψ.zipIdx) + encPoint (encList rowEncoding) := .of_tidy (.const _) + simpa only [Function.comp_def] using ComputableInPolyTime.mapWithInput hlist hbody + +private theorem computable_ordinaryClauses : + ComputableInPolyTime ordinaryClauses encPoint (encList encPlacedClause) := by + exact computable_templateClauses Sum.inl OrdinaryRing.formula + OrdinaryRing.clausePositions OrdinaryRing.incidenceRoutes + +private theorem computable_crossingClauses : + ComputableInPolyTime crossingClauses encPoint (encList encPlacedClause) := by + exact computable_templateClauses Sum.inr PairedCrossover.formula + PairedCrossover.clausePositions PairedCrossover.incidenceRoutes + +attribute [local fun_prop] computable_originalClauses computable_ordinaryClauses + computable_crossingClauses + +/-- The complete actual list of emitted clauses and drawing records is native polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_placedClauses : + ComputableInPolyTime placedClauses encCNF (encList encPlacedClause) := by + have hs := PlanarCompile.computableInPolyTime_crossingSites.tidy + have hp := PlanarCompile.computableInPolyTime_occurrencePath.tidy + have ho := (Cslib.Logic.PL.computableInPolyTime_occurrences encNat).tidy + refine .of_tidy ?_ + change ComputableTidilyInPolyTime (fun φ => placedClauses φ) _ _ + dsimp only [placedClauses] + unfold encCNF Cslib.Logic.PL.encOccurrence at * + fun_prop + +/-- Projecting the actual records computes the structured output formula. -/ +@[fun_prop] +theorem computableInPolyTime_rawFormula : + ComputableInPolyTime rawFormula encCNF (encList (encList (encPair encVariable encBool))) := by + have hp := computableInPolyTime_placedClauses.tidy + refine .of_tidy ?_ + unfold rawFormula encPlacedClause at * + fun_prop + +/-- The actual compact formula compiler is native polynomial-time under binary CNF encodings. +Compact ranks are computed as unary counters and then encoded in binary. -/ +@[fun_prop] +theorem computableInPolyTime_formula : ComputableInPolyTime formula encCNF encCNF := by + have hu := (CNF.isPolyTime_compact encVariable).comp computableInPolyTime_rawFormula + have hi : ComputableInPolyTime (fun n : ℕ => n) encUnary encNat := + .of_tidy ComputableTidilyInPolyTime.unary_to_binary + have hr := (CNF.computableInPolyTime_relabel hi).comp hu + have he (ψ : CNF ℕ) : ψ.relabel (fun n : ℕ => n) = ψ := CNF.relabel_id ψ + change ComputableInPolyTime (fun φ => (rawFormula φ).compact) _ _ + simpa only [Function.comp_def, he, encCNF] using hr + +/-- All actual output vertex positions are computed in polynomial time with binary coordinates. -/ +@[fun_prop] +theorem computableInPolyTime_positions : ComputableInPolyTime positions encCNF encRoute := by + have hv := (CNF.computableInPolyTime_distinctVars encVariable).comp + computableInPolyTime_rawFormula + have hp := computableInPolyTime_placedClauses.tidy + refine .of_tidy ?_ + change ComputableTidilyInPolyTime (fun φ => positions φ) _ _ + dsimp only [positions] + unfold encPlacedClause encRoute at * + fun_prop + +/-- All actual clause-to-variable incidence routes are computed in polynomial time with binary +coordinates. -/ +@[fun_prop] +theorem computableInPolyTime_routes : + ComputableInPolyTime routes encCNF (encList encRoute) := by + have hp := computableInPolyTime_placedClauses.tidy + refine .of_tidy ?_ + unfold routes encPlacedClause at * + fun_prop + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSize.lean b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSize.lean new file mode 100644 index 000000000..3da7c4fd4 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/Planar/PairedSize.lean @@ -0,0 +1,225 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedCompile +public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds +import Mathlib.Algebra.BigOperators.Ring.List +import Mathlib.Data.List.GetD +import Mathlib.Tactic.Linarith + +/-! +# Exact sizes of the paired-port compiler + +The actual output contains eight clauses and sixteen literal positions per ordinary ring, +78 clauses and 162 literal positions per paired crossover, and two binary clauses per retained +path gap. Source literal repetitions are counted with multiplicity. These bounds apply to all +inputs, without a geometric validity assumption. + +## Main results + +* `ordinaryCells_length_le_retained`, `ordinaryCells_length_le`: occupied ordinary-cell bounds. +* `length_formula`, `length_vars_formula`: exact clause and literal accounting. +* `width_formula`: width-three inputs produce width-three outputs. +* `length_formula_le`, `length_vars_formula_le`: explicit quadratic output bounds. + +## References + +The unchanged path estimates are reused from `Planar.Size`. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ParsimoniousPlanar + +open Cslib.Logic.PL Cslib.Logic.PL.CNF Orthogonal + +/-- Selected crossings reuse the canonical quadratic cover. -/ +theorem crossingSites_length_le (φ : CNF ℕ) : + (crossingSites φ).length ≤ 2 * φ.vars.length ^ 2 := + PlanarCompile.crossingSites_length_le φ + +/-- Filtering and deduplication do not increase the number of retained path points. -/ +theorem ordinaryCells_length_le_retained (φ : CNF ℕ) : + (ordinaryCells φ).length ≤ ((paths φ).map fun r => r.dropLast.length).sum := by + unfold ordinaryCells + rw [length_selectDistinct_replicate] + exact (List.toFinset_card_le _).trans ((List.length_filter_le _ _).trans_eq + List.length_flatMap) + +/-- The ordinary-cell count is bounded by the canonical total path budget. -/ +theorem ordinaryCells_length_le (φ : CNF ℕ) : + (ordinaryCells φ).length ≤ φ.vars.length * (5 * PlanarCompile.budget φ + 1) := by + calc + _ ≤ ((paths φ).map fun r => r.dropLast.length).sum := ordinaryCells_length_le_retained φ + _ ≤ ((paths φ).map fun _ => 5 * PlanarCompile.budget φ + 1).sum := + List.sum_le_sum fun r hr => (by simp only [List.length_dropLast]; omega : + r.dropLast.length ≤ r.length).trans (PlanarCompile.path_length_le hr) + _ = _ := by simp [PlanarCompile.paths_length, Nat.mul_comm] + +/-- Original clauses keep their count. -/ +theorem length_originalClauses (φ : CNF ℕ) (sites : List Point) : + (originalClauses φ sites).length = φ.length := by simp [originalClauses] + +/-- One ordinary cell contributes eight clauses. -/ +theorem length_ordinaryClauses (p : Point) : (ordinaryClauses p).length = 8 := by + simp [ordinaryClauses, OrdinaryRing.formula_sizes.1] + +/-- One crossing contributes 78 clauses. -/ +theorem length_crossingClauses (p : Point) : (crossingClauses p).length = 78 := by + simp [crossingClauses, PairedCrossover.length_formula] + +/-- Each retained path gap contributes two clauses. -/ +theorem length_links (sites : List Point) (r : Route) : + (links sites r).length = 2 * (r.length - 2) := by + have hsum : (links sites r).length = 2 * r.dropLast.consecutivePairs.length := by + simp [links, List.length_flatMap, Nat.mul_comm] + rw [hsum] + simp only [List.consecutivePairs, List.length_zip, List.length_dropLast, List.length_tail] + omega + +/-- Exact clause count of the emitted structured formula. -/ +theorem length_rawFormula (φ : CNF ℕ) : + (rawFormula φ).length = φ.length + 8 * (ordinaryCells φ).length + + 78 * (crossingSites φ).length + 2 * PlanarCompile.linkCount φ := by + simp [rawFormula, placedClauses, List.length_flatMap, length_originalClauses, + length_ordinaryClauses, length_crossingClauses, length_links, PlanarCompile.linkCount, + List.sum_map_mul_left, Nat.mul_comm, Nat.add_assoc] + +/-- Compaction preserves the exact output clause count. -/ +theorem length_formula (φ : CNF ℕ) : + (formula φ).length = φ.length + 8 * (ordinaryCells φ).length + + 78 * (crossingSites φ).length + 2 * PlanarCompile.linkCount φ := by + simpa only [formula, CNF.length_compact] using length_rawFormula φ + +/-- Original clause records preserve their widths. -/ +theorem width_originalClauses {φ : CNF ℕ} {k : ℕ} + (h : ∀ c ∈ φ, c.length ≤ k) (sites : List Point) : + ∀ c ∈ originalClauses φ sites, c.1.length ≤ k := by + intro c hc + obtain ⟨⟨d, i⟩, hd, rfl⟩ := List.mem_map.mp hc + simpa using h d (List.fst_mem_of_mem_zipIdx hd) + +/-- Every ordinary ring clause is binary. -/ +theorem width_ordinaryClauses (p : Point) : + ∀ c ∈ ordinaryClauses p, c.1.length = 2 := by + intro c hc + obtain ⟨⟨d, i⟩, hd, rfl⟩ := List.mem_map.mp hc + simpa [Clause.relabel] using OrdinaryRing.formula_sizes.2 d + (List.fst_mem_of_mem_zipIdx hd) + +/-- Every crossover clause has width at most three. -/ +theorem width_crossingClauses (p : Point) : + ∀ c ∈ crossingClauses p, c.1.length ≤ 3 := by + intro c hc + obtain ⟨⟨d, i⟩, hd, rfl⟩ := List.mem_map.mp hc + simpa [Clause.relabel] using PairedCrossover.width_formula d + (List.fst_mem_of_mem_zipIdx hd) + +/-- Every seam clause is binary. -/ +theorem width_links (sites : List Point) (r : Route) : + ∀ c ∈ links sites r, c.1.length = 2 := by + intro c hc + obtain ⟨e, _, hc⟩ := List.mem_flatMap.mp hc + simp only [List.mem_cons, List.not_mem_nil, or_false] at hc + rcases hc with rfl | rfl <;> rfl + +/-- Width-three inputs produce structured formulas of width at most three. -/ +theorem width_rawFormula {φ : CNF ℕ} (h : ∀ c ∈ φ, c.length ≤ 3) : + ∀ c ∈ rawFormula φ, c.length ≤ 3 := by + intro c hc + obtain ⟨d, hd, rfl⟩ := List.mem_map.mp hc + simp only [placedClauses, List.mem_append, List.mem_flatMap] at hd + rcases hd with ((hd | ⟨p, _, hd⟩) | ⟨p, _, hd⟩) | ⟨o, _, hd⟩ + · exact width_originalClauses h _ d hd + · exact (width_ordinaryClauses p d hd).le.trans (by decide) + · exact width_crossingClauses p d hd + · exact (width_links _ _ d hd).le.trans (by decide) + +/-- Compaction preserves the width-three guarantee. -/ +theorem width_formula {φ : CNF ℕ} (h : ∀ c ∈ φ, c.length ≤ 3) : + ∀ c ∈ formula φ, c.length ≤ 3 := by + intro c hc + have hm : c.length ∈ (formula φ).map List.length := List.mem_map_of_mem hc + rw [formula, CNF.map_length_compact] at hm + obtain ⟨d, hd, he⟩ := List.mem_map.mp hm + rw [← he] + exact width_rawFormula h d hd + +private theorem original_literal_count (φ : CNF ℕ) (sites : List Point) : + ((originalClauses φ sites).map fun c => c.1.length).sum = φ.vars.length := by + rw [CNF.length_vars] + simp only [originalClauses, List.map_map, Function.comp_def, List.length_map, + List.length_zipIdx] + change ((φ.zipIdx).map (List.length ∘ Prod.fst)).sum = (φ.map List.length).sum + rw [← List.map_map, List.zipIdx_map_fst] + +private theorem ordinary_literal_count (p : Point) : + ((ordinaryClauses p).map fun c => c.1.length).sum = 16 := by + simp only [ordinaryClauses, List.map_map, Function.comp_def, Clause.relabel, List.length_map] + decide +kernel + +private theorem crossing_literal_count (p : Point) : + ((crossingClauses p).map fun c => c.1.length).sum = 162 := by + simp only [crossingClauses, List.map_map, Function.comp_def, Clause.relabel, List.length_map] + decide +kernel + +private theorem links_literal_count (sites : List Point) (r : Route) : + ((links sites r).map fun c => c.1.length).sum = 4 * (r.length - 2) := by + have hmap : (links sites r).map (fun c => c.1.length) = (links sites r).map (fun _ => 2) := + List.map_congr_left fun c hc => width_links sites r c hc + rw [hmap] + have hsum : ((links sites r).map fun _ => 2).sum = 2 * (links sites r).length := by + simp [Nat.mul_comm] + rw [hsum, length_links] + omega + +/-- Exact literal count, including repetitions and all ordinary ring ports. -/ +theorem length_vars_rawFormula (φ : CNF ℕ) : + (rawFormula φ).vars.length = φ.vars.length + 16 * (ordinaryCells φ).length + + 162 * (crossingSites φ).length + 4 * PlanarCompile.linkCount φ := by + rw [CNF.length_vars] + simp only [rawFormula, List.map_map] + change ((placedClauses φ).map fun c => c.1.length).sum = _ + simp only [placedClauses, List.map_append, List.sum_append, List.flatMap, + List.map_flatten, List.sum_flatten, List.map_map, Function.comp_def] + simp_rw [original_literal_count, ordinary_literal_count, crossing_literal_count, + links_literal_count] + simp [PlanarCompile.linkCount, List.sum_map_mul_left, Nat.mul_comm, Nat.add_assoc] + +/-- Compaction preserves the exact number of output literal occurrences. -/ +theorem length_vars_formula (φ : CNF ℕ) : + (formula φ).vars.length = φ.vars.length + 16 * (ordinaryCells φ).length + + 162 * (crossingSites φ).length + 4 * PlanarCompile.linkCount φ := by + simpa only [formula, CNF.length_vars, CNF.map_length_compact] using length_vars_rawFormula φ + +/-- A generous explicit quadratic clause bound in the source incidence size. -/ +theorem length_formula_le (φ : CNF ℕ) : + (formula φ).length ≤ 5000 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by + have ho := ordinaryCells_length_le φ + have hc := crossingSites_length_le φ + have hl := PlanarCompile.linkCount_le φ + rw [length_formula] + have hb : PlanarCompile.budget φ = 80 * (φ.numVars + φ.length) + 10 * φ.vars.length := by + simp [PlanarCompile.budget, SAT.length_vertices_incidenceGraph, SAT.length_edges_incidenceGraph] + rw [hb] at ho hl + nlinarith + +/-- A generous explicit quadratic bound on output literal occurrences. -/ +theorem length_vars_formula_le (φ : CNF ℕ) : + (formula φ).vars.length ≤ 10000 * (φ.numVars + φ.length + φ.vars.length + 1) ^ 2 := by + have ho := ordinaryCells_length_le φ + have hc := crossingSites_length_le φ + have hl := PlanarCompile.linkCount_le φ + rw [length_vars_formula] + have hb : PlanarCompile.budget φ = 80 * (φ.numVars + φ.length) + 10 * φ.vars.length := by + simp [PlanarCompile.budget, SAT.length_vertices_incidenceGraph, SAT.length_edges_incidenceGraph] + rw [hb] at ho hl + nlinarith + +end Cslib.Complexity.SAT.ParsimoniousPlanar diff --git a/ComplexityTests/PairedOccurrence.lean b/ComplexityTests/PairedOccurrence.lean new file mode 100644 index 000000000..ad36a5008 --- /dev/null +++ b/ComplexityTests/PairedOccurrence.lean @@ -0,0 +1,28 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedOccurrence + +/-! Repeated literals consume separate occurrence capacity in the actual paired compiler. -/ + +open Cslib.Logic.PL Cslib.Complexity.SAT.ParsimoniousPlanar + +private def repeated : CNF ℕ := [[(0, true), (0, true), (0, true)]] + +private theorem repeated_counts : ∀ v, repeated.vars.count v ≤ 3 := by + intro v + by_cases h : 0 = v + · subst v + decide + · simp [repeated, CNF.vars, h] + +example : repeated.vars.count 0 = 3 := by decide + +example (v : Variable) : (rawFormula repeated).vars.count v ≤ 3 := + count_vars_rawFormula_le_three (by decide) repeated_counts v + +example (v : ℕ) : (formula repeated).vars.count v ≤ 3 := + count_vars_formula_le_three (by decide) repeated_counts v diff --git a/ComplexityTests/PlanarBounds.lean b/ComplexityTests/PlanarBounds.lean index d16694e29..42c3e1302 100644 --- a/ComplexityTests/PlanarBounds.lean +++ b/ComplexityTests/PlanarBounds.lean @@ -4,11 +4,11 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Erik Demaine -/ -import Complexity.Computability.Complexity.Problems.SAT.Planar.Bounds +import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedBounds namespace ComplexityTests.PlanarBounds -open Cslib.Complexity Cslib.Complexity.SAT.PlanarCompile +open Cslib.Complexity Cslib.Complexity.SAT.ParsimoniousPlanar -- The same linear grid bounds apply to an ordinary name and a succinct large binary name. example : gridWidth [[(7, true)]] = 4080 := by decide +kernel @@ -24,13 +24,13 @@ example : Graph.drawingFits (positions [[(7, true)]], routes [[(7, true)]]) 4080 example : positions [[]] = [(1080, 1080)] := by decide +kernel example : Graph.drawingFits (positions [[]], routes [[]]) 2160 2160 := drawing_fits [[]] --- Exact costs for the actual nonempty unit-clause output, including its final two-segment route. -example : (routes [[(7, true)]]).length = 63 := by decide +kernel -example : Graph.drawingSegmentCount (positions [[(7, true)]], routes [[(7, true)]]) = 64 := +-- Exact costs for the actual nonempty unit-clause output, including its final three-segment route. +example : (routes [[(7, true)]]).length = 637 := by decide +kernel +example : Graph.drawingSegmentCount (positions [[(7, true)]], routes [[(7, true)]]) = 639 := by decide +kernel -example : Graph.drawingWireLength (positions [[(7, true)]], routes [[(7, true)]]) = 7680 := +example : Graph.drawingWireLength (positions [[(7, true)]], routes [[(7, true)]]) = 28230 := by decide +kernel -example : Graph.drawingWireLength (positions [[(7, false)]], routes [[(7, false)]]) = 7680 := +example : Graph.drawingWireLength (positions [[(7, false)]], routes [[(7, false)]]) = 28230 := by decide +kernel end ComplexityTests.PlanarBounds diff --git a/ComplexityTests/PlanarOccurrence.lean b/ComplexityTests/PlanarOccurrence.lean index 2fb70bdbf..448691f92 100644 --- a/ComplexityTests/PlanarOccurrence.lean +++ b/ComplexityTests/PlanarOccurrence.lean @@ -4,43 +4,18 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Erik Demaine -/ -import Complexity.Computability.Complexity.Problems.SAT.Planar.Occurrence +import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedOccurrence -/-! Literal-position counting regressions for actual planarization paths. -/ +/-! Literal-position regressions for the paired compiler and its shared canonical paths. -/ -open Cslib.Logic.PL Cslib.Logic.PL.CNF Cslib.Complexity.SAT.PlanarCompile Cslib.Orthogonal +open Cslib.Logic.PL Cslib.Logic.PL.CNF Cslib.Complexity.SAT.ParsimoniousPlanar +open Cslib.Complexity.SAT.PlanarCompile (occurrencePaths_eq_paths) private def repeated : CNF ℕ := [[(0, true), (0, false), (0, true)]] -example : (Cslib.Logic.PL.occurrences repeated).Nodup := Cslib.Logic.PL.occurrences_nodup _ - example : (Cslib.Logic.PL.occurrences repeated).map (occurrencePath repeated) = paths repeated := occurrencePaths_eq_paths _ -example : (pathSignals [] [(0, 0), (1, 0), (2, 0)]).count ((1, 0), none) = 2 := by decide - -example : (pathSignals [] [(0, 0), (1, 0), (2, 0)]).count ((0, 0), none) = 1 := by decide - -example : (pathSignals [(1, 0)] [(0, 0), (1, 0), (2, 0)]).count - ((1, 0), some (Crossover.splitTerminal 0)) = 1 := by decide - -example : (pathSignals [(1, 0)] [(0, 0), (1, 0), (2, 0)]).count - ((1, 0), some (Crossover.splitTerminal 1)) = 1 := by decide - -example : (pathSignals [(1, 0)] [(0, 0), (1, 0), (2, 0)]).count ((1, 0), none) = 0 := by decide - -example (positive : Bool) : - (CNF.vars ((links [] positive [(0, 0), (1, 0), (2, 0)]).map Prod.fst)).count ((1, 0), none) + - [(lastLiteral [] [(0, 0), (1, 0), (2, 0)] positive).1.1].count ((1, 0), none) ≤ 2 := by - simpa only [show (pathSignals [] [(0, 0), (1, 0), (2, 0)]).count ((1, 0), none) = 2 by decide] - using count_links_lastLiteral_le [] positive [(0, 0), (1, 0), (2, 0)] (by decide) ((1, 0), none) - -example (i : Fin 4) : Crossover.splitFormula.vars.count (Crossover.splitTerminal i) = 2 := - count_vars_splitTerminal i - -example (v : Crossover.SplitVariable) : Crossover.splitFormula.vars.count v ≤ 3 := - count_vars_splitFormula v - private theorem repeated_width : ∀ c ∈ repeated, c.length ≤ 3 := by intro c hc simp only [repeated, List.mem_singleton] at hc @@ -77,20 +52,6 @@ private theorem crossed_count : ∀ v, crossed.vars.count v ≤ 3 := by · rw [List.count_eq_zero.mpr hv] decide --- This actual compiler input selects a strict bridge/stem crossing at (10,42). -private theorem crossing_selected : (10, 42) ∈ crossingSites crossed := by decide +kernel - -example (i : Fin 4) : (allPathSignals crossed).count - ((10, 42), some (Crossover.splitTerminal i)) ≤ 1 := - count_allPathSignals_terminal_le_one crossed_width crossed_count _ i crossing_selected - -example (i : Fin 4) : (crossingSignals crossed).count - ((10, 42), some (Crossover.splitTerminal i)) = 2 := by - rw [count_crossingSignals_some, count_vars_splitTerminal] - have hpos := List.count_pos_iff.mpr crossing_selected - have hle := List.nodup_iff_count_le_one.mp (crossingSites_nodup crossed) (10, 42) - omega - example : ∀ v, (rawFormula crossed).vars.count v ≤ 3 := count_vars_rawFormula_le_three crossed_width crossed_count From 31bb8a291e6efcd6b02d2c405c3b5673a023c7bf Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Mon, 26 Oct 2026 13:34:01 -0400 Subject: [PATCH 9/9] chore: regenerate Complexity.lean Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Dvi6MU29zU9t8zYfeU51sB --- Complexity.lean | 1 - 1 file changed, 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 86a5cb666..8f0059f02 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -823,7 +823,6 @@ public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedOccu public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedPolyTime public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSemantics public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSize -public import Complexity.Computability.Complexity.Problems.SAT.Planar.PairedSemantics public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathAssignments public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathBounds public import Complexity.Computability.Complexity.Problems.SAT.Planar.PathData